From 55b128e5d4fac0dbd02929402d905585cc134e75 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 30 Dec 2014 11:13:39 +0100 Subject: [PATCH 001/260] Merge remote-tracking branch 'pyqtgraph-core/core' into embedding_pyqtgraph --- .gitignore | 61 + CHANGENLOG.md | 2 + INSTALL.md | 2 + LICENSE | 165 + Makefile | 40 + README.md | 56 + cfg_collection/FourCfg.xml | 108 + cfg_collection/ORTD_bigSource.xml | 295 + cfg_collection/wizard.xml | 29 + data_sources/fourier_rect.py | 91 + design/CoreDataStructure.asta | Bin 0 -> 17776 bytes design/PaPI.asta | Bin 0 -> 74250 bytes docs/Makefile | 177 + docs/conf.py | 337 + docs/index.rst | 23 + docs/make.bat | 242 + docs/modules.rst | 7 + docs/papi.data.dcore.rst | 22 + docs/papi.data.rst | 62 + docs/papi.gui.rst | 38 + docs/papi.plugin.rst | 17 + docs/papi.rst | 91 + docs/papi.tests.rst | 70 + docs/papi.ui.gui.rst | 18 + docs/papi.ui.rst | 17 + main.py | 43 + papi/ConsoleLog.py | 42 + papi/PapiEvent.py | 69 + papi/__init__.py | 1 + papi/constants.py | 99 + papi/core.py | 1068 ++ papi/data/DCore.py | 380 + papi/data/DGui.py | 36 + papi/data/DObject.py | 35 + papi/data/DOptionalData.py | 55 + papi/data/DParameter.py | 48 + papi/data/DPlugin.py | 405 + papi/data/DSignal.py | 39 + papi/data/__init__.py | 29 + papi/error_codes.py | 37 + papi/event/__init__.py | 10 + papi/event/data/DataBase.py | 35 + papi/event/data/NewBlock.py | 36 + papi/event/data/NewData.py | 36 + papi/event/data/NewParameter.py | 36 + papi/event/data/__init__.py | 6 + papi/event/event_base.py | 69 + papi/event/instruction/CloseProgram.py | 35 + papi/event/instruction/CreatePlugin.py | 35 + papi/event/instruction/InstructionBase.py | 35 + papi/event/instruction/PausePlugin.py | 35 + papi/event/instruction/ResumePlugin.py | 35 + papi/event/instruction/SetParameter.py | 35 + papi/event/instruction/StopPlugin.py | 36 + papi/event/instruction/Subscribe.py | 35 + papi/event/instruction/Unsubscribe.py | 35 + papi/event/instruction/__init__.py | 12 + papi/event/instruction/startPlugin.py | 35 + papi/event/status/Alive.py | 35 + papi/event/status/CheckAliveStatus.py | 35 + papi/event/status/JoinRequest.py | 35 + papi/event/status/PluginClosed.py | 35 + papi/event/status/PluginStopped.py | 35 + papi/event/status/StartFailed.py | 35 + papi/event/status/StartSuccessfull.py | 35 + papi/event/status/StatusBase.py | 35 + papi/event/status/__init__.py | 10 + papi/exceptions/__init__.py | 29 + papi/exceptions/block_exceptions.py | 50 + papi/gui/__init__.py | 29 + papi/gui/gui_api.py | 660 + papi/gui/gui_event_processing.py | 419 + papi/gui/plugin_api.py | 112 + papi/gui/qt_dev/__init__.py | 29 + papi/gui/qt_dev/add_pcp_subscriber.py | 180 + papi/gui/qt_dev/add_plugin.py | 180 + papi/gui/qt_dev/add_subscriber.py | 170 + papi/gui/qt_dev/gui_main.py | 332 + papi/gui/qt_dev/manager.py | 464 + papi/gui/qt_new/__init__.py | 29 + papi/gui/qt_new/create_plugin_dialog.py | 215 + papi/gui/qt_new/create_plugin_menu.py | 155 + papi/gui/qt_new/custom.py | 59 + papi/gui/qt_new/item.py | 338 + papi/gui/qt_new/main.py | 446 + papi/gui/qt_new/overview_menu.py | 731 + papi/last_active_papi.xml | 295 + papi/main.py | 43 + papi/plugin/__init__.py | 1 + papi/plugin/base_classes/__init__.py | 1 + papi/plugin/base_classes/base_plugin.py | 186 + papi/plugin/base_classes/base_visual.py | 118 + papi/plugin/base_classes/dpp_base.py | 46 + papi/plugin/base_classes/iop_base.py | 46 + papi/plugin/base_classes/ownProcess_base.py | 138 + papi/plugin/base_classes/pcp_base.py | 45 + papi/plugin/base_classes/vip_base.py | 42 + papi/plugin/dpp/add/Add.py | 129 + papi/plugin/dpp/add/Add.yapsy-plugin | 9 + papi/plugin/dpp/toHDD/ToHDD_CSV.py | 115 + papi/plugin/dpp/toHDD/ToHDD_CSV.yapsy-plugin | 9 + .../DataSourceExample/ProtocollConfig.json | 5 + .../io/ORTD_UDP/DataSourceExample/README | 54 + .../io/ORTD_UDP/DataSourceExample/UDPio.ipar | 3206 ++++ .../io/ORTD_UDP/DataSourceExample/UDPio.rpar | 40 + .../io/ORTD_UDP/DataSourceExample/UDPio.sce | 175 + .../ORTD_UDP/DataSourceExample/run_UDPio.sh | 4 + .../webinterface/PacketFramework.sce | 487 + .../webinterface/html/mainAuto.html | 155 + .../webinterface/html/main_Plot.html | 365 + .../webinterface/install_nodejs.sh | 11 + .../webinterface/webappUDP.js | 304 + .../ORTD_UDP/DataSourceExample_Groups/PF.sci | 590 + .../ProtocollConfig.json | 9 + .../ORTD_UDP/DataSourceExample_Groups/README | 54 + .../DataSourceExample_Groups/UDPio.ipar | 5263 ++++++ .../DataSourceExample_Groups/UDPio.rpar | 70 + .../DataSourceExample_Groups/UDPio.sce | 261 + .../DataSourceExample_Groups/run_UDPio.sh | 4 + .../webinterface/PacketFramework.sce | 487 + .../webinterface/html/mainAuto.html | 155 + .../webinterface/html/main_Plot.html | 365 + .../webinterface/install_nodejs.sh | 11 + .../webinterface/webappUDP.js | 304 + .../ProtocollConfig.json | 5 + .../DataSourceExample_extended/README | 54 + .../DataSourceExample_extended/UDPio.ipar | 13965 ++++++++++++++++ .../DataSourceExample_extended/UDPio.rpar | 146 + .../DataSourceExample_extended/UDPio.sce | 211 + .../DataSourceExample_extended/run_UDPio.sh | 4 + .../webinterface/PacketFramework.sce | 487 + .../webinterface/html/mainAuto.html | 155 + .../webinterface/html/main_Plot.html | 365 + .../webinterface/install_nodejs.sh | 11 + .../webinterface/webappUDP.js | 304 + papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 286 + papi/plugin/io/ORTD_UDP/ORTD_UDP.yapsy-plugin | 9 + papi/plugin/io/ORTD_UDP/box.png | Bin 0 -> 175 bytes papi/plugin/io/cpu_load/CPU_Load.py | 98 + papi/plugin/io/cpu_load/CPU_Load.yapsy-plugin | 9 + papi/plugin/io/cpu_load/box.png | Bin 0 -> 177 bytes papi/plugin/io/fourier_rect/Fourier_Rect.py | 138 + .../io/fourier_rect/Fourier_Rect.yapsy-plugin | 9 + papi/plugin/io/fourier_rect/box.png | Bin 0 -> 177 bytes .../io/fourier_rect_mod/Fourier_Rect_MOD.py | 126 + .../Fourier_Rect_MOD.yapsy-plugin | 9 + papi/plugin/io/fourier_rect_mod/box.png | Bin 0 -> 177 bytes papi/plugin/io/sinus/Sinus.py | 129 + papi/plugin/io/sinus/Sinus.yapsy-plugin | 9 + papi/plugin/io/sinus/box.png | Bin 0 -> 177 bytes papi/plugin/pcp/button/Button.py | 92 + papi/plugin/pcp/button/Button.yapsy-plugin | 9 + papi/plugin/pcp/button/icon.png | Bin 0 -> 2554 bytes papi/plugin/pcp/slider/Slider.py | 91 + papi/plugin/pcp/slider/Slider.yapsy-plugin | 9 + papi/plugin/templates/IOP_DPP_template.py | 151 + papi/plugin/templates/visual_template.py | 158 + papi/plugin/visual/Plot/Plot.py | 549 + papi/plugin/visual/Plot/Plot.yapsy-plugin | 9 + .../visual/WizardExample/WizardExample.py | 265 + .../WizardExample/WizardExample.yapsy-plugin | 9 + papi/tests/TestCore.py | 123 + papi/tests/TestCoreMock.py | 442 + papi/tests/TestDBlock.py | 83 + papi/tests/TestDCore.py | 328 + papi/tests/TestDPlugin.py | 133 + papi/tests/TestDSubscription.py | 69 + papi/tests/TestGUI.py | 69 + papi/tests/__init__.py | 28 + papi/ui/__init__.py | 29 + papi/ui/gui/__init__.py | 1 + papi/ui/gui/qt_dev/__init__.py | 1 + papi/ui/gui/qt_dev/add_plugin.py | 67 + papi/ui/gui/qt_dev/add_subscriber.py | 54 + papi/ui/gui/qt_dev/main.py | 157 + papi/ui/gui/qt_dev/manager.py | 128 + papi/ui/gui/qt_new/__init__.py | 1 + papi/ui/gui/qt_new/create.py | 89 + papi/ui/gui/qt_new/create_dialog.py | 60 + papi/ui/gui/qt_new/main.py | 102 + papi/ui/gui/qt_new/overview.py | 154 + pyqtgraph/GraphicsScene/GraphicsScene.py | 578 + pyqtgraph/GraphicsScene/__init__.py | 1 + pyqtgraph/GraphicsScene/exportDialog.py | 139 + .../GraphicsScene/exportDialogTemplate.ui | 100 + .../exportDialogTemplate_pyqt.py | 68 + .../exportDialogTemplate_pyside.py | 63 + pyqtgraph/GraphicsScene/mouseEvents.py | 365 + pyqtgraph/PIL_Fix/README | 11 + pyqtgraph/PlotData.py | 56 + pyqtgraph/Point.py | 155 + pyqtgraph/Qt.py | 48 + pyqtgraph/SRTTransform.py | 259 + pyqtgraph/SRTTransform3D.py | 314 + pyqtgraph/SignalProxy.py | 118 + pyqtgraph/ThreadsafeTimer.py | 41 + pyqtgraph/Transform3D.py | 35 + pyqtgraph/Vector.py | 70 + pyqtgraph/WidgetGroup.py | 298 + pyqtgraph/__init__.py | 341 + pyqtgraph/canvas/Canvas.py | 608 + pyqtgraph/canvas/CanvasItem.py | 509 + pyqtgraph/canvas/CanvasManager.py | 76 + pyqtgraph/canvas/CanvasTemplate.ui | 149 + pyqtgraph/canvas/CanvasTemplate_pyqt.py | 100 + pyqtgraph/canvas/CanvasTemplate_pyside.py | 95 + pyqtgraph/canvas/TransformGuiTemplate.ui | 75 + pyqtgraph/canvas/TransformGuiTemplate_pyqt.py | 60 + .../canvas/TransformGuiTemplate_pyside.py | 55 + pyqtgraph/canvas/__init__.py | 3 + pyqtgraph/colormap.py | 239 + pyqtgraph/configfile.py | 202 + pyqtgraph/console/CmdInput.py | 62 + pyqtgraph/console/Console.py | 375 + pyqtgraph/console/__init__.py | 1 + pyqtgraph/console/template.ui | 184 + pyqtgraph/console/template_pyqt.py | 111 + pyqtgraph/console/template_pyside.py | 106 + pyqtgraph/debug.py | 946 ++ pyqtgraph/dockarea/Container.py | 267 + pyqtgraph/dockarea/Dock.py | 333 + pyqtgraph/dockarea/DockArea.py | 319 + pyqtgraph/dockarea/DockDrop.py | 128 + pyqtgraph/dockarea/__init__.py | 2 + pyqtgraph/exceptionHandling.py | 90 + pyqtgraph/exporters/CSVExporter.py | 59 + pyqtgraph/exporters/Exporter.py | 175 + pyqtgraph/exporters/ImageExporter.py | 101 + pyqtgraph/exporters/Matplotlib.py | 74 + pyqtgraph/exporters/PrintExporter.py | 65 + pyqtgraph/exporters/SVGExporter.py | 488 + pyqtgraph/exporters/__init__.py | 27 + pyqtgraph/flowchart/Flowchart.py | 955 ++ pyqtgraph/flowchart/FlowchartCtrlTemplate.ui | 120 + .../flowchart/FlowchartCtrlTemplate_pyqt.py | 71 + .../flowchart/FlowchartCtrlTemplate_pyside.py | 66 + pyqtgraph/flowchart/FlowchartGraphicsView.py | 109 + pyqtgraph/flowchart/FlowchartTemplate.ui | 98 + pyqtgraph/flowchart/FlowchartTemplate_pyqt.py | 59 + .../flowchart/FlowchartTemplate_pyside.py | 54 + pyqtgraph/flowchart/Node.py | 647 + pyqtgraph/flowchart/Terminal.py | 638 + pyqtgraph/flowchart/__init__.py | 4 + pyqtgraph/flowchart/eq.py | 36 + pyqtgraph/flowchart/library/Data.py | 356 + pyqtgraph/flowchart/library/Display.py | 275 + pyqtgraph/flowchart/library/Filters.py | 268 + pyqtgraph/flowchart/library/Operators.py | 74 + pyqtgraph/flowchart/library/__init__.py | 103 + pyqtgraph/flowchart/library/common.py | 148 + pyqtgraph/flowchart/library/functions.py | 336 + pyqtgraph/frozenSupport.py | 52 + pyqtgraph/functions.py | 2023 +++ pyqtgraph/graphicsItems/ArrowItem.py | 124 + pyqtgraph/graphicsItems/AxisItem.py | 931 ++ pyqtgraph/graphicsItems/BarGraphItem.py | 149 + pyqtgraph/graphicsItems/ButtonItem.py | 58 + pyqtgraph/graphicsItems/CurvePoint.py | 117 + pyqtgraph/graphicsItems/ErrorBarItem.py | 133 + pyqtgraph/graphicsItems/FillBetweenItem.py | 23 + pyqtgraph/graphicsItems/GradientEditorItem.py | 910 + pyqtgraph/graphicsItems/GradientLegend.py | 114 + pyqtgraph/graphicsItems/GraphItem.py | 122 + pyqtgraph/graphicsItems/GraphicsItem.py | 587 + pyqtgraph/graphicsItems/GraphicsLayout.py | 154 + pyqtgraph/graphicsItems/GraphicsObject.py | 32 + pyqtgraph/graphicsItems/GraphicsWidget.py | 59 + .../graphicsItems/GraphicsWidgetAnchor.py | 110 + pyqtgraph/graphicsItems/GridItem.py | 120 + pyqtgraph/graphicsItems/HistogramLUTItem.py | 205 + pyqtgraph/graphicsItems/ImageItem.py | 453 + pyqtgraph/graphicsItems/InfiniteLine.py | 277 + pyqtgraph/graphicsItems/IsocurveItem.py | 121 + pyqtgraph/graphicsItems/ItemGroup.py | 23 + pyqtgraph/graphicsItems/LabelItem.py | 142 + pyqtgraph/graphicsItems/LegendItem.py | 173 + pyqtgraph/graphicsItems/LinearRegionItem.py | 291 + pyqtgraph/graphicsItems/MultiPlotItem.py | 69 + pyqtgraph/graphicsItems/PlotCurveItem.py | 560 + pyqtgraph/graphicsItems/PlotDataItem.py | 843 + pyqtgraph/graphicsItems/PlotItem/PlotItem.py | 1271 ++ pyqtgraph/graphicsItems/PlotItem/__init__.py | 1 + .../PlotItem/plotConfigTemplate.ui | 343 + .../PlotItem/plotConfigTemplate_pyqt.py | 173 + .../PlotItem/plotConfigTemplate_pyside.py | 168 + pyqtgraph/graphicsItems/ROI.py | 1903 +++ pyqtgraph/graphicsItems/ScaleBar.py | 104 + pyqtgraph/graphicsItems/ScatterPlotItem.py | 925 + pyqtgraph/graphicsItems/TextItem.py | 124 + pyqtgraph/graphicsItems/UIGraphicsItem.py | 124 + pyqtgraph/graphicsItems/VTickGroup.py | 113 + pyqtgraph/graphicsItems/ViewBox/ViewBox.py | 1570 ++ .../graphicsItems/ViewBox/ViewBoxMenu.py | 278 + pyqtgraph/graphicsItems/ViewBox/__init__.py | 1 + .../graphicsItems/ViewBox/axisCtrlTemplate.ui | 161 + .../ViewBox/axisCtrlTemplate_pyqt.py | 93 + .../ViewBox/axisCtrlTemplate_pyside.py | 88 + pyqtgraph/graphicsItems/__init__.py | 21 + pyqtgraph/graphicsItems/tests/ViewBox.py | 95 + pyqtgraph/graphicsWindows.py | 80 + pyqtgraph/imageview/ImageView.py | 645 + pyqtgraph/imageview/ImageViewTemplate.ui | 252 + pyqtgraph/imageview/ImageViewTemplate_pyqt.py | 160 + .../imageview/ImageViewTemplate_pyside.py | 155 + pyqtgraph/imageview/__init__.py | 6 + pyqtgraph/metaarray/MetaArray.py | 1474 ++ pyqtgraph/metaarray/__init__.py | 1 + pyqtgraph/metaarray/license.txt | 8 + pyqtgraph/metaarray/readMeta.m | 86 + pyqtgraph/multiprocess/__init__.py | 24 + pyqtgraph/multiprocess/bootstrap.py | 28 + pyqtgraph/multiprocess/parallelizer.py | 330 + pyqtgraph/multiprocess/processes.py | 472 + pyqtgraph/multiprocess/remoteproxy.py | 1069 ++ pyqtgraph/numpy_fix.py | 22 + pyqtgraph/opengl/GLGraphicsItem.py | 293 + pyqtgraph/opengl/GLViewWidget.py | 436 + pyqtgraph/opengl/MeshData.py | 519 + pyqtgraph/opengl/__init__.py | 30 + pyqtgraph/opengl/glInfo.py | 16 + pyqtgraph/opengl/items/GLAxisItem.py | 64 + pyqtgraph/opengl/items/GLBarGraphItem.py | 29 + pyqtgraph/opengl/items/GLBoxItem.py | 88 + pyqtgraph/opengl/items/GLGridItem.py | 58 + pyqtgraph/opengl/items/GLImageItem.py | 90 + pyqtgraph/opengl/items/GLLinePlotItem.py | 101 + pyqtgraph/opengl/items/GLMeshItem.py | 223 + pyqtgraph/opengl/items/GLScatterPlotItem.py | 183 + pyqtgraph/opengl/items/GLSurfacePlotItem.py | 139 + pyqtgraph/opengl/items/GLVolumeItem.py | 213 + pyqtgraph/opengl/items/__init__.py | 0 pyqtgraph/opengl/shaders.py | 402 + pyqtgraph/ordereddict.py | 127 + pyqtgraph/parametertree/Parameter.py | 710 + pyqtgraph/parametertree/ParameterItem.py | 165 + pyqtgraph/parametertree/ParameterTree.py | 119 + pyqtgraph/parametertree/__init__.py | 5 + pyqtgraph/parametertree/parameterTypes.py | 648 + pyqtgraph/pgcollections.py | 477 + pyqtgraph/pixmaps/__init__.py | 26 + pyqtgraph/pixmaps/compile.py | 19 + pyqtgraph/pixmaps/pixmapData_2.py | 1 + pyqtgraph/pixmaps/pixmapData_3.py | 1 + pyqtgraph/ptime.py | 30 + pyqtgraph/python2_3.py | 60 + pyqtgraph/reload.py | 516 + pyqtgraph/units.py | 64 + pyqtgraph/widgets/BusyCursor.py | 24 + pyqtgraph/widgets/CheckTable.py | 93 + pyqtgraph/widgets/ColorButton.py | 91 + pyqtgraph/widgets/ColorMapWidget.py | 218 + pyqtgraph/widgets/ComboBox.py | 41 + pyqtgraph/widgets/DataFilterWidget.py | 150 + pyqtgraph/widgets/DataTreeWidget.py | 83 + pyqtgraph/widgets/FeedbackButton.py | 163 + pyqtgraph/widgets/FileDialog.py | 14 + pyqtgraph/widgets/GradientWidget.py | 74 + pyqtgraph/widgets/GraphicsLayoutWidget.py | 12 + pyqtgraph/widgets/GraphicsView.py | 393 + pyqtgraph/widgets/HistogramLUTWidget.py | 33 + pyqtgraph/widgets/JoystickButton.py | 95 + pyqtgraph/widgets/LayoutWidget.py | 101 + pyqtgraph/widgets/MatplotlibWidget.py | 41 + pyqtgraph/widgets/MultiPlotWidget.py | 45 + pyqtgraph/widgets/PathButton.py | 50 + pyqtgraph/widgets/PlotWidget.py | 93 + pyqtgraph/widgets/ProgressDialog.py | 112 + pyqtgraph/widgets/RawImageWidget.py | 140 + pyqtgraph/widgets/RemoteGraphicsView.py | 261 + pyqtgraph/widgets/ScatterPlotWidget.py | 216 + pyqtgraph/widgets/SpinBox.py | 503 + pyqtgraph/widgets/TableWidget.py | 288 + pyqtgraph/widgets/TreeWidget.py | 284 + pyqtgraph/widgets/ValueLabel.py | 73 + pyqtgraph/widgets/VerticalLabel.py | 99 + pyqtgraph/widgets/__init__.py | 21 + setup.py | 12 + ui/gui/qt_dev/add_plugin.ui | 149 + ui/gui/qt_dev/add_subscriber.ui | 125 + ui/gui/qt_dev/main.ui | 293 + ui/gui/qt_dev/manager.ui | 233 + ui/gui/qt_new/create.ui | 124 + ui/gui/qt_new/create_dialog.ui | 107 + ui/gui/qt_new/main.ui | 155 + ui/gui/qt_new/overview.ui | 222 + yapsy/AutoInstallPluginManager.py | 201 + yapsy/ConfigurablePluginManager.py | 277 + yapsy/FilteredPluginManager.py | 138 + yapsy/IPlugin.py | 60 + yapsy/IPluginLocator.py | 105 + yapsy/PluginFileLocator.py | 533 + yapsy/PluginInfo.py | 214 + yapsy/PluginManager.py | 663 + yapsy/PluginManagerDecorator.py | 102 + yapsy/VersionedPluginManager.py | 137 + yapsy/__init__.py | 88 + 396 files changed, 91960 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGENLOG.md create mode 100644 INSTALL.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 cfg_collection/FourCfg.xml create mode 100644 cfg_collection/ORTD_bigSource.xml create mode 100644 cfg_collection/wizard.xml create mode 100644 data_sources/fourier_rect.py create mode 100644 design/CoreDataStructure.asta create mode 100644 design/PaPI.asta create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/modules.rst create mode 100644 docs/papi.data.dcore.rst create mode 100644 docs/papi.data.rst create mode 100644 docs/papi.gui.rst create mode 100644 docs/papi.plugin.rst create mode 100644 docs/papi.rst create mode 100644 docs/papi.tests.rst create mode 100644 docs/papi.ui.gui.rst create mode 100644 docs/papi.ui.rst create mode 100644 main.py create mode 100644 papi/ConsoleLog.py create mode 100644 papi/PapiEvent.py create mode 100644 papi/__init__.py create mode 100644 papi/constants.py create mode 100644 papi/core.py create mode 100644 papi/data/DCore.py create mode 100644 papi/data/DGui.py create mode 100644 papi/data/DObject.py create mode 100644 papi/data/DOptionalData.py create mode 100644 papi/data/DParameter.py create mode 100644 papi/data/DPlugin.py create mode 100644 papi/data/DSignal.py create mode 100644 papi/data/__init__.py create mode 100644 papi/error_codes.py create mode 100644 papi/event/__init__.py create mode 100644 papi/event/data/DataBase.py create mode 100644 papi/event/data/NewBlock.py create mode 100644 papi/event/data/NewData.py create mode 100644 papi/event/data/NewParameter.py create mode 100644 papi/event/data/__init__.py create mode 100644 papi/event/event_base.py create mode 100644 papi/event/instruction/CloseProgram.py create mode 100644 papi/event/instruction/CreatePlugin.py create mode 100644 papi/event/instruction/InstructionBase.py create mode 100644 papi/event/instruction/PausePlugin.py create mode 100644 papi/event/instruction/ResumePlugin.py create mode 100644 papi/event/instruction/SetParameter.py create mode 100644 papi/event/instruction/StopPlugin.py create mode 100644 papi/event/instruction/Subscribe.py create mode 100644 papi/event/instruction/Unsubscribe.py create mode 100644 papi/event/instruction/__init__.py create mode 100644 papi/event/instruction/startPlugin.py create mode 100644 papi/event/status/Alive.py create mode 100644 papi/event/status/CheckAliveStatus.py create mode 100644 papi/event/status/JoinRequest.py create mode 100644 papi/event/status/PluginClosed.py create mode 100644 papi/event/status/PluginStopped.py create mode 100644 papi/event/status/StartFailed.py create mode 100644 papi/event/status/StartSuccessfull.py create mode 100644 papi/event/status/StatusBase.py create mode 100644 papi/event/status/__init__.py create mode 100644 papi/exceptions/__init__.py create mode 100644 papi/exceptions/block_exceptions.py create mode 100644 papi/gui/__init__.py create mode 100644 papi/gui/gui_api.py create mode 100644 papi/gui/gui_event_processing.py create mode 100644 papi/gui/plugin_api.py create mode 100644 papi/gui/qt_dev/__init__.py create mode 100644 papi/gui/qt_dev/add_pcp_subscriber.py create mode 100644 papi/gui/qt_dev/add_plugin.py create mode 100644 papi/gui/qt_dev/add_subscriber.py create mode 100644 papi/gui/qt_dev/gui_main.py create mode 100644 papi/gui/qt_dev/manager.py create mode 100644 papi/gui/qt_new/__init__.py create mode 100644 papi/gui/qt_new/create_plugin_dialog.py create mode 100644 papi/gui/qt_new/create_plugin_menu.py create mode 100644 papi/gui/qt_new/custom.py create mode 100644 papi/gui/qt_new/item.py create mode 100644 papi/gui/qt_new/main.py create mode 100644 papi/gui/qt_new/overview_menu.py create mode 100644 papi/last_active_papi.xml create mode 100644 papi/main.py create mode 100644 papi/plugin/__init__.py create mode 100644 papi/plugin/base_classes/__init__.py create mode 100644 papi/plugin/base_classes/base_plugin.py create mode 100644 papi/plugin/base_classes/base_visual.py create mode 100644 papi/plugin/base_classes/dpp_base.py create mode 100644 papi/plugin/base_classes/iop_base.py create mode 100644 papi/plugin/base_classes/ownProcess_base.py create mode 100644 papi/plugin/base_classes/pcp_base.py create mode 100644 papi/plugin/base_classes/vip_base.py create mode 100644 papi/plugin/dpp/add/Add.py create mode 100644 papi/plugin/dpp/add/Add.yapsy-plugin create mode 100644 papi/plugin/dpp/toHDD/ToHDD_CSV.py create mode 100644 papi/plugin/dpp/toHDD/ToHDD_CSV.yapsy-plugin create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample/README create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.ipar create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.rpar create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.sce create mode 100755 papi/plugin/io/ORTD_UDP/DataSourceExample/run_UDPio.sh create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/PacketFramework.sce create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/mainAuto.html create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/main_Plot.html create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/install_nodejs.sh create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/webappUDP.js create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/PF.sci create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/ProtocollConfig.json create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/README create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.ipar create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.rpar create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.sce create mode 100755 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/run_UDPio.sh create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/PacketFramework.sce create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/mainAuto.html create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/main_Plot.html create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/install_nodejs.sh create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/webappUDP.js create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/README create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.ipar create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.rpar create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.sce create mode 100755 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/run_UDPio.sh create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/PacketFramework.sce create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/mainAuto.html create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/main_Plot.html create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/install_nodejs.sh create mode 100644 papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/webappUDP.js create mode 100644 papi/plugin/io/ORTD_UDP/ORTD_UDP.py create mode 100644 papi/plugin/io/ORTD_UDP/ORTD_UDP.yapsy-plugin create mode 100644 papi/plugin/io/ORTD_UDP/box.png create mode 100644 papi/plugin/io/cpu_load/CPU_Load.py create mode 100644 papi/plugin/io/cpu_load/CPU_Load.yapsy-plugin create mode 100644 papi/plugin/io/cpu_load/box.png create mode 100644 papi/plugin/io/fourier_rect/Fourier_Rect.py create mode 100644 papi/plugin/io/fourier_rect/Fourier_Rect.yapsy-plugin create mode 100644 papi/plugin/io/fourier_rect/box.png create mode 100644 papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py create mode 100644 papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.yapsy-plugin create mode 100644 papi/plugin/io/fourier_rect_mod/box.png create mode 100644 papi/plugin/io/sinus/Sinus.py create mode 100644 papi/plugin/io/sinus/Sinus.yapsy-plugin create mode 100644 papi/plugin/io/sinus/box.png create mode 100644 papi/plugin/pcp/button/Button.py create mode 100644 papi/plugin/pcp/button/Button.yapsy-plugin create mode 100644 papi/plugin/pcp/button/icon.png create mode 100644 papi/plugin/pcp/slider/Slider.py create mode 100644 papi/plugin/pcp/slider/Slider.yapsy-plugin create mode 100644 papi/plugin/templates/IOP_DPP_template.py create mode 100644 papi/plugin/templates/visual_template.py create mode 100644 papi/plugin/visual/Plot/Plot.py create mode 100644 papi/plugin/visual/Plot/Plot.yapsy-plugin create mode 100644 papi/plugin/visual/WizardExample/WizardExample.py create mode 100644 papi/plugin/visual/WizardExample/WizardExample.yapsy-plugin create mode 100644 papi/tests/TestCore.py create mode 100644 papi/tests/TestCoreMock.py create mode 100644 papi/tests/TestDBlock.py create mode 100644 papi/tests/TestDCore.py create mode 100644 papi/tests/TestDPlugin.py create mode 100644 papi/tests/TestDSubscription.py create mode 100644 papi/tests/TestGUI.py create mode 100644 papi/tests/__init__.py create mode 100644 papi/ui/__init__.py create mode 100644 papi/ui/gui/__init__.py create mode 100644 papi/ui/gui/qt_dev/__init__.py create mode 100644 papi/ui/gui/qt_dev/add_plugin.py create mode 100644 papi/ui/gui/qt_dev/add_subscriber.py create mode 100644 papi/ui/gui/qt_dev/main.py create mode 100644 papi/ui/gui/qt_dev/manager.py create mode 100644 papi/ui/gui/qt_new/__init__.py create mode 100644 papi/ui/gui/qt_new/create.py create mode 100644 papi/ui/gui/qt_new/create_dialog.py create mode 100644 papi/ui/gui/qt_new/main.py create mode 100644 papi/ui/gui/qt_new/overview.py create mode 100644 pyqtgraph/GraphicsScene/GraphicsScene.py create mode 100644 pyqtgraph/GraphicsScene/__init__.py create mode 100644 pyqtgraph/GraphicsScene/exportDialog.py create mode 100644 pyqtgraph/GraphicsScene/exportDialogTemplate.ui create mode 100644 pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py create mode 100644 pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py create mode 100644 pyqtgraph/GraphicsScene/mouseEvents.py create mode 100644 pyqtgraph/PIL_Fix/README create mode 100644 pyqtgraph/PlotData.py create mode 100644 pyqtgraph/Point.py create mode 100644 pyqtgraph/Qt.py create mode 100644 pyqtgraph/SRTTransform.py create mode 100644 pyqtgraph/SRTTransform3D.py create mode 100644 pyqtgraph/SignalProxy.py create mode 100644 pyqtgraph/ThreadsafeTimer.py create mode 100644 pyqtgraph/Transform3D.py create mode 100644 pyqtgraph/Vector.py create mode 100644 pyqtgraph/WidgetGroup.py create mode 100644 pyqtgraph/__init__.py create mode 100644 pyqtgraph/canvas/Canvas.py create mode 100644 pyqtgraph/canvas/CanvasItem.py create mode 100644 pyqtgraph/canvas/CanvasManager.py create mode 100644 pyqtgraph/canvas/CanvasTemplate.ui create mode 100644 pyqtgraph/canvas/CanvasTemplate_pyqt.py create mode 100644 pyqtgraph/canvas/CanvasTemplate_pyside.py create mode 100644 pyqtgraph/canvas/TransformGuiTemplate.ui create mode 100644 pyqtgraph/canvas/TransformGuiTemplate_pyqt.py create mode 100644 pyqtgraph/canvas/TransformGuiTemplate_pyside.py create mode 100644 pyqtgraph/canvas/__init__.py create mode 100644 pyqtgraph/colormap.py create mode 100644 pyqtgraph/configfile.py create mode 100644 pyqtgraph/console/CmdInput.py create mode 100644 pyqtgraph/console/Console.py create mode 100644 pyqtgraph/console/__init__.py create mode 100644 pyqtgraph/console/template.ui create mode 100644 pyqtgraph/console/template_pyqt.py create mode 100644 pyqtgraph/console/template_pyside.py create mode 100644 pyqtgraph/debug.py create mode 100644 pyqtgraph/dockarea/Container.py create mode 100644 pyqtgraph/dockarea/Dock.py create mode 100644 pyqtgraph/dockarea/DockArea.py create mode 100644 pyqtgraph/dockarea/DockDrop.py create mode 100644 pyqtgraph/dockarea/__init__.py create mode 100644 pyqtgraph/exceptionHandling.py create mode 100644 pyqtgraph/exporters/CSVExporter.py create mode 100644 pyqtgraph/exporters/Exporter.py create mode 100644 pyqtgraph/exporters/ImageExporter.py create mode 100644 pyqtgraph/exporters/Matplotlib.py create mode 100644 pyqtgraph/exporters/PrintExporter.py create mode 100644 pyqtgraph/exporters/SVGExporter.py create mode 100644 pyqtgraph/exporters/__init__.py create mode 100644 pyqtgraph/flowchart/Flowchart.py create mode 100644 pyqtgraph/flowchart/FlowchartCtrlTemplate.ui create mode 100644 pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py create mode 100644 pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py create mode 100644 pyqtgraph/flowchart/FlowchartGraphicsView.py create mode 100644 pyqtgraph/flowchart/FlowchartTemplate.ui create mode 100644 pyqtgraph/flowchart/FlowchartTemplate_pyqt.py create mode 100644 pyqtgraph/flowchart/FlowchartTemplate_pyside.py create mode 100644 pyqtgraph/flowchart/Node.py create mode 100644 pyqtgraph/flowchart/Terminal.py create mode 100644 pyqtgraph/flowchart/__init__.py create mode 100644 pyqtgraph/flowchart/eq.py create mode 100644 pyqtgraph/flowchart/library/Data.py create mode 100644 pyqtgraph/flowchart/library/Display.py create mode 100644 pyqtgraph/flowchart/library/Filters.py create mode 100644 pyqtgraph/flowchart/library/Operators.py create mode 100644 pyqtgraph/flowchart/library/__init__.py create mode 100644 pyqtgraph/flowchart/library/common.py create mode 100644 pyqtgraph/flowchart/library/functions.py create mode 100644 pyqtgraph/frozenSupport.py create mode 100644 pyqtgraph/functions.py create mode 100644 pyqtgraph/graphicsItems/ArrowItem.py create mode 100644 pyqtgraph/graphicsItems/AxisItem.py create mode 100644 pyqtgraph/graphicsItems/BarGraphItem.py create mode 100644 pyqtgraph/graphicsItems/ButtonItem.py create mode 100644 pyqtgraph/graphicsItems/CurvePoint.py create mode 100644 pyqtgraph/graphicsItems/ErrorBarItem.py create mode 100644 pyqtgraph/graphicsItems/FillBetweenItem.py create mode 100644 pyqtgraph/graphicsItems/GradientEditorItem.py create mode 100644 pyqtgraph/graphicsItems/GradientLegend.py create mode 100644 pyqtgraph/graphicsItems/GraphItem.py create mode 100644 pyqtgraph/graphicsItems/GraphicsItem.py create mode 100644 pyqtgraph/graphicsItems/GraphicsLayout.py create mode 100644 pyqtgraph/graphicsItems/GraphicsObject.py create mode 100644 pyqtgraph/graphicsItems/GraphicsWidget.py create mode 100644 pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py create mode 100644 pyqtgraph/graphicsItems/GridItem.py create mode 100644 pyqtgraph/graphicsItems/HistogramLUTItem.py create mode 100644 pyqtgraph/graphicsItems/ImageItem.py create mode 100644 pyqtgraph/graphicsItems/InfiniteLine.py create mode 100644 pyqtgraph/graphicsItems/IsocurveItem.py create mode 100644 pyqtgraph/graphicsItems/ItemGroup.py create mode 100644 pyqtgraph/graphicsItems/LabelItem.py create mode 100644 pyqtgraph/graphicsItems/LegendItem.py create mode 100644 pyqtgraph/graphicsItems/LinearRegionItem.py create mode 100644 pyqtgraph/graphicsItems/MultiPlotItem.py create mode 100644 pyqtgraph/graphicsItems/PlotCurveItem.py create mode 100644 pyqtgraph/graphicsItems/PlotDataItem.py create mode 100644 pyqtgraph/graphicsItems/PlotItem/PlotItem.py create mode 100644 pyqtgraph/graphicsItems/PlotItem/__init__.py create mode 100644 pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui create mode 100644 pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py create mode 100644 pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py create mode 100644 pyqtgraph/graphicsItems/ROI.py create mode 100644 pyqtgraph/graphicsItems/ScaleBar.py create mode 100644 pyqtgraph/graphicsItems/ScatterPlotItem.py create mode 100644 pyqtgraph/graphicsItems/TextItem.py create mode 100644 pyqtgraph/graphicsItems/UIGraphicsItem.py create mode 100644 pyqtgraph/graphicsItems/VTickGroup.py create mode 100644 pyqtgraph/graphicsItems/ViewBox/ViewBox.py create mode 100644 pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py create mode 100644 pyqtgraph/graphicsItems/ViewBox/__init__.py create mode 100644 pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui create mode 100644 pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py create mode 100644 pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py create mode 100644 pyqtgraph/graphicsItems/__init__.py create mode 100644 pyqtgraph/graphicsItems/tests/ViewBox.py create mode 100644 pyqtgraph/graphicsWindows.py create mode 100644 pyqtgraph/imageview/ImageView.py create mode 100644 pyqtgraph/imageview/ImageViewTemplate.ui create mode 100644 pyqtgraph/imageview/ImageViewTemplate_pyqt.py create mode 100644 pyqtgraph/imageview/ImageViewTemplate_pyside.py create mode 100644 pyqtgraph/imageview/__init__.py create mode 100644 pyqtgraph/metaarray/MetaArray.py create mode 100644 pyqtgraph/metaarray/__init__.py create mode 100644 pyqtgraph/metaarray/license.txt create mode 100644 pyqtgraph/metaarray/readMeta.m create mode 100644 pyqtgraph/multiprocess/__init__.py create mode 100644 pyqtgraph/multiprocess/bootstrap.py create mode 100644 pyqtgraph/multiprocess/parallelizer.py create mode 100644 pyqtgraph/multiprocess/processes.py create mode 100644 pyqtgraph/multiprocess/remoteproxy.py create mode 100644 pyqtgraph/numpy_fix.py create mode 100644 pyqtgraph/opengl/GLGraphicsItem.py create mode 100644 pyqtgraph/opengl/GLViewWidget.py create mode 100644 pyqtgraph/opengl/MeshData.py create mode 100644 pyqtgraph/opengl/__init__.py create mode 100644 pyqtgraph/opengl/glInfo.py create mode 100644 pyqtgraph/opengl/items/GLAxisItem.py create mode 100644 pyqtgraph/opengl/items/GLBarGraphItem.py create mode 100644 pyqtgraph/opengl/items/GLBoxItem.py create mode 100644 pyqtgraph/opengl/items/GLGridItem.py create mode 100644 pyqtgraph/opengl/items/GLImageItem.py create mode 100644 pyqtgraph/opengl/items/GLLinePlotItem.py create mode 100644 pyqtgraph/opengl/items/GLMeshItem.py create mode 100644 pyqtgraph/opengl/items/GLScatterPlotItem.py create mode 100644 pyqtgraph/opengl/items/GLSurfacePlotItem.py create mode 100644 pyqtgraph/opengl/items/GLVolumeItem.py create mode 100644 pyqtgraph/opengl/items/__init__.py create mode 100644 pyqtgraph/opengl/shaders.py create mode 100644 pyqtgraph/ordereddict.py create mode 100644 pyqtgraph/parametertree/Parameter.py create mode 100644 pyqtgraph/parametertree/ParameterItem.py create mode 100644 pyqtgraph/parametertree/ParameterTree.py create mode 100644 pyqtgraph/parametertree/__init__.py create mode 100644 pyqtgraph/parametertree/parameterTypes.py create mode 100644 pyqtgraph/pgcollections.py create mode 100644 pyqtgraph/pixmaps/__init__.py create mode 100644 pyqtgraph/pixmaps/compile.py create mode 100644 pyqtgraph/pixmaps/pixmapData_2.py create mode 100644 pyqtgraph/pixmaps/pixmapData_3.py create mode 100644 pyqtgraph/ptime.py create mode 100644 pyqtgraph/python2_3.py create mode 100644 pyqtgraph/reload.py create mode 100644 pyqtgraph/units.py create mode 100644 pyqtgraph/widgets/BusyCursor.py create mode 100644 pyqtgraph/widgets/CheckTable.py create mode 100644 pyqtgraph/widgets/ColorButton.py create mode 100644 pyqtgraph/widgets/ColorMapWidget.py create mode 100644 pyqtgraph/widgets/ComboBox.py create mode 100644 pyqtgraph/widgets/DataFilterWidget.py create mode 100644 pyqtgraph/widgets/DataTreeWidget.py create mode 100644 pyqtgraph/widgets/FeedbackButton.py create mode 100644 pyqtgraph/widgets/FileDialog.py create mode 100644 pyqtgraph/widgets/GradientWidget.py create mode 100644 pyqtgraph/widgets/GraphicsLayoutWidget.py create mode 100644 pyqtgraph/widgets/GraphicsView.py create mode 100644 pyqtgraph/widgets/HistogramLUTWidget.py create mode 100644 pyqtgraph/widgets/JoystickButton.py create mode 100644 pyqtgraph/widgets/LayoutWidget.py create mode 100644 pyqtgraph/widgets/MatplotlibWidget.py create mode 100644 pyqtgraph/widgets/MultiPlotWidget.py create mode 100644 pyqtgraph/widgets/PathButton.py create mode 100644 pyqtgraph/widgets/PlotWidget.py create mode 100644 pyqtgraph/widgets/ProgressDialog.py create mode 100644 pyqtgraph/widgets/RawImageWidget.py create mode 100644 pyqtgraph/widgets/RemoteGraphicsView.py create mode 100644 pyqtgraph/widgets/ScatterPlotWidget.py create mode 100644 pyqtgraph/widgets/SpinBox.py create mode 100644 pyqtgraph/widgets/TableWidget.py create mode 100644 pyqtgraph/widgets/TreeWidget.py create mode 100644 pyqtgraph/widgets/ValueLabel.py create mode 100644 pyqtgraph/widgets/VerticalLabel.py create mode 100644 pyqtgraph/widgets/__init__.py create mode 100644 setup.py create mode 100644 ui/gui/qt_dev/add_plugin.ui create mode 100644 ui/gui/qt_dev/add_subscriber.ui create mode 100644 ui/gui/qt_dev/main.ui create mode 100644 ui/gui/qt_dev/manager.ui create mode 100644 ui/gui/qt_new/create.ui create mode 100644 ui/gui/qt_new/create_dialog.ui create mode 100644 ui/gui/qt_new/main.ui create mode 100644 ui/gui/qt_new/overview.ui create mode 100644 yapsy/AutoInstallPluginManager.py create mode 100644 yapsy/ConfigurablePluginManager.py create mode 100644 yapsy/FilteredPluginManager.py create mode 100644 yapsy/IPlugin.py create mode 100644 yapsy/IPluginLocator.py create mode 100644 yapsy/PluginFileLocator.py create mode 100644 yapsy/PluginInfo.py create mode 100644 yapsy/PluginManager.py create mode 100644 yapsy/PluginManagerDecorator.py create mode 100644 yapsy/VersionedPluginManager.py create mode 100644 yapsy/__init__.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..15528654 --- /dev/null +++ b/.gitignore @@ -0,0 +1,61 @@ +# #### Ignores added by us + +# gedit tmp-files +*~ + +# PyCharm project folder +.idea/ + +# Astah Backupfile +*.bak + +# PAPI config file + +testcfg.xml + +# #### GitHub .gitignore template for Python + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ diff --git a/CHANGENLOG.md b/CHANGENLOG.md new file mode 100644 index 00000000..b07c8787 --- /dev/null +++ b/CHANGENLOG.md @@ -0,0 +1,2 @@ +CHANGENLOG +================== \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 00000000..7848b062 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,2 @@ +INSTALL +================== \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..bde60ceb --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ +GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..a1c455c1 --- /dev/null +++ b/Makefile @@ -0,0 +1,40 @@ + +UIPY = pyside-uic + +SRC_DIR = ./ui/ + +DES_DIR = ./papi/ + +#UI_FILES = "$(shell find $(SRC) -regex ".*\.\(ui\)")" + +UI_FILES = quitter.ui + +UI_FILES_FOUND = $(shell find $(SRC_DIR) -name '*.ui') +UI_FILES = $(UI_FILES_FOUND:./%=%) +PY_FILES = $(addprefix $(DES_DIR),$(UI_FILES:.ui=.py)) + + +MKDIR_P = mkdir -p + +AUTHOR = $(shell whoami) + +#all: $(UI_FILES) + +all: + +create_ui: $(PY_FILES) + +$(PY_FILES): $(UI_FILES) + +$(UI_FILES): + + mkdir -p $(DES_DIR)$(dir $@) + $(UIPY) $@ -o $(DES_DIR)$(dir $@)$(notdir $(basename $@)).py + + @if [ -f $(DES_DIR)$(dir $@)__init__.py ] ; \ + then echo "__init__.py exists" ; \ + else echo "__author__ = '$(AUTHOR)'" > $(DES_DIR)$(dir $@)__init__.py ; \ + fi + +rst: + sphinx-apidoc -f -o docs papi diff --git a/README.md b/README.md new file mode 100644 index 00000000..29d8ee32 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +PaPI v. 0.8 +================== + +Plugin based Process Interaction + +Introduction +------ +PaPI is a tool to interact with data generating processes. It can be used to get data stream of experiments or devices +like oscilloscopes. + +PaPI is based on a strong plugin architecture. Therefore it is easy to expand to new environments. + +PaPi will make heavily use of multi-core systems. + +Installation +------ +Basic installation on Ubuntu 14.04 64Bit, using python 3.4 + +`sudo apt-get install python3.4 git python3-pyside python3-numpy python3-scipy` + +`git clone https://github.com/TUB-Control/PaPI.git PaPI` + +`cd PaPI` + +`python3.4 main.py` + + +Documentation +------ + +Sphinx doc on GitHub: https://tub-control.github.io/PaPI/ + +PaPI wiki on GitHub: https://github.com/TUB-Control/PaPI/wiki + + +Changelog +------ + +v.0.8 +--- + +* Use plugin as wizards for configurations +* Use ESC and RETURN for window interaction +* New file dialog to avoid performance issues +* [fix] signal names instead of id in overview +* Run/Edit mode +* Set/load backgorund and save it to config +* [fix] When plugin in gui crashs, gui stays alive and plugin will be stopped + + +v.0.8.2 +--- + * New minor feature: PaPI will save a cfg on close. One is able to load this cfg after startup using 'ReloadConfig' + * Big bugfix: signal and signal name relation had an order bug. There is now a new back end structure handling signals + * Signal unsubscribe is now possible using the gui + * Signals, parameter and plugins in overview are sorted now \ No newline at end of file diff --git a/cfg_collection/FourCfg.xml b/cfg_collection/FourCfg.xml new file mode 100644 index 00000000..2d9a91d2 --- /dev/null +++ b/cfg_collection/FourCfg.xml @@ -0,0 +1,108 @@ + + + + + Fourier_Rect_MOD + + + FourierXRectXMOD + + + + + + + Plot + + + \w+,\s*\w+ + Label-X + time, s + + + ^([1-9][0-9]{0,3}|10000)$ + Buffersize + 1000 + 1 + + + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + (0,0) + 1 + + + (\d+) + 1 + + + ^(1|0)$ + bool + Grid-X + 0 + + + Used display name + VisualPlugin + + + ^(1|0)$ + bool + Grid-Y + 0 + + + ^\[(\s*\d\s*)+\] + Color + [0 1 2 3 4] + 1 + + + ^\[(\s*\d\s*)+\] + Style + [0 0 0 0 0] + 1 + + + Plot + + + ^(1|0)$ + bool + Rolling Plot + 0 + + + \w+,\s+\w+ + Label-Y + amplitude, V + + + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + (300,300) + 1 + + + + [0 1 2 3 4] + [0 0 0 0 0] + 0 + 1 + 1000 + 0 + 0 + + + + + Add + + + Add + + + + + + diff --git a/cfg_collection/ORTD_bigSource.xml b/cfg_collection/ORTD_bigSource.xml new file mode 100644 index 00000000..1b1cb55b --- /dev/null +++ b/cfg_collection/ORTD_bigSource.xml @@ -0,0 +1,295 @@ + + + + + ORTD_UDP + + + 1 + 0 + + + 1 + 20001 + + + ORTDXUDP + + + 1 + 127.0.0.1 + + + 1 + 20000 + + + file + 0 + /home/control/PycharmProjects/PaPI/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json + + + + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0.0 + + + + Button + + Oscillator input + + + + + + Plot + + + ^(1|0)$ + 0 + Grid-Y + bool + + + ^(1|0)$ + 1 + Rolling Plot + bool + + + \w+,\s*\w+ + time, s + Label-X + + + ^([1-9][0-9]{0,3}|10000)$ + 50 + Buffersize + 1 + + + ^(1|0)$ + 0 + Grid-X + bool + + + ^\[(\s*\d\s*)+\] + [0 0 0 0 0] + Style + 1 + + + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] + Color + 1 + + + (\d+) + 1 + + + Plot + + + \(([0-9]+),([0-9]+)\) + (311,696) + 1 + Determine size: (height,width) + + + \w+,\s+\w+ + amplitude, V + Label-Y + + + Used display name + VisualPlugin + + + \(([0-9]+),([0-9]+)\) + (0,0) + 1 + Determine position: (x,y) + + + + 0 + [0 0 0 0 0] + 1 + [0 1 2 3 4] + 1 + 0 + 50 + + + + ORTDXUDP + + + 15 + 16 + 18 + HalloWelt14 + Sig2nal + Sign12al + Sign3al + Signal1 + Signal10 + Signal11 + Signal19 + Signal20 + Signal21 + Signal22 + Signal5 + Signal6 + Signal7 + Signal8 + Signal9 + Signal_13 + Test17 + _Sig4nal + + + + + + Plot + + + ^(1|0)$ + 0 + Grid-Y + bool + + + ^(1|0)$ + 1 + Rolling Plot + bool + + + \w+,\s*\w+ + time, s + Label-X + + + ^([1-9][0-9]{0,3}|10000)$ + 200 + Buffersize + 1 + + + ^(1|0)$ + 0 + Grid-X + bool + + + ^\[(\s*\d\s*)+\] + [0 0 0 0 0] + Style + 1 + + + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] + Color + 1 + + + (\d+) + 1 + + + PlotX2 + + + \(([0-9]+),([0-9]+)\) + (300,300) + 1 + Determine size: (height,width) + + + \w+,\s+\w+ + amplitude, V + Label-Y + + + Used display name + VisualPlugin + + + \(([0-9]+),([0-9]+)\) + (320,0) + 1 + Determine position: (x,y) + + + + 0 + [0 0 0 0 0] + 1 + [0 1 2 3 4] + 1 + 0 + 200 + + + + ORTDXUDP + + + V + X + + + + + + Button + + + 0 + 0 + + + Button + + + \(([0-9]+),([0-9]+)\) + (127,58) + 1 + Determine size: (height,width) + + + Button + 0 + + + 0.5 + 0 + + + \(([0-9]+),([0-9]+)\) + (311,305) + 1 + Determine position: (x,y) + + + + + + diff --git a/cfg_collection/wizard.xml b/cfg_collection/wizard.xml new file mode 100644 index 00000000..6366c9a6 --- /dev/null +++ b/cfg_collection/wizard.xml @@ -0,0 +1,29 @@ + + + + WizardExample + + + WizardExample + + + (300,300) + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 + + + (0,0) + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + 1 + + + VisualPlugin + Used display name + + + + + + diff --git a/data_sources/fourier_rect.py b/data_sources/fourier_rect.py new file mode 100644 index 00000000..9c08d6da --- /dev/null +++ b/data_sources/fourier_rect.py @@ -0,0 +1,91 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +import socketserver + +import time +import math +import numpy +import os +import pickle + + +class MyUDPHandler(socketserver.BaseRequestHandler): + """ + This class works similar to the TCP handler class, except that + self.request consists of a pair of data and client socket, and since + there is no connection the client address must be given explicitly + when sending data back via sendto(). + """ + + max_approx = 20 + amax = 20 + + t = 0 + + def start_init(self): + + + print(['Fourier: process id: ',os.getpid()] ) + return True + + def calculate_data(self): + + vec = numpy.ones(self.amax* ( self.max_approx + 1) ) + + amax = MyUDPHandler.amax + amplitude = 1 + max_approx = MyUDPHandler.max_approx + freq = 1 + + for i in range(amax): + vec[i] = MyUDPHandler.t + for k in range(1, max_approx + 1): + vec[i+amax*k] = 4*amplitude / math.pi * math.sin((2*k - 1)*math.pi*freq*MyUDPHandler.t)/(2*k - 1) + MyUDPHandler.t += 0.001 + + return vec + + def handle(self): + data = self.request[0] + socket = self.request[1] +# print("{} wrote:".format(self.client_address[0])) + + + vec = self.calculate_data() + + socket.sendto(pickle.dumps(vec), self.client_address) + + +if __name__ == "__main__": + HOST, PORT = "0.0.0.0", 9999 + server = socketserver.UDPServer((HOST, PORT), MyUDPHandler) + server.t = 0 + server.serve_forever() diff --git a/design/CoreDataStructure.asta b/design/CoreDataStructure.asta new file mode 100644 index 0000000000000000000000000000000000000000..42a3c8f1b9a4ea96808a742e5e86d439637b3e92 GIT binary patch literal 17776 zcmV*7KytrOO9KQH00;;O0D@c$MF0Q*000000000001E&B07Y(eX>@s0bZ>HH?R^V) zBUgFoNVaUtZ+jokJDcooLdve!k}XSSv&nAOS+c1UHk(aIwiGkcjOlJK{UEmMq(&DE-`gr1P_uTxcAk%e_*^nwp5}%XtnQM8eD2Tbsnc)mugj+&d z;JG70mOsc0W!Z}Hp*d!w@zq0;$dyvOBr~IP$JjEvE5qg%c3mw?LT+LA95Z^X#PRBz zmkC8#lvZSBgYsla7BagoV~dMduzB+6#-b?6JZF7)*zoWixw*TNXBcKX!vtRZ@S8Us zh0ik6v2T1o!N&+ZIu4JHtL=umzx~r}j%)j~qdv^yDILLGsh2uPe6IkwqYl*z{ zCkXF2pkZ7^!@Xa<_Q{`~`N|guNxSfLP-ceqi`i@`C&(*XrpFWG6EYK=j6m$GiUJOV1t&GQl|}M5rOH6qr}z7eE|u$MV%Y;I0Tyb=d|S zkePl$%42*=AuEAommQK)*2Gj<((U*)c;jF;|DIR<^0W(HwEx~96PROqit@eq5x3v>iy0fL88n=J@p2N2aS@e5s4 z!F~dNk=!$~O>dEmix*|mvwCN=Sjy)KP9+;k`($=uf#@wZQ>s4PQ&Ks%O8puPZ==3W z__tkh(=8vkDh>)9bV0bUqojVUIO-7S4K>-N zm6s%oyi{hB8UBl-xBc_@Gfx(XGCRuj@|F42BGFnrQHX(j1@(i>Y>gIoPI0jqn@&tb z6OlwL9^;cU@$}SWoKHp5$;d>MQ%DwIw|dp2-GVrE>iq_M-enfWeG{?X!luOFO5 zLF`P5+=_w=!WxVNbP_Y+r7PgPAN}el$D|uRHS5sB2+$55C+fbGl1mc5Duj&SjS&Lm ztSgY0R%AX0tZ)Snv=fS10`3u=9|vghJ#*aaum`d45|MWS^nP{R?_KSFUxS7_WYuy{iZhOUe z%X#>bC9lIDF`|APypSpeI*Fw6wjQFM`RYtt597M!(D1z@McnG*4pt`aV6r6hJH$Nk zR6q=N@VQbJFY{=a*_>>Hie2#v)w%Vc8b@HNpxpx`*>gF0WXpcIKhP_)(gH8k)b#zD}zX<~eLD6Z&^OHibxl^y$u{~(E(9s9t&9_TF#MIk9<$Rm5wY^KN$ z^h&DU*#o$Tein**RELH%59rCV($c)3^+5a-Atk5t1HBnG$!GT1FcY8fikb83Vn!oS z6+1;8JNv4?X&5rx@jh7Mf)SlcwBC-gm?8HveC<)DnmbC3`KqH_)<<~-FE5ImHpJ>z z@8c`3PbLx&1S!;# z5IVo;BI~Hn6}=Ank6Z_BRwiWhyf*qR)t_AWn0iE7gZ80qO?Xz>o9$zRKC9!HtFJH| zn9eR}8Xwvyk!%9{5opH^Kvn_&DG~r5$HxqhG>IQ~Xms3B6toiS)J`L$vv41 zvZaiSzSo`s!_<&`!FFW6B3J2e8iSY>w9<3A`gd zEl5)Z#g4h8C}wyzS4G}l19-J@H+`%LwjXe36pbq0-e|#_@fn*5u#Vl4V`v9&?x_yz zuwI9MhV$?hWl$aXt(Ji+zqWSB)jnzuHsRU&o$WkWP36@hwDy3L4X}>d@KZwrH*{8e z?2$#UV?WDz>{TUS9r_zAL)X2n){k88tMzsZzk}KQUk2~HGXLezpB;pdU04e%QpF{e@P!r&hA^Tr z{0neS%rORsN@gzf6z#lLd-IQl9JkvR14&Z2jSAn;M;~Vec0ppZ1U2V8e&qK5{@`c- z0aq8aSU$A3agI!$)v#y)p&AP~FBTYAq7IN|`9p>{>-g*qfAjD)14&pCaeaFZIKue$ z{U?tV?s(e`y9wovGX0#muOy2QV?NFwHR61Z2_Z9800E4g++Om2`vkpyhs=($rkzjVn&p-H7a&l-i3E|yLWau`E2a$FHcCE z+)}kCeMKOgB^nmOY1N-XNEuZ%b;BY6fwsD0VUcltIRUO`F!>5Di!!#bHGF@kJ>Cr( zDoZkrWL$qawZpFHzO_;1dfCz^GZV3+(`(O}g6mWQo( zD(eq`ChyJCz1Lvv9WBwDnG9?;GEB6MWeFS699BlUM!cZFyJ? zW(=k#*QOO9)pc=Nv8hIMZA&fi!`rpzL->3MzTQJHHdS8-qpt#~4w6ys|N3u=(css< zP2~Qxqs(SDlhOP@qvXR6FR4b-j6a;IMG^NqDDk7r8QSj$i35ZSCeE*4?Wl3awBL_d zez_nCDg?)LgwN)Q?+F?o7Fu};92MiU+v_}Qv^Zf57DG=&*MauFQnfq>nE`DAtNh#} z6p3d@<~T>05uu1pL$3#AHY22nSr&?y2^K zUv1Nhq78YcK@R3wdC~aTMu{h^m9xHmx?CGoN10Q~CXek}gBLO)wH6yU|+#MkG=r3n?MbYDpuVdE)k}pZg@cLj`KPEyYO;PNQdB-xPdV4it$XTSX|=PvEJ z7V{pA)2Y17bmfUhBc!wus9A7`)ke^w*tA~1X5fh;`^pV3 z``M{?+;;({OZ2e|3lhJe&NlXVn!xYVU&R@`P0z_poK`;fnO?-Esvyr9eE)t7sjwL} z3nSqP-b*VWvM|el2~(7M!h|!D6@s}__RZb>m1LSq|R zyTb79Zp;vaklWQMf-5Sj8=AtGRG2XEA~Tb;Y@@5*I!Tu{H0#KfUi+l+X}k5?zXgp!R9kyd*deNKwJN5CaqbxG|p#EwuV zM%1hAFpClY{-@q^=lfs&-%ee2=ENK{pYYPWQLc_%_xsO1{H70nC}@s)j2PwiFhHYf zA3dT$df%4IW_GekZe}VLnc*fA2{xHXPDi+@G?(B~sj0*yn~vM<6S)xx;q4PfN*_-Y ze8ky3vxgMcwpmXCs6_8M1cu?$cbWmW4r~|R>9T>w^1=)iXn|pT_X+d6(>}s~H$Pt} z@tC_9IHwhwg-uVD=`>4Vs{#~!445?uQdebWgTTX0OuY;Alb#|INoi*yl4N5XJ2R6^ zbCK8-n@+`J(P=&rOT{J=Gby&Nm>3K|JkL$}h)jL6mzK!{?F>CO*)`R4!2JK5l{wlw6JBk#Ut#n7!jeUKjcw)pMeT=g){ z1GG%}Q()nSnB$VVE8l=WL{RLLIkaSWW; z{8U2HMOTtGS4ILIln*0&b~L4PibnEQ0F~}_U1w5Dr4rY$S}K(&5`EgHmO`5OzM&;b zsXRrQ@)@|XF{KodCz|HmvgJ%$t#;kzpOCY)CNNT1YkX~!f$dU-iFWO6qG7v%oG8!d zcrb{pv|<}1fmZh5lDww{F3G8Q=Ttjr`51)*j!@b7i@ZxqyVer{e*`!`+8PxLm!=J6 zwb11qR^QuvqpBilw*D!uz!7uMpLhYk`ty4aeDj|#*nnO@Kdx)4E&^2y(7XU*a4;b8 zB5z+0QGJPUvAVniH@@UygYRKJqvT90NoaT8CA@mqo+o}*U9dO0B1ASQLP+hD4JXl0 zc+g7UaM4OSL;R65Ix)54a>MpRg2I`fe&pdta{F!y!k+vdSfvJ^l#pb9WBVsfjfc-W z{rf-q*4G}u0P_%L;&7%kKUfWeKB@Am41wJj^CDp_3FE_(z9my_(vWYo-@IengWvpO zZH3e1H^2U+_uuz}H|{(TWI~?y(mQUPN1in^grBCEok^l7n&U7to^-dq78dwMg=$sZ zFqx|I#{O5_e*Wt|#yw!4yg|Et?%>dl4Vt^(K?*PRsFocTkYNk*S?|96?q6Jg7pmwk zWum6?R99M*vdXtF+t+ld*09VPTzzx56Jz>kl2PwRx*I{f5wy)N$S^6lF$I~bkn}4g zna6~>svF*ASbes1K)E5Y+CjlIePJFnI7WsQ<3}qV_)TBgvT+{ax9{KZ_)S0m)cZ~g zLS2JVHj@#TRemhVpb9;#IXX4}h3?_KvG2e5wP+Ih)i8s)=1Q?ftnjN-OaoGE6?xEu z7Gs!@qCIUiJ;BrLeux9tQ*4rb&Tzf%ylS_?cq^>&Ti_4I;Y-U4{~ry%rqD~vlpAP* z7ih2qhpVFAZCo(2v@3XS*Im{F!@#G((~(?XHTOpmL|R*iS6Zg15>QZQ;K99=4llg$ z19LZNKVc&9i?wy4f_s5w0?#zh<-t2A%qXX@oNe=52cc%yh1Ip(i<}sdavca6k#%9j z^{__M!x{?P)`7_M~Bpt=%0Rqq;@;%hZB zD4jeAn!MrXiV3xIv4IMwE6<%U#bBl=>aVm-D|CWZuaPm*832M+l+TknvUvJ}-3 z+NS))mLh$&oyxE^!K6}_ zA}s<~xZ?~B7Co3WU=k3g)+y2$EUmkQl&qR#TYQotLAGs*V{q1vlDkaMF4Hfec1*TF zW7Klqlc^$w1CCJPyHA+jl_=&UZ6iWg9%%ru&Ne}b*!Vih#VGQm5LXAB_*%{CLh?Fi zbxlnlUqz}Z^C*?;8b~{%Fd!IHB&ViV)>9<)N1Rh6&GuI}J{@Q1?NpPzY4*XWdlgMv z0S{*L=NC%v{bKsGsUS0W6sj1Ng#}nrI?8m55(f)L=5377^N-<{-j2*nHLwwJw`so} zH2a^H9f)x6Cy@3lru`fTD)WPkg=-}=tK{l|^d!RpZtu;o@44Sf@? zzMdS?iv^*+|GJsa{GM+{K=hDr)^SG*oF&z7MoEL49g-P7U#;6YqmW`Fu7<*QHCvlN zqkm>mAQeOXD0dP%Wb4cI2wO%_Sm8rC5P)GTlA6L``2c zp>T;*0MB=ZH7_%pN;%b{^V}BA1E67~JT0tNBDH^o_qk0ZVD~RlQN*SSf-Q5DiTFQ^K!*Eg8a>39Eufat}4c&2$2xB3rG^3v9c zNZp-Qj1%TOOhP()+9E4UO_7zQ24zLv%1G-rJY%;=r0>eQMbaPHTIIae+apxi4Y>(~ z)7O1DBT;k!Ti_-v#AtF?*VJeb5GfWSHPv@T8&n?w$=!2tR(&T=o_q)VQ(AGakKm~K zJi7^u37vj-3<-!7dE*+zQ_Z8e)lEQ#auS-h7UgXWg%rz(8VZZ;5gPX#4jQ3xUj}v* z8gF5S&XnCWk-jOCUDi#L;W>KK)R414C_-^Y&pJgKqKH-RZA&f5JK3q&M0_R@PjV9z zlT%ZP2*+}fnTcsG!6#!pn{3Y%2?X@hR;9NMCM2%#X*%!Y`2J(|LaM9Me#oMo^@{qz z1L0~3K2@lu>wOj;sgf zfQ-z}&$h&;a1g7U?{Qc3fV(G}hdYf!J0FJ234^>8njtSz1M-5+o=_&r58>w72I|3Y zpIVMk6i0&&|H~OqT^H}gKD(CTC-@hV8U1YAaV=eboD(EvVRA{xapT5Akvf&?>b+?p zlNnDbr|EQWy>$Pj`y-dg%%D(&A9$|%llu5sTKVH~@;m8aKDjb)YIXlnSl!6`T9qT_iGf#v>?%nBYmxZ?aZEo+58XA|LA%D{q4{E zJjnDOWx9moK8I2`15m(nznJ6HGd1XouWMy{pu$f|%%w__#OG4P>g@AYnx4&M_Uo^! zwcxw5c)B9s3^IK*LuWW-gD|SBL0(kv4&)`?c5}>dUdKzUEqSPxaf$Ve7J}r#FTCc= z;ooieUXTeM#nOt>${Z8UoA#!nm+}h}=?iTaojM!;$e8D~{Nd2Gtx&1yl`N%7eVHyo zQM(GQYPHajfYxU>_SsFO0 zn8wm6_9J~foQz{*wcVClECA+D=9o@!IjN4qF^HT7@@&;{HJ-o#Ek9-5C%r9Q;e5lg zx_UL~(@L6BpThg-JsfF@g+Q)_qopqKlJeT6~p81sHc{IwoU8JM)>hrlI zWCb{y>Sup?@MquZ{J^V}lm0xNtDv>9yu0hrzyABatCTi8mHDcEeDuDR0 zRcpdKXs?V1?RC$pwO36;U73QO$J~LW`*mywhP*0mVwe^_Y()@8^ou+uI(qNq@0Ybce8@9%~Z% zq0`i|8CVQmXMky?nDCK=_AT)8d@bdR~-@tUi zw8*raUvBLDB}E!2$Lu+>xtuSz-Cj^We11LS(hS$z;SmFXO80>KdyI~{&>Tly^pT_9 zkry}zC!Hd0S5TewuFDFobJ8h7$0(PV)pye2`cFuS+YFQP_Q&<>&_T$_>vMAd%*k`j z5%sc3Jum@-`I*O0PuxM2mejXhE^U4lhBbMAwH_O^bCS&^V^_iDuWG21~Fq z*&NN`pJgSjijo7ndRSWOdbEYAgCY$FRmY=^sSfu6Anl3g# zQzd!{E%mlO%!qh}%d6n>ap?6hT>cy`?}p2_VCgG$BHnMcMs=I*jI=@?*&KH)#W5!hJrowpl8|#Igr9u)BGN3lWV%3)=>j zy?Q9L!i3xmMJ!BEOjG@!h=s_62MS!#GDR#zC?;x1wkzt$he}h6A{HjxV;40PF|>Jh zOI2_8EL>jd;yG1oM;5GgM3ND|AEHD+q&Qcqpn9j>OYo`1dxHr`?mNDnSk|;Rn6%{@CebP{zOP!8jL@C+q+st& z?U5Jv<1}gH#l0e%U0$$VW42VBr8#A$O*AAk^GnObLst%~w-b}4sT8dd^L;H4g9xZR&l%Kl=%2(X0?%6Gnt^*tM*zy(s*sGcp_hP1=ZGo9G2+fb2gvLL|b=E|ohbmG% zz3mYi_bQGwLgQY+%TZ{&l}zk@)?RRj^xgjH?Q_W-IeG=HEP+HQ(*6n5Iz^g#V+|I@ z^i{9!Pd0?@dWz#_2R2Pc+JDf;^My?T=Sz)gk0izMA?svmj4U>AzE+@s!(^5Sil(ep05RU&;;#5>lllHoaetAuL1 zA!mV*g+p}GBHCZp6ps&#;LUZglgMx*Ep3r%wX zwL6AfB=Z5%Y07gH!Y{;kpD@4cpFAemi>_^b2w%9GUTdBpy}(i&go4PrNRw~-3DQVk z=M$u(`I%LmAWc&zvp~6~Oh8GjpCGM2;(UU%O`Ia^3S9|tMVvy{F{XF@Qy|@1Q!%{Q z3Wi749A| zZK+TB25IYc?$!hrXQR=N{F-U7w2ehD+h4S#Hjf4K83)0vco57FuUar`CEiQ16?Zrg zkPlafLs6xPh(6M!TJjEle$+tZQUgS;CggYZwS*}_??c79@N3}mV1o(y(P`Q5$vy-` z567a0|Fyuh{!H-1@lq4c1Wz0w&ip_5`))>>1XV!ohJ{1eTixT~{3K=XYrY`jb{ z|JAVf`S!?*dnkrRUfeS(9p%MaP@(q|4rzn&a{IG*pD$Jk)pkS90wGKLHzPcc$Pyl_ z1D(G8DtxSAlv?_1?rLIHlSoX0@~FWV^EqB(Gs5xe37;2# zW7~49EaJhFr_M3m#ZnTMtz>2o?a_mJFUbslX@<}8c*bYASjzK~dO%>`M$Cozy0MPJ z40 zQ`OA0X`!Z40X*Ls*1XJYD&>lKJ_U=x+?E_$78XEYs1!*f=7i8lT&IyI8i5dBv|57b z%Xp!e6`=RSvOeG+-7&rPp)TuR;7T(pa2c_DOE}Oo(C%_FFg3$ zMipQsX~!EGCWW5|EZo0agC#huz$`PphPu@bO;fBqTbj0% zJa!;wV7KPSD>}*U$8U>D$9J;nI5)vZQ#0vEDmEF5rDr13Gl{9>Of*V>nVe`M?PT?| z9cRk*UvJeL+oHt@clb25aTwpHIh?+RF(ds&+KCSwbe-LDNg>yy-EvHW)~x?9-VOYg zK$8=56ftI6h!m^F7MtoF{wWyic#|zP8;kF{)#x zp0W7Lwpf<;4XY7PEtwHhOZLJ$vFsuhyD@O$ZU1*y&2F_eY){80-Qhsj{JYg5)!VS$ ze|<%P8g*``%=8GkqRi$}ievQ{6vL5(GArZvKp0+;@gwZyPKcZ!* zH!$2>{cb$6bbDlh_mhU|!!t{r)fB**_JFptTcxLfeiTBOM<8@Qc-pR*G;Q(nfVER3 zJl2JPU)phEuEHF*5Hlh1a!JZ*T1=_BwHQw{pbwq>-#?gq9iPoaTM^MBqokP4BFF5j z%0fSzl1pr6zRYISIM!B~>71Pt7aUrrh%6afxAUVT)1Od$zlR|Ihq)(T31Ek|(Zlz5 z+R;V;n_9|HN$kM&&M57=crSL@zYISOlYhjFpT)|uBqU2RKb{scGB3?1SLVw?QAi3I zLB>eH^)jPrB3_tJ!5GAGq=4$?t0z`Us=^9j>~qA7dF; zmV%s!uvgVFrV5?WnEo0rztJt%blzP%*6`%fBpU0x`Ny@CnGPW*x5)d3OG})LMxgV- zwBN%##mPGM|K5FO2YgE5BRpq3q18|Abj58{ugS=gC-ur0U!{n1v`u_F(-L0*Kp$#_ zR!y=JrB?kyBU+WTUsy$WWZ+FrlN$m_-ZxbBuh=X|6w56IN3Uc-qF|-AiA~?tr6lZs zODd7Rfu~y`J&Vy}l+yF7hNNdPDy%|!C`(vVNzY=`KRVIB(j!b$OpG+?F&3;QuTo5b z+a^6jP_MdZlAyuUTOmPWlp?MOC>uPJPJ&)eQL|c#`3BWGUztkdO0;uBz|4fsjH{Ig zL^QUHSxu%nji2V6-APf*@N8~i!~7} z<{OzsJD|R;nZ=DN*J6Q!D>eMTzh%m`Scpt`&?^;3+Z(kt-hZtX2(unq<4c`z*X==Odm%@oQrJ&E~4r zhcvaSL`6^t3%ux4Aq0^B9WFC)$-w2EaJkPsH8HGG?(@=D2i|~Vw+7Ed6@@5zu1;6< z(G0u_E^v0$z_04fUeq^iLR32Lr%7&?n&D&>q|)(e`uwILre*fAbetkekw+M6sX4`B z7}{JCN;{J=HXi5MWHORY#;2y^6Ny+n8lRd zIy=$|LM#!6aD@X2u~8#LC~CDg2xVx>VsC1S$_mAD{F>UE(h7w|)TnboQ|-;rMkhDE zwBjBfrf}m+D-;_}*4~XTtx#m_Y53pWIyJRInNv5U@8q<5V+ut>|5Px!@ud~_u2j`k`DMRp6YET_fHF)c3ii1Q5NL%0FwasmwW; zN&u-5TZuB%;t86AW(9B?F$8vI;j_;P)yikRsMfn%C((SCR<+(ut6H}$6sCMM2N|?4 z#hWvB3Na6pkoyX&orKidx*(J@l#`JA^hwD11{By@JjSQJx461#4KS2{HagYN)_j&? z(NClJBhA&;_c>~7KHJDHNGI(3X=g#LsSUvC9y!!W@SRrbKMKo@g!@h%k4B-YWpV?J zyGQ<=x#aHl&u#$RMr^QNSyQPg;824iDN_X8)>0>Ig98DnD*+7s&V?Uq)u4a_1O0@D zHLgK1^t;y7pfDya%l?_<)#5z5xgB!e@EOfBMvh2siLz+~Iv*r$nHaSWG_`izCT0KZ zVygG!>fF|(%+eXq;fpj-2Ch;-CLY4o%HB7JW+}GrTbiZXYHRAlPleG;s4!~?il7U; z1WS>_*R)GR=;AT#aHh)hM@`g5m*yBLhR&hnc%Be7a zZENfs4KKI4J{O?e_kqcF77&i9}RXzrs86xDg< zcSK=!A+h(yH{PT?XuVU}d)+j?vG;oA&3s_>MRkCveoB_Mu%E@Nq00zYyqZ6ljf}zY zo?M+?>cl&pcKD7*=r(YuO!x$2dY59l&^9V`st2r&oZ12-O$M8y+&(sPdL1jRP}tE* zA5XdOldEl|n^^t1_mn&$W?1UsXw}8Sk+Zy3H zPAfbI8Wf)6{t=$z>%!wMJkg0HMKIY}c*wgHOL%P)o^Fm$v!x7_a~ly`AwJQGEM@JK zkwQb_LpoSZ@swtY4*_MpdJ2X3L}<$C7{tf?E`>I3pWl`d%4mAt))Lu?@Dy2sC_C?H zOm-p^6U;WrPK0I_{d|)hb2wMu0H@6N&C!)k>MJ$P;>yqbV2VP}75~lqr%ApFbYtXsY!dWToV@`t%OWSE49{ zR!}|2(Oa8q)Ym;oT2`g-AjK?2#C>gNHLGNoWvzdzPtd#t;J~wD_AT5X^ z(9%VN6};WQYP{7-WjiiW9y~L8zi01J^+k~IPR#9}RX0dG6qCpJlsqqRPE77$PwMEy zts)ixLb23>)OxrMwMOsPcNk(YTXD9edP1p8KC`s_a+X`w7RPnw#a1$I5Y+xTebP-&hgl9glqx*01 zMZkaH8!GBVl3lsEZ3r3$UthQ`HZL64F zVSR%udy!Hb;<77AdcI|VHjzN`Y+pv>71Ql%z zbwP9KitEsv)iBui!Ea)MENuD@Lgka-LAWfz1xkAM{R46HiY~#`#nlTsn;4hSl}%QU4I z2xwyubm&Si0QNi34VPX`^Arm4ooteunTkbbxXDCsm6|6XEcQ4f49)UK_n_kXm#o0`DT1&?6X_{_Iue_i ziq1r4;#^#D3hi7+UY&7&wRucecV}bQC8cy4&pX+0=Q_)+Ez9AJ#|TaGrDR6H1(=t? z1!~3jzZWhKk;^d5l83k3^k5_+&_(YY^dNcHPjSkj%h=-Lg|pi79&qQU*12=d6(XLJ zFnklajQp5fMl+SF%jT_dV8XZP)_k3)Z6~T81rOXvE?qZLQjNloCCa3DeBF`NAF(;I zI_H}lib;to$`nw}iM-DAYQabGv|cgKOROx2xe0P-V_#E{jRP;#4qPElL=#SmzQIYs zF&-P8q&Rq^I_`~U6q*)~h2{pdC|u}uD8c;*k=#HOuEpU(95;Zxy;e1;{@wap-FO$~ z_&vSk(%C~Uy}0hxAsHMVc7YdsGx5V8ZT=hqks_oBZ$Zk$##r<0sTSSnz$mR2D*=$) zr`k@E+3~uQCr`cu{wb}vhjbK#;Z={eD5|eM^jL+U<96)z4Q6yM_@Cm79w`3D&7-)3 zU$8OeBs3HO^0h{x>;Z*;(jK9anj$pC28CvrT4<1~@-~N#^j&B>-90#THJPM^4juEL zl?|;Xw&RO5T$85-%u12=$1R^P6tU?&ZfR!P=X#HTnDn6c?rwwLBOuMJUhk1s*!DXM z+){#K`D=|So;dD-;vZ`s#jVbQle|p$9(yw}57_%$d*o%ZDe^Ma7C+%*cB@4Cj;>oJ z{gI|xB~;rDISYg=iFLBHh%7ecx4;Z)@Y>&{Cnj$pr32Dwk+^*op-V3X>1&57^Rjz+r9W~SXsUj zE)FZpPDY{olko6Xik=Fz%g__a!p=k_$;LQ#W+s{DBC#npor=ez(|jV9icKbFQf!l# zp<^C6IS^ZeW$2ipd)-2rBIw0}d!RY5Yky;IGkMqD|7u@4Wl#XSwTYrMnyX0ScVk{M zE7L3>0-iR#S;x5{FFB^z(P7P)P8Uph$>mbgm3tj^Q2^bm&}}-f=gzcU4r&vnL&(W3 zVtlx?S=;$Z^R7lkG^%9@o3iUlR)54<$&yRgIUtl!87uTNtIpjvlSu50PH;2nB+DhI zr;{@=ekPKNOeT|wsZ=taicX}`MwM%NHRk?m^YC9cm$)OZDBSP1K;dpEta>*4ONAqMTt*B`?TO4y%;;*m_En;gMzs9yiBnp|g@@J@>DLWZCpW z_qh=H8v6CBTunXB+V^g_Y+BnIKLK>T-b^lCWAN#F6ovx6J`L|)*~I8YF1xh8Q_0NS z?mLyXC$kfjsb`8+FqOZ^PEahPw#mGc`BR21%KI1j6cmfu{JGWiTeE^imUCaFRaEpt zI_f=Cu*e@D>Q9%Vr9jB zsDP)j5e@NO-8wm9x~A>?LP1@V>pY=p+RE}mV{3BQ+NKXs2~$^%LJKBgsw}71mt8Ev zWLb9U{4BW)eF`pTfmm&T3!d2YFXYnyq*cncWik{s_YFd3deuMG-(}{EtJPwLum`)i zo+H%F{lwN0g)%{e~_DRT9$GX+J^bXQVp{X_Lym!^oSzC8f9uz)yZynvK z$h)N^J!#ur-0c)?aYg#72_A!!Q^p=3m(hQOO9C!00E-Nd4E+zdIGiPrOi>&lV=&H) z>D`KtSTihT=eZTfkh~BdIBh5hDYgXqIK(9^ts8Z_w;$ngK)#C8bIK*?SVj>m#< zoclQ~Yo~aDiHhL+^)vFM!Lbpp*X|E?=veVq|DtKYF4AO3{XYQ&4jX zI^mJt&({Ggj5pvwf}#81vaz6^PcXJvP!I6ILm-E8h-m=s?B}X;rWgLgWx~x+Di!OJ zkByMa@Pj}cI1yxQPd)!lZ>0l{APIk%T)I)IB;f)?9D=Vy->Csg*Ts8L0=kyrC-?^o zo*_tm2;o9*kho7<50Jn8`{8RB@z{5eKlt3Eey-#LH=aECYmGNP!YF^4Dpo(nSpJrW zt#=e?9@v}zK7QSMz&Hp{A=;k-dDX5Z{!02C-Yzc|CoTOS-hqE??7Q-N?`ke zqk!_0jl0rSES`c`+(Q0smYFlugxqmfmL(xsBA?1aQAi3IL0&=p!ZU1YiCy5wwHk!> zXU^8&FOms7FG7Z}{jsz1k|+}=m*;9Xrju4?PSu-rZg7j<3}kJxlAPspvi-Sp^@fC; zAPa0}yv$}wPA%$B)@{+yv&73KDOUr+t@?9Xe51yr8Zqi1`gA3avMw$3-|EpqBWoSf zpKU~Mt|&{a;Dn3Q^gH8ALXN9T_&VKH2Uq8jvz*XrdulsdEQ%>Xc`iqu8s`LwbnnKy zwFg;|nSuQo((IO5fn6XSgGj;B67wxkY^HF1XwPmk+?bU%&+(37>>^#%#{T zG+&{}&4ahj#CJEC-=#=7Y^n=YZ{fci5rp~tr|fM_44|>v$R_>Ap#BR~l`9gl8*S3- z;qo@P$m9}$plc5-Zgl?~K5YO^4BDv^K6U&jI0`R?%K}{B46MLuTn6C+ zRa<+2$$DU)XV3qHH=vKsr{Dswg<&UL4_J@z7INtV=cEe;GXNMFo+p=)QTT+X(}IN_ zy}cPIeg&c!Ea3GrGg@3^IdOTMh#sHeL75h-8mN@b5RuD>5~^A;M}C{HZW+-{I?>)` zfnOdcQpcf0&nJoIT9CvN(KJQUCa4ve2~5b$Ih-P#<2)zGq*5t@n--5&g5DlW+7|*eG~j{uob8=3Fsjfj?N%v zSbsmA5trxFLXOSYP%x-J1!mLu)2IQLk}RZ2yT+eSwIFX%l!W7APG$jxQKF$O^jG=F zNc;#HC70&ZPRMg?k$_$cK1s~*HX0ZZpga|?K_-sp^Ye=Wbg<&yN}@(9ESjTvJS)h( zm6-b@)2<|)Bu@|%CzIjVYGl&2ktvz!BFHD=P^*n8*ER^X!bA*siEsj6tC@(gnbS-x zoMlssL`Lk$bL@?9P)`!H1uNBrIa-5LGHlMh1Hvdwt%fID-%jA$jz7ilCxiGAU77(2 zbl^V62Bwv^iI1d^Y~0$dp=~6H%9W*}dDKn~#A~;$bbGqqwtD6&Y7aZnoSKN$ZhcBY zx8)~`%pN#UKg?EA38)@R3@e()^Rclg%Q&rE<8)WU_c1Mho;?@Za+>Y(8PE)#XhRF^zfZKg#iH{sVq&>PI#aVx~=x3uY2A3TgXP`JGmpvM!DF^bv!qk?+Y(>(Xz zK;zE*BPVtK7z8vd=H$+*6`b>p|Bz;o;|#vG2LYNq|B@skRx1DZYk1Y!G)w6?!x_hp zoWwIT8jX`axFkH2+F=eHx#~OAQ^?#RxGS-$Gux`U;uZT5@eM@^ewMrMhXge9?A@Sr zvsZi4ajvW>r4nv8zaDO?dS`sj;UmX&UWYD)z8Cu&820O~sI1vB;U;go^_Rncwn%db z{JY*%b?I~4>j&CCf`yZIF&eU1NVM&6`~R>>>V|CSw?(2?N`vM^0DI#L*tZrBx z>)_p?%C6wolvu)3SmvN1-~Zg|sZa&)CmzWgOg0?BkIp)*&CAd-V7K41cY)rCvO^Y5 z2ecv-`P9$s@HXIhUfs&5T5&@BT#cb!v-Ymp4E1vlSpMCx&4A~QUCZ-m2j&Wg6ACh? zP1s(mINIp5d}!MIRiL3Q{T`d6mO+L9%eBKLx^t#{uVb=mU*`7kiXjhYB7-ddNj~N! z6;);5e=~^9X*j%Gpuw6Yb6T=9>(+b068DZ~{yFxRwNx~Pr{F%T%fZJL8}}%<$yOz9 zW#PYZc+JK<1y}Jk%!xOW1m?YNIKc9G5->RO)eo4nwcmXFai4@Q$395~Ry{t33}=BF zxz;6H7$00#c=EwW;PWAl6AEI_nB$Ta_B3v+z0=vE$aP1cQC*3*p`}d8;edXGeI-kP zIm-@tHlwn;fdQ|>9(Enp2#^v_E11b{?7w?qF;6qkKGAoIevPix2^W$D+z&o&5v*|T z+|?Ks_~`kDl*U8>o1#6BC0{VR-D8*Fcd!-YX`a)>F73Q0mBX$zaVsaY&W>M(M%;7H zGh}QKYju>d*|VB^%T0wlhaX%Nc%aXsn>l{+Lt5B<2ru;O}N!rs6&J>Q;x5dCnScgb6( z7ay-!+nlIoGw=HjZ0L9BE1%iyBLxm^W=q%+p@MJvUaq ziH)H%`9P5Kft!mJp2sd=byBFTXiBtyafC16ti&~G)=8y`n zwmXV_aCpxcaJFJWx%nrH2>Y1Sqo0KgPjoV#4QxminP9=;#5>esyeS)jWA z(|?8lZ+4FAR*#++E({E-#hHM7MkWyk#1SdTa-btpPyy0`DFNQBY#>QSAhZP1ir~{# E08GY9S^xk5 literal 0 HcmV?d00001 diff --git a/design/PaPI.asta b/design/PaPI.asta new file mode 100644 index 0000000000000000000000000000000000000000..3487f173664c938d0f1379633805e3308ef5b0ad GIT binary patch literal 74250 zcmYhBQ*@+l*R5lAY}>YNr(!1^+qRRAZQC|FwrzEij_q{t*Zb|0T?aLuldAFDTKAmS zn#ywE5a=KgwQZcIgN0i$C)8Et_U4)3%M*1_~OE z%zWNvYJ2F`9%BoS6-ChSa{YX*(beYTvnXSfV8@(`Ey@C07-~-(lpu_nL`3QY0uCFB z3RcJ+YHclwK=EN={%`ww=W^HJfapU8b7yN~CzI=8_a&94y-zMx^#(xU6vS)oWs%FJ zGd4QpW-xkkxbCU1$xgrO7FI0Csn!l=pvjCv+21Dhv9R~`QQAhd+jjh0rK%_w_d)>% z3=aj6d8kb?D?v6@2_T_Fl*!3A@w4e<-cpSH!6xHZh-jVjwV+6HF6XH&QZnl5$|4}| z7x@d~d@T|T3wHXtVAPeux8~!Bm61_0OfV6bE>%8KufiV(4XMt^xZt$IQw>?0$iOqD z&Jmh;R&kU%iZ`W5Hcf%wU6=1ewxh;Ep#u* z?0v?eLHQO9^eAwL= zm8_#^SOWo~^oP*jC?I^i6)2#bgK+YZ7Z&2o{zhyOuF69Yt8;KQ4o;B>Qza;O*cZn! zkRrKRGZ!u7S&^^G16^T(wQd(0tUoykO?nzM`Vf4b*8c_kzTjCtx?9+dd?ZnRQQvnU zAv`jkys$w1O?W5l5Boak?LE=EB`EaKqhc)%&5`y?yoi*n%FrtQaXhx_u|cFPA{y@g z2C$0^4M%TmVmmx0+2hCByNMS&ED>}-S|obc=#ThATp2PkYp1cNc^9u1dZi}4S{tcK zyI#H7ABBw7=AZHYXqOwxEv`mn(8B0@AcSUPflf8aP^wOYihyRb6XI%c>X{izzj&f` z(-X(IX9a*%7P^8Tvr;>A4`M^N5AlZZcJQF!%Sm_%Df^t;5(^JwAl+;-!;=&foF79) zi&x$h478vIA;MF`ZweC1 zc}0XPfSMD{Nk~3Ov4gbe7n@a;*#24qrEZSi@vml4q(j(7qbLIb)d;BJuP6-^IAPy}OhF@Kf))SpqZ7xY@mI7M|JWZLhj`TRNWzf->c5 zq<$)tG)iI0;V@>x*)7>?Rzdu>IA+sh7k_;u>Yyzo> zLPG0kM`1|I(+^9BiFCjvtRN2&oKci5bHAuwr_Ta(7N0ijncz}YjtQ2Pi8)EL(Iu>* z&ZL*%S+UK;{P59`Ga95h5`OqP+PY+?+u7RKSl_+*P0J%~v749|J78{c4^1_kK?NZM z3(rha(^Sb;u+bKH<&gz*<**mw#746!Ew52cfE%Hjsqh6^H$2c(_!)e@Wa|(9{fC+9 zTPTZDicrT4HZb~C5I1JdzW#8D*~otF+a#C@guL27#T+|Kj$;~ohjV*CXmawWw^tm&Ht?gOdBmh}_w zX=y8#zuN_PpV(@Ob$7oaHng`+P!Os%`*bW>?{P0gPukV;f$UDv%-W5%=3x+}(P%jz zHWGVHYk9z(8`k$Hk$s-NECQ&}e)OzTBp|Z#lYV|nKriRPkz{_Q{~3Nj3Io4F?mKg( ze|;(7c*zfo^YiV_skvVh6M!L-1`jbJq2|SOf)*mY(X311IZmpfs!Cc`~Jc*PRcr01oqpttx&XIj-)^b+@?715sI8KH%`we0^Dbc{4VJRS zZhnVrr!cJB^E`KpNKxS^kYWdyzDQ&bA6K5(jhR|6DIoaZLBOs9;oX3thxLuYUS_e? z!>l_eeho$bL%K8BoTZ?O0Tph=hmUt(>pX1pdWd|BU^yCiL&u3l*_tH+QLhS`M=};C zyU=?3a;a9!0BT6ec3Ucl8=abS_q0Y-SP#1lGeaaU_V}Uc!Tc8X=gM-Touu0dLLj^)j*Q;$;t}<(RbJ z@b+8qXbt!4DQ=KvV%kj#-1G4C2`Q$l;-^aGHc##&`x#`281%8`VG6n5?&m4fJAEZ7 zOBIBmjzxoUD8DvK0<->m&zr*jbv$_O9yUA$n4T#a^vK}U=%LP?OvNkts|$^ht{hay z0jWGpVSzzGXCbt0ckzo43hA_rSC{!xxInpl4Us-vZb+j%H3UMGrT7xmel8o#t`|SD z2WMr#!$))drl8Hn+Z^>LUwddnrj5Fo`ga$J15;$3-_=AuS+=n}Q!vA)pNr5^LANRh z*7OqR)!T4B}L!8JWGSD1= z5cAJyj9Nb0U>x3r>n&k~XiAVkX~U3`eyA$avd{AZN=uOBd+yh&x1El^1@nsqP6aF3 z7NG(VQNjh7e67B%PH^&5Xm!2r*EUNA0T8aB4KP}LMC-7m9QoVpVTps5;+=p##U!zL zj^iK*vezaj(DXs9K(1IRd-Qpdt}HqKzl8;}i`*EMBO5rUNu+`1iR?WG7sUj?{qz|# zMU)HKuMgpj78suXu*z^9RM&3XJY=IR2F?uABM}0%uE3KKV|_X9l`G~Jo=>T(n%)3T zJ#kmZ@UQ1Qy;=`61;5IKM4;4w1XxrqEW0jo)d%G>jAnz;G+s|_J?dP~`{Jl7h3jl! z2`aoQ^LxF}zk!A;XvUoyVj40snjY_5x^c?iLCTz>r(|1h&MU)Qn6Y}lmX8pmVe$3h zCG&~xW2k!Or|+pm3zHR&?Yh~#z;~4R2o|KFLmEGBt^)W!Qf?HwP$slFvO@u9+^mK* z>;;`I43Uyz^k~{Ozm42wh3h*Rkm-vQvrJc{u0t11p|2iMAM@?%BBfOw&>ZB%-Oun7 zcgy}biPscbXkyNb5r%L@>qwH>m|oK0`0sJtd^}GSQeUf&j9@*1x+Fhv z3K>O&Wx%JyJ9g6~cuw6V1Fu0Y};v+SIF^6dl1z!>&G>~H8u?WGQ%J3dq6wiaHwg0>7X z(Au}8U3Me4D2gAq?a`w1fe%f8p3jg66(~8*4%!G5=~!Xq=kh&Q^8~)neN+av*wi5c zZHM0{l@HkoR#yg1&3S#NL?~l}-?ESn_HI{YOJl34FY~n(!7=s3@@YRyPG}&VWRvU@ zVebPJSkd(v$b%+6V(=1i89nx7=hG3&$WD=^$i`PqiKqQJBrO6-eD)II4L?u9!3`ys zO7@R9z3k7ut+}xbDIy0fiQ(vnpVY$op_I#-X!C9#3qVk7GEL)m9dfQzS{AcY@h;^z z*|2B}Wz{yB#qrVP<~79?>tuie={kuApFvV!ftff?+Erd#ZrAsg5SrPp4E*gZ@WTk*?+=}KC(&HZqe zUI(lUWf4#U1-7k}t_kB$U4QNZ5UAXdT&rNBEdjIgR_kT8=ynA1J@ zR9HMLd)TfP6JA&z36YP%sAPR5`*r(Gp*2tPMJEJ(23k`fDnkiDbC%`wK6DxJ9AIj8 zwk2k$nFjv@U>jqzkbMfO^97{{h)pCw^_nWsnU|I^cG#v=D4y>Hlxew6>0NBFQ)WTM zEokpR+^H;Ovcr)9=A;LiUS{{5RBS9;JshJJXLt8i_nDfRGge9GwMJFS&zZAna0 z6AD}&_Nvq?{CkzUY4Nm&I@XKs(wk0wX()*!tEzleIz(NXp1?VY8Ngrre0##1`~1{` ziS%W} zvXML`#^n+c(p4;FOj`5d!vftx%p;KmOkrQ!9SfynL+FGrf^*_iS7i!u>PgkFEkvC* z?!C2-i$licoP~`O!ppHKWahr*Npr{_l;e6*l)br%QbFGD&Bf!BjhRSww|pfA*t(V|DNj@;of*BL-aYrb~Jb*!e=cfxj9CXXA_UXyS+Ll-0)U!?n2yo>5 zdXeZ6A;JAc`G-I))wNz=kxb=kLorWx=pMEM-^m@C1FVN@AT&js)%I@!VwL2SpEcS+ zRmp@;1za?~67wQzk)t!pWQBD;He7Pm<{T~~nEV_JKCC6BG~3x?V;Bh~VMKHXWXB`@ z^yr~H2+R%`UkrR0HWG2+vfXDF#vX5?rlHUJ(2&ghtfjfJ~SM8p%xa z#Z}PX^ll# zYA+T(x#zCK!w|Vn*sm#fOZm6-Hw87uCefMSnRf2@VT-K#@o(m{v{{5`l{8Tf7YI$e z*ReoX`IBpqC+k;-RYz6Jbgd%>ADy;?HF^szf#{{kC&BJ)2(%gB zJ^BWD@>WVPwgdw;bV-|f6=W>>njXWRd~gqOVi;11w)t}pl4;zGT1KpDio3g=uR9h7 zjsy~Y{#>401<2+v06Tnss@5-3rLz*YQjd2tDmNoSpR4>l1DM}IfC5Q60nvkxPR-x| zp_2C9BIjLcmeG9K`0-+{zZV{KUnNf%VV`uPuV`C#Hf368zP-FxUr^KNbh-HwA2KRA zt9Rv#Bngs)0p11#)Nc!Up9Lf z-{ODul14hXk_Oj-R!<5Uz2zLn^Nm(N%?h11(Ot4`T`tWG_W;xMTlY8}d|c60HfA0Q zSyEEcEhDNF0X{icpD7P+d!c z){83DJw7YIV#cYgYCd0P8(d(-yS$wa#Zg(W{L8gs zV+OV4SHe+uSk|}7+c5}-&vAb;XCSj|QXYv?-#WGWG&)DOb}+TyuChysrqRJs^ynre zY+pWx{K;Lw8KXv8#!reTLKF@6=8@+PIKUgLQ6qDnvTX)a~ zR&WUG#Dmkd6qR{tCj>eZ(6t8VeDE4Q{}a0vk6lR*p~AK$>BgW`O@iBgKIo6UznoR| ztSZY6yIuQ>H;f%Ah)BRwmojKXv3IEvxhn#kdHIOcxw4^2zY1}k8u`bBpU2tuKaZhl z`*J)pd?+L>!C{ImRWZO-My4BCMfNpM=7v%lRh}6EyIs#65ZOiwfCmOmfNFd^@A=+@ zG8=$Dk5_yqj=$h<_A}rC$7b0V-r!3%CTnhB>rGsNm3eu;m8t@hT}5xI9PY<}jsUCK zXAStW9?QR+-8N0jgA0+;T*)Ye<({jBH##P(;x};hwEwP;`rlV~HU@(EnDy%LxHMsz zuS5>y>ok;_1R~1wBABV0xhTwHexb9+Z1$jk|It?CgP7|f_FJ1k1FzCJ&|k>OWoj8< z<`wlBIa6<674!Od$?m>&SI5HzIV!1XIIHhyDC{g=U6;O!>GQ7M3^_7(2sHt2SX$rq za5YG1pf(lt9oq{xC!kvSjYu^itNrhqo^RkR4ZmR>EMRNc1p+bDs8_Eop?V-79TLaO zzZ8HKqvLNJrE#5?NH+nu7vUuTboFox7y+Yi#FU&1u}$W5q(uV5AfW})MSoP0zC%{REzSlUPzjuibxm4QV#Boor`fTJ(nn}H z{qL)+lbCB=Lxz~R@$e8=yy3T&nWg7WF^-0SC%x_m#n%!2`b%zW85(u_3u37yRlI{0 zN0m%$hO+r_7%L9#+J0|sM#?Vpuys#)u0`nNzf`tbN=GV^6N3s^{wF!zelJ-w`?TQq zqo^7Dp|#B|Qbe3~OG>{k0W7k}l+(om@1}&83nKrXPZ?I9Pl@)AHH>{8-8}j7pQ$r5 zWqJ}OAbnceGxvRAS=lVp!F?Xr$pk-?u3{h0kd|cmV$t!QPh;Wbu0JpAyi;WUF#?-Y9$0l`FmPqfQtc1-?ud>gG^@66mg;IuHMYEXtyHoN-XK;X zkDRoWpS*bLDxeM(IW9(-v=?hFg`t3!9rj}yFUoGKXYq}qg?Ca8GZbdSf<{gFm!%qk z8cYHh6`Rh^vLd^ksFTwKXPBZQ3DuK<`&(N%uR6ao#l))1Byi=1z^5v@7W0+Ss_Fn! z7UGnA^6I+D%gT3-N1R+wk(eUPl*e5A6%5IN#?~d4lQVPzzcAa&cv2`zv6QxgJP;*=y6P^mJu}Ps zof&36RpU@RmS zxT_gsWNIP1D*-7eNSy%}6VCycC-XM=3>LD>)pr2xX+IkezKUt6U-#xwl zfq6l))Bm{)#e#=~!4Z<(n5=P6bf@s!PT2Wg9rBhu6(`AuR%D_FO@&kmD zCzi&HhA%{FGMJ@OgE3My=`nMW=<%=45m-r=zdJ|r*s|mFTZiaAoZ^};f;ZJIj{oCVT5$g&FkdxPLgv+RcE8Rf z`Hoff@dQa!o;$XO{cC@fcXId=TTm9`z3UrJm75EMXGy<6rH{@pKtW*{U4tH-U*SCZ zye%j`@%eTjG{N%@nx$ul9Zy;e=o+F#95?Zx8Z#5`;2h!3dRG1YtsCeP3{V+zZj`A2 z@jEYH4*eZc{sJU;vh&359pbyrd%Hhf81q1YcaDA7aX+!kMJPV=mo2nZ5!QEmrZ#sR z;p=RxGan0_mlh+ZS?qUI`b{~PLpjDSE)A^%($>>D;f;>BT>GRhSt;>P9;KE{Beq#R zHwcjd_A?x`r?TXU%B1CTNI5VQuwQ*|742F5a7;>f>)R_eihld1gpbfr$0Ff?q{pUs za?vbRqQECL;?Mw@#aU7}>73Zt$8!GVCQHat?Aq8c6;V{)CxIy|@~X>v^ZIIzseu$C zljE}AR?MVb@FwQzA5tS7&gpFzWsB35bPmk|<+ES}<)^#}AOy`knX`zzv_7S@S{bjd z$YN5NMb@{nzjBu0aA{;NV{vX*5wPuCFNBHq;l`&4Qx;{=1!R75Dgrif9{oO_^5)@P z?{%yvk8gH=0QLDaVphmyAA2j>sEoz6h0=wpOC^72)Fcu8* zf*QE~(|L{>92_UGlT9v>h?xzmI3tWZ)K(t-hxq!6e2Q?IgKzc*cG8elEoU$3-pegwMQmzYkU$?#GU_1wZ|MOfCdqY>h{io?{B%C+3Y`30Mc$KHm3^}d`S z>o!KIrag_eR^xER1ImB!$Pz7%{~EG!J=LZi&;3idJvUtV7A@(Rv>WBeuB1%>xkT0j zS7s8!R}FK7?OdlF1kZkG9lZk*_Cr&>ta$VYZk^)1bsmin=GeY+$!imH_g-S4}V8J4P(Ch+_s5X5mRUIIth1)@WFrfWiS%nN+_`ub;P)?8wcSs78+L=ZZZNeMlp&-5+g39$UwPE z5~H{{FK&tVN<3kpon#_Rfbk6(renjt{2Oh>G(=&u&p$FUxh>8SOJ--ZLwF|@#=I%q z(8e|H6;c2~PkCh-8>N+BQbKLoZqz!2uA%T(F00>~%TUciRLwUNI#S;l&v=STSL{;7 zGXRcd`0qb?7||p0S*%WwMn41wd#h(0L}GVgYxR#a9kaiEiN@f@W>B!jj=Pg7=(~9d zgIj0~9}0pbZS`xlwO|Cm0f_uT;AzRtm@J5(n)&FXA)A%NgK6UG;nkf%wYba8*rzJ* zY+xwJLRE`EkR(|Toj}V~&v>=o090u(RdeNjFpubCFN5Z_U8wNLuB_dkA4M+a-a^*; zzI!<_w>y{^=yCddZcJRIAH0*R+6#R#8z2`~K<=fCZ}xD<6je11-39Yx3$F}PVF%|2Z2#drRx2+LcGp+4uO;~oeCzfP^p7b^d^{f|4R z#|)Or%(18WlEl8^2)TeT$aC8~;bFDNcjLj6;Qtq4oV;l^2`HS8C2+D-VrTTBkzUE-co)(LyJe# zrM_r`j0p5_-h_fsH}Tje6cilCgqc7C*@G-9UB*xA5O@Po>Vws+xF#Qh$m+}Z`hO9N ziZRCE)TU5wWeqi|bF8%moHuaA=#&}g&z&451OFLaS1|OKvYp(f_ShfPgl!;EHSQj% zs&tP@pq<2uGLUex*{?nEn&nb=&OdbXvd5=zXn7z8=>(Ct_a%1$A?a9%lT&f~Xl$Ni zcoo{GyjIrd6w>NtTV3kq-_{=)1Y8i1di7BD7eC zFwgfhcqOYfC^(7w1D+vz;sJS*oIGgK37mFzWE(ARr$X{0V9b+N$~?M>+-ih3546X@ z+!Qi5p8BM=Tz^RrKJn>`{241nqTTclD&i4V7-GUr z7PzUJEE`<4LmL!W=|s*$Qge_f@1xii~#Wfhm`1{7`l6%$%`|G_WU#btK=??-oK z(8RnFzD%i8y0DepZejg6euFsUZKG??HED(pC}9nL7CaV}Aoaw)6S%WTj`1(Ocg7o1 z{fm4g76Q7Pge@EkwBOWv4`%));%22Lk?gi9@Ga;fNSNls7T_X^yT(4n+l`T&8s@a< z$?;F%Q>yY7X)!Nw^w{#J)UNDUBlrA|g+zRipOcm5_5b)sqEk~H(xL>9VA+#t^1aRv zvqJbEO+xvJs&pEXX4?dJwM(XMk#E%)7ogxh+cM(7%H-8&lWdKoP!lFcx;TAsVYc&n zTD@}UiRIQ>%hTg0N9rb}j`^FZm5!aU9+Q6q;f{YW>wYswlkYIzhpdS}k!sBE>pwhd ztJXDAIdN{Uj?#_I5LJx5KYPh(T`y+NM$J5O{GFT+T1(X?ysdv=87@tQiOGlB#lNm& zQ-@w2v2o;LOf(U7^Zem(Q4z9VdPm`xLoTOjY1Hdv$j-wD(eK#)*gt`^O*lI!0z*rR zKaE>E)3@>@_8LS>^Dn@=I?ysc85`QY;Jub~#dmen>qQb0qN9B=zN@N+E&x(*;vZ^i2jDx*-^j zfR|Z}wk~lYv1z6tJL_ zGI5<}!A|X7YeXPd9x2fgWHoSpN)ZTbXd}&g$$jpUuvoC|LFh>CG|bGC2d~yKmZ&#;en^& z!G9F#FTpVZ6qAm$$;>!-byhu+K@0j4PF5MA*2;&i@k45Q(Se#*Cm$fZjyWi(=OkLQ|YIqk}N5rk|Y;zF{09whaA0d2) zBzj5TI?-$aPKNQ2vR#JTb%;Z4!AQoaF1W<;)$}dK8vV8i74&~p zP2|Ula-?aa*Z-(mmRX_lp>8;DmAf=AXYQiO_~4p4R%X2SqVgCSZ90A^uV_<=^$=9C zWg+4QU`3Rm2X%=#+CK0vmgeJSEq(h zjt=EEB+|ax&vObD9E|7;uRb6jxV3^yIxi=!WDec2VH+hQ#VNzECiz2h0c1_n#z+yW zA99ANY=Q^v15oHw4A+ilBDy>^s^9__73BtR3oSpudBnQ@?nM_+d#>gQi+nhGp&e5` zfUv0K4bA{OwyPb9por{a_HxkwoxAP9*gB&?;ySsqhT!v6L4`$vV>KdWAR78J`fwAw zb2q1Y3hE}uA0rxr#|Cx%{c;a}1{&Ke88y0r5cIxOSJd8OmBXP77mTN`Mxx^yo#UT$I@VushB5JV11|?{Mira0G!3G3 z2`-KLP!AQcvz0NkEhL8E+S06*nmQt#&_CK$Rw%Od=FJCPSi1u%E~lm1&H}@8O&p2c zT}mf|56w{%8tfS~xDjk1T{hHZLSqJp}5ead5QBx>>}-s#XqtqYMf7fjRP zPbXyhL&eJI;_)Ysjl~^W@|ChVkc`j?RZM~DipEm=@wc2e(BWYy(XX$rkvFWOOTyta~6I1OX1$WOTH(h08F z3D`#7=a^yh@7~LN)XtW?QuK@yi!+UI+q>$o3ft*fA5$C)>tu;rK3i z2Pi}bTSQxZ4Xh*JZ(a$T+8hpqo<=`QDsqNlB)gQ$7(phwpDUB|hh%uXS$?0U{Ot~* zX3f#jx{5dF1JR8{Qci-}@9sjhJY#%ckzctnHyQ4+8+@|G*+nOfMJEnNT8e)yLq1FD z3uf`vlHvUEofSd%;;CBAvVF;vx1->k>4Xo}Saa`g9C8QhnLXD#5GfVMbbFmjI6&iR zKtHtYhBe(?1|#wjIPME_|CjsF6Hxh|CgeKw9QYT)Z9xGyyAdro$U~&<2(*x$OvGagT3c`rg?rcx? zp)s7x@6ni?a}U>E$-sC^@)I;7u0p&=uZYY4UcLi!4y1%}-$ZV>cICIQ(&m^HZ1U;D z+9c2yvj`II{YLyKp8K~^Fvv_MaeVpV<9@a2maVw(Gi7l9@W*QHAw0e-xQGZen{yS> z3`{JkRD%a$eTVLB;T;lf2)=M}K*aLB%Zj!MwnT_z;Xr?m{5-XDtwc#$JZp&L6}BAI zc4%|}pV%)DESB5~)z7$Y&$kX7gLA+#K~D?fOrKZ`dEcCIC<=Ouo@ycsyAlkKJ+Tbh zfUi3%#&ZR(bb2Q@x57_^|7cM6V+zNVA%4jmLv<>=F}gxnE`Q)xbiyz_rxmN6RL_Nn z!o!&huev!N4Q=xp{pYE}$?Q@kT#O2~qm`Aj&2!2s8xRCB?HA>?sqH{BVHMGqDg<+( z$b%lFGFQ2TdPyHACD(=?#Y}B%=21z?Ce&mG5BI<*}_H2em-3>g|Uk z)Q1b`Dz33TBy#mX{EG0Cxg7r+3zM^ErY*X5v|?mF>6!c;N6#Upv*v7Q^l%&+S_?d> zFXyvQnOxRE>ac-^aO@!ODRnNO#rQ+qSixy!F*`#l?f-D@OF4G?lr&jDZyaF}cJ!a1 z_mHkyHNQk=?`w}-c8=|l6uD27Ph>b_Y;r0?oHtL_G;OwMIoQs!Z;Skk7&vvyavexQ zzRq1^{)+M!Yyv7bk0s~$Cr8L#{$;=uLAe0WP4w?2?tuP&k)@(M*s%9v-3+7>Ygzi*RPYdF5gOK0;XW zL!zayflq^^=<7#O4Miu#f`eQVq(YLeH|{;n#O;OLQe|&xnV6O%?ab{@S8Yh9f7v|# zLteQF8Cqea>I8pCZT0!bhqV%ELxjkeZUum89ZM8+c_+2^(P_DBJs*S}|G|87g4ZoY zBL!phk;_ZGzAqt=*VAM!ez!ZUZs?ytDM5jS9FsRXT^up~1Y)w-=9b%YvuX3?de82C zWk#i>_ko4Nj#jMIK{%A2)$p8`t!Uj5ZBS`a8&uTfzU`$sEA-<0KFp-P3T#B)f|k$h z7lmCw2M23>CYXn~5I>~BA@w^2UYedAXh2zLjMY&<%3NYr7DE;z<99=4Df3qoDa7lu z#VRA77e4HO>~opcUWzM;;&5Ys6KJcbNENHrh66~^Y>((B3x zXfMg{YnPpNemQe~5pa$-A3xDIu3^>-E#Ki@=jvVdd@$7_)LZs8RVLGy&og7szf8VD5t9mMFPANc10I`DxQ7pYtNlR^uoqhRkQQylRbC19OZ95Xo|qa8}MT zEI+`PD06yTZ~9!zisS*mv3XC%c?`e1!s0jrC|xj%(aAd@$XhBMcZ8MaCz}&kW>|;V zKg3`q7Au}O9f)0)65ZK|9{x5$2w}mkcoi2`1X^T0S8Y4*C;8r_KT^1S%~#?HVoPL4 zo#3!L(u$SAHjvae?*8EQu1T`e8IFuW|76c~ET`V8`9R;f5ae*v_&O7{oWH%oVhCKk zAj6LS76Li$J2<8=F7O52oh4I7{j-PL<*>|0G$&x=g%E>=LsJJ@O0{{F z7Vf|Cdl$u?=W77ifpAsI>~LP|!wdxSI+cP;LB}b8;mo!y{BFs#c1#hJp)o6wA5lvB zbRQOJRiX-Z!Q(s3vgv;pQUQjt`NfAPxD(&3l0v;fHRSN0W$Ub{`ZESnspMZtqK(5B z4^MR(Ez&6BV;2w2ZtOO*t^GpAZmA@WoI%ICCYjiJ_dh_6MOH#(xWTqDP59(%nH zL9k*PakAZ!A*hrn`Kcd1p7r3V%V^GdQizH!4>$@PPglZP+LAynqgtBjMI^qjZ3|%Z zye91`|LBP`{PqPAElCuGOVkbNLo&GKt#x5xiJIKtGZTa=OT%yIhI9t|iNfub)$&O9+wayQQDa0>lE&WGNWztmLl)XB0xfFk#duIB>H> zQSANxmzn>`EczfvIRN{jY#x*1TfE*P9IuNw>iAF2@9b#i!XW!!HZJeo=79pzbr`Hf zjA)MXN5Rdx7YCW;!BHPg42O!^Xq;u1AQiJ-c`LK5h&e43q~U>gPysPZ_?h^ffm$~E zEk|>A#E;+4rYHNo#rI>NlfRr~1WnwiCc)v0EfEQu*ZQ*`5eY5*iXzpTvV@OFiyBxI z!0u2y&>~+lDUpdhVEof|^=V1H@pPjiKRfeG9$;8V&0XA)MMO>tl|)ac2~h-QAlgtf z8Go;Vk{STxbyN9u1!C}Neot`CxSUJUB$C5^d;cyM;=jb8?H93a3M^w{<38>b*j#ZD zi_sxadSmT*))0ZOY}AFrNp!E025aMpQ)Plf&msjWU1{6(6v-dtGew7%np=DXO*_|= z@jfS5fyhZVVl0MgDN>MMQ8CfTA(0^|BXkHQA2-C@Nx3#u?(v-qR-n5&9nxXTDtozC5xvg zDAJ4vT1nzI9%*F3X>}%Gb_Tjply?rT(B?hNx%OvMm~-`Ip!GQ?&4%s1@K|3`iJGq3 zFMw<3hypL7wrw(4b_nLvZt-2DrkHt^NU-Rf$U$E{(B7>FqRkQnO@yl4~g~LlgIC`814T^NwXNy9OKQ}m3AlqF6rYuPo(KB zZpOZ#A>eKI_?%lU+ZEcy<;Q?vM~|~ck;~I()g&=hw;qBl!=8J&9&)+m`4*p%kz!d$ zCr`MMkD?~Rg{w`;7^D_p*KveNe5keO_-C19BZygZ{JyUeD}rARTuLtZ>FhoV z9MXSwX#MVF+wZn%?#yn=<`@_^T$;x#r9x58im6mH;NlSae8lru()FnB;Z#vGAo~0> zvY_9hh+>>FC~P5j_+yPP9GGV~56y&Mx^m^$gj|4M$^+~QH?R@Z<3C*Di#Ja++E~cB z|JSz~P#h~L9ciH}!PmBB4I5X)B33D{I~kf9DdX-$jEnuL^q?UKP#4xD0#onfiniDA z=LJ0JE++;7lC%bL@_%gYEK53;5oSteUI?pwQF47h>2v}+b}GeM5ALdP4qTZJoTX^! zcl43g4(Al>Y0Uq{otR^->vhs4TQwmuJ`GwKa|6(GWQ+<1+h1oqK|Ygna)$1Z%7MLqR-{z!FrqBv*9s$NT zUHvuJ8Pvha_u49Q&v$xm(!K>190Wf91`zi9A^n$&Tz#{q`uLN~7T}@ZYjA=`qkIt4 zW5C!ek+Si&a`lb#((hNmF1g^{2_cX!b+vZ|k5MNF%+xG>JuE?tR;>cbrR}>>&ZTcn z701TLV}N?+5eU(8A}P=vMcJ#yDL^5bZGH)0Q#qd9hvOW!G&dBnXc&nzTKoSI215p%uORC15!Rs$R8NoE_Vc<4geA+37U164tWhb z!kAzSOK1NCCoUnf(=^;12gC7ph0`pZ?+A*=Fe=+qgMOkPJQgLUlk*jRPXRYK@OggF zy;mP1rSLK6p>j<9Q_*non#{>cy}mJD(k5gQCkoa`Cn$xlI1p0Xf2(YOgA=Kk%N!4Yo8(jOZD~3Kp*teuv#)ke4=cYGE*!r0+R_kA<^q#F4nA{8WKdqTf!e(O96iLT&snhZBiP#Wpx20fFWDXLy@n6j5@q^1k`!i(%9t zq)?N_>rZ_@81ye)ZP(X*s7@|m>Y00l_lh8!n>rpPCb-<3jacm2yJrf^$%a(HOg#6% zHOAG#F=Y@pC%C*9a^-rEdRR5c?D_Ty9DB%A7S6&(m0LIZNry0bSHEVjwjgO00ce*s zMM^2o@F7aN(@Rvhtzzt6;}uk?S;41z3R*2S-7{BUopk&2^%z1kb56 z5_UV-2kcT_(3w|We|m6yIja&490l^HpMXX9((8)%%{#$6MCUWXS(;rJ}L9E2Z=gFWm98akab*0`e~zvxH;{)~3UY z`c(Ko=sR#yoBjBz(&QS7@n!(4;NY~fJPUm}s z|4>Pzvo&v9&6$sl)!fu%G&4{D;`F`#QCi!p!?Olz*U1UKC63}9f1mksO2kP|X;8yf6EBUVm_W#g4Gx?SFc*I8Z4oqyyo%k9%K8 zgmyUA2b{>)!cfnaR$cB92q8`&{n0r_$qB}5#|az_D^PM-t~Ik06LJb9BC>xSl8a|7 zT$|Ix4hGoPxN#R6Pbm?-Zh~0Jz2zk3KJZB zHYn(di|27lmGtLt?U3LMVQ@cL(#NIkc8&WT43g~3S~!B_f~{T}r{lOa7LtrE$#egu z=}Kh}YFl_Bt5uVb4t215t1z8=D@9O3{)!`b<>bGEV;X}zS2BeSaU4|cnT~sRK{HCt zW8(T@Q1Wl^-LQW)YFB~jOZ(YCrYY#ziL4hsaIVn@&g7_@_|x)WqkC2i)m%3caLR!dGcgr^xb>AOiJgT2|*Wa7zcd_CY053 z*4~k!djM`698H+tm(+S>2G&|lKklxl2F2o9E_zFxSn#|rWxECqk}zehJyzO z>n?g^gk`!2auUAe#X7BQv^5F5&*s1S=vgm{H%<$s(SmwqIoR+LWbFNpb51wQ2{f&LnA4CZ+F=qAN#Rm}^nu7!Wta#Srq|q-UOU<*()3^YqVZZrQyy zxylAZH;rQP{VpSz*WdFLw)m2JZOpV(pPV1P$lO}7Yh5FqYejWX-R+tWU%$p}v~lC? zLg;G3??P=B`B($UKJH4Z^8U5{K0^LI-t|@!xOE)2($rHMb!LEukQ^ZtC}U!uw8e0l zcOTNx#yzFtEB6wb?(WS^8VPdpC;ZacC3M=EJrSyCQf!R4&xWZ)m2S(`%Ei7zbGcxG zQl%5#b$s(EU*bwgqOJl1o47C1X}<34;oz=3O;{xb>I(Umg#W;#j3G8WdzHfnIMK7f z?j5j_6Roh|?4jFGxxC_fZ$CQ>Hh{$GOPX?KMH#jcB?Hz2u@^Ob)`TRXoU$ed=n|Sm z=tWHSO9-!ilIGorZJBsvF!Z?8k=#0sC9LEqdR-94Z6C+&nC3+p;f(Sbmj(t-KHgd+ zdm9xVSTnrY`bc_Xo!*D0`H0$)#;aMiR)1K3sf=CW&7#-mt|y#>QiN2S|00V3c!(hI z+dA8%wFI)nhANq+B;h1qOh}SGv~37Hg^D=|NiBJ0CIgh;cZ$iq3Nd!g<`Eei*G8Va z{;7KXUGqpa)IoZa}tSJ6UD+|$Q^`B?Et8b6e3&@T1rJQI#zev4a& zE+w-!gc}H5eAro38z*jY**Cj3yoR0H8OOInAxDN|tUKLSD4iTulKO;q-aOod1m?k9 z=c(G`U!pzm2l35gW5KNrv@42p;%890Pvu1y6u-%xIEypF>tm8PrjYy0h~h3R^2)C}2CnumX&LRC+Sd%2Q%5BE{$rLVEw6 z6X>+2>by-hl_o15T~tu>BaJOm^{eH)4mB$@IiN|iRI}IftEdHZQ_o1Ww z+!_DM7+>D9q_F<~R3^k^P-1*E)ew?3O8N)-c=_9W_SgGA-!86`?Et|8iKR&RLlZIm zfNOn%KNeU~FY%X`nn#CvUAf8?OOa#+^{t7Xv#(Het?5DE zkXD^qu)s3&!h-@<5rYS0`5*emz?jhtf3OV!+&Gv9UxgR!5I)*Wq=rHj5ZL0(r<#}u z<*W$lfyjSS=yi5_lQ0JKiHa`yJ>sz1MCQ-qF!?G2r~gjVkg{SG)|S$9WgJDH2;Z;b ze4ZYO{Pd{UoPV<+Q(<9r=`Hg(_Qt5(oK}2jQOUl2y8|zdF!x&V_JKV>9G#C%Q;WYN zep3RI^}Pt7KatJRsajV0u9VMv0FCBy=KBm4w!nO<>7sgZ%)< zmJoi5hl`2ktIBt20_~5Af^hr6yGF_VE(*?E5sZPV@6$3C(&jG`fVrg20a1H*!gKs) z#EQR(5q*g!FYtyXa+PjGcm&V?)eFB}(b+EfXRXicCNOijz{;c=8;2S7QJDPw=OA8z z>L^?>mbU4`W!f|=g7<*wpubJ)+ItiG9Tl!?=SuxygumzCi#e4F^rc#9*q#tlz5bF2 zu0D(7?3FwAK3Tg0xa2J5qgaMuD-dT zzdOW4DfQT=C`Vk-bm&40OMM(MndUWMbiB4t-Cj6l%fF;5#w*Q0K*J+Wc3<^qfp=RfIUz0d9p@xqAH`srhFlL3~|B`Ji zK=1g8eML7m21?I>CB`I%UuDuzp8p9Cfj%?*r?f+A)C(0IybbN&XmGMGB3XdeVC1lG3Q;% z6GK)sNW@~Sle}^+4zGz!VJXH4apnv8ZeMrNo3FcoLu-H3>G<@$E^ z-^=Y(u;>q>OFMa$+jepG0C%4`{@Uq*#htwJ?NKA-ohJdA!1d%pAyu3}iHi#1#pLCQ zX3*uBNPh+crb^dJF_|dIRu`tKd2PII9N%Ugu*~7k+{)wqNlrr8KNl5L+UI6!zeEs1 zSAk%cJQ$Agmaetx$5h)&b8wK2hVpw>Imh#c^Kuglryt!Yp0H4W-`i%)=@&>v%S>u2 z)QeIW+gic$gCt@u{nzq5OVz5wz{<}KpyLde%{?7$M_yS-@ZeJx(`CP4GHe6?M0hBb z7p)Ahh4humBj8&1)d%Ng1Y0=%MrByk+X>XmBL<|}uJGGM{^RU|O;68f z6_T&I52Ohsl==)de5qQx|Kz}V)^Ofz(CZE{&VZ-9qW<>-+i(d(VfdH1@<{Q@=^i*3 zVi~&zEU0irD*|YohD{F$Gi#K-WZqjji;INwAfo%Pu}Pay?=ZH3^9pVa!h} zi(+MpGJ2`11k2mSteG2D#@?Bk`Hx!#P9<4pi~!k_3A%ZQ$!*@&`#HiY@~I&23PE5M z3xc@-F8wqy%08YS=&&Qgp3((o?i6$mEmkIT=k9@hK&CPEOYBvd2L*PI`9$iKvQmhzPS@iQ$;zQE1m z%C{M2%bsdpeUjoujimGVpO9UzLp`tk!6~?LNm&F`62vF81RNYx{h6d@t4^F{7_SGf ze>%PoRC*acjkG8+^&-?-FYo9b{hKcPX@09tcQu`J=`=^)jVRbPgtt`&HpY9niSd(t zb%O*`ykF3x)bd;HvQ<%)j!&gER{y@vrwG#&(Xaf8=XF(qeq@IHA?8~PaJJ?tn3pGl zvdY_BS+GCeFY4+7f6_8jJgDPXcjwWMyk+*n}xXFydk+S&jg`|BzzDa`mJ&DxUN=m zduA1rNpmLPq)}y@1ec9>3FJ5C2drTG!+)-woJI(dC!SkC-GNT49bT<&&$dtp z9$LHzrK(Z2iGB1}ni2CZ6on7mm-@g^FI=ZTzXv-EZ+kHQ;b7Sg1B>W%vnymktU90x z%gSgXdnCEx6c$P3da2=+)$ZuW;~}AP&gbUa?g5?at9FeY?$-+VWmc9kDbV+cDJkc( zH*BmXGKhUJ?I+zlWwVl!4P+`)mGgjAqgo^soCf{C%5V^z^YY{_qQzjcPd$-0wcN*r z4VD1M>Gi2UK+&17Bjct=lW; zmgk-GpQxvSZuWFk$-UEJ8z1orzcCtNiMnopa)P5Xwvewa;fLQdV?rxB@#tHEY@vDR zOx24x)i}kX)^q3~soQ$@DaxFk+xp03nP;~GQ7qCVj?<<#Pd`;@CUc{i`7VhnfV+dG zZ^qZ*BgCE&i)v+>%?0rZn!Jj3{Gv?ldiCc>?@DO2e3gON;Oe#|Y4KR0ICR_hJ>Lw|SH`aL%nWUb2={-O!!Fhmw?O-|AHcy(XCtJmRKEq>Sd+gRy^S-5?e#fl1IpZGUOI_TqD?Xk4DSvmO3@m5$wI6}%6zAK`8dGkW;; z-mOvU{rse6ZR#Ew9qtARd*(@gmRPF`!oiVq-?bzzWV2tWWV@nSr+r^DUu6613_}Nx za|ss=2AcdO}f{bt~t?iMmvub(+kCHjU>-kE2{xOuxMn?7PW+vYn(V zP9@1}8#NPS95mci4|9Ef^RV1fcb=`g0sY+zrJG8pxuh^TWR;=$X6~N7Ru2#61@L_n zlaib=96e|`WO9w}7>sw1c#opM7|o*4E%XhXMo#EOPdDu_ zDGmPM=(804jPVf~FR)8swJtY|l7n3RIqZ$*!SyxzRLC?U1`(mM{U`p2noU=uzx^t< zIw2(^w*OkB?dV=V6EyV~uEsx3^Lf31*e~3A;`;UNOgx?bFUXjYoM7`&%pU4%k|0Mh z()h=%;OoYpaO0>YuD^c&w-%8J8LR8N-lo@qLNXr^$geCNrB>4=#iY5h28?KE&}~?W ze1f>9KF>{%3u|+(6RRgLf0JkySwT^*%>F>oh@VQvo;PsPuX_ADgQDBCCB;MTn5(}l zqsF>KJltCpl6tgd9L45^LyF;<);-)f7b*BL0XEK`O(&;LVk-{=<~j$W zcuaDq*acx={|_3$1g& z62=5lhhKprtDTVz=6q4r#<*SCR2~ovgBx8ev;O(|6Vhc$CAiKUqyQB^TAk5G1T$C5 zeWKMw90))g1>ST?{#F*Dvpq#Xp%6>R8P<|h1*~biRI_8D*Zkx-Cmn5=a8P!xsP6Ti z|4H<*(h%qUjd@EQ9mwOnwRM?TR@GbPbZRV_nl!dcsxicY0zI$leE>@5887iGlD8*8 z;&Cv%F*eqo7DCkYV`b`kLw~0fo9_#j|@A zpz&@bnBEK3_cYFJlBOdRINDkG9uD6`c7fd^&!^8>?sJc7JZt^apxJj7F&Yq@A;ED- z)!&3Y0un-Yu!h|NlVdw{3Z9Tx%j@|&y9-f; zD{MxZ|ME(-6=mhCzmd(kTPNe^_kAOEwqEq#l#Ui6nCH9-dJtpa^k%~U4x7*<6?NJB zZrfkco9WWH8NjB(Cc&xeh5YH}zxmDoZiX{&-}AKi_d5ejw%59Gp#R&6PIqo+UGoqb zAdzIh4<8Bka*qPs=ba2WgD+>PJi-~Ta694MXmL4|ZC7aB ze*B^m<_DP`qC9MAZglP80a#Qo&ssWHotZ@5PwR$Uc7MbkkntfRs7R^0bsrIEMA0(}k@3tr4Z=jtMyUk1*qfV}6p5YF&-jx5qqt&rD*Ho(8T3Z`;R0l5Q6>@U1i6sR;Df1Rp0XH2KcB zRH@T)7N*#h*wWvzLC1ryMY~6!%5|&;iPtN9BvBg^C~Q04BnNybg9h~Fhkpi-QL){s zv3isn!@mn0xB90*!6kw$y{?}};Z>&?_ ziq15n3y)C_$MvNDYv|VRJiYu_Us*5f=|N@Mp8XR`2#nT?h7qy$d5KdGrgJFPQ8HwW z8Tu~_%@j@P`mLcOT+~XNIs#D3c3f!B;J2kxWp2S)PzXYENA~33imgR&9A=kuRM}B-g~>5*nviT>cc( zKG^bCD28qaJ)2~`!ft^z2uLwqO|pB#FAfgCullo0mpW6GSI79olB;MM&NAFz9LP8y zqI2PR?#!S#HQsfD=Od}l?|?+s;&J7#R#2q|D+j0PAAG3o+H7Yq2+1>k6y<)1jROW2 z^jt^wNM_rbfm1Y)zL7S5Gj5t@KM{Ie!lOIuU+*s7RJ=j+5)Hj~KF%Mjgt^U~l4*ts zfDEgXT<2q6Cy!73r80?vNz#C$2a)4xhFU)^`!lKZcU9g8EWVe=b>W~GKSW!Qg7)?+ z0c)CmSx`3Hzn!+C)5QcV+a>ll*3yQXn+>sgv zB#BjDxI*NMz~ML8Ua6s?gv$b=GoO*4+&sg{RYFkt5&`H>`7VhL`1v&&IyR zQ!zOZ?_SxLtp3|ULX}DM^Ws#fZ~RP5G7`q)-eH$|xUcDLe8lw5n!DkS zIVTVxT_#@s!!LoWC=-8$#(DPi*ixAkf8I&m+sKKk3@R3_*hRd2C>?0CH7_nN;li(L zHbS8Vt(flNXz%eLA=;R7>8^RWavgT{Ru{AJ^@^LzluY5H#iSW{W=lfTQ+E3HuF_(~!FEAyIUs%&!c<;Zn!3}O{gcW9x?x&c6EjtDTsdx$?bT}zu60>&C{j<<9y)k+HY z&okVJiuyW_s5I)ylzhN+k@B3V4pC;oZp*NeBX&H9) z^H?nU`#MwD`fj@e^B-6dDnj26LXtT|e|03b#gC!23RmWL+m=}4x!iyz8e46=876Or zDJK!MmXh%p_`B^kc|jm}MP9GFzL3u9VfYiP00u?G%f%EA5dmCyK)-3qXnSs3|K1)z zzVKYV%nktS7jr^aT#uH)-he~Ta<1CBd1@{Hk$A#S(L)fb^toqu!C^E^-iNOscONK8 z1_1S*ItM#%7n)T$7f*YeS22NT?H^DNP)BppzgrC3t3wqomo26duh&{~TsBXk3a zL}oIxK8I^Vlc;kGIljy(%@02q&kX@sR*{*px7sgFI;JK;?t;Wb$=LT)X5$Ldr%y<_ha~^@w%mCq4SO)7~@7!$eT?2cNo=YCG!|1Sgz;_w z`Warr8?E|akHLuOrnZ}Mm7?V8?Pfig$pze3p^a&V$?VYLiaZJiMsB@bu^qoimncx@ zoh(x)o#cx!^Dcdc9-Uas!O!RKc|=R4gjQ6nJ3glG&P>EU7FHOENaoI(1Lf{DPR^kL z{yZhBzgZ#K#E6X5nvo`i4bvo*&HUj$BoPTHpiPZX{&{aR`V~kEBZf@bKnlXGwy>ea z%VQF$jEw{h{LKiV*3}J`iPvN3x76GvYHt!v-%=N42=6iSTJw2RWi}n}I6@-^?zU+` zts_Ax6KyUHS%|O(`O~tRgd6w!h{#+e25Pw;#h(3T)tAywPWTDVuP|s%K^A2CNzBVU zzdM40x4c`3XU1p>IiB-ao)t)ppTR@gsagh3*_z{UBst#}RV>tsx)883Q#%lKPt!A; zd=HHYq?t_ptuA~%nZ5{1Mwl=wl#_@>=Ho+jYsU+nUZae%%sIpIro1e~UOaBTt|0Ew zA_jE!*e~daRi{6wcD39&L{&DalFBFcDk70X3J!j@OP45)DvoMHXHk`mTmcD7Jk-c} z;yC>t@sDGOLq2Nt>I9+GW@OlXq#LVgc0=>qEaP!ObDK$e^C4Sre&%?EdOm7bC~ez3OQgg(Q`F4V5@*$s&hvxkBsGn zwW-$6lEl_XPYFDrE7fua$7NB2z7c(VY);OWP2NuQ;3Kr-+Y(yVl6am28dt?Kg6kKX z$Yhkw$-gK}57Xb)$PI27Aw=&un2We?bU=-W_JAVCsu;9rOc9aIi6;fTUXRovF(SYf zXO+pxMT|hxWS-p~r%e!&GUb#@k)Txeo7DEQksMnZuTT;KQhMl(qczyG`%Fe+p`v+K z>JmV=p?%2l6yht6rtsgNQXHYk*(I<(-{)R@^_vAID+k8v zp{|q{veUZDIm)?6K)FqrS~6yAR;2o8bUO{GML`>$29g*rL^~TgLdZ6eGnGus6=feD zTAWCLGZ6^-SL3gZMiVKT@fK@|!eDKAp|*D=O7V5aM=LJP+ERfiP8ce>WDLz%cD=!Q zL_TxazXDQJM|nnZYx9?uu56l7zOrW%o|#)YFQ(gEcH4)x!u%38JWj6|IEB1glHbC> zdL_JBq3LtO&)n_&nS+bg|-PA*)7YGCKwWW_~>j{PRAy zYBu>>@dHwP_Gg@qKLpe^IGKkOc6VnX@A50Ipy8id4orOvAJ;d9y%7g^!%%PVu7uar zEH=4PASwT%jI6y~sbL`Laf0NWOzdl1bqDV3xT!-zUJ3<0$opu+aEIRSmS^?85rh>7 zzxc_UhGw@FjJPTEr`0~C$k8cdV1f;&5CU8*w;7>CPy($p2V}+&8lNPV)4~{;nNhq` zh%^`DsJdG8lg3zU2T_GFlT$9GyMtsGE>X}7a$-7H=ul(P*>9>SVa7f4eca z#Inqcm?v{;6-tUY3-Q%|Us2%NfPUnKP?(vBvshA^LR0VX+&w8*&VK)1UsG|%fjyUG$a})F3&wHD98gAxQojd4X z&qF`KaBdD%OcgE(Fa^K(ey?ejOIyELcerV#_rT5du?-H7ac>#L5y|ndHQIO$7`?4C z1A|v+fahmD9DLN9N#1YuHR}BrCKFUL8;#U}Jp0^2J6c#QWBq%HKkC5U%|Lm7M^Pg< zFg(^B#G9)!JsDmvrH-eYBTkp`n=Nn=U1E(i_o^Yk{9!oDh$x;8 zm((L=VW*qN*ydF2K2O8Z0%`83cU(e|q8h(_B-woNREECG6Q0-gN7f{#YCJ2w*C;yu z1+6V)WBVoS5uvZ-U54&|%?%t`t~@}RG*>_@>!L$FZAyH{=zF+Sg9>@Jw;y*;I9r4_ z?Py-DLexe=_%ZyXg6pxu_LhNa9~x>TgXUUt$Z0HcwGpHg*ym84VJZDsuXO#(DNQ)% z1}>Pp{hF#$glmEefl{F`=Tfr#!RcE5MC;bfU23TW%8cNPsQD-b(2y|pw4?f&=s=SL z!z1lmmMKKh1whslBI%AUlmShMuBABn)?(vzcVRAs#lU(T$^mE3`Xl0`)ldDA3wckO z=sYV0M^N#yV)ekzH4hO-F`fi@#%h+}Wo_#6Am*b5Ud|SmJ}TBe&;c18d~tMG_>1M= z`<0y!efRxBYe{Oe9@uMSFZ8|;6*4Zw_wy=221Q|=813_J+wS&gIjsX+LJDY23dQ!veU8-R>985=S&se4rjlv$Qv`$Wn zfWjMJ@n5myTeT|(jZ1EjzZB^TX4LhP8zKa+TNH@wcvAx8$~$*b#J2*z+chZ%fk^}D zG5Ep6DE7kW84bO-RkStID|zU;xpSyegXn#oUj+2GS^lj%ooceY+> zgSt9B2xgm+PL55PsL=5HJ6&%HLrcG(igk)%r1!A7+7zd7zr6t6XxS%sAwKkUFP-H! zhjP7g!?^Ucgt-X;DA&b3fAMmlbe!=(TwArVZ+ z+o<(3U6__M+9XPA^zhYu{yz4lxYgrOLuDuoCZQp~4jd{#J!$3e7U8fVc%OT|zxqb@ zu(pP3xI%!?ia!Bw6;h`q2mTCuL=5N!ph3S^xwv0J3NjnaIMq8K84sWbdt^k|yN~Qo zNlg-s`CjEj5QqW<zwhzX}G-q63Wu(@dZNjf-$fia*y`rADl-lJW#_L$&bMYN)08 zOZ;+=Op++J{>PSOk3PsYDYD6buq$WbtaUkX=o9u{zGP*01Z*q`8kGEX14hC{gjx4YpQO>Ugr z{-M&CCbvu`%`p;)LAg3)7Uvx86{|-T4}LItZ4zD^o1bI-RQ+WSjk6unpO)JsXJa1x zA1k=IIhB*s@1UNFvkk2I`;E(~{1Fyrbu@F|=_aczSc_xcFo z*t8#vQKT|!m7UUPKSw!@Oy9E|Ra?a@HBM%5IY)P-%eet#5XvyX>>5fOw z3rhK83{sVQ9;vmc&4pX=748RL*2B5f!pP5W4xYO>4ngY}q=tDJ|C(|zTZ+}=XBvHE zQW5w0P+Nl&F`j`JfvJDE4+Q9Xq{W}2xE-BbjntE3{xOi#obN3M&ywvszArVPIBJ8Z zxyXvoh45QjcpD3oVL$y0HCP0=BsN_~3~_L2`vLK7HUQ?8D?F}i_;S=(3!nx;Hl zq($^195srW8%@x-DyrT1jS;YiG0<=ho^W4PuPB*o!7(VA+whKWK@4~6rTffT1ftHZ zOlyr5ZM#WZ5-rsT_Np|hoOf13r^-&s8!l0(VI?dD%Ax%s%guP$HtT2|{mHPss-+>J z=j)-8Cj;l2vCd>M!OCA5_rU^ImQiK~P3?2S#+oZj&m9RCXWaqF#1*wJE(tRO)^8}H z5_k-(%KucC=9jJM5A1ze=|5^{v@xh`iP4r_0Kko_HFL~((FE2$_8mW9_??!*zqsIq z?}q0{_^DCbCp~G?c@~N$qKvmdXjO@x3<^5Mv7x(PBDvIX1zti7Ki@rsinPrMRg7f} z{hDAn#0XSa?%~!&;=u^Z@Wl&TCPUS_~kCWd7;Kic)@x zJ|$KF4O<6D^yV26l6ykK_(}6d)YE-+nc1`>eXkNikaR9YC$WC=4xg9|*QC2wTBH1= zyDMhw6Z-lMm-)Yau`BkG*QV;+PJyiL6JxiS9TV>p23R}Psnc(iUacuM-b{uWhRcd|P__t_bUT);R z!a!;5dPP@KvnEG~&vIQ6JgYwt|AkDy7e?2&D$KH6U zSs%O?qpFN%eoEkJLb?)wWalb1jZ497mvyBD%|Y~TQ9BAi&Th>8Xz9Gw0WhqR{w*Cf z){`xS4@1JePB^Iy)PiYyE5AyOG(Z2XUP#!WA_XJz3tbC_gm0ZtN)@OFtPGc!UgX2b zksn1LRF^$v;4NDS2xcJC)okjyj;o(f1$ed2V@lYdPGAE4!tUbih*udaj6e2w5+j|L zr_MBCBb*lvWle_xR0!MNiOk zn)pl)pWts$Js=gt^&Iaz3Da*;@jT;&0`>uhpQprbcG34a!Gp z%nll1{v9>DRI=wSg(qle%Kj6hfAc{A#A->uvel>d=Vx_em6fI>e<19C#0rj1KVr$> zEAU^U6$mqr_l~9{U(i|LDEVQ=TeMhHpv<~|74G^=v3pyd8vUG3pJdJq_auYBE32ZV zS0}nY2=n11{6*&|-PSeZi@&5RFhbGAkLyLMPI(gNEZZHmUluQ6=HK2d0vVaO@S=cr z0uZ_REB+%JzeU^1927G88CX(!kt%;0A;E)oLl**Q)eeVr<5#zW%KmBaT29z&fhH&mz!1@<=Jm7f>;%T>ROUl(f zB%pI+8lRVUA`7KVOmP~eHgAIrQY)V=CC*G4JW)xlo$0qeDF+*DHTOtN#VA@`wgl&! z^ML{6D@Tz*nRegBN}BTx=68Zy8++fvq`^J|Z5=da_P+pDHt`inRpEcJU_jVDifxyb zR!im{YuTLqnj649VZ_4umV)MdzIM4=K7#N}GOQ8e0gz54c4NIB2e@3eT`xz2mZ*^xBs;(0G6Lv$PX3KR=h|JU$)j@inX}S7X)p z7oI(7@ukb#CvVwzYFxJ*&+SlNce@%l!^bD#_glYDe0K~I&B;8jaB1NKZ-(vK>>1P^ zZ^j?QQ!%Qxe=0>w%yOpYl~qlQ3J}hK|5JS^-fD~hWjhO4n9^waEtKzvH!w=ZuWU-c z7etRymsOH%B_pa>K0`;y@2zXjd}Fhp~SlZA*B2|_GdLYz9s1!8NPC9Plm0Fp3kQ7gfl)| zbRP;i=OEI~#9{pTeY~|4+FX{A;e>Q-d^?4kTEvZs`CPnTVr6{VTwLoPY!r%Da0g9% z)})^N9=TMg5a7%OqjSBIvkMS9e-lc6zd3WHGvcAjQ-JUe|H*+`yM}lgf4~|s^KmDV z$}(3p1$^6(O#p{T}F#MO{NyT&ji#WtEIjA6L*Q?p990% z7tA+pnqSbn?<>YP{Q1^0R+pq=u8i>CP^uyv@Hj25uAM->Nv7yLw$nOAM&qeF)qmQ;*B^FJ!qRhf{(@vP7e^3Daq0> zP{+Rg91rG8z>}$=sCVwH+V1i!r&QuY;;Q)FnrpNqDik9s{s2mU^j;WW6KdsF)Z@bC zGHKDmuQ*XBRIB)H;X`BB~* z4R_&$q_#uYiM|uxw55AckB=PCT(0yMzp}3o7SZ&o_YE=$946_F*tzsTuWS$Cq+`|~ z^q5VMnYg!X@XCx|ZotlMn@C;c*cTm?Fu2S_HrXg|5d7sd$M$DXcV{6TM+7BW;Otj&nwqTyb1PI9~F(PK#nS9!{Bn z2ckaCi!t3&!ODQ2K-+Rc4t<#NlqTuG3f^L{7#jFau{*Y^`AmHsFkf0|%!%T{J}A|u z4r_K~20rOl%?wjfq*=U8ObG_u@P;;AV^*BVD+NNvHQoJ$xbPo`+y8n+j1G4Y;e5b1 zPXBl|LGL>%X$jd)_og(;EcN2z-3hQbcVL!5ZjQk&_Ze0ae*X=cVuV{=YQ&FTW<g9h-k!ftV)LL11q-#4ZFwMA2z zzI+(;7yiENm{E1KqPT#VIya3lyn)*;Z#_Qnv>@Jz>@i}+G{y@9X8d!E>NNH10Vg8{ z>FG)BQaA)I{Ob|WSzlDTn0?|gDP{#76ntyY;@54qK71Ld{Rudt@N>O|G$O_ZjC3>@ zw=(?82b5&%zs^VfX5>CRWi4wxitgr%$o%QRk-a=c+|&Y#<`myXT&;nlu!swr^nrQ` zB?&qEUtpjgnswp)1CxKlLNGR!ccjQd*?YWi_5SWO=eIZq^&|XL{9vTCh>3T!i-ml3 z`W2LH2g2*OP=p%mAqPXqWU>|%gb<3_2h}hUl+g&qSCX94XzkB}5T-U!Is(PRf?NLj z8B9AGQI_e))GW*n{VE#+4RvTR*<(LRQCx9G0`A`Qb~@~t=q-9zT5I&uk}Mj@Z^5!N zPBJXzE-5~vgtLI;ldzwhZLpHoCXrBGT<9*0!C*?K_{-+9BCBJ@d8QWgKn0uJ)Gw@9 zTPqLhjWgM>cY=LxSQGc*(6v4L0@WNx_6q58^a9~=>8X(p@*VTmENmd6$iRSJ5dU4! z(w8U9u7LQOXKpQ@k`SJf@}nJ0&Y)WJ=cF5)LX|?FJ^!p|UcN(Sc zCv7g9nv-hl|J|(F^!5*y{ta(v-t^<*N3xt%x&dm$KAr#7I$Bl?z00t)XtZ>=t_4R} z+_49jCR8^Z5Oq0n8tvfHHD_dMN`pVvz}NwgVB&O`F1gY?0`yfCPuwVZM=Z_T@irNn z{9*h&$ufBGGK=gyabtUc%H2n)s-2#Ea>Y|NsL};`w902dk6lgY4~V%XHZi+2ohXyD zx7~N_S1(VFJUUD;d-M9q&G+68!IogUCQPxPOik`N>&XHKdvocVvnk|@W2VIyd7JY>E7SQa`Ir-<98c0Uf z@rrqkj7_DvRc0Y;EUijQcjwEpAD;zNMGbu@8|z`Wn(uLgEl8x1R1oBDaZ{`gmvp$v zgn>7Yc(^ni6D;W9w3A4x+iPRGyk6tfOVvmaKS5kWNSIF;mxqLPK%ii}GaTHdHuUnU z>3W}(;4Z|AY^#@x(2SNmnS*9dvn?Z2#upCdgq#jFUR&x;Tixxz?0H};0^;5-M~n}W z-}{2%ccWxsa@{NA6;CEW-s-Y^r{Ho(IM0(ij|ASj(#b`1dli?7IIcEOVNQvvlLQ8b zezCNHe0<3g>tQg*6C}|UgdW84c}K?drzc)An(F^B^^HM}v~Ant9ox2T+qP}(cxK18 zZQHhu9oxo^ZS9+V?(fG}m87bxJE^48ed54*A;6g*0e|yWe2X}{VR9Xtpot+i5&T33 z`V@4GUMVD*|g-lb;dPeI01k%sV&tvK!*Pebu6w~YP^ON2pIIF_k z0NwRcA_i9=%jQoev+?EL<4$W~IPQ?JFT^=n^sO!LTVTCHH6UG?VEZbYErz2}-W`Hy zqCN0gw9tAHqTl&Zdf$!k?)opI{<+m^6e;riA88@>I3XXXR^^(ET~9g7e< zVdrJYk2E0~QK=v?v-CVTXWbBq8dzoF*>vsR^-<0G{s@(qDjc6$s|($yFa{=Ub?T`U zfYX@Eb<*I*mM%`~-Mv0y9T&H`zM4AkUQq0^oEW3^jK17pffAj+2yO6Vlf3;{W-KM| z1ZA;q46F`K00*;}fTRyKb*4P46g=WODe*cve=Miz{r#AcSqI|v=w>9d21)fI(K3mU zenY2sv-;|d#ox25A$wz>YpiijOb6%I{dI=(Q~`GVvogH`?6sU_qlp~(cW^1kfMWDZ zHM%)K@gldbK{ znsla<+`TrK%)RXzKC~d-;S2s>0bc;xuSYcw*ER4Ag(S1&mrYGY9 zDu#UA4kX(5r&Z0$|B+t8z{%(Fz|5$7LWvEmD|y-S>(HhOeo}EXB`PVXT*KeN_Q3N%FO zcpI-H#2!;k0?lFED#;(mU|)CqYsseu(FvN6jcyzS9aw1{iqs4=$8rKsMF)4- zq)0(U4c7|ARo4zsK9Y=Vda=Hf)M1tHiU~c}jI98`Wc`RxFZHgjeKkv}(J(bTH3y<7 zyYOp-EnbzPF-nX=@8kG;X79z>T8LJ)G2|()iFlzGqL=!Nvi2woDso1Jn>%Nm$dwDb z#*+0$^u%>}PF&3re1*)MaOR1c9Kk4dq(B*y4!g4o{`KIe zn~{D;NN9QW274X=F$4Q85^E;U@9~=G<3Z^cusrY%D(@+%hJf8Cej${6SU`<4xX5#QNXuX4zacPp>hoC;pHJ1{a zL(I~MRr`Q+hAo~8P0`OBXC5d#I*O0f&$5}c;5z`lgwlvVTJZiTF3Mn|oEyx9W>Jrd zXap*zp$LV(i|i=8F|S@`H8AkluBHkIW!xqHfbEfw(yGUUWy}m+;R0xW#g#oAvsa$C^(B_?2T;x)iUozyyzp9Y6i#As=6wLfzj-vCm$^(jQ3FF|jjz z)7ViujnT5ug%v3kJ<$4gibr=8^%*etnlThD-!KnGwQ7KC>-3pXBSn7VDKj-0k#9+aURhtgZA4H0~Y zT#Wb^(ds2SA`_OQEFivGNs%qh<}1P6ItTN##G?Yzgb)%#AZB%;PhDR+bfW8LIJ0pcuL=9Pb%&xiSwdG1vU9BSwNYL9d5p~ypfInVSgaL zF@9JaG66UiNdoy#z4mYIsFwO_HrGxRP|IM!6TWLs_lyZS_n?;2EnBl1KBSLdd*u~f z?HEYbH9uQI9ga0=#dYAKffv=C_085StJs2Rs3|_%leQX`SG2$jq;z7K`!%C#Sys3W zTyh+#nAgBWK{&B9={ujTEK-#b)e6hWvv>ZT-Ry7E7=z>^-Jb>>7W$*c6&zGl|888T z+^}Ag39dqPE$j2jgb`{QDJ#HDyWm|_UH5a1$#x>=v#PoU76lIZPRaHuv(96$|GQ{p ziruepcE!5$lrsZcY3@u~sL}c|ka=^Zk#olyD&Ej8gjyE(o0EMPKddi-My^e#=I5?m z&h?HCafkr2&_3O68CA956nfj=s2Hj;?xJBJx$dtyhK~V>>wq*2vt=P)+{!Fh^a+q} z*f1r+EU#nRq;4+sIE@gLJ5lMnk|ePO8%3%mgkwf43;;_ess1IRokCJBaHVv+A+&)0 zX~>5)nO9MKx*VUB#vl|etd4kV!AX46s2mDp^M_)sX}5yPEV(B)VEfHG8QCdrN3=p2 zIP4ycv|;}kiB#)x(d*NNi=EJx{i11>FAh3xNt7(0sw2;$H=~05DR~M<6;7E{ zQ`92ciWY~E;7u|QKR2*flO2`WPM|B|iZm`%yu$xZTpL8b zQSai1g9v-eG^bi~U}fDlsYCG(v`kihhDA(f77k|eD9$12ii?}z;4Zg{?*;zsQrh@L zs0*Y3Co(?edW^8aUb<^&@4N~wZUY`vu;)kjdr@G3+kUK~=m6|mH@=O!Yv!=H#mYG{5G*3pI!kQ9%x7uG<0*{CjIN+6lJq(|1$={JheQwvNWQ~xT|N>~ z)UZ|7H``-z9mm%Z_}0IBow>@^!Nq%0LN4$IvArhn0>vxb*TnHN#}(?zlY6!@W7c3ad%8NpJ` z$+pPWaNA{W3l#h(9XS?uo|7|o^m|RMYYYiIgKvpTR6eKqXMBt~$y`hG#yjkv9WUKo zV~XI1*`=zRxCV?XGJKh6!Ru?YV}cPGCeTtdv5pL>tTnc$tnrJC&EdH6Qn<{qTfm1b zxv~z)GL8QR&nC*Sj%YgrvV$>lOAeL`x6$-=wKC$qrKTaS?%@bdwCfgCxOg#71C=1+ z1SC@H=kn#Ba@c;e-~cKsFXqc`D4&!}XD)CBXkknvOh?mTyz{(^WiqTGXcWoQdrhP7 zgKsnefi|AQKNB^*Mq6=4y@P=F3kbkz{#7pi93}k{#hkkq@sy zG+gDG#lRVB=a$KOWkns8AYRZw-lAz~jm>|hpRt&^h86lV$FyL@wzx>P5iFb#{lvO% zMGo(x`il<)5r5SGnqM2Q(L^C1%JF$<_ zDx6)?Z_J#WNm4w#p3$aUAw8sT^V_uBiCety=lIueb$aMRomZOM9L_0hK^Q81?y27b zPW63?va{YPSqGunhR5a!;(q=EM5pkytemlB4uJV%f+YNl&TCg|Ta2OlHY_R5r4Yjx zB)<+s1)2#cjF*~26rg{b3aUvl2e?h3ucyZv)0dcqb@eKGZZ?;JF99E0T6SK@FyKiG zHM^$jxd1}D;#{P(OvZC`knDl_qZ+qGK_mtEJor^5@4INATXG!zjw+@clB7*7oB_3U zH%>1ET4_=L+gn65OiiXavVxbmy}1_YsyaDHPPQ@T7ILDBWFa=-#-ioT7k7rw!xi~8 zLH!cH_cMg@Qta~V!OWFpAm!ExGH1%2p#)&88T`a&XMZE%oZGjKq$s*az_2OtU&it z|0J~5be;s(#6`=1V8kJ#kjH4u##(+eUAhdyiX*JLV5_0DRfFEYI_V5up)IBU>T zm9YrS95i%Z6UQ{JSy}77H3&+U8mJe|lPa${jX*QXaJETGE9~KYhZNP{a1Hl{t@bzR zez4jLEQnL6Xj9443gqSjndRLOhyE;nltq%I&pZiOu|+}FBxF>B6GY8T+!M&Yd}r$k zhJ?C0IOP=;aBrEFS(zYkYzZfdDk35xKYc_PFE^N)P2?|Q-OL>T;~T+_ezBs+75EC6 zmvgTz>UB@vCfrJHXq~!NlUPXxObO(-br#7YU{iH6 zRaC(<#re`j9Z6~~ut5sti8|;OjkG!CN8H-{Xc6AtOPCg(a-S` z@nn)(J`T6Wh-Ntyjbrj>zB3K*QH7Jz%UKikdG_CZBj+RH${|CO1a#^<=3n!jB)Hzg zs)1ZcV}-Q|a~fv{*#MX(y}BF8g~{-DAhR1r21R?sni*kEQW17#{2+MCvce&46~7c2 z5V#W6^1+D00**3bXMwyjbRoO$hZnBZ5@1`&aYvAO!NMX@YuIBzRzqBj8|QDLfI1!; zK=|UJjbF6u)uL?^7I^BoW6Jg}>bQdi+_NIT5k)JU&XaZyX=WTDZ(q1>qfMujGxV;h zggBg#H#jNK@nfid1yI^FtgO7A+$kR|2q+xx6k*oa_9elts>cuVwZ679hO7|a(O?xF z=feJ=4#nPM{$Tt?`_cx>&ina^?(AW^Vp2Y~aoi9nE-Uqg-uX+fpd;P3yw!rfP$GF? z7j87s?Q?s5c{m$tR2hxM!rxuwyOsz!fKT6Efp+m+j2+N)~)x5L6sS2D)nUP!r zR*6u5tZPuR3G~R5eL~cntUX6g!vKR|cZxrXorMa<`FZ`fE&KLDWpn*CT-f|1>Lwm> zDtMd6yoQylzLlg%S^V;#L!M778xt3+B349%40FXY6cJMlBFa!s zx)lTl7=06hPs8-=|`tEwK$0duS7a5 z_#!2jPQ22=#l0FV6rsLGhe>j_nR#-2s<(+0?HM2F4=X@1ESe4r*GeWjF=zdeS#zULnGT}NnWyrvYKN6WYCzGoNm}eP zJesWt#o~g8fc(pOtLEilqOHR58mAMuL7i&%6YYlyF3ggwqXyYQ@Sn>UD`gc#77_aX z(PWi9bYansQ#qoU6rvHaT@lFdQbnH;$nR(iVM^q?&;!Th)w(}0?krF@2VxPb$y`*J zVKUqc0dhpKXxtdCd-hxN0eyRO0m;T$d|5ILd?FvdUe%(b_!m_8)JmqwF5=d82O6X; z!n~-%vZkMfp6!5ly#RK>Z!bEca}Ay{+|{_URC_h6>O++aQSdeQloeQ$ZJI zk>lwrerL#KID={3ESyT-$EjjTqwgeoB49dFy zs5`gr91kxD_9Lybi1zeUVddo>yhHA>xk{|RD;Y64_qUvota69NQ@-l8%sr+!ZOpQB zbxoIfqj2X`?nG{r{FQPM>mw|w@SRd6Cy+P6M`Vr!%8D(}y0fgv3x-3iEt}r&m@%$2 zCo{H<=9%iF5n7mPsIv#U+6G!P)W=GnrrCvxpi{yk*PpR>i7=_tZ@5vFMW)F|s-4n% zmYRqaO4EeG0KC6)PiIk(qnL1TYT<-ZLZ8c^b)NhKguhbr*sAVd2^p>y=5vaYQDOor zJzPx)nuCOoBKdfadO^L_#=z2q8j(FJ=hUD&asnB4L}^f2*qAFaml$YTGOi8F=-$I= z(L6_dfUM_die1#SGlF$|qmAT3VrOZ@N%qc}`p4A#JvL-lk4>a}pm^h{b3jz68jhUI z3to#5af7I;>cO(007_rc8%itNp)KFet&poCT!R%ey)d?=NxBXxT%RP_k#Td0>q0+C zF2`Y0Fir&O&tU7!^e$Ft_ZHqFTv0*N7Ig$zTYdBMA?|`6yMdjP*@7Ka{e9b7!@Wv+ zDnwPpkcP|h?>g=*vUF~U>QCtA`>UVpq}i=a=@9(vj`IqmR~aNPl~^{X?6uRGY6@qI z-L>>v%0i3YU=4aGYR^=+=7M2w#Yh{KTHrLOhIx=Z3cLAQPpI9zGo&TLBo86=m1P-; zjkWYURu)9n=NzQ^>%QY^A&b_z(w|ft?@YBilX+R%PdH36hOVwXU-3t95%pjwzz6r8 zG?V&jb(!`PV9wo*ZWXDnGUWZK&Jqu4FgkF?tz=ELwP6g=+&`@T@)mB1)rVbR&Hi>G zRV=Wek_O|y7aIcQL{6LR_f+%Ed9pV8A<+mn6?IUJ``Yd?)9k56(k*%07(Z(rAcv7Q z!Wf{yzkG~ox?B}`Ppzlq6g;8W$J7!oW&mxRh%F>b2U<#Ni5F)(V0Gt=uFchRz)T9_ zB7&`)PBhOFuCG)^c;3XIA9Yp&xaM3$a-IP#e7c@s-INLdPCf1HgokGvMT^WmbdD$5 zA2~(ALKY9lkBr^tQKH!rWFjSHiGIT26_|M0Mp4!6ETR0#6TUMQtj;>hb7Hlb;$T_%ZxnuiS6F8yAg6U zJ|3#jpeEjI4bg3jR!`U>Sy0?dC??ddt^@~+4KGjyo6UV;6{BKtCDbhlp3AA4QVrH{ zYltXRJ?L?*THSEK+m!q3meqY1Fd4iJ`fs?M2&4T2>lRU^E z&a+s@g8XviP|M8#wI?oXbY~|C}T1;^b??OWA+0chqssFGoGAeK-9Q|9W=imSG zPW~Z{^(t|_zv@_8*TJH9G@R01L?C3{YjGvLPcASr=9&xufC@4h3YQ6uKVy%Ofu-2iZtj7ZD%`(YHbC?j4Z|Yh>;Bq|8hnynnBBsa zX=6gZ=VX7tN&UiPz&=MhX6jd%zURhBB<_+5v`n#;Y3o4??87{Jy(Rpc#8aD^eAI;HHFB`$8}%ICV0nQsjSE4vJU36 zDhx;2_sVXgg+tNMK=Hc4^;qHg<_DwelL_zv!kjQpa4yP*9r4Q$xsR&97xowP*Q}mIs zU6xsbTiKV4z1px%i33vKA3p>OCRzMobbWFl1>fHQhe2vbsE}rbZA|1VY%iB=B<3-^ zA*@L_PWD;w{Jo+5n*#r%J=`-Y>&4LNRP2y4d?L9ZFkitzV)Cqm@Y*tZoWV(1;2+|Ka{%{B69Vmv~eezx+_{uF> zT51rBPjzyX@6Z}X$XL(D_JT#DizCo|Z4oAtiG4=F;S^r=hyh^;Nj%b2dIo7FF>$Tm zg78`lSs(i9rPHh6>FilM1CL8`IMBw<7Y2HI3V(x-D|;*6Zq#-|-(L`)SzW7d;ijUf z^q&mib0KcJaP$EXnpeg?@QK_DR1x;q(KdXVWRBfZmHx2t#JN~KL!Eg@{)mHAX*Mjq z%6{y5MIT6-Rg1xUPogDqY$vEL`92Y@E5$(Mwo^aHCI)PnO(y8PrE~x8k^Sx4kZa3+ zW~ZR@oCbkyZEU#NNJ8I{t!@l<6jak4I}x|$^fgT~$)OYOtPJ_pqiWv4&IRErt|i%q z1OYIEgn2^4Um1XkZGL9HzCQlS?lEYomE|AFUAeKQqyPxWg|c&Y>$}R{$j%x0N%)FZ z=>$Q36CeL99B8wX&d}-%lmJ1zUEs{vFJcrIfR2krPjGv(R|Ewh=C$uB^zTG1mfW%> zxQ<-^lP~mInVtqeTFg)6Eep_xIwu`Fl?cjx{MbmxBzjLjRWDQ*-vMrggZm1<(r{*N zMIDCToQG>|ko)-j3a|N%So3m%YD*=V+KZA^6iM$GZ0{F)ef&57u7q&q6&k7jiS0|u z-Gp@a_j`Jj5(SY$?Yzx$Kfy`?LA}odMvS+7<;vxNiP^?CjU$g`tXZN-#l$?04C&yaGgdGhr)Z&HUhSh8 z%b&OUF!V($jNa@wt_Qjn`1aEuI-Fq|Lw)T1T%Wc9d@;EAB0?Cw$n?F%;yF@!DJpFI zhg`{&d&p2`!jGEr=LeG;5+r9b6QSpHMRSwlM4xDhXMQ(i-#cXs61FC|DYlSv|A%4A=4+lzoLKnyif+>fA<8m_c)b@%1piCn@NSk z*_F$siKH0Z*H>J2hez2*74}K1rtsl;FGqRXH&)}T84OM8pi>d|wpDlTu~tCIS|M4G zQZi+&X#)h{NE(CS@dZE#0j4gRP!fn(DSE(HAFTf(R|Na;Ff-xrz;6bG&3$P4-neSx z%>(N8>fb7GR*;9$SqbiwAWZww|2j0ou`GZE@juXNLy_-D4$uIAiA@fe9#Mu4piKh7 z7M$47j`m4%*HDE=nztbr+d&OE(Y53gfzO*@)nMAAR`RY0tZpJtyD#d=ojFjipvn~t ztJpgetJS-P_%RA~VV_E@T#oTUh7Q9Wn;S*otdQyUk3WfkbE!C=U{2l{O0$d>0B2LL z>_I)9%PRIh?euE5FX_?ei zWIQchPv%D?nrg1U3(Kqx#K-psEF)YorQ)mfh92cz>G`ZCo znS5~y@;mj@HGs-RLvadaOf+vm|0I;qZ%WB{jn44(DD{@`10 z`CsqmwicCF{9pBV3aI|vzvJrDN&(w}yhe<0o1#DUfV%TL2W1$gB5Gg&&D+BfuXrED zBlR$R?to43X{`eeIhj)k#q!LP_9i^6LHnzNFzGjpe(xk7thfQ@F6{zFt=VmqNx&f^ z2!;4wb`>nGkS~d-E+NWXK*Sgv<+Hl82ryJi3C8Q;grvj%O6f4zGep>vWr4abSFPl4 zBd2pObjt9jTyWP&7zfDZgcUqtXya1bbIl=47gMWn%Jrf7`dd{2vORImA@T6i1e{Yq z8I(Nr67s=ZXzDvBB+DJvD=k%HiU4kOGn`kOd|B`Sm%p_t;$XGX5qTiq|Mkw->%cH( z*m2@1cYp~}dUzW|mRpO>6)$C;p+_S_eMafCxVh#7Y%TZ$@(3 zrQA>wdIiGbt-p*-LS^TFtCC&!yhR}Ysdh3*B2QiLoULYFkU~aF!G()4X&RPTIRgMf z!GL2Bo2CZoY(=?38j(QkzYN^pAS09B(7kp54%8YhGm0n2%lCwg*wPSSD$el?rB_h| zCuPLiLE3m^86s*ZEl1Ay{znCIlfr~6I+ClvaQ{YZHgRAkGM7u$RrH#)ldiF1{S83u zrZ$$1I1z4XhQmR;D)}h_Cz8Lo{u^VIuDbBfG4`hrdgVe&L}vq<4;eW%_&i0W5l*QT zIFY{#*-!8w(>nx0eaN>G`zicrmEh?-LX1Du40$1~<1h z^|^i|S@4=9PnVuqn#(aKL%WKyD4}k%UgAUKoS+C<&Q|=@T95a4;q?5oAC3rp_4uEv zD8dZC>rr4*_ra<{!Lc~FFfG4Y=x-Ib)80-lGNs$ASLQa6lvZ1zu+$_^GrrkY|6pOM|dGRG}IC_d*f@AqDv-Mc;F`qHo@Nt88*QILjZFqy@R_D$=8Ud zdZS+*`7?MrBORT0Rtbm(I*XB;cEwK4ijRYPW_hWnOl4F)Hmy{RK0qkiCW1B>z+@q+ zyfEQ$^S}JmmvX6Cfl|#km4OvtA{W2|xjv77lAc{VG5_}Jt!OX7ngTV?M1GSv!uZg_ zvLQ0Abzo^!8r>rjAN>= zWmb-%LK=_T%J~y&NS0(H(dwqXazqe3P|6_|@R-IC?0VYRO)ECnu$0Wsp$)O4^vzW0zGAout|fxF zr4oRF#@zb6>mRMR`R0NK;yApuz0p=3YRNIq*_k<0MOsLhne6uiMf(4PLune~$;HPY zkxB_RNm|`}wa&(Pn$x{d#ChtFvO>$H3K=^tZzRodny(P3?r1&P)=SQQ^PsiRoSw%(7a1Olg6d7V%XOh5qHu+^3$ZnStq6 zB{W)~Jx!#4Dp3DP?9$Kw(4?TdYynDsWHqn^Q*jaC-vuRNsu|J!@*he2gLv-jmLf6H`*9+e?^VSyFn?v$IWM@r4u+%-HYJD1b; zcrJ>cjW+m7co#P$Xk`aBB&t6Qs|fK){NJ&>Q&n&T;nJS11_5q&7I0v^NV3%w(J{%s z1tmv12CRblc}S*)lJE+%{$%iJXKdB_t66Zuk@@=mCY8hVBX4Fw)j&P8fu*_s$R8~L z`Ll&J{GI<#AJVv_?K4qLC<7cR13Ni_c7SaDe%`(qI2;2ryXr$GfLamN3@+jyQJM;qu?Nvtd@5B|;JHzYkP}hmjT0eF_m&6y_OPom#BcW5jo*dvF%FM=7gI!cKSPn$<`9T8jX|P0(X1E8r>GO#P!SQ zi}xm}ELK`_rSvlkXJp9A5rGzUjYN!H-K*!?n($ih8c_UBk$buKP0xL7=L-O@8?~?gbuMy z2Us?9?b+XRyngO?ZaB#4<#U}@A+T#m3^Tus8#uAoipPw&Z*|Ls!>c!aKp%gUOK|0u zErEBK#s_Q&tycU*n(aTuO0^Zl7#ittIA zrLn2rEYqAW`0ED54=(iYn1U61L6{n#mDoaeS%i}eht(Rsy(%rHvzt=8?+AAn)E@Vmkys?PSCzZl(vR*TqK zg_lA*Hc@Yz0WlmfPNKNkf(K>9f=7dN1ZR<$DhhEW_j7_8fI~$7S)u7`IEV?liKo|k zi(5C)d%^_thmVrKwIJ2Z54Tzk^%K2?5L{{I7Ld`w2dnR86x&LAPoV37nWkE@8J!_kdwHKFKwCefNWK zA8yh)7-by6=LY^JropEs#S;9?xk6)HBkV&3za~SkO1=8}o@;ex(pG$~&P~!7@Zc$e zk&FZBDjOI(2k2Yx&J{Ze8n)fsftxUiwBF?}{ivnKk$QW+xH_So>*`9@^#U{xX+-x* zt3DEdHsiFv$KHPRQIqxhSgmfDQV;@x&zHTZk^m3y?=Iz`>=m)-m^dkK+-8NqjD-;V z^I9;C5M&Z&(cMRl{e{;h*9~|N*(khf>a##5TEVG3X5N~f(prYZ4vZ#z1%-`?>Cn77 zJ7x1shyJdYA2nxgj@?RRJ$CPTmcjY6uQ6hPPLb_c$va{0SFbKbVHksZR-0zKO@j5 zt`V@m{e%HP*>%G(L1KMpT4)` zhits>6DOD+<$fm+qJNU3=;6M;m8sr7i}HNFqiXy`QAG)gJmdJqMXIVQyRM~A&YHXZ$H`m2)VZf2D6(Ksg zjQ7ljUJh{aJD=dF$5i4Qd6MrkOWg&K1;6L>PQF%Q3?!W>`>r45{J_ET36I?841?1T>=W8pp7kut`gLa=1%gt?a-5jL8oN3}C(jsZ zMD!v>-ZVl&p7#I4Ees468LReUO2-eZQ-RZERi2~F;oYKTaJByDEx`FXfT4&qNZ*D>S@#m`~)W>8q= zPHQD%d;~UN#A=?TTJ7Fq78JRV!Q0IYsap*TQz?o2_5Oo-nVT&%?8|lg3JcR_7F>m5 zlN>sV5@EVqDy&&683jFditKbyeNG9FecanYt`LT76Yu@(<0xc2xrP}_QJ=N$l3dWt zls=>lkyOuTn=|MVVys!_KXpX6+F(E#s+6HSY~7%;Gm0{*KFdZC_;XmtOcsHCDrYO0 zjeMb|D!D9Tb7~`~h;DuJ_V~Zv)+T*9Fnc}SIp^`@#7L|)MlFNM?Lr@}uS#-}Htc^> z3cd0u_PWOss_9A;w{+_kM2;r^p+#z*AA?Z*6F1@K-Fdpwca49E)xE?5Si>6}Zr>{| zc-J`RoP&`b__<3#+Qn6TPN$zoWxnz_bW8*9C5cS}kMTk1@wGrMLXyFI3r>cwXwaJF zC;1*!KcRiKaj^VolL7W~SARD|&yhp+fZo&4zF9CWtrz@Mfe zC^0^qYi_z+DUe1W!S37i$Jx@CeJ?xah5S&YNRYsIK&s(^Gy8QNTG)G0_$o{D&#&9+ z&dmOVFs;&Wiv4cQb4~3zXi$gyl-=*M2fyBj0KTAxMo2SOM}FRbk)k^<@`T^+Gl+$< zGpe~92s5+qUj>BOI^o(ng(ND=D+%GEhiWc=(*io+`RAG#e(HO#|F~%U1OE&QZVYL~ zcW;3f6@*ru>LOk!Ng1Pc&LtX*7Vdz!9a zmFvT~K_5=HexiV<)mjsb6Ca7Cq`I}w`@#vC217D?@AJrm!gZezTw(nu%m-7uA}tCM zYEV_0>LbCB5lbBDm!pKQ&ZHjTUEz_wmsHWVo>Lvr@2H}2160fPjGp?Z@QKRzH6-Bh|8cS zOp|ajIVrMeP)hfJ@aF_#kwVf3n#%kyuRK_YwZ?W~L+t0jbsNpyTQs=?>B zQ5wN}3qq&2QN6~Jkt_RKOkOfYFjMUjqD z(5|4FjI`ub+hkT1R>xVba4K5TftoV*>E=pVl}4Ga1h&Vz%q!Cqy~&aZmX%2)m}QCt zg(s_A2wRg#V=mJhe8^_Kq=);Wy!dby)ei>PpT^##f}rp@r@7U3@w#^j*n#2%5nsxe z1Z=M^e@T>;SW4#vB3+zb?HdmW*s^J|UM1jqf&Z2-e3Gydl_8tp)szS41*Oudfnmt& zGs;d}sZlm39YFpAFDt`tc5Yyz?qCbqnElH(9kM=n@X1@HazE8L zb*@Go@J#iw+|!gbTo^4AMf`@(1NZxdEVRZ1qE4|{0KuT65T7#{pj_~1_lJiy zqfSi3#rm3B8sHFBWd0N4!V{8D3eHr~EGtV7S}LzerxhL^4FboW0MHscgwMq$)DK?m zAIUNcvkH?b{?tuW1dI|pe~!ZEn3NJ~z-FZTsSrNBy1(D|7+d~7n1HRInZ-ROxE)x!qa{rBJNOu%=ZBY1gd=tZ+& zIM~Fo2{ZJubi5@l%d`iOJ5x%=!#zk9e`$V#4AHMVVA0+TOolmOk;FdjGsmF>X=lfL z&>g&L96Zsk-|3r4;NtKv<-f~g+U7zswr+ZH!sPCm!;i#VB~nfh(P|~*zIh=f%eEEP zEu?0yJNKiZ<}uT5$8@L~-*qSYE0N#`AP^FolY?B`pwyO!37zUml5C3R%$9#dW~iWT8g zXLv$j+|}!ZLwtC-Q}fKiVK4`E(X^_Czu;d>RDs3GFGdkit!TI!zUlEi`Ev-xRVWCy z0ffiPE=ZN!td^c=1ez==rFLkq6r0UvStJM{q1Ja1L7v zfVGIO12wz{mn)YTYHzdw%MW4uUCer-0i1t!puww!3Z|gAXiK(09W<$TMW+<9VCaSyP%@z-$Uy`m>w+WLa_J zzTP!{(ERhF*oZxxqFB|Kd^-1-Pw3N5lf_=HWJWEhK5isboZPdprkOmStnC?f6zd~< zRl%HsfddflkUc^i6$v-0ZZzxs&`sJ(T(+gt=_x2~uEC_8G4y*#LuF?7n7JU}zRV%k z6$A(Xh$EpJ`$@))>F{w=cG+gFBa8LdFC6o)-1kW+NndXarXM)5Vz8Re{!t53wF*lHbT|x_t;92C1E~Aet$&%s(m{f&Lrv2r+>Qqp)GNEdmPj_5p5w! znM;I-70S&5iwvD=1K$hU<;s`Y-qA(SZ^u!0Oy%9{3R!KBl9pAl2Hz+>LZXUC{xQ7R79oivR|(ptbIdv~ z`5%Ll^{cjoMVc|!eqvS=9Lo!dhTsp7+SMOQohi^j@_|gs7Z~|sm3sTOO9Lh|9jrTV z+)DKQP2o%I-j&%7+TjNV#Kp%s6R_O~;W+eGZK7!bto1Ip%VlNH3~^@?(W}W&y;re2 zg&9w?zgaIo;HV@M+7EBa{62?gmRYd-zHjVf_@9eNLDltZ8KP_mR`Dtgelq;mQM&B{*A^0vMc$i%))@QU10D>XD@PqTBs+13`l; zC-5J9aoM^FZ3sm!c(bk$<6V0UMfG-8`mC}M`vcI^l9!EStP{xJ+Mmxt)zA6m9pO-o z;Picc&M5eSQTQVnNA{@=J5;Hq7jt3lN9AR523#iHiH9lH4ob7LVO(rPp5tBpEC@)npH@vh+UajYQ8p?PXfPnhH2 zub2a-vNVsxe_Czpo=SY#R2E#Q4wdk|?l^h1JRS$wZcF{oigr}vaq3ML=^hN51;CYDvyx6C@ zYS@L7YhjV^(MQ?pCm9vqd3_6>;?AO)fQ7kj%XS zbO~z|PX3p5c3BH2{;tFP)IGzgpDXk0hb73CYhq zA2hg@g{!Tsgu2nthpTQPALx>&+ft6DvAaMTb?Yuhz?PeIsls!QJ(Y3^nHU|7QZL@E zS>ZclAv85+?s04)2wdVBvP>@VDz(kDs|-WC3~M?8Wi!Lwq&Hw;k${%U`fZ>Dj|vR$ zj5EU=vwKbr(#_>ai&`0WXk)td*1RX`4fTlSwO#462{w%V)nJGUz5;+Bhqr_Rf=xV| zPcHr)jb6reXB{`J`q*g9h5Jkio<+yz?r$A-#aLTSedUUE@{AsLJ-p%jmHMgZ73!+rP1vom ztcDB5$(|`Vmi3q>JdjL$ysnP4Diq@2*Dz63`NEw<7qVLBuT(ewkI578KW1cnx^cAA zIy!TbesAB9ATLm01fgyEL_>PvjoRc@kGhvD&+Z(nD+7mBEt?U^Pd1d`?%lJ(!1y;! z*N|iKz$@L$)+yewdB>{2l_^s69iJ;_21)va)+v;TDBzNFBmNaAI!ylx)fp96p}!R= zQZAo>tA?^gHuN8tu;5n{QC$P=hW?HqE3=70{D8kgjbZTp15JS>SdwBrj6dNUM@L1? zq)Qu0$b}?0@6=oRW?Jair^9*IuY2kqA|Nc7lppS`iSQJAwHkvXx&O!1H%3R=1zX3q zZF6GVwr$(a#I|kQ&cqYjwl#4wNp8>keRr+Nu3s&?&zmxZvmtHSgu z(zDW0bRJjFjweiwNgyC)TiX>>?+C4GRqvvn zdU%)(zvXG;DyN^o2~7$>^LhYIk?Z+4>uY;WzLJFOma*6RY%x4~?7{bE&}deg5C7%g z=w~v>6I&1X1-Nn#FK#95G9uYALR)3!>U2fFx`X-jJ>FQAj<}xmfv4Igsw$gs(?S&_ z!N(QuG$VHG%=_g!ub!$piYhh1bk(jAxs-<{VU^vbE@T+fw-&Xln%qm@VZFe;M&@z| zQ@x;UjMqVo187+()&m$x9klYY>*{?uQHx&XKZFHlrD7+mx-ayD{+cq+4-~d~=g>eM z(7qZbX~Xn8*t)~PnrPDS&nW0b9#nAQ*ZB(l8~>u;d@Y9C)}!_c(_mXct!A@;owzyjWe9GpY;*_eTcQ5eFx;qjvt{vAbwFRE;{AFTNml|E=2&;i?g|bkY3B zzv3psBUkEtG{2*Vgs{FUId1eJRrX$lq{$PHsN>K9K*{sz$i6BZW8{ePlH`jEloAf% zSE<=@{_mdWnG5`N1-qK>2K0)`P&mnS-PV=9$Q&_ zt=|dt^}y+!UY>7gx;QtX0dhgmi>dbM6yV*A-#Do`3LPXjS?UwxR4z&Nu@FyS_T0qD+zUn2z1bK z&ZBFMp3Z*uggn?Ct=WoEW9JH@Ls%GNwfON<=*m~azNSOKqr{9H)DKP%YciFLt zHun7Mz2!u1PdPoJ}5=c^C&G1 z42=>}A!mSBIP(4D?C|iu#nn91NX(dNk!4wc;UdbPX1DIj)Q4j%5&w+bQsX(u#}yQLOGnCrn4n6uaqwOg zC*$|j);jo0L}M)Q1-cu;L6C%QV=U&@SR<*=z>Dd^T5CTk?hMnSB|etWxb zyVT|S#n)wxI!AC&#AYJ=*{?Ptj4gT~RKOJaN3a_pLYqs)zXD`o@a6%$^am{8#)JYa z*o@>h3lYY+PD#EFB%GQKlaMolq0m?PBc_;p8(=TB6s@aK48dy{&aetB4IW++p8-9R z4sX)05kVo)VKRvRJ^pf>-^{1wuDQD^WBZQC+}|$3_fJb|aR-y02=nT7DC1;24~7G$ zL8|@=_~oTXaT5-_bLgqNoDm|&@qWJx{$TpXkCEx$F>2oi*pn0Ux9825UVH`&TBKIJ zeEj*>wIw0Of#k|HEV&IkYsQLLC61sw5ybmGZvv_6IRBv|bwWU&Z1@qoUj)1sxZWQJLn}e4>zV)MN zoXV{jeUb&njVb{UyJw(74ab%R(S6mf0O)+*49Qa+bQV?}fBeg+A6^d(Uz@?^7 ztk2PPD5;mLN}f}Q7udXxX??&M0JamrVaK1q3&f~CurlgX^YM#V1I8R>JpRtPfBECK z-7}zrZ{7G*ux)fZB*sd?8BN3`78Dj9d`@WQ0Izry%go{!ZKCykzLI+fQRtbrM8gGN zLJgA>b}z1TlwZzDQNijE3ahvr>EWU}4AK#=c7LZQXxk@rMAHDjfsl62zlFOX0xAFL z;a;NHky@p18nQ&GdCdyAyYtAFl3$x+}leSl#sUSVO-_M`RRO*h>y`8gBqn4KBHkG7A{@`+M4{!z(j9Rm^4xHw8x zn&bl0Ub`ge=Ql9hO3CfXMNMrCjzt~qtRr>6ts+4_I4l0B>gC-P=X4qVT%G+F|cl zn*m;SVNP_$+8b-+W}TH7ZmawNjyKb|Zff84QpdrBZmN>uIt4R;b%{dL?+qiLzXZTV zIEDJp0Nt#@7$nrC>vRX@6lI6~ZAI9rRG^FiPh*vHteK|NXCdH;<}r`k|m(>25l)? z4Wf9h+nd1=H@}!VS74lt5lVkgF9GO8<0pzHcU3|sQ#me_s!cygkIck^H8x`gXg$4R zj)6%z6zi{+UU8a{hcp5KN9ih}=6iExa;@k{osJO%H8-y!Sy8B#=`tEn!M-iS_S0m) zErXVie_6aOmp9?a#E6({J@3J=&O-Hjm!_`wE_fsJsuUUMZA2nRVd8555=s`47`Gsf z)rGcBVsl=SQG5|w8;na2`~VnXA5SpA>Klb=5}DeFYhoMI;@eV+Dm+Nq<>mY|59q?{w3?=GJQqCy25X}Vq^jMVm^G~wF_&|fUoFRFJV5nZ*oJ+_KfJHWZLj`wp ze5X9%GIzH#DUBo`l5Q*`1llvPEKfa2NZc{!}Y)uHpB;s}2!JXg$?t=KXL6 zkA7dN&hU1`2`VDBB6=`B-F^?rLKnElN?cH=NR$`pLf3DbnZ=Eonk`lzNkmg1{~3K3bwW9thO&l-OvHE_<|1dh!qJjl;_hJ_qppFB8~9nq{~ z2@n&6BvG{wXvB9SxF6fxeVpFSl<%K4n9Ce`GtgFrPr6z3?J2F3`OaIq2TlpT7VQpV z?tN60lJjL@?RLxQTaJB+QBApBC91NRm^-Omi7I*Vr)EjZ8?~^k4u*LYfx>DU4KXN6 z7PNjT~01qvvvI?qAa8_7>!D#rV#Re6{zkm0XqN_`i$lQWBR~lk}Q{W5WOk z97Kmt^6*ZV25Y8a7sFra%WfUS0 zrY=E1H{t2qhrM2(n1M0Cd?$|Jx{4}S_w(V_zN;q$eUfzq`c`}cAPe~HUig)(p}<{u2y zd78VEVL`2~KmepUMf%Dv%E|;q&_$0 zg)4SV8F0>=748{^AP0jAqY=XDb<2D2i5^FTk+AoUfkJ(u0>5@-{`M-AroV+j5xWNtd)}pggq3RT2GvdJ*3UE*=;{!M#*_O{?J>wl7?@1%F?Vj_R zO!iDf;xZ#hpa*Q*=ztHv9zgH|LAz)p$;$qZmPg&Us4W|m*6$&zGt)SuFqdOAPwYBq z?n9NXYSbJBB{Z17A5=r2+_Yg|YcSfb1TuSgY=+=!1?Z+~M+*}$+N?j(Q*>B`lrl@J zP6bs0<7u610CukpbJsLK*sR?GQ1=)(z&AoU145q1Bp)RS^`HiD&ZMevg)jjP!V{&Y zeB&A}Xo+h8Wi>*&gbm(PN%UV+Xhic8)TlpqT~HK5l@(0Dct?|ztCo) zwxF*Ri|$OVOj%BRhx%O82O~rnVa=Wy$X@t15~OGT*w(Y`tF-&<##!4G^EBS z&k&|s-Ox)=$8=QksrfuLQ^$A_S(nw}$cgk{x=iv;f2&oCJ;<_Vn5REgq>Evq@uIe6 zwa(COTg?xg^pfwe#ZP8Yg|d39uNFaPfglw`iYzG6S z-BIG}qIOL20=QL4^+Abg`L}%572+ZTd|e)e_@%+EFB&^wfP`e|lDy zNQUeV{9rO8EdG&AszRr_t;BQ$oIY}cr`3JN#iA0!%1=dW>sg3CO1!e4i}{=41dpkJ z%lxc}$nwXE^lV(KzOJQ9g+)9{;$)UeT({anLgB4>NXGd(^gl*+@!KO^dJsMOZtH)F zB8mf5i6N=nk^jckvQwt40qp-m+&Tu!j*Yb+FOWSN@&9R51l7j1L@PI#uz~xy6MQDE zM#>4P@~&PFoaMVPt6JCoFdKm)EEr-|A&Zv~U_I&ek=$bmm(4kx$jH1H&vaA;3wg>eN6MH_F6Jn!oAtLTdhwB-ccE zvD#b8PZ@H=udr9)h=i{X)?0_IJvOvv`43bQn4!|gVW^F?X{Ki;ub4JLlKEg{lvo^M#t=8~D3KskkR zPZ?&izJiJvRHz* zIi3zNO$OGrA!CcAYAdNh@e7z+7o?CO)#T0uw4-po8SJ<$Z)Eb=P__p$Y;THS>TqOeczGD#Hq>wscK&B1@iT*bt0lDA*N|RGI6kT8$8RrUqtJw(ncY@u*YB% z;IYTRjungIO>gbr_mSdBp<=S~!XV*FwEb+6C2bATr#<~m5v50cI_qvVv{q;w_VQrX zf7B;WI+96*BR%VI`no_1gVb0Q=L4LSSHj_(Uul#yh1C9c{VVZ|ApP%p47sz#g~I)Q z_#*Boi0F$2WX~huQybhDuc5-<*lx2-`|x5tx%l%P#`{Av{LyD0>}YHYYaDM4%i+0OP3?0HK74-6tYq zKceuUg6Ay;e2}F@C_uaKkiv4g7#Z!m-TXK?;)c^C7QsG)dV#m~&;+BoQBwpoKmD#m z_(|UEFQTFsY)4iRYE_S1$1emDBKy%^{EB*x{UiJNuMy;8-{*S0z-!Xj?p=LT*(V6Q zIOQRK-g~cFhP+pVvr@*rsulHAJi3HY--MJA&K#c8N>-e^e;5v~i?WHyjJlCjMPs+c zaumt1e5_d0n)h2(qwvkfT;ECY=*&AS7!;Bc&pA4Hgr<^2B@ps*hQ3l@N7?9TxcL^~ z*H&yRLjj!t1xb2gQ8xAmsFamm+@%bdMm3YCw+be|ATX%PMu);zM8IFIX8LMK#_=&9 zcd#Jz0i4OoW}cY=;7zQ6HX-cFqash56Sv1m9tCCc%sXF-#rgSxGYTsA&4DBQh)&zA z3Hg1E&^17msaB(TO@jXg6SZ1OE=L@kT#?~qtM!nM!-qp5324`Zr#wZ042#u2CFnTs zKl3_=&Ms&eI1iS`sRUxhaotl&FTsG-5O6Te_LwoK%#@nRmfn~73mUo{ljh&aXUZkqaT22I=SZ^m)q z)86{#`bofZAE7EU!G{`}F#%&F)9W{wRMT^l3o&I(fEL*!V}R;0I%>kX_WtTb1h=w{ zf3H`J#gL8r-ro%5rY!wQZ3HEe>KP zC+>BuRv4m5fg$V>1%ZH!vbRjnKcoEMHEH$lF}G9c}U5}}Hl*WFtrwaTGqCyndb^eBQFVU54h zP~4M`AF7|+G#>XJ?0$fCr|j8GQIB+COmnWCL*22K4BQWC2a(WaG8OL&m z_FR9DcyL<=o&$mHHucU6!%iEgQ^+n0ovrzYSt~5~`q-SKe=3Hb zFzul;ZQ6}rRnowzjU0T6d=ZE}mD`IpG+wkwx(0kHtv%JTyQ^;d&n{XuH?0b5``naG zf}1!k=Mrn80^y#yx|kBO=7I|KAS2LVKHB|ydK}C;L>gdoXwPJ&Ee^*~9^`n46wyK4 zSRYcQNuWggXxnP9bx_jEg>loLPQ5*x&%d}1UPBIEmaJJ}zVS7O0~Uv-8V>>i-L@@-ke^dc5REicBm|=XP7? zd^d8Wzu@M7xDi^&iHHG{s4pqe2rhJdgA3T&|LVmy9#ZHuaNuX7H!_mC_WmKg%Ki%Z z#@Lt5Y8svJ$k{0}=W&v3(}4QZDT_Gla+-18VB<-1yJPke5u(-iP!?>f$LqzA{Lf{B0PFWtzf{C-=f`}h&;>16GQLT=vH@kX-Vyxmu`mN} z0=$^ZhmmOI{~gi;IjiVC4C^c;V+Fd(VRT8%$x|M{VcV%5M1n3+HymSH|{j`OS_ znNsweG)td&c3Kiq+|yp1yo*+o1`tsVM-$(@l09~g>j&;SMm*JABlc;egSS(~zpCCC zQ&n*SS0xvHy@8GvSXFk zmLu$Kli)pto{!1=}9Vr;#rBhLj|cL zMq2fZRI2@0Rm-4=Af5jL^gA6O&<*02O~N@T49-f@tsRs3^3v~rcu_KzCDIGGIPDA# z^c8|@e=!3W$-G9yeB>E5rU4H6E3ov&dCNI)*^Q zEu|bHJ6jIdi>iC*Y)91G z=f$$jJ0?&@i^Y4{m|;HJy{E4rCqq^ZgabMJlv0o#pW`?;Eg{eOLOdH&WVNk3=VdkB zmg(ECtrXIMIXQJ_C?sre%siVt-tB)4LBX{VlYvIWt{(TZLzXl(MIh~R-Z-Cmbtxk; zN)`!T%44cRB4ZH{-=nL?eCx;N!z$M?V#wGw2PXsGUR`jkMFE(E7~zF{_f`b}4!7Gf zj$jr1GtHYZ(Cp)kkc%|jm5wGDAl8rNW@M?`O7{oz&UczoG)N9Y<1JWz<~b^FDR90B8C(2Pa>7jNr>-qi(FXuOr&}QIq2Q;J zb{Ws92X#-6JI^sXiG?XW90Kr&T;Ru!C&}7^+vJuY#TP59Q+eW{71n-zr4UnY4||;{ z1u|T!8Mt{xoSxtJr5b2N#$xy{AfnZ;2jBY>dVsB=yOr-g4dg>&7g*^t-Uk7{4%p5X zu-_NiJCAH`0os9Q5SlsPf)JW|GNWIvFQySip{Xe-8L~7xa}69tR6@_$q2ZJtQ^vgc zBKKbB*i1g2uS51(#`FkfSg16mE>a!~%_82;bnp$7f0|^N;eLdrIF1v6A9}ZOLaZHj z;u`ApH!=MpJMyF(vNE05#%KmBN5ArrOXJRu{{67vTqn=gM!Q7k)u;}5_NSRfo(=1C zm5xSaDCjH-ywx`U#m*Old$rpCP}^+TrOQO-MT=G=tB3U+;+)2=-9w8L4ANzbTkX>d zLMUCham`G@zenf@!DpTQR2a?kyj+RB_j$4+xLYlhdlzD}i>2uVT z(PiU(6#Dv@F+{v`1G8C)3HXi~+J6k@F z&T&YWD!v5`?<4Wow5LFD>L9UP>6jj`_6>(s3$e`OwwWh`AMI(ZermvVdb^5H7L%BI zmv$-m9MS2`k~2e|^R(m0(p9o1L1{J7KN#i3kt>5ytZ>)K{l`2qWb7ZDn8QWH_9JUP%47ih7Xgf_>|A#iL%t!UTfNE&EwU# zVf;H&E;j_63v9XM*R(Lhj<&wW^?Fu#@T{!Ab_0e5eT{YsE(Kg`D7KaeZEv4Gx+Qld zsX`_5FWlF^-zrczT{Z|2iNu|X+7IK z%1K!@ce?+5@t}!K&M>cGaKZ4;EFC~I_J_MPSo7RHujkqi^Nu>3RmjnvQ_&D|^&pj4zWaf(!Y1;hTK;)>Y_pm`d3`@QZ|K)O|A z%%$=2mewrNsB5{n$Gm_Pl^^HwHRm4OTAlysdITbhtFFse!*@NlbE~5X_`1)m1R%BT zIla_*_9B(0Id5&apl1(gp2e+RjrN`FjV`^n+xV+S$gpt2h)RW$tj-E+(Ymdw8PziB zb?Lkaa#<5lWU3h0`GR5b(`~$Caq~favUfc%UeRH3M1pQtr=hf|bw_}{a5WH*uWV^U z_1w-$v9_0f#xG++r9BR+QSHqVdH0tXAhn0D_y>ry2jCDfLWr@b!Ht}|1Zl}*#|_X7 zug?E@_0heSj0U!^(x>KKFIf@Bw+IvSBz7O?HJMV4L@C>Hby z?;GQv1&v*>_EV?#O}P2%K%@7W>%dsK4F>L)?j+ik&Bc*^ZQHc&h_g&$sDM@>o#AKKeV_zKkIBC}Gn5NtG3yVKmclJCjM&;(hg zIHOl{QA28H$`ilQ?tyC0_PaemUJ28FQi?)BP3jV!SM ztJK~I?}>4rZouoU06;S8UHWFcg#7uuNLc8}Piji3*A!CrY#3dTPliwJ>E4d!z}AE~ zbzozQ0Rbz%17W&X;WLpU<1nL2t#F7WW)W60tLHL+t30~=4J$_Go^uTMTJD_xA++PQ zdpDxxrU@d-u{Wz*&~N%{H>1yXTz+2@>z?7j+dgiN+w{b6aY>O`nnA#haVP_2gCDJu z5nI$yQ-Ch6EkJoRraab(7BbHWhv4}9{`J}8c8J>;7gu^LM%ww_Y-7j4Hle}8#5(4e z63W4%I+W+@#q(RvGx@A|RU7t6mznNhwJNK&F7q#zuf}iDB3o>S#P~^`t`XUkI%6&g z?(Kg?sNca07#ozS8U3O&_%>ji#Y(}PkOnzP*o24K6X8N+oBA3hw^(O*S}O5BNw!F z*T{C+(sM(eADOiqJYkn6Eq9`S@Ht$q{(e(`i`99K( zoj@|y?5d)#o;QT-{>}veolyAT%c4?HMWsc69qXk-Gm%G}PKQ1U!iBoKM1am%E1=AXj!G0JGxC~jTIs3t!emx1`+5i? z5HFs^gob@Tn#u3V9c3=yfi)mU1J9C^F0+d7$LmD>{nUn~WoN&`^Bro_XMPkz)(f zdRnXeu^VdG6a!?&4tilH?$mE&i2@@HQ(Dv;s_g+g3bOFDmTxI=nt^FqgQPQ1{6)K@ z-mzE?a8>S*>lU?;*5JK5GwKPHafo6j;Pr>D<3^>_ILz`Yo%* z(o*t2$AoKz1efD9vpZ3p_*kliNK3$AcPt0HVao+7;B1aXy3%s_$5Wr3Zt#4({k(6Sg) zP?UFD@QU5%E+n=Udr+@LwCcP2M73gG@S$yer=4Wf$#MEa2p$zh2Lxf`x3)Av>^eB( zQ7H}u!ym4NU`((Gm%AWqWl#ykMcK^x*Oaiy9fcxKJ6ZUzvrgMl2iT}joHC!;V$UD3 z59QMlj2wmngt&Gj=bif{DA}qcz$2U3f8vKf53TFg@+fVcN9?Vjq&;Wp%qof1Z1rQx z{MkyP(21ybz1Y;QCRNI?r4Rl3Rem7U?~9HNu{gWh`uQ?jaNm5&5@=SP8o;~K3Fe%0 z&L~+)MzQ<0nXXV;E4LtOM~)`;teTjIo^;|Y(}!1H1!~qw8NnMtD`=nh@0={D$?5<{ zFjno1L|x?1J*eHXlerVvNH?mj?g$nbbx0|NW#fe7Y+m4k(og>kODo&&eX8{de*xP* z-k)HUR^Jgx-M;7gtnZoh;_oU3m5Qm%8sMO0Xr5@@0&39K%k)0Tom`GRHCj#!^4HOBH zi8q7~R=PPosko~FAcFnuw-;^+EpC^6at0t6%lizI9sK$$$Lb;~5h047={= zp>o~b9rqrV2RNdKz2qyzNFwm;p=sm?W0eV0{5*R|-Ja#A45t6S!%NQd_EXmNti(#{ zA@>iKVe=M_{Wmi^uK2c6CVy}V+^%2YD8BKdBp_tu{NuT3Rs7>NJsL@AY<L{^aXEE+6#_WG^2n zVK!f%_rEL#cFZHLU-8|=ssaSPPWz3x4BMF3NL!gX)s13}r`@##+hF}QfBl-gEz|!A zt#Iit^Vd-!roIBE4@Fky%jLZY;5bWsb5A)7Jkky)hMdH*D8B96OsL6?i7@MsR<$axx)mvWEMNhl*rBa z*5eGp#`9HWe_gFRGRXyub;n?Y7JnIvJA*wc1)UpjTs+D#WiOvMO2F!{VF^oA&t!gR zT-4#LjNs|AjeXIvztm;j&8fQk=;erczXwU^f!(WTJvc58A2DpqrB9FTq;%SvOZIlz zva4o-hF4^UB$v*bv2Z!AYeWy2{TvO1)y1%!x>27#Vzv&)D;m>xaAYqdNhW7Wl$w14 zy+6x9k@Q~Q-B;v1oZ)@+pb6Ld?5lnE@?**=?t(pE(_rzcKo5_R0efQSUBa0K-|VM` zKzt*$(Ay&pVC^QitE`OkGtgG46er-I>U7GC7h1=eWni7_JycV7eeo`~i`JgRQzslt z=c?sbwjdV+$7C7Pz+Hc?0V772o1(jp5Dya}O?C=kMvK*i%4}ZzV;bO_o14JZ*A>C@ zL;a7KF`9;E9~oKO;X=fY9@-o8@aj62VSQ6Wh|VEO*sPk&nj#}R zC9Wj+{ORp3h6V9V>FvG{Yf`++c^~ZKGRNV+s{eN9BY=5==j6qiUcv#~uly^4jK`{!dNKK@{BwGXs;Qugf@%V-VTIWD8;Xk_=U4YX}Rp_to> zeIELv%nnNKJEs)z+fL_yB8XXmbHcH^!8JAthz?2Q^CzvC)2zdz})5oki6Gd?zbCOx6DA4YUb2UzMziJTF=aMFAJ*Pyn}dGcnpJ=gTwwV5=BwI>+X6^FHKrlol1;rHGQ!~=`>s5% zgJ*lLiFaScYuSL4*r5eJk1MkZsSQZ{R4RZvo1z8SsZr|`q9k5V6J-9DiNT(Fq<$0~ z3iO_rBKqT6vWjf!{cW5d)g`XaAm9}wIS1$D zaX%)G+ZW`*^TD4e1{Sdixbn_DE$7ZhyK`ThoaG@pkxSpJ2~>ox5ZG4xxnBNPvxDAa zXuy5$t^YlMA?%e+?9%&cau}~K1%kDq_~p^w_D+$v&Ha6hTO|ne&zYH|WjBN^PZu}V z@H8^db1;2OUkK|y?JAfs5Nhx#oZBlVfQWi5?^=y&`#TkkX*3&yKKF8vEHuAE)@Xj) zYnsynyp>(e+SV)#W7`fpvB1!K>??XF7W-dX0v~jL`29;SKYPtaa4Y<_E3uzB@El+0 z{KWI4>V~$sd27#zH5HW>BVXyoo?>6-P3%V1R)alP->FiwU6G!a6Zxtaf5%OESy{*V ziQzS|2Mp9T%(9zaq-$Bm2N~Cn1g$#0x)>_+TL`Wn{ z6IFucUS2N*a9>Vx|=>0&mh-Y5QR3x+Xww`Yg$T5tso%gX>ot^ehRU}J+11Nl} zeBG6p>DBK<3%cgGk#W_DCTUrZrXHE{8h`lLe|^!}?O29_cAHmIQiKl{2C}Fxs@!PW z6n|Aeq&Tn^Q6@=_eiC0MmRJoXF0EWNbj4uLln{=!v1{(n0NXY3?(xpleu(86_RT=Vyr}Fgfz4*sf1O0_^Kym>3PlHE|$~++DZgH(K4H>dPfT@ z$ty}-A!PfK3kk!7(7m7azvm2B702omJ(r|f<;W#URkQVm9f_;11z?4qWf&c0M{MLsOPf{;qF!2Ao48AC^q<6)Lx+C4KnRaI za(t3Qz3~wp<&Q3T{-qxi{GGp?Us+(`h|&KJT4VW1>wIZb^W+ILCvOhX*az!}kg5=5~Reg}ta(FMBn<<6zhIu?x(< zbZfcTZ2H`fKLU;(4puPxZT{*jC6eW5Xx2Z{%OtsYL73{OG%u4^n$Bw`d4EADmo_&= z_W~JQPHAZ_A5L=7a<=RSap7?~XdWY=#C$l`!G9B{uyL4k(Dn^_j@CZB8!abF$fqMs z)%eLIk)o)=x^a9mKl9fCueO6=HpOm;-k*AXLp!3iF5Q%%?j zn1jqMe{y^3a8rq-eDNZkXfZc>u&JZF;a%2T4fJFYCAe4i$ZIpGdmH-AQYc}H>dI&c z+Ek$I9maBF3B8V1ef`6MoIA!>dv4IY#Hz&9G0+Jf&CH0UVWDMsV3$$HzT)va#k&Um zGu}+KDJ7loSMwxcYj_4lOms21@na|Qw-QOob&ZyDC7xO0D0DmC`L;(xj^ zWFP|y+aYRay}AcyY2iRp*;G}3eq>OYN7!f&3UFU1rJV)P#M|Cao>$*4@!I8ph_0dt zF@BQMH$rd^4t^RJ66%U}Hv1zz{`4#iZuK|ZMdULqB(SX&G`Hv0{P{IME?gtH7;|C- z9#`dL#|;B6Da`|?eus_#hnPlF8J0r{$|et+Lg^*u!`l?&@6FB7hALd5EYD!n@w?jq}6olF~-uo|xQ5%3#EmFYyGt>SI_WW}!c;FG`_4cUwBq zfaR~=c}tZF*PB#>Shu2x0E~wZevy3Nm-|xq-FDJ#mWCOt{kECJC2j3vNqUvbQ(0>- z(o?#C@SjUy5z}T+=E^Kwj~mjX2P%A40&UM6N7LXMd{%FUG(1EU>75hGlYYMz*qTx7 z+DHv63DO6bR+@}onAKgk2BPG*P*=CdqRM869Xn^1t(^SLiG?tX^KO9Wj{0icdrmqw z-mSMRbrUxy2s-F4e7qm}?>R6xqUSP>u#x*bxbFD$_8Kk7|CDvn+~tr!)bYhs&pmsY zBo~&HVn6YDs6X4Tk*6YjcQE_w~i|@8l-zu+8VQI z<~JME3zOrreoZX3a^^x;F*&QKm|6SjEHNP85+hAE z2R?F*iB5+jhuunNjZf7e(AaE}MA_0=#pfhx!SHN*Uh*o#%=24(ERXXjF3#+b-XBFB z65pq&pNjigRO5Z|E624)vN{~5Wck&S$_GTBv=-sgWh`%7huLK;WtAQ+w0|}`15VHh zULkBKt%T-FE{S63v;*SOIlt%&7^KkTU3%OH#|pC=-xdf{O|_J@Ou zWc_~|!4H?sCl-afaJ?Eqx#X*pO85u28GF^)mn|i`CHwp)32#t;!JT4X;f#car+;udoi&h0rO6y&WO`%*Q@|i}$jAdwd z4*4OM-aPDcCg3@zzICYbk8x5g(_4`mQwRU#2JeREW4jO9*w9b#2JbKw;ejejaO-R| z_Px+=I5rU*KxPx5R~$##jc#6WB(e?{v9{jm3m;K1LDVm+;7D*GJJjfnTmyVbLv{@N z&iTt$&{fNAd>q)OiNzESY6vt672*&cg@F-rUj?msA32UMc%4YlwRxYs8G^Ukd`FsFG-s4g>Bya~OXUh9;28eb!I{BofA z*f5$sf<=7c)_*4F1ub|1lDCIiH*l=9A3 zS3oZwUvJCGQZya)qW%PW9}Cj+NH!bx@=)4Ko1 z>IRJUU;{33tQ9Z)`(b=Do_g9L;rI`@EgS}~{H$YG>J}(nlvNr%^L!$C?sBIWYQ5Q# z0rQag2tCFa=pXC~%1MeH|MK6WDxAu)Pf=RpgjUzu>+H*(u6x}YXGCojXG^4v5cB22 ztnR*U*cSZ%xJU|*v66TWsVIJ+yOX4!r#^3Y4m=gHDuHWJecoF07Qb4tN#bp|=3m+r zVD$%=LZK%D(CVTdWLK+}P1 zxuSzetAo^)tk&N0QV^*0Vbn1EOWeovEM|~$to^b`8HBVtvHKe>gHHTXRWUVgFB#Rz zpaiKBQ*bJq{}M8h;6lbb2qpo9Y$h>7lw7+#i=k^~_nNKhqKBu&GN9?JXNz$EGkHx{ z&ApiY_MG}OU#*CE0XBuY#oc5-}!sRciyrT|0+QuHm~e+AQ* ztC0PiBz?{`=om5=im*G2^J)k8Jq9-b8fZs_u-e*^@8~1XJ-(QWF*YLX05rOw?`)hJ z;IYD`Gakg6;V0Z=b)>+lF;W^}*D7SjNCrFXsiX89om*uMUmfY9X(Rmv?now*y>QY7 zqNVgQNV@*Yp`6y6T%^(UCS-BnyP*BHb)GcpIRbUZZmb%+N;H}yc1YVGp$B_dMFGOh zR^2UL7XeLYD~_Nm&rWPE&9y=QpHv;B+%I~1A|Ay>B;yuJ+(ld$ zE%{hXb%CBZ;1DETJ)>81W^{>*GVIqprs*wU&0|2T8Itl%&T@dsQs;?FGFdsRh%{iu zYE-v;jd=eSTy1*d&mqSD5n|j6F}?x9`>ewaFSM`X@TkK)j1P^C$f?duy-a|jM4ctn zWN~`bnE-JPR#(kkpys`#DuAh1qA1rW_T-d?NABhSE{boZ(Mr(`6q)l-OgjTWsD7V@ z@0;oCZ&Cavjs7*-9Gu*NCxS68l#(KYVTYW+?J5TbQQS&%h2n+z$2-|%SX=gMHN#r< zyX-Qojr(Pe+*-=oLNlzXdlDBnJ*Q2t07?1o{~n*-H`GqN<6p{eHr$LvGk0=_!@iAL z!|BL|zeyr9K+N72%7qK&<%^~}FLE5{Tp&Pf()y`f@X~+eFr(Q#Su(2k5L3$nCii~zQznT?ocd}ytnBC&H@-s5anW}5tUi@YbJN?sA`e`W*h!uN3 zi~@SQ^^F=P+HkpD+cp$`EFcDz{{=%nA-N!K{i~R9L0ol{Os6Yx4bf_z!%2Jx~c_zV&q|UajG{6<3kt4GP$t`~C-? za3w(7pQ88+z_2LWcVl{f>eG`njv`Le1r_mPyc}z^{aBvuzl9YCemZx{v#J&9Upg^l zbaHRmJ1ntrf_JRPqgC5XkCx8vL(;SO_+;{|0u-OxJ~yTmijpz(O4{g*sm5U2eXOYd zj!1M{Ly4(;sSeMU64{mYPbu-KkTMOz>%B^NB|vzYYoZlned-=p7=S1j1AhYv z4}Hm_CA^J$c39(m%s=I?>m3}>aLBOy&W+Fg&gZ@Nf%%?!j2gx#!^zM@I1~sau^Hg` zSW2EjjIHi%>mGS&bV18}&%!=W<&ZeLNzXo6ZpfevM#2?AW!TSp`a*wr5*r9rvwfb0 zXBc@WJ)JpmNV?0Np|}#juPT!)OtK2B<#|5@g?Uz)C~QVzqo_y}Mn75zz!?b-gGNTD z{xD`bnO`Vr-ao+z@K9EwNl!za%`}w#4sdri%)B6_>eChJR6TFS4T-wk+{_4|z zH_?CopP!$vYW{6z(N#Jz(@D?5sc6WZ*IhLa^T!lfcW>o9ojo8Zvq7L5{MeogwOSYs z&#nB%o+pOemsl}lc|u{@Ig$+dT`}-%*Jh_4E0}O}@7AgN|39D}yTye}>Ug`w#h65h zevua!aC4pd?p0A$0>s9OV=Gy);RaUheS#I6Kg5a~+gZ^Md$k@bkF9ViZhHj9f3u?I zc0Bf9tT>K+s=b?8u@OI1!(+J)mwOxo&#s3c6)-3!OQ z7w-H`h-=z}1;wV*w z9u)tKmeDmYqPJQeN4?jhcn69vfMM|I*k7XfcQDpRP+&*qILN9B?)>T__=I1V{S7OY zzmpX!5h}d`-Cd2Yt$vObYtY_WjJFE=b5#hguKFw<1G1Lgj{<{P{tqaybFut`XyslM zsJsHP&?~Wrv2p;#O=u52SPLU!Ew-xHpz<12Uh_2GdjZA!QD94B)hAJW8O3ii7=OGq zERm-|XZ)|2pOr{~S~SKl8%!u2{)Ey7&fj^J^RK+^JvRWG-7k_e)fS0*{Y{B{RU3O& zA|GY(47P)TWHb(M@Zia;W;(tv_^Np@$&wii=4#Nj<3l+-rY~X!2Pu~GCD|XA?a5Vf zkvFC^2Hx^K3Y<3D@sU$GAbf~;4590j7WR)He=|2#u_)DO?d-JfhBJ=n8*OXwD2 zEV* z(*;74=kYc`nik{Jxf@W)q}ssH*aQ|=BTKJh|7%O|%ne6YHlQT|R_*d=k^bH~;<-1n}oacXe@Z_#(6RJYlyYDK; zv)9{-uN%~5ySFps$2nJ8-aKc;ng5kPQE6+&8)(gVgF2Jf3$I2`;au&26SniMtmrOd zMdLrQq6glaqjf1&hUS9_trK@qCuZ6Iw zp?Xu0kF!n*hM3BzODd^koj;Nc)%wFBenY&mQnGA<{pO)$ED5+3#A{vS$jZYf7l2BrnUAbPbGS1!`HvYCs z{|$Yz#Gj!HiOHeFOd_d_8-HJc-x)BjL=o9g7h!{}OWUAH&vp9q3a(`NYAj)#qQn`oD0a;>WNn7kC>x?}Y)r&j z6C;fK1EY-7GVyEK5~Bu>iHO4fNcN{%=_%{p@5%afGd*qjQw#lMU_T~i!{9&*s#~Ej z*+cVDC-%(pr@dm^G!ZH_P9~JBZMWFB&4*O2w;dfCV@!UUq(>&D3rz@&+0|w9mL`sN z%conjKHcc>baU3HL6j#a$H$4{tQ%>`+6W{6MU;@ytRNm*>u;1(qjos5Hl z3_5`EcSPZm-BOU#EHFhf<&TH_W@WWXkipQm(FjRk*5l2B`i&+>S;wsO?|M^jc+}|W zvB}^F34R;h7r?%!sW(iO=`vl3O@`tb&x<{tQkVh?P>&l;qb3t15lULfgXIPl&P3x( zh7MnD>_}2cBm;gkm)u}@&uBDcR?n42^%^+iAF);bN_@obNye7qOP3kjWuxYi#+C|k zB5GFahMz=TZu&{XQ%8rOx`>@sJ#Hd1T}dm;!lj8RFWU=eEA#x@IxPLT7b30bcKhR z_1uhK_Z2@MknLeVMSW|88m>rBRlbqV_%J_8_I2X?-Fb` zH~m>mJzigdqI)Aogrs4|UXK+y5YdIyBqxzjW&wsz2jD8P@;<(d6)VuI9&AY=?R9K2fuH-0$+Uk<{T-}Xb`1xZwn_+x6am3FxM zoBuAR?&JF~AI&(|-HW%aK%BuMWN+)dgkN!pXWepq`ZOz+z9y!g7^MCFYRvCEgdMEF z5|%QcZN!=IqX-x%|1nU4(3@3wq6%49d~b`X+y6U&9NMfH+!=$v0tAA~4r*~uuo=zM z@p2zJd8!WsM4(U8f1*R5Lw6A>QHh@R<3+oG`vsMF><;wmXP5xAvJ0fL4Jf`54a~z? z#Pyiu3T%F~WA@8HX8kzeH~=!E2%rjNqUlG3KrLpg4+(52l}!gGbU6s=FbMO+DPRLr z(f3QB4ruDZy0{Ep)(S)vk$b^|m<$hybuCU-_kr3eZOvg&L_Ojg>T%{^Q8h-1;H1SE zQ8{*A%HZ4C^(XiNM7;-FHw{2tBTm=%KqZ#F2~7dni?F$WEdn??;oV(|8T8?dW-|!5 z?8%sVQ2!{%whVN265KfN`j~oxlfokQBmQFbcTfdm>qa&nic8sm0d`_iDnTlA@V^^q zr+AY^*TvK{2bA%r6Ue5(tHp=~p>Pa3<9`@=@+!ehTS4vH@u~i$tXKm!*n+XOgNhH( z5mgjBkyoGw1Y8ROt^?;?ONa$>dV!p>SymhYcYD#42Tv@+i&h}Yi2JRVv@aRCj9xz=y=)M-)bspgKFt+e^p?}*U zr6`M11q2Emt6c$SP&IJcjpnb1cvuhI@BVvM^n+bIkVqap(SZmW3Y4MqyK5ktDd>%k z-|m1^X~)=30nbZL0z+tu_74}L-Xh42Q^4O&2*yE36$-nfL&h}_02Gl!n~28|>(fAc zik|}cfVax<=|nnc=Vo++j{Vl4<=qfL9T;yNB67Cl2l6j%g9sn2jj0JGD43}X5kq_b zGNvY#-1RY5Y=-Js3=X&fRJ;{WbON>v46zG`FZW?0PiN1bJG9 zG*;K(Kz29kU55eA2ZnZn^R|Ncmq6NC^XTb|Kx+^Whrw?<@XA_j z5A65^hJ~ygooJvRWYGZGv;&k~0a?5s@?aJELM}W?spUl{JwvQ$1xmI;6zoRm)EeScjyMh+%`}S1$12l$#o4XFTo@?Ag=5%=F$hwTY)^FbS}IF;-(5b z*os|i9>j^UZ9)*52Uzn#y_aFi+VN`{2~|+ob|_Lhp58?qfW2xuXTJCU#?&L`wC#Nq zvVBQ<$EzNXZ3Hot!(3PgZQqR_D$!IeaXhSo>LjXwJTA=%YmtGF32QD4LZ*{3zw0zkjwUFrLc!vj4 zZ$0L>0(3u!q6O{khdwHU8Fdu$bUUWI8yr6fdA|g6)C&}MK+RHQe@oEurBKXuKq4h=I0n&NN&7)ygEDaZVMvuV@Xgcq5G8@w0Bd9qo~5Wqii7Te zgj)e)ryb*^w0jK@X-gn@`Y}gaJAe&Lw+9v;W$UYi7(M`Nj)FdW&!hJ*V8u3!h=NEL zgD`gkCF@a-!by*kH3uY?LAag5P$}}W2ZK2P0->B0yP-)A!N6Dm;ZaTcoFYHLGyCw$ zDzq{mYJ%cIDFe}JU}H1br2=L=#i-85oHu~aDXm2rCWSHr)BzE`*uV z2+F3&W6DlKnFH2CHg$q8DRQX;)<`$n+k#K6z;iozp%JrR2gSDsBucT=*J2$|54))y z^wNXjZ$VT0Pq1Pc%-?wsX_ThA3qqzAKTzz{0rbiXvDpv)SOKX*c~4sLqHbERLK4#1 z#!8@MJ61r8K||XSo869aGyy|(Faj&+I58Htm7wu1p#2)KI2~6g2fOqTb3!Stg}k4S z>E41D9o>QkAmJ!Y3q@7;quy$eN;^nu9|dh=&`r=d6h=+K*tH;^YE$XA+_rR)J1BkdaN{iNR`9#sD*(>XIHvl5Uj^q zV?8K#H%PgcR(CM&8Yqz}217|Xo1rhaqp3dhh4QM_V;U)=TL-NTu<&fgf@l+@1tmML zMo$}o>?&AVC!xQZaWCB9+GAinlFQfSP8sPAmRx~)^_NqYayiDVT*18 zb#_5~(K&$Cz{>_u;!ddEa?Bf@SfCRDT` z3!=FZ6S@u(q!%+o$Co-F%y&caHG}ObyDeREegwATAmsf*V2FY?kAS%7=*E1Q6*c6A zzzo*HEUSS%*#wH{MDI7?<#Y;UC1zn2Bq0TX?tna9h6SJpywwP+pdLzT7lyDBgI)|b zngp%-%0NE5X_WxpKLu5}1uT9G`JiC_E`}o5 z4&Bp$Ip2i|t%ktsz!0_r2iw549>}^Q)Mt?VVn~5<;E#@=P_q7kRlqYUFNV?44t25; zm3PBsL1$eSVL5UTHVEYyK1P%S3A_mN+dzUAbhQKWstI_hhnii2g~>cLN=Me3V8Si{ zc6UKk9l;!RW3fjE-{`^tFPf?VY!iA)xwPs?Fv7MtiplFm$0-N05B5E!L_P$SwHiW( z5>rx2ueDH0TOb8`Fbk))qc0TH2lA#UVao7T1NqVcgZ@TX;X6SQt)T20@IoW(Y>G*y zBw)2z@m&jE*p0`QL)7ntKwbyRF2lfgT7v50=ekup_Er z7}5d0&5$6AU`HGQsnCh2qmTki0HZTSbUi{ZJ}t$ZFM;HuLqr`QhF!1(dog9@5M-2^ zY&)4Vuu8W;1TBMOl~Pz%U@qxm2Rf70iAk=;MD>8A=z>-+twYajytss{&n3LkXZ-yA5 zBa`zW4=Au~Cor@WR9p#JLdPw)f>&!G6OZ6)H6#d~*rL4Ll)rl=s`O#H8-TEd5F=HP zVIH{eC=c8+Xo$@uCqV*~X08I}LO1wmF&bDwYZv^`gCQ&g&+NdQH-TciVK>pO94kPq zbhwjp$W_2lr=xHSfa0<@rY2_oA5cpN2+Xqq$bumO02inT08mQ-0u%rg00;;O00?dy zMa;7S$bumO02inT01E&B0000000000000000000*ZggpMc~f+6a%E6U1qJ{B00031 P0RTAw006ro0RR91S6=N{ literal 0 HcmV?d00001 diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..2737c54f --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/papi.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/papi.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/papi" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/papi" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..5dda444a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,337 @@ +# -*- coding: utf-8 -*- +# +# papi documentation build configuration file, created by +# sphinx-quickstart on Fri Jun 20 15:28:01 2014. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# import mock +# MOCK_MODULES = ['numpy', 'ConfigParser', 'python2_3', 'scipy', 'scipy.interpolate'] +# for mod_name in MOCK_MODULES: +# sys.modules[mod_name] = mock.Mock() + +sys.path.insert(-1, os.path.abspath('../')) +sys.path.append( os.path.abspath('../')) +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'papi' +copyright = u'2014, Author' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '' +# The full version, including alpha/beta/rc tags. +release = '' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build', 'yapsy', 'pyqtgraph'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'papidoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'papi.tex', u'papi Documentation', + u'Author', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'papi', u'papi Documentation', + [u'Author'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'papi', u'papi Documentation', + u'Author', 'papi', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# -- Options for Epub output ---------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'papi' +epub_author = u'Author' +epub_publisher = u'Author' +epub_copyright = u'2014, Author' + +# The basename for the epub file. It defaults to the project name. +#epub_basename = u'papi' + +# The HTML theme for the epub output. Since the default themes are not optimized +# for small screen space, using the same theme for HTML and epub output is +# usually not wise. This defaults to 'epub', a theme designed to save visual +# space. +#epub_theme = 'epub' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# A sequence of (type, uri, title) tuples for the guide element of content.opf. +#epub_guide = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + +# Choose between 'default' and 'includehidden'. +#epub_tocscope = 'default' + +# Fix unsupported image types using the PIL. +#epub_fix_images = False + +# Scale large images. +#epub_max_image_width = 0 + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#epub_show_urls = 'inline' + +# If false, no index is generated. +#epub_use_index = True diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..074aa323 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,23 @@ +.. papi documentation master file, created by + sphinx-quickstart on Fri Jun 20 15:28:01 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to papi's documentation! +================================ + +Contents: + +.. toctree:: + :maxdepth: 4 + + papi + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 00000000..635af0d8 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,242 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\papi.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\papi.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/modules.rst b/docs/modules.rst new file mode 100644 index 00000000..0761b96f --- /dev/null +++ b/docs/modules.rst @@ -0,0 +1,7 @@ +papi +==== + +.. toctree:: + :maxdepth: 4 + + papi diff --git a/docs/papi.data.dcore.rst b/docs/papi.data.dcore.rst new file mode 100644 index 00000000..1c610d17 --- /dev/null +++ b/docs/papi.data.dcore.rst @@ -0,0 +1,22 @@ +papi.data.dcore package +======================= + +Submodules +---------- + +papi.data.dcore.DPlugin module +------------------------------ + +.. automodule:: papi.data.dcore.DPlugin + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: papi.data.dcore + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/papi.data.rst b/docs/papi.data.rst new file mode 100644 index 00000000..577b05fc --- /dev/null +++ b/docs/papi.data.rst @@ -0,0 +1,62 @@ +papi.data package +================= + +Submodules +---------- + +papi.data.DCore module +---------------------- + +.. automodule:: papi.data.DCore + :members: + :undoc-members: + :show-inheritance: + +papi.data.DGui module +--------------------- + +.. automodule:: papi.data.DGui + :members: + :undoc-members: + :show-inheritance: + +papi.data.DObject module +------------------------ + +.. automodule:: papi.data.DObject + :members: + :undoc-members: + :show-inheritance: + +papi.data.DOptionalData module +------------------------------ + +.. automodule:: papi.data.DOptionalData + :members: + :undoc-members: + :show-inheritance: + +papi.data.DParameter module +--------------------------- + +.. automodule:: papi.data.DParameter + :members: + :undoc-members: + :show-inheritance: + +papi.data.DPlugin module +------------------------ + +.. automodule:: papi.data.DPlugin + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: papi.data + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/papi.gui.rst b/docs/papi.gui.rst new file mode 100644 index 00000000..0c29f862 --- /dev/null +++ b/docs/papi.gui.rst @@ -0,0 +1,38 @@ +papi.gui package +================ + +Subpackages +----------- + +.. toctree:: + + papi.gui.qt_dev + papi.gui.qt_new + +Submodules +---------- + +papi.gui.gui_api module +----------------------- + +.. automodule:: papi.gui.gui_api + :members: + :undoc-members: + :show-inheritance: + +papi.gui.gui_event_processing module +------------------------------------ + +.. automodule:: papi.gui.gui_event_processing + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: papi.gui + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/papi.plugin.rst b/docs/papi.plugin.rst new file mode 100644 index 00000000..d627f70e --- /dev/null +++ b/docs/papi.plugin.rst @@ -0,0 +1,17 @@ +papi.plugin package +=================== + +Subpackages +----------- + +.. toctree:: + + papi.plugin.base_classes + +Module contents +--------------- + +.. automodule:: papi.plugin + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/papi.rst b/docs/papi.rst new file mode 100644 index 00000000..f89220fb --- /dev/null +++ b/docs/papi.rst @@ -0,0 +1,91 @@ +papi package +============ + +Subpackages +----------- + +.. toctree:: + + papi.data + papi.event + papi.exceptions + papi.gui + papi.plugin + papi.tests + papi.ui + +Submodules +---------- + +papi.ConsoleLog module +---------------------- + +.. automodule:: papi.ConsoleLog + :members: + :undoc-members: + :show-inheritance: + +papi.DebugOut module +-------------------- + +.. automodule:: papi.DebugOut + :members: + :undoc-members: + :show-inheritance: + +papi.PapiEvent module +--------------------- + +.. automodule:: papi.PapiEvent + :members: + :undoc-members: + :show-inheritance: + +papi.constants module +--------------------- + +.. automodule:: papi.constants + :members: + :undoc-members: + :show-inheritance: + +papi.core module +---------------- + +.. automodule:: papi.core + :members: + :undoc-members: + :show-inheritance: + +papi.error_codes module +----------------------- + +.. automodule:: papi.error_codes + :members: + :undoc-members: + :show-inheritance: + +papi.main module +---------------- + +.. automodule:: papi.main + :members: + :undoc-members: + :show-inheritance: + +papi.main_2 module +------------------ + +.. automodule:: papi.main_2 + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: papi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/papi.tests.rst b/docs/papi.tests.rst new file mode 100644 index 00000000..550bf469 --- /dev/null +++ b/docs/papi.tests.rst @@ -0,0 +1,70 @@ +papi.tests package +================== + +Submodules +---------- + +papi.tests.TestCore module +-------------------------- + +.. automodule:: papi.tests.TestCore + :members: + :undoc-members: + :show-inheritance: + +papi.tests.TestCoreMock module +------------------------------ + +.. automodule:: papi.tests.TestCoreMock + :members: + :undoc-members: + :show-inheritance: + +papi.tests.TestDBlock module +---------------------------- + +.. automodule:: papi.tests.TestDBlock + :members: + :undoc-members: + :show-inheritance: + +papi.tests.TestDCore module +--------------------------- + +.. automodule:: papi.tests.TestDCore + :members: + :undoc-members: + :show-inheritance: + +papi.tests.TestDPlugin module +----------------------------- + +.. automodule:: papi.tests.TestDPlugin + :members: + :undoc-members: + :show-inheritance: + +papi.tests.TestDSubscription module +----------------------------------- + +.. automodule:: papi.tests.TestDSubscription + :members: + :undoc-members: + :show-inheritance: + +papi.tests.TestGUI module +------------------------- + +.. automodule:: papi.tests.TestGUI + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: papi.tests + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/papi.ui.gui.rst b/docs/papi.ui.gui.rst new file mode 100644 index 00000000..7b9086e4 --- /dev/null +++ b/docs/papi.ui.gui.rst @@ -0,0 +1,18 @@ +papi.ui.gui package +=================== + +Subpackages +----------- + +.. toctree:: + + papi.ui.gui.qt_dev + papi.ui.gui.qt_new + +Module contents +--------------- + +.. automodule:: papi.ui.gui + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/papi.ui.rst b/docs/papi.ui.rst new file mode 100644 index 00000000..9f54fbbb --- /dev/null +++ b/docs/papi.ui.rst @@ -0,0 +1,17 @@ +papi.ui package +=============== + +Subpackages +----------- + +.. toctree:: + + papi.ui.gui + +Module contents +--------------- + +.. automodule:: papi.ui + :members: + :undoc-members: + :show-inheritance: diff --git a/main.py b/main.py new file mode 100644 index 00000000..c3f00f5b --- /dev/null +++ b/main.py @@ -0,0 +1,43 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'control' + +import sys +from papi.core import Core +from papi.gui.qt_dev.gui_main import startGUI as dev_startGui +from papi.gui.qt_new.main import startGUI as new_startGui + + +def main(): + core = Core(new_startGui) + core.run() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/papi/ConsoleLog.py b/papi/ConsoleLog.py new file mode 100644 index 00000000..d414bfb6 --- /dev/null +++ b/papi/ConsoleLog.py @@ -0,0 +1,42 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'ruppins' + + + +class ConsoleLog(object): + + def __init__(self,lvl,identifier): + self.lvl = lvl + self.ident = identifier + + + def printText(self,l,msg): + if l <= self.lvl: + print(self.ident+msg) \ No newline at end of file diff --git a/papi/PapiEvent.py b/papi/PapiEvent.py new file mode 100644 index 00000000..1294f081 --- /dev/null +++ b/papi/PapiEvent.py @@ -0,0 +1,69 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'control' + +class PapiEvent(object): + count = 0 + + def __init__(self,orID,destID,type_,op,optParameter): + """ + Function used to create a new Event ready to send. + + :param orID: plugin id of sender + :type orID: int + :param destID: plugin id of destination + :type destID: int + :param type_: event type, see list + :type type_: string + :param optParameter: optinalParameterObject for information + :type: optParameter: DOptionlaData + """ + + self.__originID__ = orID + self.__destID__ = destID + self.__eventtype__ = type_ + self.__operation__ = op + self.__optional_parameter__ = optParameter + PapiEvent.count += 1 + + def get_originID(self): + return self.__originID__ + + def get_destinatioID(self): + return self.__destID__ + + def get_eventtype(self): + return self.__eventtype__ + + def get_event_operation(self): + return self.__operation__ + + def get_optional_parameter(self): + return self.__optional_parameter__ + diff --git a/papi/__init__.py b/papi/__init__.py new file mode 100644 index 00000000..e87a6f2b --- /dev/null +++ b/papi/__init__.py @@ -0,0 +1 @@ +__author__ = 'Knuth' \ No newline at end of file diff --git a/papi/constants.py b/papi/constants.py new file mode 100644 index 00000000..1ff2f643 --- /dev/null +++ b/papi/constants.py @@ -0,0 +1,99 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Stefan Ruppin +""" + +__author__ = 'ruppin' + + +# CORE CONSTANTS +CORE_PROCESS_CONSOLE_IDENTIFIER = 'Core Process: ' +CORE_CONSOLE_LOG_LEVEL = 1 + + +CORE_PAPI_VERSION = 'v_0.8' # no spaces allowed +CORE_CORE_VERSION = 'v_0.9' # no spaces allowed +CORE_PAPI_CONSOLE_START_MESSAGE = 'PaPI - Plugin based Process Interaction' + ' Version: ' + CORE_PAPI_VERSION +CORE_CORE_CONSOLE_START_MESSAGE = 'PaPI Core Modul ' + CORE_CORE_VERSION + ' started' +CORE_STOP_CONSOLE_MESSAGE = 'Core and PaPI finished operation cleanly' + + +CORE_ALIVE_CHECK_ENABLED = True +CORE_ALIVE_CHECK_INTERVAL = 2 # seconds +CORE_ALIVE_MAX_COUNT = 2 + +# EVENT CONSTANTS +# TODO +# somethink like: EVENT_TYPE_STATUS = 'status_event' and EVENT_OPERATION_CHECK_ALIVE = 'check_alive' + + +# GUI CONSTANTS +GUI_PROCESS_CONSOLE_IDENTIFIER = 'Gui Process: ' +GUI_PROCESS_CONSOLE_LOG_LEVEL = 1 +GUI_VERSION = 'v_0.8' +GUI_START_CONSOLE_MESSAGE = 'PaPI GUI Modul ' + GUI_VERSION + ' started' + +GUI_PAPI_WINDOW_TITLE = 'PaPI - Plugin based Process Interaction' +GUI_WOKRING_INTERVAL = 16 # in ms +GUI_WAIT_TILL_RELOAD = 1000 # in ms + +GUI_DEFAULT_WIDTH = 800 +GUI_DEFAULT_HEIGHT = 800 + +# PLUGIN LOCATION CONSTANTS +PLUGIN_ROOT_FOLDER_LIST = ['plugin','papi/plugin', '../plugin'] +PLUGIN_IOP_FOLDER = '' +PLUGIN_VIP_FOLDER = '' +PLUGIN_DPP_FOLDER = '' +PLUGIN_PCP_FOLDER = '' + +# PLUGIN TYPE IDENTIFIER +PLUGIN_IOP_IDENTIFIER = 'IOP' +PLUGIN_VIP_IDENTIFIER = 'ViP' +PLUGIN_PCP_IDENTIFIER = 'PCP' +PLUGIN_DPP_IDENTIFIER = 'DPP' + +# PLUGIN STATE IDENTIFIER +PLUGIN_STATE_PAUSE = 'paused' +PLUGIN_STATE_RESUMED = 'resumed' +PLUGIN_STATE_START_SUCCESFUL = 'start_successfull' +PLUGIN_STATE_START_FAILED = 'start_failed' +PLUGIN_STATE_ALIVE = 'alive' +PLUGIN_STATE_DEAD = 'dead' +PLUGIN_STATE_ADDED = 'added' +PLUGIN_STATE_STOPPED = 'stopped' + + +# +PLUGIN_API_CONSOLE_IDENTIFIER = 'Plugin API: ' +PLUGIN_API_CONSOLE_LOG_LEVEL = 1 + +# CONFIG/PROFILE SYSTEM CONSTANTS + +CONFIG_DEFAULT_DIRECTORY = 'cfg_collection/' +CONFIG_DEFAULT_FILE = 'cfg_collection/testcfg.xml' +CONFIG_ROOT_ELEMENT_NAME = 'PaPiConfig' # for xml save +CONFIG_LOADER_SUBCRIBE_DELAY = 1000 # ms \ No newline at end of file diff --git a/papi/core.py b/papi/core.py new file mode 100644 index 00000000..e0e3d1aa --- /dev/null +++ b/papi/core.py @@ -0,0 +1,1068 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Stefan Ruppin +""" + +__author__ = 'ruppin' + +import os +from multiprocessing import Process, Queue + +from yapsy.PluginManager import PluginManager +from threading import Timer + +from yapsy.PluginManager import PluginManager +from papi.PapiEvent import PapiEvent +from papi.data.DCore import DCore +from papi.data.DPlugin import DPlugin +from papi.ConsoleLog import ConsoleLog +from papi.gui.qt_dev.gui_main import startGUI +from papi.data.DOptionalData import DOptionalData + + +# import contants +from papi.constants import CORE_PROCESS_CONSOLE_IDENTIFIER, CORE_CONSOLE_LOG_LEVEL, CORE_PAPI_CONSOLE_START_MESSAGE, \ + CORE_CORE_CONSOLE_START_MESSAGE, CORE_ALIVE_CHECK_ENABLED, \ + CORE_STOP_CONSOLE_MESSAGE, CORE_ALIVE_CHECK_INTERVAL, CORE_ALIVE_MAX_COUNT + +from papi.constants import PLUGIN_ROOT_FOLDER_LIST, PLUGIN_VIP_IDENTIFIER, PLUGIN_PCP_IDENTIFIER, \ + PLUGIN_DPP_IDENTIFIER, PLUGIN_IOP_IDENTIFIER, PLUGIN_STATE_PAUSE, PLUGIN_STATE_RESUMED, \ + PLUGIN_STATE_START_SUCCESFUL, PLUGIN_STATE_START_FAILED, PLUGIN_STATE_ALIVE, PLUGIN_STATE_STOPPED, \ + PLUGIN_STATE_DEAD + +import papi.error_codes as ERROR + +import papi.event as Event + +class Core: + def __init__(self, gui_start_function, use_gui = True): + """ + Init funciton of core. + Will create all data needed to use core and core.run() function + + .. document private functions + .. automethod:: _* + """ + self.gui_start_function = gui_start_function + + # switch case structure for processing incoming events + self.__process_event_by_type__ = { 'status_event': self.__process_status_event__, + 'data_event': self.__process_data_event__, + 'instr_event': self.__process_instr_event__, + } + + self.__process_status_event_l__ = { 'start_successfull': self.__process_start_successfull__, + 'start_failed': self.__process_start_failed__, + 'alive': self.__process_alive__, + 'join_request': self.__process_join_request__, + 'plugin_stopped': self.__process_plugin_stopped__ + } + + self.__process_data_event_l__ = { 'new_data': self.__process_new_data__, + 'new_block': self.__process_new_block__, + 'new_parameter': self.__process_new_parameter__ + } + + self.__process_instr_event_l__ = { 'create_plugin': self.__process_create_plugin__, + 'stop_plugin': self.__process_stop_plugin__, + 'close_program': self.__process_close_programm__, + 'subscribe': self.__process_subscribe__, + 'unsubscribe': self.__process_unsubsribe__, + 'set_parameter': self.__process_set_parameter__, + 'pause_plugin': self.__process_pause_plugin__, + 'resume_plugin': self.__process_resume_plugin__, + 'start_plugin': self.__process_start_plugin__ + } + + # creating the main core data object DCore and core queue + self.core_data = DCore() + self.core_event_queue = Queue() + self.core_goOn = 1 + self.core_id = 0 + + # setting up the yapsy plguin manager for directory structure + self.plugin_manager = PluginManager() + self.plugin_manager.setPluginPlaces(PLUGIN_ROOT_FOLDER_LIST) + + # define gui information. ID and alive status as well as gui queue for events + self.gui_event_queue = Queue() + self.gui_id = self.core_data.create_id() + self.gui_alive = False + self.use_gui = use_gui + + # set information for console logging part (debug information) + self.log = ConsoleLog(CORE_CONSOLE_LOG_LEVEL, CORE_PROCESS_CONSOLE_IDENTIFIER) + + # define variables for check alive system, e.a. timer and counts + self.alive_intervall = CORE_ALIVE_CHECK_INTERVAL + self.alive_count_max = CORE_ALIVE_MAX_COUNT + self.alive_timer = Timer(self.alive_intervall,self.check_alive_callback) + self.alive_count = 0 + self.gui_alive_count = 0 + + def run(self): + """ + Main operation function of core. + Event loop is in here. + """ + + # some start up information + self.log.printText(1,CORE_PAPI_CONSOLE_START_MESSAGE) + self.log.printText(1,CORE_CORE_CONSOLE_START_MESSAGE + ' .. Process id: '+str(os.getpid())) + + # start the GUI process to show GUI, set GUI alive status to TRUE + if self.use_gui is True: + self.gui_process = Process(target=self.gui_start_function, args=(self.core_event_queue,self.gui_event_queue,self.gui_id)) + self.gui_process.start() + self.gui_alive = True + + + # start the check alive timer + if CORE_ALIVE_CHECK_ENABLED is True: + self.log.printText(1, 'Alive check of processes is enabled') + self.alive_timer.start() + + # core main operation loop + while self.core_goOn: + # get event from queue, blocks until there is a new event + event = self.core_event_queue.get() + + # debung out + self.log.printText(2,'Event->'+event.get_eventtype()+' '+event.get_event_operation()) + + # process the next event of queue + self.__process_event__(event) + + # check if there are still plugins alive or gui is still alive before ending core main loop + self.core_goOn = self.core_data.get_dplugins_count() != 0 or self.gui_alive + + # core main loop ended, so cancel active alive timer + self.alive_timer.cancel() + + # core finished operation and did clean shutdown + self.log.printText(1, CORE_STOP_CONSOLE_MESSAGE) + + def send_alive_check_events(self): + """Function for check_alive_status timer to send check_alive events to all running processes + This will trigger all correctly running processes to send an answer to core + """ + # get list of all plugins [hash: id->dplug], type DPlugin + dplugs = self.core_data.get_all_plugins() + for id in dplugs: + # get dplugin object + plug = dplugs[id] + # check if this plugin runs in a separate process + if plug.own_process is True: + # send check_alive_status event to plugin process (via queue) + event = Event.status.CheckAliveStatus(self.core_id, plug.id, None) + plug.queue.put(event) + + # send check_alive_status event to gui process (via queue) + event = Event.status.CheckAliveStatus(self.core_id, self.gui_id, None) + self.gui_event_queue.put(event) + + def handle_alive_situation(self): + """ + Function which handles the check for the alive situation of all plugins in data base + Will distribute to error_handling methods to handle dead processes + + :return: + """ + #get all plugins from core data [hash: id->DPlugin] + dplugs = self.core_data.get_all_plugins() + if dplugs is not None: + for id in dplugs: + # get DPlugin object + plug = dplugs[id] + # check if plugins runs in a separate process + if plug.own_process is True: + # check if counts are equal: equal indicates that plugin is alive + if plug.alive_count is self.alive_count: + self.log.printText(2,'Plugin '+plug.uname+' is still alive') + # change plugin state in DCore + plug.alive_state = PLUGIN_STATE_ALIVE + else: + # Plugin is not alive anymore, so do error handling + self.plugin_process_is_dead_error_handler(plug) + + # check for gui status and do error handling + if self.gui_alive_count is self.alive_count: + self.log.printText(2,'GUI is still ALIVE') + else: + self.gui_is_dead_error_handler() + + def gui_is_dead_error_handler(self): + """ + error handler when gui is dead + :return: + """ + self.log.printText(1,'GUI is DEAD') + self.log.printText(1,'core count: '+str(self.alive_count)+' vs. '+ str(self.gui_alive_count)+' :gui count') + + def plugin_process_is_dead_error_handler(self, dplug): + """ + Error handler for a dead plugin process + + :param dplug: Plugin which is dead + :type dplug: DPlugin + :return: + """ + self.log.printText(1,'Plugin '+dplug.uname+' is DEAD') + self.log.printText(1,'core count: '+str(self.alive_count)+' vs. '+ str(dplug.alive_count)+' :plugin count') + dplug.alive_state = PLUGIN_STATE_DEAD + self.update_meta_data_to_gui(dplug.id) + + def check_alive_callback(self): + """ + callback function for check_alive status timer + handles sending events to processes and checking their answer + + :return: + """ + self.log.printText(2,'check alive') + + # check for answer status of all plugins + self.handle_alive_situation() + + # increase the global check_alive counter + self.alive_count += 1 + self.alive_count = self.alive_count % self.alive_count_max + + # send new check alive events to plugins + self.send_alive_check_events() + + # start a new one shot timer with this callback function + if CORE_ALIVE_CHECK_ENABLED is True: + self.alive_timer = Timer(self.alive_intervall,self.check_alive_callback) + self.alive_timer.start() + + def update_meta_data_to_gui(self,pl_id): + """ + On call this function will send the meta information of pl_id to GUI + + :param pl_id: id of plugin with new meta information + :return: + """ + # get DPlugin object with id and check if it exists + dplugin = self.core_data.get_dplugin_by_id(pl_id) + if dplugin is not None: + # DPlugin object exists,so build optinalData for Event + o = DOptionalData() + # get meta information of DPlugin + o.plugin_object = dplugin.get_meta() + # build event and send it to GUI with meta information + eventMeta = PapiEvent(pl_id, self.gui_id, 'instr_event', 'update_meta', o) + self.gui_event_queue.put(eventMeta) + + # check if plugin got some subscribers which run in own process + if dplugin.own_process is True: + dplugin.queue.put(eventMeta) + return 1 + else: + # Dplugin object with pl_id does not exist in DCore of core + self.log.printText(1,'update_meta, cannot update meta information because there is no plugin with id: '+str(pl_id)) + return -1 + + def update_meta_data_to_gui_for_all(self): + plugins = self.core_data.get_all_plugins() + for id in plugins: + self.update_meta_data_to_gui(id) + + def handle_parameter_change(self, plugin, parameter_name, value): + """ + This function should be called, when there is a new_data event for changing a parameter value + This function will change the value in DCore and update this information to GUI via meta update + + :param plugin: Plugin which owns the parameter + :type plugin: DPlugin + :param parameter_name: Name of the parameter which value should be changed + :type parameter_name: basestring + :param value: new value for the parameter + :type value: all possible, depends on plugin + :return: -1 for error(parameter with parameter_name does not exist in plugin), 1 for o.k., done + """ + allparas = plugin.get_parameters() + if parameter_name in allparas: + para = allparas[parameter_name] + para.value = value + self.update_meta_data_to_gui(plugin.id) + return 1 + else: + return -1 + + + # ------- Event processing initial stage --------- + def __process_event__(self,event): + """ + Initial stage of event processing, dividing to event type + + :param event: event to process + :type event: PapiEvent + """ + t = event.get_eventtype() + self.__process_event_by_type__[t](event) + + # ------- Event processing first stage --------- + def __process_status_event__(self,event): + """ + First stage of event processing, deciding which status_event this is + + :param event: event to process + :type event: PapiEvent + """ + op = event.get_event_operation() + return self.__process_status_event_l__[op](event) + + def __process_data_event__(self,event): + """ + First stage of event processing, deciding which data_event this is + + :param event: event to process + :type event: PapiEvent + """ + op = event.get_event_operation() + return self.__process_data_event_l__[op](event) + + def __process_instr_event__(self,event): + """ + First stage of event processing, deciding which instr_event this is + + :param event: event to process + :type event: PapiEvent + """ + op = event.get_event_operation() + return self.__process_instr_event_l__[op](event) + + # ------- Event processing second stage: status events --------- + def __process_start_successfull__(self,event): + """ + Process start_successfull event + + :param event: event to process + :type event: PapiEvent + """ + # get plugin from DCore with id of event origin and check if it exists + dplug = self.core_data.get_dplugin_by_id(event.get_originID()) + if (dplug != None): + # plugin exists and sent successfull event, so change it state + dplug.state = PLUGIN_STATE_START_SUCCESFUL + self.update_meta_data_to_gui(dplug.id) + return 1 + else: + # plugin does not exist + self.log.printText(1,'start_successfull_event, Event with id ' +str(event.get_originID())+ ' but plugin does not exist') + return -1 + + def __process_start_failed__(self,event): + """ + Process start failed event and do error handling + + :param event: event to process + :type event: PapiEvent + """ + # get plugin from DCore with id of event origin and check if it exists + dplug = self.core_data.get_dplugin_by_id(event.get_originID()) + if (dplug != None): + # plugin exists but start failed, so change it state + dplug.state = PLUGIN_STATE_START_FAILED + return 1 + else: + # plugin does not exist in DCore + self.log.printText(1,'start_failed_event, Event with id ' +str(event.get_originID())+ ' but plugin does not exist') + return -1 + + def __process_alive__(self,event): + """ + Processes alive response from processes/plugins and GUI, organising the counter + + :param event: event to process + :type event: PapiEvent + """ + # get event origin + oID = event.get_originID() + # check if its the gui + if oID is self.gui_id: + # increment GUI counter + self.gui_alive_count += 1 + self.gui_alive_count = self.gui_alive_count % self.alive_count_max + else: + # its not the Gui, so search for plugin in DCore + dplug = self.core_data.get_dplugin_by_id(oID) + # check if plugin exists. If plugin exists, increment its counter + if dplug is not None: + dplug.alive_count += 1 + dplug.alive_count = dplug.alive_count % self.alive_count_max + return True + + def __process_join_request__(self,event): + """ + Process join requests of processes + + :param event: event to process + :type event: PapiEvent + :type dplugin: DPlugin + """ + # get event origin id and its corresponding plugin object + pl_id = event.get_originID() + dplugin = self.core_data.get_dplugin_by_id(pl_id) + # check for existance + if dplugin is not None: + # get process of plugin and join it + dplugin.process.join() + # remove plugin from DCore, because process was joined + if self.core_data.rm_dplugin(dplugin.id) is False: + # remove failed + self.log.printText(1,'join request, remove plugin with id '+str(dplugin.id)+'failed') + return -1 + else: + # remove from DCore in core successfull + if self.gui_alive is True: + # GUI is still alive, so tell GUI, that this plugin was closed + opt = DOptionalData() + opt.plugin_id = dplugin.id + # event = PapiEvent(self.core_id,self.gui_id,'status_event','plugin_closed',opt) + event = Event.status.PluginClosed(self.core_id, self.gui_id, opt) + self.gui_event_queue.put(event) + + self.log.printText(1, 'join_request, plugin with id '+str(dplugin.id) + ' was joined (uname: ' +dplugin.uname+' )') + return ERROR.NO_ERROR + else: + # Plug does not exist + self.log.printText(1,'join_request, Event with id ' +str(event.get_originID())+ ' but plugin does not exist') + return -1 + + # ------- Event processing second stage: data events --------- + def __process_new_data__(self,event): + """ + Process new_data event from plugins. + Will do the routing: Subscriber/Subscription + + :param event: event to process + :type event: PapiEvent + :type tar_plug: DPlugin + """ + # just proceed with new_data events if GUI is still alive (indicates that program will close) + if self.gui_alive: + # get event origin and optional parameter + oID = event.get_originID() + opt = event.get_optional_parameter() + + # get origin plugin from DCore + dplug = self.core_data.get_dplugin_by_id(oID) + # check for existence + if dplug is not None: + # get data block of DPlugin with block_name from event + block= dplug.get_dblock_by_name(opt.block_name) + # check for existence of block with block_name + if block is not None: + # get subscriber list of block + subscriber = block.get_subscribers() + # id list dummy for event destination id list + id_list = [] + # for all subscriber in subscriber list + for sub_id in subscriber: + # get plugin with sub_id and check for existence + pl = self.core_data.get_dplugin_by_id(sub_id) + if pl is not None: + # plugin exists, check whether it is a ViP or not + if pl.type == PLUGIN_VIP_IDENTIFIER or pl.type == PLUGIN_PCP_IDENTIFIER: + # Because its a ViP, we need a list of destination ID for new_data + id_list.append(pl.id) + else: + # Plugin is not running in GUI, so just 1:1 relation for event and destinations + opt.parameter_alias = pl.get_subscribtions()[oID][opt.block_name].alias + new_event = Event.data.NewData(oID, [pl.id], opt) + pl.queue.put(new_event) + + # this event will be a new parameter value for a plugin + if opt.is_parameter is True: + self.handle_parameter_change(pl, opt.parameter_alias, opt.data) + else: + # pluign with sub_id does not exist in DCore of core + self.log.printText(1, 'new_data, subscriber plugin with id '+str(sub_id)+' does not exists') + return -1 + # check if our list with id is greater than 1, which will indicate that there is at least one ViP + # which will need to get this new data event + if len(id_list)> 0: + # send new_data event to GUI with id_list of destinations + opt = event.get_optional_parameter() + opt.parameter_alias = pl.get_subscribtions()[oID][opt.block_name].alias + new_event = Event.data.NewData(oID, id_list, opt) + self.gui_event_queue.put(new_event) + # process new_data seemed correct + return 1 + else: + # block is None + self.log.printText(1, 'new_data, block with name '+opt.block_name+' does not exists') + return -1 + else: + # Plugin of event origin does not exist in DCore of core + self.log.printText(1,'new_data, Plugin with id '+str(oID)+' does not exist in DCore') + return -1 + + def BACKUP__process_new_data__(self,event): + """ + Process new_data event from plugins. + Will do the routing: Subscriber/Subscription + + :param event: event to process + :type event: PapiEvent + :type tar_plug: DPlugin + """ + # just proceed with new_data events if GUI is still alive (indicates that program will close) + if self.gui_alive: + # get event origin and optional parameter + oID = event.get_originID() + opt = event.get_optional_parameter() + Data = opt.data + + # get origin plugin from DCore + dplug = self.core_data.get_dplugin_by_id(oID) + # check for existence + if dplug is not None: + # get data block of DPlugin with block_name from event + block= dplug.get_dblock_by_name(opt.block_name) + # check for existence of block with block_name + if block is not None: + # get subscriber list of block + subscriber = block.get_subscribers() + # for all subscriber in subscriber list + for sub_id in subscriber: + # get plugin with sub_id and check for existence + pl = self.core_data.get_dplugin_by_id(sub_id) + if pl is not None: + # demux signals + + + subcribtions = pl.get_subscribtions() + sub_object = subcribtions[oID][opt.block_name] + sub_signals = sub_object.signals + sub_signals.append('t') + opt.data = dict([(i, Data[i]) for i in sub_signals if i in Data]) + + # plugin exists, check whether it is a ViP or not + if pl.type == PLUGIN_VIP_IDENTIFIER or pl.type == PLUGIN_PCP_IDENTIFIER: + # Plugin runs in GUI + opt = event.get_optional_parameter() + opt.parameter_alias = pl.get_subscribtions()[oID][opt.block_name].alias + new_event = Event.data.NewData(oID, [pl.id], opt) + self.gui_event_queue.put(new_event) + else: + # Plugin is not running in GUI + opt.parameter_alias = pl.get_subscribtions()[oID][opt.block_name].alias + new_event = Event.data.NewData(oID, [pl.id], opt) + pl.queue.put(new_event) + + # this event will be a new parameter value for a plugin + if opt.is_parameter is True: + self.handle_parameter_change(pl, opt.parameter_alias, opt.data) + else: + # pluign with sub_id does not exist in DCore of core + self.log.printText(1, 'new_data, subscriber plugin with id '+str(sub_id)+' does not exists') + return -1 + + # process new_data seemed correct + return 1 + else: + # block is None + self.log.printText(1, 'new_data, block with name '+opt.block_name+' does not exists') + return -1 + else: + # Plugin of event origin does not exist in DCore of core + self.log.printText(1,'new_data, Plugin with id '+str(oID)+' does not exist in DCore') + return -1 + + # ------- Event processing second stage: instr events --------- + def __process_create_plugin__(self,event): + """ + Processes create_plugin event. + So it will create a plugin, start a process if needed, send events to GUI to create a plugin and do the pre configuration + + :param event: + :param optData: optional Data Object of event + :type event: PapiEvent + :type optData: DOptionalData + :return: -1: Error + """ + # get optData to get information about which plugin to start + optData = event.get_optional_parameter() + self.log.printText(2,'create_plugin, Try to create plugin with Name '+optData.plugin_identifier+ " and UName " + optData.plugin_uname ) + + # search yapsy plugin object with plugin_idientifier and check if it exists + plugin = self.plugin_manager.getPluginByName(optData.plugin_identifier) + if plugin is None: + # Plugin does not exist in yapsy manger, maybe it is new + # collect plugin information from file system again and recheck + self.plugin_manager.collectPlugins() + plugin = self.plugin_manager.getPluginByName(optData.plugin_identifier) + if plugin is None: + self.log.printText(1,'create_plugin, Plugin with Name '+optData.plugin_identifier+' does not exist in file system') + return -1 + + #creates a new plugin id because plugin exsits + plugin_id = self.core_data.create_id() + + # checks if plugin is of not of type ViP or PCP, because these two will run in GUI process + if plugin.plugin_object.get_type() != PLUGIN_VIP_IDENTIFIER and \ + plugin.plugin_object.get_type() != PLUGIN_PCP_IDENTIFIER: + # So plugin will not run in GUI + # it will need an own process and queue to function + + # create Queue for plugin process + plugin_queue = Queue() + + # decide if plugin will need to get Data from another plugin + if plugin.plugin_object.get_type()== PLUGIN_DPP_IDENTIFIER: + # plugin will get data from another, so make its execution triggered by events + eventTriggered = True + else: + # stand alone plugin, so it will do its permanent execute loop + eventTriggered = False + + plugin_config = optData.plugin_config + + if plugin_config is None or plugin_config =={}: + plugin_config = plugin.plugin_object.get_startup_configuration() + + # create Process object for new plugin + # set parameter for work function of plugin, such as queues, id and eventTriggered + PluginProcess = Process(target=plugin.plugin_object.work_process,\ + args=(self.core_event_queue, plugin_queue, plugin_id, eventTriggered, \ + plugin_config, optData.autostart)) + PluginProcess.start() + + #Add new Plugin process to DCore + dplug = self.core_data.add_plugin(PluginProcess, PluginProcess.pid, True, plugin_queue, plugin, plugin_id) + dplug.plugin_identifier = plugin.name + dplug.uname = optData.plugin_uname + dplug.type = plugin.plugin_object.get_type() + dplug.alive_count = self.alive_count + dplug.startup_config = plugin_config + + # change some attributes of optional data before sending it back to GUI for local creation + optData.plugin_identifier = plugin.name + optData.plugin_id = plugin_id + optData.plugin_type = dplug.type + + # send create_plugin event to GUI for local creation, now with new information like id and type + event = Event.instruction.CreatePlugin(0, self.gui_id, optData) + self.gui_event_queue.put(event) + return 1 + else: + # Plugin will run in GUI, thats why core does not need to create a new process + + # Adding plugin information to DCore of core for organisation + dplug = self.core_data.add_plugin(self.gui_process, self.gui_process.pid, False, self.gui_event_queue, plugin, plugin_id) + dplug.uname = optData.plugin_uname + dplug.type = plugin.plugin_object.get_type() + dplug.plugin_identifier = plugin.name + dplug.startup_config = optData.plugin_config + + # change some attributes of optional data before sending it back to GUI for local creation + optData.plugin_identifier = plugin.name + optData.plugin_id = plugin_id + optData.plugin_type = dplug.type + + # send create_plugin event to GUI for local creation, now with new information like id and type + event = Event.instruction.CreatePlugin(0, self.gui_id, optData) + self.gui_event_queue.put(event) + self.log.printText(2,'core sent create event to gui for plugin: '+str(optData.plugin_uname)) + return 1 + + def __process_stop_plugin__(self,event): + """ + Process stop_plugin event. + Will send an event to destination plugin to close itself. Will lead to a join request of this plugin. + + :param event: event to process + :type event: PapiEvent + :type dplugin: DPlugin + """ + # get destination id + id = event.get_destinatioID() + + # get DPlugin object and check if plugin exists + dplugin = self.core_data.get_dplugin_by_id(id) + if dplugin is not None: + # dplugin exist, get queue and route event to plugin + if dplugin.own_process is True: + # dplugin is not running in gui + # tell plugin to quit + dplugin.queue.put(event) + dplugin.state = PLUGIN_STATE_STOPPED + self.core_data.unsubscribe_all(dplugin.id) + self.core_data.rm_all_subscribers(dplugin.id) + self.update_meta_data_to_gui_for_all() + + else: + if event.delete is True: + # plugin is running in GUI + # tell gui to close plugin + opt = DOptionalData() + opt.plugin_id = id + dplugin.queue.put( Event.status.PluginClosed(self.core_id, self.gui_id, opt)) + + # remove plugin from DCore + if self.core_data.rm_dplugin(id) != ERROR.NO_ERROR: + self.log.printText(1, 'stop plugin, unable to remove plugin von core_data') + return ERROR.UNKNOWN_ERROR + + else: + dplugin.queue.put( Event.instruction.StopPlugin(self.core_id, id, None, delete=False)) + dplugin.state = PLUGIN_STATE_STOPPED + self.core_data.unsubscribe_all(dplugin.id) + self.core_data.rm_all_subscribers(dplugin.id) + self.update_meta_data_to_gui_for_all() + + return ERROR.NO_ERROR + else: + # DPlugin does not exist + self.log.printText(1,'stop_plugin, plugin with id '+str(id)+' not found') + return ERROR.UNKNOWN_ERROR + + def __process_start_plugin__(self, event): + # get destination id + id = event.get_destinatioID() + + # get DPlugin object and check if plugin exists + dplugin = self.core_data.get_dplugin_by_id(id) + if dplugin is not None: + # dplugin exist, get queue and route event to plugin + if dplugin.own_process is True: + # dplugin is not running in gui + # tell plugin to quit + dplugin.queue.put(event) + else: + dplugin.state = PLUGIN_STATE_START_SUCCESFUL + self.gui_event_queue.put(event) + self.update_meta_data_to_gui(id) + + + + def __process_plugin_stopped__(self, event): + """ + Process plugin_stopped event. + Will change plugins state and delete its parameters and blocks. + Will update meta to gui + + :param event: + :return: + """ + pl_id = event.get_originID() + plugin = self.core_data.get_dplugin_by_id(pl_id) + if plugin is not None: + plugin.state = PLUGIN_STATE_STOPPED + + self.core_data.rm_all_subscribers(pl_id) + + # delete all blocks of plugin + blocks = plugin.get_dblocks() + block_to_delete = [] + for block_key in blocks: + block = blocks[block_key] + block_to_delete.append(block) + + for b in block_to_delete: + plugin.rm_dblock(b) + + # delete all parameters of plugin + paras = plugin.get_parameters() + paras_to_delete = [] + for key in paras: + para = paras[key] + paras_to_delete.append(para) + + for p in paras_to_delete: + plugin.rm_parameter(p) + + # update to gui + self.update_meta_data_to_gui(pl_id) + + def __process_close_programm__(self,event): + """ + This functions processes a close_programm event from GUI and + sends events to all processes to close themselves + + :param event: event to process + :type event: PapiEvent + """ + + # GUI wants to close, so join process + if self.gui_alive is True: + self.gui_process.join() + + # Set gui_alive to false for core loop to know that is it closed + self.gui_alive = False + + # get a list of all running plugIns + all_plugins = self.core_data.get_all_plugins() + + # iterate through all plugins to send an event to tell them to quit and + # response with a join_request + toDelete =[] + for dplugin_key in all_plugins: + dplugin = all_plugins[dplugin_key] + # just send event to plugins running in own process + if dplugin.own_process: + event = Event.instruction.StopPlugin(0, dplugin.id, None) + dplugin.queue.put(event) + else: + toDelete.append(dplugin.id) + + for dplugin_ID in toDelete: + self.core_data.rm_dplugin(dplugin_ID) + + def __process_subscribe__(self,event): + """ + Process subscribe_event. + Will set a new route in DCore for this two plugins to route new data events. Update of meta will be send to GUI. + + :param event: event to process + :type event: PapiEvent + :type dplugin_sub: DPlugin + :type dplugin_source: DPlugin + """ + # get event origin id and optional parameters + opt = event.get_optional_parameter() + oID = event.get_originID() + + + already_sub = False + # test if already subscribed + source_pl = self.core_data.get_dplugin_by_id(opt.source_ID) + if source_pl is not None: + blocks = source_pl.get_dblocks() + if opt.block_name in blocks: + b = blocks[opt.block_name] + subs = b.get_subscribers() + if oID in subs: + already_sub = True + + if already_sub is False: + dsubscription = self.core_data.subscribe(oID, opt.source_ID, opt.block_name) + if dsubscription is None: + # subscribtion failed + self.log.printText(1,'subscribe, something failed in subsription process with subscriber id: '+str(oID)+'..target id:'+str(opt.source_ID)+'..and block '+str(opt.block_name)) + return -1 + else: + # subscribtion correct + dsubscription.alias = opt.subscription_alias + + if opt.signals != []: + if self.core_data.subscribe_signals(oID, opt.source_ID, opt.block_name, opt.signals) is None: + # subscribtion failed + self.log.printText(1,'subscribe, something failed in subsription process with subscriber id: '+str(oID)+'..target id:'+str(opt.source_ID)+'..and block '+str(opt.block_name)) + return -1 + else: + pass + + self.log.printText(1,'subscribe, subscribtion correct: '+str(oID)+'->('+str(opt.source_ID)+','+str(opt.block_name)+')') + self.update_meta_data_to_gui(oID) + self.update_meta_data_to_gui(opt.source_ID) + + def __process_unsubsribe__(self,event): + """ + Process unsubscribe_event. Will try to remove a subscription from DCore + + :param event: event to process + :type event: PapiEvent + :type dplugin_sub: DPlugin + :type dplugin_source: DPlugin + """ + # get event origin id and optional parameters + opt = event.get_optional_parameter() + oID = event.get_originID() + + if opt.signals == []: + # try to unsubscribe + if self.core_data.unsubscribe(oID, opt.source_ID, opt.block_name) is False: + # unsubscribe failed + self.log.printText(1,'unsubscribe, something failed in unsubsription process with subscriber id: '+str(oID)+'..target id:'+str(opt.source_ID)+'..and block '+str(opt.block_name)) + return -1 + else: + if self.core_data.unsubscribe_signals(oID, opt.source_ID, opt.block_name, opt.signals) is False: + return -1 + + # unsubscribe correct + self.log.printText(1,'unsubscribe, unsubscribtion correct: '+str(oID)+'->('+str(opt.source_ID)+','+str(opt.block_name)+')') + self.update_meta_data_to_gui(oID) + self.update_meta_data_to_gui(opt.source_ID) + + def __process_set_parameter__(self,event): + """ + Process set_parameter event. Core will just route this event from GUI to destination plugin and update DCore + + :param event: event to process + :type event: PapiEvent + :type dplugin_sub: DPlugin + :type dplugin_source: DPlugin + """ + # get destination id and optional parameter + opt = event.get_optional_parameter() + pl_id = event.get_destinatioID() + + # get DPlugin object of destination id from DCore and check for existence + dplugin = self.core_data.get_dplugin_by_id(pl_id) + if dplugin is not None: + # Plugin exists + # get parameter list of plugin [hash] + parameters = dplugin.get_parameters() + + + if opt.parameter_alias in parameters: + para = parameters[opt.parameter_alias] + para.value = opt.data + # route the event to the destination plugin queue + dplugin.queue.put(event) + #change event type for plugin + #update GUI + self.update_meta_data_to_gui(pl_id) + return 1 + else: + # destination plugin does not exist + self.log.printText(1,'set_paramenter, plugin with id '+str(pl_id)+' not found') + return -1 + + def __process_new_block__(self,event): + """ + Processes new_block event. + Will try to add a new data block to a DPlugin object + + :param event: event to process + :type event: PapiEvent + :type dplugin_sub: DPlugin + :type dplugin_source: DPlugin + """ + # get event origin id and optional parameter + opt = event.get_optional_parameter() + pl_id = event.get_originID() + # get DPlugin object with origin id of event to add new parameter to THIS DPlugin + dplugin = self.core_data.get_dplugin_by_id(pl_id) + # check for existence + if dplugin is not None: + # dplugin exists, so add blocks to DPlugin + for b in opt.block_list: + dplugin.add_dblock(b) + # update meta information of GUI after new blocks were added + self.update_meta_data_to_gui(pl_id) + return 1 + else: + # plugin does not exist + self.log.printText(1,'new_block, plugin with id '+str(pl_id)+' not found') + return -1 + + def __process_new_parameter__(self,event): + """ + Processes new parameter event. Adding a new parameter to DPluign in DCore and updating GUI information + + :param event: event to process + :type event: PapiEvent + :type dplugin_sub: DPlugin + :type dplugin_source: DPlugin + """ + # get event origin and optional parameter + opt = event.get_optional_parameter() + pl_id = event.get_originID() + + # get DPlugin object of event origin id which is the plugin that wants to add parameter + dplugin = self.core_data.get_dplugin_by_id(pl_id) + if dplugin is not None: + # Plugin exists so loop through parameter list to add all parameter + for p in opt.parameter_list: + # add parameter to DPlugin + dplugin.add_parameter(p) + # update meta of GUI to introduce new parameter to user + self.update_meta_data_to_gui(pl_id) + return 1 + else: + # plugin does not exist + self.log.printText(1,'new_parameter, plugin with id '+str(pl_id)+' not found') + return -1 + + def __process_pause_plugin__(self, event): + """ + Processes pause_plugin event. Will add information that a plugin is paused and send event to plugin to pause it. + + :param event: event to process + :type event: PapiEvent + :type dplugin: DPlugin + """ + pl_id = event.get_destinatioID() + + dplugin = self.core_data.get_dplugin_by_id(pl_id) + if dplugin is not None: + if dplugin.state != PLUGIN_STATE_PAUSE: + # set pause info + dplugin.state = PLUGIN_STATE_PAUSE + # send event to plugin + # event = PapiEvent(self.core_id, pl_id, 'instr_event', 'pause_plugin', None) + event = Event.instruction.PausePlugin(self.core_id, pl_id, None) + dplugin.queue.put(event) + + # update meta for Gui + self.update_meta_data_to_gui(pl_id) + + def __process_resume_plugin__(self, event): + """ + Processes resume_plugin event. Will add information that a plugin is resumed and send event to plugin to resume it. + + :param event: event to process + :type event: PapiEvent + :type dplugin: DPlugin + """ + pl_id = event.get_destinatioID() + + dplugin = self.core_data.get_dplugin_by_id(pl_id) + if dplugin is not None: + if dplugin.state == PLUGIN_STATE_PAUSE: + # set resume info + dplugin.state = PLUGIN_STATE_RESUMED + # send event to plugin + # event = PapiEvent(self.core_id, pl_id, 'instr_event', 'resume_plugin', None) + event = Event.instruction.ResumePlugin(self.core_id, pl_id, None) + dplugin.queue.put(event) + + # update meta for Gui + self.update_meta_data_to_gui(pl_id) + + # def __process_plugin_paused__(self, event): + # """ + # Processes plugin_paused event from GUI. Will add information that a plugin was paused in gui and + # send update meta. + # :param event: event to process + # :type event: PapiEvent + # :type dplugin: DPlugin + # """ + # id = event.get_originID() + # + # dplugin = self.core_data.get_dplugin_by_id(id) + # if dplugin is not None: + # dplugin.state = PLUGIN_STATE_PAUSE + # + # self.update_meta_data_to_gui(id) + # + # def __process_plugin_resumed__(self, event): + # pass \ No newline at end of file diff --git a/papi/data/DCore.py b/papi/data/DCore.py new file mode 100644 index 00000000..fd3873c7 --- /dev/null +++ b/papi/data/DCore.py @@ -0,0 +1,380 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" +from papi.data.DPlugin import DPlugin, DBlock +from papi.ConsoleLog import ConsoleLog + +import papi.error_codes as ERROR + +__author__ = 'control' + +import copy + + +class DCore(): + def __init__(self): + self.__DPlugins = {} + + self.__newid = 0 + self.log = ConsoleLog(2, "DCore") + + def create_id(self): + """ + Creates and returns random unique 64 bit integer + + :returns: 64bit random integer + :rtype: int + """ + self.__newid += 1 + return self.__newid +# return uuid.uuid4().int >> 64 + + def add_plugin(self, process, pid, own_process, queue, plugin, id): + """ + Add plugin with necessary information. + + :param process: Plugin is running in this process + :param pid: Process ID of the process in which the plugin is running + :param queue: Event queue needed for events which should be received by this plugin + :param plugin: Plugin object + :param plugin_id: ID of this plugin + :param id: ID for the new DPlugin + :return: Returns the data object DPlugin + :rtype: DPlugin + """ + + d_pl = DPlugin() + + d_pl.process = process + d_pl.pid = pid + d_pl.queue = queue + d_pl.plugin = plugin + d_pl.id = id + d_pl.own_process = own_process + + self.__DPlugins[id] = d_pl + + return d_pl + + def rm_dplugin(self, dplugin_id): + """ + Removes DPlugin with dplugin_id + + :param dplugin_id: + :return: + :rtype: bool + """ + + if dplugin_id in self.__DPlugins: + self.__DPlugins[dplugin_id].state = 'deleted' + + self.rm_all_subscribers(dplugin_id) + self.unsubscribe_all(dplugin_id) + + del self.__DPlugins[dplugin_id] + + return ERROR.NO_ERROR + else: + return ERROR.UNKNOWN_ERROR + + def get_dplugins_count(self): + """ + Returns count of known plugins in this data structure + + :return: + :rtype: int + """ + + return len(self.__DPlugins.keys()) + + def get_dplugin_by_id(self, plugin_id): + """ + Returns DPlugin object by ID + + :param plugin_id: ID of an DPlugin object + :return DPlugin: + :rtype: DPlugin + """ + + if plugin_id in self.__DPlugins: + return self.__DPlugins[plugin_id] + else: + return None + + + def get_dplugin_by_uname(self, plugin_uname): + """ + Returns DPlugin object by uname + + :param plugin_name: uname of an DPlugin object + :return DPlugin: + :rtype: DPlugin + """ + + for plugin_id in self.__DPlugins: + d_pl = self.__DPlugins[plugin_id] + + if d_pl.uname == plugin_uname: + return d_pl + + return None + + + def get_all_plugins(self): + """ + + :return: + """ + + return self.__DPlugins + + def subscribe(self, subscriber_id, target_id, dblock_name): + """ + + :param subscriber_id: DPlugin which likes to subscribes dblock + :param target_id: DPlugin which contains the dblock for subscribtion + :param dblock_name: DBlock for subscribtion + :return: + """ + + #Get Susbcriber DPlugin + subscriber = self.get_dplugin_by_id(subscriber_id) + + if subscriber is None: + self.log.printText(1, "Found no Subscriber with ID " + subscriber_id) + return None + + #Get Target DPlugin + target = self.get_dplugin_by_id(target_id) + + if target is None: + self.log.printText(1, "Found no Target with ID " + str(target_id)) + return None + + dblock = target.get_dblock_by_name(dblock_name) + + if dblock is None: + self.log.printText(1, "Target " + target.uname + " has no DBlock " + dblock_name) + + return None + + #Create relation between DPlugin and DBlock + + dsubscription = subscriber.subscribe(dblock) + + if dsubscription is None: + self.log.printText(1, "Subscriber " + str(subscriber_id) + " has already subscribed " + dblock_name) + return None + + if dblock.add_subscribers(subscriber) is False: + self.log.printText(1, "DBlock " + dblock_name + " was already subscribed by Subscriber" + subscriber_id) + return None + + return dsubscription + + def unsubscribe(self, subscriber_id, target_id, dblock_name): + """ + + :param subscriber_id: DPlugin which likes to unsubscribes dblock + :param target_id: DPlugin which contains the dblock for subscribtion + :param dblock_name: DBlock for unsubscribtion + :return: + :rtype boolean: + """ + + #Get Susbcriber DPlugin + subscriber = self.get_dplugin_by_id(subscriber_id) + + if subscriber is None: + return False + + #Get Target DPlugin + target = self.get_dplugin_by_id(target_id) + + if target is None: + return False + + dblock = target.get_dblock_by_name(dblock_name) + + if dblock is None: + return False + + #Destroy relation between DPlugin and DBlock + if subscriber.unsubscribe(dblock) is False: + self.log.printText(1, "Subscriber " + subscriber_id + " has already unsubscribed " + dblock_name) + return False + + if dblock.rm_subscriber(subscriber) is False: + self.log.printText(1, "DBlock " + dblock_name + " was already unsubscribed by Subscriber" + subscriber_id) + return False + + return True + + def unsubscribe_all(self, dplugin_id): + """ + This function is used to cancel all subscription of the DPlugin with the dplugin_id. + + :param dplugin_id: + :return: + """ + + dplugin = self.get_dplugin_by_id(dplugin_id) + + + subscribtion_ids = dplugin.get_subscribtions().copy() + + + #Iterate over all DPlugins, which own a subscribed DBlock + for sub_id in subscribtion_ids: + sub = self.get_dplugin_by_id(sub_id) + + dblock_names = subscribtion_ids[sub_id] + + for dblock_name in dblock_names: + + dblock = sub.get_dblock_by_name(dblock_name) + + dblock.rm_subscriber(dplugin) + + dplugin.unsubscribe(dblock) + + if 0 == len(dplugin.get_subscribtions()): + return True + else: + return False + + def rm_all_subscribers(self, dplugin_id): + """ + This function is used to remove all subscribers of all DBlocks, which are hold by the DPlugin with the dplugin_id. + + :param dplugin_id: + :return: + """ + + dplugin = self.get_dplugin_by_id(dplugin_id) + + dblock_names = dplugin.get_dblocks() + + for dblock_name in dblock_names: + + dblock = dplugin.get_dblock_by_name(dblock_name) + + dplugin_ids = dblock.get_subscribers() + for dplugin_id in dplugin_ids: + + subscriber = self.get_dplugin_by_id(dplugin_id) + + subscriber.unsubscribe(dblock) + dblock.rm_subscriber(subscriber) + + if len(dplugin.get_dblocks()) == 0: + return True + else: + return False + + def subscribe_signals(self, subscriber_id, target_id, dblock_name, signals): + """ + This function is used to subscribe a bunch of signals. + + :param subscriber_id: DPlugin which likes to subscribes signals of the chosen dblock + :param target_id: DPlugin which contains the dblock for subscribtion + :param dblock_name: DBlock for subscribtion + :param signals: List of signals which are needed to be added + :return: + """ + + #Get Susbcriber DPlugin + subscriber = self.get_dplugin_by_id(subscriber_id) + + if subscriber is None: + self.log.printText(1, "Found no Subscriber with ID " + subscriber_id) + return None + + #Get Target DPlugin + target = self.get_dplugin_by_id(target_id) + + if target is None: + self.log.printText(1, "Found no Target with ID " + str(target_id)) + return None + + dblock = target.get_dblock_by_name(dblock_name) + + if dblock is None: + self.log.printText(1, "Target " + target.uname + " has no DBlock " + dblock_name) + + return None + + return subscriber.subscribe_signals(dblock, signals) + + + def unsubscribe_signals(self, subscriber_id, target_id, dblock_name, signals): + """ + This function is used to unubscribe a bunch of signals. + + :param subscriber_id: DPlugin which likes to unsubscribes signals of the chosen dblock + :param target_id: DPlugin which contains the dblock for subscribtion + :param dblock_name: DBlock for subscribtion + :param signals: List of signals which are needed to be added + :return: + """ + + #Get Susbcriber DPlugin + subscriber = self.get_dplugin_by_id(subscriber_id) + + if subscriber is None: + self.log.printText(1, "Found no Subscriber with ID " + subscriber_id) + return False + + #Get Target DPlugin + target = self.get_dplugin_by_id(target_id) + + if target is None: + self.log.printText(1, "Found no Target with ID " + str(target_id)) + return False + + dblock = target.get_dblock_by_name(dblock_name) + + if dblock is None: + self.log.printText(1, " Target " + target.uname + " has no DBlock " + dblock_name) + + return False + + subscription = subscriber.unsubscribe_signals(dblock, signals) + + if subscription is None: + return False + + if len(subscription.get_signals()) == 0: + return self.unsubscribe(subscriber_id, target_id, dblock_name) + + return True + + def subscribe_dparameter(self, subscriber_id, target_id): + pass + + def unsubscribe_dparameter(self, subscriber_id, target_id): + pass diff --git a/papi/data/DGui.py b/papi/data/DGui.py new file mode 100644 index 00000000..c789809a --- /dev/null +++ b/papi/data/DGui.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'ruppins' + +from papi.data.DCore import DCore + + +class DGui(DCore): + + pass \ No newline at end of file diff --git a/papi/data/DObject.py b/papi/data/DObject.py new file mode 100644 index 00000000..450ede5f --- /dev/null +++ b/papi/data/DObject.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + + +class DObject(): + + def __init__(self): + self.id = 0 diff --git a/papi/data/DOptionalData.py b/papi/data/DOptionalData.py new file mode 100644 index 00000000..1107db31 --- /dev/null +++ b/papi/data/DOptionalData.py @@ -0,0 +1,55 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'ruppins' + + +class DOptionalData(object): + + def __init__(self,DATA=None,pluginID=None): + self.data = DATA + self.data_source_id = None + self.plugin_identifier = None + self.plugin_uname = None + self.plugin_id = pluginID + self.source_ID = None + self.unsubscribe_all = None + self.reason = None # muss raus + self.parameter_list = None # muss raus + self.block_name = None + self.block_list = None + self.plugin_object = None + self.plugin_type = None + self.plugin_config = {} + self.dblock_object = None + self.signals = [] + self.is_parameter = False + self.subscription_alias = None + self.autostart = None + + diff --git a/papi/data/DParameter.py b/papi/data/DParameter.py new file mode 100644 index 00000000..c5275761 --- /dev/null +++ b/papi/data/DParameter.py @@ -0,0 +1,48 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.data.DObject import DObject + + +class DParameter(DObject): + + def __init__(self, ptype, name, default=0, prange=0, live=1, Regex = None, OptionalObject=None): + super(DParameter, self).__init__() + + self.type = ptype + self.default = default + self.value = default + self.range = prange + self.live = live + self.name = name + self.plugin_id = None + self.plugin_identifier = None + self.regex = Regex + self.OptionalObject = OptionalObject \ No newline at end of file diff --git a/papi/data/DPlugin.py b/papi/data/DPlugin.py new file mode 100644 index 00000000..58d26b5e --- /dev/null +++ b/papi/data/DPlugin.py @@ -0,0 +1,405 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +from papi.data import DParameter + +__author__ = 'knuths' + +from papi.data.DObject import DObject +import copy + + +class DBlock(DObject): + + def __init__(self, name): + super(DObject, self).__init__() + + self.subscribers = {} + self.dplugin_id = None + self.name = name + self.signals = [] + + def add_subscribers(self, dplugin): + """ + Add dplugin as subscriber for this dblock. + + :param dplugin: + :return: + :rtype boolean: + """ + if dplugin.id not in self.subscribers: + self.subscribers[dplugin.id] = dplugin.id + return True + else: + return False + + def rm_subscriber(self, dplugin): + """ + Remove dplugin as subscriber of this dblock. + + :param dplugin: + :return: + :rtype boolean: + """ + if dplugin.id in self.subscribers: + del self.subscribers[dplugin.id] + return True + else: + return False + + def add_signal(self, signal): + """ + Add Signal for this DBlock + + :param signal: + :return: + """ + if signal not in self.signals: + self.signals.append(signal) + return True + return False + + def rm_signal(self, signal): + """ + Remove Signal for this DBlock + + :param signal: + :return: + """ + if signal in self.signals: + self.signals.remove(signal) + return True + return False + + def get_subscribers(self): + """ + :return: + :rtype []: + """ + return copy.deepcopy(self.subscribers) + + def get_signals(self): + """ + DEPRECATED + + Returns a copy of the internal signal names + :return: + """ + return copy.deepcopy(self.signals) + + + #NOT NEEDED ANYMORE !!! + def get_signal_name(self, signal: int): + """ + DEPRECATED + + :param signal: + :return: + """ + raise NotImplementedError("Stop Using this function.") + + +class DPlugin(DObject): + + def __init__(self): + super(DPlugin, self).__init__() + self.process = None + self.pid = None + self.queue = None + self.plugin = None + self.plugin_identifier = None + self.startup_config = None + self.__subscriptions = {} + self.state = None + self.alive_state = None + self.paused = False + self.own_process = None + self.uname = None + #self.display_name = None + self.__parameters = {} + self.__blocks = {} + self.type = None + self.alive_count = 0 + + def subscribe_signals(self, dblock, signals): + """ + This function is used to subscribe a bunch of signals + :param dblock: + :param signals: + :return: + :rtype DSubscription: + """ + + if dblock.dplugin_id in self.__subscriptions: + if dblock.name in self.__subscriptions[dblock.dplugin_id]: + subscription = self.__subscriptions[dblock.dplugin_id][dblock.name] + for signal in signals: + subscription.add_signal(signal) + return subscription + else: + return None + else: + return None + + def unsubscribe_signals(self, dblock, signals): + """ + This function is used to unsubscribe a bunch of signals + :param dblock: + :param signals: + :return: + :rtype DSubscription: + """ + + if dblock.dplugin_id in self.__subscriptions: + if dblock.name in self.__subscriptions[dblock.dplugin_id]: + subscription = self.__subscriptions[dblock.dplugin_id][dblock.name] + for signal in signals: + subscription.rm_signal(signal) + + return subscription + else: + return None + else: + return None + + def subscribe(self, dblock): + """ + This plugins subscribes 'dblock' by remembering the dblog id + :param dblock: DBlock which should be subscribed + :return: + :rtype boolean: + """ + + if dblock.dplugin_id not in self.__subscriptions: + self.__subscriptions[dblock.dplugin_id] = {} + self.__subscriptions[dblock.dplugin_id][dblock.name] = DSubscription(dblock) + return self.__subscriptions[dblock.dplugin_id][dblock.name] + else: + if dblock.name not in self.__subscriptions[dblock.dplugin_id]: + self.__subscriptions[dblock.dplugin_id][dblock.name] = DSubscription(dblock) + return self.__subscriptions[dblock.dplugin_id][dblock.name] + else: + return None + return None + + def unsubscribe(self, dblock): + """ + This plugins unsubscribes 'dblock' by forgetting the dblog id + :param dblock: DBlock which should be unsubscribed + :return: + :rtype boolean: + """ + + if dblock.dplugin_id not in self.__subscriptions: + return False + else: + + if dblock.name in self.__subscriptions[dblock.dplugin_id]: + del self.__subscriptions[dblock.dplugin_id][dblock.name] + + if len(self.__subscriptions[dblock.dplugin_id]) is 0: + del self.__subscriptions[dblock.dplugin_id] + return True + else: + return False + return False + + def get_subscribtions(self): + """ + Returns a dictionary of all susbcribtions + :return {}{} of DPlugin ids to DBlock names : + :rtype: {}{} + """ + + return copy.deepcopy(self.__subscriptions.copy()) + + def add_parameter(self, parameter): + """ + + :param parameter: + :return: + :rtype boolean: + """ + if parameter.name not in self.__parameters: + self.__parameters[parameter.name] = parameter + return True + else: + return False + + def rm_parameter(self, parameter): + """ + + :param parameter_id: + :return: + :rtype boolean: + """ + + if parameter.name in self.__parameters: + del self.__parameters[parameter.name] + return True + else: + return False + + def get_parameters(self): + """ + + :return: + :rtype {}: + """ + return self.__parameters + + def add_dblock(self, dblock): + """ + + :param dblock: + :return: + :rtype boolean: + """ + dblock.dplugin_id = self.id + if dblock.name not in self.__blocks: + self.__blocks[dblock.name] = dblock + return True + else: + return False + + def rm_dblock(self, dblock): + """ + + :param dblock: + :return: + :rtype boolean: + """ + if dblock.name in self.__blocks: + del self.__blocks[dblock.name] + return True + else: + return False + + def get_dblocks(self): + """ + + :return: + :rtype {}: + """ + return self.__blocks + + def get_dblock_by_name(self, dblock_name): + """ + + :return: + :rtype DBlock: + """ + + if dblock_name in self.__blocks: + return self.__blocks[dblock_name] + else: + return None + + def get_meta(self): + """ + + :return: + :rtype DPlugin: + """ + + DPlugin_new = DPlugin() + DPlugin_new.id = self.id + DPlugin_new.pid = self.pid + DPlugin_new.state = self.state + DPlugin_new.alive_state = self.alive_state + DPlugin_new.own_process = self.own_process + DPlugin_new.uname = self.uname + DPlugin_new.type = self.type + + DPlugin_new.__parameters = copy.deepcopy(self.__parameters) + DPlugin_new.__subscriptions = copy.deepcopy(self.__subscriptions) + DPlugin_new.__blocks = copy.deepcopy(self.__blocks) + + return DPlugin_new + + def update_meta(self, meta): + """ + + :param meta: of type DPlugin + :return: + """ + + self.id = meta.id + self.pid = meta.pid + self.state = meta.state + self.alive_state = meta.alive_state + self.own_process = meta.own_process + self.uname = meta.uname + self.type = meta.type + + self.__parameters = meta.__parameters + self.__subscriptions = meta.__subscriptions + self.__blocks = meta.__blocks + + +class DSubscription(DObject): + + def __init__(self, dblock): + self.dblock = dblock + self.alias = None + self.signals = [] + + def add_signal(self, signal): + """ + Add Signal for this Subscription + + :param signal: + :return: + """ + if signal not in self.signals: + self.signals.append(signal) + return True + return False + + def rm_signal(self, signal): + """ + Remove Signal for this Subscription + + :param signal: + :return: + """ + if signal in self.signals: + self.signals.remove(signal) + return True + + return False + + def get_signals(self): + return copy.copy(self.signals) + + def attach_signal(self, signal): + raise NotImplementedError("Stop Using this function.") + + def remove_signal(self, signal): + raise NotImplementedError("Stop Using this function.") + diff --git a/papi/data/DSignal.py b/papi/data/DSignal.py new file mode 100644 index 00000000..b8541bca --- /dev/null +++ b/papi/data/DSignal.py @@ -0,0 +1,39 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.data.DObject import DObject + + +class DSignal(DObject): + + def __init__(self, uname): + super(DObject, self).__init__() + self.uname = uname + self.dname = uname diff --git a/papi/data/__init__.py b/papi/data/__init__.py new file mode 100644 index 00000000..bc47e275 --- /dev/null +++ b/papi/data/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'control' diff --git a/papi/error_codes.py b/papi/error_codes.py new file mode 100644 index 00000000..d4fd040e --- /dev/null +++ b/papi/error_codes.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Stefan Ruppin +""" + +__author__ = 'ruppin' + + + +NO_ERROR = 1 + +UNKNOWN_ERROR = -1 + +NOT_EXISTING = -2 diff --git a/papi/event/__init__.py b/papi/event/__init__.py new file mode 100644 index 00000000..c4413f5a --- /dev/null +++ b/papi/event/__init__.py @@ -0,0 +1,10 @@ +__author__ = 'stefan' + +#import papi.event.data as DataEvent + +#import papi.event.data + +from papi.event import data +from papi.event import status +from papi.event import instruction + diff --git a/papi/event/data/DataBase.py b/papi/event/data/DataBase.py new file mode 100644 index 00000000..ec1caa67 --- /dev/null +++ b/papi/event/data/DataBase.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'control' + +from papi.event.event_base import PapiEventBase + +class DataBase(PapiEventBase): + def __init__(self, oID, destID, operation, opt): + super().__init__(oID, destID, 'data_event', operation, opt) \ No newline at end of file diff --git a/papi/event/data/NewBlock.py b/papi/event/data/NewBlock.py new file mode 100644 index 00000000..a9bde8da --- /dev/null +++ b/papi/event/data/NewBlock.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'Stefan' + +from papi.event.data.DataBase import DataBase + + +class NewBlock(DataBase): + def __init__(self, oID, destID, opt): + super().__init__(oID,destID, 'new_block', opt) diff --git a/papi/event/data/NewData.py b/papi/event/data/NewData.py new file mode 100644 index 00000000..f4da4252 --- /dev/null +++ b/papi/event/data/NewData.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'control' + +from papi.event.data.DataBase import DataBase + + +class NewData(DataBase): + def __init__(self, oID, destID, opt): + super().__init__(oID,destID, 'new_data', opt) diff --git a/papi/event/data/NewParameter.py b/papi/event/data/NewParameter.py new file mode 100644 index 00000000..3ddcc8f1 --- /dev/null +++ b/papi/event/data/NewParameter.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.data.DataBase import DataBase + + +class NewParameter(DataBase): + def __init__(self, oID, destID, opt): + super().__init__(oID,destID, 'new_parameter', opt) diff --git a/papi/event/data/__init__.py b/papi/event/data/__init__.py new file mode 100644 index 00000000..0c1c917a --- /dev/null +++ b/papi/event/data/__init__.py @@ -0,0 +1,6 @@ +__author__ = 'stefan' + + +from .NewData import NewData +from .NewBlock import NewBlock +from .NewParameter import NewParameter \ No newline at end of file diff --git a/papi/event/event_base.py b/papi/event/event_base.py new file mode 100644 index 00000000..a6b2937e --- /dev/null +++ b/papi/event/event_base.py @@ -0,0 +1,69 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'control' + +class PapiEventBase(object): + count = 0 + + def __init__(self,orID,destID,type_,op,optParameter): + """ + Function used to create a new Event ready to send. + + :param orID: plugin id of sender + :type orID: int + :param destID: plugin id of destination + :type destID: int + :param type_: event type, see list + :type type_: string + :param optParameter: optinalParameterObject for information + :type: optParameter: DOptionlaData + """ + + self.__originID__ = orID + self.__destID__ = destID + self.__eventtype__ = type_ + self.__operation__ = op + self.__optional_parameter__ = optParameter + PapiEventBase.count += 1 + + def get_originID(self): + return self.__originID__ + + def get_destinatioID(self): + return self.__destID__ + + def get_eventtype(self): + return self.__eventtype__ + + def get_event_operation(self): + return self.__operation__ + + def get_optional_parameter(self): + return self.__optional_parameter__ + diff --git a/papi/event/instruction/CloseProgram.py b/papi/event/instruction/CloseProgram.py new file mode 100644 index 00000000..3045d2e4 --- /dev/null +++ b/papi/event/instruction/CloseProgram.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class CloseProgram(InstructionBase): + def __init__(self, oID, destID, opt): + super().__init__(oID, destID, 'close_program', opt) diff --git a/papi/event/instruction/CreatePlugin.py b/papi/event/instruction/CreatePlugin.py new file mode 100644 index 00000000..5421f4d4 --- /dev/null +++ b/papi/event/instruction/CreatePlugin.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class CreatePlugin(InstructionBase): + def __init__(self, oID, destID, opt): + super().__init__(oID, destID, 'create_plugin', opt) diff --git a/papi/event/instruction/InstructionBase.py b/papi/event/instruction/InstructionBase.py new file mode 100644 index 00000000..137b6e95 --- /dev/null +++ b/papi/event/instruction/InstructionBase.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.event_base import PapiEventBase + +class InstructionBase(PapiEventBase): + def __init__(self, oID, destID, operation, opt): + super().__init__(oID, destID, 'instr_event', operation, opt) \ No newline at end of file diff --git a/papi/event/instruction/PausePlugin.py b/papi/event/instruction/PausePlugin.py new file mode 100644 index 00000000..11f17e4d --- /dev/null +++ b/papi/event/instruction/PausePlugin.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class PausePlugin(InstructionBase): + def __init__(self, oID, destID, opt): + super().__init__(oID, destID, 'pause_plugin', opt) diff --git a/papi/event/instruction/ResumePlugin.py b/papi/event/instruction/ResumePlugin.py new file mode 100644 index 00000000..9f96534a --- /dev/null +++ b/papi/event/instruction/ResumePlugin.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class ResumePlugin(InstructionBase): + def __init__(self, oID, destID, opt): + super().__init__(oID, destID, 'resume_plugin', opt) diff --git a/papi/event/instruction/SetParameter.py b/papi/event/instruction/SetParameter.py new file mode 100644 index 00000000..e025069e --- /dev/null +++ b/papi/event/instruction/SetParameter.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class SetParameter(InstructionBase): + def __init__(self, oID, destID, opt): + super().__init__(oID, destID, 'set_parameter', opt) diff --git a/papi/event/instruction/StopPlugin.py b/papi/event/instruction/StopPlugin.py new file mode 100644 index 00000000..b4b22b31 --- /dev/null +++ b/papi/event/instruction/StopPlugin.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class StopPlugin(InstructionBase): + def __init__(self, oID, destID, opt, delete = True): + self.delete = delete + super().__init__(oID, destID, 'stop_plugin', opt) diff --git a/papi/event/instruction/Subscribe.py b/papi/event/instruction/Subscribe.py new file mode 100644 index 00000000..94860c1e --- /dev/null +++ b/papi/event/instruction/Subscribe.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class Subscribe(InstructionBase): + def __init__(self, oID, destID, opt): + super().__init__(oID, destID, 'subscribe', opt) diff --git a/papi/event/instruction/Unsubscribe.py b/papi/event/instruction/Unsubscribe.py new file mode 100644 index 00000000..ac326bf5 --- /dev/null +++ b/papi/event/instruction/Unsubscribe.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class Unsubscribe(InstructionBase): + def __init__(self, oID, destID, opt): + super().__init__(oID, destID, 'unsubscribe', opt) diff --git a/papi/event/instruction/__init__.py b/papi/event/instruction/__init__.py new file mode 100644 index 00000000..9fe43b23 --- /dev/null +++ b/papi/event/instruction/__init__.py @@ -0,0 +1,12 @@ +__author__ = 'stefan' + + +from .CreatePlugin import CreatePlugin +from .StopPlugin import StopPlugin +from .PausePlugin import PausePlugin +from .ResumePlugin import ResumePlugin +from .startPlugin import StartPlugin +from .Subscribe import Subscribe +from .Unsubscribe import Unsubscribe +from .SetParameter import SetParameter +from .CloseProgram import CloseProgram \ No newline at end of file diff --git a/papi/event/instruction/startPlugin.py b/papi/event/instruction/startPlugin.py new file mode 100644 index 00000000..3a87c80e --- /dev/null +++ b/papi/event/instruction/startPlugin.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class StartPlugin(InstructionBase): + def __init__(self, oID, destID, opt): + super().__init__(oID, destID, 'start_plugin', opt) diff --git a/papi/event/status/Alive.py b/papi/event/status/Alive.py new file mode 100644 index 00000000..4eb4e682 --- /dev/null +++ b/papi/event/status/Alive.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.status.StatusBase import StatusBase + +class Alive(StatusBase): + def __init__(self, oID, destID, opt): + super().__init__(oID,destID, 'alive', opt) diff --git a/papi/event/status/CheckAliveStatus.py b/papi/event/status/CheckAliveStatus.py new file mode 100644 index 00000000..1370a79f --- /dev/null +++ b/papi/event/status/CheckAliveStatus.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.status.StatusBase import StatusBase + +class CheckAliveStatus(StatusBase): + def __init__(self, oID, destID, opt): + super().__init__(oID,destID, 'check_alive_status', opt) diff --git a/papi/event/status/JoinRequest.py b/papi/event/status/JoinRequest.py new file mode 100644 index 00000000..0ebbef90 --- /dev/null +++ b/papi/event/status/JoinRequest.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'control' + +from papi.event.status.StatusBase import StatusBase + +class JoinRequest(StatusBase): + def __init__(self, oID, destID, opt): + super().__init__(oID,destID, 'join_request', opt) diff --git a/papi/event/status/PluginClosed.py b/papi/event/status/PluginClosed.py new file mode 100644 index 00000000..2985f899 --- /dev/null +++ b/papi/event/status/PluginClosed.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.status.StatusBase import StatusBase + +class PluginClosed(StatusBase): + def __init__(self, oID, destID, opt): + super().__init__(oID, destID, 'plugin_closed', opt) diff --git a/papi/event/status/PluginStopped.py b/papi/event/status/PluginStopped.py new file mode 100644 index 00000000..de8aebe7 --- /dev/null +++ b/papi/event/status/PluginStopped.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'control' + +from papi.event.status.StatusBase import StatusBase + +class PluginStopped(StatusBase): + def __init__(self, oID, destID, opt): + super().__init__(oID,destID, 'plugin_stopped', opt) diff --git a/papi/event/status/StartFailed.py b/papi/event/status/StartFailed.py new file mode 100644 index 00000000..1cbd3203 --- /dev/null +++ b/papi/event/status/StartFailed.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'control' + +from papi.event.status.StatusBase import StatusBase + +class StartFailed(StatusBase): + def __init__(self, oID, destID, opt): + super().__init__(oID,destID, 'start_failed', opt) diff --git a/papi/event/status/StartSuccessfull.py b/papi/event/status/StartSuccessfull.py new file mode 100644 index 00000000..7519baa2 --- /dev/null +++ b/papi/event/status/StartSuccessfull.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'control' + +from papi.event.status.StatusBase import StatusBase + +class StartSuccessfull(StatusBase): + def __init__(self, oID, destID, opt): + super().__init__(oID,destID, 'start_successfull', opt) diff --git a/papi/event/status/StatusBase.py b/papi/event/status/StatusBase.py new file mode 100644 index 00000000..88b15da7 --- /dev/null +++ b/papi/event/status/StatusBase.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'control' + +from papi.event.event_base import PapiEventBase + +class StatusBase(PapiEventBase): + def __init__(self, oID, destID, operation, opt): + super().__init__(oID, destID, 'status_event', operation, opt) \ No newline at end of file diff --git a/papi/event/status/__init__.py b/papi/event/status/__init__.py new file mode 100644 index 00000000..a9bb429f --- /dev/null +++ b/papi/event/status/__init__.py @@ -0,0 +1,10 @@ +__author__ = 'stefan' + + +from .StartSuccessfull import StartSuccessfull +from .StartFailed import StartFailed +from .JoinRequest import JoinRequest +from .Alive import Alive +from .CheckAliveStatus import CheckAliveStatus +from .PluginClosed import PluginClosed +from .PluginStopped import PluginStopped \ No newline at end of file diff --git a/papi/exceptions/__init__.py b/papi/exceptions/__init__.py new file mode 100644 index 00000000..c34f78e3 --- /dev/null +++ b/papi/exceptions/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +. + +Contributors: +. + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py new file mode 100644 index 00000000..012b494b --- /dev/null +++ b/papi/gui/gui_api.py @@ -0,0 +1,660 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Stefan Ruppin +""" +__author__ = 'stefan' + + +import papi.event as Event + +from papi.data.DOptionalData import DOptionalData +from papi.ConsoleLog import ConsoleLog +from papi.constants import GUI_PROCESS_CONSOLE_IDENTIFIER, GUI_PROCESS_CONSOLE_LOG_LEVEL, CONFIG_LOADER_SUBCRIBE_DELAY, \ + CONFIG_ROOT_ELEMENT_NAME, CORE_PAPI_VERSION, PLUGIN_PCP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER + +from pyqtgraph import QtCore + + +import papi.error_codes as ERROR + +import datetime +import time + +import xml.etree.cElementTree as ET + +class Gui_api(QtCore.QObject): + + resize_gui = QtCore.Signal(int, int) + set_bg_gui = QtCore.Signal(str) + + + def __init__(self, gui_data, core_queue, gui_id, LOG_IDENT = GUI_PROCESS_CONSOLE_IDENTIFIER): + super(Gui_api, self).__init__() + self.gui_id = gui_id + self.gui_data = gui_data + self.core_queue = core_queue + self.log = ConsoleLog(GUI_PROCESS_CONSOLE_LOG_LEVEL, LOG_IDENT) + self.gui_size_width = None + self.gui_size_height = None + self.gui_bg_path = None + + + + def do_create_plugin(self, plugin_identifier, uname, config={}, autostart = True): + """ + Something like a callback function for gui triggered events e.a. when a user wants to create a new plugin. + + :param plugin_identifier: plugin to create + :type plugin_identifier: basestring + :param uname: uniqe name to set for new plugin + :type uname: basestring + :param config: additional configuration for creation + :type config: + :return: + """ + # create new optional Data for event + opt = DOptionalData() + # set important information + # plugin to create + opt.plugin_identifier = plugin_identifier + # uname to create plugin with + opt.plugin_uname = uname + # additional config + opt.plugin_config = config + opt.autostart = autostart + + # create event object and sent it to core + event = Event.instruction.CreatePlugin(self.gui_id, 0, opt) + self.core_queue.put(event) + + def do_delete_plugin(self, id): + """ + Delete plugin with given id. + + :param id: Plugin id to delete + :type id: int + :return: + """ + event = Event.instruction.StopPlugin(self.gui_id, id, None ) + + self.core_queue.put(event) + + def do_delete_plugin_uname(self, uname): + """ + Delete plugin with given uname. + + :param uname: Plugin uname to delete + :type uname: basestring + :return: + """ + pl_id = self.do_get_plugin_id_from_uname(uname) + + if pl_id is not None: + self.do_delete_plugin(pl_id) + else: + self.log.printText(1, " Do delete plugin with uname " + uname + ' failed') + + def do_stopReset_pluign(self, id): + """ + Stop and reset plugin with given id without deleting it. + + :param id: Plugin id to stopReset + :type id: int + :return: + """ + + event = Event.instruction.StopPlugin(self.gui_id, id, None, delete=False) + self.core_queue.put(event) + + def do_stopReset_plugin_uname(self, uname): + """ + Stop and reset plugin with given uname without deleting it. + + :param uname: Plugin uname to stop + :type uname: basestring + :return: + """ + pl_id = self.do_get_plugin_id_from_uname(uname) + + if pl_id is not None: + self.do_stopReset_pluign(pl_id) + else: + self.log.printText(1, " Do stopReset plugin with uname " + uname + ' failed') + return ERROR.NOT_EXISTING + + def do_start_plugin(self, id): + """ + Start plugin with given id. + + :param id: Plugin id to start + :type id: int + :return: + """ + event = Event.instruction.StartPlugin(self.gui_id, id, None) + self.core_queue.put(event) + + def do_start_plugin_uname(self, uname): + """ + Start plugin with given uname. + + :param uname: Plugin uname to start + :type uname: basestring + :return: + """ + pl_id = self.do_get_plugin_id_from_uname(uname) + + if pl_id is not None: + self.do_start_plugin(pl_id) + else: + self.log.printText(1, " Do start_plugin with uname " + uname + ' failed') + return ERROR.NOT_EXISTING + + def do_subscribe(self, subscriber_id, source_id, block_name, signals=None, sub_alias = None): + """ + Something like a callback function for gui triggered events. + In this case, user wants one plugin to subscribe another + + :param subscriber_id: Plugin id of plugin which should get the data + :type subscriber_id: int + :param source_id: plugin uname of plugin that should send the data + :type source_id: int + :param block_name: name of block to subscribe + :type block_name: basestring + :return: + """ + # build optional data object and add id and block name to it + opt = DOptionalData() + opt.source_ID = source_id + opt.block_name = block_name + opt.signals = signals + opt.subscription_alias = sub_alias + # send event with subscriber id as the origin to CORE + event = Event.instruction.Subscribe(subscriber_id, 0, opt) + self.core_queue.put(event) + + def do_subscribe_uname(self,subscriber_uname,source_uname,block_name, signals=None, sub_alias = None): + """ + Something like a callback function for gui triggered events. + In this case, user wants one plugin to subscribe another + + :param subscriber_uname: Plugin uname of plugin which should get the data + :type subscriber_uname: basestring + :param source_uname: plugin uname of plugin that should send the data + :type source_uname: basestring + :param block_name: name of block to subscribe + :type block_name: basestring + :return: + """ + subscriber_id = self.do_get_plugin_id_from_uname(subscriber_uname) + if subscriber_id is None: + # plugin with uname does not exist + self.log.printText(1, 'do_subscribe, sub uname worng') + return -1 + + source_id = self.do_get_plugin_id_from_uname(source_uname) + if source_id is None: + # plugin with uname does not exist + self.log.printText(1, 'do_subscribe, target uname wrong') + return -1 + + # call do_subscribe with ids to subscribe + self.do_subscribe(subscriber_id, source_id, block_name, signals, sub_alias) + + def do_unsubscribe(self, subscriber_id, source_id, block_name, signal_index=None): + """ + Something like a callback function for gui triggered events. + User wants one plugin to do not get any more data from another plugin + + :param subscriber_id: plugin id which wants to lose a data source + :type subscriber_id: int + :param source_id: plugin id of data source + :type source_id: int + :param block_name: name of block to unsubscribe + :type block_name: basestring + :return: + """ + # create optional data with source id and block_name + opt = DOptionalData() + opt.source_ID = source_id + opt.block_name = block_name + opt.signals = signal_index + # sent event to Core with origin subscriber_id + event = Event.instruction.Unsubscribe(subscriber_id, 0, opt) + self.core_queue.put(event) + + def do_unsubscribe_uname(self, subscriber_uname, source_uname, block_name, signal_index=None): + """ + Something like a callback function for gui triggered events. + User wants one plugin to do not get any more data from another plugin + + :param subscriber_uname: plugin uname which wants to lose a data source + :type subscriber_uname: basestring + :param source_uname: plugin uname of data source + :type source_uname: basestring + :param block_name: name of block to unsubscribe + :type block_name: basestring + :return: + """ + subscriber_id = self.do_get_plugin_id_from_uname(subscriber_uname) + if subscriber_id is None: + # plugin with uname does not exist + self.log.printText(1, 'do_unsubscribe, sub uname worng') + return -1 + + source_id = self.do_get_plugin_id_from_uname(source_uname) + if source_id is None: + # plugin with uname does not exist + self.log.printText(1, 'do_unsubscribe, target uname wrong') + return -1 + + # call do_subscribe with ids to subscribe + self.do_unsubscribe(subscriber_id, source_id, block_name, signal_index) + + def do_set_parameter(self, plugin_id, parameter_name, value): + """ + Something like a callback function for gui triggered events. + User wants to change a parameter of a plugin + :param plugin_id: id of plugin which owns the parameter + + :type plugin_id: int + :param parameter_name: name of parameter to change + :type parameter_name: basestring + :param value: new parameter value to set + :type value: + """ + # get plugin from DGUI + dplug = self.gui_data.get_dplugin_by_id(plugin_id) + # check for existance + if dplug is not None: + # it exists + # get its parameter list + parameters = dplug.get_parameters() + # check if there are any parameter + if parameters is not None: + # there is a parameter list + # get the parameter with parameter_name + if parameter_name in parameters: + p = parameters[parameter_name] + # check if this specific parameter exists + if p is not None: + # parameter with name parameter_name exists + + # build an event to send this information to Core + opt = DOptionalData() + opt.data = value + opt.is_parameter = True + opt.parameter_alias = parameter_name + opt.block_name = None + e = Event.instruction.SetParameter(self.gui_id, dplug.id, opt) + self.core_queue.put(e) + + def do_set_parameter_uname(self, plugin_uname, parameter_name, value): + """ + Something like a callback function for gui triggered events. + User wants to change a parameter of a plugin + :param plugin_uname: name of plugin which owns the parameter + + :type plugin_uname: basestring + :param parameter_name: name of parameter to change + :type parameter_name: basestring + :param value: new parameter value to set + :type value: + """ + id = self.do_get_plugin_id_from_uname(plugin_uname) + if id is not None: + self.do_set_parameter(id, parameter_name, value) + + def do_pause_plugin_by_id(self, plugin_id): + """ + Something like a callback function for gui triggered events. + User wants to pause a plugin, so this method will send an event to core. + + :param plugin_id: id of plugin to pause + :type plugin_id: int + """ + + if self.gui_data.get_dplugin_by_id(plugin_id) is not None: + opt = DOptionalData() + event = Event.instruction.PausePlugin(self.gui_id, plugin_id, opt) + self.core_queue.put(event) + return 1 + else: + return -1 + + def do_pause_plugin_by_uname(self, plugin_uname): + """ + Something like a callback function for gui triggered events. + User wants to pause a plugin, so this method will send an event to core. + + :param plugin_uname: uname of plugin to pause + :type plugin_uname: basestring + """ + # # get plugin from DGui with given uname + # # purpose: get its id + # dplug = self.gui_data.get_dplugin_by_uname(plugin_uname) + # # check for existance + # if dplug is not None: + # # it does exist, so get its id + # self.do_pause_plugin_by_id(dplug.id) + # else: + # # plugin with uname does not exist + # self.log.printText(1, 'do_pause, plugin uname worng') + # return -1 + + plugin_id = self.do_get_plugin_id_from_uname(plugin_uname) + if plugin_id is not None: + return self.do_pause_plugin_by_id(plugin_id) + else: + # plugin with uname does not exist + self.log.printText(1, 'do_pause, plugin uname worng') + return -1 + + def do_resume_plugin_by_id(self, plugin_id): + """ + Something like a callback function for gui triggered events. + User wants to pause a plugin, so this method will send an event to core. + + :param plugin_id: id of plugin to pause + :type plugin_id: int + """ + if self.gui_data.get_dplugin_by_id(plugin_id) is not None: + opt = DOptionalData() + event = Event.instruction.ResumePlugin(self.gui_id, plugin_id, opt) + self.core_queue.put(event) + return 1 + else: + return -1 + + def do_resume_plugin_by_uname(self, plugin_uname): + """ + Something like a callback function for gui triggered events. + User wants to resume a plugin, so this method will send an event to core. + + :param plugin_uname: uname of plugin to resume + :type plugin_uname: basestring + """ + # # get plugin from DGui with given uname + # # purpose: get its id + # dplug = self.gui_data.get_dplugin_by_uname(plugin_uname) + # # check for existance + # if dplug is not None: + # # it does exist, so get its id + # self.do_resume_plugin_by_id(dplug.id) + # else: + # # plugin with uname does not exist + # self.log.printText(1, 'do_resume, plugin uname worng') + # return -1 + + plugin_id = self.do_get_plugin_id_from_uname(plugin_uname) + if plugin_id is not None: + return self.do_resume_plugin_by_id(plugin_id) + else: + # plugin with uname does not exist + self.log.printText(1, 'do_resume, plugin uname worng') + return -1 + + def do_get_plugin_id_from_uname(self, uname): + """ + Returns the plugin id of the plugin with unique name uname + + :param uname: uname of plugin + :type uname: basestring + :return: None: plugin with uname does not exist, id: id of plugin + """ + dplugin = self.gui_data.get_dplugin_by_uname(uname) + # check for existance + if dplugin is not None: + # it does exist, so get its id + return dplugin.id + else: + return None + + def do_close_program(self): + opt = DOptionalData() + opt.reason = 'User clicked close Button' + event = Event.instruction.CloseProgram(self.gui_id, 0, opt) + self.core_queue.put(event) + + def do_load_xml(self, path): + tree = ET.parse(path) + + root = tree.getroot() + + + + plugins_to_start = [] + subs_to_make = [] + parameters_to_change = [] + + for plugin_xml in root: + if plugin_xml.tag == 'Size': + w = int(plugin_xml.attrib['w']) + h = int(plugin_xml.attrib['h']) + self.resize_gui.emit(w,h) + else: + if plugin_xml.tag == 'Background': + path = str(plugin_xml.attrib['image']) + if path != '' and path is not None and path !='default': + self.set_bg_gui.emit(path) + else: + pl_uname = plugin_xml.attrib['uname'] + identifier = plugin_xml.find('Identifier').text + config_xml = plugin_xml.find('StartConfig') + config_hash = {} + for parameter_xml in config_xml.findall('Parameter'): + para_name = parameter_xml.attrib['Name'] + config_hash[para_name] = {} + for detail_xml in parameter_xml: + detail_name = detail_xml.tag + config_hash[para_name][detail_name]= detail_xml.text + + pl_uname_new = self.change_uname_to_uniqe(pl_uname) + + plugins_to_start.append([identifier, pl_uname_new, config_hash]) + + subs_xml = plugin_xml.find('Subscriptions') + for sub_xml in subs_xml.findall('Subscription'): + data_source = sub_xml.find('data_source').text + for block_xml in sub_xml.findall('block'): + block_name = block_xml.attrib['Name'] + signals = [] + for sig_xml in block_xml.findall('Signal'): + signals.append(str(sig_xml.text)) + alias_xml = block_xml.find('alias') + alias = alias_xml.text + pl_uname_new = self.change_uname_to_uniqe(pl_uname) + data_source_new = self.change_uname_to_uniqe(data_source) + subs_to_make.append([pl_uname_new,data_source_new,block_name,signals, alias]) + + + prev_parameters_xml = plugin_xml.find('PreviousParameters') + for prev_parameter_xml in prev_parameters_xml.findall('Parameter'): + para_name = prev_parameter_xml.attrib['Name'] + para_value = prev_parameter_xml.text + pl_uname_new = self.change_uname_to_uniqe(pl_uname) + # TODO validate NO FLOAT in parameter + parameters_to_change.append([pl_uname_new, para_name, para_value]) + + for pl in plugins_to_start: + # 0: ident, 1: uname, 2: config + self.do_create_plugin(pl[0], pl[1], pl[2]) + + QtCore.QTimer.singleShot(CONFIG_LOADER_SUBCRIBE_DELAY,\ + lambda: self.config_loader_subs(plugins_to_start, subs_to_make, parameters_to_change) ) + + def change_uname_to_uniqe(self, uname): + """ + Function will search for unames and add an indentifier to it to make it unique in case of existence + + :param uname: uname to make unique + :type uname: basestring + :return: uname + """ + i=1 + while self.gui_data.get_dplugin_by_uname(uname) is not None: + i = i+1 + if i == 2: + uname = uname + 'X' +str(i) + else: + uname = uname[:-1] + str(i) + return uname + + def config_loader_subs(self, pl_to_start, subs_to_make, parameters_to_change ): + for sub in subs_to_make: + self.do_subscribe_uname(sub[0], sub[1], sub[2], sub[3], sub[4]) + + for para in parameters_to_change: + self.do_set_parameter(para[0], para[1], para[2]) + + def do_save_xml_config(self, path): + root = ET.Element(CONFIG_ROOT_ELEMENT_NAME) + root.set('Date', datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')) + root.set('PaPI_version',CORE_PAPI_VERSION) + + size_xml = ET.SubElement(root, 'Size') + size_xml.set('w',str(self.gui_size_width)) + size_xml.set('h',str(self.gui_size_height)) + + bg_xml = ET.SubElement(root, 'Background') + bg_xml.set('image',str(self.gui_bg_path)) + + # get plugins # + plugins = self.gui_data.get_all_plugins() + for dplugin_id in plugins: + dplugin = plugins[dplugin_id] + + if dplugin.type == PLUGIN_PCP_IDENTIFIER or dplugin.type == PLUGIN_VIP_IDENTIFIER: + dplugin.startup_config = dplugin.plugin.get_current_config() + + pl_xml = ET.SubElement(root,'Plugin') + pl_xml.set('uname',dplugin.uname) + + identifier_xml =ET.SubElement(pl_xml,'Identifier') + identifier_xml.text = dplugin.plugin_identifier + + cfg_xml = ET.SubElement(pl_xml,'StartConfig') + for parameter in dplugin.startup_config: + para_xml = ET.SubElement(cfg_xml, 'Parameter') + para_xml.set('Name',parameter) + for detail in dplugin.startup_config[parameter]: + detail_xml = ET.SubElement(para_xml, detail) + detail_xml.text = dplugin.startup_config[parameter][detail] + + last_paras_xml = ET.SubElement(pl_xml, 'PreviousParameters') + allparas = dplugin.get_parameters() + for para_key in allparas: + para = allparas[para_key] + last_para_xml = ET.SubElement(last_paras_xml,'Parameter') + last_para_xml.set('Name',para_key) + last_para_xml.text = str(para.value) + + subs_xml = ET.SubElement(pl_xml, 'Subscriptions') + subs = dplugin.get_subscribtions() + for sub in subs: + sub_xml = ET.SubElement(subs_xml, 'Subscription') + source_xml = ET.SubElement(sub_xml, 'data_source') + source_xml.text = self.gui_data.get_dplugin_by_id(sub).uname + for block in subs[sub]: + block_xml = ET.SubElement(sub_xml, 'block') + block_xml.set('Name',block) + + alias_xml = ET.SubElement(block_xml,'alias') + alias_xml.text = subs[sub][block].alias + + for s in subs[sub][block].get_signals(): + signal_xml = ET.SubElement(block_xml,'Signal') + signal_xml.text = str(s) + + self.indent(root) + tree = ET.ElementTree(root) + tree.write(path) + + def indent(self,elem, level=0): + # copied from http://effbot.org/zone/element-lib.htm#prettyprint 06.10.2014 15:53 + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + self.indent(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + def do_reset_papi(self): + """ + APi call to reset PaPI. + Reset in this case means to delete all plugins cleanly and keep PaPI running. + Will free all unames. + Is using the do_delete_plugin api call and the delete plugin mechanism + + :return: ERROR CODE + """ + + all_plugins = self.gui_data.get_all_plugins() + if all_plugins is not None: + for plugin_key in all_plugins: + plugin = all_plugins[plugin_key] + self.do_delete_plugin(plugin.id) + + def do_test_name_to_be_unique(self, name): + """ + Will check if a given name would be a valid, unique name for a plugin. + :param name: name to check + + :type name: basestring + :return: True or False + """ + reg =QtCore.QRegExp('\S[^_][^\W_]+') + if reg.exactMatch(name): + if self.gui_data.get_dplugin_by_uname(name) is None: + return True + else: + return False + else: + return False + + + def do_change_string_to_be_uname(self, name): + """ + This method will take a string and convert him according to some rules to be an uname + + :param name: name to convert to unmae + :type name: basestring + :return: name converted to uname + """ + uname = name + + #TODO: get more inteligence here! + + forbidden = ['_',',','.','`',' '] + for c in forbidden: + uname = uname.replace(c,'X') + return uname \ No newline at end of file diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py new file mode 100644 index 00000000..21859012 --- /dev/null +++ b/papi/gui/gui_event_processing.py @@ -0,0 +1,419 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +. + +Contributors +Stefan Ruppin +""" +__author__ = 'stefan' + + +import papi.event as Event + +from papi.data.DOptionalData import DOptionalData +from papi.ConsoleLog import ConsoleLog +from papi.constants import PLUGIN_API_CONSOLE_IDENTIFIER, PLUGIN_API_CONSOLE_LOG_LEVEL, CONFIG_LOADER_SUBCRIBE_DELAY, \ + CONFIG_ROOT_ELEMENT_NAME, CORE_PAPI_VERSION, PLUGIN_PCP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER + +from pyqtgraph import QtCore +from papi.gui.gui_api import Gui_api + +import papi.error_codes as ERROR + +import datetime +import time + +import xml.etree.cElementTree as ET + +class Plugin_api(QtCore.QObject): + + resize_gui = QtCore.Signal(int, int) + + def __init__(self, gui_data, core_queue, gui_id, PLUGIN_API_IDENTIFIER): + super(Plugin_api, self).__init__() + self.__default_api = Gui_api(gui_data, core_queue, gui_id, PLUGIN_API_IDENTIFIER) + + def do_create_plugin(self, plugin_identifier, uname, config={}, autostart = True): + """ + Something like a callback function for gui triggered events e.a. when a user wants to create a new plugin. + + :param plugin_identifier: plugin to create + :type plugin_identifier: basestring + :param uname: uniqe name to set for new plugin + :type uname: basestring + :param config: additional configuration for creation + :type config: + :return: + """ + self.__default_api.do_create_plugin(plugin_identifier,uname,config,autostart) + + def do_subscribe_uname(self,subscriber_uname,source_uname,block_name, signals=None, sub_alias = None): + """ + Something like a callback function for gui triggered events. + In this case, user wants one plugin to subscribe another + + :param subscriber_uname: Plugin uname of plugin which should get the data + :type subscriber_uname: basestring + :param source_uname: plugin uname of plugin that should send the data + :type source_uname: basestring + :param block_name: name of block to subscribe + :type block_name: basestring + :return: + """ + self.__default_api.do_subscribe_uname(subscriber_uname, source_uname, block_name, signals, sub_alias) + + + def do_set_parameter_uname(self, plugin_uname, parameter_name, value): + """ + Something like a callback function for gui triggered events. + User wants to change a parameter of a plugin + :param plugin_uname: name of plugin which owns the parameter + + :type plugin_uname: basestring + :param parameter_name: name of parameter to change + :type parameter_name: basestring + :param value: new parameter value to set + :type value: + """ + self.__default_api.do_set_parameter_uname(plugin_uname, parameter_name, value) + + def do_load_xml(self, path): + self.__default_api.do_load_xml(path) + + + def do_delete_plugin_uname(self, uname): + """ + Delete plugin with given uname. + + :param uname: Plugin uname to delete + :type uname: basestring + :return: + """ + self.__default_api.do_delete_plugin_uname(uname) \ No newline at end of file diff --git a/papi/gui/qt_dev/__init__.py b/papi/gui/qt_dev/__init__.py new file mode 100644 index 00000000..7c97c23d --- /dev/null +++ b/papi/gui/qt_dev/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' diff --git a/papi/gui/qt_dev/add_pcp_subscriber.py b/papi/gui/qt_dev/add_pcp_subscriber.py new file mode 100644 index 00000000..27216765 --- /dev/null +++ b/papi/gui/qt_dev/add_pcp_subscriber.py @@ -0,0 +1,180 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.ui.gui.qt_dev.add_subscriber import Ui_AddSubscriber +from PySide.QtGui import QDialog, QAbstractButton, QDialogButtonBox +from PySide.QtGui import QTreeWidgetItem +from papi.constants import PLUGIN_PCP_IDENTIFIER + +class AddPCPSubscriber(QDialog, Ui_AddSubscriber): + + def __init__(self, callback_functions, parent=None): + super(AddPCPSubscriber, self).__init__(parent) + self.setupUi(self) + self.dgui = None + + self.treeSubscriber.currentItemChanged.connect(self.subscriberItemChanged) + self.treeTarget.currentItemChanged.connect(self.targetItemChanged) + self.treeBlock.currentItemChanged.connect(self.blockItemChanged) + + self.treeSubscriber.setHeaderLabel('PCP') + self.treeTarget.setHeaderLabel('PCP_BLOCK') + self.treeBlock.setHeaderLabel('Plugin') + self.treeSignal.setHeaderLabel('Parameter') + + self.buttonBox.clicked.connect(self.button_clicked) + + self.subscriberID = None + self.targetID = None + self.blockName = None + self.signalName = None + self.signalIndex = [] + self.setWindowTitle("Create Subscribtion") + + self.callback_functions = callback_functions + + def setDGui(self, dgui): + self.dgui = dgui + + def getDGui(self): + """ + + :return: + :rtype DGui: + """ + return self.dgui + + def subscriberItemChanged(self, item): + self.treeTarget.clear() + if hasattr(item, 'dplugin'): + + dplugin = item.dplugin + dblock_ids = dplugin.get_dblocks() + + for dblock_id in dblock_ids: + dblock = dblock_ids[dblock_id] + block_item = QTreeWidgetItem(self.treeTarget) + block_item.dblock = dblock + block_item.setText(0, dblock.name) + block_item.dplugin = dplugin + + def targetItemChanged(self, item): + + self.treeBlock.clear() + + if hasattr(item, 'dplugin'): + + subscriber = self.treeSubscriber.currentItem().dplugin + + dplugin_ids = self.dgui.get_all_plugins() + + for dplugin_id in dplugin_ids: + dplugin = dplugin_ids[dplugin_id] + + # ------------------------------ + # Sort DPluginItem in TreeWidget of Subscriber and Target + # ------------------------------ + + if subscriber.id is not dplugin_id and len(dplugin.get_parameters()) is not 0 and dplugin.type != PLUGIN_PCP_IDENTIFIER: + + target_item = QTreeWidgetItem(self.treeBlock) + + target_item.dplugin = dplugin + target_item.setText(0, str(dplugin.uname) ) + + + def blockItemChanged(self, item): + self.treeSignal.clear() + + if hasattr(item, 'dplugin'): + dplugin = item.dplugin + dparameters = dplugin.get_parameters() + + for dparameter_key in dparameters: + dparameter = dparameters[dparameter_key] + parameter_item = QTreeWidgetItem(self.treeSignal) + parameter_item.dparameter = dparameter + parameter_item.setText(0, dparameter.name) + + + def showEvent(self, *args, **kwargs): + + self.treeSubscriber.clear() + self.treeTarget.clear() + self.treeBlock.clear() + + dplugin_ids = self.dgui.get_all_plugins() + + for dplugin_id in dplugin_ids: + dplugin = dplugin_ids[dplugin_id] + + # ------------------------------ + # Sort DPluginItem in TreeWidget of Subscriber and Target + # ------------------------------ + + if dplugin.type == PLUGIN_PCP_IDENTIFIER: + + subscriber_item = QTreeWidgetItem(self.treeSubscriber) + + subscriber_item.dplugin = dplugin + subscriber_item.setText(0, str(dplugin.uname) ) + + # # ------------------------------- + # # Set amount of blocks and parameters as meta information + # # ------------------------------- + # dblock_ids = dplugin.get_dblocks() + # + # plugin_item.setText(self.get_column_by_name("#BLOCKS"), str(len(dblock_ids.keys()))) + + def button_clicked(self, button): + + if self.buttonBox.buttonRole(button) == QDialogButtonBox.ApplyRole: + + subscriber_item = self.treeSubscriber.currentItem() + target_item = self.treeTarget.currentItem() + block_item = self.treeBlock.currentItem() + signal_item = self.treeSignal.currentItem() + + self.pcpID = subscriber_item.dplugin.id + self.pcpBlock = target_item.dblock + + self.pluginID = block_item.dplugin.id + + + self.parameter = signal_item.dparameter + + + button.setFocus() + print(self.pluginID) + print(self.pcpID) + print(self.pcpBlock) + print(self.parameter) + + self.callback_functions['do_subscribe'](self.pluginID, self.pcpID, self.pcpBlock.name , [], self.parameter.name) \ No newline at end of file diff --git a/papi/gui/qt_dev/add_plugin.py b/papi/gui/qt_dev/add_plugin.py new file mode 100644 index 00000000..d9955cdf --- /dev/null +++ b/papi/gui/qt_dev/add_plugin.py @@ -0,0 +1,180 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.ui.gui.qt_dev.add_plugin import Ui_AddPlugin +from PySide.QtGui import QDialog, QAbstractButton, QDialogButtonBox +from PySide.QtGui import QTreeWidgetItem, QRegExpValidator +from PySide import QtGui +from PySide.QtCore import QRegExp + +from papi.constants import PLUGIN_ROOT_FOLDER_LIST + +from yapsy.PluginManager import PluginManager + +class AddPlugin(QDialog, Ui_AddPlugin): + + def __init__(self, callback_function, parent=None): + super(AddPlugin, self).__init__(parent) + self.setupUi(self) + self.dgui = None + + self.callback_functions = callback_function + + self.treePlugin.currentItemChanged.connect(self.pluginItemChanged) + + + self.buttonBox.clicked.connect(self.button_clicked) + + self.subscriberID = None + self.targetID = None + self.blockName = None + self.setWindowTitle("Add Plugin") + + self.plugin_manager = PluginManager() + self.plugin_path = "../plugin/" + + self.plugin_manager.setPluginPlaces( + PLUGIN_ROOT_FOLDER_LIST + ) + self.setWindowTitle('Available Plugins') + + + self.visual_root = QTreeWidgetItem(self.treePlugin) + self.visual_root.setText(0, 'ViP') + self.io_root = QTreeWidgetItem(self.treePlugin) + self.io_root.setText(0, 'IOP') + self.dpp_root = QTreeWidgetItem(self.treePlugin) + self.dpp_root.setText(0, 'DPP') + self.pcb_root = QTreeWidgetItem(self.treePlugin) + self.pcb_root.setText(0, 'PCB') + + self.plugin_uname = None + self.plugin_name = None + + self.configuration_inputs = {} + + def setDGui(self, dgui): + self.dgui = dgui + + def getDGui(self): + """ + + :return: + :rtype DGui: + """ + return self.dgui + + def pluginItemChanged(self, item): + if hasattr(item, 'pluginfo'): + pluginfo = item.pluginfo + + self.le_path.setText(pluginfo.path) + + startup_config = pluginfo.plugin_object.get_startup_configuration() + + position = 0; + + self.configuration_inputs.clear() + + self.clear_layout(self.customFormLayout) + + for attr in startup_config: + value = startup_config[attr]['value'] + label = QtGui.QLabel(self.formLayoutWidget_2) + label.setText(attr) + label.setObjectName(attr + "_label") + + line_edit = QtGui.QLineEdit(self.formLayoutWidget_2) + line_edit.setText(str(value)) + line_edit.setObjectName(attr + "_line_edit") + + self.customFormLayout.setWidget(position, QtGui.QFormLayout.LabelRole, label) + self.customFormLayout.setWidget(position, QtGui.QFormLayout.FieldRole, line_edit) + + # ------------------------------- + # Check for regex description + # ------------------------------- + + if 'regex' in startup_config[attr]: + regex = startup_config[attr]['regex'] + rx = QRegExp(regex) + validator = QRegExpValidator(rx, self) + line_edit.setValidator(validator) + + self.configuration_inputs[attr] = line_edit + + position+=1 + + def showEvent(self, *args, **kwargs): + self.plugin_manager.collectPlugins() + for pluginfo in self.plugin_manager.getAllPlugins(): + + plugin_item = None + + if '/visual/' in pluginfo.path: + plugin_item = QTreeWidgetItem(self.visual_root) + if '/io/' in pluginfo.path: + plugin_item = QTreeWidgetItem(self.io_root) + if '/dpp/' in pluginfo.path: + plugin_item = QTreeWidgetItem(self.dpp_root) + if '/pcp/' in pluginfo.path: + plugin_item = QTreeWidgetItem(self.pcb_root) + + if plugin_item is not None: + plugin_item.pluginfo = pluginfo + plugin_item.setText(0, pluginfo.name) + + def button_clicked(self, button): + + if self.buttonBox.buttonRole(button) == QDialogButtonBox.ApplyRole: + + plugin_item = self.treePlugin.currentItem() + + self.plugin_name = plugin_item.pluginfo.name + self.plugin_uname = self.le_uname.text() + + config = plugin_item.pluginfo.plugin_object.get_startup_configuration() + + for attr in self.configuration_inputs: + config[attr]['value'] = self.configuration_inputs[attr].text() + + self.callback_functions['do_create_plugin'](self.plugin_name, self.plugin_uname, config=config) + + self.le_uname.setText('') + + button.setFocus() + + def clear_layout(self, layout): + while layout.count(): + child = layout.takeAt(0) + if child.widget() is not None: + child.widget().deleteLater() + elif child.layout() is not None: + self.clear_layout(child.layout()) \ No newline at end of file diff --git a/papi/gui/qt_dev/add_subscriber.py b/papi/gui/qt_dev/add_subscriber.py new file mode 100644 index 00000000..ee0677cc --- /dev/null +++ b/papi/gui/qt_dev/add_subscriber.py @@ -0,0 +1,170 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.ui.gui.qt_dev.add_subscriber import Ui_AddSubscriber +from PySide.QtGui import QDialog, QAbstractButton, QDialogButtonBox +from PySide.QtGui import QTreeWidgetItem +from papi.constants import PLUGIN_PCP_IDENTIFIER + +class AddSubscriber(QDialog, Ui_AddSubscriber): + + def __init__(self, callback_functions, parent=None): + super(AddSubscriber, self).__init__(parent) + self.setupUi(self) + self.dgui = None + + self.treeSubscriber.currentItemChanged.connect(self.subscriberItemChanged) + self.treeTarget.currentItemChanged.connect(self.targetItemChanged) + self.treeBlock.currentItemChanged.connect(self.blockItemChanged) + + self.buttonBox.clicked.connect(self.button_clicked) + + self.subscriberID = None + self.targetID = None + self.blockName = None + self.signalName = None + self.signalIndex = [] + self.setWindowTitle("Create Subscribtion") + + self.callback_functions = callback_functions + + def setDGui(self, dgui): + self.dgui = dgui + + def getDGui(self): + """ + + :return: + :rtype DGui: + """ + return self.dgui + + def subscriberItemChanged(self, item): + self.treeTarget.clear() + if hasattr(item, 'dplugin'): + + subscriber = self.treeSubscriber.currentItem().dplugin + + dplugin_ids = self.dgui.get_all_plugins() + + for dplugin_id in dplugin_ids: + dplugin = dplugin_ids[dplugin_id] + + # ------------------------------ + # Sort DPluginItem in TreeWidget of Subscriber and Target + # ------------------------------ + + if subscriber.id is not dplugin_id and len(dplugin.get_dblocks()) is not 0 and dplugin.type != PLUGIN_PCP_IDENTIFIER: + + target_item = QTreeWidgetItem(self.treeTarget) + + target_item.dplugin = dplugin + target_item.setText(0, str(dplugin.uname) ) + + def targetItemChanged(self, item): + + self.treeBlock.clear() + + if hasattr(item, 'dplugin'): + dplugin = item.dplugin + dblock_ids = dplugin.get_dblocks() + + for dblock_id in dblock_ids: + dblock = dblock_ids[dblock_id] + block_item = QTreeWidgetItem(self.treeBlock) + block_item.dblock = dblock + block_item.setText(0, dblock.name) + + def blockItemChanged(self, item): + self.treeSignal.clear() + + if hasattr(item, 'dblock'): + dblock = item.dblock + + signal_names = dblock.get_signals() + + for signal_name in signal_names: + signal_item = QTreeWidgetItem(self.treeSignal) + signal_item.setText(0, signal_name) + + def showEvent(self, *args, **kwargs): + + self.treeSubscriber.clear() + self.treeTarget.clear() + self.treeBlock.clear() + + dplugin_ids = self.dgui.get_all_plugins() + + for dplugin_id in dplugin_ids: + dplugin = dplugin_ids[dplugin_id] + + # ------------------------------ + # Sort DPluginItem in TreeWidget of Subscriber and Target + # ------------------------------ + + if dplugin.type != PLUGIN_PCP_IDENTIFIER: + + subscriber_item = QTreeWidgetItem(self.treeSubscriber) + + subscriber_item.dplugin = dplugin + subscriber_item.setText(0, str(dplugin.uname) ) + + # # ------------------------------- + # # Set amount of blocks and parameters as meta information + # # ------------------------------- + # dblock_ids = dplugin.get_dblocks() + # + # plugin_item.setText(self.get_column_by_name("#BLOCKS"), str(len(dblock_ids.keys()))) + + def button_clicked(self, button): + + if self.buttonBox.buttonRole(button) == QDialogButtonBox.ApplyRole: + + subscriber_item = self.treeSubscriber.currentItem() + target_item = self.treeTarget.currentItem() + block_item = self.treeBlock.currentItem() + signal_item = self.treeSignal.currentItem() + + self.subscriberID = subscriber_item.dplugin.id + self.targetID = target_item.dplugin.id + self.blockName = block_item.dblock.name + + self.signalIndex = [] + + for item in self.treeSignal.selectedItems(): + signalName = item.text(0) + + index = block_item.dblock.get_signals().index(signalName) + + self.signalIndex.append(index) + + button.setFocus() + + self.callback_functions['do_subscribe'](self.subscriberID, self.targetID, self.blockName, self.signalIndex) \ No newline at end of file diff --git a/papi/gui/qt_dev/gui_main.py b/papi/gui/qt_dev/gui_main.py new file mode 100644 index 00000000..90504976 --- /dev/null +++ b/papi/gui/qt_dev/gui_main.py @@ -0,0 +1,332 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth, Stefan Ruppin +""" + +__author__ = 'knuths' + +import sys +import time +import os + +from PySide.QtGui import QMainWindow, QApplication +from PySide.QtGui import QIcon +from PySide.QtCore import QSize + +from papi.ui.gui.qt_dev.main import Ui_MainGUI +from papi.gui.qt_dev.manager import Overview +from papi.data.DGui import DGui +from papi.ConsoleLog import ConsoleLog +from papi.gui.qt_dev.add_plugin import AddPlugin +from papi.gui.qt_dev.add_subscriber import AddSubscriber +from papi.gui.qt_dev.add_pcp_subscriber import AddPCPSubscriber +from papi.constants import GUI_PAPI_WINDOW_TITLE, GUI_WOKRING_INTERVAL, GUI_PROCESS_CONSOLE_IDENTIFIER, \ + GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_START_CONSOLE_MESSAGE +from papi.constants import CONFIG_DEFAULT_FILE +from papi.gui.gui_api import Gui_api +from papi.gui.gui_event_processing import GuiEventProcessing +import pyqtgraph as pg +from pyqtgraph import QtCore + + + +# Enable antialiasing for prettier plots +pg.setConfigOptions(antialias=False) + +class GUI(QMainWindow, Ui_MainGUI): + + def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): + super(GUI, self).__init__(parent) + self.setupUi(self) + + self.create_actions() + + if gui_data is None: + self.gui_data = DGui() + else: + self.gui_data = gui_data + + # TODO: + # callback functions for create plugin (intead scopeArea parameter) and for delete Plugin in ..GuiEventProcessing + self.gui_api = Gui_api(self.gui_data, core_queue, gui_id) + self.gui_event_processing = GuiEventProcessing(self.gui_data, core_queue, gui_id, gui_queue) + + self.callback_functions = { + 'do_create_plugin' : self.gui_api.do_create_plugin, + 'do_delete_plugin' : self.gui_api.do_delete_plugin, + 'do_set_parameter' : self.gui_api.do_set_parameter, + 'do_subscribe' : self.gui_api.do_subscribe, + 'do_unsubscribe' : self.gui_api.do_unsubscribe, + 'do_set_parameter' : self.gui_api.do_set_parameter_uname + } + + self.manager_overview = Overview(self.callback_functions) + self.manager_overview.dgui = self.gui_data + self.setWindowTitle(GUI_PAPI_WINDOW_TITLE) + + self.core_queue = core_queue + self.gui_queue = gui_queue + + self.gui_id = gui_id + + self.count = 0 + + self.log = ConsoleLog(GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_PROCESS_CONSOLE_IDENTIFIER) + + self.AddSub = None + self.AddPlu = None + + self.log.printText(1,GUI_START_CONSOLE_MESSAGE + ' .. Process id: '+str(os.getpid())) + + self.stefans_text_field.setText('1') + # ------------------------------------- + # Create actions for buttons + # ------------------------------------- + + self.buttonCreatePlugin.clicked.connect(self.create_plugin) + self.buttonCreateSubscription.clicked.connect(self.create_subscription) + self.buttonCreatePCPSubscription.clicked.connect(self.create_pcp_subscription) + self.buttonShowOverview.clicked.connect(self.ap_overview) + self.buttonExit.clicked.connect(self.close) + + # ------------------------------------- + # Create Icons for buttons + # ------------------------------------- + + addplugin_icon = QIcon.fromTheme("list-add") + close_icon = QIcon.fromTheme("application-exit") + overview_icon = QIcon.fromTheme("view-fullscreen") + addsubscription_icon = QIcon.fromTheme("list-add") + + # ------------------------------------- + # Set Icons for buttons + # ------------------------------------- + + self.buttonCreatePlugin.setIcon(addplugin_icon) + self.buttonCreatePlugin.setIconSize(QSize(30, 30)) + + self.buttonExit.setIcon(close_icon) + self.buttonExit.setIconSize(QSize(30, 30)) + + self.buttonShowOverview.setIcon(overview_icon) + self.buttonShowOverview.setIconSize(QSize(30, 30)) + + self.buttonCreateSubscription.setIcon(addsubscription_icon) + self.buttonCreateSubscription.setIconSize(QSize(30, 30)) + + self.buttonCreatePCPSubscription.setIcon(addsubscription_icon) + self.buttonCreatePCPSubscription.setIconSize(QSize(30, 30)) + + # ------------------------------------- + # Set Tooltipps for buttons + # ------------------------------------- + + self.buttonExit.setToolTip("Exit PaPI") + self.buttonCreatePlugin.setToolTip("Add New Plugin") + self.buttonCreateSubscription.setToolTip("Create New Subscription") + self.buttonCreatePCPSubscription.setToolTip("Create New PCP Subscription") + + self.buttonShowOverview.setToolTip("Show Overview") + + # ------------------------------------- + # Set TextName to '' + # ------------------------------------- + + self.buttonExit.setText('') + self.buttonCreatePlugin.setText('') + self.buttonCreateSubscription.setText('') + self.buttonShowLicence.setText('') + self.buttonShowOverview.setText('') + + def run(self): + # create a timer and set interval for processing events with working loop + QtCore.QTimer.singleShot(GUI_WOKRING_INTERVAL, lambda: self.gui_event_processing.gui_working(self.closeEvent)) + + def set_dgui_data(self, dgui): + self.gui_data = dgui + self.manager_overview.dgui =dgui + + def dbg(self): + print("Action") + + def create_actions(self): + self.actionM_License.triggered.connect(self.menu_license) + self.actionM_Quit.triggered.connect(self.menu_quit) + + self.actionP_Overview.triggered.connect(self.ap_overview) + + + self.stefans_button.clicked.connect(self.stefan) + self.stefans_button_2.clicked.connect(self.stefan_at_his_best) + + def create_plugin(self): + """ + This function is called to create an QDialog, which is used to create Plugins + :return: + """ + self.AddPlu = AddPlugin(self.callback_functions) + self.AddPlu.setDGui(self.gui_data) + self.AddPlu.show() + self.AddPlu.raise_() + self.AddPlu.activateWindow() + r = self.AddPlu.exec_() + + del self.AddPlu + self.AddPlu = None + + def create_subscription(self): + """ + This function is called to create an QDialog, which is used to create a subscription for a single Plugin + :return: + """ + + self.AddSub = AddSubscriber(self.callback_functions) + + self.AddSub.setDGui(self.gui_data) + self.AddSub.show() + self.AddSub.raise_() + self.AddSub.activateWindow() + r = self.AddSub.exec_() + + del self.AddSub + self.AddSub = None + + def create_pcp_subscription(self): + """ + This function is called to create an QDialog, which is used to create a subscription for a single Plugin + :return: + """ + + self.AddPCPSub = AddPCPSubscriber(self.callback_functions) + + self.AddPCPSub.setDGui(self.gui_data) + self.AddPCPSub.show() + self.AddPCPSub.raise_() + self.AddPCPSub.activateWindow() + r = self.AddPCPSub.exec_() + + del self.AddPCPSub + self.AddPCPSub = None + + + def menu_license(self): + pass + + def menu_quit(self): + self.close() + pass + + def ap_overview(self): + self.manager_overview.show() + pass + + def closeEvent(self, *args, **kwargs): + self.gui_api.do_close_program() + + self.manager_overview.close() + + if self.AddPlu is not None: + self.AddPlu.close() + + if self.AddSub is not None: + self.AddSub.close() + + self.close() + + + + def stefan_at_his_best(self): + + self.gui_api.do_load_xml(CONFIG_DEFAULT_FILE) + + def stefan(self): + self.count += 1 + + op= 0 + + if op == 0: + self.gui_api.do_save_xml_config(CONFIG_DEFAULT_FILE) + + if op == 1: + self.gui_api.do_delete_plugin_uname('Plot1') + self.gui_api.do_delete_plugin_uname('SInus1') + self.gui_api.do_delete_plugin_uname('Butt') + + if op == 2: + self.do_create_plugin('Sinus','Sinus1') #id 2 + self.do_create_plugin('Plot','Plot1') #id 3 + #self.do_create_plugin('Plot','Plot2') #id 4 + + time.sleep(0.1) + + #self.do_subscribe(3,2,'SinMit_f3',2) + #self.do_subsribe(4,2,'SinMit_f3') + + + +def startGUI(CoreQueue, GUIQueue,gui_id): + """ + Function to call to start gui operation + :param CoreQueue: link to queue of core + :type CoreQueue: Queue + :param GUIQueue: queue where gui receives messages + :type GUIQueue: Queue + :param gui_id: id of gui for events + :type gui_id: int + :return: + """ + app = QApplication(sys.argv) + gui = GUI(CoreQueue, GUIQueue,gui_id) + gui.run() + gui.show() + app.exec_() + +def startGUI_TESTMOCK(CoreQueue, GUIQueue,gui_id, data_mock): + """ + Function to call to start gui operation + :param CoreQueue: link to queue of core + :type CoreQueue: Queue + :param GUIQueue: queue where gui receives messages + :type GUIQueue: Queue + :param gui_id: id of gui for events + :type gui_id: int + :return: + """ + app = QApplication(sys.argv) + app.aboutToQuit.connect(app.deleteLater) + + gui = GUI(CoreQueue, GUIQueue,gui_id, data_mock) + + gui.run() + gui.show() + app.exec_() + +if __name__ == '__main__': + # main of GUI, just for stand alone gui testing + app = QApplication(sys.argv) + frame = GUI(None,None,None) + frame.show() + app.exec_() diff --git a/papi/gui/qt_dev/manager.py b/papi/gui/qt_dev/manager.py new file mode 100644 index 00000000..d4809a0d --- /dev/null +++ b/papi/gui/qt_dev/manager.py @@ -0,0 +1,464 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + + +import sys +import time + +from PySide.QtGui import QMainWindow, QTreeWidgetItem, QTableWidgetItem +from PySide.QtCore import Qt + +from papi.ui.gui.qt_dev.manager import Ui_Manager + + +from yapsy.PluginManager import PluginManager + +from papi.constants import PLUGIN_PCP_IDENTIFIER, PLUGIN_DPP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER, PLUGIN_IOP_IDENTIFIER + + +class Overview(QMainWindow, Ui_Manager): + def __init__(self, callback_functions, parent=None): + """ + + :param dgui: + :param parent: + :return: + """ + + super(Overview, self).__init__(parent) + self.setupUi(self) + + self.setWindowTitle('Overview Plugins') + + self.callback_functions = callback_functions + + self.treePlugin.currentItemChanged.connect(self.plugin_item_changed) + self.treeBlock.currentItemChanged.connect(self.block_item_changed) + #self.tableParameter.cellChanged.connect(self.parameterCellChanged) + # ---------------------------------------- + # Create Root Elements for TreeWidget + # ---------------------------------------- + self.visual_root = QTreeWidgetItem(self.treePlugin) + self.visual_root.setText(self.get_column_by_name("PLUGIN"), '[ViP]') + self.io_root = QTreeWidgetItem(self.treePlugin) + self.io_root.setText(self.get_column_by_name("PLUGIN"), '[IOP]') + self.dpp_root = QTreeWidgetItem(self.treePlugin) + self.dpp_root.setText(self.get_column_by_name("PLUGIN"), '[DPP]') + self.pcb_root = QTreeWidgetItem(self.treePlugin) + self.pcb_root.setText(self.get_column_by_name("PLUGIN"), '[PCB]') + self.dgui = None + +# self.subscribeButton.clicked.connect(self.subscribe_action) + + # --------------------------------- + # Search for PCP Plugins + # --------------------------------- + + self.plugin_manager = PluginManager() + self.plugin_path = "../plugin/" + + self.plugin_manager.setPluginPlaces( + [ + # self.plugin_path + "visual", 'plugin/visual', + # self.plugin_path + "io", 'plugin/io', + # self.plugin_path + "dpp", 'plugin/dpp', + self.plugin_path + "pcp", 'plugin/pcp' + ] + ) + + # myAction = QAction('RechtsKLick', self) + # self.setContextMenuPolicy(Qt.ActionsContextMenu) + # + # self.addAction(myAction) + # + # myAction.triggered.connect( lambda : self.contextMenu(self.treePlugin.currentItem())) + + + # def contextMenu(self, item): + # if hasattr(item, 'object'): + # print(item.object) + # print('itemContextMenu') + + # def add_plugin(self): + # AddPlu = AddPlugin() + # AddPlu.setDGui(self.dgui) + # AddPlu.show() + # AddPlu.raise_() + # AddPlu.activateWindow() + # r = AddPlu.exec_() + # + # if r == 1 : + # self.callback_functions['do_create_plugin'](AddPlu.plugin_name, AddPlu.plugin_uname) + # + # print("ReturnCode ", str(r)) + + # def add_subscribtion(self): + # + # AddSub = AddSubscriber() + # AddSub.setDGui(self.dgui) + # AddSub.show() + # AddSub.raise_() + # AddSub.activateWindow() + # r = AddSub.exec_() + # + # if r == 1 : + # subscriber_id = AddSub.subscriberID + # target_id = AddSub.targetID + # block_name = AddSub.blockName + # + # self.callback_functions['do_subscribe'](subscriber_id, target_id, block_name) + # + # print("ReturnCode " , str(r)) + +# def subscribe_action(self): +# item = self.treePlugin.currentItem() +# if hasattr(item, 'object'): +# print(item.object) +# sub_id = item.object.id +# target_id = int(self.target_id.text()) +# block_name = str(self.block_name.text()) +# # print(sub_id + " - " + target_id + " - " + block_name) +# self.callback_functions['do_subscribe'](sub_id, target_id, block_name) +# +# print('itemCreate') + + def plugin_item_changed(self, item): + """ + Function is called when a new item/plugin is selected + :param item: + :return: + """ + self.clean() + + # ------------------------------ + # Remove slot for signal cellChanged + # if possible + # ------------------------------ + try: + self.tableParameter.cellChanged.disconnect(self.parameterCellChanged) + except: + pass + + if hasattr(item, 'dplugin'): + + dplugin = item.dplugin + + + + # ------------------------------ + # Add Meta information of DPlugin + # ------------------------------ + self.le_ID.setText(str(dplugin.id)) + self.le_Type.setText(str(dplugin.type)) + + # ------------------------------ + # Add DBlocksItem for DPluginItem + # ------------------------------ + dblock_root = self.treeBlock + + dblock_ids = dplugin.get_dblocks() + + for dblock_id in dblock_ids: + dblock = dblock_ids[dblock_id] + block_item = QTreeWidgetItem(dblock_root) + block_item.dblock = dblock + block_item.setText(self.get_column_by_name("BLOCK"), dblock.name) + + # --------------------------- + # Add Subscribers of DBlock + # --------------------------- + + subscriber_ids = dblock.get_subscribers() + + for subscriber_id in subscriber_ids: + subscriber_item = QTreeWidgetItem(block_item) + + subscriber = self.dgui.get_dplugin_by_id(subscriber_id) + + subscriber_item.setText(self.get_column_by_name("SUBSCRIBER"), str(subscriber.uname)) + + # ------------------------------ + # Add DParameterItem for DPluginItem + # ------------------------------ + + dparameter_table = self.tableParameter + + dparameter_names = dplugin.get_parameters() + + row = 0 + + dparameter_table.setRowCount(len(dparameter_names.keys())) + + for dparameter_name in dparameter_names: + + dparameter = dparameter_names[dparameter_name] + # --------------------- + # Set Parameter Name + # --------------------- + parameter_item_name = QTableWidgetItem( str(dparameter.name) ) + parameter_item_name.setFlags( Qt.ItemIsSelectable and not Qt.ItemIsEditable and Qt.ItemIsEnabled) + dparameter_table.setItem(row, 0, parameter_item_name) + + # --------------------- + # Set PCPs in table + # --------------------- +# combo_box = QComboBox() +# +# #TODO: Erzeugt Fehler, da objekte beim Wechsel von Plugins geloescht werden. +# # combo_box.currentIndexChanged.connect( lambda : self.combo_box_parameter_changed(dplugin, dparameter, combo_box)) +# # +# dparameter_table.setCellWidget(row, 1, combo_box) +# +# self.plugin_manager.collectPlugins() +# +# combo_box.addItem("None", None) +# +# for pluginfo in self.plugin_manager.getAllPlugins(): +# +# combo_box.addItem(pluginfo.name, pluginfo) + + # --------------------- + # Set Current Value in table + # --------------------- + parameter_item_value = QTableWidgetItem( str(dparameter.value) ) + parameter_item_value.dplugin = dplugin + parameter_item_value.dparameter = dparameter + dparameter_table.setItem(row, 2, parameter_item_value) + + # --------------------------------- + # Add slot for signal cellChanged + # --------------------------------- + + self.tableParameter.cellChanged.connect(self.parameterCellChanged) + + row+=1 + + def parameterCellChanged(self, row, column): + """ + This function is always called when + an (text)-item within the table is changed + :param row: + :param column: + :return: + """ + # ----------------------------------------------- + # Only interested in changes of the third column + # ----------------------------------------------- + if column == 0: + return 0 + + item = self.tableParameter.item(row, column) + nvalue = item.text() + + self.callback_functions['do_set_parameter'](item.dplugin.uname, item.dparameter.name, float(nvalue)) + + #print('Parameter Change Request for ' + item.dparameter.name + " of Plugin " + item.dplugin.uname + " Value: " + str(nvalue)) + + # def combo_box_parameter_changed(self, dplugin, dparameter, box): + # """ + # This function is always called when + # an item within a combox box is changed + # + # :param dplugin: + # :param dparameter: + # :param box: + # :return: + # """ + # # if dplugin == None or dparameter == None or box == None: + # # return 0 + # + # dparameter_name = dparameter.name + # index = box.currentIndex() + # + # pcp = box.itemData(index) + # + # if pcp is None: + # return 0 + # + # config={ + # 'dplugin_id' : {}, + # 'default' : {}, + # 'name' : {} + # } + # + # config['dplugin_id']['value']= str(dplugin.id) + # config['default']['value']= str(dparameter.default) + # config['name']['value'] = dparameter_name + # + # + # self.callback_functions['do_create_plugin'](pcp.name, dparameter.name + "_" + pcp.name, config=config) + # + # # if pcp is not None: + # # print('GUI:Manager: PCP Change Request for Parameter ' + dparameter.name + " of Plugin " + dplugin.uname + " PCB " + pcp.name ) + # # else: + # # print('GUI:Manager: PCP Change Request for Parameter ' + dparameter.name + " of Plugin " + dplugin.uname + " PCB None " ) + # # pass + + def block_item_changed(self, item): + self.treeSignal.clear() + + if hasattr(item, 'dblock'): + dblock = item.dblock + + signals = dblock.get_signals() + + for signal in signals: + print(signal) + signal_item = QTreeWidgetItem(self.treeSignal) + signal_item.setText(self.get_column_by_name("SIGNAL"), signal) + + def showEvent(self, *args, **kwargs): + dplugin_ids = self.dgui.get_all_plugins() + + #Add DPlugins in QTree + + for dplugin_id in dplugin_ids: + dplugin = dplugin_ids[dplugin_id] + + # ------------------------------ + # Sort DPluginItem in TreeWidget + # ------------------------------ + + if dplugin.type == PLUGIN_VIP_IDENTIFIER: + plugin_item = QTreeWidgetItem(self.visual_root) + if dplugin.type == PLUGIN_IOP_IDENTIFIER: + plugin_item = QTreeWidgetItem(self.io_root) + if dplugin.type == PLUGIN_DPP_IDENTIFIER: + plugin_item = QTreeWidgetItem(self.dpp_root) + if dplugin.type == PLUGIN_PCP_IDENTIFIER: + plugin_item = QTreeWidgetItem(self.pcb_root) + + plugin_item.dplugin = dplugin + plugin_item.setText(self.get_column_by_name("PLUGIN"), str(dplugin.uname) ) + + # ------------------------------- + # Set amount of blocks and parameters as meta information + # ------------------------------- + dparameter_names = dplugin.get_parameters() + dblock_ids = dplugin.get_dblocks() + + plugin_item.setText(self.get_column_by_name("#PARAMETERS"), str(len(dparameter_names.keys()))) + plugin_item.setText(self.get_column_by_name("#BLOCKS"), str(len(dblock_ids.keys()))) + + def clean(self): + """ + This function is called to remove old values in form fields or similar + :return: + """ + + #------------------ + # Remove content in form fields + #------------------ + + self.le_ID.setText("") + self.le_Type.setText("") + self.le_Path.setText("") + + #------------------ + # Remove items in parameter tree + #------------------ + + self.treeBlock.clear() + + #------------------ + # Remove items in block tree + #------------------ + + self.treeSignal.clear() + + self.tableParameter.clear() + + #self.treeParameter.clear() + + #self.tableParameter.clear() + + def hideEvent(self, *args, **kwargs): + """ + This event is used to clear the TreeWidget + :param args: + :param kwargs: + :return: + """ + + item = self.io_root.child(0) + + while item is not None: + self.io_root.removeChild(item) + item = self.io_root.child(0) + + item = self.visual_root.child(0) + + while item is not None: + self.visual_root.removeChild(item) + item = self.visual_root.child(0) + + item = self.dpp_root.child(0) + + while item is not None: + self.dpp_root.removeChild(item) + item = self.dpp_root.child(0) + + item = self.pcb_root.child(0) + + while item is not None: + self.pcb_root.removeChild(item) + item = self.pcb_root.child(0) + + def resizeEvent(self, event): + h = event.size().height() + w = event.size().width() + + + def get_column_by_name(self, name): + """ + Returns column number by name + :param name: + :return: + """ + if name == "PLUGIN": + return 0 + + if name == "#PARAMETERS": + return 1 + + if name == "#BLOCKS": + return 2 + + if name == "SUBSCRIBER": + return 1 + + if name == "BLOCK": + return 0 + + if name == "PARAMETER": + return 0 + + if name == "SIGNAL": + return 0 diff --git a/papi/gui/qt_new/__init__.py b/papi/gui/qt_new/__init__.py new file mode 100644 index 00000000..7c97c23d --- /dev/null +++ b/papi/gui/qt_new/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' diff --git a/papi/gui/qt_new/create_plugin_dialog.py b/papi/gui/qt_new/create_plugin_dialog.py new file mode 100644 index 00000000..8f1c112d --- /dev/null +++ b/papi/gui/qt_new/create_plugin_dialog.py @@ -0,0 +1,215 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" +from papi.gui.qt_new.item import PaPITreeItem, PaPIRootItem, PaPITreeModel + +__author__ = 'knuths' + +from papi.ui.gui.qt_new.create_dialog import Ui_CreatePluginDialog +from PySide.QtGui import QDialog, QLabel, QFormLayout, QLineEdit, QRegExpValidator, QCheckBox, QFileDialog +from papi.gui.qt_new.custom import FileLineEdit + +from papi.constants import PLUGIN_ROOT_FOLDER_LIST +from PySide.QtCore import * +from PySide import QtGui +import PySide +from yapsy.PluginManager import PluginManager +import operator + +class CreatePluginDialog(QDialog, Ui_CreatePluginDialog): + + def __init__(self, gui_api, parent=None): + super(CreatePluginDialog, self).__init__(parent) + self.setupUi(self) + self.cfg = None + self.configuration_inputs = {} + self.gui_api = gui_api + + + def set_plugin(self, plugin): + startup_config = plugin.plugin_object.get_startup_configuration() + self.cfg = startup_config + self.plugin_name = plugin.name + self.plugin_type = plugin.plugin_object.get_type() + self.cfg['uname'] = {} + self.cfg['uname']['value'] = '' + + def accept(self): + + config = self.cfg + + for attr in self.configuration_inputs: + + if isinstance(self.configuration_inputs[attr], QCheckBox): + + if self.configuration_inputs[attr].isChecked(): + config[attr]['value'] = '1' + else: + config[attr]['value'] = '0' + + if isinstance(self.configuration_inputs[attr], QLineEdit): + config[attr]['value'] = self.configuration_inputs[attr].text() + + if not self.gui_api.do_test_name_to_be_unique(config['uname']['value']) : + self.configuration_inputs['uname'].setStyleSheet("QLineEdit { border : 2px solid red;}") + self.configuration_inputs['uname'].setFocus() + return + + self.done(0) + + autostart = True + if self.plugin_type == 'IOP' or self.plugin_type == 'DPP': + autostart = self.autostartBox.isChecked() + + self.gui_api.do_create_plugin(self.plugin_name, config['uname']['value'], config=config, autostart=autostart) + + def reject(self): + self.done(-1) + + def showEvent(self, *args, **kwargs): + startup_config = self.cfg + + self.clear_layout(self.formSimple) + self.clear_layout(self.formAdvance) + self.configuration_inputs.clear() + + self.setWindowTitle("Create Plugin " + self.plugin_name) + + position = 0 + + if 'uname' in startup_config.keys(): + value = startup_config['uname']['value'] + + display_text = 'uname' + + if 'display_text' in startup_config['uname'].keys(): + display_text = startup_config['uname']['display_text'] + + uname = self.gui_api.do_change_string_to_be_uname(self.plugin_name) + uname = self.gui_api.change_uname_to_uniqe(uname) + + + editable_field = QLineEdit(str(value)) + editable_field.setText(uname) + editable_field.setObjectName('uname' + "_line_edit") + + self.formSimple.addRow(str(display_text) , editable_field) + + self.configuration_inputs['uname'] = editable_field + + #line_edit.selectAll() + #line_edit.setFocus() + + position += 1 + + startup_config_sorted = sorted(startup_config.items(), key=operator.itemgetter(0)) + + for attr in startup_config_sorted: + attr = attr[0] + if attr != 'uname': + value = startup_config[attr]['value'] + + display_text = attr + + if 'display_text' in startup_config[attr].keys(): + display_text = startup_config[attr]['display_text'] + + + # ------------------------------- + # Check for datatype + # ------------------------------- + + editable_field = None + + if 'type' in startup_config[attr]: + parameter_type = startup_config[attr]['type'] + + if parameter_type == 'bool': + editable_field = QCheckBox() + + if value == '1': + editable_field.setChecked(True) + else: + editable_field.setChecked(False) + + if parameter_type == 'file': + editable_field = FileLineEdit() + editable_field.setReadOnly(True) + editable_field.setText(value) + + else: + editable_field = QLineEdit() + + editable_field.setText(str(value)) + editable_field.setObjectName(attr + "_line_edit") + + # ------------------------------- + # Check for regex description + # ------------------------------- + + if 'regex' in startup_config[attr]: + regex = startup_config[attr]['regex'] + rx = QRegExp(regex) + validator = QRegExpValidator(rx, self) + editable_field.setValidator(validator) + + # ------------------------------- + # Divided in advanced or simple option + # ------------------------------- + + if 'advanced' in startup_config[attr]: + if startup_config[attr]['advanced'] == '1': + self.formAdvance.addRow(str(display_text), editable_field) + else: + self.formSimple.addRow(str(display_text), editable_field) + else: + self.formSimple.addRow(str(display_text), editable_field) + + if 'tooltip' in startup_config[attr]: + editable_field.setToolTip(startup_config[attr]['tooltip']) + + + + self.configuration_inputs[attr] = editable_field + + position+=1 + + # self.configuration_inputs['uname'].setFocus() + + def keyPressEvent(self, event): + if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: + self.accept() + if event.key() == Qt.Key_Escape: + self.close() + + def clear_layout(self, layout): + while layout.count(): + child = layout.takeAt(0) + if child.widget() is not None: + child.widget().deleteLater() + elif child.layout() is not None: + self.clear_layout(child.layout()) \ No newline at end of file diff --git a/papi/gui/qt_new/create_plugin_menu.py b/papi/gui/qt_new/create_plugin_menu.py new file mode 100644 index 00000000..7f70e8e6 --- /dev/null +++ b/papi/gui/qt_new/create_plugin_menu.py @@ -0,0 +1,155 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" +from papi.gui.qt_new.item import PaPITreeItem, PaPIRootItem, PaPITreeModel +from papi.gui.qt_new.item import PluginTreeItem +__author__ = 'knuths' + +from papi.ui.gui.qt_new.create import Ui_Create +from papi.gui.qt_new.create_plugin_dialog import CreatePluginDialog +from PySide.QtGui import QMainWindow, QStandardItem, QStandardItemModel + +from papi.constants import PLUGIN_ROOT_FOLDER_LIST +from PySide.QtCore import * + +from yapsy.PluginManager import PluginManager + + +class CreatePluginMenu(QMainWindow, Ui_Create): + + def __init__(self, gui_api, parent=None): + super(CreatePluginMenu, self).__init__(parent) + self.setupUi(self) + self.dgui = gui_api.gui_data + + self.gui_api = gui_api + + self.subscriberID = None + self.targetID = None + self.blockName = None + self.setWindowTitle("Add Plugin") + + self.plugin_manager = PluginManager() + self.plugin_path = "../plugin/" + + self.plugin_manager.setPluginPlaces( + PLUGIN_ROOT_FOLDER_LIST + ) + self.setWindowTitle('Available Plugins') + + model = PaPITreeModel() + model.setHorizontalHeaderLabels(['Name']) + + self.pluginTree.setModel(model) + self.pluginTree.setUniformRowHeights(True) + self.pluginTree.setSortingEnabled(True) + + self.visual_root = PaPIRootItem('ViP') + self.io_root = PaPIRootItem('IOP') + self.dpp_root = PaPIRootItem('DPP') + self.pcp_root = PaPIRootItem('PCP') + + model.appendRow(self.visual_root) + model.appendRow(self.io_root) + model.appendRow(self.dpp_root) + model.appendRow(self.pcp_root) + + self.configuration_inputs = {} + + self.pluginTree.clicked.connect(self.pluginItemChanged) + + self.plugin_create_dialog = CreatePluginDialog(self.gui_api) + self.createButton.clicked.connect(self.show_create_plugin_dialog) + + def keyPressEvent(self, event): + if event.key() == Qt.Key_Escape: + self.close() + + if event.key() == Qt.Key_Return or event.key() == Qt.Key_Enter: + self.show_create_plugin_dialog() + + + def pluginItemChanged(self, index): + item = self.pluginTree.model().data(index, Qt.UserRole) + + self.clear() + + self.scrollArea.setDisabled(True) + + if item is None: + return + + self.scrollArea.setDisabled(False) + + self.nameEdit.setText(item.name) + self.authorEdit.setText(item.author) + self.descriptionText.setText(item.description) + self.pathEdit.setText(item.path) + + + + def show_create_plugin_dialog(self): + index = self.pluginTree.currentIndex() + item = self.pluginTree.model().data(index, Qt.UserRole) + + + if item is not None: + + self.plugin_create_dialog.set_plugin(item) + + self.plugin_create_dialog.show() + + def showEvent(self, *args, **kwargs): + self.plugin_manager.collectPlugins() + + for pluginfo in self.plugin_manager.getAllPlugins(): + + plugin_item = PluginTreeItem(pluginfo) + + if '/visual/' in pluginfo.path: + self.visual_root.appendRow(plugin_item) + if '/io/' in pluginfo.path: + self.io_root.appendRow(plugin_item) + if '/dpp/' in pluginfo.path: + self.dpp_root.appendRow(plugin_item) + if '/pcp/' in pluginfo.path: + self.pcp_root.appendRow(plugin_item) + + self.visual_root.sortChildren(0) + self.io_root.sortChildren(0) + self.dpp_root.sortChildren(0) + self.pcp_root.sortChildren(0) + + def clear(self): + self.nameEdit.setText('') + self.authorEdit.setText('') + self.descriptionText.setText('') + self.pathEdit.setText('') + + def closeEvent(self, *args, **kwargs): + self.plugin_create_dialog.close() + diff --git a/papi/gui/qt_new/custom.py b/papi/gui/qt_new/custom.py new file mode 100644 index 00000000..7a9cf1d4 --- /dev/null +++ b/papi/gui/qt_new/custom.py @@ -0,0 +1,59 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from PySide.QtGui import QLineEdit, QFileDialog +import os + +class FileLineEdit(QLineEdit): + def __init__(self): + super(FileLineEdit, self).__init__() + + self.file_type = "File (*)" + + def set_file_type(self, type): + self.file_type = type + + def mousePressEvent(self, event): + + fileNames = '' + + path, file = os.path.split(self.text()) + + dialog = QFileDialog(self) + dialog.setFileMode(QFileDialog.AnyFile) + dialog.setNameFilter( self.tr(self.file_type)) + dialog.setDirectory(path) + + if dialog.exec_(): + fileNames = dialog.selectedFiles() + + if len(fileNames): + if fileNames[0] != '': + self.setText(fileNames[0]) \ No newline at end of file diff --git a/papi/gui/qt_new/item.py b/papi/gui/qt_new/item.py new file mode 100644 index 00000000..d8a596da --- /dev/null +++ b/papi/gui/qt_new/item.py @@ -0,0 +1,338 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + + +from PySide.QtCore import * +from PySide.QtGui import * +from papi.data.DPlugin import * +from papi.data.DSignal import DSignal + +# ------------------------------------ +# Item Object +# ------------------------------------ + + +class PaPITreeItem(QStandardItem): + def __init__(self, object, name): + super(PaPITreeItem, self).__init__(name) + self.object = object + self.name = name + self.tool_tip = "Plugin: " + self.name + + def data(self, role): + """ + For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum' + :param role: + :return: + """ + + if role == Qt.ToolTipRole: + return self.tool_tip + + if role == Qt.DisplayRole: + return self.name + + if role == Qt.DecorationRole: + return self.get_decoration() + + if role == Qt.UserRole: + return self.object + + return None + + def get_decoration(self): + return None + + +class PaPIRootItem(QStandardItem): + def __init__(self, name): + super(PaPIRootItem, self).__init__(name) + self.setEditable(False) + self.setSelectable(False) + self.name = name + + def data(self, role): + """ + For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum' + :param role: + :return: + """ + + # if role == Qt.ToolTipRole: + # return self.tool_tip + + if role == Qt.DisplayRole: + return self.name + " (" + str(self.rowCount()) + ")" + + # if role == Qt.DecorationRole: + # return self.get_decoration() + + # if role == Qt.UserRole: + # return self.object + + return None + + def clean(self): + """ + This function is called to remove all rows which contain an item marked as 'deleted'. + This only works with items having an state-attribute e.g. DPlugin. + + :return: + """ + for row in range(self.rowCount()): + treeItem = self.child(row) + if treeItem is not None: + item = treeItem.data(Qt.UserRole) + if hasattr(item, 'state'): + if item.state == 'deleted': + self.removeRow(row) + + def hasItem(self, sItem): + """ + Used to check if an item is already part of this Tree + + :param sItem: Searched for this item + :return: + """ + for row in range(self.rowCount()): + treeItem = self.child(row) + if treeItem is not None: + item = treeItem.data(Qt.UserRole) + if item == sItem: + return True + +# ------------------------------------ +# Model Objects +# ------------------------------------ + + +class PaPITreeModel(QStandardItemModel): + def __init__(self, parent=None): + super(PaPITreeModel, self).__init__(parent) + + def flags(self, index): + row = index.row() + col = index.column() + + parent = index.parent() + + if not parent.isValid(): + return Qt.ItemIsEnabled + + return Qt.ItemIsSelectable | Qt.ItemIsEnabled + +# ------------------------------------ +# Item Custom +# ------------------------------------ + + +class PluginTreeItem(PaPITreeItem): + def __init__(self, plugin): + super(PluginTreeItem, self).__init__(plugin, plugin.name) + self.plugin = plugin + self.setEditable(False) + + def get_decoration(self): + l = len(self.object.name) + path = self.object.path[:-l] + path += 'box.png' + px = QPixmap(path) + return px + + +class DPluginTreeItem(PaPITreeItem): + def __init__(self, dplugin: DPlugin): + super(DPluginTreeItem, self).__init__(dplugin, dplugin.uname) + self.dplugin = dplugin + self.name = dplugin.uname + self.setEditable(False) + + def get_decoration(self): + return QIcon.fromTheme("list-add") + + +class DParameterTreeItem(PaPITreeItem): + def __init__(self, dparameter: DParameter): + super(DParameterTreeItem, self).__init__(dparameter, str(dparameter.value)) + self.dparameter = dparameter + self.setEditable(False) + self.tool_tip = dparameter.name + + def get_decoration(self): + return None + + +class DBlockTreeItem(PaPITreeItem): + def __init__(self, dblock: DBlock): + super(DBlockTreeItem, self).__init__(dblock, dblock.name) + self.dblock = dblock + self.setSelectable(False) + self.setEditable(False) + + def get_decoration(self): + return None + +class DSignalTreeItem(PaPITreeItem): + def __init__(self, dsignal: DSignal): + super(DSignalTreeItem, self).__init__(dsignal, dsignal.dname) + self.dsignal = dsignal + self.setSelectable(False) + self.setEditable(False) + + def get_decoration(self): + return None + +# ------------------------------------ +# Model Custom +# ------------------------------------ + + +class PluginTreeModel(PaPITreeModel): + """ + This model is used to handle Plugin objects in TreeView created by the yapsy plugin manager. + """ + def __init__(self, parent=None): + super(PluginTreeModel, self).__init__(parent) + + +class DPluginTreeModel(PaPITreeModel): + """ + This model is used to handle DPlugin objects in TreeView. + """ + def __init__(self, parent=None): + super(DPluginTreeModel, self).__init__(parent) + + +class DParameterTreeModel(PaPITreeModel): + """ + This model is used to handle DParameter objects in TreeView. + """ + def __init__(self, parent=None): + super(DParameterTreeModel, self).__init__(parent) + + def flags(self, index): + """ + This function returns the flags for a specific index. + + For Qt.ItemFlags see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemFlag-enum' + :param index: + :return: + """ + row = index.row() + col = index.column() + + parent = index.parent() + + # if not parent.isValid(): + # return ~Qt.ItemIsSelectable & ~Qt.ItemIsEditable + + if col == 0: + return Qt.ItemIsSelectable | Qt.ItemIsEnabled + + if col == 1: + return Qt.ItemIsEditable | Qt.ItemIsEnabled + + def data(self, index, role): + """ + For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum' + :param index: + :param role: + :return: + """ + + if not index.isValid(): + return None + + row = index.row() + col = index.column() + + if role == Qt.ToolTipRole: + return super(DParameterTreeModel, self).data(index, Qt.ToolTipRole) + + if role == Qt.DisplayRole: + + if col == 0: + dparameter = super(DParameterTreeModel, self).data(index, Qt.UserRole) + return dparameter.name + if col == 1: + index_sibling = index.sibling(row, col-1) + dparameter = super(DParameterTreeModel, self).data(index_sibling, Qt.UserRole) + return dparameter.value + + if role == Qt.DecorationRole: + if col == 0: + return super(DParameterTreeModel, self).data(index, Qt.DecorationRole) + if col == 1: + return QIcon.fromTheme("edit-clear") + + if role == Qt.UserRole: + return super(DParameterTreeModel, self).data(index, Qt.UserRole) + + return None + + def setData(self, index, value, role): + """ + This function is called when a content in a row is edited by the user. + + :param index: Current selected index. + :param value: New value from user + :param role: + :return: + """ + + if not index.isValid(): + return None + + row = index.row() + col = index.column() + + if role == Qt.EditRole: + if col == 1: + index_sibling = index.sibling(row, col-1) + dparameter = super(DParameterTreeModel, self).data(index_sibling, Qt.UserRole) + + if dparameter.regex is not None: + rx = QRegExp(dparameter.regex) + if rx.exactMatch(value): + dparameter.value = value + self.dataChanged.emit(index_sibling, None) + else: + dparameter.value = value + self.dataChanged.emit(index_sibling, None) + + return True + + return False + + +class DBlockTreeModel(PaPITreeModel): + def __init__(self, parent=None): + super(DBlockTreeModel, self).__init__(parent) + diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py new file mode 100644 index 00000000..25ef827b --- /dev/null +++ b/papi/gui/qt_new/main.py @@ -0,0 +1,446 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth, Stefan Ruppin +""" + +__author__ = 'knuths' + +import sys +import time +import os +import traceback + +from PySide.QtGui import QMainWindow, QApplication, QFileDialog +from PySide.QtGui import QIcon +from PySide.QtCore import QSize, Qt, QThread + +from papi.ui.gui.qt_new.main import Ui_QtNewMain +from papi.data.DGui import DGui +from papi.ConsoleLog import ConsoleLog + +from papi.constants import GUI_PAPI_WINDOW_TITLE, GUI_WOKRING_INTERVAL, GUI_PROCESS_CONSOLE_IDENTIFIER, \ + GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_START_CONSOLE_MESSAGE, GUI_WAIT_TILL_RELOAD, GUI_DEFAULT_HEIGHT, GUI_DEFAULT_WIDTH, \ + PLUGIN_STATE_PAUSE + +from papi.constants import CONFIG_DEFAULT_FILE, PLUGIN_VIP_IDENTIFIER, PLUGIN_PCP_IDENTIFIER, CONFIG_DEFAULT_DIRECTORY + +from papi.gui.gui_api import Gui_api +from papi.gui.gui_event_processing import GuiEventProcessing +import pyqtgraph as pg +from pyqtgraph import QtCore, QtGui + +from papi.gui.qt_new.create_plugin_menu import CreatePluginMenu +from papi.gui.qt_new.overview_menu import OverviewPluginMenu +import cProfile +import re + +# Enable antialiasing for prettier plots +pg.setConfigOptions(antialias=False) + + +class GUI(QMainWindow, Ui_QtNewMain): + + def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): + super(GUI, self).__init__(parent) + self.setupUi(self) + + if gui_data is None: + self.gui_data = DGui() + else: + self.gui_data = gui_data + + self.gui_api = Gui_api(self.gui_data, core_queue, gui_id) + + self.gui_event_processing = GuiEventProcessing(self.gui_data, core_queue, gui_id, gui_queue) + + self.gui_event_processing.added_dplugin.connect(self.add_dplugin) + self.gui_event_processing.removed_dplugin.connect(self.remove_dplugin) + self.gui_event_processing.dgui_changed.connect(self.changed_dgui) + self.gui_event_processing.plugin_died.connect(self.plugin_died) + + self.gui_api.resize_gui.connect(self.resize_gui_window) + + self.setWindowTitle(GUI_PAPI_WINDOW_TITLE) + + + self.gui_api.set_bg_gui.connect(self.update_background) + + # set GUI size + size = self.size() + self.gui_api.gui_size_height = size.height() + self.gui_api.gui_size_width = size.width() + + self.original_resize_function = self.resizeEvent + self.resizeEvent = self.user_window_resize + + self.setGeometry(self.geometry().x(),self.geometry().y(),GUI_DEFAULT_WIDTH,GUI_DEFAULT_HEIGHT) + + self.core_queue = core_queue + self.gui_queue = gui_queue + + self.gui_id = gui_id + + self.count = 0 + + self.log = ConsoleLog(GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_PROCESS_CONSOLE_IDENTIFIER) + + self.log.printText(1,GUI_START_CONSOLE_MESSAGE + ' .. Process id: '+str(os.getpid())) + + self.last_config = 'papi/last_active_papi.xml' + self.in_run_mode = False + + # ------------------------------------- + # Create placeholder + # ------------------------------------- + self.overview_menu = None + self.create_plugin_menu = None + # ------------------------------------- + # Create callback functions for buttons + # ------------------------------------- + self.loadButton.clicked.connect(self.load_triggered) + self.saveButton.clicked.connect(self.save_triggered_thread) + + # self.buttonCreatePlugin.clicked.connect(self.create_plugin) + # self.buttonCreateSubscription.clicked.connect(self.create_subscription) + # self.buttonCreatePCPSubscription.clicked.connect(self.create_pcp_subscription) + # self.buttonShowOverview.clicked.connect(self.ap_overview) + # self.buttonExit.clicked.connect(self.close) + + # ------------------------------------- + # Create actions + # ------------------------------------- + + self.actionLoad.triggered.connect(self.load_triggered) + self.actionSave.triggered.connect(self.save_triggered_thread) + + self.actionOverview.triggered.connect(self.show_overview_menu) + self.actionCreate.triggered.connect(self.show_create_plugin_menu) + + self.actionResetPaPI.triggered.connect(self.reset_papi) + self.actionReloadConfig.triggered.connect(self.reload_config) + + self.actionRunMode.triggered.connect(self.toggle_run_mode) + + self.actionSetBackground.triggered.connect(self.set_background_for_gui) + + # ------------------------------------- + # Create Icons for buttons + # ------------------------------------- + + load_icon = QIcon.fromTheme("document-open") + save_icon = QIcon.fromTheme("document-save") + + # addplugin_icon = QIcon.fromTheme("list-add") + # close_icon = QIcon.fromTheme("application-exit") + # overview_icon = QIcon.fromTheme("view-fullscreen") + # addsubscription_icon = QIcon.fromTheme("list-add") + + # ------------------------------------- + # Set Icons for buttons + # ------------------------------------- + + self.loadButton.setIconSize(QSize(30, 30)) + self.loadButton.setIcon(load_icon) + + self.saveButton.setIconSize(QSize(30, 30)) + self.saveButton.setIcon(save_icon) + + # self.buttonCreatePlugin.setIconSize(QSize(30, 30)) + # self.buttonCreatePlugin.setIcon(addplugin_icon) + # + # self.buttonExit.setIcon(close_icon) + # self.buttonExit.setIconSize(QSize(30, 30)) + # + # self.buttonShowOverview.setIcon(overview_icon) + # self.buttonShowOverview.setIconSize(QSize(30, 30)) + # + # self.buttonCreateSubscription.setIcon(addsubscription_icon) + # self.buttonCreateSubscription.setIconSize(QSize(30, 30)) + # + # self.buttonCreatePCPSubscription.setIcon(addsubscription_icon) + # self.buttonCreatePCPSubscription.setIconSize(QSize(30, 30)) + + # ------------------------------------- + # Set Tooltipps for buttons + # ------------------------------------- + + # self.buttonExit.setToolTip("Exit PaPI") + # self.buttonCreatePlugin.setToolTip("Add New Plugin") + # self.buttonCreateSubscription.setToolTip("Create New Subscription") + # self.buttonCreatePCPSubscription.setToolTip("Create New PCP Subscription") + # + # self.buttonShowOverview.setToolTip("Show Overview") + + # ------------------------------------- + # Set TextName to '' + # ------------------------------------- + + # self.buttonExit.setText('') + # self.buttonCreatePlugin.setText('') + # self.buttonCreateSubscription.setText('') + # self.buttonShowLicence.setText('') + # self.buttonShowOverview.setText('') + + def set_background_for_gui(self): + fileNames = '' + + dialog = QFileDialog(self) + dialog.setFileMode(QFileDialog.AnyFile) + #dialog.setNameFilter( self.tr("PaPI-Cfg (*.xml)")) + dialog.setDirectory(CONFIG_DEFAULT_DIRECTORY) + + if dialog.exec_(): + fileNames = dialog.selectedFiles() + + if len(fileNames): + if fileNames[0] != '': + path = fileNames[0] + pixmap = QtGui.QPixmap(path) + self.gui_api.gui_bg_path = path + self.widgetArea.setBackground(pixmap) + + def update_background(self, path): + pixmap = QtGui.QPixmap(path) + self.widgetArea.setBackground(pixmap) + + + def run(self): + # create a timer and set interval for processing events with working loop + + QtCore.QTimer.singleShot(GUI_WOKRING_INTERVAL, lambda: self.gui_event_processing.gui_working(self.closeEvent)) + + + def dbg(self): + print("Action") + + def menu_license(self): + pass + + def menu_quit(self): + pass + + def show_create_plugin_menu(self): + self.create_plugin_menu = CreatePluginMenu(self.gui_api) + + self.create_plugin_menu.show() + # self.create_plugin_menu.raise_() + # self.create_plugin_menu.activateWindow() + + # del self.create_plugin_menu + # + # self.create_plugin_menu = None + + def show_overview_menu(self): + self.overview_menu = OverviewPluginMenu(self.gui_api) + self.overview_menu.show() + + def load_triggered(self): + + fileNames = '' + + dialog = QFileDialog(self) + dialog.setFileMode(QFileDialog.AnyFile) + dialog.setNameFilter( self.tr("PaPI-Cfg (*.xml)")) + dialog.setDirectory(CONFIG_DEFAULT_DIRECTORY) + + if dialog.exec_(): + fileNames = dialog.selectedFiles() + + if len(fileNames): + if fileNames[0] != '': + self.last_config = fileNames[0] + self.gui_api.do_load_xml(fileNames[0]) + + def save_triggered(self): + + fileNames = '' + + dialog = QFileDialog(self) + dialog.setFileMode(QFileDialog.AnyFile) + dialog.setNameFilter( self.tr("PaPI-Cfg (*.xml)")) + dialog.setDirectory(CONFIG_DEFAULT_DIRECTORY) + + if dialog.exec_(): + fileNames = dialog.selectedFiles() + + if len(fileNames): + if fileNames[0] != '': + self.gui_api.do_save_xml_config(fileNames[0]) + + def save_triggered_thread(self): + QtCore.QTimer.singleShot(0, self.save_triggered) + + def closeEvent(self, *args, **kwargs): + try: + self.gui_api.do_save_xml_config('papi/last_active_papi.xml') + except Exception as E: + tb = traceback.format_exc() + + self.gui_api.do_close_program() + if self.create_plugin_menu is not None: + self.create_plugin_menu.close() + + if self.overview_menu is not None: + self.overview_menu.close() + + self.close() + + def add_dplugin(self, dplugin): + + if dplugin.type == PLUGIN_VIP_IDENTIFIER or dplugin.type == PLUGIN_PCP_IDENTIFIER: + sub_window = dplugin.plugin.get_sub_window() + self.widgetArea.addSubWindow(sub_window) + sub_window.show() + size_re = re.compile(r'([0-9]+)') + config = dplugin.startup_config + pos = config['position']['value'] + window_pos = size_re.findall(pos) + sub_window.move(int(window_pos[0]), int(window_pos[1])) + + # see http://qt-project.org/doc/qt-4.8/qt.html#WindowType-enum + + sub_window.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowMinMaxButtonsHint | Qt.WindowTitleHint ) + + if self.overview_menu is not None: + self.overview_menu.refresh_action(dplugin) + + def remove_dplugin(self, dplugin): + if dplugin.type == PLUGIN_VIP_IDENTIFIER or dplugin.type == PLUGIN_PCP_IDENTIFIER: + self.widgetArea.removeSubWindow(dplugin.plugin.get_sub_window()) + + def changed_dgui(self): + if self.overview_menu is not None: + self.overview_menu.refresh_action() + + def plugin_died(self, dplugin, e, msg): + + dplugin.state = PLUGIN_STATE_PAUSE + + self.gui_api.do_stopReset_plugin_uname(dplugin.uname) + + errMsg = QtGui.QErrorMessage(self) + errMsg.setFixedWidth(650) + errMsg.setWindowTitle("Error in" + dplugin.uname + " // " + str(e)) + errMsg.showMessage(str(msg)) + + def toggle_run_mode(self): + if self.in_run_mode: + self.in_run_mode = False + self.loadButton.show() + self.saveButton.show() + self.menubar.setHidden(False) + + elif not self.in_run_mode: + self.in_run_mode = True + + self.loadButton.hide() + self.saveButton.hide() + self.menubar.hide() + + def keyPressEvent(self, event): + if event.key() == Qt.Key_Escape: + if self.in_run_mode: + self.toggle_run_mode() + + def resize_gui_window(self, w, h): + + self.setGeometry(self.geometry().x(),self.geometry().y(),w,h) + size = self.size() + self.gui_api.gui_size_height = size.height() + self.gui_api.gui_size_width = size.width() + + def user_window_resize(self, event): + size = event.size() + self.gui_api.gui_size_width = size.width() + self.gui_api.gui_size_height = size.height() + self.original_resize_function(event) + + + def reload_config(self): + """ + This function is used to reset PaPI and to reload the last loaded configuration file. + :return: + """ + if self.last_config is not None: + self.reset_papi() + QtCore.QTimer.singleShot(GUI_WAIT_TILL_RELOAD, lambda: self.gui_api.do_load_xml(self.last_config)) + + def reset_papi(self): + """ + This function is called to reset PaPI. That means all subscriptions were canceled and all plugins were removed. + :return: + """ + h = GUI_DEFAULT_HEIGHT + w = GUI_DEFAULT_WIDTH + self.setGeometry(self.geometry().x(),self.geometry().y(),w,h) + self.gui_api.do_reset_papi() + +def startGUI(CoreQueue, GUIQueue,gui_id): + """ + Function to call to start gui operation + :param CoreQueue: link to queue of core + :type CoreQueue: Queue + :param GUIQueue: queue where gui receives messages + :type GUIQueue: Queue + :param gui_id: id of gui for events + :type gui_id: int + :return: + """ + app = QApplication(sys.argv) + gui = GUI(CoreQueue, GUIQueue,gui_id) + gui.run() + +# cProfile.runctx('gui.run()', globals(), locals()) + + gui.show() + app.exec_() + +def startGUI_TESTMOCK(CoreQueue, GUIQueue,gui_id, data_mock): + """ + Function to call to start gui operation + :param CoreQueue: link to queue of core + :type CoreQueue: Queue + :param GUIQueue: queue where gui receives messages + :type GUIQueue: Queue + :param gui_id: id of gui for events + :type gui_id: int + :return: + """ + app = QApplication(sys.argv) + app.aboutToQuit.connect(app.deleteLater) + + gui = GUI(CoreQueue, GUIQueue,gui_id, data_mock) + + gui.run() + gui.show() + app.exec_() + +if __name__ == '__main__': + # main of GUI, just for stand alone gui testing + app = QApplication(sys.argv) + frame = GUI(None,None,None) + frame.show() + app.exec_() diff --git a/papi/gui/qt_new/overview_menu.py b/papi/gui/qt_new/overview_menu.py new file mode 100644 index 00000000..2e32eef3 --- /dev/null +++ b/papi/gui/qt_new/overview_menu.py @@ -0,0 +1,731 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.gui.qt_new.item import PaPITreeItem, PaPIRootItem, PaPITreeModel +from papi.gui.qt_new.item import DPluginTreeModel, DParameterTreeModel, DBlockTreeModel +from papi.gui.qt_new.item import DPluginTreeItem, DBlockTreeItem, DParameterTreeItem, DSignalTreeItem + +from papi.ui.gui.qt_new.overview import Ui_Overview + +from PySide.QtGui import QMainWindow, QStandardItem, QMenu, QAbstractItemView, QAction, QStandardItemModel +from papi.constants import PLUGIN_PCP_IDENTIFIER, PLUGIN_DPP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER, PLUGIN_IOP_IDENTIFIER, \ + PLUGIN_STATE_DEAD, PLUGIN_STATE_STOPPED, PLUGIN_STATE_PAUSE, PLUGIN_STATE_RESUMED, PLUGIN_STATE_START_SUCCESFUL + +from PySide.QtCore import * +from PySide.QtGui import QLineEdit + +from papi.data.DPlugin import DPlugin, DBlock, DParameter + + +class OverviewPluginMenu(QMainWindow, Ui_Overview): + """ + This class is used to create an extra window which is used to display all created plugins. + The information are taken by the corresponding DPlugin-Object of a plugin. By this window a user is able to + create and cancel subscriptions. + """ + + def __init__(self, gui_api, parent=None): + super(OverviewPluginMenu, self).__init__(parent) + self.setupUi(self) + self.dgui = gui_api.gui_data + + self.gui_api = gui_api + + self.setWindowTitle("OverviewMenu") + + # ---------------------------------- + # Build structure of plugin tree + # ---------------------------------- + + self.dpluginModel = DPluginTreeModel() + self.dpluginModel.setHorizontalHeaderLabels(['Name']) + + self.pluginTree.setModel(self.dpluginModel) + self.pluginTree.setUniformRowHeights(True) + + self.visual_root = PaPIRootItem('ViP') + self.io_root = PaPIRootItem('IOP') + self.dpp_root = PaPIRootItem('DPP') + self.pcp_root = PaPIRootItem('PCP') + + self.dpluginModel.appendRow(self.visual_root) + self.dpluginModel.appendRow(self.io_root) + self.dpluginModel.appendRow(self.dpp_root) + self.dpluginModel.appendRow(self.pcp_root) + + # ----------------------------------- + # Build structure of parameter tree + # ----------------------------------- + + self.dparameterModel = DParameterTreeModel() + self.dparameterModel.setHorizontalHeaderLabels(['Name']) + self.parameterTree.setModel(self.dparameterModel) + self.parameterTree.setUniformRowHeights(True) + self.dparameterModel.dataChanged.connect(self.data_changed_parameter_model) + + # ----------------------------------- + # Build structure of block tree + # ----------------------------------- + + self.bModel = DBlockTreeModel() + self.bModel.setHorizontalHeaderLabels(['Name']) + self.blockTree.setModel(self.bModel) + self.blockTree.setUniformRowHeights(True) + + + # ----------------------------------- + # Build structure of subscriber tree + # ----------------------------------- + + self.subscriberModel = PaPITreeModel() + self.subscriberModel.setHorizontalHeaderLabels(['Subscriber']) + self.subscribersTree.setModel(self.subscriberModel) + self.subscribersTree.setUniformRowHeights(True) + + # ----------------------------------- + # Build structure of subscriptions tree + # ----------------------------------- + + self.subscriptionModel = PaPITreeModel() + self.subscriptionModel.setHorizontalHeaderLabels(['Subscription']) + self.subscriptionsTree.setModel(self.subscriptionModel) + self.subscriptionsTree.setUniformRowHeights(True) + + # ----------------------------------- + # signal/slots + # ----------------------------------- + self.playButton.clicked.connect(self.play_button_callback) + self.pauseButton.clicked.connect(self.pause_button_callback) + self.stopButton.clicked.connect(self.stop_start_button_callback) + self.pluginTree.clicked.connect(self.plugin_item_changed) + + # ---------------------------------- + # Add context menu + # ---------------------------------- + self.pluginTree.setContextMenuPolicy(Qt.CustomContextMenu) + self.pluginTree.customContextMenuRequested.connect(self.open_context_menu_dplugin_tree) + + self.blockTree.setContextMenuPolicy(Qt.CustomContextMenu) + self.blockTree.customContextMenuRequested.connect(self.open_context_menu_block_tree) + + self.parameterTree.setContextMenuPolicy(Qt.CustomContextMenu) + self.parameterTree.customContextMenuRequested.connect(self.open_context_menu_parameter_tree) + + self.subscribersTree.setContextMenuPolicy(Qt.CustomContextMenu) + self.subscribersTree.customContextMenuRequested.connect(self.open_context_menu_subscriber_tree) + + self.subscriptionsTree.setContextMenuPolicy(Qt.CustomContextMenu) + self.subscriptionsTree.customContextMenuRequested.connect(self.open_context_menu_subscription_tree) + + # ---------------------------------- + # Add Actions + # ---------------------------------- + self.actionRefresh.triggered.connect(self.refresh_action) + + self.clear() + + def clear(self): + """ + This function will clear this window. + :return: + """ + self.bModel.clear() + self.dparameterModel.clear() + self.subscriberModel.clear() + self.subscriptionModel.clear() + self.unameEdit.setText('') + self.usedpluginEdit.setText('') + self.stateEdit.setText('') + self.typeEdit.setText('') + self.alivestateEdit.setText('') + + self.bModel.setHorizontalHeaderLabels(['Name']) + self.dparameterModel.setHorizontalHeaderLabels(['Name', 'Value']) + self.subscriberModel.setHorizontalHeaderLabels(['Subscriber']) + self.subscriptionModel.setHorizontalHeaderLabels(['Subscription']) + + def plugin_item_changed(self, index): + """ + Used to display all known information for a DPlugin which is + accessible in the pluginTree by its index. + :param index: Current selected index + :return: + """ + + dplugin = self.pluginTree.model().data(index, Qt.UserRole) + self.clear() + + if dplugin is None: + self.tabWidget.setDisabled(True) + return + self.tabWidget.setDisabled(False) + + # ------------------------------------ + # Get all needed dplugin information + # ------------------------------------ + + self.unameEdit.setText(dplugin.uname) + self.usedpluginEdit.setText(dplugin.plugin_identifier) + self.stateEdit.setText(dplugin.state) + self.typeEdit.setText(dplugin.type) + self.alivestateEdit.setText(dplugin.alive_state) + + if dplugin.type != PLUGIN_PCP_IDENTIFIER: + self.pauseButton.setDisabled(False) + self.playButton.setDisabled(False) + self.stopButton.setDisabled(False) + else: + self.pauseButton.setDisabled(True) + self.playButton.setDisabled(True) + self.stopButton.setDisabled(True) + + if dplugin.alive_state != PLUGIN_STATE_DEAD: + if dplugin.state == PLUGIN_STATE_PAUSE: + self.pauseButton.setDisabled(True) + if dplugin.state == PLUGIN_STATE_STOPPED: + self.pauseButton.setDisabled(True) + self.playButton.setDisabled(True) + self.stopButton.setText('START') + if dplugin.state == PLUGIN_STATE_RESUMED: + self.playButton.setDisabled(True) + if dplugin.state == PLUGIN_STATE_START_SUCCESFUL: + self.playButton.setDisabled(True) + self.stopButton.setText('STOP') + + # --------------------------- + # Add DBlocks + # --------------------------- + + dblock_ids = dplugin.get_dblocks() + + for dblock_id in dblock_ids: + dblock = dblock_ids[dblock_id] + + block_item = DBlockTreeItem(dblock) + self.bModel.appendRow(block_item) + + # ------------------------- + # Add Signals of this DBlock + # ------------------------- + + for signal in dblock.get_signals(): + signal_item = DSignalTreeItem(signal) + block_item.appendRow(signal_item) + + block_item.sortChildren(0) + + # ---------------------------------- + # Add Subscribers of this DBlock + # ---------------------------------- + + subscriber_ids = dblock.get_subscribers() + + for subscriber_id in subscriber_ids: + subscriber = self.dgui.get_dplugin_by_id(subscriber_id) + subscriber_item = DPluginTreeItem(subscriber) + + block_item = DBlockTreeItem(dblock) + + subscriber_item.appendRow(block_item) + + self.subscriberModel.appendRow(subscriber_item) + + + # ------------------------- + # Add all Subscriptions + # for this plugin + # ------------------------- + + dplugin_sub_ids = dplugin.get_subscribtions() + + for dplugin_sub_id in dplugin_sub_ids: + + + dblock_names = dplugin_sub_ids[dplugin_sub_id] + + dplugin_sub = self.gui_api.gui_data.get_dplugin_by_id(dplugin_sub_id) + dplugin_sub_item = DPluginTreeItem(dplugin_sub) + self.subscriptionModel.appendRow(dplugin_sub_item) + + for dblock_name in dblock_names: + + dblock_sub = dplugin_sub.get_dblock_by_name(dblock_name) + dblock_sub_item = DBlockTreeItem(dblock_sub) + dplugin_sub_item.appendRow(dblock_sub_item) + + subscription = dblock_names[dblock_name] + + for signal_uname in subscription.get_signals(): + + signal_item = QStandardItem(signal_uname) + + dblock_sub_item.appendRow(signal_item) + + # -------------------------- + # Add DParameters + # -------------------------- + + dparameter_names = dplugin.get_parameters() + for dparameter_name in sorted(dparameter_names): + dparameter = dparameter_names[dparameter_name] + dparameter_item = DParameterTreeItem(dparameter) + self.dparameterModel.appendRow(dparameter_item) + + self.parameterTree.resizeColumnToContents(0) + self.parameterTree.resizeColumnToContents(1) + + self.blockTree.expandAll() + self.parameterTree.expandAll() + + # http://srinikom.github.io/pyside-docs/PySide/QtGui/QAbstractItemView.html \ + # #PySide.QtGui.PySide.QtGui.QAbstractItemView.SelectionMode + self.blockTree.setSelectionMode(QAbstractItemView.ExtendedSelection) + + # Sort Models + self.bModel.sort(0) + + # noinspection PyUnresolvedReferences + def open_context_menu_dplugin_tree(self, position): + """ + This callback function is called to create a context menu + for the dplugin tree + :param position: + :return: + """ + index = self.pluginTree.indexAt(position) + + if index.isValid() is False: + return None + + if self.pluginTree.isIndexHidden(index): + return + + dplugin = self.pluginTree.model().data(index, Qt.UserRole) + + menu = QMenu('Remove') + + action = QAction('Remove DPlugin', self) + menu.addAction(action) + + action.triggered.connect(lambda p=dplugin.id: self.gui_api.do_delete_plugin(p)) + + menu.exec_(self.pluginTree.viewport().mapToGlobal(position)) + + def open_context_menu_block_tree(self, position): + """ + This callback function is called to create a context menu + for the block tree + :param position: + :return: + """ + + index = self.blockTree.indexAt(position) + + if index.isValid() is False: + return None + + if self.blockTree.isIndexHidden(index): + return + + item = self.blockTree.model().data(index, Qt.UserRole) + + if isinstance(item, DPlugin) or isinstance(item, DBlock): + return + + index_sel = self.pluginTree.currentIndex() + dplugin_sel = self.pluginTree.model().data(index_sel, Qt.UserRole) + + if dplugin_sel is not None: + + sub_menu = QMenu('Add Subscription') + dplugin_ids = self.dgui.get_all_plugins() + + for dplugin_id in dplugin_ids: + dplugin = dplugin_ids[dplugin_id] + + if dplugin_sel.id != dplugin_id: + action = QAction(self.tr(dplugin.uname), self) + sub_menu.addAction(action) + action.triggered.connect(lambda p=dplugin.uname: self.add_subscription_action(p)) + + menu = QMenu() + menu.addMenu(sub_menu) + + menu.exec_(self.blockTree.viewport().mapToGlobal(position)) + + def open_context_menu_subscriber_tree(self, position): + """ + This callback function is called to create a context menu + for the subscriper tree + :param position: + :return: + """ + index = self.subscribersTree.indexAt(position) + + if index.isValid() is False: + return None + + if index.parent().isValid() is False: + return None + + if self.subscribersTree.isIndexHidden(index): + return + + dblock = self.subscribersTree.model().data(index, Qt.UserRole) + dplugin = self.subscribersTree.model().data(index.parent(), Qt.UserRole) + + menu = QMenu('Remove') + + action = QAction('Remove Subscriber', self) + menu.addAction(action) + + action.triggered.connect(lambda p=dblock, m=dplugin: self.remove_subscriber_action(m, p)) + + menu.exec_(self.subscribersTree.viewport().mapToGlobal(position)) + + def open_context_menu_subscription_tree(self, position): + """ + This callback function is called to create a context menu + for the subscription tree + :param position: + :return: + """ + index = self.subscriptionsTree.indexAt(position) + isSignal = False + menu = None + action = None + signals = [] + + # ---------------------------------- + # Open no context menu if invalid + # ---------------------------------- + + if index.isValid() is False: + return None + + # ---------------------------------- + # Open no context menu for parent + # ---------------------------------- + + if index.parent().isValid() is False: + return None + + # ---------------------------------- + # Open no context menu for hidden objects + # ---------------------------------- + + if self.subscriptionsTree.isIndexHidden(index): + return None + + # ---------------------------------- + # Open no context menu for signals + # ---------------------------------- + + if not index.child(0, 0).isValid(): + isSignal = True + + # ---------------------------------- + # Get necessary objects for this subscription + # ---------------------------------- + + if not isSignal: + dblock = self.subscriptionsTree.model().data(index, Qt.UserRole) + dplugin = self.subscriptionsTree.model().data(index.parent(), Qt.UserRole) + action = QAction('Remove Subscriber', self) + else: + signal_uname = self.subscriptionsTree.model().data(index, Qt.DisplayRole) + dblock = self.subscriptionsTree.model().data(index.parent(), Qt.UserRole) + dplugin = self.subscriptionsTree.model().data(index.parent().parent(), Qt.UserRole) + + action = QAction('Remove Signal -> ' + signal_uname, self) + signals.append(signal_uname) + + # ---------------------------------- + # Create context menu + # ---------------------------------- + + menu = QMenu() + menu.addAction(action) + + action.triggered.connect(lambda p=dblock, m=dplugin, s=signals: self.cancel_subscription_action(m, p, s)) + + menu.exec_(self.subscriptionsTree.viewport().mapToGlobal(position)) + + def open_context_menu_parameter_tree(self, position): + """ + This callback function is called to create a context menu + for the parameter tree + :param position: + :return: + """ + index = self.parameterTree.indexAt(position) + + if index.isValid() is False: + return None + + if self.parameterTree.isIndexHidden(index): + return + + dparameter = self.parameterTree.model().data(index, Qt.UserRole) + dplugin = self.pluginTree.model().data(self.pluginTree.currentIndex(), Qt.UserRole) + + sub_menu = QMenu('Control by') + + dplugin_ids = self.dgui.get_all_plugins() + + for dplugin_id in dplugin_ids: + dplugin_pcp = dplugin_ids[dplugin_id] + + if dplugin_pcp.type == PLUGIN_PCP_IDENTIFIER: + # action = QAction(self.tr(dplugin.uname), self) + # sub_menu.addAction(action) + + pcp_menu = QMenu(self.tr(dplugin_pcp.uname)) + sub_menu.addMenu(pcp_menu) + + dblock_pcp_ids = dplugin_pcp.get_dblocks() + + for dblock_pcp_id in dblock_pcp_ids: + dblock_pcp = dblock_pcp_ids[dblock_pcp_id] + action = QAction(self.tr(dblock_pcp.name), self) + pcp_menu.addAction(action) + + action.triggered.connect(lambda p1=dplugin, p2=dparameter, p3=dplugin_pcp, p4=dblock_pcp: + self.add_pcp_subscription_action(p1, p2, p3, p4)) + + menu = QMenu() + menu.addMenu(sub_menu) + + menu.exec_(self.parameterTree.viewport().mapToGlobal(position)) + + def add_pcp_subscription_action(self, dplugin: DPlugin, dparameter: DParameter, dplugin_pcp: DPlugin, + dblock_pcp: DBlock): + """ + This function is used to create a subscription for a process control plugin. + :param dplugin: Subscriber of a pcp plugin + :param dparameter: Parameter of the subscriber which should be controlled by the pcp plugin. + :param dplugin_pcp: The pcp plugin + :param dblock_pcp: Block of the pcp plugin which is used to control the subscriber's parameter. + :return: + """ + + self.gui_api.do_subscribe(dplugin.id, dplugin_pcp.id, dblock_pcp.name, [], dparameter.name) + pass + + def add_subscription_action(self, dplugin_uname): + """ + + :param dplugin_uname: + :return: + """ + + signals = [] + + dplugin = self.gui_api.gui_data.get_dplugin_by_uname(dplugin_uname) + + indexes = self.blockTree.selectedIndexes() + + for index in indexes: + if index.isValid(): + signal = self.blockTree.model().data(index, Qt.UserRole) + signals.append(signal.uname) + + index_dblock = index.parent() + + dblock = self.blockTree.model().data(index_dblock, Qt.UserRole) + + index = self.pluginTree.currentIndex() + + dplugin_source = self.pluginTree.model().data(index, Qt.UserRole) + + + self.gui_api.do_subscribe(dplugin.id, dplugin_source.id, dblock.name, signals) + + def remove_subscriber_action(self, subscriber: DPlugin, dblock: DBlock): + """ + + :param subscriber: + :param dblock: + :return: + """ + index = self.pluginTree.currentIndex() + + source = self.pluginTree.model().data(index, Qt.UserRole) + + self.gui_api.do_unsubscribe_uname(subscriber.uname, source.uname, dblock.name, []) + + def refresh_action(self, new_dplugin: DPlugin=None): + """ + Used to refresh the overview menu view. + :param new_dplugin: New dplugin which should be added in self.dpluginTreev. + :return: + """ + + # ----------------------------------------- + # case: no DPlugin was added or removed + # e.g. parameter was changed + # ----------------------------------------- + + index = self.pluginTree.currentIndex() + self.pluginTree.clicked.emit(index) + + # ----------------------------------------- + # case: remove already deleted plugins + # ----------------------------------------- + + self.visual_root.clean() + self.dpp_root.clean() + self.io_root.clean() + self.pcp_root.clean() + + # ----------------------------------------- + # case: a DPlugin was added + # ----------------------------------------- + + if new_dplugin is not None: + plugin_item = DPluginTreeItem(new_dplugin) + if new_dplugin.type == PLUGIN_VIP_IDENTIFIER: + if not self.visual_root.hasItem(new_dplugin): + self.visual_root.appendRow(plugin_item) + if new_dplugin.type == PLUGIN_IOP_IDENTIFIER: + if not self.io_root.hasItem(new_dplugin): +# plugin_item = DPluginTreeItem(new_dplugin) + self.io_root.appendRow(plugin_item) + if new_dplugin.type == PLUGIN_DPP_IDENTIFIER: + if not self.dpp_root.hasItem(new_dplugin): + self.dpp_root.appendRow(plugin_item) + if new_dplugin.type == PLUGIN_PCP_IDENTIFIER: + if not self.pcp_root.hasItem(new_dplugin): + self.pcp_root.appendRow(plugin_item) + + def cancel_subscription_action(self, source: DPlugin, dblock: DBlock, signals: []): + """ + Action called to cancel a subscription of the current selected dplugin. + :param source: + :param dblock: + :return: + """ + index = self.pluginTree.currentIndex() + subscriber = self.pluginTree.model().data(index, Qt.UserRole) + + self.gui_api.do_unsubscribe_uname(subscriber.uname, source.uname, dblock.name, signals) + + def showEvent(self, *args, **kwargs): + """ + ShowEvent of this class. + :param args: + :param kwargs: + :return: + """ + dplugin_ids = self.dgui.get_all_plugins() + + for dplugin_id in dplugin_ids: + dplugin = dplugin_ids[dplugin_id] + # ------------------------------ + # Sort DPluginItem in TreeWidget + # ------------------------------ + plugin_item = DPluginTreeItem(dplugin) + + if dplugin.type == PLUGIN_VIP_IDENTIFIER: + self.visual_root.appendRow(plugin_item) + if dplugin.type == PLUGIN_IOP_IDENTIFIER: + self.io_root.appendRow(plugin_item) + if dplugin.type == PLUGIN_DPP_IDENTIFIER: + self.dpp_root.appendRow(plugin_item) + if dplugin.type == PLUGIN_PCP_IDENTIFIER: + self.pcp_root.appendRow(plugin_item) + + def play_button_callback(self): + """ + Callback function for the play button. + :return: + """ + index = self.pluginTree.currentIndex() + item = self.pluginTree.model().data(index, Qt.UserRole) + if item is not None: + self.gui_api.do_resume_plugin_by_id(item.id) + self.playButton.setDisabled(True) + self.pauseButton.setDisabled(False) + self.stopButton.setDisabled(False) + + def pause_button_callback(self): + """ + Function pause_button_callback + + :return: + """ + index = self.pluginTree.currentIndex() + item = self.pluginTree.model().data(index, Qt.UserRole) + if item is not None: + self.pauseButton.setDisabled(True) + self.playButton.setDisabled(False) + self.gui_api.do_pause_plugin_by_id(item.id) + + def stop_start_button_callback(self): + """ + Function stop_start_button_callback + + :return: + """ + index = self.pluginTree.currentIndex() + item = self.pluginTree.model().data(index, Qt.UserRole) + if item is not None: + if self.stopButton.text() == 'STOP': + self.gui_api.do_stopReset_pluign(item.id) + self.stopButton.setText('START') + self.pauseButton.setDisabled(True) + self.playButton.setDisabled(True) + self.stopButton.setDisabled(False) + + else: + self.gui_api.do_start_plugin(item.id) + self.stopButton.setText('STOP') + self.pauseButton.setDisabled(False) + self.playButton.setDisabled(False) + self.stopButton.setDisabled(False) + + def data_changed_parameter_model(self, index, n): + """ + This function is called when a dparameter value is changed by editing the 'value'-column. + :param index: Index of current changed dparameter + :param n: None + :return: + """ + + dparameter = self.parameterTree.model().data(index, Qt.UserRole) + index = self.pluginTree.currentIndex() + + dplugin = self.pluginTree.model().data(index, Qt.UserRole) + + self.gui_api.do_set_parameter(dplugin.id, dparameter.name, dparameter.value) + + def keyPressEvent(self, event): + if event.key() == Qt.Key_Escape: + self.close() \ No newline at end of file diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml new file mode 100644 index 00000000..2c6cf544 --- /dev/null +++ b/papi/last_active_papi.xml @@ -0,0 +1,295 @@ + + + + + ORTD_UDP + + + ORTDXUDP + + + 127.0.0.1 + 1 + + + 20001 + 1 + + + 20000 + 1 + + + /home/control/PycharmProjects/PaPI/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json + file + 0 + + + 0 + 1 + + + + 0 + 0 + 1 + 0 + 0 + 9 + 0 + 0 + 0 + 50 + 0 + 10 + + + + Button + + Oscillator input + + + + + + Plot + + + VisualPlugin + Used display name + + + 0 + bool + ^(1|0)$ + Grid-X + + + (0,0) + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + 1 + + + [0 0 0 0 0] + ^\[(\s*\d\s*)+\] + Style + 1 + + + 1 + bool + ^(1|0)$ + Rolling Plot + + + Plot + + + 50 + ^([1-9][0-9]{0,3}|10000)$ + Buffersize + 1 + + + [0 1 2 3 4] + ^\[(\s*\d\s*)+\] + Color + 1 + + + amplitude, V + \w+,\s+\w+ + Label-Y + + + 0 + bool + ^(1|0)$ + Grid-Y + + + 1 + (\d+) + + + time, s + \w+,\s*\w+ + Label-X + + + (311,696) + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + 1 + + + + 0 + 0 + 50 + [0 1 2 3 4] + 0 + 1 + [0 0 0 0 0] + + + + ORTDXUDP + + + 15 + 16 + 18 + HalloWelt14 + Sig2nal + Sign12al + Sign3al + Signal1 + Signal10 + Signal11 + Signal19 + Signal20 + Signal21 + Signal22 + Signal5 + Signal6 + Signal7 + Signal8 + Signal9 + Signal_13 + Test17 + _Sig4nal + + + + + + Plot + + + VisualPlugin + Used display name + + + 0 + bool + ^(1|0)$ + Grid-X + + + (320,0) + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + 1 + + + [0 0 0 0 0] + ^\[(\s*\d\s*)+\] + Style + 1 + + + 1 + bool + ^(1|0)$ + Rolling Plot + + + PlotX2 + + + 200 + ^([1-9][0-9]{0,3}|10000)$ + Buffersize + 1 + + + [0 1 2 3 4] + ^\[(\s*\d\s*)+\] + Color + 1 + + + amplitude, V + \w+,\s+\w+ + Label-Y + + + 0 + bool + ^(1|0)$ + Grid-Y + + + 1 + (\d+) + + + time, s + \w+,\s*\w+ + Label-X + + + (300,300) + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + 1 + + + + 0 + 0 + 200 + [0 1 2 3 4] + 0 + 1 + [0 0 0 0 0] + + + + ORTDXUDP + + + V + X + + + + + + Button + + + Button + 0 + + + 0.5 + 0 + + + (127,58) + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + 1 + + + (311,305) + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + 1 + + + Button + + + 0 + 0 + + + + + + diff --git a/papi/main.py b/papi/main.py new file mode 100644 index 00000000..c3f00f5b --- /dev/null +++ b/papi/main.py @@ -0,0 +1,43 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'control' + +import sys +from papi.core import Core +from papi.gui.qt_dev.gui_main import startGUI as dev_startGui +from papi.gui.qt_new.main import startGUI as new_startGui + + +def main(): + core = Core(new_startGui) + core.run() + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/papi/plugin/__init__.py b/papi/plugin/__init__.py new file mode 100644 index 00000000..e87a6f2b --- /dev/null +++ b/papi/plugin/__init__.py @@ -0,0 +1 @@ +__author__ = 'Knuth' \ No newline at end of file diff --git a/papi/plugin/base_classes/__init__.py b/papi/plugin/base_classes/__init__.py new file mode 100644 index 00000000..8b866fde --- /dev/null +++ b/papi/plugin/base_classes/__init__.py @@ -0,0 +1 @@ +__author__ = 'stefan' diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py new file mode 100644 index 00000000..cf0619ae --- /dev/null +++ b/papi/plugin/base_classes/base_plugin.py @@ -0,0 +1,186 @@ +#!/usr/bin/python3 +#-*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +from yapsy.IPlugin import IPlugin +from papi.data.DPlugin import DBlock +import papi.event as Event +from papi.exceptions.block_exceptions import Wrong_type, Wrong_length +from papi.data.DOptionalData import DOptionalData + + + +class base_plugin(IPlugin): + + + def papi_init(self): + #self.__dplugin_ids__ = {} Not sure where needed TODO + self.dplugin_info = None + + + + + def get_type(self): + raise NotImplementedError("Please Implement this method") + + def execute(self, Data=None, block_name = None): + raise NotImplementedError("Please Implement this method") + + def get_configuration_base(self): + raise NotImplementedError("Please Implement this method") + + def get_startup_configuration(self): + return self.merge_configs(self.get_configuration_base(), self.get_plugin_configuration()) + + def get_plugin_configuration(self): + raise NotImplementedError("Please Implement this method") + + + + + + # some control callback functions + # ---------------------- + def pause(self): + raise NotImplementedError("Please Implement this method") + + def resume(self): + raise NotImplementedError("Please Implement this method") + + def quit(self): + raise NotImplementedError("Please Implement this method") + + def set_parameter_internal(self, name, value): + self.set_parameter(name, value) + + + # some api functions + # ------------------ + def merge_configs(self, cfg1, cfg2): + return dict(list(cfg1.items()) + list(cfg2.items()) ) + + def evaluate_event_trigger(self,default): + if self.user_event_triggered == 'default': + self.EventTriggered = default + if self.user_event_triggered is True: + self.EventTriggered = True + if self.user_event_triggered is False: + self.EventTriggered = False + + def set_event_trigger_mode(self, mode): + if mode is True or mode is False or mode == 'default': + self.user_event_triggered = mode + + + def send_parameter_change(self, data, block_name, alias): + opt = DOptionalData(DATA=data) + opt.data_source_id = self.__id__ + opt.is_parameter = True + opt.block_name = block_name + opt.parameter_alias = alias + event = Event.data.NewData(self.__id__, 0, opt) + self._Core_event_queue__.put(event) + + def create_new_block(self, name, signalNames, types, frequency): + if isinstance(signalNames, list) is not True: + raise Wrong_type('signalNames') + + if isinstance(types, list) is not True: + raise Wrong_type('types') + + if len(signalNames) != len(types): + raise Wrong_length('signalNames', 'types') + + count = len(signalNames) + + return DBlock(self.__id__, count, frequency, name, signal_names_internal=signalNames, signal_types=types ) + + def send_new_data(self, block_name, time_line, data): + + dataHash = data + dataHash['t'] = time_line + opt = DOptionalData(DATA = dataHash) + opt.data_source_id = self.__id__ + opt.block_name = block_name + + event = Event.data.NewData(self.__id__, 0, opt) + self._Core_event_queue__.put(event) + + + def send_new_data_old2(self, time_line, data, block_name): + # TODO: known limitation, signal count of data+timeline HAVE TO match len of names defined in DBlock of block_name + vec_data = [] + vec_data.append(time_line) + for item in data: + vec_data.append(item) + + opt = DOptionalData(DATA=vec_data) + opt.data_source_id = self.__id__ + opt.block_name = block_name + + event = Event.data.NewData(self.__id__, 0, opt) + self._Core_event_queue__.put(event) + + def send_new_data_old1(self, data, block_name): + opt = DOptionalData(DATA=data) + opt.data_source_id = self.__id__ + opt.block_name = block_name + + event = Event.data.NewData(self.__id__, 0, opt) + self._Core_event_queue__.put(event) + + def send_new_block_list(self, blocks): + opt = DOptionalData() + opt.block_list = blocks + event = Event.data.NewBlock(self.__id__, 0, opt) + self._Core_event_queue__.put(event) + + def send_new_parameter_list(self, parameters): + opt = DOptionalData() + opt.parameter_list = parameters + + event = Event.data.NewParameter(self.__id__, 0, opt) + self._Core_event_queue__.put(event) + + def update_plugin_meta(self, dplug): + self.dplugin_info = dplug + + self.plugin_meta_updated() + + def plugin_meta_updated(self): + raise NotImplementedError("Please Implement this method") + + def demux(self, source_id, block_name, data): + + subcribtions = self.dplugin_info.get_subscribtions() + sub_object = subcribtions[source_id][block_name] + + sub_signals = sub_object.signals + sub_signals.append('t') + + return dict([(i, data[i]) for i in sub_signals if i in data]) + #return data \ No newline at end of file diff --git a/papi/plugin/base_classes/base_visual.py b/papi/plugin/base_classes/base_visual.py new file mode 100644 index 00000000..3ca37e85 --- /dev/null +++ b/papi/plugin/base_classes/base_visual.py @@ -0,0 +1,118 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.plugin.base_classes.base_plugin import base_plugin +import re +from PySide.QtGui import QMdiSubWindow + + +class base_visual(base_plugin): + def init_plugin(self, CoreQueue, pluginQueue, id, control_api): + self._Core_event_queue__ = CoreQueue + self.__plugin_queue__ = pluginQueue + self.__id__ = id + self.control_api = control_api + super(base_visual, self).papi_init() + + + def start_init(self, config=None): + self.config = config + # -------------------------------- + + # get needed data from config + size_re = re.compile(r'([0-9]+)') + self.window_size = size_re.findall(self.config['size']['value']) + self.window_pos = size_re.findall(self.config['position']['value']) + + self.window_name = self.config['name']['value'] + + self.set_window_for_internal_usage(QMdiSubWindow()) + return self.initiate_layer_1(self.config) + + def get_current_config(self): + return self.config + + def initiate_layer_1(self, config): + raise NotImplementedError("Please Implement this method") + + + def get_configuration_base(self): + config = { + 'size': { + 'value': "(300,300)", + 'regex': '\(([0-9]+),([0-9]+)\)', + 'advanced': '1', + 'tooltip': 'Determine size: (height,width)' + }, + 'position': { + 'value': "(0,0)", + 'regex': '\(([0-9]+),([0-9]+)\)', + 'advanced': '1', + 'tooltip': 'Determine position: (x,y)' + }, + 'name': { + 'value': 'VisualPlugin', + 'tooltip': 'Used display name' + }} + return config + + + def set_window_for_internal_usage(self, subwindow): + self._subWindow = subwindow + self.original_resize_function = self._subWindow.resizeEvent + self._subWindow.resizeEvent = self.window_resize + self.original_move_function = self._subWindow.moveEvent + self._subWindow.moveEvent = self.window_move + self._subWindow.setWindowTitle(self.window_name) + self._subWindow.resize(int(self.window_size[0]), int(self.window_size[1])) + + def set_widget_for_internal_usage(self, widget): + self.widget = widget + self._subWindow.setWidget(self.widget) + + + def window_move(self, event): + pos = self._subWindow.pos() + + x = pos.x() + y = pos.y() + self.config['position']['value'] = '(' + str(x) + ',' + str(y) + ')' + self.original_move_function(event) + + + def window_resize(self, event): + size = event.size() + w = size.width() + h = size.height() + self.config['size']['value'] = '(' + str(w) + ',' + str(h) + ')' + self.original_resize_function(event) + + def get_sub_window(self): + return self._subWindow diff --git a/papi/plugin/base_classes/dpp_base.py b/papi/plugin/base_classes/dpp_base.py new file mode 100644 index 00000000..193173b9 --- /dev/null +++ b/papi/plugin/base_classes/dpp_base.py @@ -0,0 +1,46 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +. + +Contributors: +. + +Contributors: +. + +Contributors: +Stefan Ruppin +Sven Knuth +""" + +from papi.plugin.base_classes.base_visual import base_visual + +from papi.constants import PLUGIN_PCP_IDENTIFIER + + +class pcp_base(base_visual): + + def initiate_layer_1(self, config): + + self.initiate_layer_0(config) + + def initiate_layer_0(self, config): + raise NotImplementedError("Please Implement this method") + + def get_type(self): + return PLUGIN_PCP_IDENTIFIER \ No newline at end of file diff --git a/papi/plugin/base_classes/vip_base.py b/papi/plugin/base_classes/vip_base.py new file mode 100644 index 00000000..ec143268 --- /dev/null +++ b/papi/plugin/base_classes/vip_base.py @@ -0,0 +1,42 @@ +#!/usr/bin/python3 +#-*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +from papi.plugin.base_classes.base_visual import base_visual + +from papi.constants import PLUGIN_VIP_IDENTIFIER + +class vip_base(base_visual): + + def initiate_layer_1(self, config): + return self.initiate_layer_0(config) + + def initiate_layer_0(self, config): + raise NotImplementedError("Please Implement this method") + + def get_type(self): + return PLUGIN_VIP_IDENTIFIER \ No newline at end of file diff --git a/papi/plugin/dpp/add/Add.py b/papi/plugin/dpp/add/Add.py new file mode 100644 index 00000000..aa5ea0b2 --- /dev/null +++ b/papi/plugin/dpp/add/Add.py @@ -0,0 +1,129 @@ +#!/usr/bin/python3 +#-*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'ruppins' + + +from papi.plugin.base_classes.dpp_base import dpp_base +from papi.data.DPlugin import DBlock +from papi.data.DParameter import DParameter +from papi.data.DSignal import DSignal + +import time +import math +import numpy +import os + + +class Add(dpp_base): + + def start_init(self, config=None): + self.t = 0 + print(['ADD: process id: ',os.getpid()] ) + self.approx_max = 300 + self.fac= 1 + self.amax = 20 + self.approx = self.approx_max*self.fac + + self.vec = numpy.zeros((2, self.amax)) + + + self.block1 = DBlock('AddOut1') + signal = DSignal('Sum') + self.block1.add_signal(signal) + + + + #self.para1 = DParameter(None,'Count',1, [0, 1] ,1) + + self.send_new_block_list([self.block1]) + #self.send_new_parameter_list([self.para1]) + + + return True + + def pause(self): + pass + + + def resume(self): + pass + + def execute(self, Data=None, block_name = None): + #self.approx = round(self.approx_max*self.para1.value) +# self.vec[1] = 0 +# self.vec[0] = Data[0] + +# vec = numpy.zeros( (1, 2) ) + + #for i in range(self.amax): + #for k in range(1,self.approx): + # self.vec[i+self.amax] += Data[i + (k+1)*self.amax] + #self.vec[1, i] = Data[k, i] + + # Get Time Vector + +# vec[0, :] = Data['t'] + + # n_rows = Data.shape[0] + # n_cols = Data.shape[1] + + first_element = True + + for signal_name in Data: + if signal_name is not 't': + signal = Data[signal_name] + + if first_element is True: + first_element = False + + result = signal + else: + result = numpy.add(result, signal) + + + vec = numpy.zeros((2, len(result))) + + vec[0,:] = Data['t'] + vec[1,:] = result + + self.send_new_data('AddOut1', Data['t'], {'Sum':result}) + + + + def set_parameter(self, name, value): + pass + + def quit(self): + print('Add: will quit') + + def plugin_meta_updated(self): + pass + + def get_plugin_configuration(self): + return {} \ No newline at end of file diff --git a/papi/plugin/dpp/add/Add.yapsy-plugin b/papi/plugin/dpp/add/Add.yapsy-plugin new file mode 100644 index 00000000..48b17f30 --- /dev/null +++ b/papi/plugin/dpp/add/Add.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = Add +Module = Add + +[Documentation] +Author = S.K. +Version = 0.1 +Website = www +Description = simple sinus io plugin diff --git a/papi/plugin/dpp/toHDD/ToHDD_CSV.py b/papi/plugin/dpp/toHDD/ToHDD_CSV.py new file mode 100644 index 00000000..288fa111 --- /dev/null +++ b/papi/plugin/dpp/toHDD/ToHDD_CSV.py @@ -0,0 +1,115 @@ +#!/usr/bin/python3 +#-*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +from papi.plugin.base_classes.dpp_base import dpp_base + +import csv + +class ToHDD_CSV(dpp_base): + + def start_init(self, config=None): + + default_config = self.get_startup_configuration() + + if config is None: + self.config = default_config + else: + self.config = dict(list(default_config.items()) + list(config.items())) + + self.set_event_trigger_mode(True) + + self.known_blocks = {} + + print('toHDD started working') + + return True + + def pause(self): + for b in self.known_blocks.values(): + b['file'].close() + print('toHDD pause') + pass + + def resume(self): + for b in self.known_blocks.values(): + b['file'] = open(self.config['file']['value']+'.csv', 'a') + b['csv'] = csv.writer( b['file'], delimiter=self.config['delimiter']['value'], + quotechar='|', quoting=csv.QUOTE_MINIMAL) + print('toHDD resume') + pass + + def execute(self, Data=None, block_name = None): + t = Data['t'] + + if block_name not in self.known_blocks.keys(): + self.known_blocks[block_name] = {} + self.known_blocks[block_name]['file'] = open(self.config['file']['value']+'_'+block_name+'.csv', 'w+') + self.known_blocks[block_name]['csv'] = csv.writer( self.known_blocks[block_name]['file'], delimiter=self.config['delimiter']['value'], + quotechar='|', quoting=csv.QUOTE_MINIMAL) + else: + rows = [] + + for i in range(len(t)): + row = [] + row.append(t[i]) + for k in Data: + if k != 't': + vals = Data[k][i] + row.append(vals) + rows.append(row) + + self.known_blocks[block_name]['csv'].writerows(rows) + + + def set_parameter(self, name, value): + pass + + + def get_plugin_configuration(self): + config = { + "log-type": { + 'value': 1, + 'regex': '[0-9]+', + 'advanced' : '1' + }, "file": { + 'value': 'log', + 'advanced' : '0' + }, "delimiter": { + 'value': ' ', + 'advanced' : '1' + }} + return config + + def quit(self): + for b in self.known_blocks.values(): + b['file'].close() + + print('toHDD: will quit') + + def plugin_meta_updated(self): + pass diff --git a/papi/plugin/dpp/toHDD/ToHDD_CSV.yapsy-plugin b/papi/plugin/dpp/toHDD/ToHDD_CSV.yapsy-plugin new file mode 100644 index 00000000..1f86d0a2 --- /dev/null +++ b/papi/plugin/dpp/toHDD/ToHDD_CSV.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = ToHDD_CSV +Module = ToHDD_CSV + +[Documentation] +Author = S.R. +Version = 0.1 +Website = www +Description = simple write to HDD plugin \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json b/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json new file mode 100644 index 00000000..1d79619e --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json @@ -0,0 +1,5 @@ + {"SourcesConfig" : { + "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257" } , "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257" } } , + "ParametersConfig" : { + "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } } +} \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/README b/papi/plugin/io/ORTD_UDP/DataSourceExample/README new file mode 100644 index 00000000..10c48a96 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/README @@ -0,0 +1,54 @@ +This is a demonstration on how to set-up a web-interface to a control +system implemented using OpenRTDynamics.sf.net. As you'll notice +this is possible in a few lines of code, because the usage of +Javascript for implementing the gui-part and ORTD for the +real-time part including the UDP-communication allows an efficient +formulation of the algorithms. + +UDPio.sce is a sample ORTD-simulation and webappUDP.js the nodejs program +that connects to this simulation. Further, it provides a web-interface +accessible via http://localhost:8090/mainAuto.html . + +The template for the web-interface is stored in html/mainAuto.html. + + + +The files used in this example are: + +- UDPio.sce is a sample ORTD-simulation that simulates an osicillator + and a communication interface to node.js using UDPio +- webappUDP.js is the node.js program that connects to this simulation + via an UDP-interface and provides a web-interface on port 8090. +- Different templates for the html-page are stored in html/main*.html. + +- UDPio.ipar and UDPio.rpar are the compiled ORTD-programm files. +- PacketFramework.sce The file that implements the packet framework. + This will was also integrated into ORTD at Rev. 495 + +To make this example working: + +- The installation of node.js (nodejs.org) and its package manager + npm is required. +- Then, the "socket.io" node.js-package of node.js + is required that can be installed with "npm". To do this, call the + following commmand from the directory that contains webappUDP.js: + + $ cd webinterface + $ npm install socket.io + +- To start the set-up, two services / processes are required to run + at the same time: + +the ORTD-simulation is started by running + + $ sh run_UDPio.sh + +and the node.js part by + + $ cd webinterface + $ node webappUDP.js + +The order doesn't matter and it is also possible to start / stop each +service, which is one advantage of using stateless UDP-communication. + +- Finally, point your browser to http://localhost:8090/mainAuto.html . \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.ipar b/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.ipar new file mode 100644 index 00000000..d12f3e60 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.ipar @@ -0,0 +1,3206 @@ + 1 + 1 + 901 + 10 + 0 + 0 + 3198 + 40 + 1 + 3 + 201 + 100 + 0 + 0 + 6 + 0 + 203 + 100 + 6 + 0 + 3160 + 40 + 100 + 101 + 3166 + 40 + 12 + 0 + 60023 + 201 + 0 + 0 + 1 + 0 + 15011 + 203 + 3154 + 40 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 3091 + 0 + 21 + 1 + 3103 + 0 + 1 + 40 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 3090 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 21 + 4 + 4 + 0 + 19 + 0 + 22 + 4 + 23 + 0 + 4 + 0 + 900 + 10 + 27 + 0 + 3019 + 40 + 0 + 0 + 0 + 0 + 18 + 77 + 97 + 105 + 110 + 82 + 101 + 97 + 108 + 116 + 105 + 109 + 101 + 84 + 104 + 114 + 101 + 97 + 100 + 3 + 2 + 0 + -1 + 1 + 55 + 201 + 100 + 0 + 0 + 6 + 1 + 203 + 100 + 6 + 1 + 72 + 0 + 205 + 100 + 78 + 1 + 6 + 1 + 207 + 100 + 84 + 2 + 54 + 0 + 209 + 100 + 138 + 2 + 6 + 1 + 211 + 100 + 144 + 3 + 54 + 0 + 213 + 100 + 198 + 3 + 6 + 1 + 215 + 100 + 204 + 4 + 54 + 0 + 217 + 100 + 258 + 4 + 25 + 0 + 218 + 100 + 283 + 4 + 13 + 0 + 219 + 100 + 296 + 4 + 29 + 0 + 222 + 100 + 325 + 4 + 6 + 2 + 224 + 100 + 331 + 6 + 6 + 2 + 226 + 100 + 337 + 8 + 8 + 3 + 228 + 100 + 345 + 11 + 6 + 1 + 230 + 100 + 351 + 12 + 8 + 3 + 232 + 100 + 359 + 15 + 6 + 1 + 234 + 100 + 365 + 16 + 6 + 1 + 236 + 100 + 371 + 17 + 8 + 0 + 238 + 100 + 379 + 17 + 6 + 0 + 240 + 100 + 385 + 17 + 6 + 1 + 242 + 100 + 391 + 18 + 6 + 0 + 244 + 100 + 397 + 18 + 6 + 1 + 246 + 100 + 403 + 19 + 6 + 0 + 248 + 100 + 409 + 19 + 76 + 0 + 250 + 100 + 485 + 19 + 9 + 0 + 252 + 100 + 494 + 19 + 166 + 0 + 253 + 100 + 660 + 19 + 6 + 1 + 255 + 100 + 666 + 20 + 8 + 0 + 257 + 100 + 674 + 20 + 6 + 0 + 259 + 100 + 680 + 20 + 6 + 1 + 261 + 100 + 686 + 21 + 6 + 0 + 263 + 100 + 692 + 21 + 6 + 1 + 265 + 100 + 698 + 22 + 6 + 0 + 267 + 100 + 704 + 22 + 76 + 0 + 269 + 100 + 780 + 22 + 9 + 0 + 271 + 100 + 789 + 22 + 166 + 0 + 272 + 100 + 955 + 22 + 154 + 0 + 273 + 100 + 1109 + 22 + 54 + 13 + 274 + 100 + 1163 + 35 + 6 + 1 + 276 + 100 + 1169 + 36 + 780 + 0 + 278 + 100 + 1949 + 36 + 6 + 1 + 280 + 100 + 1955 + 37 + 8 + 0 + 282 + 100 + 1963 + 37 + 6 + 0 + 284 + 100 + 1969 + 37 + 6 + 1 + 286 + 100 + 1975 + 38 + 6 + 0 + 288 + 100 + 1981 + 38 + 6 + 1 + 290 + 100 + 1987 + 39 + 6 + 0 + 292 + 100 + 1993 + 39 + 6 + 1 + 294 + 100 + 1999 + 40 + 6 + 0 + 296 + 100 + 2005 + 40 + 76 + 0 + 298 + 100 + 2081 + 40 + 9 + 0 + 300 + 100 + 2090 + 40 + 166 + 0 + 301 + 100 + 2256 + 40 + 27 + 0 + 100 + 101 + 2283 + 40 + 404 + 0 + 40 + 201 + 0 + 1 + 1 + 0 + 15102 + 203 + 66 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 3 + 0 + 21 + 1 + 15 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 2 + 1 + 0 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 15007 + 207 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 209 + 0 + 1 + 1 + 0 + 15007 + 211 + 48 + 0 + 1 + 0 + 0 + 257 + 10 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 213 + 0 + 1 + 1 + 0 + 15007 + 215 + 48 + 0 + 1 + 0 + 0 + 257 + 2 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 217 + 19 + 0 + 1 + 0 + 1 + 17 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 32 + 170 + 218 + 7 + 0 + 1 + 0 + 2 + 5 + 84 + 101 + 115 + 116 + 32 + 170 + 219 + 23 + 0 + 1 + 0 + 10 + 21 + 65 + 32 + 118 + 101 + 99 + 116 + 111 + 114 + 105 + 97 + 108 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 12 + 222 + 0 + 2 + 1 + 0 + 12 + 224 + 0 + 2 + 1 + 0 + 30 + 226 + 2 + 3 + 1 + 0 + 0 + 1 + 20 + 228 + 0 + 1 + 1 + 0 + 30 + 230 + 2 + 3 + 1 + 0 + 0 + 1 + 20 + 232 + 0 + 1 + 1 + 0 + 40 + 234 + 0 + 1 + 1 + 0 + 60005 + 236 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 238 + 0 + 0 + 1 + 0 + 40 + 240 + 0 + 1 + 1 + 0 + 60031 + 242 + 0 + 0 + 1 + 0 + 40 + 244 + 0 + 1 + 1 + 0 + 60031 + 246 + 0 + 0 + 1 + 0 + 39011 + 248 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 250 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 252 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 253 + 0 + 1 + 1 + 0 + 60005 + 255 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 257 + 0 + 0 + 1 + 0 + 40 + 259 + 0 + 1 + 1 + 0 + 60031 + 261 + 0 + 0 + 1 + 0 + 40 + 263 + 0 + 1 + 1 + 0 + 60031 + 265 + 0 + 0 + 1 + 0 + 39011 + 267 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 269 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 271 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 39001 + 272 + 148 + 0 + 1 + 0 + 1 + 10 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 14 + 4 + 4 + 0 + 2 + 0 + 15 + 4 + 6 + 0 + 2 + 0 + 20 + 4 + 8 + 0 + 27 + 0 + 21 + 1 + 35 + 0 + 1 + 0 + 30 + 4 + 36 + 0 + 48 + 0 + 31 + 4 + 84 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 1 + 2 + 26 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 10 + 0 + 1 + 20001 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 1 + 0 + 15005 + 273 + 48 + 13 + 1 + 0 + 0 + 257 + 13 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 274 + 0 + 1 + 1 + 0 + 15011 + 276 + 774 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 711 + 0 + 21 + 1 + 723 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 710 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 21 + 4 + 4 + 0 + 32 + 0 + 22 + 4 + 36 + 0 + 4 + 0 + 900 + 10 + 40 + 0 + 626 + 0 + 0 + 0 + 0 + 0 + 31 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 84 + 104 + 114 + 101 + 97 + 100 + 49 + 3 + 2 + 0 + -1 + 1 + 11 + 201 + 100 + 0 + 0 + 142 + 0 + 204 + 100 + 142 + 0 + 76 + 0 + 209 + 100 + 218 + 0 + 6 + 0 + 211 + 100 + 224 + 0 + 6 + 0 + 213 + 100 + 230 + 0 + 6 + 0 + 215 + 100 + 236 + 0 + 82 + 0 + 217 + 100 + 318 + 0 + 82 + 0 + 219 + 100 + 400 + 0 + 6 + 0 + 221 + 100 + 406 + 0 + 6 + 0 + 223 + 100 + 412 + 0 + 54 + 0 + 100 + 101 + 466 + 0 + 92 + 0 + 39003 + 201 + 136 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 3 + 0 + 12 + 4 + 4 + 0 + 1 + 0 + 13 + 4 + 5 + 0 + 3 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 19 + 0 + 21 + 1 + 31 + 0 + 1 + 0 + 30 + 4 + 32 + 0 + 48 + 0 + 0 + 2 + 1396 + 6 + 0 + 2 + 6 + 6 + 1 + 1 + 1 + 1 + 18 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 1 + 1396 + 1 + 6 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 39012 + 204 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 5 + 0 + 12 + 4 + 7 + 0 + 2 + 0 + 13 + 4 + 9 + 0 + 5 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 1 + 1396 + 4 + 1 + 1 + 1 + 173 + 1 + 6 + 4 + 2 + 2 + 2 + 257 + 1 + 1 + 1 + 2 + 0 + 0 + 60032 + 209 + 0 + 0 + 1 + 0 + 60032 + 211 + 0 + 0 + 1 + 0 + 60032 + 213 + 0 + 0 + 1 + 0 + 60304 + 215 + 76 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 13 + 0 + 21 + 1 + 25 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 12 + 1 + 1 + 10 + 4 + 0 + 0 + 4 + 0 + 3 + 1 + 2 + 12 + 0 + 60304 + 217 + 76 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 13 + 0 + 21 + 1 + 25 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 12 + 1 + 1 + 10 + 4 + 0 + 0 + 4 + 0 + 3 + 1 + 10 + 2 + 0 + 60032 + 219 + 0 + 0 + 1 + 0 + 60032 + 221 + 0 + 0 + 1 + 0 + 15008 + 223 + 48 + 0 + 1 + 0 + 0 + 257 + 173 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 0 + 11 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 204 + 204 + 0 + 0 + 204 + 204 + 0 + 0 + 209 + 209 + 0 + 0 + 204 + 204 + 1 + 0 + 211 + 211 + 0 + 0 + 204 + 204 + 2 + 0 + 213 + 213 + 0 + 0 + 204 + 204 + 2 + 0 + 215 + 215 + 0 + 0 + 204 + 204 + 2 + 0 + 217 + 217 + 0 + 0 + 215 + 215 + 0 + 0 + 219 + 219 + 0 + 0 + 217 + 217 + 0 + 0 + 221 + 221 + 0 + 0 + 204 + 204 + 3 + 0 + 223 + 223 + 0 + 0 + 215 + 215 + 0 + 0 + 223 + 223 + 1 + 0 + 217 + 217 + 0 + 0 + 223 + 223 + 2 + 0 + 40 + 278 + 0 + 1 + 1 + 0 + 60005 + 280 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 282 + 0 + 0 + 1 + 0 + 40 + 284 + 0 + 1 + 1 + 0 + 60031 + 286 + 0 + 0 + 1 + 0 + 40 + 288 + 0 + 1 + 1 + 0 + 60031 + 290 + 0 + 0 + 1 + 0 + 40 + 292 + 0 + 1 + 1 + 0 + 60031 + 294 + 0 + 0 + 1 + 0 + 39011 + 296 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 16 + 4 + 2 + 2 + 2 + 2 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 298 + 3 + 0 + 1 + 0 + 1 + 0 + 16 + 39004 + 300 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 16 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 16 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 170 + 301 + 21 + 0 + 1 + 0 + 1 + 19 + 83 + 101 + 110 + 116 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 32 + 112 + 97 + 99 + 107 + 101 + 116 + 32 + 0 + 50 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 207 + 207 + 0 + 0 + 209 + 209 + 0 + 0 + 211 + 211 + 0 + 0 + 213 + 213 + 0 + 0 + 215 + 215 + 0 + 0 + 207 + 207 + 0 + 0 + 217 + 217 + 0 + 0 + 215 + 215 + 0 + 0 + 218 + 218 + 0 + 0 + 211 + 211 + 0 + 0 + 219 + 219 + 0 + 0 + 207 + 207 + 0 + 0 + 222 + 222 + 0 + 0 + 222 + 222 + 0 + 0 + 224 + 224 + 0 + 0 + 224 + 224 + 0 + 0 + 226 + 226 + 0 + 0 + 226 + 226 + 0 + 0 + 228 + 228 + 0 + 0 + 228 + 228 + 0 + 0 + 224 + 224 + 1 + 0 + 226 + 226 + 0 + 0 + 230 + 230 + 0 + 0 + 230 + 230 + 0 + 0 + 232 + 232 + 0 + 0 + 232 + 232 + 0 + 0 + 222 + 222 + 1 + 0 + 234 + 234 + 0 + 0 + 236 + 236 + 0 + 0 + 236 + 236 + 0 + 0 + 238 + 238 + 0 + 0 + 240 + 240 + 0 + 0 + 242 + 242 + 0 + 0 + 244 + 244 + 0 + 0 + 246 + 246 + 0 + 0 + 246 + 246 + 0 + 0 + 248 + 248 + 0 + 0 + 238 + 238 + 0 + 0 + 248 + 248 + 1 + 0 + 242 + 242 + 0 + 0 + 248 + 248 + 2 + 0 + 230 + 230 + 0 + 0 + 248 + 248 + 3 + 0 + 248 + 248 + 0 + 0 + 252 + 252 + 0 + 0 + 250 + 250 + 0 + 0 + 252 + 252 + 1 + 0 + 253 + 253 + 0 + 0 + 255 + 255 + 0 + 0 + 255 + 255 + 0 + 0 + 257 + 257 + 0 + 0 + 259 + 259 + 0 + 0 + 261 + 261 + 0 + 0 + 263 + 263 + 0 + 0 + 265 + 265 + 0 + 0 + 265 + 265 + 0 + 0 + 267 + 267 + 0 + 0 + 257 + 257 + 0 + 0 + 267 + 267 + 1 + 0 + 261 + 261 + 0 + 0 + 267 + 267 + 2 + 0 + 226 + 226 + 0 + 0 + 267 + 267 + 3 + 0 + 267 + 267 + 0 + 0 + 271 + 271 + 0 + 0 + 269 + 269 + 0 + 0 + 271 + 271 + 1 + 0 + 0 + 0 + 0 + 2 + 272 + 272 + 0 + 0 + 0 + 0 + 0 + 2 + 273 + 273 + 0 + 0 + 274 + 274 + 0 + 0 + 276 + 276 + 0 + 0 + 278 + 278 + 0 + 0 + 280 + 280 + 0 + 0 + 280 + 280 + 0 + 0 + 282 + 282 + 0 + 0 + 284 + 284 + 0 + 0 + 286 + 286 + 0 + 0 + 288 + 288 + 0 + 0 + 290 + 290 + 0 + 0 + 292 + 292 + 0 + 0 + 294 + 294 + 0 + 0 + 294 + 294 + 0 + 0 + 296 + 296 + 0 + 0 + 282 + 282 + 0 + 0 + 296 + 296 + 1 + 0 + 286 + 286 + 0 + 0 + 296 + 296 + 2 + 0 + 290 + 290 + 0 + 0 + 296 + 296 + 3 + 0 + 296 + 296 + 0 + 0 + 300 + 300 + 0 + 0 + 298 + 298 + 0 + 0 + 300 + 300 + 1 + 0 + 288 + 288 + 0 + 0 + 301 + 301 + 0 + 40 + 0 + 1 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.rpar b/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.rpar new file mode 100644 index 00000000..c2491c9a --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.rpar @@ -0,0 +1,40 @@ +0.2000000000000000111022302 +1.0000000000000000000000000 +2.0000000000000000000000000 +12.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +0.1000000000000000055511151 +-1.0000000000000000000000000 +1.0000000000000000000000000 +0.1000000000000000055511151 +0.1000000000000000055511151 +-1.0000000000000000000000000 +1.0000000000000000000000000 +0.5999999999999999777955395 +1.0000000000000000000000000 +0.0000000000000000000000000 +1295793.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1295793.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +0.0000000000000000000000000 +1295793.0000000000000000000000000 diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.sce b/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.sce new file mode 100644 index 00000000..8b494060 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.sce @@ -0,0 +1,175 @@ +// +// +// Example for a communication interface from ORTD using UDP datagrams to e.g. +// nodejs. +// The file webinterface/webappUDP.js is the counterpart that provides a +// web-interface to control a oscillator-system in this example. +// +// For more details, please consider the readme-file. +// +// Rev 1 +// + +// The name of the program +ProgramName = 'UDPio'; // must be the filename without .sce + + + + + + + + + + + +// And example-system that is controlled via UDP and one step further with the Web-gui +// Superblock: A more complex oscillator with damping +function [sim, x,v] = damped_oscillator(sim, u) + // create feedback signals + [sim,x_feedback] = libdyn_new_feedback(sim); + + [sim,v_feedback] = libdyn_new_feedback(sim); + + // use this as a normal signal + [sim,a] = ld_add(sim, ev, list(u, x_feedback), [1, -1]); + [sim,a] = ld_add(sim, ev, list(a, v_feedback), [1, -1]); + + [sim,v] = ld_ztf(sim, ev, a, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,v_gain] = ld_gain(sim, ev, v, 0.1); + + // close loop v_gain = v_feedback + [sim] = libdyn_close_loop(sim, v_gain, v_feedback); + + + [sim,x] = ld_ztf(sim, ev, v, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,x_gain] = ld_gain(sim, ev, x, 0.6); + + // close loop x_gain = x_feedback + [sim] = libdyn_close_loop(sim, x_gain, x_feedback); +endfunction + + + + + + + +// The main real-time thread +function [sim, outlist, userdata] = Thread_MainRT(sim, inlist, userdata) + // This will run in a thread + [sim, Tpause] = ld_const(sim, ev, 1/5); // The sampling time that is constant at 20 Hz in this example + [sim, out] = ld_ClockSync(sim, ev, in=Tpause); // synchronise this simulation + + // + // Add you own control system here + // + + + Configuration.UnderlyingProtocoll = "UDP"; + Configuration.DestHost = "127.0.0.1"; + Configuration.DestPort = 20000; + Configuration.LocalSocketHost = "127.0.0.1"; + Configuration.LocalSocketPort = 20001; + PacketFramework.Configuration.debugmode = %t; + [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="TrockenofenRemoteControl", Configuration) + + // Add a parameter for controlling the oscillator + [sim, PacketFramework, Input]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Oscillator input"); + + // some some more parameters + [sim, PacketFramework, AParVector]=ld_PF_Parameter(sim, PacketFramework, NValues=10, datatype=ORTD.DATATYPE_FLOAT, ParameterName="A vectorial parameter"); + [sim, PacketFramework, par2]=ld_PF_Parameter(sim, PacketFramework, NValues=2, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Test"); + + // printf these parameters + [sim] = ld_printf(sim, ev, Input, "Oscillator input ", 1); + [sim] = ld_printf(sim, ev, par2, "Test ", 2); + [sim] = ld_printf(sim, ev, AParVector, "A vectorial parameter", 10); + + + + // The system to control + T_a = 0.1; [sim, x,v] = damped_oscillator(sim, Input); + + // Stream the data of the oscillator + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X") + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V") + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); + ld_PF_Export_js(PacketFramework, fname="ProtocollConfig.json"); + + + outlist = list(); +endfunction + + + + +// This is the main top level schematic +function [sim, outlist] = schematic_fn(sim, inlist) + +// +// Create a thread that runs the control system +// + + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; // or ORTD.ORTD_RT_NORMALTASK + ThreadPrioStruct.prio2=0; // for ORTD.ORTD_RT_REALTIMETASK: 1-99 as described in man sched_setscheduler + // for ORTD.ORTD_RT_NORMALTASK this is the nice-value (higher value means less priority) + ThreadPrioStruct.cpu = -1; // The CPU on which the thread will run; -1 dynamically assigns to a CPU, + // counting of the CPUs starts at 0 + + [sim, StartThread] = ld_initimpuls(sim, ev); // triggers your computation only once + [sim, outlist, computation_finished] = ld_async_simulation(sim, ev, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = Thread_MainRT, ... + TriggerSignal=StartThread, name="MainRealtimeThread", ... + ThreadPrioStruct, userdata=list() ); + + // output of schematic (empty) + outlist = list(); +endfunction + + + + + +// +// Set-up (no detailed understanding necessary) +// + +thispath = get_absolute_file_path(ProgramName+'.sce'); +cd(thispath); +z = poly(0,'z'); + + +// The following code is integrated into ORTD since rev 494. +// Thus the following line is commented. + +exec('webinterface/PacketFramework.sce'); + + + +// defile ev +ev = [0]; // main event + +// set-up schematic by calling the user defined function "schematic_fn" +insizes = []; outsizes=[]; +[sim_container_irpar, sim]=libdyn_setup_schematic(schematic_fn, insizes, outsizes); + +// pack the simulation into a irpar container +parlist = new_irparam_set(); +parlist = new_irparam_container(parlist, sim_container_irpar, 901); // pack simulations into irpar container with id = 901 +par = combine_irparam(parlist); // complete irparam set +save_irparam(par, ProgramName+'.ipar', ProgramName+'.rpar'); // Save the schematic to disk + +// clear +par.ipar = []; par.rpar = []; + + diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/run_UDPio.sh b/papi/plugin/io/ORTD_UDP/DataSourceExample/run_UDPio.sh new file mode 100755 index 00000000..28def1c1 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/run_UDPio.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +#ortd --baserate=10 --rtmode 1 -s UDPio -i 901 -l 0 +ortdrun -s UDPio diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/PacketFramework.sce b/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/PacketFramework.sce new file mode 100644 index 00000000..9e3d7cdb --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/PacketFramework.sce @@ -0,0 +1,487 @@ + + +// +// +// A packet based communication interface from ORTD using UDP datagrams to e.g. +// nodejs. +// webappUDP.js is the counterpart that provides a web-interface +// +// Current Rev: 7 +// +// Revisions: +// +// 27.3.14 - possibility to reservate sources +// 3.4.14 - small re-arrangements +// 4.4.14 - Bugfixes +// 7.4.14 - Bugfix +// 12.6.14 - Bugfix +// 2.11.14 - +// + + +function [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName) + SourceID = PacketFramework.SourceID_counter; + + Source.SourceName = SourceName; + Source.SourceID = SourceID; + Source.NValues_send = NValues_send; + Source.datatype = datatype; + + // Add new source to the list + PacketFramework.Sources($+1) = Source; + + // inc counter + PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; +endfunction + +function [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName) + ParameterID = PacketFramework.Parameterid_counter; + + Parameter.ParameterName = ParameterName; + Parameter.ParameterID = ParameterID; + Parameter.NValues = NValues; + Parameter.datatype = datatype; + Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; + + // Add new source to the list + PacketFramework.Parameters($+1) = Parameter; + + // inc counters + PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; + PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; + + // return values + ParameterID = Parameter.ParameterID; + MemoryOfs = Parameter.MemoryOfs; +endfunction + +function [sim, PacketFramework, Parameter] = ld_PF_Parameter(sim, PacketFramework, NValues, datatype, ParameterName) // PARSEDOCU_BLOCK +// +// Define a parameter +// +// NValues - amount of data sets +// datatype - only ORTD.DATATYPE_FLOAT for now +// ParameterName - a unique string decribing the parameter +// +// +// + + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); + + // read data from global memory + [sim, readI] = ld_const(sim, 0, MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + datatype, NValues); +endfunction + + + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, SourceID); + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); + + printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + +endfunction + + + + +function [sim, PacketFramework] = ld_SendPacket(sim, PacketFramework, Signal, NValues_send, datatype, SourceName) // PARSEDOCU_BLOCK // PARSEDOCU_BLOCK +// +// Stream data - block +// +// Signal - the signal to stream +// NValues_send - the vector length of Signal +// datatype - only ORTD.DATATYPE_FLOAT by now +// SourceName - a unique string identifier descring the stream +// +// +// + + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); +endfunction + + + + +function [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName, Configuration) // PARSEDOCU_BLOCK +// +// Initialise an instance of the Packet Framework +// +// InstanceName - a unique string identifier for the instance +// Configuration must include the following properties: +// +// Configuration.UnderlyingProtocoll = "UDP" +// Configuration.DestHost +// Configuration.DestPort +// Configuration.LocalSocketHost +// Configuration.LocalSocketPort +// +// +// Example: +// +// +// Configuration.UnderlyingProtocoll = "UDP"; +// Configuration.DestHost = "127.0.0.1"; +// Configuration.DestPort = 20000; +// Configuration.LocalSocketHost = "127.0.0.1"; +// Configuration.LocalSocketPort = 20001; +// [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); +// +// +// +// Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations +// +// + + // initialise structure for sources + PacketFramework.InstanceName = InstanceName; + PacketFramework.Configuration = Configuration; + + PacketFramework.Configuration.debugmode = %F; + +// disp(Configuration.UnderlyingProtocoll) + + if Configuration.UnderlyingProtocoll == 'UDP' + null; + else + error("PacketFramework: Only UDP supported up to now"); + end + + // possible packet sizes for UDP + PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; + + // sources + PacketFramework.SourceID_counter = 0; + PacketFramework.Sources = list(); + + // parameters + PacketFramework.Parameterid_counter = 0; + PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory + PacketFramework.Parameters = list(); + + PacketFramework.SenderID = 1295793; +endfunction + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Group ID + [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources + [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); + +// printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + + + [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); +endfunction + +function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOCU_BLOCK +// +// Finalise the instance. +// +// + + // The main real-time thread + function [sim] = ld_PF_InitUDP(sim, InstanceName, ParameterMemory) + + function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) + // This will run in a thread. Each time a UDP-packet is received + // one simulation step is performed. Herein, the packet is parsed + // and the contained parameters are stored into a memory. + + // Sync the simulation to incomming UDP-packets + [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); + + // disassemble packet's structure + [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... + outsizes=[1,1,1,TotalElemetsPerPacket], ... + outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); + + + + DisAsm_ = list(); + DisAsm_(4) = DisAsm(4); + [sim, DisAsm_(1)] = ld_Int32ToFloat(sim, 0, DisAsm(1) ); + [sim, DisAsm_(2)] = ld_Int32ToFloat(sim, 0, DisAsm(2) ); + [sim, DisAsm_(3)] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); + + + [sim, memofs] = ld_ArrayInt32(sim, 0, array=ParameterMemory.MemoryOfs, in=DisAsm(3) ); + [sim, Nelements] = ld_ArrayInt32(sim, 0, array=ParameterMemory.Sizes, in=DisAsm(3) ); + + [sim, memofs_] = ld_Int32ToFloat(sim, 0, memofs ); + [sim, Nelements_] = ld_Int32ToFloat(sim, 0, Nelements ); + + if PacketFramework.Configuration.debugmode then + // print the contents of the packet + [sim] = ld_printf(sim, 0, DisAsm_(1), "DisAsm(1) (SenderID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(2), "DisAsm(2) (Packet Counter) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(3), "DisAsm(3) (SourceID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(4), "DisAsm(4) (Signal) = ", TotalElemetsPerPacket); + + [sim] = ld_printf(sim, 0, memofs_ , "memofs = ", 1); + [sim] = ld_printf(sim, 0, memofs_ , "Nelements = ", 1); + end + + // Store the input data into a shared memory + [sim] = ld_WriteMemory2(sim, 0, data=DisAsm(4), index=memofs, ElementsToWrite=Nelements, ... + ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); + + + + // output of schematic + outlist = list(); + endfunction + + + + // start the node.js service from the subfolder webinterface + //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); + + TotalMemorySize = sum(PacketFramework.ParameterMemory.Sizes); + TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketSize = PacketFramework.PacketSize; + + // Open an UDP-Port in server mode + [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... + hostname=PacketFramework.Configuration.LocalSocketHost, ... + UDPPort=PacketFramework.Configuration.LocalSocketPort); + + // initialise a global memory for storing the input data for the computation + [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... + datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... + initial_data=[zeros(TotalMemorySize,1)], ... + visibility='global', useMutex=1); + + // Create thread for the receiver + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; + [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step + [sim, outlist, computation_finished] = ld_async_simulation(sim, 0, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = UDPReceiverThread, ... + TriggerSignal=startcalc, name=InstanceName+"Thread1", ... + ThreadPrioStruct, userdata=list() ); + + + endfunction + + + + + + // calc memory + MemoryOfs = []; + Sizes = []; + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + + Sizes = [Sizes; P.NValues]; + MemoryOfs = [MemoryOfs; P.MemoryOfs]; + end + + PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; + PacketFramework.ParameterMemory.Sizes = Sizes; + + // udp + [sim] = ld_PF_InitUDP(sim, PacketFramework.InstanceName, PacketFramework.ParameterMemory); + + // Send to group update notifications for each group (currently only one possible) + [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); + +endfunction + +function ld_PF_Export_js(PacketFramework, fname) // PARSEDOCU_BLOCK +// +// Export configuration of the defined protocoll (Sources, Parameters) +// into JSON-format. This is to be used by software that shall communicate +// to the real-time system. +// +// fname - The file name +// +// + + + fd = mopen(fname,'wt'); + + mfprintf(fd,' {""SourcesConfig"" : {\n'); + + for i=1:length(PacketFramework.Sources) + + + SourceID = PacketFramework.Sources(i).SourceID; + SourceName = PacketFramework.Sources(i).SourceName; + disp(SourceID ); + disp( SourceName ); + + + line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"" } \n", ... + string(PacketFramework.Sources(i).SourceID), ... + string(PacketFramework.Sources(i).SourceName), ... + string(PacketFramework.Sources(i).NValues_send), ... + string(PacketFramework.Sources(i).datatype) ); + + + if i==length(PacketFramework.Sources) + // finalise the last entry without "," + printf('%s \n' , line); + mfprintf(fd,'%s', line); + else + printf('%s, \n' , line); + mfprintf(fd,'%s,', line); + end + + + end + + + + + mfprintf(fd,'} , \n ""ParametersConfig"" : {\n'); + + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + + printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); + + line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } \n", ... + string(PacketFramework.Parameters(i).ParameterID), ... + string(PacketFramework.Parameters(i).ParameterName), ... + string(PacketFramework.Parameters(i).NValues), ... + string(PacketFramework.Parameters(i).datatype) ); + + + if i==length(PacketFramework.Parameters) + // finalise the last entry without "," + printf('%s \n' , line); + mfprintf(fd,'%s', line); + else + printf('%s, \n' , line); + mfprintf(fd,'%s,', line); + end + + + end + + mfprintf(fd,'}\n}'); + + mclose(fd); +endfunction + +// +// Added 27.3.14 +// + +function [sim, PacketFramework, SourceID]=ld_SendPacketReserve(sim, PacketFramework, NValues_send, datatype, SourceName) + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); +endfunction + +function [sim, PacketFramework]=ld_SendPacket2(sim, PacketFramework, Signal, SourceName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Sources) + S = PacketFramework.Sources(i); + if S.SourceName == SourceName + index = i; + printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); + break; + end + end + + if index == -1 + printf("SourceName = %s\n", SourceName); + error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); + end + + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); +endfunction + + + +function [sim, PacketFramework, ParameterID]=ld_PF_ParameterReserve(sim, PacketFramework, NValues, datatype, ParameterName) + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); +endfunction + + +function [sim, PacketFramework, Parameter]=ld_PF_Parameter2(sim, PacketFramework, ParameterName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + if P.ParameterName == ParameterName + index = i; + printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); + break; + end + end + + if index == -1 + printf("ParameterName = %s\n", ParameterName); + error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); + end + + // read data from global memory + [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + P.datatype, P.NValues); +endfunction + diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/mainAuto.html b/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/mainAuto.html new file mode 100644 index 00000000..491f5a19 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/mainAuto.html @@ -0,0 +1,155 @@ + + + + + + + + + Generic GUI-interface to an ORTD-simulation + +
+ This is a generic interface to ORTD. All parameters and datasources are automatically shown. Feel free to copy and adapt it to your needs to build you own specialised web-interface. + +

+ + Parameters:
0
+ + Displays:
0
+ +

+ ProtocollConfiguration:
0
+ + + \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/main_Plot.html b/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/main_Plot.html new file mode 100644 index 00000000..47a8fa23 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/main_Plot.html @@ -0,0 +1,365 @@ + + + + + +
+ + + + + + + + + + + GUI-interface to ORTD-simulation + +

+ + + Parameters:
0
+ + Displays:
0
+ + + + + ProtocollConfiguration:
0
+

+ + + + + + + + \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/install_nodejs.sh b/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/install_nodejs.sh new file mode 100644 index 00000000..7cdf2b59 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/install_nodejs.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +echo 'export PATH=$HOME/local/bin:$PATH' >> ~/.bashrc +. ~/.bashrc +mkdir ~/local +mkdir ~/node-latest-install +cd ~/node-latest-install +curl http://nodejs.org/dist/node-latest.tar.gz | tar xz --strip-components=1 +./configure --prefix=~/local +make install # ok, fine, this step probably takes more than 30 seconds... +curl https://npmjs.org/install.sh | sh diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/webappUDP.js b/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/webappUDP.js new file mode 100644 index 00000000..30baf361 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/webappUDP.js @@ -0,0 +1,304 @@ +/* + + node.js interface to ORTD using UDP datagrams and the packet framework. + A web-interface is provided at e.g. http://localhost:8091/mainAuto.html, + however currently not passwort protected. + The web-interface(s) are defined in html/* + + You can adapt the html files to your need and also created new ones + to e.g. set-up an individual interface to your application + + Rev 2 + +*/ + +// http-server config +var HTTPPORT = 8091; + +// UDP config +var PORT = 20000; +var HOST = '127.0.0.1'; +var ORTD_HOST = '127.0.0.1'; // the IP and port of the ORTD simulator running UDPio.sce +var ORTD_PORT = 20001; + +var NValues = 7; // must be the same as NValues_send defined in UDPio.sce when calling UDPSend +var DataBufferSize = 20000; // Number of elementes stored in the ringbuffer + + + +// +// Includes +// + +http = require('http'); +url = require("url"), +path = require("path"), +fs = require("fs") + +var fs = require('fs'); +var dgram = require('dgram'); + + +// +// Load configuration +// + +var ProtocollConfig = require('../ProtocollConfig.json'); +console.log(ProtocollConfig); + +// +// RingBuffer +// + +var RingBuffer = new RingBuffer(DataBufferSize, NValues); +console.log("RingBuffer created"); + + + + // console.log(P.length); + +function GetParameterID(ParametersConfig, ParameterName) +{ + var P = ParametersConfig; + for (key in P) { + if (P[key].ParameterName == ParameterName) { + return key; + } + } + return -1; +} + +console.log( GetParameterID(ProtocollConfig.ParametersConfig, "Parameter2") ); + +// +// http-server +// + +// got from stackoverflow: +// http://stackoverflow.com/questions/6084360/node-js-as-a-simple-web-server +var httpserver = http.createServer(function(request, response) { + + var uri = url.parse(request.url).pathname + , filename = path.join(process.cwd(), 'html', uri); + + path.exists(filename, function(exists) { + if(!exists) { + response.writeHead(404, {"Content-Type": "text/plain"}); + response.write("404 Not Found\n"); + response.end(); + return; + } + + if (fs.statSync(filename).isDirectory()) filename += '/index.html'; + + fs.readFile(filename, "binary", function(err, file) { + if(err) { + response.writeHead(500, {"Content-Type": "text/plain"}); + response.write(err + "\n"); + response.end(); + return; + } + + response.writeHead(200); + response.write(file, "binary"); + response.end(); + }); + }); +}).listen(HTTPPORT); + +// set-up socket.io +var io = require('socket.io').listen(httpserver); +io.set('log level', 1); // reduce logging + + + + +// +// UDP interface +// +var server = dgram.createSocket('udp4'); +server.on('listening', function () { + var address = server.address(); + console.log('UDP Server listening on ' + address.address + ":" + address.port); +}); + +// Buffer for sending UDP packets +var UDPSendPacketBuffer = new Buffer(2000); // size is propably bigger than every UDP-Packet + + +server.on('message', function (message, remote) { + // received new packet from ORTD via UDP + //console.log(remote.address + ':' + remote.port); + + + var i; + + try { + // disassemble header + var SenderID = message.readInt32LE( 0 ); + var PacketCounter = message.readInt32LE( 4 ); + var SourceID = message.readInt32LE( 8 ); + + // check wheter the sender ID is correct + if (SenderID != 1295793) + throw 1; + + // get popoerties of this packet source + SourceProperties = ProtocollConfig.SourcesConfig[SourceID] +// console.log( 'SourceProperties: '); +// console.log( SourceProperties ); + + if (SourceProperties.datatype != 257) // ORTD.DATATYPE_FLOAT + throw 2; + + + + // check if the recved packet has the correct size + if ( message.length != 12+8*SourceProperties.NValues_send) + throw 2; + +// console.log('Disasm data '+ SourceProperties.NValues_send); + + // disassemble data-values + var ValuesBuffer = message.slice(12, 12+8*SourceProperties.NValues_send); + var Values = new Array(SourceProperties.NValues_send); + + for (i=0; i= this.DataBufferSize) { + this.WriteIndex = 0; + } + } + +// this.ReturnBuffer=ReturnBuffer; +// function ReturnBuffer() { +// return DataBuffer; +// } +} + diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/PF.sci b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/PF.sci new file mode 100644 index 00000000..c9b7cd1f --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/PF.sci @@ -0,0 +1,590 @@ + + + + +// +// +// A packet based communication interface from ORTD using UDP datagrams to e.g. +// nodejs. +// webappUDP.js is the counterpart that provides a web-interface +// +// Current Rev: 9 +// +// Revisions: +// +// 27.3.14 - possibility to reservate sources +// 3.4.14 - small re-arrangements +// 4.4.14 - Bugfixes +// 7.4.14 - Bugfix +// 12.6.14 - Bugfix +// 2.11.14 - Added group finalising packet +// 12.14 - Added Support for Groups +// + + +function [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName) + SourceID = PacketFramework.SourceID_counter; + + Source.SourceName = SourceName; + Source.SourceID = SourceID; + Source.NValues_send = NValues_send; + Source.datatype = datatype; + Source.Group = PacketFramework.ActiveGroup; + + // Add new source to the list + PacketFramework.Sources($+1) = Source; + + // inc counter + PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; +endfunction + +function [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName) + ParameterID = PacketFramework.Parameterid_counter; + + Parameter.ParameterName = ParameterName; + Parameter.ParameterID = ParameterID; + Parameter.NValues = NValues; + Parameter.datatype = datatype; + Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; + + // Add new source to the list + PacketFramework.Parameters($+1) = Parameter; + + // inc counters + PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; + PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; + + // return values + ParameterID = Parameter.ParameterID; + MemoryOfs = Parameter.MemoryOfs; +endfunction + +function [sim, PacketFramework, Parameter] = ld_PF_Parameter(sim, PacketFramework, NValues, datatype, ParameterName) // PARSEDOCU_BLOCK + // + // Define a parameter + // + // NValues - amount of data sets + // datatype - only ORTD.DATATYPE_FLOAT for now + // ParameterName - a unique string decribing the parameter + // + // + // + + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); + + // read data from global memory + [sim, readI] = ld_const(sim, 0, MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + datatype, NValues); +endfunction + + + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, SourceID); + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); + + printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + +endfunction + + + + +function [sim, PacketFramework] = ld_SendPacket(sim, PacketFramework, Signal, NValues_send, datatype, SourceName) // PARSEDOCU_BLOCK // PARSEDOCU_BLOCK + // + // Stream data - block + // + // Signal - the signal to stream + // NValues_send - the vector length of Signal + // datatype - only ORTD.DATATYPE_FLOAT by now + // SourceName - a unique string identifier descring the stream + // + // + // + + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); +endfunction + + + + +function [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName, Configuration) // PARSEDOCU_BLOCK + // + // Initialise an instance of the Packet Framework + // + // InstanceName - a unique string identifier for the instance + // Configuration must include the following properties: + // + // Configuration.UnderlyingProtocoll = "UDP" + // Configuration.DestHost + // Configuration.DestPort + // Configuration.LocalSocketHost + // Configuration.LocalSocketPort + // Configuration.SenderId (optional) + // Configuration.debugmode (bool, optional) + // + // + // Example: + // + // + // Configuration.UnderlyingProtocoll = "UDP"; + // Configuration.DestHost = "127.0.0.1"; + // Configuration.DestPort = 20000; + // Configuration.LocalSocketHost = "127.0.0.1"; + // Configuration.LocalSocketPort = 20001; + // Configuration.SenderId = 12; + // Configuration.debugmode = %f; + // [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); + // + // + // + // Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations + // + // + + // initialise structure for sources + PacketFramework.InstanceName = InstanceName; + PacketFramework.Configuration = Configuration; + + PacketFramework.Configuration.debugmode = %F; + try + PacketFramework.Configuration.debugmode = Configuration.debugmode; + catch + null; + end + + // disp(Configuration.UnderlyingProtocoll) + + if Configuration.UnderlyingProtocoll == 'UDP' + null; + else + error("PacketFramework: Only UDP supported up to now"); + end + + // possible packet sizes for UDP + PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; + + // sources + PacketFramework.SourceID_counter = 0; + PacketFramework.Sources = list(); + + // parameters + PacketFramework.Parameterid_counter = 0; + PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory + PacketFramework.Parameters = list(); + + + + PacketFramework.SenderID = 0; // default value + try + PacketFramework.SenderID = Configuration.SenderId; + catch + null; + end + + + // Groups + printf("Groups are enabled.\n"); + + DefaultGroup.ID = 0; + + PacketFramework.GroupIdCounter = 1; + PacketFramework.GroupList = list(); + PacketFramework.ActiveGroup = DefaultGroup; + +endfunction + + + +// added 23.12.14 +function [sim, PacketFramework, Group] = ld_PF_NewGroup(sim, PacketFramework, GroupName, opt) + + Group.ID = PacketFramework.GroupIdCounter; + Group.Name = GroupName; + + PacketFramework.GroupIdCounter = PacketFramework.GroupIdCounter + 1; + PacketFramework.GroupList( Group.ID ) = Group; + +endfunction + +// added 23.12.14 +function [sim, PacketFramework] = ld_PF_FinishGroup(sim, PacketFramework, Group) + [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, Group.ID); +endfunction + +// added 23.12.14 +function [sim, PacketFramework] = ld_PF_SetActiveGroup(sim, PacketFramework, Group) + PacketFramework.ActiveGroup = Group; +endfunction + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Group ID + [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources + [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); + + // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + + + // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); +endfunction + +function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOCU_BLOCK + // + // Finalise the instance. + // + // + + // The main real-time thread + function [sim] = ld_PF_InitUDP(sim, InstanceName, ParameterMemory) + + function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) + // This will run in a thread. Each time a UDP-packet is received + // one simulation step is performed. Herein, the packet is parsed + // and the contained parameters are stored into a memory. + + // Sync the simulation to incomming UDP-packets + [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); + + // disassemble packet's structure + [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... + outsizes=[1,1,1,TotalElemetsPerPacket], ... + outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); + + + + DisAsm_ = list(); + DisAsm_(4) = DisAsm(4); + [sim, DisAsm_(1)] = ld_Int32ToFloat(sim, 0, DisAsm(1) ); + [sim, DisAsm_(2)] = ld_Int32ToFloat(sim, 0, DisAsm(2) ); + [sim, DisAsm_(3)] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); + + + [sim, memofs] = ld_ArrayInt32(sim, 0, array=ParameterMemory.MemoryOfs, in=DisAsm(3) ); + [sim, Nelements] = ld_ArrayInt32(sim, 0, array=ParameterMemory.Sizes, in=DisAsm(3) ); + + [sim, memofs_] = ld_Int32ToFloat(sim, 0, memofs ); + [sim, Nelements_] = ld_Int32ToFloat(sim, 0, Nelements ); + + if PacketFramework.Configuration.debugmode then + // print the contents of the packet + [sim] = ld_printf(sim, 0, DisAsm_(1), "DisAsm(1) (SenderID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(2), "DisAsm(2) (Packet Counter) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(3), "DisAsm(3) (SourceID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(4), "DisAsm(4) (Signal) = ", TotalElemetsPerPacket); + + [sim] = ld_printf(sim, 0, memofs_ , "memofs = ", 1); + [sim] = ld_printf(sim, 0, memofs_ , "Nelements = ", 1); + end + + // Store the input data into a shared memory + [sim] = ld_WriteMemory2(sim, 0, data=DisAsm(4), index=memofs, ElementsToWrite=Nelements, ... + ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); + + + + // output of schematic + outlist = list(); + endfunction + + + + // start the node.js service from the subfolder webinterface + //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); + + TotalMemorySize = sum(PacketFramework.ParameterMemory.Sizes); + TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketSize = PacketFramework.PacketSize; + + // Open an UDP-Port in server mode + [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... + hostname=PacketFramework.Configuration.LocalSocketHost, ... + UDPPort=PacketFramework.Configuration.LocalSocketPort); + + // initialise a global memory for storing the input data for the computation + [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... + datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... + initial_data=[zeros(TotalMemorySize,1)], ... + visibility='global', useMutex=1); + + // Create thread for the receiver + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; + [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step + [sim, outlist, computation_finished] = ld_async_simulation(sim, 0, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = UDPReceiverThread, ... + TriggerSignal=startcalc, name=InstanceName+"Thread1", ... + ThreadPrioStruct, userdata=list() ); + + + endfunction + + + + + + // calc memory + MemoryOfs = []; + Sizes = []; + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + + Sizes = [Sizes; P.NValues]; + MemoryOfs = [MemoryOfs; P.MemoryOfs]; + end + + PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; + PacketFramework.ParameterMemory.Sizes = Sizes; + + // udp + [sim] = ld_PF_InitUDP(sim, PacketFramework.InstanceName, PacketFramework.ParameterMemory); + + // Send to group update notifications for each group (currently only one possible) + [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); + +endfunction + +function ld_PF_Export_js(PacketFramework, fname) // PARSEDOCU_BLOCK + // + // Export configuration of the defined protocoll (Sources, Parameters) + // into JSON-format. This is to be used by software that shall communicate + // to the real-time system. + // + // fname - The file name + // + // + + + + // TODO: Export Groups + + fd = mopen(fname,'wt'); + + + + mfprintf(fd,' { \n'); + + mfprintf(fd,' ""SenderID"" : ""%s"", \n', string(PacketFramework.SenderID) ); + + + + mfprintf(fd,' ""SourcesConfig"" : {\n'); + + for i=1:length(PacketFramework.Sources) + + + SourceID = PacketFramework.Sources(i).SourceID; + SourceName = PacketFramework.Sources(i).SourceName; + disp(SourceID ); + disp( SourceName ); + + + line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"", ""GroupID"" : ""%s"" } \n", ... + string(PacketFramework.Sources(i).SourceID), ... + string(PacketFramework.Sources(i).SourceName), ... + string(PacketFramework.Sources(i).NValues_send), ... + string(PacketFramework.Sources(i).datatype), string(PacketFramework.Sources(i).Group.ID) ); + + + + if i==length(PacketFramework.Sources) + // finalise the last entry without "," + printf('%s \n' , line); + mfprintf(fd,'%s', line); + else + printf('%s, \n' , line); + mfprintf(fd,'%s,', line); + end + + + end + + + + + mfprintf(fd,'} , \n '); + + + // Section ParametersConfig + mfprintf(fd,' ""ParametersConfig"" : {\n'); + + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + + printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); + + line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } \n", ... + string(PacketFramework.Parameters(i).ParameterID), ... + string(PacketFramework.Parameters(i).ParameterName), ... + string(PacketFramework.Parameters(i).NValues), ... + string(PacketFramework.Parameters(i).datatype) ); + + + + + if i==length(PacketFramework.Parameters) + // finalise the last entry without "," + printf('%s \n' , line); + mfprintf(fd,'%s', line); + else + printf('%s, \n' , line); + mfprintf(fd,'%s,', line); + end + + + end + + mfprintf(fd,' , \n'); + + // pause; + + // Section GroupsConfig + mfprintf(fd,' ""GroupsConfig"" : {\n'); + + // go through all parameters and create memories for all + for i=1:length(PacketFramework.GroupList) + + printf("export of Group %s \n",PacketFramework.GroupList(i).Name ); + + line=sprintf(" ""%s"" : { ""Name"" : ""%s"" } \n", ... + string(PacketFramework.GroupList(i).ID) , ... + string(PacketFramework.GroupList(i).Name) ... + ); + + + if i==length(PacketFramework.GroupList) + // finalise the last entry without "," + printf('%s \n' , line); + mfprintf(fd,'%s', line); + else + printf('%s, \n' , line); + mfprintf(fd,'%s,', line); + end + + + end + + mfprintf(fd,'}\n}'); + + mclose(fd); +endfunction + +// +// Added 27.3.14 +// + +function [sim, PacketFramework, SourceID]=ld_SendPacketReserve(sim, PacketFramework, NValues_send, datatype, SourceName) + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); +endfunction + +function [sim, PacketFramework]=ld_SendPacket2(sim, PacketFramework, Signal, SourceName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Sources) + S = PacketFramework.Sources(i); + if S.SourceName == SourceName + index = i; + printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); + break; + end + end + + if index == -1 + printf("SourceName = %s\n", SourceName); + error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); + end + + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); +endfunction + + + +function [sim, PacketFramework, ParameterID]=ld_PF_ParameterReserve(sim, PacketFramework, NValues, datatype, ParameterName) + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); +endfunction + + +function [sim, PacketFramework, Parameter]=ld_PF_Parameter2(sim, PacketFramework, ParameterName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + if P.ParameterName == ParameterName + index = i; + printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); + break; + end + end + + if index == -1 + printf("ParameterName = %s\n", ParameterName); + error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); + end + + // read data from global memory + [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + P.datatype, P.NValues); +endfunction + + diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/ProtocollConfig.json b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/ProtocollConfig.json new file mode 100644 index 00000000..9b532128 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/ProtocollConfig.json @@ -0,0 +1,9 @@ + { + "SenderID" : "1", + "SourcesConfig" : { + "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257", "GroupID" : "1" } , "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257", "GroupID" : "1" } , "2" : { "SourceName" : "Counter" , "NValues_send" : "1", "datatype" : "257", "GroupID" : "2" } } , + "ParametersConfig" : { + "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } , + "GroupsConfig" : { + "1" : { "Name" : "G1" } , "2" : { "Name" : "G2" } } +} \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/README b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/README new file mode 100644 index 00000000..10c48a96 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/README @@ -0,0 +1,54 @@ +This is a demonstration on how to set-up a web-interface to a control +system implemented using OpenRTDynamics.sf.net. As you'll notice +this is possible in a few lines of code, because the usage of +Javascript for implementing the gui-part and ORTD for the +real-time part including the UDP-communication allows an efficient +formulation of the algorithms. + +UDPio.sce is a sample ORTD-simulation and webappUDP.js the nodejs program +that connects to this simulation. Further, it provides a web-interface +accessible via http://localhost:8090/mainAuto.html . + +The template for the web-interface is stored in html/mainAuto.html. + + + +The files used in this example are: + +- UDPio.sce is a sample ORTD-simulation that simulates an osicillator + and a communication interface to node.js using UDPio +- webappUDP.js is the node.js program that connects to this simulation + via an UDP-interface and provides a web-interface on port 8090. +- Different templates for the html-page are stored in html/main*.html. + +- UDPio.ipar and UDPio.rpar are the compiled ORTD-programm files. +- PacketFramework.sce The file that implements the packet framework. + This will was also integrated into ORTD at Rev. 495 + +To make this example working: + +- The installation of node.js (nodejs.org) and its package manager + npm is required. +- Then, the "socket.io" node.js-package of node.js + is required that can be installed with "npm". To do this, call the + following commmand from the directory that contains webappUDP.js: + + $ cd webinterface + $ npm install socket.io + +- To start the set-up, two services / processes are required to run + at the same time: + +the ORTD-simulation is started by running + + $ sh run_UDPio.sh + +and the node.js part by + + $ cd webinterface + $ node webappUDP.js + +The order doesn't matter and it is also possible to start / stop each +service, which is one advantage of using stateless UDP-communication. + +- Finally, point your browser to http://localhost:8090/mainAuto.html . \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.ipar b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.ipar new file mode 100644 index 00000000..99921c10 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.ipar @@ -0,0 +1,5263 @@ + 1 + 1 + 901 + 10 + 0 + 0 + 5255 + 70 + 1 + 3 + 201 + 100 + 0 + 0 + 6 + 0 + 203 + 100 + 6 + 0 + 5217 + 70 + 100 + 101 + 5223 + 70 + 12 + 0 + 60023 + 201 + 0 + 0 + 1 + 0 + 15011 + 203 + 5211 + 70 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 5148 + 0 + 21 + 1 + 5160 + 0 + 1 + 70 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 5147 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 21 + 4 + 4 + 0 + 19 + 0 + 22 + 4 + 23 + 0 + 4 + 0 + 900 + 10 + 27 + 0 + 5076 + 70 + 0 + 0 + 0 + 0 + 18 + 77 + 97 + 105 + 110 + 82 + 101 + 97 + 108 + 116 + 105 + 109 + 101 + 84 + 104 + 114 + 101 + 97 + 100 + 3 + 2 + 0 + -1 + 1 + 67 + 201 + 100 + 0 + 0 + 6 + 1 + 203 + 100 + 6 + 1 + 72 + 0 + 205 + 100 + 78 + 1 + 6 + 1 + 207 + 100 + 84 + 2 + 43 + 0 + 209 + 100 + 127 + 2 + 6 + 1 + 211 + 100 + 133 + 3 + 43 + 0 + 213 + 100 + 176 + 3 + 6 + 1 + 215 + 100 + 182 + 4 + 43 + 0 + 217 + 100 + 225 + 4 + 25 + 0 + 218 + 100 + 250 + 4 + 13 + 0 + 219 + 100 + 263 + 4 + 29 + 0 + 222 + 100 + 292 + 4 + 6 + 2 + 224 + 100 + 298 + 6 + 6 + 2 + 226 + 100 + 304 + 8 + 8 + 3 + 228 + 100 + 312 + 11 + 6 + 1 + 230 + 100 + 318 + 12 + 8 + 3 + 232 + 100 + 326 + 15 + 6 + 1 + 234 + 100 + 332 + 16 + 6 + 1 + 236 + 100 + 338 + 17 + 8 + 0 + 238 + 100 + 346 + 17 + 6 + 0 + 240 + 100 + 352 + 17 + 6 + 1 + 242 + 100 + 358 + 18 + 6 + 0 + 244 + 100 + 364 + 18 + 6 + 1 + 246 + 100 + 370 + 19 + 6 + 0 + 248 + 100 + 376 + 19 + 76 + 0 + 250 + 100 + 452 + 19 + 9 + 0 + 252 + 100 + 461 + 19 + 155 + 0 + 253 + 100 + 616 + 19 + 6 + 1 + 255 + 100 + 622 + 20 + 8 + 0 + 257 + 100 + 630 + 20 + 6 + 0 + 259 + 100 + 636 + 20 + 6 + 1 + 261 + 100 + 642 + 21 + 6 + 0 + 263 + 100 + 648 + 21 + 6 + 1 + 265 + 100 + 654 + 22 + 6 + 0 + 267 + 100 + 660 + 22 + 76 + 0 + 269 + 100 + 736 + 22 + 9 + 0 + 271 + 100 + 745 + 22 + 155 + 0 + 272 + 100 + 900 + 22 + 6 + 1 + 274 + 100 + 906 + 23 + 8 + 0 + 276 + 100 + 914 + 23 + 6 + 0 + 278 + 100 + 920 + 23 + 6 + 1 + 280 + 100 + 926 + 24 + 6 + 0 + 282 + 100 + 932 + 24 + 6 + 1 + 284 + 100 + 938 + 25 + 6 + 0 + 286 + 100 + 944 + 25 + 6 + 1 + 288 + 100 + 950 + 26 + 6 + 0 + 290 + 100 + 956 + 26 + 76 + 0 + 292 + 100 + 1032 + 26 + 9 + 0 + 294 + 100 + 1041 + 26 + 155 + 0 + 295 + 100 + 1196 + 26 + 1437 + 26 + 298 + 100 + 2633 + 52 + 143 + 0 + 299 + 100 + 2776 + 52 + 43 + 13 + 300 + 100 + 2819 + 65 + 6 + 1 + 302 + 100 + 2825 + 66 + 1051 + 0 + 304 + 100 + 3876 + 66 + 6 + 1 + 306 + 100 + 3882 + 67 + 8 + 0 + 308 + 100 + 3890 + 67 + 6 + 0 + 310 + 100 + 3896 + 67 + 6 + 1 + 312 + 100 + 3902 + 68 + 6 + 0 + 314 + 100 + 3908 + 68 + 6 + 1 + 316 + 100 + 3914 + 69 + 6 + 0 + 318 + 100 + 3920 + 69 + 6 + 1 + 320 + 100 + 3926 + 70 + 6 + 0 + 322 + 100 + 3932 + 70 + 76 + 0 + 324 + 100 + 4008 + 70 + 9 + 0 + 326 + 100 + 4017 + 70 + 155 + 0 + 100 + 101 + 4172 + 70 + 500 + 0 + 40 + 201 + 0 + 1 + 1 + 0 + 15102 + 203 + 66 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 3 + 0 + 21 + 1 + 15 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 2 + 1 + 0 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 15007 + 207 + 37 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 26 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 209 + 0 + 1 + 1 + 0 + 15007 + 211 + 37 + 0 + 1 + 0 + 0 + 257 + 10 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 26 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 213 + 0 + 1 + 1 + 0 + 15007 + 215 + 37 + 0 + 1 + 0 + 0 + 257 + 2 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 26 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 217 + 19 + 0 + 1 + 0 + 1 + 17 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 32 + 170 + 218 + 7 + 0 + 1 + 0 + 2 + 5 + 84 + 101 + 115 + 116 + 32 + 170 + 219 + 23 + 0 + 1 + 0 + 10 + 21 + 65 + 32 + 118 + 101 + 99 + 116 + 111 + 114 + 105 + 97 + 108 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 12 + 222 + 0 + 2 + 1 + 0 + 12 + 224 + 0 + 2 + 1 + 0 + 30 + 226 + 2 + 3 + 1 + 0 + 0 + 1 + 20 + 228 + 0 + 1 + 1 + 0 + 30 + 230 + 2 + 3 + 1 + 0 + 0 + 1 + 20 + 232 + 0 + 1 + 1 + 0 + 40 + 234 + 0 + 1 + 1 + 0 + 60005 + 236 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 238 + 0 + 0 + 1 + 0 + 40 + 240 + 0 + 1 + 1 + 0 + 60031 + 242 + 0 + 0 + 1 + 0 + 40 + 244 + 0 + 1 + 1 + 0 + 60031 + 246 + 0 + 0 + 1 + 0 + 39011 + 248 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 250 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 252 + 149 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 37 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 36 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 253 + 0 + 1 + 1 + 0 + 60005 + 255 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 257 + 0 + 0 + 1 + 0 + 40 + 259 + 0 + 1 + 1 + 0 + 60031 + 261 + 0 + 0 + 1 + 0 + 40 + 263 + 0 + 1 + 1 + 0 + 60031 + 265 + 0 + 0 + 1 + 0 + 39011 + 267 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 269 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 271 + 149 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 37 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 36 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 272 + 0 + 1 + 1 + 0 + 60005 + 274 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 276 + 0 + 0 + 1 + 0 + 40 + 278 + 0 + 1 + 1 + 0 + 60031 + 280 + 0 + 0 + 1 + 0 + 40 + 282 + 0 + 1 + 1 + 0 + 60031 + 284 + 0 + 0 + 1 + 0 + 40 + 286 + 0 + 1 + 1 + 0 + 60031 + 288 + 0 + 0 + 1 + 0 + 39011 + 290 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 16 + 4 + 2 + 2 + 2 + 2 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 292 + 3 + 0 + 1 + 0 + 1 + 0 + 16 + 39004 + 294 + 149 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 37 + 0 + 2 + 16 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 16 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 36 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 15002 + 295 + 1431 + 26 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 4 + 0 + 11 + 4 + 4 + 0 + 3 + 0 + 12 + 4 + 7 + 0 + 4 + 0 + 13 + 4 + 11 + 0 + 3 + 0 + 20 + 4 + 14 + 0 + 4 + 0 + 21 + 1 + 18 + 0 + 1 + 4 + 900 + 10 + 19 + 4 + 1056 + 12 + 901 + 10 + 1075 + 16 + 150 + 5 + 902 + 10 + 1225 + 21 + 150 + 5 + 3 + 1 + 1 + 4 + 2 + 1 + 4 + 3 + 257 + 257 + 257 + 2 + 257 + 257 + 3 + 3 + 4 + 3 + 4 + 1 + 29 + 204 + 100 + 0 + 0 + 8 + 0 + 209 + 100 + 8 + 0 + 6 + 1 + 211 + 100 + 14 + 1 + 8 + 0 + 213 + 100 + 22 + 1 + 6 + 0 + 215 + 100 + 28 + 1 + 6 + 1 + 217 + 100 + 34 + 2 + 6 + 0 + 219 + 100 + 40 + 2 + 6 + 1 + 221 + 100 + 46 + 3 + 6 + 0 + 223 + 100 + 52 + 3 + 76 + 0 + 225 + 100 + 128 + 3 + 9 + 0 + 227 + 100 + 137 + 3 + 155 + 0 + 228 + 100 + 292 + 3 + 6 + 1 + 230 + 100 + 298 + 4 + 8 + 0 + 232 + 100 + 306 + 4 + 6 + 0 + 234 + 100 + 312 + 4 + 6 + 1 + 236 + 100 + 318 + 5 + 6 + 0 + 238 + 100 + 324 + 5 + 6 + 1 + 240 + 100 + 330 + 6 + 6 + 0 + 242 + 100 + 336 + 6 + 6 + 1 + 244 + 100 + 342 + 7 + 6 + 0 + 246 + 100 + 348 + 7 + 76 + 0 + 248 + 100 + 424 + 7 + 9 + 0 + 250 + 100 + 433 + 7 + 155 + 0 + 251 + 100 + 588 + 7 + 12 + 0 + 252 + 100 + 600 + 7 + 8 + 2 + 254 + 100 + 608 + 9 + 6 + 1 + 255 + 100 + 614 + 10 + 6 + 2 + 257 + 100 + 620 + 12 + 8 + 0 + 100 + 101 + 628 + 12 + 252 + 0 + 60002 + 204 + 2 + 0 + 1 + 0 + 4 + 0 + 40 + 209 + 0 + 1 + 1 + 0 + 60005 + 211 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 213 + 0 + 0 + 1 + 0 + 40 + 215 + 0 + 1 + 1 + 0 + 60031 + 217 + 0 + 0 + 1 + 0 + 40 + 219 + 0 + 1 + 1 + 0 + 60031 + 221 + 0 + 0 + 1 + 0 + 39011 + 223 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 225 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 227 + 149 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 37 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 36 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 228 + 0 + 1 + 1 + 0 + 60005 + 230 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 232 + 0 + 0 + 1 + 0 + 40 + 234 + 0 + 1 + 1 + 0 + 60031 + 236 + 0 + 0 + 1 + 0 + 40 + 238 + 0 + 1 + 1 + 0 + 60031 + 240 + 0 + 0 + 1 + 0 + 40 + 242 + 0 + 1 + 1 + 0 + 60031 + 244 + 0 + 0 + 1 + 0 + 39011 + 246 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 16 + 4 + 2 + 2 + 2 + 2 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 248 + 3 + 0 + 1 + 0 + 1 + 0 + 16 + 39004 + 250 + 149 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 37 + 0 + 2 + 16 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 16 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 36 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 170 + 251 + 6 + 0 + 1 + 0 + 1 + 4 + 45 + 45 + 45 + 32 + 60019 + 252 + 2 + 2 + 1 + 0 + 2 + 10 + 40 + 254 + 0 + 1 + 1 + 0 + 12 + 255 + 0 + 2 + 1 + 0 + 60003 + 257 + 2 + 0 + 1 + 0 + 4 + 0 + 0 + 31 + 8 + 4 + 1 + 0 + 0 + 2 + 0 + 204 + 204 + 0 + 0 + 209 + 209 + 0 + 0 + 211 + 211 + 0 + 0 + 211 + 211 + 0 + 0 + 213 + 213 + 0 + 0 + 215 + 215 + 0 + 0 + 217 + 217 + 0 + 0 + 219 + 219 + 0 + 0 + 221 + 221 + 0 + 0 + 221 + 221 + 0 + 0 + 223 + 223 + 0 + 0 + 213 + 213 + 0 + 0 + 223 + 223 + 1 + 0 + 217 + 217 + 0 + 0 + 223 + 223 + 2 + 0 + 204 + 204 + 0 + 0 + 223 + 223 + 3 + 0 + 223 + 223 + 0 + 0 + 227 + 227 + 0 + 0 + 225 + 225 + 0 + 0 + 227 + 227 + 1 + 0 + 228 + 228 + 0 + 0 + 230 + 230 + 0 + 0 + 230 + 230 + 0 + 0 + 232 + 232 + 0 + 0 + 234 + 234 + 0 + 0 + 236 + 236 + 0 + 0 + 238 + 238 + 0 + 0 + 240 + 240 + 0 + 0 + 242 + 242 + 0 + 0 + 244 + 244 + 0 + 0 + 244 + 244 + 0 + 0 + 246 + 246 + 0 + 0 + 232 + 232 + 0 + 0 + 246 + 246 + 1 + 0 + 236 + 236 + 0 + 0 + 246 + 246 + 2 + 0 + 240 + 240 + 0 + 0 + 246 + 246 + 3 + 0 + 246 + 246 + 0 + 0 + 250 + 250 + 0 + 0 + 248 + 248 + 0 + 0 + 250 + 250 + 1 + 0 + 204 + 204 + 0 + 0 + 251 + 251 + 0 + 0 + 204 + 204 + 0 + 0 + 255 + 255 + 0 + 0 + 254 + 254 + 0 + 0 + 255 + 255 + 1 + 0 + 255 + 255 + 0 + 0 + 257 + 257 + 0 + 0 + 204 + 204 + 1 + 0 + 257 + 257 + 1 + 0 + 204 + 204 + 2 + 0 + 257 + 257 + 2 + 0 + 204 + 204 + 3 + 0 + 257 + 257 + 3 + 0 + 252 + 252 + 0 + 1 + 0 + 0 + 0 + 0 + 257 + 257 + 0 + 1 + 0 + 0 + 1 + 1 + 6 + 204 + 100 + 0 + 0 + 8 + 0 + 209 + 100 + 8 + 0 + 8 + 2 + 211 + 100 + 16 + 2 + 6 + 1 + 212 + 100 + 22 + 3 + 6 + 2 + 214 + 100 + 28 + 5 + 8 + 0 + 100 + 101 + 36 + 5 + 76 + 0 + 60002 + 204 + 2 + 0 + 1 + 0 + 4 + 0 + 60019 + 209 + 2 + 2 + 1 + 0 + 2 + 10 + 40 + 211 + 0 + 1 + 1 + 0 + 12 + 212 + 0 + 2 + 1 + 0 + 60003 + 214 + 2 + 0 + 1 + 0 + 4 + 0 + 0 + 9 + 8 + 4 + 1 + 0 + 0 + 2 + 0 + 204 + 204 + 0 + 0 + 204 + 204 + 1 + 0 + 212 + 212 + 0 + 0 + 211 + 211 + 0 + 0 + 212 + 212 + 1 + 0 + 204 + 204 + 0 + 0 + 214 + 214 + 0 + 0 + 212 + 212 + 0 + 0 + 214 + 214 + 1 + 0 + 204 + 204 + 2 + 0 + 214 + 214 + 2 + 0 + 204 + 204 + 3 + 0 + 214 + 214 + 3 + 0 + 209 + 209 + 0 + 1 + 0 + 0 + 0 + 0 + 214 + 214 + 0 + 1 + 0 + 0 + 1 + 1 + 6 + 204 + 100 + 0 + 0 + 8 + 0 + 209 + 100 + 8 + 0 + 8 + 2 + 211 + 100 + 16 + 2 + 6 + 1 + 212 + 100 + 22 + 3 + 6 + 2 + 214 + 100 + 28 + 5 + 8 + 0 + 100 + 101 + 36 + 5 + 76 + 0 + 60002 + 204 + 2 + 0 + 1 + 0 + 4 + 0 + 60019 + 209 + 2 + 2 + 1 + 0 + 2 + 10 + 40 + 211 + 0 + 1 + 1 + 0 + 12 + 212 + 0 + 2 + 1 + 0 + 60003 + 214 + 2 + 0 + 1 + 0 + 4 + 0 + 0 + 9 + 8 + 4 + 1 + 0 + 0 + 2 + 0 + 204 + 204 + 0 + 0 + 204 + 204 + 2 + 0 + 212 + 212 + 0 + 0 + 211 + 211 + 0 + 0 + 212 + 212 + 1 + 0 + 204 + 204 + 0 + 0 + 214 + 214 + 0 + 0 + 204 + 204 + 1 + 0 + 214 + 214 + 1 + 0 + 212 + 212 + 0 + 0 + 214 + 214 + 2 + 0 + 204 + 204 + 3 + 0 + 214 + 214 + 3 + 0 + 209 + 209 + 0 + 1 + 0 + 0 + 0 + 0 + 214 + 214 + 0 + 1 + 0 + 0 + 1 + 39001 + 298 + 137 + 0 + 1 + 0 + 1 + 10 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 14 + 4 + 4 + 0 + 2 + 0 + 15 + 4 + 6 + 0 + 2 + 0 + 20 + 4 + 8 + 0 + 27 + 0 + 21 + 1 + 35 + 0 + 1 + 0 + 30 + 4 + 36 + 0 + 37 + 0 + 31 + 4 + 73 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 1 + 2 + 26 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 10 + 0 + 1 + 20001 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 36 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 1 + 0 + 15005 + 299 + 37 + 13 + 1 + 0 + 0 + 257 + 13 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 26 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 300 + 0 + 1 + 1 + 0 + 15011 + 302 + 1045 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 982 + 0 + 21 + 1 + 994 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 981 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 21 + 4 + 4 + 0 + 21 + 0 + 22 + 4 + 25 + 0 + 4 + 0 + 900 + 10 + 29 + 0 + 908 + 0 + 0 + 0 + 0 + 0 + 20 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 84 + 104 + 114 + 101 + 97 + 100 + 49 + 3 + 2 + 0 + -1 + 1 + 17 + 201 + 100 + 0 + 0 + 131 + 0 + 204 + 100 + 131 + 0 + 76 + 0 + 209 + 100 + 207 + 0 + 6 + 0 + 211 + 100 + 213 + 0 + 6 + 0 + 213 + 100 + 219 + 0 + 6 + 0 + 215 + 100 + 225 + 0 + 82 + 0 + 217 + 100 + 307 + 0 + 82 + 0 + 219 + 100 + 389 + 0 + 6 + 0 + 221 + 100 + 395 + 0 + 6 + 0 + 223 + 100 + 401 + 0 + 37 + 0 + 224 + 100 + 438 + 0 + 37 + 0 + 225 + 100 + 475 + 0 + 37 + 0 + 226 + 100 + 512 + 0 + 37 + 0 + 227 + 100 + 549 + 0 + 36 + 0 + 228 + 100 + 585 + 0 + 36 + 0 + 229 + 100 + 621 + 0 + 43 + 0 + 100 + 101 + 664 + 0 + 140 + 0 + 39003 + 201 + 125 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 3 + 0 + 12 + 4 + 4 + 0 + 1 + 0 + 13 + 4 + 5 + 0 + 3 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 19 + 0 + 21 + 1 + 31 + 0 + 1 + 0 + 30 + 4 + 32 + 0 + 37 + 0 + 0 + 2 + 1396 + 6 + 0 + 2 + 6 + 6 + 1 + 1 + 1 + 1 + 18 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 1 + 1396 + 1 + 6 + 0 + 36 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 39012 + 204 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 5 + 0 + 12 + 4 + 7 + 0 + 2 + 0 + 13 + 4 + 9 + 0 + 5 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 1 + 1396 + 4 + 1 + 1 + 1 + 173 + 1 + 6 + 4 + 2 + 2 + 2 + 257 + 1 + 1 + 1 + 2 + 0 + 0 + 60032 + 209 + 0 + 0 + 1 + 0 + 60032 + 211 + 0 + 0 + 1 + 0 + 60032 + 213 + 0 + 0 + 1 + 0 + 60304 + 215 + 76 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 13 + 0 + 21 + 1 + 25 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 12 + 1 + 1 + 10 + 4 + 0 + 0 + 4 + 0 + 3 + 1 + 2 + 12 + 0 + 60304 + 217 + 76 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 13 + 0 + 21 + 1 + 25 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 12 + 1 + 1 + 10 + 4 + 0 + 0 + 4 + 0 + 3 + 1 + 10 + 2 + 0 + 60032 + 219 + 0 + 0 + 1 + 0 + 60032 + 221 + 0 + 0 + 1 + 0 + 170 + 223 + 31 + 0 + 1 + 0 + 1 + 29 + 68 + 105 + 115 + 65 + 115 + 109 + 40 + 49 + 41 + 32 + 40 + 83 + 101 + 110 + 100 + 101 + 114 + 73 + 68 + 41 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 61 + 32 + 170 + 224 + 31 + 0 + 1 + 0 + 1 + 29 + 68 + 105 + 115 + 65 + 115 + 109 + 40 + 50 + 41 + 32 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 32 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 41 + 32 + 61 + 32 + 170 + 225 + 31 + 0 + 1 + 0 + 1 + 29 + 68 + 105 + 115 + 65 + 115 + 109 + 40 + 51 + 41 + 32 + 40 + 83 + 111 + 117 + 114 + 99 + 101 + 73 + 68 + 41 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 61 + 32 + 170 + 226 + 31 + 0 + 1 + 0 + 173 + 29 + 68 + 105 + 115 + 65 + 115 + 109 + 40 + 52 + 41 + 32 + 40 + 83 + 105 + 103 + 110 + 97 + 108 + 41 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 61 + 32 + 170 + 227 + 30 + 0 + 1 + 0 + 1 + 28 + 109 + 101 + 109 + 111 + 102 + 115 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 61 + 32 + 170 + 228 + 30 + 0 + 1 + 0 + 1 + 28 + 78 + 101 + 108 + 101 + 109 + 101 + 110 + 116 + 115 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 61 + 32 + 15008 + 229 + 37 + 0 + 1 + 0 + 0 + 257 + 173 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 26 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 0 + 17 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 204 + 204 + 0 + 0 + 204 + 204 + 0 + 0 + 209 + 209 + 0 + 0 + 204 + 204 + 1 + 0 + 211 + 211 + 0 + 0 + 204 + 204 + 2 + 0 + 213 + 213 + 0 + 0 + 204 + 204 + 2 + 0 + 215 + 215 + 0 + 0 + 204 + 204 + 2 + 0 + 217 + 217 + 0 + 0 + 215 + 215 + 0 + 0 + 219 + 219 + 0 + 0 + 217 + 217 + 0 + 0 + 221 + 221 + 0 + 0 + 209 + 209 + 0 + 0 + 223 + 223 + 0 + 0 + 211 + 211 + 0 + 0 + 224 + 224 + 0 + 0 + 213 + 213 + 0 + 0 + 225 + 225 + 0 + 0 + 204 + 204 + 3 + 0 + 226 + 226 + 0 + 0 + 219 + 219 + 0 + 0 + 227 + 227 + 0 + 0 + 219 + 219 + 0 + 0 + 228 + 228 + 0 + 0 + 204 + 204 + 3 + 0 + 229 + 229 + 0 + 0 + 215 + 215 + 0 + 0 + 229 + 229 + 1 + 0 + 217 + 217 + 0 + 0 + 229 + 229 + 2 + 0 + 40 + 304 + 0 + 1 + 1 + 0 + 60005 + 306 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 308 + 0 + 0 + 1 + 0 + 40 + 310 + 0 + 1 + 1 + 0 + 60031 + 312 + 0 + 0 + 1 + 0 + 40 + 314 + 0 + 1 + 1 + 0 + 60031 + 316 + 0 + 0 + 1 + 0 + 40 + 318 + 0 + 1 + 1 + 0 + 60031 + 320 + 0 + 0 + 1 + 0 + 39011 + 322 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 16 + 4 + 2 + 2 + 2 + 2 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 324 + 3 + 0 + 1 + 0 + 1 + 0 + 16 + 39004 + 326 + 149 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 37 + 0 + 2 + 16 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 16 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 36 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 0 + 62 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 207 + 207 + 0 + 0 + 209 + 209 + 0 + 0 + 211 + 211 + 0 + 0 + 213 + 213 + 0 + 0 + 215 + 215 + 0 + 0 + 207 + 207 + 0 + 0 + 217 + 217 + 0 + 0 + 215 + 215 + 0 + 0 + 218 + 218 + 0 + 0 + 211 + 211 + 0 + 0 + 219 + 219 + 0 + 0 + 207 + 207 + 0 + 0 + 222 + 222 + 0 + 0 + 222 + 222 + 0 + 0 + 224 + 224 + 0 + 0 + 224 + 224 + 0 + 0 + 226 + 226 + 0 + 0 + 226 + 226 + 0 + 0 + 228 + 228 + 0 + 0 + 228 + 228 + 0 + 0 + 224 + 224 + 1 + 0 + 226 + 226 + 0 + 0 + 230 + 230 + 0 + 0 + 230 + 230 + 0 + 0 + 232 + 232 + 0 + 0 + 232 + 232 + 0 + 0 + 222 + 222 + 1 + 0 + 234 + 234 + 0 + 0 + 236 + 236 + 0 + 0 + 236 + 236 + 0 + 0 + 238 + 238 + 0 + 0 + 240 + 240 + 0 + 0 + 242 + 242 + 0 + 0 + 244 + 244 + 0 + 0 + 246 + 246 + 0 + 0 + 246 + 246 + 0 + 0 + 248 + 248 + 0 + 0 + 238 + 238 + 0 + 0 + 248 + 248 + 1 + 0 + 242 + 242 + 0 + 0 + 248 + 248 + 2 + 0 + 230 + 230 + 0 + 0 + 248 + 248 + 3 + 0 + 248 + 248 + 0 + 0 + 252 + 252 + 0 + 0 + 250 + 250 + 0 + 0 + 252 + 252 + 1 + 0 + 253 + 253 + 0 + 0 + 255 + 255 + 0 + 0 + 255 + 255 + 0 + 0 + 257 + 257 + 0 + 0 + 259 + 259 + 0 + 0 + 261 + 261 + 0 + 0 + 263 + 263 + 0 + 0 + 265 + 265 + 0 + 0 + 265 + 265 + 0 + 0 + 267 + 267 + 0 + 0 + 257 + 257 + 0 + 0 + 267 + 267 + 1 + 0 + 261 + 261 + 0 + 0 + 267 + 267 + 2 + 0 + 226 + 226 + 0 + 0 + 267 + 267 + 3 + 0 + 267 + 267 + 0 + 0 + 271 + 271 + 0 + 0 + 269 + 269 + 0 + 0 + 271 + 271 + 1 + 0 + 272 + 272 + 0 + 0 + 274 + 274 + 0 + 0 + 274 + 274 + 0 + 0 + 276 + 276 + 0 + 0 + 278 + 278 + 0 + 0 + 280 + 280 + 0 + 0 + 282 + 282 + 0 + 0 + 284 + 284 + 0 + 0 + 286 + 286 + 0 + 0 + 288 + 288 + 0 + 0 + 288 + 288 + 0 + 0 + 290 + 290 + 0 + 0 + 276 + 276 + 0 + 0 + 290 + 290 + 1 + 0 + 280 + 280 + 0 + 0 + 290 + 290 + 2 + 0 + 284 + 284 + 0 + 0 + 290 + 290 + 3 + 0 + 290 + 290 + 0 + 0 + 294 + 294 + 0 + 0 + 292 + 292 + 0 + 0 + 294 + 294 + 1 + 0 + 230 + 230 + 0 + 0 + 295 + 295 + 0 + 0 + 226 + 226 + 0 + 0 + 295 + 295 + 1 + 0 + 0 + 0 + 0 + 2 + 298 + 298 + 0 + 0 + 0 + 0 + 0 + 2 + 299 + 299 + 0 + 0 + 300 + 300 + 0 + 0 + 302 + 302 + 0 + 0 + 304 + 304 + 0 + 0 + 306 + 306 + 0 + 0 + 306 + 306 + 0 + 0 + 308 + 308 + 0 + 0 + 310 + 310 + 0 + 0 + 312 + 312 + 0 + 0 + 314 + 314 + 0 + 0 + 316 + 316 + 0 + 0 + 318 + 318 + 0 + 0 + 320 + 320 + 0 + 0 + 320 + 320 + 0 + 0 + 322 + 322 + 0 + 0 + 308 + 308 + 0 + 0 + 322 + 322 + 1 + 0 + 312 + 312 + 0 + 0 + 322 + 322 + 2 + 0 + 316 + 316 + 0 + 0 + 322 + 322 + 3 + 0 + 322 + 322 + 0 + 0 + 326 + 326 + 0 + 0 + 324 + 324 + 0 + 0 + 326 + 326 + 1 + 70 + 0 + 1 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.rpar b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.rpar new file mode 100644 index 00000000..e922d927 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.rpar @@ -0,0 +1,70 @@ +0.0370370370370370349810685 +1.0000000000000000000000000 +2.0000000000000000000000000 +12.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +0.1000000000000000055511151 +-1.0000000000000000000000000 +1.0000000000000000000000000 +0.1000000000000000055511151 +0.1000000000000000055511151 +-1.0000000000000000000000000 +1.0000000000000000000000000 +0.5999999999999999777955395 +1.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +0.0000000000000000000000000 +2.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +2.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +2.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +2.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +3.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.sce b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.sce new file mode 100644 index 00000000..e4108ce4 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.sce @@ -0,0 +1,261 @@ +// +// +// Example for a communication interface from ORTD using UDP datagrams to e.g. +// nodejs. +// The file webinterface/webappUDP.js is the counterpart that provides a +// web-interface to control a oscillator-system in this example. +// +// For more details, please consider the readme-file. +// +// Rev 1 +// + +// The name of the program +ProgramName = 'UDPio'; // must be the filename without .sce + + + + + + + + + + + + + +// And example-system that is controlled via UDP and one step further with the Web-gui +// Superblock: A more complex oscillator with damping +function [sim, x,v] = damped_oscillator(sim, u) + // create feedback signals + [sim,x_feedback] = libdyn_new_feedback(sim); + + [sim,v_feedback] = libdyn_new_feedback(sim); + + // use this as a normal signal + [sim,a] = ld_add(sim, ev, list(u, x_feedback), [1, -1]); + [sim,a] = ld_add(sim, ev, list(a, v_feedback), [1, -1]); + + [sim,v] = ld_ztf(sim, ev, a, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,v_gain] = ld_gain(sim, ev, v, 0.1); + + // close loop v_gain = v_feedback + [sim] = libdyn_close_loop(sim, v_gain, v_feedback); + + + [sim,x] = ld_ztf(sim, ev, v, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,x_gain] = ld_gain(sim, ev, x, 0.6); + + // close loop x_gain = x_feedback + [sim] = libdyn_close_loop(sim, x_gain, x_feedback); +endfunction + + + + + + +function [sim, outlist, active_state, x_global_kp1, userdata] = state_mainfn(sim, inlist, x_global, state, statename, userdata) + // This function is called multiple times -- once to define each state. + // At runtime, all states will become different nested simulations of + // which only one is active a a time. Switching + // between them represents state changing, thus each simulation + // represents a certain state. + + PacketFramework = userdata(1); + + printf("Defining state %s (#%d) ...\n", statename, state); + + // define names for the first event in the simulation + events = 0; + + + // demultiplex x_global that is a state variable shared among the different states + [sim, x_global] = ld_demux(sim, events, vecsize=4, invec=x_global); + + + // The signals "active_state" is used to indicate state switching: A value > 0 means the + // the state enumed by "active_state" shall be activated in the next time step. + // A value less or equal to zero causes the statemachine to stay in its currently active + // state + + select state + case 1 // state 1 + + [sim, PacketFramework, Group2] = ld_PF_NewGroup(sim, PacketFramework, GroupName="G2", opt=list() ); + [sim, PacketFramework]=ld_PF_SetActiveGroup(sim, PacketFramework, Group2); + + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x_global(1), NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Counter"); + + [sim, PacketFramework]=ld_PF_FinishGroup(sim, PacketFramework, Group2); + + [sim] = ld_printf(sim, ev, x_global(1), "--- ", 1); + + // wait 10 simulation steps and then switch to state 2 + [sim, active_state] = ld_steps(sim, events, activation_simsteps=[10], values=[-1,2]); // -1 means stay within this state. 2 means go to state # 2 + [sim, x_global(1)] = ld_add_ofs(sim, events, x_global(1), 1); // increase counter 1 by 1 + case 2 // state 2 + // wait 10 simulation steps and then switch to state 3 + [sim, active_state] = ld_steps(sim, events, activation_simsteps=[10], values=[-1,3]); + [sim, x_global(2)] = ld_add_ofs(sim, events, x_global(2), 1); // increase counter 2 by 1 + case 3 // state 3 + // wait 10 simulation steps and then switch to state 1 + [sim, active_state] = ld_steps(sim, events, activation_simsteps=[10], values=[-1,1]); + [sim, x_global(3)] = ld_add_ofs(sim, events, x_global(3), 1); // increase counter 3 by 1 + end + + // multiplex the new global states + [sim, x_global_kp1] = ld_mux(sim, events, vecsize=4, inlist=x_global); + + userdata(1) = PacketFramework; + + // the user defined output signals of this nested simulation + outlist = list(); +endfunction + + +// The main real-time thread +function [sim, outlist, userdata] = Thread_MainRT(sim, inlist, userdata) + // This will run in a thread + [sim, Tpause] = ld_const(sim, ev, 1/27); // The sampling time that is constant at 20 Hz in this example + [sim, out] = ld_ClockSync(sim, ev, in=Tpause); // synchronise this simulation + + // + // Add you own control system here + // + + + Configuration.UnderlyingProtocoll = "UDP"; + Configuration.DestHost = "127.0.0.1"; + Configuration.DestPort = 20000; + Configuration.LocalSocketHost = "127.0.0.1"; + Configuration.LocalSocketPort = 20001; + Configuration.debugmode = %t; + Configuration.SenderId = 1; + + [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="RemoteControl", Configuration); + + + // Add a parameter for controlling the oscillator + [sim, PacketFramework, Input]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Oscillator input"); + + // some some more parameters + [sim, PacketFramework, AParVector]=ld_PF_Parameter(sim, PacketFramework, NValues=10, datatype=ORTD.DATATYPE_FLOAT, ParameterName="A vectorial parameter"); + [sim, PacketFramework, par2]=ld_PF_Parameter(sim, PacketFramework, NValues=2, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Test"); + + // printf these parameters + [sim] = ld_printf(sim, ev, Input, "Oscillator input ", 1); + [sim] = ld_printf(sim, ev, par2, "Test ", 2); + [sim] = ld_printf(sim, ev, AParVector, "A vectorial parameter", 10); + + + + + + // The system to control + T_a = 0.1; [sim, x,v] = damped_oscillator(sim, Input); + + + // Stream the data of the oscillator + [sim, PacketFramework, Group1] = ld_PF_NewGroup(sim, PacketFramework, GroupName="G1", opt=list() ); + [sim, PacketFramework]=ld_PF_SetActiveGroup(sim, PacketFramework, Group1); + + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X") + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V") + + [sim, PacketFramework]=ld_PF_FinishGroup(sim, PacketFramework, Group1); + + + + + // set-up three states represented by three nested simulations + [sim, outlist, x_global, active_state,userdata] = ld_statemachine(sim, 0, ... + inlist=list( x, v ), .. + insizes=[1,1], outsizes=[], ... + intypes=[ORTD.DATATYPE_FLOAT,ORTD.DATATYPE_FLOAT ], outtypes=[], ... + nested_fn=state_mainfn, Nstates=3, state_names_list=list("state1", "state2", "state3"), ... + inittial_state=3, x0_global=[1,0,2,0], userdata=list( PacketFramework ) ); + + PacketFramework = userdata(1); + + + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); + ld_PF_Export_js(PacketFramework, fname="ProtocollConfig.json"); + + + outlist = list(); +endfunction + + + + +// This is the main top level schematic +function [sim, outlist] = schematic_fn(sim, inlist) + +// +// Create a thread that runs the control system +// + + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; // or ORTD.ORTD_RT_NORMALTASK + ThreadPrioStruct.prio2=0; // for ORTD.ORTD_RT_REALTIMETASK: 1-99 as described in man sched_setscheduler + // for ORTD.ORTD_RT_NORMALTASK this is the nice-value (higher value means less priority) + ThreadPrioStruct.cpu = -1; // The CPU on which the thread will run; -1 dynamically assigns to a CPU, + // counting of the CPUs starts at 0 + + [sim, StartThread] = ld_initimpuls(sim, ev); // triggers your computation only once + [sim, outlist, computation_finished] = ld_async_simulation(sim, ev, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = Thread_MainRT, ... + TriggerSignal=StartThread, name="MainRealtimeThread", ... + ThreadPrioStruct, userdata=list() ); + + // output of schematic (empty) + outlist = list(); +endfunction + + + + + +// +// Set-up (no detailed understanding necessary) +// + +thispath = get_absolute_file_path(ProgramName+'.sce'); +cd(thispath); +z = poly(0,'z'); + + +// The following code is integrated into ORTD since rev 494. +// Thus the following line is commented. + +//exec('webinterface/PacketFramework.sce'); +exec('PF.sci'); + + +// defile ev +ev = [0]; // main event + +// set-up schematic by calling the user defined function "schematic_fn" +insizes = []; outsizes=[]; +[sim_container_irpar, sim]=libdyn_setup_schematic(schematic_fn, insizes, outsizes); + +// pack the simulation into a irpar container +parlist = new_irparam_set(); +parlist = new_irparam_container(parlist, sim_container_irpar, 901); // pack simulations into irpar container with id = 901 +par = combine_irparam(parlist); // complete irparam set +save_irparam(par, ProgramName+'.ipar', ProgramName+'.rpar'); // Save the schematic to disk + +// clear +par.ipar = []; par.rpar = []; + + diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/run_UDPio.sh b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/run_UDPio.sh new file mode 100755 index 00000000..28def1c1 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/run_UDPio.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +#ortd --baserate=10 --rtmode 1 -s UDPio -i 901 -l 0 +ortdrun -s UDPio diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/PacketFramework.sce b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/PacketFramework.sce new file mode 100644 index 00000000..cb51db73 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/PacketFramework.sce @@ -0,0 +1,487 @@ + + +// +// +// A packet based communication interface from ORTD using UDP datagrams to e.g. +// nodejs. +// webappUDP.js is the counterpart that provides a web-interface +// +// Current Rev: 7 +// +// Revisions: +// +// 27.3.14 - possibility to reservate sources +// 3.4.14 - small re-arrangements +// 4.4.14 - Bugfixes +// 7.4.14 - Bugfix +// 12.6.14 - Bugfix +// 2.11.14 - Added group finalising packet +// + + +function [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName) + SourceID = PacketFramework.SourceID_counter; + + Source.SourceName = SourceName; + Source.SourceID = SourceID; + Source.NValues_send = NValues_send; + Source.datatype = datatype; + + // Add new source to the list + PacketFramework.Sources($+1) = Source; + + // inc counter + PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; +endfunction + +function [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName) + ParameterID = PacketFramework.Parameterid_counter; + + Parameter.ParameterName = ParameterName; + Parameter.ParameterID = ParameterID; + Parameter.NValues = NValues; + Parameter.datatype = datatype; + Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; + + // Add new source to the list + PacketFramework.Parameters($+1) = Parameter; + + // inc counters + PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; + PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; + + // return values + ParameterID = Parameter.ParameterID; + MemoryOfs = Parameter.MemoryOfs; +endfunction + +function [sim, PacketFramework, Parameter] = ld_PF_Parameter(sim, PacketFramework, NValues, datatype, ParameterName) // PARSEDOCU_BLOCK +// +// Define a parameter +// +// NValues - amount of data sets +// datatype - only ORTD.DATATYPE_FLOAT for now +// ParameterName - a unique string decribing the parameter +// +// +// + + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); + + // read data from global memory + [sim, readI] = ld_const(sim, 0, MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + datatype, NValues); +endfunction + + + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, SourceID); + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); + + printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + +endfunction + + + + +function [sim, PacketFramework] = ld_SendPacket(sim, PacketFramework, Signal, NValues_send, datatype, SourceName) // PARSEDOCU_BLOCK // PARSEDOCU_BLOCK +// +// Stream data - block +// +// Signal - the signal to stream +// NValues_send - the vector length of Signal +// datatype - only ORTD.DATATYPE_FLOAT by now +// SourceName - a unique string identifier descring the stream +// +// +// + + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); +endfunction + + + + +function [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName, Configuration) // PARSEDOCU_BLOCK +// +// Initialise an instance of the Packet Framework +// +// InstanceName - a unique string identifier for the instance +// Configuration must include the following properties: +// +// Configuration.UnderlyingProtocoll = "UDP" +// Configuration.DestHost +// Configuration.DestPort +// Configuration.LocalSocketHost +// Configuration.LocalSocketPort +// +// +// Example: +// +// +// Configuration.UnderlyingProtocoll = "UDP"; +// Configuration.DestHost = "127.0.0.1"; +// Configuration.DestPort = 20000; +// Configuration.LocalSocketHost = "127.0.0.1"; +// Configuration.LocalSocketPort = 20001; +// [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); +// +// +// +// Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations +// +// + + // initialise structure for sources + PacketFramework.InstanceName = InstanceName; + PacketFramework.Configuration = Configuration; + + PacketFramework.Configuration.debugmode = %F; + +// disp(Configuration.UnderlyingProtocoll) + + if Configuration.UnderlyingProtocoll == 'UDP' + null; + else + error("PacketFramework: Only UDP supported up to now"); + end + + // possible packet sizes for UDP + PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; + + // sources + PacketFramework.SourceID_counter = 0; + PacketFramework.Sources = list(); + + // parameters + PacketFramework.Parameterid_counter = 0; + PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory + PacketFramework.Parameters = list(); + + PacketFramework.SenderID = 1295793; +endfunction + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Group ID + [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources + [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); + +// printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + + + [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); +endfunction + +function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOCU_BLOCK +// +// Finalise the instance. +// +// + + // The main real-time thread + function [sim] = ld_PF_InitUDP(sim, InstanceName, ParameterMemory) + + function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) + // This will run in a thread. Each time a UDP-packet is received + // one simulation step is performed. Herein, the packet is parsed + // and the contained parameters are stored into a memory. + + // Sync the simulation to incomming UDP-packets + [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); + + // disassemble packet's structure + [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... + outsizes=[1,1,1,TotalElemetsPerPacket], ... + outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); + + + + DisAsm_ = list(); + DisAsm_(4) = DisAsm(4); + [sim, DisAsm_(1)] = ld_Int32ToFloat(sim, 0, DisAsm(1) ); + [sim, DisAsm_(2)] = ld_Int32ToFloat(sim, 0, DisAsm(2) ); + [sim, DisAsm_(3)] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); + + + [sim, memofs] = ld_ArrayInt32(sim, 0, array=ParameterMemory.MemoryOfs, in=DisAsm(3) ); + [sim, Nelements] = ld_ArrayInt32(sim, 0, array=ParameterMemory.Sizes, in=DisAsm(3) ); + + [sim, memofs_] = ld_Int32ToFloat(sim, 0, memofs ); + [sim, Nelements_] = ld_Int32ToFloat(sim, 0, Nelements ); + + if PacketFramework.Configuration.debugmode then + // print the contents of the packet + [sim] = ld_printf(sim, 0, DisAsm_(1), "DisAsm(1) (SenderID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(2), "DisAsm(2) (Packet Counter) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(3), "DisAsm(3) (SourceID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(4), "DisAsm(4) (Signal) = ", TotalElemetsPerPacket); + + [sim] = ld_printf(sim, 0, memofs_ , "memofs = ", 1); + [sim] = ld_printf(sim, 0, memofs_ , "Nelements = ", 1); + end + + // Store the input data into a shared memory + [sim] = ld_WriteMemory2(sim, 0, data=DisAsm(4), index=memofs, ElementsToWrite=Nelements, ... + ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); + + + + // output of schematic + outlist = list(); + endfunction + + + + // start the node.js service from the subfolder webinterface + //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); + + TotalMemorySize = sum(PacketFramework.ParameterMemory.Sizes); + TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketSize = PacketFramework.PacketSize; + + // Open an UDP-Port in server mode + [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... + hostname=PacketFramework.Configuration.LocalSocketHost, ... + UDPPort=PacketFramework.Configuration.LocalSocketPort); + + // initialise a global memory for storing the input data for the computation + [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... + datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... + initial_data=[zeros(TotalMemorySize,1)], ... + visibility='global', useMutex=1); + + // Create thread for the receiver + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; + [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step + [sim, outlist, computation_finished] = ld_async_simulation(sim, 0, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = UDPReceiverThread, ... + TriggerSignal=startcalc, name=InstanceName+"Thread1", ... + ThreadPrioStruct, userdata=list() ); + + + endfunction + + + + + + // calc memory + MemoryOfs = []; + Sizes = []; + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + + Sizes = [Sizes; P.NValues]; + MemoryOfs = [MemoryOfs; P.MemoryOfs]; + end + + PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; + PacketFramework.ParameterMemory.Sizes = Sizes; + + // udp + [sim] = ld_PF_InitUDP(sim, PacketFramework.InstanceName, PacketFramework.ParameterMemory); + + // Send to group update notifications for each group (currently only one possible) + [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); + +endfunction + +function ld_PF_Export_js(PacketFramework, fname) // PARSEDOCU_BLOCK +// +// Export configuration of the defined protocoll (Sources, Parameters) +// into JSON-format. This is to be used by software that shall communicate +// to the real-time system. +// +// fname - The file name +// +// + + + fd = mopen(fname,'wt'); + + mfprintf(fd,' {""SourcesConfig"" : {\n'); + + for i=1:length(PacketFramework.Sources) + + + SourceID = PacketFramework.Sources(i).SourceID; + SourceName = PacketFramework.Sources(i).SourceName; + disp(SourceID ); + disp( SourceName ); + + + line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"" } \n", ... + string(PacketFramework.Sources(i).SourceID), ... + string(PacketFramework.Sources(i).SourceName), ... + string(PacketFramework.Sources(i).NValues_send), ... + string(PacketFramework.Sources(i).datatype) ); + + + if i==length(PacketFramework.Sources) + // finalise the last entry without "," + printf('%s \n' , line); + mfprintf(fd,'%s', line); + else + printf('%s, \n' , line); + mfprintf(fd,'%s,', line); + end + + + end + + + + + mfprintf(fd,'} , \n ""ParametersConfig"" : {\n'); + + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + + printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); + + line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } \n", ... + string(PacketFramework.Parameters(i).ParameterID), ... + string(PacketFramework.Parameters(i).ParameterName), ... + string(PacketFramework.Parameters(i).NValues), ... + string(PacketFramework.Parameters(i).datatype) ); + + + if i==length(PacketFramework.Parameters) + // finalise the last entry without "," + printf('%s \n' , line); + mfprintf(fd,'%s', line); + else + printf('%s, \n' , line); + mfprintf(fd,'%s,', line); + end + + + end + + mfprintf(fd,'}\n}'); + + mclose(fd); +endfunction + +// +// Added 27.3.14 +// + +function [sim, PacketFramework, SourceID]=ld_SendPacketReserve(sim, PacketFramework, NValues_send, datatype, SourceName) + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); +endfunction + +function [sim, PacketFramework]=ld_SendPacket2(sim, PacketFramework, Signal, SourceName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Sources) + S = PacketFramework.Sources(i); + if S.SourceName == SourceName + index = i; + printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); + break; + end + end + + if index == -1 + printf("SourceName = %s\n", SourceName); + error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); + end + + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); +endfunction + + + +function [sim, PacketFramework, ParameterID]=ld_PF_ParameterReserve(sim, PacketFramework, NValues, datatype, ParameterName) + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); +endfunction + + +function [sim, PacketFramework, Parameter]=ld_PF_Parameter2(sim, PacketFramework, ParameterName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + if P.ParameterName == ParameterName + index = i; + printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); + break; + end + end + + if index == -1 + printf("ParameterName = %s\n", ParameterName); + error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); + end + + // read data from global memory + [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + P.datatype, P.NValues); +endfunction + diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/mainAuto.html b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/mainAuto.html new file mode 100644 index 00000000..491f5a19 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/mainAuto.html @@ -0,0 +1,155 @@ + + + + + + + + + Generic GUI-interface to an ORTD-simulation + +
+ This is a generic interface to ORTD. All parameters and datasources are automatically shown. Feel free to copy and adapt it to your needs to build you own specialised web-interface. + +

+ + Parameters:
0
+ + Displays:
0
+ +

+ ProtocollConfiguration:
0
+ + + \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/main_Plot.html b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/main_Plot.html new file mode 100644 index 00000000..47a8fa23 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/main_Plot.html @@ -0,0 +1,365 @@ + + + + + +
+ + + + + + + + + + + GUI-interface to ORTD-simulation + +

+ + + Parameters:
0
+ + Displays:
0
+ + + + + ProtocollConfiguration:
0
+

+ + + + + + + + \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/install_nodejs.sh b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/install_nodejs.sh new file mode 100644 index 00000000..7cdf2b59 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/install_nodejs.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +echo 'export PATH=$HOME/local/bin:$PATH' >> ~/.bashrc +. ~/.bashrc +mkdir ~/local +mkdir ~/node-latest-install +cd ~/node-latest-install +curl http://nodejs.org/dist/node-latest.tar.gz | tar xz --strip-components=1 +./configure --prefix=~/local +make install # ok, fine, this step probably takes more than 30 seconds... +curl https://npmjs.org/install.sh | sh diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/webappUDP.js b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/webappUDP.js new file mode 100644 index 00000000..30baf361 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/webappUDP.js @@ -0,0 +1,304 @@ +/* + + node.js interface to ORTD using UDP datagrams and the packet framework. + A web-interface is provided at e.g. http://localhost:8091/mainAuto.html, + however currently not passwort protected. + The web-interface(s) are defined in html/* + + You can adapt the html files to your need and also created new ones + to e.g. set-up an individual interface to your application + + Rev 2 + +*/ + +// http-server config +var HTTPPORT = 8091; + +// UDP config +var PORT = 20000; +var HOST = '127.0.0.1'; +var ORTD_HOST = '127.0.0.1'; // the IP and port of the ORTD simulator running UDPio.sce +var ORTD_PORT = 20001; + +var NValues = 7; // must be the same as NValues_send defined in UDPio.sce when calling UDPSend +var DataBufferSize = 20000; // Number of elementes stored in the ringbuffer + + + +// +// Includes +// + +http = require('http'); +url = require("url"), +path = require("path"), +fs = require("fs") + +var fs = require('fs'); +var dgram = require('dgram'); + + +// +// Load configuration +// + +var ProtocollConfig = require('../ProtocollConfig.json'); +console.log(ProtocollConfig); + +// +// RingBuffer +// + +var RingBuffer = new RingBuffer(DataBufferSize, NValues); +console.log("RingBuffer created"); + + + + // console.log(P.length); + +function GetParameterID(ParametersConfig, ParameterName) +{ + var P = ParametersConfig; + for (key in P) { + if (P[key].ParameterName == ParameterName) { + return key; + } + } + return -1; +} + +console.log( GetParameterID(ProtocollConfig.ParametersConfig, "Parameter2") ); + +// +// http-server +// + +// got from stackoverflow: +// http://stackoverflow.com/questions/6084360/node-js-as-a-simple-web-server +var httpserver = http.createServer(function(request, response) { + + var uri = url.parse(request.url).pathname + , filename = path.join(process.cwd(), 'html', uri); + + path.exists(filename, function(exists) { + if(!exists) { + response.writeHead(404, {"Content-Type": "text/plain"}); + response.write("404 Not Found\n"); + response.end(); + return; + } + + if (fs.statSync(filename).isDirectory()) filename += '/index.html'; + + fs.readFile(filename, "binary", function(err, file) { + if(err) { + response.writeHead(500, {"Content-Type": "text/plain"}); + response.write(err + "\n"); + response.end(); + return; + } + + response.writeHead(200); + response.write(file, "binary"); + response.end(); + }); + }); +}).listen(HTTPPORT); + +// set-up socket.io +var io = require('socket.io').listen(httpserver); +io.set('log level', 1); // reduce logging + + + + +// +// UDP interface +// +var server = dgram.createSocket('udp4'); +server.on('listening', function () { + var address = server.address(); + console.log('UDP Server listening on ' + address.address + ":" + address.port); +}); + +// Buffer for sending UDP packets +var UDPSendPacketBuffer = new Buffer(2000); // size is propably bigger than every UDP-Packet + + +server.on('message', function (message, remote) { + // received new packet from ORTD via UDP + //console.log(remote.address + ':' + remote.port); + + + var i; + + try { + // disassemble header + var SenderID = message.readInt32LE( 0 ); + var PacketCounter = message.readInt32LE( 4 ); + var SourceID = message.readInt32LE( 8 ); + + // check wheter the sender ID is correct + if (SenderID != 1295793) + throw 1; + + // get popoerties of this packet source + SourceProperties = ProtocollConfig.SourcesConfig[SourceID] +// console.log( 'SourceProperties: '); +// console.log( SourceProperties ); + + if (SourceProperties.datatype != 257) // ORTD.DATATYPE_FLOAT + throw 2; + + + + // check if the recved packet has the correct size + if ( message.length != 12+8*SourceProperties.NValues_send) + throw 2; + +// console.log('Disasm data '+ SourceProperties.NValues_send); + + // disassemble data-values + var ValuesBuffer = message.slice(12, 12+8*SourceProperties.NValues_send); + var Values = new Array(SourceProperties.NValues_send); + + for (i=0; i= this.DataBufferSize) { + this.WriteIndex = 0; + } + } + +// this.ReturnBuffer=ReturnBuffer; +// function ReturnBuffer() { +// return DataBuffer; +// } +} + diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json new file mode 100644 index 00000000..1916cd7a --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json @@ -0,0 +1,5 @@ + {"SourcesConfig" : { + "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257" } , "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257" } , "2" : { "SourceName" : "Signal1" , "NValues_send" : "1", "datatype" : "257" } , "3" : { "SourceName" : "Sig2nal" , "NValues_send" : "1", "datatype" : "257" } , "4" : { "SourceName" : "Sign3al" , "NValues_send" : "1", "datatype" : "257" } , "5" : { "SourceName" : "_Sig4nal" , "NValues_send" : "1", "datatype" : "257" } , "6" : { "SourceName" : "Signal5" , "NValues_send" : "1", "datatype" : "257" } , "7" : { "SourceName" : "Signal6" , "NValues_send" : "1", "datatype" : "257" } , "8" : { "SourceName" : "Signal7" , "NValues_send" : "1", "datatype" : "257" } , "9" : { "SourceName" : "Signal8" , "NValues_send" : "1", "datatype" : "257" } , "10" : { "SourceName" : "Signal9" , "NValues_send" : "1", "datatype" : "257" } , "11" : { "SourceName" : "Signal10" , "NValues_send" : "1", "datatype" : "257" } , "12" : { "SourceName" : "Signal11" , "NValues_send" : "1", "datatype" : "257" } , "13" : { "SourceName" : "Sign12al" , "NValues_send" : "1", "datatype" : "257" } , "14" : { "SourceName" : "Signal_13" , "NValues_send" : "1", "datatype" : "257" } , "15" : { "SourceName" : "HalloWelt14" , "NValues_send" : "1", "datatype" : "257" } , "16" : { "SourceName" : "15" , "NValues_send" : "1", "datatype" : "257" } , "17" : { "SourceName" : "16" , "NValues_send" : "1", "datatype" : "257" } , "18" : { "SourceName" : "Test17" , "NValues_send" : "1", "datatype" : "257" } , "19" : { "SourceName" : "18" , "NValues_send" : "1", "datatype" : "257" } , "20" : { "SourceName" : "Signal19" , "NValues_send" : "1", "datatype" : "257" } , "21" : { "SourceName" : "Signal20" , "NValues_send" : "1", "datatype" : "257" } , "22" : { "SourceName" : "Signal21" , "NValues_send" : "1", "datatype" : "257" } , "23" : { "SourceName" : "Signal22" , "NValues_send" : "1", "datatype" : "257" } } , + "ParametersConfig" : { + "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } , "3" : { "ParameterName" : "P1" , "NValues" : "1", "datatype" : "257" } , "4" : { "ParameterName" : "P2" , "NValues" : "1", "datatype" : "257" } , "5" : { "ParameterName" : "P3" , "NValues" : "1", "datatype" : "257" } , "6" : { "ParameterName" : "P4" , "NValues" : "1", "datatype" : "257" } , "7" : { "ParameterName" : "Test5" , "NValues" : "1", "datatype" : "257" } , "8" : { "ParameterName" : "P6" , "NValues" : "1", "datatype" : "257" } , "9" : { "ParameterName" : "P7" , "NValues" : "1", "datatype" : "257" } , "10" : { "ParameterName" : "P8" , "NValues" : "1", "datatype" : "257" } , "11" : { "ParameterName" : "P9" , "NValues" : "1", "datatype" : "257" } } +} \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/README b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/README new file mode 100644 index 00000000..10c48a96 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/README @@ -0,0 +1,54 @@ +This is a demonstration on how to set-up a web-interface to a control +system implemented using OpenRTDynamics.sf.net. As you'll notice +this is possible in a few lines of code, because the usage of +Javascript for implementing the gui-part and ORTD for the +real-time part including the UDP-communication allows an efficient +formulation of the algorithms. + +UDPio.sce is a sample ORTD-simulation and webappUDP.js the nodejs program +that connects to this simulation. Further, it provides a web-interface +accessible via http://localhost:8090/mainAuto.html . + +The template for the web-interface is stored in html/mainAuto.html. + + + +The files used in this example are: + +- UDPio.sce is a sample ORTD-simulation that simulates an osicillator + and a communication interface to node.js using UDPio +- webappUDP.js is the node.js program that connects to this simulation + via an UDP-interface and provides a web-interface on port 8090. +- Different templates for the html-page are stored in html/main*.html. + +- UDPio.ipar and UDPio.rpar are the compiled ORTD-programm files. +- PacketFramework.sce The file that implements the packet framework. + This will was also integrated into ORTD at Rev. 495 + +To make this example working: + +- The installation of node.js (nodejs.org) and its package manager + npm is required. +- Then, the "socket.io" node.js-package of node.js + is required that can be installed with "npm". To do this, call the + following commmand from the directory that contains webappUDP.js: + + $ cd webinterface + $ npm install socket.io + +- To start the set-up, two services / processes are required to run + at the same time: + +the ORTD-simulation is started by running + + $ sh run_UDPio.sh + +and the node.js part by + + $ cd webinterface + $ node webappUDP.js + +The order doesn't matter and it is also possible to start / stop each +service, which is one advantage of using stateless UDP-communication. + +- Finally, point your browser to http://localhost:8090/mainAuto.html . \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.ipar b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.ipar new file mode 100644 index 00000000..94c962aa --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.ipar @@ -0,0 +1,13965 @@ + 1 + 1 + 901 + 10 + 0 + 0 + 13957 + 146 + 1 + 3 + 201 + 100 + 0 + 0 + 6 + 0 + 203 + 100 + 6 + 0 + 13919 + 146 + 100 + 101 + 13925 + 146 + 12 + 0 + 60023 + 201 + 0 + 0 + 1 + 0 + 15011 + 203 + 13913 + 146 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 13850 + 0 + 21 + 1 + 13862 + 0 + 1 + 146 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 13849 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 21 + 4 + 4 + 0 + 19 + 0 + 22 + 4 + 23 + 0 + 4 + 0 + 900 + 10 + 27 + 0 + 13778 + 146 + 0 + 0 + 0 + 0 + 18 + 77 + 97 + 105 + 110 + 82 + 101 + 97 + 108 + 116 + 105 + 109 + 101 + 84 + 104 + 114 + 101 + 97 + 100 + 3 + 2 + 0 + -1 + 1 + 323 + 201 + 100 + 0 + 0 + 6 + 1 + 203 + 100 + 6 + 1 + 72 + 0 + 205 + 100 + 78 + 1 + 6 + 1 + 207 + 100 + 84 + 2 + 54 + 0 + 209 + 100 + 138 + 2 + 6 + 1 + 211 + 100 + 144 + 3 + 54 + 0 + 213 + 100 + 198 + 3 + 6 + 1 + 215 + 100 + 204 + 4 + 54 + 0 + 217 + 100 + 258 + 4 + 25 + 0 + 218 + 100 + 283 + 4 + 13 + 0 + 219 + 100 + 296 + 4 + 29 + 0 + 220 + 100 + 325 + 4 + 6 + 1 + 222 + 100 + 331 + 5 + 54 + 0 + 224 + 100 + 385 + 5 + 11 + 0 + 225 + 100 + 396 + 5 + 6 + 1 + 227 + 100 + 402 + 6 + 54 + 0 + 229 + 100 + 456 + 6 + 11 + 0 + 230 + 100 + 467 + 6 + 6 + 1 + 232 + 100 + 473 + 7 + 54 + 0 + 234 + 100 + 527 + 7 + 11 + 0 + 235 + 100 + 538 + 7 + 6 + 1 + 237 + 100 + 544 + 8 + 54 + 0 + 239 + 100 + 598 + 8 + 11 + 0 + 240 + 100 + 609 + 8 + 6 + 1 + 242 + 100 + 615 + 9 + 54 + 0 + 244 + 100 + 669 + 9 + 14 + 0 + 245 + 100 + 683 + 9 + 6 + 1 + 247 + 100 + 689 + 10 + 54 + 0 + 249 + 100 + 743 + 10 + 11 + 0 + 250 + 100 + 754 + 10 + 6 + 1 + 252 + 100 + 760 + 11 + 54 + 0 + 254 + 100 + 814 + 11 + 11 + 0 + 255 + 100 + 825 + 11 + 6 + 1 + 257 + 100 + 831 + 12 + 54 + 0 + 259 + 100 + 885 + 12 + 11 + 0 + 260 + 100 + 896 + 12 + 6 + 1 + 262 + 100 + 902 + 13 + 54 + 0 + 264 + 100 + 956 + 13 + 11 + 0 + 267 + 100 + 967 + 13 + 6 + 2 + 269 + 100 + 973 + 15 + 6 + 2 + 271 + 100 + 979 + 17 + 8 + 3 + 273 + 100 + 987 + 20 + 6 + 1 + 275 + 100 + 993 + 21 + 8 + 3 + 277 + 100 + 1001 + 24 + 6 + 1 + 279 + 100 + 1007 + 25 + 6 + 1 + 281 + 100 + 1013 + 26 + 8 + 0 + 283 + 100 + 1021 + 26 + 6 + 0 + 285 + 100 + 1027 + 26 + 6 + 1 + 287 + 100 + 1033 + 27 + 6 + 0 + 289 + 100 + 1039 + 27 + 6 + 1 + 291 + 100 + 1045 + 28 + 6 + 0 + 293 + 100 + 1051 + 28 + 76 + 0 + 295 + 100 + 1127 + 28 + 9 + 0 + 297 + 100 + 1136 + 28 + 166 + 0 + 298 + 100 + 1302 + 28 + 6 + 1 + 300 + 100 + 1308 + 29 + 8 + 0 + 302 + 100 + 1316 + 29 + 6 + 0 + 304 + 100 + 1322 + 29 + 6 + 1 + 306 + 100 + 1328 + 30 + 6 + 0 + 308 + 100 + 1334 + 30 + 6 + 1 + 310 + 100 + 1340 + 31 + 6 + 0 + 312 + 100 + 1346 + 31 + 76 + 0 + 314 + 100 + 1422 + 31 + 9 + 0 + 316 + 100 + 1431 + 31 + 166 + 0 + 317 + 100 + 1597 + 31 + 6 + 1 + 319 + 100 + 1603 + 32 + 6 + 1 + 321 + 100 + 1609 + 33 + 8 + 0 + 323 + 100 + 1617 + 33 + 6 + 0 + 325 + 100 + 1623 + 33 + 6 + 1 + 327 + 100 + 1629 + 34 + 6 + 0 + 329 + 100 + 1635 + 34 + 6 + 1 + 331 + 100 + 1641 + 35 + 6 + 0 + 333 + 100 + 1647 + 35 + 76 + 0 + 335 + 100 + 1723 + 35 + 9 + 0 + 337 + 100 + 1732 + 35 + 166 + 0 + 338 + 100 + 1898 + 35 + 6 + 1 + 340 + 100 + 1904 + 36 + 6 + 1 + 342 + 100 + 1910 + 37 + 8 + 0 + 344 + 100 + 1918 + 37 + 6 + 0 + 346 + 100 + 1924 + 37 + 6 + 1 + 348 + 100 + 1930 + 38 + 6 + 0 + 350 + 100 + 1936 + 38 + 6 + 1 + 352 + 100 + 1942 + 39 + 6 + 0 + 354 + 100 + 1948 + 39 + 76 + 0 + 356 + 100 + 2024 + 39 + 9 + 0 + 358 + 100 + 2033 + 39 + 166 + 0 + 359 + 100 + 2199 + 39 + 6 + 1 + 361 + 100 + 2205 + 40 + 6 + 1 + 363 + 100 + 2211 + 41 + 8 + 0 + 365 + 100 + 2219 + 41 + 6 + 0 + 367 + 100 + 2225 + 41 + 6 + 1 + 369 + 100 + 2231 + 42 + 6 + 0 + 371 + 100 + 2237 + 42 + 6 + 1 + 373 + 100 + 2243 + 43 + 6 + 0 + 375 + 100 + 2249 + 43 + 76 + 0 + 377 + 100 + 2325 + 43 + 9 + 0 + 379 + 100 + 2334 + 43 + 166 + 0 + 380 + 100 + 2500 + 43 + 6 + 1 + 382 + 100 + 2506 + 44 + 6 + 1 + 384 + 100 + 2512 + 45 + 8 + 0 + 386 + 100 + 2520 + 45 + 6 + 0 + 388 + 100 + 2526 + 45 + 6 + 1 + 390 + 100 + 2532 + 46 + 6 + 0 + 392 + 100 + 2538 + 46 + 6 + 1 + 394 + 100 + 2544 + 47 + 6 + 0 + 396 + 100 + 2550 + 47 + 76 + 0 + 398 + 100 + 2626 + 47 + 9 + 0 + 400 + 100 + 2635 + 47 + 166 + 0 + 401 + 100 + 2801 + 47 + 6 + 1 + 403 + 100 + 2807 + 48 + 6 + 1 + 405 + 100 + 2813 + 49 + 8 + 0 + 407 + 100 + 2821 + 49 + 6 + 0 + 409 + 100 + 2827 + 49 + 6 + 1 + 411 + 100 + 2833 + 50 + 6 + 0 + 413 + 100 + 2839 + 50 + 6 + 1 + 415 + 100 + 2845 + 51 + 6 + 0 + 417 + 100 + 2851 + 51 + 76 + 0 + 419 + 100 + 2927 + 51 + 9 + 0 + 421 + 100 + 2936 + 51 + 166 + 0 + 422 + 100 + 3102 + 51 + 6 + 1 + 424 + 100 + 3108 + 52 + 6 + 1 + 426 + 100 + 3114 + 53 + 8 + 0 + 428 + 100 + 3122 + 53 + 6 + 0 + 430 + 100 + 3128 + 53 + 6 + 1 + 432 + 100 + 3134 + 54 + 6 + 0 + 434 + 100 + 3140 + 54 + 6 + 1 + 436 + 100 + 3146 + 55 + 6 + 0 + 438 + 100 + 3152 + 55 + 76 + 0 + 440 + 100 + 3228 + 55 + 9 + 0 + 442 + 100 + 3237 + 55 + 166 + 0 + 443 + 100 + 3403 + 55 + 6 + 1 + 445 + 100 + 3409 + 56 + 6 + 1 + 447 + 100 + 3415 + 57 + 8 + 0 + 449 + 100 + 3423 + 57 + 6 + 0 + 451 + 100 + 3429 + 57 + 6 + 1 + 453 + 100 + 3435 + 58 + 6 + 0 + 455 + 100 + 3441 + 58 + 6 + 1 + 457 + 100 + 3447 + 59 + 6 + 0 + 459 + 100 + 3453 + 59 + 76 + 0 + 461 + 100 + 3529 + 59 + 9 + 0 + 463 + 100 + 3538 + 59 + 166 + 0 + 464 + 100 + 3704 + 59 + 6 + 1 + 466 + 100 + 3710 + 60 + 6 + 1 + 468 + 100 + 3716 + 61 + 8 + 0 + 470 + 100 + 3724 + 61 + 6 + 0 + 472 + 100 + 3730 + 61 + 6 + 1 + 474 + 100 + 3736 + 62 + 6 + 0 + 476 + 100 + 3742 + 62 + 6 + 1 + 478 + 100 + 3748 + 63 + 6 + 0 + 480 + 100 + 3754 + 63 + 76 + 0 + 482 + 100 + 3830 + 63 + 9 + 0 + 484 + 100 + 3839 + 63 + 166 + 0 + 485 + 100 + 4005 + 63 + 6 + 1 + 487 + 100 + 4011 + 64 + 6 + 1 + 489 + 100 + 4017 + 65 + 8 + 0 + 491 + 100 + 4025 + 65 + 6 + 0 + 493 + 100 + 4031 + 65 + 6 + 1 + 495 + 100 + 4037 + 66 + 6 + 0 + 497 + 100 + 4043 + 66 + 6 + 1 + 499 + 100 + 4049 + 67 + 6 + 0 + 501 + 100 + 4055 + 67 + 76 + 0 + 503 + 100 + 4131 + 67 + 9 + 0 + 505 + 100 + 4140 + 67 + 166 + 0 + 506 + 100 + 4306 + 67 + 6 + 1 + 508 + 100 + 4312 + 68 + 6 + 1 + 510 + 100 + 4318 + 69 + 8 + 0 + 512 + 100 + 4326 + 69 + 6 + 0 + 514 + 100 + 4332 + 69 + 6 + 1 + 516 + 100 + 4338 + 70 + 6 + 0 + 518 + 100 + 4344 + 70 + 6 + 1 + 520 + 100 + 4350 + 71 + 6 + 0 + 522 + 100 + 4356 + 71 + 76 + 0 + 524 + 100 + 4432 + 71 + 9 + 0 + 526 + 100 + 4441 + 71 + 166 + 0 + 527 + 100 + 4607 + 71 + 6 + 1 + 529 + 100 + 4613 + 72 + 6 + 1 + 531 + 100 + 4619 + 73 + 8 + 0 + 533 + 100 + 4627 + 73 + 6 + 0 + 535 + 100 + 4633 + 73 + 6 + 1 + 537 + 100 + 4639 + 74 + 6 + 0 + 539 + 100 + 4645 + 74 + 6 + 1 + 541 + 100 + 4651 + 75 + 6 + 0 + 543 + 100 + 4657 + 75 + 76 + 0 + 545 + 100 + 4733 + 75 + 9 + 0 + 547 + 100 + 4742 + 75 + 166 + 0 + 548 + 100 + 4908 + 75 + 6 + 1 + 550 + 100 + 4914 + 76 + 6 + 1 + 552 + 100 + 4920 + 77 + 8 + 0 + 554 + 100 + 4928 + 77 + 6 + 0 + 556 + 100 + 4934 + 77 + 6 + 1 + 558 + 100 + 4940 + 78 + 6 + 0 + 560 + 100 + 4946 + 78 + 6 + 1 + 562 + 100 + 4952 + 79 + 6 + 0 + 564 + 100 + 4958 + 79 + 76 + 0 + 566 + 100 + 5034 + 79 + 9 + 0 + 568 + 100 + 5043 + 79 + 166 + 0 + 569 + 100 + 5209 + 79 + 6 + 1 + 571 + 100 + 5215 + 80 + 6 + 1 + 573 + 100 + 5221 + 81 + 8 + 0 + 575 + 100 + 5229 + 81 + 6 + 0 + 577 + 100 + 5235 + 81 + 6 + 1 + 579 + 100 + 5241 + 82 + 6 + 0 + 581 + 100 + 5247 + 82 + 6 + 1 + 583 + 100 + 5253 + 83 + 6 + 0 + 585 + 100 + 5259 + 83 + 76 + 0 + 587 + 100 + 5335 + 83 + 9 + 0 + 589 + 100 + 5344 + 83 + 166 + 0 + 590 + 100 + 5510 + 83 + 6 + 1 + 592 + 100 + 5516 + 84 + 6 + 1 + 594 + 100 + 5522 + 85 + 8 + 0 + 596 + 100 + 5530 + 85 + 6 + 0 + 598 + 100 + 5536 + 85 + 6 + 1 + 600 + 100 + 5542 + 86 + 6 + 0 + 602 + 100 + 5548 + 86 + 6 + 1 + 604 + 100 + 5554 + 87 + 6 + 0 + 606 + 100 + 5560 + 87 + 76 + 0 + 608 + 100 + 5636 + 87 + 9 + 0 + 610 + 100 + 5645 + 87 + 166 + 0 + 611 + 100 + 5811 + 87 + 6 + 1 + 613 + 100 + 5817 + 88 + 6 + 1 + 615 + 100 + 5823 + 89 + 8 + 0 + 617 + 100 + 5831 + 89 + 6 + 0 + 619 + 100 + 5837 + 89 + 6 + 1 + 621 + 100 + 5843 + 90 + 6 + 0 + 623 + 100 + 5849 + 90 + 6 + 1 + 625 + 100 + 5855 + 91 + 6 + 0 + 627 + 100 + 5861 + 91 + 76 + 0 + 629 + 100 + 5937 + 91 + 9 + 0 + 631 + 100 + 5946 + 91 + 166 + 0 + 632 + 100 + 6112 + 91 + 6 + 1 + 634 + 100 + 6118 + 92 + 6 + 1 + 636 + 100 + 6124 + 93 + 8 + 0 + 638 + 100 + 6132 + 93 + 6 + 0 + 640 + 100 + 6138 + 93 + 6 + 1 + 642 + 100 + 6144 + 94 + 6 + 0 + 644 + 100 + 6150 + 94 + 6 + 1 + 646 + 100 + 6156 + 95 + 6 + 0 + 648 + 100 + 6162 + 95 + 76 + 0 + 650 + 100 + 6238 + 95 + 9 + 0 + 652 + 100 + 6247 + 95 + 166 + 0 + 653 + 100 + 6413 + 95 + 6 + 1 + 655 + 100 + 6419 + 96 + 6 + 1 + 657 + 100 + 6425 + 97 + 8 + 0 + 659 + 100 + 6433 + 97 + 6 + 0 + 661 + 100 + 6439 + 97 + 6 + 1 + 663 + 100 + 6445 + 98 + 6 + 0 + 665 + 100 + 6451 + 98 + 6 + 1 + 667 + 100 + 6457 + 99 + 6 + 0 + 669 + 100 + 6463 + 99 + 76 + 0 + 671 + 100 + 6539 + 99 + 9 + 0 + 673 + 100 + 6548 + 99 + 166 + 0 + 674 + 100 + 6714 + 99 + 6 + 1 + 676 + 100 + 6720 + 100 + 6 + 1 + 678 + 100 + 6726 + 101 + 8 + 0 + 680 + 100 + 6734 + 101 + 6 + 0 + 682 + 100 + 6740 + 101 + 6 + 1 + 684 + 100 + 6746 + 102 + 6 + 0 + 686 + 100 + 6752 + 102 + 6 + 1 + 688 + 100 + 6758 + 103 + 6 + 0 + 690 + 100 + 6764 + 103 + 76 + 0 + 692 + 100 + 6840 + 103 + 9 + 0 + 694 + 100 + 6849 + 103 + 166 + 0 + 695 + 100 + 7015 + 103 + 6 + 1 + 697 + 100 + 7021 + 104 + 6 + 1 + 699 + 100 + 7027 + 105 + 8 + 0 + 701 + 100 + 7035 + 105 + 6 + 0 + 703 + 100 + 7041 + 105 + 6 + 1 + 705 + 100 + 7047 + 106 + 6 + 0 + 707 + 100 + 7053 + 106 + 6 + 1 + 709 + 100 + 7059 + 107 + 6 + 0 + 711 + 100 + 7065 + 107 + 76 + 0 + 713 + 100 + 7141 + 107 + 9 + 0 + 715 + 100 + 7150 + 107 + 166 + 0 + 716 + 100 + 7316 + 107 + 6 + 1 + 718 + 100 + 7322 + 108 + 6 + 1 + 720 + 100 + 7328 + 109 + 8 + 0 + 722 + 100 + 7336 + 109 + 6 + 0 + 724 + 100 + 7342 + 109 + 6 + 1 + 726 + 100 + 7348 + 110 + 6 + 0 + 728 + 100 + 7354 + 110 + 6 + 1 + 730 + 100 + 7360 + 111 + 6 + 0 + 732 + 100 + 7366 + 111 + 76 + 0 + 734 + 100 + 7442 + 111 + 9 + 0 + 736 + 100 + 7451 + 111 + 166 + 0 + 737 + 100 + 7617 + 111 + 6 + 1 + 739 + 100 + 7623 + 112 + 6 + 1 + 741 + 100 + 7629 + 113 + 8 + 0 + 743 + 100 + 7637 + 113 + 6 + 0 + 745 + 100 + 7643 + 113 + 6 + 1 + 747 + 100 + 7649 + 114 + 6 + 0 + 749 + 100 + 7655 + 114 + 6 + 1 + 751 + 100 + 7661 + 115 + 6 + 0 + 753 + 100 + 7667 + 115 + 76 + 0 + 755 + 100 + 7743 + 115 + 9 + 0 + 757 + 100 + 7752 + 115 + 166 + 0 + 758 + 100 + 7918 + 115 + 6 + 1 + 760 + 100 + 7924 + 116 + 6 + 1 + 762 + 100 + 7930 + 117 + 8 + 0 + 764 + 100 + 7938 + 117 + 6 + 0 + 766 + 100 + 7944 + 117 + 6 + 1 + 768 + 100 + 7950 + 118 + 6 + 0 + 770 + 100 + 7956 + 118 + 6 + 1 + 772 + 100 + 7962 + 119 + 6 + 0 + 774 + 100 + 7968 + 119 + 76 + 0 + 776 + 100 + 8044 + 119 + 9 + 0 + 778 + 100 + 8053 + 119 + 166 + 0 + 779 + 100 + 8219 + 119 + 154 + 0 + 780 + 100 + 8373 + 119 + 54 + 22 + 781 + 100 + 8427 + 141 + 6 + 1 + 783 + 100 + 8433 + 142 + 798 + 0 + 785 + 100 + 9231 + 142 + 6 + 1 + 787 + 100 + 9237 + 143 + 8 + 0 + 789 + 100 + 9245 + 143 + 6 + 0 + 791 + 100 + 9251 + 143 + 6 + 1 + 793 + 100 + 9257 + 144 + 6 + 0 + 795 + 100 + 9263 + 144 + 6 + 1 + 797 + 100 + 9269 + 145 + 6 + 0 + 799 + 100 + 9275 + 145 + 6 + 1 + 801 + 100 + 9281 + 146 + 6 + 0 + 803 + 100 + 9287 + 146 + 76 + 0 + 805 + 100 + 9363 + 146 + 9 + 0 + 807 + 100 + 9372 + 146 + 166 + 0 + 100 + 101 + 9538 + 146 + 2300 + 0 + 40 + 201 + 0 + 1 + 1 + 0 + 15102 + 203 + 66 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 3 + 0 + 21 + 1 + 15 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 2 + 1 + 0 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 15007 + 207 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 209 + 0 + 1 + 1 + 0 + 15007 + 211 + 48 + 0 + 1 + 0 + 0 + 257 + 10 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 213 + 0 + 1 + 1 + 0 + 15007 + 215 + 48 + 0 + 1 + 0 + 0 + 257 + 2 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 217 + 19 + 0 + 1 + 0 + 1 + 17 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 32 + 170 + 218 + 7 + 0 + 1 + 0 + 2 + 5 + 84 + 101 + 115 + 116 + 32 + 170 + 219 + 23 + 0 + 1 + 0 + 10 + 21 + 65 + 32 + 118 + 101 + 99 + 116 + 111 + 114 + 105 + 97 + 108 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 40 + 220 + 0 + 1 + 1 + 0 + 15007 + 222 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 224 + 5 + 0 + 1 + 0 + 1 + 3 + 80 + 49 + 32 + 40 + 225 + 0 + 1 + 1 + 0 + 15007 + 227 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 229 + 5 + 0 + 1 + 0 + 1 + 3 + 80 + 50 + 32 + 40 + 230 + 0 + 1 + 1 + 0 + 15007 + 232 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 234 + 5 + 0 + 1 + 0 + 1 + 3 + 80 + 51 + 32 + 40 + 235 + 0 + 1 + 1 + 0 + 15007 + 237 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 239 + 5 + 0 + 1 + 0 + 1 + 3 + 80 + 52 + 32 + 40 + 240 + 0 + 1 + 1 + 0 + 15007 + 242 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 244 + 8 + 0 + 1 + 0 + 1 + 6 + 84 + 101 + 115 + 116 + 53 + 32 + 40 + 245 + 0 + 1 + 1 + 0 + 15007 + 247 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 249 + 5 + 0 + 1 + 0 + 1 + 3 + 80 + 54 + 32 + 40 + 250 + 0 + 1 + 1 + 0 + 15007 + 252 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 254 + 5 + 0 + 1 + 0 + 1 + 3 + 80 + 55 + 32 + 40 + 255 + 0 + 1 + 1 + 0 + 15007 + 257 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 259 + 5 + 0 + 1 + 0 + 1 + 3 + 80 + 56 + 32 + 40 + 260 + 0 + 1 + 1 + 0 + 15007 + 262 + 48 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 170 + 264 + 5 + 0 + 1 + 0 + 1 + 3 + 80 + 57 + 32 + 12 + 267 + 0 + 2 + 1 + 0 + 12 + 269 + 0 + 2 + 1 + 0 + 30 + 271 + 2 + 3 + 1 + 0 + 0 + 1 + 20 + 273 + 0 + 1 + 1 + 0 + 30 + 275 + 2 + 3 + 1 + 0 + 0 + 1 + 20 + 277 + 0 + 1 + 1 + 0 + 40 + 279 + 0 + 1 + 1 + 0 + 60005 + 281 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 283 + 0 + 0 + 1 + 0 + 40 + 285 + 0 + 1 + 1 + 0 + 60031 + 287 + 0 + 0 + 1 + 0 + 40 + 289 + 0 + 1 + 1 + 0 + 60031 + 291 + 0 + 0 + 1 + 0 + 39011 + 293 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 295 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 297 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 298 + 0 + 1 + 1 + 0 + 60005 + 300 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 302 + 0 + 0 + 1 + 0 + 40 + 304 + 0 + 1 + 1 + 0 + 60031 + 306 + 0 + 0 + 1 + 0 + 40 + 308 + 0 + 1 + 1 + 0 + 60031 + 310 + 0 + 0 + 1 + 0 + 39011 + 312 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 314 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 316 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 317 + 0 + 1 + 1 + 0 + 40 + 319 + 0 + 1 + 1 + 0 + 60005 + 321 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 323 + 0 + 0 + 1 + 0 + 40 + 325 + 0 + 1 + 1 + 0 + 60031 + 327 + 0 + 0 + 1 + 0 + 40 + 329 + 0 + 1 + 1 + 0 + 60031 + 331 + 0 + 0 + 1 + 0 + 39011 + 333 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 335 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 337 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 338 + 0 + 1 + 1 + 0 + 40 + 340 + 0 + 1 + 1 + 0 + 60005 + 342 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 344 + 0 + 0 + 1 + 0 + 40 + 346 + 0 + 1 + 1 + 0 + 60031 + 348 + 0 + 0 + 1 + 0 + 40 + 350 + 0 + 1 + 1 + 0 + 60031 + 352 + 0 + 0 + 1 + 0 + 39011 + 354 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 356 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 358 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 359 + 0 + 1 + 1 + 0 + 40 + 361 + 0 + 1 + 1 + 0 + 60005 + 363 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 365 + 0 + 0 + 1 + 0 + 40 + 367 + 0 + 1 + 1 + 0 + 60031 + 369 + 0 + 0 + 1 + 0 + 40 + 371 + 0 + 1 + 1 + 0 + 60031 + 373 + 0 + 0 + 1 + 0 + 39011 + 375 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 377 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 379 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 380 + 0 + 1 + 1 + 0 + 40 + 382 + 0 + 1 + 1 + 0 + 60005 + 384 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 386 + 0 + 0 + 1 + 0 + 40 + 388 + 0 + 1 + 1 + 0 + 60031 + 390 + 0 + 0 + 1 + 0 + 40 + 392 + 0 + 1 + 1 + 0 + 60031 + 394 + 0 + 0 + 1 + 0 + 39011 + 396 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 398 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 400 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 401 + 0 + 1 + 1 + 0 + 40 + 403 + 0 + 1 + 1 + 0 + 60005 + 405 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 407 + 0 + 0 + 1 + 0 + 40 + 409 + 0 + 1 + 1 + 0 + 60031 + 411 + 0 + 0 + 1 + 0 + 40 + 413 + 0 + 1 + 1 + 0 + 60031 + 415 + 0 + 0 + 1 + 0 + 39011 + 417 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 419 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 421 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 422 + 0 + 1 + 1 + 0 + 40 + 424 + 0 + 1 + 1 + 0 + 60005 + 426 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 428 + 0 + 0 + 1 + 0 + 40 + 430 + 0 + 1 + 1 + 0 + 60031 + 432 + 0 + 0 + 1 + 0 + 40 + 434 + 0 + 1 + 1 + 0 + 60031 + 436 + 0 + 0 + 1 + 0 + 39011 + 438 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 440 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 442 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 443 + 0 + 1 + 1 + 0 + 40 + 445 + 0 + 1 + 1 + 0 + 60005 + 447 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 449 + 0 + 0 + 1 + 0 + 40 + 451 + 0 + 1 + 1 + 0 + 60031 + 453 + 0 + 0 + 1 + 0 + 40 + 455 + 0 + 1 + 1 + 0 + 60031 + 457 + 0 + 0 + 1 + 0 + 39011 + 459 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 461 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 463 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 464 + 0 + 1 + 1 + 0 + 40 + 466 + 0 + 1 + 1 + 0 + 60005 + 468 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 470 + 0 + 0 + 1 + 0 + 40 + 472 + 0 + 1 + 1 + 0 + 60031 + 474 + 0 + 0 + 1 + 0 + 40 + 476 + 0 + 1 + 1 + 0 + 60031 + 478 + 0 + 0 + 1 + 0 + 39011 + 480 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 482 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 484 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 485 + 0 + 1 + 1 + 0 + 40 + 487 + 0 + 1 + 1 + 0 + 60005 + 489 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 491 + 0 + 0 + 1 + 0 + 40 + 493 + 0 + 1 + 1 + 0 + 60031 + 495 + 0 + 0 + 1 + 0 + 40 + 497 + 0 + 1 + 1 + 0 + 60031 + 499 + 0 + 0 + 1 + 0 + 39011 + 501 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 503 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 505 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 506 + 0 + 1 + 1 + 0 + 40 + 508 + 0 + 1 + 1 + 0 + 60005 + 510 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 512 + 0 + 0 + 1 + 0 + 40 + 514 + 0 + 1 + 1 + 0 + 60031 + 516 + 0 + 0 + 1 + 0 + 40 + 518 + 0 + 1 + 1 + 0 + 60031 + 520 + 0 + 0 + 1 + 0 + 39011 + 522 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 524 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 526 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 527 + 0 + 1 + 1 + 0 + 40 + 529 + 0 + 1 + 1 + 0 + 60005 + 531 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 533 + 0 + 0 + 1 + 0 + 40 + 535 + 0 + 1 + 1 + 0 + 60031 + 537 + 0 + 0 + 1 + 0 + 40 + 539 + 0 + 1 + 1 + 0 + 60031 + 541 + 0 + 0 + 1 + 0 + 39011 + 543 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 545 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 547 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 548 + 0 + 1 + 1 + 0 + 40 + 550 + 0 + 1 + 1 + 0 + 60005 + 552 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 554 + 0 + 0 + 1 + 0 + 40 + 556 + 0 + 1 + 1 + 0 + 60031 + 558 + 0 + 0 + 1 + 0 + 40 + 560 + 0 + 1 + 1 + 0 + 60031 + 562 + 0 + 0 + 1 + 0 + 39011 + 564 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 566 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 568 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 569 + 0 + 1 + 1 + 0 + 40 + 571 + 0 + 1 + 1 + 0 + 60005 + 573 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 575 + 0 + 0 + 1 + 0 + 40 + 577 + 0 + 1 + 1 + 0 + 60031 + 579 + 0 + 0 + 1 + 0 + 40 + 581 + 0 + 1 + 1 + 0 + 60031 + 583 + 0 + 0 + 1 + 0 + 39011 + 585 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 587 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 589 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 590 + 0 + 1 + 1 + 0 + 40 + 592 + 0 + 1 + 1 + 0 + 60005 + 594 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 596 + 0 + 0 + 1 + 0 + 40 + 598 + 0 + 1 + 1 + 0 + 60031 + 600 + 0 + 0 + 1 + 0 + 40 + 602 + 0 + 1 + 1 + 0 + 60031 + 604 + 0 + 0 + 1 + 0 + 39011 + 606 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 608 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 610 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 611 + 0 + 1 + 1 + 0 + 40 + 613 + 0 + 1 + 1 + 0 + 60005 + 615 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 617 + 0 + 0 + 1 + 0 + 40 + 619 + 0 + 1 + 1 + 0 + 60031 + 621 + 0 + 0 + 1 + 0 + 40 + 623 + 0 + 1 + 1 + 0 + 60031 + 625 + 0 + 0 + 1 + 0 + 39011 + 627 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 629 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 631 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 632 + 0 + 1 + 1 + 0 + 40 + 634 + 0 + 1 + 1 + 0 + 60005 + 636 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 638 + 0 + 0 + 1 + 0 + 40 + 640 + 0 + 1 + 1 + 0 + 60031 + 642 + 0 + 0 + 1 + 0 + 40 + 644 + 0 + 1 + 1 + 0 + 60031 + 646 + 0 + 0 + 1 + 0 + 39011 + 648 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 650 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 652 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 653 + 0 + 1 + 1 + 0 + 40 + 655 + 0 + 1 + 1 + 0 + 60005 + 657 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 659 + 0 + 0 + 1 + 0 + 40 + 661 + 0 + 1 + 1 + 0 + 60031 + 663 + 0 + 0 + 1 + 0 + 40 + 665 + 0 + 1 + 1 + 0 + 60031 + 667 + 0 + 0 + 1 + 0 + 39011 + 669 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 671 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 673 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 674 + 0 + 1 + 1 + 0 + 40 + 676 + 0 + 1 + 1 + 0 + 60005 + 678 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 680 + 0 + 0 + 1 + 0 + 40 + 682 + 0 + 1 + 1 + 0 + 60031 + 684 + 0 + 0 + 1 + 0 + 40 + 686 + 0 + 1 + 1 + 0 + 60031 + 688 + 0 + 0 + 1 + 0 + 39011 + 690 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 692 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 694 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 695 + 0 + 1 + 1 + 0 + 40 + 697 + 0 + 1 + 1 + 0 + 60005 + 699 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 701 + 0 + 0 + 1 + 0 + 40 + 703 + 0 + 1 + 1 + 0 + 60031 + 705 + 0 + 0 + 1 + 0 + 40 + 707 + 0 + 1 + 1 + 0 + 60031 + 709 + 0 + 0 + 1 + 0 + 39011 + 711 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 713 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 715 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 716 + 0 + 1 + 1 + 0 + 40 + 718 + 0 + 1 + 1 + 0 + 60005 + 720 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 722 + 0 + 0 + 1 + 0 + 40 + 724 + 0 + 1 + 1 + 0 + 60031 + 726 + 0 + 0 + 1 + 0 + 40 + 728 + 0 + 1 + 1 + 0 + 60031 + 730 + 0 + 0 + 1 + 0 + 39011 + 732 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 734 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 736 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 737 + 0 + 1 + 1 + 0 + 40 + 739 + 0 + 1 + 1 + 0 + 60005 + 741 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 743 + 0 + 0 + 1 + 0 + 40 + 745 + 0 + 1 + 1 + 0 + 60031 + 747 + 0 + 0 + 1 + 0 + 40 + 749 + 0 + 1 + 1 + 0 + 60031 + 751 + 0 + 0 + 1 + 0 + 39011 + 753 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 755 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 757 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 758 + 0 + 1 + 1 + 0 + 40 + 760 + 0 + 1 + 1 + 0 + 60005 + 762 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 764 + 0 + 0 + 1 + 0 + 40 + 766 + 0 + 1 + 1 + 0 + 60031 + 768 + 0 + 0 + 1 + 0 + 40 + 770 + 0 + 1 + 1 + 0 + 60031 + 772 + 0 + 0 + 1 + 0 + 39011 + 774 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 776 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 778 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 39001 + 779 + 148 + 0 + 1 + 0 + 1 + 10 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 14 + 4 + 4 + 0 + 2 + 0 + 15 + 4 + 6 + 0 + 2 + 0 + 20 + 4 + 8 + 0 + 27 + 0 + 21 + 1 + 35 + 0 + 1 + 0 + 30 + 4 + 36 + 0 + 48 + 0 + 31 + 4 + 84 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 1 + 2 + 26 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 10 + 0 + 1 + 20001 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 1 + 0 + 15005 + 780 + 48 + 22 + 1 + 0 + 0 + 257 + 22 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 781 + 0 + 1 + 1 + 0 + 15011 + 783 + 792 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 729 + 0 + 21 + 1 + 741 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 728 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 21 + 4 + 4 + 0 + 32 + 0 + 22 + 4 + 36 + 0 + 4 + 0 + 900 + 10 + 40 + 0 + 644 + 0 + 0 + 0 + 0 + 0 + 31 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 84 + 104 + 114 + 101 + 97 + 100 + 49 + 3 + 2 + 0 + -1 + 1 + 11 + 201 + 100 + 0 + 0 + 142 + 0 + 204 + 100 + 142 + 0 + 76 + 0 + 209 + 100 + 218 + 0 + 6 + 0 + 211 + 100 + 224 + 0 + 6 + 0 + 213 + 100 + 230 + 0 + 6 + 0 + 215 + 100 + 236 + 0 + 91 + 0 + 217 + 100 + 327 + 0 + 91 + 0 + 219 + 100 + 418 + 0 + 6 + 0 + 221 + 100 + 424 + 0 + 6 + 0 + 223 + 100 + 430 + 0 + 54 + 0 + 100 + 101 + 484 + 0 + 92 + 0 + 39003 + 201 + 136 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 3 + 0 + 12 + 4 + 4 + 0 + 1 + 0 + 13 + 4 + 5 + 0 + 3 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 19 + 0 + 21 + 1 + 31 + 0 + 1 + 0 + 30 + 4 + 32 + 0 + 48 + 0 + 0 + 2 + 1396 + 6 + 0 + 2 + 6 + 6 + 1 + 1 + 1 + 1 + 18 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 1 + 1396 + 1 + 6 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 39012 + 204 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 5 + 0 + 12 + 4 + 7 + 0 + 2 + 0 + 13 + 4 + 9 + 0 + 5 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 1 + 1396 + 4 + 1 + 1 + 1 + 173 + 1 + 6 + 4 + 2 + 2 + 2 + 257 + 1 + 1 + 1 + 2 + 0 + 0 + 60032 + 209 + 0 + 0 + 1 + 0 + 60032 + 211 + 0 + 0 + 1 + 0 + 60032 + 213 + 0 + 0 + 1 + 0 + 60304 + 215 + 85 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 22 + 0 + 21 + 1 + 34 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 21 + 1 + 1 + 10 + 4 + 0 + 0 + 13 + 0 + 12 + 1 + 2 + 12 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 0 + 60304 + 217 + 85 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 22 + 0 + 21 + 1 + 34 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 21 + 1 + 1 + 10 + 4 + 0 + 0 + 13 + 0 + 12 + 1 + 10 + 2 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 0 + 60032 + 219 + 0 + 0 + 1 + 0 + 60032 + 221 + 0 + 0 + 1 + 0 + 15008 + 223 + 48 + 0 + 1 + 0 + 0 + 257 + 173 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 37 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 0 + 11 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 204 + 204 + 0 + 0 + 204 + 204 + 0 + 0 + 209 + 209 + 0 + 0 + 204 + 204 + 1 + 0 + 211 + 211 + 0 + 0 + 204 + 204 + 2 + 0 + 213 + 213 + 0 + 0 + 204 + 204 + 2 + 0 + 215 + 215 + 0 + 0 + 204 + 204 + 2 + 0 + 217 + 217 + 0 + 0 + 215 + 215 + 0 + 0 + 219 + 219 + 0 + 0 + 217 + 217 + 0 + 0 + 221 + 221 + 0 + 0 + 204 + 204 + 3 + 0 + 223 + 223 + 0 + 0 + 215 + 215 + 0 + 0 + 223 + 223 + 1 + 0 + 217 + 217 + 0 + 0 + 223 + 223 + 2 + 0 + 40 + 785 + 0 + 1 + 1 + 0 + 60005 + 787 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 789 + 0 + 0 + 1 + 0 + 40 + 791 + 0 + 1 + 1 + 0 + 60031 + 793 + 0 + 0 + 1 + 0 + 40 + 795 + 0 + 1 + 1 + 0 + 60031 + 797 + 0 + 0 + 1 + 0 + 40 + 799 + 0 + 1 + 1 + 0 + 60031 + 801 + 0 + 0 + 1 + 0 + 39011 + 803 + 70 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 + 4 + 7 + 0 + 5 + 0 + 13 + 4 + 12 + 0 + 2 + 0 + 14 + 4 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 + 1 + 0 + 21 + 1 + 19 + 0 + 1 + 0 + 4 + 1 + 1 + 1 + 1 + 1 + 16 + 4 + 2 + 2 + 2 + 2 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 805 + 3 + 0 + 1 + 0 + 1 + 0 + 16 + 39004 + 807 + 160 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 48 + 0 + 2 + 16 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 16 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 47 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 0 + 287 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 207 + 207 + 0 + 0 + 209 + 209 + 0 + 0 + 211 + 211 + 0 + 0 + 213 + 213 + 0 + 0 + 215 + 215 + 0 + 0 + 207 + 207 + 0 + 0 + 217 + 217 + 0 + 0 + 215 + 215 + 0 + 0 + 218 + 218 + 0 + 0 + 211 + 211 + 0 + 0 + 219 + 219 + 0 + 0 + 220 + 220 + 0 + 0 + 222 + 222 + 0 + 0 + 222 + 222 + 0 + 0 + 224 + 224 + 0 + 0 + 225 + 225 + 0 + 0 + 227 + 227 + 0 + 0 + 227 + 227 + 0 + 0 + 229 + 229 + 0 + 0 + 230 + 230 + 0 + 0 + 232 + 232 + 0 + 0 + 232 + 232 + 0 + 0 + 234 + 234 + 0 + 0 + 235 + 235 + 0 + 0 + 237 + 237 + 0 + 0 + 237 + 237 + 0 + 0 + 239 + 239 + 0 + 0 + 240 + 240 + 0 + 0 + 242 + 242 + 0 + 0 + 242 + 242 + 0 + 0 + 244 + 244 + 0 + 0 + 245 + 245 + 0 + 0 + 247 + 247 + 0 + 0 + 247 + 247 + 0 + 0 + 249 + 249 + 0 + 0 + 250 + 250 + 0 + 0 + 252 + 252 + 0 + 0 + 252 + 252 + 0 + 0 + 254 + 254 + 0 + 0 + 255 + 255 + 0 + 0 + 257 + 257 + 0 + 0 + 257 + 257 + 0 + 0 + 259 + 259 + 0 + 0 + 260 + 260 + 0 + 0 + 262 + 262 + 0 + 0 + 262 + 262 + 0 + 0 + 264 + 264 + 0 + 0 + 207 + 207 + 0 + 0 + 267 + 267 + 0 + 0 + 267 + 267 + 0 + 0 + 269 + 269 + 0 + 0 + 269 + 269 + 0 + 0 + 271 + 271 + 0 + 0 + 271 + 271 + 0 + 0 + 273 + 273 + 0 + 0 + 273 + 273 + 0 + 0 + 269 + 269 + 1 + 0 + 271 + 271 + 0 + 0 + 275 + 275 + 0 + 0 + 275 + 275 + 0 + 0 + 277 + 277 + 0 + 0 + 277 + 277 + 0 + 0 + 267 + 267 + 1 + 0 + 279 + 279 + 0 + 0 + 281 + 281 + 0 + 0 + 281 + 281 + 0 + 0 + 283 + 283 + 0 + 0 + 285 + 285 + 0 + 0 + 287 + 287 + 0 + 0 + 289 + 289 + 0 + 0 + 291 + 291 + 0 + 0 + 291 + 291 + 0 + 0 + 293 + 293 + 0 + 0 + 283 + 283 + 0 + 0 + 293 + 293 + 1 + 0 + 287 + 287 + 0 + 0 + 293 + 293 + 2 + 0 + 275 + 275 + 0 + 0 + 293 + 293 + 3 + 0 + 293 + 293 + 0 + 0 + 297 + 297 + 0 + 0 + 295 + 295 + 0 + 0 + 297 + 297 + 1 + 0 + 298 + 298 + 0 + 0 + 300 + 300 + 0 + 0 + 300 + 300 + 0 + 0 + 302 + 302 + 0 + 0 + 304 + 304 + 0 + 0 + 306 + 306 + 0 + 0 + 308 + 308 + 0 + 0 + 310 + 310 + 0 + 0 + 310 + 310 + 0 + 0 + 312 + 312 + 0 + 0 + 302 + 302 + 0 + 0 + 312 + 312 + 1 + 0 + 306 + 306 + 0 + 0 + 312 + 312 + 2 + 0 + 271 + 271 + 0 + 0 + 312 + 312 + 3 + 0 + 312 + 312 + 0 + 0 + 316 + 316 + 0 + 0 + 314 + 314 + 0 + 0 + 316 + 316 + 1 + 0 + 319 + 319 + 0 + 0 + 321 + 321 + 0 + 0 + 321 + 321 + 0 + 0 + 323 + 323 + 0 + 0 + 325 + 325 + 0 + 0 + 327 + 327 + 0 + 0 + 329 + 329 + 0 + 0 + 331 + 331 + 0 + 0 + 331 + 331 + 0 + 0 + 333 + 333 + 0 + 0 + 323 + 323 + 0 + 0 + 333 + 333 + 1 + 0 + 327 + 327 + 0 + 0 + 333 + 333 + 2 + 0 + 317 + 317 + 0 + 0 + 333 + 333 + 3 + 0 + 333 + 333 + 0 + 0 + 337 + 337 + 0 + 0 + 335 + 335 + 0 + 0 + 337 + 337 + 1 + 0 + 340 + 340 + 0 + 0 + 342 + 342 + 0 + 0 + 342 + 342 + 0 + 0 + 344 + 344 + 0 + 0 + 346 + 346 + 0 + 0 + 348 + 348 + 0 + 0 + 350 + 350 + 0 + 0 + 352 + 352 + 0 + 0 + 352 + 352 + 0 + 0 + 354 + 354 + 0 + 0 + 344 + 344 + 0 + 0 + 354 + 354 + 1 + 0 + 348 + 348 + 0 + 0 + 354 + 354 + 2 + 0 + 338 + 338 + 0 + 0 + 354 + 354 + 3 + 0 + 354 + 354 + 0 + 0 + 358 + 358 + 0 + 0 + 356 + 356 + 0 + 0 + 358 + 358 + 1 + 0 + 361 + 361 + 0 + 0 + 363 + 363 + 0 + 0 + 363 + 363 + 0 + 0 + 365 + 365 + 0 + 0 + 367 + 367 + 0 + 0 + 369 + 369 + 0 + 0 + 371 + 371 + 0 + 0 + 373 + 373 + 0 + 0 + 373 + 373 + 0 + 0 + 375 + 375 + 0 + 0 + 365 + 365 + 0 + 0 + 375 + 375 + 1 + 0 + 369 + 369 + 0 + 0 + 375 + 375 + 2 + 0 + 359 + 359 + 0 + 0 + 375 + 375 + 3 + 0 + 375 + 375 + 0 + 0 + 379 + 379 + 0 + 0 + 377 + 377 + 0 + 0 + 379 + 379 + 1 + 0 + 382 + 382 + 0 + 0 + 384 + 384 + 0 + 0 + 384 + 384 + 0 + 0 + 386 + 386 + 0 + 0 + 388 + 388 + 0 + 0 + 390 + 390 + 0 + 0 + 392 + 392 + 0 + 0 + 394 + 394 + 0 + 0 + 394 + 394 + 0 + 0 + 396 + 396 + 0 + 0 + 386 + 386 + 0 + 0 + 396 + 396 + 1 + 0 + 390 + 390 + 0 + 0 + 396 + 396 + 2 + 0 + 380 + 380 + 0 + 0 + 396 + 396 + 3 + 0 + 396 + 396 + 0 + 0 + 400 + 400 + 0 + 0 + 398 + 398 + 0 + 0 + 400 + 400 + 1 + 0 + 403 + 403 + 0 + 0 + 405 + 405 + 0 + 0 + 405 + 405 + 0 + 0 + 407 + 407 + 0 + 0 + 409 + 409 + 0 + 0 + 411 + 411 + 0 + 0 + 413 + 413 + 0 + 0 + 415 + 415 + 0 + 0 + 415 + 415 + 0 + 0 + 417 + 417 + 0 + 0 + 407 + 407 + 0 + 0 + 417 + 417 + 1 + 0 + 411 + 411 + 0 + 0 + 417 + 417 + 2 + 0 + 401 + 401 + 0 + 0 + 417 + 417 + 3 + 0 + 417 + 417 + 0 + 0 + 421 + 421 + 0 + 0 + 419 + 419 + 0 + 0 + 421 + 421 + 1 + 0 + 424 + 424 + 0 + 0 + 426 + 426 + 0 + 0 + 426 + 426 + 0 + 0 + 428 + 428 + 0 + 0 + 430 + 430 + 0 + 0 + 432 + 432 + 0 + 0 + 434 + 434 + 0 + 0 + 436 + 436 + 0 + 0 + 436 + 436 + 0 + 0 + 438 + 438 + 0 + 0 + 428 + 428 + 0 + 0 + 438 + 438 + 1 + 0 + 432 + 432 + 0 + 0 + 438 + 438 + 2 + 0 + 422 + 422 + 0 + 0 + 438 + 438 + 3 + 0 + 438 + 438 + 0 + 0 + 442 + 442 + 0 + 0 + 440 + 440 + 0 + 0 + 442 + 442 + 1 + 0 + 445 + 445 + 0 + 0 + 447 + 447 + 0 + 0 + 447 + 447 + 0 + 0 + 449 + 449 + 0 + 0 + 451 + 451 + 0 + 0 + 453 + 453 + 0 + 0 + 455 + 455 + 0 + 0 + 457 + 457 + 0 + 0 + 457 + 457 + 0 + 0 + 459 + 459 + 0 + 0 + 449 + 449 + 0 + 0 + 459 + 459 + 1 + 0 + 453 + 453 + 0 + 0 + 459 + 459 + 2 + 0 + 443 + 443 + 0 + 0 + 459 + 459 + 3 + 0 + 459 + 459 + 0 + 0 + 463 + 463 + 0 + 0 + 461 + 461 + 0 + 0 + 463 + 463 + 1 + 0 + 466 + 466 + 0 + 0 + 468 + 468 + 0 + 0 + 468 + 468 + 0 + 0 + 470 + 470 + 0 + 0 + 472 + 472 + 0 + 0 + 474 + 474 + 0 + 0 + 476 + 476 + 0 + 0 + 478 + 478 + 0 + 0 + 478 + 478 + 0 + 0 + 480 + 480 + 0 + 0 + 470 + 470 + 0 + 0 + 480 + 480 + 1 + 0 + 474 + 474 + 0 + 0 + 480 + 480 + 2 + 0 + 464 + 464 + 0 + 0 + 480 + 480 + 3 + 0 + 480 + 480 + 0 + 0 + 484 + 484 + 0 + 0 + 482 + 482 + 0 + 0 + 484 + 484 + 1 + 0 + 487 + 487 + 0 + 0 + 489 + 489 + 0 + 0 + 489 + 489 + 0 + 0 + 491 + 491 + 0 + 0 + 493 + 493 + 0 + 0 + 495 + 495 + 0 + 0 + 497 + 497 + 0 + 0 + 499 + 499 + 0 + 0 + 499 + 499 + 0 + 0 + 501 + 501 + 0 + 0 + 491 + 491 + 0 + 0 + 501 + 501 + 1 + 0 + 495 + 495 + 0 + 0 + 501 + 501 + 2 + 0 + 485 + 485 + 0 + 0 + 501 + 501 + 3 + 0 + 501 + 501 + 0 + 0 + 505 + 505 + 0 + 0 + 503 + 503 + 0 + 0 + 505 + 505 + 1 + 0 + 508 + 508 + 0 + 0 + 510 + 510 + 0 + 0 + 510 + 510 + 0 + 0 + 512 + 512 + 0 + 0 + 514 + 514 + 0 + 0 + 516 + 516 + 0 + 0 + 518 + 518 + 0 + 0 + 520 + 520 + 0 + 0 + 520 + 520 + 0 + 0 + 522 + 522 + 0 + 0 + 512 + 512 + 0 + 0 + 522 + 522 + 1 + 0 + 516 + 516 + 0 + 0 + 522 + 522 + 2 + 0 + 506 + 506 + 0 + 0 + 522 + 522 + 3 + 0 + 522 + 522 + 0 + 0 + 526 + 526 + 0 + 0 + 524 + 524 + 0 + 0 + 526 + 526 + 1 + 0 + 529 + 529 + 0 + 0 + 531 + 531 + 0 + 0 + 531 + 531 + 0 + 0 + 533 + 533 + 0 + 0 + 535 + 535 + 0 + 0 + 537 + 537 + 0 + 0 + 539 + 539 + 0 + 0 + 541 + 541 + 0 + 0 + 541 + 541 + 0 + 0 + 543 + 543 + 0 + 0 + 533 + 533 + 0 + 0 + 543 + 543 + 1 + 0 + 537 + 537 + 0 + 0 + 543 + 543 + 2 + 0 + 527 + 527 + 0 + 0 + 543 + 543 + 3 + 0 + 543 + 543 + 0 + 0 + 547 + 547 + 0 + 0 + 545 + 545 + 0 + 0 + 547 + 547 + 1 + 0 + 550 + 550 + 0 + 0 + 552 + 552 + 0 + 0 + 552 + 552 + 0 + 0 + 554 + 554 + 0 + 0 + 556 + 556 + 0 + 0 + 558 + 558 + 0 + 0 + 560 + 560 + 0 + 0 + 562 + 562 + 0 + 0 + 562 + 562 + 0 + 0 + 564 + 564 + 0 + 0 + 554 + 554 + 0 + 0 + 564 + 564 + 1 + 0 + 558 + 558 + 0 + 0 + 564 + 564 + 2 + 0 + 548 + 548 + 0 + 0 + 564 + 564 + 3 + 0 + 564 + 564 + 0 + 0 + 568 + 568 + 0 + 0 + 566 + 566 + 0 + 0 + 568 + 568 + 1 + 0 + 571 + 571 + 0 + 0 + 573 + 573 + 0 + 0 + 573 + 573 + 0 + 0 + 575 + 575 + 0 + 0 + 577 + 577 + 0 + 0 + 579 + 579 + 0 + 0 + 581 + 581 + 0 + 0 + 583 + 583 + 0 + 0 + 583 + 583 + 0 + 0 + 585 + 585 + 0 + 0 + 575 + 575 + 0 + 0 + 585 + 585 + 1 + 0 + 579 + 579 + 0 + 0 + 585 + 585 + 2 + 0 + 569 + 569 + 0 + 0 + 585 + 585 + 3 + 0 + 585 + 585 + 0 + 0 + 589 + 589 + 0 + 0 + 587 + 587 + 0 + 0 + 589 + 589 + 1 + 0 + 592 + 592 + 0 + 0 + 594 + 594 + 0 + 0 + 594 + 594 + 0 + 0 + 596 + 596 + 0 + 0 + 598 + 598 + 0 + 0 + 600 + 600 + 0 + 0 + 602 + 602 + 0 + 0 + 604 + 604 + 0 + 0 + 604 + 604 + 0 + 0 + 606 + 606 + 0 + 0 + 596 + 596 + 0 + 0 + 606 + 606 + 1 + 0 + 600 + 600 + 0 + 0 + 606 + 606 + 2 + 0 + 590 + 590 + 0 + 0 + 606 + 606 + 3 + 0 + 606 + 606 + 0 + 0 + 610 + 610 + 0 + 0 + 608 + 608 + 0 + 0 + 610 + 610 + 1 + 0 + 613 + 613 + 0 + 0 + 615 + 615 + 0 + 0 + 615 + 615 + 0 + 0 + 617 + 617 + 0 + 0 + 619 + 619 + 0 + 0 + 621 + 621 + 0 + 0 + 623 + 623 + 0 + 0 + 625 + 625 + 0 + 0 + 625 + 625 + 0 + 0 + 627 + 627 + 0 + 0 + 617 + 617 + 0 + 0 + 627 + 627 + 1 + 0 + 621 + 621 + 0 + 0 + 627 + 627 + 2 + 0 + 611 + 611 + 0 + 0 + 627 + 627 + 3 + 0 + 627 + 627 + 0 + 0 + 631 + 631 + 0 + 0 + 629 + 629 + 0 + 0 + 631 + 631 + 1 + 0 + 634 + 634 + 0 + 0 + 636 + 636 + 0 + 0 + 636 + 636 + 0 + 0 + 638 + 638 + 0 + 0 + 640 + 640 + 0 + 0 + 642 + 642 + 0 + 0 + 644 + 644 + 0 + 0 + 646 + 646 + 0 + 0 + 646 + 646 + 0 + 0 + 648 + 648 + 0 + 0 + 638 + 638 + 0 + 0 + 648 + 648 + 1 + 0 + 642 + 642 + 0 + 0 + 648 + 648 + 2 + 0 + 632 + 632 + 0 + 0 + 648 + 648 + 3 + 0 + 648 + 648 + 0 + 0 + 652 + 652 + 0 + 0 + 650 + 650 + 0 + 0 + 652 + 652 + 1 + 0 + 655 + 655 + 0 + 0 + 657 + 657 + 0 + 0 + 657 + 657 + 0 + 0 + 659 + 659 + 0 + 0 + 661 + 661 + 0 + 0 + 663 + 663 + 0 + 0 + 665 + 665 + 0 + 0 + 667 + 667 + 0 + 0 + 667 + 667 + 0 + 0 + 669 + 669 + 0 + 0 + 659 + 659 + 0 + 0 + 669 + 669 + 1 + 0 + 663 + 663 + 0 + 0 + 669 + 669 + 2 + 0 + 653 + 653 + 0 + 0 + 669 + 669 + 3 + 0 + 669 + 669 + 0 + 0 + 673 + 673 + 0 + 0 + 671 + 671 + 0 + 0 + 673 + 673 + 1 + 0 + 676 + 676 + 0 + 0 + 678 + 678 + 0 + 0 + 678 + 678 + 0 + 0 + 680 + 680 + 0 + 0 + 682 + 682 + 0 + 0 + 684 + 684 + 0 + 0 + 686 + 686 + 0 + 0 + 688 + 688 + 0 + 0 + 688 + 688 + 0 + 0 + 690 + 690 + 0 + 0 + 680 + 680 + 0 + 0 + 690 + 690 + 1 + 0 + 684 + 684 + 0 + 0 + 690 + 690 + 2 + 0 + 674 + 674 + 0 + 0 + 690 + 690 + 3 + 0 + 690 + 690 + 0 + 0 + 694 + 694 + 0 + 0 + 692 + 692 + 0 + 0 + 694 + 694 + 1 + 0 + 697 + 697 + 0 + 0 + 699 + 699 + 0 + 0 + 699 + 699 + 0 + 0 + 701 + 701 + 0 + 0 + 703 + 703 + 0 + 0 + 705 + 705 + 0 + 0 + 707 + 707 + 0 + 0 + 709 + 709 + 0 + 0 + 709 + 709 + 0 + 0 + 711 + 711 + 0 + 0 + 701 + 701 + 0 + 0 + 711 + 711 + 1 + 0 + 705 + 705 + 0 + 0 + 711 + 711 + 2 + 0 + 695 + 695 + 0 + 0 + 711 + 711 + 3 + 0 + 711 + 711 + 0 + 0 + 715 + 715 + 0 + 0 + 713 + 713 + 0 + 0 + 715 + 715 + 1 + 0 + 718 + 718 + 0 + 0 + 720 + 720 + 0 + 0 + 720 + 720 + 0 + 0 + 722 + 722 + 0 + 0 + 724 + 724 + 0 + 0 + 726 + 726 + 0 + 0 + 728 + 728 + 0 + 0 + 730 + 730 + 0 + 0 + 730 + 730 + 0 + 0 + 732 + 732 + 0 + 0 + 722 + 722 + 0 + 0 + 732 + 732 + 1 + 0 + 726 + 726 + 0 + 0 + 732 + 732 + 2 + 0 + 716 + 716 + 0 + 0 + 732 + 732 + 3 + 0 + 732 + 732 + 0 + 0 + 736 + 736 + 0 + 0 + 734 + 734 + 0 + 0 + 736 + 736 + 1 + 0 + 739 + 739 + 0 + 0 + 741 + 741 + 0 + 0 + 741 + 741 + 0 + 0 + 743 + 743 + 0 + 0 + 745 + 745 + 0 + 0 + 747 + 747 + 0 + 0 + 749 + 749 + 0 + 0 + 751 + 751 + 0 + 0 + 751 + 751 + 0 + 0 + 753 + 753 + 0 + 0 + 743 + 743 + 0 + 0 + 753 + 753 + 1 + 0 + 747 + 747 + 0 + 0 + 753 + 753 + 2 + 0 + 737 + 737 + 0 + 0 + 753 + 753 + 3 + 0 + 753 + 753 + 0 + 0 + 757 + 757 + 0 + 0 + 755 + 755 + 0 + 0 + 757 + 757 + 1 + 0 + 760 + 760 + 0 + 0 + 762 + 762 + 0 + 0 + 762 + 762 + 0 + 0 + 764 + 764 + 0 + 0 + 766 + 766 + 0 + 0 + 768 + 768 + 0 + 0 + 770 + 770 + 0 + 0 + 772 + 772 + 0 + 0 + 772 + 772 + 0 + 0 + 774 + 774 + 0 + 0 + 764 + 764 + 0 + 0 + 774 + 774 + 1 + 0 + 768 + 768 + 0 + 0 + 774 + 774 + 2 + 0 + 758 + 758 + 0 + 0 + 774 + 774 + 3 + 0 + 774 + 774 + 0 + 0 + 778 + 778 + 0 + 0 + 776 + 776 + 0 + 0 + 778 + 778 + 1 + 0 + 0 + 0 + 0 + 2 + 779 + 779 + 0 + 0 + 0 + 0 + 0 + 2 + 780 + 780 + 0 + 0 + 781 + 781 + 0 + 0 + 783 + 783 + 0 + 0 + 785 + 785 + 0 + 0 + 787 + 787 + 0 + 0 + 787 + 787 + 0 + 0 + 789 + 789 + 0 + 0 + 791 + 791 + 0 + 0 + 793 + 793 + 0 + 0 + 795 + 795 + 0 + 0 + 797 + 797 + 0 + 0 + 799 + 799 + 0 + 0 + 801 + 801 + 0 + 0 + 801 + 801 + 0 + 0 + 803 + 803 + 0 + 0 + 789 + 789 + 0 + 0 + 803 + 803 + 1 + 0 + 793 + 793 + 0 + 0 + 803 + 803 + 2 + 0 + 797 + 797 + 0 + 0 + 803 + 803 + 3 + 0 + 803 + 803 + 0 + 0 + 807 + 807 + 0 + 0 + 805 + 805 + 0 + 0 + 807 + 807 + 1 + 146 + 0 + 1 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.rpar b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.rpar new file mode 100644 index 00000000..2ea06218 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.rpar @@ -0,0 +1,146 @@ +0.0370370370370370349810685 +1.0000000000000000000000000 +2.0000000000000000000000000 +12.0000000000000000000000000 +14.0000000000000000000000000 +15.0000000000000000000000000 +16.0000000000000000000000000 +17.0000000000000000000000000 +18.0000000000000000000000000 +19.0000000000000000000000000 +20.0000000000000000000000000 +21.0000000000000000000000000 +22.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +0.1000000000000000055511151 +-1.0000000000000000000000000 +1.0000000000000000000000000 +0.1000000000000000055511151 +0.1000000000000000055511151 +-1.0000000000000000000000000 +1.0000000000000000000000000 +0.5999999999999999777955395 +1.0000000000000000000000000 +0.0000000000000000000000000 +1295793.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1295793.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +2.0000000000000000000000000 +1295793.0000000000000000000000000 +2.0000000000000000000000000 +1.0000000000000000000000000 +3.0000000000000000000000000 +1295793.0000000000000000000000000 +3.0000000000000000000000000 +1.0000000000000000000000000 +4.0000000000000000000000000 +1295793.0000000000000000000000000 +4.0000000000000000000000000 +1.0000000000000000000000000 +5.0000000000000000000000000 +1295793.0000000000000000000000000 +5.0000000000000000000000000 +1.0000000000000000000000000 +6.0000000000000000000000000 +1295793.0000000000000000000000000 +6.0000000000000000000000000 +1.0000000000000000000000000 +7.0000000000000000000000000 +1295793.0000000000000000000000000 +7.0000000000000000000000000 +1.0000000000000000000000000 +8.0000000000000000000000000 +1295793.0000000000000000000000000 +8.0000000000000000000000000 +1.0000000000000000000000000 +9.0000000000000000000000000 +1295793.0000000000000000000000000 +9.0000000000000000000000000 +1.0000000000000000000000000 +10.0000000000000000000000000 +1295793.0000000000000000000000000 +10.0000000000000000000000000 +1.0000000000000000000000000 +11.0000000000000000000000000 +1295793.0000000000000000000000000 +11.0000000000000000000000000 +1.0000000000000000000000000 +12.0000000000000000000000000 +1295793.0000000000000000000000000 +12.0000000000000000000000000 +1.0000000000000000000000000 +13.0000000000000000000000000 +1295793.0000000000000000000000000 +13.0000000000000000000000000 +1.0000000000000000000000000 +14.0000000000000000000000000 +1295793.0000000000000000000000000 +14.0000000000000000000000000 +1.0000000000000000000000000 +15.0000000000000000000000000 +1295793.0000000000000000000000000 +15.0000000000000000000000000 +1.0000000000000000000000000 +16.0000000000000000000000000 +1295793.0000000000000000000000000 +16.0000000000000000000000000 +1.0000000000000000000000000 +17.0000000000000000000000000 +1295793.0000000000000000000000000 +17.0000000000000000000000000 +1.0000000000000000000000000 +18.0000000000000000000000000 +1295793.0000000000000000000000000 +18.0000000000000000000000000 +1.0000000000000000000000000 +19.0000000000000000000000000 +1295793.0000000000000000000000000 +19.0000000000000000000000000 +1.0000000000000000000000000 +20.0000000000000000000000000 +1295793.0000000000000000000000000 +20.0000000000000000000000000 +1.0000000000000000000000000 +21.0000000000000000000000000 +1295793.0000000000000000000000000 +21.0000000000000000000000000 +1.0000000000000000000000000 +22.0000000000000000000000000 +1295793.0000000000000000000000000 +22.0000000000000000000000000 +1.0000000000000000000000000 +23.0000000000000000000000000 +1295793.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +0.0000000000000000000000000 +1295793.0000000000000000000000000 diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.sce b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.sce new file mode 100644 index 00000000..5a1d1b38 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.sce @@ -0,0 +1,211 @@ +// +// +// Example for a communication interface from ORTD using UDP datagrams to e.g. +// nodejs. +// The file webinterface/webappUDP.js is the counterpart that provides a +// web-interface to control a oscillator-system in this example. +// +// For more details, please consider the readme-file. +// +// Rev 1 +// + +// The name of the program +ProgramName = 'UDPio'; // must be the filename without .sce + + + + + + + + + + + +// And example-system that is controlled via UDP and one step further with the Web-gui +// Superblock: A more complex oscillator with damping +function [sim, x,v] = damped_oscillator(sim, u) + // create feedback signals + [sim,x_feedback] = libdyn_new_feedback(sim); + + [sim,v_feedback] = libdyn_new_feedback(sim); + + // use this as a normal signal + [sim,a] = ld_add(sim, ev, list(u, x_feedback), [1, -1]); + [sim,a] = ld_add(sim, ev, list(a, v_feedback), [1, -1]); + + [sim,v] = ld_ztf(sim, ev, a, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,v_gain] = ld_gain(sim, ev, v, 0.1); + + // close loop v_gain = v_feedback + [sim] = libdyn_close_loop(sim, v_gain, v_feedback); + + + [sim,x] = ld_ztf(sim, ev, v, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,x_gain] = ld_gain(sim, ev, x, 0.6); + + // close loop x_gain = x_feedback + [sim] = libdyn_close_loop(sim, x_gain, x_feedback); +endfunction + + + + + + + +// The main real-time thread +function [sim, outlist, userdata] = Thread_MainRT(sim, inlist, userdata) + // This will run in a thread + [sim, Tpause] = ld_const(sim, ev, 1/27); // The sampling time that is constant at 20 Hz in this example + [sim, out] = ld_ClockSync(sim, ev, in=Tpause); // synchronise this simulation + + // + // Add you own control system here + // + + + Configuration.UnderlyingProtocoll = "UDP"; + Configuration.DestHost = "127.0.0.1"; + Configuration.DestPort = 20000; + Configuration.LocalSocketHost = "127.0.0.1"; + Configuration.LocalSocketPort = 20001; + PacketFramework.Configuration.debugmode = %t; + [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="TrockenofenRemoteControl", Configuration) + + // Add a parameter for controlling the oscillator + [sim, PacketFramework, Input]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Oscillator input"); + + // some some more parameters + [sim, PacketFramework, AParVector]=ld_PF_Parameter(sim, PacketFramework, NValues=10, datatype=ORTD.DATATYPE_FLOAT, ParameterName="A vectorial parameter"); + [sim, PacketFramework, par2]=ld_PF_Parameter(sim, PacketFramework, NValues=2, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Test"); + + // printf these parameters + [sim] = ld_printf(sim, ev, Input, "Oscillator input ", 1); + [sim] = ld_printf(sim, ev, par2, "Test ", 2); + [sim] = ld_printf(sim, ev, AParVector, "A vectorial parameter", 10); + + + [sim, PacketFramework, par]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="P1"); [sim] = ld_printf(sim, ev, par, "P1 ", 1); + [sim, PacketFramework, par]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="P2"); [sim] = ld_printf(sim, ev, par, "P2 ", 1); + [sim, PacketFramework, par]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="P3"); [sim] = ld_printf(sim, ev, par, "P3 ", 1); + [sim, PacketFramework, par]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="P4"); [sim] = ld_printf(sim, ev, par, "P4 ", 1); + [sim, PacketFramework, par]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Test5"); [sim] = ld_printf(sim, ev, par, "Test5 ", 1); + [sim, PacketFramework, par]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="P6"); [sim] = ld_printf(sim, ev, par, "P6 ", 1); + [sim, PacketFramework, par]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="P7"); [sim] = ld_printf(sim, ev, par, "P7 ", 1); + [sim, PacketFramework, par]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="P8"); [sim] = ld_printf(sim, ev, par, "P8 ", 1); + [sim, PacketFramework, par]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="P9"); [sim] = ld_printf(sim, ev, par, "P9 ", 1); + + + + // The system to control + T_a = 0.1; [sim, x,v] = damped_oscillator(sim, Input); + + // Stream the data of the oscillator + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X") + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V") + + + [sim, const] = ld_const(sim, 0, 1); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal1"); + [sim, const] = ld_const(sim, 0, 2); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Sig2nal"); + [sim, const] = ld_const(sim, 0, 3); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Sign3al"); + [sim, const] = ld_const(sim, 0, 4); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="_Sig4nal"); + [sim, const] = ld_const(sim, 0, 5); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal5"); + [sim, const] = ld_const(sim, 0, 6); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal6"); + [sim, const] = ld_const(sim, 0, 7); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal7"); + [sim, const] = ld_const(sim, 0, 8); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal8"); + [sim, const] = ld_const(sim, 0, 9); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal9"); + [sim, const] = ld_const(sim, 0, 10); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal10"); + [sim, const] = ld_const(sim, 0, 11); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal11"); + [sim, const] = ld_const(sim, 0, 12); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Sign12al"); + [sim, const] = ld_const(sim, 0, 13); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal_13"); + [sim, const] = ld_const(sim, 0, 14); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="HalloWelt14"); + [sim, const] = ld_const(sim, 0, 15); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="15"); + [sim, const] = ld_const(sim, 0, 16); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="16"); + [sim, const] = ld_const(sim, 0, 17); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Test17"); + [sim, const] = ld_const(sim, 0, 18); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="18"); + [sim, const] = ld_const(sim, 0, 19); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal19"); + [sim, const] = ld_const(sim, 0, 20); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal20"); + [sim, const] = ld_const(sim, 0, 21); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal21"); + [sim, const] = ld_const(sim, 0, 22); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=const, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Signal22"); + + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); + ld_PF_Export_js(PacketFramework, fname="ProtocollConfig.json"); + + + outlist = list(); +endfunction + + + + +// This is the main top level schematic +function [sim, outlist] = schematic_fn(sim, inlist) + +// +// Create a thread that runs the control system +// + + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; // or ORTD.ORTD_RT_NORMALTASK + ThreadPrioStruct.prio2=0; // for ORTD.ORTD_RT_REALTIMETASK: 1-99 as described in man sched_setscheduler + // for ORTD.ORTD_RT_NORMALTASK this is the nice-value (higher value means less priority) + ThreadPrioStruct.cpu = -1; // The CPU on which the thread will run; -1 dynamically assigns to a CPU, + // counting of the CPUs starts at 0 + + [sim, StartThread] = ld_initimpuls(sim, ev); // triggers your computation only once + [sim, outlist, computation_finished] = ld_async_simulation(sim, ev, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = Thread_MainRT, ... + TriggerSignal=StartThread, name="MainRealtimeThread", ... + ThreadPrioStruct, userdata=list() ); + + // output of schematic (empty) + outlist = list(); +endfunction + + + + + +// +// Set-up (no detailed understanding necessary) +// + +thispath = get_absolute_file_path(ProgramName+'.sce'); +cd(thispath); +z = poly(0,'z'); + + +// The following code is integrated into ORTD since rev 494. +// Thus the following line is commented. + +//exec('webinterface/PacketFramework.sce'); + + + +// defile ev +ev = [0]; // main event + +// set-up schematic by calling the user defined function "schematic_fn" +insizes = []; outsizes=[]; +[sim_container_irpar, sim]=libdyn_setup_schematic(schematic_fn, insizes, outsizes); + +// pack the simulation into a irpar container +parlist = new_irparam_set(); +parlist = new_irparam_container(parlist, sim_container_irpar, 901); // pack simulations into irpar container with id = 901 +par = combine_irparam(parlist); // complete irparam set +save_irparam(par, ProgramName+'.ipar', ProgramName+'.rpar'); // Save the schematic to disk + +// clear +par.ipar = []; par.rpar = []; + + diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/run_UDPio.sh b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/run_UDPio.sh new file mode 100755 index 00000000..28def1c1 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/run_UDPio.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +#ortd --baserate=10 --rtmode 1 -s UDPio -i 901 -l 0 +ortdrun -s UDPio diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/PacketFramework.sce b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/PacketFramework.sce new file mode 100644 index 00000000..cb51db73 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/PacketFramework.sce @@ -0,0 +1,487 @@ + + +// +// +// A packet based communication interface from ORTD using UDP datagrams to e.g. +// nodejs. +// webappUDP.js is the counterpart that provides a web-interface +// +// Current Rev: 7 +// +// Revisions: +// +// 27.3.14 - possibility to reservate sources +// 3.4.14 - small re-arrangements +// 4.4.14 - Bugfixes +// 7.4.14 - Bugfix +// 12.6.14 - Bugfix +// 2.11.14 - Added group finalising packet +// + + +function [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName) + SourceID = PacketFramework.SourceID_counter; + + Source.SourceName = SourceName; + Source.SourceID = SourceID; + Source.NValues_send = NValues_send; + Source.datatype = datatype; + + // Add new source to the list + PacketFramework.Sources($+1) = Source; + + // inc counter + PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; +endfunction + +function [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName) + ParameterID = PacketFramework.Parameterid_counter; + + Parameter.ParameterName = ParameterName; + Parameter.ParameterID = ParameterID; + Parameter.NValues = NValues; + Parameter.datatype = datatype; + Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; + + // Add new source to the list + PacketFramework.Parameters($+1) = Parameter; + + // inc counters + PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; + PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; + + // return values + ParameterID = Parameter.ParameterID; + MemoryOfs = Parameter.MemoryOfs; +endfunction + +function [sim, PacketFramework, Parameter] = ld_PF_Parameter(sim, PacketFramework, NValues, datatype, ParameterName) // PARSEDOCU_BLOCK +// +// Define a parameter +// +// NValues - amount of data sets +// datatype - only ORTD.DATATYPE_FLOAT for now +// ParameterName - a unique string decribing the parameter +// +// +// + + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); + + // read data from global memory + [sim, readI] = ld_const(sim, 0, MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + datatype, NValues); +endfunction + + + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, SourceID); + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); + + printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + +endfunction + + + + +function [sim, PacketFramework] = ld_SendPacket(sim, PacketFramework, Signal, NValues_send, datatype, SourceName) // PARSEDOCU_BLOCK // PARSEDOCU_BLOCK +// +// Stream data - block +// +// Signal - the signal to stream +// NValues_send - the vector length of Signal +// datatype - only ORTD.DATATYPE_FLOAT by now +// SourceName - a unique string identifier descring the stream +// +// +// + + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); +endfunction + + + + +function [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName, Configuration) // PARSEDOCU_BLOCK +// +// Initialise an instance of the Packet Framework +// +// InstanceName - a unique string identifier for the instance +// Configuration must include the following properties: +// +// Configuration.UnderlyingProtocoll = "UDP" +// Configuration.DestHost +// Configuration.DestPort +// Configuration.LocalSocketHost +// Configuration.LocalSocketPort +// +// +// Example: +// +// +// Configuration.UnderlyingProtocoll = "UDP"; +// Configuration.DestHost = "127.0.0.1"; +// Configuration.DestPort = 20000; +// Configuration.LocalSocketHost = "127.0.0.1"; +// Configuration.LocalSocketPort = 20001; +// [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); +// +// +// +// Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations +// +// + + // initialise structure for sources + PacketFramework.InstanceName = InstanceName; + PacketFramework.Configuration = Configuration; + + PacketFramework.Configuration.debugmode = %F; + +// disp(Configuration.UnderlyingProtocoll) + + if Configuration.UnderlyingProtocoll == 'UDP' + null; + else + error("PacketFramework: Only UDP supported up to now"); + end + + // possible packet sizes for UDP + PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; + + // sources + PacketFramework.SourceID_counter = 0; + PacketFramework.Sources = list(); + + // parameters + PacketFramework.Parameterid_counter = 0; + PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory + PacketFramework.Parameters = list(); + + PacketFramework.SenderID = 1295793; +endfunction + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Group ID + [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources + [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); + +// printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + + + [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); +endfunction + +function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOCU_BLOCK +// +// Finalise the instance. +// +// + + // The main real-time thread + function [sim] = ld_PF_InitUDP(sim, InstanceName, ParameterMemory) + + function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) + // This will run in a thread. Each time a UDP-packet is received + // one simulation step is performed. Herein, the packet is parsed + // and the contained parameters are stored into a memory. + + // Sync the simulation to incomming UDP-packets + [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); + + // disassemble packet's structure + [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... + outsizes=[1,1,1,TotalElemetsPerPacket], ... + outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); + + + + DisAsm_ = list(); + DisAsm_(4) = DisAsm(4); + [sim, DisAsm_(1)] = ld_Int32ToFloat(sim, 0, DisAsm(1) ); + [sim, DisAsm_(2)] = ld_Int32ToFloat(sim, 0, DisAsm(2) ); + [sim, DisAsm_(3)] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); + + + [sim, memofs] = ld_ArrayInt32(sim, 0, array=ParameterMemory.MemoryOfs, in=DisAsm(3) ); + [sim, Nelements] = ld_ArrayInt32(sim, 0, array=ParameterMemory.Sizes, in=DisAsm(3) ); + + [sim, memofs_] = ld_Int32ToFloat(sim, 0, memofs ); + [sim, Nelements_] = ld_Int32ToFloat(sim, 0, Nelements ); + + if PacketFramework.Configuration.debugmode then + // print the contents of the packet + [sim] = ld_printf(sim, 0, DisAsm_(1), "DisAsm(1) (SenderID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(2), "DisAsm(2) (Packet Counter) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(3), "DisAsm(3) (SourceID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(4), "DisAsm(4) (Signal) = ", TotalElemetsPerPacket); + + [sim] = ld_printf(sim, 0, memofs_ , "memofs = ", 1); + [sim] = ld_printf(sim, 0, memofs_ , "Nelements = ", 1); + end + + // Store the input data into a shared memory + [sim] = ld_WriteMemory2(sim, 0, data=DisAsm(4), index=memofs, ElementsToWrite=Nelements, ... + ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); + + + + // output of schematic + outlist = list(); + endfunction + + + + // start the node.js service from the subfolder webinterface + //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); + + TotalMemorySize = sum(PacketFramework.ParameterMemory.Sizes); + TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketSize = PacketFramework.PacketSize; + + // Open an UDP-Port in server mode + [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... + hostname=PacketFramework.Configuration.LocalSocketHost, ... + UDPPort=PacketFramework.Configuration.LocalSocketPort); + + // initialise a global memory for storing the input data for the computation + [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... + datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... + initial_data=[zeros(TotalMemorySize,1)], ... + visibility='global', useMutex=1); + + // Create thread for the receiver + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; + [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step + [sim, outlist, computation_finished] = ld_async_simulation(sim, 0, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = UDPReceiverThread, ... + TriggerSignal=startcalc, name=InstanceName+"Thread1", ... + ThreadPrioStruct, userdata=list() ); + + + endfunction + + + + + + // calc memory + MemoryOfs = []; + Sizes = []; + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + + Sizes = [Sizes; P.NValues]; + MemoryOfs = [MemoryOfs; P.MemoryOfs]; + end + + PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; + PacketFramework.ParameterMemory.Sizes = Sizes; + + // udp + [sim] = ld_PF_InitUDP(sim, PacketFramework.InstanceName, PacketFramework.ParameterMemory); + + // Send to group update notifications for each group (currently only one possible) + [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); + +endfunction + +function ld_PF_Export_js(PacketFramework, fname) // PARSEDOCU_BLOCK +// +// Export configuration of the defined protocoll (Sources, Parameters) +// into JSON-format. This is to be used by software that shall communicate +// to the real-time system. +// +// fname - The file name +// +// + + + fd = mopen(fname,'wt'); + + mfprintf(fd,' {""SourcesConfig"" : {\n'); + + for i=1:length(PacketFramework.Sources) + + + SourceID = PacketFramework.Sources(i).SourceID; + SourceName = PacketFramework.Sources(i).SourceName; + disp(SourceID ); + disp( SourceName ); + + + line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"" } \n", ... + string(PacketFramework.Sources(i).SourceID), ... + string(PacketFramework.Sources(i).SourceName), ... + string(PacketFramework.Sources(i).NValues_send), ... + string(PacketFramework.Sources(i).datatype) ); + + + if i==length(PacketFramework.Sources) + // finalise the last entry without "," + printf('%s \n' , line); + mfprintf(fd,'%s', line); + else + printf('%s, \n' , line); + mfprintf(fd,'%s,', line); + end + + + end + + + + + mfprintf(fd,'} , \n ""ParametersConfig"" : {\n'); + + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + + printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); + + line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } \n", ... + string(PacketFramework.Parameters(i).ParameterID), ... + string(PacketFramework.Parameters(i).ParameterName), ... + string(PacketFramework.Parameters(i).NValues), ... + string(PacketFramework.Parameters(i).datatype) ); + + + if i==length(PacketFramework.Parameters) + // finalise the last entry without "," + printf('%s \n' , line); + mfprintf(fd,'%s', line); + else + printf('%s, \n' , line); + mfprintf(fd,'%s,', line); + end + + + end + + mfprintf(fd,'}\n}'); + + mclose(fd); +endfunction + +// +// Added 27.3.14 +// + +function [sim, PacketFramework, SourceID]=ld_SendPacketReserve(sim, PacketFramework, NValues_send, datatype, SourceName) + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); +endfunction + +function [sim, PacketFramework]=ld_SendPacket2(sim, PacketFramework, Signal, SourceName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Sources) + S = PacketFramework.Sources(i); + if S.SourceName == SourceName + index = i; + printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); + break; + end + end + + if index == -1 + printf("SourceName = %s\n", SourceName); + error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); + end + + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); +endfunction + + + +function [sim, PacketFramework, ParameterID]=ld_PF_ParameterReserve(sim, PacketFramework, NValues, datatype, ParameterName) + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); +endfunction + + +function [sim, PacketFramework, Parameter]=ld_PF_Parameter2(sim, PacketFramework, ParameterName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + if P.ParameterName == ParameterName + index = i; + printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); + break; + end + end + + if index == -1 + printf("ParameterName = %s\n", ParameterName); + error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); + end + + // read data from global memory + [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + P.datatype, P.NValues); +endfunction + diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/mainAuto.html b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/mainAuto.html new file mode 100644 index 00000000..491f5a19 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/mainAuto.html @@ -0,0 +1,155 @@ + + + + + + + + + Generic GUI-interface to an ORTD-simulation + +
+ This is a generic interface to ORTD. All parameters and datasources are automatically shown. Feel free to copy and adapt it to your needs to build you own specialised web-interface. + +

+ + Parameters:
0
+ + Displays:
0
+ +

+ ProtocollConfiguration:
0
+ + + \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/main_Plot.html b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/main_Plot.html new file mode 100644 index 00000000..47a8fa23 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/main_Plot.html @@ -0,0 +1,365 @@ + + + + + +
+ + + + + + + + + + + GUI-interface to ORTD-simulation + +

+ + + Parameters:
0
+ + Displays:
0
+ + + + + ProtocollConfiguration:
0
+

+ + + + + + + + \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/install_nodejs.sh b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/install_nodejs.sh new file mode 100644 index 00000000..7cdf2b59 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/install_nodejs.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +echo 'export PATH=$HOME/local/bin:$PATH' >> ~/.bashrc +. ~/.bashrc +mkdir ~/local +mkdir ~/node-latest-install +cd ~/node-latest-install +curl http://nodejs.org/dist/node-latest.tar.gz | tar xz --strip-components=1 +./configure --prefix=~/local +make install # ok, fine, this step probably takes more than 30 seconds... +curl https://npmjs.org/install.sh | sh diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/webappUDP.js b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/webappUDP.js new file mode 100644 index 00000000..30baf361 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/webappUDP.js @@ -0,0 +1,304 @@ +/* + + node.js interface to ORTD using UDP datagrams and the packet framework. + A web-interface is provided at e.g. http://localhost:8091/mainAuto.html, + however currently not passwort protected. + The web-interface(s) are defined in html/* + + You can adapt the html files to your need and also created new ones + to e.g. set-up an individual interface to your application + + Rev 2 + +*/ + +// http-server config +var HTTPPORT = 8091; + +// UDP config +var PORT = 20000; +var HOST = '127.0.0.1'; +var ORTD_HOST = '127.0.0.1'; // the IP and port of the ORTD simulator running UDPio.sce +var ORTD_PORT = 20001; + +var NValues = 7; // must be the same as NValues_send defined in UDPio.sce when calling UDPSend +var DataBufferSize = 20000; // Number of elementes stored in the ringbuffer + + + +// +// Includes +// + +http = require('http'); +url = require("url"), +path = require("path"), +fs = require("fs") + +var fs = require('fs'); +var dgram = require('dgram'); + + +// +// Load configuration +// + +var ProtocollConfig = require('../ProtocollConfig.json'); +console.log(ProtocollConfig); + +// +// RingBuffer +// + +var RingBuffer = new RingBuffer(DataBufferSize, NValues); +console.log("RingBuffer created"); + + + + // console.log(P.length); + +function GetParameterID(ParametersConfig, ParameterName) +{ + var P = ParametersConfig; + for (key in P) { + if (P[key].ParameterName == ParameterName) { + return key; + } + } + return -1; +} + +console.log( GetParameterID(ProtocollConfig.ParametersConfig, "Parameter2") ); + +// +// http-server +// + +// got from stackoverflow: +// http://stackoverflow.com/questions/6084360/node-js-as-a-simple-web-server +var httpserver = http.createServer(function(request, response) { + + var uri = url.parse(request.url).pathname + , filename = path.join(process.cwd(), 'html', uri); + + path.exists(filename, function(exists) { + if(!exists) { + response.writeHead(404, {"Content-Type": "text/plain"}); + response.write("404 Not Found\n"); + response.end(); + return; + } + + if (fs.statSync(filename).isDirectory()) filename += '/index.html'; + + fs.readFile(filename, "binary", function(err, file) { + if(err) { + response.writeHead(500, {"Content-Type": "text/plain"}); + response.write(err + "\n"); + response.end(); + return; + } + + response.writeHead(200); + response.write(file, "binary"); + response.end(); + }); + }); +}).listen(HTTPPORT); + +// set-up socket.io +var io = require('socket.io').listen(httpserver); +io.set('log level', 1); // reduce logging + + + + +// +// UDP interface +// +var server = dgram.createSocket('udp4'); +server.on('listening', function () { + var address = server.address(); + console.log('UDP Server listening on ' + address.address + ":" + address.port); +}); + +// Buffer for sending UDP packets +var UDPSendPacketBuffer = new Buffer(2000); // size is propably bigger than every UDP-Packet + + +server.on('message', function (message, remote) { + // received new packet from ORTD via UDP + //console.log(remote.address + ':' + remote.port); + + + var i; + + try { + // disassemble header + var SenderID = message.readInt32LE( 0 ); + var PacketCounter = message.readInt32LE( 4 ); + var SourceID = message.readInt32LE( 8 ); + + // check wheter the sender ID is correct + if (SenderID != 1295793) + throw 1; + + // get popoerties of this packet source + SourceProperties = ProtocollConfig.SourcesConfig[SourceID] +// console.log( 'SourceProperties: '); +// console.log( SourceProperties ); + + if (SourceProperties.datatype != 257) // ORTD.DATATYPE_FLOAT + throw 2; + + + + // check if the recved packet has the correct size + if ( message.length != 12+8*SourceProperties.NValues_send) + throw 2; + +// console.log('Disasm data '+ SourceProperties.NValues_send); + + // disassemble data-values + var ValuesBuffer = message.slice(12, 12+8*SourceProperties.NValues_send); + var Values = new Array(SourceProperties.NValues_send); + + for (i=0; i= this.DataBufferSize) { + this.WriteIndex = 0; + } + } + +// this.ReturnBuffer=ReturnBuffer; +// function ReturnBuffer() { +// return DataBuffer; +// } +} + diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py new file mode 100644 index 00000000..22acb2c1 --- /dev/null +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -0,0 +1,286 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version.b + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Christian Klauer +Stefan Ruppin + + + + +IDEAS +----- + +- The ProtocolConfig.json may contain information about how to place indivual elements in the GUI +- How to handle multiple instances and dynamically created datasources? +- Show a separated screen/page in the gui for each datasource; something like tabs? +- initial Configuration and later updates via UDP + + + + +""" + +__author__ = 'CK' + +from papi.plugin.base_classes.iop_base import iop_base + +from papi.data.DPlugin import DBlock +from papi.data.DSignal import DSignal +from papi.data.DParameter import DParameter + +import numpy as np + +import threading +import pickle + +import os + +import socket + +import struct +import json + + +class OptionalObject(object): + def __init__(self, ORTD_par_id, nvalues): + self.ORTD_par_id = ORTD_par_id + self.nvalues = nvalues + + +class ORTD_UDP(iop_base): + def get_plugin_configuration(self): + config = { + 'address': { + 'value': '127.0.0.1', + 'advanced': '1' + }, + 'source_port': { + 'value': '20000', + 'advanced': '1' + }, + 'out_port': { + 'value': '20001', + 'advanced': '1' + }, + 'Cfg_Path': { + 'value': 'papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json', + 'type': 'file', + 'advanced': '0' + }, + 'SeparateSignals': { + 'value': '0', + 'advanced': '1' + } + } + + return config + + def start_init(self, config=None): + print('ORTD', self.__id__, ':process id', os.getpid()) + + # open UDP + self.HOST = config['address']['value'] + self.SOURCE_PORT = int(config['source_port']['value']) + self.OUT_PORT = int(config['out_port']['value']) + self.separate = int(config['SeparateSignals']['value']) + + # SOCK_DGRAM is the socket type to use for UDP sockets + self.sock_parameter = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock_parameter.setblocking(1) + + # Load protocol config. + path = config['Cfg_Path']['value'] + f = open(path, 'r') + self.ProtocolConfig = json.load(f) + + self.Sources = self.ProtocolConfig['SourcesConfig'] + self.Parameters = self.ProtocolConfig['ParametersConfig'] + + + + # For each group:: loop through all sources (=signals) in the group and register the signals + # Register signals + + + if self.separate == 1: + + self.blocks = {} + + # sort hash keys for usage in right order! + keys = list(self.Sources.keys()) + #keys.sort() + for key in keys: + Source = self.Sources[key] + block = DBlock('SourceGroup' + str(key)) + block.add_signal(DSignal(Source['SourceName'])) + self.blocks[int(key)] = block + + + self.send_new_block_list(list(self.blocks.values())) + + else: + self.block1 = DBlock('SourceGroup0') + + keys = list(self.Sources.keys()) + #keys.sort() + for key in keys: + Source = self.Sources[key] + sig_name = Source['SourceName'] + self.block1.add_signal(DSignal(sig_name)) + + + + #self.block1 = DBlock(None, 1, 2, 'SourceGroup0', names) + self.send_new_block_list([self.block1]) + + + + # Register parameters + self.Parameter_List = [] + + for Pid in self.Parameters: + Para = self.Parameters[Pid] + para_name = Para['ParameterName'] + val_count = Para['NValues'] + opt_object = OptionalObject(Pid, val_count) + Parameter = DParameter('', para_name, 0, 0, OptionalObject=opt_object) + self.Parameter_List.append(Parameter) + + self.send_new_parameter_list(self.Parameter_List) + + self.t = 0 + + self.set_event_trigger_mode(True) + + self.sock_recv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock_recv.bind((self.HOST, self.SOURCE_PORT)) + self.sock_recv.settimeout(1) + + self.thread_goOn = True + self.lock = threading.Lock() + # self.thread = threading.Thread(target=self.thread_execute, args=(self.HOST, self.SOURCE_PORT) ) + self.thread = threading.Thread(target=self.thread_execute) + self.thread.start() + + return True + + def pause(self): + self.lock.acquire() + self.thread_goOn = False + self.lock.release() + self.thread.join() + + def resume(self): + self.thread_goOn = True + self.thread = threading.Thread(target=self.thread_execute, args=(self.HOST, self.SOURCE_PORT)) + self.thread.start() + + def thread_execute(self): + goOn = True + + signal_values = {} + while goOn: + try: + rev = self.sock_recv.recv(1600) + except socket.timeout: + # print('timeout') + pass + except socket.error: + print('ORTD got socket error') + else: + # unpack header + SenderId, Counter, SourceId = struct.unpack_from('`sfJsxp>Y4!^jeC_~+BuiW)N}Tg^b5rw57@Uhz6H8K46v{J8G8EiBeFMT9`NV;W zG(BA$Lp+YZJ!{Cwz`(*_Q1fg4DXoLsMI-#mYgSdL32?A5H99B=aIDigSkLg*P1%JR PsENVT)z4*}Q$iB}rkX7~ literal 0 HcmV?d00001 diff --git a/papi/plugin/io/cpu_load/CPU_Load.py b/papi/plugin/io/cpu_load/CPU_Load.py new file mode 100644 index 00000000..0251430e --- /dev/null +++ b/papi/plugin/io/cpu_load/CPU_Load.py @@ -0,0 +1,98 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" + +""" + +from papi.plugin.base_classes.iop_base import iop_base + +from papi.data.DPlugin import DBlock +from papi.data.DParameter import DParameter +from papi.data.DSignal import DSignal + +import numpy +import time +__author__ = 'knuths' + + +class CPU_Load(iop_base): + INTERVAL = 0.1 + def start_init(self, config=None): + self.t = 0 + self.delta_t = 0.01 + self.para_delta_t = DParameter('', 'Delta_t', 0.01, [0,2],1) + + self.send_new_parameter_list([self.para_delta_t]) + + block1 = DBlock('CPUload') + + signal = DSignal('load_in_percent') + block1.add_signal(signal) + + + self.send_new_block_list([block1]) + return True + + def pause(self): + pass + + + def resume(self): + pass + + def execute(self, Data=None, block_name = None): + vec = numpy.zeros((2,1)) + + vec[0,0] = self.t + vec[1,0] = self.getCpuLoad() * 100 + + self.t += self.delta_t + + + self.send_new_data('CPUload', vec[0], {'load_in_percent':vec[1]} ) + + time.sleep(self.delta_t) + + def set_parameter(self, name, value): + pass + + + def quit(self): + print('CPU Load: will quit') + + + def getTimeList(self): + """ + Fetches a list of time units the cpu has spent in various modes + Detailed explanation at http://www.linuxhowtos.org/System/procstat.htm + """ + cpuStats = open("/proc/stat", "r").readline() + columns = cpuStats.replace("cpu", "").split(" ") + return map(int, filter(None, columns)) + + def deltaTime(self, interval): + """ + Returns the difference of the cpu statistics returned by getTimeList + that occurred in the given time delta + """ + timeList1 = self.getTimeList() + time.sleep(interval) + timeList2 = self.getTimeList() + return [(t2-t1) for t1, t2 in zip(timeList1, timeList2)] + + def getCpuLoad(self): + """ + Returns the cpu load as a value from the interval [0.0, 1.0] + """ + dt = list(self.deltaTime(self.INTERVAL)) + idle_time = float(dt[3]) + total_time = sum(dt) + load = 1-(idle_time/total_time) + return load + + def plugin_meta_updated(self): + pass + + def get_plugin_configuration(self): + return {} \ No newline at end of file diff --git a/papi/plugin/io/cpu_load/CPU_Load.yapsy-plugin b/papi/plugin/io/cpu_load/CPU_Load.yapsy-plugin new file mode 100644 index 00000000..401553b8 --- /dev/null +++ b/papi/plugin/io/cpu_load/CPU_Load.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = CPU_Load +Module = CPU_Load + +[Documentation] +Author = S.K. +Version = 0.1 +Website = www +Description = simple sinus io plugin \ No newline at end of file diff --git a/papi/plugin/io/cpu_load/box.png b/papi/plugin/io/cpu_load/box.png new file mode 100644 index 0000000000000000000000000000000000000000..09db4f0ea8f0046aa1f49993c25175044301362e GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WI14-?iy0WWg+Z8+Vb&Z8pdfpR zr>`sfJsxp>X-mP4f#pCU$r9Iy66gHf+|;}h2Ir#G#FEq$h4Rdj3#4hjMsEKCQd@n@}L_. + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.plugin.base_classes.iop_base import iop_base +from papi.data.DPlugin import DBlock +from papi.data.DSignal import DSignal + +import time +import numpy +import os + +import socket +import pickle + + +class Fourier_Rect(iop_base): + max_approx = 20 + amax = 20 + + + def start_init(self, config=None): + + self.t = 0 + self.amax = Fourier_Rect.amax + self.amplitude = 1 + self.max_approx = Fourier_Rect.max_approx + self.freq = 1 + self.vec = numpy.ones(self.amax* ( self.max_approx + 1) ) + + + print(['Fourier: process id: ',os.getpid()] ) + + + self.HOST = config['host']['value'] + self.PORT = int( config['port']['value'] ) + + # SOCK_DGRAM is the socket type to use for UDP sockets + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.setblocking(0) + + + self.block1 = DBlock('Rectangle') + for i in range(1,self.max_approx): + self.block1.add_signal(DSignal('rect'+str(i))) + + self.send_new_block_list([self.block1]) + + return True + + def pause(self): + pass + + def resume(self): + pass + + def execute(self, Data=None, block_name = None): + # amount of elements per vector: self.amax + # amount of vectors : self.max_approx+1 + vec = numpy.zeros( (self.max_approx, (self.amax) )) + +# print("Amax: " + str(self.amax+1)) +# print("Max_Approx" + str(self.max_approx)) + + # As you can see, there is no connect() call; UDP has no connections. + # Instead, data is directly sent to the recipient via sendto(). + self.sock.sendto(b'GET', (self.HOST, self.PORT) ) + + + try: + received = self.sock.recv(60000) + except socket.error: + pass + else: + data = pickle.loads(received) + + vech = {} + t = data[0*self.amax:(0+1)*self.amax] + + for i in range(self.max_approx): + vech['rect'+str(i)] = data[i*self.amax:(i+1)*self.amax] + + self.send_new_data('Rectangle', t, vech) + + time.sleep(0.001*self.amax ) + + def set_parameter(self, name, value): + pass + + def quit(self): + print('Fourier_Rect: will quit') + + def get_plugin_configuration(self): + config = { + 'name': { + 'value': 'Fourier' + }, + 'host': { + 'value': "130.149.155.73", + 'regex': '\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}', + 'advanced' : '1' + }, + 'port': { + 'value': 9999, + 'regex': '\d{1,5}', + 'advanced' : '1' + }} + return config + + def plugin_meta_updated(self): + pass + diff --git a/papi/plugin/io/fourier_rect/Fourier_Rect.yapsy-plugin b/papi/plugin/io/fourier_rect/Fourier_Rect.yapsy-plugin new file mode 100644 index 00000000..e7005500 --- /dev/null +++ b/papi/plugin/io/fourier_rect/Fourier_Rect.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = Fourier_Rect +Module = Fourier_Rect + +[Documentation] +Author = S.K. +Version = 0.1 +Website = www +Description = simple fourier series of a rectangle signal diff --git a/papi/plugin/io/fourier_rect/box.png b/papi/plugin/io/fourier_rect/box.png new file mode 100644 index 0000000000000000000000000000000000000000..a3cef6e209c666096b4bdd670d6244d32f5f503e GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WI14-?iy0WWg+Z8+Vb&Z8pdfpR zr>`sfJsxp>85P@at22Q@k|nMYCC>S|xv6<249-QVi6yBi3gww484B*6z5(HleBwYw z+MX_sAs)xyo;74-FyLTNxSL*V9Z{zGrJ3nT$. + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.plugin.base_classes.iop_base import iop_base +from papi.data.DPlugin import DBlock +from papi.data.DParameter import DParameter +from papi.data.DSignal import DSignal + +import threading + +import time +import numpy +import os + +import socket +import pickle + + +class Fourier_Rect_MOD(iop_base): + max_approx = 20 + amax = 20 + def start_init(self, config=None): + self.t = 0 + self.amax = Fourier_Rect_MOD.amax + self.amplitude = 1 + self.max_approx = Fourier_Rect_MOD.max_approx + self.freq = 1 + self.vec = numpy.ones(self.amax* ( self.max_approx + 1) ) + + print(['Fourier: process id: ',os.getpid()] ) + + + self.HOST = "130.149.155.73" + self.PORT = 9999 + # SOCK_DGRAM is the socket type to use for UDP sockets + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.sock.setblocking(0) + + + self.block1 = DBlock('Rectangle') + for i in range(1,self.max_approx): + self.block1.add_signal(DSignal('rect'+str(i))) + + self.send_new_block_list([self.block1]) + + self.set_event_trigger_mode(True) + + thread = threading.Thread(target=self.thread_execute, args=(self.HOST,self.PORT) ) + thread.start() + + return True + + def pause(self): + pass + + def resume(self): + pass + + def thread_execute(self,host,port): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + #self.sock.setblocking(0) + vec = numpy.zeros( (self.max_approx, (self.amax) )) + + while True: + self.sock.sendto(b'GET', (self.HOST, self.PORT) ) + + try: + received = self.sock.recv(60000) + except socket.error: + pass + else: + data = pickle.loads(received) + + vech = {} + t = data[0*self.amax:(0+1)*self.amax] + + for i in range(self.max_approx): + vech['rect'+str(i)] = data[i*self.amax:(i+1)*self.amax] + + self.send_new_data('Rectangle', t, vech) + + time.sleep(0.001*self.amax ) + + + def execute(self, Data=None, block_name = None): + print("EXECUTE FUNC") + pass + + def set_parameter(self, name, value): + pass + + def quit(self): + print('Fourier_Rect: will quit') + + def plugin_meta_updated(self): + pass + + def get_plugin_configuration(self): + return {} \ No newline at end of file diff --git a/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.yapsy-plugin b/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.yapsy-plugin new file mode 100644 index 00000000..f90d0740 --- /dev/null +++ b/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = Fourier_Rect_MOD +Module = Fourier_Rect_MOD + +[Documentation] +Author = S.K. +Version = 0.1 +Website = www +Description = simple fourier series of a rectangle signal diff --git a/papi/plugin/io/fourier_rect_mod/box.png b/papi/plugin/io/fourier_rect_mod/box.png new file mode 100644 index 0000000000000000000000000000000000000000..a3cef6e209c666096b4bdd670d6244d32f5f503e GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WI14-?iy0WWg+Z8+Vb&Z8pdfpR zr>`sfJsxp>85P@at22Q@k|nMYCC>S|xv6<249-QVi6yBi3gww484B*6z5(HleBwYw z+MX_sAs)xyo;74-FyLTNxSL*V9Z{zGrJ3nT$. + +Contributors: +Stefan Ruppin +""" + +from papi.data.DPlugin import DBlock +from papi.data.DSignal import DSignal + +from papi.data.DParameter import DParameter +from papi.plugin.base_classes.iop_base import iop_base + +import time +import math +import numpy + + +class Sinus(iop_base): + + def start_init(self, config=None): + self.t = 0 + self.amax = int(config['amax']['value']) + self.f = float(config['f']['value']) + + + self.block1 = DBlock('SinMit_f1') + signal = DSignal('f1_1') + signal.dname = 'f1_f1DNAME' + self.block1.add_signal(signal) + + self.block2 = DBlock('SinMit_f2') + signal = DSignal('f2_1') + self.block2.add_signal(signal) + + self.block3 = DBlock('SinMit_f3') + signal = DSignal('f3_1') + self.block3.add_signal(signal) + signal = DSignal('f3_2') + self.block3.add_signal(signal) + signal = DSignal('f3_scalar') + self.block3.add_signal(signal) + + + #self.block4 = self.create_new_block('Sin4', ['t','f3_1','f3_2', 'Scalar'], [ 'numpy_vec', 'numpy_vec', 'numpy_vec', 'int'], 100 ) + + blockList = [self.block1, self.block2, self.block3] + self.send_new_block_list(blockList) + + self.para3 = DParameter(None,'Frequenz Block SinMit_f3', 0.6, [0,1],1, Regex='[0-9]+.[0-9]+') + self.para3.id = 1 + para_l = [self.para3] + + self.send_new_parameter_list(para_l) + + print('Sinus started working') + + return True + + def pause(self): + print('Sinus pause') + pass + + def resume(self): + print('Sinus resume') + pass + + def execute(self, Data=None, block_name = None): + vec = numpy.zeros( (2,self.amax)) + vec2 = numpy.zeros((2,self.amax)) + vec3 = numpy.zeros((3,self.amax)) + for i in range(self.amax): + vec[0, i] = self.t + vec[1, i] = math.sin(2*math.pi*0.8*self.t) + vec2[0, i] = self.t + vec2[1, i] = math.sin(2*math.pi*0.5*self.t) + vec3[0, i] = self.t + vec3[1, i] = math.sin(2*math.pi*self.para3.value*self.t) + vec3[2, i] = math.sin(2*math.pi*0.1*self.t) + self.t += 0.005 + + self.send_new_data('SinMit_f1' , vec[0] , {'f1_1': vec[1] } ) + self.send_new_data('SinMit_f2', vec2[0], {'f2_1': vec2[1]} ) + self.send_new_data('SinMit_f3', vec3[0], {'f3_1': vec3[1], 'f3_2': vec3[2], 'f3_scalar': [10,10,10] } ) + + time.sleep(self.amax*0.005) + + def set_parameter(self, name, value): + if name == self.para3.name: + self.para3.value = float(value) + + + def get_plugin_configuration(self): + config = { + "amax": { + 'value': 3, + 'regex': '[0-9]+' + }, 'f': { + 'value': "1", + 'regex': '\d+.{0,1}\d*' + }} + return config + + def quit(self): + print('Sinus: will quit') + + def plugin_meta_updated(self): + pass diff --git a/papi/plugin/io/sinus/Sinus.yapsy-plugin b/papi/plugin/io/sinus/Sinus.yapsy-plugin new file mode 100644 index 00000000..a65096ca --- /dev/null +++ b/papi/plugin/io/sinus/Sinus.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = Sinus +Module = Sinus + +[Documentation] +Author = S.K. +Version = 0.1 +Website = www +Description = simple sinus io plugin \ No newline at end of file diff --git a/papi/plugin/io/sinus/box.png b/papi/plugin/io/sinus/box.png new file mode 100644 index 0000000000000000000000000000000000000000..b61529159774d34551c73f69c7bdd50d76aa415e GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WI14-?iy0WWg+Z8+Vb&Z8pdfpR zr>`sfJsxp>837rm_&Y!$$r9Iy66gHf+|;}h2Ir#G#FEq$h4Rdj3#4hjMsEKCQd@w2RDG%B?4 Ras_H*@O1TaS?83{1ORc}EEE6$ literal 0 HcmV?d00001 diff --git a/papi/plugin/pcp/button/Button.py b/papi/plugin/pcp/button/Button.py new file mode 100644 index 00000000..7cb91ad5 --- /dev/null +++ b/papi/plugin/pcp/button/Button.py @@ -0,0 +1,92 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.plugin.base_classes.pcp_base import pcp_base +from PySide.QtGui import QPushButton +from papi.data.DPlugin import DBlock +from papi.data.DSignal import DSignal + +class Button(pcp_base): + + def initiate_layer_0(self, config): + + #super(Button, self).start_init(config) + + block = DBlock('Click_Event') + signal = DSignal('Parameter') + block.add_signal(signal) + self.send_new_block_list([block]) + + self.cur_value = 0 + + self.set_widget_for_internal_usage(self.create_widget()) + + self.name = config['name']['value'] + + def create_widget(self): + """ + + :return: + :rtype QWidget: + """ + button = QPushButton('Click') + button.clicked.connect(self.clicked) + + return button + + def clicked(self): + + if self.cur_value == float(self.config['up']['value']): + self.cur_value = float(self.config['low']['value']) + else: + self.cur_value = float(self.config['up']['value']) + + self.send_parameter_change(str(self.cur_value), 'Click_Event', 'TESTALIAS') + + + def get_plugin_configuration(self): + config = { + "low": { + 'value':0.1, + 'advanced' : '0' + }, "up": { + 'value': 1, + 'advanced' : '0' + },"name": { + 'value' : "Button", + 'advanced' : '0' + }} + return config + + def plugin_meta_updated(self): + pass + + def quit(self): + pass \ No newline at end of file diff --git a/papi/plugin/pcp/button/Button.yapsy-plugin b/papi/plugin/pcp/button/Button.yapsy-plugin new file mode 100644 index 00000000..8b8f45f0 --- /dev/null +++ b/papi/plugin/pcp/button/Button.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = Button +Module = Button + +[Documentation] +Author = S.K. +Version = 0.1 +Website = www +Description = simple button for parameters \ No newline at end of file diff --git a/papi/plugin/pcp/button/icon.png b/papi/plugin/pcp/button/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f63caf777c5431133feafb70591886142e41e20a GIT binary patch literal 2554 zcmVX0ssI2u-xbC00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru-U<#54F(w8P9FdO35Q8U zK~!ko?OADbRn-;#&b@C)UM6@6!I1Zo0Er|-upkLY5LAk-q5{@37i-#L)z#9f1qV=9 zaH0YZAaQ84h*CjTL1kFFunH`2F)j%SnlK3%#=N{tCU3fX_s6;SzWb7(borw{;#p_C zb?<)r>~qg|zCGM?MRQXlGy5AFnwy$5EyB$Bsa6rFzH&O9?$k7;p~08w$+ns>2Y0sE-I^p|2ZotYnreOecpB>K0Vtth z(3Oy2rJ>X#BDORs={`6Tk-1(g0##P{BW80z2SCk{);YSBr<*ufg=WyS z>T5&*6gB`7026^37CmXw>;kw%57%v3{%rX!KjG3kdPmE_l?%(>t_}R4=KrxWqr)$=1 z+F5z3DFTt0Q84W1)9#+oFFJ~qgmjN5k`@O>*QtwPZhn_7aJc-Tr#?9oknRzmnmus% zEz_or?VljN$2TM2ZeIM<=S7Rk^O79W`5JaED|@^56K@T4$N$(|6oo|wLjxZO0cHrH zqbVtB1_q87HDE^B;?83&2BX{F zbqZ)Ze=d-I%Y%O^>8Z6hovYcg{{0n?p0Tf(KgO+Gdd-)(_`ZoQJBYFcTB}4zdK#Xt zXQ3|0Vv!r0!;QONU$(8KuypBy@g7M4jJ)A?0BDDFTxM|a@Uq$Sk4}1J>BI~b(EMNi z`;CnVnU)Y|OfBmm#-!(QTUI5{`Eib?LVdbd{tzkgz(SP)m z`~NU5J5F3OJ?Z!`LwY*1#nU_Op>>Ca+zR&Z|7dR@b^NSx9($(^=UCq9*Bq#QcR|?) zkrC79%)Bx=;%`dmZRd#LoAo=Em96%b{B6y zUI0Mi$T{=x%yvLH-KlCj0JU)T!r$e`0rr$WHbm+_d;YOkYx_-@{pgUCrUUCM${+RF zUV8ZYG?f~URvrruE}b{lqqd&dzIxSb&$#-&T$<NjNQ;0zUm3lfj*U$}4I*_pXn_D;++ zfig--Hj}v#H!W3q2SAyP)I`P#x+pt|&A$XmA0VQ@*{YL~l%hdts$~cf0l~e?Ux4en z$L<@Gu68m<%)k{B9{KAiw?e_@bG5rRtX?s9iL&Iu(Wz16By>$?CL%;1&V-m_1l;mX z^+k9m=en4P0VL!O^I%`~sa7p3F2=0dGpi>K`C21HY7CSd8w*)c42bm#iS_zka+HXO z(pID2Mz zydH0M|H9!nm(H2uY1_Z$OTR`8A)tjK7EQ2embrtKSP4g8bF8DeqcRIc0co0CxLGb* z7QohDW+t}On-Vb6B$gTzk*1lQNn_F&DL=b$fO_%cHM=fEbb!q+d0@`36ULsF*GFlr zt~?)#RR(P7C9}&O7=!IEKD+Kno7T}9j8)1WX=@1qHdQc*gtP?sj$I7sf9WJ4f*R*Q zs3j0FM!^PgqTpLu`LOOaoVxzeq+`Pwpyi(N9dAZf^N=1w~^|6ebx%6qzW zP@=HBrZLb~S5sYeo`bCob=BK9{^x@0nkfY?(nRLSAxYalef6Kc?<~rU4}WvI4PZ^v zG2sm<^Z9ZpNV&(_l;{YK5W z>7nO(ZCST*+op=`?F=}2dJBg44Uxlp)8tXrZ*O?#i=u_UU^C{zRWNhG14*maeDaT_ z>lx18#di(7raron-Fw%;c@?j%9lq%9LbF5?@x9U=Ra?uS-3kEqZbd$-rAjnzWp)#00WUMM zgfdz^f?4OLSikPQ@wZP3g+ffEnT}%tkeG#D26V$iIav&G%;}`%L|s)>MG@5O5p&jL zEs{m|qo!OnfsOYU5<($nMF2v+F`dl3kt7tyHjJV;HR&_Iuj)n1}mc|`~3}H zRUZn6!`GHvCxj5qO^wWS^hoW=6UW=z+kb-Zf8uXke0)yde*JUvgirwd7nhshyMs{= QEC2ui07*qoM6N<$f__`)bpQYW literal 0 HcmV?d00001 diff --git a/papi/plugin/pcp/slider/Slider.py b/papi/plugin/pcp/slider/Slider.py new file mode 100644 index 00000000..9de79faa --- /dev/null +++ b/papi/plugin/pcp/slider/Slider.py @@ -0,0 +1,91 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.plugin.base_classes.pcp_base import pcp_base +from PySide.QtGui import QSlider, QHBoxLayout, QWidget, QLineEdit +from PySide import QtCore +from papi.data.DPlugin import DBlock +from papi.data.DSignal import DSignal + +class Slider(pcp_base): + + def initiate_layer_0(self, config): + + block = DBlock('Parameter_1') + signal = DSignal('Parameter') + block.add_signal(signal) + + self.send_new_block_list([block]) + self.set_widget_for_internal_usage(self.create_widget()) + + def create_widget(self): + self.central_widget = QWidget() + + self.slider = QSlider() + self.slider.sliderPressed.connect(self.clicked) + self.slider.valueChanged.connect(self.value_changed) + + self.slider.setMinimum(0) + self.slider.setMaximum(100) + self.slider.setSingleStep(1) + + self.slider.setOrientation(QtCore.Qt.Horizontal) + + self.text_field = QLineEdit() + self.text_field.setReadOnly(True) + self.text_field.text = self.config['default']['value'] + self.layout = QHBoxLayout(self.central_widget) + + self.layout.addWidget(self.slider) + self.layout.addWidget(self.text_field) + + return self.central_widget + + def value_changed(self, change): + cur_value = change/100 + self.text_field.setText(str(cur_value)) + self.send_parameter_change(cur_value, 'Parameter_1', 'TESTALIAS') + + def clicked(self): + pass + + def plugin_meta_updated(self): + pass + + def get_plugin_configuration(self): + config = { + 'default': { + 'value': '1' + } + } + return config + + def quit(self): + pass \ No newline at end of file diff --git a/papi/plugin/pcp/slider/Slider.yapsy-plugin b/papi/plugin/pcp/slider/Slider.yapsy-plugin new file mode 100644 index 00000000..ff2ff09e --- /dev/null +++ b/papi/plugin/pcp/slider/Slider.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = Slider +Module = Slider + +[Documentation] +Author = S.K. +Version = 0.1 +Website = www +Description = simple slider for parameters \ No newline at end of file diff --git a/papi/plugin/templates/IOP_DPP_template.py b/papi/plugin/templates/IOP_DPP_template.py new file mode 100644 index 00000000..a8222999 --- /dev/null +++ b/papi/plugin/templates/IOP_DPP_template.py @@ -0,0 +1,151 @@ +#!/usr/bin/python3 +#-*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +. + +Contributors: +. + +Contributors: + 1 or self.__signals_have_same_length: + if current_milli_time() - self.__last_time__ > self.__update_intervall__: + self.__last_time__ = current_milli_time() + self.update_plot() + self.__last_time__ = current_milli_time() + self.__new_added_data__ = 0 + else: + self.update_plot_single_timestamp(Data) + + def set_parameter(self, name, value): + """ + Function set parameters + + :param name: + :param value: + :return: + """ + if name == 'x-grid': + self.config['x-grid']['value'] = value + self.__plotWidget__.showGrid(x=value == '1') + + if name == 'y-grid': + + self.config['y-grid']['value'] = value + self.__plotWidget__.showGrid(y=value == '1') + + if name == 'downsampling_rate': + self.config['downsampling_rate']['value'] = value + self.__downsampling_rate__ = int(value) + self.__new_added_data__ = 0 + + if name == 'rolling': + self.__rolling_plot__ = int(float(value)) == int('1') + self.config['rolling_plot']['value'] = value + + if self.__rolling_plot__: + # if self.__vertical_line__ not in self.__plotWidget__.listDataItems(): + + self.__plotWidget__.addItem(self.__vertical_line__) + + if name == 'color': + self.config['color']['value'] = value + int_re = re.compile(r'(\d+)') + self.__colors_selected__ = int_re.findall(self.config['color']['value']) + self.update_pens() + + if name == 'style': + self.config['style']['value'] = value + int_re = re.compile(r'(\d+)') + self.__styles_selected__ = int_re.findall(self.config['style']['value']) + self.update_pens() + + if name == 'buffersize': + self.config['buffersize']['value'] = value + self.set_buffer_size(value) + + def update_pens(self): + """ + Function update pens + + :return: + """ + + for signal_name in self.signals.keys(): + signal_id = self.signals[signal_name]['id'] + + new_pen = self.get_pen(signal_id) + + self.signals[signal_name]['curve'].setPen(new_pen) + + def update_plot(self): + """ + Function update_plot + + :return: + """ + shift_data = 0 + + + for last_tvalue in self.__tdata_old__: + if last_tvalue in self.__tbuffer__: + shift_data = list(self.__tbuffer__).index(last_tvalue) + break + + tdata = list(self.__tbuffer__)[shift_data::self.__downsampling_rate__] + + if self.__rolling_plot__: + self.__append_at__ += self.__new_added_data__ / self.__downsampling_rate__ + self.__append_at__ %= len(tdata) + + # -------------------------- + # iterate over all buffers + # -------------------------- + for signal_name in self.signals: + data = list(self.signals[signal_name]['buffer'])[shift_data::self.__downsampling_rate__] + + if self.__rolling_plot__: + data = np.roll(data, int(self.__append_at__)) + self.__vertical_line__.setValue(tdata[int(self.__append_at__)-1]) + else: + self.__vertical_line__.setValue(tdata[0]) + + curve = self.signals[signal_name]['curve'] + curve.setData(tdata, data, _callSync='off') + + self.__tdata_old__ = tdata + + def update_plot_single_timestamp(self, data): + """ + Function update_plot_single_timestamp + + :return: + """ + + self.__text_item__.setText("Time " + str(data['t'][0]), color=(200, 200, 200)) + + for signal_name in data: + if signal_name != 't': + signal_data = data[signal_name] + if signal_name in self.signals: + tdata = np.linspace(1, len(signal_data), len(signal_data)) + + curve = self.signals[signal_name]['curve'] + curve.setData(tdata, signal_data, _callSync='off') + + pass + + def quit(self): + """ + Function quit plugin + + :return: + """ + print('PlotPerformance: will quit') + + def get_plugin_configuration(self): + """ + Function get plugin configuration + + :return {}: + """ + config = { + 'label_y': { + 'value': "amplitude, V", + 'regex': '\w+,\s+\w+', + 'display_text': 'Label-Y' + }, 'label_x': { + 'value': "time, s", + 'regex': '\w+,\s*\w+', + 'display_text': 'Label-X' + }, 'x-grid': { + 'value': "0", + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'Grid-X' + }, 'y-grid': { + 'value': "0", + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'Grid-Y' + }, 'color': { + 'value': "[0 1 2 3 4]", + 'regex': '^\[(\s*\d\s*)+\]', + 'advanced': '1', + 'display_text': 'Color' + }, 'style': { + 'value': "[0 0 0 0 0]", + 'regex': '^\[(\s*\d\s*)+\]', + 'advanced': '1', + 'display_text': 'Style' + }, 'buffersize': { + 'value': "1000", + 'regex': '^([1-9][0-9]{0,3}|10000)$', + 'advanced': '1', + 'display_text': 'Buffersize' + }, 'downsampling_rate': { + 'value': "1", + 'regex': '(\d+)' + }, 'rolling_plot': { + 'value': '0', + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'Rolling Plot' + } + } + # http://www.regexr.com/ + return config + + def set_buffer_size(self, new_size): + """ + Function set buffer size + + :param new_size: + :return: + """ + + self.__buffer_size__ = int(new_size) + + # ------------------------------- + # Change Time Buffer + # ------------------------------- + + self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) + + # ------------------------------- + # Change Buffer of current + # plotted signals + # ------------------------------- + + start_size = len(self.__tbuffer__) + + for signal_name in self.signals: + buffer_new = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION + + buffer_old = self.signals[signal_name]['buffer'] + #buffer_new.extend(buffer_old) + + self.signals[signal_name]['buffer'] = buffer_new + + self.__new_added_data__ = 0 + + def plugin_meta_updated(self): + """ + By this function the plot is able to handle more than one input for plotting. + + :return: + """ + + subscriptions = self.dplugin_info.get_subscribtions() + + current_signals = [] + + for dpluginsub_id in subscriptions: + for dblock_name in subscriptions[dpluginsub_id]: + + # get subscription for dblock + subscription = subscriptions[dpluginsub_id][dblock_name] + + for signal in subscription.get_signals(): + current_signals.append(signal) + + # Add missing buffers + for signal_name in current_signals: + if signal_name not in self.signals: + self.add_databuffer(signal_name, current_signals.index(signal_name)) + + # Delete old buffers + for signal_name in self.signals.copy(): + if signal_name not in current_signals: + self.remove_databuffer(signal_name) + + def add_databuffer(self, signal_name, signal_id): + """ + Create new buffer for signal_name. + + :param signal_name: + :param signal_id: + :return: + """ + + if signal_name not in self.signals: + self.signals[signal_name] = {} + + start_size = len(self.__tbuffer__) + + buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION + + legend_name = str(signal_id) + "# " + signal_name + curve = self.__plotWidget__.plot([0, 1], [0, 1], name=legend_name, clipToView=True) + + self.signals[signal_name]['buffer'] = buffer + self.signals[signal_name]['curve'] = curve + self.signals[signal_name]['id'] = signal_id + + self.__legend__.addItem(curve, legend_name) + + self.update_pens() + + def remove_databuffer(self, signal_name): + """ + Remove the databuffer for signal_name. + + :param signal_name: + :return: + """ + + if signal_name in self.signals: + curve = self.signals[signal_name]['curve'] + curve.clear() + self.__legend__.removeItem(signal_name) + del self.signals[signal_name] + + def get_pen(self, index): + """ + Function get pen + + :param index: + :return: + """ + index = int(index) + + style_index = index % len(self.__styles_selected__) + style_code = int(self.__styles_selected__[style_index]) + + color_index = index % len(self.__colors_selected__) + color_code = int(self.__colors_selected__[color_index]) + + if style_code in self.styles: + style = self.styles[style_code] + else: + style = self.styles[1] + + if color_code in self.colors: + color = self.colors[color_code] + else: + color = self.colors[1] + + return pq.mkPen(color=color, style=style) diff --git a/papi/plugin/visual/Plot/Plot.yapsy-plugin b/papi/plugin/visual/Plot/Plot.yapsy-plugin new file mode 100644 index 00000000..a1f8e24d --- /dev/null +++ b/papi/plugin/visual/Plot/Plot.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = Plot +Module = Plot + +[Documentation] +Author = S.R., S.K. +Version = 0.7 +Website = +Description = Basic plotting plugin. Supports more than one signals which must have the same time vector. \ No newline at end of file diff --git a/papi/plugin/visual/WizardExample/WizardExample.py b/papi/plugin/visual/WizardExample/WizardExample.py new file mode 100644 index 00000000..41116c94 --- /dev/null +++ b/papi/plugin/visual/WizardExample/WizardExample.py @@ -0,0 +1,265 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +. + +Contributors: +Stefan Ruppin +""" + + +import unittest +from papi.core import Core +from papi.PapiEvent import PapiEvent +from papi.data.DCore import DPlugin,DCore +from multiprocessing import Process, Array, Lock, Queue +import time +from papi.main import main + +class TestCore(unittest.TestCase): + + def setUp(self): + self.core = Core() + + + def test_process_event_alive(self): + self.core.__debugLevel__ = 0 + event = PapiEvent(1,2,'status_event','alive','') + self.core.__process_event__(event) + assert self.core.__debug_var__ == 'alive' + + def test_process_event_check_alive(self): + self.core.__debugLevel__ = 0 + event = PapiEvent(1,2,'status_event','check_alive_status','') + self.core.__process_event__(event) + assert self.core.__debug_var__ == 'check_alive_status' + + def test_process_event_start_successfull(self): + self.core.__debugLevel__ = 0 + print('Test start successfull -----------------------') + event = PapiEvent(1,2,'status_event','start_successfull','') + self.core.__process_event__(event) + assert self.core.__debug_var__ == 'start_successfull' + + def test_process_event_join_request(self): + print('Test join request -----------------------') + event = PapiEvent(1,0,'instr_event','create_plugin','TestPL1') + self.core.plugin_manager.setPluginPlaces(["plugin","../plugin"]) + self.core.plugin_manager.collectPlugins() + self.core.__process_event__(event) + + pl = self.core.core_data.dbg_get_first_dplugin() + + self.assert_(pl.process.is_alive()) + event = PapiEvent(id,0,'instr_event','stop','') + pl.queue.put(event) + time.sleep(1) + assert pl.process.is_alive() == False + + + def test_process_event_start_failed(self): + self.core.__debugLevel__ = 0 + event = PapiEvent(1,2,'status_event','start_failed','') + self.core.__process_event__(event) + assert self.core.__debug_var__ == 'start_failed' + + def test_process_event_new_data(self): + self.core.__debugLevel__ = 0 + event = PapiEvent(1,2,'data_event','new_data','') + self.core.__process_event__(event) + assert self.core.__debug_var__ == 'new_data' + + def test_process_event_get_output_size(self): + self.core.__debugLevel__ = 0 + event = PapiEvent(1,2,'data_event','get_output_size','') + self.core.__process_event__(event) + assert self.core.__debug_var__ == 'get_output_size' + + def test_process_event_response_output_size(self): + self.core.__debugLevel__ = 0 + event = PapiEvent(1,2,'data_event','response_output_size','') + self.core.__process_event__(event) + assert self.core.__debug_var__ == 'response_output_size' + + def test_process_event_create_plugin(self): + self.core.__debugLevel__ = 0 + return True + event = PapiEvent(1,2,'instr_event','create_plugin','TestPL1') + self.core.plugin_manager.setPluginPlaces(["plugin","../plugin"]) + self.core.plugin_manager.collectPlugins() + self.core.__process_event__(event) + assert self.core.__debug_var__ == 'create_plugin' + + def test_process_event_stop_plugin(self): + self.core.__debugLevel__ = 0 + event = PapiEvent(1,2,'instr_event','stop_plugin','') + self.core.__process_event__(event) + assert self.core.__debug_var__ == 'stop_plugin' + + + + +if __name__ == "__main__": + unittest.main(); + diff --git a/papi/tests/TestCoreMock.py b/papi/tests/TestCoreMock.py new file mode 100644 index 00000000..45febd35 --- /dev/null +++ b/papi/tests/TestCoreMock.py @@ -0,0 +1,442 @@ +__author__ = 'stefan' + +import unittest +from threading import Thread +import time + +from papi.data.DGui import DGui +from papi.core import Core +from papi.gui.qt_new.main import startGUI_TESTMOCK + +from papi.gui.gui_api import Gui_api +from papi.gui.gui_event_processing import GuiEventProcessing +from papi.PapiEvent import PapiEvent +from threading import Thread +from multiprocessing import Queue +import papi.constants as const +from pyqtgraph import QtCore +import time + + +class dummyProcess(object): + def __init__(self): + self.pid = 10 + def join(self): + pass + + +class TestCoreMock(unittest.TestCase): + + DELAY_TIME = 0.5 + + def test_create_plugin_sub(self): + # create a Plot and Sinus plugin + self.gui_api.do_create_plugin('PlotPerformance','Plot1') + self.gui_api.do_create_plugin('Sinus','Sin1') + + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertIsNotNone(self.core_data.get_dplugin_by_uname('Plot1')) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname('Plot1')) + self.assertIsNotNone(self.core_data.get_dplugin_by_uname('Sin1')) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname('Sin1')) + + self.gui_api.do_subscribe_uname('Plot1','Sin1','SinMit_f3',[1]) + + time.sleep(TestCoreMock.DELAY_TIME) + + subs = self.gui_data.get_dplugin_by_uname('Plot1').get_subscribtions() + for id in subs: + dsub = self.gui_data.get_dplugin_by_id(id) + self.assertEqual('Sin1',dsub.uname) + + def test_do_create_api(self): + + + self.gui_api.do_create_plugin('PlotPerformance','Plot1') + self.gui_api.do_create_plugin('Sinus','Sin1') + + + time.sleep(TestCoreMock.DELAY_TIME) + # + self.assertIsNotNone(self.core_data.get_dplugin_by_uname('Plot1')) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname('Plot1')) + self.assertIsNotNone(self.core_data.get_dplugin_by_uname('Sin1')) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname('Sin1')) + + self.assertEqual(self.core_data.get_dplugins_count(), 2, 'Core PL Count not 2') + self.assertEqual(self.gui_data.get_dplugins_count(), 2, 'Gui PL Count not 2') + + self.assertIsNone(self.core_data.get_dplugin_by_uname('Sinus')) + self.assertIsNone(self.gui_data.get_dplugin_by_uname('Sinus')) + + def test_delete_plugin_no_VIP_PCP(self): + PL_1_NAME = 'Sin1' + PL_1_IDENT = 'Sinus' + self.gui_api.do_create_plugin(PL_1_IDENT,PL_1_NAME) + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(PL_1_NAME), 'No Plugin in CoreData with uname '+PL_1_NAME) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(PL_1_NAME), 'No Plugin in GuiData with uname '+PL_1_NAME) + self.assertEqual(self.core_data.get_dplugins_count(), 1, 'Core PL Count not 1') + self.assertEqual(self.gui_data.get_dplugins_count(), 1, 'Gui PL Count not 1') + + self.gui_api.do_delete_plugin_uname(PL_1_NAME) + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertIsNone(self.core_data.get_dplugin_by_uname(PL_1_NAME), 'Plugin still in core_data') + self.assertIsNone(self.gui_data.get_dplugin_by_uname(PL_1_NAME), 'Plugin still in gui_data') + self.assertEqual(self.core_data.get_dplugins_count(), 0, 'Core PL Count not 0') + self.assertEqual(self.gui_data.get_dplugins_count(), 0, 'Gui PL Count not 0') + + def test_delete_plugin_is_VIP_PCP(self): + PL_1_NAME = 'Plot1' + PL_1_IDENT = 'PlotPerformance' + self.gui_api.do_create_plugin(PL_1_IDENT,PL_1_NAME) + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(PL_1_NAME), 'No Plugin in CoreData with uname '+PL_1_NAME) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(PL_1_NAME), 'No Plugin in GuiData with uname '+PL_1_NAME) + self.assertEqual(self.core_data.get_dplugins_count(), 1, 'Core PL Count not 1') + self.assertEqual(self.gui_data.get_dplugins_count(), 1, 'Gui PL Count not 1') + + self.gui_api.do_delete_plugin_uname(PL_1_NAME) + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertIsNone(self.core_data.get_dplugin_by_uname(PL_1_NAME), 'Plugin still in core_data') + self.assertIsNone(self.gui_data.get_dplugin_by_uname(PL_1_NAME), 'Plugin still in gui_data') + self.assertEqual(self.core_data.get_dplugins_count(), 0, 'Core PL Count not 0') + self.assertEqual(self.gui_data.get_dplugins_count(), 0, 'Gui PL Count not 0') + + def test_reset_papi_1(self): + PL_1_NAME = 'Plot1' + PL_1_IDENT = 'PlotPerformance' + self.gui_api.do_create_plugin(PL_1_IDENT,PL_1_NAME) + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(PL_1_NAME), 'No Plugin in CoreData with uname '+PL_1_NAME) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(PL_1_NAME), 'No Plugin in GuiData with uname '+PL_1_NAME) + self.assertEqual(self.core_data.get_dplugins_count(), 1, 'Core PL Count not 1') + self.assertEqual(self.gui_data.get_dplugins_count(), 1, 'Gui PL Count not 1') + + self.gui_api.do_reset_papi() + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertIsNone(self.core_data.get_dplugin_by_uname(PL_1_NAME), 'Plugin still in core_data') + self.assertIsNone(self.gui_data.get_dplugin_by_uname(PL_1_NAME), 'Plugin still in gui_data') + self.assertEqual(self.core_data.get_dplugins_count(), 0, 'Core PL Count not 0') + self.assertEqual(self.gui_data.get_dplugins_count(), 0, 'Gui PL Count not 0') + + def test_reset_papi_2(self): + + Plugins = [ ['Plot1', 'PlotPerformance'], ['Sinus1', 'Sinus'], ['Add1', 'Add'], ['Butt', 'Button'] ] + + for pl in Plugins: + self.gui_api.do_create_plugin(pl[1], pl[0]) + + time.sleep(TestCoreMock.DELAY_TIME) + + for pl in Plugins: + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(pl[0]), 'No Plugin in CoreData with uname '+pl[0]) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(pl[0]), 'No Plugin in GuiData with uname '+pl[0]) + + + self.assertEqual(self.core_data.get_dplugins_count(), len(Plugins), 'Core PL Count not correct') + self.assertEqual(self.gui_data.get_dplugins_count(), len(Plugins), 'Gui PL Count not correct') + + + self.gui_api.do_reset_papi() + + + time.sleep(TestCoreMock.DELAY_TIME) + + for pl in Plugins: + self.assertIsNone(self.core_data.get_dplugin_by_uname(pl[0]), 'Plugin in CoreData with uname '+pl[0]) + self.assertIsNone(self.gui_data.get_dplugin_by_uname(pl[0]), 'Plugin in GuiData with uname '+pl[0]) + + + self.assertEqual(self.core_data.get_dplugins_count(), 0, 'Core PL Count not correct') + self.assertEqual(self.gui_data.get_dplugins_count(), 0, 'Gui PL Count not correct') + + def test_stopReset_iop(self): + + Plugins = [ ['Plot1', 'PlotPerformance'], ['Sinus1', 'Sinus'], ['Add1', 'Add'], ['Butt', 'Button'] ] + + for pl in Plugins: + self.gui_api.do_create_plugin(pl[1], pl[0]) + + time.sleep(TestCoreMock.DELAY_TIME) + + for pl in Plugins: + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(pl[0]), 'No Plugin in CoreData with uname '+pl[0]) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(pl[0]), 'No Plugin in GuiData with uname '+pl[0]) + + + self.assertEqual(self.core_data.get_dplugins_count(), len(Plugins), 'Core PL Count not correct') + self.assertEqual(self.gui_data.get_dplugins_count(), len(Plugins), 'Gui PL Count not correct') + + + self.gui_api.do_stopReset_plugin_uname('Sinus1') + + time.sleep(TestCoreMock.DELAY_TIME) + + for pl in Plugins: + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(pl[0]), 'No Plugin in CoreData with uname '+pl[0]) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(pl[0]), 'No Plugin in GuiData with uname '+pl[0]) + + def test_stopReset_restart_iop(self): + + Plugins = [ ['Sinus1', 'Sinus'] ] + + for pl in Plugins: + self.gui_api.do_create_plugin(pl[1], pl[0]) + + time.sleep(TestCoreMock.DELAY_TIME) + + for pl in Plugins: + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(pl[0]), 'No Plugin in CoreData with uname '+pl[0]) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(pl[0]), 'No Plugin in GuiData with uname '+pl[0]) + + + self.assertEqual(self.core_data.get_dplugins_count(), len(Plugins), 'Core PL Count not correct') + self.assertEqual(self.gui_data.get_dplugins_count(), len(Plugins), 'Gui PL Count not correct') + + self.assertNotEqual( len( self.core_data.get_dplugin_by_uname('Sinus1').get_dblocks().keys() ), 0) + self.assertNotEqual( len( self.gui_data.get_dplugin_by_uname('Sinus1').get_dblocks().keys() ), 0) + + self.assertNotEqual( len( self.core_data.get_dplugin_by_uname('Sinus1').get_parameters().keys() ), 0) + self.assertNotEqual( len( self.gui_data.get_dplugin_by_uname('Sinus1').get_parameters().keys() ), 0) + + + self.gui_api.do_stopReset_plugin_uname('Sinus1') + + time.sleep(TestCoreMock.DELAY_TIME) + + for pl in Plugins: + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(pl[0]), 'No Plugin in CoreData with uname '+pl[0]) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(pl[0]), 'No Plugin in GuiData with uname '+pl[0]) + + self.assertEqual(self.core_data.get_dplugin_by_uname('Sinus1').state, const.PLUGIN_STATE_STOPPED) + self.assertEqual(self.gui_data.get_dplugin_by_uname('Sinus1').state, const.PLUGIN_STATE_STOPPED) + + + self.assertEqual( len( self.core_data.get_dplugin_by_uname('Sinus1').get_dblocks().keys() ), 0) + self.assertEqual( len( self.gui_data.get_dplugin_by_uname('Sinus1').get_dblocks().keys() ), 0) + + self.assertEqual( len( self.core_data.get_dplugin_by_uname('Sinus1').get_parameters().keys() ), 0) + self.assertEqual( len( self.gui_data.get_dplugin_by_uname('Sinus1').get_parameters().keys() ), 0) + + self.gui_api.do_start_plugin_uname('Sinus1') + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertEqual(self.core_data.get_dplugin_by_uname('Sinus1').state, const.PLUGIN_STATE_START_SUCCESFUL) + self.assertEqual(self.gui_data.get_dplugin_by_uname('Sinus1').state, const.PLUGIN_STATE_START_SUCCESFUL) + + self.assertNotEqual( len( self.core_data.get_dplugin_by_uname('Sinus1').get_dblocks().keys() ), 0) + self.assertNotEqual( len( self.gui_data.get_dplugin_by_uname('Sinus1').get_dblocks().keys() ), 0) + + self.assertNotEqual( len( self.core_data.get_dplugin_by_uname('Sinus1').get_parameters().keys() ), 0) + self.assertNotEqual( len( self.gui_data.get_dplugin_by_uname('Sinus1').get_parameters().keys() ), 0) + + def test_gui_api_valid_name(self): + + Plugins = [ ['Sinus1', 'Sinus'], ['ABC', 'Sinus'] ] + + for pl in Plugins: + self.gui_api.do_create_plugin(pl[1], pl[0]) + + time.sleep(TestCoreMock.DELAY_TIME) + + + self.assertTrue( self.gui_api.do_test_name_to_be_unique('test') ) + self.assertTrue( self.gui_api.do_test_name_to_be_unique('Plot1') ) + self.assertTrue( self.gui_api.do_test_name_to_be_unique('ABWDDJDWKADOI123347AJBHBEH') ) + self.assertTrue( self.gui_api.do_test_name_to_be_unique('sjdfhefuiefhu2334823482ujdjbdj') ) + self.assertFalse( self.gui_api.do_test_name_to_be_unique('eiwofueifheufh)wejkdhejdh') ) + self.assertTrue( self.gui_api.do_test_name_to_be_unique('Sinus2') ) + self.assertTrue( self.gui_api.do_test_name_to_be_unique('ABCD') ) + self.assertFalse( self.gui_api.do_test_name_to_be_unique('tt_tt') ) + self.assertFalse( self.gui_api.do_test_name_to_be_unique(' ttewf') ) + self.assertFalse( self.gui_api.do_test_name_to_be_unique('Sinus1') ) + self.assertFalse( self.gui_api.do_test_name_to_be_unique('ABC') ) + self.assertFalse( self.gui_api.do_test_name_to_be_unique('tt tt') ) + + def test_do_subscribe(self): + Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'PlotPerformance' ] ] + + for pl in Plugins: + self.gui_api.do_create_plugin(pl[1], pl[0]) + + time.sleep(TestCoreMock.DELAY_TIME) + + for pl in Plugins: + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(pl[0]), 'No Plugin in CoreData with uname '+pl[0]) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(pl[0]), 'No Plugin in GuiData with uname '+pl[0]) + + + self.assertEqual(self.core_data.get_dplugins_count(), len(Plugins), 'Core PL Count not correct') + self.assertEqual(self.gui_data.get_dplugins_count(), len(Plugins), 'Gui PL Count not correct') + + self.gui_api.do_subscribe_uname('Plot1','Sinus1','SinMit_f3',[1]) + + time.sleep(TestCoreMock.DELAY_TIME) + + subs = self.gui_data.get_dplugin_by_uname('Plot1').get_subscribtions() + for id in subs: + dsub = self.gui_data.get_dplugin_by_id(id) + self.assertEqual('Sinus1',dsub.uname) + + def test_do_unsubscribe(self): + Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'PlotPerformance' ] ] + + for pl in Plugins: + self.gui_api.do_create_plugin(pl[1], pl[0]) + + time.sleep(TestCoreMock.DELAY_TIME) + + for pl in Plugins: + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(pl[0]), 'No Plugin in CoreData with uname '+pl[0]) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(pl[0]), 'No Plugin in GuiData with uname '+pl[0]) + + + self.assertEqual(self.core_data.get_dplugins_count(), len(Plugins), 'Core PL Count not correct') + self.assertEqual(self.gui_data.get_dplugins_count(), len(Plugins), 'Gui PL Count not correct') + + self.gui_api.do_subscribe_uname('Plot1','Sinus1','SinMit_f3',[1]) + + time.sleep(TestCoreMock.DELAY_TIME) + + subs = self.gui_data.get_dplugin_by_uname('Plot1').get_subscribtions() + for id in subs: + dsub = self.gui_data.get_dplugin_by_id(id) + self.assertEqual('Sinus1',dsub.uname) + + self.gui_api.do_unsubscribe_uname('Plot1', 'Sinus1', 'SinMit_f3',[]) + + time.sleep(TestCoreMock.DELAY_TIME) + + + subs = self.gui_data.get_dplugin_by_uname('Plot1').get_subscribtions() + self.assertEqual(len(subs.keys()), 0) + + def test_do_set_parameter(self): + Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'PlotPerformance' ] ] + + for pl in Plugins: + self.gui_api.do_create_plugin(pl[1], pl[0]) + + time.sleep(TestCoreMock.DELAY_TIME) + + for pl in Plugins: + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(pl[0]), 'No Plugin in CoreData with uname '+pl[0]) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(pl[0]), 'No Plugin in GuiData with uname '+pl[0]) + + + self.assertEqual(self.core_data.get_dplugins_count(), len(Plugins), 'Core PL Count not correct') + self.assertEqual(self.gui_data.get_dplugins_count(), len(Plugins), 'Gui PL Count not correct') + + self.gui_api.do_subscribe_uname('Plot1','Sinus1','SinMit_f3',[1]) + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertEqual(0.6, \ + self.core_data.get_dplugin_by_uname('Sinus1').get_parameters()['Frequenz Block SinMit_f3'].value) + + self.gui_api.do_set_parameter_uname('Sinus1', 'Frequenz Block SinMit_f3', 0.9) + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertEqual(0.9, \ + self.core_data.get_dplugin_by_uname('Sinus1').get_parameters()['Frequenz Block SinMit_f3'].value) + self.assertEqual(0.9, \ + self.gui_data.get_dplugin_by_uname('Sinus1').get_parameters()['Frequenz Block SinMit_f3'].value) + + def test_pause_IOP(self): + Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'PlotPerformance' ] ] + + for pl in Plugins: + self.gui_api.do_create_plugin(pl[1], pl[0]) + + time.sleep(TestCoreMock.DELAY_TIME) + + for pl in Plugins: + self.assertIsNotNone(self.core_data.get_dplugin_by_uname(pl[0]), 'No Plugin in CoreData with uname '+pl[0]) + self.assertIsNotNone(self.gui_data.get_dplugin_by_uname(pl[0]), 'No Plugin in GuiData with uname '+pl[0]) + + + self.assertEqual(self.core_data.get_dplugins_count(), len(Plugins), 'Core PL Count not correct') + self.assertEqual(self.gui_data.get_dplugins_count(), len(Plugins), 'Gui PL Count not correct') + + self.gui_api.do_pause_plugin_by_uname('Sinus1') + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertEqual(self.core_data.get_dplugin_by_uname('Sinus1').state, const.PLUGIN_STATE_PAUSE, 'Core state not paused') + self.assertEqual(self.gui_data.get_dplugin_by_uname('Sinus1').state, const.PLUGIN_STATE_PAUSE, 'GUI state not paused') + + + self.gui_api.do_resume_plugin_by_uname('Sinus1') + + time.sleep(TestCoreMock.DELAY_TIME) + + self.assertEqual(self.core_data.get_dplugin_by_uname('Sinus1').state, const.PLUGIN_STATE_RESUMED, 'Core state not resumed') + self.assertEqual(self.gui_data.get_dplugin_by_uname('Sinus1').state, const.PLUGIN_STATE_RESUMED, 'GUI state not resumed') + + + + + + def setUp(self): + core = Core(None,use_gui=False) + core.gui_process = dummyProcess() + core.gui_alive = True + + self.core_thread = Thread(target=core.run) + self.core_thread.start() + + + self.gui_data = DGui() + + # + self.gui_api = Gui_api(self.gui_data, core.core_event_queue, core.gui_id) + + + + self.gui_thread = Thread(target=startGUI_TESTMOCK, args=[core.core_event_queue, core.gui_event_queue, core.gui_id, self.gui_data]) + + self.gui_thread.start() + + # get data and queues + self.core_queue = core.core_event_queue + self.gui_queue = core.gui_event_queue + self.core_data = core.core_data + + self.gui_api = Gui_api(self.gui_data, self.core_queue, core.gui_id) + + time.sleep(1) + + def tearDown(self): + # close Gui + self.gui_queue.put( PapiEvent(1,1,'instr_event','test_close', None) ) + + # wait for close + # time.sleep(1) + + # join threads + self.gui_thread.join() + self.core_thread.join() + + + + + +if __name__ == "__main__": + unittest.main(); diff --git a/papi/tests/TestDBlock.py b/papi/tests/TestDBlock.py new file mode 100644 index 00000000..46231544 --- /dev/null +++ b/papi/tests/TestDBlock.py @@ -0,0 +1,83 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +import unittest + + +from papi.data.DPlugin import DPlugin, DBlock +from papi.data.DCore import DCore + + +class TestCore(unittest.TestCase): + + def setUp(self): + self.dcore = DCore() + pass + + def test_add_susbcribers(self): + + dbl = DBlock('DBlock1') + dbl.id = 1 + + dpl_1 = DPlugin() + dpl_1.id = 2 + dpl_2 = DPlugin() + dpl_2.id = 3 + + self.assertTrue(dbl.add_subscribers(dpl_1)) + self.assertTrue(dbl.add_subscribers(dpl_2)) + + self.assertEqual(len(dbl.get_subscribers()),2) + + def test_rm_subscribers(self): + dbl = DBlock('DBlock1') + dbl.id = 1 + + dpl_1 = DPlugin() + dpl_1.id = 2 + dpl_2 = DPlugin() + dpl_2.id = 3 + + dbl.add_subscribers(dpl_1) + dbl.add_subscribers(dpl_2) + + self.assertEqual(len(dbl.get_subscribers()),2) + + dbl.rm_subscriber(dpl_1) + + self.assertEqual(len(dbl.get_subscribers()),1) + + dbl.rm_subscriber(dpl_2) + + self.assertEqual(len(dbl.get_subscribers()),0) + + self.assertFalse(dbl.rm_subscriber(dpl_1)) + + diff --git a/papi/tests/TestDCore.py b/papi/tests/TestDCore.py new file mode 100644 index 00000000..f6f70ac2 --- /dev/null +++ b/papi/tests/TestDCore.py @@ -0,0 +1,328 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +import unittest + +from papi.data.DCore import DCore +from papi.data.DCore import DPlugin, DBlock + + +class TestDCore(unittest.TestCase): + + def setUp(self): + self.dcore = DCore() + + def test_create_id(self): + id_1 = self.dcore.create_id() + id_2 = self.dcore.create_id() + + self.assertNotEqual(id_1, id_2) + + def test_add_plugin(self): + + pl_id_1 = self.dcore.create_id() + pl_id_2 = self.dcore.create_id() + + self.dcore.add_plugin(None, 1, None, None, None, pl_id_1) + self.dcore.add_plugin(None, 2, None, None, None, pl_id_2) + + dp_1 = self.dcore.get_dplugin_by_id(pl_id_1) + + self.assertTrue(isinstance(dp_1, DPlugin)) + + self.assertEqual(dp_1.id, pl_id_1) + dp_2 = self.dcore.get_dplugin_by_id(pl_id_2) + self.assertTrue(isinstance(dp_2, DPlugin)) + self.assertEqual(dp_2.id, pl_id_2) + + self.assertEqual(self.dcore.get_dplugins_count(), 2) + + self.assertTrue(self.dcore.rm_dplugin(dp_1.id)) + + self.assertEqual(self.dcore.get_dplugins_count(), 1) + + def test_rm_dplugin(self): + #Create dplugins + d_pl_1 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_2 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + + #Create dblocks + d_bl_1 = DBlock('Block_1') + d_bl_2 = DBlock('Block_2') + + #assign dblocks to DPlugin d_pl_1 + d_pl_1.add_dblock(d_bl_1) + d_pl_1.add_dblock(d_bl_2) + + #create subscribtions + + self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_1.name) + self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_2.name) + + self.assertEqual(len(d_pl_2.get_subscribtions()[d_pl_1.id]), 2) + self.assertEqual(len(d_bl_1.get_subscribers()), 1) + self.assertEqual(len(d_bl_2.get_subscribers()), 1) + + self.dcore.rm_dplugin(d_pl_1.id) + + #Check if DPlugin d_pl_1 is missing + + self.assertFalse(self.dcore.get_dplugin_by_id(d_pl_1.id)) + + #Check if all subscribtions were canceled + + self.assertEqual(len(d_pl_2.get_subscribtions().keys()), 0) + self.assertEqual(len(d_bl_1.get_subscribers()), 0) + self.assertEqual(len(d_bl_2.get_subscribers()), 0) + + pass + + def test_get_dplugin_by_id(self): + d_pl_1 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + self.dcore.get_dplugin_by_id(d_pl_1.id) + self.assertEqual(d_pl_1.id, self.dcore.get_dplugin_by_id(d_pl_1.id).id) + + def test_get_dplugin_by_uname(self): + d_pl_1 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_2 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_3 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + + d_pl_1.uname = "Test1" + d_pl_2.uname = "Test2" + d_pl_3.uname = "Test3" + + self.assertEqual("Test1", self.dcore.get_dplugin_by_uname("Test1").uname) + self.assertEqual("Test2", self.dcore.get_dplugin_by_uname("Test2").uname) + self.assertEqual("Test3", self.dcore.get_dplugin_by_uname("Test3").uname) + + self.assertIsNone(self.dcore.get_dplugin_by_uname("Test4")) + + def test_get_all_plugins(self): + self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + + self.assertEqual(len(self.dcore.get_all_plugins().keys()), 4) + + def test_subscribe(self): + #Create dplugins + d_pl_1 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_2 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + + #Create dblocks + d_bl_1 = DBlock('Block_1') + d_bl_2 = DBlock('Block_2') + + + #assign dblocks to DPlugin d_pl_1 + d_pl_1.add_dblock(d_bl_1) + d_pl_1.add_dblock(d_bl_2) + + self.assertTrue(self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_1.name)) + self.assertTrue(self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_2.name)) + + + self.assertTrue(d_pl_2.id in d_bl_1.get_subscribers()) + self.assertTrue(d_pl_2.id in d_bl_2.get_subscribers()) + + + def test_unsubscribe(self): + #Create dplugins + d_pl_1 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_2 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + + #Create dblocks + d_bl_1 = DBlock('Block_1') + d_bl_2 = DBlock('Block_2') + + + #assign dblocks to DPlugin d_pl_1 + d_pl_1.add_dblock(d_bl_1) + d_pl_1.add_dblock(d_bl_2) + + self.assertTrue(self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_1.name)) + self.assertTrue(self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_2.name)) + + self.dcore.unsubscribe(d_pl_2.id, d_pl_1.id, d_bl_1.name) + self.dcore.unsubscribe(d_pl_2.id, d_pl_1.id, d_bl_2.name) + + self.assertFalse(d_pl_2.id in d_bl_1.get_subscribers()) + self.assertFalse(d_pl_2.id in d_bl_1.get_subscribers()) + self.assertEqual(len(d_bl_2.get_subscribers()),0) + + + def test_unsubscribe_all(self): + #Create dplugins + d_pl_1 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_2 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_3 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + #Create dblocks + d_bl_1 = DBlock('Block_1') + d_bl_2 = DBlock('Block_2') + + #assign dblocks to DPlugin d_pl_1 + d_pl_1.add_dblock(d_bl_1) + d_pl_1.add_dblock(d_bl_2) + + self.assertTrue(self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_1.name)) + self.assertTrue(self.dcore.subscribe(d_pl_3.id, d_pl_1.id, d_bl_1.name)) + self.assertTrue(self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_2.name)) + + print(d_bl_1.get_subscribers()) + + self.assertEqual(len(d_pl_2.get_subscribtions()[d_pl_1.id].keys()), 2) + + self.dcore.unsubscribe_all(d_pl_2.id) + + self.assertNotIn(d_pl_1.id, d_pl_2.get_subscribtions()) + + print(d_bl_1.get_subscribers()) + self.assertTrue(len(d_bl_1.get_subscribers())==1) + self.assertTrue(len(d_bl_2.get_subscribers())==0) + + pass + + def test_rm_all_subscribers(self): + #Create dplugins + d_pl_1 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_2 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_3 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + + #Create dblocks + d_bl_1 = DBlock('Block_1') + d_bl_2 = DBlock('Block_2') + + #assign dblocks to DPlugin d_pl_1 + d_pl_1.add_dblock(d_bl_1) + d_pl_1.add_dblock(d_bl_2) + + self.assertTrue(self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_1.name)) + self.assertTrue(self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_2.name)) + self.assertTrue(self.dcore.subscribe(d_pl_3.id, d_pl_1.id, d_bl_1.name)) + self.assertTrue(self.dcore.subscribe(d_pl_3.id, d_pl_1.id, d_bl_2.name)) + + self.assertEqual(len(d_bl_1.get_subscribers()), 2) + self.assertEqual(len(d_bl_2.get_subscribers()), 2) + + self.dcore.rm_all_subscribers(d_pl_1.id) + + self.assertEqual(len(d_bl_1.get_subscribers()), 0) + self.assertEqual(len(d_bl_2.get_subscribers()), 0) + + pass + + def test_subscribe_signals(self): + #Create dplugins + d_pl_1 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_2 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_3 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + + #Create dblocks + d_bl_1 = DBlock('Block1') + d_bl_2 = DBlock('Block2') + + #assign dblocks to DPlugin d_pl_1 + d_pl_1.add_dblock(d_bl_1) + d_pl_1.add_dblock(d_bl_2) + + self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_1.name) + self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_2.name) + self.dcore.subscribe(d_pl_3.id, d_pl_1.id, d_bl_1.name) + self.dcore.subscribe(d_pl_3.id, d_pl_1.id, d_bl_2.name) + + + self.assertTrue(self.dcore.subscribe_signals(d_pl_2.id, d_pl_1.id, d_bl_1.name, [1, 2, 3])) + + self.assertTrue(self.dcore.subscribe_signals(d_pl_2.id, d_pl_1.id, d_bl_2.name, [6, 4, 5])) + + self.assertTrue(self.dcore.subscribe_signals(d_pl_3.id, d_pl_1.id, d_bl_1.name, [9])) + + self.assertTrue(self.dcore.subscribe_signals(d_pl_3.id, d_pl_1.id, d_bl_2.name, [17,19,18])) + + + subscription = d_pl_2.get_subscribtions()[d_pl_1.id][d_bl_1.name] + + self.assertIn(1, subscription.get_signals()) + self.assertIn(2, subscription.get_signals()) + self.assertIn(3, subscription.get_signals()) + + subscription = d_pl_3.get_subscribtions()[d_pl_1.id][d_bl_2.name] + + self.assertIn(19, subscription.get_signals()) + self.assertIn(17, subscription.get_signals()) + self.assertIn(18, subscription.get_signals()) + + subscription = d_pl_3.get_subscribtions()[d_pl_1.id][d_bl_1.name] + + self.assertIn(9, subscription.get_signals()) + + + def test_unsubscribe_signals(self): + #Create dplugins + d_pl_1 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_2 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + d_pl_3 = self.dcore.add_plugin(None, 1, None, None, None, self.dcore.create_id()) + + #Create dblocks + d_bl_1 = DBlock('Block1') + d_bl_2 = DBlock('Block2') + + #assign dblocks to DPlugin d_pl_1 + d_pl_1.add_dblock(d_bl_1) + d_pl_1.add_dblock(d_bl_2) + + self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_1.name) + self.dcore.subscribe(d_pl_2.id, d_pl_1.id, d_bl_2.name) + self.dcore.subscribe(d_pl_3.id, d_pl_1.id, d_bl_1.name) + self.dcore.subscribe(d_pl_3.id, d_pl_1.id, d_bl_2.name) + + + self.dcore.subscribe_signals(d_pl_2.id, d_pl_1.id, d_bl_1.name, [1, 2, 3]) + self.dcore.subscribe_signals(d_pl_2.id, d_pl_1.id, d_bl_2.name, [6, 4, 5]) + self.dcore.subscribe_signals(d_pl_3.id, d_pl_1.id, d_bl_1.name, [9]) + self.dcore.subscribe_signals(d_pl_3.id, d_pl_1.id, d_bl_2.name, [17,19,18]) + + self.assertTrue(self.dcore.unsubscribe_signals(d_pl_2.id, d_pl_1.id, d_bl_1.name, [1, 2])) + + subscription = d_pl_2.get_subscribtions()[d_pl_1.id][d_bl_1.name] + + self.assertNotIn(1, subscription.get_signals()) + self.assertNotIn(2, subscription.get_signals()) + self.assertIn(3, subscription.get_signals()) + + self.assertEqual(len(d_bl_1.get_subscribers()), 2) + + self.assertTrue(self.dcore.unsubscribe_signals(d_pl_2.id, d_pl_1.id, d_bl_1.name, [3])) + + self.assertEqual(len(d_bl_1.get_subscribers()), 1) + +if __name__ == "__main__": + unittest.main(); \ No newline at end of file diff --git a/papi/tests/TestDPlugin.py b/papi/tests/TestDPlugin.py new file mode 100644 index 00000000..505cb2ec --- /dev/null +++ b/papi/tests/TestDPlugin.py @@ -0,0 +1,133 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +import unittest + +from papi.data.DPlugin import DPlugin, DBlock +from papi.data.DParameter import DParameter + + +class TestDPlugin(unittest.TestCase): + + def setUp(self): + pass + + def test_subscribtions(self): + dpl_1 = DPlugin() + dpl_1.id = 1 + dpl_2 = DPlugin() + dpl_2.id = 2 + + dbl_1 = DBlock('DBlock1') + dbl_1.id = 3 + dbl_1.dplugin_id = dpl_2.id + + dbl_2 = DBlock('DBlock2') + dbl_2.id = 4 + dbl_2.dplugin_id = dpl_2.id + + dpl_2.add_dblock(dbl_1) + dpl_2.add_dblock(dbl_2) + + #Check: subscribe DBlocks + self.assertTrue(dpl_1.subscribe(dbl_1)) + self.assertTrue(dpl_1.subscribe(dbl_2)) + + self.assertFalse(dpl_1.subscribe(dbl_1)) + self.assertFalse(dpl_1.subscribe(dbl_2)) + + #Check: count of subscribtions + self.assertEqual(len(dpl_1.get_subscribtions()[dpl_2.id].keys()), 2) + + #Check: unsubscribe DBlock + self.assertTrue(dpl_1.unsubscribe(dbl_1)) + + #Check: count of subscribtions + self.assertEqual(len(dpl_1.get_subscribtions().keys()), 1) + #Check: count of subscribtions + self.assertEqual(len(dpl_1.get_subscribtions()[dpl_2.id].keys()), 1) + + def test_parameters(self): + dpl_1 = DPlugin() + dpl_1.id = 1 + + dp_1 = DParameter(1, 'parameter1') + dp_1.id = 2 + dp_2 = DParameter(2, 'parameter2') + dp_2.id = 3 + + #check: add Parameter + self.assertTrue(dpl_1.add_parameter(dp_1)) + self.assertTrue(dpl_1.add_parameter(dp_2)) + + self.assertFalse(dpl_1.add_parameter(dp_1)) + self.assertFalse(dpl_1.add_parameter(dp_2)) + + #Check: count of parameters + + self.assertEqual(len(dpl_1.get_parameters().keys()), 2) + + #Check: rm parameter + + self.assertTrue(dpl_1.rm_parameter(dp_1)) + self.assertEqual(len(dpl_1.get_parameters().keys()), 1) + self.assertTrue(dpl_1.rm_parameter(dp_2)) + self.assertEqual(len(dpl_1.get_parameters().keys()), 0) + + def test_dblocks(self): + dpl_1 = DPlugin() + dpl_1.id = 1 + + dbl_1 = DBlock('Block1') + dbl_1.dplugin_id = dpl_1.id + + dbl_2 = DBlock('Block2') + dbl_2.dplugin_id = dpl_1.id + + #check: add Parameter + self.assertTrue(dpl_1.add_dblock(dbl_1)) + self.assertTrue(dpl_1.add_dblock(dbl_2)) + + self.assertFalse(dpl_1.add_dblock(dbl_1)) + self.assertFalse(dpl_1.add_dblock(dbl_2)) + + #Check: count of parameters + + self.assertEqual(len(dpl_1.get_dblocks().keys()), 2) + + #Check: rm parameter + + self.assertTrue(dpl_1.rm_dblock(dbl_1)) + self.assertEqual(len(dpl_1.get_dblocks().keys()), 1) + self.assertTrue(dpl_1.rm_dblock(dbl_2)) + self.assertEqual(len(dpl_1.get_dblocks().keys()), 0) + +if __name__ == "__main__": + unittest.main(); \ No newline at end of file diff --git a/papi/tests/TestDSubscription.py b/papi/tests/TestDSubscription.py new file mode 100644 index 00000000..2829bc11 --- /dev/null +++ b/papi/tests/TestDSubscription.py @@ -0,0 +1,69 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +import unittest + +from papi.data.DPlugin import DPlugin, DBlock, DSubscription +from papi.data.DSignal import DSignal +from papi.data.DParameter import DParameter + + +class TestDSusbcription(unittest.TestCase): + + def setUp(self): + pass + + def test_attach_signal(self): + dblock = DBlock('SinMit_f1') + ds_1 = DSignal('t') + ds_2 = DSignal('f1_1') + dblock.add_signal(ds_1) + dblock.add_signal(ds_2) + + subscription = DSubscription(dblock) + + self.assertTrue(subscription.add_signal(ds_1)) + + self.assertIn(ds_1, subscription.get_signals()) + + self.assertFalse(subscription.add_signal(ds_1)) + + def test_remove_signal(self): + dblock = DBlock('SinMit_f1') + ds_1 = DSignal('t') + dblock.add_signal(ds_1) + + subscription = DSubscription(dblock) + subscription.add_signal(ds_1) + self.assertTrue(subscription.rm_signal(ds_1)) + + self.assertNotIn(ds_1, subscription.get_signals()) + + self.assertTrue(subscription.add_signal(ds_1)) \ No newline at end of file diff --git a/papi/tests/TestGUI.py b/papi/tests/TestGUI.py new file mode 100644 index 00000000..96f1e6c7 --- /dev/null +++ b/papi/tests/TestGUI.py @@ -0,0 +1,69 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +import sys + +from PySide.QtGui import QApplication + +from papi.gui.qt_dev.gui_main import GUI +from papi.data.DGui import DGui +from papi.data.DCore import DBlock + + +def get_gui_data(): + """ + + :return: + :rtype DGui: + """ + dgui = DGui() + #Create dplugins + d_pl_1 = dgui.add_plugin(None, 1, None, None, None, dgui.create_id()) + d_pl_2 = dgui.add_plugin(None, 1, None, None, None, dgui.create_id()) + d_pl_3 = dgui.add_plugin(None, 1, None, None, None, dgui.create_id()) + + #Create dblocks + d_bl_1 = DBlock(None, 0, 0, 'Block_1') + d_bl_2 = DBlock(None, 0, 0, 'Block_2') + d_bl_3 = DBlock(None, 0, 0, 'Block_3') + + #assign dblocks to DPlugin d_pl_1 + d_pl_1.add_dblock(d_bl_1) + d_pl_1.add_dblock(d_bl_2) + d_pl_1.add_dblock(d_bl_3) + return dgui + +if __name__ == '__main__': + app = QApplication(sys.argv) +# mw = QtGui.QMainWindow + frame = GUI(None,None,None) + frame.set_dgui_data(get_gui_data()) + frame.show() + app.exec_() \ No newline at end of file diff --git a/papi/tests/__init__.py b/papi/tests/__init__.py new file mode 100644 index 00000000..8efe4195 --- /dev/null +++ b/papi/tests/__init__.py @@ -0,0 +1,28 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + diff --git a/papi/ui/__init__.py b/papi/ui/__init__.py new file mode 100644 index 00000000..7c97c23d --- /dev/null +++ b/papi/ui/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' diff --git a/papi/ui/gui/__init__.py b/papi/ui/gui/__init__.py new file mode 100644 index 00000000..aa673a4c --- /dev/null +++ b/papi/ui/gui/__init__.py @@ -0,0 +1 @@ +__author__ = 'control' diff --git a/papi/ui/gui/qt_dev/__init__.py b/papi/ui/gui/qt_dev/__init__.py new file mode 100644 index 00000000..aa673a4c --- /dev/null +++ b/papi/ui/gui/qt_dev/__init__.py @@ -0,0 +1 @@ +__author__ = 'control' diff --git a/papi/ui/gui/qt_dev/add_plugin.py b/papi/ui/gui/qt_dev/add_plugin.py new file mode 100644 index 00000000..fade27e9 --- /dev/null +++ b/papi/ui/gui/qt_dev/add_plugin.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/gui/qt_dev/add_plugin.ui' +# +# Created: Mon Dec 15 17:36:09 2014 +# by: pyside-uic 0.2.15 running on PySide 1.2.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_AddPlugin(object): + def setupUi(self, AddPlugin): + AddPlugin.setObjectName("AddPlugin") + AddPlugin.resize(579, 558) + self.buttonBox = QtGui.QDialogButtonBox(AddPlugin) + self.buttonBox.setGeometry(QtCore.QRect(230, 510, 341, 32)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Apply|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setCenterButtons(True) + self.buttonBox.setObjectName("buttonBox") + self.treePlugin = QtGui.QTreeWidget(AddPlugin) + self.treePlugin.setGeometry(QtCore.QRect(10, 10, 561, 171)) + self.treePlugin.setObjectName("treePlugin") + self.formLayoutWidget = QtGui.QWidget(AddPlugin) + self.formLayoutWidget.setGeometry(QtCore.QRect(10, 190, 561, 62)) + self.formLayoutWidget.setObjectName("formLayoutWidget") + self.formLayout = QtGui.QFormLayout(self.formLayoutWidget) + self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setContentsMargins(0, 0, 0, 0) + self.formLayout.setObjectName("formLayout") + self.label = QtGui.QLabel(self.formLayoutWidget) + self.label.setObjectName("label") + self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.label) + self.le_uname = QtGui.QLineEdit(self.formLayoutWidget) + self.le_uname.setObjectName("le_uname") + self.formLayout.setWidget(0, QtGui.QFormLayout.FieldRole, self.le_uname) + self.label_2 = QtGui.QLabel(self.formLayoutWidget) + self.label_2.setObjectName("label_2") + self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.label_2) + self.le_path = QtGui.QLineEdit(self.formLayoutWidget) + self.le_path.setReadOnly(True) + self.le_path.setObjectName("le_path") + self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.le_path) + self.formLayoutWidget_2 = QtGui.QWidget(AddPlugin) + self.formLayoutWidget_2.setGeometry(QtCore.QRect(10, 270, 561, 241)) + self.formLayoutWidget_2.setObjectName("formLayoutWidget_2") + self.customFormLayout = QtGui.QFormLayout(self.formLayoutWidget_2) + self.customFormLayout.setContentsMargins(0, 0, 0, 0) + self.customFormLayout.setObjectName("customFormLayout") + self.line = QtGui.QFrame(AddPlugin) + self.line.setGeometry(QtCore.QRect(7, 250, 561, 20)) + self.line.setFrameShape(QtGui.QFrame.HLine) + self.line.setFrameShadow(QtGui.QFrame.Sunken) + self.line.setObjectName("line") + + self.retranslateUi(AddPlugin) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), AddPlugin.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), AddPlugin.reject) + QtCore.QMetaObject.connectSlotsByName(AddPlugin) + + def retranslateUi(self, AddPlugin): + AddPlugin.setWindowTitle(QtGui.QApplication.translate("AddPlugin", "Dialog", None, QtGui.QApplication.UnicodeUTF8)) + self.treePlugin.headerItem().setText(0, QtGui.QApplication.translate("AddPlugin", "Plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("AddPlugin", "UName", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("AddPlugin", "Path", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/ui/gui/qt_dev/add_subscriber.py b/papi/ui/gui/qt_dev/add_subscriber.py new file mode 100644 index 00000000..2d5c9597 --- /dev/null +++ b/papi/ui/gui/qt_dev/add_subscriber.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/gui/qt_dev/add_subscriber.ui' +# +# Created: Mon Dec 15 17:36:09 2014 +# by: pyside-uic 0.2.15 running on PySide 1.2.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_AddSubscriber(object): + def setupUi(self, AddSubscriber): + AddSubscriber.setObjectName("AddSubscriber") + AddSubscriber.resize(531, 197) + self.buttonBox = QtGui.QDialogButtonBox(AddSubscriber) + self.buttonBox.setGeometry(QtCore.QRect(10, 160, 511, 32)) + self.buttonBox.setAutoFillBackground(False) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Apply|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setCenterButtons(True) + self.buttonBox.setObjectName("buttonBox") + self.horizontalLayoutWidget = QtGui.QWidget(AddSubscriber) + self.horizontalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 511, 141)) + self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget") + self.horizontalLayout = QtGui.QHBoxLayout(self.horizontalLayoutWidget) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.treeSubscriber = QtGui.QTreeWidget(self.horizontalLayoutWidget) + self.treeSubscriber.setObjectName("treeSubscriber") + self.horizontalLayout.addWidget(self.treeSubscriber) + self.treeTarget = QtGui.QTreeWidget(self.horizontalLayoutWidget) + self.treeTarget.setObjectName("treeTarget") + self.horizontalLayout.addWidget(self.treeTarget) + self.treeBlock = QtGui.QTreeWidget(self.horizontalLayoutWidget) + self.treeBlock.setObjectName("treeBlock") + self.horizontalLayout.addWidget(self.treeBlock) + self.treeSignal = QtGui.QTreeWidget(self.horizontalLayoutWidget) + self.treeSignal.setSelectionMode(QtGui.QAbstractItemView.MultiSelection) + self.treeSignal.setObjectName("treeSignal") + self.horizontalLayout.addWidget(self.treeSignal) + + self.retranslateUi(AddSubscriber) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), AddSubscriber.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), AddSubscriber.reject) + QtCore.QMetaObject.connectSlotsByName(AddSubscriber) + + def retranslateUi(self, AddSubscriber): + AddSubscriber.setWindowTitle(QtGui.QApplication.translate("AddSubscriber", "Dialog", None, QtGui.QApplication.UnicodeUTF8)) + self.treeSubscriber.headerItem().setText(0, QtGui.QApplication.translate("AddSubscriber", "Subscriber", None, QtGui.QApplication.UnicodeUTF8)) + self.treeTarget.headerItem().setText(0, QtGui.QApplication.translate("AddSubscriber", "Target", None, QtGui.QApplication.UnicodeUTF8)) + self.treeBlock.headerItem().setText(0, QtGui.QApplication.translate("AddSubscriber", "Block", None, QtGui.QApplication.UnicodeUTF8)) + self.treeSignal.headerItem().setText(0, QtGui.QApplication.translate("AddSubscriber", "Signal", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/ui/gui/qt_dev/main.py b/papi/ui/gui/qt_dev/main.py new file mode 100644 index 00000000..67aed5d5 --- /dev/null +++ b/papi/ui/gui/qt_dev/main.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/gui/qt_dev/main.ui' +# +# Created: Mon Dec 15 17:36:09 2014 +# by: pyside-uic 0.2.15 running on PySide 1.2.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_MainGUI(object): + def setupUi(self, MainGUI): + MainGUI.setObjectName("MainGUI") + MainGUI.resize(979, 918) + self.centralwidget = QtGui.QWidget(MainGUI) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayout_2 = QtGui.QVBoxLayout(self.centralwidget) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.buttonExit = QtGui.QPushButton(self.centralwidget) + self.buttonExit.setObjectName("buttonExit") + self.horizontalLayout.addWidget(self.buttonExit) + self.buttonCreatePlugin = QtGui.QPushButton(self.centralwidget) + self.buttonCreatePlugin.setObjectName("buttonCreatePlugin") + self.horizontalLayout.addWidget(self.buttonCreatePlugin) + self.buttonShowOverview = QtGui.QPushButton(self.centralwidget) + self.buttonShowOverview.setObjectName("buttonShowOverview") + self.horizontalLayout.addWidget(self.buttonShowOverview) + self.buttonCreateSubscription = QtGui.QPushButton(self.centralwidget) + self.buttonCreateSubscription.setObjectName("buttonCreateSubscription") + self.horizontalLayout.addWidget(self.buttonCreateSubscription) + self.buttonCreatePCPSubscription = QtGui.QPushButton(self.centralwidget) + self.buttonCreatePCPSubscription.setObjectName("buttonCreatePCPSubscription") + self.horizontalLayout.addWidget(self.buttonCreatePCPSubscription) + self.buttonShowLicence = QtGui.QPushButton(self.centralwidget) + self.buttonShowLicence.setObjectName("buttonShowLicence") + self.horizontalLayout.addWidget(self.buttonShowLicence) + self.verticalLayout_2.addLayout(self.horizontalLayout) + self.stefans_button = QtGui.QPushButton(self.centralwidget) + self.stefans_button.setObjectName("stefans_button") + self.verticalLayout_2.addWidget(self.stefans_button) + self.stefans_button_2 = QtGui.QPushButton(self.centralwidget) + self.stefans_button_2.setObjectName("stefans_button_2") + self.verticalLayout_2.addWidget(self.stefans_button_2) + self.stefans_text_field = QtGui.QLineEdit(self.centralwidget) + self.stefans_text_field.setObjectName("stefans_text_field") + self.verticalLayout_2.addWidget(self.stefans_text_field) + self.scopeArea = QtGui.QMdiArea(self.centralwidget) + self.scopeArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.scopeArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.scopeArea.setObjectName("scopeArea") + self.verticalLayout_2.addWidget(self.scopeArea) + MainGUI.setCentralWidget(self.centralwidget) + self.statusbar = QtGui.QStatusBar(MainGUI) + self.statusbar.setObjectName("statusbar") + MainGUI.setStatusBar(self.statusbar) + self.menubar = QtGui.QMenuBar(MainGUI) + self.menubar.setGeometry(QtCore.QRect(0, 0, 979, 25)) + self.menubar.setObjectName("menubar") + self.menuMenu = QtGui.QMenu(self.menubar) + self.menuMenu.setObjectName("menuMenu") + self.menuAvailablePlugins = QtGui.QMenu(self.menubar) + self.menuAvailablePlugins.setObjectName("menuAvailablePlugins") + MainGUI.setMenuBar(self.menubar) + self.dockWidget_3 = QtGui.QDockWidget(MainGUI) + self.dockWidget_3.setEnabled(True) + self.dockWidget_3.setMinimumSize(QtCore.QSize(100, 41)) + self.dockWidget_3.setFloating(False) + self.dockWidget_3.setObjectName("dockWidget_3") + self.dockWidgetContents_3 = QtGui.QWidget() + self.dockWidgetContents_3.setObjectName("dockWidgetContents_3") + self.toolBox = QtGui.QToolBox(self.dockWidgetContents_3) + self.toolBox.setGeometry(QtCore.QRect(12, 4, 161, 831)) + self.toolBox.setMinimumSize(QtCore.QSize(100, 0)) + self.toolBox.setObjectName("toolBox") + self.vip = QtGui.QWidget() + self.vip.setGeometry(QtCore.QRect(0, 0, 161, 707)) + self.vip.setObjectName("vip") + self.treeWidget = QtGui.QTreeWidget(self.vip) + self.treeWidget.setGeometry(QtCore.QRect(0, 0, 161, 701)) + self.treeWidget.setObjectName("treeWidget") + self.toolBox.addItem(self.vip, "") + self.pcp = QtGui.QWidget() + self.pcp.setGeometry(QtCore.QRect(0, 0, 161, 707)) + self.pcp.setObjectName("pcp") + self.treeWidget_2 = QtGui.QTreeWidget(self.pcp) + self.treeWidget_2.setGeometry(QtCore.QRect(0, 0, 161, 701)) + self.treeWidget_2.setObjectName("treeWidget_2") + self.treeWidget_2.headerItem().setText(0, "1") + self.toolBox.addItem(self.pcp, "") + self.dpp = QtGui.QWidget() + self.dpp.setObjectName("dpp") + self.treeWidget_3 = QtGui.QTreeWidget(self.dpp) + self.treeWidget_3.setGeometry(QtCore.QRect(0, 10, 161, 691)) + self.treeWidget_3.setObjectName("treeWidget_3") + self.treeWidget_3.headerItem().setText(0, "1") + self.toolBox.addItem(self.dpp, "") + self.iop = QtGui.QWidget() + self.iop.setObjectName("iop") + self.treeWidget_4 = QtGui.QTreeWidget(self.iop) + self.treeWidget_4.setGeometry(QtCore.QRect(0, 0, 161, 701)) + self.treeWidget_4.setObjectName("treeWidget_4") + self.treeWidget_4.headerItem().setText(0, "1") + self.toolBox.addItem(self.iop, "") + self.dockWidget_3.setWidget(self.dockWidgetContents_3) + MainGUI.addDockWidget(QtCore.Qt.DockWidgetArea(1), self.dockWidget_3) + self.actionP_Available = QtGui.QAction(MainGUI) + self.actionP_Available.setObjectName("actionP_Available") + self.actionAP_IO = QtGui.QAction(MainGUI) + self.actionAP_IO.setObjectName("actionAP_IO") + self.actionRP_Visual = QtGui.QAction(MainGUI) + self.actionRP_Visual.setObjectName("actionRP_Visual") + self.actionRP_IO = QtGui.QAction(MainGUI) + self.actionRP_IO.setObjectName("actionRP_IO") + self.actionM_License = QtGui.QAction(MainGUI) + self.actionM_License.setObjectName("actionM_License") + self.actionM_Quit = QtGui.QAction(MainGUI) + self.actionM_Quit.setObjectName("actionM_Quit") + self.actionAP_Parameter = QtGui.QAction(MainGUI) + self.actionAP_Parameter.setObjectName("actionAP_Parameter") + self.actionP_Overview = QtGui.QAction(MainGUI) + self.actionP_Overview.setObjectName("actionP_Overview") + self.menubar.addAction(self.menuMenu.menuAction()) + self.menubar.addAction(self.menuAvailablePlugins.menuAction()) + + self.retranslateUi(MainGUI) + self.toolBox.setCurrentIndex(2) + QtCore.QMetaObject.connectSlotsByName(MainGUI) + + def retranslateUi(self, MainGUI): + MainGUI.setWindowTitle(QtGui.QApplication.translate("MainGUI", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) + self.buttonExit.setText(QtGui.QApplication.translate("MainGUI", "Exit", None, QtGui.QApplication.UnicodeUTF8)) + self.buttonCreatePlugin.setText(QtGui.QApplication.translate("MainGUI", "CreatePlugin", None, QtGui.QApplication.UnicodeUTF8)) + self.buttonShowOverview.setText(QtGui.QApplication.translate("MainGUI", "ShowOverview", None, QtGui.QApplication.UnicodeUTF8)) + self.buttonCreateSubscription.setText(QtGui.QApplication.translate("MainGUI", "CreateSubscription", None, QtGui.QApplication.UnicodeUTF8)) + self.buttonCreatePCPSubscription.setText(QtGui.QApplication.translate("MainGUI", "CreatePCPSubscription", None, QtGui.QApplication.UnicodeUTF8)) + self.buttonShowLicence.setText(QtGui.QApplication.translate("MainGUI", "ShowLicence", None, QtGui.QApplication.UnicodeUTF8)) + self.stefans_button.setText(QtGui.QApplication.translate("MainGUI", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.stefans_button_2.setText(QtGui.QApplication.translate("MainGUI", "Load", None, QtGui.QApplication.UnicodeUTF8)) + self.menuMenu.setTitle(QtGui.QApplication.translate("MainGUI", "Menu", None, QtGui.QApplication.UnicodeUTF8)) + self.menuAvailablePlugins.setTitle(QtGui.QApplication.translate("MainGUI", "Plugins", None, QtGui.QApplication.UnicodeUTF8)) + self.treeWidget.headerItem().setText(0, QtGui.QApplication.translate("MainGUI", "Plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.toolBox.setItemText(self.toolBox.indexOf(self.vip), QtGui.QApplication.translate("MainGUI", "Visual Plugins", None, QtGui.QApplication.UnicodeUTF8)) + self.toolBox.setItemText(self.toolBox.indexOf(self.pcp), QtGui.QApplication.translate("MainGUI", "Process Con. Plugins", None, QtGui.QApplication.UnicodeUTF8)) + self.toolBox.setItemText(self.toolBox.indexOf(self.dpp), QtGui.QApplication.translate("MainGUI", "Data Pro. Plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.toolBox.setItemText(self.toolBox.indexOf(self.iop), QtGui.QApplication.translate("MainGUI", "IO Plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.actionP_Available.setText(QtGui.QApplication.translate("MainGUI", "Available", None, QtGui.QApplication.UnicodeUTF8)) + self.actionAP_IO.setText(QtGui.QApplication.translate("MainGUI", "IO", None, QtGui.QApplication.UnicodeUTF8)) + self.actionRP_Visual.setText(QtGui.QApplication.translate("MainGUI", "Visual", None, QtGui.QApplication.UnicodeUTF8)) + self.actionRP_IO.setText(QtGui.QApplication.translate("MainGUI", "IO", None, QtGui.QApplication.UnicodeUTF8)) + self.actionM_License.setText(QtGui.QApplication.translate("MainGUI", "License", None, QtGui.QApplication.UnicodeUTF8)) + self.actionM_Quit.setText(QtGui.QApplication.translate("MainGUI", "Quit", None, QtGui.QApplication.UnicodeUTF8)) + self.actionAP_Parameter.setText(QtGui.QApplication.translate("MainGUI", "Parameter", None, QtGui.QApplication.UnicodeUTF8)) + self.actionP_Overview.setText(QtGui.QApplication.translate("MainGUI", "Overview", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/ui/gui/qt_dev/manager.py b/papi/ui/gui/qt_dev/manager.py new file mode 100644 index 00000000..ae879bbf --- /dev/null +++ b/papi/ui/gui/qt_dev/manager.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/gui/qt_dev/manager.ui' +# +# Created: Mon Dec 15 17:36:09 2014 +# by: pyside-uic 0.2.15 running on PySide 1.2.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Manager(object): + def setupUi(self, Manager): + Manager.setObjectName("Manager") + Manager.resize(883, 748) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Manager.sizePolicy().hasHeightForWidth()) + Manager.setSizePolicy(sizePolicy) + self.centralwidget = QtGui.QWidget(Manager) + self.centralwidget.setEnabled(True) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) + self.centralwidget.setSizePolicy(sizePolicy) + self.centralwidget.setMaximumSize(QtCore.QSize(791, 16777215)) + self.centralwidget.setLayoutDirection(QtCore.Qt.LeftToRight) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayoutWidget = QtGui.QWidget(self.centralwidget) + self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 0, 771, 701)) + self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") + self.horizontalLayout = QtGui.QHBoxLayout(self.verticalLayoutWidget) + self.horizontalLayout.setSizeConstraint(QtGui.QLayout.SetNoConstraint) + self.horizontalLayout.setContentsMargins(0, 0, 0, 0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.treePlugin = QtGui.QTreeWidget(self.verticalLayoutWidget) + self.treePlugin.setAnimated(True) + self.treePlugin.setAllColumnsShowFocus(True) + self.treePlugin.setObjectName("treePlugin") + self.treePlugin.header().setVisible(True) + self.treePlugin.header().setCascadingSectionResizes(False) + self.treePlugin.header().setDefaultSectionSize(100) + self.treePlugin.header().setHighlightSections(True) + self.treePlugin.header().setMinimumSectionSize(30) + self.treePlugin.header().setSortIndicatorShown(True) + self.treePlugin.header().setStretchLastSection(True) + self.horizontalLayout.addWidget(self.treePlugin) + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.formLayout = QtGui.QFormLayout() + self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setHorizontalSpacing(6) + self.formLayout.setObjectName("formLayout") + self.label = QtGui.QLabel(self.verticalLayoutWidget) + self.label.setObjectName("label") + self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.label) + self.le_ID = QtGui.QLineEdit(self.verticalLayoutWidget) + self.le_ID.setReadOnly(True) + self.le_ID.setObjectName("le_ID") + self.formLayout.setWidget(0, QtGui.QFormLayout.FieldRole, self.le_ID) + self.label_2 = QtGui.QLabel(self.verticalLayoutWidget) + self.label_2.setObjectName("label_2") + self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.label_2) + self.label_3 = QtGui.QLabel(self.verticalLayoutWidget) + self.label_3.setObjectName("label_3") + self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.label_3) + self.le_Type = QtGui.QLineEdit(self.verticalLayoutWidget) + self.le_Type.setReadOnly(True) + self.le_Type.setObjectName("le_Type") + self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.le_Type) + self.le_Path = QtGui.QLineEdit(self.verticalLayoutWidget) + self.le_Path.setEnabled(True) + self.le_Path.setReadOnly(True) + self.le_Path.setObjectName("le_Path") + self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.le_Path) + self.verticalLayout.addLayout(self.formLayout) + self.tableParameter = QtGui.QTableWidget(self.verticalLayoutWidget) + self.tableParameter.setRowCount(0) + self.tableParameter.setColumnCount(3) + self.tableParameter.setObjectName("tableParameter") + self.tableParameter.setColumnCount(3) + self.tableParameter.setRowCount(0) + item = QtGui.QTableWidgetItem() + self.tableParameter.setHorizontalHeaderItem(0, item) + item = QtGui.QTableWidgetItem() + self.tableParameter.setHorizontalHeaderItem(1, item) + item = QtGui.QTableWidgetItem() + self.tableParameter.setHorizontalHeaderItem(2, item) + self.tableParameter.horizontalHeader().setDefaultSectionSize(100) + self.verticalLayout.addWidget(self.tableParameter) + self.treeBlock = QtGui.QTreeWidget(self.verticalLayoutWidget) + self.treeBlock.setObjectName("treeBlock") + self.treeBlock.header().setDefaultSectionSize(80) + self.verticalLayout.addWidget(self.treeBlock) + self.treeSignal = QtGui.QTreeWidget(self.verticalLayoutWidget) + self.treeSignal.setObjectName("treeSignal") + self.verticalLayout.addWidget(self.treeSignal) + self.horizontalLayout.addLayout(self.verticalLayout) + Manager.setCentralWidget(self.centralwidget) + self.statusbar = QtGui.QStatusBar(Manager) + self.statusbar.setObjectName("statusbar") + Manager.setStatusBar(self.statusbar) + self.menuBar = QtGui.QMenuBar(Manager) + self.menuBar.setGeometry(QtCore.QRect(0, 0, 883, 25)) + self.menuBar.setObjectName("menuBar") + Manager.setMenuBar(self.menuBar) + + self.retranslateUi(Manager) + QtCore.QMetaObject.connectSlotsByName(Manager) + + def retranslateUi(self, Manager): + Manager.setWindowTitle(QtGui.QApplication.translate("Manager", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) + self.treePlugin.setSortingEnabled(True) + self.treePlugin.headerItem().setText(0, QtGui.QApplication.translate("Manager", "Plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.treePlugin.headerItem().setText(1, QtGui.QApplication.translate("Manager", "#Parameters", None, QtGui.QApplication.UnicodeUTF8)) + self.treePlugin.headerItem().setText(2, QtGui.QApplication.translate("Manager", "#Blocks", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("Manager", "ID", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("Manager", "Type", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("Manager", "Path", None, QtGui.QApplication.UnicodeUTF8)) + self.tableParameter.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("Manager", "Parameter", None, QtGui.QApplication.UnicodeUTF8)) + self.tableParameter.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("Manager", "PCP", None, QtGui.QApplication.UnicodeUTF8)) + self.tableParameter.horizontalHeaderItem(2).setText(QtGui.QApplication.translate("Manager", "Value", None, QtGui.QApplication.UnicodeUTF8)) + self.treeBlock.headerItem().setText(0, QtGui.QApplication.translate("Manager", "Block", None, QtGui.QApplication.UnicodeUTF8)) + self.treeBlock.headerItem().setText(1, QtGui.QApplication.translate("Manager", "Subscriber", None, QtGui.QApplication.UnicodeUTF8)) + self.treeSignal.headerItem().setText(0, QtGui.QApplication.translate("Manager", "Signal", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/ui/gui/qt_new/__init__.py b/papi/ui/gui/qt_new/__init__.py new file mode 100644 index 00000000..aa673a4c --- /dev/null +++ b/papi/ui/gui/qt_new/__init__.py @@ -0,0 +1 @@ +__author__ = 'control' diff --git a/papi/ui/gui/qt_new/create.py b/papi/ui/gui/qt_new/create.py new file mode 100644 index 00000000..e5469d62 --- /dev/null +++ b/papi/ui/gui/qt_new/create.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/gui/qt_new/create.ui' +# +# Created: Mon Dec 15 17:36:08 2014 +# by: pyside-uic 0.2.15 running on PySide 1.2.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Create(object): + def setupUi(self, Create): + Create.setObjectName("Create") + Create.resize(800, 601) + self.centralwidget = QtGui.QWidget(Create) + self.centralwidget.setObjectName("centralwidget") + self.horizontalLayout_2 = QtGui.QHBoxLayout(self.centralwidget) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.pluginTree = QtGui.QTreeView(self.centralwidget) + self.pluginTree.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems) + self.pluginTree.setObjectName("pluginTree") + self.pluginTree.header().setCascadingSectionResizes(True) + self.horizontalLayout.addWidget(self.pluginTree) + self.scrollArea = QtGui.QScrollArea(self.centralwidget) + self.scrollArea.setEnabled(False) + self.scrollArea.setWidgetResizable(True) + self.scrollArea.setObjectName("scrollArea") + self.scrollAreaWidgetContents = QtGui.QWidget() + self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 385, 532)) + self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents") + self.verticalLayout = QtGui.QVBoxLayout(self.scrollAreaWidgetContents) + self.verticalLayout.setObjectName("verticalLayout") + self.formLayout = QtGui.QFormLayout() + self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setObjectName("formLayout") + self.nameLabel = QtGui.QLabel(self.scrollAreaWidgetContents) + self.nameLabel.setObjectName("nameLabel") + self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.nameLabel) + self.authorLabel = QtGui.QLabel(self.scrollAreaWidgetContents) + self.authorLabel.setObjectName("authorLabel") + self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.authorLabel) + self.pathLabel = QtGui.QLabel(self.scrollAreaWidgetContents) + self.pathLabel.setObjectName("pathLabel") + self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.pathLabel) + self.descriptionLabel = QtGui.QLabel(self.scrollAreaWidgetContents) + self.descriptionLabel.setObjectName("descriptionLabel") + self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.descriptionLabel) + self.descriptionText = QtGui.QTextEdit(self.scrollAreaWidgetContents) + self.descriptionText.setObjectName("descriptionText") + self.formLayout.setWidget(3, QtGui.QFormLayout.FieldRole, self.descriptionText) + self.nameEdit = QtGui.QLineEdit(self.scrollAreaWidgetContents) + self.nameEdit.setObjectName("nameEdit") + self.formLayout.setWidget(0, QtGui.QFormLayout.FieldRole, self.nameEdit) + self.authorEdit = QtGui.QLineEdit(self.scrollAreaWidgetContents) + self.authorEdit.setObjectName("authorEdit") + self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.authorEdit) + self.pathEdit = QtGui.QLineEdit(self.scrollAreaWidgetContents) + self.pathEdit.setObjectName("pathEdit") + self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.pathEdit) + self.verticalLayout.addLayout(self.formLayout) + self.createButton = QtGui.QPushButton(self.scrollAreaWidgetContents) + self.createButton.setObjectName("createButton") + self.verticalLayout.addWidget(self.createButton) + self.scrollArea.setWidget(self.scrollAreaWidgetContents) + self.horizontalLayout.addWidget(self.scrollArea) + self.horizontalLayout_2.addLayout(self.horizontalLayout) + Create.setCentralWidget(self.centralwidget) + self.menubar = QtGui.QMenuBar(Create) + self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 25)) + self.menubar.setObjectName("menubar") + Create.setMenuBar(self.menubar) + self.statusbar = QtGui.QStatusBar(Create) + self.statusbar.setObjectName("statusbar") + Create.setStatusBar(self.statusbar) + + self.retranslateUi(Create) + QtCore.QMetaObject.connectSlotsByName(Create) + + def retranslateUi(self, Create): + Create.setWindowTitle(QtGui.QApplication.translate("Create", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) + self.nameLabel.setText(QtGui.QApplication.translate("Create", "Name", None, QtGui.QApplication.UnicodeUTF8)) + self.authorLabel.setText(QtGui.QApplication.translate("Create", "Author", None, QtGui.QApplication.UnicodeUTF8)) + self.pathLabel.setText(QtGui.QApplication.translate("Create", "Path", None, QtGui.QApplication.UnicodeUTF8)) + self.descriptionLabel.setText(QtGui.QApplication.translate("Create", "Description", None, QtGui.QApplication.UnicodeUTF8)) + self.createButton.setText(QtGui.QApplication.translate("Create", "Create Plugin", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/ui/gui/qt_new/create_dialog.py b/papi/ui/gui/qt_new/create_dialog.py new file mode 100644 index 00000000..6ad939e0 --- /dev/null +++ b/papi/ui/gui/qt_new/create_dialog.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/gui/qt_new/create_dialog.ui' +# +# Created: Mon Dec 15 17:36:09 2014 +# by: pyside-uic 0.2.15 running on PySide 1.2.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_CreatePluginDialog(object): + def setupUi(self, CreatePluginDialog): + CreatePluginDialog.setObjectName("CreatePluginDialog") + CreatePluginDialog.resize(498, 384) + self.verticalLayout = QtGui.QVBoxLayout(CreatePluginDialog) + self.verticalLayout.setObjectName("verticalLayout") + self.tabWidget = QtGui.QTabWidget(CreatePluginDialog) + self.tabWidget.setTabsClosable(False) + self.tabWidget.setMovable(False) + self.tabWidget.setObjectName("tabWidget") + self.tabSimple = QtGui.QWidget() + self.tabSimple.setObjectName("tabSimple") + self.verticalLayout_2 = QtGui.QVBoxLayout(self.tabSimple) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.formSimple = QtGui.QFormLayout() + self.formSimple.setObjectName("formSimple") + self.verticalLayout_2.addLayout(self.formSimple) + self.tabWidget.addTab(self.tabSimple, "") + self.tabAdvance = QtGui.QWidget() + self.tabAdvance.setObjectName("tabAdvance") + self.verticalLayout_3 = QtGui.QVBoxLayout(self.tabAdvance) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.formAdvance = QtGui.QFormLayout() + self.formAdvance.setObjectName("formAdvance") + self.verticalLayout_3.addLayout(self.formAdvance) + self.tabWidget.addTab(self.tabAdvance, "") + self.verticalLayout.addWidget(self.tabWidget) + self.buttonBox = QtGui.QDialogButtonBox(CreatePluginDialog) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Cancel|QtGui.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.verticalLayout.addWidget(self.buttonBox) + self.autostartBox = QtGui.QCheckBox(CreatePluginDialog) + self.autostartBox.setChecked(True) + self.autostartBox.setObjectName("autostartBox") + self.verticalLayout.addWidget(self.autostartBox) + + self.retranslateUi(CreatePluginDialog) + self.tabWidget.setCurrentIndex(0) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), CreatePluginDialog.accept) + QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), CreatePluginDialog.reject) + QtCore.QMetaObject.connectSlotsByName(CreatePluginDialog) + + def retranslateUi(self, CreatePluginDialog): + CreatePluginDialog.setWindowTitle(QtGui.QApplication.translate("CreatePluginDialog", "Dialog", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabSimple), QtGui.QApplication.translate("CreatePluginDialog", "Simple", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabAdvance), QtGui.QApplication.translate("CreatePluginDialog", "Advance", None, QtGui.QApplication.UnicodeUTF8)) + self.autostartBox.setText(QtGui.QApplication.translate("CreatePluginDialog", "Autostart", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/ui/gui/qt_new/main.py b/papi/ui/gui/qt_new/main.py new file mode 100644 index 00000000..c34f0f54 --- /dev/null +++ b/papi/ui/gui/qt_new/main.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/gui/qt_new/main.ui' +# +# Created: Mon Dec 15 17:36:08 2014 +# by: pyside-uic 0.2.15 running on PySide 1.2.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_QtNewMain(object): + def setupUi(self, QtNewMain): + QtNewMain.setObjectName("QtNewMain") + QtNewMain.resize(693, 600) + self.centralwidget = QtGui.QWidget(QtNewMain) + self.centralwidget.setObjectName("centralwidget") + self.verticalLayout_2 = QtGui.QVBoxLayout(self.centralwidget) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.loadButton = QtGui.QPushButton(self.centralwidget) + self.loadButton.setObjectName("loadButton") + self.horizontalLayout.addWidget(self.loadButton) + self.saveButton = QtGui.QPushButton(self.centralwidget) + self.saveButton.setObjectName("saveButton") + self.horizontalLayout.addWidget(self.saveButton) + self.verticalLayout.addLayout(self.horizontalLayout) + self.widgetArea = QtGui.QMdiArea(self.centralwidget) + self.widgetArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.widgetArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.widgetArea.setObjectName("widgetArea") + self.verticalLayout.addWidget(self.widgetArea) + self.verticalLayout_2.addLayout(self.verticalLayout) + QtNewMain.setCentralWidget(self.centralwidget) + self.menubar = QtGui.QMenuBar(QtNewMain) + self.menubar.setGeometry(QtCore.QRect(0, 0, 693, 25)) + self.menubar.setObjectName("menubar") + self.menuPaPI = QtGui.QMenu(self.menubar) + self.menuPaPI.setObjectName("menuPaPI") + self.menuPlugin = QtGui.QMenu(self.menubar) + self.menuPlugin.setObjectName("menuPlugin") + self.menuView = QtGui.QMenu(self.menubar) + self.menuView.setObjectName("menuView") + QtNewMain.setMenuBar(self.menubar) + self.statusbar = QtGui.QStatusBar(QtNewMain) + self.statusbar.setObjectName("statusbar") + QtNewMain.setStatusBar(self.statusbar) + self.actionLoad = QtGui.QAction(QtNewMain) + self.actionLoad.setObjectName("actionLoad") + self.actionSave = QtGui.QAction(QtNewMain) + self.actionSave.setObjectName("actionSave") + self.actionOverview = QtGui.QAction(QtNewMain) + self.actionOverview.setObjectName("actionOverview") + self.actionCreate = QtGui.QAction(QtNewMain) + self.actionCreate.setObjectName("actionCreate") + self.actionExit = QtGui.QAction(QtNewMain) + self.actionExit.setObjectName("actionExit") + self.actionReloadConfig = QtGui.QAction(QtNewMain) + self.actionReloadConfig.setObjectName("actionReloadConfig") + self.actionResetPaPI = QtGui.QAction(QtNewMain) + self.actionResetPaPI.setObjectName("actionResetPaPI") + self.actionRunMode = QtGui.QAction(QtNewMain) + self.actionRunMode.setObjectName("actionRunMode") + self.actionSetBackground = QtGui.QAction(QtNewMain) + self.actionSetBackground.setObjectName("actionSetBackground") + self.menuPaPI.addAction(self.actionLoad) + self.menuPaPI.addAction(self.actionSave) + self.menuPaPI.addAction(self.actionExit) + self.menuPaPI.addAction(self.actionReloadConfig) + self.menuPaPI.addAction(self.actionResetPaPI) + self.menuPlugin.addAction(self.actionOverview) + self.menuPlugin.addAction(self.actionCreate) + self.menuView.addAction(self.actionRunMode) + self.menuView.addAction(self.actionSetBackground) + self.menubar.addAction(self.menuPaPI.menuAction()) + self.menubar.addAction(self.menuPlugin.menuAction()) + self.menubar.addAction(self.menuView.menuAction()) + + self.retranslateUi(QtNewMain) + QtCore.QObject.connect(self.actionExit, QtCore.SIGNAL("triggered()"), QtNewMain.close) + QtCore.QMetaObject.connectSlotsByName(QtNewMain) + + def retranslateUi(self, QtNewMain): + QtNewMain.setWindowTitle(QtGui.QApplication.translate("QtNewMain", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) + self.loadButton.setText(QtGui.QApplication.translate("QtNewMain", "Load", None, QtGui.QApplication.UnicodeUTF8)) + self.saveButton.setText(QtGui.QApplication.translate("QtNewMain", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.menuPaPI.setTitle(QtGui.QApplication.translate("QtNewMain", "PaPI", None, QtGui.QApplication.UnicodeUTF8)) + self.menuPlugin.setTitle(QtGui.QApplication.translate("QtNewMain", "Plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.menuView.setTitle(QtGui.QApplication.translate("QtNewMain", "View", None, QtGui.QApplication.UnicodeUTF8)) + self.actionLoad.setText(QtGui.QApplication.translate("QtNewMain", "Load", None, QtGui.QApplication.UnicodeUTF8)) + self.actionSave.setText(QtGui.QApplication.translate("QtNewMain", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.actionOverview.setText(QtGui.QApplication.translate("QtNewMain", "Overview", None, QtGui.QApplication.UnicodeUTF8)) + self.actionCreate.setText(QtGui.QApplication.translate("QtNewMain", "Create", None, QtGui.QApplication.UnicodeUTF8)) + self.actionExit.setText(QtGui.QApplication.translate("QtNewMain", "Exit", None, QtGui.QApplication.UnicodeUTF8)) + self.actionReloadConfig.setText(QtGui.QApplication.translate("QtNewMain", "ReloadConfig", None, QtGui.QApplication.UnicodeUTF8)) + self.actionResetPaPI.setText(QtGui.QApplication.translate("QtNewMain", "ResetPaPI", None, QtGui.QApplication.UnicodeUTF8)) + self.actionRunMode.setText(QtGui.QApplication.translate("QtNewMain", "RunMode", None, QtGui.QApplication.UnicodeUTF8)) + self.actionSetBackground.setText(QtGui.QApplication.translate("QtNewMain", "SetBackground", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/ui/gui/qt_new/overview.py b/papi/ui/gui/qt_new/overview.py new file mode 100644 index 00000000..16045a48 --- /dev/null +++ b/papi/ui/gui/qt_new/overview.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/gui/qt_new/overview.ui' +# +# Created: Mon Dec 15 17:36:08 2014 +# by: pyside-uic 0.2.15 running on PySide 1.2.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Overview(object): + def setupUi(self, Overview): + Overview.setObjectName("Overview") + Overview.resize(803, 614) + self.centralwidget = QtGui.QWidget(Overview) + self.centralwidget.setObjectName("centralwidget") + self.horizontalLayout_3 = QtGui.QHBoxLayout(self.centralwidget) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.pluginTree = QtGui.QTreeView(self.centralwidget) + self.pluginTree.setObjectName("pluginTree") + self.horizontalLayout.addWidget(self.pluginTree) + self.tabWidget = QtGui.QTabWidget(self.centralwidget) + self.tabWidget.setEnabled(False) + self.tabWidget.setObjectName("tabWidget") + self.pluginTab = QtGui.QWidget() + self.pluginTab.setObjectName("pluginTab") + self.verticalLayout_2 = QtGui.QVBoxLayout(self.pluginTab) + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.formLayout = QtGui.QFormLayout() + self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setObjectName("formLayout") + self.unameLabel = QtGui.QLabel(self.pluginTab) + self.unameLabel.setObjectName("unameLabel") + self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.unameLabel) + self.unameEdit = QtGui.QLineEdit(self.pluginTab) + self.unameEdit.setObjectName("unameEdit") + self.formLayout.setWidget(0, QtGui.QFormLayout.FieldRole, self.unameEdit) + self.usedpluginLabel = QtGui.QLabel(self.pluginTab) + self.usedpluginLabel.setObjectName("usedpluginLabel") + self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.usedpluginLabel) + self.usedpluginEdit = QtGui.QLineEdit(self.pluginTab) + self.usedpluginEdit.setObjectName("usedpluginEdit") + self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.usedpluginEdit) + self.stateLabel = QtGui.QLabel(self.pluginTab) + self.stateLabel.setObjectName("stateLabel") + self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.stateLabel) + self.stateEdit = QtGui.QLineEdit(self.pluginTab) + self.stateEdit.setObjectName("stateEdit") + self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.stateEdit) + self.typeLabel = QtGui.QLabel(self.pluginTab) + self.typeLabel.setObjectName("typeLabel") + self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.typeLabel) + self.typeEdit = QtGui.QLineEdit(self.pluginTab) + self.typeEdit.setObjectName("typeEdit") + self.formLayout.setWidget(3, QtGui.QFormLayout.FieldRole, self.typeEdit) + self.alivestateLabel = QtGui.QLabel(self.pluginTab) + self.alivestateLabel.setObjectName("alivestateLabel") + self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.alivestateLabel) + self.alivestateEdit = QtGui.QLineEdit(self.pluginTab) + self.alivestateEdit.setObjectName("alivestateEdit") + self.formLayout.setWidget(4, QtGui.QFormLayout.FieldRole, self.alivestateEdit) + self.verticalLayout_2.addLayout(self.formLayout) + self.tabWidget_2 = QtGui.QTabWidget(self.pluginTab) + self.tabWidget_2.setObjectName("tabWidget_2") + self.parameterTab = QtGui.QWidget() + self.parameterTab.setObjectName("parameterTab") + self.verticalLayout_4 = QtGui.QVBoxLayout(self.parameterTab) + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.parameterTree = QtGui.QTreeView(self.parameterTab) + self.parameterTree.setObjectName("parameterTree") + self.verticalLayout_4.addWidget(self.parameterTree) + self.tabWidget_2.addTab(self.parameterTab, "") + self.blockTab = QtGui.QWidget() + self.blockTab.setObjectName("blockTab") + self.verticalLayout_5 = QtGui.QVBoxLayout(self.blockTab) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.blockTree = QtGui.QTreeView(self.blockTab) + self.blockTree.setObjectName("blockTree") + self.verticalLayout_5.addWidget(self.blockTree) + self.tabWidget_2.addTab(self.blockTab, "") + self.verticalLayout_2.addWidget(self.tabWidget_2) + self.horizontalLayout_2 = QtGui.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.pauseButton = QtGui.QPushButton(self.pluginTab) + self.pauseButton.setObjectName("pauseButton") + self.horizontalLayout_2.addWidget(self.pauseButton) + spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.stopButton = QtGui.QPushButton(self.pluginTab) + self.stopButton.setObjectName("stopButton") + self.horizontalLayout_2.addWidget(self.stopButton) + spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.playButton = QtGui.QPushButton(self.pluginTab) + self.playButton.setObjectName("playButton") + self.horizontalLayout_2.addWidget(self.playButton) + self.verticalLayout_2.addLayout(self.horizontalLayout_2) + self.tabWidget.addTab(self.pluginTab, "") + self.connectionTab = QtGui.QWidget() + self.connectionTab.setObjectName("connectionTab") + self.verticalLayout_3 = QtGui.QVBoxLayout(self.connectionTab) + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.subscribersTree = QtGui.QTreeView(self.connectionTab) + self.subscribersTree.setObjectName("subscribersTree") + self.verticalLayout.addWidget(self.subscribersTree) + self.subscriptionsTree = QtGui.QTreeView(self.connectionTab) + self.subscriptionsTree.setObjectName("subscriptionsTree") + self.verticalLayout.addWidget(self.subscriptionsTree) + self.verticalLayout_3.addLayout(self.verticalLayout) + self.tabWidget.addTab(self.connectionTab, "") + self.horizontalLayout.addWidget(self.tabWidget) + self.horizontalLayout_3.addLayout(self.horizontalLayout) + Overview.setCentralWidget(self.centralwidget) + self.menubar = QtGui.QMenuBar(Overview) + self.menubar.setGeometry(QtCore.QRect(0, 0, 803, 25)) + self.menubar.setObjectName("menubar") + self.menuAction = QtGui.QMenu(self.menubar) + self.menuAction.setObjectName("menuAction") + Overview.setMenuBar(self.menubar) + self.statusbar = QtGui.QStatusBar(Overview) + self.statusbar.setObjectName("statusbar") + Overview.setStatusBar(self.statusbar) + self.actionRefresh = QtGui.QAction(Overview) + self.actionRefresh.setObjectName("actionRefresh") + self.menuAction.addAction(self.actionRefresh) + self.menubar.addAction(self.menuAction.menuAction()) + + self.retranslateUi(Overview) + self.tabWidget.setCurrentIndex(0) + self.tabWidget_2.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(Overview) + + def retranslateUi(self, Overview): + Overview.setWindowTitle(QtGui.QApplication.translate("Overview", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) + self.unameLabel.setText(QtGui.QApplication.translate("Overview", "Unique name", None, QtGui.QApplication.UnicodeUTF8)) + self.usedpluginLabel.setText(QtGui.QApplication.translate("Overview", "Used plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.stateLabel.setText(QtGui.QApplication.translate("Overview", "State", None, QtGui.QApplication.UnicodeUTF8)) + self.typeLabel.setText(QtGui.QApplication.translate("Overview", "Type", None, QtGui.QApplication.UnicodeUTF8)) + self.alivestateLabel.setText(QtGui.QApplication.translate("Overview", "Alive state", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.parameterTab), QtGui.QApplication.translate("Overview", "Parameters", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.blockTab), QtGui.QApplication.translate("Overview", "Blocks", None, QtGui.QApplication.UnicodeUTF8)) + self.pauseButton.setText(QtGui.QApplication.translate("Overview", "PAUSE", None, QtGui.QApplication.UnicodeUTF8)) + self.stopButton.setText(QtGui.QApplication.translate("Overview", "STOP", None, QtGui.QApplication.UnicodeUTF8)) + self.playButton.setText(QtGui.QApplication.translate("Overview", "PLAY", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.pluginTab), QtGui.QApplication.translate("Overview", "Plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.connectionTab), QtGui.QApplication.translate("Overview", "Connections", None, QtGui.QApplication.UnicodeUTF8)) + self.menuAction.setTitle(QtGui.QApplication.translate("Overview", "Actions", None, QtGui.QApplication.UnicodeUTF8)) + self.actionRefresh.setText(QtGui.QApplication.translate("Overview", "Refresh", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/pyqtgraph/GraphicsScene/GraphicsScene.py b/pyqtgraph/GraphicsScene/GraphicsScene.py new file mode 100644 index 00000000..8729d085 --- /dev/null +++ b/pyqtgraph/GraphicsScene/GraphicsScene.py @@ -0,0 +1,578 @@ +from pyqtgraph.Qt import QtCore, QtGui + +from pyqtgraph.python2_3 import sortList +#try: + #from PyQt4 import QtOpenGL + #HAVE_OPENGL = True +#except ImportError: + #HAVE_OPENGL = False + +import weakref +from pyqtgraph.Point import Point +import pyqtgraph.functions as fn +import pyqtgraph.ptime as ptime +from .mouseEvents import * +import pyqtgraph.debug as debug +from . import exportDialog + +if hasattr(QtCore, 'PYQT_VERSION'): + try: + import sip + HAVE_SIP = True + except ImportError: + HAVE_SIP = False +else: + HAVE_SIP = False + + +__all__ = ['GraphicsScene'] + +class GraphicsScene(QtGui.QGraphicsScene): + """ + Extension of QGraphicsScene that implements a complete, parallel mouse event system. + (It would have been preferred to just alter the way QGraphicsScene creates and delivers + events, but this turned out to be impossible because the constructor for QGraphicsMouseEvent + is private) + + * Generates MouseClicked events in addition to the usual press/move/release events. + (This works around a problem where it is impossible to have one item respond to a + drag if another is watching for a click.) + * Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects + * Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu. + * Allows items to decide _before_ a mouse click which item will be the recipient of mouse events. + This lets us indicate unambiguously to the user which item they are about to click/drag on + * Eats mouseMove events that occur too soon after a mouse press. + * Reimplements items() and itemAt() to circumvent PyQt bug + + Mouse interaction is as follows: + + 1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents + as well as custom HoverEvents. + 2) Items are sent HoverEvents in Z-order and each item may optionally call event.acceptClicks(button), + acceptDrags(button) or both. If this method call returns True, this informs the item that _if_ + the user clicks/drags the specified mouse button, the item is guaranteed to be the + recipient of click/drag events (the item may wish to change its appearance to indicate this). + If the call to acceptClicks/Drags returns False, then the item is guaranteed to *not* receive + the requested event (because another item has already accepted it). + 3) If the mouse is clicked, a mousePressEvent is generated as usual. If any items accept this press event, then + No click/drag events will be generated and mouse interaction proceeds as defined by Qt. This allows + items to function properly if they are expecting the usual press/move/release sequence of events. + (It is recommended that items do NOT accept press events, and instead use click/drag events) + Note: The default implementation of QGraphicsItem.mousePressEvent will *accept* the event if the + item is has its Selectable or Movable flags enabled. You may need to override this behavior. + 4) If no item accepts the mousePressEvent, then the scene will begin delivering mouseDrag and/or mouseClick events. + If the mouse is moved a sufficient distance (or moved slowly enough) before the button is released, + then a mouseDragEvent is generated. + If no drag events are generated before the button is released, then a mouseClickEvent is generated. + 5) Click/drag events are delivered to the item that called acceptClicks/acceptDrags on the HoverEvent + in step 1. If no such items exist, then the scene attempts to deliver the events to items near the event. + ClickEvents may be delivered in this way even if no + item originally claimed it could accept the click. DragEvents may only be delivered this way if it is the initial + move in a drag. + """ + + sigMouseHover = QtCore.Signal(object) ## emits a list of objects hovered over + sigMouseMoved = QtCore.Signal(object) ## emits position of mouse on every move + sigMouseClicked = QtCore.Signal(object) ## emitted when mouse is clicked. Check for event.isAccepted() to see whether the event has already been acted on. + + sigPrepareForPaint = QtCore.Signal() ## emitted immediately before the scene is about to be rendered + + _addressCache = weakref.WeakValueDictionary() + + ExportDirectory = None + + @classmethod + def registerObject(cls, obj): + """ + Workaround for PyQt bug in qgraphicsscene.items() + All subclasses of QGraphicsObject must register themselves with this function. + (otherwise, mouse interaction with those objects will likely fail) + """ + if HAVE_SIP and isinstance(obj, sip.wrapper): + cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj + + + def __init__(self, clickRadius=2, moveDistance=5): + QtGui.QGraphicsScene.__init__(self) + self.setClickRadius(clickRadius) + self.setMoveDistance(moveDistance) + self.exportDirectory = None + + self.clickEvents = [] + self.dragButtons = [] + self.prepItems = weakref.WeakKeyDictionary() ## set of items with prepareForPaintMethods + self.mouseGrabber = None + self.dragItem = None + self.lastDrag = None + self.hoverItems = weakref.WeakKeyDictionary() + self.lastHoverEvent = None + #self.searchRect = QtGui.QGraphicsRectItem() + #self.searchRect.setPen(fn.mkPen(200,0,0)) + #self.addItem(self.searchRect) + + self.contextMenu = [QtGui.QAction("Export...", self)] + self.contextMenu[0].triggered.connect(self.showExportDialog) + + self.exportDialog = None + + def render(self, *args): + self.prepareForPaint() + return QtGui.QGraphicsScene.render(self, *args) + + def prepareForPaint(self): + """Called before every render. This method will inform items that the scene is about to + be rendered by emitting sigPrepareForPaint. + + This allows items to delay expensive processing until they know a paint will be required.""" + self.sigPrepareForPaint.emit() + + + def setClickRadius(self, r): + """ + Set the distance away from mouse clicks to search for interacting items. + When clicking, the scene searches first for items that directly intersect the click position + followed by any other items that are within a rectangle that extends r pixels away from the + click position. + """ + self._clickRadius = r + + def setMoveDistance(self, d): + """ + Set the distance the mouse must move after a press before mouseMoveEvents will be delivered. + This ensures that clicks with a small amount of movement are recognized as clicks instead of + drags. + """ + self._moveDistance = d + + def mousePressEvent(self, ev): + #print 'scenePress' + QtGui.QGraphicsScene.mousePressEvent(self, ev) + #print "mouseGrabberItem: ", self.mouseGrabberItem() + if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events + self.clickEvents.append(MouseClickEvent(ev)) + + ## set focus on the topmost focusable item under this click + items = self.items(ev.scenePos()) + for i in items: + if i.isEnabled() and i.isVisible() and int(i.flags() & i.ItemIsFocusable) > 0: + i.setFocus(QtCore.Qt.MouseFocusReason) + break + #else: + #addr = sip.unwrapinstance(sip.cast(self.mouseGrabberItem(), QtGui.QGraphicsItem)) + #item = GraphicsScene._addressCache.get(addr, self.mouseGrabberItem()) + #print "click grabbed by:", item + + def mouseMoveEvent(self, ev): + self.sigMouseMoved.emit(ev.scenePos()) + + ## First allow QGraphicsScene to deliver hoverEnter/Move/ExitEvents + QtGui.QGraphicsScene.mouseMoveEvent(self, ev) + + ## Next deliver our own HoverEvents + self.sendHoverEvents(ev) + + if int(ev.buttons()) != 0: ## button is pressed; send mouseMoveEvents and mouseDragEvents + QtGui.QGraphicsScene.mouseMoveEvent(self, ev) + if self.mouseGrabberItem() is None: + now = ptime.time() + init = False + ## keep track of which buttons are involved in dragging + for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MidButton, QtCore.Qt.RightButton]: + if int(ev.buttons() & btn) == 0: + continue + if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet + cev = [e for e in self.clickEvents if int(e.button()) == int(btn)][0] + dist = Point(ev.screenPos() - cev.screenPos()) + if dist.length() < self._moveDistance and now - cev.time() < 0.5: + continue + init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True + self.dragButtons.append(int(btn)) + + ## If we have dragged buttons, deliver a drag event + if len(self.dragButtons) > 0: + if self.sendDragEvent(ev, init=init): + ev.accept() + + def leaveEvent(self, ev): ## inform items that mouse is gone + if len(self.dragButtons) == 0: + self.sendHoverEvents(ev, exitOnly=True) + + + def mouseReleaseEvent(self, ev): + #print 'sceneRelease' + if self.mouseGrabberItem() is None: + #print "sending click/drag event" + if ev.button() in self.dragButtons: + if self.sendDragEvent(ev, final=True): + #print "sent drag event" + ev.accept() + self.dragButtons.remove(ev.button()) + else: + cev = [e for e in self.clickEvents if int(e.button()) == int(ev.button())] + if self.sendClickEvent(cev[0]): + #print "sent click event" + ev.accept() + self.clickEvents.remove(cev[0]) + + if int(ev.buttons()) == 0: + self.dragItem = None + self.dragButtons = [] + self.clickEvents = [] + self.lastDrag = None + QtGui.QGraphicsScene.mouseReleaseEvent(self, ev) + + self.sendHoverEvents(ev) ## let items prepare for next click/drag + + def mouseDoubleClickEvent(self, ev): + QtGui.QGraphicsScene.mouseDoubleClickEvent(self, ev) + if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events + self.clickEvents.append(MouseClickEvent(ev, double=True)) + + def sendHoverEvents(self, ev, exitOnly=False): + ## if exitOnly, then just inform all previously hovered items that the mouse has left. + + if exitOnly: + acceptable=False + items = [] + event = HoverEvent(None, acceptable) + else: + acceptable = int(ev.buttons()) == 0 ## if we are in mid-drag, do not allow items to accept the hover event. + event = HoverEvent(ev, acceptable) + items = self.itemsNearEvent(event, hoverable=True) + self.sigMouseHover.emit(items) + + prevItems = list(self.hoverItems.keys()) + + for item in items: + if hasattr(item, 'hoverEvent'): + event.currentItem = item + if item not in self.hoverItems: + self.hoverItems[item] = None + event.enter = True + else: + prevItems.remove(item) + event.enter = False + + try: + item.hoverEvent(event) + except: + debug.printExc("Error sending hover event:") + + event.enter = False + event.exit = True + for item in prevItems: + event.currentItem = item + try: + item.hoverEvent(event) + except: + debug.printExc("Error sending hover exit event:") + finally: + del self.hoverItems[item] + + if hasattr(ev, 'buttons') and int(ev.buttons()) == 0: + self.lastHoverEvent = event ## save this so we can ask about accepted events later. + + + def sendDragEvent(self, ev, init=False, final=False): + ## Send a MouseDragEvent to the current dragItem or to + ## items near the beginning of the drag + event = MouseDragEvent(ev, self.clickEvents[0], self.lastDrag, start=init, finish=final) + #print "dragEvent: init=", init, 'final=', final, 'self.dragItem=', self.dragItem + if init and self.dragItem is None: + if self.lastHoverEvent is not None: + acceptedItem = self.lastHoverEvent.dragItems().get(event.button(), None) + else: + acceptedItem = None + + if acceptedItem is not None: + #print "Drag -> pre-selected item:", acceptedItem + self.dragItem = acceptedItem + event.currentItem = self.dragItem + try: + self.dragItem.mouseDragEvent(event) + except: + debug.printExc("Error sending drag event:") + + else: + #print "drag -> new item" + for item in self.itemsNearEvent(event): + #print "check item:", item + if not item.isVisible() or not item.isEnabled(): + continue + if hasattr(item, 'mouseDragEvent'): + event.currentItem = item + try: + item.mouseDragEvent(event) + except: + debug.printExc("Error sending drag event:") + if event.isAccepted(): + #print " --> accepted" + self.dragItem = item + if int(item.flags() & item.ItemIsFocusable) > 0: + item.setFocus(QtCore.Qt.MouseFocusReason) + break + elif self.dragItem is not None: + event.currentItem = self.dragItem + try: + self.dragItem.mouseDragEvent(event) + except: + debug.printExc("Error sending hover exit event:") + + self.lastDrag = event + + return event.isAccepted() + + + def sendClickEvent(self, ev): + ## if we are in mid-drag, click events may only go to the dragged item. + if self.dragItem is not None and hasattr(self.dragItem, 'mouseClickEvent'): + ev.currentItem = self.dragItem + self.dragItem.mouseClickEvent(ev) + + ## otherwise, search near the cursor + else: + if self.lastHoverEvent is not None: + acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None) + else: + acceptedItem = None + + if acceptedItem is not None: + ev.currentItem = acceptedItem + try: + acceptedItem.mouseClickEvent(ev) + except: + debug.printExc("Error sending click event:") + else: + for item in self.itemsNearEvent(ev): + if not item.isVisible() or not item.isEnabled(): + continue + if hasattr(item, 'mouseClickEvent'): + ev.currentItem = item + try: + item.mouseClickEvent(ev) + except: + debug.printExc("Error sending click event:") + + if ev.isAccepted(): + if int(item.flags() & item.ItemIsFocusable) > 0: + item.setFocus(QtCore.Qt.MouseFocusReason) + break + #if not ev.isAccepted() and ev.button() is QtCore.Qt.RightButton: + #print "GraphicsScene emitting sigSceneContextMenu" + #self.sigMouseClicked.emit(ev) + #ev.accept() + self.sigMouseClicked.emit(ev) + return ev.isAccepted() + + #def claimEvent(self, item, button, eventType): + #key = (button, eventType) + #if key in self.claimedEvents: + #return False + #self.claimedEvents[key] = item + #print "event", key, "claimed by", item + #return True + + + def items(self, *args): + #print 'args:', args + items = QtGui.QGraphicsScene.items(self, *args) + ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, + ## then the object returned will be different than the actual item that was originally added to the scene + items2 = list(map(self.translateGraphicsItem, items)) + #if HAVE_SIP and isinstance(self, sip.wrapper): + #items2 = [] + #for i in items: + #addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem)) + #i2 = GraphicsScene._addressCache.get(addr, i) + ##print i, "==>", i2 + #items2.append(i2) + #print 'items:', items + return items2 + + def selectedItems(self, *args): + items = QtGui.QGraphicsScene.selectedItems(self, *args) + ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, + ## then the object returned will be different than the actual item that was originally added to the scene + #if HAVE_SIP and isinstance(self, sip.wrapper): + #items2 = [] + #for i in items: + #addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem)) + #i2 = GraphicsScene._addressCache.get(addr, i) + ##print i, "==>", i2 + #items2.append(i2) + items2 = list(map(self.translateGraphicsItem, items)) + + #print 'items:', items + return items2 + + def itemAt(self, *args): + item = QtGui.QGraphicsScene.itemAt(self, *args) + + ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, + ## then the object returned will be different than the actual item that was originally added to the scene + #if HAVE_SIP and isinstance(self, sip.wrapper): + #addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem)) + #item = GraphicsScene._addressCache.get(addr, item) + #return item + return self.translateGraphicsItem(item) + + def itemsNearEvent(self, event, selMode=QtCore.Qt.IntersectsItemShape, sortOrder=QtCore.Qt.DescendingOrder, hoverable=False): + """ + Return an iterator that iterates first through the items that directly intersect point (in Z order) + followed by any other items that are within the scene's click radius. + """ + #tr = self.getViewWidget(event.widget()).transform() + view = self.views()[0] + tr = view.viewportTransform() + r = self._clickRadius + rect = view.mapToScene(QtCore.QRect(0, 0, 2*r, 2*r)).boundingRect() + + seen = set() + if hasattr(event, 'buttonDownScenePos'): + point = event.buttonDownScenePos() + else: + point = event.scenePos() + w = rect.width() + h = rect.height() + rgn = QtCore.QRectF(point.x()-w, point.y()-h, 2*w, 2*h) + #self.searchRect.setRect(rgn) + + + items = self.items(point, selMode, sortOrder, tr) + + ## remove items whose shape does not contain point (scene.items() apparently sucks at this) + items2 = [] + for item in items: + if hoverable and not hasattr(item, 'hoverEvent'): + continue + shape = item.shape() + if shape is None: + continue + if item.mapToScene(shape).contains(point): + items2.append(item) + + ## Sort by descending Z-order (don't trust scene.itms() to do this either) + ## use 'absolute' z value, which is the sum of all item/parent ZValues + def absZValue(item): + if item is None: + return 0 + return item.zValue() + absZValue(item.parentItem()) + + sortList(items2, lambda a,b: cmp(absZValue(b), absZValue(a))) + + return items2 + + #for item in items: + ##seen.add(item) + + #shape = item.mapToScene(item.shape()) + #if not shape.contains(point): + #continue + #yield item + #for item in self.items(rgn, selMode, sortOrder, tr): + ##if item not in seen: + #yield item + + def getViewWidget(self): + return self.views()[0] + + #def getViewWidget(self, widget): + ### same pyqt bug -- mouseEvent.widget() doesn't give us the original python object. + ### [[doesn't seem to work correctly]] + #if HAVE_SIP and isinstance(self, sip.wrapper): + #addr = sip.unwrapinstance(sip.cast(widget, QtGui.QWidget)) + ##print "convert", widget, addr + #for v in self.views(): + #addr2 = sip.unwrapinstance(sip.cast(v, QtGui.QWidget)) + ##print " check:", v, addr2 + #if addr2 == addr: + #return v + #else: + #return widget + + def addParentContextMenus(self, item, menu, event): + """ + Can be called by any item in the scene to expand its context menu to include parent context menus. + Parents may implement getContextMenus to add new menus / actions to the existing menu. + getContextMenus must accept 1 argument (the event that generated the original menu) and + return a single QMenu or a list of QMenus. + + The final menu will look like: + + | Original Item 1 + | Original Item 2 + | ... + | Original Item N + | ------------------ + | Parent Item 1 + | Parent Item 2 + | ... + | Grandparent Item 1 + | ... + + + ============== ================================================== + **Arguments:** + item The item that initially created the context menu + (This is probably the item making the call to this function) + menu The context menu being shown by the item + event The original event that triggered the menu to appear. + ============== ================================================== + """ + + #items = self.itemsNearEvent(ev) + menusToAdd = [] + while item is not self: + item = item.parentItem() + + if item is None: + item = self + + if not hasattr(item, "getContextMenus"): + continue + + subMenus = item.getContextMenus(event) + if subMenus is None: + continue + if type(subMenus) is not list: ## so that some items (like FlowchartViewBox) can return multiple menus + subMenus = [subMenus] + + for sm in subMenus: + menusToAdd.append(sm) + + if len(menusToAdd) > 0: + menu.addSeparator() + + for m in menusToAdd: + if isinstance(m, QtGui.QMenu): + menu.addMenu(m) + elif isinstance(m, QtGui.QAction): + menu.addAction(m) + else: + raise Exception("Cannot add object %s (type=%s) to QMenu." % (str(m), str(type(m)))) + + return menu + + def getContextMenus(self, event): + self.contextMenuItem = event.acceptedItem + return self.contextMenu + + def showExportDialog(self): + if self.exportDialog is None: + self.exportDialog = exportDialog.ExportDialog(self) + self.exportDialog.show(self.contextMenuItem) + + @staticmethod + def translateGraphicsItem(item): + ## for fixing pyqt bugs where the wrong item is returned + if HAVE_SIP and isinstance(item, sip.wrapper): + addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem)) + item = GraphicsScene._addressCache.get(addr, item) + return item + + @staticmethod + def translateGraphicsItems(items): + return list(map(GraphicsScene.translateGraphicsItem, items)) + + + diff --git a/pyqtgraph/GraphicsScene/__init__.py b/pyqtgraph/GraphicsScene/__init__.py new file mode 100644 index 00000000..abe42c6f --- /dev/null +++ b/pyqtgraph/GraphicsScene/__init__.py @@ -0,0 +1 @@ +from .GraphicsScene import * diff --git a/pyqtgraph/GraphicsScene/exportDialog.py b/pyqtgraph/GraphicsScene/exportDialog.py new file mode 100644 index 00000000..436d5e42 --- /dev/null +++ b/pyqtgraph/GraphicsScene/exportDialog.py @@ -0,0 +1,139 @@ +from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE +import pyqtgraph as pg +import pyqtgraph.exporters as exporters + +if USE_PYSIDE: + from . import exportDialogTemplate_pyside as exportDialogTemplate +else: + from . import exportDialogTemplate_pyqt as exportDialogTemplate + + +class ExportDialog(QtGui.QWidget): + def __init__(self, scene): + QtGui.QWidget.__init__(self) + self.setVisible(False) + self.setWindowTitle("Export") + self.shown = False + self.currentExporter = None + self.scene = scene + + self.selectBox = QtGui.QGraphicsRectItem() + self.selectBox.setPen(pg.mkPen('y', width=3, style=QtCore.Qt.DashLine)) + self.selectBox.hide() + self.scene.addItem(self.selectBox) + + self.ui = exportDialogTemplate.Ui_Form() + self.ui.setupUi(self) + + self.ui.closeBtn.clicked.connect(self.close) + self.ui.exportBtn.clicked.connect(self.exportClicked) + self.ui.copyBtn.clicked.connect(self.copyClicked) + self.ui.itemTree.currentItemChanged.connect(self.exportItemChanged) + self.ui.formatList.currentItemChanged.connect(self.exportFormatChanged) + + + def show(self, item=None): + if item is not None: + ## Select next exportable parent of the item originally clicked on + while not isinstance(item, pg.ViewBox) and not isinstance(item, pg.PlotItem) and item is not None: + item = item.parentItem() + ## if this is a ViewBox inside a PlotItem, select the parent instead. + if isinstance(item, pg.ViewBox) and isinstance(item.parentItem(), pg.PlotItem): + item = item.parentItem() + self.updateItemList(select=item) + self.setVisible(True) + self.activateWindow() + self.raise_() + self.selectBox.setVisible(True) + + if not self.shown: + self.shown = True + vcenter = self.scene.getViewWidget().geometry().center() + self.setGeometry(vcenter.x()-self.width()/2, vcenter.y()-self.height()/2, self.width(), self.height()) + + def updateItemList(self, select=None): + self.ui.itemTree.clear() + si = QtGui.QTreeWidgetItem(["Entire Scene"]) + si.gitem = self.scene + self.ui.itemTree.addTopLevelItem(si) + self.ui.itemTree.setCurrentItem(si) + si.setExpanded(True) + for child in self.scene.items(): + if child.parentItem() is None: + self.updateItemTree(child, si, select=select) + + def updateItemTree(self, item, treeItem, select=None): + si = None + if isinstance(item, pg.ViewBox): + si = QtGui.QTreeWidgetItem(['ViewBox']) + elif isinstance(item, pg.PlotItem): + si = QtGui.QTreeWidgetItem(['Plot']) + + if si is not None: + si.gitem = item + treeItem.addChild(si) + treeItem = si + if si.gitem is select: + self.ui.itemTree.setCurrentItem(si) + + for ch in item.childItems(): + self.updateItemTree(ch, treeItem, select=select) + + + def exportItemChanged(self, item, prev): + if item is None: + return + if item.gitem is self.scene: + newBounds = self.scene.views()[0].viewRect() + else: + newBounds = item.gitem.sceneBoundingRect() + self.selectBox.setRect(newBounds) + self.selectBox.show() + self.updateFormatList() + + def updateFormatList(self): + current = self.ui.formatList.currentItem() + if current is not None: + current = str(current.text()) + self.ui.formatList.clear() + self.exporterClasses = {} + gotCurrent = False + for exp in exporters.listExporters(): + self.ui.formatList.addItem(exp.Name) + self.exporterClasses[exp.Name] = exp + if exp.Name == current: + self.ui.formatList.setCurrentRow(self.ui.formatList.count()-1) + gotCurrent = True + + if not gotCurrent: + self.ui.formatList.setCurrentRow(0) + + def exportFormatChanged(self, item, prev): + if item is None: + self.currentExporter = None + self.ui.paramTree.clear() + return + expClass = self.exporterClasses[str(item.text())] + exp = expClass(item=self.ui.itemTree.currentItem().gitem) + params = exp.parameters() + if params is None: + self.ui.paramTree.clear() + else: + self.ui.paramTree.setParameters(params) + self.currentExporter = exp + self.ui.copyBtn.setEnabled(exp.allowCopy) + + def exportClicked(self): + self.selectBox.hide() + self.currentExporter.export() + + def copyClicked(self): + self.selectBox.hide() + self.currentExporter.export(copy=True) + + def close(self): + self.selectBox.setVisible(False) + self.setVisible(False) + + + diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate.ui b/pyqtgraph/GraphicsScene/exportDialogTemplate.ui new file mode 100644 index 00000000..c91fbc3f --- /dev/null +++ b/pyqtgraph/GraphicsScene/exportDialogTemplate.ui @@ -0,0 +1,100 @@ + + + Form + + + + 0 + 0 + 241 + 367 + + + + Export + + + + 0 + + + + + Item to export: + + + + + + + false + + + + 1 + + + + + + + + Export format + + + + + + + + + + Export + + + + + + + Close + + + + + + + false + + + + 1 + + + + + + + + Export options + + + + + + + Copy + + + + + + + + ParameterTree + QTreeWidget +
pyqtgraph.parametertree
+
+
+ + +
diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py new file mode 100644 index 00000000..c3056d1c --- /dev/null +++ b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './GraphicsScene/exportDialogTemplate.ui' +# +# Created: Wed Jan 30 21:02:28 2013 +# by: PyQt4 UI code generator 4.9.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(241, 367) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.label = QtGui.QLabel(Form) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout.addWidget(self.label, 0, 0, 1, 3) + self.itemTree = QtGui.QTreeWidget(Form) + self.itemTree.setObjectName(_fromUtf8("itemTree")) + self.itemTree.headerItem().setText(0, _fromUtf8("1")) + self.itemTree.header().setVisible(False) + self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) + self.label_2 = QtGui.QLabel(Form) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) + self.formatList = QtGui.QListWidget(Form) + self.formatList.setObjectName(_fromUtf8("formatList")) + self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) + self.exportBtn = QtGui.QPushButton(Form) + self.exportBtn.setObjectName(_fromUtf8("exportBtn")) + self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) + self.closeBtn = QtGui.QPushButton(Form) + self.closeBtn.setObjectName(_fromUtf8("closeBtn")) + self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) + self.paramTree = ParameterTree(Form) + self.paramTree.setObjectName(_fromUtf8("paramTree")) + self.paramTree.headerItem().setText(0, _fromUtf8("1")) + self.paramTree.header().setVisible(False) + self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) + self.label_3 = QtGui.QLabel(Form) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) + self.copyBtn = QtGui.QPushButton(Form) + self.copyBtn.setObjectName(_fromUtf8("copyBtn")) + self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("Form", "Item to export:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("Form", "Export format", None, QtGui.QApplication.UnicodeUTF8)) + self.exportBtn.setText(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) + self.closeBtn.setText(QtGui.QApplication.translate("Form", "Close", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8)) + self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.parametertree import ParameterTree diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py new file mode 100644 index 00000000..cf27f60a --- /dev/null +++ b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './GraphicsScene/exportDialogTemplate.ui' +# +# Created: Wed Jan 30 21:02:28 2013 +# by: pyside-uic 0.2.13 running on PySide 1.1.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(241, 367) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.label = QtGui.QLabel(Form) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 3) + self.itemTree = QtGui.QTreeWidget(Form) + self.itemTree.setObjectName("itemTree") + self.itemTree.headerItem().setText(0, "1") + self.itemTree.header().setVisible(False) + self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) + self.label_2 = QtGui.QLabel(Form) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) + self.formatList = QtGui.QListWidget(Form) + self.formatList.setObjectName("formatList") + self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) + self.exportBtn = QtGui.QPushButton(Form) + self.exportBtn.setObjectName("exportBtn") + self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) + self.closeBtn = QtGui.QPushButton(Form) + self.closeBtn.setObjectName("closeBtn") + self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) + self.paramTree = ParameterTree(Form) + self.paramTree.setObjectName("paramTree") + self.paramTree.headerItem().setText(0, "1") + self.paramTree.header().setVisible(False) + self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) + self.label_3 = QtGui.QLabel(Form) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) + self.copyBtn = QtGui.QPushButton(Form) + self.copyBtn.setObjectName("copyBtn") + self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("Form", "Item to export:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("Form", "Export format", None, QtGui.QApplication.UnicodeUTF8)) + self.exportBtn.setText(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) + self.closeBtn.setText(QtGui.QApplication.translate("Form", "Close", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8)) + self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.parametertree import ParameterTree diff --git a/pyqtgraph/GraphicsScene/mouseEvents.py b/pyqtgraph/GraphicsScene/mouseEvents.py new file mode 100644 index 00000000..0b71ac6f --- /dev/null +++ b/pyqtgraph/GraphicsScene/mouseEvents.py @@ -0,0 +1,365 @@ +from pyqtgraph.Point import Point +from pyqtgraph.Qt import QtCore, QtGui +import weakref +import pyqtgraph.ptime as ptime + +class MouseDragEvent(object): + """ + Instances of this class are delivered to items in a :class:`GraphicsScene ` via their mouseDragEvent() method when the item is being mouse-dragged. + + """ + + + + def __init__(self, moveEvent, pressEvent, lastEvent, start=False, finish=False): + self.start = start + self.finish = finish + self.accepted = False + self.currentItem = None + self._buttonDownScenePos = {} + self._buttonDownScreenPos = {} + for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MidButton, QtCore.Qt.RightButton]: + self._buttonDownScenePos[int(btn)] = moveEvent.buttonDownScenePos(btn) + self._buttonDownScreenPos[int(btn)] = moveEvent.buttonDownScreenPos(btn) + self._scenePos = moveEvent.scenePos() + self._screenPos = moveEvent.screenPos() + if lastEvent is None: + self._lastScenePos = pressEvent.scenePos() + self._lastScreenPos = pressEvent.screenPos() + else: + self._lastScenePos = lastEvent.scenePos() + self._lastScreenPos = lastEvent.screenPos() + self._buttons = moveEvent.buttons() + self._button = pressEvent.button() + self._modifiers = moveEvent.modifiers() + self.acceptedItem = None + + def accept(self): + """An item should call this method if it can handle the event. This will prevent the event being delivered to any other items.""" + self.accepted = True + self.acceptedItem = self.currentItem + + def ignore(self): + """An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items.""" + self.accepted = False + + def isAccepted(self): + return self.accepted + + def scenePos(self): + """Return the current scene position of the mouse.""" + return Point(self._scenePos) + + def screenPos(self): + """Return the current screen position (pixels relative to widget) of the mouse.""" + return Point(self._screenPos) + + def buttonDownScenePos(self, btn=None): + """ + Return the scene position of the mouse at the time *btn* was pressed. + If *btn* is omitted, then the button that initiated the drag is assumed. + """ + if btn is None: + btn = self.button() + return Point(self._buttonDownScenePos[int(btn)]) + + def buttonDownScreenPos(self, btn=None): + """ + Return the screen position (pixels relative to widget) of the mouse at the time *btn* was pressed. + If *btn* is omitted, then the button that initiated the drag is assumed. + """ + if btn is None: + btn = self.button() + return Point(self._buttonDownScreenPos[int(btn)]) + + def lastScenePos(self): + """ + Return the scene position of the mouse immediately prior to this event. + """ + return Point(self._lastScenePos) + + def lastScreenPos(self): + """ + Return the screen position of the mouse immediately prior to this event. + """ + return Point(self._lastScreenPos) + + def buttons(self): + """ + Return the buttons currently pressed on the mouse. + (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) + """ + return self._buttons + + def button(self): + """Return the button that initiated the drag (may be different from the buttons currently pressed) + (see QGraphicsSceneMouseEvent::button in the Qt documentation) + + """ + return self._button + + def pos(self): + """ + Return the current position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._scenePos)) + + def lastPos(self): + """ + Return the previous position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._lastScenePos)) + + def buttonDownPos(self, btn=None): + """ + Return the position of the mouse at the time the drag was initiated + in the coordinate system of the item that the event was delivered to. + """ + if btn is None: + btn = self.button() + return Point(self.currentItem.mapFromScene(self._buttonDownScenePos[int(btn)])) + + def isStart(self): + """Returns True if this event is the first since a drag was initiated.""" + return self.start + + def isFinish(self): + """Returns False if this is the last event in a drag. Note that this + event will have the same position as the previous one.""" + return self.finish + + def __repr__(self): + lp = self.lastPos() + p = self.pos() + return "(%g,%g) buttons=%d start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isStart()), str(self.isFinish())) + + def modifiers(self): + """Return any keyboard modifiers currently pressed. + (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) + + """ + return self._modifiers + + + +class MouseClickEvent(object): + """ + Instances of this class are delivered to items in a :class:`GraphicsScene ` via their mouseClickEvent() method when the item is clicked. + + + """ + + def __init__(self, pressEvent, double=False): + self.accepted = False + self.currentItem = None + self._double = double + self._scenePos = pressEvent.scenePos() + self._screenPos = pressEvent.screenPos() + self._button = pressEvent.button() + self._buttons = pressEvent.buttons() + self._modifiers = pressEvent.modifiers() + self._time = ptime.time() + self.acceptedItem = None + + def accept(self): + """An item should call this method if it can handle the event. This will prevent the event being delivered to any other items.""" + self.accepted = True + self.acceptedItem = self.currentItem + + def ignore(self): + """An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items.""" + self.accepted = False + + def isAccepted(self): + return self.accepted + + def scenePos(self): + """Return the current scene position of the mouse.""" + return Point(self._scenePos) + + def screenPos(self): + """Return the current screen position (pixels relative to widget) of the mouse.""" + return Point(self._screenPos) + + def buttons(self): + """ + Return the buttons currently pressed on the mouse. + (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) + """ + return self._buttons + + def button(self): + """Return the mouse button that generated the click event. + (see QGraphicsSceneMouseEvent::button in the Qt documentation) + """ + return self._button + + def double(self): + """Return True if this is a double-click.""" + return self._double + + def pos(self): + """ + Return the current position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._scenePos)) + + def lastPos(self): + """ + Return the previous position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._lastScenePos)) + + def modifiers(self): + """Return any keyboard modifiers currently pressed. + (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) + """ + return self._modifiers + + def __repr__(self): + p = self.pos() + return "" % (p.x(), p.y(), int(self.button())) + + def time(self): + return self._time + + + +class HoverEvent(object): + """ + Instances of this class are delivered to items in a :class:`GraphicsScene ` via their hoverEvent() method when the mouse is hovering over the item. + This event class both informs items that the mouse cursor is nearby and allows items to + communicate with one another about whether each item will accept *potential* mouse events. + + It is common for multiple overlapping items to receive hover events and respond by changing + their appearance. This can be misleading to the user since, in general, only one item will + respond to mouse events. To avoid this, items make calls to event.acceptClicks(button) + and/or acceptDrags(button). + + Each item may make multiple calls to acceptClicks/Drags, each time for a different button. + If the method returns True, then the item is guaranteed to be + the recipient of the claimed event IF the user presses the specified mouse button before + moving. If claimEvent returns False, then this item is guaranteed NOT to get the specified + event (because another has already claimed it) and the item should change its appearance + accordingly. + + event.isEnter() returns True if the mouse has just entered the item's shape; + event.isExit() returns True if the mouse has just left. + """ + def __init__(self, moveEvent, acceptable): + self.enter = False + self.acceptable = acceptable + self.exit = False + self.__clickItems = weakref.WeakValueDictionary() + self.__dragItems = weakref.WeakValueDictionary() + self.currentItem = None + if moveEvent is not None: + self._scenePos = moveEvent.scenePos() + self._screenPos = moveEvent.screenPos() + self._lastScenePos = moveEvent.lastScenePos() + self._lastScreenPos = moveEvent.lastScreenPos() + self._buttons = moveEvent.buttons() + self._modifiers = moveEvent.modifiers() + else: + self.exit = True + + + + def isEnter(self): + """Returns True if the mouse has just entered the item's shape""" + return self.enter + + def isExit(self): + """Returns True if the mouse has just exited the item's shape""" + return self.exit + + def acceptClicks(self, button): + """Inform the scene that the item (that the event was delivered to) + would accept a mouse click event if the user were to click before + moving the mouse again. + + Returns True if the request is successful, otherwise returns False (indicating + that some other item would receive an incoming click). + """ + if not self.acceptable: + return False + if button not in self.__clickItems: + self.__clickItems[button] = self.currentItem + return True + return False + + def acceptDrags(self, button): + """Inform the scene that the item (that the event was delivered to) + would accept a mouse drag event if the user were to drag before + the next hover event. + + Returns True if the request is successful, otherwise returns False (indicating + that some other item would receive an incoming drag event). + """ + if not self.acceptable: + return False + if button not in self.__dragItems: + self.__dragItems[button] = self.currentItem + return True + return False + + def scenePos(self): + """Return the current scene position of the mouse.""" + return Point(self._scenePos) + + def screenPos(self): + """Return the current screen position of the mouse.""" + return Point(self._screenPos) + + def lastScenePos(self): + """Return the previous scene position of the mouse.""" + return Point(self._lastScenePos) + + def lastScreenPos(self): + """Return the previous screen position of the mouse.""" + return Point(self._lastScreenPos) + + def buttons(self): + """ + Return the buttons currently pressed on the mouse. + (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) + """ + return self._buttons + + def pos(self): + """ + Return the current position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._scenePos)) + + def lastPos(self): + """ + Return the previous position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._lastScenePos)) + + def __repr__(self): + lp = self.lastPos() + p = self.pos() + return "(%g,%g) buttons=%d enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isEnter()), str(self.isExit())) + + def modifiers(self): + """Return any keyboard modifiers currently pressed. + (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) + """ + return self._modifiers + + def clickItems(self): + return self.__clickItems + + def dragItems(self): + return self.__dragItems + + + \ No newline at end of file diff --git a/pyqtgraph/PIL_Fix/README b/pyqtgraph/PIL_Fix/README new file mode 100644 index 00000000..3711e113 --- /dev/null +++ b/pyqtgraph/PIL_Fix/README @@ -0,0 +1,11 @@ +The file Image.py is a drop-in replacement for the same file in PIL 1.1.6. +It adds support for reading 16-bit TIFF files and converting then to numpy arrays. +(I submitted the changes to the PIL folks long ago, but to my knowledge the code +is not being used by them.) + +To use, copy this file into + /usr/lib/python2.6/dist-packages/PIL/ +or + C:\Python26\lib\site-packages\PIL\ + +..or wherever your system keeps its python modules. diff --git a/pyqtgraph/PlotData.py b/pyqtgraph/PlotData.py new file mode 100644 index 00000000..e5faadda --- /dev/null +++ b/pyqtgraph/PlotData.py @@ -0,0 +1,56 @@ + + +class PlotData(object): + """ + Class used for managing plot data + - allows data sharing between multiple graphics items (curve, scatter, graph..) + - each item may define the columns it needs + - column groupings ('pos' or x, y, z) + - efficiently appendable + - log, fft transformations + - color mode conversion (float/byte/qcolor) + - pen/brush conversion + - per-field cached masking + - allows multiple masking fields (different graphics need to mask on different criteria) + - removal of nan/inf values + - option for single value shared by entire column + - cached downsampling + - cached min / max / hasnan / isuniform + """ + def __init__(self): + self.fields = {} + + self.maxVals = {} ## cache for max/min + self.minVals = {} + + def addFields(self, **fields): + for f in fields: + if f not in self.fields: + self.fields[f] = None + + def hasField(self, f): + return f in self.fields + + def __getitem__(self, field): + return self.fields[field] + + def __setitem__(self, field, val): + self.fields[field] = val + + def max(self, field): + mx = self.maxVals.get(field, None) + if mx is None: + mx = np.max(self[field]) + self.maxVals[field] = mx + return mx + + def min(self, field): + mn = self.minVals.get(field, None) + if mn is None: + mn = np.min(self[field]) + self.minVals[field] = mn + return mn + + + + \ No newline at end of file diff --git a/pyqtgraph/Point.py b/pyqtgraph/Point.py new file mode 100644 index 00000000..4d04f01c --- /dev/null +++ b/pyqtgraph/Point.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +""" +Point.py - Extension of QPointF which adds a few missing methods. +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from .Qt import QtCore +import numpy as np + +def clip(x, mn, mx): + if x > mx: + return mx + if x < mn: + return mn + return x + +class Point(QtCore.QPointF): + """Extension of QPointF which adds a few missing methods.""" + + def __init__(self, *args): + if len(args) == 1: + if isinstance(args[0], QtCore.QSizeF): + QtCore.QPointF.__init__(self, float(args[0].width()), float(args[0].height())) + return + elif isinstance(args[0], float) or isinstance(args[0], int): + QtCore.QPointF.__init__(self, float(args[0]), float(args[0])) + return + elif hasattr(args[0], '__getitem__'): + QtCore.QPointF.__init__(self, float(args[0][0]), float(args[0][1])) + return + elif len(args) == 2: + QtCore.QPointF.__init__(self, args[0], args[1]) + return + QtCore.QPointF.__init__(self, *args) + + def __len__(self): + return 2 + + def __reduce__(self): + return (Point, (self.x(), self.y())) + + def __getitem__(self, i): + if i == 0: + return self.x() + elif i == 1: + return self.y() + else: + raise IndexError("Point has no index %s" % str(i)) + + def __setitem__(self, i, x): + if i == 0: + return self.setX(x) + elif i == 1: + return self.setY(x) + else: + raise IndexError("Point has no index %s" % str(i)) + + def __radd__(self, a): + return self._math_('__radd__', a) + + def __add__(self, a): + return self._math_('__add__', a) + + def __rsub__(self, a): + return self._math_('__rsub__', a) + + def __sub__(self, a): + return self._math_('__sub__', a) + + def __rmul__(self, a): + return self._math_('__rmul__', a) + + def __mul__(self, a): + return self._math_('__mul__', a) + + def __rdiv__(self, a): + return self._math_('__rdiv__', a) + + def __div__(self, a): + return self._math_('__div__', a) + + def __truediv__(self, a): + return self._math_('__truediv__', a) + + def __rtruediv__(self, a): + return self._math_('__rtruediv__', a) + + def __rpow__(self, a): + return self._math_('__rpow__', a) + + def __pow__(self, a): + return self._math_('__pow__', a) + + def _math_(self, op, x): + #print "point math:", op + #try: + #fn = getattr(QtCore.QPointF, op) + #pt = fn(self, x) + #print fn, pt, self, x + #return Point(pt) + #except AttributeError: + x = Point(x) + return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1])) + + def length(self): + """Returns the vector length of this Point.""" + return (self[0]**2 + self[1]**2) ** 0.5 + + def norm(self): + """Returns a vector in the same direction with unit length.""" + return self / self.length() + + def angle(self, a): + """Returns the angle in degrees between this vector and the vector a.""" + n1 = self.length() + n2 = a.length() + if n1 == 0. or n2 == 0.: + return None + ## Probably this should be done with arctan2 instead.. + ang = np.arccos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians + c = self.cross(a) + if c > 0: + ang *= -1. + return ang * 180. / np.pi + + def dot(self, a): + """Returns the dot product of a and this Point.""" + a = Point(a) + return self[0]*a[0] + self[1]*a[1] + + def cross(self, a): + a = Point(a) + return self[0]*a[1] - self[1]*a[0] + + def proj(self, b): + """Return the projection of this vector onto the vector b""" + b1 = b / b.length() + return self.dot(b1) * b1 + + def __repr__(self): + return "Point(%f, %f)" % (self[0], self[1]) + + + def min(self): + return min(self[0], self[1]) + + def max(self): + return max(self[0], self[1]) + + def copy(self): + return Point(self) + + def toQPoint(self): + return QtCore.QPoint(*self) diff --git a/pyqtgraph/Qt.py b/pyqtgraph/Qt.py new file mode 100644 index 00000000..e584a381 --- /dev/null +++ b/pyqtgraph/Qt.py @@ -0,0 +1,48 @@ +## Do all Qt imports from here to allow easier PyQt / PySide compatibility +import sys, re + +## Automatically determine whether to use PyQt or PySide. +## This is done by first checking to see whether one of the libraries +## is already imported. If not, then attempt to import PyQt4, then PySide. +if 'PyQt4' in sys.modules: + USE_PYSIDE = False +elif 'PySide' in sys.modules: + USE_PYSIDE = True +else: + try: + import PyQt4 + USE_PYSIDE = False + except ImportError: + try: + import PySide + USE_PYSIDE = True + except ImportError: + raise Exception("PyQtGraph requires either PyQt4 or PySide; neither package could be imported.") + +if USE_PYSIDE: + from PySide import QtGui, QtCore, QtOpenGL, QtSvg + import PySide + VERSION_INFO = 'PySide ' + PySide.__version__ +else: + from PyQt4 import QtGui, QtCore + try: + from PyQt4 import QtSvg + except ImportError: + pass + try: + from PyQt4 import QtOpenGL + except ImportError: + pass + + QtCore.Signal = QtCore.pyqtSignal + VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR + + +## Make sure we have Qt >= 4.7 +versionReq = [4, 7] +QtVersion = PySide.QtCore.__version__ if USE_PYSIDE else QtCore.QT_VERSION_STR +m = re.match(r'(\d+)\.(\d+).*', QtVersion) +if m is not None and list(map(int, m.groups())) < versionReq: + print(map(int, m.groups())) + raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion)) + diff --git a/pyqtgraph/SRTTransform.py b/pyqtgraph/SRTTransform.py new file mode 100644 index 00000000..efb24f60 --- /dev/null +++ b/pyqtgraph/SRTTransform.py @@ -0,0 +1,259 @@ +# -*- coding: utf-8 -*- +from .Qt import QtCore, QtGui +from .Point import Point +import numpy as np +import pyqtgraph as pg + +class SRTTransform(QtGui.QTransform): + """Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate + This transform has no shear; angles are always preserved. + """ + def __init__(self, init=None): + QtGui.QTransform.__init__(self) + self.reset() + + if init is None: + return + elif isinstance(init, dict): + self.restoreState(init) + elif isinstance(init, SRTTransform): + self._state = { + 'pos': Point(init._state['pos']), + 'scale': Point(init._state['scale']), + 'angle': init._state['angle'] + } + self.update() + elif isinstance(init, QtGui.QTransform): + self.setFromQTransform(init) + elif isinstance(init, QtGui.QMatrix4x4): + self.setFromMatrix4x4(init) + else: + raise Exception("Cannot create SRTTransform from input type: %s" % str(type(init))) + + + def getScale(self): + return self._state['scale'] + + def getAngle(self): + ## deprecated; for backward compatibility + return self.getRotation() + + def getRotation(self): + return self._state['angle'] + + def getTranslation(self): + return self._state['pos'] + + def reset(self): + self._state = { + 'pos': Point(0,0), + 'scale': Point(1,1), + 'angle': 0.0 ## in degrees + } + self.update() + + def setFromQTransform(self, tr): + p1 = Point(tr.map(0., 0.)) + p2 = Point(tr.map(1., 0.)) + p3 = Point(tr.map(0., 1.)) + + dp2 = Point(p2-p1) + dp3 = Point(p3-p1) + + ## detect flipped axes + if dp2.angle(dp3) > 0: + #da = 180 + da = 0 + sy = -1.0 + else: + da = 0 + sy = 1.0 + + self._state = { + 'pos': Point(p1), + 'scale': Point(dp2.length(), dp3.length() * sy), + 'angle': (np.arctan2(dp2[1], dp2[0]) * 180. / np.pi) + da + } + self.update() + + def setFromMatrix4x4(self, m): + m = pg.SRTTransform3D(m) + angle, axis = m.getRotation() + if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1): + print("angle: %s axis: %s" % (str(angle), str(axis))) + raise Exception("Can only convert 4x4 matrix to 3x3 if rotation is around Z-axis.") + self._state = { + 'pos': Point(m.getTranslation()), + 'scale': Point(m.getScale()), + 'angle': angle + } + self.update() + + def translate(self, *args): + """Acceptable arguments are: + x, y + [x, y] + Point(x,y)""" + t = Point(*args) + self.setTranslate(self._state['pos']+t) + + def setTranslate(self, *args): + """Acceptable arguments are: + x, y + [x, y] + Point(x,y)""" + self._state['pos'] = Point(*args) + self.update() + + def scale(self, *args): + """Acceptable arguments are: + x, y + [x, y] + Point(x,y)""" + s = Point(*args) + self.setScale(self._state['scale'] * s) + + def setScale(self, *args): + """Acceptable arguments are: + x, y + [x, y] + Point(x,y)""" + self._state['scale'] = Point(*args) + self.update() + + def rotate(self, angle): + """Rotate the transformation by angle (in degrees)""" + self.setRotate(self._state['angle'] + angle) + + def setRotate(self, angle): + """Set the transformation rotation to angle (in degrees)""" + self._state['angle'] = angle + self.update() + + def __truediv__(self, t): + """A / B == B^-1 * A""" + dt = t.inverted()[0] * self + return SRTTransform(dt) + + def __div__(self, t): + return self.__truediv__(t) + + def __mul__(self, t): + return SRTTransform(QtGui.QTransform.__mul__(self, t)) + + def saveState(self): + p = self._state['pos'] + s = self._state['scale'] + #if s[0] == 0: + #raise Exception('Invalid scale: %s' % str(s)) + return {'pos': (p[0], p[1]), 'scale': (s[0], s[1]), 'angle': self._state['angle']} + + def restoreState(self, state): + self._state['pos'] = Point(state.get('pos', (0,0))) + self._state['scale'] = Point(state.get('scale', (1.,1.))) + self._state['angle'] = state.get('angle', 0) + self.update() + + def update(self): + QtGui.QTransform.reset(self) + ## modifications to the transform are multiplied on the right, so we need to reverse order here. + QtGui.QTransform.translate(self, *self._state['pos']) + QtGui.QTransform.rotate(self, self._state['angle']) + QtGui.QTransform.scale(self, *self._state['scale']) + + def __repr__(self): + return str(self.saveState()) + + def matrix(self): + return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]]) + +if __name__ == '__main__': + from . import widgets + import GraphicsView + from .functions import * + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + win.show() + cw = GraphicsView.GraphicsView() + #cw.enableMouse() + win.setCentralWidget(cw) + s = QtGui.QGraphicsScene() + cw.setScene(s) + win.resize(600,600) + cw.enableMouse() + cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) + + class Item(QtGui.QGraphicsItem): + def __init__(self): + QtGui.QGraphicsItem.__init__(self) + self.b = QtGui.QGraphicsRectItem(20, 20, 20, 20, self) + self.b.setPen(QtGui.QPen(mkPen('y'))) + self.t1 = QtGui.QGraphicsTextItem(self) + self.t1.setHtml('R') + self.t1.translate(20, 20) + self.l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0, self) + self.l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10, self) + self.l1.setPen(QtGui.QPen(mkPen('y'))) + self.l2.setPen(QtGui.QPen(mkPen('y'))) + def boundingRect(self): + return QtCore.QRectF() + def paint(self, *args): + pass + + #s.addItem(b) + #s.addItem(t1) + item = Item() + s.addItem(item) + l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0) + l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10) + l1.setPen(QtGui.QPen(mkPen('r'))) + l2.setPen(QtGui.QPen(mkPen('r'))) + s.addItem(l1) + s.addItem(l2) + + tr1 = SRTTransform() + tr2 = SRTTransform() + tr3 = QtGui.QTransform() + tr3.translate(20, 0) + tr3.rotate(45) + print("QTransform -> Transform:", SRTTransform(tr3)) + + print("tr1:", tr1) + + tr2.translate(20, 0) + tr2.rotate(45) + print("tr2:", tr2) + + dt = tr2/tr1 + print("tr2 / tr1 = ", dt) + + print("tr2 * tr1 = ", tr2*tr1) + + tr4 = SRTTransform() + tr4.scale(-1, 1) + tr4.rotate(30) + print("tr1 * tr4 = ", tr1*tr4) + + w1 = widgets.TestROI((19,19), (22, 22), invertible=True) + #w2 = widgets.TestROI((0,0), (150, 150)) + w1.setZValue(10) + s.addItem(w1) + #s.addItem(w2) + w1Base = w1.getState() + #w2Base = w2.getState() + def update(): + tr1 = w1.getGlobalTransform(w1Base) + #tr2 = w2.getGlobalTransform(w2Base) + item.setTransform(tr1) + + #def update2(): + #tr1 = w1.getGlobalTransform(w1Base) + #tr2 = w2.getGlobalTransform(w2Base) + #t1.setTransform(tr1) + #w1.setState(w1Base) + #w1.applyGlobalTransform(tr2) + + w1.sigRegionChanged.connect(update) + #w2.sigRegionChanged.connect(update2) + + \ No newline at end of file diff --git a/pyqtgraph/SRTTransform3D.py b/pyqtgraph/SRTTransform3D.py new file mode 100644 index 00000000..7d87dcb8 --- /dev/null +++ b/pyqtgraph/SRTTransform3D.py @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- +from .Qt import QtCore, QtGui +from .Vector import Vector +from .SRTTransform import SRTTransform +import pyqtgraph as pg +import numpy as np +import scipy.linalg + +class SRTTransform3D(pg.Transform3D): + """4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate + This transform has no shear; angles are always preserved. + """ + def __init__(self, init=None): + pg.Transform3D.__init__(self) + self.reset() + if init is None: + return + if init.__class__ is QtGui.QTransform: + init = SRTTransform(init) + + if isinstance(init, dict): + self.restoreState(init) + elif isinstance(init, SRTTransform3D): + self._state = { + 'pos': Vector(init._state['pos']), + 'scale': Vector(init._state['scale']), + 'angle': init._state['angle'], + 'axis': Vector(init._state['axis']), + } + self.update() + elif isinstance(init, SRTTransform): + self._state = { + 'pos': Vector(init._state['pos']), + 'scale': Vector(init._state['scale']), + 'angle': init._state['angle'], + 'axis': Vector(0, 0, 1), + } + self._state['scale'][2] = 1.0 + self.update() + elif isinstance(init, QtGui.QMatrix4x4): + self.setFromMatrix(init) + else: + raise Exception("Cannot build SRTTransform3D from argument type:", type(init)) + + + def getScale(self): + return pg.Vector(self._state['scale']) + + def getRotation(self): + """Return (angle, axis) of rotation""" + return self._state['angle'], pg.Vector(self._state['axis']) + + def getTranslation(self): + return pg.Vector(self._state['pos']) + + def reset(self): + self._state = { + 'pos': Vector(0,0,0), + 'scale': Vector(1,1,1), + 'angle': 0.0, ## in degrees + 'axis': (0, 0, 1) + } + self.update() + + def translate(self, *args): + """Adjust the translation of this transform""" + t = Vector(*args) + self.setTranslate(self._state['pos']+t) + + def setTranslate(self, *args): + """Set the translation of this transform""" + self._state['pos'] = Vector(*args) + self.update() + + def scale(self, *args): + """adjust the scale of this transform""" + ## try to prevent accidentally setting 0 scale on z axis + if len(args) == 1 and hasattr(args[0], '__len__'): + args = args[0] + if len(args) == 2: + args = args + (1,) + + s = Vector(*args) + self.setScale(self._state['scale'] * s) + + def setScale(self, *args): + """Set the scale of this transform""" + if len(args) == 1 and hasattr(args[0], '__len__'): + args = args[0] + if len(args) == 2: + args = args + (1,) + self._state['scale'] = Vector(*args) + self.update() + + def rotate(self, angle, axis=(0,0,1)): + """Adjust the rotation of this transform""" + origAxis = self._state['axis'] + if axis[0] == origAxis[0] and axis[1] == origAxis[1] and axis[2] == origAxis[2]: + self.setRotate(self._state['angle'] + angle) + else: + m = QtGui.QMatrix4x4() + m.translate(*self._state['pos']) + m.rotate(self._state['angle'], *self._state['axis']) + m.rotate(angle, *axis) + m.scale(*self._state['scale']) + self.setFromMatrix(m) + + def setRotate(self, angle, axis=(0,0,1)): + """Set the transformation rotation to angle (in degrees)""" + + self._state['angle'] = angle + self._state['axis'] = Vector(axis) + self.update() + + def setFromMatrix(self, m): + """ + Set this transform mased on the elements of *m* + The input matrix must be affine AND have no shear, + otherwise the conversion will most likely fail. + """ + for i in range(4): + self.setRow(i, m.row(i)) + m = self.matrix().reshape(4,4) + ## translation is 4th column + self._state['pos'] = m[:3,3] + ## scale is vector-length of first three columns + scale = (m[:3,:3]**2).sum(axis=0)**0.5 + ## see whether there is an inversion + z = np.cross(m[0, :3], m[1, :3]) + if np.dot(z, m[2, :3]) < 0: + scale[1] *= -1 ## doesn't really matter which axis we invert + self._state['scale'] = scale + + ## rotation axis is the eigenvector with eigenvalue=1 + r = m[:3, :3] / scale[:, np.newaxis] + try: + evals, evecs = scipy.linalg.eig(r) + except: + print("Rotation matrix: %s" % str(r)) + print("Scale: %s" % str(scale)) + print("Original matrix: %s" % str(m)) + raise + eigIndex = np.argwhere(np.abs(evals-1) < 1e-6) + if len(eigIndex) < 1: + print("eigenvalues: %s" % str(evals)) + print("eigenvectors: %s" % str(evecs)) + print("index: %s, %s" % (str(eigIndex), str(evals-1))) + raise Exception("Could not determine rotation axis.") + axis = evecs[:,eigIndex[0,0]].real + axis /= ((axis**2).sum())**0.5 + self._state['axis'] = axis + + ## trace(r) == 2 cos(angle) + 1, so: + cos = (r.trace()-1)*0.5 ## this only gets us abs(angle) + + ## The off-diagonal values can be used to correct the angle ambiguity, + ## but we need to figure out which element to use: + axisInd = np.argmax(np.abs(axis)) + rInd,sign = [((1,2), -1), ((0,2), 1), ((0,1), -1)][axisInd] + + ## Then we have r-r.T = sin(angle) * 2 * sign * axis[axisInd]; + ## solve for sin(angle) + sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd]) + + ## finally, we get the complete angle from arctan(sin/cos) + self._state['angle'] = np.arctan2(sin, cos) * 180 / np.pi + if self._state['angle'] == 0: + self._state['axis'] = (0,0,1) + + def as2D(self): + """Return a QTransform representing the x,y portion of this transform (if possible)""" + return pg.SRTTransform(self) + + #def __div__(self, t): + #"""A / B == B^-1 * A""" + #dt = t.inverted()[0] * self + #return SRTTransform(dt) + + #def __mul__(self, t): + #return SRTTransform(QtGui.QTransform.__mul__(self, t)) + + def saveState(self): + p = self._state['pos'] + s = self._state['scale'] + ax = self._state['axis'] + #if s[0] == 0: + #raise Exception('Invalid scale: %s' % str(s)) + return { + 'pos': (p[0], p[1], p[2]), + 'scale': (s[0], s[1], s[2]), + 'angle': self._state['angle'], + 'axis': (ax[0], ax[1], ax[2]) + } + + def restoreState(self, state): + self._state['pos'] = Vector(state.get('pos', (0.,0.,0.))) + scale = state.get('scale', (1.,1.,1.)) + scale = tuple(scale) + (1.,) * (3-len(scale)) + self._state['scale'] = Vector(scale) + self._state['angle'] = state.get('angle', 0.) + self._state['axis'] = state.get('axis', (0, 0, 1)) + self.update() + + def update(self): + pg.Transform3D.setToIdentity(self) + ## modifications to the transform are multiplied on the right, so we need to reverse order here. + pg.Transform3D.translate(self, *self._state['pos']) + pg.Transform3D.rotate(self, self._state['angle'], *self._state['axis']) + pg.Transform3D.scale(self, *self._state['scale']) + + def __repr__(self): + return str(self.saveState()) + + def matrix(self, nd=3): + if nd == 3: + return np.array(self.copyDataTo()).reshape(4,4) + elif nd == 2: + m = np.array(self.copyDataTo()).reshape(4,4) + m[2] = m[3] + m[:,2] = m[:,3] + return m[:3,:3] + else: + raise Exception("Argument 'nd' must be 2 or 3") + +if __name__ == '__main__': + import widgets + import GraphicsView + from functions import * + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + win.show() + cw = GraphicsView.GraphicsView() + #cw.enableMouse() + win.setCentralWidget(cw) + s = QtGui.QGraphicsScene() + cw.setScene(s) + win.resize(600,600) + cw.enableMouse() + cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) + + class Item(QtGui.QGraphicsItem): + def __init__(self): + QtGui.QGraphicsItem.__init__(self) + self.b = QtGui.QGraphicsRectItem(20, 20, 20, 20, self) + self.b.setPen(QtGui.QPen(mkPen('y'))) + self.t1 = QtGui.QGraphicsTextItem(self) + self.t1.setHtml('R') + self.t1.translate(20, 20) + self.l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0, self) + self.l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10, self) + self.l1.setPen(QtGui.QPen(mkPen('y'))) + self.l2.setPen(QtGui.QPen(mkPen('y'))) + def boundingRect(self): + return QtCore.QRectF() + def paint(self, *args): + pass + + #s.addItem(b) + #s.addItem(t1) + item = Item() + s.addItem(item) + l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0) + l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10) + l1.setPen(QtGui.QPen(mkPen('r'))) + l2.setPen(QtGui.QPen(mkPen('r'))) + s.addItem(l1) + s.addItem(l2) + + tr1 = SRTTransform() + tr2 = SRTTransform() + tr3 = QtGui.QTransform() + tr3.translate(20, 0) + tr3.rotate(45) + print("QTransform -> Transform: %s" % str(SRTTransform(tr3))) + + print("tr1: %s" % str(tr1)) + + tr2.translate(20, 0) + tr2.rotate(45) + print("tr2: %s" % str(tr2)) + + dt = tr2/tr1 + print("tr2 / tr1 = %s" % str(dt)) + + print("tr2 * tr1 = %s" % str(tr2*tr1)) + + tr4 = SRTTransform() + tr4.scale(-1, 1) + tr4.rotate(30) + print("tr1 * tr4 = %s" % str(tr1*tr4)) + + w1 = widgets.TestROI((19,19), (22, 22), invertible=True) + #w2 = widgets.TestROI((0,0), (150, 150)) + w1.setZValue(10) + s.addItem(w1) + #s.addItem(w2) + w1Base = w1.getState() + #w2Base = w2.getState() + def update(): + tr1 = w1.getGlobalTransform(w1Base) + #tr2 = w2.getGlobalTransform(w2Base) + item.setTransform(tr1) + + #def update2(): + #tr1 = w1.getGlobalTransform(w1Base) + #tr2 = w2.getGlobalTransform(w2Base) + #t1.setTransform(tr1) + #w1.setState(w1Base) + #w1.applyGlobalTransform(tr2) + + w1.sigRegionChanged.connect(update) + #w2.sigRegionChanged.connect(update2) + + \ No newline at end of file diff --git a/pyqtgraph/SignalProxy.py b/pyqtgraph/SignalProxy.py new file mode 100644 index 00000000..6f9b9112 --- /dev/null +++ b/pyqtgraph/SignalProxy.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +from .Qt import QtCore +from .ptime import time +from . import ThreadsafeTimer + +__all__ = ['SignalProxy'] + +class SignalProxy(QtCore.QObject): + """Object which collects rapid-fire signals and condenses them + into a single signal or a rate-limited stream of signals. + Used, for example, to prevent a SpinBox from generating multiple + signals when the mouse wheel is rolled over it. + + Emits sigDelayed after input signals have stopped for a certain period of time. + """ + + sigDelayed = QtCore.Signal(object) + + def __init__(self, signal, delay=0.3, rateLimit=0, slot=None): + """Initialization arguments: + signal - a bound Signal or pyqtSignal instance + delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s) + slot - Optional function to connect sigDelayed to. + rateLimit - (signals/sec) if greater than 0, this allows signals to stream out at a + steady rate while they are being received. + """ + + QtCore.QObject.__init__(self) + signal.connect(self.signalReceived) + self.signal = signal + self.delay = delay + self.rateLimit = rateLimit + self.args = None + self.timer = ThreadsafeTimer.ThreadsafeTimer() + self.timer.timeout.connect(self.flush) + self.block = False + self.slot = slot + self.lastFlushTime = None + if slot is not None: + self.sigDelayed.connect(slot) + + def setDelay(self, delay): + self.delay = delay + + def signalReceived(self, *args): + """Received signal. Cancel previous timer and store args to be forwarded later.""" + if self.block: + return + self.args = args + if self.rateLimit == 0: + self.timer.stop() + self.timer.start((self.delay*1000)+1) + else: + now = time() + if self.lastFlushTime is None: + leakTime = 0 + else: + lastFlush = self.lastFlushTime + leakTime = max(0, (lastFlush + (1.0 / self.rateLimit)) - now) + + self.timer.stop() + self.timer.start((min(leakTime, self.delay)*1000)+1) + + + def flush(self): + """If there is a signal queued up, send it now.""" + if self.args is None or self.block: + return False + #self.emit(self.signal, *self.args) + self.sigDelayed.emit(self.args) + self.args = None + self.timer.stop() + self.lastFlushTime = time() + return True + + def disconnect(self): + self.block = True + try: + self.signal.disconnect(self.signalReceived) + except: + pass + try: + self.sigDelayed.disconnect(self.slot) + except: + pass + + + +#def proxyConnect(source, signal, slot, delay=0.3): + #"""Connect a signal to a slot with delay. Returns the SignalProxy + #object that was created. Be sure to store this object so it is not + #garbage-collected immediately.""" + #sp = SignalProxy(source, signal, delay) + #if source is None: + #sp.connect(sp, QtCore.SIGNAL('signal'), slot) + #else: + #sp.connect(sp, signal, slot) + #return sp + + +if __name__ == '__main__': + from .Qt import QtGui + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + spin = QtGui.QSpinBox() + win.setCentralWidget(spin) + win.show() + + def fn(*args): + print("Raw signal:", args) + def fn2(*args): + print("Delayed signal:", args) + + + spin.valueChanged.connect(fn) + #proxy = proxyConnect(spin, QtCore.SIGNAL('valueChanged(int)'), fn) + proxy = SignalProxy(spin.valueChanged, delay=0.5, slot=fn2) + \ No newline at end of file diff --git a/pyqtgraph/ThreadsafeTimer.py b/pyqtgraph/ThreadsafeTimer.py new file mode 100644 index 00000000..f2de9791 --- /dev/null +++ b/pyqtgraph/ThreadsafeTimer.py @@ -0,0 +1,41 @@ +from pyqtgraph.Qt import QtCore, QtGui + +class ThreadsafeTimer(QtCore.QObject): + """ + Thread-safe replacement for QTimer. + """ + + timeout = QtCore.Signal() + sigTimerStopRequested = QtCore.Signal() + sigTimerStartRequested = QtCore.Signal(object) + + def __init__(self): + QtCore.QObject.__init__(self) + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.timerFinished) + self.timer.moveToThread(QtCore.QCoreApplication.instance().thread()) + self.moveToThread(QtCore.QCoreApplication.instance().thread()) + self.sigTimerStopRequested.connect(self.stop, QtCore.Qt.QueuedConnection) + self.sigTimerStartRequested.connect(self.start, QtCore.Qt.QueuedConnection) + + + def start(self, timeout): + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + #print "start timer", self, "from gui thread" + self.timer.start(timeout) + else: + #print "start timer", self, "from remote thread" + self.sigTimerStartRequested.emit(timeout) + + def stop(self): + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + #print "stop timer", self, "from gui thread" + self.timer.stop() + else: + #print "stop timer", self, "from remote thread" + self.sigTimerStopRequested.emit() + + def timerFinished(self): + self.timeout.emit() \ No newline at end of file diff --git a/pyqtgraph/Transform3D.py b/pyqtgraph/Transform3D.py new file mode 100644 index 00000000..aa948e28 --- /dev/null +++ b/pyqtgraph/Transform3D.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +from .Qt import QtCore, QtGui +import pyqtgraph as pg +import numpy as np + +class Transform3D(QtGui.QMatrix4x4): + """ + Extension of QMatrix4x4 with some helpful methods added. + """ + def __init__(self, *args): + QtGui.QMatrix4x4.__init__(self, *args) + + def matrix(self, nd=3): + if nd == 3: + return np.array(self.copyDataTo()).reshape(4,4) + elif nd == 2: + m = np.array(self.copyDataTo()).reshape(4,4) + m[2] = m[3] + m[:,2] = m[:,3] + return m[:3,:3] + else: + raise Exception("Argument 'nd' must be 2 or 3") + + def map(self, obj): + """ + Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates + """ + if isinstance(obj, np.ndarray) and obj.ndim >= 2 and obj.shape[0] in (2,3): + return pg.transformCoordinates(self, obj) + else: + return QtGui.QMatrix4x4.map(self, obj) + + def inverted(self): + inv, b = QtGui.QMatrix4x4.inverted(self) + return Transform3D(inv), b \ No newline at end of file diff --git a/pyqtgraph/Vector.py b/pyqtgraph/Vector.py new file mode 100644 index 00000000..4b4fb02f --- /dev/null +++ b/pyqtgraph/Vector.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +""" +Vector.py - Extension of QVector3D which adds a few missing methods. +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from .Qt import QtGui, QtCore, USE_PYSIDE +import numpy as np + +class Vector(QtGui.QVector3D): + """Extension of QVector3D which adds a few helpful methods.""" + + def __init__(self, *args): + if len(args) == 1: + if isinstance(args[0], QtCore.QSizeF): + QtGui.QVector3D.__init__(self, float(args[0].width()), float(args[0].height()), 0) + return + elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF): + QtGui.QVector3D.__init__(self, float(args[0].x()), float(args[0].y()), 0) + elif hasattr(args[0], '__getitem__'): + vals = list(args[0]) + if len(vals) == 2: + vals.append(0) + if len(vals) != 3: + raise Exception('Cannot init Vector with sequence of length %d' % len(args[0])) + QtGui.QVector3D.__init__(self, *vals) + return + elif len(args) == 2: + QtGui.QVector3D.__init__(self, args[0], args[1], 0) + return + QtGui.QVector3D.__init__(self, *args) + + def __len__(self): + return 3 + + def __add__(self, b): + # workaround for pyside bug. see https://bugs.launchpad.net/pyqtgraph/+bug/1223173 + if USE_PYSIDE and isinstance(b, QtGui.QVector3D): + b = Vector(b) + return QtGui.QVector3D.__add__(self, b) + + #def __reduce__(self): + #return (Point, (self.x(), self.y())) + + def __getitem__(self, i): + if i == 0: + return self.x() + elif i == 1: + return self.y() + elif i == 2: + return self.z() + else: + raise IndexError("Point has no index %s" % str(i)) + + def __setitem__(self, i, x): + if i == 0: + return self.setX(x) + elif i == 1: + return self.setY(x) + elif i == 2: + return self.setZ(x) + else: + raise IndexError("Point has no index %s" % str(i)) + + def __iter__(self): + yield(self.x()) + yield(self.y()) + yield(self.z()) + \ No newline at end of file diff --git a/pyqtgraph/WidgetGroup.py b/pyqtgraph/WidgetGroup.py new file mode 100644 index 00000000..29541454 --- /dev/null +++ b/pyqtgraph/WidgetGroup.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +""" +WidgetGroup.py - WidgetGroup class for easily managing lots of Qt widgets +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +This class addresses the problem of having to save and restore the state +of a large group of widgets. +""" + +from .Qt import QtCore, QtGui +import weakref, inspect +from .python2_3 import asUnicode + + +__all__ = ['WidgetGroup'] + +def splitterState(w): + s = str(w.saveState().toPercentEncoding()) + return s + +def restoreSplitter(w, s): + if type(s) is list: + w.setSizes(s) + elif type(s) is str: + w.restoreState(QtCore.QByteArray.fromPercentEncoding(s)) + else: + print("Can't configure QSplitter using object of type", type(s)) + if w.count() > 0: ## make sure at least one item is not collapsed + for i in w.sizes(): + if i > 0: + return + w.setSizes([50] * w.count()) + +def comboState(w): + ind = w.currentIndex() + data = w.itemData(ind) + #if not data.isValid(): + if data is not None: + try: + if not data.isValid(): + data = None + else: + data = data.toInt()[0] + except AttributeError: + pass + if data is None: + return asUnicode(w.itemText(ind)) + else: + return data + +def setComboState(w, v): + if type(v) is int: + #ind = w.findData(QtCore.QVariant(v)) + ind = w.findData(v) + if ind > -1: + w.setCurrentIndex(ind) + return + w.setCurrentIndex(w.findText(str(v))) + + +class WidgetGroup(QtCore.QObject): + """This class takes a list of widgets and keeps an internal record of their state which is always up to date. Allows reading and writing from groups of widgets simultaneously.""" + + ## List of widget types which can be handled by WidgetGroup. + ## The value for each type is a tuple (change signal function, get function, set function, [auto-add children]) + ## The change signal function that takes an object and returns a signal that is emitted any time the state of the widget changes, not just + ## when it is changed by user interaction. (for example, 'clicked' is not a valid signal here) + ## If the change signal is None, the value of the widget is not cached. + ## Custom widgets not in this list can be made to work with WidgetGroup by giving them a 'widgetGroupInterface' method + ## which returns the tuple. + classes = { + QtGui.QSpinBox: + (lambda w: w.valueChanged, + QtGui.QSpinBox.value, + QtGui.QSpinBox.setValue), + QtGui.QDoubleSpinBox: + (lambda w: w.valueChanged, + QtGui.QDoubleSpinBox.value, + QtGui.QDoubleSpinBox.setValue), + QtGui.QSplitter: + (None, + splitterState, + restoreSplitter, + True), + QtGui.QCheckBox: + (lambda w: w.stateChanged, + QtGui.QCheckBox.isChecked, + QtGui.QCheckBox.setChecked), + QtGui.QComboBox: + (lambda w: w.currentIndexChanged, + comboState, + setComboState), + QtGui.QGroupBox: + (lambda w: w.toggled, + QtGui.QGroupBox.isChecked, + QtGui.QGroupBox.setChecked, + True), + QtGui.QLineEdit: + (lambda w: w.editingFinished, + lambda w: str(w.text()), + QtGui.QLineEdit.setText), + QtGui.QRadioButton: + (lambda w: w.toggled, + QtGui.QRadioButton.isChecked, + QtGui.QRadioButton.setChecked), + QtGui.QSlider: + (lambda w: w.valueChanged, + QtGui.QSlider.value, + QtGui.QSlider.setValue), + } + + sigChanged = QtCore.Signal(str, object) + + + def __init__(self, widgetList=None): + """Initialize WidgetGroup, adding specified widgets into this group. + widgetList can be: + - a list of widget specifications (widget, [name], [scale]) + - a dict of name: widget pairs + - any QObject, and all compatible child widgets will be added recursively. + + The 'scale' parameter for each widget allows QSpinBox to display a different value than the value recorded + in the group state (for example, the program may set a spin box value to 100e-6 and have it displayed as 100 to the user) + """ + QtCore.QObject.__init__(self) + self.widgetList = weakref.WeakKeyDictionary() # Make sure widgets don't stick around just because they are listed here + self.scales = weakref.WeakKeyDictionary() + self.cache = {} ## name:value pairs + self.uncachedWidgets = weakref.WeakKeyDictionary() + if isinstance(widgetList, QtCore.QObject): + self.autoAdd(widgetList) + elif isinstance(widgetList, list): + for w in widgetList: + self.addWidget(*w) + elif isinstance(widgetList, dict): + for name, w in widgetList.items(): + self.addWidget(w, name) + elif widgetList is None: + return + else: + raise Exception("Wrong argument type %s" % type(widgetList)) + + def addWidget(self, w, name=None, scale=None): + if not self.acceptsType(w): + raise Exception("Widget type %s not supported by WidgetGroup" % type(w)) + if name is None: + name = str(w.objectName()) + if name == '': + raise Exception("Cannot add widget '%s' without a name." % str(w)) + self.widgetList[w] = name + self.scales[w] = scale + self.readWidget(w) + + if type(w) in WidgetGroup.classes: + signal = WidgetGroup.classes[type(w)][0] + else: + signal = w.widgetGroupInterface()[0] + + if signal is not None: + if inspect.isfunction(signal) or inspect.ismethod(signal): + signal = signal(w) + signal.connect(self.mkChangeCallback(w)) + else: + self.uncachedWidgets[w] = None + + def findWidget(self, name): + for w in self.widgetList: + if self.widgetList[w] == name: + return w + return None + + def interface(self, obj): + t = type(obj) + if t in WidgetGroup.classes: + return WidgetGroup.classes[t] + else: + return obj.widgetGroupInterface() + + def checkForChildren(self, obj): + """Return true if we should automatically search the children of this object for more.""" + iface = self.interface(obj) + return (len(iface) > 3 and iface[3]) + + def autoAdd(self, obj): + ## Find all children of this object and add them if possible. + accepted = self.acceptsType(obj) + if accepted: + #print "%s auto add %s" % (self.objectName(), obj.objectName()) + self.addWidget(obj) + + if not accepted or self.checkForChildren(obj): + for c in obj.children(): + self.autoAdd(c) + + def acceptsType(self, obj): + for c in WidgetGroup.classes: + if isinstance(obj, c): + return True + if hasattr(obj, 'widgetGroupInterface'): + return True + return False + #return (type(obj) in WidgetGroup.classes) + + def setScale(self, widget, scale): + val = self.readWidget(widget) + self.scales[widget] = scale + self.setWidget(widget, val) + #print "scaling %f to %f" % (val, self.readWidget(widget)) + + + def mkChangeCallback(self, w): + return lambda *args: self.widgetChanged(w, *args) + + def widgetChanged(self, w, *args): + #print "widget changed" + n = self.widgetList[w] + v1 = self.cache[n] + v2 = self.readWidget(w) + if v1 != v2: + #print "widget", n, " = ", v2 + self.emit(QtCore.SIGNAL('changed'), self.widgetList[w], v2) + self.sigChanged.emit(self.widgetList[w], v2) + + def state(self): + for w in self.uncachedWidgets: + self.readWidget(w) + + #cc = self.cache.copy() + #if 'averageGroup' in cc: + #val = cc['averageGroup'] + #w = self.findWidget('averageGroup') + #self.readWidget(w) + #if val != self.cache['averageGroup']: + #print " AverageGroup did not match cached value!" + #else: + #print " AverageGroup OK" + return self.cache.copy() + + def setState(self, s): + #print "SET STATE", self, s + for w in self.widgetList: + n = self.widgetList[w] + #print " restore %s?" % n + if n not in s: + continue + #print " restore state", w, n, s[n] + self.setWidget(w, s[n]) + + def readWidget(self, w): + if type(w) in WidgetGroup.classes: + getFunc = WidgetGroup.classes[type(w)][1] + else: + getFunc = w.widgetGroupInterface()[1] + + if getFunc is None: + return None + + ## if the getter function provided in the interface is a bound method, + ## then just call the method directly. Otherwise, pass in the widget as the first arg + ## to the function. + if inspect.ismethod(getFunc) and getFunc.__self__ is not None: + val = getFunc() + else: + val = getFunc(w) + + if self.scales[w] is not None: + val /= self.scales[w] + #if isinstance(val, QtCore.QString): + #val = str(val) + n = self.widgetList[w] + self.cache[n] = val + return val + + def setWidget(self, w, v): + v1 = v + if self.scales[w] is not None: + v *= self.scales[w] + + if type(w) in WidgetGroup.classes: + setFunc = WidgetGroup.classes[type(w)][2] + else: + setFunc = w.widgetGroupInterface()[2] + + ## if the setter function provided in the interface is a bound method, + ## then just call the method directly. Otherwise, pass in the widget as the first arg + ## to the function. + if inspect.ismethod(setFunc) and setFunc.__self__ is not None: + setFunc(v) + else: + setFunc(w, v) + + #name = self.widgetList[w] + #if name in self.cache and (self.cache[name] != v1): + #print "%s: Cached value %s != set value %s" % (name, str(self.cache[name]), str(v1)) + + + \ No newline at end of file diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py new file mode 100644 index 00000000..f46184b4 --- /dev/null +++ b/pyqtgraph/__init__.py @@ -0,0 +1,341 @@ +# -*- coding: utf-8 -*- +""" +PyQtGraph - Scientific Graphics and GUI Library for Python +www.pyqtgraph.org +""" + +__version__ = '0.9.8' + +### import all the goodies and add some helper functions for easy CLI use + +## 'Qt' is a local module; it is intended mainly to cover up the differences +## between PyQt4 and PySide. +from .Qt import QtGui + +## not really safe--If we accidentally create another QApplication, the process hangs (and it is very difficult to trace the cause) +#if QtGui.QApplication.instance() is None: + #app = QtGui.QApplication([]) + +import numpy ## pyqtgraph requires numpy + ## (import here to avoid massive error dump later on if numpy is not available) + +import os, sys + +## check python version +## Allow anything >= 2.7 +if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] < 6): + raise Exception("Pyqtgraph requires Python version 2.6 or greater (this is %d.%d)" % (sys.version_info[0], sys.version_info[1])) + +## helpers for 2/3 compatibility +from . import python2_3 + +## install workarounds for numpy bugs +from . import numpy_fix + +## in general openGL is poorly supported with Qt+GraphicsView. +## we only enable it where the performance benefit is critical. +## Note this only applies to 2D graphics; 3D graphics always use OpenGL. +if 'linux' in sys.platform: ## linux has numerous bugs in opengl implementation + useOpenGL = False +elif 'darwin' in sys.platform: ## openGL can have a major impact on mac, but also has serious bugs + useOpenGL = False + if QtGui.QApplication.instance() is not None: + print('Warning: QApplication was created before pyqtgraph was imported; there may be problems (to avoid bugs, call QApplication.setGraphicsSystem("raster") before the QApplication is created).') + QtGui.QApplication.setGraphicsSystem('raster') ## work around a variety of bugs in the native graphics system +else: + useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff. + +CONFIG_OPTIONS = { + 'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl. + 'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox + 'foreground': (150, 150, 150), ## default foreground color for axes, labels, etc. + 'background': (0, 0, 0), ## default background for GraphicsWidget + 'antialias': False, + 'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets + 'useWeave': True, ## Use weave to speed up some operations, if it is available + 'weaveDebug': False, ## Print full error message if weave compile fails + 'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide + 'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code) +} + + +def setConfigOption(opt, value): + CONFIG_OPTIONS[opt] = value + +def setConfigOptions(**opts): + CONFIG_OPTIONS.update(opts) + +def getConfigOption(opt): + return CONFIG_OPTIONS[opt] + + +def systemInfo(): + print("sys.platform: %s" % sys.platform) + print("sys.version: %s" % sys.version) + from .Qt import VERSION_INFO + print("qt bindings: %s" % VERSION_INFO) + + global __version__ + rev = None + if __version__ is None: ## this code was probably checked out from bzr; look up the last-revision file + lastRevFile = os.path.join(os.path.dirname(__file__), '..', '.bzr', 'branch', 'last-revision') + if os.path.exists(lastRevFile): + rev = open(lastRevFile, 'r').read().strip() + + print("pyqtgraph: %s; %s" % (__version__, rev)) + print("config:") + import pprint + pprint.pprint(CONFIG_OPTIONS) + +## Rename orphaned .pyc files. This is *probably* safe :) +## We only do this if __version__ is None, indicating the code was probably pulled +## from the repository. +def renamePyc(startDir): + ### Used to rename orphaned .pyc files + ### When a python file changes its location in the repository, usually the .pyc file + ### is left behind, possibly causing mysterious and difficult to track bugs. + + ### Note that this is no longer necessary for python 3.2; from PEP 3147: + ### "If the py source file is missing, the pyc file inside __pycache__ will be ignored. + ### This eliminates the problem of accidental stale pyc file imports." + + printed = False + startDir = os.path.abspath(startDir) + for path, dirs, files in os.walk(startDir): + if '__pycache__' in path: + continue + for f in files: + fileName = os.path.join(path, f) + base, ext = os.path.splitext(fileName) + py = base + ".py" + if ext == '.pyc' and not os.path.isfile(py): + if not printed: + print("NOTE: Renaming orphaned .pyc files:") + printed = True + n = 1 + while True: + name2 = fileName + ".renamed%d" % n + if not os.path.exists(name2): + break + n += 1 + print(" " + fileName + " ==>") + print(" " + name2) + os.rename(fileName, name2) + +path = os.path.split(__file__)[0] +if __version__ is None and not hasattr(sys, 'frozen') and sys.version_info[0] == 2: ## If we are frozen, there's a good chance we don't have the original .py files anymore. + renamePyc(path) + + +## Import almost everything to make it available from a single namespace +## don't import the more complex systems--canvas, parametertree, flowchart, dockarea +## these must be imported separately. +from . import frozenSupport +def importModules(path, globals, locals, excludes=()): + """Import all modules residing within *path*, return a dict of name: module pairs. + + Note that *path* MUST be relative to the module doing the import. + """ + d = os.path.join(os.path.split(globals['__file__'])[0], path) + files = set() + for f in frozenSupport.listdir(d): + if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']: + files.add(f) + elif f[-3:] == '.py' and f != '__init__.py': + files.add(f[:-3]) + elif f[-4:] == '.pyc' and f != '__init__.pyc': + files.add(f[:-4]) + + mods = {} + path = path.replace(os.sep, '.') + for modName in files: + if modName in excludes: + continue + try: + if len(path) > 0: + modName = path + '.' + modName + #mod = __import__(modName, globals, locals, fromlist=['*']) + mod = __import__(modName, globals, locals, ['*'], 1) + mods[modName] = mod + except: + import traceback + traceback.print_stack() + sys.excepthook(*sys.exc_info()) + print("[Error importing module: %s]" % modName) + + return mods + +def importAll(path, globals, locals, excludes=()): + """Given a list of modules, import all names from each module into the global namespace.""" + mods = importModules(path, globals, locals, excludes) + for mod in mods.values(): + if hasattr(mod, '__all__'): + names = mod.__all__ + else: + names = [n for n in dir(mod) if n[0] != '_'] + for k in names: + if hasattr(mod, k): + globals[k] = getattr(mod, k) + +importAll('graphicsItems', globals(), locals()) +importAll('widgets', globals(), locals(), + excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView']) + +from .imageview import * +from .WidgetGroup import * +from .Point import Point +from .Vector import Vector +from .SRTTransform import SRTTransform +from .Transform3D import Transform3D +from .SRTTransform3D import SRTTransform3D +from .functions import * +from .graphicsWindows import * +from .SignalProxy import * +from .colormap import * +from .ptime import time + +############################################################## +## PyQt and PySide both are prone to crashing on exit. +## There are two general approaches to dealing with this: +## 1. Install atexit handlers that assist in tearing down to avoid crashes. +## This helps, but is never perfect. +## 2. Terminate the process before python starts tearing down +## This is potentially dangerous + +## Attempts to work around exit crashes: +import atexit +def cleanup(): + if not getConfigOption('exitCleanup'): + return + + ViewBox.quit() ## tell ViewBox that it doesn't need to deregister views anymore. + + ## Workaround for Qt exit crash: + ## ALL QGraphicsItems must have a scene before they are deleted. + ## This is potentially very expensive, but preferred over crashing. + ## Note: this appears to be fixed in PySide as of 2012.12, but it should be left in for a while longer.. + if QtGui.QApplication.instance() is None: + return + import gc + s = QtGui.QGraphicsScene() + for o in gc.get_objects(): + try: + if isinstance(o, QtGui.QGraphicsItem) and o.scene() is None: + s.addItem(o) + except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object + continue +atexit.register(cleanup) + + +## Optional function for exiting immediately (with some manual teardown) +def exit(): + """ + Causes python to exit without garbage-collecting any objects, and thus avoids + calling object destructor methods. This is a sledgehammer workaround for + a variety of bugs in PyQt and Pyside that cause crashes on exit. + + This function does the following in an attempt to 'safely' terminate + the process: + + * Invoke atexit callbacks + * Close all open file handles + * os._exit() + + Note: there is some potential for causing damage with this function if you + are using objects that _require_ their destructors to be called (for example, + to properly terminate log files, disconnect from devices, etc). Situations + like this are probably quite rare, but use at your own risk. + """ + + ## first disable our own cleanup function; won't be needing it. + setConfigOptions(exitCleanup=False) + + ## invoke atexit callbacks + atexit._run_exitfuncs() + + ## close file handles + os.closerange(3, 4096) ## just guessing on the maximum descriptor count.. + + os._exit(0) + + + +## Convenience functions for command-line use + +plots = [] +images = [] +QAPP = None + +def plot(*args, **kargs): + """ + Create and return a :class:`PlotWindow ` + (this is just a window with :class:`PlotWidget ` inside), plot data in it. + Accepts a *title* argument to set the title of the window. + All other arguments are used to plot data. (see :func:`PlotItem.plot() `) + """ + mkQApp() + #if 'title' in kargs: + #w = PlotWindow(title=kargs['title']) + #del kargs['title'] + #else: + #w = PlotWindow() + #if len(args)+len(kargs) > 0: + #w.plot(*args, **kargs) + + pwArgList = ['title', 'labels', 'name', 'left', 'right', 'top', 'bottom', 'background'] + pwArgs = {} + dataArgs = {} + for k in kargs: + if k in pwArgList: + pwArgs[k] = kargs[k] + else: + dataArgs[k] = kargs[k] + + w = PlotWindow(**pwArgs) + w.plot(*args, **dataArgs) + plots.append(w) + w.show() + return w + +def image(*args, **kargs): + """ + Create and return an :class:`ImageWindow ` + (this is just a window with :class:`ImageView ` widget inside), show image data inside. + Will show 2D or 3D image data. + Accepts a *title* argument to set the title of the window. + All other arguments are used to show data. (see :func:`ImageView.setImage() `) + """ + mkQApp() + w = ImageWindow(*args, **kargs) + images.append(w) + w.show() + return w +show = image ## for backward compatibility + +def dbg(*args, **kwds): + """ + Create a console window and begin watching for exceptions. + + All arguments are passed to :func:`ConsoleWidget.__init__() `. + """ + mkQApp() + from . import console + c = console.ConsoleWidget(*args, **kwds) + c.catchAllExceptions() + c.show() + global consoles + try: + consoles.append(c) + except NameError: + consoles = [c] + + +def mkQApp(): + global QAPP + inst = QtGui.QApplication.instance() + if inst is None: + QAPP = QtGui.QApplication([]) + else: + QAPP = inst + return QAPP + diff --git a/pyqtgraph/canvas/Canvas.py b/pyqtgraph/canvas/Canvas.py new file mode 100644 index 00000000..17a39c2b --- /dev/null +++ b/pyqtgraph/canvas/Canvas.py @@ -0,0 +1,608 @@ +# -*- coding: utf-8 -*- +if __name__ == '__main__': + import sys, os + md = os.path.dirname(os.path.abspath(__file__)) + sys.path = [os.path.dirname(md), os.path.join(md, '..', '..', '..')] + sys.path + #print md + + +#from pyqtgraph.GraphicsView import GraphicsView +#import pyqtgraph.graphicsItems as graphicsItems +#from pyqtgraph.PlotWidget import PlotWidget +from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE +from pyqtgraph.graphicsItems.ROI import ROI +from pyqtgraph.graphicsItems.ViewBox import ViewBox +from pyqtgraph.graphicsItems.GridItem import GridItem + +if USE_PYSIDE: + from .CanvasTemplate_pyside import * +else: + from .CanvasTemplate_pyqt import * + +#import DataManager +import numpy as np +from pyqtgraph import debug +#import pyqtgraph as pg +import weakref +from .CanvasManager import CanvasManager +#import items +from .CanvasItem import CanvasItem, GroupCanvasItem + +class Canvas(QtGui.QWidget): + + sigSelectionChanged = QtCore.Signal(object, object) + sigItemTransformChanged = QtCore.Signal(object, object) + sigItemTransformChangeFinished = QtCore.Signal(object, object) + + def __init__(self, parent=None, allowTransforms=True, hideCtrl=False, name=None): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_Form() + self.ui.setupUi(self) + #self.view = self.ui.view + self.view = ViewBox() + self.ui.view.setCentralItem(self.view) + self.itemList = self.ui.itemList + self.itemList.setSelectionMode(self.itemList.ExtendedSelection) + self.allowTransforms = allowTransforms + self.multiSelectBox = SelectBox() + self.view.addItem(self.multiSelectBox) + self.multiSelectBox.hide() + self.multiSelectBox.setZValue(1e6) + self.ui.mirrorSelectionBtn.hide() + self.ui.reflectSelectionBtn.hide() + self.ui.resetTransformsBtn.hide() + + self.redirect = None ## which canvas to redirect items to + self.items = [] + + #self.view.enableMouse() + self.view.setAspectLocked(True) + #self.view.invertY() + + grid = GridItem() + self.grid = CanvasItem(grid, name='Grid', movable=False) + self.addItem(self.grid) + + self.hideBtn = QtGui.QPushButton('>', self) + self.hideBtn.setFixedWidth(20) + self.hideBtn.setFixedHeight(20) + self.ctrlSize = 200 + self.sizeApplied = False + self.hideBtn.clicked.connect(self.hideBtnClicked) + self.ui.splitter.splitterMoved.connect(self.splitterMoved) + + self.ui.itemList.itemChanged.connect(self.treeItemChanged) + self.ui.itemList.sigItemMoved.connect(self.treeItemMoved) + self.ui.itemList.itemSelectionChanged.connect(self.treeItemSelected) + self.ui.autoRangeBtn.clicked.connect(self.autoRange) + self.ui.storeSvgBtn.clicked.connect(self.storeSvg) + self.ui.storePngBtn.clicked.connect(self.storePng) + self.ui.redirectCheck.toggled.connect(self.updateRedirect) + self.ui.redirectCombo.currentIndexChanged.connect(self.updateRedirect) + self.multiSelectBox.sigRegionChanged.connect(self.multiSelectBoxChanged) + self.multiSelectBox.sigRegionChangeFinished.connect(self.multiSelectBoxChangeFinished) + self.ui.mirrorSelectionBtn.clicked.connect(self.mirrorSelectionClicked) + self.ui.reflectSelectionBtn.clicked.connect(self.reflectSelectionClicked) + self.ui.resetTransformsBtn.clicked.connect(self.resetTransformsClicked) + + self.resizeEvent() + if hideCtrl: + self.hideBtnClicked() + + if name is not None: + self.registeredName = CanvasManager.instance().registerCanvas(self, name) + self.ui.redirectCombo.setHostName(self.registeredName) + + self.menu = QtGui.QMenu() + #self.menu.setTitle("Image") + remAct = QtGui.QAction("Remove item", self.menu) + remAct.triggered.connect(self.removeClicked) + self.menu.addAction(remAct) + self.menu.remAct = remAct + self.ui.itemList.contextMenuEvent = self.itemListContextMenuEvent + + + def storeSvg(self): + self.ui.view.writeSvg() + + def storePng(self): + self.ui.view.writeImage() + + def splitterMoved(self): + self.resizeEvent() + + def hideBtnClicked(self): + ctrlSize = self.ui.splitter.sizes()[1] + if ctrlSize == 0: + cs = self.ctrlSize + w = self.ui.splitter.size().width() + if cs > w: + cs = w - 20 + self.ui.splitter.setSizes([w-cs, cs]) + self.hideBtn.setText('>') + else: + self.ctrlSize = ctrlSize + self.ui.splitter.setSizes([100, 0]) + self.hideBtn.setText('<') + self.resizeEvent() + + def autoRange(self): + self.view.autoRange() + + def resizeEvent(self, ev=None): + if ev is not None: + QtGui.QWidget.resizeEvent(self, ev) + self.hideBtn.move(self.ui.view.size().width() - self.hideBtn.width(), 0) + + if not self.sizeApplied: + self.sizeApplied = True + s = min(self.width(), max(100, min(200, self.width()*0.25))) + s2 = self.width()-s + self.ui.splitter.setSizes([s2, s]) + + + def updateRedirect(self, *args): + ### Decide whether/where to redirect items and make it so + cname = str(self.ui.redirectCombo.currentText()) + man = CanvasManager.instance() + if self.ui.redirectCheck.isChecked() and cname != '': + redirect = man.getCanvas(cname) + else: + redirect = None + + if self.redirect is redirect: + return + + self.redirect = redirect + if redirect is None: + self.reclaimItems() + else: + self.redirectItems(redirect) + + + def redirectItems(self, canvas): + for i in self.items: + if i is self.grid: + continue + li = i.listItem + parent = li.parent() + if parent is None: + tree = li.treeWidget() + if tree is None: + print("Skipping item", i, i.name) + continue + tree.removeTopLevelItem(li) + else: + parent.removeChild(li) + canvas.addItem(i) + + + def reclaimItems(self): + items = self.items + #self.items = {'Grid': items['Grid']} + #del items['Grid'] + self.items = [self.grid] + items.remove(self.grid) + + for i in items: + i.canvas.removeItem(i) + self.addItem(i) + + def treeItemChanged(self, item, col): + #gi = self.items.get(item.name, None) + #if gi is None: + #return + try: + citem = item.canvasItem() + except AttributeError: + return + if item.checkState(0) == QtCore.Qt.Checked: + for i in range(item.childCount()): + item.child(i).setCheckState(0, QtCore.Qt.Checked) + citem.show() + else: + for i in range(item.childCount()): + item.child(i).setCheckState(0, QtCore.Qt.Unchecked) + citem.hide() + + def treeItemSelected(self): + sel = self.selectedItems() + #sel = [] + #for listItem in self.itemList.selectedItems(): + #if hasattr(listItem, 'canvasItem') and listItem.canvasItem is not None: + #sel.append(listItem.canvasItem) + #sel = [self.items[item.name] for item in sel] + + if len(sel) == 0: + #self.selectWidget.hide() + return + + multi = len(sel) > 1 + for i in self.items: + #i.ctrlWidget().hide() + ## updated the selected state of every item + i.selectionChanged(i in sel, multi) + + if len(sel)==1: + #item = sel[0] + #item.ctrlWidget().show() + self.multiSelectBox.hide() + self.ui.mirrorSelectionBtn.hide() + self.ui.reflectSelectionBtn.hide() + self.ui.resetTransformsBtn.hide() + elif len(sel) > 1: + self.showMultiSelectBox() + + #if item.isMovable(): + #self.selectBox.setPos(item.item.pos()) + #self.selectBox.setSize(item.item.sceneBoundingRect().size()) + #self.selectBox.show() + #else: + #self.selectBox.hide() + + #self.emit(QtCore.SIGNAL('itemSelected'), self, item) + self.sigSelectionChanged.emit(self, sel) + + def selectedItems(self): + """ + Return list of all selected canvasItems + """ + return [item.canvasItem() for item in self.itemList.selectedItems() if item.canvasItem() is not None] + + #def selectedItem(self): + #sel = self.itemList.selectedItems() + #if sel is None or len(sel) < 1: + #return + #return self.items.get(sel[0].name, None) + + def selectItem(self, item): + li = item.listItem + #li = self.getListItem(item.name()) + #print "select", li + self.itemList.setCurrentItem(li) + + + + def showMultiSelectBox(self): + ## Get list of selected canvas items + items = self.selectedItems() + + rect = self.view.itemBoundingRect(items[0].graphicsItem()) + for i in items: + if not i.isMovable(): ## all items in selection must be movable + return + br = self.view.itemBoundingRect(i.graphicsItem()) + rect = rect|br + + self.multiSelectBox.blockSignals(True) + self.multiSelectBox.setPos([rect.x(), rect.y()]) + self.multiSelectBox.setSize(rect.size()) + self.multiSelectBox.setAngle(0) + self.multiSelectBox.blockSignals(False) + + self.multiSelectBox.show() + + self.ui.mirrorSelectionBtn.show() + self.ui.reflectSelectionBtn.show() + self.ui.resetTransformsBtn.show() + #self.multiSelectBoxBase = self.multiSelectBox.getState().copy() + + def mirrorSelectionClicked(self): + for ci in self.selectedItems(): + ci.mirrorY() + self.showMultiSelectBox() + + def reflectSelectionClicked(self): + for ci in self.selectedItems(): + ci.mirrorXY() + self.showMultiSelectBox() + + def resetTransformsClicked(self): + for i in self.selectedItems(): + i.resetTransformClicked() + self.showMultiSelectBox() + + def multiSelectBoxChanged(self): + self.multiSelectBoxMoved() + + def multiSelectBoxChangeFinished(self): + for ci in self.selectedItems(): + ci.applyTemporaryTransform() + ci.sigTransformChangeFinished.emit(ci) + + def multiSelectBoxMoved(self): + transform = self.multiSelectBox.getGlobalTransform() + for ci in self.selectedItems(): + ci.setTemporaryTransform(transform) + ci.sigTransformChanged.emit(ci) + + + def addGraphicsItem(self, item, **opts): + """Add a new GraphicsItem to the scene at pos. + Common options are name, pos, scale, and z + """ + citem = CanvasItem(item, **opts) + item._canvasItem = citem + self.addItem(citem) + return citem + + + def addGroup(self, name, **kargs): + group = GroupCanvasItem(name=name) + self.addItem(group, **kargs) + return group + + + def addItem(self, citem): + """ + Add an item to the canvas. + """ + + ## Check for redirections + if self.redirect is not None: + name = self.redirect.addItem(citem) + self.items.append(citem) + return name + + if not self.allowTransforms: + citem.setMovable(False) + + citem.sigTransformChanged.connect(self.itemTransformChanged) + citem.sigTransformChangeFinished.connect(self.itemTransformChangeFinished) + citem.sigVisibilityChanged.connect(self.itemVisibilityChanged) + + + ## Determine name to use in the item list + name = citem.opts['name'] + if name is None: + name = 'item' + newname = name + + ## If name already exists, append a number to the end + ## NAH. Let items have the same name if they really want. + #c=0 + #while newname in self.items: + #c += 1 + #newname = name + '_%03d' %c + #name = newname + + ## find parent and add item to tree + #currentNode = self.itemList.invisibleRootItem() + insertLocation = 0 + #print "Inserting node:", name + + + ## determine parent list item where this item should be inserted + parent = citem.parentItem() + if parent in (None, self.view.childGroup): + parent = self.itemList.invisibleRootItem() + else: + parent = parent.listItem + + ## set Z value above all other siblings if none was specified + siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())] + z = citem.zValue() + if z is None: + zvals = [i.zValue() for i in siblings] + if parent == self.itemList.invisibleRootItem(): + if len(zvals) == 0: + z = 0 + else: + z = max(zvals)+10 + else: + if len(zvals) == 0: + z = parent.canvasItem().zValue() + else: + z = max(zvals)+1 + citem.setZValue(z) + + ## determine location to insert item relative to its siblings + for i in range(parent.childCount()): + ch = parent.child(i) + zval = ch.canvasItem().graphicsItem().zValue() ## should we use CanvasItem.zValue here? + if zval < z: + insertLocation = i + break + else: + insertLocation = i+1 + + node = QtGui.QTreeWidgetItem([name]) + flags = node.flags() | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsDragEnabled + if not isinstance(citem, GroupCanvasItem): + flags = flags & ~QtCore.Qt.ItemIsDropEnabled + node.setFlags(flags) + if citem.opts['visible']: + node.setCheckState(0, QtCore.Qt.Checked) + else: + node.setCheckState(0, QtCore.Qt.Unchecked) + + node.name = name + #if citem.opts['parent'] != None: + ## insertLocation is incorrect in this case + parent.insertChild(insertLocation, node) + #else: + #root.insertChild(insertLocation, node) + + citem.name = name + citem.listItem = node + node.canvasItem = weakref.ref(citem) + self.items.append(citem) + + ctrl = citem.ctrlWidget() + ctrl.hide() + self.ui.ctrlLayout.addWidget(ctrl) + + ## inform the canvasItem that its parent canvas has changed + citem.setCanvas(self) + + ## Autoscale to fit the first item added (not including the grid). + if len(self.items) == 2: + self.autoRange() + + + #for n in name: + #nextnode = None + #for x in range(currentNode.childCount()): + #ch = currentNode.child(x) + #if hasattr(ch, 'name'): ## check Z-value of current item to determine insert location + #zval = ch.canvasItem.zValue() + #if zval > z: + ###print " ->", x + #insertLocation = x+1 + #if n == ch.text(0): + #nextnode = ch + #break + #if nextnode is None: ## If name doesn't exist, create it + #nextnode = QtGui.QTreeWidgetItem([n]) + #nextnode.setFlags((nextnode.flags() | QtCore.Qt.ItemIsUserCheckable) & ~QtCore.Qt.ItemIsDropEnabled) + #nextnode.setCheckState(0, QtCore.Qt.Checked) + ### Add node to correct position in list by Z-value + ###print " ==>", insertLocation + #currentNode.insertChild(insertLocation, nextnode) + + #if n == name[-1]: ## This is the leaf; add some extra properties. + #nextnode.name = name + + #if n == name[0]: ## This is the root; make the item movable + #nextnode.setFlags(nextnode.flags() | QtCore.Qt.ItemIsDragEnabled) + #else: + #nextnode.setFlags(nextnode.flags() & ~QtCore.Qt.ItemIsDragEnabled) + + #currentNode = nextnode + return citem + + def treeItemMoved(self, item, parent, index): + ##Item moved in tree; update Z values + if parent is self.itemList.invisibleRootItem(): + item.canvasItem().setParentItem(self.view.childGroup) + else: + item.canvasItem().setParentItem(parent.canvasItem()) + siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())] + + zvals = [i.zValue() for i in siblings] + zvals.sort(reverse=True) + + for i in range(len(siblings)): + item = siblings[i] + item.setZValue(zvals[i]) + #item = self.itemList.topLevelItem(i) + + ##ci = self.items[item.name] + #ci = item.canvasItem + #if ci is None: + #continue + #if ci.zValue() != zvals[i]: + #ci.setZValue(zvals[i]) + + #if self.itemList.topLevelItemCount() < 2: + #return + #name = item.name + #gi = self.items[name] + #if index == 0: + #next = self.itemList.topLevelItem(1) + #z = self.items[next.name].zValue()+1 + #else: + #prev = self.itemList.topLevelItem(index-1) + #z = self.items[prev.name].zValue()-1 + #gi.setZValue(z) + + + + + + + def itemVisibilityChanged(self, item): + listItem = item.listItem + checked = listItem.checkState(0) == QtCore.Qt.Checked + vis = item.isVisible() + if vis != checked: + if vis: + listItem.setCheckState(0, QtCore.Qt.Checked) + else: + listItem.setCheckState(0, QtCore.Qt.Unchecked) + + def removeItem(self, item): + if isinstance(item, QtGui.QTreeWidgetItem): + item = item.canvasItem() + + + if isinstance(item, CanvasItem): + item.setCanvas(None) + listItem = item.listItem + listItem.canvasItem = None + item.listItem = None + self.itemList.removeTopLevelItem(listItem) + self.items.remove(item) + ctrl = item.ctrlWidget() + ctrl.hide() + self.ui.ctrlLayout.removeWidget(ctrl) + else: + if hasattr(item, '_canvasItem'): + self.removeItem(item._canvasItem) + else: + self.view.removeItem(item) + + ## disconnect signals, remove from list, etc.. + + def clear(self): + while len(self.items) > 0: + self.removeItem(self.items[0]) + + + def addToScene(self, item): + self.view.addItem(item) + + def removeFromScene(self, item): + self.view.removeItem(item) + + + def listItems(self): + """Return a dictionary of name:item pairs""" + return self.items + + def getListItem(self, name): + return self.items[name] + + #def scene(self): + #return self.view.scene() + + def itemTransformChanged(self, item): + #self.emit(QtCore.SIGNAL('itemTransformChanged'), self, item) + self.sigItemTransformChanged.emit(self, item) + + def itemTransformChangeFinished(self, item): + #self.emit(QtCore.SIGNAL('itemTransformChangeFinished'), self, item) + self.sigItemTransformChangeFinished.emit(self, item) + + def itemListContextMenuEvent(self, ev): + self.menuItem = self.itemList.itemAt(ev.pos()) + self.menu.popup(ev.globalPos()) + + def removeClicked(self): + self.removeItem(self.menuItem) + self.menuItem = None + import gc + gc.collect() + +class SelectBox(ROI): + def __init__(self, scalable=False): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, [0,0], [1,1]) + center = [0.5, 0.5] + + if scalable: + self.addScaleHandle([1, 1], center, lockAspect=True) + self.addScaleHandle([0, 0], center, lockAspect=True) + self.addRotateHandle([0, 1], center) + self.addRotateHandle([1, 0], center) + + + + + + + + + + + \ No newline at end of file diff --git a/pyqtgraph/canvas/CanvasItem.py b/pyqtgraph/canvas/CanvasItem.py new file mode 100644 index 00000000..81388cb6 --- /dev/null +++ b/pyqtgraph/canvas/CanvasItem.py @@ -0,0 +1,509 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE +from pyqtgraph.graphicsItems.ROI import ROI +import pyqtgraph as pg +if USE_PYSIDE: + from . import TransformGuiTemplate_pyside as TransformGuiTemplate +else: + from . import TransformGuiTemplate_pyqt as TransformGuiTemplate + +from pyqtgraph import debug + +class SelectBox(ROI): + def __init__(self, scalable=False, rotatable=True): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, [0,0], [1,1], invertible=True) + center = [0.5, 0.5] + + if scalable: + self.addScaleHandle([1, 1], center, lockAspect=True) + self.addScaleHandle([0, 0], center, lockAspect=True) + if rotatable: + self.addRotateHandle([0, 1], center) + self.addRotateHandle([1, 0], center) + +class CanvasItem(QtCore.QObject): + + sigResetUserTransform = QtCore.Signal(object) + sigTransformChangeFinished = QtCore.Signal(object) + sigTransformChanged = QtCore.Signal(object) + + """CanvasItem takes care of managing an item's state--alpha, visibility, z-value, transformations, etc. and + provides a control widget""" + + sigVisibilityChanged = QtCore.Signal(object) + transformCopyBuffer = None + + def __init__(self, item, **opts): + defOpts = {'name': None, 'z': None, 'movable': True, 'scalable': False, 'rotatable': True, 'visible': True, 'parent':None} #'pos': [0,0], 'scale': [1,1], 'angle':0, + defOpts.update(opts) + self.opts = defOpts + self.selectedAlone = False ## whether this item is the only one selected + + QtCore.QObject.__init__(self) + self.canvas = None + self._graphicsItem = item + + parent = self.opts['parent'] + if parent is not None: + self._graphicsItem.setParentItem(parent.graphicsItem()) + self._parentItem = parent + else: + self._parentItem = None + + z = self.opts['z'] + if z is not None: + item.setZValue(z) + + self.ctrl = QtGui.QWidget() + self.layout = QtGui.QGridLayout() + self.layout.setSpacing(0) + self.layout.setContentsMargins(0,0,0,0) + self.ctrl.setLayout(self.layout) + + self.alphaLabel = QtGui.QLabel("Alpha") + self.alphaSlider = QtGui.QSlider() + self.alphaSlider.setMaximum(1023) + self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.alphaSlider.setValue(1023) + self.layout.addWidget(self.alphaLabel, 0, 0) + self.layout.addWidget(self.alphaSlider, 0, 1) + self.resetTransformBtn = QtGui.QPushButton('Reset Transform') + self.copyBtn = QtGui.QPushButton('Copy') + self.pasteBtn = QtGui.QPushButton('Paste') + + self.transformWidget = QtGui.QWidget() + self.transformGui = TransformGuiTemplate.Ui_Form() + self.transformGui.setupUi(self.transformWidget) + self.layout.addWidget(self.transformWidget, 3, 0, 1, 2) + self.transformGui.mirrorImageBtn.clicked.connect(self.mirrorY) + self.transformGui.reflectImageBtn.clicked.connect(self.mirrorXY) + + self.layout.addWidget(self.resetTransformBtn, 1, 0, 1, 2) + self.layout.addWidget(self.copyBtn, 2, 0, 1, 1) + self.layout.addWidget(self.pasteBtn, 2, 1, 1, 1) + self.alphaSlider.valueChanged.connect(self.alphaChanged) + self.alphaSlider.sliderPressed.connect(self.alphaPressed) + self.alphaSlider.sliderReleased.connect(self.alphaReleased) + #self.canvas.sigSelectionChanged.connect(self.selectionChanged) + self.resetTransformBtn.clicked.connect(self.resetTransformClicked) + self.copyBtn.clicked.connect(self.copyClicked) + self.pasteBtn.clicked.connect(self.pasteClicked) + + self.setMovable(self.opts['movable']) ## update gui to reflect this option + + + if 'transform' in self.opts: + self.baseTransform = self.opts['transform'] + else: + self.baseTransform = pg.SRTTransform() + if 'pos' in self.opts and self.opts['pos'] is not None: + self.baseTransform.translate(self.opts['pos']) + if 'angle' in self.opts and self.opts['angle'] is not None: + self.baseTransform.rotate(self.opts['angle']) + if 'scale' in self.opts and self.opts['scale'] is not None: + self.baseTransform.scale(self.opts['scale']) + + ## create selection box (only visible when selected) + tr = self.baseTransform.saveState() + if 'scalable' not in opts and tr['scale'] == (1,1): + self.opts['scalable'] = True + + ## every CanvasItem implements its own individual selection box + ## so that subclasses are free to make their own. + self.selectBox = SelectBox(scalable=self.opts['scalable'], rotatable=self.opts['rotatable']) + #self.canvas.scene().addItem(self.selectBox) + self.selectBox.hide() + self.selectBox.setZValue(1e6) + self.selectBox.sigRegionChanged.connect(self.selectBoxChanged) ## calls selectBoxMoved + self.selectBox.sigRegionChangeFinished.connect(self.selectBoxChangeFinished) + + ## set up the transformations that will be applied to the item + ## (It is not safe to use item.setTransform, since the item might count on that not changing) + self.itemRotation = QtGui.QGraphicsRotation() + self.itemScale = QtGui.QGraphicsScale() + self._graphicsItem.setTransformations([self.itemRotation, self.itemScale]) + + self.tempTransform = pg.SRTTransform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done. + self.userTransform = pg.SRTTransform() ## stores the total transform of the object + self.resetUserTransform() + + ## now happens inside resetUserTransform -> selectBoxToItem + # self.selectBoxBase = self.selectBox.getState().copy() + + + #print "Created canvas item", self + #print " base:", self.baseTransform + #print " user:", self.userTransform + #print " temp:", self.tempTransform + #print " bounds:", self.item.sceneBoundingRect() + + def setMovable(self, m): + self.opts['movable'] = m + + if m: + self.resetTransformBtn.show() + self.copyBtn.show() + self.pasteBtn.show() + else: + self.resetTransformBtn.hide() + self.copyBtn.hide() + self.pasteBtn.hide() + + def setCanvas(self, canvas): + ## Called by canvas whenever the item is added. + ## It is our responsibility to add all graphicsItems to the canvas's scene + ## The canvas will automatically add our graphicsitem, + ## so we just need to take care of the selectbox. + if canvas is self.canvas: + return + + if canvas is None: + self.canvas.removeFromScene(self._graphicsItem) + self.canvas.removeFromScene(self.selectBox) + else: + canvas.addToScene(self._graphicsItem) + canvas.addToScene(self.selectBox) + self.canvas = canvas + + def graphicsItem(self): + """Return the graphicsItem for this canvasItem.""" + return self._graphicsItem + + def parentItem(self): + return self._parentItem + + def setParentItem(self, parent): + self._parentItem = parent + if parent is not None: + if isinstance(parent, CanvasItem): + parent = parent.graphicsItem() + self.graphicsItem().setParentItem(parent) + + #def name(self): + #return self.opts['name'] + + def copyClicked(self): + CanvasItem.transformCopyBuffer = self.saveTransform() + + def pasteClicked(self): + t = CanvasItem.transformCopyBuffer + if t is None: + return + else: + self.restoreTransform(t) + + def mirrorY(self): + if not self.isMovable(): + return + + #flip = self.transformGui.mirrorImageCheck.isChecked() + #tr = self.userTransform.saveState() + + inv = pg.SRTTransform() + inv.scale(-1, 1) + self.userTransform = self.userTransform * inv + self.updateTransform() + self.selectBoxFromUser() + self.sigTransformChangeFinished.emit(self) + #if flip: + #if tr['scale'][0] < 0 xor tr['scale'][1] < 0: + #return + #else: + #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) + #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) + #self.userTransform.setRotate(-tr['angle']) + #self.updateTransform() + #self.selectBoxFromUser() + #return + #elif not flip: + #if tr['scale'][0] > 0 and tr['scale'][1] > 0: + #return + #else: + #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) + #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) + #self.userTransform.setRotate(-tr['angle']) + #self.updateTransform() + #self.selectBoxFromUser() + #return + + def mirrorXY(self): + if not self.isMovable(): + return + self.rotate(180.) + # inv = pg.SRTTransform() + # inv.scale(-1, -1) + # self.userTransform = self.userTransform * inv #flip lr/ud + # s=self.updateTransform() + # self.setTranslate(-2*s['pos'][0], -2*s['pos'][1]) + # self.selectBoxFromUser() + + + def hasUserTransform(self): + #print self.userRotate, self.userTranslate + return not self.userTransform.isIdentity() + + def ctrlWidget(self): + return self.ctrl + + def alphaChanged(self, val): + alpha = val / 1023. + self._graphicsItem.setOpacity(alpha) + + def isMovable(self): + return self.opts['movable'] + + + def selectBoxMoved(self): + """The selection box has moved; get its transformation information and pass to the graphics item""" + self.userTransform = self.selectBox.getGlobalTransform(relativeTo=self.selectBoxBase) + self.updateTransform() + + def scale(self, x, y): + self.userTransform.scale(x, y) + self.selectBoxFromUser() + self.updateTransform() + + def rotate(self, ang): + self.userTransform.rotate(ang) + self.selectBoxFromUser() + self.updateTransform() + + def translate(self, x, y): + self.userTransform.translate(x, y) + self.selectBoxFromUser() + self.updateTransform() + + def setTranslate(self, x, y): + self.userTransform.setTranslate(x, y) + self.selectBoxFromUser() + self.updateTransform() + + def setRotate(self, angle): + self.userTransform.setRotate(angle) + self.selectBoxFromUser() + self.updateTransform() + + def setScale(self, x, y): + self.userTransform.setScale(x, y) + self.selectBoxFromUser() + self.updateTransform() + + + def setTemporaryTransform(self, transform): + self.tempTransform = transform + self.updateTransform() + + def applyTemporaryTransform(self): + """Collapses tempTransform into UserTransform, resets tempTransform""" + self.userTransform = self.userTransform * self.tempTransform ## order is important! + self.resetTemporaryTransform() + self.selectBoxFromUser() ## update the selection box to match the new userTransform + + #st = self.userTransform.saveState() + + #self.userTransform = self.userTransform * self.tempTransform ## order is important! + + #### matrix multiplication affects the scale factors, need to reset + #if st['scale'][0] < 0 or st['scale'][1] < 0: + #nst = self.userTransform.saveState() + #self.userTransform.setScale([-nst['scale'][0], -nst['scale'][1]]) + + #self.resetTemporaryTransform() + #self.selectBoxFromUser() + #self.selectBoxChangeFinished() + + + + def resetTemporaryTransform(self): + self.tempTransform = pg.SRTTransform() ## don't use Transform.reset()--this transform might be used elsewhere. + self.updateTransform() + + def transform(self): + return self._graphicsItem.transform() + + def updateTransform(self): + """Regenerate the item position from the base, user, and temp transforms""" + transform = self.baseTransform * self.userTransform * self.tempTransform ## order is important + s = transform.saveState() + self._graphicsItem.setPos(*s['pos']) + + self.itemRotation.setAngle(s['angle']) + self.itemScale.setXScale(s['scale'][0]) + self.itemScale.setYScale(s['scale'][1]) + + self.displayTransform(transform) + return(s) # return the transform state + + def displayTransform(self, transform): + """Updates transform numbers in the ctrl widget.""" + + tr = transform.saveState() + + self.transformGui.translateLabel.setText("Translate: (%f, %f)" %(tr['pos'][0], tr['pos'][1])) + self.transformGui.rotateLabel.setText("Rotate: %f degrees" %tr['angle']) + self.transformGui.scaleLabel.setText("Scale: (%f, %f)" %(tr['scale'][0], tr['scale'][1])) + #self.transformGui.mirrorImageCheck.setChecked(False) + #if tr['scale'][0] < 0: + # self.transformGui.mirrorImageCheck.setChecked(True) + + + def resetUserTransform(self): + #self.userRotate = 0 + #self.userTranslate = pg.Point(0,0) + self.userTransform.reset() + self.updateTransform() + + self.selectBox.blockSignals(True) + self.selectBoxToItem() + self.selectBox.blockSignals(False) + self.sigTransformChanged.emit(self) + self.sigTransformChangeFinished.emit(self) + + def resetTransformClicked(self): + self.resetUserTransform() + self.sigResetUserTransform.emit(self) + + def restoreTransform(self, tr): + try: + #self.userTranslate = pg.Point(tr['trans']) + #self.userRotate = tr['rot'] + self.userTransform = pg.SRTTransform(tr) + self.updateTransform() + + self.selectBoxFromUser() ## move select box to match + self.sigTransformChanged.emit(self) + self.sigTransformChangeFinished.emit(self) + except: + #self.userTranslate = pg.Point([0,0]) + #self.userRotate = 0 + self.userTransform = pg.SRTTransform() + debug.printExc("Failed to load transform:") + #print "set transform", self, self.userTranslate + + def saveTransform(self): + """Return a dict containing the current user transform""" + #print "save transform", self, self.userTranslate + #return {'trans': list(self.userTranslate), 'rot': self.userRotate} + return self.userTransform.saveState() + + def selectBoxFromUser(self): + """Move the selection box to match the current userTransform""" + ## user transform + #trans = QtGui.QTransform() + #trans.translate(*self.userTranslate) + #trans.rotate(-self.userRotate) + + #x2, y2 = trans.map(*self.selectBoxBase['pos']) + + self.selectBox.blockSignals(True) + self.selectBox.setState(self.selectBoxBase) + self.selectBox.applyGlobalTransform(self.userTransform) + #self.selectBox.setAngle(self.userRotate) + #self.selectBox.setPos([x2, y2]) + self.selectBox.blockSignals(False) + + + def selectBoxToItem(self): + """Move/scale the selection box so it fits the item's bounding rect. (assumes item is not rotated)""" + self.itemRect = self._graphicsItem.boundingRect() + rect = self._graphicsItem.mapRectToParent(self.itemRect) + self.selectBox.blockSignals(True) + self.selectBox.setPos([rect.x(), rect.y()]) + self.selectBox.setSize(rect.size()) + self.selectBox.setAngle(0) + self.selectBoxBase = self.selectBox.getState().copy() + self.selectBox.blockSignals(False) + + def zValue(self): + return self.opts['z'] + + def setZValue(self, z): + self.opts['z'] = z + if z is not None: + self._graphicsItem.setZValue(z) + + #def selectionChanged(self, canvas, items): + #self.selected = len(items) == 1 and (items[0] is self) + #self.showSelectBox() + + + def selectionChanged(self, sel, multi): + """ + Inform the item that its selection state has changed. + Arguments: + sel: bool, whether the item is currently selected + multi: bool, whether there are multiple items currently selected + """ + self.selectedAlone = sel and not multi + self.showSelectBox() + if self.selectedAlone: + self.ctrlWidget().show() + else: + self.ctrlWidget().hide() + + def showSelectBox(self): + """Display the selection box around this item if it is selected and movable""" + if self.selectedAlone and self.isMovable() and self.isVisible(): #and len(self.canvas.itemList.selectedItems())==1: + self.selectBox.show() + else: + self.selectBox.hide() + + def hideSelectBox(self): + self.selectBox.hide() + + + def selectBoxChanged(self): + self.selectBoxMoved() + #self.updateTransform(self.selectBox) + #self.emit(QtCore.SIGNAL('transformChanged'), self) + self.sigTransformChanged.emit(self) + + def selectBoxChangeFinished(self): + #self.emit(QtCore.SIGNAL('transformChangeFinished'), self) + self.sigTransformChangeFinished.emit(self) + + def alphaPressed(self): + """Hide selection box while slider is moving""" + self.hideSelectBox() + + def alphaReleased(self): + self.showSelectBox() + + def show(self): + if self.opts['visible']: + return + self.opts['visible'] = True + self._graphicsItem.show() + self.showSelectBox() + self.sigVisibilityChanged.emit(self) + + def hide(self): + if not self.opts['visible']: + return + self.opts['visible'] = False + self._graphicsItem.hide() + self.hideSelectBox() + self.sigVisibilityChanged.emit(self) + + def setVisible(self, vis): + if vis: + self.show() + else: + self.hide() + + def isVisible(self): + return self.opts['visible'] + + +class GroupCanvasItem(CanvasItem): + """ + Canvas item used for grouping others + """ + + def __init__(self, **opts): + defOpts = {'movable': False, 'scalable': False} + defOpts.update(opts) + item = pg.ItemGroup() + CanvasItem.__init__(self, item, **defOpts) + diff --git a/pyqtgraph/canvas/CanvasManager.py b/pyqtgraph/canvas/CanvasManager.py new file mode 100644 index 00000000..e89ec00f --- /dev/null +++ b/pyqtgraph/canvas/CanvasManager.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +if not hasattr(QtCore, 'Signal'): + QtCore.Signal = QtCore.pyqtSignal +import weakref + +class CanvasManager(QtCore.QObject): + SINGLETON = None + + sigCanvasListChanged = QtCore.Signal() + + def __init__(self): + if CanvasManager.SINGLETON is not None: + raise Exception("Can only create one canvas manager.") + CanvasManager.SINGLETON = self + QtCore.QObject.__init__(self) + self.canvases = weakref.WeakValueDictionary() + + @classmethod + def instance(cls): + return CanvasManager.SINGLETON + + def registerCanvas(self, canvas, name): + n2 = name + i = 0 + while n2 in self.canvases: + n2 = "%s_%03d" % (name, i) + i += 1 + self.canvases[n2] = canvas + self.sigCanvasListChanged.emit() + return n2 + + def unregisterCanvas(self, name): + c = self.canvases[name] + del self.canvases[name] + self.sigCanvasListChanged.emit() + + def listCanvases(self): + return list(self.canvases.keys()) + + def getCanvas(self, name): + return self.canvases[name] + + +manager = CanvasManager() + + +class CanvasCombo(QtGui.QComboBox): + def __init__(self, parent=None): + QtGui.QComboBox.__init__(self, parent) + man = CanvasManager.instance() + man.sigCanvasListChanged.connect(self.updateCanvasList) + self.hostName = None + self.updateCanvasList() + + def updateCanvasList(self): + canvases = CanvasManager.instance().listCanvases() + canvases.insert(0, "") + if self.hostName in canvases: + canvases.remove(self.hostName) + + sel = self.currentText() + if sel in canvases: + self.blockSignals(True) ## change does not affect current selection; block signals during update + self.clear() + for i in canvases: + self.addItem(i) + if i == sel: + self.setCurrentIndex(self.count()) + + self.blockSignals(False) + + def setHostName(self, name): + self.hostName = name + self.updateCanvasList() + diff --git a/pyqtgraph/canvas/CanvasTemplate.ui b/pyqtgraph/canvas/CanvasTemplate.ui new file mode 100644 index 00000000..da032906 --- /dev/null +++ b/pyqtgraph/canvas/CanvasTemplate.ui @@ -0,0 +1,149 @@ + + + Form + + + + 0 + 0 + 490 + 414 + + + + Form + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + Store SVG + + + + + + + Store PNG + + + + + + + + 0 + 1 + + + + Auto Range + + + + + + + 0 + + + + + Check to display all local items in a remote canvas. + + + Redirect + + + + + + + + + + + + + 0 + 100 + + + + true + + + + 1 + + + + + + + + 0 + + + + + + + Reset Transforms + + + + + + + Mirror Selection + + + + + + + MirrorXY + + + + + + + + + + + + TreeWidget + QTreeWidget +
pyqtgraph.widgets.TreeWidget
+
+ + GraphicsView + QGraphicsView +
pyqtgraph.widgets.GraphicsView
+
+ + CanvasCombo + QComboBox +
CanvasManager
+
+
+ + +
diff --git a/pyqtgraph/canvas/CanvasTemplate_pyqt.py b/pyqtgraph/canvas/CanvasTemplate_pyqt.py new file mode 100644 index 00000000..4d1d8208 --- /dev/null +++ b/pyqtgraph/canvas/CanvasTemplate_pyqt.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './canvas/CanvasTemplate.ui' +# +# Created: Sun Sep 9 14:41:30 2012 +# by: PyQt4 UI code generator 4.9.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(490, 414) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName(_fromUtf8("splitter")) + self.view = GraphicsView(self.splitter) + self.view.setObjectName(_fromUtf8("view")) + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) + self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget) + self.gridLayout_2.setMargin(0) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.storeSvgBtn = QtGui.QPushButton(self.layoutWidget) + self.storeSvgBtn.setObjectName(_fromUtf8("storeSvgBtn")) + self.gridLayout_2.addWidget(self.storeSvgBtn, 1, 0, 1, 1) + self.storePngBtn = QtGui.QPushButton(self.layoutWidget) + self.storePngBtn.setObjectName(_fromUtf8("storePngBtn")) + self.gridLayout_2.addWidget(self.storePngBtn, 1, 1, 1, 1) + self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) + self.autoRangeBtn.setSizePolicy(sizePolicy) + self.autoRangeBtn.setObjectName(_fromUtf8("autoRangeBtn")) + self.gridLayout_2.addWidget(self.autoRangeBtn, 3, 0, 1, 2) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.redirectCheck = QtGui.QCheckBox(self.layoutWidget) + self.redirectCheck.setObjectName(_fromUtf8("redirectCheck")) + self.horizontalLayout.addWidget(self.redirectCheck) + self.redirectCombo = CanvasCombo(self.layoutWidget) + self.redirectCombo.setObjectName(_fromUtf8("redirectCombo")) + self.horizontalLayout.addWidget(self.redirectCombo) + self.gridLayout_2.addLayout(self.horizontalLayout, 6, 0, 1, 2) + self.itemList = TreeWidget(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(100) + sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) + self.itemList.setSizePolicy(sizePolicy) + self.itemList.setHeaderHidden(True) + self.itemList.setObjectName(_fromUtf8("itemList")) + self.itemList.headerItem().setText(0, _fromUtf8("1")) + self.gridLayout_2.addWidget(self.itemList, 7, 0, 1, 2) + self.ctrlLayout = QtGui.QGridLayout() + self.ctrlLayout.setSpacing(0) + self.ctrlLayout.setObjectName(_fromUtf8("ctrlLayout")) + self.gridLayout_2.addLayout(self.ctrlLayout, 11, 0, 1, 2) + self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget) + self.resetTransformsBtn.setObjectName(_fromUtf8("resetTransformsBtn")) + self.gridLayout_2.addWidget(self.resetTransformsBtn, 8, 0, 1, 1) + self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget) + self.mirrorSelectionBtn.setObjectName(_fromUtf8("mirrorSelectionBtn")) + self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) + self.reflectSelectionBtn = QtGui.QPushButton(self.layoutWidget) + self.reflectSelectionBtn.setObjectName(_fromUtf8("reflectSelectionBtn")) + self.gridLayout_2.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.storeSvgBtn.setText(QtGui.QApplication.translate("Form", "Store SVG", None, QtGui.QApplication.UnicodeUTF8)) + self.storePngBtn.setText(QtGui.QApplication.translate("Form", "Store PNG", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8)) + self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8)) + self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8)) + self.resetTransformsBtn.setText(QtGui.QApplication.translate("Form", "Reset Transforms", None, QtGui.QApplication.UnicodeUTF8)) + self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8)) + self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.widgets.GraphicsView import GraphicsView +from CanvasManager import CanvasCombo +from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/canvas/CanvasTemplate_pyside.py b/pyqtgraph/canvas/CanvasTemplate_pyside.py new file mode 100644 index 00000000..12afdf25 --- /dev/null +++ b/pyqtgraph/canvas/CanvasTemplate_pyside.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './canvas/CanvasTemplate.ui' +# +# Created: Sun Sep 9 14:41:30 2012 +# by: pyside-uic 0.2.13 running on PySide 1.1.0 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(490, 414) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName("splitter") + self.view = GraphicsView(self.splitter) + self.view.setObjectName("view") + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName("layoutWidget") + self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.storeSvgBtn = QtGui.QPushButton(self.layoutWidget) + self.storeSvgBtn.setObjectName("storeSvgBtn") + self.gridLayout_2.addWidget(self.storeSvgBtn, 1, 0, 1, 1) + self.storePngBtn = QtGui.QPushButton(self.layoutWidget) + self.storePngBtn.setObjectName("storePngBtn") + self.gridLayout_2.addWidget(self.storePngBtn, 1, 1, 1, 1) + self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) + self.autoRangeBtn.setSizePolicy(sizePolicy) + self.autoRangeBtn.setObjectName("autoRangeBtn") + self.gridLayout_2.addWidget(self.autoRangeBtn, 3, 0, 1, 2) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.redirectCheck = QtGui.QCheckBox(self.layoutWidget) + self.redirectCheck.setObjectName("redirectCheck") + self.horizontalLayout.addWidget(self.redirectCheck) + self.redirectCombo = CanvasCombo(self.layoutWidget) + self.redirectCombo.setObjectName("redirectCombo") + self.horizontalLayout.addWidget(self.redirectCombo) + self.gridLayout_2.addLayout(self.horizontalLayout, 6, 0, 1, 2) + self.itemList = TreeWidget(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(100) + sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) + self.itemList.setSizePolicy(sizePolicy) + self.itemList.setHeaderHidden(True) + self.itemList.setObjectName("itemList") + self.itemList.headerItem().setText(0, "1") + self.gridLayout_2.addWidget(self.itemList, 7, 0, 1, 2) + self.ctrlLayout = QtGui.QGridLayout() + self.ctrlLayout.setSpacing(0) + self.ctrlLayout.setObjectName("ctrlLayout") + self.gridLayout_2.addLayout(self.ctrlLayout, 11, 0, 1, 2) + self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget) + self.resetTransformsBtn.setObjectName("resetTransformsBtn") + self.gridLayout_2.addWidget(self.resetTransformsBtn, 8, 0, 1, 1) + self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget) + self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") + self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) + self.reflectSelectionBtn = QtGui.QPushButton(self.layoutWidget) + self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") + self.gridLayout_2.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.storeSvgBtn.setText(QtGui.QApplication.translate("Form", "Store SVG", None, QtGui.QApplication.UnicodeUTF8)) + self.storePngBtn.setText(QtGui.QApplication.translate("Form", "Store PNG", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8)) + self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8)) + self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8)) + self.resetTransformsBtn.setText(QtGui.QApplication.translate("Form", "Reset Transforms", None, QtGui.QApplication.UnicodeUTF8)) + self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8)) + self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.widgets.GraphicsView import GraphicsView +from CanvasManager import CanvasCombo +from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/canvas/TransformGuiTemplate.ui b/pyqtgraph/canvas/TransformGuiTemplate.ui new file mode 100644 index 00000000..d8312388 --- /dev/null +++ b/pyqtgraph/canvas/TransformGuiTemplate.ui @@ -0,0 +1,75 @@ + + + Form + + + + 0 + 0 + 224 + 117 + + + + + 0 + 0 + + + + Form + + + + 1 + + + 0 + + + + + Translate: + + + + + + + Rotate: + + + + + + + Scale: + + + + + + + + + + + + Mirror + + + + + + + Reflect + + + + + + + + + + diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py b/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py new file mode 100644 index 00000000..1fb86d24 --- /dev/null +++ b/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './canvas/TransformGuiTemplate.ui' +# +# Created: Sun Sep 9 14:41:30 2012 +# by: PyQt4 UI code generator 4.9.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(224, 117) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + self.verticalLayout = QtGui.QVBoxLayout(Form) + self.verticalLayout.setSpacing(1) + self.verticalLayout.setMargin(0) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.translateLabel = QtGui.QLabel(Form) + self.translateLabel.setObjectName(_fromUtf8("translateLabel")) + self.verticalLayout.addWidget(self.translateLabel) + self.rotateLabel = QtGui.QLabel(Form) + self.rotateLabel.setObjectName(_fromUtf8("rotateLabel")) + self.verticalLayout.addWidget(self.rotateLabel) + self.scaleLabel = QtGui.QLabel(Form) + self.scaleLabel.setObjectName(_fromUtf8("scaleLabel")) + self.verticalLayout.addWidget(self.scaleLabel) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.mirrorImageBtn = QtGui.QPushButton(Form) + self.mirrorImageBtn.setToolTip(_fromUtf8("")) + self.mirrorImageBtn.setObjectName(_fromUtf8("mirrorImageBtn")) + self.horizontalLayout.addWidget(self.mirrorImageBtn) + self.reflectImageBtn = QtGui.QPushButton(Form) + self.reflectImageBtn.setObjectName(_fromUtf8("reflectImageBtn")) + self.horizontalLayout.addWidget(self.reflectImageBtn) + self.verticalLayout.addLayout(self.horizontalLayout) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.translateLabel.setText(QtGui.QApplication.translate("Form", "Translate:", None, QtGui.QApplication.UnicodeUTF8)) + self.rotateLabel.setText(QtGui.QApplication.translate("Form", "Rotate:", None, QtGui.QApplication.UnicodeUTF8)) + self.scaleLabel.setText(QtGui.QApplication.translate("Form", "Scale:", None, QtGui.QApplication.UnicodeUTF8)) + self.mirrorImageBtn.setText(QtGui.QApplication.translate("Form", "Mirror", None, QtGui.QApplication.UnicodeUTF8)) + self.reflectImageBtn.setText(QtGui.QApplication.translate("Form", "Reflect", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyside.py b/pyqtgraph/canvas/TransformGuiTemplate_pyside.py new file mode 100644 index 00000000..47b23faa --- /dev/null +++ b/pyqtgraph/canvas/TransformGuiTemplate_pyside.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './canvas/TransformGuiTemplate.ui' +# +# Created: Sun Sep 9 14:41:30 2012 +# by: pyside-uic 0.2.13 running on PySide 1.1.0 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(224, 117) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + self.verticalLayout = QtGui.QVBoxLayout(Form) + self.verticalLayout.setSpacing(1) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.translateLabel = QtGui.QLabel(Form) + self.translateLabel.setObjectName("translateLabel") + self.verticalLayout.addWidget(self.translateLabel) + self.rotateLabel = QtGui.QLabel(Form) + self.rotateLabel.setObjectName("rotateLabel") + self.verticalLayout.addWidget(self.rotateLabel) + self.scaleLabel = QtGui.QLabel(Form) + self.scaleLabel.setObjectName("scaleLabel") + self.verticalLayout.addWidget(self.scaleLabel) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.mirrorImageBtn = QtGui.QPushButton(Form) + self.mirrorImageBtn.setToolTip("") + self.mirrorImageBtn.setObjectName("mirrorImageBtn") + self.horizontalLayout.addWidget(self.mirrorImageBtn) + self.reflectImageBtn = QtGui.QPushButton(Form) + self.reflectImageBtn.setObjectName("reflectImageBtn") + self.horizontalLayout.addWidget(self.reflectImageBtn) + self.verticalLayout.addLayout(self.horizontalLayout) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.translateLabel.setText(QtGui.QApplication.translate("Form", "Translate:", None, QtGui.QApplication.UnicodeUTF8)) + self.rotateLabel.setText(QtGui.QApplication.translate("Form", "Rotate:", None, QtGui.QApplication.UnicodeUTF8)) + self.scaleLabel.setText(QtGui.QApplication.translate("Form", "Scale:", None, QtGui.QApplication.UnicodeUTF8)) + self.mirrorImageBtn.setText(QtGui.QApplication.translate("Form", "Mirror", None, QtGui.QApplication.UnicodeUTF8)) + self.reflectImageBtn.setText(QtGui.QApplication.translate("Form", "Reflect", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/pyqtgraph/canvas/__init__.py b/pyqtgraph/canvas/__init__.py new file mode 100644 index 00000000..f649d0a1 --- /dev/null +++ b/pyqtgraph/canvas/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from .Canvas import * +from .CanvasItem import * \ No newline at end of file diff --git a/pyqtgraph/colormap.py b/pyqtgraph/colormap.py new file mode 100644 index 00000000..d6169209 --- /dev/null +++ b/pyqtgraph/colormap.py @@ -0,0 +1,239 @@ +import numpy as np +import scipy.interpolate +from pyqtgraph.Qt import QtGui, QtCore + +class ColorMap(object): + """ + A ColorMap defines a relationship between a scalar value and a range of colors. + ColorMaps are commonly used for false-coloring monochromatic images, coloring + scatter-plot points, and coloring surface plots by height. + + Each color map is defined by a set of colors, each corresponding to a + particular scalar value. For example: + + | 0.0 -> black + | 0.2 -> red + | 0.6 -> yellow + | 1.0 -> white + + The colors for intermediate values are determined by interpolating between + the two nearest colors in either RGB or HSV color space. + + To provide user-defined color mappings, see :class:`GradientWidget `. + """ + + + ## color interpolation modes + RGB = 1 + HSV_POS = 2 + HSV_NEG = 3 + + ## boundary modes + CLIP = 1 + REPEAT = 2 + MIRROR = 3 + + ## return types + BYTE = 1 + FLOAT = 2 + QCOLOR = 3 + + enumMap = { + 'rgb': RGB, + 'hsv+': HSV_POS, + 'hsv-': HSV_NEG, + 'clip': CLIP, + 'repeat': REPEAT, + 'mirror': MIRROR, + 'byte': BYTE, + 'float': FLOAT, + 'qcolor': QCOLOR, + } + + def __init__(self, pos, color, mode=None): + """ + ========= ============================================================== + Arguments + pos Array of positions where each color is defined + color Array of RGBA colors. + Integer data types are interpreted as 0-255; float data types + are interpreted as 0.0-1.0 + mode Array of color modes (ColorMap.RGB, HSV_POS, or HSV_NEG) + indicating the color space that should be used when + interpolating between stops. Note that the last mode value is + ignored. By default, the mode is entirely RGB. + ========= ============================================================== + """ + self.pos = pos + self.color = color + if mode is None: + mode = np.ones(len(pos)) + self.mode = mode + self.stopsCache = {} + + def map(self, data, mode='byte'): + """ + Return an array of colors corresponding to the values in *data*. + Data must be either a scalar position or an array (any shape) of positions. + + The *mode* argument determines the type of data returned: + + =========== =============================================================== + byte (default) Values are returned as 0-255 unsigned bytes. + float Values are returned as 0.0-1.0 floats. + qcolor Values are returned as an array of QColor objects. + =========== =============================================================== + """ + if isinstance(mode, basestring): + mode = self.enumMap[mode.lower()] + + if mode == self.QCOLOR: + pos, color = self.getStops(self.BYTE) + else: + pos, color = self.getStops(mode) + + data = np.clip(data, pos.min(), pos.max()) + + if not isinstance(data, np.ndarray): + interp = scipy.interpolate.griddata(pos, color, np.array([data]))[0] + else: + interp = scipy.interpolate.griddata(pos, color, data) + + if mode == self.QCOLOR: + if not isinstance(data, np.ndarray): + return QtGui.QColor(*interp) + else: + return [QtGui.QColor(*x) for x in interp] + else: + return interp + + def mapToQColor(self, data): + """Convenience function; see :func:`map() `.""" + return self.map(data, mode=self.QCOLOR) + + def mapToByte(self, data): + """Convenience function; see :func:`map() `.""" + return self.map(data, mode=self.BYTE) + + def mapToFloat(self, data): + """Convenience function; see :func:`map() `.""" + return self.map(data, mode=self.FLOAT) + + def getGradient(self, p1=None, p2=None): + """Return a QLinearGradient object spanning from QPoints p1 to p2.""" + if p1 == None: + p1 = QtCore.QPointF(0,0) + if p2 == None: + p2 = QtCore.QPointF(self.pos.max()-self.pos.min(),0) + g = QtGui.QLinearGradient(p1, p2) + + pos, color = self.getStops(mode=self.BYTE) + color = [QtGui.QColor(*x) for x in color] + g.setStops(zip(pos, color)) + + #if self.colorMode == 'rgb': + #ticks = self.listTicks() + #g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) + #elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop + #ticks = self.listTicks() + #stops = [] + #stops.append((ticks[0][1], ticks[0][0].color)) + #for i in range(1,len(ticks)): + #x1 = ticks[i-1][1] + #x2 = ticks[i][1] + #dx = (x2-x1) / 10. + #for j in range(1,10): + #x = x1 + dx*j + #stops.append((x, self.getColor(x))) + #stops.append((x2, self.getColor(x2))) + #g.setStops(stops) + return g + + def getColors(self, mode=None): + """Return list of all color stops converted to the specified mode. + If mode is None, then no conversion is done.""" + if isinstance(mode, basestring): + mode = self.enumMap[mode.lower()] + + color = self.color + if mode in [self.BYTE, self.QCOLOR] and color.dtype.kind == 'f': + color = (color * 255).astype(np.ubyte) + elif mode == self.FLOAT and color.dtype.kind != 'f': + color = color.astype(float) / 255. + + if mode == self.QCOLOR: + color = [QtGui.QColor(*x) for x in color] + + return color + + def getStops(self, mode): + ## Get fully-expanded set of RGBA stops in either float or byte mode. + if mode not in self.stopsCache: + color = self.color + if mode == self.BYTE and color.dtype.kind == 'f': + color = (color * 255).astype(np.ubyte) + elif mode == self.FLOAT and color.dtype.kind != 'f': + color = color.astype(float) / 255. + + ## to support HSV mode, we need to do a little more work.. + #stops = [] + #for i in range(len(self.pos)): + #pos = self.pos[i] + #color = color[i] + + #imode = self.mode[i] + #if imode == self.RGB: + #stops.append((x,color)) + #else: + #ns = + self.stopsCache[mode] = (self.pos, color) + return self.stopsCache[mode] + + def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode='byte'): + """ + Return an RGB(A) lookup table (ndarray). + + ============= ============================================================================ + **Arguments** + start The starting value in the lookup table (default=0.0) + stop The final value in the lookup table (default=1.0) + nPts The number of points in the returned lookup table. + alpha True, False, or None - Specifies whether or not alpha values are included + in the table. If alpha is None, it will be automatically determined. + mode Determines return type: 'byte' (0-255), 'float' (0.0-1.0), or 'qcolor'. + See :func:`map() `. + ============= ============================================================================ + """ + if isinstance(mode, basestring): + mode = self.enumMap[mode.lower()] + + if alpha is None: + alpha = self.usesAlpha() + + x = np.linspace(start, stop, nPts) + table = self.map(x, mode) + + if not alpha: + return table[:,:3] + else: + return table + + def usesAlpha(self): + """Return True if any stops have an alpha < 255""" + max = 1.0 if self.color.dtype.kind == 'f' else 255 + return np.any(self.color[:,3] != max) + + def isMapTrivial(self): + """ + Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0. + """ + if len(self.pos) != 2: + return False + if self.pos[0] != 0.0 or self.pos[1] != 1.0: + return False + if self.color.dtype.kind == 'f': + return np.all(self.color == np.array([[0.,0.,0.,1.], [1.,1.,1.,1.]])) + else: + return np.all(self.color == np.array([[0,0,0,255], [255,255,255,255]])) + + diff --git a/pyqtgraph/configfile.py b/pyqtgraph/configfile.py new file mode 100644 index 00000000..f709c786 --- /dev/null +++ b/pyqtgraph/configfile.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- +""" +configfile.py - Human-readable text configuration file library +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +Used for reading and writing dictionary objects to a python-like configuration +file format. Data structures may be nested and contain any data type as long +as it can be converted to/from a string using repr and eval. +""" + +import re, os, sys +from .pgcollections import OrderedDict +GLOBAL_PATH = None # so not thread safe. +from . import units +from .python2_3 import asUnicode + +class ParseError(Exception): + def __init__(self, message, lineNum, line, fileName=None): + self.lineNum = lineNum + self.line = line + #self.message = message + self.fileName = fileName + Exception.__init__(self, message) + + def __str__(self): + if self.fileName is None: + msg = "Error parsing string at line %d:\n" % self.lineNum + else: + msg = "Error parsing config file '%s' at line %d:\n" % (self.fileName, self.lineNum) + msg += "%s\n%s" % (self.line, self.message) + return msg + #raise Exception() + + +def writeConfigFile(data, fname): + s = genString(data) + fd = open(fname, 'w') + fd.write(s) + fd.close() + +def readConfigFile(fname): + #cwd = os.getcwd() + global GLOBAL_PATH + if GLOBAL_PATH is not None: + fname2 = os.path.join(GLOBAL_PATH, fname) + if os.path.exists(fname2): + fname = fname2 + + GLOBAL_PATH = os.path.dirname(os.path.abspath(fname)) + + try: + #os.chdir(newDir) ## bad. + fd = open(fname) + s = asUnicode(fd.read()) + fd.close() + s = s.replace("\r\n", "\n") + s = s.replace("\r", "\n") + data = parseString(s)[1] + except ParseError: + sys.exc_info()[1].fileName = fname + raise + except: + print("Error while reading config file %s:"% fname) + raise + #finally: + #os.chdir(cwd) + return data + +def appendConfigFile(data, fname): + s = genString(data) + fd = open(fname, 'a') + fd.write(s) + fd.close() + + +def genString(data, indent=''): + s = '' + for k in data: + sk = str(k) + if len(sk) == 0: + print(data) + raise Exception('blank dict keys not allowed (see data above)') + if sk[0] == ' ' or ':' in sk: + print(data) + raise Exception('dict keys must not contain ":" or start with spaces [offending key is "%s"]' % sk) + if isinstance(data[k], dict): + s += indent + sk + ':\n' + s += genString(data[k], indent + ' ') + else: + s += indent + sk + ': ' + repr(data[k]) + '\n' + return s + +def parseString(lines, start=0): + + data = OrderedDict() + if isinstance(lines, basestring): + lines = lines.split('\n') + lines = [l for l in lines if re.search(r'\S', l) and not re.match(r'\s*#', l)] ## remove empty lines + + indent = measureIndent(lines[start]) + ln = start - 1 + + try: + while True: + ln += 1 + #print ln + if ln >= len(lines): + break + + l = lines[ln] + + ## Skip blank lines or lines starting with # + if re.match(r'\s*#', l) or not re.search(r'\S', l): + continue + + ## Measure line indentation, make sure it is correct for this level + lineInd = measureIndent(l) + if lineInd < indent: + ln -= 1 + break + if lineInd > indent: + #print lineInd, indent + raise ParseError('Indentation is incorrect. Expected %d, got %d' % (indent, lineInd), ln+1, l) + + + if ':' not in l: + raise ParseError('Missing colon', ln+1, l) + + (k, p, v) = l.partition(':') + k = k.strip() + v = v.strip() + + ## set up local variables to use for eval + local = units.allUnits.copy() + local['OrderedDict'] = OrderedDict + local['readConfigFile'] = readConfigFile + if len(k) < 1: + raise ParseError('Missing name preceding colon', ln+1, l) + if k[0] == '(' and k[-1] == ')': ## If the key looks like a tuple, try evaluating it. + try: + k1 = eval(k, local) + if type(k1) is tuple: + k = k1 + except: + pass + if re.search(r'\S', v) and v[0] != '#': ## eval the value + try: + val = eval(v, local) + except: + ex = sys.exc_info()[1] + raise ParseError("Error evaluating expression '%s': [%s: %s]" % (v, ex.__class__.__name__, str(ex)), (ln+1), l) + else: + if ln+1 >= len(lines) or measureIndent(lines[ln+1]) <= indent: + #print "blank dict" + val = {} + else: + #print "Going deeper..", ln+1 + (ln, val) = parseString(lines, start=ln+1) + data[k] = val + #print k, repr(val) + except ParseError: + raise + except: + ex = sys.exc_info()[1] + raise ParseError("%s: %s" % (ex.__class__.__name__, str(ex)), ln+1, l) + #print "Returning shallower..", ln+1 + return (ln, data) + +def measureIndent(s): + n = 0 + while n < len(s) and s[n] == ' ': + n += 1 + return n + + + +if __name__ == '__main__': + import tempfile + fn = tempfile.mktemp() + tf = open(fn, 'w') + cf = """ +key: 'value' +key2: ##comment + ##comment + key21: 'value' ## comment + ##comment + key22: [1,2,3] + key23: 234 #comment + """ + tf.write(cf) + tf.close() + print("=== Test:===") + num = 1 + for line in cf.split('\n'): + print("%02d %s" % (num, line)) + num += 1 + print(cf) + print("============") + data = readConfigFile(fn) + print(data) + os.remove(fn) diff --git a/pyqtgraph/console/CmdInput.py b/pyqtgraph/console/CmdInput.py new file mode 100644 index 00000000..3e9730d6 --- /dev/null +++ b/pyqtgraph/console/CmdInput.py @@ -0,0 +1,62 @@ +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.python2_3 import asUnicode + +class CmdInput(QtGui.QLineEdit): + + sigExecuteCmd = QtCore.Signal(object) + + def __init__(self, parent): + QtGui.QLineEdit.__init__(self, parent) + self.history = [""] + self.ptr = 0 + #self.lastCmd = None + #self.setMultiline(False) + + def keyPressEvent(self, ev): + #print "press:", ev.key(), QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_Enter + if ev.key() == QtCore.Qt.Key_Up and self.ptr < len(self.history) - 1: + self.setHistory(self.ptr+1) + ev.accept() + return + elif ev.key() == QtCore.Qt.Key_Down and self.ptr > 0: + self.setHistory(self.ptr-1) + ev.accept() + return + elif ev.key() == QtCore.Qt.Key_Return: + self.execCmd() + else: + QtGui.QLineEdit.keyPressEvent(self, ev) + self.history[0] = asUnicode(self.text()) + + def execCmd(self): + cmd = asUnicode(self.text()) + if len(self.history) == 1 or cmd != self.history[1]: + self.history.insert(1, cmd) + #self.lastCmd = cmd + self.history[0] = "" + self.setHistory(0) + self.sigExecuteCmd.emit(cmd) + + def setHistory(self, num): + self.ptr = num + self.setText(self.history[self.ptr]) + + #def setMultiline(self, m): + #height = QtGui.QFontMetrics(self.font()).lineSpacing() + #if m: + #self.setFixedHeight(height*5) + #else: + #self.setFixedHeight(height+15) + #self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + #self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + + #def sizeHint(self): + #hint = QtGui.QPlainTextEdit.sizeHint(self) + #height = QtGui.QFontMetrics(self.font()).lineSpacing() + #hint.setHeight(height) + #return hint + + + + \ No newline at end of file diff --git a/pyqtgraph/console/Console.py b/pyqtgraph/console/Console.py new file mode 100644 index 00000000..982c2424 --- /dev/null +++ b/pyqtgraph/console/Console.py @@ -0,0 +1,375 @@ + +from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE +import sys, re, os, time, traceback, subprocess +import pyqtgraph as pg +if USE_PYSIDE: + from . import template_pyside as template +else: + from . import template_pyqt as template + +import pyqtgraph.exceptionHandling as exceptionHandling +import pickle + +class ConsoleWidget(QtGui.QWidget): + """ + Widget displaying console output and accepting command input. + Implements: + + - eval python expressions / exec python statements + - storable history of commands + - exception handling allowing commands to be interpreted in the context of any level in the exception stack frame + + Why not just use python in an interactive shell (or ipython) ? There are a few reasons: + + - pyside does not yet allow Qt event processing and interactive shell at the same time + - on some systems, typing in the console _blocks_ the qt event loop until the user presses enter. This can + be baffling and frustrating to users since it would appear the program has frozen. + - some terminals (eg windows cmd.exe) have notoriously unfriendly interfaces + - ability to add extra features like exception stack introspection + - ability to have multiple interactive prompts, including for spawned sub-processes + """ + + def __init__(self, parent=None, namespace=None, historyFile=None, text=None, editor=None): + """ + ============ ============================================================================ + Arguments: + namespace dictionary containing the initial variables present in the default namespace + historyFile optional file for storing command history + text initial text to display in the console window + editor optional string for invoking code editor (called when stack trace entries are + double-clicked). May contain {fileName} and {lineNum} format keys. Example:: + + editorCommand --loadfile {fileName} --gotoline {lineNum} + ============ ============================================================================= + """ + QtGui.QWidget.__init__(self, parent) + if namespace is None: + namespace = {} + self.localNamespace = namespace + self.editor = editor + self.multiline = None + self.inCmd = False + + self.ui = template.Ui_Form() + self.ui.setupUi(self) + self.output = self.ui.output + self.input = self.ui.input + self.input.setFocus() + + if text is not None: + self.output.setPlainText(text) + + self.historyFile = historyFile + + history = self.loadHistory() + if history is not None: + self.input.history = [""] + history + self.ui.historyList.addItems(history[::-1]) + self.ui.historyList.hide() + self.ui.exceptionGroup.hide() + + self.input.sigExecuteCmd.connect(self.runCmd) + self.ui.historyBtn.toggled.connect(self.ui.historyList.setVisible) + self.ui.historyList.itemClicked.connect(self.cmdSelected) + self.ui.historyList.itemDoubleClicked.connect(self.cmdDblClicked) + self.ui.exceptionBtn.toggled.connect(self.ui.exceptionGroup.setVisible) + + self.ui.catchAllExceptionsBtn.toggled.connect(self.catchAllExceptions) + self.ui.catchNextExceptionBtn.toggled.connect(self.catchNextException) + self.ui.clearExceptionBtn.clicked.connect(self.clearExceptionClicked) + self.ui.exceptionStackList.itemClicked.connect(self.stackItemClicked) + self.ui.exceptionStackList.itemDoubleClicked.connect(self.stackItemDblClicked) + self.ui.onlyUncaughtCheck.toggled.connect(self.updateSysTrace) + + self.currentTraceback = None + + def loadHistory(self): + """Return the list of previously-invoked command strings (or None).""" + if self.historyFile is not None: + return pickle.load(open(self.historyFile, 'rb')) + + def saveHistory(self, history): + """Store the list of previously-invoked command strings.""" + if self.historyFile is not None: + pickle.dump(open(self.historyFile, 'wb'), history) + + def runCmd(self, cmd): + #cmd = str(self.input.lastCmd) + self.stdout = sys.stdout + self.stderr = sys.stderr + encCmd = re.sub(r'>', '>', re.sub(r'<', '<', cmd)) + encCmd = re.sub(r' ', ' ', encCmd) + + self.ui.historyList.addItem(cmd) + self.saveHistory(self.input.history[1:100]) + + try: + sys.stdout = self + sys.stderr = self + if self.multiline is not None: + self.write("
%s\n"%encCmd, html=True) + self.execMulti(cmd) + else: + self.write("
%s\n"%encCmd, html=True) + self.inCmd = True + self.execSingle(cmd) + + if not self.inCmd: + self.write("
\n", html=True) + + finally: + sys.stdout = self.stdout + sys.stderr = self.stderr + + sb = self.output.verticalScrollBar() + sb.setValue(sb.maximum()) + sb = self.ui.historyList.verticalScrollBar() + sb.setValue(sb.maximum()) + + def globals(self): + frame = self.currentFrame() + if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): + return self.currentFrame().tb_frame.f_globals + else: + return globals() + + def locals(self): + frame = self.currentFrame() + if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): + return self.currentFrame().tb_frame.f_locals + else: + return self.localNamespace + + def currentFrame(self): + ## Return the currently selected exception stack frame (or None if there is no exception) + if self.currentTraceback is None: + return None + index = self.ui.exceptionStackList.currentRow() + tb = self.currentTraceback + for i in range(index): + tb = tb.tb_next + return tb + + def execSingle(self, cmd): + try: + output = eval(cmd, self.globals(), self.locals()) + self.write(repr(output) + '\n') + except SyntaxError: + try: + exec(cmd, self.globals(), self.locals()) + except SyntaxError as exc: + if 'unexpected EOF' in exc.msg: + self.multiline = cmd + else: + self.displayException() + except: + self.displayException() + except: + self.displayException() + + + def execMulti(self, nextLine): + #self.stdout.write(nextLine+"\n") + if nextLine.strip() != '': + self.multiline += "\n" + nextLine + return + else: + cmd = self.multiline + + try: + output = eval(cmd, self.globals(), self.locals()) + self.write(str(output) + '\n') + self.multiline = None + except SyntaxError: + try: + exec(cmd, self.globals(), self.locals()) + self.multiline = None + except SyntaxError as exc: + if 'unexpected EOF' in exc.msg: + self.multiline = cmd + else: + self.displayException() + self.multiline = None + except: + self.displayException() + self.multiline = None + except: + self.displayException() + self.multiline = None + + def write(self, strn, html=False): + self.output.moveCursor(QtGui.QTextCursor.End) + if html: + self.output.textCursor().insertHtml(strn) + else: + if self.inCmd: + self.inCmd = False + self.output.textCursor().insertHtml("
") + #self.stdout.write("

") + self.output.insertPlainText(strn) + #self.stdout.write(strn) + + def displayException(self): + """ + Display the current exception and stack. + """ + tb = traceback.format_exc() + lines = [] + indent = 4 + prefix = '' + for l in tb.split('\n'): + lines.append(" "*indent + prefix + l) + self.write('\n'.join(lines)) + self.exceptionHandler(*sys.exc_info()) + + def cmdSelected(self, item): + index = -(self.ui.historyList.row(item)+1) + self.input.setHistory(index) + self.input.setFocus() + + def cmdDblClicked(self, item): + index = -(self.ui.historyList.row(item)+1) + self.input.setHistory(index) + self.input.execCmd() + + def flush(self): + pass + + def catchAllExceptions(self, catch=True): + """ + If True, the console will catch all unhandled exceptions and display the stack + trace. Each exception caught clears the last. + """ + self.ui.catchAllExceptionsBtn.setChecked(catch) + if catch: + self.ui.catchNextExceptionBtn.setChecked(False) + self.enableExceptionHandling() + self.ui.exceptionBtn.setChecked(True) + else: + self.disableExceptionHandling() + + def catchNextException(self, catch=True): + """ + If True, the console will catch the next unhandled exception and display the stack + trace. + """ + self.ui.catchNextExceptionBtn.setChecked(catch) + if catch: + self.ui.catchAllExceptionsBtn.setChecked(False) + self.enableExceptionHandling() + self.ui.exceptionBtn.setChecked(True) + else: + self.disableExceptionHandling() + + def enableExceptionHandling(self): + exceptionHandling.register(self.exceptionHandler) + self.updateSysTrace() + + def disableExceptionHandling(self): + exceptionHandling.unregister(self.exceptionHandler) + self.updateSysTrace() + + def clearExceptionClicked(self): + self.currentTraceback = None + self.ui.exceptionInfoLabel.setText("[No current exception]") + self.ui.exceptionStackList.clear() + self.ui.clearExceptionBtn.setEnabled(False) + + def stackItemClicked(self, item): + pass + + def stackItemDblClicked(self, item): + editor = self.editor + if editor is None: + editor = pg.getConfigOption('editorCommand') + if editor is None: + return + tb = self.currentFrame() + lineNum = tb.tb_lineno + fileName = tb.tb_frame.f_code.co_filename + subprocess.Popen(self.editor.format(fileName=fileName, lineNum=lineNum), shell=True) + + + #def allExceptionsHandler(self, *args): + #self.exceptionHandler(*args) + + #def nextExceptionHandler(self, *args): + #self.ui.catchNextExceptionBtn.setChecked(False) + #self.exceptionHandler(*args) + + def updateSysTrace(self): + ## Install or uninstall sys.settrace handler + + if not self.ui.catchNextExceptionBtn.isChecked() and not self.ui.catchAllExceptionsBtn.isChecked(): + if sys.gettrace() == self.systrace: + sys.settrace(None) + return + + if self.ui.onlyUncaughtCheck.isChecked(): + if sys.gettrace() == self.systrace: + sys.settrace(None) + else: + if sys.gettrace() is not None and sys.gettrace() != self.systrace: + self.ui.onlyUncaughtCheck.setChecked(False) + raise Exception("sys.settrace is in use; cannot monitor for caught exceptions.") + else: + sys.settrace(self.systrace) + + def exceptionHandler(self, excType, exc, tb): + if self.ui.catchNextExceptionBtn.isChecked(): + self.ui.catchNextExceptionBtn.setChecked(False) + elif not self.ui.catchAllExceptionsBtn.isChecked(): + return + + self.ui.clearExceptionBtn.setEnabled(True) + self.currentTraceback = tb + + excMessage = ''.join(traceback.format_exception_only(excType, exc)) + self.ui.exceptionInfoLabel.setText(excMessage) + self.ui.exceptionStackList.clear() + for index, line in enumerate(traceback.extract_tb(tb)): + self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line) + + def systrace(self, frame, event, arg): + if event == 'exception' and self.checkException(*arg): + self.exceptionHandler(*arg) + return self.systrace + + def checkException(self, excType, exc, tb): + ## Return True if the exception is interesting; False if it should be ignored. + + filename = tb.tb_frame.f_code.co_filename + function = tb.tb_frame.f_code.co_name + + ## Go through a list of common exception points we like to ignore: + if excType is GeneratorExit or excType is StopIteration: + return False + if excType is KeyError: + if filename.endswith('python2.7/weakref.py') and function in ('__contains__', 'get'): + return False + if filename.endswith('python2.7/copy.py') and function == '_keep_alive': + return False + if excType is AttributeError: + if filename.endswith('python2.7/collections.py') and function == '__init__': + return False + if filename.endswith('numpy/core/fromnumeric.py') and function in ('all', '_wrapit', 'transpose', 'sum'): + return False + if filename.endswith('numpy/core/arrayprint.py') and function in ('_array2string'): + return False + if filename.endswith('MetaArray.py') and function == '__getattr__': + for name in ('__array_interface__', '__array_struct__', '__array__'): ## numpy looks for these when converting objects to array + if name in exc: + return False + if filename.endswith('flowchart/eq.py'): + return False + if filename.endswith('pyqtgraph/functions.py') and function == 'makeQImage': + return False + if excType is TypeError: + if filename.endswith('numpy/lib/function_base.py') and function == 'iterable': + return False + if excType is ZeroDivisionError: + if filename.endswith('python2.7/traceback.py'): + return False + + return True + diff --git a/pyqtgraph/console/__init__.py b/pyqtgraph/console/__init__.py new file mode 100644 index 00000000..16436abd --- /dev/null +++ b/pyqtgraph/console/__init__.py @@ -0,0 +1 @@ +from .Console import ConsoleWidget \ No newline at end of file diff --git a/pyqtgraph/console/template.ui b/pyqtgraph/console/template.ui new file mode 100644 index 00000000..6e5c5be3 --- /dev/null +++ b/pyqtgraph/console/template.ui @@ -0,0 +1,184 @@ + + + Form + + + + 0 + 0 + 710 + 497 + + + + Console + + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + + + + Monospace + + + + true + + + + + + + + + + + + History.. + + + true + + + + + + + Exceptions.. + + + true + + + + + + + + + + + Monospace + + + + + + Exception Handling + + + + 0 + + + 0 + + + 0 + + + + + Show All Exceptions + + + true + + + + + + + Show Next Exception + + + true + + + + + + + Only Uncaught Exceptions + + + true + + + + + + + true + + + + + + + Run commands in selected stack frame + + + true + + + + + + + Exception Info + + + + + + + false + + + Clear Exception + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + CmdInput + QLineEdit +
.CmdInput
+
+
+ + +
diff --git a/pyqtgraph/console/template_pyqt.py b/pyqtgraph/console/template_pyqt.py new file mode 100644 index 00000000..89ee6cff --- /dev/null +++ b/pyqtgraph/console/template_pyqt.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './console/template.ui' +# +# Created: Sun Sep 9 14:41:30 2012 +# by: PyQt4 UI code generator 4.9.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(710, 497) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName(_fromUtf8("splitter")) + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) + self.verticalLayout = QtGui.QVBoxLayout(self.layoutWidget) + self.verticalLayout.setMargin(0) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.output = QtGui.QPlainTextEdit(self.layoutWidget) + font = QtGui.QFont() + font.setFamily(_fromUtf8("Monospace")) + self.output.setFont(font) + self.output.setReadOnly(True) + self.output.setObjectName(_fromUtf8("output")) + self.verticalLayout.addWidget(self.output) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.input = CmdInput(self.layoutWidget) + self.input.setObjectName(_fromUtf8("input")) + self.horizontalLayout.addWidget(self.input) + self.historyBtn = QtGui.QPushButton(self.layoutWidget) + self.historyBtn.setCheckable(True) + self.historyBtn.setObjectName(_fromUtf8("historyBtn")) + self.horizontalLayout.addWidget(self.historyBtn) + self.exceptionBtn = QtGui.QPushButton(self.layoutWidget) + self.exceptionBtn.setCheckable(True) + self.exceptionBtn.setObjectName(_fromUtf8("exceptionBtn")) + self.horizontalLayout.addWidget(self.exceptionBtn) + self.verticalLayout.addLayout(self.horizontalLayout) + self.historyList = QtGui.QListWidget(self.splitter) + font = QtGui.QFont() + font.setFamily(_fromUtf8("Monospace")) + self.historyList.setFont(font) + self.historyList.setObjectName(_fromUtf8("historyList")) + self.exceptionGroup = QtGui.QGroupBox(self.splitter) + self.exceptionGroup.setObjectName(_fromUtf8("exceptionGroup")) + self.gridLayout_2 = QtGui.QGridLayout(self.exceptionGroup) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.catchAllExceptionsBtn = QtGui.QPushButton(self.exceptionGroup) + self.catchAllExceptionsBtn.setCheckable(True) + self.catchAllExceptionsBtn.setObjectName(_fromUtf8("catchAllExceptionsBtn")) + self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) + self.catchNextExceptionBtn = QtGui.QPushButton(self.exceptionGroup) + self.catchNextExceptionBtn.setCheckable(True) + self.catchNextExceptionBtn.setObjectName(_fromUtf8("catchNextExceptionBtn")) + self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) + self.onlyUncaughtCheck = QtGui.QCheckBox(self.exceptionGroup) + self.onlyUncaughtCheck.setChecked(True) + self.onlyUncaughtCheck.setObjectName(_fromUtf8("onlyUncaughtCheck")) + self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 2, 1, 1) + self.exceptionStackList = QtGui.QListWidget(self.exceptionGroup) + self.exceptionStackList.setAlternatingRowColors(True) + self.exceptionStackList.setObjectName(_fromUtf8("exceptionStackList")) + self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 5) + self.runSelectedFrameCheck = QtGui.QCheckBox(self.exceptionGroup) + self.runSelectedFrameCheck.setChecked(True) + self.runSelectedFrameCheck.setObjectName(_fromUtf8("runSelectedFrameCheck")) + self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 5) + self.exceptionInfoLabel = QtGui.QLabel(self.exceptionGroup) + self.exceptionInfoLabel.setObjectName(_fromUtf8("exceptionInfoLabel")) + self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 5) + self.clearExceptionBtn = QtGui.QPushButton(self.exceptionGroup) + self.clearExceptionBtn.setEnabled(False) + self.clearExceptionBtn.setObjectName(_fromUtf8("clearExceptionBtn")) + self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 4, 1, 1) + spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem, 0, 3, 1, 1) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Console", None, QtGui.QApplication.UnicodeUTF8)) + self.historyBtn.setText(QtGui.QApplication.translate("Form", "History..", None, QtGui.QApplication.UnicodeUTF8)) + self.exceptionBtn.setText(QtGui.QApplication.translate("Form", "Exceptions..", None, QtGui.QApplication.UnicodeUTF8)) + self.exceptionGroup.setTitle(QtGui.QApplication.translate("Form", "Exception Handling", None, QtGui.QApplication.UnicodeUTF8)) + self.catchAllExceptionsBtn.setText(QtGui.QApplication.translate("Form", "Show All Exceptions", None, QtGui.QApplication.UnicodeUTF8)) + self.catchNextExceptionBtn.setText(QtGui.QApplication.translate("Form", "Show Next Exception", None, QtGui.QApplication.UnicodeUTF8)) + self.onlyUncaughtCheck.setText(QtGui.QApplication.translate("Form", "Only Uncaught Exceptions", None, QtGui.QApplication.UnicodeUTF8)) + self.runSelectedFrameCheck.setText(QtGui.QApplication.translate("Form", "Run commands in selected stack frame", None, QtGui.QApplication.UnicodeUTF8)) + self.exceptionInfoLabel.setText(QtGui.QApplication.translate("Form", "Exception Info", None, QtGui.QApplication.UnicodeUTF8)) + self.clearExceptionBtn.setText(QtGui.QApplication.translate("Form", "Clear Exception", None, QtGui.QApplication.UnicodeUTF8)) + +from .CmdInput import CmdInput diff --git a/pyqtgraph/console/template_pyside.py b/pyqtgraph/console/template_pyside.py new file mode 100644 index 00000000..0493a0fe --- /dev/null +++ b/pyqtgraph/console/template_pyside.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './console/template.ui' +# +# Created: Sun Sep 9 14:41:30 2012 +# by: pyside-uic 0.2.13 running on PySide 1.1.0 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(710, 497) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName("splitter") + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName("layoutWidget") + self.verticalLayout = QtGui.QVBoxLayout(self.layoutWidget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.output = QtGui.QPlainTextEdit(self.layoutWidget) + font = QtGui.QFont() + font.setFamily("Monospace") + self.output.setFont(font) + self.output.setReadOnly(True) + self.output.setObjectName("output") + self.verticalLayout.addWidget(self.output) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.input = CmdInput(self.layoutWidget) + self.input.setObjectName("input") + self.horizontalLayout.addWidget(self.input) + self.historyBtn = QtGui.QPushButton(self.layoutWidget) + self.historyBtn.setCheckable(True) + self.historyBtn.setObjectName("historyBtn") + self.horizontalLayout.addWidget(self.historyBtn) + self.exceptionBtn = QtGui.QPushButton(self.layoutWidget) + self.exceptionBtn.setCheckable(True) + self.exceptionBtn.setObjectName("exceptionBtn") + self.horizontalLayout.addWidget(self.exceptionBtn) + self.verticalLayout.addLayout(self.horizontalLayout) + self.historyList = QtGui.QListWidget(self.splitter) + font = QtGui.QFont() + font.setFamily("Monospace") + self.historyList.setFont(font) + self.historyList.setObjectName("historyList") + self.exceptionGroup = QtGui.QGroupBox(self.splitter) + self.exceptionGroup.setObjectName("exceptionGroup") + self.gridLayout_2 = QtGui.QGridLayout(self.exceptionGroup) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.catchAllExceptionsBtn = QtGui.QPushButton(self.exceptionGroup) + self.catchAllExceptionsBtn.setCheckable(True) + self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn") + self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) + self.catchNextExceptionBtn = QtGui.QPushButton(self.exceptionGroup) + self.catchNextExceptionBtn.setCheckable(True) + self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn") + self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) + self.onlyUncaughtCheck = QtGui.QCheckBox(self.exceptionGroup) + self.onlyUncaughtCheck.setChecked(True) + self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck") + self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 2, 1, 1) + self.exceptionStackList = QtGui.QListWidget(self.exceptionGroup) + self.exceptionStackList.setAlternatingRowColors(True) + self.exceptionStackList.setObjectName("exceptionStackList") + self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 5) + self.runSelectedFrameCheck = QtGui.QCheckBox(self.exceptionGroup) + self.runSelectedFrameCheck.setChecked(True) + self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck") + self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 5) + self.exceptionInfoLabel = QtGui.QLabel(self.exceptionGroup) + self.exceptionInfoLabel.setObjectName("exceptionInfoLabel") + self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 5) + self.clearExceptionBtn = QtGui.QPushButton(self.exceptionGroup) + self.clearExceptionBtn.setEnabled(False) + self.clearExceptionBtn.setObjectName("clearExceptionBtn") + self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 4, 1, 1) + spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem, 0, 3, 1, 1) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Console", None, QtGui.QApplication.UnicodeUTF8)) + self.historyBtn.setText(QtGui.QApplication.translate("Form", "History..", None, QtGui.QApplication.UnicodeUTF8)) + self.exceptionBtn.setText(QtGui.QApplication.translate("Form", "Exceptions..", None, QtGui.QApplication.UnicodeUTF8)) + self.exceptionGroup.setTitle(QtGui.QApplication.translate("Form", "Exception Handling", None, QtGui.QApplication.UnicodeUTF8)) + self.catchAllExceptionsBtn.setText(QtGui.QApplication.translate("Form", "Show All Exceptions", None, QtGui.QApplication.UnicodeUTF8)) + self.catchNextExceptionBtn.setText(QtGui.QApplication.translate("Form", "Show Next Exception", None, QtGui.QApplication.UnicodeUTF8)) + self.onlyUncaughtCheck.setText(QtGui.QApplication.translate("Form", "Only Uncaught Exceptions", None, QtGui.QApplication.UnicodeUTF8)) + self.runSelectedFrameCheck.setText(QtGui.QApplication.translate("Form", "Run commands in selected stack frame", None, QtGui.QApplication.UnicodeUTF8)) + self.exceptionInfoLabel.setText(QtGui.QApplication.translate("Form", "Exception Info", None, QtGui.QApplication.UnicodeUTF8)) + self.clearExceptionBtn.setText(QtGui.QApplication.translate("Form", "Clear Exception", None, QtGui.QApplication.UnicodeUTF8)) + +from .CmdInput import CmdInput diff --git a/pyqtgraph/debug.py b/pyqtgraph/debug.py new file mode 100644 index 00000000..a175be9c --- /dev/null +++ b/pyqtgraph/debug.py @@ -0,0 +1,946 @@ +# -*- coding: utf-8 -*- +""" +debug.py - Functions to aid in debugging +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile +from . import ptime +from numpy import ndarray +from .Qt import QtCore, QtGui + +__ftraceDepth = 0 +def ftrace(func): + """Decorator used for marking the beginning and end of function calls. + Automatically indents nested calls. + """ + def w(*args, **kargs): + global __ftraceDepth + pfx = " " * __ftraceDepth + print(pfx + func.__name__ + " start") + __ftraceDepth += 1 + try: + rv = func(*args, **kargs) + finally: + __ftraceDepth -= 1 + print(pfx + func.__name__ + " done") + return rv + return w + +def warnOnException(func): + """Decorator which catches/ignores exceptions and prints a stack trace.""" + def w(*args, **kwds): + try: + func(*args, **kwds) + except: + printExc('Ignored exception:') + return w + +def getExc(indent=4, prefix='| '): + tb = traceback.format_exc() + lines = [] + for l in tb.split('\n'): + lines.append(" "*indent + prefix + l) + return '\n'.join(lines) + +def printExc(msg='', indent=4, prefix='|'): + """Print an error message followed by an indented exception backtrace + (This function is intended to be called within except: blocks)""" + exc = getExc(indent, prefix + ' ') + print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) + print(" "*indent + prefix + '='*30 + '>>') + print(exc) + print(" "*indent + prefix + '='*30 + '<<') + +def printTrace(msg='', indent=4, prefix='|'): + """Print an error message followed by an indented stack trace""" + trace = backtrace(1) + #exc = getExc(indent, prefix + ' ') + print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) + print(" "*indent + prefix + '='*30 + '>>') + for line in trace.split('\n'): + print(" "*indent + prefix + " " + line) + print(" "*indent + prefix + '='*30 + '<<') + + +def backtrace(skip=0): + return ''.join(traceback.format_stack()[:-(skip+1)]) + + +def listObjs(regex='Q', typ=None): + """List all objects managed by python gc with class name matching regex. + Finds 'Q...' classes by default.""" + if typ is not None: + return [x for x in gc.get_objects() if isinstance(x, typ)] + else: + return [x for x in gc.get_objects() if re.match(regex, type(x).__name__)] + + + +def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ignore=None): + """Determine all paths of object references from startObj to endObj""" + refs = [] + if path is None: + path = [endObj] + if ignore is None: + ignore = {} + ignore[id(sys._getframe())] = None + ignore[id(path)] = None + ignore[id(seen)] = None + prefix = " "*(8-maxLen) + #print prefix + str(map(type, path)) + prefix += " " + if restart: + #gc.collect() + seen.clear() + gc.collect() + newRefs = [r for r in gc.get_referrers(endObj) if id(r) not in ignore] + ignore[id(newRefs)] = None + #fo = allFrameObjs() + #newRefs = [] + #for r in gc.get_referrers(endObj): + #try: + #if r not in fo: + #newRefs.append(r) + #except: + #newRefs.append(r) + + for r in newRefs: + #print prefix+"->"+str(type(r)) + if type(r).__name__ in ['frame', 'function', 'listiterator']: + #print prefix+" FRAME" + continue + try: + if any([r is x for x in path]): + #print prefix+" LOOP", objChainString([r]+path) + continue + except: + print(r) + print(path) + raise + if r is startObj: + refs.append([r]) + print(refPathString([startObj]+path)) + continue + if maxLen == 0: + #print prefix+" END:", objChainString([r]+path) + continue + ## See if we have already searched this node. + ## If not, recurse. + tree = None + try: + cache = seen[id(r)] + if cache[0] >= maxLen: + tree = cache[1] + for p in tree: + print(refPathString(p+path)) + except KeyError: + pass + + ignore[id(tree)] = None + if tree is None: + tree = findRefPath(startObj, r, maxLen-1, restart=False, path=[r]+path, ignore=ignore) + seen[id(r)] = [maxLen, tree] + ## integrate any returned results + if len(tree) == 0: + #print prefix+" EMPTY TREE" + continue + else: + for p in tree: + refs.append(p+[r]) + #seen[id(r)] = [maxLen, refs] + return refs + + +def objString(obj): + """Return a short but descriptive string for any object""" + try: + if type(obj) in [int, float]: + return str(obj) + elif isinstance(obj, dict): + if len(obj) > 5: + return "" % (",".join(list(obj.keys())[:5])) + else: + return "" % (",".join(list(obj.keys()))) + elif isinstance(obj, str): + if len(obj) > 50: + return '"%s..."' % obj[:50] + else: + return obj[:] + elif isinstance(obj, ndarray): + return "" % (str(obj.dtype), str(obj.shape)) + elif hasattr(obj, '__len__'): + if len(obj) > 5: + return "<%s [%s,...]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj[:5]])) + else: + return "<%s [%s]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj])) + else: + return "<%s %s>" % (type(obj).__name__, obj.__class__.__name__) + except: + return str(type(obj)) + +def refPathString(chain): + """Given a list of adjacent objects in a reference path, print the 'natural' path + names (ie, attribute names, keys, and indexes) that follow from one object to the next .""" + s = objString(chain[0]) + i = 0 + while i < len(chain)-1: + #print " -> ", i + i += 1 + o1 = chain[i-1] + o2 = chain[i] + cont = False + if isinstance(o1, list) or isinstance(o1, tuple): + if any([o2 is x for x in o1]): + s += "[%d]" % o1.index(o2) + continue + #print " not list" + if isinstance(o2, dict) and hasattr(o1, '__dict__') and o2 == o1.__dict__: + i += 1 + if i >= len(chain): + s += ".__dict__" + continue + o3 = chain[i] + for k in o2: + if o2[k] is o3: + s += '.%s' % k + cont = True + continue + #print " not __dict__" + if isinstance(o1, dict): + try: + if o2 in o1: + s += "[key:%s]" % objString(o2) + continue + except TypeError: + pass + for k in o1: + if o1[k] is o2: + s += "[%s]" % objString(k) + cont = True + continue + #print " not dict" + #for k in dir(o1): ## Not safe to request attributes like this. + #if getattr(o1, k) is o2: + #s += ".%s" % k + #cont = True + #continue + #print " not attr" + if cont: + continue + s += " ? " + sys.stdout.flush() + return s + + +def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False): + """Guess how much memory an object is using""" + ignoreTypes = [types.MethodType, types.UnboundMethodType, types.BuiltinMethodType, types.FunctionType, types.BuiltinFunctionType] + ignoreRegex = re.compile('(method-wrapper|Flag|ItemChange|Option|Mode)') + + + if ignore is None: + ignore = {} + + indent = ' '*depth + + try: + hash(obj) + hsh = obj + except: + hsh = "%s:%d" % (str(type(obj)), id(obj)) + + if hsh in ignore: + return 0 + ignore[hsh] = 1 + + try: + size = sys.getsizeof(obj) + except TypeError: + size = 0 + + if isinstance(obj, ndarray): + try: + size += len(obj.data) + except: + pass + + + if recursive: + if type(obj) in [list, tuple]: + if verbose: + print(indent+"list:") + for o in obj: + s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) + if verbose: + print(indent+' +', s) + size += s + elif isinstance(obj, dict): + if verbose: + print(indent+"list:") + for k in obj: + s = objectSize(obj[k], ignore=ignore, verbose=verbose, depth=depth+1) + if verbose: + print(indent+' +', k, s) + size += s + #elif isinstance(obj, QtCore.QObject): + #try: + #childs = obj.children() + #if verbose: + #print indent+"Qt children:" + #for ch in childs: + #s = objectSize(obj, ignore=ignore, verbose=verbose, depth=depth+1) + #size += s + #if verbose: + #print indent + ' +', ch.objectName(), s + + #except: + #pass + #if isinstance(obj, types.InstanceType): + gc.collect() + if verbose: + print(indent+'attrs:') + for k in dir(obj): + if k in ['__dict__']: + continue + o = getattr(obj, k) + if type(o) in ignoreTypes: + continue + strtyp = str(type(o)) + if ignoreRegex.search(strtyp): + continue + #if isinstance(o, types.ObjectType) and strtyp == "": + #continue + + #if verbose: + #print indent, k, '?' + refs = [r for r in gc.get_referrers(o) if type(r) != types.FrameType] + if len(refs) == 1: + s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) + size += s + if verbose: + print(indent + " +", k, s) + #else: + #if verbose: + #print indent + ' -', k, len(refs) + return size + +class GarbageWatcher(object): + """ + Convenient dictionary for holding weak references to objects. + Mainly used to check whether the objects have been collect yet or not. + + Example: + gw = GarbageWatcher() + gw['objName'] = obj + gw['objName2'] = obj2 + gw.check() + + + """ + def __init__(self): + self.objs = weakref.WeakValueDictionary() + self.allNames = [] + + def add(self, obj, name): + self.objs[name] = obj + self.allNames.append(name) + + def __setitem__(self, name, obj): + self.add(obj, name) + + def check(self): + """Print a list of all watched objects and whether they have been collected.""" + gc.collect() + dead = self.allNames[:] + alive = [] + for k in self.objs: + dead.remove(k) + alive.append(k) + print("Deleted objects:", dead) + print("Live objects:", alive) + + def __getitem__(self, item): + return self.objs[item] + + +class Profiler: + """Simple profiler allowing measurement of multiple time intervals. + Arguments: + msg: message to print at start and finish of profiling + disabled: If true, profiler does nothing (so you can leave it in place) + delayed: If true, all messages are printed after call to finish() + (this can result in more accurate time step measurements) + globalDelay: if True, all nested profilers delay printing until the top level finishes + + Example: + prof = Profiler('Function') + ... do stuff ... + prof.mark('did stuff') + ... do other stuff ... + prof.mark('did other stuff') + prof.finish() + """ + depth = 0 + msgs = [] + + def __init__(self, msg="Profiler", disabled=False, delayed=True, globalDelay=True): + self.disabled = disabled + if disabled: + return + + self.markCount = 0 + self.finished = False + self.depth = Profiler.depth + Profiler.depth += 1 + if not globalDelay: + self.msgs = [] + self.delayed = delayed + self.msg = " "*self.depth + msg + msg2 = self.msg + " >>> Started" + if self.delayed: + self.msgs.append(msg2) + else: + print(msg2) + self.t0 = ptime.time() + self.t1 = self.t0 + + def mark(self, msg=None): + if self.disabled: + return + + if msg is None: + msg = str(self.markCount) + self.markCount += 1 + + t1 = ptime.time() + msg2 = " "+self.msg+" "+msg+" "+"%gms" % ((t1-self.t1)*1000) + if self.delayed: + self.msgs.append(msg2) + else: + print(msg2) + self.t1 = ptime.time() ## don't measure time it took to print + + def finish(self, msg=None): + if self.disabled or self.finished: + return + + if msg is not None: + self.mark(msg) + t1 = ptime.time() + msg = self.msg + ' <<< Finished, total time: %gms' % ((t1-self.t0)*1000) + if self.delayed: + self.msgs.append(msg) + if self.depth == 0: + for line in self.msgs: + print(line) + Profiler.msgs = [] + else: + print(msg) + Profiler.depth = self.depth + self.finished = True + + + + +def profile(code, name='profile_run', sort='cumulative', num=30): + """Common-use for cProfile""" + cProfile.run(code, name) + stats = pstats.Stats(name) + stats.sort_stats(sort) + stats.print_stats(num) + return stats + + + +#### Code for listing (nearly) all objects in the known universe +#### http://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects +# Recursively expand slist's objects +# into olist, using seen to track +# already processed objects. +def _getr(slist, olist, first=True): + i = 0 + for e in slist: + + oid = id(e) + typ = type(e) + if oid in olist or typ is int: ## or e in olist: ## since we're excluding all ints, there is no longer a need to check for olist keys + continue + olist[oid] = e + if first and (i%1000) == 0: + gc.collect() + tl = gc.get_referents(e) + if tl: + _getr(tl, olist, first=False) + i += 1 +# The public function. +def get_all_objects(): + """Return a list of all live Python objects (excluding int and long), not including the list itself.""" + gc.collect() + gcl = gc.get_objects() + olist = {} + _getr(gcl, olist) + + del olist[id(olist)] + del olist[id(gcl)] + del olist[id(sys._getframe())] + return olist + + +def lookup(oid, objects=None): + """Return an object given its ID, if it exists.""" + if objects is None: + objects = get_all_objects() + return objects[oid] + + + + +class ObjTracker(object): + """ + Tracks all objects under the sun, reporting the changes between snapshots: what objects are created, deleted, and persistent. + This class is very useful for tracking memory leaks. The class goes to great (but not heroic) lengths to avoid tracking + its own internal objects. + + Example: + ot = ObjTracker() # takes snapshot of currently existing objects + ... do stuff ... + ot.diff() # prints lists of objects created and deleted since ot was initialized + ... do stuff ... + ot.diff() # prints lists of objects created and deleted since last call to ot.diff() + # also prints list of items that were created since initialization AND have not been deleted yet + # (if done correctly, this list can tell you about objects that were leaked) + + arrays = ot.findPersistent('ndarray') ## returns all objects matching 'ndarray' (string match, not instance checking) + ## that were considered persistent when the last diff() was run + + describeObj(arrays[0]) ## See if we can determine who has references to this array + """ + + + allObjs = {} ## keep track of all objects created and stored within class instances + allObjs[id(allObjs)] = None + + def __init__(self): + self.startRefs = {} ## list of objects that exist when the tracker is initialized {oid: weakref} + ## (If it is not possible to weakref the object, then the value is None) + self.startCount = {} + self.newRefs = {} ## list of objects that have been created since initialization + self.persistentRefs = {} ## list of objects considered 'persistent' when the last diff() was called + self.objTypes = {} + + ObjTracker.allObjs[id(self)] = None + self.objs = [self.__dict__, self.startRefs, self.startCount, self.newRefs, self.persistentRefs, self.objTypes] + self.objs.append(self.objs) + for v in self.objs: + ObjTracker.allObjs[id(v)] = None + + self.start() + + def findNew(self, regex): + """Return all objects matching regex that were considered 'new' when the last diff() was run.""" + return self.findTypes(self.newRefs, regex) + + def findPersistent(self, regex): + """Return all objects matching regex that were considered 'persistent' when the last diff() was run.""" + return self.findTypes(self.persistentRefs, regex) + + + def start(self): + """ + Remember the current set of objects as the comparison for all future calls to diff() + Called automatically on init, but can be called manually as well. + """ + refs, count, objs = self.collect() + for r in self.startRefs: + self.forgetRef(self.startRefs[r]) + self.startRefs.clear() + self.startRefs.update(refs) + for r in refs: + self.rememberRef(r) + self.startCount.clear() + self.startCount.update(count) + #self.newRefs.clear() + #self.newRefs.update(refs) + + def diff(self, **kargs): + """ + Compute all differences between the current object set and the reference set. + Print a set of reports for created, deleted, and persistent objects + """ + refs, count, objs = self.collect() ## refs contains the list of ALL objects + + ## Which refs have disappeared since call to start() (these are only displayed once, then forgotten.) + delRefs = {} + for i in self.startRefs.keys(): + if i not in refs: + delRefs[i] = self.startRefs[i] + del self.startRefs[i] + self.forgetRef(delRefs[i]) + for i in self.newRefs.keys(): + if i not in refs: + delRefs[i] = self.newRefs[i] + del self.newRefs[i] + self.forgetRef(delRefs[i]) + #print "deleted:", len(delRefs) + + ## Which refs have appeared since call to start() or diff() + persistentRefs = {} ## created since start(), but before last diff() + createRefs = {} ## created since last diff() + for o in refs: + if o not in self.startRefs: + if o not in self.newRefs: + createRefs[o] = refs[o] ## object has been created since last diff() + else: + persistentRefs[o] = refs[o] ## object has been created since start(), but before last diff() (persistent) + #print "new:", len(newRefs) + + ## self.newRefs holds the entire set of objects created since start() + for r in self.newRefs: + self.forgetRef(self.newRefs[r]) + self.newRefs.clear() + self.newRefs.update(persistentRefs) + self.newRefs.update(createRefs) + for r in self.newRefs: + self.rememberRef(self.newRefs[r]) + #print "created:", len(createRefs) + + ## self.persistentRefs holds all objects considered persistent. + self.persistentRefs.clear() + self.persistentRefs.update(persistentRefs) + + + print("----------- Count changes since start: ----------") + c1 = count.copy() + for k in self.startCount: + c1[k] = c1.get(k, 0) - self.startCount[k] + typs = list(c1.keys()) + typs.sort(lambda a,b: cmp(c1[a], c1[b])) + for t in typs: + if c1[t] == 0: + continue + num = "%d" % c1[t] + print(" " + num + " "*(10-len(num)) + str(t)) + + print("----------- %d Deleted since last diff: ------------" % len(delRefs)) + self.report(delRefs, objs, **kargs) + print("----------- %d Created since last diff: ------------" % len(createRefs)) + self.report(createRefs, objs, **kargs) + print("----------- %d Created since start (persistent): ------------" % len(persistentRefs)) + self.report(persistentRefs, objs, **kargs) + + + def __del__(self): + self.startRefs.clear() + self.startCount.clear() + self.newRefs.clear() + self.persistentRefs.clear() + + del ObjTracker.allObjs[id(self)] + for v in self.objs: + del ObjTracker.allObjs[id(v)] + + @classmethod + def isObjVar(cls, o): + return type(o) is cls or id(o) in cls.allObjs + + def collect(self): + print("Collecting list of all objects...") + gc.collect() + objs = get_all_objects() + frame = sys._getframe() + del objs[id(frame)] ## ignore the current frame + del objs[id(frame.f_code)] + + ignoreTypes = [int] + refs = {} + count = {} + for k in objs: + o = objs[k] + typ = type(o) + oid = id(o) + if ObjTracker.isObjVar(o) or typ in ignoreTypes: + continue + + try: + ref = weakref.ref(obj) + except: + ref = None + refs[oid] = ref + typ = type(o) + typStr = typeStr(o) + self.objTypes[oid] = typStr + ObjTracker.allObjs[id(typStr)] = None + count[typ] = count.get(typ, 0) + 1 + + print("All objects: %d Tracked objects: %d" % (len(objs), len(refs))) + return refs, count, objs + + def forgetRef(self, ref): + if ref is not None: + del ObjTracker.allObjs[id(ref)] + + def rememberRef(self, ref): + ## Record the address of the weakref object so it is not included in future object counts. + if ref is not None: + ObjTracker.allObjs[id(ref)] = None + + + def lookup(self, oid, ref, objs=None): + if ref is None or ref() is None: + try: + obj = lookup(oid, objects=objs) + except: + obj = None + else: + obj = ref() + return obj + + + def report(self, refs, allobjs=None, showIDs=False): + if allobjs is None: + allobjs = get_all_objects() + + count = {} + rev = {} + for oid in refs: + obj = self.lookup(oid, refs[oid], allobjs) + if obj is None: + typ = "[del] " + self.objTypes[oid] + else: + typ = typeStr(obj) + if typ not in rev: + rev[typ] = [] + rev[typ].append(oid) + c = count.get(typ, [0,0]) + count[typ] = [c[0]+1, c[1]+objectSize(obj)] + typs = list(count.keys()) + typs.sort(lambda a,b: cmp(count[a][1], count[b][1])) + + for t in typs: + line = " %d\t%d\t%s" % (count[t][0], count[t][1], t) + if showIDs: + line += "\t"+",".join(map(str,rev[t])) + print(line) + + def findTypes(self, refs, regex): + allObjs = get_all_objects() + ids = {} + objs = [] + r = re.compile(regex) + for k in refs: + if r.search(self.objTypes[k]): + objs.append(self.lookup(k, refs[k], allObjs)) + return objs + + + + +def describeObj(obj, depth=4, path=None, ignore=None): + """ + Trace all reference paths backward, printing a list of different ways this object can be accessed. + Attempts to answer the question "who has a reference to this object" + """ + if path is None: + path = [obj] + if ignore is None: + ignore = {} ## holds IDs of objects used within the function. + ignore[id(sys._getframe())] = None + ignore[id(path)] = None + gc.collect() + refs = gc.get_referrers(obj) + ignore[id(refs)] = None + printed=False + for ref in refs: + if id(ref) in ignore: + continue + if id(ref) in list(map(id, path)): + print("Cyclic reference: " + refPathString([ref]+path)) + printed = True + continue + newPath = [ref]+path + if len(newPath) >= depth: + refStr = refPathString(newPath) + if '[_]' not in refStr: ## ignore '_' references generated by the interactive shell + print(refStr) + printed = True + else: + describeObj(ref, depth, newPath, ignore) + printed = True + if not printed: + print("Dead end: " + refPathString(path)) + + + +def typeStr(obj): + """Create a more useful type string by making types report their class.""" + typ = type(obj) + if typ == types.InstanceType: + return "" % obj.__class__.__name__ + else: + return str(typ) + +def searchRefs(obj, *args): + """Pseudo-interactive function for tracing references backward. + Arguments: + obj: The initial object from which to start searching + args: A set of string or int arguments. + each integer selects one of obj's referrers to be the new 'obj' + each string indicates an action to take on the current 'obj': + t: print the types of obj's referrers + l: print the lengths of obj's referrers (if they have __len__) + i: print the IDs of obj's referrers + o: print obj + ro: return obj + rr: return list of obj's referrers + + Examples: + searchRefs(obj, 't') ## Print types of all objects referring to obj + searchRefs(obj, 't', 0, 't') ## ..then select the first referrer and print the types of its referrers + searchRefs(obj, 't', 0, 't', 'l') ## ..also print lengths of the last set of referrers + searchRefs(obj, 0, 1, 'ro') ## Select index 0 from obj's referrer, then select index 1 from the next set of referrers, then return that object + + """ + ignore = {id(sys._getframe()): None} + gc.collect() + refs = gc.get_referrers(obj) + ignore[id(refs)] = None + refs = [r for r in refs if id(r) not in ignore] + for a in args: + + #fo = allFrameObjs() + #refs = [r for r in refs if r not in fo] + + if type(a) is int: + obj = refs[a] + gc.collect() + refs = gc.get_referrers(obj) + ignore[id(refs)] = None + refs = [r for r in refs if id(r) not in ignore] + elif a == 't': + print(list(map(typeStr, refs))) + elif a == 'i': + print(list(map(id, refs))) + elif a == 'l': + def slen(o): + if hasattr(o, '__len__'): + return len(o) + else: + return None + print(list(map(slen, refs))) + elif a == 'o': + print(obj) + elif a == 'ro': + return obj + elif a == 'rr': + return refs + +def allFrameObjs(): + """Return list of frame objects in current stack. Useful if you want to ignore these objects in refernece searches""" + f = sys._getframe() + objs = [] + while f is not None: + objs.append(f) + objs.append(f.f_code) + #objs.append(f.f_locals) + #objs.append(f.f_globals) + #objs.append(f.f_builtins) + f = f.f_back + return objs + + +def findObj(regex): + """Return a list of objects whose typeStr matches regex""" + allObjs = get_all_objects() + objs = [] + r = re.compile(regex) + for i in allObjs: + obj = allObjs[i] + if r.search(typeStr(obj)): + objs.append(obj) + return objs + + + +def listRedundantModules(): + """List modules that have been imported more than once via different paths.""" + mods = {} + for name, mod in sys.modules.items(): + if not hasattr(mod, '__file__'): + continue + mfile = os.path.abspath(mod.__file__) + if mfile[-1] == 'c': + mfile = mfile[:-1] + if mfile in mods: + print("module at %s has 2 names: %s, %s" % (mfile, name, mods[mfile])) + else: + mods[mfile] = name + + +def walkQObjectTree(obj, counts=None, verbose=False, depth=0): + """ + Walk through a tree of QObjects, doing nothing to them. + The purpose of this function is to find dead objects and generate a crash + immediately rather than stumbling upon them later. + Prints a count of the objects encountered, for fun. (or is it?) + """ + + if verbose: + print(" "*depth + typeStr(obj)) + report = False + if counts is None: + counts = {} + report = True + typ = str(type(obj)) + try: + counts[typ] += 1 + except KeyError: + counts[typ] = 1 + for child in obj.children(): + walkQObjectTree(child, counts, verbose, depth+1) + + return counts + +QObjCache = {} +def qObjectReport(verbose=False): + """Generate a report counting all QObjects and their types""" + global qObjCache + count = {} + for obj in findObj('PyQt'): + if isinstance(obj, QtCore.QObject): + oid = id(obj) + if oid not in QObjCache: + QObjCache[oid] = typeStr(obj) + " " + obj.objectName() + try: + QObjCache[oid] += " " + obj.parent().objectName() + QObjCache[oid] += " " + obj.text() + except: + pass + print("check obj", oid, str(QObjCache[oid])) + if obj.parent() is None: + walkQObjectTree(obj, count, verbose) + + typs = list(count.keys()) + typs.sort() + for t in typs: + print(count[t], "\t", t) + + +class PrintDetector(object): + def __init__(self): + self.stdout = sys.stdout + sys.stdout = self + + def remove(self): + sys.stdout = self.stdout + + def __del__(self): + self.remove() + + def write(self, x): + self.stdout.write(x) + traceback.print_stack() + + def flush(self): + self.stdout.flush() \ No newline at end of file diff --git a/pyqtgraph/dockarea/Container.py b/pyqtgraph/dockarea/Container.py new file mode 100644 index 00000000..83610937 --- /dev/null +++ b/pyqtgraph/dockarea/Container.py @@ -0,0 +1,267 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +import weakref + +class Container(object): + #sigStretchChanged = QtCore.Signal() ## can't do this here; not a QObject. + + def __init__(self, area): + object.__init__(self) + self.area = area + self._container = None + self._stretch = (10, 10) + self.stretches = weakref.WeakKeyDictionary() + + def container(self): + return self._container + + def containerChanged(self, c): + self._container = c + + def type(self): + return None + + def insert(self, new, pos=None, neighbor=None): + if not isinstance(new, list): + new = [new] + if neighbor is None: + if pos == 'before': + index = 0 + else: + index = self.count() + else: + index = self.indexOf(neighbor) + if index == -1: + index = 0 + if pos == 'after': + index += 1 + + for n in new: + #print "change container", n, " -> ", self + n.containerChanged(self) + #print "insert", n, " -> ", self, index + self._insertItem(n, index) + index += 1 + n.sigStretchChanged.connect(self.childStretchChanged) + #print "child added", self + self.updateStretch() + + def apoptose(self, propagate=True): + ##if there is only one (or zero) item in this container, disappear. + cont = self._container + c = self.count() + if c > 1: + return + if self.count() == 1: ## if there is one item, give it to the parent container (unless this is the top) + if self is self.area.topContainer: + return + self.container().insert(self.widget(0), 'before', self) + #print "apoptose:", self + self.close() + if propagate and cont is not None: + cont.apoptose() + + def close(self): + self.area = None + self._container = None + self.setParent(None) + + def childEvent(self, ev): + ch = ev.child() + if ev.removed() and hasattr(ch, 'sigStretchChanged'): + #print "Child", ev.child(), "removed, updating", self + try: + ch.sigStretchChanged.disconnect(self.childStretchChanged) + except: + pass + self.updateStretch() + + def childStretchChanged(self): + #print "child", QtCore.QObject.sender(self), "changed shape, updating", self + self.updateStretch() + + def setStretch(self, x=None, y=None): + #print "setStretch", self, x, y + self._stretch = (x, y) + self.sigStretchChanged.emit() + + def updateStretch(self): + ###Set the stretch values for this container to reflect its contents + pass + + + def stretch(self): + """Return the stretch factors for this container""" + return self._stretch + + +class SplitContainer(Container, QtGui.QSplitter): + """Horizontal or vertical splitter with some changes: + - save/restore works correctly + """ + sigStretchChanged = QtCore.Signal() + + def __init__(self, area, orientation): + QtGui.QSplitter.__init__(self) + self.setOrientation(orientation) + Container.__init__(self, area) + #self.splitterMoved.connect(self.restretchChildren) + + def _insertItem(self, item, index): + self.insertWidget(index, item) + item.show() ## need to show since it may have been previously hidden by tab + + def saveState(self): + sizes = self.sizes() + if all([x == 0 for x in sizes]): + sizes = [10] * len(sizes) + return {'sizes': sizes} + + def restoreState(self, state): + sizes = state['sizes'] + self.setSizes(sizes) + for i in range(len(sizes)): + self.setStretchFactor(i, sizes[i]) + + def childEvent(self, ev): + QtGui.QSplitter.childEvent(self, ev) + Container.childEvent(self, ev) + + #def restretchChildren(self): + #sizes = self.sizes() + #tot = sum(sizes) + + + + +class HContainer(SplitContainer): + def __init__(self, area): + SplitContainer.__init__(self, area, QtCore.Qt.Horizontal) + + def type(self): + return 'horizontal' + + def updateStretch(self): + ##Set the stretch values for this container to reflect its contents + #print "updateStretch", self + x = 0 + y = 0 + sizes = [] + for i in range(self.count()): + wx, wy = self.widget(i).stretch() + x += wx + y = max(y, wy) + sizes.append(wx) + #print " child", self.widget(i), wx, wy + self.setStretch(x, y) + #print sizes + + tot = float(sum(sizes)) + if tot == 0: + scale = 1.0 + else: + scale = self.width() / tot + self.setSizes([int(s*scale) for s in sizes]) + + + +class VContainer(SplitContainer): + def __init__(self, area): + SplitContainer.__init__(self, area, QtCore.Qt.Vertical) + + def type(self): + return 'vertical' + + def updateStretch(self): + ##Set the stretch values for this container to reflect its contents + #print "updateStretch", self + x = 0 + y = 0 + sizes = [] + for i in range(self.count()): + wx, wy = self.widget(i).stretch() + y += wy + x = max(x, wx) + sizes.append(wy) + #print " child", self.widget(i), wx, wy + self.setStretch(x, y) + + #print sizes + tot = float(sum(sizes)) + if tot == 0: + scale = 1.0 + else: + scale = self.height() / tot + self.setSizes([int(s*scale) for s in sizes]) + + +class TContainer(Container, QtGui.QWidget): + sigStretchChanged = QtCore.Signal() + def __init__(self, area): + QtGui.QWidget.__init__(self) + Container.__init__(self, area) + self.layout = QtGui.QGridLayout() + self.layout.setSpacing(0) + self.layout.setContentsMargins(0,0,0,0) + self.setLayout(self.layout) + + self.hTabLayout = QtGui.QHBoxLayout() + self.hTabBox = QtGui.QWidget() + self.hTabBox.setLayout(self.hTabLayout) + self.hTabLayout.setSpacing(2) + self.hTabLayout.setContentsMargins(0,0,0,0) + self.layout.addWidget(self.hTabBox, 0, 1) + + self.stack = QtGui.QStackedWidget() + self.layout.addWidget(self.stack, 1, 1) + self.stack.childEvent = self.stackChildEvent + + + self.setLayout(self.layout) + for n in ['count', 'widget', 'indexOf']: + setattr(self, n, getattr(self.stack, n)) + + + def _insertItem(self, item, index): + if not isinstance(item, Dock.Dock): + raise Exception("Tab containers may hold only docks, not other containers.") + self.stack.insertWidget(index, item) + self.hTabLayout.insertWidget(index, item.label) + #QtCore.QObject.connect(item.label, QtCore.SIGNAL('clicked'), self.tabClicked) + item.label.sigClicked.connect(self.tabClicked) + self.tabClicked(item.label) + + def tabClicked(self, tab, ev=None): + if ev is None or ev.button() == QtCore.Qt.LeftButton: + for i in range(self.count()): + w = self.widget(i) + if w is tab.dock: + w.label.setDim(False) + self.stack.setCurrentIndex(i) + else: + w.label.setDim(True) + + def type(self): + return 'tab' + + def saveState(self): + return {'index': self.stack.currentIndex()} + + def restoreState(self, state): + self.stack.setCurrentIndex(state['index']) + + def updateStretch(self): + ##Set the stretch values for this container to reflect its contents + x = 0 + y = 0 + for i in range(self.count()): + wx, wy = self.widget(i).stretch() + x = max(x, wx) + y = max(y, wy) + self.setStretch(x, y) + + def stackChildEvent(self, ev): + QtGui.QStackedWidget.childEvent(self.stack, ev) + Container.childEvent(self, ev) + +from . import Dock diff --git a/pyqtgraph/dockarea/Dock.py b/pyqtgraph/dockarea/Dock.py new file mode 100644 index 00000000..414980ac --- /dev/null +++ b/pyqtgraph/dockarea/Dock.py @@ -0,0 +1,333 @@ +from pyqtgraph.Qt import QtCore, QtGui + +from .DockDrop import * +from pyqtgraph.widgets.VerticalLabel import VerticalLabel + +class Dock(QtGui.QWidget, DockDrop): + + sigStretchChanged = QtCore.Signal() + + def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True): + QtGui.QWidget.__init__(self) + DockDrop.__init__(self) + self.area = area + self.label = DockLabel(name, self) + self.labelHidden = False + self.moveLabel = True ## If false, the dock is no longer allowed to move the label. + self.autoOrient = autoOrientation + self.orientation = 'horizontal' + #self.label.setAlignment(QtCore.Qt.AlignHCenter) + self.topLayout = QtGui.QGridLayout() + self.topLayout.setContentsMargins(0, 0, 0, 0) + self.topLayout.setSpacing(0) + self.setLayout(self.topLayout) + self.topLayout.addWidget(self.label, 0, 1) + self.widgetArea = QtGui.QWidget() + self.topLayout.addWidget(self.widgetArea, 1, 1) + self.layout = QtGui.QGridLayout() + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSpacing(0) + self.widgetArea.setLayout(self.layout) + self.widgetArea.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.widgets = [] + self.currentRow = 0 + #self.titlePos = 'top' + self.raiseOverlay() + self.hStyle = """ + Dock > QWidget { + border: 1px solid #000; + border-radius: 5px; + border-top-left-radius: 0px; + border-top-right-radius: 0px; + border-top-width: 0px; + }""" + self.vStyle = """ + Dock > QWidget { + border: 1px solid #000; + border-radius: 5px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + border-left-width: 0px; + }""" + self.nStyle = """ + Dock > QWidget { + border: 1px solid #000; + border-radius: 5px; + }""" + self.dragStyle = """ + Dock > QWidget { + border: 4px solid #00F; + border-radius: 5px; + }""" + self.setAutoFillBackground(False) + self.widgetArea.setStyleSheet(self.hStyle) + + self.setStretch(*size) + + if widget is not None: + self.addWidget(widget) + + if hideTitle: + self.hideTitleBar() + + def implements(self, name=None): + if name is None: + return ['dock'] + else: + return name == 'dock' + + def setStretch(self, x=None, y=None): + """ + Set the 'target' size for this Dock. + The actual size will be determined by comparing this Dock's + stretch value to the rest of the docks it shares space with. + """ + #print "setStretch", self, x, y + #self._stretch = (x, y) + if x is None: + x = 0 + if y is None: + y = 0 + #policy = self.sizePolicy() + #policy.setHorizontalStretch(x) + #policy.setVerticalStretch(y) + #self.setSizePolicy(policy) + self._stretch = (x, y) + self.sigStretchChanged.emit() + #print "setStretch", self, x, y, self.stretch() + + def stretch(self): + #policy = self.sizePolicy() + #return policy.horizontalStretch(), policy.verticalStretch() + return self._stretch + + #def stretch(self): + #return self._stretch + + def hideTitleBar(self): + """ + Hide the title bar for this Dock. + This will prevent the Dock being moved by the user. + """ + self.label.hide() + self.labelHidden = True + if 'center' in self.allowedAreas: + self.allowedAreas.remove('center') + self.updateStyle() + + def showTitleBar(self): + """ + Show the title bar for this Dock. + """ + self.label.show() + self.labelHidden = False + self.allowedAreas.add('center') + self.updateStyle() + + def setOrientation(self, o='auto', force=False): + """ + Sets the orientation of the title bar for this Dock. + Must be one of 'auto', 'horizontal', or 'vertical'. + By default ('auto'), the orientation is determined + based on the aspect ratio of the Dock. + """ + #print self.name(), "setOrientation", o, force + if o == 'auto' and self.autoOrient: + if self.container().type() == 'tab': + o = 'horizontal' + elif self.width() > self.height()*1.5: + o = 'vertical' + else: + o = 'horizontal' + if force or self.orientation != o: + self.orientation = o + self.label.setOrientation(o) + self.updateStyle() + + def updateStyle(self): + ## updates orientation and appearance of title bar + #print self.name(), "update style:", self.orientation, self.moveLabel, self.label.isVisible() + if self.labelHidden: + self.widgetArea.setStyleSheet(self.nStyle) + elif self.orientation == 'vertical': + self.label.setOrientation('vertical') + if self.moveLabel: + #print self.name(), "reclaim label" + self.topLayout.addWidget(self.label, 1, 0) + self.widgetArea.setStyleSheet(self.vStyle) + else: + self.label.setOrientation('horizontal') + if self.moveLabel: + #print self.name(), "reclaim label" + self.topLayout.addWidget(self.label, 0, 1) + self.widgetArea.setStyleSheet(self.hStyle) + + def resizeEvent(self, ev): + self.setOrientation() + self.resizeOverlay(self.size()) + + def name(self): + return str(self.label.text()) + + def container(self): + return self._container + + def addWidget(self, widget, row=None, col=0, rowspan=1, colspan=1): + """ + Add a new widget to the interior of this Dock. + Each Dock uses a QGridLayout to arrange widgets within. + """ + if row is None: + row = self.currentRow + self.currentRow = max(row+1, self.currentRow) + self.widgets.append(widget) + self.layout.addWidget(widget, row, col, rowspan, colspan) + self.raiseOverlay() + + + def startDrag(self): + self.drag = QtGui.QDrag(self) + mime = QtCore.QMimeData() + #mime.setPlainText("asd") + self.drag.setMimeData(mime) + self.widgetArea.setStyleSheet(self.dragStyle) + self.update() + action = self.drag.exec_() + self.updateStyle() + + def float(self): + self.area.floatDock(self) + + def containerChanged(self, c): + #print self.name(), "container changed" + self._container = c + if c.type() != 'tab': + self.moveLabel = True + self.label.setDim(False) + else: + self.moveLabel = False + + self.setOrientation(force=True) + + def close(self): + """Remove this dock from the DockArea it lives inside.""" + self.setParent(None) + self.label.setParent(None) + self._container.apoptose() + self._container = None + + def __repr__(self): + return "" % (self.name(), self.stretch()) + + ## PySide bug: We need to explicitly redefine these methods + ## or else drag/drop events will not be delivered. + def dragEnterEvent(self, *args): + DockDrop.dragEnterEvent(self, *args) + + def dragMoveEvent(self, *args): + DockDrop.dragMoveEvent(self, *args) + + def dragLeaveEvent(self, *args): + DockDrop.dragLeaveEvent(self, *args) + + def dropEvent(self, *args): + DockDrop.dropEvent(self, *args) + +class DockLabel(VerticalLabel): + + sigClicked = QtCore.Signal(object, object) + + def __init__(self, text, dock): + self.dim = False + self.fixedWidth = False + VerticalLabel.__init__(self, text, orientation='horizontal', forceWidth=False) + self.setAlignment(QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter) + self.dock = dock + self.updateStyle() + self.setAutoFillBackground(False) + + #def minimumSizeHint(self): + ##sh = QtGui.QWidget.minimumSizeHint(self) + #return QtCore.QSize(20, 20) + + def updateStyle(self): + r = '3px' + if self.dim: + fg = '#aaa' + bg = '#44a' + border = '#339' + else: + fg = '#fff' + bg = '#66c' + border = '#55B' + + if self.orientation == 'vertical': + self.vStyle = """DockLabel { + background-color : %s; + color : %s; + border-top-right-radius: 0px; + border-top-left-radius: %s; + border-bottom-right-radius: 0px; + border-bottom-left-radius: %s; + border-width: 0px; + border-right: 2px solid %s; + padding-top: 3px; + padding-bottom: 3px; + }""" % (bg, fg, r, r, border) + self.setStyleSheet(self.vStyle) + else: + self.hStyle = """DockLabel { + background-color : %s; + color : %s; + border-top-right-radius: %s; + border-top-left-radius: %s; + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; + border-width: 0px; + border-bottom: 2px solid %s; + padding-left: 3px; + padding-right: 3px; + }""" % (bg, fg, r, r, border) + self.setStyleSheet(self.hStyle) + + def setDim(self, d): + if self.dim != d: + self.dim = d + self.updateStyle() + + def setOrientation(self, o): + VerticalLabel.setOrientation(self, o) + self.updateStyle() + + def mousePressEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + self.pressPos = ev.pos() + self.startedDrag = False + ev.accept() + + def mouseMoveEvent(self, ev): + if not self.startedDrag and (ev.pos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance(): + self.dock.startDrag() + ev.accept() + #print ev.pos() + + def mouseReleaseEvent(self, ev): + if not self.startedDrag: + #self.emit(QtCore.SIGNAL('clicked'), self, ev) + self.sigClicked.emit(self, ev) + ev.accept() + + def mouseDoubleClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + self.dock.float() + + #def paintEvent(self, ev): + #p = QtGui.QPainter(self) + ##p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200))) + #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100))) + #p.drawRect(self.rect().adjusted(0, 0, -1, -1)) + + #VerticalLabel.paintEvent(self, ev) + + + diff --git a/pyqtgraph/dockarea/DockArea.py b/pyqtgraph/dockarea/DockArea.py new file mode 100644 index 00000000..882b29a3 --- /dev/null +++ b/pyqtgraph/dockarea/DockArea.py @@ -0,0 +1,319 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +from .Container import * +from .DockDrop import * +from .Dock import Dock +import pyqtgraph.debug as debug +import weakref + +## TODO: +# - containers should be drop areas, not docks. (but every slot within a container must have its own drop areas?) +# - drop between tabs +# - nest splitters inside tab boxes, etc. + + + + +class DockArea(Container, QtGui.QWidget, DockDrop): + def __init__(self, temporary=False, home=None): + Container.__init__(self, self) + QtGui.QWidget.__init__(self) + DockDrop.__init__(self, allowedAreas=['left', 'right', 'top', 'bottom']) + self.layout = QtGui.QVBoxLayout() + self.layout.setContentsMargins(0,0,0,0) + self.layout.setSpacing(0) + self.setLayout(self.layout) + self.docks = weakref.WeakValueDictionary() + self.topContainer = None + self.raiseOverlay() + self.temporary = temporary + self.tempAreas = [] + self.home = home + + def type(self): + return "top" + + def addDock(self, dock=None, position='bottom', relativeTo=None, **kwds): + """Adds a dock to this area. + + =========== ================================================================= + Arguments: + dock The new Dock object to add. If None, then a new Dock will be + created. + position 'bottom', 'top', 'left', 'right', 'above', or 'below' + relativeTo If relativeTo is None, then the new Dock is added to fill an + entire edge of the window. If relativeTo is another Dock, then + the new Dock is placed adjacent to it (or in a tabbed + configuration for 'above' and 'below'). + =========== ================================================================= + + All extra keyword arguments are passed to Dock.__init__() if *dock* is + None. + """ + if dock is None: + dock = Dock(**kwds) + + + ## Determine the container to insert this dock into. + ## If there is no neighbor, then the container is the top. + if relativeTo is None or relativeTo is self: + if self.topContainer is None: + container = self + neighbor = None + else: + container = self.topContainer + neighbor = None + else: + if isinstance(relativeTo, basestring): + relativeTo = self.docks[relativeTo] + container = self.getContainer(relativeTo) + neighbor = relativeTo + + ## what container type do we need? + neededContainer = { + 'bottom': 'vertical', + 'top': 'vertical', + 'left': 'horizontal', + 'right': 'horizontal', + 'above': 'tab', + 'below': 'tab' + }[position] + + ## Can't insert new containers into a tab container; insert outside instead. + if neededContainer != container.type() and container.type() == 'tab': + neighbor = container + container = container.container() + + ## Decide if the container we have is suitable. + ## If not, insert a new container inside. + if neededContainer != container.type(): + if neighbor is None: + container = self.addContainer(neededContainer, self.topContainer) + else: + container = self.addContainer(neededContainer, neighbor) + + ## Insert the new dock before/after its neighbor + insertPos = { + 'bottom': 'after', + 'top': 'before', + 'left': 'before', + 'right': 'after', + 'above': 'before', + 'below': 'after' + }[position] + #print "request insert", dock, insertPos, neighbor + container.insert(dock, insertPos, neighbor) + dock.area = self + self.docks[dock.name()] = dock + + return dock + + def moveDock(self, dock, position, neighbor): + """ + Move an existing Dock to a new location. + """ + old = dock.container() + ## Moving to the edge of a tabbed dock causes a drop outside the tab box + if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab': + neighbor = neighbor.container() + self.addDock(dock, position, neighbor) + old.apoptose() + + def getContainer(self, obj): + if obj is None: + return self + return obj.container() + + def makeContainer(self, typ): + if typ == 'vertical': + new = VContainer(self) + elif typ == 'horizontal': + new = HContainer(self) + elif typ == 'tab': + new = TContainer(self) + return new + + def addContainer(self, typ, obj): + """Add a new container around obj""" + new = self.makeContainer(typ) + + container = self.getContainer(obj) + container.insert(new, 'before', obj) + #print "Add container:", new, " -> ", container + if obj is not None: + new.insert(obj) + self.raiseOverlay() + return new + + def insert(self, new, pos=None, neighbor=None): + if self.topContainer is not None: + self.topContainer.containerChanged(None) + self.layout.addWidget(new) + self.topContainer = new + #print self, "set top:", new + new._container = self + self.raiseOverlay() + #print "Insert top:", new + + def count(self): + if self.topContainer is None: + return 0 + return 1 + + + #def paintEvent(self, ev): + #self.drawDockOverlay() + + def resizeEvent(self, ev): + self.resizeOverlay(self.size()) + + def addTempArea(self): + if self.home is None: + area = DockArea(temporary=True, home=self) + self.tempAreas.append(area) + win = QtGui.QMainWindow() + win.setCentralWidget(area) + area.win = win + win.show() + else: + area = self.home.addTempArea() + #print "added temp area", area, area.window() + return area + + def floatDock(self, dock): + """Removes *dock* from this DockArea and places it in a new window.""" + area = self.addTempArea() + area.win.resize(dock.size()) + area.moveDock(dock, 'top', None) + + + def removeTempArea(self, area): + self.tempAreas.remove(area) + #print "close window", area.window() + area.window().close() + + def saveState(self): + """ + Return a serialized (storable) representation of the state of + all Docks in this DockArea.""" + state = {'main': self.childState(self.topContainer), 'float': []} + for a in self.tempAreas: + geo = a.win.geometry() + geo = (geo.x(), geo.y(), geo.width(), geo.height()) + state['float'].append((a.saveState(), geo)) + return state + + def childState(self, obj): + if isinstance(obj, Dock): + return ('dock', obj.name(), {}) + else: + childs = [] + for i in range(obj.count()): + childs.append(self.childState(obj.widget(i))) + return (obj.type(), childs, obj.saveState()) + + + def restoreState(self, state): + """ + Restore Dock configuration as generated by saveState. + + Note that this function does not create any Docks--it will only + restore the arrangement of an existing set of Docks. + + """ + + ## 1) make dict of all docks and list of existing containers + containers, docks = self.findAll() + oldTemps = self.tempAreas[:] + #print "found docks:", docks + + ## 2) create container structure, move docks into new containers + self.buildFromState(state['main'], docks, self) + + ## 3) create floating areas, populate + for s in state['float']: + a = self.addTempArea() + a.buildFromState(s[0]['main'], docks, a) + a.win.setGeometry(*s[1]) + + ## 4) Add any remaining docks to the bottom + for d in docks.values(): + self.moveDock(d, 'below', None) + + #print "\nKill old containers:" + ## 5) kill old containers + for c in containers: + c.close() + for a in oldTemps: + a.apoptose() + + + def buildFromState(self, state, docks, root, depth=0): + typ, contents, state = state + pfx = " " * depth + if typ == 'dock': + try: + obj = docks[contents] + del docks[contents] + except KeyError: + raise Exception('Cannot restore dock state; no dock with name "%s"' % contents) + else: + obj = self.makeContainer(typ) + + root.insert(obj, 'after') + #print pfx+"Add:", obj, " -> ", root + + if typ != 'dock': + for o in contents: + self.buildFromState(o, docks, obj, depth+1) + obj.apoptose(propagate=False) + obj.restoreState(state) ## this has to be done later? + + + def findAll(self, obj=None, c=None, d=None): + if obj is None: + obj = self.topContainer + + ## check all temp areas first + if c is None: + c = [] + d = {} + for a in self.tempAreas: + c1, d1 = a.findAll() + c.extend(c1) + d.update(d1) + + if isinstance(obj, Dock): + d[obj.name()] = obj + elif obj is not None: + c.append(obj) + for i in range(obj.count()): + o2 = obj.widget(i) + c2, d2 = self.findAll(o2) + c.extend(c2) + d.update(d2) + return (c, d) + + def apoptose(self): + #print "apoptose area:", self.temporary, self.topContainer, self.topContainer.count() + if self.temporary and self.topContainer.count() == 0: + self.topContainer = None + self.home.removeTempArea(self) + #self.close() + + ## PySide bug: We need to explicitly redefine these methods + ## or else drag/drop events will not be delivered. + def dragEnterEvent(self, *args): + DockDrop.dragEnterEvent(self, *args) + + def dragMoveEvent(self, *args): + DockDrop.dragMoveEvent(self, *args) + + def dragLeaveEvent(self, *args): + DockDrop.dragLeaveEvent(self, *args) + + def dropEvent(self, *args): + DockDrop.dropEvent(self, *args) + + + diff --git a/pyqtgraph/dockarea/DockDrop.py b/pyqtgraph/dockarea/DockDrop.py new file mode 100644 index 00000000..acab28cd --- /dev/null +++ b/pyqtgraph/dockarea/DockDrop.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui + +class DockDrop(object): + """Provides dock-dropping methods""" + def __init__(self, allowedAreas=None): + object.__init__(self) + if allowedAreas is None: + allowedAreas = ['center', 'right', 'left', 'top', 'bottom'] + self.allowedAreas = set(allowedAreas) + self.setAcceptDrops(True) + self.dropArea = None + self.overlay = DropAreaOverlay(self) + self.overlay.raise_() + + def resizeOverlay(self, size): + self.overlay.resize(size) + + def raiseOverlay(self): + self.overlay.raise_() + + def dragEnterEvent(self, ev): + src = ev.source() + if hasattr(src, 'implements') and src.implements('dock'): + #print "drag enter accept" + ev.accept() + else: + #print "drag enter ignore" + ev.ignore() + + def dragMoveEvent(self, ev): + #print "drag move" + ld = ev.pos().x() + rd = self.width() - ld + td = ev.pos().y() + bd = self.height() - td + + mn = min(ld, rd, td, bd) + if mn > 30: + self.dropArea = "center" + elif (ld == mn or td == mn) and mn > self.height()/3.: + self.dropArea = "center" + elif (rd == mn or ld == mn) and mn > self.width()/3.: + self.dropArea = "center" + + elif rd == mn: + self.dropArea = "right" + elif ld == mn: + self.dropArea = "left" + elif td == mn: + self.dropArea = "top" + elif bd == mn: + self.dropArea = "bottom" + + if ev.source() is self and self.dropArea == 'center': + #print " no self-center" + self.dropArea = None + ev.ignore() + elif self.dropArea not in self.allowedAreas: + #print " not allowed" + self.dropArea = None + ev.ignore() + else: + #print " ok" + ev.accept() + self.overlay.setDropArea(self.dropArea) + + def dragLeaveEvent(self, ev): + self.dropArea = None + self.overlay.setDropArea(self.dropArea) + + def dropEvent(self, ev): + area = self.dropArea + if area is None: + return + if area == 'center': + area = 'above' + self.area.moveDock(ev.source(), area, self) + self.dropArea = None + self.overlay.setDropArea(self.dropArea) + + + +class DropAreaOverlay(QtGui.QWidget): + """Overlay widget that draws drop areas during a drag-drop operation""" + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.dropArea = None + self.hide() + self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) + + def setDropArea(self, area): + self.dropArea = area + if area is None: + self.hide() + else: + ## Resize overlay to just the region where drop area should be displayed. + ## This works around a Qt bug--can't display transparent widgets over QGLWidget + prgn = self.parent().rect() + rgn = QtCore.QRect(prgn) + w = min(30, prgn.width()/3.) + h = min(30, prgn.height()/3.) + + if self.dropArea == 'left': + rgn.setWidth(w) + elif self.dropArea == 'right': + rgn.setLeft(rgn.left() + prgn.width() - w) + elif self.dropArea == 'top': + rgn.setHeight(h) + elif self.dropArea == 'bottom': + rgn.setTop(rgn.top() + prgn.height() - h) + elif self.dropArea == 'center': + rgn.adjust(w, h, -w, -h) + self.setGeometry(rgn) + self.show() + + self.update() + + def paintEvent(self, ev): + if self.dropArea is None: + return + p = QtGui.QPainter(self) + rgn = self.rect() + + p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 255, 50))) + p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 150), 3)) + p.drawRect(rgn) diff --git a/pyqtgraph/dockarea/__init__.py b/pyqtgraph/dockarea/__init__.py new file mode 100644 index 00000000..f67c50c3 --- /dev/null +++ b/pyqtgraph/dockarea/__init__.py @@ -0,0 +1,2 @@ +from .DockArea import DockArea +from .Dock import Dock \ No newline at end of file diff --git a/pyqtgraph/exceptionHandling.py b/pyqtgraph/exceptionHandling.py new file mode 100644 index 00000000..daa821b7 --- /dev/null +++ b/pyqtgraph/exceptionHandling.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- +"""This module installs a wrapper around sys.excepthook which allows multiple +new exception handlers to be registered. + +Optionally, the wrapper also stops exceptions from causing long-term storage +of local stack frames. This has two major effects: + - Unhandled exceptions will no longer cause memory leaks + (If an exception occurs while a lot of data is present on the stack, + such as when loading large files, the data would ordinarily be kept + until the next exception occurs. We would rather release this memory + as soon as possible.) + - Some debuggers may have a hard time handling uncaught exceptions + +The module also provides a callback mechanism allowing others to respond +to exceptions. +""" + +import sys, time +#from lib.Manager import logMsg +import traceback +#from log import * + +#logging = False + +callbacks = [] +clear_tracebacks = False + +def register(fn): + """ + Register a callable to be invoked when there is an unhandled exception. + The callback will be passed the output of sys.exc_info(): (exception type, exception, traceback) + Multiple callbacks will be invoked in the order they were registered. + """ + callbacks.append(fn) + +def unregister(fn): + """Unregister a previously registered callback.""" + callbacks.remove(fn) + +def setTracebackClearing(clear=True): + """ + Enable or disable traceback clearing. + By default, clearing is disabled and Python will indefinitely store unhandled exception stack traces. + This function is provided since Python's default behavior can cause unexpected retention of + large memory-consuming objects. + """ + global clear_tracebacks + clear_tracebacks = clear + +class ExceptionHandler(object): + def __call__(self, *args): + ## call original exception handler first (prints exception) + global original_excepthook, callbacks, clear_tracebacks + print("===== %s =====" % str(time.strftime("%Y.%m.%d %H:%m:%S", time.localtime(time.time())))) + ret = original_excepthook(*args) + + for cb in callbacks: + try: + cb(*args) + except: + print(" --------------------------------------------------------------") + print(" Error occurred during exception callback %s" % str(cb)) + print(" --------------------------------------------------------------") + traceback.print_exception(*sys.exc_info()) + + + ## Clear long-term storage of last traceback to prevent memory-hogging. + ## (If an exception occurs while a lot of data is present on the stack, + ## such as when loading large files, the data would ordinarily be kept + ## until the next exception occurs. We would rather release this memory + ## as soon as possible.) + if clear_tracebacks is True: + sys.last_traceback = None + + def implements(self, interface=None): + ## this just makes it easy for us to detect whether an ExceptionHook is already installed. + if interface is None: + return ['ExceptionHandler'] + else: + return interface == 'ExceptionHandler' + + + +## replace built-in excepthook only if this has not already been done +if not (hasattr(sys.excepthook, 'implements') and sys.excepthook.implements('ExceptionHandler')): + original_excepthook = sys.excepthook + sys.excepthook = ExceptionHandler() + + + diff --git a/pyqtgraph/exporters/CSVExporter.py b/pyqtgraph/exporters/CSVExporter.py new file mode 100644 index 00000000..0439fc35 --- /dev/null +++ b/pyqtgraph/exporters/CSVExporter.py @@ -0,0 +1,59 @@ +import pyqtgraph as pg +from pyqtgraph.Qt import QtGui, QtCore +from .Exporter import Exporter +from pyqtgraph.parametertree import Parameter + + +__all__ = ['CSVExporter'] + + +class CSVExporter(Exporter): + Name = "CSV from plot data" + windows = [] + def __init__(self, item): + Exporter.__init__(self, item) + self.params = Parameter(name='params', type='group', children=[ + {'name': 'separator', 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']}, + {'name': 'precision', 'type': 'int', 'value': 10, 'limits': [0, None]}, + ]) + + def parameters(self): + return self.params + + def export(self, fileName=None): + + if not isinstance(self.item, pg.PlotItem): + raise Exception("Must have a PlotItem selected for CSV export.") + + if fileName is None: + self.fileSaveDialog(filter=["*.csv", "*.tsv"]) + return + + fd = open(fileName, 'w') + data = [] + header = [] + for c in self.item.curves: + data.append(c.getData()) + header.extend(['x', 'y']) + + if self.params['separator'] == 'comma': + sep = ',' + else: + sep = '\t' + + fd.write(sep.join(header) + '\n') + i = 0 + numFormat = '%%0.%dg' % self.params['precision'] + numRows = reduce(max, [len(d[0]) for d in data]) + for i in range(numRows): + for d in data: + if i < len(d[0]): + fd.write(numFormat % d[0][i] + sep + numFormat % d[1][i] + sep) + else: + fd.write(' %s %s' % (sep, sep)) + fd.write('\n') + fd.close() + + + + diff --git a/pyqtgraph/exporters/Exporter.py b/pyqtgraph/exporters/Exporter.py new file mode 100644 index 00000000..6371a3b9 --- /dev/null +++ b/pyqtgraph/exporters/Exporter.py @@ -0,0 +1,175 @@ +from pyqtgraph.widgets.FileDialog import FileDialog +import pyqtgraph as pg +from pyqtgraph.Qt import QtGui, QtCore, QtSvg +from pyqtgraph.python2_3 import asUnicode +import os, re +LastExportDirectory = None + + +class Exporter(object): + """ + Abstract class used for exporting graphics to file / printer / whatever. + """ + allowCopy = False # subclasses set this to True if they can use the copy buffer + + def __init__(self, item): + """ + Initialize with the item to be exported. + Can be an individual graphics item or a scene. + """ + object.__init__(self) + self.item = item + + #def item(self): + #return self.item + + def parameters(self): + """Return the parameters used to configure this exporter.""" + raise Exception("Abstract method must be overridden in subclass.") + + def export(self, fileName=None, toBytes=False, copy=False): + """ + If *fileName* is None, pop-up a file dialog. + If *toBytes* is True, return a bytes object rather than writing to file. + If *copy* is True, export to the copy buffer rather than writing to file. + """ + raise Exception("Abstract method must be overridden in subclass.") + + def fileSaveDialog(self, filter=None, opts=None): + ## Show a file dialog, call self.export(fileName) when finished. + if opts is None: + opts = {} + self.fileDialog = FileDialog() + self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + if filter is not None: + if isinstance(filter, basestring): + self.fileDialog.setNameFilter(filter) + elif isinstance(filter, list): + self.fileDialog.setNameFilters(filter) + global LastExportDirectory + exportDir = LastExportDirectory + if exportDir is not None: + self.fileDialog.setDirectory(exportDir) + self.fileDialog.show() + self.fileDialog.opts = opts + self.fileDialog.fileSelected.connect(self.fileSaveFinished) + return + + def fileSaveFinished(self, fileName): + fileName = asUnicode(fileName) + global LastExportDirectory + LastExportDirectory = os.path.split(fileName)[0] + + ## If file name does not match selected extension, append it now + ext = os.path.splitext(fileName)[1].lower().lstrip('.') + selectedExt = re.search(r'\*\.(\w+)\b', asUnicode(self.fileDialog.selectedNameFilter())) + if selectedExt is not None: + selectedExt = selectedExt.groups()[0].lower() + if ext != selectedExt: + fileName = fileName + '.' + selectedExt.lstrip('.') + + self.export(fileName=fileName, **self.fileDialog.opts) + + def getScene(self): + if isinstance(self.item, pg.GraphicsScene): + return self.item + else: + return self.item.scene() + + def getSourceRect(self): + if isinstance(self.item, pg.GraphicsScene): + w = self.item.getViewWidget() + return w.viewportTransform().inverted()[0].mapRect(w.rect()) + else: + return self.item.sceneBoundingRect() + + def getTargetRect(self): + if isinstance(self.item, pg.GraphicsScene): + return self.item.getViewWidget().rect() + else: + return self.item.mapRectToDevice(self.item.boundingRect()) + + def setExportMode(self, export, opts=None): + """ + Call setExportMode(export, opts) on all items that will + be painted during the export. This informs the item + that it is about to be painted for export, allowing it to + alter its appearance temporarily + + + *export* - bool; must be True before exporting and False afterward + *opts* - dict; common parameters are 'antialias' and 'background' + """ + if opts is None: + opts = {} + for item in self.getPaintItems(): + if hasattr(item, 'setExportMode'): + item.setExportMode(export, opts) + + def getPaintItems(self, root=None): + """Return a list of all items that should be painted in the correct order.""" + if root is None: + root = self.item + preItems = [] + postItems = [] + if isinstance(root, QtGui.QGraphicsScene): + childs = [i for i in root.items() if i.parentItem() is None] + rootItem = [] + else: + childs = root.childItems() + rootItem = [root] + childs.sort(key=lambda a: a.zValue()) + while len(childs) > 0: + ch = childs.pop(0) + tree = self.getPaintItems(ch) + if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0): + preItems.extend(tree) + else: + postItems.extend(tree) + + return preItems + rootItem + postItems + + def render(self, painter, targetRect, sourceRect, item=None): + + #if item is None: + #item = self.item + #preItems = [] + #postItems = [] + #if isinstance(item, QtGui.QGraphicsScene): + #childs = [i for i in item.items() if i.parentItem() is None] + #rootItem = [] + #else: + #childs = item.childItems() + #rootItem = [item] + #childs.sort(lambda a,b: cmp(a.zValue(), b.zValue())) + #while len(childs) > 0: + #ch = childs.pop(0) + #if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0): + #preItems.extend(tree) + #else: + #postItems.extend(tree) + + #for ch in preItems: + #self.render(painter, sourceRect, targetRect, item=ch) + ### paint root here + #for ch in postItems: + #self.render(painter, sourceRect, targetRect, item=ch) + + + self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) + + #def writePs(self, fileName=None, item=None): + #if fileName is None: + #self.fileSaveDialog(self.writeSvg, filter="PostScript (*.ps)") + #return + #if item is None: + #item = self + #printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) + #printer.setOutputFileName(fileName) + #painter = QtGui.QPainter(printer) + #self.render(painter) + #painter.end() + + #def writeToPrinter(self): + #pass diff --git a/pyqtgraph/exporters/ImageExporter.py b/pyqtgraph/exporters/ImageExporter.py new file mode 100644 index 00000000..9fb77e2a --- /dev/null +++ b/pyqtgraph/exporters/ImageExporter.py @@ -0,0 +1,101 @@ +from .Exporter import Exporter +from pyqtgraph.parametertree import Parameter +from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE +import pyqtgraph as pg +import numpy as np + +__all__ = ['ImageExporter'] + +class ImageExporter(Exporter): + Name = "Image File (PNG, TIF, JPG, ...)" + allowCopy = True + + def __init__(self, item): + Exporter.__init__(self, item) + tr = self.getTargetRect() + if isinstance(item, QtGui.QGraphicsItem): + scene = item.scene() + else: + scene = item + bgbrush = scene.views()[0].backgroundBrush() + bg = bgbrush.color() + if bgbrush.style() == QtCore.Qt.NoBrush: + bg.setAlpha(0) + + self.params = Parameter(name='params', type='group', children=[ + {'name': 'width', 'type': 'int', 'value': tr.width(), 'limits': (0, None)}, + {'name': 'height', 'type': 'int', 'value': tr.height(), 'limits': (0, None)}, + {'name': 'antialias', 'type': 'bool', 'value': True}, + {'name': 'background', 'type': 'color', 'value': bg}, + ]) + self.params.param('width').sigValueChanged.connect(self.widthChanged) + self.params.param('height').sigValueChanged.connect(self.heightChanged) + + def widthChanged(self): + sr = self.getSourceRect() + ar = float(sr.height()) / sr.width() + self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) + + def heightChanged(self): + sr = self.getSourceRect() + ar = float(sr.width()) / sr.height() + self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) + + def parameters(self): + return self.params + + def export(self, fileName=None, toBytes=False, copy=False): + if fileName is None and not toBytes and not copy: + if USE_PYSIDE: + filter = ["*."+str(f) for f in QtGui.QImageWriter.supportedImageFormats()] + else: + filter = ["*."+bytes(f).decode('utf-8') for f in QtGui.QImageWriter.supportedImageFormats()] + preferred = ['*.png', '*.tif', '*.jpg'] + for p in preferred[::-1]: + if p in filter: + filter.remove(p) + filter.insert(0, p) + self.fileSaveDialog(filter=filter) + return + + targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) + sourceRect = self.getSourceRect() + + + #self.png = QtGui.QImage(targetRect.size(), QtGui.QImage.Format_ARGB32) + #self.png.fill(pyqtgraph.mkColor(self.params['background'])) + w, h = self.params['width'], self.params['height'] + if w == 0 or h == 0: + raise Exception("Cannot export image with size=0 (requested export size is %dx%d)" % (w,h)) + bg = np.empty((self.params['width'], self.params['height'], 4), dtype=np.ubyte) + color = self.params['background'] + bg[:,:,0] = color.blue() + bg[:,:,1] = color.green() + bg[:,:,2] = color.red() + bg[:,:,3] = color.alpha() + self.png = pg.makeQImage(bg, alpha=True) + + ## set resolution of image: + origTargetRect = self.getTargetRect() + resolutionScale = targetRect.width() / origTargetRect.width() + #self.png.setDotsPerMeterX(self.png.dotsPerMeterX() * resolutionScale) + #self.png.setDotsPerMeterY(self.png.dotsPerMeterY() * resolutionScale) + + painter = QtGui.QPainter(self.png) + #dtr = painter.deviceTransform() + try: + self.setExportMode(True, {'antialias': self.params['antialias'], 'background': self.params['background'], 'painter': painter, 'resolutionScale': resolutionScale}) + painter.setRenderHint(QtGui.QPainter.Antialiasing, self.params['antialias']) + self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) + finally: + self.setExportMode(False) + painter.end() + + if copy: + QtGui.QApplication.clipboard().setImage(self.png) + elif toBytes: + return self.png + else: + self.png.save(fileName) + + \ No newline at end of file diff --git a/pyqtgraph/exporters/Matplotlib.py b/pyqtgraph/exporters/Matplotlib.py new file mode 100644 index 00000000..76f878d2 --- /dev/null +++ b/pyqtgraph/exporters/Matplotlib.py @@ -0,0 +1,74 @@ +import pyqtgraph as pg +from pyqtgraph.Qt import QtGui, QtCore +from .Exporter import Exporter + + +__all__ = ['MatplotlibExporter'] + + +class MatplotlibExporter(Exporter): + Name = "Matplotlib Window" + windows = [] + def __init__(self, item): + Exporter.__init__(self, item) + + def parameters(self): + return None + + def export(self, fileName=None): + + if isinstance(self.item, pg.PlotItem): + mpw = MatplotlibWindow() + MatplotlibExporter.windows.append(mpw) + fig = mpw.getFigure() + + ax = fig.add_subplot(111) + ax.clear() + #ax.grid(True) + + for item in self.item.curves: + x, y = item.getData() + opts = item.opts + pen = pg.mkPen(opts['pen']) + if pen.style() == QtCore.Qt.NoPen: + linestyle = '' + else: + linestyle = '-' + color = tuple([c/255. for c in pg.colorTuple(pen.color())]) + symbol = opts['symbol'] + if symbol == 't': + symbol = '^' + symbolPen = pg.mkPen(opts['symbolPen']) + symbolBrush = pg.mkBrush(opts['symbolBrush']) + markeredgecolor = tuple([c/255. for c in pg.colorTuple(symbolPen.color())]) + markerfacecolor = tuple([c/255. for c in pg.colorTuple(symbolBrush.color())]) + + if opts['fillLevel'] is not None and opts['fillBrush'] is not None: + fillBrush = pg.mkBrush(opts['fillBrush']) + fillcolor = tuple([c/255. for c in pg.colorTuple(fillBrush.color())]) + ax.fill_between(x=x, y1=y, y2=opts['fillLevel'], facecolor=fillcolor) + + ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(), linestyle=linestyle, markeredgecolor=markeredgecolor, markerfacecolor=markerfacecolor) + + xr, yr = self.item.viewRange() + ax.set_xbound(*xr) + ax.set_ybound(*yr) + mpw.draw() + else: + raise Exception("Matplotlib export currently only works with plot items") + + + +class MatplotlibWindow(QtGui.QMainWindow): + def __init__(self): + import pyqtgraph.widgets.MatplotlibWidget + QtGui.QMainWindow.__init__(self) + self.mpl = pyqtgraph.widgets.MatplotlibWidget.MatplotlibWidget() + self.setCentralWidget(self.mpl) + self.show() + + def __getattr__(self, attr): + return getattr(self.mpl, attr) + + def closeEvent(self, ev): + MatplotlibExporter.windows.remove(self) diff --git a/pyqtgraph/exporters/PrintExporter.py b/pyqtgraph/exporters/PrintExporter.py new file mode 100644 index 00000000..5b31b45d --- /dev/null +++ b/pyqtgraph/exporters/PrintExporter.py @@ -0,0 +1,65 @@ +from .Exporter import Exporter +from pyqtgraph.parametertree import Parameter +from pyqtgraph.Qt import QtGui, QtCore, QtSvg +import re + +__all__ = ['PrintExporter'] +#__all__ = [] ## Printer is disabled for now--does not work very well. + +class PrintExporter(Exporter): + Name = "Printer" + def __init__(self, item): + Exporter.__init__(self, item) + tr = self.getTargetRect() + self.params = Parameter(name='params', type='group', children=[ + {'name': 'width', 'type': 'float', 'value': 0.1, 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, + {'name': 'height', 'type': 'float', 'value': (0.1 * tr.height()) / tr.width(), 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, + ]) + self.params.param('width').sigValueChanged.connect(self.widthChanged) + self.params.param('height').sigValueChanged.connect(self.heightChanged) + + def widthChanged(self): + sr = self.getSourceRect() + ar = sr.height() / sr.width() + self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) + + def heightChanged(self): + sr = self.getSourceRect() + ar = sr.width() / sr.height() + self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) + + def parameters(self): + return self.params + + def export(self, fileName=None): + printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) + dialog = QtGui.QPrintDialog(printer) + dialog.setWindowTitle("Print Document") + if dialog.exec_() != QtGui.QDialog.Accepted: + return; + + #dpi = QtGui.QDesktopWidget().physicalDpiX() + + #self.svg.setSize(QtCore.QSize(100,100)) + #self.svg.setResolution(600) + #res = printer.resolution() + sr = self.getSourceRect() + #res = sr.width() * .4 / (self.params['width'] * 100 / 2.54) + res = QtGui.QDesktopWidget().physicalDpiX() + printer.setResolution(res) + rect = printer.pageRect() + center = rect.center() + h = self.params['height'] * res * 100. / 2.54 + w = self.params['width'] * res * 100. / 2.54 + x = center.x() - w/2. + y = center.y() - h/2. + + targetRect = QtCore.QRect(x, y, w, h) + sourceRect = self.getSourceRect() + painter = QtGui.QPainter(printer) + try: + self.setExportMode(True, {'painter': painter}) + self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) + finally: + self.setExportMode(False) + painter.end() diff --git a/pyqtgraph/exporters/SVGExporter.py b/pyqtgraph/exporters/SVGExporter.py new file mode 100644 index 00000000..62b49d30 --- /dev/null +++ b/pyqtgraph/exporters/SVGExporter.py @@ -0,0 +1,488 @@ +from .Exporter import Exporter +from pyqtgraph.python2_3 import asUnicode +from pyqtgraph.parametertree import Parameter +from pyqtgraph.Qt import QtGui, QtCore, QtSvg +import pyqtgraph as pg +import re +import xml.dom.minidom as xml +import numpy as np + + +__all__ = ['SVGExporter'] + +class SVGExporter(Exporter): + Name = "Scalable Vector Graphics (SVG)" + allowCopy=True + + def __init__(self, item): + Exporter.__init__(self, item) + #tr = self.getTargetRect() + self.params = Parameter(name='params', type='group', children=[ + #{'name': 'width', 'type': 'float', 'value': tr.width(), 'limits': (0, None)}, + #{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)}, + #{'name': 'viewbox clipping', 'type': 'bool', 'value': True}, + #{'name': 'normalize coordinates', 'type': 'bool', 'value': True}, + #{'name': 'normalize line width', 'type': 'bool', 'value': True}, + ]) + #self.params.param('width').sigValueChanged.connect(self.widthChanged) + #self.params.param('height').sigValueChanged.connect(self.heightChanged) + + def widthChanged(self): + sr = self.getSourceRect() + ar = sr.height() / sr.width() + self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) + + def heightChanged(self): + sr = self.getSourceRect() + ar = sr.width() / sr.height() + self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) + + def parameters(self): + return self.params + + def export(self, fileName=None, toBytes=False, copy=False): + if toBytes is False and copy is False and fileName is None: + self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)") + return + #self.svg = QtSvg.QSvgGenerator() + #self.svg.setFileName(fileName) + #dpi = QtGui.QDesktopWidget().physicalDpiX() + ### not really sure why this works, but it seems to be important: + #self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.)) + #self.svg.setResolution(dpi) + ##self.svg.setViewBox() + #targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) + #sourceRect = self.getSourceRect() + + #painter = QtGui.QPainter(self.svg) + #try: + #self.setExportMode(True) + #self.render(painter, QtCore.QRectF(targetRect), sourceRect) + #finally: + #self.setExportMode(False) + #painter.end() + + ## Workaround to set pen widths correctly + #data = open(fileName).readlines() + #for i in range(len(data)): + #line = data[i] + #m = re.match(r'( + +pyqtgraph SVG export +Generated with Qt and pyqtgraph + + +""" + +def generateSvg(item): + global xmlHeader + try: + node = _generateItemSvg(item) + finally: + ## reset export mode for all items in the tree + if isinstance(item, QtGui.QGraphicsScene): + items = item.items() + else: + items = [item] + for i in items: + items.extend(i.childItems()) + for i in items: + if hasattr(i, 'setExportMode'): + i.setExportMode(False) + + cleanXml(node) + + return xmlHeader + node.toprettyxml(indent=' ') + "\n\n" + + +def _generateItemSvg(item, nodes=None, root=None): + ## This function is intended to work around some issues with Qt's SVG generator + ## and SVG in general. + ## 1) Qt SVG does not implement clipping paths. This is absurd. + ## The solution is to let Qt generate SVG for each item independently, + ## then glue them together manually with clipping. + ## + ## The format Qt generates for all items looks like this: + ## + ## + ## + ## one or more of: or or + ## + ## + ## one or more of: or or + ## + ## . . . + ## + ## + ## 2) There seems to be wide disagreement over whether path strokes + ## should be scaled anisotropically. + ## see: http://web.mit.edu/jonas/www/anisotropy/ + ## Given that both inkscape and illustrator seem to prefer isotropic + ## scaling, we will optimize for those cases. + ## + ## 3) Qt generates paths using non-scaling-stroke from SVG 1.2, but + ## inkscape only supports 1.1. + ## + ## Both 2 and 3 can be addressed by drawing all items in world coordinates. + + prof = pg.debug.Profiler('generateItemSvg %s' % str(item), disabled=True) + + if nodes is None: ## nodes maps all node IDs to their XML element. + ## this allows us to ensure all elements receive unique names. + nodes = {} + + if root is None: + root = item + + ## Skip hidden items + if hasattr(item, 'isVisible') and not item.isVisible(): + return None + + ## If this item defines its own SVG generator, use that. + if hasattr(item, 'generateSvg'): + return item.generateSvg(nodes) + + + ## Generate SVG text for just this item (exclude its children; we'll handle them later) + tr = QtGui.QTransform() + if isinstance(item, QtGui.QGraphicsScene): + xmlStr = "\n\n" + doc = xml.parseString(xmlStr) + childs = [i for i in item.items() if i.parentItem() is None] + elif item.__class__.paint == QtGui.QGraphicsItem.paint: + xmlStr = "\n\n" + doc = xml.parseString(xmlStr) + childs = item.childItems() + else: + childs = item.childItems() + tr = itemTransform(item, item.scene()) + + ## offset to corner of root item + if isinstance(root, QtGui.QGraphicsScene): + rootPos = QtCore.QPoint(0,0) + else: + rootPos = root.scenePos() + tr2 = QtGui.QTransform() + tr2.translate(-rootPos.x(), -rootPos.y()) + tr = tr * tr2 + #print item, pg.SRTTransform(tr) + + #tr.translate(item.pos().x(), item.pos().y()) + #tr = tr * item.transform() + arr = QtCore.QByteArray() + buf = QtCore.QBuffer(arr) + svg = QtSvg.QSvgGenerator() + svg.setOutputDevice(buf) + dpi = QtGui.QDesktopWidget().physicalDpiX() + ### not really sure why this works, but it seems to be important: + #self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.)) + svg.setResolution(dpi) + + p = QtGui.QPainter() + p.begin(svg) + if hasattr(item, 'setExportMode'): + item.setExportMode(True, {'painter': p}) + try: + p.setTransform(tr) + item.paint(p, QtGui.QStyleOptionGraphicsItem(), None) + finally: + p.end() + ## Can't do this here--we need to wait until all children have painted as well. + ## this is taken care of in generateSvg instead. + #if hasattr(item, 'setExportMode'): + #item.setExportMode(False) + + xmlStr = bytes(arr).decode('utf-8') + doc = xml.parseString(xmlStr) + + try: + ## Get top-level group for this item + g1 = doc.getElementsByTagName('g')[0] + ## get list of sub-groups + g2 = [n for n in g1.childNodes if isinstance(n, xml.Element) and n.tagName == 'g'] + except: + print(doc.toxml()) + raise + + prof.mark('render') + + ## Get rid of group transformation matrices by applying + ## transformation to inner coordinates + correctCoordinates(g1, item) + prof.mark('correct') + ## make sure g1 has the transformation matrix + #m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32()) + #g1.setAttribute('transform', "matrix(%f,%f,%f,%f,%f,%f)" % m) + + #print "=================",item,"=====================" + #print g1.toprettyxml(indent=" ", newl='') + + ## Inkscape does not support non-scaling-stroke (this is SVG 1.2, inkscape supports 1.1) + ## So we need to correct anything attempting to use this. + #correctStroke(g1, item, root) + + ## decide on a name for this item + baseName = item.__class__.__name__ + i = 1 + while True: + name = baseName + "_%d" % i + if name not in nodes: + break + i += 1 + nodes[name] = g1 + g1.setAttribute('id', name) + + ## If this item clips its children, we need to take care of that. + childGroup = g1 ## add children directly to this node unless we are clipping + if not isinstance(item, QtGui.QGraphicsScene): + ## See if this item clips its children + if int(item.flags() & item.ItemClipsChildrenToShape) > 0: + ## Generate svg for just the path + #if isinstance(root, QtGui.QGraphicsScene): + #path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) + #else: + #path = QtGui.QGraphicsPathItem(root.mapToParent(item.mapToItem(root, item.shape()))) + path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) + item.scene().addItem(path) + try: + pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0] + finally: + item.scene().removeItem(path) + + ## and for the clipPath element + clip = name + '_clip' + clipNode = g1.ownerDocument.createElement('clipPath') + clipNode.setAttribute('id', clip) + clipNode.appendChild(pathNode) + g1.appendChild(clipNode) + + childGroup = g1.ownerDocument.createElement('g') + childGroup.setAttribute('clip-path', 'url(#%s)' % clip) + g1.appendChild(childGroup) + prof.mark('clipping') + + ## Add all child items as sub-elements. + childs.sort(key=lambda c: c.zValue()) + for ch in childs: + cg = _generateItemSvg(ch, nodes, root) + if cg is None: + continue + childGroup.appendChild(cg) ### this isn't quite right--some items draw below their parent (good enough for now) + prof.mark('children') + prof.finish() + return g1 + +def correctCoordinates(node, item): + ## Remove transformation matrices from tags by applying matrix to coordinates inside. + ## Each item is represented by a single top-level group with one or more groups inside. + ## Each inner group contains one or more drawing primitives, possibly of different types. + groups = node.getElementsByTagName('g') + + ## Since we leave text unchanged, groups which combine text and non-text primitives must be split apart. + ## (if at some point we start correcting text transforms as well, then it should be safe to remove this) + groups2 = [] + for grp in groups: + subGroups = [grp.cloneNode(deep=False)] + textGroup = None + for ch in grp.childNodes[:]: + if isinstance(ch, xml.Element): + if textGroup is None: + textGroup = ch.tagName == 'text' + if ch.tagName == 'text': + if textGroup is False: + subGroups.append(grp.cloneNode(deep=False)) + textGroup = True + else: + if textGroup is True: + subGroups.append(grp.cloneNode(deep=False)) + textGroup = False + subGroups[-1].appendChild(ch) + groups2.extend(subGroups) + for sg in subGroups: + node.insertBefore(sg, grp) + node.removeChild(grp) + groups = groups2 + + + for grp in groups: + matrix = grp.getAttribute('transform') + match = re.match(r'matrix\((.*)\)', matrix) + if match is None: + vals = [1,0,0,1,0,0] + else: + vals = [float(a) for a in match.groups()[0].split(',')] + tr = np.array([[vals[0], vals[2], vals[4]], [vals[1], vals[3], vals[5]]]) + + removeTransform = False + for ch in grp.childNodes: + if not isinstance(ch, xml.Element): + continue + if ch.tagName == 'polyline': + removeTransform = True + coords = np.array([[float(a) for a in c.split(',')] for c in ch.getAttribute('points').strip().split(' ')]) + coords = pg.transformCoordinates(tr, coords, transpose=True) + ch.setAttribute('points', ' '.join([','.join([str(a) for a in c]) for c in coords])) + elif ch.tagName == 'path': + removeTransform = True + newCoords = '' + oldCoords = ch.getAttribute('d').strip() + if oldCoords == '': + continue + for c in oldCoords.split(' '): + x,y = c.split(',') + if x[0].isalpha(): + t = x[0] + x = x[1:] + else: + t = '' + nc = pg.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True) + newCoords += t+str(nc[0,0])+','+str(nc[0,1])+' ' + ch.setAttribute('d', newCoords) + elif ch.tagName == 'text': + removeTransform = False + ## leave text alone for now. Might need this later to correctly render text with outline. + #c = np.array([ + #[float(ch.getAttribute('x')), float(ch.getAttribute('y'))], + #[float(ch.getAttribute('font-size')), 0], + #[0,0]]) + #c = pg.transformCoordinates(tr, c, transpose=True) + #ch.setAttribute('x', str(c[0,0])) + #ch.setAttribute('y', str(c[0,1])) + #fs = c[1]-c[2] + #fs = (fs**2).sum()**0.5 + #ch.setAttribute('font-size', str(fs)) + + ## Correct some font information + families = ch.getAttribute('font-family').split(',') + if len(families) == 1: + font = QtGui.QFont(families[0].strip('" ')) + if font.style() == font.SansSerif: + families.append('sans-serif') + elif font.style() == font.Serif: + families.append('serif') + elif font.style() == font.Courier: + families.append('monospace') + ch.setAttribute('font-family', ', '.join([f if ' ' not in f else '"%s"'%f for f in families])) + + ## correct line widths if needed + if removeTransform and ch.getAttribute('vector-effect') != 'non-scaling-stroke': + w = float(grp.getAttribute('stroke-width')) + s = pg.transformCoordinates(tr, np.array([[w,0], [0,0]]), transpose=True) + w = ((s[0]-s[1])**2).sum()**0.5 + ch.setAttribute('stroke-width', str(w)) + + if removeTransform: + grp.removeAttribute('transform') + +def itemTransform(item, root): + ## Return the transformation mapping item to root + ## (actually to parent coordinate system of root) + + if item is root: + tr = QtGui.QTransform() + tr.translate(*item.pos()) + tr = tr * item.transform() + return tr + + + if int(item.flags() & item.ItemIgnoresTransformations) > 0: + pos = item.pos() + parent = item.parentItem() + if parent is not None: + pos = itemTransform(parent, root).map(pos) + tr = QtGui.QTransform() + tr.translate(pos.x(), pos.y()) + tr = item.transform() * tr + else: + ## find next parent that is either the root item or + ## an item that ignores its transformation + nextRoot = item + while True: + nextRoot = nextRoot.parentItem() + if nextRoot is None: + nextRoot = root + break + if nextRoot is root or int(nextRoot.flags() & nextRoot.ItemIgnoresTransformations) > 0: + break + + if isinstance(nextRoot, QtGui.QGraphicsScene): + tr = item.sceneTransform() + else: + tr = itemTransform(nextRoot, root) * item.itemTransform(nextRoot)[0] + #pos = QtGui.QTransform() + #pos.translate(root.pos().x(), root.pos().y()) + #tr = pos * root.transform() * item.itemTransform(root)[0] + + + return tr + + +#def correctStroke(node, item, root, width=1): + ##print "==============", item, node + #if node.hasAttribute('stroke-width'): + #width = float(node.getAttribute('stroke-width')) + #if node.getAttribute('vector-effect') == 'non-scaling-stroke': + #node.removeAttribute('vector-effect') + #if isinstance(root, QtGui.QGraphicsScene): + #w = item.mapFromScene(pg.Point(width,0)) + #o = item.mapFromScene(pg.Point(0,0)) + #else: + #w = item.mapFromItem(root, pg.Point(width,0)) + #o = item.mapFromItem(root, pg.Point(0,0)) + #w = w-o + ##print " ", w, o, w-o + #w = (w.x()**2 + w.y()**2) ** 0.5 + ##print " ", w + #node.setAttribute('stroke-width', str(w)) + + #for ch in node.childNodes: + #if isinstance(ch, xml.Element): + #correctStroke(ch, item, root, width) + +def cleanXml(node): + ## remove extraneous text; let the xml library do the formatting. + hasElement = False + nonElement = [] + for ch in node.childNodes: + if isinstance(ch, xml.Element): + hasElement = True + cleanXml(ch) + else: + nonElement.append(ch) + + if hasElement: + for ch in nonElement: + node.removeChild(ch) + elif node.tagName == 'g': ## remove childless groups + node.parentNode.removeChild(node) diff --git a/pyqtgraph/exporters/__init__.py b/pyqtgraph/exporters/__init__.py new file mode 100644 index 00000000..3f3c1f1d --- /dev/null +++ b/pyqtgraph/exporters/__init__.py @@ -0,0 +1,27 @@ +Exporters = [] +from pyqtgraph import importModules +#from .. import frozenSupport +import os +d = os.path.split(__file__)[0] +#files = [] +#for f in frozenSupport.listdir(d): + #if frozenSupport.isdir(os.path.join(d, f)) and f != '__pycache__': + #files.append(f) + #elif f[-3:] == '.py' and f not in ['__init__.py', 'Exporter.py']: + #files.append(f[:-3]) + +#for modName in files: + #mod = __import__(modName, globals(), locals(), fromlist=['*']) +for mod in importModules('', globals(), locals(), excludes=['Exporter']).values(): + if hasattr(mod, '__all__'): + names = mod.__all__ + else: + names = [n for n in dir(mod) if n[0] != '_'] + for k in names: + if hasattr(mod, k): + Exporters.append(getattr(mod, k)) + + +def listExporters(): + return Exporters[:] + diff --git a/pyqtgraph/flowchart/Flowchart.py b/pyqtgraph/flowchart/Flowchart.py new file mode 100644 index 00000000..81f9e163 --- /dev/null +++ b/pyqtgraph/flowchart/Flowchart.py @@ -0,0 +1,955 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE +from .Node import * +from pyqtgraph.pgcollections import OrderedDict +from pyqtgraph.widgets.TreeWidget import * + +## pyside and pyqt use incompatible ui files. +if USE_PYSIDE: + from . import FlowchartTemplate_pyside as FlowchartTemplate + from . import FlowchartCtrlTemplate_pyside as FlowchartCtrlTemplate +else: + from . import FlowchartTemplate_pyqt as FlowchartTemplate + from . import FlowchartCtrlTemplate_pyqt as FlowchartCtrlTemplate + +from .Terminal import Terminal +from numpy import ndarray +from . import library +from pyqtgraph.debug import printExc +import pyqtgraph.configfile as configfile +import pyqtgraph.dockarea as dockarea +import pyqtgraph as pg +from . import FlowchartGraphicsView + +def strDict(d): + return dict([(str(k), v) for k, v in d.items()]) + + +def toposort(deps, nodes=None, seen=None, stack=None, depth=0): + """Topological sort. Arguments are: + deps dictionary describing dependencies where a:[b,c] means "a depends on b and c" + nodes optional, specifies list of starting nodes (these should be the nodes + which are not depended on by any other nodes) + """ + + if nodes is None: + ## run through deps to find nodes that are not depended upon + rem = set() + for dep in deps.values(): + rem |= set(dep) + nodes = set(deps.keys()) - rem + if seen is None: + seen = set() + stack = [] + sorted = [] + #print " "*depth, "Starting from", nodes + for n in nodes: + if n in stack: + raise Exception("Cyclic dependency detected", stack + [n]) + if n in seen: + continue + seen.add(n) + #print " "*depth, " descending into", n, deps[n] + sorted.extend( toposort(deps, deps[n], seen, stack+[n], depth=depth+1)) + #print " "*depth, " Added", n + sorted.append(n) + #print " "*depth, " ", sorted + return sorted + + +class Flowchart(Node): + + sigFileLoaded = QtCore.Signal(object) + sigFileSaved = QtCore.Signal(object) + + + #sigOutputChanged = QtCore.Signal() ## inherited from Node + sigChartLoaded = QtCore.Signal() + sigStateChanged = QtCore.Signal() + + def __init__(self, terminals=None, name=None, filePath=None): + if name is None: + name = "Flowchart" + if terminals is None: + terminals = {} + self.filePath = filePath + Node.__init__(self, name, allowAddInput=True, allowAddOutput=True) ## create node without terminals; we'll add these later + + + self.inputWasSet = False ## flag allows detection of changes in the absence of input change. + self._nodes = {} + self.nextZVal = 10 + #self.connects = [] + #self._chartGraphicsItem = FlowchartGraphicsItem(self) + self._widget = None + self._scene = None + self.processing = False ## flag that prevents recursive node updates + + self.widget() + + self.inputNode = Node('Input', allowRemove=False, allowAddOutput=True) + self.outputNode = Node('Output', allowRemove=False, allowAddInput=True) + self.addNode(self.inputNode, 'Input', [-150, 0]) + self.addNode(self.outputNode, 'Output', [300, 0]) + + self.outputNode.sigOutputChanged.connect(self.outputChanged) + self.outputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) + self.inputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) + self.outputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) + self.inputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) + self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) + self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) + + self.viewBox.autoRange(padding = 0.04) + + for name, opts in terminals.items(): + self.addTerminal(name, **opts) + + def setInput(self, **args): + """Set the input values of the flowchart. This will automatically propagate + the new values throughout the flowchart, (possibly) causing the output to change. + """ + #print "setInput", args + #Node.setInput(self, **args) + #print " ....." + self.inputWasSet = True + self.inputNode.setOutput(**args) + + def outputChanged(self): + ## called when output of internal node has changed + vals = self.outputNode.inputValues() + self.widget().outputChanged(vals) + self.setOutput(**vals) + #self.sigOutputChanged.emit(self) + + def output(self): + """Return a dict of the values on the Flowchart's output terminals. + """ + return self.outputNode.inputValues() + + def nodes(self): + return self._nodes + + def addTerminal(self, name, **opts): + term = Node.addTerminal(self, name, **opts) + name = term.name() + if opts['io'] == 'in': ## inputs to the flowchart become outputs on the input node + opts['io'] = 'out' + opts['multi'] = False + self.inputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) + try: + term2 = self.inputNode.addTerminal(name, **opts) + finally: + self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) + + else: + opts['io'] = 'in' + #opts['multi'] = False + self.outputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) + try: + term2 = self.outputNode.addTerminal(name, **opts) + finally: + self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) + return term + + def removeTerminal(self, name): + #print "remove:", name + term = self[name] + inTerm = self.internalTerminal(term) + Node.removeTerminal(self, name) + inTerm.node().removeTerminal(inTerm.name()) + + def internalTerminalRenamed(self, term, oldName): + self[oldName].rename(term.name()) + + def internalTerminalAdded(self, node, term): + if term._io == 'in': + io = 'out' + else: + io = 'in' + Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable()) + + def internalTerminalRemoved(self, node, term): + try: + Node.removeTerminal(self, term.name()) + except KeyError: + pass + + def terminalRenamed(self, term, oldName): + newName = term.name() + #print "flowchart rename", newName, oldName + #print self.terminals + Node.terminalRenamed(self, self[oldName], oldName) + #print self.terminals + for n in [self.inputNode, self.outputNode]: + if oldName in n.terminals: + n[oldName].rename(newName) + + def createNode(self, nodeType, name=None, pos=None): + if name is None: + n = 0 + while True: + name = "%s.%d" % (nodeType, n) + if name not in self._nodes: + break + n += 1 + + node = library.getNodeType(nodeType)(name) + self.addNode(node, name, pos) + return node + + def addNode(self, node, name, pos=None): + if pos is None: + pos = [0, 0] + if type(pos) in [QtCore.QPoint, QtCore.QPointF]: + pos = [pos.x(), pos.y()] + item = node.graphicsItem() + item.setZValue(self.nextZVal*2) + self.nextZVal += 1 + self.viewBox.addItem(item) + item.moveBy(*pos) + self._nodes[name] = node + self.widget().addNode(node) + node.sigClosed.connect(self.nodeClosed) + node.sigRenamed.connect(self.nodeRenamed) + node.sigOutputChanged.connect(self.nodeOutputChanged) + + def removeNode(self, node): + node.close() + + def nodeClosed(self, node): + del self._nodes[node.name()] + self.widget().removeNode(node) + try: + node.sigClosed.disconnect(self.nodeClosed) + except TypeError: + pass + try: + node.sigRenamed.disconnect(self.nodeRenamed) + except TypeError: + pass + try: + node.sigOutputChanged.disconnect(self.nodeOutputChanged) + except TypeError: + pass + + def nodeRenamed(self, node, oldName): + del self._nodes[oldName] + self._nodes[node.name()] = node + self.widget().nodeRenamed(node, oldName) + + def arrangeNodes(self): + pass + + def internalTerminal(self, term): + """If the terminal belongs to the external Node, return the corresponding internal terminal""" + if term.node() is self: + if term.isInput(): + return self.inputNode[term.name()] + else: + return self.outputNode[term.name()] + else: + return term + + def connectTerminals(self, term1, term2): + """Connect two terminals together within this flowchart.""" + term1 = self.internalTerminal(term1) + term2 = self.internalTerminal(term2) + term1.connectTo(term2) + + + def process(self, **args): + """ + Process data through the flowchart, returning the output. + + Keyword arguments must be the names of input terminals. + The return value is a dict with one key per output terminal. + + """ + data = {} ## Stores terminal:value pairs + + ## determine order of operations + ## order should look like [('p', node1), ('p', node2), ('d', terminal1), ...] + ## Each tuple specifies either (p)rocess this node or (d)elete the result from this terminal + order = self.processOrder() + #print "ORDER:", order + + ## Record inputs given to process() + for n, t in self.inputNode.outputs().items(): + if n not in args: + raise Exception("Parameter %s required to process this chart." % n) + data[t] = args[n] + + ret = {} + + ## process all in order + for c, arg in order: + + if c == 'p': ## Process a single node + #print "===> process:", arg + node = arg + if node is self.inputNode: + continue ## input node has already been processed. + + + ## get input and output terminals for this node + outs = list(node.outputs().values()) + ins = list(node.inputs().values()) + + ## construct input value dictionary + args = {} + for inp in ins: + inputs = inp.inputTerminals() + if len(inputs) == 0: + continue + if inp.isMultiValue(): ## multi-input terminals require a dict of all inputs + args[inp.name()] = dict([(i, data[i]) for i in inputs]) + else: ## single-inputs terminals only need the single input value available + args[inp.name()] = data[inputs[0]] + + if node is self.outputNode: + ret = args ## we now have the return value, but must keep processing in case there are other endpoint nodes in the chart + else: + try: + if node.isBypassed(): + result = node.processBypassed(args) + else: + result = node.process(display=False, **args) + except: + print("Error processing node %s. Args are: %s" % (str(node), str(args))) + raise + for out in outs: + #print " Output:", out, out.name() + #print out.name() + try: + data[out] = result[out.name()] + except: + print(out, out.name()) + raise + elif c == 'd': ## delete a terminal result (no longer needed; may be holding a lot of memory) + #print "===> delete", arg + if arg in data: + del data[arg] + + return ret + + def processOrder(self): + """Return the order of operations required to process this chart. + The order returned should look like [('p', node1), ('p', node2), ('d', terminal1), ...] + where each tuple specifies either (p)rocess this node or (d)elete the result from this terminal + """ + + ## first collect list of nodes/terminals and their dependencies + deps = {} + tdeps = {} ## {terminal: [nodes that depend on terminal]} + for name, node in self._nodes.items(): + deps[node] = node.dependentNodes() + for t in node.outputs().values(): + tdeps[t] = t.dependentNodes() + + #print "DEPS:", deps + ## determine correct node-processing order + #deps[self] = [] + order = toposort(deps) + #print "ORDER1:", order + + ## construct list of operations + ops = [('p', n) for n in order] + + ## determine when it is safe to delete terminal values + dels = [] + for t, nodes in tdeps.items(): + lastInd = 0 + lastNode = None + for n in nodes: ## determine which node is the last to be processed according to order + if n is self: + lastInd = None + break + else: + try: + ind = order.index(n) + except ValueError: + continue + if lastNode is None or ind > lastInd: + lastNode = n + lastInd = ind + #tdeps[t] = lastNode + if lastInd is not None: + dels.append((lastInd+1, t)) + #dels.sort(lambda a,b: cmp(b[0], a[0])) + dels.sort(key=lambda a: a[0], reverse=True) + for i, t in dels: + ops.insert(i, ('d', t)) + return ops + + + def nodeOutputChanged(self, startNode): + """Triggered when a node's output values have changed. (NOT called during process()) + Propagates new data forward through network.""" + ## first collect list of nodes/terminals and their dependencies + + if self.processing: + return + self.processing = True + try: + deps = {} + for name, node in self._nodes.items(): + deps[node] = [] + for t in node.outputs().values(): + deps[node].extend(t.dependentNodes()) + + ## determine order of updates + order = toposort(deps, nodes=[startNode]) + order.reverse() + + ## keep track of terminals that have been updated + terms = set(startNode.outputs().values()) + + #print "======= Updating", startNode + #print "Order:", order + for node in order[1:]: + #print "Processing node", node + for term in list(node.inputs().values()): + #print " checking terminal", term + deps = list(term.connections().keys()) + update = False + for d in deps: + if d in terms: + #print " ..input", d, "changed" + update = True + term.inputChanged(d, process=False) + if update: + #print " processing.." + node.update() + terms |= set(node.outputs().values()) + + finally: + self.processing = False + if self.inputWasSet: + self.inputWasSet = False + else: + self.sigStateChanged.emit() + + + + def chartGraphicsItem(self): + """Return the graphicsItem which displays the internals of this flowchart. + (graphicsItem() still returns the external-view item)""" + #return self._chartGraphicsItem + return self.viewBox + + def widget(self): + if self._widget is None: + self._widget = FlowchartCtrlWidget(self) + self.scene = self._widget.scene() + self.viewBox = self._widget.viewBox() + #self._scene = QtGui.QGraphicsScene() + #self._widget.setScene(self._scene) + #self.scene.addItem(self.chartGraphicsItem()) + + #ci = self.chartGraphicsItem() + #self.viewBox.addItem(ci) + #self.viewBox.autoRange() + return self._widget + + def listConnections(self): + conn = set() + for n in self._nodes.values(): + terms = n.outputs() + for n, t in terms.items(): + for c in t.connections(): + conn.add((t, c)) + return conn + + def saveState(self): + state = Node.saveState(self) + state['nodes'] = [] + state['connects'] = [] + #state['terminals'] = self.saveTerminals() + + for name, node in self._nodes.items(): + cls = type(node) + if hasattr(cls, 'nodeName'): + clsName = cls.nodeName + pos = node.graphicsItem().pos() + ns = {'class': clsName, 'name': name, 'pos': (pos.x(), pos.y()), 'state': node.saveState()} + state['nodes'].append(ns) + + conn = self.listConnections() + for a, b in conn: + state['connects'].append((a.node().name(), a.name(), b.node().name(), b.name())) + + state['inputNode'] = self.inputNode.saveState() + state['outputNode'] = self.outputNode.saveState() + + return state + + def restoreState(self, state, clear=False): + self.blockSignals(True) + try: + if clear: + self.clear() + Node.restoreState(self, state) + nodes = state['nodes'] + #nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0])) + nodes.sort(key=lambda a: a['pos'][0]) + for n in nodes: + if n['name'] in self._nodes: + #self._nodes[n['name']].graphicsItem().moveBy(*n['pos']) + self._nodes[n['name']].restoreState(n['state']) + continue + try: + node = self.createNode(n['class'], name=n['name']) + node.restoreState(n['state']) + except: + printExc("Error creating node %s: (continuing anyway)" % n['name']) + #node.graphicsItem().moveBy(*n['pos']) + + self.inputNode.restoreState(state.get('inputNode', {})) + self.outputNode.restoreState(state.get('outputNode', {})) + + #self.restoreTerminals(state['terminals']) + for n1, t1, n2, t2 in state['connects']: + try: + self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2]) + except: + print(self._nodes[n1].terminals) + print(self._nodes[n2].terminals) + printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2)) + + + finally: + self.blockSignals(False) + + self.sigChartLoaded.emit() + self.outputChanged() + self.sigStateChanged.emit() + #self.sigOutputChanged.emit() + + def loadFile(self, fileName=None, startDir=None): + if fileName is None: + if startDir is None: + startDir = self.filePath + if startDir is None: + startDir = '.' + self.fileDialog = pg.FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") + #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + #self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.loadFile) + return + ## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs.. + #fileName = QtGui.QFileDialog.getOpenFileName(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") + fileName = str(fileName) + state = configfile.readConfigFile(fileName) + self.restoreState(state, clear=True) + self.viewBox.autoRange() + #self.emit(QtCore.SIGNAL('fileLoaded'), fileName) + self.sigFileLoaded.emit(fileName) + + def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'): + if fileName is None: + if startDir is None: + startDir = self.filePath + if startDir is None: + startDir = '.' + self.fileDialog = pg.FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") + #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + #self.fileDialog.setDirectory(startDir) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.saveFile) + return + #fileName = QtGui.QFileDialog.getSaveFileName(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") + fileName = str(fileName) + configfile.writeConfigFile(self.saveState(), fileName) + self.sigFileSaved.emit(fileName) + + def clear(self): + for n in list(self._nodes.values()): + if n is self.inputNode or n is self.outputNode: + continue + n.close() ## calls self.nodeClosed(n) by signal + #self.clearTerminals() + self.widget().clear() + + def clearTerminals(self): + Node.clearTerminals(self) + self.inputNode.clearTerminals() + self.outputNode.clearTerminals() + +#class FlowchartGraphicsItem(QtGui.QGraphicsItem): +class FlowchartGraphicsItem(GraphicsObject): + + def __init__(self, chart): + #print "FlowchartGraphicsItem.__init__" + #QtGui.QGraphicsItem.__init__(self) + GraphicsObject.__init__(self) + self.chart = chart ## chart is an instance of Flowchart() + self.updateTerminals() + + def updateTerminals(self): + #print "FlowchartGraphicsItem.updateTerminals" + self.terminals = {} + bounds = self.boundingRect() + inp = self.chart.inputs() + dy = bounds.height() / (len(inp)+1) + y = dy + for n, t in inp.items(): + item = t.graphicsItem() + self.terminals[n] = item + item.setParentItem(self) + item.setAnchor(bounds.width(), y) + y += dy + out = self.chart.outputs() + dy = bounds.height() / (len(out)+1) + y = dy + for n, t in out.items(): + item = t.graphicsItem() + self.terminals[n] = item + item.setParentItem(self) + item.setAnchor(0, y) + y += dy + + def boundingRect(self): + #print "FlowchartGraphicsItem.boundingRect" + return QtCore.QRectF() + + def paint(self, p, *args): + #print "FlowchartGraphicsItem.paint" + pass + #p.drawRect(self.boundingRect()) + + +class FlowchartCtrlWidget(QtGui.QWidget): + """The widget that contains the list of all the nodes in a flowchart and their controls, as well as buttons for loading/saving flowcharts.""" + + def __init__(self, chart): + self.items = {} + #self.loadDir = loadDir ## where to look initially for chart files + self.currentFileName = None + QtGui.QWidget.__init__(self) + self.chart = chart + self.ui = FlowchartCtrlTemplate.Ui_Form() + self.ui.setupUi(self) + self.ui.ctrlList.setColumnCount(2) + #self.ui.ctrlList.setColumnWidth(0, 200) + self.ui.ctrlList.setColumnWidth(1, 20) + self.ui.ctrlList.setVerticalScrollMode(self.ui.ctrlList.ScrollPerPixel) + self.ui.ctrlList.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + self.chartWidget = FlowchartWidget(chart, self) + #self.chartWidget.viewBox().autoRange() + self.cwWin = QtGui.QMainWindow() + self.cwWin.setWindowTitle('Flowchart') + self.cwWin.setCentralWidget(self.chartWidget) + self.cwWin.resize(1000,800) + + h = self.ui.ctrlList.header() + h.setResizeMode(0, h.Stretch) + + self.ui.ctrlList.itemChanged.connect(self.itemChanged) + self.ui.loadBtn.clicked.connect(self.loadClicked) + self.ui.saveBtn.clicked.connect(self.saveClicked) + self.ui.saveAsBtn.clicked.connect(self.saveAsClicked) + self.ui.showChartBtn.toggled.connect(self.chartToggled) + self.chart.sigFileLoaded.connect(self.setCurrentFile) + self.ui.reloadBtn.clicked.connect(self.reloadClicked) + self.chart.sigFileSaved.connect(self.fileSaved) + + + + #def resizeEvent(self, ev): + #QtGui.QWidget.resizeEvent(self, ev) + #self.ui.ctrlList.setColumnWidth(0, self.ui.ctrlList.viewport().width()-20) + + def chartToggled(self, b): + if b: + self.cwWin.show() + else: + self.cwWin.hide() + + def reloadClicked(self): + try: + self.chartWidget.reloadLibrary() + self.ui.reloadBtn.success("Reloaded.") + except: + self.ui.reloadBtn.success("Error.") + raise + + + def loadClicked(self): + newFile = self.chart.loadFile() + #self.setCurrentFile(newFile) + + def fileSaved(self, fileName): + self.setCurrentFile(str(fileName)) + self.ui.saveBtn.success("Saved.") + + def saveClicked(self): + if self.currentFileName is None: + self.saveAsClicked() + else: + try: + self.chart.saveFile(self.currentFileName) + #self.ui.saveBtn.success("Saved.") + except: + self.ui.saveBtn.failure("Error") + raise + + def saveAsClicked(self): + try: + if self.currentFileName is None: + newFile = self.chart.saveFile() + else: + newFile = self.chart.saveFile(suggestedFileName=self.currentFileName) + #self.ui.saveAsBtn.success("Saved.") + #print "Back to saveAsClicked." + except: + self.ui.saveBtn.failure("Error") + raise + + #self.setCurrentFile(newFile) + + def setCurrentFile(self, fileName): + self.currentFileName = str(fileName) + if fileName is None: + self.ui.fileNameLabel.setText("[ new ]") + else: + self.ui.fileNameLabel.setText("%s" % os.path.split(self.currentFileName)[1]) + self.resizeEvent(None) + + def itemChanged(self, *args): + pass + + def scene(self): + return self.chartWidget.scene() ## returns the GraphicsScene object + + def viewBox(self): + return self.chartWidget.viewBox() + + def nodeRenamed(self, node, oldName): + self.items[node].setText(0, node.name()) + + def addNode(self, node): + ctrl = node.ctrlWidget() + #if ctrl is None: + #return + item = QtGui.QTreeWidgetItem([node.name(), '', '']) + self.ui.ctrlList.addTopLevelItem(item) + byp = QtGui.QPushButton('X') + byp.setCheckable(True) + byp.setFixedWidth(20) + item.bypassBtn = byp + self.ui.ctrlList.setItemWidget(item, 1, byp) + byp.node = node + node.bypassButton = byp + byp.setChecked(node.isBypassed()) + byp.clicked.connect(self.bypassClicked) + + if ctrl is not None: + item2 = QtGui.QTreeWidgetItem() + item.addChild(item2) + self.ui.ctrlList.setItemWidget(item2, 0, ctrl) + + self.items[node] = item + + def removeNode(self, node): + if node in self.items: + item = self.items[node] + #self.disconnect(item.bypassBtn, QtCore.SIGNAL('clicked()'), self.bypassClicked) + try: + item.bypassBtn.clicked.disconnect(self.bypassClicked) + except TypeError: + pass + self.ui.ctrlList.removeTopLevelItem(item) + + def bypassClicked(self): + btn = QtCore.QObject.sender(self) + btn.node.bypass(btn.isChecked()) + + def chartWidget(self): + return self.chartWidget + + def outputChanged(self, data): + pass + #self.ui.outputTree.setData(data, hideRoot=True) + + def clear(self): + self.chartWidget.clear() + + def select(self, node): + item = self.items[node] + self.ui.ctrlList.setCurrentItem(item) + +class FlowchartWidget(dockarea.DockArea): + """Includes the actual graphical flowchart and debugging interface""" + def __init__(self, chart, ctrl): + #QtGui.QWidget.__init__(self) + dockarea.DockArea.__init__(self) + self.chart = chart + self.ctrl = ctrl + self.hoverItem = None + #self.setMinimumWidth(250) + #self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)) + + #self.ui = FlowchartTemplate.Ui_Form() + #self.ui.setupUi(self) + + ## build user interface (it was easier to do it here than via developer) + self.view = FlowchartGraphicsView.FlowchartGraphicsView(self) + self.viewDock = dockarea.Dock('view', size=(1000,600)) + self.viewDock.addWidget(self.view) + self.viewDock.hideTitleBar() + self.addDock(self.viewDock) + + + self.hoverText = QtGui.QTextEdit() + self.hoverText.setReadOnly(True) + self.hoverDock = dockarea.Dock('Hover Info', size=(1000,20)) + self.hoverDock.addWidget(self.hoverText) + self.addDock(self.hoverDock, 'bottom') + + self.selInfo = QtGui.QWidget() + self.selInfoLayout = QtGui.QGridLayout() + self.selInfo.setLayout(self.selInfoLayout) + self.selDescLabel = QtGui.QLabel() + self.selNameLabel = QtGui.QLabel() + self.selDescLabel.setWordWrap(True) + self.selectedTree = pg.DataTreeWidget() + #self.selectedTree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + #self.selInfoLayout.addWidget(self.selNameLabel) + self.selInfoLayout.addWidget(self.selDescLabel) + self.selInfoLayout.addWidget(self.selectedTree) + self.selDock = dockarea.Dock('Selected Node', size=(1000,200)) + self.selDock.addWidget(self.selInfo) + self.addDock(self.selDock, 'bottom') + + self._scene = self.view.scene() + self._viewBox = self.view.viewBox() + #self._scene = QtGui.QGraphicsScene() + #self._scene = FlowchartGraphicsView.FlowchartGraphicsScene() + #self.view.setScene(self._scene) + + self.buildMenu() + #self.ui.addNodeBtn.mouseReleaseEvent = self.addNodeBtnReleased + + self._scene.selectionChanged.connect(self.selectionChanged) + self._scene.sigMouseHover.connect(self.hoverOver) + #self.view.sigClicked.connect(self.showViewMenu) + #self._scene.sigSceneContextMenu.connect(self.showViewMenu) + #self._viewBox.sigActionPositionChanged.connect(self.menuPosChanged) + + + def reloadLibrary(self): + #QtCore.QObject.disconnect(self.nodeMenu, QtCore.SIGNAL('triggered(QAction*)'), self.nodeMenuTriggered) + self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered) + self.nodeMenu = None + self.subMenus = [] + library.loadLibrary(reloadLibs=True) + self.buildMenu() + + def buildMenu(self, pos=None): + self.nodeMenu = QtGui.QMenu() + self.subMenus = [] + for section, nodes in library.getNodeTree().items(): + menu = QtGui.QMenu(section) + self.nodeMenu.addMenu(menu) + for name in nodes: + act = menu.addAction(name) + act.nodeType = name + act.pos = pos + self.subMenus.append(menu) + self.nodeMenu.triggered.connect(self.nodeMenuTriggered) + return self.nodeMenu + + def menuPosChanged(self, pos): + self.menuPos = pos + + def showViewMenu(self, ev): + #QtGui.QPushButton.mouseReleaseEvent(self.ui.addNodeBtn, ev) + #if ev.button() == QtCore.Qt.RightButton: + #self.menuPos = self.view.mapToScene(ev.pos()) + #self.nodeMenu.popup(ev.globalPos()) + #print "Flowchart.showViewMenu called" + + #self.menuPos = ev.scenePos() + self.buildMenu(ev.scenePos()) + self.nodeMenu.popup(ev.screenPos()) + + def scene(self): + return self._scene ## the GraphicsScene item + + def viewBox(self): + return self._viewBox ## the viewBox that items should be added to + + def nodeMenuTriggered(self, action): + nodeType = action.nodeType + if action.pos is not None: + pos = action.pos + else: + pos = self.menuPos + pos = self.viewBox().mapSceneToView(pos) + + self.chart.createNode(nodeType, pos=pos) + + + def selectionChanged(self): + #print "FlowchartWidget.selectionChanged called." + items = self._scene.selectedItems() + #print " scene.selectedItems: ", items + if len(items) == 0: + data = None + else: + item = items[0] + if hasattr(item, 'node') and isinstance(item.node, Node): + n = item.node + self.ctrl.select(n) + data = {'outputs': n.outputValues(), 'inputs': n.inputValues()} + self.selNameLabel.setText(n.name()) + if hasattr(n, 'nodeName'): + self.selDescLabel.setText("%s: %s" % (n.nodeName, n.__class__.__doc__)) + else: + self.selDescLabel.setText("") + if n.exception is not None: + data['exception'] = n.exception + else: + data = None + self.selectedTree.setData(data, hideRoot=True) + + def hoverOver(self, items): + #print "FlowchartWidget.hoverOver called." + term = None + for item in items: + if item is self.hoverItem: + return + self.hoverItem = item + if hasattr(item, 'term') and isinstance(item.term, Terminal): + term = item.term + break + if term is None: + self.hoverText.setPlainText("") + else: + val = term.value() + if isinstance(val, ndarray): + val = "%s %s %s" % (type(val).__name__, str(val.shape), str(val.dtype)) + else: + val = str(val) + if len(val) > 400: + val = val[:400] + "..." + self.hoverText.setPlainText("%s.%s = %s" % (term.node().name(), term.name(), val)) + #self.hoverLabel.setCursorPosition(0) + + + + def clear(self): + #self.outputTree.setData(None) + self.selectedTree.setData(None) + self.hoverText.setPlainText('') + self.selNameLabel.setText('') + self.selDescLabel.setText('') + + +class FlowchartNode(Node): + pass + diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui b/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui new file mode 100644 index 00000000..610846b6 --- /dev/null +++ b/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui @@ -0,0 +1,120 @@ + + + Form + + + + 0 + 0 + 217 + 499 + + + + Form + + + + 0 + + + 0 + + + + + Load.. + + + + + + + + + + + + + + + + Flowchart + + + true + + + + + + + false + + + false + + + false + + + false + + + + 1 + + + + + + + + + 75 + true + + + + + + + Qt::AlignCenter + + + + + + + + TreeWidget + QTreeWidget +
pyqtgraph.widgets.TreeWidget
+
+ + FeedbackButton + QPushButton +
pyqtgraph.widgets.FeedbackButton
+
+
+ + +
diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py new file mode 100644 index 00000000..0410cdf3 --- /dev/null +++ b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './flowchart/FlowchartCtrlTemplate.ui' +# +# Created: Sun Sep 9 14:41:30 2012 +# by: PyQt4 UI code generator 4.9.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(217, 499) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setVerticalSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.loadBtn = QtGui.QPushButton(Form) + self.loadBtn.setObjectName(_fromUtf8("loadBtn")) + self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) + self.saveBtn = FeedbackButton(Form) + self.saveBtn.setObjectName(_fromUtf8("saveBtn")) + self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) + self.saveAsBtn = FeedbackButton(Form) + self.saveAsBtn.setObjectName(_fromUtf8("saveAsBtn")) + self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) + self.reloadBtn = FeedbackButton(Form) + self.reloadBtn.setCheckable(False) + self.reloadBtn.setFlat(False) + self.reloadBtn.setObjectName(_fromUtf8("reloadBtn")) + self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) + self.showChartBtn = QtGui.QPushButton(Form) + self.showChartBtn.setCheckable(True) + self.showChartBtn.setObjectName(_fromUtf8("showChartBtn")) + self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) + self.ctrlList = TreeWidget(Form) + self.ctrlList.setObjectName(_fromUtf8("ctrlList")) + self.ctrlList.headerItem().setText(0, _fromUtf8("1")) + self.ctrlList.header().setVisible(False) + self.ctrlList.header().setStretchLastSection(False) + self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) + self.fileNameLabel = QtGui.QLabel(Form) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.fileNameLabel.setFont(font) + self.fileNameLabel.setText(_fromUtf8("")) + self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) + self.fileNameLabel.setObjectName(_fromUtf8("fileNameLabel")) + self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load..", None, QtGui.QApplication.UnicodeUTF8)) + self.saveBtn.setText(QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.saveAsBtn.setText(QtGui.QApplication.translate("Form", "As..", None, QtGui.QApplication.UnicodeUTF8)) + self.reloadBtn.setText(QtGui.QApplication.translate("Form", "Reload Libs", None, QtGui.QApplication.UnicodeUTF8)) + self.showChartBtn.setText(QtGui.QApplication.translate("Form", "Flowchart", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.widgets.FeedbackButton import FeedbackButton +from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py new file mode 100644 index 00000000..f579c957 --- /dev/null +++ b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './flowchart/FlowchartCtrlTemplate.ui' +# +# Created: Sun Sep 9 14:41:30 2012 +# by: pyside-uic 0.2.13 running on PySide 1.1.0 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(217, 499) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setVerticalSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.loadBtn = QtGui.QPushButton(Form) + self.loadBtn.setObjectName("loadBtn") + self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) + self.saveBtn = FeedbackButton(Form) + self.saveBtn.setObjectName("saveBtn") + self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) + self.saveAsBtn = FeedbackButton(Form) + self.saveAsBtn.setObjectName("saveAsBtn") + self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) + self.reloadBtn = FeedbackButton(Form) + self.reloadBtn.setCheckable(False) + self.reloadBtn.setFlat(False) + self.reloadBtn.setObjectName("reloadBtn") + self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) + self.showChartBtn = QtGui.QPushButton(Form) + self.showChartBtn.setCheckable(True) + self.showChartBtn.setObjectName("showChartBtn") + self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) + self.ctrlList = TreeWidget(Form) + self.ctrlList.setObjectName("ctrlList") + self.ctrlList.headerItem().setText(0, "1") + self.ctrlList.header().setVisible(False) + self.ctrlList.header().setStretchLastSection(False) + self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) + self.fileNameLabel = QtGui.QLabel(Form) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.fileNameLabel.setFont(font) + self.fileNameLabel.setText("") + self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) + self.fileNameLabel.setObjectName("fileNameLabel") + self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load..", None, QtGui.QApplication.UnicodeUTF8)) + self.saveBtn.setText(QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.saveAsBtn.setText(QtGui.QApplication.translate("Form", "As..", None, QtGui.QApplication.UnicodeUTF8)) + self.reloadBtn.setText(QtGui.QApplication.translate("Form", "Reload Libs", None, QtGui.QApplication.UnicodeUTF8)) + self.showChartBtn.setText(QtGui.QApplication.translate("Form", "Flowchart", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.widgets.FeedbackButton import FeedbackButton +from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/flowchart/FlowchartGraphicsView.py b/pyqtgraph/flowchart/FlowchartGraphicsView.py new file mode 100644 index 00000000..0ec4d5c8 --- /dev/null +++ b/pyqtgraph/flowchart/FlowchartGraphicsView.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.widgets.GraphicsView import GraphicsView +from pyqtgraph.GraphicsScene import GraphicsScene +from pyqtgraph.graphicsItems.ViewBox import ViewBox + +#class FlowchartGraphicsView(QtGui.QGraphicsView): +class FlowchartGraphicsView(GraphicsView): + + sigHoverOver = QtCore.Signal(object) + sigClicked = QtCore.Signal(object) + + def __init__(self, widget, *args): + #QtGui.QGraphicsView.__init__(self, *args) + GraphicsView.__init__(self, *args, useOpenGL=False) + #self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(255,255,255))) + self._vb = FlowchartViewBox(widget, lockAspect=True, invertY=True) + self.setCentralItem(self._vb) + #self.scene().addItem(self.vb) + #self.setMouseTracking(True) + #self.lastPos = None + #self.setTransformationAnchor(self.AnchorViewCenter) + #self.setRenderHints(QtGui.QPainter.Antialiasing) + self.setRenderHint(QtGui.QPainter.Antialiasing, True) + #self.setDragMode(QtGui.QGraphicsView.RubberBandDrag) + #self.setRubberBandSelectionMode(QtCore.Qt.ContainsItemBoundingRect) + + def viewBox(self): + return self._vb + + + #def mousePressEvent(self, ev): + #self.moved = False + #self.lastPos = ev.pos() + #return QtGui.QGraphicsView.mousePressEvent(self, ev) + + #def mouseMoveEvent(self, ev): + #self.moved = True + #callSuper = False + #if ev.buttons() & QtCore.Qt.RightButton: + #if self.lastPos is not None: + #dif = ev.pos() - self.lastPos + #self.scale(1.01**-dif.y(), 1.01**-dif.y()) + #elif ev.buttons() & QtCore.Qt.MidButton: + #if self.lastPos is not None: + #dif = ev.pos() - self.lastPos + #self.translate(dif.x(), -dif.y()) + #else: + ##self.emit(QtCore.SIGNAL('hoverOver'), self.items(ev.pos())) + #self.sigHoverOver.emit(self.items(ev.pos())) + #callSuper = True + #self.lastPos = ev.pos() + + #if callSuper: + #QtGui.QGraphicsView.mouseMoveEvent(self, ev) + + #def mouseReleaseEvent(self, ev): + #if not self.moved: + ##self.emit(QtCore.SIGNAL('clicked'), ev) + #self.sigClicked.emit(ev) + #return QtGui.QGraphicsView.mouseReleaseEvent(self, ev) + +class FlowchartViewBox(ViewBox): + + def __init__(self, widget, *args, **kwargs): + ViewBox.__init__(self, *args, **kwargs) + self.widget = widget + #self.menu = None + #self._subMenus = None ## need a place to store the menus otherwise they dissappear (even though they've been added to other menus) ((yes, it doesn't make sense)) + + + + + def getMenu(self, ev): + ## called by ViewBox to create a new context menu + self._fc_menu = QtGui.QMenu() + self._subMenus = self.getContextMenus(ev) + for menu in self._subMenus: + self._fc_menu.addMenu(menu) + return self._fc_menu + + def getContextMenus(self, ev): + ## called by scene to add menus on to someone else's context menu + menu = self.widget.buildMenu(ev.scenePos()) + menu.setTitle("Add node") + return [menu, ViewBox.getMenu(self, ev)] + + + + + + + + + + +##class FlowchartGraphicsScene(QtGui.QGraphicsScene): +#class FlowchartGraphicsScene(GraphicsScene): + + #sigContextMenuEvent = QtCore.Signal(object) + + #def __init__(self, *args): + ##QtGui.QGraphicsScene.__init__(self, *args) + #GraphicsScene.__init__(self, *args) + + #def mouseClickEvent(self, ev): + ##QtGui.QGraphicsScene.contextMenuEvent(self, ev) + #if not ev.button() in [QtCore.Qt.RightButton]: + #self.sigContextMenuEvent.emit(ev) \ No newline at end of file diff --git a/pyqtgraph/flowchart/FlowchartTemplate.ui b/pyqtgraph/flowchart/FlowchartTemplate.ui new file mode 100644 index 00000000..31b1359c --- /dev/null +++ b/pyqtgraph/flowchart/FlowchartTemplate.ui @@ -0,0 +1,98 @@ + + + Form + + + + 0 + 0 + 529 + 329 + + + + Form + + + + + 260 + 10 + 264 + 222 + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + 75 + true + + + + + + + + + + + + 1 + + + + + + + + + + 0 + 240 + 521 + 81 + + + + + + + 0 + 0 + 256 + 192 + + + + + + + DataTreeWidget + QTreeWidget +
pyqtgraph.widgets.DataTreeWidget
+
+ + FlowchartGraphicsView + QGraphicsView +
pyqtgraph.flowchart.FlowchartGraphicsView
+
+
+ + +
diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py b/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py new file mode 100644 index 00000000..c07dd734 --- /dev/null +++ b/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui' +# +# Created: Sun Feb 24 19:47:29 2013 +# by: PyQt4 UI code generator 4.9.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(529, 329) + self.selInfoWidget = QtGui.QWidget(Form) + self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) + self.selInfoWidget.setObjectName(_fromUtf8("selInfoWidget")) + self.gridLayout = QtGui.QGridLayout(self.selInfoWidget) + self.gridLayout.setMargin(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.selDescLabel = QtGui.QLabel(self.selInfoWidget) + self.selDescLabel.setText(_fromUtf8("")) + self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.selDescLabel.setWordWrap(True) + self.selDescLabel.setObjectName(_fromUtf8("selDescLabel")) + self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) + self.selNameLabel = QtGui.QLabel(self.selInfoWidget) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.selNameLabel.setFont(font) + self.selNameLabel.setText(_fromUtf8("")) + self.selNameLabel.setObjectName(_fromUtf8("selNameLabel")) + self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) + self.selectedTree = DataTreeWidget(self.selInfoWidget) + self.selectedTree.setObjectName(_fromUtf8("selectedTree")) + self.selectedTree.headerItem().setText(0, _fromUtf8("1")) + self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) + self.hoverText = QtGui.QTextEdit(Form) + self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) + self.hoverText.setObjectName(_fromUtf8("hoverText")) + self.view = FlowchartGraphicsView(Form) + self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) + self.view.setObjectName(_fromUtf8("view")) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget +from pyqtgraph.flowchart.FlowchartGraphicsView import FlowchartGraphicsView diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyside.py b/pyqtgraph/flowchart/FlowchartTemplate_pyside.py new file mode 100644 index 00000000..c73f3c00 --- /dev/null +++ b/pyqtgraph/flowchart/FlowchartTemplate_pyside.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui' +# +# Created: Sun Feb 24 19:47:30 2013 +# by: pyside-uic 0.2.13 running on PySide 1.1.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(529, 329) + self.selInfoWidget = QtGui.QWidget(Form) + self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) + self.selInfoWidget.setObjectName("selInfoWidget") + self.gridLayout = QtGui.QGridLayout(self.selInfoWidget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.selDescLabel = QtGui.QLabel(self.selInfoWidget) + self.selDescLabel.setText("") + self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.selDescLabel.setWordWrap(True) + self.selDescLabel.setObjectName("selDescLabel") + self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) + self.selNameLabel = QtGui.QLabel(self.selInfoWidget) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.selNameLabel.setFont(font) + self.selNameLabel.setText("") + self.selNameLabel.setObjectName("selNameLabel") + self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) + self.selectedTree = DataTreeWidget(self.selInfoWidget) + self.selectedTree.setObjectName("selectedTree") + self.selectedTree.headerItem().setText(0, "1") + self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) + self.hoverText = QtGui.QTextEdit(Form) + self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) + self.hoverText.setObjectName("hoverText") + self.view = FlowchartGraphicsView(Form) + self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) + self.view.setObjectName("view") + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget +from pyqtgraph.flowchart.FlowchartGraphicsView import FlowchartGraphicsView diff --git a/pyqtgraph/flowchart/Node.py b/pyqtgraph/flowchart/Node.py new file mode 100644 index 00000000..cd73b42b --- /dev/null +++ b/pyqtgraph/flowchart/Node.py @@ -0,0 +1,647 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject +import pyqtgraph.functions as fn +from .Terminal import * +from pyqtgraph.pgcollections import OrderedDict +from pyqtgraph.debug import * +import numpy as np +from .eq import * + + +def strDict(d): + return dict([(str(k), v) for k, v in d.items()]) + +class Node(QtCore.QObject): + """ + Node represents the basic processing unit of a flowchart. + A Node subclass implements at least: + + 1) A list of input / ouptut terminals and their properties + 2) a process() function which takes the names of input terminals as keyword arguments and returns a dict with the names of output terminals as keys. + + A flowchart thus consists of multiple instances of Node subclasses, each of which is connected + to other by wires between their terminals. A flowchart is, itself, also a special subclass of Node. + This allows Nodes within the flowchart to connect to the input/output nodes of the flowchart itself. + + Optionally, a node class can implement the ctrlWidget() method, which must return a QWidget (usually containing other widgets) that will be displayed in the flowchart control panel. Some nodes implement fairly complex control widgets, but most nodes follow a simple form-like pattern: a list of parameter names and a single value (represented as spin box, check box, etc..) for each parameter. To make this easier, the CtrlNode subclass allows you to instead define a simple data structure that CtrlNode will use to automatically generate the control widget. """ + + sigOutputChanged = QtCore.Signal(object) # self + sigClosed = QtCore.Signal(object) + sigRenamed = QtCore.Signal(object, object) + sigTerminalRenamed = QtCore.Signal(object, object) # term, oldName + sigTerminalAdded = QtCore.Signal(object, object) # self, term + sigTerminalRemoved = QtCore.Signal(object, object) # self, term + + + def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True): + """ + ============== ============================================================ + Arguments + name The name of this specific node instance. It can be any + string, but must be unique within a flowchart. Usually, + we simply let the flowchart decide on a name when calling + Flowchart.addNode(...) + terminals Dict-of-dicts specifying the terminals present on this Node. + Terminal specifications look like:: + + 'inputTerminalName': {'io': 'in'} + 'outputTerminalName': {'io': 'out'} + + There are a number of optional parameters for terminals: + multi, pos, renamable, removable, multiable, bypass. See + the Terminal class for more information. + allowAddInput bool; whether the user is allowed to add inputs by the + context menu. + allowAddOutput bool; whether the user is allowed to add outputs by the + context menu. + allowRemove bool; whether the user is allowed to remove this node by the + context menu. + ============== ============================================================ + + """ + QtCore.QObject.__init__(self) + self._name = name + self._bypass = False + self.bypassButton = None ## this will be set by the flowchart ctrl widget.. + self._graphicsItem = None + self.terminals = OrderedDict() + self._inputs = OrderedDict() + self._outputs = OrderedDict() + self._allowAddInput = allowAddInput ## flags to allow the user to add/remove terminals + self._allowAddOutput = allowAddOutput + self._allowRemove = allowRemove + + self.exception = None + if terminals is None: + return + for name, opts in terminals.items(): + self.addTerminal(name, **opts) + + + def nextTerminalName(self, name): + """Return an unused terminal name""" + name2 = name + i = 1 + while name2 in self.terminals: + name2 = "%s.%d" % (name, i) + i += 1 + return name2 + + def addInput(self, name="Input", **args): + """Add a new input terminal to this Node with the given name. Extra + keyword arguments are passed to Terminal.__init__. + + This is a convenience function that just calls addTerminal(io='in', ...)""" + #print "Node.addInput called." + return self.addTerminal(name, io='in', **args) + + def addOutput(self, name="Output", **args): + """Add a new output terminal to this Node with the given name. Extra + keyword arguments are passed to Terminal.__init__. + + This is a convenience function that just calls addTerminal(io='out', ...)""" + return self.addTerminal(name, io='out', **args) + + def removeTerminal(self, term): + """Remove the specified terminal from this Node. May specify either the + terminal's name or the terminal itself. + + Causes sigTerminalRemoved to be emitted.""" + if isinstance(term, Terminal): + name = term.name() + else: + name = term + term = self.terminals[name] + + #print "remove", name + #term.disconnectAll() + term.close() + del self.terminals[name] + if name in self._inputs: + del self._inputs[name] + if name in self._outputs: + del self._outputs[name] + self.graphicsItem().updateTerminals() + self.sigTerminalRemoved.emit(self, term) + + + def terminalRenamed(self, term, oldName): + """Called after a terminal has been renamed + + Causes sigTerminalRenamed to be emitted.""" + newName = term.name() + for d in [self.terminals, self._inputs, self._outputs]: + if oldName not in d: + continue + d[newName] = d[oldName] + del d[oldName] + + self.graphicsItem().updateTerminals() + self.sigTerminalRenamed.emit(term, oldName) + + def addTerminal(self, name, **opts): + """Add a new terminal to this Node with the given name. Extra + keyword arguments are passed to Terminal.__init__. + + Causes sigTerminalAdded to be emitted.""" + name = self.nextTerminalName(name) + term = Terminal(self, name, **opts) + self.terminals[name] = term + if term.isInput(): + self._inputs[name] = term + elif term.isOutput(): + self._outputs[name] = term + self.graphicsItem().updateTerminals() + self.sigTerminalAdded.emit(self, term) + return term + + + def inputs(self): + """Return dict of all input terminals. + Warning: do not modify.""" + return self._inputs + + def outputs(self): + """Return dict of all output terminals. + Warning: do not modify.""" + return self._outputs + + def process(self, **kargs): + """Process data through this node. This method is called any time the flowchart + wants the node to process data. It will be called with one keyword argument + corresponding to each input terminal, and must return a dict mapping the name + of each output terminal to its new value. + + This method is also called with a 'display' keyword argument, which indicates + whether the node should update its display (if it implements any) while processing + this data. This is primarily used to disable expensive display operations + during batch processing. + """ + return {} + + def graphicsItem(self): + """Return the GraphicsItem for this node. Subclasses may re-implement + this method to customize their appearance in the flowchart.""" + if self._graphicsItem is None: + self._graphicsItem = NodeGraphicsItem(self) + return self._graphicsItem + + ## this is just bad planning. Causes too many bugs. + def __getattr__(self, attr): + """Return the terminal with the given name""" + if attr not in self.terminals: + raise AttributeError(attr) + else: + import traceback + traceback.print_stack() + print("Warning: use of node.terminalName is deprecated; use node['terminalName'] instead.") + return self.terminals[attr] + + def __getitem__(self, item): + #return getattr(self, item) + """Return the terminal with the given name""" + if item not in self.terminals: + raise KeyError(item) + else: + return self.terminals[item] + + def name(self): + """Return the name of this node.""" + return self._name + + def rename(self, name): + """Rename this node. This will cause sigRenamed to be emitted.""" + oldName = self._name + self._name = name + #self.emit(QtCore.SIGNAL('renamed'), self, oldName) + self.sigRenamed.emit(self, oldName) + + def dependentNodes(self): + """Return the list of nodes which provide direct input to this node""" + nodes = set() + for t in self.inputs().values(): + nodes |= set([i.node() for i in t.inputTerminals()]) + return nodes + #return set([t.inputTerminals().node() for t in self.listInputs().itervalues()]) + + def __repr__(self): + return "" % (self.name(), id(self)) + + def ctrlWidget(self): + """Return this Node's control widget. + + By default, Nodes have no control widget. Subclasses may reimplement this + method to provide a custom widget. This method is called by Flowcharts + when they are constructing their Node list.""" + return None + + def bypass(self, byp): + """Set whether this node should be bypassed. + + When bypassed, a Node's process() method is never called. In some cases, + data is automatically copied directly from specific input nodes to + output nodes instead (see the bypass argument to Terminal.__init__). + This is usually called when the user disables a node from the flowchart + control panel. + """ + self._bypass = byp + if self.bypassButton is not None: + self.bypassButton.setChecked(byp) + self.update() + + def isBypassed(self): + """Return True if this Node is currently bypassed.""" + return self._bypass + + def setInput(self, **args): + """Set the values on input terminals. For most nodes, this will happen automatically through Terminal.inputChanged. + This is normally only used for nodes with no connected inputs.""" + changed = False + for k, v in args.items(): + term = self._inputs[k] + oldVal = term.value() + if not eq(oldVal, v): + changed = True + term.setValue(v, process=False) + if changed and '_updatesHandled_' not in args: + self.update() + + def inputValues(self): + """Return a dict of all input values currently assigned to this node.""" + vals = {} + for n, t in self.inputs().items(): + vals[n] = t.value() + return vals + + def outputValues(self): + """Return a dict of all output values currently generated by this node.""" + vals = {} + for n, t in self.outputs().items(): + vals[n] = t.value() + return vals + + def connected(self, localTerm, remoteTerm): + """Called whenever one of this node's terminals is connected elsewhere.""" + pass + + def disconnected(self, localTerm, remoteTerm): + """Called whenever one of this node's terminals is disconnected from another.""" + pass + + def update(self, signal=True): + """Collect all input values, attempt to process new output values, and propagate downstream. + Subclasses should call update() whenever thir internal state has changed + (such as when the user interacts with the Node's control widget). Update + is automatically called when the inputs to the node are changed. + """ + vals = self.inputValues() + #print " inputs:", vals + try: + if self.isBypassed(): + out = self.processBypassed(vals) + else: + out = self.process(**strDict(vals)) + #print " output:", out + if out is not None: + if signal: + self.setOutput(**out) + else: + self.setOutputNoSignal(**out) + for n,t in self.inputs().items(): + t.setValueAcceptable(True) + self.clearException() + except: + #printExc( "Exception while processing %s:" % self.name()) + for n,t in self.outputs().items(): + t.setValue(None) + self.setException(sys.exc_info()) + + if signal: + #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data + self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data + + def processBypassed(self, args): + """Called when the flowchart would normally call Node.process, but this node is currently bypassed. + The default implementation looks for output terminals with a bypass connection and returns the + corresponding values. Most Node subclasses will _not_ need to reimplement this method.""" + result = {} + for term in list(self.outputs().values()): + byp = term.bypassValue() + if byp is None: + result[term.name()] = None + else: + result[term.name()] = args.get(byp, None) + return result + + def setOutput(self, **vals): + self.setOutputNoSignal(**vals) + #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data + self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data + + def setOutputNoSignal(self, **vals): + for k, v in vals.items(): + term = self.outputs()[k] + term.setValue(v) + #targets = term.connections() + #for t in targets: ## propagate downstream + #if t is term: + #continue + #t.inputChanged(term) + term.setValueAcceptable(True) + + def setException(self, exc): + self.exception = exc + self.recolor() + + def clearException(self): + self.setException(None) + + def recolor(self): + if self.exception is None: + self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(0, 0, 0))) + else: + self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(150, 0, 0), 3)) + + def saveState(self): + """Return a dictionary representing the current state of this node + (excluding input / output values). This is used for saving/reloading + flowcharts. The default implementation returns this Node's position, + bypass state, and information about each of its terminals. + + Subclasses may want to extend this method, adding extra keys to the returned + dict.""" + pos = self.graphicsItem().pos() + state = {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()} + termsEditable = self._allowAddInput | self._allowAddOutput + for term in self._inputs.values() + self._outputs.values(): + termsEditable |= term._renamable | term._removable | term._multiable + if termsEditable: + state['terminals'] = self.saveTerminals() + return state + + def restoreState(self, state): + """Restore the state of this node from a structure previously generated + by saveState(). """ + pos = state.get('pos', (0,0)) + self.graphicsItem().setPos(*pos) + self.bypass(state.get('bypass', False)) + if 'terminals' in state: + self.restoreTerminals(state['terminals']) + + def saveTerminals(self): + terms = OrderedDict() + for n, t in self.terminals.items(): + terms[n] = (t.saveState()) + return terms + + def restoreTerminals(self, state): + for name in list(self.terminals.keys()): + if name not in state: + self.removeTerminal(name) + for name, opts in state.items(): + if name in self.terminals: + term = self[name] + term.setOpts(**opts) + continue + try: + opts = strDict(opts) + self.addTerminal(name, **opts) + except: + printExc("Error restoring terminal %s (%s):" % (str(name), str(opts))) + + + def clearTerminals(self): + for t in self.terminals.values(): + t.close() + self.terminals = OrderedDict() + self._inputs = OrderedDict() + self._outputs = OrderedDict() + + def close(self): + """Cleans up after the node--removes terminals, graphicsItem, widget""" + self.disconnectAll() + self.clearTerminals() + item = self.graphicsItem() + if item.scene() is not None: + item.scene().removeItem(item) + self._graphicsItem = None + w = self.ctrlWidget() + if w is not None: + w.setParent(None) + #self.emit(QtCore.SIGNAL('closed'), self) + self.sigClosed.emit(self) + + def disconnectAll(self): + for t in self.terminals.values(): + t.disconnectAll() + + +#class NodeGraphicsItem(QtGui.QGraphicsItem): +class NodeGraphicsItem(GraphicsObject): + def __init__(self, node): + #QtGui.QGraphicsItem.__init__(self) + GraphicsObject.__init__(self) + #QObjectWorkaround.__init__(self) + + #self.shadow = QtGui.QGraphicsDropShadowEffect() + #self.shadow.setOffset(5,5) + #self.shadow.setBlurRadius(10) + #self.setGraphicsEffect(self.shadow) + + self.pen = fn.mkPen(0,0,0) + self.selectPen = fn.mkPen(200,200,200,width=2) + self.brush = fn.mkBrush(200, 200, 200, 150) + self.hoverBrush = fn.mkBrush(200, 200, 200, 200) + self.selectBrush = fn.mkBrush(200, 200, 255, 200) + self.hovered = False + + self.node = node + flags = self.ItemIsMovable | self.ItemIsSelectable | self.ItemIsFocusable |self.ItemSendsGeometryChanges + #flags = self.ItemIsFocusable |self.ItemSendsGeometryChanges + + self.setFlags(flags) + self.bounds = QtCore.QRectF(0, 0, 100, 100) + self.nameItem = QtGui.QGraphicsTextItem(self.node.name(), self) + self.nameItem.setDefaultTextColor(QtGui.QColor(50, 50, 50)) + self.nameItem.moveBy(self.bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) + self.nameItem.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) + self.updateTerminals() + #self.setZValue(10) + + self.nameItem.focusOutEvent = self.labelFocusOut + self.nameItem.keyPressEvent = self.labelKeyPress + + self.menu = None + self.buildMenu() + + #self.node.sigTerminalRenamed.connect(self.updateActionMenu) + + #def setZValue(self, z): + #for t, item in self.terminals.itervalues(): + #item.setZValue(z+1) + #GraphicsObject.setZValue(self, z) + + def labelFocusOut(self, ev): + QtGui.QGraphicsTextItem.focusOutEvent(self.nameItem, ev) + self.labelChanged() + + def labelKeyPress(self, ev): + if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: + self.labelChanged() + else: + QtGui.QGraphicsTextItem.keyPressEvent(self.nameItem, ev) + + def labelChanged(self): + newName = str(self.nameItem.toPlainText()) + if newName != self.node.name(): + self.node.rename(newName) + + ### re-center the label + bounds = self.boundingRect() + self.nameItem.setPos(bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) + + def setPen(self, pen): + self.pen = pen + self.update() + + def setBrush(self, brush): + self.brush = brush + self.update() + + + def updateTerminals(self): + bounds = self.bounds + self.terminals = {} + inp = self.node.inputs() + dy = bounds.height() / (len(inp)+1) + y = dy + for i, t in inp.items(): + item = t.graphicsItem() + item.setParentItem(self) + #item.setZValue(self.zValue()+1) + br = self.bounds + item.setAnchor(0, y) + self.terminals[i] = (t, item) + y += dy + + out = self.node.outputs() + dy = bounds.height() / (len(out)+1) + y = dy + for i, t in out.items(): + item = t.graphicsItem() + item.setParentItem(self) + item.setZValue(self.zValue()) + br = self.bounds + item.setAnchor(bounds.width(), y) + self.terminals[i] = (t, item) + y += dy + + #self.buildMenu() + + + def boundingRect(self): + return self.bounds.adjusted(-5, -5, 5, 5) + + def paint(self, p, *args): + + p.setPen(self.pen) + if self.isSelected(): + p.setPen(self.selectPen) + p.setBrush(self.selectBrush) + else: + p.setPen(self.pen) + if self.hovered: + p.setBrush(self.hoverBrush) + else: + p.setBrush(self.brush) + + p.drawRect(self.bounds) + + + def mousePressEvent(self, ev): + ev.ignore() + + + def mouseClickEvent(self, ev): + #print "Node.mouseClickEvent called." + if int(ev.button()) == int(QtCore.Qt.LeftButton): + ev.accept() + #print " ev.button: left" + sel = self.isSelected() + #ret = QtGui.QGraphicsItem.mousePressEvent(self, ev) + self.setSelected(True) + if not sel and self.isSelected(): + #self.setBrush(QtGui.QBrush(QtGui.QColor(200, 200, 255))) + #self.emit(QtCore.SIGNAL('selected')) + #self.scene().selectionChanged.emit() ## for some reason this doesn't seem to be happening automatically + self.update() + #return ret + + elif int(ev.button()) == int(QtCore.Qt.RightButton): + #print " ev.button: right" + ev.accept() + #pos = ev.screenPos() + self.raiseContextMenu(ev) + #self.menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def mouseDragEvent(self, ev): + #print "Node.mouseDrag" + if ev.button() == QtCore.Qt.LeftButton: + ev.accept() + self.setPos(self.pos()+self.mapToParent(ev.pos())-self.mapToParent(ev.lastPos())) + + def hoverEvent(self, ev): + if not ev.isExit() and ev.acceptClicks(QtCore.Qt.LeftButton): + ev.acceptDrags(QtCore.Qt.LeftButton) + self.hovered = True + else: + self.hovered = False + self.update() + + def keyPressEvent(self, ev): + if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: + ev.accept() + if not self.node._allowRemove: + return + self.node.close() + else: + ev.ignore() + + def itemChange(self, change, val): + if change == self.ItemPositionHasChanged: + for k, t in self.terminals.items(): + t[1].nodeMoved() + return GraphicsObject.itemChange(self, change, val) + + + def getMenu(self): + return self.menu + + def getContextMenus(self, event): + return [self.menu] + + def raiseContextMenu(self, ev): + menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def buildMenu(self): + self.menu = QtGui.QMenu() + self.menu.setTitle("Node") + a = self.menu.addAction("Add input", self.addInputFromMenu) + if not self.node._allowAddInput: + a.setEnabled(False) + a = self.menu.addAction("Add output", self.addOutputFromMenu) + if not self.node._allowAddOutput: + a.setEnabled(False) + a = self.menu.addAction("Remove node", self.node.close) + if not self.node._allowRemove: + a.setEnabled(False) + + def addInputFromMenu(self): ## called when add input is clicked in context menu + self.node.addInput(renamable=True, removable=True, multiable=True) + + def addOutputFromMenu(self): ## called when add output is clicked in context menu + self.node.addOutput(renamable=True, removable=True, multiable=False) + diff --git a/pyqtgraph/flowchart/Terminal.py b/pyqtgraph/flowchart/Terminal.py new file mode 100644 index 00000000..45805cd8 --- /dev/null +++ b/pyqtgraph/flowchart/Terminal.py @@ -0,0 +1,638 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +import weakref +from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject +import pyqtgraph.functions as fn +from pyqtgraph.Point import Point +#from PySide import QtCore, QtGui +from .eq import * + +class Terminal(object): + def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None): + """ + Construct a new terminal. + + ============== ================================================================================= + **Arguments:** + node the node to which this terminal belongs + name string, the name of the terminal + io 'in' or 'out' + optional bool, whether the node may process without connection to this terminal + multi bool, for inputs: whether this terminal may make multiple connections + for outputs: whether this terminal creates a different value for each connection + pos [x, y], the position of the terminal within its node's boundaries + renamable (bool) Whether the terminal can be renamed by the user + removable (bool) Whether the terminal can be removed by the user + multiable (bool) Whether the user may toggle the *multi* option for this terminal + bypass (str) Name of the terminal from which this terminal's value is derived + when the Node is in bypass mode. + ============== ================================================================================= + """ + self._io = io + #self._isOutput = opts[0] in ['out', 'io'] + #self._isInput = opts[0]] in ['in', 'io'] + #self._isIO = opts[0]=='io' + self._optional = optional + self._multi = multi + self._node = weakref.ref(node) + self._name = name + self._renamable = renamable + self._removable = removable + self._multiable = multiable + self._connections = {} + self._graphicsItem = TerminalGraphicsItem(self, parent=self._node().graphicsItem()) + self._bypass = bypass + + if multi: + self._value = {} ## dictionary of terminal:value pairs. + else: + self._value = None + + self.valueOk = None + self.recolor() + + def value(self, term=None): + """Return the value this terminal provides for the connected terminal""" + if term is None: + return self._value + + if self.isMultiValue(): + return self._value.get(term, None) + else: + return self._value + + def bypassValue(self): + return self._bypass + + def setValue(self, val, process=True): + """If this is a single-value terminal, val should be a single value. + If this is a multi-value terminal, val should be a dict of terminal:value pairs""" + if not self.isMultiValue(): + if eq(val, self._value): + return + self._value = val + else: + if not isinstance(self._value, dict): + self._value = {} + if val is not None: + self._value.update(val) + + self.setValueAcceptable(None) ## by default, input values are 'unchecked' until Node.update(). + if self.isInput() and process: + self.node().update() + + ## Let the flowchart handle this. + #if self.isOutput(): + #for c in self.connections(): + #if c.isInput(): + #c.inputChanged(self) + self.recolor() + + def setOpts(self, **opts): + self._renamable = opts.get('renamable', self._renamable) + self._removable = opts.get('removable', self._removable) + self._multiable = opts.get('multiable', self._multiable) + if 'multi' in opts: + self.setMultiValue(opts['multi']) + + + def connected(self, term): + """Called whenever this terminal has been connected to another. (note--this function is called on both terminals)""" + if self.isInput() and term.isOutput(): + self.inputChanged(term) + if self.isOutput() and self.isMultiValue(): + self.node().update() + self.node().connected(self, term) + + def disconnected(self, term): + """Called whenever this terminal has been disconnected from another. (note--this function is called on both terminals)""" + if self.isMultiValue() and term in self._value: + del self._value[term] + self.node().update() + #self.recolor() + else: + if self.isInput(): + self.setValue(None) + self.node().disconnected(self, term) + #self.node().update() + + def inputChanged(self, term, process=True): + """Called whenever there is a change to the input value to this terminal. + It may often be useful to override this function.""" + if self.isMultiValue(): + self.setValue({term: term.value(self)}, process=process) + else: + self.setValue(term.value(self), process=process) + + def valueIsAcceptable(self): + """Returns True->acceptable None->unknown False->Unacceptable""" + return self.valueOk + + def setValueAcceptable(self, v=True): + self.valueOk = v + self.recolor() + + def connections(self): + return self._connections + + def node(self): + return self._node() + + def isInput(self): + return self._io == 'in' + + def isMultiValue(self): + return self._multi + + def setMultiValue(self, multi): + """Set whether this is a multi-value terminal.""" + self._multi = multi + if not multi and len(self.inputTerminals()) > 1: + self.disconnectAll() + + for term in self.inputTerminals(): + self.inputChanged(term) + + def isOutput(self): + return self._io == 'out' + + def isRenamable(self): + return self._renamable + + def isRemovable(self): + return self._removable + + def isMultiable(self): + return self._multiable + + def name(self): + return self._name + + def graphicsItem(self): + return self._graphicsItem + + def isConnected(self): + return len(self.connections()) > 0 + + def connectedTo(self, term): + return term in self.connections() + + def hasInput(self): + #conn = self.extendedConnections() + for t in self.connections(): + if t.isOutput(): + return True + return False + + def inputTerminals(self): + """Return the terminal(s) that give input to this one.""" + #terms = self.extendedConnections() + #for t in terms: + #if t.isOutput(): + #return t + return [t for t in self.connections() if t.isOutput()] + + + def dependentNodes(self): + """Return the list of nodes which receive input from this terminal.""" + #conn = self.extendedConnections() + #del conn[self] + return set([t.node() for t in self.connections() if t.isInput()]) + + def connectTo(self, term, connectionItem=None): + try: + if self.connectedTo(term): + raise Exception('Already connected') + if term is self: + raise Exception('Not connecting terminal to self') + if term.node() is self.node(): + raise Exception("Can't connect to terminal on same node.") + for t in [self, term]: + if t.isInput() and not t._multi and len(t.connections()) > 0: + raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, list(t.connections().keys()))) + #if self.hasInput() and term.hasInput(): + #raise Exception('Target terminal already has input') + + #if term in self.node().terminals.values(): + #if self.isOutput() or term.isOutput(): + #raise Exception('Can not connect an output back to the same node.') + except: + if connectionItem is not None: + connectionItem.close() + raise + + if connectionItem is None: + connectionItem = ConnectionItem(self.graphicsItem(), term.graphicsItem()) + #self.graphicsItem().scene().addItem(connectionItem) + self.graphicsItem().getViewBox().addItem(connectionItem) + #connectionItem.setParentItem(self.graphicsItem().parent().parent()) + self._connections[term] = connectionItem + term._connections[self] = connectionItem + + self.recolor() + + #if self.isOutput() and term.isInput(): + #term.inputChanged(self) + #if term.isInput() and term.isOutput(): + #self.inputChanged(term) + self.connected(term) + term.connected(self) + + return connectionItem + + def disconnectFrom(self, term): + if not self.connectedTo(term): + return + item = self._connections[term] + #print "removing connection", item + #item.scene().removeItem(item) + item.close() + del self._connections[term] + del term._connections[self] + self.recolor() + term.recolor() + + self.disconnected(term) + term.disconnected(self) + #if self.isOutput() and term.isInput(): + #term.inputChanged(self) + #if term.isInput() and term.isOutput(): + #self.inputChanged(term) + + + def disconnectAll(self): + for t in list(self._connections.keys()): + self.disconnectFrom(t) + + def recolor(self, color=None, recurse=True): + if color is None: + if not self.isConnected(): ## disconnected terminals are black + color = QtGui.QColor(0,0,0) + elif self.isInput() and not self.hasInput(): ## input terminal with no connected output terminals + color = QtGui.QColor(200,200,0) + elif self._value is None or eq(self._value, {}): ## terminal is connected but has no data (possibly due to processing error) + color = QtGui.QColor(255,255,255) + elif self.valueIsAcceptable() is None: ## terminal has data, but it is unknown if the data is ok + color = QtGui.QColor(200, 200, 0) + elif self.valueIsAcceptable() is True: ## terminal has good input, all ok + color = QtGui.QColor(0, 200, 0) + else: ## terminal has bad input + color = QtGui.QColor(200, 0, 0) + self.graphicsItem().setBrush(QtGui.QBrush(color)) + + if recurse: + for t in self.connections(): + t.recolor(color, recurse=False) + + + def rename(self, name): + oldName = self._name + self._name = name + self.node().terminalRenamed(self, oldName) + self.graphicsItem().termRenamed(name) + + def __repr__(self): + return "" % (str(self.node().name()), str(self.name())) + + #def extendedConnections(self, terms=None): + #"""Return list of terminals (including this one) that are directly or indirectly wired to this.""" + #if terms is None: + #terms = {} + #terms[self] = None + #for t in self._connections: + #if t in terms: + #continue + #terms.update(t.extendedConnections(terms)) + #return terms + + def __hash__(self): + return id(self) + + def close(self): + self.disconnectAll() + item = self.graphicsItem() + if item.scene() is not None: + item.scene().removeItem(item) + + def saveState(self): + return {'io': self._io, 'multi': self._multi, 'optional': self._optional, 'renamable': self._renamable, 'removable': self._removable, 'multiable': self._multiable} + + +#class TerminalGraphicsItem(QtGui.QGraphicsItem): +class TerminalGraphicsItem(GraphicsObject): + + def __init__(self, term, parent=None): + self.term = term + #QtGui.QGraphicsItem.__init__(self, parent) + GraphicsObject.__init__(self, parent) + self.brush = fn.mkBrush(0,0,0) + self.box = QtGui.QGraphicsRectItem(0, 0, 10, 10, self) + self.label = QtGui.QGraphicsTextItem(self.term.name(), self) + self.label.scale(0.7, 0.7) + #self.setAcceptHoverEvents(True) + self.newConnection = None + self.setFiltersChildEvents(True) ## to pick up mouse events on the rectitem + if self.term.isRenamable(): + self.label.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) + self.label.focusOutEvent = self.labelFocusOut + self.label.keyPressEvent = self.labelKeyPress + self.setZValue(1) + self.menu = None + + + def labelFocusOut(self, ev): + QtGui.QGraphicsTextItem.focusOutEvent(self.label, ev) + self.labelChanged() + + def labelKeyPress(self, ev): + if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: + self.labelChanged() + else: + QtGui.QGraphicsTextItem.keyPressEvent(self.label, ev) + + def labelChanged(self): + newName = str(self.label.toPlainText()) + if newName != self.term.name(): + self.term.rename(newName) + + def termRenamed(self, name): + self.label.setPlainText(name) + + def setBrush(self, brush): + self.brush = brush + self.box.setBrush(brush) + + def disconnect(self, target): + self.term.disconnectFrom(target.term) + + def boundingRect(self): + br = self.box.mapRectToParent(self.box.boundingRect()) + lr = self.label.mapRectToParent(self.label.boundingRect()) + return br | lr + + def paint(self, p, *args): + pass + + def setAnchor(self, x, y): + pos = QtCore.QPointF(x, y) + self.anchorPos = pos + br = self.box.mapRectToParent(self.box.boundingRect()) + lr = self.label.mapRectToParent(self.label.boundingRect()) + + + if self.term.isInput(): + self.box.setPos(pos.x(), pos.y()-br.height()/2.) + self.label.setPos(pos.x() + br.width(), pos.y() - lr.height()/2.) + else: + self.box.setPos(pos.x()-br.width(), pos.y()-br.height()/2.) + self.label.setPos(pos.x()-br.width()-lr.width(), pos.y()-lr.height()/2.) + self.updateConnections() + + def updateConnections(self): + for t, c in self.term.connections().items(): + c.updateLine() + + def mousePressEvent(self, ev): + #ev.accept() + ev.ignore() ## necessary to allow click/drag events to process correctly + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + ev.accept() + self.label.setFocus(QtCore.Qt.MouseFocusReason) + elif ev.button() == QtCore.Qt.RightButton: + ev.accept() + self.raiseContextMenu(ev) + + def raiseContextMenu(self, ev): + ## only raise menu if this terminal is removable + menu = self.getMenu() + menu = self.scene().addParentContextMenus(self, menu, ev) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def getMenu(self): + if self.menu is None: + self.menu = QtGui.QMenu() + self.menu.setTitle("Terminal") + remAct = QtGui.QAction("Remove terminal", self.menu) + remAct.triggered.connect(self.removeSelf) + self.menu.addAction(remAct) + self.menu.remAct = remAct + if not self.term.isRemovable(): + remAct.setEnabled(False) + multiAct = QtGui.QAction("Multi-value", self.menu) + multiAct.setCheckable(True) + multiAct.setChecked(self.term.isMultiValue()) + multiAct.setEnabled(self.term.isMultiable()) + + multiAct.triggered.connect(self.toggleMulti) + self.menu.addAction(multiAct) + self.menu.multiAct = multiAct + if self.term.isMultiable(): + multiAct.setEnabled = False + return self.menu + + def toggleMulti(self): + multi = self.menu.multiAct.isChecked() + self.term.setMultiValue(multi) + + ## probably never need this + #def getContextMenus(self, ev): + #return [self.getMenu()] + + def removeSelf(self): + self.term.node().removeTerminal(self.term) + + def mouseDragEvent(self, ev): + if ev.button() != QtCore.Qt.LeftButton: + ev.ignore() + return + + ev.accept() + if ev.isStart(): + if self.newConnection is None: + self.newConnection = ConnectionItem(self) + #self.scene().addItem(self.newConnection) + self.getViewBox().addItem(self.newConnection) + #self.newConnection.setParentItem(self.parent().parent()) + + self.newConnection.setTarget(self.mapToView(ev.pos())) + elif ev.isFinish(): + if self.newConnection is not None: + items = self.scene().items(ev.scenePos()) + gotTarget = False + for i in items: + if isinstance(i, TerminalGraphicsItem): + self.newConnection.setTarget(i) + try: + self.term.connectTo(i.term, self.newConnection) + gotTarget = True + except: + self.scene().removeItem(self.newConnection) + self.newConnection = None + raise + break + + if not gotTarget: + #print "remove unused connection" + #self.scene().removeItem(self.newConnection) + self.newConnection.close() + self.newConnection = None + else: + if self.newConnection is not None: + self.newConnection.setTarget(self.mapToView(ev.pos())) + + def hoverEvent(self, ev): + if not ev.isExit() and ev.acceptDrags(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. + ev.acceptClicks(QtCore.Qt.RightButton) + self.box.setBrush(fn.mkBrush('w')) + else: + self.box.setBrush(self.brush) + self.update() + + #def hoverEnterEvent(self, ev): + #self.hover = True + + #def hoverLeaveEvent(self, ev): + #self.hover = False + + def connectPoint(self): + ## return the connect position of this terminal in view coords + return self.mapToView(self.mapFromItem(self.box, self.box.boundingRect().center())) + + def nodeMoved(self): + for t, item in self.term.connections().items(): + item.updateLine() + + +#class ConnectionItem(QtGui.QGraphicsItem): +class ConnectionItem(GraphicsObject): + + def __init__(self, source, target=None): + #QtGui.QGraphicsItem.__init__(self) + GraphicsObject.__init__(self) + self.setFlags( + self.ItemIsSelectable | + self.ItemIsFocusable + ) + self.source = source + self.target = target + self.length = 0 + self.hovered = False + self.path = None + self.shapePath = None + self.style = { + 'shape': 'line', + 'color': (100, 100, 250), + 'width': 1.0, + 'hoverColor': (150, 150, 250), + 'hoverWidth': 1.0, + 'selectedColor': (200, 200, 0), + 'selectedWidth': 3.0, + } + #self.line = QtGui.QGraphicsLineItem(self) + self.source.getViewBox().addItem(self) + self.updateLine() + self.setZValue(0) + + def close(self): + if self.scene() is not None: + #self.scene().removeItem(self.line) + self.scene().removeItem(self) + + def setTarget(self, target): + self.target = target + self.updateLine() + + def setStyle(self, **kwds): + self.style.update(kwds) + if 'shape' in kwds: + self.updateLine() + else: + self.update() + + def updateLine(self): + start = Point(self.source.connectPoint()) + if isinstance(self.target, TerminalGraphicsItem): + stop = Point(self.target.connectPoint()) + elif isinstance(self.target, QtCore.QPointF): + stop = Point(self.target) + else: + return + self.prepareGeometryChange() + + self.path = self.generatePath(start, stop) + self.shapePath = None + self.update() + + def generatePath(self, start, stop): + path = QtGui.QPainterPath() + path.moveTo(start) + if self.style['shape'] == 'line': + path.lineTo(stop) + elif self.style['shape'] == 'cubic': + path.cubicTo(Point(stop.x(), start.y()), Point(start.x(), stop.y()), Point(stop.x(), stop.y())) + else: + raise Exception('Invalid shape "%s"; options are "line" or "cubic"' % self.style['shape']) + return path + + def keyPressEvent(self, ev): + if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: + #if isinstance(self.target, TerminalGraphicsItem): + self.source.disconnect(self.target) + ev.accept() + else: + ev.ignore() + + def mousePressEvent(self, ev): + ev.ignore() + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + ev.accept() + sel = self.isSelected() + self.setSelected(True) + if not sel and self.isSelected(): + self.update() + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.LeftButton): + self.hovered = True + else: + self.hovered = False + self.update() + + + def boundingRect(self): + return self.shape().boundingRect() + ##return self.line.boundingRect() + #px = self.pixelWidth() + #return QtCore.QRectF(-5*px, 0, 10*px, self.length) + def viewRangeChanged(self): + self.shapePath = None + self.prepareGeometryChange() + + def shape(self): + if self.shapePath is None: + if self.path is None: + return QtGui.QPainterPath() + stroker = QtGui.QPainterPathStroker() + px = self.pixelWidth() + stroker.setWidth(px*8) + self.shapePath = stroker.createStroke(self.path) + return self.shapePath + + def paint(self, p, *args): + if self.isSelected(): + p.setPen(fn.mkPen(self.style['selectedColor'], width=self.style['selectedWidth'])) + else: + if self.hovered: + p.setPen(fn.mkPen(self.style['hoverColor'], width=self.style['hoverWidth'])) + else: + p.setPen(fn.mkPen(self.style['color'], width=self.style['width'])) + + #p.drawLine(0, 0, 0, self.length) + + p.drawPath(self.path) diff --git a/pyqtgraph/flowchart/__init__.py b/pyqtgraph/flowchart/__init__.py new file mode 100644 index 00000000..46e04db0 --- /dev/null +++ b/pyqtgraph/flowchart/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from .Flowchart import * + +from .library import getNodeType, registerNodeType, getNodeTree \ No newline at end of file diff --git a/pyqtgraph/flowchart/eq.py b/pyqtgraph/flowchart/eq.py new file mode 100644 index 00000000..031ebce8 --- /dev/null +++ b/pyqtgraph/flowchart/eq.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from numpy import ndarray, bool_ +from pyqtgraph.metaarray import MetaArray + +def eq(a, b): + """The great missing equivalence function: Guaranteed evaluation to a single bool value.""" + if a is b: + return True + + try: + e = a==b + except ValueError: + return False + except AttributeError: + return False + except: + print("a:", str(type(a)), str(a)) + print("b:", str(type(b)), str(b)) + raise + t = type(e) + if t is bool: + return e + elif t is bool_: + return bool(e) + elif isinstance(e, ndarray) or (hasattr(e, 'implements') and e.implements('MetaArray')): + try: ## disaster: if a is an empty array and b is not, then e.all() is True + if a.shape != b.shape: + return False + except: + return False + if (hasattr(e, 'implements') and e.implements('MetaArray')): + return e.asarray().all() + else: + return e.all() + else: + raise Exception("== operator returned type %s" % str(type(e))) diff --git a/pyqtgraph/flowchart/library/Data.py b/pyqtgraph/flowchart/library/Data.py new file mode 100644 index 00000000..cbef848a --- /dev/null +++ b/pyqtgraph/flowchart/library/Data.py @@ -0,0 +1,356 @@ +# -*- coding: utf-8 -*- +from ..Node import Node +from pyqtgraph.Qt import QtGui, QtCore +import numpy as np +from .common import * +from pyqtgraph.SRTTransform import SRTTransform +from pyqtgraph.Point import Point +from pyqtgraph.widgets.TreeWidget import TreeWidget +from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem + +from . import functions + +class ColumnSelectNode(Node): + """Select named columns from a record array or MetaArray.""" + nodeName = "ColumnSelect" + def __init__(self, name): + Node.__init__(self, name, terminals={'In': {'io': 'in'}}) + self.columns = set() + self.columnList = QtGui.QListWidget() + self.axis = 0 + self.columnList.itemChanged.connect(self.itemChanged) + + def process(self, In, display=True): + if display: + self.updateList(In) + + out = {} + if hasattr(In, 'implements') and In.implements('MetaArray'): + for c in self.columns: + out[c] = In[self.axis:c] + elif isinstance(In, np.ndarray) and In.dtype.fields is not None: + for c in self.columns: + out[c] = In[c] + else: + self.In.setValueAcceptable(False) + raise Exception("Input must be MetaArray or ndarray with named fields") + + return out + + def ctrlWidget(self): + return self.columnList + + def updateList(self, data): + if hasattr(data, 'implements') and data.implements('MetaArray'): + cols = data.listColumns() + for ax in cols: ## find first axis with columns + if len(cols[ax]) > 0: + self.axis = ax + cols = set(cols[ax]) + break + else: + cols = list(data.dtype.fields.keys()) + + rem = set() + for c in self.columns: + if c not in cols: + self.removeTerminal(c) + rem.add(c) + self.columns -= rem + + self.columnList.blockSignals(True) + self.columnList.clear() + for c in cols: + item = QtGui.QListWidgetItem(c) + item.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsUserCheckable) + if c in self.columns: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + self.columnList.addItem(item) + self.columnList.blockSignals(False) + + + def itemChanged(self, item): + col = str(item.text()) + if item.checkState() == QtCore.Qt.Checked: + if col not in self.columns: + self.columns.add(col) + self.addOutput(col) + else: + if col in self.columns: + self.columns.remove(col) + self.removeTerminal(col) + self.update() + + def saveState(self): + state = Node.saveState(self) + state['columns'] = list(self.columns) + return state + + def restoreState(self, state): + Node.restoreState(self, state) + self.columns = set(state.get('columns', [])) + for c in self.columns: + self.addOutput(c) + + + +class RegionSelectNode(CtrlNode): + """Returns a slice from a 1-D array. Connect the 'widget' output to a plot to display a region-selection widget.""" + nodeName = "RegionSelect" + uiTemplate = [ + ('start', 'spin', {'value': 0, 'step': 0.1}), + ('stop', 'spin', {'value': 0.1, 'step': 0.1}), + ('display', 'check', {'value': True}), + ('movable', 'check', {'value': True}), + ] + + def __init__(self, name): + self.items = {} + CtrlNode.__init__(self, name, terminals={ + 'data': {'io': 'in'}, + 'selected': {'io': 'out'}, + 'region': {'io': 'out'}, + 'widget': {'io': 'out', 'multi': True} + }) + self.ctrls['display'].toggled.connect(self.displayToggled) + self.ctrls['movable'].toggled.connect(self.movableToggled) + + def displayToggled(self, b): + for item in self.items.values(): + item.setVisible(b) + + def movableToggled(self, b): + for item in self.items.values(): + item.setMovable(b) + + + def process(self, data=None, display=True): + #print "process.." + s = self.stateGroup.state() + region = [s['start'], s['stop']] + + if display: + conn = self['widget'].connections() + for c in conn: + plot = c.node().getPlot() + if plot is None: + continue + if c in self.items: + item = self.items[c] + item.setRegion(region) + #print " set rgn:", c, region + #item.setXVals(events) + else: + item = LinearRegionItem(values=region) + self.items[c] = item + #item.connect(item, QtCore.SIGNAL('regionChanged'), self.rgnChanged) + item.sigRegionChanged.connect(self.rgnChanged) + item.setVisible(s['display']) + item.setMovable(s['movable']) + #print " new rgn:", c, region + #self.items[c].setYRange([0., 0.2], relative=True) + + if self['selected'].isConnected(): + if data is None: + sliced = None + elif (hasattr(data, 'implements') and data.implements('MetaArray')): + sliced = data[0:s['start']:s['stop']] + else: + mask = (data['time'] >= s['start']) * (data['time'] < s['stop']) + sliced = data[mask] + else: + sliced = None + + return {'selected': sliced, 'widget': self.items, 'region': region} + + + def rgnChanged(self, item): + region = item.getRegion() + self.stateGroup.setState({'start': region[0], 'stop': region[1]}) + self.update() + + +class EvalNode(Node): + """Return the output of a string evaluated/executed by the python interpreter. + The string may be either an expression or a python script, and inputs are accessed as the name of the terminal. + For expressions, a single value may be evaluated for a single output, or a dict for multiple outputs. + For a script, the text will be executed as the body of a function.""" + nodeName = 'PythonEval' + + def __init__(self, name): + Node.__init__(self, name, + terminals = { + 'input': {'io': 'in', 'renamable': True}, + 'output': {'io': 'out', 'renamable': True}, + }, + allowAddInput=True, allowAddOutput=True) + + self.ui = QtGui.QWidget() + self.layout = QtGui.QGridLayout() + #self.addInBtn = QtGui.QPushButton('+Input') + #self.addOutBtn = QtGui.QPushButton('+Output') + self.text = QtGui.QTextEdit() + self.text.setTabStopWidth(30) + self.text.setPlainText("# Access inputs as args['input_name']\nreturn {'output': None} ## one key per output terminal") + #self.layout.addWidget(self.addInBtn, 0, 0) + #self.layout.addWidget(self.addOutBtn, 0, 1) + self.layout.addWidget(self.text, 1, 0, 1, 2) + self.ui.setLayout(self.layout) + + #QtCore.QObject.connect(self.addInBtn, QtCore.SIGNAL('clicked()'), self.addInput) + #self.addInBtn.clicked.connect(self.addInput) + #QtCore.QObject.connect(self.addOutBtn, QtCore.SIGNAL('clicked()'), self.addOutput) + #self.addOutBtn.clicked.connect(self.addOutput) + self.text.focusOutEvent = self.focusOutEvent + self.lastText = None + + def ctrlWidget(self): + return self.ui + + #def addInput(self): + #Node.addInput(self, 'input', renamable=True) + + #def addOutput(self): + #Node.addOutput(self, 'output', renamable=True) + + def focusOutEvent(self, ev): + text = str(self.text.toPlainText()) + if text != self.lastText: + self.lastText = text + self.update() + return QtGui.QTextEdit.focusOutEvent(self.text, ev) + + def process(self, display=True, **args): + l = locals() + l.update(args) + ## try eval first, then exec + try: + text = str(self.text.toPlainText()).replace('\n', ' ') + output = eval(text, globals(), l) + except SyntaxError: + fn = "def fn(**args):\n" + run = "\noutput=fn(**args)\n" + text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run + exec(text) + except: + print("Error processing node: %s" % self.name()) + raise + return output + + def saveState(self): + state = Node.saveState(self) + state['text'] = str(self.text.toPlainText()) + #state['terminals'] = self.saveTerminals() + return state + + def restoreState(self, state): + Node.restoreState(self, state) + self.text.clear() + self.text.insertPlainText(state['text']) + self.restoreTerminals(state['terminals']) + self.update() + +class ColumnJoinNode(Node): + """Concatenates record arrays and/or adds new columns""" + nodeName = 'ColumnJoin' + + def __init__(self, name): + Node.__init__(self, name, terminals = { + 'output': {'io': 'out'}, + }) + + #self.items = [] + + self.ui = QtGui.QWidget() + self.layout = QtGui.QGridLayout() + self.ui.setLayout(self.layout) + + self.tree = TreeWidget() + self.addInBtn = QtGui.QPushButton('+ Input') + self.remInBtn = QtGui.QPushButton('- Input') + + self.layout.addWidget(self.tree, 0, 0, 1, 2) + self.layout.addWidget(self.addInBtn, 1, 0) + self.layout.addWidget(self.remInBtn, 1, 1) + + self.addInBtn.clicked.connect(self.addInput) + self.remInBtn.clicked.connect(self.remInput) + self.tree.sigItemMoved.connect(self.update) + + def ctrlWidget(self): + return self.ui + + def addInput(self): + #print "ColumnJoinNode.addInput called." + term = Node.addInput(self, 'input', renamable=True, removable=True, multiable=True) + #print "Node.addInput returned. term:", term + item = QtGui.QTreeWidgetItem([term.name()]) + item.term = term + term.joinItem = item + #self.items.append((term, item)) + self.tree.addTopLevelItem(item) + + def remInput(self): + sel = self.tree.currentItem() + term = sel.term + term.joinItem = None + sel.term = None + self.tree.removeTopLevelItem(sel) + self.removeTerminal(term) + self.update() + + def process(self, display=True, **args): + order = self.order() + vals = [] + for name in order: + if name not in args: + continue + val = args[name] + if isinstance(val, np.ndarray) and len(val.dtype) > 0: + vals.append(val) + else: + vals.append((name, None, val)) + return {'output': functions.concatenateColumns(vals)} + + def order(self): + return [str(self.tree.topLevelItem(i).text(0)) for i in range(self.tree.topLevelItemCount())] + + def saveState(self): + state = Node.saveState(self) + state['order'] = self.order() + return state + + def restoreState(self, state): + Node.restoreState(self, state) + inputs = self.inputs() + + ## Node.restoreState should have created all of the terminals we need + ## However: to maintain support for some older flowchart files, we need + ## to manually add any terminals that were not taken care of. + for name in [n for n in state['order'] if n not in inputs]: + Node.addInput(self, name, renamable=True, removable=True, multiable=True) + inputs = self.inputs() + + order = [name for name in state['order'] if name in inputs] + for name in inputs: + if name not in order: + order.append(name) + + self.tree.clear() + for name in order: + term = self[name] + item = QtGui.QTreeWidgetItem([name]) + item.term = term + term.joinItem = item + #self.items.append((term, item)) + self.tree.addTopLevelItem(item) + + def terminalRenamed(self, term, oldName): + Node.terminalRenamed(self, term, oldName) + item = term.joinItem + item.setText(0, term.name()) + self.update() + + diff --git a/pyqtgraph/flowchart/library/Display.py b/pyqtgraph/flowchart/library/Display.py new file mode 100644 index 00000000..9068c0ec --- /dev/null +++ b/pyqtgraph/flowchart/library/Display.py @@ -0,0 +1,275 @@ +# -*- coding: utf-8 -*- +from ..Node import Node +import weakref +#from pyqtgraph import graphicsItems +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.graphicsItems.ScatterPlotItem import ScatterPlotItem +from pyqtgraph.graphicsItems.PlotCurveItem import PlotCurveItem +from pyqtgraph import PlotDataItem + +from .common import * +import numpy as np + +class PlotWidgetNode(Node): + """Connection to PlotWidget. Will plot arrays, metaarrays, and display event lists.""" + nodeName = 'PlotWidget' + sigPlotChanged = QtCore.Signal(object) + + def __init__(self, name): + Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) + self.plot = None + self.items = {} + + def disconnected(self, localTerm, remoteTerm): + if localTerm is self['In'] and remoteTerm in self.items: + self.plot.removeItem(self.items[remoteTerm]) + del self.items[remoteTerm] + + def setPlot(self, plot): + #print "======set plot" + self.plot = plot + self.sigPlotChanged.emit(self) + + def getPlot(self): + return self.plot + + def process(self, In, display=True): + if display: + #self.plot.clearPlots() + items = set() + for name, vals in In.items(): + if vals is None: + continue + if type(vals) is not list: + vals = [vals] + + for val in vals: + vid = id(val) + if vid in self.items and self.items[vid].scene() is self.plot.scene(): + items.add(vid) + else: + #if isinstance(val, PlotCurveItem): + #self.plot.addItem(val) + #item = val + #if isinstance(val, ScatterPlotItem): + #self.plot.addItem(val) + #item = val + if isinstance(val, QtGui.QGraphicsItem): + self.plot.addItem(val) + item = val + else: + item = self.plot.plot(val) + self.items[vid] = item + items.add(vid) + for vid in list(self.items.keys()): + if vid not in items: + #print "remove", self.items[vid] + self.plot.removeItem(self.items[vid]) + del self.items[vid] + + def processBypassed(self, args): + for item in list(self.items.values()): + self.plot.removeItem(item) + self.items = {} + + #def setInput(self, **args): + #for k in args: + #self.plot.plot(args[k]) + + + +class CanvasNode(Node): + """Connection to a Canvas widget.""" + nodeName = 'CanvasWidget' + + def __init__(self, name): + Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) + self.canvas = None + self.items = {} + + def disconnected(self, localTerm, remoteTerm): + if localTerm is self.In and remoteTerm in self.items: + self.canvas.removeItem(self.items[remoteTerm]) + del self.items[remoteTerm] + + def setCanvas(self, canvas): + self.canvas = canvas + + def getCanvas(self): + return self.canvas + + def process(self, In, display=True): + if display: + items = set() + for name, vals in In.items(): + if vals is None: + continue + if type(vals) is not list: + vals = [vals] + + for val in vals: + vid = id(val) + if vid in self.items: + items.add(vid) + else: + self.canvas.addItem(val) + item = val + self.items[vid] = item + items.add(vid) + for vid in list(self.items.keys()): + if vid not in items: + #print "remove", self.items[vid] + self.canvas.removeItem(self.items[vid]) + del self.items[vid] + + +class PlotCurve(CtrlNode): + """Generates a plot curve from x/y data""" + nodeName = 'PlotCurve' + uiTemplate = [ + ('color', 'color'), + ] + + def __init__(self, name): + CtrlNode.__init__(self, name, terminals={ + 'x': {'io': 'in'}, + 'y': {'io': 'in'}, + 'plot': {'io': 'out'} + }) + self.item = PlotDataItem() + + def process(self, x, y, display=True): + #print "scatterplot process" + if not display: + return {'plot': None} + + self.item.setData(x, y, pen=self.ctrls['color'].color()) + return {'plot': self.item} + + + + +class ScatterPlot(CtrlNode): + """Generates a scatter plot from a record array or nested dicts""" + nodeName = 'ScatterPlot' + uiTemplate = [ + ('x', 'combo', {'values': [], 'index': 0}), + ('y', 'combo', {'values': [], 'index': 0}), + ('sizeEnabled', 'check', {'value': False}), + ('size', 'combo', {'values': [], 'index': 0}), + ('absoluteSize', 'check', {'value': False}), + ('colorEnabled', 'check', {'value': False}), + ('color', 'colormap', {}), + ('borderEnabled', 'check', {'value': False}), + ('border', 'colormap', {}), + ] + + def __init__(self, name): + CtrlNode.__init__(self, name, terminals={ + 'input': {'io': 'in'}, + 'plot': {'io': 'out'} + }) + self.item = ScatterPlotItem() + self.keys = [] + + #self.ui = QtGui.QWidget() + #self.layout = QtGui.QGridLayout() + #self.ui.setLayout(self.layout) + + #self.xCombo = QtGui.QComboBox() + #self.yCombo = QtGui.QComboBox() + + + + def process(self, input, display=True): + #print "scatterplot process" + if not display: + return {'plot': None} + + self.updateKeys(input[0]) + + x = str(self.ctrls['x'].currentText()) + y = str(self.ctrls['y'].currentText()) + size = str(self.ctrls['size'].currentText()) + pen = QtGui.QPen(QtGui.QColor(0,0,0,0)) + points = [] + for i in input: + pt = {'pos': (i[x], i[y])} + if self.ctrls['sizeEnabled'].isChecked(): + pt['size'] = i[size] + if self.ctrls['borderEnabled'].isChecked(): + pt['pen'] = QtGui.QPen(self.ctrls['border'].getColor(i)) + else: + pt['pen'] = pen + if self.ctrls['colorEnabled'].isChecked(): + pt['brush'] = QtGui.QBrush(self.ctrls['color'].getColor(i)) + points.append(pt) + self.item.setPxMode(not self.ctrls['absoluteSize'].isChecked()) + + self.item.setPoints(points) + + return {'plot': self.item} + + + + def updateKeys(self, data): + if isinstance(data, dict): + keys = list(data.keys()) + elif isinstance(data, list) or isinstance(data, tuple): + keys = data + elif isinstance(data, np.ndarray) or isinstance(data, np.void): + keys = data.dtype.names + else: + print("Unknown data type:", type(data), data) + return + + for c in self.ctrls.values(): + c.blockSignals(True) + for c in [self.ctrls['x'], self.ctrls['y'], self.ctrls['size']]: + cur = str(c.currentText()) + c.clear() + for k in keys: + c.addItem(k) + if k == cur: + c.setCurrentIndex(c.count()-1) + for c in [self.ctrls['color'], self.ctrls['border']]: + c.setArgList(keys) + for c in self.ctrls.values(): + c.blockSignals(False) + + self.keys = keys + + + def saveState(self): + state = CtrlNode.saveState(self) + return {'keys': self.keys, 'ctrls': state} + + def restoreState(self, state): + self.updateKeys(state['keys']) + CtrlNode.restoreState(self, state['ctrls']) + +#class ImageItem(Node): + #"""Creates an ImageItem for display in a canvas from a file handle.""" + #nodeName = 'Image' + + #def __init__(self, name): + #Node.__init__(self, name, terminals={ + #'file': {'io': 'in'}, + #'image': {'io': 'out'} + #}) + #self.imageItem = graphicsItems.ImageItem() + #self.handle = None + + #def process(self, file, display=True): + #if not display: + #return {'image': None} + + #if file != self.handle: + #self.handle = file + #data = file.read() + #self.imageItem.updateImage(data) + + #pos = file. + + + \ No newline at end of file diff --git a/pyqtgraph/flowchart/library/Filters.py b/pyqtgraph/flowchart/library/Filters.py new file mode 100644 index 00000000..090c261c --- /dev/null +++ b/pyqtgraph/flowchart/library/Filters.py @@ -0,0 +1,268 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +from ..Node import Node +from scipy.signal import detrend +from scipy.ndimage import median_filter, gaussian_filter +#from pyqtgraph.SignalProxy import SignalProxy +from . import functions +from .common import * +import numpy as np + +import pyqtgraph.metaarray as metaarray + + +class Downsample(CtrlNode): + """Downsample by averaging samples together.""" + nodeName = 'Downsample' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + def processData(self, data): + return functions.downsample(data, self.ctrls['n'].value(), axis=0) + + +class Subsample(CtrlNode): + """Downsample by selecting every Nth sample.""" + nodeName = 'Subsample' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + def processData(self, data): + return data[::self.ctrls['n'].value()] + + +class Bessel(CtrlNode): + """Bessel filter. Input data must have time values.""" + nodeName = 'BesselFilter' + uiTemplate = [ + ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), + ('cutoff', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('order', 'intSpin', {'value': 4, 'min': 1, 'max': 16}), + ('bidir', 'check', {'checked': True}) + ] + + def processData(self, data): + s = self.stateGroup.state() + if s['band'] == 'lowpass': + mode = 'low' + else: + mode = 'high' + return functions.besselFilter(data, bidir=s['bidir'], btype=mode, cutoff=s['cutoff'], order=s['order']) + + +class Butterworth(CtrlNode): + """Butterworth filter""" + nodeName = 'ButterworthFilter' + uiTemplate = [ + ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), + ('wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('bidir', 'check', {'checked': True}) + ] + + def processData(self, data): + s = self.stateGroup.state() + if s['band'] == 'lowpass': + mode = 'low' + else: + mode = 'high' + ret = functions.butterworthFilter(data, bidir=s['bidir'], btype=mode, wPass=s['wPass'], wStop=s['wStop'], gPass=s['gPass'], gStop=s['gStop']) + return ret + + +class ButterworthNotch(CtrlNode): + """Butterworth notch filter""" + nodeName = 'ButterworthNotchFilter' + uiTemplate = [ + ('low_wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('low_wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('low_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('low_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('high_wPass', 'spin', {'value': 3000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('high_wStop', 'spin', {'value': 4000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('high_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('high_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('bidir', 'check', {'checked': True}) + ] + + def processData(self, data): + s = self.stateGroup.state() + + low = functions.butterworthFilter(data, bidir=s['bidir'], btype='low', wPass=s['low_wPass'], wStop=s['low_wStop'], gPass=s['low_gPass'], gStop=s['low_gStop']) + high = functions.butterworthFilter(data, bidir=s['bidir'], btype='high', wPass=s['high_wPass'], wStop=s['high_wStop'], gPass=s['high_gPass'], gStop=s['high_gStop']) + return low + high + + +class Mean(CtrlNode): + """Filters data by taking the mean of a sliding window""" + nodeName = 'MeanFilter' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + @metaArrayWrapper + def processData(self, data): + n = self.ctrls['n'].value() + return functions.rollingSum(data, n) / n + + +class Median(CtrlNode): + """Filters data by taking the median of a sliding window""" + nodeName = 'MedianFilter' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + @metaArrayWrapper + def processData(self, data): + return median_filter(data, self.ctrls['n'].value()) + +class Mode(CtrlNode): + """Filters data by taking the mode (histogram-based) of a sliding window""" + nodeName = 'ModeFilter' + uiTemplate = [ + ('window', 'intSpin', {'value': 500, 'min': 1, 'max': 1000000}), + ] + + @metaArrayWrapper + def processData(self, data): + return functions.modeFilter(data, self.ctrls['window'].value()) + + +class Denoise(CtrlNode): + """Removes anomalous spikes from data, replacing with nearby values""" + nodeName = 'DenoiseFilter' + uiTemplate = [ + ('radius', 'intSpin', {'value': 2, 'min': 0, 'max': 1000000}), + ('threshold', 'doubleSpin', {'value': 4.0, 'min': 0, 'max': 1000}) + ] + + def processData(self, data): + #print "DENOISE" + s = self.stateGroup.state() + return functions.denoise(data, **s) + + +class Gaussian(CtrlNode): + """Gaussian smoothing filter.""" + nodeName = 'GaussianFilter' + uiTemplate = [ + ('sigma', 'doubleSpin', {'min': 0, 'max': 1000000}) + ] + + @metaArrayWrapper + def processData(self, data): + return gaussian_filter(data, self.ctrls['sigma'].value()) + + +class Derivative(CtrlNode): + """Returns the pointwise derivative of the input""" + nodeName = 'DerivativeFilter' + + def processData(self, data): + if hasattr(data, 'implements') and data.implements('MetaArray'): + info = data.infoCopy() + if 'values' in info[0]: + info[0]['values'] = info[0]['values'][:-1] + return metaarray.MetaArray(data[1:] - data[:-1], info=info) + else: + return data[1:] - data[:-1] + + +class Integral(CtrlNode): + """Returns the pointwise integral of the input""" + nodeName = 'IntegralFilter' + + @metaArrayWrapper + def processData(self, data): + data[1:] += data[:-1] + return data + + +class Detrend(CtrlNode): + """Removes linear trend from the data""" + nodeName = 'DetrendFilter' + + @metaArrayWrapper + def processData(self, data): + return detrend(data) + + +class AdaptiveDetrend(CtrlNode): + """Removes baseline from data, ignoring anomalous events""" + nodeName = 'AdaptiveDetrend' + uiTemplate = [ + ('threshold', 'doubleSpin', {'value': 3.0, 'min': 0, 'max': 1000000}) + ] + + def processData(self, data): + return functions.adaptiveDetrend(data, threshold=self.ctrls['threshold'].value()) + +class HistogramDetrend(CtrlNode): + """Removes baseline from data by computing mode (from histogram) of beginning and end of data.""" + nodeName = 'HistogramDetrend' + uiTemplate = [ + ('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), + ('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}), + ('offsetOnly', 'check', {'checked': False}), + ] + + def processData(self, data): + s = self.stateGroup.state() + #ws = self.ctrls['windowSize'].value() + #bn = self.ctrls['numBins'].value() + #offset = self.ctrls['offsetOnly'].checked() + return functions.histogramDetrend(data, window=s['windowSize'], bins=s['numBins'], offsetOnly=s['offsetOnly']) + + + +class RemovePeriodic(CtrlNode): + nodeName = 'RemovePeriodic' + uiTemplate = [ + #('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), + #('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}) + ('f0', 'spin', {'value': 60, 'suffix': 'Hz', 'siPrefix': True, 'min': 0, 'max': None}), + ('harmonics', 'intSpin', {'value': 30, 'min': 0}), + ('samples', 'intSpin', {'value': 1, 'min': 1}), + ] + + def processData(self, data): + times = data.xvals('Time') + dt = times[1]-times[0] + + data1 = data.asarray() + ft = np.fft.fft(data1) + + ## determine frequencies in fft data + df = 1.0 / (len(data1) * dt) + freqs = np.linspace(0.0, (len(ft)-1) * df, len(ft)) + + ## flatten spikes at f0 and harmonics + f0 = self.ctrls['f0'].value() + for i in xrange(1, self.ctrls['harmonics'].value()+2): + f = f0 * i # target frequency + + ## determine index range to check for this frequency + ind1 = int(np.floor(f / df)) + ind2 = int(np.ceil(f / df)) + (self.ctrls['samples'].value()-1) + if ind1 > len(ft)/2.: + break + mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5 + for j in range(ind1, ind2+1): + phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts. + re = mag * np.cos(phase) + im = mag * np.sin(phase) + ft[j] = re + im*1j + ft[len(ft)-j] = re - im*1j + + data2 = np.fft.ifft(ft).real + + ma = metaarray.MetaArray(data2, info=data.infoCopy()) + return ma + + + \ No newline at end of file diff --git a/pyqtgraph/flowchart/library/Operators.py b/pyqtgraph/flowchart/library/Operators.py new file mode 100644 index 00000000..579d2cd2 --- /dev/null +++ b/pyqtgraph/flowchart/library/Operators.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from ..Node import Node + +class UniOpNode(Node): + """Generic node for performing any operation like Out = In.fn()""" + def __init__(self, name, fn): + self.fn = fn + Node.__init__(self, name, terminals={ + 'In': {'io': 'in'}, + 'Out': {'io': 'out', 'bypass': 'In'} + }) + + def process(self, **args): + return {'Out': getattr(args['In'], self.fn)()} + +class BinOpNode(Node): + """Generic node for performing any operation like A.fn(B)""" + def __init__(self, name, fn): + self.fn = fn + Node.__init__(self, name, terminals={ + 'A': {'io': 'in'}, + 'B': {'io': 'in'}, + 'Out': {'io': 'out', 'bypass': 'A'} + }) + + def process(self, **args): + if isinstance(self.fn, tuple): + for name in self.fn: + try: + fn = getattr(args['A'], name) + break + except AttributeError: + pass + else: + fn = getattr(args['A'], self.fn) + out = fn(args['B']) + if out is NotImplemented: + raise Exception("Operation %s not implemented between %s and %s" % (fn, str(type(args['A'])), str(type(args['B'])))) + #print " ", fn, out + return {'Out': out} + + +class AbsNode(UniOpNode): + """Returns abs(Inp). Does not check input types.""" + nodeName = 'Abs' + def __init__(self, name): + UniOpNode.__init__(self, name, '__abs__') + +class AddNode(BinOpNode): + """Returns A + B. Does not check input types.""" + nodeName = 'Add' + def __init__(self, name): + BinOpNode.__init__(self, name, '__add__') + +class SubtractNode(BinOpNode): + """Returns A - B. Does not check input types.""" + nodeName = 'Subtract' + def __init__(self, name): + BinOpNode.__init__(self, name, '__sub__') + +class MultiplyNode(BinOpNode): + """Returns A * B. Does not check input types.""" + nodeName = 'Multiply' + def __init__(self, name): + BinOpNode.__init__(self, name, '__mul__') + +class DivideNode(BinOpNode): + """Returns A / B. Does not check input types.""" + nodeName = 'Divide' + def __init__(self, name): + # try truediv first, followed by div + BinOpNode.__init__(self, name, ('__truediv__', '__div__')) + + diff --git a/pyqtgraph/flowchart/library/__init__.py b/pyqtgraph/flowchart/library/__init__.py new file mode 100644 index 00000000..1e44edff --- /dev/null +++ b/pyqtgraph/flowchart/library/__init__.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.pgcollections import OrderedDict +from pyqtgraph import importModules +import os, types +from pyqtgraph.debug import printExc +from ..Node import Node +import pyqtgraph.reload as reload + + +NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses +NODE_TREE = OrderedDict() ## categorized tree of Node subclasses + +def getNodeType(name): + try: + return NODE_LIST[name] + except KeyError: + raise Exception("No node type called '%s'" % name) + +def getNodeTree(): + return NODE_TREE + +def registerNodeType(cls, paths, override=False): + """ + Register a new node type. If the type's name is already in use, + an exception will be raised (unless override=True). + + Arguments: + cls - a subclass of Node (must have typ.nodeName) + paths - list of tuples specifying the location(s) this + type will appear in the library tree. + override - if True, overwrite any class having the same name + """ + if not isNodeClass(cls): + raise Exception("Object %s is not a Node subclass" % str(cls)) + + name = cls.nodeName + if not override and name in NODE_LIST: + raise Exception("Node type name '%s' is already registered." % name) + + NODE_LIST[name] = cls + for path in paths: + root = NODE_TREE + for n in path: + if n not in root: + root[n] = OrderedDict() + root = root[n] + root[name] = cls + + + +def isNodeClass(cls): + try: + if not issubclass(cls, Node): + return False + except: + return False + return hasattr(cls, 'nodeName') + +def loadLibrary(reloadLibs=False, libPath=None): + """Import all Node subclasses found within files in the library module.""" + + global NODE_LIST, NODE_TREE + #if libPath is None: + #libPath = os.path.dirname(os.path.abspath(__file__)) + + if reloadLibs: + reload.reloadAll(libPath) + + mods = importModules('', globals(), locals()) + #for f in frozenSupport.listdir(libPath): + #pathName, ext = os.path.splitext(f) + #if ext not in ('.py', '.pyc') or '__init__' in pathName or '__pycache__' in pathName: + #continue + #try: + ##print "importing from", f + #mod = __import__(pathName, globals(), locals()) + #except: + #printExc("Error loading flowchart library %s:" % pathName) + #continue + + for name, mod in mods.items(): + nodes = [] + for n in dir(mod): + o = getattr(mod, n) + if isNodeClass(o): + #print " ", str(o) + registerNodeType(o, [(name,)], override=reloadLibs) + #nodes.append((o.nodeName, o)) + #if len(nodes) > 0: + #NODE_TREE[name] = OrderedDict(nodes) + #NODE_LIST.extend(nodes) + #NODE_LIST = OrderedDict(NODE_LIST) + +def reloadLibrary(): + loadLibrary(reloadLibs=True) + +loadLibrary() +#NODE_LIST = [] +#for o in locals().values(): + #if type(o) is type(AddNode) and issubclass(o, Node) and o is not Node and hasattr(o, 'nodeName'): + #NODE_LIST.append((o.nodeName, o)) +#NODE_LIST.sort(lambda a,b: cmp(a[0], b[0])) +#NODE_LIST = OrderedDict(NODE_LIST) \ No newline at end of file diff --git a/pyqtgraph/flowchart/library/common.py b/pyqtgraph/flowchart/library/common.py new file mode 100644 index 00000000..65f8c1fd --- /dev/null +++ b/pyqtgraph/flowchart/library/common.py @@ -0,0 +1,148 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.widgets.SpinBox import SpinBox +#from pyqtgraph.SignalProxy import SignalProxy +from pyqtgraph.WidgetGroup import WidgetGroup +#from ColorMapper import ColorMapper +from ..Node import Node +import numpy as np +from pyqtgraph.widgets.ColorButton import ColorButton +try: + import metaarray + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + + +def generateUi(opts): + """Convenience function for generating common UI types""" + widget = QtGui.QWidget() + l = QtGui.QFormLayout() + l.setSpacing(0) + widget.setLayout(l) + ctrls = {} + row = 0 + for opt in opts: + if len(opt) == 2: + k, t = opt + o = {} + elif len(opt) == 3: + k, t, o = opt + else: + raise Exception("Widget specification must be (name, type) or (name, type, {opts})") + if t == 'intSpin': + w = QtGui.QSpinBox() + if 'max' in o: + w.setMaximum(o['max']) + if 'min' in o: + w.setMinimum(o['min']) + if 'value' in o: + w.setValue(o['value']) + elif t == 'doubleSpin': + w = QtGui.QDoubleSpinBox() + if 'max' in o: + w.setMaximum(o['max']) + if 'min' in o: + w.setMinimum(o['min']) + if 'value' in o: + w.setValue(o['value']) + elif t == 'spin': + w = SpinBox() + w.setOpts(**o) + elif t == 'check': + w = QtGui.QCheckBox() + if 'checked' in o: + w.setChecked(o['checked']) + elif t == 'combo': + w = QtGui.QComboBox() + for i in o['values']: + w.addItem(i) + #elif t == 'colormap': + #w = ColorMapper() + elif t == 'color': + w = ColorButton() + else: + raise Exception("Unknown widget type '%s'" % str(t)) + if 'tip' in o: + w.setToolTip(o['tip']) + w.setObjectName(k) + l.addRow(k, w) + if o.get('hidden', False): + w.hide() + label = l.labelForField(w) + label.hide() + + ctrls[k] = w + w.rowNum = row + row += 1 + group = WidgetGroup(widget) + return widget, group, ctrls + + +class CtrlNode(Node): + """Abstract class for nodes with auto-generated control UI""" + + sigStateChanged = QtCore.Signal(object) + + def __init__(self, name, ui=None, terminals=None): + if ui is None: + if hasattr(self, 'uiTemplate'): + ui = self.uiTemplate + else: + ui = [] + if terminals is None: + terminals = {'In': {'io': 'in'}, 'Out': {'io': 'out', 'bypass': 'In'}} + Node.__init__(self, name=name, terminals=terminals) + + self.ui, self.stateGroup, self.ctrls = generateUi(ui) + self.stateGroup.sigChanged.connect(self.changed) + + def ctrlWidget(self): + return self.ui + + def changed(self): + self.update() + self.sigStateChanged.emit(self) + + def process(self, In, display=True): + out = self.processData(In) + return {'Out': out} + + def saveState(self): + state = Node.saveState(self) + state['ctrl'] = self.stateGroup.state() + return state + + def restoreState(self, state): + Node.restoreState(self, state) + if self.stateGroup is not None: + self.stateGroup.setState(state.get('ctrl', {})) + + def hideRow(self, name): + w = self.ctrls[name] + l = self.ui.layout().labelForField(w) + w.hide() + l.hide() + + def showRow(self, name): + w = self.ctrls[name] + l = self.ui.layout().labelForField(w) + w.show() + l.show() + + + +def metaArrayWrapper(fn): + def newFn(self, data, *args, **kargs): + if HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): + d1 = fn(self, data.view(np.ndarray), *args, **kargs) + info = data.infoCopy() + if d1.shape != data.shape: + for i in range(data.ndim): + if 'values' in info[i]: + info[i]['values'] = info[i]['values'][:d1.shape[i]] + return metaarray.MetaArray(d1, info=info) + else: + return fn(self, data, *args, **kargs) + return newFn + diff --git a/pyqtgraph/flowchart/library/functions.py b/pyqtgraph/flowchart/library/functions.py new file mode 100644 index 00000000..0476e02f --- /dev/null +++ b/pyqtgraph/flowchart/library/functions.py @@ -0,0 +1,336 @@ +import scipy +import numpy as np +from pyqtgraph.metaarray import MetaArray + +def downsample(data, n, axis=0, xvals='subsample'): + """Downsample by averaging points together across axis. + If multiple axes are specified, runs once per axis. + If a metaArray is given, then the axis values can be either subsampled + or downsampled to match. + """ + ma = None + if (hasattr(data, 'implements') and data.implements('MetaArray')): + ma = data + data = data.view(np.ndarray) + + + if hasattr(axis, '__len__'): + if not hasattr(n, '__len__'): + n = [n]*len(axis) + for i in range(len(axis)): + data = downsample(data, n[i], axis[i]) + return data + + nPts = int(data.shape[axis] / n) + s = list(data.shape) + s[axis] = nPts + s.insert(axis+1, n) + sl = [slice(None)] * data.ndim + sl[axis] = slice(0, nPts*n) + d1 = data[tuple(sl)] + #print d1.shape, s + d1.shape = tuple(s) + d2 = d1.mean(axis+1) + + if ma is None: + return d2 + else: + info = ma.infoCopy() + if 'values' in info[axis]: + if xvals == 'subsample': + info[axis]['values'] = info[axis]['values'][::n][:nPts] + elif xvals == 'downsample': + info[axis]['values'] = downsample(info[axis]['values'], n) + return MetaArray(d2, info=info) + + +def applyFilter(data, b, a, padding=100, bidir=True): + """Apply a linear filter with coefficients a, b. Optionally pad the data before filtering + and/or run the filter in both directions.""" + d1 = data.view(np.ndarray) + + if padding > 0: + d1 = np.hstack([d1[:padding], d1, d1[-padding:]]) + + if bidir: + d1 = scipy.signal.lfilter(b, a, scipy.signal.lfilter(b, a, d1)[::-1])[::-1] + else: + d1 = scipy.signal.lfilter(b, a, d1) + + if padding > 0: + d1 = d1[padding:-padding] + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return MetaArray(d1, info=data.infoCopy()) + else: + return d1 + +def besselFilter(data, cutoff, order=1, dt=None, btype='low', bidir=True): + """return data passed through bessel filter""" + if dt is None: + try: + tvals = data.xvals('Time') + dt = (tvals[-1]-tvals[0]) / (len(tvals)-1) + except: + dt = 1.0 + + b,a = scipy.signal.bessel(order, cutoff * dt, btype=btype) + + return applyFilter(data, b, a, bidir=bidir) + #base = data.mean() + #d1 = scipy.signal.lfilter(b, a, data.view(ndarray)-base) + base + #if (hasattr(data, 'implements') and data.implements('MetaArray')): + #return MetaArray(d1, info=data.infoCopy()) + #return d1 + +def butterworthFilter(data, wPass, wStop=None, gPass=2.0, gStop=20.0, order=1, dt=None, btype='low', bidir=True): + """return data passed through bessel filter""" + if dt is None: + try: + tvals = data.xvals('Time') + dt = (tvals[-1]-tvals[0]) / (len(tvals)-1) + except: + dt = 1.0 + + if wStop is None: + wStop = wPass * 2.0 + ord, Wn = scipy.signal.buttord(wPass*dt*2., wStop*dt*2., gPass, gStop) + #print "butterworth ord %f Wn %f c %f sc %f" % (ord, Wn, cutoff, stopCutoff) + b,a = scipy.signal.butter(ord, Wn, btype=btype) + + return applyFilter(data, b, a, bidir=bidir) + + +def rollingSum(data, n): + d1 = data.copy() + d1[1:] += d1[:-1] # integrate + d2 = np.empty(len(d1) - n + 1, dtype=data.dtype) + d2[0] = d1[n-1] # copy first point + d2[1:] = d1[n:] - d1[:-n] # subtract + return d2 + + +def mode(data, bins=None): + """Returns location max value from histogram.""" + if bins is None: + bins = int(len(data)/10.) + if bins < 2: + bins = 2 + y, x = np.histogram(data, bins=bins) + ind = np.argmax(y) + mode = 0.5 * (x[ind] + x[ind+1]) + return mode + +def modeFilter(data, window=500, step=None, bins=None): + """Filter based on histogram-based mode function""" + d1 = data.view(np.ndarray) + vals = [] + l2 = int(window/2.) + if step is None: + step = l2 + i = 0 + while True: + if i > len(data)-step: + break + vals.append(mode(d1[i:i+window], bins)) + i += step + + chunks = [np.linspace(vals[0], vals[0], l2)] + for i in range(len(vals)-1): + chunks.append(np.linspace(vals[i], vals[i+1], step)) + remain = len(data) - step*(len(vals)-1) - l2 + chunks.append(np.linspace(vals[-1], vals[-1], remain)) + d2 = np.hstack(chunks) + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return MetaArray(d2, info=data.infoCopy()) + return d2 + +def denoise(data, radius=2, threshold=4): + """Very simple noise removal function. Compares a point to surrounding points, + replaces with nearby values if the difference is too large.""" + + + r2 = radius * 2 + d1 = data.view(np.ndarray) + d2 = d1[radius:] - d1[:-radius] #a derivative + #d3 = data[r2:] - data[:-r2] + #d4 = d2 - d3 + stdev = d2.std() + #print "denoise: stdev of derivative:", stdev + mask1 = d2 > stdev*threshold #where derivative is large and positive + mask2 = d2 < -stdev*threshold #where derivative is large and negative + maskpos = mask1[:-radius] * mask2[radius:] #both need to be true + maskneg = mask1[radius:] * mask2[:-radius] + mask = maskpos + maskneg + d5 = np.where(mask, d1[:-r2], d1[radius:-radius]) #where both are true replace the value with the value from 2 points before + d6 = np.empty(d1.shape, dtype=d1.dtype) #add points back to the ends + d6[radius:-radius] = d5 + d6[:radius] = d1[:radius] + d6[-radius:] = d1[-radius:] + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return MetaArray(d6, info=data.infoCopy()) + return d6 + +def adaptiveDetrend(data, x=None, threshold=3.0): + """Return the signal with baseline removed. Discards outliers from baseline measurement.""" + if x is None: + x = data.xvals(0) + + d = data.view(np.ndarray) + + d2 = scipy.signal.detrend(d) + + stdev = d2.std() + mask = abs(d2) < stdev*threshold + #d3 = where(mask, 0, d2) + #d4 = d2 - lowPass(d3, cutoffs[1], dt=dt) + + lr = stats.linregress(x[mask], d[mask]) + base = lr[1] + lr[0]*x + d4 = d - base + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return MetaArray(d4, info=data.infoCopy()) + return d4 + + +def histogramDetrend(data, window=500, bins=50, threshold=3.0, offsetOnly=False): + """Linear detrend. Works by finding the most common value at the beginning and end of a trace, excluding outliers. + If offsetOnly is True, then only the offset from the beginning of the trace is subtracted. + """ + + d1 = data.view(np.ndarray) + d2 = [d1[:window], d1[-window:]] + v = [0, 0] + for i in [0, 1]: + d3 = d2[i] + stdev = d3.std() + mask = abs(d3-np.median(d3)) < stdev*threshold + d4 = d3[mask] + y, x = np.histogram(d4, bins=bins) + ind = np.argmax(y) + v[i] = 0.5 * (x[ind] + x[ind+1]) + + if offsetOnly: + d3 = data.view(np.ndarray) - v[0] + else: + base = np.linspace(v[0], v[1], len(data)) + d3 = data.view(np.ndarray) - base + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return MetaArray(d3, info=data.infoCopy()) + return d3 + +def concatenateColumns(data): + """Returns a single record array with columns taken from the elements in data. + data should be a list of elements, which can be either record arrays or tuples (name, type, data) + """ + + ## first determine dtype + dtype = [] + names = set() + maxLen = 0 + for element in data: + if isinstance(element, np.ndarray): + ## use existing columns + for i in range(len(element.dtype)): + name = element.dtype.names[i] + dtype.append((name, element.dtype[i])) + maxLen = max(maxLen, len(element)) + else: + name, type, d = element + if type is None: + type = suggestDType(d) + dtype.append((name, type)) + if isinstance(d, list) or isinstance(d, np.ndarray): + maxLen = max(maxLen, len(d)) + if name in names: + raise Exception('Name "%s" repeated' % name) + names.add(name) + + + + ## create empty array + out = np.empty(maxLen, dtype) + + ## fill columns + for element in data: + if isinstance(element, np.ndarray): + for i in range(len(element.dtype)): + name = element.dtype.names[i] + try: + out[name] = element[name] + except: + print("Column:", name) + print("Input shape:", element.shape, element.dtype) + print("Output shape:", out.shape, out.dtype) + raise + else: + name, type, d = element + out[name] = d + + return out + +def suggestDType(x): + """Return a suitable dtype for x""" + if isinstance(x, list) or isinstance(x, tuple): + if len(x) == 0: + raise Exception('can not determine dtype for empty list') + x = x[0] + + if hasattr(x, 'dtype'): + return x.dtype + elif isinstance(x, float): + return float + elif isinstance(x, int): + return int + #elif isinstance(x, basestring): ## don't try to guess correct string length; use object instead. + #return ' len(ft)/2.: + break + mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5 + for j in range(ind1, ind2+1): + phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts. + re = mag * np.cos(phase) + im = mag * np.sin(phase) + ft[j] = re + im*1j + ft[len(ft)-j] = re - im*1j + + data2 = np.fft.ifft(ft).real + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return metaarray.MetaArray(data2, info=data.infoCopy()) + else: + return data2 + + + \ No newline at end of file diff --git a/pyqtgraph/frozenSupport.py b/pyqtgraph/frozenSupport.py new file mode 100644 index 00000000..c42a12e1 --- /dev/null +++ b/pyqtgraph/frozenSupport.py @@ -0,0 +1,52 @@ +## Definitions helpful in frozen environments (eg py2exe) +import os, sys, zipfile + +def listdir(path): + """Replacement for os.listdir that works in frozen environments.""" + if not hasattr(sys, 'frozen'): + return os.listdir(path) + + (zipPath, archivePath) = splitZip(path) + if archivePath is None: + return os.listdir(path) + + with zipfile.ZipFile(zipPath, "r") as zipobj: + contents = zipobj.namelist() + results = set() + for name in contents: + # components in zip archive paths are always separated by forward slash + if name.startswith(archivePath) and len(name) > len(archivePath): + name = name[len(archivePath):].split('/')[0] + results.add(name) + return list(results) + +def isdir(path): + """Replacement for os.path.isdir that works in frozen environments.""" + if not hasattr(sys, 'frozen'): + return os.path.isdir(path) + + (zipPath, archivePath) = splitZip(path) + if archivePath is None: + return os.path.isdir(path) + with zipfile.ZipFile(zipPath, "r") as zipobj: + contents = zipobj.namelist() + archivePath = archivePath.rstrip('/') + '/' ## make sure there's exactly one '/' at the end + for c in contents: + if c.startswith(archivePath): + return True + return False + + +def splitZip(path): + """Splits a path containing a zip file into (zipfile, subpath). + If there is no zip file, returns (path, None)""" + components = os.path.normpath(path).split(os.sep) + for index, component in enumerate(components): + if component.endswith('.zip'): + zipPath = os.sep.join(components[0:index+1]) + archivePath = ''.join([x+'/' for x in components[index+1:]]) + return (zipPath, archivePath) + else: + return (path, None) + + \ No newline at end of file diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py new file mode 100644 index 00000000..337dfb67 --- /dev/null +++ b/pyqtgraph/functions.py @@ -0,0 +1,2023 @@ +# -*- coding: utf-8 -*- +""" +functions.py - Miscellaneous functions with no other home +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from __future__ import division +from .python2_3 import asUnicode +Colors = { + 'b': (0,0,255,255), + 'g': (0,255,0,255), + 'r': (255,0,0,255), + 'c': (0,255,255,255), + 'm': (255,0,255,255), + 'y': (255,255,0,255), + 'k': (0,0,0,255), + 'w': (255,255,255,255), +} + +SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY') +SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY' + + + +from .Qt import QtGui, QtCore, USE_PYSIDE +import pyqtgraph as pg +import numpy as np +import decimal, re +import ctypes +import sys, struct + +try: + import scipy.ndimage + HAVE_SCIPY = True + if pg.getConfigOption('useWeave'): + try: + import scipy.weave + except ImportError: + pg.setConfigOptions(useWeave=False) +except ImportError: + HAVE_SCIPY = False + +from . import debug + +def siScale(x, minVal=1e-25, allowUnicode=True): + """ + Return the recommended scale factor and SI prefix string for x. + + Example:: + + siScale(0.0001) # returns (1e6, 'μ') + # This indicates that the number 0.0001 is best represented as 0.0001 * 1e6 = 100 μUnits + """ + + if isinstance(x, decimal.Decimal): + x = float(x) + + try: + if np.isnan(x) or np.isinf(x): + return(1, '') + except: + print(x, type(x)) + raise + if abs(x) < minVal: + m = 0 + x = 0 + else: + m = int(np.clip(np.floor(np.log(abs(x))/np.log(1000)), -9.0, 9.0)) + + if m == 0: + pref = '' + elif m < -8 or m > 8: + pref = 'e%d' % (m*3) + else: + if allowUnicode: + pref = SI_PREFIXES[m+8] + else: + pref = SI_PREFIXES_ASCII[m+8] + p = .001**m + + return (p, pref) + +def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, allowUnicode=True): + """ + Return the number x formatted in engineering notation with SI prefix. + + Example:: + siFormat(0.0001, suffix='V') # returns "100 μV" + """ + + if space is True: + space = ' ' + if space is False: + space = '' + + + (p, pref) = siScale(x, minVal, allowUnicode) + if not (len(pref) > 0 and pref[0] == 'e'): + pref = space + pref + + if error is None: + fmt = "%." + str(precision) + "g%s%s" + return fmt % (x*p, pref, suffix) + else: + if allowUnicode: + plusminus = space + asUnicode("±") + space + else: + plusminus = " +/- " + fmt = "%." + str(precision) + "g%s%s%s%s" + return fmt % (x*p, pref, suffix, plusminus, siFormat(error, precision=precision, suffix=suffix, space=space, minVal=minVal)) + +def siEval(s): + """ + Convert a value written in SI notation to its equivalent prefixless value + + Example:: + + siEval("100 μV") # returns 0.0001 + """ + + s = asUnicode(s) + m = re.match(r'(-?((\d+(\.\d*)?)|(\.\d+))([eE]-?\d+)?)\s*([u' + SI_PREFIXES + r']?).*$', s) + if m is None: + raise Exception("Can't convert string '%s' to number." % s) + v = float(m.groups()[0]) + p = m.groups()[6] + #if p not in SI_PREFIXES: + #raise Exception("Can't convert string '%s' to number--unknown prefix." % s) + if p == '': + n = 0 + elif p == 'u': + n = -2 + else: + n = SI_PREFIXES.index(p) - 8 + return v * 1000**n + + +class Color(QtGui.QColor): + def __init__(self, *args): + QtGui.QColor.__init__(self, mkColor(*args)) + + def glColor(self): + """Return (r,g,b,a) normalized for use in opengl""" + return (self.red()/255., self.green()/255., self.blue()/255., self.alpha()/255.) + + def __getitem__(self, ind): + return (self.red, self.green, self.blue, self.alpha)[ind]() + + +def mkColor(*args): + """ + Convenience function for constructing QColor from a variety of argument types. Accepted arguments are: + + ================ ================================================ + 'c' one of: r, g, b, c, m, y, k, w + R, G, B, [A] integers 0-255 + (R, G, B, [A]) tuple of integers 0-255 + float greyscale, 0.0-1.0 + int see :func:`intColor() ` + (int, hues) see :func:`intColor() ` + "RGB" hexadecimal strings; may begin with '#' + "RGBA" + "RRGGBB" + "RRGGBBAA" + QColor QColor instance; makes a copy. + ================ ================================================ + """ + err = 'Not sure how to make a color from "%s"' % str(args) + if len(args) == 1: + if isinstance(args[0], QtGui.QColor): + return QtGui.QColor(args[0]) + elif isinstance(args[0], float): + r = g = b = int(args[0] * 255) + a = 255 + elif isinstance(args[0], basestring): + c = args[0] + if c[0] == '#': + c = c[1:] + if len(c) == 1: + (r, g, b, a) = Colors[c] + if len(c) == 3: + r = int(c[0]*2, 16) + g = int(c[1]*2, 16) + b = int(c[2]*2, 16) + a = 255 + elif len(c) == 4: + r = int(c[0]*2, 16) + g = int(c[1]*2, 16) + b = int(c[2]*2, 16) + a = int(c[3]*2, 16) + elif len(c) == 6: + r = int(c[0:2], 16) + g = int(c[2:4], 16) + b = int(c[4:6], 16) + a = 255 + elif len(c) == 8: + r = int(c[0:2], 16) + g = int(c[2:4], 16) + b = int(c[4:6], 16) + a = int(c[6:8], 16) + elif hasattr(args[0], '__len__'): + if len(args[0]) == 3: + (r, g, b) = args[0] + a = 255 + elif len(args[0]) == 4: + (r, g, b, a) = args[0] + elif len(args[0]) == 2: + return intColor(*args[0]) + else: + raise Exception(err) + elif type(args[0]) == int: + return intColor(args[0]) + else: + raise Exception(err) + elif len(args) == 3: + (r, g, b) = args + a = 255 + elif len(args) == 4: + (r, g, b, a) = args + else: + raise Exception(err) + + args = [r,g,b,a] + args = [0 if np.isnan(a) or np.isinf(a) else a for a in args] + args = list(map(int, args)) + return QtGui.QColor(*args) + + +def mkBrush(*args, **kwds): + """ + | Convenience function for constructing Brush. + | This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() ` + | Calling mkBrush(None) returns an invisible brush. + """ + if 'color' in kwds: + color = kwds['color'] + elif len(args) == 1: + arg = args[0] + if arg is None: + return QtGui.QBrush(QtCore.Qt.NoBrush) + elif isinstance(arg, QtGui.QBrush): + return QtGui.QBrush(arg) + else: + color = arg + elif len(args) > 1: + color = args + return QtGui.QBrush(mkColor(color)) + +def mkPen(*args, **kargs): + """ + Convenience function for constructing QPen. + + Examples:: + + mkPen(color) + mkPen(color, width=2) + mkPen(cosmetic=False, width=4.5, color='r') + mkPen({'color': "FF0", width: 2}) + mkPen(None) # (no pen) + + In these examples, *color* may be replaced with any arguments accepted by :func:`mkColor() ` """ + + color = kargs.get('color', None) + width = kargs.get('width', 1) + style = kargs.get('style', None) + dash = kargs.get('dash', None) + cosmetic = kargs.get('cosmetic', True) + hsv = kargs.get('hsv', None) + + if len(args) == 1: + arg = args[0] + if isinstance(arg, dict): + return mkPen(**arg) + if isinstance(arg, QtGui.QPen): + return QtGui.QPen(arg) ## return a copy of this pen + elif arg is None: + style = QtCore.Qt.NoPen + else: + color = arg + if len(args) > 1: + color = args + + if color is None: + color = mkColor(200, 200, 200) + if hsv is not None: + color = hsvColor(*hsv) + else: + color = mkColor(color) + + pen = QtGui.QPen(QtGui.QBrush(color), width) + pen.setCosmetic(cosmetic) + if style is not None: + pen.setStyle(style) + if dash is not None: + pen.setDashPattern(dash) + return pen + +def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0): + """Generate a QColor from HSVa values. (all arguments are float 0.0-1.0)""" + c = QtGui.QColor() + c.setHsvF(hue, sat, val, alpha) + return c + + +def colorTuple(c): + """Return a tuple (R,G,B,A) from a QColor""" + return (c.red(), c.green(), c.blue(), c.alpha()) + +def colorStr(c): + """Generate a hex string code from a QColor""" + return ('%02x'*4) % colorTuple(c) + +def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255, **kargs): + """ + Creates a QColor from a single index. Useful for stepping through a predefined list of colors. + + The argument *index* determines which color from the set will be returned. All other arguments determine what the set of predefined colors will be + + Colors are chosen by cycling across hues while varying the value (brightness). + By default, this selects from a list of 9 hues.""" + hues = int(hues) + values = int(values) + ind = int(index) % (hues * values) + indh = ind % hues + indv = ind / hues + if values > 1: + v = minValue + indv * ((maxValue-minValue) / (values-1)) + else: + v = maxValue + h = minHue + (indh * (maxHue-minHue)) / hues + + c = QtGui.QColor() + c.setHsv(h, sat, v) + c.setAlpha(alpha) + return c + +def glColor(*args, **kargs): + """ + Convert a color to OpenGL color format (r,g,b,a) floats 0.0-1.0 + Accepts same arguments as :func:`mkColor `. + """ + c = mkColor(*args, **kargs) + return (c.red()/255., c.green()/255., c.blue()/255., c.alpha()/255.) + + + +def makeArrowPath(headLen=20, tipAngle=20, tailLen=20, tailWidth=3, baseAngle=0): + """ + Construct a path outlining an arrow with the given dimensions. + The arrow points in the -x direction with tip positioned at 0,0. + If *tipAngle* is supplied (in degrees), it overrides *headWidth*. + If *tailLen* is None, no tail will be drawn. + """ + headWidth = headLen * np.tan(tipAngle * 0.5 * np.pi/180.) + path = QtGui.QPainterPath() + path.moveTo(0,0) + path.lineTo(headLen, -headWidth) + if tailLen is None: + innerY = headLen - headWidth * np.tan(baseAngle*np.pi/180.) + path.lineTo(innerY, 0) + else: + tailWidth *= 0.5 + innerY = headLen - (headWidth-tailWidth) * np.tan(baseAngle*np.pi/180.) + path.lineTo(innerY, -tailWidth) + path.lineTo(headLen + tailLen, -tailWidth) + path.lineTo(headLen + tailLen, tailWidth) + path.lineTo(innerY, tailWidth) + path.lineTo(headLen, headWidth) + path.lineTo(0,0) + return path + + + +def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs): + """ + Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data. + + The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates (see the scipy documentation for more information about this). + + For a graphical interface to this function, see :func:`ROI.getArrayRegion ` + + ============== ==================================================================================================== + Arguments: + *data* (ndarray) the original dataset + *shape* the shape of the slice to take (Note the return value may have more dimensions than len(shape)) + *origin* the location in the original dataset that will become the origin of the sliced data. + *vectors* list of unit vectors which point in the direction of the slice axes. Each vector must have the same + length as *axes*. If the vectors are not unit length, the result will be scaled relative to the + original data. If the vectors are not orthogonal, the result will be sheared relative to the + original data. + *axes* The axes in the original dataset which correspond to the slice *vectors* + *order* The order of spline interpolation. Default is 1 (linear). See scipy.ndimage.map_coordinates + for more information. + *returnCoords* If True, return a tuple (result, coords) where coords is the array of coordinates used to select + values from the original dataset. + *All extra keyword arguments are passed to scipy.ndimage.map_coordinates.* + -------------------------------------------------------------------------------------------------------------------- + ============== ==================================================================================================== + + Note the following must be true: + + | len(shape) == len(vectors) + | len(origin) == len(axes) == len(vectors[i]) + + Example: start with a 4D fMRI data set, take a diagonal-planar slice out of the last 3 axes + + * data = array with dims (time, x, y, z) = (100, 40, 40, 40) + * The plane to pull out is perpendicular to the vector (x,y,z) = (1,1,1) + * The origin of the slice will be at (x,y,z) = (40, 0, 0) + * We will slice a 20x20 plane from each timepoint, giving a final shape (100, 20, 20) + + The call for this example would look like:: + + affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3)) + + """ + if not HAVE_SCIPY: + raise Exception("This function requires the scipy library, but it does not appear to be importable.") + + # sanity check + if len(shape) != len(vectors): + raise Exception("shape and vectors must have same length.") + if len(origin) != len(axes): + raise Exception("origin and axes must have same length.") + for v in vectors: + if len(v) != len(axes): + raise Exception("each vector must be same length as axes.") + + shape = list(map(np.ceil, shape)) + + ## transpose data so slice axes come first + trAx = list(range(data.ndim)) + for x in axes: + trAx.remove(x) + tr1 = tuple(axes) + tuple(trAx) + data = data.transpose(tr1) + #print "tr1:", tr1 + ## dims are now [(slice axes), (other axes)] + + + ## make sure vectors are arrays + if not isinstance(vectors, np.ndarray): + vectors = np.array(vectors) + if not isinstance(origin, np.ndarray): + origin = np.array(origin) + origin.shape = (len(axes),) + (1,)*len(shape) + + ## Build array of sample locations. + grid = np.mgrid[tuple([slice(0,x) for x in shape])] ## mesh grid of indexes + #print shape, grid.shape + x = (grid[np.newaxis,...] * vectors.transpose()[(Ellipsis,) + (np.newaxis,)*len(shape)]).sum(axis=1) ## magic + x += origin + #print "X values:" + #print x + ## iterate manually over unused axes since map_coordinates won't do it for us + extraShape = data.shape[len(axes):] + output = np.empty(tuple(shape) + extraShape, dtype=data.dtype) + for inds in np.ndindex(*extraShape): + ind = (Ellipsis,) + inds + #print data[ind].shape, x.shape, output[ind].shape, output.shape + output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs) + + tr = list(range(output.ndim)) + trb = [] + for i in range(min(axes)): + ind = tr1.index(i) + (len(shape)-len(axes)) + tr.remove(ind) + trb.append(ind) + tr2 = tuple(trb+tr) + + ## Untranspose array before returning + output = output.transpose(tr2) + if returnCoords: + return (output, x) + else: + return output + +def transformToArray(tr): + """ + Given a QTransform, return a 3x3 numpy array. + Given a QMatrix4x4, return a 4x4 numpy array. + + Example: map an array of x,y coordinates through a transform:: + + ## coordinates to map are (1,5), (2,6), (3,7), and (4,8) + coords = np.array([[1,2,3,4], [5,6,7,8], [1,1,1,1]]) # the extra '1' coordinate is needed for translation to work + + ## Make an example transform + tr = QtGui.QTransform() + tr.translate(3,4) + tr.scale(2, 0.1) + + ## convert to array + m = pg.transformToArray()[:2] # ignore the perspective portion of the transformation + + ## map coordinates through transform + mapped = np.dot(m, coords) + """ + #return np.array([[tr.m11(), tr.m12(), tr.m13()],[tr.m21(), tr.m22(), tr.m23()],[tr.m31(), tr.m32(), tr.m33()]]) + ## The order of elements given by the method names m11..m33 is misleading-- + ## It is most common for x,y translation to occupy the positions 1,3 and 2,3 in + ## a transformation matrix. However, with QTransform these values appear at m31 and m32. + ## So the correct interpretation is transposed: + if isinstance(tr, QtGui.QTransform): + return np.array([[tr.m11(), tr.m21(), tr.m31()], [tr.m12(), tr.m22(), tr.m32()], [tr.m13(), tr.m23(), tr.m33()]]) + elif isinstance(tr, QtGui.QMatrix4x4): + return np.array(tr.copyDataTo()).reshape(4,4) + else: + raise Exception("Transform argument must be either QTransform or QMatrix4x4.") + +def transformCoordinates(tr, coords, transpose=False): + """ + Map a set of 2D or 3D coordinates through a QTransform or QMatrix4x4. + The shape of coords must be (2,...) or (3,...) + The mapping will _ignore_ any perspective transformations. + + For coordinate arrays with ndim=2, this is basically equivalent to matrix multiplication. + Most arrays, however, prefer to put the coordinate axis at the end (eg. shape=(...,3)). To + allow this, use transpose=True. + + """ + + if transpose: + ## move last axis to beginning. This transposition will be reversed before returning the mapped coordinates. + coords = coords.transpose((coords.ndim-1,) + tuple(range(0,coords.ndim-1))) + + nd = coords.shape[0] + if isinstance(tr, np.ndarray): + m = tr + else: + m = transformToArray(tr) + m = m[:m.shape[0]-1] # remove perspective + + ## If coords are 3D and tr is 2D, assume no change for Z axis + if m.shape == (2,3) and nd == 3: + m2 = np.zeros((3,4)) + m2[:2, :2] = m[:2,:2] + m2[:2, 3] = m[:2,2] + m2[2,2] = 1 + m = m2 + + ## if coords are 2D and tr is 3D, ignore Z axis + if m.shape == (3,4) and nd == 2: + m2 = np.empty((2,3)) + m2[:,:2] = m[:2,:2] + m2[:,2] = m[:2,3] + m = m2 + + ## reshape tr and coords to prepare for multiplication + m = m.reshape(m.shape + (1,)*(coords.ndim-1)) + coords = coords[np.newaxis, ...] + + # separate scale/rotate and translation + translate = m[:,-1] + m = m[:, :-1] + + ## map coordinates and return + mapped = (m*coords).sum(axis=1) ## apply scale/rotate + mapped += translate + + if transpose: + ## move first axis to end. + mapped = mapped.transpose(tuple(range(1,mapped.ndim)) + (0,)) + return mapped + + + + +def solve3DTransform(points1, points2): + """ + Find a 3D transformation matrix that maps points1 onto points2. + Points must be specified as a list of 4 Vectors. + """ + if not HAVE_SCIPY: + raise Exception("This function depends on the scipy library, but it does not appear to be importable.") + A = np.array([[points1[i].x(), points1[i].y(), points1[i].z(), 1] for i in range(4)]) + B = np.array([[points2[i].x(), points2[i].y(), points2[i].z(), 1] for i in range(4)]) + + ## solve 3 sets of linear equations to determine transformation matrix elements + matrix = np.zeros((4,4)) + for i in range(3): + matrix[i] = scipy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix + + return matrix + +def solveBilinearTransform(points1, points2): + """ + Find a bilinear transformation matrix (2x4) that maps points1 onto points2. + Points must be specified as a list of 4 Vector, Point, QPointF, etc. + + To use this matrix to map a point [x,y]:: + + mapped = np.dot(matrix, [x*y, x, y, 1]) + """ + if not HAVE_SCIPY: + raise Exception("This function depends on the scipy library, but it does not appear to be importable.") + ## A is 4 rows (points) x 4 columns (xy, x, y, 1) + ## B is 4 rows (points) x 2 columns (x, y) + A = np.array([[points1[i].x()*points1[i].y(), points1[i].x(), points1[i].y(), 1] for i in range(4)]) + B = np.array([[points2[i].x(), points2[i].y()] for i in range(4)]) + + ## solve 2 sets of linear equations to determine transformation matrix elements + matrix = np.zeros((2,4)) + for i in range(2): + matrix[i] = scipy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix + + return matrix + +def rescaleData(data, scale, offset, dtype=None): + """Return data rescaled and optionally cast to a new dtype:: + + data => (data-offset) * scale + + Uses scipy.weave (if available) to improve performance. + """ + if dtype is None: + dtype = data.dtype + else: + dtype = np.dtype(dtype) + + try: + if not pg.getConfigOption('useWeave'): + raise Exception('Weave is disabled; falling back to slower version.') + + ## require native dtype when using weave + if not data.dtype.isnative: + data = data.astype(data.dtype.newbyteorder('=')) + if not dtype.isnative: + weaveDtype = dtype.newbyteorder('=') + else: + weaveDtype = dtype + + newData = np.empty((data.size,), dtype=weaveDtype) + flat = np.ascontiguousarray(data).reshape(data.size) + size = data.size + + code = """ + double sc = (double)scale; + double off = (double)offset; + for( int i=0; i0 and max->*scale*:: + + rescaled = (clip(data, min, max) - min) * (*scale* / (max - min)) + + It is also possible to use a 2D (N,2) array of values for levels. In this case, + it is assumed that each pair of min,max values in the levels array should be + applied to a different subset of the input data (for example, the input data may + already have RGB values and the levels are used to independently scale each + channel). The use of this feature requires that levels.shape[0] == data.shape[-1]. + scale The maximum value to which data will be rescaled before being passed through the + lookup table (or returned if there is no lookup table). By default this will + be set to the length of the lookup table, or 256 is no lookup table is provided. + For OpenGL color specifications (as in GLColor4f) use scale=1.0 + lut Optional lookup table (array with dtype=ubyte). + Values in data will be converted to color by indexing directly from lut. + The output data shape will be input.shape + lut.shape[1:]. + + Note: the output of makeARGB will have the same dtype as the lookup table, so + for conversion to QImage, the dtype must be ubyte. + + Lookup tables can be built using GradientWidget. + useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures). + The default is False, which returns in ARGB order for use with QImage + (Note that 'ARGB' is a term used by the Qt documentation; the _actual_ order + is BGRA). + ============ ================================================================================== + """ + prof = debug.Profiler('functions.makeARGB', disabled=True) + + if lut is not None and not isinstance(lut, np.ndarray): + lut = np.array(lut) + if levels is not None and not isinstance(levels, np.ndarray): + levels = np.array(levels) + + ## sanity checks + #if data.ndim == 3: + #if data.shape[2] not in (3,4): + #raise Exception("data.shape[2] must be 3 or 4") + ##if lut is not None: + ##raise Exception("can not use lookup table with 3D data") + #elif data.ndim != 2: + #raise Exception("data must be 2D or 3D") + + #if lut is not None: + ##if lut.ndim == 2: + ##if lut.shape[1] : + ##raise Exception("lut.shape[1] must be 3 or 4") + ##elif lut.ndim != 1: + ##raise Exception("lut must be 1D or 2D") + #if lut.dtype != np.ubyte: + #raise Exception('lookup table must have dtype=ubyte (got %s instead)' % str(lut.dtype)) + + + if levels is not None: + if levels.ndim == 1: + if len(levels) != 2: + raise Exception('levels argument must have length 2') + elif levels.ndim == 2: + if lut is not None and lut.ndim > 1: + raise Exception('Cannot make ARGB data when bot levels and lut have ndim > 2') + if levels.shape != (data.shape[-1], 2): + raise Exception('levels must have shape (data.shape[-1], 2)') + else: + print(levels) + raise Exception("levels argument must be 1D or 2D.") + #levels = np.array(levels) + #if levels.shape == (2,): + #pass + #elif levels.shape in [(3,2), (4,2)]: + #if data.ndim == 3: + #raise Exception("Can not use 2D levels with 3D data.") + #if lut is not None: + #raise Exception('Can not use 2D levels and lookup table together.') + #else: + #raise Exception("Levels must have shape (2,) or (3,2) or (4,2)") + + prof.mark('1') + + if scale is None: + if lut is not None: + scale = lut.shape[0] + else: + scale = 255. + + ## Apply levels if given + if levels is not None: + + if isinstance(levels, np.ndarray) and levels.ndim == 2: + ## we are going to rescale each channel independently + if levels.shape[0] != data.shape[-1]: + raise Exception("When rescaling multi-channel data, there must be the same number of levels as channels (data.shape[-1] == levels.shape[0])") + newData = np.empty(data.shape, dtype=int) + for i in range(data.shape[-1]): + minVal, maxVal = levels[i] + if minVal == maxVal: + maxVal += 1e-16 + newData[...,i] = rescaleData(data[...,i], scale/(maxVal-minVal), minVal, dtype=int) + data = newData + else: + minVal, maxVal = levels + if minVal == maxVal: + maxVal += 1e-16 + data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int) + prof.mark('2') + + + ## apply LUT if given + if lut is not None: + data = applyLookupTable(data, lut) + else: + if data.dtype is not np.ubyte: + data = np.clip(data, 0, 255).astype(np.ubyte) + prof.mark('3') + + + ## copy data into ARGB ordered array + imgData = np.empty(data.shape[:2]+(4,), dtype=np.ubyte) + if data.ndim == 2: + data = data[..., np.newaxis] + + prof.mark('4') + + if useRGBA: + order = [0,1,2,3] ## array comes out RGBA + else: + order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image. + + if data.shape[2] == 1: + for i in range(3): + imgData[..., order[i]] = data[..., 0] + else: + for i in range(0, data.shape[2]): + imgData[..., order[i]] = data[..., i] + + prof.mark('5') + + if data.shape[2] == 4: + alpha = True + else: + alpha = False + imgData[..., 3] = 255 + + prof.mark('6') + + prof.finish() + return imgData, alpha + + +def makeQImage(imgData, alpha=None, copy=True, transpose=True): + """ + Turn an ARGB array into QImage. + By default, the data is copied; changes to the array will not + be reflected in the image. The image will be given a 'data' attribute + pointing to the array which shares its data to prevent python + freeing that memory while the image is in use. + + =========== =================================================================== + Arguments: + imgData Array of data to convert. Must have shape (width, height, 3 or 4) + and dtype=ubyte. The order of values in the 3rd axis must be + (b, g, r, a). + alpha If True, the QImage returned will have format ARGB32. If False, + the format will be RGB32. By default, _alpha_ is True if + array.shape[2] == 4. + copy If True, the data is copied before converting to QImage. + If False, the new QImage points directly to the data in the array. + Note that the array must be contiguous for this to work + (see numpy.ascontiguousarray). + transpose If True (the default), the array x/y axes are transposed before + creating the image. Note that Qt expects the axes to be in + (height, width) order whereas pyqtgraph usually prefers the + opposite. + =========== =================================================================== + """ + ## create QImage from buffer + prof = debug.Profiler('functions.makeQImage', disabled=True) + + ## If we didn't explicitly specify alpha, check the array shape. + if alpha is None: + alpha = (imgData.shape[2] == 4) + + copied = False + if imgData.shape[2] == 3: ## need to make alpha channel (even if alpha==False; QImage requires 32 bpp) + if copy is True: + d2 = np.empty(imgData.shape[:2] + (4,), dtype=imgData.dtype) + d2[:,:,:3] = imgData + d2[:,:,3] = 255 + imgData = d2 + copied = True + else: + raise Exception('Array has only 3 channels; cannot make QImage without copying.') + + if alpha: + imgFormat = QtGui.QImage.Format_ARGB32 + else: + imgFormat = QtGui.QImage.Format_RGB32 + + if transpose: + imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite + + if not imgData.flags['C_CONTIGUOUS']: + if copy is False: + extra = ' (try setting transpose=False)' if transpose else '' + raise Exception('Array is not contiguous; cannot make QImage without copying.'+extra) + imgData = np.ascontiguousarray(imgData) + copied = True + + if copy is True and copied is False: + imgData = imgData.copy() + + if USE_PYSIDE: + ch = ctypes.c_char.from_buffer(imgData, 0) + img = QtGui.QImage(ch, imgData.shape[1], imgData.shape[0], imgFormat) + else: + #addr = ctypes.addressof(ctypes.c_char.from_buffer(imgData, 0)) + ## PyQt API for QImage changed between 4.9.3 and 4.9.6 (I don't know exactly which version it was) + ## So we first attempt the 4.9.6 API, then fall back to 4.9.3 + #addr = ctypes.c_char.from_buffer(imgData, 0) + #try: + #img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat) + #except TypeError: + #addr = ctypes.addressof(addr) + #img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat) + try: + img = QtGui.QImage(imgData.ctypes.data, imgData.shape[1], imgData.shape[0], imgFormat) + except: + if copy: + # does not leak memory, is not mutable + img = QtGui.QImage(buffer(imgData), imgData.shape[1], imgData.shape[0], imgFormat) + else: + # mutable, but leaks memory + img = QtGui.QImage(memoryview(imgData), imgData.shape[1], imgData.shape[0], imgFormat) + + img.data = imgData + return img + #try: + #buf = imgData.data + #except AttributeError: ## happens when image data is non-contiguous + #buf = imgData.data + + #prof.mark('1') + #qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat) + #prof.mark('2') + #qimage.data = imgData + #prof.finish() + #return qimage + +def imageToArray(img, copy=False, transpose=True): + """ + Convert a QImage into numpy array. The image must have format RGB32, ARGB32, or ARGB32_Premultiplied. + By default, the image is not copied; changes made to the array will appear in the QImage as well (beware: if + the QImage is collected before the array, there may be trouble). + The array will have shape (width, height, (b,g,r,a)). + """ + fmt = img.format() + ptr = img.bits() + if USE_PYSIDE: + arr = np.frombuffer(ptr, dtype=np.ubyte) + else: + ptr.setsize(img.byteCount()) + arr = np.asarray(ptr) + + if fmt == img.Format_RGB32: + arr = arr.reshape(img.height(), img.width(), 3) + elif fmt == img.Format_ARGB32 or fmt == img.Format_ARGB32_Premultiplied: + arr = arr.reshape(img.height(), img.width(), 4) + + if copy: + arr = arr.copy() + + if transpose: + return arr.transpose((1,0,2)) + else: + return arr + +def colorToAlpha(data, color): + """ + Given an RGBA image in *data*, convert *color* to be transparent. + *data* must be an array (w, h, 3 or 4) of ubyte values and *color* must be + an array (3) of ubyte values. + This is particularly useful for use with images that have a black or white background. + + Algorithm is taken from Gimp's color-to-alpha function in plug-ins/common/colortoalpha.c + Credit: + /* + * Color To Alpha plug-in v1.0 by Seth Burgess, sjburges@gimp.org 1999/05/14 + * with algorithm by clahey + */ + + """ + data = data.astype(float) + if data.shape[-1] == 3: ## add alpha channel if needed + d2 = np.empty(data.shape[:2]+(4,), dtype=data.dtype) + d2[...,:3] = data + d2[...,3] = 255 + data = d2 + + color = color.astype(float) + alpha = np.zeros(data.shape[:2]+(3,), dtype=float) + output = data.copy() + + for i in [0,1,2]: + d = data[...,i] + c = color[i] + mask = d > c + alpha[...,i][mask] = (d[mask] - c) / (255. - c) + imask = d < c + alpha[...,i][imask] = (c - d[imask]) / c + + output[...,3] = alpha.max(axis=2) * 255. + + mask = output[...,3] >= 1.0 ## avoid zero division while processing alpha channel + correction = 255. / output[...,3][mask] ## increase value to compensate for decreased alpha + for i in [0,1,2]: + output[...,i][mask] = ((output[...,i][mask]-color[i]) * correction) + color[i] + output[...,3][mask] *= data[...,3][mask] / 255. ## combine computed and previous alpha values + + #raise Exception() + return np.clip(output, 0, 255).astype(np.ubyte) + + + +def arrayToQPath(x, y, connect='all'): + """Convert an array of x,y coordinats to QPainterPath as efficiently as possible. + The *connect* argument may be 'all', indicating that each point should be + connected to the next; 'pairs', indicating that each pair of points + should be connected, or an array of int32 values (0 or 1) indicating + connections. + """ + + ## Create all vertices in path. The method used below creates a binary format so that all + ## vertices can be read in at once. This binary format may change in future versions of Qt, + ## so the original (slower) method is left here for emergencies: + #path.moveTo(x[0], y[0]) + #if connect == 'all': + #for i in range(1, y.shape[0]): + #path.lineTo(x[i], y[i]) + #elif connect == 'pairs': + #for i in range(1, y.shape[0]): + #if i%2 == 0: + #path.lineTo(x[i], y[i]) + #else: + #path.moveTo(x[i], y[i]) + #elif isinstance(connect, np.ndarray): + #for i in range(1, y.shape[0]): + #if connect[i] == 1: + #path.lineTo(x[i], y[i]) + #else: + #path.moveTo(x[i], y[i]) + #else: + #raise Exception('connect argument must be "all", "pairs", or array') + + ## Speed this up using >> operator + ## Format is: + ## numVerts(i4) 0(i4) + ## x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect + ## x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex + ## ... + ## 0(i4) + ## + ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') + + path = QtGui.QPainterPath() + + #prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True) + n = x.shape[0] + # create empty array, pad with extra space on either end + arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) + # write first two integers + #prof.mark('allocate empty') + byteview = arr.view(dtype=np.ubyte) + byteview[:12] = 0 + byteview.data[12:20] = struct.pack('>ii', n, 0) + #prof.mark('pack header') + # Fill array with vertex values + arr[1:-1]['x'] = x + arr[1:-1]['y'] = y + + # decide which points are connected by lines + if connect == 'pairs': + connect = np.empty((n/2,2), dtype=np.int32) + connect[:,0] = 1 + connect[:,1] = 0 + connect = connect.flatten() + if connect == 'finite': + connect = np.isfinite(x) & np.isfinite(y) + arr[1:-1]['c'] = connect + if connect == 'all': + arr[1:-1]['c'] = 1 + elif isinstance(connect, np.ndarray): + arr[1:-1]['c'] = connect + else: + raise Exception('connect argument must be "all", "pairs", or array') + + #prof.mark('fill array') + # write last 0 + lastInd = 20*(n+1) + byteview.data[lastInd:lastInd+4] = struct.pack('>i', 0) + #prof.mark('footer') + # create datastream object and stream into path + + ## Avoiding this method because QByteArray(str) leaks memory in PySide + #buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here + + path.strn = byteview.data[12:lastInd+4] # make sure data doesn't run away + try: + buf = QtCore.QByteArray.fromRawData(path.strn) + except TypeError: + buf = QtCore.QByteArray(bytes(path.strn)) + #prof.mark('create buffer') + ds = QtCore.QDataStream(buf) + + ds >> path + #prof.mark('load') + + #prof.finish() + + return path + +#def isosurface(data, level): + #""" + #Generate isosurface from volumetric data using marching tetrahedra algorithm. + #See Paul Bourke, "Polygonising a Scalar Field Using Tetrahedrons" (http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/) + + #*data* 3D numpy array of scalar values + #*level* The level at which to generate an isosurface + #""" + + #facets = [] + + ### mark everything below the isosurface level + #mask = data < level + + #### make eight sub-fields + #fields = np.empty((2,2,2), dtype=object) + #slices = [slice(0,-1), slice(1,None)] + #for i in [0,1]: + #for j in [0,1]: + #for k in [0,1]: + #fields[i,j,k] = mask[slices[i], slices[j], slices[k]] + + + + ### split each cell into 6 tetrahedra + ### these all have the same 'orienation'; points 1,2,3 circle + ### clockwise around point 0 + #tetrahedra = [ + #[(0,1,0), (1,1,1), (0,1,1), (1,0,1)], + #[(0,1,0), (0,1,1), (0,0,1), (1,0,1)], + #[(0,1,0), (0,0,1), (0,0,0), (1,0,1)], + #[(0,1,0), (0,0,0), (1,0,0), (1,0,1)], + #[(0,1,0), (1,0,0), (1,1,0), (1,0,1)], + #[(0,1,0), (1,1,0), (1,1,1), (1,0,1)] + #] + + ### each tetrahedron will be assigned an index + ### which determines how to generate its facets. + ### this structure is: + ### facets[index][facet1, facet2, ...] + ### where each facet is triangular and its points are each + ### interpolated between two points on the tetrahedron + ### facet = [(p1a, p1b), (p2a, p2b), (p3a, p3b)] + ### facet points always circle clockwise if you are looking + ### at them from below the isosurface. + #indexFacets = [ + #[], ## all above + #[[(0,1), (0,2), (0,3)]], # 0 below + #[[(1,0), (1,3), (1,2)]], # 1 below + #[[(0,2), (1,3), (1,2)], [(0,2), (0,3), (1,3)]], # 0,1 below + #[[(2,0), (2,1), (2,3)]], # 2 below + #[[(0,3), (1,2), (2,3)], [(0,3), (0,1), (1,2)]], # 0,2 below + #[[(1,0), (2,3), (2,0)], [(1,0), (1,3), (2,3)]], # 1,2 below + #[[(3,0), (3,1), (3,2)]], # 3 above + #[[(3,0), (3,2), (3,1)]], # 3 below + #[[(1,0), (2,0), (2,3)], [(1,0), (2,3), (1,3)]], # 0,3 below + #[[(0,3), (2,3), (1,2)], [(0,3), (1,2), (0,1)]], # 1,3 below + #[[(2,0), (2,3), (2,1)]], # 0,1,3 below + #[[(0,2), (1,2), (1,3)], [(0,2), (1,3), (0,3)]], # 2,3 below + #[[(1,0), (1,2), (1,3)]], # 0,2,3 below + #[[(0,1), (0,3), (0,2)]], # 1,2,3 below + #[] ## all below + #] + + #for tet in tetrahedra: + + ### get the 4 fields for this tetrahedron + #tetFields = [fields[c] for c in tet] + + ### generate an index for each grid cell + #index = tetFields[0] + tetFields[1]*2 + tetFields[2]*4 + tetFields[3]*8 + + ### add facets + #for i in xrange(index.shape[0]): # data x-axis + #for j in xrange(index.shape[1]): # data y-axis + #for k in xrange(index.shape[2]): # data z-axis + #for f in indexFacets[index[i,j,k]]: # faces to generate for this tet + #pts = [] + #for l in [0,1,2]: # points in this face + #p1 = tet[f[l][0]] # tet corner 1 + #p2 = tet[f[l][1]] # tet corner 2 + #pts.append([(p1[x]+p2[x])*0.5+[i,j,k][x]+0.5 for x in [0,1,2]]) ## interpolate between tet corners + #facets.append(pts) + + #return facets + + +def isocurve(data, level, connected=False, extendToEdge=False, path=False): + """ + Generate isocurve from 2D data using marching squares algorithm. + + ============= ========================================================= + Arguments + data 2D numpy array of scalar values + level The level at which to generate an isosurface + connected If False, return a single long list of point pairs + If True, return multiple long lists of connected point + locations. (This is slower but better for drawing + continuous lines) + extendToEdge If True, extend the curves to reach the exact edges of + the data. + path if True, return a QPainterPath rather than a list of + vertex coordinates. This forces connected=True. + ============= ========================================================= + + This function is SLOW; plenty of room for optimization here. + """ + + if path is True: + connected = True + + if extendToEdge: + d2 = np.empty((data.shape[0]+2, data.shape[1]+2), dtype=data.dtype) + d2[1:-1, 1:-1] = data + d2[0, 1:-1] = data[0] + d2[-1, 1:-1] = data[-1] + d2[1:-1, 0] = data[:, 0] + d2[1:-1, -1] = data[:, -1] + d2[0,0] = d2[0,1] + d2[0,-1] = d2[1,-1] + d2[-1,0] = d2[-1,1] + d2[-1,-1] = d2[-1,-2] + data = d2 + + sideTable = [ + [], + [0,1], + [1,2], + [0,2], + [0,3], + [1,3], + [0,1,2,3], + [2,3], + [2,3], + [0,1,2,3], + [1,3], + [0,3], + [0,2], + [1,2], + [0,1], + [] + ] + + edgeKey=[ + [(0,1), (0,0)], + [(0,0), (1,0)], + [(1,0), (1,1)], + [(1,1), (0,1)] + ] + + + lines = [] + + ## mark everything below the isosurface level + mask = data < level + + ### make four sub-fields and compute indexes for grid cells + index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) + fields = np.empty((2,2), dtype=object) + slices = [slice(0,-1), slice(1,None)] + for i in [0,1]: + for j in [0,1]: + fields[i,j] = mask[slices[i], slices[j]] + #vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme + vertIndex = i+2*j + #print i,j,k," : ", fields[i,j,k], 2**vertIndex + index += fields[i,j] * 2**vertIndex + #print index + #print index + + ## add lines + for i in range(index.shape[0]): # data x-axis + for j in range(index.shape[1]): # data y-axis + sides = sideTable[index[i,j]] + for l in range(0, len(sides), 2): ## faces for this grid cell + edges = sides[l:l+2] + pts = [] + for m in [0,1]: # points in this face + p1 = edgeKey[edges[m]][0] # p1, p2 are points at either side of an edge + p2 = edgeKey[edges[m]][1] + v1 = data[i+p1[0], j+p1[1]] # v1 and v2 are the values at p1 and p2 + v2 = data[i+p2[0], j+p2[1]] + f = (level-v1) / (v2-v1) + fi = 1.0 - f + p = ( ## interpolate between corners + p1[0]*fi + p2[0]*f + i + 0.5, + p1[1]*fi + p2[1]*f + j + 0.5 + ) + if extendToEdge: + ## check bounds + p = ( + min(data.shape[0]-2, max(0, p[0]-1)), + min(data.shape[1]-2, max(0, p[1]-1)), + ) + if connected: + gridKey = i + (1 if edges[m]==2 else 0), j + (1 if edges[m]==3 else 0), edges[m]%2 + pts.append((p, gridKey)) ## give the actual position and a key identifying the grid location (for connecting segments) + else: + pts.append(p) + + lines.append(pts) + + if not connected: + return lines + + ## turn disjoint list of segments into continuous lines + + #lines = [[2,5], [5,4], [3,4], [1,3], [6,7], [7,8], [8,6], [11,12], [12,15], [11,13], [13,14]] + #lines = [[(float(a), a), (float(b), b)] for a,b in lines] + points = {} ## maps each point to its connections + for a,b in lines: + if a[1] not in points: + points[a[1]] = [] + points[a[1]].append([a,b]) + if b[1] not in points: + points[b[1]] = [] + points[b[1]].append([b,a]) + + ## rearrange into chains + for k in list(points.keys()): + try: + chains = points[k] + except KeyError: ## already used this point elsewhere + continue + #print "===========", k + for chain in chains: + #print " chain:", chain + x = None + while True: + if x == chain[-1][1]: + break ## nothing left to do on this chain + + x = chain[-1][1] + if x == k: + break ## chain has looped; we're done and can ignore the opposite chain + y = chain[-2][1] + connects = points[x] + for conn in connects[:]: + if conn[1][1] != y: + #print " ext:", conn + chain.extend(conn[1:]) + #print " del:", x + del points[x] + if chain[0][1] == chain[-1][1]: # looped chain; no need to continue the other direction + chains.pop() + break + + + ## extract point locations + lines = [] + for chain in points.values(): + if len(chain) == 2: + chain = chain[1][1:][::-1] + chain[0] # join together ends of chain + else: + chain = chain[0] + lines.append([p[0] for p in chain]) + + if not path: + return lines ## a list of pairs of points + + path = QtGui.QPainterPath() + for line in lines: + path.moveTo(*line[0]) + for p in line[1:]: + path.lineTo(*p) + + return path + + +def traceImage(image, values, smooth=0.5): + """ + Convert an image to a set of QPainterPath curves. + One curve will be generated for each item in *values*; each curve outlines the area + of the image that is closer to its value than to any others. + + If image is RGB or RGBA, then the shape of values should be (nvals, 3/4) + The parameter *smooth* is expressed in pixels. + """ + import scipy.ndimage as ndi + if values.ndim == 2: + values = values.T + values = values[np.newaxis, np.newaxis, ...].astype(float) + image = image[..., np.newaxis].astype(float) + diff = np.abs(image-values) + if values.ndim == 4: + diff = diff.sum(axis=2) + + labels = np.argmin(diff, axis=2) + + paths = [] + for i in range(diff.shape[-1]): + d = (labels==i).astype(float) + d = ndi.gaussian_filter(d, (smooth, smooth)) + lines = isocurve(d, 0.5, connected=True, extendToEdge=True) + path = QtGui.QPainterPath() + for line in lines: + path.moveTo(*line[0]) + for p in line[1:]: + path.lineTo(*p) + + paths.append(path) + return paths + + + +IsosurfaceDataCache = None +def isosurface(data, level): + """ + Generate isosurface from volumetric data using marching cubes algorithm. + See Paul Bourke, "Polygonising a Scalar Field" + (http://paulbourke.net/geometry/polygonise/) + + *data* 3D numpy array of scalar values + *level* The level at which to generate an isosurface + + Returns an array of vertex coordinates (Nv, 3) and an array of + per-face vertex indexes (Nf, 3) + """ + ## For improvement, see: + ## + ## Efficient implementation of Marching Cubes' cases with topological guarantees. + ## Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan Tavares. + ## Journal of Graphics Tools 8(2): pp. 1-15 (december 2003) + + ## Precompute lookup tables on the first run + global IsosurfaceDataCache + if IsosurfaceDataCache is None: + ## map from grid cell index to edge index. + ## grid cell index tells us which corners are below the isosurface, + ## edge index tells us which edges are cut by the isosurface. + ## (Data stolen from Bourk; see above.) + edgeTable = np.array([ + 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, + 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, + 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, + 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, + 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, + 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, + 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, + 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , + 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, + 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, + 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, + 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, + 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, + 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, + 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, + 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, + 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ], dtype=np.uint16) + + ## Table of triangles to use for filling each grid cell. + ## Each set of three integers tells us which three edges to + ## draw a triangle between. + ## (Data stolen from Bourk; see above.) + triTable = [ + [], + [0, 8, 3], + [0, 1, 9], + [1, 8, 3, 9, 8, 1], + [1, 2, 10], + [0, 8, 3, 1, 2, 10], + [9, 2, 10, 0, 2, 9], + [2, 8, 3, 2, 10, 8, 10, 9, 8], + [3, 11, 2], + [0, 11, 2, 8, 11, 0], + [1, 9, 0, 2, 3, 11], + [1, 11, 2, 1, 9, 11, 9, 8, 11], + [3, 10, 1, 11, 10, 3], + [0, 10, 1, 0, 8, 10, 8, 11, 10], + [3, 9, 0, 3, 11, 9, 11, 10, 9], + [9, 8, 10, 10, 8, 11], + [4, 7, 8], + [4, 3, 0, 7, 3, 4], + [0, 1, 9, 8, 4, 7], + [4, 1, 9, 4, 7, 1, 7, 3, 1], + [1, 2, 10, 8, 4, 7], + [3, 4, 7, 3, 0, 4, 1, 2, 10], + [9, 2, 10, 9, 0, 2, 8, 4, 7], + [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4], + [8, 4, 7, 3, 11, 2], + [11, 4, 7, 11, 2, 4, 2, 0, 4], + [9, 0, 1, 8, 4, 7, 2, 3, 11], + [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1], + [3, 10, 1, 3, 11, 10, 7, 8, 4], + [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4], + [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3], + [4, 7, 11, 4, 11, 9, 9, 11, 10], + [9, 5, 4], + [9, 5, 4, 0, 8, 3], + [0, 5, 4, 1, 5, 0], + [8, 5, 4, 8, 3, 5, 3, 1, 5], + [1, 2, 10, 9, 5, 4], + [3, 0, 8, 1, 2, 10, 4, 9, 5], + [5, 2, 10, 5, 4, 2, 4, 0, 2], + [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8], + [9, 5, 4, 2, 3, 11], + [0, 11, 2, 0, 8, 11, 4, 9, 5], + [0, 5, 4, 0, 1, 5, 2, 3, 11], + [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5], + [10, 3, 11, 10, 1, 3, 9, 5, 4], + [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10], + [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3], + [5, 4, 8, 5, 8, 10, 10, 8, 11], + [9, 7, 8, 5, 7, 9], + [9, 3, 0, 9, 5, 3, 5, 7, 3], + [0, 7, 8, 0, 1, 7, 1, 5, 7], + [1, 5, 3, 3, 5, 7], + [9, 7, 8, 9, 5, 7, 10, 1, 2], + [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3], + [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2], + [2, 10, 5, 2, 5, 3, 3, 5, 7], + [7, 9, 5, 7, 8, 9, 3, 11, 2], + [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11], + [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7], + [11, 2, 1, 11, 1, 7, 7, 1, 5], + [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11], + [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0], + [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0], + [11, 10, 5, 7, 11, 5], + [10, 6, 5], + [0, 8, 3, 5, 10, 6], + [9, 0, 1, 5, 10, 6], + [1, 8, 3, 1, 9, 8, 5, 10, 6], + [1, 6, 5, 2, 6, 1], + [1, 6, 5, 1, 2, 6, 3, 0, 8], + [9, 6, 5, 9, 0, 6, 0, 2, 6], + [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8], + [2, 3, 11, 10, 6, 5], + [11, 0, 8, 11, 2, 0, 10, 6, 5], + [0, 1, 9, 2, 3, 11, 5, 10, 6], + [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11], + [6, 3, 11, 6, 5, 3, 5, 1, 3], + [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6], + [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9], + [6, 5, 9, 6, 9, 11, 11, 9, 8], + [5, 10, 6, 4, 7, 8], + [4, 3, 0, 4, 7, 3, 6, 5, 10], + [1, 9, 0, 5, 10, 6, 8, 4, 7], + [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4], + [6, 1, 2, 6, 5, 1, 4, 7, 8], + [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7], + [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6], + [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9], + [3, 11, 2, 7, 8, 4, 10, 6, 5], + [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11], + [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6], + [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6], + [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6], + [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11], + [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7], + [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9], + [10, 4, 9, 6, 4, 10], + [4, 10, 6, 4, 9, 10, 0, 8, 3], + [10, 0, 1, 10, 6, 0, 6, 4, 0], + [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10], + [1, 4, 9, 1, 2, 4, 2, 6, 4], + [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4], + [0, 2, 4, 4, 2, 6], + [8, 3, 2, 8, 2, 4, 4, 2, 6], + [10, 4, 9, 10, 6, 4, 11, 2, 3], + [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6], + [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10], + [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1], + [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3], + [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1], + [3, 11, 6, 3, 6, 0, 0, 6, 4], + [6, 4, 8, 11, 6, 8], + [7, 10, 6, 7, 8, 10, 8, 9, 10], + [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10], + [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0], + [10, 6, 7, 10, 7, 1, 1, 7, 3], + [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7], + [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9], + [7, 8, 0, 7, 0, 6, 6, 0, 2], + [7, 3, 2, 6, 7, 2], + [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7], + [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7], + [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11], + [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1], + [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6], + [0, 9, 1, 11, 6, 7], + [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0], + [7, 11, 6], + [7, 6, 11], + [3, 0, 8, 11, 7, 6], + [0, 1, 9, 11, 7, 6], + [8, 1, 9, 8, 3, 1, 11, 7, 6], + [10, 1, 2, 6, 11, 7], + [1, 2, 10, 3, 0, 8, 6, 11, 7], + [2, 9, 0, 2, 10, 9, 6, 11, 7], + [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8], + [7, 2, 3, 6, 2, 7], + [7, 0, 8, 7, 6, 0, 6, 2, 0], + [2, 7, 6, 2, 3, 7, 0, 1, 9], + [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6], + [10, 7, 6, 10, 1, 7, 1, 3, 7], + [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8], + [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7], + [7, 6, 10, 7, 10, 8, 8, 10, 9], + [6, 8, 4, 11, 8, 6], + [3, 6, 11, 3, 0, 6, 0, 4, 6], + [8, 6, 11, 8, 4, 6, 9, 0, 1], + [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6], + [6, 8, 4, 6, 11, 8, 2, 10, 1], + [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6], + [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9], + [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3], + [8, 2, 3, 8, 4, 2, 4, 6, 2], + [0, 4, 2, 4, 6, 2], + [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8], + [1, 9, 4, 1, 4, 2, 2, 4, 6], + [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1], + [10, 1, 0, 10, 0, 6, 6, 0, 4], + [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3], + [10, 9, 4, 6, 10, 4], + [4, 9, 5, 7, 6, 11], + [0, 8, 3, 4, 9, 5, 11, 7, 6], + [5, 0, 1, 5, 4, 0, 7, 6, 11], + [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5], + [9, 5, 4, 10, 1, 2, 7, 6, 11], + [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5], + [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2], + [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6], + [7, 2, 3, 7, 6, 2, 5, 4, 9], + [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7], + [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0], + [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8], + [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7], + [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4], + [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10], + [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10], + [6, 9, 5, 6, 11, 9, 11, 8, 9], + [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5], + [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11], + [6, 11, 3, 6, 3, 5, 5, 3, 1], + [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6], + [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10], + [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5], + [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3], + [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2], + [9, 5, 6, 9, 6, 0, 0, 6, 2], + [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8], + [1, 5, 6, 2, 1, 6], + [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6], + [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0], + [0, 3, 8, 5, 6, 10], + [10, 5, 6], + [11, 5, 10, 7, 5, 11], + [11, 5, 10, 11, 7, 5, 8, 3, 0], + [5, 11, 7, 5, 10, 11, 1, 9, 0], + [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1], + [11, 1, 2, 11, 7, 1, 7, 5, 1], + [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11], + [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7], + [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2], + [2, 5, 10, 2, 3, 5, 3, 7, 5], + [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5], + [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2], + [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2], + [1, 3, 5, 3, 7, 5], + [0, 8, 7, 0, 7, 1, 1, 7, 5], + [9, 0, 3, 9, 3, 5, 5, 3, 7], + [9, 8, 7, 5, 9, 7], + [5, 8, 4, 5, 10, 8, 10, 11, 8], + [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0], + [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5], + [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4], + [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8], + [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11], + [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5], + [9, 4, 5, 2, 11, 3], + [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4], + [5, 10, 2, 5, 2, 4, 4, 2, 0], + [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9], + [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2], + [8, 4, 5, 8, 5, 3, 3, 5, 1], + [0, 4, 5, 1, 0, 5], + [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5], + [9, 4, 5], + [4, 11, 7, 4, 9, 11, 9, 10, 11], + [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11], + [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11], + [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4], + [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2], + [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3], + [11, 7, 4, 11, 4, 2, 2, 4, 0], + [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4], + [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9], + [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7], + [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10], + [1, 10, 2, 8, 7, 4], + [4, 9, 1, 4, 1, 7, 7, 1, 3], + [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1], + [4, 0, 3, 7, 4, 3], + [4, 8, 7], + [9, 10, 8, 10, 11, 8], + [3, 0, 9, 3, 9, 11, 11, 9, 10], + [0, 1, 10, 0, 10, 8, 8, 10, 11], + [3, 1, 10, 11, 3, 10], + [1, 2, 11, 1, 11, 9, 9, 11, 8], + [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9], + [0, 2, 11, 8, 0, 11], + [3, 2, 11], + [2, 3, 8, 2, 8, 10, 10, 8, 9], + [9, 10, 2, 0, 9, 2], + [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8], + [1, 10, 2], + [1, 3, 8, 9, 1, 8], + [0, 9, 1], + [0, 3, 8], + [] + ] + edgeShifts = np.array([ ## maps edge ID (0-11) to (x,y,z) cell offset and edge ID (0-2) + [0, 0, 0, 0], + [1, 0, 0, 1], + [0, 1, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], + [1, 0, 1, 1], + [0, 1, 1, 0], + [0, 0, 1, 1], + [0, 0, 0, 2], + [1, 0, 0, 2], + [1, 1, 0, 2], + [0, 1, 0, 2], + #[9, 9, 9, 9] ## fake + ], dtype=np.ubyte) + nTableFaces = np.array([len(f)/3 for f in triTable], dtype=np.ubyte) + faceShiftTables = [None] + for i in range(1,6): + ## compute lookup table of index: vertexes mapping + faceTableI = np.zeros((len(triTable), i*3), dtype=np.ubyte) + faceTableInds = np.argwhere(nTableFaces == i) + faceTableI[faceTableInds[:,0]] = np.array([triTable[j] for j in faceTableInds]) + faceTableI = faceTableI.reshape((len(triTable), i, 3)) + faceShiftTables.append(edgeShifts[faceTableI]) + + ## Let's try something different: + #faceTable = np.empty((256, 5, 3, 4), dtype=np.ubyte) # (grid cell index, faces, vertexes, edge lookup) + #for i,f in enumerate(triTable): + #f = np.array(f + [12] * (15-len(f))).reshape(5,3) + #faceTable[i] = edgeShifts[f] + + + IsosurfaceDataCache = (faceShiftTables, edgeShifts, edgeTable, nTableFaces) + else: + faceShiftTables, edgeShifts, edgeTable, nTableFaces = IsosurfaceDataCache + + + + ## mark everything below the isosurface level + mask = data < level + + ### make eight sub-fields and compute indexes for grid cells + index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) + fields = np.empty((2,2,2), dtype=object) + slices = [slice(0,-1), slice(1,None)] + for i in [0,1]: + for j in [0,1]: + for k in [0,1]: + fields[i,j,k] = mask[slices[i], slices[j], slices[k]] + vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme + index += fields[i,j,k] * 2**vertIndex + + ### Generate table of edges that have been cut + cutEdges = np.zeros([x+1 for x in index.shape]+[3], dtype=np.uint32) + edges = edgeTable[index] + for i, shift in enumerate(edgeShifts[:12]): + slices = [slice(shift[j],cutEdges.shape[j]+(shift[j]-1)) for j in range(3)] + cutEdges[slices[0], slices[1], slices[2], shift[3]] += edges & 2**i + + ## for each cut edge, interpolate to see where exactly the edge is cut and generate vertex positions + m = cutEdges > 0 + vertexInds = np.argwhere(m) ## argwhere is slow! + vertexes = vertexInds[:,:3].astype(np.float32) + dataFlat = data.reshape(data.shape[0]*data.shape[1]*data.shape[2]) + + ## re-use the cutEdges array as a lookup table for vertex IDs + cutEdges[vertexInds[:,0], vertexInds[:,1], vertexInds[:,2], vertexInds[:,3]] = np.arange(vertexInds.shape[0]) + + for i in [0,1,2]: + vim = vertexInds[:,3] == i + vi = vertexInds[vim, :3] + viFlat = (vi * (np.array(data.strides[:3]) // data.itemsize)[np.newaxis,:]).sum(axis=1) + v1 = dataFlat[viFlat] + v2 = dataFlat[viFlat + data.strides[i]//data.itemsize] + vertexes[vim,i] += (level-v1) / (v2-v1) + + ### compute the set of vertex indexes for each face. + + ## This works, but runs a bit slower. + #cells = np.argwhere((index != 0) & (index != 255)) ## all cells with at least one face + #cellInds = index[cells[:,0], cells[:,1], cells[:,2]] + #verts = faceTable[cellInds] + #mask = verts[...,0,0] != 9 + #verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges + #verts = verts[mask] + #faces = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want. + + + ## To allow this to be vectorized efficiently, we count the number of faces in each + ## grid cell and handle each group of cells with the same number together. + ## determine how many faces to assign to each grid cell + nFaces = nTableFaces[index] + totFaces = nFaces.sum() + faces = np.empty((totFaces, 3), dtype=np.uint32) + ptr = 0 + #import debug + #p = debug.Profiler('isosurface', disabled=False) + + ## this helps speed up an indexing operation later on + cs = np.array(cutEdges.strides)//cutEdges.itemsize + cutEdges = cutEdges.flatten() + + ## this, strangely, does not seem to help. + #ins = np.array(index.strides)/index.itemsize + #index = index.flatten() + + for i in range(1,6): + ### expensive: + #p.mark('1') + cells = np.argwhere(nFaces == i) ## all cells which require i faces (argwhere is expensive) + #p.mark('2') + if cells.shape[0] == 0: + continue + #cellInds = index[(cells*ins[np.newaxis,:]).sum(axis=1)] + cellInds = index[cells[:,0], cells[:,1], cells[:,2]] ## index values of cells to process for this round + #p.mark('3') + + ### expensive: + verts = faceShiftTables[i][cellInds] + #p.mark('4') + verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges + verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:]) + #p.mark('5') + + ### expensive: + #print verts.shape + verts = (verts * cs[np.newaxis, np.newaxis, :]).sum(axis=2) + #vertInds = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want. + vertInds = cutEdges[verts] + #p.mark('6') + nv = vertInds.shape[0] + #p.mark('7') + faces[ptr:ptr+nv] = vertInds #.reshape((nv, 3)) + #p.mark('8') + ptr += nv + + return vertexes, faces + + + +def invertQTransform(tr): + """Return a QTransform that is the inverse of *tr*. + Rasises an exception if tr is not invertible. + + Note that this function is preferred over QTransform.inverted() due to + bugs in that method. (specifically, Qt has floating-point precision issues + when determining whether a matrix is invertible) + """ + if not HAVE_SCIPY: + inv = tr.inverted() + if inv[1] is False: + raise Exception("Transform is not invertible.") + return inv[0] + arr = np.array([[tr.m11(), tr.m12(), tr.m13()], [tr.m21(), tr.m22(), tr.m23()], [tr.m31(), tr.m32(), tr.m33()]]) + inv = scipy.linalg.inv(arr) + return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1]) + + +def pseudoScatter(data, spacing=None, shuffle=True, bidir=False): + """ + Used for examining the distribution of values in a set. Produces scattering as in beeswarm or column scatter plots. + + Given a list of x-values, construct a set of y-values such that an x,y scatter-plot + will not have overlapping points (it will look similar to a histogram). + """ + inds = np.arange(len(data)) + if shuffle: + np.random.shuffle(inds) + + data = data[inds] + + if spacing is None: + spacing = 2.*np.std(data)/len(data)**0.5 + s2 = spacing**2 + + yvals = np.empty(len(data)) + if len(data) == 0: + return yvals + yvals[0] = 0 + for i in range(1,len(data)): + x = data[i] # current x value to be placed + x0 = data[:i] # all x values already placed + y0 = yvals[:i] # all y values already placed + y = 0 + + dx = (x0-x)**2 # x-distance to each previous point + xmask = dx < s2 # exclude anything too far away + + if xmask.sum() > 0: + if bidir: + dirs = [-1, 1] + else: + dirs = [1] + yopts = [] + for direction in dirs: + y = 0 + dx2 = dx[xmask] + dy = (s2 - dx2)**0.5 + limits = np.empty((2,len(dy))) # ranges of y-values to exclude + limits[0] = y0[xmask] - dy + limits[1] = y0[xmask] + dy + while True: + # ignore anything below this y-value + if direction > 0: + mask = limits[1] >= y + else: + mask = limits[0] <= y + + limits2 = limits[:,mask] + + # are we inside an excluded region? + mask = (limits2[0] < y) & (limits2[1] > y) + if mask.sum() == 0: + break + + if direction > 0: + y = limits2[:,mask].max() + else: + y = limits2[:,mask].min() + yopts.append(y) + if bidir: + y = yopts[0] if -yopts[0] < yopts[1] else yopts[1] + else: + y = yopts[0] + yvals[i] = y + + return yvals[np.argsort(inds)] ## un-shuffle values before returning diff --git a/pyqtgraph/graphicsItems/ArrowItem.py b/pyqtgraph/graphicsItems/ArrowItem.py new file mode 100644 index 00000000..dcede02a --- /dev/null +++ b/pyqtgraph/graphicsItems/ArrowItem.py @@ -0,0 +1,124 @@ +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn +import numpy as np +__all__ = ['ArrowItem'] + +class ArrowItem(QtGui.QGraphicsPathItem): + """ + For displaying scale-invariant arrows. + For arrows pointing to a location on a curve, see CurveArrow + + """ + + + def __init__(self, **opts): + """ + Arrows can be initialized with any keyword arguments accepted by + the setStyle() method. + """ + QtGui.QGraphicsPathItem.__init__(self, opts.get('parent', None)) + if 'size' in opts: + opts['headLen'] = opts['size'] + if 'width' in opts: + opts['headWidth'] = opts['width'] + defOpts = { + 'pxMode': True, + 'angle': -150, ## If the angle is 0, the arrow points left + 'pos': (0,0), + 'headLen': 20, + 'tipAngle': 25, + 'baseAngle': 0, + 'tailLen': None, + 'tailWidth': 3, + 'pen': (200,200,200), + 'brush': (50,50,200), + } + defOpts.update(opts) + + self.setStyle(**defOpts) + + self.setPen(fn.mkPen(defOpts['pen'])) + self.setBrush(fn.mkBrush(defOpts['brush'])) + + self.rotate(self.opts['angle']) + self.moveBy(*self.opts['pos']) + + def setStyle(self, **opts): + """ + Changes the appearance of the arrow. + All arguments are optional: + + ================= ================================================= + Keyword Arguments + angle Orientation of the arrow in degrees. Default is + 0; arrow pointing to the left. + headLen Length of the arrow head, from tip to base. + default=20 + headWidth Width of the arrow head at its base. + tipAngle Angle of the tip of the arrow in degrees. Smaller + values make a 'sharper' arrow. If tipAngle is + specified, ot overrides headWidth. default=25 + baseAngle Angle of the base of the arrow head. Default is + 0, which means that the base of the arrow head + is perpendicular to the arrow shaft. + tailLen Length of the arrow tail, measured from the base + of the arrow head to the tip of the tail. If + this value is None, no tail will be drawn. + default=None + tailWidth Width of the tail. default=3 + pen The pen used to draw the outline of the arrow. + brush The brush used to fill the arrow. + ================= ================================================= + """ + self.opts = opts + + opt = dict([(k,self.opts[k]) for k in ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth']]) + self.path = fn.makeArrowPath(**opt) + self.setPath(self.path) + + if opts['pxMode']: + self.setFlags(self.flags() | self.ItemIgnoresTransformations) + else: + self.setFlags(self.flags() & ~self.ItemIgnoresTransformations) + + def paint(self, p, *args): + p.setRenderHint(QtGui.QPainter.Antialiasing) + QtGui.QGraphicsPathItem.paint(self, p, *args) + + #p.setPen(fn.mkPen('r')) + #p.setBrush(fn.mkBrush(None)) + #p.drawRect(self.boundingRect()) + + def shape(self): + #if not self.opts['pxMode']: + #return QtGui.QGraphicsPathItem.shape(self) + return self.path + + ## dataBounds and pixelPadding methods are provided to ensure ViewBox can + ## properly auto-range + def dataBounds(self, ax, frac, orthoRange=None): + pw = 0 + pen = self.pen() + if not pen.isCosmetic(): + pw = pen.width() * 0.7072 + if self.opts['pxMode']: + return [0,0] + else: + br = self.boundingRect() + if ax == 0: + return [br.left()-pw, br.right()+pw] + else: + return [br.top()-pw, br.bottom()+pw] + + def pixelPadding(self): + pad = 0 + if self.opts['pxMode']: + br = self.boundingRect() + pad += (br.width()**2 + br.height()**2) ** 0.5 + pen = self.pen() + if pen.isCosmetic(): + pad += max(1, pen.width()) * 0.7072 + return pad + + + \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/AxisItem.py b/pyqtgraph/graphicsItems/AxisItem.py new file mode 100644 index 00000000..429ff49c --- /dev/null +++ b/pyqtgraph/graphicsItems/AxisItem.py @@ -0,0 +1,931 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.python2_3 import asUnicode +import numpy as np +from pyqtgraph.Point import Point +import pyqtgraph.debug as debug +import weakref +import pyqtgraph.functions as fn +import pyqtgraph as pg +from .GraphicsWidget import GraphicsWidget + +__all__ = ['AxisItem'] +class AxisItem(GraphicsWidget): + """ + GraphicsItem showing a single plot axis with ticks, values, and label. + Can be configured to fit on any side of a plot, and can automatically synchronize its displayed scale with ViewBox items. + Ticks can be extended to draw a grid. + If maxTickLength is negative, ticks point into the plot. + """ + + def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True): + """ + ============== =============================================================== + **Arguments:** + orientation one of 'left', 'right', 'top', or 'bottom' + maxTickLength (px) maximum length of ticks to draw. Negative values draw + into the plot, positive values draw outward. + linkView (ViewBox) causes the range of values displayed in the axis + to be linked to the visible range of a ViewBox. + showValues (bool) Whether to display values adjacent to ticks + pen (QPen) Pen used when drawing ticks. + ============== =============================================================== + """ + + GraphicsWidget.__init__(self, parent) + self.label = QtGui.QGraphicsTextItem(self) + self.showValues = showValues + self.picture = None + self.orientation = orientation + if orientation not in ['left', 'right', 'top', 'bottom']: + raise Exception("Orientation argument must be one of 'left', 'right', 'top', or 'bottom'.") + if orientation in ['left', 'right']: + self.label.rotate(-90) + + self.style = { + 'tickTextOffset': (5, 2), ## (horizontal, vertical) spacing between text and axis + 'tickTextWidth': 30, ## space reserved for tick text + 'tickTextHeight': 18, + 'autoExpandTextSpace': True, ## automatically expand text space if needed + 'tickFont': None, + 'stopAxisAtTick': (False, False), ## whether axis is drawn to edge of box or to last tick + 'textFillLimits': [ ## how much of the axis to fill up with tick text, maximally. + (0, 0.8), ## never fill more than 80% of the axis + (2, 0.6), ## If we already have 2 ticks with text, fill no more than 60% of the axis + (4, 0.4), ## If we already have 4 ticks with text, fill no more than 40% of the axis + (6, 0.2), ## If we already have 6 ticks with text, fill no more than 20% of the axis + ] + } + + self.textWidth = 30 ## Keeps track of maximum width / height of tick text + self.textHeight = 18 + + self.labelText = '' + self.labelUnits = '' + self.labelUnitPrefix='' + self.labelStyle = {} + self.logMode = False + self.tickFont = None + + self.tickLength = maxTickLength + self._tickLevels = None ## used to override the automatic ticking system with explicit ticks + self.scale = 1.0 + self.autoSIPrefix = True + self.autoSIPrefixScale = 1.0 + + self.setRange(0, 1) + + self.setPen(pen) + + self._linkedView = None + if linkView is not None: + self.linkToView(linkView) + + self.showLabel(False) + + self.grid = False + #self.setCacheMode(self.DeviceCoordinateCache) + + def close(self): + self.scene().removeItem(self.label) + self.label = None + self.scene().removeItem(self) + + def setGrid(self, grid): + """Set the alpha value for the grid, or False to disable.""" + self.grid = grid + self.picture = None + self.prepareGeometryChange() + self.update() + + def setLogMode(self, log): + """ + If *log* is True, then ticks are displayed on a logarithmic scale and values + are adjusted accordingly. (This is usually accessed by changing the log mode + of a :func:`PlotItem `) + """ + self.logMode = log + self.picture = None + self.update() + + def setTickFont(self, font): + self.tickFont = font + self.picture = None + self.prepareGeometryChange() + ## Need to re-allocate space depending on font size? + + self.update() + + def resizeEvent(self, ev=None): + #s = self.size() + + ## Set the position of the label + nudge = 5 + br = self.label.boundingRect() + p = QtCore.QPointF(0, 0) + if self.orientation == 'left': + p.setY(int(self.size().height()/2 + br.width()/2)) + p.setX(-nudge) + #s.setWidth(10) + elif self.orientation == 'right': + #s.setWidth(10) + p.setY(int(self.size().height()/2 + br.width()/2)) + p.setX(int(self.size().width()-br.height()+nudge)) + elif self.orientation == 'top': + #s.setHeight(10) + p.setY(-nudge) + p.setX(int(self.size().width()/2. - br.width()/2.)) + elif self.orientation == 'bottom': + p.setX(int(self.size().width()/2. - br.width()/2.)) + #s.setHeight(10) + p.setY(int(self.size().height()-br.height()+nudge)) + #self.label.resize(s) + self.label.setPos(p) + self.picture = None + + def showLabel(self, show=True): + """Show/hide the label text for this axis.""" + #self.drawLabel = show + self.label.setVisible(show) + if self.orientation in ['left', 'right']: + self.setWidth() + else: + self.setHeight() + if self.autoSIPrefix: + self.updateAutoSIPrefix() + + def setLabel(self, text=None, units=None, unitPrefix=None, **args): + """Set the text displayed adjacent to the axis. + + ============= ============================================================= + Arguments + text The text (excluding units) to display on the label for this + axis. + units The units for this axis. Units should generally be given + without any scaling prefix (eg, 'V' instead of 'mV'). The + scaling prefix will be automatically prepended based on the + range of data displayed. + **args All extra keyword arguments become CSS style options for + the tag which will surround the axis label and units. + ============= ============================================================= + + The final text generated for the label will look like:: + + {text} (prefix{units}) + + Each extra keyword argument will become a CSS option in the above template. + For example, you can set the font size and color of the label:: + + labelStyle = {'color': '#FFF', 'font-size': '14pt'} + axis.setLabel('label text', units='V', **labelStyle) + + """ + if text is not None: + self.labelText = text + self.showLabel() + if units is not None: + self.labelUnits = units + self.showLabel() + if unitPrefix is not None: + self.labelUnitPrefix = unitPrefix + if len(args) > 0: + self.labelStyle = args + self.label.setHtml(self.labelString()) + self._adjustSize() + self.picture = None + self.update() + + def labelString(self): + if self.labelUnits == '': + if not self.autoSIPrefix or self.autoSIPrefixScale == 1.0: + units = '' + else: + units = asUnicode('(x%g)') % (1.0/self.autoSIPrefixScale) + else: + #print repr(self.labelUnitPrefix), repr(self.labelUnits) + units = asUnicode('(%s%s)') % (asUnicode(self.labelUnitPrefix), asUnicode(self.labelUnits)) + + s = asUnicode('%s %s') % (asUnicode(self.labelText), asUnicode(units)) + + style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle]) + + return asUnicode("%s") % (style, asUnicode(s)) + + def _updateMaxTextSize(self, x): + ## Informs that the maximum tick size orthogonal to the axis has + ## changed; we use this to decide whether the item needs to be resized + ## to accomodate. + if self.orientation in ['left', 'right']: + mx = max(self.textWidth, x) + if mx > self.textWidth or mx < self.textWidth-10: + self.textWidth = mx + if self.style['autoExpandTextSpace'] is True: + self.setWidth() + #return True ## size has changed + else: + mx = max(self.textHeight, x) + if mx > self.textHeight or mx < self.textHeight-10: + self.textHeight = mx + if self.style['autoExpandTextSpace'] is True: + self.setHeight() + #return True ## size has changed + + def _adjustSize(self): + if self.orientation in ['left', 'right']: + self.setWidth() + else: + self.setHeight() + + def setHeight(self, h=None): + """Set the height of this axis reserved for ticks and tick labels. + The height of the axis label is automatically added.""" + if h is None: + if self.style['autoExpandTextSpace'] is True: + h = self.textHeight + else: + h = self.style['tickTextHeight'] + h += max(0, self.tickLength) + self.style['tickTextOffset'][1] + if self.label.isVisible(): + h += self.label.boundingRect().height() * 0.8 + self.setMaximumHeight(h) + self.setMinimumHeight(h) + self.picture = None + + + def setWidth(self, w=None): + """Set the width of this axis reserved for ticks and tick labels. + The width of the axis label is automatically added.""" + if w is None: + if self.style['autoExpandTextSpace'] is True: + w = self.textWidth + else: + w = self.style['tickTextWidth'] + w += max(0, self.tickLength) + self.style['tickTextOffset'][0] + if self.label.isVisible(): + w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate + self.setMaximumWidth(w) + self.setMinimumWidth(w) + self.picture = None + + def pen(self): + if self._pen is None: + return fn.mkPen(pg.getConfigOption('foreground')) + return pg.mkPen(self._pen) + + def setPen(self, pen): + """ + Set the pen used for drawing text, axes, ticks, and grid lines. + if pen == None, the default will be used (see :func:`setConfigOption + `) + """ + self._pen = pen + self.picture = None + if pen is None: + pen = pg.getConfigOption('foreground') + self.labelStyle['color'] = '#' + pg.colorStr(pg.mkPen(pen).color())[:6] + self.setLabel() + self.update() + + def setScale(self, scale=None): + """ + Set the value scaling for this axis. + + Setting this value causes the axis to draw ticks and tick labels as if + the view coordinate system were scaled. By default, the axis scaling is + 1.0. + """ + # Deprecated usage, kept for backward compatibility + if scale is None: + scale = 1.0 + self.enableAutoSIPrefix(True) + + if scale != self.scale: + self.scale = scale + self.setLabel() + self.picture = None + self.update() + + def enableAutoSIPrefix(self, enable=True): + """ + Enable (or disable) automatic SI prefix scaling on this axis. + + When enabled, this feature automatically determines the best SI prefix + to prepend to the label units, while ensuring that axis values are scaled + accordingly. + + For example, if the axis spans values from -0.1 to 0.1 and has units set + to 'V' then the axis would display values -100 to 100 + and the units would appear as 'mV' + + This feature is enabled by default, and is only available when a suffix + (unit string) is provided to display on the label. + """ + self.autoSIPrefix = enable + self.updateAutoSIPrefix() + + def updateAutoSIPrefix(self): + if self.label.isVisible(): + (scale, prefix) = fn.siScale(max(abs(self.range[0]*self.scale), abs(self.range[1]*self.scale))) + if self.labelUnits == '' and prefix in ['k', 'm']: ## If we are not showing units, wait until 1e6 before scaling. + scale = 1.0 + prefix = '' + self.setLabel(unitPrefix=prefix) + else: + scale = 1.0 + + self.autoSIPrefixScale = scale + self.picture = None + self.update() + + + def setRange(self, mn, mx): + """Set the range of values displayed by the axis. + Usually this is handled automatically by linking the axis to a ViewBox with :func:`linkToView `""" + if any(np.isinf((mn, mx))) or any(np.isnan((mn, mx))): + raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx))) + self.range = [mn, mx] + if self.autoSIPrefix: + self.updateAutoSIPrefix() + self.picture = None + self.update() + + def linkedView(self): + """Return the ViewBox this axis is linked to""" + if self._linkedView is None: + return None + else: + return self._linkedView() + + def linkToView(self, view): + """Link this axis to a ViewBox, causing its displayed range to match the visible range of the view.""" + oldView = self.linkedView() + self._linkedView = weakref.ref(view) + if self.orientation in ['right', 'left']: + if oldView is not None: + oldView.sigYRangeChanged.disconnect(self.linkedViewChanged) + view.sigYRangeChanged.connect(self.linkedViewChanged) + else: + if oldView is not None: + oldView.sigXRangeChanged.disconnect(self.linkedViewChanged) + view.sigXRangeChanged.connect(self.linkedViewChanged) + + if oldView is not None: + oldView.sigResized.disconnect(self.linkedViewChanged) + view.sigResized.connect(self.linkedViewChanged) + + def linkedViewChanged(self, view, newRange=None): + if self.orientation in ['right', 'left']: + if newRange is None: + newRange = view.viewRange()[1] + if view.yInverted(): + self.setRange(*newRange[::-1]) + else: + self.setRange(*newRange) + else: + if newRange is None: + newRange = view.viewRange()[0] + self.setRange(*newRange) + + def boundingRect(self): + linkedView = self.linkedView() + if linkedView is None or self.grid is False: + rect = self.mapRectFromParent(self.geometry()) + ## extend rect if ticks go in negative direction + ## also extend to account for text that flows past the edges + if self.orientation == 'left': + rect = rect.adjusted(0, -15, -min(0,self.tickLength), 15) + elif self.orientation == 'right': + rect = rect.adjusted(min(0,self.tickLength), -15, 0, 15) + elif self.orientation == 'top': + rect = rect.adjusted(-15, 0, 15, -min(0,self.tickLength)) + elif self.orientation == 'bottom': + rect = rect.adjusted(-15, min(0,self.tickLength), 15, 0) + return rect + else: + return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect()) + + def paint(self, p, opt, widget): + prof = debug.Profiler('AxisItem.paint', disabled=True) + if self.picture is None: + try: + picture = QtGui.QPicture() + painter = QtGui.QPainter(picture) + specs = self.generateDrawSpecs(painter) + prof.mark('generate specs') + if specs is not None: + self.drawPicture(painter, *specs) + prof.mark('draw picture') + finally: + painter.end() + self.picture = picture + #p.setRenderHint(p.Antialiasing, False) ## Sometimes we get a segfault here ??? + #p.setRenderHint(p.TextAntialiasing, True) + self.picture.play(p) + prof.finish() + + + + def setTicks(self, ticks): + """Explicitly determine which ticks to display. + This overrides the behavior specified by tickSpacing(), tickValues(), and tickStrings() + The format for *ticks* looks like:: + + [ + [ (majorTickValue1, majorTickString1), (majorTickValue2, majorTickString2), ... ], + [ (minorTickValue1, minorTickString1), (minorTickValue2, minorTickString2), ... ], + ... + ] + + If *ticks* is None, then the default tick system will be used instead. + """ + self._tickLevels = ticks + self.picture = None + self.update() + + def tickSpacing(self, minVal, maxVal, size): + """Return values describing the desired spacing and offset of ticks. + + This method is called whenever the axis needs to be redrawn and is a + good method to override in subclasses that require control over tick locations. + + The return value must be a list of tuples, one for each set of ticks:: + + [ + (major tick spacing, offset), + (minor tick spacing, offset), + (sub-minor tick spacing, offset), + ... + ] + """ + dif = abs(maxVal - minVal) + if dif == 0: + return [] + + ## decide optimal minor tick spacing in pixels (this is just aesthetics) + pixelSpacing = size / np.log(size) + optimalTickCount = max(2., size / pixelSpacing) + + ## optimal minor tick spacing + optimalSpacing = dif / optimalTickCount + + ## the largest power-of-10 spacing which is smaller than optimal + p10unit = 10 ** np.floor(np.log10(optimalSpacing)) + + ## Determine major/minor tick spacings which flank the optimal spacing. + intervals = np.array([1., 2., 10., 20., 100.]) * p10unit + minorIndex = 0 + while intervals[minorIndex+1] <= optimalSpacing: + minorIndex += 1 + + levels = [ + (intervals[minorIndex+2], 0), + (intervals[minorIndex+1], 0), + #(intervals[minorIndex], 0) ## Pretty, but eats up CPU + ] + + ## decide whether to include the last level of ticks + minSpacing = min(size / 20., 30.) + maxTickCount = size / minSpacing + if dif / intervals[minorIndex] <= maxTickCount: + levels.append((intervals[minorIndex], 0)) + return levels + + + + ##### This does not work -- switching between 2/5 confuses the automatic text-level-selection + ### Determine major/minor tick spacings which flank the optimal spacing. + #intervals = np.array([1., 2., 5., 10., 20., 50., 100.]) * p10unit + #minorIndex = 0 + #while intervals[minorIndex+1] <= optimalSpacing: + #minorIndex += 1 + + ### make sure we never see 5 and 2 at the same time + #intIndexes = [ + #[0,1,3], + #[0,2,3], + #[2,3,4], + #[3,4,6], + #[3,5,6], + #][minorIndex] + + #return [ + #(intervals[intIndexes[2]], 0), + #(intervals[intIndexes[1]], 0), + #(intervals[intIndexes[0]], 0) + #] + + + + def tickValues(self, minVal, maxVal, size): + """ + Return the values and spacing of ticks to draw:: + + [ + (spacing, [major ticks]), + (spacing, [minor ticks]), + ... + ] + + By default, this method calls tickSpacing to determine the correct tick locations. + This is a good method to override in subclasses. + """ + minVal, maxVal = sorted((minVal, maxVal)) + + + minVal *= self.scale + maxVal *= self.scale + #size *= self.scale + + ticks = [] + tickLevels = self.tickSpacing(minVal, maxVal, size) + allValues = np.array([]) + for i in range(len(tickLevels)): + spacing, offset = tickLevels[i] + + ## determine starting tick + start = (np.ceil((minVal-offset) / spacing) * spacing) + offset + + ## determine number of ticks + num = int((maxVal-start) / spacing) + 1 + values = (np.arange(num) * spacing + start) / self.scale + ## remove any ticks that were present in higher levels + ## we assume here that if the difference between a tick value and a previously seen tick value + ## is less than spacing/100, then they are 'equal' and we can ignore the new tick. + values = list(filter(lambda x: all(np.abs(allValues-x) > spacing*0.01), values) ) + allValues = np.concatenate([allValues, values]) + ticks.append((spacing/self.scale, values)) + + if self.logMode: + return self.logTickValues(minVal, maxVal, size, ticks) + + + #nticks = [] + #for t in ticks: + #nvals = [] + #for v in t[1]: + #nvals.append(v/self.scale) + #nticks.append((t[0]/self.scale,nvals)) + #ticks = nticks + + return ticks + + def logTickValues(self, minVal, maxVal, size, stdTicks): + + ## start with the tick spacing given by tickValues(). + ## Any level whose spacing is < 1 needs to be converted to log scale + + ticks = [] + for (spacing, t) in stdTicks: + if spacing >= 1.0: + ticks.append((spacing, t)) + + if len(ticks) < 3: + v1 = int(np.floor(minVal)) + v2 = int(np.ceil(maxVal)) + #major = list(range(v1+1, v2)) + + minor = [] + for v in range(v1, v2): + minor.extend(v + np.log10(np.arange(1, 10))) + minor = [x for x in minor if x>minVal and x= 10000: + vstr = "%g" % vs + else: + vstr = ("%%0.%df" % places) % vs + strings.append(vstr) + return strings + + def logTickStrings(self, values, scale, spacing): + return ["%0.1g"%x for x in 10 ** np.array(values).astype(float)] + + def generateDrawSpecs(self, p): + """ + Calls tickValues() and tickStrings to determine where and how ticks should + be drawn, then generates from this a set of drawing commands to be + interpreted by drawPicture(). + """ + prof = debug.Profiler("AxisItem.generateDrawSpecs", disabled=True) + + #bounds = self.boundingRect() + bounds = self.mapRectFromParent(self.geometry()) + + linkedView = self.linkedView() + if linkedView is None or self.grid is False: + tickBounds = bounds + else: + tickBounds = linkedView.mapRectToItem(self, linkedView.boundingRect()) + + if self.orientation == 'left': + span = (bounds.topRight(), bounds.bottomRight()) + tickStart = tickBounds.right() + tickStop = bounds.right() + tickDir = -1 + axis = 0 + elif self.orientation == 'right': + span = (bounds.topLeft(), bounds.bottomLeft()) + tickStart = tickBounds.left() + tickStop = bounds.left() + tickDir = 1 + axis = 0 + elif self.orientation == 'top': + span = (bounds.bottomLeft(), bounds.bottomRight()) + tickStart = tickBounds.bottom() + tickStop = bounds.bottom() + tickDir = -1 + axis = 1 + elif self.orientation == 'bottom': + span = (bounds.topLeft(), bounds.topRight()) + tickStart = tickBounds.top() + tickStop = bounds.top() + tickDir = 1 + axis = 1 + #print tickStart, tickStop, span + + ## determine size of this item in pixels + points = list(map(self.mapToDevice, span)) + if None in points: + return + lengthInPixels = Point(points[1] - points[0]).length() + if lengthInPixels == 0: + return + + if self._tickLevels is None: + tickLevels = self.tickValues(self.range[0], self.range[1], lengthInPixels) + tickStrings = None + else: + ## parse self.tickLevels into the formats returned by tickLevels() and tickStrings() + tickLevels = [] + tickStrings = [] + for level in self._tickLevels: + values = [] + strings = [] + tickLevels.append((None, values)) + tickStrings.append(strings) + for val, strn in level: + values.append(val) + strings.append(strn) + + textLevel = 1 ## draw text at this scale level + + ## determine mapping between tick values and local coordinates + dif = self.range[1] - self.range[0] + if dif == 0: + xscale = 1 + offset = 0 + else: + if axis == 0: + xScale = -bounds.height() / dif + offset = self.range[0] * xScale - bounds.height() + else: + xScale = bounds.width() / dif + offset = self.range[0] * xScale + + xRange = [x * xScale - offset for x in self.range] + xMin = min(xRange) + xMax = max(xRange) + + prof.mark('init') + + tickPositions = [] # remembers positions of previously drawn ticks + + ## draw ticks + ## (to improve performance, we do not interleave line and text drawing, since this causes unnecessary pipeline switching) + ## draw three different intervals, long ticks first + tickSpecs = [] + for i in range(len(tickLevels)): + tickPositions.append([]) + ticks = tickLevels[i][1] + + ## length of tick + tickLength = self.tickLength / ((i*0.5)+1.0) + + lineAlpha = 255 / (i+1) + if self.grid is not False: + lineAlpha *= self.grid/255. * np.clip((0.05 * lengthInPixels / (len(ticks)+1)), 0., 1.) + + for v in ticks: + ## determine actual position to draw this tick + x = (v * xScale) - offset + if x < xMin or x > xMax: ## last check to make sure no out-of-bounds ticks are drawn + tickPositions[i].append(None) + continue + tickPositions[i].append(x) + + p1 = [x, x] + p2 = [x, x] + p1[axis] = tickStart + p2[axis] = tickStop + if self.grid is False: + p2[axis] += tickLength*tickDir + tickPen = self.pen() + color = tickPen.color() + color.setAlpha(lineAlpha) + tickPen.setColor(color) + tickSpecs.append((tickPen, Point(p1), Point(p2))) + prof.mark('compute ticks') + + ## This is where the long axis line should be drawn + + if self.style['stopAxisAtTick'][0] is True: + stop = max(span[0].y(), min(map(min, tickPositions))) + if axis == 0: + span[0].setY(stop) + else: + span[0].setX(stop) + if self.style['stopAxisAtTick'][1] is True: + stop = min(span[1].y(), max(map(max, tickPositions))) + if axis == 0: + span[1].setY(stop) + else: + span[1].setX(stop) + axisSpec = (self.pen(), span[0], span[1]) + + + + textOffset = self.style['tickTextOffset'][axis] ## spacing between axis and text + #if self.style['autoExpandTextSpace'] is True: + #textWidth = self.textWidth + #textHeight = self.textHeight + #else: + #textWidth = self.style['tickTextWidth'] ## space allocated for horizontal text + #textHeight = self.style['tickTextHeight'] ## space allocated for horizontal text + + textSize2 = 0 + textRects = [] + textSpecs = [] ## list of draw + textSize2 = 0 + for i in range(len(tickLevels)): + ## Get the list of strings to display for this level + if tickStrings is None: + spacing, values = tickLevels[i] + strings = self.tickStrings(values, self.autoSIPrefixScale * self.scale, spacing) + else: + strings = tickStrings[i] + + if len(strings) == 0: + continue + + ## ignore strings belonging to ticks that were previously ignored + for j in range(len(strings)): + if tickPositions[i][j] is None: + strings[j] = None + + ## Measure density of text; decide whether to draw this level + rects = [] + for s in strings: + if s is None: + rects.append(None) + else: + br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, str(s)) + ## boundingRect is usually just a bit too large + ## (but this probably depends on per-font metrics?) + br.setHeight(br.height() * 0.8) + + rects.append(br) + textRects.append(rects[-1]) + + if i > 0: ## always draw top level + ## measure all text, make sure there's enough room + if axis == 0: + textSize = np.sum([r.height() for r in textRects]) + textSize2 = np.max([r.width() for r in textRects]) + else: + textSize = np.sum([r.width() for r in textRects]) + textSize2 = np.max([r.height() for r in textRects]) + + ## If the strings are too crowded, stop drawing text now. + ## We use three different crowding limits based on the number + ## of texts drawn so far. + textFillRatio = float(textSize) / lengthInPixels + finished = False + for nTexts, limit in self.style['textFillLimits']: + if len(textSpecs) >= nTexts and textFillRatio >= limit: + finished = True + break + if finished: + break + + #spacing, values = tickLevels[best] + #strings = self.tickStrings(values, self.scale, spacing) + for j in range(len(strings)): + vstr = strings[j] + if vstr is None: ## this tick was ignored because it is out of bounds + continue + vstr = str(vstr) + x = tickPositions[i][j] + #textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, vstr) + textRect = rects[j] + height = textRect.height() + width = textRect.width() + #self.textHeight = height + offset = max(0,self.tickLength) + textOffset + if self.orientation == 'left': + textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter + rect = QtCore.QRectF(tickStop-offset-width, x-(height/2), width, height) + elif self.orientation == 'right': + textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter + rect = QtCore.QRectF(tickStop+offset, x-(height/2), width, height) + elif self.orientation == 'top': + textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignCenter|QtCore.Qt.AlignBottom + rect = QtCore.QRectF(x-width/2., tickStop-offset-height, width, height) + elif self.orientation == 'bottom': + textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignCenter|QtCore.Qt.AlignTop + rect = QtCore.QRectF(x-width/2., tickStop+offset, width, height) + + #p.setPen(self.pen()) + #p.drawText(rect, textFlags, vstr) + textSpecs.append((rect, textFlags, vstr)) + prof.mark('compute text') + + ## update max text size if needed. + self._updateMaxTextSize(textSize2) + + return (axisSpec, tickSpecs, textSpecs) + + def drawPicture(self, p, axisSpec, tickSpecs, textSpecs): + prof = debug.Profiler("AxisItem.drawPicture", disabled=True) + + p.setRenderHint(p.Antialiasing, False) + p.setRenderHint(p.TextAntialiasing, True) + + ## draw long line along axis + pen, p1, p2 = axisSpec + p.setPen(pen) + p.drawLine(p1, p2) + p.translate(0.5,0) ## resolves some damn pixel ambiguity + + ## draw ticks + for pen, p1, p2 in tickSpecs: + p.setPen(pen) + p.drawLine(p1, p2) + prof.mark('draw ticks') + + ## Draw all text + if self.tickFont is not None: + p.setFont(self.tickFont) + p.setPen(self.pen()) + for rect, flags, text in textSpecs: + p.drawText(rect, flags, text) + #p.drawRect(rect) + + prof.mark('draw text') + prof.finish() + + def show(self): + + if self.orientation in ['left', 'right']: + self.setWidth() + else: + self.setHeight() + GraphicsWidget.show(self) + + def hide(self): + if self.orientation in ['left', 'right']: + self.setWidth(0) + else: + self.setHeight(0) + GraphicsWidget.hide(self) + + def wheelEvent(self, ev): + if self.linkedView() is None: + return + if self.orientation in ['left', 'right']: + self.linkedView().wheelEvent(ev, axis=1) + else: + self.linkedView().wheelEvent(ev, axis=0) + ev.accept() + + def mouseDragEvent(self, event): + if self.linkedView() is None: + return + if self.orientation in ['left', 'right']: + return self.linkedView().mouseDragEvent(event, axis=1) + else: + return self.linkedView().mouseDragEvent(event, axis=0) + + def mouseClickEvent(self, event): + if self.linkedView() is None: + return + return self.linkedView().mouseClickEvent(event) diff --git a/pyqtgraph/graphicsItems/BarGraphItem.py b/pyqtgraph/graphicsItems/BarGraphItem.py new file mode 100644 index 00000000..0527e9f1 --- /dev/null +++ b/pyqtgraph/graphicsItems/BarGraphItem.py @@ -0,0 +1,149 @@ +import pyqtgraph as pg +from pyqtgraph.Qt import QtGui, QtCore +from .GraphicsObject import GraphicsObject +import numpy as np + +__all__ = ['BarGraphItem'] + +class BarGraphItem(GraphicsObject): + def __init__(self, **opts): + """ + Valid keyword options are: + x, x0, x1, y, y0, y1, width, height, pen, brush + + x specifies the x-position of the center of the bar. + x0, x1 specify left and right edges of the bar, respectively. + width specifies distance from x0 to x1. + You may specify any combination: + + x, width + x0, width + x1, width + x0, x1 + + Likewise y, y0, y1, and height. + If only height is specified, then y0 will be set to 0 + + Example uses: + + BarGraphItem(x=range(5), height=[1,5,2,4,3], width=0.5) + + + """ + GraphicsObject.__init__(self) + self.opts = dict( + x=None, + y=None, + x0=None, + y0=None, + x1=None, + y1=None, + height=None, + width=None, + pen=None, + brush=None, + pens=None, + brushes=None, + ) + self.setOpts(**opts) + + def setOpts(self, **opts): + self.opts.update(opts) + self.picture = None + self.update() + self.informViewBoundsChanged() + + def drawPicture(self): + self.picture = QtGui.QPicture() + p = QtGui.QPainter(self.picture) + + pen = self.opts['pen'] + pens = self.opts['pens'] + + if pen is None and pens is None: + pen = pg.getConfigOption('foreground') + + brush = self.opts['brush'] + brushes = self.opts['brushes'] + if brush is None and brushes is None: + brush = (128, 128, 128) + + def asarray(x): + if x is None or np.isscalar(x) or isinstance(x, np.ndarray): + return x + return np.array(x) + + + x = asarray(self.opts.get('x')) + x0 = asarray(self.opts.get('x0')) + x1 = asarray(self.opts.get('x1')) + width = asarray(self.opts.get('width')) + + if x0 is None: + if width is None: + raise Exception('must specify either x0 or width') + if x1 is not None: + x0 = x1 - width + elif x is not None: + x0 = x - width/2. + else: + raise Exception('must specify at least one of x, x0, or x1') + if width is None: + if x1 is None: + raise Exception('must specify either x1 or width') + width = x1 - x0 + + y = asarray(self.opts.get('y')) + y0 = asarray(self.opts.get('y0')) + y1 = asarray(self.opts.get('y1')) + height = asarray(self.opts.get('height')) + + if y0 is None: + if height is None: + y0 = 0 + elif y1 is not None: + y0 = y1 - height + elif y is not None: + y0 = y - height/2. + else: + y0 = 0 + if height is None: + if y1 is None: + raise Exception('must specify either y1 or height') + height = y1 - y0 + + p.setPen(pg.mkPen(pen)) + p.setBrush(pg.mkBrush(brush)) + for i in range(len(x0)): + if pens is not None: + p.setPen(pg.mkPen(pens[i])) + if brushes is not None: + p.setBrush(pg.mkBrush(brushes[i])) + + if np.isscalar(y0): + y = y0 + else: + y = y0[i] + if np.isscalar(width): + w = width + else: + w = width[i] + + p.drawRect(QtCore.QRectF(x0[i], y, w, height[i])) + + + p.end() + self.prepareGeometryChange() + + + def paint(self, p, *args): + if self.picture is None: + self.drawPicture() + self.picture.play(p) + + def boundingRect(self): + if self.picture is None: + self.drawPicture() + return QtCore.QRectF(self.picture.boundingRect()) + + \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/ButtonItem.py b/pyqtgraph/graphicsItems/ButtonItem.py new file mode 100644 index 00000000..741f2666 --- /dev/null +++ b/pyqtgraph/graphicsItems/ButtonItem.py @@ -0,0 +1,58 @@ +from pyqtgraph.Qt import QtGui, QtCore +from .GraphicsObject import GraphicsObject + +__all__ = ['ButtonItem'] +class ButtonItem(GraphicsObject): + """Button graphicsItem displaying an image.""" + + clicked = QtCore.Signal(object) + + def __init__(self, imageFile=None, width=None, parentItem=None, pixmap=None): + self.enabled = True + GraphicsObject.__init__(self) + if imageFile is not None: + self.setImageFile(imageFile) + elif pixmap is not None: + self.setPixmap(pixmap) + + if width is not None: + s = float(width) / self.pixmap.width() + self.scale(s, s) + if parentItem is not None: + self.setParentItem(parentItem) + self.setOpacity(0.7) + + def setImageFile(self, imageFile): + self.setPixmap(QtGui.QPixmap(imageFile)) + + def setPixmap(self, pixmap): + self.pixmap = pixmap + self.update() + + def mouseClickEvent(self, ev): + if self.enabled: + self.clicked.emit(self) + + def mouseHoverEvent(self, ev): + if not self.enabled: + return + if ev.isEnter(): + self.setOpacity(1.0) + else: + self.setOpacity(0.7) + + def disable(self): + self.enabled = False + self.setOpacity(0.4) + + def enable(self): + self.enabled = True + self.setOpacity(0.7) + + def paint(self, p, *args): + p.setRenderHint(p.Antialiasing) + p.drawPixmap(0, 0, self.pixmap) + + def boundingRect(self): + return QtCore.QRectF(self.pixmap.rect()) + diff --git a/pyqtgraph/graphicsItems/CurvePoint.py b/pyqtgraph/graphicsItems/CurvePoint.py new file mode 100644 index 00000000..668830f7 --- /dev/null +++ b/pyqtgraph/graphicsItems/CurvePoint.py @@ -0,0 +1,117 @@ +from pyqtgraph.Qt import QtGui, QtCore +from . import ArrowItem +import numpy as np +from pyqtgraph.Point import Point +import weakref +from .GraphicsObject import GraphicsObject + +__all__ = ['CurvePoint', 'CurveArrow'] +class CurvePoint(GraphicsObject): + """A GraphicsItem that sets its location to a point on a PlotCurveItem. + Also rotates to be tangent to the curve. + The position along the curve is a Qt property, and thus can be easily animated. + + Note: This class does not display anything; see CurveArrow for an applied example + """ + + def __init__(self, curve, index=0, pos=None, rotate=True): + """Position can be set either as an index referring to the sample number or + the position 0.0 - 1.0 + If *rotate* is True, then the item rotates to match the tangent of the curve. + """ + + GraphicsObject.__init__(self) + #QObjectWorkaround.__init__(self) + self._rotate = rotate + self.curve = weakref.ref(curve) + self.setParentItem(curve) + self.setProperty('position', 0.0) + self.setProperty('index', 0) + + if hasattr(self, 'ItemHasNoContents'): + self.setFlags(self.flags() | self.ItemHasNoContents) + + if pos is not None: + self.setPos(pos) + else: + self.setIndex(index) + + def setPos(self, pos): + self.setProperty('position', float(pos))## cannot use numpy types here, MUST be python float. + + def setIndex(self, index): + self.setProperty('index', int(index)) ## cannot use numpy types here, MUST be python int. + + def event(self, ev): + if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None: + return False + + if ev.propertyName() == 'index': + index = self.property('index') + if 'QVariant' in repr(index): + index = index.toInt()[0] + elif ev.propertyName() == 'position': + index = None + else: + return False + + (x, y) = self.curve().getData() + if index is None: + #print ev.propertyName(), self.property('position').toDouble()[0], self.property('position').typeName() + pos = self.property('position') + if 'QVariant' in repr(pos): ## need to support 2 APIs :( + pos = pos.toDouble()[0] + index = (len(x)-1) * np.clip(pos, 0.0, 1.0) + + if index != int(index): ## interpolate floating-point values + i1 = int(index) + i2 = np.clip(i1+1, 0, len(x)-1) + s2 = index-i1 + s1 = 1.0-s2 + newPos = (x[i1]*s1+x[i2]*s2, y[i1]*s1+y[i2]*s2) + else: + index = int(index) + i1 = np.clip(index-1, 0, len(x)-1) + i2 = np.clip(index+1, 0, len(x)-1) + newPos = (x[index], y[index]) + + p1 = self.parentItem().mapToScene(QtCore.QPointF(x[i1], y[i1])) + p2 = self.parentItem().mapToScene(QtCore.QPointF(x[i2], y[i2])) + ang = np.arctan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians + self.resetTransform() + if self._rotate: + self.rotate(180+ ang * 180 / np.pi) ## takes degrees + QtGui.QGraphicsItem.setPos(self, *newPos) + return True + + def boundingRect(self): + return QtCore.QRectF() + + def paint(self, *args): + pass + + def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1): + anim = QtCore.QPropertyAnimation(self, prop) + anim.setDuration(duration) + anim.setStartValue(start) + anim.setEndValue(end) + anim.setLoopCount(loop) + return anim + + +class CurveArrow(CurvePoint): + """Provides an arrow that points to any specific sample on a PlotCurveItem. + Provides properties that can be animated.""" + + def __init__(self, curve, index=0, pos=None, **opts): + CurvePoint.__init__(self, curve, index=index, pos=pos) + if opts.get('pxMode', True): + opts['pxMode'] = False + self.setFlags(self.flags() | self.ItemIgnoresTransformations) + opts['angle'] = 0 + self.arrow = ArrowItem.ArrowItem(**opts) + self.arrow.setParentItem(self) + + def setStyle(**opts): + return self.arrow.setStyle(**opts) + diff --git a/pyqtgraph/graphicsItems/ErrorBarItem.py b/pyqtgraph/graphicsItems/ErrorBarItem.py new file mode 100644 index 00000000..656b9e2e --- /dev/null +++ b/pyqtgraph/graphicsItems/ErrorBarItem.py @@ -0,0 +1,133 @@ +import pyqtgraph as pg +from pyqtgraph.Qt import QtGui, QtCore +from .GraphicsObject import GraphicsObject + +__all__ = ['ErrorBarItem'] + +class ErrorBarItem(GraphicsObject): + def __init__(self, **opts): + """ + Valid keyword options are: + x, y, height, width, top, bottom, left, right, beam, pen + + x and y must be numpy arrays specifying the coordinates of data points. + height, width, top, bottom, left, right, and beam may be numpy arrays, + single values, or None to disable. All values should be positive. + + If height is specified, it overrides top and bottom. + If width is specified, it overrides left and right. + """ + GraphicsObject.__init__(self) + self.opts = dict( + x=None, + y=None, + height=None, + width=None, + top=None, + bottom=None, + left=None, + right=None, + beam=None, + pen=None + ) + self.setOpts(**opts) + + def setOpts(self, **opts): + self.opts.update(opts) + self.path = None + self.update() + self.informViewBoundsChanged() + + def drawPath(self): + p = QtGui.QPainterPath() + + x, y = self.opts['x'], self.opts['y'] + if x is None or y is None: + return + + beam = self.opts['beam'] + + + height, top, bottom = self.opts['height'], self.opts['top'], self.opts['bottom'] + if height is not None or top is not None or bottom is not None: + ## draw vertical error bars + if height is not None: + y1 = y - height/2. + y2 = y + height/2. + else: + if bottom is None: + y1 = y + else: + y1 = y - bottom + if top is None: + y2 = y + else: + y2 = y + top + + for i in range(len(x)): + p.moveTo(x[i], y1[i]) + p.lineTo(x[i], y2[i]) + + if beam is not None and beam > 0: + x1 = x - beam/2. + x2 = x + beam/2. + if height is not None or top is not None: + for i in range(len(x)): + p.moveTo(x1[i], y2[i]) + p.lineTo(x2[i], y2[i]) + if height is not None or bottom is not None: + for i in range(len(x)): + p.moveTo(x1[i], y1[i]) + p.lineTo(x2[i], y1[i]) + + width, right, left = self.opts['width'], self.opts['right'], self.opts['left'] + if width is not None or right is not None or left is not None: + ## draw vertical error bars + if width is not None: + x1 = x - width/2. + x2 = x + width/2. + else: + if left is None: + x1 = x + else: + x1 = x - left + if right is None: + x2 = x + else: + x2 = x + right + + for i in range(len(x)): + p.moveTo(x1[i], y[i]) + p.lineTo(x2[i], y[i]) + + if beam is not None and beam > 0: + y1 = y - beam/2. + y2 = y + beam/2. + if width is not None or right is not None: + for i in range(len(x)): + p.moveTo(x2[i], y1[i]) + p.lineTo(x2[i], y2[i]) + if width is not None or left is not None: + for i in range(len(x)): + p.moveTo(x1[i], y1[i]) + p.lineTo(x1[i], y2[i]) + + self.path = p + self.prepareGeometryChange() + + + def paint(self, p, *args): + if self.path is None: + self.drawPath() + pen = self.opts['pen'] + if pen is None: + pen = pg.getConfigOption('foreground') + p.setPen(pg.mkPen(pen)) + p.drawPath(self.path) + + def boundingRect(self): + if self.path is None: + self.drawPath() + return self.path.boundingRect() + + \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/FillBetweenItem.py b/pyqtgraph/graphicsItems/FillBetweenItem.py new file mode 100644 index 00000000..e0011177 --- /dev/null +++ b/pyqtgraph/graphicsItems/FillBetweenItem.py @@ -0,0 +1,23 @@ +import pyqtgraph as pg + +class FillBetweenItem(pg.QtGui.QGraphicsPathItem): + """ + GraphicsItem filling the space between two PlotDataItems. + """ + def __init__(self, p1, p2, brush=None): + pg.QtGui.QGraphicsPathItem.__init__(self) + self.p1 = p1 + self.p2 = p2 + p1.sigPlotChanged.connect(self.updatePath) + p2.sigPlotChanged.connect(self.updatePath) + if brush is not None: + self.setBrush(pg.mkBrush(brush)) + self.setZValue(min(p1.zValue(), p2.zValue())-1) + self.updatePath() + + def updatePath(self): + p1 = self.p1.curve.path + p2 = self.p2.curve.path + path = pg.QtGui.QPainterPath() + path.addPolygon(p1.toSubpathPolygons()[0] + p2.toReversed().toSubpathPolygons()[0]) + self.setPath(path) diff --git a/pyqtgraph/graphicsItems/GradientEditorItem.py b/pyqtgraph/graphicsItems/GradientEditorItem.py new file mode 100644 index 00000000..955106d8 --- /dev/null +++ b/pyqtgraph/graphicsItems/GradientEditorItem.py @@ -0,0 +1,910 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.python2_3 import sortList +import pyqtgraph.functions as fn +from .GraphicsObject import GraphicsObject +from .GraphicsWidget import GraphicsWidget +import weakref +from pyqtgraph.pgcollections import OrderedDict +from pyqtgraph.colormap import ColorMap + +import numpy as np + +__all__ = ['TickSliderItem', 'GradientEditorItem'] + + +Gradients = OrderedDict([ + ('thermal', {'ticks': [(0.3333, (185, 0, 0, 255)), (0.6666, (255, 220, 0, 255)), (1, (255, 255, 255, 255)), (0, (0, 0, 0, 255))], 'mode': 'rgb'}), + ('flame', {'ticks': [(0.2, (7, 0, 220, 255)), (0.5, (236, 0, 134, 255)), (0.8, (246, 246, 0, 255)), (1.0, (255, 255, 255, 255)), (0.0, (0, 0, 0, 255))], 'mode': 'rgb'}), + ('yellowy', {'ticks': [(0.0, (0, 0, 0, 255)), (0.2328863796753704, (32, 0, 129, 255)), (0.8362738179251941, (255, 255, 0, 255)), (0.5257586450247, (115, 15, 255, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'} ), + ('bipolar', {'ticks': [(0.0, (0, 255, 255, 255)), (1.0, (255, 255, 0, 255)), (0.5, (0, 0, 0, 255)), (0.25, (0, 0, 255, 255)), (0.75, (255, 0, 0, 255))], 'mode': 'rgb'}), + ('spectrum', {'ticks': [(1.0, (255, 0, 255, 255)), (0.0, (255, 0, 0, 255))], 'mode': 'hsv'}), + ('cyclic', {'ticks': [(0.0, (255, 0, 4, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'hsv'}), + ('greyclip', {'ticks': [(0.0, (0, 0, 0, 255)), (0.99, (255, 255, 255, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'rgb'}), + ('grey', {'ticks': [(0.0, (0, 0, 0, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'}), +]) + + + + + +class TickSliderItem(GraphicsWidget): + ## public class + """**Bases:** :class:`GraphicsWidget ` + + A rectangular item with tick marks along its length that can (optionally) be moved by the user.""" + + def __init__(self, orientation='bottom', allowAdd=True, **kargs): + """ + ============= ================================================================================= + **Arguments** + orientation Set the orientation of the gradient. Options are: 'left', 'right' + 'top', and 'bottom'. + allowAdd Specifies whether ticks can be added to the item by the user. + tickPen Default is white. Specifies the color of the outline of the ticks. + Can be any of the valid arguments for :func:`mkPen ` + ============= ================================================================================= + """ + ## public + GraphicsWidget.__init__(self) + self.orientation = orientation + self.length = 100 + self.tickSize = 15 + self.ticks = {} + self.maxDim = 20 + self.allowAdd = allowAdd + if 'tickPen' in kargs: + self.tickPen = fn.mkPen(kargs['tickPen']) + else: + self.tickPen = fn.mkPen('w') + + self.orientations = { + 'left': (90, 1, 1), + 'right': (90, 1, 1), + 'top': (0, 1, -1), + 'bottom': (0, 1, 1) + } + + self.setOrientation(orientation) + #self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) + #self.setBackgroundRole(QtGui.QPalette.NoRole) + #self.setMouseTracking(True) + + #def boundingRect(self): + #return self.mapRectFromParent(self.geometry()).normalized() + + #def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. + #p = QtGui.QPainterPath() + #p.addRect(self.boundingRect()) + #return p + + def paint(self, p, opt, widget): + #p.setPen(fn.mkPen('g', width=3)) + #p.drawRect(self.boundingRect()) + return + + def keyPressEvent(self, ev): + ev.ignore() + + def setMaxDim(self, mx=None): + if mx is None: + mx = self.maxDim + else: + self.maxDim = mx + + if self.orientation in ['bottom', 'top']: + self.setFixedHeight(mx) + self.setMaximumWidth(16777215) + else: + self.setFixedWidth(mx) + self.setMaximumHeight(16777215) + + + def setOrientation(self, orientation): + ## public + """Set the orientation of the TickSliderItem. + + ============= =================================================================== + **Arguments** + orientation Options are: 'left', 'right', 'top', 'bottom' + The orientation option specifies which side of the slider the + ticks are on, as well as whether the slider is vertical ('right' + and 'left') or horizontal ('top' and 'bottom'). + ============= =================================================================== + """ + self.orientation = orientation + self.setMaxDim() + self.resetTransform() + ort = orientation + if ort == 'top': + self.scale(1, -1) + self.translate(0, -self.height()) + elif ort == 'left': + self.rotate(270) + self.scale(1, -1) + self.translate(-self.height(), -self.maxDim) + elif ort == 'right': + self.rotate(270) + self.translate(-self.height(), 0) + #self.setPos(0, -self.height()) + elif ort != 'bottom': + raise Exception("%s is not a valid orientation. Options are 'left', 'right', 'top', and 'bottom'" %str(ort)) + + self.translate(self.tickSize/2., 0) + + def addTick(self, x, color=None, movable=True): + ## public + """ + Add a tick to the item. + + ============= ================================================================== + **Arguments** + x Position where tick should be added. + color Color of added tick. If color is not specified, the color will be + white. + movable Specifies whether the tick is movable with the mouse. + ============= ================================================================== + """ + + if color is None: + color = QtGui.QColor(255,255,255) + tick = Tick(self, [x*self.length, 0], color, movable, self.tickSize, pen=self.tickPen) + self.ticks[tick] = x + tick.setParentItem(self) + return tick + + def removeTick(self, tick): + ## public + """ + Removes the specified tick. + """ + del self.ticks[tick] + tick.setParentItem(None) + if self.scene() is not None: + self.scene().removeItem(tick) + + def tickMoved(self, tick, pos): + #print "tick changed" + ## Correct position of tick if it has left bounds. + newX = min(max(0, pos.x()), self.length) + pos.setX(newX) + tick.setPos(pos) + self.ticks[tick] = float(newX) / self.length + + def tickMoveFinished(self, tick): + pass + + def tickClicked(self, tick, ev): + if ev.button() == QtCore.Qt.RightButton: + self.removeTick(tick) + + def widgetLength(self): + if self.orientation in ['bottom', 'top']: + return self.width() + else: + return self.height() + + def resizeEvent(self, ev): + wlen = max(40, self.widgetLength()) + self.setLength(wlen-self.tickSize-2) + self.setOrientation(self.orientation) + #bounds = self.scene().itemsBoundingRect() + #bounds.setLeft(min(-self.tickSize*0.5, bounds.left())) + #bounds.setRight(max(self.length + self.tickSize, bounds.right())) + #self.setSceneRect(bounds) + #self.fitInView(bounds, QtCore.Qt.KeepAspectRatio) + + def setLength(self, newLen): + #private + for t, x in list(self.ticks.items()): + t.setPos(x * newLen + 1, t.pos().y()) + self.length = float(newLen) + + #def mousePressEvent(self, ev): + #QtGui.QGraphicsView.mousePressEvent(self, ev) + #self.ignoreRelease = False + #for i in self.items(ev.pos()): + #if isinstance(i, Tick): + #self.ignoreRelease = True + #break + ##if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks + ##self.ignoreRelease = True + + #def mouseReleaseEvent(self, ev): + #QtGui.QGraphicsView.mouseReleaseEvent(self, ev) + #if self.ignoreRelease: + #return + + #pos = self.mapToScene(ev.pos()) + + #if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: + #if pos.x() < 0 or pos.x() > self.length: + #return + #if pos.y() < 0 or pos.y() > self.tickSize: + #return + #pos.setX(min(max(pos.x(), 0), self.length)) + #self.addTick(pos.x()/self.length) + #elif ev.button() == QtCore.Qt.RightButton: + #self.showMenu(ev) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: + pos = ev.pos() + if pos.x() < 0 or pos.x() > self.length: + return + if pos.y() < 0 or pos.y() > self.tickSize: + return + pos.setX(min(max(pos.x(), 0), self.length)) + self.addTick(pos.x()/self.length) + elif ev.button() == QtCore.Qt.RightButton: + self.showMenu(ev) + + #if ev.button() == QtCore.Qt.RightButton: + #if self.moving: + #ev.accept() + #self.setPos(self.startPosition) + #self.moving = False + #self.sigMoving.emit(self) + #self.sigMoved.emit(self) + #else: + #pass + #self.view().tickClicked(self, ev) + ###remove + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.RightButton) + ## show ghost tick + #self.currentPen = fn.mkPen(255, 0,0) + #else: + #self.currentPen = self.pen + #self.update() + + def showMenu(self, ev): + pass + + def setTickColor(self, tick, color): + """Set the color of the specified tick. + + ============= ================================================================== + **Arguments** + tick Can be either an integer corresponding to the index of the tick + or a Tick object. Ex: if you had a slider with 3 ticks and you + wanted to change the middle tick, the index would be 1. + color The color to make the tick. Can be any argument that is valid for + :func:`mkBrush ` + ============= ================================================================== + """ + tick = self.getTick(tick) + tick.color = color + tick.update() + #tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color))) + + def setTickValue(self, tick, val): + ## public + """ + Set the position (along the slider) of the tick. + + ============= ================================================================== + **Arguments** + tick Can be either an integer corresponding to the index of the tick + or a Tick object. Ex: if you had a slider with 3 ticks and you + wanted to change the middle tick, the index would be 1. + val The desired position of the tick. If val is < 0, position will be + set to 0. If val is > 1, position will be set to 1. + ============= ================================================================== + """ + tick = self.getTick(tick) + val = min(max(0.0, val), 1.0) + x = val * self.length + pos = tick.pos() + pos.setX(x) + tick.setPos(pos) + self.ticks[tick] = val + + def tickValue(self, tick): + ## public + """Return the value (from 0.0 to 1.0) of the specified tick. + + ============= ================================================================== + **Arguments** + tick Can be either an integer corresponding to the index of the tick + or a Tick object. Ex: if you had a slider with 3 ticks and you + wanted the value of the middle tick, the index would be 1. + ============= ================================================================== + """ + tick = self.getTick(tick) + return self.ticks[tick] + + def getTick(self, tick): + ## public + """Return the Tick object at the specified index. + + ============= ================================================================== + **Arguments** + tick An integer corresponding to the index of the desired tick. If the + argument is not an integer it will be returned unchanged. + ============= ================================================================== + """ + if type(tick) is int: + tick = self.listTicks()[tick][0] + return tick + + #def mouseMoveEvent(self, ev): + #QtGui.QGraphicsView.mouseMoveEvent(self, ev) + + def listTicks(self): + """Return a sorted list of all the Tick objects on the slider.""" + ## public + ticks = list(self.ticks.items()) + sortList(ticks, lambda a,b: cmp(a[1], b[1])) ## see pyqtgraph.python2_3.sortList + return ticks + + +class GradientEditorItem(TickSliderItem): + """ + **Bases:** :class:`TickSliderItem ` + + An item that can be used to define a color gradient. Implements common pre-defined gradients that are + customizable by the user. :class: `GradientWidget ` provides a widget + with a GradientEditorItem that can be added to a GUI. + + ================================ =========================================================== + **Signals** + sigGradientChanged(self) Signal is emitted anytime the gradient changes. The signal + is emitted in real time while ticks are being dragged or + colors are being changed. + sigGradientChangeFinished(self) Signal is emitted when the gradient is finished changing. + ================================ =========================================================== + + """ + + sigGradientChanged = QtCore.Signal(object) + sigGradientChangeFinished = QtCore.Signal(object) + + def __init__(self, *args, **kargs): + """ + Create a new GradientEditorItem. + All arguments are passed to :func:`TickSliderItem.__init__ ` + + ============= ================================================================================= + **Arguments** + orientation Set the orientation of the gradient. Options are: 'left', 'right' + 'top', and 'bottom'. + allowAdd Default is True. Specifies whether ticks can be added to the item. + tickPen Default is white. Specifies the color of the outline of the ticks. + Can be any of the valid arguments for :func:`mkPen ` + ============= ================================================================================= + """ + self.currentTick = None + self.currentTickColor = None + self.rectSize = 15 + self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, self.rectSize, 100, self.rectSize)) + self.backgroundRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize)) + self.backgroundRect.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) + self.colorMode = 'rgb' + + TickSliderItem.__init__(self, *args, **kargs) + + self.colorDialog = QtGui.QColorDialog() + self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) + self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) + + self.colorDialog.currentColorChanged.connect(self.currentColorChanged) + self.colorDialog.rejected.connect(self.currentColorRejected) + self.colorDialog.accepted.connect(self.currentColorAccepted) + + self.backgroundRect.setParentItem(self) + self.gradRect.setParentItem(self) + + self.setMaxDim(self.rectSize + self.tickSize) + + self.rgbAction = QtGui.QAction('RGB', self) + self.rgbAction.setCheckable(True) + self.rgbAction.triggered.connect(lambda: self.setColorMode('rgb')) + self.hsvAction = QtGui.QAction('HSV', self) + self.hsvAction.setCheckable(True) + self.hsvAction.triggered.connect(lambda: self.setColorMode('hsv')) + + self.menu = QtGui.QMenu() + + ## build context menu of gradients + l = self.length + self.length = 100 + global Gradients + for g in Gradients: + px = QtGui.QPixmap(100, 15) + p = QtGui.QPainter(px) + self.restoreState(Gradients[g]) + grad = self.getGradient() + brush = QtGui.QBrush(grad) + p.fillRect(QtCore.QRect(0, 0, 100, 15), brush) + p.end() + label = QtGui.QLabel() + label.setPixmap(px) + label.setContentsMargins(1, 1, 1, 1) + act = QtGui.QWidgetAction(self) + act.setDefaultWidget(label) + act.triggered.connect(self.contextMenuClicked) + act.name = g + self.menu.addAction(act) + self.length = l + self.menu.addSeparator() + self.menu.addAction(self.rgbAction) + self.menu.addAction(self.hsvAction) + + + for t in list(self.ticks.keys()): + self.removeTick(t) + self.addTick(0, QtGui.QColor(0,0,0), True) + self.addTick(1, QtGui.QColor(255,0,0), True) + self.setColorMode('rgb') + self.updateGradient() + + def setOrientation(self, orientation): + ## public + """ + Set the orientation of the GradientEditorItem. + + ============= =================================================================== + **Arguments** + orientation Options are: 'left', 'right', 'top', 'bottom' + The orientation option specifies which side of the gradient the + ticks are on, as well as whether the gradient is vertical ('right' + and 'left') or horizontal ('top' and 'bottom'). + ============= =================================================================== + """ + TickSliderItem.setOrientation(self, orientation) + self.translate(0, self.rectSize) + + def showMenu(self, ev): + #private + self.menu.popup(ev.screenPos().toQPoint()) + + def contextMenuClicked(self, b=None): + #private + #global Gradients + act = self.sender() + self.loadPreset(act.name) + + def loadPreset(self, name): + """ + Load a predefined gradient. + + """ ## TODO: provide image with names of defined gradients + #global Gradients + self.restoreState(Gradients[name]) + + def setColorMode(self, cm): + """ + Set the color mode for the gradient. Options are: 'hsv', 'rgb' + + """ + + ## public + if cm not in ['rgb', 'hsv']: + raise Exception("Unknown color mode %s. Options are 'rgb' and 'hsv'." % str(cm)) + + try: + self.rgbAction.blockSignals(True) + self.hsvAction.blockSignals(True) + self.rgbAction.setChecked(cm == 'rgb') + self.hsvAction.setChecked(cm == 'hsv') + finally: + self.rgbAction.blockSignals(False) + self.hsvAction.blockSignals(False) + self.colorMode = cm + self.updateGradient() + + def colorMap(self): + """Return a ColorMap object representing the current state of the editor.""" + if self.colorMode == 'hsv': + raise NotImplementedError('hsv colormaps not yet supported') + pos = [] + color = [] + for t,x in self.listTicks(): + pos.append(x) + c = t.color + color.append([c.red(), c.green(), c.blue(), c.alpha()]) + return ColorMap(np.array(pos), np.array(color, dtype=np.ubyte)) + + def updateGradient(self): + #private + self.gradient = self.getGradient() + self.gradRect.setBrush(QtGui.QBrush(self.gradient)) + self.sigGradientChanged.emit(self) + + def setLength(self, newLen): + #private (but maybe public) + TickSliderItem.setLength(self, newLen) + self.backgroundRect.setRect(1, -self.rectSize, newLen, self.rectSize) + self.gradRect.setRect(1, -self.rectSize, newLen, self.rectSize) + self.updateGradient() + + def currentColorChanged(self, color): + #private + if color.isValid() and self.currentTick is not None: + self.setTickColor(self.currentTick, color) + self.updateGradient() + + def currentColorRejected(self): + #private + self.setTickColor(self.currentTick, self.currentTickColor) + self.updateGradient() + + def currentColorAccepted(self): + self.sigGradientChangeFinished.emit(self) + + def tickClicked(self, tick, ev): + #private + if ev.button() == QtCore.Qt.LeftButton: + if not tick.colorChangeAllowed: + return + self.currentTick = tick + self.currentTickColor = tick.color + self.colorDialog.setCurrentColor(tick.color) + self.colorDialog.open() + #color = QtGui.QColorDialog.getColor(tick.color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) + #if color.isValid(): + #self.setTickColor(tick, color) + #self.updateGradient() + elif ev.button() == QtCore.Qt.RightButton: + if not tick.removeAllowed: + return + if len(self.ticks) > 2: + self.removeTick(tick) + self.updateGradient() + + def tickMoved(self, tick, pos): + #private + TickSliderItem.tickMoved(self, tick, pos) + self.updateGradient() + + def tickMoveFinished(self, tick): + self.sigGradientChangeFinished.emit(self) + + + def getGradient(self): + """Return a QLinearGradient object.""" + g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0)) + if self.colorMode == 'rgb': + ticks = self.listTicks() + g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) + elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop + ticks = self.listTicks() + stops = [] + stops.append((ticks[0][1], ticks[0][0].color)) + for i in range(1,len(ticks)): + x1 = ticks[i-1][1] + x2 = ticks[i][1] + dx = (x2-x1) / 10. + for j in range(1,10): + x = x1 + dx*j + stops.append((x, self.getColor(x))) + stops.append((x2, self.getColor(x2))) + g.setStops(stops) + return g + + def getColor(self, x, toQColor=True): + """ + Return a color for a given value. + + ============= ================================================================== + **Arguments** + x Value (position on gradient) of requested color. + toQColor If true, returns a QColor object, else returns a (r,g,b,a) tuple. + ============= ================================================================== + """ + ticks = self.listTicks() + if x <= ticks[0][1]: + c = ticks[0][0].color + if toQColor: + return QtGui.QColor(c) # always copy colors before handing them out + else: + return (c.red(), c.green(), c.blue(), c.alpha()) + if x >= ticks[-1][1]: + c = ticks[-1][0].color + if toQColor: + return QtGui.QColor(c) # always copy colors before handing them out + else: + return (c.red(), c.green(), c.blue(), c.alpha()) + + x2 = ticks[0][1] + for i in range(1,len(ticks)): + x1 = x2 + x2 = ticks[i][1] + if x1 <= x and x2 >= x: + break + + dx = (x2-x1) + if dx == 0: + f = 0. + else: + f = (x-x1) / dx + c1 = ticks[i-1][0].color + c2 = ticks[i][0].color + if self.colorMode == 'rgb': + r = c1.red() * (1.-f) + c2.red() * f + g = c1.green() * (1.-f) + c2.green() * f + b = c1.blue() * (1.-f) + c2.blue() * f + a = c1.alpha() * (1.-f) + c2.alpha() * f + if toQColor: + return QtGui.QColor(int(r), int(g), int(b), int(a)) + else: + return (r,g,b,a) + elif self.colorMode == 'hsv': + h1,s1,v1,_ = c1.getHsv() + h2,s2,v2,_ = c2.getHsv() + h = h1 * (1.-f) + h2 * f + s = s1 * (1.-f) + s2 * f + v = v1 * (1.-f) + v2 * f + c = QtGui.QColor() + c.setHsv(h,s,v) + if toQColor: + return c + else: + return (c.red(), c.green(), c.blue(), c.alpha()) + + def getLookupTable(self, nPts, alpha=None): + """ + Return an RGB(A) lookup table (ndarray). + + ============= ============================================================================ + **Arguments** + nPts The number of points in the returned lookup table. + alpha True, False, or None - Specifies whether or not alpha values are included + in the table.If alpha is None, alpha will be automatically determined. + ============= ============================================================================ + """ + if alpha is None: + alpha = self.usesAlpha() + if alpha: + table = np.empty((nPts,4), dtype=np.ubyte) + else: + table = np.empty((nPts,3), dtype=np.ubyte) + + for i in range(nPts): + x = float(i)/(nPts-1) + color = self.getColor(x, toQColor=False) + table[i] = color[:table.shape[1]] + + return table + + def usesAlpha(self): + """Return True if any ticks have an alpha < 255""" + + ticks = self.listTicks() + for t in ticks: + if t[0].color.alpha() < 255: + return True + + return False + + def isLookupTrivial(self): + """Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0""" + ticks = self.listTicks() + if len(ticks) != 2: + return False + if ticks[0][1] != 0.0 or ticks[1][1] != 1.0: + return False + c1 = fn.colorTuple(ticks[0][0].color) + c2 = fn.colorTuple(ticks[1][0].color) + if c1 != (0,0,0,255) or c2 != (255,255,255,255): + return False + return True + + + def mouseReleaseEvent(self, ev): + #private + TickSliderItem.mouseReleaseEvent(self, ev) + self.updateGradient() + + def addTick(self, x, color=None, movable=True, finish=True): + """ + Add a tick to the gradient. Return the tick. + + ============= ================================================================== + **Arguments** + x Position where tick should be added. + color Color of added tick. If color is not specified, the color will be + the color of the gradient at the specified position. + movable Specifies whether the tick is movable with the mouse. + ============= ================================================================== + """ + + + if color is None: + color = self.getColor(x) + t = TickSliderItem.addTick(self, x, color=color, movable=movable) + t.colorChangeAllowed = True + t.removeAllowed = True + + if finish: + self.sigGradientChangeFinished.emit(self) + return t + + + def removeTick(self, tick, finish=True): + TickSliderItem.removeTick(self, tick) + if finish: + self.sigGradientChangeFinished.emit(self) + + + def saveState(self): + """ + Return a dictionary with parameters for rebuilding the gradient. Keys will include: + + - 'mode': hsv or rgb + - 'ticks': a list of tuples (pos, (r,g,b,a)) + """ + ## public + ticks = [] + for t in self.ticks: + c = t.color + ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha()))) + state = {'mode': self.colorMode, 'ticks': ticks} + return state + + def restoreState(self, state): + """ + Restore the gradient specified in state. + + ============= ==================================================================== + **Arguments** + state A dictionary with same structure as those returned by + :func:`saveState ` + + Keys must include: + + - 'mode': hsv or rgb + - 'ticks': a list of tuples (pos, (r,g,b,a)) + ============= ==================================================================== + """ + ## public + self.setColorMode(state['mode']) + for t in list(self.ticks.keys()): + self.removeTick(t, finish=False) + for t in state['ticks']: + c = QtGui.QColor(*t[1]) + self.addTick(t[0], c, finish=False) + self.updateGradient() + self.sigGradientChangeFinished.emit(self) + + def setColorMap(self, cm): + self.setColorMode('rgb') + for t in list(self.ticks.keys()): + self.removeTick(t, finish=False) + colors = cm.getColors(mode='qcolor') + for i in range(len(cm.pos)): + x = cm.pos[i] + c = colors[i] + self.addTick(x, c, finish=False) + self.updateGradient() + self.sigGradientChangeFinished.emit(self) + + +class Tick(QtGui.QGraphicsObject): ## NOTE: Making this a subclass of GraphicsObject instead results in + ## activating this bug: https://bugreports.qt-project.org/browse/PYSIDE-86 + ## private class + + sigMoving = QtCore.Signal(object) + sigMoved = QtCore.Signal(object) + + def __init__(self, view, pos, color, movable=True, scale=10, pen='w'): + self.movable = movable + self.moving = False + self.view = weakref.ref(view) + self.scale = scale + self.color = color + self.pen = fn.mkPen(pen) + self.hoverPen = fn.mkPen(255,255,0) + self.currentPen = self.pen + self.pg = QtGui.QPainterPath(QtCore.QPointF(0,0)) + self.pg.lineTo(QtCore.QPointF(-scale/3**0.5, scale)) + self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale)) + self.pg.closeSubpath() + + QtGui.QGraphicsObject.__init__(self) + self.setPos(pos[0], pos[1]) + if self.movable: + self.setZValue(1) + else: + self.setZValue(0) + + def boundingRect(self): + return self.pg.boundingRect() + + def shape(self): + return self.pg + + def paint(self, p, *args): + p.setRenderHints(QtGui.QPainter.Antialiasing) + p.fillPath(self.pg, fn.mkBrush(self.color)) + + p.setPen(self.currentPen) + p.drawPath(self.pg) + + + def mouseDragEvent(self, ev): + if self.movable and ev.button() == QtCore.Qt.LeftButton: + if ev.isStart(): + self.moving = True + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + self.startPosition = self.pos() + ev.accept() + + if not self.moving: + return + + newPos = self.cursorOffset + self.mapToParent(ev.pos()) + newPos.setY(self.pos().y()) + + self.setPos(newPos) + self.view().tickMoved(self, newPos) + self.sigMoving.emit(self) + if ev.isFinish(): + self.moving = False + self.sigMoved.emit(self) + self.view().tickMoveFinished(self) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton and self.moving: + ev.accept() + self.setPos(self.startPosition) + self.view().tickMoved(self, self.startPosition) + self.moving = False + self.sigMoving.emit(self) + self.sigMoved.emit(self) + else: + self.view().tickClicked(self, ev) + ##remove + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.LeftButton) + ev.acceptClicks(QtCore.Qt.RightButton) + self.currentPen = self.hoverPen + else: + self.currentPen = self.pen + self.update() + + #def mouseMoveEvent(self, ev): + ##print self, "move", ev.scenePos() + #if not self.movable: + #return + #if not ev.buttons() & QtCore.Qt.LeftButton: + #return + + + #newPos = ev.scenePos() + self.mouseOffset + #newPos.setY(self.pos().y()) + ##newPos.setX(min(max(newPos.x(), 0), 100)) + #self.setPos(newPos) + #self.view().tickMoved(self, newPos) + #self.movedSincePress = True + ##self.emit(QtCore.SIGNAL('tickChanged'), self) + #ev.accept() + + #def mousePressEvent(self, ev): + #self.movedSincePress = False + #if ev.button() == QtCore.Qt.LeftButton: + #ev.accept() + #self.mouseOffset = self.pos() - ev.scenePos() + #self.pressPos = ev.scenePos() + #elif ev.button() == QtCore.Qt.RightButton: + #ev.accept() + ##if self.endTick: + ##return + ##self.view.tickChanged(self, delete=True) + + #def mouseReleaseEvent(self, ev): + ##print self, "release", ev.scenePos() + #if not self.movedSincePress: + #self.view().tickClicked(self, ev) + + ##if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos: + ##color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) + ##if color.isValid(): + ##self.color = color + ##self.setBrush(QtGui.QBrush(QtGui.QColor(self.color))) + ###self.emit(QtCore.SIGNAL('tickChanged'), self) + ##self.view.tickChanged(self) diff --git a/pyqtgraph/graphicsItems/GradientLegend.py b/pyqtgraph/graphicsItems/GradientLegend.py new file mode 100644 index 00000000..4528b7ed --- /dev/null +++ b/pyqtgraph/graphicsItems/GradientLegend.py @@ -0,0 +1,114 @@ +from pyqtgraph.Qt import QtGui, QtCore +from .UIGraphicsItem import * +import pyqtgraph.functions as fn + +__all__ = ['GradientLegend'] + +class GradientLegend(UIGraphicsItem): + """ + Draws a color gradient rectangle along with text labels denoting the value at specific + points along the gradient. + """ + + def __init__(self, size, offset): + self.size = size + self.offset = offset + UIGraphicsItem.__init__(self) + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + self.brush = QtGui.QBrush(QtGui.QColor(200,0,0)) + self.pen = QtGui.QPen(QtGui.QColor(0,0,0)) + self.labels = {'max': 1, 'min': 0} + self.gradient = QtGui.QLinearGradient() + self.gradient.setColorAt(0, QtGui.QColor(0,0,0)) + self.gradient.setColorAt(1, QtGui.QColor(255,0,0)) + + def setGradient(self, g): + self.gradient = g + self.update() + + def setIntColorScale(self, minVal, maxVal, *args, **kargs): + colors = [fn.intColor(i, maxVal-minVal, *args, **kargs) for i in range(minVal, maxVal)] + g = QtGui.QLinearGradient() + for i in range(len(colors)): + x = float(i)/len(colors) + g.setColorAt(x, colors[i]) + self.setGradient(g) + if 'labels' not in kargs: + self.setLabels({str(minVal/10.): 0, str(maxVal): 1}) + else: + self.setLabels({kargs['labels'][0]:0, kargs['labels'][1]:1}) + + def setLabels(self, l): + """Defines labels to appear next to the color scale. Accepts a dict of {text: value} pairs""" + self.labels = l + self.update() + + def paint(self, p, opt, widget): + UIGraphicsItem.paint(self, p, opt, widget) + rect = self.boundingRect() ## Boundaries of visible area in scene coords. + unit = self.pixelSize() ## Size of one view pixel in scene coords. + if unit[0] is None: + return + + ## determine max width of all labels + labelWidth = 0 + labelHeight = 0 + for k in self.labels: + b = p.boundingRect(QtCore.QRectF(0, 0, 0, 0), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) + labelWidth = max(labelWidth, b.width()) + labelHeight = max(labelHeight, b.height()) + + labelWidth *= unit[0] + labelHeight *= unit[1] + + textPadding = 2 # in px + + if self.offset[0] < 0: + x3 = rect.right() + unit[0] * self.offset[0] + x2 = x3 - labelWidth - unit[0]*textPadding*2 + x1 = x2 - unit[0] * self.size[0] + else: + x1 = rect.left() + unit[0] * self.offset[0] + x2 = x1 + unit[0] * self.size[0] + x3 = x2 + labelWidth + unit[0]*textPadding*2 + if self.offset[1] < 0: + y2 = rect.top() - unit[1] * self.offset[1] + y1 = y2 + unit[1] * self.size[1] + else: + y1 = rect.bottom() - unit[1] * self.offset[1] + y2 = y1 - unit[1] * self.size[1] + self.b = [x1,x2,x3,y1,y2,labelWidth] + + ## Draw background + p.setPen(self.pen) + p.setBrush(QtGui.QBrush(QtGui.QColor(255,255,255,100))) + rect = QtCore.QRectF( + QtCore.QPointF(x1 - unit[0]*textPadding, y1 + labelHeight/2 + unit[1]*textPadding), + QtCore.QPointF(x3, y2 - labelHeight/2 - unit[1]*textPadding) + ) + p.drawRect(rect) + + + ## Have to scale painter so that text and gradients are correct size. Bleh. + p.scale(unit[0], unit[1]) + + ## Draw color bar + self.gradient.setStart(0, y1/unit[1]) + self.gradient.setFinalStop(0, y2/unit[1]) + p.setBrush(self.gradient) + rect = QtCore.QRectF( + QtCore.QPointF(x1/unit[0], y1/unit[1]), + QtCore.QPointF(x2/unit[0], y2/unit[1]) + ) + p.drawRect(rect) + + + ## draw labels + p.setPen(QtGui.QPen(QtGui.QColor(0,0,0))) + tx = x2 + unit[0]*textPadding + lh = labelHeight/unit[1] + for k in self.labels: + y = y1 + self.labels[k] * (y2-y1) + p.drawText(QtCore.QRectF(tx/unit[0], y/unit[1] - lh/2.0, 1000, lh), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) + + diff --git a/pyqtgraph/graphicsItems/GraphItem.py b/pyqtgraph/graphicsItems/GraphItem.py new file mode 100644 index 00000000..b1f34baa --- /dev/null +++ b/pyqtgraph/graphicsItems/GraphItem.py @@ -0,0 +1,122 @@ +from .. import functions as fn +from .GraphicsObject import GraphicsObject +from .ScatterPlotItem import ScatterPlotItem +import pyqtgraph as pg +import numpy as np + +__all__ = ['GraphItem'] + + +class GraphItem(GraphicsObject): + """A GraphItem displays graph information as + a set of nodes connected by lines (as in 'graph theory', not 'graphics'). + Useful for drawing networks, trees, etc. + """ + + def __init__(self, **kwds): + GraphicsObject.__init__(self) + self.scatter = ScatterPlotItem() + self.scatter.setParentItem(self) + self.adjacency = None + self.pos = None + self.picture = None + self.pen = 'default' + self.setData(**kwds) + + def setData(self, **kwds): + """ + Change the data displayed by the graph. + + ============ ========================================================= + Arguments + pos (N,2) array of the positions of each node in the graph. + adj (M,2) array of connection data. Each row contains indexes + of two nodes that are connected. + pen The pen to use when drawing lines between connected + nodes. May be one of: + + * QPen + * a single argument to pass to pg.mkPen + * a record array of length M + with fields (red, green, blue, alpha, width). Note + that using this option may have a significant performance + cost. + * None (to disable connection drawing) + * 'default' to use the default foreground color. + + symbolPen The pen used for drawing nodes. + ``**opts`` All other keyword arguments are given to + :func:`ScatterPlotItem.setData() ` + to affect the appearance of nodes (symbol, size, brush, + etc.) + ============ ========================================================= + """ + if 'adj' in kwds: + self.adjacency = kwds.pop('adj') + assert self.adjacency.dtype.kind in 'iu' + self.picture = None + if 'pos' in kwds: + self.pos = kwds['pos'] + self.picture = None + if 'pen' in kwds: + self.setPen(kwds.pop('pen')) + self.picture = None + if 'symbolPen' in kwds: + kwds['pen'] = kwds.pop('symbolPen') + self.scatter.setData(**kwds) + self.informViewBoundsChanged() + + def setPen(self, pen): + self.pen = pen + self.picture = None + + def generatePicture(self): + self.picture = pg.QtGui.QPicture() + if self.pen is None or self.pos is None or self.adjacency is None: + return + + p = pg.QtGui.QPainter(self.picture) + try: + pts = self.pos[self.adjacency] + pen = self.pen + if isinstance(pen, np.ndarray): + lastPen = None + for i in range(pts.shape[0]): + pen = self.pen[i] + if np.any(pen != lastPen): + lastPen = pen + if pen.dtype.fields is None: + p.setPen(pg.mkPen(color=(pen[0], pen[1], pen[2], pen[3]), width=1)) + else: + p.setPen(pg.mkPen(color=(pen['red'], pen['green'], pen['blue'], pen['alpha']), width=pen['width'])) + p.drawLine(pg.QtCore.QPointF(*pts[i][0]), pg.QtCore.QPointF(*pts[i][1])) + else: + if pen == 'default': + pen = pg.getConfigOption('foreground') + p.setPen(pg.mkPen(pen)) + pts = pts.reshape((pts.shape[0]*pts.shape[1], pts.shape[2])) + path = fn.arrayToQPath(x=pts[:,0], y=pts[:,1], connect='pairs') + p.drawPath(path) + finally: + p.end() + + def paint(self, p, *args): + if self.picture == None: + self.generatePicture() + if pg.getConfigOption('antialias') is True: + p.setRenderHint(p.Antialiasing) + self.picture.play(p) + + def boundingRect(self): + return self.scatter.boundingRect() + + def dataBounds(self, *args, **kwds): + return self.scatter.dataBounds(*args, **kwds) + + def pixelPadding(self): + return self.scatter.pixelPadding() + + + + + diff --git a/pyqtgraph/graphicsItems/GraphicsItem.py b/pyqtgraph/graphicsItems/GraphicsItem.py new file mode 100644 index 00000000..a129436e --- /dev/null +++ b/pyqtgraph/graphicsItems/GraphicsItem.py @@ -0,0 +1,587 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.GraphicsScene import GraphicsScene +from pyqtgraph.Point import Point +import pyqtgraph.functions as fn +import weakref +from pyqtgraph.pgcollections import OrderedDict +import operator, sys + +class FiniteCache(OrderedDict): + """Caches a finite number of objects, removing + least-frequently used items.""" + def __init__(self, length): + self._length = length + OrderedDict.__init__(self) + + def __setitem__(self, item, val): + self.pop(item, None) # make sure item is added to end + OrderedDict.__setitem__(self, item, val) + while len(self) > self._length: + del self[list(self.keys())[0]] + + def __getitem__(self, item): + val = OrderedDict.__getitem__(self, item) + del self[item] + self[item] = val ## promote this key + return val + + + +class GraphicsItem(object): + """ + **Bases:** :class:`object` + + Abstract class providing useful methods to GraphicsObject and GraphicsWidget. + (This is required because we cannot have multiple inheritance with QObject subclasses.) + + A note about Qt's GraphicsView framework: + + The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task. + """ + _pixelVectorGlobalCache = FiniteCache(100) + + def __init__(self, register=True): + if not hasattr(self, '_qtBaseClass'): + for b in self.__class__.__bases__: + if issubclass(b, QtGui.QGraphicsItem): + self.__class__._qtBaseClass = b + break + if not hasattr(self, '_qtBaseClass'): + raise Exception('Could not determine Qt base class for GraphicsItem: %s' % str(self)) + + self._pixelVectorCache = [None, None] + self._viewWidget = None + self._viewBox = None + self._connectedView = None + self._exportOpts = False ## If False, not currently exporting. Otherwise, contains dict of export options. + if register: + GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() + + + + + def getViewWidget(self): + """ + Return the view widget for this item. If the scene has multiple views, only the first view is returned. + The return value is cached; clear the cached value with forgetViewWidget() + """ + if self._viewWidget is None: + scene = self.scene() + if scene is None: + return None + views = scene.views() + if len(views) < 1: + return None + self._viewWidget = weakref.ref(self.scene().views()[0]) + return self._viewWidget() + + def forgetViewWidget(self): + self._viewWidget = None + + def getViewBox(self): + """ + Return the first ViewBox or GraphicsView which bounds this item's visible space. + If this item is not contained within a ViewBox, then the GraphicsView is returned. + If the item is contained inside nested ViewBoxes, then the inner-most ViewBox is returned. + The result is cached; clear the cache with forgetViewBox() + """ + if self._viewBox is None: + p = self + while True: + try: + p = p.parentItem() + except RuntimeError: ## sometimes happens as items are being removed from a scene and collected. + return None + if p is None: + vb = self.getViewWidget() + if vb is None: + return None + else: + self._viewBox = weakref.ref(vb) + break + if hasattr(p, 'implements') and p.implements('ViewBox'): + self._viewBox = weakref.ref(p) + break + return self._viewBox() ## If we made it this far, _viewBox is definitely not None + + def forgetViewBox(self): + self._viewBox = None + + + def deviceTransform(self, viewportTransform=None): + """ + Return the transform that converts local item coordinates to device coordinates (usually pixels). + Extends deviceTransform to automatically determine the viewportTransform. + """ + if self._exportOpts is not False and 'painter' in self._exportOpts: ## currently exporting; device transform may be different. + return self._exportOpts['painter'].deviceTransform() + + if viewportTransform is None: + view = self.getViewWidget() + if view is None: + return None + viewportTransform = view.viewportTransform() + dt = self._qtBaseClass.deviceTransform(self, viewportTransform) + + #xmag = abs(dt.m11())+abs(dt.m12()) + #ymag = abs(dt.m21())+abs(dt.m22()) + #if xmag * ymag == 0: + if dt.determinant() == 0: ## occurs when deviceTransform is invalid because widget has not been displayed + return None + else: + return dt + + def viewTransform(self): + """Return the transform that maps from local coordinates to the item's ViewBox coordinates + If there is no ViewBox, return the scene transform. + Returns None if the item does not have a view.""" + view = self.getViewBox() + if view is None: + return None + if hasattr(view, 'implements') and view.implements('ViewBox'): + tr = self.itemTransform(view.innerSceneItem()) + if isinstance(tr, tuple): + tr = tr[0] ## difference between pyside and pyqt + return tr + else: + return self.sceneTransform() + #return self.deviceTransform(view.viewportTransform()) + + + + def getBoundingParents(self): + """Return a list of parents to this item that have child clipping enabled.""" + p = self + parents = [] + while True: + p = p.parentItem() + if p is None: + break + if p.flags() & self.ItemClipsChildrenToShape: + parents.append(p) + return parents + + def viewRect(self): + """Return the bounds (in item coordinates) of this item's ViewBox or GraphicsWidget""" + view = self.getViewBox() + if view is None: + return None + bounds = self.mapRectFromView(view.viewRect()) + if bounds is None: + return None + + bounds = bounds.normalized() + + ## nah. + #for p in self.getBoundingParents(): + #bounds &= self.mapRectFromScene(p.sceneBoundingRect()) + + return bounds + + + + def pixelVectors(self, direction=None): + """Return vectors in local coordinates representing the width and height of a view pixel. + If direction is specified, then return vectors parallel and orthogonal to it. + + Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed) + or if pixel size is below floating-point precision limit. + """ + + ## This is an expensive function that gets called very frequently. + ## We have two levels of cache to try speeding things up. + + dt = self.deviceTransform() + if dt is None: + return None, None + + ## Ignore translation. If the translation is much larger than the scale + ## (such as when looking at unix timestamps), we can get floating-point errors. + dt.setMatrix(dt.m11(), dt.m12(), 0, dt.m21(), dt.m22(), 0, 0, 0, 1) + + ## check local cache + if direction is None and dt == self._pixelVectorCache[0]: + return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy* + + ## check global cache + #key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32()) + key = (dt.m11(), dt.m21(), dt.m12(), dt.m22()) + pv = self._pixelVectorGlobalCache.get(key, None) + if direction is None and pv is not None: + self._pixelVectorCache = [dt, pv] + return tuple(map(Point,pv)) ## return a *copy* + + + if direction is None: + direction = QtCore.QPointF(1, 0) + if direction.manhattanLength() == 0: + raise Exception("Cannot compute pixel length for 0-length vector.") + + ## attempt to re-scale direction vector to fit within the precision of the coordinate system + ## Here's the problem: we need to map the vector 'direction' from the item to the device, via transform 'dt'. + ## In some extreme cases, this mapping can fail unless the length of 'direction' is cleverly chosen. + ## Example: + ## dt = [ 1, 0, 2 + ## 0, 2, 1e20 + ## 0, 0, 1 ] + ## Then we map the origin (0,0) and direction (0,1) and get: + ## o' = 2,1e20 + ## d' = 2,1e20 <-- should be 1e20+2, but this can't be represented with a 32-bit float + ## + ## |o' - d'| == 0 <-- this is the problem. + + ## Perhaps the easiest solution is to exclude the transformation column from dt. Does this cause any other problems? + + #if direction.x() == 0: + #r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22())) + ##r = 1.0/(abs(dt.m12()) + abs(dt.m22())) + #elif direction.y() == 0: + #r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21())) + ##r = 1.0/(abs(dt.m11()) + abs(dt.m21())) + #else: + #r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5 + #if r == 0: + #r = 1. ## shouldn't need to do this; probably means the math above is wrong? + #directionr = direction * r + directionr = direction + + ## map direction vector onto device + #viewDir = Point(dt.map(directionr) - dt.map(Point(0,0))) + #mdirection = dt.map(directionr) + dirLine = QtCore.QLineF(QtCore.QPointF(0,0), directionr) + viewDir = dt.map(dirLine) + if viewDir.length() == 0: + return None, None ## pixel size cannot be represented on this scale + + ## get unit vector and orthogonal vector (length of pixel) + #orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space + try: + normView = viewDir.unitVector() + #normView = viewDir.norm() ## direction of one pixel orthogonal to line + normOrtho = normView.normalVector() + #normOrtho = orthoDir.norm() + except: + raise Exception("Invalid direction %s" %directionr) + + ## map back to item + dti = fn.invertQTransform(dt) + #pv = Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0))) + pv = Point(dti.map(normView).p2()), Point(dti.map(normOrtho).p2()) + self._pixelVectorCache[1] = pv + self._pixelVectorCache[0] = dt + self._pixelVectorGlobalCache[key] = pv + return self._pixelVectorCache[1] + + + def pixelLength(self, direction, ortho=False): + """Return the length of one pixel in the direction indicated (in local coordinates) + If ortho=True, then return the length of one pixel orthogonal to the direction indicated. + + Return None if pixel size is not yet defined (usually because the item has not yet been displayed). + """ + normV, orthoV = self.pixelVectors(direction) + if normV == None or orthoV == None: + return None + if ortho: + return orthoV.length() + return normV.length() + + + def pixelSize(self): + ## deprecated + v = self.pixelVectors() + if v == (None, None): + return None, None + return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5 + + def pixelWidth(self): + ## deprecated + vt = self.deviceTransform() + if vt is None: + return 0 + vt = fn.invertQTransform(vt) + return vt.map(QtCore.QLineF(0, 0, 1, 0)).length() + + def pixelHeight(self): + ## deprecated + vt = self.deviceTransform() + if vt is None: + return 0 + vt = fn.invertQTransform(vt) + return vt.map(QtCore.QLineF(0, 0, 0, 1)).length() + #return Point(vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).length() + + + def mapToDevice(self, obj): + """ + Return *obj* mapped from local coordinates to device coordinates (pixels). + If there is no device mapping available, return None. + """ + vt = self.deviceTransform() + if vt is None: + return None + return vt.map(obj) + + def mapFromDevice(self, obj): + """ + Return *obj* mapped from device coordinates (pixels) to local coordinates. + If there is no device mapping available, return None. + """ + vt = self.deviceTransform() + if vt is None: + return None + vt = fn.invertQTransform(vt) + return vt.map(obj) + + def mapRectToDevice(self, rect): + """ + Return *rect* mapped from local coordinates to device coordinates (pixels). + If there is no device mapping available, return None. + """ + vt = self.deviceTransform() + if vt is None: + return None + return vt.mapRect(rect) + + def mapRectFromDevice(self, rect): + """ + Return *rect* mapped from device coordinates (pixels) to local coordinates. + If there is no device mapping available, return None. + """ + vt = self.deviceTransform() + if vt is None: + return None + vt = fn.invertQTransform(vt) + return vt.mapRect(rect) + + def mapToView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + return vt.map(obj) + + def mapRectToView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + return vt.mapRect(obj) + + def mapFromView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + vt = fn.invertQTransform(vt) + return vt.map(obj) + + def mapRectFromView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + vt = fn.invertQTransform(vt) + return vt.mapRect(obj) + + def pos(self): + return Point(self._qtBaseClass.pos(self)) + + def viewPos(self): + return self.mapToView(self.mapFromParent(self.pos())) + + def parentItem(self): + ## PyQt bug -- some items are returned incorrectly. + return GraphicsScene.translateGraphicsItem(self._qtBaseClass.parentItem(self)) + + def setParentItem(self, parent): + ## Workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 + if parent is not None: + pscene = parent.scene() + if pscene is not None and self.scene() is not pscene: + pscene.addItem(self) + return self._qtBaseClass.setParentItem(self, parent) + + def childItems(self): + ## PyQt bug -- some child items are returned incorrectly. + return list(map(GraphicsScene.translateGraphicsItem, self._qtBaseClass.childItems(self))) + + + def sceneTransform(self): + ## Qt bug: do no allow access to sceneTransform() until + ## the item has a scene. + + if self.scene() is None: + return self.transform() + else: + return self._qtBaseClass.sceneTransform(self) + + + def transformAngle(self, relativeItem=None): + """Return the rotation produced by this item's transform (this assumes there is no shear in the transform) + If relativeItem is given, then the angle is determined relative to that item. + """ + if relativeItem is None: + relativeItem = self.parentItem() + + + tr = self.itemTransform(relativeItem) + if isinstance(tr, tuple): ## difference between pyside and pyqt + tr = tr[0] + #vec = tr.map(Point(1,0)) - tr.map(Point(0,0)) + vec = tr.map(QtCore.QLineF(0,0,1,0)) + #return Point(vec).angle(Point(1,0)) + return vec.angleTo(QtCore.QLineF(vec.p1(), vec.p1()+QtCore.QPointF(1,0))) + + #def itemChange(self, change, value): + #ret = self._qtBaseClass.itemChange(self, change, value) + #if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged: + #print "Item scene changed:", self + #self.setChildScene(self) ## This is bizarre. + #return ret + + #def setChildScene(self, ch): + #scene = self.scene() + #for ch2 in ch.childItems(): + #if ch2.scene() is not scene: + #print "item", ch2, "has different scene:", ch2.scene(), scene + #scene.addItem(ch2) + #QtGui.QApplication.processEvents() + #print " --> ", ch2.scene() + #self.setChildScene(ch2) + + def parentChanged(self): + """Called when the item's parent has changed. + This method handles connecting / disconnecting from ViewBox signals + to make sure viewRangeChanged works properly. It should generally be + extended, not overridden.""" + self._updateView() + + + def _updateView(self): + ## called to see whether this item has a new view to connect to + ## NOTE: This is called from GraphicsObject.itemChange or GraphicsWidget.itemChange. + + ## It is possible this item has moved to a different ViewBox or widget; + ## clear out previously determined references to these. + self.forgetViewBox() + self.forgetViewWidget() + + ## check for this item's current viewbox or view widget + view = self.getViewBox() + #if view is None: + ##print " no view" + #return + + oldView = None + if self._connectedView is not None: + oldView = self._connectedView() + + if view is oldView: + #print " already have view", view + return + + ## disconnect from previous view + if oldView is not None: + #print "disconnect:", self, oldView + try: + oldView.sigRangeChanged.disconnect(self.viewRangeChanged) + except TypeError: + pass + + try: + oldView.sigTransformChanged.disconnect(self.viewTransformChanged) + except TypeError: + pass + + self._connectedView = None + + ## connect to new view + if view is not None: + #print "connect:", self, view + view.sigRangeChanged.connect(self.viewRangeChanged) + view.sigTransformChanged.connect(self.viewTransformChanged) + self._connectedView = weakref.ref(view) + self.viewRangeChanged() + self.viewTransformChanged() + + ## inform children that their view might have changed + self._replaceView(oldView) + + self.viewChanged(view, oldView) + + def viewChanged(self, view, oldView): + """Called when this item's view has changed + (ie, the item has been added to or removed from a ViewBox)""" + pass + + def _replaceView(self, oldView, item=None): + if item is None: + item = self + for child in item.childItems(): + if isinstance(child, GraphicsItem): + if child.getViewBox() is oldView: + child._updateView() + #self._replaceView(oldView, child) + else: + self._replaceView(oldView, child) + + + + def viewRangeChanged(self): + """ + Called whenever the view coordinates of the ViewBox containing this item have changed. + """ + pass + + def viewTransformChanged(self): + """ + Called whenever the transformation matrix of the view has changed. + (eg, the view range has changed or the view was resized) + """ + pass + + #def prepareGeometryChange(self): + #self._qtBaseClass.prepareGeometryChange(self) + #self.informViewBoundsChanged() + + def informViewBoundsChanged(self): + """ + Inform this item's container ViewBox that the bounds of this item have changed. + This is used by ViewBox to react if auto-range is enabled. + """ + view = self.getViewBox() + if view is not None and hasattr(view, 'implements') and view.implements('ViewBox'): + view.itemBoundsChanged(self) ## inform view so it can update its range if it wants + + def childrenShape(self): + """Return the union of the shapes of all descendants of this item in local coordinates.""" + childs = self.allChildItems() + shapes = [self.mapFromItem(c, c.shape()) for c in self.allChildItems()] + return reduce(operator.add, shapes) + + def allChildItems(self, root=None): + """Return list of the entire item tree descending from this item.""" + if root is None: + root = self + tree = [] + for ch in root.childItems(): + tree.append(ch) + tree.extend(self.allChildItems(ch)) + return tree + + + def setExportMode(self, export, opts=None): + """ + This method is called by exporters to inform items that they are being drawn for export + with a specific set of options. Items access these via self._exportOptions. + When exporting is complete, _exportOptions is set to False. + """ + if opts is None: + opts = {} + if export: + self._exportOpts = opts + #if 'antialias' not in opts: + #self._exportOpts['antialias'] = True + else: + self._exportOpts = False + + #def update(self): + #self._qtBaseClass.update(self) + #print "Update:", self diff --git a/pyqtgraph/graphicsItems/GraphicsLayout.py b/pyqtgraph/graphicsItems/GraphicsLayout.py new file mode 100644 index 00000000..9d48e627 --- /dev/null +++ b/pyqtgraph/graphicsItems/GraphicsLayout.py @@ -0,0 +1,154 @@ +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn +from .GraphicsWidget import GraphicsWidget +## Must be imported at the end to avoid cyclic-dependency hell: +from .ViewBox import ViewBox +from .PlotItem import PlotItem +from .LabelItem import LabelItem + +__all__ = ['GraphicsLayout'] +class GraphicsLayout(GraphicsWidget): + """ + Used for laying out GraphicsWidgets in a grid. + This is usually created automatically as part of a :class:`GraphicsWindow ` or :class:`GraphicsLayoutWidget `. + """ + + + def __init__(self, parent=None, border=None): + GraphicsWidget.__init__(self, parent) + if border is True: + border = (100,100,100) + self.border = border + self.layout = QtGui.QGraphicsGridLayout() + self.setLayout(self.layout) + self.items = {} ## item: [(row, col), (row, col), ...] lists all cells occupied by the item + self.rows = {} ## row: {col1: item1, col2: item2, ...} maps cell location to item + self.currentRow = 0 + self.currentCol = 0 + self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) + + #def resizeEvent(self, ev): + #ret = GraphicsWidget.resizeEvent(self, ev) + #print self.pos(), self.mapToDevice(self.rect().topLeft()) + #return ret + + def nextRow(self): + """Advance to next row for automatic item placement""" + self.currentRow += 1 + self.currentCol = -1 + self.nextColumn() + + def nextColumn(self): + """Advance to next available column + (generally only for internal use--called by addItem)""" + self.currentCol += 1 + while self.getItem(self.currentRow, self.currentCol) is not None: + self.currentCol += 1 + + def nextCol(self, *args, **kargs): + """Alias of nextColumn""" + return self.nextColumn(*args, **kargs) + + def addPlot(self, row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create a PlotItem and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to :func:`PlotItem.__init__ ` + Returns the created item. + """ + plot = PlotItem(**kargs) + self.addItem(plot, row, col, rowspan, colspan) + return plot + + def addViewBox(self, row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create a ViewBox and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to :func:`ViewBox.__init__ ` + Returns the created item. + """ + vb = ViewBox(**kargs) + self.addItem(vb, row, col, rowspan, colspan) + return vb + + def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create a LabelItem with *text* and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to :func:`LabelItem.__init__ ` + Returns the created item. + + To create a vertical label, use *angle* = -90. + """ + text = LabelItem(text, **kargs) + self.addItem(text, row, col, rowspan, colspan) + return text + + def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create an empty GraphicsLayout and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to :func:`GraphicsLayout.__init__ ` + Returns the created item. + """ + layout = GraphicsLayout(**kargs) + self.addItem(layout, row, col, rowspan, colspan) + return layout + + def addItem(self, item, row=None, col=None, rowspan=1, colspan=1): + """ + Add an item to the layout and place it in the next available cell (or in the cell specified). + The item must be an instance of a QGraphicsWidget subclass. + """ + if row is None: + row = self.currentRow + if col is None: + col = self.currentCol + + self.items[item] = [] + for i in range(rowspan): + for j in range(colspan): + row2 = row + i + col2 = col + j + if row2 not in self.rows: + self.rows[row2] = {} + self.rows[row2][col2] = item + self.items[item].append((row2, col2)) + + self.layout.addItem(item, row, col, rowspan, colspan) + self.nextColumn() + + def getItem(self, row, col): + """Return the item in (*row*, *col*). If the cell is empty, return None.""" + return self.rows.get(row, {}).get(col, None) + + def boundingRect(self): + return self.rect() + + def paint(self, p, *args): + if self.border is None: + return + p.setPen(fn.mkPen(self.border)) + for i in self.items: + r = i.mapRectToParent(i.boundingRect()) + p.drawRect(r) + + def itemIndex(self, item): + for i in range(self.layout.count()): + if self.layout.itemAt(i).graphicsItem() is item: + return i + raise Exception("Could not determine index of item " + str(item)) + + def removeItem(self, item): + """Remove *item* from the layout.""" + ind = self.itemIndex(item) + self.layout.removeAt(ind) + self.scene().removeItem(item) + + for r,c in self.items[item]: + del self.rows[r][c] + del self.items[item] + self.update() + + def clear(self): + items = [] + for i in list(self.items.keys()): + self.removeItem(i) + + diff --git a/pyqtgraph/graphicsItems/GraphicsObject.py b/pyqtgraph/graphicsItems/GraphicsObject.py new file mode 100644 index 00000000..d8f55d27 --- /dev/null +++ b/pyqtgraph/graphicsItems/GraphicsObject.py @@ -0,0 +1,32 @@ +from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE +if not USE_PYSIDE: + import sip +from .GraphicsItem import GraphicsItem + +__all__ = ['GraphicsObject'] +class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject): + """ + **Bases:** :class:`GraphicsItem `, :class:`QtGui.QGraphicsObject` + + Extension of QGraphicsObject with some useful methods (provided by :class:`GraphicsItem `) + """ + _qtBaseClass = QtGui.QGraphicsObject + def __init__(self, *args): + self.__inform_view_on_changes = True + QtGui.QGraphicsObject.__init__(self, *args) + self.setFlag(self.ItemSendsGeometryChanges) + GraphicsItem.__init__(self) + + def itemChange(self, change, value): + ret = QtGui.QGraphicsObject.itemChange(self, change, value) + if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: + self.parentChanged() + if self.__inform_view_on_changes and change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]: + self.informViewBoundsChanged() + + ## workaround for pyqt bug: + ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html + if not USE_PYSIDE and change == self.ItemParentChange and isinstance(ret, QtGui.QGraphicsItem): + ret = sip.cast(ret, QtGui.QGraphicsItem) + + return ret diff --git a/pyqtgraph/graphicsItems/GraphicsWidget.py b/pyqtgraph/graphicsItems/GraphicsWidget.py new file mode 100644 index 00000000..7650b125 --- /dev/null +++ b/pyqtgraph/graphicsItems/GraphicsWidget.py @@ -0,0 +1,59 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.GraphicsScene import GraphicsScene +from .GraphicsItem import GraphicsItem + +__all__ = ['GraphicsWidget'] + +class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget): + + _qtBaseClass = QtGui.QGraphicsWidget + def __init__(self, *args, **kargs): + """ + **Bases:** :class:`GraphicsItem `, :class:`QtGui.QGraphicsWidget` + + Extends QGraphicsWidget with several helpful methods and workarounds for PyQt bugs. + Most of the extra functionality is inherited from :class:`GraphicsItem `. + """ + QtGui.QGraphicsWidget.__init__(self, *args, **kargs) + GraphicsItem.__init__(self) + + ## done by GraphicsItem init + #GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() + + # Removed due to https://bugreports.qt-project.org/browse/PYSIDE-86 + #def itemChange(self, change, value): + ## BEWARE: Calling QGraphicsWidget.itemChange can lead to crashing! + ##ret = QtGui.QGraphicsWidget.itemChange(self, change, value) ## segv occurs here + ## The default behavior is just to return the value argument, so we'll do that + ## without calling the original method. + #ret = value + #if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: + #self._updateView() + #return ret + + def setFixedHeight(self, h): + self.setMaximumHeight(h) + self.setMinimumHeight(h) + + def setFixedWidth(self, h): + self.setMaximumWidth(h) + self.setMinimumWidth(h) + + def height(self): + return self.geometry().height() + + def width(self): + return self.geometry().width() + + def boundingRect(self): + br = self.mapRectFromParent(self.geometry()).normalized() + #print "bounds:", br + return br + + def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. + p = QtGui.QPainterPath() + p.addRect(self.boundingRect()) + #print "shape:", p.boundingRect() + return p + + diff --git a/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py b/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py new file mode 100644 index 00000000..251bc0c8 --- /dev/null +++ b/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py @@ -0,0 +1,110 @@ +from ..Qt import QtGui, QtCore +from ..Point import Point + + +class GraphicsWidgetAnchor(object): + """ + Class used to allow GraphicsWidgets to anchor to a specific position on their + parent. The item will be automatically repositioned if the parent is resized. + This is used, for example, to anchor a LegendItem to a corner of its parent + PlotItem. + + """ + + def __init__(self): + self.__parent = None + self.__parentAnchor = None + self.__itemAnchor = None + self.__offset = (0,0) + if hasattr(self, 'geometryChanged'): + self.geometryChanged.connect(self.__geometryChanged) + + def anchor(self, itemPos, parentPos, offset=(0,0)): + """ + Anchors the item at its local itemPos to the item's parent at parentPos. + Both positions are expressed in values relative to the size of the item or parent; + a value of 0 indicates left or top edge, while 1 indicates right or bottom edge. + + Optionally, offset may be specified to introduce an absolute offset. + + Example: anchor a box such that its upper-right corner is fixed 10px left + and 10px down from its parent's upper-right corner:: + + box.anchor(itemPos=(1,0), parentPos=(1,0), offset=(-10,10)) + """ + parent = self.parentItem() + if parent is None: + raise Exception("Cannot anchor; parent is not set.") + + if self.__parent is not parent: + if self.__parent is not None: + self.__parent.geometryChanged.disconnect(self.__geometryChanged) + + self.__parent = parent + parent.geometryChanged.connect(self.__geometryChanged) + + self.__itemAnchor = itemPos + self.__parentAnchor = parentPos + self.__offset = offset + self.__geometryChanged() + + + def autoAnchor(self, pos, relative=True): + """ + Set the position of this item relative to its parent by automatically + choosing appropriate anchor settings. + + If relative is True, one corner of the item will be anchored to + the appropriate location on the parent with no offset. The anchored + corner will be whichever is closest to the parent's boundary. + + If relative is False, one corner of the item will be anchored to the same + corner of the parent, with an absolute offset to achieve the correct + position. + """ + pos = Point(pos) + br = self.mapRectToParent(self.boundingRect()).translated(pos - self.pos()) + pbr = self.parentItem().boundingRect() + anchorPos = [0,0] + parentPos = Point() + itemPos = Point() + if abs(br.left() - pbr.left()) < abs(br.right() - pbr.right()): + anchorPos[0] = 0 + parentPos[0] = pbr.left() + itemPos[0] = br.left() + else: + anchorPos[0] = 1 + parentPos[0] = pbr.right() + itemPos[0] = br.right() + + if abs(br.top() - pbr.top()) < abs(br.bottom() - pbr.bottom()): + anchorPos[1] = 0 + parentPos[1] = pbr.top() + itemPos[1] = br.top() + else: + anchorPos[1] = 1 + parentPos[1] = pbr.bottom() + itemPos[1] = br.bottom() + + if relative: + relPos = [(itemPos[0]-pbr.left()) / pbr.width(), (itemPos[1]-pbr.top()) / pbr.height()] + self.anchor(anchorPos, relPos) + else: + offset = itemPos - parentPos + self.anchor(anchorPos, anchorPos, offset) + + def __geometryChanged(self): + if self.__parent is None: + return + if self.__itemAnchor is None: + return + + o = self.mapToParent(Point(0,0)) + a = self.boundingRect().bottomRight() * Point(self.__itemAnchor) + a = self.mapToParent(a) + p = self.__parent.boundingRect().bottomRight() * Point(self.__parentAnchor) + off = Point(self.__offset) + pos = p + (o-a) + off + self.setPos(pos) + + \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/GridItem.py b/pyqtgraph/graphicsItems/GridItem.py new file mode 100644 index 00000000..29b0aa2c --- /dev/null +++ b/pyqtgraph/graphicsItems/GridItem.py @@ -0,0 +1,120 @@ +from pyqtgraph.Qt import QtGui, QtCore +from .UIGraphicsItem import * +import numpy as np +from pyqtgraph.Point import Point +import pyqtgraph.functions as fn + +__all__ = ['GridItem'] +class GridItem(UIGraphicsItem): + """ + **Bases:** :class:`UIGraphicsItem ` + + Displays a rectangular grid of lines indicating major divisions within a coordinate system. + Automatically determines what divisions to use. + """ + + def __init__(self): + UIGraphicsItem.__init__(self) + #QtGui.QGraphicsItem.__init__(self, *args) + #self.setFlag(QtGui.QGraphicsItem.ItemClipsToShape) + #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) + + self.picture = None + + + def viewRangeChanged(self): + UIGraphicsItem.viewRangeChanged(self) + self.picture = None + #UIGraphicsItem.viewRangeChanged(self) + #self.update() + + def paint(self, p, opt, widget): + #p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) + #p.drawRect(self.boundingRect()) + #UIGraphicsItem.paint(self, p, opt, widget) + ### draw picture + if self.picture is None: + #print "no pic, draw.." + self.generatePicture() + p.drawPicture(QtCore.QPointF(0, 0), self.picture) + #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) + #p.drawLine(0, -100, 0, 100) + #p.drawLine(-100, 0, 100, 0) + #print "drawing Grid." + + + def generatePicture(self): + self.picture = QtGui.QPicture() + p = QtGui.QPainter() + p.begin(self.picture) + + dt = fn.invertQTransform(self.viewTransform()) + vr = self.getViewWidget().rect() + unit = self.pixelWidth(), self.pixelHeight() + dim = [vr.width(), vr.height()] + lvr = self.boundingRect() + ul = np.array([lvr.left(), lvr.top()]) + br = np.array([lvr.right(), lvr.bottom()]) + + texts = [] + + if ul[1] > br[1]: + x = ul[1] + ul[1] = br[1] + br[1] = x + for i in [2,1,0]: ## Draw three different scales of grid + dist = br-ul + nlTarget = 10.**i + d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5) + ul1 = np.floor(ul / d) * d + br1 = np.ceil(br / d) * d + dist = br1-ul1 + nl = (dist / d) + 0.5 + #print "level", i + #print " dim", dim + #print " dist", dist + #print " d", d + #print " nl", nl + for ax in range(0,2): ## Draw grid for both axes + ppl = dim[ax] / nl[ax] + c = np.clip(3.*(ppl-3), 0., 30.) + linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c)) + textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2)) + #linePen.setCosmetic(True) + #linePen.setWidth(1) + bx = (ax+1) % 2 + for x in range(0, int(nl[ax])): + linePen.setCosmetic(False) + if ax == 0: + linePen.setWidthF(self.pixelWidth()) + #print "ax 0 height", self.pixelHeight() + else: + linePen.setWidthF(self.pixelHeight()) + #print "ax 1 width", self.pixelWidth() + p.setPen(linePen) + p1 = np.array([0.,0.]) + p2 = np.array([0.,0.]) + p1[ax] = ul1[ax] + x * d[ax] + p2[ax] = p1[ax] + p1[bx] = ul[bx] + p2[bx] = br[bx] + ## don't draw lines that are out of bounds. + if p1[ax] < min(ul[ax], br[ax]) or p1[ax] > max(ul[ax], br[ax]): + continue + p.drawLine(QtCore.QPointF(p1[0], p1[1]), QtCore.QPointF(p2[0], p2[1])) + if i < 2: + p.setPen(textPen) + if ax == 0: + x = p1[0] + unit[0] + y = ul[1] + unit[1] * 8. + else: + x = ul[0] + unit[0]*3 + y = p1[1] + unit[1] + texts.append((QtCore.QPointF(x, y), "%g"%p1[ax])) + tr = self.deviceTransform() + #tr.scale(1.5, 1.5) + p.setWorldTransform(fn.invertQTransform(tr)) + for t in texts: + x = tr.map(t[0]) + Point(0.5, 0.5) + p.drawText(x, t[1]) + p.end() diff --git a/pyqtgraph/graphicsItems/HistogramLUTItem.py b/pyqtgraph/graphicsItems/HistogramLUTItem.py new file mode 100644 index 00000000..5a3b63d6 --- /dev/null +++ b/pyqtgraph/graphicsItems/HistogramLUTItem.py @@ -0,0 +1,205 @@ +""" +GraphicsWidget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. +""" + + +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn +from .GraphicsWidget import GraphicsWidget +from .ViewBox import * +from .GradientEditorItem import * +from .LinearRegionItem import * +from .PlotDataItem import * +from .AxisItem import * +from .GridItem import * +from pyqtgraph.Point import Point +import pyqtgraph.functions as fn +import numpy as np +import pyqtgraph.debug as debug + + +__all__ = ['HistogramLUTItem'] + + +class HistogramLUTItem(GraphicsWidget): + """ + This is a graphicsWidget which provides controls for adjusting the display of an image. + Includes: + + - Image histogram + - Movable region over histogram to select black/white levels + - Gradient editor to define color lookup table for single-channel images + """ + + sigLookupTableChanged = QtCore.Signal(object) + sigLevelsChanged = QtCore.Signal(object) + sigLevelChangeFinished = QtCore.Signal(object) + + def __init__(self, image=None, fillHistogram=True): + """ + If *image* (ImageItem) is provided, then the control will be automatically linked to the image and changes to the control will be immediately reflected in the image's appearance. + By default, the histogram is rendered with a fill. For performance, set *fillHistogram* = False. + """ + GraphicsWidget.__init__(self) + self.lut = None + self.imageItem = None + + self.layout = QtGui.QGraphicsGridLayout() + self.setLayout(self.layout) + self.layout.setContentsMargins(1,1,1,1) + self.layout.setSpacing(0) + self.vb = ViewBox() + self.vb.setMaximumWidth(152) + self.vb.setMinimumWidth(45) + self.vb.setMouseEnabled(x=False, y=True) + self.gradient = GradientEditorItem() + self.gradient.setOrientation('right') + self.gradient.loadPreset('grey') + self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal) + self.region.setZValue(1000) + self.vb.addItem(self.region) + self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, showValues=False) + self.layout.addItem(self.axis, 0, 0) + self.layout.addItem(self.vb, 0, 1) + self.layout.addItem(self.gradient, 0, 2) + self.range = None + self.gradient.setFlag(self.gradient.ItemStacksBehindParent) + self.vb.setFlag(self.gradient.ItemStacksBehindParent) + + #self.grid = GridItem() + #self.vb.addItem(self.grid) + + self.gradient.sigGradientChanged.connect(self.gradientChanged) + self.region.sigRegionChanged.connect(self.regionChanging) + self.region.sigRegionChangeFinished.connect(self.regionChanged) + self.vb.sigRangeChanged.connect(self.viewRangeChanged) + self.plot = PlotDataItem() + self.plot.rotate(90) + self.fillHistogram(fillHistogram) + + self.vb.addItem(self.plot) + self.autoHistogramRange() + + if image is not None: + self.setImageItem(image) + #self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + + def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)): + if fill: + self.plot.setFillLevel(level) + self.plot.setFillBrush(color) + else: + self.plot.setFillLevel(None) + + #def sizeHint(self, *args): + #return QtCore.QSizeF(115, 200) + + def paint(self, p, *args): + pen = self.region.lines[0].pen + rgn = self.getLevels() + p1 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[0])) + p2 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[1])) + gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect()) + for pen in [fn.mkPen('k', width=3), pen]: + p.setPen(pen) + p.drawLine(p1, gradRect.bottomLeft()) + p.drawLine(p2, gradRect.topLeft()) + p.drawLine(gradRect.topLeft(), gradRect.topRight()) + p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight()) + #p.drawRect(self.boundingRect()) + + + def setHistogramRange(self, mn, mx, padding=0.1): + """Set the Y range on the histogram plot. This disables auto-scaling.""" + self.vb.enableAutoRange(self.vb.YAxis, False) + self.vb.setYRange(mn, mx, padding) + + #d = mx-mn + #mn -= d*padding + #mx += d*padding + #self.range = [mn,mx] + #self.updateRange() + #self.vb.setMouseEnabled(False, True) + #self.region.setBounds([mn,mx]) + + def autoHistogramRange(self): + """Enable auto-scaling on the histogram plot.""" + self.vb.enableAutoRange(self.vb.XYAxes) + #self.range = None + #self.updateRange() + #self.vb.setMouseEnabled(False, False) + + #def updateRange(self): + #self.vb.autoRange() + #if self.range is not None: + #self.vb.setYRange(*self.range) + #vr = self.vb.viewRect() + + #self.region.setBounds([vr.top(), vr.bottom()]) + + def setImageItem(self, img): + self.imageItem = img + img.sigImageChanged.connect(self.imageChanged) + img.setLookupTable(self.getLookupTable) ## send function pointer, not the result + #self.gradientChanged() + self.regionChanged() + self.imageChanged(autoLevel=True) + #self.vb.autoRange() + + def viewRangeChanged(self): + self.update() + + def gradientChanged(self): + if self.imageItem is not None: + if self.gradient.isLookupTrivial(): + self.imageItem.setLookupTable(None) #lambda x: x.astype(np.uint8)) + else: + self.imageItem.setLookupTable(self.getLookupTable) ## send function pointer, not the result + + self.lut = None + #if self.imageItem is not None: + #self.imageItem.setLookupTable(self.gradient.getLookupTable(512)) + self.sigLookupTableChanged.emit(self) + + def getLookupTable(self, img=None, n=None, alpha=None): + if n is None: + if img.dtype == np.uint8: + n = 256 + else: + n = 512 + if self.lut is None: + self.lut = self.gradient.getLookupTable(n, alpha=alpha) + return self.lut + + def regionChanged(self): + #if self.imageItem is not None: + #self.imageItem.setLevels(self.region.getRegion()) + self.sigLevelChangeFinished.emit(self) + #self.update() + + def regionChanging(self): + if self.imageItem is not None: + self.imageItem.setLevels(self.region.getRegion()) + self.sigLevelsChanged.emit(self) + self.update() + + def imageChanged(self, autoLevel=False, autoRange=False): + prof = debug.Profiler('HistogramLUTItem.imageChanged', disabled=True) + h = self.imageItem.getHistogram() + prof.mark('get histogram') + if h[0] is None: + return + self.plot.setData(*h) + prof.mark('set plot') + if autoLevel: + mn = h[0][0] + mx = h[0][-1] + self.region.setRegion([mn, mx]) + prof.mark('set region') + prof.finish() + + def getLevels(self): + return self.region.getRegion() + + def setLevels(self, mn, mx): + self.region.setRegion([mn, mx]) diff --git a/pyqtgraph/graphicsItems/ImageItem.py b/pyqtgraph/graphicsItems/ImageItem.py new file mode 100644 index 00000000..530db7fb --- /dev/null +++ b/pyqtgraph/graphicsItems/ImageItem.py @@ -0,0 +1,453 @@ +from pyqtgraph.Qt import QtGui, QtCore +import numpy as np +import collections +import pyqtgraph.functions as fn +import pyqtgraph.debug as debug +from .GraphicsObject import GraphicsObject + +__all__ = ['ImageItem'] +class ImageItem(GraphicsObject): + """ + **Bases:** :class:`GraphicsObject ` + + GraphicsObject displaying an image. Optimized for rapid update (ie video display). + This item displays either a 2D numpy array (height, width) or + a 3D array (height, width, RGBa). This array is optionally scaled (see + :func:`setLevels `) and/or colored + with a lookup table (see :func:`setLookupTable `) + before being displayed. + + ImageItem is frequently used in conjunction with + :class:`HistogramLUTItem ` or + :class:`HistogramLUTWidget ` to provide a GUI + for controlling the levels and lookup table used to display the image. + """ + + + sigImageChanged = QtCore.Signal() + sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu + + def __init__(self, image=None, **kargs): + """ + See :func:`setImage ` for all allowed initialization arguments. + """ + GraphicsObject.__init__(self) + #self.pixmapItem = QtGui.QGraphicsPixmapItem(self) + #self.qimage = QtGui.QImage() + #self._pixmap = None + self.menu = None + self.image = None ## original image data + self.qimage = None ## rendered image for display + #self.clipMask = None + + self.paintMode = None + + self.levels = None ## [min, max] or [[redMin, redMax], ...] + self.lut = None + + #self.clipLevel = None + self.drawKernel = None + self.border = None + self.removable = False + + if image is not None: + self.setImage(image, **kargs) + else: + self.setOpts(**kargs) + + def setCompositionMode(self, mode): + """Change the composition mode of the item (see QPainter::CompositionMode + in the Qt documentation). This is useful when overlaying multiple ImageItems. + + ============================================ ============================================================ + **Most common arguments:** + QtGui.QPainter.CompositionMode_SourceOver Default; image replaces the background if it + is opaque. Otherwise, it uses the alpha channel to blend + the image with the background. + QtGui.QPainter.CompositionMode_Overlay The image color is mixed with the background color to + reflect the lightness or darkness of the background. + QtGui.QPainter.CompositionMode_Plus Both the alpha and color of the image and background pixels + are added together. + QtGui.QPainter.CompositionMode_Multiply The output is the image color multiplied by the background. + ============================================ ============================================================ + """ + self.paintMode = mode + self.update() + + ## use setOpacity instead. + #def setAlpha(self, alpha): + #self.setOpacity(alpha) + #self.updateImage() + + def setBorder(self, b): + self.border = fn.mkPen(b) + self.update() + + def width(self): + if self.image is None: + return None + return self.image.shape[0] + + def height(self): + if self.image is None: + return None + return self.image.shape[1] + + def boundingRect(self): + if self.image is None: + return QtCore.QRectF(0., 0., 0., 0.) + return QtCore.QRectF(0., 0., float(self.width()), float(self.height())) + + #def setClipLevel(self, level=None): + #self.clipLevel = level + #self.updateImage() + + #def paint(self, p, opt, widget): + #pass + #if self.pixmap is not None: + #p.drawPixmap(0, 0, self.pixmap) + #print "paint" + + def setLevels(self, levels, update=True): + """ + Set image scaling levels. Can be one of: + + * [blackLevel, whiteLevel] + * [[minRed, maxRed], [minGreen, maxGreen], [minBlue, maxBlue]] + + Only the first format is compatible with lookup tables. See :func:`makeARGB ` + for more details on how levels are applied. + """ + self.levels = levels + if update: + self.updateImage() + + def getLevels(self): + return self.levels + #return self.whiteLevel, self.blackLevel + + def setLookupTable(self, lut, update=True): + """ + Set the lookup table (numpy array) to use for this image. (see + :func:`makeARGB ` for more information on how this is used). + Optionally, lut can be a callable that accepts the current image as an + argument and returns the lookup table to use. + + Ordinarily, this table is supplied by a :class:`HistogramLUTItem ` + or :class:`GradientEditorItem `. + """ + self.lut = lut + if update: + self.updateImage() + + def setOpts(self, update=True, **kargs): + if 'lut' in kargs: + self.setLookupTable(kargs['lut'], update=update) + if 'levels' in kargs: + self.setLevels(kargs['levels'], update=update) + #if 'clipLevel' in kargs: + #self.setClipLevel(kargs['clipLevel']) + if 'opacity' in kargs: + self.setOpacity(kargs['opacity']) + if 'compositionMode' in kargs: + self.setCompositionMode(kargs['compositionMode']) + if 'border' in kargs: + self.setBorder(kargs['border']) + if 'removable' in kargs: + self.removable = kargs['removable'] + self.menu = None + + def setRect(self, rect): + """Scale and translate the image to fit within rect (must be a QRect or QRectF).""" + self.resetTransform() + self.translate(rect.left(), rect.top()) + self.scale(rect.width() / self.width(), rect.height() / self.height()) + + def setImage(self, image=None, autoLevels=None, **kargs): + """ + Update the image displayed by this item. For more information on how the image + is processed before displaying, see :func:`makeARGB ` + + ================= ========================================================================= + **Arguments:** + image (numpy array) Specifies the image data. May be 2D (width, height) or + 3D (width, height, RGBa). The array dtype must be integer or floating + point of any bit depth. For 3D arrays, the third dimension must + be of length 3 (RGB) or 4 (RGBA). + autoLevels (bool) If True, this forces the image to automatically select + levels based on the maximum and minimum values in the data. + By default, this argument is true unless the levels argument is + given. + lut (numpy array) The color lookup table to use when displaying the image. + See :func:`setLookupTable `. + levels (min, max) The minimum and maximum values to use when rescaling the image + data. By default, this will be set to the minimum and maximum values + in the image. If the image array has dtype uint8, no rescaling is necessary. + opacity (float 0.0-1.0) + compositionMode see :func:`setCompositionMode ` + border Sets the pen used when drawing the image border. Default is None. + ================= ========================================================================= + """ + prof = debug.Profiler('ImageItem.setImage', disabled=True) + + gotNewData = False + if image is None: + if self.image is None: + return + else: + gotNewData = True + shapeChanged = (self.image is None or image.shape != self.image.shape) + self.image = image.view(np.ndarray) + if shapeChanged: + self.prepareGeometryChange() + self.informViewBoundsChanged() + + prof.mark('1') + + if autoLevels is None: + if 'levels' in kargs: + autoLevels = False + else: + autoLevels = True + if autoLevels: + img = self.image + while img.size > 2**16: + img = img[::2, ::2] + mn, mx = img.min(), img.max() + if mn == mx: + mn = 0 + mx = 255 + kargs['levels'] = [mn,mx] + prof.mark('2') + + self.setOpts(update=False, **kargs) + prof.mark('3') + + self.qimage = None + self.update() + prof.mark('4') + + if gotNewData: + self.sigImageChanged.emit() + + + prof.finish() + + + + def updateImage(self, *args, **kargs): + ## used for re-rendering qimage from self.image. + + ## can we make any assumptions here that speed things up? + ## dtype, range, size are all the same? + defaults = { + 'autoLevels': False, + } + defaults.update(kargs) + return self.setImage(*args, **defaults) + + + + + def render(self): + prof = debug.Profiler('ImageItem.render', disabled=True) + if self.image is None or self.image.size == 0: + return + if isinstance(self.lut, collections.Callable): + lut = self.lut(self.image) + else: + lut = self.lut + #print lut.shape + #print self.lut + + argb, alpha = fn.makeARGB(self.image, lut=lut, levels=self.levels) + self.qimage = fn.makeQImage(argb, alpha) + prof.finish() + + + def paint(self, p, *args): + prof = debug.Profiler('ImageItem.paint', disabled=True) + if self.image is None: + return + if self.qimage is None: + self.render() + if self.qimage is None: + return + prof.mark('render QImage') + if self.paintMode is not None: + p.setCompositionMode(self.paintMode) + prof.mark('set comp mode') + + p.drawImage(QtCore.QPointF(0,0), self.qimage) + prof.mark('p.drawImage') + if self.border is not None: + p.setPen(self.border) + p.drawRect(self.boundingRect()) + prof.finish() + + def save(self, fileName, *args): + """Save this image to file. Note that this saves the visible image (after scale/color changes), not the original data.""" + if self.qimage is None: + self.render() + self.qimage.save(fileName, *args) + + def getHistogram(self, bins=500, step=3): + """Returns x and y arrays containing the histogram values for the current image. + The step argument causes pixels to be skipped when computing the histogram to save time. + This method is also used when automatically computing levels. + """ + if self.image is None: + return None,None + stepData = self.image[::step, ::step] + hist = np.histogram(stepData, bins=bins) + return hist[1][:-1], hist[0] + + def setPxMode(self, b): + """ + Set whether the item ignores transformations and draws directly to screen pixels. + If True, the item will not inherit any scale or rotation transformations from its + parent items, but its position will be transformed as usual. + (see GraphicsItem::ItemIgnoresTransformations in the Qt documentation) + """ + self.setFlag(self.ItemIgnoresTransformations, b) + + def setScaledMode(self): + self.setPxMode(False) + + def getPixmap(self): + if self.qimage is None: + self.render() + if self.qimage is None: + return None + return QtGui.QPixmap.fromImage(self.qimage) + + def pixelSize(self): + """return scene-size of a single pixel in the image""" + br = self.sceneBoundingRect() + if self.image is None: + return 1,1 + return br.width()/self.width(), br.height()/self.height() + + #def mousePressEvent(self, ev): + #if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: + #self.drawAt(ev.pos(), ev) + #ev.accept() + #else: + #ev.ignore() + + #def mouseMoveEvent(self, ev): + ##print "mouse move", ev.pos() + #if self.drawKernel is not None: + #self.drawAt(ev.pos(), ev) + + #def mouseReleaseEvent(self, ev): + #pass + + def mouseDragEvent(self, ev): + if ev.button() != QtCore.Qt.LeftButton: + ev.ignore() + return + elif self.drawKernel is not None: + ev.accept() + self.drawAt(ev.pos(), ev) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton: + if self.raiseContextMenu(ev): + ev.accept() + if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: + self.drawAt(ev.pos(), ev) + + def raiseContextMenu(self, ev): + menu = self.getMenu() + if menu is None: + return False + menu = self.scene().addParentContextMenus(self, menu, ev) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + return True + + def getMenu(self): + if self.menu is None: + if not self.removable: + return None + self.menu = QtGui.QMenu() + self.menu.setTitle("Image") + remAct = QtGui.QAction("Remove image", self.menu) + remAct.triggered.connect(self.removeClicked) + self.menu.addAction(remAct) + self.menu.remAct = remAct + return self.menu + + + def hoverEvent(self, ev): + if not ev.isExit() and self.drawKernel is not None and ev.acceptDrags(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. + ev.acceptClicks(QtCore.Qt.RightButton) + #self.box.setBrush(fn.mkBrush('w')) + elif not ev.isExit() and self.removable: + ev.acceptClicks(QtCore.Qt.RightButton) ## accept context menu clicks + #else: + #self.box.setBrush(self.brush) + #self.update() + + + + def tabletEvent(self, ev): + print(ev.device()) + print(ev.pointerType()) + print(ev.pressure()) + + def drawAt(self, pos, ev=None): + pos = [int(pos.x()), int(pos.y())] + dk = self.drawKernel + kc = self.drawKernelCenter + sx = [0,dk.shape[0]] + sy = [0,dk.shape[1]] + tx = [pos[0] - kc[0], pos[0] - kc[0]+ dk.shape[0]] + ty = [pos[1] - kc[1], pos[1] - kc[1]+ dk.shape[1]] + + for i in [0,1]: + dx1 = -min(0, tx[i]) + dx2 = min(0, self.image.shape[0]-tx[i]) + tx[i] += dx1+dx2 + sx[i] += dx1+dx2 + + dy1 = -min(0, ty[i]) + dy2 = min(0, self.image.shape[1]-ty[i]) + ty[i] += dy1+dy2 + sy[i] += dy1+dy2 + + ts = (slice(tx[0],tx[1]), slice(ty[0],ty[1])) + ss = (slice(sx[0],sx[1]), slice(sy[0],sy[1])) + mask = self.drawMask + src = dk + + if isinstance(self.drawMode, collections.Callable): + self.drawMode(dk, self.image, mask, ss, ts, ev) + else: + src = src[ss] + if self.drawMode == 'set': + if mask is not None: + mask = mask[ss] + self.image[ts] = self.image[ts] * (1-mask) + src * mask + else: + self.image[ts] = src + elif self.drawMode == 'add': + self.image[ts] += src + else: + raise Exception("Unknown draw mode '%s'" % self.drawMode) + self.updateImage() + + def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'): + self.drawKernel = kernel + self.drawKernelCenter = center + self.drawMode = mode + self.drawMask = mask + + def removeClicked(self): + ## Send remove event only after we have exited the menu event handler + self.removeTimer = QtCore.QTimer() + self.removeTimer.timeout.connect(lambda: self.sigRemoveRequested.emit(self)) + self.removeTimer.start(0) + diff --git a/pyqtgraph/graphicsItems/InfiniteLine.py b/pyqtgraph/graphicsItems/InfiniteLine.py new file mode 100644 index 00000000..4f0df863 --- /dev/null +++ b/pyqtgraph/graphicsItems/InfiniteLine.py @@ -0,0 +1,277 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.Point import Point +from .GraphicsObject import GraphicsObject +import pyqtgraph.functions as fn +import numpy as np +import weakref + + +__all__ = ['InfiniteLine'] +class InfiniteLine(GraphicsObject): + """ + **Bases:** :class:`GraphicsObject ` + + Displays a line of infinite length. + This line may be dragged to indicate a position in data coordinates. + + =============================== =================================================== + **Signals** + sigDragged(self) + sigPositionChangeFinished(self) + sigPositionChanged(self) + =============================== =================================================== + """ + + sigDragged = QtCore.Signal(object) + sigPositionChangeFinished = QtCore.Signal(object) + sigPositionChanged = QtCore.Signal(object) + + def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None): + """ + ============= ================================================================== + **Arguments** + pos Position of the line. This can be a QPointF or a single value for + vertical/horizontal lines. + angle Angle of line in degrees. 0 is horizontal, 90 is vertical. + pen Pen to use when drawing line. Can be any arguments that are valid + for :func:`mkPen `. Default pen is transparent + yellow. + movable If True, the line can be dragged to a new position by the user. + bounds Optional [min, max] bounding values. Bounds are only valid if the + line is vertical or horizontal. + ============= ================================================================== + """ + + GraphicsObject.__init__(self) + + if bounds is None: ## allowed value boundaries for orthogonal lines + self.maxRange = [None, None] + else: + self.maxRange = bounds + self.moving = False + self.setMovable(movable) + self.mouseHovering = False + self.p = [0, 0] + self.setAngle(angle) + if pos is None: + pos = Point(0,0) + self.setPos(pos) + + if pen is None: + pen = (200, 200, 100) + self.setPen(pen) + self.currentPen = self.pen + #self.setFlag(self.ItemSendsScenePositionChanges) + + def setMovable(self, m): + """Set whether the line is movable by the user.""" + self.movable = m + self.setAcceptHoverEvents(m) + + def setBounds(self, bounds): + """Set the (minimum, maximum) allowable values when dragging.""" + self.maxRange = bounds + self.setValue(self.value()) + + def setPen(self, pen): + """Set the pen for drawing the line. Allowable arguments are any that are valid + for :func:`mkPen `.""" + self.pen = fn.mkPen(pen) + self.currentPen = self.pen + self.update() + + def setAngle(self, angle): + """ + Takes angle argument in degrees. + 0 is horizontal; 90 is vertical. + + Note that the use of value() and setValue() changes if the line is + not vertical or horizontal. + """ + self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135 + self.resetTransform() + self.rotate(self.angle) + self.update() + + def setPos(self, pos): + + if type(pos) in [list, tuple]: + newPos = pos + elif isinstance(pos, QtCore.QPointF): + newPos = [pos.x(), pos.y()] + else: + if self.angle == 90: + newPos = [pos, 0] + elif self.angle == 0: + newPos = [0, pos] + else: + raise Exception("Must specify 2D coordinate for non-orthogonal lines.") + + ## check bounds (only works for orthogonal lines) + if self.angle == 90: + if self.maxRange[0] is not None: + newPos[0] = max(newPos[0], self.maxRange[0]) + if self.maxRange[1] is not None: + newPos[0] = min(newPos[0], self.maxRange[1]) + elif self.angle == 0: + if self.maxRange[0] is not None: + newPos[1] = max(newPos[1], self.maxRange[0]) + if self.maxRange[1] is not None: + newPos[1] = min(newPos[1], self.maxRange[1]) + + if self.p != newPos: + self.p = newPos + GraphicsObject.setPos(self, Point(self.p)) + self.update() + self.sigPositionChanged.emit(self) + + def getXPos(self): + return self.p[0] + + def getYPos(self): + return self.p[1] + + def getPos(self): + return self.p + + def value(self): + """Return the value of the line. Will be a single number for horizontal and + vertical lines, and a list of [x,y] values for diagonal lines.""" + if self.angle%180 == 0: + return self.getYPos() + elif self.angle%180 == 90: + return self.getXPos() + else: + return self.getPos() + + def setValue(self, v): + """Set the position of the line. If line is horizontal or vertical, v can be + a single value. Otherwise, a 2D coordinate must be specified (list, tuple and + QPointF are all acceptable).""" + self.setPos(v) + + ## broken in 4.7 + #def itemChange(self, change, val): + #if change in [self.ItemScenePositionHasChanged, self.ItemSceneHasChanged]: + #self.updateLine() + #print "update", change + #print self.getBoundingParents() + #else: + #print "ignore", change + #return GraphicsObject.itemChange(self, change, val) + + def boundingRect(self): + #br = UIGraphicsItem.boundingRect(self) + br = self.viewRect() + ## add a 4-pixel radius around the line for mouse interaction. + + px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line + if px is None: + px = 0 + br.setBottom(-px*4) + br.setTop(px*4) + return br.normalized() + + def paint(self, p, *args): + br = self.boundingRect() + p.setPen(self.currentPen) + p.drawLine(Point(br.right(), 0), Point(br.left(), 0)) + #p.drawRect(self.boundingRect()) + + def dataBounds(self, axis, frac=1.0, orthoRange=None): + if axis == 0: + return None ## x axis should never be auto-scaled + else: + return (0,0) + + #def mousePressEvent(self, ev): + #if self.movable and ev.button() == QtCore.Qt.LeftButton: + #ev.accept() + #self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) + #else: + #ev.ignore() + + #def mouseMoveEvent(self, ev): + #self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) + ##self.emit(QtCore.SIGNAL('dragged'), self) + #self.sigDragged.emit(self) + #self.hasMoved = True + + #def mouseReleaseEvent(self, ev): + #if self.hasMoved and ev.button() == QtCore.Qt.LeftButton: + #self.hasMoved = False + ##self.emit(QtCore.SIGNAL('positionChangeFinished'), self) + #self.sigPositionChangeFinished.emit(self) + + def mouseDragEvent(self, ev): + if self.movable and ev.button() == QtCore.Qt.LeftButton: + if ev.isStart(): + self.moving = True + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + self.startPosition = self.pos() + ev.accept() + + if not self.moving: + return + + #pressDelta = self.mapToParent(ev.buttonDownPos()) - Point(self.p) + self.setPos(self.cursorOffset + self.mapToParent(ev.pos())) + self.sigDragged.emit(self) + if ev.isFinish(): + self.moving = False + self.sigPositionChangeFinished.emit(self) + #else: + #print ev + + + def mouseClickEvent(self, ev): + if self.moving and ev.button() == QtCore.Qt.RightButton: + ev.accept() + self.setPos(self.startPosition) + self.moving = False + self.sigDragged.emit(self) + self.sigPositionChangeFinished.emit(self) + + def hoverEvent(self, ev): + if (not ev.isExit()) and self.movable and ev.acceptDrags(QtCore.Qt.LeftButton): + self.setMouseHover(True) + else: + self.setMouseHover(False) + + def setMouseHover(self, hover): + ## Inform the item that the mouse is(not) hovering over it + if self.mouseHovering == hover: + return + self.mouseHovering = hover + if hover: + self.currentPen = fn.mkPen(255, 0,0) + else: + self.currentPen = self.pen + self.update() + + #def hoverEnterEvent(self, ev): + #print "line hover enter" + #ev.ignore() + #self.updateHoverPen() + + #def hoverMoveEvent(self, ev): + #print "line hover move" + #ev.ignore() + #self.updateHoverPen() + + #def hoverLeaveEvent(self, ev): + #print "line hover leave" + #ev.ignore() + #self.updateHoverPen(False) + + #def updateHoverPen(self, hover=None): + #if hover is None: + #scene = self.scene() + #hover = scene.claimEvent(self, QtCore.Qt.LeftButton, scene.Drag) + + #if hover: + #self.currentPen = fn.mkPen(255, 0,0) + #else: + #self.currentPen = self.pen + #self.update() + diff --git a/pyqtgraph/graphicsItems/IsocurveItem.py b/pyqtgraph/graphicsItems/IsocurveItem.py new file mode 100644 index 00000000..01ef57b6 --- /dev/null +++ b/pyqtgraph/graphicsItems/IsocurveItem.py @@ -0,0 +1,121 @@ + + +from .GraphicsObject import * +import pyqtgraph.functions as fn +from pyqtgraph.Qt import QtGui, QtCore + + +class IsocurveItem(GraphicsObject): + """ + **Bases:** :class:`GraphicsObject ` + + Item displaying an isocurve of a 2D array.To align this item correctly with an + ImageItem,call isocurve.setParentItem(image) + """ + + + def __init__(self, data=None, level=0, pen='w'): + """ + Create a new isocurve item. + + ============= =============================================================== + **Arguments** + data A 2-dimensional ndarray. Can be initialized as None, and set + later using :func:`setData ` + level The cutoff value at which to draw the isocurve. + pen The color of the curve item. Can be anything valid for + :func:`mkPen ` + ============= =============================================================== + """ + GraphicsObject.__init__(self) + + self.level = level + self.data = None + self.path = None + self.setPen(pen) + self.setData(data, level) + + + + #if data is not None and level is not None: + #self.updateLines(data, level) + + + def setData(self, data, level=None): + """ + Set the data/image to draw isocurves for. + + ============= ======================================================================== + **Arguments** + data A 2-dimensional ndarray. + level The cutoff value at which to draw the curve. If level is not specified, + the previously set level is used. + ============= ======================================================================== + """ + if level is None: + level = self.level + self.level = level + self.data = data + self.path = None + self.prepareGeometryChange() + self.update() + + + def setLevel(self, level): + """Set the level at which the isocurve is drawn.""" + self.level = level + self.path = None + self.update() + + + def setPen(self, *args, **kwargs): + """Set the pen used to draw the isocurve. Arguments can be any that are valid + for :func:`mkPen `""" + self.pen = fn.mkPen(*args, **kwargs) + self.update() + + def setBrush(self, *args, **kwargs): + """Set the brush used to draw the isocurve. Arguments can be any that are valid + for :func:`mkBrush `""" + self.brush = fn.mkBrush(*args, **kwargs) + self.update() + + + def updateLines(self, data, level): + ##print "data:", data + ##print "level", level + #lines = fn.isocurve(data, level) + ##print len(lines) + #self.path = QtGui.QPainterPath() + #for line in lines: + #self.path.moveTo(*line[0]) + #self.path.lineTo(*line[1]) + #self.update() + self.setData(data, level) + + def boundingRect(self): + if self.data is None: + return QtCore.QRectF() + if self.path is None: + self.generatePath() + return self.path.boundingRect() + + def generatePath(self): + if self.data is None: + self.path = None + return + lines = fn.isocurve(self.data, self.level, connected=True, extendToEdge=True) + self.path = QtGui.QPainterPath() + for line in lines: + self.path.moveTo(*line[0]) + for p in line[1:]: + self.path.lineTo(*p) + + def paint(self, p, *args): + if self.data is None: + return + if self.path is None: + self.generatePath() + p.setPen(self.pen) + p.drawPath(self.path) + \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/ItemGroup.py b/pyqtgraph/graphicsItems/ItemGroup.py new file mode 100644 index 00000000..930fdf80 --- /dev/null +++ b/pyqtgraph/graphicsItems/ItemGroup.py @@ -0,0 +1,23 @@ +from pyqtgraph.Qt import QtGui, QtCore +from .GraphicsObject import GraphicsObject + +__all__ = ['ItemGroup'] +class ItemGroup(GraphicsObject): + """ + Replacement for QGraphicsItemGroup + """ + + def __init__(self, *args): + GraphicsObject.__init__(self, *args) + if hasattr(self, "ItemHasNoContents"): + self.setFlag(self.ItemHasNoContents) + + def boundingRect(self): + return QtCore.QRectF() + + def paint(self, *args): + pass + + def addItem(self, item): + item.setParentItem(self) + diff --git a/pyqtgraph/graphicsItems/LabelItem.py b/pyqtgraph/graphicsItems/LabelItem.py new file mode 100644 index 00000000..6101c4bc --- /dev/null +++ b/pyqtgraph/graphicsItems/LabelItem.py @@ -0,0 +1,142 @@ +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn +import pyqtgraph as pg +from .GraphicsWidget import GraphicsWidget +from .GraphicsWidgetAnchor import GraphicsWidgetAnchor + + +__all__ = ['LabelItem'] + +class LabelItem(GraphicsWidget, GraphicsWidgetAnchor): + """ + GraphicsWidget displaying text. + Used mainly as axis labels, titles, etc. + + Note: To display text inside a scaled view (ViewBox, PlotWidget, etc) use TextItem + """ + + + def __init__(self, text=' ', parent=None, angle=0, **args): + GraphicsWidget.__init__(self, parent) + GraphicsWidgetAnchor.__init__(self) + self.item = QtGui.QGraphicsTextItem(self) + self.opts = { + 'color': None, + 'justify': 'center' + } + self.opts.update(args) + self._sizeHint = {} + self.setText(text) + self.setAngle(angle) + + def setAttr(self, attr, value): + """Set default text properties. See setText() for accepted parameters.""" + self.opts[attr] = value + + def setText(self, text, **args): + """Set the text and text properties in the label. Accepts optional arguments for auto-generating + a CSS style string: + + ==================== ============================== + **Style Arguments:** + color (str) example: 'CCFF00' + size (str) example: '8pt' + bold (bool) + italic (bool) + ==================== ============================== + """ + self.text = text + opts = self.opts + for k in args: + opts[k] = args[k] + + optlist = [] + + color = self.opts['color'] + if color is None: + color = pg.getConfigOption('foreground') + color = fn.mkColor(color) + optlist.append('color: #' + fn.colorStr(color)[:6]) + if 'size' in opts: + optlist.append('font-size: ' + opts['size']) + if 'bold' in opts and opts['bold'] in [True, False]: + optlist.append('font-weight: ' + {True:'bold', False:'normal'}[opts['bold']]) + if 'italic' in opts and opts['italic'] in [True, False]: + optlist.append('font-style: ' + {True:'italic', False:'normal'}[opts['italic']]) + full = "%s" % ('; '.join(optlist), text) + #print full + self.item.setHtml(full) + self.updateMin() + self.resizeEvent(None) + self.updateGeometry() + + def resizeEvent(self, ev): + #c1 = self.boundingRect().center() + #c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos() + #dif = c1 - c2 + #self.item.moveBy(dif.x(), dif.y()) + #print c1, c2, dif, self.item.pos() + self.item.setPos(0,0) + bounds = self.itemRect() + left = self.mapFromItem(self.item, QtCore.QPointF(0,0)) - self.mapFromItem(self.item, QtCore.QPointF(1,0)) + rect = self.rect() + + if self.opts['justify'] == 'left': + if left.x() != 0: + bounds.moveLeft(rect.left()) + if left.y() < 0: + bounds.moveTop(rect.top()) + elif left.y() > 0: + bounds.moveBottom(rect.bottom()) + + elif self.opts['justify'] == 'center': + bounds.moveCenter(rect.center()) + #bounds = self.itemRect() + #self.item.setPos(self.width()/2. - bounds.width()/2., 0) + elif self.opts['justify'] == 'right': + if left.x() != 0: + bounds.moveRight(rect.right()) + if left.y() < 0: + bounds.moveBottom(rect.bottom()) + elif left.y() > 0: + bounds.moveTop(rect.top()) + #bounds = self.itemRect() + #self.item.setPos(self.width() - bounds.width(), 0) + + self.item.setPos(bounds.topLeft() - self.itemRect().topLeft()) + self.updateMin() + + def setAngle(self, angle): + self.angle = angle + self.item.resetTransform() + self.item.rotate(angle) + self.updateMin() + + + def updateMin(self): + bounds = self.itemRect() + self.setMinimumWidth(bounds.width()) + self.setMinimumHeight(bounds.height()) + + self._sizeHint = { + QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()), + QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()), + QtCore.Qt.MaximumSize: (-1, -1), #bounds.width()*2, bounds.height()*2), + QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this? + } + self.updateGeometry() + + def sizeHint(self, hint, constraint): + if hint not in self._sizeHint: + return QtCore.QSizeF(0, 0) + return QtCore.QSizeF(*self._sizeHint[hint]) + + def itemRect(self): + return self.item.mapRectToParent(self.item.boundingRect()) + + #def paint(self, p, *args): + #p.setPen(fn.mkPen('r')) + #p.drawRect(self.rect()) + #p.setPen(fn.mkPen('g')) + #p.drawRect(self.itemRect()) + diff --git a/pyqtgraph/graphicsItems/LegendItem.py b/pyqtgraph/graphicsItems/LegendItem.py new file mode 100644 index 00000000..69ddffea --- /dev/null +++ b/pyqtgraph/graphicsItems/LegendItem.py @@ -0,0 +1,173 @@ +from .GraphicsWidget import GraphicsWidget +from .LabelItem import LabelItem +from ..Qt import QtGui, QtCore +from .. import functions as fn +from ..Point import Point +from .GraphicsWidgetAnchor import GraphicsWidgetAnchor +import pyqtgraph as pg +__all__ = ['LegendItem'] + +class LegendItem(GraphicsWidget, GraphicsWidgetAnchor): + """ + Displays a legend used for describing the contents of a plot. + LegendItems are most commonly created by calling PlotItem.addLegend(). + + Note that this item should not be added directly to a PlotItem. Instead, + Make it a direct descendant of the PlotItem:: + + legend.setParentItem(plotItem) + + """ + def __init__(self, size=None, offset=None): + """ + ========== =============================================================== + Arguments + size Specifies the fixed size (width, height) of the legend. If + this argument is omitted, the legend will autimatically resize + to fit its contents. + offset Specifies the offset position relative to the legend's parent. + Positive values offset from the left or top; negative values + offset from the right or bottom. If offset is None, the + legend must be anchored manually by calling anchor() or + positioned by calling setPos(). + ========== =============================================================== + + """ + + + GraphicsWidget.__init__(self) + GraphicsWidgetAnchor.__init__(self) + self.setFlag(self.ItemIgnoresTransformations) + self.layout = QtGui.QGraphicsGridLayout() + self.setLayout(self.layout) + self.items = [] + self.size = size + self.offset = offset + if size is not None: + self.setGeometry(QtCore.QRectF(0, 0, self.size[0], self.size[1])) + + def setParentItem(self, p): + ret = GraphicsWidget.setParentItem(self, p) + if self.offset is not None: + offset = Point(self.offset) + anchorx = 1 if offset[0] <= 0 else 0 + anchory = 1 if offset[1] <= 0 else 0 + anchor = (anchorx, anchory) + self.anchor(itemPos=anchor, parentPos=anchor, offset=offset) + return ret + + def addItem(self, item, name): + """ + Add a new entry to the legend. + + =========== ======================================================== + Arguments + item A PlotDataItem from which the line and point style + of the item will be determined or an instance of + ItemSample (or a subclass), allowing the item display + to be customized. + title The title to display for this item. Simple HTML allowed. + =========== ======================================================== + """ + label = LabelItem(name) + if isinstance(item, ItemSample): + sample = item + else: + sample = ItemSample(item) + row = len(self.items) + self.items.append((sample, label)) + self.layout.addItem(sample, row, 0) + self.layout.addItem(label, row, 1) + self.updateSize() + + def removeItem(self, name): + """ + Removes one item from the legend. + + =========== ======================================================== + Arguments + title The title displayed for this item. + =========== ======================================================== + """ + # Thanks, Ulrich! + # cycle for a match + for sample, label in self.items: + if label.text == name: # hit + self.items.remove( (sample, label) ) # remove from itemlist + self.layout.removeItem(sample) # remove from layout + sample.close() # remove from drawing + self.layout.removeItem(label) + label.close() + self.updateSize() # redraq box + + def updateSize(self): + if self.size is not None: + return + + height = 0 + width = 0 + #print("-------") + for sample, label in self.items: + height += max(sample.height(), label.height()) + 3 + width = max(width, sample.width()+label.width()) + #print(width, height) + #print width, height + self.setGeometry(0, 0, width+25, height) + + def boundingRect(self): + return QtCore.QRectF(0, 0, self.width(), self.height()) + + def paint(self, p, *args): + p.setPen(fn.mkPen(255,255,255,100)) + p.setBrush(fn.mkBrush(100,100,100,50)) + p.drawRect(self.boundingRect()) + + def hoverEvent(self, ev): + ev.acceptDrags(QtCore.Qt.LeftButton) + + def mouseDragEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + dpos = ev.pos() - ev.lastPos() + self.autoAnchor(self.pos() + dpos) + +class ItemSample(GraphicsWidget): + """ Class responsible for drawing a single item in a LegendItem (sans label). + + This may be subclassed to draw custom graphics in a Legend. + """ + ## Todo: make this more generic; let each item decide how it should be represented. + def __init__(self, item): + GraphicsWidget.__init__(self) + self.item = item + + def boundingRect(self): + return QtCore.QRectF(0, 0, 20, 20) + + def paint(self, p, *args): + #p.setRenderHint(p.Antialiasing) # only if the data is antialiased. + opts = self.item.opts + + if opts.get('fillLevel',None) is not None and opts.get('fillBrush',None) is not None: + p.setBrush(fn.mkBrush(opts['fillBrush'])) + p.setPen(fn.mkPen(None)) + p.drawPolygon(QtGui.QPolygonF([QtCore.QPointF(2,18), QtCore.QPointF(18,2), QtCore.QPointF(18,18)])) + + if not isinstance(self.item, pg.ScatterPlotItem): + p.setPen(fn.mkPen(opts['pen'])) + p.drawLine(2, 18, 18, 2) + + symbol = opts.get('symbol', None) + if symbol is not None: + if isinstance(self.item, pg.PlotDataItem): + opts = self.item.scatter.opts + + pen = pg.mkPen(opts['pen']) + brush = pg.mkBrush(opts['brush']) + size = opts['size'] + + p.translate(10,10) + path = pg.graphicsItems.ScatterPlotItem.drawSymbol(p, symbol, size, pen, brush) + + + + diff --git a/pyqtgraph/graphicsItems/LinearRegionItem.py b/pyqtgraph/graphicsItems/LinearRegionItem.py new file mode 100644 index 00000000..a35e8efc --- /dev/null +++ b/pyqtgraph/graphicsItems/LinearRegionItem.py @@ -0,0 +1,291 @@ +from pyqtgraph.Qt import QtGui, QtCore +from .UIGraphicsItem import UIGraphicsItem +from .InfiniteLine import InfiniteLine +import pyqtgraph.functions as fn +import pyqtgraph.debug as debug + +__all__ = ['LinearRegionItem'] + +class LinearRegionItem(UIGraphicsItem): + """ + **Bases:** :class:`UIGraphicsItem ` + + Used for marking a horizontal or vertical region in plots. + The region can be dragged and is bounded by lines which can be dragged individually. + + =============================== ============================================================================= + **Signals:** + sigRegionChangeFinished(self) Emitted when the user has finished dragging the region (or one of its lines) + and when the region is changed programatically. + sigRegionChanged(self) Emitted while the user is dragging the region (or one of its lines) + and when the region is changed programatically. + =============================== ============================================================================= + """ + + sigRegionChangeFinished = QtCore.Signal(object) + sigRegionChanged = QtCore.Signal(object) + Vertical = 0 + Horizontal = 1 + + def __init__(self, values=[0,1], orientation=None, brush=None, movable=True, bounds=None): + """Create a new LinearRegionItem. + + ============= ===================================================================== + **Arguments** + values A list of the positions of the lines in the region. These are not + limits; limits can be set by specifying bounds. + orientation Options are LinearRegionItem.Vertical or LinearRegionItem.Horizontal. + If not specified it will be vertical. + brush Defines the brush that fills the region. Can be any arguments that + are valid for :func:`mkBrush `. Default is + transparent blue. + movable If True, the region and individual lines are movable by the user; if + False, they are static. + bounds Optional [min, max] bounding values for the region + ============= ===================================================================== + """ + + UIGraphicsItem.__init__(self) + if orientation is None: + orientation = LinearRegionItem.Vertical + self.orientation = orientation + self.bounds = QtCore.QRectF() + self.blockLineSignal = False + self.moving = False + self.mouseHovering = False + + if orientation == LinearRegionItem.Horizontal: + self.lines = [ + InfiniteLine(QtCore.QPointF(0, values[0]), 0, movable=movable, bounds=bounds), + InfiniteLine(QtCore.QPointF(0, values[1]), 0, movable=movable, bounds=bounds)] + elif orientation == LinearRegionItem.Vertical: + self.lines = [ + InfiniteLine(QtCore.QPointF(values[1], 0), 90, movable=movable, bounds=bounds), + InfiniteLine(QtCore.QPointF(values[0], 0), 90, movable=movable, bounds=bounds)] + else: + raise Exception('Orientation must be one of LinearRegionItem.Vertical or LinearRegionItem.Horizontal') + + + for l in self.lines: + l.setParentItem(self) + l.sigPositionChangeFinished.connect(self.lineMoveFinished) + l.sigPositionChanged.connect(self.lineMoved) + + if brush is None: + brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50)) + self.setBrush(brush) + + self.setMovable(movable) + + def getRegion(self): + """Return the values at the edges of the region.""" + #if self.orientation[0] == 'h': + #r = (self.bounds.top(), self.bounds.bottom()) + #else: + #r = (self.bounds.left(), self.bounds.right()) + r = [self.lines[0].value(), self.lines[1].value()] + return (min(r), max(r)) + + def setRegion(self, rgn): + """Set the values for the edges of the region. + + ============= ============================================== + **Arguments** + rgn A list or tuple of the lower and upper values. + ============= ============================================== + """ + if self.lines[0].value() == rgn[0] and self.lines[1].value() == rgn[1]: + return + self.blockLineSignal = True + self.lines[0].setValue(rgn[0]) + self.blockLineSignal = False + self.lines[1].setValue(rgn[1]) + #self.blockLineSignal = False + self.lineMoved() + self.lineMoveFinished() + + def setBrush(self, *br, **kargs): + """Set the brush that fills the region. Can have any arguments that are valid + for :func:`mkBrush `. + """ + self.brush = fn.mkBrush(*br, **kargs) + self.currentBrush = self.brush + + def setBounds(self, bounds): + """Optional [min, max] bounding values for the region. To have no bounds on the + region use [None, None]. + Does not affect the current position of the region unless it is outside the new bounds. + See :func:`setRegion ` to set the position + of the region.""" + for l in self.lines: + l.setBounds(bounds) + + def setMovable(self, m): + """Set lines to be movable by the user, or not. If lines are movable, they will + also accept HoverEvents.""" + for l in self.lines: + l.setMovable(m) + self.movable = m + self.setAcceptHoverEvents(m) + + def boundingRect(self): + br = UIGraphicsItem.boundingRect(self) + rng = self.getRegion() + if self.orientation == LinearRegionItem.Vertical: + br.setLeft(rng[0]) + br.setRight(rng[1]) + else: + br.setTop(rng[0]) + br.setBottom(rng[1]) + return br.normalized() + + def paint(self, p, *args): + #prof = debug.Profiler('LinearRegionItem.paint') + UIGraphicsItem.paint(self, p, *args) + p.setBrush(self.currentBrush) + p.setPen(fn.mkPen(None)) + p.drawRect(self.boundingRect()) + #prof.finish() + + def dataBounds(self, axis, frac=1.0, orthoRange=None): + if axis == self.orientation: + return self.getRegion() + else: + return None + + def lineMoved(self): + if self.blockLineSignal: + return + self.prepareGeometryChange() + #self.emit(QtCore.SIGNAL('regionChanged'), self) + self.sigRegionChanged.emit(self) + + def lineMoveFinished(self): + #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) + self.sigRegionChangeFinished.emit(self) + + + #def updateBounds(self): + #vb = self.view().viewRect() + #vals = [self.lines[0].value(), self.lines[1].value()] + #if self.orientation[0] == 'h': + #vb.setTop(min(vals)) + #vb.setBottom(max(vals)) + #else: + #vb.setLeft(min(vals)) + #vb.setRight(max(vals)) + #if vb != self.bounds: + #self.bounds = vb + #self.rect.setRect(vb) + + #def mousePressEvent(self, ev): + #if not self.movable: + #ev.ignore() + #return + #for l in self.lines: + #l.mousePressEvent(ev) ## pass event to both lines so they move together + ##if self.movable and ev.button() == QtCore.Qt.LeftButton: + ##ev.accept() + ##self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) + ##else: + ##ev.ignore() + + #def mouseReleaseEvent(self, ev): + #for l in self.lines: + #l.mouseReleaseEvent(ev) + + #def mouseMoveEvent(self, ev): + ##print "move", ev.pos() + #if not self.movable: + #return + #self.lines[0].blockSignals(True) # only want to update once + #for l in self.lines: + #l.mouseMoveEvent(ev) + #self.lines[0].blockSignals(False) + ##self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) + ##self.emit(QtCore.SIGNAL('dragged'), self) + + def mouseDragEvent(self, ev): + if not self.movable or int(ev.button() & QtCore.Qt.LeftButton) == 0: + return + ev.accept() + + if ev.isStart(): + bdp = ev.buttonDownPos() + self.cursorOffsets = [l.pos() - bdp for l in self.lines] + self.startPositions = [l.pos() for l in self.lines] + self.moving = True + + if not self.moving: + return + + #delta = ev.pos() - ev.lastPos() + self.lines[0].blockSignals(True) # only want to update once + for i, l in enumerate(self.lines): + l.setPos(self.cursorOffsets[i] + ev.pos()) + #l.setPos(l.pos()+delta) + #l.mouseDragEvent(ev) + self.lines[0].blockSignals(False) + self.prepareGeometryChange() + + if ev.isFinish(): + self.moving = False + self.sigRegionChangeFinished.emit(self) + else: + self.sigRegionChanged.emit(self) + + def mouseClickEvent(self, ev): + if self.moving and ev.button() == QtCore.Qt.RightButton: + ev.accept() + for i, l in enumerate(self.lines): + l.setPos(self.startPositions[i]) + self.moving = False + self.sigRegionChanged.emit(self) + self.sigRegionChangeFinished.emit(self) + + + def hoverEvent(self, ev): + if self.movable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + self.setMouseHover(True) + else: + self.setMouseHover(False) + + def setMouseHover(self, hover): + ## Inform the item that the mouse is(not) hovering over it + if self.mouseHovering == hover: + return + self.mouseHovering = hover + if hover: + c = self.brush.color() + c.setAlpha(c.alpha() * 2) + self.currentBrush = fn.mkBrush(c) + else: + self.currentBrush = self.brush + self.update() + + #def hoverEnterEvent(self, ev): + #print "rgn hover enter" + #ev.ignore() + #self.updateHoverBrush() + + #def hoverMoveEvent(self, ev): + #print "rgn hover move" + #ev.ignore() + #self.updateHoverBrush() + + #def hoverLeaveEvent(self, ev): + #print "rgn hover leave" + #ev.ignore() + #self.updateHoverBrush(False) + + #def updateHoverBrush(self, hover=None): + #if hover is None: + #scene = self.scene() + #hover = scene.claimEvent(self, QtCore.Qt.LeftButton, scene.Drag) + + #if hover: + #self.currentBrush = fn.mkBrush(255, 0,0,100) + #else: + #self.currentBrush = self.brush + #self.update() + diff --git a/pyqtgraph/graphicsItems/MultiPlotItem.py b/pyqtgraph/graphicsItems/MultiPlotItem.py new file mode 100644 index 00000000..d20467a9 --- /dev/null +++ b/pyqtgraph/graphicsItems/MultiPlotItem.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +""" +MultiPlotItem.py - Graphics item used for displaying an array of PlotItems +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from numpy import ndarray +from . import GraphicsLayout + +try: + from metaarray import * + HAVE_METAARRAY = True +except: + #raise + HAVE_METAARRAY = False + + +__all__ = ['MultiPlotItem'] +class MultiPlotItem(GraphicsLayout.GraphicsLayout): + """ + Automaticaly generates a grid of plots from a multi-dimensional array + """ + + def plot(self, data): + #self.layout.clear() + self.plots = [] + + if HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): + if data.ndim != 2: + raise Exception("MultiPlot currently only accepts 2D MetaArray.") + ic = data.infoCopy() + ax = 0 + for i in [0, 1]: + if 'cols' in ic[i]: + ax = i + break + #print "Plotting using axis %d as columns (%d plots)" % (ax, data.shape[ax]) + for i in range(data.shape[ax]): + pi = self.addPlot() + self.nextRow() + sl = [slice(None)] * 2 + sl[ax] = i + pi.plot(data[tuple(sl)]) + #self.layout.addItem(pi, i, 0) + self.plots.append((pi, i, 0)) + title = None + units = None + info = ic[ax]['cols'][i] + if 'title' in info: + title = info['title'] + elif 'name' in info: + title = info['name'] + if 'units' in info: + units = info['units'] + + pi.setLabel('left', text=title, units=units) + + else: + raise Exception("Data type %s not (yet?) supported for MultiPlot." % type(data)) + + def close(self): + for p in self.plots: + p[0].close() + self.plots = None + self.clear() + + + diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py new file mode 100644 index 00000000..28214552 --- /dev/null +++ b/pyqtgraph/graphicsItems/PlotCurveItem.py @@ -0,0 +1,560 @@ +from pyqtgraph.Qt import QtGui, QtCore +try: + from pyqtgraph.Qt import QtOpenGL + HAVE_OPENGL = True +except: + HAVE_OPENGL = False + +import numpy as np +from .GraphicsObject import GraphicsObject +import pyqtgraph.functions as fn +from pyqtgraph import debug +from pyqtgraph.Point import Point +import pyqtgraph as pg +import struct, sys + +__all__ = ['PlotCurveItem'] +class PlotCurveItem(GraphicsObject): + + + """ + Class representing a single plot curve. Instances of this class are created + automatically as part of PlotDataItem; these rarely need to be instantiated + directly. + + Features: + + - Fast data update + - Fill under curve + - Mouse interaction + + ==================== =============================================== + **Signals:** + sigPlotChanged(self) Emitted when the data being plotted has changed + sigClicked(self) Emitted when the curve is clicked + ==================== =============================================== + """ + + sigPlotChanged = QtCore.Signal(object) + sigClicked = QtCore.Signal(object) + + def __init__(self, *args, **kargs): + """ + Forwards all arguments to :func:`setData `. + + Some extra arguments are accepted as well: + + ============== ======================================================= + **Arguments:** + parent The parent GraphicsObject (optional) + clickable If True, the item will emit sigClicked when it is + clicked on. Defaults to False. + ============== ======================================================= + """ + GraphicsObject.__init__(self, kargs.get('parent', None)) + self.clear() + self.path = None + self.fillPath = None + self._boundsCache = [None, None] + + ## this is disastrous for performance. + #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) + + self.metaData = {} + self.opts = { + 'pen': fn.mkPen('w'), + 'shadowPen': None, + 'fillLevel': None, + 'brush': None, + 'stepMode': False, + 'name': None, + 'antialias': pg.getConfigOption('antialias'),\ + 'connect': 'all', + } + self.setClickable(kargs.get('clickable', False)) + self.setData(*args, **kargs) + + def implements(self, interface=None): + ints = ['plotData'] + if interface is None: + return ints + return interface in ints + + def setClickable(self, s): + """Sets whether the item responds to mouse clicks.""" + self.clickable = s + + + def getData(self): + return self.xData, self.yData + + def dataBounds(self, ax, frac=1.0, orthoRange=None): + ## Need this to run as fast as possible. + ## check cache first: + cache = self._boundsCache[ax] + if cache is not None and cache[0] == (frac, orthoRange): + return cache[1] + + (x, y) = self.getData() + if x is None or len(x) == 0: + return (None, None) + + if ax == 0: + d = x + d2 = y + elif ax == 1: + d = y + d2 = x + + ## If an orthogonal range is specified, mask the data now + if orthoRange is not None: + mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) + d = d[mask] + #d2 = d2[mask] + + if len(d) == 0: + return (None, None) + + ## Get min/max (or percentiles) of the requested data range + if frac >= 1.0: + b = (np.nanmin(d), np.nanmax(d)) + elif frac <= 0.0: + raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) + else: + mask = np.isfinite(d) + d = d[mask] + b = np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) + + ## adjust for fill level + if ax == 1 and self.opts['fillLevel'] is not None: + b = (min(b[0], self.opts['fillLevel']), max(b[1], self.opts['fillLevel'])) + + ## Add pen width only if it is non-cosmetic. + pen = self.opts['pen'] + spen = self.opts['shadowPen'] + if not pen.isCosmetic(): + b = (b[0] - pen.widthF()*0.7072, b[1] + pen.widthF()*0.7072) + if spen is not None and not spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen: + b = (b[0] - spen.widthF()*0.7072, b[1] + spen.widthF()*0.7072) + + self._boundsCache[ax] = [(frac, orthoRange), b] + return b + + def pixelPadding(self): + pen = self.opts['pen'] + spen = self.opts['shadowPen'] + w = 0 + if pen.isCosmetic(): + w += pen.widthF()*0.7072 + if spen is not None and spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen: + w = max(w, spen.widthF()*0.7072) + return w + + def boundingRect(self): + if self._boundingRect is None: + (xmn, xmx) = self.dataBounds(ax=0) + (ymn, ymx) = self.dataBounds(ax=1) + if xmn is None: + return QtCore.QRectF() + + px = py = 0.0 + pxPad = self.pixelPadding() + if pxPad > 0: + # determine length of pixel in local x, y directions + px, py = self.pixelVectors() + px = 0 if px is None else px.length() + py = 0 if py is None else py.length() + + # return bounds expanded by pixel size + px *= pxPad + py *= pxPad + #px += self._maxSpotWidth * 0.5 + #py += self._maxSpotWidth * 0.5 + self._boundingRect = QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) + return self._boundingRect + + def viewTransformChanged(self): + self.invalidateBounds() + self.prepareGeometryChange() + + #def boundingRect(self): + #if self._boundingRect is None: + #(x, y) = self.getData() + #if x is None or y is None or len(x) == 0 or len(y) == 0: + #return QtCore.QRectF() + + + #if self.opts['shadowPen'] is not None: + #lineWidth = (max(self.opts['pen'].width(), self.opts['shadowPen'].width()) + 1) + #else: + #lineWidth = (self.opts['pen'].width()+1) + + + #pixels = self.pixelVectors() + #if pixels == (None, None): + #pixels = [Point(0,0), Point(0,0)] + + #xmin = x.min() + #xmax = x.max() + #ymin = y.min() + #ymax = y.max() + + #if self.opts['fillLevel'] is not None: + #ymin = min(ymin, self.opts['fillLevel']) + #ymax = max(ymax, self.opts['fillLevel']) + + #xmin -= pixels[0].x() * lineWidth + #xmax += pixels[0].x() * lineWidth + #ymin -= abs(pixels[1].y()) * lineWidth + #ymax += abs(pixels[1].y()) * lineWidth + + #self._boundingRect = QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin) + #return self._boundingRect + + + def invalidateBounds(self): + self._boundingRect = None + self._boundsCache = [None, None] + + def setPen(self, *args, **kargs): + """Set the pen used to draw the curve.""" + self.opts['pen'] = fn.mkPen(*args, **kargs) + self.invalidateBounds() + self.update() + + def setShadowPen(self, *args, **kargs): + """Set the shadow pen used to draw behind tyhe primary pen. + This pen must have a larger width than the primary + pen to be visible. + """ + self.opts['shadowPen'] = fn.mkPen(*args, **kargs) + self.invalidateBounds() + self.update() + + def setBrush(self, *args, **kargs): + """Set the brush used when filling the area under the curve""" + self.opts['brush'] = fn.mkBrush(*args, **kargs) + self.invalidateBounds() + self.update() + + def setFillLevel(self, level): + """Set the level filled to when filling under the curve""" + self.opts['fillLevel'] = level + self.fillPath = None + self.invalidateBounds() + self.update() + + def setData(self, *args, **kargs): + """ + ============== ======================================================== + **Arguments:** + x, y (numpy arrays) Data to show + pen Pen to use when drawing. Any single argument accepted by + :func:`mkPen ` is allowed. + shadowPen Pen for drawing behind the primary pen. Usually this + is used to emphasize the curve by providing a + high-contrast border. Any single argument accepted by + :func:`mkPen ` is allowed. + fillLevel (float or None) Fill the area 'under' the curve to + *fillLevel* + brush QBrush to use when filling. Any single argument accepted + by :func:`mkBrush ` is allowed. + antialias (bool) Whether to use antialiasing when drawing. This + is disabled by default because it decreases performance. + stepMode If True, two orthogonal lines are drawn for each sample + as steps. This is commonly used when drawing histograms. + Note that in this case, len(x) == len(y) + 1 + connect Argument specifying how vertexes should be connected + by line segments. Default is "all", indicating full + connection. "pairs" causes only even-numbered segments + to be drawn. "finite" causes segments to be omitted if + they are attached to nan or inf values. For any other + connectivity, specify an array of boolean values. + ============== ======================================================== + + If non-keyword arguments are used, they will be interpreted as + setData(y) for a single argument and setData(x, y) for two + arguments. + + + """ + self.updateData(*args, **kargs) + + def updateData(self, *args, **kargs): + prof = debug.Profiler('PlotCurveItem.updateData', disabled=True) + + if len(args) == 1: + kargs['y'] = args[0] + elif len(args) == 2: + kargs['x'] = args[0] + kargs['y'] = args[1] + + if 'y' not in kargs or kargs['y'] is None: + kargs['y'] = np.array([]) + if 'x' not in kargs or kargs['x'] is None: + kargs['x'] = np.arange(len(kargs['y'])) + + for k in ['x', 'y']: + data = kargs[k] + if isinstance(data, list): + data = np.array(data) + kargs[k] = data + if not isinstance(data, np.ndarray) or data.ndim > 1: + raise Exception("Plot data must be 1D ndarray.") + if 'complex' in str(data.dtype): + raise Exception("Can not plot complex data types.") + + prof.mark("data checks") + + #self.setCacheMode(QtGui.QGraphicsItem.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly + ## Test this bug with test_PlotWidget and zoom in on the animated plot + self.invalidateBounds() + self.prepareGeometryChange() + self.informViewBoundsChanged() + self.yData = kargs['y'].view(np.ndarray) + self.xData = kargs['x'].view(np.ndarray) + + prof.mark('copy') + + if 'stepMode' in kargs: + self.opts['stepMode'] = kargs['stepMode'] + + if self.opts['stepMode'] is True: + if len(self.xData) != len(self.yData)+1: ## allow difference of 1 for step mode plots + raise Exception("len(X) must be len(Y)+1 since stepMode=True (got %s and %s)" % (self.xData.shape, self.yData.shape)) + else: + if self.xData.shape != self.yData.shape: ## allow difference of 1 for step mode plots + raise Exception("X and Y arrays must be the same shape--got %s and %s." % (self.xData.shape, self.yData.shape)) + + self.path = None + self.fillPath = None + #self.xDisp = self.yDisp = None + + if 'name' in kargs: + self.opts['name'] = kargs['name'] + if 'connect' in kargs: + self.opts['connect'] = kargs['connect'] + if 'pen' in kargs: + self.setPen(kargs['pen']) + if 'shadowPen' in kargs: + self.setShadowPen(kargs['shadowPen']) + if 'fillLevel' in kargs: + self.setFillLevel(kargs['fillLevel']) + if 'brush' in kargs: + self.setBrush(kargs['brush']) + if 'antialias' in kargs: + self.opts['antialias'] = kargs['antialias'] + + + prof.mark('set') + self.update() + prof.mark('update') + self.sigPlotChanged.emit(self) + prof.mark('emit') + prof.finish() + + def generatePath(self, x, y): + if self.opts['stepMode']: + ## each value in the x/y arrays generates 2 points. + x2 = np.empty((len(x),2), dtype=x.dtype) + x2[:] = x[:,np.newaxis] + if self.opts['fillLevel'] is None: + x = x2.reshape(x2.size)[1:-1] + y2 = np.empty((len(y),2), dtype=y.dtype) + y2[:] = y[:,np.newaxis] + y = y2.reshape(y2.size) + else: + ## If we have a fill level, add two extra points at either end + x = x2.reshape(x2.size) + y2 = np.empty((len(y)+2,2), dtype=y.dtype) + y2[1:-1] = y[:,np.newaxis] + y = y2.reshape(y2.size)[1:-1] + y[0] = self.opts['fillLevel'] + y[-1] = self.opts['fillLevel'] + + path = fn.arrayToQPath(x, y, connect=self.opts['connect']) + + return path + + + def shape(self): + if self.path is None: + try: + self.path = self.generatePath(*self.getData()) + except: + return QtGui.QPainterPath() + return self.path + + @pg.debug.warnOnException ## raising an exception here causes crash + def paint(self, p, opt, widget): + prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True) + if self.xData is None: + return + + if HAVE_OPENGL and pg.getConfigOption('enableExperimental') and isinstance(widget, QtOpenGL.QGLWidget): + self.paintGL(p, opt, widget) + return + + x = None + y = None + if self.path is None: + x,y = self.getData() + if x is None or len(x) == 0 or y is None or len(y) == 0: + return + self.path = self.generatePath(x,y) + self.fillPath = None + + path = self.path + prof.mark('generate path') + + if self._exportOpts is not False: + aa = self._exportOpts.get('antialias', True) + else: + aa = self.opts['antialias'] + + p.setRenderHint(p.Antialiasing, aa) + + + if self.opts['brush'] is not None and self.opts['fillLevel'] is not None: + if self.fillPath is None: + if x is None: + x,y = self.getData() + p2 = QtGui.QPainterPath(self.path) + p2.lineTo(x[-1], self.opts['fillLevel']) + p2.lineTo(x[0], self.opts['fillLevel']) + p2.lineTo(x[0], y[0]) + p2.closeSubpath() + self.fillPath = p2 + + prof.mark('generate fill path') + p.fillPath(self.fillPath, self.opts['brush']) + prof.mark('draw fill path') + + sp = fn.mkPen(self.opts['shadowPen']) + cp = fn.mkPen(self.opts['pen']) + + ## Copy pens and apply alpha adjustment + #sp = QtGui.QPen(self.opts['shadowPen']) + #cp = QtGui.QPen(self.opts['pen']) + #for pen in [sp, cp]: + #if pen is None: + #continue + #c = pen.color() + #c.setAlpha(c.alpha() * self.opts['alphaHint']) + #pen.setColor(c) + ##pen.setCosmetic(True) + + + + if sp is not None and sp.style() != QtCore.Qt.NoPen: + p.setPen(sp) + p.drawPath(path) + p.setPen(cp) + p.drawPath(path) + prof.mark('drawPath') + + #print "Render hints:", int(p.renderHints()) + prof.finish() + #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) + #p.drawRect(self.boundingRect()) + + def paintGL(self, p, opt, widget): + p.beginNativePainting() + import OpenGL.GL as gl + + ## set clipping viewport + view = self.getViewBox() + if view is not None: + rect = view.mapRectToItem(self, view.boundingRect()) + #gl.glViewport(int(rect.x()), int(rect.y()), int(rect.width()), int(rect.height())) + + #gl.glTranslate(-rect.x(), -rect.y(), 0) + + gl.glEnable(gl.GL_STENCIL_TEST) + gl.glColorMask(gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE) # disable drawing to frame buffer + gl.glDepthMask(gl.GL_FALSE) # disable drawing to depth buffer + gl.glStencilFunc(gl.GL_NEVER, 1, 0xFF) + gl.glStencilOp(gl.GL_REPLACE, gl.GL_KEEP, gl.GL_KEEP) + + ## draw stencil pattern + gl.glStencilMask(0xFF); + gl.glClear(gl.GL_STENCIL_BUFFER_BIT) + gl.glBegin(gl.GL_TRIANGLES) + gl.glVertex2f(rect.x(), rect.y()) + gl.glVertex2f(rect.x()+rect.width(), rect.y()) + gl.glVertex2f(rect.x(), rect.y()+rect.height()) + gl.glVertex2f(rect.x()+rect.width(), rect.y()+rect.height()) + gl.glVertex2f(rect.x()+rect.width(), rect.y()) + gl.glVertex2f(rect.x(), rect.y()+rect.height()) + gl.glEnd() + + gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE) + gl.glDepthMask(gl.GL_TRUE) + gl.glStencilMask(0x00) + gl.glStencilFunc(gl.GL_EQUAL, 1, 0xFF) + + try: + x, y = self.getData() + pos = np.empty((len(x), 2)) + pos[:,0] = x + pos[:,1] = y + gl.glEnableClientState(gl.GL_VERTEX_ARRAY) + try: + gl.glVertexPointerf(pos) + pen = fn.mkPen(self.opts['pen']) + color = pen.color() + gl.glColor4f(color.red()/255., color.green()/255., color.blue()/255., color.alpha()/255.) + width = pen.width() + if pen.isCosmetic() and width < 1: + width = 1 + gl.glPointSize(width) + gl.glEnable(gl.GL_LINE_SMOOTH) + gl.glEnable(gl.GL_BLEND) + gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) + gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST); + gl.glDrawArrays(gl.GL_LINE_STRIP, 0, pos.size / pos.shape[-1]) + finally: + gl.glDisableClientState(gl.GL_VERTEX_ARRAY) + finally: + p.endNativePainting() + + def clear(self): + self.xData = None ## raw values + self.yData = None + self.xDisp = None ## display values (after log / fft) + self.yDisp = None + self.path = None + #del self.xData, self.yData, self.xDisp, self.yDisp, self.path + + def mouseClickEvent(self, ev): + if not self.clickable or ev.button() != QtCore.Qt.LeftButton: + return + ev.accept() + self.sigClicked.emit(self) + + +class ROIPlotItem(PlotCurveItem): + """Plot curve that monitors an ROI and image for changes to automatically replot.""" + def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None): + self.roi = roi + self.roiData = data + self.roiImg = img + self.axes = axes + self.xVals = xVals + PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color) + #roi.connect(roi, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent) + roi.sigRegionChanged.connect(self.roiChangedEvent) + #self.roiChangedEvent() + + def getRoiData(self): + d = self.roi.getArrayRegion(self.roiData, self.roiImg, axes=self.axes) + if d is None: + return + while d.ndim > 1: + d = d.mean(axis=1) + return d + + def roiChangedEvent(self): + d = self.getRoiData() + self.updateData(d, self.xVals) + diff --git a/pyqtgraph/graphicsItems/PlotDataItem.py b/pyqtgraph/graphicsItems/PlotDataItem.py new file mode 100644 index 00000000..87b47227 --- /dev/null +++ b/pyqtgraph/graphicsItems/PlotDataItem.py @@ -0,0 +1,843 @@ +import pyqtgraph.metaarray as metaarray +from pyqtgraph.Qt import QtCore +from .GraphicsObject import GraphicsObject +from .PlotCurveItem import PlotCurveItem +from .ScatterPlotItem import ScatterPlotItem +import numpy as np +import pyqtgraph.functions as fn +import pyqtgraph.debug as debug +import pyqtgraph as pg + +class PlotDataItem(GraphicsObject): + """ + **Bases:** :class:`GraphicsObject ` + + GraphicsItem for displaying plot curves, scatter plots, or both. + While it is possible to use :class:`PlotCurveItem ` or + :class:`ScatterPlotItem ` individually, this class + provides a unified interface to both. Inspances of :class:`PlotDataItem` are + usually created by plot() methods such as :func:`pyqtgraph.plot` and + :func:`PlotItem.plot() `. + + ============================== ============================================== + **Signals:** + sigPlotChanged(self) Emitted when the data in this item is updated. + sigClicked(self) Emitted when the item is clicked. + sigPointsClicked(self, points) Emitted when a plot point is clicked + Sends the list of points under the mouse. + ============================== ============================================== + """ + + sigPlotChanged = QtCore.Signal(object) + sigClicked = QtCore.Signal(object) + sigPointsClicked = QtCore.Signal(object, object) + + def __init__(self, *args, **kargs): + """ + There are many different ways to create a PlotDataItem: + + **Data initialization arguments:** (x,y data only) + + =================================== ====================================== + PlotDataItem(xValues, yValues) x and y values may be any sequence (including ndarray) of real numbers + PlotDataItem(yValues) y values only -- x will be automatically set to range(len(y)) + PlotDataItem(x=xValues, y=yValues) x and y given by keyword arguments + PlotDataItem(ndarray(Nx2)) numpy array with shape (N, 2) where x=data[:,0] and y=data[:,1] + =================================== ====================================== + + **Data initialization arguments:** (x,y data AND may include spot style) + + =========================== ========================================= + PlotDataItem(recarray) numpy array with dtype=[('x', float), ('y', float), ...] + PlotDataItem(list-of-dicts) [{'x': x, 'y': y, ...}, ...] + PlotDataItem(dict-of-lists) {'x': [...], 'y': [...], ...} + PlotDataItem(MetaArray) 1D array of Y values with X sepecified as axis values + OR 2D array with a column 'y' and extra columns as needed. + =========================== ========================================= + + **Line style keyword arguments:** + ========== ================================================ + connect Specifies how / whether vertexes should be connected. + See :func:`arrayToQPath() ` + pen Pen to use for drawing line between points. + Default is solid grey, 1px width. Use None to disable line drawing. + May be any single argument accepted by :func:`mkPen() ` + shadowPen Pen for secondary line to draw behind the primary line. disabled by default. + May be any single argument accepted by :func:`mkPen() ` + fillLevel Fill the area between the curve and fillLevel + fillBrush Fill to use when fillLevel is specified. + May be any single argument accepted by :func:`mkBrush() ` + ========== ================================================ + + **Point style keyword arguments:** (see :func:`ScatterPlotItem.setData() ` for more information) + + ============ ================================================ + symbol Symbol to use for drawing points OR list of symbols, one per point. Default is no symbol. + Options are o, s, t, d, +, or any QPainterPath + symbolPen Outline pen for drawing points OR list of pens, one per point. + May be any single argument accepted by :func:`mkPen() ` + symbolBrush Brush for filling points OR list of brushes, one per point. + May be any single argument accepted by :func:`mkBrush() ` + symbolSize Diameter of symbols OR list of diameters. + pxMode (bool) If True, then symbolSize is specified in pixels. If False, then symbolSize is + specified in data coordinates. + ============ ================================================ + + **Optimization keyword arguments:** + + ================ ===================================================================== + antialias (bool) By default, antialiasing is disabled to improve performance. + Note that in some cases (in particluar, when pxMode=True), points + will be rendered antialiased even if this is set to False. + decimate deprecated. + downsample (int) Reduce the number of samples displayed by this value + downsampleMethod 'subsample': Downsample by taking the first of N samples. + This method is fastest and least accurate. + 'mean': Downsample by taking the mean of N samples. + 'peak': Downsample by drawing a saw wave that follows the min + and max of the original data. This method produces the best + visual representation of the data but is slower. + autoDownsample (bool) If True, resample the data before plotting to avoid plotting + multiple line segments per pixel. This can improve performance when + viewing very high-density data, but increases the initial overhead + and memory usage. + clipToView (bool) If True, only plot data that is visible within the X range of + the containing ViewBox. This can improve performance when plotting + very large data sets where only a fraction of the data is visible + at any time. + identical *deprecated* + ================ ===================================================================== + + **Meta-info keyword arguments:** + + ========== ================================================ + name name of dataset. This would appear in a legend + ========== ================================================ + """ + GraphicsObject.__init__(self) + self.setFlag(self.ItemHasNoContents) + self.xData = None + self.yData = None + self.xDisp = None + self.yDisp = None + #self.dataMask = None + #self.curves = [] + #self.scatters = [] + self.curve = PlotCurveItem() + self.scatter = ScatterPlotItem() + self.curve.setParentItem(self) + self.scatter.setParentItem(self) + + self.curve.sigClicked.connect(self.curveClicked) + self.scatter.sigClicked.connect(self.scatterClicked) + + + #self.clear() + self.opts = { + 'connect': 'all', + + 'fftMode': False, + 'logMode': [False, False], + 'alphaHint': 1.0, + 'alphaMode': False, + + 'pen': (200,200,200), + 'shadowPen': None, + 'fillLevel': None, + 'fillBrush': None, + + 'symbol': None, + 'symbolSize': 10, + 'symbolPen': (200,200,200), + 'symbolBrush': (50, 50, 150), + 'pxMode': True, + + 'antialias': pg.getConfigOption('antialias'), + 'pointMode': None, + + 'downsample': 1, + 'autoDownsample': False, + 'downsampleMethod': 'peak', + 'clipToView': False, + + 'data': None, + } + self.setData(*args, **kargs) + + def implements(self, interface=None): + ints = ['plotData'] + if interface is None: + return ints + return interface in ints + + def boundingRect(self): + return QtCore.QRectF() ## let child items handle this + + def setAlpha(self, alpha, auto): + if self.opts['alphaHint'] == alpha and self.opts['alphaMode'] == auto: + return + self.opts['alphaHint'] = alpha + self.opts['alphaMode'] = auto + self.setOpacity(alpha) + #self.update() + + def setFftMode(self, mode): + if self.opts['fftMode'] == mode: + return + self.opts['fftMode'] = mode + self.xDisp = self.yDisp = None + self.xClean = self.yClean = None + self.updateItems() + self.informViewBoundsChanged() + + def setLogMode(self, xMode, yMode): + if self.opts['logMode'] == [xMode, yMode]: + return + self.opts['logMode'] = [xMode, yMode] + self.xDisp = self.yDisp = None + self.xClean = self.yClean = None + self.updateItems() + self.informViewBoundsChanged() + + def setPointMode(self, mode): + if self.opts['pointMode'] == mode: + return + self.opts['pointMode'] = mode + self.update() + + def setPen(self, *args, **kargs): + """ + | Sets the pen used to draw lines between points. + | *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() ` + """ + pen = fn.mkPen(*args, **kargs) + self.opts['pen'] = pen + #self.curve.setPen(pen) + #for c in self.curves: + #c.setPen(pen) + #self.update() + self.updateItems() + + def setShadowPen(self, *args, **kargs): + """ + | Sets the shadow pen used to draw lines between points (this is for enhancing contrast or + emphacizing data). + | This line is drawn behind the primary pen (see :func:`setPen() `) + and should generally be assigned greater width than the primary pen. + | *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() ` + """ + pen = fn.mkPen(*args, **kargs) + self.opts['shadowPen'] = pen + #for c in self.curves: + #c.setPen(pen) + #self.update() + self.updateItems() + + def setFillBrush(self, *args, **kargs): + brush = fn.mkBrush(*args, **kargs) + if self.opts['fillBrush'] == brush: + return + self.opts['fillBrush'] = brush + self.updateItems() + + def setBrush(self, *args, **kargs): + return self.setFillBrush(*args, **kargs) + + def setFillLevel(self, level): + if self.opts['fillLevel'] == level: + return + self.opts['fillLevel'] = level + self.updateItems() + + def setSymbol(self, symbol): + if self.opts['symbol'] == symbol: + return + self.opts['symbol'] = symbol + #self.scatter.setSymbol(symbol) + self.updateItems() + + def setSymbolPen(self, *args, **kargs): + pen = fn.mkPen(*args, **kargs) + if self.opts['symbolPen'] == pen: + return + self.opts['symbolPen'] = pen + #self.scatter.setSymbolPen(pen) + self.updateItems() + + + + def setSymbolBrush(self, *args, **kargs): + brush = fn.mkBrush(*args, **kargs) + if self.opts['symbolBrush'] == brush: + return + self.opts['symbolBrush'] = brush + #self.scatter.setSymbolBrush(brush) + self.updateItems() + + + def setSymbolSize(self, size): + if self.opts['symbolSize'] == size: + return + self.opts['symbolSize'] = size + #self.scatter.setSymbolSize(symbolSize) + self.updateItems() + + def setDownsampling(self, ds=None, auto=None, method=None): + """ + Set the downsampling mode of this item. Downsampling reduces the number + of samples drawn to increase performance. + + =========== ================================================================= + Arguments + ds (int) Reduce visible plot samples by this factor. To disable, + set ds=1. + auto (bool) If True, automatically pick *ds* based on visible range + mode 'subsample': Downsample by taking the first of N samples. + This method is fastest and least accurate. + 'mean': Downsample by taking the mean of N samples. + 'peak': Downsample by drawing a saw wave that follows the min + and max of the original data. This method produces the best + visual representation of the data but is slower. + =========== ================================================================= + """ + changed = False + if ds is not None: + if self.opts['downsample'] != ds: + changed = True + self.opts['downsample'] = ds + + if auto is not None and self.opts['autoDownsample'] != auto: + self.opts['autoDownsample'] = auto + changed = True + + if method is not None: + if self.opts['downsampleMethod'] != method: + changed = True + self.opts['downsampleMethod'] = method + + if changed: + self.xDisp = self.yDisp = None + self.updateItems() + + def setClipToView(self, clip): + if self.opts['clipToView'] == clip: + return + self.opts['clipToView'] = clip + self.xDisp = self.yDisp = None + self.updateItems() + + + def setData(self, *args, **kargs): + """ + Clear any data displayed by this item and display new data. + See :func:`__init__() ` for details; it accepts the same arguments. + """ + #self.clear() + prof = debug.Profiler('PlotDataItem.setData (0x%x)' % id(self), disabled=True) + y = None + x = None + if len(args) == 1: + data = args[0] + dt = dataType(data) + if dt == 'empty': + pass + elif dt == 'listOfValues': + y = np.array(data) + elif dt == 'Nx2array': + x = data[:,0] + y = data[:,1] + elif dt == 'recarray' or dt == 'dictOfLists': + if 'x' in data: + x = np.array(data['x']) + if 'y' in data: + y = np.array(data['y']) + elif dt == 'listOfDicts': + if 'x' in data[0]: + x = np.array([d.get('x',None) for d in data]) + if 'y' in data[0]: + y = np.array([d.get('y',None) for d in data]) + for k in ['data', 'symbolSize', 'symbolPen', 'symbolBrush', 'symbolShape']: + if k in data: + kargs[k] = [d.get(k, None) for d in data] + elif dt == 'MetaArray': + y = data.view(np.ndarray) + x = data.xvals(0).view(np.ndarray) + else: + raise Exception('Invalid data type %s' % type(data)) + + elif len(args) == 2: + seq = ('listOfValues', 'MetaArray', 'empty') + if dataType(args[0]) not in seq or dataType(args[1]) not in seq: + raise Exception('When passing two unnamed arguments, both must be a list or array of values. (got %s, %s)' % (str(type(args[0])), str(type(args[1])))) + if not isinstance(args[0], np.ndarray): + x = np.array(args[0]) + else: + x = args[0].view(np.ndarray) + if not isinstance(args[1], np.ndarray): + y = np.array(args[1]) + else: + y = args[1].view(np.ndarray) + + if 'x' in kargs: + x = kargs['x'] + if 'y' in kargs: + y = kargs['y'] + + prof.mark('interpret data') + ## pull in all style arguments. + ## Use self.opts to fill in anything not present in kargs. + + if 'name' in kargs: + self.opts['name'] = kargs['name'] + if 'connect' in kargs: + self.opts['connect'] = kargs['connect'] + + ## if symbol pen/brush are given with no symbol, then assume symbol is 'o' + + if 'symbol' not in kargs and ('symbolPen' in kargs or 'symbolBrush' in kargs or 'symbolSize' in kargs): + kargs['symbol'] = 'o' + + if 'brush' in kargs: + kargs['fillBrush'] = kargs['brush'] + + for k in list(self.opts.keys()): + if k in kargs: + self.opts[k] = kargs[k] + + #curveArgs = {} + #for k in ['pen', 'shadowPen', 'fillLevel', 'brush']: + #if k in kargs: + #self.opts[k] = kargs[k] + #curveArgs[k] = self.opts[k] + + #scatterArgs = {} + #for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol')]: + #if k in kargs: + #self.opts[k] = kargs[k] + #scatterArgs[v] = self.opts[k] + + + if y is None: + return + if y is not None and x is None: + x = np.arange(len(y)) + + if isinstance(x, list): + x = np.array(x) + if isinstance(y, list): + y = np.array(y) + + self.xData = x.view(np.ndarray) ## one last check to make sure there are no MetaArrays getting by + self.yData = y.view(np.ndarray) + self.xClean = self.yClean = None + self.xDisp = None + self.yDisp = None + prof.mark('set data') + + self.updateItems() + prof.mark('update items') + + self.informViewBoundsChanged() + #view = self.getViewBox() + #if view is not None: + #view.itemBoundsChanged(self) ## inform view so it can update its range if it wants + + self.sigPlotChanged.emit(self) + prof.mark('emit') + prof.finish() + + + def updateItems(self): + + curveArgs = {} + for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillBrush', 'brush'), ('antialias', 'antialias'), ('connect', 'connect')]: + curveArgs[v] = self.opts[k] + + scatterArgs = {} + for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol'), ('symbolSize', 'size'), ('data', 'data'), ('pxMode', 'pxMode'), ('antialias', 'antialias')]: + if k in self.opts: + scatterArgs[v] = self.opts[k] + + x,y = self.getData() + #scatterArgs['mask'] = self.dataMask + + if curveArgs['pen'] is not None or (curveArgs['brush'] is not None and curveArgs['fillLevel'] is not None): + self.curve.setData(x=x, y=y, **curveArgs) + self.curve.show() + else: + self.curve.hide() + + if scatterArgs['symbol'] is not None: + self.scatter.setData(x=x, y=y, **scatterArgs) + self.scatter.show() + else: + self.scatter.hide() + + + def getData(self): + if self.xData is None: + return (None, None) + + #if self.xClean is None: + #nanMask = np.isnan(self.xData) | np.isnan(self.yData) | np.isinf(self.xData) | np.isinf(self.yData) + #if nanMask.any(): + #self.dataMask = ~nanMask + #self.xClean = self.xData[self.dataMask] + #self.yClean = self.yData[self.dataMask] + #else: + #self.dataMask = None + #self.xClean = self.xData + #self.yClean = self.yData + + if self.xDisp is None: + x = self.xData + y = self.yData + + + #ds = self.opts['downsample'] + #if isinstance(ds, int) and ds > 1: + #x = x[::ds] + ##y = resample(y[:len(x)*ds], len(x)) ## scipy.signal.resample causes nasty ringing + #y = y[::ds] + if self.opts['fftMode']: + x,y = self._fourierTransform(x, y) + if self.opts['logMode'][0]: + x = np.log10(x) + if self.opts['logMode'][1]: + y = np.log10(y) + #if any(self.opts['logMode']): ## re-check for NANs after log + #nanMask = np.isinf(x) | np.isinf(y) | np.isnan(x) | np.isnan(y) + #if any(nanMask): + #self.dataMask = ~nanMask + #x = x[self.dataMask] + #y = y[self.dataMask] + #else: + #self.dataMask = None + + ds = self.opts['downsample'] + if not isinstance(ds, int): + ds = 1 + + if self.opts['autoDownsample']: + # this option presumes that x-values have uniform spacing + range = self.viewRect() + if range is not None: + dx = float(x[-1]-x[0]) / (len(x)-1) + x0 = (range.left()-x[0]) / dx + x1 = (range.right()-x[0]) / dx + width = self.getViewBox().width() + ds = int(max(1, int(0.2 * (x1-x0) / width))) + ## downsampling is expensive; delay until after clipping. + + if self.opts['clipToView']: + # this option presumes that x-values have uniform spacing + range = self.viewRect() + if range is not None: + dx = float(x[-1]-x[0]) / (len(x)-1) + # clip to visible region extended by downsampling value + x0 = np.clip(int((range.left()-x[0])/dx)-1*ds , 0, len(x)-1) + x1 = np.clip(int((range.right()-x[0])/dx)+2*ds , 0, len(x)-1) + x = x[x0:x1] + y = y[x0:x1] + + if ds > 1: + if self.opts['downsampleMethod'] == 'subsample': + x = x[::ds] + y = y[::ds] + elif self.opts['downsampleMethod'] == 'mean': + n = len(x) / ds + x = x[:n*ds:ds] + y = y[:n*ds].reshape(n,ds).mean(axis=1) + elif self.opts['downsampleMethod'] == 'peak': + n = len(x) / ds + x1 = np.empty((n,2)) + x1[:] = x[:n*ds:ds,np.newaxis] + x = x1.reshape(n*2) + y1 = np.empty((n,2)) + y2 = y[:n*ds].reshape((n, ds)) + y1[:,0] = y2.max(axis=1) + y1[:,1] = y2.min(axis=1) + y = y1.reshape(n*2) + + + self.xDisp = x + self.yDisp = y + #print self.yDisp.shape, self.yDisp.min(), self.yDisp.max() + #print self.xDisp.shape, self.xDisp.min(), self.xDisp.max() + return self.xDisp, self.yDisp + + def dataBounds(self, ax, frac=1.0, orthoRange=None): + """ + Returns the range occupied by the data (along a specific axis) in this item. + This method is called by ViewBox when auto-scaling. + + =============== ============================================================= + **Arguments:** + ax (0 or 1) the axis for which to return this item's data range + frac (float 0.0-1.0) Specifies what fraction of the total data + range to return. By default, the entire range is returned. + This allows the ViewBox to ignore large spikes in the data + when auto-scaling. + orthoRange ([min,max] or None) Specifies that only the data within the + given range (orthogonal to *ax*) should me measured when + returning the data range. (For example, a ViewBox might ask + what is the y-range of all data with x-values between min + and max) + =============== ============================================================= + """ + + range = [None, None] + if self.curve.isVisible(): + range = self.curve.dataBounds(ax, frac, orthoRange) + elif self.scatter.isVisible(): + r2 = self.scatter.dataBounds(ax, frac, orthoRange) + range = [ + r2[0] if range[0] is None else (range[0] if r2[0] is None else min(r2[0], range[0])), + r2[1] if range[1] is None else (range[1] if r2[1] is None else min(r2[1], range[1])) + ] + return range + + def pixelPadding(self): + """ + Return the size in pixels that this item may draw beyond the values returned by dataBounds(). + This method is called by ViewBox when auto-scaling. + """ + pad = 0 + if self.curve.isVisible(): + pad = max(pad, self.curve.pixelPadding()) + elif self.scatter.isVisible(): + pad = max(pad, self.scatter.pixelPadding()) + return pad + + + def clear(self): + #for i in self.curves+self.scatters: + #if i.scene() is not None: + #i.scene().removeItem(i) + #self.curves = [] + #self.scatters = [] + self.xData = None + self.yData = None + #self.xClean = None + #self.yClean = None + self.xDisp = None + self.yDisp = None + self.curve.setData([]) + self.scatter.setData([]) + + def appendData(self, *args, **kargs): + pass + + def curveClicked(self): + self.sigClicked.emit(self) + + def scatterClicked(self, plt, points): + self.sigClicked.emit(self) + self.sigPointsClicked.emit(self, points) + + def viewRangeChanged(self): + # view range has changed; re-plot if needed + if self.opts['clipToView'] or self.opts['autoDownsample']: + self.xDisp = self.yDisp = None + self.updateItems() + + def _fourierTransform(self, x, y): + ## Perform fourier transform. If x values are not sampled uniformly, + ## then use interpolate.griddata to resample before taking fft. + dx = np.diff(x) + uniform = not np.any(np.abs(dx-dx[0]) > (abs(dx[0]) / 1000.)) + if not uniform: + import scipy.interpolate as interp + x2 = np.linspace(x[0], x[-1], len(x)) + y = interp.griddata(x, y, x2, method='linear') + x = x2 + f = np.fft.fft(y) / len(y) + y = abs(f[1:len(f)/2]) + dt = x[-1] - x[0] + x = np.linspace(0, 0.5*len(x)/dt, len(y)) + return x, y + +def dataType(obj): + if hasattr(obj, '__len__') and len(obj) == 0: + return 'empty' + if isinstance(obj, dict): + return 'dictOfLists' + elif isSequence(obj): + first = obj[0] + + if (hasattr(obj, 'implements') and obj.implements('MetaArray')): + return 'MetaArray' + elif isinstance(obj, np.ndarray): + if obj.ndim == 1: + if obj.dtype.names is None: + return 'listOfValues' + else: + return 'recarray' + elif obj.ndim == 2 and obj.dtype.names is None and obj.shape[1] == 2: + return 'Nx2array' + else: + raise Exception('array shape must be (N,) or (N,2); got %s instead' % str(obj.shape)) + elif isinstance(first, dict): + return 'listOfDicts' + else: + return 'listOfValues' + + +def isSequence(obj): + return hasattr(obj, '__iter__') or isinstance(obj, np.ndarray) or (hasattr(obj, 'implements') and obj.implements('MetaArray')) + + + +#class TableData: + #""" + #Class for presenting multiple forms of tabular data through a consistent interface. + #May contain: + #- numpy record array + #- list-of-dicts (all dicts are _not_ required to have the same keys) + #- dict-of-lists + #- dict (single record) + #Note: if all the values in this record are lists, it will be interpreted as multiple records + + #Data can be accessed and modified by column, by row, or by value + #data[columnName] + #data[rowId] + #data[columnName, rowId] = value + #data[columnName] = [value, value, ...] + #data[rowId] = {columnName: value, ...} + #""" + + #def __init__(self, data): + #self.data = data + #if isinstance(data, np.ndarray): + #self.mode = 'array' + #elif isinstance(data, list): + #self.mode = 'list' + #elif isinstance(data, dict): + #types = set(map(type, data.values())) + ### dict may be a dict-of-lists or a single record + #types -= set([list, np.ndarray]) ## if dict contains any non-sequence values, it is probably a single record. + #if len(types) != 0: + #self.data = [self.data] + #self.mode = 'list' + #else: + #self.mode = 'dict' + #elif isinstance(data, TableData): + #self.data = data.data + #self.mode = data.mode + #else: + #raise TypeError(type(data)) + + #for fn in ['__getitem__', '__setitem__']: + #setattr(self, fn, getattr(self, '_TableData'+fn+self.mode)) + + #def originalData(self): + #return self.data + + #def toArray(self): + #if self.mode == 'array': + #return self.data + #if len(self) < 1: + ##return np.array([]) ## need to return empty array *with correct columns*, but this is very difficult, so just return None + #return None + #rec1 = self[0] + #dtype = functions.suggestRecordDType(rec1) + ##print rec1, dtype + #arr = np.empty(len(self), dtype=dtype) + #arr[0] = tuple(rec1.values()) + #for i in xrange(1, len(self)): + #arr[i] = tuple(self[i].values()) + #return arr + + #def __getitem__array(self, arg): + #if isinstance(arg, tuple): + #return self.data[arg[0]][arg[1]] + #else: + #return self.data[arg] + + #def __getitem__list(self, arg): + #if isinstance(arg, basestring): + #return [d.get(arg, None) for d in self.data] + #elif isinstance(arg, int): + #return self.data[arg] + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #return self.data[arg[0]][arg[1]] + #else: + #raise TypeError(type(arg)) + + #def __getitem__dict(self, arg): + #if isinstance(arg, basestring): + #return self.data[arg] + #elif isinstance(arg, int): + #return dict([(k, v[arg]) for k, v in self.data.iteritems()]) + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #return self.data[arg[1]][arg[0]] + #else: + #raise TypeError(type(arg)) + + #def __setitem__array(self, arg, val): + #if isinstance(arg, tuple): + #self.data[arg[0]][arg[1]] = val + #else: + #self.data[arg] = val + + #def __setitem__list(self, arg, val): + #if isinstance(arg, basestring): + #if len(val) != len(self.data): + #raise Exception("Values (%d) and data set (%d) are not the same length." % (len(val), len(self.data))) + #for i, rec in enumerate(self.data): + #rec[arg] = val[i] + #elif isinstance(arg, int): + #self.data[arg] = val + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #self.data[arg[0]][arg[1]] = val + #else: + #raise TypeError(type(arg)) + + #def __setitem__dict(self, arg, val): + #if isinstance(arg, basestring): + #if len(val) != len(self.data[arg]): + #raise Exception("Values (%d) and data set (%d) are not the same length." % (len(val), len(self.data[arg]))) + #self.data[arg] = val + #elif isinstance(arg, int): + #for k in self.data: + #self.data[k][arg] = val[k] + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #self.data[arg[1]][arg[0]] = val + #else: + #raise TypeError(type(arg)) + + #def _orderArgs(self, args): + ### return args in (int, str) order + #if isinstance(args[0], basestring): + #return (args[1], args[0]) + #else: + #return args + + #def __iter__(self): + #for i in xrange(len(self)): + #yield self[i] + + #def __len__(self): + #if self.mode == 'array' or self.mode == 'list': + #return len(self.data) + #else: + #return max(map(len, self.data.values())) + + #def columnNames(self): + #"""returns column names in no particular order""" + #if self.mode == 'array': + #return self.data.dtype.names + #elif self.mode == 'list': + #names = set() + #for row in self.data: + #names.update(row.keys()) + #return list(names) + #elif self.mode == 'dict': + #return self.data.keys() + + #def keys(self): + #return self.columnNames() diff --git a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py new file mode 100644 index 00000000..ec0960ba --- /dev/null +++ b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py @@ -0,0 +1,1271 @@ +# -*- coding: utf-8 -*- +""" +PlotItem.py - Graphics item implementing a scalable ViewBox with plotting powers. +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +This class is one of the workhorses of pyqtgraph. It implements a graphics item with +plots, labels, and scales which can be viewed inside a QGraphicsScene. If you want +a widget that can be added to your GUI, see PlotWidget instead. + +This class is very heavily featured: + - Automatically creates and manages PlotCurveItems + - Fast display and update of plots + - Manages zoom/pan ViewBox, scale, and label elements + - Automatic scaling when data changes + - Control panel with a huge feature set including averaging, decimation, + display, power spectrum, svg/png export, plot linking, and more. +""" +from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE +import pyqtgraph.pixmaps + +if USE_PYSIDE: + from .plotConfigTemplate_pyside import * +else: + from .plotConfigTemplate_pyqt import * + +import pyqtgraph.functions as fn +from pyqtgraph.widgets.FileDialog import FileDialog +import weakref +import numpy as np +import os +from .. PlotDataItem import PlotDataItem +from .. ViewBox import ViewBox +from .. AxisItem import AxisItem +from .. LabelItem import LabelItem +from .. LegendItem import LegendItem +from .. GraphicsWidget import GraphicsWidget +from .. ButtonItem import ButtonItem +from .. InfiniteLine import InfiniteLine +from pyqtgraph.WidgetGroup import WidgetGroup + +__all__ = ['PlotItem'] + +try: + from metaarray import * + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + + + + +class PlotItem(GraphicsWidget): + + """ + **Bases:** :class:`GraphicsWidget ` + + Plot graphics item that can be added to any graphics scene. Implements axes, titles, and interactive viewbox. + PlotItem also provides some basic analysis functionality that may be accessed from the context menu. + Use :func:`plot() ` to create a new PlotDataItem and add it to the view. + Use :func:`addItem() ` to add any QGraphicsItem to the view. + + This class wraps several methods from its internal ViewBox: + :func:`setXRange `, + :func:`setYRange `, + :func:`setRange `, + :func:`autoRange `, + :func:`setXLink `, + :func:`setYLink `, + :func:`setAutoPan `, + :func:`setAutoVisible `, + :func:`viewRect `, + :func:`viewRange `, + :func:`setMouseEnabled `, + :func:`enableAutoRange `, + :func:`disableAutoRange `, + :func:`setAspectLocked `, + :func:`invertY `, + :func:`register `, + :func:`unregister ` + + The ViewBox itself can be accessed by calling :func:`getViewBox() ` + + ==================== ======================================================================= + **Signals** + sigYRangeChanged wrapped from :class:`ViewBox ` + sigXRangeChanged wrapped from :class:`ViewBox ` + sigRangeChanged wrapped from :class:`ViewBox ` + ==================== ======================================================================= + """ + + sigRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox range has changed + sigYRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox Y range has changed + sigXRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox X range has changed + + + lastFileDir = None + managers = {} + + def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs): + """ + Create a new PlotItem. All arguments are optional. + Any extra keyword arguments are passed to PlotItem.plot(). + + ============== ========================================================================================== + **Arguments** + *title* Title to display at the top of the item. Html is allowed. + *labels* A dictionary specifying the axis labels to display:: + + {'left': (args), 'bottom': (args), ...} + + The name of each axis and the corresponding arguments are passed to + :func:`PlotItem.setLabel() ` + Optionally, PlotItem my also be initialized with the keyword arguments left, + right, top, or bottom to achieve the same effect. + *name* Registers a name for this view so that others may link to it + *viewBox* If specified, the PlotItem will be constructed with this as its ViewBox. + *axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items + for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top') + and the values must be instances of AxisItem (or at least compatible with AxisItem). + ============== ========================================================================================== + """ + + GraphicsWidget.__init__(self, parent) + + self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + + ## Set up control buttons + path = os.path.dirname(__file__) + #self.autoImageFile = os.path.join(path, 'auto.png') + #self.lockImageFile = os.path.join(path, 'lock.png') + self.autoBtn = ButtonItem(pyqtgraph.pixmaps.getPixmap('auto'), 14, self) + self.autoBtn.mode = 'auto' + self.autoBtn.clicked.connect(self.autoBtnClicked) + #self.autoBtn.hide() + self.buttonsHidden = False ## whether the user has requested buttons to be hidden + self.mouseHovering = False + + self.layout = QtGui.QGraphicsGridLayout() + self.layout.setContentsMargins(1,1,1,1) + self.setLayout(self.layout) + self.layout.setHorizontalSpacing(0) + self.layout.setVerticalSpacing(0) + + if viewBox is None: + viewBox = ViewBox() + self.vb = viewBox + self.vb.sigStateChanged.connect(self.viewStateChanged) + self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus + + if name is not None: + self.vb.register(name) + self.vb.sigRangeChanged.connect(self.sigRangeChanged) + self.vb.sigXRangeChanged.connect(self.sigXRangeChanged) + self.vb.sigYRangeChanged.connect(self.sigYRangeChanged) + + self.layout.addItem(self.vb, 2, 1) + self.alpha = 1.0 + self.autoAlpha = True + self.spectrumMode = False + + self.legend = None + + ## Create and place axis items + if axisItems is None: + axisItems = {} + self.axes = {} + for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))): + axis = axisItems.get(k, AxisItem(orientation=k)) + axis.linkToView(self.vb) + self.axes[k] = {'item': axis, 'pos': pos} + self.layout.addItem(axis, *pos) + axis.setZValue(-1000) + axis.setFlag(axis.ItemNegativeZStacksBehindParent) + + self.titleLabel = LabelItem('', size='11pt') + self.layout.addItem(self.titleLabel, 0, 1) + self.setTitle(None) ## hide + + + for i in range(4): + self.layout.setRowPreferredHeight(i, 0) + self.layout.setRowMinimumHeight(i, 0) + self.layout.setRowSpacing(i, 0) + self.layout.setRowStretchFactor(i, 1) + + for i in range(3): + self.layout.setColumnPreferredWidth(i, 0) + self.layout.setColumnMinimumWidth(i, 0) + self.layout.setColumnSpacing(i, 0) + self.layout.setColumnStretchFactor(i, 1) + self.layout.setRowStretchFactor(2, 100) + self.layout.setColumnStretchFactor(1, 100) + + + ## Wrap a few methods from viewBox + for m in [ + 'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible', + 'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled', + 'enableAutoRange', 'disableAutoRange', 'setAspectLocked', 'invertY', + 'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well. + setattr(self, m, getattr(self.vb, m)) + + self.items = [] + self.curves = [] + self.itemMeta = weakref.WeakKeyDictionary() + self.dataItems = [] + self.paramList = {} + self.avgCurves = {} + + ### Set up context menu + + w = QtGui.QWidget() + self.ctrl = c = Ui_Form() + c.setupUi(w) + dv = QtGui.QDoubleValidator(self) + + menuItems = [ + ('Transforms', c.transformGroup), + ('Downsample', c.decimateGroup), + ('Average', c.averageGroup), + ('Alpha', c.alphaGroup), + ('Grid', c.gridGroup), + ('Points', c.pointsGroup), + ] + + + self.ctrlMenu = QtGui.QMenu() + + self.ctrlMenu.setTitle('Plot Options') + self.subMenus = [] + for name, grp in menuItems: + sm = QtGui.QMenu(name) + act = QtGui.QWidgetAction(self) + act.setDefaultWidget(grp) + sm.addAction(act) + self.subMenus.append(sm) + self.ctrlMenu.addMenu(sm) + + self.stateGroup = WidgetGroup() + for name, w in menuItems: + self.stateGroup.autoAdd(w) + + self.fileDialog = None + + c.alphaGroup.toggled.connect(self.updateAlpha) + c.alphaSlider.valueChanged.connect(self.updateAlpha) + c.autoAlphaCheck.toggled.connect(self.updateAlpha) + + c.xGridCheck.toggled.connect(self.updateGrid) + c.yGridCheck.toggled.connect(self.updateGrid) + c.gridAlphaSlider.valueChanged.connect(self.updateGrid) + + c.fftCheck.toggled.connect(self.updateSpectrumMode) + c.logXCheck.toggled.connect(self.updateLogMode) + c.logYCheck.toggled.connect(self.updateLogMode) + + c.downsampleSpin.valueChanged.connect(self.updateDownsampling) + c.downsampleCheck.toggled.connect(self.updateDownsampling) + c.autoDownsampleCheck.toggled.connect(self.updateDownsampling) + c.subsampleRadio.toggled.connect(self.updateDownsampling) + c.meanRadio.toggled.connect(self.updateDownsampling) + c.clipToViewCheck.toggled.connect(self.updateDownsampling) + + self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked) + self.ctrl.averageGroup.toggled.connect(self.avgToggled) + + self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation) + self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation) + + self.hideAxis('right') + self.hideAxis('top') + self.showAxis('left') + self.showAxis('bottom') + + if labels is None: + labels = {} + for label in list(self.axes.keys()): + if label in kargs: + labels[label] = kargs[label] + del kargs[label] + for k in labels: + if isinstance(labels[k], basestring): + labels[k] = (labels[k],) + self.setLabel(k, *labels[k]) + + if title is not None: + self.setTitle(title) + + if len(kargs) > 0: + self.plot(**kargs) + + + def implements(self, interface=None): + return interface in ['ViewBoxWrapper'] + + def getViewBox(self): + """Return the :class:`ViewBox ` contained within.""" + return self.vb + + + + def setLogMode(self, x=None, y=None): + """ + Set log scaling for x and/or y axes. + This informs PlotDataItems to transform logarithmically and switches + the axes to use log ticking. + + Note that *no other items* in the scene will be affected by + this; there is (currently) no generic way to redisplay a GraphicsItem + with log coordinates. + + """ + if x is not None: + self.ctrl.logXCheck.setChecked(x) + if y is not None: + self.ctrl.logYCheck.setChecked(y) + + def showGrid(self, x=None, y=None, alpha=None): + """ + Show or hide the grid for either axis. + + ============== ===================================== + **Arguments:** + x (bool) Whether to show the X grid + y (bool) Whether to show the Y grid + alpha (0.0-1.0) Opacity of the grid + ============== ===================================== + """ + if x is None and y is None and alpha is None: + raise Exception("Must specify at least one of x, y, or alpha.") ## prevent people getting confused if they just call showGrid() + + if x is not None: + self.ctrl.xGridCheck.setChecked(x) + if y is not None: + self.ctrl.yGridCheck.setChecked(y) + if alpha is not None: + v = np.clip(alpha, 0, 1)*self.ctrl.gridAlphaSlider.maximum() + self.ctrl.gridAlphaSlider.setValue(v) + + #def paint(self, *args): + #prof = debug.Profiler('PlotItem.paint', disabled=True) + #QtGui.QGraphicsWidget.paint(self, *args) + #prof.finish() + + ## bad idea. + #def __getattr__(self, attr): ## wrap ms + #return getattr(self.vb, attr) + + def close(self): + #print "delete", self + ## Most of this crap is needed to avoid PySide trouble. + ## The problem seems to be whenever scene.clear() leads to deletion of widgets (either through proxies or qgraphicswidgets) + ## the solution is to manually remove all widgets before scene.clear() is called + if self.ctrlMenu is None: ## already shut down + return + self.ctrlMenu.setParent(None) + self.ctrlMenu = None + + #self.ctrlBtn.setParent(None) + #self.ctrlBtn = None + #self.autoBtn.setParent(None) + #self.autoBtn = None + + for k in self.axes: + i = self.axes[k]['item'] + i.close() + + self.axes = None + self.scene().removeItem(self.vb) + self.vb = None + + ## causes invalid index errors: + #for i in range(self.layout.count()): + #self.layout.removeAt(i) + + #for p in self.proxies: + #try: + #p.setWidget(None) + #except RuntimeError: + #break + #self.scene().removeItem(p) + #self.proxies = [] + + #self.menuAction.releaseWidget(self.menuAction.defaultWidget()) + #self.menuAction.setParent(None) + #self.menuAction = None + + #if self.manager is not None: + #self.manager.sigWidgetListChanged.disconnect(self.updatePlotList) + #self.manager.removeWidget(self.name) + #else: + #print "no manager" + + def registerPlot(self, name): ## for backward compatibility + self.vb.register(name) + + def updateGrid(self, *args): + alpha = self.ctrl.gridAlphaSlider.value() + x = alpha if self.ctrl.xGridCheck.isChecked() else False + y = alpha if self.ctrl.yGridCheck.isChecked() else False + self.getAxis('top').setGrid(x) + self.getAxis('bottom').setGrid(x) + self.getAxis('left').setGrid(y) + self.getAxis('right').setGrid(y) + + def viewGeometry(self): + """Return the screen geometry of the viewbox""" + v = self.scene().views()[0] + b = self.vb.mapRectToScene(self.vb.boundingRect()) + wr = v.mapFromScene(b).boundingRect() + pos = v.mapToGlobal(v.pos()) + wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) + return wr + + + def avgToggled(self, b): + if b: + self.recomputeAverages() + for k in self.avgCurves: + self.avgCurves[k][1].setVisible(b) + + def avgParamListClicked(self, item): + name = str(item.text()) + self.paramList[name] = (item.checkState() == QtCore.Qt.Checked) + self.recomputeAverages() + + def recomputeAverages(self): + if not self.ctrl.averageGroup.isChecked(): + return + for k in self.avgCurves: + self.removeItem(self.avgCurves[k][1]) + self.avgCurves = {} + for c in self.curves: + self.addAvgCurve(c) + self.replot() + + def addAvgCurve(self, curve): + ## Add a single curve into the pool of curves averaged together + + ## If there are plot parameters, then we need to determine which to average together. + remKeys = [] + addKeys = [] + if self.ctrl.avgParamList.count() > 0: + + ### First determine the key of the curve to which this new data should be averaged + for i in range(self.ctrl.avgParamList.count()): + item = self.ctrl.avgParamList.item(i) + if item.checkState() == QtCore.Qt.Checked: + remKeys.append(str(item.text())) + else: + addKeys.append(str(item.text())) + + if len(remKeys) < 1: ## In this case, there would be 1 average plot for each data plot; not useful. + return + + p = self.itemMeta.get(curve,{}).copy() + for k in p: + if type(k) is tuple: + p['.'.join(k)] = p[k] + del p[k] + for rk in remKeys: + if rk in p: + del p[rk] + for ak in addKeys: + if ak not in p: + p[ak] = None + key = tuple(p.items()) + + ### Create a new curve if needed + if key not in self.avgCurves: + plot = PlotDataItem() + plot.setPen(fn.mkPen([0, 200, 0])) + plot.setShadowPen(fn.mkPen([0, 0, 0, 100], width=3)) + plot.setAlpha(1.0, False) + plot.setZValue(100) + self.addItem(plot, skipAverage=True) + self.avgCurves[key] = [0, plot] + self.avgCurves[key][0] += 1 + (n, plot) = self.avgCurves[key] + + ### Average data together + (x, y) = curve.getData() + if plot.yData is not None: + newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n) + plot.setData(plot.xData, newData) + else: + plot.setData(x, y) + + def autoBtnClicked(self): + if self.autoBtn.mode == 'auto': + self.enableAutoRange() + self.autoBtn.hide() + else: + self.disableAutoRange() + + def viewStateChanged(self): + self.updateButtons() + + def enableAutoScale(self): + """ + Enable auto-scaling. The plot will continuously scale to fit the boundaries of its data. + """ + print("Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead.") + self.vb.enableAutoRange(self.vb.XYAxes) + + def addItem(self, item, *args, **kargs): + """ + Add a graphics item to the view box. + If the item has plot data (PlotDataItem, PlotCurveItem, ScatterPlotItem), it may + be included in analysis performed by the PlotItem. + """ + self.items.append(item) + vbargs = {} + if 'ignoreBounds' in kargs: + vbargs['ignoreBounds'] = kargs['ignoreBounds'] + self.vb.addItem(item, *args, **vbargs) + if hasattr(item, 'implements') and item.implements('plotData'): + self.dataItems.append(item) + #self.plotChanged() + + params = kargs.get('params', {}) + self.itemMeta[item] = params + #item.setMeta(params) + self.curves.append(item) + #self.addItem(c) + + if hasattr(item, 'setLogMode'): + item.setLogMode(self.ctrl.logXCheck.isChecked(), self.ctrl.logYCheck.isChecked()) + + if isinstance(item, PlotDataItem): + ## configure curve for this plot + (alpha, auto) = self.alphaState() + item.setAlpha(alpha, auto) + item.setFftMode(self.ctrl.fftCheck.isChecked()) + item.setDownsampling(*self.downsampleMode()) + item.setClipToView(self.clipToViewMode()) + item.setPointMode(self.pointMode()) + + ## Hide older plots if needed + self.updateDecimation() + + ## Add to average if needed + self.updateParamList() + if self.ctrl.averageGroup.isChecked() and 'skipAverage' not in kargs: + self.addAvgCurve(item) + + #c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged) + #item.sigPlotChanged.connect(self.plotChanged) + #self.plotChanged() + name = kargs.get('name', getattr(item, 'opts', {}).get('name', None)) + if name is not None and hasattr(self, 'legend') and self.legend is not None: + self.legend.addItem(item, name=name) + + + def addDataItem(self, item, *args): + print("PlotItem.addDataItem is deprecated. Use addItem instead.") + self.addItem(item, *args) + + def listDataItems(self): + """Return a list of all data items (PlotDataItem, PlotCurveItem, ScatterPlotItem, etc) + contained in this PlotItem.""" + return self.dataItems[:] + + def addCurve(self, c, params=None): + print("PlotItem.addCurve is deprecated. Use addItem instead.") + self.addItem(c, params) + + def addLine(self, x=None, y=None, z=None, **kwds): + """ + Create an InfiniteLine and add to the plot. + + If *x* is specified, + the line will be vertical. If *y* is specified, the line will be + horizontal. All extra keyword arguments are passed to + :func:`InfiniteLine.__init__() `. + Returns the item created. + """ + pos = kwds.get('pos', x if x is not None else y) + angle = kwds.get('angle', 0 if x is None else 90) + line = InfiniteLine(pos, angle, **kwds) + self.addItem(line) + if z is not None: + line.setZValue(z) + return line + + + + def removeItem(self, item): + """ + Remove an item from the internal ViewBox. + """ + if not item in self.items: + return + self.items.remove(item) + if item in self.dataItems: + self.dataItems.remove(item) + + if item.scene() is not None: + self.vb.removeItem(item) + if item in self.curves: + self.curves.remove(item) + self.updateDecimation() + self.updateParamList() + #item.connect(item, QtCore.SIGNAL('plotChanged'), self.plotChanged) + #item.sigPlotChanged.connect(self.plotChanged) + + def clear(self): + """ + Remove all items from the ViewBox. + """ + for i in self.items[:]: + self.removeItem(i) + self.avgCurves = {} + + def clearPlots(self): + for i in self.curves[:]: + self.removeItem(i) + self.avgCurves = {} + + + def plot(self, *args, **kargs): + """ + Add and return a new plot. + See :func:`PlotDataItem.__init__ ` for data arguments + + Extra allowed arguments are: + clear - clear all plots before displaying new data + params - meta-parameters to associate with this data + """ + + + clear = kargs.get('clear', False) + params = kargs.get('params', None) + + if clear: + self.clear() + + item = PlotDataItem(*args, **kargs) + + if params is None: + params = {} + self.addItem(item, params=params) + + return item + + def addLegend(self, size=None, offset=(30, 30)): + """ + Create a new LegendItem and anchor it over the internal ViewBox. + Plots will be automatically displayed in the legend if they + are created with the 'name' argument. + """ + self.legend = LegendItem(size, offset) + self.legend.setParentItem(self.vb) + return self.legend + + def scatterPlot(self, *args, **kargs): + if 'pen' in kargs: + kargs['symbolPen'] = kargs['pen'] + kargs['pen'] = None + + if 'brush' in kargs: + kargs['symbolBrush'] = kargs['brush'] + del kargs['brush'] + + if 'size' in kargs: + kargs['symbolSize'] = kargs['size'] + del kargs['size'] + + return self.plot(*args, **kargs) + + def replot(self): + self.update() + + def updateParamList(self): + self.ctrl.avgParamList.clear() + ## Check to see that each parameter for each curve is present in the list + for c in self.curves: + for p in list(self.itemMeta.get(c, {}).keys()): + if type(p) is tuple: + p = '.'.join(p) + + ## If the parameter is not in the list, add it. + matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly) + if len(matches) == 0: + i = QtGui.QListWidgetItem(p) + if p in self.paramList and self.paramList[p] is True: + i.setCheckState(QtCore.Qt.Checked) + else: + i.setCheckState(QtCore.Qt.Unchecked) + self.ctrl.avgParamList.addItem(i) + else: + i = matches[0] + + self.paramList[p] = (i.checkState() == QtCore.Qt.Checked) + + + ## Qt's SVG-writing capabilities are pretty terrible. + def writeSvgCurves(self, fileName=None): + if fileName is None: + self.fileDialog = FileDialog() + if PlotItem.lastFileDir is not None: + self.fileDialog.setDirectory(PlotItem.lastFileDir) + self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.writeSvg) + return + #if fileName is None: + #fileName = QtGui.QFileDialog.getSaveFileName() + if isinstance(fileName, tuple): + raise Exception("Not implemented yet..") + fileName = str(fileName) + PlotItem.lastFileDir = os.path.dirname(fileName) + + rect = self.vb.viewRect() + xRange = rect.left(), rect.right() + + svg = "" + fh = open(fileName, 'w') + + dx = max(rect.right(),0) - min(rect.left(),0) + ymn = min(rect.top(), rect.bottom()) + ymx = max(rect.top(), rect.bottom()) + dy = max(ymx,0) - min(ymn,0) + sx = 1. + sy = 1. + while dx*sx < 10: + sx *= 1000 + while dy*sy < 10: + sy *= 1000 + sy *= -1 + + #fh.write('\n' % (rect.left()*sx, rect.top()*sx, rect.width()*sy, rect.height()*sy)) + fh.write('\n') + fh.write('\n' % (rect.left()*sx, rect.right()*sx)) + fh.write('\n' % (rect.top()*sy, rect.bottom()*sy)) + + + for item in self.curves: + if isinstance(item, PlotCurveItem): + color = fn.colorStr(item.pen.color()) + opacity = item.pen.color().alpha() / 255. + color = color[:6] + x, y = item.getData() + mask = (x > xRange[0]) * (x < xRange[1]) + mask[:-1] += mask[1:] + m2 = mask.copy() + mask[1:] += m2[:-1] + x = x[mask] + y = y[mask] + + x *= sx + y *= sy + + #fh.write('\n' % color) + fh.write('') + #fh.write("") + for item in self.dataItems: + if isinstance(item, ScatterPlotItem): + + pRect = item.boundingRect() + vRect = pRect.intersected(rect) + + for point in item.points(): + pos = point.pos() + if not rect.contains(pos): + continue + color = fn.colorStr(point.brush.color()) + opacity = point.brush.color().alpha() / 255. + color = color[:6] + x = pos.x() * sx + y = pos.y() * sy + + fh.write('\n' % (x, y, color, opacity)) + #fh.write('') + + ## get list of curves, scatter plots + + + fh.write("\n") + + + + def writeSvg(self, fileName=None): + if fileName is None: + fileName = QtGui.QFileDialog.getSaveFileName() + fileName = str(fileName) + PlotItem.lastFileDir = os.path.dirname(fileName) + + self.svg = QtSvg.QSvgGenerator() + self.svg.setFileName(fileName) + res = 120. + view = self.scene().views()[0] + bounds = view.viewport().rect() + bounds = QtCore.QRectF(0, 0, bounds.width(), bounds.height()) + + self.svg.setResolution(res) + self.svg.setViewBox(bounds) + + self.svg.setSize(QtCore.QSize(bounds.width(), bounds.height())) + + painter = QtGui.QPainter(self.svg) + view.render(painter, bounds) + + painter.end() + + ## Workaround to set pen widths correctly + import re + data = open(fileName).readlines() + for i in range(len(data)): + line = data[i] + m = re.match(r'(= split: + curves[i].show() + else: + if self.ctrl.forgetTracesCheck.isChecked(): + curves[i].clear() + self.removeItem(curves[i]) + else: + curves[i].hide() + + + def updateAlpha(self, *args): + (alpha, auto) = self.alphaState() + for c in self.curves: + c.setAlpha(alpha**2, auto) + + def alphaState(self): + enabled = self.ctrl.alphaGroup.isChecked() + auto = self.ctrl.autoAlphaCheck.isChecked() + alpha = float(self.ctrl.alphaSlider.value()) / self.ctrl.alphaSlider.maximum() + if auto: + alpha = 1.0 ## should be 1/number of overlapping plots + if not enabled: + auto = False + alpha = 1.0 + return (alpha, auto) + + def pointMode(self): + if self.ctrl.pointsGroup.isChecked(): + if self.ctrl.autoPointsCheck.isChecked(): + mode = None + else: + mode = True + else: + mode = False + return mode + + + def resizeEvent(self, ev): + if self.autoBtn is None: ## already closed down + return + btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect()) + y = self.size().height() - btnRect.height() + self.autoBtn.setPos(0, y) + + + def getMenu(self): + return self.ctrlMenu + + def getContextMenus(self, event): + ## called when another item is displaying its context menu; we get to add extras to the end of the menu. + if self.menuEnabled(): + return self.ctrlMenu + else: + return None + + def setMenuEnabled(self, enableMenu=True, enableViewBoxMenu='same'): + """ + Enable or disable the context menu for this PlotItem. + By default, the ViewBox's context menu will also be affected. + (use enableViewBoxMenu=None to leave the ViewBox unchanged) + """ + self._menuEnabled = enableMenu + if enableViewBoxMenu is None: + return + if enableViewBoxMenu is 'same': + enableViewBoxMenu = enableMenu + self.vb.setMenuEnabled(enableViewBoxMenu) + + def menuEnabled(self): + return self._menuEnabled + + def hoverEvent(self, ev): + if ev.enter: + self.mouseHovering = True + if ev.exit: + self.mouseHovering = False + + self.updateButtons() + + + def getLabel(self, key): + pass + + def _checkScaleKey(self, key): + if key not in self.axes: + raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.axes.keys())))) + + def getScale(self, key): + return self.getAxis(key) + + def getAxis(self, name): + """Return the specified AxisItem. + *name* should be 'left', 'bottom', 'top', or 'right'.""" + self._checkScaleKey(name) + return self.axes[name]['item'] + + def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args): + """ + Set the label for an axis. Basic HTML formatting is allowed. + + ============= ================================================================= + **Arguments** + axis must be one of 'left', 'bottom', 'right', or 'top' + text text to display along the axis. HTML allowed. + units units to display after the title. If units are given, + then an SI prefix will be automatically appended + and the axis values will be scaled accordingly. + (ie, use 'V' instead of 'mV'; 'm' will be added automatically) + ============= ================================================================= + """ + self.getAxis(axis).setLabel(text=text, units=units, **args) + self.showAxis(axis) + + def setLabels(self, **kwds): + """ + Convenience function allowing multiple labels and/or title to be set in one call. + Keyword arguments can be 'title', 'left', 'bottom', 'right', or 'top'. + Values may be strings or a tuple of arguments to pass to setLabel. + """ + for k,v in kwds.items(): + if k == 'title': + self.setTitle(v) + else: + if isinstance(v, basestring): + v = (v,) + self.setLabel(k, *v) + + + def showLabel(self, axis, show=True): + """ + Show or hide one of the plot's axis labels (the axis itself will be unaffected). + axis must be one of 'left', 'bottom', 'right', or 'top' + """ + self.getScale(axis).showLabel(show) + + def setTitle(self, title=None, **args): + """ + Set the title of the plot. Basic HTML formatting is allowed. + If title is None, then the title will be hidden. + """ + if title is None: + self.titleLabel.setVisible(False) + self.layout.setRowFixedHeight(0, 0) + self.titleLabel.setMaximumHeight(0) + else: + self.titleLabel.setMaximumHeight(30) + self.layout.setRowFixedHeight(0, 30) + self.titleLabel.setVisible(True) + self.titleLabel.setText(title, **args) + + def showAxis(self, axis, show=True): + """ + Show or hide one of the plot's axes. + axis must be one of 'left', 'bottom', 'right', or 'top' + """ + s = self.getScale(axis) + p = self.axes[axis]['pos'] + if show: + s.show() + else: + s.hide() + + def hideAxis(self, axis): + """Hide one of the PlotItem's axes. ('left', 'bottom', 'right', or 'top')""" + self.showAxis(axis, False) + + def showScale(self, *args, **kargs): + print("Deprecated. use showAxis() instead") + return self.showAxis(*args, **kargs) + + def hideButtons(self): + """Causes auto-scale button ('A' in lower-left corner) to be hidden for this PlotItem""" + #self.ctrlBtn.hide() + self.buttonsHidden = True + self.updateButtons() + + def showButtons(self): + """Causes auto-scale button ('A' in lower-left corner) to be visible for this PlotItem""" + #self.ctrlBtn.hide() + self.buttonsHidden = False + self.updateButtons() + + def updateButtons(self): + if self._exportOpts is False and self.mouseHovering and not self.buttonsHidden and not all(self.vb.autoRangeEnabled()): + self.autoBtn.show() + else: + self.autoBtn.hide() + + def _plotArray(self, arr, x=None, **kargs): + if arr.ndim != 1: + raise Exception("Array must be 1D to plot (shape is %s)" % arr.shape) + if x is None: + x = np.arange(arr.shape[0]) + if x.ndim != 1: + raise Exception("X array must be 1D to plot (shape is %s)" % x.shape) + c = PlotCurveItem(arr, x=x, **kargs) + return c + + + + def _plotMetaArray(self, arr, x=None, autoLabel=True, **kargs): + inf = arr.infoCopy() + if arr.ndim != 1: + raise Exception('can only automatically plot 1 dimensional arrays.') + ## create curve + try: + xv = arr.xvals(0) + except: + if x is None: + xv = np.arange(arr.shape[0]) + else: + xv = x + c = PlotCurveItem(**kargs) + c.setData(x=xv, y=arr.view(np.ndarray)) + + if autoLabel: + name = arr._info[0].get('name', None) + units = arr._info[0].get('units', None) + self.setLabel('bottom', text=name, units=units) + + name = arr._info[1].get('name', None) + units = arr._info[1].get('units', None) + self.setLabel('left', text=name, units=units) + + return c + + + def setExportMode(self, export, opts=None): + GraphicsWidget.setExportMode(self, export, opts) + self.updateButtons() + #if export: + #self.autoBtn.hide() + #else: + #self.autoBtn.show() + diff --git a/pyqtgraph/graphicsItems/PlotItem/__init__.py b/pyqtgraph/graphicsItems/PlotItem/__init__.py new file mode 100644 index 00000000..d797978c --- /dev/null +++ b/pyqtgraph/graphicsItems/PlotItem/__init__.py @@ -0,0 +1 @@ +from .PlotItem import PlotItem diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui new file mode 100644 index 00000000..dffc62d0 --- /dev/null +++ b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui @@ -0,0 +1,343 @@ + + + Form + + + + 0 + 0 + 481 + 840 + + + + Form + + + + + 0 + 640 + 242 + 182 + + + + Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available). + + + Average + + + true + + + false + + + + 0 + + + 0 + + + + + + + + + + 10 + 140 + 191 + 171 + + + + + 0 + + + 0 + + + + + Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced. + + + Clip to View + + + + + + + If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed. + + + Max Traces: + + + + + + + Downsample + + + + + + + Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower. + + + Peak + + + true + + + + + + + If multiple curves are displayed in this plot, check "Max Traces" and set this value to limit the number of traces that are displayed. + + + + + + + If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden). + + + Forget hidden traces + + + + + + + Downsample by taking the mean of N samples. + + + Mean + + + + + + + Downsample by taking the first of N samples. This method is fastest and least accurate. + + + Subsample + + + + + + + Automatically downsample data based on the visible range. This assumes X values are uniformly spaced. + + + Auto + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 30 + 20 + + + + + + + + Downsample data before plotting. (plot every Nth sample) + + + x + + + 1 + + + 100000 + + + 1 + + + + + + + + + 0 + 0 + 154 + 79 + + + + + + + Power Spectrum (FFT) + + + + + + + Log X + + + + + + + Log Y + + + + + + + + + 10 + 550 + 234 + 58 + + + + Points + + + true + + + + + + Auto + + + true + + + + + + + + + 10 + 460 + 221 + 81 + + + + + + + Show X Grid + + + + + + + Show Y Grid + + + + + + + 255 + + + 128 + + + Qt::Horizontal + + + + + + + Opacity + + + + + + + + + 10 + 390 + 234 + 60 + + + + Alpha + + + true + + + + + + Auto + + + false + + + + + + + 1000 + + + 1000 + + + Qt::Horizontal + + + + + + + + + diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py new file mode 100644 index 00000000..5335ee76 --- /dev/null +++ b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui' +# +# Created: Mon Jul 1 23:21:08 2013 +# by: PyQt4 UI code generator 4.9.3 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(481, 840) + self.averageGroup = QtGui.QGroupBox(Form) + self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) + self.averageGroup.setCheckable(True) + self.averageGroup.setChecked(False) + self.averageGroup.setObjectName(_fromUtf8("averageGroup")) + self.gridLayout_5 = QtGui.QGridLayout(self.averageGroup) + self.gridLayout_5.setMargin(0) + self.gridLayout_5.setSpacing(0) + self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) + self.avgParamList = QtGui.QListWidget(self.averageGroup) + self.avgParamList.setObjectName(_fromUtf8("avgParamList")) + self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) + self.decimateGroup = QtGui.QFrame(Form) + self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) + self.decimateGroup.setObjectName(_fromUtf8("decimateGroup")) + self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) + self.gridLayout_4.setMargin(0) + self.gridLayout_4.setSpacing(0) + self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) + self.clipToViewCheck = QtGui.QCheckBox(self.decimateGroup) + self.clipToViewCheck.setObjectName(_fromUtf8("clipToViewCheck")) + self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) + self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup) + self.maxTracesCheck.setObjectName(_fromUtf8("maxTracesCheck")) + self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) + self.downsampleCheck = QtGui.QCheckBox(self.decimateGroup) + self.downsampleCheck.setObjectName(_fromUtf8("downsampleCheck")) + self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) + self.peakRadio = QtGui.QRadioButton(self.decimateGroup) + self.peakRadio.setChecked(True) + self.peakRadio.setObjectName(_fromUtf8("peakRadio")) + self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) + self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup) + self.maxTracesSpin.setObjectName(_fromUtf8("maxTracesSpin")) + self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) + self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup) + self.forgetTracesCheck.setObjectName(_fromUtf8("forgetTracesCheck")) + self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) + self.meanRadio = QtGui.QRadioButton(self.decimateGroup) + self.meanRadio.setObjectName(_fromUtf8("meanRadio")) + self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) + self.subsampleRadio = QtGui.QRadioButton(self.decimateGroup) + self.subsampleRadio.setObjectName(_fromUtf8("subsampleRadio")) + self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) + self.autoDownsampleCheck = QtGui.QCheckBox(self.decimateGroup) + self.autoDownsampleCheck.setChecked(True) + self.autoDownsampleCheck.setObjectName(_fromUtf8("autoDownsampleCheck")) + self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) + spacerItem = QtGui.QSpacerItem(30, 20, QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Minimum) + self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) + self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) + self.downsampleSpin.setMinimum(1) + self.downsampleSpin.setMaximum(100000) + self.downsampleSpin.setProperty("value", 1) + self.downsampleSpin.setObjectName(_fromUtf8("downsampleSpin")) + self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) + self.transformGroup = QtGui.QFrame(Form) + self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79)) + self.transformGroup.setObjectName(_fromUtf8("transformGroup")) + self.gridLayout = QtGui.QGridLayout(self.transformGroup) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.fftCheck = QtGui.QCheckBox(self.transformGroup) + self.fftCheck.setObjectName(_fromUtf8("fftCheck")) + self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) + self.logXCheck = QtGui.QCheckBox(self.transformGroup) + self.logXCheck.setObjectName(_fromUtf8("logXCheck")) + self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) + self.logYCheck = QtGui.QCheckBox(self.transformGroup) + self.logYCheck.setObjectName(_fromUtf8("logYCheck")) + self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) + self.pointsGroup = QtGui.QGroupBox(Form) + self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) + self.pointsGroup.setCheckable(True) + self.pointsGroup.setObjectName(_fromUtf8("pointsGroup")) + self.verticalLayout_5 = QtGui.QVBoxLayout(self.pointsGroup) + self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5")) + self.autoPointsCheck = QtGui.QCheckBox(self.pointsGroup) + self.autoPointsCheck.setChecked(True) + self.autoPointsCheck.setObjectName(_fromUtf8("autoPointsCheck")) + self.verticalLayout_5.addWidget(self.autoPointsCheck) + self.gridGroup = QtGui.QFrame(Form) + self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) + self.gridGroup.setObjectName(_fromUtf8("gridGroup")) + self.gridLayout_2 = QtGui.QGridLayout(self.gridGroup) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.xGridCheck = QtGui.QCheckBox(self.gridGroup) + self.xGridCheck.setObjectName(_fromUtf8("xGridCheck")) + self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) + self.yGridCheck = QtGui.QCheckBox(self.gridGroup) + self.yGridCheck.setObjectName(_fromUtf8("yGridCheck")) + self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) + self.gridAlphaSlider = QtGui.QSlider(self.gridGroup) + self.gridAlphaSlider.setMaximum(255) + self.gridAlphaSlider.setProperty("value", 128) + self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.gridAlphaSlider.setObjectName(_fromUtf8("gridAlphaSlider")) + self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) + self.label = QtGui.QLabel(self.gridGroup) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) + self.alphaGroup = QtGui.QGroupBox(Form) + self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) + self.alphaGroup.setCheckable(True) + self.alphaGroup.setObjectName(_fromUtf8("alphaGroup")) + self.horizontalLayout = QtGui.QHBoxLayout(self.alphaGroup) + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.autoAlphaCheck = QtGui.QCheckBox(self.alphaGroup) + self.autoAlphaCheck.setChecked(False) + self.autoAlphaCheck.setObjectName(_fromUtf8("autoAlphaCheck")) + self.horizontalLayout.addWidget(self.autoAlphaCheck) + self.alphaSlider = QtGui.QSlider(self.alphaGroup) + self.alphaSlider.setMaximum(1000) + self.alphaSlider.setProperty("value", 1000) + self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.alphaSlider.setObjectName(_fromUtf8("alphaSlider")) + self.horizontalLayout.addWidget(self.alphaSlider) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8)) + self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8)) + self.clipToViewCheck.setToolTip(QtGui.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) + self.clipToViewCheck.setText(QtGui.QApplication.translate("Form", "Clip to View", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8)) + self.downsampleCheck.setText(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8)) + self.peakRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None, QtGui.QApplication.UnicodeUTF8)) + self.peakRadio.setText(QtGui.QApplication.translate("Form", "Peak", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) + self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8)) + self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8)) + self.meanRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the mean of N samples.", None, QtGui.QApplication.UnicodeUTF8)) + self.meanRadio.setText(QtGui.QApplication.translate("Form", "Mean", None, QtGui.QApplication.UnicodeUTF8)) + self.subsampleRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None, QtGui.QApplication.UnicodeUTF8)) + self.subsampleRadio.setText(QtGui.QApplication.translate("Form", "Subsample", None, QtGui.QApplication.UnicodeUTF8)) + self.autoDownsampleCheck.setToolTip(QtGui.QApplication.translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) + self.autoDownsampleCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.downsampleSpin.setToolTip(QtGui.QApplication.translate("Form", "Downsample data before plotting. (plot every Nth sample)", None, QtGui.QApplication.UnicodeUTF8)) + self.downsampleSpin.setSuffix(QtGui.QApplication.translate("Form", "x", None, QtGui.QApplication.UnicodeUTF8)) + self.fftCheck.setText(QtGui.QApplication.translate("Form", "Power Spectrum (FFT)", None, QtGui.QApplication.UnicodeUTF8)) + self.logXCheck.setText(QtGui.QApplication.translate("Form", "Log X", None, QtGui.QApplication.UnicodeUTF8)) + self.logYCheck.setText(QtGui.QApplication.translate("Form", "Log Y", None, QtGui.QApplication.UnicodeUTF8)) + self.pointsGroup.setTitle(QtGui.QApplication.translate("Form", "Points", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPointsCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.xGridCheck.setText(QtGui.QApplication.translate("Form", "Show X Grid", None, QtGui.QApplication.UnicodeUTF8)) + self.yGridCheck.setText(QtGui.QApplication.translate("Form", "Show Y Grid", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("Form", "Opacity", None, QtGui.QApplication.UnicodeUTF8)) + self.alphaGroup.setTitle(QtGui.QApplication.translate("Form", "Alpha", None, QtGui.QApplication.UnicodeUTF8)) + self.autoAlphaCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py new file mode 100644 index 00000000..b8e0b19e --- /dev/null +++ b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui' +# +# Created: Mon Jul 1 23:21:08 2013 +# by: pyside-uic 0.2.13 running on PySide 1.1.2 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(481, 840) + self.averageGroup = QtGui.QGroupBox(Form) + self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) + self.averageGroup.setCheckable(True) + self.averageGroup.setChecked(False) + self.averageGroup.setObjectName("averageGroup") + self.gridLayout_5 = QtGui.QGridLayout(self.averageGroup) + self.gridLayout_5.setContentsMargins(0, 0, 0, 0) + self.gridLayout_5.setSpacing(0) + self.gridLayout_5.setObjectName("gridLayout_5") + self.avgParamList = QtGui.QListWidget(self.averageGroup) + self.avgParamList.setObjectName("avgParamList") + self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) + self.decimateGroup = QtGui.QFrame(Form) + self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) + self.decimateGroup.setObjectName("decimateGroup") + self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) + self.gridLayout_4.setContentsMargins(0, 0, 0, 0) + self.gridLayout_4.setSpacing(0) + self.gridLayout_4.setObjectName("gridLayout_4") + self.clipToViewCheck = QtGui.QCheckBox(self.decimateGroup) + self.clipToViewCheck.setObjectName("clipToViewCheck") + self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) + self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup) + self.maxTracesCheck.setObjectName("maxTracesCheck") + self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) + self.downsampleCheck = QtGui.QCheckBox(self.decimateGroup) + self.downsampleCheck.setObjectName("downsampleCheck") + self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) + self.peakRadio = QtGui.QRadioButton(self.decimateGroup) + self.peakRadio.setChecked(True) + self.peakRadio.setObjectName("peakRadio") + self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) + self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup) + self.maxTracesSpin.setObjectName("maxTracesSpin") + self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) + self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup) + self.forgetTracesCheck.setObjectName("forgetTracesCheck") + self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) + self.meanRadio = QtGui.QRadioButton(self.decimateGroup) + self.meanRadio.setObjectName("meanRadio") + self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) + self.subsampleRadio = QtGui.QRadioButton(self.decimateGroup) + self.subsampleRadio.setObjectName("subsampleRadio") + self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) + self.autoDownsampleCheck = QtGui.QCheckBox(self.decimateGroup) + self.autoDownsampleCheck.setChecked(True) + self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") + self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) + spacerItem = QtGui.QSpacerItem(30, 20, QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Minimum) + self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) + self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) + self.downsampleSpin.setMinimum(1) + self.downsampleSpin.setMaximum(100000) + self.downsampleSpin.setProperty("value", 1) + self.downsampleSpin.setObjectName("downsampleSpin") + self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) + self.transformGroup = QtGui.QFrame(Form) + self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79)) + self.transformGroup.setObjectName("transformGroup") + self.gridLayout = QtGui.QGridLayout(self.transformGroup) + self.gridLayout.setObjectName("gridLayout") + self.fftCheck = QtGui.QCheckBox(self.transformGroup) + self.fftCheck.setObjectName("fftCheck") + self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) + self.logXCheck = QtGui.QCheckBox(self.transformGroup) + self.logXCheck.setObjectName("logXCheck") + self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) + self.logYCheck = QtGui.QCheckBox(self.transformGroup) + self.logYCheck.setObjectName("logYCheck") + self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) + self.pointsGroup = QtGui.QGroupBox(Form) + self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) + self.pointsGroup.setCheckable(True) + self.pointsGroup.setObjectName("pointsGroup") + self.verticalLayout_5 = QtGui.QVBoxLayout(self.pointsGroup) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.autoPointsCheck = QtGui.QCheckBox(self.pointsGroup) + self.autoPointsCheck.setChecked(True) + self.autoPointsCheck.setObjectName("autoPointsCheck") + self.verticalLayout_5.addWidget(self.autoPointsCheck) + self.gridGroup = QtGui.QFrame(Form) + self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) + self.gridGroup.setObjectName("gridGroup") + self.gridLayout_2 = QtGui.QGridLayout(self.gridGroup) + self.gridLayout_2.setObjectName("gridLayout_2") + self.xGridCheck = QtGui.QCheckBox(self.gridGroup) + self.xGridCheck.setObjectName("xGridCheck") + self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) + self.yGridCheck = QtGui.QCheckBox(self.gridGroup) + self.yGridCheck.setObjectName("yGridCheck") + self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) + self.gridAlphaSlider = QtGui.QSlider(self.gridGroup) + self.gridAlphaSlider.setMaximum(255) + self.gridAlphaSlider.setProperty("value", 128) + self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.gridAlphaSlider.setObjectName("gridAlphaSlider") + self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) + self.label = QtGui.QLabel(self.gridGroup) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) + self.alphaGroup = QtGui.QGroupBox(Form) + self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) + self.alphaGroup.setCheckable(True) + self.alphaGroup.setObjectName("alphaGroup") + self.horizontalLayout = QtGui.QHBoxLayout(self.alphaGroup) + self.horizontalLayout.setObjectName("horizontalLayout") + self.autoAlphaCheck = QtGui.QCheckBox(self.alphaGroup) + self.autoAlphaCheck.setChecked(False) + self.autoAlphaCheck.setObjectName("autoAlphaCheck") + self.horizontalLayout.addWidget(self.autoAlphaCheck) + self.alphaSlider = QtGui.QSlider(self.alphaGroup) + self.alphaSlider.setMaximum(1000) + self.alphaSlider.setProperty("value", 1000) + self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.alphaSlider.setObjectName("alphaSlider") + self.horizontalLayout.addWidget(self.alphaSlider) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8)) + self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8)) + self.clipToViewCheck.setToolTip(QtGui.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) + self.clipToViewCheck.setText(QtGui.QApplication.translate("Form", "Clip to View", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8)) + self.downsampleCheck.setText(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8)) + self.peakRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None, QtGui.QApplication.UnicodeUTF8)) + self.peakRadio.setText(QtGui.QApplication.translate("Form", "Peak", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) + self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8)) + self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8)) + self.meanRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the mean of N samples.", None, QtGui.QApplication.UnicodeUTF8)) + self.meanRadio.setText(QtGui.QApplication.translate("Form", "Mean", None, QtGui.QApplication.UnicodeUTF8)) + self.subsampleRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None, QtGui.QApplication.UnicodeUTF8)) + self.subsampleRadio.setText(QtGui.QApplication.translate("Form", "Subsample", None, QtGui.QApplication.UnicodeUTF8)) + self.autoDownsampleCheck.setToolTip(QtGui.QApplication.translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) + self.autoDownsampleCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.downsampleSpin.setToolTip(QtGui.QApplication.translate("Form", "Downsample data before plotting. (plot every Nth sample)", None, QtGui.QApplication.UnicodeUTF8)) + self.downsampleSpin.setSuffix(QtGui.QApplication.translate("Form", "x", None, QtGui.QApplication.UnicodeUTF8)) + self.fftCheck.setText(QtGui.QApplication.translate("Form", "Power Spectrum (FFT)", None, QtGui.QApplication.UnicodeUTF8)) + self.logXCheck.setText(QtGui.QApplication.translate("Form", "Log X", None, QtGui.QApplication.UnicodeUTF8)) + self.logYCheck.setText(QtGui.QApplication.translate("Form", "Log Y", None, QtGui.QApplication.UnicodeUTF8)) + self.pointsGroup.setTitle(QtGui.QApplication.translate("Form", "Points", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPointsCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.xGridCheck.setText(QtGui.QApplication.translate("Form", "Show X Grid", None, QtGui.QApplication.UnicodeUTF8)) + self.yGridCheck.setText(QtGui.QApplication.translate("Form", "Show Y Grid", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("Form", "Opacity", None, QtGui.QApplication.UnicodeUTF8)) + self.alphaGroup.setTitle(QtGui.QApplication.translate("Form", "Alpha", None, QtGui.QApplication.UnicodeUTF8)) + self.autoAlphaCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py new file mode 100644 index 00000000..f6ce4680 --- /dev/null +++ b/pyqtgraph/graphicsItems/ROI.py @@ -0,0 +1,1903 @@ +# -*- coding: utf-8 -*- +""" +ROI.py - Interactive graphics items for GraphicsView (ROI widgets) +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +Implements a series of graphics items which display movable/scalable/rotatable shapes +for use as region-of-interest markers. ROI class automatically handles extraction +of array data from ImageItems. + +The ROI class is meant to serve as the base for more specific types; see several examples +of how to build an ROI at the bottom of the file. +""" + +from pyqtgraph.Qt import QtCore, QtGui +#if not hasattr(QtCore, 'Signal'): + #QtCore.Signal = QtCore.pyqtSignal +import numpy as np +from numpy.linalg import norm +import scipy.ndimage as ndimage +from pyqtgraph.Point import * +from pyqtgraph.SRTTransform import SRTTransform +from math import cos, sin +import pyqtgraph.functions as fn +from .GraphicsObject import GraphicsObject +from .UIGraphicsItem import UIGraphicsItem + +__all__ = [ + 'ROI', + 'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI', + 'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI', +] + + +def rectStr(r): + return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height()) + +class ROI(GraphicsObject): + """Generic region-of-interest widget. + Can be used for implementing many types of selection box with rotate/translate/scale handles. + + Signals + ----------------------- ---------------------------------------------------- + sigRegionChangeFinished Emitted when the user stops dragging the ROI (or + one of its handles) or if the ROI is changed + programatically. + sigRegionChangeStarted Emitted when the user starts dragging the ROI (or + one of its handles). + sigRegionChanged Emitted any time the position of the ROI changes, + including while it is being dragged by the user. + sigHoverEvent Emitted when the mouse hovers over the ROI. + sigClicked Emitted when the user clicks on the ROI. + Note that clicking is disabled by default to prevent + stealing clicks from objects behind the ROI. To + enable clicking, call + roi.setAcceptedMouseButtons(QtCore.Qt.LeftButton). + See QtGui.QGraphicsItem documentation for more + details. + sigRemoveRequested Emitted when the user selects 'remove' from the + ROI's context menu (if available). + ----------------------- ---------------------------------------------------- + """ + + sigRegionChangeFinished = QtCore.Signal(object) + sigRegionChangeStarted = QtCore.Signal(object) + sigRegionChanged = QtCore.Signal(object) + sigHoverEvent = QtCore.Signal(object) + sigClicked = QtCore.Signal(object, object) + sigRemoveRequested = QtCore.Signal(object) + + def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None, movable=True, removable=False): + #QObjectWorkaround.__init__(self) + GraphicsObject.__init__(self, parent) + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + pos = Point(pos) + size = Point(size) + self.aspectLocked = False + self.translatable = movable + self.rotateAllowed = True + self.removable = removable + self.menu = None + + self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted. + self.mouseHovering = False + if pen is None: + pen = (255, 255, 255) + self.setPen(pen) + + self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255)) + self.handles = [] + self.state = {'pos': Point(0,0), 'size': Point(1,1), 'angle': 0} ## angle is in degrees for ease of Qt integration + self.lastState = None + self.setPos(pos) + self.setAngle(angle) + self.setSize(size) + self.setZValue(10) + self.isMoving = False + + self.handleSize = 5 + self.invertible = invertible + self.maxBounds = maxBounds + + self.snapSize = snapSize + self.translateSnap = translateSnap + self.rotateSnap = rotateSnap + self.scaleSnap = scaleSnap + #self.setFlag(self.ItemIsSelectable, True) + + def getState(self): + return self.stateCopy() + + def stateCopy(self): + sc = {} + sc['pos'] = Point(self.state['pos']) + sc['size'] = Point(self.state['size']) + sc['angle'] = self.state['angle'] + return sc + + def saveState(self): + """Return the state of the widget in a format suitable for storing to disk. (Points are converted to tuple)""" + state = {} + state['pos'] = tuple(self.state['pos']) + state['size'] = tuple(self.state['size']) + state['angle'] = self.state['angle'] + return state + + def setState(self, state, update=True): + self.setPos(state['pos'], update=False) + self.setSize(state['size'], update=False) + self.setAngle(state['angle'], update=update) + + def setZValue(self, z): + QtGui.QGraphicsItem.setZValue(self, z) + for h in self.handles: + h['item'].setZValue(z+1) + + def parentBounds(self): + return self.mapToParent(self.boundingRect()).boundingRect() + + def setPen(self, pen): + self.pen = fn.mkPen(pen) + self.currentPen = self.pen + self.update() + + def size(self): + return self.getState()['size'] + + def pos(self): + return self.getState()['pos'] + + def angle(self): + return self.getState()['angle'] + + def setPos(self, pos, update=True, finish=True): + """Set the position of the ROI (in the parent's coordinate system). + By default, this will cause both sigRegionChanged and sigRegionChangeFinished to be emitted. + + If finish is False, then sigRegionChangeFinished will not be emitted. You can then use + stateChangeFinished() to cause the signal to be emitted after a series of state changes. + + If update is False, the state change will be remembered but not processed and no signals + will be emitted. You can then use stateChanged() to complete the state change. This allows + multiple change functions to be called sequentially while minimizing processing overhead + and repeated signals. Setting update=False also forces finish=False. + """ + + pos = Point(pos) + self.state['pos'] = pos + QtGui.QGraphicsItem.setPos(self, pos) + if update: + self.stateChanged(finish=finish) + + def setSize(self, size, update=True, finish=True): + """Set the size of the ROI. May be specified as a QPoint, Point, or list of two values. + See setPos() for an explanation of the update and finish arguments. + """ + size = Point(size) + self.prepareGeometryChange() + self.state['size'] = size + if update: + self.stateChanged(finish=finish) + + def setAngle(self, angle, update=True, finish=True): + """Set the angle of rotation (in degrees) for this ROI. + See setPos() for an explanation of the update and finish arguments. + """ + self.state['angle'] = angle + tr = QtGui.QTransform() + #tr.rotate(-angle * 180 / np.pi) + tr.rotate(angle) + self.setTransform(tr) + if update: + self.stateChanged(finish=finish) + + def scale(self, s, center=[0,0], update=True, finish=True): + """ + Resize the ROI by scaling relative to *center*. + See setPos() for an explanation of the *update* and *finish* arguments. + """ + c = self.mapToParent(Point(center) * self.state['size']) + self.prepareGeometryChange() + newSize = self.state['size'] * s + c1 = self.mapToParent(Point(center) * newSize) + newPos = self.state['pos'] + c - c1 + + self.setSize(newSize, update=False) + self.setPos(newPos, update=update, finish=finish) + + + def translate(self, *args, **kargs): + """ + Move the ROI to a new position. + Accepts either (x, y, snap) or ([x,y], snap) as arguments + If the ROI is bounded and the move would exceed boundaries, then the ROI + is moved to the nearest acceptable position instead. + + snap can be: + None (default): use self.translateSnap and self.snapSize to determine whether/how to snap + False: do not snap + Point(w,h) snap to rectangular grid with spacing (w,h) + True: snap using self.snapSize (and ignoring self.translateSnap) + + Also accepts *update* and *finish* arguments (see setPos() for a description of these). + """ + + if len(args) == 1: + pt = args[0] + else: + pt = args + + newState = self.stateCopy() + newState['pos'] = newState['pos'] + pt + + ## snap position + #snap = kargs.get('snap', None) + #if (snap is not False) and not (snap is None and self.translateSnap is False): + + snap = kargs.get('snap', None) + if snap is None: + snap = self.translateSnap + if snap is not False: + newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap) + + #d = ev.scenePos() - self.mapToScene(self.pressPos) + if self.maxBounds is not None: + r = self.stateRect(newState) + #r0 = self.sceneTransform().mapRect(self.boundingRect()) + d = Point(0,0) + if self.maxBounds.left() > r.left(): + d[0] = self.maxBounds.left() - r.left() + elif self.maxBounds.right() < r.right(): + d[0] = self.maxBounds.right() - r.right() + if self.maxBounds.top() > r.top(): + d[1] = self.maxBounds.top() - r.top() + elif self.maxBounds.bottom() < r.bottom(): + d[1] = self.maxBounds.bottom() - r.bottom() + newState['pos'] += d + + #self.state['pos'] = newState['pos'] + update = kargs.get('update', True) + finish = kargs.get('finish', True) + self.setPos(newState['pos'], update=update, finish=finish) + #if 'update' not in kargs or kargs['update'] is True: + #self.stateChanged() + + def rotate(self, angle, update=True, finish=True): + self.setAngle(self.angle()+angle, update=update, finish=finish) + + def handleMoveStarted(self): + self.preMoveState = self.getState() + + def addTranslateHandle(self, pos, axes=None, item=None, name=None, index=None): + pos = Point(pos) + return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item}, index=index) + + def addFreeHandle(self, pos=None, axes=None, item=None, name=None, index=None): + if pos is not None: + pos = Point(pos) + return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item}, index=index) + + def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspect=False, index=None): + pos = Point(pos) + center = Point(center) + info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item, 'lockAspect': lockAspect} + if pos.x() == center.x(): + info['xoff'] = True + if pos.y() == center.y(): + info['yoff'] = True + return self.addHandle(info, index=index) + + def addRotateHandle(self, pos, center, item=None, name=None, index=None): + pos = Point(pos) + center = Point(center) + return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item}, index=index) + + def addScaleRotateHandle(self, pos, center, item=None, name=None, index=None): + pos = Point(pos) + center = Point(center) + if pos[0] != center[0] and pos[1] != center[1]: + raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.") + return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item}, index=index) + + def addRotateFreeHandle(self, pos, center, axes=None, item=None, name=None, index=None): + pos = Point(pos) + center = Point(center) + return self.addHandle({'name': name, 'type': 'rf', 'center': center, 'pos': pos, 'item': item}, index=index) + + def addHandle(self, info, index=None): + ## If a Handle was not supplied, create it now + if 'item' not in info or info['item'] is None: + h = Handle(self.handleSize, typ=info['type'], pen=self.handlePen, parent=self) + h.setPos(info['pos'] * self.state['size']) + info['item'] = h + else: + h = info['item'] + if info['pos'] is None: + info['pos'] = h.pos() + + ## connect the handle to this ROI + #iid = len(self.handles) + h.connectROI(self) + if index is None: + self.handles.append(info) + else: + self.handles.insert(index, info) + + h.setZValue(self.zValue()+1) + self.stateChanged() + return h + + def indexOfHandle(self, handle): + if isinstance(handle, Handle): + index = [i for i, info in enumerate(self.handles) if info['item'] is handle] + if len(index) == 0: + raise Exception("Cannot remove handle; it is not attached to this ROI") + return index[0] + else: + return handle + + def removeHandle(self, handle): + """Remove a handle from this ROI. Argument may be either a Handle instance or the integer index of the handle.""" + index = self.indexOfHandle(handle) + + handle = self.handles[index]['item'] + self.handles.pop(index) + handle.disconnectROI(self) + if len(handle.rois) == 0: + self.scene().removeItem(handle) + self.stateChanged() + + def replaceHandle(self, oldHandle, newHandle): + """Replace one handle in the ROI for another. This is useful when connecting multiple ROIs together. + *oldHandle* may be a Handle instance or the index of a handle.""" + #print "=========================" + #print "replace", oldHandle, newHandle + #print self + #print self.handles + #print "-----------------" + index = self.indexOfHandle(oldHandle) + info = self.handles[index] + self.removeHandle(index) + info['item'] = newHandle + info['pos'] = newHandle.pos() + self.addHandle(info, index=index) + #print self.handles + + def checkRemoveHandle(self, handle): + ## This is used when displaying a Handle's context menu to determine + ## whether removing is allowed. + ## Subclasses may wish to override this to disable the menu entry. + ## Note: by default, handles are not user-removable even if this method returns True. + return True + + + def getLocalHandlePositions(self, index=None): + """Returns the position of a handle in ROI coordinates""" + if index == None: + positions = [] + for h in self.handles: + positions.append((h['name'], h['pos'])) + return positions + else: + return (self.handles[index]['name'], self.handles[index]['pos']) + + def getSceneHandlePositions(self, index=None): + if index == None: + positions = [] + for h in self.handles: + positions.append((h['name'], h['item'].scenePos())) + return positions + else: + return (self.handles[index]['name'], self.handles[index]['item'].scenePos()) + + def getHandles(self): + return [h['item'] for h in self.handles] + + def mapSceneToParent(self, pt): + return self.mapToParent(self.mapFromScene(pt)) + + def setSelected(self, s): + QtGui.QGraphicsItem.setSelected(self, s) + #print "select", self, s + if s: + for h in self.handles: + h['item'].show() + else: + for h in self.handles: + h['item'].hide() + + + def hoverEvent(self, ev): + hover = False + if not ev.isExit(): + if self.translatable and ev.acceptDrags(QtCore.Qt.LeftButton): + hover=True + + for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MidButton]: + if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn): + hover=True + if self.contextMenuEnabled(): + ev.acceptClicks(QtCore.Qt.RightButton) + + if hover: + self.setMouseHover(True) + self.sigHoverEvent.emit(self) + ev.acceptClicks(QtCore.Qt.LeftButton) ## If the ROI is hilighted, we should accept all clicks to avoid confusion. + ev.acceptClicks(QtCore.Qt.RightButton) + ev.acceptClicks(QtCore.Qt.MidButton) + else: + self.setMouseHover(False) + + def setMouseHover(self, hover): + ## Inform the ROI that the mouse is(not) hovering over it + if self.mouseHovering == hover: + return + self.mouseHovering = hover + if hover: + self.currentPen = fn.mkPen(255, 255, 0) + else: + self.currentPen = self.pen + self.update() + + def contextMenuEnabled(self): + return self.removable + + def raiseContextMenu(self, ev): + if not self.contextMenuEnabled(): + return + menu = self.getMenu() + menu = self.scene().addParentContextMenus(self, menu, ev) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def getMenu(self): + if self.menu is None: + self.menu = QtGui.QMenu() + self.menu.setTitle("ROI") + remAct = QtGui.QAction("Remove ROI", self.menu) + remAct.triggered.connect(self.removeClicked) + self.menu.addAction(remAct) + self.menu.remAct = remAct + return self.menu + + def removeClicked(self): + ## Send remove event only after we have exited the menu event handler + self.removeTimer = QtCore.QTimer() + self.removeTimer.timeout.connect(lambda: self.sigRemoveRequested.emit(self)) + self.removeTimer.start(0) + + + + def mouseDragEvent(self, ev): + if ev.isStart(): + #p = ev.pos() + #if not self.isMoving and not self.shape().contains(p): + #ev.ignore() + #return + if ev.button() == QtCore.Qt.LeftButton: + self.setSelected(True) + if self.translatable: + self.isMoving = True + self.preMoveState = self.getState() + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + self.sigRegionChangeStarted.emit(self) + ev.accept() + else: + ev.ignore() + + elif ev.isFinish(): + if self.translatable: + if self.isMoving: + self.stateChangeFinished() + self.isMoving = False + return + + if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton: + snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None + newPos = self.mapToParent(ev.pos()) + self.cursorOffset + self.translate(newPos - self.pos(), snap=snap, finish=False) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton and self.isMoving: + ev.accept() + self.cancelMove() + if ev.button() == QtCore.Qt.RightButton and self.contextMenuEnabled(): + self.raiseContextMenu(ev) + ev.accept() + elif int(ev.button() & self.acceptedMouseButtons()) > 0: + ev.accept() + self.sigClicked.emit(self, ev) + else: + ev.ignore() + + + + + def cancelMove(self): + self.isMoving = False + self.setState(self.preMoveState) + + + #def pointDragEvent(self, pt, ev): + ### just for handling drag start/stop. + ### drag moves are handled through movePoint() + + #if ev.isStart(): + #self.isMoving = True + #self.preMoveState = self.getState() + + #self.sigRegionChangeStarted.emit(self) + #elif ev.isFinish(): + #self.isMoving = False + #self.sigRegionChangeFinished.emit(self) + #return + + + #def pointPressEvent(self, pt, ev): + ##print "press" + #self.isMoving = True + #self.preMoveState = self.getState() + + ##self.emit(QtCore.SIGNAL('regionChangeStarted'), self) + #self.sigRegionChangeStarted.emit(self) + ##self.pressPos = self.mapFromScene(ev.scenePos()) + ##self.pressHandlePos = self.handles[pt]['item'].pos() + + #def pointReleaseEvent(self, pt, ev): + ##print "release" + #self.isMoving = False + ##self.emit(QtCore.SIGNAL('regionChangeFinished'), self) + #self.sigRegionChangeFinished.emit(self) + + #def pointMoveEvent(self, pt, ev): + #self.movePoint(pt, ev.scenePos(), ev.modifiers()) + + + def checkPointMove(self, handle, pos, modifiers): + """When handles move, they must ask the ROI if the move is acceptable. + By default, this always returns True. Subclasses may wish override. + """ + return True + + + def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True, coords='parent'): + ## called by Handles when they are moved. + ## pos is the new position of the handle in scene coords, as requested by the handle. + + newState = self.stateCopy() + index = self.indexOfHandle(handle) + h = self.handles[index] + p0 = self.mapToParent(h['pos'] * self.state['size']) + p1 = Point(pos) + + if coords == 'parent': + pass + elif coords == 'scene': + p1 = self.mapSceneToParent(p1) + else: + raise Exception("New point location must be given in either 'parent' or 'scene' coordinates.") + + + ## transform p0 and p1 into parent's coordinates (same as scene coords if there is no parent). I forget why. + #p0 = self.mapSceneToParent(p0) + #p1 = self.mapSceneToParent(p1) + + ## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1) + if 'center' in h: + c = h['center'] + cs = c * self.state['size'] + lp0 = self.mapFromParent(p0) - cs + lp1 = self.mapFromParent(p1) - cs + + if h['type'] == 't': + snap = True if (modifiers & QtCore.Qt.ControlModifier) else None + #if self.translateSnap or (): + #snap = Point(self.snapSize, self.snapSize) + self.translate(p1-p0, snap=snap, update=False) + + elif h['type'] == 'f': + newPos = self.mapFromParent(p1) + h['item'].setPos(newPos) + h['pos'] = newPos + self.freeHandleMoved = True + #self.sigRegionChanged.emit(self) ## should be taken care of by call to stateChanged() + + elif h['type'] == 's': + ## If a handle and its center have the same x or y value, we can't scale across that axis. + if h['center'][0] == h['pos'][0]: + lp1[0] = 0 + if h['center'][1] == h['pos'][1]: + lp1[1] = 0 + + ## snap + if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): + lp1[0] = round(lp1[0] / self.snapSize) * self.snapSize + lp1[1] = round(lp1[1] / self.snapSize) * self.snapSize + + ## preserve aspect ratio (this can override snapping) + if h['lockAspect'] or (modifiers & QtCore.Qt.AltModifier): + #arv = Point(self.preMoveState['size']) - + lp1 = lp1.proj(lp0) + + ## determine scale factors and new size of ROI + hs = h['pos'] - c + if hs[0] == 0: + hs[0] = 1 + if hs[1] == 0: + hs[1] = 1 + newSize = lp1 / hs + + ## Perform some corrections and limit checks + if newSize[0] == 0: + newSize[0] = newState['size'][0] + if newSize[1] == 0: + newSize[1] = newState['size'][1] + if not self.invertible: + if newSize[0] < 0: + newSize[0] = newState['size'][0] + if newSize[1] < 0: + newSize[1] = newState['size'][1] + if self.aspectLocked: + newSize[0] = newSize[1] + + ## Move ROI so the center point occupies the same scene location after the scale + s0 = c * self.state['size'] + s1 = c * newSize + cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0)) + + ## update state, do more boundary checks + newState['size'] = newSize + newState['pos'] = newState['pos'] + cc + if self.maxBounds is not None: + r = self.stateRect(newState) + if not self.maxBounds.contains(r): + return + + self.setPos(newState['pos'], update=False) + self.setSize(newState['size'], update=False) + + elif h['type'] in ['r', 'rf']: + if h['type'] == 'rf': + self.freeHandleMoved = True + + if not self.rotateAllowed: + return + ## If the handle is directly over its center point, we can't compute an angle. + if lp1.length() == 0 or lp0.length() == 0: + return + + ## determine new rotation angle, constrained if necessary + ang = newState['angle'] - lp0.angle(lp1) + if ang is None: ## this should never happen.. + return + if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): + ang = round(ang / 15.) * 15. ## 180/12 = 15 + + ## create rotation transform + tr = QtGui.QTransform() + tr.rotate(ang) + + ## move ROI so that center point remains stationary after rotate + cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos']) + newState['angle'] = ang + newState['pos'] = newState['pos'] + cc + + ## check boundaries, update + if self.maxBounds is not None: + r = self.stateRect(newState) + if not self.maxBounds.contains(r): + return + #self.setTransform(tr) + self.setPos(newState['pos'], update=False) + self.setAngle(ang, update=False) + #self.state = newState + + ## If this is a free-rotate handle, its distance from the center may change. + + if h['type'] == 'rf': + h['item'].setPos(self.mapFromScene(p1)) ## changes ROI coordinates of handle + + elif h['type'] == 'sr': + if h['center'][0] == h['pos'][0]: + scaleAxis = 1 + else: + scaleAxis = 0 + + if lp1.length() == 0 or lp0.length() == 0: + return + + ang = newState['angle'] - lp0.angle(lp1) + if ang is None: + return + if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): + #ang = round(ang / (np.pi/12.)) * (np.pi/12.) + ang = round(ang / 15.) * 15. + + hs = abs(h['pos'][scaleAxis] - c[scaleAxis]) + newState['size'][scaleAxis] = lp1.length() / hs + #if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): + if self.scaleSnap: ## use CTRL only for angular snap here. + newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize + if newState['size'][scaleAxis] == 0: + newState['size'][scaleAxis] = 1 + + c1 = c * newState['size'] + tr = QtGui.QTransform() + tr.rotate(ang) + + cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos']) + newState['angle'] = ang + newState['pos'] = newState['pos'] + cc + if self.maxBounds is not None: + r = self.stateRect(newState) + if not self.maxBounds.contains(r): + return + #self.setTransform(tr) + #self.setPos(newState['pos'], update=False) + #self.prepareGeometryChange() + #self.state = newState + self.setState(newState, update=False) + + self.stateChanged(finish=finish) + + def stateChanged(self, finish=True): + """Process changes to the state of the ROI. + If there are any changes, then the positions of handles are updated accordingly + and sigRegionChanged is emitted. If finish is True, then + sigRegionChangeFinished will also be emitted.""" + + changed = False + if self.lastState is None: + changed = True + else: + for k in list(self.state.keys()): + if self.state[k] != self.lastState[k]: + changed = True + + self.prepareGeometryChange() + if changed: + ## Move all handles to match the current configuration of the ROI + for h in self.handles: + if h['item'] in self.childItems(): + p = h['pos'] + h['item'].setPos(h['pos'] * self.state['size']) + #else: + # trans = self.state['pos']-self.lastState['pos'] + # h['item'].setPos(h['pos'] + h['item'].parentItem().mapFromParent(trans)) + + self.update() + self.sigRegionChanged.emit(self) + elif self.freeHandleMoved: + self.sigRegionChanged.emit(self) + + self.freeHandleMoved = False + self.lastState = self.stateCopy() + + if finish: + self.stateChangeFinished() + + def stateChangeFinished(self): + self.sigRegionChangeFinished.emit(self) + + def stateRect(self, state): + r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1]) + tr = QtGui.QTransform() + #tr.rotate(-state['angle'] * 180 / np.pi) + tr.rotate(-state['angle']) + r = tr.mapRect(r) + return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1]) + + + def getSnapPosition(self, pos, snap=None): + ## Given that pos has been requested, return the nearest snap-to position + ## optionally, snap may be passed in to specify a rectangular snap grid. + ## override this function for more interesting snap functionality.. + + if snap is None or snap is True: + if self.snapSize is None: + return pos + snap = Point(self.snapSize, self.snapSize) + + return Point( + round(pos[0] / snap[0]) * snap[0], + round(pos[1] / snap[1]) * snap[1] + ) + + + def boundingRect(self): + return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() + + def paint(self, p, opt, widget): + p.save() + r = self.boundingRect() + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + p.translate(r.left(), r.top()) + p.scale(r.width(), r.height()) + p.drawRect(0, 0, 1, 1) + p.restore() + + def getArraySlice(self, data, img, axes=(0,1), returnSlice=True): + """Return a tuple of slice objects that can be used to slice the region from data covered by this ROI. + Also returns the transform which maps the ROI into data coordinates. + + If returnSlice is set to False, the function returns a pair of tuples with the values that would have + been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop)) + + If the slice can not be computed (usually because the scene/transforms are not properly + constructed yet), then the method returns None. + """ + #print "getArraySlice" + + ## Determine shape of array along ROI axes + dShape = (data.shape[axes[0]], data.shape[axes[1]]) + #print " dshape", dShape + + ## Determine transform that maps ROI bounding box to image coordinates + try: + tr = self.sceneTransform() * fn.invertQTransform(img.sceneTransform()) + except np.linalg.linalg.LinAlgError: + return None + + ## Modify transform to scale from image coords to data coords + #m = QtGui.QTransform() + tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height()) + #tr = tr * m + + ## Transform ROI bounds into data bounds + dataBounds = tr.mapRect(self.boundingRect()) + #print " boundingRect:", self.boundingRect() + #print " dataBounds:", dataBounds + + ## Intersect transformed ROI bounds with data bounds + intBounds = dataBounds.intersect(QtCore.QRectF(0, 0, dShape[0], dShape[1])) + #print " intBounds:", intBounds + + ## Determine index values to use when referencing the array. + bounds = ( + (int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))), + (int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top()))) + ) + #print " bounds:", bounds + + if returnSlice: + ## Create slice objects + sl = [slice(None)] * data.ndim + sl[axes[0]] = slice(*bounds[0]) + sl[axes[1]] = slice(*bounds[1]) + return tuple(sl), tr + else: + return bounds, tr + + def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds): + """Use the position and orientation of this ROI relative to an imageItem to pull a slice from an array. + + This method uses :func:`affineSlice ` to generate + the slice from *data* and uses :func:`getAffineSliceParams ` to determine the parameters to + pass to :func:`affineSlice `. + + If *returnMappedCoords* is True, then the method returns a tuple (result, coords) + such that coords is the set of coordinates used to interpolate values from the original + data, mapped into the parent coordinate system of the image. This is useful, when slicing + data from images that have been transformed, for determining the location of each value + in the sliced data. + + All extra keyword arguments are passed to :func:`affineSlice `. + """ + + shape, vectors, origin = self.getAffineSliceParams(data, img, axes) + if not returnMappedCoords: + return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) + else: + kwds['returnCoords'] = True + result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) + #tr = fn.transformToArray(img.transform())[:2] ## remove perspective transform values + + ### separate translation from scale/rotate + #translate = tr[:,2] + #tr = tr[:,:2] + #tr = tr.reshape((2,2) + (1,)*(coords.ndim-1)) + #coords = coords[np.newaxis, ...] + + ### map coordinates and return + #mapped = (tr*coords).sum(axis=0) ## apply scale/rotate + #mapped += translate.reshape((2,1,1)) + mapped = fn.transformCoordinates(img.transform(), coords) + return result, mapped + + + ### transpose data so x and y are the first 2 axes + #trAx = range(0, data.ndim) + #trAx.remove(axes[0]) + #trAx.remove(axes[1]) + #tr1 = tuple(axes) + tuple(trAx) + #arr = data.transpose(tr1) + + ### Determine the minimal area of the data we will need + #(dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes) + + ### Pad data boundaries by 1px if possible + #dataBounds = ( + #(max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])), + #(max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1])) + #) + + ### Extract minimal data from array + #arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]] + + ### Update roiDataTransform to reflect this extraction + #roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0]) + #### (roiDataTransform now maps from ROI coords to extracted data coords) + + + ### Rotate array + #if abs(self.state['angle']) > 1e-5: + #arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1) + + ### update data transforms to reflect this rotation + #rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi) + #roiDataTransform *= rot + + ### The rotation also causes a shift which must be accounted for: + #dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1]) + #rotBound = rot.mapRect(dataBound) + #roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top()) + + #else: + #arr2 = arr1 + + + + #### Shift off partial pixels + ## 1. map ROI into current data space + #roiBounds = roiDataTransform.mapRect(self.boundingRect()) + + ## 2. Determine amount to shift data + #shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom()) + #if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6: + ## 3. pad array with 0s before shifting + #arr2a = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype) + #arr2a[1:-1, 1:-1] = arr2 + + ## 4. shift array and udpate transforms + #arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1) + #roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1]) + #else: + #arr3 = arr2 + + + #### Extract needed region from rotated/shifted array + ## 1. map ROI into current data space (round these values off--they should be exact integer values at this point) + #roiBounds = roiDataTransform.mapRect(self.boundingRect()) + ##print self, roiBounds.height() + ##import traceback + ##traceback.print_stack() + + #roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height())) + + ##2. intersect ROI with data bounds + #dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1])) + + ##3. Extract data from array + #db = dataBounds + #bounds = ( + #(db.left(), db.right()+1), + #(db.top(), db.bottom()+1) + #) + #arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]] + + #### Create zero array in size of ROI + #arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype) + + ### Fill array with ROI data + #orig = Point(dataBounds.topLeft() - roiBounds.topLeft()) + #subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]] + #subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]] + + + ### figure out the reverse transpose order + #tr2 = np.array(tr1) + #for i in range(0, len(tr2)): + #tr2[tr1[i]] = i + #tr2 = tuple(tr2) + + ### Untranspose array before returning + #return arr5.transpose(tr2) + + def getAffineSliceParams(self, data, img, axes=(0,1)): + """ + Returns the parameters needed to use :func:`affineSlice ` to + extract a subset of *data* using this ROI and *img* to specify the subset. + + See :func:`getArrayRegion ` for more information. + """ + if self.scene() is not img.scene(): + raise Exception("ROI and target item must be members of the same scene.") + + shape = self.state['size'] + + origin = self.mapToItem(img, QtCore.QPointF(0, 0)) + + ## vx and vy point in the directions of the slice axes, but must be scaled properly + vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin + vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin + + lvx = np.sqrt(vx.x()**2 + vx.y()**2) + lvy = np.sqrt(vy.x()**2 + vy.y()**2) + pxLen = img.width() / float(data.shape[axes[0]]) + #img.width is number of pixels or width of item? + #need pxWidth and pxHeight instead of pxLen ? + sx = pxLen / lvx + sy = pxLen / lvy + + vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) + shape = self.state['size'] + shape = [abs(shape[0]/sx), abs(shape[1]/sy)] + + origin = (origin.x(), origin.y()) + return shape, vectors, origin + + def getGlobalTransform(self, relativeTo=None): + """Return global transformation (rotation angle+translation) required to move + from relative state to current state. If relative state isn't specified, + then we use the state of the ROI when mouse is pressed.""" + if relativeTo == None: + relativeTo = self.preMoveState + st = self.getState() + + ## this is only allowed because we will be comparing the two + relativeTo['scale'] = relativeTo['size'] + st['scale'] = st['size'] + + + + t1 = SRTTransform(relativeTo) + t2 = SRTTransform(st) + return t2/t1 + + + #st = self.getState() + + ### rotation + #ang = (st['angle']-relativeTo['angle']) * 180. / 3.14159265358 + #rot = QtGui.QTransform() + #rot.rotate(-ang) + + ### We need to come up with a universal transformation--one that can be applied to other objects + ### such that all maintain alignment. + ### More specifically, we need to turn the ROI's position and angle into + ### a rotation _around the origin_ and a translation. + + #p0 = Point(relativeTo['pos']) + + ### base position, rotated + #p1 = rot.map(p0) + + #trans = Point(st['pos']) - p1 + #return trans, ang + + def applyGlobalTransform(self, tr): + st = self.getState() + + st['scale'] = st['size'] + st = SRTTransform(st) + st = (st * tr).saveState() + st['size'] = st['scale'] + self.setState(st) + + +class Handle(UIGraphicsItem): + + types = { ## defines number of sides, start angle for each handle type + 't': (4, np.pi/4), + 'f': (4, np.pi/4), + 's': (4, 0), + 'r': (12, 0), + 'sr': (12, 0), + 'rf': (12, 0), + } + + sigClicked = QtCore.Signal(object, object) # self, event + sigRemoveRequested = QtCore.Signal(object) # self + + def __init__(self, radius, typ=None, pen=(200, 200, 220), parent=None, deletable=False): + #print " create item with parent", parent + #self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10) + #self.setFlags(self.ItemIgnoresTransformations | self.ItemSendsScenePositionChanges) + self.rois = [] + self.radius = radius + self.typ = typ + self.pen = fn.mkPen(pen) + self.currentPen = self.pen + self.pen.setWidth(0) + self.pen.setCosmetic(True) + self.isMoving = False + self.sides, self.startAng = self.types[typ] + self.buildPath() + self._shape = None + self.menu = self.buildMenu() + + UIGraphicsItem.__init__(self, parent=parent) + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + self.deletable = deletable + if deletable: + self.setAcceptedMouseButtons(QtCore.Qt.RightButton) + #self.updateShape() + self.setZValue(11) + + def connectROI(self, roi): + ### roi is the "parent" roi, i is the index of the handle in roi.handles + self.rois.append(roi) + + def disconnectROI(self, roi): + self.rois.remove(roi) + #for i, r in enumerate(self.roi): + #if r[0] == roi: + #self.roi.pop(i) + + #def close(self): + #for r in self.roi: + #r.removeHandle(self) + + def setDeletable(self, b): + self.deletable = b + if b: + self.setAcceptedMouseButtons(self.acceptedMouseButtons() | QtCore.Qt.RightButton) + else: + self.setAcceptedMouseButtons(self.acceptedMouseButtons() & ~QtCore.Qt.RightButton) + + def removeClicked(self): + self.sigRemoveRequested.emit(self) + + def hoverEvent(self, ev): + hover = False + if not ev.isExit(): + if ev.acceptDrags(QtCore.Qt.LeftButton): + hover=True + for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MidButton]: + if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn): + hover=True + + if hover: + self.currentPen = fn.mkPen(255, 255,0) + else: + self.currentPen = self.pen + self.update() + #if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + #self.currentPen = fn.mkPen(255, 255,0) + #else: + #self.currentPen = self.pen + #self.update() + + + + def mouseClickEvent(self, ev): + ## right-click cancels drag + if ev.button() == QtCore.Qt.RightButton and self.isMoving: + self.isMoving = False ## prevents any further motion + self.movePoint(self.startPos, finish=True) + #for r in self.roi: + #r[0].cancelMove() + ev.accept() + elif int(ev.button() & self.acceptedMouseButtons()) > 0: + ev.accept() + if ev.button() == QtCore.Qt.RightButton and self.deletable: + self.raiseContextMenu(ev) + self.sigClicked.emit(self, ev) + else: + ev.ignore() + + #elif self.deletable: + #ev.accept() + #self.raiseContextMenu(ev) + #else: + #ev.ignore() + + def buildMenu(self): + menu = QtGui.QMenu() + menu.setTitle("Handle") + self.removeAction = menu.addAction("Remove handle", self.removeClicked) + return menu + + def getMenu(self): + return self.menu + + + def getContextMenus(self, event): + return [self.menu] + + def raiseContextMenu(self, ev): + menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) + + ## Make sure it is still ok to remove this handle + removeAllowed = all([r.checkRemoveHandle(self) for r in self.rois]) + self.removeAction.setEnabled(removeAllowed) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def mouseDragEvent(self, ev): + if ev.button() != QtCore.Qt.LeftButton: + return + ev.accept() + + ## Inform ROIs that a drag is happening + ## note: the ROI is informed that the handle has moved using ROI.movePoint + ## this is for other (more nefarious) purposes. + #for r in self.roi: + #r[0].pointDragEvent(r[1], ev) + + if ev.isFinish(): + if self.isMoving: + for r in self.rois: + r.stateChangeFinished() + self.isMoving = False + elif ev.isStart(): + for r in self.rois: + r.handleMoveStarted() + self.isMoving = True + self.startPos = self.scenePos() + self.cursorOffset = self.scenePos() - ev.buttonDownScenePos() + + if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click. + pos = ev.scenePos() + self.cursorOffset + self.movePoint(pos, ev.modifiers(), finish=False) + + def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True): + for r in self.rois: + if not r.checkPointMove(self, pos, modifiers): + return + #print "point moved; inform %d ROIs" % len(self.roi) + # A handle can be used by multiple ROIs; tell each to update its handle position + for r in self.rois: + r.movePoint(self, pos, modifiers, finish=finish, coords='scene') + + def buildPath(self): + size = self.radius + self.path = QtGui.QPainterPath() + ang = self.startAng + dt = 2*np.pi / self.sides + for i in range(0, self.sides+1): + x = size * cos(ang) + y = size * sin(ang) + ang += dt + if i == 0: + self.path.moveTo(x, y) + else: + self.path.lineTo(x, y) + + def paint(self, p, opt, widget): + ### determine rotation of transform + #m = self.sceneTransform() + ##mi = m.inverted()[0] + #v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0)) + #va = np.arctan2(v.y(), v.x()) + + ### Determine length of unit vector in painter's coords + ##size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0)) + ##size = (size.x()*size.x() + size.y() * size.y()) ** 0.5 + #size = self.radius + + #bounds = QtCore.QRectF(-size, -size, size*2, size*2) + #if bounds != self.bounds: + #self.bounds = bounds + #self.prepareGeometryChange() + p.setRenderHints(p.Antialiasing, True) + p.setPen(self.currentPen) + + #p.rotate(va * 180. / 3.1415926) + #p.drawPath(self.path) + p.drawPath(self.shape()) + #ang = self.startAng + va + #dt = 2*np.pi / self.sides + #for i in range(0, self.sides): + #x1 = size * cos(ang) + #y1 = size * sin(ang) + #x2 = size * cos(ang+dt) + #y2 = size * sin(ang+dt) + #ang += dt + #p.drawLine(Point(x1, y1), Point(x2, y2)) + + def shape(self): + if self._shape is None: + s = self.generateShape() + if s is None: + return self.path + self._shape = s + self.prepareGeometryChange() ## beware--this can cause the view to adjust, which would immediately invalidate the shape. + return self._shape + + def boundingRect(self): + #print 'roi:', self.roi + s1 = self.shape() + #print " s1:", s1 + #s2 = self.shape() + #print " s2:", s2 + + return self.shape().boundingRect() + + def generateShape(self): + ## determine rotation of transform + #m = self.sceneTransform() ## Qt bug: do not access sceneTransform() until we know this object has a scene. + #mi = m.inverted()[0] + dt = self.deviceTransform() + + if dt is None: + self._shape = self.path + return None + + v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) + va = np.arctan2(v.y(), v.x()) + + dti = fn.invertQTransform(dt) + devPos = dt.map(QtCore.QPointF(0,0)) + tr = QtGui.QTransform() + tr.translate(devPos.x(), devPos.y()) + tr.rotate(va * 180. / 3.1415926) + + return dti.map(tr.map(self.path)) + + + def viewTransformChanged(self): + GraphicsObject.viewTransformChanged(self) + self._shape = None ## invalidate shape, recompute later if requested. + self.update() + + #def itemChange(self, change, value): + #if change == self.ItemScenePositionHasChanged: + #self.updateShape() + + +class TestROI(ROI): + def __init__(self, pos, size, **args): + #QtGui.QGraphicsRectItem.__init__(self, pos[0], pos[1], size[0], size[1]) + ROI.__init__(self, pos, size, **args) + #self.addTranslateHandle([0, 0]) + self.addTranslateHandle([0.5, 0.5]) + self.addScaleHandle([1, 1], [0, 0]) + self.addScaleHandle([0, 0], [1, 1]) + self.addScaleRotateHandle([1, 0.5], [0.5, 0.5]) + self.addScaleHandle([0.5, 1], [0.5, 0.5]) + self.addRotateHandle([1, 0], [0, 0]) + self.addRotateHandle([0, 1], [1, 1]) + + + +class RectROI(ROI): + def __init__(self, pos, size, centered=False, sideScalers=False, **args): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, pos, size, **args) + if centered: + center = [0.5, 0.5] + else: + center = [0, 0] + + #self.addTranslateHandle(center) + self.addScaleHandle([1, 1], center) + if sideScalers: + self.addScaleHandle([1, 0.5], [center[0], 0.5]) + self.addScaleHandle([0.5, 1], [0.5, center[1]]) + +class LineROI(ROI): + def __init__(self, pos1, pos2, width, **args): + pos1 = Point(pos1) + pos2 = Point(pos2) + d = pos2-pos1 + l = d.length() + ang = Point(1, 0).angle(d) + ra = ang * np.pi / 180. + c = Point(-width/2. * sin(ra), -width/2. * cos(ra)) + pos1 = pos1 + c + + ROI.__init__(self, pos1, size=Point(l, width), angle=ang, **args) + self.addScaleRotateHandle([0, 0.5], [1, 0.5]) + self.addScaleRotateHandle([1, 0.5], [0, 0.5]) + self.addScaleHandle([0.5, 1], [0.5, 0.5]) + + + +class MultiRectROI(QtGui.QGraphicsObject): + """ + Chain of rectangular ROIs connected by handles. + + This is generally used to mark a curved path through + an image similarly to PolyLineROI. It differs in that each segment + of the chain is rectangular instead of linear and thus has width. + """ + sigRegionChangeFinished = QtCore.Signal(object) + sigRegionChangeStarted = QtCore.Signal(object) + sigRegionChanged = QtCore.Signal(object) + + def __init__(self, points, width, pen=None, **args): + QtGui.QGraphicsObject.__init__(self) + self.pen = pen + self.roiArgs = args + self.lines = [] + if len(points) < 2: + raise Exception("Must start with at least 2 points") + + ## create first segment + self.addSegment(points[1], connectTo=points[0], scaleHandle=True) + + ## create remaining segments + for p in points[2:]: + self.addSegment(p) + + + def paint(self, *args): + pass + + def boundingRect(self): + return QtCore.QRectF() + + def roiChangedEvent(self): + w = self.lines[0].state['size'][1] + for l in self.lines[1:]: + w0 = l.state['size'][1] + if w == w0: + continue + l.scale([1.0, w/w0], center=[0.5,0.5]) + self.sigRegionChanged.emit(self) + + def roiChangeStartedEvent(self): + self.sigRegionChangeStarted.emit(self) + + def roiChangeFinishedEvent(self): + self.sigRegionChangeFinished.emit(self) + + def getHandlePositions(self): + """Return the positions of all handles in local coordinates.""" + pos = [self.mapFromScene(self.lines[0].getHandles()[0].scenePos())] + for l in self.lines: + pos.append(self.mapFromScene(l.getHandles()[1].scenePos())) + return pos + + def getArrayRegion(self, arr, img=None, axes=(0,1)): + rgns = [] + for l in self.lines: + rgn = l.getArrayRegion(arr, img, axes=axes) + if rgn is None: + continue + #return None + rgns.append(rgn) + #print l.state['size'] + + ## make sure orthogonal axis is the same size + ## (sometimes fp errors cause differences) + ms = min([r.shape[axes[1]] for r in rgns]) + sl = [slice(None)] * rgns[0].ndim + sl[axes[1]] = slice(0,ms) + rgns = [r[sl] for r in rgns] + #print [r.shape for r in rgns], axes + + return np.concatenate(rgns, axis=axes[0]) + + def addSegment(self, pos=(0,0), scaleHandle=False, connectTo=None): + """ + Add a new segment to the ROI connecting from the previous endpoint to *pos*. + (pos is specified in the parent coordinate system of the MultiRectROI) + """ + + ## by default, connect to the previous endpoint + if connectTo is None: + connectTo = self.lines[-1].getHandles()[1] + + ## create new ROI + newRoi = ROI((0,0), [1, 5], parent=self, pen=self.pen, **self.roiArgs) + self.lines.append(newRoi) + + ## Add first SR handle + if isinstance(connectTo, Handle): + self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=connectTo) + newRoi.movePoint(connectTo, connectTo.scenePos(), coords='scene') + else: + h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5]) + newRoi.movePoint(h, connectTo, coords='scene') + + ## add second SR handle + h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5]) + newRoi.movePoint(h, pos) + + ## optionally add scale handle (this MUST come after the two SR handles) + if scaleHandle: + newRoi.addScaleHandle([0.5, 1], [0.5, 0.5]) + + newRoi.translatable = False + newRoi.sigRegionChanged.connect(self.roiChangedEvent) + newRoi.sigRegionChangeStarted.connect(self.roiChangeStartedEvent) + newRoi.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent) + self.sigRegionChanged.emit(self) + + + def removeSegment(self, index=-1): + """Remove a segment from the ROI.""" + roi = self.lines[index] + self.lines.pop(index) + self.scene().removeItem(roi) + roi.sigRegionChanged.disconnect(self.roiChangedEvent) + roi.sigRegionChangeStarted.disconnect(self.roiChangeStartedEvent) + roi.sigRegionChangeFinished.disconnect(self.roiChangeFinishedEvent) + + self.sigRegionChanged.emit(self) + + +class MultiLineROI(MultiRectROI): + def __init__(self, *args, **kwds): + MultiRectROI.__init__(self, *args, **kwds) + print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)") + +class EllipseROI(ROI): + def __init__(self, pos, size, **args): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, pos, size, **args) + self.addRotateHandle([1.0, 0.5], [0.5, 0.5]) + self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) + + def paint(self, p, opt, widget): + r = self.boundingRect() + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + + p.scale(r.width(), r.height())## workaround for GL bug + r = QtCore.QRectF(r.x()/r.width(), r.y()/r.height(), 1,1) + + p.drawEllipse(r) + + def getArrayRegion(self, arr, img=None): + arr = ROI.getArrayRegion(self, arr, img) + if arr is None or arr.shape[0] == 0 or arr.shape[1] == 0: + return None + w = arr.shape[0] + h = arr.shape[1] + ## generate an ellipsoidal mask + mask = np.fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h)) + + return arr * mask + + def shape(self): + self.path = QtGui.QPainterPath() + self.path.addEllipse(self.boundingRect()) + return self.path + + +class CircleROI(EllipseROI): + def __init__(self, pos, size, **args): + ROI.__init__(self, pos, size, **args) + self.aspectLocked = True + #self.addTranslateHandle([0.5, 0.5]) + self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) + +class PolygonROI(ROI): + ## deprecated. Use PloyLineROI instead. + + def __init__(self, positions, pos=None, **args): + if pos is None: + pos = [0,0] + ROI.__init__(self, pos, [1,1], **args) + #ROI.__init__(self, positions[0]) + for p in positions: + self.addFreeHandle(p) + self.setZValue(1000) + print("Warning: PolygonROI is deprecated. Use PolyLineROI instead.") + + + def listPoints(self): + return [p['item'].pos() for p in self.handles] + + #def movePoint(self, *args, **kargs): + #ROI.movePoint(self, *args, **kargs) + #self.prepareGeometryChange() + #for h in self.handles: + #h['pos'] = h['item'].pos() + + def paint(self, p, *args): + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + for i in range(len(self.handles)): + h1 = self.handles[i]['item'].pos() + h2 = self.handles[i-1]['item'].pos() + p.drawLine(h1, h2) + + def boundingRect(self): + r = QtCore.QRectF() + for h in self.handles: + r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs + return r + + def shape(self): + p = QtGui.QPainterPath() + p.moveTo(self.handles[0]['item'].pos()) + for i in range(len(self.handles)): + p.lineTo(self.handles[i]['item'].pos()) + return p + + def stateCopy(self): + sc = {} + sc['pos'] = Point(self.state['pos']) + sc['size'] = Point(self.state['size']) + sc['angle'] = self.state['angle'] + #sc['handles'] = self.handles + return sc + +class PolyLineROI(ROI): + """Container class for multiple connected LineSegmentROIs. Responsible for adding new + line segments, and for translation/(rotation?) of multiple lines together.""" + def __init__(self, positions, closed=False, pos=None, **args): + + if pos is None: + pos = [0,0] + + ROI.__init__(self, pos, size=[1,1], **args) + self.closed = closed + self.segments = [] + + for p in positions: + self.addFreeHandle(p) + + start = -1 if self.closed else 0 + for i in range(start, len(self.handles)-1): + self.addSegment(self.handles[i]['item'], self.handles[i+1]['item']) + + def addSegment(self, h1, h2, index=None): + seg = LineSegmentROI(handles=(h1, h2), pen=self.pen, parent=self, movable=False) + if index is None: + self.segments.append(seg) + else: + self.segments.insert(index, seg) + seg.sigClicked.connect(self.segmentClicked) + seg.setAcceptedMouseButtons(QtCore.Qt.LeftButton) + seg.setZValue(self.zValue()+1) + for h in seg.handles: + h['item'].setDeletable(True) + h['item'].setAcceptedMouseButtons(h['item'].acceptedMouseButtons() | QtCore.Qt.LeftButton) ## have these handles take left clicks too, so that handles cannot be added on top of other handles + + def setMouseHover(self, hover): + ## Inform all the ROI's segments that the mouse is(not) hovering over it + ROI.setMouseHover(self, hover) + for s in self.segments: + s.setMouseHover(hover) + + def addHandle(self, info, index=None): + h = ROI.addHandle(self, info, index=index) + h.sigRemoveRequested.connect(self.removeHandle) + return h + + def segmentClicked(self, segment, ev=None, pos=None): ## pos should be in this item's coordinate system + if ev != None: + pos = segment.mapToParent(ev.pos()) + elif pos != None: + pos = pos + else: + raise Exception("Either an event or a position must be given.") + h1 = segment.handles[0]['item'] + h2 = segment.handles[1]['item'] + + i = self.segments.index(segment) + h3 = self.addFreeHandle(pos, index=self.indexOfHandle(h2)) + self.addSegment(h3, h2, index=i+1) + segment.replaceHandle(h2, h3) + + def removeHandle(self, handle, updateSegments=True): + ROI.removeHandle(self, handle) + handle.sigRemoveRequested.disconnect(self.removeHandle) + + if not updateSegments: + return + segments = handle.rois[:] + + if len(segments) == 1: + self.removeSegment(segments[0]) + else: + handles = [h['item'] for h in segments[1].handles] + handles.remove(handle) + segments[0].replaceHandle(handle, handles[0]) + self.removeSegment(segments[1]) + + def removeSegment(self, seg): + for handle in seg.handles[:]: + seg.removeHandle(handle['item']) + self.segments.remove(seg) + seg.sigClicked.disconnect(self.segmentClicked) + self.scene().removeItem(seg) + + def checkRemoveHandle(self, h): + ## called when a handle is about to display its context menu + if self.closed: + return len(self.handles) > 3 + else: + return len(self.handles) > 2 + + def paint(self, p, *args): + #for s in self.segments: + #s.update() + #p.setPen(self.currentPen) + #p.setPen(fn.mkPen('w')) + #p.drawRect(self.boundingRect()) + #p.drawPath(self.shape()) + pass + + def boundingRect(self): + return self.shape().boundingRect() + #r = QtCore.QRectF() + #for h in self.handles: + #r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs + #return r + + def shape(self): + p = QtGui.QPainterPath() + if len(self.handles) == 0: + return p + p.moveTo(self.handles[0]['item'].pos()) + for i in range(len(self.handles)): + p.lineTo(self.handles[i]['item'].pos()) + p.lineTo(self.handles[0]['item'].pos()) + return p + + def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds): + sl = self.getArraySlice(data, img, axes=(0,1)) + if sl is None: + return None + sliced = data[sl[0]] + im = QtGui.QImage(sliced.shape[axes[0]], sliced.shape[axes[1]], QtGui.QImage.Format_ARGB32) + im.fill(0x0) + p = QtGui.QPainter(im) + p.setPen(fn.mkPen(None)) + p.setBrush(fn.mkBrush('w')) + p.setTransform(self.itemTransform(img)[0]) + bounds = self.mapRectToItem(img, self.boundingRect()) + p.translate(-bounds.left(), -bounds.top()) + p.drawPath(self.shape()) + p.end() + mask = fn.imageToArray(im)[:,:,0].astype(float) / 255. + shape = [1] * data.ndim + shape[axes[0]] = sliced.shape[axes[0]] + shape[axes[1]] = sliced.shape[axes[1]] + return sliced * mask.reshape(shape) + + +class LineSegmentROI(ROI): + """ + ROI subclass with two freely-moving handles defining a line. + """ + + def __init__(self, positions=(None, None), pos=None, handles=(None,None), **args): + if pos is None: + pos = [0,0] + + ROI.__init__(self, pos, [1,1], **args) + #ROI.__init__(self, positions[0]) + if len(positions) > 2: + raise Exception("LineSegmentROI must be defined by exactly 2 positions. For more points, use PolyLineROI.") + + for i, p in enumerate(positions): + self.addFreeHandle(p, item=handles[i]) + + + def listPoints(self): + return [p['item'].pos() for p in self.handles] + + def paint(self, p, *args): + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + h1 = self.handles[0]['item'].pos() + h2 = self.handles[1]['item'].pos() + p.drawLine(h1, h2) + + def boundingRect(self): + return self.shape().boundingRect() + + def shape(self): + p = QtGui.QPainterPath() + + h1 = self.handles[0]['item'].pos() + h2 = self.handles[1]['item'].pos() + dh = h2-h1 + if dh.length() == 0: + return p + pxv = self.pixelVectors(dh)[1] + if pxv is None: + return p + + pxv *= 4 + + p.moveTo(h1+pxv) + p.lineTo(h2+pxv) + p.lineTo(h2-pxv) + p.lineTo(h1-pxv) + p.lineTo(h1+pxv) + + return p + + def getArrayRegion(self, data, img, axes=(0,1)): + """ + Use the position of this ROI relative to an imageItem to pull a slice from an array. + Since this pulls 1D data from a 2D coordinate system, the return value will have ndim = data.ndim-1 + """ + + imgPts = [self.mapToItem(img, h['item'].pos()) for h in self.handles] + rgns = [] + for i in range(len(imgPts)-1): + d = Point(imgPts[i+1] - imgPts[i]) + o = Point(imgPts[i]) + r = fn.affineSlice(data, shape=(int(d.length()),), vectors=[Point(d.norm())], origin=o, axes=axes, order=1) + rgns.append(r) + + return np.concatenate(rgns, axis=axes[0]) + + +class SpiralROI(ROI): + def __init__(self, pos=None, size=None, **args): + if size == None: + size = [100e-6,100e-6] + if pos == None: + pos = [0,0] + ROI.__init__(self, pos, size, **args) + self.translateSnap = False + self.addFreeHandle([0.25,0], name='a') + self.addRotateFreeHandle([1,0], [0,0], name='r') + #self.getRadius() + #QtCore.connect(self, QtCore.SIGNAL('regionChanged'), self. + + + def getRadius(self): + radius = Point(self.handles[1]['item'].pos()).length() + #r2 = radius[1] + #r3 = r2[0] + return radius + + def boundingRect(self): + r = self.getRadius() + return QtCore.QRectF(-r*1.1, -r*1.1, 2.2*r, 2.2*r) + #return self.bounds + + #def movePoint(self, *args, **kargs): + #ROI.movePoint(self, *args, **kargs) + #self.prepareGeometryChange() + #for h in self.handles: + #h['pos'] = h['item'].pos()/self.state['size'][0] + + def stateChanged(self, finish=True): + ROI.stateChanged(self, finish=finish) + if len(self.handles) > 1: + self.path = QtGui.QPainterPath() + h0 = Point(self.handles[0]['item'].pos()).length() + a = h0/(2.0*np.pi) + theta = 30.0*(2.0*np.pi)/360.0 + self.path.moveTo(QtCore.QPointF(a*theta*cos(theta), a*theta*sin(theta))) + x0 = a*theta*cos(theta) + y0 = a*theta*sin(theta) + radius = self.getRadius() + theta += 20.0*(2.0*np.pi)/360.0 + i = 0 + while Point(x0, y0).length() < radius and i < 1000: + x1 = a*theta*cos(theta) + y1 = a*theta*sin(theta) + self.path.lineTo(QtCore.QPointF(x1,y1)) + theta += 20.0*(2.0*np.pi)/360.0 + x0 = x1 + y0 = y1 + i += 1 + + + return self.path + + + def shape(self): + p = QtGui.QPainterPath() + p.addEllipse(self.boundingRect()) + return p + + def paint(self, p, *args): + p.setRenderHint(QtGui.QPainter.Antialiasing) + #path = self.shape() + p.setPen(self.currentPen) + p.drawPath(self.path) + p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) + p.drawPath(self.shape()) + p.setPen(QtGui.QPen(QtGui.QColor(0,0,255))) + p.drawRect(self.boundingRect()) + + + + + diff --git a/pyqtgraph/graphicsItems/ScaleBar.py b/pyqtgraph/graphicsItems/ScaleBar.py new file mode 100644 index 00000000..768f6978 --- /dev/null +++ b/pyqtgraph/graphicsItems/ScaleBar.py @@ -0,0 +1,104 @@ +from pyqtgraph.Qt import QtGui, QtCore +from .GraphicsObject import * +from .GraphicsWidgetAnchor import * +from .TextItem import TextItem +import numpy as np +import pyqtgraph.functions as fn +import pyqtgraph as pg + +__all__ = ['ScaleBar'] + +class ScaleBar(GraphicsObject, GraphicsWidgetAnchor): + """ + Displays a rectangular bar to indicate the relative scale of objects on the view. + """ + def __init__(self, size, width=5, brush=None, pen=None, suffix='m'): + GraphicsObject.__init__(self) + GraphicsWidgetAnchor.__init__(self) + self.setFlag(self.ItemHasNoContents) + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + + if brush is None: + brush = pg.getConfigOption('foreground') + self.brush = fn.mkBrush(brush) + self.pen = fn.mkPen(pen) + self._width = width + self.size = size + + self.bar = QtGui.QGraphicsRectItem() + self.bar.setPen(self.pen) + self.bar.setBrush(self.brush) + self.bar.setParentItem(self) + + self.text = TextItem(text=fn.siFormat(size, suffix=suffix), anchor=(0.5,1)) + self.text.setParentItem(self) + + def parentChanged(self): + view = self.parentItem() + if view is None: + return + view.sigRangeChanged.connect(self.updateBar) + self.updateBar() + + + def updateBar(self): + view = self.parentItem() + if view is None: + return + p1 = view.mapFromViewToItem(self, QtCore.QPointF(0,0)) + p2 = view.mapFromViewToItem(self, QtCore.QPointF(self.size,0)) + w = (p2-p1).x() + self.bar.setRect(QtCore.QRectF(-w, 0, w, self._width)) + self.text.setPos(-w/2., 0) + + def boundingRect(self): + return QtCore.QRectF() + + + + + +#class ScaleBar(UIGraphicsItem): + #""" + #Displays a rectangular bar with 10 divisions to indicate the relative scale of objects on the view. + #""" + #def __init__(self, size, width=5, color=(100, 100, 255)): + #UIGraphicsItem.__init__(self) + #self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + + #self.brush = fn.mkBrush(color) + #self.pen = fn.mkPen((0,0,0)) + #self._width = width + #self.size = size + + #def paint(self, p, opt, widget): + #UIGraphicsItem.paint(self, p, opt, widget) + + #rect = self.boundingRect() + #unit = self.pixelSize() + #y = rect.top() + (rect.bottom()-rect.top()) * 0.02 + #y1 = y + unit[1]*self._width + #x = rect.right() + (rect.left()-rect.right()) * 0.02 + #x1 = x - self.size + + #p.setPen(self.pen) + #p.setBrush(self.brush) + #rect = QtCore.QRectF( + #QtCore.QPointF(x1, y1), + #QtCore.QPointF(x, y) + #) + #p.translate(x1, y1) + #p.scale(rect.width(), rect.height()) + #p.drawRect(0, 0, 1, 1) + + #alpha = np.clip(((self.size/unit[0]) - 40.) * 255. / 80., 0, 255) + #p.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, alpha))) + #for i in range(1, 10): + ##x2 = x + (x1-x) * 0.1 * i + #x2 = 0.1 * i + #p.drawLine(QtCore.QPointF(x2, 0), QtCore.QPointF(x2, 1)) + + + #def setSize(self, s): + #self.size = s + diff --git a/pyqtgraph/graphicsItems/ScatterPlotItem.py b/pyqtgraph/graphicsItems/ScatterPlotItem.py new file mode 100644 index 00000000..f1a5201d --- /dev/null +++ b/pyqtgraph/graphicsItems/ScatterPlotItem.py @@ -0,0 +1,925 @@ +from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE +from pyqtgraph.Point import Point +import pyqtgraph.functions as fn +from .GraphicsItem import GraphicsItem +from .GraphicsObject import GraphicsObject +import numpy as np +import weakref +import pyqtgraph.debug as debug +from pyqtgraph.pgcollections import OrderedDict +import pyqtgraph as pg +#import pyqtgraph as pg + +__all__ = ['ScatterPlotItem', 'SpotItem'] + + +## Build all symbol paths +Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in ['o', 's', 't', 'd', '+', 'x']]) +Symbols['o'].addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) +Symbols['s'].addRect(QtCore.QRectF(-0.5, -0.5, 1, 1)) +coords = { + 't': [(-0.5, -0.5), (0, 0.5), (0.5, -0.5)], + 'd': [(0., -0.5), (-0.4, 0.), (0, 0.5), (0.4, 0)], + '+': [ + (-0.5, -0.05), (-0.5, 0.05), (-0.05, 0.05), (-0.05, 0.5), + (0.05, 0.5), (0.05, 0.05), (0.5, 0.05), (0.5, -0.05), + (0.05, -0.05), (0.05, -0.5), (-0.05, -0.5), (-0.05, -0.05) + ], +} +for k, c in coords.items(): + Symbols[k].moveTo(*c[0]) + for x,y in c[1:]: + Symbols[k].lineTo(x, y) + Symbols[k].closeSubpath() +tr = QtGui.QTransform() +tr.rotate(45) +Symbols['x'] = tr.map(Symbols['+']) + + +def drawSymbol(painter, symbol, size, pen, brush): + if symbol is None: + return + painter.scale(size, size) + painter.setPen(pen) + painter.setBrush(brush) + if isinstance(symbol, basestring): + symbol = Symbols[symbol] + if np.isscalar(symbol): + symbol = list(Symbols.values())[symbol % len(Symbols)] + painter.drawPath(symbol) + + +def renderSymbol(symbol, size, pen, brush, device=None): + """ + Render a symbol specification to QImage. + Symbol may be either a QPainterPath or one of the keys in the Symbols dict. + If *device* is None, a new QPixmap will be returned. Otherwise, + the symbol will be rendered into the device specified (See QPainter documentation + for more information). + """ + ## Render a spot with the given parameters to a pixmap + penPxWidth = max(np.ceil(pen.widthF()), 1) + if device is None: + device = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32) + device.fill(0) + p = QtGui.QPainter(device) + p.setRenderHint(p.Antialiasing) + p.translate(device.width()*0.5, device.height()*0.5) + drawSymbol(p, symbol, size, pen, brush) + p.end() + return device + +def makeSymbolPixmap(size, pen, brush, symbol): + ## deprecated + img = renderSymbol(symbol, size, pen, brush) + return QtGui.QPixmap(img) + +class SymbolAtlas(object): + """ + Used to efficiently construct a single QPixmap containing all rendered symbols + for a ScatterPlotItem. This is required for fragment rendering. + + Use example: + atlas = SymbolAtlas() + sc1 = atlas.getSymbolCoords('o', 5, QPen(..), QBrush(..)) + sc2 = atlas.getSymbolCoords('t', 10, QPen(..), QBrush(..)) + pm = atlas.getAtlas() + + """ + class SymbolCoords(list): ## needed because lists are not allowed in weak references. + pass + + def __init__(self): + # symbol key : [x, y, w, h] atlas coordinates + # note that the coordinate list will always be the same list object as + # long as the symbol is in the atlas, but the coordinates may + # change if the atlas is rebuilt. + # weak value; if all external refs to this list disappear, + # the symbol will be forgotten. + self.symbolMap = weakref.WeakValueDictionary() + + self.atlasData = None # numpy array of atlas image + self.atlas = None # atlas as QPixmap + self.atlasValid = False + + def getSymbolCoords(self, opts): + """ + Given a list of spot records, return an object representing the coordinates of that symbol within the atlas + """ + coords = np.empty(len(opts), dtype=object) + for i, rec in enumerate(opts): + symbol, size, pen, brush = rec['symbol'], rec['size'], rec['pen'], rec['brush'] + pen = fn.mkPen(pen) if not isinstance(pen, QtGui.QPen) else pen + brush = fn.mkBrush(brush) if not isinstance(pen, QtGui.QBrush) else brush + key = (symbol, size, fn.colorTuple(pen.color()), pen.widthF(), pen.style(), fn.colorTuple(brush.color())) + if key not in self.symbolMap: + newCoords = SymbolAtlas.SymbolCoords() + self.symbolMap[key] = newCoords + self.atlasValid = False + #try: + #self.addToAtlas(key) ## squeeze this into the atlas if there is room + #except: + #self.buildAtlas() ## otherwise, we need to rebuild + + coords[i] = self.symbolMap[key] + return coords + + def buildAtlas(self): + # get rendered array for all symbols, keep track of avg/max width + rendered = {} + avgWidth = 0.0 + maxWidth = 0 + images = [] + for key, coords in self.symbolMap.items(): + if len(coords) == 0: + pen = fn.mkPen(color=key[2], width=key[3], style=key[4]) + brush = fn.mkBrush(color=key[5]) + img = renderSymbol(key[0], key[1], pen, brush) + images.append(img) ## we only need this to prevent the images being garbage collected immediately + arr = fn.imageToArray(img, copy=False, transpose=False) + else: + (x,y,w,h) = self.symbolMap[key] + arr = self.atlasData[x:x+w, y:y+w] + rendered[key] = arr + w = arr.shape[0] + avgWidth += w + maxWidth = max(maxWidth, w) + + nSymbols = len(rendered) + if nSymbols > 0: + avgWidth /= nSymbols + width = max(maxWidth, avgWidth * (nSymbols**0.5)) + else: + avgWidth = 0 + width = 0 + + # sort symbols by height + symbols = sorted(rendered.keys(), key=lambda x: rendered[x].shape[1], reverse=True) + + self.atlasRows = [] + + x = width + y = 0 + rowheight = 0 + for key in symbols: + arr = rendered[key] + w,h = arr.shape[:2] + if x+w > width: + y += rowheight + x = 0 + rowheight = h + self.atlasRows.append([y, rowheight, 0]) + self.symbolMap[key][:] = x, y, w, h + x += w + self.atlasRows[-1][2] = x + height = y + rowheight + + self.atlasData = np.zeros((width, height, 4), dtype=np.ubyte) + for key in symbols: + x, y, w, h = self.symbolMap[key] + self.atlasData[x:x+w, y:y+h] = rendered[key] + self.atlas = None + self.atlasValid = True + + def getAtlas(self): + if not self.atlasValid: + self.buildAtlas() + if self.atlas is None: + if len(self.atlasData) == 0: + return QtGui.QPixmap(0,0) + img = fn.makeQImage(self.atlasData, copy=False, transpose=False) + self.atlas = QtGui.QPixmap(img) + return self.atlas + + + + +class ScatterPlotItem(GraphicsObject): + """ + Displays a set of x/y points. Instances of this class are created + automatically as part of PlotDataItem; these rarely need to be instantiated + directly. + + The size, shape, pen, and fill brush may be set for each point individually + or for all points. + + + ======================== =============================================== + **Signals:** + sigPlotChanged(self) Emitted when the data being plotted has changed + sigClicked(self, points) Emitted when the curve is clicked. Sends a list + of all the points under the mouse pointer. + ======================== =============================================== + + """ + #sigPointClicked = QtCore.Signal(object, object) + sigClicked = QtCore.Signal(object, object) ## self, points + sigPlotChanged = QtCore.Signal(object) + def __init__(self, *args, **kargs): + """ + Accepts the same arguments as setData() + """ + prof = debug.Profiler('ScatterPlotItem.__init__', disabled=True) + GraphicsObject.__init__(self) + + self.picture = None # QPicture used for rendering when pxmode==False + self.fragments = None # fragment specification for pxmode; updated every time the view changes. + self.fragmentAtlas = SymbolAtlas() + + self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('data', object), ('fragCoords', object), ('item', object)]) + self.bounds = [None, None] ## caches data bounds + self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots + self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots + self.opts = { + 'pxMode': True, + 'useCache': True, ## If useCache is False, symbols are re-drawn on every paint. + 'antialias': pg.getConfigOption('antialias'), + } + + self.setPen(200,200,200, update=False) + self.setBrush(100,100,150, update=False) + self.setSymbol('o', update=False) + self.setSize(7, update=False) + prof.mark('1') + self.setData(*args, **kargs) + prof.mark('setData') + prof.finish() + + #self.setCacheMode(self.DeviceCoordinateCache) + + def setData(self, *args, **kargs): + """ + **Ordered Arguments:** + + * If there is only one unnamed argument, it will be interpreted like the 'spots' argument. + * If there are two unnamed arguments, they will be interpreted as sequences of x and y values. + + ====================== =============================================================================================== + **Keyword Arguments:** + *spots* Optional list of dicts. Each dict specifies parameters for a single spot: + {'pos': (x,y), 'size', 'pen', 'brush', 'symbol'}. This is just an alternate method + of passing in data for the corresponding arguments. + *x*,*y* 1D arrays of x,y values. + *pos* 2D structure of x,y pairs (such as Nx2 array or list of tuples) + *pxMode* If True, spots are always the same size regardless of scaling, and size is given in px. + Otherwise, size is in scene coordinates and the spots scale with the view. + Default is True + *symbol* can be one (or a list) of: + * 'o' circle (default) + * 's' square + * 't' triangle + * 'd' diamond + * '+' plus + * any QPainterPath to specify custom symbol shapes. To properly obey the position and size, + custom symbols should be centered at (0,0) and width and height of 1.0. Note that it is also + possible to 'install' custom shapes by setting ScatterPlotItem.Symbols[key] = shape. + *pen* The pen (or list of pens) to use for drawing spot outlines. + *brush* The brush (or list of brushes) to use for filling spots. + *size* The size (or list of sizes) of spots. If *pxMode* is True, this value is in pixels. Otherwise, + it is in the item's local coordinate system. + *data* a list of python objects used to uniquely identify each spot. + *identical* *Deprecated*. This functionality is handled automatically now. + *antialias* Whether to draw symbols with antialiasing. Note that if pxMode is True, symbols are + always rendered with antialiasing (since the rendered symbols can be cached, this + incurs very little performance cost) + ====================== =============================================================================================== + """ + oldData = self.data ## this causes cached pixmaps to be preserved while new data is registered. + self.clear() ## clear out all old data + self.addPoints(*args, **kargs) + + def addPoints(self, *args, **kargs): + """ + Add new points to the scatter plot. + Arguments are the same as setData() + """ + + ## deal with non-keyword arguments + if len(args) == 1: + kargs['spots'] = args[0] + elif len(args) == 2: + kargs['x'] = args[0] + kargs['y'] = args[1] + elif len(args) > 2: + raise Exception('Only accepts up to two non-keyword arguments.') + + ## convert 'pos' argument to 'x' and 'y' + if 'pos' in kargs: + pos = kargs['pos'] + if isinstance(pos, np.ndarray): + kargs['x'] = pos[:,0] + kargs['y'] = pos[:,1] + else: + x = [] + y = [] + for p in pos: + if isinstance(p, QtCore.QPointF): + x.append(p.x()) + y.append(p.y()) + else: + x.append(p[0]) + y.append(p[1]) + kargs['x'] = x + kargs['y'] = y + + ## determine how many spots we have + if 'spots' in kargs: + numPts = len(kargs['spots']) + elif 'y' in kargs and kargs['y'] is not None: + numPts = len(kargs['y']) + else: + kargs['x'] = [] + kargs['y'] = [] + numPts = 0 + + ## Extend record array + oldData = self.data + self.data = np.empty(len(oldData)+numPts, dtype=self.data.dtype) + ## note that np.empty initializes object fields to None and string fields to '' + + self.data[:len(oldData)] = oldData + #for i in range(len(oldData)): + #oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array + + newData = self.data[len(oldData):] + newData['size'] = -1 ## indicates to use default size + + if 'spots' in kargs: + spots = kargs['spots'] + for i in range(len(spots)): + spot = spots[i] + for k in spot: + #if k == 'pen': + #newData[k] = fn.mkPen(spot[k]) + #elif k == 'brush': + #newData[k] = fn.mkBrush(spot[k]) + if k == 'pos': + pos = spot[k] + if isinstance(pos, QtCore.QPointF): + x,y = pos.x(), pos.y() + else: + x,y = pos[0], pos[1] + newData[i]['x'] = x + newData[i]['y'] = y + elif k in ['x', 'y', 'size', 'symbol', 'pen', 'brush', 'data']: + newData[i][k] = spot[k] + #elif k == 'data': + #self.pointData[i] = spot[k] + else: + raise Exception("Unknown spot parameter: %s" % k) + elif 'y' in kargs: + newData['x'] = kargs['x'] + newData['y'] = kargs['y'] + + if 'pxMode' in kargs: + self.setPxMode(kargs['pxMode']) + if 'antialias' in kargs: + self.opts['antialias'] = kargs['antialias'] + + ## Set any extra parameters provided in keyword arguments + for k in ['pen', 'brush', 'symbol', 'size']: + if k in kargs: + setMethod = getattr(self, 'set' + k[0].upper() + k[1:]) + setMethod(kargs[k], update=False, dataSet=newData, mask=kargs.get('mask', None)) + + if 'data' in kargs: + self.setPointData(kargs['data'], dataSet=newData) + + self.prepareGeometryChange() + self.bounds = [None, None] + self.invalidate() + self.updateSpots(newData) + self.sigPlotChanged.emit(self) + + def invalidate(self): + ## clear any cached drawing state + self.picture = None + self.fragments = None + self.update() + + def getData(self): + return self.data['x'], self.data['y'] + + + def setPoints(self, *args, **kargs): + ##Deprecated; use setData + return self.setData(*args, **kargs) + + def implements(self, interface=None): + ints = ['plotData'] + if interface is None: + return ints + return interface in ints + + def setPen(self, *args, **kargs): + """Set the pen(s) used to draw the outline around each spot. + If a list or array is provided, then the pen for each spot will be set separately. + Otherwise, the arguments are passed to pg.mkPen and used as the default pen for + all spots which do not have a pen explicitly set.""" + update = kargs.pop('update', True) + dataSet = kargs.pop('dataSet', self.data) + + if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)): + pens = args[0] + if kargs['mask'] is not None: + pens = pens[kargs['mask']] + if len(pens) != len(dataSet): + raise Exception("Number of pens does not match number of points (%d != %d)" % (len(pens), len(dataSet))) + dataSet['pen'] = pens + else: + self.opts['pen'] = fn.mkPen(*args, **kargs) + + dataSet['fragCoords'] = None + if update: + self.updateSpots(dataSet) + + def setBrush(self, *args, **kargs): + """Set the brush(es) used to fill the interior of each spot. + If a list or array is provided, then the brush for each spot will be set separately. + Otherwise, the arguments are passed to pg.mkBrush and used as the default brush for + all spots which do not have a brush explicitly set.""" + update = kargs.pop('update', True) + dataSet = kargs.pop('dataSet', self.data) + + if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)): + brushes = args[0] + if kargs['mask'] is not None: + brushes = brushes[kargs['mask']] + if len(brushes) != len(dataSet): + raise Exception("Number of brushes does not match number of points (%d != %d)" % (len(brushes), len(dataSet))) + #for i in xrange(len(brushes)): + #self.data[i]['brush'] = fn.mkBrush(brushes[i], **kargs) + dataSet['brush'] = brushes + else: + self.opts['brush'] = fn.mkBrush(*args, **kargs) + #self._spotPixmap = None + + dataSet['fragCoords'] = None + if update: + self.updateSpots(dataSet) + + def setSymbol(self, symbol, update=True, dataSet=None, mask=None): + """Set the symbol(s) used to draw each spot. + If a list or array is provided, then the symbol for each spot will be set separately. + Otherwise, the argument will be used as the default symbol for + all spots which do not have a symbol explicitly set.""" + if dataSet is None: + dataSet = self.data + + if isinstance(symbol, np.ndarray) or isinstance(symbol, list): + symbols = symbol + if mask is not None: + symbols = symbols[mask] + if len(symbols) != len(dataSet): + raise Exception("Number of symbols does not match number of points (%d != %d)" % (len(symbols), len(dataSet))) + dataSet['symbol'] = symbols + else: + self.opts['symbol'] = symbol + self._spotPixmap = None + + dataSet['fragCoords'] = None + if update: + self.updateSpots(dataSet) + + def setSize(self, size, update=True, dataSet=None, mask=None): + """Set the size(s) used to draw each spot. + If a list or array is provided, then the size for each spot will be set separately. + Otherwise, the argument will be used as the default size for + all spots which do not have a size explicitly set.""" + if dataSet is None: + dataSet = self.data + + if isinstance(size, np.ndarray) or isinstance(size, list): + sizes = size + if mask is not None: + sizes = sizes[mask] + if len(sizes) != len(dataSet): + raise Exception("Number of sizes does not match number of points (%d != %d)" % (len(sizes), len(dataSet))) + dataSet['size'] = sizes + else: + self.opts['size'] = size + self._spotPixmap = None + + dataSet['fragCoords'] = None + if update: + self.updateSpots(dataSet) + + def setPointData(self, data, dataSet=None, mask=None): + if dataSet is None: + dataSet = self.data + + if isinstance(data, np.ndarray) or isinstance(data, list): + if mask is not None: + data = data[mask] + if len(data) != len(dataSet): + raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(dataSet))) + + ## Bug: If data is a numpy record array, then items from that array must be copied to dataSet one at a time. + ## (otherwise they are converted to tuples and thus lose their field names. + if isinstance(data, np.ndarray) and (data.dtype.fields is not None)and len(data.dtype.fields) > 1: + for i, rec in enumerate(data): + dataSet['data'][i] = rec + else: + dataSet['data'] = data + + def setPxMode(self, mode): + if self.opts['pxMode'] == mode: + return + + self.opts['pxMode'] = mode + self.invalidate() + + def updateSpots(self, dataSet=None): + if dataSet is None: + dataSet = self.data + self._maxSpotWidth = 0 + self._maxSpotPxWidth = 0 + invalidate = False + self.measureSpotSizes(dataSet) + if self.opts['pxMode']: + mask = np.equal(dataSet['fragCoords'], None) + if np.any(mask): + invalidate = True + opts = self.getSpotOpts(dataSet[mask]) + coords = self.fragmentAtlas.getSymbolCoords(opts) + dataSet['fragCoords'][mask] = coords + + #for rec in dataSet: + #if rec['fragCoords'] is None: + #invalidate = True + #rec['fragCoords'] = self.fragmentAtlas.getSymbolCoords(*self.getSpotOpts(rec)) + if invalidate: + self.invalidate() + + def getSpotOpts(self, recs, scale=1.0): + if recs.ndim == 0: + rec = recs + symbol = rec['symbol'] + if symbol is None: + symbol = self.opts['symbol'] + size = rec['size'] + if size < 0: + size = self.opts['size'] + pen = rec['pen'] + if pen is None: + pen = self.opts['pen'] + brush = rec['brush'] + if brush is None: + brush = self.opts['brush'] + return (symbol, size*scale, fn.mkPen(pen), fn.mkBrush(brush)) + else: + recs = recs.copy() + recs['symbol'][np.equal(recs['symbol'], None)] = self.opts['symbol'] + recs['size'][np.equal(recs['size'], -1)] = self.opts['size'] + recs['size'] *= scale + recs['pen'][np.equal(recs['pen'], None)] = fn.mkPen(self.opts['pen']) + recs['brush'][np.equal(recs['brush'], None)] = fn.mkBrush(self.opts['brush']) + return recs + + + + def measureSpotSizes(self, dataSet): + for rec in dataSet: + ## keep track of the maximum spot size and pixel size + symbol, size, pen, brush = self.getSpotOpts(rec) + width = 0 + pxWidth = 0 + if self.opts['pxMode']: + pxWidth = size + pen.widthF() + else: + width = size + if pen.isCosmetic(): + pxWidth += pen.widthF() + else: + width += pen.widthF() + self._maxSpotWidth = max(self._maxSpotWidth, width) + self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth) + self.bounds = [None, None] + + + def clear(self): + """Remove all spots from the scatter plot""" + #self.clearItems() + self.data = np.empty(0, dtype=self.data.dtype) + self.bounds = [None, None] + self.invalidate() + + def dataBounds(self, ax, frac=1.0, orthoRange=None): + if frac >= 1.0 and orthoRange is None and self.bounds[ax] is not None: + return self.bounds[ax] + + #self.prepareGeometryChange() + if self.data is None or len(self.data) == 0: + return (None, None) + + if ax == 0: + d = self.data['x'] + d2 = self.data['y'] + elif ax == 1: + d = self.data['y'] + d2 = self.data['x'] + + if orthoRange is not None: + mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) + d = d[mask] + d2 = d2[mask] + + if frac >= 1.0: + self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072) + return self.bounds[ax] + elif frac <= 0.0: + raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) + else: + mask = np.isfinite(d) + d = d[mask] + return np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) + + def pixelPadding(self): + return self._maxSpotPxWidth*0.7072 + + def boundingRect(self): + (xmn, xmx) = self.dataBounds(ax=0) + (ymn, ymx) = self.dataBounds(ax=1) + if xmn is None or xmx is None: + xmn = 0 + xmx = 0 + if ymn is None or ymx is None: + ymn = 0 + ymx = 0 + + px = py = 0.0 + pxPad = self.pixelPadding() + if pxPad > 0: + # determine length of pixel in local x, y directions + px, py = self.pixelVectors() + px = 0 if px is None else px.length() + py = 0 if py is None else py.length() + + # return bounds expanded by pixel size + px *= pxPad + py *= pxPad + return QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) + + def viewTransformChanged(self): + self.prepareGeometryChange() + GraphicsObject.viewTransformChanged(self) + self.bounds = [None, None] + self.fragments = None + + def generateFragments(self): + tr = self.deviceTransform() + if tr is None: + return + pts = np.empty((2,len(self.data['x']))) + pts[0] = self.data['x'] + pts[1] = self.data['y'] + pts = fn.transformCoordinates(tr, pts) + self.fragments = [] + pts = np.clip(pts, -2**30, 2**30) ## prevent Qt segmentation fault. + ## Still won't be able to render correctly, though. + for i in xrange(len(self.data)): + rec = self.data[i] + pos = QtCore.QPointF(pts[0,i], pts[1,i]) + x,y,w,h = rec['fragCoords'] + rect = QtCore.QRectF(y, x, h, w) + self.fragments.append(QtGui.QPainter.PixmapFragment.create(pos, rect)) + + def setExportMode(self, *args, **kwds): + GraphicsObject.setExportMode(self, *args, **kwds) + self.invalidate() + + @pg.debug.warnOnException ## raising an exception here causes crash + def paint(self, p, *args): + + #p.setPen(fn.mkPen('r')) + #p.drawRect(self.boundingRect()) + + if self._exportOpts is not False: + aa = self._exportOpts.get('antialias', True) + scale = self._exportOpts.get('resolutionScale', 1.0) ## exporting to image; pixel resolution may have changed + else: + aa = self.opts['antialias'] + scale = 1.0 + + if self.opts['pxMode'] is True: + atlas = self.fragmentAtlas.getAtlas() + #arr = fn.imageToArray(atlas.toImage(), copy=True) + #if hasattr(self, 'lastAtlas'): + #if np.any(self.lastAtlas != arr): + #print "Atlas changed:", arr + #self.lastAtlas = arr + + if self.fragments is None: + self.updateSpots() + self.generateFragments() + + p.resetTransform() + + if not USE_PYSIDE and self.opts['useCache'] and self._exportOpts is False: + p.drawPixmapFragments(self.fragments, atlas) + else: + p.setRenderHint(p.Antialiasing, aa) + + for i in range(len(self.data)): + rec = self.data[i] + frag = self.fragments[i] + p.resetTransform() + p.translate(frag.x, frag.y) + drawSymbol(p, *self.getSpotOpts(rec, scale)) + else: + if self.picture is None: + self.picture = QtGui.QPicture() + p2 = QtGui.QPainter(self.picture) + for rec in self.data: + if scale != 1.0: + rec = rec.copy() + rec['size'] *= scale + p2.resetTransform() + p2.translate(rec['x'], rec['y']) + drawSymbol(p2, *self.getSpotOpts(rec, scale)) + p2.end() + + p.setRenderHint(p.Antialiasing, aa) + self.picture.play(p) + + def points(self): + for rec in self.data: + if rec['item'] is None: + rec['item'] = SpotItem(rec, self) + return self.data['item'] + + def pointsAt(self, pos): + x = pos.x() + y = pos.y() + pw = self.pixelWidth() + ph = self.pixelHeight() + pts = [] + for s in self.points(): + sp = s.pos() + ss = s.size() + sx = sp.x() + sy = sp.y() + s2x = s2y = ss * 0.5 + if self.opts['pxMode']: + s2x *= pw + s2y *= ph + if x > sx-s2x and x < sx+s2x and y > sy-s2y and y < sy+s2y: + pts.append(s) + #print "HIT:", x, y, sx, sy, s2x, s2y + #else: + #print "No hit:", (x, y), (sx, sy) + #print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y) + #pts.sort(lambda a,b: cmp(b.zValue(), a.zValue())) + return pts[::-1] + + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + pts = self.pointsAt(ev.pos()) + if len(pts) > 0: + self.ptsClicked = pts + self.sigClicked.emit(self, self.ptsClicked) + ev.accept() + else: + #print "no spots" + ev.ignore() + else: + ev.ignore() + + +class SpotItem(object): + """ + Class referring to individual spots in a scatter plot. + These can be retrieved by calling ScatterPlotItem.points() or + by connecting to the ScatterPlotItem's click signals. + """ + + def __init__(self, data, plot): + #GraphicsItem.__init__(self, register=False) + self._data = data + self._plot = plot + #self.setParentItem(plot) + #self.setPos(QtCore.QPointF(data['x'], data['y'])) + #self.updateItem() + + def data(self): + """Return the user data associated with this spot.""" + return self._data['data'] + + def size(self): + """Return the size of this spot. + If the spot has no explicit size set, then return the ScatterPlotItem's default size instead.""" + if self._data['size'] == -1: + return self._plot.opts['size'] + else: + return self._data['size'] + + def pos(self): + return Point(self._data['x'], self._data['y']) + + def viewPos(self): + return self._plot.mapToView(self.pos()) + + def setSize(self, size): + """Set the size of this spot. + If the size is set to -1, then the ScatterPlotItem's default size + will be used instead.""" + self._data['size'] = size + self.updateItem() + + def symbol(self): + """Return the symbol of this spot. + If the spot has no explicit symbol set, then return the ScatterPlotItem's default symbol instead. + """ + symbol = self._data['symbol'] + if symbol is None: + symbol = self._plot.opts['symbol'] + try: + n = int(symbol) + symbol = list(Symbols.keys())[n % len(Symbols)] + except: + pass + return symbol + + def setSymbol(self, symbol): + """Set the symbol for this spot. + If the symbol is set to '', then the ScatterPlotItem's default symbol will be used instead.""" + self._data['symbol'] = symbol + self.updateItem() + + def pen(self): + pen = self._data['pen'] + if pen is None: + pen = self._plot.opts['pen'] + return fn.mkPen(pen) + + def setPen(self, *args, **kargs): + """Set the outline pen for this spot""" + pen = fn.mkPen(*args, **kargs) + self._data['pen'] = pen + self.updateItem() + + def resetPen(self): + """Remove the pen set for this spot; the scatter plot's default pen will be used instead.""" + self._data['pen'] = None ## Note this is NOT the same as calling setPen(None) + self.updateItem() + + def brush(self): + brush = self._data['brush'] + if brush is None: + brush = self._plot.opts['brush'] + return fn.mkBrush(brush) + + def setBrush(self, *args, **kargs): + """Set the fill brush for this spot""" + brush = fn.mkBrush(*args, **kargs) + self._data['brush'] = brush + self.updateItem() + + def resetBrush(self): + """Remove the brush set for this spot; the scatter plot's default brush will be used instead.""" + self._data['brush'] = None ## Note this is NOT the same as calling setBrush(None) + self.updateItem() + + def setData(self, data): + """Set the user-data associated with this spot""" + self._data['data'] = data + + def updateItem(self): + self._data['fragCoords'] = None + self._plot.updateSpots(self._data.reshape(1)) + self._plot.invalidate() + +#class PixmapSpotItem(SpotItem, QtGui.QGraphicsPixmapItem): + #def __init__(self, data, plot): + #QtGui.QGraphicsPixmapItem.__init__(self) + #self.setFlags(self.flags() | self.ItemIgnoresTransformations) + #SpotItem.__init__(self, data, plot) + + #def setPixmap(self, pixmap): + #QtGui.QGraphicsPixmapItem.setPixmap(self, pixmap) + #self.setOffset(-pixmap.width()/2.+0.5, -pixmap.height()/2.) + + #def updateItem(self): + #symbolOpts = (self._data['pen'], self._data['brush'], self._data['size'], self._data['symbol']) + + ### If all symbol options are default, use default pixmap + #if symbolOpts == (None, None, -1, ''): + #pixmap = self._plot.defaultSpotPixmap() + #else: + #pixmap = makeSymbolPixmap(size=self.size(), pen=self.pen(), brush=self.brush(), symbol=self.symbol()) + #self.setPixmap(pixmap) + + +#class PathSpotItem(SpotItem, QtGui.QGraphicsPathItem): + #def __init__(self, data, plot): + #QtGui.QGraphicsPathItem.__init__(self) + #SpotItem.__init__(self, data, plot) + + #def updateItem(self): + #QtGui.QGraphicsPathItem.setPath(self, Symbols[self.symbol()]) + #QtGui.QGraphicsPathItem.setPen(self, self.pen()) + #QtGui.QGraphicsPathItem.setBrush(self, self.brush()) + #size = self.size() + #self.resetTransform() + #self.scale(size, size) diff --git a/pyqtgraph/graphicsItems/TextItem.py b/pyqtgraph/graphicsItems/TextItem.py new file mode 100644 index 00000000..911057f4 --- /dev/null +++ b/pyqtgraph/graphicsItems/TextItem.py @@ -0,0 +1,124 @@ +from pyqtgraph.Qt import QtCore, QtGui +import pyqtgraph as pg +from .UIGraphicsItem import * +import pyqtgraph.functions as fn + +class TextItem(UIGraphicsItem): + """ + GraphicsItem displaying unscaled text (the text will always appear normal even inside a scaled ViewBox). + """ + def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0), border=None, fill=None, angle=0): + """ + =========== ================================================================================= + Arguments: + *text* The text to display + *color* The color of the text (any format accepted by pg.mkColor) + *html* If specified, this overrides both *text* and *color* + *anchor* A QPointF or (x,y) sequence indicating what region of the text box will + be anchored to the item's position. A value of (0,0) sets the upper-left corner + of the text box to be at the position specified by setPos(), while a value of (1,1) + sets the lower-right corner. + *border* A pen to use when drawing the border + *fill* A brush to use when filling within the border + =========== ================================================================================= + """ + + ## not working yet + #*angle* Angle in degrees to rotate text (note that the rotation assigned in this item's + #transformation will be ignored) + + self.anchor = pg.Point(anchor) + #self.angle = 0 + UIGraphicsItem.__init__(self) + self.textItem = QtGui.QGraphicsTextItem() + self.textItem.setParentItem(self) + self.lastTransform = None + self._bounds = QtCore.QRectF() + if html is None: + self.setText(text, color) + else: + self.setHtml(html) + self.fill = pg.mkBrush(fill) + self.border = pg.mkPen(border) + self.rotate(angle) + self.setFlag(self.ItemIgnoresTransformations) ## This is required to keep the text unscaled inside the viewport + + def setText(self, text, color=(200,200,200)): + color = pg.mkColor(color) + self.textItem.setDefaultTextColor(color) + self.textItem.setPlainText(text) + self.updateText() + #html = '%s' % (color, text) + #self.setHtml(html) + + def updateAnchor(self): + pass + #self.resetTransform() + #self.translate(0, 20) + + def setPlainText(self, *args): + self.textItem.setPlainText(*args) + self.updateText() + + def setHtml(self, *args): + self.textItem.setHtml(*args) + self.updateText() + + def setTextWidth(self, *args): + self.textItem.setTextWidth(*args) + self.updateText() + + def setFont(self, *args): + self.textItem.setFont(*args) + self.updateText() + + #def setAngle(self, angle): + #self.angle = angle + #self.updateText() + + + def updateText(self): + + ## Needed to maintain font size when rendering to image with increased resolution + self.textItem.resetTransform() + #self.textItem.rotate(self.angle) + if self._exportOpts is not False and 'resolutionScale' in self._exportOpts: + s = self._exportOpts['resolutionScale'] + self.textItem.scale(s, s) + + #br = self.textItem.mapRectToParent(self.textItem.boundingRect()) + self.textItem.setPos(0,0) + br = self.textItem.boundingRect() + apos = self.textItem.mapToParent(pg.Point(br.width()*self.anchor.x(), br.height()*self.anchor.y())) + #print br, apos + self.textItem.setPos(-apos.x(), -apos.y()) + + #def textBoundingRect(self): + ### return the bounds of the text box in device coordinates + #pos = self.mapToDevice(QtCore.QPointF(0,0)) + #if pos is None: + #return None + #tbr = self.textItem.boundingRect() + #return QtCore.QRectF(pos.x() - tbr.width()*self.anchor.x(), pos.y() - tbr.height()*self.anchor.y(), tbr.width(), tbr.height()) + + + def viewRangeChanged(self): + self.updateText() + + def boundingRect(self): + return self.textItem.mapToParent(self.textItem.boundingRect()).boundingRect() + + def paint(self, p, *args): + tr = p.transform() + if self.lastTransform is not None: + if tr != self.lastTransform: + self.viewRangeChanged() + self.lastTransform = tr + + if self.border.style() != QtCore.Qt.NoPen or self.fill.style() != QtCore.Qt.NoBrush: + p.setPen(self.border) + p.setBrush(self.fill) + p.setRenderHint(p.Antialiasing, True) + p.drawPolygon(self.textItem.mapToParent(self.textItem.boundingRect())) + + \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/UIGraphicsItem.py b/pyqtgraph/graphicsItems/UIGraphicsItem.py new file mode 100644 index 00000000..19fda424 --- /dev/null +++ b/pyqtgraph/graphicsItems/UIGraphicsItem.py @@ -0,0 +1,124 @@ +from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE +import weakref +from .GraphicsObject import GraphicsObject +if not USE_PYSIDE: + import sip + +__all__ = ['UIGraphicsItem'] +class UIGraphicsItem(GraphicsObject): + """ + Base class for graphics items with boundaries relative to a GraphicsView or ViewBox. + The purpose of this class is to allow the creation of GraphicsItems which live inside + a scalable view, but whose boundaries will always stay fixed relative to the view's boundaries. + For example: GridItem, InfiniteLine + + The view can be specified on initialization or it can be automatically detected when the item is painted. + + NOTE: Only the item's boundingRect is affected; the item is not transformed in any way. Use viewRangeChanged + to respond to changes in the view. + """ + + #sigViewChanged = QtCore.Signal(object) ## emitted whenever the viewport coords have changed + + def __init__(self, bounds=None, parent=None): + """ + ============== ============================================================================= + **Arguments:** + bounds QRectF with coordinates relative to view box. The default is QRectF(0,0,1,1), + which means the item will have the same bounds as the view. + ============== ============================================================================= + """ + GraphicsObject.__init__(self, parent) + self.setFlag(self.ItemSendsScenePositionChanges) + + if bounds is None: + self._bounds = QtCore.QRectF(0, 0, 1, 1) + else: + self._bounds = bounds + + self._boundingRect = None + self._updateView() + + def paint(self, *args): + ## check for a new view object every time we paint. + #self.updateView() + pass + + def itemChange(self, change, value): + ret = GraphicsObject.itemChange(self, change, value) + + ## workaround for pyqt bug: + ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html + if not USE_PYSIDE and change == self.ItemParentChange and isinstance(ret, QtGui.QGraphicsItem): + ret = sip.cast(ret, QtGui.QGraphicsItem) + + if change == self.ItemScenePositionHasChanged: + self.setNewBounds() + return ret + + #def updateView(self): + ### called to see whether this item has a new view to connect to + + ### check for this item's current viewbox or view widget + #view = self.getViewBox() + #if view is None: + ##print " no view" + #return + + #if self._connectedView is not None and view is self._connectedView(): + ##print " already have view", view + #return + + ### disconnect from previous view + #if self._connectedView is not None: + #cv = self._connectedView() + #if cv is not None: + ##print "disconnect:", self + #cv.sigRangeChanged.disconnect(self.viewRangeChanged) + + ### connect to new view + ##print "connect:", self + #view.sigRangeChanged.connect(self.viewRangeChanged) + #self._connectedView = weakref.ref(view) + #self.setNewBounds() + + def boundingRect(self): + if self._boundingRect is None: + br = self.viewRect() + if br is None: + return QtCore.QRectF() + else: + self._boundingRect = br + return QtCore.QRectF(self._boundingRect) + + def dataBounds(self, axis, frac=1.0, orthoRange=None): + """Called by ViewBox for determining the auto-range bounds. + By default, UIGraphicsItems are excluded from autoRange.""" + return None + + def viewRangeChanged(self): + """Called when the view widget/viewbox is resized/rescaled""" + self.setNewBounds() + self.update() + + def setNewBounds(self): + """Update the item's bounding rect to match the viewport""" + self._boundingRect = None ## invalidate bounding rect, regenerate later if needed. + self.prepareGeometryChange() + + + def setPos(self, *args): + GraphicsObject.setPos(self, *args) + self.setNewBounds() + + def mouseShape(self): + """Return the shape of this item after expanding by 2 pixels""" + shape = self.shape() + ds = self.mapToDevice(shape) + stroker = QtGui.QPainterPathStroker() + stroker.setWidh(2) + ds2 = stroker.createStroke(ds).united(ds) + return self.mapFromDevice(ds2) + + + diff --git a/pyqtgraph/graphicsItems/VTickGroup.py b/pyqtgraph/graphicsItems/VTickGroup.py new file mode 100644 index 00000000..c6880f91 --- /dev/null +++ b/pyqtgraph/graphicsItems/VTickGroup.py @@ -0,0 +1,113 @@ +if __name__ == '__main__': + import os, sys + path = os.path.abspath(os.path.dirname(__file__)) + sys.path.insert(0, os.path.join(path, '..', '..')) + +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as fn +import weakref +from .UIGraphicsItem import UIGraphicsItem + +__all__ = ['VTickGroup'] +class VTickGroup(UIGraphicsItem): + """ + **Bases:** :class:`UIGraphicsItem ` + + Draws a set of tick marks which always occupy the same vertical range of the view, + but have x coordinates relative to the data within the view. + + """ + def __init__(self, xvals=None, yrange=None, pen=None): + """ + ============= =================================================================== + **Arguments** + xvals A list of x values (in data coordinates) at which to draw ticks. + yrange A list of [low, high] limits for the tick. 0 is the bottom of + the view, 1 is the top. [0.8, 1] would draw ticks in the top + fifth of the view. + pen The pen to use for drawing ticks. Default is grey. Can be specified + as any argument valid for :func:`mkPen` + ============= =================================================================== + """ + if yrange is None: + yrange = [0, 1] + if xvals is None: + xvals = [] + + UIGraphicsItem.__init__(self) + + if pen is None: + pen = (200, 200, 200) + + self.path = QtGui.QGraphicsPathItem() + + self.ticks = [] + self.xvals = [] + self.yrange = [0,1] + self.setPen(pen) + self.setYRange(yrange) + self.setXVals(xvals) + + def setPen(self, *args, **kwargs): + """Set the pen to use for drawing ticks. Can be specified as any arguments valid + for :func:`mkPen`""" + self.pen = fn.mkPen(*args, **kwargs) + + def setXVals(self, vals): + """Set the x values for the ticks. + + ============= ===================================================================== + **Arguments** + vals A list of x values (in data/plot coordinates) at which to draw ticks. + ============= ===================================================================== + """ + self.xvals = vals + self.rebuildTicks() + #self.valid = False + + def setYRange(self, vals): + """Set the y range [low, high] that the ticks are drawn on. 0 is the bottom of + the view, 1 is the top.""" + self.yrange = vals + self.rebuildTicks() + + def dataBounds(self, *args, **kargs): + return None ## item should never affect view autoscaling + + def yRange(self): + return self.yrange + + def rebuildTicks(self): + self.path = QtGui.QPainterPath() + yrange = self.yRange() + for x in self.xvals: + self.path.moveTo(x, 0.) + self.path.lineTo(x, 1.) + + def paint(self, p, *args): + UIGraphicsItem.paint(self, p, *args) + + br = self.boundingRect() + h = br.height() + br.setY(br.y() + self.yrange[0] * h) + br.setHeight(h - (1.0-self.yrange[1]) * h) + p.translate(0, br.y()) + p.scale(1.0, br.height()) + p.setPen(self.pen) + p.drawPath(self.path) + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + import pyqtgraph as pg + vt = VTickGroup([1,3,4,7,9], [0.8, 1.0]) + p = pg.plot() + p.addItem(vt) + + if sys.flags.interactive == 0: + app.exec_() + + + + + \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py new file mode 100644 index 00000000..3cbb1ea2 --- /dev/null +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -0,0 +1,1570 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.python2_3 import sortList +import numpy as np +from pyqtgraph.Point import Point +import pyqtgraph.functions as fn +from .. ItemGroup import ItemGroup +from .. GraphicsWidget import GraphicsWidget +from pyqtgraph.GraphicsScene import GraphicsScene +import pyqtgraph +import weakref +from copy import deepcopy +import pyqtgraph.debug as debug + +__all__ = ['ViewBox'] + + +class ChildGroup(ItemGroup): + + sigItemsChanged = QtCore.Signal() + def __init__(self, parent): + ItemGroup.__init__(self, parent) + # excempt from telling view when transform changes + self._GraphicsObject__inform_view_on_change = False + + def itemChange(self, change, value): + ret = ItemGroup.itemChange(self, change, value) + if change == self.ItemChildAddedChange or change == self.ItemChildRemovedChange: + self.sigItemsChanged.emit() + + return ret + + +class ViewBox(GraphicsWidget): + """ + **Bases:** :class:`GraphicsWidget ` + + Box that allows internal scaling/panning of children by mouse drag. + This class is usually created automatically as part of a :class:`PlotItem ` or :class:`Canvas ` or with :func:`GraphicsLayout.addViewBox() `. + + Features: + + - Scaling contents by mouse or auto-scale when contents change + - View linking--multiple views display the same data ranges + - Configurable by context menu + - Item coordinate mapping methods + + Not really compatible with GraphicsView having the same functionality. + """ + + sigYRangeChanged = QtCore.Signal(object, object) + sigXRangeChanged = QtCore.Signal(object, object) + sigRangeChangedManually = QtCore.Signal(object) + sigRangeChanged = QtCore.Signal(object, object) + #sigActionPositionChanged = QtCore.Signal(object) + sigStateChanged = QtCore.Signal(object) + sigTransformChanged = QtCore.Signal(object) + sigResized = QtCore.Signal(object) + + ## mouse modes + PanMode = 3 + RectMode = 1 + + ## axes + XAxis = 0 + YAxis = 1 + XYAxes = 2 + + ## for linking views together + NamedViews = weakref.WeakValueDictionary() # name: ViewBox + AllViews = weakref.WeakKeyDictionary() # ViewBox: None + + def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu=True, name=None): + """ + ============= ============================================================= + **Arguments** + *parent* (QGraphicsWidget) Optional parent widget + *border* (QPen) Do draw a border around the view, give any + single argument accepted by :func:`mkPen ` + *lockAspect* (False or float) The aspect ratio to lock the view + coorinates to. (or False to allow the ratio to change) + *enableMouse* (bool) Whether mouse can be used to scale/pan the view + *invertY* (bool) See :func:`invertY ` + ============= ============================================================= + """ + + + + GraphicsWidget.__init__(self, parent) + self.name = None + self.linksBlocked = False + self.addedItems = [] + #self.gView = view + #self.showGrid = showGrid + self._matrixNeedsUpdate = True ## indicates that range has changed, but matrix update was deferred + self._autoRangeNeedsUpdate = True ## indicates auto-range needs to be recomputed. + + self._lastScene = None ## stores reference to the last known scene this view was a part of. + + self.state = { + + ## separating targetRange and viewRange allows the view to be resized + ## while keeping all previously viewed contents visible + 'targetRange': [[0,1], [0,1]], ## child coord. range visible [[xmin, xmax], [ymin, ymax]] + 'viewRange': [[0,1], [0,1]], ## actual range viewed + + 'yInverted': invertY, + 'aspectLocked': False, ## False if aspect is unlocked, otherwise float specifies the locked ratio. + 'autoRange': [True, True], ## False if auto range is disabled, + ## otherwise float gives the fraction of data that is visible + 'autoPan': [False, False], ## whether to only pan (do not change scaling) when auto-range is enabled + 'autoVisibleOnly': [False, False], ## whether to auto-range only to the visible portion of a plot + 'linkedViews': [None, None], ## may be None, "viewName", or weakref.ref(view) + ## a name string indicates that the view *should* link to another, but no view with that name exists yet. + + 'mouseEnabled': [enableMouse, enableMouse], + 'mouseMode': ViewBox.PanMode if pyqtgraph.getConfigOption('leftButtonPan') else ViewBox.RectMode, + 'enableMenu': enableMenu, + 'wheelScaleFactor': -1.0 / 8.0, + + 'background': None, + } + self._updatingRange = False ## Used to break recursive loops. See updateAutoRange. + self._itemBoundsCache = weakref.WeakKeyDictionary() + + self.locateGroup = None ## items displayed when using ViewBox.locate(item) + + self.setFlag(self.ItemClipsChildrenToShape) + self.setFlag(self.ItemIsFocusable, True) ## so we can receive key presses + + ## childGroup is required so that ViewBox has local coordinates similar to device coordinates. + ## this is a workaround for a Qt + OpenGL bug that causes improper clipping + ## https://bugreports.qt.nokia.com/browse/QTBUG-23723 + self.childGroup = ChildGroup(self) + self.childGroup.sigItemsChanged.connect(self.itemsChanged) + + self.background = QtGui.QGraphicsRectItem(self.rect()) + self.background.setParentItem(self) + self.background.setZValue(-1e6) + self.background.setPen(fn.mkPen(None)) + self.updateBackground() + + #self.useLeftButtonPan = pyqtgraph.getConfigOption('leftButtonPan') # normally use left button to pan + # this also enables capture of keyPressEvents. + + ## Make scale box that is shown when dragging on the view + self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1) + self.rbScaleBox.setPen(fn.mkPen((255,255,100), width=1)) + self.rbScaleBox.setBrush(fn.mkBrush(255,255,0,100)) + self.rbScaleBox.setZValue(1e9) + self.rbScaleBox.hide() + self.addItem(self.rbScaleBox, ignoreBounds=True) + + ## show target rect for debugging + self.target = QtGui.QGraphicsRectItem(0, 0, 1, 1) + self.target.setPen(fn.mkPen('r')) + self.target.setParentItem(self) + self.target.hide() + + self.axHistory = [] # maintain a history of zoom locations + self.axHistoryPointer = -1 # pointer into the history. Allows forward/backward movement, not just "undo" + + self.setZValue(-100) + self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) + + self.setAspectLocked(lockAspect) + + self.border = fn.mkPen(border) + self.menu = ViewBoxMenu(self) + + self.register(name) + if name is None: + self.updateViewLists() + + def register(self, name): + """ + Add this ViewBox to the registered list of views. + *name* will appear in the drop-down lists for axis linking in all other views. + The same can be accomplished by initializing the ViewBox with the *name* attribute. + """ + ViewBox.AllViews[self] = None + if self.name is not None: + del ViewBox.NamedViews[self.name] + self.name = name + if name is not None: + ViewBox.NamedViews[name] = self + ViewBox.updateAllViewLists() + sid = id(self) + self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if (ViewBox is not None and 'sid' in locals() and 'name' in locals()) else None) + #self.destroyed.connect(self.unregister) + + def unregister(self): + """ + Remove this ViewBox from the list of linkable views. (see :func:`register() `) + """ + del ViewBox.AllViews[self] + if self.name is not None: + del ViewBox.NamedViews[self.name] + + def close(self): + self.unregister() + + def implements(self, interface): + return interface == 'ViewBox' + + # removed due to https://bugreports.qt-project.org/browse/PYSIDE-86 + #def itemChange(self, change, value): + ## Note: Calling QWidget.itemChange causes segv in python 3 + PyQt + ##ret = QtGui.QGraphicsItem.itemChange(self, change, value) + #ret = GraphicsWidget.itemChange(self, change, value) + #if change == self.ItemSceneChange: + #scene = self.scene() + #if scene is not None and hasattr(scene, 'sigPrepareForPaint'): + #scene.sigPrepareForPaint.disconnect(self.prepareForPaint) + #elif change == self.ItemSceneHasChanged: + #scene = self.scene() + #if scene is not None and hasattr(scene, 'sigPrepareForPaint'): + #scene.sigPrepareForPaint.connect(self.prepareForPaint) + #return ret + + def checkSceneChange(self): + # ViewBox needs to receive sigPrepareForPaint from its scene before + # being painted. However, we have no way of being informed when the + # scene has changed in order to make this connection. The usual way + # to do this is via itemChange(), but bugs prevent this approach + # (see above). Instead, we simply check at every paint to see whether + # (the scene has changed. + scene = self.scene() + if scene == self._lastScene: + return + if self._lastScene is not None and hasattr(self.lastScene, 'sigPrepareForPaint'): + self._lastScene.sigPrepareForPaint.disconnect(self.prepareForPaint) + if scene is not None and hasattr(scene, 'sigPrepareForPaint'): + scene.sigPrepareForPaint.connect(self.prepareForPaint) + self.prepareForPaint() + self._lastScene = scene + + + + + def prepareForPaint(self): + #autoRangeEnabled = (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False) + # don't check whether auto range is enabled here--only check when setting dirty flag. + if self._autoRangeNeedsUpdate: # and autoRangeEnabled: + self.updateAutoRange() + if self._matrixNeedsUpdate: + self.updateMatrix() + + def getState(self, copy=True): + """Return the current state of the ViewBox. + Linked views are always converted to view names in the returned state.""" + state = self.state.copy() + views = [] + for v in state['linkedViews']: + if isinstance(v, weakref.ref): + v = v() + if v is None or isinstance(v, basestring): + views.append(v) + else: + views.append(v.name) + state['linkedViews'] = views + if copy: + return deepcopy(state) + else: + return state + + def setState(self, state): + """Restore the state of this ViewBox. + (see also getState)""" + state = state.copy() + self.setXLink(state['linkedViews'][0]) + self.setYLink(state['linkedViews'][1]) + del state['linkedViews'] + + self.state.update(state) + #self.updateMatrix() + self.updateViewRange() + self.sigStateChanged.emit(self) + + + def setMouseMode(self, mode): + """ + Set the mouse interaction mode. *mode* must be either ViewBox.PanMode or ViewBox.RectMode. + In PanMode, the left mouse button pans the view and the right button scales. + In RectMode, the left button draws a rectangle which updates the visible region (this mode is more suitable for single-button mice) + """ + if mode not in [ViewBox.PanMode, ViewBox.RectMode]: + raise Exception("Mode must be ViewBox.PanMode or ViewBox.RectMode") + self.state['mouseMode'] = mode + self.sigStateChanged.emit(self) + + #def toggleLeftAction(self, act): ## for backward compatibility + #if act.text() is 'pan': + #self.setLeftButtonAction('pan') + #elif act.text() is 'zoom': + #self.setLeftButtonAction('rect') + + def setLeftButtonAction(self, mode='rect'): ## for backward compatibility + if mode.lower() == 'rect': + self.setMouseMode(ViewBox.RectMode) + elif mode.lower() == 'pan': + self.setMouseMode(ViewBox.PanMode) + else: + raise Exception('graphicsItems:ViewBox:setLeftButtonAction: unknown mode = %s (Options are "pan" and "rect")' % mode) + + def innerSceneItem(self): + return self.childGroup + + def setMouseEnabled(self, x=None, y=None): + """ + Set whether each axis is enabled for mouse interaction. *x*, *y* arguments must be True or False. + This allows the user to pan/scale one axis of the view while leaving the other axis unchanged. + """ + if x is not None: + self.state['mouseEnabled'][0] = x + if y is not None: + self.state['mouseEnabled'][1] = y + self.sigStateChanged.emit(self) + + def mouseEnabled(self): + return self.state['mouseEnabled'][:] + + def setMenuEnabled(self, enableMenu=True): + self.state['enableMenu'] = enableMenu + self.sigStateChanged.emit(self) + + def menuEnabled(self): + return self.state.get('enableMenu', True) + + def addItem(self, item, ignoreBounds=False): + """ + Add a QGraphicsItem to this view. The view will include this item when determining how to set its range + automatically unless *ignoreBounds* is True. + """ + if item.zValue() < self.zValue(): + item.setZValue(self.zValue()+1) + scene = self.scene() + if scene is not None and scene is not item.scene(): + scene.addItem(item) ## Necessary due to Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 + item.setParentItem(self.childGroup) + if not ignoreBounds: + self.addedItems.append(item) + self.updateAutoRange() + #print "addItem:", item, item.boundingRect() + + def removeItem(self, item): + """Remove an item from this view.""" + try: + self.addedItems.remove(item) + except: + pass + self.scene().removeItem(item) + self.updateAutoRange() + + def clear(self): + for i in self.addedItems[:]: + self.removeItem(i) + for ch in self.childGroup.childItems(): + ch.setParentItem(None) + + def resizeEvent(self, ev): + self.linkedXChanged() + self.linkedYChanged() + self.updateAutoRange() + self.updateViewRange() + self.sigStateChanged.emit(self) + self.background.setRect(self.rect()) + self.sigResized.emit(self) + + + def viewRange(self): + """Return a the view's visible range as a list: [[xmin, xmax], [ymin, ymax]]""" + return [x[:] for x in self.state['viewRange']] ## return copy + + def viewRect(self): + """Return a QRectF bounding the region visible within the ViewBox""" + try: + vr0 = self.state['viewRange'][0] + vr1 = self.state['viewRange'][1] + return QtCore.QRectF(vr0[0], vr1[0], vr0[1]-vr0[0], vr1[1] - vr1[0]) + except: + print("make qrectf failed:", self.state['viewRange']) + raise + + def targetRange(self): + return [x[:] for x in self.state['targetRange']] ## return copy + + def targetRect(self): + """ + Return the region which has been requested to be visible. + (this is not necessarily the same as the region that is *actually* visible-- + resizing and aspect ratio constraints can cause targetRect() and viewRect() to differ) + """ + try: + tr0 = self.state['targetRange'][0] + tr1 = self.state['targetRange'][1] + return QtCore.QRectF(tr0[0], tr1[0], tr0[1]-tr0[0], tr1[1] - tr1[0]) + except: + print("make qrectf failed:", self.state['targetRange']) + raise + + def setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=True, disableAutoRange=True): + """ + Set the visible range of the ViewBox. + Must specify at least one of *rect*, *xRange*, or *yRange*. + + ================== ===================================================================== + **Arguments** + *rect* (QRectF) The full range that should be visible in the view box. + *xRange* (min,max) The range that should be visible along the x-axis. + *yRange* (min,max) The range that should be visible along the y-axis. + *padding* (float) Expand the view by a fraction of the requested range. + By default, this value is set between 0.02 and 0.1 depending on + the size of the ViewBox. + *update* (bool) If True, update the range of the ViewBox immediately. + Otherwise, the update is deferred until before the next render. + *disableAutoRange* (bool) If True, auto-ranging is diabled. Otherwise, it is left + unchanged. + ================== ===================================================================== + + """ + #print self.name, "ViewBox.setRange", rect, xRange, yRange, padding + #import traceback + #traceback.print_stack() + + changes = {} # axes + setRequested = [False, False] + + if rect is not None: + changes = {0: [rect.left(), rect.right()], 1: [rect.top(), rect.bottom()]} + setRequested = [True, True] + if xRange is not None: + changes[0] = xRange + setRequested[0] = True + if yRange is not None: + changes[1] = yRange + setRequested[1] = True + + if len(changes) == 0: + print(rect) + raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect))) + + # Update axes one at a time + changed = [False, False] + for ax, range in changes.items(): + mn = min(range) + mx = max(range) + + # If we requested 0 range, try to preserve previous scale. + # Otherwise just pick an arbitrary scale. + if mn == mx: + dy = self.state['viewRange'][ax][1] - self.state['viewRange'][ax][0] + if dy == 0: + dy = 1 + mn -= dy*0.5 + mx += dy*0.5 + xpad = 0.0 + + # Make sure no nan/inf get through + if not all(np.isfinite([mn, mx])): + raise Exception("Cannot set range [%s, %s]" % (str(mn), str(mx))) + + # Apply padding + if padding is None: + xpad = self.suggestPadding(ax) + else: + xpad = padding + p = (mx-mn) * xpad + mn -= p + mx += p + + # Set target range + if self.state['targetRange'][ax] != [mn, mx]: + self.state['targetRange'][ax] = [mn, mx] + changed[ax] = True + + # Update viewRange to match targetRange as closely as possible while + # accounting for aspect ratio constraint + lockX, lockY = setRequested + if lockX and lockY: + lockX = False + lockY = False + self.updateViewRange(lockX, lockY) + + # Disable auto-range for each axis that was requested to be set + if disableAutoRange: + xOff = False if setRequested[0] else None + yOff = False if setRequested[1] else None + self.enableAutoRange(x=xOff, y=yOff) + changed.append(True) + + # If nothing has changed, we are done. + if any(changed): + #if update and self.matrixNeedsUpdate: + #self.updateMatrix(changed) + #return + + self.sigStateChanged.emit(self) + + # Update target rect for debugging + if self.target.isVisible(): + self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect())) + + # If ortho axes have auto-visible-only, update them now + # Note that aspect ratio constraints and auto-visible probably do not work together.. + if changed[0] and self.state['autoVisibleOnly'][1] and (self.state['autoRange'][0] is not False): + self._autoRangeNeedsUpdate = True + #self.updateAutoRange() ## Maybe just indicate that auto range needs to be updated? + elif changed[1] and self.state['autoVisibleOnly'][0] and (self.state['autoRange'][1] is not False): + self._autoRangeNeedsUpdate = True + #self.updateAutoRange() + + ## Update view matrix only if requested + #if update: + #self.updateMatrix(changed) + ## Otherwise, indicate that the matrix needs to be updated + #else: + #self.matrixNeedsUpdate = True + + ## Inform linked views that the range has changed <> + #for ax, range in changes.items(): + #link = self.linkedView(ax) + #if link is not None: + #link.linkedViewChanged(self, ax) + + + + def setYRange(self, min, max, padding=None, update=True): + """ + Set the visible Y range of the view to [*min*, *max*]. + The *padding* argument causes the range to be set larger by the fraction specified. + (by default, this value is between 0.02 and 0.1 depending on the size of the ViewBox) + """ + self.setRange(yRange=[min, max], update=update, padding=padding) + + def setXRange(self, min, max, padding=None, update=True): + """ + Set the visible X range of the view to [*min*, *max*]. + The *padding* argument causes the range to be set larger by the fraction specified. + (by default, this value is between 0.02 and 0.1 depending on the size of the ViewBox) + """ + self.setRange(xRange=[min, max], update=update, padding=padding) + + def autoRange(self, padding=None, items=None, item=None): + """ + Set the range of the view box to make all children visible. + Note that this is not the same as enableAutoRange, which causes the view to + automatically auto-range whenever its contents are changed. + + =========== ============================================================ + Arguments + padding The fraction of the total data range to add on to the final + visible range. By default, this value is set between 0.02 + and 0.1 depending on the size of the ViewBox. + items If specified, this is a list of items to consider when + determining the visible range. + =========== ============================================================ + """ + if item is None: + bounds = self.childrenBoundingRect(items=items) + else: + print("Warning: ViewBox.autoRange(item=__) is deprecated. Use 'items' argument instead.") + bounds = self.mapFromItemToView(item, item.boundingRect()).boundingRect() + + if bounds is not None: + self.setRange(bounds, padding=padding) + + def suggestPadding(self, axis): + l = self.width() if axis==0 else self.height() + if l > 0: + padding = np.clip(1./(l**0.5), 0.02, 0.1) + else: + padding = 0.02 + return padding + + def scaleBy(self, s=None, center=None, x=None, y=None): + """ + Scale by *s* around given center point (or center of view). + *s* may be a Point or tuple (x, y). + + Optionally, x or y may be specified individually. This allows the other + axis to be left unaffected (note that using a scale factor of 1.0 may + cause slight changes due to floating-point error). + """ + if s is not None: + scale = Point(s) + else: + scale = [x, y] + + affect = [True, True] + if scale[0] is None and scale[1] is None: + return + elif scale[0] is None: + affect[0] = False + scale[0] = 1.0 + elif scale[1] is None: + affect[1] = False + scale[1] = 1.0 + + scale = Point(scale) + + if self.state['aspectLocked'] is not False: + scale[0] = scale[1] + + vr = self.targetRect() + if center is None: + center = Point(vr.center()) + else: + center = Point(center) + + tl = center + (vr.topLeft()-center) * scale + br = center + (vr.bottomRight()-center) * scale + + if not affect[0]: + self.setYRange(tl.y(), br.y(), padding=0) + elif not affect[1]: + self.setXRange(tl.x(), br.x(), padding=0) + else: + self.setRange(QtCore.QRectF(tl, br), padding=0) + + def translateBy(self, t=None, x=None, y=None): + """ + Translate the view by *t*, which may be a Point or tuple (x, y). + + Alternately, x or y may be specified independently, leaving the other + axis unchanged (note that using a translation of 0 may still cause + small changes due to floating-point error). + """ + vr = self.targetRect() + if t is not None: + t = Point(t) + self.setRange(vr.translated(t), padding=0) + else: + if x is not None: + x = vr.left()+x, vr.right()+x + if y is not None: + y = vr.top()+y, vr.bottom()+y + self.setRange(xRange=x, yRange=y, padding=0) + + + + def enableAutoRange(self, axis=None, enable=True, x=None, y=None): + """ + Enable (or disable) auto-range for *axis*, which may be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes for both + (if *axis* is omitted, both axes will be changed). + When enabled, the axis will automatically rescale when items are added/removed or change their shape. + The argument *enable* may optionally be a float (0.0-1.0) which indicates the fraction of the data that should + be visible (this only works with items implementing a dataRange method, such as PlotDataItem). + """ + #print "autorange:", axis, enable + #if not enable: + #import traceback + #traceback.print_stack() + + # support simpler interface: + if x is not None or y is not None: + if x is not None: + self.enableAutoRange(ViewBox.XAxis, x) + if y is not None: + self.enableAutoRange(ViewBox.YAxis, y) + return + + if enable is True: + enable = 1.0 + + if axis is None: + axis = ViewBox.XYAxes + + needAutoRangeUpdate = False + + if axis == ViewBox.XYAxes or axis == 'xy': + axes = [0, 1] + elif axis == ViewBox.XAxis or axis == 'x': + axes = [0] + elif axis == ViewBox.YAxis or axis == 'y': + axes = [1] + else: + raise Exception('axis argument must be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes.') + + for ax in axes: + if self.state['autoRange'][ax] != enable: + # If we are disabling, do one last auto-range to make sure that + # previously scheduled auto-range changes are enacted + if enable is False and self._autoRangeNeedsUpdate: + self.updateAutoRange() + + self.state['autoRange'][ax] = enable + self._autoRangeNeedsUpdate |= (enable is not False) + self.update() + + + #if needAutoRangeUpdate: + # self.updateAutoRange() + + self.sigStateChanged.emit(self) + + def disableAutoRange(self, axis=None): + """Disables auto-range. (See enableAutoRange)""" + self.enableAutoRange(axis, enable=False) + + def autoRangeEnabled(self): + return self.state['autoRange'][:] + + def setAutoPan(self, x=None, y=None): + if x is not None: + self.state['autoPan'][0] = x + if y is not None: + self.state['autoPan'][1] = y + if None not in [x,y]: + self.updateAutoRange() + + def setAutoVisible(self, x=None, y=None): + if x is not None: + self.state['autoVisibleOnly'][0] = x + if x is True: + self.state['autoVisibleOnly'][1] = False + if y is not None: + self.state['autoVisibleOnly'][1] = y + if y is True: + self.state['autoVisibleOnly'][0] = False + + if x is not None or y is not None: + self.updateAutoRange() + + def updateAutoRange(self): + ## Break recursive loops when auto-ranging. + ## This is needed because some items change their size in response + ## to a view change. + if self._updatingRange: + return + + self._updatingRange = True + try: + targetRect = self.viewRange() + if not any(self.state['autoRange']): + return + + fractionVisible = self.state['autoRange'][:] + for i in [0,1]: + if type(fractionVisible[i]) is bool: + fractionVisible[i] = 1.0 + + childRange = None + + order = [0,1] + if self.state['autoVisibleOnly'][0] is True: + order = [1,0] + + args = {} + for ax in order: + if self.state['autoRange'][ax] is False: + continue + if self.state['autoVisibleOnly'][ax]: + oRange = [None, None] + oRange[ax] = targetRect[1-ax] + childRange = self.childrenBounds(frac=fractionVisible, orthoRange=oRange) + + else: + if childRange is None: + childRange = self.childrenBounds(frac=fractionVisible) + + ## Make corrections to range + xr = childRange[ax] + if xr is not None: + if self.state['autoPan'][ax]: + x = sum(xr) * 0.5 + w2 = (targetRect[ax][1]-targetRect[ax][0]) / 2. + childRange[ax] = [x-w2, x+w2] + else: + padding = self.suggestPadding(ax) + wp = (xr[1] - xr[0]) * padding + childRange[ax][0] -= wp + childRange[ax][1] += wp + targetRect[ax] = childRange[ax] + args['xRange' if ax == 0 else 'yRange'] = targetRect[ax] + if len(args) == 0: + return + args['padding'] = 0 + args['disableAutoRange'] = False + self.setRange(**args) + finally: + self._autoRangeNeedsUpdate = False + self._updatingRange = False + + def setXLink(self, view): + """Link this view's X axis to another view. (see LinkView)""" + self.linkView(self.XAxis, view) + + def setYLink(self, view): + """Link this view's Y axis to another view. (see LinkView)""" + self.linkView(self.YAxis, view) + + + def linkView(self, axis, view): + """ + Link X or Y axes of two views and unlink any previously connected axes. *axis* must be ViewBox.XAxis or ViewBox.YAxis. + If view is None, the axis is left unlinked. + """ + if isinstance(view, basestring): + if view == '': + view = None + else: + view = ViewBox.NamedViews.get(view, view) ## convert view name to ViewBox if possible + + if hasattr(view, 'implements') and view.implements('ViewBoxWrapper'): + view = view.getViewBox() + + ## used to connect/disconnect signals between a pair of views + if axis == ViewBox.XAxis: + signal = 'sigXRangeChanged' + slot = self.linkedXChanged + else: + signal = 'sigYRangeChanged' + slot = self.linkedYChanged + + + oldLink = self.linkedView(axis) + if oldLink is not None: + try: + getattr(oldLink, signal).disconnect(slot) + oldLink.sigResized.disconnect(slot) + except TypeError: + ## This can occur if the view has been deleted already + pass + + + if view is None or isinstance(view, basestring): + self.state['linkedViews'][axis] = view + else: + self.state['linkedViews'][axis] = weakref.ref(view) + getattr(view, signal).connect(slot) + view.sigResized.connect(slot) + if view.autoRangeEnabled()[axis] is not False: + self.enableAutoRange(axis, False) + slot() + else: + if self.autoRangeEnabled()[axis] is False: + slot() + + + self.sigStateChanged.emit(self) + + def blockLink(self, b): + self.linksBlocked = b ## prevents recursive plot-change propagation + + def linkedXChanged(self): + ## called when x range of linked view has changed + view = self.linkedView(0) + self.linkedViewChanged(view, ViewBox.XAxis) + + def linkedYChanged(self): + ## called when y range of linked view has changed + view = self.linkedView(1) + self.linkedViewChanged(view, ViewBox.YAxis) + + def linkedView(self, ax): + ## Return the linked view for axis *ax*. + ## this method _always_ returns either a ViewBox or None. + v = self.state['linkedViews'][ax] + if v is None or isinstance(v, basestring): + return None + else: + return v() ## dereference weakref pointer. If the reference is dead, this returns None + + def linkedViewChanged(self, view, axis): + if self.linksBlocked or view is None: + return + + #print self.name, "ViewBox.linkedViewChanged", axis, view.viewRange()[axis] + vr = view.viewRect() + vg = view.screenGeometry() + sg = self.screenGeometry() + if vg is None or sg is None: + return + + view.blockLink(True) + try: + if axis == ViewBox.XAxis: + overlap = min(sg.right(), vg.right()) - max(sg.left(), vg.left()) + if overlap < min(vg.width()/3, sg.width()/3): ## if less than 1/3 of views overlap, + ## then just replicate the view + x1 = vr.left() + x2 = vr.right() + else: ## views overlap; line them up + upp = float(vr.width()) / vg.width() + x1 = vr.left() + (sg.x()-vg.x()) * upp + x2 = x1 + sg.width() * upp + self.enableAutoRange(ViewBox.XAxis, False) + self.setXRange(x1, x2, padding=0) + else: + overlap = min(sg.bottom(), vg.bottom()) - max(sg.top(), vg.top()) + if overlap < min(vg.height()/3, sg.height()/3): ## if less than 1/3 of views overlap, + ## then just replicate the view + y1 = vr.top() + y2 = vr.bottom() + else: ## views overlap; line them up + upp = float(vr.height()) / vg.height() + if self.yInverted(): + y2 = vr.bottom() + (sg.bottom()-vg.bottom()) * upp + else: + y2 = vr.bottom() + (sg.top()-vg.top()) * upp + y1 = y2 - sg.height() * upp + self.enableAutoRange(ViewBox.YAxis, False) + self.setYRange(y1, y2, padding=0) + finally: + view.blockLink(False) + + + def screenGeometry(self): + """return the screen geometry of the viewbox""" + v = self.getViewWidget() + if v is None: + return None + b = self.sceneBoundingRect() + wr = v.mapFromScene(b).boundingRect() + pos = v.mapToGlobal(v.pos()) + wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) + return wr + + + + def itemsChanged(self): + ## called when items are added/removed from self.childGroup + self.updateAutoRange() + + def itemBoundsChanged(self, item): + self._itemBoundsCache.pop(item, None) + if (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False): + self._autoRangeNeedsUpdate = True + self.update() + #self.updateAutoRange() + + def invertY(self, b=True): + """ + By default, the positive y-axis points upward on the screen. Use invertY(True) to reverse the y-axis. + """ + if self.state['yInverted'] == b: + return + + self.state['yInverted'] = b + #self.updateMatrix(changed=(False, True)) + self.updateViewRange() + self.sigStateChanged.emit(self) + + def yInverted(self): + return self.state['yInverted'] + + def setAspectLocked(self, lock=True, ratio=1): + """ + If the aspect ratio is locked, view scaling must always preserve the aspect ratio. + By default, the ratio is set to 1; x and y both have the same scaling. + This ratio can be overridden (xScale/yScale), or use None to lock in the current ratio. + """ + + if not lock: + if self.state['aspectLocked'] == False: + return + self.state['aspectLocked'] = False + else: + rect = self.rect() + vr = self.viewRect() + if rect.height() == 0 or vr.width() == 0 or vr.height() == 0: + currentRatio = 1.0 + else: + currentRatio = (rect.width()/float(rect.height())) / (vr.width()/vr.height()) + if ratio is None: + ratio = currentRatio + if self.state['aspectLocked'] == ratio: # nothing to change + return + self.state['aspectLocked'] = ratio + if ratio != currentRatio: ## If this would change the current range, do that now + #self.setRange(0, self.state['viewRange'][0][0], self.state['viewRange'][0][1]) + self.updateViewRange() + + self.updateAutoRange() + self.updateViewRange() + self.sigStateChanged.emit(self) + + def childTransform(self): + """ + Return the transform that maps from child(item in the childGroup) coordinates to local coordinates. + (This maps from inside the viewbox to outside) + """ + m = self.childGroup.transform() + #m1 = QtGui.QTransform() + #m1.translate(self.childGroup.pos().x(), self.childGroup.pos().y()) + return m #*m1 + + def mapToView(self, obj): + """Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox""" + m = fn.invertQTransform(self.childTransform()) + return m.map(obj) + + def mapFromView(self, obj): + """Maps from the coordinate system displayed inside the ViewBox to the local coordinates of the ViewBox""" + m = self.childTransform() + return m.map(obj) + + def mapSceneToView(self, obj): + """Maps from scene coordinates to the coordinate system displayed inside the ViewBox""" + return self.mapToView(self.mapFromScene(obj)) + + def mapViewToScene(self, obj): + """Maps from the coordinate system displayed inside the ViewBox to scene coordinates""" + return self.mapToScene(self.mapFromView(obj)) + + def mapFromItemToView(self, item, obj): + """Maps *obj* from the local coordinate system of *item* to the view coordinates""" + return self.childGroup.mapFromItem(item, obj) + #return self.mapSceneToView(item.mapToScene(obj)) + + def mapFromViewToItem(self, item, obj): + """Maps *obj* from view coordinates to the local coordinate system of *item*.""" + return self.childGroup.mapToItem(item, obj) + #return item.mapFromScene(self.mapViewToScene(obj)) + + def mapViewToDevice(self, obj): + return self.mapToDevice(self.mapFromView(obj)) + + def mapDeviceToView(self, obj): + return self.mapToView(self.mapFromDevice(obj)) + + def viewPixelSize(self): + """Return the (width, height) of a screen pixel in view coordinates.""" + o = self.mapToView(Point(0,0)) + px, py = [Point(self.mapToView(v) - o) for v in self.pixelVectors()] + return (px.length(), py.length()) + + + def itemBoundingRect(self, item): + """Return the bounding rect of the item in view coordinates""" + return self.mapSceneToView(item.sceneBoundingRect()).boundingRect() + + #def viewScale(self): + #vr = self.viewRect() + ##print "viewScale:", self.range + #xd = vr.width() + #yd = vr.height() + #if xd == 0 or yd == 0: + #print "Warning: 0 range in view:", xd, yd + #return np.array([1,1]) + + ##cs = self.canvas().size() + #cs = self.boundingRect() + #scale = np.array([cs.width() / xd, cs.height() / yd]) + ##print "view scale:", scale + #return scale + + def wheelEvent(self, ev, axis=None): + mask = np.array(self.state['mouseEnabled'], dtype=np.float) + if axis is not None and axis >= 0 and axis < len(mask): + mv = mask[axis] + mask[:] = 0 + mask[axis] = mv + s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor + + center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) + #center = ev.pos() + + self.scaleBy(s, center) + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + ev.accept() + + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton and self.menuEnabled(): + ev.accept() + self.raiseContextMenu(ev) + + def raiseContextMenu(self, ev): + #print "viewbox.raiseContextMenu called." + + #menu = self.getMenu(ev) + menu = self.getMenu(ev) + self.scene().addParentContextMenus(self, menu, ev) + #print "2:", [str(a.text()) for a in self.menu.actions()] + pos = ev.screenPos() + #pos2 = ev.scenePos() + #print "3:", [str(a.text()) for a in self.menu.actions()] + #self.sigActionPositionChanged.emit(pos2) + + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + #print "4:", [str(a.text()) for a in self.menu.actions()] + + def getMenu(self, ev): + self._menuCopy = self.menu.copy() ## temporary storage to prevent menu disappearing + return self._menuCopy + + def getContextMenus(self, event): + if self.menuEnabled(): + return self.menu.subMenus() + else: + return None + #return [self.getMenu(event)] + + + def mouseDragEvent(self, ev, axis=None): + ## if axis is specified, event will only affect that axis. + ev.accept() ## we accept all buttons + + pos = ev.pos() + lastPos = ev.lastPos() + dif = pos - lastPos + dif = dif * -1 + + ## Ignore axes if mouse is disabled + mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float) + mask = mouseEnabled.copy() + if axis is not None: + mask[1-axis] = 0.0 + + ## Scale or translate based on mouse button + if ev.button() & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): + if self.state['mouseMode'] == ViewBox.RectMode: + if ev.isFinish(): ## This is the final move in the drag; change the view scale now + #print "finish" + self.rbScaleBox.hide() + #ax = QtCore.QRectF(Point(self.pressPos), Point(self.mousePos)) + ax = QtCore.QRectF(Point(ev.buttonDownPos(ev.button())), Point(pos)) + ax = self.childGroup.mapRectFromParent(ax) + self.showAxRect(ax) + self.axHistoryPointer += 1 + self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax] + else: + ## update shape of scale box + self.updateScaleBox(ev.buttonDownPos(), ev.pos()) + else: + tr = dif*mask + tr = self.mapToView(tr) - self.mapToView(Point(0,0)) + x = tr.x() if mask[0] == 1 else None + y = tr.y() if mask[1] == 1 else None + + self.translateBy(x=x, y=y) + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + elif ev.button() & QtCore.Qt.RightButton: + #print "vb.rightDrag" + if self.state['aspectLocked'] is not False: + mask[0] = 0 + + dif = ev.screenPos() - ev.lastScreenPos() + dif = np.array([dif.x(), dif.y()]) + dif[0] *= -1 + s = ((mask * 0.02) + 1) ** dif + + tr = self.childGroup.transform() + tr = fn.invertQTransform(tr) + + x = s[0] if mouseEnabled[0] == 1 else None + y = s[1] if mouseEnabled[1] == 1 else None + + center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton))) + self.scaleBy(x=x, y=y, center=center) + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + + def keyPressEvent(self, ev): + """ + This routine should capture key presses in the current view box. + Key presses are used only when mouse mode is RectMode + The following events are implemented: + ctrl-A : zooms out to the default "full" view of the plot + ctrl-+ : moves forward in the zooming stack (if it exists) + ctrl-- : moves backward in the zooming stack (if it exists) + + """ + #print ev.key() + #print 'I intercepted a key press, but did not accept it' + + ## not implemented yet ? + #self.keypress.sigkeyPressEvent.emit() + + ev.accept() + if ev.text() == '-': + self.scaleHistory(-1) + elif ev.text() in ['+', '=']: + self.scaleHistory(1) + elif ev.key() == QtCore.Qt.Key_Backspace: + self.scaleHistory(len(self.axHistory)) + else: + ev.ignore() + + def scaleHistory(self, d): + ptr = max(0, min(len(self.axHistory)-1, self.axHistoryPointer+d)) + if ptr != self.axHistoryPointer: + self.axHistoryPointer = ptr + self.showAxRect(self.axHistory[ptr]) + + + def updateScaleBox(self, p1, p2): + r = QtCore.QRectF(p1, p2) + r = self.childGroup.mapRectFromParent(r) + self.rbScaleBox.setPos(r.topLeft()) + self.rbScaleBox.resetTransform() + self.rbScaleBox.scale(r.width(), r.height()) + self.rbScaleBox.show() + + def showAxRect(self, ax): + self.setRange(ax.normalized()) # be sure w, h are correct coordinates + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + + #def mouseRect(self): + #vs = self.viewScale() + #vr = self.state['viewRange'] + ## Convert positions from screen (view) pixel coordinates to axis coordinates + #ax = QtCore.QRectF(self.pressPos[0]/vs[0]+vr[0][0], -(self.pressPos[1]/vs[1]-vr[1][1]), + #(self.mousePos[0]-self.pressPos[0])/vs[0], -(self.mousePos[1]-self.pressPos[1])/vs[1]) + #return(ax) + + def allChildren(self, item=None): + """Return a list of all children and grandchildren of this ViewBox""" + if item is None: + item = self.childGroup + + children = [item] + for ch in item.childItems(): + children.extend(self.allChildren(ch)) + return children + + + + def childrenBounds(self, frac=None, orthoRange=(None,None), items=None): + """Return the bounding range of all children. + [[xmin, xmax], [ymin, ymax]] + Values may be None if there are no specific bounds for an axis. + """ + prof = debug.Profiler('updateAutoRange', disabled=True) + if items is None: + items = self.addedItems + + ## measure pixel dimensions in view box + px, py = [v.length() if v is not None else 0 for v in self.childGroup.pixelVectors()] + + ## First collect all boundary information + itemBounds = [] + for item in items: + if not item.isVisible(): + continue + + useX = True + useY = True + + if hasattr(item, 'dataBounds'): + #bounds = self._itemBoundsCache.get(item, None) + #if bounds is None: + if frac is None: + frac = (1.0, 1.0) + xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) + yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) + pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() + if xr is None or (xr[0] is None and xr[1] is None) or np.isnan(xr).any() or np.isinf(xr).any(): + useX = False + xr = (0,0) + if yr is None or (yr[0] is None and yr[1] is None) or np.isnan(yr).any() or np.isinf(yr).any(): + useY = False + yr = (0,0) + + bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0]) + bounds = self.mapFromItemToView(item, bounds).boundingRect() + + if not any([useX, useY]): + continue + + ## If we are ignoring only one axis, we need to check for rotations + if useX != useY: ## != means xor + ang = round(item.transformAngle()) + if ang == 0 or ang == 180: + pass + elif ang == 90 or ang == 270: + useX, useY = useY, useX + else: + ## Item is rotated at non-orthogonal angle, ignore bounds entirely. + ## Not really sure what is the expected behavior in this case. + continue ## need to check for item rotations and decide how best to apply this boundary. + + + itemBounds.append((bounds, useX, useY, pxPad)) + #self._itemBoundsCache[item] = (bounds, useX, useY) + #else: + #bounds, useX, useY = bounds + else: + if int(item.flags() & item.ItemHasNoContents) > 0: + continue + else: + bounds = item.boundingRect() + bounds = self.mapFromItemToView(item, bounds).boundingRect() + itemBounds.append((bounds, True, True, 0)) + + #print itemBounds + + ## determine tentative new range + range = [None, None] + for bounds, useX, useY, px in itemBounds: + if useY: + if range[1] is not None: + range[1] = [min(bounds.top(), range[1][0]), max(bounds.bottom(), range[1][1])] + else: + range[1] = [bounds.top(), bounds.bottom()] + if useX: + if range[0] is not None: + range[0] = [min(bounds.left(), range[0][0]), max(bounds.right(), range[0][1])] + else: + range[0] = [bounds.left(), bounds.right()] + prof.mark('2') + + #print "range", range + + ## Now expand any bounds that have a pixel margin + ## This must be done _after_ we have a good estimate of the new range + ## to ensure that the pixel size is roughly accurate. + w = self.width() + h = self.height() + #print "w:", w, "h:", h + if w > 0 and range[0] is not None: + pxSize = (range[0][1] - range[0][0]) / w + for bounds, useX, useY, px in itemBounds: + if px == 0 or not useX: + continue + range[0][0] = min(range[0][0], bounds.left() - px*pxSize) + range[0][1] = max(range[0][1], bounds.right() + px*pxSize) + if h > 0 and range[1] is not None: + pxSize = (range[1][1] - range[1][0]) / h + for bounds, useX, useY, px in itemBounds: + if px == 0 or not useY: + continue + range[1][0] = min(range[1][0], bounds.top() - px*pxSize) + range[1][1] = max(range[1][1], bounds.bottom() + px*pxSize) + + #print "final range", range + + prof.finish() + return range + + def childrenBoundingRect(self, *args, **kwds): + range = self.childrenBounds(*args, **kwds) + tr = self.targetRange() + if range[0] is None: + range[0] = tr[0] + if range[1] is None: + range[1] = tr[1] + + bounds = QtCore.QRectF(range[0][0], range[1][0], range[0][1]-range[0][0], range[1][1]-range[1][0]) + return bounds + + def updateViewRange(self, forceX=False, forceY=False): + ## Update viewRange to match targetRange as closely as possible, given + ## aspect ratio constraints. The *force* arguments are used to indicate + ## which axis (if any) should be unchanged when applying constraints. + viewRange = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]] + changed = [False, False] + + # Make correction for aspect ratio constraint + + ## aspect is (widget w/h) / (view range w/h) + aspect = self.state['aspectLocked'] # size ratio / view ratio + tr = self.targetRect() + bounds = self.rect() + if aspect is not False and aspect != 0 and tr.height() != 0 and bounds.height() != 0: + + ## This is the view range aspect ratio we have requested + targetRatio = tr.width() / tr.height() + ## This is the view range aspect ratio we need to obey aspect constraint + viewRatio = (bounds.width() / bounds.height()) / aspect + + # Decide which range to keep unchanged + #print self.name, "aspect:", aspect, "changed:", changed, "auto:", self.state['autoRange'] + if forceX: + ax = 0 + elif forceY: + ax = 1 + else: + # if we are not required to keep a particular axis unchanged, + # then make the entire target range visible + ax = 0 if targetRatio > viewRatio else 1 + + #### these should affect viewRange, not targetRange! + if ax == 0: + ## view range needs to be taller than target + dy = 0.5 * (tr.width() / viewRatio - tr.height()) + if dy != 0: + changed[1] = True + viewRange[1] = [self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy] + else: + ## view range needs to be wider than target + dx = 0.5 * (tr.height() * viewRatio - tr.width()) + if dx != 0: + changed[0] = True + viewRange[0] = [self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx] + + changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) and (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)] + self.state['viewRange'] = viewRange + + # emit range change signals + if changed[0]: + self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0])) + if changed[1]: + self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1])) + + if any(changed): + self.sigRangeChanged.emit(self, self.state['viewRange']) + self.update() + + # Inform linked views that the range has changed + for ax in [0, 1]: + if not changed[ax]: + continue + link = self.linkedView(ax) + if link is not None: + link.linkedViewChanged(self, ax) + + self._matrixNeedsUpdate = True + + def updateMatrix(self, changed=None): + ## Make the childGroup's transform match the requested viewRange. + bounds = self.rect() + + vr = self.viewRect() + if vr.height() == 0 or vr.width() == 0: + return + scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height()) + if not self.state['yInverted']: + scale = scale * Point(1, -1) + m = QtGui.QTransform() + + ## First center the viewport at 0 + center = bounds.center() + m.translate(center.x(), center.y()) + + ## Now scale and translate properly + m.scale(scale[0], scale[1]) + st = Point(vr.center()) + m.translate(-st[0], -st[1]) + + self.childGroup.setTransform(m) + + self.sigTransformChanged.emit(self) ## segfaults here: 1 + self._matrixNeedsUpdate = False + + def paint(self, p, opt, widget): + self.checkSceneChange() + + if self.border is not None: + bounds = self.shape() + p.setPen(self.border) + #p.fillRect(bounds, QtGui.QColor(0, 0, 0)) + p.drawPath(bounds) + + #p.setPen(fn.mkPen('r')) + #path = QtGui.QPainterPath() + #path.addRect(self.targetRect()) + #tr = self.mapFromView(path) + #p.drawPath(tr) + + def updateBackground(self): + bg = self.state['background'] + if bg is None: + self.background.hide() + else: + self.background.show() + self.background.setBrush(fn.mkBrush(bg)) + + + def updateViewLists(self): + try: + self.window() + except RuntimeError: ## this view has already been deleted; it will probably be collected shortly. + return + + def cmpViews(a, b): + wins = 100 * cmp(a.window() is self.window(), b.window() is self.window()) + alpha = cmp(a.name, b.name) + return wins + alpha + + ## make a sorted list of all named views + nv = list(ViewBox.NamedViews.values()) + #print "new view list:", nv + sortList(nv, cmpViews) ## see pyqtgraph.python2_3.sortList + + if self in nv: + nv.remove(self) + + self.menu.setViewList(nv) + + for ax in [0,1]: + link = self.state['linkedViews'][ax] + if isinstance(link, basestring): ## axis has not been linked yet; see if it's possible now + for v in nv: + if link == v.name: + self.linkView(ax, v) + #print "New view list:", nv + #print "linked views:", self.state['linkedViews'] + + @staticmethod + def updateAllViewLists(): + #print "Update:", ViewBox.AllViews.keys() + #print "Update:", ViewBox.NamedViews.keys() + for v in ViewBox.AllViews: + v.updateViewLists() + + + @staticmethod + def forgetView(vid, name): + if ViewBox is None: ## can happen as python is shutting down + return + ## Called with ID and name of view (the view itself is no longer available) + for v in list(ViewBox.AllViews.keys()): + if id(v) == vid: + ViewBox.AllViews.pop(v) + break + ViewBox.NamedViews.pop(name, None) + ViewBox.updateAllViewLists() + + @staticmethod + def quit(): + ## called when the application is about to exit. + ## this disables all callbacks, which might otherwise generate errors if invoked during exit. + for k in ViewBox.AllViews: + try: + k.destroyed.disconnect() + except RuntimeError: ## signal is already disconnected. + pass + except TypeError: ## view has already been deleted (?) + pass + + def locate(self, item, timeout=3.0, children=False): + """ + Temporarily display the bounding rect of an item and lines connecting to the center of the view. + This is useful for determining the location of items that may be out of the range of the ViewBox. + if allChildren is True, then the bounding rect of all item's children will be shown instead. + """ + self.clearLocate() + + if item.scene() is not self.scene(): + raise Exception("Item does not share a scene with this ViewBox.") + + c = self.viewRect().center() + if children: + br = self.mapFromItemToView(item, item.childrenBoundingRect()).boundingRect() + else: + br = self.mapFromItemToView(item, item.boundingRect()).boundingRect() + + g = ItemGroup() + g.setParentItem(self.childGroup) + self.locateGroup = g + g.box = QtGui.QGraphicsRectItem(br) + g.box.setParentItem(g) + g.lines = [] + for p in (br.topLeft(), br.bottomLeft(), br.bottomRight(), br.topRight()): + line = QtGui.QGraphicsLineItem(c.x(), c.y(), p.x(), p.y()) + line.setParentItem(g) + g.lines.append(line) + + for item in g.childItems(): + item.setPen(fn.mkPen(color='y', width=3)) + g.setZValue(1000000) + + if children: + g.path = QtGui.QGraphicsPathItem(g.childrenShape()) + else: + g.path = QtGui.QGraphicsPathItem(g.shape()) + g.path.setParentItem(g) + g.path.setPen(fn.mkPen('g')) + g.path.setZValue(100) + + QtCore.QTimer.singleShot(timeout*1000, self.clearLocate) + + def clearLocate(self): + if self.locateGroup is None: + return + self.scene().removeItem(self.locateGroup) + self.locateGroup = None + +from .ViewBoxMenu import ViewBoxMenu diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py b/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py new file mode 100644 index 00000000..5242ecdd --- /dev/null +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py @@ -0,0 +1,278 @@ +from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE +from pyqtgraph.python2_3 import asUnicode +from pyqtgraph.WidgetGroup import WidgetGroup + +if USE_PYSIDE: + from .axisCtrlTemplate_pyside import Ui_Form as AxisCtrlTemplate +else: + from .axisCtrlTemplate_pyqt import Ui_Form as AxisCtrlTemplate + +import weakref + +class ViewBoxMenu(QtGui.QMenu): + def __init__(self, view): + QtGui.QMenu.__init__(self) + + self.view = weakref.ref(view) ## keep weakref to view to avoid circular reference (don't know why, but this prevents the ViewBox from being collected) + self.valid = False ## tells us whether the ui needs to be updated + self.viewMap = weakref.WeakValueDictionary() ## weakrefs to all views listed in the link combos + + self.setTitle("ViewBox options") + self.viewAll = QtGui.QAction("View All", self) + self.viewAll.triggered.connect(self.autoRange) + self.addAction(self.viewAll) + + self.axes = [] + self.ctrl = [] + self.widgetGroups = [] + self.dv = QtGui.QDoubleValidator(self) + for axis in 'XY': + m = QtGui.QMenu() + m.setTitle("%s Axis" % axis) + w = QtGui.QWidget() + ui = AxisCtrlTemplate() + ui.setupUi(w) + a = QtGui.QWidgetAction(self) + a.setDefaultWidget(w) + m.addAction(a) + self.addMenu(m) + self.axes.append(m) + self.ctrl.append(ui) + wg = WidgetGroup(w) + self.widgetGroups.append(w) + + connects = [ + (ui.mouseCheck.toggled, 'MouseToggled'), + (ui.manualRadio.clicked, 'ManualClicked'), + (ui.minText.editingFinished, 'MinTextChanged'), + (ui.maxText.editingFinished, 'MaxTextChanged'), + (ui.autoRadio.clicked, 'AutoClicked'), + (ui.autoPercentSpin.valueChanged, 'AutoSpinChanged'), + (ui.linkCombo.currentIndexChanged, 'LinkComboChanged'), + (ui.autoPanCheck.toggled, 'AutoPanToggled'), + (ui.visibleOnlyCheck.toggled, 'VisibleOnlyToggled') + ] + + for sig, fn in connects: + sig.connect(getattr(self, axis.lower()+fn)) + + self.ctrl[0].invertCheck.hide() ## no invert for x-axis + self.ctrl[1].invertCheck.toggled.connect(self.yInvertToggled) + ## exporting is handled by GraphicsScene now + #self.export = QtGui.QMenu("Export") + #self.setExportMethods(view.exportMethods) + #self.addMenu(self.export) + + self.leftMenu = QtGui.QMenu("Mouse Mode") + group = QtGui.QActionGroup(self) + + # This does not work! QAction _must_ be initialized with a permanent + # object as the parent or else it may be collected prematurely. + #pan = self.leftMenu.addAction("3 button", self.set3ButtonMode) + #zoom = self.leftMenu.addAction("1 button", self.set1ButtonMode) + pan = QtGui.QAction("3 button", self.leftMenu) + zoom = QtGui.QAction("1 button", self.leftMenu) + self.leftMenu.addAction(pan) + self.leftMenu.addAction(zoom) + pan.triggered.connect(self.set3ButtonMode) + zoom.triggered.connect(self.set1ButtonMode) + + pan.setCheckable(True) + zoom.setCheckable(True) + pan.setActionGroup(group) + zoom.setActionGroup(group) + self.mouseModes = [pan, zoom] + self.addMenu(self.leftMenu) + + self.view().sigStateChanged.connect(self.viewStateChanged) + + self.updateState() + + def copy(self): + m = QtGui.QMenu() + for sm in self.subMenus(): + if isinstance(sm, QtGui.QMenu): + m.addMenu(sm) + else: + m.addAction(sm) + m.setTitle(self.title()) + return m + + def subMenus(self): + if not self.valid: + self.updateState() + return [self.viewAll] + self.axes + [self.leftMenu] + + + def setExportMethods(self, methods): + self.exportMethods = methods + self.export.clear() + for opt, fn in methods.items(): + self.export.addAction(opt, self.exportMethod) + + + def viewStateChanged(self): + self.valid = False + if self.ctrl[0].minText.isVisible() or self.ctrl[1].minText.isVisible(): + self.updateState() + + def updateState(self): + ## Something about the viewbox has changed; update the menu GUI + + state = self.view().getState(copy=False) + if state['mouseMode'] == ViewBox.PanMode: + self.mouseModes[0].setChecked(True) + else: + self.mouseModes[1].setChecked(True) + + for i in [0,1]: # x, y + tr = state['targetRange'][i] + self.ctrl[i].minText.setText("%0.5g" % tr[0]) + self.ctrl[i].maxText.setText("%0.5g" % tr[1]) + if state['autoRange'][i] is not False: + self.ctrl[i].autoRadio.setChecked(True) + if state['autoRange'][i] is not True: + self.ctrl[i].autoPercentSpin.setValue(state['autoRange'][i]*100) + else: + self.ctrl[i].manualRadio.setChecked(True) + self.ctrl[i].mouseCheck.setChecked(state['mouseEnabled'][i]) + + ## Update combo to show currently linked view + c = self.ctrl[i].linkCombo + c.blockSignals(True) + try: + view = state['linkedViews'][i] ## will always be string or None + if view is None: + view = '' + + ind = c.findText(view) + + if ind == -1: + ind = 0 + c.setCurrentIndex(ind) + finally: + c.blockSignals(False) + + self.ctrl[i].autoPanCheck.setChecked(state['autoPan'][i]) + self.ctrl[i].visibleOnlyCheck.setChecked(state['autoVisibleOnly'][i]) + + self.ctrl[1].invertCheck.setChecked(state['yInverted']) + self.valid = True + + + def autoRange(self): + self.view().autoRange() ## don't let signal call this directly--it'll add an unwanted argument + + def xMouseToggled(self, b): + self.view().setMouseEnabled(x=b) + + def xManualClicked(self): + self.view().enableAutoRange(ViewBox.XAxis, False) + + def xMinTextChanged(self): + self.ctrl[0].manualRadio.setChecked(True) + self.view().setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0) + + def xMaxTextChanged(self): + self.ctrl[0].manualRadio.setChecked(True) + self.view().setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0) + + def xAutoClicked(self): + val = self.ctrl[0].autoPercentSpin.value() * 0.01 + self.view().enableAutoRange(ViewBox.XAxis, val) + + def xAutoSpinChanged(self, val): + self.ctrl[0].autoRadio.setChecked(True) + self.view().enableAutoRange(ViewBox.XAxis, val*0.01) + + def xLinkComboChanged(self, ind): + self.view().setXLink(str(self.ctrl[0].linkCombo.currentText())) + + def xAutoPanToggled(self, b): + self.view().setAutoPan(x=b) + + def xVisibleOnlyToggled(self, b): + self.view().setAutoVisible(x=b) + + + def yMouseToggled(self, b): + self.view().setMouseEnabled(y=b) + + def yManualClicked(self): + self.view().enableAutoRange(ViewBox.YAxis, False) + + def yMinTextChanged(self): + self.ctrl[1].manualRadio.setChecked(True) + self.view().setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0) + + def yMaxTextChanged(self): + self.ctrl[1].manualRadio.setChecked(True) + self.view().setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0) + + def yAutoClicked(self): + val = self.ctrl[1].autoPercentSpin.value() * 0.01 + self.view().enableAutoRange(ViewBox.YAxis, val) + + def yAutoSpinChanged(self, val): + self.ctrl[1].autoRadio.setChecked(True) + self.view().enableAutoRange(ViewBox.YAxis, val*0.01) + + def yLinkComboChanged(self, ind): + self.view().setYLink(str(self.ctrl[1].linkCombo.currentText())) + + def yAutoPanToggled(self, b): + self.view().setAutoPan(y=b) + + def yVisibleOnlyToggled(self, b): + self.view().setAutoVisible(y=b) + + def yInvertToggled(self, b): + self.view().invertY(b) + + + def exportMethod(self): + act = self.sender() + self.exportMethods[str(act.text())]() + + + def set3ButtonMode(self): + self.view().setLeftButtonAction('pan') + + def set1ButtonMode(self): + self.view().setLeftButtonAction('rect') + + + def setViewList(self, views): + names = [''] + self.viewMap.clear() + + ## generate list of views to show in the link combo + for v in views: + name = v.name + if name is None: ## unnamed views do not show up in the view list (although they are linkable) + continue + names.append(name) + self.viewMap[name] = v + + for i in [0,1]: + c = self.ctrl[i].linkCombo + current = asUnicode(c.currentText()) + c.blockSignals(True) + changed = True + try: + c.clear() + for name in names: + c.addItem(name) + if name == current: + changed = False + c.setCurrentIndex(c.count()-1) + finally: + c.blockSignals(False) + + if changed: + c.setCurrentIndex(0) + c.currentIndexChanged.emit(c.currentIndex()) + +from .ViewBox import ViewBox + + \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/ViewBox/__init__.py b/pyqtgraph/graphicsItems/ViewBox/__init__.py new file mode 100644 index 00000000..685a314d --- /dev/null +++ b/pyqtgraph/graphicsItems/ViewBox/__init__.py @@ -0,0 +1 @@ +from .ViewBox import ViewBox diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui new file mode 100644 index 00000000..297fce75 --- /dev/null +++ b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui @@ -0,0 +1,161 @@ + + + Form + + + + 0 + 0 + 186 + 154 + + + + + 200 + 16777215 + + + + Form + + + + 0 + + + 0 + + + + + Link Axis: + + + + + + + <html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html> + + + QComboBox::AdjustToContents + + + + + + + true + + + <html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html> + + + % + + + 1 + + + 100 + + + 1 + + + 100 + + + + + + + <html><head/><body><p>Automatically resize this axis whenever the displayed data is changed.</p></body></html> + + + Auto + + + true + + + + + + + <html><head/><body><p>Set the range for this axis manually. This disables automatic scaling. </p></body></html> + + + Manual + + + + + + + <html><head/><body><p>Minimum value to display for this axis.</p></body></html> + + + 0 + + + + + + + <html><head/><body><p>Maximum value to display for this axis.</p></body></html> + + + 0 + + + + + + + <html><head/><body><p>Inverts the display of this axis. (+y points downward instead of upward)</p></body></html> + + + Invert Axis + + + + + + + <html><head/><body><p>Enables mouse interaction (panning, scaling) for this axis.</p></body></html> + + + Mouse Enabled + + + true + + + + + + + <html><head/><body><p>When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.</p></body></html> + + + Visible Data Only + + + + + + + <html><head/><body><p>When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.</p></body></html> + + + Auto Pan Only + + + + + + + + diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py new file mode 100644 index 00000000..db14033e --- /dev/null +++ b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './graphicsItems/ViewBox/axisCtrlTemplate.ui' +# +# Created: Sun Sep 9 14:41:31 2012 +# by: PyQt4 UI code generator 4.9.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(186, 154) + Form.setMaximumSize(QtCore.QSize(200, 16777215)) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.label = QtGui.QLabel(Form) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout.addWidget(self.label, 7, 0, 1, 2) + self.linkCombo = QtGui.QComboBox(Form) + self.linkCombo.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) + self.linkCombo.setObjectName(_fromUtf8("linkCombo")) + self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) + self.autoPercentSpin = QtGui.QSpinBox(Form) + self.autoPercentSpin.setEnabled(True) + self.autoPercentSpin.setMinimum(1) + self.autoPercentSpin.setMaximum(100) + self.autoPercentSpin.setSingleStep(1) + self.autoPercentSpin.setProperty("value", 100) + self.autoPercentSpin.setObjectName(_fromUtf8("autoPercentSpin")) + self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) + self.autoRadio = QtGui.QRadioButton(Form) + self.autoRadio.setChecked(True) + self.autoRadio.setObjectName(_fromUtf8("autoRadio")) + self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) + self.manualRadio = QtGui.QRadioButton(Form) + self.manualRadio.setObjectName(_fromUtf8("manualRadio")) + self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) + self.minText = QtGui.QLineEdit(Form) + self.minText.setObjectName(_fromUtf8("minText")) + self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) + self.maxText = QtGui.QLineEdit(Form) + self.maxText.setObjectName(_fromUtf8("maxText")) + self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) + self.invertCheck = QtGui.QCheckBox(Form) + self.invertCheck.setObjectName(_fromUtf8("invertCheck")) + self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) + self.mouseCheck = QtGui.QCheckBox(Form) + self.mouseCheck.setChecked(True) + self.mouseCheck.setObjectName(_fromUtf8("mouseCheck")) + self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) + self.visibleOnlyCheck = QtGui.QCheckBox(Form) + self.visibleOnlyCheck.setObjectName(_fromUtf8("visibleOnlyCheck")) + self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) + self.autoPanCheck = QtGui.QCheckBox(Form) + self.autoPanCheck.setObjectName(_fromUtf8("autoPanCheck")) + self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8)) + self.linkCombo.setToolTip(QtGui.QApplication.translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPercentSpin.setToolTip(QtGui.QApplication.translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRadio.setToolTip(QtGui.QApplication.translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.manualRadio.setToolTip(QtGui.QApplication.translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

", None, QtGui.QApplication.UnicodeUTF8)) + self.manualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) + self.minText.setToolTip(QtGui.QApplication.translate("Form", "

Minimum value to display for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.minText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) + self.maxText.setToolTip(QtGui.QApplication.translate("Form", "

Maximum value to display for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.maxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) + self.invertCheck.setToolTip(QtGui.QApplication.translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

", None, QtGui.QApplication.UnicodeUTF8)) + self.invertCheck.setText(QtGui.QApplication.translate("Form", "Invert Axis", None, QtGui.QApplication.UnicodeUTF8)) + self.mouseCheck.setToolTip(QtGui.QApplication.translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.mouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse Enabled", None, QtGui.QApplication.UnicodeUTF8)) + self.visibleOnlyCheck.setToolTip(QtGui.QApplication.translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.visibleOnlyCheck.setText(QtGui.QApplication.translate("Form", "Visible Data Only", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPanCheck.setToolTip(QtGui.QApplication.translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPanCheck.setText(QtGui.QApplication.translate("Form", "Auto Pan Only", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py new file mode 100644 index 00000000..18510bc2 --- /dev/null +++ b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './graphicsItems/ViewBox/axisCtrlTemplate.ui' +# +# Created: Sun Sep 9 14:41:32 2012 +# by: pyside-uic 0.2.13 running on PySide 1.1.0 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(186, 154) + Form.setMaximumSize(QtCore.QSize(200, 16777215)) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.label = QtGui.QLabel(Form) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 7, 0, 1, 2) + self.linkCombo = QtGui.QComboBox(Form) + self.linkCombo.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) + self.linkCombo.setObjectName("linkCombo") + self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) + self.autoPercentSpin = QtGui.QSpinBox(Form) + self.autoPercentSpin.setEnabled(True) + self.autoPercentSpin.setMinimum(1) + self.autoPercentSpin.setMaximum(100) + self.autoPercentSpin.setSingleStep(1) + self.autoPercentSpin.setProperty("value", 100) + self.autoPercentSpin.setObjectName("autoPercentSpin") + self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) + self.autoRadio = QtGui.QRadioButton(Form) + self.autoRadio.setChecked(True) + self.autoRadio.setObjectName("autoRadio") + self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) + self.manualRadio = QtGui.QRadioButton(Form) + self.manualRadio.setObjectName("manualRadio") + self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) + self.minText = QtGui.QLineEdit(Form) + self.minText.setObjectName("minText") + self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) + self.maxText = QtGui.QLineEdit(Form) + self.maxText.setObjectName("maxText") + self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) + self.invertCheck = QtGui.QCheckBox(Form) + self.invertCheck.setObjectName("invertCheck") + self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) + self.mouseCheck = QtGui.QCheckBox(Form) + self.mouseCheck.setChecked(True) + self.mouseCheck.setObjectName("mouseCheck") + self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) + self.visibleOnlyCheck = QtGui.QCheckBox(Form) + self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") + self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) + self.autoPanCheck = QtGui.QCheckBox(Form) + self.autoPanCheck.setObjectName("autoPanCheck") + self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8)) + self.linkCombo.setToolTip(QtGui.QApplication.translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPercentSpin.setToolTip(QtGui.QApplication.translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRadio.setToolTip(QtGui.QApplication.translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.manualRadio.setToolTip(QtGui.QApplication.translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

", None, QtGui.QApplication.UnicodeUTF8)) + self.manualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) + self.minText.setToolTip(QtGui.QApplication.translate("Form", "

Minimum value to display for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.minText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) + self.maxText.setToolTip(QtGui.QApplication.translate("Form", "

Maximum value to display for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.maxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) + self.invertCheck.setToolTip(QtGui.QApplication.translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

", None, QtGui.QApplication.UnicodeUTF8)) + self.invertCheck.setText(QtGui.QApplication.translate("Form", "Invert Axis", None, QtGui.QApplication.UnicodeUTF8)) + self.mouseCheck.setToolTip(QtGui.QApplication.translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.mouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse Enabled", None, QtGui.QApplication.UnicodeUTF8)) + self.visibleOnlyCheck.setToolTip(QtGui.QApplication.translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.visibleOnlyCheck.setText(QtGui.QApplication.translate("Form", "Visible Data Only", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPanCheck.setToolTip(QtGui.QApplication.translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPanCheck.setText(QtGui.QApplication.translate("Form", "Auto Pan Only", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/pyqtgraph/graphicsItems/__init__.py b/pyqtgraph/graphicsItems/__init__.py new file mode 100644 index 00000000..8e411816 --- /dev/null +++ b/pyqtgraph/graphicsItems/__init__.py @@ -0,0 +1,21 @@ +### just import everything from sub-modules + +#import os + +#d = os.path.split(__file__)[0] +#files = [] +#for f in os.listdir(d): + #if os.path.isdir(os.path.join(d, f)): + #files.append(f) + #elif f[-3:] == '.py' and f != '__init__.py': + #files.append(f[:-3]) + +#for modName in files: + #mod = __import__(modName, globals(), locals(), fromlist=['*']) + #if hasattr(mod, '__all__'): + #names = mod.__all__ + #else: + #names = [n for n in dir(mod) if n[0] != '_'] + #for k in names: + ##print modName, k + #globals()[k] = getattr(mod, k) diff --git a/pyqtgraph/graphicsItems/tests/ViewBox.py b/pyqtgraph/graphicsItems/tests/ViewBox.py new file mode 100644 index 00000000..91d9b617 --- /dev/null +++ b/pyqtgraph/graphicsItems/tests/ViewBox.py @@ -0,0 +1,95 @@ +""" +ViewBox test cases: + +* call setRange then resize; requested range must be fully visible +* lockAspect works correctly for arbitrary aspect ratio +* autoRange works correctly with aspect locked +* call setRange with aspect locked, then resize +* AutoRange with all the bells and whistles + * item moves / changes transformation / changes bounds + * pan only + * fractional range + + +""" + +import pyqtgraph as pg +app = pg.mkQApp() + +imgData = pg.np.zeros((10, 10)) +imgData[0] = 3 +imgData[-1] = 3 +imgData[:,0] = 3 +imgData[:,-1] = 3 + +def testLinkWithAspectLock(): + global win, vb + win = pg.GraphicsWindow() + vb = win.addViewBox(name="image view") + vb.setAspectLocked() + vb.enableAutoRange(x=False, y=False) + p1 = win.addPlot(name="plot 1") + p2 = win.addPlot(name="plot 2", row=1, col=0) + win.ci.layout.setRowFixedHeight(1, 150) + win.ci.layout.setColumnFixedWidth(1, 150) + + def viewsMatch(): + r0 = pg.np.array(vb.viewRange()) + r1 = pg.np.array(p1.vb.viewRange()[1]) + r2 = pg.np.array(p2.vb.viewRange()[1]) + match = (abs(r0[1]-r1) <= (abs(r1) * 0.001)).all() and (abs(r0[0]-r2) <= (abs(r2) * 0.001)).all() + return match + + p1.setYLink(vb) + p2.setXLink(vb) + print "link views match:", viewsMatch() + win.show() + print "show views match:", viewsMatch() + img = pg.ImageItem(imgData) + vb.addItem(img) + vb.autoRange() + p1.plot(x=imgData.sum(axis=0), y=range(10)) + p2.plot(x=range(10), y=imgData.sum(axis=1)) + print "add items views match:", viewsMatch() + #p1.setAspectLocked() + #grid = pg.GridItem() + #vb.addItem(grid) + pg.QtGui.QApplication.processEvents() + pg.QtGui.QApplication.processEvents() + #win.resize(801, 600) + +def testAspectLock(): + global win, vb + win = pg.GraphicsWindow() + vb = win.addViewBox(name="image view") + vb.setAspectLocked() + img = pg.ImageItem(imgData) + vb.addItem(img) + + +#app.processEvents() +#print "init views match:", viewsMatch() +#p2.setYRange(-300, 300) +#print "setRange views match:", viewsMatch() +#app.processEvents() +#print "setRange views match (after update):", viewsMatch() + +#print "--lock aspect--" +#p1.setAspectLocked(True) +#print "lockAspect views match:", viewsMatch() +#p2.setYRange(-200, 200) +#print "setRange views match:", viewsMatch() +#app.processEvents() +#print "setRange views match (after update):", viewsMatch() + +#win.resize(100, 600) +#app.processEvents() +#vb.setRange(xRange=[-10, 10], padding=0) +#app.processEvents() +#win.resize(600, 100) +#app.processEvents() +#print vb.viewRange() + + +if __name__ == '__main__': + testLinkWithAspectLock() diff --git a/pyqtgraph/graphicsWindows.py b/pyqtgraph/graphicsWindows.py new file mode 100644 index 00000000..6e7d6305 --- /dev/null +++ b/pyqtgraph/graphicsWindows.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +""" +graphicsWindows.py - Convenience classes which create a new window with PlotWidget or ImageView. +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from .Qt import QtCore, QtGui +from .widgets.PlotWidget import * +from .imageview import * +from .widgets.GraphicsLayoutWidget import GraphicsLayoutWidget +from .widgets.GraphicsView import GraphicsView +QAPP = None + +def mkQApp(): + if QtGui.QApplication.instance() is None: + global QAPP + QAPP = QtGui.QApplication([]) + + +class GraphicsWindow(GraphicsLayoutWidget): + def __init__(self, title=None, size=(800,600), **kargs): + mkQApp() + #self.win = QtGui.QMainWindow() + GraphicsLayoutWidget.__init__(self, **kargs) + #self.win.setCentralWidget(self) + self.resize(*size) + if title is not None: + self.setWindowTitle(title) + self.show() + + +class TabWindow(QtGui.QMainWindow): + def __init__(self, title=None, size=(800,600)): + mkQApp() + QtGui.QMainWindow.__init__(self) + self.resize(*size) + self.cw = QtGui.QTabWidget() + self.setCentralWidget(self.cw) + if title is not None: + self.setWindowTitle(title) + self.show() + + def __getattr__(self, attr): + if hasattr(self.cw, attr): + return getattr(self.cw, attr) + else: + raise NameError(attr) + + +class PlotWindow(PlotWidget): + def __init__(self, title=None, **kargs): + mkQApp() + self.win = QtGui.QMainWindow() + PlotWidget.__init__(self, **kargs) + self.win.setCentralWidget(self) + for m in ['resize']: + setattr(self, m, getattr(self.win, m)) + if title is not None: + self.win.setWindowTitle(title) + self.win.show() + + +class ImageWindow(ImageView): + def __init__(self, *args, **kargs): + mkQApp() + self.win = QtGui.QMainWindow() + self.win.resize(800,600) + if 'title' in kargs: + self.win.setWindowTitle(kargs['title']) + del kargs['title'] + ImageView.__init__(self, self.win) + if len(args) > 0 or len(kargs) > 0: + self.setImage(*args, **kargs) + self.win.setCentralWidget(self) + for m in ['resize']: + setattr(self, m, getattr(self.win, m)) + #for m in ['setImage', 'autoRange', 'addItem', 'removeItem', 'blackLevel', 'whiteLevel', 'imageItem']: + #setattr(self, m, getattr(self.cw, m)) + self.win.show() diff --git a/pyqtgraph/imageview/ImageView.py b/pyqtgraph/imageview/ImageView.py new file mode 100644 index 00000000..77f34419 --- /dev/null +++ b/pyqtgraph/imageview/ImageView.py @@ -0,0 +1,645 @@ +# -*- coding: utf-8 -*- +""" +ImageView.py - Widget for basic image dispay and analysis +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +Widget used for displaying 2D or 3D data. Features: + - float or int (including 16-bit int) image display via ImageItem + - zoom/pan via GraphicsView + - black/white level controls + - time slider for 3D data sets + - ROI plotting + - Image normalization through a variety of methods +""" +from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE + +if USE_PYSIDE: + from .ImageViewTemplate_pyside import * +else: + from .ImageViewTemplate_pyqt import * + +from pyqtgraph.graphicsItems.ImageItem import * +from pyqtgraph.graphicsItems.ROI import * +from pyqtgraph.graphicsItems.LinearRegionItem import * +from pyqtgraph.graphicsItems.InfiniteLine import * +from pyqtgraph.graphicsItems.ViewBox import * +#from widgets import ROI +import sys +#from numpy import ndarray +import pyqtgraph.ptime as ptime +import numpy as np +import pyqtgraph.debug as debug + +from pyqtgraph.SignalProxy import SignalProxy + +#try: + #import pyqtgraph.metaarray as metaarray + #HAVE_METAARRAY = True +#except: + #HAVE_METAARRAY = False + + +class PlotROI(ROI): + def __init__(self, size): + ROI.__init__(self, pos=[0,0], size=size) #, scaleSnap=True, translateSnap=True) + self.addScaleHandle([1, 1], [0, 0]) + self.addRotateHandle([0, 0], [0.5, 0.5]) + + +class ImageView(QtGui.QWidget): + """ + Widget used for display and analysis of image data. + Implements many features: + + * Displays 2D and 3D image data. For 3D data, a z-axis + slider is displayed allowing the user to select which frame is displayed. + * Displays histogram of image data with movable region defining the dark/light levels + * Editable gradient provides a color lookup table + * Frame slider may also be moved using left/right arrow keys as well as pgup, pgdn, home, and end. + * Basic analysis features including: + + * ROI and embedded plot for measuring image values across frames + * Image normalization / background subtraction + + Basic Usage:: + + imv = pg.ImageView() + imv.show() + imv.setImage(data) + """ + sigTimeChanged = QtCore.Signal(object, object) + sigProcessingChanged = QtCore.Signal(object) + + def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, *args): + """ + By default, this class creates an :class:`ImageItem ` to display image data + and a :class:`ViewBox ` to contain the ImageItem. Custom items may be given instead + by specifying the *view* and/or *imageItem* arguments. + """ + QtGui.QWidget.__init__(self, parent, *args) + self.levelMax = 4096 + self.levelMin = 0 + self.name = name + self.image = None + self.axes = {} + self.imageDisp = None + self.ui = Ui_Form() + self.ui.setupUi(self) + self.scene = self.ui.graphicsView.scene() + + self.ignoreTimeLine = False + + if view is None: + self.view = ViewBox() + else: + self.view = view + self.ui.graphicsView.setCentralItem(self.view) + self.view.setAspectLocked(True) + self.view.invertY() + + if imageItem is None: + self.imageItem = ImageItem() + else: + self.imageItem = imageItem + self.view.addItem(self.imageItem) + self.currentIndex = 0 + + self.ui.histogram.setImageItem(self.imageItem) + + self.ui.normGroup.hide() + + self.roi = PlotROI(10) + self.roi.setZValue(20) + self.view.addItem(self.roi) + self.roi.hide() + self.normRoi = PlotROI(10) + self.normRoi.setPen(QtGui.QPen(QtGui.QColor(255,255,0))) + self.normRoi.setZValue(20) + self.view.addItem(self.normRoi) + self.normRoi.hide() + self.roiCurve = self.ui.roiPlot.plot() + self.timeLine = InfiniteLine(0, movable=True) + self.timeLine.setPen(QtGui.QPen(QtGui.QColor(255, 255, 0, 200))) + self.timeLine.setZValue(1) + self.ui.roiPlot.addItem(self.timeLine) + self.ui.splitter.setSizes([self.height()-35, 35]) + self.ui.roiPlot.hideAxis('left') + + self.keysPressed = {} + self.playTimer = QtCore.QTimer() + self.playRate = 0 + self.lastPlayTime = 0 + + self.normRgn = LinearRegionItem() + self.normRgn.setZValue(0) + self.ui.roiPlot.addItem(self.normRgn) + self.normRgn.hide() + + ## wrap functions from view box + for fn in ['addItem', 'removeItem']: + setattr(self, fn, getattr(self.view, fn)) + + ## wrap functions from histogram + for fn in ['setHistogramRange', 'autoHistogramRange', 'getLookupTable', 'getLevels']: + setattr(self, fn, getattr(self.ui.histogram, fn)) + + self.timeLine.sigPositionChanged.connect(self.timeLineChanged) + self.ui.roiBtn.clicked.connect(self.roiClicked) + self.roi.sigRegionChanged.connect(self.roiChanged) + self.ui.normBtn.toggled.connect(self.normToggled) + self.ui.normDivideRadio.clicked.connect(self.normRadioChanged) + self.ui.normSubtractRadio.clicked.connect(self.normRadioChanged) + self.ui.normOffRadio.clicked.connect(self.normRadioChanged) + self.ui.normROICheck.clicked.connect(self.updateNorm) + self.ui.normFrameCheck.clicked.connect(self.updateNorm) + self.ui.normTimeRangeCheck.clicked.connect(self.updateNorm) + self.playTimer.timeout.connect(self.timeout) + + self.normProxy = SignalProxy(self.normRgn.sigRegionChanged, slot=self.updateNorm) + self.normRoi.sigRegionChangeFinished.connect(self.updateNorm) + + self.ui.roiPlot.registerPlot(self.name + '_ROI') + + self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown] + + self.roiClicked() ## initialize roi plot to correct shape / visibility + + def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None, transform=None, autoHistogramRange=True): + """ + Set the image to be displayed in the widget. + + ================== ======================================================================= + **Arguments:** + img (numpy array) the image to be displayed. + xvals (numpy array) 1D array of z-axis values corresponding to the third axis + in a 3D image. For video, this array should contain the time of each frame. + autoRange (bool) whether to scale/pan the view to fit the image. + autoLevels (bool) whether to update the white/black levels to fit the image. + levels (min, max); the white and black level values to use. + axes Dictionary indicating the interpretation for each axis. + This is only needed to override the default guess. Format is:: + + {'t':0, 'x':1, 'y':2, 'c':3}; + + pos Change the position of the displayed image + scale Change the scale of the displayed image + transform Set the transform of the displayed image. This option overrides *pos* + and *scale*. + autoHistogramRange If True, the histogram y-range is automatically scaled to fit the + image data. + ================== ======================================================================= + """ + prof = debug.Profiler('ImageView.setImage', disabled=True) + + if hasattr(img, 'implements') and img.implements('MetaArray'): + img = img.asarray() + + if not isinstance(img, np.ndarray): + raise Exception("Image must be specified as ndarray.") + self.image = img + + if xvals is not None: + self.tVals = xvals + elif hasattr(img, 'xvals'): + try: + self.tVals = img.xvals(0) + except: + self.tVals = np.arange(img.shape[0]) + else: + self.tVals = np.arange(img.shape[0]) + + prof.mark('1') + + if axes is None: + if img.ndim == 2: + self.axes = {'t': None, 'x': 0, 'y': 1, 'c': None} + elif img.ndim == 3: + if img.shape[2] <= 4: + self.axes = {'t': None, 'x': 0, 'y': 1, 'c': 2} + else: + self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None} + elif img.ndim == 4: + self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3} + else: + raise Exception("Can not interpret image with dimensions %s" % (str(img.shape))) + elif isinstance(axes, dict): + self.axes = axes.copy() + elif isinstance(axes, list) or isinstance(axes, tuple): + self.axes = {} + for i in range(len(axes)): + self.axes[axes[i]] = i + else: + raise Exception("Can not interpret axis specification %s. Must be like {'t': 2, 'x': 0, 'y': 1} or ('t', 'x', 'y', 'c')" % (str(axes))) + + for x in ['t', 'x', 'y', 'c']: + self.axes[x] = self.axes.get(x, None) + prof.mark('2') + + self.imageDisp = None + + + prof.mark('3') + + self.currentIndex = 0 + self.updateImage(autoHistogramRange=autoHistogramRange) + if levels is None and autoLevels: + self.autoLevels() + if levels is not None: ## this does nothing since getProcessedImage sets these values again. + self.setLevels(*levels) + + if self.ui.roiBtn.isChecked(): + self.roiChanged() + prof.mark('4') + + + if self.axes['t'] is not None: + #self.ui.roiPlot.show() + self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max()) + self.timeLine.setValue(0) + #self.ui.roiPlot.setMouseEnabled(False, False) + if len(self.tVals) > 1: + start = self.tVals.min() + stop = self.tVals.max() + abs(self.tVals[-1] - self.tVals[0]) * 0.02 + elif len(self.tVals) == 1: + start = self.tVals[0] - 0.5 + stop = self.tVals[0] + 0.5 + else: + start = 0 + stop = 1 + for s in [self.timeLine, self.normRgn]: + s.setBounds([start, stop]) + #else: + #self.ui.roiPlot.hide() + prof.mark('5') + + self.imageItem.resetTransform() + if scale is not None: + self.imageItem.scale(*scale) + if pos is not None: + self.imageItem.setPos(*pos) + if transform is not None: + self.imageItem.setTransform(transform) + prof.mark('6') + + if autoRange: + self.autoRange() + self.roiClicked() + prof.mark('7') + prof.finish() + + + def play(self, rate): + """Begin automatically stepping frames forward at the given rate (in fps). + This can also be accessed by pressing the spacebar.""" + #print "play:", rate + self.playRate = rate + if rate == 0: + self.playTimer.stop() + return + + self.lastPlayTime = ptime.time() + if not self.playTimer.isActive(): + self.playTimer.start(16) + + def autoLevels(self): + """Set the min/max intensity levels automatically to match the image data.""" + self.setLevels(self.levelMin, self.levelMax) + + def setLevels(self, min, max): + """Set the min/max (bright and dark) levels.""" + self.ui.histogram.setLevels(min, max) + + def autoRange(self): + """Auto scale and pan the view around the image.""" + image = self.getProcessedImage() + self.view.autoRange() + + def getProcessedImage(self): + """Returns the image data after it has been processed by any normalization options in use. + This method also sets the attributes self.levelMin and self.levelMax + to indicate the range of data in the image.""" + if self.imageDisp is None: + image = self.normalize(self.image) + self.imageDisp = image + self.levelMin, self.levelMax = list(map(float, ImageView.quickMinMax(self.imageDisp))) + + return self.imageDisp + + + def close(self): + """Closes the widget nicely, making sure to clear the graphics scene and release memory.""" + self.ui.roiPlot.close() + self.ui.graphicsView.close() + self.scene.clear() + del self.image + del self.imageDisp + self.setParent(None) + + def keyPressEvent(self, ev): + #print ev.key() + if ev.key() == QtCore.Qt.Key_Space: + if self.playRate == 0: + fps = (self.getProcessedImage().shape[0]-1) / (self.tVals[-1] - self.tVals[0]) + self.play(fps) + #print fps + else: + self.play(0) + ev.accept() + elif ev.key() == QtCore.Qt.Key_Home: + self.setCurrentIndex(0) + self.play(0) + ev.accept() + elif ev.key() == QtCore.Qt.Key_End: + self.setCurrentIndex(self.getProcessedImage().shape[0]-1) + self.play(0) + ev.accept() + elif ev.key() in self.noRepeatKeys: + ev.accept() + if ev.isAutoRepeat(): + return + self.keysPressed[ev.key()] = 1 + self.evalKeyState() + else: + QtGui.QWidget.keyPressEvent(self, ev) + + def keyReleaseEvent(self, ev): + if ev.key() in [QtCore.Qt.Key_Space, QtCore.Qt.Key_Home, QtCore.Qt.Key_End]: + ev.accept() + elif ev.key() in self.noRepeatKeys: + ev.accept() + if ev.isAutoRepeat(): + return + try: + del self.keysPressed[ev.key()] + except: + self.keysPressed = {} + self.evalKeyState() + else: + QtGui.QWidget.keyReleaseEvent(self, ev) + + + def evalKeyState(self): + if len(self.keysPressed) == 1: + key = list(self.keysPressed.keys())[0] + if key == QtCore.Qt.Key_Right: + self.play(20) + self.jumpFrames(1) + self.lastPlayTime = ptime.time() + 0.2 ## 2ms wait before start + ## This happens *after* jumpFrames, since it might take longer than 2ms + elif key == QtCore.Qt.Key_Left: + self.play(-20) + self.jumpFrames(-1) + self.lastPlayTime = ptime.time() + 0.2 + elif key == QtCore.Qt.Key_Up: + self.play(-100) + elif key == QtCore.Qt.Key_Down: + self.play(100) + elif key == QtCore.Qt.Key_PageUp: + self.play(-1000) + elif key == QtCore.Qt.Key_PageDown: + self.play(1000) + else: + self.play(0) + + + def timeout(self): + now = ptime.time() + dt = now - self.lastPlayTime + if dt < 0: + return + n = int(self.playRate * dt) + #print n, dt + if n != 0: + #print n, dt, self.lastPlayTime + self.lastPlayTime += (float(n)/self.playRate) + if self.currentIndex+n > self.image.shape[0]: + self.play(0) + self.jumpFrames(n) + + def setCurrentIndex(self, ind): + """Set the currently displayed frame index.""" + self.currentIndex = np.clip(ind, 0, self.getProcessedImage().shape[0]-1) + self.updateImage() + self.ignoreTimeLine = True + self.timeLine.setValue(self.tVals[self.currentIndex]) + self.ignoreTimeLine = False + + def jumpFrames(self, n): + """Move video frame ahead n frames (may be negative)""" + if self.axes['t'] is not None: + self.setCurrentIndex(self.currentIndex + n) + + def normRadioChanged(self): + self.imageDisp = None + self.updateImage() + self.autoLevels() + self.roiChanged() + self.sigProcessingChanged.emit(self) + + + def updateNorm(self): + if self.ui.normTimeRangeCheck.isChecked(): + #print "show!" + self.normRgn.show() + else: + self.normRgn.hide() + + if self.ui.normROICheck.isChecked(): + #print "show!" + self.normRoi.show() + else: + self.normRoi.hide() + + if not self.ui.normOffRadio.isChecked(): + self.imageDisp = None + self.updateImage() + self.autoLevels() + self.roiChanged() + self.sigProcessingChanged.emit(self) + + def normToggled(self, b): + self.ui.normGroup.setVisible(b) + self.normRoi.setVisible(b and self.ui.normROICheck.isChecked()) + self.normRgn.setVisible(b and self.ui.normTimeRangeCheck.isChecked()) + + def hasTimeAxis(self): + return 't' in self.axes and self.axes['t'] is not None + + def roiClicked(self): + showRoiPlot = False + if self.ui.roiBtn.isChecked(): + showRoiPlot = True + self.roi.show() + #self.ui.roiPlot.show() + self.ui.roiPlot.setMouseEnabled(True, True) + self.ui.splitter.setSizes([self.height()*0.6, self.height()*0.4]) + self.roiCurve.show() + self.roiChanged() + self.ui.roiPlot.showAxis('left') + else: + self.roi.hide() + self.ui.roiPlot.setMouseEnabled(False, False) + self.roiCurve.hide() + self.ui.roiPlot.hideAxis('left') + + if self.hasTimeAxis(): + showRoiPlot = True + mn = self.tVals.min() + mx = self.tVals.max() + self.ui.roiPlot.setXRange(mn, mx, padding=0.01) + self.timeLine.show() + self.timeLine.setBounds([mn, mx]) + self.ui.roiPlot.show() + if not self.ui.roiBtn.isChecked(): + self.ui.splitter.setSizes([self.height()-35, 35]) + else: + self.timeLine.hide() + #self.ui.roiPlot.hide() + + self.ui.roiPlot.setVisible(showRoiPlot) + + def roiChanged(self): + if self.image is None: + return + + image = self.getProcessedImage() + if image.ndim == 2: + axes = (0, 1) + elif image.ndim == 3: + axes = (1, 2) + else: + return + data, coords = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes, returnMappedCoords=True) + if data is not None: + while data.ndim > 1: + data = data.mean(axis=1) + if image.ndim == 3: + self.roiCurve.setData(y=data, x=self.tVals) + else: + while coords.ndim > 2: + coords = coords[:,:,0] + coords = coords - coords[:,0,np.newaxis] + xvals = (coords**2).sum(axis=0) ** 0.5 + self.roiCurve.setData(y=data, x=xvals) + + #self.ui.roiPlot.replot() + + + @staticmethod + def quickMinMax(data): + while data.size > 1e6: + ax = np.argmax(data.shape) + sl = [slice(None)] * data.ndim + sl[ax] = slice(None, None, 2) + data = data[sl] + return data.min(), data.max() + + def normalize(self, image): + + if self.ui.normOffRadio.isChecked(): + return image + + div = self.ui.normDivideRadio.isChecked() + norm = image.view(np.ndarray).copy() + #if div: + #norm = ones(image.shape) + #else: + #norm = zeros(image.shape) + if div: + norm = norm.astype(np.float32) + + if self.ui.normTimeRangeCheck.isChecked() and image.ndim == 3: + (sind, start) = self.timeIndex(self.normRgn.lines[0]) + (eind, end) = self.timeIndex(self.normRgn.lines[1]) + #print start, end, sind, eind + n = image[sind:eind+1].mean(axis=0) + n.shape = (1,) + n.shape + if div: + norm /= n + else: + norm -= n + + if self.ui.normFrameCheck.isChecked() and image.ndim == 3: + n = image.mean(axis=1).mean(axis=1) + n.shape = n.shape + (1, 1) + if div: + norm /= n + else: + norm -= n + + if self.ui.normROICheck.isChecked() and image.ndim == 3: + n = self.normRoi.getArrayRegion(norm, self.imageItem, (1, 2)).mean(axis=1).mean(axis=1) + n = n[:,np.newaxis,np.newaxis] + #print start, end, sind, eind + if div: + norm /= n + else: + norm -= n + + return norm + + def timeLineChanged(self): + #(ind, time) = self.timeIndex(self.ui.timeSlider) + if self.ignoreTimeLine: + return + self.play(0) + (ind, time) = self.timeIndex(self.timeLine) + if ind != self.currentIndex: + self.currentIndex = ind + self.updateImage() + #self.timeLine.setPos(time) + #self.emit(QtCore.SIGNAL('timeChanged'), ind, time) + self.sigTimeChanged.emit(ind, time) + + def updateImage(self, autoHistogramRange=True): + ## Redraw image on screen + if self.image is None: + return + + image = self.getProcessedImage() + + if autoHistogramRange: + self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax) + if self.axes['t'] is None: + self.imageItem.updateImage(image) + else: + self.ui.roiPlot.show() + self.imageItem.updateImage(image[self.currentIndex]) + + + def timeIndex(self, slider): + ## Return the time and frame index indicated by a slider + if self.image is None: + return (0,0) + + t = slider.value() + + xv = self.tVals + if xv is None: + ind = int(t) + else: + if len(xv) < 2: + return (0,0) + totTime = xv[-1] + (xv[-1]-xv[-2]) + inds = np.argwhere(xv < t) + if len(inds) < 1: + return (0,t) + ind = inds[-1,0] + return ind, t + + def getView(self): + """Return the ViewBox (or other compatible object) which displays the ImageItem""" + return self.view + + def getImageItem(self): + """Return the ImageItem for this ImageView.""" + return self.imageItem + + def getRoiPlot(self): + """Return the ROI PlotWidget for this ImageView""" + return self.ui.roiPlot + + def getHistogramWidget(self): + """Return the HistogramLUTWidget for this ImageView""" + return self.ui.histogram diff --git a/pyqtgraph/imageview/ImageViewTemplate.ui b/pyqtgraph/imageview/ImageViewTemplate.ui new file mode 100644 index 00000000..497c0c59 --- /dev/null +++ b/pyqtgraph/imageview/ImageViewTemplate.ui @@ -0,0 +1,252 @@ + + + Form + + + + 0 + 0 + 726 + 588 + + + + Form + + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + 0 + + + + + + + + + + + + 0 + 1 + + + + ROI + + + true + + + + + + + + 0 + 1 + + + + Norm + + + true + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + + + + + Normalization + + + + 0 + + + 0 + + + + + Subtract + + + + + + + Divide + + + false + + + + + + + + 75 + true + + + + Operation: + + + + + + + + 75 + true + + + + Mean: + + + + + + + + 75 + true + + + + Blur: + + + + + + + ROI + + + + + + + + + + X + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Y + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + T + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Off + + + true + + + + + + + Time range + + + + + + + Frame + + + + + + + + + + + + + + PlotWidget + QWidget +
pyqtgraph.widgets.PlotWidget
+ 1 +
+ + GraphicsView + QGraphicsView +
pyqtgraph.widgets.GraphicsView
+
+ + HistogramLUTWidget + QGraphicsView +
pyqtgraph.widgets.HistogramLUTWidget
+
+
+ + +
diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyqt.py b/pyqtgraph/imageview/ImageViewTemplate_pyqt.py new file mode 100644 index 00000000..e6423276 --- /dev/null +++ b/pyqtgraph/imageview/ImageViewTemplate_pyqt.py @@ -0,0 +1,160 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './imageview/ImageViewTemplate.ui' +# +# Created: Sun Sep 9 14:41:30 2012 +# by: PyQt4 UI code generator 4.9.1 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(726, 588) + self.gridLayout_3 = QtGui.QGridLayout(Form) + self.gridLayout_3.setMargin(0) + self.gridLayout_3.setSpacing(0) + self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName(_fromUtf8("splitter")) + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) + self.gridLayout = QtGui.QGridLayout(self.layoutWidget) + self.gridLayout.setSpacing(0) + self.gridLayout.setMargin(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.graphicsView = GraphicsView(self.layoutWidget) + self.graphicsView.setObjectName(_fromUtf8("graphicsView")) + self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) + self.histogram = HistogramLUTWidget(self.layoutWidget) + self.histogram.setObjectName(_fromUtf8("histogram")) + self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) + self.roiBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) + self.roiBtn.setSizePolicy(sizePolicy) + self.roiBtn.setCheckable(True) + self.roiBtn.setObjectName(_fromUtf8("roiBtn")) + self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) + self.normBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.normBtn.sizePolicy().hasHeightForWidth()) + self.normBtn.setSizePolicy(sizePolicy) + self.normBtn.setCheckable(True) + self.normBtn.setObjectName(_fromUtf8("normBtn")) + self.gridLayout.addWidget(self.normBtn, 1, 2, 1, 1) + self.roiPlot = PlotWidget(self.splitter) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) + self.roiPlot.setSizePolicy(sizePolicy) + self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) + self.roiPlot.setObjectName(_fromUtf8("roiPlot")) + self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) + self.normGroup = QtGui.QGroupBox(Form) + self.normGroup.setObjectName(_fromUtf8("normGroup")) + self.gridLayout_2 = QtGui.QGridLayout(self.normGroup) + self.gridLayout_2.setMargin(0) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.normSubtractRadio = QtGui.QRadioButton(self.normGroup) + self.normSubtractRadio.setObjectName(_fromUtf8("normSubtractRadio")) + self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) + self.normDivideRadio = QtGui.QRadioButton(self.normGroup) + self.normDivideRadio.setChecked(False) + self.normDivideRadio.setObjectName(_fromUtf8("normDivideRadio")) + self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) + self.label_5 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_5.setFont(font) + self.label_5.setObjectName(_fromUtf8("label_5")) + self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) + self.label_3 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_3.setFont(font) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) + self.label_4 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_4.setFont(font) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) + self.normROICheck = QtGui.QCheckBox(self.normGroup) + self.normROICheck.setObjectName(_fromUtf8("normROICheck")) + self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) + self.normXBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normXBlurSpin.setObjectName(_fromUtf8("normXBlurSpin")) + self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) + self.label_8 = QtGui.QLabel(self.normGroup) + self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_8.setObjectName(_fromUtf8("label_8")) + self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) + self.label_9 = QtGui.QLabel(self.normGroup) + self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_9.setObjectName(_fromUtf8("label_9")) + self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) + self.normYBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normYBlurSpin.setObjectName(_fromUtf8("normYBlurSpin")) + self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) + self.label_10 = QtGui.QLabel(self.normGroup) + self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_10.setObjectName(_fromUtf8("label_10")) + self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) + self.normOffRadio = QtGui.QRadioButton(self.normGroup) + self.normOffRadio.setChecked(True) + self.normOffRadio.setObjectName(_fromUtf8("normOffRadio")) + self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) + self.normTimeRangeCheck = QtGui.QCheckBox(self.normGroup) + self.normTimeRangeCheck.setObjectName(_fromUtf8("normTimeRangeCheck")) + self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) + self.normFrameCheck = QtGui.QCheckBox(self.normGroup) + self.normFrameCheck.setObjectName(_fromUtf8("normFrameCheck")) + self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) + self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normTBlurSpin.setObjectName(_fromUtf8("normTBlurSpin")) + self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) + self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) + self.normBtn.setText(QtGui.QApplication.translate("Form", "Norm", None, QtGui.QApplication.UnicodeUTF8)) + self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8)) + self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8)) + self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8)) + self.label_5.setText(QtGui.QApplication.translate("Form", "Operation:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("Form", "Mean:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.translate("Form", "Blur:", None, QtGui.QApplication.UnicodeUTF8)) + self.normROICheck.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) + self.label_8.setText(QtGui.QApplication.translate("Form", "X", None, QtGui.QApplication.UnicodeUTF8)) + self.label_9.setText(QtGui.QApplication.translate("Form", "Y", None, QtGui.QApplication.UnicodeUTF8)) + self.label_10.setText(QtGui.QApplication.translate("Form", "T", None, QtGui.QApplication.UnicodeUTF8)) + self.normOffRadio.setText(QtGui.QApplication.translate("Form", "Off", None, QtGui.QApplication.UnicodeUTF8)) + self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8)) + self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.widgets.GraphicsView import GraphicsView +from pyqtgraph.widgets.PlotWidget import PlotWidget +from pyqtgraph.widgets.HistogramLUTWidget import HistogramLUTWidget diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyside.py b/pyqtgraph/imageview/ImageViewTemplate_pyside.py new file mode 100644 index 00000000..c17bbfe1 --- /dev/null +++ b/pyqtgraph/imageview/ImageViewTemplate_pyside.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './imageview/ImageViewTemplate.ui' +# +# Created: Sun Sep 9 14:41:31 2012 +# by: pyside-uic 0.2.13 running on PySide 1.1.0 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(726, 588) + self.gridLayout_3 = QtGui.QGridLayout(Form) + self.gridLayout_3.setContentsMargins(0, 0, 0, 0) + self.gridLayout_3.setSpacing(0) + self.gridLayout_3.setObjectName("gridLayout_3") + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName("splitter") + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName("layoutWidget") + self.gridLayout = QtGui.QGridLayout(self.layoutWidget) + self.gridLayout.setSpacing(0) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.graphicsView = GraphicsView(self.layoutWidget) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) + self.histogram = HistogramLUTWidget(self.layoutWidget) + self.histogram.setObjectName("histogram") + self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) + self.roiBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) + self.roiBtn.setSizePolicy(sizePolicy) + self.roiBtn.setCheckable(True) + self.roiBtn.setObjectName("roiBtn") + self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) + self.normBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.normBtn.sizePolicy().hasHeightForWidth()) + self.normBtn.setSizePolicy(sizePolicy) + self.normBtn.setCheckable(True) + self.normBtn.setObjectName("normBtn") + self.gridLayout.addWidget(self.normBtn, 1, 2, 1, 1) + self.roiPlot = PlotWidget(self.splitter) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) + self.roiPlot.setSizePolicy(sizePolicy) + self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) + self.roiPlot.setObjectName("roiPlot") + self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) + self.normGroup = QtGui.QGroupBox(Form) + self.normGroup.setObjectName("normGroup") + self.gridLayout_2 = QtGui.QGridLayout(self.normGroup) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.normSubtractRadio = QtGui.QRadioButton(self.normGroup) + self.normSubtractRadio.setObjectName("normSubtractRadio") + self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) + self.normDivideRadio = QtGui.QRadioButton(self.normGroup) + self.normDivideRadio.setChecked(False) + self.normDivideRadio.setObjectName("normDivideRadio") + self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) + self.label_5 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.label_5.setFont(font) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) + self.label_3 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.label_3.setFont(font) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) + self.label_4 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.label_4.setFont(font) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) + self.normROICheck = QtGui.QCheckBox(self.normGroup) + self.normROICheck.setObjectName("normROICheck") + self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) + self.normXBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normXBlurSpin.setObjectName("normXBlurSpin") + self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) + self.label_8 = QtGui.QLabel(self.normGroup) + self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_8.setObjectName("label_8") + self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) + self.label_9 = QtGui.QLabel(self.normGroup) + self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_9.setObjectName("label_9") + self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) + self.normYBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normYBlurSpin.setObjectName("normYBlurSpin") + self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) + self.label_10 = QtGui.QLabel(self.normGroup) + self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_10.setObjectName("label_10") + self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) + self.normOffRadio = QtGui.QRadioButton(self.normGroup) + self.normOffRadio.setChecked(True) + self.normOffRadio.setObjectName("normOffRadio") + self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) + self.normTimeRangeCheck = QtGui.QCheckBox(self.normGroup) + self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") + self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) + self.normFrameCheck = QtGui.QCheckBox(self.normGroup) + self.normFrameCheck.setObjectName("normFrameCheck") + self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) + self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normTBlurSpin.setObjectName("normTBlurSpin") + self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) + self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) + self.normBtn.setText(QtGui.QApplication.translate("Form", "Norm", None, QtGui.QApplication.UnicodeUTF8)) + self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8)) + self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8)) + self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8)) + self.label_5.setText(QtGui.QApplication.translate("Form", "Operation:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("Form", "Mean:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.translate("Form", "Blur:", None, QtGui.QApplication.UnicodeUTF8)) + self.normROICheck.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) + self.label_8.setText(QtGui.QApplication.translate("Form", "X", None, QtGui.QApplication.UnicodeUTF8)) + self.label_9.setText(QtGui.QApplication.translate("Form", "Y", None, QtGui.QApplication.UnicodeUTF8)) + self.label_10.setText(QtGui.QApplication.translate("Form", "T", None, QtGui.QApplication.UnicodeUTF8)) + self.normOffRadio.setText(QtGui.QApplication.translate("Form", "Off", None, QtGui.QApplication.UnicodeUTF8)) + self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8)) + self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8)) + +from pyqtgraph.widgets.GraphicsView import GraphicsView +from pyqtgraph.widgets.PlotWidget import PlotWidget +from pyqtgraph.widgets.HistogramLUTWidget import HistogramLUTWidget diff --git a/pyqtgraph/imageview/__init__.py b/pyqtgraph/imageview/__init__.py new file mode 100644 index 00000000..0060e823 --- /dev/null +++ b/pyqtgraph/imageview/__init__.py @@ -0,0 +1,6 @@ +""" +Widget used for display and analysis of 2D and 3D image data. +Includes ROI plotting over time and image normalization. +""" + +from .ImageView import ImageView diff --git a/pyqtgraph/metaarray/MetaArray.py b/pyqtgraph/metaarray/MetaArray.py new file mode 100644 index 00000000..f55c60dc --- /dev/null +++ b/pyqtgraph/metaarray/MetaArray.py @@ -0,0 +1,1474 @@ +# -*- coding: utf-8 -*- +""" +MetaArray.py - Class encapsulating ndarray with meta data +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +MetaArray is an array class based on numpy.ndarray that allows storage of per-axis meta data +such as axis values, names, units, column names, etc. It also enables several +new methods for slicing and indexing the array based on this meta data. +More info at http://www.scipy.org/Cookbook/MetaArray +""" + +import numpy as np +import types, copy, threading, os, re +import pickle +from functools import reduce +#import traceback + +## By default, the library will use HDF5 when writing files. +## This can be overridden by setting USE_HDF5 = False +USE_HDF5 = True +try: + import h5py + HAVE_HDF5 = True +except: + USE_HDF5 = False + HAVE_HDF5 = False + + +def axis(name=None, cols=None, values=None, units=None): + """Convenience function for generating axis descriptions when defining MetaArrays""" + ax = {} + cNameOrder = ['name', 'units', 'title'] + if name is not None: + ax['name'] = name + if values is not None: + ax['values'] = values + if units is not None: + ax['units'] = units + if cols is not None: + ax['cols'] = [] + for c in cols: + if type(c) != list and type(c) != tuple: + c = [c] + col = {} + for i in range(0,len(c)): + col[cNameOrder[i]] = c[i] + ax['cols'].append(col) + return ax + +class sliceGenerator(object): + """Just a compact way to generate tuples of slice objects.""" + def __getitem__(self, arg): + return arg + def __getslice__(self, arg): + return arg +SLICER = sliceGenerator() + + +class MetaArray(object): + """N-dimensional array with meta data such as axis titles, units, and column names. + + May be initialized with a file name, a tuple representing the dimensions of the array, + or any arguments that could be passed on to numpy.array() + + The info argument sets the metadata for the entire array. It is composed of a list + of axis descriptions where each axis may have a name, title, units, and a list of column + descriptions. An additional dict at the end of the axis list may specify parameters + that apply to values in the entire array. + + For example: + A 2D array of altitude values for a topographical map might look like + info=[ + {'name': 'lat', 'title': 'Lattitude'}, + {'name': 'lon', 'title': 'Longitude'}, + {'title': 'Altitude', 'units': 'm'} + ] + In this case, every value in the array represents the altitude in feet at the lat, lon + position represented by the array index. All of the following return the + value at lat=10, lon=5: + array[10, 5] + array['lon':5, 'lat':10] + array['lat':10][5] + Now suppose we want to combine this data with another array of equal dimensions that + represents the average rainfall for each location. We could easily store these as two + separate arrays or combine them into a 3D array with this description: + info=[ + {'name': 'vals', 'cols': [ + {'name': 'altitude', 'units': 'm'}, + {'name': 'rainfall', 'units': 'cm/year'} + ]}, + {'name': 'lat', 'title': 'Lattitude'}, + {'name': 'lon', 'title': 'Longitude'} + ] + We can now access the altitude values with array[0] or array['altitude'], and the + rainfall values with array[1] or array['rainfall']. All of the following return + the rainfall value at lat=10, lon=5: + array[1, 10, 5] + array['lon':5, 'lat':10, 'val': 'rainfall'] + array['rainfall', 'lon':5, 'lat':10] + Notice that in the second example, there is no need for an extra (4th) axis description + since the actual values are described (name and units) in the column info for the first axis. + """ + + version = '2' + + ## Types allowed as axis or column names + nameTypes = [basestring, tuple] + @staticmethod + def isNameType(var): + return any([isinstance(var, t) for t in MetaArray.nameTypes]) + + + ## methods to wrap from embedded ndarray / HDF5 + wrapMethods = set(['__eq__', '__ne__', '__le__', '__lt__', '__ge__', '__gt__']) + + def __init__(self, data=None, info=None, dtype=None, file=None, copy=False, **kwargs): + object.__init__(self) + #self._infoOwned = False + self._isHDF = False + + if file is not None: + self._data = None + self.readFile(file, **kwargs) + if self._data is None: + raise Exception("File read failed: %s" % file) + else: + self._info = info + if (hasattr(data, 'implements') and data.implements('MetaArray')): + self._info = data._info + self._data = data.asarray() + elif isinstance(data, tuple): ## create empty array with specified shape + self._data = np.empty(data, dtype=dtype) + else: + self._data = np.array(data, dtype=dtype, copy=copy) + + ## run sanity checks on info structure + self.checkInfo() + + def checkInfo(self): + info = self._info + if info is None: + if self._data is None: + return + else: + self._info = [{} for i in range(self.ndim)] + return + else: + try: + info = list(info) + except: + raise Exception("Info must be a list of axis specifications") + if len(info) < self.ndim+1: + info.extend([{}]*(self.ndim+1-len(info))) + elif len(info) > self.ndim+1: + raise Exception("Info parameter must be list of length ndim+1 or less.") + for i in range(len(info)): + if not isinstance(info[i], dict): + if info[i] is None: + info[i] = {} + else: + raise Exception("Axis specification must be Dict or None") + if i < self.ndim and 'values' in info[i]: + if type(info[i]['values']) is list: + info[i]['values'] = np.array(info[i]['values']) + elif type(info[i]['values']) is not np.ndarray: + raise Exception("Axis values must be specified as list or ndarray") + if info[i]['values'].ndim != 1 or info[i]['values'].shape[0] != self.shape[i]: + raise Exception("Values array for axis %d has incorrect shape. (given %s, but should be %s)" % (i, str(info[i]['values'].shape), str((self.shape[i],)))) + if i < self.ndim and 'cols' in info[i]: + if not isinstance(info[i]['cols'], list): + info[i]['cols'] = list(info[i]['cols']) + if len(info[i]['cols']) != self.shape[i]: + raise Exception('Length of column list for axis %d does not match data. (given %d, but should be %d)' % (i, len(info[i]['cols']), self.shape[i])) + + def implements(self, name=None): + ## Rather than isinstance(obj, MetaArray) use object.implements('MetaArray') + if name is None: + return ['MetaArray'] + else: + return name == 'MetaArray' + + #def __array_finalize__(self,obj): + ### array_finalize is called every time a MetaArray is created + ### (whereas __new__ is not necessarily called every time) + + ### obj is the object from which this array was generated (for example, when slicing or view()ing) + + ## We use the getattr method to set a default if 'obj' doesn't have the 'info' attribute + ##print "Create new MA from object", str(type(obj)) + ##import traceback + ##traceback.print_stack() + ##print "finalize", type(self), type(obj) + #if not hasattr(self, '_info'): + ##if isinstance(obj, MetaArray): + ##print " copy info:", obj._info + #self._info = getattr(obj, '_info', [{}]*(obj.ndim+1)) + #self._infoOwned = False ## Do not make changes to _info until it is copied at least once + ##print " self info:", self._info + + ## We could have checked first whether self._info was already defined: + ##if not hasattr(self, 'info'): + ## self._info = getattr(obj, 'info', {}) + + + def __getitem__(self, ind): + #print "getitem:", ind + + ## should catch scalar requests as early as possible to speed things up (?) + + nInd = self._interpretIndexes(ind) + + #a = np.ndarray.__getitem__(self, nInd) + a = self._data[nInd] + if len(nInd) == self.ndim: + if np.all([not isinstance(ind, slice) for ind in nInd]): ## no slices; we have requested a single value from the array + return a + #if type(a) != type(self._data) and not isinstance(a, np.ndarray): ## indexing returned single value + #return a + + ## indexing returned a sub-array; generate new info array to go with it + #print " new MA:", type(a), a.shape + info = [] + extraInfo = self._info[-1].copy() + for i in range(0, len(nInd)): ## iterate over all axes + #print " axis", i + if type(nInd[i]) in [slice, list] or isinstance(nInd[i], np.ndarray): ## If the axis is sliced, keep the info but chop if necessary + #print " slice axis", i, nInd[i] + #a._info[i] = self._axisSlice(i, nInd[i]) + #print " info:", a._info[i] + info.append(self._axisSlice(i, nInd[i])) + else: ## If the axis is indexed, then move the information from that single index to the last info dictionary + #print "indexed:", i, nInd[i], type(nInd[i]) + newInfo = self._axisSlice(i, nInd[i]) + name = None + colName = None + for k in newInfo: + if k == 'cols': + if 'cols' not in extraInfo: + extraInfo['cols'] = [] + extraInfo['cols'].append(newInfo[k]) + if 'units' in newInfo[k]: + extraInfo['units'] = newInfo[k]['units'] + if 'name' in newInfo[k]: + colName = newInfo[k]['name'] + elif k == 'name': + name = newInfo[k] + else: + if k not in extraInfo: + extraInfo[k] = newInfo[k] + extraInfo[k] = newInfo[k] + if 'name' not in extraInfo: + if name is None: + if colName is not None: + extraInfo['name'] = colName + else: + if colName is not None: + extraInfo['name'] = str(name) + ': ' + str(colName) + else: + extraInfo['name'] = name + + + #print "Lost info:", newInfo + #a._info[i] = None + #if 'name' in newInfo: + #a._info[-1][newInfo['name']] = newInfo + info.append(extraInfo) + + #self._infoOwned = False + #while None in a._info: + #a._info.remove(None) + return MetaArray(a, info=info) + + @property + def ndim(self): + return len(self.shape) ## hdf5 objects do not have ndim property. + + @property + def shape(self): + return self._data.shape + + @property + def dtype(self): + return self._data.dtype + + def __len__(self): + return len(self._data) + + def __getslice__(self, *args): + return self.__getitem__(slice(*args)) + + def __setitem__(self, ind, val): + nInd = self._interpretIndexes(ind) + try: + self._data[nInd] = val + except: + print(self, nInd, val) + raise + + def __getattr__(self, attr): + if attr in self.wrapMethods: + return getattr(self._data, attr) + else: + raise AttributeError(attr) + #return lambda *args, **kwargs: MetaArray(getattr(a.view(ndarray), attr)(*args, **kwargs) + + def __eq__(self, b): + return self._binop('__eq__', b) + + def __ne__(self, b): + return self._binop('__ne__', b) + #if isinstance(b, MetaArray): + #b = b.asarray() + #return self.asarray() != b + + def __sub__(self, b): + return self._binop('__sub__', b) + #if isinstance(b, MetaArray): + #b = b.asarray() + #return MetaArray(self.asarray() - b, info=self.infoCopy()) + + def __add__(self, b): + return self._binop('__add__', b) + + def __mul__(self, b): + return self._binop('__mul__', b) + + def __div__(self, b): + return self._binop('__div__', b) + + def __truediv__(self, b): + return self._binop('__truediv__', b) + + def _binop(self, op, b): + if isinstance(b, MetaArray): + b = b.asarray() + a = self.asarray() + c = getattr(a, op)(b) + if c.shape != a.shape: + raise Exception("Binary operators with MetaArray must return an array of the same shape (this shape is %s, result shape was %s)" % (a.shape, c.shape)) + return MetaArray(c, info=self.infoCopy()) + + def asarray(self): + if isinstance(self._data, np.ndarray): + return self._data + else: + return np.array(self._data) + + def __array__(self): + ## supports np.array(metaarray_instance) + return self.asarray() + + def view(self, typ): + ## deprecated; kept for backward compatibility + if typ is np.ndarray: + return self.asarray() + else: + raise Exception('invalid view type: %s' % str(typ)) + + def axisValues(self, axis): + """Return the list of values for an axis""" + ax = self._interpretAxis(axis) + if 'values' in self._info[ax]: + return self._info[ax]['values'] + else: + raise Exception('Array axis %s (%d) has no associated values.' % (str(axis), ax)) + + def xvals(self, axis): + """Synonym for axisValues()""" + return self.axisValues(axis) + + def axisHasValues(self, axis): + ax = self._interpretAxis(axis) + return 'values' in self._info[ax] + + def axisHasColumns(self, axis): + ax = self._interpretAxis(axis) + return 'cols' in self._info[ax] + + def axisUnits(self, axis): + """Return the units for axis""" + ax = self._info[self._interpretAxis(axis)] + if 'units' in ax: + return ax['units'] + + def hasColumn(self, axis, col): + ax = self._info[self._interpretAxis(axis)] + if 'cols' in ax: + for c in ax['cols']: + if c['name'] == col: + return True + return False + + def listColumns(self, axis=None): + """Return a list of column names for axis. If axis is not specified, then return a dict of {axisName: (column names), ...}.""" + if axis is None: + ret = {} + for i in range(self.ndim): + if 'cols' in self._info[i]: + cols = [c['name'] for c in self._info[i]['cols']] + else: + cols = [] + ret[self.axisName(i)] = cols + return ret + else: + axis = self._interpretAxis(axis) + return [c['name'] for c in self._info[axis]['cols']] + + def columnName(self, axis, col): + ax = self._info[self._interpretAxis(axis)] + return ax['cols'][col]['name'] + + def axisName(self, n): + return self._info[n].get('name', n) + + def columnUnits(self, axis, column): + """Return the units for column in axis""" + ax = self._info[self._interpretAxis(axis)] + if 'cols' in ax: + for c in ax['cols']: + if c['name'] == column: + return c['units'] + raise Exception("Axis %s has no column named %s" % (str(axis), str(column))) + else: + raise Exception("Axis %s has no column definitions" % str(axis)) + + def rowsort(self, axis, key=0): + """Return this object with all records sorted along axis using key as the index to the values to compare. Does not yet modify meta info.""" + ## make sure _info is copied locally before modifying it! + + keyList = self[key] + order = keyList.argsort() + if type(axis) == int: + ind = [slice(None)]*axis + ind.append(order) + elif isinstance(axis, basestring): + ind = (slice(axis, order),) + return self[tuple(ind)] + + def append(self, val, axis): + """Return this object with val appended along axis. Does not yet combine meta info.""" + ## make sure _info is copied locally before modifying it! + + s = list(self.shape) + axis = self._interpretAxis(axis) + s[axis] += 1 + n = MetaArray(tuple(s), info=self._info, dtype=self.dtype) + ind = [slice(None)]*self.ndim + ind[axis] = slice(None,-1) + n[tuple(ind)] = self + ind[axis] = -1 + n[tuple(ind)] = val + return n + + def extend(self, val, axis): + """Return the concatenation along axis of this object and val. Does not yet combine meta info.""" + ## make sure _info is copied locally before modifying it! + + axis = self._interpretAxis(axis) + return MetaArray(np.concatenate(self, val, axis), info=self._info) + + def infoCopy(self, axis=None): + """Return a deep copy of the axis meta info for this object""" + if axis is None: + return copy.deepcopy(self._info) + else: + return copy.deepcopy(self._info[self._interpretAxis(axis)]) + + def copy(self): + return MetaArray(self._data.copy(), info=self.infoCopy()) + + + def _interpretIndexes(self, ind): + #print "interpret", ind + if not isinstance(ind, tuple): + ## a list of slices should be interpreted as a tuple of slices. + if isinstance(ind, list) and len(ind) > 0 and isinstance(ind[0], slice): + ind = tuple(ind) + ## everything else can just be converted to a length-1 tuple + else: + ind = (ind,) + + nInd = [slice(None)]*self.ndim + numOk = True ## Named indices not started yet; numbered sill ok + for i in range(0,len(ind)): + (axis, index, isNamed) = self._interpretIndex(ind[i], i, numOk) + #try: + nInd[axis] = index + #except: + #print "ndim:", self.ndim + #print "axis:", axis + #print "index spec:", ind[i] + #print "index num:", index + #raise + if isNamed: + numOk = False + return tuple(nInd) + + def _interpretAxis(self, axis): + if isinstance(axis, basestring) or isinstance(axis, tuple): + return self._getAxis(axis) + else: + return axis + + def _interpretIndex(self, ind, pos, numOk): + #print "Interpreting index", ind, pos, numOk + + ## should probably check for int first to speed things up.. + if type(ind) is int: + if not numOk: + raise Exception("string and integer indexes may not follow named indexes") + #print " normal numerical index" + return (pos, ind, False) + if MetaArray.isNameType(ind): + if not numOk: + raise Exception("string and integer indexes may not follow named indexes") + #print " String index, column is ", self._getIndex(pos, ind) + return (pos, self._getIndex(pos, ind), False) + elif type(ind) is slice: + #print " Slice index" + if MetaArray.isNameType(ind.start) or MetaArray.isNameType(ind.stop): ## Not an actual slice! + #print " ..not a real slice" + axis = self._interpretAxis(ind.start) + #print " axis is", axis + + ## x[Axis:Column] + if MetaArray.isNameType(ind.stop): + #print " column name, column is ", self._getIndex(axis, ind.stop) + index = self._getIndex(axis, ind.stop) + + ## x[Axis:min:max] + elif (isinstance(ind.stop, float) or isinstance(ind.step, float)) and ('values' in self._info[axis]): + #print " axis value range" + if ind.stop is None: + mask = self.xvals(axis) < ind.step + elif ind.step is None: + mask = self.xvals(axis) >= ind.stop + else: + mask = (self.xvals(axis) >= ind.stop) * (self.xvals(axis) < ind.step) + ##print "mask:", mask + index = mask + + ## x[Axis:columnIndex] + elif isinstance(ind.stop, int) or isinstance(ind.step, int): + #print " normal slice after named axis" + if ind.step is None: + index = ind.stop + else: + index = slice(ind.stop, ind.step) + + ## x[Axis: [list]] + elif type(ind.stop) is list: + #print " list of indexes from named axis" + index = [] + for i in ind.stop: + if type(i) is int: + index.append(i) + elif MetaArray.isNameType(i): + index.append(self._getIndex(axis, i)) + else: + ## unrecognized type, try just passing on to array + index = ind.stop + break + + else: + #print " other type.. forward on to array for handling", type(ind.stop) + index = ind.stop + #print "Axis %s (%s) : %s" % (ind.start, str(axis), str(type(index))) + #if type(index) is np.ndarray: + #print " ", index.shape + return (axis, index, True) + else: + #print " Looks like a real slice, passing on to array" + return (pos, ind, False) + elif type(ind) is list: + #print " List index., interpreting each element individually" + indList = [self._interpretIndex(i, pos, numOk)[1] for i in ind] + return (pos, indList, False) + else: + if not numOk: + raise Exception("string and integer indexes may not follow named indexes") + #print " normal numerical index" + return (pos, ind, False) + + def _getAxis(self, name): + for i in range(0, len(self._info)): + axis = self._info[i] + if 'name' in axis and axis['name'] == name: + return i + raise Exception("No axis named %s.\n info=%s" % (name, self._info)) + + def _getIndex(self, axis, name): + ax = self._info[axis] + if ax is not None and 'cols' in ax: + for i in range(0, len(ax['cols'])): + if 'name' in ax['cols'][i] and ax['cols'][i]['name'] == name: + return i + raise Exception("Axis %d has no column named %s.\n info=%s" % (axis, name, self._info)) + + def _axisCopy(self, i): + return copy.deepcopy(self._info[i]) + + def _axisSlice(self, i, cols): + #print "axisSlice", i, cols + if 'cols' in self._info[i] or 'values' in self._info[i]: + ax = self._axisCopy(i) + if 'cols' in ax: + #print " slicing columns..", array(ax['cols']), cols + sl = np.array(ax['cols'])[cols] + if isinstance(sl, np.ndarray): + sl = list(sl) + ax['cols'] = sl + #print " result:", ax['cols'] + if 'values' in ax: + ax['values'] = np.array(ax['values'])[cols] + else: + ax = self._info[i] + #print " ", ax + return ax + + def prettyInfo(self): + s = '' + titles = [] + maxl = 0 + for i in range(len(self._info)-1): + ax = self._info[i] + axs = '' + if 'name' in ax: + axs += '"%s"' % str(ax['name']) + else: + axs += "%d" % i + if 'units' in ax: + axs += " (%s)" % str(ax['units']) + titles.append(axs) + if len(axs) > maxl: + maxl = len(axs) + + for i in range(min(self.ndim, len(self._info)-1)): + ax = self._info[i] + axs = titles[i] + axs += '%s[%d] :' % (' ' * (maxl + 2 - len(axs)), self.shape[i]) + if 'values' in ax: + v0 = ax['values'][0] + v1 = ax['values'][-1] + axs += " values: [%g ... %g] (step %g)" % (v0, v1, (v1-v0)/(self.shape[i]-1)) + if 'cols' in ax: + axs += " columns: " + colstrs = [] + for c in range(len(ax['cols'])): + col = ax['cols'][c] + cs = str(col.get('name', c)) + if 'units' in col: + cs += " (%s)" % col['units'] + colstrs.append(cs) + axs += '[' + ', '.join(colstrs) + ']' + s += axs + "\n" + s += str(self._info[-1]) + return s + + def __repr__(self): + return "%s\n-----------------------------------------------\n%s" % (self.view(np.ndarray).__repr__(), self.prettyInfo()) + + def __str__(self): + return self.__repr__() + + + def axisCollapsingFn(self, fn, axis=None, *args, **kargs): + #arr = self.view(np.ndarray) + fn = getattr(self._data, fn) + if axis is None: + return fn(axis, *args, **kargs) + else: + info = self.infoCopy() + axis = self._interpretAxis(axis) + info.pop(axis) + return MetaArray(fn(axis, *args, **kargs), info=info) + + def mean(self, axis=None, *args, **kargs): + return self.axisCollapsingFn('mean', axis, *args, **kargs) + + + def min(self, axis=None, *args, **kargs): + return self.axisCollapsingFn('min', axis, *args, **kargs) + + def max(self, axis=None, *args, **kargs): + return self.axisCollapsingFn('max', axis, *args, **kargs) + + def transpose(self, *args): + if len(args) == 1 and hasattr(args[0], '__iter__'): + order = args[0] + else: + order = args + + order = [self._interpretAxis(ax) for ax in order] + infoOrder = order + list(range(len(order), len(self._info))) + info = [self._info[i] for i in infoOrder] + order = order + list(range(len(order), self.ndim)) + + try: + if self._isHDF: + return MetaArray(np.array(self._data).transpose(order), info=info) + else: + return MetaArray(self._data.transpose(order), info=info) + except: + print(order) + raise + + #### File I/O Routines + def readFile(self, filename, **kwargs): + """Load the data and meta info stored in *filename* + Different arguments are allowed depending on the type of file. + For HDF5 files: + + *writable* (bool) if True, then any modifications to data in the array will be stored to disk. + *readAllData* (bool) if True, then all data in the array is immediately read from disk + and the file is closed (this is the default for files < 500MB). Otherwise, the file will + be left open and data will be read only as requested (this is + the default for files >= 500MB). + + + """ + ## decide which read function to use + fd = open(filename, 'rb') + magic = fd.read(8) + if magic == '\x89HDF\r\n\x1a\n': + fd.close() + self._readHDF5(filename, **kwargs) + self._isHDF = True + else: + fd.seek(0) + meta = MetaArray._readMeta(fd) + if 'version' in meta: + ver = meta['version'] + else: + ver = 1 + rFuncName = '_readData%s' % str(ver) + if not hasattr(MetaArray, rFuncName): + raise Exception("This MetaArray library does not support array version '%s'" % ver) + rFunc = getattr(self, rFuncName) + rFunc(fd, meta, **kwargs) + self._isHDF = False + + @staticmethod + def _readMeta(fd): + """Read meta array from the top of a file. Read lines until a blank line is reached. + This function should ideally work for ALL versions of MetaArray. + """ + meta = '' + ## Read meta information until the first blank line + while True: + line = fd.readline().strip() + if line == '': + break + meta += line + ret = eval(meta) + #print ret + return ret + + def _readData1(self, fd, meta, mmap=False): + ## Read array data from the file descriptor for MetaArray v1 files + ## read in axis values for any axis that specifies a length + frameSize = 1 + for ax in meta['info']: + if 'values_len' in ax: + ax['values'] = np.fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) + frameSize *= ax['values_len'] + del ax['values_len'] + del ax['values_type'] + ## the remaining data is the actual array + if mmap: + subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape']) + else: + subarr = np.fromstring(fd.read(), dtype=meta['type']) + subarr.shape = meta['shape'] + self._info = meta['info'] + self._data = subarr + + def _readData2(self, fd, meta, mmap=False, subset=None): + ## read in axis values + dynAxis = None + frameSize = 1 + ## read in axis values for any axis that specifies a length + for i in range(len(meta['info'])): + ax = meta['info'][i] + if 'values_len' in ax: + if ax['values_len'] == 'dynamic': + if dynAxis is not None: + raise Exception("MetaArray has more than one dynamic axis! (this is not allowed)") + dynAxis = i + else: + ax['values'] = np.fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) + frameSize *= ax['values_len'] + del ax['values_len'] + del ax['values_type'] + + ## No axes are dynamic, just read the entire array in at once + if dynAxis is None: + #if rewriteDynamic is not None: + #raise Exception("") + if meta['type'] == 'object': + if mmap: + raise Exception('memmap not supported for arrays with dtype=object') + subarr = pickle.loads(fd.read()) + else: + if mmap: + subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape']) + else: + subarr = np.fromstring(fd.read(), dtype=meta['type']) + #subarr = subarr.view(subtype) + subarr.shape = meta['shape'] + #subarr._info = meta['info'] + ## One axis is dynamic, read in a frame at a time + else: + if mmap: + raise Exception('memmap not supported for non-contiguous arrays. Use rewriteContiguous() to convert.') + ax = meta['info'][dynAxis] + xVals = [] + frames = [] + frameShape = list(meta['shape']) + frameShape[dynAxis] = 1 + frameSize = reduce(lambda a,b: a*b, frameShape) + n = 0 + while True: + ## Extract one non-blank line + while True: + line = fd.readline() + if line != '\n': + break + if line == '': + break + + ## evaluate line + inf = eval(line) + + ## read data block + #print "read %d bytes as %s" % (inf['len'], meta['type']) + if meta['type'] == 'object': + data = pickle.loads(fd.read(inf['len'])) + else: + data = np.fromstring(fd.read(inf['len']), dtype=meta['type']) + + if data.size != frameSize * inf['numFrames']: + #print data.size, frameSize, inf['numFrames'] + raise Exception("Wrong frame size in MetaArray file! (frame %d)" % n) + + ## read in data block + shape = list(frameShape) + shape[dynAxis] = inf['numFrames'] + data.shape = shape + if subset is not None: + dSlice = subset[dynAxis] + if dSlice.start is None: + dStart = 0 + else: + dStart = max(0, dSlice.start - n) + if dSlice.stop is None: + dStop = data.shape[dynAxis] + else: + dStop = min(data.shape[dynAxis], dSlice.stop - n) + newSubset = list(subset[:]) + newSubset[dynAxis] = slice(dStart, dStop) + if dStop > dStart: + #print n, data.shape, " => ", newSubset, data[tuple(newSubset)].shape + frames.append(data[tuple(newSubset)].copy()) + else: + #data = data[subset].copy() ## what's this for?? + frames.append(data) + + n += inf['numFrames'] + if 'xVals' in inf: + xVals.extend(inf['xVals']) + subarr = np.concatenate(frames, axis=dynAxis) + if len(xVals)> 0: + ax['values'] = np.array(xVals, dtype=ax['values_type']) + del ax['values_len'] + del ax['values_type'] + #subarr = subarr.view(subtype) + #subarr._info = meta['info'] + self._info = meta['info'] + self._data = subarr + #raise Exception() ## stress-testing + #return subarr + + def _readHDF5(self, fileName, readAllData=None, writable=False, **kargs): + if 'close' in kargs and readAllData is None: ## for backward compatibility + readAllData = kargs['close'] + + if readAllData is True and writable is True: + raise Exception("Incompatible arguments: readAllData=True and writable=True") + + if not HAVE_HDF5: + try: + assert writable==False + assert readAllData != False + self._readHDF5Remote(fileName) + return + except: + raise Exception("The file '%s' is HDF5-formatted, but the HDF5 library (h5py) was not found." % fileName) + + ## by default, readAllData=True for files < 500MB + if readAllData is None: + size = os.stat(fileName).st_size + readAllData = (size < 500e6) + + if writable is True: + mode = 'r+' + else: + mode = 'r' + f = h5py.File(fileName, mode) + + ver = f.attrs['MetaArray'] + if ver > MetaArray.version: + print("Warning: This file was written with MetaArray version %s, but you are using version %s. (Will attempt to read anyway)" % (str(ver), str(MetaArray.version))) + meta = MetaArray.readHDF5Meta(f['info']) + self._info = meta + + if writable or not readAllData: ## read all data, convert to ndarray, close file + self._data = f['data'] + self._openFile = f + else: + self._data = f['data'][:] + f.close() + + def _readHDF5Remote(self, fileName): + ## Used to read HDF5 files via remote process. + ## This is needed in the case that HDF5 is not importable due to the use of python-dbg. + proc = getattr(MetaArray, '_hdf5Process', None) + + if proc == False: + raise Exception('remote read failed') + if proc == None: + import pyqtgraph.multiprocess as mp + #print "new process" + proc = mp.Process(executable='/usr/bin/python') + proc.setProxyOptions(deferGetattr=True) + MetaArray._hdf5Process = proc + MetaArray._h5py_metaarray = proc._import('pyqtgraph.metaarray') + ma = MetaArray._h5py_metaarray.MetaArray(file=fileName) + self._data = ma.asarray()._getValue() + self._info = ma._info._getValue() + #print MetaArray._hdf5Process + #import inspect + #print MetaArray, id(MetaArray), inspect.getmodule(MetaArray) + + + + @staticmethod + def mapHDF5Array(data, writable=False): + off = data.id.get_offset() + if writable: + mode = 'r+' + else: + mode = 'r' + if off is None: + raise Exception("This dataset uses chunked storage; it can not be memory-mapped. (store using mappable=True)") + return np.memmap(filename=data.file.filename, offset=off, dtype=data.dtype, shape=data.shape, mode=mode) + + + + + @staticmethod + def readHDF5Meta(root, mmap=False): + data = {} + + ## Pull list of values from attributes and child objects + for k in root.attrs: + val = root.attrs[k] + if isinstance(val, basestring): ## strings need to be re-evaluated to their original types + try: + val = eval(val) + except: + raise Exception('Can not evaluate string: "%s"' % val) + data[k] = val + for k in root: + obj = root[k] + if isinstance(obj, h5py.highlevel.Group): + val = MetaArray.readHDF5Meta(obj) + elif isinstance(obj, h5py.highlevel.Dataset): + if mmap: + val = MetaArray.mapHDF5Array(obj) + else: + val = obj[:] + else: + raise Exception("Don't know what to do with type '%s'" % str(type(obj))) + data[k] = val + + typ = root.attrs['_metaType_'] + del data['_metaType_'] + + if typ == 'dict': + return data + elif typ == 'list' or typ == 'tuple': + d2 = [None]*len(data) + for k in data: + d2[int(k)] = data[k] + if typ == 'tuple': + d2 = tuple(d2) + return d2 + else: + raise Exception("Don't understand metaType '%s'" % typ) + + + def write(self, fileName, **opts): + """Write this object to a file. The object can be restored by calling MetaArray(file=fileName) + opts: + appendAxis: the name (or index) of the appendable axis. Allows the array to grow. + compression: None, 'gzip' (good compression), 'lzf' (fast compression), etc. + chunks: bool or tuple specifying chunk shape + """ + + if USE_HDF5 and HAVE_HDF5: + return self.writeHDF5(fileName, **opts) + else: + return self.writeMa(fileName, **opts) + + def writeMeta(self, fileName): + """Used to re-write meta info to the given file. + This feature is only available for HDF5 files.""" + f = h5py.File(fileName, 'r+') + if f.attrs['MetaArray'] != MetaArray.version: + raise Exception("The file %s was created with a different version of MetaArray. Will not modify." % fileName) + del f['info'] + + self.writeHDF5Meta(f, 'info', self._info) + f.close() + + + def writeHDF5(self, fileName, **opts): + ## default options for writing datasets + dsOpts = { + 'compression': 'lzf', + 'chunks': True, + } + + ## if there is an appendable axis, then we can guess the desired chunk shape (optimized for appending) + appAxis = opts.get('appendAxis', None) + if appAxis is not None: + appAxis = self._interpretAxis(appAxis) + cs = [min(100000, x) for x in self.shape] + cs[appAxis] = 1 + dsOpts['chunks'] = tuple(cs) + + ## if there are columns, then we can guess a different chunk shape + ## (read one column at a time) + else: + cs = [min(100000, x) for x in self.shape] + for i in range(self.ndim): + if 'cols' in self._info[i]: + cs[i] = 1 + dsOpts['chunks'] = tuple(cs) + + ## update options if they were passed in + for k in dsOpts: + if k in opts: + dsOpts[k] = opts[k] + + + ## If mappable is in options, it disables chunking/compression + if opts.get('mappable', False): + dsOpts = { + 'chunks': None, + 'compression': None + } + + + ## set maximum shape to allow expansion along appendAxis + append = False + if appAxis is not None: + maxShape = list(self.shape) + ax = self._interpretAxis(appAxis) + maxShape[ax] = None + if os.path.exists(fileName): + append = True + dsOpts['maxshape'] = tuple(maxShape) + else: + dsOpts['maxshape'] = None + + if append: + f = h5py.File(fileName, 'r+') + if f.attrs['MetaArray'] != MetaArray.version: + raise Exception("The file %s was created with a different version of MetaArray. Will not modify." % fileName) + + ## resize data and write in new values + data = f['data'] + shape = list(data.shape) + shape[ax] += self.shape[ax] + data.resize(tuple(shape)) + sl = [slice(None)] * len(data.shape) + sl[ax] = slice(-self.shape[ax], None) + data[tuple(sl)] = self.view(np.ndarray) + + ## add axis values if they are present. + axInfo = f['info'][str(ax)] + if 'values' in axInfo: + v = axInfo['values'] + v2 = self._info[ax]['values'] + shape = list(v.shape) + shape[0] += v2.shape[0] + v.resize(shape) + v[-v2.shape[0]:] = v2 + f.close() + else: + f = h5py.File(fileName, 'w') + f.attrs['MetaArray'] = MetaArray.version + #print dsOpts + f.create_dataset('data', data=self.view(np.ndarray), **dsOpts) + + ## dsOpts is used when storing meta data whenever an array is encountered + ## however, 'chunks' will no longer be valid for these arrays if it specifies a chunk shape. + ## 'maxshape' is right-out. + if isinstance(dsOpts['chunks'], tuple): + dsOpts['chunks'] = True + if 'maxshape' in dsOpts: + del dsOpts['maxshape'] + self.writeHDF5Meta(f, 'info', self._info, **dsOpts) + f.close() + + def writeHDF5Meta(self, root, name, data, **dsOpts): + if isinstance(data, np.ndarray): + dsOpts['maxshape'] = (None,) + data.shape[1:] + root.create_dataset(name, data=data, **dsOpts) + elif isinstance(data, list) or isinstance(data, tuple): + gr = root.create_group(name) + if isinstance(data, list): + gr.attrs['_metaType_'] = 'list' + else: + gr.attrs['_metaType_'] = 'tuple' + #n = int(np.log10(len(data))) + 1 + for i in range(len(data)): + self.writeHDF5Meta(gr, str(i), data[i], **dsOpts) + elif isinstance(data, dict): + gr = root.create_group(name) + gr.attrs['_metaType_'] = 'dict' + for k, v in data.items(): + self.writeHDF5Meta(gr, k, v, **dsOpts) + elif isinstance(data, int) or isinstance(data, float) or isinstance(data, np.integer) or isinstance(data, np.floating): + root.attrs[name] = data + else: + try: ## strings, bools, None are stored as repr() strings + root.attrs[name] = repr(data) + except: + print("Can not store meta data of type '%s' in HDF5. (key is '%s')" % (str(type(data)), str(name))) + raise + + + def writeMa(self, fileName, appendAxis=None, newFile=False): + """Write an old-style .ma file""" + meta = {'shape':self.shape, 'type':str(self.dtype), 'info':self.infoCopy(), 'version':MetaArray.version} + axstrs = [] + + ## copy out axis values for dynamic axis if requested + if appendAxis is not None: + if MetaArray.isNameType(appendAxis): + appendAxis = self._interpretAxis(appendAxis) + + + ax = meta['info'][appendAxis] + ax['values_len'] = 'dynamic' + if 'values' in ax: + ax['values_type'] = str(ax['values'].dtype) + dynXVals = ax['values'] + del ax['values'] + else: + dynXVals = None + + ## Generate axis data string, modify axis info so we know how to read it back in later + for ax in meta['info']: + if 'values' in ax: + axstrs.append(ax['values'].tostring()) + ax['values_len'] = len(axstrs[-1]) + ax['values_type'] = str(ax['values'].dtype) + del ax['values'] + + ## Decide whether to output the meta block for a new file + if not newFile: + ## If the file does not exist or its size is 0, then we must write the header + newFile = (not os.path.exists(fileName)) or (os.stat(fileName).st_size == 0) + + ## write data to file + if appendAxis is None or newFile: + fd = open(fileName, 'wb') + fd.write(str(meta) + '\n\n') + for ax in axstrs: + fd.write(ax) + else: + fd = open(fileName, 'ab') + + if self.dtype != object: + dataStr = self.view(np.ndarray).tostring() + else: + dataStr = pickle.dumps(self.view(np.ndarray)) + #print self.size, len(dataStr), self.dtype + if appendAxis is not None: + frameInfo = {'len':len(dataStr), 'numFrames':self.shape[appendAxis]} + if dynXVals is not None: + frameInfo['xVals'] = list(dynXVals) + fd.write('\n'+str(frameInfo)+'\n') + fd.write(dataStr) + fd.close() + + def writeCsv(self, fileName=None): + """Write 2D array to CSV file or return the string if no filename is given""" + if self.ndim > 2: + raise Exception("CSV Export is only for 2D arrays") + if fileName is not None: + file = open(fileName, 'w') + ret = '' + if 'cols' in self._info[0]: + s = ','.join([x['name'] for x in self._info[0]['cols']]) + '\n' + if fileName is not None: + file.write(s) + else: + ret += s + for row in range(0, self.shape[1]): + s = ','.join(["%g" % x for x in self[:, row]]) + '\n' + if fileName is not None: + file.write(s) + else: + ret += s + if fileName is not None: + file.close() + else: + return ret + + + +#class H5MetaList(): + + +#def rewriteContiguous(fileName, newName): + #"""Rewrite a dynamic array file as contiguous""" + #def _readData2(fd, meta, subtype, mmap): + ### read in axis values + #dynAxis = None + #frameSize = 1 + ### read in axis values for any axis that specifies a length + #for i in range(len(meta['info'])): + #ax = meta['info'][i] + #if ax.has_key('values_len'): + #if ax['values_len'] == 'dynamic': + #if dynAxis is not None: + #raise Exception("MetaArray has more than one dynamic axis! (this is not allowed)") + #dynAxis = i + #else: + #ax['values'] = fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) + #frameSize *= ax['values_len'] + #del ax['values_len'] + #del ax['values_type'] + + ### No axes are dynamic, just read the entire array in at once + #if dynAxis is None: + #raise Exception('Array has no dynamic axes.') + ### One axis is dynamic, read in a frame at a time + #else: + #if mmap: + #raise Exception('memmap not supported for non-contiguous arrays. Use rewriteContiguous() to convert.') + #ax = meta['info'][dynAxis] + #xVals = [] + #frames = [] + #frameShape = list(meta['shape']) + #frameShape[dynAxis] = 1 + #frameSize = reduce(lambda a,b: a*b, frameShape) + #n = 0 + #while True: + ### Extract one non-blank line + #while True: + #line = fd.readline() + #if line != '\n': + #break + #if line == '': + #break + + ### evaluate line + #inf = eval(line) + + ### read data block + ##print "read %d bytes as %s" % (inf['len'], meta['type']) + #if meta['type'] == 'object': + #data = pickle.loads(fd.read(inf['len'])) + #else: + #data = fromstring(fd.read(inf['len']), dtype=meta['type']) + + #if data.size != frameSize * inf['numFrames']: + ##print data.size, frameSize, inf['numFrames'] + #raise Exception("Wrong frame size in MetaArray file! (frame %d)" % n) + + ### read in data block + #shape = list(frameShape) + #shape[dynAxis] = inf['numFrames'] + #data.shape = shape + #frames.append(data) + + #n += inf['numFrames'] + #if 'xVals' in inf: + #xVals.extend(inf['xVals']) + #subarr = np.concatenate(frames, axis=dynAxis) + #if len(xVals)> 0: + #ax['values'] = array(xVals, dtype=ax['values_type']) + #del ax['values_len'] + #del ax['values_type'] + #subarr = subarr.view(subtype) + #subarr._info = meta['info'] + #return subarr + + + + + +if __name__ == '__main__': + ## Create an array with every option possible + + arr = np.zeros((2, 5, 3, 5), dtype=int) + for i in range(arr.shape[0]): + for j in range(arr.shape[1]): + for k in range(arr.shape[2]): + for l in range(arr.shape[3]): + arr[i,j,k,l] = (i+1)*1000 + (j+1)*100 + (k+1)*10 + (l+1) + + info = [ + axis('Axis1'), + axis('Axis2', values=[1,2,3,4,5]), + axis('Axis3', cols=[ + ('Ax3Col1'), + ('Ax3Col2', 'mV', 'Axis3 Column2'), + (('Ax3','Col3'), 'A', 'Axis3 Column3')]), + {'name': 'Axis4', 'values': np.array([1.1, 1.2, 1.3, 1.4, 1.5]), 'units': 's'}, + {'extra': 'info'} + ] + + ma = MetaArray(arr, info=info) + + print("==== Original Array =======") + print(ma) + print("\n\n") + + #### Tests follow: + + + #### Index/slice tests: check that all values and meta info are correct after slice + print("\n -- normal integer indexing\n") + + print("\n ma[1]") + print(ma[1]) + + print("\n ma[1, 2:4]") + print(ma[1, 2:4]) + + print("\n ma[1, 1:5:2]") + print(ma[1, 1:5:2]) + + print("\n -- named axis indexing\n") + + print("\n ma['Axis2':3]") + print(ma['Axis2':3]) + + print("\n ma['Axis2':3:5]") + print(ma['Axis2':3:5]) + + print("\n ma[1, 'Axis2':3]") + print(ma[1, 'Axis2':3]) + + print("\n ma[:, 'Axis2':3]") + print(ma[:, 'Axis2':3]) + + print("\n ma['Axis2':3, 'Axis4':0:2]") + print(ma['Axis2':3, 'Axis4':0:2]) + + + print("\n -- column name indexing\n") + + print("\n ma['Axis3':'Ax3Col1']") + print(ma['Axis3':'Ax3Col1']) + + print("\n ma['Axis3':('Ax3','Col3')]") + print(ma['Axis3':('Ax3','Col3')]) + + print("\n ma[:, :, 'Ax3Col2']") + print(ma[:, :, 'Ax3Col2']) + + print("\n ma[:, :, ('Ax3','Col3')]") + print(ma[:, :, ('Ax3','Col3')]) + + + print("\n -- axis value range indexing\n") + + print("\n ma['Axis2':1.5:4.5]") + print(ma['Axis2':1.5:4.5]) + + print("\n ma['Axis4':1.15:1.45]") + print(ma['Axis4':1.15:1.45]) + + print("\n ma['Axis4':1.15:1.25]") + print(ma['Axis4':1.15:1.25]) + + + + print("\n -- list indexing\n") + + print("\n ma[:, [0,2,4]]") + print(ma[:, [0,2,4]]) + + print("\n ma['Axis4':[0,2,4]]") + print(ma['Axis4':[0,2,4]]) + + print("\n ma['Axis3':[0, ('Ax3','Col3')]]") + print(ma['Axis3':[0, ('Ax3','Col3')]]) + + + + print("\n -- boolean indexing\n") + + print("\n ma[:, array([True, True, False, True, False])]") + print(ma[:, np.array([True, True, False, True, False])]) + + print("\n ma['Axis4':array([True, False, False, False])]") + print(ma['Axis4':np.array([True, False, False, False])]) + + + + + + #### Array operations + # - Concatenate + # - Append + # - Extend + # - Rowsort + + + + + #### File I/O tests + + print("\n================ File I/O Tests ===================\n") + import tempfile + tf = tempfile.mktemp() + tf = 'test.ma' + # write whole array + + print("\n -- write/read test") + ma.write(tf) + ma2 = MetaArray(file=tf) + + #print ma2 + print("\nArrays are equivalent:", (ma == ma2).all()) + #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() + os.remove(tf) + + # CSV write + + # append mode + + + print("\n================append test (%s)===============" % tf) + ma['Axis2':0:2].write(tf, appendAxis='Axis2') + for i in range(2,ma.shape[1]): + ma['Axis2':[i]].write(tf, appendAxis='Axis2') + + ma2 = MetaArray(file=tf) + + #print ma2 + print("\nArrays are equivalent:", (ma == ma2).all()) + #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() + + os.remove(tf) + + + + ## memmap test + print("\n==========Memmap test============") + ma.write(tf, mappable=True) + ma2 = MetaArray(file=tf, mmap=True) + print("\nArrays are equivalent:", (ma == ma2).all()) + os.remove(tf) + \ No newline at end of file diff --git a/pyqtgraph/metaarray/__init__.py b/pyqtgraph/metaarray/__init__.py new file mode 100644 index 00000000..a12f40d5 --- /dev/null +++ b/pyqtgraph/metaarray/__init__.py @@ -0,0 +1 @@ +from .MetaArray import * diff --git a/pyqtgraph/metaarray/license.txt b/pyqtgraph/metaarray/license.txt new file mode 100644 index 00000000..7ef3e5e9 --- /dev/null +++ b/pyqtgraph/metaarray/license.txt @@ -0,0 +1,8 @@ +Copyright (c) 2010 Luke Campagnola ('luke.campagnola@%s.com' % 'gmail') +The MIT License +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/pyqtgraph/metaarray/readMeta.m b/pyqtgraph/metaarray/readMeta.m new file mode 100644 index 00000000..b18ad49d --- /dev/null +++ b/pyqtgraph/metaarray/readMeta.m @@ -0,0 +1,86 @@ +function f = readMeta(file) +info = hdf5info(file); +f = readMetaRecursive(info.GroupHierarchy.Groups(1)); +end + + +function f = readMetaRecursive(root) +typ = 0; +for i = 1:length(root.Attributes) + if strcmp(root.Attributes(i).Shortname, '_metaType_') + typ = root.Attributes(i).Value.Data; + break + end +end +if typ == 0 + printf('group has no _metaType_') + typ = 'dict'; +end + +list = 0; +if strcmp(typ, 'list') || strcmp(typ, 'tuple') + data = {}; + list = 1; +elseif strcmp(typ, 'dict') + data = struct(); +else + printf('Unrecognized meta type %s', typ); + data = struct(); +end + +for i = 1:length(root.Attributes) + name = root.Attributes(i).Shortname; + if strcmp(name, '_metaType_') + continue + end + val = root.Attributes(i).Value; + if isa(val, 'hdf5.h5string') + val = val.Data; + end + if list + ind = str2num(name)+1; + data{ind} = val; + else + data.(name) = val; + end +end + +for i = 1:length(root.Datasets) + fullName = root.Datasets(i).Name; + name = stripName(fullName); + file = root.Datasets(i).Filename; + data2 = hdf5read(file, fullName); + if list + ind = str2num(name)+1; + data{ind} = data2; + else + data.(name) = data2; + end +end + +for i = 1:length(root.Groups) + name = stripName(root.Groups(i).Name); + data2 = readMetaRecursive(root.Groups(i)); + if list + ind = str2num(name)+1; + data{ind} = data2; + else + data.(name) = data2; + end +end +f = data; +return; +end + + +function f = stripName(str) +inds = strfind(str, '/'); +if isempty(inds) + f = str; +else + f = str(inds(length(inds))+1:length(str)); +end +end + + + diff --git a/pyqtgraph/multiprocess/__init__.py b/pyqtgraph/multiprocess/__init__.py new file mode 100644 index 00000000..843b42a3 --- /dev/null +++ b/pyqtgraph/multiprocess/__init__.py @@ -0,0 +1,24 @@ +""" +Multiprocessing utility library +(parallelization done the way I like it) + +Luke Campagnola +2012.06.10 + +This library provides: + + - simple mechanism for starting a new python interpreter process that can be controlled from the original process + (this allows, for example, displaying and manipulating plots in a remote process + while the parent process is free to do other work) + - proxy system that allows objects hosted in the remote process to be used as if they were local + - Qt signal connection between processes + - very simple in-line parallelization (fork only; does not work on windows) for number-crunching + +TODO: + allow remote processes to serve as rendering engines that pass pixmaps back to the parent process for display + (RemoteGraphicsView class) +""" + +from .processes import * +from .parallelizer import Parallelize, CanceledError +from .remoteproxy import proxy \ No newline at end of file diff --git a/pyqtgraph/multiprocess/bootstrap.py b/pyqtgraph/multiprocess/bootstrap.py new file mode 100644 index 00000000..bb71a703 --- /dev/null +++ b/pyqtgraph/multiprocess/bootstrap.py @@ -0,0 +1,28 @@ +"""For starting up remote processes""" +import sys, pickle, os + +if __name__ == '__main__': + if hasattr(os, 'setpgrp'): + os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process + if sys.version[0] == '3': + #name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin.buffer) + opts = pickle.load(sys.stdin.buffer) + else: + #name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin) + opts = pickle.load(sys.stdin) + #print "key:", ' '.join([str(ord(x)) for x in authkey]) + path = opts.pop('path', None) + if path is not None: + ## rewrite sys.path without assigning a new object--no idea who already has a reference to the existing list. + while len(sys.path) > 0: + sys.path.pop() + sys.path.extend(path) + + if opts.pop('pyside', False): + import PySide + + + targetStr = opts.pop('targetStr') + target = pickle.loads(targetStr) ## unpickling the target should import everything we need + target(**opts) ## Send all other options to the target function + sys.exit(0) diff --git a/pyqtgraph/multiprocess/parallelizer.py b/pyqtgraph/multiprocess/parallelizer.py new file mode 100644 index 00000000..659b5efc --- /dev/null +++ b/pyqtgraph/multiprocess/parallelizer.py @@ -0,0 +1,330 @@ +import os, sys, time, multiprocessing, re +from .processes import ForkedProcess +from .remoteproxy import ClosedError + +class CanceledError(Exception): + """Raised when the progress dialog is canceled during a processing operation.""" + pass + +class Parallelize(object): + """ + Class for ultra-simple inline parallelization on multi-core CPUs + + Example:: + + ## Here is the serial (single-process) task: + + tasks = [1, 2, 4, 8] + results = [] + for task in tasks: + result = processTask(task) + results.append(result) + print(results) + + + ## Here is the parallelized version: + + tasks = [1, 2, 4, 8] + results = [] + with Parallelize(tasks, workers=4, results=results) as tasker: + for task in tasker: + result = processTask(task) + tasker.results.append(result) + print(results) + + + The only major caveat is that *result* in the example above must be picklable, + since it is automatically sent via pipe back to the parent process. + """ + + def __init__(self, tasks=None, workers=None, block=True, progressDialog=None, randomReseed=True, **kwds): + """ + =============== =================================================================== + Arguments: + tasks list of objects to be processed (Parallelize will determine how to + distribute the tasks). If unspecified, then each worker will receive + a single task with a unique id number. + workers number of worker processes or None to use number of CPUs in the + system + progressDialog optional dict of arguments for ProgressDialog + to update while tasks are processed + randomReseed If True, each forked process will reseed its random number generator + to ensure independent results. Works with the built-in random + and numpy.random. + kwds objects to be shared by proxy with child processes (they will + appear as attributes of the tasker) + =============== =================================================================== + """ + + ## Generate progress dialog. + ## Note that we want to avoid letting forked child processes play with progress dialogs.. + self.showProgress = False + if progressDialog is not None: + self.showProgress = True + if isinstance(progressDialog, basestring): + progressDialog = {'labelText': progressDialog} + import pyqtgraph as pg + self.progressDlg = pg.ProgressDialog(**progressDialog) + + if workers is None: + workers = self.suggestedWorkerCount() + if not hasattr(os, 'fork'): + workers = 1 + self.workers = workers + if tasks is None: + tasks = range(workers) + self.tasks = list(tasks) + self.reseed = randomReseed + self.kwds = kwds.copy() + self.kwds['_taskStarted'] = self._taskStarted + + def __enter__(self): + self.proc = None + if self.workers == 1: + return self.runSerial() + else: + return self.runParallel() + + def __exit__(self, *exc_info): + + if self.proc is not None: ## worker + exceptOccurred = exc_info[0] is not None ## hit an exception during processing. + + try: + if exceptOccurred: + sys.excepthook(*exc_info) + finally: + #print os.getpid(), 'exit' + os._exit(1 if exceptOccurred else 0) + + else: ## parent + if self.showProgress: + self.progressDlg.__exit__(None, None, None) + + def runSerial(self): + if self.showProgress: + self.progressDlg.__enter__() + self.progressDlg.setMaximum(len(self.tasks)) + self.progress = {os.getpid(): []} + return Tasker(self, None, self.tasks, self.kwds) + + + def runParallel(self): + self.childs = [] + + ## break up tasks into one set per worker + workers = self.workers + chunks = [[] for i in xrange(workers)] + i = 0 + for i in range(len(self.tasks)): + chunks[i%workers].append(self.tasks[i]) + + ## fork and assign tasks to each worker + for i in range(workers): + proc = ForkedProcess(target=None, preProxy=self.kwds, randomReseed=self.reseed) + if not proc.isParent: + self.proc = proc + return Tasker(self, proc, chunks[i], proc.forkedProxies) + else: + self.childs.append(proc) + + ## Keep track of the progress of each worker independently. + self.progress = dict([(ch.childPid, []) for ch in self.childs]) + ## for each child process, self.progress[pid] is a list + ## of task indexes. The last index is the task currently being + ## processed; all others are finished. + + + try: + if self.showProgress: + self.progressDlg.__enter__() + self.progressDlg.setMaximum(len(self.tasks)) + ## process events from workers until all have exited. + + activeChilds = self.childs[:] + self.exitCodes = [] + pollInterval = 0.01 + while len(activeChilds) > 0: + waitingChildren = 0 + rem = [] + for ch in activeChilds: + try: + n = ch.processRequests() + if n > 0: + waitingChildren += 1 + except ClosedError: + #print ch.childPid, 'process finished' + rem.append(ch) + if self.showProgress: + self.progressDlg += 1 + #print "remove:", [ch.childPid for ch in rem] + for ch in rem: + activeChilds.remove(ch) + while True: + try: + pid, exitcode = os.waitpid(ch.childPid, 0) + self.exitCodes.append(exitcode) + break + except OSError as ex: + if ex.errno == 4: ## If we get this error, just try again + continue + #print "Ignored system call interruption" + else: + raise + + #print [ch.childPid for ch in activeChilds] + + if self.showProgress and self.progressDlg.wasCanceled(): + for ch in activeChilds: + ch.kill() + raise CanceledError() + + ## adjust polling interval--prefer to get exactly 1 event per poll cycle. + if waitingChildren > 1: + pollInterval *= 0.7 + elif waitingChildren == 0: + pollInterval /= 0.7 + pollInterval = max(min(pollInterval, 0.5), 0.0005) ## but keep it within reasonable limits + + time.sleep(pollInterval) + finally: + if self.showProgress: + self.progressDlg.__exit__(None, None, None) + if len(self.exitCodes) < len(self.childs): + raise Exception("Parallelizer started %d processes but only received exit codes from %d." % (len(self.childs), len(self.exitCodes))) + for code in self.exitCodes: + if code != 0: + raise Exception("Error occurred in parallel-executed subprocess (console output may have more information).") + return [] ## no tasks for parent process. + + + @staticmethod + def suggestedWorkerCount(): + if 'linux' in sys.platform: + ## I think we can do a little better here.. + ## cpu_count does not consider that there is little extra benefit to using hyperthreaded cores. + try: + cores = {} + pid = None + + for line in open('/proc/cpuinfo'): + m = re.match(r'physical id\s+:\s+(\d+)', line) + if m is not None: + pid = m.groups()[0] + m = re.match(r'cpu cores\s+:\s+(\d+)', line) + if m is not None: + cores[pid] = int(m.groups()[0]) + return sum(cores.values()) + except: + return multiprocessing.cpu_count() + + else: + return multiprocessing.cpu_count() + + def _taskStarted(self, pid, i, **kwds): + ## called remotely by tasker to indicate it has started working on task i + #print pid, 'reported starting task', i + if self.showProgress: + if len(self.progress[pid]) > 0: + self.progressDlg += 1 + if pid == os.getpid(): ## single-worker process + if self.progressDlg.wasCanceled(): + raise CanceledError() + self.progress[pid].append(i) + + +class Tasker(object): + def __init__(self, parallelizer, process, tasks, kwds): + self.proc = process + self.par = parallelizer + self.tasks = tasks + for k, v in kwds.iteritems(): + setattr(self, k, v) + + def __iter__(self): + ## we could fix this up such that tasks are retrieved from the parent process one at a time.. + for i, task in enumerate(self.tasks): + self.index = i + #print os.getpid(), 'starting task', i + self._taskStarted(os.getpid(), i, _callSync='off') + yield task + if self.proc is not None: + #print os.getpid(), 'no more tasks' + self.proc.close() + + def process(self): + """ + Process requests from parent. + Usually it is not necessary to call this unless you would like to + receive messages (such as exit requests) during an iteration. + """ + if self.proc is not None: + self.proc.processRequests() + + def numWorkers(self): + """ + Return the number of parallel workers + """ + return self.par.workers + +#class Parallelizer: + #""" + #Use:: + + #p = Parallelizer() + #with p(4) as i: + #p.finish(do_work(i)) + #print p.results() + + #""" + #def __init__(self): + #pass + + #def __call__(self, n): + #self.replies = [] + #self.conn = None ## indicates this is the parent process + #return Session(self, n) + + #def finish(self, data): + #if self.conn is None: + #self.replies.append((self.i, data)) + #else: + ##print "send", self.i, data + #self.conn.send((self.i, data)) + #os._exit(0) + + #def result(self): + #print self.replies + +#class Session: + #def __init__(self, par, n): + #self.par = par + #self.n = n + + #def __enter__(self): + #self.childs = [] + #for i in range(1, self.n): + #c1, c2 = multiprocessingTest.Pipe() + #pid = os.fork() + #if pid == 0: ## child + #self.par.i = i + #self.par.conn = c2 + #self.childs = None + #c1.close() + #return i + #else: + #self.childs.append(c1) + #c2.close() + #self.par.i = 0 + #return 0 + + + + #def __exit__(self, *exc_info): + #if exc_info[0] is not None: + #sys.excepthook(*exc_info) + #if self.childs is not None: + #self.par.replies.extend([conn.recv() for conn in self.childs]) + #else: + #self.par.finish(None) + diff --git a/pyqtgraph/multiprocess/processes.py b/pyqtgraph/multiprocess/processes.py new file mode 100644 index 00000000..6d32a5a1 --- /dev/null +++ b/pyqtgraph/multiprocess/processes.py @@ -0,0 +1,472 @@ +from .remoteproxy import RemoteEventHandler, ClosedError, NoResultError, LocalObjectProxy, ObjectProxy +import subprocess, atexit, os, sys, time, random, socket, signal +import multiprocessing.connection +import pyqtgraph as pg +try: + import cPickle as pickle +except ImportError: + import pickle + +__all__ = ['Process', 'QtProcess', 'ForkedProcess', 'ClosedError', 'NoResultError'] + +class Process(RemoteEventHandler): + """ + Bases: RemoteEventHandler + + This class is used to spawn and control a new python interpreter. + It uses subprocess.Popen to start the new process and communicates with it + using multiprocessingTest.Connection objects over a network socket. + + By default, the remote process will immediately enter an event-processing + loop that carries out requests send from the parent process. + + Remote control works mainly through proxy objects:: + + proc = Process() ## starts process, returns handle + rsys = proc._import('sys') ## asks remote process to import 'sys', returns + ## a proxy which references the imported module + rsys.stdout.write('hello\n') ## This message will be printed from the remote + ## process. Proxy objects can usually be used + ## exactly as regular objects are. + proc.close() ## Request the remote process shut down + + Requests made via proxy objects may be synchronous or asynchronous and may + return objects either by proxy or by value (if they are picklable). See + ProxyObject for more information. + """ + + def __init__(self, name=None, target=None, executable=None, copySysPath=True, debug=False, timeout=20, wrapStdout=None): + """ + ============ ============================================================= + Arguments: + name Optional name for this process used when printing messages + from the remote process. + target Optional function to call after starting remote process. + By default, this is startEventLoop(), which causes the remote + process to process requests from the parent process until it + is asked to quit. If you wish to specify a different target, + it must be picklable (bound methods are not). + copySysPath If True, copy the contents of sys.path to the remote process + debug If True, print detailed information about communication + with the child process. + wrapStdout If True (default on windows) then stdout and stderr from the + child process will be caught by the parent process and + forwarded to its stdout/stderr. This provides a workaround + for a python bug: http://bugs.python.org/issue3905 + but has the side effect that child output is significantly + delayed relative to the parent output. + ============ ============================================================= + """ + if target is None: + target = startEventLoop + if name is None: + name = str(self) + if executable is None: + executable = sys.executable + self.debug = debug + + ## random authentication key + authkey = os.urandom(20) + + ## Windows seems to have a hard time with hmac + if sys.platform.startswith('win'): + authkey = None + + #print "key:", ' '.join([str(ord(x)) for x in authkey]) + ## Listen for connection from remote process (and find free port number) + port = 10000 + while True: + try: + l = multiprocessing.connection.Listener(('localhost', int(port)), authkey=authkey) + break + except socket.error as ex: + if ex.errno != 98 and ex.errno != 10048: # unix=98, win=10048 + raise + port += 1 + + + ## start remote process, instruct it to run target function + sysPath = sys.path if copySysPath else None + bootstrap = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bootstrap.py')) + self.debugMsg('Starting child process (%s %s)' % (executable, bootstrap)) + + if wrapStdout is None: + wrapStdout = sys.platform.startswith('win') + + if wrapStdout: + ## note: we need all three streams to have their own PIPE due to this bug: + ## http://bugs.python.org/issue3905 + stdout = subprocess.PIPE + stderr = subprocess.PIPE + self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE, stdout=stdout, stderr=stderr) + ## to circumvent the bug and still make the output visible, we use + ## background threads to pass data from pipes to stdout/stderr + self._stdoutForwarder = FileForwarder(self.proc.stdout, "stdout") + self._stderrForwarder = FileForwarder(self.proc.stderr, "stderr") + else: + self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE) + + targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to + ## set its sys.path properly before unpickling the target + pid = os.getpid() # we must send pid to child because windows does not have getppid + + ## Send everything the remote process needs to start correctly + data = dict( + name=name+'_child', + port=port, + authkey=authkey, + ppid=pid, + targetStr=targetStr, + path=sysPath, + pyside=pg.Qt.USE_PYSIDE, + debug=debug + ) + pickle.dump(data, self.proc.stdin) + self.proc.stdin.close() + + ## open connection for remote process + self.debugMsg('Listening for child process on port %d, authkey=%s..' % (port, repr(authkey))) + while True: + try: + conn = l.accept() + break + except IOError as err: + if err.errno == 4: # interrupted; try again + continue + else: + raise + + RemoteEventHandler.__init__(self, conn, name+'_parent', pid=self.proc.pid, debug=debug) + self.debugMsg('Connected to child process.') + + atexit.register(self.join) + + + def join(self, timeout=10): + self.debugMsg('Joining child process..') + if self.proc.poll() is None: + self.close() + start = time.time() + while self.proc.poll() is None: + if timeout is not None and time.time() - start > timeout: + raise Exception('Timed out waiting for remote process to end.') + time.sleep(0.05) + self.debugMsg('Child process exited. (%d)' % self.proc.returncode) + + def debugMsg(self, msg): + if hasattr(self, '_stdoutForwarder'): + ## Lock output from subprocess to make sure we do not get line collisions + with self._stdoutForwarder.lock: + with self._stderrForwarder.lock: + RemoteEventHandler.debugMsg(self, msg) + else: + RemoteEventHandler.debugMsg(self, msg) + + +def startEventLoop(name, port, authkey, ppid, debug=False): + if debug: + import os + print('[%d] connecting to server at port localhost:%d, authkey=%s..' % (os.getpid(), port, repr(authkey))) + conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) + if debug: + print('[%d] connected; starting remote proxy.' % os.getpid()) + global HANDLER + #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() + HANDLER = RemoteEventHandler(conn, name, ppid, debug=debug) + while True: + try: + HANDLER.processRequests() # exception raised when the loop should exit + time.sleep(0.01) + except ClosedError: + break + + +class ForkedProcess(RemoteEventHandler): + """ + ForkedProcess is a substitute for Process that uses os.fork() to generate a new process. + This is much faster than starting a completely new interpreter and child processes + automatically have a copy of the entire program state from before the fork. This + makes it an appealing approach when parallelizing expensive computations. (see + also Parallelizer) + + However, fork() comes with some caveats and limitations: + + - fork() is not available on Windows. + - It is not possible to have a QApplication in both parent and child process + (unless both QApplications are created _after_ the call to fork()) + Attempts by the forked process to access Qt GUI elements created by the parent + will most likely cause the child to crash. + - Likewise, database connections are unlikely to function correctly in a forked child. + - Threads are not copied by fork(); the new process + will have only one thread that starts wherever fork() was called in the parent process. + - Forked processes are unceremoniously terminated when join() is called; they are not + given any opportunity to clean up. (This prevents them calling any cleanup code that + was only intended to be used by the parent process) + - Normally when fork()ing, open file handles are shared with the parent process, + which is potentially dangerous. ForkedProcess is careful to close all file handles + that are not explicitly needed--stdout, stderr, and a single pipe to the parent + process. + + """ + + def __init__(self, name=None, target=0, preProxy=None, randomReseed=True): + """ + When initializing, an optional target may be given. + If no target is specified, self.eventLoop will be used. + If None is given, no target will be called (and it will be up + to the caller to properly shut down the forked process) + + preProxy may be a dict of values that will appear as ObjectProxy + in the remote process (but do not need to be sent explicitly since + they are available immediately before the call to fork(). + Proxies will be availabe as self.proxies[name]. + + If randomReseed is True, the built-in random and numpy.random generators + will be reseeded in the child process. + """ + self.hasJoined = False + if target == 0: + target = self.eventLoop + if name is None: + name = str(self) + + conn, remoteConn = multiprocessing.Pipe() + + proxyIDs = {} + if preProxy is not None: + for k, v in preProxy.iteritems(): + proxyId = LocalObjectProxy.registerObject(v) + proxyIDs[k] = proxyId + + ppid = os.getpid() # write this down now; windows doesn't have getppid + pid = os.fork() + if pid == 0: + self.isParent = False + ## We are now in the forked process; need to be extra careful what we touch while here. + ## - no reading/writing file handles/sockets owned by parent process (stdout is ok) + ## - don't touch QtGui or QApplication at all; these are landmines. + ## - don't let the process call exit handlers + + os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process + + ## close all file handles we do not want shared with parent + conn.close() + sys.stdin.close() ## otherwise we screw with interactive prompts. + fid = remoteConn.fileno() + os.closerange(3, fid) + os.closerange(fid+1, 4096) ## just guessing on the maximum descriptor count.. + + ## Override any custom exception hooks + def excepthook(*args): + import traceback + traceback.print_exception(*args) + sys.excepthook = excepthook + + ## Make it harder to access QApplication instance + if 'PyQt4.QtGui' in sys.modules: + sys.modules['PyQt4.QtGui'].QApplication = None + sys.modules.pop('PyQt4.QtGui', None) + sys.modules.pop('PyQt4.QtCore', None) + + ## sabotage atexit callbacks + atexit._exithandlers = [] + atexit.register(lambda: os._exit(0)) + + if randomReseed: + if 'numpy.random' in sys.modules: + sys.modules['numpy.random'].seed(os.getpid() ^ int(time.time()*10000%10000)) + if 'random' in sys.modules: + sys.modules['random'].seed(os.getpid() ^ int(time.time()*10000%10000)) + + #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() + RemoteEventHandler.__init__(self, remoteConn, name+'_child', pid=ppid) + + self.forkedProxies = {} + for name, proxyId in proxyIDs.iteritems(): + self.forkedProxies[name] = ObjectProxy(ppid, proxyId=proxyId, typeStr=repr(preProxy[name])) + + if target is not None: + target() + + else: + self.isParent = True + self.childPid = pid + remoteConn.close() + RemoteEventHandler.handlers = {} ## don't want to inherit any of this from the parent. + + RemoteEventHandler.__init__(self, conn, name+'_parent', pid=pid) + atexit.register(self.join) + + + def eventLoop(self): + while True: + try: + self.processRequests() # exception raised when the loop should exit + time.sleep(0.01) + except ClosedError: + break + except: + print("Error occurred in forked event loop:") + sys.excepthook(*sys.exc_info()) + sys.exit(0) + + def join(self, timeout=10): + if self.hasJoined: + return + #os.kill(pid, 9) + try: + self.close(callSync='sync', timeout=timeout, noCleanup=True) ## ask the child process to exit and require that it return a confirmation. + os.waitpid(self.childPid, 0) + except IOError: ## probably remote process has already quit + pass + self.hasJoined = True + + def kill(self): + """Immediately kill the forked remote process. + This is generally safe because forked processes are already + expected to _avoid_ any cleanup at exit.""" + os.kill(self.childPid, signal.SIGKILL) + self.hasJoined = True + + + +##Special set of subclasses that implement a Qt event loop instead. + +class RemoteQtEventHandler(RemoteEventHandler): + def __init__(self, *args, **kwds): + RemoteEventHandler.__init__(self, *args, **kwds) + + def startEventTimer(self): + from pyqtgraph.Qt import QtGui, QtCore + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.processRequests) + self.timer.start(10) + + def processRequests(self): + try: + RemoteEventHandler.processRequests(self) + except ClosedError: + from pyqtgraph.Qt import QtGui, QtCore + QtGui.QApplication.instance().quit() + self.timer.stop() + #raise SystemExit + +class QtProcess(Process): + """ + QtProcess is essentially the same as Process, with two major differences: + + - The remote process starts by running startQtEventLoop() which creates a + QApplication in the remote process and uses a QTimer to trigger + remote event processing. This allows the remote process to have its own + GUI. + - A QTimer is also started on the parent process which polls for requests + from the child process. This allows Qt signals emitted within the child + process to invoke slots on the parent process and vice-versa. This can + be disabled using processRequests=False in the constructor. + + Example:: + + proc = QtProcess() + rQtGui = proc._import('PyQt4.QtGui') + btn = rQtGui.QPushButton('button on child process') + btn.show() + + def slot(): + print('slot invoked on parent process') + btn.clicked.connect(proxy(slot)) # be sure to send a proxy of the slot + """ + + def __init__(self, **kwds): + if 'target' not in kwds: + kwds['target'] = startQtEventLoop + self._processRequests = kwds.pop('processRequests', True) + Process.__init__(self, **kwds) + self.startEventTimer() + + def startEventTimer(self): + from pyqtgraph.Qt import QtGui, QtCore ## avoid module-level import to keep bootstrap snappy. + self.timer = QtCore.QTimer() + if self._processRequests: + app = QtGui.QApplication.instance() + if app is None: + raise Exception("Must create QApplication before starting QtProcess, or use QtProcess(processRequests=False)") + self.startRequestProcessing() + + def startRequestProcessing(self, interval=0.01): + """Start listening for requests coming from the child process. + This allows signals to be connected from the child process to the parent. + """ + self.timer.timeout.connect(self.processRequests) + self.timer.start(interval*1000) + + def stopRequestProcessing(self): + self.timer.stop() + + def processRequests(self): + try: + Process.processRequests(self) + except ClosedError: + self.timer.stop() + +def startQtEventLoop(name, port, authkey, ppid, debug=False): + if debug: + import os + print('[%d] connecting to server at port localhost:%d, authkey=%s..' % (os.getpid(), port, repr(authkey))) + conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) + if debug: + print('[%d] connected; starting remote proxy.' % os.getpid()) + from pyqtgraph.Qt import QtGui, QtCore + #from PyQt4 import QtGui, QtCore + app = QtGui.QApplication.instance() + #print app + if app is None: + app = QtGui.QApplication([]) + app.setQuitOnLastWindowClosed(False) ## generally we want the event loop to stay open + ## until it is explicitly closed by the parent process. + + global HANDLER + #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() + HANDLER = RemoteQtEventHandler(conn, name, ppid, debug=debug) + HANDLER.startEventTimer() + app.exec_() + +import threading +class FileForwarder(threading.Thread): + """ + Background thread that forwards data from one pipe to another. + This is used to catch data from stdout/stderr of the child process + and print it back out to stdout/stderr. We need this because this + bug: http://bugs.python.org/issue3905 _requires_ us to catch + stdout/stderr. + + *output* may be a file or 'stdout' or 'stderr'. In the latter cases, + sys.stdout/stderr are retrieved once for every line that is output, + which ensures that the correct behavior is achieved even if + sys.stdout/stderr are replaced at runtime. + """ + def __init__(self, input, output): + threading.Thread.__init__(self) + self.input = input + self.output = output + self.lock = threading.Lock() + self.start() + + def run(self): + if self.output == 'stdout': + while True: + line = self.input.readline() + with self.lock: + sys.stdout.write(line) + elif self.output == 'stderr': + while True: + line = self.input.readline() + with self.lock: + sys.stderr.write(line) + else: + while True: + line = self.input.readline() + with self.lock: + self.output.write(line) + + + diff --git a/pyqtgraph/multiprocess/remoteproxy.py b/pyqtgraph/multiprocess/remoteproxy.py new file mode 100644 index 00000000..eba42ef3 --- /dev/null +++ b/pyqtgraph/multiprocess/remoteproxy.py @@ -0,0 +1,1069 @@ +import os, time, sys, traceback, weakref +import numpy as np +try: + import __builtin__ as builtins + import cPickle as pickle +except ImportError: + import builtins + import pickle + +class ClosedError(Exception): + """Raised when an event handler receives a request to close the connection + or discovers that the connection has been closed.""" + pass + +class NoResultError(Exception): + """Raised when a request for the return value of a remote call fails + because the call has not yet returned.""" + pass + + +class RemoteEventHandler(object): + """ + This class handles communication between two processes. One instance is present on + each process and listens for communication from the other process. This enables + (amongst other things) ObjectProxy instances to look up their attributes and call + their methods. + + This class is responsible for carrying out actions on behalf of the remote process. + Each instance holds one end of a Connection which allows python + objects to be passed between processes. + + For the most common operations, see _import(), close(), and transfer() + + To handle and respond to incoming requests, RemoteEventHandler requires that its + processRequests method is called repeatedly (this is usually handled by the Process + classes defined in multiprocess.processes). + + + + + """ + handlers = {} ## maps {process ID : handler}. This allows unpickler to determine which process + ## an object proxy belongs to + + def __init__(self, connection, name, pid, debug=False): + self.debug = debug + self.conn = connection + self.name = name + self.results = {} ## reqId: (status, result); cache of request results received from the remote process + ## status is either 'result' or 'error' + ## if 'error', then result will be (exception, formatted exceprion) + ## where exception may be None if it could not be passed through the Connection. + + self.proxies = {} ## maps {weakref(proxy): proxyId}; used to inform the remote process when a proxy has been deleted. + + ## attributes that affect the behavior of the proxy. + ## See ObjectProxy._setProxyOptions for description + self.proxyOptions = { + 'callSync': 'sync', ## 'sync', 'async', 'off' + 'timeout': 10, ## float + 'returnType': 'auto', ## 'proxy', 'value', 'auto' + 'autoProxy': False, ## bool + 'deferGetattr': False, ## True, False + 'noProxyTypes': [ type(None), str, int, float, tuple, list, dict, LocalObjectProxy, ObjectProxy ], + } + + self.nextRequestId = 0 + self.exited = False + + RemoteEventHandler.handlers[pid] = self ## register this handler as the one communicating with pid + + @classmethod + def getHandler(cls, pid): + try: + return cls.handlers[pid] + except: + print(pid, cls.handlers) + raise + + def debugMsg(self, msg): + if not self.debug: + return + print("[%d] %s" % (os.getpid(), str(msg))) + + def getProxyOption(self, opt): + return self.proxyOptions[opt] + + def setProxyOptions(self, **kwds): + """ + Set the default behavior options for object proxies. + See ObjectProxy._setProxyOptions for more info. + """ + self.proxyOptions.update(kwds) + + def processRequests(self): + """Process all pending requests from the pipe, return + after no more events are immediately available. (non-blocking) + Returns the number of events processed. + """ + if self.exited: + self.debugMsg(' processRequests: exited already; raise ClosedError.') + raise ClosedError() + + numProcessed = 0 + while self.conn.poll(): + try: + self.handleRequest() + numProcessed += 1 + except ClosedError: + self.debugMsg('processRequests: got ClosedError from handleRequest; setting exited=True.') + self.exited = True + raise + #except IOError as err: ## let handleRequest take care of this. + #self.debugMsg(' got IOError from handleRequest; try again.') + #if err.errno == 4: ## interrupted system call; try again + #continue + #else: + #raise + except: + print("Error in process %s" % self.name) + sys.excepthook(*sys.exc_info()) + + if numProcessed > 0: + self.debugMsg('processRequests: finished %d requests' % numProcessed) + return numProcessed + + def handleRequest(self): + """Handle a single request from the remote process. + Blocks until a request is available.""" + result = None + while True: + try: + ## args, kwds are double-pickled to ensure this recv() call never fails + cmd, reqId, nByteMsgs, optStr = self.conn.recv() + break + except EOFError: + self.debugMsg(' handleRequest: got EOFError from recv; raise ClosedError.') + ## remote process has shut down; end event loop + raise ClosedError() + except IOError as err: + if err.errno == 4: ## interrupted system call; try again + self.debugMsg(' handleRequest: got IOError 4 from recv; try again.') + continue + else: + self.debugMsg(' handleRequest: got IOError %d from recv (%s); raise ClosedError.' % (err.errno, err.strerror)) + raise ClosedError() + + self.debugMsg(" handleRequest: received %s %s" % (str(cmd), str(reqId))) + + ## read byte messages following the main request + byteData = [] + if nByteMsgs > 0: + self.debugMsg(" handleRequest: reading %d byte messages" % nByteMsgs) + for i in range(nByteMsgs): + while True: + try: + byteData.append(self.conn.recv_bytes()) + break + except EOFError: + self.debugMsg(" handleRequest: got EOF while reading byte messages; raise ClosedError.") + raise ClosedError() + except IOError as err: + if err.errno == 4: + self.debugMsg(" handleRequest: got IOError 4 while reading byte messages; try again.") + continue + else: + self.debugMsg(" handleRequest: got IOError while reading byte messages; raise ClosedError.") + raise ClosedError() + + + try: + if cmd == 'result' or cmd == 'error': + resultId = reqId + reqId = None ## prevents attempt to return information from this request + ## (this is already a return from a previous request) + + opts = pickle.loads(optStr) + self.debugMsg(" handleRequest: id=%s opts=%s" % (str(reqId), str(opts))) + #print os.getpid(), "received request:", cmd, reqId, opts + returnType = opts.get('returnType', 'auto') + + if cmd == 'result': + self.results[resultId] = ('result', opts['result']) + elif cmd == 'error': + self.results[resultId] = ('error', (opts['exception'], opts['excString'])) + elif cmd == 'getObjAttr': + result = getattr(opts['obj'], opts['attr']) + elif cmd == 'callObj': + obj = opts['obj'] + fnargs = opts['args'] + fnkwds = opts['kwds'] + + ## If arrays were sent as byte messages, they must be re-inserted into the + ## arguments + if len(byteData) > 0: + for i,arg in enumerate(fnargs): + if isinstance(arg, tuple) and len(arg) > 0 and arg[0] == '__byte_message__': + ind = arg[1] + dtype, shape = arg[2] + fnargs[i] = np.fromstring(byteData[ind], dtype=dtype).reshape(shape) + for k,arg in fnkwds.items(): + if isinstance(arg, tuple) and len(arg) > 0 and arg[0] == '__byte_message__': + ind = arg[1] + dtype, shape = arg[2] + fnkwds[k] = np.fromstring(byteData[ind], dtype=dtype).reshape(shape) + + if len(fnkwds) == 0: ## need to do this because some functions do not allow keyword arguments. + try: + result = obj(*fnargs) + except: + print("Failed to call object %s: %d, %s" % (obj, len(fnargs), fnargs[1:])) + raise + else: + result = obj(*fnargs, **fnkwds) + + elif cmd == 'getObjValue': + result = opts['obj'] ## has already been unpickled into its local value + returnType = 'value' + elif cmd == 'transfer': + result = opts['obj'] + returnType = 'proxy' + elif cmd == 'transferArray': + ## read array data from next message: + result = np.fromstring(byteData[0], dtype=opts['dtype']).reshape(opts['shape']) + returnType = 'proxy' + elif cmd == 'import': + name = opts['module'] + fromlist = opts.get('fromlist', []) + mod = builtins.__import__(name, fromlist=fromlist) + + if len(fromlist) == 0: + parts = name.lstrip('.').split('.') + result = mod + for part in parts[1:]: + result = getattr(result, part) + else: + result = map(mod.__getattr__, fromlist) + + elif cmd == 'del': + LocalObjectProxy.releaseProxyId(opts['proxyId']) + #del self.proxiedObjects[opts['objId']] + + elif cmd == 'close': + if reqId is not None: + result = True + returnType = 'value' + + exc = None + except: + exc = sys.exc_info() + + + + if reqId is not None: + if exc is None: + self.debugMsg(" handleRequest: sending return value for %d: %s" % (reqId, str(result))) + #print "returnValue:", returnValue, result + if returnType == 'auto': + result = self.autoProxy(result, self.proxyOptions['noProxyTypes']) + elif returnType == 'proxy': + result = LocalObjectProxy(result) + + try: + self.replyResult(reqId, result) + except: + sys.excepthook(*sys.exc_info()) + self.replyError(reqId, *sys.exc_info()) + else: + self.debugMsg(" handleRequest: returning exception for %d" % reqId) + self.replyError(reqId, *exc) + + elif exc is not None: + sys.excepthook(*exc) + + if cmd == 'close': + if opts.get('noCleanup', False) is True: + os._exit(0) ## exit immediately, do not pass GO, do not collect $200. + ## (more importantly, do not call any code that would + ## normally be invoked at exit) + else: + raise ClosedError() + + + + def replyResult(self, reqId, result): + self.send(request='result', reqId=reqId, callSync='off', opts=dict(result=result)) + + def replyError(self, reqId, *exc): + print("error: %s %s %s" % (self.name, str(reqId), str(exc[1]))) + excStr = traceback.format_exception(*exc) + try: + self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=exc[1], excString=excStr)) + except: + self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=None, excString=excStr)) + + def send(self, request, opts=None, reqId=None, callSync='sync', timeout=10, returnType=None, byteData=None, **kwds): + """Send a request or return packet to the remote process. + Generally it is not necessary to call this method directly; it is for internal use. + (The docstring has information that is nevertheless useful to the programmer + as it describes the internal protocol used to communicate between processes) + + ========== ==================================================================== + Arguments: + request String describing the type of request being sent (see below) + reqId Integer uniquely linking a result back to the request that generated + it. (most requests leave this blank) + callSync 'sync': return the actual result of the request + 'async': return a Request object which can be used to look up the + result later + 'off': return no result + timeout Time in seconds to wait for a response when callSync=='sync' + opts Extra arguments sent to the remote process that determine the way + the request will be handled (see below) + returnType 'proxy', 'value', or 'auto' + byteData If specified, this is a list of objects to be sent as byte messages + to the remote process. + This is used to send large arrays without the cost of pickling. + ========== ==================================================================== + + Description of request strings and options allowed for each: + + ============= ============= ======================================================== + request option description + ------------- ------------- -------------------------------------------------------- + getObjAttr Request the remote process return (proxy to) an + attribute of an object. + obj reference to object whose attribute should be + returned + attr string name of attribute to return + returnValue bool or 'auto' indicating whether to return a proxy or + the actual value. + + callObj Request the remote process call a function or + method. If a request ID is given, then the call's + return value will be sent back (or information + about the error that occurred while running the + function) + obj the (reference to) object to call + args tuple of arguments to pass to callable + kwds dict of keyword arguments to pass to callable + returnValue bool or 'auto' indicating whether to return a proxy or + the actual value. + + getObjValue Request the remote process return the value of + a proxied object (must be picklable) + obj reference to object whose value should be returned + + transfer Copy an object to the remote process and request + it return a proxy for the new object. + obj The object to transfer. + + import Request the remote process import new symbols + and return proxy(ies) to the imported objects + module the string name of the module to import + fromlist optional list of string names to import from module + + del Inform the remote process that a proxy has been + released (thus the remote process may be able to + release the original object) + proxyId id of proxy which is no longer referenced by + remote host + + close Instruct the remote process to stop its event loop + and exit. Optionally, this request may return a + confirmation. + + result Inform the remote process that its request has + been processed + result return value of a request + + error Inform the remote process that its request failed + exception the Exception that was raised (or None if the + exception could not be pickled) + excString string-formatted version of the exception and + traceback + ============= ===================================================================== + """ + #if len(kwds) > 0: + #print "Warning: send() ignored args:", kwds + + if opts is None: + opts = {} + + assert callSync in ['off', 'sync', 'async'], 'callSync must be one of "off", "sync", or "async"' + if reqId is None: + if callSync != 'off': ## requested return value; use the next available request ID + reqId = self.nextRequestId + self.nextRequestId += 1 + else: + ## If requestId is provided, this _must_ be a response to a previously received request. + assert request in ['result', 'error'] + + if returnType is not None: + opts['returnType'] = returnType + + #print os.getpid(), "send request:", request, reqId, opts + + ## double-pickle args to ensure that at least status and request ID get through + try: + optStr = pickle.dumps(opts) + except: + print("==== Error pickling this object: ====") + print(opts) + print("=======================================") + raise + + nByteMsgs = 0 + if byteData is not None: + nByteMsgs = len(byteData) + + ## Send primary request + request = (request, reqId, nByteMsgs, optStr) + self.debugMsg('send request: cmd=%s nByteMsgs=%d id=%s opts=%s' % (str(request[0]), nByteMsgs, str(reqId), str(opts))) + self.conn.send(request) + + ## follow up by sending byte messages + if byteData is not None: + for obj in byteData: ## Remote process _must_ be prepared to read the same number of byte messages! + self.conn.send_bytes(obj) + self.debugMsg(' sent %d byte messages' % len(byteData)) + + self.debugMsg(' call sync: %s' % callSync) + if callSync == 'off': + return + + req = Request(self, reqId, description=str(request), timeout=timeout) + if callSync == 'async': + return req + + if callSync == 'sync': + try: + return req.result() + except NoResultError: + return req + + def close(self, callSync='off', noCleanup=False, **kwds): + self.send(request='close', opts=dict(noCleanup=noCleanup), callSync=callSync, **kwds) + + def getResult(self, reqId): + ## raises NoResultError if the result is not available yet + #print self.results.keys(), os.getpid() + if reqId not in self.results: + try: + self.processRequests() + except ClosedError: ## even if remote connection has closed, we may have + ## received new data during this call to processRequests() + pass + if reqId not in self.results: + raise NoResultError() + status, result = self.results.pop(reqId) + if status == 'result': + return result + elif status == 'error': + #print ''.join(result) + exc, excStr = result + if exc is not None: + print("===== Remote process raised exception on request: =====") + print(''.join(excStr)) + print("===== Local Traceback to request follows: =====") + raise exc + else: + print(''.join(excStr)) + raise Exception("Error getting result. See above for exception from remote process.") + + else: + raise Exception("Internal error.") + + def _import(self, mod, **kwds): + """ + Request the remote process import a module (or symbols from a module) + and return the proxied results. Uses built-in __import__() function, but + adds a bit more processing: + + _import('module') => returns module + _import('module.submodule') => returns submodule + (note this differs from behavior of __import__) + _import('module', fromlist=[name1, name2, ...]) => returns [module.name1, module.name2, ...] + (this also differs from behavior of __import__) + + """ + return self.send(request='import', callSync='sync', opts=dict(module=mod), **kwds) + + def getObjAttr(self, obj, attr, **kwds): + return self.send(request='getObjAttr', opts=dict(obj=obj, attr=attr), **kwds) + + def getObjValue(self, obj, **kwds): + return self.send(request='getObjValue', opts=dict(obj=obj), **kwds) + + def callObj(self, obj, args, kwds, **opts): + opts = opts.copy() + args = list(args) + + ## Decide whether to send arguments by value or by proxy + noProxyTypes = opts.pop('noProxyTypes', None) + if noProxyTypes is None: + noProxyTypes = self.proxyOptions['noProxyTypes'] + + autoProxy = opts.pop('autoProxy', self.proxyOptions['autoProxy']) + if autoProxy is True: + args = [self.autoProxy(v, noProxyTypes) for v in args] + for k, v in kwds.iteritems(): + opts[k] = self.autoProxy(v, noProxyTypes) + + byteMsgs = [] + + ## If there are arrays in the arguments, send those as byte messages. + ## We do this because pickling arrays is too expensive. + for i,arg in enumerate(args): + if arg.__class__ == np.ndarray: + args[i] = ("__byte_message__", len(byteMsgs), (arg.dtype, arg.shape)) + byteMsgs.append(arg) + for k,v in kwds.items(): + if v.__class__ == np.ndarray: + kwds[k] = ("__byte_message__", len(byteMsgs), (v.dtype, v.shape)) + byteMsgs.append(v) + + return self.send(request='callObj', opts=dict(obj=obj, args=args, kwds=kwds), byteData=byteMsgs, **opts) + + def registerProxy(self, proxy): + ref = weakref.ref(proxy, self.deleteProxy) + self.proxies[ref] = proxy._proxyId + + def deleteProxy(self, ref): + proxyId = self.proxies.pop(ref) + try: + self.send(request='del', opts=dict(proxyId=proxyId), callSync='off') + except IOError: ## if remote process has closed down, there is no need to send delete requests anymore + pass + + def transfer(self, obj, **kwds): + """ + Transfer an object by value to the remote host (the object must be picklable) + and return a proxy for the new remote object. + """ + if obj.__class__ is np.ndarray: + opts = {'dtype': obj.dtype, 'shape': obj.shape} + return self.send(request='transferArray', opts=opts, byteData=[obj], **kwds) + else: + return self.send(request='transfer', opts=dict(obj=obj), **kwds) + + def autoProxy(self, obj, noProxyTypes): + ## Return object wrapped in LocalObjectProxy _unless_ its type is in noProxyTypes. + for typ in noProxyTypes: + if isinstance(obj, typ): + return obj + return LocalObjectProxy(obj) + + +class Request(object): + """ + Request objects are returned when calling an ObjectProxy in asynchronous mode + or if a synchronous call has timed out. Use hasResult() to ask whether + the result of the call has been returned yet. Use result() to get + the returned value. + """ + def __init__(self, process, reqId, description=None, timeout=10): + self.proc = process + self.description = description + self.reqId = reqId + self.gotResult = False + self._result = None + self.timeout = timeout + + def result(self, block=True, timeout=None): + """ + Return the result for this request. + + If block is True, wait until the result has arrived or *timeout* seconds passes. + If the timeout is reached, raise NoResultError. (use timeout=None to disable) + If block is False, raise NoResultError immediately if the result has not arrived yet. + + If the process's connection has closed before the result arrives, raise ClosedError. + """ + + if self.gotResult: + return self._result + + if timeout is None: + timeout = self.timeout + + if block: + start = time.time() + while not self.hasResult(): + if self.proc.exited: + raise ClosedError() + time.sleep(0.005) + if timeout >= 0 and time.time() - start > timeout: + print("Request timed out: %s" % self.description) + import traceback + traceback.print_stack() + raise NoResultError() + return self._result + else: + self._result = self.proc.getResult(self.reqId) ## raises NoResultError if result is not available yet + self.gotResult = True + return self._result + + def hasResult(self): + """Returns True if the result for this request has arrived.""" + try: + self.result(block=False) + except NoResultError: + pass + + return self.gotResult + +class LocalObjectProxy(object): + """ + Used for wrapping local objects to ensure that they are send by proxy to a remote host. + Note that 'proxy' is just a shorter alias for LocalObjectProxy. + + For example:: + + data = [1,2,3,4,5] + remotePlot.plot(data) ## by default, lists are pickled and sent by value + remotePlot.plot(proxy(data)) ## force the object to be sent by proxy + + """ + nextProxyId = 0 + proxiedObjects = {} ## maps {proxyId: object} + + + @classmethod + def registerObject(cls, obj): + ## assign it a unique ID so we can keep a reference to the local object + + pid = cls.nextProxyId + cls.nextProxyId += 1 + cls.proxiedObjects[pid] = obj + #print "register:", cls.proxiedObjects + return pid + + @classmethod + def lookupProxyId(cls, pid): + return cls.proxiedObjects[pid] + + @classmethod + def releaseProxyId(cls, pid): + del cls.proxiedObjects[pid] + #print "release:", cls.proxiedObjects + + def __init__(self, obj, **opts): + """ + Create a 'local' proxy object that, when sent to a remote host, + will appear as a normal ObjectProxy to *obj*. + Any extra keyword arguments are passed to proxy._setProxyOptions() + on the remote side. + """ + self.processId = os.getpid() + #self.objectId = id(obj) + self.typeStr = repr(obj) + #self.handler = handler + self.obj = obj + self.opts = opts + + def __reduce__(self): + ## a proxy is being pickled and sent to a remote process. + ## every time this happens, a new proxy will be generated in the remote process, + ## so we keep a new ID so we can track when each is released. + pid = LocalObjectProxy.registerObject(self.obj) + return (unpickleObjectProxy, (self.processId, pid, self.typeStr, None, self.opts)) + +## alias +proxy = LocalObjectProxy + +def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None, opts=None): + if processId == os.getpid(): + obj = LocalObjectProxy.lookupProxyId(proxyId) + if attributes is not None: + for attr in attributes: + obj = getattr(obj, attr) + return obj + else: + proxy = ObjectProxy(processId, proxyId=proxyId, typeStr=typeStr) + if opts is not None: + proxy._setProxyOptions(**opts) + return proxy + +class ObjectProxy(object): + """ + Proxy to an object stored by the remote process. Proxies are created + by calling Process._import(), Process.transfer(), or by requesting/calling + attributes on existing proxy objects. + + For the most part, this object can be used exactly as if it + were a local object:: + + rsys = proc._import('sys') # returns proxy to sys module on remote process + rsys.stdout # proxy to remote sys.stdout + rsys.stdout.write # proxy to remote sys.stdout.write + rsys.stdout.write('hello') # calls sys.stdout.write('hello') on remote machine + # and returns the result (None) + + When calling a proxy to a remote function, the call can be made synchronous + (result of call is returned immediately), asynchronous (result is returned later), + or return can be disabled entirely:: + + ros = proc._import('os') + + ## synchronous call; result is returned immediately + pid = ros.getpid() + + ## asynchronous call + request = ros.getpid(_callSync='async') + while not request.hasResult(): + time.sleep(0.01) + pid = request.result() + + ## disable return when we know it isn't needed + rsys.stdout.write('hello', _callSync='off') + + Additionally, values returned from a remote function call are automatically + returned either by value (must be picklable) or by proxy. + This behavior can be forced:: + + rnp = proc._import('numpy') + arrProxy = rnp.array([1,2,3,4], _returnType='proxy') + arrValue = rnp.array([1,2,3,4], _returnType='value') + + The default callSync and returnType behaviors (as well as others) can be set + for each proxy individually using ObjectProxy._setProxyOptions() or globally using + proc.setProxyOptions(). + + """ + def __init__(self, processId, proxyId, typeStr='', parent=None): + object.__init__(self) + ## can't set attributes directly because setattr is overridden. + self.__dict__['_processId'] = processId + self.__dict__['_typeStr'] = typeStr + self.__dict__['_proxyId'] = proxyId + self.__dict__['_attributes'] = () + ## attributes that affect the behavior of the proxy. + ## in all cases, a value of None causes the proxy to ask + ## its parent event handler to make the decision + self.__dict__['_proxyOptions'] = { + 'callSync': None, ## 'sync', 'async', None + 'timeout': None, ## float, None + 'returnType': None, ## 'proxy', 'value', 'auto', None + 'deferGetattr': None, ## True, False, None + 'noProxyTypes': None, ## list of types to send by value instead of by proxy + } + + self.__dict__['_handler'] = RemoteEventHandler.getHandler(processId) + self.__dict__['_handler'].registerProxy(self) ## handler will watch proxy; inform remote process when the proxy is deleted. + + def _setProxyOptions(self, **kwds): + """ + Change the behavior of this proxy. For all options, a value of None + will cause the proxy to instead use the default behavior defined + by its parent Process. + + Options are: + + ============= ============================================================= + callSync 'sync', 'async', 'off', or None. + If 'async', then calling methods will return a Request object + which can be used to inquire later about the result of the + method call. + If 'sync', then calling a method + will block until the remote process has returned its result + or the timeout has elapsed (in this case, a Request object + is returned instead). + If 'off', then the remote process is instructed _not_ to + reply and the method call will return None immediately. + returnType 'auto', 'proxy', 'value', or None. + If 'proxy', then the value returned when calling a method + will be a proxy to the object on the remote process. + If 'value', then attempt to pickle the returned object and + send it back. + If 'auto', then the decision is made by consulting the + 'noProxyTypes' option. + autoProxy bool or None. If True, arguments to __call__ are + automatically converted to proxy unless their type is + listed in noProxyTypes (see below). If False, arguments + are left untouched. Use proxy(obj) to manually convert + arguments before sending. + timeout float or None. Length of time to wait during synchronous + requests before returning a Request object instead. + deferGetattr True, False, or None. + If False, all attribute requests will be sent to the remote + process immediately and will block until a response is + received (or timeout has elapsed). + If True, requesting an attribute from the proxy returns a + new proxy immediately. The remote process is _not_ contacted + to make this request. This is faster, but it is possible to + request an attribute that does not exist on the proxied + object. In this case, AttributeError will not be raised + until an attempt is made to look up the attribute on the + remote process. + noProxyTypes List of object types that should _not_ be proxied when + sent to the remote process. + ============= ============================================================= + """ + self._proxyOptions.update(kwds) + + def _getValue(self): + """ + Return the value of the proxied object + (the remote object must be picklable) + """ + return self._handler.getObjValue(self) + + def _getProxyOption(self, opt): + val = self._proxyOptions[opt] + if val is None: + return self._handler.getProxyOption(opt) + return val + + def _getProxyOptions(self): + return dict([(k, self._getProxyOption(k)) for k in self._proxyOptions]) + + def __reduce__(self): + return (unpickleObjectProxy, (self._processId, self._proxyId, self._typeStr, self._attributes)) + + def __repr__(self): + #objRepr = self.__getattr__('__repr__')(callSync='value') + return "" % (self._processId, self._proxyId, self._typeStr) + + + def __getattr__(self, attr, **kwds): + """ + Calls __getattr__ on the remote object and returns the attribute + by value or by proxy depending on the options set (see + ObjectProxy._setProxyOptions and RemoteEventHandler.setProxyOptions) + + If the option 'deferGetattr' is True for this proxy, then a new proxy object + is returned _without_ asking the remote object whether the named attribute exists. + This can save time when making multiple chained attribute requests, + but may also defer a possible AttributeError until later, making + them more difficult to debug. + """ + opts = self._getProxyOptions() + for k in opts: + if '_'+k in kwds: + opts[k] = kwds.pop('_'+k) + if opts['deferGetattr'] is True: + return self._deferredAttr(attr) + else: + #opts = self._getProxyOptions() + return self._handler.getObjAttr(self, attr, **opts) + + def _deferredAttr(self, attr): + return DeferredObjectProxy(self, attr) + + def __call__(self, *args, **kwds): + """ + Attempts to call the proxied object from the remote process. + Accepts extra keyword arguments: + + _callSync 'off', 'sync', or 'async' + _returnType 'value', 'proxy', or 'auto' + + If the remote call raises an exception on the remote process, + it will be re-raised on the local process. + + """ + opts = self._getProxyOptions() + for k in opts: + if '_'+k in kwds: + opts[k] = kwds.pop('_'+k) + return self._handler.callObj(obj=self, args=args, kwds=kwds, **opts) + + + ## Explicitly proxy special methods. Is there a better way to do this?? + + def _getSpecialAttr(self, attr): + ## this just gives us an easy way to change the behavior of the special methods + return self._deferredAttr(attr) + + def __getitem__(self, *args): + return self._getSpecialAttr('__getitem__')(*args) + + def __setitem__(self, *args): + return self._getSpecialAttr('__setitem__')(*args, _callSync='off') + + def __setattr__(self, *args): + return self._getSpecialAttr('__setattr__')(*args, _callSync='off') + + def __str__(self, *args): + return self._getSpecialAttr('__str__')(*args, _returnType='value') + + def __len__(self, *args): + return self._getSpecialAttr('__len__')(*args) + + def __add__(self, *args): + return self._getSpecialAttr('__add__')(*args) + + def __sub__(self, *args): + return self._getSpecialAttr('__sub__')(*args) + + def __div__(self, *args): + return self._getSpecialAttr('__div__')(*args) + + def __truediv__(self, *args): + return self._getSpecialAttr('__truediv__')(*args) + + def __floordiv__(self, *args): + return self._getSpecialAttr('__floordiv__')(*args) + + def __mul__(self, *args): + return self._getSpecialAttr('__mul__')(*args) + + def __pow__(self, *args): + return self._getSpecialAttr('__pow__')(*args) + + def __iadd__(self, *args): + return self._getSpecialAttr('__iadd__')(*args, _callSync='off') + + def __isub__(self, *args): + return self._getSpecialAttr('__isub__')(*args, _callSync='off') + + def __idiv__(self, *args): + return self._getSpecialAttr('__idiv__')(*args, _callSync='off') + + def __itruediv__(self, *args): + return self._getSpecialAttr('__itruediv__')(*args, _callSync='off') + + def __ifloordiv__(self, *args): + return self._getSpecialAttr('__ifloordiv__')(*args, _callSync='off') + + def __imul__(self, *args): + return self._getSpecialAttr('__imul__')(*args, _callSync='off') + + def __ipow__(self, *args): + return self._getSpecialAttr('__ipow__')(*args, _callSync='off') + + def __rshift__(self, *args): + return self._getSpecialAttr('__rshift__')(*args) + + def __lshift__(self, *args): + return self._getSpecialAttr('__lshift__')(*args) + + def __irshift__(self, *args): + return self._getSpecialAttr('__irshift__')(*args, _callSync='off') + + def __ilshift__(self, *args): + return self._getSpecialAttr('__ilshift__')(*args, _callSync='off') + + def __eq__(self, *args): + return self._getSpecialAttr('__eq__')(*args) + + def __ne__(self, *args): + return self._getSpecialAttr('__ne__')(*args) + + def __lt__(self, *args): + return self._getSpecialAttr('__lt__')(*args) + + def __gt__(self, *args): + return self._getSpecialAttr('__gt__')(*args) + + def __le__(self, *args): + return self._getSpecialAttr('__le__')(*args) + + def __ge__(self, *args): + return self._getSpecialAttr('__ge__')(*args) + + def __and__(self, *args): + return self._getSpecialAttr('__and__')(*args) + + def __or__(self, *args): + return self._getSpecialAttr('__or__')(*args) + + def __xor__(self, *args): + return self._getSpecialAttr('__xor__')(*args) + + def __iand__(self, *args): + return self._getSpecialAttr('__iand__')(*args, _callSync='off') + + def __ior__(self, *args): + return self._getSpecialAttr('__ior__')(*args, _callSync='off') + + def __ixor__(self, *args): + return self._getSpecialAttr('__ixor__')(*args, _callSync='off') + + def __mod__(self, *args): + return self._getSpecialAttr('__mod__')(*args) + + def __radd__(self, *args): + return self._getSpecialAttr('__radd__')(*args) + + def __rsub__(self, *args): + return self._getSpecialAttr('__rsub__')(*args) + + def __rdiv__(self, *args): + return self._getSpecialAttr('__rdiv__')(*args) + + def __rfloordiv__(self, *args): + return self._getSpecialAttr('__rfloordiv__')(*args) + + def __rtruediv__(self, *args): + return self._getSpecialAttr('__rtruediv__')(*args) + + def __rmul__(self, *args): + return self._getSpecialAttr('__rmul__')(*args) + + def __rpow__(self, *args): + return self._getSpecialAttr('__rpow__')(*args) + + def __rrshift__(self, *args): + return self._getSpecialAttr('__rrshift__')(*args) + + def __rlshift__(self, *args): + return self._getSpecialAttr('__rlshift__')(*args) + + def __rand__(self, *args): + return self._getSpecialAttr('__rand__')(*args) + + def __ror__(self, *args): + return self._getSpecialAttr('__ror__')(*args) + + def __rxor__(self, *args): + return self._getSpecialAttr('__ror__')(*args) + + def __rmod__(self, *args): + return self._getSpecialAttr('__rmod__')(*args) + + def __hash__(self): + ## Required for python3 since __eq__ is defined. + return id(self) + +class DeferredObjectProxy(ObjectProxy): + """ + This class represents an attribute (or sub-attribute) of a proxied object. + It is used to speed up attribute requests. Take the following scenario:: + + rsys = proc._import('sys') + rsys.stdout.write('hello') + + For this simple example, a total of 4 synchronous requests are made to + the remote process: + + 1) import sys + 2) getattr(sys, 'stdout') + 3) getattr(stdout, 'write') + 4) write('hello') + + This takes a lot longer than running the equivalent code locally. To + speed things up, we can 'defer' the two attribute lookups so they are + only carried out when neccessary:: + + rsys = proc._import('sys') + rsys._setProxyOptions(deferGetattr=True) + rsys.stdout.write('hello') + + This example only makes two requests to the remote process; the two + attribute lookups immediately return DeferredObjectProxy instances + immediately without contacting the remote process. When the call + to write() is made, all attribute requests are processed at the same time. + + Note that if the attributes requested do not exist on the remote object, + making the call to write() will raise an AttributeError. + """ + def __init__(self, parentProxy, attribute): + ## can't set attributes directly because setattr is overridden. + for k in ['_processId', '_typeStr', '_proxyId', '_handler']: + self.__dict__[k] = getattr(parentProxy, k) + self.__dict__['_parent'] = parentProxy ## make sure parent stays alive + self.__dict__['_attributes'] = parentProxy._attributes + (attribute,) + self.__dict__['_proxyOptions'] = parentProxy._proxyOptions.copy() + + def __repr__(self): + return ObjectProxy.__repr__(self) + '.' + '.'.join(self._attributes) + + def _undefer(self): + """ + Return a non-deferred ObjectProxy referencing the same object + """ + return self._parent.__getattr__(self._attributes[-1], _deferGetattr=False) + diff --git a/pyqtgraph/numpy_fix.py b/pyqtgraph/numpy_fix.py new file mode 100644 index 00000000..2fa8ef1f --- /dev/null +++ b/pyqtgraph/numpy_fix.py @@ -0,0 +1,22 @@ +try: + import numpy as np + + ## Wrap np.concatenate to catch and avoid a segmentation fault bug + ## (numpy trac issue #2084) + if not hasattr(np, 'concatenate_orig'): + np.concatenate_orig = np.concatenate + def concatenate(vals, *args, **kwds): + """Wrapper around numpy.concatenate (see pyqtgraph/numpy_fix.py)""" + dtypes = [getattr(v, 'dtype', None) for v in vals] + names = [getattr(dt, 'names', None) for dt in dtypes] + if len(dtypes) < 2 or all([n is None for n in names]): + return np.concatenate_orig(vals, *args, **kwds) + if any([dt != dtypes[0] for dt in dtypes[1:]]): + raise TypeError("Cannot concatenate structured arrays of different dtype.") + return np.concatenate_orig(vals, *args, **kwds) + + np.concatenate = concatenate + +except ImportError: + pass + diff --git a/pyqtgraph/opengl/GLGraphicsItem.py b/pyqtgraph/opengl/GLGraphicsItem.py new file mode 100644 index 00000000..9680fba7 --- /dev/null +++ b/pyqtgraph/opengl/GLGraphicsItem.py @@ -0,0 +1,293 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph import Transform3D +from OpenGL.GL import * +from OpenGL import GL + +GLOptions = { + 'opaque': { + GL_DEPTH_TEST: True, + GL_BLEND: False, + GL_ALPHA_TEST: False, + GL_CULL_FACE: False, + }, + 'translucent': { + GL_DEPTH_TEST: True, + GL_BLEND: True, + GL_ALPHA_TEST: False, + GL_CULL_FACE: False, + 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), + }, + 'additive': { + GL_DEPTH_TEST: False, + GL_BLEND: True, + GL_ALPHA_TEST: False, + GL_CULL_FACE: False, + 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE), + }, +} + + +class GLGraphicsItem(QtCore.QObject): + def __init__(self, parentItem=None): + QtCore.QObject.__init__(self) + self.__parent = None + self.__view = None + self.__children = set() + self.__transform = Transform3D() + self.__visible = True + self.setParentItem(parentItem) + self.setDepthValue(0) + self.__glOpts = {} + + def setParentItem(self, item): + """Set this item's parent in the scenegraph hierarchy.""" + if self.__parent is not None: + self.__parent.__children.remove(self) + if item is not None: + item.__children.add(self) + self.__parent = item + + if self.__parent is not None and self.view() is not self.__parent.view(): + if self.view() is not None: + self.view().removeItem(self) + self.__parent.view().addItem(self) + + def setGLOptions(self, opts): + """ + Set the OpenGL state options to use immediately before drawing this item. + (Note that subclasses must call setupGLState before painting for this to work) + + The simplest way to invoke this method is to pass in the name of + a predefined set of options (see the GLOptions variable): + + ============= ====================================================== + opaque Enables depth testing and disables blending + translucent Enables depth testing and blending + Elements must be drawn sorted back-to-front for + translucency to work correctly. + additive Disables depth testing, enables blending. + Colors are added together, so sorting is not required. + ============= ====================================================== + + It is also possible to specify any arbitrary settings as a dictionary. + This may consist of {'functionName': (args...)} pairs where functionName must + be a callable attribute of OpenGL.GL, or {GL_STATE_VAR: bool} pairs + which will be interpreted as calls to glEnable or glDisable(GL_STATE_VAR). + + For example:: + + { + GL_ALPHA_TEST: True, + GL_CULL_FACE: False, + 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), + } + + + """ + if isinstance(opts, basestring): + opts = GLOptions[opts] + self.__glOpts = opts.copy() + self.update() + + def updateGLOptions(self, opts): + """ + Modify the OpenGL state options to use immediately before drawing this item. + *opts* must be a dictionary as specified by setGLOptions. + Values may also be None, in which case the key will be ignored. + """ + self.__glOpts.update(opts) + + + def parentItem(self): + """Return a this item's parent in the scenegraph hierarchy.""" + return self.__parent + + def childItems(self): + """Return a list of this item's children in the scenegraph hierarchy.""" + return list(self.__children) + + def _setView(self, v): + self.__view = v + + def view(self): + return self.__view + + def setDepthValue(self, value): + """ + Sets the depth value of this item. Default is 0. + This controls the order in which items are drawn--those with a greater depth value will be drawn later. + Items with negative depth values are drawn before their parent. + (This is analogous to QGraphicsItem.zValue) + The depthValue does NOT affect the position of the item or the values it imparts to the GL depth buffer. + """ + self.__depthValue = value + + def depthValue(self): + """Return the depth value of this item. See setDepthValue for more information.""" + return self.__depthValue + + def setTransform(self, tr): + """Set the local transform for this object. + Must be a :class:`Transform3D ` instance. This transform + determines how the local coordinate system of the item is mapped to the coordinate + system of its parent.""" + self.__transform = Transform3D(tr) + self.update() + + def resetTransform(self): + """Reset this item's transform to an identity transformation.""" + self.__transform.setToIdentity() + self.update() + + def applyTransform(self, tr, local): + """ + Multiply this object's transform by *tr*. + If local is True, then *tr* is multiplied on the right of the current transform:: + + newTransform = transform * tr + + If local is False, then *tr* is instead multiplied on the left:: + + newTransform = tr * transform + """ + if local: + self.setTransform(self.transform() * tr) + else: + self.setTransform(tr * self.transform()) + + def transform(self): + """Return this item's transform object.""" + return self.__transform + + def viewTransform(self): + """Return the transform mapping this item's local coordinate system to the + view coordinate system.""" + tr = self.__transform + p = self + while True: + p = p.parentItem() + if p is None: + break + tr = p.transform() * tr + return Transform3D(tr) + + def translate(self, dx, dy, dz, local=False): + """ + Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system. + If *local* is True, then translation takes place in local coordinates. + """ + tr = Transform3D() + tr.translate(dx, dy, dz) + self.applyTransform(tr, local=local) + + def rotate(self, angle, x, y, z, local=False): + """ + Rotate the object around the axis specified by (x,y,z). + *angle* is in degrees. + + """ + tr = Transform3D() + tr.rotate(angle, x, y, z) + self.applyTransform(tr, local=local) + + def scale(self, x, y, z, local=True): + """ + Scale the object by (*dx*, *dy*, *dz*) in its local coordinate system. + If *local* is False, then scale takes place in the parent's coordinates. + """ + tr = Transform3D() + tr.scale(x, y, z) + self.applyTransform(tr, local=local) + + + def hide(self): + """Hide this item. + This is equivalent to setVisible(False).""" + self.setVisible(False) + + def show(self): + """Make this item visible if it was previously hidden. + This is equivalent to setVisible(True).""" + self.setVisible(True) + + def setVisible(self, vis): + """Set the visibility of this item.""" + self.__visible = vis + self.update() + + def visible(self): + """Return True if the item is currently set to be visible. + Note that this does not guarantee that the item actually appears in the + view, as it may be obscured or outside of the current view area.""" + return self.__visible + + + def initializeGL(self): + """ + Called after an item is added to a GLViewWidget. + The widget's GL context is made current before this method is called. + (So this would be an appropriate time to generate lists, upload textures, etc.) + """ + pass + + def setupGLState(self): + """ + This method is responsible for preparing the GL state options needed to render + this item (blending, depth testing, etc). The method is called immediately before painting the item. + """ + for k,v in self.__glOpts.items(): + if v is None: + continue + if isinstance(k, basestring): + func = getattr(GL, k) + func(*v) + else: + if v is True: + glEnable(k) + else: + glDisable(k) + + def paint(self): + """ + Called by the GLViewWidget to draw this item. + It is the responsibility of the item to set up its own modelview matrix, + but the caller will take care of pushing/popping. + """ + self.setupGLState() + + def update(self): + """ + Indicates that this item needs to be redrawn, and schedules an update + with the view it is displayed in. + """ + v = self.view() + if v is None: + return + v.update() + + def mapToParent(self, point): + tr = self.transform() + if tr is None: + return point + return tr.map(point) + + def mapFromParent(self, point): + tr = self.transform() + if tr is None: + return point + return tr.inverted()[0].map(point) + + def mapToView(self, point): + tr = self.viewTransform() + if tr is None: + return point + return tr.map(point) + + def mapFromView(self, point): + tr = self.viewTransform() + if tr is None: + return point + return tr.inverted()[0].map(point) + + + \ No newline at end of file diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py new file mode 100644 index 00000000..89fef92e --- /dev/null +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -0,0 +1,436 @@ +from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL +from OpenGL.GL import * +import OpenGL.GL.framebufferobjects as glfbo +import numpy as np +from pyqtgraph import Vector +import pyqtgraph.functions as fn + +##Vector = QtGui.QVector3D + +class GLViewWidget(QtOpenGL.QGLWidget): + """ + Basic widget for displaying 3D data + - Rotation/scale controls + - Axis/grid display + - Export options + + """ + + ShareWidget = None + + def __init__(self, parent=None): + if GLViewWidget.ShareWidget is None: + ## create a dummy widget to allow sharing objects (textures, shaders, etc) between views + GLViewWidget.ShareWidget = QtOpenGL.QGLWidget() + + QtOpenGL.QGLWidget.__init__(self, parent, GLViewWidget.ShareWidget) + + self.setFocusPolicy(QtCore.Qt.ClickFocus) + + self.opts = { + 'center': Vector(0,0,0), ## will always appear at the center of the widget + 'distance': 10.0, ## distance of camera from center + 'fov': 60, ## horizontal field of view in degrees + 'elevation': 30, ## camera's angle of elevation in degrees + 'azimuth': 45, ## camera's azimuthal angle in degrees + ## (rotation around z-axis 0 points along x-axis) + 'viewport': None, ## glViewport params; None == whole widget + } + self.items = [] + self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown] + self.keysPressed = {} + self.keyTimer = QtCore.QTimer() + self.keyTimer.timeout.connect(self.evalKeyState) + + self.makeCurrent() + + def addItem(self, item): + self.items.append(item) + if hasattr(item, 'initializeGL'): + self.makeCurrent() + try: + item.initializeGL() + except: + self.checkOpenGLVersion('Error while adding item %s to GLViewWidget.' % str(item)) + + item._setView(self) + #print "set view", item, self, item.view() + self.update() + + def removeItem(self, item): + self.items.remove(item) + item._setView(None) + self.update() + + + def initializeGL(self): + glClearColor(0.0, 0.0, 0.0, 0.0) + self.resizeGL(self.width(), self.height()) + + def getViewport(self): + vp = self.opts['viewport'] + if vp is None: + return (0, 0, self.width(), self.height()) + else: + return vp + + def resizeGL(self, w, h): + pass + #glViewport(*self.getViewport()) + #self.update() + + def setProjection(self, region=None): + m = self.projectionMatrix(region) + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + a = np.array(m.copyDataTo()).reshape((4,4)) + glMultMatrixf(a.transpose()) + + def projectionMatrix(self, region=None): + # Xw = (Xnd + 1) * width/2 + X + if region is None: + region = (0, 0, self.width(), self.height()) + + x0, y0, w, h = self.getViewport() + dist = self.opts['distance'] + fov = self.opts['fov'] + nearClip = dist * 0.001 + farClip = dist * 1000. + + r = nearClip * np.tan(fov * 0.5 * np.pi / 180.) + t = r * h / w + + # convert screen coordinates (region) to normalized device coordinates + # Xnd = (Xw - X0) * 2/width - 1 + ## Note that X0 and width in these equations must be the values used in viewport + left = r * ((region[0]-x0) * (2.0/w) - 1) + right = r * ((region[0]+region[2]-x0) * (2.0/w) - 1) + bottom = t * ((region[1]-y0) * (2.0/h) - 1) + top = t * ((region[1]+region[3]-y0) * (2.0/h) - 1) + + tr = QtGui.QMatrix4x4() + tr.frustum(left, right, bottom, top, nearClip, farClip) + return tr + + def setModelview(self): + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + m = self.viewMatrix() + a = np.array(m.copyDataTo()).reshape((4,4)) + glMultMatrixf(a.transpose()) + + def viewMatrix(self): + tr = QtGui.QMatrix4x4() + tr.translate( 0.0, 0.0, -self.opts['distance']) + tr.rotate(self.opts['elevation']-90, 1, 0, 0) + tr.rotate(self.opts['azimuth']+90, 0, 0, -1) + center = self.opts['center'] + tr.translate(-center.x(), -center.y(), -center.z()) + return tr + + def itemsAt(self, region=None): + #buf = np.zeros(100000, dtype=np.uint) + buf = glSelectBuffer(100000) + try: + glRenderMode(GL_SELECT) + glInitNames() + glPushName(0) + self._itemNames = {} + self.paintGL(region=region, useItemNames=True) + + finally: + hits = glRenderMode(GL_RENDER) + + items = [(h.near, h.names[0]) for h in hits] + items.sort(key=lambda i: i[0]) + + return [self._itemNames[i[1]] for i in items] + + def paintGL(self, region=None, viewport=None, useItemNames=False): + """ + viewport specifies the arguments to glViewport. If None, then we use self.opts['viewport'] + region specifies the sub-region of self.opts['viewport'] that should be rendered. + Note that we may use viewport != self.opts['viewport'] when exporting. + """ + if viewport is None: + glViewport(*self.getViewport()) + else: + glViewport(*viewport) + self.setProjection(region=region) + self.setModelview() + glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT ) + self.drawItemTree(useItemNames=useItemNames) + + def drawItemTree(self, item=None, useItemNames=False): + if item is None: + items = [x for x in self.items if x.parentItem() is None] + else: + items = item.childItems() + items.append(item) + items.sort(key=lambda a: a.depthValue()) + for i in items: + if not i.visible(): + continue + if i is item: + try: + glPushAttrib(GL_ALL_ATTRIB_BITS) + if useItemNames: + glLoadName(id(i)) + self._itemNames[id(i)] = i + i.paint() + except: + import pyqtgraph.debug + pyqtgraph.debug.printExc() + msg = "Error while drawing item %s." % str(item) + ver = glGetString(GL_VERSION) + if ver is not None: + ver = ver.split()[0] + if int(ver.split(b'.')[0]) < 2: + print(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver) + else: + print(msg) + + finally: + glPopAttrib() + else: + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + try: + tr = i.transform() + a = np.array(tr.copyDataTo()).reshape((4,4)) + glMultMatrixf(a.transpose()) + self.drawItemTree(i, useItemNames=useItemNames) + finally: + glMatrixMode(GL_MODELVIEW) + glPopMatrix() + + def setCameraPosition(self, pos=None, distance=None, elevation=None, azimuth=None): + if distance is not None: + self.opts['distance'] = distance + if elevation is not None: + self.opts['elevation'] = elevation + if azimuth is not None: + self.opts['azimuth'] = azimuth + self.update() + + + + def cameraPosition(self): + """Return current position of camera based on center, dist, elevation, and azimuth""" + center = self.opts['center'] + dist = self.opts['distance'] + elev = self.opts['elevation'] * np.pi/180. + azim = self.opts['azimuth'] * np.pi/180. + + pos = Vector( + center.x() + dist * np.cos(elev) * np.cos(azim), + center.y() + dist * np.cos(elev) * np.sin(azim), + center.z() + dist * np.sin(elev) + ) + + return pos + + def orbit(self, azim, elev): + """Orbits the camera around the center position. *azim* and *elev* are given in degrees.""" + self.opts['azimuth'] += azim + #self.opts['elevation'] += elev + self.opts['elevation'] = np.clip(self.opts['elevation'] + elev, -90, 90) + self.update() + + def pan(self, dx, dy, dz, relative=False): + """ + Moves the center (look-at) position while holding the camera in place. + + If relative=True, then the coordinates are interpreted such that x + if in the global xy plane and points to the right side of the view, y is + in the global xy plane and orthogonal to x, and z points in the global z + direction. Distances are scaled roughly such that a value of 1.0 moves + by one pixel on screen. + + """ + if not relative: + self.opts['center'] += QtGui.QVector3D(dx, dy, dz) + else: + cPos = self.cameraPosition() + cVec = self.opts['center'] - cPos + dist = cVec.length() ## distance from camera to center + xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) ## approx. width of view at distance of center point + xScale = xDist / self.width() + zVec = QtGui.QVector3D(0,0,1) + xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized() + yVec = QtGui.QVector3D.crossProduct(xVec, zVec).normalized() + self.opts['center'] = self.opts['center'] + xVec * xScale * dx + yVec * xScale * dy + zVec * xScale * dz + self.update() + + def pixelSize(self, pos): + """ + Return the approximate size of a screen pixel at the location pos + Pos may be a Vector or an (N,3) array of locations + """ + cam = self.cameraPosition() + if isinstance(pos, np.ndarray): + cam = np.array(cam).reshape((1,)*(pos.ndim-1)+(3,)) + dist = ((pos-cam)**2).sum(axis=-1)**0.5 + else: + dist = (pos-cam).length() + xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) + return xDist / self.width() + + def mousePressEvent(self, ev): + self.mousePos = ev.pos() + + def mouseMoveEvent(self, ev): + diff = ev.pos() - self.mousePos + self.mousePos = ev.pos() + + if ev.buttons() == QtCore.Qt.LeftButton: + self.orbit(-diff.x(), diff.y()) + #print self.opts['azimuth'], self.opts['elevation'] + elif ev.buttons() == QtCore.Qt.MidButton: + if (ev.modifiers() & QtCore.Qt.ControlModifier): + self.pan(diff.x(), 0, diff.y(), relative=True) + else: + self.pan(diff.x(), diff.y(), 0, relative=True) + + def mouseReleaseEvent(self, ev): + pass + + def wheelEvent(self, ev): + if (ev.modifiers() & QtCore.Qt.ControlModifier): + self.opts['fov'] *= 0.999**ev.delta() + else: + self.opts['distance'] *= 0.999**ev.delta() + self.update() + + def keyPressEvent(self, ev): + if ev.key() in self.noRepeatKeys: + ev.accept() + if ev.isAutoRepeat(): + return + self.keysPressed[ev.key()] = 1 + self.evalKeyState() + + def keyReleaseEvent(self, ev): + if ev.key() in self.noRepeatKeys: + ev.accept() + if ev.isAutoRepeat(): + return + try: + del self.keysPressed[ev.key()] + except: + self.keysPressed = {} + self.evalKeyState() + + def evalKeyState(self): + speed = 2.0 + if len(self.keysPressed) > 0: + for key in self.keysPressed: + if key == QtCore.Qt.Key_Right: + self.orbit(azim=-speed, elev=0) + elif key == QtCore.Qt.Key_Left: + self.orbit(azim=speed, elev=0) + elif key == QtCore.Qt.Key_Up: + self.orbit(azim=0, elev=-speed) + elif key == QtCore.Qt.Key_Down: + self.orbit(azim=0, elev=speed) + elif key == QtCore.Qt.Key_PageUp: + pass + elif key == QtCore.Qt.Key_PageDown: + pass + self.keyTimer.start(16) + else: + self.keyTimer.stop() + + def checkOpenGLVersion(self, msg): + ## Only to be called from within exception handler. + ver = glGetString(GL_VERSION).split()[0] + if int(ver.split('.')[0]) < 2: + import pyqtgraph.debug + pyqtgraph.debug.printExc() + raise Exception(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver) + else: + raise + + + + def readQImage(self): + """ + Read the current buffer pixels out as a QImage. + """ + w = self.width() + h = self.height() + self.repaint() + pixels = np.empty((h, w, 4), dtype=np.ubyte) + pixels[:] = 128 + pixels[...,0] = 50 + pixels[...,3] = 255 + + glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels) + + # swap B,R channels for Qt + tmp = pixels[...,0].copy() + pixels[...,0] = pixels[...,2] + pixels[...,2] = tmp + pixels = pixels[::-1] # flip vertical + + img = fn.makeQImage(pixels, transpose=False) + return img + + + def renderToArray(self, size, format=GL_BGRA, type=GL_UNSIGNED_BYTE, textureSize=1024, padding=256): + w,h = map(int, size) + + self.makeCurrent() + tex = None + fb = None + try: + output = np.empty((w, h, 4), dtype=np.ubyte) + fb = glfbo.glGenFramebuffers(1) + glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, fb ) + + glEnable(GL_TEXTURE_2D) + tex = glGenTextures(1) + glBindTexture(GL_TEXTURE_2D, tex) + texwidth = textureSize + data = np.zeros((texwidth,texwidth,4), dtype=np.ubyte) + + ## Test texture dimensions first + glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, None) + if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: + raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) + ## create teture + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.transpose((1,0,2))) + + self.opts['viewport'] = (0, 0, w, h) # viewport is the complete image; this ensures that paintGL(region=...) + # is interpreted correctly. + p2 = 2 * padding + for x in range(-padding, w-padding, texwidth-p2): + for y in range(-padding, h-padding, texwidth-p2): + x2 = min(x+texwidth, w+padding) + y2 = min(y+texwidth, h+padding) + w2 = x2-x + h2 = y2-y + + ## render to texture + glfbo.glFramebufferTexture2D(glfbo.GL_FRAMEBUFFER, glfbo.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0) + + self.paintGL(region=(x, h-y-h2, w2, h2), viewport=(0, 0, w2, h2)) # only render sub-region + + ## read texture back to array + data = glGetTexImage(GL_TEXTURE_2D, 0, format, type) + data = np.fromstring(data, dtype=np.ubyte).reshape(texwidth,texwidth,4).transpose(1,0,2)[:, ::-1] + output[x+padding:x2-padding, y+padding:y2-padding] = data[padding:w2-padding, -(h2-padding):-padding] + + finally: + self.opts['viewport'] = None + glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, 0) + glBindTexture(GL_TEXTURE_2D, 0) + if tex is not None: + glDeleteTextures([tex]) + if fb is not None: + glfbo.glDeleteFramebuffers([fb]) + + return output + + + diff --git a/pyqtgraph/opengl/MeshData.py b/pyqtgraph/opengl/MeshData.py new file mode 100644 index 00000000..71e566c9 --- /dev/null +++ b/pyqtgraph/opengl/MeshData.py @@ -0,0 +1,519 @@ +from pyqtgraph.Qt import QtGui +import pyqtgraph.functions as fn +import numpy as np + +class MeshData(object): + """ + Class for storing and operating on 3D mesh data. May contain: + + - list of vertex locations + - list of edges + - list of triangles + - colors per vertex, edge, or tri + - normals per vertex or tri + + This class handles conversion between the standard [list of vertexes, list of faces] + format (suitable for use with glDrawElements) and 'indexed' [list of vertexes] format + (suitable for use with glDrawArrays). It will automatically compute face normal + vectors as well as averaged vertex normal vectors. + + The class attempts to be as efficient as possible in caching conversion results and + avoiding unnecessary conversions. + """ + + def __init__(self, vertexes=None, faces=None, edges=None, vertexColors=None, faceColors=None): + """ + ============= ===================================================== + Arguments + vertexes (Nv, 3) array of vertex coordinates. + If faces is not specified, then this will instead be + interpreted as (Nf, 3, 3) array of coordinates. + faces (Nf, 3) array of indexes into the vertex array. + edges [not available yet] + vertexColors (Nv, 4) array of vertex colors. + If faces is not specified, then this will instead be + interpreted as (Nf, 3, 4) array of colors. + faceColors (Nf, 4) array of face colors. + ============= ===================================================== + + All arguments are optional. + """ + self._vertexes = None # (Nv,3) array of vertex coordinates + self._vertexesIndexedByFaces = None # (Nf, 3, 3) array of vertex coordinates + self._vertexesIndexedByEdges = None # (Ne, 2, 3) array of vertex coordinates + + ## mappings between vertexes, faces, and edges + self._faces = None # Nx3 array of indexes into self._vertexes specifying three vertexes for each face + self._edges = None # Nx2 array of indexes into self._vertexes specifying two vertexes per edge + self._vertexFaces = None ## maps vertex ID to a list of face IDs (inverse mapping of _faces) + self._vertexEdges = None ## maps vertex ID to a list of edge IDs (inverse mapping of _edges) + + ## Per-vertex data + self._vertexNormals = None # (Nv, 3) array of normals, one per vertex + self._vertexNormalsIndexedByFaces = None # (Nf, 3, 3) array of normals + self._vertexColors = None # (Nv, 3) array of colors + self._vertexColorsIndexedByFaces = None # (Nf, 3, 4) array of colors + self._vertexColorsIndexedByEdges = None # (Nf, 2, 4) array of colors + + ## Per-face data + self._faceNormals = None # (Nf, 3) array of face normals + self._faceNormalsIndexedByFaces = None # (Nf, 3, 3) array of face normals + self._faceColors = None # (Nf, 4) array of face colors + self._faceColorsIndexedByFaces = None # (Nf, 3, 4) array of face colors + self._faceColorsIndexedByEdges = None # (Ne, 2, 4) array of face colors + + ## Per-edge data + self._edgeColors = None # (Ne, 4) array of edge colors + self._edgeColorsIndexedByEdges = None # (Ne, 2, 4) array of edge colors + #self._meshColor = (1, 1, 1, 0.1) # default color to use if no face/edge/vertex colors are given + + + + if vertexes is not None: + if faces is None: + self.setVertexes(vertexes, indexed='faces') + if vertexColors is not None: + self.setVertexColors(vertexColors, indexed='faces') + if faceColors is not None: + self.setFaceColors(faceColors, indexed='faces') + else: + self.setVertexes(vertexes) + self.setFaces(faces) + if vertexColors is not None: + self.setVertexColors(vertexColors) + if faceColors is not None: + self.setFaceColors(faceColors) + + #self.setFaces(vertexes=vertexes, faces=faces, vertexColors=vertexColors, faceColors=faceColors) + + + #def setFaces(self, vertexes=None, faces=None, vertexColors=None, faceColors=None): + #""" + #Set the faces in this data set. + #Data may be provided either as an Nx3x3 array of floats (9 float coordinate values per face):: + + #faces = [ [(x, y, z), (x, y, z), (x, y, z)], ... ] + + #or as an Nx3 array of ints (vertex integers) AND an Mx3 array of floats (3 float coordinate values per vertex):: + + #faces = [ (p1, p2, p3), ... ] + #vertexes = [ (x, y, z), ... ] + + #""" + #if not isinstance(vertexes, np.ndarray): + #vertexes = np.array(vertexes) + #if vertexes.dtype != np.float: + #vertexes = vertexes.astype(float) + #if faces is None: + #self._setIndexedFaces(vertexes, vertexColors, faceColors) + #else: + #self._setUnindexedFaces(faces, vertexes, vertexColors, faceColors) + ##print self.vertexes().shape + ##print self.faces().shape + + + #def setMeshColor(self, color): + #"""Set the color of the entire mesh. This removes any per-face or per-vertex colors.""" + #color = fn.Color(color) + #self._meshColor = color.glColor() + #self._vertexColors = None + #self._faceColors = None + + + #def __iter__(self): + #"""Iterate over all faces, yielding a list of three tuples [(position, normal, color), ...] for each face.""" + #vnorms = self.vertexNormals() + #vcolors = self.vertexColors() + #for i in range(self._faces.shape[0]): + #face = [] + #for j in [0,1,2]: + #vind = self._faces[i,j] + #pos = self._vertexes[vind] + #norm = vnorms[vind] + #if vcolors is None: + #color = self._meshColor + #else: + #color = vcolors[vind] + #face.append((pos, norm, color)) + #yield face + + #def __len__(self): + #return len(self._faces) + + def faces(self): + """Return an array (Nf, 3) of vertex indexes, three per triangular face in the mesh.""" + return self._faces + + def edges(self): + """Return an array (Nf, 3) of vertex indexes, two per edge in the mesh.""" + if self._edges is None: + self._computeEdges() + return self._edges + + def setFaces(self, faces): + """Set the (Nf, 3) array of faces. Each rown in the array contains + three indexes into the vertex array, specifying the three corners + of a triangular face.""" + self._faces = faces + self._edges = None + self._vertexFaces = None + self._vertexesIndexedByFaces = None + self.resetNormals() + self._vertexColorsIndexedByFaces = None + self._faceColorsIndexedByFaces = None + + + + def vertexes(self, indexed=None): + """Return an array (N,3) of the positions of vertexes in the mesh. + By default, each unique vertex appears only once in the array. + If indexed is 'faces', then the array will instead contain three vertexes + per face in the mesh (and a single vertex may appear more than once in the array).""" + if indexed is None: + if self._vertexes is None and self._vertexesIndexedByFaces is not None: + self._computeUnindexedVertexes() + return self._vertexes + elif indexed == 'faces': + if self._vertexesIndexedByFaces is None and self._vertexes is not None: + self._vertexesIndexedByFaces = self._vertexes[self.faces()] + return self._vertexesIndexedByFaces + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def setVertexes(self, verts=None, indexed=None, resetNormals=True): + """ + Set the array (Nv, 3) of vertex coordinates. + If indexed=='faces', then the data must have shape (Nf, 3, 3) and is + assumed to be already indexed as a list of faces. + This will cause any pre-existing normal vectors to be cleared + unless resetNormals=False. + """ + if indexed is None: + if verts is not None: + self._vertexes = verts + self._vertexesIndexedByFaces = None + elif indexed=='faces': + self._vertexes = None + if verts is not None: + self._vertexesIndexedByFaces = verts + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + if resetNormals: + self.resetNormals() + + def resetNormals(self): + self._vertexNormals = None + self._vertexNormalsIndexedByFaces = None + self._faceNormals = None + self._faceNormalsIndexedByFaces = None + + + def hasFaceIndexedData(self): + """Return True if this object already has vertex positions indexed by face""" + return self._vertexesIndexedByFaces is not None + + def hasEdgeIndexedData(self): + return self._vertexesIndexedByEdges is not None + + def hasVertexColor(self): + """Return True if this data set has vertex color information""" + for v in (self._vertexColors, self._vertexColorsIndexedByFaces, self._vertexColorsIndexedByEdges): + if v is not None: + return True + return False + + def hasFaceColor(self): + """Return True if this data set has face color information""" + for v in (self._faceColors, self._faceColorsIndexedByFaces, self._faceColorsIndexedByEdges): + if v is not None: + return True + return False + + + def faceNormals(self, indexed=None): + """ + Return an array (Nf, 3) of normal vectors for each face. + If indexed='faces', then instead return an indexed array + (Nf, 3, 3) (this is just the same array with each vector + copied three times). + """ + if self._faceNormals is None: + v = self.vertexes(indexed='faces') + self._faceNormals = np.cross(v[:,1]-v[:,0], v[:,2]-v[:,0]) + + + if indexed is None: + return self._faceNormals + elif indexed == 'faces': + if self._faceNormalsIndexedByFaces is None: + norms = np.empty((self._faceNormals.shape[0], 3, 3)) + norms[:] = self._faceNormals[:,np.newaxis,:] + self._faceNormalsIndexedByFaces = norms + return self._faceNormalsIndexedByFaces + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def vertexNormals(self, indexed=None): + """ + Return an array of normal vectors. + By default, the array will be (N, 3) with one entry per unique vertex in the mesh. + If indexed is 'faces', then the array will contain three normal vectors per face + (and some vertexes may be repeated). + """ + if self._vertexNormals is None: + faceNorms = self.faceNormals() + vertFaces = self.vertexFaces() + self._vertexNormals = np.empty(self._vertexes.shape, dtype=float) + for vindex in xrange(self._vertexes.shape[0]): + norms = faceNorms[vertFaces[vindex]] ## get all face normals + norm = norms.sum(axis=0) ## sum normals + norm /= (norm**2).sum()**0.5 ## and re-normalize + self._vertexNormals[vindex] = norm + + if indexed is None: + return self._vertexNormals + elif indexed == 'faces': + return self._vertexNormals[self.faces()] + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def vertexColors(self, indexed=None): + """ + Return an array (Nv, 4) of vertex colors. + If indexed=='faces', then instead return an indexed array + (Nf, 3, 4). + """ + if indexed is None: + return self._vertexColors + elif indexed == 'faces': + if self._vertexColorsIndexedByFaces is None: + self._vertexColorsIndexedByFaces = self._vertexColors[self.faces()] + return self._vertexColorsIndexedByFaces + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def setVertexColors(self, colors, indexed=None): + """ + Set the vertex color array (Nv, 4). + If indexed=='faces', then the array will be interpreted + as indexed and should have shape (Nf, 3, 4) + """ + if indexed is None: + self._vertexColors = colors + self._vertexColorsIndexedByFaces = None + elif indexed == 'faces': + self._vertexColors = None + self._vertexColorsIndexedByFaces = colors + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def faceColors(self, indexed=None): + """ + Return an array (Nf, 4) of face colors. + If indexed=='faces', then instead return an indexed array + (Nf, 3, 4) (note this is just the same array with each color + repeated three times). + """ + if indexed is None: + return self._faceColors + elif indexed == 'faces': + if self._faceColorsIndexedByFaces is None and self._faceColors is not None: + Nf = self._faceColors.shape[0] + self._faceColorsIndexedByFaces = np.empty((Nf, 3, 4), dtype=self._faceColors.dtype) + self._faceColorsIndexedByFaces[:] = self._faceColors.reshape(Nf, 1, 4) + return self._faceColorsIndexedByFaces + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def setFaceColors(self, colors, indexed=None): + """ + Set the face color array (Nf, 4). + If indexed=='faces', then the array will be interpreted + as indexed and should have shape (Nf, 3, 4) + """ + if indexed is None: + self._faceColors = colors + self._faceColorsIndexedByFaces = None + elif indexed == 'faces': + self._faceColors = None + self._faceColorsIndexedByFaces = colors + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def faceCount(self): + """ + Return the number of faces in the mesh. + """ + if self._faces is not None: + return self._faces.shape[0] + elif self._vertexesIndexedByFaces is not None: + return self._vertexesIndexedByFaces.shape[0] + + def edgeColors(self): + return self._edgeColors + + #def _setIndexedFaces(self, faces, vertexColors=None, faceColors=None): + #self._vertexesIndexedByFaces = faces + #self._vertexColorsIndexedByFaces = vertexColors + #self._faceColorsIndexedByFaces = faceColors + + def _computeUnindexedVertexes(self): + ## Given (Nv, 3, 3) array of vertexes-indexed-by-face, convert backward to unindexed vertexes + ## This is done by collapsing into a list of 'unique' vertexes (difference < 1e-14) + + ## I think generally this should be discouraged.. + + faces = self._vertexesIndexedByFaces + verts = {} ## used to remember the index of each vertex position + self._faces = np.empty(faces.shape[:2], dtype=np.uint) + self._vertexes = [] + self._vertexFaces = [] + self._faceNormals = None + self._vertexNormals = None + for i in xrange(faces.shape[0]): + face = faces[i] + inds = [] + for j in range(face.shape[0]): + pt = face[j] + pt2 = tuple([round(x*1e14) for x in pt]) ## quantize to be sure that nearly-identical points will be merged + index = verts.get(pt2, None) + if index is None: + #self._vertexes.append(QtGui.QVector3D(*pt)) + self._vertexes.append(pt) + self._vertexFaces.append([]) + index = len(self._vertexes)-1 + verts[pt2] = index + self._vertexFaces[index].append(i) # keep track of which vertexes belong to which faces + self._faces[i,j] = index + self._vertexes = np.array(self._vertexes, dtype=float) + + #def _setUnindexedFaces(self, faces, vertexes, vertexColors=None, faceColors=None): + #self._vertexes = vertexes #[QtGui.QVector3D(*v) for v in vertexes] + #self._faces = faces.astype(np.uint) + #self._edges = None + #self._vertexFaces = None + #self._faceNormals = None + #self._vertexNormals = None + #self._vertexColors = vertexColors + #self._faceColors = faceColors + + def vertexFaces(self): + """ + Return list mapping each vertex index to a list of face indexes that use the vertex. + """ + if self._vertexFaces is None: + self._vertexFaces = [None] * len(self.vertexes()) + for i in xrange(self._faces.shape[0]): + face = self._faces[i] + for ind in face: + if self._vertexFaces[ind] is None: + self._vertexFaces[ind] = [] ## need a unique/empty list to fill + self._vertexFaces[ind].append(i) + return self._vertexFaces + + #def reverseNormals(self): + #""" + #Reverses the direction of all normal vectors. + #""" + #pass + + #def generateEdgesFromFaces(self): + #""" + #Generate a set of edges by listing all the edges of faces and removing any duplicates. + #Useful for displaying wireframe meshes. + #""" + #pass + + def _computeEdges(self): + ## generate self._edges from self._faces + #print self._faces + nf = len(self._faces) + edges = np.empty(nf*3, dtype=[('i', np.uint, 2)]) + edges['i'][0:nf] = self._faces[:,:2] + edges['i'][nf:2*nf] = self._faces[:,1:3] + edges['i'][-nf:,0] = self._faces[:,2] + edges['i'][-nf:,1] = self._faces[:,0] + + # sort per-edge + mask = edges['i'][:,0] > edges['i'][:,1] + edges['i'][mask] = edges['i'][mask][:,::-1] + + # remove duplicate entries + self._edges = np.unique(edges)['i'] + #print self._edges + + + def save(self): + """Serialize this mesh to a string appropriate for disk storage""" + import pickle + if self._faces is not None: + names = ['_vertexes', '_faces'] + else: + names = ['_vertexesIndexedByFaces'] + + if self._vertexColors is not None: + names.append('_vertexColors') + elif self._vertexColorsIndexedByFaces is not None: + names.append('_vertexColorsIndexedByFaces') + + if self._faceColors is not None: + names.append('_faceColors') + elif self._faceColorsIndexedByFaces is not None: + names.append('_faceColorsIndexedByFaces') + + state = dict([(n,getattr(self, n)) for n in names]) + return pickle.dumps(state) + + def restore(self, state): + """Restore the state of a mesh previously saved using save()""" + import pickle + state = pickle.loads(state) + for k in state: + if isinstance(state[k], list): + if isinstance(state[k][0], QtGui.QVector3D): + state[k] = [[v.x(), v.y(), v.z()] for v in state[k]] + state[k] = np.array(state[k]) + setattr(self, k, state[k]) + + + + @staticmethod + def sphere(rows, cols, radius=1.0, offset=True): + """ + Return a MeshData instance with vertexes and faces computed + for a spherical surface. + """ + verts = np.empty((rows+1, cols, 3), dtype=float) + + ## compute vertexes + phi = (np.arange(rows+1) * np.pi / rows).reshape(rows+1, 1) + s = radius * np.sin(phi) + verts[...,2] = radius * np.cos(phi) + th = ((np.arange(cols) * 2 * np.pi / cols).reshape(1, cols)) + if offset: + th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1,1)) ## rotate each row by 1/2 column + verts[...,0] = s * np.cos(th) + verts[...,1] = s * np.sin(th) + verts = verts.reshape((rows+1)*cols, 3)[cols-1:-(cols-1)] ## remove redundant vertexes from top and bottom + + ## compute faces + faces = np.empty((rows*cols*2, 3), dtype=np.uint) + rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]]) + rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]]) + for row in range(rows): + start = row * cols * 2 + faces[start:start+cols] = rowtemplate1 + row * cols + faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols + faces = faces[cols:-cols] ## cut off zero-area triangles at top and bottom + + ## adjust for redundant vertexes that were removed from top and bottom + vmin = cols-1 + faces[facesvmax] = vmax + + return MeshData(vertexes=verts, faces=faces) + + \ No newline at end of file diff --git a/pyqtgraph/opengl/__init__.py b/pyqtgraph/opengl/__init__.py new file mode 100644 index 00000000..5345e187 --- /dev/null +++ b/pyqtgraph/opengl/__init__.py @@ -0,0 +1,30 @@ +from .GLViewWidget import GLViewWidget + +from pyqtgraph import importAll +#import os +#def importAll(path): + #d = os.path.join(os.path.split(__file__)[0], path) + #files = [] + #for f in os.listdir(d): + #if os.path.isdir(os.path.join(d, f)) and f != '__pycache__': + #files.append(f) + #elif f[-3:] == '.py' and f != '__init__.py': + #files.append(f[:-3]) + + #for modName in files: + #mod = __import__(path+"."+modName, globals(), locals(), fromlist=['*']) + #if hasattr(mod, '__all__'): + #names = mod.__all__ + #else: + #names = [n for n in dir(mod) if n[0] != '_'] + #for k in names: + #if hasattr(mod, k): + #globals()[k] = getattr(mod, k) + +importAll('items', globals(), locals()) +\ +from .MeshData import MeshData +## for backward compatibility: +#MeshData.MeshData = MeshData ## breaks autodoc. + +from . import shaders diff --git a/pyqtgraph/opengl/glInfo.py b/pyqtgraph/opengl/glInfo.py new file mode 100644 index 00000000..28da1f69 --- /dev/null +++ b/pyqtgraph/opengl/glInfo.py @@ -0,0 +1,16 @@ +from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL +from OpenGL.GL import * +app = QtGui.QApplication([]) + +class GLTest(QtOpenGL.QGLWidget): + def __init__(self): + QtOpenGL.QGLWidget.__init__(self) + self.makeCurrent() + print("GL version:" + glGetString(GL_VERSION)) + print("MAX_TEXTURE_SIZE: %d" % glGetIntegerv(GL_MAX_TEXTURE_SIZE)) + print("MAX_3D_TEXTURE_SIZE: %d" % glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE)) + print("Extensions: " + glGetString(GL_EXTENSIONS)) + +GLTest() + + diff --git a/pyqtgraph/opengl/items/GLAxisItem.py b/pyqtgraph/opengl/items/GLAxisItem.py new file mode 100644 index 00000000..860ac497 --- /dev/null +++ b/pyqtgraph/opengl/items/GLAxisItem.py @@ -0,0 +1,64 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from pyqtgraph import QtGui + +__all__ = ['GLAxisItem'] + +class GLAxisItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays three lines indicating origin and orientation of local coordinate system. + + """ + + def __init__(self, size=None, antialias=True, glOptions='translucent'): + GLGraphicsItem.__init__(self) + if size is None: + size = QtGui.QVector3D(1,1,1) + self.antialias = antialias + self.setSize(size=size) + self.setGLOptions(glOptions) + + def setSize(self, x=None, y=None, z=None, size=None): + """ + Set the size of the axes (in its local coordinate system; this does not affect the transform) + Arguments can be x,y,z or size=QVector3D(). + """ + if size is not None: + x = size.x() + y = size.y() + z = size.z() + self.__size = [x,y,z] + self.update() + + def size(self): + return self.__size[:] + + + def paint(self): + + #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + self.setupGLState() + + if self.antialias: + glEnable(GL_LINE_SMOOTH) + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + + glBegin( GL_LINES ) + + x,y,z = self.size() + glColor4f(0, 1, 0, .6) # z is green + glVertex3f(0, 0, 0) + glVertex3f(0, 0, z) + + glColor4f(1, 1, 0, .6) # y is yellow + glVertex3f(0, 0, 0) + glVertex3f(0, y, 0) + + glColor4f(0, 0, 1, .6) # x is blue + glVertex3f(0, 0, 0) + glVertex3f(x, 0, 0) + glEnd() diff --git a/pyqtgraph/opengl/items/GLBarGraphItem.py b/pyqtgraph/opengl/items/GLBarGraphItem.py new file mode 100644 index 00000000..b3060dc9 --- /dev/null +++ b/pyqtgraph/opengl/items/GLBarGraphItem.py @@ -0,0 +1,29 @@ +from .GLMeshItem import GLMeshItem +from ..MeshData import MeshData +import numpy as np + +class GLBarGraphItem(GLMeshItem): + def __init__(self, pos, size): + """ + pos is (...,3) array of the bar positions (the corner of each bar) + size is (...,3) array of the sizes of each bar + """ + nCubes = reduce(lambda a,b: a*b, pos.shape[:-1]) + cubeVerts = np.mgrid[0:2,0:2,0:2].reshape(3,8).transpose().reshape(1,8,3) + cubeFaces = np.array([ + [0,1,2], [3,2,1], + [4,5,6], [7,6,5], + [0,1,4], [5,4,1], + [2,3,6], [7,6,3], + [0,2,4], [6,4,2], + [1,3,5], [7,5,3]]).reshape(1,12,3) + size = size.reshape((nCubes, 1, 3)) + pos = pos.reshape((nCubes, 1, 3)) + verts = cubeVerts * size + pos + faces = cubeFaces + (np.arange(nCubes) * 8).reshape(nCubes,1,1) + md = MeshData(verts.reshape(nCubes*8,3), faces.reshape(nCubes*12,3)) + + GLMeshItem.__init__(self, meshdata=md, shader='shaded', smooth=False) + + + \ No newline at end of file diff --git a/pyqtgraph/opengl/items/GLBoxItem.py b/pyqtgraph/opengl/items/GLBoxItem.py new file mode 100644 index 00000000..bc25afd1 --- /dev/null +++ b/pyqtgraph/opengl/items/GLBoxItem.py @@ -0,0 +1,88 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from pyqtgraph.Qt import QtGui +import pyqtgraph as pg + +__all__ = ['GLBoxItem'] + +class GLBoxItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays a wire-frame box. + """ + def __init__(self, size=None, color=None, glOptions='translucent'): + GLGraphicsItem.__init__(self) + if size is None: + size = QtGui.QVector3D(1,1,1) + self.setSize(size=size) + if color is None: + color = (255,255,255,80) + self.setColor(color) + self.setGLOptions(glOptions) + + def setSize(self, x=None, y=None, z=None, size=None): + """ + Set the size of the box (in its local coordinate system; this does not affect the transform) + Arguments can be x,y,z or size=QVector3D(). + """ + if size is not None: + x = size.x() + y = size.y() + z = size.z() + self.__size = [x,y,z] + self.update() + + def size(self): + return self.__size[:] + + def setColor(self, *args): + """Set the color of the box. Arguments are the same as those accepted by functions.mkColor()""" + self.__color = pg.Color(*args) + + def color(self): + return self.__color + + def paint(self): + #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + ##glAlphaFunc( GL_ALWAYS,0.5 ) + #glEnable( GL_POINT_SMOOTH ) + #glDisable( GL_DEPTH_TEST ) + self.setupGLState() + + glBegin( GL_LINES ) + + glColor4f(*self.color().glColor()) + x,y,z = self.size() + glVertex3f(0, 0, 0) + glVertex3f(0, 0, z) + glVertex3f(x, 0, 0) + glVertex3f(x, 0, z) + glVertex3f(0, y, 0) + glVertex3f(0, y, z) + glVertex3f(x, y, 0) + glVertex3f(x, y, z) + + glVertex3f(0, 0, 0) + glVertex3f(0, y, 0) + glVertex3f(x, 0, 0) + glVertex3f(x, y, 0) + glVertex3f(0, 0, z) + glVertex3f(0, y, z) + glVertex3f(x, 0, z) + glVertex3f(x, y, z) + + glVertex3f(0, 0, 0) + glVertex3f(x, 0, 0) + glVertex3f(0, y, 0) + glVertex3f(x, y, 0) + glVertex3f(0, 0, z) + glVertex3f(x, 0, z) + glVertex3f(0, y, z) + glVertex3f(x, y, z) + + glEnd() + + \ No newline at end of file diff --git a/pyqtgraph/opengl/items/GLGridItem.py b/pyqtgraph/opengl/items/GLGridItem.py new file mode 100644 index 00000000..01a2b178 --- /dev/null +++ b/pyqtgraph/opengl/items/GLGridItem.py @@ -0,0 +1,58 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from pyqtgraph import QtGui + +__all__ = ['GLGridItem'] + +class GLGridItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays a wire-grame grid. + """ + + def __init__(self, size=None, color=None, antialias=True, glOptions='translucent'): + GLGraphicsItem.__init__(self) + self.setGLOptions(glOptions) + self.antialias = antialias + if size is None: + size = QtGui.QVector3D(1,1,1) + self.setSize(size=size) + + def setSize(self, x=None, y=None, z=None, size=None): + """ + Set the size of the axes (in its local coordinate system; this does not affect the transform) + Arguments can be x,y,z or size=QVector3D(). + """ + if size is not None: + x = size.x() + y = size.y() + z = size.z() + self.__size = [x,y,z] + self.update() + + def size(self): + return self.__size[:] + + + def paint(self): + self.setupGLState() + + if self.antialias: + glEnable(GL_LINE_SMOOTH) + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + + glBegin( GL_LINES ) + + x,y,z = self.size() + glColor4f(1, 1, 1, .3) + for x in range(-10, 11): + glVertex3f(x, -10, 0) + glVertex3f(x, 10, 0) + for y in range(-10, 11): + glVertex3f(-10, y, 0) + glVertex3f( 10, y, 0) + + glEnd() diff --git a/pyqtgraph/opengl/items/GLImageItem.py b/pyqtgraph/opengl/items/GLImageItem.py new file mode 100644 index 00000000..aca68f3d --- /dev/null +++ b/pyqtgraph/opengl/items/GLImageItem.py @@ -0,0 +1,90 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from pyqtgraph.Qt import QtGui +import numpy as np + +__all__ = ['GLImageItem'] + +class GLImageItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays image data as a textured quad. + """ + + + def __init__(self, data, smooth=False, glOptions='translucent'): + """ + + ============== ======================================================================================= + **Arguments:** + data Volume data to be rendered. *Must* be 3D numpy array (x, y, RGBA) with dtype=ubyte. + (See functions.makeRGBA) + smooth (bool) If True, the volume slices are rendered with linear interpolation + ============== ======================================================================================= + """ + + self.smooth = smooth + self.data = data + GLGraphicsItem.__init__(self) + self.setGLOptions(glOptions) + + def initializeGL(self): + glEnable(GL_TEXTURE_2D) + self.texture = glGenTextures(1) + glBindTexture(GL_TEXTURE_2D, self.texture) + if self.smooth: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + else: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) + #glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) + shape = self.data.shape + + ## Test texture dimensions first + glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) + if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: + raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((1,0,2))) + glDisable(GL_TEXTURE_2D) + + #self.lists = {} + #for ax in [0,1,2]: + #for d in [-1, 1]: + #l = glGenLists(1) + #self.lists[(ax,d)] = l + #glNewList(l, GL_COMPILE) + #self.drawVolume(ax, d) + #glEndList() + + + def paint(self): + + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.texture) + + self.setupGLState() + + #glEnable(GL_DEPTH_TEST) + ##glDisable(GL_CULL_FACE) + #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + glColor4f(1,1,1,1) + + glBegin(GL_QUADS) + glTexCoord2f(0,0) + glVertex3f(0,0,0) + glTexCoord2f(1,0) + glVertex3f(self.data.shape[0], 0, 0) + glTexCoord2f(1,1) + glVertex3f(self.data.shape[0], self.data.shape[1], 0) + glTexCoord2f(0,1) + glVertex3f(0, self.data.shape[1], 0) + glEnd() + glDisable(GL_TEXTURE_3D) + diff --git a/pyqtgraph/opengl/items/GLLinePlotItem.py b/pyqtgraph/opengl/items/GLLinePlotItem.py new file mode 100644 index 00000000..23d227c9 --- /dev/null +++ b/pyqtgraph/opengl/items/GLLinePlotItem.py @@ -0,0 +1,101 @@ +from OpenGL.GL import * +from OpenGL.arrays import vbo +from .. GLGraphicsItem import GLGraphicsItem +from .. import shaders +from pyqtgraph import QtGui +import numpy as np + +__all__ = ['GLLinePlotItem'] + +class GLLinePlotItem(GLGraphicsItem): + """Draws line plots in 3D.""" + + def __init__(self, **kwds): + """All keyword arguments are passed to setData()""" + GLGraphicsItem.__init__(self) + glopts = kwds.pop('glOptions', 'additive') + self.setGLOptions(glopts) + self.pos = None + self.width = 1. + self.color = (1.0,1.0,1.0,1.0) + self.setData(**kwds) + + def setData(self, **kwds): + """ + Update the data displayed by this item. All arguments are optional; + for example it is allowed to update vertex positions while leaving + colors unchanged, etc. + + ==================== ================================================== + Arguments: + ------------------------------------------------------------------------ + pos (N,3) array of floats specifying point locations. + color (N,4) array of floats (0.0-1.0) or + tuple of floats specifying + a single color for the entire item. + width float specifying line width + antialias enables smooth line drawing + ==================== ================================================== + """ + args = ['pos', 'color', 'width', 'connected', 'antialias'] + for k in kwds.keys(): + if k not in args: + raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) + self.antialias = False + for arg in args: + if arg in kwds: + setattr(self, arg, kwds[arg]) + #self.vbo.pop(arg, None) + self.update() + + def initializeGL(self): + pass + + #def setupGLState(self): + #"""Prepare OpenGL state for drawing. This function is called immediately before painting.""" + ##glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly. + #glBlendFunc(GL_SRC_ALPHA, GL_ONE) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + #glDisable( GL_DEPTH_TEST ) + + ##glEnable( GL_POINT_SMOOTH ) + + ##glHint(GL_POINT_SMOOTH_HINT, GL_NICEST) + ##glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3)) + ##glPointParameterfv(GL_POINT_SIZE_MAX, (65500,)) + ##glPointParameterfv(GL_POINT_SIZE_MIN, (0,)) + + def paint(self): + if self.pos is None: + return + self.setupGLState() + + glEnableClientState(GL_VERTEX_ARRAY) + + try: + glVertexPointerf(self.pos) + + if isinstance(self.color, np.ndarray): + glEnableClientState(GL_COLOR_ARRAY) + glColorPointerf(self.color) + else: + if isinstance(self.color, QtGui.QColor): + glColor4f(*fn.glColor(self.color)) + else: + glColor4f(*self.color) + glLineWidth(self.width) + #glPointSize(self.width) + + if self.antialias: + glEnable(GL_LINE_SMOOTH) + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + + glDrawArrays(GL_LINE_STRIP, 0, int(self.pos.size / self.pos.shape[-1])) + finally: + glDisableClientState(GL_COLOR_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + + diff --git a/pyqtgraph/opengl/items/GLMeshItem.py b/pyqtgraph/opengl/items/GLMeshItem.py new file mode 100644 index 00000000..5b245e64 --- /dev/null +++ b/pyqtgraph/opengl/items/GLMeshItem.py @@ -0,0 +1,223 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from .. MeshData import MeshData +from pyqtgraph.Qt import QtGui +import pyqtgraph as pg +from .. import shaders +import numpy as np + + + +__all__ = ['GLMeshItem'] + +class GLMeshItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays a 3D triangle mesh. + """ + def __init__(self, **kwds): + """ + ============== ===================================================== + Arguments + meshdata MeshData object from which to determine geometry for + this item. + color Default face color used if no vertex or face colors + are specified. + edgeColor Default edge color to use if no edge colors are + specified in the mesh data. + drawEdges If True, a wireframe mesh will be drawn. + (default=False) + drawFaces If True, mesh faces are drawn. (default=True) + shader Name of shader program to use when drawing faces. + (None for no shader) + smooth If True, normal vectors are computed for each vertex + and interpolated within each face. + computeNormals If False, then computation of normal vectors is + disabled. This can provide a performance boost for + meshes that do not make use of normals. + ============== ===================================================== + """ + self.opts = { + 'meshdata': None, + 'color': (1., 1., 1., 1.), + 'drawEdges': False, + 'drawFaces': True, + 'edgeColor': (0.5, 0.5, 0.5, 1.0), + 'shader': None, + 'smooth': True, + 'computeNormals': True, + } + + GLGraphicsItem.__init__(self) + glopts = kwds.pop('glOptions', 'opaque') + self.setGLOptions(glopts) + shader = kwds.pop('shader', None) + self.setShader(shader) + + self.setMeshData(**kwds) + + ## storage for data compiled from MeshData object + self.vertexes = None + self.normals = None + self.colors = None + self.faces = None + + def setShader(self, shader): + """Set the shader used when rendering faces in the mesh. (see the GL shaders example)""" + self.opts['shader'] = shader + self.update() + + def shader(self): + shader = self.opts['shader'] + if isinstance(shader, shaders.ShaderProgram): + return shader + else: + return shaders.getShaderProgram(shader) + + def setColor(self, c): + """Set the default color to use when no vertex or face colors are specified.""" + self.opts['color'] = c + self.update() + + def setMeshData(self, **kwds): + """ + Set mesh data for this item. This can be invoked two ways: + + 1. Specify *meshdata* argument with a new MeshData object + 2. Specify keyword arguments to be passed to MeshData(..) to create a new instance. + """ + md = kwds.get('meshdata', None) + if md is None: + opts = {} + for k in ['vertexes', 'faces', 'edges', 'vertexColors', 'faceColors']: + try: + opts[k] = kwds.pop(k) + except KeyError: + pass + md = MeshData(**opts) + + self.opts['meshdata'] = md + self.opts.update(kwds) + self.meshDataChanged() + self.update() + + + def meshDataChanged(self): + """ + This method must be called to inform the item that the MeshData object + has been altered. + """ + + self.vertexes = None + self.faces = None + self.normals = None + self.colors = None + self.edges = None + self.edgeColors = None + self.update() + + def parseMeshData(self): + ## interpret vertex / normal data before drawing + ## This can: + ## - automatically generate normals if they were not specified + ## - pull vertexes/noormals/faces from MeshData if that was specified + + if self.vertexes is not None and self.normals is not None: + return + #if self.opts['normals'] is None: + #if self.opts['meshdata'] is None: + #self.opts['meshdata'] = MeshData(vertexes=self.opts['vertexes'], faces=self.opts['faces']) + if self.opts['meshdata'] is not None: + md = self.opts['meshdata'] + if self.opts['smooth'] and not md.hasFaceIndexedData(): + self.vertexes = md.vertexes() + if self.opts['computeNormals']: + self.normals = md.vertexNormals() + self.faces = md.faces() + if md.hasVertexColor(): + self.colors = md.vertexColors() + if md.hasFaceColor(): + self.colors = md.faceColors() + else: + self.vertexes = md.vertexes(indexed='faces') + if self.opts['computeNormals']: + if self.opts['smooth']: + self.normals = md.vertexNormals(indexed='faces') + else: + self.normals = md.faceNormals(indexed='faces') + self.faces = None + if md.hasVertexColor(): + self.colors = md.vertexColors(indexed='faces') + elif md.hasFaceColor(): + self.colors = md.faceColors(indexed='faces') + + if self.opts['drawEdges']: + self.edges = md.edges() + self.edgeVerts = md.vertexes() + return + + def paint(self): + self.setupGLState() + + self.parseMeshData() + + if self.opts['drawFaces']: + with self.shader(): + verts = self.vertexes + norms = self.normals + color = self.colors + faces = self.faces + if verts is None: + return + glEnableClientState(GL_VERTEX_ARRAY) + try: + glVertexPointerf(verts) + + if self.colors is None: + color = self.opts['color'] + if isinstance(color, QtGui.QColor): + glColor4f(*pg.glColor(color)) + else: + glColor4f(*color) + else: + glEnableClientState(GL_COLOR_ARRAY) + glColorPointerf(color) + + + if norms is not None: + glEnableClientState(GL_NORMAL_ARRAY) + glNormalPointerf(norms) + + if faces is None: + glDrawArrays(GL_TRIANGLES, 0, np.product(verts.shape[:-1])) + else: + faces = faces.astype(np.uint).flatten() + glDrawElements(GL_TRIANGLES, faces.shape[0], GL_UNSIGNED_INT, faces) + finally: + glDisableClientState(GL_NORMAL_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + glDisableClientState(GL_COLOR_ARRAY) + + if self.opts['drawEdges']: + verts = self.edgeVerts + edges = self.edges + glEnableClientState(GL_VERTEX_ARRAY) + try: + glVertexPointerf(verts) + + if self.edgeColors is None: + color = self.opts['edgeColor'] + if isinstance(color, QtGui.QColor): + glColor4f(*pg.glColor(color)) + else: + glColor4f(*color) + else: + glEnableClientState(GL_COLOR_ARRAY) + glColorPointerf(color) + edges = edges.flatten() + glDrawElements(GL_LINES, edges.shape[0], GL_UNSIGNED_INT, edges) + finally: + glDisableClientState(GL_VERTEX_ARRAY) + glDisableClientState(GL_COLOR_ARRAY) + diff --git a/pyqtgraph/opengl/items/GLScatterPlotItem.py b/pyqtgraph/opengl/items/GLScatterPlotItem.py new file mode 100644 index 00000000..b02a9dda --- /dev/null +++ b/pyqtgraph/opengl/items/GLScatterPlotItem.py @@ -0,0 +1,183 @@ +from OpenGL.GL import * +from OpenGL.arrays import vbo +from .. GLGraphicsItem import GLGraphicsItem +from .. import shaders +from pyqtgraph import QtGui +import numpy as np + +__all__ = ['GLScatterPlotItem'] + +class GLScatterPlotItem(GLGraphicsItem): + """Draws points at a list of 3D positions.""" + + def __init__(self, **kwds): + GLGraphicsItem.__init__(self) + glopts = kwds.pop('glOptions', 'additive') + self.setGLOptions(glopts) + self.pos = [] + self.size = 10 + self.color = [1.0,1.0,1.0,0.5] + self.pxMode = True + #self.vbo = {} ## VBO does not appear to improve performance very much. + self.setData(**kwds) + + def setData(self, **kwds): + """ + Update the data displayed by this item. All arguments are optional; + for example it is allowed to update spot positions while leaving + colors unchanged, etc. + + ==================== ================================================== + Arguments: + ------------------------------------------------------------------------ + pos (N,3) array of floats specifying point locations. + color (N,4) array of floats (0.0-1.0) specifying + spot colors OR a tuple of floats specifying + a single color for all spots. + size (N,) array of floats specifying spot sizes or + a single value to apply to all spots. + pxMode If True, spot sizes are expressed in pixels. + Otherwise, they are expressed in item coordinates. + ==================== ================================================== + """ + args = ['pos', 'color', 'size', 'pxMode'] + for k in kwds.keys(): + if k not in args: + raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) + + args.remove('pxMode') + for arg in args: + if arg in kwds: + setattr(self, arg, kwds[arg]) + #self.vbo.pop(arg, None) + + self.pxMode = kwds.get('pxMode', self.pxMode) + self.update() + + def initializeGL(self): + + ## Generate texture for rendering points + w = 64 + def fn(x,y): + r = ((x-w/2.)**2 + (y-w/2.)**2) ** 0.5 + return 200 * (w/2. - np.clip(r, w/2.-1.0, w/2.)) + pData = np.empty((w, w, 4)) + pData[:] = 255 + pData[:,:,3] = np.fromfunction(fn, pData.shape[:2]) + #print pData.shape, pData.min(), pData.max() + pData = pData.astype(np.ubyte) + + self.pointTexture = glGenTextures(1) + glActiveTexture(GL_TEXTURE0) + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.pointTexture) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pData.shape[0], pData.shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, pData) + + self.shader = shaders.getShaderProgram('pointSprite') + + #def getVBO(self, name): + #if name not in self.vbo: + #self.vbo[name] = vbo.VBO(getattr(self, name).astype('f')) + #return self.vbo[name] + + #def setupGLState(self): + #"""Prepare OpenGL state for drawing. This function is called immediately before painting.""" + ##glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly. + #glBlendFunc(GL_SRC_ALPHA, GL_ONE) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + #glDisable( GL_DEPTH_TEST ) + + ##glEnable( GL_POINT_SMOOTH ) + + ##glHint(GL_POINT_SMOOTH_HINT, GL_NICEST) + ##glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3)) + ##glPointParameterfv(GL_POINT_SIZE_MAX, (65500,)) + ##glPointParameterfv(GL_POINT_SIZE_MIN, (0,)) + + def paint(self): + self.setupGLState() + + glEnable(GL_POINT_SPRITE) + + glActiveTexture(GL_TEXTURE0) + glEnable( GL_TEXTURE_2D ) + glBindTexture(GL_TEXTURE_2D, self.pointTexture) + + glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE) + #glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) ## use texture color exactly + #glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ) ## texture modulates current color + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + glEnable(GL_PROGRAM_POINT_SIZE) + + + with self.shader: + #glUniform1i(self.shader.uniform('texture'), 0) ## inform the shader which texture to use + glEnableClientState(GL_VERTEX_ARRAY) + try: + pos = self.pos + #if pos.ndim > 2: + #pos = pos.reshape((reduce(lambda a,b: a*b, pos.shape[:-1]), pos.shape[-1])) + glVertexPointerf(pos) + + if isinstance(self.color, np.ndarray): + glEnableClientState(GL_COLOR_ARRAY) + glColorPointerf(self.color) + else: + if isinstance(self.color, QtGui.QColor): + glColor4f(*fn.glColor(self.color)) + else: + glColor4f(*self.color) + + if not self.pxMode or isinstance(self.size, np.ndarray): + glEnableClientState(GL_NORMAL_ARRAY) + norm = np.empty(pos.shape) + if self.pxMode: + norm[...,0] = self.size + else: + gpos = self.mapToView(pos.transpose()).transpose() + pxSize = self.view().pixelSize(gpos) + norm[...,0] = self.size / pxSize + + glNormalPointerf(norm) + else: + glNormal3f(self.size, 0, 0) ## vertex shader uses norm.x to determine point size + #glPointSize(self.size) + glDrawArrays(GL_POINTS, 0, int(pos.size / pos.shape[-1])) + finally: + glDisableClientState(GL_NORMAL_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + glDisableClientState(GL_COLOR_ARRAY) + #posVBO.unbind() + + #for i in range(len(self.pos)): + #pos = self.pos[i] + + #if isinstance(self.color, np.ndarray): + #color = self.color[i] + #else: + #color = self.color + #if isinstance(self.color, QtGui.QColor): + #color = fn.glColor(self.color) + + #if isinstance(self.size, np.ndarray): + #size = self.size[i] + #else: + #size = self.size + + #pxSize = self.view().pixelSize(QtGui.QVector3D(*pos)) + + #glPointSize(size / pxSize) + #glBegin( GL_POINTS ) + #glColor4f(*color) # x is blue + ##glNormal3f(size, 0, 0) + #glVertex3f(*pos) + #glEnd() + + + + + diff --git a/pyqtgraph/opengl/items/GLSurfacePlotItem.py b/pyqtgraph/opengl/items/GLSurfacePlotItem.py new file mode 100644 index 00000000..88d50fac --- /dev/null +++ b/pyqtgraph/opengl/items/GLSurfacePlotItem.py @@ -0,0 +1,139 @@ +from OpenGL.GL import * +from .GLMeshItem import GLMeshItem +from .. MeshData import MeshData +from pyqtgraph.Qt import QtGui +import pyqtgraph as pg +import numpy as np + + + +__all__ = ['GLSurfacePlotItem'] + +class GLSurfacePlotItem(GLMeshItem): + """ + **Bases:** :class:`GLMeshItem ` + + Displays a surface plot on a regular x,y grid + """ + def __init__(self, x=None, y=None, z=None, colors=None, **kwds): + """ + The x, y, z, and colors arguments are passed to setData(). + All other keyword arguments are passed to GLMeshItem.__init__(). + """ + + self._x = None + self._y = None + self._z = None + self._color = None + self._vertexes = None + self._meshdata = MeshData() + GLMeshItem.__init__(self, meshdata=self._meshdata, **kwds) + + self.setData(x, y, z, colors) + + + + def setData(self, x=None, y=None, z=None, colors=None): + """ + Update the data in this surface plot. + + ========== ===================================================================== + Arguments + x,y 1D arrays of values specifying the x,y positions of vertexes in the + grid. If these are omitted, then the values will be assumed to be + integers. + z 2D array of height values for each grid vertex. + colors (width, height, 4) array of vertex colors. + ========== ===================================================================== + + All arguments are optional. + + Note that if vertex positions are updated, the normal vectors for each triangle must + be recomputed. This is somewhat expensive if the surface was initialized with smooth=False + and very expensive if smooth=True. For faster performance, initialize with + computeNormals=False and use per-vertex colors or a normal-independent shader program. + """ + if x is not None: + if self._x is None or len(x) != len(self._x): + self._vertexes = None + self._x = x + + if y is not None: + if self._y is None or len(y) != len(self._y): + self._vertexes = None + self._y = y + + if z is not None: + #if self._x is None: + #self._x = np.arange(z.shape[0]) + #self._vertexes = None + #if self._y is None: + #self._y = np.arange(z.shape[1]) + #self._vertexes = None + + if self._x is not None and z.shape[0] != len(self._x): + raise Exception('Z values must have shape (len(x), len(y))') + if self._y is not None and z.shape[1] != len(self._y): + raise Exception('Z values must have shape (len(x), len(y))') + self._z = z + if self._vertexes is not None and self._z.shape != self._vertexes.shape[:2]: + self._vertexes = None + + if colors is not None: + self._colors = colors + self._meshdata.setVertexColors(colors) + + if self._z is None: + return + + updateMesh = False + newVertexes = False + + ## Generate vertex and face array + if self._vertexes is None: + newVertexes = True + self._vertexes = np.empty((self._z.shape[0], self._z.shape[1], 3), dtype=float) + self.generateFaces() + self._meshdata.setFaces(self._faces) + updateMesh = True + + ## Copy x, y, z data into vertex array + if newVertexes or x is not None: + if x is None: + if self._x is None: + x = np.arange(self._z.shape[0]) + else: + x = self._x + self._vertexes[:, :, 0] = x.reshape(len(x), 1) + updateMesh = True + + if newVertexes or y is not None: + if y is None: + if self._y is None: + y = np.arange(self._z.shape[1]) + else: + y = self._y + self._vertexes[:, :, 1] = y.reshape(1, len(y)) + updateMesh = True + + if newVertexes or z is not None: + self._vertexes[...,2] = self._z + updateMesh = True + + ## Update MeshData + if updateMesh: + self._meshdata.setVertexes(self._vertexes.reshape(self._vertexes.shape[0]*self._vertexes.shape[1], 3)) + self.meshDataChanged() + + + def generateFaces(self): + cols = self._z.shape[1]-1 + rows = self._z.shape[0]-1 + faces = np.empty((cols*rows*2, 3), dtype=np.uint) + rowtemplate1 = np.arange(cols).reshape(cols, 1) + np.array([[0, 1, cols+1]]) + rowtemplate2 = np.arange(cols).reshape(cols, 1) + np.array([[cols+1, 1, cols+2]]) + for row in range(rows): + start = row * cols * 2 + faces[start:start+cols] = rowtemplate1 + row * (cols+1) + faces[start+cols:start+(cols*2)] = rowtemplate2 + row * (cols+1) + self._faces = faces diff --git a/pyqtgraph/opengl/items/GLVolumeItem.py b/pyqtgraph/opengl/items/GLVolumeItem.py new file mode 100644 index 00000000..4980239d --- /dev/null +++ b/pyqtgraph/opengl/items/GLVolumeItem.py @@ -0,0 +1,213 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from pyqtgraph.Qt import QtGui +import numpy as np + +__all__ = ['GLVolumeItem'] + +class GLVolumeItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays volumetric data. + """ + + + def __init__(self, data, sliceDensity=1, smooth=True, glOptions='translucent'): + """ + ============== ======================================================================================= + **Arguments:** + data Volume data to be rendered. *Must* be 4D numpy array (x, y, z, RGBA) with dtype=ubyte. + sliceDensity Density of slices to render through the volume. A value of 1 means one slice per voxel. + smooth (bool) If True, the volume slices are rendered with linear interpolation + ============== ======================================================================================= + """ + + self.sliceDensity = sliceDensity + self.smooth = smooth + self.data = data + GLGraphicsItem.__init__(self) + self.setGLOptions(glOptions) + + def initializeGL(self): + glEnable(GL_TEXTURE_3D) + self.texture = glGenTextures(1) + glBindTexture(GL_TEXTURE_3D, self.texture) + if self.smooth: + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + else: + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) + shape = self.data.shape + + ## Test texture dimensions first + glTexImage3D(GL_PROXY_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) + if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_WIDTH) == 0: + raise Exception("OpenGL failed to create 3D texture (%dx%dx%d); too large for this hardware." % shape[:3]) + + glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((2,1,0,3))) + glDisable(GL_TEXTURE_3D) + + self.lists = {} + for ax in [0,1,2]: + for d in [-1, 1]: + l = glGenLists(1) + self.lists[(ax,d)] = l + glNewList(l, GL_COMPILE) + self.drawVolume(ax, d) + glEndList() + + + def paint(self): + self.setupGLState() + + glEnable(GL_TEXTURE_3D) + glBindTexture(GL_TEXTURE_3D, self.texture) + + #glEnable(GL_DEPTH_TEST) + #glDisable(GL_CULL_FACE) + #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + glColor4f(1,1,1,1) + + view = self.view() + center = QtGui.QVector3D(*[x/2. for x in self.data.shape[:3]]) + cam = self.mapFromParent(view.cameraPosition()) - center + #print "center", center, "cam", view.cameraPosition(), self.mapFromParent(view.cameraPosition()), "diff", cam + cam = np.array([cam.x(), cam.y(), cam.z()]) + ax = np.argmax(abs(cam)) + d = 1 if cam[ax] > 0 else -1 + glCallList(self.lists[(ax,d)]) ## draw axes + glDisable(GL_TEXTURE_3D) + + def drawVolume(self, ax, d): + N = 5 + + imax = [0,1,2] + imax.remove(ax) + + tp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]] + vp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]] + nudge = [0.5/x for x in self.data.shape] + tp[0][imax[0]] = 0+nudge[imax[0]] + tp[0][imax[1]] = 0+nudge[imax[1]] + tp[1][imax[0]] = 1-nudge[imax[0]] + tp[1][imax[1]] = 0+nudge[imax[1]] + tp[2][imax[0]] = 1-nudge[imax[0]] + tp[2][imax[1]] = 1-nudge[imax[1]] + tp[3][imax[0]] = 0+nudge[imax[0]] + tp[3][imax[1]] = 1-nudge[imax[1]] + + vp[0][imax[0]] = 0 + vp[0][imax[1]] = 0 + vp[1][imax[0]] = self.data.shape[imax[0]] + vp[1][imax[1]] = 0 + vp[2][imax[0]] = self.data.shape[imax[0]] + vp[2][imax[1]] = self.data.shape[imax[1]] + vp[3][imax[0]] = 0 + vp[3][imax[1]] = self.data.shape[imax[1]] + slices = self.data.shape[ax] * self.sliceDensity + r = list(range(slices)) + if d == -1: + r = r[::-1] + + glBegin(GL_QUADS) + tzVals = np.linspace(nudge[ax], 1.0-nudge[ax], slices) + vzVals = np.linspace(0, self.data.shape[ax], slices) + for i in r: + z = tzVals[i] + w = vzVals[i] + + tp[0][ax] = z + tp[1][ax] = z + tp[2][ax] = z + tp[3][ax] = z + + vp[0][ax] = w + vp[1][ax] = w + vp[2][ax] = w + vp[3][ax] = w + + + glTexCoord3f(*tp[0]) + glVertex3f(*vp[0]) + glTexCoord3f(*tp[1]) + glVertex3f(*vp[1]) + glTexCoord3f(*tp[2]) + glVertex3f(*vp[2]) + glTexCoord3f(*tp[3]) + glVertex3f(*vp[3]) + glEnd() + + + + + + + + + + ## Interesting idea: + ## remove projection/modelview matrixes, recreate in texture coords. + ## it _sorta_ works, but needs tweaking. + #mvm = glGetDoublev(GL_MODELVIEW_MATRIX) + #pm = glGetDoublev(GL_PROJECTION_MATRIX) + #m = QtGui.QMatrix4x4(mvm.flatten()).inverted()[0] + #p = QtGui.QMatrix4x4(pm.flatten()).inverted()[0] + + #glMatrixMode(GL_PROJECTION) + #glPushMatrix() + #glLoadIdentity() + #N=1 + #glOrtho(-N,N,-N,N,-100,100) + + #glMatrixMode(GL_MODELVIEW) + #glLoadIdentity() + + + #glMatrixMode(GL_TEXTURE) + #glLoadIdentity() + #glMultMatrixf(m.copyDataTo()) + + #view = self.view() + #w = view.width() + #h = view.height() + #dist = view.opts['distance'] + #fov = view.opts['fov'] + #nearClip = dist * .1 + #farClip = dist * 5. + #r = nearClip * np.tan(fov) + #t = r * h / w + + #p = QtGui.QMatrix4x4() + #p.frustum( -r, r, -t, t, nearClip, farClip) + #glMultMatrixf(p.inverted()[0].copyDataTo()) + + + #glBegin(GL_QUADS) + + #M=1 + #for i in range(500): + #z = i/500. + #w = -i/500. + #glTexCoord3f(-M, -M, z) + #glVertex3f(-N, -N, w) + #glTexCoord3f(M, -M, z) + #glVertex3f(N, -N, w) + #glTexCoord3f(M, M, z) + #glVertex3f(N, N, w) + #glTexCoord3f(-M, M, z) + #glVertex3f(-N, N, w) + #glEnd() + #glDisable(GL_TEXTURE_3D) + + #glMatrixMode(GL_PROJECTION) + #glPopMatrix() + + + diff --git a/pyqtgraph/opengl/items/__init__.py b/pyqtgraph/opengl/items/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyqtgraph/opengl/shaders.py b/pyqtgraph/opengl/shaders.py new file mode 100644 index 00000000..8922cd21 --- /dev/null +++ b/pyqtgraph/opengl/shaders.py @@ -0,0 +1,402 @@ +try: + from OpenGL import NullFunctionError +except ImportError: + from OpenGL.error import NullFunctionError +from OpenGL.GL import * +from OpenGL.GL import shaders +import re + +## For centralizing and managing vertex/fragment shader programs. + +def initShaders(): + global Shaders + Shaders = [ + ShaderProgram(None, []), + + ## increases fragment alpha as the normal turns orthogonal to the view + ## this is useful for viewing shells that enclose a volume (such as isosurfaces) + ShaderProgram('balloon', [ + VertexShader(""" + varying vec3 normal; + void main() { + // compute here for use in fragment shader + normal = normalize(gl_NormalMatrix * gl_Normal); + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + varying vec3 normal; + void main() { + vec4 color = gl_Color; + color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0); + gl_FragColor = color; + } + """) + ]), + + ## colors fragments based on face normals relative to view + ## This means that the colors will change depending on how the view is rotated + ShaderProgram('viewNormalColor', [ + VertexShader(""" + varying vec3 normal; + void main() { + // compute here for use in fragment shader + normal = normalize(gl_NormalMatrix * gl_Normal); + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + varying vec3 normal; + void main() { + vec4 color = gl_Color; + color.x = (normal.x + 1.0) * 0.5; + color.y = (normal.y + 1.0) * 0.5; + color.z = (normal.z + 1.0) * 0.5; + gl_FragColor = color; + } + """) + ]), + + ## colors fragments based on absolute face normals. + ShaderProgram('normalColor', [ + VertexShader(""" + varying vec3 normal; + void main() { + // compute here for use in fragment shader + normal = normalize(gl_Normal); + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + varying vec3 normal; + void main() { + vec4 color = gl_Color; + color.x = (normal.x + 1.0) * 0.5; + color.y = (normal.y + 1.0) * 0.5; + color.z = (normal.z + 1.0) * 0.5; + gl_FragColor = color; + } + """) + ]), + + ## very simple simulation of lighting. + ## The light source position is always relative to the camera. + ShaderProgram('shaded', [ + VertexShader(""" + varying vec3 normal; + void main() { + // compute here for use in fragment shader + normal = normalize(gl_NormalMatrix * gl_Normal); + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + varying vec3 normal; + void main() { + float p = dot(normal, normalize(vec3(1.0, -1.0, -1.0))); + p = p < 0. ? 0. : p * 0.8; + vec4 color = gl_Color; + color.x = color.x * (0.2 + p); + color.y = color.y * (0.2 + p); + color.z = color.z * (0.2 + p); + gl_FragColor = color; + } + """) + ]), + + ## colors get brighter near edges of object + ShaderProgram('edgeHilight', [ + VertexShader(""" + varying vec3 normal; + void main() { + // compute here for use in fragment shader + normal = normalize(gl_NormalMatrix * gl_Normal); + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + varying vec3 normal; + void main() { + vec4 color = gl_Color; + float s = pow(normal.x*normal.x + normal.y*normal.y, 2.0); + color.x = color.x + s * (1.0-color.x); + color.y = color.y + s * (1.0-color.y); + color.z = color.z + s * (1.0-color.z); + gl_FragColor = color; + } + """) + ]), + + ## colors fragments by z-value. + ## This is useful for coloring surface plots by height. + ## This shader uses a uniform called "colorMap" to determine how to map the colors: + ## red = pow(z * colorMap[0] + colorMap[1], colorMap[2]) + ## green = pow(z * colorMap[3] + colorMap[4], colorMap[5]) + ## blue = pow(z * colorMap[6] + colorMap[7], colorMap[8]) + ## (set the values like this: shader['uniformMap'] = array([...]) + ShaderProgram('heightColor', [ + VertexShader(""" + varying vec4 pos; + void main() { + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + pos = gl_Vertex; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + uniform float colorMap[9]; + varying vec4 pos; + //out vec4 gl_FragColor; // only needed for later glsl versions + //in vec4 gl_Color; + void main() { + vec4 color = gl_Color; + color.x = colorMap[0] * (pos.z + colorMap[1]); + if (colorMap[2] != 1.0) + color.x = pow(color.x, colorMap[2]); + color.x = color.x < 0. ? 0. : (color.x > 1. ? 1. : color.x); + + color.y = colorMap[3] * (pos.z + colorMap[4]); + if (colorMap[5] != 1.0) + color.y = pow(color.y, colorMap[5]); + color.y = color.y < 0. ? 0. : (color.y > 1. ? 1. : color.y); + + color.z = colorMap[6] * (pos.z + colorMap[7]); + if (colorMap[8] != 1.0) + color.z = pow(color.z, colorMap[8]); + color.z = color.z < 0. ? 0. : (color.z > 1. ? 1. : color.z); + + color.w = 1.0; + gl_FragColor = color; + } + """), + ], uniforms={'colorMap': [1, 1, 1, 1, 0.5, 1, 1, 0, 1]}), + ShaderProgram('pointSprite', [ ## allows specifying point size using normal.x + ## See: + ## + ## http://stackoverflow.com/questions/9609423/applying-part-of-a-texture-sprite-sheet-texture-map-to-a-point-sprite-in-ios + ## http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 + ## + ## + VertexShader(""" + void main() { + gl_FrontColor=gl_Color; + gl_PointSize = gl_Normal.x; + gl_Position = ftransform(); + } + """), + #FragmentShader(""" + ##version 120 + #uniform sampler2D texture; + #void main ( ) + #{ + #gl_FragColor = texture2D(texture, gl_PointCoord) * gl_Color; + #} + #""") + ]), + ] + + +CompiledShaderPrograms = {} + +def getShaderProgram(name): + return ShaderProgram.names[name] + +class Shader(object): + def __init__(self, shaderType, code): + self.shaderType = shaderType + self.code = code + self.compiled = None + + def shader(self): + if self.compiled is None: + try: + self.compiled = shaders.compileShader(self.code, self.shaderType) + except NullFunctionError: + raise Exception("This OpenGL implementation does not support shader programs; many OpenGL features in pyqtgraph will not work.") + except RuntimeError as exc: + ## Format compile errors a bit more nicely + if len(exc.args) == 3: + err, code, typ = exc.args + if not err.startswith('Shader compile failure'): + raise + code = code[0].decode('utf_8').split('\n') + err, c, msgs = err.partition(':') + err = err + '\n' + msgs = re.sub('b\'','',msgs) + msgs = re.sub('\'$','',msgs) + msgs = re.sub('\\\\n','\n',msgs) + msgs = msgs.split('\n') + errNums = [()] * len(code) + for i, msg in enumerate(msgs): + msg = msg.strip() + if msg == '': + continue + m = re.match(r'(\d+\:)?\d+\((\d+)\)', msg) + if m is not None: + line = int(m.groups()[1]) + errNums[line-1] = errNums[line-1] + (str(i+1),) + #code[line-1] = '%d\t%s' % (i+1, code[line-1]) + err = err + "%d %s\n" % (i+1, msg) + errNums = [','.join(n) for n in errNums] + maxlen = max(map(len, errNums)) + code = [errNums[i] + " "*(maxlen-len(errNums[i])) + line for i, line in enumerate(code)] + err = err + '\n'.join(code) + raise Exception(err) + else: + raise + return self.compiled + +class VertexShader(Shader): + def __init__(self, code): + Shader.__init__(self, GL_VERTEX_SHADER, code) + +class FragmentShader(Shader): + def __init__(self, code): + Shader.__init__(self, GL_FRAGMENT_SHADER, code) + + + + +class ShaderProgram(object): + names = {} + + def __init__(self, name, shaders, uniforms=None): + self.name = name + ShaderProgram.names[name] = self + self.shaders = shaders + self.prog = None + self.blockData = {} + self.uniformData = {} + + ## parse extra options from the shader definition + if uniforms is not None: + for k,v in uniforms.items(): + self[k] = v + + def setBlockData(self, blockName, data): + if data is None: + del self.blockData[blockName] + else: + self.blockData[blockName] = data + + def setUniformData(self, uniformName, data): + if data is None: + del self.uniformData[uniformName] + else: + self.uniformData[uniformName] = data + + def __setitem__(self, item, val): + self.setUniformData(item, val) + + def __delitem__(self, item): + self.setUniformData(item, None) + + def program(self): + if self.prog is None: + try: + compiled = [s.shader() for s in self.shaders] ## compile all shaders + self.prog = shaders.compileProgram(*compiled) ## compile program + except: + self.prog = -1 + raise + return self.prog + + def __enter__(self): + if len(self.shaders) > 0 and self.program() != -1: + glUseProgram(self.program()) + + try: + ## load uniform values into program + for uniformName, data in self.uniformData.items(): + loc = self.uniform(uniformName) + if loc == -1: + raise Exception('Could not find uniform variable "%s"' % uniformName) + glUniform1fv(loc, len(data), data) + + ### bind buffer data to program blocks + #if len(self.blockData) > 0: + #bindPoint = 1 + #for blockName, data in self.blockData.items(): + ### Program should have a uniform block declared: + ### + ### layout (std140) uniform blockName { + ### vec4 diffuse; + ### }; + + ### pick any-old binding point. (there are a limited number of these per-program + #bindPoint = 1 + + ### get the block index for a uniform variable in the shader + #blockIndex = glGetUniformBlockIndex(self.program(), blockName) + + ### give the shader block a binding point + #glUniformBlockBinding(self.program(), blockIndex, bindPoint) + + ### create a buffer + #buf = glGenBuffers(1) + #glBindBuffer(GL_UNIFORM_BUFFER, buf) + #glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW) + ### also possible to use glBufferSubData to fill parts of the buffer + + ### bind buffer to the same binding point + #glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf) + except: + glUseProgram(0) + raise + + + + def __exit__(self, *args): + if len(self.shaders) > 0: + glUseProgram(0) + + def uniform(self, name): + """Return the location integer for a uniform variable in this program""" + return glGetUniformLocation(self.program(), name.encode('utf_8')) + + #def uniformBlockInfo(self, blockName): + #blockIndex = glGetUniformBlockIndex(self.program(), blockName) + #count = glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS) + #indices = [] + #for i in range(count): + #indices.append(glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES)) + +class HeightColorShader(ShaderProgram): + def __enter__(self): + ## Program should have a uniform block declared: + ## + ## layout (std140) uniform blockName { + ## vec4 diffuse; + ## vec4 ambient; + ## }; + + ## pick any-old binding point. (there are a limited number of these per-program + bindPoint = 1 + + ## get the block index for a uniform variable in the shader + blockIndex = glGetUniformBlockIndex(self.program(), "blockName") + + ## give the shader block a binding point + glUniformBlockBinding(self.program(), blockIndex, bindPoint) + + ## create a buffer + buf = glGenBuffers(1) + glBindBuffer(GL_UNIFORM_BUFFER, buf) + glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW) + ## also possible to use glBufferSubData to fill parts of the buffer + + ## bind buffer to the same binding point + glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf) + +initShaders() diff --git a/pyqtgraph/ordereddict.py b/pyqtgraph/ordereddict.py new file mode 100644 index 00000000..7242b506 --- /dev/null +++ b/pyqtgraph/ordereddict.py @@ -0,0 +1,127 @@ +# Copyright (c) 2009 Raymond Hettinger +# +# 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. + +from UserDict import DictMixin + +class OrderedDict(dict, DictMixin): + + def __init__(self, *args, **kwds): + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__end + except AttributeError: + self.clear() + self.update(*args, **kwds) + + def clear(self): + self.__end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.__map = {} # key --> [key, prev, next] + dict.clear(self) + + def __setitem__(self, key, value): + if key not in self: + end = self.__end + curr = end[1] + curr[2] = end[1] = self.__map[key] = [key, curr, end] + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + key, prev, next = self.__map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.__end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.__end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def popitem(self, last=True): + if not self: + raise KeyError('dictionary is empty') + if last: + key = reversed(self).next() + else: + key = iter(self).next() + value = self.pop(key) + return key, value + + def __reduce__(self): + items = [[k, self[k]] for k in self] + tmp = self.__map, self.__end + del self.__map, self.__end + inst_dict = vars(self).copy() + self.__map, self.__end = tmp + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def keys(self): + return list(self) + + setdefault = DictMixin.setdefault + update = DictMixin.update + pop = DictMixin.pop + values = DictMixin.values + items = DictMixin.items + iterkeys = DictMixin.iterkeys + itervalues = DictMixin.itervalues + iteritems = DictMixin.iteritems + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def copy(self): + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + if isinstance(other, OrderedDict): + if len(self) != len(other): + return False + for p, q in zip(self.items(), other.items()): + if p != q: + return False + return True + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other diff --git a/pyqtgraph/parametertree/Parameter.py b/pyqtgraph/parametertree/Parameter.py new file mode 100644 index 00000000..9a7ece25 --- /dev/null +++ b/pyqtgraph/parametertree/Parameter.py @@ -0,0 +1,710 @@ +from pyqtgraph.Qt import QtGui, QtCore +import os, weakref, re +from pyqtgraph.pgcollections import OrderedDict +from .ParameterItem import ParameterItem + +PARAM_TYPES = {} +PARAM_NAMES = {} + +def registerParameterType(name, cls, override=False): + global PARAM_TYPES + if name in PARAM_TYPES and not override: + raise Exception("Parameter type '%s' already exists (use override=True to replace)" % name) + PARAM_TYPES[name] = cls + PARAM_NAMES[cls] = name + + + +class Parameter(QtCore.QObject): + """ + A Parameter is the basic unit of data in a parameter tree. Each parameter has + a name, a type, a value, and several other properties that modify the behavior of the + Parameter. Parameters may have parent / child / sibling relationships to construct + organized hierarchies. Parameters generally do not have any inherent GUI or visual + interpretation; instead they manage ParameterItem instances which take care of + display and user interaction. + + Note: It is fairly uncommon to use the Parameter class directly; mostly you + will use subclasses which provide specialized type and data handling. The static + pethod Parameter.create(...) is an easy way to generate instances of these subclasses. + + For more Parameter types, see ParameterTree.parameterTypes module. + + =================================== ========================================================= + **Signals:** + sigStateChanged(self, change, info) Emitted when anything changes about this parameter at + all. + The second argument is a string indicating what changed + ('value', 'childAdded', etc..) + The third argument can be any extra information about + the change + sigTreeStateChanged(self, changes) Emitted when any child in the tree changes state + (but only if monitorChildren() is called) + the format of *changes* is [(param, change, info), ...] + sigValueChanged(self, value) Emitted when value is finished changing + sigValueChanging(self, value) Emitted immediately for all value changes, + including during editing. + sigChildAdded(self, child, index) Emitted when a child is added + sigChildRemoved(self, child) Emitted when a child is removed + sigParentChanged(self, parent) Emitted when this parameter's parent has changed + sigLimitsChanged(self, limits) Emitted when this parameter's limits have changed + sigDefaultChanged(self, default) Emitted when this parameter's default value has changed + sigNameChanged(self, name) Emitted when this parameter's name has changed + sigOptionsChanged(self, opts) Emitted when any of this parameter's options have changed + =================================== ========================================================= + """ + ## name, type, limits, etc. + ## can also carry UI hints (slider vs spinbox, etc.) + + sigValueChanged = QtCore.Signal(object, object) ## self, value emitted when value is finished being edited + sigValueChanging = QtCore.Signal(object, object) ## self, value emitted as value is being edited + + sigChildAdded = QtCore.Signal(object, object, object) ## self, child, index + sigChildRemoved = QtCore.Signal(object, object) ## self, child + sigParentChanged = QtCore.Signal(object, object) ## self, parent + sigLimitsChanged = QtCore.Signal(object, object) ## self, limits + sigDefaultChanged = QtCore.Signal(object, object) ## self, default + sigNameChanged = QtCore.Signal(object, object) ## self, name + sigOptionsChanged = QtCore.Signal(object, object) ## self, {opt:val, ...} + + ## Emitted when anything changes about this parameter at all. + ## The second argument is a string indicating what changed ('value', 'childAdded', etc..) + ## The third argument can be any extra information about the change + sigStateChanged = QtCore.Signal(object, object, object) ## self, change, info + + ## emitted when any child in the tree changes state + ## (but only if monitorChildren() is called) + sigTreeStateChanged = QtCore.Signal(object, object) # self, changes + # changes = [(param, change, info), ...] + + # bad planning. + #def __new__(cls, *args, **opts): + #try: + #cls = PARAM_TYPES[opts['type']] + #except KeyError: + #pass + #return QtCore.QObject.__new__(cls, *args, **opts) + + @staticmethod + def create(**opts): + """ + Static method that creates a new Parameter (or subclass) instance using + opts['type'] to select the appropriate class. + + All options are passed directly to the new Parameter's __init__ method. + Use registerParameterType() to add new class types. + """ + typ = opts.get('type', None) + if typ is None: + cls = Parameter + else: + cls = PARAM_TYPES[opts['type']] + return cls(**opts) + + def __init__(self, **opts): + """ + Initialize a Parameter object. Although it is rare to directly create a + Parameter instance, the options available to this method are also allowed + by most Parameter subclasses. + + ================= ========================================================= + Keyword Arguments + name The name to give this Parameter. This is the name that + will appear in the left-most column of a ParameterTree + for this Parameter. + value The value to initially assign to this Parameter. + default The default value for this Parameter (most Parameters + provide an option to 'reset to default'). + children A list of children for this Parameter. Children + may be given either as a Parameter instance or as a + dictionary to pass to Parameter.create(). In this way, + it is possible to specify complex hierarchies of + Parameters from a single nested data structure. + readonly If True, the user will not be allowed to edit this + Parameter. (default=False) + enabled If False, any widget(s) for this parameter will appear + disabled. (default=True) + visible If False, the Parameter will not appear when displayed + in a ParameterTree. (default=True) + renamable If True, the user may rename this Parameter. + (default=False) + removable If True, the user may remove this Parameter. + (default=False) + expanded If True, the Parameter will appear expanded when + displayed in a ParameterTree (its children will be + visible). (default=True) + ================= ========================================================= + """ + + + QtCore.QObject.__init__(self) + + self.opts = { + 'type': None, + 'readonly': False, + 'visible': True, + 'enabled': True, + 'renamable': False, + 'removable': False, + 'strictNaming': False, # forces name to be usable as a python variable + 'expanded': True, + #'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits. + } + self.opts.update(opts) + + self.childs = [] + self.names = {} ## map name:child + self.items = weakref.WeakKeyDictionary() ## keeps track of tree items representing this parameter + self._parent = None + self.treeStateChanges = [] ## cache of tree state changes to be delivered on next emit + self.blockTreeChangeEmit = 0 + #self.monitoringChildren = False ## prevent calling monitorChildren more than once + + if 'value' not in self.opts: + self.opts['value'] = None + + if 'name' not in self.opts or not isinstance(self.opts['name'], basestring): + raise Exception("Parameter must have a string name specified in opts.") + self.setName(opts['name']) + + self.addChildren(self.opts.get('children', [])) + + if 'value' in self.opts and 'default' not in self.opts: + self.opts['default'] = self.opts['value'] + + ## Connect all state changed signals to the general sigStateChanged + self.sigValueChanged.connect(lambda param, data: self.emitStateChanged('value', data)) + self.sigChildAdded.connect(lambda param, *data: self.emitStateChanged('childAdded', data)) + self.sigChildRemoved.connect(lambda param, data: self.emitStateChanged('childRemoved', data)) + self.sigParentChanged.connect(lambda param, data: self.emitStateChanged('parent', data)) + self.sigLimitsChanged.connect(lambda param, data: self.emitStateChanged('limits', data)) + self.sigDefaultChanged.connect(lambda param, data: self.emitStateChanged('default', data)) + self.sigNameChanged.connect(lambda param, data: self.emitStateChanged('name', data)) + self.sigOptionsChanged.connect(lambda param, data: self.emitStateChanged('options', data)) + + #self.watchParam(self) ## emit treechange signals if our own state changes + + def name(self): + """Return the name of this Parameter.""" + return self.opts['name'] + + def setName(self, name): + """Attempt to change the name of this parameter; return the actual name. + (The parameter may reject the name change or automatically pick a different name)""" + if self.opts['strictNaming']: + if len(name) < 1 or re.search(r'\W', name) or re.match(r'\d', name[0]): + raise Exception("Parameter name '%s' is invalid. (Must contain only alphanumeric and underscore characters and may not start with a number)" % name) + parent = self.parent() + if parent is not None: + name = parent._renameChild(self, name) ## first ask parent if it's ok to rename + if self.opts['name'] != name: + self.opts['name'] = name + self.sigNameChanged.emit(self, name) + return name + + def type(self): + """Return the type string for this Parameter.""" + return self.opts['type'] + + def isType(self, typ): + """ + Return True if this parameter type matches the name *typ*. + This can occur either of two ways: + + - If self.type() == *typ* + - If this parameter's class is registered with the name *typ* + """ + if self.type() == typ: + return True + global PARAM_TYPES + cls = PARAM_TYPES.get(typ, None) + if cls is None: + raise Exception("Type name '%s' is not registered." % str(typ)) + return self.__class__ is cls + + def childPath(self, child): + """ + Return the path of parameter names from self to child. + If child is not a (grand)child of self, return None. + """ + path = [] + while child is not self: + path.insert(0, child.name()) + child = child.parent() + if child is None: + return None + return path + + def setValue(self, value, blockSignal=None): + """ + Set the value of this Parameter; return the actual value that was set. + (this may be different from the value that was requested) + """ + try: + if blockSignal is not None: + self.sigValueChanged.disconnect(blockSignal) + if self.opts['value'] == value: + return value + self.opts['value'] = value + self.sigValueChanged.emit(self, value) + finally: + if blockSignal is not None: + self.sigValueChanged.connect(blockSignal) + + return value + + def value(self): + """ + Return the value of this Parameter. + """ + return self.opts['value'] + + def getValues(self): + """Return a tree of all values that are children of this parameter""" + vals = OrderedDict() + for ch in self: + vals[ch.name()] = (ch.value(), ch.getValues()) + return vals + + def saveState(self): + """ + Return a structure representing the entire state of the parameter tree. + The tree state may be restored from this structure using restoreState() + """ + state = self.opts.copy() + state['children'] = OrderedDict([(ch.name(), ch.saveState()) for ch in self]) + if state['type'] is None: + global PARAM_NAMES + state['type'] = PARAM_NAMES.get(type(self), None) + return state + + def restoreState(self, state, recursive=True, addChildren=True, removeChildren=True, blockSignals=True): + """ + Restore the state of this parameter and its children from a structure generated using saveState() + If recursive is True, then attempt to restore the state of child parameters as well. + If addChildren is True, then any children which are referenced in the state object will be + created if they do not already exist. + If removeChildren is True, then any children which are not referenced in the state object will + be removed. + If blockSignals is True, no signals will be emitted until the tree has been completely restored. + This prevents signal handlers from responding to a partially-rebuilt network. + """ + childState = state.get('children', []) + + ## list of children may be stored either as list or dict. + if isinstance(childState, dict): + childState = childState.values() + + + if blockSignals: + self.blockTreeChangeSignal() + + try: + self.setOpts(**state) + + if not recursive: + return + + ptr = 0 ## pointer to first child that has not been restored yet + foundChilds = set() + #print "==============", self.name() + + for ch in childState: + name = ch['name'] + typ = ch['type'] + #print('child: %s, %s' % (self.name()+'.'+name, typ)) + + ## First, see if there is already a child with this name and type + gotChild = False + for i, ch2 in enumerate(self.childs[ptr:]): + #print " ", ch2.name(), ch2.type() + if ch2.name() != name or not ch2.isType(typ): + continue + gotChild = True + #print " found it" + if i != 0: ## move parameter to next position + #self.removeChild(ch2) + self.insertChild(ptr, ch2) + #print " moved to position", ptr + ch2.restoreState(ch, recursive=recursive, addChildren=addChildren, removeChildren=removeChildren) + foundChilds.add(ch2) + + break + + if not gotChild: + if not addChildren: + #print " ignored child" + continue + #print " created new" + ch2 = Parameter.create(**ch) + self.insertChild(ptr, ch2) + foundChilds.add(ch2) + + ptr += 1 + + if removeChildren: + for ch in self.childs[:]: + if ch not in foundChilds: + #print " remove:", ch + self.removeChild(ch) + finally: + if blockSignals: + self.unblockTreeChangeSignal() + + + + def defaultValue(self): + """Return the default value for this parameter.""" + return self.opts['default'] + + def setDefault(self, val): + """Set the default value for this parameter.""" + if self.opts['default'] == val: + return + self.opts['default'] = val + self.sigDefaultChanged.emit(self, val) + + def setToDefault(self): + """Set this parameter's value to the default.""" + if self.hasDefault(): + self.setValue(self.defaultValue()) + + def hasDefault(self): + """Returns True if this parameter has a default value.""" + return 'default' in self.opts + + def valueIsDefault(self): + """Returns True if this parameter's value is equal to the default value.""" + return self.value() == self.defaultValue() + + def setLimits(self, limits): + """Set limits on the acceptable values for this parameter. + The format of limits depends on the type of the parameter and + some parameters do not make use of limits at all.""" + if 'limits' in self.opts and self.opts['limits'] == limits: + return + self.opts['limits'] = limits + self.sigLimitsChanged.emit(self, limits) + return limits + + def writable(self): + """ + Returns True if this parameter's value can be changed by the user. + Note that the value of the parameter can *always* be changed by + calling setValue(). + """ + return not self.opts.get('readonly', False) + + def setWritable(self, writable=True): + """Set whether this Parameter should be editable by the user. (This is + exactly the opposite of setReadonly).""" + self.setOpts(readonly=not writable) + + def setReadonly(self, readonly=True): + """Set whether this Parameter's value may be edited by the user.""" + self.setOpts(readonly=readonly) + + def setOpts(self, **opts): + """ + Set any arbitrary options on this parameter. + The exact behavior of this function will depend on the parameter type, but + most parameters will accept a common set of options: value, name, limits, + default, readonly, removable, renamable, visible, enabled, and expanded. + + See :func:`Parameter.__init__ ` + for more information on default options. + """ + changed = OrderedDict() + for k in opts: + if k == 'value': + self.setValue(opts[k]) + elif k == 'name': + self.setName(opts[k]) + elif k == 'limits': + self.setLimits(opts[k]) + elif k == 'default': + self.setDefault(opts[k]) + elif k not in self.opts or self.opts[k] != opts[k]: + self.opts[k] = opts[k] + changed[k] = opts[k] + + if len(changed) > 0: + self.sigOptionsChanged.emit(self, changed) + + def emitStateChanged(self, changeDesc, data): + ## Emits stateChanged signal and + ## requests emission of new treeStateChanged signal + self.sigStateChanged.emit(self, changeDesc, data) + #self.treeStateChanged(self, changeDesc, data) + self.treeStateChanges.append((self, changeDesc, data)) + self.emitTreeChanges() + + def makeTreeItem(self, depth): + """ + Return a TreeWidgetItem suitable for displaying/controlling the content of + this parameter. This is called automatically when a ParameterTree attempts + to display this Parameter. + Most subclasses will want to override this function. + """ + if hasattr(self, 'itemClass'): + #print "Param:", self, "Make item from itemClass:", self.itemClass + return self.itemClass(self, depth) + else: + return ParameterItem(self, depth=depth) + + + def addChild(self, child): + """Add another parameter to the end of this parameter's child list.""" + return self.insertChild(len(self.childs), child) + + def addChildren(self, children): + ## If children was specified as dict, then assume keys are the names. + if isinstance(children, dict): + ch2 = [] + for name, opts in children.items(): + if isinstance(opts, dict) and 'name' not in opts: + opts = opts.copy() + opts['name'] = name + ch2.append(opts) + children = ch2 + + for chOpts in children: + #print self, "Add child:", type(chOpts), id(chOpts) + self.addChild(chOpts) + + + def insertChild(self, pos, child): + """ + Insert a new child at pos. + If pos is a Parameter, then insert at the position of that Parameter. + If child is a dict, then a parameter is constructed using + :func:`Parameter.create `. + """ + if isinstance(child, dict): + child = Parameter.create(**child) + + name = child.name() + if name in self.names and child is not self.names[name]: + if child.opts.get('autoIncrementName', False): + name = self.incrementName(name) + child.setName(name) + else: + raise Exception("Already have child named %s" % str(name)) + if isinstance(pos, Parameter): + pos = self.childs.index(pos) + + with self.treeChangeBlocker(): + if child.parent() is not None: + child.remove() + + self.names[name] = child + self.childs.insert(pos, child) + + child.parentChanged(self) + self.sigChildAdded.emit(self, child, pos) + child.sigTreeStateChanged.connect(self.treeStateChanged) + return child + + def removeChild(self, child): + """Remove a child parameter.""" + name = child.name() + if name not in self.names or self.names[name] is not child: + raise Exception("Parameter %s is not my child; can't remove." % str(child)) + del self.names[name] + self.childs.pop(self.childs.index(child)) + child.parentChanged(None) + self.sigChildRemoved.emit(self, child) + try: + child.sigTreeStateChanged.disconnect(self.treeStateChanged) + except TypeError: ## already disconnected + pass + + def clearChildren(self): + """Remove all child parameters.""" + for ch in self.childs[:]: + self.removeChild(ch) + + def children(self): + """Return a list of this parameter's children. + Warning: this overrides QObject.children + """ + return self.childs[:] + + def hasChildren(self): + """Return True if this Parameter has children.""" + return len(self.childs) > 0 + + def parentChanged(self, parent): + """This method is called when the parameter's parent has changed. + It may be useful to extend this method in subclasses.""" + self._parent = parent + self.sigParentChanged.emit(self, parent) + + def parent(self): + """Return the parent of this parameter.""" + return self._parent + + def remove(self): + """Remove this parameter from its parent's child list""" + parent = self.parent() + if parent is None: + raise Exception("Cannot remove; no parent.") + parent.removeChild(self) + + def incrementName(self, name): + ## return an unused name by adding a number to the name given + base, num = re.match('(.*)(\d*)', name).groups() + numLen = len(num) + if numLen == 0: + num = 2 + numLen = 1 + else: + num = int(num) + while True: + newName = base + ("%%0%dd"%numLen) % num + if newName not in self.names: + return newName + num += 1 + + def __iter__(self): + for ch in self.childs: + yield ch + + def __getitem__(self, names): + """Get the value of a child parameter. The name may also be a tuple giving + the path to a sub-parameter:: + + value = param[('child', 'grandchild')] + """ + if not isinstance(names, tuple): + names = (names,) + return self.param(*names).value() + + def __setitem__(self, names, value): + """Set the value of a child parameter. The name may also be a tuple giving + the path to a sub-parameter:: + + param[('child', 'grandchild')] = value + """ + if isinstance(names, basestring): + names = (names,) + return self.param(*names).setValue(value) + + def param(self, *names): + """Return a child parameter. + Accepts the name of the child or a tuple (path, to, child)""" + try: + param = self.names[names[0]] + except KeyError: + raise Exception("Parameter %s has no child named %s" % (self.name(), names[0])) + + if len(names) > 1: + return param.param(*names[1:]) + else: + return param + + def __repr__(self): + return "<%s '%s' at 0x%x>" % (self.__class__.__name__, self.name(), id(self)) + + def __getattr__(self, attr): + ## Leaving this undocumented because I might like to remove it in the future.. + #print type(self), attr + + if 'names' not in self.__dict__: + raise AttributeError(attr) + if attr in self.names: + import traceback + traceback.print_stack() + print("Warning: Use of Parameter.subParam is deprecated. Use Parameter.param(name) instead.") + return self.param(attr) + else: + raise AttributeError(attr) + + def _renameChild(self, child, name): + ## Only to be called from Parameter.rename + if name in self.names: + return child.name() + self.names[name] = child + del self.names[child.name()] + return name + + def registerItem(self, item): + self.items[item] = None + + def hide(self): + """Hide this parameter. It and its children will no longer be visible in any ParameterTree + widgets it is connected to.""" + self.show(False) + + def show(self, s=True): + """Show this parameter. """ + self.opts['visible'] = s + self.sigOptionsChanged.emit(self, {'visible': s}) + + + def treeChangeBlocker(self): + """ + Return an object that can be used to temporarily block and accumulate + sigTreeStateChanged signals. This is meant to be used when numerous changes are + about to be made to the tree and only one change signal should be + emitted at the end. + + Example:: + + with param.treeChangeBlocker(): + param.addChild(...) + param.removeChild(...) + param.setValue(...) + """ + return SignalBlocker(self.blockTreeChangeSignal, self.unblockTreeChangeSignal) + + def blockTreeChangeSignal(self): + """ + Used to temporarily block and accumulate tree change signals. + *You must remember to unblock*, so it is advisable to use treeChangeBlocker() instead. + """ + self.blockTreeChangeEmit += 1 + + def unblockTreeChangeSignal(self): + """Unblocks enission of sigTreeStateChanged and flushes the changes out through a single signal.""" + self.blockTreeChangeEmit -= 1 + self.emitTreeChanges() + + + def treeStateChanged(self, param, changes): + """ + Called when the state of any sub-parameter has changed. + + ========== ================================================================ + Arguments: + param The immediate child whose tree state has changed. + note that the change may have originated from a grandchild. + changes List of tuples describing all changes that have been made + in this event: (param, changeDescr, data) + ========== ================================================================ + + This function can be extended to react to tree state changes. + """ + self.treeStateChanges.extend(changes) + self.emitTreeChanges() + + def emitTreeChanges(self): + if self.blockTreeChangeEmit == 0: + changes = self.treeStateChanges + self.treeStateChanges = [] + self.sigTreeStateChanged.emit(self, changes) + + +class SignalBlocker(object): + def __init__(self, enterFn, exitFn): + self.enterFn = enterFn + self.exitFn = exitFn + + def __enter__(self): + self.enterFn() + + def __exit__(self, exc_type, exc_value, tb): + self.exitFn() + + + diff --git a/pyqtgraph/parametertree/ParameterItem.py b/pyqtgraph/parametertree/ParameterItem.py new file mode 100644 index 00000000..46499fd3 --- /dev/null +++ b/pyqtgraph/parametertree/ParameterItem.py @@ -0,0 +1,165 @@ +from pyqtgraph.Qt import QtGui, QtCore +import os, weakref, re + +class ParameterItem(QtGui.QTreeWidgetItem): + """ + Abstract ParameterTree item. + Used to represent the state of a Parameter from within a ParameterTree. + + - Sets first column of item to name + - generates context menu if item is renamable or removable + - handles child added / removed events + - provides virtual functions for handling changes from parameter + + For more ParameterItem types, see ParameterTree.parameterTypes module. + """ + + def __init__(self, param, depth=0): + QtGui.QTreeWidgetItem.__init__(self, [param.name(), '']) + + self.param = param + self.param.registerItem(self) ## let parameter know this item is connected to it (for debugging) + self.depth = depth + + param.sigValueChanged.connect(self.valueChanged) + param.sigChildAdded.connect(self.childAdded) + param.sigChildRemoved.connect(self.childRemoved) + param.sigNameChanged.connect(self.nameChanged) + param.sigLimitsChanged.connect(self.limitsChanged) + param.sigDefaultChanged.connect(self.defaultChanged) + param.sigOptionsChanged.connect(self.optsChanged) + param.sigParentChanged.connect(self.parentChanged) + + + opts = param.opts + + ## Generate context menu for renaming/removing parameter + self.contextMenu = QtGui.QMenu() + self.contextMenu.addSeparator() + flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled + if opts.get('renamable', False): + flags |= QtCore.Qt.ItemIsEditable + self.contextMenu.addAction('Rename').triggered.connect(self.editName) + if opts.get('removable', False): + self.contextMenu.addAction("Remove").triggered.connect(self.requestRemove) + + ## handle movable / dropEnabled options + if opts.get('movable', False): + flags |= QtCore.Qt.ItemIsDragEnabled + if opts.get('dropEnabled', False): + flags |= QtCore.Qt.ItemIsDropEnabled + self.setFlags(flags) + + ## flag used internally during name editing + self.ignoreNameColumnChange = False + + + def valueChanged(self, param, val): + ## called when the parameter's value has changed + pass + + def isFocusable(self): + """Return True if this item should be included in the tab-focus order""" + return False + + def setFocus(self): + """Give input focus to this item. + Can be reimplemented to display editor widgets, etc. + """ + pass + + def focusNext(self, forward=True): + """Give focus to the next (or previous) focusable item in the parameter tree""" + self.treeWidget().focusNext(self, forward=forward) + + + def treeWidgetChanged(self): + """Called when this item is added or removed from a tree. + Expansion, visibility, and column widgets must all be configured AFTER + the item is added to a tree, not during __init__. + """ + self.setHidden(not self.param.opts.get('visible', True)) + self.setExpanded(self.param.opts.get('expanded', True)) + + def childAdded(self, param, child, pos): + item = child.makeTreeItem(depth=self.depth+1) + self.insertChild(pos, item) + item.treeWidgetChanged() + + for i, ch in enumerate(child): + item.childAdded(child, ch, i) + + def childRemoved(self, param, child): + for i in range(self.childCount()): + item = self.child(i) + if item.param is child: + self.takeChild(i) + break + + def parentChanged(self, param, parent): + ## called when the parameter's parent has changed. + pass + + def contextMenuEvent(self, ev): + if not self.param.opts.get('removable', False) and not self.param.opts.get('renamable', False): + return + + self.contextMenu.popup(ev.globalPos()) + + def columnChangedEvent(self, col): + """Called when the text in a column has been edited. + By default, we only use changes to column 0 to rename the parameter. + """ + if col == 0: + if self.ignoreNameColumnChange: + return + try: + newName = self.param.setName(str(self.text(col))) + except: + self.setText(0, self.param.name()) + raise + + try: + self.ignoreNameColumnChange = True + self.nameChanged(self, newName) ## If the parameter rejects the name change, we need to set it back. + finally: + self.ignoreNameColumnChange = False + + def nameChanged(self, param, name): + ## called when the parameter's name has changed. + self.setText(0, name) + + def limitsChanged(self, param, limits): + """Called when the parameter's limits have changed""" + pass + + def defaultChanged(self, param, default): + """Called when the parameter's default value has changed""" + pass + + def optsChanged(self, param, opts): + """Called when any options are changed that are not + name, value, default, or limits""" + #print opts + if 'visible' in opts: + self.setHidden(not opts['visible']) + + def editName(self): + self.treeWidget().editItem(self, 0) + + def selected(self, sel): + """Called when this item has been selected (sel=True) OR deselected (sel=False)""" + pass + + def requestRemove(self): + ## called when remove is selected from the context menu. + ## we need to delay removal until the action is complete + ## since destroying the menu in mid-action will cause a crash. + QtCore.QTimer.singleShot(0, self.param.remove) + + ## for python 3 support, we need to redefine hash and eq methods. + def __hash__(self): + return id(self) + + def __eq__(self, x): + return x is self diff --git a/pyqtgraph/parametertree/ParameterTree.py b/pyqtgraph/parametertree/ParameterTree.py new file mode 100644 index 00000000..866875e5 --- /dev/null +++ b/pyqtgraph/parametertree/ParameterTree.py @@ -0,0 +1,119 @@ +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.widgets.TreeWidget import TreeWidget +import os, weakref, re +from .ParameterItem import ParameterItem +#import functions as fn + + + +class ParameterTree(TreeWidget): + """Widget used to display or control data from a ParameterSet""" + + def __init__(self, parent=None, showHeader=True): + TreeWidget.__init__(self, parent) + self.setVerticalScrollMode(self.ScrollPerPixel) + self.setHorizontalScrollMode(self.ScrollPerPixel) + self.setAnimated(False) + self.setColumnCount(2) + self.setHeaderLabels(["Parameter", "Value"]) + self.setAlternatingRowColors(True) + self.paramSet = None + self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents) + self.setHeaderHidden(not showHeader) + self.itemChanged.connect(self.itemChangedEvent) + self.lastSel = None + self.setRootIsDecorated(False) + + def setParameters(self, param, showTop=True): + self.clear() + self.addParameters(param, showTop=showTop) + + def addParameters(self, param, root=None, depth=0, showTop=True): + item = param.makeTreeItem(depth=depth) + if root is None: + root = self.invisibleRootItem() + ## Hide top-level item + if not showTop: + item.setText(0, '') + item.setSizeHint(0, QtCore.QSize(1,1)) + item.setSizeHint(1, QtCore.QSize(1,1)) + depth -= 1 + root.addChild(item) + item.treeWidgetChanged() + + for ch in param: + self.addParameters(ch, root=item, depth=depth+1) + + def clear(self): + self.invisibleRootItem().takeChildren() + + + def focusNext(self, item, forward=True): + ## Give input focus to the next (or previous) item after 'item' + while True: + parent = item.parent() + if parent is None: + return + nextItem = self.nextFocusableChild(parent, item, forward=forward) + if nextItem is not None: + nextItem.setFocus() + self.setCurrentItem(nextItem) + return + item = parent + + def focusPrevious(self, item): + self.focusNext(item, forward=False) + + def nextFocusableChild(self, root, startItem=None, forward=True): + if startItem is None: + if forward: + index = 0 + else: + index = root.childCount()-1 + else: + if forward: + index = root.indexOfChild(startItem) + 1 + else: + index = root.indexOfChild(startItem) - 1 + + if forward: + inds = list(range(index, root.childCount())) + else: + inds = list(range(index, -1, -1)) + + for i in inds: + item = root.child(i) + if hasattr(item, 'isFocusable') and item.isFocusable(): + return item + else: + item = self.nextFocusableChild(item, forward=forward) + if item is not None: + return item + return None + + def contextMenuEvent(self, ev): + item = self.currentItem() + if hasattr(item, 'contextMenuEvent'): + item.contextMenuEvent(ev) + + def itemChangedEvent(self, item, col): + if hasattr(item, 'columnChangedEvent'): + item.columnChangedEvent(col) + + def selectionChanged(self, *args): + sel = self.selectedItems() + if len(sel) != 1: + sel = None + if self.lastSel is not None and isinstance(self.lastSel, ParameterItem): + self.lastSel.selected(False) + if sel is None: + self.lastSel = None + return + self.lastSel = sel[0] + if hasattr(sel[0], 'selected'): + sel[0].selected(True) + return TreeWidget.selectionChanged(self, *args) + + def wheelEvent(self, ev): + self.clearSelection() + return TreeWidget.wheelEvent(self, ev) diff --git a/pyqtgraph/parametertree/__init__.py b/pyqtgraph/parametertree/__init__.py new file mode 100644 index 00000000..acdb7a37 --- /dev/null +++ b/pyqtgraph/parametertree/__init__.py @@ -0,0 +1,5 @@ +from .Parameter import Parameter, registerParameterType +from .ParameterTree import ParameterTree +from .ParameterItem import ParameterItem + +from . import parameterTypes as types \ No newline at end of file diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py new file mode 100644 index 00000000..3300171f --- /dev/null +++ b/pyqtgraph/parametertree/parameterTypes.py @@ -0,0 +1,648 @@ +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.python2_3 import asUnicode +from .Parameter import Parameter, registerParameterType +from .ParameterItem import ParameterItem +from pyqtgraph.widgets.SpinBox import SpinBox +from pyqtgraph.widgets.ColorButton import ColorButton +#from pyqtgraph.widgets.GradientWidget import GradientWidget ## creates import loop +import pyqtgraph as pg +import pyqtgraph.pixmaps as pixmaps +import os +from pyqtgraph.pgcollections import OrderedDict + +class WidgetParameterItem(ParameterItem): + """ + ParameterTree item with: + + * label in second column for displaying value + * simple widget for editing value (displayed instead of label when item is selected) + * button that resets value to default + + ================= ============================================================= + Registered Types: + int Displays a :class:`SpinBox ` in integer + mode. + float Displays a :class:`SpinBox `. + bool Displays a QCheckBox + str Displays a QLineEdit + color Displays a :class:`ColorButton ` + colormap Displays a :class:`GradientWidget ` + ================= ============================================================= + + This class can be subclassed by overriding makeWidget() to provide a custom widget. + """ + def __init__(self, param, depth): + ParameterItem.__init__(self, param, depth) + + self.hideWidget = True ## hide edit widget, replace with label when not selected + ## set this to False to keep the editor widget always visible + + + ## build widget into column 1 with a display label and default button. + w = self.makeWidget() + self.widget = w + self.eventProxy = EventProxy(w, self.widgetEventFilter) + + opts = self.param.opts + if 'tip' in opts: + w.setToolTip(opts['tip']) + + self.defaultBtn = QtGui.QPushButton() + self.defaultBtn.setFixedWidth(20) + self.defaultBtn.setFixedHeight(20) + modDir = os.path.dirname(__file__) + self.defaultBtn.setIcon(QtGui.QIcon(pixmaps.getPixmap('default'))) + self.defaultBtn.clicked.connect(self.defaultClicked) + + self.displayLabel = QtGui.QLabel() + + layout = QtGui.QHBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(2) + layout.addWidget(w) + layout.addWidget(self.displayLabel) + layout.addWidget(self.defaultBtn) + self.layoutWidget = QtGui.QWidget() + self.layoutWidget.setLayout(layout) + + if w.sigChanged is not None: + w.sigChanged.connect(self.widgetValueChanged) + + if hasattr(w, 'sigChanging'): + w.sigChanging.connect(self.widgetValueChanging) + + ## update value shown in widget. + if opts.get('value', None) is not None: + self.valueChanged(self, opts['value'], force=True) + else: + ## no starting value was given; use whatever the widget has + self.widgetValueChanged() + + + def makeWidget(self): + """ + Return a single widget that should be placed in the second tree column. + The widget must be given three attributes: + + ========== ============================================================ + sigChanged a signal that is emitted when the widget's value is changed + value a function that returns the value + setValue a function that sets the value + ========== ============================================================ + + This is a good function to override in subclasses. + """ + opts = self.param.opts + t = opts['type'] + if t == 'int': + defs = { + 'value': 0, 'min': None, 'max': None, 'int': True, + 'step': 1.0, 'minStep': 1.0, 'dec': False, + 'siPrefix': False, 'suffix': '' + } + defs.update(opts) + if 'limits' in opts: + defs['bounds'] = opts['limits'] + w = SpinBox() + w.setOpts(**defs) + w.sigChanged = w.sigValueChanged + w.sigChanging = w.sigValueChanging + elif t == 'float': + defs = { + 'value': 0, 'min': None, 'max': None, + 'step': 1.0, 'dec': False, + 'siPrefix': False, 'suffix': '' + } + defs.update(opts) + if 'limits' in opts: + defs['bounds'] = opts['limits'] + w = SpinBox() + w.setOpts(**defs) + w.sigChanged = w.sigValueChanged + w.sigChanging = w.sigValueChanging + elif t == 'bool': + w = QtGui.QCheckBox() + w.sigChanged = w.toggled + w.value = w.isChecked + w.setValue = w.setChecked + self.hideWidget = False + elif t == 'str': + w = QtGui.QLineEdit() + w.sigChanged = w.editingFinished + w.value = lambda: asUnicode(w.text()) + w.setValue = lambda v: w.setText(asUnicode(v)) + w.sigChanging = w.textChanged + elif t == 'color': + w = ColorButton() + w.sigChanged = w.sigColorChanged + w.sigChanging = w.sigColorChanging + w.value = w.color + w.setValue = w.setColor + self.hideWidget = False + w.setFlat(True) + elif t == 'colormap': + from pyqtgraph.widgets.GradientWidget import GradientWidget ## need this here to avoid import loop + w = GradientWidget(orientation='bottom') + w.sigChanged = w.sigGradientChangeFinished + w.sigChanging = w.sigGradientChanged + w.value = w.colorMap + w.setValue = w.setColorMap + self.hideWidget = False + else: + raise Exception("Unknown type '%s'" % asUnicode(t)) + return w + + def widgetEventFilter(self, obj, ev): + ## filter widget's events + ## catch TAB to change focus + ## catch focusOut to hide editor + if ev.type() == ev.KeyPress: + if ev.key() == QtCore.Qt.Key_Tab: + self.focusNext(forward=True) + return True ## don't let anyone else see this event + elif ev.key() == QtCore.Qt.Key_Backtab: + self.focusNext(forward=False) + return True ## don't let anyone else see this event + + #elif ev.type() == ev.FocusOut: + #self.hideEditor() + return False + + def setFocus(self): + self.showEditor() + + def isFocusable(self): + return self.param.writable() + + def valueChanged(self, param, val, force=False): + ## called when the parameter's value has changed + ParameterItem.valueChanged(self, param, val) + self.widget.sigChanged.disconnect(self.widgetValueChanged) + try: + if force or val != self.widget.value(): + self.widget.setValue(val) + self.updateDisplayLabel(val) ## always make sure label is updated, even if values match! + finally: + self.widget.sigChanged.connect(self.widgetValueChanged) + self.updateDefaultBtn() + + def updateDefaultBtn(self): + ## enable/disable default btn + self.defaultBtn.setEnabled(not self.param.valueIsDefault() and self.param.writable()) + + def updateDisplayLabel(self, value=None): + """Update the display label to reflect the value of the parameter.""" + if value is None: + value = self.param.value() + opts = self.param.opts + if isinstance(self.widget, QtGui.QAbstractSpinBox): + text = asUnicode(self.widget.lineEdit().text()) + elif isinstance(self.widget, QtGui.QComboBox): + text = self.widget.currentText() + else: + text = asUnicode(value) + self.displayLabel.setText(text) + + def widgetValueChanged(self): + ## called when the widget's value has been changed by the user + val = self.widget.value() + newVal = self.param.setValue(val) + + def widgetValueChanging(self): + """ + Called when the widget's value is changing, but not finalized. + For example: editing text before pressing enter or changing focus. + """ + pass + + def selected(self, sel): + """Called when this item has been selected (sel=True) OR deselected (sel=False)""" + ParameterItem.selected(self, sel) + + if self.widget is None: + return + if sel and self.param.writable(): + self.showEditor() + elif self.hideWidget: + self.hideEditor() + + def showEditor(self): + self.widget.show() + self.displayLabel.hide() + self.widget.setFocus(QtCore.Qt.OtherFocusReason) + + def hideEditor(self): + self.widget.hide() + self.displayLabel.show() + + def limitsChanged(self, param, limits): + """Called when the parameter's limits have changed""" + ParameterItem.limitsChanged(self, param, limits) + + t = self.param.opts['type'] + if t == 'int' or t == 'float': + self.widget.setOpts(bounds=limits) + else: + return ## don't know what to do with any other types.. + + def defaultChanged(self, param, value): + self.updateDefaultBtn() + + def treeWidgetChanged(self): + """Called when this item is added or removed from a tree.""" + ParameterItem.treeWidgetChanged(self) + + ## add all widgets for this item into the tree + if self.widget is not None: + tree = self.treeWidget() + if tree is None: + return + tree.setItemWidget(self, 1, self.layoutWidget) + self.displayLabel.hide() + self.selected(False) + + def defaultClicked(self): + self.param.setToDefault() + + def optsChanged(self, param, opts): + """Called when any options are changed that are not + name, value, default, or limits""" + #print "opts changed:", opts + ParameterItem.optsChanged(self, param, opts) + + if 'readonly' in opts: + self.updateDefaultBtn() + + ## If widget is a SpinBox, pass options straight through + if isinstance(self.widget, SpinBox): + if 'units' in opts and 'suffix' not in opts: + opts['suffix'] = opts['units'] + self.widget.setOpts(**opts) + self.updateDisplayLabel() + +class EventProxy(QtCore.QObject): + def __init__(self, qobj, callback): + QtCore.QObject.__init__(self) + self.callback = callback + qobj.installEventFilter(self) + + def eventFilter(self, obj, ev): + return self.callback(obj, ev) + + + + +class SimpleParameter(Parameter): + itemClass = WidgetParameterItem + + def __init__(self, *args, **kargs): + Parameter.__init__(self, *args, **kargs) + + ## override a few methods for color parameters + if self.opts['type'] == 'color': + self.value = self.colorValue + self.saveState = self.saveColorState + + def colorValue(self): + return pg.mkColor(Parameter.value(self)) + + def saveColorState(self): + state = Parameter.saveState(self) + state['value'] = pg.colorTuple(self.value()) + return state + + +registerParameterType('int', SimpleParameter, override=True) +registerParameterType('float', SimpleParameter, override=True) +registerParameterType('bool', SimpleParameter, override=True) +registerParameterType('str', SimpleParameter, override=True) +registerParameterType('color', SimpleParameter, override=True) +registerParameterType('colormap', SimpleParameter, override=True) + + + + +class GroupParameterItem(ParameterItem): + """ + Group parameters are used mainly as a generic parent item that holds (and groups!) a set + of child parameters. It also provides a simple mechanism for displaying a button or combo + that can be used to add new parameters to the group. + """ + def __init__(self, param, depth): + ParameterItem.__init__(self, param, depth) + self.updateDepth(depth) + + self.addItem = None + if 'addText' in param.opts: + addText = param.opts['addText'] + if 'addList' in param.opts: + self.addWidget = QtGui.QComboBox() + self.addWidget.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) + self.updateAddList() + self.addWidget.currentIndexChanged.connect(self.addChanged) + else: + self.addWidget = QtGui.QPushButton(addText) + self.addWidget.clicked.connect(self.addClicked) + w = QtGui.QWidget() + l = QtGui.QHBoxLayout() + l.setContentsMargins(0,0,0,0) + w.setLayout(l) + l.addWidget(self.addWidget) + l.addStretch() + #l.addItem(QtGui.QSpacerItem(200, 10, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)) + self.addWidgetBox = w + self.addItem = QtGui.QTreeWidgetItem([]) + self.addItem.setFlags(QtCore.Qt.ItemIsEnabled) + ParameterItem.addChild(self, self.addItem) + + def updateDepth(self, depth): + ## Change item's appearance based on its depth in the tree + ## This allows highest-level groups to be displayed more prominently. + if depth == 0: + for c in [0,1]: + self.setBackground(c, QtGui.QBrush(QtGui.QColor(100,100,100))) + self.setForeground(c, QtGui.QBrush(QtGui.QColor(220,220,255))) + font = self.font(c) + font.setBold(True) + font.setPointSize(font.pointSize()+1) + self.setFont(c, font) + self.setSizeHint(0, QtCore.QSize(0, 25)) + else: + for c in [0,1]: + self.setBackground(c, QtGui.QBrush(QtGui.QColor(220,220,220))) + font = self.font(c) + font.setBold(True) + #font.setPointSize(font.pointSize()+1) + self.setFont(c, font) + self.setSizeHint(0, QtCore.QSize(0, 20)) + + def addClicked(self): + """Called when "add new" button is clicked + The parameter MUST have an 'addNew' method defined. + """ + self.param.addNew() + + def addChanged(self): + """Called when "add new" combo is changed + The parameter MUST have an 'addNew' method defined. + """ + if self.addWidget.currentIndex() == 0: + return + typ = asUnicode(self.addWidget.currentText()) + self.param.addNew(typ) + self.addWidget.setCurrentIndex(0) + + def treeWidgetChanged(self): + ParameterItem.treeWidgetChanged(self) + self.treeWidget().setFirstItemColumnSpanned(self, True) + if self.addItem is not None: + self.treeWidget().setItemWidget(self.addItem, 0, self.addWidgetBox) + self.treeWidget().setFirstItemColumnSpanned(self.addItem, True) + + def addChild(self, child): ## make sure added childs are actually inserted before add btn + if self.addItem is not None: + ParameterItem.insertChild(self, self.childCount()-1, child) + else: + ParameterItem.addChild(self, child) + + def optsChanged(self, param, changed): + if 'addList' in changed: + self.updateAddList() + + def updateAddList(self): + self.addWidget.blockSignals(True) + try: + self.addWidget.clear() + self.addWidget.addItem(self.param.opts['addText']) + for t in self.param.opts['addList']: + self.addWidget.addItem(t) + finally: + self.addWidget.blockSignals(False) + +class GroupParameter(Parameter): + """ + Group parameters are used mainly as a generic parent item that holds (and groups!) a set + of child parameters. + + It also provides a simple mechanism for displaying a button or combo + that can be used to add new parameters to the group. To enable this, the group + must be initialized with the 'addText' option (the text will be displayed on + a button which, when clicked, will cause addNew() to be called). If the 'addList' + option is specified as well, then a dropdown-list of addable items will be displayed + instead of a button. + """ + itemClass = GroupParameterItem + + def addNew(self, typ=None): + """ + This method is called when the user has requested to add a new item to the group. + """ + raise Exception("Must override this function in subclass.") + + def setAddList(self, vals): + """Change the list of options available for the user to add to the group.""" + self.setOpts(addList=vals) + + + +registerParameterType('group', GroupParameter, override=True) + + + + + +class ListParameterItem(WidgetParameterItem): + """ + WidgetParameterItem subclass providing comboBox that lets the user select from a list of options. + + """ + def __init__(self, param, depth): + self.targetValue = None + WidgetParameterItem.__init__(self, param, depth) + + + def makeWidget(self): + opts = self.param.opts + t = opts['type'] + w = QtGui.QComboBox() + w.setMaximumHeight(20) ## set to match height of spin box and line edit + w.sigChanged = w.currentIndexChanged + w.value = self.value + w.setValue = self.setValue + self.widget = w ## needs to be set before limits are changed + self.limitsChanged(self.param, self.param.opts['limits']) + if len(self.forward) > 0: + self.setValue(self.param.value()) + return w + + def value(self): + key = asUnicode(self.widget.currentText()) + + return self.forward.get(key, None) + + def setValue(self, val): + self.targetValue = val + if val not in self.reverse[0]: + self.widget.setCurrentIndex(0) + else: + key = self.reverse[1][self.reverse[0].index(val)] + ind = self.widget.findText(key) + self.widget.setCurrentIndex(ind) + + def limitsChanged(self, param, limits): + # set up forward / reverse mappings for name:value + + if len(limits) == 0: + limits = [''] ## Can never have an empty list--there is always at least a singhe blank item. + + self.forward, self.reverse = ListParameter.mapping(limits) + try: + self.widget.blockSignals(True) + val = self.targetValue #asUnicode(self.widget.currentText()) + + self.widget.clear() + for k in self.forward: + self.widget.addItem(k) + if k == val: + self.widget.setCurrentIndex(self.widget.count()-1) + self.updateDisplayLabel() + finally: + self.widget.blockSignals(False) + + + +class ListParameter(Parameter): + itemClass = ListParameterItem + + def __init__(self, **opts): + self.forward = OrderedDict() ## {name: value, ...} + self.reverse = ([], []) ## ([value, ...], [name, ...]) + + ## Parameter uses 'limits' option to define the set of allowed values + if 'values' in opts: + opts['limits'] = opts['values'] + if opts.get('limits', None) is None: + opts['limits'] = [] + Parameter.__init__(self, **opts) + self.setLimits(opts['limits']) + + def setLimits(self, limits): + self.forward, self.reverse = self.mapping(limits) + + Parameter.setLimits(self, limits) + #print self.name(), self.value(), limits + if len(self.reverse) > 0 and self.value() not in self.reverse[0]: + self.setValue(self.reverse[0][0]) + + #def addItem(self, name, value=None): + #if name in self.forward: + #raise Exception("Name '%s' is already in use for this parameter" % name) + #limits = self.opts['limits'] + #if isinstance(limits, dict): + #limits = limits.copy() + #limits[name] = value + #self.setLimits(limits) + #else: + #if value is not None: + #raise Exception ## raise exception or convert to dict? + #limits = limits[:] + #limits.append(name) + ## what if limits == None? + + @staticmethod + def mapping(limits): + ## Return forward and reverse mapping objects given a limit specification + forward = OrderedDict() ## {name: value, ...} + reverse = ([], []) ## ([value, ...], [name, ...]) + if isinstance(limits, dict): + for k, v in limits.items(): + forward[k] = v + reverse[0].append(v) + reverse[1].append(k) + else: + for v in limits: + n = asUnicode(v) + forward[n] = v + reverse[0].append(v) + reverse[1].append(n) + return forward, reverse + +registerParameterType('list', ListParameter, override=True) + + + +class ActionParameterItem(ParameterItem): + def __init__(self, param, depth): + ParameterItem.__init__(self, param, depth) + self.layoutWidget = QtGui.QWidget() + self.layout = QtGui.QHBoxLayout() + self.layoutWidget.setLayout(self.layout) + self.button = QtGui.QPushButton(param.name()) + #self.layout.addSpacing(100) + self.layout.addWidget(self.button) + self.layout.addStretch() + self.button.clicked.connect(self.buttonClicked) + param.sigNameChanged.connect(self.paramRenamed) + self.setText(0, '') + + def treeWidgetChanged(self): + ParameterItem.treeWidgetChanged(self) + tree = self.treeWidget() + if tree is None: + return + + tree.setFirstItemColumnSpanned(self, True) + tree.setItemWidget(self, 0, self.layoutWidget) + + def paramRenamed(self, param, name): + self.button.setText(name) + + def buttonClicked(self): + self.param.activate() + +class ActionParameter(Parameter): + """Used for displaying a button within the tree.""" + itemClass = ActionParameterItem + sigActivated = QtCore.Signal(object) + + def activate(self): + self.sigActivated.emit(self) + self.emitStateChanged('activated', None) + +registerParameterType('action', ActionParameter, override=True) + + + +class TextParameterItem(WidgetParameterItem): + def __init__(self, param, depth): + WidgetParameterItem.__init__(self, param, depth) + self.hideWidget = False + self.subItem = QtGui.QTreeWidgetItem() + self.addChild(self.subItem) + + def treeWidgetChanged(self): + ## TODO: fix so that superclass method can be called + ## (WidgetParameter should just natively support this style) + #WidgetParameterItem.treeWidgetChanged(self) + self.treeWidget().setFirstItemColumnSpanned(self.subItem, True) + self.treeWidget().setItemWidget(self.subItem, 0, self.textBox) + + # for now, these are copied from ParameterItem.treeWidgetChanged + self.setHidden(not self.param.opts.get('visible', True)) + self.setExpanded(self.param.opts.get('expanded', True)) + + def makeWidget(self): + self.textBox = QtGui.QTextEdit() + self.textBox.setMaximumHeight(100) + self.textBox.value = lambda: str(self.textBox.toPlainText()) + self.textBox.setValue = self.textBox.setPlainText + self.textBox.sigChanged = self.textBox.textChanged + return self.textBox + +class TextParameter(Parameter): + """Editable string; displayed as large text box in the tree.""" + itemClass = TextParameterItem + + + +registerParameterType('text', TextParameter, override=True) diff --git a/pyqtgraph/pgcollections.py b/pyqtgraph/pgcollections.py new file mode 100644 index 00000000..76850622 --- /dev/null +++ b/pyqtgraph/pgcollections.py @@ -0,0 +1,477 @@ +# -*- coding: utf-8 -*- +""" +advancedTypes.py - Basic data structures not included with python +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +Includes: + - OrderedDict - Dictionary which preserves the order of its elements + - BiDict, ReverseDict - Bi-directional dictionaries + - ThreadsafeDict, ThreadsafeList - Self-mutexed data structures +""" + +import threading, sys, copy, collections +#from debug import * + +try: + from collections import OrderedDict +except ImportError: + # fallback: try to use the ordereddict backport when using python 2.6 + from ordereddict import OrderedDict + + +class ReverseDict(dict): + """extends dict so that reverse lookups are possible by requesting the key as a list of length 1: + d = BiDict({'x': 1, 'y': 2}) + d['x'] + 1 + d[[2]] + 'y' + """ + def __init__(self, data=None): + if data is None: + data = {} + self.reverse = {} + for k in data: + self.reverse[data[k]] = k + dict.__init__(self, data) + + def __getitem__(self, item): + if type(item) is list: + return self.reverse[item[0]] + else: + return dict.__getitem__(self, item) + + def __setitem__(self, item, value): + self.reverse[value] = item + dict.__setitem__(self, item, value) + + def __deepcopy__(self, memo): + raise Exception("deepcopy not implemented") + + +class BiDict(dict): + """extends dict so that reverse lookups are possible by adding each reverse combination to the dict. + This only works if all values and keys are unique.""" + def __init__(self, data=None): + if data is None: + data = {} + dict.__init__(self) + for k in data: + self[data[k]] = k + + def __setitem__(self, item, value): + dict.__setitem__(self, item, value) + dict.__setitem__(self, value, item) + + def __deepcopy__(self, memo): + raise Exception("deepcopy not implemented") + +class ThreadsafeDict(dict): + """Extends dict so that getitem, setitem, and contains are all thread-safe. + Also adds lock/unlock functions for extended exclusive operations + Converts all sub-dicts and lists to threadsafe as well. + """ + + def __init__(self, *args, **kwargs): + self.mutex = threading.RLock() + dict.__init__(self, *args, **kwargs) + for k in self: + if type(self[k]) is dict: + self[k] = ThreadsafeDict(self[k]) + + def __getitem__(self, attr): + self.lock() + try: + val = dict.__getitem__(self, attr) + finally: + self.unlock() + return val + + def __setitem__(self, attr, val): + if type(val) is dict: + val = ThreadsafeDict(val) + self.lock() + try: + dict.__setitem__(self, attr, val) + finally: + self.unlock() + + def __contains__(self, attr): + self.lock() + try: + val = dict.__contains__(self, attr) + finally: + self.unlock() + return val + + def __len__(self): + self.lock() + try: + val = dict.__len__(self) + finally: + self.unlock() + return val + + def clear(self): + self.lock() + try: + dict.clear(self) + finally: + self.unlock() + + def lock(self): + self.mutex.acquire() + + def unlock(self): + self.mutex.release() + + def __deepcopy__(self, memo): + raise Exception("deepcopy not implemented") + +class ThreadsafeList(list): + """Extends list so that getitem, setitem, and contains are all thread-safe. + Also adds lock/unlock functions for extended exclusive operations + Converts all sub-lists and dicts to threadsafe as well. + """ + + def __init__(self, *args, **kwargs): + self.mutex = threading.RLock() + list.__init__(self, *args, **kwargs) + for k in self: + self[k] = mkThreadsafe(self[k]) + + def __getitem__(self, attr): + self.lock() + try: + val = list.__getitem__(self, attr) + finally: + self.unlock() + return val + + def __setitem__(self, attr, val): + val = makeThreadsafe(val) + self.lock() + try: + list.__setitem__(self, attr, val) + finally: + self.unlock() + + def __contains__(self, attr): + self.lock() + try: + val = list.__contains__(self, attr) + finally: + self.unlock() + return val + + def __len__(self): + self.lock() + try: + val = list.__len__(self) + finally: + self.unlock() + return val + + def lock(self): + self.mutex.acquire() + + def unlock(self): + self.mutex.release() + + def __deepcopy__(self, memo): + raise Exception("deepcopy not implemented") + + +def makeThreadsafe(obj): + if type(obj) is dict: + return ThreadsafeDict(obj) + elif type(obj) is list: + return ThreadsafeList(obj) + elif type(obj) in [str, int, float, bool, tuple]: + return obj + else: + raise Exception("Not sure how to make object of type %s thread-safe" % str(type(obj))) + + +class Locker(object): + def __init__(self, lock): + self.lock = lock + self.lock.acquire() + def __del__(self): + try: + self.lock.release() + except: + pass + +class CaselessDict(OrderedDict): + """Case-insensitive dict. Values can be set and retrieved using keys of any case. + Note that when iterating, the original case is returned for each key.""" + def __init__(self, *args): + OrderedDict.__init__(self, {}) ## requirement for the empty {} here seems to be a python bug? + self.keyMap = OrderedDict([(k.lower(), k) for k in OrderedDict.keys(self)]) + if len(args) == 0: + return + elif len(args) == 1 and isinstance(args[0], dict): + for k in args[0]: + self[k] = args[0][k] + else: + raise Exception("CaselessDict may only be instantiated with a single dict.") + + #def keys(self): + #return self.keyMap.values() + + def __setitem__(self, key, val): + kl = key.lower() + if kl in self.keyMap: + OrderedDict.__setitem__(self, self.keyMap[kl], val) + else: + OrderedDict.__setitem__(self, key, val) + self.keyMap[kl] = key + + def __getitem__(self, key): + kl = key.lower() + if kl not in self.keyMap: + raise KeyError(key) + return OrderedDict.__getitem__(self, self.keyMap[kl]) + + def __contains__(self, key): + return key.lower() in self.keyMap + + def update(self, d): + for k, v in d.iteritems(): + self[k] = v + + def copy(self): + return CaselessDict(OrderedDict.copy(self)) + + def __delitem__(self, key): + kl = key.lower() + if kl not in self.keyMap: + raise KeyError(key) + OrderedDict.__delitem__(self, self.keyMap[kl]) + del self.keyMap[kl] + + def __deepcopy__(self, memo): + raise Exception("deepcopy not implemented") + + def clear(self): + OrderedDict.clear(self) + self.keyMap.clear() + + + +class ProtectedDict(dict): + """ + A class allowing read-only 'view' of a dict. + The object can be treated like a normal dict, but will never modify the original dict it points to. + Any values accessed from the dict will also be read-only. + """ + def __init__(self, data): + self._data_ = data + + ## List of methods to directly wrap from _data_ + wrapMethods = ['_cmp_', '__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'has_key', 'iterkeys', 'keys', ] + + ## List of methods which wrap from _data_ but return protected results + protectMethods = ['__getitem__', '__iter__', 'get', 'items', 'values'] + + ## List of methods to disable + disableMethods = ['__delitem__', '__setitem__', 'clear', 'pop', 'popitem', 'setdefault', 'update'] + + + ## Template methods + def wrapMethod(methodName): + return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) + + def protectMethod(methodName): + return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) + + def error(self, *args, **kargs): + raise Exception("Can not modify read-only list.") + + + ## Directly (and explicitly) wrap some methods from _data_ + ## Many of these methods can not be intercepted using __getattribute__, so they + ## must be implemented explicitly + for methodName in wrapMethods: + locals()[methodName] = wrapMethod(methodName) + + ## Wrap some methods from _data_ with the results converted to protected objects + for methodName in protectMethods: + locals()[methodName] = protectMethod(methodName) + + ## Disable any methods that could change data in the list + for methodName in disableMethods: + locals()[methodName] = error + + + ## Add a few extra methods. + def copy(self): + raise Exception("It is not safe to copy protected dicts! (instead try deepcopy, but be careful.)") + + def itervalues(self): + for v in self._data_.itervalues(): + yield protect(v) + + def iteritems(self): + for k, v in self._data_.iteritems(): + yield (k, protect(v)) + + def deepcopy(self): + return copy.deepcopy(self._data_) + + def __deepcopy__(self, memo): + return copy.deepcopy(self._data_, memo) + + + +class ProtectedList(collections.Sequence): + """ + A class allowing read-only 'view' of a list or dict. + The object can be treated like a normal list, but will never modify the original list it points to. + Any values accessed from the list will also be read-only. + + Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. + However, doing this causes tuple(obj) to return unprotected results (importantly, this means + unpacking into function arguments will also fail) + """ + def __init__(self, data): + self._data_ = data + #self.__mro__ = (ProtectedList, object) + + ## List of methods to directly wrap from _data_ + wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] + + ## List of methods which wrap from _data_ but return protected results + protectMethods = ['__getitem__', '__getslice__', '__mul__', '__reversed__', '__rmul__'] + + ## List of methods to disable + disableMethods = ['__delitem__', '__delslice__', '__iadd__', '__imul__', '__setitem__', '__setslice__', 'append', 'extend', 'insert', 'pop', 'remove', 'reverse', 'sort'] + + + ## Template methods + def wrapMethod(methodName): + return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) + + def protectMethod(methodName): + return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) + + def error(self, *args, **kargs): + raise Exception("Can not modify read-only list.") + + + ## Directly (and explicitly) wrap some methods from _data_ + ## Many of these methods can not be intercepted using __getattribute__, so they + ## must be implemented explicitly + for methodName in wrapMethods: + locals()[methodName] = wrapMethod(methodName) + + ## Wrap some methods from _data_ with the results converted to protected objects + for methodName in protectMethods: + locals()[methodName] = protectMethod(methodName) + + ## Disable any methods that could change data in the list + for methodName in disableMethods: + locals()[methodName] = error + + + ## Add a few extra methods. + def __iter__(self): + for item in self._data_: + yield protect(item) + + + def __add__(self, op): + if isinstance(op, ProtectedList): + return protect(self._data_.__add__(op._data_)) + elif isinstance(op, list): + return protect(self._data_.__add__(op)) + else: + raise TypeError("Argument must be a list.") + + def __radd__(self, op): + if isinstance(op, ProtectedList): + return protect(op._data_.__add__(self._data_)) + elif isinstance(op, list): + return protect(op.__add__(self._data_)) + else: + raise TypeError("Argument must be a list.") + + def deepcopy(self): + return copy.deepcopy(self._data_) + + def __deepcopy__(self, memo): + return copy.deepcopy(self._data_, memo) + + def poop(self): + raise Exception("This is a list. It does not poop.") + + +class ProtectedTuple(collections.Sequence): + """ + A class allowing read-only 'view' of a tuple. + The object can be treated like a normal tuple, but its contents will be returned as protected objects. + + Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. + However, doing this causes tuple(obj) to return unprotected results (importantly, this means + unpacking into function arguments will also fail) + """ + def __init__(self, data): + self._data_ = data + + ## List of methods to directly wrap from _data_ + wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__getnewargs__', '__gt__', '__hash__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] + + ## List of methods which wrap from _data_ but return protected results + protectMethods = ['__getitem__', '__getslice__', '__iter__', '__add__', '__mul__', '__reversed__', '__rmul__'] + + + ## Template methods + def wrapMethod(methodName): + return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) + + def protectMethod(methodName): + return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) + + + ## Directly (and explicitly) wrap some methods from _data_ + ## Many of these methods can not be intercepted using __getattribute__, so they + ## must be implemented explicitly + for methodName in wrapMethods: + locals()[methodName] = wrapMethod(methodName) + + ## Wrap some methods from _data_ with the results converted to protected objects + for methodName in protectMethods: + locals()[methodName] = protectMethod(methodName) + + + ## Add a few extra methods. + def deepcopy(self): + return copy.deepcopy(self._data_) + + def __deepcopy__(self, memo): + return copy.deepcopy(self._data_, memo) + + + +def protect(obj): + if isinstance(obj, dict): + return ProtectedDict(obj) + elif isinstance(obj, list): + return ProtectedList(obj) + elif isinstance(obj, tuple): + return ProtectedTuple(obj) + else: + return obj + + +if __name__ == '__main__': + d = {'x': 1, 'y': [1,2], 'z': ({'a': 2, 'b': [3,4], 'c': (5,6)}, 1, 2)} + dp = protect(d) + + l = [1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}] + lp = protect(l) + + t = (1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}) + tp = protect(t) diff --git a/pyqtgraph/pixmaps/__init__.py b/pyqtgraph/pixmaps/__init__.py new file mode 100644 index 00000000..c26e4a6b --- /dev/null +++ b/pyqtgraph/pixmaps/__init__.py @@ -0,0 +1,26 @@ +""" +Allows easy loading of pixmaps used in UI elements. +Provides support for frozen environments as well. +""" + +import os, sys, pickle +from ..functions import makeQImage +from ..Qt import QtGui +if sys.version_info[0] == 2: + from . import pixmapData_2 as pixmapData +else: + from . import pixmapData_3 as pixmapData + + +def getPixmap(name): + """ + Return a QPixmap corresponding to the image file with the given name. + (eg. getPixmap('auto') loads pyqtgraph/pixmaps/auto.png) + """ + key = name+'.png' + data = pixmapData.pixmapData[key] + if isinstance(data, basestring) or isinstance(data, bytes): + pixmapData.pixmapData[key] = pickle.loads(data) + arr = pixmapData.pixmapData[key] + return QtGui.QPixmap(makeQImage(arr, alpha=True)) + diff --git a/pyqtgraph/pixmaps/compile.py b/pyqtgraph/pixmaps/compile.py new file mode 100644 index 00000000..fa0d2408 --- /dev/null +++ b/pyqtgraph/pixmaps/compile.py @@ -0,0 +1,19 @@ +import numpy as np +from PyQt4 import QtGui +import os, pickle, sys + +path = os.path.abspath(os.path.split(__file__)[0]) +pixmaps = {} +for f in os.listdir(path): + if not f.endswith('.png'): + continue + print(f) + img = QtGui.QImage(os.path.join(path, f)) + ptr = img.bits() + ptr.setsize(img.byteCount()) + arr = np.asarray(ptr).reshape(img.height(), img.width(), 4).transpose(1,0,2) + pixmaps[f] = pickle.dumps(arr) +ver = sys.version_info[0] +fh = open(os.path.join(path, 'pixmapData_%d.py' %ver), 'w') +fh.write("import numpy as np; pixmapData=%s" % repr(pixmaps)) + diff --git a/pyqtgraph/pixmaps/pixmapData_2.py b/pyqtgraph/pixmaps/pixmapData_2.py new file mode 100644 index 00000000..7813b6a6 --- /dev/null +++ b/pyqtgraph/pixmaps/pixmapData_2.py @@ -0,0 +1 @@ +import numpy as np; pixmapData={'lock.png': "cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I32\nI32\nI4\ntp6\ncnumpy\ndtype\np7\n(S'u1'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'|'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS'\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xad\\xad\\xad\\x19\\xa8\\xa8\\xa8\\x8d\\xa9\\xa9\\xa9\\xc1\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xaa\\xaa\\xaa\\xc2\\xa9\\xa9\\xa9\\x8e\\xad\\xad\\xad\\x19\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xa8\\xa8\\xa8X\\xa9\\xa9\\xa9\\xed\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xed\\xa8\\xa8\\xa8X\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x0c\\x0c\\x0c\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xd2\\xd2\\xd2\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe1\\xe1\\xe1\\xff{{{\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x0e\\x0e\\x0e\\xff***\\xff+++\\xff+++\\xff\\xaf\\xaf\\xaf\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe2\\xe2\\xe2\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x1e\\x1e\\x1e\\xff\\x93\\x93\\x93\\xff\\xc6\\xc6\\xc6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffaaa\\xff\\xdc\\xdc\\xdc\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\\\\\\\\\\\\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe2\\xe2\\xe2\\xff\\xbb\\xbb\\xbb\\xff\\x9f\\x9f\\x9f\\xff\\x9f\\x9f\\x9f\\xff\\x9f\\x9f\\x9f\\xff\\xd7\\xd7\\xd7\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x1c\\x1c\\x1c\\xff\\xda\\xda\\xda\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x91\\x91\\x91\\xff\\x0f\\x0f\\x0f\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x87\\x87\\x87\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x98\\x98\\x98\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xba\\xba\\xba\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x19\\x19\\x19\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x08\\x08\\x08\\xff\\xe2\\xe2\\xe2\\xff\\xe6\\xe6\\xe6\\xff\\xcc\\xcc\\xcc\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x08\\x08\\x08\\xff\\xe2\\xe2\\xe2\\xff\\xe6\\xe6\\xe6\\xff\\xcc\\xcc\\xcc\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xba\\xba\\xba\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x19\\x19\\x19\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x85\\x85\\x85\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x98\\x98\\x98\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x19\\x19\\x19\\xff\\xd9\\xd9\\xd9\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x91\\x91\\x91\\xff\\x0f\\x0f\\x0f\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffZZZ\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe2\\xe2\\xe2\\xff\\xbc\\xbc\\xbc\\xff\\x9f\\x9f\\x9f\\xff\\x9f\\x9f\\x9f\\xff\\x9f\\x9f\\x9f\\xff\\xd7\\xd7\\xd7\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffaaa\\xff\\xdc\\xdc\\xdc\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x1e\\x1e\\x1e\\xff\\x93\\x93\\x93\\xff\\xc6\\xc6\\xc6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x0e\\x0e\\x0e\\xff***\\xff+++\\xff+++\\xff\\xaf\\xaf\\xaf\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe2\\xe2\\xe2\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xd2\\xd2\\xd2\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe1\\xe1\\xe1\\xff{{{\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x0c\\x0c\\x0c\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00'\np13\ntp14\nb.", 'default.png': 'cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS\'b\'\np3\ntp4\nRp5\n(I1\n(I16\nI16\nI4\ntp6\ncnumpy\ndtype\np7\n(S\'u1\'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS\'|\'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS\'\\x00\\x7f\\xa6\\x1b\\x0c\\x8a\\xad\\xdc\\r\\x91\\xb0\\xf3\\r\\x91\\xb0\\xf3\\r\\x91\\xb0\\xf4\\r\\x91\\xb1\\xf4\\r\\x90\\xb0\\xf4\\x05\\x85\\xa9\\xef\\x00\\x7f\\xa6<\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6!\\x1d\\x9c\\xb9\\xf5g\\xd9\\xf1\\xffi\\xd9\\xf3\\xffd\\xd1\\xee\\xff]\\xcb\\xeb\\xff@\\xbb\\xe3\\xff\\x16\\x9c\\xc2\\xf8\\x00\\x7f\\xa6\\xb4\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6U\\\'\\xac\\xc5\\xf9i\\xd9\\xf3\\xffc\\xd3\\xef\\xff\\\\\\xcf\\xeb\\xffP\\xc8\\xe6\\xff\\x17\\x9f\\xc4\\xfd\\x00\\x7f\\xa6\\xfc\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x02\\x83\\xa8lH\\xc5\\xdd\\xfah\\xdc\\xf3\\xffc\\xd4\\xef\\xffV\\xce\\xe9\\xffN\\xcf\\xe7\\xff&\\xaa\\xca\\xfd\\x00\\x7f\\xa6\\xff\\x03\\x81\\xc7\\x01\\x04\\x8d\\xda\\x01\\t\\x94\\xd9\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6"$\\xa9\\xc4\\xf7g\\xdf\\xf5\\xfff\\xdb\\xf3\\xffU\\xcd\\xeb\\xff\\x16\\xb3\\xda\\xff.\\xc9\\xe1\\xff(\\xb2\\xd0\\xfe\\x01\\x7f\\xa6\\xff\\x04\\x84\\xc9\\x05\\t\\x94\\xd9\\x06\\x10\\x9c\\xd7\\x01\\x16\\xa2\\xd6\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x02\\x83\\xa9\\x81T\\xd3\\xeb\\xffg\\xe5\\xf7\\xffe\\xda\\xf3\\xff!\\xaa\\xde\\xff\\x11\\x9d\\xc3\\xfe\\x11\\xba\\xd7\\xff \\xb9\\xd5\\xfe\\x00\\x7f\\xa6\\xff\\x16u\\x8d\\x03\\x14\\x84\\xae\\x05\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x10\\x92\\xb4\\xc0d\\xde\\xf3\\xffg\\xe5\\xf7\\xff_\\xcc\\xef\\xff\\x0e\\x9c\\xd5\\xff\\rx\\x95\\xf6\\x0e\\x89\\xab\\xf4\\x18\\xb2\\xd1\\xfc\\x00\\x7f\\xa6\\xff\\xff\\xff\\xff\\x00\\x1a~\\x91\\x01\\x1d\\xa5\\xce\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x005\\xa9\\xc3\\xefq\\xec\\xf9\\xffg\\xe5\\xf7\\xff>\\xb7\\xe8\\xff\\x14\\x96\\xc8\\xfe\\x02}\\xa3\\xb1\\x00\\x7f\\xa6Q\\x03\\x82\\xa9\\xe8\\x00\\x7f\\xa6\\xe9\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x11\\x1c\\x98\\xb8\\x04%\\xb5\\xd3\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00D\\xad\\xc8\\xf3r\\xec\\xf9\\xffg\\xe5\\xf7\\xff:\\xb7\\xe8\\xff\\x19\\x90\\xc5\\xfe\\x03{\\xa0\\xa6\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6*\\x00\\x7f\\xa6*\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x98\\x0f\\x8f\\xb1\\x13&\\xb5\\xd3\\x04.\\xc0\\xd1\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x19\\x93\\xb7\\xc6i\\xdf\\xf4\\xffg\\xe5\\xf7\\xffT\\xc8\\xee\\xff\\x06\\x88\\xcd\\xff\\x08g\\x85\\xf7\\x00\\x7f\\xa6\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x1b\\x01\\x80\\xa7\\xeb\\x1d\\xa3\\xca\\x16#\\xb2\\xd4\\n*\\xbb\\xd2\\x04.\\xbc\\xd7\\x01\\xff\\xff\\xff\\x00\\x01\\x81\\xa7\\x88Y\\xd1\\xee\\xffg\\xe5\\xf7\\xfff\\xd9\\xf3\\xff\\\'\\xa2\\xe2\\xff\\x05e\\x99\\xf9\\x06~\\xa5\\xf3\\x01\\x81\\xa8\\x9c\\x01\\x80\\xa8\\x9f\\x04\\x85\\xad\\xef\\x08\\x8f\\xb9\\x92\\x17\\xa4\\xd6*\\x1e\\xac\\xd5\\x1a$\\xb3\\xd3\\x0c\\x19\\xa7\\xd5\\x02\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6+!\\xa3\\xc8\\xf5i\\xe0\\xf5\\xffe\\xd9\\xf3\\xff\\\\\\xca\\xee\\xff\\x1f\\x9c\\xe0\\xfa\\x03\\x84\\xca\\xd6\\x07\\x8b\\xc5\\xca\\x06\\x88\\xc1\\xb8\\x08\\x8e\\xd0l\\x0b\\x96\\xd8I\\x11\\x9e\\xd74\\x17\\xa5\\xd6 \\xab\\xd7\\x0b\\x17\\xa2\\xdc\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x01\\x80\\xa8~?\\xb9\\xe0\\xf9h\\xda\\xf3\\xff_\\xcc\\xef\\xffV\\xc1\\xec\\xfd3\\xa7\\xe3\\xe3\\x1a\\x96\\xde\\xae\\x04\\x8b\\xdb\\x89\\x00\\x89\\xdao\\x05\\x8f\\xd9T\\x0b\\x96\\xd8<\\x11\\x9b\\xd7\\x1d\\x18\\x95\\xc9\\x0c\\x00\\x80\\xd5\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x04\\x03\\x83\\xaa\\xcd5\\xa2\\xc9\\xf9[\\xc6\\xea\\xffU\\xc1\\xec\\xffH\\xb4\\xe8\\xf39\\xa8\\xe4\\xc5\\x0b\\x8f\\xdc\\x9f\\x00\\x89\\xda{\\x00\\x89\\xda_\\x07\\x87\\xc4I\\x05|\\xa5s\\x05m\\xa3\\x02\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x06\\x01\\x7f\\xa6\\x89\\x12x\\x9e\\xf63\\x88\\xae\\xfe6\\x93\\xc3\\xfe4\\x9d\\xd6\\xdf\\x08\\x82\\xc7\\xb8\\x03k\\xa2\\xab\\x04k\\x97\\xa8\\x02w\\x9e\\xeb\\x00\\x7f\\xa6j\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa67\\x00~\\xa5\\x95\\x03v\\x9c\\xd4\\x03h\\x8c\\xfa\\x02i\\x8e\\xf9\\x01x\\x9f\\xcc\\x00\\x7f\\xa6\\x92\\x00\\x7f\\xa63\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\'\np13\ntp14\nb.', 'ctrl.png': "cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I32\nI32\nI4\ntp6\ncnumpy\ndtype\np7\n(S'u1'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'|'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS'\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xad\\xad\\xad\\x19\\xa8\\xa8\\xa8\\x8d\\xa9\\xa9\\xa9\\xc1\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xaa\\xaa\\xaa\\xc2\\xa9\\xa9\\xa9\\x8e\\xad\\xad\\xad\\x19\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xa8\\xa8\\xa8X\\xa9\\xa9\\xa9\\xed\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xed\\xa8\\xa8\\xa8X\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff555\\xffPPP\\xff\\x13\\x13\\x13\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x01\\x01\\x01\\xff\\xb2\\xb2\\xb2\\xff\\xe3\\xe3\\xe3\\xff\\xd9\\xd9\\xd9\\xff]]]\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x13\\x13\\x13\\xff\\xbb\\xbb\\xbb\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xffFFF\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x13\\x13\\x13\\xff\\xbb\\xbb\\xbb\\xff\\xe3\\xe3\\xe3\\xff\\xc4\\xc4\\xc4\\xff\\x06\\x06\\x06\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff```\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff:::\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff666\\xff\\xaf\\xaf\\xaf\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9b\\x9b\\x9b\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff@@@\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffSSS\\xff\\xe3\\xe3\\xe3\\xff\\xb7\\xb7\\xb7\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x04\\x04\\x04\\xff\\xd5\\xd5\\xd5\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xffXXX\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x17\\x17\\x17\\xff\\xdb\\xdb\\xdb\\xff\\xe3\\xe3\\xe3\\xff\\xb7\\xb7\\xb7\\xff[[[\\xff\\x97\\x97\\x97\\xff\\xd4\\xd4\\xd4\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff```\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffHHH\\xff\\xc6\\xc6\\xc6\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x07\\x07\\x07\\xff;;;\\xffAAA\\xff\\\\\\\\\\\\\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xc7\\xc7\\xc7\\xffZZZ\\xff~~~\\xff\\xd9\\xd9\\xd9\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xffXXX\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb0\\xb0\\xb0\\xfffff\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xffyyy\\xff\\x00\\x00\\x00\\xff\\x06\\x06\\x06\\xff\\xcd\\xcd\\xcd\\xfffff\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xda\\xda\\xda\\xff\\xaf\\xaf\\xaf\\xff\\xcd\\xcd\\xcd\\xff\\xd7\\xd7\\xd7\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x12\\x12\\x12\\xffiii\\xffccc\\xff\\x0e\\x0e\\x0e\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00'\np13\ntp14\nb.", 'auto.png': "cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I32\nI32\nI4\ntp6\ncnumpy\ndtype\np7\n(S'u1'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'|'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS'\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xad\\xad\\xad\\x19\\xa8\\xa8\\xa8\\x8d\\xa9\\xa9\\xa9\\xc1\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xaa\\xaa\\xaa\\xc2\\xa9\\xa9\\xa9\\x8e\\xad\\xad\\xad\\x19\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xa8\\xa8\\xa8X\\xa9\\xa9\\xa9\\xed\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xed\\xa8\\xa8\\xa8X\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x19\\x19\\x19\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x04\\x04\\x04\\xffHHH\\xff\\xa4\\xa4\\xa4\\xff\\xe5\\xe5\\xe5\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff \\xffyyy\\xff\\xd1\\xd1\\xd1\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x06\\x06\\x06\\xffPPP\\xff\\xab\\xab\\xab\\xff\\xe6\\xe6\\xe6\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff&&&\\xff\\x82\\x82\\x82\\xff\\xd6\\xd6\\xd6\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\t\\t\\t\\xffWWW\\xff\\xb2\\xb2\\xb2\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe5\\xe5\\xe5\\xff\\xa8\\xa8\\xa8\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff---\\xff\\x89\\x89\\x89\\xff\\xda\\xda\\xda\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xc1\\xc1\\xc1\\xfflll\\xff\\x18\\x18\\x18\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\r\\r\\r\\xff^^^\\xff\\xba\\xba\\xba\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xda\\xda\\xda\\xff...\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff555\\xff\\x90\\x90\\x90\\xff\\xde\\xde\\xde\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe2\\xe2\\xe2\\xff\\xe3\\xe3\\xe3\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff;;;\\xff\\xc1\\xc1\\xc1\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xb7\\xb7\\xb7\\xffbbb\\xff\\x12\\x12\\x12\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xcd\\xcd\\xcd\\xffyyy\\xff$$$\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe3\\xe3\\xe3\\xff\\x91\\x91\\x91\\xff<<<\\xff\\x01\\x01\\x01\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xc3\\xc3\\xc3\\xfflll\\xff\\x18\\x18\\x18\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe4\\xe4\\xe4\\xff\\xa6\\xa6\\xa6\\xffOOO\\xff\\x07\\x07\\x07\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff555\\xff\\xb4\\xb4\\xb4\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd9\\xd9\\xd9\\xff\\x8a\\x8a\\x8a\\xff333\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff+++\\xff\\x88\\x88\\x88\\xff\\xda\\xda\\xda\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\n\\n\\n\\xff[[[\\xff\\xb8\\xb8\\xb8\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xdc\\xdc\\xdc\\xffAAA\\xff\\x02\\x02\\x02\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff...\\xff\\x8c\\x8c\\x8c\\xff\\xdc\\xdc\\xdc\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xcc\\xcc\\xcc\\xffsss\\xff\\x1a\\x1a\\x1a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x0c\\x0c\\x0c\\xff___\\xff\\xbc\\xbc\\xbc\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe5\\xe5\\xe5\\xff\\xa5\\xa5\\xa5\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff222\\xff\\x8f\\x8f\\x8f\\xff\\xde\\xde\\xde\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x0e\\x0e\\x0e\\xffccc\\xff\\xc0\\xc0\\xc0\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff555\\xff\\x94\\x94\\x94\\xff\\xe0\\xe0\\xe0\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x10\\x10\\x10\\xfffff\\xff\\xc4\\xc4\\xc4\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff:::\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00'\np13\ntp14\nb."} \ No newline at end of file diff --git a/pyqtgraph/pixmaps/pixmapData_3.py b/pyqtgraph/pixmaps/pixmapData_3.py new file mode 100644 index 00000000..bb512029 --- /dev/null +++ b/pyqtgraph/pixmaps/pixmapData_3.py @@ -0,0 +1 @@ +import numpy as np; pixmapData={'lock.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K K K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x10\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xad\xad\xad\x19\xa8\xa8\xa8\x8d\xa9\xa9\xa9\xc1\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xaa\xaa\xaa\xc2\xa9\xa9\xa9\x8e\xad\xad\xad\x19\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xa8\xa8\xa8X\xa9\xa9\xa9\xed\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xed\xa8\xa8\xa8X\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x0c\x0c\x0c\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xd2\xd2\xd2\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe1\xe1\xe1\xff{{{\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0e\x0e\x0e\xff***\xff+++\xff+++\xff\xaf\xaf\xaf\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\x10\x10\x10\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x1e\x1e\x1e\xff\x93\x93\x93\xff\xc6\xc6\xc6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffaaa\xff\xdc\xdc\xdc\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\\\\\\\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\xbb\xbb\xbb\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\xd7\xd7\xd7\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x1c\x1c\x1c\xff\xda\xda\xda\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x91\x91\x91\xff\x0f\x0f\x0f\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x87\x87\x87\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x98\x98\x98\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\xba\xba\xba\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x19\x19\x19\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x08\x08\x08\xff\xe2\xe2\xe2\xff\xe6\xe6\xe6\xff\xcc\xcc\xcc\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x08\x08\x08\xff\xe2\xe2\xe2\xff\xe6\xe6\xe6\xff\xcc\xcc\xcc\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\xba\xba\xba\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x19\x19\x19\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x85\x85\x85\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x98\x98\x98\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x19\x19\x19\xff\xd9\xd9\xd9\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x91\x91\x91\xff\x0f\x0f\x0f\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffZZZ\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\xbc\xbc\xbc\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\xd7\xd7\xd7\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffaaa\xff\xdc\xdc\xdc\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x1e\x1e\x1e\xff\x93\x93\x93\xff\xc6\xc6\xc6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0e\x0e\x0e\xff***\xff+++\xff+++\xff\xaf\xaf\xaf\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\x10\x10\x10\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xd2\xd2\xd2\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe1\xe1\xe1\xff{{{\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x0c\x0c\x0c\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbd\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbe\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.', 'default.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K\x10K\x10K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x04\x00\x00\x00\x7f\xa6\x1b\x0c\x8a\xad\xdc\r\x91\xb0\xf3\r\x91\xb0\xf3\r\x91\xb0\xf4\r\x91\xb1\xf4\r\x90\xb0\xf4\x05\x85\xa9\xef\x00\x7f\xa6<\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6!\x1d\x9c\xb9\xf5g\xd9\xf1\xffi\xd9\xf3\xffd\xd1\xee\xff]\xcb\xeb\xff@\xbb\xe3\xff\x16\x9c\xc2\xf8\x00\x7f\xa6\xb4\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6U\'\xac\xc5\xf9i\xd9\xf3\xffc\xd3\xef\xff\\\xcf\xeb\xffP\xc8\xe6\xff\x17\x9f\xc4\xfd\x00\x7f\xa6\xfc\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x02\x83\xa8lH\xc5\xdd\xfah\xdc\xf3\xffc\xd4\xef\xffV\xce\xe9\xffN\xcf\xe7\xff&\xaa\xca\xfd\x00\x7f\xa6\xff\x03\x81\xc7\x01\x04\x8d\xda\x01\t\x94\xd9\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6"$\xa9\xc4\xf7g\xdf\xf5\xfff\xdb\xf3\xffU\xcd\xeb\xff\x16\xb3\xda\xff.\xc9\xe1\xff(\xb2\xd0\xfe\x01\x7f\xa6\xff\x04\x84\xc9\x05\t\x94\xd9\x06\x10\x9c\xd7\x01\x16\xa2\xd6\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x02\x83\xa9\x81T\xd3\xeb\xffg\xe5\xf7\xffe\xda\xf3\xff!\xaa\xde\xff\x11\x9d\xc3\xfe\x11\xba\xd7\xff \xb9\xd5\xfe\x00\x7f\xa6\xff\x16u\x8d\x03\x14\x84\xae\x05\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x10\x92\xb4\xc0d\xde\xf3\xffg\xe5\xf7\xff_\xcc\xef\xff\x0e\x9c\xd5\xff\rx\x95\xf6\x0e\x89\xab\xf4\x18\xb2\xd1\xfc\x00\x7f\xa6\xff\xff\xff\xff\x00\x1a~\x91\x01\x1d\xa5\xce\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x005\xa9\xc3\xefq\xec\xf9\xffg\xe5\xf7\xff>\xb7\xe8\xff\x14\x96\xc8\xfe\x02}\xa3\xb1\x00\x7f\xa6Q\x03\x82\xa9\xe8\x00\x7f\xa6\xe9\xff\xff\xff\x00\x00\x7f\xa6\x11\x1c\x98\xb8\x04%\xb5\xd3\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00D\xad\xc8\xf3r\xec\xf9\xffg\xe5\xf7\xff:\xb7\xe8\xff\x19\x90\xc5\xfe\x03{\xa0\xa6\xff\xff\xff\x00\x00\x7f\xa6*\x00\x7f\xa6*\xff\xff\xff\x00\x00\x7f\xa6\x98\x0f\x8f\xb1\x13&\xb5\xd3\x04.\xc0\xd1\x01\xff\xff\xff\x00\xff\xff\xff\x00\x19\x93\xb7\xc6i\xdf\xf4\xffg\xe5\xf7\xffT\xc8\xee\xff\x06\x88\xcd\xff\x08g\x85\xf7\x00\x7f\xa6\x15\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6\x1b\x01\x80\xa7\xeb\x1d\xa3\xca\x16#\xb2\xd4\n*\xbb\xd2\x04.\xbc\xd7\x01\xff\xff\xff\x00\x01\x81\xa7\x88Y\xd1\xee\xffg\xe5\xf7\xfff\xd9\xf3\xff\'\xa2\xe2\xff\x05e\x99\xf9\x06~\xa5\xf3\x01\x81\xa8\x9c\x01\x80\xa8\x9f\x04\x85\xad\xef\x08\x8f\xb9\x92\x17\xa4\xd6*\x1e\xac\xd5\x1a$\xb3\xd3\x0c\x19\xa7\xd5\x02\xff\xff\xff\x00\x00\x7f\xa6+!\xa3\xc8\xf5i\xe0\xf5\xffe\xd9\xf3\xff\\\xca\xee\xff\x1f\x9c\xe0\xfa\x03\x84\xca\xd6\x07\x8b\xc5\xca\x06\x88\xc1\xb8\x08\x8e\xd0l\x0b\x96\xd8I\x11\x9e\xd74\x17\xa5\xd6 \xab\xd7\x0b\x17\xa2\xdc\x01\xff\xff\xff\x00\xff\xff\xff\x00\x01\x80\xa8~?\xb9\xe0\xf9h\xda\xf3\xff_\xcc\xef\xffV\xc1\xec\xfd3\xa7\xe3\xe3\x1a\x96\xde\xae\x04\x8b\xdb\x89\x00\x89\xdao\x05\x8f\xd9T\x0b\x96\xd8<\x11\x9b\xd7\x1d\x18\x95\xc9\x0c\x00\x80\xd5\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6\x04\x03\x83\xaa\xcd5\xa2\xc9\xf9[\xc6\xea\xffU\xc1\xec\xffH\xb4\xe8\xf39\xa8\xe4\xc5\x0b\x8f\xdc\x9f\x00\x89\xda{\x00\x89\xda_\x07\x87\xc4I\x05|\xa5s\x05m\xa3\x02\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6\x06\x01\x7f\xa6\x89\x12x\x9e\xf63\x88\xae\xfe6\x93\xc3\xfe4\x9d\xd6\xdf\x08\x82\xc7\xb8\x03k\xa2\xab\x04k\x97\xa8\x02w\x9e\xeb\x00\x7f\xa6j\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa67\x00~\xa5\x95\x03v\x9c\xd4\x03h\x8c\xfa\x02i\x8e\xf9\x01x\x9f\xcc\x00\x7f\xa6\x92\x00\x7f\xa63\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.', 'ctrl.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K K K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x10\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xad\xad\xad\x19\xa8\xa8\xa8\x8d\xa9\xa9\xa9\xc1\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xaa\xaa\xaa\xc2\xa9\xa9\xa9\x8e\xad\xad\xad\x19\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xa8\xa8\xa8X\xa9\xa9\xa9\xed\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xed\xa8\xa8\xa8X\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff555\xffPPP\xff\x13\x13\x13\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x01\x01\x01\xff\xb2\xb2\xb2\xff\xe3\xe3\xe3\xff\xd9\xd9\xd9\xff]]]\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x13\x13\x13\xff\xbb\xbb\xbb\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xffFFF\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x13\x13\x13\xff\xbb\xbb\xbb\xff\xe3\xe3\xe3\xff\xc4\xc4\xc4\xff\x06\x06\x06\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff```\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff:::\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff666\xff\xaf\xaf\xaf\xff\x10\x10\x10\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9b\x9b\x9b\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff@@@\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xffSSS\xff\xe3\xe3\xe3\xff\xb7\xb7\xb7\xff\x10\x10\x10\xff\x00\x00\x00\xff\x00\x00\x00\xff\x04\x04\x04\xff\xd5\xd5\xd5\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xffXXX\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x17\x17\x17\xff\xdb\xdb\xdb\xff\xe3\xe3\xe3\xff\xb7\xb7\xb7\xff[[[\xff\x97\x97\x97\xff\xd4\xd4\xd4\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff```\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffHHH\xff\xc6\xc6\xc6\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x07\x07\x07\xff;;;\xffAAA\xff\\\\\\\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xc7\xc7\xc7\xffZZZ\xff~~~\xff\xd9\xd9\xd9\xff\x10\x10\x10\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xffXXX\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb0\xb0\xb0\xfffff\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xffyyy\xff\x00\x00\x00\xff\x06\x06\x06\xff\xcd\xcd\xcd\xfffff\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xda\xda\xda\xff\xaf\xaf\xaf\xff\xcd\xcd\xcd\xff\xd7\xd7\xd7\xff\x10\x10\x10\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x12\x12\x12\xffiii\xffccc\xff\x0e\x0e\x0e\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbd\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbe\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.', 'auto.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K K K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x10\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xad\xad\xad\x19\xa8\xa8\xa8\x8d\xa9\xa9\xa9\xc1\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xaa\xaa\xaa\xc2\xa9\xa9\xa9\x8e\xad\xad\xad\x19\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xa8\xa8\xa8X\xa9\xa9\xa9\xed\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xed\xa8\xa8\xa8X\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x19\x19\x19\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x04\x04\x04\xffHHH\xff\xa4\xa4\xa4\xff\xe5\xe5\xe5\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff \xffyyy\xff\xd1\xd1\xd1\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x06\x06\x06\xffPPP\xff\xab\xab\xab\xff\xe6\xe6\xe6\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff&&&\xff\x82\x82\x82\xff\xd6\xd6\xd6\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\t\t\t\xffWWW\xff\xb2\xb2\xb2\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe5\xe5\xe5\xff\xa8\xa8\xa8\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff---\xff\x89\x89\x89\xff\xda\xda\xda\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xc1\xc1\xc1\xfflll\xff\x18\x18\x18\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\r\r\r\xff^^^\xff\xba\xba\xba\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xda\xda\xda\xff...\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff555\xff\x90\x90\x90\xff\xde\xde\xde\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe2\xe2\xe2\xff\xe3\xe3\xe3\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff;;;\xff\xc1\xc1\xc1\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xb7\xb7\xb7\xffbbb\xff\x12\x12\x12\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xcd\xcd\xcd\xffyyy\xff$$$\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe3\xe3\xe3\xff\x91\x91\x91\xff<<<\xff\x01\x01\x01\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xc3\xc3\xc3\xfflll\xff\x18\x18\x18\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe4\xe4\xe4\xff\xa6\xa6\xa6\xffOOO\xff\x07\x07\x07\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff555\xff\xb4\xb4\xb4\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd9\xd9\xd9\xff\x8a\x8a\x8a\xff333\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff+++\xff\x88\x88\x88\xff\xda\xda\xda\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\n\n\n\xff[[[\xff\xb8\xb8\xb8\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xdc\xdc\xdc\xffAAA\xff\x02\x02\x02\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff...\xff\x8c\x8c\x8c\xff\xdc\xdc\xdc\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xcc\xcc\xcc\xffsss\xff\x1a\x1a\x1a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0c\x0c\x0c\xff___\xff\xbc\xbc\xbc\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe5\xe5\xe5\xff\xa5\xa5\xa5\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff222\xff\x8f\x8f\x8f\xff\xde\xde\xde\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0e\x0e\x0e\xffccc\xff\xc0\xc0\xc0\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff555\xff\x94\x94\x94\xff\xe0\xe0\xe0\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x10\x10\x10\xfffff\xff\xc4\xc4\xc4\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbd\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff:::\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbe\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.'} \ No newline at end of file diff --git a/pyqtgraph/ptime.py b/pyqtgraph/ptime.py new file mode 100644 index 00000000..1de8282f --- /dev/null +++ b/pyqtgraph/ptime.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +ptime.py - Precision time function made os-independent (should have been taken care of by python) +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + + +import sys +import time as systime +START_TIME = None +time = None + +def winTime(): + """Return the current time in seconds with high precision (windows version, use Manager.time() to stay platform independent).""" + return systime.clock() + START_TIME + #return systime.time() + +def unixTime(): + """Return the current time in seconds with high precision (unix version, use Manager.time() to stay platform independent).""" + return systime.time() + +if sys.platform.startswith('win'): + cstart = systime.clock() ### Required to start the clock in windows + START_TIME = systime.time() - cstart + + time = winTime +else: + time = unixTime + diff --git a/pyqtgraph/python2_3.py b/pyqtgraph/python2_3.py new file mode 100644 index 00000000..2182d3a1 --- /dev/null +++ b/pyqtgraph/python2_3.py @@ -0,0 +1,60 @@ +""" +Helper functions which smooth out the differences between python 2 and 3. +""" +import sys + +def asUnicode(x): + if sys.version_info[0] == 2: + if isinstance(x, unicode): + return x + elif isinstance(x, str): + return x.decode('UTF-8') + else: + return unicode(x) + else: + return str(x) + +def cmpToKey(mycmp): + 'Convert a cmp= function into a key= function' + class K(object): + def __init__(self, obj, *args): + self.obj = obj + def __lt__(self, other): + return mycmp(self.obj, other.obj) < 0 + def __gt__(self, other): + return mycmp(self.obj, other.obj) > 0 + def __eq__(self, other): + return mycmp(self.obj, other.obj) == 0 + def __le__(self, other): + return mycmp(self.obj, other.obj) <= 0 + def __ge__(self, other): + return mycmp(self.obj, other.obj) >= 0 + def __ne__(self, other): + return mycmp(self.obj, other.obj) != 0 + return K + +def sortList(l, cmpFunc): + if sys.version_info[0] == 2: + l.sort(cmpFunc) + else: + l.sort(key=cmpToKey(cmpFunc)) + +if sys.version_info[0] == 3: + import builtins + builtins.basestring = str + #builtins.asUnicode = asUnicode + #builtins.sortList = sortList + basestring = str + def cmp(a,b): + if a>b: + return 1 + elif b > a: + return -1 + else: + return 0 + builtins.cmp = cmp + builtins.xrange = range +#else: ## don't use __builtin__ -- this confuses things like pyshell and ActiveState's lazy import recipe + #import __builtin__ + #__builtin__.asUnicode = asUnicode + #__builtin__.sortList = sortList diff --git a/pyqtgraph/reload.py b/pyqtgraph/reload.py new file mode 100644 index 00000000..ccf83913 --- /dev/null +++ b/pyqtgraph/reload.py @@ -0,0 +1,516 @@ +# -*- coding: utf-8 -*- +""" +Magic Reload Library +Luke Campagnola 2010 + +Python reload function that actually works (the way you expect it to) + - No re-importing necessary + - Modules can be reloaded in any order + - Replaces functions and methods with their updated code + - Changes instances to use updated classes + - Automatically decides which modules to update by comparing file modification times + +Does NOT: + - re-initialize exting instances, even if __init__ changes + - update references to any module-level objects + ie, this does not reload correctly: + from module import someObject + print someObject + ..but you can use this instead: (this works even for the builtin reload) + import module + print module.someObject +""" + + +import inspect, os, sys, gc, traceback +try: + import __builtin__ as builtins +except ImportError: + import builtins +from .debug import printExc + +def reloadAll(prefix=None, debug=False): + """Automatically reload everything whose __file__ begins with prefix. + - Skips reload if the file has not been updated (if .pyc is newer than .py) + - if prefix is None, checks all loaded modules + """ + failed = [] + changed = [] + for modName, mod in list(sys.modules.items()): ## don't use iteritems; size may change during reload + if not inspect.ismodule(mod): + continue + if modName == '__main__': + continue + + ## Ignore if the file name does not start with prefix + if not hasattr(mod, '__file__') or os.path.splitext(mod.__file__)[1] not in ['.py', '.pyc']: + continue + if prefix is not None and mod.__file__[:len(prefix)] != prefix: + continue + + ## ignore if the .pyc is newer than the .py (or if there is no pyc or py) + py = os.path.splitext(mod.__file__)[0] + '.py' + pyc = py + 'c' + if py not in changed and os.path.isfile(pyc) and os.path.isfile(py) and os.stat(pyc).st_mtime >= os.stat(py).st_mtime: + #if debug: + #print "Ignoring module %s; unchanged" % str(mod) + continue + changed.append(py) ## keep track of which modules have changed to insure that duplicate-import modules get reloaded. + + try: + reload(mod, debug=debug) + except: + printExc("Error while reloading module %s, skipping\n" % mod) + failed.append(mod.__name__) + + if len(failed) > 0: + raise Exception("Some modules failed to reload: %s" % ', '.join(failed)) + +def reload(module, debug=False, lists=False, dicts=False): + """Replacement for the builtin reload function: + - Reloads the module as usual + - Updates all old functions and class methods to use the new code + - Updates all instances of each modified class to use the new class + - Can update lists and dicts, but this is disabled by default + - Requires that class and function names have not changed + """ + if debug: + print("Reloading %s" % str(module)) + + ## make a copy of the old module dictionary, reload, then grab the new module dictionary for comparison + oldDict = module.__dict__.copy() + builtins.reload(module) + newDict = module.__dict__ + + ## Allow modules access to the old dictionary after they reload + if hasattr(module, '__reload__'): + module.__reload__(oldDict) + + ## compare old and new elements from each dict; update where appropriate + for k in oldDict: + old = oldDict[k] + new = newDict.get(k, None) + if old is new or new is None: + continue + + if inspect.isclass(old): + if debug: + print(" Updating class %s.%s (0x%x -> 0x%x)" % (module.__name__, k, id(old), id(new))) + updateClass(old, new, debug) + + elif inspect.isfunction(old): + depth = updateFunction(old, new, debug) + if debug: + extra = "" + if depth > 0: + extra = " (and %d previous versions)" % depth + print(" Updating function %s.%s%s" % (module.__name__, k, extra)) + elif lists and isinstance(old, list): + l = old.len() + old.extend(new) + for i in range(l): + old.pop(0) + elif dicts and isinstance(old, dict): + old.update(new) + for k in old: + if k not in new: + del old[k] + + + +## For functions: +## 1) update the code and defaults to new versions. +## 2) keep a reference to the previous version so ALL versions get updated for every reload +def updateFunction(old, new, debug, depth=0, visited=None): + #if debug and depth > 0: + #print " -> also updating previous version", old, " -> ", new + + old.__code__ = new.__code__ + old.__defaults__ = new.__defaults__ + + if visited is None: + visited = [] + if old in visited: + return + visited.append(old) + + ## finally, update any previous versions still hanging around.. + if hasattr(old, '__previous_reload_version__'): + maxDepth = updateFunction(old.__previous_reload_version__, new, debug, depth=depth+1, visited=visited) + else: + maxDepth = depth + + ## We need to keep a pointer to the previous version so we remember to update BOTH + ## when the next reload comes around. + if depth == 0: + new.__previous_reload_version__ = old + return maxDepth + + + +## For classes: +## 1) find all instances of the old class and set instance.__class__ to the new class +## 2) update all old class methods to use code from the new class methods +def updateClass(old, new, debug): + + ## Track town all instances and subclasses of old + refs = gc.get_referrers(old) + for ref in refs: + try: + if isinstance(ref, old) and ref.__class__ is old: + ref.__class__ = new + if debug: + print(" Changed class for %s" % safeStr(ref)) + elif inspect.isclass(ref) and issubclass(ref, old) and old in ref.__bases__: + ind = ref.__bases__.index(old) + + ## Does not work: + #ref.__bases__ = ref.__bases__[:ind] + (new,) + ref.__bases__[ind+1:] + ## reason: Even though we change the code on methods, they remain bound + ## to their old classes (changing im_class is not allowed). Instead, + ## we have to update the __bases__ such that this class will be allowed + ## as an argument to older methods. + + ## This seems to work. Is there any reason not to? + ## Note that every time we reload, the class hierarchy becomes more complex. + ## (and I presume this may slow things down?) + ref.__bases__ = ref.__bases__[:ind] + (new,old) + ref.__bases__[ind+1:] + if debug: + print(" Changed superclass for %s" % safeStr(ref)) + #else: + #if debug: + #print " Ignoring reference", type(ref) + except: + print("Error updating reference (%s) for class change (%s -> %s)" % (safeStr(ref), safeStr(old), safeStr(new))) + raise + + ## update all class methods to use new code. + ## Generally this is not needed since instances already know about the new class, + ## but it fixes a few specific cases (pyqt signals, for one) + for attr in dir(old): + oa = getattr(old, attr) + if inspect.ismethod(oa): + try: + na = getattr(new, attr) + except AttributeError: + if debug: + print(" Skipping method update for %s; new class does not have this attribute" % attr) + continue + + if hasattr(oa, 'im_func') and hasattr(na, 'im_func') and oa.__func__ is not na.__func__: + depth = updateFunction(oa.__func__, na.__func__, debug) + #oa.im_class = new ## bind old method to new class ## not allowed + if debug: + extra = "" + if depth > 0: + extra = " (and %d previous versions)" % depth + print(" Updating method %s%s" % (attr, extra)) + + ## And copy in new functions that didn't exist previously + for attr in dir(new): + if not hasattr(old, attr): + if debug: + print(" Adding missing attribute %s" % attr) + setattr(old, attr, getattr(new, attr)) + + ## finally, update any previous versions still hanging around.. + if hasattr(old, '__previous_reload_version__'): + updateClass(old.__previous_reload_version__, new, debug) + + +## It is possible to build classes for which str(obj) just causes an exception. +## Avoid thusly: +def safeStr(obj): + try: + s = str(obj) + except: + try: + s = repr(obj) + except: + s = "" % (safeStr(type(obj)), id(obj)) + return s + + + + + +## Tests: +# write modules to disk, import, then re-write and run again +if __name__ == '__main__': + doQtTest = True + try: + from PyQt4 import QtCore + if not hasattr(QtCore, 'Signal'): + QtCore.Signal = QtCore.pyqtSignal + #app = QtGui.QApplication([]) + class Btn(QtCore.QObject): + sig = QtCore.Signal() + def emit(self): + self.sig.emit() + btn = Btn() + except: + raise + print("Error; skipping Qt tests") + doQtTest = False + + + + import os + if not os.path.isdir('test1'): + os.mkdir('test1') + open('test1/__init__.py', 'w') + modFile1 = "test1/test1.py" + modCode1 = """ +import sys +class A(object): + def __init__(self, msg): + object.__init__(self) + self.msg = msg + def fn(self, pfx = ""): + print(pfx+"A class: %%s %%s" %% (str(self.__class__), str(id(self.__class__)))) + print(pfx+" %%s: %d" %% self.msg) + +class B(A): + def fn(self, pfx=""): + print(pfx+"B class:", self.__class__, id(self.__class__)) + print(pfx+" %%s: %d" %% self.msg) + print(pfx+" calling superclass.. (%%s)" %% id(A) ) + A.fn(self, " ") +""" + + modFile2 = "test2.py" + modCode2 = """ +from test1.test1 import A +from test1.test1 import B + +a1 = A("ax1") +b1 = B("bx1") +class C(A): + def __init__(self, msg): + #print "| C init:" + #print "| C.__bases__ = ", map(id, C.__bases__) + #print "| A:", id(A) + #print "| A.__init__ = ", id(A.__init__.im_func), id(A.__init__.im_func.__code__), id(A.__init__.im_class) + A.__init__(self, msg + "(init from C)") + +def fn(): + print("fn: %s") +""" + + open(modFile1, 'w').write(modCode1%(1,1)) + open(modFile2, 'w').write(modCode2%"message 1") + import test1.test1 as test1 + import test2 + print("Test 1 originals:") + A1 = test1.A + B1 = test1.B + a1 = test1.A("a1") + b1 = test1.B("b1") + a1.fn() + b1.fn() + #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) + + + from test2 import fn, C + + if doQtTest: + print("Button test before:") + btn.sig.connect(fn) + btn.sig.connect(a1.fn) + btn.emit() + #btn.sig.emit() + print("") + + #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) + + + print("Test2 before reload:") + + fn() + oldfn = fn + test2.a1.fn() + test2.b1.fn() + c1 = test2.C('c1') + c1.fn() + + os.remove(modFile1+'c') + open(modFile1, 'w').write(modCode1%(2,2)) + print("\n----RELOAD test1-----\n") + reloadAll(os.path.abspath(__file__)[:10], debug=True) + + + print("Subclass test:") + c2 = test2.C('c2') + c2.fn() + + + os.remove(modFile2+'c') + open(modFile2, 'w').write(modCode2%"message 2") + print("\n----RELOAD test2-----\n") + reloadAll(os.path.abspath(__file__)[:10], debug=True) + + if doQtTest: + print("Button test after:") + btn.emit() + #btn.sig.emit() + + #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) + + print("Test2 after reload:") + fn() + test2.a1.fn() + test2.b1.fn() + + print("\n==> Test 1 Old instances:") + a1.fn() + b1.fn() + c1.fn() + #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) + + print("\n==> Test 1 New instances:") + a2 = test1.A("a2") + b2 = test1.B("b2") + a2.fn() + b2.fn() + c2 = test2.C('c2') + c2.fn() + #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) + + + + + os.remove(modFile1+'c') + os.remove(modFile2+'c') + open(modFile1, 'w').write(modCode1%(3,3)) + open(modFile2, 'w').write(modCode2%"message 3") + + print("\n----RELOAD-----\n") + reloadAll(os.path.abspath(__file__)[:10], debug=True) + + if doQtTest: + print("Button test after:") + btn.emit() + #btn.sig.emit() + + #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) + + print("Test2 after reload:") + fn() + test2.a1.fn() + test2.b1.fn() + + print("\n==> Test 1 Old instances:") + a1.fn() + b1.fn() + print("function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.__func__), id(a1.fn.__self__.__class__), id(b1.fn.__func__), id(b1.fn.__self__.__class__))) + + print("\n==> Test 1 New instances:") + a2 = test1.A("a2") + b2 = test1.B("b2") + a2.fn() + b2.fn() + print("function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.__func__), id(a1.fn.__self__.__class__), id(b1.fn.__func__), id(b1.fn.__self__.__class__))) + + + os.remove(modFile1) + os.remove(modFile2) + os.remove(modFile1+'c') + os.remove(modFile2+'c') + os.system('rm -r test1') + + + + + + + + +# +# Failure graveyard ahead: +# + + +"""Reload Importer: +Hooks into import system to +1) keep a record of module dependencies as they are imported +2) make sure modules are always reloaded in correct order +3) update old classes and functions to use reloaded code""" + +#import imp, sys + +## python's import hook mechanism doesn't work since we need to be +## informed every time there is an import statement, not just for new imports +#class ReloadImporter: + #def __init__(self): + #self.depth = 0 + + #def find_module(self, name, path): + #print " "*self.depth + "find: ", name, path + ##if name == 'PyQt4' and path is None: + ##print "PyQt4 -> PySide" + ##self.modData = imp.find_module('PySide') + ##return self + ##return None ## return none to allow the import to proceed normally; return self to intercept with load_module + #self.modData = imp.find_module(name, path) + #self.depth += 1 + ##sys.path_importer_cache = {} + #return self + + #def load_module(self, name): + #mod = imp.load_module(name, *self.modData) + #self.depth -= 1 + #print " "*self.depth + "load: ", name + #return mod + +#def pathHook(path): + #print "path hook:", path + #raise ImportError +#sys.path_hooks.append(pathHook) + +#sys.meta_path.append(ReloadImporter()) + + +### replace __import__ with a wrapper that tracks module dependencies +#modDeps = {} +#reloadModule = None +#origImport = __builtins__.__import__ +#def _import(name, globals=None, locals=None, fromlist=None, level=-1, stack=[]): + ### Note that stack behaves as a static variable. + ##print " "*len(importStack) + "import %s" % args[0] + #stack.append(set()) + #mod = origImport(name, globals, locals, fromlist, level) + #deps = stack.pop() + #if len(stack) > 0: + #stack[-1].add(mod) + #elif reloadModule is not None: ## If this is the top level import AND we're inside a module reload + #modDeps[reloadModule].add(mod) + + #if mod in modDeps: + #modDeps[mod] |= deps + #else: + #modDeps[mod] = deps + + + #return mod + +#__builtins__.__import__ = _import + +### replace +#origReload = __builtins__.reload +#def _reload(mod): + #reloadModule = mod + #ret = origReload(mod) + #reloadModule = None + #return ret +#__builtins__.reload = _reload + + +#def reload(mod, visited=None): + #if visited is None: + #visited = set() + #if mod in visited: + #return + #visited.add(mod) + #for dep in modDeps.get(mod, []): + #reload(dep, visited) + #__builtins__.reload(mod) diff --git a/pyqtgraph/units.py b/pyqtgraph/units.py new file mode 100644 index 00000000..6b7f3099 --- /dev/null +++ b/pyqtgraph/units.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +## Very simple unit support: +## - creates variable names like 'mV' and 'kHz' +## - the value assigned to the variable corresponds to the scale prefix +## (mV = 0.001) +## - the actual units are purely cosmetic for making code clearer: +## +## x = 20*pA is identical to x = 20*1e-12 + +## No unicode variable names (μ,Ω) allowed until python 3 + +SI_PREFIXES = 'yzafpnum kMGTPEZY' +UNITS = 'm,s,g,W,J,V,A,F,T,Hz,Ohm,S,N,C,px,b,B'.split(',') +allUnits = {} + +def addUnit(p, n): + g = globals() + v = 1000**n + for u in UNITS: + g[p+u] = v + allUnits[p+u] = v + +for p in SI_PREFIXES: + if p == ' ': + p = '' + n = 0 + elif p == 'u': + n = -2 + else: + n = SI_PREFIXES.index(p) - 8 + + addUnit(p, n) + +cm = 0.01 + + + + + + +def evalUnits(unitStr): + """ + Evaluate a unit string into ([numerators,...], [denominators,...]) + Examples: + N m/s^2 => ([N, m], [s, s]) + A*s / V => ([A, s], [V,]) + """ + pass + +def formatUnits(units): + """ + Format a unit specification ([numerators,...], [denominators,...]) + into a string (this is the inverse of evalUnits) + """ + pass + +def simplify(units): + """ + Cancel units that appear in both numerator and denominator, then attempt to replace + groups of units with single units where possible (ie, J/s => W) + """ + pass + + \ No newline at end of file diff --git a/pyqtgraph/widgets/BusyCursor.py b/pyqtgraph/widgets/BusyCursor.py new file mode 100644 index 00000000..b013dda0 --- /dev/null +++ b/pyqtgraph/widgets/BusyCursor.py @@ -0,0 +1,24 @@ +from pyqtgraph.Qt import QtGui, QtCore + +__all__ = ['BusyCursor'] + +class BusyCursor(object): + """Class for displaying a busy mouse cursor during long operations. + Usage:: + + with pyqtgraph.BusyCursor(): + doLongOperation() + + May be nested. + """ + active = [] + + def __enter__(self): + QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) + BusyCursor.active.append(self) + + def __exit__(self, *args): + BusyCursor.active.pop(-1) + if len(BusyCursor.active) == 0: + QtGui.QApplication.restoreOverrideCursor() + \ No newline at end of file diff --git a/pyqtgraph/widgets/CheckTable.py b/pyqtgraph/widgets/CheckTable.py new file mode 100644 index 00000000..dd33fd75 --- /dev/null +++ b/pyqtgraph/widgets/CheckTable.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +from . import VerticalLabel + +__all__ = ['CheckTable'] + +class CheckTable(QtGui.QWidget): + + sigStateChanged = QtCore.Signal(object, object, object) # (row, col, state) + + def __init__(self, columns): + QtGui.QWidget.__init__(self) + self.layout = QtGui.QGridLayout() + self.layout.setSpacing(0) + self.setLayout(self.layout) + self.headers = [] + self.columns = columns + col = 1 + for c in columns: + label = VerticalLabel.VerticalLabel(c, orientation='vertical') + self.headers.append(label) + self.layout.addWidget(label, 0, col) + col += 1 + + self.rowNames = [] + self.rowWidgets = [] + self.oldRows = {} ## remember settings from removed rows; reapply if they reappear. + + + def updateRows(self, rows): + for r in self.rowNames[:]: + if r not in rows: + self.removeRow(r) + for r in rows: + if r not in self.rowNames: + self.addRow(r) + + def addRow(self, name): + label = QtGui.QLabel(name) + row = len(self.rowNames)+1 + self.layout.addWidget(label, row, 0) + checks = [] + col = 1 + for c in self.columns: + check = QtGui.QCheckBox('') + check.col = c + check.row = name + self.layout.addWidget(check, row, col) + checks.append(check) + if name in self.oldRows: + check.setChecked(self.oldRows[name][col]) + col += 1 + #QtCore.QObject.connect(check, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) + check.stateChanged.connect(self.checkChanged) + self.rowNames.append(name) + self.rowWidgets.append([label] + checks) + + def removeRow(self, name): + row = self.rowNames.index(name) + self.oldRows[name] = self.saveState()['rows'][row] ## save for later + self.rowNames.pop(row) + for w in self.rowWidgets[row]: + w.setParent(None) + #QtCore.QObject.disconnect(w, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) + if isinstance(w, QtGui.QCheckBox): + w.stateChanged.disconnect(self.checkChanged) + self.rowWidgets.pop(row) + for i in range(row, len(self.rowNames)): + widgets = self.rowWidgets[i] + for j in range(len(widgets)): + widgets[j].setParent(None) + self.layout.addWidget(widgets[j], i+1, j) + + def checkChanged(self, state): + check = QtCore.QObject.sender(self) + #self.emit(QtCore.SIGNAL('stateChanged'), check.row, check.col, state) + self.sigStateChanged.emit(check.row, check.col, state) + + def saveState(self): + rows = [] + for i in range(len(self.rowNames)): + row = [self.rowNames[i]] + [c.isChecked() for c in self.rowWidgets[i][1:]] + rows.append(row) + return {'cols': self.columns, 'rows': rows} + + def restoreState(self, state): + rows = [r[0] for r in state['rows']] + self.updateRows(rows) + for r in state['rows']: + rowNum = self.rowNames.index(r[0]) + for i in range(1, len(r)): + self.rowWidgets[rowNum][i].setChecked(r[i]) + diff --git a/pyqtgraph/widgets/ColorButton.py b/pyqtgraph/widgets/ColorButton.py new file mode 100644 index 00000000..ee91801a --- /dev/null +++ b/pyqtgraph/widgets/ColorButton.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.functions as functions + +__all__ = ['ColorButton'] + +class ColorButton(QtGui.QPushButton): + """ + **Bases:** QtGui.QPushButton + + Button displaying a color and allowing the user to select a new color. + + ====================== ============================================================ + **Signals**: + sigColorChanging(self) emitted whenever a new color is picked in the color dialog + sigColorChanged(self) emitted when the selected color is accepted (user clicks OK) + ====================== ============================================================ + """ + sigColorChanging = QtCore.Signal(object) ## emitted whenever a new color is picked in the color dialog + sigColorChanged = QtCore.Signal(object) ## emitted when the selected color is accepted (user clicks OK) + + def __init__(self, parent=None, color=(128,128,128)): + QtGui.QPushButton.__init__(self, parent) + self.setColor(color) + self.colorDialog = QtGui.QColorDialog() + self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) + self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) + self.colorDialog.currentColorChanged.connect(self.dialogColorChanged) + self.colorDialog.rejected.connect(self.colorRejected) + self.colorDialog.colorSelected.connect(self.colorSelected) + #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged) + #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected) + self.clicked.connect(self.selectColor) + self.setMinimumHeight(15) + self.setMinimumWidth(15) + + def paintEvent(self, ev): + QtGui.QPushButton.paintEvent(self, ev) + p = QtGui.QPainter(self) + rect = self.rect().adjusted(6, 6, -6, -6) + ## draw white base, then texture for indicating transparency, then actual color + p.setBrush(functions.mkBrush('w')) + p.drawRect(rect) + p.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) + p.drawRect(rect) + p.setBrush(functions.mkBrush(self._color)) + p.drawRect(rect) + p.end() + + def setColor(self, color, finished=True): + """Sets the button's color and emits both sigColorChanged and sigColorChanging.""" + self._color = functions.mkColor(color) + if finished: + self.sigColorChanged.emit(self) + else: + self.sigColorChanging.emit(self) + self.update() + + def selectColor(self): + self.origColor = self.color() + self.colorDialog.setCurrentColor(self.color()) + self.colorDialog.open() + + def dialogColorChanged(self, color): + if color.isValid(): + self.setColor(color, finished=False) + + def colorRejected(self): + self.setColor(self.origColor, finished=False) + + def colorSelected(self, color): + self.setColor(self._color, finished=True) + + def saveState(self): + return functions.colorTuple(self._color) + + def restoreState(self, state): + self.setColor(state) + + def color(self, mode='qcolor'): + color = functions.mkColor(self._color) + if mode == 'qcolor': + return color + elif mode == 'byte': + return (color.red(), color.green(), color.blue(), color.alpha()) + elif mode == 'float': + return (color.red()/255., color.green()/255., color.blue()/255., color.alpha()/255.) + + def widgetGroupInterface(self): + return (self.sigColorChanged, ColorButton.saveState, ColorButton.restoreState) + diff --git a/pyqtgraph/widgets/ColorMapWidget.py b/pyqtgraph/widgets/ColorMapWidget.py new file mode 100644 index 00000000..26539d7e --- /dev/null +++ b/pyqtgraph/widgets/ColorMapWidget.py @@ -0,0 +1,218 @@ +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.parametertree as ptree +import numpy as np +from pyqtgraph.pgcollections import OrderedDict +import pyqtgraph.functions as fn + +__all__ = ['ColorMapWidget'] + +class ColorMapWidget(ptree.ParameterTree): + """ + This class provides a widget allowing the user to customize color mapping + for multi-column data. Given a list of field names, the user may specify + multiple criteria for assigning colors to each record in a numpy record array. + Multiple criteria are evaluated and combined into a single color for each + record by user-defined compositing methods. + + For simpler color mapping using a single gradient editor, see + :class:`GradientWidget ` + """ + sigColorMapChanged = QtCore.Signal(object) + + def __init__(self): + ptree.ParameterTree.__init__(self, showHeader=False) + + self.params = ColorMapParameter() + self.setParameters(self.params) + self.params.sigTreeStateChanged.connect(self.mapChanged) + + ## wrap a couple methods + self.setFields = self.params.setFields + self.map = self.params.map + + def mapChanged(self): + self.sigColorMapChanged.emit(self) + + +class ColorMapParameter(ptree.types.GroupParameter): + sigColorMapChanged = QtCore.Signal(object) + + def __init__(self): + self.fields = {} + ptree.types.GroupParameter.__init__(self, name='Color Map', addText='Add Mapping..', addList=[]) + self.sigTreeStateChanged.connect(self.mapChanged) + + def mapChanged(self): + self.sigColorMapChanged.emit(self) + + def addNew(self, name): + mode = self.fields[name].get('mode', 'range') + if mode == 'range': + self.addChild(RangeColorMapItem(name, self.fields[name])) + elif mode == 'enum': + self.addChild(EnumColorMapItem(name, self.fields[name])) + + def fieldNames(self): + return self.fields.keys() + + def setFields(self, fields): + """ + Set the list of fields to be used by the mapper. + + The format of *fields* is:: + + [ (fieldName, {options}), ... ] + + ============== ============================================================ + Field Options: + mode Either 'range' or 'enum' (default is range). For 'range', + The user may specify a gradient of colors to be applied + linearly across a specific range of values. For 'enum', + the user specifies a single color for each unique value + (see *values* option). + units String indicating the units of the data for this field. + values List of unique values for which the user may assign a + color when mode=='enum'. Optionally may specify a dict + instead {value: name}. + ============== ============================================================ + """ + self.fields = OrderedDict(fields) + #self.fields = fields + #self.fields.sort() + names = self.fieldNames() + self.setAddList(names) + + def map(self, data, mode='byte'): + """ + Return an array of colors corresponding to *data*. + + ========= ================================================================= + Arguments + data A numpy record array where the fields in data.dtype match those + defined by a prior call to setFields(). + mode Either 'byte' or 'float'. For 'byte', the method returns an array + of dtype ubyte with values scaled 0-255. For 'float', colors are + returned as 0.0-1.0 float values. + ========= ================================================================= + """ + colors = np.zeros((len(data),4)) + for item in self.children(): + if not item['Enabled']: + continue + chans = item.param('Channels..') + mask = np.empty((len(data), 4), dtype=bool) + for i,f in enumerate(['Red', 'Green', 'Blue', 'Alpha']): + mask[:,i] = chans[f] + + colors2 = item.map(data) + + op = item['Operation'] + if op == 'Add': + colors[mask] = colors[mask] + colors2[mask] + elif op == 'Multiply': + colors[mask] *= colors2[mask] + elif op == 'Overlay': + a = colors2[:,3:4] + c3 = colors * (1-a) + colors2 * a + c3[:,3:4] = colors[:,3:4] + (1-colors[:,3:4]) * a + colors = c3 + elif op == 'Set': + colors[mask] = colors2[mask] + + + colors = np.clip(colors, 0, 1) + if mode == 'byte': + colors = (colors * 255).astype(np.ubyte) + + return colors + + +class RangeColorMapItem(ptree.types.SimpleParameter): + def __init__(self, name, opts): + self.fieldName = name + units = opts.get('units', '') + ptree.types.SimpleParameter.__init__(self, + name=name, autoIncrementName=True, type='colormap', removable=True, renamable=True, + children=[ + #dict(name="Field", type='list', value=name, values=fields), + dict(name='Min', type='float', value=0.0, suffix=units, siPrefix=True), + dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True), + dict(name='Operation', type='list', value='Overlay', values=['Overlay', 'Add', 'Multiply', 'Set']), + dict(name='Channels..', type='group', expanded=False, children=[ + dict(name='Red', type='bool', value=True), + dict(name='Green', type='bool', value=True), + dict(name='Blue', type='bool', value=True), + dict(name='Alpha', type='bool', value=True), + ]), + dict(name='Enabled', type='bool', value=True), + dict(name='NaN', type='color'), + ]) + + def map(self, data): + data = data[self.fieldName] + + + + scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) + cmap = self.value() + colors = cmap.map(scaled, mode='float') + + mask = np.isnan(data) | np.isinf(data) + nanColor = self['NaN'] + nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.) + colors[mask] = nanColor + + return colors + + +class EnumColorMapItem(ptree.types.GroupParameter): + def __init__(self, name, opts): + self.fieldName = name + vals = opts.get('values', []) + if isinstance(vals, list): + vals = OrderedDict([(v,str(v)) for v in vals]) + childs = [{'name': v, 'type': 'color'} for v in vals] + + childs = [] + for val,vname in vals.items(): + ch = ptree.Parameter.create(name=vname, type='color') + ch.maskValue = val + childs.append(ch) + + ptree.types.GroupParameter.__init__(self, + name=name, autoIncrementName=True, removable=True, renamable=True, + children=[ + dict(name='Values', type='group', children=childs), + dict(name='Operation', type='list', value='Overlay', values=['Overlay', 'Add', 'Multiply', 'Set']), + dict(name='Channels..', type='group', expanded=False, children=[ + dict(name='Red', type='bool', value=True), + dict(name='Green', type='bool', value=True), + dict(name='Blue', type='bool', value=True), + dict(name='Alpha', type='bool', value=True), + ]), + dict(name='Enabled', type='bool', value=True), + dict(name='Default', type='color'), + ]) + + def map(self, data): + data = data[self.fieldName] + colors = np.empty((len(data), 4)) + default = np.array(fn.colorTuple(self['Default'])) / 255. + colors[:] = default + + for v in self.param('Values'): + mask = data == v.maskValue + c = np.array(fn.colorTuple(v.value())) / 255. + colors[mask] = c + #scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) + #cmap = self.value() + #colors = cmap.map(scaled, mode='float') + + #mask = np.isnan(data) | np.isinf(data) + #nanColor = self['NaN'] + #nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.) + #colors[mask] = nanColor + + return colors + + diff --git a/pyqtgraph/widgets/ComboBox.py b/pyqtgraph/widgets/ComboBox.py new file mode 100644 index 00000000..1884648c --- /dev/null +++ b/pyqtgraph/widgets/ComboBox.py @@ -0,0 +1,41 @@ +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.SignalProxy import SignalProxy + + +class ComboBox(QtGui.QComboBox): + """Extends QComboBox to add extra functionality. + - updateList() - updates the items in the comboBox while blocking signals, remembers and resets to the previous values if it's still in the list + """ + + + def __init__(self, parent=None, items=None, default=None): + QtGui.QComboBox.__init__(self, parent) + + #self.value = default + + if items is not None: + self.addItems(items) + if default is not None: + self.setValue(default) + + def setValue(self, value): + ind = self.findText(value) + if ind == -1: + return + #self.value = value + self.setCurrentIndex(ind) + + def updateList(self, items): + prevVal = str(self.currentText()) + try: + self.blockSignals(True) + self.clear() + self.addItems(items) + self.setValue(prevVal) + + finally: + self.blockSignals(False) + + if str(self.currentText()) != prevVal: + self.currentIndexChanged.emit(self.currentIndex()) + \ No newline at end of file diff --git a/pyqtgraph/widgets/DataFilterWidget.py b/pyqtgraph/widgets/DataFilterWidget.py new file mode 100644 index 00000000..c94f6c68 --- /dev/null +++ b/pyqtgraph/widgets/DataFilterWidget.py @@ -0,0 +1,150 @@ +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph.parametertree as ptree +import numpy as np +from pyqtgraph.pgcollections import OrderedDict +import pyqtgraph as pg + +__all__ = ['DataFilterWidget'] + +class DataFilterWidget(ptree.ParameterTree): + """ + This class allows the user to filter multi-column data sets by specifying + multiple criteria + """ + + sigFilterChanged = QtCore.Signal(object) + + def __init__(self): + ptree.ParameterTree.__init__(self, showHeader=False) + self.params = DataFilterParameter() + + self.setParameters(self.params) + self.params.sigTreeStateChanged.connect(self.filterChanged) + + self.setFields = self.params.setFields + self.filterData = self.params.filterData + self.describe = self.params.describe + + def filterChanged(self): + self.sigFilterChanged.emit(self) + + def parameters(self): + return self.params + + +class DataFilterParameter(ptree.types.GroupParameter): + + sigFilterChanged = QtCore.Signal(object) + + def __init__(self): + self.fields = {} + ptree.types.GroupParameter.__init__(self, name='Data Filter', addText='Add filter..', addList=[]) + self.sigTreeStateChanged.connect(self.filterChanged) + + def filterChanged(self): + self.sigFilterChanged.emit(self) + + def addNew(self, name): + mode = self.fields[name].get('mode', 'range') + if mode == 'range': + self.addChild(RangeFilterItem(name, self.fields[name])) + elif mode == 'enum': + self.addChild(EnumFilterItem(name, self.fields[name])) + + + def fieldNames(self): + return self.fields.keys() + + def setFields(self, fields): + self.fields = OrderedDict(fields) + names = self.fieldNames() + self.setAddList(names) + + def filterData(self, data): + if len(data) == 0: + return data + return data[self.generateMask(data)] + + def generateMask(self, data): + mask = np.ones(len(data), dtype=bool) + if len(data) == 0: + return mask + for fp in self: + if fp.value() is False: + continue + mask &= fp.generateMask(data, mask.copy()) + #key, mn, mx = fp.fieldName, fp['Min'], fp['Max'] + + #vals = data[key] + #mask &= (vals >= mn) + #mask &= (vals < mx) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections + return mask + + def describe(self): + """Return a list of strings describing the currently enabled filters.""" + desc = [] + for fp in self: + if fp.value() is False: + continue + desc.append(fp.describe()) + return desc + +class RangeFilterItem(ptree.types.SimpleParameter): + def __init__(self, name, opts): + self.fieldName = name + units = opts.get('units', '') + self.units = units + ptree.types.SimpleParameter.__init__(self, + name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True, + children=[ + #dict(name="Field", type='list', value=name, values=fields), + dict(name='Min', type='float', value=0.0, suffix=units, siPrefix=True), + dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True), + ]) + + def generateMask(self, data, mask): + vals = data[self.fieldName][mask] + mask[mask] = (vals >= self['Min']) & (vals < self['Max']) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections + return mask + + def describe(self): + return "%s < %s < %s" % (pg.siFormat(self['Min'], suffix=self.units), self.fieldName, pg.siFormat(self['Max'], suffix=self.units)) + +class EnumFilterItem(ptree.types.SimpleParameter): + def __init__(self, name, opts): + self.fieldName = name + vals = opts.get('values', []) + childs = [] + if isinstance(vals, list): + vals = OrderedDict([(v,str(v)) for v in vals]) + for val,vname in vals.items(): + ch = ptree.Parameter.create(name=vname, type='bool', value=True) + ch.maskValue = val + childs.append(ch) + ch = ptree.Parameter.create(name='(other)', type='bool', value=True) + ch.maskValue = '__other__' + childs.append(ch) + + ptree.types.SimpleParameter.__init__(self, + name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True, + children=childs) + + def generateMask(self, data, startMask): + vals = data[self.fieldName][startMask] + mask = np.ones(len(vals), dtype=bool) + otherMask = np.ones(len(vals), dtype=bool) + for c in self: + key = c.maskValue + if key == '__other__': + m = ~otherMask + else: + m = vals != key + otherMask &= m + if c.value() is False: + mask &= m + startMask[startMask] = mask + return startMask + + def describe(self): + vals = [ch.name() for ch in self if ch.value() is True] + return "%s: %s" % (self.fieldName, ', '.join(vals)) \ No newline at end of file diff --git a/pyqtgraph/widgets/DataTreeWidget.py b/pyqtgraph/widgets/DataTreeWidget.py new file mode 100644 index 00000000..a6b5cac8 --- /dev/null +++ b/pyqtgraph/widgets/DataTreeWidget.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.pgcollections import OrderedDict +import types, traceback +import numpy as np + +try: + import metaarray + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + +__all__ = ['DataTreeWidget'] + +class DataTreeWidget(QtGui.QTreeWidget): + """ + Widget for displaying hierarchical python data structures + (eg, nested dicts, lists, and arrays) + """ + + + def __init__(self, parent=None, data=None): + QtGui.QTreeWidget.__init__(self, parent) + self.setVerticalScrollMode(self.ScrollPerPixel) + self.setData(data) + self.setColumnCount(3) + self.setHeaderLabels(['key / index', 'type', 'value']) + + def setData(self, data, hideRoot=False): + """data should be a dictionary.""" + self.clear() + self.buildTree(data, self.invisibleRootItem(), hideRoot=hideRoot) + #node = self.mkNode('', data) + #while node.childCount() > 0: + #c = node.child(0) + #node.removeChild(c) + #self.invisibleRootItem().addChild(c) + self.expandToDepth(3) + self.resizeColumnToContents(0) + + def buildTree(self, data, parent, name='', hideRoot=False): + if hideRoot: + node = parent + else: + typeStr = type(data).__name__ + if typeStr == 'instance': + typeStr += ": " + data.__class__.__name__ + node = QtGui.QTreeWidgetItem([name, typeStr, ""]) + parent.addChild(node) + + if isinstance(data, types.TracebackType): ## convert traceback to a list of strings + data = list(map(str.strip, traceback.format_list(traceback.extract_tb(data)))) + elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): + data = { + 'data': data.view(np.ndarray), + 'meta': data.infoCopy() + } + + if isinstance(data, dict): + for k in data: + self.buildTree(data[k], node, str(k)) + elif isinstance(data, list) or isinstance(data, tuple): + for i in range(len(data)): + self.buildTree(data[i], node, str(i)) + else: + node.setText(2, str(data)) + + + #def mkNode(self, name, v): + #if type(v) is list and len(v) > 0 and isinstance(v[0], dict): + #inds = map(unicode, range(len(v))) + #v = OrderedDict(zip(inds, v)) + #if isinstance(v, dict): + ##print "\nadd tree", k, v + #node = QtGui.QTreeWidgetItem([name]) + #for k in v: + #newNode = self.mkNode(k, v[k]) + #node.addChild(newNode) + #else: + ##print "\nadd value", k, str(v) + #node = QtGui.QTreeWidgetItem([unicode(name), unicode(v)]) + #return node + diff --git a/pyqtgraph/widgets/FeedbackButton.py b/pyqtgraph/widgets/FeedbackButton.py new file mode 100644 index 00000000..f788f4b6 --- /dev/null +++ b/pyqtgraph/widgets/FeedbackButton.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtCore, QtGui + +__all__ = ['FeedbackButton'] + +class FeedbackButton(QtGui.QPushButton): + """ + QPushButton which flashes success/failure indication for slow or asynchronous procedures. + """ + + + ### For thread-safetyness + sigCallSuccess = QtCore.Signal(object, object, object) + sigCallFailure = QtCore.Signal(object, object, object) + sigCallProcess = QtCore.Signal(object, object, object) + sigReset = QtCore.Signal() + + def __init__(self, *args): + QtGui.QPushButton.__init__(self, *args) + self.origStyle = None + self.origText = self.text() + self.origStyle = self.styleSheet() + self.origTip = self.toolTip() + self.limitedTime = True + + + #self.textTimer = QtCore.QTimer() + #self.tipTimer = QtCore.QTimer() + #self.textTimer.timeout.connect(self.setText) + #self.tipTimer.timeout.connect(self.setToolTip) + + self.sigCallSuccess.connect(self.success) + self.sigCallFailure.connect(self.failure) + self.sigCallProcess.connect(self.processing) + self.sigReset.connect(self.reset) + + + def feedback(self, success, message=None, tip="", limitedTime=True): + """Calls success() or failure(). If you want the message to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action.Threadsafe.""" + if success: + self.success(message, tip, limitedTime=limitedTime) + else: + self.failure(message, tip, limitedTime=limitedTime) + + def success(self, message=None, tip="", limitedTime=True): + """Displays specified message on button and flashes button green to let user know action was successful. If you want the success to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action. Threadsafe.""" + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + self.setEnabled(True) + #print "success" + self.startBlink("#0F0", message, tip, limitedTime=limitedTime) + else: + self.sigCallSuccess.emit(message, tip, limitedTime) + + def failure(self, message=None, tip="", limitedTime=True): + """Displays specified message on button and flashes button red to let user know there was an error. If you want the error to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action. Threadsafe. """ + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + self.setEnabled(True) + #print "fail" + self.startBlink("#F00", message, tip, limitedTime=limitedTime) + else: + self.sigCallFailure.emit(message, tip, limitedTime) + + def processing(self, message="Processing..", tip="", processEvents=True): + """Displays specified message on button to let user know the action is in progress. Threadsafe. """ + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + self.setEnabled(False) + self.setText(message, temporary=True) + self.setToolTip(tip, temporary=True) + if processEvents: + QtGui.QApplication.processEvents() + else: + self.sigCallProcess.emit(message, tip, processEvents) + + + def reset(self): + """Resets the button to its original text and style. Threadsafe.""" + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + self.limitedTime = True + self.setText() + self.setToolTip() + self.setStyleSheet() + else: + self.sigReset.emit() + + def startBlink(self, color, message=None, tip="", limitedTime=True): + #if self.origStyle is None: + #self.origStyle = self.styleSheet() + #self.origText = self.text() + self.setFixedHeight(self.height()) + + if message is not None: + self.setText(message, temporary=True) + self.setToolTip(tip, temporary=True) + self.count = 0 + #self.indStyle = "QPushButton {border: 2px solid %s; border-radius: 5px}" % color + self.indStyle = "QPushButton {background-color: %s}" % color + self.limitedTime = limitedTime + self.borderOn() + if limitedTime: + QtCore.QTimer.singleShot(2000, self.setText) + QtCore.QTimer.singleShot(10000, self.setToolTip) + + def borderOn(self): + self.setStyleSheet(self.indStyle, temporary=True) + if self.limitedTime or self.count <=2: + QtCore.QTimer.singleShot(100, self.borderOff) + + + def borderOff(self): + self.setStyleSheet() + self.count += 1 + if self.count >= 2: + if self.limitedTime: + return + QtCore.QTimer.singleShot(30, self.borderOn) + + + def setText(self, text=None, temporary=False): + if text is None: + text = self.origText + #print text + QtGui.QPushButton.setText(self, text) + if not temporary: + self.origText = text + + def setToolTip(self, text=None, temporary=False): + if text is None: + text = self.origTip + QtGui.QPushButton.setToolTip(self, text) + if not temporary: + self.origTip = text + + def setStyleSheet(self, style=None, temporary=False): + if style is None: + style = self.origStyle + QtGui.QPushButton.setStyleSheet(self, style) + if not temporary: + self.origStyle = style + + +if __name__ == '__main__': + import time + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + btn = FeedbackButton("Button") + fail = True + def click(): + btn.processing("Hold on..") + time.sleep(2.0) + + global fail + fail = not fail + if fail: + btn.failure(message="FAIL.", tip="There was a failure. Get over it.") + else: + btn.success(message="Bueno!") + btn.clicked.connect(click) + win.setCentralWidget(btn) + win.show() \ No newline at end of file diff --git a/pyqtgraph/widgets/FileDialog.py b/pyqtgraph/widgets/FileDialog.py new file mode 100644 index 00000000..33b838a2 --- /dev/null +++ b/pyqtgraph/widgets/FileDialog.py @@ -0,0 +1,14 @@ +from pyqtgraph.Qt import QtGui, QtCore +import sys + +__all__ = ['FileDialog'] + +class FileDialog(QtGui.QFileDialog): + ## Compatibility fix for OSX: + ## For some reason the native dialog doesn't show up when you set AcceptMode to AcceptSave on OS X, so we don't use the native dialog + + def __init__(self, *args): + QtGui.QFileDialog.__init__(self, *args) + + if sys.platform == 'darwin': + self.setOption(QtGui.QFileDialog.DontUseNativeDialog) \ No newline at end of file diff --git a/pyqtgraph/widgets/GradientWidget.py b/pyqtgraph/widgets/GradientWidget.py new file mode 100644 index 00000000..1723a94b --- /dev/null +++ b/pyqtgraph/widgets/GradientWidget.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +from .GraphicsView import GraphicsView +from pyqtgraph.graphicsItems.GradientEditorItem import GradientEditorItem +import weakref +import numpy as np + +__all__ = ['TickSlider', 'GradientWidget', 'BlackWhiteSlider'] + + +class GradientWidget(GraphicsView): + """ + Widget displaying an editable color gradient. The user may add, move, recolor, + or remove colors from the gradient. Additionally, a context menu allows the + user to select from pre-defined gradients. + """ + sigGradientChanged = QtCore.Signal(object) + sigGradientChangeFinished = QtCore.Signal(object) + + def __init__(self, parent=None, orientation='bottom', *args, **kargs): + """ + The *orientation* argument may be 'bottom', 'top', 'left', or 'right' + indicating whether the gradient is displayed horizontally (top, bottom) + or vertically (left, right) and on what side of the gradient the editable + ticks will appear. + + All other arguments are passed to + :func:`GradientEditorItem.__init__ `. + + Note: For convenience, this class wraps methods from + :class:`GradientEditorItem `. + """ + GraphicsView.__init__(self, parent, useOpenGL=False, background=None) + self.maxDim = 31 + kargs['tickPen'] = 'k' + self.item = GradientEditorItem(*args, **kargs) + self.item.sigGradientChanged.connect(self.sigGradientChanged) + self.item.sigGradientChangeFinished.connect(self.sigGradientChangeFinished) + self.setCentralItem(self.item) + self.setOrientation(orientation) + self.setCacheMode(self.CacheNone) + self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing) + self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) + #self.setBackgroundRole(QtGui.QPalette.NoRole) + #self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.NoBrush)) + #self.setAutoFillBackground(False) + #self.setAttribute(QtCore.Qt.WA_PaintOnScreen, False) + #self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, True) + + def setOrientation(self, ort): + """Set the orientation of the widget. May be one of 'bottom', 'top', + 'left', or 'right'.""" + self.item.setOrientation(ort) + self.orientation = ort + self.setMaxDim() + + def setMaxDim(self, mx=None): + if mx is None: + mx = self.maxDim + else: + self.maxDim = mx + + if self.orientation in ['bottom', 'top']: + self.setFixedHeight(mx) + self.setMaximumWidth(16777215) + else: + self.setFixedWidth(mx) + self.setMaximumHeight(16777215) + + def __getattr__(self, attr): + ### wrap methods from GradientEditorItem + return getattr(self.item, attr) + + diff --git a/pyqtgraph/widgets/GraphicsLayoutWidget.py b/pyqtgraph/widgets/GraphicsLayoutWidget.py new file mode 100644 index 00000000..1e667278 --- /dev/null +++ b/pyqtgraph/widgets/GraphicsLayoutWidget.py @@ -0,0 +1,12 @@ +from pyqtgraph.Qt import QtGui +from pyqtgraph.graphicsItems.GraphicsLayout import GraphicsLayout +from .GraphicsView import GraphicsView + +__all__ = ['GraphicsLayoutWidget'] +class GraphicsLayoutWidget(GraphicsView): + def __init__(self, parent=None, **kargs): + GraphicsView.__init__(self, parent) + self.ci = GraphicsLayout(**kargs) + for n in ['nextRow', 'nextCol', 'nextColumn', 'addPlot', 'addViewBox', 'addItem', 'getItem', 'addLabel', 'addLayout', 'addLabel', 'addViewBox', 'removeItem', 'itemIndex', 'clear']: + setattr(self, n, getattr(self.ci, n)) + self.setCentralItem(self.ci) diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py new file mode 100644 index 00000000..fb535929 --- /dev/null +++ b/pyqtgraph/widgets/GraphicsView.py @@ -0,0 +1,393 @@ +# -*- coding: utf-8 -*- +""" +GraphicsView.py - Extension of QGraphicsView +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from pyqtgraph.Qt import QtCore, QtGui +import pyqtgraph as pg + +try: + from pyqtgraph.Qt import QtOpenGL + HAVE_OPENGL = True +except ImportError: + HAVE_OPENGL = False + +from pyqtgraph.Point import Point +import sys, os +from .FileDialog import FileDialog +from pyqtgraph.GraphicsScene import GraphicsScene +import numpy as np +import pyqtgraph.functions as fn +import pyqtgraph.debug as debug +import pyqtgraph + +__all__ = ['GraphicsView'] + +class GraphicsView(QtGui.QGraphicsView): + """Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the + viewed coordinate range. Also automatically creates a GraphicsScene and a central QGraphicsWidget + that is automatically scaled to the full view geometry. + + This widget is the basis for :class:`PlotWidget `, + :class:`GraphicsLayoutWidget `, and the view widget in + :class:`ImageView `. + + By default, the view coordinate system matches the widget's pixel coordinates and + automatically updates when the view is resized. This can be overridden by setting + autoPixelRange=False. The exact visible range can be set with setRange(). + + The view can be panned using the middle mouse button and scaled using the right mouse button if + enabled via enableMouse() (but ordinarily, we use ViewBox for this functionality).""" + + sigRangeChanged = QtCore.Signal(object, object) + sigTransformChanged = QtCore.Signal(object) + sigMouseReleased = QtCore.Signal(object) + sigSceneMouseMoved = QtCore.Signal(object) + #sigRegionChanged = QtCore.Signal(object) + sigScaleChanged = QtCore.Signal(object) + lastFileDir = None + + def __init__(self, parent=None, useOpenGL=None, background='default'): + """ + ============ ============================================================ + Arguments: + parent Optional parent widget + useOpenGL If True, the GraphicsView will use OpenGL to do all of its + rendering. This can improve performance on some systems, + but may also introduce bugs (the combination of + QGraphicsView and QGLWidget is still an 'experimental' + feature of Qt) + background Set the background color of the GraphicsView. Accepts any + single argument accepted by + :func:`mkColor `. By + default, the background color is determined using the + 'backgroundColor' configuration option (see + :func:`setConfigOption `. + ============ ============================================================ + """ + + self.closed = False + + QtGui.QGraphicsView.__init__(self, parent) + + if useOpenGL is None: + useOpenGL = pyqtgraph.getConfigOption('useOpenGL') + + self.useOpenGL(useOpenGL) + + self.setCacheMode(self.CacheBackground) + + ## This might help, but it's probably dangerous in the general case.. + #self.setOptimizationFlag(self.DontSavePainterState, True) + + self.setBackgroundRole(QtGui.QPalette.NoRole) + self.setBackground(background) + + self.setFocusPolicy(QtCore.Qt.StrongFocus) + self.setFrameShape(QtGui.QFrame.NoFrame) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor) + self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) + self.setViewportUpdateMode(QtGui.QGraphicsView.MinimalViewportUpdate) + + + self.lockedViewports = [] + self.lastMousePos = None + self.setMouseTracking(True) + self.aspectLocked = False + self.range = QtCore.QRectF(0, 0, 1, 1) + self.autoPixelRange = True + self.currentItem = None + self.clearMouse() + self.updateMatrix() + self.sceneObj = GraphicsScene() + self.setScene(self.sceneObj) + + ## Workaround for PySide crash + ## This ensures that the scene will outlive the view. + if pyqtgraph.Qt.USE_PYSIDE: + self.sceneObj._view_ref_workaround = self + + ## by default we set up a central widget with a grid layout. + ## this can be replaced if needed. + self.centralWidget = None + self.setCentralItem(QtGui.QGraphicsWidget()) + self.centralLayout = QtGui.QGraphicsGridLayout() + self.centralWidget.setLayout(self.centralLayout) + + self.mouseEnabled = False + self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False) + self.clickAccepted = False + + def setAntialiasing(self, aa): + """Enable or disable default antialiasing. + Note that this will only affect items that do not specify their own antialiasing options.""" + if aa: + self.setRenderHints(self.renderHints() | QtGui.QPainter.Antialiasing) + else: + self.setRenderHints(self.renderHints() & ~QtGui.QPainter.Antialiasing) + + def setBackground(self, background): + """ + Set the background color of the GraphicsView. + To use the defaults specified py pyqtgraph.setConfigOption, use background='default'. + To make the background transparent, use background=None. + """ + self._background = background + if background == 'default': + background = pyqtgraph.getConfigOption('background') + brush = fn.mkBrush(background) + self.setBackgroundBrush(brush) + + def paintEvent(self, ev): + self.scene().prepareForPaint() + #print "GV: paint", ev.rect() + return QtGui.QGraphicsView.paintEvent(self, ev) + + def render(self, *args, **kwds): + self.scene().prepareForPaint() + return QtGui.QGraphicsView.render(self, *args, **kwds) + + + def close(self): + self.centralWidget = None + self.scene().clear() + self.currentItem = None + self.sceneObj = None + self.closed = True + self.setViewport(None) + + def useOpenGL(self, b=True): + if b: + if not HAVE_OPENGL: + raise Exception("Requested to use OpenGL with QGraphicsView, but QtOpenGL module is not available.") + v = QtOpenGL.QGLWidget() + else: + v = QtGui.QWidget() + + self.setViewport(v) + + def keyPressEvent(self, ev): + self.scene().keyPressEvent(ev) ## bypass view, hand event directly to scene + ## (view likes to eat arrow key events) + + + def setCentralItem(self, item): + return self.setCentralWidget(item) + + def setCentralWidget(self, item): + """Sets a QGraphicsWidget to automatically fill the entire view (the item will be automatically + resize whenever the GraphicsView is resized).""" + if self.centralWidget is not None: + self.scene().removeItem(self.centralWidget) + self.centralWidget = item + if item is not None: + self.sceneObj.addItem(item) + self.resizeEvent(None) + + def addItem(self, *args): + return self.scene().addItem(*args) + + def removeItem(self, *args): + return self.scene().removeItem(*args) + + def enableMouse(self, b=True): + self.mouseEnabled = b + self.autoPixelRange = (not b) + + def clearMouse(self): + self.mouseTrail = [] + self.lastButtonReleased = None + + def resizeEvent(self, ev): + if self.closed: + return + if self.autoPixelRange: + self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height()) + GraphicsView.setRange(self, self.range, padding=0, disableAutoPixel=False) ## we do this because some subclasses like to redefine setRange in an incompatible way. + self.updateMatrix() + + def updateMatrix(self, propagate=True): + self.setSceneRect(self.range) + if self.autoPixelRange: + self.resetTransform() + else: + if self.aspectLocked: + self.fitInView(self.range, QtCore.Qt.KeepAspectRatio) + else: + self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio) + + self.sigRangeChanged.emit(self, self.range) + self.sigTransformChanged.emit(self) + + if propagate: + for v in self.lockedViewports: + v.setXRange(self.range, padding=0) + + def viewRect(self): + """Return the boundaries of the view in scene coordinates""" + ## easier to just return self.range ? + r = QtCore.QRectF(self.rect()) + return self.viewportTransform().inverted()[0].mapRect(r) + + def visibleRange(self): + ## for backward compatibility + return self.viewRect() + + def translate(self, dx, dy): + self.range.adjust(dx, dy, dx, dy) + self.updateMatrix() + + def scale(self, sx, sy, center=None): + scale = [sx, sy] + if self.aspectLocked: + scale[0] = scale[1] + + if self.scaleCenter: + center = None + if center is None: + center = self.range.center() + + w = self.range.width() / scale[0] + h = self.range.height() / scale[1] + self.range = QtCore.QRectF(center.x() - (center.x()-self.range.left()) / scale[0], center.y() - (center.y()-self.range.top()) /scale[1], w, h) + + + self.updateMatrix() + self.sigScaleChanged.emit(self) + + def setRange(self, newRect=None, padding=0.05, lockAspect=None, propagate=True, disableAutoPixel=True): + if disableAutoPixel: + self.autoPixelRange=False + if newRect is None: + newRect = self.visibleRange() + padding = 0 + + padding = Point(padding) + newRect = QtCore.QRectF(newRect) + pw = newRect.width() * padding[0] + ph = newRect.height() * padding[1] + newRect = newRect.adjusted(-pw, -ph, pw, ph) + scaleChanged = False + if self.range.width() != newRect.width() or self.range.height() != newRect.height(): + scaleChanged = True + self.range = newRect + #print "New Range:", self.range + if self.centralWidget is not None: + self.centralWidget.setGeometry(self.range) + self.updateMatrix(propagate) + if scaleChanged: + self.sigScaleChanged.emit(self) + + def scaleToImage(self, image): + """Scales such that pixels in image are the same size as screen pixels. This may result in a significant performance increase.""" + pxSize = image.pixelSize() + image.setPxMode(True) + try: + self.sigScaleChanged.disconnect(image.setScaledMode) + except TypeError: + pass + tl = image.sceneBoundingRect().topLeft() + w = self.size().width() * pxSize[0] + h = self.size().height() * pxSize[1] + range = QtCore.QRectF(tl.x(), tl.y(), w, h) + GraphicsView.setRange(self, range, padding=0) + self.sigScaleChanged.connect(image.setScaledMode) + + + + def lockXRange(self, v1): + if not v1 in self.lockedViewports: + self.lockedViewports.append(v1) + + def setXRange(self, r, padding=0.05): + r1 = QtCore.QRectF(self.range) + r1.setLeft(r.left()) + r1.setRight(r.right()) + GraphicsView.setRange(self, r1, padding=[padding, 0], propagate=False) + + def setYRange(self, r, padding=0.05): + r1 = QtCore.QRectF(self.range) + r1.setTop(r.top()) + r1.setBottom(r.bottom()) + GraphicsView.setRange(self, r1, padding=[0, padding], propagate=False) + + def wheelEvent(self, ev): + QtGui.QGraphicsView.wheelEvent(self, ev) + if not self.mouseEnabled: + return + sc = 1.001 ** ev.delta() + #self.scale *= sc + #self.updateMatrix() + self.scale(sc, sc) + + def setAspectLocked(self, s): + self.aspectLocked = s + + def leaveEvent(self, ev): + self.scene().leaveEvent(ev) ## inform scene when mouse leaves + + def mousePressEvent(self, ev): + QtGui.QGraphicsView.mousePressEvent(self, ev) + + + if not self.mouseEnabled: + return + self.lastMousePos = Point(ev.pos()) + self.mousePressPos = ev.pos() + self.clickAccepted = ev.isAccepted() + if not self.clickAccepted: + self.scene().clearSelection() + return ## Everything below disabled for now.. + + def mouseReleaseEvent(self, ev): + QtGui.QGraphicsView.mouseReleaseEvent(self, ev) + if not self.mouseEnabled: + return + self.sigMouseReleased.emit(ev) + self.lastButtonReleased = ev.button() + return ## Everything below disabled for now.. + + def mouseMoveEvent(self, ev): + if self.lastMousePos is None: + self.lastMousePos = Point(ev.pos()) + delta = Point(ev.pos() - self.lastMousePos) + self.lastMousePos = Point(ev.pos()) + + QtGui.QGraphicsView.mouseMoveEvent(self, ev) + if not self.mouseEnabled: + return + self.sigSceneMouseMoved.emit(self.mapToScene(ev.pos())) + + if self.clickAccepted: ## Ignore event if an item in the scene has already claimed it. + return + + if ev.buttons() == QtCore.Qt.RightButton: + delta = Point(np.clip(delta[0], -50, 50), np.clip(-delta[1], -50, 50)) + scale = 1.01 ** delta + self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos)) + self.sigRangeChanged.emit(self, self.range) + + elif ev.buttons() in [QtCore.Qt.MidButton, QtCore.Qt.LeftButton]: ## Allow panning by left or mid button. + px = self.pixelSize() + tr = -delta * px + + self.translate(tr[0], tr[1]) + self.sigRangeChanged.emit(self, self.range) + + def pixelSize(self): + """Return vector with the length and width of one view pixel in scene coordinates""" + p0 = Point(0,0) + p1 = Point(1,1) + tr = self.transform().inverted()[0] + p01 = tr.map(p0) + p11 = tr.map(p1) + return Point(p11 - p01) + + def dragEnterEvent(self, ev): + ev.ignore() ## not sure why, but for some reason this class likes to consume drag events + + diff --git a/pyqtgraph/widgets/HistogramLUTWidget.py b/pyqtgraph/widgets/HistogramLUTWidget.py new file mode 100644 index 00000000..cbe8eb61 --- /dev/null +++ b/pyqtgraph/widgets/HistogramLUTWidget.py @@ -0,0 +1,33 @@ +""" +Widget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. +This is a wrapper around HistogramLUTItem +""" + +from pyqtgraph.Qt import QtGui, QtCore +from .GraphicsView import GraphicsView +from pyqtgraph.graphicsItems.HistogramLUTItem import HistogramLUTItem + +__all__ = ['HistogramLUTWidget'] + + +class HistogramLUTWidget(GraphicsView): + + def __init__(self, parent=None, *args, **kargs): + background = kargs.get('background', 'default') + GraphicsView.__init__(self, parent, useOpenGL=False, background=background) + self.item = HistogramLUTItem(*args, **kargs) + self.setCentralItem(self.item) + self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + self.setMinimumWidth(95) + + + def sizeHint(self): + return QtCore.QSize(115, 200) + + + + def __getattr__(self, attr): + return getattr(self.item, attr) + + + diff --git a/pyqtgraph/widgets/JoystickButton.py b/pyqtgraph/widgets/JoystickButton.py new file mode 100644 index 00000000..201a957a --- /dev/null +++ b/pyqtgraph/widgets/JoystickButton.py @@ -0,0 +1,95 @@ +from pyqtgraph.Qt import QtGui, QtCore + + +__all__ = ['JoystickButton'] + +class JoystickButton(QtGui.QPushButton): + sigStateChanged = QtCore.Signal(object, object) ## self, state + + def __init__(self, parent=None): + QtGui.QPushButton.__init__(self, parent) + self.radius = 200 + self.setCheckable(True) + self.state = None + self.setState(0,0) + self.setFixedWidth(50) + self.setFixedHeight(50) + + + def mousePressEvent(self, ev): + self.setChecked(True) + self.pressPos = ev.pos() + ev.accept() + + def mouseMoveEvent(self, ev): + dif = ev.pos()-self.pressPos + self.setState(dif.x(), -dif.y()) + + def mouseReleaseEvent(self, ev): + self.setChecked(False) + self.setState(0,0) + + def wheelEvent(self, ev): + ev.accept() + + + def doubleClickEvent(self, ev): + ev.accept() + + def getState(self): + return self.state + + def setState(self, *xy): + xy = list(xy) + d = (xy[0]**2 + xy[1]**2)**0.5 + nxy = [0,0] + for i in [0,1]: + if xy[i] == 0: + nxy[i] = 0 + else: + nxy[i] = xy[i]/d + + if d > self.radius: + d = self.radius + d = (d/self.radius)**2 + xy = [nxy[0]*d, nxy[1]*d] + + w2 = self.width()/2. + h2 = self.height()/2 + self.spotPos = QtCore.QPoint(w2*(1+xy[0]), h2*(1-xy[1])) + self.update() + if self.state == xy: + return + self.state = xy + self.sigStateChanged.emit(self, self.state) + + def paintEvent(self, ev): + QtGui.QPushButton.paintEvent(self, ev) + p = QtGui.QPainter(self) + p.setBrush(QtGui.QBrush(QtGui.QColor(0,0,0))) + p.drawEllipse(self.spotPos.x()-3,self.spotPos.y()-3,6,6) + + def resizeEvent(self, ev): + self.setState(*self.state) + QtGui.QPushButton.resizeEvent(self, ev) + + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + w = QtGui.QMainWindow() + b = JoystickButton() + w.setCentralWidget(b) + w.show() + w.resize(100, 100) + + def fn(b, s): + print("state changed:", s) + + b.sigStateChanged.connect(fn) + + ## Start Qt event loop unless running in interactive mode. + import sys + if sys.flags.interactive != 1: + app.exec_() + \ No newline at end of file diff --git a/pyqtgraph/widgets/LayoutWidget.py b/pyqtgraph/widgets/LayoutWidget.py new file mode 100644 index 00000000..f567ad74 --- /dev/null +++ b/pyqtgraph/widgets/LayoutWidget.py @@ -0,0 +1,101 @@ +from pyqtgraph.Qt import QtGui, QtCore + +__all__ = ['LayoutWidget'] +class LayoutWidget(QtGui.QWidget): + """ + Convenience class used for laying out QWidgets in a grid. + (It's just a little less effort to use than QGridLayout) + """ + + def __init__(self, parent=None): + QtGui.QWidget.__init__(self, parent) + self.layout = QtGui.QGridLayout() + self.setLayout(self.layout) + self.items = {} + self.rows = {} + self.currentRow = 0 + self.currentCol = 0 + + def nextRow(self): + """Advance to next row for automatic widget placement""" + self.currentRow += 1 + self.currentCol = 0 + + def nextColumn(self, colspan=1): + """Advance to next column, while returning the current column number + (generally only for internal use--called by addWidget)""" + self.currentCol += colspan + return self.currentCol-colspan + + def nextCol(self, *args, **kargs): + """Alias of nextColumn""" + return self.nextColumn(*args, **kargs) + + + def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create a QLabel with *text* and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to QLabel(). + Returns the created widget. + """ + text = QtGui.QLabel(text, **kargs) + self.addItem(text, row, col, rowspan, colspan) + return text + + def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create an empty LayoutWidget and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to :func:`LayoutWidget.__init__ ` + Returns the created widget. + """ + layout = LayoutWidget(**kargs) + self.addItem(layout, row, col, rowspan, colspan) + return layout + + def addWidget(self, item, row=None, col=None, rowspan=1, colspan=1): + """ + Add a widget to the layout and place it in the next available cell (or in the cell specified). + """ + if row == 'next': + self.nextRow() + row = self.currentRow + elif row is None: + row = self.currentRow + + + if col is None: + col = self.nextCol(colspan) + + if row not in self.rows: + self.rows[row] = {} + self.rows[row][col] = item + self.items[item] = (row, col) + + self.layout.addWidget(item, row, col, rowspan, colspan) + + def getWidget(self, row, col): + """Return the widget in (*row*, *col*)""" + return self.row[row][col] + + #def itemIndex(self, item): + #for i in range(self.layout.count()): + #if self.layout.itemAt(i).graphicsItem() is item: + #return i + #raise Exception("Could not determine index of item " + str(item)) + + #def removeItem(self, item): + #"""Remove *item* from the layout.""" + #ind = self.itemIndex(item) + #self.layout.removeAt(ind) + #self.scene().removeItem(item) + #r,c = self.items[item] + #del self.items[item] + #del self.rows[r][c] + #self.update() + + #def clear(self): + #items = [] + #for i in list(self.items.keys()): + #self.removeItem(i) + + diff --git a/pyqtgraph/widgets/MatplotlibWidget.py b/pyqtgraph/widgets/MatplotlibWidget.py new file mode 100644 index 00000000..6a22c973 --- /dev/null +++ b/pyqtgraph/widgets/MatplotlibWidget.py @@ -0,0 +1,41 @@ +from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE +import matplotlib + +if USE_PYSIDE: + matplotlib.rcParams['backend.qt4']='PySide' + +from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar +from matplotlib.figure import Figure + +class MatplotlibWidget(QtGui.QWidget): + """ + Implements a Matplotlib figure inside a QWidget. + Use getFigure() and redraw() to interact with matplotlib. + + Example:: + + mw = MatplotlibWidget() + subplot = mw.getFigure().add_subplot(111) + subplot.plot(x,y) + mw.draw() + """ + + def __init__(self, size=(5.0, 4.0), dpi=100): + QtGui.QWidget.__init__(self) + self.fig = Figure(size, dpi=dpi) + self.canvas = FigureCanvas(self.fig) + self.canvas.setParent(self) + self.toolbar = NavigationToolbar(self.canvas, self) + + self.vbox = QtGui.QVBoxLayout() + self.vbox.addWidget(self.toolbar) + self.vbox.addWidget(self.canvas) + + self.setLayout(self.vbox) + + def getFigure(self): + return self.fig + + def draw(self): + self.canvas.draw() diff --git a/pyqtgraph/widgets/MultiPlotWidget.py b/pyqtgraph/widgets/MultiPlotWidget.py new file mode 100644 index 00000000..400bee92 --- /dev/null +++ b/pyqtgraph/widgets/MultiPlotWidget.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +""" +MultiPlotWidget.py - Convenience class--GraphicsView widget displaying a MultiPlotItem +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from .GraphicsView import GraphicsView +import pyqtgraph.graphicsItems.MultiPlotItem as MultiPlotItem + +__all__ = ['MultiPlotWidget'] +class MultiPlotWidget(GraphicsView): + """Widget implementing a graphicsView with a single PlotItem inside.""" + def __init__(self, parent=None): + GraphicsView.__init__(self, parent) + self.enableMouse(False) + self.mPlotItem = MultiPlotItem.MultiPlotItem() + self.setCentralItem(self.mPlotItem) + ## Explicitly wrap methods from mPlotItem + #for m in ['setData']: + #setattr(self, m, getattr(self.mPlotItem, m)) + + def __getattr__(self, attr): ## implicitly wrap methods from plotItem + if hasattr(self.mPlotItem, attr): + m = getattr(self.mPlotItem, attr) + if hasattr(m, '__call__'): + return m + raise NameError(attr) + + def widgetGroupInterface(self): + return (None, MultiPlotWidget.saveState, MultiPlotWidget.restoreState) + + def saveState(self): + return {} + #return self.plotItem.saveState() + + def restoreState(self, state): + pass + #return self.plotItem.restoreState(state) + + def close(self): + self.mPlotItem.close() + self.mPlotItem = None + self.setParent(None) + GraphicsView.close(self) \ No newline at end of file diff --git a/pyqtgraph/widgets/PathButton.py b/pyqtgraph/widgets/PathButton.py new file mode 100644 index 00000000..7950a53d --- /dev/null +++ b/pyqtgraph/widgets/PathButton.py @@ -0,0 +1,50 @@ +from pyqtgraph.Qt import QtGui, QtCore +import pyqtgraph as pg +__all__ = ['PathButton'] + + +class PathButton(QtGui.QPushButton): + """Simple PushButton extension which paints a QPainterPath on its face""" + def __init__(self, parent=None, path=None, pen='default', brush=None, size=(30,30)): + QtGui.QPushButton.__init__(self, parent) + self.path = None + if pen == 'default': + pen = 'k' + self.setPen(pen) + self.setBrush(brush) + if path is not None: + self.setPath(path) + if size is not None: + self.setFixedWidth(size[0]) + self.setFixedHeight(size[1]) + + + def setBrush(self, brush): + self.brush = pg.mkBrush(brush) + + def setPen(self, pen): + self.pen = pg.mkPen(pen) + + def setPath(self, path): + self.path = path + self.update() + + def paintEvent(self, ev): + QtGui.QPushButton.paintEvent(self, ev) + margin = 7 + geom = QtCore.QRectF(0, 0, self.width(), self.height()).adjusted(margin, margin, -margin, -margin) + rect = self.path.boundingRect() + scale = min(geom.width() / float(rect.width()), geom.height() / float(rect.height())) + + p = QtGui.QPainter(self) + p.setRenderHint(p.Antialiasing) + p.translate(geom.center()) + p.scale(scale, scale) + p.translate(-rect.center()) + p.setPen(self.pen) + p.setBrush(self.brush) + p.drawPath(self.path) + p.end() + + + \ No newline at end of file diff --git a/pyqtgraph/widgets/PlotWidget.py b/pyqtgraph/widgets/PlotWidget.py new file mode 100644 index 00000000..7b3c685c --- /dev/null +++ b/pyqtgraph/widgets/PlotWidget.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +""" +PlotWidget.py - Convenience class--GraphicsView widget displaying a single PlotItem +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from pyqtgraph.Qt import QtCore, QtGui +from .GraphicsView import * +from pyqtgraph.graphicsItems.PlotItem import * + +__all__ = ['PlotWidget'] +class PlotWidget(GraphicsView): + + #sigRangeChanged = QtCore.Signal(object, object) ## already defined in GraphicsView + + """ + :class:`GraphicsView ` widget with a single + :class:`PlotItem ` inside. + + The following methods are wrapped directly from PlotItem: + :func:`addItem `, + :func:`removeItem `, + :func:`clear `, + :func:`setXRange `, + :func:`setYRange `, + :func:`setRange `, + :func:`autoRange `, + :func:`setXLink `, + :func:`setYLink `, + :func:`viewRect `, + :func:`setMouseEnabled `, + :func:`enableAutoRange `, + :func:`disableAutoRange `, + :func:`setAspectLocked `, + :func:`register `, + :func:`unregister ` + + + For all + other methods, use :func:`getPlotItem `. + """ + def __init__(self, parent=None, background='default', **kargs): + """When initializing PlotWidget, *parent* and *background* are passed to + :func:`GraphicsWidget.__init__() ` + and all others are passed + to :func:`PlotItem.__init__() `.""" + GraphicsView.__init__(self, parent, background=background) + self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.enableMouse(False) + self.plotItem = PlotItem(**kargs) + self.setCentralItem(self.plotItem) + ## Explicitly wrap methods from plotItem + ## NOTE: If you change this list, update the documentation above as well. + for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setXRange', 'setYRange', 'setRange', 'setAspectLocked', 'setMouseEnabled', 'setXLink', 'setYLink', 'enableAutoRange', 'disableAutoRange', 'register', 'unregister', 'viewRect']: + setattr(self, m, getattr(self.plotItem, m)) + #QtCore.QObject.connect(self.plotItem, QtCore.SIGNAL('viewChanged'), self.viewChanged) + self.plotItem.sigRangeChanged.connect(self.viewRangeChanged) + + def close(self): + self.plotItem.close() + self.plotItem = None + #self.scene().clear() + #self.mPlotItem.close() + self.setParent(None) + GraphicsView.close(self) + + def __getattr__(self, attr): ## implicitly wrap methods from plotItem + if hasattr(self.plotItem, attr): + m = getattr(self.plotItem, attr) + if hasattr(m, '__call__'): + return m + raise NameError(attr) + + def viewRangeChanged(self, view, range): + #self.emit(QtCore.SIGNAL('viewChanged'), *args) + self.sigRangeChanged.emit(self, range) + + def widgetGroupInterface(self): + return (None, PlotWidget.saveState, PlotWidget.restoreState) + + def saveState(self): + return self.plotItem.saveState() + + def restoreState(self, state): + return self.plotItem.restoreState(state) + + def getPlotItem(self): + """Return the PlotItem contained within.""" + return self.plotItem + + + \ No newline at end of file diff --git a/pyqtgraph/widgets/ProgressDialog.py b/pyqtgraph/widgets/ProgressDialog.py new file mode 100644 index 00000000..0f55e227 --- /dev/null +++ b/pyqtgraph/widgets/ProgressDialog.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore + +__all__ = ['ProgressDialog'] +class ProgressDialog(QtGui.QProgressDialog): + """ + Extends QProgressDialog for use in 'with' statements. + + Example:: + + with ProgressDialog("Processing..", minVal, maxVal) as dlg: + # do stuff + dlg.setValue(i) ## could also use dlg += 1 + if dlg.wasCanceled(): + raise Exception("Processing canceled by user") + """ + def __init__(self, labelText, minimum=0, maximum=100, cancelText='Cancel', parent=None, wait=250, busyCursor=False, disable=False): + """ + ============== ================================================================ + **Arguments:** + labelText (required) + cancelText Text to display on cancel button, or None to disable it. + minimum + maximum + parent + wait Length of time (im ms) to wait before displaying dialog + busyCursor If True, show busy cursor until dialog finishes + disable If True, the progress dialog will not be displayed + and calls to wasCanceled() will always return False. + If ProgressDialog is entered from a non-gui thread, it will + always be disabled. + ============== ================================================================ + """ + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + self.disabled = disable or (not isGuiThread) + if self.disabled: + return + + noCancel = False + if cancelText is None: + cancelText = '' + noCancel = True + + self.busyCursor = busyCursor + + QtGui.QProgressDialog.__init__(self, labelText, cancelText, minimum, maximum, parent) + self.setMinimumDuration(wait) + self.setWindowModality(QtCore.Qt.WindowModal) + self.setValue(self.minimum()) + if noCancel: + self.setCancelButton(None) + + + def __enter__(self): + if self.disabled: + return self + if self.busyCursor: + QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) + return self + + def __exit__(self, exType, exValue, exTrace): + if self.disabled: + return + if self.busyCursor: + QtGui.QApplication.restoreOverrideCursor() + self.setValue(self.maximum()) + + def __iadd__(self, val): + """Use inplace-addition operator for easy incrementing.""" + if self.disabled: + return self + self.setValue(self.value()+val) + return self + + + ## wrap all other functions to make sure they aren't being called from non-gui threads + + def setValue(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setValue(self, val) + + def setLabelText(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setLabelText(self, val) + + def setMaximum(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setMaximum(self, val) + + def setMinimum(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setMinimum(self, val) + + def wasCanceled(self): + if self.disabled: + return False + return QtGui.QProgressDialog.wasCanceled(self) + + def maximum(self): + if self.disabled: + return 0 + return QtGui.QProgressDialog.maximum(self) + + def minimum(self): + if self.disabled: + return 0 + return QtGui.QProgressDialog.minimum(self) + diff --git a/pyqtgraph/widgets/RawImageWidget.py b/pyqtgraph/widgets/RawImageWidget.py new file mode 100644 index 00000000..517f4f99 --- /dev/null +++ b/pyqtgraph/widgets/RawImageWidget.py @@ -0,0 +1,140 @@ +from pyqtgraph.Qt import QtCore, QtGui +try: + from pyqtgraph.Qt import QtOpenGL + from OpenGL.GL import * + HAVE_OPENGL = True +except ImportError: + HAVE_OPENGL = False + +import pyqtgraph.functions as fn +import numpy as np + +class RawImageWidget(QtGui.QWidget): + """ + Widget optimized for very fast video display. + Generally using an ImageItem inside GraphicsView is fast enough. + On some systems this may provide faster video. See the VideoSpeedTest example for benchmarking. + """ + def __init__(self, parent=None, scaled=False): + """ + Setting scaled=True will cause the entire image to be displayed within the boundaries of the widget. This also greatly reduces the speed at which it will draw frames. + """ + QtGui.QWidget.__init__(self, parent=None) + self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)) + self.scaled = scaled + self.opts = None + self.image = None + + def setImage(self, img, *args, **kargs): + """ + img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). + Extra arguments are sent to functions.makeARGB + """ + self.opts = (img, args, kargs) + self.image = None + self.update() + + def paintEvent(self, ev): + if self.opts is None: + return + if self.image is None: + argb, alpha = fn.makeARGB(self.opts[0], *self.opts[1], **self.opts[2]) + self.image = fn.makeQImage(argb, alpha) + self.opts = () + #if self.pixmap is None: + #self.pixmap = QtGui.QPixmap.fromImage(self.image) + p = QtGui.QPainter(self) + if self.scaled: + rect = self.rect() + ar = rect.width() / float(rect.height()) + imar = self.image.width() / float(self.image.height()) + if ar > imar: + rect.setWidth(int(rect.width() * imar/ar)) + else: + rect.setHeight(int(rect.height() * ar/imar)) + + p.drawImage(rect, self.image) + else: + p.drawImage(QtCore.QPointF(), self.image) + #p.drawPixmap(self.rect(), self.pixmap) + p.end() + +if HAVE_OPENGL: + class RawImageGLWidget(QtOpenGL.QGLWidget): + """ + Similar to RawImageWidget, but uses a GL widget to do all drawing. + Perfomance varies between platforms; see examples/VideoSpeedTest for benchmarking. + """ + def __init__(self, parent=None, scaled=False): + QtOpenGL.QGLWidget.__init__(self, parent=None) + self.scaled = scaled + self.image = None + self.uploaded = False + self.smooth = False + self.opts = None + + def setImage(self, img, *args, **kargs): + """ + img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). + Extra arguments are sent to functions.makeARGB + """ + self.opts = (img, args, kargs) + self.image = None + self.uploaded = False + self.update() + + def initializeGL(self): + self.texture = glGenTextures(1) + + def uploadTexture(self): + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.texture) + if self.smooth: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + else: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) + #glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) + shape = self.image.shape + + ### Test texture dimensions first + #glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) + #if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: + #raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.image.transpose((1,0,2))) + glDisable(GL_TEXTURE_2D) + + def paintGL(self): + if self.image is None: + if self.opts is None: + return + img, args, kwds = self.opts + kwds['useRGBA'] = True + self.image, alpha = fn.makeARGB(img, *args, **kwds) + + if not self.uploaded: + self.uploadTexture() + + glViewport(0, 0, self.width(), self.height()) + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.texture) + glColor4f(1,1,1,1) + + glBegin(GL_QUADS) + glTexCoord2f(0,0) + glVertex3f(-1,-1,0) + glTexCoord2f(1,0) + glVertex3f(1, -1, 0) + glTexCoord2f(1,1) + glVertex3f(1, 1, 0) + glTexCoord2f(0,1) + glVertex3f(-1, 1, 0) + glEnd() + glDisable(GL_TEXTURE_3D) + + + diff --git a/pyqtgraph/widgets/RemoteGraphicsView.py b/pyqtgraph/widgets/RemoteGraphicsView.py new file mode 100644 index 00000000..d44fd1c3 --- /dev/null +++ b/pyqtgraph/widgets/RemoteGraphicsView.py @@ -0,0 +1,261 @@ +from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE +if not USE_PYSIDE: + import sip +import pyqtgraph.multiprocess as mp +import pyqtgraph as pg +from .GraphicsView import GraphicsView +import numpy as np +import mmap, tempfile, ctypes, atexit, sys, random + +__all__ = ['RemoteGraphicsView'] + +class RemoteGraphicsView(QtGui.QWidget): + """ + Replacement for GraphicsView that does all scene management and rendering on a remote process, + while displaying on the local widget. + + GraphicsItems must be created by proxy to the remote process. + + """ + def __init__(self, parent=None, *args, **kwds): + """ + The keyword arguments 'useOpenGL' and 'backgound', if specified, are passed to the remote + GraphicsView.__init__(). All other keyword arguments are passed to multiprocess.QtProcess.__init__(). + """ + self._img = None + self._imgReq = None + self._sizeHint = (640,480) ## no clue why this is needed, but it seems to be the default sizeHint for GraphicsView. + ## without it, the widget will not compete for space against another GraphicsView. + QtGui.QWidget.__init__(self) + + # separate local keyword arguments from remote. + remoteKwds = {} + for kwd in ['useOpenGL', 'background']: + if kwd in kwds: + remoteKwds[kwd] = kwds.pop(kwd) + + self._proc = mp.QtProcess(**kwds) + self.pg = self._proc._import('pyqtgraph') + self.pg.setConfigOptions(**self.pg.CONFIG_OPTIONS) + rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView') + self._view = rpgRemote.Renderer(*args, **remoteKwds) + self._view._setProxyOptions(deferGetattr=True) + + self.setFocusPolicy(QtCore.Qt.StrongFocus) + self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.setMouseTracking(True) + self.shm = None + shmFileName = self._view.shmFileName() + if sys.platform.startswith('win'): + self.shmtag = shmFileName + else: + self.shmFile = open(shmFileName, 'r') + + self._view.sceneRendered.connect(mp.proxy(self.remoteSceneChanged)) #, callSync='off')) + ## Note: we need synchronous signals + ## even though there is no return value-- + ## this informs the renderer that it is + ## safe to begin rendering again. + + for method in ['scene', 'setCentralItem']: + setattr(self, method, getattr(self._view, method)) + + def resizeEvent(self, ev): + ret = QtGui.QWidget.resizeEvent(self, ev) + self._view.resize(self.size(), _callSync='off') + return ret + + def sizeHint(self): + return QtCore.QSize(*self._sizeHint) + + def remoteSceneChanged(self, data): + w, h, size, newfile = data + #self._sizeHint = (whint, hhint) + if self.shm is None or self.shm.size != size: + if self.shm is not None: + self.shm.close() + if sys.platform.startswith('win'): + self.shmtag = newfile ## on windows, we create a new tag for every resize + self.shm = mmap.mmap(-1, size, self.shmtag) ## can't use tmpfile on windows because the file can only be opened once. + else: + self.shm = mmap.mmap(self.shmFile.fileno(), size, mmap.MAP_SHARED, mmap.PROT_READ) + self.shm.seek(0) + data = self.shm.read(w*h*4) + self._img = QtGui.QImage(data, w, h, QtGui.QImage.Format_ARGB32) + self._img.data = data # data must be kept alive or PySide 1.2.1 (and probably earlier) will crash. + self.update() + + def paintEvent(self, ev): + if self._img is None: + return + p = QtGui.QPainter(self) + p.drawImage(self.rect(), self._img, QtCore.QRect(0, 0, self._img.width(), self._img.height())) + p.end() + + def mousePressEvent(self, ev): + self._view.mousePressEvent(int(ev.type()), ev.pos(), ev.globalPos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') + ev.accept() + return QtGui.QWidget.mousePressEvent(self, ev) + + def mouseReleaseEvent(self, ev): + self._view.mouseReleaseEvent(int(ev.type()), ev.pos(), ev.globalPos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') + ev.accept() + return QtGui.QWidget.mouseReleaseEvent(self, ev) + + def mouseMoveEvent(self, ev): + self._view.mouseMoveEvent(int(ev.type()), ev.pos(), ev.globalPos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') + ev.accept() + return QtGui.QWidget.mouseMoveEvent(self, ev) + + def wheelEvent(self, ev): + self._view.wheelEvent(ev.pos(), ev.globalPos(), ev.delta(), int(ev.buttons()), int(ev.modifiers()), ev.orientation(), _callSync='off') + ev.accept() + return QtGui.QWidget.wheelEvent(self, ev) + + def keyEvent(self, ev): + if self._view.keyEvent(int(ev.type()), int(ev.modifiers()), text, autorep, count): + ev.accept() + return QtGui.QWidget.keyEvent(self, ev) + + def enterEvent(self, ev): + self._view.enterEvent(int(ev.type()), _callSync='off') + return QtGui.QWidget.enterEvent(self, ev) + + def leaveEvent(self, ev): + self._view.leaveEvent(int(ev.type()), _callSync='off') + return QtGui.QWidget.leaveEvent(self, ev) + + def remoteProcess(self): + """Return the remote process handle. (see multiprocess.remoteproxy.RemoteEventHandler)""" + return self._proc + + def close(self): + """Close the remote process. After this call, the widget will no longer be updated.""" + self._proc.close() + + +class Renderer(GraphicsView): + ## Created by the remote process to handle render requests + + sceneRendered = QtCore.Signal(object) + + def __init__(self, *args, **kwds): + ## Create shared memory for rendered image + #pg.dbg(namespace={'r': self}) + if sys.platform.startswith('win'): + self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)]) + self.shm = mmap.mmap(-1, mmap.PAGESIZE, self.shmtag) # use anonymous mmap on windows + else: + self.shmFile = tempfile.NamedTemporaryFile(prefix='pyqtgraph_shmem_') + self.shmFile.write(b'\x00' * (mmap.PAGESIZE+1)) + fd = self.shmFile.fileno() + self.shm = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE) + atexit.register(self.close) + + GraphicsView.__init__(self, *args, **kwds) + self.scene().changed.connect(self.update) + self.img = None + self.renderTimer = QtCore.QTimer() + self.renderTimer.timeout.connect(self.renderView) + self.renderTimer.start(16) + + def close(self): + self.shm.close() + if not sys.platform.startswith('win'): + self.shmFile.close() + + def shmFileName(self): + if sys.platform.startswith('win'): + return self.shmtag + else: + return self.shmFile.name + + def update(self): + self.img = None + return GraphicsView.update(self) + + def resize(self, size): + oldSize = self.size() + GraphicsView.resize(self, size) + self.resizeEvent(QtGui.QResizeEvent(size, oldSize)) + self.update() + + def renderView(self): + if self.img is None: + ## make sure shm is large enough and get its address + if self.width() == 0 or self.height() == 0: + return + size = self.width() * self.height() * 4 + if size > self.shm.size(): + if sys.platform.startswith('win'): + ## windows says "WindowsError: [Error 87] the parameter is incorrect" if we try to resize the mmap + self.shm.close() + ## it also says (sometimes) 'access is denied' if we try to reuse the tag. + self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)]) + self.shm = mmap.mmap(-1, size, self.shmtag) + else: + self.shm.resize(size) + + ## render the scene directly to shared memory + if USE_PYSIDE: + ch = ctypes.c_char.from_buffer(self.shm, 0) + #ch = ctypes.c_char_p(address) + self.img = QtGui.QImage(ch, self.width(), self.height(), QtGui.QImage.Format_ARGB32) + else: + address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0)) + + # different versions of pyqt have different requirements here.. + try: + self.img = QtGui.QImage(sip.voidptr(address), self.width(), self.height(), QtGui.QImage.Format_ARGB32) + except TypeError: + try: + self.img = QtGui.QImage(memoryview(buffer(self.shm)), self.width(), self.height(), QtGui.QImage.Format_ARGB32) + except TypeError: + # Works on PyQt 4.9.6 + self.img = QtGui.QImage(address, self.width(), self.height(), QtGui.QImage.Format_ARGB32) + self.img.fill(0xffffffff) + p = QtGui.QPainter(self.img) + self.render(p, self.viewRect(), self.rect()) + p.end() + self.sceneRendered.emit((self.width(), self.height(), self.shm.size(), self.shmFileName())) + + def mousePressEvent(self, typ, pos, gpos, btn, btns, mods): + typ = QtCore.QEvent.Type(typ) + btn = QtCore.Qt.MouseButton(btn) + btns = QtCore.Qt.MouseButtons(btns) + mods = QtCore.Qt.KeyboardModifiers(mods) + return GraphicsView.mousePressEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) + + def mouseMoveEvent(self, typ, pos, gpos, btn, btns, mods): + typ = QtCore.QEvent.Type(typ) + btn = QtCore.Qt.MouseButton(btn) + btns = QtCore.Qt.MouseButtons(btns) + mods = QtCore.Qt.KeyboardModifiers(mods) + return GraphicsView.mouseMoveEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) + + def mouseReleaseEvent(self, typ, pos, gpos, btn, btns, mods): + typ = QtCore.QEvent.Type(typ) + btn = QtCore.Qt.MouseButton(btn) + btns = QtCore.Qt.MouseButtons(btns) + mods = QtCore.Qt.KeyboardModifiers(mods) + return GraphicsView.mouseReleaseEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) + + def wheelEvent(self, pos, gpos, d, btns, mods, ori): + btns = QtCore.Qt.MouseButtons(btns) + mods = QtCore.Qt.KeyboardModifiers(mods) + return GraphicsView.wheelEvent(self, QtGui.QWheelEvent(pos, gpos, d, btns, mods, ori)) + + def keyEvent(self, typ, mods, text, autorep, count): + typ = QtCore.QEvent.Type(typ) + mods = QtCore.Qt.KeyboardModifiers(mods) + GraphicsView.keyEvent(self, QtGui.QKeyEvent(typ, mods, text, autorep, count)) + return ev.accepted() + + def enterEvent(self, typ): + ev = QtCore.QEvent(QtCore.QEvent.Type(typ)) + return GraphicsView.enterEvent(self, ev) + + def leaveEvent(self, typ): + ev = QtCore.QEvent(QtCore.QEvent.Type(typ)) + return GraphicsView.leaveEvent(self, ev) + diff --git a/pyqtgraph/widgets/ScatterPlotWidget.py b/pyqtgraph/widgets/ScatterPlotWidget.py new file mode 100644 index 00000000..e9e24dd7 --- /dev/null +++ b/pyqtgraph/widgets/ScatterPlotWidget.py @@ -0,0 +1,216 @@ +from pyqtgraph.Qt import QtGui, QtCore +from .PlotWidget import PlotWidget +from .DataFilterWidget import DataFilterParameter +from .ColorMapWidget import ColorMapParameter +import pyqtgraph.parametertree as ptree +import pyqtgraph.functions as fn +import numpy as np +from pyqtgraph.pgcollections import OrderedDict +import pyqtgraph as pg + +__all__ = ['ScatterPlotWidget'] + +class ScatterPlotWidget(QtGui.QSplitter): + """ + Given a record array, display a scatter plot of a specific set of data. + This widget includes controls for selecting the columns to plot, + filtering data, and determining symbol color and shape. This widget allows + the user to explore relationships between columns in a record array. + + The widget consists of four components: + + 1) A list of column names from which the user may select 1 or 2 columns + to plot. If one column is selected, the data for that column will be + plotted in a histogram-like manner by using :func:`pseudoScatter() + `. If two columns are selected, then the + scatter plot will be generated with x determined by the first column + that was selected and y by the second. + 2) A DataFilter that allows the user to select a subset of the data by + specifying multiple selection criteria. + 3) A ColorMap that allows the user to determine how points are colored by + specifying multiple criteria. + 4) A PlotWidget for displaying the data. + """ + def __init__(self, parent=None): + QtGui.QSplitter.__init__(self, QtCore.Qt.Horizontal) + self.ctrlPanel = QtGui.QSplitter(QtCore.Qt.Vertical) + self.addWidget(self.ctrlPanel) + self.fieldList = QtGui.QListWidget() + self.fieldList.setSelectionMode(self.fieldList.ExtendedSelection) + self.ptree = ptree.ParameterTree(showHeader=False) + self.filter = DataFilterParameter() + self.colorMap = ColorMapParameter() + self.params = ptree.Parameter.create(name='params', type='group', children=[self.filter, self.colorMap]) + self.ptree.setParameters(self.params, showTop=False) + + self.plot = PlotWidget() + self.ctrlPanel.addWidget(self.fieldList) + self.ctrlPanel.addWidget(self.ptree) + self.addWidget(self.plot) + + bg = pg.mkColor(pg.getConfigOption('background')) + bg.setAlpha(150) + self.filterText = pg.TextItem(border=pg.getConfigOption('foreground'), color=bg) + self.filterText.setPos(60,20) + self.filterText.setParentItem(self.plot.plotItem) + + self.data = None + self.mouseOverField = None + self.scatterPlot = None + self.style = dict(pen=None, symbol='o') + + self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged) + self.filter.sigFilterChanged.connect(self.filterChanged) + self.colorMap.sigColorMapChanged.connect(self.updatePlot) + + def setFields(self, fields, mouseOverField=None): + """ + Set the list of field names/units to be processed. + + The format of *fields* is the same as used by + :func:`ColorMapWidget.setFields ` + """ + self.fields = OrderedDict(fields) + self.mouseOverField = mouseOverField + self.fieldList.clear() + for f,opts in fields: + item = QtGui.QListWidgetItem(f) + item.opts = opts + item = self.fieldList.addItem(item) + self.filter.setFields(fields) + self.colorMap.setFields(fields) + + def setData(self, data): + """ + Set the data to be processed and displayed. + Argument must be a numpy record array. + """ + self.data = data + self.filtered = None + self.updatePlot() + + def fieldSelectionChanged(self): + sel = self.fieldList.selectedItems() + if len(sel) > 2: + self.fieldList.blockSignals(True) + try: + for item in sel[1:-1]: + item.setSelected(False) + finally: + self.fieldList.blockSignals(False) + + self.updatePlot() + + def filterChanged(self, f): + self.filtered = None + self.updatePlot() + desc = self.filter.describe() + if len(desc) == 0: + self.filterText.setVisible(False) + else: + self.filterText.setText('\n'.join(desc)) + self.filterText.setVisible(True) + + + def updatePlot(self): + self.plot.clear() + if self.data is None: + return + + if self.filtered is None: + self.filtered = self.filter.filterData(self.data) + data = self.filtered + if len(data) == 0: + return + + colors = np.array([fn.mkBrush(*x) for x in self.colorMap.map(data)]) + + style = self.style.copy() + + ## Look up selected columns and units + sel = list([str(item.text()) for item in self.fieldList.selectedItems()]) + units = list([item.opts.get('units', '') for item in self.fieldList.selectedItems()]) + if len(sel) == 0: + self.plot.setTitle('') + return + + + if len(sel) == 1: + self.plot.setLabels(left=('N', ''), bottom=(sel[0], units[0]), title='') + if len(data) == 0: + return + #x = data[sel[0]] + #y = None + xy = [data[sel[0]], None] + elif len(sel) == 2: + self.plot.setLabels(left=(sel[1],units[1]), bottom=(sel[0],units[0])) + if len(data) == 0: + return + + xy = [data[sel[0]], data[sel[1]]] + #xydata = [] + #for ax in [0,1]: + #d = data[sel[ax]] + ### scatter catecorical values just a bit so they show up better in the scatter plot. + ##if sel[ax] in ['MorphologyBSMean', 'MorphologyTDMean', 'FIType']: + ##d += np.random.normal(size=len(cells), scale=0.1) + + #xydata.append(d) + #x,y = xydata + + ## convert enum-type fields to float, set axis labels + enum = [False, False] + for i in [0,1]: + axis = self.plot.getAxis(['bottom', 'left'][i]) + if xy[i] is not None and (self.fields[sel[i]].get('mode', None) == 'enum' or xy[i].dtype.kind in ('S', 'O')): + vals = self.fields[sel[i]].get('values', list(set(xy[i]))) + xy[i] = np.array([vals.index(x) if x in vals else len(vals) for x in xy[i]], dtype=float) + axis.setTicks([list(enumerate(vals))]) + enum[i] = True + else: + axis.setTicks(None) # reset to automatic ticking + + ## mask out any nan values + mask = np.ones(len(xy[0]), dtype=bool) + if xy[0].dtype.kind == 'f': + mask &= ~np.isnan(xy[0]) + if xy[1] is not None and xy[1].dtype.kind == 'f': + mask &= ~np.isnan(xy[1]) + + xy[0] = xy[0][mask] + style['symbolBrush'] = colors[mask] + + ## Scatter y-values for a histogram-like appearance + if xy[1] is None: + ## column scatter plot + xy[1] = fn.pseudoScatter(xy[0]) + else: + ## beeswarm plots + xy[1] = xy[1][mask] + for ax in [0,1]: + if not enum[ax]: + continue + imax = int(xy[ax].max()) if len(xy[ax]) > 0 else 0 + for i in range(imax+1): + keymask = xy[ax] == i + scatter = pg.pseudoScatter(xy[1-ax][keymask], bidir=True) + if len(scatter) == 0: + continue + smax = np.abs(scatter).max() + if smax != 0: + scatter *= 0.2 / smax + xy[ax][keymask] += scatter + + if self.scatterPlot is not None: + try: + self.scatterPlot.sigPointsClicked.disconnect(self.plotClicked) + except: + pass + self.scatterPlot = self.plot.plot(xy[0], xy[1], data=data[mask], **style) + self.scatterPlot.sigPointsClicked.connect(self.plotClicked) + + + def plotClicked(self, plot, points): + pass + + diff --git a/pyqtgraph/widgets/SpinBox.py b/pyqtgraph/widgets/SpinBox.py new file mode 100644 index 00000000..57e4f1ed --- /dev/null +++ b/pyqtgraph/widgets/SpinBox.py @@ -0,0 +1,503 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.python2_3 import asUnicode +from pyqtgraph.SignalProxy import SignalProxy + +import pyqtgraph.functions as fn +from math import log +from decimal import Decimal as D ## Use decimal to avoid accumulating floating-point errors +from decimal import * +import weakref + +__all__ = ['SpinBox'] +class SpinBox(QtGui.QAbstractSpinBox): + """ + **Bases:** QtGui.QAbstractSpinBox + + QSpinBox widget on steroids. Allows selection of numerical value, with extra features: + + - SI prefix notation (eg, automatically display "300 mV" instead of "0.003 V") + - Float values with linear and decimal stepping (1-9, 10-90, 100-900, etc.) + - Option for unbounded values + - Delayed signals (allows multiple rapid changes with only one change signal) + + ============================= ============================================== + **Signals:** + valueChanged(value) Same as QSpinBox; emitted every time the value + has changed. + sigValueChanged(self) Emitted when value has changed, but also combines + multiple rapid changes into one signal (eg, + when rolling the mouse wheel). + sigValueChanging(self, value) Emitted immediately for all value changes. + ============================= ============================================== + """ + + ## There's a PyQt bug that leaks a reference to the + ## QLineEdit returned from QAbstractSpinBox.lineEdit() + ## This makes it possible to crash the entire program + ## by making accesses to the LineEdit after the spinBox has been deleted. + ## I have no idea how to get around this.. + + + valueChanged = QtCore.Signal(object) # (value) for compatibility with QSpinBox + sigValueChanged = QtCore.Signal(object) # (self) + sigValueChanging = QtCore.Signal(object, object) # (self, value) sent immediately; no delay. + + def __init__(self, parent=None, value=0.0, **kwargs): + """ + ============== ======================================================================== + **Arguments:** + parent Sets the parent widget for this SpinBox (optional) + value (float/int) initial value + bounds (min,max) Minimum and maximum values allowed in the SpinBox. + Either may be None to leave the value unbounded. + suffix (str) suffix (units) to display after the numerical value + siPrefix (bool) If True, then an SI prefix is automatically prepended + to the units and the value is scaled accordingly. For example, + if value=0.003 and suffix='V', then the SpinBox will display + "300 mV" (but a call to SpinBox.value will still return 0.003). + step (float) The size of a single step. This is used when clicking the up/ + down arrows, when rolling the mouse wheel, or when pressing + keyboard arrows while the widget has keyboard focus. Note that + the interpretation of this value is different when specifying + the 'dec' argument. + dec (bool) If True, then the step value will be adjusted to match + the current size of the variable (for example, a value of 15 + might step in increments of 1 whereas a value of 1500 would + step in increments of 100). In this case, the 'step' argument + is interpreted *relative* to the current value. The most common + 'step' values when dec=True are 0.1, 0.2, 0.5, and 1.0. + minStep (float) When dec=True, this specifies the minimum allowable step size. + int (bool) if True, the value is forced to integer type + decimals (int) Number of decimal values to display + ============== ======================================================================== + """ + QtGui.QAbstractSpinBox.__init__(self, parent) + self.lastValEmitted = None + self.lastText = '' + self.textValid = True ## If false, we draw a red border + self.setMinimumWidth(0) + self.setMaximumHeight(20) + self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) + self.opts = { + 'bounds': [None, None], + + ## Log scaling options #### Log mode is no longer supported. + #'step': 0.1, + #'minStep': 0.001, + #'log': True, + #'dec': False, + + ## decimal scaling option - example + #'step': 0.1, + #'minStep': .001, + #'log': False, + #'dec': True, + + ## normal arithmetic step + 'step': D('0.01'), ## if 'dec' is false, the spinBox steps by 'step' every time + ## if 'dec' is True, the step size is relative to the value + ## 'step' needs to be an integral divisor of ten, ie 'step'*n=10 for some integer value of n (but only if dec is True) + 'log': False, + 'dec': False, ## if true, does decimal stepping. ie from 1-10 it steps by 'step', from 10 to 100 it steps by 10*'step', etc. + ## if true, minStep must be set in order to cross zero. + + + 'int': False, ## Set True to force value to be integer + + 'suffix': '', + 'siPrefix': False, ## Set to True to display numbers with SI prefix (ie, 100pA instead of 1e-10A) + + 'delay': 0.3, ## delay sending wheel update signals for 300ms + + 'delayUntilEditFinished': True, ## do not send signals until text editing has finished + + ## for compatibility with QDoubleSpinBox and QSpinBox + 'decimals': 2, + + } + + self.decOpts = ['step', 'minStep'] + + self.val = D(asUnicode(value)) ## Value is precise decimal. Ordinary math not allowed. + self.updateText() + self.skipValidate = False + self.setCorrectionMode(self.CorrectToPreviousValue) + self.setKeyboardTracking(False) + self.setOpts(**kwargs) + + + self.editingFinished.connect(self.editingFinishedEvent) + self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange, delay=self.opts['delay']) + + def event(self, ev): + ret = QtGui.QAbstractSpinBox.event(self, ev) + if ev.type() == QtCore.QEvent.KeyPress and ev.key() == QtCore.Qt.Key_Return: + ret = True ## For some reason, spinbox pretends to ignore return key press + return ret + + ##lots of config options, just gonna stuff 'em all in here rather than do the get/set crap. + def setOpts(self, **opts): + """ + Changes the behavior of the SpinBox. Accepts most of the arguments + allowed in :func:`__init__ `. + + """ + #print opts + for k in opts: + if k == 'bounds': + #print opts[k] + self.setMinimum(opts[k][0], update=False) + self.setMaximum(opts[k][1], update=False) + #for i in [0,1]: + #if opts[k][i] is None: + #self.opts[k][i] = None + #else: + #self.opts[k][i] = D(unicode(opts[k][i])) + elif k in ['step', 'minStep']: + self.opts[k] = D(asUnicode(opts[k])) + elif k == 'value': + pass ## don't set value until bounds have been set + else: + self.opts[k] = opts[k] + if 'value' in opts: + self.setValue(opts['value']) + + ## If bounds have changed, update value to match + if 'bounds' in opts and 'value' not in opts: + self.setValue() + + ## sanity checks: + if self.opts['int']: + if 'step' in opts: + step = opts['step'] + ## not necessary.. + #if int(step) != step: + #raise Exception('Integer SpinBox must have integer step size.') + else: + self.opts['step'] = int(self.opts['step']) + + if 'minStep' in opts: + step = opts['minStep'] + if int(step) != step: + raise Exception('Integer SpinBox must have integer minStep size.') + else: + ms = int(self.opts.get('minStep', 1)) + if ms < 1: + ms = 1 + self.opts['minStep'] = ms + + if 'delay' in opts: + self.proxy.setDelay(opts['delay']) + + self.updateText() + + + + def setMaximum(self, m, update=True): + """Set the maximum allowed value (or None for no limit)""" + if m is not None: + m = D(asUnicode(m)) + self.opts['bounds'][1] = m + if update: + self.setValue() + + def setMinimum(self, m, update=True): + """Set the minimum allowed value (or None for no limit)""" + if m is not None: + m = D(asUnicode(m)) + self.opts['bounds'][0] = m + if update: + self.setValue() + + def setPrefix(self, p): + self.setOpts(prefix=p) + + def setRange(self, r0, r1): + self.setOpts(bounds = [r0,r1]) + + def setProperty(self, prop, val): + ## for QSpinBox compatibility + if prop == 'value': + #if type(val) is QtCore.QVariant: + #val = val.toDouble()[0] + self.setValue(val) + else: + print("Warning: SpinBox.setProperty('%s', ..) not supported." % prop) + + def setSuffix(self, suf): + self.setOpts(suffix=suf) + + def setSingleStep(self, step): + self.setOpts(step=step) + + def setDecimals(self, decimals): + self.setOpts(decimals=decimals) + + def value(self): + """ + Return the value of this SpinBox. + + """ + if self.opts['int']: + return int(self.val) + else: + return float(self.val) + + def setValue(self, value=None, update=True, delaySignal=False): + """ + Set the value of this spin. + If the value is out of bounds, it will be clipped to the nearest boundary. + If the spin is integer type, the value will be coerced to int. + Returns the actual value set. + + If value is None, then the current value is used (this is for resetting + the value after bounds, etc. have changed) + """ + + if value is None: + value = self.value() + + bounds = self.opts['bounds'] + if bounds[0] is not None and value < bounds[0]: + value = bounds[0] + if bounds[1] is not None and value > bounds[1]: + value = bounds[1] + + if self.opts['int']: + value = int(value) + + value = D(asUnicode(value)) + if value == self.val: + return + prev = self.val + + self.val = value + if update: + self.updateText(prev=prev) + + self.sigValueChanging.emit(self, float(self.val)) ## change will be emitted in 300ms if there are no subsequent changes. + if not delaySignal: + self.emitChanged() + + return value + + + def emitChanged(self): + self.lastValEmitted = self.val + self.valueChanged.emit(float(self.val)) + self.sigValueChanged.emit(self) + + def delayedChange(self): + try: + if self.val != self.lastValEmitted: + self.emitChanged() + except RuntimeError: + pass ## This can happen if we try to handle a delayed signal after someone else has already deleted the underlying C++ object. + + def widgetGroupInterface(self): + return (self.valueChanged, SpinBox.value, SpinBox.setValue) + + def sizeHint(self): + return QtCore.QSize(120, 0) + + + def stepEnabled(self): + return self.StepUpEnabled | self.StepDownEnabled + + #def fixup(self, *args): + #print "fixup:", args + + def stepBy(self, n): + n = D(int(n)) ## n must be integral number of steps. + s = [D(-1), D(1)][n >= 0] ## determine sign of step + val = self.val + + for i in range(int(abs(n))): + + if self.opts['log']: + raise Exception("Log mode no longer supported.") + # step = abs(val) * self.opts['step'] + # if 'minStep' in self.opts: + # step = max(step, self.opts['minStep']) + # val += step * s + if self.opts['dec']: + if val == 0: + step = self.opts['minStep'] + exp = None + else: + vs = [D(-1), D(1)][val >= 0] + #exp = D(int(abs(val*(D('1.01')**(s*vs))).log10())) + fudge = D('1.01')**(s*vs) ## fudge factor. at some places, the step size depends on the step sign. + exp = abs(val * fudge).log10().quantize(1, ROUND_FLOOR) + step = self.opts['step'] * D(10)**exp + if 'minStep' in self.opts: + step = max(step, self.opts['minStep']) + val += s * step + #print "Exp:", exp, "step", step, "val", val + else: + val += s*self.opts['step'] + + if 'minStep' in self.opts and abs(val) < self.opts['minStep']: + val = D(0) + self.setValue(val, delaySignal=True) ## note all steps (arrow buttons, wheel, up/down keys..) emit delayed signals only. + + + def valueInRange(self, value): + bounds = self.opts['bounds'] + if bounds[0] is not None and value < bounds[0]: + return False + if bounds[1] is not None and value > bounds[1]: + return False + if self.opts.get('int', False): + if int(value) != value: + return False + return True + + + def updateText(self, prev=None): + #print "Update text." + self.skipValidate = True + if self.opts['siPrefix']: + if self.val == 0 and prev is not None: + (s, p) = fn.siScale(prev) + txt = "0.0 %s%s" % (p, self.opts['suffix']) + else: + txt = fn.siFormat(float(self.val), suffix=self.opts['suffix']) + else: + txt = '%g%s' % (self.val , self.opts['suffix']) + self.lineEdit().setText(txt) + self.lastText = txt + self.skipValidate = False + + def validate(self, strn, pos): + if self.skipValidate: + #print "skip validate" + #self.textValid = False + ret = QtGui.QValidator.Acceptable + else: + try: + ## first make sure we didn't mess with the suffix + suff = self.opts.get('suffix', '') + if len(suff) > 0 and asUnicode(strn)[-len(suff):] != suff: + #print '"%s" != "%s"' % (unicode(strn)[-len(suff):], suff) + ret = QtGui.QValidator.Invalid + + ## next see if we actually have an interpretable value + else: + val = self.interpret() + if val is False: + #print "can't interpret" + #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') + #self.textValid = False + ret = QtGui.QValidator.Intermediate + else: + if self.valueInRange(val): + if not self.opts['delayUntilEditFinished']: + self.setValue(val, update=False) + #print " OK:", self.val + #self.setStyleSheet('') + #self.textValid = True + + ret = QtGui.QValidator.Acceptable + else: + ret = QtGui.QValidator.Intermediate + + except: + #print " BAD" + #import sys + #sys.excepthook(*sys.exc_info()) + #self.textValid = False + #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') + ret = QtGui.QValidator.Intermediate + + ## draw / clear border + if ret == QtGui.QValidator.Intermediate: + self.textValid = False + elif ret == QtGui.QValidator.Acceptable: + self.textValid = True + ## note: if text is invalid, we don't change the textValid flag + ## since the text will be forced to its previous state anyway + self.update() + + ## support 2 different pyqt APIs. Bleh. + if hasattr(QtCore, 'QString'): + return (ret, pos) + else: + return (ret, strn, pos) + + def paintEvent(self, ev): + QtGui.QAbstractSpinBox.paintEvent(self, ev) + + ## draw red border if text is invalid + if not self.textValid: + p = QtGui.QPainter(self) + p.setRenderHint(p.Antialiasing) + p.setPen(fn.mkPen((200,50,50), width=2)) + p.drawRoundedRect(self.rect().adjusted(2, 2, -2, -2), 4, 4) + p.end() + + + def interpret(self): + """Return value of text. Return False if text is invalid, raise exception if text is intermediate""" + strn = self.lineEdit().text() + suf = self.opts['suffix'] + if len(suf) > 0: + if strn[-len(suf):] != suf: + return False + #raise Exception("Units are invalid.") + strn = strn[:-len(suf)] + try: + val = fn.siEval(strn) + except: + #sys.excepthook(*sys.exc_info()) + #print "invalid" + return False + #print val + return val + + #def interpretText(self, strn=None): + #print "Interpret:", strn + #if strn is None: + #strn = self.lineEdit().text() + #self.setValue(siEval(strn), update=False) + ##QtGui.QAbstractSpinBox.interpretText(self) + + + def editingFinishedEvent(self): + """Edit has finished; set value.""" + #print "Edit finished." + if asUnicode(self.lineEdit().text()) == self.lastText: + #print "no text change." + return + try: + val = self.interpret() + except: + return + + if val is False: + #print "value invalid:", str(self.lineEdit().text()) + return + if val == self.val: + #print "no value change:", val, self.val + return + self.setValue(val, delaySignal=False) ## allow text update so that values are reformatted pretty-like + + #def textChanged(self): + #print "Text changed." + + +### Drop-in replacement for SpinBox; just for crash-testing +#class SpinBox(QtGui.QDoubleSpinBox): + #valueChanged = QtCore.Signal(object) # (value) for compatibility with QSpinBox + #sigValueChanged = QtCore.Signal(object) # (self) + #sigValueChanging = QtCore.Signal(object) # (value) + #def __init__(self, parent=None, *args, **kargs): + #QtGui.QSpinBox.__init__(self, parent) + + #def __getattr__(self, attr): + #return lambda *args, **kargs: None + + #def widgetGroupInterface(self): + #return (self.valueChanged, SpinBox.value, SpinBox.setValue) + diff --git a/pyqtgraph/widgets/TableWidget.py b/pyqtgraph/widgets/TableWidget.py new file mode 100644 index 00000000..8ffe7291 --- /dev/null +++ b/pyqtgraph/widgets/TableWidget.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +from pyqtgraph.python2_3 import asUnicode + +import numpy as np +try: + import metaarray + HAVE_METAARRAY = True +except ImportError: + HAVE_METAARRAY = False + +__all__ = ['TableWidget'] +class TableWidget(QtGui.QTableWidget): + """Extends QTableWidget with some useful functions for automatic data handling + and copy / export context menu. Can automatically format and display a variety + of data types (see :func:`setData() ` for more + information. + """ + + def __init__(self, *args, **kwds): + QtGui.QTableWidget.__init__(self, *args) + self.setVerticalScrollMode(self.ScrollPerPixel) + self.setSelectionMode(QtGui.QAbstractItemView.ContiguousSelection) + self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + self.setSortingEnabled(True) + self.clear() + editable = kwds.get('editable', False) + self.setEditable(editable) + self.contextMenu = QtGui.QMenu() + self.contextMenu.addAction('Copy Selection').triggered.connect(self.copySel) + self.contextMenu.addAction('Copy All').triggered.connect(self.copyAll) + self.contextMenu.addAction('Save Selection').triggered.connect(self.saveSel) + self.contextMenu.addAction('Save All').triggered.connect(self.saveAll) + + def clear(self): + """Clear all contents from the table.""" + QtGui.QTableWidget.clear(self) + self.verticalHeadersSet = False + self.horizontalHeadersSet = False + self.items = [] + self.setRowCount(0) + self.setColumnCount(0) + + def setData(self, data): + """Set the data displayed in the table. + Allowed formats are: + + * numpy arrays + * numpy record arrays + * metaarrays + * list-of-lists [[1,2,3], [4,5,6]] + * dict-of-lists {'x': [1,2,3], 'y': [4,5,6]} + * list-of-dicts [{'x': 1, 'y': 4}, {'x': 2, 'y': 5}, ...] + """ + self.clear() + self.appendData(data) + self.resizeColumnsToContents() + + def appendData(self, data): + """Types allowed: + 1 or 2D numpy array or metaArray + 1D numpy record array + list-of-lists, list-of-dicts or dict-of-lists + """ + fn0, header0 = self.iteratorFn(data) + if fn0 is None: + self.clear() + return + it0 = fn0(data) + try: + first = next(it0) + except StopIteration: + return + fn1, header1 = self.iteratorFn(first) + if fn1 is None: + self.clear() + return + + firstVals = [x for x in fn1(first)] + self.setColumnCount(len(firstVals)) + + if not self.verticalHeadersSet and header0 is not None: + self.setRowCount(len(header0)) + self.setVerticalHeaderLabels(header0) + self.verticalHeadersSet = True + if not self.horizontalHeadersSet and header1 is not None: + self.setHorizontalHeaderLabels(header1) + self.horizontalHeadersSet = True + + self.setRow(0, firstVals) + i = 1 + for row in it0: + self.setRow(i, [x for x in fn1(row)]) + i += 1 + + def setEditable(self, editable=True): + self.editable = editable + for item in self.items: + item.setEditable(editable) + + def iteratorFn(self, data): + ## Return 1) a function that will provide an iterator for data and 2) a list of header strings + if isinstance(data, list) or isinstance(data, tuple): + return lambda d: d.__iter__(), None + elif isinstance(data, dict): + return lambda d: iter(d.values()), list(map(str, data.keys())) + elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): + if data.axisHasColumns(0): + header = [str(data.columnName(0, i)) for i in range(data.shape[0])] + elif data.axisHasValues(0): + header = list(map(str, data.xvals(0))) + else: + header = None + return self.iterFirstAxis, header + elif isinstance(data, np.ndarray): + return self.iterFirstAxis, None + elif isinstance(data, np.void): + return self.iterate, list(map(str, data.dtype.names)) + elif data is None: + return (None,None) + else: + msg = "Don't know how to iterate over data type: {!s}".format(type(data)) + raise TypeError(msg) + + def iterFirstAxis(self, data): + for i in range(data.shape[0]): + yield data[i] + + def iterate(self, data): + # for numpy.void, which can be iterated but mysteriously + # has no __iter__ (??) + for x in data: + yield x + + def appendRow(self, data): + self.appendData([data]) + + def addRow(self, vals): + row = self.rowCount() + self.setRowCount(row + 1) + self.setRow(row, vals) + + def setRow(self, row, vals): + if row > self.rowCount() - 1: + self.setRowCount(row + 1) + for col in range(len(vals)): + val = vals[col] + item = TableWidgetItem(val) + item.setEditable(self.editable) + self.items.append(item) + self.setItem(row, col, item) + + def sizeHint(self): + # based on http://stackoverflow.com/a/7195443/54056 + width = sum(self.columnWidth(i) for i in range(self.columnCount())) + width += self.verticalHeader().sizeHint().width() + width += self.verticalScrollBar().sizeHint().width() + width += self.frameWidth() * 2 + height = sum(self.rowHeight(i) for i in range(self.rowCount())) + height += self.verticalHeader().sizeHint().height() + height += self.horizontalScrollBar().sizeHint().height() + return QtCore.QSize(width, height) + + def serialize(self, useSelection=False): + """Convert entire table (or just selected area) into tab-separated text values""" + if useSelection: + selection = self.selectedRanges()[0] + rows = list(range(selection.topRow(), + selection.bottomRow() + 1)) + columns = list(range(selection.leftColumn(), + selection.rightColumn() + 1)) + else: + rows = list(range(self.rowCount())) + columns = list(range(self.columnCount())) + + + data = [] + if self.horizontalHeadersSet: + row = [] + if self.verticalHeadersSet: + row.append(asUnicode('')) + + for c in columns: + row.append(asUnicode(self.horizontalHeaderItem(c).text())) + data.append(row) + + for r in rows: + row = [] + if self.verticalHeadersSet: + row.append(asUnicode(self.verticalHeaderItem(r).text())) + for c in columns: + item = self.item(r, c) + if item is not None: + row.append(asUnicode(item.value)) + else: + row.append(asUnicode('')) + data.append(row) + + s = '' + for row in data: + s += ('\t'.join(row) + '\n') + return s + + def copySel(self): + """Copy selected data to clipboard.""" + QtGui.QApplication.clipboard().setText(self.serialize(useSelection=True)) + + def copyAll(self): + """Copy all data to clipboard.""" + QtGui.QApplication.clipboard().setText(self.serialize(useSelection=False)) + + def saveSel(self): + """Save selected data to file.""" + self.save(self.serialize(useSelection=True)) + + def saveAll(self): + """Save all data to file.""" + self.save(self.serialize(useSelection=False)) + + def save(self, data): + fileName = QtGui.QFileDialog.getSaveFileName(self, "Save As..", "", "Tab-separated values (*.tsv)") + if fileName == '': + return + open(fileName, 'w').write(data) + + + def contextMenuEvent(self, ev): + self.contextMenu.popup(ev.globalPos()) + + def keyPressEvent(self, ev): + if ev.text() == 'c' and ev.modifiers() == QtCore.Qt.ControlModifier: + ev.accept() + self.copy() + else: + ev.ignore() + +class TableWidgetItem(QtGui.QTableWidgetItem): + def __init__(self, val): + if isinstance(val, float) or isinstance(val, np.floating): + s = "%0.3g" % val + else: + s = str(val) + QtGui.QTableWidgetItem.__init__(self, s) + self.value = val + flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled + self.setFlags(flags) + + def setEditable(self, editable): + if editable: + self.setFlags(self.flags() | QtCore.Qt.ItemIsEditable) + else: + self.setFlags(self.flags() & ~QtCore.Qt.ItemIsEditable) + + def __lt__(self, other): + if hasattr(other, 'value'): + return self.value < other.value + else: + return self.text() < other.text() + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + t = TableWidget() + win.setCentralWidget(t) + win.resize(800,600) + win.show() + + ll = [[1,2,3,4,5]] * 20 + ld = [{'x': 1, 'y': 2, 'z': 3}] * 20 + dl = {'x': list(range(20)), 'y': list(range(20)), 'z': list(range(20))} + + a = np.ones((20, 5)) + ra = np.ones((20,), dtype=[('x', int), ('y', int), ('z', int)]) + + t.setData(ll) + + if HAVE_METAARRAY: + ma = metaarray.MetaArray(np.ones((20, 3)), info=[ + {'values': np.linspace(1, 5, 20)}, + {'cols': [ + {'name': 'x'}, + {'name': 'y'}, + {'name': 'z'}, + ]} + ]) + t.setData(ma) + diff --git a/pyqtgraph/widgets/TreeWidget.py b/pyqtgraph/widgets/TreeWidget.py new file mode 100644 index 00000000..97fbe953 --- /dev/null +++ b/pyqtgraph/widgets/TreeWidget.py @@ -0,0 +1,284 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore +from weakref import * + +__all__ = ['TreeWidget', 'TreeWidgetItem'] +class TreeWidget(QtGui.QTreeWidget): + """Extends QTreeWidget to allow internal drag/drop with widgets in the tree. + Also maintains the expanded state of subtrees as they are moved. + This class demonstrates the absurd lengths one must go to to make drag/drop work.""" + + sigItemMoved = QtCore.Signal(object, object, object) # (item, parent, index) + + def __init__(self, parent=None): + QtGui.QTreeWidget.__init__(self, parent) + #self.itemWidgets = WeakKeyDictionary() + self.setAcceptDrops(True) + self.setDragEnabled(True) + self.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed|QtGui.QAbstractItemView.SelectedClicked) + self.placeholders = [] + self.childNestingLimit = None + + def setItemWidget(self, item, col, wid): + """ + Overrides QTreeWidget.setItemWidget such that widgets are added inside an invisible wrapper widget. + This makes it possible to move the item in and out of the tree without its widgets being automatically deleted. + """ + w = QtGui.QWidget() ## foster parent / surrogate child widget + l = QtGui.QVBoxLayout() + l.setContentsMargins(0,0,0,0) + w.setLayout(l) + w.setSizePolicy(wid.sizePolicy()) + w.setMinimumHeight(wid.minimumHeight()) + w.setMinimumWidth(wid.minimumWidth()) + l.addWidget(wid) + w.realChild = wid + self.placeholders.append(w) + QtGui.QTreeWidget.setItemWidget(self, item, col, w) + + def itemWidget(self, item, col): + w = QtGui.QTreeWidget.itemWidget(self, item, col) + if w is not None: + w = w.realChild + return w + + def dropMimeData(self, parent, index, data, action): + item = self.currentItem() + p = parent + #print "drop", item, "->", parent, index + while True: + if p is None: + break + if p is item: + return False + #raise Exception("Can not move item into itself.") + p = p.parent() + + if not self.itemMoving(item, parent, index): + return False + + currentParent = item.parent() + if currentParent is None: + currentParent = self.invisibleRootItem() + if parent is None: + parent = self.invisibleRootItem() + + if currentParent is parent and index > parent.indexOfChild(item): + index -= 1 + + self.prepareMove(item) + + currentParent.removeChild(item) + #print " insert child to index", index + parent.insertChild(index, item) ## index will not be correct + self.setCurrentItem(item) + + self.recoverMove(item) + #self.emit(QtCore.SIGNAL('itemMoved'), item, parent, index) + self.sigItemMoved.emit(item, parent, index) + return True + + def itemMoving(self, item, parent, index): + """Called when item has been dropped elsewhere in the tree. + Return True to accept the move, False to reject.""" + return True + + def prepareMove(self, item): + item.__widgets = [] + item.__expanded = item.isExpanded() + for i in range(self.columnCount()): + w = self.itemWidget(item, i) + item.__widgets.append(w) + if w is None: + continue + w.setParent(None) + for i in range(item.childCount()): + self.prepareMove(item.child(i)) + + def recoverMove(self, item): + for i in range(self.columnCount()): + w = item.__widgets[i] + if w is None: + continue + self.setItemWidget(item, i, w) + for i in range(item.childCount()): + self.recoverMove(item.child(i)) + + item.setExpanded(False) ## Items do not re-expand correctly unless they are collapsed first. + QtGui.QApplication.instance().processEvents() + item.setExpanded(item.__expanded) + + def collapseTree(self, item): + item.setExpanded(False) + for i in range(item.childCount()): + self.collapseTree(item.child(i)) + + def removeTopLevelItem(self, item): + for i in range(self.topLevelItemCount()): + if self.topLevelItem(i) is item: + self.takeTopLevelItem(i) + return + raise Exception("Item '%s' not in top-level items." % str(item)) + + def listAllItems(self, item=None): + items = [] + if item != None: + items.append(item) + else: + item = self.invisibleRootItem() + + for cindex in range(item.childCount()): + foundItems = self.listAllItems(item=item.child(cindex)) + for f in foundItems: + items.append(f) + return items + + def dropEvent(self, ev): + QtGui.QTreeWidget.dropEvent(self, ev) + self.updateDropFlags() + + + def updateDropFlags(self): + ### intended to put a limit on how deep nests of children can go. + ### self.childNestingLimit is upheld when moving items without children, but if the item being moved has children/grandchildren, the children/grandchildren + ### can end up over the childNestingLimit. + if self.childNestingLimit == None: + pass # enable drops in all items (but only if there are drops that aren't enabled? for performance...) + else: + items = self.listAllItems() + for item in items: + parentCount = 0 + p = item.parent() + while p is not None: + parentCount += 1 + p = p.parent() + if parentCount >= self.childNestingLimit: + item.setFlags(item.flags() & (~QtCore.Qt.ItemIsDropEnabled)) + else: + item.setFlags(item.flags() | QtCore.Qt.ItemIsDropEnabled) + + @staticmethod + def informTreeWidgetChange(item): + if hasattr(item, 'treeWidgetChanged'): + item.treeWidgetChanged() + else: + for i in xrange(item.childCount()): + TreeWidget.informTreeWidgetChange(item.child(i)) + + + def addTopLevelItem(self, item): + QtGui.QTreeWidget.addTopLevelItem(self, item) + self.informTreeWidgetChange(item) + + def addTopLevelItems(self, items): + QtGui.QTreeWidget.addTopLevelItems(self, items) + for item in items: + self.informTreeWidgetChange(item) + + def insertTopLevelItem(self, index, item): + QtGui.QTreeWidget.insertTopLevelItem(self, index, item) + self.informTreeWidgetChange(item) + + def insertTopLevelItems(self, index, items): + QtGui.QTreeWidget.insertTopLevelItems(self, index, items) + for item in items: + self.informTreeWidgetChange(item) + + def takeTopLevelItem(self, index): + item = self.topLevelItem(index) + if item is not None: + self.prepareMove(item) + item = QtGui.QTreeWidget.takeTopLevelItem(self, index) + self.prepareMove(item) + self.informTreeWidgetChange(item) + return item + + def topLevelItems(self): + return map(self.topLevelItem, xrange(self.topLevelItemCount())) + + def clear(self): + items = self.topLevelItems() + for item in items: + self.prepareMove(item) + QtGui.QTreeWidget.clear(self) + + ## Why do we want to do this? It causes RuntimeErrors. + #for item in items: + #self.informTreeWidgetChange(item) + + +class TreeWidgetItem(QtGui.QTreeWidgetItem): + """ + TreeWidgetItem that keeps track of its own widgets. + Widgets may be added to columns before the item is added to a tree. + """ + def __init__(self, *args): + QtGui.QTreeWidgetItem.__init__(self, *args) + self._widgets = {} # col: widget + self._tree = None + + + def setChecked(self, column, checked): + self.setCheckState(column, QtCore.Qt.Checked if checked else QtCore.Qt.Unchecked) + + def setWidget(self, column, widget): + if column in self._widgets: + self.removeWidget(column) + self._widgets[column] = widget + tree = self.treeWidget() + if tree is None: + return + else: + tree.setItemWidget(self, column, widget) + + def removeWidget(self, column): + del self._widgets[column] + tree = self.treeWidget() + if tree is None: + return + tree.removeItemWidget(self, column) + + def treeWidgetChanged(self): + tree = self.treeWidget() + if self._tree is tree: + return + self._tree = self.treeWidget() + if tree is None: + return + for col, widget in self._widgets.items(): + tree.setItemWidget(self, col, widget) + + def addChild(self, child): + QtGui.QTreeWidgetItem.addChild(self, child) + TreeWidget.informTreeWidgetChange(child) + + def addChildren(self, childs): + QtGui.QTreeWidgetItem.addChildren(self, childs) + for child in childs: + TreeWidget.informTreeWidgetChange(child) + + def insertChild(self, index, child): + QtGui.QTreeWidgetItem.insertChild(self, index, child) + TreeWidget.informTreeWidgetChange(child) + + def insertChildren(self, index, childs): + QtGui.QTreeWidgetItem.addChildren(self, index, childs) + for child in childs: + TreeWidget.informTreeWidgetChange(child) + + def removeChild(self, child): + QtGui.QTreeWidgetItem.removeChild(self, child) + TreeWidget.informTreeWidgetChange(child) + + def takeChild(self, index): + child = QtGui.QTreeWidgetItem.takeChild(self, index) + TreeWidget.informTreeWidgetChange(child) + return child + + def takeChildren(self): + childs = QtGui.QTreeWidgetItem.takeChildren(self) + for child in childs: + TreeWidget.informTreeWidgetChange(child) + return childs + + diff --git a/pyqtgraph/widgets/ValueLabel.py b/pyqtgraph/widgets/ValueLabel.py new file mode 100644 index 00000000..7f6fa84b --- /dev/null +++ b/pyqtgraph/widgets/ValueLabel.py @@ -0,0 +1,73 @@ +from pyqtgraph.Qt import QtCore, QtGui +from pyqtgraph.ptime import time +import pyqtgraph as pg +from functools import reduce + +__all__ = ['ValueLabel'] + +class ValueLabel(QtGui.QLabel): + """ + QLabel specifically for displaying numerical values. + Extends QLabel adding some extra functionality: + + - displaying units with si prefix + - built-in exponential averaging + """ + + def __init__(self, parent=None, suffix='', siPrefix=False, averageTime=0, formatStr=None): + """ + ============ ================================================================================== + Arguments + suffix (str or None) The suffix to place after the value + siPrefix (bool) Whether to add an SI prefix to the units and display a scaled value + averageTime (float) The length of time in seconds to average values. If this value + is 0, then no averaging is performed. As this value increases + the display value will appear to change more slowly and smoothly. + formatStr (str) Optionally, provide a format string to use when displaying text. The text + will be generated by calling formatStr.format(value=, avgValue=, suffix=) + (see Python documentation on str.format) + This option is not compatible with siPrefix + ============ ================================================================================== + """ + QtGui.QLabel.__init__(self, parent) + self.values = [] + self.averageTime = averageTime ## no averaging by default + self.suffix = suffix + self.siPrefix = siPrefix + if formatStr is None: + formatStr = '{avgValue:0.2g} {suffix}' + self.formatStr = formatStr + + def setValue(self, value): + now = time() + self.values.append((now, value)) + cutoff = now - self.averageTime + while len(self.values) > 0 and self.values[0][0] < cutoff: + self.values.pop(0) + self.update() + + def setFormatStr(self, text): + self.formatStr = text + self.update() + + def setAverageTime(self, t): + self.averageTime = t + + def averageValue(self): + return reduce(lambda a,b: a+b, [v[1] for v in self.values]) / float(len(self.values)) + + + def paintEvent(self, ev): + self.setText(self.generateText()) + return QtGui.QLabel.paintEvent(self, ev) + + def generateText(self): + if len(self.values) == 0: + return '' + avg = self.averageValue() + val = self.values[-1][1] + if self.siPrefix: + return pg.siFormat(avg, suffix=self.suffix) + else: + return self.formatStr.format(value=val, avgValue=avg, suffix=self.suffix) + diff --git a/pyqtgraph/widgets/VerticalLabel.py b/pyqtgraph/widgets/VerticalLabel.py new file mode 100644 index 00000000..fa45ae5d --- /dev/null +++ b/pyqtgraph/widgets/VerticalLabel.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +from pyqtgraph.Qt import QtGui, QtCore + +__all__ = ['VerticalLabel'] +#class VerticalLabel(QtGui.QLabel): + #def paintEvent(self, ev): + #p = QtGui.QPainter(self) + #p.rotate(-90) + #self.hint = p.drawText(QtCore.QRect(-self.height(), 0, self.height(), self.width()), QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter, self.text()) + #p.end() + #self.setMinimumWidth(self.hint.height()) + #self.setMinimumHeight(self.hint.width()) + + #def sizeHint(self): + #if hasattr(self, 'hint'): + #return QtCore.QSize(self.hint.height(), self.hint.width()) + #else: + #return QtCore.QSize(16, 50) + +class VerticalLabel(QtGui.QLabel): + def __init__(self, text, orientation='vertical', forceWidth=True): + QtGui.QLabel.__init__(self, text) + self.forceWidth = forceWidth + self.orientation = None + self.setOrientation(orientation) + + def setOrientation(self, o): + if self.orientation == o: + return + self.orientation = o + self.update() + self.updateGeometry() + + def paintEvent(self, ev): + p = QtGui.QPainter(self) + #p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200))) + #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100))) + #p.drawRect(self.rect().adjusted(0, 0, -1, -1)) + + #p.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255))) + + if self.orientation == 'vertical': + p.rotate(-90) + rgn = QtCore.QRect(-self.height(), 0, self.height(), self.width()) + else: + rgn = self.contentsRect() + align = self.alignment() + #align = QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter + + self.hint = p.drawText(rgn, align, self.text()) + p.end() + + if self.orientation == 'vertical': + self.setMaximumWidth(self.hint.height()) + self.setMinimumWidth(0) + self.setMaximumHeight(16777215) + if self.forceWidth: + self.setMinimumHeight(self.hint.width()) + else: + self.setMinimumHeight(0) + else: + self.setMaximumHeight(self.hint.height()) + self.setMinimumHeight(0) + self.setMaximumWidth(16777215) + if self.forceWidth: + self.setMinimumWidth(self.hint.width()) + else: + self.setMinimumWidth(0) + + def sizeHint(self): + if self.orientation == 'vertical': + if hasattr(self, 'hint'): + return QtCore.QSize(self.hint.height(), self.hint.width()) + else: + return QtCore.QSize(19, 50) + else: + if hasattr(self, 'hint'): + return QtCore.QSize(self.hint.width(), self.hint.height()) + else: + return QtCore.QSize(50, 19) + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + w = QtGui.QWidget() + l = QtGui.QGridLayout() + w.setLayout(l) + + l1 = VerticalLabel("text 1", orientation='horizontal') + l2 = VerticalLabel("text 2") + l3 = VerticalLabel("text 3") + l4 = VerticalLabel("text 4", orientation='horizontal') + l.addWidget(l1, 0, 0) + l.addWidget(l2, 1, 1) + l.addWidget(l3, 2, 2) + l.addWidget(l4, 3, 3) + win.setCentralWidget(w) + win.show() \ No newline at end of file diff --git a/pyqtgraph/widgets/__init__.py b/pyqtgraph/widgets/__init__.py new file mode 100644 index 00000000..a81fe391 --- /dev/null +++ b/pyqtgraph/widgets/__init__.py @@ -0,0 +1,21 @@ +## just import everything from sub-modules + +#import os + +#d = os.path.split(__file__)[0] +#files = [] +#for f in os.listdir(d): + #if os.path.isdir(os.path.join(d, f)): + #files.append(f) + #elif f[-3:] == '.py' and f != '__init__.py': + #files.append(f[:-3]) + +#for modName in files: + #mod = __import__(modName, globals(), locals(), fromlist=['*']) + #if hasattr(mod, '__all__'): + #names = mod.__all__ + #else: + #names = [n for n in dir(mod) if n[0] != '_'] + #for k in names: + #print modName, k + #globals()[k] = getattr(mod, k) diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..7b55a51f --- /dev/null +++ b/setup.py @@ -0,0 +1,12 @@ +from distutils.core import setup + +setup( + name='PaPI', + version='0.01', + packages=['papi'], + url='https://github.com/TUB-Control/PaPI', + license='LGPL v3', + author='TU-Berlin, FG Regelungssysteme', + author_email='github@control.tu-berlin.de', + description='PaPI' +) \ No newline at end of file diff --git a/ui/gui/qt_dev/add_plugin.ui b/ui/gui/qt_dev/add_plugin.ui new file mode 100644 index 00000000..bf7f0b32 --- /dev/null +++ b/ui/gui/qt_dev/add_plugin.ui @@ -0,0 +1,149 @@ + + + AddPlugin + + + + 0 + 0 + 579 + 558 + + + + Dialog + + + + + 230 + 510 + 341 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Ok + + + true + + + + + + 10 + 10 + 561 + 171 + + + + + Plugin + + + + + + + 10 + 190 + 561 + 62 + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + UName + + + + + + + + + + Path + + + + + + + true + + + + + + + + + 10 + 270 + 561 + 241 + + + + + + + + 7 + 250 + 561 + 20 + + + + Qt::Horizontal + + + + + + + buttonBox + accepted() + AddPlugin + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AddPlugin + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/ui/gui/qt_dev/add_subscriber.ui b/ui/gui/qt_dev/add_subscriber.ui new file mode 100644 index 00000000..432e97d8 --- /dev/null +++ b/ui/gui/qt_dev/add_subscriber.ui @@ -0,0 +1,125 @@ + + + AddSubscriber + + + + 0 + 0 + 531 + 197 + + + + Dialog + + + + + 10 + 160 + 511 + 32 + + + + false + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Ok + + + true + + + + + + 10 + 10 + 511 + 141 + + + + + + + + Subscriber + + + + + + + + + Target + + + + + + + + + Block + + + + + + + + QAbstractItemView::MultiSelection + + + + Signal + + + + + + + + + + + buttonBox + accepted() + AddSubscriber + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AddSubscriber + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/ui/gui/qt_dev/main.ui b/ui/gui/qt_dev/main.ui new file mode 100644 index 00000000..6172b53a --- /dev/null +++ b/ui/gui/qt_dev/main.ui @@ -0,0 +1,293 @@ + + + MainGUI + + + + 0 + 0 + 979 + 918 + + + + MainWindow + + + + + + + + + Exit + + + + + + + CreatePlugin + + + + + + + ShowOverview + + + + + + + CreateSubscription + + + + + + + CreatePCPSubscription + + + + + + + ShowLicence + + + + + + + + + Save + + + + + + + Load + + + + + + + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + + + + + + + + 0 + 0 + 979 + 25 + + + + + Menu + + + + + Plugins + + + + + + + + true + + + + 100 + 41 + + + + false + + + 1 + + + + + + 12 + 4 + 161 + 831 + + + + + 100 + 0 + + + + 2 + + + + + 0 + 0 + 161 + 707 + + + + Visual Plugins + + + + + 0 + 0 + 161 + 701 + + + + + Plugin + + + + + + + + 0 + 0 + 161 + 707 + + + + Process Con. Plugins + + + + + 0 + 0 + 161 + 701 + + + + + 1 + + + + + + + Data Pro. Plugin + + + + + 0 + 10 + 161 + 691 + + + + + 1 + + + + + + + IO Plugin + + + + + 0 + 0 + 161 + 701 + + + + + 1 + + + + + + + + + + Available + + + + + IO + + + + + Visual + + + + + IO + + + + + License + + + + + Quit + + + + + Parameter + + + + + Overview + + + + + + diff --git a/ui/gui/qt_dev/manager.ui b/ui/gui/qt_dev/manager.ui new file mode 100644 index 00000000..39bdc62d --- /dev/null +++ b/ui/gui/qt_dev/manager.ui @@ -0,0 +1,233 @@ + + + Manager + + + + 0 + 0 + 883 + 748 + + + + + 0 + 0 + + + + MainWindow + + + + true + + + + 0 + 0 + + + + + 791 + 16777215 + + + + Qt::LeftToRight + + + + + 10 + 0 + 771 + 701 + + + + + QLayout::SetNoConstraint + + + + + true + + + true + + + true + + + true + + + false + + + 100 + + + true + + + 30 + + + true + + + true + + + + Plugin + + + + + #Parameters + + + + + #Blocks + + + + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + 6 + + + + + ID + + + + + + + true + + + + + + + Type + + + + + + + Path + + + + + + + true + + + + + + + true + + + true + + + + + + + + + 0 + + + 3 + + + 100 + + + + Parameter + + + + + PCP + + + + + Value + + + + + + + + 80 + + + + Block + + + + + Subscriber + + + + + + + + + Signal + + + + + + + + + + + + + + 0 + 0 + 883 + 25 + + + + + + + diff --git a/ui/gui/qt_new/create.ui b/ui/gui/qt_new/create.ui new file mode 100644 index 00000000..c71e5fa0 --- /dev/null +++ b/ui/gui/qt_new/create.ui @@ -0,0 +1,124 @@ + + + Create + + + + 0 + 0 + 800 + 601 + + + + MainWindow + + + + + + + + + QAbstractItemView::SelectItems + + + true + + + + + + + false + + + true + + + + + 0 + 0 + 385 + 532 + + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Name + + + + + + + Author + + + + + + + Path + + + + + + + Description + + + + + + + + + + + + + + + + + + + + + Create Plugin + + + + + + + + + + + + + + + 0 + 0 + 800 + 25 + + + + + + + + diff --git a/ui/gui/qt_new/create_dialog.ui b/ui/gui/qt_new/create_dialog.ui new file mode 100644 index 00000000..2e0f2fef --- /dev/null +++ b/ui/gui/qt_new/create_dialog.ui @@ -0,0 +1,107 @@ + + + CreatePluginDialog + + + + 0 + 0 + 498 + 384 + + + + Dialog + + + + + + 0 + + + false + + + false + + + + Simple + + + + + + + + + + Advance + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Autostart + + + true + + + + + + + + + buttonBox + accepted() + CreatePluginDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CreatePluginDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/ui/gui/qt_new/main.ui b/ui/gui/qt_new/main.ui new file mode 100644 index 00000000..9b73ba39 --- /dev/null +++ b/ui/gui/qt_new/main.ui @@ -0,0 +1,155 @@ + + + QtNewMain + + + + 0 + 0 + 693 + 600 + + + + MainWindow + + + + + + + + + + + Load + + + + + + + Save + + + + + + + + + Qt::ScrollBarAsNeeded + + + Qt::ScrollBarAsNeeded + + + + + + + + + + + 0 + 0 + 693 + 25 + + + + + PaPI + + + + + + + + + + Plugin + + + + + + + View + + + + + + + + + + + + Load + + + + + Save + + + + + Overview + + + + + Create + + + + + Exit + + + + + ReloadConfig + + + + + ResetPaPI + + + + + RunMode + + + + + SetBackground + + + + + + + actionExit + triggered() + QtNewMain + close() + + + -1 + -1 + + + 346 + 299 + + + + + diff --git a/ui/gui/qt_new/overview.ui b/ui/gui/qt_new/overview.ui new file mode 100644 index 00000000..e34f8a42 --- /dev/null +++ b/ui/gui/qt_new/overview.ui @@ -0,0 +1,222 @@ + + + Overview + + + + 0 + 0 + 803 + 614 + + + + MainWindow + + + + + + + + + + + + false + + + 0 + + + + Plugin + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Unique name + + + + + + + + + + Used plugin + + + + + + + + + + State + + + + + + + + + + Type + + + + + + + + + + Alive state + + + + + + + + + + + + 0 + + + + Parameters + + + + + + + + + + Blocks + + + + + + + + + + + + + + + PAUSE + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + STOP + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + PLAY + + + + + + + + + + Connections + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 803 + 25 + + + + + Actions + + + + + + + + + Refresh + + + + + + diff --git a/yapsy/AutoInstallPluginManager.py b/yapsy/AutoInstallPluginManager.py new file mode 100644 index 00000000..d8ded989 --- /dev/null +++ b/yapsy/AutoInstallPluginManager.py @@ -0,0 +1,201 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + +""" +Role +==== + +Defines plugin managers that can handle the installation of plugin +files into the right place. Then the end-user does not have to browse +to the plugin directory to install them. + +API +=== +""" + +import sys +import os +import shutil +import zipfile +import io + +from yapsy.IPlugin import IPlugin +from yapsy.PluginManagerDecorator import PluginManagerDecorator +from yapsy import log + + +class AutoInstallPluginManager(PluginManagerDecorator): + """ + A plugin manager that also manages the installation of the plugin + files into the appropriate directory. + """ + + + def __init__(self, + plugin_install_dir=None, + decorated_manager=None, + # The following args will only be used if we need to + # create a default PluginManager + categories_filter={"Default":IPlugin}, + directories_list=None, + plugin_info_ext="yapsy-plugin"): + """ + Create the plugin manager and set up the directory where to + install new plugins. + + Arguments + + ``plugin_install_dir`` + The directory where new plugins to be installed will be copied. + + .. warning:: If ``plugin_install_dir`` does not correspond to + an element of the ``directories_list``, it is + appended to the later. + + """ + # Create the base decorator class + PluginManagerDecorator.__init__(self, + decorated_manager, + categories_filter, + directories_list, + plugin_info_ext) + # set the directory for new plugins + self.plugins_places=[] + self.setInstallDir(plugin_install_dir) + + def setInstallDir(self,plugin_install_dir): + """ + Set the directory where to install new plugins. + """ + if not (plugin_install_dir in self.plugins_places): + self.plugins_places.append(plugin_install_dir) + self.install_dir = plugin_install_dir + + def getInstallDir(self): + """ + Return the directory where new plugins should be installed. + """ + return self.install_dir + + def install(self, directory, plugin_info_filename): + """ + Giving the plugin's info file (e.g. ``myplugin.yapsy-plugin``), + and the directory where it is located, get all the files that + define the plugin and copy them into the correct directory. + + Return ``True`` if the installation is a success, ``False`` if + it is a failure. + """ + # start collecting essential info about the new plugin + plugin_info, config_parser = self._gatherCorePluginInfo(directory, plugin_info_filename) + # now determine the path of the file to execute, + # depending on wether the path indicated is a + # directory or a file + if not (os.path.exists(plugin_info.path) or os.path.exists(plugin_info.path+".py") ): + log.warning("Could not find the plugin's implementation for %s." % plugin_info.name) + return False + if os.path.isdir(plugin_info.path): + try: + shutil.copytree(plugin_info.path, + os.path.join(self.install_dir,os.path.basename(plugin_info.path))) + shutil.copy(os.path.join(directory, plugin_info_filename), + self.install_dir) + except: + log.error("Could not install plugin: %s." % plugin_info.name) + return False + else: + return True + elif os.path.isfile(plugin_info.path+".py"): + try: + shutil.copy(plugin_info.path+".py", + self.install_dir) + shutil.copy(os.path.join(directory, plugin_info_filename), + self.install_dir) + except: + log.error("Could not install plugin: %s." % plugin_info.name) + return False + else: + return True + else: + return False + + + def installFromZIP(self, plugin_ZIP_filename): + """ + Giving the plugin's zip file (e.g. ``myplugin.zip``), check + that their is a valid info file in it and correct all the + plugin files into the correct directory. + + .. warning:: Only available for python 2.6 and later. + + Return ``True`` if the installation is a success, ``False`` if + it is a failure. + """ + if not os.path.isfile(plugin_ZIP_filename): + log.warning("Could not find the plugin's zip file at '%s'." % plugin_ZIP_filename) + return False + try: + candidateZipFile = zipfile.ZipFile(plugin_ZIP_filename) + first_bad_file = candidateZipFile.testzip() + if first_bad_file: + raise Exception("Corrupted ZIP with first bad file '%s'" % first_bad_file) + except Exception as e: + log.warning("Invalid zip file '%s' (error: %s)." % (plugin_ZIP_filename,e)) + return False + zipContent = candidateZipFile.namelist() + log.info("Investigating the content of a zip file containing: '%s'" % zipContent) + log.info("Sanity checks on zip's contained files (looking for hazardous path symbols).") + # check absence of root path and ".." shortcut that would + # send the file oustide the desired directory + for containedFileName in zipContent: + # WARNING: the sanity checks below are certainly not + # exhaustive (maybe we could do something a bit smarter by + # using os.path.expanduser, os.path.expandvars and + # os.path.normpath) + if containedFileName.startswith("/"): + log.warning("Unsecure zip file, rejected because one of its file paths ('%s') starts with '/'" % containedFileName) + return False + if containedFileName.startswith(r"\\") or containedFileName.startswith("//"): + log.warning(r"Unsecure zip file, rejected because one of its file paths ('%s') starts with '\\'" % containedFileName) + return False + if os.path.splitdrive(containedFileName)[0]: + log.warning("Unsecure zip file, rejected because one of its file paths ('%s') starts with a drive letter" % containedFileName) + return False + if os.path.isabs(containedFileName): + log.warning("Unsecure zip file, rejected because one of its file paths ('%s') is absolute" % containedFileName) + return False + pathComponent = os.path.split(containedFileName) + if ".." in pathComponent: + log.warning("Unsecure zip file, rejected because one of its file paths ('%s') contains '..'" % containedFileName) + return False + if "~" in pathComponent: + log.warning("Unsecure zip file, rejected because one of its file paths ('%s') contains '~'" % containedFileName) + return False + infoFileCandidates = [filename for filename in zipContent if os.path.dirname(filename)==""] + if not infoFileCandidates: + log.warning("Zip file structure seems wrong in '%s', no info file found." % plugin_ZIP_filename) + return False + isValid = False + log.info("Looking for the zipped plugin's info file among '%s'" % infoFileCandidates) + for infoFileName in infoFileCandidates: + infoFile = candidateZipFile.read(infoFileName) + log.info("Assuming the zipped plugin info file to be '%s'" % infoFileName) + pluginName,moduleName,_ = self._getPluginNameAndModuleFromStream(io.StringIO(str(infoFile,encoding="utf-8"))) + if moduleName is None: + continue + log.info("Checking existence of the expected module '%s' in the zip file" % moduleName) + if moduleName in zipContent or os.path.join(moduleName,"__init__.py") in zipContent: + isValid = True + break + if not isValid: + log.warning("Zip file structure seems wrong in '%s', " + "could not match info file with the implementation of plugin '%s'." % (plugin_ZIP_filename,pluginName)) + return False + else: + try: + candidateZipFile.extractall(self.install_dir) + return True + except Exception as e: + log.error("Could not install plugin '%s' from zip file '%s' (exception: '%s')." % (pluginName,plugin_ZIP_filename,e)) + return False + diff --git a/yapsy/ConfigurablePluginManager.py b/yapsy/ConfigurablePluginManager.py new file mode 100644 index 00000000..57e03879 --- /dev/null +++ b/yapsy/ConfigurablePluginManager.py @@ -0,0 +1,277 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + +""" +Role +==== + +Defines plugin managers that can handle configuration files similar to +the ini files manipulated by Python's ConfigParser module. + +API +=== +""" + +from yapsy.IPlugin import IPlugin + + +from yapsy.PluginManagerDecorator import PluginManagerDecorator +from yapsy.PluginManager import PLUGIN_NAME_FORBIDEN_STRING + + +class ConfigurablePluginManager(PluginManagerDecorator): + """ + A plugin manager that also manages a configuration file. + + The configuration file will be accessed through a ``ConfigParser`` + derivated object. The file can be used for other purpose by the + application using this plugin manager as it will only add a new + specific section ``[Plugin Management]`` for itself and also new + sections for some plugins that will start with ``[Plugin:...]`` + (only the plugins that explicitly requires to save configuration + options will have this kind of section). + + .. warning:: when giving/building the list of plugins to activate + by default, there must not be any space in the list + (neither in the names nor in between) + """ + + CONFIG_SECTION_NAME = "Plugin Management" + + + def __init__(self, + configparser_instance=None, + config_change_trigger= lambda x:True, + decorated_manager=None, + # The following args will only be used if we need to + # create a default PluginManager + categories_filter={"Default":IPlugin}, + directories_list=None, + plugin_info_ext="yapsy-plugin"): + """ + Create the plugin manager and record the ConfigParser instance + that will be used afterwards. + + The ``config_change_trigger`` argument can be used to set a + specific method to call when the configuration is + altered. This will let the client application manage the way + they want the configuration to be updated (e.g. write on file + at each change or at precise time intervalls or whatever....) + """ + # Create the base decorator class + PluginManagerDecorator.__init__(self,decorated_manager, + categories_filter, + directories_list, + plugin_info_ext) + self.setConfigParser(configparser_instance, config_change_trigger) + + + def setConfigParser(self,configparser_instance,config_change_trigger): + """ + Set the ConfigParser instance. + """ + self.config_parser = configparser_instance + # set the (optional) fucntion to be called when the + # configuration is changed: + self.config_has_changed = config_change_trigger + + def __getCategoryPluginsListFromConfig(self, plugin_list_str): + """ + Parse the string describing the list of plugins to activate, + to discover their actual names and return them. + """ + return plugin_list_str.strip(" ").split("%s"%PLUGIN_NAME_FORBIDEN_STRING) + + def __getCategoryPluginsConfigFromList(self, plugin_list): + """ + Compose a string describing the list of plugins to activate + """ + return PLUGIN_NAME_FORBIDEN_STRING.join(plugin_list) + + def __getCategoryOptionsName(self,category_name): + """ + Return the appropirately formated version of the category's + option. + """ + return "%s_plugins_to_load" % category_name.replace(" ","_") + + def __addPluginToConfig(self,category_name, plugin_name): + """ + Utility function to add a plugin to the list of plugin to be + activated. + """ + # check that the section is here + if not self.config_parser.has_section(self.CONFIG_SECTION_NAME): + self.config_parser.add_section(self.CONFIG_SECTION_NAME) + # check that the category's list of activated plugins is here too + option_name = self.__getCategoryOptionsName(category_name) + if not self.config_parser.has_option(self.CONFIG_SECTION_NAME, option_name): + # if there is no list yet add a new one + self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,plugin_name) + return self.config_has_changed() + else: + # get the already existing list and append the new + # activated plugin to it. + past_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME,option_name) + past_list = self.__getCategoryPluginsListFromConfig(past_list_str) + # make sure we don't add it twice + if plugin_name not in past_list: + past_list.append(plugin_name) + new_list_str = self.__getCategoryPluginsConfigFromList(past_list) + self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,new_list_str) + return self.config_has_changed() + + def __removePluginFromConfig(self,category_name, plugin_name): + """ + Utility function to add a plugin to the list of plugin to be + activated. + """ + # check that the section is here + if not self.config_parser.has_section(self.CONFIG_SECTION_NAME): + # then nothing to remove :) + return + # check that the category's list of activated plugins is here too + option_name = self.__getCategoryOptionsName(category_name) + if not self.config_parser.has_option(self.CONFIG_SECTION_NAME, option_name): + # if there is no list still nothing to do + return + else: + # get the already existing list + past_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME,option_name) + past_list = self.__getCategoryPluginsListFromConfig(past_list_str) + if plugin_name in past_list: + past_list.remove(plugin_name) + new_list_str = self.__getCategoryPluginsConfigFromList(past_list) + self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,new_list_str) + self.config_has_changed() + + + + def registerOptionFromPlugin(self, + category_name, plugin_name, + option_name, option_value): + """ + To be called from a plugin object, register a given option in + the name of a given plugin. + """ + section_name = "%s Plugin: %s" % (category_name,plugin_name) + # if the plugin's section is not here yet, create it + if not self.config_parser.has_section(section_name): + self.config_parser.add_section(section_name) + # set the required option + self.config_parser.set(section_name,option_name,option_value) + self.config_has_changed() + + def hasOptionFromPlugin(self, + category_name, plugin_name, option_name): + """ + To be called from a plugin object, return True if the option + has already been registered. + """ + section_name = "%s Plugin: %s" % (category_name,plugin_name) + return self.config_parser.has_section(section_name) and self.config_parser.has_option(section_name,option_name) + + def readOptionFromPlugin(self, + category_name, plugin_name, option_name): + """ + To be called from a plugin object, read a given option in + the name of a given plugin. + """ + section_name = "%s Plugin: %s" % (category_name,plugin_name) + return self.config_parser.get(section_name,option_name) + + + def __decoratePluginObject(self, category_name, plugin_name, plugin_object): + """ + Add two methods to the plugin objects that will make it + possible for it to benefit from this class's api concerning + the management of the options. + """ + plugin_object.setConfigOption = lambda x,y: self.registerOptionFromPlugin(category_name, + plugin_name, + x,y) + plugin_object.setConfigOption.__doc__ = self.registerOptionFromPlugin.__doc__ + plugin_object.getConfigOption = lambda x: self.readOptionFromPlugin(category_name, + plugin_name, + x) + plugin_object.getConfigOption.__doc__ = self.readOptionFromPlugin.__doc__ + plugin_object.hasConfigOption = lambda x: self.hasOptionFromPlugin(category_name, + plugin_name, + x) + plugin_object.hasConfigOption.__doc__ = self.hasOptionFromPlugin.__doc__ + + def activatePluginByName(self, plugin_name, category_name="Default", save_state=True): + """ + Activate a plugin, , and remember it (in the config file). + + If you want the plugin to benefit from the configuration + utility defined by this manager, it is crucial to use this + method to activate a plugin and not call the plugin object's + ``activate`` method. In fact, this method will also "decorate" + the plugin object so that it can use this class's methods to + register its own options. + + By default, the plugin's activation is registered in the + config file but if you d'ont want this set the 'save_state' + argument to False. + """ + # first decorate the plugin + pta = self._component.getPluginByName(plugin_name,category_name) + if pta is None: + return None + self.__decoratePluginObject(category_name,plugin_name,pta.plugin_object) + # activate the plugin + plugin_object = self._component.activatePluginByName(plugin_name,category_name) + # check the activation and then optionally set the config option + if plugin_object.is_activated: + if save_state: + self.__addPluginToConfig(category_name,plugin_name) + return plugin_object + return None + + def deactivatePluginByName(self, plugin_name, category_name="Default", save_state=True): + """ + Deactivate a plugin, and remember it (in the config file). + + By default, the plugin's deactivation is registered in the + config file but if you d'ont want this set the ``save_state`` + argument to False. + """ + # activate the plugin + plugin_object = self._component.deactivatePluginByName(plugin_name,category_name) + if plugin_object is None: + return None + # check the deactivation and then optionnally set the config option + if not plugin_object.is_activated: + if save_state: + self.__removePluginFromConfig(category_name,plugin_name) + return plugin_object + return None + + def loadPlugins(self,callback=None): + """ + Walk through the plugins' places and look for plugins. Then + for each plugin candidate look for its category, load it and + stores it in the appropriate slot of the ``category_mapping``. + """ + self._component.loadPlugins(callback) + # now load the plugins according to the recorded configuration + if self.config_parser.has_section(self.CONFIG_SECTION_NAME): + # browse all the categories + for category_name in list(self._component.category_mapping.keys()): + # get the list of plugins to be activated for this + # category + option_name = "%s_plugins_to_load"%category_name + if self.config_parser.has_option(self.CONFIG_SECTION_NAME, + option_name): + plugin_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME, + option_name) + plugin_list = self.__getCategoryPluginsListFromConfig(plugin_list_str) + # activate all the plugins that should be + # activated + for plugin_name in plugin_list: + self.activatePluginByName(plugin_name,category_name) + + + + diff --git a/yapsy/FilteredPluginManager.py b/yapsy/FilteredPluginManager.py new file mode 100644 index 00000000..05b03ef1 --- /dev/null +++ b/yapsy/FilteredPluginManager.py @@ -0,0 +1,138 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + +""" +Role +==== + +Defines the basic mechanisms to have a plugin manager filter the +available list of plugins after locating them and before loading them. + +One use fo this would be to prevent untrusted plugins from entering +the system. + +To use it properly you must reimplement or monkey patch the +``IsPluginOk`` method, as in the following example:: + + # define a plugin manager (with you prefered options) + pm = PluginManager(...) + # decorate it with the Filtering mechanics + pm = FilteredPluginManager(pm) + # define a custom predicate that filters out plugins without descriptions + pm.isPluginOk = lambda x: x.description!="" + + +API +=== +""" + + +from yapsy.IPlugin import IPlugin +from yapsy.PluginManagerDecorator import PluginManagerDecorator + + +class FilteredPluginManager(PluginManagerDecorator): + """ + Base class for decorators which filter the plugins list + before they are loaded. + """ + + def __init__(self, + decorated_manager=None, + categories_filter={"Default":IPlugin}, + directories_list=None, + plugin_info_ext="yapsy-plugin"): + """ + """ + # Create the base decorator class + PluginManagerDecorator.__init__(self,decorated_manager, + categories_filter, + directories_list, + plugin_info_ext) + # prepare the mapping of the latest version of each plugin + self.rejectedPlugins = [ ] + + + + def filterPlugins(self): + """ + Go through the currently available candidates, and and either + leaves them, or moves them into the list of rejected Plugins. + + Can be overridden if overriding ``isPluginOk`` sentinel is not + powerful enough. + """ + self.rejectedPlugins = [ ] + for candidate_infofile, candidate_filepath, plugin_info in self._component.getPluginCandidates(): + if not self.isPluginOk( plugin_info): + self.rejectPluginCandidate((candidate_infofile, candidate_filepath, plugin_info) ) + + def rejectPluginCandidate(self,pluginTuple): + """ + Move a plugin from the candidates list to the rejected List. + """ + if pluginTuple in self.getPluginCandidates(): + self._component.removePluginCandidate(pluginTuple) + if not pluginTuple in self.rejectedPlugins: + self.rejectedPlugins.append(pluginTuple) + + def unrejectPluginCandidate(self,pluginTuple): + """ + Move a plugin from the rejected list to into the candidates + list. + """ + if not pluginTuple in self.getPluginCandidates(): + self._component.appendPluginCandidate(pluginTuple) + if pluginTuple in self.rejectedPlugins: + self.rejectedPlugins.remove(pluginTuple) + + def removePluginCandidate(self,pluginTuple): + """ + Remove a plugin from the list of candidates. + """ + if pluginTuple in self.getPluginCandidates(): + self._component.removePluginCandidate(pluginTuple) + if pluginTuple in self.rejectedPlugins: + self.rejectedPlugins.remove(pluginTuple) + + + def appendPluginCandidate(self,pluginTuple): + """ + Add a new candidate. + """ + if self.isPluginOk(pluginTuple[2]): + if pluginTuple not in self.getPluginCandidates(): + self._component.appendPluginCandidate(pluginTuple) + else: + if not pluginTuple in self.rejectedPlugins: + self.rejectedPlugins.append(pluginTuple) + + def isPluginOk(self,info): + """ + Sentinel function to detect if a plugin should be filtered. + + ``info`` is an instance of a ``PluginInfo`` and this method is + expected to return True if the corresponding plugin can be + accepted, and False if it must be filtered out. + + Subclasses should override this function and return false for + any plugin which they do not want to be loadable. + """ + return True + + def locatePlugins(self): + """ + locate and filter plugins. + """ + #Reset Catalogue + self.setCategoriesFilter(self._component.categories_interfaces) + #Reread and filter. + self._component.locatePlugins() + self.filterPlugins() + return len(self._component.getPluginCandidates()) + + def getRejectedPlugins(self): + """ + Return the list of rejected plugins. + """ + return self.rejectedPlugins[:] diff --git a/yapsy/IPlugin.py b/yapsy/IPlugin.py new file mode 100644 index 00000000..cbe2d327 --- /dev/null +++ b/yapsy/IPlugin.py @@ -0,0 +1,60 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + + +""" +Role +==== + +Defines the basic interfaces for a plugin. These interfaces are +inherited by the *core* class of a plugin. The *core* class of a +plugin is then the one that will be notified the +activation/deactivation of a plugin via the ``activate/deactivate`` +methods. + + +For simple (near trivial) plugin systems, one can directly use the +following interfaces. + +Extensibility +============= + +In your own software, you'll probably want to build derived classes of +the ``IPlugin`` class as it is a mere interface with no specific +functionality. + +Your software's plugins should then inherit your very own plugin class +(itself derived from ``IPlugin``). + +Where and how to code these plugins is explained in the section about +the :doc:`PluginManager`. + + +API +=== +""" + + +class IPlugin(object): + """ + The most simple interface to be inherited when creating a plugin. + """ + + def __init__(self): + """ + Set the basic variables. + """ + self.is_activated = False + + def activate(self): + """ + Called at plugin activation. + """ + self.is_activated = True + + def deactivate(self): + """ + Called when the plugin is disabled. + """ + self.is_activated = False + diff --git a/yapsy/IPluginLocator.py b/yapsy/IPluginLocator.py new file mode 100644 index 00000000..fb128925 --- /dev/null +++ b/yapsy/IPluginLocator.py @@ -0,0 +1,105 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + + + +""" +Role +==== + +``IPluginLocator`` defines the basic interface expected by a +``PluginManager`` to be able to locate plugins and get basic info +about each discovered plugin (name, version etc). + +API +=== + +""" + + +from yapsy import log + +class IPluginLocator(object): + """ + Plugin Locator interface with some methods already implemented to + manage the awkward backward compatible stuff. + """ + + def locatePlugins(self): + """ + Walk through the plugins' places and look for plugins. + + Return the discovered plugins as a list of + ``(candidate_infofile_path, candidate_file_path,plugin_info_instance)`` + and their number. + """ + raise NotImplementedError("locatePlugins must be reimplemented by %s" % self) + + def gatherCorePluginInfo(self, directory, filename): + """ + Return a ``PluginInfo`` as well as the ``ConfigParser`` used to build it. + + If filename is a valid plugin discovered by any of the known + strategy in use. Returns None,None otherwise. + """ + raise NotImplementedError("gatherPluginInfo must be reimplemented by %s" % self) + + # -------------------------------------------------------------------- + # Below are backward compatibility methods: if you inherit from + # IPluginLocator it's ok not to reimplement them, there will only + # be a warning message logged if they are called and not + # reimplemented. + # -------------------------------------------------------------------- + + def getPluginNameAndModuleFromStream(self,fileobj): + """ + DEPRECATED(>1.9): kept for backward compatibility + with existing PluginManager child classes. + + Return a 3-uple with the name of the plugin, its + module and the config_parser used to gather the core + data *in a tuple*, if the required info could be + localised, else return ``(None,None,None)``. + """ + log.warn("setPluginInfoClass was called but '%s' doesn't implement it." % self) + return None,None,None + + + def setPluginInfoClass(self, picls, names=None): + """ + DEPRECATED(>1.9): kept for backward compatibility + with existing PluginManager child classes. + + Set the class that holds PluginInfo. The class should inherit + from ``PluginInfo``. + """ + log.warn("setPluginInfoClass was called but '%s' doesn't implement it." % self) + + def getPluginInfoClass(self): + """ + DEPRECATED(>1.9): kept for backward compatibility + with existing PluginManager child classes. + + Get the class that holds PluginInfo. + """ + log.warn("getPluginInfoClass was called but '%s' doesn't implement it." % self) + return None + + def setPluginPlaces(self, directories_list): + """ + DEPRECATED(>1.9): kept for backward compatibility + with existing PluginManager child classes. + + Set the list of directories where to look for plugin places. + """ + log.warn("setPluginPlaces was called but '%s' doesn't implement it." % self) + + def updatePluginPlaces(self, directories_list): + """ + DEPRECATED(>1.9): kept for backward compatibility + with existing PluginManager child classes. + + Updates the list of directories where to look for plugin places. + """ + log.warn("updatePluginPlaces was called but '%s' doesn't implement it." % self) + diff --git a/yapsy/PluginFileLocator.py b/yapsy/PluginFileLocator.py new file mode 100644 index 00000000..24b3ed9d --- /dev/null +++ b/yapsy/PluginFileLocator.py @@ -0,0 +1,533 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + + +""" +Role +==== + +The ``PluginFileLocator`` locates plugins when they are accessible via the filesystem. + +It's default behaviour is to look for text files with the +'.yapsy-plugin' extensions and to read the plugin's decription in +them. + + +Customization +------------- + +The behaviour of a ``PluginFileLocator`` can be customized by instanciating it with a specific 'analyzer'. + +Two analyzers are already implemented and provided here: + + ``PluginFileAnalyzerWithInfoFile`` + + the default 'analyzer' that looks for plugin 'info files' as + text file with a predefined extension. This implements the way + yapsy looks for plugin since version 1. + + ``PluginFileAnalyzerMathingRegex`` + + look for files matching a regex and considers them as being + the plugin itself. + +All analyzers must enforce the + +It enforces the ``plugin locator`` policy as defined by ``IPluginLocator`` and used by ``PluginManager``. + + ``info_ext`` + + expects a plugin to be discovered through its *plugin info file*. + User just needs to provide an extension (without '.') to look + for *plugin_info_file*. + + ``regexp`` + + looks for file matching the given regular pattern expression. + User just needs to provide the regular pattern expression. + +All analyzers must enforce the policy represented by the ``IPluginFileAnalyzer`` interface. +""" + +import os +import re +from yapsy import log +import configparser + +from yapsy.PluginInfo import PluginInfo +from yapsy import PLUGIN_NAME_FORBIDEN_STRING +from yapsy.IPluginLocator import IPluginLocator + + + + +class IPluginFileAnalyzer(object): + """ + Define the methods expected by PluginFileLocator for its 'analyzer'. + """ + + def __init__(self,name): + self.name = name + + def isValidPlugin(self, filename): + """ + Check if the resource found at filename is a valid plugin. + """ + raise NotImplementedError("'isValidPlugin' must be reimplemented by %s" % self) + + + def getInfosDictFromPlugin(self, dirpath, filename): + """ + Returns the extracted plugin informations as a dictionary. + This function ensures that "name" and "path" are provided. + + *dirpath* is the full path to the directory where the plugin file is + + *filename* is the name (ie the basename) of the plugin file. + + If *callback* function has not been provided for this strategy, + we use the filename alone to extract minimal informations. + """ + raise NotImplementedError("'getInfosDictFromPlugin' must be reimplemented by %s" % self) + + +class PluginFileAnalyzerWithInfoFile(IPluginFileAnalyzer): + """ + Consider plugins described by a textual description file. + + A plugin is expected to be described by a text file ('ini' format) with a specific extension (.yapsy-plugin by default). + + This file must contain at least the following information:: + + [Core] + Name = name of the module + Module = relative_path/to/python_file_or_directory + + Optionnally the description file may also contain the following section (in addition to the above one):: + + [Documentation] + Author = Author Name + Version = Major.minor + Website = url_for_plugin + Description = A simple one-sentence description + + """ + def __init__(self, name, extensions="yapsy-plugin"): + """ + Creates a new analyzer named *name* and dedicated to check and analyze plugins described by a textual "info file". + + *name* name of the plugin. + + *extensions* the expected extensions for the plugin info file. May be a string or a tuple of strings if several extensions are expected. + """ + IPluginFileAnalyzer.__init__(self,name) + self.setPluginInfoExtension(extensions) + + + def setPluginInfoExtension(self,extensions): + """ + Set the extension that will identify a plugin info file. + + *extensions* May be a string or a tuple of strings if several extensions are expected. + """ + # Make sure extension is a tuple + if not isinstance(extensions, tuple): + extensions = (extensions, ) + self.expectedExtensions = extensions + + + def isValidPlugin(self, filename): + """ + Check if it is a valid plugin based on the given plugin info file extension(s). + If several extensions are provided, the first matching will cause the function + to exit successfully. + """ + res = False + for ext in self.expectedExtensions: + if filename.endswith(".%s" % ext): + res = True + break + return res + + def getPluginNameAndModuleFromStream(self, infoFileObject, candidate_infofile=None): + """ + Extract the name and module of a plugin from the + content of the info file that describes it and which + is stored in ``infoFileObject``. + + .. note:: Prefer using ``_extractCorePluginInfo`` + instead, whenever possible... + + .. warning:: ``infoFileObject`` must be a file-like object: + either an opened file for instance or a string + buffer wrapped in a StringIO instance as another + example. + + .. note:: ``candidate_infofile`` must be provided + whenever possible to get better error messages. + + Return a 3-uple with the name of the plugin, its + module and the config_parser used to gather the core + data *in a tuple*, if the required info could be + localised, else return ``(None,None,None)``. + + .. note:: This is supposed to be used internally by subclasses + and decorators. + """ + # parse the information buffer to get info about the plugin + config_parser = configparser.ConfigParser() + try: + config_parser.read_file(infoFileObject) + except Exception as e: + log.debug("Could not parse the plugin file '%s' (exception raised was '%s')" % (candidate_infofile,e)) + return (None, None, None) + # check if the basic info is available + if not config_parser.has_section("Core"): + log.debug("Plugin info file has no 'Core' section (in '%s')" % candidate_infofile) + return (None, None, None) + if not config_parser.has_option("Core","Name") or not config_parser.has_option("Core","Module"): + log.debug("Plugin info file has no 'Name' or 'Module' section (in '%s')" % candidate_infofile) + return (None, None, None) + # check that the given name is valid + name = config_parser.get("Core", "Name") + name = name.strip() + if PLUGIN_NAME_FORBIDEN_STRING in name: + log.debug("Plugin name contains forbiden character: %s (in '%s')" % (PLUGIN_NAME_FORBIDEN_STRING, + candidate_infofile)) + return (None, None, None) + return (name, config_parser.get("Core", "Module"), config_parser) + + def _extractCorePluginInfo(self,directory, filename): + """ + Gather the core information (name, and module to be loaded) + about a plugin described by it's info file (found at + 'directory/filename'). + + Return a dictionary with name and path of the plugin as well + as the ConfigParser instance used to collect these info. + + .. note:: This is supposed to be used internally by subclasses + and decorators. + """ + # now we can consider the file as a serious candidate + if not isinstance(filename, str): + # filename is a file object: use it + name, moduleName, config_parser = self.getPluginNameAndModuleFromStream(filename) + else: + candidate_infofile_path = os.path.join(directory, filename) + # parse the information file to get info about the plugin + with open(candidate_infofile_path) as candidate_infofile: + name, moduleName, config_parser = self.getPluginNameAndModuleFromStream(candidate_infofile,candidate_infofile_path) + if (name, moduleName, config_parser) == (None, None, None): + return (None,None) + infos = {"name":name, "path":os.path.join(directory, moduleName)} + return infos, config_parser + + def _extractBasicPluginInfo(self,directory, filename): + """ + Gather some basic documentation about the plugin described by + it's info file (found at 'directory/filename'). + + Return a dictionary containing the core information (name and + path) as well as as the 'documentation' info (version, author, + description etc). + + See also: + + ``self._extractCorePluginInfo`` + """ + infos, config_parser = self._extractCorePluginInfo(directory, filename) + # collect additional (but usually quite usefull) information + if infos and config_parser and config_parser.has_section("Documentation"): + if config_parser.has_option("Documentation","Author"): + infos["author"] = config_parser.get("Documentation", "Author") + if config_parser.has_option("Documentation","Version"): + infos["version"] = config_parser.get("Documentation", "Version") + if config_parser.has_option("Documentation","Website"): + infos["website"] = config_parser.get("Documentation", "Website") + if config_parser.has_option("Documentation","Copyright"): + infos["copyright"] = config_parser.get("Documentation", "Copyright") + if config_parser.has_option("Documentation","Description"): + infos["description"] = config_parser.get("Documentation", "Description") + return infos, config_parser + + def getInfosDictFromPlugin(self, dirpath, filename): + """ + Returns the extracted plugin informations as a dictionary. + This function ensures that "name" and "path" are provided. + + If *callback* function has not been provided for this strategy, + we use the filename alone to extract minimal informations. + """ + infos, config_parser = self._extractBasicPluginInfo(dirpath, filename) + if not infos or infos.get("name", None) is None: + raise ValueError("Missing *name* of the plugin in extracted infos.") + if not infos or infos.get("path", None) is None: + raise ValueError("Missing *path* of the plugin in extracted infos.") + return infos, config_parser + + +class PluginFileAnalyzerMathingRegex(IPluginFileAnalyzer): + """ + An analyzer that targets plugins decribed by files whose name match a given regex. + """ + def __init__(self, name, regexp): + IPluginFileAnalyzer.__init__(self,name) + self.regexp = regexp + + def isValidPlugin(self, filename): + """ + Checks if the given filename is a valid plugin for this Strategy + """ + reg = re.compile(self.regexp) + if reg.match(filename) is not None: + return True + return False + + def getInfosDictFromPlugin(self, dirpath, filename): + """ + Returns the extracted plugin informations as a dictionary. + This function ensures that "name" and "path" are provided. + """ + # use the filename alone to extract minimal informations. + infos = {} + module_name = os.path.splitext(filename)[0] + plugin_filename = os.path.join(dirpath,filename) + if module_name == "__init__": + module_name = os.path.basename(dirpath) + plugin_filename = dirpath + infos["name"] = "%s" % module_name + infos["path"] = plugin_filename + cf_parser = configparser.ConfigParser() + cf_parser.add_section("Core") + cf_parser.set("Core","Name",infos["name"]) + cf_parser.set("Core","Module",infos["path"]) + return infos,cf_parser + + + +class PluginFileLocator(IPluginLocator): + """ + Locates plugins on the file system using a set of analyzers to + determine what files actually corresponds to plugins. + + If more than one analyzer is being used, the first that will discover a + new plugin will avoid other strategies to find it too. + + By default each directory set as a "plugin place" is scanned + recursively. You can change that by a call to + ``disableRecursiveScan``. + """ + def __init__(self, analyzers=None, plugin_info_cls=PluginInfo): + """ + Defines the strategies, and the places for plugins to look into. + """ + IPluginLocator.__init__(self) + self._discovered_plugins = {} + self.setPluginPlaces(None) + self._analyzers = analyzers # analyzers used to locate plugins + if self._analyzers is None: + self._analyzers = [PluginFileAnalyzerWithInfoFile("info_ext")] + self._default_plugin_info_cls = PluginInfo + self._plugin_info_cls_map = {} + self._max_size = 1e3*1024 # in octets (by default 1 Mo) + self.recursive = True + + def disableRecursiveScan(self): + """ + Disable recursive scan of the directories given as plugin places. + """ + self.recursive = False + + def setAnalyzers(self, analyzers): + """ + Sets a new set of analyzers. + + .. warning:: the new analyzers won't be aware of the plugin + info class that may have been set via a previous + call to ``setPluginInfoClass``. + """ + self._analyzers = analyzers + + def removeAnalyzers(self, name): + """ + Removes analyzers of a given name. + """ + analyzersListCopy = self._analyzers[:] + foundAndRemoved = False + for obj in analyzersListCopy: + if obj.name == name: + self._analyzers.remove(obj) + foundAndRemoved = True + if not foundAndRemoved: + log.debug("'%s' is not a known strategy name: can't remove it." % name) + + def removeAllAnalyzer(self): + """ + Remove all analyzers. + """ + self._analyzers = [] + + def appendAnalyzer(self, analyzer): + """ + Append an analyzer to the existing list. + """ + self._analyzers.append(analyzer) + + + def _getInfoForPluginFromAnalyzer(self,analyzer,dirpath, filename): + """ + Return an instance of plugin_info_cls filled with data extracted by the analyzer. + + May return None if the analyzer fails to extract any info. + """ + plugin_info_dict,config_parser = analyzer.getInfosDictFromPlugin(dirpath, filename) + if plugin_info_dict is None: + return None + plugin_info_cls = self._plugin_info_cls_map.get(analyzer.name,self._default_plugin_info_cls) + plugin_info = plugin_info_cls(plugin_info_dict["name"],plugin_info_dict["path"]) + plugin_info.details = config_parser + return plugin_info + + def locatePlugins(self): + """ + Walk through the plugins' places and look for plugins. + + Return the candidates and number of plugins found. + """ +# print "%s.locatePlugins" % self.__class__ + _candidates = [] + _discovered = {} + for directory in map(os.path.abspath, self.plugins_places): + # first of all, is it a directory :) + if not os.path.isdir(directory): + log.debug("%s skips %s (not a directory)" % (self.__class__.__name__, directory)) + continue + if self.recursive: + debug_txt_mode = "recursively" + walk_iter = os.walk(directory, followlinks=True) + else: + debug_txt_mode = "non-recursively" + walk_iter = [(directory,[],os.listdir(directory))] + # iteratively walks through the directory + log.debug("%s walks (%s) into directory: %s" % (self.__class__.__name__, debug_txt_mode, directory)) + for item in walk_iter: + dirpath = item[0] + for filename in item[2]: + # print("testing candidate file %s" % filename) + for analyzer in self._analyzers: + # print("... with analyzer %s" % analyzer.name) + # eliminate the obvious non plugin files + if not analyzer.isValidPlugin(filename): + log.debug("%s is not a valid plugin for strategy %s" % (filename, analyzer.name)) + continue + candidate_infofile = os.path.join(dirpath, filename) + if candidate_infofile in _discovered: + log.debug("%s (with strategy %s) rejected because already discovered" % (candidate_infofile, analyzer.name)) + continue + log.debug("%s found a candidate:\n %s" % (self.__class__.__name__, candidate_infofile)) +# print candidate_infofile + plugin_info = self._getInfoForPluginFromAnalyzer(analyzer, dirpath, filename) + if plugin_info is None: + log.warning("Plugin candidate '%s' rejected by strategy '%s'" % (candidate_infofile, analyzer.name)) + break # we consider this was the good strategy to use for: it failed -> not a plugin -> don't try another strategy + # now determine the path of the file to execute, + # depending on wether the path indicated is a + # directory or a file +# print plugin_info.path + # Remember all the files belonging to a discovered + # plugin, so that strategies (if several in use) won't + # collide + if os.path.isdir(plugin_info.path): + candidate_filepath = os.path.join(plugin_info.path, "__init__") + # it is a package, adds all the files concerned + for _file in os.listdir(plugin_info.path): + if _file.endswith(".py"): + self._discovered_plugins[os.path.join(plugin_info.path, _file)] = candidate_filepath + _discovered[os.path.join(plugin_info.path, _file)] = candidate_filepath + elif (plugin_info.path.endswith(".py") and os.path.isfile(plugin_info.path)) or os.path.isfile(plugin_info.path+".py"): + candidate_filepath = plugin_info.path + if candidate_filepath.endswith(".py"): + candidate_filepath = candidate_filepath[:-3] + # it is a file, adds it + self._discovered_plugins[".".join((plugin_info.path, "py"))] = candidate_filepath + _discovered[".".join((plugin_info.path, "py"))] = candidate_filepath + else: + log.error("Plugin candidate rejected: cannot find the file or directory module for '%s'" % (candidate_infofile)) + break +# print candidate_filepath + _candidates.append((candidate_infofile, candidate_filepath, plugin_info)) + # finally the candidate_infofile must not be discovered again + _discovered[candidate_infofile] = candidate_filepath + self._discovered_plugins[candidate_infofile] = candidate_filepath +# print "%s found by strategy %s" % (candidate_filepath, analyzer.name) + return _candidates, len(_candidates) + + def gatherCorePluginInfo(self, directory, filename): + """ + Return a ``PluginInfo`` as well as the ``ConfigParser`` used to build it. + + If filename is a valid plugin discovered by any of the known + strategy in use. Returns None,None otherwise. + """ + for analyzer in self._analyzers: + # eliminate the obvious non plugin files + if not analyzer.isValidPlugin(filename): + continue + plugin_info = self._getInfoForPluginFromAnalyzer(analyzer,directory, filename) + return plugin_info,plugin_info.details + return None,None + + # ----------------------------------------------- + # Backward compatible methods + # Note: their implementation must be conform to their + # counterpart in yapsy<1.10 + # ----------------------------------------------- + + def getPluginNameAndModuleFromStream(self, infoFileObject, candidate_infofile=None): + for analyzer in self._analyzers: + if analyzer.name == "info_ext": + return analyzer.getPluginNameAndModuleFromStream(infoFileObject) + else: + raise RuntimeError("No current file analyzer is able to provide plugin information from stream") + + def setPluginInfoClass(self, picls, name=None): + """ + Set the class that holds PluginInfo. The class should inherit + from ``PluginInfo``. + + If name is given, then the class will be used only by the corresponding analyzer. + + If name is None, the class will be set for all analyzers. + """ + if name is None: + self._default_plugin_info_cls = picls + self._plugin_info_cls_map = {} + else: + self._plugin_info_cls_map[name] = picls + + def setPluginPlaces(self, directories_list): + """ + Set the list of directories where to look for plugin places. + """ + if directories_list is None: + directories_list = [os.path.dirname(__file__)] + self.plugins_places = directories_list + + def updatePluginPlaces(self, directories_list): + """ + Updates the list of directories where to look for plugin places. + """ + self.plugins_places = list(set.union(set(directories_list), set(self.plugins_places))) + + def setPluginInfoExtension(self, ext): + """ + DEPRECATED(>1.9): for backward compatibility. Directly configure the + IPluginLocator instance instead ! + + This will only work if the strategy "info_ext" is active + for locating plugins. + """ + for analyzer in self._analyzers: + if analyzer.name == "info_ext": + analyzer.setPluginInfoExtension(ext) diff --git a/yapsy/PluginInfo.py b/yapsy/PluginInfo.py new file mode 100644 index 00000000..c3cc8d51 --- /dev/null +++ b/yapsy/PluginInfo.py @@ -0,0 +1,214 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + + +""" +Role +==== + +Encapsulate a plugin instance as well as some metadata. + +API +=== +""" + +from configparser import ConfigParser +from distutils.version import StrictVersion + + +class PluginInfo(object): + """Representation of the most basic set of information related to a + given plugin such as its name, author, description... + + Any additional information can be stored ad retrieved in a + PluginInfo, when this one is created with a + ``ConfigParser.ConfigParser`` instance. + + This typically means that when metadata is read from a text file + (the original way for yapsy to describe plugins), all info that is + not part of the basic variables (name, path, version etc), can + still be accessed though the ``details`` member variables that + behaves like Python's ``ConfigParser.ConfigParser``. + + Warning: the instance associated with the ``details`` member + variable is never copied and used to store all plugin infos. If + you set it to a custom instance, it will be modified as soon as + another member variale of the plugin info is + changed. Alternatively, if you change the instance "outside" the + plugin info, it will also change the plugin info. + """ + + def __init__(self, plugin_name, plugin_path): + """ + Set the basic information (at least name and path) about the + plugin as well as the default values for other usefull + variables. + + *plugin_name* is a simple string describing the name of + the plugin. + + *plugin_path* describe the location where the plugin can be + found. + + .. warning:: The ``path`` attribute is the full path to the + plugin if it is organised as a directory or the + full path to a file without the ``.py`` extension + if the plugin is defined by a simple file. In the + later case, the actual plugin is reached via + ``plugin_info.path+'.py'``. + """ + self.__details = ConfigParser() + self.name = plugin_name + self.path = plugin_path + self._ensureDetailsDefaultsAreBackwardCompatible() + # Storage for stuff created during the plugin lifetime + self.plugin_object = None + self.categories = [] + self.error = None + + + def __setDetails(self,cfDetails): + """ + Fill in all details by storing a ``ConfigParser`` instance. + + .. warning: The values for ``plugin_name`` and + ``plugin_path`` given a init time will superseed + any value found in ``cfDetails`` in section + 'Core' for the options 'Name' and 'Module' (this + is mostly for backward compatibility). + """ + bkp_name = self.name + bkp_path = self.path + self.__details = cfDetails + self.name = bkp_name + self.path = bkp_path + self._ensureDetailsDefaultsAreBackwardCompatible() + + def __getDetails(self): + return self.__details + + def __getName(self): + return self.details.get("Core","Name") + + def __setName(self, name): + if not self.details.has_section("Core"): + self.details.add_section("Core") + self.details.set("Core","Name",name) + + + def __getPath(self): + return self.details.get("Core","Module") + + def __setPath(self,path): + if not self.details.has_section("Core"): + self.details.add_section("Core") + self.details.set("Core","Module",path) + + + def __getVersion(self): + return StrictVersion(self.details.get("Documentation","Version")) + + def setVersion(self, vstring): + """ + Set the version of the plugin. + + Used by subclasses to provide different handling of the + version number. + """ + if isinstance(vstring,StrictVersion): + vstring = str(vstring) + if not self.details.has_section("Documentation"): + self.details.add_section("Documentation") + self.details.set("Documentation","Version",vstring) + + def __getAuthor(self): + return self.details.get("Documentation","Author") + + def __setAuthor(self,author): + if not self.details.has_section("Documentation"): + self.details.add_section("Documentation") + self.details.set("Documentation","Author",author) + + + def __getCopyright(self): + return self.details.get("Documentation","Copyright") + + def __setCopyright(self,copyrightTxt): + if not self.details.has_section("Documentation"): + self.details.add_section("Documentation") + self.details.set("Documentation","Copyright",copyrightTxt) + + + def __getWebsite(self): + return self.details.get("Documentation","Website") + + def __setWebsite(self,website): + if not self.details.has_section("Documentation"): + self.details.add_section("Documentation") + self.details.set("Documentation","Website",website) + + + def __getDescription(self): + return self.details.get("Documentation","Description") + + def __setDescription(self,description): + if not self.details.has_section("Documentation"): + self.details.add_section("Documentation") + return self.details.set("Documentation","Description",description) + + + def __getCategory(self): + """ + DEPRECATED (>1.9): Mimic former behaviour when what is + noz the first category was considered as the only one the + plugin belonged to. + """ + if self.categories: + return self.categories[0] + else: + return "UnknownCategory" + + def __setCategory(self,c): + """ + DEPRECATED (>1.9): Mimic former behaviour by making so + that if a category is set as it it was the only category to + which the plugin belongs, then a __getCategory will return + this newly set category. + """ + self.categories = [c] + self.categories + + name = property(fget=__getName,fset=__setName) + path = property(fget=__getPath,fset=__setPath) + version = property(fget=__getVersion,fset=setVersion) + author = property(fget=__getAuthor,fset=__setAuthor) + copyright = property(fget=__getCopyright,fset=__setCopyright) + website = property(fget=__getWebsite,fset=__setWebsite) + description = property(fget=__getDescription,fset=__setDescription) + details = property(fget=__getDetails,fset=__setDetails) + # deprecated (>1.9): plugins are not longer associated to a + # single category ! + category = property(fget=__getCategory,fset=__setCategory) + + def _getIsActivated(self): + """ + Return the activated state of the plugin object. + Makes it possible to define a property. + """ + return self.plugin_object.is_activated + + is_activated = property(fget=_getIsActivated) + + def _ensureDetailsDefaultsAreBackwardCompatible(self): + """ + Internal helper function. + """ + if not self.details.has_option("Documentation","Author"): + self.author = "Unknown" + if not self.details.has_option("Documentation","Version"): + self.version = "0.0" + if not self.details.has_option("Documentation","Website"): + self.website = "None" + if not self.details.has_option("Documentation","Copyright"): + self.copyright = "Unknown" + if not self.details.has_option("Documentation","Description"): + self.description = "" diff --git a/yapsy/PluginManager.py b/yapsy/PluginManager.py new file mode 100644 index 00000000..9ac966ba --- /dev/null +++ b/yapsy/PluginManager.py @@ -0,0 +1,663 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + +""" +Role +==== + +The ``PluginManager`` loads plugins that enforce the `Plugin +Description Policy`_, and offers the most simple methods to activate +and deactivate the plugins once they are loaded. + +.. note:: It may also classify the plugins in various categories, but + this behaviour is optional and if not specified elseway all + plugins are stored in the same default category. + +.. note:: It is often more useful to have the plugin manager behave + like singleton, this functionality is provided by + ``PluginManagerSingleton`` + + +Plugin Description Policy +========================= + +When creating a ``PluginManager`` instance, one should provide it with +a list of directories where plugins may be found. In each directory, +a plugin should contain the following elements: + +For a *Standard* plugin: + + ``myplugin.yapsy-plugin`` + + A *plugin info file* identical to the one previously described. + + ``myplugin`` + + A directory ontaining an actual Python plugin (ie with a + ``__init__.py`` file that makes it importable). The upper + namespace of the plugin should present a class inheriting the + ``IPlugin`` interface (the same remarks apply here as in the + previous case). + + +For a *Single file* plugin: + + ``myplugin.yapsy-plugin`` + + A *plugin info file* which is identified thanks to its extension, + see the `Plugin Info File Format`_ to see what should be in this + file. + + The extension is customisable at the ``PluginManager``'s + instanciation, since one may usually prefer the extension to bear + the application name. + + ``myplugin.py`` + + The source of the plugin. This file should at least define a class + inheriting the ``IPlugin`` interface. This class will be + instanciated at plugin loading and it will be notified the + activation/deactivation events. + + +Plugin Info File Format +----------------------- + +The plugin info file is a text file *encoded in ASCII or UTF-8* and +gathering, as its name suggests, some basic information about the +plugin. + +- it gives crucial information needed to be able to load the plugin + +- it provides some documentation like information like the plugin + author's name and a short description fo the plugin functionality. + +Here is an example of what such a file should contain:: + + [Core] + Name = My plugin Name + Module = the_name_of_the_pluginto_load_with_no_py_ending + + [Documentation] + Description = What my plugin broadly does + Author = My very own name + Version = the_version_number_of_the_plugin + Website = My very own website + + + +.. note:: From such plugin descriptions, the ``PluginManager`` will + built its own representations of the plugins as instances of + the :doc:`PluginInfo` class. + +Changing the default behaviour +============================== + +The default behaviour for locating and loading plugins can be changed +using the various options exposed on the interface via getters. + +The plugin detection, in particular, can be fully customized by +settting a custom plugin locator. See ``IPluginLocator`` for more +details on this. + + +Extensibility +============= + +Several mechanisms have been put up to help extending the basic +functionalities of the proivided classes. + +A few *hints* to help you extend those classes: + +If the new functionalities do not overlap the ones already +implemented, then they should be implemented as a Decorator class of the +base plugin. This should be done by inheriting the +``PluginManagerDecorator``. + +If this previous way is not possible, then the functionalities should +be added as a subclass of ``PluginManager``. + +.. note:: The first method is highly prefered since it makes it + possible to have a more flexible design where one can pick + several functionalities and litterally *add* them to get an + object corresponding to one's precise needs. + +API +=== + +""" + +import sys +import os +import imp + +from yapsy import log +from yapsy import NormalizePluginNameForModuleName + +from yapsy.IPlugin import IPlugin +from yapsy.IPluginLocator import IPluginLocator +# The follozing two imports are used to implement the default behaviour +from yapsy.PluginFileLocator import PluginFileAnalyzerWithInfoFile +from yapsy.PluginFileLocator import PluginFileLocator +# imported for backward compatibility (this variable was defined here +# before 1.10) +from yapsy import PLUGIN_NAME_FORBIDEN_STRING +# imported for backward compatibility (this PluginInfo was imported +# here before 1.10) +from yapsy.PluginInfo import PluginInfo + + +class PluginManager(object): + """ + Manage several plugins by ordering them in categories. + + The mechanism for searching and loading the plugins is already + implemented in this class so that it can be used directly (hence + it can be considered as a bit more than a mere interface) + + The file describing a plugin must be written in the syntax + compatible with Python's ConfigParser module as in the + `Plugin Info File Format`_ + """ + + def __init__(self, + categories_filter=None, + directories_list=None, + plugin_info_ext=None, + plugin_locator=None): + """ + Initialize the mapping of the categories and set the list of + directories where plugins may be. This can also be set by + direct call the methods: + + - ``setCategoriesFilter`` for ``categories_filter`` + - ``setPluginPlaces`` for ``directories_list`` + - ``setPluginInfoExtension`` for ``plugin_info_ext`` + + You may look at these function's documentation for the meaning + of each corresponding arguments. + """ + # as a good practice we don't use mutable objects as default + # values (these objects would become like static variables) + # for function/method arguments, but rather use None. + if categories_filter is None: + categories_filter = {"Default":IPlugin} + self.setCategoriesFilter(categories_filter) + plugin_locator = self._locatorDecide(plugin_info_ext, plugin_locator) + # plugin_locator could be either a dict defining strategies, or directly + # an IPluginLocator object + self.setPluginLocator(plugin_locator, directories_list) + + def _locatorDecide(self, plugin_info_ext, plugin_locator): + """ + For backward compatibility, we kept the *plugin_info_ext* argument. + Thus we may use it if provided. Returns the (possibly modified) + *plugin_locator*. + """ + specific_info_ext = plugin_info_ext is not None + specific_locator = plugin_locator is not None + if not specific_info_ext and not specific_locator: + # use the default behavior + res = PluginFileLocator() + elif not specific_info_ext and specific_locator: + # plugin_info_ext not used + res = plugin_locator + elif not specific_locator and specific_info_ext: + # plugin_locator not used, and plugin_info_ext provided + # -> compatibility mode + res = PluginFileLocator() + res.setAnalyzers([PluginFileAnalyzerWithInfoFile("info_ext",plugin_info_ext)]) + elif specific_info_ext and specific_locator: + # both provided... issue a warning that tells "plugin_info_ext" + # will be ignored + msg = ("Two incompatible arguments (%s) provided:", + "'plugin_info_ext' and 'plugin_locator'). Ignoring", + "'plugin_info_ext'.") + raise ValueError(" ".join(msg) % self.__class__.__name__) + return res + + def setCategoriesFilter(self, categories_filter): + """ + Set the categories of plugins to be looked for as well as the + way to recognise them. + + The ``categories_filter`` first defines the various categories + in which the plugins will be stored via its keys and it also + defines the interface tha has to be inherited by the actual + plugin class belonging to each category. + """ + self.categories_interfaces = categories_filter.copy() + # prepare the mapping from categories to plugin lists + self.category_mapping = {} + # also maps the plugin info files (useful to avoid loading + # twice the same plugin...) + self._category_file_mapping = {} + for categ in categories_filter: + self.category_mapping[categ] = [] + self._category_file_mapping[categ] = [] + + + def setPluginPlaces(self, directories_list): + """ + DEPRECATED(>1.9): directly configure the IPluginLocator instance instead ! + + Convenience method (actually call the IPluginLocator method) + """ + self.getPluginLocator().setPluginPlaces(directories_list) + + def updatePluginPlaces(self, directories_list): + """ + DEPRECATED(>1.9): directly configure the IPluginLocator instance instead ! + + Convenience method (actually call the IPluginLocator method) + """ + self.getPluginLocator().updatePluginPlaces(directories_list) + + def setPluginInfoExtension(self, ext): + """ + DEPRECATED(>1.9): for backward compatibility. Directly configure the + IPluginLocator instance instead ! + + .. warning:: This will only work if the strategy "info_ext" is + active for locating plugins. + """ + try: + self.getPluginLocator().setPluginInfoExtension(ext) + except KeyError: + log.error("Current plugin locator doesn't support setting the plugin info extension.") + + def setPluginInfoClass(self, picls, strategies=None): + """ + DEPRECATED(>1.9): directly configure the IPluginLocator instance instead ! + + Convenience method (actually call self.getPluginLocator().setPluginInfoClass) + + When using a ``PluginFileLocator`` you may restrict the + strategies to which the change of PluginInfo class will occur + by just giving the list of strategy names in the argument + "strategies" + """ + if strategies: + for name in strategies: + self.getPluginLocator().setPluginInfoClass(picls, name) + else: + self.getPluginLocator().setPluginInfoClass(picls) + + def getPluginInfoClass(self): + """ + DEPRECATED(>1.9): directly control that with the IPluginLocator + instance instead ! + + Get the class that holds PluginInfo. + """ + return self.getPluginLocator().getPluginInfoClass() + + def setPluginLocator(self, plugin_locator, dir_list=None, picls=None): + """ + Sets the strategy used to locate the basic information. + + See ``IPluginLocator`` for the policy that plugin_locator must enforce. + """ + if isinstance(plugin_locator, IPluginLocator): + self._plugin_locator = plugin_locator + if dir_list is not None: + self._plugin_locator.updatePluginPlaces(dir_list) + if picls is not None: + self.setPluginInfoClass(picls) + else: + raise TypeError("Unexpected format for plugin_locator ('%s' is not an instance of IPluginLocator)" % plugin_locator) + + def getPluginLocator(self): + """ + Grant direct access to the plugin locator. + """ + return self._plugin_locator + + def _gatherCorePluginInfo(self, directory, plugin_info_filename): + """ + DEPRECATED(>1.9): please use a specific plugin + locator if you need such information. + + Gather the core information (name, and module to be loaded) + about a plugin described by it's info file (found at + 'directory/filename'). + + Return an instance of ``PluginInfo`` and the + config_parser used to gather the core data *in a tuple*, if the + required info could be localised, else return ``(None,None)``. + + .. note:: This is supposed to be used internally by subclasses + and decorators. + + """ + return self.getPluginLocator().gatherCorePluginInfo(directory,plugin_info_filename) + + def _getPluginNameAndModuleFromStream(self,infoFileObject,candidate_infofile=""): + """ + DEPRECATED(>1.9): please use a specific plugin + locator if you need such information. + + Extract the name and module of a plugin from the + content of the info file that describes it and which + is stored in infoFileObject. + + .. note:: Prefer using ``_gatherCorePluginInfo`` + instead, whenever possible... + + .. warning:: ``infoFileObject`` must be a file-like + object: either an opened file for instance or a string + buffer wrapped in a StringIO instance as another + example. + + .. note:: ``candidate_infofile`` must be provided + whenever possible to get better error messages. + + Return a 3-uple with the name of the plugin, its + module and the config_parser used to gather the core + data *in a tuple*, if the required info could be + localised, else return ``(None,None,None)``. + + .. note:: This is supposed to be used internally by subclasses + and decorators. + """ + return self.getPluginLocator().getPluginNameAndModuleFromStream(infoFileObject, candidate_infofile) + + + def getCategories(self): + """ + Return the list of all categories. + """ + return list(self.category_mapping.keys()) + + def removePluginFromCategory(self, plugin,category_name): + """ + Remove a plugin from the category where it's assumed to belong. + """ + self.category_mapping[category_name].remove(plugin) + + + def appendPluginToCategory(self, plugin, category_name): + """ + Append a new plugin to the given category. + """ + self.category_mapping[category_name].append(plugin) + + def getPluginsOfCategory(self, category_name): + """ + Return the list of all plugins belonging to a category. + """ + return self.category_mapping[category_name][:] + + def getAllPlugins(self): + """ + Return the list of all plugins (belonging to all categories). + """ + allPlugins = set() + for pluginsOfOneCategory in self.category_mapping.values(): + allPlugins.update(pluginsOfOneCategory) + return list(allPlugins) + + def getPluginCandidates(self): + """ + Return the list of possible plugins. + + Each possible plugin (ie a candidate) is described by a 3-uple: + (info file path, python file path, plugin info instance) + + .. warning: locatePlugins must be called before ! + """ + if not hasattr(self, '_candidates'): + raise RuntimeError("locatePlugins must be called before getPluginCandidates") + return self._candidates[:] + + def removePluginCandidate(self,candidateTuple): + """ + Remove a given candidate from the list of plugins that should be loaded. + + The candidate must be represented by the same tuple described + in ``getPluginCandidates``. + + .. warning: locatePlugins must be called before ! + """ + if not hasattr(self, '_candidates'): + raise ValueError("locatePlugins must be called before removePluginCandidate") + self._candidates.remove(candidateTuple) + + def appendPluginCandidate(self, candidateTuple): + """ + Append a new candidate to the list of plugins that should be loaded. + + The candidate must be represented by the same tuple described + in ``getPluginCandidates``. + + .. warning: locatePlugins must be called before ! + """ + if not hasattr(self, '_candidates'): + raise ValueError("locatePlugins must be called before removePluginCandidate") + self._candidates.append(candidateTuple) + + def locatePlugins(self): + """ + Convenience method (actually call the IPluginLocator method) + """ + self._candidates, npc = self.getPluginLocator().locatePlugins() + + def loadPlugins(self, callback=None): + """ + Load the candidate plugins that have been identified through a + previous call to locatePlugins. For each plugin candidate + look for its category, load it and store it in the appropriate + slot of the ``category_mapping``. + + If a callback function is specified, call it before every load + attempt. The ``plugin_info`` instance is passed as an argument to + the callback. + """ +# print "%s.loadPlugins" % self.__class__ + if not hasattr(self, '_candidates'): + raise ValueError("locatePlugins must be called before loadPlugins") + + processed_plugins = [] + for candidate_infofile, candidate_filepath, plugin_info in self._candidates: + # make sure to attribute a unique module name to the one + # that is about to be loaded + plugin_module_name_template = NormalizePluginNameForModuleName("yapsy_loaded_plugin_" + plugin_info.name) + "_%d" + for plugin_name_suffix in range(len(sys.modules)): + plugin_module_name = plugin_module_name_template % plugin_name_suffix + if plugin_module_name not in sys.modules: + break + + # tolerance on the presence (or not) of the py extensions + if candidate_filepath.endswith(".py"): + candidate_filepath = candidate_filepath[:-3] + # if a callback exists, call it before attempting to load + # the plugin so that a message can be displayed to the + # user + if callback is not None: + callback(plugin_info) + # cover the case when the __init__ of a package has been + # explicitely indicated + if "__init__" in os.path.basename(candidate_filepath): + candidate_filepath = os.path.dirname(candidate_filepath) + try: + # use imp to correctly load the plugin as a module + if os.path.isdir(candidate_filepath): + candidate_module = imp.load_module(plugin_module_name,None,candidate_filepath,("py","r",imp.PKG_DIRECTORY)) + else: + with open(candidate_filepath+".py","r") as plugin_file: + candidate_module = imp.load_module(plugin_module_name,plugin_file,candidate_filepath+".py",("py","r",imp.PY_SOURCE)) + except Exception: + exc_info = sys.exc_info() + log.error("Unable to import plugin: %s" % candidate_filepath, exc_info=exc_info) + plugin_info.error = exc_info + processed_plugins.append(plugin_info) + continue + processed_plugins.append(plugin_info) + if "__init__" in os.path.basename(candidate_filepath): + sys.path.remove(plugin_info.path) + # now try to find and initialise the first subclass of the correct plugin interface + for element in (getattr(candidate_module,name) for name in dir(candidate_module)): + plugin_info_reference = None + for category_name in self.categories_interfaces: + try: + is_correct_subclass = issubclass(element, self.categories_interfaces[category_name]) + except TypeError: + continue + if is_correct_subclass and element is not self.categories_interfaces[category_name]: + current_category = category_name + if candidate_infofile not in self._category_file_mapping[current_category]: + # we found a new plugin: initialise it and search for the next one + if not plugin_info_reference: + plugin_info.plugin_object = element() + plugin_info_reference = plugin_info + plugin_info.categories.append(current_category) + self.category_mapping[current_category].append(plugin_info_reference) + self._category_file_mapping[current_category].append(candidate_infofile) + # Remove candidates list since we don't need them any more and + # don't need to take up the space + delattr(self, '_candidates') + return processed_plugins + + def collectPlugins(self): + """ + Walk through the plugins' places and look for plugins. Then + for each plugin candidate look for its category, load it and + stores it in the appropriate slot of the category_mapping. + """ +# print "%s.collectPlugins" % self.__class__ + self.locatePlugins() + self.loadPlugins() + + + def getPluginByName(self,name,category="Default"): + """ + Get the plugin correspoding to a given category and name + """ + if category in self.category_mapping: + for item in self.category_mapping[category]: + if item.name == name: + return item + return None + + def activatePluginByName(self,name,category="Default"): + """ + Activate a plugin corresponding to a given category + name. + """ + pta_item = self.getPluginByName(name,category) + if pta_item is not None: + plugin_to_activate = pta_item.plugin_object + if plugin_to_activate is not None: + log.debug("Activating plugin: %s.%s"% (category,name)) + plugin_to_activate.activate() + return plugin_to_activate + return None + + + def deactivatePluginByName(self,name,category="Default"): + """ + Desactivate a plugin corresponding to a given category + name. + """ + if category in self.category_mapping: + plugin_to_deactivate = None + for item in self.category_mapping[category]: + if item.name == name: + plugin_to_deactivate = item.plugin_object + break + if plugin_to_deactivate is not None: + log.debug("Deactivating plugin: %s.%s"% (category,name)) + plugin_to_deactivate.deactivate() + return plugin_to_deactivate + return None + + +class PluginManagerSingleton(object): + """ + Singleton version of the most basic plugin manager. + + Being a singleton, this class should not be initialised explicitly + and the ``get`` classmethod must be called instead. + + To call one of this class's methods you have to use the ``get`` + method in the following way: + ``PluginManagerSingleton.get().themethodname(theargs)`` + + To set up the various coonfigurables variables of the + PluginManager's behaviour please call explicitly the following + methods: + + - ``setCategoriesFilter`` for ``categories_filter`` + - ``setPluginPlaces`` for ``directories_list`` + - ``setPluginInfoExtension`` for ``plugin_info_ext`` + """ + + __instance = None + + __decoration_chain = None + + def __init__(self): + """ + Initialisation: this class should not be initialised + explicitly and the ``get`` classmethod must be called instead. + + To set up the various configurables variables of the + PluginManager's behaviour please call explicitly the following + methods: + + - ``setCategoriesFilter`` for ``categories_filter`` + - ``setPluginPlaces`` for ``directories_list`` + - ``setPluginInfoExtension`` for ``plugin_info_ext`` + """ + if self.__instance is not None: + raise Exception("Singleton can't be created twice !") + + def setBehaviour(self,list_of_pmd): + """ + Set the functionalities handled by the plugin manager by + giving a list of ``PluginManager`` decorators. + + This function shouldn't be called several time in a same + process, but if it is only the first call will have an effect. + + It also has an effect only if called before the initialisation + of the singleton. + + In cases where the function is indeed going to change anything + the ``True`` value is return, in all other cases, the ``False`` + value is returned. + """ + if self.__decoration_chain is None and self.__instance is None: + log.debug("Setting up a specific behaviour for the PluginManagerSingleton") + self.__decoration_chain = list_of_pmd + return True + else: + log.debug("Useless call to setBehaviour: the singleton is already instanciated of already has a behaviour.") + return False + setBehaviour = classmethod(setBehaviour) + + + def get(self): + """ + Actually create an instance + """ + if self.__instance is None: + if self.__decoration_chain is not None: + # Get the object to be decorated +# print self.__decoration_chain + pm = self.__decoration_chain[0]() + for cls_item in self.__decoration_chain[1:]: +# print cls_item + pm = cls_item(decorated_manager=pm) + # Decorate the whole object + self.__instance = pm + else: + # initialise the 'inner' PluginManagerDecorator + self.__instance = PluginManager() + log.debug("PluginManagerSingleton initialised") + return self.__instance + get = classmethod(get) + + +# For backward compatility import the most basic decorator (it changed +# place as of v1.8) +from yapsy.PluginManagerDecorator import PluginManagerDecorator + diff --git a/yapsy/PluginManagerDecorator.py b/yapsy/PluginManagerDecorator.py new file mode 100644 index 00000000..893ba5a9 --- /dev/null +++ b/yapsy/PluginManagerDecorator.py @@ -0,0 +1,102 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + + +""" +Role +==== + +Provide an easy way to build a chain of decorators extending the +functionalities of the default plugin manager, when it comes to +activating, deactivating or looking into loaded plugins. + +The ``PluginManagerDecorator`` is the base class to be inherited by +each element of the chain of decorator. + +.. warning:: If you want to customise the way the plugins are detected + and loaded, you should not try to do it by implementing a + new ``PluginManagerDecorator``. Instead, you'll have to + reimplement the :doc:`PluginManager` itself. And if you + do so by enforcing the ``PluginManager`` interface, just + giving an instance of your new manager class to the + ``PluginManagerDecorator`` should be transparent to the + "stantard" decorators. + +API +=== +""" + +import os + +from yapsy.IPlugin import IPlugin +from yapsy import log + + +class PluginManagerDecorator(object): + """ + Add several responsibilities to a plugin manager object in a + more flexible way than by mere subclassing. This is indeed an + implementation of the Decorator Design Patterns. + + + There is also an additional mechanism that allows for the + automatic creation of the object to be decorated when this object + is an instance of PluginManager (and not an instance of its + subclasses). This way we can keep the plugin managers creation + simple when the user don't want to mix a lot of 'enhancements' on + the base class. + """ + + def __init__(self, decorated_object=None, + # The following args will only be used if we need to + # create a default PluginManager + categories_filter={"Default":IPlugin}, + directories_list=[os.path.dirname(__file__)], + plugin_info_ext="yapsy-plugin"): + """ + Mimics the PluginManager's __init__ method and wraps an + instance of this class into this decorator class. + + - *If the decorated_object is not specified*, then we use the + PluginManager class to create the 'base' manager, and to do + so we will use the arguments: ``categories_filter``, + ``directories_list``, and ``plugin_info_ext`` or their + default value if they are not given. + + - *If the decorated object is given*, these last arguments are + simply **ignored** ! + + All classes (and especially subclasses of this one) that want + to be a decorator must accept the decorated manager as an + object passed to the init function under the exact keyword + ``decorated_object``. + """ + + if decorated_object is None: + log.debug("Creating a default PluginManager instance to be decorated.") + from yapsy.PluginManager import PluginManager + decorated_object = PluginManager(categories_filter, + directories_list, + plugin_info_ext) + self._component = decorated_object + + def __getattr__(self,name): + """ + Decorator trick copied from: + http://www.pasteur.fr/formation/infobio/python/ch18s06.html + """ +# print "looking for %s in %s" % (name, self.__class__) + return getattr(self._component,name) + + + def collectPlugins(self): + """ + This function will usually be a shortcut to successively call + ``self.locatePlugins`` and then ``self.loadPlugins`` which are + very likely to be redefined in each new decorator. + + So in order for this to keep on being a "shortcut" and not a + real pain, I'm redefining it here. + """ + self.locatePlugins() + self.loadPlugins() diff --git a/yapsy/VersionedPluginManager.py b/yapsy/VersionedPluginManager.py new file mode 100644 index 00000000..abee6d70 --- /dev/null +++ b/yapsy/VersionedPluginManager.py @@ -0,0 +1,137 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + +""" +Role +==== + +Defines the basic interface for a plugin manager that also keeps track +of versions of plugins + +API +=== +""" + + +from distutils.version import StrictVersion + +from yapsy.PluginInfo import PluginInfo +from yapsy.IPlugin import IPlugin +from yapsy.PluginManagerDecorator import PluginManagerDecorator + + +class VersionedPluginInfo(PluginInfo): + """ + Gather some info about a plugin such as its name, author, + description... + """ + + def __init__(self, plugin_name, plugin_path): + """ + Set the name and path of the plugin as well as the default + values for other usefull variables. + """ + PluginInfo.__init__(self, plugin_name, plugin_path) + # version number is now required to be a StrictVersion object + self.version = StrictVersion("0.0") + + def setVersion(self, vstring): + self.version = StrictVersion(vstring) + + +class VersionedPluginManager(PluginManagerDecorator): + """ + Handle plugin versioning by making sure that when several + versions are present for a same plugin, only the latest version is + manipulated via the standard methods (eg for activation and + deactivation) + + More precisely, for operations that must be applied on a single + named plugin at a time (``getPluginByName``, + ``activatePluginByName``, ``deactivatePluginByName`` etc) the + targetted plugin will always be the one with the latest version. + + .. note:: The older versions of a given plugin are still reachable + via the ``getPluginsOfCategoryFromAttic`` method. + """ + + def __init__(self, + decorated_manager=None, + categories_filter={"Default":IPlugin}, + directories_list=None, + plugin_info_ext="yapsy-plugin"): + """ + Create the plugin manager and record the ConfigParser instance + that will be used afterwards. + + The ``config_change_trigger`` argument can be used to set a + specific method to call when the configuration is + altered. This will let the client application manage the way + they want the configuration to be updated (e.g. write on file + at each change or at precise time intervalls or whatever....) + """ + # Create the base decorator class + PluginManagerDecorator.__init__(self,decorated_manager, + categories_filter, + directories_list, + plugin_info_ext) + self.setPluginInfoClass(VersionedPluginInfo) + # prepare the storage for the early version of the plugins, + # for which only the latest version is the one that will be + # kept in the "core" plugin storage. + self._prepareAttic() + + def _prepareAttic(self): + """ + Create and correctly initialize the storage where the wrong + version of the plugins will be stored. + """ + self._attic = {} + for categ in self.getCategories(): + self._attic[categ] = [] + + + def getLatestPluginsOfCategory(self,category_name): + """ + DEPRECATED(>1.8): Please consider using getPluginsOfCategory + instead. + + Return the list of all plugins belonging to a category. + """ + return self.getPluginsOfCategory(category_name) + + def loadPlugins(self, callback=None): + """ + Load the candidate plugins that have been identified through a + previous call to locatePlugins. + + In addition to the baseclass functionality, this subclass also + needs to find the latest version of each plugin. + """ + self._component.loadPlugins(callback) + for categ in self.getCategories(): + latest_plugins = {} + allPlugins = self.getPluginsOfCategory(categ) + # identify the latest version of each plugin + for plugin in allPlugins: + name = plugin.name + version = plugin.version + if name in latest_plugins: + if version > latest_plugins[name].version: + older_plugin = latest_plugins[name] + latest_plugins[name] = plugin + self.removePluginFromCategory(older_plugin,categ) + self._attic[categ].append(older_plugin) + else: + self.removePluginFromCategory(plugin,categ) + self._attic[categ].append(plugin) + else: + latest_plugins[name] = plugin + + def getPluginsOfCategoryFromAttic(self,categ): + """ + Access the older version of plugins for which only the latest + version is available through standard methods. + """ + return self._attic[categ] + diff --git a/yapsy/__init__.py b/yapsy/__init__.py new file mode 100644 index 00000000..693fad84 --- /dev/null +++ b/yapsy/__init__.py @@ -0,0 +1,88 @@ +#!/usr/bin/python +# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- + +""" + +Overview +======== + +Yapsy's main purpose is to offer a way to easily design a plugin +system in Python, and motivated by the fact that many other Python +plugin system are either too complicated for a basic use or depend on +a lot of libraries. Yapsy only depends on Python's standard library. + +|yapsy| basically defines two core classes: + +- a fully functional though very simple ``PluginManager`` class + +- an interface ``IPlugin`` which defines the interface of plugin + instances handled by the ``PluginManager`` + + +Getting started +=============== + +The basic classes defined by |yapsy| should work "as is" and enable +you to load and activate your plugins. So that the following code +should get you a fully working plugin management system:: + + from yapsy.PluginManager import PluginManager + + # Build the manager + simplePluginManager = PluginManager() + # Tell it the default place(s) where to find plugins + simplePluginManager.setPluginPlaces(["path/to/myplugins"]) + # Load all plugins + simplePluginManager.collectPlugins() + + # Activate all loaded plugins + for pluginInfo in simplePluginManager.getAllPlugins(): + simplePluginManager.activatePluginByName(pluginInfo.name) + + +.. note:: The ``plugin_info`` object (typically an instance of + ``IPlugin``) plays as *the entry point of each + plugin*. That's also where |yapsy| ceases to guide you: it's + up to you to define what your plugins can do and how you + want to talk to them ! Talking to your plugin will then look + very much like the following:: + + # Trigger 'some action' from the loaded plugins + for pluginInfo in simplePluginManager.getAllPlugins(): + pluginInfo.plugin_object.doSomething(...) + +""" + +__version__="1.10.423" + +# tell epydoc that the documentation is in the reStructuredText format +__docformat__ = "restructuredtext en" + +# provide a default named log for package-wide use +import logging +log = logging.getLogger('yapsy') + +# Some constants concerning the plugins +PLUGIN_NAME_FORBIDEN_STRING=";;" +""" +.. warning:: This string (';;' by default) is forbidden in plugin + names, and will be usable to describe lists of plugins + for instance (see :doc:`ConfigurablePluginManager`) +""" + +import re + +RE_NON_ALPHANUM = re.compile("\W") + +def NormalizePluginNameForModuleName(pluginName): + """ + Normalize a plugin name into a safer name for a module name. + + .. note:: may do a little more modifications than strictly + necessary and is not optimized for speed. + """ + if len(pluginName)==0: + return "_" + if pluginName[0].isdigit(): + pluginName = "_" + pluginName + return RE_NON_ALPHANUM.sub("_",pluginName) From fff765224b7126df7bab26929b8d85f47475a782 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 30 Dec 2014 11:16:23 +0100 Subject: [PATCH 002/260] Removed pyqtgraph --- pyqtgraph/GraphicsScene/GraphicsScene.py | 578 ----- pyqtgraph/GraphicsScene/__init__.py | 1 - pyqtgraph/GraphicsScene/exportDialog.py | 139 -- .../GraphicsScene/exportDialogTemplate.ui | 100 - .../exportDialogTemplate_pyqt.py | 68 - .../exportDialogTemplate_pyside.py | 63 - pyqtgraph/GraphicsScene/mouseEvents.py | 365 --- pyqtgraph/PIL_Fix/README | 11 - pyqtgraph/PlotData.py | 56 - pyqtgraph/Point.py | 155 -- pyqtgraph/Qt.py | 48 - pyqtgraph/SRTTransform.py | 259 --- pyqtgraph/SRTTransform3D.py | 314 --- pyqtgraph/SignalProxy.py | 118 - pyqtgraph/ThreadsafeTimer.py | 41 - pyqtgraph/Transform3D.py | 35 - pyqtgraph/Vector.py | 70 - pyqtgraph/WidgetGroup.py | 298 --- pyqtgraph/__init__.py | 341 --- pyqtgraph/canvas/Canvas.py | 608 ----- pyqtgraph/canvas/CanvasItem.py | 509 ----- pyqtgraph/canvas/CanvasManager.py | 76 - pyqtgraph/canvas/CanvasTemplate.ui | 149 -- pyqtgraph/canvas/CanvasTemplate_pyqt.py | 100 - pyqtgraph/canvas/CanvasTemplate_pyside.py | 95 - pyqtgraph/canvas/TransformGuiTemplate.ui | 75 - pyqtgraph/canvas/TransformGuiTemplate_pyqt.py | 60 - .../canvas/TransformGuiTemplate_pyside.py | 55 - pyqtgraph/canvas/__init__.py | 3 - pyqtgraph/colormap.py | 239 -- pyqtgraph/configfile.py | 202 -- pyqtgraph/console/CmdInput.py | 62 - pyqtgraph/console/Console.py | 375 --- pyqtgraph/console/__init__.py | 1 - pyqtgraph/console/template.ui | 184 -- pyqtgraph/console/template_pyqt.py | 111 - pyqtgraph/console/template_pyside.py | 106 - pyqtgraph/debug.py | 946 -------- pyqtgraph/dockarea/Container.py | 267 --- pyqtgraph/dockarea/Dock.py | 333 --- pyqtgraph/dockarea/DockArea.py | 319 --- pyqtgraph/dockarea/DockDrop.py | 128 -- pyqtgraph/dockarea/__init__.py | 2 - pyqtgraph/exceptionHandling.py | 90 - pyqtgraph/exporters/CSVExporter.py | 59 - pyqtgraph/exporters/Exporter.py | 175 -- pyqtgraph/exporters/ImageExporter.py | 101 - pyqtgraph/exporters/Matplotlib.py | 74 - pyqtgraph/exporters/PrintExporter.py | 65 - pyqtgraph/exporters/SVGExporter.py | 488 ---- pyqtgraph/exporters/__init__.py | 27 - pyqtgraph/flowchart/Flowchart.py | 955 -------- pyqtgraph/flowchart/FlowchartCtrlTemplate.ui | 120 - .../flowchart/FlowchartCtrlTemplate_pyqt.py | 71 - .../flowchart/FlowchartCtrlTemplate_pyside.py | 66 - pyqtgraph/flowchart/FlowchartGraphicsView.py | 109 - pyqtgraph/flowchart/FlowchartTemplate.ui | 98 - pyqtgraph/flowchart/FlowchartTemplate_pyqt.py | 59 - .../flowchart/FlowchartTemplate_pyside.py | 54 - pyqtgraph/flowchart/Node.py | 647 ------ pyqtgraph/flowchart/Terminal.py | 638 ------ pyqtgraph/flowchart/__init__.py | 4 - pyqtgraph/flowchart/eq.py | 36 - pyqtgraph/flowchart/library/Data.py | 356 --- pyqtgraph/flowchart/library/Display.py | 275 --- pyqtgraph/flowchart/library/Filters.py | 268 --- pyqtgraph/flowchart/library/Operators.py | 74 - pyqtgraph/flowchart/library/__init__.py | 103 - pyqtgraph/flowchart/library/common.py | 148 -- pyqtgraph/flowchart/library/functions.py | 336 --- pyqtgraph/frozenSupport.py | 52 - pyqtgraph/functions.py | 2023 ----------------- pyqtgraph/graphicsItems/ArrowItem.py | 124 - pyqtgraph/graphicsItems/AxisItem.py | 931 -------- pyqtgraph/graphicsItems/BarGraphItem.py | 149 -- pyqtgraph/graphicsItems/ButtonItem.py | 58 - pyqtgraph/graphicsItems/CurvePoint.py | 117 - pyqtgraph/graphicsItems/ErrorBarItem.py | 133 -- pyqtgraph/graphicsItems/FillBetweenItem.py | 23 - pyqtgraph/graphicsItems/GradientEditorItem.py | 910 -------- pyqtgraph/graphicsItems/GradientLegend.py | 114 - pyqtgraph/graphicsItems/GraphItem.py | 122 - pyqtgraph/graphicsItems/GraphicsItem.py | 587 ----- pyqtgraph/graphicsItems/GraphicsLayout.py | 154 -- pyqtgraph/graphicsItems/GraphicsObject.py | 32 - pyqtgraph/graphicsItems/GraphicsWidget.py | 59 - .../graphicsItems/GraphicsWidgetAnchor.py | 110 - pyqtgraph/graphicsItems/GridItem.py | 120 - pyqtgraph/graphicsItems/HistogramLUTItem.py | 205 -- pyqtgraph/graphicsItems/ImageItem.py | 453 ---- pyqtgraph/graphicsItems/InfiniteLine.py | 277 --- pyqtgraph/graphicsItems/IsocurveItem.py | 121 - pyqtgraph/graphicsItems/ItemGroup.py | 23 - pyqtgraph/graphicsItems/LabelItem.py | 142 -- pyqtgraph/graphicsItems/LegendItem.py | 173 -- pyqtgraph/graphicsItems/LinearRegionItem.py | 291 --- pyqtgraph/graphicsItems/MultiPlotItem.py | 69 - pyqtgraph/graphicsItems/PlotCurveItem.py | 560 ----- pyqtgraph/graphicsItems/PlotDataItem.py | 843 ------- pyqtgraph/graphicsItems/PlotItem/PlotItem.py | 1271 ----------- pyqtgraph/graphicsItems/PlotItem/__init__.py | 1 - .../PlotItem/plotConfigTemplate.ui | 343 --- .../PlotItem/plotConfigTemplate_pyqt.py | 173 -- .../PlotItem/plotConfigTemplate_pyside.py | 168 -- pyqtgraph/graphicsItems/ROI.py | 1903 ---------------- pyqtgraph/graphicsItems/ScaleBar.py | 104 - pyqtgraph/graphicsItems/ScatterPlotItem.py | 925 -------- pyqtgraph/graphicsItems/TextItem.py | 124 - pyqtgraph/graphicsItems/UIGraphicsItem.py | 124 - pyqtgraph/graphicsItems/VTickGroup.py | 113 - pyqtgraph/graphicsItems/ViewBox/ViewBox.py | 1570 ------------- .../graphicsItems/ViewBox/ViewBoxMenu.py | 278 --- pyqtgraph/graphicsItems/ViewBox/__init__.py | 1 - .../graphicsItems/ViewBox/axisCtrlTemplate.ui | 161 -- .../ViewBox/axisCtrlTemplate_pyqt.py | 93 - .../ViewBox/axisCtrlTemplate_pyside.py | 88 - pyqtgraph/graphicsItems/__init__.py | 21 - pyqtgraph/graphicsItems/tests/ViewBox.py | 95 - pyqtgraph/graphicsWindows.py | 80 - pyqtgraph/imageview/ImageView.py | 645 ------ pyqtgraph/imageview/ImageViewTemplate.ui | 252 -- pyqtgraph/imageview/ImageViewTemplate_pyqt.py | 160 -- .../imageview/ImageViewTemplate_pyside.py | 155 -- pyqtgraph/imageview/__init__.py | 6 - pyqtgraph/metaarray/MetaArray.py | 1474 ------------ pyqtgraph/metaarray/__init__.py | 1 - pyqtgraph/metaarray/license.txt | 8 - pyqtgraph/metaarray/readMeta.m | 86 - pyqtgraph/multiprocess/__init__.py | 24 - pyqtgraph/multiprocess/bootstrap.py | 28 - pyqtgraph/multiprocess/parallelizer.py | 330 --- pyqtgraph/multiprocess/processes.py | 472 ---- pyqtgraph/multiprocess/remoteproxy.py | 1069 --------- pyqtgraph/numpy_fix.py | 22 - pyqtgraph/opengl/GLGraphicsItem.py | 293 --- pyqtgraph/opengl/GLViewWidget.py | 436 ---- pyqtgraph/opengl/MeshData.py | 519 ----- pyqtgraph/opengl/__init__.py | 30 - pyqtgraph/opengl/glInfo.py | 16 - pyqtgraph/opengl/items/GLAxisItem.py | 64 - pyqtgraph/opengl/items/GLBarGraphItem.py | 29 - pyqtgraph/opengl/items/GLBoxItem.py | 88 - pyqtgraph/opengl/items/GLGridItem.py | 58 - pyqtgraph/opengl/items/GLImageItem.py | 90 - pyqtgraph/opengl/items/GLLinePlotItem.py | 101 - pyqtgraph/opengl/items/GLMeshItem.py | 223 -- pyqtgraph/opengl/items/GLScatterPlotItem.py | 183 -- pyqtgraph/opengl/items/GLSurfacePlotItem.py | 139 -- pyqtgraph/opengl/items/GLVolumeItem.py | 213 -- pyqtgraph/opengl/items/__init__.py | 0 pyqtgraph/opengl/shaders.py | 402 ---- pyqtgraph/ordereddict.py | 127 -- pyqtgraph/parametertree/Parameter.py | 710 ------ pyqtgraph/parametertree/ParameterItem.py | 165 -- pyqtgraph/parametertree/ParameterTree.py | 119 - pyqtgraph/parametertree/__init__.py | 5 - pyqtgraph/parametertree/parameterTypes.py | 648 ------ pyqtgraph/pgcollections.py | 477 ---- pyqtgraph/pixmaps/__init__.py | 26 - pyqtgraph/pixmaps/compile.py | 19 - pyqtgraph/pixmaps/pixmapData_2.py | 1 - pyqtgraph/pixmaps/pixmapData_3.py | 1 - pyqtgraph/ptime.py | 30 - pyqtgraph/python2_3.py | 60 - pyqtgraph/reload.py | 516 ----- pyqtgraph/units.py | 64 - pyqtgraph/widgets/BusyCursor.py | 24 - pyqtgraph/widgets/CheckTable.py | 93 - pyqtgraph/widgets/ColorButton.py | 91 - pyqtgraph/widgets/ColorMapWidget.py | 218 -- pyqtgraph/widgets/ComboBox.py | 41 - pyqtgraph/widgets/DataFilterWidget.py | 150 -- pyqtgraph/widgets/DataTreeWidget.py | 83 - pyqtgraph/widgets/FeedbackButton.py | 163 -- pyqtgraph/widgets/FileDialog.py | 14 - pyqtgraph/widgets/GradientWidget.py | 74 - pyqtgraph/widgets/GraphicsLayoutWidget.py | 12 - pyqtgraph/widgets/GraphicsView.py | 393 ---- pyqtgraph/widgets/HistogramLUTWidget.py | 33 - pyqtgraph/widgets/JoystickButton.py | 95 - pyqtgraph/widgets/LayoutWidget.py | 101 - pyqtgraph/widgets/MatplotlibWidget.py | 41 - pyqtgraph/widgets/MultiPlotWidget.py | 45 - pyqtgraph/widgets/PathButton.py | 50 - pyqtgraph/widgets/PlotWidget.py | 93 - pyqtgraph/widgets/ProgressDialog.py | 112 - pyqtgraph/widgets/RawImageWidget.py | 140 -- pyqtgraph/widgets/RemoteGraphicsView.py | 261 --- pyqtgraph/widgets/ScatterPlotWidget.py | 216 -- pyqtgraph/widgets/SpinBox.py | 503 ---- pyqtgraph/widgets/TableWidget.py | 288 --- pyqtgraph/widgets/TreeWidget.py | 284 --- pyqtgraph/widgets/ValueLabel.py | 73 - pyqtgraph/widgets/VerticalLabel.py | 99 - pyqtgraph/widgets/__init__.py | 21 - 195 files changed, 44567 deletions(-) delete mode 100644 pyqtgraph/GraphicsScene/GraphicsScene.py delete mode 100644 pyqtgraph/GraphicsScene/__init__.py delete mode 100644 pyqtgraph/GraphicsScene/exportDialog.py delete mode 100644 pyqtgraph/GraphicsScene/exportDialogTemplate.ui delete mode 100644 pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py delete mode 100644 pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py delete mode 100644 pyqtgraph/GraphicsScene/mouseEvents.py delete mode 100644 pyqtgraph/PIL_Fix/README delete mode 100644 pyqtgraph/PlotData.py delete mode 100644 pyqtgraph/Point.py delete mode 100644 pyqtgraph/Qt.py delete mode 100644 pyqtgraph/SRTTransform.py delete mode 100644 pyqtgraph/SRTTransform3D.py delete mode 100644 pyqtgraph/SignalProxy.py delete mode 100644 pyqtgraph/ThreadsafeTimer.py delete mode 100644 pyqtgraph/Transform3D.py delete mode 100644 pyqtgraph/Vector.py delete mode 100644 pyqtgraph/WidgetGroup.py delete mode 100644 pyqtgraph/__init__.py delete mode 100644 pyqtgraph/canvas/Canvas.py delete mode 100644 pyqtgraph/canvas/CanvasItem.py delete mode 100644 pyqtgraph/canvas/CanvasManager.py delete mode 100644 pyqtgraph/canvas/CanvasTemplate.ui delete mode 100644 pyqtgraph/canvas/CanvasTemplate_pyqt.py delete mode 100644 pyqtgraph/canvas/CanvasTemplate_pyside.py delete mode 100644 pyqtgraph/canvas/TransformGuiTemplate.ui delete mode 100644 pyqtgraph/canvas/TransformGuiTemplate_pyqt.py delete mode 100644 pyqtgraph/canvas/TransformGuiTemplate_pyside.py delete mode 100644 pyqtgraph/canvas/__init__.py delete mode 100644 pyqtgraph/colormap.py delete mode 100644 pyqtgraph/configfile.py delete mode 100644 pyqtgraph/console/CmdInput.py delete mode 100644 pyqtgraph/console/Console.py delete mode 100644 pyqtgraph/console/__init__.py delete mode 100644 pyqtgraph/console/template.ui delete mode 100644 pyqtgraph/console/template_pyqt.py delete mode 100644 pyqtgraph/console/template_pyside.py delete mode 100644 pyqtgraph/debug.py delete mode 100644 pyqtgraph/dockarea/Container.py delete mode 100644 pyqtgraph/dockarea/Dock.py delete mode 100644 pyqtgraph/dockarea/DockArea.py delete mode 100644 pyqtgraph/dockarea/DockDrop.py delete mode 100644 pyqtgraph/dockarea/__init__.py delete mode 100644 pyqtgraph/exceptionHandling.py delete mode 100644 pyqtgraph/exporters/CSVExporter.py delete mode 100644 pyqtgraph/exporters/Exporter.py delete mode 100644 pyqtgraph/exporters/ImageExporter.py delete mode 100644 pyqtgraph/exporters/Matplotlib.py delete mode 100644 pyqtgraph/exporters/PrintExporter.py delete mode 100644 pyqtgraph/exporters/SVGExporter.py delete mode 100644 pyqtgraph/exporters/__init__.py delete mode 100644 pyqtgraph/flowchart/Flowchart.py delete mode 100644 pyqtgraph/flowchart/FlowchartCtrlTemplate.ui delete mode 100644 pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py delete mode 100644 pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py delete mode 100644 pyqtgraph/flowchart/FlowchartGraphicsView.py delete mode 100644 pyqtgraph/flowchart/FlowchartTemplate.ui delete mode 100644 pyqtgraph/flowchart/FlowchartTemplate_pyqt.py delete mode 100644 pyqtgraph/flowchart/FlowchartTemplate_pyside.py delete mode 100644 pyqtgraph/flowchart/Node.py delete mode 100644 pyqtgraph/flowchart/Terminal.py delete mode 100644 pyqtgraph/flowchart/__init__.py delete mode 100644 pyqtgraph/flowchart/eq.py delete mode 100644 pyqtgraph/flowchart/library/Data.py delete mode 100644 pyqtgraph/flowchart/library/Display.py delete mode 100644 pyqtgraph/flowchart/library/Filters.py delete mode 100644 pyqtgraph/flowchart/library/Operators.py delete mode 100644 pyqtgraph/flowchart/library/__init__.py delete mode 100644 pyqtgraph/flowchart/library/common.py delete mode 100644 pyqtgraph/flowchart/library/functions.py delete mode 100644 pyqtgraph/frozenSupport.py delete mode 100644 pyqtgraph/functions.py delete mode 100644 pyqtgraph/graphicsItems/ArrowItem.py delete mode 100644 pyqtgraph/graphicsItems/AxisItem.py delete mode 100644 pyqtgraph/graphicsItems/BarGraphItem.py delete mode 100644 pyqtgraph/graphicsItems/ButtonItem.py delete mode 100644 pyqtgraph/graphicsItems/CurvePoint.py delete mode 100644 pyqtgraph/graphicsItems/ErrorBarItem.py delete mode 100644 pyqtgraph/graphicsItems/FillBetweenItem.py delete mode 100644 pyqtgraph/graphicsItems/GradientEditorItem.py delete mode 100644 pyqtgraph/graphicsItems/GradientLegend.py delete mode 100644 pyqtgraph/graphicsItems/GraphItem.py delete mode 100644 pyqtgraph/graphicsItems/GraphicsItem.py delete mode 100644 pyqtgraph/graphicsItems/GraphicsLayout.py delete mode 100644 pyqtgraph/graphicsItems/GraphicsObject.py delete mode 100644 pyqtgraph/graphicsItems/GraphicsWidget.py delete mode 100644 pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py delete mode 100644 pyqtgraph/graphicsItems/GridItem.py delete mode 100644 pyqtgraph/graphicsItems/HistogramLUTItem.py delete mode 100644 pyqtgraph/graphicsItems/ImageItem.py delete mode 100644 pyqtgraph/graphicsItems/InfiniteLine.py delete mode 100644 pyqtgraph/graphicsItems/IsocurveItem.py delete mode 100644 pyqtgraph/graphicsItems/ItemGroup.py delete mode 100644 pyqtgraph/graphicsItems/LabelItem.py delete mode 100644 pyqtgraph/graphicsItems/LegendItem.py delete mode 100644 pyqtgraph/graphicsItems/LinearRegionItem.py delete mode 100644 pyqtgraph/graphicsItems/MultiPlotItem.py delete mode 100644 pyqtgraph/graphicsItems/PlotCurveItem.py delete mode 100644 pyqtgraph/graphicsItems/PlotDataItem.py delete mode 100644 pyqtgraph/graphicsItems/PlotItem/PlotItem.py delete mode 100644 pyqtgraph/graphicsItems/PlotItem/__init__.py delete mode 100644 pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui delete mode 100644 pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py delete mode 100644 pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py delete mode 100644 pyqtgraph/graphicsItems/ROI.py delete mode 100644 pyqtgraph/graphicsItems/ScaleBar.py delete mode 100644 pyqtgraph/graphicsItems/ScatterPlotItem.py delete mode 100644 pyqtgraph/graphicsItems/TextItem.py delete mode 100644 pyqtgraph/graphicsItems/UIGraphicsItem.py delete mode 100644 pyqtgraph/graphicsItems/VTickGroup.py delete mode 100644 pyqtgraph/graphicsItems/ViewBox/ViewBox.py delete mode 100644 pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py delete mode 100644 pyqtgraph/graphicsItems/ViewBox/__init__.py delete mode 100644 pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui delete mode 100644 pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py delete mode 100644 pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py delete mode 100644 pyqtgraph/graphicsItems/__init__.py delete mode 100644 pyqtgraph/graphicsItems/tests/ViewBox.py delete mode 100644 pyqtgraph/graphicsWindows.py delete mode 100644 pyqtgraph/imageview/ImageView.py delete mode 100644 pyqtgraph/imageview/ImageViewTemplate.ui delete mode 100644 pyqtgraph/imageview/ImageViewTemplate_pyqt.py delete mode 100644 pyqtgraph/imageview/ImageViewTemplate_pyside.py delete mode 100644 pyqtgraph/imageview/__init__.py delete mode 100644 pyqtgraph/metaarray/MetaArray.py delete mode 100644 pyqtgraph/metaarray/__init__.py delete mode 100644 pyqtgraph/metaarray/license.txt delete mode 100644 pyqtgraph/metaarray/readMeta.m delete mode 100644 pyqtgraph/multiprocess/__init__.py delete mode 100644 pyqtgraph/multiprocess/bootstrap.py delete mode 100644 pyqtgraph/multiprocess/parallelizer.py delete mode 100644 pyqtgraph/multiprocess/processes.py delete mode 100644 pyqtgraph/multiprocess/remoteproxy.py delete mode 100644 pyqtgraph/numpy_fix.py delete mode 100644 pyqtgraph/opengl/GLGraphicsItem.py delete mode 100644 pyqtgraph/opengl/GLViewWidget.py delete mode 100644 pyqtgraph/opengl/MeshData.py delete mode 100644 pyqtgraph/opengl/__init__.py delete mode 100644 pyqtgraph/opengl/glInfo.py delete mode 100644 pyqtgraph/opengl/items/GLAxisItem.py delete mode 100644 pyqtgraph/opengl/items/GLBarGraphItem.py delete mode 100644 pyqtgraph/opengl/items/GLBoxItem.py delete mode 100644 pyqtgraph/opengl/items/GLGridItem.py delete mode 100644 pyqtgraph/opengl/items/GLImageItem.py delete mode 100644 pyqtgraph/opengl/items/GLLinePlotItem.py delete mode 100644 pyqtgraph/opengl/items/GLMeshItem.py delete mode 100644 pyqtgraph/opengl/items/GLScatterPlotItem.py delete mode 100644 pyqtgraph/opengl/items/GLSurfacePlotItem.py delete mode 100644 pyqtgraph/opengl/items/GLVolumeItem.py delete mode 100644 pyqtgraph/opengl/items/__init__.py delete mode 100644 pyqtgraph/opengl/shaders.py delete mode 100644 pyqtgraph/ordereddict.py delete mode 100644 pyqtgraph/parametertree/Parameter.py delete mode 100644 pyqtgraph/parametertree/ParameterItem.py delete mode 100644 pyqtgraph/parametertree/ParameterTree.py delete mode 100644 pyqtgraph/parametertree/__init__.py delete mode 100644 pyqtgraph/parametertree/parameterTypes.py delete mode 100644 pyqtgraph/pgcollections.py delete mode 100644 pyqtgraph/pixmaps/__init__.py delete mode 100644 pyqtgraph/pixmaps/compile.py delete mode 100644 pyqtgraph/pixmaps/pixmapData_2.py delete mode 100644 pyqtgraph/pixmaps/pixmapData_3.py delete mode 100644 pyqtgraph/ptime.py delete mode 100644 pyqtgraph/python2_3.py delete mode 100644 pyqtgraph/reload.py delete mode 100644 pyqtgraph/units.py delete mode 100644 pyqtgraph/widgets/BusyCursor.py delete mode 100644 pyqtgraph/widgets/CheckTable.py delete mode 100644 pyqtgraph/widgets/ColorButton.py delete mode 100644 pyqtgraph/widgets/ColorMapWidget.py delete mode 100644 pyqtgraph/widgets/ComboBox.py delete mode 100644 pyqtgraph/widgets/DataFilterWidget.py delete mode 100644 pyqtgraph/widgets/DataTreeWidget.py delete mode 100644 pyqtgraph/widgets/FeedbackButton.py delete mode 100644 pyqtgraph/widgets/FileDialog.py delete mode 100644 pyqtgraph/widgets/GradientWidget.py delete mode 100644 pyqtgraph/widgets/GraphicsLayoutWidget.py delete mode 100644 pyqtgraph/widgets/GraphicsView.py delete mode 100644 pyqtgraph/widgets/HistogramLUTWidget.py delete mode 100644 pyqtgraph/widgets/JoystickButton.py delete mode 100644 pyqtgraph/widgets/LayoutWidget.py delete mode 100644 pyqtgraph/widgets/MatplotlibWidget.py delete mode 100644 pyqtgraph/widgets/MultiPlotWidget.py delete mode 100644 pyqtgraph/widgets/PathButton.py delete mode 100644 pyqtgraph/widgets/PlotWidget.py delete mode 100644 pyqtgraph/widgets/ProgressDialog.py delete mode 100644 pyqtgraph/widgets/RawImageWidget.py delete mode 100644 pyqtgraph/widgets/RemoteGraphicsView.py delete mode 100644 pyqtgraph/widgets/ScatterPlotWidget.py delete mode 100644 pyqtgraph/widgets/SpinBox.py delete mode 100644 pyqtgraph/widgets/TableWidget.py delete mode 100644 pyqtgraph/widgets/TreeWidget.py delete mode 100644 pyqtgraph/widgets/ValueLabel.py delete mode 100644 pyqtgraph/widgets/VerticalLabel.py delete mode 100644 pyqtgraph/widgets/__init__.py diff --git a/pyqtgraph/GraphicsScene/GraphicsScene.py b/pyqtgraph/GraphicsScene/GraphicsScene.py deleted file mode 100644 index 8729d085..00000000 --- a/pyqtgraph/GraphicsScene/GraphicsScene.py +++ /dev/null @@ -1,578 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui - -from pyqtgraph.python2_3 import sortList -#try: - #from PyQt4 import QtOpenGL - #HAVE_OPENGL = True -#except ImportError: - #HAVE_OPENGL = False - -import weakref -from pyqtgraph.Point import Point -import pyqtgraph.functions as fn -import pyqtgraph.ptime as ptime -from .mouseEvents import * -import pyqtgraph.debug as debug -from . import exportDialog - -if hasattr(QtCore, 'PYQT_VERSION'): - try: - import sip - HAVE_SIP = True - except ImportError: - HAVE_SIP = False -else: - HAVE_SIP = False - - -__all__ = ['GraphicsScene'] - -class GraphicsScene(QtGui.QGraphicsScene): - """ - Extension of QGraphicsScene that implements a complete, parallel mouse event system. - (It would have been preferred to just alter the way QGraphicsScene creates and delivers - events, but this turned out to be impossible because the constructor for QGraphicsMouseEvent - is private) - - * Generates MouseClicked events in addition to the usual press/move/release events. - (This works around a problem where it is impossible to have one item respond to a - drag if another is watching for a click.) - * Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects - * Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu. - * Allows items to decide _before_ a mouse click which item will be the recipient of mouse events. - This lets us indicate unambiguously to the user which item they are about to click/drag on - * Eats mouseMove events that occur too soon after a mouse press. - * Reimplements items() and itemAt() to circumvent PyQt bug - - Mouse interaction is as follows: - - 1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents - as well as custom HoverEvents. - 2) Items are sent HoverEvents in Z-order and each item may optionally call event.acceptClicks(button), - acceptDrags(button) or both. If this method call returns True, this informs the item that _if_ - the user clicks/drags the specified mouse button, the item is guaranteed to be the - recipient of click/drag events (the item may wish to change its appearance to indicate this). - If the call to acceptClicks/Drags returns False, then the item is guaranteed to *not* receive - the requested event (because another item has already accepted it). - 3) If the mouse is clicked, a mousePressEvent is generated as usual. If any items accept this press event, then - No click/drag events will be generated and mouse interaction proceeds as defined by Qt. This allows - items to function properly if they are expecting the usual press/move/release sequence of events. - (It is recommended that items do NOT accept press events, and instead use click/drag events) - Note: The default implementation of QGraphicsItem.mousePressEvent will *accept* the event if the - item is has its Selectable or Movable flags enabled. You may need to override this behavior. - 4) If no item accepts the mousePressEvent, then the scene will begin delivering mouseDrag and/or mouseClick events. - If the mouse is moved a sufficient distance (or moved slowly enough) before the button is released, - then a mouseDragEvent is generated. - If no drag events are generated before the button is released, then a mouseClickEvent is generated. - 5) Click/drag events are delivered to the item that called acceptClicks/acceptDrags on the HoverEvent - in step 1. If no such items exist, then the scene attempts to deliver the events to items near the event. - ClickEvents may be delivered in this way even if no - item originally claimed it could accept the click. DragEvents may only be delivered this way if it is the initial - move in a drag. - """ - - sigMouseHover = QtCore.Signal(object) ## emits a list of objects hovered over - sigMouseMoved = QtCore.Signal(object) ## emits position of mouse on every move - sigMouseClicked = QtCore.Signal(object) ## emitted when mouse is clicked. Check for event.isAccepted() to see whether the event has already been acted on. - - sigPrepareForPaint = QtCore.Signal() ## emitted immediately before the scene is about to be rendered - - _addressCache = weakref.WeakValueDictionary() - - ExportDirectory = None - - @classmethod - def registerObject(cls, obj): - """ - Workaround for PyQt bug in qgraphicsscene.items() - All subclasses of QGraphicsObject must register themselves with this function. - (otherwise, mouse interaction with those objects will likely fail) - """ - if HAVE_SIP and isinstance(obj, sip.wrapper): - cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj - - - def __init__(self, clickRadius=2, moveDistance=5): - QtGui.QGraphicsScene.__init__(self) - self.setClickRadius(clickRadius) - self.setMoveDistance(moveDistance) - self.exportDirectory = None - - self.clickEvents = [] - self.dragButtons = [] - self.prepItems = weakref.WeakKeyDictionary() ## set of items with prepareForPaintMethods - self.mouseGrabber = None - self.dragItem = None - self.lastDrag = None - self.hoverItems = weakref.WeakKeyDictionary() - self.lastHoverEvent = None - #self.searchRect = QtGui.QGraphicsRectItem() - #self.searchRect.setPen(fn.mkPen(200,0,0)) - #self.addItem(self.searchRect) - - self.contextMenu = [QtGui.QAction("Export...", self)] - self.contextMenu[0].triggered.connect(self.showExportDialog) - - self.exportDialog = None - - def render(self, *args): - self.prepareForPaint() - return QtGui.QGraphicsScene.render(self, *args) - - def prepareForPaint(self): - """Called before every render. This method will inform items that the scene is about to - be rendered by emitting sigPrepareForPaint. - - This allows items to delay expensive processing until they know a paint will be required.""" - self.sigPrepareForPaint.emit() - - - def setClickRadius(self, r): - """ - Set the distance away from mouse clicks to search for interacting items. - When clicking, the scene searches first for items that directly intersect the click position - followed by any other items that are within a rectangle that extends r pixels away from the - click position. - """ - self._clickRadius = r - - def setMoveDistance(self, d): - """ - Set the distance the mouse must move after a press before mouseMoveEvents will be delivered. - This ensures that clicks with a small amount of movement are recognized as clicks instead of - drags. - """ - self._moveDistance = d - - def mousePressEvent(self, ev): - #print 'scenePress' - QtGui.QGraphicsScene.mousePressEvent(self, ev) - #print "mouseGrabberItem: ", self.mouseGrabberItem() - if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events - self.clickEvents.append(MouseClickEvent(ev)) - - ## set focus on the topmost focusable item under this click - items = self.items(ev.scenePos()) - for i in items: - if i.isEnabled() and i.isVisible() and int(i.flags() & i.ItemIsFocusable) > 0: - i.setFocus(QtCore.Qt.MouseFocusReason) - break - #else: - #addr = sip.unwrapinstance(sip.cast(self.mouseGrabberItem(), QtGui.QGraphicsItem)) - #item = GraphicsScene._addressCache.get(addr, self.mouseGrabberItem()) - #print "click grabbed by:", item - - def mouseMoveEvent(self, ev): - self.sigMouseMoved.emit(ev.scenePos()) - - ## First allow QGraphicsScene to deliver hoverEnter/Move/ExitEvents - QtGui.QGraphicsScene.mouseMoveEvent(self, ev) - - ## Next deliver our own HoverEvents - self.sendHoverEvents(ev) - - if int(ev.buttons()) != 0: ## button is pressed; send mouseMoveEvents and mouseDragEvents - QtGui.QGraphicsScene.mouseMoveEvent(self, ev) - if self.mouseGrabberItem() is None: - now = ptime.time() - init = False - ## keep track of which buttons are involved in dragging - for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MidButton, QtCore.Qt.RightButton]: - if int(ev.buttons() & btn) == 0: - continue - if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet - cev = [e for e in self.clickEvents if int(e.button()) == int(btn)][0] - dist = Point(ev.screenPos() - cev.screenPos()) - if dist.length() < self._moveDistance and now - cev.time() < 0.5: - continue - init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True - self.dragButtons.append(int(btn)) - - ## If we have dragged buttons, deliver a drag event - if len(self.dragButtons) > 0: - if self.sendDragEvent(ev, init=init): - ev.accept() - - def leaveEvent(self, ev): ## inform items that mouse is gone - if len(self.dragButtons) == 0: - self.sendHoverEvents(ev, exitOnly=True) - - - def mouseReleaseEvent(self, ev): - #print 'sceneRelease' - if self.mouseGrabberItem() is None: - #print "sending click/drag event" - if ev.button() in self.dragButtons: - if self.sendDragEvent(ev, final=True): - #print "sent drag event" - ev.accept() - self.dragButtons.remove(ev.button()) - else: - cev = [e for e in self.clickEvents if int(e.button()) == int(ev.button())] - if self.sendClickEvent(cev[0]): - #print "sent click event" - ev.accept() - self.clickEvents.remove(cev[0]) - - if int(ev.buttons()) == 0: - self.dragItem = None - self.dragButtons = [] - self.clickEvents = [] - self.lastDrag = None - QtGui.QGraphicsScene.mouseReleaseEvent(self, ev) - - self.sendHoverEvents(ev) ## let items prepare for next click/drag - - def mouseDoubleClickEvent(self, ev): - QtGui.QGraphicsScene.mouseDoubleClickEvent(self, ev) - if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events - self.clickEvents.append(MouseClickEvent(ev, double=True)) - - def sendHoverEvents(self, ev, exitOnly=False): - ## if exitOnly, then just inform all previously hovered items that the mouse has left. - - if exitOnly: - acceptable=False - items = [] - event = HoverEvent(None, acceptable) - else: - acceptable = int(ev.buttons()) == 0 ## if we are in mid-drag, do not allow items to accept the hover event. - event = HoverEvent(ev, acceptable) - items = self.itemsNearEvent(event, hoverable=True) - self.sigMouseHover.emit(items) - - prevItems = list(self.hoverItems.keys()) - - for item in items: - if hasattr(item, 'hoverEvent'): - event.currentItem = item - if item not in self.hoverItems: - self.hoverItems[item] = None - event.enter = True - else: - prevItems.remove(item) - event.enter = False - - try: - item.hoverEvent(event) - except: - debug.printExc("Error sending hover event:") - - event.enter = False - event.exit = True - for item in prevItems: - event.currentItem = item - try: - item.hoverEvent(event) - except: - debug.printExc("Error sending hover exit event:") - finally: - del self.hoverItems[item] - - if hasattr(ev, 'buttons') and int(ev.buttons()) == 0: - self.lastHoverEvent = event ## save this so we can ask about accepted events later. - - - def sendDragEvent(self, ev, init=False, final=False): - ## Send a MouseDragEvent to the current dragItem or to - ## items near the beginning of the drag - event = MouseDragEvent(ev, self.clickEvents[0], self.lastDrag, start=init, finish=final) - #print "dragEvent: init=", init, 'final=', final, 'self.dragItem=', self.dragItem - if init and self.dragItem is None: - if self.lastHoverEvent is not None: - acceptedItem = self.lastHoverEvent.dragItems().get(event.button(), None) - else: - acceptedItem = None - - if acceptedItem is not None: - #print "Drag -> pre-selected item:", acceptedItem - self.dragItem = acceptedItem - event.currentItem = self.dragItem - try: - self.dragItem.mouseDragEvent(event) - except: - debug.printExc("Error sending drag event:") - - else: - #print "drag -> new item" - for item in self.itemsNearEvent(event): - #print "check item:", item - if not item.isVisible() or not item.isEnabled(): - continue - if hasattr(item, 'mouseDragEvent'): - event.currentItem = item - try: - item.mouseDragEvent(event) - except: - debug.printExc("Error sending drag event:") - if event.isAccepted(): - #print " --> accepted" - self.dragItem = item - if int(item.flags() & item.ItemIsFocusable) > 0: - item.setFocus(QtCore.Qt.MouseFocusReason) - break - elif self.dragItem is not None: - event.currentItem = self.dragItem - try: - self.dragItem.mouseDragEvent(event) - except: - debug.printExc("Error sending hover exit event:") - - self.lastDrag = event - - return event.isAccepted() - - - def sendClickEvent(self, ev): - ## if we are in mid-drag, click events may only go to the dragged item. - if self.dragItem is not None and hasattr(self.dragItem, 'mouseClickEvent'): - ev.currentItem = self.dragItem - self.dragItem.mouseClickEvent(ev) - - ## otherwise, search near the cursor - else: - if self.lastHoverEvent is not None: - acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None) - else: - acceptedItem = None - - if acceptedItem is not None: - ev.currentItem = acceptedItem - try: - acceptedItem.mouseClickEvent(ev) - except: - debug.printExc("Error sending click event:") - else: - for item in self.itemsNearEvent(ev): - if not item.isVisible() or not item.isEnabled(): - continue - if hasattr(item, 'mouseClickEvent'): - ev.currentItem = item - try: - item.mouseClickEvent(ev) - except: - debug.printExc("Error sending click event:") - - if ev.isAccepted(): - if int(item.flags() & item.ItemIsFocusable) > 0: - item.setFocus(QtCore.Qt.MouseFocusReason) - break - #if not ev.isAccepted() and ev.button() is QtCore.Qt.RightButton: - #print "GraphicsScene emitting sigSceneContextMenu" - #self.sigMouseClicked.emit(ev) - #ev.accept() - self.sigMouseClicked.emit(ev) - return ev.isAccepted() - - #def claimEvent(self, item, button, eventType): - #key = (button, eventType) - #if key in self.claimedEvents: - #return False - #self.claimedEvents[key] = item - #print "event", key, "claimed by", item - #return True - - - def items(self, *args): - #print 'args:', args - items = QtGui.QGraphicsScene.items(self, *args) - ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, - ## then the object returned will be different than the actual item that was originally added to the scene - items2 = list(map(self.translateGraphicsItem, items)) - #if HAVE_SIP and isinstance(self, sip.wrapper): - #items2 = [] - #for i in items: - #addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem)) - #i2 = GraphicsScene._addressCache.get(addr, i) - ##print i, "==>", i2 - #items2.append(i2) - #print 'items:', items - return items2 - - def selectedItems(self, *args): - items = QtGui.QGraphicsScene.selectedItems(self, *args) - ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, - ## then the object returned will be different than the actual item that was originally added to the scene - #if HAVE_SIP and isinstance(self, sip.wrapper): - #items2 = [] - #for i in items: - #addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem)) - #i2 = GraphicsScene._addressCache.get(addr, i) - ##print i, "==>", i2 - #items2.append(i2) - items2 = list(map(self.translateGraphicsItem, items)) - - #print 'items:', items - return items2 - - def itemAt(self, *args): - item = QtGui.QGraphicsScene.itemAt(self, *args) - - ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, - ## then the object returned will be different than the actual item that was originally added to the scene - #if HAVE_SIP and isinstance(self, sip.wrapper): - #addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem)) - #item = GraphicsScene._addressCache.get(addr, item) - #return item - return self.translateGraphicsItem(item) - - def itemsNearEvent(self, event, selMode=QtCore.Qt.IntersectsItemShape, sortOrder=QtCore.Qt.DescendingOrder, hoverable=False): - """ - Return an iterator that iterates first through the items that directly intersect point (in Z order) - followed by any other items that are within the scene's click radius. - """ - #tr = self.getViewWidget(event.widget()).transform() - view = self.views()[0] - tr = view.viewportTransform() - r = self._clickRadius - rect = view.mapToScene(QtCore.QRect(0, 0, 2*r, 2*r)).boundingRect() - - seen = set() - if hasattr(event, 'buttonDownScenePos'): - point = event.buttonDownScenePos() - else: - point = event.scenePos() - w = rect.width() - h = rect.height() - rgn = QtCore.QRectF(point.x()-w, point.y()-h, 2*w, 2*h) - #self.searchRect.setRect(rgn) - - - items = self.items(point, selMode, sortOrder, tr) - - ## remove items whose shape does not contain point (scene.items() apparently sucks at this) - items2 = [] - for item in items: - if hoverable and not hasattr(item, 'hoverEvent'): - continue - shape = item.shape() - if shape is None: - continue - if item.mapToScene(shape).contains(point): - items2.append(item) - - ## Sort by descending Z-order (don't trust scene.itms() to do this either) - ## use 'absolute' z value, which is the sum of all item/parent ZValues - def absZValue(item): - if item is None: - return 0 - return item.zValue() + absZValue(item.parentItem()) - - sortList(items2, lambda a,b: cmp(absZValue(b), absZValue(a))) - - return items2 - - #for item in items: - ##seen.add(item) - - #shape = item.mapToScene(item.shape()) - #if not shape.contains(point): - #continue - #yield item - #for item in self.items(rgn, selMode, sortOrder, tr): - ##if item not in seen: - #yield item - - def getViewWidget(self): - return self.views()[0] - - #def getViewWidget(self, widget): - ### same pyqt bug -- mouseEvent.widget() doesn't give us the original python object. - ### [[doesn't seem to work correctly]] - #if HAVE_SIP and isinstance(self, sip.wrapper): - #addr = sip.unwrapinstance(sip.cast(widget, QtGui.QWidget)) - ##print "convert", widget, addr - #for v in self.views(): - #addr2 = sip.unwrapinstance(sip.cast(v, QtGui.QWidget)) - ##print " check:", v, addr2 - #if addr2 == addr: - #return v - #else: - #return widget - - def addParentContextMenus(self, item, menu, event): - """ - Can be called by any item in the scene to expand its context menu to include parent context menus. - Parents may implement getContextMenus to add new menus / actions to the existing menu. - getContextMenus must accept 1 argument (the event that generated the original menu) and - return a single QMenu or a list of QMenus. - - The final menu will look like: - - | Original Item 1 - | Original Item 2 - | ... - | Original Item N - | ------------------ - | Parent Item 1 - | Parent Item 2 - | ... - | Grandparent Item 1 - | ... - - - ============== ================================================== - **Arguments:** - item The item that initially created the context menu - (This is probably the item making the call to this function) - menu The context menu being shown by the item - event The original event that triggered the menu to appear. - ============== ================================================== - """ - - #items = self.itemsNearEvent(ev) - menusToAdd = [] - while item is not self: - item = item.parentItem() - - if item is None: - item = self - - if not hasattr(item, "getContextMenus"): - continue - - subMenus = item.getContextMenus(event) - if subMenus is None: - continue - if type(subMenus) is not list: ## so that some items (like FlowchartViewBox) can return multiple menus - subMenus = [subMenus] - - for sm in subMenus: - menusToAdd.append(sm) - - if len(menusToAdd) > 0: - menu.addSeparator() - - for m in menusToAdd: - if isinstance(m, QtGui.QMenu): - menu.addMenu(m) - elif isinstance(m, QtGui.QAction): - menu.addAction(m) - else: - raise Exception("Cannot add object %s (type=%s) to QMenu." % (str(m), str(type(m)))) - - return menu - - def getContextMenus(self, event): - self.contextMenuItem = event.acceptedItem - return self.contextMenu - - def showExportDialog(self): - if self.exportDialog is None: - self.exportDialog = exportDialog.ExportDialog(self) - self.exportDialog.show(self.contextMenuItem) - - @staticmethod - def translateGraphicsItem(item): - ## for fixing pyqt bugs where the wrong item is returned - if HAVE_SIP and isinstance(item, sip.wrapper): - addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem)) - item = GraphicsScene._addressCache.get(addr, item) - return item - - @staticmethod - def translateGraphicsItems(items): - return list(map(GraphicsScene.translateGraphicsItem, items)) - - - diff --git a/pyqtgraph/GraphicsScene/__init__.py b/pyqtgraph/GraphicsScene/__init__.py deleted file mode 100644 index abe42c6f..00000000 --- a/pyqtgraph/GraphicsScene/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .GraphicsScene import * diff --git a/pyqtgraph/GraphicsScene/exportDialog.py b/pyqtgraph/GraphicsScene/exportDialog.py deleted file mode 100644 index 436d5e42..00000000 --- a/pyqtgraph/GraphicsScene/exportDialog.py +++ /dev/null @@ -1,139 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE -import pyqtgraph as pg -import pyqtgraph.exporters as exporters - -if USE_PYSIDE: - from . import exportDialogTemplate_pyside as exportDialogTemplate -else: - from . import exportDialogTemplate_pyqt as exportDialogTemplate - - -class ExportDialog(QtGui.QWidget): - def __init__(self, scene): - QtGui.QWidget.__init__(self) - self.setVisible(False) - self.setWindowTitle("Export") - self.shown = False - self.currentExporter = None - self.scene = scene - - self.selectBox = QtGui.QGraphicsRectItem() - self.selectBox.setPen(pg.mkPen('y', width=3, style=QtCore.Qt.DashLine)) - self.selectBox.hide() - self.scene.addItem(self.selectBox) - - self.ui = exportDialogTemplate.Ui_Form() - self.ui.setupUi(self) - - self.ui.closeBtn.clicked.connect(self.close) - self.ui.exportBtn.clicked.connect(self.exportClicked) - self.ui.copyBtn.clicked.connect(self.copyClicked) - self.ui.itemTree.currentItemChanged.connect(self.exportItemChanged) - self.ui.formatList.currentItemChanged.connect(self.exportFormatChanged) - - - def show(self, item=None): - if item is not None: - ## Select next exportable parent of the item originally clicked on - while not isinstance(item, pg.ViewBox) and not isinstance(item, pg.PlotItem) and item is not None: - item = item.parentItem() - ## if this is a ViewBox inside a PlotItem, select the parent instead. - if isinstance(item, pg.ViewBox) and isinstance(item.parentItem(), pg.PlotItem): - item = item.parentItem() - self.updateItemList(select=item) - self.setVisible(True) - self.activateWindow() - self.raise_() - self.selectBox.setVisible(True) - - if not self.shown: - self.shown = True - vcenter = self.scene.getViewWidget().geometry().center() - self.setGeometry(vcenter.x()-self.width()/2, vcenter.y()-self.height()/2, self.width(), self.height()) - - def updateItemList(self, select=None): - self.ui.itemTree.clear() - si = QtGui.QTreeWidgetItem(["Entire Scene"]) - si.gitem = self.scene - self.ui.itemTree.addTopLevelItem(si) - self.ui.itemTree.setCurrentItem(si) - si.setExpanded(True) - for child in self.scene.items(): - if child.parentItem() is None: - self.updateItemTree(child, si, select=select) - - def updateItemTree(self, item, treeItem, select=None): - si = None - if isinstance(item, pg.ViewBox): - si = QtGui.QTreeWidgetItem(['ViewBox']) - elif isinstance(item, pg.PlotItem): - si = QtGui.QTreeWidgetItem(['Plot']) - - if si is not None: - si.gitem = item - treeItem.addChild(si) - treeItem = si - if si.gitem is select: - self.ui.itemTree.setCurrentItem(si) - - for ch in item.childItems(): - self.updateItemTree(ch, treeItem, select=select) - - - def exportItemChanged(self, item, prev): - if item is None: - return - if item.gitem is self.scene: - newBounds = self.scene.views()[0].viewRect() - else: - newBounds = item.gitem.sceneBoundingRect() - self.selectBox.setRect(newBounds) - self.selectBox.show() - self.updateFormatList() - - def updateFormatList(self): - current = self.ui.formatList.currentItem() - if current is not None: - current = str(current.text()) - self.ui.formatList.clear() - self.exporterClasses = {} - gotCurrent = False - for exp in exporters.listExporters(): - self.ui.formatList.addItem(exp.Name) - self.exporterClasses[exp.Name] = exp - if exp.Name == current: - self.ui.formatList.setCurrentRow(self.ui.formatList.count()-1) - gotCurrent = True - - if not gotCurrent: - self.ui.formatList.setCurrentRow(0) - - def exportFormatChanged(self, item, prev): - if item is None: - self.currentExporter = None - self.ui.paramTree.clear() - return - expClass = self.exporterClasses[str(item.text())] - exp = expClass(item=self.ui.itemTree.currentItem().gitem) - params = exp.parameters() - if params is None: - self.ui.paramTree.clear() - else: - self.ui.paramTree.setParameters(params) - self.currentExporter = exp - self.ui.copyBtn.setEnabled(exp.allowCopy) - - def exportClicked(self): - self.selectBox.hide() - self.currentExporter.export() - - def copyClicked(self): - self.selectBox.hide() - self.currentExporter.export(copy=True) - - def close(self): - self.selectBox.setVisible(False) - self.setVisible(False) - - - diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate.ui b/pyqtgraph/GraphicsScene/exportDialogTemplate.ui deleted file mode 100644 index c91fbc3f..00000000 --- a/pyqtgraph/GraphicsScene/exportDialogTemplate.ui +++ /dev/null @@ -1,100 +0,0 @@ - - - Form - - - - 0 - 0 - 241 - 367 - - - - Export - - - - 0 - - - - - Item to export: - - - - - - - false - - - - 1 - - - - - - - - Export format - - - - - - - - - - Export - - - - - - - Close - - - - - - - false - - - - 1 - - - - - - - - Export options - - - - - - - Copy - - - - - - - - ParameterTree - QTreeWidget -
pyqtgraph.parametertree
-
-
- - -
diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py deleted file mode 100644 index c3056d1c..00000000 --- a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './GraphicsScene/exportDialogTemplate.ui' -# -# Created: Wed Jan 30 21:02:28 2013 -# by: PyQt4 UI code generator 4.9.3 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName(_fromUtf8("Form")) - Form.resize(241, 367) - self.gridLayout = QtGui.QGridLayout(Form) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.label = QtGui.QLabel(Form) - self.label.setObjectName(_fromUtf8("label")) - self.gridLayout.addWidget(self.label, 0, 0, 1, 3) - self.itemTree = QtGui.QTreeWidget(Form) - self.itemTree.setObjectName(_fromUtf8("itemTree")) - self.itemTree.headerItem().setText(0, _fromUtf8("1")) - self.itemTree.header().setVisible(False) - self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) - self.label_2 = QtGui.QLabel(Form) - self.label_2.setObjectName(_fromUtf8("label_2")) - self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) - self.formatList = QtGui.QListWidget(Form) - self.formatList.setObjectName(_fromUtf8("formatList")) - self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) - self.exportBtn = QtGui.QPushButton(Form) - self.exportBtn.setObjectName(_fromUtf8("exportBtn")) - self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) - self.closeBtn = QtGui.QPushButton(Form) - self.closeBtn.setObjectName(_fromUtf8("closeBtn")) - self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) - self.paramTree = ParameterTree(Form) - self.paramTree.setObjectName(_fromUtf8("paramTree")) - self.paramTree.headerItem().setText(0, _fromUtf8("1")) - self.paramTree.header().setVisible(False) - self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) - self.label_3 = QtGui.QLabel(Form) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) - self.copyBtn = QtGui.QPushButton(Form) - self.copyBtn.setObjectName(_fromUtf8("copyBtn")) - self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("Form", "Item to export:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_2.setText(QtGui.QApplication.translate("Form", "Export format", None, QtGui.QApplication.UnicodeUTF8)) - self.exportBtn.setText(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) - self.closeBtn.setText(QtGui.QApplication.translate("Form", "Close", None, QtGui.QApplication.UnicodeUTF8)) - self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8)) - self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8)) - -from pyqtgraph.parametertree import ParameterTree diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py b/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py deleted file mode 100644 index cf27f60a..00000000 --- a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py +++ /dev/null @@ -1,63 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './GraphicsScene/exportDialogTemplate.ui' -# -# Created: Wed Jan 30 21:02:28 2013 -# by: pyside-uic 0.2.13 running on PySide 1.1.1 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(241, 367) - self.gridLayout = QtGui.QGridLayout(Form) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.label = QtGui.QLabel(Form) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 0, 0, 1, 3) - self.itemTree = QtGui.QTreeWidget(Form) - self.itemTree.setObjectName("itemTree") - self.itemTree.headerItem().setText(0, "1") - self.itemTree.header().setVisible(False) - self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) - self.label_2 = QtGui.QLabel(Form) - self.label_2.setObjectName("label_2") - self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) - self.formatList = QtGui.QListWidget(Form) - self.formatList.setObjectName("formatList") - self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) - self.exportBtn = QtGui.QPushButton(Form) - self.exportBtn.setObjectName("exportBtn") - self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) - self.closeBtn = QtGui.QPushButton(Form) - self.closeBtn.setObjectName("closeBtn") - self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) - self.paramTree = ParameterTree(Form) - self.paramTree.setObjectName("paramTree") - self.paramTree.headerItem().setText(0, "1") - self.paramTree.header().setVisible(False) - self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) - self.label_3 = QtGui.QLabel(Form) - self.label_3.setObjectName("label_3") - self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) - self.copyBtn = QtGui.QPushButton(Form) - self.copyBtn.setObjectName("copyBtn") - self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("Form", "Item to export:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_2.setText(QtGui.QApplication.translate("Form", "Export format", None, QtGui.QApplication.UnicodeUTF8)) - self.exportBtn.setText(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) - self.closeBtn.setText(QtGui.QApplication.translate("Form", "Close", None, QtGui.QApplication.UnicodeUTF8)) - self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8)) - self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8)) - -from pyqtgraph.parametertree import ParameterTree diff --git a/pyqtgraph/GraphicsScene/mouseEvents.py b/pyqtgraph/GraphicsScene/mouseEvents.py deleted file mode 100644 index 0b71ac6f..00000000 --- a/pyqtgraph/GraphicsScene/mouseEvents.py +++ /dev/null @@ -1,365 +0,0 @@ -from pyqtgraph.Point import Point -from pyqtgraph.Qt import QtCore, QtGui -import weakref -import pyqtgraph.ptime as ptime - -class MouseDragEvent(object): - """ - Instances of this class are delivered to items in a :class:`GraphicsScene ` via their mouseDragEvent() method when the item is being mouse-dragged. - - """ - - - - def __init__(self, moveEvent, pressEvent, lastEvent, start=False, finish=False): - self.start = start - self.finish = finish - self.accepted = False - self.currentItem = None - self._buttonDownScenePos = {} - self._buttonDownScreenPos = {} - for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MidButton, QtCore.Qt.RightButton]: - self._buttonDownScenePos[int(btn)] = moveEvent.buttonDownScenePos(btn) - self._buttonDownScreenPos[int(btn)] = moveEvent.buttonDownScreenPos(btn) - self._scenePos = moveEvent.scenePos() - self._screenPos = moveEvent.screenPos() - if lastEvent is None: - self._lastScenePos = pressEvent.scenePos() - self._lastScreenPos = pressEvent.screenPos() - else: - self._lastScenePos = lastEvent.scenePos() - self._lastScreenPos = lastEvent.screenPos() - self._buttons = moveEvent.buttons() - self._button = pressEvent.button() - self._modifiers = moveEvent.modifiers() - self.acceptedItem = None - - def accept(self): - """An item should call this method if it can handle the event. This will prevent the event being delivered to any other items.""" - self.accepted = True - self.acceptedItem = self.currentItem - - def ignore(self): - """An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items.""" - self.accepted = False - - def isAccepted(self): - return self.accepted - - def scenePos(self): - """Return the current scene position of the mouse.""" - return Point(self._scenePos) - - def screenPos(self): - """Return the current screen position (pixels relative to widget) of the mouse.""" - return Point(self._screenPos) - - def buttonDownScenePos(self, btn=None): - """ - Return the scene position of the mouse at the time *btn* was pressed. - If *btn* is omitted, then the button that initiated the drag is assumed. - """ - if btn is None: - btn = self.button() - return Point(self._buttonDownScenePos[int(btn)]) - - def buttonDownScreenPos(self, btn=None): - """ - Return the screen position (pixels relative to widget) of the mouse at the time *btn* was pressed. - If *btn* is omitted, then the button that initiated the drag is assumed. - """ - if btn is None: - btn = self.button() - return Point(self._buttonDownScreenPos[int(btn)]) - - def lastScenePos(self): - """ - Return the scene position of the mouse immediately prior to this event. - """ - return Point(self._lastScenePos) - - def lastScreenPos(self): - """ - Return the screen position of the mouse immediately prior to this event. - """ - return Point(self._lastScreenPos) - - def buttons(self): - """ - Return the buttons currently pressed on the mouse. - (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) - """ - return self._buttons - - def button(self): - """Return the button that initiated the drag (may be different from the buttons currently pressed) - (see QGraphicsSceneMouseEvent::button in the Qt documentation) - - """ - return self._button - - def pos(self): - """ - Return the current position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._scenePos)) - - def lastPos(self): - """ - Return the previous position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._lastScenePos)) - - def buttonDownPos(self, btn=None): - """ - Return the position of the mouse at the time the drag was initiated - in the coordinate system of the item that the event was delivered to. - """ - if btn is None: - btn = self.button() - return Point(self.currentItem.mapFromScene(self._buttonDownScenePos[int(btn)])) - - def isStart(self): - """Returns True if this event is the first since a drag was initiated.""" - return self.start - - def isFinish(self): - """Returns False if this is the last event in a drag. Note that this - event will have the same position as the previous one.""" - return self.finish - - def __repr__(self): - lp = self.lastPos() - p = self.pos() - return "(%g,%g) buttons=%d start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isStart()), str(self.isFinish())) - - def modifiers(self): - """Return any keyboard modifiers currently pressed. - (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) - - """ - return self._modifiers - - - -class MouseClickEvent(object): - """ - Instances of this class are delivered to items in a :class:`GraphicsScene ` via their mouseClickEvent() method when the item is clicked. - - - """ - - def __init__(self, pressEvent, double=False): - self.accepted = False - self.currentItem = None - self._double = double - self._scenePos = pressEvent.scenePos() - self._screenPos = pressEvent.screenPos() - self._button = pressEvent.button() - self._buttons = pressEvent.buttons() - self._modifiers = pressEvent.modifiers() - self._time = ptime.time() - self.acceptedItem = None - - def accept(self): - """An item should call this method if it can handle the event. This will prevent the event being delivered to any other items.""" - self.accepted = True - self.acceptedItem = self.currentItem - - def ignore(self): - """An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items.""" - self.accepted = False - - def isAccepted(self): - return self.accepted - - def scenePos(self): - """Return the current scene position of the mouse.""" - return Point(self._scenePos) - - def screenPos(self): - """Return the current screen position (pixels relative to widget) of the mouse.""" - return Point(self._screenPos) - - def buttons(self): - """ - Return the buttons currently pressed on the mouse. - (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) - """ - return self._buttons - - def button(self): - """Return the mouse button that generated the click event. - (see QGraphicsSceneMouseEvent::button in the Qt documentation) - """ - return self._button - - def double(self): - """Return True if this is a double-click.""" - return self._double - - def pos(self): - """ - Return the current position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._scenePos)) - - def lastPos(self): - """ - Return the previous position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._lastScenePos)) - - def modifiers(self): - """Return any keyboard modifiers currently pressed. - (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) - """ - return self._modifiers - - def __repr__(self): - p = self.pos() - return "" % (p.x(), p.y(), int(self.button())) - - def time(self): - return self._time - - - -class HoverEvent(object): - """ - Instances of this class are delivered to items in a :class:`GraphicsScene ` via their hoverEvent() method when the mouse is hovering over the item. - This event class both informs items that the mouse cursor is nearby and allows items to - communicate with one another about whether each item will accept *potential* mouse events. - - It is common for multiple overlapping items to receive hover events and respond by changing - their appearance. This can be misleading to the user since, in general, only one item will - respond to mouse events. To avoid this, items make calls to event.acceptClicks(button) - and/or acceptDrags(button). - - Each item may make multiple calls to acceptClicks/Drags, each time for a different button. - If the method returns True, then the item is guaranteed to be - the recipient of the claimed event IF the user presses the specified mouse button before - moving. If claimEvent returns False, then this item is guaranteed NOT to get the specified - event (because another has already claimed it) and the item should change its appearance - accordingly. - - event.isEnter() returns True if the mouse has just entered the item's shape; - event.isExit() returns True if the mouse has just left. - """ - def __init__(self, moveEvent, acceptable): - self.enter = False - self.acceptable = acceptable - self.exit = False - self.__clickItems = weakref.WeakValueDictionary() - self.__dragItems = weakref.WeakValueDictionary() - self.currentItem = None - if moveEvent is not None: - self._scenePos = moveEvent.scenePos() - self._screenPos = moveEvent.screenPos() - self._lastScenePos = moveEvent.lastScenePos() - self._lastScreenPos = moveEvent.lastScreenPos() - self._buttons = moveEvent.buttons() - self._modifiers = moveEvent.modifiers() - else: - self.exit = True - - - - def isEnter(self): - """Returns True if the mouse has just entered the item's shape""" - return self.enter - - def isExit(self): - """Returns True if the mouse has just exited the item's shape""" - return self.exit - - def acceptClicks(self, button): - """Inform the scene that the item (that the event was delivered to) - would accept a mouse click event if the user were to click before - moving the mouse again. - - Returns True if the request is successful, otherwise returns False (indicating - that some other item would receive an incoming click). - """ - if not self.acceptable: - return False - if button not in self.__clickItems: - self.__clickItems[button] = self.currentItem - return True - return False - - def acceptDrags(self, button): - """Inform the scene that the item (that the event was delivered to) - would accept a mouse drag event if the user were to drag before - the next hover event. - - Returns True if the request is successful, otherwise returns False (indicating - that some other item would receive an incoming drag event). - """ - if not self.acceptable: - return False - if button not in self.__dragItems: - self.__dragItems[button] = self.currentItem - return True - return False - - def scenePos(self): - """Return the current scene position of the mouse.""" - return Point(self._scenePos) - - def screenPos(self): - """Return the current screen position of the mouse.""" - return Point(self._screenPos) - - def lastScenePos(self): - """Return the previous scene position of the mouse.""" - return Point(self._lastScenePos) - - def lastScreenPos(self): - """Return the previous screen position of the mouse.""" - return Point(self._lastScreenPos) - - def buttons(self): - """ - Return the buttons currently pressed on the mouse. - (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) - """ - return self._buttons - - def pos(self): - """ - Return the current position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._scenePos)) - - def lastPos(self): - """ - Return the previous position of the mouse in the coordinate system of the item - that the event was delivered to. - """ - return Point(self.currentItem.mapFromScene(self._lastScenePos)) - - def __repr__(self): - lp = self.lastPos() - p = self.pos() - return "(%g,%g) buttons=%d enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isEnter()), str(self.isExit())) - - def modifiers(self): - """Return any keyboard modifiers currently pressed. - (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) - """ - return self._modifiers - - def clickItems(self): - return self.__clickItems - - def dragItems(self): - return self.__dragItems - - - \ No newline at end of file diff --git a/pyqtgraph/PIL_Fix/README b/pyqtgraph/PIL_Fix/README deleted file mode 100644 index 3711e113..00000000 --- a/pyqtgraph/PIL_Fix/README +++ /dev/null @@ -1,11 +0,0 @@ -The file Image.py is a drop-in replacement for the same file in PIL 1.1.6. -It adds support for reading 16-bit TIFF files and converting then to numpy arrays. -(I submitted the changes to the PIL folks long ago, but to my knowledge the code -is not being used by them.) - -To use, copy this file into - /usr/lib/python2.6/dist-packages/PIL/ -or - C:\Python26\lib\site-packages\PIL\ - -..or wherever your system keeps its python modules. diff --git a/pyqtgraph/PlotData.py b/pyqtgraph/PlotData.py deleted file mode 100644 index e5faadda..00000000 --- a/pyqtgraph/PlotData.py +++ /dev/null @@ -1,56 +0,0 @@ - - -class PlotData(object): - """ - Class used for managing plot data - - allows data sharing between multiple graphics items (curve, scatter, graph..) - - each item may define the columns it needs - - column groupings ('pos' or x, y, z) - - efficiently appendable - - log, fft transformations - - color mode conversion (float/byte/qcolor) - - pen/brush conversion - - per-field cached masking - - allows multiple masking fields (different graphics need to mask on different criteria) - - removal of nan/inf values - - option for single value shared by entire column - - cached downsampling - - cached min / max / hasnan / isuniform - """ - def __init__(self): - self.fields = {} - - self.maxVals = {} ## cache for max/min - self.minVals = {} - - def addFields(self, **fields): - for f in fields: - if f not in self.fields: - self.fields[f] = None - - def hasField(self, f): - return f in self.fields - - def __getitem__(self, field): - return self.fields[field] - - def __setitem__(self, field, val): - self.fields[field] = val - - def max(self, field): - mx = self.maxVals.get(field, None) - if mx is None: - mx = np.max(self[field]) - self.maxVals[field] = mx - return mx - - def min(self, field): - mn = self.minVals.get(field, None) - if mn is None: - mn = np.min(self[field]) - self.minVals[field] = mn - return mn - - - - \ No newline at end of file diff --git a/pyqtgraph/Point.py b/pyqtgraph/Point.py deleted file mode 100644 index 4d04f01c..00000000 --- a/pyqtgraph/Point.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Point.py - Extension of QPointF which adds a few missing methods. -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - -from .Qt import QtCore -import numpy as np - -def clip(x, mn, mx): - if x > mx: - return mx - if x < mn: - return mn - return x - -class Point(QtCore.QPointF): - """Extension of QPointF which adds a few missing methods.""" - - def __init__(self, *args): - if len(args) == 1: - if isinstance(args[0], QtCore.QSizeF): - QtCore.QPointF.__init__(self, float(args[0].width()), float(args[0].height())) - return - elif isinstance(args[0], float) or isinstance(args[0], int): - QtCore.QPointF.__init__(self, float(args[0]), float(args[0])) - return - elif hasattr(args[0], '__getitem__'): - QtCore.QPointF.__init__(self, float(args[0][0]), float(args[0][1])) - return - elif len(args) == 2: - QtCore.QPointF.__init__(self, args[0], args[1]) - return - QtCore.QPointF.__init__(self, *args) - - def __len__(self): - return 2 - - def __reduce__(self): - return (Point, (self.x(), self.y())) - - def __getitem__(self, i): - if i == 0: - return self.x() - elif i == 1: - return self.y() - else: - raise IndexError("Point has no index %s" % str(i)) - - def __setitem__(self, i, x): - if i == 0: - return self.setX(x) - elif i == 1: - return self.setY(x) - else: - raise IndexError("Point has no index %s" % str(i)) - - def __radd__(self, a): - return self._math_('__radd__', a) - - def __add__(self, a): - return self._math_('__add__', a) - - def __rsub__(self, a): - return self._math_('__rsub__', a) - - def __sub__(self, a): - return self._math_('__sub__', a) - - def __rmul__(self, a): - return self._math_('__rmul__', a) - - def __mul__(self, a): - return self._math_('__mul__', a) - - def __rdiv__(self, a): - return self._math_('__rdiv__', a) - - def __div__(self, a): - return self._math_('__div__', a) - - def __truediv__(self, a): - return self._math_('__truediv__', a) - - def __rtruediv__(self, a): - return self._math_('__rtruediv__', a) - - def __rpow__(self, a): - return self._math_('__rpow__', a) - - def __pow__(self, a): - return self._math_('__pow__', a) - - def _math_(self, op, x): - #print "point math:", op - #try: - #fn = getattr(QtCore.QPointF, op) - #pt = fn(self, x) - #print fn, pt, self, x - #return Point(pt) - #except AttributeError: - x = Point(x) - return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1])) - - def length(self): - """Returns the vector length of this Point.""" - return (self[0]**2 + self[1]**2) ** 0.5 - - def norm(self): - """Returns a vector in the same direction with unit length.""" - return self / self.length() - - def angle(self, a): - """Returns the angle in degrees between this vector and the vector a.""" - n1 = self.length() - n2 = a.length() - if n1 == 0. or n2 == 0.: - return None - ## Probably this should be done with arctan2 instead.. - ang = np.arccos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians - c = self.cross(a) - if c > 0: - ang *= -1. - return ang * 180. / np.pi - - def dot(self, a): - """Returns the dot product of a and this Point.""" - a = Point(a) - return self[0]*a[0] + self[1]*a[1] - - def cross(self, a): - a = Point(a) - return self[0]*a[1] - self[1]*a[0] - - def proj(self, b): - """Return the projection of this vector onto the vector b""" - b1 = b / b.length() - return self.dot(b1) * b1 - - def __repr__(self): - return "Point(%f, %f)" % (self[0], self[1]) - - - def min(self): - return min(self[0], self[1]) - - def max(self): - return max(self[0], self[1]) - - def copy(self): - return Point(self) - - def toQPoint(self): - return QtCore.QPoint(*self) diff --git a/pyqtgraph/Qt.py b/pyqtgraph/Qt.py deleted file mode 100644 index e584a381..00000000 --- a/pyqtgraph/Qt.py +++ /dev/null @@ -1,48 +0,0 @@ -## Do all Qt imports from here to allow easier PyQt / PySide compatibility -import sys, re - -## Automatically determine whether to use PyQt or PySide. -## This is done by first checking to see whether one of the libraries -## is already imported. If not, then attempt to import PyQt4, then PySide. -if 'PyQt4' in sys.modules: - USE_PYSIDE = False -elif 'PySide' in sys.modules: - USE_PYSIDE = True -else: - try: - import PyQt4 - USE_PYSIDE = False - except ImportError: - try: - import PySide - USE_PYSIDE = True - except ImportError: - raise Exception("PyQtGraph requires either PyQt4 or PySide; neither package could be imported.") - -if USE_PYSIDE: - from PySide import QtGui, QtCore, QtOpenGL, QtSvg - import PySide - VERSION_INFO = 'PySide ' + PySide.__version__ -else: - from PyQt4 import QtGui, QtCore - try: - from PyQt4 import QtSvg - except ImportError: - pass - try: - from PyQt4 import QtOpenGL - except ImportError: - pass - - QtCore.Signal = QtCore.pyqtSignal - VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR - - -## Make sure we have Qt >= 4.7 -versionReq = [4, 7] -QtVersion = PySide.QtCore.__version__ if USE_PYSIDE else QtCore.QT_VERSION_STR -m = re.match(r'(\d+)\.(\d+).*', QtVersion) -if m is not None and list(map(int, m.groups())) < versionReq: - print(map(int, m.groups())) - raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion)) - diff --git a/pyqtgraph/SRTTransform.py b/pyqtgraph/SRTTransform.py deleted file mode 100644 index efb24f60..00000000 --- a/pyqtgraph/SRTTransform.py +++ /dev/null @@ -1,259 +0,0 @@ -# -*- coding: utf-8 -*- -from .Qt import QtCore, QtGui -from .Point import Point -import numpy as np -import pyqtgraph as pg - -class SRTTransform(QtGui.QTransform): - """Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate - This transform has no shear; angles are always preserved. - """ - def __init__(self, init=None): - QtGui.QTransform.__init__(self) - self.reset() - - if init is None: - return - elif isinstance(init, dict): - self.restoreState(init) - elif isinstance(init, SRTTransform): - self._state = { - 'pos': Point(init._state['pos']), - 'scale': Point(init._state['scale']), - 'angle': init._state['angle'] - } - self.update() - elif isinstance(init, QtGui.QTransform): - self.setFromQTransform(init) - elif isinstance(init, QtGui.QMatrix4x4): - self.setFromMatrix4x4(init) - else: - raise Exception("Cannot create SRTTransform from input type: %s" % str(type(init))) - - - def getScale(self): - return self._state['scale'] - - def getAngle(self): - ## deprecated; for backward compatibility - return self.getRotation() - - def getRotation(self): - return self._state['angle'] - - def getTranslation(self): - return self._state['pos'] - - def reset(self): - self._state = { - 'pos': Point(0,0), - 'scale': Point(1,1), - 'angle': 0.0 ## in degrees - } - self.update() - - def setFromQTransform(self, tr): - p1 = Point(tr.map(0., 0.)) - p2 = Point(tr.map(1., 0.)) - p3 = Point(tr.map(0., 1.)) - - dp2 = Point(p2-p1) - dp3 = Point(p3-p1) - - ## detect flipped axes - if dp2.angle(dp3) > 0: - #da = 180 - da = 0 - sy = -1.0 - else: - da = 0 - sy = 1.0 - - self._state = { - 'pos': Point(p1), - 'scale': Point(dp2.length(), dp3.length() * sy), - 'angle': (np.arctan2(dp2[1], dp2[0]) * 180. / np.pi) + da - } - self.update() - - def setFromMatrix4x4(self, m): - m = pg.SRTTransform3D(m) - angle, axis = m.getRotation() - if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1): - print("angle: %s axis: %s" % (str(angle), str(axis))) - raise Exception("Can only convert 4x4 matrix to 3x3 if rotation is around Z-axis.") - self._state = { - 'pos': Point(m.getTranslation()), - 'scale': Point(m.getScale()), - 'angle': angle - } - self.update() - - def translate(self, *args): - """Acceptable arguments are: - x, y - [x, y] - Point(x,y)""" - t = Point(*args) - self.setTranslate(self._state['pos']+t) - - def setTranslate(self, *args): - """Acceptable arguments are: - x, y - [x, y] - Point(x,y)""" - self._state['pos'] = Point(*args) - self.update() - - def scale(self, *args): - """Acceptable arguments are: - x, y - [x, y] - Point(x,y)""" - s = Point(*args) - self.setScale(self._state['scale'] * s) - - def setScale(self, *args): - """Acceptable arguments are: - x, y - [x, y] - Point(x,y)""" - self._state['scale'] = Point(*args) - self.update() - - def rotate(self, angle): - """Rotate the transformation by angle (in degrees)""" - self.setRotate(self._state['angle'] + angle) - - def setRotate(self, angle): - """Set the transformation rotation to angle (in degrees)""" - self._state['angle'] = angle - self.update() - - def __truediv__(self, t): - """A / B == B^-1 * A""" - dt = t.inverted()[0] * self - return SRTTransform(dt) - - def __div__(self, t): - return self.__truediv__(t) - - def __mul__(self, t): - return SRTTransform(QtGui.QTransform.__mul__(self, t)) - - def saveState(self): - p = self._state['pos'] - s = self._state['scale'] - #if s[0] == 0: - #raise Exception('Invalid scale: %s' % str(s)) - return {'pos': (p[0], p[1]), 'scale': (s[0], s[1]), 'angle': self._state['angle']} - - def restoreState(self, state): - self._state['pos'] = Point(state.get('pos', (0,0))) - self._state['scale'] = Point(state.get('scale', (1.,1.))) - self._state['angle'] = state.get('angle', 0) - self.update() - - def update(self): - QtGui.QTransform.reset(self) - ## modifications to the transform are multiplied on the right, so we need to reverse order here. - QtGui.QTransform.translate(self, *self._state['pos']) - QtGui.QTransform.rotate(self, self._state['angle']) - QtGui.QTransform.scale(self, *self._state['scale']) - - def __repr__(self): - return str(self.saveState()) - - def matrix(self): - return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]]) - -if __name__ == '__main__': - from . import widgets - import GraphicsView - from .functions import * - app = QtGui.QApplication([]) - win = QtGui.QMainWindow() - win.show() - cw = GraphicsView.GraphicsView() - #cw.enableMouse() - win.setCentralWidget(cw) - s = QtGui.QGraphicsScene() - cw.setScene(s) - win.resize(600,600) - cw.enableMouse() - cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) - - class Item(QtGui.QGraphicsItem): - def __init__(self): - QtGui.QGraphicsItem.__init__(self) - self.b = QtGui.QGraphicsRectItem(20, 20, 20, 20, self) - self.b.setPen(QtGui.QPen(mkPen('y'))) - self.t1 = QtGui.QGraphicsTextItem(self) - self.t1.setHtml('R') - self.t1.translate(20, 20) - self.l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0, self) - self.l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10, self) - self.l1.setPen(QtGui.QPen(mkPen('y'))) - self.l2.setPen(QtGui.QPen(mkPen('y'))) - def boundingRect(self): - return QtCore.QRectF() - def paint(self, *args): - pass - - #s.addItem(b) - #s.addItem(t1) - item = Item() - s.addItem(item) - l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0) - l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10) - l1.setPen(QtGui.QPen(mkPen('r'))) - l2.setPen(QtGui.QPen(mkPen('r'))) - s.addItem(l1) - s.addItem(l2) - - tr1 = SRTTransform() - tr2 = SRTTransform() - tr3 = QtGui.QTransform() - tr3.translate(20, 0) - tr3.rotate(45) - print("QTransform -> Transform:", SRTTransform(tr3)) - - print("tr1:", tr1) - - tr2.translate(20, 0) - tr2.rotate(45) - print("tr2:", tr2) - - dt = tr2/tr1 - print("tr2 / tr1 = ", dt) - - print("tr2 * tr1 = ", tr2*tr1) - - tr4 = SRTTransform() - tr4.scale(-1, 1) - tr4.rotate(30) - print("tr1 * tr4 = ", tr1*tr4) - - w1 = widgets.TestROI((19,19), (22, 22), invertible=True) - #w2 = widgets.TestROI((0,0), (150, 150)) - w1.setZValue(10) - s.addItem(w1) - #s.addItem(w2) - w1Base = w1.getState() - #w2Base = w2.getState() - def update(): - tr1 = w1.getGlobalTransform(w1Base) - #tr2 = w2.getGlobalTransform(w2Base) - item.setTransform(tr1) - - #def update2(): - #tr1 = w1.getGlobalTransform(w1Base) - #tr2 = w2.getGlobalTransform(w2Base) - #t1.setTransform(tr1) - #w1.setState(w1Base) - #w1.applyGlobalTransform(tr2) - - w1.sigRegionChanged.connect(update) - #w2.sigRegionChanged.connect(update2) - - \ No newline at end of file diff --git a/pyqtgraph/SRTTransform3D.py b/pyqtgraph/SRTTransform3D.py deleted file mode 100644 index 7d87dcb8..00000000 --- a/pyqtgraph/SRTTransform3D.py +++ /dev/null @@ -1,314 +0,0 @@ -# -*- coding: utf-8 -*- -from .Qt import QtCore, QtGui -from .Vector import Vector -from .SRTTransform import SRTTransform -import pyqtgraph as pg -import numpy as np -import scipy.linalg - -class SRTTransform3D(pg.Transform3D): - """4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate - This transform has no shear; angles are always preserved. - """ - def __init__(self, init=None): - pg.Transform3D.__init__(self) - self.reset() - if init is None: - return - if init.__class__ is QtGui.QTransform: - init = SRTTransform(init) - - if isinstance(init, dict): - self.restoreState(init) - elif isinstance(init, SRTTransform3D): - self._state = { - 'pos': Vector(init._state['pos']), - 'scale': Vector(init._state['scale']), - 'angle': init._state['angle'], - 'axis': Vector(init._state['axis']), - } - self.update() - elif isinstance(init, SRTTransform): - self._state = { - 'pos': Vector(init._state['pos']), - 'scale': Vector(init._state['scale']), - 'angle': init._state['angle'], - 'axis': Vector(0, 0, 1), - } - self._state['scale'][2] = 1.0 - self.update() - elif isinstance(init, QtGui.QMatrix4x4): - self.setFromMatrix(init) - else: - raise Exception("Cannot build SRTTransform3D from argument type:", type(init)) - - - def getScale(self): - return pg.Vector(self._state['scale']) - - def getRotation(self): - """Return (angle, axis) of rotation""" - return self._state['angle'], pg.Vector(self._state['axis']) - - def getTranslation(self): - return pg.Vector(self._state['pos']) - - def reset(self): - self._state = { - 'pos': Vector(0,0,0), - 'scale': Vector(1,1,1), - 'angle': 0.0, ## in degrees - 'axis': (0, 0, 1) - } - self.update() - - def translate(self, *args): - """Adjust the translation of this transform""" - t = Vector(*args) - self.setTranslate(self._state['pos']+t) - - def setTranslate(self, *args): - """Set the translation of this transform""" - self._state['pos'] = Vector(*args) - self.update() - - def scale(self, *args): - """adjust the scale of this transform""" - ## try to prevent accidentally setting 0 scale on z axis - if len(args) == 1 and hasattr(args[0], '__len__'): - args = args[0] - if len(args) == 2: - args = args + (1,) - - s = Vector(*args) - self.setScale(self._state['scale'] * s) - - def setScale(self, *args): - """Set the scale of this transform""" - if len(args) == 1 and hasattr(args[0], '__len__'): - args = args[0] - if len(args) == 2: - args = args + (1,) - self._state['scale'] = Vector(*args) - self.update() - - def rotate(self, angle, axis=(0,0,1)): - """Adjust the rotation of this transform""" - origAxis = self._state['axis'] - if axis[0] == origAxis[0] and axis[1] == origAxis[1] and axis[2] == origAxis[2]: - self.setRotate(self._state['angle'] + angle) - else: - m = QtGui.QMatrix4x4() - m.translate(*self._state['pos']) - m.rotate(self._state['angle'], *self._state['axis']) - m.rotate(angle, *axis) - m.scale(*self._state['scale']) - self.setFromMatrix(m) - - def setRotate(self, angle, axis=(0,0,1)): - """Set the transformation rotation to angle (in degrees)""" - - self._state['angle'] = angle - self._state['axis'] = Vector(axis) - self.update() - - def setFromMatrix(self, m): - """ - Set this transform mased on the elements of *m* - The input matrix must be affine AND have no shear, - otherwise the conversion will most likely fail. - """ - for i in range(4): - self.setRow(i, m.row(i)) - m = self.matrix().reshape(4,4) - ## translation is 4th column - self._state['pos'] = m[:3,3] - ## scale is vector-length of first three columns - scale = (m[:3,:3]**2).sum(axis=0)**0.5 - ## see whether there is an inversion - z = np.cross(m[0, :3], m[1, :3]) - if np.dot(z, m[2, :3]) < 0: - scale[1] *= -1 ## doesn't really matter which axis we invert - self._state['scale'] = scale - - ## rotation axis is the eigenvector with eigenvalue=1 - r = m[:3, :3] / scale[:, np.newaxis] - try: - evals, evecs = scipy.linalg.eig(r) - except: - print("Rotation matrix: %s" % str(r)) - print("Scale: %s" % str(scale)) - print("Original matrix: %s" % str(m)) - raise - eigIndex = np.argwhere(np.abs(evals-1) < 1e-6) - if len(eigIndex) < 1: - print("eigenvalues: %s" % str(evals)) - print("eigenvectors: %s" % str(evecs)) - print("index: %s, %s" % (str(eigIndex), str(evals-1))) - raise Exception("Could not determine rotation axis.") - axis = evecs[:,eigIndex[0,0]].real - axis /= ((axis**2).sum())**0.5 - self._state['axis'] = axis - - ## trace(r) == 2 cos(angle) + 1, so: - cos = (r.trace()-1)*0.5 ## this only gets us abs(angle) - - ## The off-diagonal values can be used to correct the angle ambiguity, - ## but we need to figure out which element to use: - axisInd = np.argmax(np.abs(axis)) - rInd,sign = [((1,2), -1), ((0,2), 1), ((0,1), -1)][axisInd] - - ## Then we have r-r.T = sin(angle) * 2 * sign * axis[axisInd]; - ## solve for sin(angle) - sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd]) - - ## finally, we get the complete angle from arctan(sin/cos) - self._state['angle'] = np.arctan2(sin, cos) * 180 / np.pi - if self._state['angle'] == 0: - self._state['axis'] = (0,0,1) - - def as2D(self): - """Return a QTransform representing the x,y portion of this transform (if possible)""" - return pg.SRTTransform(self) - - #def __div__(self, t): - #"""A / B == B^-1 * A""" - #dt = t.inverted()[0] * self - #return SRTTransform(dt) - - #def __mul__(self, t): - #return SRTTransform(QtGui.QTransform.__mul__(self, t)) - - def saveState(self): - p = self._state['pos'] - s = self._state['scale'] - ax = self._state['axis'] - #if s[0] == 0: - #raise Exception('Invalid scale: %s' % str(s)) - return { - 'pos': (p[0], p[1], p[2]), - 'scale': (s[0], s[1], s[2]), - 'angle': self._state['angle'], - 'axis': (ax[0], ax[1], ax[2]) - } - - def restoreState(self, state): - self._state['pos'] = Vector(state.get('pos', (0.,0.,0.))) - scale = state.get('scale', (1.,1.,1.)) - scale = tuple(scale) + (1.,) * (3-len(scale)) - self._state['scale'] = Vector(scale) - self._state['angle'] = state.get('angle', 0.) - self._state['axis'] = state.get('axis', (0, 0, 1)) - self.update() - - def update(self): - pg.Transform3D.setToIdentity(self) - ## modifications to the transform are multiplied on the right, so we need to reverse order here. - pg.Transform3D.translate(self, *self._state['pos']) - pg.Transform3D.rotate(self, self._state['angle'], *self._state['axis']) - pg.Transform3D.scale(self, *self._state['scale']) - - def __repr__(self): - return str(self.saveState()) - - def matrix(self, nd=3): - if nd == 3: - return np.array(self.copyDataTo()).reshape(4,4) - elif nd == 2: - m = np.array(self.copyDataTo()).reshape(4,4) - m[2] = m[3] - m[:,2] = m[:,3] - return m[:3,:3] - else: - raise Exception("Argument 'nd' must be 2 or 3") - -if __name__ == '__main__': - import widgets - import GraphicsView - from functions import * - app = QtGui.QApplication([]) - win = QtGui.QMainWindow() - win.show() - cw = GraphicsView.GraphicsView() - #cw.enableMouse() - win.setCentralWidget(cw) - s = QtGui.QGraphicsScene() - cw.setScene(s) - win.resize(600,600) - cw.enableMouse() - cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) - - class Item(QtGui.QGraphicsItem): - def __init__(self): - QtGui.QGraphicsItem.__init__(self) - self.b = QtGui.QGraphicsRectItem(20, 20, 20, 20, self) - self.b.setPen(QtGui.QPen(mkPen('y'))) - self.t1 = QtGui.QGraphicsTextItem(self) - self.t1.setHtml('R') - self.t1.translate(20, 20) - self.l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0, self) - self.l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10, self) - self.l1.setPen(QtGui.QPen(mkPen('y'))) - self.l2.setPen(QtGui.QPen(mkPen('y'))) - def boundingRect(self): - return QtCore.QRectF() - def paint(self, *args): - pass - - #s.addItem(b) - #s.addItem(t1) - item = Item() - s.addItem(item) - l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0) - l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10) - l1.setPen(QtGui.QPen(mkPen('r'))) - l2.setPen(QtGui.QPen(mkPen('r'))) - s.addItem(l1) - s.addItem(l2) - - tr1 = SRTTransform() - tr2 = SRTTransform() - tr3 = QtGui.QTransform() - tr3.translate(20, 0) - tr3.rotate(45) - print("QTransform -> Transform: %s" % str(SRTTransform(tr3))) - - print("tr1: %s" % str(tr1)) - - tr2.translate(20, 0) - tr2.rotate(45) - print("tr2: %s" % str(tr2)) - - dt = tr2/tr1 - print("tr2 / tr1 = %s" % str(dt)) - - print("tr2 * tr1 = %s" % str(tr2*tr1)) - - tr4 = SRTTransform() - tr4.scale(-1, 1) - tr4.rotate(30) - print("tr1 * tr4 = %s" % str(tr1*tr4)) - - w1 = widgets.TestROI((19,19), (22, 22), invertible=True) - #w2 = widgets.TestROI((0,0), (150, 150)) - w1.setZValue(10) - s.addItem(w1) - #s.addItem(w2) - w1Base = w1.getState() - #w2Base = w2.getState() - def update(): - tr1 = w1.getGlobalTransform(w1Base) - #tr2 = w2.getGlobalTransform(w2Base) - item.setTransform(tr1) - - #def update2(): - #tr1 = w1.getGlobalTransform(w1Base) - #tr2 = w2.getGlobalTransform(w2Base) - #t1.setTransform(tr1) - #w1.setState(w1Base) - #w1.applyGlobalTransform(tr2) - - w1.sigRegionChanged.connect(update) - #w2.sigRegionChanged.connect(update2) - - \ No newline at end of file diff --git a/pyqtgraph/SignalProxy.py b/pyqtgraph/SignalProxy.py deleted file mode 100644 index 6f9b9112..00000000 --- a/pyqtgraph/SignalProxy.py +++ /dev/null @@ -1,118 +0,0 @@ -# -*- coding: utf-8 -*- -from .Qt import QtCore -from .ptime import time -from . import ThreadsafeTimer - -__all__ = ['SignalProxy'] - -class SignalProxy(QtCore.QObject): - """Object which collects rapid-fire signals and condenses them - into a single signal or a rate-limited stream of signals. - Used, for example, to prevent a SpinBox from generating multiple - signals when the mouse wheel is rolled over it. - - Emits sigDelayed after input signals have stopped for a certain period of time. - """ - - sigDelayed = QtCore.Signal(object) - - def __init__(self, signal, delay=0.3, rateLimit=0, slot=None): - """Initialization arguments: - signal - a bound Signal or pyqtSignal instance - delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s) - slot - Optional function to connect sigDelayed to. - rateLimit - (signals/sec) if greater than 0, this allows signals to stream out at a - steady rate while they are being received. - """ - - QtCore.QObject.__init__(self) - signal.connect(self.signalReceived) - self.signal = signal - self.delay = delay - self.rateLimit = rateLimit - self.args = None - self.timer = ThreadsafeTimer.ThreadsafeTimer() - self.timer.timeout.connect(self.flush) - self.block = False - self.slot = slot - self.lastFlushTime = None - if slot is not None: - self.sigDelayed.connect(slot) - - def setDelay(self, delay): - self.delay = delay - - def signalReceived(self, *args): - """Received signal. Cancel previous timer and store args to be forwarded later.""" - if self.block: - return - self.args = args - if self.rateLimit == 0: - self.timer.stop() - self.timer.start((self.delay*1000)+1) - else: - now = time() - if self.lastFlushTime is None: - leakTime = 0 - else: - lastFlush = self.lastFlushTime - leakTime = max(0, (lastFlush + (1.0 / self.rateLimit)) - now) - - self.timer.stop() - self.timer.start((min(leakTime, self.delay)*1000)+1) - - - def flush(self): - """If there is a signal queued up, send it now.""" - if self.args is None or self.block: - return False - #self.emit(self.signal, *self.args) - self.sigDelayed.emit(self.args) - self.args = None - self.timer.stop() - self.lastFlushTime = time() - return True - - def disconnect(self): - self.block = True - try: - self.signal.disconnect(self.signalReceived) - except: - pass - try: - self.sigDelayed.disconnect(self.slot) - except: - pass - - - -#def proxyConnect(source, signal, slot, delay=0.3): - #"""Connect a signal to a slot with delay. Returns the SignalProxy - #object that was created. Be sure to store this object so it is not - #garbage-collected immediately.""" - #sp = SignalProxy(source, signal, delay) - #if source is None: - #sp.connect(sp, QtCore.SIGNAL('signal'), slot) - #else: - #sp.connect(sp, signal, slot) - #return sp - - -if __name__ == '__main__': - from .Qt import QtGui - app = QtGui.QApplication([]) - win = QtGui.QMainWindow() - spin = QtGui.QSpinBox() - win.setCentralWidget(spin) - win.show() - - def fn(*args): - print("Raw signal:", args) - def fn2(*args): - print("Delayed signal:", args) - - - spin.valueChanged.connect(fn) - #proxy = proxyConnect(spin, QtCore.SIGNAL('valueChanged(int)'), fn) - proxy = SignalProxy(spin.valueChanged, delay=0.5, slot=fn2) - \ No newline at end of file diff --git a/pyqtgraph/ThreadsafeTimer.py b/pyqtgraph/ThreadsafeTimer.py deleted file mode 100644 index f2de9791..00000000 --- a/pyqtgraph/ThreadsafeTimer.py +++ /dev/null @@ -1,41 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui - -class ThreadsafeTimer(QtCore.QObject): - """ - Thread-safe replacement for QTimer. - """ - - timeout = QtCore.Signal() - sigTimerStopRequested = QtCore.Signal() - sigTimerStartRequested = QtCore.Signal(object) - - def __init__(self): - QtCore.QObject.__init__(self) - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.timerFinished) - self.timer.moveToThread(QtCore.QCoreApplication.instance().thread()) - self.moveToThread(QtCore.QCoreApplication.instance().thread()) - self.sigTimerStopRequested.connect(self.stop, QtCore.Qt.QueuedConnection) - self.sigTimerStartRequested.connect(self.start, QtCore.Qt.QueuedConnection) - - - def start(self, timeout): - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - #print "start timer", self, "from gui thread" - self.timer.start(timeout) - else: - #print "start timer", self, "from remote thread" - self.sigTimerStartRequested.emit(timeout) - - def stop(self): - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - #print "stop timer", self, "from gui thread" - self.timer.stop() - else: - #print "stop timer", self, "from remote thread" - self.sigTimerStopRequested.emit() - - def timerFinished(self): - self.timeout.emit() \ No newline at end of file diff --git a/pyqtgraph/Transform3D.py b/pyqtgraph/Transform3D.py deleted file mode 100644 index aa948e28..00000000 --- a/pyqtgraph/Transform3D.py +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 -*- -from .Qt import QtCore, QtGui -import pyqtgraph as pg -import numpy as np - -class Transform3D(QtGui.QMatrix4x4): - """ - Extension of QMatrix4x4 with some helpful methods added. - """ - def __init__(self, *args): - QtGui.QMatrix4x4.__init__(self, *args) - - def matrix(self, nd=3): - if nd == 3: - return np.array(self.copyDataTo()).reshape(4,4) - elif nd == 2: - m = np.array(self.copyDataTo()).reshape(4,4) - m[2] = m[3] - m[:,2] = m[:,3] - return m[:3,:3] - else: - raise Exception("Argument 'nd' must be 2 or 3") - - def map(self, obj): - """ - Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates - """ - if isinstance(obj, np.ndarray) and obj.ndim >= 2 and obj.shape[0] in (2,3): - return pg.transformCoordinates(self, obj) - else: - return QtGui.QMatrix4x4.map(self, obj) - - def inverted(self): - inv, b = QtGui.QMatrix4x4.inverted(self) - return Transform3D(inv), b \ No newline at end of file diff --git a/pyqtgraph/Vector.py b/pyqtgraph/Vector.py deleted file mode 100644 index 4b4fb02f..00000000 --- a/pyqtgraph/Vector.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Vector.py - Extension of QVector3D which adds a few missing methods. -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - -from .Qt import QtGui, QtCore, USE_PYSIDE -import numpy as np - -class Vector(QtGui.QVector3D): - """Extension of QVector3D which adds a few helpful methods.""" - - def __init__(self, *args): - if len(args) == 1: - if isinstance(args[0], QtCore.QSizeF): - QtGui.QVector3D.__init__(self, float(args[0].width()), float(args[0].height()), 0) - return - elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF): - QtGui.QVector3D.__init__(self, float(args[0].x()), float(args[0].y()), 0) - elif hasattr(args[0], '__getitem__'): - vals = list(args[0]) - if len(vals) == 2: - vals.append(0) - if len(vals) != 3: - raise Exception('Cannot init Vector with sequence of length %d' % len(args[0])) - QtGui.QVector3D.__init__(self, *vals) - return - elif len(args) == 2: - QtGui.QVector3D.__init__(self, args[0], args[1], 0) - return - QtGui.QVector3D.__init__(self, *args) - - def __len__(self): - return 3 - - def __add__(self, b): - # workaround for pyside bug. see https://bugs.launchpad.net/pyqtgraph/+bug/1223173 - if USE_PYSIDE and isinstance(b, QtGui.QVector3D): - b = Vector(b) - return QtGui.QVector3D.__add__(self, b) - - #def __reduce__(self): - #return (Point, (self.x(), self.y())) - - def __getitem__(self, i): - if i == 0: - return self.x() - elif i == 1: - return self.y() - elif i == 2: - return self.z() - else: - raise IndexError("Point has no index %s" % str(i)) - - def __setitem__(self, i, x): - if i == 0: - return self.setX(x) - elif i == 1: - return self.setY(x) - elif i == 2: - return self.setZ(x) - else: - raise IndexError("Point has no index %s" % str(i)) - - def __iter__(self): - yield(self.x()) - yield(self.y()) - yield(self.z()) - \ No newline at end of file diff --git a/pyqtgraph/WidgetGroup.py b/pyqtgraph/WidgetGroup.py deleted file mode 100644 index 29541454..00000000 --- a/pyqtgraph/WidgetGroup.py +++ /dev/null @@ -1,298 +0,0 @@ -# -*- coding: utf-8 -*- -""" -WidgetGroup.py - WidgetGroup class for easily managing lots of Qt widgets -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. - -This class addresses the problem of having to save and restore the state -of a large group of widgets. -""" - -from .Qt import QtCore, QtGui -import weakref, inspect -from .python2_3 import asUnicode - - -__all__ = ['WidgetGroup'] - -def splitterState(w): - s = str(w.saveState().toPercentEncoding()) - return s - -def restoreSplitter(w, s): - if type(s) is list: - w.setSizes(s) - elif type(s) is str: - w.restoreState(QtCore.QByteArray.fromPercentEncoding(s)) - else: - print("Can't configure QSplitter using object of type", type(s)) - if w.count() > 0: ## make sure at least one item is not collapsed - for i in w.sizes(): - if i > 0: - return - w.setSizes([50] * w.count()) - -def comboState(w): - ind = w.currentIndex() - data = w.itemData(ind) - #if not data.isValid(): - if data is not None: - try: - if not data.isValid(): - data = None - else: - data = data.toInt()[0] - except AttributeError: - pass - if data is None: - return asUnicode(w.itemText(ind)) - else: - return data - -def setComboState(w, v): - if type(v) is int: - #ind = w.findData(QtCore.QVariant(v)) - ind = w.findData(v) - if ind > -1: - w.setCurrentIndex(ind) - return - w.setCurrentIndex(w.findText(str(v))) - - -class WidgetGroup(QtCore.QObject): - """This class takes a list of widgets and keeps an internal record of their state which is always up to date. Allows reading and writing from groups of widgets simultaneously.""" - - ## List of widget types which can be handled by WidgetGroup. - ## The value for each type is a tuple (change signal function, get function, set function, [auto-add children]) - ## The change signal function that takes an object and returns a signal that is emitted any time the state of the widget changes, not just - ## when it is changed by user interaction. (for example, 'clicked' is not a valid signal here) - ## If the change signal is None, the value of the widget is not cached. - ## Custom widgets not in this list can be made to work with WidgetGroup by giving them a 'widgetGroupInterface' method - ## which returns the tuple. - classes = { - QtGui.QSpinBox: - (lambda w: w.valueChanged, - QtGui.QSpinBox.value, - QtGui.QSpinBox.setValue), - QtGui.QDoubleSpinBox: - (lambda w: w.valueChanged, - QtGui.QDoubleSpinBox.value, - QtGui.QDoubleSpinBox.setValue), - QtGui.QSplitter: - (None, - splitterState, - restoreSplitter, - True), - QtGui.QCheckBox: - (lambda w: w.stateChanged, - QtGui.QCheckBox.isChecked, - QtGui.QCheckBox.setChecked), - QtGui.QComboBox: - (lambda w: w.currentIndexChanged, - comboState, - setComboState), - QtGui.QGroupBox: - (lambda w: w.toggled, - QtGui.QGroupBox.isChecked, - QtGui.QGroupBox.setChecked, - True), - QtGui.QLineEdit: - (lambda w: w.editingFinished, - lambda w: str(w.text()), - QtGui.QLineEdit.setText), - QtGui.QRadioButton: - (lambda w: w.toggled, - QtGui.QRadioButton.isChecked, - QtGui.QRadioButton.setChecked), - QtGui.QSlider: - (lambda w: w.valueChanged, - QtGui.QSlider.value, - QtGui.QSlider.setValue), - } - - sigChanged = QtCore.Signal(str, object) - - - def __init__(self, widgetList=None): - """Initialize WidgetGroup, adding specified widgets into this group. - widgetList can be: - - a list of widget specifications (widget, [name], [scale]) - - a dict of name: widget pairs - - any QObject, and all compatible child widgets will be added recursively. - - The 'scale' parameter for each widget allows QSpinBox to display a different value than the value recorded - in the group state (for example, the program may set a spin box value to 100e-6 and have it displayed as 100 to the user) - """ - QtCore.QObject.__init__(self) - self.widgetList = weakref.WeakKeyDictionary() # Make sure widgets don't stick around just because they are listed here - self.scales = weakref.WeakKeyDictionary() - self.cache = {} ## name:value pairs - self.uncachedWidgets = weakref.WeakKeyDictionary() - if isinstance(widgetList, QtCore.QObject): - self.autoAdd(widgetList) - elif isinstance(widgetList, list): - for w in widgetList: - self.addWidget(*w) - elif isinstance(widgetList, dict): - for name, w in widgetList.items(): - self.addWidget(w, name) - elif widgetList is None: - return - else: - raise Exception("Wrong argument type %s" % type(widgetList)) - - def addWidget(self, w, name=None, scale=None): - if not self.acceptsType(w): - raise Exception("Widget type %s not supported by WidgetGroup" % type(w)) - if name is None: - name = str(w.objectName()) - if name == '': - raise Exception("Cannot add widget '%s' without a name." % str(w)) - self.widgetList[w] = name - self.scales[w] = scale - self.readWidget(w) - - if type(w) in WidgetGroup.classes: - signal = WidgetGroup.classes[type(w)][0] - else: - signal = w.widgetGroupInterface()[0] - - if signal is not None: - if inspect.isfunction(signal) or inspect.ismethod(signal): - signal = signal(w) - signal.connect(self.mkChangeCallback(w)) - else: - self.uncachedWidgets[w] = None - - def findWidget(self, name): - for w in self.widgetList: - if self.widgetList[w] == name: - return w - return None - - def interface(self, obj): - t = type(obj) - if t in WidgetGroup.classes: - return WidgetGroup.classes[t] - else: - return obj.widgetGroupInterface() - - def checkForChildren(self, obj): - """Return true if we should automatically search the children of this object for more.""" - iface = self.interface(obj) - return (len(iface) > 3 and iface[3]) - - def autoAdd(self, obj): - ## Find all children of this object and add them if possible. - accepted = self.acceptsType(obj) - if accepted: - #print "%s auto add %s" % (self.objectName(), obj.objectName()) - self.addWidget(obj) - - if not accepted or self.checkForChildren(obj): - for c in obj.children(): - self.autoAdd(c) - - def acceptsType(self, obj): - for c in WidgetGroup.classes: - if isinstance(obj, c): - return True - if hasattr(obj, 'widgetGroupInterface'): - return True - return False - #return (type(obj) in WidgetGroup.classes) - - def setScale(self, widget, scale): - val = self.readWidget(widget) - self.scales[widget] = scale - self.setWidget(widget, val) - #print "scaling %f to %f" % (val, self.readWidget(widget)) - - - def mkChangeCallback(self, w): - return lambda *args: self.widgetChanged(w, *args) - - def widgetChanged(self, w, *args): - #print "widget changed" - n = self.widgetList[w] - v1 = self.cache[n] - v2 = self.readWidget(w) - if v1 != v2: - #print "widget", n, " = ", v2 - self.emit(QtCore.SIGNAL('changed'), self.widgetList[w], v2) - self.sigChanged.emit(self.widgetList[w], v2) - - def state(self): - for w in self.uncachedWidgets: - self.readWidget(w) - - #cc = self.cache.copy() - #if 'averageGroup' in cc: - #val = cc['averageGroup'] - #w = self.findWidget('averageGroup') - #self.readWidget(w) - #if val != self.cache['averageGroup']: - #print " AverageGroup did not match cached value!" - #else: - #print " AverageGroup OK" - return self.cache.copy() - - def setState(self, s): - #print "SET STATE", self, s - for w in self.widgetList: - n = self.widgetList[w] - #print " restore %s?" % n - if n not in s: - continue - #print " restore state", w, n, s[n] - self.setWidget(w, s[n]) - - def readWidget(self, w): - if type(w) in WidgetGroup.classes: - getFunc = WidgetGroup.classes[type(w)][1] - else: - getFunc = w.widgetGroupInterface()[1] - - if getFunc is None: - return None - - ## if the getter function provided in the interface is a bound method, - ## then just call the method directly. Otherwise, pass in the widget as the first arg - ## to the function. - if inspect.ismethod(getFunc) and getFunc.__self__ is not None: - val = getFunc() - else: - val = getFunc(w) - - if self.scales[w] is not None: - val /= self.scales[w] - #if isinstance(val, QtCore.QString): - #val = str(val) - n = self.widgetList[w] - self.cache[n] = val - return val - - def setWidget(self, w, v): - v1 = v - if self.scales[w] is not None: - v *= self.scales[w] - - if type(w) in WidgetGroup.classes: - setFunc = WidgetGroup.classes[type(w)][2] - else: - setFunc = w.widgetGroupInterface()[2] - - ## if the setter function provided in the interface is a bound method, - ## then just call the method directly. Otherwise, pass in the widget as the first arg - ## to the function. - if inspect.ismethod(setFunc) and setFunc.__self__ is not None: - setFunc(v) - else: - setFunc(w, v) - - #name = self.widgetList[w] - #if name in self.cache and (self.cache[name] != v1): - #print "%s: Cached value %s != set value %s" % (name, str(self.cache[name]), str(v1)) - - - \ No newline at end of file diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py deleted file mode 100644 index f46184b4..00000000 --- a/pyqtgraph/__init__.py +++ /dev/null @@ -1,341 +0,0 @@ -# -*- coding: utf-8 -*- -""" -PyQtGraph - Scientific Graphics and GUI Library for Python -www.pyqtgraph.org -""" - -__version__ = '0.9.8' - -### import all the goodies and add some helper functions for easy CLI use - -## 'Qt' is a local module; it is intended mainly to cover up the differences -## between PyQt4 and PySide. -from .Qt import QtGui - -## not really safe--If we accidentally create another QApplication, the process hangs (and it is very difficult to trace the cause) -#if QtGui.QApplication.instance() is None: - #app = QtGui.QApplication([]) - -import numpy ## pyqtgraph requires numpy - ## (import here to avoid massive error dump later on if numpy is not available) - -import os, sys - -## check python version -## Allow anything >= 2.7 -if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] < 6): - raise Exception("Pyqtgraph requires Python version 2.6 or greater (this is %d.%d)" % (sys.version_info[0], sys.version_info[1])) - -## helpers for 2/3 compatibility -from . import python2_3 - -## install workarounds for numpy bugs -from . import numpy_fix - -## in general openGL is poorly supported with Qt+GraphicsView. -## we only enable it where the performance benefit is critical. -## Note this only applies to 2D graphics; 3D graphics always use OpenGL. -if 'linux' in sys.platform: ## linux has numerous bugs in opengl implementation - useOpenGL = False -elif 'darwin' in sys.platform: ## openGL can have a major impact on mac, but also has serious bugs - useOpenGL = False - if QtGui.QApplication.instance() is not None: - print('Warning: QApplication was created before pyqtgraph was imported; there may be problems (to avoid bugs, call QApplication.setGraphicsSystem("raster") before the QApplication is created).') - QtGui.QApplication.setGraphicsSystem('raster') ## work around a variety of bugs in the native graphics system -else: - useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff. - -CONFIG_OPTIONS = { - 'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl. - 'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox - 'foreground': (150, 150, 150), ## default foreground color for axes, labels, etc. - 'background': (0, 0, 0), ## default background for GraphicsWidget - 'antialias': False, - 'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets - 'useWeave': True, ## Use weave to speed up some operations, if it is available - 'weaveDebug': False, ## Print full error message if weave compile fails - 'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide - 'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code) -} - - -def setConfigOption(opt, value): - CONFIG_OPTIONS[opt] = value - -def setConfigOptions(**opts): - CONFIG_OPTIONS.update(opts) - -def getConfigOption(opt): - return CONFIG_OPTIONS[opt] - - -def systemInfo(): - print("sys.platform: %s" % sys.platform) - print("sys.version: %s" % sys.version) - from .Qt import VERSION_INFO - print("qt bindings: %s" % VERSION_INFO) - - global __version__ - rev = None - if __version__ is None: ## this code was probably checked out from bzr; look up the last-revision file - lastRevFile = os.path.join(os.path.dirname(__file__), '..', '.bzr', 'branch', 'last-revision') - if os.path.exists(lastRevFile): - rev = open(lastRevFile, 'r').read().strip() - - print("pyqtgraph: %s; %s" % (__version__, rev)) - print("config:") - import pprint - pprint.pprint(CONFIG_OPTIONS) - -## Rename orphaned .pyc files. This is *probably* safe :) -## We only do this if __version__ is None, indicating the code was probably pulled -## from the repository. -def renamePyc(startDir): - ### Used to rename orphaned .pyc files - ### When a python file changes its location in the repository, usually the .pyc file - ### is left behind, possibly causing mysterious and difficult to track bugs. - - ### Note that this is no longer necessary for python 3.2; from PEP 3147: - ### "If the py source file is missing, the pyc file inside __pycache__ will be ignored. - ### This eliminates the problem of accidental stale pyc file imports." - - printed = False - startDir = os.path.abspath(startDir) - for path, dirs, files in os.walk(startDir): - if '__pycache__' in path: - continue - for f in files: - fileName = os.path.join(path, f) - base, ext = os.path.splitext(fileName) - py = base + ".py" - if ext == '.pyc' and not os.path.isfile(py): - if not printed: - print("NOTE: Renaming orphaned .pyc files:") - printed = True - n = 1 - while True: - name2 = fileName + ".renamed%d" % n - if not os.path.exists(name2): - break - n += 1 - print(" " + fileName + " ==>") - print(" " + name2) - os.rename(fileName, name2) - -path = os.path.split(__file__)[0] -if __version__ is None and not hasattr(sys, 'frozen') and sys.version_info[0] == 2: ## If we are frozen, there's a good chance we don't have the original .py files anymore. - renamePyc(path) - - -## Import almost everything to make it available from a single namespace -## don't import the more complex systems--canvas, parametertree, flowchart, dockarea -## these must be imported separately. -from . import frozenSupport -def importModules(path, globals, locals, excludes=()): - """Import all modules residing within *path*, return a dict of name: module pairs. - - Note that *path* MUST be relative to the module doing the import. - """ - d = os.path.join(os.path.split(globals['__file__'])[0], path) - files = set() - for f in frozenSupport.listdir(d): - if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']: - files.add(f) - elif f[-3:] == '.py' and f != '__init__.py': - files.add(f[:-3]) - elif f[-4:] == '.pyc' and f != '__init__.pyc': - files.add(f[:-4]) - - mods = {} - path = path.replace(os.sep, '.') - for modName in files: - if modName in excludes: - continue - try: - if len(path) > 0: - modName = path + '.' + modName - #mod = __import__(modName, globals, locals, fromlist=['*']) - mod = __import__(modName, globals, locals, ['*'], 1) - mods[modName] = mod - except: - import traceback - traceback.print_stack() - sys.excepthook(*sys.exc_info()) - print("[Error importing module: %s]" % modName) - - return mods - -def importAll(path, globals, locals, excludes=()): - """Given a list of modules, import all names from each module into the global namespace.""" - mods = importModules(path, globals, locals, excludes) - for mod in mods.values(): - if hasattr(mod, '__all__'): - names = mod.__all__ - else: - names = [n for n in dir(mod) if n[0] != '_'] - for k in names: - if hasattr(mod, k): - globals[k] = getattr(mod, k) - -importAll('graphicsItems', globals(), locals()) -importAll('widgets', globals(), locals(), - excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView']) - -from .imageview import * -from .WidgetGroup import * -from .Point import Point -from .Vector import Vector -from .SRTTransform import SRTTransform -from .Transform3D import Transform3D -from .SRTTransform3D import SRTTransform3D -from .functions import * -from .graphicsWindows import * -from .SignalProxy import * -from .colormap import * -from .ptime import time - -############################################################## -## PyQt and PySide both are prone to crashing on exit. -## There are two general approaches to dealing with this: -## 1. Install atexit handlers that assist in tearing down to avoid crashes. -## This helps, but is never perfect. -## 2. Terminate the process before python starts tearing down -## This is potentially dangerous - -## Attempts to work around exit crashes: -import atexit -def cleanup(): - if not getConfigOption('exitCleanup'): - return - - ViewBox.quit() ## tell ViewBox that it doesn't need to deregister views anymore. - - ## Workaround for Qt exit crash: - ## ALL QGraphicsItems must have a scene before they are deleted. - ## This is potentially very expensive, but preferred over crashing. - ## Note: this appears to be fixed in PySide as of 2012.12, but it should be left in for a while longer.. - if QtGui.QApplication.instance() is None: - return - import gc - s = QtGui.QGraphicsScene() - for o in gc.get_objects(): - try: - if isinstance(o, QtGui.QGraphicsItem) and o.scene() is None: - s.addItem(o) - except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object - continue -atexit.register(cleanup) - - -## Optional function for exiting immediately (with some manual teardown) -def exit(): - """ - Causes python to exit without garbage-collecting any objects, and thus avoids - calling object destructor methods. This is a sledgehammer workaround for - a variety of bugs in PyQt and Pyside that cause crashes on exit. - - This function does the following in an attempt to 'safely' terminate - the process: - - * Invoke atexit callbacks - * Close all open file handles - * os._exit() - - Note: there is some potential for causing damage with this function if you - are using objects that _require_ their destructors to be called (for example, - to properly terminate log files, disconnect from devices, etc). Situations - like this are probably quite rare, but use at your own risk. - """ - - ## first disable our own cleanup function; won't be needing it. - setConfigOptions(exitCleanup=False) - - ## invoke atexit callbacks - atexit._run_exitfuncs() - - ## close file handles - os.closerange(3, 4096) ## just guessing on the maximum descriptor count.. - - os._exit(0) - - - -## Convenience functions for command-line use - -plots = [] -images = [] -QAPP = None - -def plot(*args, **kargs): - """ - Create and return a :class:`PlotWindow ` - (this is just a window with :class:`PlotWidget ` inside), plot data in it. - Accepts a *title* argument to set the title of the window. - All other arguments are used to plot data. (see :func:`PlotItem.plot() `) - """ - mkQApp() - #if 'title' in kargs: - #w = PlotWindow(title=kargs['title']) - #del kargs['title'] - #else: - #w = PlotWindow() - #if len(args)+len(kargs) > 0: - #w.plot(*args, **kargs) - - pwArgList = ['title', 'labels', 'name', 'left', 'right', 'top', 'bottom', 'background'] - pwArgs = {} - dataArgs = {} - for k in kargs: - if k in pwArgList: - pwArgs[k] = kargs[k] - else: - dataArgs[k] = kargs[k] - - w = PlotWindow(**pwArgs) - w.plot(*args, **dataArgs) - plots.append(w) - w.show() - return w - -def image(*args, **kargs): - """ - Create and return an :class:`ImageWindow ` - (this is just a window with :class:`ImageView ` widget inside), show image data inside. - Will show 2D or 3D image data. - Accepts a *title* argument to set the title of the window. - All other arguments are used to show data. (see :func:`ImageView.setImage() `) - """ - mkQApp() - w = ImageWindow(*args, **kargs) - images.append(w) - w.show() - return w -show = image ## for backward compatibility - -def dbg(*args, **kwds): - """ - Create a console window and begin watching for exceptions. - - All arguments are passed to :func:`ConsoleWidget.__init__() `. - """ - mkQApp() - from . import console - c = console.ConsoleWidget(*args, **kwds) - c.catchAllExceptions() - c.show() - global consoles - try: - consoles.append(c) - except NameError: - consoles = [c] - - -def mkQApp(): - global QAPP - inst = QtGui.QApplication.instance() - if inst is None: - QAPP = QtGui.QApplication([]) - else: - QAPP = inst - return QAPP - diff --git a/pyqtgraph/canvas/Canvas.py b/pyqtgraph/canvas/Canvas.py deleted file mode 100644 index 17a39c2b..00000000 --- a/pyqtgraph/canvas/Canvas.py +++ /dev/null @@ -1,608 +0,0 @@ -# -*- coding: utf-8 -*- -if __name__ == '__main__': - import sys, os - md = os.path.dirname(os.path.abspath(__file__)) - sys.path = [os.path.dirname(md), os.path.join(md, '..', '..', '..')] + sys.path - #print md - - -#from pyqtgraph.GraphicsView import GraphicsView -#import pyqtgraph.graphicsItems as graphicsItems -#from pyqtgraph.PlotWidget import PlotWidget -from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE -from pyqtgraph.graphicsItems.ROI import ROI -from pyqtgraph.graphicsItems.ViewBox import ViewBox -from pyqtgraph.graphicsItems.GridItem import GridItem - -if USE_PYSIDE: - from .CanvasTemplate_pyside import * -else: - from .CanvasTemplate_pyqt import * - -#import DataManager -import numpy as np -from pyqtgraph import debug -#import pyqtgraph as pg -import weakref -from .CanvasManager import CanvasManager -#import items -from .CanvasItem import CanvasItem, GroupCanvasItem - -class Canvas(QtGui.QWidget): - - sigSelectionChanged = QtCore.Signal(object, object) - sigItemTransformChanged = QtCore.Signal(object, object) - sigItemTransformChangeFinished = QtCore.Signal(object, object) - - def __init__(self, parent=None, allowTransforms=True, hideCtrl=False, name=None): - QtGui.QWidget.__init__(self, parent) - self.ui = Ui_Form() - self.ui.setupUi(self) - #self.view = self.ui.view - self.view = ViewBox() - self.ui.view.setCentralItem(self.view) - self.itemList = self.ui.itemList - self.itemList.setSelectionMode(self.itemList.ExtendedSelection) - self.allowTransforms = allowTransforms - self.multiSelectBox = SelectBox() - self.view.addItem(self.multiSelectBox) - self.multiSelectBox.hide() - self.multiSelectBox.setZValue(1e6) - self.ui.mirrorSelectionBtn.hide() - self.ui.reflectSelectionBtn.hide() - self.ui.resetTransformsBtn.hide() - - self.redirect = None ## which canvas to redirect items to - self.items = [] - - #self.view.enableMouse() - self.view.setAspectLocked(True) - #self.view.invertY() - - grid = GridItem() - self.grid = CanvasItem(grid, name='Grid', movable=False) - self.addItem(self.grid) - - self.hideBtn = QtGui.QPushButton('>', self) - self.hideBtn.setFixedWidth(20) - self.hideBtn.setFixedHeight(20) - self.ctrlSize = 200 - self.sizeApplied = False - self.hideBtn.clicked.connect(self.hideBtnClicked) - self.ui.splitter.splitterMoved.connect(self.splitterMoved) - - self.ui.itemList.itemChanged.connect(self.treeItemChanged) - self.ui.itemList.sigItemMoved.connect(self.treeItemMoved) - self.ui.itemList.itemSelectionChanged.connect(self.treeItemSelected) - self.ui.autoRangeBtn.clicked.connect(self.autoRange) - self.ui.storeSvgBtn.clicked.connect(self.storeSvg) - self.ui.storePngBtn.clicked.connect(self.storePng) - self.ui.redirectCheck.toggled.connect(self.updateRedirect) - self.ui.redirectCombo.currentIndexChanged.connect(self.updateRedirect) - self.multiSelectBox.sigRegionChanged.connect(self.multiSelectBoxChanged) - self.multiSelectBox.sigRegionChangeFinished.connect(self.multiSelectBoxChangeFinished) - self.ui.mirrorSelectionBtn.clicked.connect(self.mirrorSelectionClicked) - self.ui.reflectSelectionBtn.clicked.connect(self.reflectSelectionClicked) - self.ui.resetTransformsBtn.clicked.connect(self.resetTransformsClicked) - - self.resizeEvent() - if hideCtrl: - self.hideBtnClicked() - - if name is not None: - self.registeredName = CanvasManager.instance().registerCanvas(self, name) - self.ui.redirectCombo.setHostName(self.registeredName) - - self.menu = QtGui.QMenu() - #self.menu.setTitle("Image") - remAct = QtGui.QAction("Remove item", self.menu) - remAct.triggered.connect(self.removeClicked) - self.menu.addAction(remAct) - self.menu.remAct = remAct - self.ui.itemList.contextMenuEvent = self.itemListContextMenuEvent - - - def storeSvg(self): - self.ui.view.writeSvg() - - def storePng(self): - self.ui.view.writeImage() - - def splitterMoved(self): - self.resizeEvent() - - def hideBtnClicked(self): - ctrlSize = self.ui.splitter.sizes()[1] - if ctrlSize == 0: - cs = self.ctrlSize - w = self.ui.splitter.size().width() - if cs > w: - cs = w - 20 - self.ui.splitter.setSizes([w-cs, cs]) - self.hideBtn.setText('>') - else: - self.ctrlSize = ctrlSize - self.ui.splitter.setSizes([100, 0]) - self.hideBtn.setText('<') - self.resizeEvent() - - def autoRange(self): - self.view.autoRange() - - def resizeEvent(self, ev=None): - if ev is not None: - QtGui.QWidget.resizeEvent(self, ev) - self.hideBtn.move(self.ui.view.size().width() - self.hideBtn.width(), 0) - - if not self.sizeApplied: - self.sizeApplied = True - s = min(self.width(), max(100, min(200, self.width()*0.25))) - s2 = self.width()-s - self.ui.splitter.setSizes([s2, s]) - - - def updateRedirect(self, *args): - ### Decide whether/where to redirect items and make it so - cname = str(self.ui.redirectCombo.currentText()) - man = CanvasManager.instance() - if self.ui.redirectCheck.isChecked() and cname != '': - redirect = man.getCanvas(cname) - else: - redirect = None - - if self.redirect is redirect: - return - - self.redirect = redirect - if redirect is None: - self.reclaimItems() - else: - self.redirectItems(redirect) - - - def redirectItems(self, canvas): - for i in self.items: - if i is self.grid: - continue - li = i.listItem - parent = li.parent() - if parent is None: - tree = li.treeWidget() - if tree is None: - print("Skipping item", i, i.name) - continue - tree.removeTopLevelItem(li) - else: - parent.removeChild(li) - canvas.addItem(i) - - - def reclaimItems(self): - items = self.items - #self.items = {'Grid': items['Grid']} - #del items['Grid'] - self.items = [self.grid] - items.remove(self.grid) - - for i in items: - i.canvas.removeItem(i) - self.addItem(i) - - def treeItemChanged(self, item, col): - #gi = self.items.get(item.name, None) - #if gi is None: - #return - try: - citem = item.canvasItem() - except AttributeError: - return - if item.checkState(0) == QtCore.Qt.Checked: - for i in range(item.childCount()): - item.child(i).setCheckState(0, QtCore.Qt.Checked) - citem.show() - else: - for i in range(item.childCount()): - item.child(i).setCheckState(0, QtCore.Qt.Unchecked) - citem.hide() - - def treeItemSelected(self): - sel = self.selectedItems() - #sel = [] - #for listItem in self.itemList.selectedItems(): - #if hasattr(listItem, 'canvasItem') and listItem.canvasItem is not None: - #sel.append(listItem.canvasItem) - #sel = [self.items[item.name] for item in sel] - - if len(sel) == 0: - #self.selectWidget.hide() - return - - multi = len(sel) > 1 - for i in self.items: - #i.ctrlWidget().hide() - ## updated the selected state of every item - i.selectionChanged(i in sel, multi) - - if len(sel)==1: - #item = sel[0] - #item.ctrlWidget().show() - self.multiSelectBox.hide() - self.ui.mirrorSelectionBtn.hide() - self.ui.reflectSelectionBtn.hide() - self.ui.resetTransformsBtn.hide() - elif len(sel) > 1: - self.showMultiSelectBox() - - #if item.isMovable(): - #self.selectBox.setPos(item.item.pos()) - #self.selectBox.setSize(item.item.sceneBoundingRect().size()) - #self.selectBox.show() - #else: - #self.selectBox.hide() - - #self.emit(QtCore.SIGNAL('itemSelected'), self, item) - self.sigSelectionChanged.emit(self, sel) - - def selectedItems(self): - """ - Return list of all selected canvasItems - """ - return [item.canvasItem() for item in self.itemList.selectedItems() if item.canvasItem() is not None] - - #def selectedItem(self): - #sel = self.itemList.selectedItems() - #if sel is None or len(sel) < 1: - #return - #return self.items.get(sel[0].name, None) - - def selectItem(self, item): - li = item.listItem - #li = self.getListItem(item.name()) - #print "select", li - self.itemList.setCurrentItem(li) - - - - def showMultiSelectBox(self): - ## Get list of selected canvas items - items = self.selectedItems() - - rect = self.view.itemBoundingRect(items[0].graphicsItem()) - for i in items: - if not i.isMovable(): ## all items in selection must be movable - return - br = self.view.itemBoundingRect(i.graphicsItem()) - rect = rect|br - - self.multiSelectBox.blockSignals(True) - self.multiSelectBox.setPos([rect.x(), rect.y()]) - self.multiSelectBox.setSize(rect.size()) - self.multiSelectBox.setAngle(0) - self.multiSelectBox.blockSignals(False) - - self.multiSelectBox.show() - - self.ui.mirrorSelectionBtn.show() - self.ui.reflectSelectionBtn.show() - self.ui.resetTransformsBtn.show() - #self.multiSelectBoxBase = self.multiSelectBox.getState().copy() - - def mirrorSelectionClicked(self): - for ci in self.selectedItems(): - ci.mirrorY() - self.showMultiSelectBox() - - def reflectSelectionClicked(self): - for ci in self.selectedItems(): - ci.mirrorXY() - self.showMultiSelectBox() - - def resetTransformsClicked(self): - for i in self.selectedItems(): - i.resetTransformClicked() - self.showMultiSelectBox() - - def multiSelectBoxChanged(self): - self.multiSelectBoxMoved() - - def multiSelectBoxChangeFinished(self): - for ci in self.selectedItems(): - ci.applyTemporaryTransform() - ci.sigTransformChangeFinished.emit(ci) - - def multiSelectBoxMoved(self): - transform = self.multiSelectBox.getGlobalTransform() - for ci in self.selectedItems(): - ci.setTemporaryTransform(transform) - ci.sigTransformChanged.emit(ci) - - - def addGraphicsItem(self, item, **opts): - """Add a new GraphicsItem to the scene at pos. - Common options are name, pos, scale, and z - """ - citem = CanvasItem(item, **opts) - item._canvasItem = citem - self.addItem(citem) - return citem - - - def addGroup(self, name, **kargs): - group = GroupCanvasItem(name=name) - self.addItem(group, **kargs) - return group - - - def addItem(self, citem): - """ - Add an item to the canvas. - """ - - ## Check for redirections - if self.redirect is not None: - name = self.redirect.addItem(citem) - self.items.append(citem) - return name - - if not self.allowTransforms: - citem.setMovable(False) - - citem.sigTransformChanged.connect(self.itemTransformChanged) - citem.sigTransformChangeFinished.connect(self.itemTransformChangeFinished) - citem.sigVisibilityChanged.connect(self.itemVisibilityChanged) - - - ## Determine name to use in the item list - name = citem.opts['name'] - if name is None: - name = 'item' - newname = name - - ## If name already exists, append a number to the end - ## NAH. Let items have the same name if they really want. - #c=0 - #while newname in self.items: - #c += 1 - #newname = name + '_%03d' %c - #name = newname - - ## find parent and add item to tree - #currentNode = self.itemList.invisibleRootItem() - insertLocation = 0 - #print "Inserting node:", name - - - ## determine parent list item where this item should be inserted - parent = citem.parentItem() - if parent in (None, self.view.childGroup): - parent = self.itemList.invisibleRootItem() - else: - parent = parent.listItem - - ## set Z value above all other siblings if none was specified - siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())] - z = citem.zValue() - if z is None: - zvals = [i.zValue() for i in siblings] - if parent == self.itemList.invisibleRootItem(): - if len(zvals) == 0: - z = 0 - else: - z = max(zvals)+10 - else: - if len(zvals) == 0: - z = parent.canvasItem().zValue() - else: - z = max(zvals)+1 - citem.setZValue(z) - - ## determine location to insert item relative to its siblings - for i in range(parent.childCount()): - ch = parent.child(i) - zval = ch.canvasItem().graphicsItem().zValue() ## should we use CanvasItem.zValue here? - if zval < z: - insertLocation = i - break - else: - insertLocation = i+1 - - node = QtGui.QTreeWidgetItem([name]) - flags = node.flags() | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsDragEnabled - if not isinstance(citem, GroupCanvasItem): - flags = flags & ~QtCore.Qt.ItemIsDropEnabled - node.setFlags(flags) - if citem.opts['visible']: - node.setCheckState(0, QtCore.Qt.Checked) - else: - node.setCheckState(0, QtCore.Qt.Unchecked) - - node.name = name - #if citem.opts['parent'] != None: - ## insertLocation is incorrect in this case - parent.insertChild(insertLocation, node) - #else: - #root.insertChild(insertLocation, node) - - citem.name = name - citem.listItem = node - node.canvasItem = weakref.ref(citem) - self.items.append(citem) - - ctrl = citem.ctrlWidget() - ctrl.hide() - self.ui.ctrlLayout.addWidget(ctrl) - - ## inform the canvasItem that its parent canvas has changed - citem.setCanvas(self) - - ## Autoscale to fit the first item added (not including the grid). - if len(self.items) == 2: - self.autoRange() - - - #for n in name: - #nextnode = None - #for x in range(currentNode.childCount()): - #ch = currentNode.child(x) - #if hasattr(ch, 'name'): ## check Z-value of current item to determine insert location - #zval = ch.canvasItem.zValue() - #if zval > z: - ###print " ->", x - #insertLocation = x+1 - #if n == ch.text(0): - #nextnode = ch - #break - #if nextnode is None: ## If name doesn't exist, create it - #nextnode = QtGui.QTreeWidgetItem([n]) - #nextnode.setFlags((nextnode.flags() | QtCore.Qt.ItemIsUserCheckable) & ~QtCore.Qt.ItemIsDropEnabled) - #nextnode.setCheckState(0, QtCore.Qt.Checked) - ### Add node to correct position in list by Z-value - ###print " ==>", insertLocation - #currentNode.insertChild(insertLocation, nextnode) - - #if n == name[-1]: ## This is the leaf; add some extra properties. - #nextnode.name = name - - #if n == name[0]: ## This is the root; make the item movable - #nextnode.setFlags(nextnode.flags() | QtCore.Qt.ItemIsDragEnabled) - #else: - #nextnode.setFlags(nextnode.flags() & ~QtCore.Qt.ItemIsDragEnabled) - - #currentNode = nextnode - return citem - - def treeItemMoved(self, item, parent, index): - ##Item moved in tree; update Z values - if parent is self.itemList.invisibleRootItem(): - item.canvasItem().setParentItem(self.view.childGroup) - else: - item.canvasItem().setParentItem(parent.canvasItem()) - siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())] - - zvals = [i.zValue() for i in siblings] - zvals.sort(reverse=True) - - for i in range(len(siblings)): - item = siblings[i] - item.setZValue(zvals[i]) - #item = self.itemList.topLevelItem(i) - - ##ci = self.items[item.name] - #ci = item.canvasItem - #if ci is None: - #continue - #if ci.zValue() != zvals[i]: - #ci.setZValue(zvals[i]) - - #if self.itemList.topLevelItemCount() < 2: - #return - #name = item.name - #gi = self.items[name] - #if index == 0: - #next = self.itemList.topLevelItem(1) - #z = self.items[next.name].zValue()+1 - #else: - #prev = self.itemList.topLevelItem(index-1) - #z = self.items[prev.name].zValue()-1 - #gi.setZValue(z) - - - - - - - def itemVisibilityChanged(self, item): - listItem = item.listItem - checked = listItem.checkState(0) == QtCore.Qt.Checked - vis = item.isVisible() - if vis != checked: - if vis: - listItem.setCheckState(0, QtCore.Qt.Checked) - else: - listItem.setCheckState(0, QtCore.Qt.Unchecked) - - def removeItem(self, item): - if isinstance(item, QtGui.QTreeWidgetItem): - item = item.canvasItem() - - - if isinstance(item, CanvasItem): - item.setCanvas(None) - listItem = item.listItem - listItem.canvasItem = None - item.listItem = None - self.itemList.removeTopLevelItem(listItem) - self.items.remove(item) - ctrl = item.ctrlWidget() - ctrl.hide() - self.ui.ctrlLayout.removeWidget(ctrl) - else: - if hasattr(item, '_canvasItem'): - self.removeItem(item._canvasItem) - else: - self.view.removeItem(item) - - ## disconnect signals, remove from list, etc.. - - def clear(self): - while len(self.items) > 0: - self.removeItem(self.items[0]) - - - def addToScene(self, item): - self.view.addItem(item) - - def removeFromScene(self, item): - self.view.removeItem(item) - - - def listItems(self): - """Return a dictionary of name:item pairs""" - return self.items - - def getListItem(self, name): - return self.items[name] - - #def scene(self): - #return self.view.scene() - - def itemTransformChanged(self, item): - #self.emit(QtCore.SIGNAL('itemTransformChanged'), self, item) - self.sigItemTransformChanged.emit(self, item) - - def itemTransformChangeFinished(self, item): - #self.emit(QtCore.SIGNAL('itemTransformChangeFinished'), self, item) - self.sigItemTransformChangeFinished.emit(self, item) - - def itemListContextMenuEvent(self, ev): - self.menuItem = self.itemList.itemAt(ev.pos()) - self.menu.popup(ev.globalPos()) - - def removeClicked(self): - self.removeItem(self.menuItem) - self.menuItem = None - import gc - gc.collect() - -class SelectBox(ROI): - def __init__(self, scalable=False): - #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) - ROI.__init__(self, [0,0], [1,1]) - center = [0.5, 0.5] - - if scalable: - self.addScaleHandle([1, 1], center, lockAspect=True) - self.addScaleHandle([0, 0], center, lockAspect=True) - self.addRotateHandle([0, 1], center) - self.addRotateHandle([1, 0], center) - - - - - - - - - - - \ No newline at end of file diff --git a/pyqtgraph/canvas/CanvasItem.py b/pyqtgraph/canvas/CanvasItem.py deleted file mode 100644 index 81388cb6..00000000 --- a/pyqtgraph/canvas/CanvasItem.py +++ /dev/null @@ -1,509 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE -from pyqtgraph.graphicsItems.ROI import ROI -import pyqtgraph as pg -if USE_PYSIDE: - from . import TransformGuiTemplate_pyside as TransformGuiTemplate -else: - from . import TransformGuiTemplate_pyqt as TransformGuiTemplate - -from pyqtgraph import debug - -class SelectBox(ROI): - def __init__(self, scalable=False, rotatable=True): - #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) - ROI.__init__(self, [0,0], [1,1], invertible=True) - center = [0.5, 0.5] - - if scalable: - self.addScaleHandle([1, 1], center, lockAspect=True) - self.addScaleHandle([0, 0], center, lockAspect=True) - if rotatable: - self.addRotateHandle([0, 1], center) - self.addRotateHandle([1, 0], center) - -class CanvasItem(QtCore.QObject): - - sigResetUserTransform = QtCore.Signal(object) - sigTransformChangeFinished = QtCore.Signal(object) - sigTransformChanged = QtCore.Signal(object) - - """CanvasItem takes care of managing an item's state--alpha, visibility, z-value, transformations, etc. and - provides a control widget""" - - sigVisibilityChanged = QtCore.Signal(object) - transformCopyBuffer = None - - def __init__(self, item, **opts): - defOpts = {'name': None, 'z': None, 'movable': True, 'scalable': False, 'rotatable': True, 'visible': True, 'parent':None} #'pos': [0,0], 'scale': [1,1], 'angle':0, - defOpts.update(opts) - self.opts = defOpts - self.selectedAlone = False ## whether this item is the only one selected - - QtCore.QObject.__init__(self) - self.canvas = None - self._graphicsItem = item - - parent = self.opts['parent'] - if parent is not None: - self._graphicsItem.setParentItem(parent.graphicsItem()) - self._parentItem = parent - else: - self._parentItem = None - - z = self.opts['z'] - if z is not None: - item.setZValue(z) - - self.ctrl = QtGui.QWidget() - self.layout = QtGui.QGridLayout() - self.layout.setSpacing(0) - self.layout.setContentsMargins(0,0,0,0) - self.ctrl.setLayout(self.layout) - - self.alphaLabel = QtGui.QLabel("Alpha") - self.alphaSlider = QtGui.QSlider() - self.alphaSlider.setMaximum(1023) - self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.alphaSlider.setValue(1023) - self.layout.addWidget(self.alphaLabel, 0, 0) - self.layout.addWidget(self.alphaSlider, 0, 1) - self.resetTransformBtn = QtGui.QPushButton('Reset Transform') - self.copyBtn = QtGui.QPushButton('Copy') - self.pasteBtn = QtGui.QPushButton('Paste') - - self.transformWidget = QtGui.QWidget() - self.transformGui = TransformGuiTemplate.Ui_Form() - self.transformGui.setupUi(self.transformWidget) - self.layout.addWidget(self.transformWidget, 3, 0, 1, 2) - self.transformGui.mirrorImageBtn.clicked.connect(self.mirrorY) - self.transformGui.reflectImageBtn.clicked.connect(self.mirrorXY) - - self.layout.addWidget(self.resetTransformBtn, 1, 0, 1, 2) - self.layout.addWidget(self.copyBtn, 2, 0, 1, 1) - self.layout.addWidget(self.pasteBtn, 2, 1, 1, 1) - self.alphaSlider.valueChanged.connect(self.alphaChanged) - self.alphaSlider.sliderPressed.connect(self.alphaPressed) - self.alphaSlider.sliderReleased.connect(self.alphaReleased) - #self.canvas.sigSelectionChanged.connect(self.selectionChanged) - self.resetTransformBtn.clicked.connect(self.resetTransformClicked) - self.copyBtn.clicked.connect(self.copyClicked) - self.pasteBtn.clicked.connect(self.pasteClicked) - - self.setMovable(self.opts['movable']) ## update gui to reflect this option - - - if 'transform' in self.opts: - self.baseTransform = self.opts['transform'] - else: - self.baseTransform = pg.SRTTransform() - if 'pos' in self.opts and self.opts['pos'] is not None: - self.baseTransform.translate(self.opts['pos']) - if 'angle' in self.opts and self.opts['angle'] is not None: - self.baseTransform.rotate(self.opts['angle']) - if 'scale' in self.opts and self.opts['scale'] is not None: - self.baseTransform.scale(self.opts['scale']) - - ## create selection box (only visible when selected) - tr = self.baseTransform.saveState() - if 'scalable' not in opts and tr['scale'] == (1,1): - self.opts['scalable'] = True - - ## every CanvasItem implements its own individual selection box - ## so that subclasses are free to make their own. - self.selectBox = SelectBox(scalable=self.opts['scalable'], rotatable=self.opts['rotatable']) - #self.canvas.scene().addItem(self.selectBox) - self.selectBox.hide() - self.selectBox.setZValue(1e6) - self.selectBox.sigRegionChanged.connect(self.selectBoxChanged) ## calls selectBoxMoved - self.selectBox.sigRegionChangeFinished.connect(self.selectBoxChangeFinished) - - ## set up the transformations that will be applied to the item - ## (It is not safe to use item.setTransform, since the item might count on that not changing) - self.itemRotation = QtGui.QGraphicsRotation() - self.itemScale = QtGui.QGraphicsScale() - self._graphicsItem.setTransformations([self.itemRotation, self.itemScale]) - - self.tempTransform = pg.SRTTransform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done. - self.userTransform = pg.SRTTransform() ## stores the total transform of the object - self.resetUserTransform() - - ## now happens inside resetUserTransform -> selectBoxToItem - # self.selectBoxBase = self.selectBox.getState().copy() - - - #print "Created canvas item", self - #print " base:", self.baseTransform - #print " user:", self.userTransform - #print " temp:", self.tempTransform - #print " bounds:", self.item.sceneBoundingRect() - - def setMovable(self, m): - self.opts['movable'] = m - - if m: - self.resetTransformBtn.show() - self.copyBtn.show() - self.pasteBtn.show() - else: - self.resetTransformBtn.hide() - self.copyBtn.hide() - self.pasteBtn.hide() - - def setCanvas(self, canvas): - ## Called by canvas whenever the item is added. - ## It is our responsibility to add all graphicsItems to the canvas's scene - ## The canvas will automatically add our graphicsitem, - ## so we just need to take care of the selectbox. - if canvas is self.canvas: - return - - if canvas is None: - self.canvas.removeFromScene(self._graphicsItem) - self.canvas.removeFromScene(self.selectBox) - else: - canvas.addToScene(self._graphicsItem) - canvas.addToScene(self.selectBox) - self.canvas = canvas - - def graphicsItem(self): - """Return the graphicsItem for this canvasItem.""" - return self._graphicsItem - - def parentItem(self): - return self._parentItem - - def setParentItem(self, parent): - self._parentItem = parent - if parent is not None: - if isinstance(parent, CanvasItem): - parent = parent.graphicsItem() - self.graphicsItem().setParentItem(parent) - - #def name(self): - #return self.opts['name'] - - def copyClicked(self): - CanvasItem.transformCopyBuffer = self.saveTransform() - - def pasteClicked(self): - t = CanvasItem.transformCopyBuffer - if t is None: - return - else: - self.restoreTransform(t) - - def mirrorY(self): - if not self.isMovable(): - return - - #flip = self.transformGui.mirrorImageCheck.isChecked() - #tr = self.userTransform.saveState() - - inv = pg.SRTTransform() - inv.scale(-1, 1) - self.userTransform = self.userTransform * inv - self.updateTransform() - self.selectBoxFromUser() - self.sigTransformChangeFinished.emit(self) - #if flip: - #if tr['scale'][0] < 0 xor tr['scale'][1] < 0: - #return - #else: - #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) - #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) - #self.userTransform.setRotate(-tr['angle']) - #self.updateTransform() - #self.selectBoxFromUser() - #return - #elif not flip: - #if tr['scale'][0] > 0 and tr['scale'][1] > 0: - #return - #else: - #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) - #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) - #self.userTransform.setRotate(-tr['angle']) - #self.updateTransform() - #self.selectBoxFromUser() - #return - - def mirrorXY(self): - if not self.isMovable(): - return - self.rotate(180.) - # inv = pg.SRTTransform() - # inv.scale(-1, -1) - # self.userTransform = self.userTransform * inv #flip lr/ud - # s=self.updateTransform() - # self.setTranslate(-2*s['pos'][0], -2*s['pos'][1]) - # self.selectBoxFromUser() - - - def hasUserTransform(self): - #print self.userRotate, self.userTranslate - return not self.userTransform.isIdentity() - - def ctrlWidget(self): - return self.ctrl - - def alphaChanged(self, val): - alpha = val / 1023. - self._graphicsItem.setOpacity(alpha) - - def isMovable(self): - return self.opts['movable'] - - - def selectBoxMoved(self): - """The selection box has moved; get its transformation information and pass to the graphics item""" - self.userTransform = self.selectBox.getGlobalTransform(relativeTo=self.selectBoxBase) - self.updateTransform() - - def scale(self, x, y): - self.userTransform.scale(x, y) - self.selectBoxFromUser() - self.updateTransform() - - def rotate(self, ang): - self.userTransform.rotate(ang) - self.selectBoxFromUser() - self.updateTransform() - - def translate(self, x, y): - self.userTransform.translate(x, y) - self.selectBoxFromUser() - self.updateTransform() - - def setTranslate(self, x, y): - self.userTransform.setTranslate(x, y) - self.selectBoxFromUser() - self.updateTransform() - - def setRotate(self, angle): - self.userTransform.setRotate(angle) - self.selectBoxFromUser() - self.updateTransform() - - def setScale(self, x, y): - self.userTransform.setScale(x, y) - self.selectBoxFromUser() - self.updateTransform() - - - def setTemporaryTransform(self, transform): - self.tempTransform = transform - self.updateTransform() - - def applyTemporaryTransform(self): - """Collapses tempTransform into UserTransform, resets tempTransform""" - self.userTransform = self.userTransform * self.tempTransform ## order is important! - self.resetTemporaryTransform() - self.selectBoxFromUser() ## update the selection box to match the new userTransform - - #st = self.userTransform.saveState() - - #self.userTransform = self.userTransform * self.tempTransform ## order is important! - - #### matrix multiplication affects the scale factors, need to reset - #if st['scale'][0] < 0 or st['scale'][1] < 0: - #nst = self.userTransform.saveState() - #self.userTransform.setScale([-nst['scale'][0], -nst['scale'][1]]) - - #self.resetTemporaryTransform() - #self.selectBoxFromUser() - #self.selectBoxChangeFinished() - - - - def resetTemporaryTransform(self): - self.tempTransform = pg.SRTTransform() ## don't use Transform.reset()--this transform might be used elsewhere. - self.updateTransform() - - def transform(self): - return self._graphicsItem.transform() - - def updateTransform(self): - """Regenerate the item position from the base, user, and temp transforms""" - transform = self.baseTransform * self.userTransform * self.tempTransform ## order is important - s = transform.saveState() - self._graphicsItem.setPos(*s['pos']) - - self.itemRotation.setAngle(s['angle']) - self.itemScale.setXScale(s['scale'][0]) - self.itemScale.setYScale(s['scale'][1]) - - self.displayTransform(transform) - return(s) # return the transform state - - def displayTransform(self, transform): - """Updates transform numbers in the ctrl widget.""" - - tr = transform.saveState() - - self.transformGui.translateLabel.setText("Translate: (%f, %f)" %(tr['pos'][0], tr['pos'][1])) - self.transformGui.rotateLabel.setText("Rotate: %f degrees" %tr['angle']) - self.transformGui.scaleLabel.setText("Scale: (%f, %f)" %(tr['scale'][0], tr['scale'][1])) - #self.transformGui.mirrorImageCheck.setChecked(False) - #if tr['scale'][0] < 0: - # self.transformGui.mirrorImageCheck.setChecked(True) - - - def resetUserTransform(self): - #self.userRotate = 0 - #self.userTranslate = pg.Point(0,0) - self.userTransform.reset() - self.updateTransform() - - self.selectBox.blockSignals(True) - self.selectBoxToItem() - self.selectBox.blockSignals(False) - self.sigTransformChanged.emit(self) - self.sigTransformChangeFinished.emit(self) - - def resetTransformClicked(self): - self.resetUserTransform() - self.sigResetUserTransform.emit(self) - - def restoreTransform(self, tr): - try: - #self.userTranslate = pg.Point(tr['trans']) - #self.userRotate = tr['rot'] - self.userTransform = pg.SRTTransform(tr) - self.updateTransform() - - self.selectBoxFromUser() ## move select box to match - self.sigTransformChanged.emit(self) - self.sigTransformChangeFinished.emit(self) - except: - #self.userTranslate = pg.Point([0,0]) - #self.userRotate = 0 - self.userTransform = pg.SRTTransform() - debug.printExc("Failed to load transform:") - #print "set transform", self, self.userTranslate - - def saveTransform(self): - """Return a dict containing the current user transform""" - #print "save transform", self, self.userTranslate - #return {'trans': list(self.userTranslate), 'rot': self.userRotate} - return self.userTransform.saveState() - - def selectBoxFromUser(self): - """Move the selection box to match the current userTransform""" - ## user transform - #trans = QtGui.QTransform() - #trans.translate(*self.userTranslate) - #trans.rotate(-self.userRotate) - - #x2, y2 = trans.map(*self.selectBoxBase['pos']) - - self.selectBox.blockSignals(True) - self.selectBox.setState(self.selectBoxBase) - self.selectBox.applyGlobalTransform(self.userTransform) - #self.selectBox.setAngle(self.userRotate) - #self.selectBox.setPos([x2, y2]) - self.selectBox.blockSignals(False) - - - def selectBoxToItem(self): - """Move/scale the selection box so it fits the item's bounding rect. (assumes item is not rotated)""" - self.itemRect = self._graphicsItem.boundingRect() - rect = self._graphicsItem.mapRectToParent(self.itemRect) - self.selectBox.blockSignals(True) - self.selectBox.setPos([rect.x(), rect.y()]) - self.selectBox.setSize(rect.size()) - self.selectBox.setAngle(0) - self.selectBoxBase = self.selectBox.getState().copy() - self.selectBox.blockSignals(False) - - def zValue(self): - return self.opts['z'] - - def setZValue(self, z): - self.opts['z'] = z - if z is not None: - self._graphicsItem.setZValue(z) - - #def selectionChanged(self, canvas, items): - #self.selected = len(items) == 1 and (items[0] is self) - #self.showSelectBox() - - - def selectionChanged(self, sel, multi): - """ - Inform the item that its selection state has changed. - Arguments: - sel: bool, whether the item is currently selected - multi: bool, whether there are multiple items currently selected - """ - self.selectedAlone = sel and not multi - self.showSelectBox() - if self.selectedAlone: - self.ctrlWidget().show() - else: - self.ctrlWidget().hide() - - def showSelectBox(self): - """Display the selection box around this item if it is selected and movable""" - if self.selectedAlone and self.isMovable() and self.isVisible(): #and len(self.canvas.itemList.selectedItems())==1: - self.selectBox.show() - else: - self.selectBox.hide() - - def hideSelectBox(self): - self.selectBox.hide() - - - def selectBoxChanged(self): - self.selectBoxMoved() - #self.updateTransform(self.selectBox) - #self.emit(QtCore.SIGNAL('transformChanged'), self) - self.sigTransformChanged.emit(self) - - def selectBoxChangeFinished(self): - #self.emit(QtCore.SIGNAL('transformChangeFinished'), self) - self.sigTransformChangeFinished.emit(self) - - def alphaPressed(self): - """Hide selection box while slider is moving""" - self.hideSelectBox() - - def alphaReleased(self): - self.showSelectBox() - - def show(self): - if self.opts['visible']: - return - self.opts['visible'] = True - self._graphicsItem.show() - self.showSelectBox() - self.sigVisibilityChanged.emit(self) - - def hide(self): - if not self.opts['visible']: - return - self.opts['visible'] = False - self._graphicsItem.hide() - self.hideSelectBox() - self.sigVisibilityChanged.emit(self) - - def setVisible(self, vis): - if vis: - self.show() - else: - self.hide() - - def isVisible(self): - return self.opts['visible'] - - -class GroupCanvasItem(CanvasItem): - """ - Canvas item used for grouping others - """ - - def __init__(self, **opts): - defOpts = {'movable': False, 'scalable': False} - defOpts.update(opts) - item = pg.ItemGroup() - CanvasItem.__init__(self, item, **defOpts) - diff --git a/pyqtgraph/canvas/CanvasManager.py b/pyqtgraph/canvas/CanvasManager.py deleted file mode 100644 index e89ec00f..00000000 --- a/pyqtgraph/canvas/CanvasManager.py +++ /dev/null @@ -1,76 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtCore, QtGui -if not hasattr(QtCore, 'Signal'): - QtCore.Signal = QtCore.pyqtSignal -import weakref - -class CanvasManager(QtCore.QObject): - SINGLETON = None - - sigCanvasListChanged = QtCore.Signal() - - def __init__(self): - if CanvasManager.SINGLETON is not None: - raise Exception("Can only create one canvas manager.") - CanvasManager.SINGLETON = self - QtCore.QObject.__init__(self) - self.canvases = weakref.WeakValueDictionary() - - @classmethod - def instance(cls): - return CanvasManager.SINGLETON - - def registerCanvas(self, canvas, name): - n2 = name - i = 0 - while n2 in self.canvases: - n2 = "%s_%03d" % (name, i) - i += 1 - self.canvases[n2] = canvas - self.sigCanvasListChanged.emit() - return n2 - - def unregisterCanvas(self, name): - c = self.canvases[name] - del self.canvases[name] - self.sigCanvasListChanged.emit() - - def listCanvases(self): - return list(self.canvases.keys()) - - def getCanvas(self, name): - return self.canvases[name] - - -manager = CanvasManager() - - -class CanvasCombo(QtGui.QComboBox): - def __init__(self, parent=None): - QtGui.QComboBox.__init__(self, parent) - man = CanvasManager.instance() - man.sigCanvasListChanged.connect(self.updateCanvasList) - self.hostName = None - self.updateCanvasList() - - def updateCanvasList(self): - canvases = CanvasManager.instance().listCanvases() - canvases.insert(0, "") - if self.hostName in canvases: - canvases.remove(self.hostName) - - sel = self.currentText() - if sel in canvases: - self.blockSignals(True) ## change does not affect current selection; block signals during update - self.clear() - for i in canvases: - self.addItem(i) - if i == sel: - self.setCurrentIndex(self.count()) - - self.blockSignals(False) - - def setHostName(self, name): - self.hostName = name - self.updateCanvasList() - diff --git a/pyqtgraph/canvas/CanvasTemplate.ui b/pyqtgraph/canvas/CanvasTemplate.ui deleted file mode 100644 index da032906..00000000 --- a/pyqtgraph/canvas/CanvasTemplate.ui +++ /dev/null @@ -1,149 +0,0 @@ - - - Form - - - - 0 - 0 - 490 - 414 - - - - Form - - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - - - - - Store SVG - - - - - - - Store PNG - - - - - - - - 0 - 1 - - - - Auto Range - - - - - - - 0 - - - - - Check to display all local items in a remote canvas. - - - Redirect - - - - - - - - - - - - - 0 - 100 - - - - true - - - - 1 - - - - - - - - 0 - - - - - - - Reset Transforms - - - - - - - Mirror Selection - - - - - - - MirrorXY - - - - - - - - - - - - TreeWidget - QTreeWidget -
pyqtgraph.widgets.TreeWidget
-
- - GraphicsView - QGraphicsView -
pyqtgraph.widgets.GraphicsView
-
- - CanvasCombo - QComboBox -
CanvasManager
-
-
- - -
diff --git a/pyqtgraph/canvas/CanvasTemplate_pyqt.py b/pyqtgraph/canvas/CanvasTemplate_pyqt.py deleted file mode 100644 index 4d1d8208..00000000 --- a/pyqtgraph/canvas/CanvasTemplate_pyqt.py +++ /dev/null @@ -1,100 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './canvas/CanvasTemplate.ui' -# -# Created: Sun Sep 9 14:41:30 2012 -# by: PyQt4 UI code generator 4.9.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName(_fromUtf8("Form")) - Form.resize(490, 414) - self.gridLayout = QtGui.QGridLayout(Form) - self.gridLayout.setMargin(0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.splitter = QtGui.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Horizontal) - self.splitter.setObjectName(_fromUtf8("splitter")) - self.view = GraphicsView(self.splitter) - self.view.setObjectName(_fromUtf8("view")) - self.layoutWidget = QtGui.QWidget(self.splitter) - self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) - self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget) - self.gridLayout_2.setMargin(0) - self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.storeSvgBtn = QtGui.QPushButton(self.layoutWidget) - self.storeSvgBtn.setObjectName(_fromUtf8("storeSvgBtn")) - self.gridLayout_2.addWidget(self.storeSvgBtn, 1, 0, 1, 1) - self.storePngBtn = QtGui.QPushButton(self.layoutWidget) - self.storePngBtn.setObjectName(_fromUtf8("storePngBtn")) - self.gridLayout_2.addWidget(self.storePngBtn, 1, 1, 1, 1) - self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) - self.autoRangeBtn.setSizePolicy(sizePolicy) - self.autoRangeBtn.setObjectName(_fromUtf8("autoRangeBtn")) - self.gridLayout_2.addWidget(self.autoRangeBtn, 3, 0, 1, 2) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setSpacing(0) - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.redirectCheck = QtGui.QCheckBox(self.layoutWidget) - self.redirectCheck.setObjectName(_fromUtf8("redirectCheck")) - self.horizontalLayout.addWidget(self.redirectCheck) - self.redirectCombo = CanvasCombo(self.layoutWidget) - self.redirectCombo.setObjectName(_fromUtf8("redirectCombo")) - self.horizontalLayout.addWidget(self.redirectCombo) - self.gridLayout_2.addLayout(self.horizontalLayout, 6, 0, 1, 2) - self.itemList = TreeWidget(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(100) - sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) - self.itemList.setSizePolicy(sizePolicy) - self.itemList.setHeaderHidden(True) - self.itemList.setObjectName(_fromUtf8("itemList")) - self.itemList.headerItem().setText(0, _fromUtf8("1")) - self.gridLayout_2.addWidget(self.itemList, 7, 0, 1, 2) - self.ctrlLayout = QtGui.QGridLayout() - self.ctrlLayout.setSpacing(0) - self.ctrlLayout.setObjectName(_fromUtf8("ctrlLayout")) - self.gridLayout_2.addLayout(self.ctrlLayout, 11, 0, 1, 2) - self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget) - self.resetTransformsBtn.setObjectName(_fromUtf8("resetTransformsBtn")) - self.gridLayout_2.addWidget(self.resetTransformsBtn, 8, 0, 1, 1) - self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget) - self.mirrorSelectionBtn.setObjectName(_fromUtf8("mirrorSelectionBtn")) - self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) - self.reflectSelectionBtn = QtGui.QPushButton(self.layoutWidget) - self.reflectSelectionBtn.setObjectName(_fromUtf8("reflectSelectionBtn")) - self.gridLayout_2.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) - self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.storeSvgBtn.setText(QtGui.QApplication.translate("Form", "Store SVG", None, QtGui.QApplication.UnicodeUTF8)) - self.storePngBtn.setText(QtGui.QApplication.translate("Form", "Store PNG", None, QtGui.QApplication.UnicodeUTF8)) - self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8)) - self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8)) - self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8)) - self.resetTransformsBtn.setText(QtGui.QApplication.translate("Form", "Reset Transforms", None, QtGui.QApplication.UnicodeUTF8)) - self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8)) - self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8)) - -from pyqtgraph.widgets.GraphicsView import GraphicsView -from CanvasManager import CanvasCombo -from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/canvas/CanvasTemplate_pyside.py b/pyqtgraph/canvas/CanvasTemplate_pyside.py deleted file mode 100644 index 12afdf25..00000000 --- a/pyqtgraph/canvas/CanvasTemplate_pyside.py +++ /dev/null @@ -1,95 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './canvas/CanvasTemplate.ui' -# -# Created: Sun Sep 9 14:41:30 2012 -# by: pyside-uic 0.2.13 running on PySide 1.1.0 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(490, 414) - self.gridLayout = QtGui.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.splitter = QtGui.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Horizontal) - self.splitter.setObjectName("splitter") - self.view = GraphicsView(self.splitter) - self.view.setObjectName("view") - self.layoutWidget = QtGui.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget) - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.storeSvgBtn = QtGui.QPushButton(self.layoutWidget) - self.storeSvgBtn.setObjectName("storeSvgBtn") - self.gridLayout_2.addWidget(self.storeSvgBtn, 1, 0, 1, 1) - self.storePngBtn = QtGui.QPushButton(self.layoutWidget) - self.storePngBtn.setObjectName("storePngBtn") - self.gridLayout_2.addWidget(self.storePngBtn, 1, 1, 1, 1) - self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) - self.autoRangeBtn.setSizePolicy(sizePolicy) - self.autoRangeBtn.setObjectName("autoRangeBtn") - self.gridLayout_2.addWidget(self.autoRangeBtn, 3, 0, 1, 2) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setSpacing(0) - self.horizontalLayout.setObjectName("horizontalLayout") - self.redirectCheck = QtGui.QCheckBox(self.layoutWidget) - self.redirectCheck.setObjectName("redirectCheck") - self.horizontalLayout.addWidget(self.redirectCheck) - self.redirectCombo = CanvasCombo(self.layoutWidget) - self.redirectCombo.setObjectName("redirectCombo") - self.horizontalLayout.addWidget(self.redirectCombo) - self.gridLayout_2.addLayout(self.horizontalLayout, 6, 0, 1, 2) - self.itemList = TreeWidget(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(100) - sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) - self.itemList.setSizePolicy(sizePolicy) - self.itemList.setHeaderHidden(True) - self.itemList.setObjectName("itemList") - self.itemList.headerItem().setText(0, "1") - self.gridLayout_2.addWidget(self.itemList, 7, 0, 1, 2) - self.ctrlLayout = QtGui.QGridLayout() - self.ctrlLayout.setSpacing(0) - self.ctrlLayout.setObjectName("ctrlLayout") - self.gridLayout_2.addLayout(self.ctrlLayout, 11, 0, 1, 2) - self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget) - self.resetTransformsBtn.setObjectName("resetTransformsBtn") - self.gridLayout_2.addWidget(self.resetTransformsBtn, 8, 0, 1, 1) - self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget) - self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") - self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) - self.reflectSelectionBtn = QtGui.QPushButton(self.layoutWidget) - self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") - self.gridLayout_2.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) - self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.storeSvgBtn.setText(QtGui.QApplication.translate("Form", "Store SVG", None, QtGui.QApplication.UnicodeUTF8)) - self.storePngBtn.setText(QtGui.QApplication.translate("Form", "Store PNG", None, QtGui.QApplication.UnicodeUTF8)) - self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8)) - self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8)) - self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8)) - self.resetTransformsBtn.setText(QtGui.QApplication.translate("Form", "Reset Transforms", None, QtGui.QApplication.UnicodeUTF8)) - self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8)) - self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8)) - -from pyqtgraph.widgets.GraphicsView import GraphicsView -from CanvasManager import CanvasCombo -from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/canvas/TransformGuiTemplate.ui b/pyqtgraph/canvas/TransformGuiTemplate.ui deleted file mode 100644 index d8312388..00000000 --- a/pyqtgraph/canvas/TransformGuiTemplate.ui +++ /dev/null @@ -1,75 +0,0 @@ - - - Form - - - - 0 - 0 - 224 - 117 - - - - - 0 - 0 - - - - Form - - - - 1 - - - 0 - - - - - Translate: - - - - - - - Rotate: - - - - - - - Scale: - - - - - - - - - - - - Mirror - - - - - - - Reflect - - - - - - - - - - diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py b/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py deleted file mode 100644 index 1fb86d24..00000000 --- a/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './canvas/TransformGuiTemplate.ui' -# -# Created: Sun Sep 9 14:41:30 2012 -# by: PyQt4 UI code generator 4.9.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName(_fromUtf8("Form")) - Form.resize(224, 117) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) - Form.setSizePolicy(sizePolicy) - self.verticalLayout = QtGui.QVBoxLayout(Form) - self.verticalLayout.setSpacing(1) - self.verticalLayout.setMargin(0) - self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) - self.translateLabel = QtGui.QLabel(Form) - self.translateLabel.setObjectName(_fromUtf8("translateLabel")) - self.verticalLayout.addWidget(self.translateLabel) - self.rotateLabel = QtGui.QLabel(Form) - self.rotateLabel.setObjectName(_fromUtf8("rotateLabel")) - self.verticalLayout.addWidget(self.rotateLabel) - self.scaleLabel = QtGui.QLabel(Form) - self.scaleLabel.setObjectName(_fromUtf8("scaleLabel")) - self.verticalLayout.addWidget(self.scaleLabel) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.mirrorImageBtn = QtGui.QPushButton(Form) - self.mirrorImageBtn.setToolTip(_fromUtf8("")) - self.mirrorImageBtn.setObjectName(_fromUtf8("mirrorImageBtn")) - self.horizontalLayout.addWidget(self.mirrorImageBtn) - self.reflectImageBtn = QtGui.QPushButton(Form) - self.reflectImageBtn.setObjectName(_fromUtf8("reflectImageBtn")) - self.horizontalLayout.addWidget(self.reflectImageBtn) - self.verticalLayout.addLayout(self.horizontalLayout) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.translateLabel.setText(QtGui.QApplication.translate("Form", "Translate:", None, QtGui.QApplication.UnicodeUTF8)) - self.rotateLabel.setText(QtGui.QApplication.translate("Form", "Rotate:", None, QtGui.QApplication.UnicodeUTF8)) - self.scaleLabel.setText(QtGui.QApplication.translate("Form", "Scale:", None, QtGui.QApplication.UnicodeUTF8)) - self.mirrorImageBtn.setText(QtGui.QApplication.translate("Form", "Mirror", None, QtGui.QApplication.UnicodeUTF8)) - self.reflectImageBtn.setText(QtGui.QApplication.translate("Form", "Reflect", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyside.py b/pyqtgraph/canvas/TransformGuiTemplate_pyside.py deleted file mode 100644 index 47b23faa..00000000 --- a/pyqtgraph/canvas/TransformGuiTemplate_pyside.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './canvas/TransformGuiTemplate.ui' -# -# Created: Sun Sep 9 14:41:30 2012 -# by: pyside-uic 0.2.13 running on PySide 1.1.0 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(224, 117) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) - Form.setSizePolicy(sizePolicy) - self.verticalLayout = QtGui.QVBoxLayout(Form) - self.verticalLayout.setSpacing(1) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName("verticalLayout") - self.translateLabel = QtGui.QLabel(Form) - self.translateLabel.setObjectName("translateLabel") - self.verticalLayout.addWidget(self.translateLabel) - self.rotateLabel = QtGui.QLabel(Form) - self.rotateLabel.setObjectName("rotateLabel") - self.verticalLayout.addWidget(self.rotateLabel) - self.scaleLabel = QtGui.QLabel(Form) - self.scaleLabel.setObjectName("scaleLabel") - self.verticalLayout.addWidget(self.scaleLabel) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.mirrorImageBtn = QtGui.QPushButton(Form) - self.mirrorImageBtn.setToolTip("") - self.mirrorImageBtn.setObjectName("mirrorImageBtn") - self.horizontalLayout.addWidget(self.mirrorImageBtn) - self.reflectImageBtn = QtGui.QPushButton(Form) - self.reflectImageBtn.setObjectName("reflectImageBtn") - self.horizontalLayout.addWidget(self.reflectImageBtn) - self.verticalLayout.addLayout(self.horizontalLayout) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.translateLabel.setText(QtGui.QApplication.translate("Form", "Translate:", None, QtGui.QApplication.UnicodeUTF8)) - self.rotateLabel.setText(QtGui.QApplication.translate("Form", "Rotate:", None, QtGui.QApplication.UnicodeUTF8)) - self.scaleLabel.setText(QtGui.QApplication.translate("Form", "Scale:", None, QtGui.QApplication.UnicodeUTF8)) - self.mirrorImageBtn.setText(QtGui.QApplication.translate("Form", "Mirror", None, QtGui.QApplication.UnicodeUTF8)) - self.reflectImageBtn.setText(QtGui.QApplication.translate("Form", "Reflect", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/pyqtgraph/canvas/__init__.py b/pyqtgraph/canvas/__init__.py deleted file mode 100644 index f649d0a1..00000000 --- a/pyqtgraph/canvas/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- coding: utf-8 -*- -from .Canvas import * -from .CanvasItem import * \ No newline at end of file diff --git a/pyqtgraph/colormap.py b/pyqtgraph/colormap.py deleted file mode 100644 index d6169209..00000000 --- a/pyqtgraph/colormap.py +++ /dev/null @@ -1,239 +0,0 @@ -import numpy as np -import scipy.interpolate -from pyqtgraph.Qt import QtGui, QtCore - -class ColorMap(object): - """ - A ColorMap defines a relationship between a scalar value and a range of colors. - ColorMaps are commonly used for false-coloring monochromatic images, coloring - scatter-plot points, and coloring surface plots by height. - - Each color map is defined by a set of colors, each corresponding to a - particular scalar value. For example: - - | 0.0 -> black - | 0.2 -> red - | 0.6 -> yellow - | 1.0 -> white - - The colors for intermediate values are determined by interpolating between - the two nearest colors in either RGB or HSV color space. - - To provide user-defined color mappings, see :class:`GradientWidget `. - """ - - - ## color interpolation modes - RGB = 1 - HSV_POS = 2 - HSV_NEG = 3 - - ## boundary modes - CLIP = 1 - REPEAT = 2 - MIRROR = 3 - - ## return types - BYTE = 1 - FLOAT = 2 - QCOLOR = 3 - - enumMap = { - 'rgb': RGB, - 'hsv+': HSV_POS, - 'hsv-': HSV_NEG, - 'clip': CLIP, - 'repeat': REPEAT, - 'mirror': MIRROR, - 'byte': BYTE, - 'float': FLOAT, - 'qcolor': QCOLOR, - } - - def __init__(self, pos, color, mode=None): - """ - ========= ============================================================== - Arguments - pos Array of positions where each color is defined - color Array of RGBA colors. - Integer data types are interpreted as 0-255; float data types - are interpreted as 0.0-1.0 - mode Array of color modes (ColorMap.RGB, HSV_POS, or HSV_NEG) - indicating the color space that should be used when - interpolating between stops. Note that the last mode value is - ignored. By default, the mode is entirely RGB. - ========= ============================================================== - """ - self.pos = pos - self.color = color - if mode is None: - mode = np.ones(len(pos)) - self.mode = mode - self.stopsCache = {} - - def map(self, data, mode='byte'): - """ - Return an array of colors corresponding to the values in *data*. - Data must be either a scalar position or an array (any shape) of positions. - - The *mode* argument determines the type of data returned: - - =========== =============================================================== - byte (default) Values are returned as 0-255 unsigned bytes. - float Values are returned as 0.0-1.0 floats. - qcolor Values are returned as an array of QColor objects. - =========== =============================================================== - """ - if isinstance(mode, basestring): - mode = self.enumMap[mode.lower()] - - if mode == self.QCOLOR: - pos, color = self.getStops(self.BYTE) - else: - pos, color = self.getStops(mode) - - data = np.clip(data, pos.min(), pos.max()) - - if not isinstance(data, np.ndarray): - interp = scipy.interpolate.griddata(pos, color, np.array([data]))[0] - else: - interp = scipy.interpolate.griddata(pos, color, data) - - if mode == self.QCOLOR: - if not isinstance(data, np.ndarray): - return QtGui.QColor(*interp) - else: - return [QtGui.QColor(*x) for x in interp] - else: - return interp - - def mapToQColor(self, data): - """Convenience function; see :func:`map() `.""" - return self.map(data, mode=self.QCOLOR) - - def mapToByte(self, data): - """Convenience function; see :func:`map() `.""" - return self.map(data, mode=self.BYTE) - - def mapToFloat(self, data): - """Convenience function; see :func:`map() `.""" - return self.map(data, mode=self.FLOAT) - - def getGradient(self, p1=None, p2=None): - """Return a QLinearGradient object spanning from QPoints p1 to p2.""" - if p1 == None: - p1 = QtCore.QPointF(0,0) - if p2 == None: - p2 = QtCore.QPointF(self.pos.max()-self.pos.min(),0) - g = QtGui.QLinearGradient(p1, p2) - - pos, color = self.getStops(mode=self.BYTE) - color = [QtGui.QColor(*x) for x in color] - g.setStops(zip(pos, color)) - - #if self.colorMode == 'rgb': - #ticks = self.listTicks() - #g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) - #elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop - #ticks = self.listTicks() - #stops = [] - #stops.append((ticks[0][1], ticks[0][0].color)) - #for i in range(1,len(ticks)): - #x1 = ticks[i-1][1] - #x2 = ticks[i][1] - #dx = (x2-x1) / 10. - #for j in range(1,10): - #x = x1 + dx*j - #stops.append((x, self.getColor(x))) - #stops.append((x2, self.getColor(x2))) - #g.setStops(stops) - return g - - def getColors(self, mode=None): - """Return list of all color stops converted to the specified mode. - If mode is None, then no conversion is done.""" - if isinstance(mode, basestring): - mode = self.enumMap[mode.lower()] - - color = self.color - if mode in [self.BYTE, self.QCOLOR] and color.dtype.kind == 'f': - color = (color * 255).astype(np.ubyte) - elif mode == self.FLOAT and color.dtype.kind != 'f': - color = color.astype(float) / 255. - - if mode == self.QCOLOR: - color = [QtGui.QColor(*x) for x in color] - - return color - - def getStops(self, mode): - ## Get fully-expanded set of RGBA stops in either float or byte mode. - if mode not in self.stopsCache: - color = self.color - if mode == self.BYTE and color.dtype.kind == 'f': - color = (color * 255).astype(np.ubyte) - elif mode == self.FLOAT and color.dtype.kind != 'f': - color = color.astype(float) / 255. - - ## to support HSV mode, we need to do a little more work.. - #stops = [] - #for i in range(len(self.pos)): - #pos = self.pos[i] - #color = color[i] - - #imode = self.mode[i] - #if imode == self.RGB: - #stops.append((x,color)) - #else: - #ns = - self.stopsCache[mode] = (self.pos, color) - return self.stopsCache[mode] - - def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode='byte'): - """ - Return an RGB(A) lookup table (ndarray). - - ============= ============================================================================ - **Arguments** - start The starting value in the lookup table (default=0.0) - stop The final value in the lookup table (default=1.0) - nPts The number of points in the returned lookup table. - alpha True, False, or None - Specifies whether or not alpha values are included - in the table. If alpha is None, it will be automatically determined. - mode Determines return type: 'byte' (0-255), 'float' (0.0-1.0), or 'qcolor'. - See :func:`map() `. - ============= ============================================================================ - """ - if isinstance(mode, basestring): - mode = self.enumMap[mode.lower()] - - if alpha is None: - alpha = self.usesAlpha() - - x = np.linspace(start, stop, nPts) - table = self.map(x, mode) - - if not alpha: - return table[:,:3] - else: - return table - - def usesAlpha(self): - """Return True if any stops have an alpha < 255""" - max = 1.0 if self.color.dtype.kind == 'f' else 255 - return np.any(self.color[:,3] != max) - - def isMapTrivial(self): - """ - Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0. - """ - if len(self.pos) != 2: - return False - if self.pos[0] != 0.0 or self.pos[1] != 1.0: - return False - if self.color.dtype.kind == 'f': - return np.all(self.color == np.array([[0.,0.,0.,1.], [1.,1.,1.,1.]])) - else: - return np.all(self.color == np.array([[0,0,0,255], [255,255,255,255]])) - - diff --git a/pyqtgraph/configfile.py b/pyqtgraph/configfile.py deleted file mode 100644 index f709c786..00000000 --- a/pyqtgraph/configfile.py +++ /dev/null @@ -1,202 +0,0 @@ -# -*- coding: utf-8 -*- -""" -configfile.py - Human-readable text configuration file library -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. - -Used for reading and writing dictionary objects to a python-like configuration -file format. Data structures may be nested and contain any data type as long -as it can be converted to/from a string using repr and eval. -""" - -import re, os, sys -from .pgcollections import OrderedDict -GLOBAL_PATH = None # so not thread safe. -from . import units -from .python2_3 import asUnicode - -class ParseError(Exception): - def __init__(self, message, lineNum, line, fileName=None): - self.lineNum = lineNum - self.line = line - #self.message = message - self.fileName = fileName - Exception.__init__(self, message) - - def __str__(self): - if self.fileName is None: - msg = "Error parsing string at line %d:\n" % self.lineNum - else: - msg = "Error parsing config file '%s' at line %d:\n" % (self.fileName, self.lineNum) - msg += "%s\n%s" % (self.line, self.message) - return msg - #raise Exception() - - -def writeConfigFile(data, fname): - s = genString(data) - fd = open(fname, 'w') - fd.write(s) - fd.close() - -def readConfigFile(fname): - #cwd = os.getcwd() - global GLOBAL_PATH - if GLOBAL_PATH is not None: - fname2 = os.path.join(GLOBAL_PATH, fname) - if os.path.exists(fname2): - fname = fname2 - - GLOBAL_PATH = os.path.dirname(os.path.abspath(fname)) - - try: - #os.chdir(newDir) ## bad. - fd = open(fname) - s = asUnicode(fd.read()) - fd.close() - s = s.replace("\r\n", "\n") - s = s.replace("\r", "\n") - data = parseString(s)[1] - except ParseError: - sys.exc_info()[1].fileName = fname - raise - except: - print("Error while reading config file %s:"% fname) - raise - #finally: - #os.chdir(cwd) - return data - -def appendConfigFile(data, fname): - s = genString(data) - fd = open(fname, 'a') - fd.write(s) - fd.close() - - -def genString(data, indent=''): - s = '' - for k in data: - sk = str(k) - if len(sk) == 0: - print(data) - raise Exception('blank dict keys not allowed (see data above)') - if sk[0] == ' ' or ':' in sk: - print(data) - raise Exception('dict keys must not contain ":" or start with spaces [offending key is "%s"]' % sk) - if isinstance(data[k], dict): - s += indent + sk + ':\n' - s += genString(data[k], indent + ' ') - else: - s += indent + sk + ': ' + repr(data[k]) + '\n' - return s - -def parseString(lines, start=0): - - data = OrderedDict() - if isinstance(lines, basestring): - lines = lines.split('\n') - lines = [l for l in lines if re.search(r'\S', l) and not re.match(r'\s*#', l)] ## remove empty lines - - indent = measureIndent(lines[start]) - ln = start - 1 - - try: - while True: - ln += 1 - #print ln - if ln >= len(lines): - break - - l = lines[ln] - - ## Skip blank lines or lines starting with # - if re.match(r'\s*#', l) or not re.search(r'\S', l): - continue - - ## Measure line indentation, make sure it is correct for this level - lineInd = measureIndent(l) - if lineInd < indent: - ln -= 1 - break - if lineInd > indent: - #print lineInd, indent - raise ParseError('Indentation is incorrect. Expected %d, got %d' % (indent, lineInd), ln+1, l) - - - if ':' not in l: - raise ParseError('Missing colon', ln+1, l) - - (k, p, v) = l.partition(':') - k = k.strip() - v = v.strip() - - ## set up local variables to use for eval - local = units.allUnits.copy() - local['OrderedDict'] = OrderedDict - local['readConfigFile'] = readConfigFile - if len(k) < 1: - raise ParseError('Missing name preceding colon', ln+1, l) - if k[0] == '(' and k[-1] == ')': ## If the key looks like a tuple, try evaluating it. - try: - k1 = eval(k, local) - if type(k1) is tuple: - k = k1 - except: - pass - if re.search(r'\S', v) and v[0] != '#': ## eval the value - try: - val = eval(v, local) - except: - ex = sys.exc_info()[1] - raise ParseError("Error evaluating expression '%s': [%s: %s]" % (v, ex.__class__.__name__, str(ex)), (ln+1), l) - else: - if ln+1 >= len(lines) or measureIndent(lines[ln+1]) <= indent: - #print "blank dict" - val = {} - else: - #print "Going deeper..", ln+1 - (ln, val) = parseString(lines, start=ln+1) - data[k] = val - #print k, repr(val) - except ParseError: - raise - except: - ex = sys.exc_info()[1] - raise ParseError("%s: %s" % (ex.__class__.__name__, str(ex)), ln+1, l) - #print "Returning shallower..", ln+1 - return (ln, data) - -def measureIndent(s): - n = 0 - while n < len(s) and s[n] == ' ': - n += 1 - return n - - - -if __name__ == '__main__': - import tempfile - fn = tempfile.mktemp() - tf = open(fn, 'w') - cf = """ -key: 'value' -key2: ##comment - ##comment - key21: 'value' ## comment - ##comment - key22: [1,2,3] - key23: 234 #comment - """ - tf.write(cf) - tf.close() - print("=== Test:===") - num = 1 - for line in cf.split('\n'): - print("%02d %s" % (num, line)) - num += 1 - print(cf) - print("============") - data = readConfigFile(fn) - print(data) - os.remove(fn) diff --git a/pyqtgraph/console/CmdInput.py b/pyqtgraph/console/CmdInput.py deleted file mode 100644 index 3e9730d6..00000000 --- a/pyqtgraph/console/CmdInput.py +++ /dev/null @@ -1,62 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui -from pyqtgraph.python2_3 import asUnicode - -class CmdInput(QtGui.QLineEdit): - - sigExecuteCmd = QtCore.Signal(object) - - def __init__(self, parent): - QtGui.QLineEdit.__init__(self, parent) - self.history = [""] - self.ptr = 0 - #self.lastCmd = None - #self.setMultiline(False) - - def keyPressEvent(self, ev): - #print "press:", ev.key(), QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_Enter - if ev.key() == QtCore.Qt.Key_Up and self.ptr < len(self.history) - 1: - self.setHistory(self.ptr+1) - ev.accept() - return - elif ev.key() == QtCore.Qt.Key_Down and self.ptr > 0: - self.setHistory(self.ptr-1) - ev.accept() - return - elif ev.key() == QtCore.Qt.Key_Return: - self.execCmd() - else: - QtGui.QLineEdit.keyPressEvent(self, ev) - self.history[0] = asUnicode(self.text()) - - def execCmd(self): - cmd = asUnicode(self.text()) - if len(self.history) == 1 or cmd != self.history[1]: - self.history.insert(1, cmd) - #self.lastCmd = cmd - self.history[0] = "" - self.setHistory(0) - self.sigExecuteCmd.emit(cmd) - - def setHistory(self, num): - self.ptr = num - self.setText(self.history[self.ptr]) - - #def setMultiline(self, m): - #height = QtGui.QFontMetrics(self.font()).lineSpacing() - #if m: - #self.setFixedHeight(height*5) - #else: - #self.setFixedHeight(height+15) - #self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - #self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - - #def sizeHint(self): - #hint = QtGui.QPlainTextEdit.sizeHint(self) - #height = QtGui.QFontMetrics(self.font()).lineSpacing() - #hint.setHeight(height) - #return hint - - - - \ No newline at end of file diff --git a/pyqtgraph/console/Console.py b/pyqtgraph/console/Console.py deleted file mode 100644 index 982c2424..00000000 --- a/pyqtgraph/console/Console.py +++ /dev/null @@ -1,375 +0,0 @@ - -from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE -import sys, re, os, time, traceback, subprocess -import pyqtgraph as pg -if USE_PYSIDE: - from . import template_pyside as template -else: - from . import template_pyqt as template - -import pyqtgraph.exceptionHandling as exceptionHandling -import pickle - -class ConsoleWidget(QtGui.QWidget): - """ - Widget displaying console output and accepting command input. - Implements: - - - eval python expressions / exec python statements - - storable history of commands - - exception handling allowing commands to be interpreted in the context of any level in the exception stack frame - - Why not just use python in an interactive shell (or ipython) ? There are a few reasons: - - - pyside does not yet allow Qt event processing and interactive shell at the same time - - on some systems, typing in the console _blocks_ the qt event loop until the user presses enter. This can - be baffling and frustrating to users since it would appear the program has frozen. - - some terminals (eg windows cmd.exe) have notoriously unfriendly interfaces - - ability to add extra features like exception stack introspection - - ability to have multiple interactive prompts, including for spawned sub-processes - """ - - def __init__(self, parent=None, namespace=None, historyFile=None, text=None, editor=None): - """ - ============ ============================================================================ - Arguments: - namespace dictionary containing the initial variables present in the default namespace - historyFile optional file for storing command history - text initial text to display in the console window - editor optional string for invoking code editor (called when stack trace entries are - double-clicked). May contain {fileName} and {lineNum} format keys. Example:: - - editorCommand --loadfile {fileName} --gotoline {lineNum} - ============ ============================================================================= - """ - QtGui.QWidget.__init__(self, parent) - if namespace is None: - namespace = {} - self.localNamespace = namespace - self.editor = editor - self.multiline = None - self.inCmd = False - - self.ui = template.Ui_Form() - self.ui.setupUi(self) - self.output = self.ui.output - self.input = self.ui.input - self.input.setFocus() - - if text is not None: - self.output.setPlainText(text) - - self.historyFile = historyFile - - history = self.loadHistory() - if history is not None: - self.input.history = [""] + history - self.ui.historyList.addItems(history[::-1]) - self.ui.historyList.hide() - self.ui.exceptionGroup.hide() - - self.input.sigExecuteCmd.connect(self.runCmd) - self.ui.historyBtn.toggled.connect(self.ui.historyList.setVisible) - self.ui.historyList.itemClicked.connect(self.cmdSelected) - self.ui.historyList.itemDoubleClicked.connect(self.cmdDblClicked) - self.ui.exceptionBtn.toggled.connect(self.ui.exceptionGroup.setVisible) - - self.ui.catchAllExceptionsBtn.toggled.connect(self.catchAllExceptions) - self.ui.catchNextExceptionBtn.toggled.connect(self.catchNextException) - self.ui.clearExceptionBtn.clicked.connect(self.clearExceptionClicked) - self.ui.exceptionStackList.itemClicked.connect(self.stackItemClicked) - self.ui.exceptionStackList.itemDoubleClicked.connect(self.stackItemDblClicked) - self.ui.onlyUncaughtCheck.toggled.connect(self.updateSysTrace) - - self.currentTraceback = None - - def loadHistory(self): - """Return the list of previously-invoked command strings (or None).""" - if self.historyFile is not None: - return pickle.load(open(self.historyFile, 'rb')) - - def saveHistory(self, history): - """Store the list of previously-invoked command strings.""" - if self.historyFile is not None: - pickle.dump(open(self.historyFile, 'wb'), history) - - def runCmd(self, cmd): - #cmd = str(self.input.lastCmd) - self.stdout = sys.stdout - self.stderr = sys.stderr - encCmd = re.sub(r'>', '>', re.sub(r'<', '<', cmd)) - encCmd = re.sub(r' ', ' ', encCmd) - - self.ui.historyList.addItem(cmd) - self.saveHistory(self.input.history[1:100]) - - try: - sys.stdout = self - sys.stderr = self - if self.multiline is not None: - self.write("
%s\n"%encCmd, html=True) - self.execMulti(cmd) - else: - self.write("
%s\n"%encCmd, html=True) - self.inCmd = True - self.execSingle(cmd) - - if not self.inCmd: - self.write("
\n", html=True) - - finally: - sys.stdout = self.stdout - sys.stderr = self.stderr - - sb = self.output.verticalScrollBar() - sb.setValue(sb.maximum()) - sb = self.ui.historyList.verticalScrollBar() - sb.setValue(sb.maximum()) - - def globals(self): - frame = self.currentFrame() - if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): - return self.currentFrame().tb_frame.f_globals - else: - return globals() - - def locals(self): - frame = self.currentFrame() - if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): - return self.currentFrame().tb_frame.f_locals - else: - return self.localNamespace - - def currentFrame(self): - ## Return the currently selected exception stack frame (or None if there is no exception) - if self.currentTraceback is None: - return None - index = self.ui.exceptionStackList.currentRow() - tb = self.currentTraceback - for i in range(index): - tb = tb.tb_next - return tb - - def execSingle(self, cmd): - try: - output = eval(cmd, self.globals(), self.locals()) - self.write(repr(output) + '\n') - except SyntaxError: - try: - exec(cmd, self.globals(), self.locals()) - except SyntaxError as exc: - if 'unexpected EOF' in exc.msg: - self.multiline = cmd - else: - self.displayException() - except: - self.displayException() - except: - self.displayException() - - - def execMulti(self, nextLine): - #self.stdout.write(nextLine+"\n") - if nextLine.strip() != '': - self.multiline += "\n" + nextLine - return - else: - cmd = self.multiline - - try: - output = eval(cmd, self.globals(), self.locals()) - self.write(str(output) + '\n') - self.multiline = None - except SyntaxError: - try: - exec(cmd, self.globals(), self.locals()) - self.multiline = None - except SyntaxError as exc: - if 'unexpected EOF' in exc.msg: - self.multiline = cmd - else: - self.displayException() - self.multiline = None - except: - self.displayException() - self.multiline = None - except: - self.displayException() - self.multiline = None - - def write(self, strn, html=False): - self.output.moveCursor(QtGui.QTextCursor.End) - if html: - self.output.textCursor().insertHtml(strn) - else: - if self.inCmd: - self.inCmd = False - self.output.textCursor().insertHtml("

") - #self.stdout.write("

") - self.output.insertPlainText(strn) - #self.stdout.write(strn) - - def displayException(self): - """ - Display the current exception and stack. - """ - tb = traceback.format_exc() - lines = [] - indent = 4 - prefix = '' - for l in tb.split('\n'): - lines.append(" "*indent + prefix + l) - self.write('\n'.join(lines)) - self.exceptionHandler(*sys.exc_info()) - - def cmdSelected(self, item): - index = -(self.ui.historyList.row(item)+1) - self.input.setHistory(index) - self.input.setFocus() - - def cmdDblClicked(self, item): - index = -(self.ui.historyList.row(item)+1) - self.input.setHistory(index) - self.input.execCmd() - - def flush(self): - pass - - def catchAllExceptions(self, catch=True): - """ - If True, the console will catch all unhandled exceptions and display the stack - trace. Each exception caught clears the last. - """ - self.ui.catchAllExceptionsBtn.setChecked(catch) - if catch: - self.ui.catchNextExceptionBtn.setChecked(False) - self.enableExceptionHandling() - self.ui.exceptionBtn.setChecked(True) - else: - self.disableExceptionHandling() - - def catchNextException(self, catch=True): - """ - If True, the console will catch the next unhandled exception and display the stack - trace. - """ - self.ui.catchNextExceptionBtn.setChecked(catch) - if catch: - self.ui.catchAllExceptionsBtn.setChecked(False) - self.enableExceptionHandling() - self.ui.exceptionBtn.setChecked(True) - else: - self.disableExceptionHandling() - - def enableExceptionHandling(self): - exceptionHandling.register(self.exceptionHandler) - self.updateSysTrace() - - def disableExceptionHandling(self): - exceptionHandling.unregister(self.exceptionHandler) - self.updateSysTrace() - - def clearExceptionClicked(self): - self.currentTraceback = None - self.ui.exceptionInfoLabel.setText("[No current exception]") - self.ui.exceptionStackList.clear() - self.ui.clearExceptionBtn.setEnabled(False) - - def stackItemClicked(self, item): - pass - - def stackItemDblClicked(self, item): - editor = self.editor - if editor is None: - editor = pg.getConfigOption('editorCommand') - if editor is None: - return - tb = self.currentFrame() - lineNum = tb.tb_lineno - fileName = tb.tb_frame.f_code.co_filename - subprocess.Popen(self.editor.format(fileName=fileName, lineNum=lineNum), shell=True) - - - #def allExceptionsHandler(self, *args): - #self.exceptionHandler(*args) - - #def nextExceptionHandler(self, *args): - #self.ui.catchNextExceptionBtn.setChecked(False) - #self.exceptionHandler(*args) - - def updateSysTrace(self): - ## Install or uninstall sys.settrace handler - - if not self.ui.catchNextExceptionBtn.isChecked() and not self.ui.catchAllExceptionsBtn.isChecked(): - if sys.gettrace() == self.systrace: - sys.settrace(None) - return - - if self.ui.onlyUncaughtCheck.isChecked(): - if sys.gettrace() == self.systrace: - sys.settrace(None) - else: - if sys.gettrace() is not None and sys.gettrace() != self.systrace: - self.ui.onlyUncaughtCheck.setChecked(False) - raise Exception("sys.settrace is in use; cannot monitor for caught exceptions.") - else: - sys.settrace(self.systrace) - - def exceptionHandler(self, excType, exc, tb): - if self.ui.catchNextExceptionBtn.isChecked(): - self.ui.catchNextExceptionBtn.setChecked(False) - elif not self.ui.catchAllExceptionsBtn.isChecked(): - return - - self.ui.clearExceptionBtn.setEnabled(True) - self.currentTraceback = tb - - excMessage = ''.join(traceback.format_exception_only(excType, exc)) - self.ui.exceptionInfoLabel.setText(excMessage) - self.ui.exceptionStackList.clear() - for index, line in enumerate(traceback.extract_tb(tb)): - self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line) - - def systrace(self, frame, event, arg): - if event == 'exception' and self.checkException(*arg): - self.exceptionHandler(*arg) - return self.systrace - - def checkException(self, excType, exc, tb): - ## Return True if the exception is interesting; False if it should be ignored. - - filename = tb.tb_frame.f_code.co_filename - function = tb.tb_frame.f_code.co_name - - ## Go through a list of common exception points we like to ignore: - if excType is GeneratorExit or excType is StopIteration: - return False - if excType is KeyError: - if filename.endswith('python2.7/weakref.py') and function in ('__contains__', 'get'): - return False - if filename.endswith('python2.7/copy.py') and function == '_keep_alive': - return False - if excType is AttributeError: - if filename.endswith('python2.7/collections.py') and function == '__init__': - return False - if filename.endswith('numpy/core/fromnumeric.py') and function in ('all', '_wrapit', 'transpose', 'sum'): - return False - if filename.endswith('numpy/core/arrayprint.py') and function in ('_array2string'): - return False - if filename.endswith('MetaArray.py') and function == '__getattr__': - for name in ('__array_interface__', '__array_struct__', '__array__'): ## numpy looks for these when converting objects to array - if name in exc: - return False - if filename.endswith('flowchart/eq.py'): - return False - if filename.endswith('pyqtgraph/functions.py') and function == 'makeQImage': - return False - if excType is TypeError: - if filename.endswith('numpy/lib/function_base.py') and function == 'iterable': - return False - if excType is ZeroDivisionError: - if filename.endswith('python2.7/traceback.py'): - return False - - return True - diff --git a/pyqtgraph/console/__init__.py b/pyqtgraph/console/__init__.py deleted file mode 100644 index 16436abd..00000000 --- a/pyqtgraph/console/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .Console import ConsoleWidget \ No newline at end of file diff --git a/pyqtgraph/console/template.ui b/pyqtgraph/console/template.ui deleted file mode 100644 index 6e5c5be3..00000000 --- a/pyqtgraph/console/template.ui +++ /dev/null @@ -1,184 +0,0 @@ - - - Form - - - - 0 - 0 - 710 - 497 - - - - Console - - - - 0 - - - 0 - - - - - Qt::Vertical - - - - - - - - Monospace - - - - true - - - - - - - - - - - - History.. - - - true - - - - - - - Exceptions.. - - - true - - - - - - - - - - - Monospace - - - - - - Exception Handling - - - - 0 - - - 0 - - - 0 - - - - - Show All Exceptions - - - true - - - - - - - Show Next Exception - - - true - - - - - - - Only Uncaught Exceptions - - - true - - - - - - - true - - - - - - - Run commands in selected stack frame - - - true - - - - - - - Exception Info - - - - - - - false - - - Clear Exception - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - CmdInput - QLineEdit -
.CmdInput
-
-
- - -
diff --git a/pyqtgraph/console/template_pyqt.py b/pyqtgraph/console/template_pyqt.py deleted file mode 100644 index 89ee6cff..00000000 --- a/pyqtgraph/console/template_pyqt.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './console/template.ui' -# -# Created: Sun Sep 9 14:41:30 2012 -# by: PyQt4 UI code generator 4.9.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName(_fromUtf8("Form")) - Form.resize(710, 497) - self.gridLayout = QtGui.QGridLayout(Form) - self.gridLayout.setMargin(0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.splitter = QtGui.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Vertical) - self.splitter.setObjectName(_fromUtf8("splitter")) - self.layoutWidget = QtGui.QWidget(self.splitter) - self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) - self.verticalLayout = QtGui.QVBoxLayout(self.layoutWidget) - self.verticalLayout.setMargin(0) - self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) - self.output = QtGui.QPlainTextEdit(self.layoutWidget) - font = QtGui.QFont() - font.setFamily(_fromUtf8("Monospace")) - self.output.setFont(font) - self.output.setReadOnly(True) - self.output.setObjectName(_fromUtf8("output")) - self.verticalLayout.addWidget(self.output) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.input = CmdInput(self.layoutWidget) - self.input.setObjectName(_fromUtf8("input")) - self.horizontalLayout.addWidget(self.input) - self.historyBtn = QtGui.QPushButton(self.layoutWidget) - self.historyBtn.setCheckable(True) - self.historyBtn.setObjectName(_fromUtf8("historyBtn")) - self.horizontalLayout.addWidget(self.historyBtn) - self.exceptionBtn = QtGui.QPushButton(self.layoutWidget) - self.exceptionBtn.setCheckable(True) - self.exceptionBtn.setObjectName(_fromUtf8("exceptionBtn")) - self.horizontalLayout.addWidget(self.exceptionBtn) - self.verticalLayout.addLayout(self.horizontalLayout) - self.historyList = QtGui.QListWidget(self.splitter) - font = QtGui.QFont() - font.setFamily(_fromUtf8("Monospace")) - self.historyList.setFont(font) - self.historyList.setObjectName(_fromUtf8("historyList")) - self.exceptionGroup = QtGui.QGroupBox(self.splitter) - self.exceptionGroup.setObjectName(_fromUtf8("exceptionGroup")) - self.gridLayout_2 = QtGui.QGridLayout(self.exceptionGroup) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) - self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.catchAllExceptionsBtn = QtGui.QPushButton(self.exceptionGroup) - self.catchAllExceptionsBtn.setCheckable(True) - self.catchAllExceptionsBtn.setObjectName(_fromUtf8("catchAllExceptionsBtn")) - self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) - self.catchNextExceptionBtn = QtGui.QPushButton(self.exceptionGroup) - self.catchNextExceptionBtn.setCheckable(True) - self.catchNextExceptionBtn.setObjectName(_fromUtf8("catchNextExceptionBtn")) - self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) - self.onlyUncaughtCheck = QtGui.QCheckBox(self.exceptionGroup) - self.onlyUncaughtCheck.setChecked(True) - self.onlyUncaughtCheck.setObjectName(_fromUtf8("onlyUncaughtCheck")) - self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 2, 1, 1) - self.exceptionStackList = QtGui.QListWidget(self.exceptionGroup) - self.exceptionStackList.setAlternatingRowColors(True) - self.exceptionStackList.setObjectName(_fromUtf8("exceptionStackList")) - self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 5) - self.runSelectedFrameCheck = QtGui.QCheckBox(self.exceptionGroup) - self.runSelectedFrameCheck.setChecked(True) - self.runSelectedFrameCheck.setObjectName(_fromUtf8("runSelectedFrameCheck")) - self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 5) - self.exceptionInfoLabel = QtGui.QLabel(self.exceptionGroup) - self.exceptionInfoLabel.setObjectName(_fromUtf8("exceptionInfoLabel")) - self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 5) - self.clearExceptionBtn = QtGui.QPushButton(self.exceptionGroup) - self.clearExceptionBtn.setEnabled(False) - self.clearExceptionBtn.setObjectName(_fromUtf8("clearExceptionBtn")) - self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 4, 1, 1) - spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_2.addItem(spacerItem, 0, 3, 1, 1) - self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Console", None, QtGui.QApplication.UnicodeUTF8)) - self.historyBtn.setText(QtGui.QApplication.translate("Form", "History..", None, QtGui.QApplication.UnicodeUTF8)) - self.exceptionBtn.setText(QtGui.QApplication.translate("Form", "Exceptions..", None, QtGui.QApplication.UnicodeUTF8)) - self.exceptionGroup.setTitle(QtGui.QApplication.translate("Form", "Exception Handling", None, QtGui.QApplication.UnicodeUTF8)) - self.catchAllExceptionsBtn.setText(QtGui.QApplication.translate("Form", "Show All Exceptions", None, QtGui.QApplication.UnicodeUTF8)) - self.catchNextExceptionBtn.setText(QtGui.QApplication.translate("Form", "Show Next Exception", None, QtGui.QApplication.UnicodeUTF8)) - self.onlyUncaughtCheck.setText(QtGui.QApplication.translate("Form", "Only Uncaught Exceptions", None, QtGui.QApplication.UnicodeUTF8)) - self.runSelectedFrameCheck.setText(QtGui.QApplication.translate("Form", "Run commands in selected stack frame", None, QtGui.QApplication.UnicodeUTF8)) - self.exceptionInfoLabel.setText(QtGui.QApplication.translate("Form", "Exception Info", None, QtGui.QApplication.UnicodeUTF8)) - self.clearExceptionBtn.setText(QtGui.QApplication.translate("Form", "Clear Exception", None, QtGui.QApplication.UnicodeUTF8)) - -from .CmdInput import CmdInput diff --git a/pyqtgraph/console/template_pyside.py b/pyqtgraph/console/template_pyside.py deleted file mode 100644 index 0493a0fe..00000000 --- a/pyqtgraph/console/template_pyside.py +++ /dev/null @@ -1,106 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './console/template.ui' -# -# Created: Sun Sep 9 14:41:30 2012 -# by: pyside-uic 0.2.13 running on PySide 1.1.0 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(710, 497) - self.gridLayout = QtGui.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.splitter = QtGui.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Vertical) - self.splitter.setObjectName("splitter") - self.layoutWidget = QtGui.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.verticalLayout = QtGui.QVBoxLayout(self.layoutWidget) - self.verticalLayout.setContentsMargins(0, 0, 0, 0) - self.verticalLayout.setObjectName("verticalLayout") - self.output = QtGui.QPlainTextEdit(self.layoutWidget) - font = QtGui.QFont() - font.setFamily("Monospace") - self.output.setFont(font) - self.output.setReadOnly(True) - self.output.setObjectName("output") - self.verticalLayout.addWidget(self.output) - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.input = CmdInput(self.layoutWidget) - self.input.setObjectName("input") - self.horizontalLayout.addWidget(self.input) - self.historyBtn = QtGui.QPushButton(self.layoutWidget) - self.historyBtn.setCheckable(True) - self.historyBtn.setObjectName("historyBtn") - self.horizontalLayout.addWidget(self.historyBtn) - self.exceptionBtn = QtGui.QPushButton(self.layoutWidget) - self.exceptionBtn.setCheckable(True) - self.exceptionBtn.setObjectName("exceptionBtn") - self.horizontalLayout.addWidget(self.exceptionBtn) - self.verticalLayout.addLayout(self.horizontalLayout) - self.historyList = QtGui.QListWidget(self.splitter) - font = QtGui.QFont() - font.setFamily("Monospace") - self.historyList.setFont(font) - self.historyList.setObjectName("historyList") - self.exceptionGroup = QtGui.QGroupBox(self.splitter) - self.exceptionGroup.setObjectName("exceptionGroup") - self.gridLayout_2 = QtGui.QGridLayout(self.exceptionGroup) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.catchAllExceptionsBtn = QtGui.QPushButton(self.exceptionGroup) - self.catchAllExceptionsBtn.setCheckable(True) - self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn") - self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) - self.catchNextExceptionBtn = QtGui.QPushButton(self.exceptionGroup) - self.catchNextExceptionBtn.setCheckable(True) - self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn") - self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) - self.onlyUncaughtCheck = QtGui.QCheckBox(self.exceptionGroup) - self.onlyUncaughtCheck.setChecked(True) - self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck") - self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 2, 1, 1) - self.exceptionStackList = QtGui.QListWidget(self.exceptionGroup) - self.exceptionStackList.setAlternatingRowColors(True) - self.exceptionStackList.setObjectName("exceptionStackList") - self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 5) - self.runSelectedFrameCheck = QtGui.QCheckBox(self.exceptionGroup) - self.runSelectedFrameCheck.setChecked(True) - self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck") - self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 5) - self.exceptionInfoLabel = QtGui.QLabel(self.exceptionGroup) - self.exceptionInfoLabel.setObjectName("exceptionInfoLabel") - self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 5) - self.clearExceptionBtn = QtGui.QPushButton(self.exceptionGroup) - self.clearExceptionBtn.setEnabled(False) - self.clearExceptionBtn.setObjectName("clearExceptionBtn") - self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 4, 1, 1) - spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) - self.gridLayout_2.addItem(spacerItem, 0, 3, 1, 1) - self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Console", None, QtGui.QApplication.UnicodeUTF8)) - self.historyBtn.setText(QtGui.QApplication.translate("Form", "History..", None, QtGui.QApplication.UnicodeUTF8)) - self.exceptionBtn.setText(QtGui.QApplication.translate("Form", "Exceptions..", None, QtGui.QApplication.UnicodeUTF8)) - self.exceptionGroup.setTitle(QtGui.QApplication.translate("Form", "Exception Handling", None, QtGui.QApplication.UnicodeUTF8)) - self.catchAllExceptionsBtn.setText(QtGui.QApplication.translate("Form", "Show All Exceptions", None, QtGui.QApplication.UnicodeUTF8)) - self.catchNextExceptionBtn.setText(QtGui.QApplication.translate("Form", "Show Next Exception", None, QtGui.QApplication.UnicodeUTF8)) - self.onlyUncaughtCheck.setText(QtGui.QApplication.translate("Form", "Only Uncaught Exceptions", None, QtGui.QApplication.UnicodeUTF8)) - self.runSelectedFrameCheck.setText(QtGui.QApplication.translate("Form", "Run commands in selected stack frame", None, QtGui.QApplication.UnicodeUTF8)) - self.exceptionInfoLabel.setText(QtGui.QApplication.translate("Form", "Exception Info", None, QtGui.QApplication.UnicodeUTF8)) - self.clearExceptionBtn.setText(QtGui.QApplication.translate("Form", "Clear Exception", None, QtGui.QApplication.UnicodeUTF8)) - -from .CmdInput import CmdInput diff --git a/pyqtgraph/debug.py b/pyqtgraph/debug.py deleted file mode 100644 index a175be9c..00000000 --- a/pyqtgraph/debug.py +++ /dev/null @@ -1,946 +0,0 @@ -# -*- coding: utf-8 -*- -""" -debug.py - Functions to aid in debugging -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - -import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile -from . import ptime -from numpy import ndarray -from .Qt import QtCore, QtGui - -__ftraceDepth = 0 -def ftrace(func): - """Decorator used for marking the beginning and end of function calls. - Automatically indents nested calls. - """ - def w(*args, **kargs): - global __ftraceDepth - pfx = " " * __ftraceDepth - print(pfx + func.__name__ + " start") - __ftraceDepth += 1 - try: - rv = func(*args, **kargs) - finally: - __ftraceDepth -= 1 - print(pfx + func.__name__ + " done") - return rv - return w - -def warnOnException(func): - """Decorator which catches/ignores exceptions and prints a stack trace.""" - def w(*args, **kwds): - try: - func(*args, **kwds) - except: - printExc('Ignored exception:') - return w - -def getExc(indent=4, prefix='| '): - tb = traceback.format_exc() - lines = [] - for l in tb.split('\n'): - lines.append(" "*indent + prefix + l) - return '\n'.join(lines) - -def printExc(msg='', indent=4, prefix='|'): - """Print an error message followed by an indented exception backtrace - (This function is intended to be called within except: blocks)""" - exc = getExc(indent, prefix + ' ') - print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) - print(" "*indent + prefix + '='*30 + '>>') - print(exc) - print(" "*indent + prefix + '='*30 + '<<') - -def printTrace(msg='', indent=4, prefix='|'): - """Print an error message followed by an indented stack trace""" - trace = backtrace(1) - #exc = getExc(indent, prefix + ' ') - print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) - print(" "*indent + prefix + '='*30 + '>>') - for line in trace.split('\n'): - print(" "*indent + prefix + " " + line) - print(" "*indent + prefix + '='*30 + '<<') - - -def backtrace(skip=0): - return ''.join(traceback.format_stack()[:-(skip+1)]) - - -def listObjs(regex='Q', typ=None): - """List all objects managed by python gc with class name matching regex. - Finds 'Q...' classes by default.""" - if typ is not None: - return [x for x in gc.get_objects() if isinstance(x, typ)] - else: - return [x for x in gc.get_objects() if re.match(regex, type(x).__name__)] - - - -def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ignore=None): - """Determine all paths of object references from startObj to endObj""" - refs = [] - if path is None: - path = [endObj] - if ignore is None: - ignore = {} - ignore[id(sys._getframe())] = None - ignore[id(path)] = None - ignore[id(seen)] = None - prefix = " "*(8-maxLen) - #print prefix + str(map(type, path)) - prefix += " " - if restart: - #gc.collect() - seen.clear() - gc.collect() - newRefs = [r for r in gc.get_referrers(endObj) if id(r) not in ignore] - ignore[id(newRefs)] = None - #fo = allFrameObjs() - #newRefs = [] - #for r in gc.get_referrers(endObj): - #try: - #if r not in fo: - #newRefs.append(r) - #except: - #newRefs.append(r) - - for r in newRefs: - #print prefix+"->"+str(type(r)) - if type(r).__name__ in ['frame', 'function', 'listiterator']: - #print prefix+" FRAME" - continue - try: - if any([r is x for x in path]): - #print prefix+" LOOP", objChainString([r]+path) - continue - except: - print(r) - print(path) - raise - if r is startObj: - refs.append([r]) - print(refPathString([startObj]+path)) - continue - if maxLen == 0: - #print prefix+" END:", objChainString([r]+path) - continue - ## See if we have already searched this node. - ## If not, recurse. - tree = None - try: - cache = seen[id(r)] - if cache[0] >= maxLen: - tree = cache[1] - for p in tree: - print(refPathString(p+path)) - except KeyError: - pass - - ignore[id(tree)] = None - if tree is None: - tree = findRefPath(startObj, r, maxLen-1, restart=False, path=[r]+path, ignore=ignore) - seen[id(r)] = [maxLen, tree] - ## integrate any returned results - if len(tree) == 0: - #print prefix+" EMPTY TREE" - continue - else: - for p in tree: - refs.append(p+[r]) - #seen[id(r)] = [maxLen, refs] - return refs - - -def objString(obj): - """Return a short but descriptive string for any object""" - try: - if type(obj) in [int, float]: - return str(obj) - elif isinstance(obj, dict): - if len(obj) > 5: - return "" % (",".join(list(obj.keys())[:5])) - else: - return "" % (",".join(list(obj.keys()))) - elif isinstance(obj, str): - if len(obj) > 50: - return '"%s..."' % obj[:50] - else: - return obj[:] - elif isinstance(obj, ndarray): - return "" % (str(obj.dtype), str(obj.shape)) - elif hasattr(obj, '__len__'): - if len(obj) > 5: - return "<%s [%s,...]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj[:5]])) - else: - return "<%s [%s]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj])) - else: - return "<%s %s>" % (type(obj).__name__, obj.__class__.__name__) - except: - return str(type(obj)) - -def refPathString(chain): - """Given a list of adjacent objects in a reference path, print the 'natural' path - names (ie, attribute names, keys, and indexes) that follow from one object to the next .""" - s = objString(chain[0]) - i = 0 - while i < len(chain)-1: - #print " -> ", i - i += 1 - o1 = chain[i-1] - o2 = chain[i] - cont = False - if isinstance(o1, list) or isinstance(o1, tuple): - if any([o2 is x for x in o1]): - s += "[%d]" % o1.index(o2) - continue - #print " not list" - if isinstance(o2, dict) and hasattr(o1, '__dict__') and o2 == o1.__dict__: - i += 1 - if i >= len(chain): - s += ".__dict__" - continue - o3 = chain[i] - for k in o2: - if o2[k] is o3: - s += '.%s' % k - cont = True - continue - #print " not __dict__" - if isinstance(o1, dict): - try: - if o2 in o1: - s += "[key:%s]" % objString(o2) - continue - except TypeError: - pass - for k in o1: - if o1[k] is o2: - s += "[%s]" % objString(k) - cont = True - continue - #print " not dict" - #for k in dir(o1): ## Not safe to request attributes like this. - #if getattr(o1, k) is o2: - #s += ".%s" % k - #cont = True - #continue - #print " not attr" - if cont: - continue - s += " ? " - sys.stdout.flush() - return s - - -def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False): - """Guess how much memory an object is using""" - ignoreTypes = [types.MethodType, types.UnboundMethodType, types.BuiltinMethodType, types.FunctionType, types.BuiltinFunctionType] - ignoreRegex = re.compile('(method-wrapper|Flag|ItemChange|Option|Mode)') - - - if ignore is None: - ignore = {} - - indent = ' '*depth - - try: - hash(obj) - hsh = obj - except: - hsh = "%s:%d" % (str(type(obj)), id(obj)) - - if hsh in ignore: - return 0 - ignore[hsh] = 1 - - try: - size = sys.getsizeof(obj) - except TypeError: - size = 0 - - if isinstance(obj, ndarray): - try: - size += len(obj.data) - except: - pass - - - if recursive: - if type(obj) in [list, tuple]: - if verbose: - print(indent+"list:") - for o in obj: - s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) - if verbose: - print(indent+' +', s) - size += s - elif isinstance(obj, dict): - if verbose: - print(indent+"list:") - for k in obj: - s = objectSize(obj[k], ignore=ignore, verbose=verbose, depth=depth+1) - if verbose: - print(indent+' +', k, s) - size += s - #elif isinstance(obj, QtCore.QObject): - #try: - #childs = obj.children() - #if verbose: - #print indent+"Qt children:" - #for ch in childs: - #s = objectSize(obj, ignore=ignore, verbose=verbose, depth=depth+1) - #size += s - #if verbose: - #print indent + ' +', ch.objectName(), s - - #except: - #pass - #if isinstance(obj, types.InstanceType): - gc.collect() - if verbose: - print(indent+'attrs:') - for k in dir(obj): - if k in ['__dict__']: - continue - o = getattr(obj, k) - if type(o) in ignoreTypes: - continue - strtyp = str(type(o)) - if ignoreRegex.search(strtyp): - continue - #if isinstance(o, types.ObjectType) and strtyp == "": - #continue - - #if verbose: - #print indent, k, '?' - refs = [r for r in gc.get_referrers(o) if type(r) != types.FrameType] - if len(refs) == 1: - s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) - size += s - if verbose: - print(indent + " +", k, s) - #else: - #if verbose: - #print indent + ' -', k, len(refs) - return size - -class GarbageWatcher(object): - """ - Convenient dictionary for holding weak references to objects. - Mainly used to check whether the objects have been collect yet or not. - - Example: - gw = GarbageWatcher() - gw['objName'] = obj - gw['objName2'] = obj2 - gw.check() - - - """ - def __init__(self): - self.objs = weakref.WeakValueDictionary() - self.allNames = [] - - def add(self, obj, name): - self.objs[name] = obj - self.allNames.append(name) - - def __setitem__(self, name, obj): - self.add(obj, name) - - def check(self): - """Print a list of all watched objects and whether they have been collected.""" - gc.collect() - dead = self.allNames[:] - alive = [] - for k in self.objs: - dead.remove(k) - alive.append(k) - print("Deleted objects:", dead) - print("Live objects:", alive) - - def __getitem__(self, item): - return self.objs[item] - - -class Profiler: - """Simple profiler allowing measurement of multiple time intervals. - Arguments: - msg: message to print at start and finish of profiling - disabled: If true, profiler does nothing (so you can leave it in place) - delayed: If true, all messages are printed after call to finish() - (this can result in more accurate time step measurements) - globalDelay: if True, all nested profilers delay printing until the top level finishes - - Example: - prof = Profiler('Function') - ... do stuff ... - prof.mark('did stuff') - ... do other stuff ... - prof.mark('did other stuff') - prof.finish() - """ - depth = 0 - msgs = [] - - def __init__(self, msg="Profiler", disabled=False, delayed=True, globalDelay=True): - self.disabled = disabled - if disabled: - return - - self.markCount = 0 - self.finished = False - self.depth = Profiler.depth - Profiler.depth += 1 - if not globalDelay: - self.msgs = [] - self.delayed = delayed - self.msg = " "*self.depth + msg - msg2 = self.msg + " >>> Started" - if self.delayed: - self.msgs.append(msg2) - else: - print(msg2) - self.t0 = ptime.time() - self.t1 = self.t0 - - def mark(self, msg=None): - if self.disabled: - return - - if msg is None: - msg = str(self.markCount) - self.markCount += 1 - - t1 = ptime.time() - msg2 = " "+self.msg+" "+msg+" "+"%gms" % ((t1-self.t1)*1000) - if self.delayed: - self.msgs.append(msg2) - else: - print(msg2) - self.t1 = ptime.time() ## don't measure time it took to print - - def finish(self, msg=None): - if self.disabled or self.finished: - return - - if msg is not None: - self.mark(msg) - t1 = ptime.time() - msg = self.msg + ' <<< Finished, total time: %gms' % ((t1-self.t0)*1000) - if self.delayed: - self.msgs.append(msg) - if self.depth == 0: - for line in self.msgs: - print(line) - Profiler.msgs = [] - else: - print(msg) - Profiler.depth = self.depth - self.finished = True - - - - -def profile(code, name='profile_run', sort='cumulative', num=30): - """Common-use for cProfile""" - cProfile.run(code, name) - stats = pstats.Stats(name) - stats.sort_stats(sort) - stats.print_stats(num) - return stats - - - -#### Code for listing (nearly) all objects in the known universe -#### http://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects -# Recursively expand slist's objects -# into olist, using seen to track -# already processed objects. -def _getr(slist, olist, first=True): - i = 0 - for e in slist: - - oid = id(e) - typ = type(e) - if oid in olist or typ is int: ## or e in olist: ## since we're excluding all ints, there is no longer a need to check for olist keys - continue - olist[oid] = e - if first and (i%1000) == 0: - gc.collect() - tl = gc.get_referents(e) - if tl: - _getr(tl, olist, first=False) - i += 1 -# The public function. -def get_all_objects(): - """Return a list of all live Python objects (excluding int and long), not including the list itself.""" - gc.collect() - gcl = gc.get_objects() - olist = {} - _getr(gcl, olist) - - del olist[id(olist)] - del olist[id(gcl)] - del olist[id(sys._getframe())] - return olist - - -def lookup(oid, objects=None): - """Return an object given its ID, if it exists.""" - if objects is None: - objects = get_all_objects() - return objects[oid] - - - - -class ObjTracker(object): - """ - Tracks all objects under the sun, reporting the changes between snapshots: what objects are created, deleted, and persistent. - This class is very useful for tracking memory leaks. The class goes to great (but not heroic) lengths to avoid tracking - its own internal objects. - - Example: - ot = ObjTracker() # takes snapshot of currently existing objects - ... do stuff ... - ot.diff() # prints lists of objects created and deleted since ot was initialized - ... do stuff ... - ot.diff() # prints lists of objects created and deleted since last call to ot.diff() - # also prints list of items that were created since initialization AND have not been deleted yet - # (if done correctly, this list can tell you about objects that were leaked) - - arrays = ot.findPersistent('ndarray') ## returns all objects matching 'ndarray' (string match, not instance checking) - ## that were considered persistent when the last diff() was run - - describeObj(arrays[0]) ## See if we can determine who has references to this array - """ - - - allObjs = {} ## keep track of all objects created and stored within class instances - allObjs[id(allObjs)] = None - - def __init__(self): - self.startRefs = {} ## list of objects that exist when the tracker is initialized {oid: weakref} - ## (If it is not possible to weakref the object, then the value is None) - self.startCount = {} - self.newRefs = {} ## list of objects that have been created since initialization - self.persistentRefs = {} ## list of objects considered 'persistent' when the last diff() was called - self.objTypes = {} - - ObjTracker.allObjs[id(self)] = None - self.objs = [self.__dict__, self.startRefs, self.startCount, self.newRefs, self.persistentRefs, self.objTypes] - self.objs.append(self.objs) - for v in self.objs: - ObjTracker.allObjs[id(v)] = None - - self.start() - - def findNew(self, regex): - """Return all objects matching regex that were considered 'new' when the last diff() was run.""" - return self.findTypes(self.newRefs, regex) - - def findPersistent(self, regex): - """Return all objects matching regex that were considered 'persistent' when the last diff() was run.""" - return self.findTypes(self.persistentRefs, regex) - - - def start(self): - """ - Remember the current set of objects as the comparison for all future calls to diff() - Called automatically on init, but can be called manually as well. - """ - refs, count, objs = self.collect() - for r in self.startRefs: - self.forgetRef(self.startRefs[r]) - self.startRefs.clear() - self.startRefs.update(refs) - for r in refs: - self.rememberRef(r) - self.startCount.clear() - self.startCount.update(count) - #self.newRefs.clear() - #self.newRefs.update(refs) - - def diff(self, **kargs): - """ - Compute all differences between the current object set and the reference set. - Print a set of reports for created, deleted, and persistent objects - """ - refs, count, objs = self.collect() ## refs contains the list of ALL objects - - ## Which refs have disappeared since call to start() (these are only displayed once, then forgotten.) - delRefs = {} - for i in self.startRefs.keys(): - if i not in refs: - delRefs[i] = self.startRefs[i] - del self.startRefs[i] - self.forgetRef(delRefs[i]) - for i in self.newRefs.keys(): - if i not in refs: - delRefs[i] = self.newRefs[i] - del self.newRefs[i] - self.forgetRef(delRefs[i]) - #print "deleted:", len(delRefs) - - ## Which refs have appeared since call to start() or diff() - persistentRefs = {} ## created since start(), but before last diff() - createRefs = {} ## created since last diff() - for o in refs: - if o not in self.startRefs: - if o not in self.newRefs: - createRefs[o] = refs[o] ## object has been created since last diff() - else: - persistentRefs[o] = refs[o] ## object has been created since start(), but before last diff() (persistent) - #print "new:", len(newRefs) - - ## self.newRefs holds the entire set of objects created since start() - for r in self.newRefs: - self.forgetRef(self.newRefs[r]) - self.newRefs.clear() - self.newRefs.update(persistentRefs) - self.newRefs.update(createRefs) - for r in self.newRefs: - self.rememberRef(self.newRefs[r]) - #print "created:", len(createRefs) - - ## self.persistentRefs holds all objects considered persistent. - self.persistentRefs.clear() - self.persistentRefs.update(persistentRefs) - - - print("----------- Count changes since start: ----------") - c1 = count.copy() - for k in self.startCount: - c1[k] = c1.get(k, 0) - self.startCount[k] - typs = list(c1.keys()) - typs.sort(lambda a,b: cmp(c1[a], c1[b])) - for t in typs: - if c1[t] == 0: - continue - num = "%d" % c1[t] - print(" " + num + " "*(10-len(num)) + str(t)) - - print("----------- %d Deleted since last diff: ------------" % len(delRefs)) - self.report(delRefs, objs, **kargs) - print("----------- %d Created since last diff: ------------" % len(createRefs)) - self.report(createRefs, objs, **kargs) - print("----------- %d Created since start (persistent): ------------" % len(persistentRefs)) - self.report(persistentRefs, objs, **kargs) - - - def __del__(self): - self.startRefs.clear() - self.startCount.clear() - self.newRefs.clear() - self.persistentRefs.clear() - - del ObjTracker.allObjs[id(self)] - for v in self.objs: - del ObjTracker.allObjs[id(v)] - - @classmethod - def isObjVar(cls, o): - return type(o) is cls or id(o) in cls.allObjs - - def collect(self): - print("Collecting list of all objects...") - gc.collect() - objs = get_all_objects() - frame = sys._getframe() - del objs[id(frame)] ## ignore the current frame - del objs[id(frame.f_code)] - - ignoreTypes = [int] - refs = {} - count = {} - for k in objs: - o = objs[k] - typ = type(o) - oid = id(o) - if ObjTracker.isObjVar(o) or typ in ignoreTypes: - continue - - try: - ref = weakref.ref(obj) - except: - ref = None - refs[oid] = ref - typ = type(o) - typStr = typeStr(o) - self.objTypes[oid] = typStr - ObjTracker.allObjs[id(typStr)] = None - count[typ] = count.get(typ, 0) + 1 - - print("All objects: %d Tracked objects: %d" % (len(objs), len(refs))) - return refs, count, objs - - def forgetRef(self, ref): - if ref is not None: - del ObjTracker.allObjs[id(ref)] - - def rememberRef(self, ref): - ## Record the address of the weakref object so it is not included in future object counts. - if ref is not None: - ObjTracker.allObjs[id(ref)] = None - - - def lookup(self, oid, ref, objs=None): - if ref is None or ref() is None: - try: - obj = lookup(oid, objects=objs) - except: - obj = None - else: - obj = ref() - return obj - - - def report(self, refs, allobjs=None, showIDs=False): - if allobjs is None: - allobjs = get_all_objects() - - count = {} - rev = {} - for oid in refs: - obj = self.lookup(oid, refs[oid], allobjs) - if obj is None: - typ = "[del] " + self.objTypes[oid] - else: - typ = typeStr(obj) - if typ not in rev: - rev[typ] = [] - rev[typ].append(oid) - c = count.get(typ, [0,0]) - count[typ] = [c[0]+1, c[1]+objectSize(obj)] - typs = list(count.keys()) - typs.sort(lambda a,b: cmp(count[a][1], count[b][1])) - - for t in typs: - line = " %d\t%d\t%s" % (count[t][0], count[t][1], t) - if showIDs: - line += "\t"+",".join(map(str,rev[t])) - print(line) - - def findTypes(self, refs, regex): - allObjs = get_all_objects() - ids = {} - objs = [] - r = re.compile(regex) - for k in refs: - if r.search(self.objTypes[k]): - objs.append(self.lookup(k, refs[k], allObjs)) - return objs - - - - -def describeObj(obj, depth=4, path=None, ignore=None): - """ - Trace all reference paths backward, printing a list of different ways this object can be accessed. - Attempts to answer the question "who has a reference to this object" - """ - if path is None: - path = [obj] - if ignore is None: - ignore = {} ## holds IDs of objects used within the function. - ignore[id(sys._getframe())] = None - ignore[id(path)] = None - gc.collect() - refs = gc.get_referrers(obj) - ignore[id(refs)] = None - printed=False - for ref in refs: - if id(ref) in ignore: - continue - if id(ref) in list(map(id, path)): - print("Cyclic reference: " + refPathString([ref]+path)) - printed = True - continue - newPath = [ref]+path - if len(newPath) >= depth: - refStr = refPathString(newPath) - if '[_]' not in refStr: ## ignore '_' references generated by the interactive shell - print(refStr) - printed = True - else: - describeObj(ref, depth, newPath, ignore) - printed = True - if not printed: - print("Dead end: " + refPathString(path)) - - - -def typeStr(obj): - """Create a more useful type string by making types report their class.""" - typ = type(obj) - if typ == types.InstanceType: - return "" % obj.__class__.__name__ - else: - return str(typ) - -def searchRefs(obj, *args): - """Pseudo-interactive function for tracing references backward. - Arguments: - obj: The initial object from which to start searching - args: A set of string or int arguments. - each integer selects one of obj's referrers to be the new 'obj' - each string indicates an action to take on the current 'obj': - t: print the types of obj's referrers - l: print the lengths of obj's referrers (if they have __len__) - i: print the IDs of obj's referrers - o: print obj - ro: return obj - rr: return list of obj's referrers - - Examples: - searchRefs(obj, 't') ## Print types of all objects referring to obj - searchRefs(obj, 't', 0, 't') ## ..then select the first referrer and print the types of its referrers - searchRefs(obj, 't', 0, 't', 'l') ## ..also print lengths of the last set of referrers - searchRefs(obj, 0, 1, 'ro') ## Select index 0 from obj's referrer, then select index 1 from the next set of referrers, then return that object - - """ - ignore = {id(sys._getframe()): None} - gc.collect() - refs = gc.get_referrers(obj) - ignore[id(refs)] = None - refs = [r for r in refs if id(r) not in ignore] - for a in args: - - #fo = allFrameObjs() - #refs = [r for r in refs if r not in fo] - - if type(a) is int: - obj = refs[a] - gc.collect() - refs = gc.get_referrers(obj) - ignore[id(refs)] = None - refs = [r for r in refs if id(r) not in ignore] - elif a == 't': - print(list(map(typeStr, refs))) - elif a == 'i': - print(list(map(id, refs))) - elif a == 'l': - def slen(o): - if hasattr(o, '__len__'): - return len(o) - else: - return None - print(list(map(slen, refs))) - elif a == 'o': - print(obj) - elif a == 'ro': - return obj - elif a == 'rr': - return refs - -def allFrameObjs(): - """Return list of frame objects in current stack. Useful if you want to ignore these objects in refernece searches""" - f = sys._getframe() - objs = [] - while f is not None: - objs.append(f) - objs.append(f.f_code) - #objs.append(f.f_locals) - #objs.append(f.f_globals) - #objs.append(f.f_builtins) - f = f.f_back - return objs - - -def findObj(regex): - """Return a list of objects whose typeStr matches regex""" - allObjs = get_all_objects() - objs = [] - r = re.compile(regex) - for i in allObjs: - obj = allObjs[i] - if r.search(typeStr(obj)): - objs.append(obj) - return objs - - - -def listRedundantModules(): - """List modules that have been imported more than once via different paths.""" - mods = {} - for name, mod in sys.modules.items(): - if not hasattr(mod, '__file__'): - continue - mfile = os.path.abspath(mod.__file__) - if mfile[-1] == 'c': - mfile = mfile[:-1] - if mfile in mods: - print("module at %s has 2 names: %s, %s" % (mfile, name, mods[mfile])) - else: - mods[mfile] = name - - -def walkQObjectTree(obj, counts=None, verbose=False, depth=0): - """ - Walk through a tree of QObjects, doing nothing to them. - The purpose of this function is to find dead objects and generate a crash - immediately rather than stumbling upon them later. - Prints a count of the objects encountered, for fun. (or is it?) - """ - - if verbose: - print(" "*depth + typeStr(obj)) - report = False - if counts is None: - counts = {} - report = True - typ = str(type(obj)) - try: - counts[typ] += 1 - except KeyError: - counts[typ] = 1 - for child in obj.children(): - walkQObjectTree(child, counts, verbose, depth+1) - - return counts - -QObjCache = {} -def qObjectReport(verbose=False): - """Generate a report counting all QObjects and their types""" - global qObjCache - count = {} - for obj in findObj('PyQt'): - if isinstance(obj, QtCore.QObject): - oid = id(obj) - if oid not in QObjCache: - QObjCache[oid] = typeStr(obj) + " " + obj.objectName() - try: - QObjCache[oid] += " " + obj.parent().objectName() - QObjCache[oid] += " " + obj.text() - except: - pass - print("check obj", oid, str(QObjCache[oid])) - if obj.parent() is None: - walkQObjectTree(obj, count, verbose) - - typs = list(count.keys()) - typs.sort() - for t in typs: - print(count[t], "\t", t) - - -class PrintDetector(object): - def __init__(self): - self.stdout = sys.stdout - sys.stdout = self - - def remove(self): - sys.stdout = self.stdout - - def __del__(self): - self.remove() - - def write(self, x): - self.stdout.write(x) - traceback.print_stack() - - def flush(self): - self.stdout.flush() \ No newline at end of file diff --git a/pyqtgraph/dockarea/Container.py b/pyqtgraph/dockarea/Container.py deleted file mode 100644 index 83610937..00000000 --- a/pyqtgraph/dockarea/Container.py +++ /dev/null @@ -1,267 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtCore, QtGui -import weakref - -class Container(object): - #sigStretchChanged = QtCore.Signal() ## can't do this here; not a QObject. - - def __init__(self, area): - object.__init__(self) - self.area = area - self._container = None - self._stretch = (10, 10) - self.stretches = weakref.WeakKeyDictionary() - - def container(self): - return self._container - - def containerChanged(self, c): - self._container = c - - def type(self): - return None - - def insert(self, new, pos=None, neighbor=None): - if not isinstance(new, list): - new = [new] - if neighbor is None: - if pos == 'before': - index = 0 - else: - index = self.count() - else: - index = self.indexOf(neighbor) - if index == -1: - index = 0 - if pos == 'after': - index += 1 - - for n in new: - #print "change container", n, " -> ", self - n.containerChanged(self) - #print "insert", n, " -> ", self, index - self._insertItem(n, index) - index += 1 - n.sigStretchChanged.connect(self.childStretchChanged) - #print "child added", self - self.updateStretch() - - def apoptose(self, propagate=True): - ##if there is only one (or zero) item in this container, disappear. - cont = self._container - c = self.count() - if c > 1: - return - if self.count() == 1: ## if there is one item, give it to the parent container (unless this is the top) - if self is self.area.topContainer: - return - self.container().insert(self.widget(0), 'before', self) - #print "apoptose:", self - self.close() - if propagate and cont is not None: - cont.apoptose() - - def close(self): - self.area = None - self._container = None - self.setParent(None) - - def childEvent(self, ev): - ch = ev.child() - if ev.removed() and hasattr(ch, 'sigStretchChanged'): - #print "Child", ev.child(), "removed, updating", self - try: - ch.sigStretchChanged.disconnect(self.childStretchChanged) - except: - pass - self.updateStretch() - - def childStretchChanged(self): - #print "child", QtCore.QObject.sender(self), "changed shape, updating", self - self.updateStretch() - - def setStretch(self, x=None, y=None): - #print "setStretch", self, x, y - self._stretch = (x, y) - self.sigStretchChanged.emit() - - def updateStretch(self): - ###Set the stretch values for this container to reflect its contents - pass - - - def stretch(self): - """Return the stretch factors for this container""" - return self._stretch - - -class SplitContainer(Container, QtGui.QSplitter): - """Horizontal or vertical splitter with some changes: - - save/restore works correctly - """ - sigStretchChanged = QtCore.Signal() - - def __init__(self, area, orientation): - QtGui.QSplitter.__init__(self) - self.setOrientation(orientation) - Container.__init__(self, area) - #self.splitterMoved.connect(self.restretchChildren) - - def _insertItem(self, item, index): - self.insertWidget(index, item) - item.show() ## need to show since it may have been previously hidden by tab - - def saveState(self): - sizes = self.sizes() - if all([x == 0 for x in sizes]): - sizes = [10] * len(sizes) - return {'sizes': sizes} - - def restoreState(self, state): - sizes = state['sizes'] - self.setSizes(sizes) - for i in range(len(sizes)): - self.setStretchFactor(i, sizes[i]) - - def childEvent(self, ev): - QtGui.QSplitter.childEvent(self, ev) - Container.childEvent(self, ev) - - #def restretchChildren(self): - #sizes = self.sizes() - #tot = sum(sizes) - - - - -class HContainer(SplitContainer): - def __init__(self, area): - SplitContainer.__init__(self, area, QtCore.Qt.Horizontal) - - def type(self): - return 'horizontal' - - def updateStretch(self): - ##Set the stretch values for this container to reflect its contents - #print "updateStretch", self - x = 0 - y = 0 - sizes = [] - for i in range(self.count()): - wx, wy = self.widget(i).stretch() - x += wx - y = max(y, wy) - sizes.append(wx) - #print " child", self.widget(i), wx, wy - self.setStretch(x, y) - #print sizes - - tot = float(sum(sizes)) - if tot == 0: - scale = 1.0 - else: - scale = self.width() / tot - self.setSizes([int(s*scale) for s in sizes]) - - - -class VContainer(SplitContainer): - def __init__(self, area): - SplitContainer.__init__(self, area, QtCore.Qt.Vertical) - - def type(self): - return 'vertical' - - def updateStretch(self): - ##Set the stretch values for this container to reflect its contents - #print "updateStretch", self - x = 0 - y = 0 - sizes = [] - for i in range(self.count()): - wx, wy = self.widget(i).stretch() - y += wy - x = max(x, wx) - sizes.append(wy) - #print " child", self.widget(i), wx, wy - self.setStretch(x, y) - - #print sizes - tot = float(sum(sizes)) - if tot == 0: - scale = 1.0 - else: - scale = self.height() / tot - self.setSizes([int(s*scale) for s in sizes]) - - -class TContainer(Container, QtGui.QWidget): - sigStretchChanged = QtCore.Signal() - def __init__(self, area): - QtGui.QWidget.__init__(self) - Container.__init__(self, area) - self.layout = QtGui.QGridLayout() - self.layout.setSpacing(0) - self.layout.setContentsMargins(0,0,0,0) - self.setLayout(self.layout) - - self.hTabLayout = QtGui.QHBoxLayout() - self.hTabBox = QtGui.QWidget() - self.hTabBox.setLayout(self.hTabLayout) - self.hTabLayout.setSpacing(2) - self.hTabLayout.setContentsMargins(0,0,0,0) - self.layout.addWidget(self.hTabBox, 0, 1) - - self.stack = QtGui.QStackedWidget() - self.layout.addWidget(self.stack, 1, 1) - self.stack.childEvent = self.stackChildEvent - - - self.setLayout(self.layout) - for n in ['count', 'widget', 'indexOf']: - setattr(self, n, getattr(self.stack, n)) - - - def _insertItem(self, item, index): - if not isinstance(item, Dock.Dock): - raise Exception("Tab containers may hold only docks, not other containers.") - self.stack.insertWidget(index, item) - self.hTabLayout.insertWidget(index, item.label) - #QtCore.QObject.connect(item.label, QtCore.SIGNAL('clicked'), self.tabClicked) - item.label.sigClicked.connect(self.tabClicked) - self.tabClicked(item.label) - - def tabClicked(self, tab, ev=None): - if ev is None or ev.button() == QtCore.Qt.LeftButton: - for i in range(self.count()): - w = self.widget(i) - if w is tab.dock: - w.label.setDim(False) - self.stack.setCurrentIndex(i) - else: - w.label.setDim(True) - - def type(self): - return 'tab' - - def saveState(self): - return {'index': self.stack.currentIndex()} - - def restoreState(self, state): - self.stack.setCurrentIndex(state['index']) - - def updateStretch(self): - ##Set the stretch values for this container to reflect its contents - x = 0 - y = 0 - for i in range(self.count()): - wx, wy = self.widget(i).stretch() - x = max(x, wx) - y = max(y, wy) - self.setStretch(x, y) - - def stackChildEvent(self, ev): - QtGui.QStackedWidget.childEvent(self.stack, ev) - Container.childEvent(self, ev) - -from . import Dock diff --git a/pyqtgraph/dockarea/Dock.py b/pyqtgraph/dockarea/Dock.py deleted file mode 100644 index 414980ac..00000000 --- a/pyqtgraph/dockarea/Dock.py +++ /dev/null @@ -1,333 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui - -from .DockDrop import * -from pyqtgraph.widgets.VerticalLabel import VerticalLabel - -class Dock(QtGui.QWidget, DockDrop): - - sigStretchChanged = QtCore.Signal() - - def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True): - QtGui.QWidget.__init__(self) - DockDrop.__init__(self) - self.area = area - self.label = DockLabel(name, self) - self.labelHidden = False - self.moveLabel = True ## If false, the dock is no longer allowed to move the label. - self.autoOrient = autoOrientation - self.orientation = 'horizontal' - #self.label.setAlignment(QtCore.Qt.AlignHCenter) - self.topLayout = QtGui.QGridLayout() - self.topLayout.setContentsMargins(0, 0, 0, 0) - self.topLayout.setSpacing(0) - self.setLayout(self.topLayout) - self.topLayout.addWidget(self.label, 0, 1) - self.widgetArea = QtGui.QWidget() - self.topLayout.addWidget(self.widgetArea, 1, 1) - self.layout = QtGui.QGridLayout() - self.layout.setContentsMargins(0, 0, 0, 0) - self.layout.setSpacing(0) - self.widgetArea.setLayout(self.layout) - self.widgetArea.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - self.widgets = [] - self.currentRow = 0 - #self.titlePos = 'top' - self.raiseOverlay() - self.hStyle = """ - Dock > QWidget { - border: 1px solid #000; - border-radius: 5px; - border-top-left-radius: 0px; - border-top-right-radius: 0px; - border-top-width: 0px; - }""" - self.vStyle = """ - Dock > QWidget { - border: 1px solid #000; - border-radius: 5px; - border-top-left-radius: 0px; - border-bottom-left-radius: 0px; - border-left-width: 0px; - }""" - self.nStyle = """ - Dock > QWidget { - border: 1px solid #000; - border-radius: 5px; - }""" - self.dragStyle = """ - Dock > QWidget { - border: 4px solid #00F; - border-radius: 5px; - }""" - self.setAutoFillBackground(False) - self.widgetArea.setStyleSheet(self.hStyle) - - self.setStretch(*size) - - if widget is not None: - self.addWidget(widget) - - if hideTitle: - self.hideTitleBar() - - def implements(self, name=None): - if name is None: - return ['dock'] - else: - return name == 'dock' - - def setStretch(self, x=None, y=None): - """ - Set the 'target' size for this Dock. - The actual size will be determined by comparing this Dock's - stretch value to the rest of the docks it shares space with. - """ - #print "setStretch", self, x, y - #self._stretch = (x, y) - if x is None: - x = 0 - if y is None: - y = 0 - #policy = self.sizePolicy() - #policy.setHorizontalStretch(x) - #policy.setVerticalStretch(y) - #self.setSizePolicy(policy) - self._stretch = (x, y) - self.sigStretchChanged.emit() - #print "setStretch", self, x, y, self.stretch() - - def stretch(self): - #policy = self.sizePolicy() - #return policy.horizontalStretch(), policy.verticalStretch() - return self._stretch - - #def stretch(self): - #return self._stretch - - def hideTitleBar(self): - """ - Hide the title bar for this Dock. - This will prevent the Dock being moved by the user. - """ - self.label.hide() - self.labelHidden = True - if 'center' in self.allowedAreas: - self.allowedAreas.remove('center') - self.updateStyle() - - def showTitleBar(self): - """ - Show the title bar for this Dock. - """ - self.label.show() - self.labelHidden = False - self.allowedAreas.add('center') - self.updateStyle() - - def setOrientation(self, o='auto', force=False): - """ - Sets the orientation of the title bar for this Dock. - Must be one of 'auto', 'horizontal', or 'vertical'. - By default ('auto'), the orientation is determined - based on the aspect ratio of the Dock. - """ - #print self.name(), "setOrientation", o, force - if o == 'auto' and self.autoOrient: - if self.container().type() == 'tab': - o = 'horizontal' - elif self.width() > self.height()*1.5: - o = 'vertical' - else: - o = 'horizontal' - if force or self.orientation != o: - self.orientation = o - self.label.setOrientation(o) - self.updateStyle() - - def updateStyle(self): - ## updates orientation and appearance of title bar - #print self.name(), "update style:", self.orientation, self.moveLabel, self.label.isVisible() - if self.labelHidden: - self.widgetArea.setStyleSheet(self.nStyle) - elif self.orientation == 'vertical': - self.label.setOrientation('vertical') - if self.moveLabel: - #print self.name(), "reclaim label" - self.topLayout.addWidget(self.label, 1, 0) - self.widgetArea.setStyleSheet(self.vStyle) - else: - self.label.setOrientation('horizontal') - if self.moveLabel: - #print self.name(), "reclaim label" - self.topLayout.addWidget(self.label, 0, 1) - self.widgetArea.setStyleSheet(self.hStyle) - - def resizeEvent(self, ev): - self.setOrientation() - self.resizeOverlay(self.size()) - - def name(self): - return str(self.label.text()) - - def container(self): - return self._container - - def addWidget(self, widget, row=None, col=0, rowspan=1, colspan=1): - """ - Add a new widget to the interior of this Dock. - Each Dock uses a QGridLayout to arrange widgets within. - """ - if row is None: - row = self.currentRow - self.currentRow = max(row+1, self.currentRow) - self.widgets.append(widget) - self.layout.addWidget(widget, row, col, rowspan, colspan) - self.raiseOverlay() - - - def startDrag(self): - self.drag = QtGui.QDrag(self) - mime = QtCore.QMimeData() - #mime.setPlainText("asd") - self.drag.setMimeData(mime) - self.widgetArea.setStyleSheet(self.dragStyle) - self.update() - action = self.drag.exec_() - self.updateStyle() - - def float(self): - self.area.floatDock(self) - - def containerChanged(self, c): - #print self.name(), "container changed" - self._container = c - if c.type() != 'tab': - self.moveLabel = True - self.label.setDim(False) - else: - self.moveLabel = False - - self.setOrientation(force=True) - - def close(self): - """Remove this dock from the DockArea it lives inside.""" - self.setParent(None) - self.label.setParent(None) - self._container.apoptose() - self._container = None - - def __repr__(self): - return "" % (self.name(), self.stretch()) - - ## PySide bug: We need to explicitly redefine these methods - ## or else drag/drop events will not be delivered. - def dragEnterEvent(self, *args): - DockDrop.dragEnterEvent(self, *args) - - def dragMoveEvent(self, *args): - DockDrop.dragMoveEvent(self, *args) - - def dragLeaveEvent(self, *args): - DockDrop.dragLeaveEvent(self, *args) - - def dropEvent(self, *args): - DockDrop.dropEvent(self, *args) - -class DockLabel(VerticalLabel): - - sigClicked = QtCore.Signal(object, object) - - def __init__(self, text, dock): - self.dim = False - self.fixedWidth = False - VerticalLabel.__init__(self, text, orientation='horizontal', forceWidth=False) - self.setAlignment(QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter) - self.dock = dock - self.updateStyle() - self.setAutoFillBackground(False) - - #def minimumSizeHint(self): - ##sh = QtGui.QWidget.minimumSizeHint(self) - #return QtCore.QSize(20, 20) - - def updateStyle(self): - r = '3px' - if self.dim: - fg = '#aaa' - bg = '#44a' - border = '#339' - else: - fg = '#fff' - bg = '#66c' - border = '#55B' - - if self.orientation == 'vertical': - self.vStyle = """DockLabel { - background-color : %s; - color : %s; - border-top-right-radius: 0px; - border-top-left-radius: %s; - border-bottom-right-radius: 0px; - border-bottom-left-radius: %s; - border-width: 0px; - border-right: 2px solid %s; - padding-top: 3px; - padding-bottom: 3px; - }""" % (bg, fg, r, r, border) - self.setStyleSheet(self.vStyle) - else: - self.hStyle = """DockLabel { - background-color : %s; - color : %s; - border-top-right-radius: %s; - border-top-left-radius: %s; - border-bottom-right-radius: 0px; - border-bottom-left-radius: 0px; - border-width: 0px; - border-bottom: 2px solid %s; - padding-left: 3px; - padding-right: 3px; - }""" % (bg, fg, r, r, border) - self.setStyleSheet(self.hStyle) - - def setDim(self, d): - if self.dim != d: - self.dim = d - self.updateStyle() - - def setOrientation(self, o): - VerticalLabel.setOrientation(self, o) - self.updateStyle() - - def mousePressEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - self.pressPos = ev.pos() - self.startedDrag = False - ev.accept() - - def mouseMoveEvent(self, ev): - if not self.startedDrag and (ev.pos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance(): - self.dock.startDrag() - ev.accept() - #print ev.pos() - - def mouseReleaseEvent(self, ev): - if not self.startedDrag: - #self.emit(QtCore.SIGNAL('clicked'), self, ev) - self.sigClicked.emit(self, ev) - ev.accept() - - def mouseDoubleClickEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - self.dock.float() - - #def paintEvent(self, ev): - #p = QtGui.QPainter(self) - ##p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200))) - #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100))) - #p.drawRect(self.rect().adjusted(0, 0, -1, -1)) - - #VerticalLabel.paintEvent(self, ev) - - - diff --git a/pyqtgraph/dockarea/DockArea.py b/pyqtgraph/dockarea/DockArea.py deleted file mode 100644 index 882b29a3..00000000 --- a/pyqtgraph/dockarea/DockArea.py +++ /dev/null @@ -1,319 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtCore, QtGui -from .Container import * -from .DockDrop import * -from .Dock import Dock -import pyqtgraph.debug as debug -import weakref - -## TODO: -# - containers should be drop areas, not docks. (but every slot within a container must have its own drop areas?) -# - drop between tabs -# - nest splitters inside tab boxes, etc. - - - - -class DockArea(Container, QtGui.QWidget, DockDrop): - def __init__(self, temporary=False, home=None): - Container.__init__(self, self) - QtGui.QWidget.__init__(self) - DockDrop.__init__(self, allowedAreas=['left', 'right', 'top', 'bottom']) - self.layout = QtGui.QVBoxLayout() - self.layout.setContentsMargins(0,0,0,0) - self.layout.setSpacing(0) - self.setLayout(self.layout) - self.docks = weakref.WeakValueDictionary() - self.topContainer = None - self.raiseOverlay() - self.temporary = temporary - self.tempAreas = [] - self.home = home - - def type(self): - return "top" - - def addDock(self, dock=None, position='bottom', relativeTo=None, **kwds): - """Adds a dock to this area. - - =========== ================================================================= - Arguments: - dock The new Dock object to add. If None, then a new Dock will be - created. - position 'bottom', 'top', 'left', 'right', 'above', or 'below' - relativeTo If relativeTo is None, then the new Dock is added to fill an - entire edge of the window. If relativeTo is another Dock, then - the new Dock is placed adjacent to it (or in a tabbed - configuration for 'above' and 'below'). - =========== ================================================================= - - All extra keyword arguments are passed to Dock.__init__() if *dock* is - None. - """ - if dock is None: - dock = Dock(**kwds) - - - ## Determine the container to insert this dock into. - ## If there is no neighbor, then the container is the top. - if relativeTo is None or relativeTo is self: - if self.topContainer is None: - container = self - neighbor = None - else: - container = self.topContainer - neighbor = None - else: - if isinstance(relativeTo, basestring): - relativeTo = self.docks[relativeTo] - container = self.getContainer(relativeTo) - neighbor = relativeTo - - ## what container type do we need? - neededContainer = { - 'bottom': 'vertical', - 'top': 'vertical', - 'left': 'horizontal', - 'right': 'horizontal', - 'above': 'tab', - 'below': 'tab' - }[position] - - ## Can't insert new containers into a tab container; insert outside instead. - if neededContainer != container.type() and container.type() == 'tab': - neighbor = container - container = container.container() - - ## Decide if the container we have is suitable. - ## If not, insert a new container inside. - if neededContainer != container.type(): - if neighbor is None: - container = self.addContainer(neededContainer, self.topContainer) - else: - container = self.addContainer(neededContainer, neighbor) - - ## Insert the new dock before/after its neighbor - insertPos = { - 'bottom': 'after', - 'top': 'before', - 'left': 'before', - 'right': 'after', - 'above': 'before', - 'below': 'after' - }[position] - #print "request insert", dock, insertPos, neighbor - container.insert(dock, insertPos, neighbor) - dock.area = self - self.docks[dock.name()] = dock - - return dock - - def moveDock(self, dock, position, neighbor): - """ - Move an existing Dock to a new location. - """ - old = dock.container() - ## Moving to the edge of a tabbed dock causes a drop outside the tab box - if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab': - neighbor = neighbor.container() - self.addDock(dock, position, neighbor) - old.apoptose() - - def getContainer(self, obj): - if obj is None: - return self - return obj.container() - - def makeContainer(self, typ): - if typ == 'vertical': - new = VContainer(self) - elif typ == 'horizontal': - new = HContainer(self) - elif typ == 'tab': - new = TContainer(self) - return new - - def addContainer(self, typ, obj): - """Add a new container around obj""" - new = self.makeContainer(typ) - - container = self.getContainer(obj) - container.insert(new, 'before', obj) - #print "Add container:", new, " -> ", container - if obj is not None: - new.insert(obj) - self.raiseOverlay() - return new - - def insert(self, new, pos=None, neighbor=None): - if self.topContainer is not None: - self.topContainer.containerChanged(None) - self.layout.addWidget(new) - self.topContainer = new - #print self, "set top:", new - new._container = self - self.raiseOverlay() - #print "Insert top:", new - - def count(self): - if self.topContainer is None: - return 0 - return 1 - - - #def paintEvent(self, ev): - #self.drawDockOverlay() - - def resizeEvent(self, ev): - self.resizeOverlay(self.size()) - - def addTempArea(self): - if self.home is None: - area = DockArea(temporary=True, home=self) - self.tempAreas.append(area) - win = QtGui.QMainWindow() - win.setCentralWidget(area) - area.win = win - win.show() - else: - area = self.home.addTempArea() - #print "added temp area", area, area.window() - return area - - def floatDock(self, dock): - """Removes *dock* from this DockArea and places it in a new window.""" - area = self.addTempArea() - area.win.resize(dock.size()) - area.moveDock(dock, 'top', None) - - - def removeTempArea(self, area): - self.tempAreas.remove(area) - #print "close window", area.window() - area.window().close() - - def saveState(self): - """ - Return a serialized (storable) representation of the state of - all Docks in this DockArea.""" - state = {'main': self.childState(self.topContainer), 'float': []} - for a in self.tempAreas: - geo = a.win.geometry() - geo = (geo.x(), geo.y(), geo.width(), geo.height()) - state['float'].append((a.saveState(), geo)) - return state - - def childState(self, obj): - if isinstance(obj, Dock): - return ('dock', obj.name(), {}) - else: - childs = [] - for i in range(obj.count()): - childs.append(self.childState(obj.widget(i))) - return (obj.type(), childs, obj.saveState()) - - - def restoreState(self, state): - """ - Restore Dock configuration as generated by saveState. - - Note that this function does not create any Docks--it will only - restore the arrangement of an existing set of Docks. - - """ - - ## 1) make dict of all docks and list of existing containers - containers, docks = self.findAll() - oldTemps = self.tempAreas[:] - #print "found docks:", docks - - ## 2) create container structure, move docks into new containers - self.buildFromState(state['main'], docks, self) - - ## 3) create floating areas, populate - for s in state['float']: - a = self.addTempArea() - a.buildFromState(s[0]['main'], docks, a) - a.win.setGeometry(*s[1]) - - ## 4) Add any remaining docks to the bottom - for d in docks.values(): - self.moveDock(d, 'below', None) - - #print "\nKill old containers:" - ## 5) kill old containers - for c in containers: - c.close() - for a in oldTemps: - a.apoptose() - - - def buildFromState(self, state, docks, root, depth=0): - typ, contents, state = state - pfx = " " * depth - if typ == 'dock': - try: - obj = docks[contents] - del docks[contents] - except KeyError: - raise Exception('Cannot restore dock state; no dock with name "%s"' % contents) - else: - obj = self.makeContainer(typ) - - root.insert(obj, 'after') - #print pfx+"Add:", obj, " -> ", root - - if typ != 'dock': - for o in contents: - self.buildFromState(o, docks, obj, depth+1) - obj.apoptose(propagate=False) - obj.restoreState(state) ## this has to be done later? - - - def findAll(self, obj=None, c=None, d=None): - if obj is None: - obj = self.topContainer - - ## check all temp areas first - if c is None: - c = [] - d = {} - for a in self.tempAreas: - c1, d1 = a.findAll() - c.extend(c1) - d.update(d1) - - if isinstance(obj, Dock): - d[obj.name()] = obj - elif obj is not None: - c.append(obj) - for i in range(obj.count()): - o2 = obj.widget(i) - c2, d2 = self.findAll(o2) - c.extend(c2) - d.update(d2) - return (c, d) - - def apoptose(self): - #print "apoptose area:", self.temporary, self.topContainer, self.topContainer.count() - if self.temporary and self.topContainer.count() == 0: - self.topContainer = None - self.home.removeTempArea(self) - #self.close() - - ## PySide bug: We need to explicitly redefine these methods - ## or else drag/drop events will not be delivered. - def dragEnterEvent(self, *args): - DockDrop.dragEnterEvent(self, *args) - - def dragMoveEvent(self, *args): - DockDrop.dragMoveEvent(self, *args) - - def dragLeaveEvent(self, *args): - DockDrop.dragLeaveEvent(self, *args) - - def dropEvent(self, *args): - DockDrop.dropEvent(self, *args) - - - diff --git a/pyqtgraph/dockarea/DockDrop.py b/pyqtgraph/dockarea/DockDrop.py deleted file mode 100644 index acab28cd..00000000 --- a/pyqtgraph/dockarea/DockDrop.py +++ /dev/null @@ -1,128 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtCore, QtGui - -class DockDrop(object): - """Provides dock-dropping methods""" - def __init__(self, allowedAreas=None): - object.__init__(self) - if allowedAreas is None: - allowedAreas = ['center', 'right', 'left', 'top', 'bottom'] - self.allowedAreas = set(allowedAreas) - self.setAcceptDrops(True) - self.dropArea = None - self.overlay = DropAreaOverlay(self) - self.overlay.raise_() - - def resizeOverlay(self, size): - self.overlay.resize(size) - - def raiseOverlay(self): - self.overlay.raise_() - - def dragEnterEvent(self, ev): - src = ev.source() - if hasattr(src, 'implements') and src.implements('dock'): - #print "drag enter accept" - ev.accept() - else: - #print "drag enter ignore" - ev.ignore() - - def dragMoveEvent(self, ev): - #print "drag move" - ld = ev.pos().x() - rd = self.width() - ld - td = ev.pos().y() - bd = self.height() - td - - mn = min(ld, rd, td, bd) - if mn > 30: - self.dropArea = "center" - elif (ld == mn or td == mn) and mn > self.height()/3.: - self.dropArea = "center" - elif (rd == mn or ld == mn) and mn > self.width()/3.: - self.dropArea = "center" - - elif rd == mn: - self.dropArea = "right" - elif ld == mn: - self.dropArea = "left" - elif td == mn: - self.dropArea = "top" - elif bd == mn: - self.dropArea = "bottom" - - if ev.source() is self and self.dropArea == 'center': - #print " no self-center" - self.dropArea = None - ev.ignore() - elif self.dropArea not in self.allowedAreas: - #print " not allowed" - self.dropArea = None - ev.ignore() - else: - #print " ok" - ev.accept() - self.overlay.setDropArea(self.dropArea) - - def dragLeaveEvent(self, ev): - self.dropArea = None - self.overlay.setDropArea(self.dropArea) - - def dropEvent(self, ev): - area = self.dropArea - if area is None: - return - if area == 'center': - area = 'above' - self.area.moveDock(ev.source(), area, self) - self.dropArea = None - self.overlay.setDropArea(self.dropArea) - - - -class DropAreaOverlay(QtGui.QWidget): - """Overlay widget that draws drop areas during a drag-drop operation""" - - def __init__(self, parent): - QtGui.QWidget.__init__(self, parent) - self.dropArea = None - self.hide() - self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) - - def setDropArea(self, area): - self.dropArea = area - if area is None: - self.hide() - else: - ## Resize overlay to just the region where drop area should be displayed. - ## This works around a Qt bug--can't display transparent widgets over QGLWidget - prgn = self.parent().rect() - rgn = QtCore.QRect(prgn) - w = min(30, prgn.width()/3.) - h = min(30, prgn.height()/3.) - - if self.dropArea == 'left': - rgn.setWidth(w) - elif self.dropArea == 'right': - rgn.setLeft(rgn.left() + prgn.width() - w) - elif self.dropArea == 'top': - rgn.setHeight(h) - elif self.dropArea == 'bottom': - rgn.setTop(rgn.top() + prgn.height() - h) - elif self.dropArea == 'center': - rgn.adjust(w, h, -w, -h) - self.setGeometry(rgn) - self.show() - - self.update() - - def paintEvent(self, ev): - if self.dropArea is None: - return - p = QtGui.QPainter(self) - rgn = self.rect() - - p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 255, 50))) - p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 150), 3)) - p.drawRect(rgn) diff --git a/pyqtgraph/dockarea/__init__.py b/pyqtgraph/dockarea/__init__.py deleted file mode 100644 index f67c50c3..00000000 --- a/pyqtgraph/dockarea/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .DockArea import DockArea -from .Dock import Dock \ No newline at end of file diff --git a/pyqtgraph/exceptionHandling.py b/pyqtgraph/exceptionHandling.py deleted file mode 100644 index daa821b7..00000000 --- a/pyqtgraph/exceptionHandling.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -"""This module installs a wrapper around sys.excepthook which allows multiple -new exception handlers to be registered. - -Optionally, the wrapper also stops exceptions from causing long-term storage -of local stack frames. This has two major effects: - - Unhandled exceptions will no longer cause memory leaks - (If an exception occurs while a lot of data is present on the stack, - such as when loading large files, the data would ordinarily be kept - until the next exception occurs. We would rather release this memory - as soon as possible.) - - Some debuggers may have a hard time handling uncaught exceptions - -The module also provides a callback mechanism allowing others to respond -to exceptions. -""" - -import sys, time -#from lib.Manager import logMsg -import traceback -#from log import * - -#logging = False - -callbacks = [] -clear_tracebacks = False - -def register(fn): - """ - Register a callable to be invoked when there is an unhandled exception. - The callback will be passed the output of sys.exc_info(): (exception type, exception, traceback) - Multiple callbacks will be invoked in the order they were registered. - """ - callbacks.append(fn) - -def unregister(fn): - """Unregister a previously registered callback.""" - callbacks.remove(fn) - -def setTracebackClearing(clear=True): - """ - Enable or disable traceback clearing. - By default, clearing is disabled and Python will indefinitely store unhandled exception stack traces. - This function is provided since Python's default behavior can cause unexpected retention of - large memory-consuming objects. - """ - global clear_tracebacks - clear_tracebacks = clear - -class ExceptionHandler(object): - def __call__(self, *args): - ## call original exception handler first (prints exception) - global original_excepthook, callbacks, clear_tracebacks - print("===== %s =====" % str(time.strftime("%Y.%m.%d %H:%m:%S", time.localtime(time.time())))) - ret = original_excepthook(*args) - - for cb in callbacks: - try: - cb(*args) - except: - print(" --------------------------------------------------------------") - print(" Error occurred during exception callback %s" % str(cb)) - print(" --------------------------------------------------------------") - traceback.print_exception(*sys.exc_info()) - - - ## Clear long-term storage of last traceback to prevent memory-hogging. - ## (If an exception occurs while a lot of data is present on the stack, - ## such as when loading large files, the data would ordinarily be kept - ## until the next exception occurs. We would rather release this memory - ## as soon as possible.) - if clear_tracebacks is True: - sys.last_traceback = None - - def implements(self, interface=None): - ## this just makes it easy for us to detect whether an ExceptionHook is already installed. - if interface is None: - return ['ExceptionHandler'] - else: - return interface == 'ExceptionHandler' - - - -## replace built-in excepthook only if this has not already been done -if not (hasattr(sys.excepthook, 'implements') and sys.excepthook.implements('ExceptionHandler')): - original_excepthook = sys.excepthook - sys.excepthook = ExceptionHandler() - - - diff --git a/pyqtgraph/exporters/CSVExporter.py b/pyqtgraph/exporters/CSVExporter.py deleted file mode 100644 index 0439fc35..00000000 --- a/pyqtgraph/exporters/CSVExporter.py +++ /dev/null @@ -1,59 +0,0 @@ -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore -from .Exporter import Exporter -from pyqtgraph.parametertree import Parameter - - -__all__ = ['CSVExporter'] - - -class CSVExporter(Exporter): - Name = "CSV from plot data" - windows = [] - def __init__(self, item): - Exporter.__init__(self, item) - self.params = Parameter(name='params', type='group', children=[ - {'name': 'separator', 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']}, - {'name': 'precision', 'type': 'int', 'value': 10, 'limits': [0, None]}, - ]) - - def parameters(self): - return self.params - - def export(self, fileName=None): - - if not isinstance(self.item, pg.PlotItem): - raise Exception("Must have a PlotItem selected for CSV export.") - - if fileName is None: - self.fileSaveDialog(filter=["*.csv", "*.tsv"]) - return - - fd = open(fileName, 'w') - data = [] - header = [] - for c in self.item.curves: - data.append(c.getData()) - header.extend(['x', 'y']) - - if self.params['separator'] == 'comma': - sep = ',' - else: - sep = '\t' - - fd.write(sep.join(header) + '\n') - i = 0 - numFormat = '%%0.%dg' % self.params['precision'] - numRows = reduce(max, [len(d[0]) for d in data]) - for i in range(numRows): - for d in data: - if i < len(d[0]): - fd.write(numFormat % d[0][i] + sep + numFormat % d[1][i] + sep) - else: - fd.write(' %s %s' % (sep, sep)) - fd.write('\n') - fd.close() - - - - diff --git a/pyqtgraph/exporters/Exporter.py b/pyqtgraph/exporters/Exporter.py deleted file mode 100644 index 6371a3b9..00000000 --- a/pyqtgraph/exporters/Exporter.py +++ /dev/null @@ -1,175 +0,0 @@ -from pyqtgraph.widgets.FileDialog import FileDialog -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore, QtSvg -from pyqtgraph.python2_3 import asUnicode -import os, re -LastExportDirectory = None - - -class Exporter(object): - """ - Abstract class used for exporting graphics to file / printer / whatever. - """ - allowCopy = False # subclasses set this to True if they can use the copy buffer - - def __init__(self, item): - """ - Initialize with the item to be exported. - Can be an individual graphics item or a scene. - """ - object.__init__(self) - self.item = item - - #def item(self): - #return self.item - - def parameters(self): - """Return the parameters used to configure this exporter.""" - raise Exception("Abstract method must be overridden in subclass.") - - def export(self, fileName=None, toBytes=False, copy=False): - """ - If *fileName* is None, pop-up a file dialog. - If *toBytes* is True, return a bytes object rather than writing to file. - If *copy* is True, export to the copy buffer rather than writing to file. - """ - raise Exception("Abstract method must be overridden in subclass.") - - def fileSaveDialog(self, filter=None, opts=None): - ## Show a file dialog, call self.export(fileName) when finished. - if opts is None: - opts = {} - self.fileDialog = FileDialog() - self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - if filter is not None: - if isinstance(filter, basestring): - self.fileDialog.setNameFilter(filter) - elif isinstance(filter, list): - self.fileDialog.setNameFilters(filter) - global LastExportDirectory - exportDir = LastExportDirectory - if exportDir is not None: - self.fileDialog.setDirectory(exportDir) - self.fileDialog.show() - self.fileDialog.opts = opts - self.fileDialog.fileSelected.connect(self.fileSaveFinished) - return - - def fileSaveFinished(self, fileName): - fileName = asUnicode(fileName) - global LastExportDirectory - LastExportDirectory = os.path.split(fileName)[0] - - ## If file name does not match selected extension, append it now - ext = os.path.splitext(fileName)[1].lower().lstrip('.') - selectedExt = re.search(r'\*\.(\w+)\b', asUnicode(self.fileDialog.selectedNameFilter())) - if selectedExt is not None: - selectedExt = selectedExt.groups()[0].lower() - if ext != selectedExt: - fileName = fileName + '.' + selectedExt.lstrip('.') - - self.export(fileName=fileName, **self.fileDialog.opts) - - def getScene(self): - if isinstance(self.item, pg.GraphicsScene): - return self.item - else: - return self.item.scene() - - def getSourceRect(self): - if isinstance(self.item, pg.GraphicsScene): - w = self.item.getViewWidget() - return w.viewportTransform().inverted()[0].mapRect(w.rect()) - else: - return self.item.sceneBoundingRect() - - def getTargetRect(self): - if isinstance(self.item, pg.GraphicsScene): - return self.item.getViewWidget().rect() - else: - return self.item.mapRectToDevice(self.item.boundingRect()) - - def setExportMode(self, export, opts=None): - """ - Call setExportMode(export, opts) on all items that will - be painted during the export. This informs the item - that it is about to be painted for export, allowing it to - alter its appearance temporarily - - - *export* - bool; must be True before exporting and False afterward - *opts* - dict; common parameters are 'antialias' and 'background' - """ - if opts is None: - opts = {} - for item in self.getPaintItems(): - if hasattr(item, 'setExportMode'): - item.setExportMode(export, opts) - - def getPaintItems(self, root=None): - """Return a list of all items that should be painted in the correct order.""" - if root is None: - root = self.item - preItems = [] - postItems = [] - if isinstance(root, QtGui.QGraphicsScene): - childs = [i for i in root.items() if i.parentItem() is None] - rootItem = [] - else: - childs = root.childItems() - rootItem = [root] - childs.sort(key=lambda a: a.zValue()) - while len(childs) > 0: - ch = childs.pop(0) - tree = self.getPaintItems(ch) - if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0): - preItems.extend(tree) - else: - postItems.extend(tree) - - return preItems + rootItem + postItems - - def render(self, painter, targetRect, sourceRect, item=None): - - #if item is None: - #item = self.item - #preItems = [] - #postItems = [] - #if isinstance(item, QtGui.QGraphicsScene): - #childs = [i for i in item.items() if i.parentItem() is None] - #rootItem = [] - #else: - #childs = item.childItems() - #rootItem = [item] - #childs.sort(lambda a,b: cmp(a.zValue(), b.zValue())) - #while len(childs) > 0: - #ch = childs.pop(0) - #if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0): - #preItems.extend(tree) - #else: - #postItems.extend(tree) - - #for ch in preItems: - #self.render(painter, sourceRect, targetRect, item=ch) - ### paint root here - #for ch in postItems: - #self.render(painter, sourceRect, targetRect, item=ch) - - - self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) - - #def writePs(self, fileName=None, item=None): - #if fileName is None: - #self.fileSaveDialog(self.writeSvg, filter="PostScript (*.ps)") - #return - #if item is None: - #item = self - #printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) - #printer.setOutputFileName(fileName) - #painter = QtGui.QPainter(printer) - #self.render(painter) - #painter.end() - - #def writeToPrinter(self): - #pass diff --git a/pyqtgraph/exporters/ImageExporter.py b/pyqtgraph/exporters/ImageExporter.py deleted file mode 100644 index 9fb77e2a..00000000 --- a/pyqtgraph/exporters/ImageExporter.py +++ /dev/null @@ -1,101 +0,0 @@ -from .Exporter import Exporter -from pyqtgraph.parametertree import Parameter -from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE -import pyqtgraph as pg -import numpy as np - -__all__ = ['ImageExporter'] - -class ImageExporter(Exporter): - Name = "Image File (PNG, TIF, JPG, ...)" - allowCopy = True - - def __init__(self, item): - Exporter.__init__(self, item) - tr = self.getTargetRect() - if isinstance(item, QtGui.QGraphicsItem): - scene = item.scene() - else: - scene = item - bgbrush = scene.views()[0].backgroundBrush() - bg = bgbrush.color() - if bgbrush.style() == QtCore.Qt.NoBrush: - bg.setAlpha(0) - - self.params = Parameter(name='params', type='group', children=[ - {'name': 'width', 'type': 'int', 'value': tr.width(), 'limits': (0, None)}, - {'name': 'height', 'type': 'int', 'value': tr.height(), 'limits': (0, None)}, - {'name': 'antialias', 'type': 'bool', 'value': True}, - {'name': 'background', 'type': 'color', 'value': bg}, - ]) - self.params.param('width').sigValueChanged.connect(self.widthChanged) - self.params.param('height').sigValueChanged.connect(self.heightChanged) - - def widthChanged(self): - sr = self.getSourceRect() - ar = float(sr.height()) / sr.width() - self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) - - def heightChanged(self): - sr = self.getSourceRect() - ar = float(sr.width()) / sr.height() - self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) - - def parameters(self): - return self.params - - def export(self, fileName=None, toBytes=False, copy=False): - if fileName is None and not toBytes and not copy: - if USE_PYSIDE: - filter = ["*."+str(f) for f in QtGui.QImageWriter.supportedImageFormats()] - else: - filter = ["*."+bytes(f).decode('utf-8') for f in QtGui.QImageWriter.supportedImageFormats()] - preferred = ['*.png', '*.tif', '*.jpg'] - for p in preferred[::-1]: - if p in filter: - filter.remove(p) - filter.insert(0, p) - self.fileSaveDialog(filter=filter) - return - - targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) - sourceRect = self.getSourceRect() - - - #self.png = QtGui.QImage(targetRect.size(), QtGui.QImage.Format_ARGB32) - #self.png.fill(pyqtgraph.mkColor(self.params['background'])) - w, h = self.params['width'], self.params['height'] - if w == 0 or h == 0: - raise Exception("Cannot export image with size=0 (requested export size is %dx%d)" % (w,h)) - bg = np.empty((self.params['width'], self.params['height'], 4), dtype=np.ubyte) - color = self.params['background'] - bg[:,:,0] = color.blue() - bg[:,:,1] = color.green() - bg[:,:,2] = color.red() - bg[:,:,3] = color.alpha() - self.png = pg.makeQImage(bg, alpha=True) - - ## set resolution of image: - origTargetRect = self.getTargetRect() - resolutionScale = targetRect.width() / origTargetRect.width() - #self.png.setDotsPerMeterX(self.png.dotsPerMeterX() * resolutionScale) - #self.png.setDotsPerMeterY(self.png.dotsPerMeterY() * resolutionScale) - - painter = QtGui.QPainter(self.png) - #dtr = painter.deviceTransform() - try: - self.setExportMode(True, {'antialias': self.params['antialias'], 'background': self.params['background'], 'painter': painter, 'resolutionScale': resolutionScale}) - painter.setRenderHint(QtGui.QPainter.Antialiasing, self.params['antialias']) - self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) - finally: - self.setExportMode(False) - painter.end() - - if copy: - QtGui.QApplication.clipboard().setImage(self.png) - elif toBytes: - return self.png - else: - self.png.save(fileName) - - \ No newline at end of file diff --git a/pyqtgraph/exporters/Matplotlib.py b/pyqtgraph/exporters/Matplotlib.py deleted file mode 100644 index 76f878d2..00000000 --- a/pyqtgraph/exporters/Matplotlib.py +++ /dev/null @@ -1,74 +0,0 @@ -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore -from .Exporter import Exporter - - -__all__ = ['MatplotlibExporter'] - - -class MatplotlibExporter(Exporter): - Name = "Matplotlib Window" - windows = [] - def __init__(self, item): - Exporter.__init__(self, item) - - def parameters(self): - return None - - def export(self, fileName=None): - - if isinstance(self.item, pg.PlotItem): - mpw = MatplotlibWindow() - MatplotlibExporter.windows.append(mpw) - fig = mpw.getFigure() - - ax = fig.add_subplot(111) - ax.clear() - #ax.grid(True) - - for item in self.item.curves: - x, y = item.getData() - opts = item.opts - pen = pg.mkPen(opts['pen']) - if pen.style() == QtCore.Qt.NoPen: - linestyle = '' - else: - linestyle = '-' - color = tuple([c/255. for c in pg.colorTuple(pen.color())]) - symbol = opts['symbol'] - if symbol == 't': - symbol = '^' - symbolPen = pg.mkPen(opts['symbolPen']) - symbolBrush = pg.mkBrush(opts['symbolBrush']) - markeredgecolor = tuple([c/255. for c in pg.colorTuple(symbolPen.color())]) - markerfacecolor = tuple([c/255. for c in pg.colorTuple(symbolBrush.color())]) - - if opts['fillLevel'] is not None and opts['fillBrush'] is not None: - fillBrush = pg.mkBrush(opts['fillBrush']) - fillcolor = tuple([c/255. for c in pg.colorTuple(fillBrush.color())]) - ax.fill_between(x=x, y1=y, y2=opts['fillLevel'], facecolor=fillcolor) - - ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(), linestyle=linestyle, markeredgecolor=markeredgecolor, markerfacecolor=markerfacecolor) - - xr, yr = self.item.viewRange() - ax.set_xbound(*xr) - ax.set_ybound(*yr) - mpw.draw() - else: - raise Exception("Matplotlib export currently only works with plot items") - - - -class MatplotlibWindow(QtGui.QMainWindow): - def __init__(self): - import pyqtgraph.widgets.MatplotlibWidget - QtGui.QMainWindow.__init__(self) - self.mpl = pyqtgraph.widgets.MatplotlibWidget.MatplotlibWidget() - self.setCentralWidget(self.mpl) - self.show() - - def __getattr__(self, attr): - return getattr(self.mpl, attr) - - def closeEvent(self, ev): - MatplotlibExporter.windows.remove(self) diff --git a/pyqtgraph/exporters/PrintExporter.py b/pyqtgraph/exporters/PrintExporter.py deleted file mode 100644 index 5b31b45d..00000000 --- a/pyqtgraph/exporters/PrintExporter.py +++ /dev/null @@ -1,65 +0,0 @@ -from .Exporter import Exporter -from pyqtgraph.parametertree import Parameter -from pyqtgraph.Qt import QtGui, QtCore, QtSvg -import re - -__all__ = ['PrintExporter'] -#__all__ = [] ## Printer is disabled for now--does not work very well. - -class PrintExporter(Exporter): - Name = "Printer" - def __init__(self, item): - Exporter.__init__(self, item) - tr = self.getTargetRect() - self.params = Parameter(name='params', type='group', children=[ - {'name': 'width', 'type': 'float', 'value': 0.1, 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, - {'name': 'height', 'type': 'float', 'value': (0.1 * tr.height()) / tr.width(), 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, - ]) - self.params.param('width').sigValueChanged.connect(self.widthChanged) - self.params.param('height').sigValueChanged.connect(self.heightChanged) - - def widthChanged(self): - sr = self.getSourceRect() - ar = sr.height() / sr.width() - self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) - - def heightChanged(self): - sr = self.getSourceRect() - ar = sr.width() / sr.height() - self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) - - def parameters(self): - return self.params - - def export(self, fileName=None): - printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) - dialog = QtGui.QPrintDialog(printer) - dialog.setWindowTitle("Print Document") - if dialog.exec_() != QtGui.QDialog.Accepted: - return; - - #dpi = QtGui.QDesktopWidget().physicalDpiX() - - #self.svg.setSize(QtCore.QSize(100,100)) - #self.svg.setResolution(600) - #res = printer.resolution() - sr = self.getSourceRect() - #res = sr.width() * .4 / (self.params['width'] * 100 / 2.54) - res = QtGui.QDesktopWidget().physicalDpiX() - printer.setResolution(res) - rect = printer.pageRect() - center = rect.center() - h = self.params['height'] * res * 100. / 2.54 - w = self.params['width'] * res * 100. / 2.54 - x = center.x() - w/2. - y = center.y() - h/2. - - targetRect = QtCore.QRect(x, y, w, h) - sourceRect = self.getSourceRect() - painter = QtGui.QPainter(printer) - try: - self.setExportMode(True, {'painter': painter}) - self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) - finally: - self.setExportMode(False) - painter.end() diff --git a/pyqtgraph/exporters/SVGExporter.py b/pyqtgraph/exporters/SVGExporter.py deleted file mode 100644 index 62b49d30..00000000 --- a/pyqtgraph/exporters/SVGExporter.py +++ /dev/null @@ -1,488 +0,0 @@ -from .Exporter import Exporter -from pyqtgraph.python2_3 import asUnicode -from pyqtgraph.parametertree import Parameter -from pyqtgraph.Qt import QtGui, QtCore, QtSvg -import pyqtgraph as pg -import re -import xml.dom.minidom as xml -import numpy as np - - -__all__ = ['SVGExporter'] - -class SVGExporter(Exporter): - Name = "Scalable Vector Graphics (SVG)" - allowCopy=True - - def __init__(self, item): - Exporter.__init__(self, item) - #tr = self.getTargetRect() - self.params = Parameter(name='params', type='group', children=[ - #{'name': 'width', 'type': 'float', 'value': tr.width(), 'limits': (0, None)}, - #{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)}, - #{'name': 'viewbox clipping', 'type': 'bool', 'value': True}, - #{'name': 'normalize coordinates', 'type': 'bool', 'value': True}, - #{'name': 'normalize line width', 'type': 'bool', 'value': True}, - ]) - #self.params.param('width').sigValueChanged.connect(self.widthChanged) - #self.params.param('height').sigValueChanged.connect(self.heightChanged) - - def widthChanged(self): - sr = self.getSourceRect() - ar = sr.height() / sr.width() - self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) - - def heightChanged(self): - sr = self.getSourceRect() - ar = sr.width() / sr.height() - self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) - - def parameters(self): - return self.params - - def export(self, fileName=None, toBytes=False, copy=False): - if toBytes is False and copy is False and fileName is None: - self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)") - return - #self.svg = QtSvg.QSvgGenerator() - #self.svg.setFileName(fileName) - #dpi = QtGui.QDesktopWidget().physicalDpiX() - ### not really sure why this works, but it seems to be important: - #self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.)) - #self.svg.setResolution(dpi) - ##self.svg.setViewBox() - #targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) - #sourceRect = self.getSourceRect() - - #painter = QtGui.QPainter(self.svg) - #try: - #self.setExportMode(True) - #self.render(painter, QtCore.QRectF(targetRect), sourceRect) - #finally: - #self.setExportMode(False) - #painter.end() - - ## Workaround to set pen widths correctly - #data = open(fileName).readlines() - #for i in range(len(data)): - #line = data[i] - #m = re.match(r'( - -pyqtgraph SVG export -Generated with Qt and pyqtgraph - - -""" - -def generateSvg(item): - global xmlHeader - try: - node = _generateItemSvg(item) - finally: - ## reset export mode for all items in the tree - if isinstance(item, QtGui.QGraphicsScene): - items = item.items() - else: - items = [item] - for i in items: - items.extend(i.childItems()) - for i in items: - if hasattr(i, 'setExportMode'): - i.setExportMode(False) - - cleanXml(node) - - return xmlHeader + node.toprettyxml(indent=' ') + "\n\n" - - -def _generateItemSvg(item, nodes=None, root=None): - ## This function is intended to work around some issues with Qt's SVG generator - ## and SVG in general. - ## 1) Qt SVG does not implement clipping paths. This is absurd. - ## The solution is to let Qt generate SVG for each item independently, - ## then glue them together manually with clipping. - ## - ## The format Qt generates for all items looks like this: - ## - ## - ## - ## one or more of: or or - ## - ## - ## one or more of: or or - ## - ## . . . - ## - ## - ## 2) There seems to be wide disagreement over whether path strokes - ## should be scaled anisotropically. - ## see: http://web.mit.edu/jonas/www/anisotropy/ - ## Given that both inkscape and illustrator seem to prefer isotropic - ## scaling, we will optimize for those cases. - ## - ## 3) Qt generates paths using non-scaling-stroke from SVG 1.2, but - ## inkscape only supports 1.1. - ## - ## Both 2 and 3 can be addressed by drawing all items in world coordinates. - - prof = pg.debug.Profiler('generateItemSvg %s' % str(item), disabled=True) - - if nodes is None: ## nodes maps all node IDs to their XML element. - ## this allows us to ensure all elements receive unique names. - nodes = {} - - if root is None: - root = item - - ## Skip hidden items - if hasattr(item, 'isVisible') and not item.isVisible(): - return None - - ## If this item defines its own SVG generator, use that. - if hasattr(item, 'generateSvg'): - return item.generateSvg(nodes) - - - ## Generate SVG text for just this item (exclude its children; we'll handle them later) - tr = QtGui.QTransform() - if isinstance(item, QtGui.QGraphicsScene): - xmlStr = "\n\n" - doc = xml.parseString(xmlStr) - childs = [i for i in item.items() if i.parentItem() is None] - elif item.__class__.paint == QtGui.QGraphicsItem.paint: - xmlStr = "\n\n" - doc = xml.parseString(xmlStr) - childs = item.childItems() - else: - childs = item.childItems() - tr = itemTransform(item, item.scene()) - - ## offset to corner of root item - if isinstance(root, QtGui.QGraphicsScene): - rootPos = QtCore.QPoint(0,0) - else: - rootPos = root.scenePos() - tr2 = QtGui.QTransform() - tr2.translate(-rootPos.x(), -rootPos.y()) - tr = tr * tr2 - #print item, pg.SRTTransform(tr) - - #tr.translate(item.pos().x(), item.pos().y()) - #tr = tr * item.transform() - arr = QtCore.QByteArray() - buf = QtCore.QBuffer(arr) - svg = QtSvg.QSvgGenerator() - svg.setOutputDevice(buf) - dpi = QtGui.QDesktopWidget().physicalDpiX() - ### not really sure why this works, but it seems to be important: - #self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.)) - svg.setResolution(dpi) - - p = QtGui.QPainter() - p.begin(svg) - if hasattr(item, 'setExportMode'): - item.setExportMode(True, {'painter': p}) - try: - p.setTransform(tr) - item.paint(p, QtGui.QStyleOptionGraphicsItem(), None) - finally: - p.end() - ## Can't do this here--we need to wait until all children have painted as well. - ## this is taken care of in generateSvg instead. - #if hasattr(item, 'setExportMode'): - #item.setExportMode(False) - - xmlStr = bytes(arr).decode('utf-8') - doc = xml.parseString(xmlStr) - - try: - ## Get top-level group for this item - g1 = doc.getElementsByTagName('g')[0] - ## get list of sub-groups - g2 = [n for n in g1.childNodes if isinstance(n, xml.Element) and n.tagName == 'g'] - except: - print(doc.toxml()) - raise - - prof.mark('render') - - ## Get rid of group transformation matrices by applying - ## transformation to inner coordinates - correctCoordinates(g1, item) - prof.mark('correct') - ## make sure g1 has the transformation matrix - #m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32()) - #g1.setAttribute('transform', "matrix(%f,%f,%f,%f,%f,%f)" % m) - - #print "=================",item,"=====================" - #print g1.toprettyxml(indent=" ", newl='') - - ## Inkscape does not support non-scaling-stroke (this is SVG 1.2, inkscape supports 1.1) - ## So we need to correct anything attempting to use this. - #correctStroke(g1, item, root) - - ## decide on a name for this item - baseName = item.__class__.__name__ - i = 1 - while True: - name = baseName + "_%d" % i - if name not in nodes: - break - i += 1 - nodes[name] = g1 - g1.setAttribute('id', name) - - ## If this item clips its children, we need to take care of that. - childGroup = g1 ## add children directly to this node unless we are clipping - if not isinstance(item, QtGui.QGraphicsScene): - ## See if this item clips its children - if int(item.flags() & item.ItemClipsChildrenToShape) > 0: - ## Generate svg for just the path - #if isinstance(root, QtGui.QGraphicsScene): - #path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) - #else: - #path = QtGui.QGraphicsPathItem(root.mapToParent(item.mapToItem(root, item.shape()))) - path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) - item.scene().addItem(path) - try: - pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0] - finally: - item.scene().removeItem(path) - - ## and for the clipPath element - clip = name + '_clip' - clipNode = g1.ownerDocument.createElement('clipPath') - clipNode.setAttribute('id', clip) - clipNode.appendChild(pathNode) - g1.appendChild(clipNode) - - childGroup = g1.ownerDocument.createElement('g') - childGroup.setAttribute('clip-path', 'url(#%s)' % clip) - g1.appendChild(childGroup) - prof.mark('clipping') - - ## Add all child items as sub-elements. - childs.sort(key=lambda c: c.zValue()) - for ch in childs: - cg = _generateItemSvg(ch, nodes, root) - if cg is None: - continue - childGroup.appendChild(cg) ### this isn't quite right--some items draw below their parent (good enough for now) - prof.mark('children') - prof.finish() - return g1 - -def correctCoordinates(node, item): - ## Remove transformation matrices from tags by applying matrix to coordinates inside. - ## Each item is represented by a single top-level group with one or more groups inside. - ## Each inner group contains one or more drawing primitives, possibly of different types. - groups = node.getElementsByTagName('g') - - ## Since we leave text unchanged, groups which combine text and non-text primitives must be split apart. - ## (if at some point we start correcting text transforms as well, then it should be safe to remove this) - groups2 = [] - for grp in groups: - subGroups = [grp.cloneNode(deep=False)] - textGroup = None - for ch in grp.childNodes[:]: - if isinstance(ch, xml.Element): - if textGroup is None: - textGroup = ch.tagName == 'text' - if ch.tagName == 'text': - if textGroup is False: - subGroups.append(grp.cloneNode(deep=False)) - textGroup = True - else: - if textGroup is True: - subGroups.append(grp.cloneNode(deep=False)) - textGroup = False - subGroups[-1].appendChild(ch) - groups2.extend(subGroups) - for sg in subGroups: - node.insertBefore(sg, grp) - node.removeChild(grp) - groups = groups2 - - - for grp in groups: - matrix = grp.getAttribute('transform') - match = re.match(r'matrix\((.*)\)', matrix) - if match is None: - vals = [1,0,0,1,0,0] - else: - vals = [float(a) for a in match.groups()[0].split(',')] - tr = np.array([[vals[0], vals[2], vals[4]], [vals[1], vals[3], vals[5]]]) - - removeTransform = False - for ch in grp.childNodes: - if not isinstance(ch, xml.Element): - continue - if ch.tagName == 'polyline': - removeTransform = True - coords = np.array([[float(a) for a in c.split(',')] for c in ch.getAttribute('points').strip().split(' ')]) - coords = pg.transformCoordinates(tr, coords, transpose=True) - ch.setAttribute('points', ' '.join([','.join([str(a) for a in c]) for c in coords])) - elif ch.tagName == 'path': - removeTransform = True - newCoords = '' - oldCoords = ch.getAttribute('d').strip() - if oldCoords == '': - continue - for c in oldCoords.split(' '): - x,y = c.split(',') - if x[0].isalpha(): - t = x[0] - x = x[1:] - else: - t = '' - nc = pg.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True) - newCoords += t+str(nc[0,0])+','+str(nc[0,1])+' ' - ch.setAttribute('d', newCoords) - elif ch.tagName == 'text': - removeTransform = False - ## leave text alone for now. Might need this later to correctly render text with outline. - #c = np.array([ - #[float(ch.getAttribute('x')), float(ch.getAttribute('y'))], - #[float(ch.getAttribute('font-size')), 0], - #[0,0]]) - #c = pg.transformCoordinates(tr, c, transpose=True) - #ch.setAttribute('x', str(c[0,0])) - #ch.setAttribute('y', str(c[0,1])) - #fs = c[1]-c[2] - #fs = (fs**2).sum()**0.5 - #ch.setAttribute('font-size', str(fs)) - - ## Correct some font information - families = ch.getAttribute('font-family').split(',') - if len(families) == 1: - font = QtGui.QFont(families[0].strip('" ')) - if font.style() == font.SansSerif: - families.append('sans-serif') - elif font.style() == font.Serif: - families.append('serif') - elif font.style() == font.Courier: - families.append('monospace') - ch.setAttribute('font-family', ', '.join([f if ' ' not in f else '"%s"'%f for f in families])) - - ## correct line widths if needed - if removeTransform and ch.getAttribute('vector-effect') != 'non-scaling-stroke': - w = float(grp.getAttribute('stroke-width')) - s = pg.transformCoordinates(tr, np.array([[w,0], [0,0]]), transpose=True) - w = ((s[0]-s[1])**2).sum()**0.5 - ch.setAttribute('stroke-width', str(w)) - - if removeTransform: - grp.removeAttribute('transform') - -def itemTransform(item, root): - ## Return the transformation mapping item to root - ## (actually to parent coordinate system of root) - - if item is root: - tr = QtGui.QTransform() - tr.translate(*item.pos()) - tr = tr * item.transform() - return tr - - - if int(item.flags() & item.ItemIgnoresTransformations) > 0: - pos = item.pos() - parent = item.parentItem() - if parent is not None: - pos = itemTransform(parent, root).map(pos) - tr = QtGui.QTransform() - tr.translate(pos.x(), pos.y()) - tr = item.transform() * tr - else: - ## find next parent that is either the root item or - ## an item that ignores its transformation - nextRoot = item - while True: - nextRoot = nextRoot.parentItem() - if nextRoot is None: - nextRoot = root - break - if nextRoot is root or int(nextRoot.flags() & nextRoot.ItemIgnoresTransformations) > 0: - break - - if isinstance(nextRoot, QtGui.QGraphicsScene): - tr = item.sceneTransform() - else: - tr = itemTransform(nextRoot, root) * item.itemTransform(nextRoot)[0] - #pos = QtGui.QTransform() - #pos.translate(root.pos().x(), root.pos().y()) - #tr = pos * root.transform() * item.itemTransform(root)[0] - - - return tr - - -#def correctStroke(node, item, root, width=1): - ##print "==============", item, node - #if node.hasAttribute('stroke-width'): - #width = float(node.getAttribute('stroke-width')) - #if node.getAttribute('vector-effect') == 'non-scaling-stroke': - #node.removeAttribute('vector-effect') - #if isinstance(root, QtGui.QGraphicsScene): - #w = item.mapFromScene(pg.Point(width,0)) - #o = item.mapFromScene(pg.Point(0,0)) - #else: - #w = item.mapFromItem(root, pg.Point(width,0)) - #o = item.mapFromItem(root, pg.Point(0,0)) - #w = w-o - ##print " ", w, o, w-o - #w = (w.x()**2 + w.y()**2) ** 0.5 - ##print " ", w - #node.setAttribute('stroke-width', str(w)) - - #for ch in node.childNodes: - #if isinstance(ch, xml.Element): - #correctStroke(ch, item, root, width) - -def cleanXml(node): - ## remove extraneous text; let the xml library do the formatting. - hasElement = False - nonElement = [] - for ch in node.childNodes: - if isinstance(ch, xml.Element): - hasElement = True - cleanXml(ch) - else: - nonElement.append(ch) - - if hasElement: - for ch in nonElement: - node.removeChild(ch) - elif node.tagName == 'g': ## remove childless groups - node.parentNode.removeChild(node) diff --git a/pyqtgraph/exporters/__init__.py b/pyqtgraph/exporters/__init__.py deleted file mode 100644 index 3f3c1f1d..00000000 --- a/pyqtgraph/exporters/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -Exporters = [] -from pyqtgraph import importModules -#from .. import frozenSupport -import os -d = os.path.split(__file__)[0] -#files = [] -#for f in frozenSupport.listdir(d): - #if frozenSupport.isdir(os.path.join(d, f)) and f != '__pycache__': - #files.append(f) - #elif f[-3:] == '.py' and f not in ['__init__.py', 'Exporter.py']: - #files.append(f[:-3]) - -#for modName in files: - #mod = __import__(modName, globals(), locals(), fromlist=['*']) -for mod in importModules('', globals(), locals(), excludes=['Exporter']).values(): - if hasattr(mod, '__all__'): - names = mod.__all__ - else: - names = [n for n in dir(mod) if n[0] != '_'] - for k in names: - if hasattr(mod, k): - Exporters.append(getattr(mod, k)) - - -def listExporters(): - return Exporters[:] - diff --git a/pyqtgraph/flowchart/Flowchart.py b/pyqtgraph/flowchart/Flowchart.py deleted file mode 100644 index 81f9e163..00000000 --- a/pyqtgraph/flowchart/Flowchart.py +++ /dev/null @@ -1,955 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE -from .Node import * -from pyqtgraph.pgcollections import OrderedDict -from pyqtgraph.widgets.TreeWidget import * - -## pyside and pyqt use incompatible ui files. -if USE_PYSIDE: - from . import FlowchartTemplate_pyside as FlowchartTemplate - from . import FlowchartCtrlTemplate_pyside as FlowchartCtrlTemplate -else: - from . import FlowchartTemplate_pyqt as FlowchartTemplate - from . import FlowchartCtrlTemplate_pyqt as FlowchartCtrlTemplate - -from .Terminal import Terminal -from numpy import ndarray -from . import library -from pyqtgraph.debug import printExc -import pyqtgraph.configfile as configfile -import pyqtgraph.dockarea as dockarea -import pyqtgraph as pg -from . import FlowchartGraphicsView - -def strDict(d): - return dict([(str(k), v) for k, v in d.items()]) - - -def toposort(deps, nodes=None, seen=None, stack=None, depth=0): - """Topological sort. Arguments are: - deps dictionary describing dependencies where a:[b,c] means "a depends on b and c" - nodes optional, specifies list of starting nodes (these should be the nodes - which are not depended on by any other nodes) - """ - - if nodes is None: - ## run through deps to find nodes that are not depended upon - rem = set() - for dep in deps.values(): - rem |= set(dep) - nodes = set(deps.keys()) - rem - if seen is None: - seen = set() - stack = [] - sorted = [] - #print " "*depth, "Starting from", nodes - for n in nodes: - if n in stack: - raise Exception("Cyclic dependency detected", stack + [n]) - if n in seen: - continue - seen.add(n) - #print " "*depth, " descending into", n, deps[n] - sorted.extend( toposort(deps, deps[n], seen, stack+[n], depth=depth+1)) - #print " "*depth, " Added", n - sorted.append(n) - #print " "*depth, " ", sorted - return sorted - - -class Flowchart(Node): - - sigFileLoaded = QtCore.Signal(object) - sigFileSaved = QtCore.Signal(object) - - - #sigOutputChanged = QtCore.Signal() ## inherited from Node - sigChartLoaded = QtCore.Signal() - sigStateChanged = QtCore.Signal() - - def __init__(self, terminals=None, name=None, filePath=None): - if name is None: - name = "Flowchart" - if terminals is None: - terminals = {} - self.filePath = filePath - Node.__init__(self, name, allowAddInput=True, allowAddOutput=True) ## create node without terminals; we'll add these later - - - self.inputWasSet = False ## flag allows detection of changes in the absence of input change. - self._nodes = {} - self.nextZVal = 10 - #self.connects = [] - #self._chartGraphicsItem = FlowchartGraphicsItem(self) - self._widget = None - self._scene = None - self.processing = False ## flag that prevents recursive node updates - - self.widget() - - self.inputNode = Node('Input', allowRemove=False, allowAddOutput=True) - self.outputNode = Node('Output', allowRemove=False, allowAddInput=True) - self.addNode(self.inputNode, 'Input', [-150, 0]) - self.addNode(self.outputNode, 'Output', [300, 0]) - - self.outputNode.sigOutputChanged.connect(self.outputChanged) - self.outputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) - self.inputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) - self.outputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) - self.inputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) - self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) - self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) - - self.viewBox.autoRange(padding = 0.04) - - for name, opts in terminals.items(): - self.addTerminal(name, **opts) - - def setInput(self, **args): - """Set the input values of the flowchart. This will automatically propagate - the new values throughout the flowchart, (possibly) causing the output to change. - """ - #print "setInput", args - #Node.setInput(self, **args) - #print " ....." - self.inputWasSet = True - self.inputNode.setOutput(**args) - - def outputChanged(self): - ## called when output of internal node has changed - vals = self.outputNode.inputValues() - self.widget().outputChanged(vals) - self.setOutput(**vals) - #self.sigOutputChanged.emit(self) - - def output(self): - """Return a dict of the values on the Flowchart's output terminals. - """ - return self.outputNode.inputValues() - - def nodes(self): - return self._nodes - - def addTerminal(self, name, **opts): - term = Node.addTerminal(self, name, **opts) - name = term.name() - if opts['io'] == 'in': ## inputs to the flowchart become outputs on the input node - opts['io'] = 'out' - opts['multi'] = False - self.inputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) - try: - term2 = self.inputNode.addTerminal(name, **opts) - finally: - self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) - - else: - opts['io'] = 'in' - #opts['multi'] = False - self.outputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) - try: - term2 = self.outputNode.addTerminal(name, **opts) - finally: - self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) - return term - - def removeTerminal(self, name): - #print "remove:", name - term = self[name] - inTerm = self.internalTerminal(term) - Node.removeTerminal(self, name) - inTerm.node().removeTerminal(inTerm.name()) - - def internalTerminalRenamed(self, term, oldName): - self[oldName].rename(term.name()) - - def internalTerminalAdded(self, node, term): - if term._io == 'in': - io = 'out' - else: - io = 'in' - Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable()) - - def internalTerminalRemoved(self, node, term): - try: - Node.removeTerminal(self, term.name()) - except KeyError: - pass - - def terminalRenamed(self, term, oldName): - newName = term.name() - #print "flowchart rename", newName, oldName - #print self.terminals - Node.terminalRenamed(self, self[oldName], oldName) - #print self.terminals - for n in [self.inputNode, self.outputNode]: - if oldName in n.terminals: - n[oldName].rename(newName) - - def createNode(self, nodeType, name=None, pos=None): - if name is None: - n = 0 - while True: - name = "%s.%d" % (nodeType, n) - if name not in self._nodes: - break - n += 1 - - node = library.getNodeType(nodeType)(name) - self.addNode(node, name, pos) - return node - - def addNode(self, node, name, pos=None): - if pos is None: - pos = [0, 0] - if type(pos) in [QtCore.QPoint, QtCore.QPointF]: - pos = [pos.x(), pos.y()] - item = node.graphicsItem() - item.setZValue(self.nextZVal*2) - self.nextZVal += 1 - self.viewBox.addItem(item) - item.moveBy(*pos) - self._nodes[name] = node - self.widget().addNode(node) - node.sigClosed.connect(self.nodeClosed) - node.sigRenamed.connect(self.nodeRenamed) - node.sigOutputChanged.connect(self.nodeOutputChanged) - - def removeNode(self, node): - node.close() - - def nodeClosed(self, node): - del self._nodes[node.name()] - self.widget().removeNode(node) - try: - node.sigClosed.disconnect(self.nodeClosed) - except TypeError: - pass - try: - node.sigRenamed.disconnect(self.nodeRenamed) - except TypeError: - pass - try: - node.sigOutputChanged.disconnect(self.nodeOutputChanged) - except TypeError: - pass - - def nodeRenamed(self, node, oldName): - del self._nodes[oldName] - self._nodes[node.name()] = node - self.widget().nodeRenamed(node, oldName) - - def arrangeNodes(self): - pass - - def internalTerminal(self, term): - """If the terminal belongs to the external Node, return the corresponding internal terminal""" - if term.node() is self: - if term.isInput(): - return self.inputNode[term.name()] - else: - return self.outputNode[term.name()] - else: - return term - - def connectTerminals(self, term1, term2): - """Connect two terminals together within this flowchart.""" - term1 = self.internalTerminal(term1) - term2 = self.internalTerminal(term2) - term1.connectTo(term2) - - - def process(self, **args): - """ - Process data through the flowchart, returning the output. - - Keyword arguments must be the names of input terminals. - The return value is a dict with one key per output terminal. - - """ - data = {} ## Stores terminal:value pairs - - ## determine order of operations - ## order should look like [('p', node1), ('p', node2), ('d', terminal1), ...] - ## Each tuple specifies either (p)rocess this node or (d)elete the result from this terminal - order = self.processOrder() - #print "ORDER:", order - - ## Record inputs given to process() - for n, t in self.inputNode.outputs().items(): - if n not in args: - raise Exception("Parameter %s required to process this chart." % n) - data[t] = args[n] - - ret = {} - - ## process all in order - for c, arg in order: - - if c == 'p': ## Process a single node - #print "===> process:", arg - node = arg - if node is self.inputNode: - continue ## input node has already been processed. - - - ## get input and output terminals for this node - outs = list(node.outputs().values()) - ins = list(node.inputs().values()) - - ## construct input value dictionary - args = {} - for inp in ins: - inputs = inp.inputTerminals() - if len(inputs) == 0: - continue - if inp.isMultiValue(): ## multi-input terminals require a dict of all inputs - args[inp.name()] = dict([(i, data[i]) for i in inputs]) - else: ## single-inputs terminals only need the single input value available - args[inp.name()] = data[inputs[0]] - - if node is self.outputNode: - ret = args ## we now have the return value, but must keep processing in case there are other endpoint nodes in the chart - else: - try: - if node.isBypassed(): - result = node.processBypassed(args) - else: - result = node.process(display=False, **args) - except: - print("Error processing node %s. Args are: %s" % (str(node), str(args))) - raise - for out in outs: - #print " Output:", out, out.name() - #print out.name() - try: - data[out] = result[out.name()] - except: - print(out, out.name()) - raise - elif c == 'd': ## delete a terminal result (no longer needed; may be holding a lot of memory) - #print "===> delete", arg - if arg in data: - del data[arg] - - return ret - - def processOrder(self): - """Return the order of operations required to process this chart. - The order returned should look like [('p', node1), ('p', node2), ('d', terminal1), ...] - where each tuple specifies either (p)rocess this node or (d)elete the result from this terminal - """ - - ## first collect list of nodes/terminals and their dependencies - deps = {} - tdeps = {} ## {terminal: [nodes that depend on terminal]} - for name, node in self._nodes.items(): - deps[node] = node.dependentNodes() - for t in node.outputs().values(): - tdeps[t] = t.dependentNodes() - - #print "DEPS:", deps - ## determine correct node-processing order - #deps[self] = [] - order = toposort(deps) - #print "ORDER1:", order - - ## construct list of operations - ops = [('p', n) for n in order] - - ## determine when it is safe to delete terminal values - dels = [] - for t, nodes in tdeps.items(): - lastInd = 0 - lastNode = None - for n in nodes: ## determine which node is the last to be processed according to order - if n is self: - lastInd = None - break - else: - try: - ind = order.index(n) - except ValueError: - continue - if lastNode is None or ind > lastInd: - lastNode = n - lastInd = ind - #tdeps[t] = lastNode - if lastInd is not None: - dels.append((lastInd+1, t)) - #dels.sort(lambda a,b: cmp(b[0], a[0])) - dels.sort(key=lambda a: a[0], reverse=True) - for i, t in dels: - ops.insert(i, ('d', t)) - return ops - - - def nodeOutputChanged(self, startNode): - """Triggered when a node's output values have changed. (NOT called during process()) - Propagates new data forward through network.""" - ## first collect list of nodes/terminals and their dependencies - - if self.processing: - return - self.processing = True - try: - deps = {} - for name, node in self._nodes.items(): - deps[node] = [] - for t in node.outputs().values(): - deps[node].extend(t.dependentNodes()) - - ## determine order of updates - order = toposort(deps, nodes=[startNode]) - order.reverse() - - ## keep track of terminals that have been updated - terms = set(startNode.outputs().values()) - - #print "======= Updating", startNode - #print "Order:", order - for node in order[1:]: - #print "Processing node", node - for term in list(node.inputs().values()): - #print " checking terminal", term - deps = list(term.connections().keys()) - update = False - for d in deps: - if d in terms: - #print " ..input", d, "changed" - update = True - term.inputChanged(d, process=False) - if update: - #print " processing.." - node.update() - terms |= set(node.outputs().values()) - - finally: - self.processing = False - if self.inputWasSet: - self.inputWasSet = False - else: - self.sigStateChanged.emit() - - - - def chartGraphicsItem(self): - """Return the graphicsItem which displays the internals of this flowchart. - (graphicsItem() still returns the external-view item)""" - #return self._chartGraphicsItem - return self.viewBox - - def widget(self): - if self._widget is None: - self._widget = FlowchartCtrlWidget(self) - self.scene = self._widget.scene() - self.viewBox = self._widget.viewBox() - #self._scene = QtGui.QGraphicsScene() - #self._widget.setScene(self._scene) - #self.scene.addItem(self.chartGraphicsItem()) - - #ci = self.chartGraphicsItem() - #self.viewBox.addItem(ci) - #self.viewBox.autoRange() - return self._widget - - def listConnections(self): - conn = set() - for n in self._nodes.values(): - terms = n.outputs() - for n, t in terms.items(): - for c in t.connections(): - conn.add((t, c)) - return conn - - def saveState(self): - state = Node.saveState(self) - state['nodes'] = [] - state['connects'] = [] - #state['terminals'] = self.saveTerminals() - - for name, node in self._nodes.items(): - cls = type(node) - if hasattr(cls, 'nodeName'): - clsName = cls.nodeName - pos = node.graphicsItem().pos() - ns = {'class': clsName, 'name': name, 'pos': (pos.x(), pos.y()), 'state': node.saveState()} - state['nodes'].append(ns) - - conn = self.listConnections() - for a, b in conn: - state['connects'].append((a.node().name(), a.name(), b.node().name(), b.name())) - - state['inputNode'] = self.inputNode.saveState() - state['outputNode'] = self.outputNode.saveState() - - return state - - def restoreState(self, state, clear=False): - self.blockSignals(True) - try: - if clear: - self.clear() - Node.restoreState(self, state) - nodes = state['nodes'] - #nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0])) - nodes.sort(key=lambda a: a['pos'][0]) - for n in nodes: - if n['name'] in self._nodes: - #self._nodes[n['name']].graphicsItem().moveBy(*n['pos']) - self._nodes[n['name']].restoreState(n['state']) - continue - try: - node = self.createNode(n['class'], name=n['name']) - node.restoreState(n['state']) - except: - printExc("Error creating node %s: (continuing anyway)" % n['name']) - #node.graphicsItem().moveBy(*n['pos']) - - self.inputNode.restoreState(state.get('inputNode', {})) - self.outputNode.restoreState(state.get('outputNode', {})) - - #self.restoreTerminals(state['terminals']) - for n1, t1, n2, t2 in state['connects']: - try: - self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2]) - except: - print(self._nodes[n1].terminals) - print(self._nodes[n2].terminals) - printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2)) - - - finally: - self.blockSignals(False) - - self.sigChartLoaded.emit() - self.outputChanged() - self.sigStateChanged.emit() - #self.sigOutputChanged.emit() - - def loadFile(self, fileName=None, startDir=None): - if fileName is None: - if startDir is None: - startDir = self.filePath - if startDir is None: - startDir = '.' - self.fileDialog = pg.FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") - #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - #self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - self.fileDialog.show() - self.fileDialog.fileSelected.connect(self.loadFile) - return - ## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs.. - #fileName = QtGui.QFileDialog.getOpenFileName(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") - fileName = str(fileName) - state = configfile.readConfigFile(fileName) - self.restoreState(state, clear=True) - self.viewBox.autoRange() - #self.emit(QtCore.SIGNAL('fileLoaded'), fileName) - self.sigFileLoaded.emit(fileName) - - def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'): - if fileName is None: - if startDir is None: - startDir = self.filePath - if startDir is None: - startDir = '.' - self.fileDialog = pg.FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") - #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - #self.fileDialog.setDirectory(startDir) - self.fileDialog.show() - self.fileDialog.fileSelected.connect(self.saveFile) - return - #fileName = QtGui.QFileDialog.getSaveFileName(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") - fileName = str(fileName) - configfile.writeConfigFile(self.saveState(), fileName) - self.sigFileSaved.emit(fileName) - - def clear(self): - for n in list(self._nodes.values()): - if n is self.inputNode or n is self.outputNode: - continue - n.close() ## calls self.nodeClosed(n) by signal - #self.clearTerminals() - self.widget().clear() - - def clearTerminals(self): - Node.clearTerminals(self) - self.inputNode.clearTerminals() - self.outputNode.clearTerminals() - -#class FlowchartGraphicsItem(QtGui.QGraphicsItem): -class FlowchartGraphicsItem(GraphicsObject): - - def __init__(self, chart): - #print "FlowchartGraphicsItem.__init__" - #QtGui.QGraphicsItem.__init__(self) - GraphicsObject.__init__(self) - self.chart = chart ## chart is an instance of Flowchart() - self.updateTerminals() - - def updateTerminals(self): - #print "FlowchartGraphicsItem.updateTerminals" - self.terminals = {} - bounds = self.boundingRect() - inp = self.chart.inputs() - dy = bounds.height() / (len(inp)+1) - y = dy - for n, t in inp.items(): - item = t.graphicsItem() - self.terminals[n] = item - item.setParentItem(self) - item.setAnchor(bounds.width(), y) - y += dy - out = self.chart.outputs() - dy = bounds.height() / (len(out)+1) - y = dy - for n, t in out.items(): - item = t.graphicsItem() - self.terminals[n] = item - item.setParentItem(self) - item.setAnchor(0, y) - y += dy - - def boundingRect(self): - #print "FlowchartGraphicsItem.boundingRect" - return QtCore.QRectF() - - def paint(self, p, *args): - #print "FlowchartGraphicsItem.paint" - pass - #p.drawRect(self.boundingRect()) - - -class FlowchartCtrlWidget(QtGui.QWidget): - """The widget that contains the list of all the nodes in a flowchart and their controls, as well as buttons for loading/saving flowcharts.""" - - def __init__(self, chart): - self.items = {} - #self.loadDir = loadDir ## where to look initially for chart files - self.currentFileName = None - QtGui.QWidget.__init__(self) - self.chart = chart - self.ui = FlowchartCtrlTemplate.Ui_Form() - self.ui.setupUi(self) - self.ui.ctrlList.setColumnCount(2) - #self.ui.ctrlList.setColumnWidth(0, 200) - self.ui.ctrlList.setColumnWidth(1, 20) - self.ui.ctrlList.setVerticalScrollMode(self.ui.ctrlList.ScrollPerPixel) - self.ui.ctrlList.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - - self.chartWidget = FlowchartWidget(chart, self) - #self.chartWidget.viewBox().autoRange() - self.cwWin = QtGui.QMainWindow() - self.cwWin.setWindowTitle('Flowchart') - self.cwWin.setCentralWidget(self.chartWidget) - self.cwWin.resize(1000,800) - - h = self.ui.ctrlList.header() - h.setResizeMode(0, h.Stretch) - - self.ui.ctrlList.itemChanged.connect(self.itemChanged) - self.ui.loadBtn.clicked.connect(self.loadClicked) - self.ui.saveBtn.clicked.connect(self.saveClicked) - self.ui.saveAsBtn.clicked.connect(self.saveAsClicked) - self.ui.showChartBtn.toggled.connect(self.chartToggled) - self.chart.sigFileLoaded.connect(self.setCurrentFile) - self.ui.reloadBtn.clicked.connect(self.reloadClicked) - self.chart.sigFileSaved.connect(self.fileSaved) - - - - #def resizeEvent(self, ev): - #QtGui.QWidget.resizeEvent(self, ev) - #self.ui.ctrlList.setColumnWidth(0, self.ui.ctrlList.viewport().width()-20) - - def chartToggled(self, b): - if b: - self.cwWin.show() - else: - self.cwWin.hide() - - def reloadClicked(self): - try: - self.chartWidget.reloadLibrary() - self.ui.reloadBtn.success("Reloaded.") - except: - self.ui.reloadBtn.success("Error.") - raise - - - def loadClicked(self): - newFile = self.chart.loadFile() - #self.setCurrentFile(newFile) - - def fileSaved(self, fileName): - self.setCurrentFile(str(fileName)) - self.ui.saveBtn.success("Saved.") - - def saveClicked(self): - if self.currentFileName is None: - self.saveAsClicked() - else: - try: - self.chart.saveFile(self.currentFileName) - #self.ui.saveBtn.success("Saved.") - except: - self.ui.saveBtn.failure("Error") - raise - - def saveAsClicked(self): - try: - if self.currentFileName is None: - newFile = self.chart.saveFile() - else: - newFile = self.chart.saveFile(suggestedFileName=self.currentFileName) - #self.ui.saveAsBtn.success("Saved.") - #print "Back to saveAsClicked." - except: - self.ui.saveBtn.failure("Error") - raise - - #self.setCurrentFile(newFile) - - def setCurrentFile(self, fileName): - self.currentFileName = str(fileName) - if fileName is None: - self.ui.fileNameLabel.setText("[ new ]") - else: - self.ui.fileNameLabel.setText("%s" % os.path.split(self.currentFileName)[1]) - self.resizeEvent(None) - - def itemChanged(self, *args): - pass - - def scene(self): - return self.chartWidget.scene() ## returns the GraphicsScene object - - def viewBox(self): - return self.chartWidget.viewBox() - - def nodeRenamed(self, node, oldName): - self.items[node].setText(0, node.name()) - - def addNode(self, node): - ctrl = node.ctrlWidget() - #if ctrl is None: - #return - item = QtGui.QTreeWidgetItem([node.name(), '', '']) - self.ui.ctrlList.addTopLevelItem(item) - byp = QtGui.QPushButton('X') - byp.setCheckable(True) - byp.setFixedWidth(20) - item.bypassBtn = byp - self.ui.ctrlList.setItemWidget(item, 1, byp) - byp.node = node - node.bypassButton = byp - byp.setChecked(node.isBypassed()) - byp.clicked.connect(self.bypassClicked) - - if ctrl is not None: - item2 = QtGui.QTreeWidgetItem() - item.addChild(item2) - self.ui.ctrlList.setItemWidget(item2, 0, ctrl) - - self.items[node] = item - - def removeNode(self, node): - if node in self.items: - item = self.items[node] - #self.disconnect(item.bypassBtn, QtCore.SIGNAL('clicked()'), self.bypassClicked) - try: - item.bypassBtn.clicked.disconnect(self.bypassClicked) - except TypeError: - pass - self.ui.ctrlList.removeTopLevelItem(item) - - def bypassClicked(self): - btn = QtCore.QObject.sender(self) - btn.node.bypass(btn.isChecked()) - - def chartWidget(self): - return self.chartWidget - - def outputChanged(self, data): - pass - #self.ui.outputTree.setData(data, hideRoot=True) - - def clear(self): - self.chartWidget.clear() - - def select(self, node): - item = self.items[node] - self.ui.ctrlList.setCurrentItem(item) - -class FlowchartWidget(dockarea.DockArea): - """Includes the actual graphical flowchart and debugging interface""" - def __init__(self, chart, ctrl): - #QtGui.QWidget.__init__(self) - dockarea.DockArea.__init__(self) - self.chart = chart - self.ctrl = ctrl - self.hoverItem = None - #self.setMinimumWidth(250) - #self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)) - - #self.ui = FlowchartTemplate.Ui_Form() - #self.ui.setupUi(self) - - ## build user interface (it was easier to do it here than via developer) - self.view = FlowchartGraphicsView.FlowchartGraphicsView(self) - self.viewDock = dockarea.Dock('view', size=(1000,600)) - self.viewDock.addWidget(self.view) - self.viewDock.hideTitleBar() - self.addDock(self.viewDock) - - - self.hoverText = QtGui.QTextEdit() - self.hoverText.setReadOnly(True) - self.hoverDock = dockarea.Dock('Hover Info', size=(1000,20)) - self.hoverDock.addWidget(self.hoverText) - self.addDock(self.hoverDock, 'bottom') - - self.selInfo = QtGui.QWidget() - self.selInfoLayout = QtGui.QGridLayout() - self.selInfo.setLayout(self.selInfoLayout) - self.selDescLabel = QtGui.QLabel() - self.selNameLabel = QtGui.QLabel() - self.selDescLabel.setWordWrap(True) - self.selectedTree = pg.DataTreeWidget() - #self.selectedTree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) - #self.selInfoLayout.addWidget(self.selNameLabel) - self.selInfoLayout.addWidget(self.selDescLabel) - self.selInfoLayout.addWidget(self.selectedTree) - self.selDock = dockarea.Dock('Selected Node', size=(1000,200)) - self.selDock.addWidget(self.selInfo) - self.addDock(self.selDock, 'bottom') - - self._scene = self.view.scene() - self._viewBox = self.view.viewBox() - #self._scene = QtGui.QGraphicsScene() - #self._scene = FlowchartGraphicsView.FlowchartGraphicsScene() - #self.view.setScene(self._scene) - - self.buildMenu() - #self.ui.addNodeBtn.mouseReleaseEvent = self.addNodeBtnReleased - - self._scene.selectionChanged.connect(self.selectionChanged) - self._scene.sigMouseHover.connect(self.hoverOver) - #self.view.sigClicked.connect(self.showViewMenu) - #self._scene.sigSceneContextMenu.connect(self.showViewMenu) - #self._viewBox.sigActionPositionChanged.connect(self.menuPosChanged) - - - def reloadLibrary(self): - #QtCore.QObject.disconnect(self.nodeMenu, QtCore.SIGNAL('triggered(QAction*)'), self.nodeMenuTriggered) - self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered) - self.nodeMenu = None - self.subMenus = [] - library.loadLibrary(reloadLibs=True) - self.buildMenu() - - def buildMenu(self, pos=None): - self.nodeMenu = QtGui.QMenu() - self.subMenus = [] - for section, nodes in library.getNodeTree().items(): - menu = QtGui.QMenu(section) - self.nodeMenu.addMenu(menu) - for name in nodes: - act = menu.addAction(name) - act.nodeType = name - act.pos = pos - self.subMenus.append(menu) - self.nodeMenu.triggered.connect(self.nodeMenuTriggered) - return self.nodeMenu - - def menuPosChanged(self, pos): - self.menuPos = pos - - def showViewMenu(self, ev): - #QtGui.QPushButton.mouseReleaseEvent(self.ui.addNodeBtn, ev) - #if ev.button() == QtCore.Qt.RightButton: - #self.menuPos = self.view.mapToScene(ev.pos()) - #self.nodeMenu.popup(ev.globalPos()) - #print "Flowchart.showViewMenu called" - - #self.menuPos = ev.scenePos() - self.buildMenu(ev.scenePos()) - self.nodeMenu.popup(ev.screenPos()) - - def scene(self): - return self._scene ## the GraphicsScene item - - def viewBox(self): - return self._viewBox ## the viewBox that items should be added to - - def nodeMenuTriggered(self, action): - nodeType = action.nodeType - if action.pos is not None: - pos = action.pos - else: - pos = self.menuPos - pos = self.viewBox().mapSceneToView(pos) - - self.chart.createNode(nodeType, pos=pos) - - - def selectionChanged(self): - #print "FlowchartWidget.selectionChanged called." - items = self._scene.selectedItems() - #print " scene.selectedItems: ", items - if len(items) == 0: - data = None - else: - item = items[0] - if hasattr(item, 'node') and isinstance(item.node, Node): - n = item.node - self.ctrl.select(n) - data = {'outputs': n.outputValues(), 'inputs': n.inputValues()} - self.selNameLabel.setText(n.name()) - if hasattr(n, 'nodeName'): - self.selDescLabel.setText("%s: %s" % (n.nodeName, n.__class__.__doc__)) - else: - self.selDescLabel.setText("") - if n.exception is not None: - data['exception'] = n.exception - else: - data = None - self.selectedTree.setData(data, hideRoot=True) - - def hoverOver(self, items): - #print "FlowchartWidget.hoverOver called." - term = None - for item in items: - if item is self.hoverItem: - return - self.hoverItem = item - if hasattr(item, 'term') and isinstance(item.term, Terminal): - term = item.term - break - if term is None: - self.hoverText.setPlainText("") - else: - val = term.value() - if isinstance(val, ndarray): - val = "%s %s %s" % (type(val).__name__, str(val.shape), str(val.dtype)) - else: - val = str(val) - if len(val) > 400: - val = val[:400] + "..." - self.hoverText.setPlainText("%s.%s = %s" % (term.node().name(), term.name(), val)) - #self.hoverLabel.setCursorPosition(0) - - - - def clear(self): - #self.outputTree.setData(None) - self.selectedTree.setData(None) - self.hoverText.setPlainText('') - self.selNameLabel.setText('') - self.selDescLabel.setText('') - - -class FlowchartNode(Node): - pass - diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui b/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui deleted file mode 100644 index 610846b6..00000000 --- a/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui +++ /dev/null @@ -1,120 +0,0 @@ - - - Form - - - - 0 - 0 - 217 - 499 - - - - Form - - - - 0 - - - 0 - - - - - Load.. - - - - - - - - - - - - - - - - Flowchart - - - true - - - - - - - false - - - false - - - false - - - false - - - - 1 - - - - - - - - - 75 - true - - - - - - - Qt::AlignCenter - - - - - - - - TreeWidget - QTreeWidget -
pyqtgraph.widgets.TreeWidget
-
- - FeedbackButton - QPushButton -
pyqtgraph.widgets.FeedbackButton
-
-
- - -
diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py deleted file mode 100644 index 0410cdf3..00000000 --- a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py +++ /dev/null @@ -1,71 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './flowchart/FlowchartCtrlTemplate.ui' -# -# Created: Sun Sep 9 14:41:30 2012 -# by: PyQt4 UI code generator 4.9.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName(_fromUtf8("Form")) - Form.resize(217, 499) - self.gridLayout = QtGui.QGridLayout(Form) - self.gridLayout.setMargin(0) - self.gridLayout.setVerticalSpacing(0) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.loadBtn = QtGui.QPushButton(Form) - self.loadBtn.setObjectName(_fromUtf8("loadBtn")) - self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) - self.saveBtn = FeedbackButton(Form) - self.saveBtn.setObjectName(_fromUtf8("saveBtn")) - self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) - self.saveAsBtn = FeedbackButton(Form) - self.saveAsBtn.setObjectName(_fromUtf8("saveAsBtn")) - self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) - self.reloadBtn = FeedbackButton(Form) - self.reloadBtn.setCheckable(False) - self.reloadBtn.setFlat(False) - self.reloadBtn.setObjectName(_fromUtf8("reloadBtn")) - self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) - self.showChartBtn = QtGui.QPushButton(Form) - self.showChartBtn.setCheckable(True) - self.showChartBtn.setObjectName(_fromUtf8("showChartBtn")) - self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) - self.ctrlList = TreeWidget(Form) - self.ctrlList.setObjectName(_fromUtf8("ctrlList")) - self.ctrlList.headerItem().setText(0, _fromUtf8("1")) - self.ctrlList.header().setVisible(False) - self.ctrlList.header().setStretchLastSection(False) - self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) - self.fileNameLabel = QtGui.QLabel(Form) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.fileNameLabel.setFont(font) - self.fileNameLabel.setText(_fromUtf8("")) - self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) - self.fileNameLabel.setObjectName(_fromUtf8("fileNameLabel")) - self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load..", None, QtGui.QApplication.UnicodeUTF8)) - self.saveBtn.setText(QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8)) - self.saveAsBtn.setText(QtGui.QApplication.translate("Form", "As..", None, QtGui.QApplication.UnicodeUTF8)) - self.reloadBtn.setText(QtGui.QApplication.translate("Form", "Reload Libs", None, QtGui.QApplication.UnicodeUTF8)) - self.showChartBtn.setText(QtGui.QApplication.translate("Form", "Flowchart", None, QtGui.QApplication.UnicodeUTF8)) - -from pyqtgraph.widgets.FeedbackButton import FeedbackButton -from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py b/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py deleted file mode 100644 index f579c957..00000000 --- a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './flowchart/FlowchartCtrlTemplate.ui' -# -# Created: Sun Sep 9 14:41:30 2012 -# by: pyside-uic 0.2.13 running on PySide 1.1.0 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(217, 499) - self.gridLayout = QtGui.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setVerticalSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.loadBtn = QtGui.QPushButton(Form) - self.loadBtn.setObjectName("loadBtn") - self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) - self.saveBtn = FeedbackButton(Form) - self.saveBtn.setObjectName("saveBtn") - self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) - self.saveAsBtn = FeedbackButton(Form) - self.saveAsBtn.setObjectName("saveAsBtn") - self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) - self.reloadBtn = FeedbackButton(Form) - self.reloadBtn.setCheckable(False) - self.reloadBtn.setFlat(False) - self.reloadBtn.setObjectName("reloadBtn") - self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) - self.showChartBtn = QtGui.QPushButton(Form) - self.showChartBtn.setCheckable(True) - self.showChartBtn.setObjectName("showChartBtn") - self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) - self.ctrlList = TreeWidget(Form) - self.ctrlList.setObjectName("ctrlList") - self.ctrlList.headerItem().setText(0, "1") - self.ctrlList.header().setVisible(False) - self.ctrlList.header().setStretchLastSection(False) - self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) - self.fileNameLabel = QtGui.QLabel(Form) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.fileNameLabel.setFont(font) - self.fileNameLabel.setText("") - self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) - self.fileNameLabel.setObjectName("fileNameLabel") - self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load..", None, QtGui.QApplication.UnicodeUTF8)) - self.saveBtn.setText(QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8)) - self.saveAsBtn.setText(QtGui.QApplication.translate("Form", "As..", None, QtGui.QApplication.UnicodeUTF8)) - self.reloadBtn.setText(QtGui.QApplication.translate("Form", "Reload Libs", None, QtGui.QApplication.UnicodeUTF8)) - self.showChartBtn.setText(QtGui.QApplication.translate("Form", "Flowchart", None, QtGui.QApplication.UnicodeUTF8)) - -from pyqtgraph.widgets.FeedbackButton import FeedbackButton -from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/pyqtgraph/flowchart/FlowchartGraphicsView.py b/pyqtgraph/flowchart/FlowchartGraphicsView.py deleted file mode 100644 index 0ec4d5c8..00000000 --- a/pyqtgraph/flowchart/FlowchartGraphicsView.py +++ /dev/null @@ -1,109 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.widgets.GraphicsView import GraphicsView -from pyqtgraph.GraphicsScene import GraphicsScene -from pyqtgraph.graphicsItems.ViewBox import ViewBox - -#class FlowchartGraphicsView(QtGui.QGraphicsView): -class FlowchartGraphicsView(GraphicsView): - - sigHoverOver = QtCore.Signal(object) - sigClicked = QtCore.Signal(object) - - def __init__(self, widget, *args): - #QtGui.QGraphicsView.__init__(self, *args) - GraphicsView.__init__(self, *args, useOpenGL=False) - #self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(255,255,255))) - self._vb = FlowchartViewBox(widget, lockAspect=True, invertY=True) - self.setCentralItem(self._vb) - #self.scene().addItem(self.vb) - #self.setMouseTracking(True) - #self.lastPos = None - #self.setTransformationAnchor(self.AnchorViewCenter) - #self.setRenderHints(QtGui.QPainter.Antialiasing) - self.setRenderHint(QtGui.QPainter.Antialiasing, True) - #self.setDragMode(QtGui.QGraphicsView.RubberBandDrag) - #self.setRubberBandSelectionMode(QtCore.Qt.ContainsItemBoundingRect) - - def viewBox(self): - return self._vb - - - #def mousePressEvent(self, ev): - #self.moved = False - #self.lastPos = ev.pos() - #return QtGui.QGraphicsView.mousePressEvent(self, ev) - - #def mouseMoveEvent(self, ev): - #self.moved = True - #callSuper = False - #if ev.buttons() & QtCore.Qt.RightButton: - #if self.lastPos is not None: - #dif = ev.pos() - self.lastPos - #self.scale(1.01**-dif.y(), 1.01**-dif.y()) - #elif ev.buttons() & QtCore.Qt.MidButton: - #if self.lastPos is not None: - #dif = ev.pos() - self.lastPos - #self.translate(dif.x(), -dif.y()) - #else: - ##self.emit(QtCore.SIGNAL('hoverOver'), self.items(ev.pos())) - #self.sigHoverOver.emit(self.items(ev.pos())) - #callSuper = True - #self.lastPos = ev.pos() - - #if callSuper: - #QtGui.QGraphicsView.mouseMoveEvent(self, ev) - - #def mouseReleaseEvent(self, ev): - #if not self.moved: - ##self.emit(QtCore.SIGNAL('clicked'), ev) - #self.sigClicked.emit(ev) - #return QtGui.QGraphicsView.mouseReleaseEvent(self, ev) - -class FlowchartViewBox(ViewBox): - - def __init__(self, widget, *args, **kwargs): - ViewBox.__init__(self, *args, **kwargs) - self.widget = widget - #self.menu = None - #self._subMenus = None ## need a place to store the menus otherwise they dissappear (even though they've been added to other menus) ((yes, it doesn't make sense)) - - - - - def getMenu(self, ev): - ## called by ViewBox to create a new context menu - self._fc_menu = QtGui.QMenu() - self._subMenus = self.getContextMenus(ev) - for menu in self._subMenus: - self._fc_menu.addMenu(menu) - return self._fc_menu - - def getContextMenus(self, ev): - ## called by scene to add menus on to someone else's context menu - menu = self.widget.buildMenu(ev.scenePos()) - menu.setTitle("Add node") - return [menu, ViewBox.getMenu(self, ev)] - - - - - - - - - - -##class FlowchartGraphicsScene(QtGui.QGraphicsScene): -#class FlowchartGraphicsScene(GraphicsScene): - - #sigContextMenuEvent = QtCore.Signal(object) - - #def __init__(self, *args): - ##QtGui.QGraphicsScene.__init__(self, *args) - #GraphicsScene.__init__(self, *args) - - #def mouseClickEvent(self, ev): - ##QtGui.QGraphicsScene.contextMenuEvent(self, ev) - #if not ev.button() in [QtCore.Qt.RightButton]: - #self.sigContextMenuEvent.emit(ev) \ No newline at end of file diff --git a/pyqtgraph/flowchart/FlowchartTemplate.ui b/pyqtgraph/flowchart/FlowchartTemplate.ui deleted file mode 100644 index 31b1359c..00000000 --- a/pyqtgraph/flowchart/FlowchartTemplate.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - Form - - - - 0 - 0 - 529 - 329 - - - - Form - - - - - 260 - 10 - 264 - 222 - - - - - - - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - - 75 - true - - - - - - - - - - - - 1 - - - - - - - - - - 0 - 240 - 521 - 81 - - - - - - - 0 - 0 - 256 - 192 - - - - - - - DataTreeWidget - QTreeWidget -
pyqtgraph.widgets.DataTreeWidget
-
- - FlowchartGraphicsView - QGraphicsView -
pyqtgraph.flowchart.FlowchartGraphicsView
-
-
- - -
diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py b/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py deleted file mode 100644 index c07dd734..00000000 --- a/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py +++ /dev/null @@ -1,59 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui' -# -# Created: Sun Feb 24 19:47:29 2013 -# by: PyQt4 UI code generator 4.9.3 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName(_fromUtf8("Form")) - Form.resize(529, 329) - self.selInfoWidget = QtGui.QWidget(Form) - self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) - self.selInfoWidget.setObjectName(_fromUtf8("selInfoWidget")) - self.gridLayout = QtGui.QGridLayout(self.selInfoWidget) - self.gridLayout.setMargin(0) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.selDescLabel = QtGui.QLabel(self.selInfoWidget) - self.selDescLabel.setText(_fromUtf8("")) - self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) - self.selDescLabel.setWordWrap(True) - self.selDescLabel.setObjectName(_fromUtf8("selDescLabel")) - self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) - self.selNameLabel = QtGui.QLabel(self.selInfoWidget) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.selNameLabel.setFont(font) - self.selNameLabel.setText(_fromUtf8("")) - self.selNameLabel.setObjectName(_fromUtf8("selNameLabel")) - self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) - self.selectedTree = DataTreeWidget(self.selInfoWidget) - self.selectedTree.setObjectName(_fromUtf8("selectedTree")) - self.selectedTree.headerItem().setText(0, _fromUtf8("1")) - self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) - self.hoverText = QtGui.QTextEdit(Form) - self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) - self.hoverText.setObjectName(_fromUtf8("hoverText")) - self.view = FlowchartGraphicsView(Form) - self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) - self.view.setObjectName(_fromUtf8("view")) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - -from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget -from pyqtgraph.flowchart.FlowchartGraphicsView import FlowchartGraphicsView diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyside.py b/pyqtgraph/flowchart/FlowchartTemplate_pyside.py deleted file mode 100644 index c73f3c00..00000000 --- a/pyqtgraph/flowchart/FlowchartTemplate_pyside.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui' -# -# Created: Sun Feb 24 19:47:30 2013 -# by: pyside-uic 0.2.13 running on PySide 1.1.1 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(529, 329) - self.selInfoWidget = QtGui.QWidget(Form) - self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) - self.selInfoWidget.setObjectName("selInfoWidget") - self.gridLayout = QtGui.QGridLayout(self.selInfoWidget) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.selDescLabel = QtGui.QLabel(self.selInfoWidget) - self.selDescLabel.setText("") - self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) - self.selDescLabel.setWordWrap(True) - self.selDescLabel.setObjectName("selDescLabel") - self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) - self.selNameLabel = QtGui.QLabel(self.selInfoWidget) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.selNameLabel.setFont(font) - self.selNameLabel.setText("") - self.selNameLabel.setObjectName("selNameLabel") - self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) - self.selectedTree = DataTreeWidget(self.selInfoWidget) - self.selectedTree.setObjectName("selectedTree") - self.selectedTree.headerItem().setText(0, "1") - self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) - self.hoverText = QtGui.QTextEdit(Form) - self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) - self.hoverText.setObjectName("hoverText") - self.view = FlowchartGraphicsView(Form) - self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) - self.view.setObjectName("view") - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - -from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget -from pyqtgraph.flowchart.FlowchartGraphicsView import FlowchartGraphicsView diff --git a/pyqtgraph/flowchart/Node.py b/pyqtgraph/flowchart/Node.py deleted file mode 100644 index cd73b42b..00000000 --- a/pyqtgraph/flowchart/Node.py +++ /dev/null @@ -1,647 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtCore, QtGui -from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject -import pyqtgraph.functions as fn -from .Terminal import * -from pyqtgraph.pgcollections import OrderedDict -from pyqtgraph.debug import * -import numpy as np -from .eq import * - - -def strDict(d): - return dict([(str(k), v) for k, v in d.items()]) - -class Node(QtCore.QObject): - """ - Node represents the basic processing unit of a flowchart. - A Node subclass implements at least: - - 1) A list of input / ouptut terminals and their properties - 2) a process() function which takes the names of input terminals as keyword arguments and returns a dict with the names of output terminals as keys. - - A flowchart thus consists of multiple instances of Node subclasses, each of which is connected - to other by wires between their terminals. A flowchart is, itself, also a special subclass of Node. - This allows Nodes within the flowchart to connect to the input/output nodes of the flowchart itself. - - Optionally, a node class can implement the ctrlWidget() method, which must return a QWidget (usually containing other widgets) that will be displayed in the flowchart control panel. Some nodes implement fairly complex control widgets, but most nodes follow a simple form-like pattern: a list of parameter names and a single value (represented as spin box, check box, etc..) for each parameter. To make this easier, the CtrlNode subclass allows you to instead define a simple data structure that CtrlNode will use to automatically generate the control widget. """ - - sigOutputChanged = QtCore.Signal(object) # self - sigClosed = QtCore.Signal(object) - sigRenamed = QtCore.Signal(object, object) - sigTerminalRenamed = QtCore.Signal(object, object) # term, oldName - sigTerminalAdded = QtCore.Signal(object, object) # self, term - sigTerminalRemoved = QtCore.Signal(object, object) # self, term - - - def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True): - """ - ============== ============================================================ - Arguments - name The name of this specific node instance. It can be any - string, but must be unique within a flowchart. Usually, - we simply let the flowchart decide on a name when calling - Flowchart.addNode(...) - terminals Dict-of-dicts specifying the terminals present on this Node. - Terminal specifications look like:: - - 'inputTerminalName': {'io': 'in'} - 'outputTerminalName': {'io': 'out'} - - There are a number of optional parameters for terminals: - multi, pos, renamable, removable, multiable, bypass. See - the Terminal class for more information. - allowAddInput bool; whether the user is allowed to add inputs by the - context menu. - allowAddOutput bool; whether the user is allowed to add outputs by the - context menu. - allowRemove bool; whether the user is allowed to remove this node by the - context menu. - ============== ============================================================ - - """ - QtCore.QObject.__init__(self) - self._name = name - self._bypass = False - self.bypassButton = None ## this will be set by the flowchart ctrl widget.. - self._graphicsItem = None - self.terminals = OrderedDict() - self._inputs = OrderedDict() - self._outputs = OrderedDict() - self._allowAddInput = allowAddInput ## flags to allow the user to add/remove terminals - self._allowAddOutput = allowAddOutput - self._allowRemove = allowRemove - - self.exception = None - if terminals is None: - return - for name, opts in terminals.items(): - self.addTerminal(name, **opts) - - - def nextTerminalName(self, name): - """Return an unused terminal name""" - name2 = name - i = 1 - while name2 in self.terminals: - name2 = "%s.%d" % (name, i) - i += 1 - return name2 - - def addInput(self, name="Input", **args): - """Add a new input terminal to this Node with the given name. Extra - keyword arguments are passed to Terminal.__init__. - - This is a convenience function that just calls addTerminal(io='in', ...)""" - #print "Node.addInput called." - return self.addTerminal(name, io='in', **args) - - def addOutput(self, name="Output", **args): - """Add a new output terminal to this Node with the given name. Extra - keyword arguments are passed to Terminal.__init__. - - This is a convenience function that just calls addTerminal(io='out', ...)""" - return self.addTerminal(name, io='out', **args) - - def removeTerminal(self, term): - """Remove the specified terminal from this Node. May specify either the - terminal's name or the terminal itself. - - Causes sigTerminalRemoved to be emitted.""" - if isinstance(term, Terminal): - name = term.name() - else: - name = term - term = self.terminals[name] - - #print "remove", name - #term.disconnectAll() - term.close() - del self.terminals[name] - if name in self._inputs: - del self._inputs[name] - if name in self._outputs: - del self._outputs[name] - self.graphicsItem().updateTerminals() - self.sigTerminalRemoved.emit(self, term) - - - def terminalRenamed(self, term, oldName): - """Called after a terminal has been renamed - - Causes sigTerminalRenamed to be emitted.""" - newName = term.name() - for d in [self.terminals, self._inputs, self._outputs]: - if oldName not in d: - continue - d[newName] = d[oldName] - del d[oldName] - - self.graphicsItem().updateTerminals() - self.sigTerminalRenamed.emit(term, oldName) - - def addTerminal(self, name, **opts): - """Add a new terminal to this Node with the given name. Extra - keyword arguments are passed to Terminal.__init__. - - Causes sigTerminalAdded to be emitted.""" - name = self.nextTerminalName(name) - term = Terminal(self, name, **opts) - self.terminals[name] = term - if term.isInput(): - self._inputs[name] = term - elif term.isOutput(): - self._outputs[name] = term - self.graphicsItem().updateTerminals() - self.sigTerminalAdded.emit(self, term) - return term - - - def inputs(self): - """Return dict of all input terminals. - Warning: do not modify.""" - return self._inputs - - def outputs(self): - """Return dict of all output terminals. - Warning: do not modify.""" - return self._outputs - - def process(self, **kargs): - """Process data through this node. This method is called any time the flowchart - wants the node to process data. It will be called with one keyword argument - corresponding to each input terminal, and must return a dict mapping the name - of each output terminal to its new value. - - This method is also called with a 'display' keyword argument, which indicates - whether the node should update its display (if it implements any) while processing - this data. This is primarily used to disable expensive display operations - during batch processing. - """ - return {} - - def graphicsItem(self): - """Return the GraphicsItem for this node. Subclasses may re-implement - this method to customize their appearance in the flowchart.""" - if self._graphicsItem is None: - self._graphicsItem = NodeGraphicsItem(self) - return self._graphicsItem - - ## this is just bad planning. Causes too many bugs. - def __getattr__(self, attr): - """Return the terminal with the given name""" - if attr not in self.terminals: - raise AttributeError(attr) - else: - import traceback - traceback.print_stack() - print("Warning: use of node.terminalName is deprecated; use node['terminalName'] instead.") - return self.terminals[attr] - - def __getitem__(self, item): - #return getattr(self, item) - """Return the terminal with the given name""" - if item not in self.terminals: - raise KeyError(item) - else: - return self.terminals[item] - - def name(self): - """Return the name of this node.""" - return self._name - - def rename(self, name): - """Rename this node. This will cause sigRenamed to be emitted.""" - oldName = self._name - self._name = name - #self.emit(QtCore.SIGNAL('renamed'), self, oldName) - self.sigRenamed.emit(self, oldName) - - def dependentNodes(self): - """Return the list of nodes which provide direct input to this node""" - nodes = set() - for t in self.inputs().values(): - nodes |= set([i.node() for i in t.inputTerminals()]) - return nodes - #return set([t.inputTerminals().node() for t in self.listInputs().itervalues()]) - - def __repr__(self): - return "" % (self.name(), id(self)) - - def ctrlWidget(self): - """Return this Node's control widget. - - By default, Nodes have no control widget. Subclasses may reimplement this - method to provide a custom widget. This method is called by Flowcharts - when they are constructing their Node list.""" - return None - - def bypass(self, byp): - """Set whether this node should be bypassed. - - When bypassed, a Node's process() method is never called. In some cases, - data is automatically copied directly from specific input nodes to - output nodes instead (see the bypass argument to Terminal.__init__). - This is usually called when the user disables a node from the flowchart - control panel. - """ - self._bypass = byp - if self.bypassButton is not None: - self.bypassButton.setChecked(byp) - self.update() - - def isBypassed(self): - """Return True if this Node is currently bypassed.""" - return self._bypass - - def setInput(self, **args): - """Set the values on input terminals. For most nodes, this will happen automatically through Terminal.inputChanged. - This is normally only used for nodes with no connected inputs.""" - changed = False - for k, v in args.items(): - term = self._inputs[k] - oldVal = term.value() - if not eq(oldVal, v): - changed = True - term.setValue(v, process=False) - if changed and '_updatesHandled_' not in args: - self.update() - - def inputValues(self): - """Return a dict of all input values currently assigned to this node.""" - vals = {} - for n, t in self.inputs().items(): - vals[n] = t.value() - return vals - - def outputValues(self): - """Return a dict of all output values currently generated by this node.""" - vals = {} - for n, t in self.outputs().items(): - vals[n] = t.value() - return vals - - def connected(self, localTerm, remoteTerm): - """Called whenever one of this node's terminals is connected elsewhere.""" - pass - - def disconnected(self, localTerm, remoteTerm): - """Called whenever one of this node's terminals is disconnected from another.""" - pass - - def update(self, signal=True): - """Collect all input values, attempt to process new output values, and propagate downstream. - Subclasses should call update() whenever thir internal state has changed - (such as when the user interacts with the Node's control widget). Update - is automatically called when the inputs to the node are changed. - """ - vals = self.inputValues() - #print " inputs:", vals - try: - if self.isBypassed(): - out = self.processBypassed(vals) - else: - out = self.process(**strDict(vals)) - #print " output:", out - if out is not None: - if signal: - self.setOutput(**out) - else: - self.setOutputNoSignal(**out) - for n,t in self.inputs().items(): - t.setValueAcceptable(True) - self.clearException() - except: - #printExc( "Exception while processing %s:" % self.name()) - for n,t in self.outputs().items(): - t.setValue(None) - self.setException(sys.exc_info()) - - if signal: - #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data - self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data - - def processBypassed(self, args): - """Called when the flowchart would normally call Node.process, but this node is currently bypassed. - The default implementation looks for output terminals with a bypass connection and returns the - corresponding values. Most Node subclasses will _not_ need to reimplement this method.""" - result = {} - for term in list(self.outputs().values()): - byp = term.bypassValue() - if byp is None: - result[term.name()] = None - else: - result[term.name()] = args.get(byp, None) - return result - - def setOutput(self, **vals): - self.setOutputNoSignal(**vals) - #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data - self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data - - def setOutputNoSignal(self, **vals): - for k, v in vals.items(): - term = self.outputs()[k] - term.setValue(v) - #targets = term.connections() - #for t in targets: ## propagate downstream - #if t is term: - #continue - #t.inputChanged(term) - term.setValueAcceptable(True) - - def setException(self, exc): - self.exception = exc - self.recolor() - - def clearException(self): - self.setException(None) - - def recolor(self): - if self.exception is None: - self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(0, 0, 0))) - else: - self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(150, 0, 0), 3)) - - def saveState(self): - """Return a dictionary representing the current state of this node - (excluding input / output values). This is used for saving/reloading - flowcharts. The default implementation returns this Node's position, - bypass state, and information about each of its terminals. - - Subclasses may want to extend this method, adding extra keys to the returned - dict.""" - pos = self.graphicsItem().pos() - state = {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()} - termsEditable = self._allowAddInput | self._allowAddOutput - for term in self._inputs.values() + self._outputs.values(): - termsEditable |= term._renamable | term._removable | term._multiable - if termsEditable: - state['terminals'] = self.saveTerminals() - return state - - def restoreState(self, state): - """Restore the state of this node from a structure previously generated - by saveState(). """ - pos = state.get('pos', (0,0)) - self.graphicsItem().setPos(*pos) - self.bypass(state.get('bypass', False)) - if 'terminals' in state: - self.restoreTerminals(state['terminals']) - - def saveTerminals(self): - terms = OrderedDict() - for n, t in self.terminals.items(): - terms[n] = (t.saveState()) - return terms - - def restoreTerminals(self, state): - for name in list(self.terminals.keys()): - if name not in state: - self.removeTerminal(name) - for name, opts in state.items(): - if name in self.terminals: - term = self[name] - term.setOpts(**opts) - continue - try: - opts = strDict(opts) - self.addTerminal(name, **opts) - except: - printExc("Error restoring terminal %s (%s):" % (str(name), str(opts))) - - - def clearTerminals(self): - for t in self.terminals.values(): - t.close() - self.terminals = OrderedDict() - self._inputs = OrderedDict() - self._outputs = OrderedDict() - - def close(self): - """Cleans up after the node--removes terminals, graphicsItem, widget""" - self.disconnectAll() - self.clearTerminals() - item = self.graphicsItem() - if item.scene() is not None: - item.scene().removeItem(item) - self._graphicsItem = None - w = self.ctrlWidget() - if w is not None: - w.setParent(None) - #self.emit(QtCore.SIGNAL('closed'), self) - self.sigClosed.emit(self) - - def disconnectAll(self): - for t in self.terminals.values(): - t.disconnectAll() - - -#class NodeGraphicsItem(QtGui.QGraphicsItem): -class NodeGraphicsItem(GraphicsObject): - def __init__(self, node): - #QtGui.QGraphicsItem.__init__(self) - GraphicsObject.__init__(self) - #QObjectWorkaround.__init__(self) - - #self.shadow = QtGui.QGraphicsDropShadowEffect() - #self.shadow.setOffset(5,5) - #self.shadow.setBlurRadius(10) - #self.setGraphicsEffect(self.shadow) - - self.pen = fn.mkPen(0,0,0) - self.selectPen = fn.mkPen(200,200,200,width=2) - self.brush = fn.mkBrush(200, 200, 200, 150) - self.hoverBrush = fn.mkBrush(200, 200, 200, 200) - self.selectBrush = fn.mkBrush(200, 200, 255, 200) - self.hovered = False - - self.node = node - flags = self.ItemIsMovable | self.ItemIsSelectable | self.ItemIsFocusable |self.ItemSendsGeometryChanges - #flags = self.ItemIsFocusable |self.ItemSendsGeometryChanges - - self.setFlags(flags) - self.bounds = QtCore.QRectF(0, 0, 100, 100) - self.nameItem = QtGui.QGraphicsTextItem(self.node.name(), self) - self.nameItem.setDefaultTextColor(QtGui.QColor(50, 50, 50)) - self.nameItem.moveBy(self.bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) - self.nameItem.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) - self.updateTerminals() - #self.setZValue(10) - - self.nameItem.focusOutEvent = self.labelFocusOut - self.nameItem.keyPressEvent = self.labelKeyPress - - self.menu = None - self.buildMenu() - - #self.node.sigTerminalRenamed.connect(self.updateActionMenu) - - #def setZValue(self, z): - #for t, item in self.terminals.itervalues(): - #item.setZValue(z+1) - #GraphicsObject.setZValue(self, z) - - def labelFocusOut(self, ev): - QtGui.QGraphicsTextItem.focusOutEvent(self.nameItem, ev) - self.labelChanged() - - def labelKeyPress(self, ev): - if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: - self.labelChanged() - else: - QtGui.QGraphicsTextItem.keyPressEvent(self.nameItem, ev) - - def labelChanged(self): - newName = str(self.nameItem.toPlainText()) - if newName != self.node.name(): - self.node.rename(newName) - - ### re-center the label - bounds = self.boundingRect() - self.nameItem.setPos(bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) - - def setPen(self, pen): - self.pen = pen - self.update() - - def setBrush(self, brush): - self.brush = brush - self.update() - - - def updateTerminals(self): - bounds = self.bounds - self.terminals = {} - inp = self.node.inputs() - dy = bounds.height() / (len(inp)+1) - y = dy - for i, t in inp.items(): - item = t.graphicsItem() - item.setParentItem(self) - #item.setZValue(self.zValue()+1) - br = self.bounds - item.setAnchor(0, y) - self.terminals[i] = (t, item) - y += dy - - out = self.node.outputs() - dy = bounds.height() / (len(out)+1) - y = dy - for i, t in out.items(): - item = t.graphicsItem() - item.setParentItem(self) - item.setZValue(self.zValue()) - br = self.bounds - item.setAnchor(bounds.width(), y) - self.terminals[i] = (t, item) - y += dy - - #self.buildMenu() - - - def boundingRect(self): - return self.bounds.adjusted(-5, -5, 5, 5) - - def paint(self, p, *args): - - p.setPen(self.pen) - if self.isSelected(): - p.setPen(self.selectPen) - p.setBrush(self.selectBrush) - else: - p.setPen(self.pen) - if self.hovered: - p.setBrush(self.hoverBrush) - else: - p.setBrush(self.brush) - - p.drawRect(self.bounds) - - - def mousePressEvent(self, ev): - ev.ignore() - - - def mouseClickEvent(self, ev): - #print "Node.mouseClickEvent called." - if int(ev.button()) == int(QtCore.Qt.LeftButton): - ev.accept() - #print " ev.button: left" - sel = self.isSelected() - #ret = QtGui.QGraphicsItem.mousePressEvent(self, ev) - self.setSelected(True) - if not sel and self.isSelected(): - #self.setBrush(QtGui.QBrush(QtGui.QColor(200, 200, 255))) - #self.emit(QtCore.SIGNAL('selected')) - #self.scene().selectionChanged.emit() ## for some reason this doesn't seem to be happening automatically - self.update() - #return ret - - elif int(ev.button()) == int(QtCore.Qt.RightButton): - #print " ev.button: right" - ev.accept() - #pos = ev.screenPos() - self.raiseContextMenu(ev) - #self.menu.popup(QtCore.QPoint(pos.x(), pos.y())) - - def mouseDragEvent(self, ev): - #print "Node.mouseDrag" - if ev.button() == QtCore.Qt.LeftButton: - ev.accept() - self.setPos(self.pos()+self.mapToParent(ev.pos())-self.mapToParent(ev.lastPos())) - - def hoverEvent(self, ev): - if not ev.isExit() and ev.acceptClicks(QtCore.Qt.LeftButton): - ev.acceptDrags(QtCore.Qt.LeftButton) - self.hovered = True - else: - self.hovered = False - self.update() - - def keyPressEvent(self, ev): - if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: - ev.accept() - if not self.node._allowRemove: - return - self.node.close() - else: - ev.ignore() - - def itemChange(self, change, val): - if change == self.ItemPositionHasChanged: - for k, t in self.terminals.items(): - t[1].nodeMoved() - return GraphicsObject.itemChange(self, change, val) - - - def getMenu(self): - return self.menu - - def getContextMenus(self, event): - return [self.menu] - - def raiseContextMenu(self, ev): - menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - - def buildMenu(self): - self.menu = QtGui.QMenu() - self.menu.setTitle("Node") - a = self.menu.addAction("Add input", self.addInputFromMenu) - if not self.node._allowAddInput: - a.setEnabled(False) - a = self.menu.addAction("Add output", self.addOutputFromMenu) - if not self.node._allowAddOutput: - a.setEnabled(False) - a = self.menu.addAction("Remove node", self.node.close) - if not self.node._allowRemove: - a.setEnabled(False) - - def addInputFromMenu(self): ## called when add input is clicked in context menu - self.node.addInput(renamable=True, removable=True, multiable=True) - - def addOutputFromMenu(self): ## called when add output is clicked in context menu - self.node.addOutput(renamable=True, removable=True, multiable=False) - diff --git a/pyqtgraph/flowchart/Terminal.py b/pyqtgraph/flowchart/Terminal.py deleted file mode 100644 index 45805cd8..00000000 --- a/pyqtgraph/flowchart/Terminal.py +++ /dev/null @@ -1,638 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtCore, QtGui -import weakref -from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject -import pyqtgraph.functions as fn -from pyqtgraph.Point import Point -#from PySide import QtCore, QtGui -from .eq import * - -class Terminal(object): - def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None): - """ - Construct a new terminal. - - ============== ================================================================================= - **Arguments:** - node the node to which this terminal belongs - name string, the name of the terminal - io 'in' or 'out' - optional bool, whether the node may process without connection to this terminal - multi bool, for inputs: whether this terminal may make multiple connections - for outputs: whether this terminal creates a different value for each connection - pos [x, y], the position of the terminal within its node's boundaries - renamable (bool) Whether the terminal can be renamed by the user - removable (bool) Whether the terminal can be removed by the user - multiable (bool) Whether the user may toggle the *multi* option for this terminal - bypass (str) Name of the terminal from which this terminal's value is derived - when the Node is in bypass mode. - ============== ================================================================================= - """ - self._io = io - #self._isOutput = opts[0] in ['out', 'io'] - #self._isInput = opts[0]] in ['in', 'io'] - #self._isIO = opts[0]=='io' - self._optional = optional - self._multi = multi - self._node = weakref.ref(node) - self._name = name - self._renamable = renamable - self._removable = removable - self._multiable = multiable - self._connections = {} - self._graphicsItem = TerminalGraphicsItem(self, parent=self._node().graphicsItem()) - self._bypass = bypass - - if multi: - self._value = {} ## dictionary of terminal:value pairs. - else: - self._value = None - - self.valueOk = None - self.recolor() - - def value(self, term=None): - """Return the value this terminal provides for the connected terminal""" - if term is None: - return self._value - - if self.isMultiValue(): - return self._value.get(term, None) - else: - return self._value - - def bypassValue(self): - return self._bypass - - def setValue(self, val, process=True): - """If this is a single-value terminal, val should be a single value. - If this is a multi-value terminal, val should be a dict of terminal:value pairs""" - if not self.isMultiValue(): - if eq(val, self._value): - return - self._value = val - else: - if not isinstance(self._value, dict): - self._value = {} - if val is not None: - self._value.update(val) - - self.setValueAcceptable(None) ## by default, input values are 'unchecked' until Node.update(). - if self.isInput() and process: - self.node().update() - - ## Let the flowchart handle this. - #if self.isOutput(): - #for c in self.connections(): - #if c.isInput(): - #c.inputChanged(self) - self.recolor() - - def setOpts(self, **opts): - self._renamable = opts.get('renamable', self._renamable) - self._removable = opts.get('removable', self._removable) - self._multiable = opts.get('multiable', self._multiable) - if 'multi' in opts: - self.setMultiValue(opts['multi']) - - - def connected(self, term): - """Called whenever this terminal has been connected to another. (note--this function is called on both terminals)""" - if self.isInput() and term.isOutput(): - self.inputChanged(term) - if self.isOutput() and self.isMultiValue(): - self.node().update() - self.node().connected(self, term) - - def disconnected(self, term): - """Called whenever this terminal has been disconnected from another. (note--this function is called on both terminals)""" - if self.isMultiValue() and term in self._value: - del self._value[term] - self.node().update() - #self.recolor() - else: - if self.isInput(): - self.setValue(None) - self.node().disconnected(self, term) - #self.node().update() - - def inputChanged(self, term, process=True): - """Called whenever there is a change to the input value to this terminal. - It may often be useful to override this function.""" - if self.isMultiValue(): - self.setValue({term: term.value(self)}, process=process) - else: - self.setValue(term.value(self), process=process) - - def valueIsAcceptable(self): - """Returns True->acceptable None->unknown False->Unacceptable""" - return self.valueOk - - def setValueAcceptable(self, v=True): - self.valueOk = v - self.recolor() - - def connections(self): - return self._connections - - def node(self): - return self._node() - - def isInput(self): - return self._io == 'in' - - def isMultiValue(self): - return self._multi - - def setMultiValue(self, multi): - """Set whether this is a multi-value terminal.""" - self._multi = multi - if not multi and len(self.inputTerminals()) > 1: - self.disconnectAll() - - for term in self.inputTerminals(): - self.inputChanged(term) - - def isOutput(self): - return self._io == 'out' - - def isRenamable(self): - return self._renamable - - def isRemovable(self): - return self._removable - - def isMultiable(self): - return self._multiable - - def name(self): - return self._name - - def graphicsItem(self): - return self._graphicsItem - - def isConnected(self): - return len(self.connections()) > 0 - - def connectedTo(self, term): - return term in self.connections() - - def hasInput(self): - #conn = self.extendedConnections() - for t in self.connections(): - if t.isOutput(): - return True - return False - - def inputTerminals(self): - """Return the terminal(s) that give input to this one.""" - #terms = self.extendedConnections() - #for t in terms: - #if t.isOutput(): - #return t - return [t for t in self.connections() if t.isOutput()] - - - def dependentNodes(self): - """Return the list of nodes which receive input from this terminal.""" - #conn = self.extendedConnections() - #del conn[self] - return set([t.node() for t in self.connections() if t.isInput()]) - - def connectTo(self, term, connectionItem=None): - try: - if self.connectedTo(term): - raise Exception('Already connected') - if term is self: - raise Exception('Not connecting terminal to self') - if term.node() is self.node(): - raise Exception("Can't connect to terminal on same node.") - for t in [self, term]: - if t.isInput() and not t._multi and len(t.connections()) > 0: - raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, list(t.connections().keys()))) - #if self.hasInput() and term.hasInput(): - #raise Exception('Target terminal already has input') - - #if term in self.node().terminals.values(): - #if self.isOutput() or term.isOutput(): - #raise Exception('Can not connect an output back to the same node.') - except: - if connectionItem is not None: - connectionItem.close() - raise - - if connectionItem is None: - connectionItem = ConnectionItem(self.graphicsItem(), term.graphicsItem()) - #self.graphicsItem().scene().addItem(connectionItem) - self.graphicsItem().getViewBox().addItem(connectionItem) - #connectionItem.setParentItem(self.graphicsItem().parent().parent()) - self._connections[term] = connectionItem - term._connections[self] = connectionItem - - self.recolor() - - #if self.isOutput() and term.isInput(): - #term.inputChanged(self) - #if term.isInput() and term.isOutput(): - #self.inputChanged(term) - self.connected(term) - term.connected(self) - - return connectionItem - - def disconnectFrom(self, term): - if not self.connectedTo(term): - return - item = self._connections[term] - #print "removing connection", item - #item.scene().removeItem(item) - item.close() - del self._connections[term] - del term._connections[self] - self.recolor() - term.recolor() - - self.disconnected(term) - term.disconnected(self) - #if self.isOutput() and term.isInput(): - #term.inputChanged(self) - #if term.isInput() and term.isOutput(): - #self.inputChanged(term) - - - def disconnectAll(self): - for t in list(self._connections.keys()): - self.disconnectFrom(t) - - def recolor(self, color=None, recurse=True): - if color is None: - if not self.isConnected(): ## disconnected terminals are black - color = QtGui.QColor(0,0,0) - elif self.isInput() and not self.hasInput(): ## input terminal with no connected output terminals - color = QtGui.QColor(200,200,0) - elif self._value is None or eq(self._value, {}): ## terminal is connected but has no data (possibly due to processing error) - color = QtGui.QColor(255,255,255) - elif self.valueIsAcceptable() is None: ## terminal has data, but it is unknown if the data is ok - color = QtGui.QColor(200, 200, 0) - elif self.valueIsAcceptable() is True: ## terminal has good input, all ok - color = QtGui.QColor(0, 200, 0) - else: ## terminal has bad input - color = QtGui.QColor(200, 0, 0) - self.graphicsItem().setBrush(QtGui.QBrush(color)) - - if recurse: - for t in self.connections(): - t.recolor(color, recurse=False) - - - def rename(self, name): - oldName = self._name - self._name = name - self.node().terminalRenamed(self, oldName) - self.graphicsItem().termRenamed(name) - - def __repr__(self): - return "" % (str(self.node().name()), str(self.name())) - - #def extendedConnections(self, terms=None): - #"""Return list of terminals (including this one) that are directly or indirectly wired to this.""" - #if terms is None: - #terms = {} - #terms[self] = None - #for t in self._connections: - #if t in terms: - #continue - #terms.update(t.extendedConnections(terms)) - #return terms - - def __hash__(self): - return id(self) - - def close(self): - self.disconnectAll() - item = self.graphicsItem() - if item.scene() is not None: - item.scene().removeItem(item) - - def saveState(self): - return {'io': self._io, 'multi': self._multi, 'optional': self._optional, 'renamable': self._renamable, 'removable': self._removable, 'multiable': self._multiable} - - -#class TerminalGraphicsItem(QtGui.QGraphicsItem): -class TerminalGraphicsItem(GraphicsObject): - - def __init__(self, term, parent=None): - self.term = term - #QtGui.QGraphicsItem.__init__(self, parent) - GraphicsObject.__init__(self, parent) - self.brush = fn.mkBrush(0,0,0) - self.box = QtGui.QGraphicsRectItem(0, 0, 10, 10, self) - self.label = QtGui.QGraphicsTextItem(self.term.name(), self) - self.label.scale(0.7, 0.7) - #self.setAcceptHoverEvents(True) - self.newConnection = None - self.setFiltersChildEvents(True) ## to pick up mouse events on the rectitem - if self.term.isRenamable(): - self.label.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) - self.label.focusOutEvent = self.labelFocusOut - self.label.keyPressEvent = self.labelKeyPress - self.setZValue(1) - self.menu = None - - - def labelFocusOut(self, ev): - QtGui.QGraphicsTextItem.focusOutEvent(self.label, ev) - self.labelChanged() - - def labelKeyPress(self, ev): - if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: - self.labelChanged() - else: - QtGui.QGraphicsTextItem.keyPressEvent(self.label, ev) - - def labelChanged(self): - newName = str(self.label.toPlainText()) - if newName != self.term.name(): - self.term.rename(newName) - - def termRenamed(self, name): - self.label.setPlainText(name) - - def setBrush(self, brush): - self.brush = brush - self.box.setBrush(brush) - - def disconnect(self, target): - self.term.disconnectFrom(target.term) - - def boundingRect(self): - br = self.box.mapRectToParent(self.box.boundingRect()) - lr = self.label.mapRectToParent(self.label.boundingRect()) - return br | lr - - def paint(self, p, *args): - pass - - def setAnchor(self, x, y): - pos = QtCore.QPointF(x, y) - self.anchorPos = pos - br = self.box.mapRectToParent(self.box.boundingRect()) - lr = self.label.mapRectToParent(self.label.boundingRect()) - - - if self.term.isInput(): - self.box.setPos(pos.x(), pos.y()-br.height()/2.) - self.label.setPos(pos.x() + br.width(), pos.y() - lr.height()/2.) - else: - self.box.setPos(pos.x()-br.width(), pos.y()-br.height()/2.) - self.label.setPos(pos.x()-br.width()-lr.width(), pos.y()-lr.height()/2.) - self.updateConnections() - - def updateConnections(self): - for t, c in self.term.connections().items(): - c.updateLine() - - def mousePressEvent(self, ev): - #ev.accept() - ev.ignore() ## necessary to allow click/drag events to process correctly - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - ev.accept() - self.label.setFocus(QtCore.Qt.MouseFocusReason) - elif ev.button() == QtCore.Qt.RightButton: - ev.accept() - self.raiseContextMenu(ev) - - def raiseContextMenu(self, ev): - ## only raise menu if this terminal is removable - menu = self.getMenu() - menu = self.scene().addParentContextMenus(self, menu, ev) - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - - def getMenu(self): - if self.menu is None: - self.menu = QtGui.QMenu() - self.menu.setTitle("Terminal") - remAct = QtGui.QAction("Remove terminal", self.menu) - remAct.triggered.connect(self.removeSelf) - self.menu.addAction(remAct) - self.menu.remAct = remAct - if not self.term.isRemovable(): - remAct.setEnabled(False) - multiAct = QtGui.QAction("Multi-value", self.menu) - multiAct.setCheckable(True) - multiAct.setChecked(self.term.isMultiValue()) - multiAct.setEnabled(self.term.isMultiable()) - - multiAct.triggered.connect(self.toggleMulti) - self.menu.addAction(multiAct) - self.menu.multiAct = multiAct - if self.term.isMultiable(): - multiAct.setEnabled = False - return self.menu - - def toggleMulti(self): - multi = self.menu.multiAct.isChecked() - self.term.setMultiValue(multi) - - ## probably never need this - #def getContextMenus(self, ev): - #return [self.getMenu()] - - def removeSelf(self): - self.term.node().removeTerminal(self.term) - - def mouseDragEvent(self, ev): - if ev.button() != QtCore.Qt.LeftButton: - ev.ignore() - return - - ev.accept() - if ev.isStart(): - if self.newConnection is None: - self.newConnection = ConnectionItem(self) - #self.scene().addItem(self.newConnection) - self.getViewBox().addItem(self.newConnection) - #self.newConnection.setParentItem(self.parent().parent()) - - self.newConnection.setTarget(self.mapToView(ev.pos())) - elif ev.isFinish(): - if self.newConnection is not None: - items = self.scene().items(ev.scenePos()) - gotTarget = False - for i in items: - if isinstance(i, TerminalGraphicsItem): - self.newConnection.setTarget(i) - try: - self.term.connectTo(i.term, self.newConnection) - gotTarget = True - except: - self.scene().removeItem(self.newConnection) - self.newConnection = None - raise - break - - if not gotTarget: - #print "remove unused connection" - #self.scene().removeItem(self.newConnection) - self.newConnection.close() - self.newConnection = None - else: - if self.newConnection is not None: - self.newConnection.setTarget(self.mapToView(ev.pos())) - - def hoverEvent(self, ev): - if not ev.isExit() and ev.acceptDrags(QtCore.Qt.LeftButton): - ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. - ev.acceptClicks(QtCore.Qt.RightButton) - self.box.setBrush(fn.mkBrush('w')) - else: - self.box.setBrush(self.brush) - self.update() - - #def hoverEnterEvent(self, ev): - #self.hover = True - - #def hoverLeaveEvent(self, ev): - #self.hover = False - - def connectPoint(self): - ## return the connect position of this terminal in view coords - return self.mapToView(self.mapFromItem(self.box, self.box.boundingRect().center())) - - def nodeMoved(self): - for t, item in self.term.connections().items(): - item.updateLine() - - -#class ConnectionItem(QtGui.QGraphicsItem): -class ConnectionItem(GraphicsObject): - - def __init__(self, source, target=None): - #QtGui.QGraphicsItem.__init__(self) - GraphicsObject.__init__(self) - self.setFlags( - self.ItemIsSelectable | - self.ItemIsFocusable - ) - self.source = source - self.target = target - self.length = 0 - self.hovered = False - self.path = None - self.shapePath = None - self.style = { - 'shape': 'line', - 'color': (100, 100, 250), - 'width': 1.0, - 'hoverColor': (150, 150, 250), - 'hoverWidth': 1.0, - 'selectedColor': (200, 200, 0), - 'selectedWidth': 3.0, - } - #self.line = QtGui.QGraphicsLineItem(self) - self.source.getViewBox().addItem(self) - self.updateLine() - self.setZValue(0) - - def close(self): - if self.scene() is not None: - #self.scene().removeItem(self.line) - self.scene().removeItem(self) - - def setTarget(self, target): - self.target = target - self.updateLine() - - def setStyle(self, **kwds): - self.style.update(kwds) - if 'shape' in kwds: - self.updateLine() - else: - self.update() - - def updateLine(self): - start = Point(self.source.connectPoint()) - if isinstance(self.target, TerminalGraphicsItem): - stop = Point(self.target.connectPoint()) - elif isinstance(self.target, QtCore.QPointF): - stop = Point(self.target) - else: - return - self.prepareGeometryChange() - - self.path = self.generatePath(start, stop) - self.shapePath = None - self.update() - - def generatePath(self, start, stop): - path = QtGui.QPainterPath() - path.moveTo(start) - if self.style['shape'] == 'line': - path.lineTo(stop) - elif self.style['shape'] == 'cubic': - path.cubicTo(Point(stop.x(), start.y()), Point(start.x(), stop.y()), Point(stop.x(), stop.y())) - else: - raise Exception('Invalid shape "%s"; options are "line" or "cubic"' % self.style['shape']) - return path - - def keyPressEvent(self, ev): - if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: - #if isinstance(self.target, TerminalGraphicsItem): - self.source.disconnect(self.target) - ev.accept() - else: - ev.ignore() - - def mousePressEvent(self, ev): - ev.ignore() - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - ev.accept() - sel = self.isSelected() - self.setSelected(True) - if not sel and self.isSelected(): - self.update() - - def hoverEvent(self, ev): - if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.LeftButton): - self.hovered = True - else: - self.hovered = False - self.update() - - - def boundingRect(self): - return self.shape().boundingRect() - ##return self.line.boundingRect() - #px = self.pixelWidth() - #return QtCore.QRectF(-5*px, 0, 10*px, self.length) - def viewRangeChanged(self): - self.shapePath = None - self.prepareGeometryChange() - - def shape(self): - if self.shapePath is None: - if self.path is None: - return QtGui.QPainterPath() - stroker = QtGui.QPainterPathStroker() - px = self.pixelWidth() - stroker.setWidth(px*8) - self.shapePath = stroker.createStroke(self.path) - return self.shapePath - - def paint(self, p, *args): - if self.isSelected(): - p.setPen(fn.mkPen(self.style['selectedColor'], width=self.style['selectedWidth'])) - else: - if self.hovered: - p.setPen(fn.mkPen(self.style['hoverColor'], width=self.style['hoverWidth'])) - else: - p.setPen(fn.mkPen(self.style['color'], width=self.style['width'])) - - #p.drawLine(0, 0, 0, self.length) - - p.drawPath(self.path) diff --git a/pyqtgraph/flowchart/__init__.py b/pyqtgraph/flowchart/__init__.py deleted file mode 100644 index 46e04db0..00000000 --- a/pyqtgraph/flowchart/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# -*- coding: utf-8 -*- -from .Flowchart import * - -from .library import getNodeType, registerNodeType, getNodeTree \ No newline at end of file diff --git a/pyqtgraph/flowchart/eq.py b/pyqtgraph/flowchart/eq.py deleted file mode 100644 index 031ebce8..00000000 --- a/pyqtgraph/flowchart/eq.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -from numpy import ndarray, bool_ -from pyqtgraph.metaarray import MetaArray - -def eq(a, b): - """The great missing equivalence function: Guaranteed evaluation to a single bool value.""" - if a is b: - return True - - try: - e = a==b - except ValueError: - return False - except AttributeError: - return False - except: - print("a:", str(type(a)), str(a)) - print("b:", str(type(b)), str(b)) - raise - t = type(e) - if t is bool: - return e - elif t is bool_: - return bool(e) - elif isinstance(e, ndarray) or (hasattr(e, 'implements') and e.implements('MetaArray')): - try: ## disaster: if a is an empty array and b is not, then e.all() is True - if a.shape != b.shape: - return False - except: - return False - if (hasattr(e, 'implements') and e.implements('MetaArray')): - return e.asarray().all() - else: - return e.all() - else: - raise Exception("== operator returned type %s" % str(type(e))) diff --git a/pyqtgraph/flowchart/library/Data.py b/pyqtgraph/flowchart/library/Data.py deleted file mode 100644 index cbef848a..00000000 --- a/pyqtgraph/flowchart/library/Data.py +++ /dev/null @@ -1,356 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Node import Node -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -from .common import * -from pyqtgraph.SRTTransform import SRTTransform -from pyqtgraph.Point import Point -from pyqtgraph.widgets.TreeWidget import TreeWidget -from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem - -from . import functions - -class ColumnSelectNode(Node): - """Select named columns from a record array or MetaArray.""" - nodeName = "ColumnSelect" - def __init__(self, name): - Node.__init__(self, name, terminals={'In': {'io': 'in'}}) - self.columns = set() - self.columnList = QtGui.QListWidget() - self.axis = 0 - self.columnList.itemChanged.connect(self.itemChanged) - - def process(self, In, display=True): - if display: - self.updateList(In) - - out = {} - if hasattr(In, 'implements') and In.implements('MetaArray'): - for c in self.columns: - out[c] = In[self.axis:c] - elif isinstance(In, np.ndarray) and In.dtype.fields is not None: - for c in self.columns: - out[c] = In[c] - else: - self.In.setValueAcceptable(False) - raise Exception("Input must be MetaArray or ndarray with named fields") - - return out - - def ctrlWidget(self): - return self.columnList - - def updateList(self, data): - if hasattr(data, 'implements') and data.implements('MetaArray'): - cols = data.listColumns() - for ax in cols: ## find first axis with columns - if len(cols[ax]) > 0: - self.axis = ax - cols = set(cols[ax]) - break - else: - cols = list(data.dtype.fields.keys()) - - rem = set() - for c in self.columns: - if c not in cols: - self.removeTerminal(c) - rem.add(c) - self.columns -= rem - - self.columnList.blockSignals(True) - self.columnList.clear() - for c in cols: - item = QtGui.QListWidgetItem(c) - item.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsUserCheckable) - if c in self.columns: - item.setCheckState(QtCore.Qt.Checked) - else: - item.setCheckState(QtCore.Qt.Unchecked) - self.columnList.addItem(item) - self.columnList.blockSignals(False) - - - def itemChanged(self, item): - col = str(item.text()) - if item.checkState() == QtCore.Qt.Checked: - if col not in self.columns: - self.columns.add(col) - self.addOutput(col) - else: - if col in self.columns: - self.columns.remove(col) - self.removeTerminal(col) - self.update() - - def saveState(self): - state = Node.saveState(self) - state['columns'] = list(self.columns) - return state - - def restoreState(self, state): - Node.restoreState(self, state) - self.columns = set(state.get('columns', [])) - for c in self.columns: - self.addOutput(c) - - - -class RegionSelectNode(CtrlNode): - """Returns a slice from a 1-D array. Connect the 'widget' output to a plot to display a region-selection widget.""" - nodeName = "RegionSelect" - uiTemplate = [ - ('start', 'spin', {'value': 0, 'step': 0.1}), - ('stop', 'spin', {'value': 0.1, 'step': 0.1}), - ('display', 'check', {'value': True}), - ('movable', 'check', {'value': True}), - ] - - def __init__(self, name): - self.items = {} - CtrlNode.__init__(self, name, terminals={ - 'data': {'io': 'in'}, - 'selected': {'io': 'out'}, - 'region': {'io': 'out'}, - 'widget': {'io': 'out', 'multi': True} - }) - self.ctrls['display'].toggled.connect(self.displayToggled) - self.ctrls['movable'].toggled.connect(self.movableToggled) - - def displayToggled(self, b): - for item in self.items.values(): - item.setVisible(b) - - def movableToggled(self, b): - for item in self.items.values(): - item.setMovable(b) - - - def process(self, data=None, display=True): - #print "process.." - s = self.stateGroup.state() - region = [s['start'], s['stop']] - - if display: - conn = self['widget'].connections() - for c in conn: - plot = c.node().getPlot() - if plot is None: - continue - if c in self.items: - item = self.items[c] - item.setRegion(region) - #print " set rgn:", c, region - #item.setXVals(events) - else: - item = LinearRegionItem(values=region) - self.items[c] = item - #item.connect(item, QtCore.SIGNAL('regionChanged'), self.rgnChanged) - item.sigRegionChanged.connect(self.rgnChanged) - item.setVisible(s['display']) - item.setMovable(s['movable']) - #print " new rgn:", c, region - #self.items[c].setYRange([0., 0.2], relative=True) - - if self['selected'].isConnected(): - if data is None: - sliced = None - elif (hasattr(data, 'implements') and data.implements('MetaArray')): - sliced = data[0:s['start']:s['stop']] - else: - mask = (data['time'] >= s['start']) * (data['time'] < s['stop']) - sliced = data[mask] - else: - sliced = None - - return {'selected': sliced, 'widget': self.items, 'region': region} - - - def rgnChanged(self, item): - region = item.getRegion() - self.stateGroup.setState({'start': region[0], 'stop': region[1]}) - self.update() - - -class EvalNode(Node): - """Return the output of a string evaluated/executed by the python interpreter. - The string may be either an expression or a python script, and inputs are accessed as the name of the terminal. - For expressions, a single value may be evaluated for a single output, or a dict for multiple outputs. - For a script, the text will be executed as the body of a function.""" - nodeName = 'PythonEval' - - def __init__(self, name): - Node.__init__(self, name, - terminals = { - 'input': {'io': 'in', 'renamable': True}, - 'output': {'io': 'out', 'renamable': True}, - }, - allowAddInput=True, allowAddOutput=True) - - self.ui = QtGui.QWidget() - self.layout = QtGui.QGridLayout() - #self.addInBtn = QtGui.QPushButton('+Input') - #self.addOutBtn = QtGui.QPushButton('+Output') - self.text = QtGui.QTextEdit() - self.text.setTabStopWidth(30) - self.text.setPlainText("# Access inputs as args['input_name']\nreturn {'output': None} ## one key per output terminal") - #self.layout.addWidget(self.addInBtn, 0, 0) - #self.layout.addWidget(self.addOutBtn, 0, 1) - self.layout.addWidget(self.text, 1, 0, 1, 2) - self.ui.setLayout(self.layout) - - #QtCore.QObject.connect(self.addInBtn, QtCore.SIGNAL('clicked()'), self.addInput) - #self.addInBtn.clicked.connect(self.addInput) - #QtCore.QObject.connect(self.addOutBtn, QtCore.SIGNAL('clicked()'), self.addOutput) - #self.addOutBtn.clicked.connect(self.addOutput) - self.text.focusOutEvent = self.focusOutEvent - self.lastText = None - - def ctrlWidget(self): - return self.ui - - #def addInput(self): - #Node.addInput(self, 'input', renamable=True) - - #def addOutput(self): - #Node.addOutput(self, 'output', renamable=True) - - def focusOutEvent(self, ev): - text = str(self.text.toPlainText()) - if text != self.lastText: - self.lastText = text - self.update() - return QtGui.QTextEdit.focusOutEvent(self.text, ev) - - def process(self, display=True, **args): - l = locals() - l.update(args) - ## try eval first, then exec - try: - text = str(self.text.toPlainText()).replace('\n', ' ') - output = eval(text, globals(), l) - except SyntaxError: - fn = "def fn(**args):\n" - run = "\noutput=fn(**args)\n" - text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run - exec(text) - except: - print("Error processing node: %s" % self.name()) - raise - return output - - def saveState(self): - state = Node.saveState(self) - state['text'] = str(self.text.toPlainText()) - #state['terminals'] = self.saveTerminals() - return state - - def restoreState(self, state): - Node.restoreState(self, state) - self.text.clear() - self.text.insertPlainText(state['text']) - self.restoreTerminals(state['terminals']) - self.update() - -class ColumnJoinNode(Node): - """Concatenates record arrays and/or adds new columns""" - nodeName = 'ColumnJoin' - - def __init__(self, name): - Node.__init__(self, name, terminals = { - 'output': {'io': 'out'}, - }) - - #self.items = [] - - self.ui = QtGui.QWidget() - self.layout = QtGui.QGridLayout() - self.ui.setLayout(self.layout) - - self.tree = TreeWidget() - self.addInBtn = QtGui.QPushButton('+ Input') - self.remInBtn = QtGui.QPushButton('- Input') - - self.layout.addWidget(self.tree, 0, 0, 1, 2) - self.layout.addWidget(self.addInBtn, 1, 0) - self.layout.addWidget(self.remInBtn, 1, 1) - - self.addInBtn.clicked.connect(self.addInput) - self.remInBtn.clicked.connect(self.remInput) - self.tree.sigItemMoved.connect(self.update) - - def ctrlWidget(self): - return self.ui - - def addInput(self): - #print "ColumnJoinNode.addInput called." - term = Node.addInput(self, 'input', renamable=True, removable=True, multiable=True) - #print "Node.addInput returned. term:", term - item = QtGui.QTreeWidgetItem([term.name()]) - item.term = term - term.joinItem = item - #self.items.append((term, item)) - self.tree.addTopLevelItem(item) - - def remInput(self): - sel = self.tree.currentItem() - term = sel.term - term.joinItem = None - sel.term = None - self.tree.removeTopLevelItem(sel) - self.removeTerminal(term) - self.update() - - def process(self, display=True, **args): - order = self.order() - vals = [] - for name in order: - if name not in args: - continue - val = args[name] - if isinstance(val, np.ndarray) and len(val.dtype) > 0: - vals.append(val) - else: - vals.append((name, None, val)) - return {'output': functions.concatenateColumns(vals)} - - def order(self): - return [str(self.tree.topLevelItem(i).text(0)) for i in range(self.tree.topLevelItemCount())] - - def saveState(self): - state = Node.saveState(self) - state['order'] = self.order() - return state - - def restoreState(self, state): - Node.restoreState(self, state) - inputs = self.inputs() - - ## Node.restoreState should have created all of the terminals we need - ## However: to maintain support for some older flowchart files, we need - ## to manually add any terminals that were not taken care of. - for name in [n for n in state['order'] if n not in inputs]: - Node.addInput(self, name, renamable=True, removable=True, multiable=True) - inputs = self.inputs() - - order = [name for name in state['order'] if name in inputs] - for name in inputs: - if name not in order: - order.append(name) - - self.tree.clear() - for name in order: - term = self[name] - item = QtGui.QTreeWidgetItem([name]) - item.term = term - term.joinItem = item - #self.items.append((term, item)) - self.tree.addTopLevelItem(item) - - def terminalRenamed(self, term, oldName): - Node.terminalRenamed(self, term, oldName) - item = term.joinItem - item.setText(0, term.name()) - self.update() - - diff --git a/pyqtgraph/flowchart/library/Display.py b/pyqtgraph/flowchart/library/Display.py deleted file mode 100644 index 9068c0ec..00000000 --- a/pyqtgraph/flowchart/library/Display.py +++ /dev/null @@ -1,275 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Node import Node -import weakref -#from pyqtgraph import graphicsItems -from pyqtgraph.Qt import QtCore, QtGui -from pyqtgraph.graphicsItems.ScatterPlotItem import ScatterPlotItem -from pyqtgraph.graphicsItems.PlotCurveItem import PlotCurveItem -from pyqtgraph import PlotDataItem - -from .common import * -import numpy as np - -class PlotWidgetNode(Node): - """Connection to PlotWidget. Will plot arrays, metaarrays, and display event lists.""" - nodeName = 'PlotWidget' - sigPlotChanged = QtCore.Signal(object) - - def __init__(self, name): - Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) - self.plot = None - self.items = {} - - def disconnected(self, localTerm, remoteTerm): - if localTerm is self['In'] and remoteTerm in self.items: - self.plot.removeItem(self.items[remoteTerm]) - del self.items[remoteTerm] - - def setPlot(self, plot): - #print "======set plot" - self.plot = plot - self.sigPlotChanged.emit(self) - - def getPlot(self): - return self.plot - - def process(self, In, display=True): - if display: - #self.plot.clearPlots() - items = set() - for name, vals in In.items(): - if vals is None: - continue - if type(vals) is not list: - vals = [vals] - - for val in vals: - vid = id(val) - if vid in self.items and self.items[vid].scene() is self.plot.scene(): - items.add(vid) - else: - #if isinstance(val, PlotCurveItem): - #self.plot.addItem(val) - #item = val - #if isinstance(val, ScatterPlotItem): - #self.plot.addItem(val) - #item = val - if isinstance(val, QtGui.QGraphicsItem): - self.plot.addItem(val) - item = val - else: - item = self.plot.plot(val) - self.items[vid] = item - items.add(vid) - for vid in list(self.items.keys()): - if vid not in items: - #print "remove", self.items[vid] - self.plot.removeItem(self.items[vid]) - del self.items[vid] - - def processBypassed(self, args): - for item in list(self.items.values()): - self.plot.removeItem(item) - self.items = {} - - #def setInput(self, **args): - #for k in args: - #self.plot.plot(args[k]) - - - -class CanvasNode(Node): - """Connection to a Canvas widget.""" - nodeName = 'CanvasWidget' - - def __init__(self, name): - Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) - self.canvas = None - self.items = {} - - def disconnected(self, localTerm, remoteTerm): - if localTerm is self.In and remoteTerm in self.items: - self.canvas.removeItem(self.items[remoteTerm]) - del self.items[remoteTerm] - - def setCanvas(self, canvas): - self.canvas = canvas - - def getCanvas(self): - return self.canvas - - def process(self, In, display=True): - if display: - items = set() - for name, vals in In.items(): - if vals is None: - continue - if type(vals) is not list: - vals = [vals] - - for val in vals: - vid = id(val) - if vid in self.items: - items.add(vid) - else: - self.canvas.addItem(val) - item = val - self.items[vid] = item - items.add(vid) - for vid in list(self.items.keys()): - if vid not in items: - #print "remove", self.items[vid] - self.canvas.removeItem(self.items[vid]) - del self.items[vid] - - -class PlotCurve(CtrlNode): - """Generates a plot curve from x/y data""" - nodeName = 'PlotCurve' - uiTemplate = [ - ('color', 'color'), - ] - - def __init__(self, name): - CtrlNode.__init__(self, name, terminals={ - 'x': {'io': 'in'}, - 'y': {'io': 'in'}, - 'plot': {'io': 'out'} - }) - self.item = PlotDataItem() - - def process(self, x, y, display=True): - #print "scatterplot process" - if not display: - return {'plot': None} - - self.item.setData(x, y, pen=self.ctrls['color'].color()) - return {'plot': self.item} - - - - -class ScatterPlot(CtrlNode): - """Generates a scatter plot from a record array or nested dicts""" - nodeName = 'ScatterPlot' - uiTemplate = [ - ('x', 'combo', {'values': [], 'index': 0}), - ('y', 'combo', {'values': [], 'index': 0}), - ('sizeEnabled', 'check', {'value': False}), - ('size', 'combo', {'values': [], 'index': 0}), - ('absoluteSize', 'check', {'value': False}), - ('colorEnabled', 'check', {'value': False}), - ('color', 'colormap', {}), - ('borderEnabled', 'check', {'value': False}), - ('border', 'colormap', {}), - ] - - def __init__(self, name): - CtrlNode.__init__(self, name, terminals={ - 'input': {'io': 'in'}, - 'plot': {'io': 'out'} - }) - self.item = ScatterPlotItem() - self.keys = [] - - #self.ui = QtGui.QWidget() - #self.layout = QtGui.QGridLayout() - #self.ui.setLayout(self.layout) - - #self.xCombo = QtGui.QComboBox() - #self.yCombo = QtGui.QComboBox() - - - - def process(self, input, display=True): - #print "scatterplot process" - if not display: - return {'plot': None} - - self.updateKeys(input[0]) - - x = str(self.ctrls['x'].currentText()) - y = str(self.ctrls['y'].currentText()) - size = str(self.ctrls['size'].currentText()) - pen = QtGui.QPen(QtGui.QColor(0,0,0,0)) - points = [] - for i in input: - pt = {'pos': (i[x], i[y])} - if self.ctrls['sizeEnabled'].isChecked(): - pt['size'] = i[size] - if self.ctrls['borderEnabled'].isChecked(): - pt['pen'] = QtGui.QPen(self.ctrls['border'].getColor(i)) - else: - pt['pen'] = pen - if self.ctrls['colorEnabled'].isChecked(): - pt['brush'] = QtGui.QBrush(self.ctrls['color'].getColor(i)) - points.append(pt) - self.item.setPxMode(not self.ctrls['absoluteSize'].isChecked()) - - self.item.setPoints(points) - - return {'plot': self.item} - - - - def updateKeys(self, data): - if isinstance(data, dict): - keys = list(data.keys()) - elif isinstance(data, list) or isinstance(data, tuple): - keys = data - elif isinstance(data, np.ndarray) or isinstance(data, np.void): - keys = data.dtype.names - else: - print("Unknown data type:", type(data), data) - return - - for c in self.ctrls.values(): - c.blockSignals(True) - for c in [self.ctrls['x'], self.ctrls['y'], self.ctrls['size']]: - cur = str(c.currentText()) - c.clear() - for k in keys: - c.addItem(k) - if k == cur: - c.setCurrentIndex(c.count()-1) - for c in [self.ctrls['color'], self.ctrls['border']]: - c.setArgList(keys) - for c in self.ctrls.values(): - c.blockSignals(False) - - self.keys = keys - - - def saveState(self): - state = CtrlNode.saveState(self) - return {'keys': self.keys, 'ctrls': state} - - def restoreState(self, state): - self.updateKeys(state['keys']) - CtrlNode.restoreState(self, state['ctrls']) - -#class ImageItem(Node): - #"""Creates an ImageItem for display in a canvas from a file handle.""" - #nodeName = 'Image' - - #def __init__(self, name): - #Node.__init__(self, name, terminals={ - #'file': {'io': 'in'}, - #'image': {'io': 'out'} - #}) - #self.imageItem = graphicsItems.ImageItem() - #self.handle = None - - #def process(self, file, display=True): - #if not display: - #return {'image': None} - - #if file != self.handle: - #self.handle = file - #data = file.read() - #self.imageItem.updateImage(data) - - #pos = file. - - - \ No newline at end of file diff --git a/pyqtgraph/flowchart/library/Filters.py b/pyqtgraph/flowchart/library/Filters.py deleted file mode 100644 index 090c261c..00000000 --- a/pyqtgraph/flowchart/library/Filters.py +++ /dev/null @@ -1,268 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtCore, QtGui -from ..Node import Node -from scipy.signal import detrend -from scipy.ndimage import median_filter, gaussian_filter -#from pyqtgraph.SignalProxy import SignalProxy -from . import functions -from .common import * -import numpy as np - -import pyqtgraph.metaarray as metaarray - - -class Downsample(CtrlNode): - """Downsample by averaging samples together.""" - nodeName = 'Downsample' - uiTemplate = [ - ('n', 'intSpin', {'min': 1, 'max': 1000000}) - ] - - def processData(self, data): - return functions.downsample(data, self.ctrls['n'].value(), axis=0) - - -class Subsample(CtrlNode): - """Downsample by selecting every Nth sample.""" - nodeName = 'Subsample' - uiTemplate = [ - ('n', 'intSpin', {'min': 1, 'max': 1000000}) - ] - - def processData(self, data): - return data[::self.ctrls['n'].value()] - - -class Bessel(CtrlNode): - """Bessel filter. Input data must have time values.""" - nodeName = 'BesselFilter' - uiTemplate = [ - ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), - ('cutoff', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('order', 'intSpin', {'value': 4, 'min': 1, 'max': 16}), - ('bidir', 'check', {'checked': True}) - ] - - def processData(self, data): - s = self.stateGroup.state() - if s['band'] == 'lowpass': - mode = 'low' - else: - mode = 'high' - return functions.besselFilter(data, bidir=s['bidir'], btype=mode, cutoff=s['cutoff'], order=s['order']) - - -class Butterworth(CtrlNode): - """Butterworth filter""" - nodeName = 'ButterworthFilter' - uiTemplate = [ - ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), - ('wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('bidir', 'check', {'checked': True}) - ] - - def processData(self, data): - s = self.stateGroup.state() - if s['band'] == 'lowpass': - mode = 'low' - else: - mode = 'high' - ret = functions.butterworthFilter(data, bidir=s['bidir'], btype=mode, wPass=s['wPass'], wStop=s['wStop'], gPass=s['gPass'], gStop=s['gStop']) - return ret - - -class ButterworthNotch(CtrlNode): - """Butterworth notch filter""" - nodeName = 'ButterworthNotchFilter' - uiTemplate = [ - ('low_wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('low_wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('low_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('low_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('high_wPass', 'spin', {'value': 3000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('high_wStop', 'spin', {'value': 4000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), - ('high_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('high_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), - ('bidir', 'check', {'checked': True}) - ] - - def processData(self, data): - s = self.stateGroup.state() - - low = functions.butterworthFilter(data, bidir=s['bidir'], btype='low', wPass=s['low_wPass'], wStop=s['low_wStop'], gPass=s['low_gPass'], gStop=s['low_gStop']) - high = functions.butterworthFilter(data, bidir=s['bidir'], btype='high', wPass=s['high_wPass'], wStop=s['high_wStop'], gPass=s['high_gPass'], gStop=s['high_gStop']) - return low + high - - -class Mean(CtrlNode): - """Filters data by taking the mean of a sliding window""" - nodeName = 'MeanFilter' - uiTemplate = [ - ('n', 'intSpin', {'min': 1, 'max': 1000000}) - ] - - @metaArrayWrapper - def processData(self, data): - n = self.ctrls['n'].value() - return functions.rollingSum(data, n) / n - - -class Median(CtrlNode): - """Filters data by taking the median of a sliding window""" - nodeName = 'MedianFilter' - uiTemplate = [ - ('n', 'intSpin', {'min': 1, 'max': 1000000}) - ] - - @metaArrayWrapper - def processData(self, data): - return median_filter(data, self.ctrls['n'].value()) - -class Mode(CtrlNode): - """Filters data by taking the mode (histogram-based) of a sliding window""" - nodeName = 'ModeFilter' - uiTemplate = [ - ('window', 'intSpin', {'value': 500, 'min': 1, 'max': 1000000}), - ] - - @metaArrayWrapper - def processData(self, data): - return functions.modeFilter(data, self.ctrls['window'].value()) - - -class Denoise(CtrlNode): - """Removes anomalous spikes from data, replacing with nearby values""" - nodeName = 'DenoiseFilter' - uiTemplate = [ - ('radius', 'intSpin', {'value': 2, 'min': 0, 'max': 1000000}), - ('threshold', 'doubleSpin', {'value': 4.0, 'min': 0, 'max': 1000}) - ] - - def processData(self, data): - #print "DENOISE" - s = self.stateGroup.state() - return functions.denoise(data, **s) - - -class Gaussian(CtrlNode): - """Gaussian smoothing filter.""" - nodeName = 'GaussianFilter' - uiTemplate = [ - ('sigma', 'doubleSpin', {'min': 0, 'max': 1000000}) - ] - - @metaArrayWrapper - def processData(self, data): - return gaussian_filter(data, self.ctrls['sigma'].value()) - - -class Derivative(CtrlNode): - """Returns the pointwise derivative of the input""" - nodeName = 'DerivativeFilter' - - def processData(self, data): - if hasattr(data, 'implements') and data.implements('MetaArray'): - info = data.infoCopy() - if 'values' in info[0]: - info[0]['values'] = info[0]['values'][:-1] - return metaarray.MetaArray(data[1:] - data[:-1], info=info) - else: - return data[1:] - data[:-1] - - -class Integral(CtrlNode): - """Returns the pointwise integral of the input""" - nodeName = 'IntegralFilter' - - @metaArrayWrapper - def processData(self, data): - data[1:] += data[:-1] - return data - - -class Detrend(CtrlNode): - """Removes linear trend from the data""" - nodeName = 'DetrendFilter' - - @metaArrayWrapper - def processData(self, data): - return detrend(data) - - -class AdaptiveDetrend(CtrlNode): - """Removes baseline from data, ignoring anomalous events""" - nodeName = 'AdaptiveDetrend' - uiTemplate = [ - ('threshold', 'doubleSpin', {'value': 3.0, 'min': 0, 'max': 1000000}) - ] - - def processData(self, data): - return functions.adaptiveDetrend(data, threshold=self.ctrls['threshold'].value()) - -class HistogramDetrend(CtrlNode): - """Removes baseline from data by computing mode (from histogram) of beginning and end of data.""" - nodeName = 'HistogramDetrend' - uiTemplate = [ - ('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), - ('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}), - ('offsetOnly', 'check', {'checked': False}), - ] - - def processData(self, data): - s = self.stateGroup.state() - #ws = self.ctrls['windowSize'].value() - #bn = self.ctrls['numBins'].value() - #offset = self.ctrls['offsetOnly'].checked() - return functions.histogramDetrend(data, window=s['windowSize'], bins=s['numBins'], offsetOnly=s['offsetOnly']) - - - -class RemovePeriodic(CtrlNode): - nodeName = 'RemovePeriodic' - uiTemplate = [ - #('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), - #('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}) - ('f0', 'spin', {'value': 60, 'suffix': 'Hz', 'siPrefix': True, 'min': 0, 'max': None}), - ('harmonics', 'intSpin', {'value': 30, 'min': 0}), - ('samples', 'intSpin', {'value': 1, 'min': 1}), - ] - - def processData(self, data): - times = data.xvals('Time') - dt = times[1]-times[0] - - data1 = data.asarray() - ft = np.fft.fft(data1) - - ## determine frequencies in fft data - df = 1.0 / (len(data1) * dt) - freqs = np.linspace(0.0, (len(ft)-1) * df, len(ft)) - - ## flatten spikes at f0 and harmonics - f0 = self.ctrls['f0'].value() - for i in xrange(1, self.ctrls['harmonics'].value()+2): - f = f0 * i # target frequency - - ## determine index range to check for this frequency - ind1 = int(np.floor(f / df)) - ind2 = int(np.ceil(f / df)) + (self.ctrls['samples'].value()-1) - if ind1 > len(ft)/2.: - break - mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5 - for j in range(ind1, ind2+1): - phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts. - re = mag * np.cos(phase) - im = mag * np.sin(phase) - ft[j] = re + im*1j - ft[len(ft)-j] = re - im*1j - - data2 = np.fft.ifft(ft).real - - ma = metaarray.MetaArray(data2, info=data.infoCopy()) - return ma - - - \ No newline at end of file diff --git a/pyqtgraph/flowchart/library/Operators.py b/pyqtgraph/flowchart/library/Operators.py deleted file mode 100644 index 579d2cd2..00000000 --- a/pyqtgraph/flowchart/library/Operators.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -from ..Node import Node - -class UniOpNode(Node): - """Generic node for performing any operation like Out = In.fn()""" - def __init__(self, name, fn): - self.fn = fn - Node.__init__(self, name, terminals={ - 'In': {'io': 'in'}, - 'Out': {'io': 'out', 'bypass': 'In'} - }) - - def process(self, **args): - return {'Out': getattr(args['In'], self.fn)()} - -class BinOpNode(Node): - """Generic node for performing any operation like A.fn(B)""" - def __init__(self, name, fn): - self.fn = fn - Node.__init__(self, name, terminals={ - 'A': {'io': 'in'}, - 'B': {'io': 'in'}, - 'Out': {'io': 'out', 'bypass': 'A'} - }) - - def process(self, **args): - if isinstance(self.fn, tuple): - for name in self.fn: - try: - fn = getattr(args['A'], name) - break - except AttributeError: - pass - else: - fn = getattr(args['A'], self.fn) - out = fn(args['B']) - if out is NotImplemented: - raise Exception("Operation %s not implemented between %s and %s" % (fn, str(type(args['A'])), str(type(args['B'])))) - #print " ", fn, out - return {'Out': out} - - -class AbsNode(UniOpNode): - """Returns abs(Inp). Does not check input types.""" - nodeName = 'Abs' - def __init__(self, name): - UniOpNode.__init__(self, name, '__abs__') - -class AddNode(BinOpNode): - """Returns A + B. Does not check input types.""" - nodeName = 'Add' - def __init__(self, name): - BinOpNode.__init__(self, name, '__add__') - -class SubtractNode(BinOpNode): - """Returns A - B. Does not check input types.""" - nodeName = 'Subtract' - def __init__(self, name): - BinOpNode.__init__(self, name, '__sub__') - -class MultiplyNode(BinOpNode): - """Returns A * B. Does not check input types.""" - nodeName = 'Multiply' - def __init__(self, name): - BinOpNode.__init__(self, name, '__mul__') - -class DivideNode(BinOpNode): - """Returns A / B. Does not check input types.""" - nodeName = 'Divide' - def __init__(self, name): - # try truediv first, followed by div - BinOpNode.__init__(self, name, ('__truediv__', '__div__')) - - diff --git a/pyqtgraph/flowchart/library/__init__.py b/pyqtgraph/flowchart/library/__init__.py deleted file mode 100644 index 1e44edff..00000000 --- a/pyqtgraph/flowchart/library/__init__.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.pgcollections import OrderedDict -from pyqtgraph import importModules -import os, types -from pyqtgraph.debug import printExc -from ..Node import Node -import pyqtgraph.reload as reload - - -NODE_LIST = OrderedDict() ## maps name:class for all registered Node subclasses -NODE_TREE = OrderedDict() ## categorized tree of Node subclasses - -def getNodeType(name): - try: - return NODE_LIST[name] - except KeyError: - raise Exception("No node type called '%s'" % name) - -def getNodeTree(): - return NODE_TREE - -def registerNodeType(cls, paths, override=False): - """ - Register a new node type. If the type's name is already in use, - an exception will be raised (unless override=True). - - Arguments: - cls - a subclass of Node (must have typ.nodeName) - paths - list of tuples specifying the location(s) this - type will appear in the library tree. - override - if True, overwrite any class having the same name - """ - if not isNodeClass(cls): - raise Exception("Object %s is not a Node subclass" % str(cls)) - - name = cls.nodeName - if not override and name in NODE_LIST: - raise Exception("Node type name '%s' is already registered." % name) - - NODE_LIST[name] = cls - for path in paths: - root = NODE_TREE - for n in path: - if n not in root: - root[n] = OrderedDict() - root = root[n] - root[name] = cls - - - -def isNodeClass(cls): - try: - if not issubclass(cls, Node): - return False - except: - return False - return hasattr(cls, 'nodeName') - -def loadLibrary(reloadLibs=False, libPath=None): - """Import all Node subclasses found within files in the library module.""" - - global NODE_LIST, NODE_TREE - #if libPath is None: - #libPath = os.path.dirname(os.path.abspath(__file__)) - - if reloadLibs: - reload.reloadAll(libPath) - - mods = importModules('', globals(), locals()) - #for f in frozenSupport.listdir(libPath): - #pathName, ext = os.path.splitext(f) - #if ext not in ('.py', '.pyc') or '__init__' in pathName or '__pycache__' in pathName: - #continue - #try: - ##print "importing from", f - #mod = __import__(pathName, globals(), locals()) - #except: - #printExc("Error loading flowchart library %s:" % pathName) - #continue - - for name, mod in mods.items(): - nodes = [] - for n in dir(mod): - o = getattr(mod, n) - if isNodeClass(o): - #print " ", str(o) - registerNodeType(o, [(name,)], override=reloadLibs) - #nodes.append((o.nodeName, o)) - #if len(nodes) > 0: - #NODE_TREE[name] = OrderedDict(nodes) - #NODE_LIST.extend(nodes) - #NODE_LIST = OrderedDict(NODE_LIST) - -def reloadLibrary(): - loadLibrary(reloadLibs=True) - -loadLibrary() -#NODE_LIST = [] -#for o in locals().values(): - #if type(o) is type(AddNode) and issubclass(o, Node) and o is not Node and hasattr(o, 'nodeName'): - #NODE_LIST.append((o.nodeName, o)) -#NODE_LIST.sort(lambda a,b: cmp(a[0], b[0])) -#NODE_LIST = OrderedDict(NODE_LIST) \ No newline at end of file diff --git a/pyqtgraph/flowchart/library/common.py b/pyqtgraph/flowchart/library/common.py deleted file mode 100644 index 65f8c1fd..00000000 --- a/pyqtgraph/flowchart/library/common.py +++ /dev/null @@ -1,148 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtCore, QtGui -from pyqtgraph.widgets.SpinBox import SpinBox -#from pyqtgraph.SignalProxy import SignalProxy -from pyqtgraph.WidgetGroup import WidgetGroup -#from ColorMapper import ColorMapper -from ..Node import Node -import numpy as np -from pyqtgraph.widgets.ColorButton import ColorButton -try: - import metaarray - HAVE_METAARRAY = True -except: - HAVE_METAARRAY = False - - -def generateUi(opts): - """Convenience function for generating common UI types""" - widget = QtGui.QWidget() - l = QtGui.QFormLayout() - l.setSpacing(0) - widget.setLayout(l) - ctrls = {} - row = 0 - for opt in opts: - if len(opt) == 2: - k, t = opt - o = {} - elif len(opt) == 3: - k, t, o = opt - else: - raise Exception("Widget specification must be (name, type) or (name, type, {opts})") - if t == 'intSpin': - w = QtGui.QSpinBox() - if 'max' in o: - w.setMaximum(o['max']) - if 'min' in o: - w.setMinimum(o['min']) - if 'value' in o: - w.setValue(o['value']) - elif t == 'doubleSpin': - w = QtGui.QDoubleSpinBox() - if 'max' in o: - w.setMaximum(o['max']) - if 'min' in o: - w.setMinimum(o['min']) - if 'value' in o: - w.setValue(o['value']) - elif t == 'spin': - w = SpinBox() - w.setOpts(**o) - elif t == 'check': - w = QtGui.QCheckBox() - if 'checked' in o: - w.setChecked(o['checked']) - elif t == 'combo': - w = QtGui.QComboBox() - for i in o['values']: - w.addItem(i) - #elif t == 'colormap': - #w = ColorMapper() - elif t == 'color': - w = ColorButton() - else: - raise Exception("Unknown widget type '%s'" % str(t)) - if 'tip' in o: - w.setToolTip(o['tip']) - w.setObjectName(k) - l.addRow(k, w) - if o.get('hidden', False): - w.hide() - label = l.labelForField(w) - label.hide() - - ctrls[k] = w - w.rowNum = row - row += 1 - group = WidgetGroup(widget) - return widget, group, ctrls - - -class CtrlNode(Node): - """Abstract class for nodes with auto-generated control UI""" - - sigStateChanged = QtCore.Signal(object) - - def __init__(self, name, ui=None, terminals=None): - if ui is None: - if hasattr(self, 'uiTemplate'): - ui = self.uiTemplate - else: - ui = [] - if terminals is None: - terminals = {'In': {'io': 'in'}, 'Out': {'io': 'out', 'bypass': 'In'}} - Node.__init__(self, name=name, terminals=terminals) - - self.ui, self.stateGroup, self.ctrls = generateUi(ui) - self.stateGroup.sigChanged.connect(self.changed) - - def ctrlWidget(self): - return self.ui - - def changed(self): - self.update() - self.sigStateChanged.emit(self) - - def process(self, In, display=True): - out = self.processData(In) - return {'Out': out} - - def saveState(self): - state = Node.saveState(self) - state['ctrl'] = self.stateGroup.state() - return state - - def restoreState(self, state): - Node.restoreState(self, state) - if self.stateGroup is not None: - self.stateGroup.setState(state.get('ctrl', {})) - - def hideRow(self, name): - w = self.ctrls[name] - l = self.ui.layout().labelForField(w) - w.hide() - l.hide() - - def showRow(self, name): - w = self.ctrls[name] - l = self.ui.layout().labelForField(w) - w.show() - l.show() - - - -def metaArrayWrapper(fn): - def newFn(self, data, *args, **kargs): - if HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): - d1 = fn(self, data.view(np.ndarray), *args, **kargs) - info = data.infoCopy() - if d1.shape != data.shape: - for i in range(data.ndim): - if 'values' in info[i]: - info[i]['values'] = info[i]['values'][:d1.shape[i]] - return metaarray.MetaArray(d1, info=info) - else: - return fn(self, data, *args, **kargs) - return newFn - diff --git a/pyqtgraph/flowchart/library/functions.py b/pyqtgraph/flowchart/library/functions.py deleted file mode 100644 index 0476e02f..00000000 --- a/pyqtgraph/flowchart/library/functions.py +++ /dev/null @@ -1,336 +0,0 @@ -import scipy -import numpy as np -from pyqtgraph.metaarray import MetaArray - -def downsample(data, n, axis=0, xvals='subsample'): - """Downsample by averaging points together across axis. - If multiple axes are specified, runs once per axis. - If a metaArray is given, then the axis values can be either subsampled - or downsampled to match. - """ - ma = None - if (hasattr(data, 'implements') and data.implements('MetaArray')): - ma = data - data = data.view(np.ndarray) - - - if hasattr(axis, '__len__'): - if not hasattr(n, '__len__'): - n = [n]*len(axis) - for i in range(len(axis)): - data = downsample(data, n[i], axis[i]) - return data - - nPts = int(data.shape[axis] / n) - s = list(data.shape) - s[axis] = nPts - s.insert(axis+1, n) - sl = [slice(None)] * data.ndim - sl[axis] = slice(0, nPts*n) - d1 = data[tuple(sl)] - #print d1.shape, s - d1.shape = tuple(s) - d2 = d1.mean(axis+1) - - if ma is None: - return d2 - else: - info = ma.infoCopy() - if 'values' in info[axis]: - if xvals == 'subsample': - info[axis]['values'] = info[axis]['values'][::n][:nPts] - elif xvals == 'downsample': - info[axis]['values'] = downsample(info[axis]['values'], n) - return MetaArray(d2, info=info) - - -def applyFilter(data, b, a, padding=100, bidir=True): - """Apply a linear filter with coefficients a, b. Optionally pad the data before filtering - and/or run the filter in both directions.""" - d1 = data.view(np.ndarray) - - if padding > 0: - d1 = np.hstack([d1[:padding], d1, d1[-padding:]]) - - if bidir: - d1 = scipy.signal.lfilter(b, a, scipy.signal.lfilter(b, a, d1)[::-1])[::-1] - else: - d1 = scipy.signal.lfilter(b, a, d1) - - if padding > 0: - d1 = d1[padding:-padding] - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return MetaArray(d1, info=data.infoCopy()) - else: - return d1 - -def besselFilter(data, cutoff, order=1, dt=None, btype='low', bidir=True): - """return data passed through bessel filter""" - if dt is None: - try: - tvals = data.xvals('Time') - dt = (tvals[-1]-tvals[0]) / (len(tvals)-1) - except: - dt = 1.0 - - b,a = scipy.signal.bessel(order, cutoff * dt, btype=btype) - - return applyFilter(data, b, a, bidir=bidir) - #base = data.mean() - #d1 = scipy.signal.lfilter(b, a, data.view(ndarray)-base) + base - #if (hasattr(data, 'implements') and data.implements('MetaArray')): - #return MetaArray(d1, info=data.infoCopy()) - #return d1 - -def butterworthFilter(data, wPass, wStop=None, gPass=2.0, gStop=20.0, order=1, dt=None, btype='low', bidir=True): - """return data passed through bessel filter""" - if dt is None: - try: - tvals = data.xvals('Time') - dt = (tvals[-1]-tvals[0]) / (len(tvals)-1) - except: - dt = 1.0 - - if wStop is None: - wStop = wPass * 2.0 - ord, Wn = scipy.signal.buttord(wPass*dt*2., wStop*dt*2., gPass, gStop) - #print "butterworth ord %f Wn %f c %f sc %f" % (ord, Wn, cutoff, stopCutoff) - b,a = scipy.signal.butter(ord, Wn, btype=btype) - - return applyFilter(data, b, a, bidir=bidir) - - -def rollingSum(data, n): - d1 = data.copy() - d1[1:] += d1[:-1] # integrate - d2 = np.empty(len(d1) - n + 1, dtype=data.dtype) - d2[0] = d1[n-1] # copy first point - d2[1:] = d1[n:] - d1[:-n] # subtract - return d2 - - -def mode(data, bins=None): - """Returns location max value from histogram.""" - if bins is None: - bins = int(len(data)/10.) - if bins < 2: - bins = 2 - y, x = np.histogram(data, bins=bins) - ind = np.argmax(y) - mode = 0.5 * (x[ind] + x[ind+1]) - return mode - -def modeFilter(data, window=500, step=None, bins=None): - """Filter based on histogram-based mode function""" - d1 = data.view(np.ndarray) - vals = [] - l2 = int(window/2.) - if step is None: - step = l2 - i = 0 - while True: - if i > len(data)-step: - break - vals.append(mode(d1[i:i+window], bins)) - i += step - - chunks = [np.linspace(vals[0], vals[0], l2)] - for i in range(len(vals)-1): - chunks.append(np.linspace(vals[i], vals[i+1], step)) - remain = len(data) - step*(len(vals)-1) - l2 - chunks.append(np.linspace(vals[-1], vals[-1], remain)) - d2 = np.hstack(chunks) - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return MetaArray(d2, info=data.infoCopy()) - return d2 - -def denoise(data, radius=2, threshold=4): - """Very simple noise removal function. Compares a point to surrounding points, - replaces with nearby values if the difference is too large.""" - - - r2 = radius * 2 - d1 = data.view(np.ndarray) - d2 = d1[radius:] - d1[:-radius] #a derivative - #d3 = data[r2:] - data[:-r2] - #d4 = d2 - d3 - stdev = d2.std() - #print "denoise: stdev of derivative:", stdev - mask1 = d2 > stdev*threshold #where derivative is large and positive - mask2 = d2 < -stdev*threshold #where derivative is large and negative - maskpos = mask1[:-radius] * mask2[radius:] #both need to be true - maskneg = mask1[radius:] * mask2[:-radius] - mask = maskpos + maskneg - d5 = np.where(mask, d1[:-r2], d1[radius:-radius]) #where both are true replace the value with the value from 2 points before - d6 = np.empty(d1.shape, dtype=d1.dtype) #add points back to the ends - d6[radius:-radius] = d5 - d6[:radius] = d1[:radius] - d6[-radius:] = d1[-radius:] - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return MetaArray(d6, info=data.infoCopy()) - return d6 - -def adaptiveDetrend(data, x=None, threshold=3.0): - """Return the signal with baseline removed. Discards outliers from baseline measurement.""" - if x is None: - x = data.xvals(0) - - d = data.view(np.ndarray) - - d2 = scipy.signal.detrend(d) - - stdev = d2.std() - mask = abs(d2) < stdev*threshold - #d3 = where(mask, 0, d2) - #d4 = d2 - lowPass(d3, cutoffs[1], dt=dt) - - lr = stats.linregress(x[mask], d[mask]) - base = lr[1] + lr[0]*x - d4 = d - base - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return MetaArray(d4, info=data.infoCopy()) - return d4 - - -def histogramDetrend(data, window=500, bins=50, threshold=3.0, offsetOnly=False): - """Linear detrend. Works by finding the most common value at the beginning and end of a trace, excluding outliers. - If offsetOnly is True, then only the offset from the beginning of the trace is subtracted. - """ - - d1 = data.view(np.ndarray) - d2 = [d1[:window], d1[-window:]] - v = [0, 0] - for i in [0, 1]: - d3 = d2[i] - stdev = d3.std() - mask = abs(d3-np.median(d3)) < stdev*threshold - d4 = d3[mask] - y, x = np.histogram(d4, bins=bins) - ind = np.argmax(y) - v[i] = 0.5 * (x[ind] + x[ind+1]) - - if offsetOnly: - d3 = data.view(np.ndarray) - v[0] - else: - base = np.linspace(v[0], v[1], len(data)) - d3 = data.view(np.ndarray) - base - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return MetaArray(d3, info=data.infoCopy()) - return d3 - -def concatenateColumns(data): - """Returns a single record array with columns taken from the elements in data. - data should be a list of elements, which can be either record arrays or tuples (name, type, data) - """ - - ## first determine dtype - dtype = [] - names = set() - maxLen = 0 - for element in data: - if isinstance(element, np.ndarray): - ## use existing columns - for i in range(len(element.dtype)): - name = element.dtype.names[i] - dtype.append((name, element.dtype[i])) - maxLen = max(maxLen, len(element)) - else: - name, type, d = element - if type is None: - type = suggestDType(d) - dtype.append((name, type)) - if isinstance(d, list) or isinstance(d, np.ndarray): - maxLen = max(maxLen, len(d)) - if name in names: - raise Exception('Name "%s" repeated' % name) - names.add(name) - - - - ## create empty array - out = np.empty(maxLen, dtype) - - ## fill columns - for element in data: - if isinstance(element, np.ndarray): - for i in range(len(element.dtype)): - name = element.dtype.names[i] - try: - out[name] = element[name] - except: - print("Column:", name) - print("Input shape:", element.shape, element.dtype) - print("Output shape:", out.shape, out.dtype) - raise - else: - name, type, d = element - out[name] = d - - return out - -def suggestDType(x): - """Return a suitable dtype for x""" - if isinstance(x, list) or isinstance(x, tuple): - if len(x) == 0: - raise Exception('can not determine dtype for empty list') - x = x[0] - - if hasattr(x, 'dtype'): - return x.dtype - elif isinstance(x, float): - return float - elif isinstance(x, int): - return int - #elif isinstance(x, basestring): ## don't try to guess correct string length; use object instead. - #return ' len(ft)/2.: - break - mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5 - for j in range(ind1, ind2+1): - phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts. - re = mag * np.cos(phase) - im = mag * np.sin(phase) - ft[j] = re + im*1j - ft[len(ft)-j] = re - im*1j - - data2 = np.fft.ifft(ft).real - - if (hasattr(data, 'implements') and data.implements('MetaArray')): - return metaarray.MetaArray(data2, info=data.infoCopy()) - else: - return data2 - - - \ No newline at end of file diff --git a/pyqtgraph/frozenSupport.py b/pyqtgraph/frozenSupport.py deleted file mode 100644 index c42a12e1..00000000 --- a/pyqtgraph/frozenSupport.py +++ /dev/null @@ -1,52 +0,0 @@ -## Definitions helpful in frozen environments (eg py2exe) -import os, sys, zipfile - -def listdir(path): - """Replacement for os.listdir that works in frozen environments.""" - if not hasattr(sys, 'frozen'): - return os.listdir(path) - - (zipPath, archivePath) = splitZip(path) - if archivePath is None: - return os.listdir(path) - - with zipfile.ZipFile(zipPath, "r") as zipobj: - contents = zipobj.namelist() - results = set() - for name in contents: - # components in zip archive paths are always separated by forward slash - if name.startswith(archivePath) and len(name) > len(archivePath): - name = name[len(archivePath):].split('/')[0] - results.add(name) - return list(results) - -def isdir(path): - """Replacement for os.path.isdir that works in frozen environments.""" - if not hasattr(sys, 'frozen'): - return os.path.isdir(path) - - (zipPath, archivePath) = splitZip(path) - if archivePath is None: - return os.path.isdir(path) - with zipfile.ZipFile(zipPath, "r") as zipobj: - contents = zipobj.namelist() - archivePath = archivePath.rstrip('/') + '/' ## make sure there's exactly one '/' at the end - for c in contents: - if c.startswith(archivePath): - return True - return False - - -def splitZip(path): - """Splits a path containing a zip file into (zipfile, subpath). - If there is no zip file, returns (path, None)""" - components = os.path.normpath(path).split(os.sep) - for index, component in enumerate(components): - if component.endswith('.zip'): - zipPath = os.sep.join(components[0:index+1]) - archivePath = ''.join([x+'/' for x in components[index+1:]]) - return (zipPath, archivePath) - else: - return (path, None) - - \ No newline at end of file diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py deleted file mode 100644 index 337dfb67..00000000 --- a/pyqtgraph/functions.py +++ /dev/null @@ -1,2023 +0,0 @@ -# -*- coding: utf-8 -*- -""" -functions.py - Miscellaneous functions with no other home -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - -from __future__ import division -from .python2_3 import asUnicode -Colors = { - 'b': (0,0,255,255), - 'g': (0,255,0,255), - 'r': (255,0,0,255), - 'c': (0,255,255,255), - 'm': (255,0,255,255), - 'y': (255,255,0,255), - 'k': (0,0,0,255), - 'w': (255,255,255,255), -} - -SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY') -SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY' - - - -from .Qt import QtGui, QtCore, USE_PYSIDE -import pyqtgraph as pg -import numpy as np -import decimal, re -import ctypes -import sys, struct - -try: - import scipy.ndimage - HAVE_SCIPY = True - if pg.getConfigOption('useWeave'): - try: - import scipy.weave - except ImportError: - pg.setConfigOptions(useWeave=False) -except ImportError: - HAVE_SCIPY = False - -from . import debug - -def siScale(x, minVal=1e-25, allowUnicode=True): - """ - Return the recommended scale factor and SI prefix string for x. - - Example:: - - siScale(0.0001) # returns (1e6, 'μ') - # This indicates that the number 0.0001 is best represented as 0.0001 * 1e6 = 100 μUnits - """ - - if isinstance(x, decimal.Decimal): - x = float(x) - - try: - if np.isnan(x) or np.isinf(x): - return(1, '') - except: - print(x, type(x)) - raise - if abs(x) < minVal: - m = 0 - x = 0 - else: - m = int(np.clip(np.floor(np.log(abs(x))/np.log(1000)), -9.0, 9.0)) - - if m == 0: - pref = '' - elif m < -8 or m > 8: - pref = 'e%d' % (m*3) - else: - if allowUnicode: - pref = SI_PREFIXES[m+8] - else: - pref = SI_PREFIXES_ASCII[m+8] - p = .001**m - - return (p, pref) - -def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, allowUnicode=True): - """ - Return the number x formatted in engineering notation with SI prefix. - - Example:: - siFormat(0.0001, suffix='V') # returns "100 μV" - """ - - if space is True: - space = ' ' - if space is False: - space = '' - - - (p, pref) = siScale(x, minVal, allowUnicode) - if not (len(pref) > 0 and pref[0] == 'e'): - pref = space + pref - - if error is None: - fmt = "%." + str(precision) + "g%s%s" - return fmt % (x*p, pref, suffix) - else: - if allowUnicode: - plusminus = space + asUnicode("±") + space - else: - plusminus = " +/- " - fmt = "%." + str(precision) + "g%s%s%s%s" - return fmt % (x*p, pref, suffix, plusminus, siFormat(error, precision=precision, suffix=suffix, space=space, minVal=minVal)) - -def siEval(s): - """ - Convert a value written in SI notation to its equivalent prefixless value - - Example:: - - siEval("100 μV") # returns 0.0001 - """ - - s = asUnicode(s) - m = re.match(r'(-?((\d+(\.\d*)?)|(\.\d+))([eE]-?\d+)?)\s*([u' + SI_PREFIXES + r']?).*$', s) - if m is None: - raise Exception("Can't convert string '%s' to number." % s) - v = float(m.groups()[0]) - p = m.groups()[6] - #if p not in SI_PREFIXES: - #raise Exception("Can't convert string '%s' to number--unknown prefix." % s) - if p == '': - n = 0 - elif p == 'u': - n = -2 - else: - n = SI_PREFIXES.index(p) - 8 - return v * 1000**n - - -class Color(QtGui.QColor): - def __init__(self, *args): - QtGui.QColor.__init__(self, mkColor(*args)) - - def glColor(self): - """Return (r,g,b,a) normalized for use in opengl""" - return (self.red()/255., self.green()/255., self.blue()/255., self.alpha()/255.) - - def __getitem__(self, ind): - return (self.red, self.green, self.blue, self.alpha)[ind]() - - -def mkColor(*args): - """ - Convenience function for constructing QColor from a variety of argument types. Accepted arguments are: - - ================ ================================================ - 'c' one of: r, g, b, c, m, y, k, w - R, G, B, [A] integers 0-255 - (R, G, B, [A]) tuple of integers 0-255 - float greyscale, 0.0-1.0 - int see :func:`intColor() ` - (int, hues) see :func:`intColor() ` - "RGB" hexadecimal strings; may begin with '#' - "RGBA" - "RRGGBB" - "RRGGBBAA" - QColor QColor instance; makes a copy. - ================ ================================================ - """ - err = 'Not sure how to make a color from "%s"' % str(args) - if len(args) == 1: - if isinstance(args[0], QtGui.QColor): - return QtGui.QColor(args[0]) - elif isinstance(args[0], float): - r = g = b = int(args[0] * 255) - a = 255 - elif isinstance(args[0], basestring): - c = args[0] - if c[0] == '#': - c = c[1:] - if len(c) == 1: - (r, g, b, a) = Colors[c] - if len(c) == 3: - r = int(c[0]*2, 16) - g = int(c[1]*2, 16) - b = int(c[2]*2, 16) - a = 255 - elif len(c) == 4: - r = int(c[0]*2, 16) - g = int(c[1]*2, 16) - b = int(c[2]*2, 16) - a = int(c[3]*2, 16) - elif len(c) == 6: - r = int(c[0:2], 16) - g = int(c[2:4], 16) - b = int(c[4:6], 16) - a = 255 - elif len(c) == 8: - r = int(c[0:2], 16) - g = int(c[2:4], 16) - b = int(c[4:6], 16) - a = int(c[6:8], 16) - elif hasattr(args[0], '__len__'): - if len(args[0]) == 3: - (r, g, b) = args[0] - a = 255 - elif len(args[0]) == 4: - (r, g, b, a) = args[0] - elif len(args[0]) == 2: - return intColor(*args[0]) - else: - raise Exception(err) - elif type(args[0]) == int: - return intColor(args[0]) - else: - raise Exception(err) - elif len(args) == 3: - (r, g, b) = args - a = 255 - elif len(args) == 4: - (r, g, b, a) = args - else: - raise Exception(err) - - args = [r,g,b,a] - args = [0 if np.isnan(a) or np.isinf(a) else a for a in args] - args = list(map(int, args)) - return QtGui.QColor(*args) - - -def mkBrush(*args, **kwds): - """ - | Convenience function for constructing Brush. - | This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() ` - | Calling mkBrush(None) returns an invisible brush. - """ - if 'color' in kwds: - color = kwds['color'] - elif len(args) == 1: - arg = args[0] - if arg is None: - return QtGui.QBrush(QtCore.Qt.NoBrush) - elif isinstance(arg, QtGui.QBrush): - return QtGui.QBrush(arg) - else: - color = arg - elif len(args) > 1: - color = args - return QtGui.QBrush(mkColor(color)) - -def mkPen(*args, **kargs): - """ - Convenience function for constructing QPen. - - Examples:: - - mkPen(color) - mkPen(color, width=2) - mkPen(cosmetic=False, width=4.5, color='r') - mkPen({'color': "FF0", width: 2}) - mkPen(None) # (no pen) - - In these examples, *color* may be replaced with any arguments accepted by :func:`mkColor() ` """ - - color = kargs.get('color', None) - width = kargs.get('width', 1) - style = kargs.get('style', None) - dash = kargs.get('dash', None) - cosmetic = kargs.get('cosmetic', True) - hsv = kargs.get('hsv', None) - - if len(args) == 1: - arg = args[0] - if isinstance(arg, dict): - return mkPen(**arg) - if isinstance(arg, QtGui.QPen): - return QtGui.QPen(arg) ## return a copy of this pen - elif arg is None: - style = QtCore.Qt.NoPen - else: - color = arg - if len(args) > 1: - color = args - - if color is None: - color = mkColor(200, 200, 200) - if hsv is not None: - color = hsvColor(*hsv) - else: - color = mkColor(color) - - pen = QtGui.QPen(QtGui.QBrush(color), width) - pen.setCosmetic(cosmetic) - if style is not None: - pen.setStyle(style) - if dash is not None: - pen.setDashPattern(dash) - return pen - -def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0): - """Generate a QColor from HSVa values. (all arguments are float 0.0-1.0)""" - c = QtGui.QColor() - c.setHsvF(hue, sat, val, alpha) - return c - - -def colorTuple(c): - """Return a tuple (R,G,B,A) from a QColor""" - return (c.red(), c.green(), c.blue(), c.alpha()) - -def colorStr(c): - """Generate a hex string code from a QColor""" - return ('%02x'*4) % colorTuple(c) - -def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255, **kargs): - """ - Creates a QColor from a single index. Useful for stepping through a predefined list of colors. - - The argument *index* determines which color from the set will be returned. All other arguments determine what the set of predefined colors will be - - Colors are chosen by cycling across hues while varying the value (brightness). - By default, this selects from a list of 9 hues.""" - hues = int(hues) - values = int(values) - ind = int(index) % (hues * values) - indh = ind % hues - indv = ind / hues - if values > 1: - v = minValue + indv * ((maxValue-minValue) / (values-1)) - else: - v = maxValue - h = minHue + (indh * (maxHue-minHue)) / hues - - c = QtGui.QColor() - c.setHsv(h, sat, v) - c.setAlpha(alpha) - return c - -def glColor(*args, **kargs): - """ - Convert a color to OpenGL color format (r,g,b,a) floats 0.0-1.0 - Accepts same arguments as :func:`mkColor `. - """ - c = mkColor(*args, **kargs) - return (c.red()/255., c.green()/255., c.blue()/255., c.alpha()/255.) - - - -def makeArrowPath(headLen=20, tipAngle=20, tailLen=20, tailWidth=3, baseAngle=0): - """ - Construct a path outlining an arrow with the given dimensions. - The arrow points in the -x direction with tip positioned at 0,0. - If *tipAngle* is supplied (in degrees), it overrides *headWidth*. - If *tailLen* is None, no tail will be drawn. - """ - headWidth = headLen * np.tan(tipAngle * 0.5 * np.pi/180.) - path = QtGui.QPainterPath() - path.moveTo(0,0) - path.lineTo(headLen, -headWidth) - if tailLen is None: - innerY = headLen - headWidth * np.tan(baseAngle*np.pi/180.) - path.lineTo(innerY, 0) - else: - tailWidth *= 0.5 - innerY = headLen - (headWidth-tailWidth) * np.tan(baseAngle*np.pi/180.) - path.lineTo(innerY, -tailWidth) - path.lineTo(headLen + tailLen, -tailWidth) - path.lineTo(headLen + tailLen, tailWidth) - path.lineTo(innerY, tailWidth) - path.lineTo(headLen, headWidth) - path.lineTo(0,0) - return path - - - -def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs): - """ - Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data. - - The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates (see the scipy documentation for more information about this). - - For a graphical interface to this function, see :func:`ROI.getArrayRegion ` - - ============== ==================================================================================================== - Arguments: - *data* (ndarray) the original dataset - *shape* the shape of the slice to take (Note the return value may have more dimensions than len(shape)) - *origin* the location in the original dataset that will become the origin of the sliced data. - *vectors* list of unit vectors which point in the direction of the slice axes. Each vector must have the same - length as *axes*. If the vectors are not unit length, the result will be scaled relative to the - original data. If the vectors are not orthogonal, the result will be sheared relative to the - original data. - *axes* The axes in the original dataset which correspond to the slice *vectors* - *order* The order of spline interpolation. Default is 1 (linear). See scipy.ndimage.map_coordinates - for more information. - *returnCoords* If True, return a tuple (result, coords) where coords is the array of coordinates used to select - values from the original dataset. - *All extra keyword arguments are passed to scipy.ndimage.map_coordinates.* - -------------------------------------------------------------------------------------------------------------------- - ============== ==================================================================================================== - - Note the following must be true: - - | len(shape) == len(vectors) - | len(origin) == len(axes) == len(vectors[i]) - - Example: start with a 4D fMRI data set, take a diagonal-planar slice out of the last 3 axes - - * data = array with dims (time, x, y, z) = (100, 40, 40, 40) - * The plane to pull out is perpendicular to the vector (x,y,z) = (1,1,1) - * The origin of the slice will be at (x,y,z) = (40, 0, 0) - * We will slice a 20x20 plane from each timepoint, giving a final shape (100, 20, 20) - - The call for this example would look like:: - - affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3)) - - """ - if not HAVE_SCIPY: - raise Exception("This function requires the scipy library, but it does not appear to be importable.") - - # sanity check - if len(shape) != len(vectors): - raise Exception("shape and vectors must have same length.") - if len(origin) != len(axes): - raise Exception("origin and axes must have same length.") - for v in vectors: - if len(v) != len(axes): - raise Exception("each vector must be same length as axes.") - - shape = list(map(np.ceil, shape)) - - ## transpose data so slice axes come first - trAx = list(range(data.ndim)) - for x in axes: - trAx.remove(x) - tr1 = tuple(axes) + tuple(trAx) - data = data.transpose(tr1) - #print "tr1:", tr1 - ## dims are now [(slice axes), (other axes)] - - - ## make sure vectors are arrays - if not isinstance(vectors, np.ndarray): - vectors = np.array(vectors) - if not isinstance(origin, np.ndarray): - origin = np.array(origin) - origin.shape = (len(axes),) + (1,)*len(shape) - - ## Build array of sample locations. - grid = np.mgrid[tuple([slice(0,x) for x in shape])] ## mesh grid of indexes - #print shape, grid.shape - x = (grid[np.newaxis,...] * vectors.transpose()[(Ellipsis,) + (np.newaxis,)*len(shape)]).sum(axis=1) ## magic - x += origin - #print "X values:" - #print x - ## iterate manually over unused axes since map_coordinates won't do it for us - extraShape = data.shape[len(axes):] - output = np.empty(tuple(shape) + extraShape, dtype=data.dtype) - for inds in np.ndindex(*extraShape): - ind = (Ellipsis,) + inds - #print data[ind].shape, x.shape, output[ind].shape, output.shape - output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs) - - tr = list(range(output.ndim)) - trb = [] - for i in range(min(axes)): - ind = tr1.index(i) + (len(shape)-len(axes)) - tr.remove(ind) - trb.append(ind) - tr2 = tuple(trb+tr) - - ## Untranspose array before returning - output = output.transpose(tr2) - if returnCoords: - return (output, x) - else: - return output - -def transformToArray(tr): - """ - Given a QTransform, return a 3x3 numpy array. - Given a QMatrix4x4, return a 4x4 numpy array. - - Example: map an array of x,y coordinates through a transform:: - - ## coordinates to map are (1,5), (2,6), (3,7), and (4,8) - coords = np.array([[1,2,3,4], [5,6,7,8], [1,1,1,1]]) # the extra '1' coordinate is needed for translation to work - - ## Make an example transform - tr = QtGui.QTransform() - tr.translate(3,4) - tr.scale(2, 0.1) - - ## convert to array - m = pg.transformToArray()[:2] # ignore the perspective portion of the transformation - - ## map coordinates through transform - mapped = np.dot(m, coords) - """ - #return np.array([[tr.m11(), tr.m12(), tr.m13()],[tr.m21(), tr.m22(), tr.m23()],[tr.m31(), tr.m32(), tr.m33()]]) - ## The order of elements given by the method names m11..m33 is misleading-- - ## It is most common for x,y translation to occupy the positions 1,3 and 2,3 in - ## a transformation matrix. However, with QTransform these values appear at m31 and m32. - ## So the correct interpretation is transposed: - if isinstance(tr, QtGui.QTransform): - return np.array([[tr.m11(), tr.m21(), tr.m31()], [tr.m12(), tr.m22(), tr.m32()], [tr.m13(), tr.m23(), tr.m33()]]) - elif isinstance(tr, QtGui.QMatrix4x4): - return np.array(tr.copyDataTo()).reshape(4,4) - else: - raise Exception("Transform argument must be either QTransform or QMatrix4x4.") - -def transformCoordinates(tr, coords, transpose=False): - """ - Map a set of 2D or 3D coordinates through a QTransform or QMatrix4x4. - The shape of coords must be (2,...) or (3,...) - The mapping will _ignore_ any perspective transformations. - - For coordinate arrays with ndim=2, this is basically equivalent to matrix multiplication. - Most arrays, however, prefer to put the coordinate axis at the end (eg. shape=(...,3)). To - allow this, use transpose=True. - - """ - - if transpose: - ## move last axis to beginning. This transposition will be reversed before returning the mapped coordinates. - coords = coords.transpose((coords.ndim-1,) + tuple(range(0,coords.ndim-1))) - - nd = coords.shape[0] - if isinstance(tr, np.ndarray): - m = tr - else: - m = transformToArray(tr) - m = m[:m.shape[0]-1] # remove perspective - - ## If coords are 3D and tr is 2D, assume no change for Z axis - if m.shape == (2,3) and nd == 3: - m2 = np.zeros((3,4)) - m2[:2, :2] = m[:2,:2] - m2[:2, 3] = m[:2,2] - m2[2,2] = 1 - m = m2 - - ## if coords are 2D and tr is 3D, ignore Z axis - if m.shape == (3,4) and nd == 2: - m2 = np.empty((2,3)) - m2[:,:2] = m[:2,:2] - m2[:,2] = m[:2,3] - m = m2 - - ## reshape tr and coords to prepare for multiplication - m = m.reshape(m.shape + (1,)*(coords.ndim-1)) - coords = coords[np.newaxis, ...] - - # separate scale/rotate and translation - translate = m[:,-1] - m = m[:, :-1] - - ## map coordinates and return - mapped = (m*coords).sum(axis=1) ## apply scale/rotate - mapped += translate - - if transpose: - ## move first axis to end. - mapped = mapped.transpose(tuple(range(1,mapped.ndim)) + (0,)) - return mapped - - - - -def solve3DTransform(points1, points2): - """ - Find a 3D transformation matrix that maps points1 onto points2. - Points must be specified as a list of 4 Vectors. - """ - if not HAVE_SCIPY: - raise Exception("This function depends on the scipy library, but it does not appear to be importable.") - A = np.array([[points1[i].x(), points1[i].y(), points1[i].z(), 1] for i in range(4)]) - B = np.array([[points2[i].x(), points2[i].y(), points2[i].z(), 1] for i in range(4)]) - - ## solve 3 sets of linear equations to determine transformation matrix elements - matrix = np.zeros((4,4)) - for i in range(3): - matrix[i] = scipy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix - - return matrix - -def solveBilinearTransform(points1, points2): - """ - Find a bilinear transformation matrix (2x4) that maps points1 onto points2. - Points must be specified as a list of 4 Vector, Point, QPointF, etc. - - To use this matrix to map a point [x,y]:: - - mapped = np.dot(matrix, [x*y, x, y, 1]) - """ - if not HAVE_SCIPY: - raise Exception("This function depends on the scipy library, but it does not appear to be importable.") - ## A is 4 rows (points) x 4 columns (xy, x, y, 1) - ## B is 4 rows (points) x 2 columns (x, y) - A = np.array([[points1[i].x()*points1[i].y(), points1[i].x(), points1[i].y(), 1] for i in range(4)]) - B = np.array([[points2[i].x(), points2[i].y()] for i in range(4)]) - - ## solve 2 sets of linear equations to determine transformation matrix elements - matrix = np.zeros((2,4)) - for i in range(2): - matrix[i] = scipy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix - - return matrix - -def rescaleData(data, scale, offset, dtype=None): - """Return data rescaled and optionally cast to a new dtype:: - - data => (data-offset) * scale - - Uses scipy.weave (if available) to improve performance. - """ - if dtype is None: - dtype = data.dtype - else: - dtype = np.dtype(dtype) - - try: - if not pg.getConfigOption('useWeave'): - raise Exception('Weave is disabled; falling back to slower version.') - - ## require native dtype when using weave - if not data.dtype.isnative: - data = data.astype(data.dtype.newbyteorder('=')) - if not dtype.isnative: - weaveDtype = dtype.newbyteorder('=') - else: - weaveDtype = dtype - - newData = np.empty((data.size,), dtype=weaveDtype) - flat = np.ascontiguousarray(data).reshape(data.size) - size = data.size - - code = """ - double sc = (double)scale; - double off = (double)offset; - for( int i=0; i0 and max->*scale*:: - - rescaled = (clip(data, min, max) - min) * (*scale* / (max - min)) - - It is also possible to use a 2D (N,2) array of values for levels. In this case, - it is assumed that each pair of min,max values in the levels array should be - applied to a different subset of the input data (for example, the input data may - already have RGB values and the levels are used to independently scale each - channel). The use of this feature requires that levels.shape[0] == data.shape[-1]. - scale The maximum value to which data will be rescaled before being passed through the - lookup table (or returned if there is no lookup table). By default this will - be set to the length of the lookup table, or 256 is no lookup table is provided. - For OpenGL color specifications (as in GLColor4f) use scale=1.0 - lut Optional lookup table (array with dtype=ubyte). - Values in data will be converted to color by indexing directly from lut. - The output data shape will be input.shape + lut.shape[1:]. - - Note: the output of makeARGB will have the same dtype as the lookup table, so - for conversion to QImage, the dtype must be ubyte. - - Lookup tables can be built using GradientWidget. - useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures). - The default is False, which returns in ARGB order for use with QImage - (Note that 'ARGB' is a term used by the Qt documentation; the _actual_ order - is BGRA). - ============ ================================================================================== - """ - prof = debug.Profiler('functions.makeARGB', disabled=True) - - if lut is not None and not isinstance(lut, np.ndarray): - lut = np.array(lut) - if levels is not None and not isinstance(levels, np.ndarray): - levels = np.array(levels) - - ## sanity checks - #if data.ndim == 3: - #if data.shape[2] not in (3,4): - #raise Exception("data.shape[2] must be 3 or 4") - ##if lut is not None: - ##raise Exception("can not use lookup table with 3D data") - #elif data.ndim != 2: - #raise Exception("data must be 2D or 3D") - - #if lut is not None: - ##if lut.ndim == 2: - ##if lut.shape[1] : - ##raise Exception("lut.shape[1] must be 3 or 4") - ##elif lut.ndim != 1: - ##raise Exception("lut must be 1D or 2D") - #if lut.dtype != np.ubyte: - #raise Exception('lookup table must have dtype=ubyte (got %s instead)' % str(lut.dtype)) - - - if levels is not None: - if levels.ndim == 1: - if len(levels) != 2: - raise Exception('levels argument must have length 2') - elif levels.ndim == 2: - if lut is not None and lut.ndim > 1: - raise Exception('Cannot make ARGB data when bot levels and lut have ndim > 2') - if levels.shape != (data.shape[-1], 2): - raise Exception('levels must have shape (data.shape[-1], 2)') - else: - print(levels) - raise Exception("levels argument must be 1D or 2D.") - #levels = np.array(levels) - #if levels.shape == (2,): - #pass - #elif levels.shape in [(3,2), (4,2)]: - #if data.ndim == 3: - #raise Exception("Can not use 2D levels with 3D data.") - #if lut is not None: - #raise Exception('Can not use 2D levels and lookup table together.') - #else: - #raise Exception("Levels must have shape (2,) or (3,2) or (4,2)") - - prof.mark('1') - - if scale is None: - if lut is not None: - scale = lut.shape[0] - else: - scale = 255. - - ## Apply levels if given - if levels is not None: - - if isinstance(levels, np.ndarray) and levels.ndim == 2: - ## we are going to rescale each channel independently - if levels.shape[0] != data.shape[-1]: - raise Exception("When rescaling multi-channel data, there must be the same number of levels as channels (data.shape[-1] == levels.shape[0])") - newData = np.empty(data.shape, dtype=int) - for i in range(data.shape[-1]): - minVal, maxVal = levels[i] - if minVal == maxVal: - maxVal += 1e-16 - newData[...,i] = rescaleData(data[...,i], scale/(maxVal-minVal), minVal, dtype=int) - data = newData - else: - minVal, maxVal = levels - if minVal == maxVal: - maxVal += 1e-16 - data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int) - prof.mark('2') - - - ## apply LUT if given - if lut is not None: - data = applyLookupTable(data, lut) - else: - if data.dtype is not np.ubyte: - data = np.clip(data, 0, 255).astype(np.ubyte) - prof.mark('3') - - - ## copy data into ARGB ordered array - imgData = np.empty(data.shape[:2]+(4,), dtype=np.ubyte) - if data.ndim == 2: - data = data[..., np.newaxis] - - prof.mark('4') - - if useRGBA: - order = [0,1,2,3] ## array comes out RGBA - else: - order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image. - - if data.shape[2] == 1: - for i in range(3): - imgData[..., order[i]] = data[..., 0] - else: - for i in range(0, data.shape[2]): - imgData[..., order[i]] = data[..., i] - - prof.mark('5') - - if data.shape[2] == 4: - alpha = True - else: - alpha = False - imgData[..., 3] = 255 - - prof.mark('6') - - prof.finish() - return imgData, alpha - - -def makeQImage(imgData, alpha=None, copy=True, transpose=True): - """ - Turn an ARGB array into QImage. - By default, the data is copied; changes to the array will not - be reflected in the image. The image will be given a 'data' attribute - pointing to the array which shares its data to prevent python - freeing that memory while the image is in use. - - =========== =================================================================== - Arguments: - imgData Array of data to convert. Must have shape (width, height, 3 or 4) - and dtype=ubyte. The order of values in the 3rd axis must be - (b, g, r, a). - alpha If True, the QImage returned will have format ARGB32. If False, - the format will be RGB32. By default, _alpha_ is True if - array.shape[2] == 4. - copy If True, the data is copied before converting to QImage. - If False, the new QImage points directly to the data in the array. - Note that the array must be contiguous for this to work - (see numpy.ascontiguousarray). - transpose If True (the default), the array x/y axes are transposed before - creating the image. Note that Qt expects the axes to be in - (height, width) order whereas pyqtgraph usually prefers the - opposite. - =========== =================================================================== - """ - ## create QImage from buffer - prof = debug.Profiler('functions.makeQImage', disabled=True) - - ## If we didn't explicitly specify alpha, check the array shape. - if alpha is None: - alpha = (imgData.shape[2] == 4) - - copied = False - if imgData.shape[2] == 3: ## need to make alpha channel (even if alpha==False; QImage requires 32 bpp) - if copy is True: - d2 = np.empty(imgData.shape[:2] + (4,), dtype=imgData.dtype) - d2[:,:,:3] = imgData - d2[:,:,3] = 255 - imgData = d2 - copied = True - else: - raise Exception('Array has only 3 channels; cannot make QImage without copying.') - - if alpha: - imgFormat = QtGui.QImage.Format_ARGB32 - else: - imgFormat = QtGui.QImage.Format_RGB32 - - if transpose: - imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite - - if not imgData.flags['C_CONTIGUOUS']: - if copy is False: - extra = ' (try setting transpose=False)' if transpose else '' - raise Exception('Array is not contiguous; cannot make QImage without copying.'+extra) - imgData = np.ascontiguousarray(imgData) - copied = True - - if copy is True and copied is False: - imgData = imgData.copy() - - if USE_PYSIDE: - ch = ctypes.c_char.from_buffer(imgData, 0) - img = QtGui.QImage(ch, imgData.shape[1], imgData.shape[0], imgFormat) - else: - #addr = ctypes.addressof(ctypes.c_char.from_buffer(imgData, 0)) - ## PyQt API for QImage changed between 4.9.3 and 4.9.6 (I don't know exactly which version it was) - ## So we first attempt the 4.9.6 API, then fall back to 4.9.3 - #addr = ctypes.c_char.from_buffer(imgData, 0) - #try: - #img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat) - #except TypeError: - #addr = ctypes.addressof(addr) - #img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat) - try: - img = QtGui.QImage(imgData.ctypes.data, imgData.shape[1], imgData.shape[0], imgFormat) - except: - if copy: - # does not leak memory, is not mutable - img = QtGui.QImage(buffer(imgData), imgData.shape[1], imgData.shape[0], imgFormat) - else: - # mutable, but leaks memory - img = QtGui.QImage(memoryview(imgData), imgData.shape[1], imgData.shape[0], imgFormat) - - img.data = imgData - return img - #try: - #buf = imgData.data - #except AttributeError: ## happens when image data is non-contiguous - #buf = imgData.data - - #prof.mark('1') - #qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat) - #prof.mark('2') - #qimage.data = imgData - #prof.finish() - #return qimage - -def imageToArray(img, copy=False, transpose=True): - """ - Convert a QImage into numpy array. The image must have format RGB32, ARGB32, or ARGB32_Premultiplied. - By default, the image is not copied; changes made to the array will appear in the QImage as well (beware: if - the QImage is collected before the array, there may be trouble). - The array will have shape (width, height, (b,g,r,a)). - """ - fmt = img.format() - ptr = img.bits() - if USE_PYSIDE: - arr = np.frombuffer(ptr, dtype=np.ubyte) - else: - ptr.setsize(img.byteCount()) - arr = np.asarray(ptr) - - if fmt == img.Format_RGB32: - arr = arr.reshape(img.height(), img.width(), 3) - elif fmt == img.Format_ARGB32 or fmt == img.Format_ARGB32_Premultiplied: - arr = arr.reshape(img.height(), img.width(), 4) - - if copy: - arr = arr.copy() - - if transpose: - return arr.transpose((1,0,2)) - else: - return arr - -def colorToAlpha(data, color): - """ - Given an RGBA image in *data*, convert *color* to be transparent. - *data* must be an array (w, h, 3 or 4) of ubyte values and *color* must be - an array (3) of ubyte values. - This is particularly useful for use with images that have a black or white background. - - Algorithm is taken from Gimp's color-to-alpha function in plug-ins/common/colortoalpha.c - Credit: - /* - * Color To Alpha plug-in v1.0 by Seth Burgess, sjburges@gimp.org 1999/05/14 - * with algorithm by clahey - */ - - """ - data = data.astype(float) - if data.shape[-1] == 3: ## add alpha channel if needed - d2 = np.empty(data.shape[:2]+(4,), dtype=data.dtype) - d2[...,:3] = data - d2[...,3] = 255 - data = d2 - - color = color.astype(float) - alpha = np.zeros(data.shape[:2]+(3,), dtype=float) - output = data.copy() - - for i in [0,1,2]: - d = data[...,i] - c = color[i] - mask = d > c - alpha[...,i][mask] = (d[mask] - c) / (255. - c) - imask = d < c - alpha[...,i][imask] = (c - d[imask]) / c - - output[...,3] = alpha.max(axis=2) * 255. - - mask = output[...,3] >= 1.0 ## avoid zero division while processing alpha channel - correction = 255. / output[...,3][mask] ## increase value to compensate for decreased alpha - for i in [0,1,2]: - output[...,i][mask] = ((output[...,i][mask]-color[i]) * correction) + color[i] - output[...,3][mask] *= data[...,3][mask] / 255. ## combine computed and previous alpha values - - #raise Exception() - return np.clip(output, 0, 255).astype(np.ubyte) - - - -def arrayToQPath(x, y, connect='all'): - """Convert an array of x,y coordinats to QPainterPath as efficiently as possible. - The *connect* argument may be 'all', indicating that each point should be - connected to the next; 'pairs', indicating that each pair of points - should be connected, or an array of int32 values (0 or 1) indicating - connections. - """ - - ## Create all vertices in path. The method used below creates a binary format so that all - ## vertices can be read in at once. This binary format may change in future versions of Qt, - ## so the original (slower) method is left here for emergencies: - #path.moveTo(x[0], y[0]) - #if connect == 'all': - #for i in range(1, y.shape[0]): - #path.lineTo(x[i], y[i]) - #elif connect == 'pairs': - #for i in range(1, y.shape[0]): - #if i%2 == 0: - #path.lineTo(x[i], y[i]) - #else: - #path.moveTo(x[i], y[i]) - #elif isinstance(connect, np.ndarray): - #for i in range(1, y.shape[0]): - #if connect[i] == 1: - #path.lineTo(x[i], y[i]) - #else: - #path.moveTo(x[i], y[i]) - #else: - #raise Exception('connect argument must be "all", "pairs", or array') - - ## Speed this up using >> operator - ## Format is: - ## numVerts(i4) 0(i4) - ## x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect - ## x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex - ## ... - ## 0(i4) - ## - ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') - - path = QtGui.QPainterPath() - - #prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True) - n = x.shape[0] - # create empty array, pad with extra space on either end - arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) - # write first two integers - #prof.mark('allocate empty') - byteview = arr.view(dtype=np.ubyte) - byteview[:12] = 0 - byteview.data[12:20] = struct.pack('>ii', n, 0) - #prof.mark('pack header') - # Fill array with vertex values - arr[1:-1]['x'] = x - arr[1:-1]['y'] = y - - # decide which points are connected by lines - if connect == 'pairs': - connect = np.empty((n/2,2), dtype=np.int32) - connect[:,0] = 1 - connect[:,1] = 0 - connect = connect.flatten() - if connect == 'finite': - connect = np.isfinite(x) & np.isfinite(y) - arr[1:-1]['c'] = connect - if connect == 'all': - arr[1:-1]['c'] = 1 - elif isinstance(connect, np.ndarray): - arr[1:-1]['c'] = connect - else: - raise Exception('connect argument must be "all", "pairs", or array') - - #prof.mark('fill array') - # write last 0 - lastInd = 20*(n+1) - byteview.data[lastInd:lastInd+4] = struct.pack('>i', 0) - #prof.mark('footer') - # create datastream object and stream into path - - ## Avoiding this method because QByteArray(str) leaks memory in PySide - #buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here - - path.strn = byteview.data[12:lastInd+4] # make sure data doesn't run away - try: - buf = QtCore.QByteArray.fromRawData(path.strn) - except TypeError: - buf = QtCore.QByteArray(bytes(path.strn)) - #prof.mark('create buffer') - ds = QtCore.QDataStream(buf) - - ds >> path - #prof.mark('load') - - #prof.finish() - - return path - -#def isosurface(data, level): - #""" - #Generate isosurface from volumetric data using marching tetrahedra algorithm. - #See Paul Bourke, "Polygonising a Scalar Field Using Tetrahedrons" (http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/) - - #*data* 3D numpy array of scalar values - #*level* The level at which to generate an isosurface - #""" - - #facets = [] - - ### mark everything below the isosurface level - #mask = data < level - - #### make eight sub-fields - #fields = np.empty((2,2,2), dtype=object) - #slices = [slice(0,-1), slice(1,None)] - #for i in [0,1]: - #for j in [0,1]: - #for k in [0,1]: - #fields[i,j,k] = mask[slices[i], slices[j], slices[k]] - - - - ### split each cell into 6 tetrahedra - ### these all have the same 'orienation'; points 1,2,3 circle - ### clockwise around point 0 - #tetrahedra = [ - #[(0,1,0), (1,1,1), (0,1,1), (1,0,1)], - #[(0,1,0), (0,1,1), (0,0,1), (1,0,1)], - #[(0,1,0), (0,0,1), (0,0,0), (1,0,1)], - #[(0,1,0), (0,0,0), (1,0,0), (1,0,1)], - #[(0,1,0), (1,0,0), (1,1,0), (1,0,1)], - #[(0,1,0), (1,1,0), (1,1,1), (1,0,1)] - #] - - ### each tetrahedron will be assigned an index - ### which determines how to generate its facets. - ### this structure is: - ### facets[index][facet1, facet2, ...] - ### where each facet is triangular and its points are each - ### interpolated between two points on the tetrahedron - ### facet = [(p1a, p1b), (p2a, p2b), (p3a, p3b)] - ### facet points always circle clockwise if you are looking - ### at them from below the isosurface. - #indexFacets = [ - #[], ## all above - #[[(0,1), (0,2), (0,3)]], # 0 below - #[[(1,0), (1,3), (1,2)]], # 1 below - #[[(0,2), (1,3), (1,2)], [(0,2), (0,3), (1,3)]], # 0,1 below - #[[(2,0), (2,1), (2,3)]], # 2 below - #[[(0,3), (1,2), (2,3)], [(0,3), (0,1), (1,2)]], # 0,2 below - #[[(1,0), (2,3), (2,0)], [(1,0), (1,3), (2,3)]], # 1,2 below - #[[(3,0), (3,1), (3,2)]], # 3 above - #[[(3,0), (3,2), (3,1)]], # 3 below - #[[(1,0), (2,0), (2,3)], [(1,0), (2,3), (1,3)]], # 0,3 below - #[[(0,3), (2,3), (1,2)], [(0,3), (1,2), (0,1)]], # 1,3 below - #[[(2,0), (2,3), (2,1)]], # 0,1,3 below - #[[(0,2), (1,2), (1,3)], [(0,2), (1,3), (0,3)]], # 2,3 below - #[[(1,0), (1,2), (1,3)]], # 0,2,3 below - #[[(0,1), (0,3), (0,2)]], # 1,2,3 below - #[] ## all below - #] - - #for tet in tetrahedra: - - ### get the 4 fields for this tetrahedron - #tetFields = [fields[c] for c in tet] - - ### generate an index for each grid cell - #index = tetFields[0] + tetFields[1]*2 + tetFields[2]*4 + tetFields[3]*8 - - ### add facets - #for i in xrange(index.shape[0]): # data x-axis - #for j in xrange(index.shape[1]): # data y-axis - #for k in xrange(index.shape[2]): # data z-axis - #for f in indexFacets[index[i,j,k]]: # faces to generate for this tet - #pts = [] - #for l in [0,1,2]: # points in this face - #p1 = tet[f[l][0]] # tet corner 1 - #p2 = tet[f[l][1]] # tet corner 2 - #pts.append([(p1[x]+p2[x])*0.5+[i,j,k][x]+0.5 for x in [0,1,2]]) ## interpolate between tet corners - #facets.append(pts) - - #return facets - - -def isocurve(data, level, connected=False, extendToEdge=False, path=False): - """ - Generate isocurve from 2D data using marching squares algorithm. - - ============= ========================================================= - Arguments - data 2D numpy array of scalar values - level The level at which to generate an isosurface - connected If False, return a single long list of point pairs - If True, return multiple long lists of connected point - locations. (This is slower but better for drawing - continuous lines) - extendToEdge If True, extend the curves to reach the exact edges of - the data. - path if True, return a QPainterPath rather than a list of - vertex coordinates. This forces connected=True. - ============= ========================================================= - - This function is SLOW; plenty of room for optimization here. - """ - - if path is True: - connected = True - - if extendToEdge: - d2 = np.empty((data.shape[0]+2, data.shape[1]+2), dtype=data.dtype) - d2[1:-1, 1:-1] = data - d2[0, 1:-1] = data[0] - d2[-1, 1:-1] = data[-1] - d2[1:-1, 0] = data[:, 0] - d2[1:-1, -1] = data[:, -1] - d2[0,0] = d2[0,1] - d2[0,-1] = d2[1,-1] - d2[-1,0] = d2[-1,1] - d2[-1,-1] = d2[-1,-2] - data = d2 - - sideTable = [ - [], - [0,1], - [1,2], - [0,2], - [0,3], - [1,3], - [0,1,2,3], - [2,3], - [2,3], - [0,1,2,3], - [1,3], - [0,3], - [0,2], - [1,2], - [0,1], - [] - ] - - edgeKey=[ - [(0,1), (0,0)], - [(0,0), (1,0)], - [(1,0), (1,1)], - [(1,1), (0,1)] - ] - - - lines = [] - - ## mark everything below the isosurface level - mask = data < level - - ### make four sub-fields and compute indexes for grid cells - index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) - fields = np.empty((2,2), dtype=object) - slices = [slice(0,-1), slice(1,None)] - for i in [0,1]: - for j in [0,1]: - fields[i,j] = mask[slices[i], slices[j]] - #vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme - vertIndex = i+2*j - #print i,j,k," : ", fields[i,j,k], 2**vertIndex - index += fields[i,j] * 2**vertIndex - #print index - #print index - - ## add lines - for i in range(index.shape[0]): # data x-axis - for j in range(index.shape[1]): # data y-axis - sides = sideTable[index[i,j]] - for l in range(0, len(sides), 2): ## faces for this grid cell - edges = sides[l:l+2] - pts = [] - for m in [0,1]: # points in this face - p1 = edgeKey[edges[m]][0] # p1, p2 are points at either side of an edge - p2 = edgeKey[edges[m]][1] - v1 = data[i+p1[0], j+p1[1]] # v1 and v2 are the values at p1 and p2 - v2 = data[i+p2[0], j+p2[1]] - f = (level-v1) / (v2-v1) - fi = 1.0 - f - p = ( ## interpolate between corners - p1[0]*fi + p2[0]*f + i + 0.5, - p1[1]*fi + p2[1]*f + j + 0.5 - ) - if extendToEdge: - ## check bounds - p = ( - min(data.shape[0]-2, max(0, p[0]-1)), - min(data.shape[1]-2, max(0, p[1]-1)), - ) - if connected: - gridKey = i + (1 if edges[m]==2 else 0), j + (1 if edges[m]==3 else 0), edges[m]%2 - pts.append((p, gridKey)) ## give the actual position and a key identifying the grid location (for connecting segments) - else: - pts.append(p) - - lines.append(pts) - - if not connected: - return lines - - ## turn disjoint list of segments into continuous lines - - #lines = [[2,5], [5,4], [3,4], [1,3], [6,7], [7,8], [8,6], [11,12], [12,15], [11,13], [13,14]] - #lines = [[(float(a), a), (float(b), b)] for a,b in lines] - points = {} ## maps each point to its connections - for a,b in lines: - if a[1] not in points: - points[a[1]] = [] - points[a[1]].append([a,b]) - if b[1] not in points: - points[b[1]] = [] - points[b[1]].append([b,a]) - - ## rearrange into chains - for k in list(points.keys()): - try: - chains = points[k] - except KeyError: ## already used this point elsewhere - continue - #print "===========", k - for chain in chains: - #print " chain:", chain - x = None - while True: - if x == chain[-1][1]: - break ## nothing left to do on this chain - - x = chain[-1][1] - if x == k: - break ## chain has looped; we're done and can ignore the opposite chain - y = chain[-2][1] - connects = points[x] - for conn in connects[:]: - if conn[1][1] != y: - #print " ext:", conn - chain.extend(conn[1:]) - #print " del:", x - del points[x] - if chain[0][1] == chain[-1][1]: # looped chain; no need to continue the other direction - chains.pop() - break - - - ## extract point locations - lines = [] - for chain in points.values(): - if len(chain) == 2: - chain = chain[1][1:][::-1] + chain[0] # join together ends of chain - else: - chain = chain[0] - lines.append([p[0] for p in chain]) - - if not path: - return lines ## a list of pairs of points - - path = QtGui.QPainterPath() - for line in lines: - path.moveTo(*line[0]) - for p in line[1:]: - path.lineTo(*p) - - return path - - -def traceImage(image, values, smooth=0.5): - """ - Convert an image to a set of QPainterPath curves. - One curve will be generated for each item in *values*; each curve outlines the area - of the image that is closer to its value than to any others. - - If image is RGB or RGBA, then the shape of values should be (nvals, 3/4) - The parameter *smooth* is expressed in pixels. - """ - import scipy.ndimage as ndi - if values.ndim == 2: - values = values.T - values = values[np.newaxis, np.newaxis, ...].astype(float) - image = image[..., np.newaxis].astype(float) - diff = np.abs(image-values) - if values.ndim == 4: - diff = diff.sum(axis=2) - - labels = np.argmin(diff, axis=2) - - paths = [] - for i in range(diff.shape[-1]): - d = (labels==i).astype(float) - d = ndi.gaussian_filter(d, (smooth, smooth)) - lines = isocurve(d, 0.5, connected=True, extendToEdge=True) - path = QtGui.QPainterPath() - for line in lines: - path.moveTo(*line[0]) - for p in line[1:]: - path.lineTo(*p) - - paths.append(path) - return paths - - - -IsosurfaceDataCache = None -def isosurface(data, level): - """ - Generate isosurface from volumetric data using marching cubes algorithm. - See Paul Bourke, "Polygonising a Scalar Field" - (http://paulbourke.net/geometry/polygonise/) - - *data* 3D numpy array of scalar values - *level* The level at which to generate an isosurface - - Returns an array of vertex coordinates (Nv, 3) and an array of - per-face vertex indexes (Nf, 3) - """ - ## For improvement, see: - ## - ## Efficient implementation of Marching Cubes' cases with topological guarantees. - ## Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan Tavares. - ## Journal of Graphics Tools 8(2): pp. 1-15 (december 2003) - - ## Precompute lookup tables on the first run - global IsosurfaceDataCache - if IsosurfaceDataCache is None: - ## map from grid cell index to edge index. - ## grid cell index tells us which corners are below the isosurface, - ## edge index tells us which edges are cut by the isosurface. - ## (Data stolen from Bourk; see above.) - edgeTable = np.array([ - 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, - 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, - 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, - 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, - 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, - 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, - 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, - 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, - 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, - 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, - 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, - 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, - 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, - 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, - 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , - 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, - 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, - 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, - 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, - 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, - 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, - 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, - 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, - 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, - 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, - 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, - 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, - 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, - 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, - 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, - 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, - 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ], dtype=np.uint16) - - ## Table of triangles to use for filling each grid cell. - ## Each set of three integers tells us which three edges to - ## draw a triangle between. - ## (Data stolen from Bourk; see above.) - triTable = [ - [], - [0, 8, 3], - [0, 1, 9], - [1, 8, 3, 9, 8, 1], - [1, 2, 10], - [0, 8, 3, 1, 2, 10], - [9, 2, 10, 0, 2, 9], - [2, 8, 3, 2, 10, 8, 10, 9, 8], - [3, 11, 2], - [0, 11, 2, 8, 11, 0], - [1, 9, 0, 2, 3, 11], - [1, 11, 2, 1, 9, 11, 9, 8, 11], - [3, 10, 1, 11, 10, 3], - [0, 10, 1, 0, 8, 10, 8, 11, 10], - [3, 9, 0, 3, 11, 9, 11, 10, 9], - [9, 8, 10, 10, 8, 11], - [4, 7, 8], - [4, 3, 0, 7, 3, 4], - [0, 1, 9, 8, 4, 7], - [4, 1, 9, 4, 7, 1, 7, 3, 1], - [1, 2, 10, 8, 4, 7], - [3, 4, 7, 3, 0, 4, 1, 2, 10], - [9, 2, 10, 9, 0, 2, 8, 4, 7], - [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4], - [8, 4, 7, 3, 11, 2], - [11, 4, 7, 11, 2, 4, 2, 0, 4], - [9, 0, 1, 8, 4, 7, 2, 3, 11], - [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1], - [3, 10, 1, 3, 11, 10, 7, 8, 4], - [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4], - [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3], - [4, 7, 11, 4, 11, 9, 9, 11, 10], - [9, 5, 4], - [9, 5, 4, 0, 8, 3], - [0, 5, 4, 1, 5, 0], - [8, 5, 4, 8, 3, 5, 3, 1, 5], - [1, 2, 10, 9, 5, 4], - [3, 0, 8, 1, 2, 10, 4, 9, 5], - [5, 2, 10, 5, 4, 2, 4, 0, 2], - [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8], - [9, 5, 4, 2, 3, 11], - [0, 11, 2, 0, 8, 11, 4, 9, 5], - [0, 5, 4, 0, 1, 5, 2, 3, 11], - [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5], - [10, 3, 11, 10, 1, 3, 9, 5, 4], - [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10], - [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3], - [5, 4, 8, 5, 8, 10, 10, 8, 11], - [9, 7, 8, 5, 7, 9], - [9, 3, 0, 9, 5, 3, 5, 7, 3], - [0, 7, 8, 0, 1, 7, 1, 5, 7], - [1, 5, 3, 3, 5, 7], - [9, 7, 8, 9, 5, 7, 10, 1, 2], - [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3], - [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2], - [2, 10, 5, 2, 5, 3, 3, 5, 7], - [7, 9, 5, 7, 8, 9, 3, 11, 2], - [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11], - [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7], - [11, 2, 1, 11, 1, 7, 7, 1, 5], - [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11], - [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0], - [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0], - [11, 10, 5, 7, 11, 5], - [10, 6, 5], - [0, 8, 3, 5, 10, 6], - [9, 0, 1, 5, 10, 6], - [1, 8, 3, 1, 9, 8, 5, 10, 6], - [1, 6, 5, 2, 6, 1], - [1, 6, 5, 1, 2, 6, 3, 0, 8], - [9, 6, 5, 9, 0, 6, 0, 2, 6], - [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8], - [2, 3, 11, 10, 6, 5], - [11, 0, 8, 11, 2, 0, 10, 6, 5], - [0, 1, 9, 2, 3, 11, 5, 10, 6], - [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11], - [6, 3, 11, 6, 5, 3, 5, 1, 3], - [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6], - [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9], - [6, 5, 9, 6, 9, 11, 11, 9, 8], - [5, 10, 6, 4, 7, 8], - [4, 3, 0, 4, 7, 3, 6, 5, 10], - [1, 9, 0, 5, 10, 6, 8, 4, 7], - [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4], - [6, 1, 2, 6, 5, 1, 4, 7, 8], - [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7], - [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6], - [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9], - [3, 11, 2, 7, 8, 4, 10, 6, 5], - [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11], - [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6], - [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6], - [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6], - [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11], - [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7], - [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9], - [10, 4, 9, 6, 4, 10], - [4, 10, 6, 4, 9, 10, 0, 8, 3], - [10, 0, 1, 10, 6, 0, 6, 4, 0], - [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10], - [1, 4, 9, 1, 2, 4, 2, 6, 4], - [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4], - [0, 2, 4, 4, 2, 6], - [8, 3, 2, 8, 2, 4, 4, 2, 6], - [10, 4, 9, 10, 6, 4, 11, 2, 3], - [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6], - [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10], - [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1], - [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3], - [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1], - [3, 11, 6, 3, 6, 0, 0, 6, 4], - [6, 4, 8, 11, 6, 8], - [7, 10, 6, 7, 8, 10, 8, 9, 10], - [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10], - [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0], - [10, 6, 7, 10, 7, 1, 1, 7, 3], - [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7], - [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9], - [7, 8, 0, 7, 0, 6, 6, 0, 2], - [7, 3, 2, 6, 7, 2], - [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7], - [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7], - [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11], - [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1], - [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6], - [0, 9, 1, 11, 6, 7], - [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0], - [7, 11, 6], - [7, 6, 11], - [3, 0, 8, 11, 7, 6], - [0, 1, 9, 11, 7, 6], - [8, 1, 9, 8, 3, 1, 11, 7, 6], - [10, 1, 2, 6, 11, 7], - [1, 2, 10, 3, 0, 8, 6, 11, 7], - [2, 9, 0, 2, 10, 9, 6, 11, 7], - [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8], - [7, 2, 3, 6, 2, 7], - [7, 0, 8, 7, 6, 0, 6, 2, 0], - [2, 7, 6, 2, 3, 7, 0, 1, 9], - [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6], - [10, 7, 6, 10, 1, 7, 1, 3, 7], - [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8], - [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7], - [7, 6, 10, 7, 10, 8, 8, 10, 9], - [6, 8, 4, 11, 8, 6], - [3, 6, 11, 3, 0, 6, 0, 4, 6], - [8, 6, 11, 8, 4, 6, 9, 0, 1], - [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6], - [6, 8, 4, 6, 11, 8, 2, 10, 1], - [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6], - [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9], - [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3], - [8, 2, 3, 8, 4, 2, 4, 6, 2], - [0, 4, 2, 4, 6, 2], - [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8], - [1, 9, 4, 1, 4, 2, 2, 4, 6], - [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1], - [10, 1, 0, 10, 0, 6, 6, 0, 4], - [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3], - [10, 9, 4, 6, 10, 4], - [4, 9, 5, 7, 6, 11], - [0, 8, 3, 4, 9, 5, 11, 7, 6], - [5, 0, 1, 5, 4, 0, 7, 6, 11], - [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5], - [9, 5, 4, 10, 1, 2, 7, 6, 11], - [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5], - [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2], - [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6], - [7, 2, 3, 7, 6, 2, 5, 4, 9], - [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7], - [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0], - [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8], - [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7], - [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4], - [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10], - [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10], - [6, 9, 5, 6, 11, 9, 11, 8, 9], - [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5], - [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11], - [6, 11, 3, 6, 3, 5, 5, 3, 1], - [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6], - [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10], - [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5], - [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3], - [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2], - [9, 5, 6, 9, 6, 0, 0, 6, 2], - [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8], - [1, 5, 6, 2, 1, 6], - [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6], - [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0], - [0, 3, 8, 5, 6, 10], - [10, 5, 6], - [11, 5, 10, 7, 5, 11], - [11, 5, 10, 11, 7, 5, 8, 3, 0], - [5, 11, 7, 5, 10, 11, 1, 9, 0], - [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1], - [11, 1, 2, 11, 7, 1, 7, 5, 1], - [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11], - [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7], - [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2], - [2, 5, 10, 2, 3, 5, 3, 7, 5], - [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5], - [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2], - [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2], - [1, 3, 5, 3, 7, 5], - [0, 8, 7, 0, 7, 1, 1, 7, 5], - [9, 0, 3, 9, 3, 5, 5, 3, 7], - [9, 8, 7, 5, 9, 7], - [5, 8, 4, 5, 10, 8, 10, 11, 8], - [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0], - [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5], - [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4], - [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8], - [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11], - [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5], - [9, 4, 5, 2, 11, 3], - [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4], - [5, 10, 2, 5, 2, 4, 4, 2, 0], - [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9], - [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2], - [8, 4, 5, 8, 5, 3, 3, 5, 1], - [0, 4, 5, 1, 0, 5], - [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5], - [9, 4, 5], - [4, 11, 7, 4, 9, 11, 9, 10, 11], - [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11], - [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11], - [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4], - [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2], - [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3], - [11, 7, 4, 11, 4, 2, 2, 4, 0], - [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4], - [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9], - [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7], - [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10], - [1, 10, 2, 8, 7, 4], - [4, 9, 1, 4, 1, 7, 7, 1, 3], - [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1], - [4, 0, 3, 7, 4, 3], - [4, 8, 7], - [9, 10, 8, 10, 11, 8], - [3, 0, 9, 3, 9, 11, 11, 9, 10], - [0, 1, 10, 0, 10, 8, 8, 10, 11], - [3, 1, 10, 11, 3, 10], - [1, 2, 11, 1, 11, 9, 9, 11, 8], - [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9], - [0, 2, 11, 8, 0, 11], - [3, 2, 11], - [2, 3, 8, 2, 8, 10, 10, 8, 9], - [9, 10, 2, 0, 9, 2], - [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8], - [1, 10, 2], - [1, 3, 8, 9, 1, 8], - [0, 9, 1], - [0, 3, 8], - [] - ] - edgeShifts = np.array([ ## maps edge ID (0-11) to (x,y,z) cell offset and edge ID (0-2) - [0, 0, 0, 0], - [1, 0, 0, 1], - [0, 1, 0, 0], - [0, 0, 0, 1], - [0, 0, 1, 0], - [1, 0, 1, 1], - [0, 1, 1, 0], - [0, 0, 1, 1], - [0, 0, 0, 2], - [1, 0, 0, 2], - [1, 1, 0, 2], - [0, 1, 0, 2], - #[9, 9, 9, 9] ## fake - ], dtype=np.ubyte) - nTableFaces = np.array([len(f)/3 for f in triTable], dtype=np.ubyte) - faceShiftTables = [None] - for i in range(1,6): - ## compute lookup table of index: vertexes mapping - faceTableI = np.zeros((len(triTable), i*3), dtype=np.ubyte) - faceTableInds = np.argwhere(nTableFaces == i) - faceTableI[faceTableInds[:,0]] = np.array([triTable[j] for j in faceTableInds]) - faceTableI = faceTableI.reshape((len(triTable), i, 3)) - faceShiftTables.append(edgeShifts[faceTableI]) - - ## Let's try something different: - #faceTable = np.empty((256, 5, 3, 4), dtype=np.ubyte) # (grid cell index, faces, vertexes, edge lookup) - #for i,f in enumerate(triTable): - #f = np.array(f + [12] * (15-len(f))).reshape(5,3) - #faceTable[i] = edgeShifts[f] - - - IsosurfaceDataCache = (faceShiftTables, edgeShifts, edgeTable, nTableFaces) - else: - faceShiftTables, edgeShifts, edgeTable, nTableFaces = IsosurfaceDataCache - - - - ## mark everything below the isosurface level - mask = data < level - - ### make eight sub-fields and compute indexes for grid cells - index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) - fields = np.empty((2,2,2), dtype=object) - slices = [slice(0,-1), slice(1,None)] - for i in [0,1]: - for j in [0,1]: - for k in [0,1]: - fields[i,j,k] = mask[slices[i], slices[j], slices[k]] - vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme - index += fields[i,j,k] * 2**vertIndex - - ### Generate table of edges that have been cut - cutEdges = np.zeros([x+1 for x in index.shape]+[3], dtype=np.uint32) - edges = edgeTable[index] - for i, shift in enumerate(edgeShifts[:12]): - slices = [slice(shift[j],cutEdges.shape[j]+(shift[j]-1)) for j in range(3)] - cutEdges[slices[0], slices[1], slices[2], shift[3]] += edges & 2**i - - ## for each cut edge, interpolate to see where exactly the edge is cut and generate vertex positions - m = cutEdges > 0 - vertexInds = np.argwhere(m) ## argwhere is slow! - vertexes = vertexInds[:,:3].astype(np.float32) - dataFlat = data.reshape(data.shape[0]*data.shape[1]*data.shape[2]) - - ## re-use the cutEdges array as a lookup table for vertex IDs - cutEdges[vertexInds[:,0], vertexInds[:,1], vertexInds[:,2], vertexInds[:,3]] = np.arange(vertexInds.shape[0]) - - for i in [0,1,2]: - vim = vertexInds[:,3] == i - vi = vertexInds[vim, :3] - viFlat = (vi * (np.array(data.strides[:3]) // data.itemsize)[np.newaxis,:]).sum(axis=1) - v1 = dataFlat[viFlat] - v2 = dataFlat[viFlat + data.strides[i]//data.itemsize] - vertexes[vim,i] += (level-v1) / (v2-v1) - - ### compute the set of vertex indexes for each face. - - ## This works, but runs a bit slower. - #cells = np.argwhere((index != 0) & (index != 255)) ## all cells with at least one face - #cellInds = index[cells[:,0], cells[:,1], cells[:,2]] - #verts = faceTable[cellInds] - #mask = verts[...,0,0] != 9 - #verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges - #verts = verts[mask] - #faces = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want. - - - ## To allow this to be vectorized efficiently, we count the number of faces in each - ## grid cell and handle each group of cells with the same number together. - ## determine how many faces to assign to each grid cell - nFaces = nTableFaces[index] - totFaces = nFaces.sum() - faces = np.empty((totFaces, 3), dtype=np.uint32) - ptr = 0 - #import debug - #p = debug.Profiler('isosurface', disabled=False) - - ## this helps speed up an indexing operation later on - cs = np.array(cutEdges.strides)//cutEdges.itemsize - cutEdges = cutEdges.flatten() - - ## this, strangely, does not seem to help. - #ins = np.array(index.strides)/index.itemsize - #index = index.flatten() - - for i in range(1,6): - ### expensive: - #p.mark('1') - cells = np.argwhere(nFaces == i) ## all cells which require i faces (argwhere is expensive) - #p.mark('2') - if cells.shape[0] == 0: - continue - #cellInds = index[(cells*ins[np.newaxis,:]).sum(axis=1)] - cellInds = index[cells[:,0], cells[:,1], cells[:,2]] ## index values of cells to process for this round - #p.mark('3') - - ### expensive: - verts = faceShiftTables[i][cellInds] - #p.mark('4') - verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges - verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:]) - #p.mark('5') - - ### expensive: - #print verts.shape - verts = (verts * cs[np.newaxis, np.newaxis, :]).sum(axis=2) - #vertInds = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want. - vertInds = cutEdges[verts] - #p.mark('6') - nv = vertInds.shape[0] - #p.mark('7') - faces[ptr:ptr+nv] = vertInds #.reshape((nv, 3)) - #p.mark('8') - ptr += nv - - return vertexes, faces - - - -def invertQTransform(tr): - """Return a QTransform that is the inverse of *tr*. - Rasises an exception if tr is not invertible. - - Note that this function is preferred over QTransform.inverted() due to - bugs in that method. (specifically, Qt has floating-point precision issues - when determining whether a matrix is invertible) - """ - if not HAVE_SCIPY: - inv = tr.inverted() - if inv[1] is False: - raise Exception("Transform is not invertible.") - return inv[0] - arr = np.array([[tr.m11(), tr.m12(), tr.m13()], [tr.m21(), tr.m22(), tr.m23()], [tr.m31(), tr.m32(), tr.m33()]]) - inv = scipy.linalg.inv(arr) - return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1]) - - -def pseudoScatter(data, spacing=None, shuffle=True, bidir=False): - """ - Used for examining the distribution of values in a set. Produces scattering as in beeswarm or column scatter plots. - - Given a list of x-values, construct a set of y-values such that an x,y scatter-plot - will not have overlapping points (it will look similar to a histogram). - """ - inds = np.arange(len(data)) - if shuffle: - np.random.shuffle(inds) - - data = data[inds] - - if spacing is None: - spacing = 2.*np.std(data)/len(data)**0.5 - s2 = spacing**2 - - yvals = np.empty(len(data)) - if len(data) == 0: - return yvals - yvals[0] = 0 - for i in range(1,len(data)): - x = data[i] # current x value to be placed - x0 = data[:i] # all x values already placed - y0 = yvals[:i] # all y values already placed - y = 0 - - dx = (x0-x)**2 # x-distance to each previous point - xmask = dx < s2 # exclude anything too far away - - if xmask.sum() > 0: - if bidir: - dirs = [-1, 1] - else: - dirs = [1] - yopts = [] - for direction in dirs: - y = 0 - dx2 = dx[xmask] - dy = (s2 - dx2)**0.5 - limits = np.empty((2,len(dy))) # ranges of y-values to exclude - limits[0] = y0[xmask] - dy - limits[1] = y0[xmask] + dy - while True: - # ignore anything below this y-value - if direction > 0: - mask = limits[1] >= y - else: - mask = limits[0] <= y - - limits2 = limits[:,mask] - - # are we inside an excluded region? - mask = (limits2[0] < y) & (limits2[1] > y) - if mask.sum() == 0: - break - - if direction > 0: - y = limits2[:,mask].max() - else: - y = limits2[:,mask].min() - yopts.append(y) - if bidir: - y = yopts[0] if -yopts[0] < yopts[1] else yopts[1] - else: - y = yopts[0] - yvals[i] = y - - return yvals[np.argsort(inds)] ## un-shuffle values before returning diff --git a/pyqtgraph/graphicsItems/ArrowItem.py b/pyqtgraph/graphicsItems/ArrowItem.py deleted file mode 100644 index dcede02a..00000000 --- a/pyqtgraph/graphicsItems/ArrowItem.py +++ /dev/null @@ -1,124 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph.functions as fn -import numpy as np -__all__ = ['ArrowItem'] - -class ArrowItem(QtGui.QGraphicsPathItem): - """ - For displaying scale-invariant arrows. - For arrows pointing to a location on a curve, see CurveArrow - - """ - - - def __init__(self, **opts): - """ - Arrows can be initialized with any keyword arguments accepted by - the setStyle() method. - """ - QtGui.QGraphicsPathItem.__init__(self, opts.get('parent', None)) - if 'size' in opts: - opts['headLen'] = opts['size'] - if 'width' in opts: - opts['headWidth'] = opts['width'] - defOpts = { - 'pxMode': True, - 'angle': -150, ## If the angle is 0, the arrow points left - 'pos': (0,0), - 'headLen': 20, - 'tipAngle': 25, - 'baseAngle': 0, - 'tailLen': None, - 'tailWidth': 3, - 'pen': (200,200,200), - 'brush': (50,50,200), - } - defOpts.update(opts) - - self.setStyle(**defOpts) - - self.setPen(fn.mkPen(defOpts['pen'])) - self.setBrush(fn.mkBrush(defOpts['brush'])) - - self.rotate(self.opts['angle']) - self.moveBy(*self.opts['pos']) - - def setStyle(self, **opts): - """ - Changes the appearance of the arrow. - All arguments are optional: - - ================= ================================================= - Keyword Arguments - angle Orientation of the arrow in degrees. Default is - 0; arrow pointing to the left. - headLen Length of the arrow head, from tip to base. - default=20 - headWidth Width of the arrow head at its base. - tipAngle Angle of the tip of the arrow in degrees. Smaller - values make a 'sharper' arrow. If tipAngle is - specified, ot overrides headWidth. default=25 - baseAngle Angle of the base of the arrow head. Default is - 0, which means that the base of the arrow head - is perpendicular to the arrow shaft. - tailLen Length of the arrow tail, measured from the base - of the arrow head to the tip of the tail. If - this value is None, no tail will be drawn. - default=None - tailWidth Width of the tail. default=3 - pen The pen used to draw the outline of the arrow. - brush The brush used to fill the arrow. - ================= ================================================= - """ - self.opts = opts - - opt = dict([(k,self.opts[k]) for k in ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth']]) - self.path = fn.makeArrowPath(**opt) - self.setPath(self.path) - - if opts['pxMode']: - self.setFlags(self.flags() | self.ItemIgnoresTransformations) - else: - self.setFlags(self.flags() & ~self.ItemIgnoresTransformations) - - def paint(self, p, *args): - p.setRenderHint(QtGui.QPainter.Antialiasing) - QtGui.QGraphicsPathItem.paint(self, p, *args) - - #p.setPen(fn.mkPen('r')) - #p.setBrush(fn.mkBrush(None)) - #p.drawRect(self.boundingRect()) - - def shape(self): - #if not self.opts['pxMode']: - #return QtGui.QGraphicsPathItem.shape(self) - return self.path - - ## dataBounds and pixelPadding methods are provided to ensure ViewBox can - ## properly auto-range - def dataBounds(self, ax, frac, orthoRange=None): - pw = 0 - pen = self.pen() - if not pen.isCosmetic(): - pw = pen.width() * 0.7072 - if self.opts['pxMode']: - return [0,0] - else: - br = self.boundingRect() - if ax == 0: - return [br.left()-pw, br.right()+pw] - else: - return [br.top()-pw, br.bottom()+pw] - - def pixelPadding(self): - pad = 0 - if self.opts['pxMode']: - br = self.boundingRect() - pad += (br.width()**2 + br.height()**2) ** 0.5 - pen = self.pen() - if pen.isCosmetic(): - pad += max(1, pen.width()) * 0.7072 - return pad - - - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/AxisItem.py b/pyqtgraph/graphicsItems/AxisItem.py deleted file mode 100644 index 429ff49c..00000000 --- a/pyqtgraph/graphicsItems/AxisItem.py +++ /dev/null @@ -1,931 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.python2_3 import asUnicode -import numpy as np -from pyqtgraph.Point import Point -import pyqtgraph.debug as debug -import weakref -import pyqtgraph.functions as fn -import pyqtgraph as pg -from .GraphicsWidget import GraphicsWidget - -__all__ = ['AxisItem'] -class AxisItem(GraphicsWidget): - """ - GraphicsItem showing a single plot axis with ticks, values, and label. - Can be configured to fit on any side of a plot, and can automatically synchronize its displayed scale with ViewBox items. - Ticks can be extended to draw a grid. - If maxTickLength is negative, ticks point into the plot. - """ - - def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True): - """ - ============== =============================================================== - **Arguments:** - orientation one of 'left', 'right', 'top', or 'bottom' - maxTickLength (px) maximum length of ticks to draw. Negative values draw - into the plot, positive values draw outward. - linkView (ViewBox) causes the range of values displayed in the axis - to be linked to the visible range of a ViewBox. - showValues (bool) Whether to display values adjacent to ticks - pen (QPen) Pen used when drawing ticks. - ============== =============================================================== - """ - - GraphicsWidget.__init__(self, parent) - self.label = QtGui.QGraphicsTextItem(self) - self.showValues = showValues - self.picture = None - self.orientation = orientation - if orientation not in ['left', 'right', 'top', 'bottom']: - raise Exception("Orientation argument must be one of 'left', 'right', 'top', or 'bottom'.") - if orientation in ['left', 'right']: - self.label.rotate(-90) - - self.style = { - 'tickTextOffset': (5, 2), ## (horizontal, vertical) spacing between text and axis - 'tickTextWidth': 30, ## space reserved for tick text - 'tickTextHeight': 18, - 'autoExpandTextSpace': True, ## automatically expand text space if needed - 'tickFont': None, - 'stopAxisAtTick': (False, False), ## whether axis is drawn to edge of box or to last tick - 'textFillLimits': [ ## how much of the axis to fill up with tick text, maximally. - (0, 0.8), ## never fill more than 80% of the axis - (2, 0.6), ## If we already have 2 ticks with text, fill no more than 60% of the axis - (4, 0.4), ## If we already have 4 ticks with text, fill no more than 40% of the axis - (6, 0.2), ## If we already have 6 ticks with text, fill no more than 20% of the axis - ] - } - - self.textWidth = 30 ## Keeps track of maximum width / height of tick text - self.textHeight = 18 - - self.labelText = '' - self.labelUnits = '' - self.labelUnitPrefix='' - self.labelStyle = {} - self.logMode = False - self.tickFont = None - - self.tickLength = maxTickLength - self._tickLevels = None ## used to override the automatic ticking system with explicit ticks - self.scale = 1.0 - self.autoSIPrefix = True - self.autoSIPrefixScale = 1.0 - - self.setRange(0, 1) - - self.setPen(pen) - - self._linkedView = None - if linkView is not None: - self.linkToView(linkView) - - self.showLabel(False) - - self.grid = False - #self.setCacheMode(self.DeviceCoordinateCache) - - def close(self): - self.scene().removeItem(self.label) - self.label = None - self.scene().removeItem(self) - - def setGrid(self, grid): - """Set the alpha value for the grid, or False to disable.""" - self.grid = grid - self.picture = None - self.prepareGeometryChange() - self.update() - - def setLogMode(self, log): - """ - If *log* is True, then ticks are displayed on a logarithmic scale and values - are adjusted accordingly. (This is usually accessed by changing the log mode - of a :func:`PlotItem `) - """ - self.logMode = log - self.picture = None - self.update() - - def setTickFont(self, font): - self.tickFont = font - self.picture = None - self.prepareGeometryChange() - ## Need to re-allocate space depending on font size? - - self.update() - - def resizeEvent(self, ev=None): - #s = self.size() - - ## Set the position of the label - nudge = 5 - br = self.label.boundingRect() - p = QtCore.QPointF(0, 0) - if self.orientation == 'left': - p.setY(int(self.size().height()/2 + br.width()/2)) - p.setX(-nudge) - #s.setWidth(10) - elif self.orientation == 'right': - #s.setWidth(10) - p.setY(int(self.size().height()/2 + br.width()/2)) - p.setX(int(self.size().width()-br.height()+nudge)) - elif self.orientation == 'top': - #s.setHeight(10) - p.setY(-nudge) - p.setX(int(self.size().width()/2. - br.width()/2.)) - elif self.orientation == 'bottom': - p.setX(int(self.size().width()/2. - br.width()/2.)) - #s.setHeight(10) - p.setY(int(self.size().height()-br.height()+nudge)) - #self.label.resize(s) - self.label.setPos(p) - self.picture = None - - def showLabel(self, show=True): - """Show/hide the label text for this axis.""" - #self.drawLabel = show - self.label.setVisible(show) - if self.orientation in ['left', 'right']: - self.setWidth() - else: - self.setHeight() - if self.autoSIPrefix: - self.updateAutoSIPrefix() - - def setLabel(self, text=None, units=None, unitPrefix=None, **args): - """Set the text displayed adjacent to the axis. - - ============= ============================================================= - Arguments - text The text (excluding units) to display on the label for this - axis. - units The units for this axis. Units should generally be given - without any scaling prefix (eg, 'V' instead of 'mV'). The - scaling prefix will be automatically prepended based on the - range of data displayed. - **args All extra keyword arguments become CSS style options for - the tag which will surround the axis label and units. - ============= ============================================================= - - The final text generated for the label will look like:: - - {text} (prefix{units}) - - Each extra keyword argument will become a CSS option in the above template. - For example, you can set the font size and color of the label:: - - labelStyle = {'color': '#FFF', 'font-size': '14pt'} - axis.setLabel('label text', units='V', **labelStyle) - - """ - if text is not None: - self.labelText = text - self.showLabel() - if units is not None: - self.labelUnits = units - self.showLabel() - if unitPrefix is not None: - self.labelUnitPrefix = unitPrefix - if len(args) > 0: - self.labelStyle = args - self.label.setHtml(self.labelString()) - self._adjustSize() - self.picture = None - self.update() - - def labelString(self): - if self.labelUnits == '': - if not self.autoSIPrefix or self.autoSIPrefixScale == 1.0: - units = '' - else: - units = asUnicode('(x%g)') % (1.0/self.autoSIPrefixScale) - else: - #print repr(self.labelUnitPrefix), repr(self.labelUnits) - units = asUnicode('(%s%s)') % (asUnicode(self.labelUnitPrefix), asUnicode(self.labelUnits)) - - s = asUnicode('%s %s') % (asUnicode(self.labelText), asUnicode(units)) - - style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle]) - - return asUnicode("%s") % (style, asUnicode(s)) - - def _updateMaxTextSize(self, x): - ## Informs that the maximum tick size orthogonal to the axis has - ## changed; we use this to decide whether the item needs to be resized - ## to accomodate. - if self.orientation in ['left', 'right']: - mx = max(self.textWidth, x) - if mx > self.textWidth or mx < self.textWidth-10: - self.textWidth = mx - if self.style['autoExpandTextSpace'] is True: - self.setWidth() - #return True ## size has changed - else: - mx = max(self.textHeight, x) - if mx > self.textHeight or mx < self.textHeight-10: - self.textHeight = mx - if self.style['autoExpandTextSpace'] is True: - self.setHeight() - #return True ## size has changed - - def _adjustSize(self): - if self.orientation in ['left', 'right']: - self.setWidth() - else: - self.setHeight() - - def setHeight(self, h=None): - """Set the height of this axis reserved for ticks and tick labels. - The height of the axis label is automatically added.""" - if h is None: - if self.style['autoExpandTextSpace'] is True: - h = self.textHeight - else: - h = self.style['tickTextHeight'] - h += max(0, self.tickLength) + self.style['tickTextOffset'][1] - if self.label.isVisible(): - h += self.label.boundingRect().height() * 0.8 - self.setMaximumHeight(h) - self.setMinimumHeight(h) - self.picture = None - - - def setWidth(self, w=None): - """Set the width of this axis reserved for ticks and tick labels. - The width of the axis label is automatically added.""" - if w is None: - if self.style['autoExpandTextSpace'] is True: - w = self.textWidth - else: - w = self.style['tickTextWidth'] - w += max(0, self.tickLength) + self.style['tickTextOffset'][0] - if self.label.isVisible(): - w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate - self.setMaximumWidth(w) - self.setMinimumWidth(w) - self.picture = None - - def pen(self): - if self._pen is None: - return fn.mkPen(pg.getConfigOption('foreground')) - return pg.mkPen(self._pen) - - def setPen(self, pen): - """ - Set the pen used for drawing text, axes, ticks, and grid lines. - if pen == None, the default will be used (see :func:`setConfigOption - `) - """ - self._pen = pen - self.picture = None - if pen is None: - pen = pg.getConfigOption('foreground') - self.labelStyle['color'] = '#' + pg.colorStr(pg.mkPen(pen).color())[:6] - self.setLabel() - self.update() - - def setScale(self, scale=None): - """ - Set the value scaling for this axis. - - Setting this value causes the axis to draw ticks and tick labels as if - the view coordinate system were scaled. By default, the axis scaling is - 1.0. - """ - # Deprecated usage, kept for backward compatibility - if scale is None: - scale = 1.0 - self.enableAutoSIPrefix(True) - - if scale != self.scale: - self.scale = scale - self.setLabel() - self.picture = None - self.update() - - def enableAutoSIPrefix(self, enable=True): - """ - Enable (or disable) automatic SI prefix scaling on this axis. - - When enabled, this feature automatically determines the best SI prefix - to prepend to the label units, while ensuring that axis values are scaled - accordingly. - - For example, if the axis spans values from -0.1 to 0.1 and has units set - to 'V' then the axis would display values -100 to 100 - and the units would appear as 'mV' - - This feature is enabled by default, and is only available when a suffix - (unit string) is provided to display on the label. - """ - self.autoSIPrefix = enable - self.updateAutoSIPrefix() - - def updateAutoSIPrefix(self): - if self.label.isVisible(): - (scale, prefix) = fn.siScale(max(abs(self.range[0]*self.scale), abs(self.range[1]*self.scale))) - if self.labelUnits == '' and prefix in ['k', 'm']: ## If we are not showing units, wait until 1e6 before scaling. - scale = 1.0 - prefix = '' - self.setLabel(unitPrefix=prefix) - else: - scale = 1.0 - - self.autoSIPrefixScale = scale - self.picture = None - self.update() - - - def setRange(self, mn, mx): - """Set the range of values displayed by the axis. - Usually this is handled automatically by linking the axis to a ViewBox with :func:`linkToView `""" - if any(np.isinf((mn, mx))) or any(np.isnan((mn, mx))): - raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx))) - self.range = [mn, mx] - if self.autoSIPrefix: - self.updateAutoSIPrefix() - self.picture = None - self.update() - - def linkedView(self): - """Return the ViewBox this axis is linked to""" - if self._linkedView is None: - return None - else: - return self._linkedView() - - def linkToView(self, view): - """Link this axis to a ViewBox, causing its displayed range to match the visible range of the view.""" - oldView = self.linkedView() - self._linkedView = weakref.ref(view) - if self.orientation in ['right', 'left']: - if oldView is not None: - oldView.sigYRangeChanged.disconnect(self.linkedViewChanged) - view.sigYRangeChanged.connect(self.linkedViewChanged) - else: - if oldView is not None: - oldView.sigXRangeChanged.disconnect(self.linkedViewChanged) - view.sigXRangeChanged.connect(self.linkedViewChanged) - - if oldView is not None: - oldView.sigResized.disconnect(self.linkedViewChanged) - view.sigResized.connect(self.linkedViewChanged) - - def linkedViewChanged(self, view, newRange=None): - if self.orientation in ['right', 'left']: - if newRange is None: - newRange = view.viewRange()[1] - if view.yInverted(): - self.setRange(*newRange[::-1]) - else: - self.setRange(*newRange) - else: - if newRange is None: - newRange = view.viewRange()[0] - self.setRange(*newRange) - - def boundingRect(self): - linkedView = self.linkedView() - if linkedView is None or self.grid is False: - rect = self.mapRectFromParent(self.geometry()) - ## extend rect if ticks go in negative direction - ## also extend to account for text that flows past the edges - if self.orientation == 'left': - rect = rect.adjusted(0, -15, -min(0,self.tickLength), 15) - elif self.orientation == 'right': - rect = rect.adjusted(min(0,self.tickLength), -15, 0, 15) - elif self.orientation == 'top': - rect = rect.adjusted(-15, 0, 15, -min(0,self.tickLength)) - elif self.orientation == 'bottom': - rect = rect.adjusted(-15, min(0,self.tickLength), 15, 0) - return rect - else: - return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect()) - - def paint(self, p, opt, widget): - prof = debug.Profiler('AxisItem.paint', disabled=True) - if self.picture is None: - try: - picture = QtGui.QPicture() - painter = QtGui.QPainter(picture) - specs = self.generateDrawSpecs(painter) - prof.mark('generate specs') - if specs is not None: - self.drawPicture(painter, *specs) - prof.mark('draw picture') - finally: - painter.end() - self.picture = picture - #p.setRenderHint(p.Antialiasing, False) ## Sometimes we get a segfault here ??? - #p.setRenderHint(p.TextAntialiasing, True) - self.picture.play(p) - prof.finish() - - - - def setTicks(self, ticks): - """Explicitly determine which ticks to display. - This overrides the behavior specified by tickSpacing(), tickValues(), and tickStrings() - The format for *ticks* looks like:: - - [ - [ (majorTickValue1, majorTickString1), (majorTickValue2, majorTickString2), ... ], - [ (minorTickValue1, minorTickString1), (minorTickValue2, minorTickString2), ... ], - ... - ] - - If *ticks* is None, then the default tick system will be used instead. - """ - self._tickLevels = ticks - self.picture = None - self.update() - - def tickSpacing(self, minVal, maxVal, size): - """Return values describing the desired spacing and offset of ticks. - - This method is called whenever the axis needs to be redrawn and is a - good method to override in subclasses that require control over tick locations. - - The return value must be a list of tuples, one for each set of ticks:: - - [ - (major tick spacing, offset), - (minor tick spacing, offset), - (sub-minor tick spacing, offset), - ... - ] - """ - dif = abs(maxVal - minVal) - if dif == 0: - return [] - - ## decide optimal minor tick spacing in pixels (this is just aesthetics) - pixelSpacing = size / np.log(size) - optimalTickCount = max(2., size / pixelSpacing) - - ## optimal minor tick spacing - optimalSpacing = dif / optimalTickCount - - ## the largest power-of-10 spacing which is smaller than optimal - p10unit = 10 ** np.floor(np.log10(optimalSpacing)) - - ## Determine major/minor tick spacings which flank the optimal spacing. - intervals = np.array([1., 2., 10., 20., 100.]) * p10unit - minorIndex = 0 - while intervals[minorIndex+1] <= optimalSpacing: - minorIndex += 1 - - levels = [ - (intervals[minorIndex+2], 0), - (intervals[minorIndex+1], 0), - #(intervals[minorIndex], 0) ## Pretty, but eats up CPU - ] - - ## decide whether to include the last level of ticks - minSpacing = min(size / 20., 30.) - maxTickCount = size / minSpacing - if dif / intervals[minorIndex] <= maxTickCount: - levels.append((intervals[minorIndex], 0)) - return levels - - - - ##### This does not work -- switching between 2/5 confuses the automatic text-level-selection - ### Determine major/minor tick spacings which flank the optimal spacing. - #intervals = np.array([1., 2., 5., 10., 20., 50., 100.]) * p10unit - #minorIndex = 0 - #while intervals[minorIndex+1] <= optimalSpacing: - #minorIndex += 1 - - ### make sure we never see 5 and 2 at the same time - #intIndexes = [ - #[0,1,3], - #[0,2,3], - #[2,3,4], - #[3,4,6], - #[3,5,6], - #][minorIndex] - - #return [ - #(intervals[intIndexes[2]], 0), - #(intervals[intIndexes[1]], 0), - #(intervals[intIndexes[0]], 0) - #] - - - - def tickValues(self, minVal, maxVal, size): - """ - Return the values and spacing of ticks to draw:: - - [ - (spacing, [major ticks]), - (spacing, [minor ticks]), - ... - ] - - By default, this method calls tickSpacing to determine the correct tick locations. - This is a good method to override in subclasses. - """ - minVal, maxVal = sorted((minVal, maxVal)) - - - minVal *= self.scale - maxVal *= self.scale - #size *= self.scale - - ticks = [] - tickLevels = self.tickSpacing(minVal, maxVal, size) - allValues = np.array([]) - for i in range(len(tickLevels)): - spacing, offset = tickLevels[i] - - ## determine starting tick - start = (np.ceil((minVal-offset) / spacing) * spacing) + offset - - ## determine number of ticks - num = int((maxVal-start) / spacing) + 1 - values = (np.arange(num) * spacing + start) / self.scale - ## remove any ticks that were present in higher levels - ## we assume here that if the difference between a tick value and a previously seen tick value - ## is less than spacing/100, then they are 'equal' and we can ignore the new tick. - values = list(filter(lambda x: all(np.abs(allValues-x) > spacing*0.01), values) ) - allValues = np.concatenate([allValues, values]) - ticks.append((spacing/self.scale, values)) - - if self.logMode: - return self.logTickValues(minVal, maxVal, size, ticks) - - - #nticks = [] - #for t in ticks: - #nvals = [] - #for v in t[1]: - #nvals.append(v/self.scale) - #nticks.append((t[0]/self.scale,nvals)) - #ticks = nticks - - return ticks - - def logTickValues(self, minVal, maxVal, size, stdTicks): - - ## start with the tick spacing given by tickValues(). - ## Any level whose spacing is < 1 needs to be converted to log scale - - ticks = [] - for (spacing, t) in stdTicks: - if spacing >= 1.0: - ticks.append((spacing, t)) - - if len(ticks) < 3: - v1 = int(np.floor(minVal)) - v2 = int(np.ceil(maxVal)) - #major = list(range(v1+1, v2)) - - minor = [] - for v in range(v1, v2): - minor.extend(v + np.log10(np.arange(1, 10))) - minor = [x for x in minor if x>minVal and x= 10000: - vstr = "%g" % vs - else: - vstr = ("%%0.%df" % places) % vs - strings.append(vstr) - return strings - - def logTickStrings(self, values, scale, spacing): - return ["%0.1g"%x for x in 10 ** np.array(values).astype(float)] - - def generateDrawSpecs(self, p): - """ - Calls tickValues() and tickStrings to determine where and how ticks should - be drawn, then generates from this a set of drawing commands to be - interpreted by drawPicture(). - """ - prof = debug.Profiler("AxisItem.generateDrawSpecs", disabled=True) - - #bounds = self.boundingRect() - bounds = self.mapRectFromParent(self.geometry()) - - linkedView = self.linkedView() - if linkedView is None or self.grid is False: - tickBounds = bounds - else: - tickBounds = linkedView.mapRectToItem(self, linkedView.boundingRect()) - - if self.orientation == 'left': - span = (bounds.topRight(), bounds.bottomRight()) - tickStart = tickBounds.right() - tickStop = bounds.right() - tickDir = -1 - axis = 0 - elif self.orientation == 'right': - span = (bounds.topLeft(), bounds.bottomLeft()) - tickStart = tickBounds.left() - tickStop = bounds.left() - tickDir = 1 - axis = 0 - elif self.orientation == 'top': - span = (bounds.bottomLeft(), bounds.bottomRight()) - tickStart = tickBounds.bottom() - tickStop = bounds.bottom() - tickDir = -1 - axis = 1 - elif self.orientation == 'bottom': - span = (bounds.topLeft(), bounds.topRight()) - tickStart = tickBounds.top() - tickStop = bounds.top() - tickDir = 1 - axis = 1 - #print tickStart, tickStop, span - - ## determine size of this item in pixels - points = list(map(self.mapToDevice, span)) - if None in points: - return - lengthInPixels = Point(points[1] - points[0]).length() - if lengthInPixels == 0: - return - - if self._tickLevels is None: - tickLevels = self.tickValues(self.range[0], self.range[1], lengthInPixels) - tickStrings = None - else: - ## parse self.tickLevels into the formats returned by tickLevels() and tickStrings() - tickLevels = [] - tickStrings = [] - for level in self._tickLevels: - values = [] - strings = [] - tickLevels.append((None, values)) - tickStrings.append(strings) - for val, strn in level: - values.append(val) - strings.append(strn) - - textLevel = 1 ## draw text at this scale level - - ## determine mapping between tick values and local coordinates - dif = self.range[1] - self.range[0] - if dif == 0: - xscale = 1 - offset = 0 - else: - if axis == 0: - xScale = -bounds.height() / dif - offset = self.range[0] * xScale - bounds.height() - else: - xScale = bounds.width() / dif - offset = self.range[0] * xScale - - xRange = [x * xScale - offset for x in self.range] - xMin = min(xRange) - xMax = max(xRange) - - prof.mark('init') - - tickPositions = [] # remembers positions of previously drawn ticks - - ## draw ticks - ## (to improve performance, we do not interleave line and text drawing, since this causes unnecessary pipeline switching) - ## draw three different intervals, long ticks first - tickSpecs = [] - for i in range(len(tickLevels)): - tickPositions.append([]) - ticks = tickLevels[i][1] - - ## length of tick - tickLength = self.tickLength / ((i*0.5)+1.0) - - lineAlpha = 255 / (i+1) - if self.grid is not False: - lineAlpha *= self.grid/255. * np.clip((0.05 * lengthInPixels / (len(ticks)+1)), 0., 1.) - - for v in ticks: - ## determine actual position to draw this tick - x = (v * xScale) - offset - if x < xMin or x > xMax: ## last check to make sure no out-of-bounds ticks are drawn - tickPositions[i].append(None) - continue - tickPositions[i].append(x) - - p1 = [x, x] - p2 = [x, x] - p1[axis] = tickStart - p2[axis] = tickStop - if self.grid is False: - p2[axis] += tickLength*tickDir - tickPen = self.pen() - color = tickPen.color() - color.setAlpha(lineAlpha) - tickPen.setColor(color) - tickSpecs.append((tickPen, Point(p1), Point(p2))) - prof.mark('compute ticks') - - ## This is where the long axis line should be drawn - - if self.style['stopAxisAtTick'][0] is True: - stop = max(span[0].y(), min(map(min, tickPositions))) - if axis == 0: - span[0].setY(stop) - else: - span[0].setX(stop) - if self.style['stopAxisAtTick'][1] is True: - stop = min(span[1].y(), max(map(max, tickPositions))) - if axis == 0: - span[1].setY(stop) - else: - span[1].setX(stop) - axisSpec = (self.pen(), span[0], span[1]) - - - - textOffset = self.style['tickTextOffset'][axis] ## spacing between axis and text - #if self.style['autoExpandTextSpace'] is True: - #textWidth = self.textWidth - #textHeight = self.textHeight - #else: - #textWidth = self.style['tickTextWidth'] ## space allocated for horizontal text - #textHeight = self.style['tickTextHeight'] ## space allocated for horizontal text - - textSize2 = 0 - textRects = [] - textSpecs = [] ## list of draw - textSize2 = 0 - for i in range(len(tickLevels)): - ## Get the list of strings to display for this level - if tickStrings is None: - spacing, values = tickLevels[i] - strings = self.tickStrings(values, self.autoSIPrefixScale * self.scale, spacing) - else: - strings = tickStrings[i] - - if len(strings) == 0: - continue - - ## ignore strings belonging to ticks that were previously ignored - for j in range(len(strings)): - if tickPositions[i][j] is None: - strings[j] = None - - ## Measure density of text; decide whether to draw this level - rects = [] - for s in strings: - if s is None: - rects.append(None) - else: - br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, str(s)) - ## boundingRect is usually just a bit too large - ## (but this probably depends on per-font metrics?) - br.setHeight(br.height() * 0.8) - - rects.append(br) - textRects.append(rects[-1]) - - if i > 0: ## always draw top level - ## measure all text, make sure there's enough room - if axis == 0: - textSize = np.sum([r.height() for r in textRects]) - textSize2 = np.max([r.width() for r in textRects]) - else: - textSize = np.sum([r.width() for r in textRects]) - textSize2 = np.max([r.height() for r in textRects]) - - ## If the strings are too crowded, stop drawing text now. - ## We use three different crowding limits based on the number - ## of texts drawn so far. - textFillRatio = float(textSize) / lengthInPixels - finished = False - for nTexts, limit in self.style['textFillLimits']: - if len(textSpecs) >= nTexts and textFillRatio >= limit: - finished = True - break - if finished: - break - - #spacing, values = tickLevels[best] - #strings = self.tickStrings(values, self.scale, spacing) - for j in range(len(strings)): - vstr = strings[j] - if vstr is None: ## this tick was ignored because it is out of bounds - continue - vstr = str(vstr) - x = tickPositions[i][j] - #textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, vstr) - textRect = rects[j] - height = textRect.height() - width = textRect.width() - #self.textHeight = height - offset = max(0,self.tickLength) + textOffset - if self.orientation == 'left': - textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter - rect = QtCore.QRectF(tickStop-offset-width, x-(height/2), width, height) - elif self.orientation == 'right': - textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter - rect = QtCore.QRectF(tickStop+offset, x-(height/2), width, height) - elif self.orientation == 'top': - textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignCenter|QtCore.Qt.AlignBottom - rect = QtCore.QRectF(x-width/2., tickStop-offset-height, width, height) - elif self.orientation == 'bottom': - textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignCenter|QtCore.Qt.AlignTop - rect = QtCore.QRectF(x-width/2., tickStop+offset, width, height) - - #p.setPen(self.pen()) - #p.drawText(rect, textFlags, vstr) - textSpecs.append((rect, textFlags, vstr)) - prof.mark('compute text') - - ## update max text size if needed. - self._updateMaxTextSize(textSize2) - - return (axisSpec, tickSpecs, textSpecs) - - def drawPicture(self, p, axisSpec, tickSpecs, textSpecs): - prof = debug.Profiler("AxisItem.drawPicture", disabled=True) - - p.setRenderHint(p.Antialiasing, False) - p.setRenderHint(p.TextAntialiasing, True) - - ## draw long line along axis - pen, p1, p2 = axisSpec - p.setPen(pen) - p.drawLine(p1, p2) - p.translate(0.5,0) ## resolves some damn pixel ambiguity - - ## draw ticks - for pen, p1, p2 in tickSpecs: - p.setPen(pen) - p.drawLine(p1, p2) - prof.mark('draw ticks') - - ## Draw all text - if self.tickFont is not None: - p.setFont(self.tickFont) - p.setPen(self.pen()) - for rect, flags, text in textSpecs: - p.drawText(rect, flags, text) - #p.drawRect(rect) - - prof.mark('draw text') - prof.finish() - - def show(self): - - if self.orientation in ['left', 'right']: - self.setWidth() - else: - self.setHeight() - GraphicsWidget.show(self) - - def hide(self): - if self.orientation in ['left', 'right']: - self.setWidth(0) - else: - self.setHeight(0) - GraphicsWidget.hide(self) - - def wheelEvent(self, ev): - if self.linkedView() is None: - return - if self.orientation in ['left', 'right']: - self.linkedView().wheelEvent(ev, axis=1) - else: - self.linkedView().wheelEvent(ev, axis=0) - ev.accept() - - def mouseDragEvent(self, event): - if self.linkedView() is None: - return - if self.orientation in ['left', 'right']: - return self.linkedView().mouseDragEvent(event, axis=1) - else: - return self.linkedView().mouseDragEvent(event, axis=0) - - def mouseClickEvent(self, event): - if self.linkedView() is None: - return - return self.linkedView().mouseClickEvent(event) diff --git a/pyqtgraph/graphicsItems/BarGraphItem.py b/pyqtgraph/graphicsItems/BarGraphItem.py deleted file mode 100644 index 0527e9f1..00000000 --- a/pyqtgraph/graphicsItems/BarGraphItem.py +++ /dev/null @@ -1,149 +0,0 @@ -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore -from .GraphicsObject import GraphicsObject -import numpy as np - -__all__ = ['BarGraphItem'] - -class BarGraphItem(GraphicsObject): - def __init__(self, **opts): - """ - Valid keyword options are: - x, x0, x1, y, y0, y1, width, height, pen, brush - - x specifies the x-position of the center of the bar. - x0, x1 specify left and right edges of the bar, respectively. - width specifies distance from x0 to x1. - You may specify any combination: - - x, width - x0, width - x1, width - x0, x1 - - Likewise y, y0, y1, and height. - If only height is specified, then y0 will be set to 0 - - Example uses: - - BarGraphItem(x=range(5), height=[1,5,2,4,3], width=0.5) - - - """ - GraphicsObject.__init__(self) - self.opts = dict( - x=None, - y=None, - x0=None, - y0=None, - x1=None, - y1=None, - height=None, - width=None, - pen=None, - brush=None, - pens=None, - brushes=None, - ) - self.setOpts(**opts) - - def setOpts(self, **opts): - self.opts.update(opts) - self.picture = None - self.update() - self.informViewBoundsChanged() - - def drawPicture(self): - self.picture = QtGui.QPicture() - p = QtGui.QPainter(self.picture) - - pen = self.opts['pen'] - pens = self.opts['pens'] - - if pen is None and pens is None: - pen = pg.getConfigOption('foreground') - - brush = self.opts['brush'] - brushes = self.opts['brushes'] - if brush is None and brushes is None: - brush = (128, 128, 128) - - def asarray(x): - if x is None or np.isscalar(x) or isinstance(x, np.ndarray): - return x - return np.array(x) - - - x = asarray(self.opts.get('x')) - x0 = asarray(self.opts.get('x0')) - x1 = asarray(self.opts.get('x1')) - width = asarray(self.opts.get('width')) - - if x0 is None: - if width is None: - raise Exception('must specify either x0 or width') - if x1 is not None: - x0 = x1 - width - elif x is not None: - x0 = x - width/2. - else: - raise Exception('must specify at least one of x, x0, or x1') - if width is None: - if x1 is None: - raise Exception('must specify either x1 or width') - width = x1 - x0 - - y = asarray(self.opts.get('y')) - y0 = asarray(self.opts.get('y0')) - y1 = asarray(self.opts.get('y1')) - height = asarray(self.opts.get('height')) - - if y0 is None: - if height is None: - y0 = 0 - elif y1 is not None: - y0 = y1 - height - elif y is not None: - y0 = y - height/2. - else: - y0 = 0 - if height is None: - if y1 is None: - raise Exception('must specify either y1 or height') - height = y1 - y0 - - p.setPen(pg.mkPen(pen)) - p.setBrush(pg.mkBrush(brush)) - for i in range(len(x0)): - if pens is not None: - p.setPen(pg.mkPen(pens[i])) - if brushes is not None: - p.setBrush(pg.mkBrush(brushes[i])) - - if np.isscalar(y0): - y = y0 - else: - y = y0[i] - if np.isscalar(width): - w = width - else: - w = width[i] - - p.drawRect(QtCore.QRectF(x0[i], y, w, height[i])) - - - p.end() - self.prepareGeometryChange() - - - def paint(self, p, *args): - if self.picture is None: - self.drawPicture() - self.picture.play(p) - - def boundingRect(self): - if self.picture is None: - self.drawPicture() - return QtCore.QRectF(self.picture.boundingRect()) - - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/ButtonItem.py b/pyqtgraph/graphicsItems/ButtonItem.py deleted file mode 100644 index 741f2666..00000000 --- a/pyqtgraph/graphicsItems/ButtonItem.py +++ /dev/null @@ -1,58 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from .GraphicsObject import GraphicsObject - -__all__ = ['ButtonItem'] -class ButtonItem(GraphicsObject): - """Button graphicsItem displaying an image.""" - - clicked = QtCore.Signal(object) - - def __init__(self, imageFile=None, width=None, parentItem=None, pixmap=None): - self.enabled = True - GraphicsObject.__init__(self) - if imageFile is not None: - self.setImageFile(imageFile) - elif pixmap is not None: - self.setPixmap(pixmap) - - if width is not None: - s = float(width) / self.pixmap.width() - self.scale(s, s) - if parentItem is not None: - self.setParentItem(parentItem) - self.setOpacity(0.7) - - def setImageFile(self, imageFile): - self.setPixmap(QtGui.QPixmap(imageFile)) - - def setPixmap(self, pixmap): - self.pixmap = pixmap - self.update() - - def mouseClickEvent(self, ev): - if self.enabled: - self.clicked.emit(self) - - def mouseHoverEvent(self, ev): - if not self.enabled: - return - if ev.isEnter(): - self.setOpacity(1.0) - else: - self.setOpacity(0.7) - - def disable(self): - self.enabled = False - self.setOpacity(0.4) - - def enable(self): - self.enabled = True - self.setOpacity(0.7) - - def paint(self, p, *args): - p.setRenderHint(p.Antialiasing) - p.drawPixmap(0, 0, self.pixmap) - - def boundingRect(self): - return QtCore.QRectF(self.pixmap.rect()) - diff --git a/pyqtgraph/graphicsItems/CurvePoint.py b/pyqtgraph/graphicsItems/CurvePoint.py deleted file mode 100644 index 668830f7..00000000 --- a/pyqtgraph/graphicsItems/CurvePoint.py +++ /dev/null @@ -1,117 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from . import ArrowItem -import numpy as np -from pyqtgraph.Point import Point -import weakref -from .GraphicsObject import GraphicsObject - -__all__ = ['CurvePoint', 'CurveArrow'] -class CurvePoint(GraphicsObject): - """A GraphicsItem that sets its location to a point on a PlotCurveItem. - Also rotates to be tangent to the curve. - The position along the curve is a Qt property, and thus can be easily animated. - - Note: This class does not display anything; see CurveArrow for an applied example - """ - - def __init__(self, curve, index=0, pos=None, rotate=True): - """Position can be set either as an index referring to the sample number or - the position 0.0 - 1.0 - If *rotate* is True, then the item rotates to match the tangent of the curve. - """ - - GraphicsObject.__init__(self) - #QObjectWorkaround.__init__(self) - self._rotate = rotate - self.curve = weakref.ref(curve) - self.setParentItem(curve) - self.setProperty('position', 0.0) - self.setProperty('index', 0) - - if hasattr(self, 'ItemHasNoContents'): - self.setFlags(self.flags() | self.ItemHasNoContents) - - if pos is not None: - self.setPos(pos) - else: - self.setIndex(index) - - def setPos(self, pos): - self.setProperty('position', float(pos))## cannot use numpy types here, MUST be python float. - - def setIndex(self, index): - self.setProperty('index', int(index)) ## cannot use numpy types here, MUST be python int. - - def event(self, ev): - if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None: - return False - - if ev.propertyName() == 'index': - index = self.property('index') - if 'QVariant' in repr(index): - index = index.toInt()[0] - elif ev.propertyName() == 'position': - index = None - else: - return False - - (x, y) = self.curve().getData() - if index is None: - #print ev.propertyName(), self.property('position').toDouble()[0], self.property('position').typeName() - pos = self.property('position') - if 'QVariant' in repr(pos): ## need to support 2 APIs :( - pos = pos.toDouble()[0] - index = (len(x)-1) * np.clip(pos, 0.0, 1.0) - - if index != int(index): ## interpolate floating-point values - i1 = int(index) - i2 = np.clip(i1+1, 0, len(x)-1) - s2 = index-i1 - s1 = 1.0-s2 - newPos = (x[i1]*s1+x[i2]*s2, y[i1]*s1+y[i2]*s2) - else: - index = int(index) - i1 = np.clip(index-1, 0, len(x)-1) - i2 = np.clip(index+1, 0, len(x)-1) - newPos = (x[index], y[index]) - - p1 = self.parentItem().mapToScene(QtCore.QPointF(x[i1], y[i1])) - p2 = self.parentItem().mapToScene(QtCore.QPointF(x[i2], y[i2])) - ang = np.arctan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians - self.resetTransform() - if self._rotate: - self.rotate(180+ ang * 180 / np.pi) ## takes degrees - QtGui.QGraphicsItem.setPos(self, *newPos) - return True - - def boundingRect(self): - return QtCore.QRectF() - - def paint(self, *args): - pass - - def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1): - anim = QtCore.QPropertyAnimation(self, prop) - anim.setDuration(duration) - anim.setStartValue(start) - anim.setEndValue(end) - anim.setLoopCount(loop) - return anim - - -class CurveArrow(CurvePoint): - """Provides an arrow that points to any specific sample on a PlotCurveItem. - Provides properties that can be animated.""" - - def __init__(self, curve, index=0, pos=None, **opts): - CurvePoint.__init__(self, curve, index=index, pos=pos) - if opts.get('pxMode', True): - opts['pxMode'] = False - self.setFlags(self.flags() | self.ItemIgnoresTransformations) - opts['angle'] = 0 - self.arrow = ArrowItem.ArrowItem(**opts) - self.arrow.setParentItem(self) - - def setStyle(**opts): - return self.arrow.setStyle(**opts) - diff --git a/pyqtgraph/graphicsItems/ErrorBarItem.py b/pyqtgraph/graphicsItems/ErrorBarItem.py deleted file mode 100644 index 656b9e2e..00000000 --- a/pyqtgraph/graphicsItems/ErrorBarItem.py +++ /dev/null @@ -1,133 +0,0 @@ -import pyqtgraph as pg -from pyqtgraph.Qt import QtGui, QtCore -from .GraphicsObject import GraphicsObject - -__all__ = ['ErrorBarItem'] - -class ErrorBarItem(GraphicsObject): - def __init__(self, **opts): - """ - Valid keyword options are: - x, y, height, width, top, bottom, left, right, beam, pen - - x and y must be numpy arrays specifying the coordinates of data points. - height, width, top, bottom, left, right, and beam may be numpy arrays, - single values, or None to disable. All values should be positive. - - If height is specified, it overrides top and bottom. - If width is specified, it overrides left and right. - """ - GraphicsObject.__init__(self) - self.opts = dict( - x=None, - y=None, - height=None, - width=None, - top=None, - bottom=None, - left=None, - right=None, - beam=None, - pen=None - ) - self.setOpts(**opts) - - def setOpts(self, **opts): - self.opts.update(opts) - self.path = None - self.update() - self.informViewBoundsChanged() - - def drawPath(self): - p = QtGui.QPainterPath() - - x, y = self.opts['x'], self.opts['y'] - if x is None or y is None: - return - - beam = self.opts['beam'] - - - height, top, bottom = self.opts['height'], self.opts['top'], self.opts['bottom'] - if height is not None or top is not None or bottom is not None: - ## draw vertical error bars - if height is not None: - y1 = y - height/2. - y2 = y + height/2. - else: - if bottom is None: - y1 = y - else: - y1 = y - bottom - if top is None: - y2 = y - else: - y2 = y + top - - for i in range(len(x)): - p.moveTo(x[i], y1[i]) - p.lineTo(x[i], y2[i]) - - if beam is not None and beam > 0: - x1 = x - beam/2. - x2 = x + beam/2. - if height is not None or top is not None: - for i in range(len(x)): - p.moveTo(x1[i], y2[i]) - p.lineTo(x2[i], y2[i]) - if height is not None or bottom is not None: - for i in range(len(x)): - p.moveTo(x1[i], y1[i]) - p.lineTo(x2[i], y1[i]) - - width, right, left = self.opts['width'], self.opts['right'], self.opts['left'] - if width is not None or right is not None or left is not None: - ## draw vertical error bars - if width is not None: - x1 = x - width/2. - x2 = x + width/2. - else: - if left is None: - x1 = x - else: - x1 = x - left - if right is None: - x2 = x - else: - x2 = x + right - - for i in range(len(x)): - p.moveTo(x1[i], y[i]) - p.lineTo(x2[i], y[i]) - - if beam is not None and beam > 0: - y1 = y - beam/2. - y2 = y + beam/2. - if width is not None or right is not None: - for i in range(len(x)): - p.moveTo(x2[i], y1[i]) - p.lineTo(x2[i], y2[i]) - if width is not None or left is not None: - for i in range(len(x)): - p.moveTo(x1[i], y1[i]) - p.lineTo(x1[i], y2[i]) - - self.path = p - self.prepareGeometryChange() - - - def paint(self, p, *args): - if self.path is None: - self.drawPath() - pen = self.opts['pen'] - if pen is None: - pen = pg.getConfigOption('foreground') - p.setPen(pg.mkPen(pen)) - p.drawPath(self.path) - - def boundingRect(self): - if self.path is None: - self.drawPath() - return self.path.boundingRect() - - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/FillBetweenItem.py b/pyqtgraph/graphicsItems/FillBetweenItem.py deleted file mode 100644 index e0011177..00000000 --- a/pyqtgraph/graphicsItems/FillBetweenItem.py +++ /dev/null @@ -1,23 +0,0 @@ -import pyqtgraph as pg - -class FillBetweenItem(pg.QtGui.QGraphicsPathItem): - """ - GraphicsItem filling the space between two PlotDataItems. - """ - def __init__(self, p1, p2, brush=None): - pg.QtGui.QGraphicsPathItem.__init__(self) - self.p1 = p1 - self.p2 = p2 - p1.sigPlotChanged.connect(self.updatePath) - p2.sigPlotChanged.connect(self.updatePath) - if brush is not None: - self.setBrush(pg.mkBrush(brush)) - self.setZValue(min(p1.zValue(), p2.zValue())-1) - self.updatePath() - - def updatePath(self): - p1 = self.p1.curve.path - p2 = self.p2.curve.path - path = pg.QtGui.QPainterPath() - path.addPolygon(p1.toSubpathPolygons()[0] + p2.toReversed().toSubpathPolygons()[0]) - self.setPath(path) diff --git a/pyqtgraph/graphicsItems/GradientEditorItem.py b/pyqtgraph/graphicsItems/GradientEditorItem.py deleted file mode 100644 index 955106d8..00000000 --- a/pyqtgraph/graphicsItems/GradientEditorItem.py +++ /dev/null @@ -1,910 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.python2_3 import sortList -import pyqtgraph.functions as fn -from .GraphicsObject import GraphicsObject -from .GraphicsWidget import GraphicsWidget -import weakref -from pyqtgraph.pgcollections import OrderedDict -from pyqtgraph.colormap import ColorMap - -import numpy as np - -__all__ = ['TickSliderItem', 'GradientEditorItem'] - - -Gradients = OrderedDict([ - ('thermal', {'ticks': [(0.3333, (185, 0, 0, 255)), (0.6666, (255, 220, 0, 255)), (1, (255, 255, 255, 255)), (0, (0, 0, 0, 255))], 'mode': 'rgb'}), - ('flame', {'ticks': [(0.2, (7, 0, 220, 255)), (0.5, (236, 0, 134, 255)), (0.8, (246, 246, 0, 255)), (1.0, (255, 255, 255, 255)), (0.0, (0, 0, 0, 255))], 'mode': 'rgb'}), - ('yellowy', {'ticks': [(0.0, (0, 0, 0, 255)), (0.2328863796753704, (32, 0, 129, 255)), (0.8362738179251941, (255, 255, 0, 255)), (0.5257586450247, (115, 15, 255, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'} ), - ('bipolar', {'ticks': [(0.0, (0, 255, 255, 255)), (1.0, (255, 255, 0, 255)), (0.5, (0, 0, 0, 255)), (0.25, (0, 0, 255, 255)), (0.75, (255, 0, 0, 255))], 'mode': 'rgb'}), - ('spectrum', {'ticks': [(1.0, (255, 0, 255, 255)), (0.0, (255, 0, 0, 255))], 'mode': 'hsv'}), - ('cyclic', {'ticks': [(0.0, (255, 0, 4, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'hsv'}), - ('greyclip', {'ticks': [(0.0, (0, 0, 0, 255)), (0.99, (255, 255, 255, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'rgb'}), - ('grey', {'ticks': [(0.0, (0, 0, 0, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'}), -]) - - - - - -class TickSliderItem(GraphicsWidget): - ## public class - """**Bases:** :class:`GraphicsWidget ` - - A rectangular item with tick marks along its length that can (optionally) be moved by the user.""" - - def __init__(self, orientation='bottom', allowAdd=True, **kargs): - """ - ============= ================================================================================= - **Arguments** - orientation Set the orientation of the gradient. Options are: 'left', 'right' - 'top', and 'bottom'. - allowAdd Specifies whether ticks can be added to the item by the user. - tickPen Default is white. Specifies the color of the outline of the ticks. - Can be any of the valid arguments for :func:`mkPen ` - ============= ================================================================================= - """ - ## public - GraphicsWidget.__init__(self) - self.orientation = orientation - self.length = 100 - self.tickSize = 15 - self.ticks = {} - self.maxDim = 20 - self.allowAdd = allowAdd - if 'tickPen' in kargs: - self.tickPen = fn.mkPen(kargs['tickPen']) - else: - self.tickPen = fn.mkPen('w') - - self.orientations = { - 'left': (90, 1, 1), - 'right': (90, 1, 1), - 'top': (0, 1, -1), - 'bottom': (0, 1, 1) - } - - self.setOrientation(orientation) - #self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) - #self.setBackgroundRole(QtGui.QPalette.NoRole) - #self.setMouseTracking(True) - - #def boundingRect(self): - #return self.mapRectFromParent(self.geometry()).normalized() - - #def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. - #p = QtGui.QPainterPath() - #p.addRect(self.boundingRect()) - #return p - - def paint(self, p, opt, widget): - #p.setPen(fn.mkPen('g', width=3)) - #p.drawRect(self.boundingRect()) - return - - def keyPressEvent(self, ev): - ev.ignore() - - def setMaxDim(self, mx=None): - if mx is None: - mx = self.maxDim - else: - self.maxDim = mx - - if self.orientation in ['bottom', 'top']: - self.setFixedHeight(mx) - self.setMaximumWidth(16777215) - else: - self.setFixedWidth(mx) - self.setMaximumHeight(16777215) - - - def setOrientation(self, orientation): - ## public - """Set the orientation of the TickSliderItem. - - ============= =================================================================== - **Arguments** - orientation Options are: 'left', 'right', 'top', 'bottom' - The orientation option specifies which side of the slider the - ticks are on, as well as whether the slider is vertical ('right' - and 'left') or horizontal ('top' and 'bottom'). - ============= =================================================================== - """ - self.orientation = orientation - self.setMaxDim() - self.resetTransform() - ort = orientation - if ort == 'top': - self.scale(1, -1) - self.translate(0, -self.height()) - elif ort == 'left': - self.rotate(270) - self.scale(1, -1) - self.translate(-self.height(), -self.maxDim) - elif ort == 'right': - self.rotate(270) - self.translate(-self.height(), 0) - #self.setPos(0, -self.height()) - elif ort != 'bottom': - raise Exception("%s is not a valid orientation. Options are 'left', 'right', 'top', and 'bottom'" %str(ort)) - - self.translate(self.tickSize/2., 0) - - def addTick(self, x, color=None, movable=True): - ## public - """ - Add a tick to the item. - - ============= ================================================================== - **Arguments** - x Position where tick should be added. - color Color of added tick. If color is not specified, the color will be - white. - movable Specifies whether the tick is movable with the mouse. - ============= ================================================================== - """ - - if color is None: - color = QtGui.QColor(255,255,255) - tick = Tick(self, [x*self.length, 0], color, movable, self.tickSize, pen=self.tickPen) - self.ticks[tick] = x - tick.setParentItem(self) - return tick - - def removeTick(self, tick): - ## public - """ - Removes the specified tick. - """ - del self.ticks[tick] - tick.setParentItem(None) - if self.scene() is not None: - self.scene().removeItem(tick) - - def tickMoved(self, tick, pos): - #print "tick changed" - ## Correct position of tick if it has left bounds. - newX = min(max(0, pos.x()), self.length) - pos.setX(newX) - tick.setPos(pos) - self.ticks[tick] = float(newX) / self.length - - def tickMoveFinished(self, tick): - pass - - def tickClicked(self, tick, ev): - if ev.button() == QtCore.Qt.RightButton: - self.removeTick(tick) - - def widgetLength(self): - if self.orientation in ['bottom', 'top']: - return self.width() - else: - return self.height() - - def resizeEvent(self, ev): - wlen = max(40, self.widgetLength()) - self.setLength(wlen-self.tickSize-2) - self.setOrientation(self.orientation) - #bounds = self.scene().itemsBoundingRect() - #bounds.setLeft(min(-self.tickSize*0.5, bounds.left())) - #bounds.setRight(max(self.length + self.tickSize, bounds.right())) - #self.setSceneRect(bounds) - #self.fitInView(bounds, QtCore.Qt.KeepAspectRatio) - - def setLength(self, newLen): - #private - for t, x in list(self.ticks.items()): - t.setPos(x * newLen + 1, t.pos().y()) - self.length = float(newLen) - - #def mousePressEvent(self, ev): - #QtGui.QGraphicsView.mousePressEvent(self, ev) - #self.ignoreRelease = False - #for i in self.items(ev.pos()): - #if isinstance(i, Tick): - #self.ignoreRelease = True - #break - ##if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks - ##self.ignoreRelease = True - - #def mouseReleaseEvent(self, ev): - #QtGui.QGraphicsView.mouseReleaseEvent(self, ev) - #if self.ignoreRelease: - #return - - #pos = self.mapToScene(ev.pos()) - - #if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: - #if pos.x() < 0 or pos.x() > self.length: - #return - #if pos.y() < 0 or pos.y() > self.tickSize: - #return - #pos.setX(min(max(pos.x(), 0), self.length)) - #self.addTick(pos.x()/self.length) - #elif ev.button() == QtCore.Qt.RightButton: - #self.showMenu(ev) - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: - pos = ev.pos() - if pos.x() < 0 or pos.x() > self.length: - return - if pos.y() < 0 or pos.y() > self.tickSize: - return - pos.setX(min(max(pos.x(), 0), self.length)) - self.addTick(pos.x()/self.length) - elif ev.button() == QtCore.Qt.RightButton: - self.showMenu(ev) - - #if ev.button() == QtCore.Qt.RightButton: - #if self.moving: - #ev.accept() - #self.setPos(self.startPosition) - #self.moving = False - #self.sigMoving.emit(self) - #self.sigMoved.emit(self) - #else: - #pass - #self.view().tickClicked(self, ev) - ###remove - - def hoverEvent(self, ev): - if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.LeftButton): - ev.acceptClicks(QtCore.Qt.RightButton) - ## show ghost tick - #self.currentPen = fn.mkPen(255, 0,0) - #else: - #self.currentPen = self.pen - #self.update() - - def showMenu(self, ev): - pass - - def setTickColor(self, tick, color): - """Set the color of the specified tick. - - ============= ================================================================== - **Arguments** - tick Can be either an integer corresponding to the index of the tick - or a Tick object. Ex: if you had a slider with 3 ticks and you - wanted to change the middle tick, the index would be 1. - color The color to make the tick. Can be any argument that is valid for - :func:`mkBrush ` - ============= ================================================================== - """ - tick = self.getTick(tick) - tick.color = color - tick.update() - #tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color))) - - def setTickValue(self, tick, val): - ## public - """ - Set the position (along the slider) of the tick. - - ============= ================================================================== - **Arguments** - tick Can be either an integer corresponding to the index of the tick - or a Tick object. Ex: if you had a slider with 3 ticks and you - wanted to change the middle tick, the index would be 1. - val The desired position of the tick. If val is < 0, position will be - set to 0. If val is > 1, position will be set to 1. - ============= ================================================================== - """ - tick = self.getTick(tick) - val = min(max(0.0, val), 1.0) - x = val * self.length - pos = tick.pos() - pos.setX(x) - tick.setPos(pos) - self.ticks[tick] = val - - def tickValue(self, tick): - ## public - """Return the value (from 0.0 to 1.0) of the specified tick. - - ============= ================================================================== - **Arguments** - tick Can be either an integer corresponding to the index of the tick - or a Tick object. Ex: if you had a slider with 3 ticks and you - wanted the value of the middle tick, the index would be 1. - ============= ================================================================== - """ - tick = self.getTick(tick) - return self.ticks[tick] - - def getTick(self, tick): - ## public - """Return the Tick object at the specified index. - - ============= ================================================================== - **Arguments** - tick An integer corresponding to the index of the desired tick. If the - argument is not an integer it will be returned unchanged. - ============= ================================================================== - """ - if type(tick) is int: - tick = self.listTicks()[tick][0] - return tick - - #def mouseMoveEvent(self, ev): - #QtGui.QGraphicsView.mouseMoveEvent(self, ev) - - def listTicks(self): - """Return a sorted list of all the Tick objects on the slider.""" - ## public - ticks = list(self.ticks.items()) - sortList(ticks, lambda a,b: cmp(a[1], b[1])) ## see pyqtgraph.python2_3.sortList - return ticks - - -class GradientEditorItem(TickSliderItem): - """ - **Bases:** :class:`TickSliderItem ` - - An item that can be used to define a color gradient. Implements common pre-defined gradients that are - customizable by the user. :class: `GradientWidget ` provides a widget - with a GradientEditorItem that can be added to a GUI. - - ================================ =========================================================== - **Signals** - sigGradientChanged(self) Signal is emitted anytime the gradient changes. The signal - is emitted in real time while ticks are being dragged or - colors are being changed. - sigGradientChangeFinished(self) Signal is emitted when the gradient is finished changing. - ================================ =========================================================== - - """ - - sigGradientChanged = QtCore.Signal(object) - sigGradientChangeFinished = QtCore.Signal(object) - - def __init__(self, *args, **kargs): - """ - Create a new GradientEditorItem. - All arguments are passed to :func:`TickSliderItem.__init__ ` - - ============= ================================================================================= - **Arguments** - orientation Set the orientation of the gradient. Options are: 'left', 'right' - 'top', and 'bottom'. - allowAdd Default is True. Specifies whether ticks can be added to the item. - tickPen Default is white. Specifies the color of the outline of the ticks. - Can be any of the valid arguments for :func:`mkPen ` - ============= ================================================================================= - """ - self.currentTick = None - self.currentTickColor = None - self.rectSize = 15 - self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, self.rectSize, 100, self.rectSize)) - self.backgroundRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize)) - self.backgroundRect.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) - self.colorMode = 'rgb' - - TickSliderItem.__init__(self, *args, **kargs) - - self.colorDialog = QtGui.QColorDialog() - self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) - self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) - - self.colorDialog.currentColorChanged.connect(self.currentColorChanged) - self.colorDialog.rejected.connect(self.currentColorRejected) - self.colorDialog.accepted.connect(self.currentColorAccepted) - - self.backgroundRect.setParentItem(self) - self.gradRect.setParentItem(self) - - self.setMaxDim(self.rectSize + self.tickSize) - - self.rgbAction = QtGui.QAction('RGB', self) - self.rgbAction.setCheckable(True) - self.rgbAction.triggered.connect(lambda: self.setColorMode('rgb')) - self.hsvAction = QtGui.QAction('HSV', self) - self.hsvAction.setCheckable(True) - self.hsvAction.triggered.connect(lambda: self.setColorMode('hsv')) - - self.menu = QtGui.QMenu() - - ## build context menu of gradients - l = self.length - self.length = 100 - global Gradients - for g in Gradients: - px = QtGui.QPixmap(100, 15) - p = QtGui.QPainter(px) - self.restoreState(Gradients[g]) - grad = self.getGradient() - brush = QtGui.QBrush(grad) - p.fillRect(QtCore.QRect(0, 0, 100, 15), brush) - p.end() - label = QtGui.QLabel() - label.setPixmap(px) - label.setContentsMargins(1, 1, 1, 1) - act = QtGui.QWidgetAction(self) - act.setDefaultWidget(label) - act.triggered.connect(self.contextMenuClicked) - act.name = g - self.menu.addAction(act) - self.length = l - self.menu.addSeparator() - self.menu.addAction(self.rgbAction) - self.menu.addAction(self.hsvAction) - - - for t in list(self.ticks.keys()): - self.removeTick(t) - self.addTick(0, QtGui.QColor(0,0,0), True) - self.addTick(1, QtGui.QColor(255,0,0), True) - self.setColorMode('rgb') - self.updateGradient() - - def setOrientation(self, orientation): - ## public - """ - Set the orientation of the GradientEditorItem. - - ============= =================================================================== - **Arguments** - orientation Options are: 'left', 'right', 'top', 'bottom' - The orientation option specifies which side of the gradient the - ticks are on, as well as whether the gradient is vertical ('right' - and 'left') or horizontal ('top' and 'bottom'). - ============= =================================================================== - """ - TickSliderItem.setOrientation(self, orientation) - self.translate(0, self.rectSize) - - def showMenu(self, ev): - #private - self.menu.popup(ev.screenPos().toQPoint()) - - def contextMenuClicked(self, b=None): - #private - #global Gradients - act = self.sender() - self.loadPreset(act.name) - - def loadPreset(self, name): - """ - Load a predefined gradient. - - """ ## TODO: provide image with names of defined gradients - #global Gradients - self.restoreState(Gradients[name]) - - def setColorMode(self, cm): - """ - Set the color mode for the gradient. Options are: 'hsv', 'rgb' - - """ - - ## public - if cm not in ['rgb', 'hsv']: - raise Exception("Unknown color mode %s. Options are 'rgb' and 'hsv'." % str(cm)) - - try: - self.rgbAction.blockSignals(True) - self.hsvAction.blockSignals(True) - self.rgbAction.setChecked(cm == 'rgb') - self.hsvAction.setChecked(cm == 'hsv') - finally: - self.rgbAction.blockSignals(False) - self.hsvAction.blockSignals(False) - self.colorMode = cm - self.updateGradient() - - def colorMap(self): - """Return a ColorMap object representing the current state of the editor.""" - if self.colorMode == 'hsv': - raise NotImplementedError('hsv colormaps not yet supported') - pos = [] - color = [] - for t,x in self.listTicks(): - pos.append(x) - c = t.color - color.append([c.red(), c.green(), c.blue(), c.alpha()]) - return ColorMap(np.array(pos), np.array(color, dtype=np.ubyte)) - - def updateGradient(self): - #private - self.gradient = self.getGradient() - self.gradRect.setBrush(QtGui.QBrush(self.gradient)) - self.sigGradientChanged.emit(self) - - def setLength(self, newLen): - #private (but maybe public) - TickSliderItem.setLength(self, newLen) - self.backgroundRect.setRect(1, -self.rectSize, newLen, self.rectSize) - self.gradRect.setRect(1, -self.rectSize, newLen, self.rectSize) - self.updateGradient() - - def currentColorChanged(self, color): - #private - if color.isValid() and self.currentTick is not None: - self.setTickColor(self.currentTick, color) - self.updateGradient() - - def currentColorRejected(self): - #private - self.setTickColor(self.currentTick, self.currentTickColor) - self.updateGradient() - - def currentColorAccepted(self): - self.sigGradientChangeFinished.emit(self) - - def tickClicked(self, tick, ev): - #private - if ev.button() == QtCore.Qt.LeftButton: - if not tick.colorChangeAllowed: - return - self.currentTick = tick - self.currentTickColor = tick.color - self.colorDialog.setCurrentColor(tick.color) - self.colorDialog.open() - #color = QtGui.QColorDialog.getColor(tick.color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) - #if color.isValid(): - #self.setTickColor(tick, color) - #self.updateGradient() - elif ev.button() == QtCore.Qt.RightButton: - if not tick.removeAllowed: - return - if len(self.ticks) > 2: - self.removeTick(tick) - self.updateGradient() - - def tickMoved(self, tick, pos): - #private - TickSliderItem.tickMoved(self, tick, pos) - self.updateGradient() - - def tickMoveFinished(self, tick): - self.sigGradientChangeFinished.emit(self) - - - def getGradient(self): - """Return a QLinearGradient object.""" - g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0)) - if self.colorMode == 'rgb': - ticks = self.listTicks() - g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) - elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop - ticks = self.listTicks() - stops = [] - stops.append((ticks[0][1], ticks[0][0].color)) - for i in range(1,len(ticks)): - x1 = ticks[i-1][1] - x2 = ticks[i][1] - dx = (x2-x1) / 10. - for j in range(1,10): - x = x1 + dx*j - stops.append((x, self.getColor(x))) - stops.append((x2, self.getColor(x2))) - g.setStops(stops) - return g - - def getColor(self, x, toQColor=True): - """ - Return a color for a given value. - - ============= ================================================================== - **Arguments** - x Value (position on gradient) of requested color. - toQColor If true, returns a QColor object, else returns a (r,g,b,a) tuple. - ============= ================================================================== - """ - ticks = self.listTicks() - if x <= ticks[0][1]: - c = ticks[0][0].color - if toQColor: - return QtGui.QColor(c) # always copy colors before handing them out - else: - return (c.red(), c.green(), c.blue(), c.alpha()) - if x >= ticks[-1][1]: - c = ticks[-1][0].color - if toQColor: - return QtGui.QColor(c) # always copy colors before handing them out - else: - return (c.red(), c.green(), c.blue(), c.alpha()) - - x2 = ticks[0][1] - for i in range(1,len(ticks)): - x1 = x2 - x2 = ticks[i][1] - if x1 <= x and x2 >= x: - break - - dx = (x2-x1) - if dx == 0: - f = 0. - else: - f = (x-x1) / dx - c1 = ticks[i-1][0].color - c2 = ticks[i][0].color - if self.colorMode == 'rgb': - r = c1.red() * (1.-f) + c2.red() * f - g = c1.green() * (1.-f) + c2.green() * f - b = c1.blue() * (1.-f) + c2.blue() * f - a = c1.alpha() * (1.-f) + c2.alpha() * f - if toQColor: - return QtGui.QColor(int(r), int(g), int(b), int(a)) - else: - return (r,g,b,a) - elif self.colorMode == 'hsv': - h1,s1,v1,_ = c1.getHsv() - h2,s2,v2,_ = c2.getHsv() - h = h1 * (1.-f) + h2 * f - s = s1 * (1.-f) + s2 * f - v = v1 * (1.-f) + v2 * f - c = QtGui.QColor() - c.setHsv(h,s,v) - if toQColor: - return c - else: - return (c.red(), c.green(), c.blue(), c.alpha()) - - def getLookupTable(self, nPts, alpha=None): - """ - Return an RGB(A) lookup table (ndarray). - - ============= ============================================================================ - **Arguments** - nPts The number of points in the returned lookup table. - alpha True, False, or None - Specifies whether or not alpha values are included - in the table.If alpha is None, alpha will be automatically determined. - ============= ============================================================================ - """ - if alpha is None: - alpha = self.usesAlpha() - if alpha: - table = np.empty((nPts,4), dtype=np.ubyte) - else: - table = np.empty((nPts,3), dtype=np.ubyte) - - for i in range(nPts): - x = float(i)/(nPts-1) - color = self.getColor(x, toQColor=False) - table[i] = color[:table.shape[1]] - - return table - - def usesAlpha(self): - """Return True if any ticks have an alpha < 255""" - - ticks = self.listTicks() - for t in ticks: - if t[0].color.alpha() < 255: - return True - - return False - - def isLookupTrivial(self): - """Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0""" - ticks = self.listTicks() - if len(ticks) != 2: - return False - if ticks[0][1] != 0.0 or ticks[1][1] != 1.0: - return False - c1 = fn.colorTuple(ticks[0][0].color) - c2 = fn.colorTuple(ticks[1][0].color) - if c1 != (0,0,0,255) or c2 != (255,255,255,255): - return False - return True - - - def mouseReleaseEvent(self, ev): - #private - TickSliderItem.mouseReleaseEvent(self, ev) - self.updateGradient() - - def addTick(self, x, color=None, movable=True, finish=True): - """ - Add a tick to the gradient. Return the tick. - - ============= ================================================================== - **Arguments** - x Position where tick should be added. - color Color of added tick. If color is not specified, the color will be - the color of the gradient at the specified position. - movable Specifies whether the tick is movable with the mouse. - ============= ================================================================== - """ - - - if color is None: - color = self.getColor(x) - t = TickSliderItem.addTick(self, x, color=color, movable=movable) - t.colorChangeAllowed = True - t.removeAllowed = True - - if finish: - self.sigGradientChangeFinished.emit(self) - return t - - - def removeTick(self, tick, finish=True): - TickSliderItem.removeTick(self, tick) - if finish: - self.sigGradientChangeFinished.emit(self) - - - def saveState(self): - """ - Return a dictionary with parameters for rebuilding the gradient. Keys will include: - - - 'mode': hsv or rgb - - 'ticks': a list of tuples (pos, (r,g,b,a)) - """ - ## public - ticks = [] - for t in self.ticks: - c = t.color - ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha()))) - state = {'mode': self.colorMode, 'ticks': ticks} - return state - - def restoreState(self, state): - """ - Restore the gradient specified in state. - - ============= ==================================================================== - **Arguments** - state A dictionary with same structure as those returned by - :func:`saveState ` - - Keys must include: - - - 'mode': hsv or rgb - - 'ticks': a list of tuples (pos, (r,g,b,a)) - ============= ==================================================================== - """ - ## public - self.setColorMode(state['mode']) - for t in list(self.ticks.keys()): - self.removeTick(t, finish=False) - for t in state['ticks']: - c = QtGui.QColor(*t[1]) - self.addTick(t[0], c, finish=False) - self.updateGradient() - self.sigGradientChangeFinished.emit(self) - - def setColorMap(self, cm): - self.setColorMode('rgb') - for t in list(self.ticks.keys()): - self.removeTick(t, finish=False) - colors = cm.getColors(mode='qcolor') - for i in range(len(cm.pos)): - x = cm.pos[i] - c = colors[i] - self.addTick(x, c, finish=False) - self.updateGradient() - self.sigGradientChangeFinished.emit(self) - - -class Tick(QtGui.QGraphicsObject): ## NOTE: Making this a subclass of GraphicsObject instead results in - ## activating this bug: https://bugreports.qt-project.org/browse/PYSIDE-86 - ## private class - - sigMoving = QtCore.Signal(object) - sigMoved = QtCore.Signal(object) - - def __init__(self, view, pos, color, movable=True, scale=10, pen='w'): - self.movable = movable - self.moving = False - self.view = weakref.ref(view) - self.scale = scale - self.color = color - self.pen = fn.mkPen(pen) - self.hoverPen = fn.mkPen(255,255,0) - self.currentPen = self.pen - self.pg = QtGui.QPainterPath(QtCore.QPointF(0,0)) - self.pg.lineTo(QtCore.QPointF(-scale/3**0.5, scale)) - self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale)) - self.pg.closeSubpath() - - QtGui.QGraphicsObject.__init__(self) - self.setPos(pos[0], pos[1]) - if self.movable: - self.setZValue(1) - else: - self.setZValue(0) - - def boundingRect(self): - return self.pg.boundingRect() - - def shape(self): - return self.pg - - def paint(self, p, *args): - p.setRenderHints(QtGui.QPainter.Antialiasing) - p.fillPath(self.pg, fn.mkBrush(self.color)) - - p.setPen(self.currentPen) - p.drawPath(self.pg) - - - def mouseDragEvent(self, ev): - if self.movable and ev.button() == QtCore.Qt.LeftButton: - if ev.isStart(): - self.moving = True - self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) - self.startPosition = self.pos() - ev.accept() - - if not self.moving: - return - - newPos = self.cursorOffset + self.mapToParent(ev.pos()) - newPos.setY(self.pos().y()) - - self.setPos(newPos) - self.view().tickMoved(self, newPos) - self.sigMoving.emit(self) - if ev.isFinish(): - self.moving = False - self.sigMoved.emit(self) - self.view().tickMoveFinished(self) - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton and self.moving: - ev.accept() - self.setPos(self.startPosition) - self.view().tickMoved(self, self.startPosition) - self.moving = False - self.sigMoving.emit(self) - self.sigMoved.emit(self) - else: - self.view().tickClicked(self, ev) - ##remove - - def hoverEvent(self, ev): - if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): - ev.acceptClicks(QtCore.Qt.LeftButton) - ev.acceptClicks(QtCore.Qt.RightButton) - self.currentPen = self.hoverPen - else: - self.currentPen = self.pen - self.update() - - #def mouseMoveEvent(self, ev): - ##print self, "move", ev.scenePos() - #if not self.movable: - #return - #if not ev.buttons() & QtCore.Qt.LeftButton: - #return - - - #newPos = ev.scenePos() + self.mouseOffset - #newPos.setY(self.pos().y()) - ##newPos.setX(min(max(newPos.x(), 0), 100)) - #self.setPos(newPos) - #self.view().tickMoved(self, newPos) - #self.movedSincePress = True - ##self.emit(QtCore.SIGNAL('tickChanged'), self) - #ev.accept() - - #def mousePressEvent(self, ev): - #self.movedSincePress = False - #if ev.button() == QtCore.Qt.LeftButton: - #ev.accept() - #self.mouseOffset = self.pos() - ev.scenePos() - #self.pressPos = ev.scenePos() - #elif ev.button() == QtCore.Qt.RightButton: - #ev.accept() - ##if self.endTick: - ##return - ##self.view.tickChanged(self, delete=True) - - #def mouseReleaseEvent(self, ev): - ##print self, "release", ev.scenePos() - #if not self.movedSincePress: - #self.view().tickClicked(self, ev) - - ##if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos: - ##color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel) - ##if color.isValid(): - ##self.color = color - ##self.setBrush(QtGui.QBrush(QtGui.QColor(self.color))) - ###self.emit(QtCore.SIGNAL('tickChanged'), self) - ##self.view.tickChanged(self) diff --git a/pyqtgraph/graphicsItems/GradientLegend.py b/pyqtgraph/graphicsItems/GradientLegend.py deleted file mode 100644 index 4528b7ed..00000000 --- a/pyqtgraph/graphicsItems/GradientLegend.py +++ /dev/null @@ -1,114 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from .UIGraphicsItem import * -import pyqtgraph.functions as fn - -__all__ = ['GradientLegend'] - -class GradientLegend(UIGraphicsItem): - """ - Draws a color gradient rectangle along with text labels denoting the value at specific - points along the gradient. - """ - - def __init__(self, size, offset): - self.size = size - self.offset = offset - UIGraphicsItem.__init__(self) - self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - self.brush = QtGui.QBrush(QtGui.QColor(200,0,0)) - self.pen = QtGui.QPen(QtGui.QColor(0,0,0)) - self.labels = {'max': 1, 'min': 0} - self.gradient = QtGui.QLinearGradient() - self.gradient.setColorAt(0, QtGui.QColor(0,0,0)) - self.gradient.setColorAt(1, QtGui.QColor(255,0,0)) - - def setGradient(self, g): - self.gradient = g - self.update() - - def setIntColorScale(self, minVal, maxVal, *args, **kargs): - colors = [fn.intColor(i, maxVal-minVal, *args, **kargs) for i in range(minVal, maxVal)] - g = QtGui.QLinearGradient() - for i in range(len(colors)): - x = float(i)/len(colors) - g.setColorAt(x, colors[i]) - self.setGradient(g) - if 'labels' not in kargs: - self.setLabels({str(minVal/10.): 0, str(maxVal): 1}) - else: - self.setLabels({kargs['labels'][0]:0, kargs['labels'][1]:1}) - - def setLabels(self, l): - """Defines labels to appear next to the color scale. Accepts a dict of {text: value} pairs""" - self.labels = l - self.update() - - def paint(self, p, opt, widget): - UIGraphicsItem.paint(self, p, opt, widget) - rect = self.boundingRect() ## Boundaries of visible area in scene coords. - unit = self.pixelSize() ## Size of one view pixel in scene coords. - if unit[0] is None: - return - - ## determine max width of all labels - labelWidth = 0 - labelHeight = 0 - for k in self.labels: - b = p.boundingRect(QtCore.QRectF(0, 0, 0, 0), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) - labelWidth = max(labelWidth, b.width()) - labelHeight = max(labelHeight, b.height()) - - labelWidth *= unit[0] - labelHeight *= unit[1] - - textPadding = 2 # in px - - if self.offset[0] < 0: - x3 = rect.right() + unit[0] * self.offset[0] - x2 = x3 - labelWidth - unit[0]*textPadding*2 - x1 = x2 - unit[0] * self.size[0] - else: - x1 = rect.left() + unit[0] * self.offset[0] - x2 = x1 + unit[0] * self.size[0] - x3 = x2 + labelWidth + unit[0]*textPadding*2 - if self.offset[1] < 0: - y2 = rect.top() - unit[1] * self.offset[1] - y1 = y2 + unit[1] * self.size[1] - else: - y1 = rect.bottom() - unit[1] * self.offset[1] - y2 = y1 - unit[1] * self.size[1] - self.b = [x1,x2,x3,y1,y2,labelWidth] - - ## Draw background - p.setPen(self.pen) - p.setBrush(QtGui.QBrush(QtGui.QColor(255,255,255,100))) - rect = QtCore.QRectF( - QtCore.QPointF(x1 - unit[0]*textPadding, y1 + labelHeight/2 + unit[1]*textPadding), - QtCore.QPointF(x3, y2 - labelHeight/2 - unit[1]*textPadding) - ) - p.drawRect(rect) - - - ## Have to scale painter so that text and gradients are correct size. Bleh. - p.scale(unit[0], unit[1]) - - ## Draw color bar - self.gradient.setStart(0, y1/unit[1]) - self.gradient.setFinalStop(0, y2/unit[1]) - p.setBrush(self.gradient) - rect = QtCore.QRectF( - QtCore.QPointF(x1/unit[0], y1/unit[1]), - QtCore.QPointF(x2/unit[0], y2/unit[1]) - ) - p.drawRect(rect) - - - ## draw labels - p.setPen(QtGui.QPen(QtGui.QColor(0,0,0))) - tx = x2 + unit[0]*textPadding - lh = labelHeight/unit[1] - for k in self.labels: - y = y1 + self.labels[k] * (y2-y1) - p.drawText(QtCore.QRectF(tx/unit[0], y/unit[1] - lh/2.0, 1000, lh), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) - - diff --git a/pyqtgraph/graphicsItems/GraphItem.py b/pyqtgraph/graphicsItems/GraphItem.py deleted file mode 100644 index b1f34baa..00000000 --- a/pyqtgraph/graphicsItems/GraphItem.py +++ /dev/null @@ -1,122 +0,0 @@ -from .. import functions as fn -from .GraphicsObject import GraphicsObject -from .ScatterPlotItem import ScatterPlotItem -import pyqtgraph as pg -import numpy as np - -__all__ = ['GraphItem'] - - -class GraphItem(GraphicsObject): - """A GraphItem displays graph information as - a set of nodes connected by lines (as in 'graph theory', not 'graphics'). - Useful for drawing networks, trees, etc. - """ - - def __init__(self, **kwds): - GraphicsObject.__init__(self) - self.scatter = ScatterPlotItem() - self.scatter.setParentItem(self) - self.adjacency = None - self.pos = None - self.picture = None - self.pen = 'default' - self.setData(**kwds) - - def setData(self, **kwds): - """ - Change the data displayed by the graph. - - ============ ========================================================= - Arguments - pos (N,2) array of the positions of each node in the graph. - adj (M,2) array of connection data. Each row contains indexes - of two nodes that are connected. - pen The pen to use when drawing lines between connected - nodes. May be one of: - - * QPen - * a single argument to pass to pg.mkPen - * a record array of length M - with fields (red, green, blue, alpha, width). Note - that using this option may have a significant performance - cost. - * None (to disable connection drawing) - * 'default' to use the default foreground color. - - symbolPen The pen used for drawing nodes. - ``**opts`` All other keyword arguments are given to - :func:`ScatterPlotItem.setData() ` - to affect the appearance of nodes (symbol, size, brush, - etc.) - ============ ========================================================= - """ - if 'adj' in kwds: - self.adjacency = kwds.pop('adj') - assert self.adjacency.dtype.kind in 'iu' - self.picture = None - if 'pos' in kwds: - self.pos = kwds['pos'] - self.picture = None - if 'pen' in kwds: - self.setPen(kwds.pop('pen')) - self.picture = None - if 'symbolPen' in kwds: - kwds['pen'] = kwds.pop('symbolPen') - self.scatter.setData(**kwds) - self.informViewBoundsChanged() - - def setPen(self, pen): - self.pen = pen - self.picture = None - - def generatePicture(self): - self.picture = pg.QtGui.QPicture() - if self.pen is None or self.pos is None or self.adjacency is None: - return - - p = pg.QtGui.QPainter(self.picture) - try: - pts = self.pos[self.adjacency] - pen = self.pen - if isinstance(pen, np.ndarray): - lastPen = None - for i in range(pts.shape[0]): - pen = self.pen[i] - if np.any(pen != lastPen): - lastPen = pen - if pen.dtype.fields is None: - p.setPen(pg.mkPen(color=(pen[0], pen[1], pen[2], pen[3]), width=1)) - else: - p.setPen(pg.mkPen(color=(pen['red'], pen['green'], pen['blue'], pen['alpha']), width=pen['width'])) - p.drawLine(pg.QtCore.QPointF(*pts[i][0]), pg.QtCore.QPointF(*pts[i][1])) - else: - if pen == 'default': - pen = pg.getConfigOption('foreground') - p.setPen(pg.mkPen(pen)) - pts = pts.reshape((pts.shape[0]*pts.shape[1], pts.shape[2])) - path = fn.arrayToQPath(x=pts[:,0], y=pts[:,1], connect='pairs') - p.drawPath(path) - finally: - p.end() - - def paint(self, p, *args): - if self.picture == None: - self.generatePicture() - if pg.getConfigOption('antialias') is True: - p.setRenderHint(p.Antialiasing) - self.picture.play(p) - - def boundingRect(self): - return self.scatter.boundingRect() - - def dataBounds(self, *args, **kwds): - return self.scatter.dataBounds(*args, **kwds) - - def pixelPadding(self): - return self.scatter.pixelPadding() - - - - - diff --git a/pyqtgraph/graphicsItems/GraphicsItem.py b/pyqtgraph/graphicsItems/GraphicsItem.py deleted file mode 100644 index a129436e..00000000 --- a/pyqtgraph/graphicsItems/GraphicsItem.py +++ /dev/null @@ -1,587 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.GraphicsScene import GraphicsScene -from pyqtgraph.Point import Point -import pyqtgraph.functions as fn -import weakref -from pyqtgraph.pgcollections import OrderedDict -import operator, sys - -class FiniteCache(OrderedDict): - """Caches a finite number of objects, removing - least-frequently used items.""" - def __init__(self, length): - self._length = length - OrderedDict.__init__(self) - - def __setitem__(self, item, val): - self.pop(item, None) # make sure item is added to end - OrderedDict.__setitem__(self, item, val) - while len(self) > self._length: - del self[list(self.keys())[0]] - - def __getitem__(self, item): - val = OrderedDict.__getitem__(self, item) - del self[item] - self[item] = val ## promote this key - return val - - - -class GraphicsItem(object): - """ - **Bases:** :class:`object` - - Abstract class providing useful methods to GraphicsObject and GraphicsWidget. - (This is required because we cannot have multiple inheritance with QObject subclasses.) - - A note about Qt's GraphicsView framework: - - The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task. - """ - _pixelVectorGlobalCache = FiniteCache(100) - - def __init__(self, register=True): - if not hasattr(self, '_qtBaseClass'): - for b in self.__class__.__bases__: - if issubclass(b, QtGui.QGraphicsItem): - self.__class__._qtBaseClass = b - break - if not hasattr(self, '_qtBaseClass'): - raise Exception('Could not determine Qt base class for GraphicsItem: %s' % str(self)) - - self._pixelVectorCache = [None, None] - self._viewWidget = None - self._viewBox = None - self._connectedView = None - self._exportOpts = False ## If False, not currently exporting. Otherwise, contains dict of export options. - if register: - GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() - - - - - def getViewWidget(self): - """ - Return the view widget for this item. If the scene has multiple views, only the first view is returned. - The return value is cached; clear the cached value with forgetViewWidget() - """ - if self._viewWidget is None: - scene = self.scene() - if scene is None: - return None - views = scene.views() - if len(views) < 1: - return None - self._viewWidget = weakref.ref(self.scene().views()[0]) - return self._viewWidget() - - def forgetViewWidget(self): - self._viewWidget = None - - def getViewBox(self): - """ - Return the first ViewBox or GraphicsView which bounds this item's visible space. - If this item is not contained within a ViewBox, then the GraphicsView is returned. - If the item is contained inside nested ViewBoxes, then the inner-most ViewBox is returned. - The result is cached; clear the cache with forgetViewBox() - """ - if self._viewBox is None: - p = self - while True: - try: - p = p.parentItem() - except RuntimeError: ## sometimes happens as items are being removed from a scene and collected. - return None - if p is None: - vb = self.getViewWidget() - if vb is None: - return None - else: - self._viewBox = weakref.ref(vb) - break - if hasattr(p, 'implements') and p.implements('ViewBox'): - self._viewBox = weakref.ref(p) - break - return self._viewBox() ## If we made it this far, _viewBox is definitely not None - - def forgetViewBox(self): - self._viewBox = None - - - def deviceTransform(self, viewportTransform=None): - """ - Return the transform that converts local item coordinates to device coordinates (usually pixels). - Extends deviceTransform to automatically determine the viewportTransform. - """ - if self._exportOpts is not False and 'painter' in self._exportOpts: ## currently exporting; device transform may be different. - return self._exportOpts['painter'].deviceTransform() - - if viewportTransform is None: - view = self.getViewWidget() - if view is None: - return None - viewportTransform = view.viewportTransform() - dt = self._qtBaseClass.deviceTransform(self, viewportTransform) - - #xmag = abs(dt.m11())+abs(dt.m12()) - #ymag = abs(dt.m21())+abs(dt.m22()) - #if xmag * ymag == 0: - if dt.determinant() == 0: ## occurs when deviceTransform is invalid because widget has not been displayed - return None - else: - return dt - - def viewTransform(self): - """Return the transform that maps from local coordinates to the item's ViewBox coordinates - If there is no ViewBox, return the scene transform. - Returns None if the item does not have a view.""" - view = self.getViewBox() - if view is None: - return None - if hasattr(view, 'implements') and view.implements('ViewBox'): - tr = self.itemTransform(view.innerSceneItem()) - if isinstance(tr, tuple): - tr = tr[0] ## difference between pyside and pyqt - return tr - else: - return self.sceneTransform() - #return self.deviceTransform(view.viewportTransform()) - - - - def getBoundingParents(self): - """Return a list of parents to this item that have child clipping enabled.""" - p = self - parents = [] - while True: - p = p.parentItem() - if p is None: - break - if p.flags() & self.ItemClipsChildrenToShape: - parents.append(p) - return parents - - def viewRect(self): - """Return the bounds (in item coordinates) of this item's ViewBox or GraphicsWidget""" - view = self.getViewBox() - if view is None: - return None - bounds = self.mapRectFromView(view.viewRect()) - if bounds is None: - return None - - bounds = bounds.normalized() - - ## nah. - #for p in self.getBoundingParents(): - #bounds &= self.mapRectFromScene(p.sceneBoundingRect()) - - return bounds - - - - def pixelVectors(self, direction=None): - """Return vectors in local coordinates representing the width and height of a view pixel. - If direction is specified, then return vectors parallel and orthogonal to it. - - Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed) - or if pixel size is below floating-point precision limit. - """ - - ## This is an expensive function that gets called very frequently. - ## We have two levels of cache to try speeding things up. - - dt = self.deviceTransform() - if dt is None: - return None, None - - ## Ignore translation. If the translation is much larger than the scale - ## (such as when looking at unix timestamps), we can get floating-point errors. - dt.setMatrix(dt.m11(), dt.m12(), 0, dt.m21(), dt.m22(), 0, 0, 0, 1) - - ## check local cache - if direction is None and dt == self._pixelVectorCache[0]: - return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy* - - ## check global cache - #key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32()) - key = (dt.m11(), dt.m21(), dt.m12(), dt.m22()) - pv = self._pixelVectorGlobalCache.get(key, None) - if direction is None and pv is not None: - self._pixelVectorCache = [dt, pv] - return tuple(map(Point,pv)) ## return a *copy* - - - if direction is None: - direction = QtCore.QPointF(1, 0) - if direction.manhattanLength() == 0: - raise Exception("Cannot compute pixel length for 0-length vector.") - - ## attempt to re-scale direction vector to fit within the precision of the coordinate system - ## Here's the problem: we need to map the vector 'direction' from the item to the device, via transform 'dt'. - ## In some extreme cases, this mapping can fail unless the length of 'direction' is cleverly chosen. - ## Example: - ## dt = [ 1, 0, 2 - ## 0, 2, 1e20 - ## 0, 0, 1 ] - ## Then we map the origin (0,0) and direction (0,1) and get: - ## o' = 2,1e20 - ## d' = 2,1e20 <-- should be 1e20+2, but this can't be represented with a 32-bit float - ## - ## |o' - d'| == 0 <-- this is the problem. - - ## Perhaps the easiest solution is to exclude the transformation column from dt. Does this cause any other problems? - - #if direction.x() == 0: - #r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22())) - ##r = 1.0/(abs(dt.m12()) + abs(dt.m22())) - #elif direction.y() == 0: - #r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21())) - ##r = 1.0/(abs(dt.m11()) + abs(dt.m21())) - #else: - #r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5 - #if r == 0: - #r = 1. ## shouldn't need to do this; probably means the math above is wrong? - #directionr = direction * r - directionr = direction - - ## map direction vector onto device - #viewDir = Point(dt.map(directionr) - dt.map(Point(0,0))) - #mdirection = dt.map(directionr) - dirLine = QtCore.QLineF(QtCore.QPointF(0,0), directionr) - viewDir = dt.map(dirLine) - if viewDir.length() == 0: - return None, None ## pixel size cannot be represented on this scale - - ## get unit vector and orthogonal vector (length of pixel) - #orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space - try: - normView = viewDir.unitVector() - #normView = viewDir.norm() ## direction of one pixel orthogonal to line - normOrtho = normView.normalVector() - #normOrtho = orthoDir.norm() - except: - raise Exception("Invalid direction %s" %directionr) - - ## map back to item - dti = fn.invertQTransform(dt) - #pv = Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0))) - pv = Point(dti.map(normView).p2()), Point(dti.map(normOrtho).p2()) - self._pixelVectorCache[1] = pv - self._pixelVectorCache[0] = dt - self._pixelVectorGlobalCache[key] = pv - return self._pixelVectorCache[1] - - - def pixelLength(self, direction, ortho=False): - """Return the length of one pixel in the direction indicated (in local coordinates) - If ortho=True, then return the length of one pixel orthogonal to the direction indicated. - - Return None if pixel size is not yet defined (usually because the item has not yet been displayed). - """ - normV, orthoV = self.pixelVectors(direction) - if normV == None or orthoV == None: - return None - if ortho: - return orthoV.length() - return normV.length() - - - def pixelSize(self): - ## deprecated - v = self.pixelVectors() - if v == (None, None): - return None, None - return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5 - - def pixelWidth(self): - ## deprecated - vt = self.deviceTransform() - if vt is None: - return 0 - vt = fn.invertQTransform(vt) - return vt.map(QtCore.QLineF(0, 0, 1, 0)).length() - - def pixelHeight(self): - ## deprecated - vt = self.deviceTransform() - if vt is None: - return 0 - vt = fn.invertQTransform(vt) - return vt.map(QtCore.QLineF(0, 0, 0, 1)).length() - #return Point(vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).length() - - - def mapToDevice(self, obj): - """ - Return *obj* mapped from local coordinates to device coordinates (pixels). - If there is no device mapping available, return None. - """ - vt = self.deviceTransform() - if vt is None: - return None - return vt.map(obj) - - def mapFromDevice(self, obj): - """ - Return *obj* mapped from device coordinates (pixels) to local coordinates. - If there is no device mapping available, return None. - """ - vt = self.deviceTransform() - if vt is None: - return None - vt = fn.invertQTransform(vt) - return vt.map(obj) - - def mapRectToDevice(self, rect): - """ - Return *rect* mapped from local coordinates to device coordinates (pixels). - If there is no device mapping available, return None. - """ - vt = self.deviceTransform() - if vt is None: - return None - return vt.mapRect(rect) - - def mapRectFromDevice(self, rect): - """ - Return *rect* mapped from device coordinates (pixels) to local coordinates. - If there is no device mapping available, return None. - """ - vt = self.deviceTransform() - if vt is None: - return None - vt = fn.invertQTransform(vt) - return vt.mapRect(rect) - - def mapToView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - return vt.map(obj) - - def mapRectToView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - return vt.mapRect(obj) - - def mapFromView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - vt = fn.invertQTransform(vt) - return vt.map(obj) - - def mapRectFromView(self, obj): - vt = self.viewTransform() - if vt is None: - return None - vt = fn.invertQTransform(vt) - return vt.mapRect(obj) - - def pos(self): - return Point(self._qtBaseClass.pos(self)) - - def viewPos(self): - return self.mapToView(self.mapFromParent(self.pos())) - - def parentItem(self): - ## PyQt bug -- some items are returned incorrectly. - return GraphicsScene.translateGraphicsItem(self._qtBaseClass.parentItem(self)) - - def setParentItem(self, parent): - ## Workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 - if parent is not None: - pscene = parent.scene() - if pscene is not None and self.scene() is not pscene: - pscene.addItem(self) - return self._qtBaseClass.setParentItem(self, parent) - - def childItems(self): - ## PyQt bug -- some child items are returned incorrectly. - return list(map(GraphicsScene.translateGraphicsItem, self._qtBaseClass.childItems(self))) - - - def sceneTransform(self): - ## Qt bug: do no allow access to sceneTransform() until - ## the item has a scene. - - if self.scene() is None: - return self.transform() - else: - return self._qtBaseClass.sceneTransform(self) - - - def transformAngle(self, relativeItem=None): - """Return the rotation produced by this item's transform (this assumes there is no shear in the transform) - If relativeItem is given, then the angle is determined relative to that item. - """ - if relativeItem is None: - relativeItem = self.parentItem() - - - tr = self.itemTransform(relativeItem) - if isinstance(tr, tuple): ## difference between pyside and pyqt - tr = tr[0] - #vec = tr.map(Point(1,0)) - tr.map(Point(0,0)) - vec = tr.map(QtCore.QLineF(0,0,1,0)) - #return Point(vec).angle(Point(1,0)) - return vec.angleTo(QtCore.QLineF(vec.p1(), vec.p1()+QtCore.QPointF(1,0))) - - #def itemChange(self, change, value): - #ret = self._qtBaseClass.itemChange(self, change, value) - #if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged: - #print "Item scene changed:", self - #self.setChildScene(self) ## This is bizarre. - #return ret - - #def setChildScene(self, ch): - #scene = self.scene() - #for ch2 in ch.childItems(): - #if ch2.scene() is not scene: - #print "item", ch2, "has different scene:", ch2.scene(), scene - #scene.addItem(ch2) - #QtGui.QApplication.processEvents() - #print " --> ", ch2.scene() - #self.setChildScene(ch2) - - def parentChanged(self): - """Called when the item's parent has changed. - This method handles connecting / disconnecting from ViewBox signals - to make sure viewRangeChanged works properly. It should generally be - extended, not overridden.""" - self._updateView() - - - def _updateView(self): - ## called to see whether this item has a new view to connect to - ## NOTE: This is called from GraphicsObject.itemChange or GraphicsWidget.itemChange. - - ## It is possible this item has moved to a different ViewBox or widget; - ## clear out previously determined references to these. - self.forgetViewBox() - self.forgetViewWidget() - - ## check for this item's current viewbox or view widget - view = self.getViewBox() - #if view is None: - ##print " no view" - #return - - oldView = None - if self._connectedView is not None: - oldView = self._connectedView() - - if view is oldView: - #print " already have view", view - return - - ## disconnect from previous view - if oldView is not None: - #print "disconnect:", self, oldView - try: - oldView.sigRangeChanged.disconnect(self.viewRangeChanged) - except TypeError: - pass - - try: - oldView.sigTransformChanged.disconnect(self.viewTransformChanged) - except TypeError: - pass - - self._connectedView = None - - ## connect to new view - if view is not None: - #print "connect:", self, view - view.sigRangeChanged.connect(self.viewRangeChanged) - view.sigTransformChanged.connect(self.viewTransformChanged) - self._connectedView = weakref.ref(view) - self.viewRangeChanged() - self.viewTransformChanged() - - ## inform children that their view might have changed - self._replaceView(oldView) - - self.viewChanged(view, oldView) - - def viewChanged(self, view, oldView): - """Called when this item's view has changed - (ie, the item has been added to or removed from a ViewBox)""" - pass - - def _replaceView(self, oldView, item=None): - if item is None: - item = self - for child in item.childItems(): - if isinstance(child, GraphicsItem): - if child.getViewBox() is oldView: - child._updateView() - #self._replaceView(oldView, child) - else: - self._replaceView(oldView, child) - - - - def viewRangeChanged(self): - """ - Called whenever the view coordinates of the ViewBox containing this item have changed. - """ - pass - - def viewTransformChanged(self): - """ - Called whenever the transformation matrix of the view has changed. - (eg, the view range has changed or the view was resized) - """ - pass - - #def prepareGeometryChange(self): - #self._qtBaseClass.prepareGeometryChange(self) - #self.informViewBoundsChanged() - - def informViewBoundsChanged(self): - """ - Inform this item's container ViewBox that the bounds of this item have changed. - This is used by ViewBox to react if auto-range is enabled. - """ - view = self.getViewBox() - if view is not None and hasattr(view, 'implements') and view.implements('ViewBox'): - view.itemBoundsChanged(self) ## inform view so it can update its range if it wants - - def childrenShape(self): - """Return the union of the shapes of all descendants of this item in local coordinates.""" - childs = self.allChildItems() - shapes = [self.mapFromItem(c, c.shape()) for c in self.allChildItems()] - return reduce(operator.add, shapes) - - def allChildItems(self, root=None): - """Return list of the entire item tree descending from this item.""" - if root is None: - root = self - tree = [] - for ch in root.childItems(): - tree.append(ch) - tree.extend(self.allChildItems(ch)) - return tree - - - def setExportMode(self, export, opts=None): - """ - This method is called by exporters to inform items that they are being drawn for export - with a specific set of options. Items access these via self._exportOptions. - When exporting is complete, _exportOptions is set to False. - """ - if opts is None: - opts = {} - if export: - self._exportOpts = opts - #if 'antialias' not in opts: - #self._exportOpts['antialias'] = True - else: - self._exportOpts = False - - #def update(self): - #self._qtBaseClass.update(self) - #print "Update:", self diff --git a/pyqtgraph/graphicsItems/GraphicsLayout.py b/pyqtgraph/graphicsItems/GraphicsLayout.py deleted file mode 100644 index 9d48e627..00000000 --- a/pyqtgraph/graphicsItems/GraphicsLayout.py +++ /dev/null @@ -1,154 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph.functions as fn -from .GraphicsWidget import GraphicsWidget -## Must be imported at the end to avoid cyclic-dependency hell: -from .ViewBox import ViewBox -from .PlotItem import PlotItem -from .LabelItem import LabelItem - -__all__ = ['GraphicsLayout'] -class GraphicsLayout(GraphicsWidget): - """ - Used for laying out GraphicsWidgets in a grid. - This is usually created automatically as part of a :class:`GraphicsWindow ` or :class:`GraphicsLayoutWidget `. - """ - - - def __init__(self, parent=None, border=None): - GraphicsWidget.__init__(self, parent) - if border is True: - border = (100,100,100) - self.border = border - self.layout = QtGui.QGraphicsGridLayout() - self.setLayout(self.layout) - self.items = {} ## item: [(row, col), (row, col), ...] lists all cells occupied by the item - self.rows = {} ## row: {col1: item1, col2: item2, ...} maps cell location to item - self.currentRow = 0 - self.currentCol = 0 - self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) - - #def resizeEvent(self, ev): - #ret = GraphicsWidget.resizeEvent(self, ev) - #print self.pos(), self.mapToDevice(self.rect().topLeft()) - #return ret - - def nextRow(self): - """Advance to next row for automatic item placement""" - self.currentRow += 1 - self.currentCol = -1 - self.nextColumn() - - def nextColumn(self): - """Advance to next available column - (generally only for internal use--called by addItem)""" - self.currentCol += 1 - while self.getItem(self.currentRow, self.currentCol) is not None: - self.currentCol += 1 - - def nextCol(self, *args, **kargs): - """Alias of nextColumn""" - return self.nextColumn(*args, **kargs) - - def addPlot(self, row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create a PlotItem and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to :func:`PlotItem.__init__ ` - Returns the created item. - """ - plot = PlotItem(**kargs) - self.addItem(plot, row, col, rowspan, colspan) - return plot - - def addViewBox(self, row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create a ViewBox and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to :func:`ViewBox.__init__ ` - Returns the created item. - """ - vb = ViewBox(**kargs) - self.addItem(vb, row, col, rowspan, colspan) - return vb - - def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create a LabelItem with *text* and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to :func:`LabelItem.__init__ ` - Returns the created item. - - To create a vertical label, use *angle* = -90. - """ - text = LabelItem(text, **kargs) - self.addItem(text, row, col, rowspan, colspan) - return text - - def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create an empty GraphicsLayout and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to :func:`GraphicsLayout.__init__ ` - Returns the created item. - """ - layout = GraphicsLayout(**kargs) - self.addItem(layout, row, col, rowspan, colspan) - return layout - - def addItem(self, item, row=None, col=None, rowspan=1, colspan=1): - """ - Add an item to the layout and place it in the next available cell (or in the cell specified). - The item must be an instance of a QGraphicsWidget subclass. - """ - if row is None: - row = self.currentRow - if col is None: - col = self.currentCol - - self.items[item] = [] - for i in range(rowspan): - for j in range(colspan): - row2 = row + i - col2 = col + j - if row2 not in self.rows: - self.rows[row2] = {} - self.rows[row2][col2] = item - self.items[item].append((row2, col2)) - - self.layout.addItem(item, row, col, rowspan, colspan) - self.nextColumn() - - def getItem(self, row, col): - """Return the item in (*row*, *col*). If the cell is empty, return None.""" - return self.rows.get(row, {}).get(col, None) - - def boundingRect(self): - return self.rect() - - def paint(self, p, *args): - if self.border is None: - return - p.setPen(fn.mkPen(self.border)) - for i in self.items: - r = i.mapRectToParent(i.boundingRect()) - p.drawRect(r) - - def itemIndex(self, item): - for i in range(self.layout.count()): - if self.layout.itemAt(i).graphicsItem() is item: - return i - raise Exception("Could not determine index of item " + str(item)) - - def removeItem(self, item): - """Remove *item* from the layout.""" - ind = self.itemIndex(item) - self.layout.removeAt(ind) - self.scene().removeItem(item) - - for r,c in self.items[item]: - del self.rows[r][c] - del self.items[item] - self.update() - - def clear(self): - items = [] - for i in list(self.items.keys()): - self.removeItem(i) - - diff --git a/pyqtgraph/graphicsItems/GraphicsObject.py b/pyqtgraph/graphicsItems/GraphicsObject.py deleted file mode 100644 index d8f55d27..00000000 --- a/pyqtgraph/graphicsItems/GraphicsObject.py +++ /dev/null @@ -1,32 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE -if not USE_PYSIDE: - import sip -from .GraphicsItem import GraphicsItem - -__all__ = ['GraphicsObject'] -class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject): - """ - **Bases:** :class:`GraphicsItem `, :class:`QtGui.QGraphicsObject` - - Extension of QGraphicsObject with some useful methods (provided by :class:`GraphicsItem `) - """ - _qtBaseClass = QtGui.QGraphicsObject - def __init__(self, *args): - self.__inform_view_on_changes = True - QtGui.QGraphicsObject.__init__(self, *args) - self.setFlag(self.ItemSendsGeometryChanges) - GraphicsItem.__init__(self) - - def itemChange(self, change, value): - ret = QtGui.QGraphicsObject.itemChange(self, change, value) - if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: - self.parentChanged() - if self.__inform_view_on_changes and change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]: - self.informViewBoundsChanged() - - ## workaround for pyqt bug: - ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html - if not USE_PYSIDE and change == self.ItemParentChange and isinstance(ret, QtGui.QGraphicsItem): - ret = sip.cast(ret, QtGui.QGraphicsItem) - - return ret diff --git a/pyqtgraph/graphicsItems/GraphicsWidget.py b/pyqtgraph/graphicsItems/GraphicsWidget.py deleted file mode 100644 index 7650b125..00000000 --- a/pyqtgraph/graphicsItems/GraphicsWidget.py +++ /dev/null @@ -1,59 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.GraphicsScene import GraphicsScene -from .GraphicsItem import GraphicsItem - -__all__ = ['GraphicsWidget'] - -class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget): - - _qtBaseClass = QtGui.QGraphicsWidget - def __init__(self, *args, **kargs): - """ - **Bases:** :class:`GraphicsItem `, :class:`QtGui.QGraphicsWidget` - - Extends QGraphicsWidget with several helpful methods and workarounds for PyQt bugs. - Most of the extra functionality is inherited from :class:`GraphicsItem `. - """ - QtGui.QGraphicsWidget.__init__(self, *args, **kargs) - GraphicsItem.__init__(self) - - ## done by GraphicsItem init - #GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() - - # Removed due to https://bugreports.qt-project.org/browse/PYSIDE-86 - #def itemChange(self, change, value): - ## BEWARE: Calling QGraphicsWidget.itemChange can lead to crashing! - ##ret = QtGui.QGraphicsWidget.itemChange(self, change, value) ## segv occurs here - ## The default behavior is just to return the value argument, so we'll do that - ## without calling the original method. - #ret = value - #if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: - #self._updateView() - #return ret - - def setFixedHeight(self, h): - self.setMaximumHeight(h) - self.setMinimumHeight(h) - - def setFixedWidth(self, h): - self.setMaximumWidth(h) - self.setMinimumWidth(h) - - def height(self): - return self.geometry().height() - - def width(self): - return self.geometry().width() - - def boundingRect(self): - br = self.mapRectFromParent(self.geometry()).normalized() - #print "bounds:", br - return br - - def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. - p = QtGui.QPainterPath() - p.addRect(self.boundingRect()) - #print "shape:", p.boundingRect() - return p - - diff --git a/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py b/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py deleted file mode 100644 index 251bc0c8..00000000 --- a/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py +++ /dev/null @@ -1,110 +0,0 @@ -from ..Qt import QtGui, QtCore -from ..Point import Point - - -class GraphicsWidgetAnchor(object): - """ - Class used to allow GraphicsWidgets to anchor to a specific position on their - parent. The item will be automatically repositioned if the parent is resized. - This is used, for example, to anchor a LegendItem to a corner of its parent - PlotItem. - - """ - - def __init__(self): - self.__parent = None - self.__parentAnchor = None - self.__itemAnchor = None - self.__offset = (0,0) - if hasattr(self, 'geometryChanged'): - self.geometryChanged.connect(self.__geometryChanged) - - def anchor(self, itemPos, parentPos, offset=(0,0)): - """ - Anchors the item at its local itemPos to the item's parent at parentPos. - Both positions are expressed in values relative to the size of the item or parent; - a value of 0 indicates left or top edge, while 1 indicates right or bottom edge. - - Optionally, offset may be specified to introduce an absolute offset. - - Example: anchor a box such that its upper-right corner is fixed 10px left - and 10px down from its parent's upper-right corner:: - - box.anchor(itemPos=(1,0), parentPos=(1,0), offset=(-10,10)) - """ - parent = self.parentItem() - if parent is None: - raise Exception("Cannot anchor; parent is not set.") - - if self.__parent is not parent: - if self.__parent is not None: - self.__parent.geometryChanged.disconnect(self.__geometryChanged) - - self.__parent = parent - parent.geometryChanged.connect(self.__geometryChanged) - - self.__itemAnchor = itemPos - self.__parentAnchor = parentPos - self.__offset = offset - self.__geometryChanged() - - - def autoAnchor(self, pos, relative=True): - """ - Set the position of this item relative to its parent by automatically - choosing appropriate anchor settings. - - If relative is True, one corner of the item will be anchored to - the appropriate location on the parent with no offset. The anchored - corner will be whichever is closest to the parent's boundary. - - If relative is False, one corner of the item will be anchored to the same - corner of the parent, with an absolute offset to achieve the correct - position. - """ - pos = Point(pos) - br = self.mapRectToParent(self.boundingRect()).translated(pos - self.pos()) - pbr = self.parentItem().boundingRect() - anchorPos = [0,0] - parentPos = Point() - itemPos = Point() - if abs(br.left() - pbr.left()) < abs(br.right() - pbr.right()): - anchorPos[0] = 0 - parentPos[0] = pbr.left() - itemPos[0] = br.left() - else: - anchorPos[0] = 1 - parentPos[0] = pbr.right() - itemPos[0] = br.right() - - if abs(br.top() - pbr.top()) < abs(br.bottom() - pbr.bottom()): - anchorPos[1] = 0 - parentPos[1] = pbr.top() - itemPos[1] = br.top() - else: - anchorPos[1] = 1 - parentPos[1] = pbr.bottom() - itemPos[1] = br.bottom() - - if relative: - relPos = [(itemPos[0]-pbr.left()) / pbr.width(), (itemPos[1]-pbr.top()) / pbr.height()] - self.anchor(anchorPos, relPos) - else: - offset = itemPos - parentPos - self.anchor(anchorPos, anchorPos, offset) - - def __geometryChanged(self): - if self.__parent is None: - return - if self.__itemAnchor is None: - return - - o = self.mapToParent(Point(0,0)) - a = self.boundingRect().bottomRight() * Point(self.__itemAnchor) - a = self.mapToParent(a) - p = self.__parent.boundingRect().bottomRight() * Point(self.__parentAnchor) - off = Point(self.__offset) - pos = p + (o-a) + off - self.setPos(pos) - - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/GridItem.py b/pyqtgraph/graphicsItems/GridItem.py deleted file mode 100644 index 29b0aa2c..00000000 --- a/pyqtgraph/graphicsItems/GridItem.py +++ /dev/null @@ -1,120 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from .UIGraphicsItem import * -import numpy as np -from pyqtgraph.Point import Point -import pyqtgraph.functions as fn - -__all__ = ['GridItem'] -class GridItem(UIGraphicsItem): - """ - **Bases:** :class:`UIGraphicsItem ` - - Displays a rectangular grid of lines indicating major divisions within a coordinate system. - Automatically determines what divisions to use. - """ - - def __init__(self): - UIGraphicsItem.__init__(self) - #QtGui.QGraphicsItem.__init__(self, *args) - #self.setFlag(QtGui.QGraphicsItem.ItemClipsToShape) - #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - - self.picture = None - - - def viewRangeChanged(self): - UIGraphicsItem.viewRangeChanged(self) - self.picture = None - #UIGraphicsItem.viewRangeChanged(self) - #self.update() - - def paint(self, p, opt, widget): - #p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) - #p.drawRect(self.boundingRect()) - #UIGraphicsItem.paint(self, p, opt, widget) - ### draw picture - if self.picture is None: - #print "no pic, draw.." - self.generatePicture() - p.drawPicture(QtCore.QPointF(0, 0), self.picture) - #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) - #p.drawLine(0, -100, 0, 100) - #p.drawLine(-100, 0, 100, 0) - #print "drawing Grid." - - - def generatePicture(self): - self.picture = QtGui.QPicture() - p = QtGui.QPainter() - p.begin(self.picture) - - dt = fn.invertQTransform(self.viewTransform()) - vr = self.getViewWidget().rect() - unit = self.pixelWidth(), self.pixelHeight() - dim = [vr.width(), vr.height()] - lvr = self.boundingRect() - ul = np.array([lvr.left(), lvr.top()]) - br = np.array([lvr.right(), lvr.bottom()]) - - texts = [] - - if ul[1] > br[1]: - x = ul[1] - ul[1] = br[1] - br[1] = x - for i in [2,1,0]: ## Draw three different scales of grid - dist = br-ul - nlTarget = 10.**i - d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5) - ul1 = np.floor(ul / d) * d - br1 = np.ceil(br / d) * d - dist = br1-ul1 - nl = (dist / d) + 0.5 - #print "level", i - #print " dim", dim - #print " dist", dist - #print " d", d - #print " nl", nl - for ax in range(0,2): ## Draw grid for both axes - ppl = dim[ax] / nl[ax] - c = np.clip(3.*(ppl-3), 0., 30.) - linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c)) - textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2)) - #linePen.setCosmetic(True) - #linePen.setWidth(1) - bx = (ax+1) % 2 - for x in range(0, int(nl[ax])): - linePen.setCosmetic(False) - if ax == 0: - linePen.setWidthF(self.pixelWidth()) - #print "ax 0 height", self.pixelHeight() - else: - linePen.setWidthF(self.pixelHeight()) - #print "ax 1 width", self.pixelWidth() - p.setPen(linePen) - p1 = np.array([0.,0.]) - p2 = np.array([0.,0.]) - p1[ax] = ul1[ax] + x * d[ax] - p2[ax] = p1[ax] - p1[bx] = ul[bx] - p2[bx] = br[bx] - ## don't draw lines that are out of bounds. - if p1[ax] < min(ul[ax], br[ax]) or p1[ax] > max(ul[ax], br[ax]): - continue - p.drawLine(QtCore.QPointF(p1[0], p1[1]), QtCore.QPointF(p2[0], p2[1])) - if i < 2: - p.setPen(textPen) - if ax == 0: - x = p1[0] + unit[0] - y = ul[1] + unit[1] * 8. - else: - x = ul[0] + unit[0]*3 - y = p1[1] + unit[1] - texts.append((QtCore.QPointF(x, y), "%g"%p1[ax])) - tr = self.deviceTransform() - #tr.scale(1.5, 1.5) - p.setWorldTransform(fn.invertQTransform(tr)) - for t in texts: - x = tr.map(t[0]) + Point(0.5, 0.5) - p.drawText(x, t[1]) - p.end() diff --git a/pyqtgraph/graphicsItems/HistogramLUTItem.py b/pyqtgraph/graphicsItems/HistogramLUTItem.py deleted file mode 100644 index 5a3b63d6..00000000 --- a/pyqtgraph/graphicsItems/HistogramLUTItem.py +++ /dev/null @@ -1,205 +0,0 @@ -""" -GraphicsWidget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. -""" - - -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph.functions as fn -from .GraphicsWidget import GraphicsWidget -from .ViewBox import * -from .GradientEditorItem import * -from .LinearRegionItem import * -from .PlotDataItem import * -from .AxisItem import * -from .GridItem import * -from pyqtgraph.Point import Point -import pyqtgraph.functions as fn -import numpy as np -import pyqtgraph.debug as debug - - -__all__ = ['HistogramLUTItem'] - - -class HistogramLUTItem(GraphicsWidget): - """ - This is a graphicsWidget which provides controls for adjusting the display of an image. - Includes: - - - Image histogram - - Movable region over histogram to select black/white levels - - Gradient editor to define color lookup table for single-channel images - """ - - sigLookupTableChanged = QtCore.Signal(object) - sigLevelsChanged = QtCore.Signal(object) - sigLevelChangeFinished = QtCore.Signal(object) - - def __init__(self, image=None, fillHistogram=True): - """ - If *image* (ImageItem) is provided, then the control will be automatically linked to the image and changes to the control will be immediately reflected in the image's appearance. - By default, the histogram is rendered with a fill. For performance, set *fillHistogram* = False. - """ - GraphicsWidget.__init__(self) - self.lut = None - self.imageItem = None - - self.layout = QtGui.QGraphicsGridLayout() - self.setLayout(self.layout) - self.layout.setContentsMargins(1,1,1,1) - self.layout.setSpacing(0) - self.vb = ViewBox() - self.vb.setMaximumWidth(152) - self.vb.setMinimumWidth(45) - self.vb.setMouseEnabled(x=False, y=True) - self.gradient = GradientEditorItem() - self.gradient.setOrientation('right') - self.gradient.loadPreset('grey') - self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal) - self.region.setZValue(1000) - self.vb.addItem(self.region) - self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, showValues=False) - self.layout.addItem(self.axis, 0, 0) - self.layout.addItem(self.vb, 0, 1) - self.layout.addItem(self.gradient, 0, 2) - self.range = None - self.gradient.setFlag(self.gradient.ItemStacksBehindParent) - self.vb.setFlag(self.gradient.ItemStacksBehindParent) - - #self.grid = GridItem() - #self.vb.addItem(self.grid) - - self.gradient.sigGradientChanged.connect(self.gradientChanged) - self.region.sigRegionChanged.connect(self.regionChanging) - self.region.sigRegionChangeFinished.connect(self.regionChanged) - self.vb.sigRangeChanged.connect(self.viewRangeChanged) - self.plot = PlotDataItem() - self.plot.rotate(90) - self.fillHistogram(fillHistogram) - - self.vb.addItem(self.plot) - self.autoHistogramRange() - - if image is not None: - self.setImageItem(image) - #self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) - - def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)): - if fill: - self.plot.setFillLevel(level) - self.plot.setFillBrush(color) - else: - self.plot.setFillLevel(None) - - #def sizeHint(self, *args): - #return QtCore.QSizeF(115, 200) - - def paint(self, p, *args): - pen = self.region.lines[0].pen - rgn = self.getLevels() - p1 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[0])) - p2 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[1])) - gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect()) - for pen in [fn.mkPen('k', width=3), pen]: - p.setPen(pen) - p.drawLine(p1, gradRect.bottomLeft()) - p.drawLine(p2, gradRect.topLeft()) - p.drawLine(gradRect.topLeft(), gradRect.topRight()) - p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight()) - #p.drawRect(self.boundingRect()) - - - def setHistogramRange(self, mn, mx, padding=0.1): - """Set the Y range on the histogram plot. This disables auto-scaling.""" - self.vb.enableAutoRange(self.vb.YAxis, False) - self.vb.setYRange(mn, mx, padding) - - #d = mx-mn - #mn -= d*padding - #mx += d*padding - #self.range = [mn,mx] - #self.updateRange() - #self.vb.setMouseEnabled(False, True) - #self.region.setBounds([mn,mx]) - - def autoHistogramRange(self): - """Enable auto-scaling on the histogram plot.""" - self.vb.enableAutoRange(self.vb.XYAxes) - #self.range = None - #self.updateRange() - #self.vb.setMouseEnabled(False, False) - - #def updateRange(self): - #self.vb.autoRange() - #if self.range is not None: - #self.vb.setYRange(*self.range) - #vr = self.vb.viewRect() - - #self.region.setBounds([vr.top(), vr.bottom()]) - - def setImageItem(self, img): - self.imageItem = img - img.sigImageChanged.connect(self.imageChanged) - img.setLookupTable(self.getLookupTable) ## send function pointer, not the result - #self.gradientChanged() - self.regionChanged() - self.imageChanged(autoLevel=True) - #self.vb.autoRange() - - def viewRangeChanged(self): - self.update() - - def gradientChanged(self): - if self.imageItem is not None: - if self.gradient.isLookupTrivial(): - self.imageItem.setLookupTable(None) #lambda x: x.astype(np.uint8)) - else: - self.imageItem.setLookupTable(self.getLookupTable) ## send function pointer, not the result - - self.lut = None - #if self.imageItem is not None: - #self.imageItem.setLookupTable(self.gradient.getLookupTable(512)) - self.sigLookupTableChanged.emit(self) - - def getLookupTable(self, img=None, n=None, alpha=None): - if n is None: - if img.dtype == np.uint8: - n = 256 - else: - n = 512 - if self.lut is None: - self.lut = self.gradient.getLookupTable(n, alpha=alpha) - return self.lut - - def regionChanged(self): - #if self.imageItem is not None: - #self.imageItem.setLevels(self.region.getRegion()) - self.sigLevelChangeFinished.emit(self) - #self.update() - - def regionChanging(self): - if self.imageItem is not None: - self.imageItem.setLevels(self.region.getRegion()) - self.sigLevelsChanged.emit(self) - self.update() - - def imageChanged(self, autoLevel=False, autoRange=False): - prof = debug.Profiler('HistogramLUTItem.imageChanged', disabled=True) - h = self.imageItem.getHistogram() - prof.mark('get histogram') - if h[0] is None: - return - self.plot.setData(*h) - prof.mark('set plot') - if autoLevel: - mn = h[0][0] - mx = h[0][-1] - self.region.setRegion([mn, mx]) - prof.mark('set region') - prof.finish() - - def getLevels(self): - return self.region.getRegion() - - def setLevels(self, mn, mx): - self.region.setRegion([mn, mx]) diff --git a/pyqtgraph/graphicsItems/ImageItem.py b/pyqtgraph/graphicsItems/ImageItem.py deleted file mode 100644 index 530db7fb..00000000 --- a/pyqtgraph/graphicsItems/ImageItem.py +++ /dev/null @@ -1,453 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -import numpy as np -import collections -import pyqtgraph.functions as fn -import pyqtgraph.debug as debug -from .GraphicsObject import GraphicsObject - -__all__ = ['ImageItem'] -class ImageItem(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - - GraphicsObject displaying an image. Optimized for rapid update (ie video display). - This item displays either a 2D numpy array (height, width) or - a 3D array (height, width, RGBa). This array is optionally scaled (see - :func:`setLevels `) and/or colored - with a lookup table (see :func:`setLookupTable `) - before being displayed. - - ImageItem is frequently used in conjunction with - :class:`HistogramLUTItem ` or - :class:`HistogramLUTWidget ` to provide a GUI - for controlling the levels and lookup table used to display the image. - """ - - - sigImageChanged = QtCore.Signal() - sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu - - def __init__(self, image=None, **kargs): - """ - See :func:`setImage ` for all allowed initialization arguments. - """ - GraphicsObject.__init__(self) - #self.pixmapItem = QtGui.QGraphicsPixmapItem(self) - #self.qimage = QtGui.QImage() - #self._pixmap = None - self.menu = None - self.image = None ## original image data - self.qimage = None ## rendered image for display - #self.clipMask = None - - self.paintMode = None - - self.levels = None ## [min, max] or [[redMin, redMax], ...] - self.lut = None - - #self.clipLevel = None - self.drawKernel = None - self.border = None - self.removable = False - - if image is not None: - self.setImage(image, **kargs) - else: - self.setOpts(**kargs) - - def setCompositionMode(self, mode): - """Change the composition mode of the item (see QPainter::CompositionMode - in the Qt documentation). This is useful when overlaying multiple ImageItems. - - ============================================ ============================================================ - **Most common arguments:** - QtGui.QPainter.CompositionMode_SourceOver Default; image replaces the background if it - is opaque. Otherwise, it uses the alpha channel to blend - the image with the background. - QtGui.QPainter.CompositionMode_Overlay The image color is mixed with the background color to - reflect the lightness or darkness of the background. - QtGui.QPainter.CompositionMode_Plus Both the alpha and color of the image and background pixels - are added together. - QtGui.QPainter.CompositionMode_Multiply The output is the image color multiplied by the background. - ============================================ ============================================================ - """ - self.paintMode = mode - self.update() - - ## use setOpacity instead. - #def setAlpha(self, alpha): - #self.setOpacity(alpha) - #self.updateImage() - - def setBorder(self, b): - self.border = fn.mkPen(b) - self.update() - - def width(self): - if self.image is None: - return None - return self.image.shape[0] - - def height(self): - if self.image is None: - return None - return self.image.shape[1] - - def boundingRect(self): - if self.image is None: - return QtCore.QRectF(0., 0., 0., 0.) - return QtCore.QRectF(0., 0., float(self.width()), float(self.height())) - - #def setClipLevel(self, level=None): - #self.clipLevel = level - #self.updateImage() - - #def paint(self, p, opt, widget): - #pass - #if self.pixmap is not None: - #p.drawPixmap(0, 0, self.pixmap) - #print "paint" - - def setLevels(self, levels, update=True): - """ - Set image scaling levels. Can be one of: - - * [blackLevel, whiteLevel] - * [[minRed, maxRed], [minGreen, maxGreen], [minBlue, maxBlue]] - - Only the first format is compatible with lookup tables. See :func:`makeARGB ` - for more details on how levels are applied. - """ - self.levels = levels - if update: - self.updateImage() - - def getLevels(self): - return self.levels - #return self.whiteLevel, self.blackLevel - - def setLookupTable(self, lut, update=True): - """ - Set the lookup table (numpy array) to use for this image. (see - :func:`makeARGB ` for more information on how this is used). - Optionally, lut can be a callable that accepts the current image as an - argument and returns the lookup table to use. - - Ordinarily, this table is supplied by a :class:`HistogramLUTItem ` - or :class:`GradientEditorItem `. - """ - self.lut = lut - if update: - self.updateImage() - - def setOpts(self, update=True, **kargs): - if 'lut' in kargs: - self.setLookupTable(kargs['lut'], update=update) - if 'levels' in kargs: - self.setLevels(kargs['levels'], update=update) - #if 'clipLevel' in kargs: - #self.setClipLevel(kargs['clipLevel']) - if 'opacity' in kargs: - self.setOpacity(kargs['opacity']) - if 'compositionMode' in kargs: - self.setCompositionMode(kargs['compositionMode']) - if 'border' in kargs: - self.setBorder(kargs['border']) - if 'removable' in kargs: - self.removable = kargs['removable'] - self.menu = None - - def setRect(self, rect): - """Scale and translate the image to fit within rect (must be a QRect or QRectF).""" - self.resetTransform() - self.translate(rect.left(), rect.top()) - self.scale(rect.width() / self.width(), rect.height() / self.height()) - - def setImage(self, image=None, autoLevels=None, **kargs): - """ - Update the image displayed by this item. For more information on how the image - is processed before displaying, see :func:`makeARGB ` - - ================= ========================================================================= - **Arguments:** - image (numpy array) Specifies the image data. May be 2D (width, height) or - 3D (width, height, RGBa). The array dtype must be integer or floating - point of any bit depth. For 3D arrays, the third dimension must - be of length 3 (RGB) or 4 (RGBA). - autoLevels (bool) If True, this forces the image to automatically select - levels based on the maximum and minimum values in the data. - By default, this argument is true unless the levels argument is - given. - lut (numpy array) The color lookup table to use when displaying the image. - See :func:`setLookupTable `. - levels (min, max) The minimum and maximum values to use when rescaling the image - data. By default, this will be set to the minimum and maximum values - in the image. If the image array has dtype uint8, no rescaling is necessary. - opacity (float 0.0-1.0) - compositionMode see :func:`setCompositionMode ` - border Sets the pen used when drawing the image border. Default is None. - ================= ========================================================================= - """ - prof = debug.Profiler('ImageItem.setImage', disabled=True) - - gotNewData = False - if image is None: - if self.image is None: - return - else: - gotNewData = True - shapeChanged = (self.image is None or image.shape != self.image.shape) - self.image = image.view(np.ndarray) - if shapeChanged: - self.prepareGeometryChange() - self.informViewBoundsChanged() - - prof.mark('1') - - if autoLevels is None: - if 'levels' in kargs: - autoLevels = False - else: - autoLevels = True - if autoLevels: - img = self.image - while img.size > 2**16: - img = img[::2, ::2] - mn, mx = img.min(), img.max() - if mn == mx: - mn = 0 - mx = 255 - kargs['levels'] = [mn,mx] - prof.mark('2') - - self.setOpts(update=False, **kargs) - prof.mark('3') - - self.qimage = None - self.update() - prof.mark('4') - - if gotNewData: - self.sigImageChanged.emit() - - - prof.finish() - - - - def updateImage(self, *args, **kargs): - ## used for re-rendering qimage from self.image. - - ## can we make any assumptions here that speed things up? - ## dtype, range, size are all the same? - defaults = { - 'autoLevels': False, - } - defaults.update(kargs) - return self.setImage(*args, **defaults) - - - - - def render(self): - prof = debug.Profiler('ImageItem.render', disabled=True) - if self.image is None or self.image.size == 0: - return - if isinstance(self.lut, collections.Callable): - lut = self.lut(self.image) - else: - lut = self.lut - #print lut.shape - #print self.lut - - argb, alpha = fn.makeARGB(self.image, lut=lut, levels=self.levels) - self.qimage = fn.makeQImage(argb, alpha) - prof.finish() - - - def paint(self, p, *args): - prof = debug.Profiler('ImageItem.paint', disabled=True) - if self.image is None: - return - if self.qimage is None: - self.render() - if self.qimage is None: - return - prof.mark('render QImage') - if self.paintMode is not None: - p.setCompositionMode(self.paintMode) - prof.mark('set comp mode') - - p.drawImage(QtCore.QPointF(0,0), self.qimage) - prof.mark('p.drawImage') - if self.border is not None: - p.setPen(self.border) - p.drawRect(self.boundingRect()) - prof.finish() - - def save(self, fileName, *args): - """Save this image to file. Note that this saves the visible image (after scale/color changes), not the original data.""" - if self.qimage is None: - self.render() - self.qimage.save(fileName, *args) - - def getHistogram(self, bins=500, step=3): - """Returns x and y arrays containing the histogram values for the current image. - The step argument causes pixels to be skipped when computing the histogram to save time. - This method is also used when automatically computing levels. - """ - if self.image is None: - return None,None - stepData = self.image[::step, ::step] - hist = np.histogram(stepData, bins=bins) - return hist[1][:-1], hist[0] - - def setPxMode(self, b): - """ - Set whether the item ignores transformations and draws directly to screen pixels. - If True, the item will not inherit any scale or rotation transformations from its - parent items, but its position will be transformed as usual. - (see GraphicsItem::ItemIgnoresTransformations in the Qt documentation) - """ - self.setFlag(self.ItemIgnoresTransformations, b) - - def setScaledMode(self): - self.setPxMode(False) - - def getPixmap(self): - if self.qimage is None: - self.render() - if self.qimage is None: - return None - return QtGui.QPixmap.fromImage(self.qimage) - - def pixelSize(self): - """return scene-size of a single pixel in the image""" - br = self.sceneBoundingRect() - if self.image is None: - return 1,1 - return br.width()/self.width(), br.height()/self.height() - - #def mousePressEvent(self, ev): - #if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: - #self.drawAt(ev.pos(), ev) - #ev.accept() - #else: - #ev.ignore() - - #def mouseMoveEvent(self, ev): - ##print "mouse move", ev.pos() - #if self.drawKernel is not None: - #self.drawAt(ev.pos(), ev) - - #def mouseReleaseEvent(self, ev): - #pass - - def mouseDragEvent(self, ev): - if ev.button() != QtCore.Qt.LeftButton: - ev.ignore() - return - elif self.drawKernel is not None: - ev.accept() - self.drawAt(ev.pos(), ev) - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton: - if self.raiseContextMenu(ev): - ev.accept() - if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: - self.drawAt(ev.pos(), ev) - - def raiseContextMenu(self, ev): - menu = self.getMenu() - if menu is None: - return False - menu = self.scene().addParentContextMenus(self, menu, ev) - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - return True - - def getMenu(self): - if self.menu is None: - if not self.removable: - return None - self.menu = QtGui.QMenu() - self.menu.setTitle("Image") - remAct = QtGui.QAction("Remove image", self.menu) - remAct.triggered.connect(self.removeClicked) - self.menu.addAction(remAct) - self.menu.remAct = remAct - return self.menu - - - def hoverEvent(self, ev): - if not ev.isExit() and self.drawKernel is not None and ev.acceptDrags(QtCore.Qt.LeftButton): - ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. - ev.acceptClicks(QtCore.Qt.RightButton) - #self.box.setBrush(fn.mkBrush('w')) - elif not ev.isExit() and self.removable: - ev.acceptClicks(QtCore.Qt.RightButton) ## accept context menu clicks - #else: - #self.box.setBrush(self.brush) - #self.update() - - - - def tabletEvent(self, ev): - print(ev.device()) - print(ev.pointerType()) - print(ev.pressure()) - - def drawAt(self, pos, ev=None): - pos = [int(pos.x()), int(pos.y())] - dk = self.drawKernel - kc = self.drawKernelCenter - sx = [0,dk.shape[0]] - sy = [0,dk.shape[1]] - tx = [pos[0] - kc[0], pos[0] - kc[0]+ dk.shape[0]] - ty = [pos[1] - kc[1], pos[1] - kc[1]+ dk.shape[1]] - - for i in [0,1]: - dx1 = -min(0, tx[i]) - dx2 = min(0, self.image.shape[0]-tx[i]) - tx[i] += dx1+dx2 - sx[i] += dx1+dx2 - - dy1 = -min(0, ty[i]) - dy2 = min(0, self.image.shape[1]-ty[i]) - ty[i] += dy1+dy2 - sy[i] += dy1+dy2 - - ts = (slice(tx[0],tx[1]), slice(ty[0],ty[1])) - ss = (slice(sx[0],sx[1]), slice(sy[0],sy[1])) - mask = self.drawMask - src = dk - - if isinstance(self.drawMode, collections.Callable): - self.drawMode(dk, self.image, mask, ss, ts, ev) - else: - src = src[ss] - if self.drawMode == 'set': - if mask is not None: - mask = mask[ss] - self.image[ts] = self.image[ts] * (1-mask) + src * mask - else: - self.image[ts] = src - elif self.drawMode == 'add': - self.image[ts] += src - else: - raise Exception("Unknown draw mode '%s'" % self.drawMode) - self.updateImage() - - def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'): - self.drawKernel = kernel - self.drawKernelCenter = center - self.drawMode = mode - self.drawMask = mask - - def removeClicked(self): - ## Send remove event only after we have exited the menu event handler - self.removeTimer = QtCore.QTimer() - self.removeTimer.timeout.connect(lambda: self.sigRemoveRequested.emit(self)) - self.removeTimer.start(0) - diff --git a/pyqtgraph/graphicsItems/InfiniteLine.py b/pyqtgraph/graphicsItems/InfiniteLine.py deleted file mode 100644 index 4f0df863..00000000 --- a/pyqtgraph/graphicsItems/InfiniteLine.py +++ /dev/null @@ -1,277 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.Point import Point -from .GraphicsObject import GraphicsObject -import pyqtgraph.functions as fn -import numpy as np -import weakref - - -__all__ = ['InfiniteLine'] -class InfiniteLine(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - - Displays a line of infinite length. - This line may be dragged to indicate a position in data coordinates. - - =============================== =================================================== - **Signals** - sigDragged(self) - sigPositionChangeFinished(self) - sigPositionChanged(self) - =============================== =================================================== - """ - - sigDragged = QtCore.Signal(object) - sigPositionChangeFinished = QtCore.Signal(object) - sigPositionChanged = QtCore.Signal(object) - - def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None): - """ - ============= ================================================================== - **Arguments** - pos Position of the line. This can be a QPointF or a single value for - vertical/horizontal lines. - angle Angle of line in degrees. 0 is horizontal, 90 is vertical. - pen Pen to use when drawing line. Can be any arguments that are valid - for :func:`mkPen `. Default pen is transparent - yellow. - movable If True, the line can be dragged to a new position by the user. - bounds Optional [min, max] bounding values. Bounds are only valid if the - line is vertical or horizontal. - ============= ================================================================== - """ - - GraphicsObject.__init__(self) - - if bounds is None: ## allowed value boundaries for orthogonal lines - self.maxRange = [None, None] - else: - self.maxRange = bounds - self.moving = False - self.setMovable(movable) - self.mouseHovering = False - self.p = [0, 0] - self.setAngle(angle) - if pos is None: - pos = Point(0,0) - self.setPos(pos) - - if pen is None: - pen = (200, 200, 100) - self.setPen(pen) - self.currentPen = self.pen - #self.setFlag(self.ItemSendsScenePositionChanges) - - def setMovable(self, m): - """Set whether the line is movable by the user.""" - self.movable = m - self.setAcceptHoverEvents(m) - - def setBounds(self, bounds): - """Set the (minimum, maximum) allowable values when dragging.""" - self.maxRange = bounds - self.setValue(self.value()) - - def setPen(self, pen): - """Set the pen for drawing the line. Allowable arguments are any that are valid - for :func:`mkPen `.""" - self.pen = fn.mkPen(pen) - self.currentPen = self.pen - self.update() - - def setAngle(self, angle): - """ - Takes angle argument in degrees. - 0 is horizontal; 90 is vertical. - - Note that the use of value() and setValue() changes if the line is - not vertical or horizontal. - """ - self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135 - self.resetTransform() - self.rotate(self.angle) - self.update() - - def setPos(self, pos): - - if type(pos) in [list, tuple]: - newPos = pos - elif isinstance(pos, QtCore.QPointF): - newPos = [pos.x(), pos.y()] - else: - if self.angle == 90: - newPos = [pos, 0] - elif self.angle == 0: - newPos = [0, pos] - else: - raise Exception("Must specify 2D coordinate for non-orthogonal lines.") - - ## check bounds (only works for orthogonal lines) - if self.angle == 90: - if self.maxRange[0] is not None: - newPos[0] = max(newPos[0], self.maxRange[0]) - if self.maxRange[1] is not None: - newPos[0] = min(newPos[0], self.maxRange[1]) - elif self.angle == 0: - if self.maxRange[0] is not None: - newPos[1] = max(newPos[1], self.maxRange[0]) - if self.maxRange[1] is not None: - newPos[1] = min(newPos[1], self.maxRange[1]) - - if self.p != newPos: - self.p = newPos - GraphicsObject.setPos(self, Point(self.p)) - self.update() - self.sigPositionChanged.emit(self) - - def getXPos(self): - return self.p[0] - - def getYPos(self): - return self.p[1] - - def getPos(self): - return self.p - - def value(self): - """Return the value of the line. Will be a single number for horizontal and - vertical lines, and a list of [x,y] values for diagonal lines.""" - if self.angle%180 == 0: - return self.getYPos() - elif self.angle%180 == 90: - return self.getXPos() - else: - return self.getPos() - - def setValue(self, v): - """Set the position of the line. If line is horizontal or vertical, v can be - a single value. Otherwise, a 2D coordinate must be specified (list, tuple and - QPointF are all acceptable).""" - self.setPos(v) - - ## broken in 4.7 - #def itemChange(self, change, val): - #if change in [self.ItemScenePositionHasChanged, self.ItemSceneHasChanged]: - #self.updateLine() - #print "update", change - #print self.getBoundingParents() - #else: - #print "ignore", change - #return GraphicsObject.itemChange(self, change, val) - - def boundingRect(self): - #br = UIGraphicsItem.boundingRect(self) - br = self.viewRect() - ## add a 4-pixel radius around the line for mouse interaction. - - px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line - if px is None: - px = 0 - br.setBottom(-px*4) - br.setTop(px*4) - return br.normalized() - - def paint(self, p, *args): - br = self.boundingRect() - p.setPen(self.currentPen) - p.drawLine(Point(br.right(), 0), Point(br.left(), 0)) - #p.drawRect(self.boundingRect()) - - def dataBounds(self, axis, frac=1.0, orthoRange=None): - if axis == 0: - return None ## x axis should never be auto-scaled - else: - return (0,0) - - #def mousePressEvent(self, ev): - #if self.movable and ev.button() == QtCore.Qt.LeftButton: - #ev.accept() - #self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) - #else: - #ev.ignore() - - #def mouseMoveEvent(self, ev): - #self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) - ##self.emit(QtCore.SIGNAL('dragged'), self) - #self.sigDragged.emit(self) - #self.hasMoved = True - - #def mouseReleaseEvent(self, ev): - #if self.hasMoved and ev.button() == QtCore.Qt.LeftButton: - #self.hasMoved = False - ##self.emit(QtCore.SIGNAL('positionChangeFinished'), self) - #self.sigPositionChangeFinished.emit(self) - - def mouseDragEvent(self, ev): - if self.movable and ev.button() == QtCore.Qt.LeftButton: - if ev.isStart(): - self.moving = True - self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) - self.startPosition = self.pos() - ev.accept() - - if not self.moving: - return - - #pressDelta = self.mapToParent(ev.buttonDownPos()) - Point(self.p) - self.setPos(self.cursorOffset + self.mapToParent(ev.pos())) - self.sigDragged.emit(self) - if ev.isFinish(): - self.moving = False - self.sigPositionChangeFinished.emit(self) - #else: - #print ev - - - def mouseClickEvent(self, ev): - if self.moving and ev.button() == QtCore.Qt.RightButton: - ev.accept() - self.setPos(self.startPosition) - self.moving = False - self.sigDragged.emit(self) - self.sigPositionChangeFinished.emit(self) - - def hoverEvent(self, ev): - if (not ev.isExit()) and self.movable and ev.acceptDrags(QtCore.Qt.LeftButton): - self.setMouseHover(True) - else: - self.setMouseHover(False) - - def setMouseHover(self, hover): - ## Inform the item that the mouse is(not) hovering over it - if self.mouseHovering == hover: - return - self.mouseHovering = hover - if hover: - self.currentPen = fn.mkPen(255, 0,0) - else: - self.currentPen = self.pen - self.update() - - #def hoverEnterEvent(self, ev): - #print "line hover enter" - #ev.ignore() - #self.updateHoverPen() - - #def hoverMoveEvent(self, ev): - #print "line hover move" - #ev.ignore() - #self.updateHoverPen() - - #def hoverLeaveEvent(self, ev): - #print "line hover leave" - #ev.ignore() - #self.updateHoverPen(False) - - #def updateHoverPen(self, hover=None): - #if hover is None: - #scene = self.scene() - #hover = scene.claimEvent(self, QtCore.Qt.LeftButton, scene.Drag) - - #if hover: - #self.currentPen = fn.mkPen(255, 0,0) - #else: - #self.currentPen = self.pen - #self.update() - diff --git a/pyqtgraph/graphicsItems/IsocurveItem.py b/pyqtgraph/graphicsItems/IsocurveItem.py deleted file mode 100644 index 01ef57b6..00000000 --- a/pyqtgraph/graphicsItems/IsocurveItem.py +++ /dev/null @@ -1,121 +0,0 @@ - - -from .GraphicsObject import * -import pyqtgraph.functions as fn -from pyqtgraph.Qt import QtGui, QtCore - - -class IsocurveItem(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - - Item displaying an isocurve of a 2D array.To align this item correctly with an - ImageItem,call isocurve.setParentItem(image) - """ - - - def __init__(self, data=None, level=0, pen='w'): - """ - Create a new isocurve item. - - ============= =============================================================== - **Arguments** - data A 2-dimensional ndarray. Can be initialized as None, and set - later using :func:`setData ` - level The cutoff value at which to draw the isocurve. - pen The color of the curve item. Can be anything valid for - :func:`mkPen ` - ============= =============================================================== - """ - GraphicsObject.__init__(self) - - self.level = level - self.data = None - self.path = None - self.setPen(pen) - self.setData(data, level) - - - - #if data is not None and level is not None: - #self.updateLines(data, level) - - - def setData(self, data, level=None): - """ - Set the data/image to draw isocurves for. - - ============= ======================================================================== - **Arguments** - data A 2-dimensional ndarray. - level The cutoff value at which to draw the curve. If level is not specified, - the previously set level is used. - ============= ======================================================================== - """ - if level is None: - level = self.level - self.level = level - self.data = data - self.path = None - self.prepareGeometryChange() - self.update() - - - def setLevel(self, level): - """Set the level at which the isocurve is drawn.""" - self.level = level - self.path = None - self.update() - - - def setPen(self, *args, **kwargs): - """Set the pen used to draw the isocurve. Arguments can be any that are valid - for :func:`mkPen `""" - self.pen = fn.mkPen(*args, **kwargs) - self.update() - - def setBrush(self, *args, **kwargs): - """Set the brush used to draw the isocurve. Arguments can be any that are valid - for :func:`mkBrush `""" - self.brush = fn.mkBrush(*args, **kwargs) - self.update() - - - def updateLines(self, data, level): - ##print "data:", data - ##print "level", level - #lines = fn.isocurve(data, level) - ##print len(lines) - #self.path = QtGui.QPainterPath() - #for line in lines: - #self.path.moveTo(*line[0]) - #self.path.lineTo(*line[1]) - #self.update() - self.setData(data, level) - - def boundingRect(self): - if self.data is None: - return QtCore.QRectF() - if self.path is None: - self.generatePath() - return self.path.boundingRect() - - def generatePath(self): - if self.data is None: - self.path = None - return - lines = fn.isocurve(self.data, self.level, connected=True, extendToEdge=True) - self.path = QtGui.QPainterPath() - for line in lines: - self.path.moveTo(*line[0]) - for p in line[1:]: - self.path.lineTo(*p) - - def paint(self, p, *args): - if self.data is None: - return - if self.path is None: - self.generatePath() - p.setPen(self.pen) - p.drawPath(self.path) - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/ItemGroup.py b/pyqtgraph/graphicsItems/ItemGroup.py deleted file mode 100644 index 930fdf80..00000000 --- a/pyqtgraph/graphicsItems/ItemGroup.py +++ /dev/null @@ -1,23 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from .GraphicsObject import GraphicsObject - -__all__ = ['ItemGroup'] -class ItemGroup(GraphicsObject): - """ - Replacement for QGraphicsItemGroup - """ - - def __init__(self, *args): - GraphicsObject.__init__(self, *args) - if hasattr(self, "ItemHasNoContents"): - self.setFlag(self.ItemHasNoContents) - - def boundingRect(self): - return QtCore.QRectF() - - def paint(self, *args): - pass - - def addItem(self, item): - item.setParentItem(self) - diff --git a/pyqtgraph/graphicsItems/LabelItem.py b/pyqtgraph/graphicsItems/LabelItem.py deleted file mode 100644 index 6101c4bc..00000000 --- a/pyqtgraph/graphicsItems/LabelItem.py +++ /dev/null @@ -1,142 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph.functions as fn -import pyqtgraph as pg -from .GraphicsWidget import GraphicsWidget -from .GraphicsWidgetAnchor import GraphicsWidgetAnchor - - -__all__ = ['LabelItem'] - -class LabelItem(GraphicsWidget, GraphicsWidgetAnchor): - """ - GraphicsWidget displaying text. - Used mainly as axis labels, titles, etc. - - Note: To display text inside a scaled view (ViewBox, PlotWidget, etc) use TextItem - """ - - - def __init__(self, text=' ', parent=None, angle=0, **args): - GraphicsWidget.__init__(self, parent) - GraphicsWidgetAnchor.__init__(self) - self.item = QtGui.QGraphicsTextItem(self) - self.opts = { - 'color': None, - 'justify': 'center' - } - self.opts.update(args) - self._sizeHint = {} - self.setText(text) - self.setAngle(angle) - - def setAttr(self, attr, value): - """Set default text properties. See setText() for accepted parameters.""" - self.opts[attr] = value - - def setText(self, text, **args): - """Set the text and text properties in the label. Accepts optional arguments for auto-generating - a CSS style string: - - ==================== ============================== - **Style Arguments:** - color (str) example: 'CCFF00' - size (str) example: '8pt' - bold (bool) - italic (bool) - ==================== ============================== - """ - self.text = text - opts = self.opts - for k in args: - opts[k] = args[k] - - optlist = [] - - color = self.opts['color'] - if color is None: - color = pg.getConfigOption('foreground') - color = fn.mkColor(color) - optlist.append('color: #' + fn.colorStr(color)[:6]) - if 'size' in opts: - optlist.append('font-size: ' + opts['size']) - if 'bold' in opts and opts['bold'] in [True, False]: - optlist.append('font-weight: ' + {True:'bold', False:'normal'}[opts['bold']]) - if 'italic' in opts and opts['italic'] in [True, False]: - optlist.append('font-style: ' + {True:'italic', False:'normal'}[opts['italic']]) - full = "%s" % ('; '.join(optlist), text) - #print full - self.item.setHtml(full) - self.updateMin() - self.resizeEvent(None) - self.updateGeometry() - - def resizeEvent(self, ev): - #c1 = self.boundingRect().center() - #c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos() - #dif = c1 - c2 - #self.item.moveBy(dif.x(), dif.y()) - #print c1, c2, dif, self.item.pos() - self.item.setPos(0,0) - bounds = self.itemRect() - left = self.mapFromItem(self.item, QtCore.QPointF(0,0)) - self.mapFromItem(self.item, QtCore.QPointF(1,0)) - rect = self.rect() - - if self.opts['justify'] == 'left': - if left.x() != 0: - bounds.moveLeft(rect.left()) - if left.y() < 0: - bounds.moveTop(rect.top()) - elif left.y() > 0: - bounds.moveBottom(rect.bottom()) - - elif self.opts['justify'] == 'center': - bounds.moveCenter(rect.center()) - #bounds = self.itemRect() - #self.item.setPos(self.width()/2. - bounds.width()/2., 0) - elif self.opts['justify'] == 'right': - if left.x() != 0: - bounds.moveRight(rect.right()) - if left.y() < 0: - bounds.moveBottom(rect.bottom()) - elif left.y() > 0: - bounds.moveTop(rect.top()) - #bounds = self.itemRect() - #self.item.setPos(self.width() - bounds.width(), 0) - - self.item.setPos(bounds.topLeft() - self.itemRect().topLeft()) - self.updateMin() - - def setAngle(self, angle): - self.angle = angle - self.item.resetTransform() - self.item.rotate(angle) - self.updateMin() - - - def updateMin(self): - bounds = self.itemRect() - self.setMinimumWidth(bounds.width()) - self.setMinimumHeight(bounds.height()) - - self._sizeHint = { - QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()), - QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()), - QtCore.Qt.MaximumSize: (-1, -1), #bounds.width()*2, bounds.height()*2), - QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this? - } - self.updateGeometry() - - def sizeHint(self, hint, constraint): - if hint not in self._sizeHint: - return QtCore.QSizeF(0, 0) - return QtCore.QSizeF(*self._sizeHint[hint]) - - def itemRect(self): - return self.item.mapRectToParent(self.item.boundingRect()) - - #def paint(self, p, *args): - #p.setPen(fn.mkPen('r')) - #p.drawRect(self.rect()) - #p.setPen(fn.mkPen('g')) - #p.drawRect(self.itemRect()) - diff --git a/pyqtgraph/graphicsItems/LegendItem.py b/pyqtgraph/graphicsItems/LegendItem.py deleted file mode 100644 index 69ddffea..00000000 --- a/pyqtgraph/graphicsItems/LegendItem.py +++ /dev/null @@ -1,173 +0,0 @@ -from .GraphicsWidget import GraphicsWidget -from .LabelItem import LabelItem -from ..Qt import QtGui, QtCore -from .. import functions as fn -from ..Point import Point -from .GraphicsWidgetAnchor import GraphicsWidgetAnchor -import pyqtgraph as pg -__all__ = ['LegendItem'] - -class LegendItem(GraphicsWidget, GraphicsWidgetAnchor): - """ - Displays a legend used for describing the contents of a plot. - LegendItems are most commonly created by calling PlotItem.addLegend(). - - Note that this item should not be added directly to a PlotItem. Instead, - Make it a direct descendant of the PlotItem:: - - legend.setParentItem(plotItem) - - """ - def __init__(self, size=None, offset=None): - """ - ========== =============================================================== - Arguments - size Specifies the fixed size (width, height) of the legend. If - this argument is omitted, the legend will autimatically resize - to fit its contents. - offset Specifies the offset position relative to the legend's parent. - Positive values offset from the left or top; negative values - offset from the right or bottom. If offset is None, the - legend must be anchored manually by calling anchor() or - positioned by calling setPos(). - ========== =============================================================== - - """ - - - GraphicsWidget.__init__(self) - GraphicsWidgetAnchor.__init__(self) - self.setFlag(self.ItemIgnoresTransformations) - self.layout = QtGui.QGraphicsGridLayout() - self.setLayout(self.layout) - self.items = [] - self.size = size - self.offset = offset - if size is not None: - self.setGeometry(QtCore.QRectF(0, 0, self.size[0], self.size[1])) - - def setParentItem(self, p): - ret = GraphicsWidget.setParentItem(self, p) - if self.offset is not None: - offset = Point(self.offset) - anchorx = 1 if offset[0] <= 0 else 0 - anchory = 1 if offset[1] <= 0 else 0 - anchor = (anchorx, anchory) - self.anchor(itemPos=anchor, parentPos=anchor, offset=offset) - return ret - - def addItem(self, item, name): - """ - Add a new entry to the legend. - - =========== ======================================================== - Arguments - item A PlotDataItem from which the line and point style - of the item will be determined or an instance of - ItemSample (or a subclass), allowing the item display - to be customized. - title The title to display for this item. Simple HTML allowed. - =========== ======================================================== - """ - label = LabelItem(name) - if isinstance(item, ItemSample): - sample = item - else: - sample = ItemSample(item) - row = len(self.items) - self.items.append((sample, label)) - self.layout.addItem(sample, row, 0) - self.layout.addItem(label, row, 1) - self.updateSize() - - def removeItem(self, name): - """ - Removes one item from the legend. - - =========== ======================================================== - Arguments - title The title displayed for this item. - =========== ======================================================== - """ - # Thanks, Ulrich! - # cycle for a match - for sample, label in self.items: - if label.text == name: # hit - self.items.remove( (sample, label) ) # remove from itemlist - self.layout.removeItem(sample) # remove from layout - sample.close() # remove from drawing - self.layout.removeItem(label) - label.close() - self.updateSize() # redraq box - - def updateSize(self): - if self.size is not None: - return - - height = 0 - width = 0 - #print("-------") - for sample, label in self.items: - height += max(sample.height(), label.height()) + 3 - width = max(width, sample.width()+label.width()) - #print(width, height) - #print width, height - self.setGeometry(0, 0, width+25, height) - - def boundingRect(self): - return QtCore.QRectF(0, 0, self.width(), self.height()) - - def paint(self, p, *args): - p.setPen(fn.mkPen(255,255,255,100)) - p.setBrush(fn.mkBrush(100,100,100,50)) - p.drawRect(self.boundingRect()) - - def hoverEvent(self, ev): - ev.acceptDrags(QtCore.Qt.LeftButton) - - def mouseDragEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - dpos = ev.pos() - ev.lastPos() - self.autoAnchor(self.pos() + dpos) - -class ItemSample(GraphicsWidget): - """ Class responsible for drawing a single item in a LegendItem (sans label). - - This may be subclassed to draw custom graphics in a Legend. - """ - ## Todo: make this more generic; let each item decide how it should be represented. - def __init__(self, item): - GraphicsWidget.__init__(self) - self.item = item - - def boundingRect(self): - return QtCore.QRectF(0, 0, 20, 20) - - def paint(self, p, *args): - #p.setRenderHint(p.Antialiasing) # only if the data is antialiased. - opts = self.item.opts - - if opts.get('fillLevel',None) is not None and opts.get('fillBrush',None) is not None: - p.setBrush(fn.mkBrush(opts['fillBrush'])) - p.setPen(fn.mkPen(None)) - p.drawPolygon(QtGui.QPolygonF([QtCore.QPointF(2,18), QtCore.QPointF(18,2), QtCore.QPointF(18,18)])) - - if not isinstance(self.item, pg.ScatterPlotItem): - p.setPen(fn.mkPen(opts['pen'])) - p.drawLine(2, 18, 18, 2) - - symbol = opts.get('symbol', None) - if symbol is not None: - if isinstance(self.item, pg.PlotDataItem): - opts = self.item.scatter.opts - - pen = pg.mkPen(opts['pen']) - brush = pg.mkBrush(opts['brush']) - size = opts['size'] - - p.translate(10,10) - path = pg.graphicsItems.ScatterPlotItem.drawSymbol(p, symbol, size, pen, brush) - - - - diff --git a/pyqtgraph/graphicsItems/LinearRegionItem.py b/pyqtgraph/graphicsItems/LinearRegionItem.py deleted file mode 100644 index a35e8efc..00000000 --- a/pyqtgraph/graphicsItems/LinearRegionItem.py +++ /dev/null @@ -1,291 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from .UIGraphicsItem import UIGraphicsItem -from .InfiniteLine import InfiniteLine -import pyqtgraph.functions as fn -import pyqtgraph.debug as debug - -__all__ = ['LinearRegionItem'] - -class LinearRegionItem(UIGraphicsItem): - """ - **Bases:** :class:`UIGraphicsItem ` - - Used for marking a horizontal or vertical region in plots. - The region can be dragged and is bounded by lines which can be dragged individually. - - =============================== ============================================================================= - **Signals:** - sigRegionChangeFinished(self) Emitted when the user has finished dragging the region (or one of its lines) - and when the region is changed programatically. - sigRegionChanged(self) Emitted while the user is dragging the region (or one of its lines) - and when the region is changed programatically. - =============================== ============================================================================= - """ - - sigRegionChangeFinished = QtCore.Signal(object) - sigRegionChanged = QtCore.Signal(object) - Vertical = 0 - Horizontal = 1 - - def __init__(self, values=[0,1], orientation=None, brush=None, movable=True, bounds=None): - """Create a new LinearRegionItem. - - ============= ===================================================================== - **Arguments** - values A list of the positions of the lines in the region. These are not - limits; limits can be set by specifying bounds. - orientation Options are LinearRegionItem.Vertical or LinearRegionItem.Horizontal. - If not specified it will be vertical. - brush Defines the brush that fills the region. Can be any arguments that - are valid for :func:`mkBrush `. Default is - transparent blue. - movable If True, the region and individual lines are movable by the user; if - False, they are static. - bounds Optional [min, max] bounding values for the region - ============= ===================================================================== - """ - - UIGraphicsItem.__init__(self) - if orientation is None: - orientation = LinearRegionItem.Vertical - self.orientation = orientation - self.bounds = QtCore.QRectF() - self.blockLineSignal = False - self.moving = False - self.mouseHovering = False - - if orientation == LinearRegionItem.Horizontal: - self.lines = [ - InfiniteLine(QtCore.QPointF(0, values[0]), 0, movable=movable, bounds=bounds), - InfiniteLine(QtCore.QPointF(0, values[1]), 0, movable=movable, bounds=bounds)] - elif orientation == LinearRegionItem.Vertical: - self.lines = [ - InfiniteLine(QtCore.QPointF(values[1], 0), 90, movable=movable, bounds=bounds), - InfiniteLine(QtCore.QPointF(values[0], 0), 90, movable=movable, bounds=bounds)] - else: - raise Exception('Orientation must be one of LinearRegionItem.Vertical or LinearRegionItem.Horizontal') - - - for l in self.lines: - l.setParentItem(self) - l.sigPositionChangeFinished.connect(self.lineMoveFinished) - l.sigPositionChanged.connect(self.lineMoved) - - if brush is None: - brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50)) - self.setBrush(brush) - - self.setMovable(movable) - - def getRegion(self): - """Return the values at the edges of the region.""" - #if self.orientation[0] == 'h': - #r = (self.bounds.top(), self.bounds.bottom()) - #else: - #r = (self.bounds.left(), self.bounds.right()) - r = [self.lines[0].value(), self.lines[1].value()] - return (min(r), max(r)) - - def setRegion(self, rgn): - """Set the values for the edges of the region. - - ============= ============================================== - **Arguments** - rgn A list or tuple of the lower and upper values. - ============= ============================================== - """ - if self.lines[0].value() == rgn[0] and self.lines[1].value() == rgn[1]: - return - self.blockLineSignal = True - self.lines[0].setValue(rgn[0]) - self.blockLineSignal = False - self.lines[1].setValue(rgn[1]) - #self.blockLineSignal = False - self.lineMoved() - self.lineMoveFinished() - - def setBrush(self, *br, **kargs): - """Set the brush that fills the region. Can have any arguments that are valid - for :func:`mkBrush `. - """ - self.brush = fn.mkBrush(*br, **kargs) - self.currentBrush = self.brush - - def setBounds(self, bounds): - """Optional [min, max] bounding values for the region. To have no bounds on the - region use [None, None]. - Does not affect the current position of the region unless it is outside the new bounds. - See :func:`setRegion ` to set the position - of the region.""" - for l in self.lines: - l.setBounds(bounds) - - def setMovable(self, m): - """Set lines to be movable by the user, or not. If lines are movable, they will - also accept HoverEvents.""" - for l in self.lines: - l.setMovable(m) - self.movable = m - self.setAcceptHoverEvents(m) - - def boundingRect(self): - br = UIGraphicsItem.boundingRect(self) - rng = self.getRegion() - if self.orientation == LinearRegionItem.Vertical: - br.setLeft(rng[0]) - br.setRight(rng[1]) - else: - br.setTop(rng[0]) - br.setBottom(rng[1]) - return br.normalized() - - def paint(self, p, *args): - #prof = debug.Profiler('LinearRegionItem.paint') - UIGraphicsItem.paint(self, p, *args) - p.setBrush(self.currentBrush) - p.setPen(fn.mkPen(None)) - p.drawRect(self.boundingRect()) - #prof.finish() - - def dataBounds(self, axis, frac=1.0, orthoRange=None): - if axis == self.orientation: - return self.getRegion() - else: - return None - - def lineMoved(self): - if self.blockLineSignal: - return - self.prepareGeometryChange() - #self.emit(QtCore.SIGNAL('regionChanged'), self) - self.sigRegionChanged.emit(self) - - def lineMoveFinished(self): - #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) - self.sigRegionChangeFinished.emit(self) - - - #def updateBounds(self): - #vb = self.view().viewRect() - #vals = [self.lines[0].value(), self.lines[1].value()] - #if self.orientation[0] == 'h': - #vb.setTop(min(vals)) - #vb.setBottom(max(vals)) - #else: - #vb.setLeft(min(vals)) - #vb.setRight(max(vals)) - #if vb != self.bounds: - #self.bounds = vb - #self.rect.setRect(vb) - - #def mousePressEvent(self, ev): - #if not self.movable: - #ev.ignore() - #return - #for l in self.lines: - #l.mousePressEvent(ev) ## pass event to both lines so they move together - ##if self.movable and ev.button() == QtCore.Qt.LeftButton: - ##ev.accept() - ##self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) - ##else: - ##ev.ignore() - - #def mouseReleaseEvent(self, ev): - #for l in self.lines: - #l.mouseReleaseEvent(ev) - - #def mouseMoveEvent(self, ev): - ##print "move", ev.pos() - #if not self.movable: - #return - #self.lines[0].blockSignals(True) # only want to update once - #for l in self.lines: - #l.mouseMoveEvent(ev) - #self.lines[0].blockSignals(False) - ##self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) - ##self.emit(QtCore.SIGNAL('dragged'), self) - - def mouseDragEvent(self, ev): - if not self.movable or int(ev.button() & QtCore.Qt.LeftButton) == 0: - return - ev.accept() - - if ev.isStart(): - bdp = ev.buttonDownPos() - self.cursorOffsets = [l.pos() - bdp for l in self.lines] - self.startPositions = [l.pos() for l in self.lines] - self.moving = True - - if not self.moving: - return - - #delta = ev.pos() - ev.lastPos() - self.lines[0].blockSignals(True) # only want to update once - for i, l in enumerate(self.lines): - l.setPos(self.cursorOffsets[i] + ev.pos()) - #l.setPos(l.pos()+delta) - #l.mouseDragEvent(ev) - self.lines[0].blockSignals(False) - self.prepareGeometryChange() - - if ev.isFinish(): - self.moving = False - self.sigRegionChangeFinished.emit(self) - else: - self.sigRegionChanged.emit(self) - - def mouseClickEvent(self, ev): - if self.moving and ev.button() == QtCore.Qt.RightButton: - ev.accept() - for i, l in enumerate(self.lines): - l.setPos(self.startPositions[i]) - self.moving = False - self.sigRegionChanged.emit(self) - self.sigRegionChangeFinished.emit(self) - - - def hoverEvent(self, ev): - if self.movable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): - self.setMouseHover(True) - else: - self.setMouseHover(False) - - def setMouseHover(self, hover): - ## Inform the item that the mouse is(not) hovering over it - if self.mouseHovering == hover: - return - self.mouseHovering = hover - if hover: - c = self.brush.color() - c.setAlpha(c.alpha() * 2) - self.currentBrush = fn.mkBrush(c) - else: - self.currentBrush = self.brush - self.update() - - #def hoverEnterEvent(self, ev): - #print "rgn hover enter" - #ev.ignore() - #self.updateHoverBrush() - - #def hoverMoveEvent(self, ev): - #print "rgn hover move" - #ev.ignore() - #self.updateHoverBrush() - - #def hoverLeaveEvent(self, ev): - #print "rgn hover leave" - #ev.ignore() - #self.updateHoverBrush(False) - - #def updateHoverBrush(self, hover=None): - #if hover is None: - #scene = self.scene() - #hover = scene.claimEvent(self, QtCore.Qt.LeftButton, scene.Drag) - - #if hover: - #self.currentBrush = fn.mkBrush(255, 0,0,100) - #else: - #self.currentBrush = self.brush - #self.update() - diff --git a/pyqtgraph/graphicsItems/MultiPlotItem.py b/pyqtgraph/graphicsItems/MultiPlotItem.py deleted file mode 100644 index d20467a9..00000000 --- a/pyqtgraph/graphicsItems/MultiPlotItem.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -""" -MultiPlotItem.py - Graphics item used for displaying an array of PlotItems -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - -from numpy import ndarray -from . import GraphicsLayout - -try: - from metaarray import * - HAVE_METAARRAY = True -except: - #raise - HAVE_METAARRAY = False - - -__all__ = ['MultiPlotItem'] -class MultiPlotItem(GraphicsLayout.GraphicsLayout): - """ - Automaticaly generates a grid of plots from a multi-dimensional array - """ - - def plot(self, data): - #self.layout.clear() - self.plots = [] - - if HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): - if data.ndim != 2: - raise Exception("MultiPlot currently only accepts 2D MetaArray.") - ic = data.infoCopy() - ax = 0 - for i in [0, 1]: - if 'cols' in ic[i]: - ax = i - break - #print "Plotting using axis %d as columns (%d plots)" % (ax, data.shape[ax]) - for i in range(data.shape[ax]): - pi = self.addPlot() - self.nextRow() - sl = [slice(None)] * 2 - sl[ax] = i - pi.plot(data[tuple(sl)]) - #self.layout.addItem(pi, i, 0) - self.plots.append((pi, i, 0)) - title = None - units = None - info = ic[ax]['cols'][i] - if 'title' in info: - title = info['title'] - elif 'name' in info: - title = info['name'] - if 'units' in info: - units = info['units'] - - pi.setLabel('left', text=title, units=units) - - else: - raise Exception("Data type %s not (yet?) supported for MultiPlot." % type(data)) - - def close(self): - for p in self.plots: - p[0].close() - self.plots = None - self.clear() - - - diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py deleted file mode 100644 index 28214552..00000000 --- a/pyqtgraph/graphicsItems/PlotCurveItem.py +++ /dev/null @@ -1,560 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -try: - from pyqtgraph.Qt import QtOpenGL - HAVE_OPENGL = True -except: - HAVE_OPENGL = False - -import numpy as np -from .GraphicsObject import GraphicsObject -import pyqtgraph.functions as fn -from pyqtgraph import debug -from pyqtgraph.Point import Point -import pyqtgraph as pg -import struct, sys - -__all__ = ['PlotCurveItem'] -class PlotCurveItem(GraphicsObject): - - - """ - Class representing a single plot curve. Instances of this class are created - automatically as part of PlotDataItem; these rarely need to be instantiated - directly. - - Features: - - - Fast data update - - Fill under curve - - Mouse interaction - - ==================== =============================================== - **Signals:** - sigPlotChanged(self) Emitted when the data being plotted has changed - sigClicked(self) Emitted when the curve is clicked - ==================== =============================================== - """ - - sigPlotChanged = QtCore.Signal(object) - sigClicked = QtCore.Signal(object) - - def __init__(self, *args, **kargs): - """ - Forwards all arguments to :func:`setData `. - - Some extra arguments are accepted as well: - - ============== ======================================================= - **Arguments:** - parent The parent GraphicsObject (optional) - clickable If True, the item will emit sigClicked when it is - clicked on. Defaults to False. - ============== ======================================================= - """ - GraphicsObject.__init__(self, kargs.get('parent', None)) - self.clear() - self.path = None - self.fillPath = None - self._boundsCache = [None, None] - - ## this is disastrous for performance. - #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) - - self.metaData = {} - self.opts = { - 'pen': fn.mkPen('w'), - 'shadowPen': None, - 'fillLevel': None, - 'brush': None, - 'stepMode': False, - 'name': None, - 'antialias': pg.getConfigOption('antialias'),\ - 'connect': 'all', - } - self.setClickable(kargs.get('clickable', False)) - self.setData(*args, **kargs) - - def implements(self, interface=None): - ints = ['plotData'] - if interface is None: - return ints - return interface in ints - - def setClickable(self, s): - """Sets whether the item responds to mouse clicks.""" - self.clickable = s - - - def getData(self): - return self.xData, self.yData - - def dataBounds(self, ax, frac=1.0, orthoRange=None): - ## Need this to run as fast as possible. - ## check cache first: - cache = self._boundsCache[ax] - if cache is not None and cache[0] == (frac, orthoRange): - return cache[1] - - (x, y) = self.getData() - if x is None or len(x) == 0: - return (None, None) - - if ax == 0: - d = x - d2 = y - elif ax == 1: - d = y - d2 = x - - ## If an orthogonal range is specified, mask the data now - if orthoRange is not None: - mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) - d = d[mask] - #d2 = d2[mask] - - if len(d) == 0: - return (None, None) - - ## Get min/max (or percentiles) of the requested data range - if frac >= 1.0: - b = (np.nanmin(d), np.nanmax(d)) - elif frac <= 0.0: - raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) - else: - mask = np.isfinite(d) - d = d[mask] - b = np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) - - ## adjust for fill level - if ax == 1 and self.opts['fillLevel'] is not None: - b = (min(b[0], self.opts['fillLevel']), max(b[1], self.opts['fillLevel'])) - - ## Add pen width only if it is non-cosmetic. - pen = self.opts['pen'] - spen = self.opts['shadowPen'] - if not pen.isCosmetic(): - b = (b[0] - pen.widthF()*0.7072, b[1] + pen.widthF()*0.7072) - if spen is not None and not spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen: - b = (b[0] - spen.widthF()*0.7072, b[1] + spen.widthF()*0.7072) - - self._boundsCache[ax] = [(frac, orthoRange), b] - return b - - def pixelPadding(self): - pen = self.opts['pen'] - spen = self.opts['shadowPen'] - w = 0 - if pen.isCosmetic(): - w += pen.widthF()*0.7072 - if spen is not None and spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen: - w = max(w, spen.widthF()*0.7072) - return w - - def boundingRect(self): - if self._boundingRect is None: - (xmn, xmx) = self.dataBounds(ax=0) - (ymn, ymx) = self.dataBounds(ax=1) - if xmn is None: - return QtCore.QRectF() - - px = py = 0.0 - pxPad = self.pixelPadding() - if pxPad > 0: - # determine length of pixel in local x, y directions - px, py = self.pixelVectors() - px = 0 if px is None else px.length() - py = 0 if py is None else py.length() - - # return bounds expanded by pixel size - px *= pxPad - py *= pxPad - #px += self._maxSpotWidth * 0.5 - #py += self._maxSpotWidth * 0.5 - self._boundingRect = QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) - return self._boundingRect - - def viewTransformChanged(self): - self.invalidateBounds() - self.prepareGeometryChange() - - #def boundingRect(self): - #if self._boundingRect is None: - #(x, y) = self.getData() - #if x is None or y is None or len(x) == 0 or len(y) == 0: - #return QtCore.QRectF() - - - #if self.opts['shadowPen'] is not None: - #lineWidth = (max(self.opts['pen'].width(), self.opts['shadowPen'].width()) + 1) - #else: - #lineWidth = (self.opts['pen'].width()+1) - - - #pixels = self.pixelVectors() - #if pixels == (None, None): - #pixels = [Point(0,0), Point(0,0)] - - #xmin = x.min() - #xmax = x.max() - #ymin = y.min() - #ymax = y.max() - - #if self.opts['fillLevel'] is not None: - #ymin = min(ymin, self.opts['fillLevel']) - #ymax = max(ymax, self.opts['fillLevel']) - - #xmin -= pixels[0].x() * lineWidth - #xmax += pixels[0].x() * lineWidth - #ymin -= abs(pixels[1].y()) * lineWidth - #ymax += abs(pixels[1].y()) * lineWidth - - #self._boundingRect = QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin) - #return self._boundingRect - - - def invalidateBounds(self): - self._boundingRect = None - self._boundsCache = [None, None] - - def setPen(self, *args, **kargs): - """Set the pen used to draw the curve.""" - self.opts['pen'] = fn.mkPen(*args, **kargs) - self.invalidateBounds() - self.update() - - def setShadowPen(self, *args, **kargs): - """Set the shadow pen used to draw behind tyhe primary pen. - This pen must have a larger width than the primary - pen to be visible. - """ - self.opts['shadowPen'] = fn.mkPen(*args, **kargs) - self.invalidateBounds() - self.update() - - def setBrush(self, *args, **kargs): - """Set the brush used when filling the area under the curve""" - self.opts['brush'] = fn.mkBrush(*args, **kargs) - self.invalidateBounds() - self.update() - - def setFillLevel(self, level): - """Set the level filled to when filling under the curve""" - self.opts['fillLevel'] = level - self.fillPath = None - self.invalidateBounds() - self.update() - - def setData(self, *args, **kargs): - """ - ============== ======================================================== - **Arguments:** - x, y (numpy arrays) Data to show - pen Pen to use when drawing. Any single argument accepted by - :func:`mkPen ` is allowed. - shadowPen Pen for drawing behind the primary pen. Usually this - is used to emphasize the curve by providing a - high-contrast border. Any single argument accepted by - :func:`mkPen ` is allowed. - fillLevel (float or None) Fill the area 'under' the curve to - *fillLevel* - brush QBrush to use when filling. Any single argument accepted - by :func:`mkBrush ` is allowed. - antialias (bool) Whether to use antialiasing when drawing. This - is disabled by default because it decreases performance. - stepMode If True, two orthogonal lines are drawn for each sample - as steps. This is commonly used when drawing histograms. - Note that in this case, len(x) == len(y) + 1 - connect Argument specifying how vertexes should be connected - by line segments. Default is "all", indicating full - connection. "pairs" causes only even-numbered segments - to be drawn. "finite" causes segments to be omitted if - they are attached to nan or inf values. For any other - connectivity, specify an array of boolean values. - ============== ======================================================== - - If non-keyword arguments are used, they will be interpreted as - setData(y) for a single argument and setData(x, y) for two - arguments. - - - """ - self.updateData(*args, **kargs) - - def updateData(self, *args, **kargs): - prof = debug.Profiler('PlotCurveItem.updateData', disabled=True) - - if len(args) == 1: - kargs['y'] = args[0] - elif len(args) == 2: - kargs['x'] = args[0] - kargs['y'] = args[1] - - if 'y' not in kargs or kargs['y'] is None: - kargs['y'] = np.array([]) - if 'x' not in kargs or kargs['x'] is None: - kargs['x'] = np.arange(len(kargs['y'])) - - for k in ['x', 'y']: - data = kargs[k] - if isinstance(data, list): - data = np.array(data) - kargs[k] = data - if not isinstance(data, np.ndarray) or data.ndim > 1: - raise Exception("Plot data must be 1D ndarray.") - if 'complex' in str(data.dtype): - raise Exception("Can not plot complex data types.") - - prof.mark("data checks") - - #self.setCacheMode(QtGui.QGraphicsItem.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly - ## Test this bug with test_PlotWidget and zoom in on the animated plot - self.invalidateBounds() - self.prepareGeometryChange() - self.informViewBoundsChanged() - self.yData = kargs['y'].view(np.ndarray) - self.xData = kargs['x'].view(np.ndarray) - - prof.mark('copy') - - if 'stepMode' in kargs: - self.opts['stepMode'] = kargs['stepMode'] - - if self.opts['stepMode'] is True: - if len(self.xData) != len(self.yData)+1: ## allow difference of 1 for step mode plots - raise Exception("len(X) must be len(Y)+1 since stepMode=True (got %s and %s)" % (self.xData.shape, self.yData.shape)) - else: - if self.xData.shape != self.yData.shape: ## allow difference of 1 for step mode plots - raise Exception("X and Y arrays must be the same shape--got %s and %s." % (self.xData.shape, self.yData.shape)) - - self.path = None - self.fillPath = None - #self.xDisp = self.yDisp = None - - if 'name' in kargs: - self.opts['name'] = kargs['name'] - if 'connect' in kargs: - self.opts['connect'] = kargs['connect'] - if 'pen' in kargs: - self.setPen(kargs['pen']) - if 'shadowPen' in kargs: - self.setShadowPen(kargs['shadowPen']) - if 'fillLevel' in kargs: - self.setFillLevel(kargs['fillLevel']) - if 'brush' in kargs: - self.setBrush(kargs['brush']) - if 'antialias' in kargs: - self.opts['antialias'] = kargs['antialias'] - - - prof.mark('set') - self.update() - prof.mark('update') - self.sigPlotChanged.emit(self) - prof.mark('emit') - prof.finish() - - def generatePath(self, x, y): - if self.opts['stepMode']: - ## each value in the x/y arrays generates 2 points. - x2 = np.empty((len(x),2), dtype=x.dtype) - x2[:] = x[:,np.newaxis] - if self.opts['fillLevel'] is None: - x = x2.reshape(x2.size)[1:-1] - y2 = np.empty((len(y),2), dtype=y.dtype) - y2[:] = y[:,np.newaxis] - y = y2.reshape(y2.size) - else: - ## If we have a fill level, add two extra points at either end - x = x2.reshape(x2.size) - y2 = np.empty((len(y)+2,2), dtype=y.dtype) - y2[1:-1] = y[:,np.newaxis] - y = y2.reshape(y2.size)[1:-1] - y[0] = self.opts['fillLevel'] - y[-1] = self.opts['fillLevel'] - - path = fn.arrayToQPath(x, y, connect=self.opts['connect']) - - return path - - - def shape(self): - if self.path is None: - try: - self.path = self.generatePath(*self.getData()) - except: - return QtGui.QPainterPath() - return self.path - - @pg.debug.warnOnException ## raising an exception here causes crash - def paint(self, p, opt, widget): - prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True) - if self.xData is None: - return - - if HAVE_OPENGL and pg.getConfigOption('enableExperimental') and isinstance(widget, QtOpenGL.QGLWidget): - self.paintGL(p, opt, widget) - return - - x = None - y = None - if self.path is None: - x,y = self.getData() - if x is None or len(x) == 0 or y is None or len(y) == 0: - return - self.path = self.generatePath(x,y) - self.fillPath = None - - path = self.path - prof.mark('generate path') - - if self._exportOpts is not False: - aa = self._exportOpts.get('antialias', True) - else: - aa = self.opts['antialias'] - - p.setRenderHint(p.Antialiasing, aa) - - - if self.opts['brush'] is not None and self.opts['fillLevel'] is not None: - if self.fillPath is None: - if x is None: - x,y = self.getData() - p2 = QtGui.QPainterPath(self.path) - p2.lineTo(x[-1], self.opts['fillLevel']) - p2.lineTo(x[0], self.opts['fillLevel']) - p2.lineTo(x[0], y[0]) - p2.closeSubpath() - self.fillPath = p2 - - prof.mark('generate fill path') - p.fillPath(self.fillPath, self.opts['brush']) - prof.mark('draw fill path') - - sp = fn.mkPen(self.opts['shadowPen']) - cp = fn.mkPen(self.opts['pen']) - - ## Copy pens and apply alpha adjustment - #sp = QtGui.QPen(self.opts['shadowPen']) - #cp = QtGui.QPen(self.opts['pen']) - #for pen in [sp, cp]: - #if pen is None: - #continue - #c = pen.color() - #c.setAlpha(c.alpha() * self.opts['alphaHint']) - #pen.setColor(c) - ##pen.setCosmetic(True) - - - - if sp is not None and sp.style() != QtCore.Qt.NoPen: - p.setPen(sp) - p.drawPath(path) - p.setPen(cp) - p.drawPath(path) - prof.mark('drawPath') - - #print "Render hints:", int(p.renderHints()) - prof.finish() - #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) - #p.drawRect(self.boundingRect()) - - def paintGL(self, p, opt, widget): - p.beginNativePainting() - import OpenGL.GL as gl - - ## set clipping viewport - view = self.getViewBox() - if view is not None: - rect = view.mapRectToItem(self, view.boundingRect()) - #gl.glViewport(int(rect.x()), int(rect.y()), int(rect.width()), int(rect.height())) - - #gl.glTranslate(-rect.x(), -rect.y(), 0) - - gl.glEnable(gl.GL_STENCIL_TEST) - gl.glColorMask(gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE) # disable drawing to frame buffer - gl.glDepthMask(gl.GL_FALSE) # disable drawing to depth buffer - gl.glStencilFunc(gl.GL_NEVER, 1, 0xFF) - gl.glStencilOp(gl.GL_REPLACE, gl.GL_KEEP, gl.GL_KEEP) - - ## draw stencil pattern - gl.glStencilMask(0xFF); - gl.glClear(gl.GL_STENCIL_BUFFER_BIT) - gl.glBegin(gl.GL_TRIANGLES) - gl.glVertex2f(rect.x(), rect.y()) - gl.glVertex2f(rect.x()+rect.width(), rect.y()) - gl.glVertex2f(rect.x(), rect.y()+rect.height()) - gl.glVertex2f(rect.x()+rect.width(), rect.y()+rect.height()) - gl.glVertex2f(rect.x()+rect.width(), rect.y()) - gl.glVertex2f(rect.x(), rect.y()+rect.height()) - gl.glEnd() - - gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE) - gl.glDepthMask(gl.GL_TRUE) - gl.glStencilMask(0x00) - gl.glStencilFunc(gl.GL_EQUAL, 1, 0xFF) - - try: - x, y = self.getData() - pos = np.empty((len(x), 2)) - pos[:,0] = x - pos[:,1] = y - gl.glEnableClientState(gl.GL_VERTEX_ARRAY) - try: - gl.glVertexPointerf(pos) - pen = fn.mkPen(self.opts['pen']) - color = pen.color() - gl.glColor4f(color.red()/255., color.green()/255., color.blue()/255., color.alpha()/255.) - width = pen.width() - if pen.isCosmetic() and width < 1: - width = 1 - gl.glPointSize(width) - gl.glEnable(gl.GL_LINE_SMOOTH) - gl.glEnable(gl.GL_BLEND) - gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) - gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST); - gl.glDrawArrays(gl.GL_LINE_STRIP, 0, pos.size / pos.shape[-1]) - finally: - gl.glDisableClientState(gl.GL_VERTEX_ARRAY) - finally: - p.endNativePainting() - - def clear(self): - self.xData = None ## raw values - self.yData = None - self.xDisp = None ## display values (after log / fft) - self.yDisp = None - self.path = None - #del self.xData, self.yData, self.xDisp, self.yDisp, self.path - - def mouseClickEvent(self, ev): - if not self.clickable or ev.button() != QtCore.Qt.LeftButton: - return - ev.accept() - self.sigClicked.emit(self) - - -class ROIPlotItem(PlotCurveItem): - """Plot curve that monitors an ROI and image for changes to automatically replot.""" - def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None): - self.roi = roi - self.roiData = data - self.roiImg = img - self.axes = axes - self.xVals = xVals - PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color) - #roi.connect(roi, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent) - roi.sigRegionChanged.connect(self.roiChangedEvent) - #self.roiChangedEvent() - - def getRoiData(self): - d = self.roi.getArrayRegion(self.roiData, self.roiImg, axes=self.axes) - if d is None: - return - while d.ndim > 1: - d = d.mean(axis=1) - return d - - def roiChangedEvent(self): - d = self.getRoiData() - self.updateData(d, self.xVals) - diff --git a/pyqtgraph/graphicsItems/PlotDataItem.py b/pyqtgraph/graphicsItems/PlotDataItem.py deleted file mode 100644 index 87b47227..00000000 --- a/pyqtgraph/graphicsItems/PlotDataItem.py +++ /dev/null @@ -1,843 +0,0 @@ -import pyqtgraph.metaarray as metaarray -from pyqtgraph.Qt import QtCore -from .GraphicsObject import GraphicsObject -from .PlotCurveItem import PlotCurveItem -from .ScatterPlotItem import ScatterPlotItem -import numpy as np -import pyqtgraph.functions as fn -import pyqtgraph.debug as debug -import pyqtgraph as pg - -class PlotDataItem(GraphicsObject): - """ - **Bases:** :class:`GraphicsObject ` - - GraphicsItem for displaying plot curves, scatter plots, or both. - While it is possible to use :class:`PlotCurveItem ` or - :class:`ScatterPlotItem ` individually, this class - provides a unified interface to both. Inspances of :class:`PlotDataItem` are - usually created by plot() methods such as :func:`pyqtgraph.plot` and - :func:`PlotItem.plot() `. - - ============================== ============================================== - **Signals:** - sigPlotChanged(self) Emitted when the data in this item is updated. - sigClicked(self) Emitted when the item is clicked. - sigPointsClicked(self, points) Emitted when a plot point is clicked - Sends the list of points under the mouse. - ============================== ============================================== - """ - - sigPlotChanged = QtCore.Signal(object) - sigClicked = QtCore.Signal(object) - sigPointsClicked = QtCore.Signal(object, object) - - def __init__(self, *args, **kargs): - """ - There are many different ways to create a PlotDataItem: - - **Data initialization arguments:** (x,y data only) - - =================================== ====================================== - PlotDataItem(xValues, yValues) x and y values may be any sequence (including ndarray) of real numbers - PlotDataItem(yValues) y values only -- x will be automatically set to range(len(y)) - PlotDataItem(x=xValues, y=yValues) x and y given by keyword arguments - PlotDataItem(ndarray(Nx2)) numpy array with shape (N, 2) where x=data[:,0] and y=data[:,1] - =================================== ====================================== - - **Data initialization arguments:** (x,y data AND may include spot style) - - =========================== ========================================= - PlotDataItem(recarray) numpy array with dtype=[('x', float), ('y', float), ...] - PlotDataItem(list-of-dicts) [{'x': x, 'y': y, ...}, ...] - PlotDataItem(dict-of-lists) {'x': [...], 'y': [...], ...} - PlotDataItem(MetaArray) 1D array of Y values with X sepecified as axis values - OR 2D array with a column 'y' and extra columns as needed. - =========================== ========================================= - - **Line style keyword arguments:** - ========== ================================================ - connect Specifies how / whether vertexes should be connected. - See :func:`arrayToQPath() ` - pen Pen to use for drawing line between points. - Default is solid grey, 1px width. Use None to disable line drawing. - May be any single argument accepted by :func:`mkPen() ` - shadowPen Pen for secondary line to draw behind the primary line. disabled by default. - May be any single argument accepted by :func:`mkPen() ` - fillLevel Fill the area between the curve and fillLevel - fillBrush Fill to use when fillLevel is specified. - May be any single argument accepted by :func:`mkBrush() ` - ========== ================================================ - - **Point style keyword arguments:** (see :func:`ScatterPlotItem.setData() ` for more information) - - ============ ================================================ - symbol Symbol to use for drawing points OR list of symbols, one per point. Default is no symbol. - Options are o, s, t, d, +, or any QPainterPath - symbolPen Outline pen for drawing points OR list of pens, one per point. - May be any single argument accepted by :func:`mkPen() ` - symbolBrush Brush for filling points OR list of brushes, one per point. - May be any single argument accepted by :func:`mkBrush() ` - symbolSize Diameter of symbols OR list of diameters. - pxMode (bool) If True, then symbolSize is specified in pixels. If False, then symbolSize is - specified in data coordinates. - ============ ================================================ - - **Optimization keyword arguments:** - - ================ ===================================================================== - antialias (bool) By default, antialiasing is disabled to improve performance. - Note that in some cases (in particluar, when pxMode=True), points - will be rendered antialiased even if this is set to False. - decimate deprecated. - downsample (int) Reduce the number of samples displayed by this value - downsampleMethod 'subsample': Downsample by taking the first of N samples. - This method is fastest and least accurate. - 'mean': Downsample by taking the mean of N samples. - 'peak': Downsample by drawing a saw wave that follows the min - and max of the original data. This method produces the best - visual representation of the data but is slower. - autoDownsample (bool) If True, resample the data before plotting to avoid plotting - multiple line segments per pixel. This can improve performance when - viewing very high-density data, but increases the initial overhead - and memory usage. - clipToView (bool) If True, only plot data that is visible within the X range of - the containing ViewBox. This can improve performance when plotting - very large data sets where only a fraction of the data is visible - at any time. - identical *deprecated* - ================ ===================================================================== - - **Meta-info keyword arguments:** - - ========== ================================================ - name name of dataset. This would appear in a legend - ========== ================================================ - """ - GraphicsObject.__init__(self) - self.setFlag(self.ItemHasNoContents) - self.xData = None - self.yData = None - self.xDisp = None - self.yDisp = None - #self.dataMask = None - #self.curves = [] - #self.scatters = [] - self.curve = PlotCurveItem() - self.scatter = ScatterPlotItem() - self.curve.setParentItem(self) - self.scatter.setParentItem(self) - - self.curve.sigClicked.connect(self.curveClicked) - self.scatter.sigClicked.connect(self.scatterClicked) - - - #self.clear() - self.opts = { - 'connect': 'all', - - 'fftMode': False, - 'logMode': [False, False], - 'alphaHint': 1.0, - 'alphaMode': False, - - 'pen': (200,200,200), - 'shadowPen': None, - 'fillLevel': None, - 'fillBrush': None, - - 'symbol': None, - 'symbolSize': 10, - 'symbolPen': (200,200,200), - 'symbolBrush': (50, 50, 150), - 'pxMode': True, - - 'antialias': pg.getConfigOption('antialias'), - 'pointMode': None, - - 'downsample': 1, - 'autoDownsample': False, - 'downsampleMethod': 'peak', - 'clipToView': False, - - 'data': None, - } - self.setData(*args, **kargs) - - def implements(self, interface=None): - ints = ['plotData'] - if interface is None: - return ints - return interface in ints - - def boundingRect(self): - return QtCore.QRectF() ## let child items handle this - - def setAlpha(self, alpha, auto): - if self.opts['alphaHint'] == alpha and self.opts['alphaMode'] == auto: - return - self.opts['alphaHint'] = alpha - self.opts['alphaMode'] = auto - self.setOpacity(alpha) - #self.update() - - def setFftMode(self, mode): - if self.opts['fftMode'] == mode: - return - self.opts['fftMode'] = mode - self.xDisp = self.yDisp = None - self.xClean = self.yClean = None - self.updateItems() - self.informViewBoundsChanged() - - def setLogMode(self, xMode, yMode): - if self.opts['logMode'] == [xMode, yMode]: - return - self.opts['logMode'] = [xMode, yMode] - self.xDisp = self.yDisp = None - self.xClean = self.yClean = None - self.updateItems() - self.informViewBoundsChanged() - - def setPointMode(self, mode): - if self.opts['pointMode'] == mode: - return - self.opts['pointMode'] = mode - self.update() - - def setPen(self, *args, **kargs): - """ - | Sets the pen used to draw lines between points. - | *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() ` - """ - pen = fn.mkPen(*args, **kargs) - self.opts['pen'] = pen - #self.curve.setPen(pen) - #for c in self.curves: - #c.setPen(pen) - #self.update() - self.updateItems() - - def setShadowPen(self, *args, **kargs): - """ - | Sets the shadow pen used to draw lines between points (this is for enhancing contrast or - emphacizing data). - | This line is drawn behind the primary pen (see :func:`setPen() `) - and should generally be assigned greater width than the primary pen. - | *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() ` - """ - pen = fn.mkPen(*args, **kargs) - self.opts['shadowPen'] = pen - #for c in self.curves: - #c.setPen(pen) - #self.update() - self.updateItems() - - def setFillBrush(self, *args, **kargs): - brush = fn.mkBrush(*args, **kargs) - if self.opts['fillBrush'] == brush: - return - self.opts['fillBrush'] = brush - self.updateItems() - - def setBrush(self, *args, **kargs): - return self.setFillBrush(*args, **kargs) - - def setFillLevel(self, level): - if self.opts['fillLevel'] == level: - return - self.opts['fillLevel'] = level - self.updateItems() - - def setSymbol(self, symbol): - if self.opts['symbol'] == symbol: - return - self.opts['symbol'] = symbol - #self.scatter.setSymbol(symbol) - self.updateItems() - - def setSymbolPen(self, *args, **kargs): - pen = fn.mkPen(*args, **kargs) - if self.opts['symbolPen'] == pen: - return - self.opts['symbolPen'] = pen - #self.scatter.setSymbolPen(pen) - self.updateItems() - - - - def setSymbolBrush(self, *args, **kargs): - brush = fn.mkBrush(*args, **kargs) - if self.opts['symbolBrush'] == brush: - return - self.opts['symbolBrush'] = brush - #self.scatter.setSymbolBrush(brush) - self.updateItems() - - - def setSymbolSize(self, size): - if self.opts['symbolSize'] == size: - return - self.opts['symbolSize'] = size - #self.scatter.setSymbolSize(symbolSize) - self.updateItems() - - def setDownsampling(self, ds=None, auto=None, method=None): - """ - Set the downsampling mode of this item. Downsampling reduces the number - of samples drawn to increase performance. - - =========== ================================================================= - Arguments - ds (int) Reduce visible plot samples by this factor. To disable, - set ds=1. - auto (bool) If True, automatically pick *ds* based on visible range - mode 'subsample': Downsample by taking the first of N samples. - This method is fastest and least accurate. - 'mean': Downsample by taking the mean of N samples. - 'peak': Downsample by drawing a saw wave that follows the min - and max of the original data. This method produces the best - visual representation of the data but is slower. - =========== ================================================================= - """ - changed = False - if ds is not None: - if self.opts['downsample'] != ds: - changed = True - self.opts['downsample'] = ds - - if auto is not None and self.opts['autoDownsample'] != auto: - self.opts['autoDownsample'] = auto - changed = True - - if method is not None: - if self.opts['downsampleMethod'] != method: - changed = True - self.opts['downsampleMethod'] = method - - if changed: - self.xDisp = self.yDisp = None - self.updateItems() - - def setClipToView(self, clip): - if self.opts['clipToView'] == clip: - return - self.opts['clipToView'] = clip - self.xDisp = self.yDisp = None - self.updateItems() - - - def setData(self, *args, **kargs): - """ - Clear any data displayed by this item and display new data. - See :func:`__init__() ` for details; it accepts the same arguments. - """ - #self.clear() - prof = debug.Profiler('PlotDataItem.setData (0x%x)' % id(self), disabled=True) - y = None - x = None - if len(args) == 1: - data = args[0] - dt = dataType(data) - if dt == 'empty': - pass - elif dt == 'listOfValues': - y = np.array(data) - elif dt == 'Nx2array': - x = data[:,0] - y = data[:,1] - elif dt == 'recarray' or dt == 'dictOfLists': - if 'x' in data: - x = np.array(data['x']) - if 'y' in data: - y = np.array(data['y']) - elif dt == 'listOfDicts': - if 'x' in data[0]: - x = np.array([d.get('x',None) for d in data]) - if 'y' in data[0]: - y = np.array([d.get('y',None) for d in data]) - for k in ['data', 'symbolSize', 'symbolPen', 'symbolBrush', 'symbolShape']: - if k in data: - kargs[k] = [d.get(k, None) for d in data] - elif dt == 'MetaArray': - y = data.view(np.ndarray) - x = data.xvals(0).view(np.ndarray) - else: - raise Exception('Invalid data type %s' % type(data)) - - elif len(args) == 2: - seq = ('listOfValues', 'MetaArray', 'empty') - if dataType(args[0]) not in seq or dataType(args[1]) not in seq: - raise Exception('When passing two unnamed arguments, both must be a list or array of values. (got %s, %s)' % (str(type(args[0])), str(type(args[1])))) - if not isinstance(args[0], np.ndarray): - x = np.array(args[0]) - else: - x = args[0].view(np.ndarray) - if not isinstance(args[1], np.ndarray): - y = np.array(args[1]) - else: - y = args[1].view(np.ndarray) - - if 'x' in kargs: - x = kargs['x'] - if 'y' in kargs: - y = kargs['y'] - - prof.mark('interpret data') - ## pull in all style arguments. - ## Use self.opts to fill in anything not present in kargs. - - if 'name' in kargs: - self.opts['name'] = kargs['name'] - if 'connect' in kargs: - self.opts['connect'] = kargs['connect'] - - ## if symbol pen/brush are given with no symbol, then assume symbol is 'o' - - if 'symbol' not in kargs and ('symbolPen' in kargs or 'symbolBrush' in kargs or 'symbolSize' in kargs): - kargs['symbol'] = 'o' - - if 'brush' in kargs: - kargs['fillBrush'] = kargs['brush'] - - for k in list(self.opts.keys()): - if k in kargs: - self.opts[k] = kargs[k] - - #curveArgs = {} - #for k in ['pen', 'shadowPen', 'fillLevel', 'brush']: - #if k in kargs: - #self.opts[k] = kargs[k] - #curveArgs[k] = self.opts[k] - - #scatterArgs = {} - #for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol')]: - #if k in kargs: - #self.opts[k] = kargs[k] - #scatterArgs[v] = self.opts[k] - - - if y is None: - return - if y is not None and x is None: - x = np.arange(len(y)) - - if isinstance(x, list): - x = np.array(x) - if isinstance(y, list): - y = np.array(y) - - self.xData = x.view(np.ndarray) ## one last check to make sure there are no MetaArrays getting by - self.yData = y.view(np.ndarray) - self.xClean = self.yClean = None - self.xDisp = None - self.yDisp = None - prof.mark('set data') - - self.updateItems() - prof.mark('update items') - - self.informViewBoundsChanged() - #view = self.getViewBox() - #if view is not None: - #view.itemBoundsChanged(self) ## inform view so it can update its range if it wants - - self.sigPlotChanged.emit(self) - prof.mark('emit') - prof.finish() - - - def updateItems(self): - - curveArgs = {} - for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillBrush', 'brush'), ('antialias', 'antialias'), ('connect', 'connect')]: - curveArgs[v] = self.opts[k] - - scatterArgs = {} - for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol'), ('symbolSize', 'size'), ('data', 'data'), ('pxMode', 'pxMode'), ('antialias', 'antialias')]: - if k in self.opts: - scatterArgs[v] = self.opts[k] - - x,y = self.getData() - #scatterArgs['mask'] = self.dataMask - - if curveArgs['pen'] is not None or (curveArgs['brush'] is not None and curveArgs['fillLevel'] is not None): - self.curve.setData(x=x, y=y, **curveArgs) - self.curve.show() - else: - self.curve.hide() - - if scatterArgs['symbol'] is not None: - self.scatter.setData(x=x, y=y, **scatterArgs) - self.scatter.show() - else: - self.scatter.hide() - - - def getData(self): - if self.xData is None: - return (None, None) - - #if self.xClean is None: - #nanMask = np.isnan(self.xData) | np.isnan(self.yData) | np.isinf(self.xData) | np.isinf(self.yData) - #if nanMask.any(): - #self.dataMask = ~nanMask - #self.xClean = self.xData[self.dataMask] - #self.yClean = self.yData[self.dataMask] - #else: - #self.dataMask = None - #self.xClean = self.xData - #self.yClean = self.yData - - if self.xDisp is None: - x = self.xData - y = self.yData - - - #ds = self.opts['downsample'] - #if isinstance(ds, int) and ds > 1: - #x = x[::ds] - ##y = resample(y[:len(x)*ds], len(x)) ## scipy.signal.resample causes nasty ringing - #y = y[::ds] - if self.opts['fftMode']: - x,y = self._fourierTransform(x, y) - if self.opts['logMode'][0]: - x = np.log10(x) - if self.opts['logMode'][1]: - y = np.log10(y) - #if any(self.opts['logMode']): ## re-check for NANs after log - #nanMask = np.isinf(x) | np.isinf(y) | np.isnan(x) | np.isnan(y) - #if any(nanMask): - #self.dataMask = ~nanMask - #x = x[self.dataMask] - #y = y[self.dataMask] - #else: - #self.dataMask = None - - ds = self.opts['downsample'] - if not isinstance(ds, int): - ds = 1 - - if self.opts['autoDownsample']: - # this option presumes that x-values have uniform spacing - range = self.viewRect() - if range is not None: - dx = float(x[-1]-x[0]) / (len(x)-1) - x0 = (range.left()-x[0]) / dx - x1 = (range.right()-x[0]) / dx - width = self.getViewBox().width() - ds = int(max(1, int(0.2 * (x1-x0) / width))) - ## downsampling is expensive; delay until after clipping. - - if self.opts['clipToView']: - # this option presumes that x-values have uniform spacing - range = self.viewRect() - if range is not None: - dx = float(x[-1]-x[0]) / (len(x)-1) - # clip to visible region extended by downsampling value - x0 = np.clip(int((range.left()-x[0])/dx)-1*ds , 0, len(x)-1) - x1 = np.clip(int((range.right()-x[0])/dx)+2*ds , 0, len(x)-1) - x = x[x0:x1] - y = y[x0:x1] - - if ds > 1: - if self.opts['downsampleMethod'] == 'subsample': - x = x[::ds] - y = y[::ds] - elif self.opts['downsampleMethod'] == 'mean': - n = len(x) / ds - x = x[:n*ds:ds] - y = y[:n*ds].reshape(n,ds).mean(axis=1) - elif self.opts['downsampleMethod'] == 'peak': - n = len(x) / ds - x1 = np.empty((n,2)) - x1[:] = x[:n*ds:ds,np.newaxis] - x = x1.reshape(n*2) - y1 = np.empty((n,2)) - y2 = y[:n*ds].reshape((n, ds)) - y1[:,0] = y2.max(axis=1) - y1[:,1] = y2.min(axis=1) - y = y1.reshape(n*2) - - - self.xDisp = x - self.yDisp = y - #print self.yDisp.shape, self.yDisp.min(), self.yDisp.max() - #print self.xDisp.shape, self.xDisp.min(), self.xDisp.max() - return self.xDisp, self.yDisp - - def dataBounds(self, ax, frac=1.0, orthoRange=None): - """ - Returns the range occupied by the data (along a specific axis) in this item. - This method is called by ViewBox when auto-scaling. - - =============== ============================================================= - **Arguments:** - ax (0 or 1) the axis for which to return this item's data range - frac (float 0.0-1.0) Specifies what fraction of the total data - range to return. By default, the entire range is returned. - This allows the ViewBox to ignore large spikes in the data - when auto-scaling. - orthoRange ([min,max] or None) Specifies that only the data within the - given range (orthogonal to *ax*) should me measured when - returning the data range. (For example, a ViewBox might ask - what is the y-range of all data with x-values between min - and max) - =============== ============================================================= - """ - - range = [None, None] - if self.curve.isVisible(): - range = self.curve.dataBounds(ax, frac, orthoRange) - elif self.scatter.isVisible(): - r2 = self.scatter.dataBounds(ax, frac, orthoRange) - range = [ - r2[0] if range[0] is None else (range[0] if r2[0] is None else min(r2[0], range[0])), - r2[1] if range[1] is None else (range[1] if r2[1] is None else min(r2[1], range[1])) - ] - return range - - def pixelPadding(self): - """ - Return the size in pixels that this item may draw beyond the values returned by dataBounds(). - This method is called by ViewBox when auto-scaling. - """ - pad = 0 - if self.curve.isVisible(): - pad = max(pad, self.curve.pixelPadding()) - elif self.scatter.isVisible(): - pad = max(pad, self.scatter.pixelPadding()) - return pad - - - def clear(self): - #for i in self.curves+self.scatters: - #if i.scene() is not None: - #i.scene().removeItem(i) - #self.curves = [] - #self.scatters = [] - self.xData = None - self.yData = None - #self.xClean = None - #self.yClean = None - self.xDisp = None - self.yDisp = None - self.curve.setData([]) - self.scatter.setData([]) - - def appendData(self, *args, **kargs): - pass - - def curveClicked(self): - self.sigClicked.emit(self) - - def scatterClicked(self, plt, points): - self.sigClicked.emit(self) - self.sigPointsClicked.emit(self, points) - - def viewRangeChanged(self): - # view range has changed; re-plot if needed - if self.opts['clipToView'] or self.opts['autoDownsample']: - self.xDisp = self.yDisp = None - self.updateItems() - - def _fourierTransform(self, x, y): - ## Perform fourier transform. If x values are not sampled uniformly, - ## then use interpolate.griddata to resample before taking fft. - dx = np.diff(x) - uniform = not np.any(np.abs(dx-dx[0]) > (abs(dx[0]) / 1000.)) - if not uniform: - import scipy.interpolate as interp - x2 = np.linspace(x[0], x[-1], len(x)) - y = interp.griddata(x, y, x2, method='linear') - x = x2 - f = np.fft.fft(y) / len(y) - y = abs(f[1:len(f)/2]) - dt = x[-1] - x[0] - x = np.linspace(0, 0.5*len(x)/dt, len(y)) - return x, y - -def dataType(obj): - if hasattr(obj, '__len__') and len(obj) == 0: - return 'empty' - if isinstance(obj, dict): - return 'dictOfLists' - elif isSequence(obj): - first = obj[0] - - if (hasattr(obj, 'implements') and obj.implements('MetaArray')): - return 'MetaArray' - elif isinstance(obj, np.ndarray): - if obj.ndim == 1: - if obj.dtype.names is None: - return 'listOfValues' - else: - return 'recarray' - elif obj.ndim == 2 and obj.dtype.names is None and obj.shape[1] == 2: - return 'Nx2array' - else: - raise Exception('array shape must be (N,) or (N,2); got %s instead' % str(obj.shape)) - elif isinstance(first, dict): - return 'listOfDicts' - else: - return 'listOfValues' - - -def isSequence(obj): - return hasattr(obj, '__iter__') or isinstance(obj, np.ndarray) or (hasattr(obj, 'implements') and obj.implements('MetaArray')) - - - -#class TableData: - #""" - #Class for presenting multiple forms of tabular data through a consistent interface. - #May contain: - #- numpy record array - #- list-of-dicts (all dicts are _not_ required to have the same keys) - #- dict-of-lists - #- dict (single record) - #Note: if all the values in this record are lists, it will be interpreted as multiple records - - #Data can be accessed and modified by column, by row, or by value - #data[columnName] - #data[rowId] - #data[columnName, rowId] = value - #data[columnName] = [value, value, ...] - #data[rowId] = {columnName: value, ...} - #""" - - #def __init__(self, data): - #self.data = data - #if isinstance(data, np.ndarray): - #self.mode = 'array' - #elif isinstance(data, list): - #self.mode = 'list' - #elif isinstance(data, dict): - #types = set(map(type, data.values())) - ### dict may be a dict-of-lists or a single record - #types -= set([list, np.ndarray]) ## if dict contains any non-sequence values, it is probably a single record. - #if len(types) != 0: - #self.data = [self.data] - #self.mode = 'list' - #else: - #self.mode = 'dict' - #elif isinstance(data, TableData): - #self.data = data.data - #self.mode = data.mode - #else: - #raise TypeError(type(data)) - - #for fn in ['__getitem__', '__setitem__']: - #setattr(self, fn, getattr(self, '_TableData'+fn+self.mode)) - - #def originalData(self): - #return self.data - - #def toArray(self): - #if self.mode == 'array': - #return self.data - #if len(self) < 1: - ##return np.array([]) ## need to return empty array *with correct columns*, but this is very difficult, so just return None - #return None - #rec1 = self[0] - #dtype = functions.suggestRecordDType(rec1) - ##print rec1, dtype - #arr = np.empty(len(self), dtype=dtype) - #arr[0] = tuple(rec1.values()) - #for i in xrange(1, len(self)): - #arr[i] = tuple(self[i].values()) - #return arr - - #def __getitem__array(self, arg): - #if isinstance(arg, tuple): - #return self.data[arg[0]][arg[1]] - #else: - #return self.data[arg] - - #def __getitem__list(self, arg): - #if isinstance(arg, basestring): - #return [d.get(arg, None) for d in self.data] - #elif isinstance(arg, int): - #return self.data[arg] - #elif isinstance(arg, tuple): - #arg = self._orderArgs(arg) - #return self.data[arg[0]][arg[1]] - #else: - #raise TypeError(type(arg)) - - #def __getitem__dict(self, arg): - #if isinstance(arg, basestring): - #return self.data[arg] - #elif isinstance(arg, int): - #return dict([(k, v[arg]) for k, v in self.data.iteritems()]) - #elif isinstance(arg, tuple): - #arg = self._orderArgs(arg) - #return self.data[arg[1]][arg[0]] - #else: - #raise TypeError(type(arg)) - - #def __setitem__array(self, arg, val): - #if isinstance(arg, tuple): - #self.data[arg[0]][arg[1]] = val - #else: - #self.data[arg] = val - - #def __setitem__list(self, arg, val): - #if isinstance(arg, basestring): - #if len(val) != len(self.data): - #raise Exception("Values (%d) and data set (%d) are not the same length." % (len(val), len(self.data))) - #for i, rec in enumerate(self.data): - #rec[arg] = val[i] - #elif isinstance(arg, int): - #self.data[arg] = val - #elif isinstance(arg, tuple): - #arg = self._orderArgs(arg) - #self.data[arg[0]][arg[1]] = val - #else: - #raise TypeError(type(arg)) - - #def __setitem__dict(self, arg, val): - #if isinstance(arg, basestring): - #if len(val) != len(self.data[arg]): - #raise Exception("Values (%d) and data set (%d) are not the same length." % (len(val), len(self.data[arg]))) - #self.data[arg] = val - #elif isinstance(arg, int): - #for k in self.data: - #self.data[k][arg] = val[k] - #elif isinstance(arg, tuple): - #arg = self._orderArgs(arg) - #self.data[arg[1]][arg[0]] = val - #else: - #raise TypeError(type(arg)) - - #def _orderArgs(self, args): - ### return args in (int, str) order - #if isinstance(args[0], basestring): - #return (args[1], args[0]) - #else: - #return args - - #def __iter__(self): - #for i in xrange(len(self)): - #yield self[i] - - #def __len__(self): - #if self.mode == 'array' or self.mode == 'list': - #return len(self.data) - #else: - #return max(map(len, self.data.values())) - - #def columnNames(self): - #"""returns column names in no particular order""" - #if self.mode == 'array': - #return self.data.dtype.names - #elif self.mode == 'list': - #names = set() - #for row in self.data: - #names.update(row.keys()) - #return list(names) - #elif self.mode == 'dict': - #return self.data.keys() - - #def keys(self): - #return self.columnNames() diff --git a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py deleted file mode 100644 index ec0960ba..00000000 --- a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py +++ /dev/null @@ -1,1271 +0,0 @@ -# -*- coding: utf-8 -*- -""" -PlotItem.py - Graphics item implementing a scalable ViewBox with plotting powers. -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. - -This class is one of the workhorses of pyqtgraph. It implements a graphics item with -plots, labels, and scales which can be viewed inside a QGraphicsScene. If you want -a widget that can be added to your GUI, see PlotWidget instead. - -This class is very heavily featured: - - Automatically creates and manages PlotCurveItems - - Fast display and update of plots - - Manages zoom/pan ViewBox, scale, and label elements - - Automatic scaling when data changes - - Control panel with a huge feature set including averaging, decimation, - display, power spectrum, svg/png export, plot linking, and more. -""" -from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE -import pyqtgraph.pixmaps - -if USE_PYSIDE: - from .plotConfigTemplate_pyside import * -else: - from .plotConfigTemplate_pyqt import * - -import pyqtgraph.functions as fn -from pyqtgraph.widgets.FileDialog import FileDialog -import weakref -import numpy as np -import os -from .. PlotDataItem import PlotDataItem -from .. ViewBox import ViewBox -from .. AxisItem import AxisItem -from .. LabelItem import LabelItem -from .. LegendItem import LegendItem -from .. GraphicsWidget import GraphicsWidget -from .. ButtonItem import ButtonItem -from .. InfiniteLine import InfiniteLine -from pyqtgraph.WidgetGroup import WidgetGroup - -__all__ = ['PlotItem'] - -try: - from metaarray import * - HAVE_METAARRAY = True -except: - HAVE_METAARRAY = False - - - - -class PlotItem(GraphicsWidget): - - """ - **Bases:** :class:`GraphicsWidget ` - - Plot graphics item that can be added to any graphics scene. Implements axes, titles, and interactive viewbox. - PlotItem also provides some basic analysis functionality that may be accessed from the context menu. - Use :func:`plot() ` to create a new PlotDataItem and add it to the view. - Use :func:`addItem() ` to add any QGraphicsItem to the view. - - This class wraps several methods from its internal ViewBox: - :func:`setXRange `, - :func:`setYRange `, - :func:`setRange `, - :func:`autoRange `, - :func:`setXLink `, - :func:`setYLink `, - :func:`setAutoPan `, - :func:`setAutoVisible `, - :func:`viewRect `, - :func:`viewRange `, - :func:`setMouseEnabled `, - :func:`enableAutoRange `, - :func:`disableAutoRange `, - :func:`setAspectLocked `, - :func:`invertY `, - :func:`register `, - :func:`unregister ` - - The ViewBox itself can be accessed by calling :func:`getViewBox() ` - - ==================== ======================================================================= - **Signals** - sigYRangeChanged wrapped from :class:`ViewBox ` - sigXRangeChanged wrapped from :class:`ViewBox ` - sigRangeChanged wrapped from :class:`ViewBox ` - ==================== ======================================================================= - """ - - sigRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox range has changed - sigYRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox Y range has changed - sigXRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox X range has changed - - - lastFileDir = None - managers = {} - - def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs): - """ - Create a new PlotItem. All arguments are optional. - Any extra keyword arguments are passed to PlotItem.plot(). - - ============== ========================================================================================== - **Arguments** - *title* Title to display at the top of the item. Html is allowed. - *labels* A dictionary specifying the axis labels to display:: - - {'left': (args), 'bottom': (args), ...} - - The name of each axis and the corresponding arguments are passed to - :func:`PlotItem.setLabel() ` - Optionally, PlotItem my also be initialized with the keyword arguments left, - right, top, or bottom to achieve the same effect. - *name* Registers a name for this view so that others may link to it - *viewBox* If specified, the PlotItem will be constructed with this as its ViewBox. - *axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items - for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top') - and the values must be instances of AxisItem (or at least compatible with AxisItem). - ============== ========================================================================================== - """ - - GraphicsWidget.__init__(self, parent) - - self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - - ## Set up control buttons - path = os.path.dirname(__file__) - #self.autoImageFile = os.path.join(path, 'auto.png') - #self.lockImageFile = os.path.join(path, 'lock.png') - self.autoBtn = ButtonItem(pyqtgraph.pixmaps.getPixmap('auto'), 14, self) - self.autoBtn.mode = 'auto' - self.autoBtn.clicked.connect(self.autoBtnClicked) - #self.autoBtn.hide() - self.buttonsHidden = False ## whether the user has requested buttons to be hidden - self.mouseHovering = False - - self.layout = QtGui.QGraphicsGridLayout() - self.layout.setContentsMargins(1,1,1,1) - self.setLayout(self.layout) - self.layout.setHorizontalSpacing(0) - self.layout.setVerticalSpacing(0) - - if viewBox is None: - viewBox = ViewBox() - self.vb = viewBox - self.vb.sigStateChanged.connect(self.viewStateChanged) - self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus - - if name is not None: - self.vb.register(name) - self.vb.sigRangeChanged.connect(self.sigRangeChanged) - self.vb.sigXRangeChanged.connect(self.sigXRangeChanged) - self.vb.sigYRangeChanged.connect(self.sigYRangeChanged) - - self.layout.addItem(self.vb, 2, 1) - self.alpha = 1.0 - self.autoAlpha = True - self.spectrumMode = False - - self.legend = None - - ## Create and place axis items - if axisItems is None: - axisItems = {} - self.axes = {} - for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))): - axis = axisItems.get(k, AxisItem(orientation=k)) - axis.linkToView(self.vb) - self.axes[k] = {'item': axis, 'pos': pos} - self.layout.addItem(axis, *pos) - axis.setZValue(-1000) - axis.setFlag(axis.ItemNegativeZStacksBehindParent) - - self.titleLabel = LabelItem('', size='11pt') - self.layout.addItem(self.titleLabel, 0, 1) - self.setTitle(None) ## hide - - - for i in range(4): - self.layout.setRowPreferredHeight(i, 0) - self.layout.setRowMinimumHeight(i, 0) - self.layout.setRowSpacing(i, 0) - self.layout.setRowStretchFactor(i, 1) - - for i in range(3): - self.layout.setColumnPreferredWidth(i, 0) - self.layout.setColumnMinimumWidth(i, 0) - self.layout.setColumnSpacing(i, 0) - self.layout.setColumnStretchFactor(i, 1) - self.layout.setRowStretchFactor(2, 100) - self.layout.setColumnStretchFactor(1, 100) - - - ## Wrap a few methods from viewBox - for m in [ - 'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible', - 'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled', - 'enableAutoRange', 'disableAutoRange', 'setAspectLocked', 'invertY', - 'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well. - setattr(self, m, getattr(self.vb, m)) - - self.items = [] - self.curves = [] - self.itemMeta = weakref.WeakKeyDictionary() - self.dataItems = [] - self.paramList = {} - self.avgCurves = {} - - ### Set up context menu - - w = QtGui.QWidget() - self.ctrl = c = Ui_Form() - c.setupUi(w) - dv = QtGui.QDoubleValidator(self) - - menuItems = [ - ('Transforms', c.transformGroup), - ('Downsample', c.decimateGroup), - ('Average', c.averageGroup), - ('Alpha', c.alphaGroup), - ('Grid', c.gridGroup), - ('Points', c.pointsGroup), - ] - - - self.ctrlMenu = QtGui.QMenu() - - self.ctrlMenu.setTitle('Plot Options') - self.subMenus = [] - for name, grp in menuItems: - sm = QtGui.QMenu(name) - act = QtGui.QWidgetAction(self) - act.setDefaultWidget(grp) - sm.addAction(act) - self.subMenus.append(sm) - self.ctrlMenu.addMenu(sm) - - self.stateGroup = WidgetGroup() - for name, w in menuItems: - self.stateGroup.autoAdd(w) - - self.fileDialog = None - - c.alphaGroup.toggled.connect(self.updateAlpha) - c.alphaSlider.valueChanged.connect(self.updateAlpha) - c.autoAlphaCheck.toggled.connect(self.updateAlpha) - - c.xGridCheck.toggled.connect(self.updateGrid) - c.yGridCheck.toggled.connect(self.updateGrid) - c.gridAlphaSlider.valueChanged.connect(self.updateGrid) - - c.fftCheck.toggled.connect(self.updateSpectrumMode) - c.logXCheck.toggled.connect(self.updateLogMode) - c.logYCheck.toggled.connect(self.updateLogMode) - - c.downsampleSpin.valueChanged.connect(self.updateDownsampling) - c.downsampleCheck.toggled.connect(self.updateDownsampling) - c.autoDownsampleCheck.toggled.connect(self.updateDownsampling) - c.subsampleRadio.toggled.connect(self.updateDownsampling) - c.meanRadio.toggled.connect(self.updateDownsampling) - c.clipToViewCheck.toggled.connect(self.updateDownsampling) - - self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked) - self.ctrl.averageGroup.toggled.connect(self.avgToggled) - - self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation) - self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation) - - self.hideAxis('right') - self.hideAxis('top') - self.showAxis('left') - self.showAxis('bottom') - - if labels is None: - labels = {} - for label in list(self.axes.keys()): - if label in kargs: - labels[label] = kargs[label] - del kargs[label] - for k in labels: - if isinstance(labels[k], basestring): - labels[k] = (labels[k],) - self.setLabel(k, *labels[k]) - - if title is not None: - self.setTitle(title) - - if len(kargs) > 0: - self.plot(**kargs) - - - def implements(self, interface=None): - return interface in ['ViewBoxWrapper'] - - def getViewBox(self): - """Return the :class:`ViewBox ` contained within.""" - return self.vb - - - - def setLogMode(self, x=None, y=None): - """ - Set log scaling for x and/or y axes. - This informs PlotDataItems to transform logarithmically and switches - the axes to use log ticking. - - Note that *no other items* in the scene will be affected by - this; there is (currently) no generic way to redisplay a GraphicsItem - with log coordinates. - - """ - if x is not None: - self.ctrl.logXCheck.setChecked(x) - if y is not None: - self.ctrl.logYCheck.setChecked(y) - - def showGrid(self, x=None, y=None, alpha=None): - """ - Show or hide the grid for either axis. - - ============== ===================================== - **Arguments:** - x (bool) Whether to show the X grid - y (bool) Whether to show the Y grid - alpha (0.0-1.0) Opacity of the grid - ============== ===================================== - """ - if x is None and y is None and alpha is None: - raise Exception("Must specify at least one of x, y, or alpha.") ## prevent people getting confused if they just call showGrid() - - if x is not None: - self.ctrl.xGridCheck.setChecked(x) - if y is not None: - self.ctrl.yGridCheck.setChecked(y) - if alpha is not None: - v = np.clip(alpha, 0, 1)*self.ctrl.gridAlphaSlider.maximum() - self.ctrl.gridAlphaSlider.setValue(v) - - #def paint(self, *args): - #prof = debug.Profiler('PlotItem.paint', disabled=True) - #QtGui.QGraphicsWidget.paint(self, *args) - #prof.finish() - - ## bad idea. - #def __getattr__(self, attr): ## wrap ms - #return getattr(self.vb, attr) - - def close(self): - #print "delete", self - ## Most of this crap is needed to avoid PySide trouble. - ## The problem seems to be whenever scene.clear() leads to deletion of widgets (either through proxies or qgraphicswidgets) - ## the solution is to manually remove all widgets before scene.clear() is called - if self.ctrlMenu is None: ## already shut down - return - self.ctrlMenu.setParent(None) - self.ctrlMenu = None - - #self.ctrlBtn.setParent(None) - #self.ctrlBtn = None - #self.autoBtn.setParent(None) - #self.autoBtn = None - - for k in self.axes: - i = self.axes[k]['item'] - i.close() - - self.axes = None - self.scene().removeItem(self.vb) - self.vb = None - - ## causes invalid index errors: - #for i in range(self.layout.count()): - #self.layout.removeAt(i) - - #for p in self.proxies: - #try: - #p.setWidget(None) - #except RuntimeError: - #break - #self.scene().removeItem(p) - #self.proxies = [] - - #self.menuAction.releaseWidget(self.menuAction.defaultWidget()) - #self.menuAction.setParent(None) - #self.menuAction = None - - #if self.manager is not None: - #self.manager.sigWidgetListChanged.disconnect(self.updatePlotList) - #self.manager.removeWidget(self.name) - #else: - #print "no manager" - - def registerPlot(self, name): ## for backward compatibility - self.vb.register(name) - - def updateGrid(self, *args): - alpha = self.ctrl.gridAlphaSlider.value() - x = alpha if self.ctrl.xGridCheck.isChecked() else False - y = alpha if self.ctrl.yGridCheck.isChecked() else False - self.getAxis('top').setGrid(x) - self.getAxis('bottom').setGrid(x) - self.getAxis('left').setGrid(y) - self.getAxis('right').setGrid(y) - - def viewGeometry(self): - """Return the screen geometry of the viewbox""" - v = self.scene().views()[0] - b = self.vb.mapRectToScene(self.vb.boundingRect()) - wr = v.mapFromScene(b).boundingRect() - pos = v.mapToGlobal(v.pos()) - wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) - return wr - - - def avgToggled(self, b): - if b: - self.recomputeAverages() - for k in self.avgCurves: - self.avgCurves[k][1].setVisible(b) - - def avgParamListClicked(self, item): - name = str(item.text()) - self.paramList[name] = (item.checkState() == QtCore.Qt.Checked) - self.recomputeAverages() - - def recomputeAverages(self): - if not self.ctrl.averageGroup.isChecked(): - return - for k in self.avgCurves: - self.removeItem(self.avgCurves[k][1]) - self.avgCurves = {} - for c in self.curves: - self.addAvgCurve(c) - self.replot() - - def addAvgCurve(self, curve): - ## Add a single curve into the pool of curves averaged together - - ## If there are plot parameters, then we need to determine which to average together. - remKeys = [] - addKeys = [] - if self.ctrl.avgParamList.count() > 0: - - ### First determine the key of the curve to which this new data should be averaged - for i in range(self.ctrl.avgParamList.count()): - item = self.ctrl.avgParamList.item(i) - if item.checkState() == QtCore.Qt.Checked: - remKeys.append(str(item.text())) - else: - addKeys.append(str(item.text())) - - if len(remKeys) < 1: ## In this case, there would be 1 average plot for each data plot; not useful. - return - - p = self.itemMeta.get(curve,{}).copy() - for k in p: - if type(k) is tuple: - p['.'.join(k)] = p[k] - del p[k] - for rk in remKeys: - if rk in p: - del p[rk] - for ak in addKeys: - if ak not in p: - p[ak] = None - key = tuple(p.items()) - - ### Create a new curve if needed - if key not in self.avgCurves: - plot = PlotDataItem() - plot.setPen(fn.mkPen([0, 200, 0])) - plot.setShadowPen(fn.mkPen([0, 0, 0, 100], width=3)) - plot.setAlpha(1.0, False) - plot.setZValue(100) - self.addItem(plot, skipAverage=True) - self.avgCurves[key] = [0, plot] - self.avgCurves[key][0] += 1 - (n, plot) = self.avgCurves[key] - - ### Average data together - (x, y) = curve.getData() - if plot.yData is not None: - newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n) - plot.setData(plot.xData, newData) - else: - plot.setData(x, y) - - def autoBtnClicked(self): - if self.autoBtn.mode == 'auto': - self.enableAutoRange() - self.autoBtn.hide() - else: - self.disableAutoRange() - - def viewStateChanged(self): - self.updateButtons() - - def enableAutoScale(self): - """ - Enable auto-scaling. The plot will continuously scale to fit the boundaries of its data. - """ - print("Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead.") - self.vb.enableAutoRange(self.vb.XYAxes) - - def addItem(self, item, *args, **kargs): - """ - Add a graphics item to the view box. - If the item has plot data (PlotDataItem, PlotCurveItem, ScatterPlotItem), it may - be included in analysis performed by the PlotItem. - """ - self.items.append(item) - vbargs = {} - if 'ignoreBounds' in kargs: - vbargs['ignoreBounds'] = kargs['ignoreBounds'] - self.vb.addItem(item, *args, **vbargs) - if hasattr(item, 'implements') and item.implements('plotData'): - self.dataItems.append(item) - #self.plotChanged() - - params = kargs.get('params', {}) - self.itemMeta[item] = params - #item.setMeta(params) - self.curves.append(item) - #self.addItem(c) - - if hasattr(item, 'setLogMode'): - item.setLogMode(self.ctrl.logXCheck.isChecked(), self.ctrl.logYCheck.isChecked()) - - if isinstance(item, PlotDataItem): - ## configure curve for this plot - (alpha, auto) = self.alphaState() - item.setAlpha(alpha, auto) - item.setFftMode(self.ctrl.fftCheck.isChecked()) - item.setDownsampling(*self.downsampleMode()) - item.setClipToView(self.clipToViewMode()) - item.setPointMode(self.pointMode()) - - ## Hide older plots if needed - self.updateDecimation() - - ## Add to average if needed - self.updateParamList() - if self.ctrl.averageGroup.isChecked() and 'skipAverage' not in kargs: - self.addAvgCurve(item) - - #c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged) - #item.sigPlotChanged.connect(self.plotChanged) - #self.plotChanged() - name = kargs.get('name', getattr(item, 'opts', {}).get('name', None)) - if name is not None and hasattr(self, 'legend') and self.legend is not None: - self.legend.addItem(item, name=name) - - - def addDataItem(self, item, *args): - print("PlotItem.addDataItem is deprecated. Use addItem instead.") - self.addItem(item, *args) - - def listDataItems(self): - """Return a list of all data items (PlotDataItem, PlotCurveItem, ScatterPlotItem, etc) - contained in this PlotItem.""" - return self.dataItems[:] - - def addCurve(self, c, params=None): - print("PlotItem.addCurve is deprecated. Use addItem instead.") - self.addItem(c, params) - - def addLine(self, x=None, y=None, z=None, **kwds): - """ - Create an InfiniteLine and add to the plot. - - If *x* is specified, - the line will be vertical. If *y* is specified, the line will be - horizontal. All extra keyword arguments are passed to - :func:`InfiniteLine.__init__() `. - Returns the item created. - """ - pos = kwds.get('pos', x if x is not None else y) - angle = kwds.get('angle', 0 if x is None else 90) - line = InfiniteLine(pos, angle, **kwds) - self.addItem(line) - if z is not None: - line.setZValue(z) - return line - - - - def removeItem(self, item): - """ - Remove an item from the internal ViewBox. - """ - if not item in self.items: - return - self.items.remove(item) - if item in self.dataItems: - self.dataItems.remove(item) - - if item.scene() is not None: - self.vb.removeItem(item) - if item in self.curves: - self.curves.remove(item) - self.updateDecimation() - self.updateParamList() - #item.connect(item, QtCore.SIGNAL('plotChanged'), self.plotChanged) - #item.sigPlotChanged.connect(self.plotChanged) - - def clear(self): - """ - Remove all items from the ViewBox. - """ - for i in self.items[:]: - self.removeItem(i) - self.avgCurves = {} - - def clearPlots(self): - for i in self.curves[:]: - self.removeItem(i) - self.avgCurves = {} - - - def plot(self, *args, **kargs): - """ - Add and return a new plot. - See :func:`PlotDataItem.__init__ ` for data arguments - - Extra allowed arguments are: - clear - clear all plots before displaying new data - params - meta-parameters to associate with this data - """ - - - clear = kargs.get('clear', False) - params = kargs.get('params', None) - - if clear: - self.clear() - - item = PlotDataItem(*args, **kargs) - - if params is None: - params = {} - self.addItem(item, params=params) - - return item - - def addLegend(self, size=None, offset=(30, 30)): - """ - Create a new LegendItem and anchor it over the internal ViewBox. - Plots will be automatically displayed in the legend if they - are created with the 'name' argument. - """ - self.legend = LegendItem(size, offset) - self.legend.setParentItem(self.vb) - return self.legend - - def scatterPlot(self, *args, **kargs): - if 'pen' in kargs: - kargs['symbolPen'] = kargs['pen'] - kargs['pen'] = None - - if 'brush' in kargs: - kargs['symbolBrush'] = kargs['brush'] - del kargs['brush'] - - if 'size' in kargs: - kargs['symbolSize'] = kargs['size'] - del kargs['size'] - - return self.plot(*args, **kargs) - - def replot(self): - self.update() - - def updateParamList(self): - self.ctrl.avgParamList.clear() - ## Check to see that each parameter for each curve is present in the list - for c in self.curves: - for p in list(self.itemMeta.get(c, {}).keys()): - if type(p) is tuple: - p = '.'.join(p) - - ## If the parameter is not in the list, add it. - matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly) - if len(matches) == 0: - i = QtGui.QListWidgetItem(p) - if p in self.paramList and self.paramList[p] is True: - i.setCheckState(QtCore.Qt.Checked) - else: - i.setCheckState(QtCore.Qt.Unchecked) - self.ctrl.avgParamList.addItem(i) - else: - i = matches[0] - - self.paramList[p] = (i.checkState() == QtCore.Qt.Checked) - - - ## Qt's SVG-writing capabilities are pretty terrible. - def writeSvgCurves(self, fileName=None): - if fileName is None: - self.fileDialog = FileDialog() - if PlotItem.lastFileDir is not None: - self.fileDialog.setDirectory(PlotItem.lastFileDir) - self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) - self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) - self.fileDialog.show() - self.fileDialog.fileSelected.connect(self.writeSvg) - return - #if fileName is None: - #fileName = QtGui.QFileDialog.getSaveFileName() - if isinstance(fileName, tuple): - raise Exception("Not implemented yet..") - fileName = str(fileName) - PlotItem.lastFileDir = os.path.dirname(fileName) - - rect = self.vb.viewRect() - xRange = rect.left(), rect.right() - - svg = "" - fh = open(fileName, 'w') - - dx = max(rect.right(),0) - min(rect.left(),0) - ymn = min(rect.top(), rect.bottom()) - ymx = max(rect.top(), rect.bottom()) - dy = max(ymx,0) - min(ymn,0) - sx = 1. - sy = 1. - while dx*sx < 10: - sx *= 1000 - while dy*sy < 10: - sy *= 1000 - sy *= -1 - - #fh.write('\n' % (rect.left()*sx, rect.top()*sx, rect.width()*sy, rect.height()*sy)) - fh.write('\n') - fh.write('\n' % (rect.left()*sx, rect.right()*sx)) - fh.write('\n' % (rect.top()*sy, rect.bottom()*sy)) - - - for item in self.curves: - if isinstance(item, PlotCurveItem): - color = fn.colorStr(item.pen.color()) - opacity = item.pen.color().alpha() / 255. - color = color[:6] - x, y = item.getData() - mask = (x > xRange[0]) * (x < xRange[1]) - mask[:-1] += mask[1:] - m2 = mask.copy() - mask[1:] += m2[:-1] - x = x[mask] - y = y[mask] - - x *= sx - y *= sy - - #fh.write('\n' % color) - fh.write('') - #fh.write("") - for item in self.dataItems: - if isinstance(item, ScatterPlotItem): - - pRect = item.boundingRect() - vRect = pRect.intersected(rect) - - for point in item.points(): - pos = point.pos() - if not rect.contains(pos): - continue - color = fn.colorStr(point.brush.color()) - opacity = point.brush.color().alpha() / 255. - color = color[:6] - x = pos.x() * sx - y = pos.y() * sy - - fh.write('\n' % (x, y, color, opacity)) - #fh.write('') - - ## get list of curves, scatter plots - - - fh.write("\n") - - - - def writeSvg(self, fileName=None): - if fileName is None: - fileName = QtGui.QFileDialog.getSaveFileName() - fileName = str(fileName) - PlotItem.lastFileDir = os.path.dirname(fileName) - - self.svg = QtSvg.QSvgGenerator() - self.svg.setFileName(fileName) - res = 120. - view = self.scene().views()[0] - bounds = view.viewport().rect() - bounds = QtCore.QRectF(0, 0, bounds.width(), bounds.height()) - - self.svg.setResolution(res) - self.svg.setViewBox(bounds) - - self.svg.setSize(QtCore.QSize(bounds.width(), bounds.height())) - - painter = QtGui.QPainter(self.svg) - view.render(painter, bounds) - - painter.end() - - ## Workaround to set pen widths correctly - import re - data = open(fileName).readlines() - for i in range(len(data)): - line = data[i] - m = re.match(r'(= split: - curves[i].show() - else: - if self.ctrl.forgetTracesCheck.isChecked(): - curves[i].clear() - self.removeItem(curves[i]) - else: - curves[i].hide() - - - def updateAlpha(self, *args): - (alpha, auto) = self.alphaState() - for c in self.curves: - c.setAlpha(alpha**2, auto) - - def alphaState(self): - enabled = self.ctrl.alphaGroup.isChecked() - auto = self.ctrl.autoAlphaCheck.isChecked() - alpha = float(self.ctrl.alphaSlider.value()) / self.ctrl.alphaSlider.maximum() - if auto: - alpha = 1.0 ## should be 1/number of overlapping plots - if not enabled: - auto = False - alpha = 1.0 - return (alpha, auto) - - def pointMode(self): - if self.ctrl.pointsGroup.isChecked(): - if self.ctrl.autoPointsCheck.isChecked(): - mode = None - else: - mode = True - else: - mode = False - return mode - - - def resizeEvent(self, ev): - if self.autoBtn is None: ## already closed down - return - btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect()) - y = self.size().height() - btnRect.height() - self.autoBtn.setPos(0, y) - - - def getMenu(self): - return self.ctrlMenu - - def getContextMenus(self, event): - ## called when another item is displaying its context menu; we get to add extras to the end of the menu. - if self.menuEnabled(): - return self.ctrlMenu - else: - return None - - def setMenuEnabled(self, enableMenu=True, enableViewBoxMenu='same'): - """ - Enable or disable the context menu for this PlotItem. - By default, the ViewBox's context menu will also be affected. - (use enableViewBoxMenu=None to leave the ViewBox unchanged) - """ - self._menuEnabled = enableMenu - if enableViewBoxMenu is None: - return - if enableViewBoxMenu is 'same': - enableViewBoxMenu = enableMenu - self.vb.setMenuEnabled(enableViewBoxMenu) - - def menuEnabled(self): - return self._menuEnabled - - def hoverEvent(self, ev): - if ev.enter: - self.mouseHovering = True - if ev.exit: - self.mouseHovering = False - - self.updateButtons() - - - def getLabel(self, key): - pass - - def _checkScaleKey(self, key): - if key not in self.axes: - raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.axes.keys())))) - - def getScale(self, key): - return self.getAxis(key) - - def getAxis(self, name): - """Return the specified AxisItem. - *name* should be 'left', 'bottom', 'top', or 'right'.""" - self._checkScaleKey(name) - return self.axes[name]['item'] - - def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args): - """ - Set the label for an axis. Basic HTML formatting is allowed. - - ============= ================================================================= - **Arguments** - axis must be one of 'left', 'bottom', 'right', or 'top' - text text to display along the axis. HTML allowed. - units units to display after the title. If units are given, - then an SI prefix will be automatically appended - and the axis values will be scaled accordingly. - (ie, use 'V' instead of 'mV'; 'm' will be added automatically) - ============= ================================================================= - """ - self.getAxis(axis).setLabel(text=text, units=units, **args) - self.showAxis(axis) - - def setLabels(self, **kwds): - """ - Convenience function allowing multiple labels and/or title to be set in one call. - Keyword arguments can be 'title', 'left', 'bottom', 'right', or 'top'. - Values may be strings or a tuple of arguments to pass to setLabel. - """ - for k,v in kwds.items(): - if k == 'title': - self.setTitle(v) - else: - if isinstance(v, basestring): - v = (v,) - self.setLabel(k, *v) - - - def showLabel(self, axis, show=True): - """ - Show or hide one of the plot's axis labels (the axis itself will be unaffected). - axis must be one of 'left', 'bottom', 'right', or 'top' - """ - self.getScale(axis).showLabel(show) - - def setTitle(self, title=None, **args): - """ - Set the title of the plot. Basic HTML formatting is allowed. - If title is None, then the title will be hidden. - """ - if title is None: - self.titleLabel.setVisible(False) - self.layout.setRowFixedHeight(0, 0) - self.titleLabel.setMaximumHeight(0) - else: - self.titleLabel.setMaximumHeight(30) - self.layout.setRowFixedHeight(0, 30) - self.titleLabel.setVisible(True) - self.titleLabel.setText(title, **args) - - def showAxis(self, axis, show=True): - """ - Show or hide one of the plot's axes. - axis must be one of 'left', 'bottom', 'right', or 'top' - """ - s = self.getScale(axis) - p = self.axes[axis]['pos'] - if show: - s.show() - else: - s.hide() - - def hideAxis(self, axis): - """Hide one of the PlotItem's axes. ('left', 'bottom', 'right', or 'top')""" - self.showAxis(axis, False) - - def showScale(self, *args, **kargs): - print("Deprecated. use showAxis() instead") - return self.showAxis(*args, **kargs) - - def hideButtons(self): - """Causes auto-scale button ('A' in lower-left corner) to be hidden for this PlotItem""" - #self.ctrlBtn.hide() - self.buttonsHidden = True - self.updateButtons() - - def showButtons(self): - """Causes auto-scale button ('A' in lower-left corner) to be visible for this PlotItem""" - #self.ctrlBtn.hide() - self.buttonsHidden = False - self.updateButtons() - - def updateButtons(self): - if self._exportOpts is False and self.mouseHovering and not self.buttonsHidden and not all(self.vb.autoRangeEnabled()): - self.autoBtn.show() - else: - self.autoBtn.hide() - - def _plotArray(self, arr, x=None, **kargs): - if arr.ndim != 1: - raise Exception("Array must be 1D to plot (shape is %s)" % arr.shape) - if x is None: - x = np.arange(arr.shape[0]) - if x.ndim != 1: - raise Exception("X array must be 1D to plot (shape is %s)" % x.shape) - c = PlotCurveItem(arr, x=x, **kargs) - return c - - - - def _plotMetaArray(self, arr, x=None, autoLabel=True, **kargs): - inf = arr.infoCopy() - if arr.ndim != 1: - raise Exception('can only automatically plot 1 dimensional arrays.') - ## create curve - try: - xv = arr.xvals(0) - except: - if x is None: - xv = np.arange(arr.shape[0]) - else: - xv = x - c = PlotCurveItem(**kargs) - c.setData(x=xv, y=arr.view(np.ndarray)) - - if autoLabel: - name = arr._info[0].get('name', None) - units = arr._info[0].get('units', None) - self.setLabel('bottom', text=name, units=units) - - name = arr._info[1].get('name', None) - units = arr._info[1].get('units', None) - self.setLabel('left', text=name, units=units) - - return c - - - def setExportMode(self, export, opts=None): - GraphicsWidget.setExportMode(self, export, opts) - self.updateButtons() - #if export: - #self.autoBtn.hide() - #else: - #self.autoBtn.show() - diff --git a/pyqtgraph/graphicsItems/PlotItem/__init__.py b/pyqtgraph/graphicsItems/PlotItem/__init__.py deleted file mode 100644 index d797978c..00000000 --- a/pyqtgraph/graphicsItems/PlotItem/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .PlotItem import PlotItem diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui deleted file mode 100644 index dffc62d0..00000000 --- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui +++ /dev/null @@ -1,343 +0,0 @@ - - - Form - - - - 0 - 0 - 481 - 840 - - - - Form - - - - - 0 - 640 - 242 - 182 - - - - Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available). - - - Average - - - true - - - false - - - - 0 - - - 0 - - - - - - - - - - 10 - 140 - 191 - 171 - - - - - 0 - - - 0 - - - - - Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced. - - - Clip to View - - - - - - - If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed. - - - Max Traces: - - - - - - - Downsample - - - - - - - Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower. - - - Peak - - - true - - - - - - - If multiple curves are displayed in this plot, check "Max Traces" and set this value to limit the number of traces that are displayed. - - - - - - - If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden). - - - Forget hidden traces - - - - - - - Downsample by taking the mean of N samples. - - - Mean - - - - - - - Downsample by taking the first of N samples. This method is fastest and least accurate. - - - Subsample - - - - - - - Automatically downsample data based on the visible range. This assumes X values are uniformly spaced. - - - Auto - - - true - - - - - - - Qt::Horizontal - - - QSizePolicy::Maximum - - - - 30 - 20 - - - - - - - - Downsample data before plotting. (plot every Nth sample) - - - x - - - 1 - - - 100000 - - - 1 - - - - - - - - - 0 - 0 - 154 - 79 - - - - - - - Power Spectrum (FFT) - - - - - - - Log X - - - - - - - Log Y - - - - - - - - - 10 - 550 - 234 - 58 - - - - Points - - - true - - - - - - Auto - - - true - - - - - - - - - 10 - 460 - 221 - 81 - - - - - - - Show X Grid - - - - - - - Show Y Grid - - - - - - - 255 - - - 128 - - - Qt::Horizontal - - - - - - - Opacity - - - - - - - - - 10 - 390 - 234 - 60 - - - - Alpha - - - true - - - - - - Auto - - - false - - - - - - - 1000 - - - 1000 - - - Qt::Horizontal - - - - - - - - - diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py deleted file mode 100644 index 5335ee76..00000000 --- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py +++ /dev/null @@ -1,173 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui' -# -# Created: Mon Jul 1 23:21:08 2013 -# by: PyQt4 UI code generator 4.9.3 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName(_fromUtf8("Form")) - Form.resize(481, 840) - self.averageGroup = QtGui.QGroupBox(Form) - self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) - self.averageGroup.setCheckable(True) - self.averageGroup.setChecked(False) - self.averageGroup.setObjectName(_fromUtf8("averageGroup")) - self.gridLayout_5 = QtGui.QGridLayout(self.averageGroup) - self.gridLayout_5.setMargin(0) - self.gridLayout_5.setSpacing(0) - self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) - self.avgParamList = QtGui.QListWidget(self.averageGroup) - self.avgParamList.setObjectName(_fromUtf8("avgParamList")) - self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) - self.decimateGroup = QtGui.QFrame(Form) - self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) - self.decimateGroup.setObjectName(_fromUtf8("decimateGroup")) - self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) - self.gridLayout_4.setMargin(0) - self.gridLayout_4.setSpacing(0) - self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) - self.clipToViewCheck = QtGui.QCheckBox(self.decimateGroup) - self.clipToViewCheck.setObjectName(_fromUtf8("clipToViewCheck")) - self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) - self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup) - self.maxTracesCheck.setObjectName(_fromUtf8("maxTracesCheck")) - self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) - self.downsampleCheck = QtGui.QCheckBox(self.decimateGroup) - self.downsampleCheck.setObjectName(_fromUtf8("downsampleCheck")) - self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) - self.peakRadio = QtGui.QRadioButton(self.decimateGroup) - self.peakRadio.setChecked(True) - self.peakRadio.setObjectName(_fromUtf8("peakRadio")) - self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) - self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup) - self.maxTracesSpin.setObjectName(_fromUtf8("maxTracesSpin")) - self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) - self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup) - self.forgetTracesCheck.setObjectName(_fromUtf8("forgetTracesCheck")) - self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) - self.meanRadio = QtGui.QRadioButton(self.decimateGroup) - self.meanRadio.setObjectName(_fromUtf8("meanRadio")) - self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) - self.subsampleRadio = QtGui.QRadioButton(self.decimateGroup) - self.subsampleRadio.setObjectName(_fromUtf8("subsampleRadio")) - self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) - self.autoDownsampleCheck = QtGui.QCheckBox(self.decimateGroup) - self.autoDownsampleCheck.setChecked(True) - self.autoDownsampleCheck.setObjectName(_fromUtf8("autoDownsampleCheck")) - self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) - spacerItem = QtGui.QSpacerItem(30, 20, QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Minimum) - self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) - self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) - self.downsampleSpin.setMinimum(1) - self.downsampleSpin.setMaximum(100000) - self.downsampleSpin.setProperty("value", 1) - self.downsampleSpin.setObjectName(_fromUtf8("downsampleSpin")) - self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) - self.transformGroup = QtGui.QFrame(Form) - self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79)) - self.transformGroup.setObjectName(_fromUtf8("transformGroup")) - self.gridLayout = QtGui.QGridLayout(self.transformGroup) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.fftCheck = QtGui.QCheckBox(self.transformGroup) - self.fftCheck.setObjectName(_fromUtf8("fftCheck")) - self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) - self.logXCheck = QtGui.QCheckBox(self.transformGroup) - self.logXCheck.setObjectName(_fromUtf8("logXCheck")) - self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) - self.logYCheck = QtGui.QCheckBox(self.transformGroup) - self.logYCheck.setObjectName(_fromUtf8("logYCheck")) - self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) - self.pointsGroup = QtGui.QGroupBox(Form) - self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) - self.pointsGroup.setCheckable(True) - self.pointsGroup.setObjectName(_fromUtf8("pointsGroup")) - self.verticalLayout_5 = QtGui.QVBoxLayout(self.pointsGroup) - self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5")) - self.autoPointsCheck = QtGui.QCheckBox(self.pointsGroup) - self.autoPointsCheck.setChecked(True) - self.autoPointsCheck.setObjectName(_fromUtf8("autoPointsCheck")) - self.verticalLayout_5.addWidget(self.autoPointsCheck) - self.gridGroup = QtGui.QFrame(Form) - self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) - self.gridGroup.setObjectName(_fromUtf8("gridGroup")) - self.gridLayout_2 = QtGui.QGridLayout(self.gridGroup) - self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.xGridCheck = QtGui.QCheckBox(self.gridGroup) - self.xGridCheck.setObjectName(_fromUtf8("xGridCheck")) - self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) - self.yGridCheck = QtGui.QCheckBox(self.gridGroup) - self.yGridCheck.setObjectName(_fromUtf8("yGridCheck")) - self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) - self.gridAlphaSlider = QtGui.QSlider(self.gridGroup) - self.gridAlphaSlider.setMaximum(255) - self.gridAlphaSlider.setProperty("value", 128) - self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.gridAlphaSlider.setObjectName(_fromUtf8("gridAlphaSlider")) - self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) - self.label = QtGui.QLabel(self.gridGroup) - self.label.setObjectName(_fromUtf8("label")) - self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) - self.alphaGroup = QtGui.QGroupBox(Form) - self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) - self.alphaGroup.setCheckable(True) - self.alphaGroup.setObjectName(_fromUtf8("alphaGroup")) - self.horizontalLayout = QtGui.QHBoxLayout(self.alphaGroup) - self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) - self.autoAlphaCheck = QtGui.QCheckBox(self.alphaGroup) - self.autoAlphaCheck.setChecked(False) - self.autoAlphaCheck.setObjectName(_fromUtf8("autoAlphaCheck")) - self.horizontalLayout.addWidget(self.autoAlphaCheck) - self.alphaSlider = QtGui.QSlider(self.alphaGroup) - self.alphaSlider.setMaximum(1000) - self.alphaSlider.setProperty("value", 1000) - self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.alphaSlider.setObjectName(_fromUtf8("alphaSlider")) - self.horizontalLayout.addWidget(self.alphaSlider) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8)) - self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8)) - self.clipToViewCheck.setToolTip(QtGui.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) - self.clipToViewCheck.setText(QtGui.QApplication.translate("Form", "Clip to View", None, QtGui.QApplication.UnicodeUTF8)) - self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) - self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8)) - self.downsampleCheck.setText(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8)) - self.peakRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None, QtGui.QApplication.UnicodeUTF8)) - self.peakRadio.setText(QtGui.QApplication.translate("Form", "Peak", None, QtGui.QApplication.UnicodeUTF8)) - self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) - self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8)) - self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8)) - self.meanRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the mean of N samples.", None, QtGui.QApplication.UnicodeUTF8)) - self.meanRadio.setText(QtGui.QApplication.translate("Form", "Mean", None, QtGui.QApplication.UnicodeUTF8)) - self.subsampleRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None, QtGui.QApplication.UnicodeUTF8)) - self.subsampleRadio.setText(QtGui.QApplication.translate("Form", "Subsample", None, QtGui.QApplication.UnicodeUTF8)) - self.autoDownsampleCheck.setToolTip(QtGui.QApplication.translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) - self.autoDownsampleCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.downsampleSpin.setToolTip(QtGui.QApplication.translate("Form", "Downsample data before plotting. (plot every Nth sample)", None, QtGui.QApplication.UnicodeUTF8)) - self.downsampleSpin.setSuffix(QtGui.QApplication.translate("Form", "x", None, QtGui.QApplication.UnicodeUTF8)) - self.fftCheck.setText(QtGui.QApplication.translate("Form", "Power Spectrum (FFT)", None, QtGui.QApplication.UnicodeUTF8)) - self.logXCheck.setText(QtGui.QApplication.translate("Form", "Log X", None, QtGui.QApplication.UnicodeUTF8)) - self.logYCheck.setText(QtGui.QApplication.translate("Form", "Log Y", None, QtGui.QApplication.UnicodeUTF8)) - self.pointsGroup.setTitle(QtGui.QApplication.translate("Form", "Points", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPointsCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.xGridCheck.setText(QtGui.QApplication.translate("Form", "Show X Grid", None, QtGui.QApplication.UnicodeUTF8)) - self.yGridCheck.setText(QtGui.QApplication.translate("Form", "Show Y Grid", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("Form", "Opacity", None, QtGui.QApplication.UnicodeUTF8)) - self.alphaGroup.setTitle(QtGui.QApplication.translate("Form", "Alpha", None, QtGui.QApplication.UnicodeUTF8)) - self.autoAlphaCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py b/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py deleted file mode 100644 index b8e0b19e..00000000 --- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py +++ /dev/null @@ -1,168 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui' -# -# Created: Mon Jul 1 23:21:08 2013 -# by: pyside-uic 0.2.13 running on PySide 1.1.2 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(481, 840) - self.averageGroup = QtGui.QGroupBox(Form) - self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) - self.averageGroup.setCheckable(True) - self.averageGroup.setChecked(False) - self.averageGroup.setObjectName("averageGroup") - self.gridLayout_5 = QtGui.QGridLayout(self.averageGroup) - self.gridLayout_5.setContentsMargins(0, 0, 0, 0) - self.gridLayout_5.setSpacing(0) - self.gridLayout_5.setObjectName("gridLayout_5") - self.avgParamList = QtGui.QListWidget(self.averageGroup) - self.avgParamList.setObjectName("avgParamList") - self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) - self.decimateGroup = QtGui.QFrame(Form) - self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) - self.decimateGroup.setObjectName("decimateGroup") - self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) - self.gridLayout_4.setContentsMargins(0, 0, 0, 0) - self.gridLayout_4.setSpacing(0) - self.gridLayout_4.setObjectName("gridLayout_4") - self.clipToViewCheck = QtGui.QCheckBox(self.decimateGroup) - self.clipToViewCheck.setObjectName("clipToViewCheck") - self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) - self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup) - self.maxTracesCheck.setObjectName("maxTracesCheck") - self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) - self.downsampleCheck = QtGui.QCheckBox(self.decimateGroup) - self.downsampleCheck.setObjectName("downsampleCheck") - self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) - self.peakRadio = QtGui.QRadioButton(self.decimateGroup) - self.peakRadio.setChecked(True) - self.peakRadio.setObjectName("peakRadio") - self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) - self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup) - self.maxTracesSpin.setObjectName("maxTracesSpin") - self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) - self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup) - self.forgetTracesCheck.setObjectName("forgetTracesCheck") - self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) - self.meanRadio = QtGui.QRadioButton(self.decimateGroup) - self.meanRadio.setObjectName("meanRadio") - self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) - self.subsampleRadio = QtGui.QRadioButton(self.decimateGroup) - self.subsampleRadio.setObjectName("subsampleRadio") - self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) - self.autoDownsampleCheck = QtGui.QCheckBox(self.decimateGroup) - self.autoDownsampleCheck.setChecked(True) - self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") - self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) - spacerItem = QtGui.QSpacerItem(30, 20, QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Minimum) - self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) - self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) - self.downsampleSpin.setMinimum(1) - self.downsampleSpin.setMaximum(100000) - self.downsampleSpin.setProperty("value", 1) - self.downsampleSpin.setObjectName("downsampleSpin") - self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) - self.transformGroup = QtGui.QFrame(Form) - self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79)) - self.transformGroup.setObjectName("transformGroup") - self.gridLayout = QtGui.QGridLayout(self.transformGroup) - self.gridLayout.setObjectName("gridLayout") - self.fftCheck = QtGui.QCheckBox(self.transformGroup) - self.fftCheck.setObjectName("fftCheck") - self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) - self.logXCheck = QtGui.QCheckBox(self.transformGroup) - self.logXCheck.setObjectName("logXCheck") - self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) - self.logYCheck = QtGui.QCheckBox(self.transformGroup) - self.logYCheck.setObjectName("logYCheck") - self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) - self.pointsGroup = QtGui.QGroupBox(Form) - self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) - self.pointsGroup.setCheckable(True) - self.pointsGroup.setObjectName("pointsGroup") - self.verticalLayout_5 = QtGui.QVBoxLayout(self.pointsGroup) - self.verticalLayout_5.setObjectName("verticalLayout_5") - self.autoPointsCheck = QtGui.QCheckBox(self.pointsGroup) - self.autoPointsCheck.setChecked(True) - self.autoPointsCheck.setObjectName("autoPointsCheck") - self.verticalLayout_5.addWidget(self.autoPointsCheck) - self.gridGroup = QtGui.QFrame(Form) - self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) - self.gridGroup.setObjectName("gridGroup") - self.gridLayout_2 = QtGui.QGridLayout(self.gridGroup) - self.gridLayout_2.setObjectName("gridLayout_2") - self.xGridCheck = QtGui.QCheckBox(self.gridGroup) - self.xGridCheck.setObjectName("xGridCheck") - self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) - self.yGridCheck = QtGui.QCheckBox(self.gridGroup) - self.yGridCheck.setObjectName("yGridCheck") - self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) - self.gridAlphaSlider = QtGui.QSlider(self.gridGroup) - self.gridAlphaSlider.setMaximum(255) - self.gridAlphaSlider.setProperty("value", 128) - self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.gridAlphaSlider.setObjectName("gridAlphaSlider") - self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) - self.label = QtGui.QLabel(self.gridGroup) - self.label.setObjectName("label") - self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) - self.alphaGroup = QtGui.QGroupBox(Form) - self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) - self.alphaGroup.setCheckable(True) - self.alphaGroup.setObjectName("alphaGroup") - self.horizontalLayout = QtGui.QHBoxLayout(self.alphaGroup) - self.horizontalLayout.setObjectName("horizontalLayout") - self.autoAlphaCheck = QtGui.QCheckBox(self.alphaGroup) - self.autoAlphaCheck.setChecked(False) - self.autoAlphaCheck.setObjectName("autoAlphaCheck") - self.horizontalLayout.addWidget(self.autoAlphaCheck) - self.alphaSlider = QtGui.QSlider(self.alphaGroup) - self.alphaSlider.setMaximum(1000) - self.alphaSlider.setProperty("value", 1000) - self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) - self.alphaSlider.setObjectName("alphaSlider") - self.horizontalLayout.addWidget(self.alphaSlider) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8)) - self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8)) - self.clipToViewCheck.setToolTip(QtGui.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) - self.clipToViewCheck.setText(QtGui.QApplication.translate("Form", "Clip to View", None, QtGui.QApplication.UnicodeUTF8)) - self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) - self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8)) - self.downsampleCheck.setText(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8)) - self.peakRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None, QtGui.QApplication.UnicodeUTF8)) - self.peakRadio.setText(QtGui.QApplication.translate("Form", "Peak", None, QtGui.QApplication.UnicodeUTF8)) - self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) - self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8)) - self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8)) - self.meanRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the mean of N samples.", None, QtGui.QApplication.UnicodeUTF8)) - self.meanRadio.setText(QtGui.QApplication.translate("Form", "Mean", None, QtGui.QApplication.UnicodeUTF8)) - self.subsampleRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None, QtGui.QApplication.UnicodeUTF8)) - self.subsampleRadio.setText(QtGui.QApplication.translate("Form", "Subsample", None, QtGui.QApplication.UnicodeUTF8)) - self.autoDownsampleCheck.setToolTip(QtGui.QApplication.translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) - self.autoDownsampleCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.downsampleSpin.setToolTip(QtGui.QApplication.translate("Form", "Downsample data before plotting. (plot every Nth sample)", None, QtGui.QApplication.UnicodeUTF8)) - self.downsampleSpin.setSuffix(QtGui.QApplication.translate("Form", "x", None, QtGui.QApplication.UnicodeUTF8)) - self.fftCheck.setText(QtGui.QApplication.translate("Form", "Power Spectrum (FFT)", None, QtGui.QApplication.UnicodeUTF8)) - self.logXCheck.setText(QtGui.QApplication.translate("Form", "Log X", None, QtGui.QApplication.UnicodeUTF8)) - self.logYCheck.setText(QtGui.QApplication.translate("Form", "Log Y", None, QtGui.QApplication.UnicodeUTF8)) - self.pointsGroup.setTitle(QtGui.QApplication.translate("Form", "Points", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPointsCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.xGridCheck.setText(QtGui.QApplication.translate("Form", "Show X Grid", None, QtGui.QApplication.UnicodeUTF8)) - self.yGridCheck.setText(QtGui.QApplication.translate("Form", "Show Y Grid", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("Form", "Opacity", None, QtGui.QApplication.UnicodeUTF8)) - self.alphaGroup.setTitle(QtGui.QApplication.translate("Form", "Alpha", None, QtGui.QApplication.UnicodeUTF8)) - self.autoAlphaCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py deleted file mode 100644 index f6ce4680..00000000 --- a/pyqtgraph/graphicsItems/ROI.py +++ /dev/null @@ -1,1903 +0,0 @@ -# -*- coding: utf-8 -*- -""" -ROI.py - Interactive graphics items for GraphicsView (ROI widgets) -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. - -Implements a series of graphics items which display movable/scalable/rotatable shapes -for use as region-of-interest markers. ROI class automatically handles extraction -of array data from ImageItems. - -The ROI class is meant to serve as the base for more specific types; see several examples -of how to build an ROI at the bottom of the file. -""" - -from pyqtgraph.Qt import QtCore, QtGui -#if not hasattr(QtCore, 'Signal'): - #QtCore.Signal = QtCore.pyqtSignal -import numpy as np -from numpy.linalg import norm -import scipy.ndimage as ndimage -from pyqtgraph.Point import * -from pyqtgraph.SRTTransform import SRTTransform -from math import cos, sin -import pyqtgraph.functions as fn -from .GraphicsObject import GraphicsObject -from .UIGraphicsItem import UIGraphicsItem - -__all__ = [ - 'ROI', - 'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI', - 'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI', -] - - -def rectStr(r): - return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height()) - -class ROI(GraphicsObject): - """Generic region-of-interest widget. - Can be used for implementing many types of selection box with rotate/translate/scale handles. - - Signals - ----------------------- ---------------------------------------------------- - sigRegionChangeFinished Emitted when the user stops dragging the ROI (or - one of its handles) or if the ROI is changed - programatically. - sigRegionChangeStarted Emitted when the user starts dragging the ROI (or - one of its handles). - sigRegionChanged Emitted any time the position of the ROI changes, - including while it is being dragged by the user. - sigHoverEvent Emitted when the mouse hovers over the ROI. - sigClicked Emitted when the user clicks on the ROI. - Note that clicking is disabled by default to prevent - stealing clicks from objects behind the ROI. To - enable clicking, call - roi.setAcceptedMouseButtons(QtCore.Qt.LeftButton). - See QtGui.QGraphicsItem documentation for more - details. - sigRemoveRequested Emitted when the user selects 'remove' from the - ROI's context menu (if available). - ----------------------- ---------------------------------------------------- - """ - - sigRegionChangeFinished = QtCore.Signal(object) - sigRegionChangeStarted = QtCore.Signal(object) - sigRegionChanged = QtCore.Signal(object) - sigHoverEvent = QtCore.Signal(object) - sigClicked = QtCore.Signal(object, object) - sigRemoveRequested = QtCore.Signal(object) - - def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None, movable=True, removable=False): - #QObjectWorkaround.__init__(self) - GraphicsObject.__init__(self, parent) - self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - pos = Point(pos) - size = Point(size) - self.aspectLocked = False - self.translatable = movable - self.rotateAllowed = True - self.removable = removable - self.menu = None - - self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted. - self.mouseHovering = False - if pen is None: - pen = (255, 255, 255) - self.setPen(pen) - - self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255)) - self.handles = [] - self.state = {'pos': Point(0,0), 'size': Point(1,1), 'angle': 0} ## angle is in degrees for ease of Qt integration - self.lastState = None - self.setPos(pos) - self.setAngle(angle) - self.setSize(size) - self.setZValue(10) - self.isMoving = False - - self.handleSize = 5 - self.invertible = invertible - self.maxBounds = maxBounds - - self.snapSize = snapSize - self.translateSnap = translateSnap - self.rotateSnap = rotateSnap - self.scaleSnap = scaleSnap - #self.setFlag(self.ItemIsSelectable, True) - - def getState(self): - return self.stateCopy() - - def stateCopy(self): - sc = {} - sc['pos'] = Point(self.state['pos']) - sc['size'] = Point(self.state['size']) - sc['angle'] = self.state['angle'] - return sc - - def saveState(self): - """Return the state of the widget in a format suitable for storing to disk. (Points are converted to tuple)""" - state = {} - state['pos'] = tuple(self.state['pos']) - state['size'] = tuple(self.state['size']) - state['angle'] = self.state['angle'] - return state - - def setState(self, state, update=True): - self.setPos(state['pos'], update=False) - self.setSize(state['size'], update=False) - self.setAngle(state['angle'], update=update) - - def setZValue(self, z): - QtGui.QGraphicsItem.setZValue(self, z) - for h in self.handles: - h['item'].setZValue(z+1) - - def parentBounds(self): - return self.mapToParent(self.boundingRect()).boundingRect() - - def setPen(self, pen): - self.pen = fn.mkPen(pen) - self.currentPen = self.pen - self.update() - - def size(self): - return self.getState()['size'] - - def pos(self): - return self.getState()['pos'] - - def angle(self): - return self.getState()['angle'] - - def setPos(self, pos, update=True, finish=True): - """Set the position of the ROI (in the parent's coordinate system). - By default, this will cause both sigRegionChanged and sigRegionChangeFinished to be emitted. - - If finish is False, then sigRegionChangeFinished will not be emitted. You can then use - stateChangeFinished() to cause the signal to be emitted after a series of state changes. - - If update is False, the state change will be remembered but not processed and no signals - will be emitted. You can then use stateChanged() to complete the state change. This allows - multiple change functions to be called sequentially while minimizing processing overhead - and repeated signals. Setting update=False also forces finish=False. - """ - - pos = Point(pos) - self.state['pos'] = pos - QtGui.QGraphicsItem.setPos(self, pos) - if update: - self.stateChanged(finish=finish) - - def setSize(self, size, update=True, finish=True): - """Set the size of the ROI. May be specified as a QPoint, Point, or list of two values. - See setPos() for an explanation of the update and finish arguments. - """ - size = Point(size) - self.prepareGeometryChange() - self.state['size'] = size - if update: - self.stateChanged(finish=finish) - - def setAngle(self, angle, update=True, finish=True): - """Set the angle of rotation (in degrees) for this ROI. - See setPos() for an explanation of the update and finish arguments. - """ - self.state['angle'] = angle - tr = QtGui.QTransform() - #tr.rotate(-angle * 180 / np.pi) - tr.rotate(angle) - self.setTransform(tr) - if update: - self.stateChanged(finish=finish) - - def scale(self, s, center=[0,0], update=True, finish=True): - """ - Resize the ROI by scaling relative to *center*. - See setPos() for an explanation of the *update* and *finish* arguments. - """ - c = self.mapToParent(Point(center) * self.state['size']) - self.prepareGeometryChange() - newSize = self.state['size'] * s - c1 = self.mapToParent(Point(center) * newSize) - newPos = self.state['pos'] + c - c1 - - self.setSize(newSize, update=False) - self.setPos(newPos, update=update, finish=finish) - - - def translate(self, *args, **kargs): - """ - Move the ROI to a new position. - Accepts either (x, y, snap) or ([x,y], snap) as arguments - If the ROI is bounded and the move would exceed boundaries, then the ROI - is moved to the nearest acceptable position instead. - - snap can be: - None (default): use self.translateSnap and self.snapSize to determine whether/how to snap - False: do not snap - Point(w,h) snap to rectangular grid with spacing (w,h) - True: snap using self.snapSize (and ignoring self.translateSnap) - - Also accepts *update* and *finish* arguments (see setPos() for a description of these). - """ - - if len(args) == 1: - pt = args[0] - else: - pt = args - - newState = self.stateCopy() - newState['pos'] = newState['pos'] + pt - - ## snap position - #snap = kargs.get('snap', None) - #if (snap is not False) and not (snap is None and self.translateSnap is False): - - snap = kargs.get('snap', None) - if snap is None: - snap = self.translateSnap - if snap is not False: - newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap) - - #d = ev.scenePos() - self.mapToScene(self.pressPos) - if self.maxBounds is not None: - r = self.stateRect(newState) - #r0 = self.sceneTransform().mapRect(self.boundingRect()) - d = Point(0,0) - if self.maxBounds.left() > r.left(): - d[0] = self.maxBounds.left() - r.left() - elif self.maxBounds.right() < r.right(): - d[0] = self.maxBounds.right() - r.right() - if self.maxBounds.top() > r.top(): - d[1] = self.maxBounds.top() - r.top() - elif self.maxBounds.bottom() < r.bottom(): - d[1] = self.maxBounds.bottom() - r.bottom() - newState['pos'] += d - - #self.state['pos'] = newState['pos'] - update = kargs.get('update', True) - finish = kargs.get('finish', True) - self.setPos(newState['pos'], update=update, finish=finish) - #if 'update' not in kargs or kargs['update'] is True: - #self.stateChanged() - - def rotate(self, angle, update=True, finish=True): - self.setAngle(self.angle()+angle, update=update, finish=finish) - - def handleMoveStarted(self): - self.preMoveState = self.getState() - - def addTranslateHandle(self, pos, axes=None, item=None, name=None, index=None): - pos = Point(pos) - return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item}, index=index) - - def addFreeHandle(self, pos=None, axes=None, item=None, name=None, index=None): - if pos is not None: - pos = Point(pos) - return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item}, index=index) - - def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspect=False, index=None): - pos = Point(pos) - center = Point(center) - info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item, 'lockAspect': lockAspect} - if pos.x() == center.x(): - info['xoff'] = True - if pos.y() == center.y(): - info['yoff'] = True - return self.addHandle(info, index=index) - - def addRotateHandle(self, pos, center, item=None, name=None, index=None): - pos = Point(pos) - center = Point(center) - return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item}, index=index) - - def addScaleRotateHandle(self, pos, center, item=None, name=None, index=None): - pos = Point(pos) - center = Point(center) - if pos[0] != center[0] and pos[1] != center[1]: - raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.") - return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item}, index=index) - - def addRotateFreeHandle(self, pos, center, axes=None, item=None, name=None, index=None): - pos = Point(pos) - center = Point(center) - return self.addHandle({'name': name, 'type': 'rf', 'center': center, 'pos': pos, 'item': item}, index=index) - - def addHandle(self, info, index=None): - ## If a Handle was not supplied, create it now - if 'item' not in info or info['item'] is None: - h = Handle(self.handleSize, typ=info['type'], pen=self.handlePen, parent=self) - h.setPos(info['pos'] * self.state['size']) - info['item'] = h - else: - h = info['item'] - if info['pos'] is None: - info['pos'] = h.pos() - - ## connect the handle to this ROI - #iid = len(self.handles) - h.connectROI(self) - if index is None: - self.handles.append(info) - else: - self.handles.insert(index, info) - - h.setZValue(self.zValue()+1) - self.stateChanged() - return h - - def indexOfHandle(self, handle): - if isinstance(handle, Handle): - index = [i for i, info in enumerate(self.handles) if info['item'] is handle] - if len(index) == 0: - raise Exception("Cannot remove handle; it is not attached to this ROI") - return index[0] - else: - return handle - - def removeHandle(self, handle): - """Remove a handle from this ROI. Argument may be either a Handle instance or the integer index of the handle.""" - index = self.indexOfHandle(handle) - - handle = self.handles[index]['item'] - self.handles.pop(index) - handle.disconnectROI(self) - if len(handle.rois) == 0: - self.scene().removeItem(handle) - self.stateChanged() - - def replaceHandle(self, oldHandle, newHandle): - """Replace one handle in the ROI for another. This is useful when connecting multiple ROIs together. - *oldHandle* may be a Handle instance or the index of a handle.""" - #print "=========================" - #print "replace", oldHandle, newHandle - #print self - #print self.handles - #print "-----------------" - index = self.indexOfHandle(oldHandle) - info = self.handles[index] - self.removeHandle(index) - info['item'] = newHandle - info['pos'] = newHandle.pos() - self.addHandle(info, index=index) - #print self.handles - - def checkRemoveHandle(self, handle): - ## This is used when displaying a Handle's context menu to determine - ## whether removing is allowed. - ## Subclasses may wish to override this to disable the menu entry. - ## Note: by default, handles are not user-removable even if this method returns True. - return True - - - def getLocalHandlePositions(self, index=None): - """Returns the position of a handle in ROI coordinates""" - if index == None: - positions = [] - for h in self.handles: - positions.append((h['name'], h['pos'])) - return positions - else: - return (self.handles[index]['name'], self.handles[index]['pos']) - - def getSceneHandlePositions(self, index=None): - if index == None: - positions = [] - for h in self.handles: - positions.append((h['name'], h['item'].scenePos())) - return positions - else: - return (self.handles[index]['name'], self.handles[index]['item'].scenePos()) - - def getHandles(self): - return [h['item'] for h in self.handles] - - def mapSceneToParent(self, pt): - return self.mapToParent(self.mapFromScene(pt)) - - def setSelected(self, s): - QtGui.QGraphicsItem.setSelected(self, s) - #print "select", self, s - if s: - for h in self.handles: - h['item'].show() - else: - for h in self.handles: - h['item'].hide() - - - def hoverEvent(self, ev): - hover = False - if not ev.isExit(): - if self.translatable and ev.acceptDrags(QtCore.Qt.LeftButton): - hover=True - - for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MidButton]: - if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn): - hover=True - if self.contextMenuEnabled(): - ev.acceptClicks(QtCore.Qt.RightButton) - - if hover: - self.setMouseHover(True) - self.sigHoverEvent.emit(self) - ev.acceptClicks(QtCore.Qt.LeftButton) ## If the ROI is hilighted, we should accept all clicks to avoid confusion. - ev.acceptClicks(QtCore.Qt.RightButton) - ev.acceptClicks(QtCore.Qt.MidButton) - else: - self.setMouseHover(False) - - def setMouseHover(self, hover): - ## Inform the ROI that the mouse is(not) hovering over it - if self.mouseHovering == hover: - return - self.mouseHovering = hover - if hover: - self.currentPen = fn.mkPen(255, 255, 0) - else: - self.currentPen = self.pen - self.update() - - def contextMenuEnabled(self): - return self.removable - - def raiseContextMenu(self, ev): - if not self.contextMenuEnabled(): - return - menu = self.getMenu() - menu = self.scene().addParentContextMenus(self, menu, ev) - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - - def getMenu(self): - if self.menu is None: - self.menu = QtGui.QMenu() - self.menu.setTitle("ROI") - remAct = QtGui.QAction("Remove ROI", self.menu) - remAct.triggered.connect(self.removeClicked) - self.menu.addAction(remAct) - self.menu.remAct = remAct - return self.menu - - def removeClicked(self): - ## Send remove event only after we have exited the menu event handler - self.removeTimer = QtCore.QTimer() - self.removeTimer.timeout.connect(lambda: self.sigRemoveRequested.emit(self)) - self.removeTimer.start(0) - - - - def mouseDragEvent(self, ev): - if ev.isStart(): - #p = ev.pos() - #if not self.isMoving and not self.shape().contains(p): - #ev.ignore() - #return - if ev.button() == QtCore.Qt.LeftButton: - self.setSelected(True) - if self.translatable: - self.isMoving = True - self.preMoveState = self.getState() - self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) - self.sigRegionChangeStarted.emit(self) - ev.accept() - else: - ev.ignore() - - elif ev.isFinish(): - if self.translatable: - if self.isMoving: - self.stateChangeFinished() - self.isMoving = False - return - - if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton: - snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None - newPos = self.mapToParent(ev.pos()) + self.cursorOffset - self.translate(newPos - self.pos(), snap=snap, finish=False) - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton and self.isMoving: - ev.accept() - self.cancelMove() - if ev.button() == QtCore.Qt.RightButton and self.contextMenuEnabled(): - self.raiseContextMenu(ev) - ev.accept() - elif int(ev.button() & self.acceptedMouseButtons()) > 0: - ev.accept() - self.sigClicked.emit(self, ev) - else: - ev.ignore() - - - - - def cancelMove(self): - self.isMoving = False - self.setState(self.preMoveState) - - - #def pointDragEvent(self, pt, ev): - ### just for handling drag start/stop. - ### drag moves are handled through movePoint() - - #if ev.isStart(): - #self.isMoving = True - #self.preMoveState = self.getState() - - #self.sigRegionChangeStarted.emit(self) - #elif ev.isFinish(): - #self.isMoving = False - #self.sigRegionChangeFinished.emit(self) - #return - - - #def pointPressEvent(self, pt, ev): - ##print "press" - #self.isMoving = True - #self.preMoveState = self.getState() - - ##self.emit(QtCore.SIGNAL('regionChangeStarted'), self) - #self.sigRegionChangeStarted.emit(self) - ##self.pressPos = self.mapFromScene(ev.scenePos()) - ##self.pressHandlePos = self.handles[pt]['item'].pos() - - #def pointReleaseEvent(self, pt, ev): - ##print "release" - #self.isMoving = False - ##self.emit(QtCore.SIGNAL('regionChangeFinished'), self) - #self.sigRegionChangeFinished.emit(self) - - #def pointMoveEvent(self, pt, ev): - #self.movePoint(pt, ev.scenePos(), ev.modifiers()) - - - def checkPointMove(self, handle, pos, modifiers): - """When handles move, they must ask the ROI if the move is acceptable. - By default, this always returns True. Subclasses may wish override. - """ - return True - - - def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True, coords='parent'): - ## called by Handles when they are moved. - ## pos is the new position of the handle in scene coords, as requested by the handle. - - newState = self.stateCopy() - index = self.indexOfHandle(handle) - h = self.handles[index] - p0 = self.mapToParent(h['pos'] * self.state['size']) - p1 = Point(pos) - - if coords == 'parent': - pass - elif coords == 'scene': - p1 = self.mapSceneToParent(p1) - else: - raise Exception("New point location must be given in either 'parent' or 'scene' coordinates.") - - - ## transform p0 and p1 into parent's coordinates (same as scene coords if there is no parent). I forget why. - #p0 = self.mapSceneToParent(p0) - #p1 = self.mapSceneToParent(p1) - - ## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1) - if 'center' in h: - c = h['center'] - cs = c * self.state['size'] - lp0 = self.mapFromParent(p0) - cs - lp1 = self.mapFromParent(p1) - cs - - if h['type'] == 't': - snap = True if (modifiers & QtCore.Qt.ControlModifier) else None - #if self.translateSnap or (): - #snap = Point(self.snapSize, self.snapSize) - self.translate(p1-p0, snap=snap, update=False) - - elif h['type'] == 'f': - newPos = self.mapFromParent(p1) - h['item'].setPos(newPos) - h['pos'] = newPos - self.freeHandleMoved = True - #self.sigRegionChanged.emit(self) ## should be taken care of by call to stateChanged() - - elif h['type'] == 's': - ## If a handle and its center have the same x or y value, we can't scale across that axis. - if h['center'][0] == h['pos'][0]: - lp1[0] = 0 - if h['center'][1] == h['pos'][1]: - lp1[1] = 0 - - ## snap - if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): - lp1[0] = round(lp1[0] / self.snapSize) * self.snapSize - lp1[1] = round(lp1[1] / self.snapSize) * self.snapSize - - ## preserve aspect ratio (this can override snapping) - if h['lockAspect'] or (modifiers & QtCore.Qt.AltModifier): - #arv = Point(self.preMoveState['size']) - - lp1 = lp1.proj(lp0) - - ## determine scale factors and new size of ROI - hs = h['pos'] - c - if hs[0] == 0: - hs[0] = 1 - if hs[1] == 0: - hs[1] = 1 - newSize = lp1 / hs - - ## Perform some corrections and limit checks - if newSize[0] == 0: - newSize[0] = newState['size'][0] - if newSize[1] == 0: - newSize[1] = newState['size'][1] - if not self.invertible: - if newSize[0] < 0: - newSize[0] = newState['size'][0] - if newSize[1] < 0: - newSize[1] = newState['size'][1] - if self.aspectLocked: - newSize[0] = newSize[1] - - ## Move ROI so the center point occupies the same scene location after the scale - s0 = c * self.state['size'] - s1 = c * newSize - cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0)) - - ## update state, do more boundary checks - newState['size'] = newSize - newState['pos'] = newState['pos'] + cc - if self.maxBounds is not None: - r = self.stateRect(newState) - if not self.maxBounds.contains(r): - return - - self.setPos(newState['pos'], update=False) - self.setSize(newState['size'], update=False) - - elif h['type'] in ['r', 'rf']: - if h['type'] == 'rf': - self.freeHandleMoved = True - - if not self.rotateAllowed: - return - ## If the handle is directly over its center point, we can't compute an angle. - if lp1.length() == 0 or lp0.length() == 0: - return - - ## determine new rotation angle, constrained if necessary - ang = newState['angle'] - lp0.angle(lp1) - if ang is None: ## this should never happen.. - return - if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): - ang = round(ang / 15.) * 15. ## 180/12 = 15 - - ## create rotation transform - tr = QtGui.QTransform() - tr.rotate(ang) - - ## move ROI so that center point remains stationary after rotate - cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos']) - newState['angle'] = ang - newState['pos'] = newState['pos'] + cc - - ## check boundaries, update - if self.maxBounds is not None: - r = self.stateRect(newState) - if not self.maxBounds.contains(r): - return - #self.setTransform(tr) - self.setPos(newState['pos'], update=False) - self.setAngle(ang, update=False) - #self.state = newState - - ## If this is a free-rotate handle, its distance from the center may change. - - if h['type'] == 'rf': - h['item'].setPos(self.mapFromScene(p1)) ## changes ROI coordinates of handle - - elif h['type'] == 'sr': - if h['center'][0] == h['pos'][0]: - scaleAxis = 1 - else: - scaleAxis = 0 - - if lp1.length() == 0 or lp0.length() == 0: - return - - ang = newState['angle'] - lp0.angle(lp1) - if ang is None: - return - if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): - #ang = round(ang / (np.pi/12.)) * (np.pi/12.) - ang = round(ang / 15.) * 15. - - hs = abs(h['pos'][scaleAxis] - c[scaleAxis]) - newState['size'][scaleAxis] = lp1.length() / hs - #if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): - if self.scaleSnap: ## use CTRL only for angular snap here. - newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize - if newState['size'][scaleAxis] == 0: - newState['size'][scaleAxis] = 1 - - c1 = c * newState['size'] - tr = QtGui.QTransform() - tr.rotate(ang) - - cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos']) - newState['angle'] = ang - newState['pos'] = newState['pos'] + cc - if self.maxBounds is not None: - r = self.stateRect(newState) - if not self.maxBounds.contains(r): - return - #self.setTransform(tr) - #self.setPos(newState['pos'], update=False) - #self.prepareGeometryChange() - #self.state = newState - self.setState(newState, update=False) - - self.stateChanged(finish=finish) - - def stateChanged(self, finish=True): - """Process changes to the state of the ROI. - If there are any changes, then the positions of handles are updated accordingly - and sigRegionChanged is emitted. If finish is True, then - sigRegionChangeFinished will also be emitted.""" - - changed = False - if self.lastState is None: - changed = True - else: - for k in list(self.state.keys()): - if self.state[k] != self.lastState[k]: - changed = True - - self.prepareGeometryChange() - if changed: - ## Move all handles to match the current configuration of the ROI - for h in self.handles: - if h['item'] in self.childItems(): - p = h['pos'] - h['item'].setPos(h['pos'] * self.state['size']) - #else: - # trans = self.state['pos']-self.lastState['pos'] - # h['item'].setPos(h['pos'] + h['item'].parentItem().mapFromParent(trans)) - - self.update() - self.sigRegionChanged.emit(self) - elif self.freeHandleMoved: - self.sigRegionChanged.emit(self) - - self.freeHandleMoved = False - self.lastState = self.stateCopy() - - if finish: - self.stateChangeFinished() - - def stateChangeFinished(self): - self.sigRegionChangeFinished.emit(self) - - def stateRect(self, state): - r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1]) - tr = QtGui.QTransform() - #tr.rotate(-state['angle'] * 180 / np.pi) - tr.rotate(-state['angle']) - r = tr.mapRect(r) - return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1]) - - - def getSnapPosition(self, pos, snap=None): - ## Given that pos has been requested, return the nearest snap-to position - ## optionally, snap may be passed in to specify a rectangular snap grid. - ## override this function for more interesting snap functionality.. - - if snap is None or snap is True: - if self.snapSize is None: - return pos - snap = Point(self.snapSize, self.snapSize) - - return Point( - round(pos[0] / snap[0]) * snap[0], - round(pos[1] / snap[1]) * snap[1] - ) - - - def boundingRect(self): - return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() - - def paint(self, p, opt, widget): - p.save() - r = self.boundingRect() - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - p.translate(r.left(), r.top()) - p.scale(r.width(), r.height()) - p.drawRect(0, 0, 1, 1) - p.restore() - - def getArraySlice(self, data, img, axes=(0,1), returnSlice=True): - """Return a tuple of slice objects that can be used to slice the region from data covered by this ROI. - Also returns the transform which maps the ROI into data coordinates. - - If returnSlice is set to False, the function returns a pair of tuples with the values that would have - been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop)) - - If the slice can not be computed (usually because the scene/transforms are not properly - constructed yet), then the method returns None. - """ - #print "getArraySlice" - - ## Determine shape of array along ROI axes - dShape = (data.shape[axes[0]], data.shape[axes[1]]) - #print " dshape", dShape - - ## Determine transform that maps ROI bounding box to image coordinates - try: - tr = self.sceneTransform() * fn.invertQTransform(img.sceneTransform()) - except np.linalg.linalg.LinAlgError: - return None - - ## Modify transform to scale from image coords to data coords - #m = QtGui.QTransform() - tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height()) - #tr = tr * m - - ## Transform ROI bounds into data bounds - dataBounds = tr.mapRect(self.boundingRect()) - #print " boundingRect:", self.boundingRect() - #print " dataBounds:", dataBounds - - ## Intersect transformed ROI bounds with data bounds - intBounds = dataBounds.intersect(QtCore.QRectF(0, 0, dShape[0], dShape[1])) - #print " intBounds:", intBounds - - ## Determine index values to use when referencing the array. - bounds = ( - (int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))), - (int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top()))) - ) - #print " bounds:", bounds - - if returnSlice: - ## Create slice objects - sl = [slice(None)] * data.ndim - sl[axes[0]] = slice(*bounds[0]) - sl[axes[1]] = slice(*bounds[1]) - return tuple(sl), tr - else: - return bounds, tr - - def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds): - """Use the position and orientation of this ROI relative to an imageItem to pull a slice from an array. - - This method uses :func:`affineSlice ` to generate - the slice from *data* and uses :func:`getAffineSliceParams ` to determine the parameters to - pass to :func:`affineSlice `. - - If *returnMappedCoords* is True, then the method returns a tuple (result, coords) - such that coords is the set of coordinates used to interpolate values from the original - data, mapped into the parent coordinate system of the image. This is useful, when slicing - data from images that have been transformed, for determining the location of each value - in the sliced data. - - All extra keyword arguments are passed to :func:`affineSlice `. - """ - - shape, vectors, origin = self.getAffineSliceParams(data, img, axes) - if not returnMappedCoords: - return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) - else: - kwds['returnCoords'] = True - result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) - #tr = fn.transformToArray(img.transform())[:2] ## remove perspective transform values - - ### separate translation from scale/rotate - #translate = tr[:,2] - #tr = tr[:,:2] - #tr = tr.reshape((2,2) + (1,)*(coords.ndim-1)) - #coords = coords[np.newaxis, ...] - - ### map coordinates and return - #mapped = (tr*coords).sum(axis=0) ## apply scale/rotate - #mapped += translate.reshape((2,1,1)) - mapped = fn.transformCoordinates(img.transform(), coords) - return result, mapped - - - ### transpose data so x and y are the first 2 axes - #trAx = range(0, data.ndim) - #trAx.remove(axes[0]) - #trAx.remove(axes[1]) - #tr1 = tuple(axes) + tuple(trAx) - #arr = data.transpose(tr1) - - ### Determine the minimal area of the data we will need - #(dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes) - - ### Pad data boundaries by 1px if possible - #dataBounds = ( - #(max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])), - #(max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1])) - #) - - ### Extract minimal data from array - #arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]] - - ### Update roiDataTransform to reflect this extraction - #roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0]) - #### (roiDataTransform now maps from ROI coords to extracted data coords) - - - ### Rotate array - #if abs(self.state['angle']) > 1e-5: - #arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1) - - ### update data transforms to reflect this rotation - #rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi) - #roiDataTransform *= rot - - ### The rotation also causes a shift which must be accounted for: - #dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1]) - #rotBound = rot.mapRect(dataBound) - #roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top()) - - #else: - #arr2 = arr1 - - - - #### Shift off partial pixels - ## 1. map ROI into current data space - #roiBounds = roiDataTransform.mapRect(self.boundingRect()) - - ## 2. Determine amount to shift data - #shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom()) - #if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6: - ## 3. pad array with 0s before shifting - #arr2a = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype) - #arr2a[1:-1, 1:-1] = arr2 - - ## 4. shift array and udpate transforms - #arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1) - #roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1]) - #else: - #arr3 = arr2 - - - #### Extract needed region from rotated/shifted array - ## 1. map ROI into current data space (round these values off--they should be exact integer values at this point) - #roiBounds = roiDataTransform.mapRect(self.boundingRect()) - ##print self, roiBounds.height() - ##import traceback - ##traceback.print_stack() - - #roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height())) - - ##2. intersect ROI with data bounds - #dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1])) - - ##3. Extract data from array - #db = dataBounds - #bounds = ( - #(db.left(), db.right()+1), - #(db.top(), db.bottom()+1) - #) - #arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]] - - #### Create zero array in size of ROI - #arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype) - - ### Fill array with ROI data - #orig = Point(dataBounds.topLeft() - roiBounds.topLeft()) - #subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]] - #subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]] - - - ### figure out the reverse transpose order - #tr2 = np.array(tr1) - #for i in range(0, len(tr2)): - #tr2[tr1[i]] = i - #tr2 = tuple(tr2) - - ### Untranspose array before returning - #return arr5.transpose(tr2) - - def getAffineSliceParams(self, data, img, axes=(0,1)): - """ - Returns the parameters needed to use :func:`affineSlice ` to - extract a subset of *data* using this ROI and *img* to specify the subset. - - See :func:`getArrayRegion ` for more information. - """ - if self.scene() is not img.scene(): - raise Exception("ROI and target item must be members of the same scene.") - - shape = self.state['size'] - - origin = self.mapToItem(img, QtCore.QPointF(0, 0)) - - ## vx and vy point in the directions of the slice axes, but must be scaled properly - vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin - vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin - - lvx = np.sqrt(vx.x()**2 + vx.y()**2) - lvy = np.sqrt(vy.x()**2 + vy.y()**2) - pxLen = img.width() / float(data.shape[axes[0]]) - #img.width is number of pixels or width of item? - #need pxWidth and pxHeight instead of pxLen ? - sx = pxLen / lvx - sy = pxLen / lvy - - vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) - shape = self.state['size'] - shape = [abs(shape[0]/sx), abs(shape[1]/sy)] - - origin = (origin.x(), origin.y()) - return shape, vectors, origin - - def getGlobalTransform(self, relativeTo=None): - """Return global transformation (rotation angle+translation) required to move - from relative state to current state. If relative state isn't specified, - then we use the state of the ROI when mouse is pressed.""" - if relativeTo == None: - relativeTo = self.preMoveState - st = self.getState() - - ## this is only allowed because we will be comparing the two - relativeTo['scale'] = relativeTo['size'] - st['scale'] = st['size'] - - - - t1 = SRTTransform(relativeTo) - t2 = SRTTransform(st) - return t2/t1 - - - #st = self.getState() - - ### rotation - #ang = (st['angle']-relativeTo['angle']) * 180. / 3.14159265358 - #rot = QtGui.QTransform() - #rot.rotate(-ang) - - ### We need to come up with a universal transformation--one that can be applied to other objects - ### such that all maintain alignment. - ### More specifically, we need to turn the ROI's position and angle into - ### a rotation _around the origin_ and a translation. - - #p0 = Point(relativeTo['pos']) - - ### base position, rotated - #p1 = rot.map(p0) - - #trans = Point(st['pos']) - p1 - #return trans, ang - - def applyGlobalTransform(self, tr): - st = self.getState() - - st['scale'] = st['size'] - st = SRTTransform(st) - st = (st * tr).saveState() - st['size'] = st['scale'] - self.setState(st) - - -class Handle(UIGraphicsItem): - - types = { ## defines number of sides, start angle for each handle type - 't': (4, np.pi/4), - 'f': (4, np.pi/4), - 's': (4, 0), - 'r': (12, 0), - 'sr': (12, 0), - 'rf': (12, 0), - } - - sigClicked = QtCore.Signal(object, object) # self, event - sigRemoveRequested = QtCore.Signal(object) # self - - def __init__(self, radius, typ=None, pen=(200, 200, 220), parent=None, deletable=False): - #print " create item with parent", parent - #self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10) - #self.setFlags(self.ItemIgnoresTransformations | self.ItemSendsScenePositionChanges) - self.rois = [] - self.radius = radius - self.typ = typ - self.pen = fn.mkPen(pen) - self.currentPen = self.pen - self.pen.setWidth(0) - self.pen.setCosmetic(True) - self.isMoving = False - self.sides, self.startAng = self.types[typ] - self.buildPath() - self._shape = None - self.menu = self.buildMenu() - - UIGraphicsItem.__init__(self, parent=parent) - self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - self.deletable = deletable - if deletable: - self.setAcceptedMouseButtons(QtCore.Qt.RightButton) - #self.updateShape() - self.setZValue(11) - - def connectROI(self, roi): - ### roi is the "parent" roi, i is the index of the handle in roi.handles - self.rois.append(roi) - - def disconnectROI(self, roi): - self.rois.remove(roi) - #for i, r in enumerate(self.roi): - #if r[0] == roi: - #self.roi.pop(i) - - #def close(self): - #for r in self.roi: - #r.removeHandle(self) - - def setDeletable(self, b): - self.deletable = b - if b: - self.setAcceptedMouseButtons(self.acceptedMouseButtons() | QtCore.Qt.RightButton) - else: - self.setAcceptedMouseButtons(self.acceptedMouseButtons() & ~QtCore.Qt.RightButton) - - def removeClicked(self): - self.sigRemoveRequested.emit(self) - - def hoverEvent(self, ev): - hover = False - if not ev.isExit(): - if ev.acceptDrags(QtCore.Qt.LeftButton): - hover=True - for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MidButton]: - if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn): - hover=True - - if hover: - self.currentPen = fn.mkPen(255, 255,0) - else: - self.currentPen = self.pen - self.update() - #if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): - #self.currentPen = fn.mkPen(255, 255,0) - #else: - #self.currentPen = self.pen - #self.update() - - - - def mouseClickEvent(self, ev): - ## right-click cancels drag - if ev.button() == QtCore.Qt.RightButton and self.isMoving: - self.isMoving = False ## prevents any further motion - self.movePoint(self.startPos, finish=True) - #for r in self.roi: - #r[0].cancelMove() - ev.accept() - elif int(ev.button() & self.acceptedMouseButtons()) > 0: - ev.accept() - if ev.button() == QtCore.Qt.RightButton and self.deletable: - self.raiseContextMenu(ev) - self.sigClicked.emit(self, ev) - else: - ev.ignore() - - #elif self.deletable: - #ev.accept() - #self.raiseContextMenu(ev) - #else: - #ev.ignore() - - def buildMenu(self): - menu = QtGui.QMenu() - menu.setTitle("Handle") - self.removeAction = menu.addAction("Remove handle", self.removeClicked) - return menu - - def getMenu(self): - return self.menu - - - def getContextMenus(self, event): - return [self.menu] - - def raiseContextMenu(self, ev): - menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) - - ## Make sure it is still ok to remove this handle - removeAllowed = all([r.checkRemoveHandle(self) for r in self.rois]) - self.removeAction.setEnabled(removeAllowed) - pos = ev.screenPos() - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - - def mouseDragEvent(self, ev): - if ev.button() != QtCore.Qt.LeftButton: - return - ev.accept() - - ## Inform ROIs that a drag is happening - ## note: the ROI is informed that the handle has moved using ROI.movePoint - ## this is for other (more nefarious) purposes. - #for r in self.roi: - #r[0].pointDragEvent(r[1], ev) - - if ev.isFinish(): - if self.isMoving: - for r in self.rois: - r.stateChangeFinished() - self.isMoving = False - elif ev.isStart(): - for r in self.rois: - r.handleMoveStarted() - self.isMoving = True - self.startPos = self.scenePos() - self.cursorOffset = self.scenePos() - ev.buttonDownScenePos() - - if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click. - pos = ev.scenePos() + self.cursorOffset - self.movePoint(pos, ev.modifiers(), finish=False) - - def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True): - for r in self.rois: - if not r.checkPointMove(self, pos, modifiers): - return - #print "point moved; inform %d ROIs" % len(self.roi) - # A handle can be used by multiple ROIs; tell each to update its handle position - for r in self.rois: - r.movePoint(self, pos, modifiers, finish=finish, coords='scene') - - def buildPath(self): - size = self.radius - self.path = QtGui.QPainterPath() - ang = self.startAng - dt = 2*np.pi / self.sides - for i in range(0, self.sides+1): - x = size * cos(ang) - y = size * sin(ang) - ang += dt - if i == 0: - self.path.moveTo(x, y) - else: - self.path.lineTo(x, y) - - def paint(self, p, opt, widget): - ### determine rotation of transform - #m = self.sceneTransform() - ##mi = m.inverted()[0] - #v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0)) - #va = np.arctan2(v.y(), v.x()) - - ### Determine length of unit vector in painter's coords - ##size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0)) - ##size = (size.x()*size.x() + size.y() * size.y()) ** 0.5 - #size = self.radius - - #bounds = QtCore.QRectF(-size, -size, size*2, size*2) - #if bounds != self.bounds: - #self.bounds = bounds - #self.prepareGeometryChange() - p.setRenderHints(p.Antialiasing, True) - p.setPen(self.currentPen) - - #p.rotate(va * 180. / 3.1415926) - #p.drawPath(self.path) - p.drawPath(self.shape()) - #ang = self.startAng + va - #dt = 2*np.pi / self.sides - #for i in range(0, self.sides): - #x1 = size * cos(ang) - #y1 = size * sin(ang) - #x2 = size * cos(ang+dt) - #y2 = size * sin(ang+dt) - #ang += dt - #p.drawLine(Point(x1, y1), Point(x2, y2)) - - def shape(self): - if self._shape is None: - s = self.generateShape() - if s is None: - return self.path - self._shape = s - self.prepareGeometryChange() ## beware--this can cause the view to adjust, which would immediately invalidate the shape. - return self._shape - - def boundingRect(self): - #print 'roi:', self.roi - s1 = self.shape() - #print " s1:", s1 - #s2 = self.shape() - #print " s2:", s2 - - return self.shape().boundingRect() - - def generateShape(self): - ## determine rotation of transform - #m = self.sceneTransform() ## Qt bug: do not access sceneTransform() until we know this object has a scene. - #mi = m.inverted()[0] - dt = self.deviceTransform() - - if dt is None: - self._shape = self.path - return None - - v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) - va = np.arctan2(v.y(), v.x()) - - dti = fn.invertQTransform(dt) - devPos = dt.map(QtCore.QPointF(0,0)) - tr = QtGui.QTransform() - tr.translate(devPos.x(), devPos.y()) - tr.rotate(va * 180. / 3.1415926) - - return dti.map(tr.map(self.path)) - - - def viewTransformChanged(self): - GraphicsObject.viewTransformChanged(self) - self._shape = None ## invalidate shape, recompute later if requested. - self.update() - - #def itemChange(self, change, value): - #if change == self.ItemScenePositionHasChanged: - #self.updateShape() - - -class TestROI(ROI): - def __init__(self, pos, size, **args): - #QtGui.QGraphicsRectItem.__init__(self, pos[0], pos[1], size[0], size[1]) - ROI.__init__(self, pos, size, **args) - #self.addTranslateHandle([0, 0]) - self.addTranslateHandle([0.5, 0.5]) - self.addScaleHandle([1, 1], [0, 0]) - self.addScaleHandle([0, 0], [1, 1]) - self.addScaleRotateHandle([1, 0.5], [0.5, 0.5]) - self.addScaleHandle([0.5, 1], [0.5, 0.5]) - self.addRotateHandle([1, 0], [0, 0]) - self.addRotateHandle([0, 1], [1, 1]) - - - -class RectROI(ROI): - def __init__(self, pos, size, centered=False, sideScalers=False, **args): - #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) - ROI.__init__(self, pos, size, **args) - if centered: - center = [0.5, 0.5] - else: - center = [0, 0] - - #self.addTranslateHandle(center) - self.addScaleHandle([1, 1], center) - if sideScalers: - self.addScaleHandle([1, 0.5], [center[0], 0.5]) - self.addScaleHandle([0.5, 1], [0.5, center[1]]) - -class LineROI(ROI): - def __init__(self, pos1, pos2, width, **args): - pos1 = Point(pos1) - pos2 = Point(pos2) - d = pos2-pos1 - l = d.length() - ang = Point(1, 0).angle(d) - ra = ang * np.pi / 180. - c = Point(-width/2. * sin(ra), -width/2. * cos(ra)) - pos1 = pos1 + c - - ROI.__init__(self, pos1, size=Point(l, width), angle=ang, **args) - self.addScaleRotateHandle([0, 0.5], [1, 0.5]) - self.addScaleRotateHandle([1, 0.5], [0, 0.5]) - self.addScaleHandle([0.5, 1], [0.5, 0.5]) - - - -class MultiRectROI(QtGui.QGraphicsObject): - """ - Chain of rectangular ROIs connected by handles. - - This is generally used to mark a curved path through - an image similarly to PolyLineROI. It differs in that each segment - of the chain is rectangular instead of linear and thus has width. - """ - sigRegionChangeFinished = QtCore.Signal(object) - sigRegionChangeStarted = QtCore.Signal(object) - sigRegionChanged = QtCore.Signal(object) - - def __init__(self, points, width, pen=None, **args): - QtGui.QGraphicsObject.__init__(self) - self.pen = pen - self.roiArgs = args - self.lines = [] - if len(points) < 2: - raise Exception("Must start with at least 2 points") - - ## create first segment - self.addSegment(points[1], connectTo=points[0], scaleHandle=True) - - ## create remaining segments - for p in points[2:]: - self.addSegment(p) - - - def paint(self, *args): - pass - - def boundingRect(self): - return QtCore.QRectF() - - def roiChangedEvent(self): - w = self.lines[0].state['size'][1] - for l in self.lines[1:]: - w0 = l.state['size'][1] - if w == w0: - continue - l.scale([1.0, w/w0], center=[0.5,0.5]) - self.sigRegionChanged.emit(self) - - def roiChangeStartedEvent(self): - self.sigRegionChangeStarted.emit(self) - - def roiChangeFinishedEvent(self): - self.sigRegionChangeFinished.emit(self) - - def getHandlePositions(self): - """Return the positions of all handles in local coordinates.""" - pos = [self.mapFromScene(self.lines[0].getHandles()[0].scenePos())] - for l in self.lines: - pos.append(self.mapFromScene(l.getHandles()[1].scenePos())) - return pos - - def getArrayRegion(self, arr, img=None, axes=(0,1)): - rgns = [] - for l in self.lines: - rgn = l.getArrayRegion(arr, img, axes=axes) - if rgn is None: - continue - #return None - rgns.append(rgn) - #print l.state['size'] - - ## make sure orthogonal axis is the same size - ## (sometimes fp errors cause differences) - ms = min([r.shape[axes[1]] for r in rgns]) - sl = [slice(None)] * rgns[0].ndim - sl[axes[1]] = slice(0,ms) - rgns = [r[sl] for r in rgns] - #print [r.shape for r in rgns], axes - - return np.concatenate(rgns, axis=axes[0]) - - def addSegment(self, pos=(0,0), scaleHandle=False, connectTo=None): - """ - Add a new segment to the ROI connecting from the previous endpoint to *pos*. - (pos is specified in the parent coordinate system of the MultiRectROI) - """ - - ## by default, connect to the previous endpoint - if connectTo is None: - connectTo = self.lines[-1].getHandles()[1] - - ## create new ROI - newRoi = ROI((0,0), [1, 5], parent=self, pen=self.pen, **self.roiArgs) - self.lines.append(newRoi) - - ## Add first SR handle - if isinstance(connectTo, Handle): - self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=connectTo) - newRoi.movePoint(connectTo, connectTo.scenePos(), coords='scene') - else: - h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5]) - newRoi.movePoint(h, connectTo, coords='scene') - - ## add second SR handle - h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5]) - newRoi.movePoint(h, pos) - - ## optionally add scale handle (this MUST come after the two SR handles) - if scaleHandle: - newRoi.addScaleHandle([0.5, 1], [0.5, 0.5]) - - newRoi.translatable = False - newRoi.sigRegionChanged.connect(self.roiChangedEvent) - newRoi.sigRegionChangeStarted.connect(self.roiChangeStartedEvent) - newRoi.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent) - self.sigRegionChanged.emit(self) - - - def removeSegment(self, index=-1): - """Remove a segment from the ROI.""" - roi = self.lines[index] - self.lines.pop(index) - self.scene().removeItem(roi) - roi.sigRegionChanged.disconnect(self.roiChangedEvent) - roi.sigRegionChangeStarted.disconnect(self.roiChangeStartedEvent) - roi.sigRegionChangeFinished.disconnect(self.roiChangeFinishedEvent) - - self.sigRegionChanged.emit(self) - - -class MultiLineROI(MultiRectROI): - def __init__(self, *args, **kwds): - MultiRectROI.__init__(self, *args, **kwds) - print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)") - -class EllipseROI(ROI): - def __init__(self, pos, size, **args): - #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) - ROI.__init__(self, pos, size, **args) - self.addRotateHandle([1.0, 0.5], [0.5, 0.5]) - self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) - - def paint(self, p, opt, widget): - r = self.boundingRect() - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - - p.scale(r.width(), r.height())## workaround for GL bug - r = QtCore.QRectF(r.x()/r.width(), r.y()/r.height(), 1,1) - - p.drawEllipse(r) - - def getArrayRegion(self, arr, img=None): - arr = ROI.getArrayRegion(self, arr, img) - if arr is None or arr.shape[0] == 0 or arr.shape[1] == 0: - return None - w = arr.shape[0] - h = arr.shape[1] - ## generate an ellipsoidal mask - mask = np.fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h)) - - return arr * mask - - def shape(self): - self.path = QtGui.QPainterPath() - self.path.addEllipse(self.boundingRect()) - return self.path - - -class CircleROI(EllipseROI): - def __init__(self, pos, size, **args): - ROI.__init__(self, pos, size, **args) - self.aspectLocked = True - #self.addTranslateHandle([0.5, 0.5]) - self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) - -class PolygonROI(ROI): - ## deprecated. Use PloyLineROI instead. - - def __init__(self, positions, pos=None, **args): - if pos is None: - pos = [0,0] - ROI.__init__(self, pos, [1,1], **args) - #ROI.__init__(self, positions[0]) - for p in positions: - self.addFreeHandle(p) - self.setZValue(1000) - print("Warning: PolygonROI is deprecated. Use PolyLineROI instead.") - - - def listPoints(self): - return [p['item'].pos() for p in self.handles] - - #def movePoint(self, *args, **kargs): - #ROI.movePoint(self, *args, **kargs) - #self.prepareGeometryChange() - #for h in self.handles: - #h['pos'] = h['item'].pos() - - def paint(self, p, *args): - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - for i in range(len(self.handles)): - h1 = self.handles[i]['item'].pos() - h2 = self.handles[i-1]['item'].pos() - p.drawLine(h1, h2) - - def boundingRect(self): - r = QtCore.QRectF() - for h in self.handles: - r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs - return r - - def shape(self): - p = QtGui.QPainterPath() - p.moveTo(self.handles[0]['item'].pos()) - for i in range(len(self.handles)): - p.lineTo(self.handles[i]['item'].pos()) - return p - - def stateCopy(self): - sc = {} - sc['pos'] = Point(self.state['pos']) - sc['size'] = Point(self.state['size']) - sc['angle'] = self.state['angle'] - #sc['handles'] = self.handles - return sc - -class PolyLineROI(ROI): - """Container class for multiple connected LineSegmentROIs. Responsible for adding new - line segments, and for translation/(rotation?) of multiple lines together.""" - def __init__(self, positions, closed=False, pos=None, **args): - - if pos is None: - pos = [0,0] - - ROI.__init__(self, pos, size=[1,1], **args) - self.closed = closed - self.segments = [] - - for p in positions: - self.addFreeHandle(p) - - start = -1 if self.closed else 0 - for i in range(start, len(self.handles)-1): - self.addSegment(self.handles[i]['item'], self.handles[i+1]['item']) - - def addSegment(self, h1, h2, index=None): - seg = LineSegmentROI(handles=(h1, h2), pen=self.pen, parent=self, movable=False) - if index is None: - self.segments.append(seg) - else: - self.segments.insert(index, seg) - seg.sigClicked.connect(self.segmentClicked) - seg.setAcceptedMouseButtons(QtCore.Qt.LeftButton) - seg.setZValue(self.zValue()+1) - for h in seg.handles: - h['item'].setDeletable(True) - h['item'].setAcceptedMouseButtons(h['item'].acceptedMouseButtons() | QtCore.Qt.LeftButton) ## have these handles take left clicks too, so that handles cannot be added on top of other handles - - def setMouseHover(self, hover): - ## Inform all the ROI's segments that the mouse is(not) hovering over it - ROI.setMouseHover(self, hover) - for s in self.segments: - s.setMouseHover(hover) - - def addHandle(self, info, index=None): - h = ROI.addHandle(self, info, index=index) - h.sigRemoveRequested.connect(self.removeHandle) - return h - - def segmentClicked(self, segment, ev=None, pos=None): ## pos should be in this item's coordinate system - if ev != None: - pos = segment.mapToParent(ev.pos()) - elif pos != None: - pos = pos - else: - raise Exception("Either an event or a position must be given.") - h1 = segment.handles[0]['item'] - h2 = segment.handles[1]['item'] - - i = self.segments.index(segment) - h3 = self.addFreeHandle(pos, index=self.indexOfHandle(h2)) - self.addSegment(h3, h2, index=i+1) - segment.replaceHandle(h2, h3) - - def removeHandle(self, handle, updateSegments=True): - ROI.removeHandle(self, handle) - handle.sigRemoveRequested.disconnect(self.removeHandle) - - if not updateSegments: - return - segments = handle.rois[:] - - if len(segments) == 1: - self.removeSegment(segments[0]) - else: - handles = [h['item'] for h in segments[1].handles] - handles.remove(handle) - segments[0].replaceHandle(handle, handles[0]) - self.removeSegment(segments[1]) - - def removeSegment(self, seg): - for handle in seg.handles[:]: - seg.removeHandle(handle['item']) - self.segments.remove(seg) - seg.sigClicked.disconnect(self.segmentClicked) - self.scene().removeItem(seg) - - def checkRemoveHandle(self, h): - ## called when a handle is about to display its context menu - if self.closed: - return len(self.handles) > 3 - else: - return len(self.handles) > 2 - - def paint(self, p, *args): - #for s in self.segments: - #s.update() - #p.setPen(self.currentPen) - #p.setPen(fn.mkPen('w')) - #p.drawRect(self.boundingRect()) - #p.drawPath(self.shape()) - pass - - def boundingRect(self): - return self.shape().boundingRect() - #r = QtCore.QRectF() - #for h in self.handles: - #r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs - #return r - - def shape(self): - p = QtGui.QPainterPath() - if len(self.handles) == 0: - return p - p.moveTo(self.handles[0]['item'].pos()) - for i in range(len(self.handles)): - p.lineTo(self.handles[i]['item'].pos()) - p.lineTo(self.handles[0]['item'].pos()) - return p - - def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds): - sl = self.getArraySlice(data, img, axes=(0,1)) - if sl is None: - return None - sliced = data[sl[0]] - im = QtGui.QImage(sliced.shape[axes[0]], sliced.shape[axes[1]], QtGui.QImage.Format_ARGB32) - im.fill(0x0) - p = QtGui.QPainter(im) - p.setPen(fn.mkPen(None)) - p.setBrush(fn.mkBrush('w')) - p.setTransform(self.itemTransform(img)[0]) - bounds = self.mapRectToItem(img, self.boundingRect()) - p.translate(-bounds.left(), -bounds.top()) - p.drawPath(self.shape()) - p.end() - mask = fn.imageToArray(im)[:,:,0].astype(float) / 255. - shape = [1] * data.ndim - shape[axes[0]] = sliced.shape[axes[0]] - shape[axes[1]] = sliced.shape[axes[1]] - return sliced * mask.reshape(shape) - - -class LineSegmentROI(ROI): - """ - ROI subclass with two freely-moving handles defining a line. - """ - - def __init__(self, positions=(None, None), pos=None, handles=(None,None), **args): - if pos is None: - pos = [0,0] - - ROI.__init__(self, pos, [1,1], **args) - #ROI.__init__(self, positions[0]) - if len(positions) > 2: - raise Exception("LineSegmentROI must be defined by exactly 2 positions. For more points, use PolyLineROI.") - - for i, p in enumerate(positions): - self.addFreeHandle(p, item=handles[i]) - - - def listPoints(self): - return [p['item'].pos() for p in self.handles] - - def paint(self, p, *args): - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - h1 = self.handles[0]['item'].pos() - h2 = self.handles[1]['item'].pos() - p.drawLine(h1, h2) - - def boundingRect(self): - return self.shape().boundingRect() - - def shape(self): - p = QtGui.QPainterPath() - - h1 = self.handles[0]['item'].pos() - h2 = self.handles[1]['item'].pos() - dh = h2-h1 - if dh.length() == 0: - return p - pxv = self.pixelVectors(dh)[1] - if pxv is None: - return p - - pxv *= 4 - - p.moveTo(h1+pxv) - p.lineTo(h2+pxv) - p.lineTo(h2-pxv) - p.lineTo(h1-pxv) - p.lineTo(h1+pxv) - - return p - - def getArrayRegion(self, data, img, axes=(0,1)): - """ - Use the position of this ROI relative to an imageItem to pull a slice from an array. - Since this pulls 1D data from a 2D coordinate system, the return value will have ndim = data.ndim-1 - """ - - imgPts = [self.mapToItem(img, h['item'].pos()) for h in self.handles] - rgns = [] - for i in range(len(imgPts)-1): - d = Point(imgPts[i+1] - imgPts[i]) - o = Point(imgPts[i]) - r = fn.affineSlice(data, shape=(int(d.length()),), vectors=[Point(d.norm())], origin=o, axes=axes, order=1) - rgns.append(r) - - return np.concatenate(rgns, axis=axes[0]) - - -class SpiralROI(ROI): - def __init__(self, pos=None, size=None, **args): - if size == None: - size = [100e-6,100e-6] - if pos == None: - pos = [0,0] - ROI.__init__(self, pos, size, **args) - self.translateSnap = False - self.addFreeHandle([0.25,0], name='a') - self.addRotateFreeHandle([1,0], [0,0], name='r') - #self.getRadius() - #QtCore.connect(self, QtCore.SIGNAL('regionChanged'), self. - - - def getRadius(self): - radius = Point(self.handles[1]['item'].pos()).length() - #r2 = radius[1] - #r3 = r2[0] - return radius - - def boundingRect(self): - r = self.getRadius() - return QtCore.QRectF(-r*1.1, -r*1.1, 2.2*r, 2.2*r) - #return self.bounds - - #def movePoint(self, *args, **kargs): - #ROI.movePoint(self, *args, **kargs) - #self.prepareGeometryChange() - #for h in self.handles: - #h['pos'] = h['item'].pos()/self.state['size'][0] - - def stateChanged(self, finish=True): - ROI.stateChanged(self, finish=finish) - if len(self.handles) > 1: - self.path = QtGui.QPainterPath() - h0 = Point(self.handles[0]['item'].pos()).length() - a = h0/(2.0*np.pi) - theta = 30.0*(2.0*np.pi)/360.0 - self.path.moveTo(QtCore.QPointF(a*theta*cos(theta), a*theta*sin(theta))) - x0 = a*theta*cos(theta) - y0 = a*theta*sin(theta) - radius = self.getRadius() - theta += 20.0*(2.0*np.pi)/360.0 - i = 0 - while Point(x0, y0).length() < radius and i < 1000: - x1 = a*theta*cos(theta) - y1 = a*theta*sin(theta) - self.path.lineTo(QtCore.QPointF(x1,y1)) - theta += 20.0*(2.0*np.pi)/360.0 - x0 = x1 - y0 = y1 - i += 1 - - - return self.path - - - def shape(self): - p = QtGui.QPainterPath() - p.addEllipse(self.boundingRect()) - return p - - def paint(self, p, *args): - p.setRenderHint(QtGui.QPainter.Antialiasing) - #path = self.shape() - p.setPen(self.currentPen) - p.drawPath(self.path) - p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) - p.drawPath(self.shape()) - p.setPen(QtGui.QPen(QtGui.QColor(0,0,255))) - p.drawRect(self.boundingRect()) - - - - - diff --git a/pyqtgraph/graphicsItems/ScaleBar.py b/pyqtgraph/graphicsItems/ScaleBar.py deleted file mode 100644 index 768f6978..00000000 --- a/pyqtgraph/graphicsItems/ScaleBar.py +++ /dev/null @@ -1,104 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from .GraphicsObject import * -from .GraphicsWidgetAnchor import * -from .TextItem import TextItem -import numpy as np -import pyqtgraph.functions as fn -import pyqtgraph as pg - -__all__ = ['ScaleBar'] - -class ScaleBar(GraphicsObject, GraphicsWidgetAnchor): - """ - Displays a rectangular bar to indicate the relative scale of objects on the view. - """ - def __init__(self, size, width=5, brush=None, pen=None, suffix='m'): - GraphicsObject.__init__(self) - GraphicsWidgetAnchor.__init__(self) - self.setFlag(self.ItemHasNoContents) - self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - - if brush is None: - brush = pg.getConfigOption('foreground') - self.brush = fn.mkBrush(brush) - self.pen = fn.mkPen(pen) - self._width = width - self.size = size - - self.bar = QtGui.QGraphicsRectItem() - self.bar.setPen(self.pen) - self.bar.setBrush(self.brush) - self.bar.setParentItem(self) - - self.text = TextItem(text=fn.siFormat(size, suffix=suffix), anchor=(0.5,1)) - self.text.setParentItem(self) - - def parentChanged(self): - view = self.parentItem() - if view is None: - return - view.sigRangeChanged.connect(self.updateBar) - self.updateBar() - - - def updateBar(self): - view = self.parentItem() - if view is None: - return - p1 = view.mapFromViewToItem(self, QtCore.QPointF(0,0)) - p2 = view.mapFromViewToItem(self, QtCore.QPointF(self.size,0)) - w = (p2-p1).x() - self.bar.setRect(QtCore.QRectF(-w, 0, w, self._width)) - self.text.setPos(-w/2., 0) - - def boundingRect(self): - return QtCore.QRectF() - - - - - -#class ScaleBar(UIGraphicsItem): - #""" - #Displays a rectangular bar with 10 divisions to indicate the relative scale of objects on the view. - #""" - #def __init__(self, size, width=5, color=(100, 100, 255)): - #UIGraphicsItem.__init__(self) - #self.setAcceptedMouseButtons(QtCore.Qt.NoButton) - - #self.brush = fn.mkBrush(color) - #self.pen = fn.mkPen((0,0,0)) - #self._width = width - #self.size = size - - #def paint(self, p, opt, widget): - #UIGraphicsItem.paint(self, p, opt, widget) - - #rect = self.boundingRect() - #unit = self.pixelSize() - #y = rect.top() + (rect.bottom()-rect.top()) * 0.02 - #y1 = y + unit[1]*self._width - #x = rect.right() + (rect.left()-rect.right()) * 0.02 - #x1 = x - self.size - - #p.setPen(self.pen) - #p.setBrush(self.brush) - #rect = QtCore.QRectF( - #QtCore.QPointF(x1, y1), - #QtCore.QPointF(x, y) - #) - #p.translate(x1, y1) - #p.scale(rect.width(), rect.height()) - #p.drawRect(0, 0, 1, 1) - - #alpha = np.clip(((self.size/unit[0]) - 40.) * 255. / 80., 0, 255) - #p.setPen(QtGui.QPen(QtGui.QColor(0, 0, 0, alpha))) - #for i in range(1, 10): - ##x2 = x + (x1-x) * 0.1 * i - #x2 = 0.1 * i - #p.drawLine(QtCore.QPointF(x2, 0), QtCore.QPointF(x2, 1)) - - - #def setSize(self, s): - #self.size = s - diff --git a/pyqtgraph/graphicsItems/ScatterPlotItem.py b/pyqtgraph/graphicsItems/ScatterPlotItem.py deleted file mode 100644 index f1a5201d..00000000 --- a/pyqtgraph/graphicsItems/ScatterPlotItem.py +++ /dev/null @@ -1,925 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE -from pyqtgraph.Point import Point -import pyqtgraph.functions as fn -from .GraphicsItem import GraphicsItem -from .GraphicsObject import GraphicsObject -import numpy as np -import weakref -import pyqtgraph.debug as debug -from pyqtgraph.pgcollections import OrderedDict -import pyqtgraph as pg -#import pyqtgraph as pg - -__all__ = ['ScatterPlotItem', 'SpotItem'] - - -## Build all symbol paths -Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in ['o', 's', 't', 'd', '+', 'x']]) -Symbols['o'].addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) -Symbols['s'].addRect(QtCore.QRectF(-0.5, -0.5, 1, 1)) -coords = { - 't': [(-0.5, -0.5), (0, 0.5), (0.5, -0.5)], - 'd': [(0., -0.5), (-0.4, 0.), (0, 0.5), (0.4, 0)], - '+': [ - (-0.5, -0.05), (-0.5, 0.05), (-0.05, 0.05), (-0.05, 0.5), - (0.05, 0.5), (0.05, 0.05), (0.5, 0.05), (0.5, -0.05), - (0.05, -0.05), (0.05, -0.5), (-0.05, -0.5), (-0.05, -0.05) - ], -} -for k, c in coords.items(): - Symbols[k].moveTo(*c[0]) - for x,y in c[1:]: - Symbols[k].lineTo(x, y) - Symbols[k].closeSubpath() -tr = QtGui.QTransform() -tr.rotate(45) -Symbols['x'] = tr.map(Symbols['+']) - - -def drawSymbol(painter, symbol, size, pen, brush): - if symbol is None: - return - painter.scale(size, size) - painter.setPen(pen) - painter.setBrush(brush) - if isinstance(symbol, basestring): - symbol = Symbols[symbol] - if np.isscalar(symbol): - symbol = list(Symbols.values())[symbol % len(Symbols)] - painter.drawPath(symbol) - - -def renderSymbol(symbol, size, pen, brush, device=None): - """ - Render a symbol specification to QImage. - Symbol may be either a QPainterPath or one of the keys in the Symbols dict. - If *device* is None, a new QPixmap will be returned. Otherwise, - the symbol will be rendered into the device specified (See QPainter documentation - for more information). - """ - ## Render a spot with the given parameters to a pixmap - penPxWidth = max(np.ceil(pen.widthF()), 1) - if device is None: - device = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32) - device.fill(0) - p = QtGui.QPainter(device) - p.setRenderHint(p.Antialiasing) - p.translate(device.width()*0.5, device.height()*0.5) - drawSymbol(p, symbol, size, pen, brush) - p.end() - return device - -def makeSymbolPixmap(size, pen, brush, symbol): - ## deprecated - img = renderSymbol(symbol, size, pen, brush) - return QtGui.QPixmap(img) - -class SymbolAtlas(object): - """ - Used to efficiently construct a single QPixmap containing all rendered symbols - for a ScatterPlotItem. This is required for fragment rendering. - - Use example: - atlas = SymbolAtlas() - sc1 = atlas.getSymbolCoords('o', 5, QPen(..), QBrush(..)) - sc2 = atlas.getSymbolCoords('t', 10, QPen(..), QBrush(..)) - pm = atlas.getAtlas() - - """ - class SymbolCoords(list): ## needed because lists are not allowed in weak references. - pass - - def __init__(self): - # symbol key : [x, y, w, h] atlas coordinates - # note that the coordinate list will always be the same list object as - # long as the symbol is in the atlas, but the coordinates may - # change if the atlas is rebuilt. - # weak value; if all external refs to this list disappear, - # the symbol will be forgotten. - self.symbolMap = weakref.WeakValueDictionary() - - self.atlasData = None # numpy array of atlas image - self.atlas = None # atlas as QPixmap - self.atlasValid = False - - def getSymbolCoords(self, opts): - """ - Given a list of spot records, return an object representing the coordinates of that symbol within the atlas - """ - coords = np.empty(len(opts), dtype=object) - for i, rec in enumerate(opts): - symbol, size, pen, brush = rec['symbol'], rec['size'], rec['pen'], rec['brush'] - pen = fn.mkPen(pen) if not isinstance(pen, QtGui.QPen) else pen - brush = fn.mkBrush(brush) if not isinstance(pen, QtGui.QBrush) else brush - key = (symbol, size, fn.colorTuple(pen.color()), pen.widthF(), pen.style(), fn.colorTuple(brush.color())) - if key not in self.symbolMap: - newCoords = SymbolAtlas.SymbolCoords() - self.symbolMap[key] = newCoords - self.atlasValid = False - #try: - #self.addToAtlas(key) ## squeeze this into the atlas if there is room - #except: - #self.buildAtlas() ## otherwise, we need to rebuild - - coords[i] = self.symbolMap[key] - return coords - - def buildAtlas(self): - # get rendered array for all symbols, keep track of avg/max width - rendered = {} - avgWidth = 0.0 - maxWidth = 0 - images = [] - for key, coords in self.symbolMap.items(): - if len(coords) == 0: - pen = fn.mkPen(color=key[2], width=key[3], style=key[4]) - brush = fn.mkBrush(color=key[5]) - img = renderSymbol(key[0], key[1], pen, brush) - images.append(img) ## we only need this to prevent the images being garbage collected immediately - arr = fn.imageToArray(img, copy=False, transpose=False) - else: - (x,y,w,h) = self.symbolMap[key] - arr = self.atlasData[x:x+w, y:y+w] - rendered[key] = arr - w = arr.shape[0] - avgWidth += w - maxWidth = max(maxWidth, w) - - nSymbols = len(rendered) - if nSymbols > 0: - avgWidth /= nSymbols - width = max(maxWidth, avgWidth * (nSymbols**0.5)) - else: - avgWidth = 0 - width = 0 - - # sort symbols by height - symbols = sorted(rendered.keys(), key=lambda x: rendered[x].shape[1], reverse=True) - - self.atlasRows = [] - - x = width - y = 0 - rowheight = 0 - for key in symbols: - arr = rendered[key] - w,h = arr.shape[:2] - if x+w > width: - y += rowheight - x = 0 - rowheight = h - self.atlasRows.append([y, rowheight, 0]) - self.symbolMap[key][:] = x, y, w, h - x += w - self.atlasRows[-1][2] = x - height = y + rowheight - - self.atlasData = np.zeros((width, height, 4), dtype=np.ubyte) - for key in symbols: - x, y, w, h = self.symbolMap[key] - self.atlasData[x:x+w, y:y+h] = rendered[key] - self.atlas = None - self.atlasValid = True - - def getAtlas(self): - if not self.atlasValid: - self.buildAtlas() - if self.atlas is None: - if len(self.atlasData) == 0: - return QtGui.QPixmap(0,0) - img = fn.makeQImage(self.atlasData, copy=False, transpose=False) - self.atlas = QtGui.QPixmap(img) - return self.atlas - - - - -class ScatterPlotItem(GraphicsObject): - """ - Displays a set of x/y points. Instances of this class are created - automatically as part of PlotDataItem; these rarely need to be instantiated - directly. - - The size, shape, pen, and fill brush may be set for each point individually - or for all points. - - - ======================== =============================================== - **Signals:** - sigPlotChanged(self) Emitted when the data being plotted has changed - sigClicked(self, points) Emitted when the curve is clicked. Sends a list - of all the points under the mouse pointer. - ======================== =============================================== - - """ - #sigPointClicked = QtCore.Signal(object, object) - sigClicked = QtCore.Signal(object, object) ## self, points - sigPlotChanged = QtCore.Signal(object) - def __init__(self, *args, **kargs): - """ - Accepts the same arguments as setData() - """ - prof = debug.Profiler('ScatterPlotItem.__init__', disabled=True) - GraphicsObject.__init__(self) - - self.picture = None # QPicture used for rendering when pxmode==False - self.fragments = None # fragment specification for pxmode; updated every time the view changes. - self.fragmentAtlas = SymbolAtlas() - - self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('data', object), ('fragCoords', object), ('item', object)]) - self.bounds = [None, None] ## caches data bounds - self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots - self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots - self.opts = { - 'pxMode': True, - 'useCache': True, ## If useCache is False, symbols are re-drawn on every paint. - 'antialias': pg.getConfigOption('antialias'), - } - - self.setPen(200,200,200, update=False) - self.setBrush(100,100,150, update=False) - self.setSymbol('o', update=False) - self.setSize(7, update=False) - prof.mark('1') - self.setData(*args, **kargs) - prof.mark('setData') - prof.finish() - - #self.setCacheMode(self.DeviceCoordinateCache) - - def setData(self, *args, **kargs): - """ - **Ordered Arguments:** - - * If there is only one unnamed argument, it will be interpreted like the 'spots' argument. - * If there are two unnamed arguments, they will be interpreted as sequences of x and y values. - - ====================== =============================================================================================== - **Keyword Arguments:** - *spots* Optional list of dicts. Each dict specifies parameters for a single spot: - {'pos': (x,y), 'size', 'pen', 'brush', 'symbol'}. This is just an alternate method - of passing in data for the corresponding arguments. - *x*,*y* 1D arrays of x,y values. - *pos* 2D structure of x,y pairs (such as Nx2 array or list of tuples) - *pxMode* If True, spots are always the same size regardless of scaling, and size is given in px. - Otherwise, size is in scene coordinates and the spots scale with the view. - Default is True - *symbol* can be one (or a list) of: - * 'o' circle (default) - * 's' square - * 't' triangle - * 'd' diamond - * '+' plus - * any QPainterPath to specify custom symbol shapes. To properly obey the position and size, - custom symbols should be centered at (0,0) and width and height of 1.0. Note that it is also - possible to 'install' custom shapes by setting ScatterPlotItem.Symbols[key] = shape. - *pen* The pen (or list of pens) to use for drawing spot outlines. - *brush* The brush (or list of brushes) to use for filling spots. - *size* The size (or list of sizes) of spots. If *pxMode* is True, this value is in pixels. Otherwise, - it is in the item's local coordinate system. - *data* a list of python objects used to uniquely identify each spot. - *identical* *Deprecated*. This functionality is handled automatically now. - *antialias* Whether to draw symbols with antialiasing. Note that if pxMode is True, symbols are - always rendered with antialiasing (since the rendered symbols can be cached, this - incurs very little performance cost) - ====================== =============================================================================================== - """ - oldData = self.data ## this causes cached pixmaps to be preserved while new data is registered. - self.clear() ## clear out all old data - self.addPoints(*args, **kargs) - - def addPoints(self, *args, **kargs): - """ - Add new points to the scatter plot. - Arguments are the same as setData() - """ - - ## deal with non-keyword arguments - if len(args) == 1: - kargs['spots'] = args[0] - elif len(args) == 2: - kargs['x'] = args[0] - kargs['y'] = args[1] - elif len(args) > 2: - raise Exception('Only accepts up to two non-keyword arguments.') - - ## convert 'pos' argument to 'x' and 'y' - if 'pos' in kargs: - pos = kargs['pos'] - if isinstance(pos, np.ndarray): - kargs['x'] = pos[:,0] - kargs['y'] = pos[:,1] - else: - x = [] - y = [] - for p in pos: - if isinstance(p, QtCore.QPointF): - x.append(p.x()) - y.append(p.y()) - else: - x.append(p[0]) - y.append(p[1]) - kargs['x'] = x - kargs['y'] = y - - ## determine how many spots we have - if 'spots' in kargs: - numPts = len(kargs['spots']) - elif 'y' in kargs and kargs['y'] is not None: - numPts = len(kargs['y']) - else: - kargs['x'] = [] - kargs['y'] = [] - numPts = 0 - - ## Extend record array - oldData = self.data - self.data = np.empty(len(oldData)+numPts, dtype=self.data.dtype) - ## note that np.empty initializes object fields to None and string fields to '' - - self.data[:len(oldData)] = oldData - #for i in range(len(oldData)): - #oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array - - newData = self.data[len(oldData):] - newData['size'] = -1 ## indicates to use default size - - if 'spots' in kargs: - spots = kargs['spots'] - for i in range(len(spots)): - spot = spots[i] - for k in spot: - #if k == 'pen': - #newData[k] = fn.mkPen(spot[k]) - #elif k == 'brush': - #newData[k] = fn.mkBrush(spot[k]) - if k == 'pos': - pos = spot[k] - if isinstance(pos, QtCore.QPointF): - x,y = pos.x(), pos.y() - else: - x,y = pos[0], pos[1] - newData[i]['x'] = x - newData[i]['y'] = y - elif k in ['x', 'y', 'size', 'symbol', 'pen', 'brush', 'data']: - newData[i][k] = spot[k] - #elif k == 'data': - #self.pointData[i] = spot[k] - else: - raise Exception("Unknown spot parameter: %s" % k) - elif 'y' in kargs: - newData['x'] = kargs['x'] - newData['y'] = kargs['y'] - - if 'pxMode' in kargs: - self.setPxMode(kargs['pxMode']) - if 'antialias' in kargs: - self.opts['antialias'] = kargs['antialias'] - - ## Set any extra parameters provided in keyword arguments - for k in ['pen', 'brush', 'symbol', 'size']: - if k in kargs: - setMethod = getattr(self, 'set' + k[0].upper() + k[1:]) - setMethod(kargs[k], update=False, dataSet=newData, mask=kargs.get('mask', None)) - - if 'data' in kargs: - self.setPointData(kargs['data'], dataSet=newData) - - self.prepareGeometryChange() - self.bounds = [None, None] - self.invalidate() - self.updateSpots(newData) - self.sigPlotChanged.emit(self) - - def invalidate(self): - ## clear any cached drawing state - self.picture = None - self.fragments = None - self.update() - - def getData(self): - return self.data['x'], self.data['y'] - - - def setPoints(self, *args, **kargs): - ##Deprecated; use setData - return self.setData(*args, **kargs) - - def implements(self, interface=None): - ints = ['plotData'] - if interface is None: - return ints - return interface in ints - - def setPen(self, *args, **kargs): - """Set the pen(s) used to draw the outline around each spot. - If a list or array is provided, then the pen for each spot will be set separately. - Otherwise, the arguments are passed to pg.mkPen and used as the default pen for - all spots which do not have a pen explicitly set.""" - update = kargs.pop('update', True) - dataSet = kargs.pop('dataSet', self.data) - - if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)): - pens = args[0] - if kargs['mask'] is not None: - pens = pens[kargs['mask']] - if len(pens) != len(dataSet): - raise Exception("Number of pens does not match number of points (%d != %d)" % (len(pens), len(dataSet))) - dataSet['pen'] = pens - else: - self.opts['pen'] = fn.mkPen(*args, **kargs) - - dataSet['fragCoords'] = None - if update: - self.updateSpots(dataSet) - - def setBrush(self, *args, **kargs): - """Set the brush(es) used to fill the interior of each spot. - If a list or array is provided, then the brush for each spot will be set separately. - Otherwise, the arguments are passed to pg.mkBrush and used as the default brush for - all spots which do not have a brush explicitly set.""" - update = kargs.pop('update', True) - dataSet = kargs.pop('dataSet', self.data) - - if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)): - brushes = args[0] - if kargs['mask'] is not None: - brushes = brushes[kargs['mask']] - if len(brushes) != len(dataSet): - raise Exception("Number of brushes does not match number of points (%d != %d)" % (len(brushes), len(dataSet))) - #for i in xrange(len(brushes)): - #self.data[i]['brush'] = fn.mkBrush(brushes[i], **kargs) - dataSet['brush'] = brushes - else: - self.opts['brush'] = fn.mkBrush(*args, **kargs) - #self._spotPixmap = None - - dataSet['fragCoords'] = None - if update: - self.updateSpots(dataSet) - - def setSymbol(self, symbol, update=True, dataSet=None, mask=None): - """Set the symbol(s) used to draw each spot. - If a list or array is provided, then the symbol for each spot will be set separately. - Otherwise, the argument will be used as the default symbol for - all spots which do not have a symbol explicitly set.""" - if dataSet is None: - dataSet = self.data - - if isinstance(symbol, np.ndarray) or isinstance(symbol, list): - symbols = symbol - if mask is not None: - symbols = symbols[mask] - if len(symbols) != len(dataSet): - raise Exception("Number of symbols does not match number of points (%d != %d)" % (len(symbols), len(dataSet))) - dataSet['symbol'] = symbols - else: - self.opts['symbol'] = symbol - self._spotPixmap = None - - dataSet['fragCoords'] = None - if update: - self.updateSpots(dataSet) - - def setSize(self, size, update=True, dataSet=None, mask=None): - """Set the size(s) used to draw each spot. - If a list or array is provided, then the size for each spot will be set separately. - Otherwise, the argument will be used as the default size for - all spots which do not have a size explicitly set.""" - if dataSet is None: - dataSet = self.data - - if isinstance(size, np.ndarray) or isinstance(size, list): - sizes = size - if mask is not None: - sizes = sizes[mask] - if len(sizes) != len(dataSet): - raise Exception("Number of sizes does not match number of points (%d != %d)" % (len(sizes), len(dataSet))) - dataSet['size'] = sizes - else: - self.opts['size'] = size - self._spotPixmap = None - - dataSet['fragCoords'] = None - if update: - self.updateSpots(dataSet) - - def setPointData(self, data, dataSet=None, mask=None): - if dataSet is None: - dataSet = self.data - - if isinstance(data, np.ndarray) or isinstance(data, list): - if mask is not None: - data = data[mask] - if len(data) != len(dataSet): - raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(dataSet))) - - ## Bug: If data is a numpy record array, then items from that array must be copied to dataSet one at a time. - ## (otherwise they are converted to tuples and thus lose their field names. - if isinstance(data, np.ndarray) and (data.dtype.fields is not None)and len(data.dtype.fields) > 1: - for i, rec in enumerate(data): - dataSet['data'][i] = rec - else: - dataSet['data'] = data - - def setPxMode(self, mode): - if self.opts['pxMode'] == mode: - return - - self.opts['pxMode'] = mode - self.invalidate() - - def updateSpots(self, dataSet=None): - if dataSet is None: - dataSet = self.data - self._maxSpotWidth = 0 - self._maxSpotPxWidth = 0 - invalidate = False - self.measureSpotSizes(dataSet) - if self.opts['pxMode']: - mask = np.equal(dataSet['fragCoords'], None) - if np.any(mask): - invalidate = True - opts = self.getSpotOpts(dataSet[mask]) - coords = self.fragmentAtlas.getSymbolCoords(opts) - dataSet['fragCoords'][mask] = coords - - #for rec in dataSet: - #if rec['fragCoords'] is None: - #invalidate = True - #rec['fragCoords'] = self.fragmentAtlas.getSymbolCoords(*self.getSpotOpts(rec)) - if invalidate: - self.invalidate() - - def getSpotOpts(self, recs, scale=1.0): - if recs.ndim == 0: - rec = recs - symbol = rec['symbol'] - if symbol is None: - symbol = self.opts['symbol'] - size = rec['size'] - if size < 0: - size = self.opts['size'] - pen = rec['pen'] - if pen is None: - pen = self.opts['pen'] - brush = rec['brush'] - if brush is None: - brush = self.opts['brush'] - return (symbol, size*scale, fn.mkPen(pen), fn.mkBrush(brush)) - else: - recs = recs.copy() - recs['symbol'][np.equal(recs['symbol'], None)] = self.opts['symbol'] - recs['size'][np.equal(recs['size'], -1)] = self.opts['size'] - recs['size'] *= scale - recs['pen'][np.equal(recs['pen'], None)] = fn.mkPen(self.opts['pen']) - recs['brush'][np.equal(recs['brush'], None)] = fn.mkBrush(self.opts['brush']) - return recs - - - - def measureSpotSizes(self, dataSet): - for rec in dataSet: - ## keep track of the maximum spot size and pixel size - symbol, size, pen, brush = self.getSpotOpts(rec) - width = 0 - pxWidth = 0 - if self.opts['pxMode']: - pxWidth = size + pen.widthF() - else: - width = size - if pen.isCosmetic(): - pxWidth += pen.widthF() - else: - width += pen.widthF() - self._maxSpotWidth = max(self._maxSpotWidth, width) - self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth) - self.bounds = [None, None] - - - def clear(self): - """Remove all spots from the scatter plot""" - #self.clearItems() - self.data = np.empty(0, dtype=self.data.dtype) - self.bounds = [None, None] - self.invalidate() - - def dataBounds(self, ax, frac=1.0, orthoRange=None): - if frac >= 1.0 and orthoRange is None and self.bounds[ax] is not None: - return self.bounds[ax] - - #self.prepareGeometryChange() - if self.data is None or len(self.data) == 0: - return (None, None) - - if ax == 0: - d = self.data['x'] - d2 = self.data['y'] - elif ax == 1: - d = self.data['y'] - d2 = self.data['x'] - - if orthoRange is not None: - mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) - d = d[mask] - d2 = d2[mask] - - if frac >= 1.0: - self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072) - return self.bounds[ax] - elif frac <= 0.0: - raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) - else: - mask = np.isfinite(d) - d = d[mask] - return np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) - - def pixelPadding(self): - return self._maxSpotPxWidth*0.7072 - - def boundingRect(self): - (xmn, xmx) = self.dataBounds(ax=0) - (ymn, ymx) = self.dataBounds(ax=1) - if xmn is None or xmx is None: - xmn = 0 - xmx = 0 - if ymn is None or ymx is None: - ymn = 0 - ymx = 0 - - px = py = 0.0 - pxPad = self.pixelPadding() - if pxPad > 0: - # determine length of pixel in local x, y directions - px, py = self.pixelVectors() - px = 0 if px is None else px.length() - py = 0 if py is None else py.length() - - # return bounds expanded by pixel size - px *= pxPad - py *= pxPad - return QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) - - def viewTransformChanged(self): - self.prepareGeometryChange() - GraphicsObject.viewTransformChanged(self) - self.bounds = [None, None] - self.fragments = None - - def generateFragments(self): - tr = self.deviceTransform() - if tr is None: - return - pts = np.empty((2,len(self.data['x']))) - pts[0] = self.data['x'] - pts[1] = self.data['y'] - pts = fn.transformCoordinates(tr, pts) - self.fragments = [] - pts = np.clip(pts, -2**30, 2**30) ## prevent Qt segmentation fault. - ## Still won't be able to render correctly, though. - for i in xrange(len(self.data)): - rec = self.data[i] - pos = QtCore.QPointF(pts[0,i], pts[1,i]) - x,y,w,h = rec['fragCoords'] - rect = QtCore.QRectF(y, x, h, w) - self.fragments.append(QtGui.QPainter.PixmapFragment.create(pos, rect)) - - def setExportMode(self, *args, **kwds): - GraphicsObject.setExportMode(self, *args, **kwds) - self.invalidate() - - @pg.debug.warnOnException ## raising an exception here causes crash - def paint(self, p, *args): - - #p.setPen(fn.mkPen('r')) - #p.drawRect(self.boundingRect()) - - if self._exportOpts is not False: - aa = self._exportOpts.get('antialias', True) - scale = self._exportOpts.get('resolutionScale', 1.0) ## exporting to image; pixel resolution may have changed - else: - aa = self.opts['antialias'] - scale = 1.0 - - if self.opts['pxMode'] is True: - atlas = self.fragmentAtlas.getAtlas() - #arr = fn.imageToArray(atlas.toImage(), copy=True) - #if hasattr(self, 'lastAtlas'): - #if np.any(self.lastAtlas != arr): - #print "Atlas changed:", arr - #self.lastAtlas = arr - - if self.fragments is None: - self.updateSpots() - self.generateFragments() - - p.resetTransform() - - if not USE_PYSIDE and self.opts['useCache'] and self._exportOpts is False: - p.drawPixmapFragments(self.fragments, atlas) - else: - p.setRenderHint(p.Antialiasing, aa) - - for i in range(len(self.data)): - rec = self.data[i] - frag = self.fragments[i] - p.resetTransform() - p.translate(frag.x, frag.y) - drawSymbol(p, *self.getSpotOpts(rec, scale)) - else: - if self.picture is None: - self.picture = QtGui.QPicture() - p2 = QtGui.QPainter(self.picture) - for rec in self.data: - if scale != 1.0: - rec = rec.copy() - rec['size'] *= scale - p2.resetTransform() - p2.translate(rec['x'], rec['y']) - drawSymbol(p2, *self.getSpotOpts(rec, scale)) - p2.end() - - p.setRenderHint(p.Antialiasing, aa) - self.picture.play(p) - - def points(self): - for rec in self.data: - if rec['item'] is None: - rec['item'] = SpotItem(rec, self) - return self.data['item'] - - def pointsAt(self, pos): - x = pos.x() - y = pos.y() - pw = self.pixelWidth() - ph = self.pixelHeight() - pts = [] - for s in self.points(): - sp = s.pos() - ss = s.size() - sx = sp.x() - sy = sp.y() - s2x = s2y = ss * 0.5 - if self.opts['pxMode']: - s2x *= pw - s2y *= ph - if x > sx-s2x and x < sx+s2x and y > sy-s2y and y < sy+s2y: - pts.append(s) - #print "HIT:", x, y, sx, sy, s2x, s2y - #else: - #print "No hit:", (x, y), (sx, sy) - #print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y) - #pts.sort(lambda a,b: cmp(b.zValue(), a.zValue())) - return pts[::-1] - - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.LeftButton: - pts = self.pointsAt(ev.pos()) - if len(pts) > 0: - self.ptsClicked = pts - self.sigClicked.emit(self, self.ptsClicked) - ev.accept() - else: - #print "no spots" - ev.ignore() - else: - ev.ignore() - - -class SpotItem(object): - """ - Class referring to individual spots in a scatter plot. - These can be retrieved by calling ScatterPlotItem.points() or - by connecting to the ScatterPlotItem's click signals. - """ - - def __init__(self, data, plot): - #GraphicsItem.__init__(self, register=False) - self._data = data - self._plot = plot - #self.setParentItem(plot) - #self.setPos(QtCore.QPointF(data['x'], data['y'])) - #self.updateItem() - - def data(self): - """Return the user data associated with this spot.""" - return self._data['data'] - - def size(self): - """Return the size of this spot. - If the spot has no explicit size set, then return the ScatterPlotItem's default size instead.""" - if self._data['size'] == -1: - return self._plot.opts['size'] - else: - return self._data['size'] - - def pos(self): - return Point(self._data['x'], self._data['y']) - - def viewPos(self): - return self._plot.mapToView(self.pos()) - - def setSize(self, size): - """Set the size of this spot. - If the size is set to -1, then the ScatterPlotItem's default size - will be used instead.""" - self._data['size'] = size - self.updateItem() - - def symbol(self): - """Return the symbol of this spot. - If the spot has no explicit symbol set, then return the ScatterPlotItem's default symbol instead. - """ - symbol = self._data['symbol'] - if symbol is None: - symbol = self._plot.opts['symbol'] - try: - n = int(symbol) - symbol = list(Symbols.keys())[n % len(Symbols)] - except: - pass - return symbol - - def setSymbol(self, symbol): - """Set the symbol for this spot. - If the symbol is set to '', then the ScatterPlotItem's default symbol will be used instead.""" - self._data['symbol'] = symbol - self.updateItem() - - def pen(self): - pen = self._data['pen'] - if pen is None: - pen = self._plot.opts['pen'] - return fn.mkPen(pen) - - def setPen(self, *args, **kargs): - """Set the outline pen for this spot""" - pen = fn.mkPen(*args, **kargs) - self._data['pen'] = pen - self.updateItem() - - def resetPen(self): - """Remove the pen set for this spot; the scatter plot's default pen will be used instead.""" - self._data['pen'] = None ## Note this is NOT the same as calling setPen(None) - self.updateItem() - - def brush(self): - brush = self._data['brush'] - if brush is None: - brush = self._plot.opts['brush'] - return fn.mkBrush(brush) - - def setBrush(self, *args, **kargs): - """Set the fill brush for this spot""" - brush = fn.mkBrush(*args, **kargs) - self._data['brush'] = brush - self.updateItem() - - def resetBrush(self): - """Remove the brush set for this spot; the scatter plot's default brush will be used instead.""" - self._data['brush'] = None ## Note this is NOT the same as calling setBrush(None) - self.updateItem() - - def setData(self, data): - """Set the user-data associated with this spot""" - self._data['data'] = data - - def updateItem(self): - self._data['fragCoords'] = None - self._plot.updateSpots(self._data.reshape(1)) - self._plot.invalidate() - -#class PixmapSpotItem(SpotItem, QtGui.QGraphicsPixmapItem): - #def __init__(self, data, plot): - #QtGui.QGraphicsPixmapItem.__init__(self) - #self.setFlags(self.flags() | self.ItemIgnoresTransformations) - #SpotItem.__init__(self, data, plot) - - #def setPixmap(self, pixmap): - #QtGui.QGraphicsPixmapItem.setPixmap(self, pixmap) - #self.setOffset(-pixmap.width()/2.+0.5, -pixmap.height()/2.) - - #def updateItem(self): - #symbolOpts = (self._data['pen'], self._data['brush'], self._data['size'], self._data['symbol']) - - ### If all symbol options are default, use default pixmap - #if symbolOpts == (None, None, -1, ''): - #pixmap = self._plot.defaultSpotPixmap() - #else: - #pixmap = makeSymbolPixmap(size=self.size(), pen=self.pen(), brush=self.brush(), symbol=self.symbol()) - #self.setPixmap(pixmap) - - -#class PathSpotItem(SpotItem, QtGui.QGraphicsPathItem): - #def __init__(self, data, plot): - #QtGui.QGraphicsPathItem.__init__(self) - #SpotItem.__init__(self, data, plot) - - #def updateItem(self): - #QtGui.QGraphicsPathItem.setPath(self, Symbols[self.symbol()]) - #QtGui.QGraphicsPathItem.setPen(self, self.pen()) - #QtGui.QGraphicsPathItem.setBrush(self, self.brush()) - #size = self.size() - #self.resetTransform() - #self.scale(size, size) diff --git a/pyqtgraph/graphicsItems/TextItem.py b/pyqtgraph/graphicsItems/TextItem.py deleted file mode 100644 index 911057f4..00000000 --- a/pyqtgraph/graphicsItems/TextItem.py +++ /dev/null @@ -1,124 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph as pg -from .UIGraphicsItem import * -import pyqtgraph.functions as fn - -class TextItem(UIGraphicsItem): - """ - GraphicsItem displaying unscaled text (the text will always appear normal even inside a scaled ViewBox). - """ - def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0), border=None, fill=None, angle=0): - """ - =========== ================================================================================= - Arguments: - *text* The text to display - *color* The color of the text (any format accepted by pg.mkColor) - *html* If specified, this overrides both *text* and *color* - *anchor* A QPointF or (x,y) sequence indicating what region of the text box will - be anchored to the item's position. A value of (0,0) sets the upper-left corner - of the text box to be at the position specified by setPos(), while a value of (1,1) - sets the lower-right corner. - *border* A pen to use when drawing the border - *fill* A brush to use when filling within the border - =========== ================================================================================= - """ - - ## not working yet - #*angle* Angle in degrees to rotate text (note that the rotation assigned in this item's - #transformation will be ignored) - - self.anchor = pg.Point(anchor) - #self.angle = 0 - UIGraphicsItem.__init__(self) - self.textItem = QtGui.QGraphicsTextItem() - self.textItem.setParentItem(self) - self.lastTransform = None - self._bounds = QtCore.QRectF() - if html is None: - self.setText(text, color) - else: - self.setHtml(html) - self.fill = pg.mkBrush(fill) - self.border = pg.mkPen(border) - self.rotate(angle) - self.setFlag(self.ItemIgnoresTransformations) ## This is required to keep the text unscaled inside the viewport - - def setText(self, text, color=(200,200,200)): - color = pg.mkColor(color) - self.textItem.setDefaultTextColor(color) - self.textItem.setPlainText(text) - self.updateText() - #html = '%s' % (color, text) - #self.setHtml(html) - - def updateAnchor(self): - pass - #self.resetTransform() - #self.translate(0, 20) - - def setPlainText(self, *args): - self.textItem.setPlainText(*args) - self.updateText() - - def setHtml(self, *args): - self.textItem.setHtml(*args) - self.updateText() - - def setTextWidth(self, *args): - self.textItem.setTextWidth(*args) - self.updateText() - - def setFont(self, *args): - self.textItem.setFont(*args) - self.updateText() - - #def setAngle(self, angle): - #self.angle = angle - #self.updateText() - - - def updateText(self): - - ## Needed to maintain font size when rendering to image with increased resolution - self.textItem.resetTransform() - #self.textItem.rotate(self.angle) - if self._exportOpts is not False and 'resolutionScale' in self._exportOpts: - s = self._exportOpts['resolutionScale'] - self.textItem.scale(s, s) - - #br = self.textItem.mapRectToParent(self.textItem.boundingRect()) - self.textItem.setPos(0,0) - br = self.textItem.boundingRect() - apos = self.textItem.mapToParent(pg.Point(br.width()*self.anchor.x(), br.height()*self.anchor.y())) - #print br, apos - self.textItem.setPos(-apos.x(), -apos.y()) - - #def textBoundingRect(self): - ### return the bounds of the text box in device coordinates - #pos = self.mapToDevice(QtCore.QPointF(0,0)) - #if pos is None: - #return None - #tbr = self.textItem.boundingRect() - #return QtCore.QRectF(pos.x() - tbr.width()*self.anchor.x(), pos.y() - tbr.height()*self.anchor.y(), tbr.width(), tbr.height()) - - - def viewRangeChanged(self): - self.updateText() - - def boundingRect(self): - return self.textItem.mapToParent(self.textItem.boundingRect()).boundingRect() - - def paint(self, p, *args): - tr = p.transform() - if self.lastTransform is not None: - if tr != self.lastTransform: - self.viewRangeChanged() - self.lastTransform = tr - - if self.border.style() != QtCore.Qt.NoPen or self.fill.style() != QtCore.Qt.NoBrush: - p.setPen(self.border) - p.setBrush(self.fill) - p.setRenderHint(p.Antialiasing, True) - p.drawPolygon(self.textItem.mapToParent(self.textItem.boundingRect())) - - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/UIGraphicsItem.py b/pyqtgraph/graphicsItems/UIGraphicsItem.py deleted file mode 100644 index 19fda424..00000000 --- a/pyqtgraph/graphicsItems/UIGraphicsItem.py +++ /dev/null @@ -1,124 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE -import weakref -from .GraphicsObject import GraphicsObject -if not USE_PYSIDE: - import sip - -__all__ = ['UIGraphicsItem'] -class UIGraphicsItem(GraphicsObject): - """ - Base class for graphics items with boundaries relative to a GraphicsView or ViewBox. - The purpose of this class is to allow the creation of GraphicsItems which live inside - a scalable view, but whose boundaries will always stay fixed relative to the view's boundaries. - For example: GridItem, InfiniteLine - - The view can be specified on initialization or it can be automatically detected when the item is painted. - - NOTE: Only the item's boundingRect is affected; the item is not transformed in any way. Use viewRangeChanged - to respond to changes in the view. - """ - - #sigViewChanged = QtCore.Signal(object) ## emitted whenever the viewport coords have changed - - def __init__(self, bounds=None, parent=None): - """ - ============== ============================================================================= - **Arguments:** - bounds QRectF with coordinates relative to view box. The default is QRectF(0,0,1,1), - which means the item will have the same bounds as the view. - ============== ============================================================================= - """ - GraphicsObject.__init__(self, parent) - self.setFlag(self.ItemSendsScenePositionChanges) - - if bounds is None: - self._bounds = QtCore.QRectF(0, 0, 1, 1) - else: - self._bounds = bounds - - self._boundingRect = None - self._updateView() - - def paint(self, *args): - ## check for a new view object every time we paint. - #self.updateView() - pass - - def itemChange(self, change, value): - ret = GraphicsObject.itemChange(self, change, value) - - ## workaround for pyqt bug: - ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html - if not USE_PYSIDE and change == self.ItemParentChange and isinstance(ret, QtGui.QGraphicsItem): - ret = sip.cast(ret, QtGui.QGraphicsItem) - - if change == self.ItemScenePositionHasChanged: - self.setNewBounds() - return ret - - #def updateView(self): - ### called to see whether this item has a new view to connect to - - ### check for this item's current viewbox or view widget - #view = self.getViewBox() - #if view is None: - ##print " no view" - #return - - #if self._connectedView is not None and view is self._connectedView(): - ##print " already have view", view - #return - - ### disconnect from previous view - #if self._connectedView is not None: - #cv = self._connectedView() - #if cv is not None: - ##print "disconnect:", self - #cv.sigRangeChanged.disconnect(self.viewRangeChanged) - - ### connect to new view - ##print "connect:", self - #view.sigRangeChanged.connect(self.viewRangeChanged) - #self._connectedView = weakref.ref(view) - #self.setNewBounds() - - def boundingRect(self): - if self._boundingRect is None: - br = self.viewRect() - if br is None: - return QtCore.QRectF() - else: - self._boundingRect = br - return QtCore.QRectF(self._boundingRect) - - def dataBounds(self, axis, frac=1.0, orthoRange=None): - """Called by ViewBox for determining the auto-range bounds. - By default, UIGraphicsItems are excluded from autoRange.""" - return None - - def viewRangeChanged(self): - """Called when the view widget/viewbox is resized/rescaled""" - self.setNewBounds() - self.update() - - def setNewBounds(self): - """Update the item's bounding rect to match the viewport""" - self._boundingRect = None ## invalidate bounding rect, regenerate later if needed. - self.prepareGeometryChange() - - - def setPos(self, *args): - GraphicsObject.setPos(self, *args) - self.setNewBounds() - - def mouseShape(self): - """Return the shape of this item after expanding by 2 pixels""" - shape = self.shape() - ds = self.mapToDevice(shape) - stroker = QtGui.QPainterPathStroker() - stroker.setWidh(2) - ds2 = stroker.createStroke(ds).united(ds) - return self.mapFromDevice(ds2) - - - diff --git a/pyqtgraph/graphicsItems/VTickGroup.py b/pyqtgraph/graphicsItems/VTickGroup.py deleted file mode 100644 index c6880f91..00000000 --- a/pyqtgraph/graphicsItems/VTickGroup.py +++ /dev/null @@ -1,113 +0,0 @@ -if __name__ == '__main__': - import os, sys - path = os.path.abspath(os.path.dirname(__file__)) - sys.path.insert(0, os.path.join(path, '..', '..')) - -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph.functions as fn -import weakref -from .UIGraphicsItem import UIGraphicsItem - -__all__ = ['VTickGroup'] -class VTickGroup(UIGraphicsItem): - """ - **Bases:** :class:`UIGraphicsItem ` - - Draws a set of tick marks which always occupy the same vertical range of the view, - but have x coordinates relative to the data within the view. - - """ - def __init__(self, xvals=None, yrange=None, pen=None): - """ - ============= =================================================================== - **Arguments** - xvals A list of x values (in data coordinates) at which to draw ticks. - yrange A list of [low, high] limits for the tick. 0 is the bottom of - the view, 1 is the top. [0.8, 1] would draw ticks in the top - fifth of the view. - pen The pen to use for drawing ticks. Default is grey. Can be specified - as any argument valid for :func:`mkPen` - ============= =================================================================== - """ - if yrange is None: - yrange = [0, 1] - if xvals is None: - xvals = [] - - UIGraphicsItem.__init__(self) - - if pen is None: - pen = (200, 200, 200) - - self.path = QtGui.QGraphicsPathItem() - - self.ticks = [] - self.xvals = [] - self.yrange = [0,1] - self.setPen(pen) - self.setYRange(yrange) - self.setXVals(xvals) - - def setPen(self, *args, **kwargs): - """Set the pen to use for drawing ticks. Can be specified as any arguments valid - for :func:`mkPen`""" - self.pen = fn.mkPen(*args, **kwargs) - - def setXVals(self, vals): - """Set the x values for the ticks. - - ============= ===================================================================== - **Arguments** - vals A list of x values (in data/plot coordinates) at which to draw ticks. - ============= ===================================================================== - """ - self.xvals = vals - self.rebuildTicks() - #self.valid = False - - def setYRange(self, vals): - """Set the y range [low, high] that the ticks are drawn on. 0 is the bottom of - the view, 1 is the top.""" - self.yrange = vals - self.rebuildTicks() - - def dataBounds(self, *args, **kargs): - return None ## item should never affect view autoscaling - - def yRange(self): - return self.yrange - - def rebuildTicks(self): - self.path = QtGui.QPainterPath() - yrange = self.yRange() - for x in self.xvals: - self.path.moveTo(x, 0.) - self.path.lineTo(x, 1.) - - def paint(self, p, *args): - UIGraphicsItem.paint(self, p, *args) - - br = self.boundingRect() - h = br.height() - br.setY(br.y() + self.yrange[0] * h) - br.setHeight(h - (1.0-self.yrange[1]) * h) - p.translate(0, br.y()) - p.scale(1.0, br.height()) - p.setPen(self.pen) - p.drawPath(self.path) - - -if __name__ == '__main__': - app = QtGui.QApplication([]) - import pyqtgraph as pg - vt = VTickGroup([1,3,4,7,9], [0.8, 1.0]) - p = pg.plot() - p.addItem(vt) - - if sys.flags.interactive == 0: - app.exec_() - - - - - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py deleted file mode 100644 index 3cbb1ea2..00000000 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ /dev/null @@ -1,1570 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.python2_3 import sortList -import numpy as np -from pyqtgraph.Point import Point -import pyqtgraph.functions as fn -from .. ItemGroup import ItemGroup -from .. GraphicsWidget import GraphicsWidget -from pyqtgraph.GraphicsScene import GraphicsScene -import pyqtgraph -import weakref -from copy import deepcopy -import pyqtgraph.debug as debug - -__all__ = ['ViewBox'] - - -class ChildGroup(ItemGroup): - - sigItemsChanged = QtCore.Signal() - def __init__(self, parent): - ItemGroup.__init__(self, parent) - # excempt from telling view when transform changes - self._GraphicsObject__inform_view_on_change = False - - def itemChange(self, change, value): - ret = ItemGroup.itemChange(self, change, value) - if change == self.ItemChildAddedChange or change == self.ItemChildRemovedChange: - self.sigItemsChanged.emit() - - return ret - - -class ViewBox(GraphicsWidget): - """ - **Bases:** :class:`GraphicsWidget ` - - Box that allows internal scaling/panning of children by mouse drag. - This class is usually created automatically as part of a :class:`PlotItem ` or :class:`Canvas ` or with :func:`GraphicsLayout.addViewBox() `. - - Features: - - - Scaling contents by mouse or auto-scale when contents change - - View linking--multiple views display the same data ranges - - Configurable by context menu - - Item coordinate mapping methods - - Not really compatible with GraphicsView having the same functionality. - """ - - sigYRangeChanged = QtCore.Signal(object, object) - sigXRangeChanged = QtCore.Signal(object, object) - sigRangeChangedManually = QtCore.Signal(object) - sigRangeChanged = QtCore.Signal(object, object) - #sigActionPositionChanged = QtCore.Signal(object) - sigStateChanged = QtCore.Signal(object) - sigTransformChanged = QtCore.Signal(object) - sigResized = QtCore.Signal(object) - - ## mouse modes - PanMode = 3 - RectMode = 1 - - ## axes - XAxis = 0 - YAxis = 1 - XYAxes = 2 - - ## for linking views together - NamedViews = weakref.WeakValueDictionary() # name: ViewBox - AllViews = weakref.WeakKeyDictionary() # ViewBox: None - - def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu=True, name=None): - """ - ============= ============================================================= - **Arguments** - *parent* (QGraphicsWidget) Optional parent widget - *border* (QPen) Do draw a border around the view, give any - single argument accepted by :func:`mkPen ` - *lockAspect* (False or float) The aspect ratio to lock the view - coorinates to. (or False to allow the ratio to change) - *enableMouse* (bool) Whether mouse can be used to scale/pan the view - *invertY* (bool) See :func:`invertY ` - ============= ============================================================= - """ - - - - GraphicsWidget.__init__(self, parent) - self.name = None - self.linksBlocked = False - self.addedItems = [] - #self.gView = view - #self.showGrid = showGrid - self._matrixNeedsUpdate = True ## indicates that range has changed, but matrix update was deferred - self._autoRangeNeedsUpdate = True ## indicates auto-range needs to be recomputed. - - self._lastScene = None ## stores reference to the last known scene this view was a part of. - - self.state = { - - ## separating targetRange and viewRange allows the view to be resized - ## while keeping all previously viewed contents visible - 'targetRange': [[0,1], [0,1]], ## child coord. range visible [[xmin, xmax], [ymin, ymax]] - 'viewRange': [[0,1], [0,1]], ## actual range viewed - - 'yInverted': invertY, - 'aspectLocked': False, ## False if aspect is unlocked, otherwise float specifies the locked ratio. - 'autoRange': [True, True], ## False if auto range is disabled, - ## otherwise float gives the fraction of data that is visible - 'autoPan': [False, False], ## whether to only pan (do not change scaling) when auto-range is enabled - 'autoVisibleOnly': [False, False], ## whether to auto-range only to the visible portion of a plot - 'linkedViews': [None, None], ## may be None, "viewName", or weakref.ref(view) - ## a name string indicates that the view *should* link to another, but no view with that name exists yet. - - 'mouseEnabled': [enableMouse, enableMouse], - 'mouseMode': ViewBox.PanMode if pyqtgraph.getConfigOption('leftButtonPan') else ViewBox.RectMode, - 'enableMenu': enableMenu, - 'wheelScaleFactor': -1.0 / 8.0, - - 'background': None, - } - self._updatingRange = False ## Used to break recursive loops. See updateAutoRange. - self._itemBoundsCache = weakref.WeakKeyDictionary() - - self.locateGroup = None ## items displayed when using ViewBox.locate(item) - - self.setFlag(self.ItemClipsChildrenToShape) - self.setFlag(self.ItemIsFocusable, True) ## so we can receive key presses - - ## childGroup is required so that ViewBox has local coordinates similar to device coordinates. - ## this is a workaround for a Qt + OpenGL bug that causes improper clipping - ## https://bugreports.qt.nokia.com/browse/QTBUG-23723 - self.childGroup = ChildGroup(self) - self.childGroup.sigItemsChanged.connect(self.itemsChanged) - - self.background = QtGui.QGraphicsRectItem(self.rect()) - self.background.setParentItem(self) - self.background.setZValue(-1e6) - self.background.setPen(fn.mkPen(None)) - self.updateBackground() - - #self.useLeftButtonPan = pyqtgraph.getConfigOption('leftButtonPan') # normally use left button to pan - # this also enables capture of keyPressEvents. - - ## Make scale box that is shown when dragging on the view - self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1) - self.rbScaleBox.setPen(fn.mkPen((255,255,100), width=1)) - self.rbScaleBox.setBrush(fn.mkBrush(255,255,0,100)) - self.rbScaleBox.setZValue(1e9) - self.rbScaleBox.hide() - self.addItem(self.rbScaleBox, ignoreBounds=True) - - ## show target rect for debugging - self.target = QtGui.QGraphicsRectItem(0, 0, 1, 1) - self.target.setPen(fn.mkPen('r')) - self.target.setParentItem(self) - self.target.hide() - - self.axHistory = [] # maintain a history of zoom locations - self.axHistoryPointer = -1 # pointer into the history. Allows forward/backward movement, not just "undo" - - self.setZValue(-100) - self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) - - self.setAspectLocked(lockAspect) - - self.border = fn.mkPen(border) - self.menu = ViewBoxMenu(self) - - self.register(name) - if name is None: - self.updateViewLists() - - def register(self, name): - """ - Add this ViewBox to the registered list of views. - *name* will appear in the drop-down lists for axis linking in all other views. - The same can be accomplished by initializing the ViewBox with the *name* attribute. - """ - ViewBox.AllViews[self] = None - if self.name is not None: - del ViewBox.NamedViews[self.name] - self.name = name - if name is not None: - ViewBox.NamedViews[name] = self - ViewBox.updateAllViewLists() - sid = id(self) - self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if (ViewBox is not None and 'sid' in locals() and 'name' in locals()) else None) - #self.destroyed.connect(self.unregister) - - def unregister(self): - """ - Remove this ViewBox from the list of linkable views. (see :func:`register() `) - """ - del ViewBox.AllViews[self] - if self.name is not None: - del ViewBox.NamedViews[self.name] - - def close(self): - self.unregister() - - def implements(self, interface): - return interface == 'ViewBox' - - # removed due to https://bugreports.qt-project.org/browse/PYSIDE-86 - #def itemChange(self, change, value): - ## Note: Calling QWidget.itemChange causes segv in python 3 + PyQt - ##ret = QtGui.QGraphicsItem.itemChange(self, change, value) - #ret = GraphicsWidget.itemChange(self, change, value) - #if change == self.ItemSceneChange: - #scene = self.scene() - #if scene is not None and hasattr(scene, 'sigPrepareForPaint'): - #scene.sigPrepareForPaint.disconnect(self.prepareForPaint) - #elif change == self.ItemSceneHasChanged: - #scene = self.scene() - #if scene is not None and hasattr(scene, 'sigPrepareForPaint'): - #scene.sigPrepareForPaint.connect(self.prepareForPaint) - #return ret - - def checkSceneChange(self): - # ViewBox needs to receive sigPrepareForPaint from its scene before - # being painted. However, we have no way of being informed when the - # scene has changed in order to make this connection. The usual way - # to do this is via itemChange(), but bugs prevent this approach - # (see above). Instead, we simply check at every paint to see whether - # (the scene has changed. - scene = self.scene() - if scene == self._lastScene: - return - if self._lastScene is not None and hasattr(self.lastScene, 'sigPrepareForPaint'): - self._lastScene.sigPrepareForPaint.disconnect(self.prepareForPaint) - if scene is not None and hasattr(scene, 'sigPrepareForPaint'): - scene.sigPrepareForPaint.connect(self.prepareForPaint) - self.prepareForPaint() - self._lastScene = scene - - - - - def prepareForPaint(self): - #autoRangeEnabled = (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False) - # don't check whether auto range is enabled here--only check when setting dirty flag. - if self._autoRangeNeedsUpdate: # and autoRangeEnabled: - self.updateAutoRange() - if self._matrixNeedsUpdate: - self.updateMatrix() - - def getState(self, copy=True): - """Return the current state of the ViewBox. - Linked views are always converted to view names in the returned state.""" - state = self.state.copy() - views = [] - for v in state['linkedViews']: - if isinstance(v, weakref.ref): - v = v() - if v is None or isinstance(v, basestring): - views.append(v) - else: - views.append(v.name) - state['linkedViews'] = views - if copy: - return deepcopy(state) - else: - return state - - def setState(self, state): - """Restore the state of this ViewBox. - (see also getState)""" - state = state.copy() - self.setXLink(state['linkedViews'][0]) - self.setYLink(state['linkedViews'][1]) - del state['linkedViews'] - - self.state.update(state) - #self.updateMatrix() - self.updateViewRange() - self.sigStateChanged.emit(self) - - - def setMouseMode(self, mode): - """ - Set the mouse interaction mode. *mode* must be either ViewBox.PanMode or ViewBox.RectMode. - In PanMode, the left mouse button pans the view and the right button scales. - In RectMode, the left button draws a rectangle which updates the visible region (this mode is more suitable for single-button mice) - """ - if mode not in [ViewBox.PanMode, ViewBox.RectMode]: - raise Exception("Mode must be ViewBox.PanMode or ViewBox.RectMode") - self.state['mouseMode'] = mode - self.sigStateChanged.emit(self) - - #def toggleLeftAction(self, act): ## for backward compatibility - #if act.text() is 'pan': - #self.setLeftButtonAction('pan') - #elif act.text() is 'zoom': - #self.setLeftButtonAction('rect') - - def setLeftButtonAction(self, mode='rect'): ## for backward compatibility - if mode.lower() == 'rect': - self.setMouseMode(ViewBox.RectMode) - elif mode.lower() == 'pan': - self.setMouseMode(ViewBox.PanMode) - else: - raise Exception('graphicsItems:ViewBox:setLeftButtonAction: unknown mode = %s (Options are "pan" and "rect")' % mode) - - def innerSceneItem(self): - return self.childGroup - - def setMouseEnabled(self, x=None, y=None): - """ - Set whether each axis is enabled for mouse interaction. *x*, *y* arguments must be True or False. - This allows the user to pan/scale one axis of the view while leaving the other axis unchanged. - """ - if x is not None: - self.state['mouseEnabled'][0] = x - if y is not None: - self.state['mouseEnabled'][1] = y - self.sigStateChanged.emit(self) - - def mouseEnabled(self): - return self.state['mouseEnabled'][:] - - def setMenuEnabled(self, enableMenu=True): - self.state['enableMenu'] = enableMenu - self.sigStateChanged.emit(self) - - def menuEnabled(self): - return self.state.get('enableMenu', True) - - def addItem(self, item, ignoreBounds=False): - """ - Add a QGraphicsItem to this view. The view will include this item when determining how to set its range - automatically unless *ignoreBounds* is True. - """ - if item.zValue() < self.zValue(): - item.setZValue(self.zValue()+1) - scene = self.scene() - if scene is not None and scene is not item.scene(): - scene.addItem(item) ## Necessary due to Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 - item.setParentItem(self.childGroup) - if not ignoreBounds: - self.addedItems.append(item) - self.updateAutoRange() - #print "addItem:", item, item.boundingRect() - - def removeItem(self, item): - """Remove an item from this view.""" - try: - self.addedItems.remove(item) - except: - pass - self.scene().removeItem(item) - self.updateAutoRange() - - def clear(self): - for i in self.addedItems[:]: - self.removeItem(i) - for ch in self.childGroup.childItems(): - ch.setParentItem(None) - - def resizeEvent(self, ev): - self.linkedXChanged() - self.linkedYChanged() - self.updateAutoRange() - self.updateViewRange() - self.sigStateChanged.emit(self) - self.background.setRect(self.rect()) - self.sigResized.emit(self) - - - def viewRange(self): - """Return a the view's visible range as a list: [[xmin, xmax], [ymin, ymax]]""" - return [x[:] for x in self.state['viewRange']] ## return copy - - def viewRect(self): - """Return a QRectF bounding the region visible within the ViewBox""" - try: - vr0 = self.state['viewRange'][0] - vr1 = self.state['viewRange'][1] - return QtCore.QRectF(vr0[0], vr1[0], vr0[1]-vr0[0], vr1[1] - vr1[0]) - except: - print("make qrectf failed:", self.state['viewRange']) - raise - - def targetRange(self): - return [x[:] for x in self.state['targetRange']] ## return copy - - def targetRect(self): - """ - Return the region which has been requested to be visible. - (this is not necessarily the same as the region that is *actually* visible-- - resizing and aspect ratio constraints can cause targetRect() and viewRect() to differ) - """ - try: - tr0 = self.state['targetRange'][0] - tr1 = self.state['targetRange'][1] - return QtCore.QRectF(tr0[0], tr1[0], tr0[1]-tr0[0], tr1[1] - tr1[0]) - except: - print("make qrectf failed:", self.state['targetRange']) - raise - - def setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=True, disableAutoRange=True): - """ - Set the visible range of the ViewBox. - Must specify at least one of *rect*, *xRange*, or *yRange*. - - ================== ===================================================================== - **Arguments** - *rect* (QRectF) The full range that should be visible in the view box. - *xRange* (min,max) The range that should be visible along the x-axis. - *yRange* (min,max) The range that should be visible along the y-axis. - *padding* (float) Expand the view by a fraction of the requested range. - By default, this value is set between 0.02 and 0.1 depending on - the size of the ViewBox. - *update* (bool) If True, update the range of the ViewBox immediately. - Otherwise, the update is deferred until before the next render. - *disableAutoRange* (bool) If True, auto-ranging is diabled. Otherwise, it is left - unchanged. - ================== ===================================================================== - - """ - #print self.name, "ViewBox.setRange", rect, xRange, yRange, padding - #import traceback - #traceback.print_stack() - - changes = {} # axes - setRequested = [False, False] - - if rect is not None: - changes = {0: [rect.left(), rect.right()], 1: [rect.top(), rect.bottom()]} - setRequested = [True, True] - if xRange is not None: - changes[0] = xRange - setRequested[0] = True - if yRange is not None: - changes[1] = yRange - setRequested[1] = True - - if len(changes) == 0: - print(rect) - raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect))) - - # Update axes one at a time - changed = [False, False] - for ax, range in changes.items(): - mn = min(range) - mx = max(range) - - # If we requested 0 range, try to preserve previous scale. - # Otherwise just pick an arbitrary scale. - if mn == mx: - dy = self.state['viewRange'][ax][1] - self.state['viewRange'][ax][0] - if dy == 0: - dy = 1 - mn -= dy*0.5 - mx += dy*0.5 - xpad = 0.0 - - # Make sure no nan/inf get through - if not all(np.isfinite([mn, mx])): - raise Exception("Cannot set range [%s, %s]" % (str(mn), str(mx))) - - # Apply padding - if padding is None: - xpad = self.suggestPadding(ax) - else: - xpad = padding - p = (mx-mn) * xpad - mn -= p - mx += p - - # Set target range - if self.state['targetRange'][ax] != [mn, mx]: - self.state['targetRange'][ax] = [mn, mx] - changed[ax] = True - - # Update viewRange to match targetRange as closely as possible while - # accounting for aspect ratio constraint - lockX, lockY = setRequested - if lockX and lockY: - lockX = False - lockY = False - self.updateViewRange(lockX, lockY) - - # Disable auto-range for each axis that was requested to be set - if disableAutoRange: - xOff = False if setRequested[0] else None - yOff = False if setRequested[1] else None - self.enableAutoRange(x=xOff, y=yOff) - changed.append(True) - - # If nothing has changed, we are done. - if any(changed): - #if update and self.matrixNeedsUpdate: - #self.updateMatrix(changed) - #return - - self.sigStateChanged.emit(self) - - # Update target rect for debugging - if self.target.isVisible(): - self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect())) - - # If ortho axes have auto-visible-only, update them now - # Note that aspect ratio constraints and auto-visible probably do not work together.. - if changed[0] and self.state['autoVisibleOnly'][1] and (self.state['autoRange'][0] is not False): - self._autoRangeNeedsUpdate = True - #self.updateAutoRange() ## Maybe just indicate that auto range needs to be updated? - elif changed[1] and self.state['autoVisibleOnly'][0] and (self.state['autoRange'][1] is not False): - self._autoRangeNeedsUpdate = True - #self.updateAutoRange() - - ## Update view matrix only if requested - #if update: - #self.updateMatrix(changed) - ## Otherwise, indicate that the matrix needs to be updated - #else: - #self.matrixNeedsUpdate = True - - ## Inform linked views that the range has changed <> - #for ax, range in changes.items(): - #link = self.linkedView(ax) - #if link is not None: - #link.linkedViewChanged(self, ax) - - - - def setYRange(self, min, max, padding=None, update=True): - """ - Set the visible Y range of the view to [*min*, *max*]. - The *padding* argument causes the range to be set larger by the fraction specified. - (by default, this value is between 0.02 and 0.1 depending on the size of the ViewBox) - """ - self.setRange(yRange=[min, max], update=update, padding=padding) - - def setXRange(self, min, max, padding=None, update=True): - """ - Set the visible X range of the view to [*min*, *max*]. - The *padding* argument causes the range to be set larger by the fraction specified. - (by default, this value is between 0.02 and 0.1 depending on the size of the ViewBox) - """ - self.setRange(xRange=[min, max], update=update, padding=padding) - - def autoRange(self, padding=None, items=None, item=None): - """ - Set the range of the view box to make all children visible. - Note that this is not the same as enableAutoRange, which causes the view to - automatically auto-range whenever its contents are changed. - - =========== ============================================================ - Arguments - padding The fraction of the total data range to add on to the final - visible range. By default, this value is set between 0.02 - and 0.1 depending on the size of the ViewBox. - items If specified, this is a list of items to consider when - determining the visible range. - =========== ============================================================ - """ - if item is None: - bounds = self.childrenBoundingRect(items=items) - else: - print("Warning: ViewBox.autoRange(item=__) is deprecated. Use 'items' argument instead.") - bounds = self.mapFromItemToView(item, item.boundingRect()).boundingRect() - - if bounds is not None: - self.setRange(bounds, padding=padding) - - def suggestPadding(self, axis): - l = self.width() if axis==0 else self.height() - if l > 0: - padding = np.clip(1./(l**0.5), 0.02, 0.1) - else: - padding = 0.02 - return padding - - def scaleBy(self, s=None, center=None, x=None, y=None): - """ - Scale by *s* around given center point (or center of view). - *s* may be a Point or tuple (x, y). - - Optionally, x or y may be specified individually. This allows the other - axis to be left unaffected (note that using a scale factor of 1.0 may - cause slight changes due to floating-point error). - """ - if s is not None: - scale = Point(s) - else: - scale = [x, y] - - affect = [True, True] - if scale[0] is None and scale[1] is None: - return - elif scale[0] is None: - affect[0] = False - scale[0] = 1.0 - elif scale[1] is None: - affect[1] = False - scale[1] = 1.0 - - scale = Point(scale) - - if self.state['aspectLocked'] is not False: - scale[0] = scale[1] - - vr = self.targetRect() - if center is None: - center = Point(vr.center()) - else: - center = Point(center) - - tl = center + (vr.topLeft()-center) * scale - br = center + (vr.bottomRight()-center) * scale - - if not affect[0]: - self.setYRange(tl.y(), br.y(), padding=0) - elif not affect[1]: - self.setXRange(tl.x(), br.x(), padding=0) - else: - self.setRange(QtCore.QRectF(tl, br), padding=0) - - def translateBy(self, t=None, x=None, y=None): - """ - Translate the view by *t*, which may be a Point or tuple (x, y). - - Alternately, x or y may be specified independently, leaving the other - axis unchanged (note that using a translation of 0 may still cause - small changes due to floating-point error). - """ - vr = self.targetRect() - if t is not None: - t = Point(t) - self.setRange(vr.translated(t), padding=0) - else: - if x is not None: - x = vr.left()+x, vr.right()+x - if y is not None: - y = vr.top()+y, vr.bottom()+y - self.setRange(xRange=x, yRange=y, padding=0) - - - - def enableAutoRange(self, axis=None, enable=True, x=None, y=None): - """ - Enable (or disable) auto-range for *axis*, which may be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes for both - (if *axis* is omitted, both axes will be changed). - When enabled, the axis will automatically rescale when items are added/removed or change their shape. - The argument *enable* may optionally be a float (0.0-1.0) which indicates the fraction of the data that should - be visible (this only works with items implementing a dataRange method, such as PlotDataItem). - """ - #print "autorange:", axis, enable - #if not enable: - #import traceback - #traceback.print_stack() - - # support simpler interface: - if x is not None or y is not None: - if x is not None: - self.enableAutoRange(ViewBox.XAxis, x) - if y is not None: - self.enableAutoRange(ViewBox.YAxis, y) - return - - if enable is True: - enable = 1.0 - - if axis is None: - axis = ViewBox.XYAxes - - needAutoRangeUpdate = False - - if axis == ViewBox.XYAxes or axis == 'xy': - axes = [0, 1] - elif axis == ViewBox.XAxis or axis == 'x': - axes = [0] - elif axis == ViewBox.YAxis or axis == 'y': - axes = [1] - else: - raise Exception('axis argument must be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes.') - - for ax in axes: - if self.state['autoRange'][ax] != enable: - # If we are disabling, do one last auto-range to make sure that - # previously scheduled auto-range changes are enacted - if enable is False and self._autoRangeNeedsUpdate: - self.updateAutoRange() - - self.state['autoRange'][ax] = enable - self._autoRangeNeedsUpdate |= (enable is not False) - self.update() - - - #if needAutoRangeUpdate: - # self.updateAutoRange() - - self.sigStateChanged.emit(self) - - def disableAutoRange(self, axis=None): - """Disables auto-range. (See enableAutoRange)""" - self.enableAutoRange(axis, enable=False) - - def autoRangeEnabled(self): - return self.state['autoRange'][:] - - def setAutoPan(self, x=None, y=None): - if x is not None: - self.state['autoPan'][0] = x - if y is not None: - self.state['autoPan'][1] = y - if None not in [x,y]: - self.updateAutoRange() - - def setAutoVisible(self, x=None, y=None): - if x is not None: - self.state['autoVisibleOnly'][0] = x - if x is True: - self.state['autoVisibleOnly'][1] = False - if y is not None: - self.state['autoVisibleOnly'][1] = y - if y is True: - self.state['autoVisibleOnly'][0] = False - - if x is not None or y is not None: - self.updateAutoRange() - - def updateAutoRange(self): - ## Break recursive loops when auto-ranging. - ## This is needed because some items change their size in response - ## to a view change. - if self._updatingRange: - return - - self._updatingRange = True - try: - targetRect = self.viewRange() - if not any(self.state['autoRange']): - return - - fractionVisible = self.state['autoRange'][:] - for i in [0,1]: - if type(fractionVisible[i]) is bool: - fractionVisible[i] = 1.0 - - childRange = None - - order = [0,1] - if self.state['autoVisibleOnly'][0] is True: - order = [1,0] - - args = {} - for ax in order: - if self.state['autoRange'][ax] is False: - continue - if self.state['autoVisibleOnly'][ax]: - oRange = [None, None] - oRange[ax] = targetRect[1-ax] - childRange = self.childrenBounds(frac=fractionVisible, orthoRange=oRange) - - else: - if childRange is None: - childRange = self.childrenBounds(frac=fractionVisible) - - ## Make corrections to range - xr = childRange[ax] - if xr is not None: - if self.state['autoPan'][ax]: - x = sum(xr) * 0.5 - w2 = (targetRect[ax][1]-targetRect[ax][0]) / 2. - childRange[ax] = [x-w2, x+w2] - else: - padding = self.suggestPadding(ax) - wp = (xr[1] - xr[0]) * padding - childRange[ax][0] -= wp - childRange[ax][1] += wp - targetRect[ax] = childRange[ax] - args['xRange' if ax == 0 else 'yRange'] = targetRect[ax] - if len(args) == 0: - return - args['padding'] = 0 - args['disableAutoRange'] = False - self.setRange(**args) - finally: - self._autoRangeNeedsUpdate = False - self._updatingRange = False - - def setXLink(self, view): - """Link this view's X axis to another view. (see LinkView)""" - self.linkView(self.XAxis, view) - - def setYLink(self, view): - """Link this view's Y axis to another view. (see LinkView)""" - self.linkView(self.YAxis, view) - - - def linkView(self, axis, view): - """ - Link X or Y axes of two views and unlink any previously connected axes. *axis* must be ViewBox.XAxis or ViewBox.YAxis. - If view is None, the axis is left unlinked. - """ - if isinstance(view, basestring): - if view == '': - view = None - else: - view = ViewBox.NamedViews.get(view, view) ## convert view name to ViewBox if possible - - if hasattr(view, 'implements') and view.implements('ViewBoxWrapper'): - view = view.getViewBox() - - ## used to connect/disconnect signals between a pair of views - if axis == ViewBox.XAxis: - signal = 'sigXRangeChanged' - slot = self.linkedXChanged - else: - signal = 'sigYRangeChanged' - slot = self.linkedYChanged - - - oldLink = self.linkedView(axis) - if oldLink is not None: - try: - getattr(oldLink, signal).disconnect(slot) - oldLink.sigResized.disconnect(slot) - except TypeError: - ## This can occur if the view has been deleted already - pass - - - if view is None or isinstance(view, basestring): - self.state['linkedViews'][axis] = view - else: - self.state['linkedViews'][axis] = weakref.ref(view) - getattr(view, signal).connect(slot) - view.sigResized.connect(slot) - if view.autoRangeEnabled()[axis] is not False: - self.enableAutoRange(axis, False) - slot() - else: - if self.autoRangeEnabled()[axis] is False: - slot() - - - self.sigStateChanged.emit(self) - - def blockLink(self, b): - self.linksBlocked = b ## prevents recursive plot-change propagation - - def linkedXChanged(self): - ## called when x range of linked view has changed - view = self.linkedView(0) - self.linkedViewChanged(view, ViewBox.XAxis) - - def linkedYChanged(self): - ## called when y range of linked view has changed - view = self.linkedView(1) - self.linkedViewChanged(view, ViewBox.YAxis) - - def linkedView(self, ax): - ## Return the linked view for axis *ax*. - ## this method _always_ returns either a ViewBox or None. - v = self.state['linkedViews'][ax] - if v is None or isinstance(v, basestring): - return None - else: - return v() ## dereference weakref pointer. If the reference is dead, this returns None - - def linkedViewChanged(self, view, axis): - if self.linksBlocked or view is None: - return - - #print self.name, "ViewBox.linkedViewChanged", axis, view.viewRange()[axis] - vr = view.viewRect() - vg = view.screenGeometry() - sg = self.screenGeometry() - if vg is None or sg is None: - return - - view.blockLink(True) - try: - if axis == ViewBox.XAxis: - overlap = min(sg.right(), vg.right()) - max(sg.left(), vg.left()) - if overlap < min(vg.width()/3, sg.width()/3): ## if less than 1/3 of views overlap, - ## then just replicate the view - x1 = vr.left() - x2 = vr.right() - else: ## views overlap; line them up - upp = float(vr.width()) / vg.width() - x1 = vr.left() + (sg.x()-vg.x()) * upp - x2 = x1 + sg.width() * upp - self.enableAutoRange(ViewBox.XAxis, False) - self.setXRange(x1, x2, padding=0) - else: - overlap = min(sg.bottom(), vg.bottom()) - max(sg.top(), vg.top()) - if overlap < min(vg.height()/3, sg.height()/3): ## if less than 1/3 of views overlap, - ## then just replicate the view - y1 = vr.top() - y2 = vr.bottom() - else: ## views overlap; line them up - upp = float(vr.height()) / vg.height() - if self.yInverted(): - y2 = vr.bottom() + (sg.bottom()-vg.bottom()) * upp - else: - y2 = vr.bottom() + (sg.top()-vg.top()) * upp - y1 = y2 - sg.height() * upp - self.enableAutoRange(ViewBox.YAxis, False) - self.setYRange(y1, y2, padding=0) - finally: - view.blockLink(False) - - - def screenGeometry(self): - """return the screen geometry of the viewbox""" - v = self.getViewWidget() - if v is None: - return None - b = self.sceneBoundingRect() - wr = v.mapFromScene(b).boundingRect() - pos = v.mapToGlobal(v.pos()) - wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) - return wr - - - - def itemsChanged(self): - ## called when items are added/removed from self.childGroup - self.updateAutoRange() - - def itemBoundsChanged(self, item): - self._itemBoundsCache.pop(item, None) - if (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False): - self._autoRangeNeedsUpdate = True - self.update() - #self.updateAutoRange() - - def invertY(self, b=True): - """ - By default, the positive y-axis points upward on the screen. Use invertY(True) to reverse the y-axis. - """ - if self.state['yInverted'] == b: - return - - self.state['yInverted'] = b - #self.updateMatrix(changed=(False, True)) - self.updateViewRange() - self.sigStateChanged.emit(self) - - def yInverted(self): - return self.state['yInverted'] - - def setAspectLocked(self, lock=True, ratio=1): - """ - If the aspect ratio is locked, view scaling must always preserve the aspect ratio. - By default, the ratio is set to 1; x and y both have the same scaling. - This ratio can be overridden (xScale/yScale), or use None to lock in the current ratio. - """ - - if not lock: - if self.state['aspectLocked'] == False: - return - self.state['aspectLocked'] = False - else: - rect = self.rect() - vr = self.viewRect() - if rect.height() == 0 or vr.width() == 0 or vr.height() == 0: - currentRatio = 1.0 - else: - currentRatio = (rect.width()/float(rect.height())) / (vr.width()/vr.height()) - if ratio is None: - ratio = currentRatio - if self.state['aspectLocked'] == ratio: # nothing to change - return - self.state['aspectLocked'] = ratio - if ratio != currentRatio: ## If this would change the current range, do that now - #self.setRange(0, self.state['viewRange'][0][0], self.state['viewRange'][0][1]) - self.updateViewRange() - - self.updateAutoRange() - self.updateViewRange() - self.sigStateChanged.emit(self) - - def childTransform(self): - """ - Return the transform that maps from child(item in the childGroup) coordinates to local coordinates. - (This maps from inside the viewbox to outside) - """ - m = self.childGroup.transform() - #m1 = QtGui.QTransform() - #m1.translate(self.childGroup.pos().x(), self.childGroup.pos().y()) - return m #*m1 - - def mapToView(self, obj): - """Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox""" - m = fn.invertQTransform(self.childTransform()) - return m.map(obj) - - def mapFromView(self, obj): - """Maps from the coordinate system displayed inside the ViewBox to the local coordinates of the ViewBox""" - m = self.childTransform() - return m.map(obj) - - def mapSceneToView(self, obj): - """Maps from scene coordinates to the coordinate system displayed inside the ViewBox""" - return self.mapToView(self.mapFromScene(obj)) - - def mapViewToScene(self, obj): - """Maps from the coordinate system displayed inside the ViewBox to scene coordinates""" - return self.mapToScene(self.mapFromView(obj)) - - def mapFromItemToView(self, item, obj): - """Maps *obj* from the local coordinate system of *item* to the view coordinates""" - return self.childGroup.mapFromItem(item, obj) - #return self.mapSceneToView(item.mapToScene(obj)) - - def mapFromViewToItem(self, item, obj): - """Maps *obj* from view coordinates to the local coordinate system of *item*.""" - return self.childGroup.mapToItem(item, obj) - #return item.mapFromScene(self.mapViewToScene(obj)) - - def mapViewToDevice(self, obj): - return self.mapToDevice(self.mapFromView(obj)) - - def mapDeviceToView(self, obj): - return self.mapToView(self.mapFromDevice(obj)) - - def viewPixelSize(self): - """Return the (width, height) of a screen pixel in view coordinates.""" - o = self.mapToView(Point(0,0)) - px, py = [Point(self.mapToView(v) - o) for v in self.pixelVectors()] - return (px.length(), py.length()) - - - def itemBoundingRect(self, item): - """Return the bounding rect of the item in view coordinates""" - return self.mapSceneToView(item.sceneBoundingRect()).boundingRect() - - #def viewScale(self): - #vr = self.viewRect() - ##print "viewScale:", self.range - #xd = vr.width() - #yd = vr.height() - #if xd == 0 or yd == 0: - #print "Warning: 0 range in view:", xd, yd - #return np.array([1,1]) - - ##cs = self.canvas().size() - #cs = self.boundingRect() - #scale = np.array([cs.width() / xd, cs.height() / yd]) - ##print "view scale:", scale - #return scale - - def wheelEvent(self, ev, axis=None): - mask = np.array(self.state['mouseEnabled'], dtype=np.float) - if axis is not None and axis >= 0 and axis < len(mask): - mv = mask[axis] - mask[:] = 0 - mask[axis] = mv - s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor - - center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) - #center = ev.pos() - - self.scaleBy(s, center) - self.sigRangeChangedManually.emit(self.state['mouseEnabled']) - ev.accept() - - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton and self.menuEnabled(): - ev.accept() - self.raiseContextMenu(ev) - - def raiseContextMenu(self, ev): - #print "viewbox.raiseContextMenu called." - - #menu = self.getMenu(ev) - menu = self.getMenu(ev) - self.scene().addParentContextMenus(self, menu, ev) - #print "2:", [str(a.text()) for a in self.menu.actions()] - pos = ev.screenPos() - #pos2 = ev.scenePos() - #print "3:", [str(a.text()) for a in self.menu.actions()] - #self.sigActionPositionChanged.emit(pos2) - - menu.popup(QtCore.QPoint(pos.x(), pos.y())) - #print "4:", [str(a.text()) for a in self.menu.actions()] - - def getMenu(self, ev): - self._menuCopy = self.menu.copy() ## temporary storage to prevent menu disappearing - return self._menuCopy - - def getContextMenus(self, event): - if self.menuEnabled(): - return self.menu.subMenus() - else: - return None - #return [self.getMenu(event)] - - - def mouseDragEvent(self, ev, axis=None): - ## if axis is specified, event will only affect that axis. - ev.accept() ## we accept all buttons - - pos = ev.pos() - lastPos = ev.lastPos() - dif = pos - lastPos - dif = dif * -1 - - ## Ignore axes if mouse is disabled - mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float) - mask = mouseEnabled.copy() - if axis is not None: - mask[1-axis] = 0.0 - - ## Scale or translate based on mouse button - if ev.button() & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): - if self.state['mouseMode'] == ViewBox.RectMode: - if ev.isFinish(): ## This is the final move in the drag; change the view scale now - #print "finish" - self.rbScaleBox.hide() - #ax = QtCore.QRectF(Point(self.pressPos), Point(self.mousePos)) - ax = QtCore.QRectF(Point(ev.buttonDownPos(ev.button())), Point(pos)) - ax = self.childGroup.mapRectFromParent(ax) - self.showAxRect(ax) - self.axHistoryPointer += 1 - self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax] - else: - ## update shape of scale box - self.updateScaleBox(ev.buttonDownPos(), ev.pos()) - else: - tr = dif*mask - tr = self.mapToView(tr) - self.mapToView(Point(0,0)) - x = tr.x() if mask[0] == 1 else None - y = tr.y() if mask[1] == 1 else None - - self.translateBy(x=x, y=y) - self.sigRangeChangedManually.emit(self.state['mouseEnabled']) - elif ev.button() & QtCore.Qt.RightButton: - #print "vb.rightDrag" - if self.state['aspectLocked'] is not False: - mask[0] = 0 - - dif = ev.screenPos() - ev.lastScreenPos() - dif = np.array([dif.x(), dif.y()]) - dif[0] *= -1 - s = ((mask * 0.02) + 1) ** dif - - tr = self.childGroup.transform() - tr = fn.invertQTransform(tr) - - x = s[0] if mouseEnabled[0] == 1 else None - y = s[1] if mouseEnabled[1] == 1 else None - - center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton))) - self.scaleBy(x=x, y=y, center=center) - self.sigRangeChangedManually.emit(self.state['mouseEnabled']) - - def keyPressEvent(self, ev): - """ - This routine should capture key presses in the current view box. - Key presses are used only when mouse mode is RectMode - The following events are implemented: - ctrl-A : zooms out to the default "full" view of the plot - ctrl-+ : moves forward in the zooming stack (if it exists) - ctrl-- : moves backward in the zooming stack (if it exists) - - """ - #print ev.key() - #print 'I intercepted a key press, but did not accept it' - - ## not implemented yet ? - #self.keypress.sigkeyPressEvent.emit() - - ev.accept() - if ev.text() == '-': - self.scaleHistory(-1) - elif ev.text() in ['+', '=']: - self.scaleHistory(1) - elif ev.key() == QtCore.Qt.Key_Backspace: - self.scaleHistory(len(self.axHistory)) - else: - ev.ignore() - - def scaleHistory(self, d): - ptr = max(0, min(len(self.axHistory)-1, self.axHistoryPointer+d)) - if ptr != self.axHistoryPointer: - self.axHistoryPointer = ptr - self.showAxRect(self.axHistory[ptr]) - - - def updateScaleBox(self, p1, p2): - r = QtCore.QRectF(p1, p2) - r = self.childGroup.mapRectFromParent(r) - self.rbScaleBox.setPos(r.topLeft()) - self.rbScaleBox.resetTransform() - self.rbScaleBox.scale(r.width(), r.height()) - self.rbScaleBox.show() - - def showAxRect(self, ax): - self.setRange(ax.normalized()) # be sure w, h are correct coordinates - self.sigRangeChangedManually.emit(self.state['mouseEnabled']) - - #def mouseRect(self): - #vs = self.viewScale() - #vr = self.state['viewRange'] - ## Convert positions from screen (view) pixel coordinates to axis coordinates - #ax = QtCore.QRectF(self.pressPos[0]/vs[0]+vr[0][0], -(self.pressPos[1]/vs[1]-vr[1][1]), - #(self.mousePos[0]-self.pressPos[0])/vs[0], -(self.mousePos[1]-self.pressPos[1])/vs[1]) - #return(ax) - - def allChildren(self, item=None): - """Return a list of all children and grandchildren of this ViewBox""" - if item is None: - item = self.childGroup - - children = [item] - for ch in item.childItems(): - children.extend(self.allChildren(ch)) - return children - - - - def childrenBounds(self, frac=None, orthoRange=(None,None), items=None): - """Return the bounding range of all children. - [[xmin, xmax], [ymin, ymax]] - Values may be None if there are no specific bounds for an axis. - """ - prof = debug.Profiler('updateAutoRange', disabled=True) - if items is None: - items = self.addedItems - - ## measure pixel dimensions in view box - px, py = [v.length() if v is not None else 0 for v in self.childGroup.pixelVectors()] - - ## First collect all boundary information - itemBounds = [] - for item in items: - if not item.isVisible(): - continue - - useX = True - useY = True - - if hasattr(item, 'dataBounds'): - #bounds = self._itemBoundsCache.get(item, None) - #if bounds is None: - if frac is None: - frac = (1.0, 1.0) - xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) - yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) - pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() - if xr is None or (xr[0] is None and xr[1] is None) or np.isnan(xr).any() or np.isinf(xr).any(): - useX = False - xr = (0,0) - if yr is None or (yr[0] is None and yr[1] is None) or np.isnan(yr).any() or np.isinf(yr).any(): - useY = False - yr = (0,0) - - bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0]) - bounds = self.mapFromItemToView(item, bounds).boundingRect() - - if not any([useX, useY]): - continue - - ## If we are ignoring only one axis, we need to check for rotations - if useX != useY: ## != means xor - ang = round(item.transformAngle()) - if ang == 0 or ang == 180: - pass - elif ang == 90 or ang == 270: - useX, useY = useY, useX - else: - ## Item is rotated at non-orthogonal angle, ignore bounds entirely. - ## Not really sure what is the expected behavior in this case. - continue ## need to check for item rotations and decide how best to apply this boundary. - - - itemBounds.append((bounds, useX, useY, pxPad)) - #self._itemBoundsCache[item] = (bounds, useX, useY) - #else: - #bounds, useX, useY = bounds - else: - if int(item.flags() & item.ItemHasNoContents) > 0: - continue - else: - bounds = item.boundingRect() - bounds = self.mapFromItemToView(item, bounds).boundingRect() - itemBounds.append((bounds, True, True, 0)) - - #print itemBounds - - ## determine tentative new range - range = [None, None] - for bounds, useX, useY, px in itemBounds: - if useY: - if range[1] is not None: - range[1] = [min(bounds.top(), range[1][0]), max(bounds.bottom(), range[1][1])] - else: - range[1] = [bounds.top(), bounds.bottom()] - if useX: - if range[0] is not None: - range[0] = [min(bounds.left(), range[0][0]), max(bounds.right(), range[0][1])] - else: - range[0] = [bounds.left(), bounds.right()] - prof.mark('2') - - #print "range", range - - ## Now expand any bounds that have a pixel margin - ## This must be done _after_ we have a good estimate of the new range - ## to ensure that the pixel size is roughly accurate. - w = self.width() - h = self.height() - #print "w:", w, "h:", h - if w > 0 and range[0] is not None: - pxSize = (range[0][1] - range[0][0]) / w - for bounds, useX, useY, px in itemBounds: - if px == 0 or not useX: - continue - range[0][0] = min(range[0][0], bounds.left() - px*pxSize) - range[0][1] = max(range[0][1], bounds.right() + px*pxSize) - if h > 0 and range[1] is not None: - pxSize = (range[1][1] - range[1][0]) / h - for bounds, useX, useY, px in itemBounds: - if px == 0 or not useY: - continue - range[1][0] = min(range[1][0], bounds.top() - px*pxSize) - range[1][1] = max(range[1][1], bounds.bottom() + px*pxSize) - - #print "final range", range - - prof.finish() - return range - - def childrenBoundingRect(self, *args, **kwds): - range = self.childrenBounds(*args, **kwds) - tr = self.targetRange() - if range[0] is None: - range[0] = tr[0] - if range[1] is None: - range[1] = tr[1] - - bounds = QtCore.QRectF(range[0][0], range[1][0], range[0][1]-range[0][0], range[1][1]-range[1][0]) - return bounds - - def updateViewRange(self, forceX=False, forceY=False): - ## Update viewRange to match targetRange as closely as possible, given - ## aspect ratio constraints. The *force* arguments are used to indicate - ## which axis (if any) should be unchanged when applying constraints. - viewRange = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]] - changed = [False, False] - - # Make correction for aspect ratio constraint - - ## aspect is (widget w/h) / (view range w/h) - aspect = self.state['aspectLocked'] # size ratio / view ratio - tr = self.targetRect() - bounds = self.rect() - if aspect is not False and aspect != 0 and tr.height() != 0 and bounds.height() != 0: - - ## This is the view range aspect ratio we have requested - targetRatio = tr.width() / tr.height() - ## This is the view range aspect ratio we need to obey aspect constraint - viewRatio = (bounds.width() / bounds.height()) / aspect - - # Decide which range to keep unchanged - #print self.name, "aspect:", aspect, "changed:", changed, "auto:", self.state['autoRange'] - if forceX: - ax = 0 - elif forceY: - ax = 1 - else: - # if we are not required to keep a particular axis unchanged, - # then make the entire target range visible - ax = 0 if targetRatio > viewRatio else 1 - - #### these should affect viewRange, not targetRange! - if ax == 0: - ## view range needs to be taller than target - dy = 0.5 * (tr.width() / viewRatio - tr.height()) - if dy != 0: - changed[1] = True - viewRange[1] = [self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy] - else: - ## view range needs to be wider than target - dx = 0.5 * (tr.height() * viewRatio - tr.width()) - if dx != 0: - changed[0] = True - viewRange[0] = [self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx] - - changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) and (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)] - self.state['viewRange'] = viewRange - - # emit range change signals - if changed[0]: - self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0])) - if changed[1]: - self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1])) - - if any(changed): - self.sigRangeChanged.emit(self, self.state['viewRange']) - self.update() - - # Inform linked views that the range has changed - for ax in [0, 1]: - if not changed[ax]: - continue - link = self.linkedView(ax) - if link is not None: - link.linkedViewChanged(self, ax) - - self._matrixNeedsUpdate = True - - def updateMatrix(self, changed=None): - ## Make the childGroup's transform match the requested viewRange. - bounds = self.rect() - - vr = self.viewRect() - if vr.height() == 0 or vr.width() == 0: - return - scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height()) - if not self.state['yInverted']: - scale = scale * Point(1, -1) - m = QtGui.QTransform() - - ## First center the viewport at 0 - center = bounds.center() - m.translate(center.x(), center.y()) - - ## Now scale and translate properly - m.scale(scale[0], scale[1]) - st = Point(vr.center()) - m.translate(-st[0], -st[1]) - - self.childGroup.setTransform(m) - - self.sigTransformChanged.emit(self) ## segfaults here: 1 - self._matrixNeedsUpdate = False - - def paint(self, p, opt, widget): - self.checkSceneChange() - - if self.border is not None: - bounds = self.shape() - p.setPen(self.border) - #p.fillRect(bounds, QtGui.QColor(0, 0, 0)) - p.drawPath(bounds) - - #p.setPen(fn.mkPen('r')) - #path = QtGui.QPainterPath() - #path.addRect(self.targetRect()) - #tr = self.mapFromView(path) - #p.drawPath(tr) - - def updateBackground(self): - bg = self.state['background'] - if bg is None: - self.background.hide() - else: - self.background.show() - self.background.setBrush(fn.mkBrush(bg)) - - - def updateViewLists(self): - try: - self.window() - except RuntimeError: ## this view has already been deleted; it will probably be collected shortly. - return - - def cmpViews(a, b): - wins = 100 * cmp(a.window() is self.window(), b.window() is self.window()) - alpha = cmp(a.name, b.name) - return wins + alpha - - ## make a sorted list of all named views - nv = list(ViewBox.NamedViews.values()) - #print "new view list:", nv - sortList(nv, cmpViews) ## see pyqtgraph.python2_3.sortList - - if self in nv: - nv.remove(self) - - self.menu.setViewList(nv) - - for ax in [0,1]: - link = self.state['linkedViews'][ax] - if isinstance(link, basestring): ## axis has not been linked yet; see if it's possible now - for v in nv: - if link == v.name: - self.linkView(ax, v) - #print "New view list:", nv - #print "linked views:", self.state['linkedViews'] - - @staticmethod - def updateAllViewLists(): - #print "Update:", ViewBox.AllViews.keys() - #print "Update:", ViewBox.NamedViews.keys() - for v in ViewBox.AllViews: - v.updateViewLists() - - - @staticmethod - def forgetView(vid, name): - if ViewBox is None: ## can happen as python is shutting down - return - ## Called with ID and name of view (the view itself is no longer available) - for v in list(ViewBox.AllViews.keys()): - if id(v) == vid: - ViewBox.AllViews.pop(v) - break - ViewBox.NamedViews.pop(name, None) - ViewBox.updateAllViewLists() - - @staticmethod - def quit(): - ## called when the application is about to exit. - ## this disables all callbacks, which might otherwise generate errors if invoked during exit. - for k in ViewBox.AllViews: - try: - k.destroyed.disconnect() - except RuntimeError: ## signal is already disconnected. - pass - except TypeError: ## view has already been deleted (?) - pass - - def locate(self, item, timeout=3.0, children=False): - """ - Temporarily display the bounding rect of an item and lines connecting to the center of the view. - This is useful for determining the location of items that may be out of the range of the ViewBox. - if allChildren is True, then the bounding rect of all item's children will be shown instead. - """ - self.clearLocate() - - if item.scene() is not self.scene(): - raise Exception("Item does not share a scene with this ViewBox.") - - c = self.viewRect().center() - if children: - br = self.mapFromItemToView(item, item.childrenBoundingRect()).boundingRect() - else: - br = self.mapFromItemToView(item, item.boundingRect()).boundingRect() - - g = ItemGroup() - g.setParentItem(self.childGroup) - self.locateGroup = g - g.box = QtGui.QGraphicsRectItem(br) - g.box.setParentItem(g) - g.lines = [] - for p in (br.topLeft(), br.bottomLeft(), br.bottomRight(), br.topRight()): - line = QtGui.QGraphicsLineItem(c.x(), c.y(), p.x(), p.y()) - line.setParentItem(g) - g.lines.append(line) - - for item in g.childItems(): - item.setPen(fn.mkPen(color='y', width=3)) - g.setZValue(1000000) - - if children: - g.path = QtGui.QGraphicsPathItem(g.childrenShape()) - else: - g.path = QtGui.QGraphicsPathItem(g.shape()) - g.path.setParentItem(g) - g.path.setPen(fn.mkPen('g')) - g.path.setZValue(100) - - QtCore.QTimer.singleShot(timeout*1000, self.clearLocate) - - def clearLocate(self): - if self.locateGroup is None: - return - self.scene().removeItem(self.locateGroup) - self.locateGroup = None - -from .ViewBoxMenu import ViewBoxMenu diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py b/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py deleted file mode 100644 index 5242ecdd..00000000 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py +++ /dev/null @@ -1,278 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE -from pyqtgraph.python2_3 import asUnicode -from pyqtgraph.WidgetGroup import WidgetGroup - -if USE_PYSIDE: - from .axisCtrlTemplate_pyside import Ui_Form as AxisCtrlTemplate -else: - from .axisCtrlTemplate_pyqt import Ui_Form as AxisCtrlTemplate - -import weakref - -class ViewBoxMenu(QtGui.QMenu): - def __init__(self, view): - QtGui.QMenu.__init__(self) - - self.view = weakref.ref(view) ## keep weakref to view to avoid circular reference (don't know why, but this prevents the ViewBox from being collected) - self.valid = False ## tells us whether the ui needs to be updated - self.viewMap = weakref.WeakValueDictionary() ## weakrefs to all views listed in the link combos - - self.setTitle("ViewBox options") - self.viewAll = QtGui.QAction("View All", self) - self.viewAll.triggered.connect(self.autoRange) - self.addAction(self.viewAll) - - self.axes = [] - self.ctrl = [] - self.widgetGroups = [] - self.dv = QtGui.QDoubleValidator(self) - for axis in 'XY': - m = QtGui.QMenu() - m.setTitle("%s Axis" % axis) - w = QtGui.QWidget() - ui = AxisCtrlTemplate() - ui.setupUi(w) - a = QtGui.QWidgetAction(self) - a.setDefaultWidget(w) - m.addAction(a) - self.addMenu(m) - self.axes.append(m) - self.ctrl.append(ui) - wg = WidgetGroup(w) - self.widgetGroups.append(w) - - connects = [ - (ui.mouseCheck.toggled, 'MouseToggled'), - (ui.manualRadio.clicked, 'ManualClicked'), - (ui.minText.editingFinished, 'MinTextChanged'), - (ui.maxText.editingFinished, 'MaxTextChanged'), - (ui.autoRadio.clicked, 'AutoClicked'), - (ui.autoPercentSpin.valueChanged, 'AutoSpinChanged'), - (ui.linkCombo.currentIndexChanged, 'LinkComboChanged'), - (ui.autoPanCheck.toggled, 'AutoPanToggled'), - (ui.visibleOnlyCheck.toggled, 'VisibleOnlyToggled') - ] - - for sig, fn in connects: - sig.connect(getattr(self, axis.lower()+fn)) - - self.ctrl[0].invertCheck.hide() ## no invert for x-axis - self.ctrl[1].invertCheck.toggled.connect(self.yInvertToggled) - ## exporting is handled by GraphicsScene now - #self.export = QtGui.QMenu("Export") - #self.setExportMethods(view.exportMethods) - #self.addMenu(self.export) - - self.leftMenu = QtGui.QMenu("Mouse Mode") - group = QtGui.QActionGroup(self) - - # This does not work! QAction _must_ be initialized with a permanent - # object as the parent or else it may be collected prematurely. - #pan = self.leftMenu.addAction("3 button", self.set3ButtonMode) - #zoom = self.leftMenu.addAction("1 button", self.set1ButtonMode) - pan = QtGui.QAction("3 button", self.leftMenu) - zoom = QtGui.QAction("1 button", self.leftMenu) - self.leftMenu.addAction(pan) - self.leftMenu.addAction(zoom) - pan.triggered.connect(self.set3ButtonMode) - zoom.triggered.connect(self.set1ButtonMode) - - pan.setCheckable(True) - zoom.setCheckable(True) - pan.setActionGroup(group) - zoom.setActionGroup(group) - self.mouseModes = [pan, zoom] - self.addMenu(self.leftMenu) - - self.view().sigStateChanged.connect(self.viewStateChanged) - - self.updateState() - - def copy(self): - m = QtGui.QMenu() - for sm in self.subMenus(): - if isinstance(sm, QtGui.QMenu): - m.addMenu(sm) - else: - m.addAction(sm) - m.setTitle(self.title()) - return m - - def subMenus(self): - if not self.valid: - self.updateState() - return [self.viewAll] + self.axes + [self.leftMenu] - - - def setExportMethods(self, methods): - self.exportMethods = methods - self.export.clear() - for opt, fn in methods.items(): - self.export.addAction(opt, self.exportMethod) - - - def viewStateChanged(self): - self.valid = False - if self.ctrl[0].minText.isVisible() or self.ctrl[1].minText.isVisible(): - self.updateState() - - def updateState(self): - ## Something about the viewbox has changed; update the menu GUI - - state = self.view().getState(copy=False) - if state['mouseMode'] == ViewBox.PanMode: - self.mouseModes[0].setChecked(True) - else: - self.mouseModes[1].setChecked(True) - - for i in [0,1]: # x, y - tr = state['targetRange'][i] - self.ctrl[i].minText.setText("%0.5g" % tr[0]) - self.ctrl[i].maxText.setText("%0.5g" % tr[1]) - if state['autoRange'][i] is not False: - self.ctrl[i].autoRadio.setChecked(True) - if state['autoRange'][i] is not True: - self.ctrl[i].autoPercentSpin.setValue(state['autoRange'][i]*100) - else: - self.ctrl[i].manualRadio.setChecked(True) - self.ctrl[i].mouseCheck.setChecked(state['mouseEnabled'][i]) - - ## Update combo to show currently linked view - c = self.ctrl[i].linkCombo - c.blockSignals(True) - try: - view = state['linkedViews'][i] ## will always be string or None - if view is None: - view = '' - - ind = c.findText(view) - - if ind == -1: - ind = 0 - c.setCurrentIndex(ind) - finally: - c.blockSignals(False) - - self.ctrl[i].autoPanCheck.setChecked(state['autoPan'][i]) - self.ctrl[i].visibleOnlyCheck.setChecked(state['autoVisibleOnly'][i]) - - self.ctrl[1].invertCheck.setChecked(state['yInverted']) - self.valid = True - - - def autoRange(self): - self.view().autoRange() ## don't let signal call this directly--it'll add an unwanted argument - - def xMouseToggled(self, b): - self.view().setMouseEnabled(x=b) - - def xManualClicked(self): - self.view().enableAutoRange(ViewBox.XAxis, False) - - def xMinTextChanged(self): - self.ctrl[0].manualRadio.setChecked(True) - self.view().setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0) - - def xMaxTextChanged(self): - self.ctrl[0].manualRadio.setChecked(True) - self.view().setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0) - - def xAutoClicked(self): - val = self.ctrl[0].autoPercentSpin.value() * 0.01 - self.view().enableAutoRange(ViewBox.XAxis, val) - - def xAutoSpinChanged(self, val): - self.ctrl[0].autoRadio.setChecked(True) - self.view().enableAutoRange(ViewBox.XAxis, val*0.01) - - def xLinkComboChanged(self, ind): - self.view().setXLink(str(self.ctrl[0].linkCombo.currentText())) - - def xAutoPanToggled(self, b): - self.view().setAutoPan(x=b) - - def xVisibleOnlyToggled(self, b): - self.view().setAutoVisible(x=b) - - - def yMouseToggled(self, b): - self.view().setMouseEnabled(y=b) - - def yManualClicked(self): - self.view().enableAutoRange(ViewBox.YAxis, False) - - def yMinTextChanged(self): - self.ctrl[1].manualRadio.setChecked(True) - self.view().setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0) - - def yMaxTextChanged(self): - self.ctrl[1].manualRadio.setChecked(True) - self.view().setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0) - - def yAutoClicked(self): - val = self.ctrl[1].autoPercentSpin.value() * 0.01 - self.view().enableAutoRange(ViewBox.YAxis, val) - - def yAutoSpinChanged(self, val): - self.ctrl[1].autoRadio.setChecked(True) - self.view().enableAutoRange(ViewBox.YAxis, val*0.01) - - def yLinkComboChanged(self, ind): - self.view().setYLink(str(self.ctrl[1].linkCombo.currentText())) - - def yAutoPanToggled(self, b): - self.view().setAutoPan(y=b) - - def yVisibleOnlyToggled(self, b): - self.view().setAutoVisible(y=b) - - def yInvertToggled(self, b): - self.view().invertY(b) - - - def exportMethod(self): - act = self.sender() - self.exportMethods[str(act.text())]() - - - def set3ButtonMode(self): - self.view().setLeftButtonAction('pan') - - def set1ButtonMode(self): - self.view().setLeftButtonAction('rect') - - - def setViewList(self, views): - names = [''] - self.viewMap.clear() - - ## generate list of views to show in the link combo - for v in views: - name = v.name - if name is None: ## unnamed views do not show up in the view list (although they are linkable) - continue - names.append(name) - self.viewMap[name] = v - - for i in [0,1]: - c = self.ctrl[i].linkCombo - current = asUnicode(c.currentText()) - c.blockSignals(True) - changed = True - try: - c.clear() - for name in names: - c.addItem(name) - if name == current: - changed = False - c.setCurrentIndex(c.count()-1) - finally: - c.blockSignals(False) - - if changed: - c.setCurrentIndex(0) - c.currentIndexChanged.emit(c.currentIndex()) - -from .ViewBox import ViewBox - - \ No newline at end of file diff --git a/pyqtgraph/graphicsItems/ViewBox/__init__.py b/pyqtgraph/graphicsItems/ViewBox/__init__.py deleted file mode 100644 index 685a314d..00000000 --- a/pyqtgraph/graphicsItems/ViewBox/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .ViewBox import ViewBox diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui deleted file mode 100644 index 297fce75..00000000 --- a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui +++ /dev/null @@ -1,161 +0,0 @@ - - - Form - - - - 0 - 0 - 186 - 154 - - - - - 200 - 16777215 - - - - Form - - - - 0 - - - 0 - - - - - Link Axis: - - - - - - - <html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html> - - - QComboBox::AdjustToContents - - - - - - - true - - - <html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html> - - - % - - - 1 - - - 100 - - - 1 - - - 100 - - - - - - - <html><head/><body><p>Automatically resize this axis whenever the displayed data is changed.</p></body></html> - - - Auto - - - true - - - - - - - <html><head/><body><p>Set the range for this axis manually. This disables automatic scaling. </p></body></html> - - - Manual - - - - - - - <html><head/><body><p>Minimum value to display for this axis.</p></body></html> - - - 0 - - - - - - - <html><head/><body><p>Maximum value to display for this axis.</p></body></html> - - - 0 - - - - - - - <html><head/><body><p>Inverts the display of this axis. (+y points downward instead of upward)</p></body></html> - - - Invert Axis - - - - - - - <html><head/><body><p>Enables mouse interaction (panning, scaling) for this axis.</p></body></html> - - - Mouse Enabled - - - true - - - - - - - <html><head/><body><p>When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.</p></body></html> - - - Visible Data Only - - - - - - - <html><head/><body><p>When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.</p></body></html> - - - Auto Pan Only - - - - - - - - diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py deleted file mode 100644 index db14033e..00000000 --- a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './graphicsItems/ViewBox/axisCtrlTemplate.ui' -# -# Created: Sun Sep 9 14:41:31 2012 -# by: PyQt4 UI code generator 4.9.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName(_fromUtf8("Form")) - Form.resize(186, 154) - Form.setMaximumSize(QtCore.QSize(200, 16777215)) - self.gridLayout = QtGui.QGridLayout(Form) - self.gridLayout.setMargin(0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.label = QtGui.QLabel(Form) - self.label.setObjectName(_fromUtf8("label")) - self.gridLayout.addWidget(self.label, 7, 0, 1, 2) - self.linkCombo = QtGui.QComboBox(Form) - self.linkCombo.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.linkCombo.setObjectName(_fromUtf8("linkCombo")) - self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) - self.autoPercentSpin = QtGui.QSpinBox(Form) - self.autoPercentSpin.setEnabled(True) - self.autoPercentSpin.setMinimum(1) - self.autoPercentSpin.setMaximum(100) - self.autoPercentSpin.setSingleStep(1) - self.autoPercentSpin.setProperty("value", 100) - self.autoPercentSpin.setObjectName(_fromUtf8("autoPercentSpin")) - self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) - self.autoRadio = QtGui.QRadioButton(Form) - self.autoRadio.setChecked(True) - self.autoRadio.setObjectName(_fromUtf8("autoRadio")) - self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) - self.manualRadio = QtGui.QRadioButton(Form) - self.manualRadio.setObjectName(_fromUtf8("manualRadio")) - self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) - self.minText = QtGui.QLineEdit(Form) - self.minText.setObjectName(_fromUtf8("minText")) - self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) - self.maxText = QtGui.QLineEdit(Form) - self.maxText.setObjectName(_fromUtf8("maxText")) - self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) - self.invertCheck = QtGui.QCheckBox(Form) - self.invertCheck.setObjectName(_fromUtf8("invertCheck")) - self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) - self.mouseCheck = QtGui.QCheckBox(Form) - self.mouseCheck.setChecked(True) - self.mouseCheck.setObjectName(_fromUtf8("mouseCheck")) - self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) - self.visibleOnlyCheck = QtGui.QCheckBox(Form) - self.visibleOnlyCheck.setObjectName(_fromUtf8("visibleOnlyCheck")) - self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) - self.autoPanCheck = QtGui.QCheckBox(Form) - self.autoPanCheck.setObjectName(_fromUtf8("autoPanCheck")) - self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8)) - self.linkCombo.setToolTip(QtGui.QApplication.translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPercentSpin.setToolTip(QtGui.QApplication.translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8)) - self.autoRadio.setToolTip(QtGui.QApplication.translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

", None, QtGui.QApplication.UnicodeUTF8)) - self.autoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.manualRadio.setToolTip(QtGui.QApplication.translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

", None, QtGui.QApplication.UnicodeUTF8)) - self.manualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) - self.minText.setToolTip(QtGui.QApplication.translate("Form", "

Minimum value to display for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) - self.minText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) - self.maxText.setToolTip(QtGui.QApplication.translate("Form", "

Maximum value to display for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) - self.maxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) - self.invertCheck.setToolTip(QtGui.QApplication.translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

", None, QtGui.QApplication.UnicodeUTF8)) - self.invertCheck.setText(QtGui.QApplication.translate("Form", "Invert Axis", None, QtGui.QApplication.UnicodeUTF8)) - self.mouseCheck.setToolTip(QtGui.QApplication.translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) - self.mouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse Enabled", None, QtGui.QApplication.UnicodeUTF8)) - self.visibleOnlyCheck.setToolTip(QtGui.QApplication.translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

", None, QtGui.QApplication.UnicodeUTF8)) - self.visibleOnlyCheck.setText(QtGui.QApplication.translate("Form", "Visible Data Only", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPanCheck.setToolTip(QtGui.QApplication.translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPanCheck.setText(QtGui.QApplication.translate("Form", "Auto Pan Only", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py b/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py deleted file mode 100644 index 18510bc2..00000000 --- a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './graphicsItems/ViewBox/axisCtrlTemplate.ui' -# -# Created: Sun Sep 9 14:41:32 2012 -# by: pyside-uic 0.2.13 running on PySide 1.1.0 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(186, 154) - Form.setMaximumSize(QtCore.QSize(200, 16777215)) - self.gridLayout = QtGui.QGridLayout(Form) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setSpacing(0) - self.gridLayout.setObjectName("gridLayout") - self.label = QtGui.QLabel(Form) - self.label.setObjectName("label") - self.gridLayout.addWidget(self.label, 7, 0, 1, 2) - self.linkCombo = QtGui.QComboBox(Form) - self.linkCombo.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.linkCombo.setObjectName("linkCombo") - self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) - self.autoPercentSpin = QtGui.QSpinBox(Form) - self.autoPercentSpin.setEnabled(True) - self.autoPercentSpin.setMinimum(1) - self.autoPercentSpin.setMaximum(100) - self.autoPercentSpin.setSingleStep(1) - self.autoPercentSpin.setProperty("value", 100) - self.autoPercentSpin.setObjectName("autoPercentSpin") - self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) - self.autoRadio = QtGui.QRadioButton(Form) - self.autoRadio.setChecked(True) - self.autoRadio.setObjectName("autoRadio") - self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) - self.manualRadio = QtGui.QRadioButton(Form) - self.manualRadio.setObjectName("manualRadio") - self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) - self.minText = QtGui.QLineEdit(Form) - self.minText.setObjectName("minText") - self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) - self.maxText = QtGui.QLineEdit(Form) - self.maxText.setObjectName("maxText") - self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) - self.invertCheck = QtGui.QCheckBox(Form) - self.invertCheck.setObjectName("invertCheck") - self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) - self.mouseCheck = QtGui.QCheckBox(Form) - self.mouseCheck.setChecked(True) - self.mouseCheck.setObjectName("mouseCheck") - self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) - self.visibleOnlyCheck = QtGui.QCheckBox(Form) - self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") - self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) - self.autoPanCheck = QtGui.QCheckBox(Form) - self.autoPanCheck.setObjectName("autoPanCheck") - self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8)) - self.linkCombo.setToolTip(QtGui.QApplication.translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPercentSpin.setToolTip(QtGui.QApplication.translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8)) - self.autoRadio.setToolTip(QtGui.QApplication.translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

", None, QtGui.QApplication.UnicodeUTF8)) - self.autoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) - self.manualRadio.setToolTip(QtGui.QApplication.translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

", None, QtGui.QApplication.UnicodeUTF8)) - self.manualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) - self.minText.setToolTip(QtGui.QApplication.translate("Form", "

Minimum value to display for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) - self.minText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) - self.maxText.setToolTip(QtGui.QApplication.translate("Form", "

Maximum value to display for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) - self.maxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) - self.invertCheck.setToolTip(QtGui.QApplication.translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

", None, QtGui.QApplication.UnicodeUTF8)) - self.invertCheck.setText(QtGui.QApplication.translate("Form", "Invert Axis", None, QtGui.QApplication.UnicodeUTF8)) - self.mouseCheck.setToolTip(QtGui.QApplication.translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) - self.mouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse Enabled", None, QtGui.QApplication.UnicodeUTF8)) - self.visibleOnlyCheck.setToolTip(QtGui.QApplication.translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

", None, QtGui.QApplication.UnicodeUTF8)) - self.visibleOnlyCheck.setText(QtGui.QApplication.translate("Form", "Visible Data Only", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPanCheck.setToolTip(QtGui.QApplication.translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

", None, QtGui.QApplication.UnicodeUTF8)) - self.autoPanCheck.setText(QtGui.QApplication.translate("Form", "Auto Pan Only", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/pyqtgraph/graphicsItems/__init__.py b/pyqtgraph/graphicsItems/__init__.py deleted file mode 100644 index 8e411816..00000000 --- a/pyqtgraph/graphicsItems/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -### just import everything from sub-modules - -#import os - -#d = os.path.split(__file__)[0] -#files = [] -#for f in os.listdir(d): - #if os.path.isdir(os.path.join(d, f)): - #files.append(f) - #elif f[-3:] == '.py' and f != '__init__.py': - #files.append(f[:-3]) - -#for modName in files: - #mod = __import__(modName, globals(), locals(), fromlist=['*']) - #if hasattr(mod, '__all__'): - #names = mod.__all__ - #else: - #names = [n for n in dir(mod) if n[0] != '_'] - #for k in names: - ##print modName, k - #globals()[k] = getattr(mod, k) diff --git a/pyqtgraph/graphicsItems/tests/ViewBox.py b/pyqtgraph/graphicsItems/tests/ViewBox.py deleted file mode 100644 index 91d9b617..00000000 --- a/pyqtgraph/graphicsItems/tests/ViewBox.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -ViewBox test cases: - -* call setRange then resize; requested range must be fully visible -* lockAspect works correctly for arbitrary aspect ratio -* autoRange works correctly with aspect locked -* call setRange with aspect locked, then resize -* AutoRange with all the bells and whistles - * item moves / changes transformation / changes bounds - * pan only - * fractional range - - -""" - -import pyqtgraph as pg -app = pg.mkQApp() - -imgData = pg.np.zeros((10, 10)) -imgData[0] = 3 -imgData[-1] = 3 -imgData[:,0] = 3 -imgData[:,-1] = 3 - -def testLinkWithAspectLock(): - global win, vb - win = pg.GraphicsWindow() - vb = win.addViewBox(name="image view") - vb.setAspectLocked() - vb.enableAutoRange(x=False, y=False) - p1 = win.addPlot(name="plot 1") - p2 = win.addPlot(name="plot 2", row=1, col=0) - win.ci.layout.setRowFixedHeight(1, 150) - win.ci.layout.setColumnFixedWidth(1, 150) - - def viewsMatch(): - r0 = pg.np.array(vb.viewRange()) - r1 = pg.np.array(p1.vb.viewRange()[1]) - r2 = pg.np.array(p2.vb.viewRange()[1]) - match = (abs(r0[1]-r1) <= (abs(r1) * 0.001)).all() and (abs(r0[0]-r2) <= (abs(r2) * 0.001)).all() - return match - - p1.setYLink(vb) - p2.setXLink(vb) - print "link views match:", viewsMatch() - win.show() - print "show views match:", viewsMatch() - img = pg.ImageItem(imgData) - vb.addItem(img) - vb.autoRange() - p1.plot(x=imgData.sum(axis=0), y=range(10)) - p2.plot(x=range(10), y=imgData.sum(axis=1)) - print "add items views match:", viewsMatch() - #p1.setAspectLocked() - #grid = pg.GridItem() - #vb.addItem(grid) - pg.QtGui.QApplication.processEvents() - pg.QtGui.QApplication.processEvents() - #win.resize(801, 600) - -def testAspectLock(): - global win, vb - win = pg.GraphicsWindow() - vb = win.addViewBox(name="image view") - vb.setAspectLocked() - img = pg.ImageItem(imgData) - vb.addItem(img) - - -#app.processEvents() -#print "init views match:", viewsMatch() -#p2.setYRange(-300, 300) -#print "setRange views match:", viewsMatch() -#app.processEvents() -#print "setRange views match (after update):", viewsMatch() - -#print "--lock aspect--" -#p1.setAspectLocked(True) -#print "lockAspect views match:", viewsMatch() -#p2.setYRange(-200, 200) -#print "setRange views match:", viewsMatch() -#app.processEvents() -#print "setRange views match (after update):", viewsMatch() - -#win.resize(100, 600) -#app.processEvents() -#vb.setRange(xRange=[-10, 10], padding=0) -#app.processEvents() -#win.resize(600, 100) -#app.processEvents() -#print vb.viewRange() - - -if __name__ == '__main__': - testLinkWithAspectLock() diff --git a/pyqtgraph/graphicsWindows.py b/pyqtgraph/graphicsWindows.py deleted file mode 100644 index 6e7d6305..00000000 --- a/pyqtgraph/graphicsWindows.py +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 -*- -""" -graphicsWindows.py - Convenience classes which create a new window with PlotWidget or ImageView. -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - -from .Qt import QtCore, QtGui -from .widgets.PlotWidget import * -from .imageview import * -from .widgets.GraphicsLayoutWidget import GraphicsLayoutWidget -from .widgets.GraphicsView import GraphicsView -QAPP = None - -def mkQApp(): - if QtGui.QApplication.instance() is None: - global QAPP - QAPP = QtGui.QApplication([]) - - -class GraphicsWindow(GraphicsLayoutWidget): - def __init__(self, title=None, size=(800,600), **kargs): - mkQApp() - #self.win = QtGui.QMainWindow() - GraphicsLayoutWidget.__init__(self, **kargs) - #self.win.setCentralWidget(self) - self.resize(*size) - if title is not None: - self.setWindowTitle(title) - self.show() - - -class TabWindow(QtGui.QMainWindow): - def __init__(self, title=None, size=(800,600)): - mkQApp() - QtGui.QMainWindow.__init__(self) - self.resize(*size) - self.cw = QtGui.QTabWidget() - self.setCentralWidget(self.cw) - if title is not None: - self.setWindowTitle(title) - self.show() - - def __getattr__(self, attr): - if hasattr(self.cw, attr): - return getattr(self.cw, attr) - else: - raise NameError(attr) - - -class PlotWindow(PlotWidget): - def __init__(self, title=None, **kargs): - mkQApp() - self.win = QtGui.QMainWindow() - PlotWidget.__init__(self, **kargs) - self.win.setCentralWidget(self) - for m in ['resize']: - setattr(self, m, getattr(self.win, m)) - if title is not None: - self.win.setWindowTitle(title) - self.win.show() - - -class ImageWindow(ImageView): - def __init__(self, *args, **kargs): - mkQApp() - self.win = QtGui.QMainWindow() - self.win.resize(800,600) - if 'title' in kargs: - self.win.setWindowTitle(kargs['title']) - del kargs['title'] - ImageView.__init__(self, self.win) - if len(args) > 0 or len(kargs) > 0: - self.setImage(*args, **kargs) - self.win.setCentralWidget(self) - for m in ['resize']: - setattr(self, m, getattr(self.win, m)) - #for m in ['setImage', 'autoRange', 'addItem', 'removeItem', 'blackLevel', 'whiteLevel', 'imageItem']: - #setattr(self, m, getattr(self.cw, m)) - self.win.show() diff --git a/pyqtgraph/imageview/ImageView.py b/pyqtgraph/imageview/ImageView.py deleted file mode 100644 index 77f34419..00000000 --- a/pyqtgraph/imageview/ImageView.py +++ /dev/null @@ -1,645 +0,0 @@ -# -*- coding: utf-8 -*- -""" -ImageView.py - Widget for basic image dispay and analysis -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. - -Widget used for displaying 2D or 3D data. Features: - - float or int (including 16-bit int) image display via ImageItem - - zoom/pan via GraphicsView - - black/white level controls - - time slider for 3D data sets - - ROI plotting - - Image normalization through a variety of methods -""" -from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE - -if USE_PYSIDE: - from .ImageViewTemplate_pyside import * -else: - from .ImageViewTemplate_pyqt import * - -from pyqtgraph.graphicsItems.ImageItem import * -from pyqtgraph.graphicsItems.ROI import * -from pyqtgraph.graphicsItems.LinearRegionItem import * -from pyqtgraph.graphicsItems.InfiniteLine import * -from pyqtgraph.graphicsItems.ViewBox import * -#from widgets import ROI -import sys -#from numpy import ndarray -import pyqtgraph.ptime as ptime -import numpy as np -import pyqtgraph.debug as debug - -from pyqtgraph.SignalProxy import SignalProxy - -#try: - #import pyqtgraph.metaarray as metaarray - #HAVE_METAARRAY = True -#except: - #HAVE_METAARRAY = False - - -class PlotROI(ROI): - def __init__(self, size): - ROI.__init__(self, pos=[0,0], size=size) #, scaleSnap=True, translateSnap=True) - self.addScaleHandle([1, 1], [0, 0]) - self.addRotateHandle([0, 0], [0.5, 0.5]) - - -class ImageView(QtGui.QWidget): - """ - Widget used for display and analysis of image data. - Implements many features: - - * Displays 2D and 3D image data. For 3D data, a z-axis - slider is displayed allowing the user to select which frame is displayed. - * Displays histogram of image data with movable region defining the dark/light levels - * Editable gradient provides a color lookup table - * Frame slider may also be moved using left/right arrow keys as well as pgup, pgdn, home, and end. - * Basic analysis features including: - - * ROI and embedded plot for measuring image values across frames - * Image normalization / background subtraction - - Basic Usage:: - - imv = pg.ImageView() - imv.show() - imv.setImage(data) - """ - sigTimeChanged = QtCore.Signal(object, object) - sigProcessingChanged = QtCore.Signal(object) - - def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, *args): - """ - By default, this class creates an :class:`ImageItem ` to display image data - and a :class:`ViewBox ` to contain the ImageItem. Custom items may be given instead - by specifying the *view* and/or *imageItem* arguments. - """ - QtGui.QWidget.__init__(self, parent, *args) - self.levelMax = 4096 - self.levelMin = 0 - self.name = name - self.image = None - self.axes = {} - self.imageDisp = None - self.ui = Ui_Form() - self.ui.setupUi(self) - self.scene = self.ui.graphicsView.scene() - - self.ignoreTimeLine = False - - if view is None: - self.view = ViewBox() - else: - self.view = view - self.ui.graphicsView.setCentralItem(self.view) - self.view.setAspectLocked(True) - self.view.invertY() - - if imageItem is None: - self.imageItem = ImageItem() - else: - self.imageItem = imageItem - self.view.addItem(self.imageItem) - self.currentIndex = 0 - - self.ui.histogram.setImageItem(self.imageItem) - - self.ui.normGroup.hide() - - self.roi = PlotROI(10) - self.roi.setZValue(20) - self.view.addItem(self.roi) - self.roi.hide() - self.normRoi = PlotROI(10) - self.normRoi.setPen(QtGui.QPen(QtGui.QColor(255,255,0))) - self.normRoi.setZValue(20) - self.view.addItem(self.normRoi) - self.normRoi.hide() - self.roiCurve = self.ui.roiPlot.plot() - self.timeLine = InfiniteLine(0, movable=True) - self.timeLine.setPen(QtGui.QPen(QtGui.QColor(255, 255, 0, 200))) - self.timeLine.setZValue(1) - self.ui.roiPlot.addItem(self.timeLine) - self.ui.splitter.setSizes([self.height()-35, 35]) - self.ui.roiPlot.hideAxis('left') - - self.keysPressed = {} - self.playTimer = QtCore.QTimer() - self.playRate = 0 - self.lastPlayTime = 0 - - self.normRgn = LinearRegionItem() - self.normRgn.setZValue(0) - self.ui.roiPlot.addItem(self.normRgn) - self.normRgn.hide() - - ## wrap functions from view box - for fn in ['addItem', 'removeItem']: - setattr(self, fn, getattr(self.view, fn)) - - ## wrap functions from histogram - for fn in ['setHistogramRange', 'autoHistogramRange', 'getLookupTable', 'getLevels']: - setattr(self, fn, getattr(self.ui.histogram, fn)) - - self.timeLine.sigPositionChanged.connect(self.timeLineChanged) - self.ui.roiBtn.clicked.connect(self.roiClicked) - self.roi.sigRegionChanged.connect(self.roiChanged) - self.ui.normBtn.toggled.connect(self.normToggled) - self.ui.normDivideRadio.clicked.connect(self.normRadioChanged) - self.ui.normSubtractRadio.clicked.connect(self.normRadioChanged) - self.ui.normOffRadio.clicked.connect(self.normRadioChanged) - self.ui.normROICheck.clicked.connect(self.updateNorm) - self.ui.normFrameCheck.clicked.connect(self.updateNorm) - self.ui.normTimeRangeCheck.clicked.connect(self.updateNorm) - self.playTimer.timeout.connect(self.timeout) - - self.normProxy = SignalProxy(self.normRgn.sigRegionChanged, slot=self.updateNorm) - self.normRoi.sigRegionChangeFinished.connect(self.updateNorm) - - self.ui.roiPlot.registerPlot(self.name + '_ROI') - - self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown] - - self.roiClicked() ## initialize roi plot to correct shape / visibility - - def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None, transform=None, autoHistogramRange=True): - """ - Set the image to be displayed in the widget. - - ================== ======================================================================= - **Arguments:** - img (numpy array) the image to be displayed. - xvals (numpy array) 1D array of z-axis values corresponding to the third axis - in a 3D image. For video, this array should contain the time of each frame. - autoRange (bool) whether to scale/pan the view to fit the image. - autoLevels (bool) whether to update the white/black levels to fit the image. - levels (min, max); the white and black level values to use. - axes Dictionary indicating the interpretation for each axis. - This is only needed to override the default guess. Format is:: - - {'t':0, 'x':1, 'y':2, 'c':3}; - - pos Change the position of the displayed image - scale Change the scale of the displayed image - transform Set the transform of the displayed image. This option overrides *pos* - and *scale*. - autoHistogramRange If True, the histogram y-range is automatically scaled to fit the - image data. - ================== ======================================================================= - """ - prof = debug.Profiler('ImageView.setImage', disabled=True) - - if hasattr(img, 'implements') and img.implements('MetaArray'): - img = img.asarray() - - if not isinstance(img, np.ndarray): - raise Exception("Image must be specified as ndarray.") - self.image = img - - if xvals is not None: - self.tVals = xvals - elif hasattr(img, 'xvals'): - try: - self.tVals = img.xvals(0) - except: - self.tVals = np.arange(img.shape[0]) - else: - self.tVals = np.arange(img.shape[0]) - - prof.mark('1') - - if axes is None: - if img.ndim == 2: - self.axes = {'t': None, 'x': 0, 'y': 1, 'c': None} - elif img.ndim == 3: - if img.shape[2] <= 4: - self.axes = {'t': None, 'x': 0, 'y': 1, 'c': 2} - else: - self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None} - elif img.ndim == 4: - self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3} - else: - raise Exception("Can not interpret image with dimensions %s" % (str(img.shape))) - elif isinstance(axes, dict): - self.axes = axes.copy() - elif isinstance(axes, list) or isinstance(axes, tuple): - self.axes = {} - for i in range(len(axes)): - self.axes[axes[i]] = i - else: - raise Exception("Can not interpret axis specification %s. Must be like {'t': 2, 'x': 0, 'y': 1} or ('t', 'x', 'y', 'c')" % (str(axes))) - - for x in ['t', 'x', 'y', 'c']: - self.axes[x] = self.axes.get(x, None) - prof.mark('2') - - self.imageDisp = None - - - prof.mark('3') - - self.currentIndex = 0 - self.updateImage(autoHistogramRange=autoHistogramRange) - if levels is None and autoLevels: - self.autoLevels() - if levels is not None: ## this does nothing since getProcessedImage sets these values again. - self.setLevels(*levels) - - if self.ui.roiBtn.isChecked(): - self.roiChanged() - prof.mark('4') - - - if self.axes['t'] is not None: - #self.ui.roiPlot.show() - self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max()) - self.timeLine.setValue(0) - #self.ui.roiPlot.setMouseEnabled(False, False) - if len(self.tVals) > 1: - start = self.tVals.min() - stop = self.tVals.max() + abs(self.tVals[-1] - self.tVals[0]) * 0.02 - elif len(self.tVals) == 1: - start = self.tVals[0] - 0.5 - stop = self.tVals[0] + 0.5 - else: - start = 0 - stop = 1 - for s in [self.timeLine, self.normRgn]: - s.setBounds([start, stop]) - #else: - #self.ui.roiPlot.hide() - prof.mark('5') - - self.imageItem.resetTransform() - if scale is not None: - self.imageItem.scale(*scale) - if pos is not None: - self.imageItem.setPos(*pos) - if transform is not None: - self.imageItem.setTransform(transform) - prof.mark('6') - - if autoRange: - self.autoRange() - self.roiClicked() - prof.mark('7') - prof.finish() - - - def play(self, rate): - """Begin automatically stepping frames forward at the given rate (in fps). - This can also be accessed by pressing the spacebar.""" - #print "play:", rate - self.playRate = rate - if rate == 0: - self.playTimer.stop() - return - - self.lastPlayTime = ptime.time() - if not self.playTimer.isActive(): - self.playTimer.start(16) - - def autoLevels(self): - """Set the min/max intensity levels automatically to match the image data.""" - self.setLevels(self.levelMin, self.levelMax) - - def setLevels(self, min, max): - """Set the min/max (bright and dark) levels.""" - self.ui.histogram.setLevels(min, max) - - def autoRange(self): - """Auto scale and pan the view around the image.""" - image = self.getProcessedImage() - self.view.autoRange() - - def getProcessedImage(self): - """Returns the image data after it has been processed by any normalization options in use. - This method also sets the attributes self.levelMin and self.levelMax - to indicate the range of data in the image.""" - if self.imageDisp is None: - image = self.normalize(self.image) - self.imageDisp = image - self.levelMin, self.levelMax = list(map(float, ImageView.quickMinMax(self.imageDisp))) - - return self.imageDisp - - - def close(self): - """Closes the widget nicely, making sure to clear the graphics scene and release memory.""" - self.ui.roiPlot.close() - self.ui.graphicsView.close() - self.scene.clear() - del self.image - del self.imageDisp - self.setParent(None) - - def keyPressEvent(self, ev): - #print ev.key() - if ev.key() == QtCore.Qt.Key_Space: - if self.playRate == 0: - fps = (self.getProcessedImage().shape[0]-1) / (self.tVals[-1] - self.tVals[0]) - self.play(fps) - #print fps - else: - self.play(0) - ev.accept() - elif ev.key() == QtCore.Qt.Key_Home: - self.setCurrentIndex(0) - self.play(0) - ev.accept() - elif ev.key() == QtCore.Qt.Key_End: - self.setCurrentIndex(self.getProcessedImage().shape[0]-1) - self.play(0) - ev.accept() - elif ev.key() in self.noRepeatKeys: - ev.accept() - if ev.isAutoRepeat(): - return - self.keysPressed[ev.key()] = 1 - self.evalKeyState() - else: - QtGui.QWidget.keyPressEvent(self, ev) - - def keyReleaseEvent(self, ev): - if ev.key() in [QtCore.Qt.Key_Space, QtCore.Qt.Key_Home, QtCore.Qt.Key_End]: - ev.accept() - elif ev.key() in self.noRepeatKeys: - ev.accept() - if ev.isAutoRepeat(): - return - try: - del self.keysPressed[ev.key()] - except: - self.keysPressed = {} - self.evalKeyState() - else: - QtGui.QWidget.keyReleaseEvent(self, ev) - - - def evalKeyState(self): - if len(self.keysPressed) == 1: - key = list(self.keysPressed.keys())[0] - if key == QtCore.Qt.Key_Right: - self.play(20) - self.jumpFrames(1) - self.lastPlayTime = ptime.time() + 0.2 ## 2ms wait before start - ## This happens *after* jumpFrames, since it might take longer than 2ms - elif key == QtCore.Qt.Key_Left: - self.play(-20) - self.jumpFrames(-1) - self.lastPlayTime = ptime.time() + 0.2 - elif key == QtCore.Qt.Key_Up: - self.play(-100) - elif key == QtCore.Qt.Key_Down: - self.play(100) - elif key == QtCore.Qt.Key_PageUp: - self.play(-1000) - elif key == QtCore.Qt.Key_PageDown: - self.play(1000) - else: - self.play(0) - - - def timeout(self): - now = ptime.time() - dt = now - self.lastPlayTime - if dt < 0: - return - n = int(self.playRate * dt) - #print n, dt - if n != 0: - #print n, dt, self.lastPlayTime - self.lastPlayTime += (float(n)/self.playRate) - if self.currentIndex+n > self.image.shape[0]: - self.play(0) - self.jumpFrames(n) - - def setCurrentIndex(self, ind): - """Set the currently displayed frame index.""" - self.currentIndex = np.clip(ind, 0, self.getProcessedImage().shape[0]-1) - self.updateImage() - self.ignoreTimeLine = True - self.timeLine.setValue(self.tVals[self.currentIndex]) - self.ignoreTimeLine = False - - def jumpFrames(self, n): - """Move video frame ahead n frames (may be negative)""" - if self.axes['t'] is not None: - self.setCurrentIndex(self.currentIndex + n) - - def normRadioChanged(self): - self.imageDisp = None - self.updateImage() - self.autoLevels() - self.roiChanged() - self.sigProcessingChanged.emit(self) - - - def updateNorm(self): - if self.ui.normTimeRangeCheck.isChecked(): - #print "show!" - self.normRgn.show() - else: - self.normRgn.hide() - - if self.ui.normROICheck.isChecked(): - #print "show!" - self.normRoi.show() - else: - self.normRoi.hide() - - if not self.ui.normOffRadio.isChecked(): - self.imageDisp = None - self.updateImage() - self.autoLevels() - self.roiChanged() - self.sigProcessingChanged.emit(self) - - def normToggled(self, b): - self.ui.normGroup.setVisible(b) - self.normRoi.setVisible(b and self.ui.normROICheck.isChecked()) - self.normRgn.setVisible(b and self.ui.normTimeRangeCheck.isChecked()) - - def hasTimeAxis(self): - return 't' in self.axes and self.axes['t'] is not None - - def roiClicked(self): - showRoiPlot = False - if self.ui.roiBtn.isChecked(): - showRoiPlot = True - self.roi.show() - #self.ui.roiPlot.show() - self.ui.roiPlot.setMouseEnabled(True, True) - self.ui.splitter.setSizes([self.height()*0.6, self.height()*0.4]) - self.roiCurve.show() - self.roiChanged() - self.ui.roiPlot.showAxis('left') - else: - self.roi.hide() - self.ui.roiPlot.setMouseEnabled(False, False) - self.roiCurve.hide() - self.ui.roiPlot.hideAxis('left') - - if self.hasTimeAxis(): - showRoiPlot = True - mn = self.tVals.min() - mx = self.tVals.max() - self.ui.roiPlot.setXRange(mn, mx, padding=0.01) - self.timeLine.show() - self.timeLine.setBounds([mn, mx]) - self.ui.roiPlot.show() - if not self.ui.roiBtn.isChecked(): - self.ui.splitter.setSizes([self.height()-35, 35]) - else: - self.timeLine.hide() - #self.ui.roiPlot.hide() - - self.ui.roiPlot.setVisible(showRoiPlot) - - def roiChanged(self): - if self.image is None: - return - - image = self.getProcessedImage() - if image.ndim == 2: - axes = (0, 1) - elif image.ndim == 3: - axes = (1, 2) - else: - return - data, coords = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes, returnMappedCoords=True) - if data is not None: - while data.ndim > 1: - data = data.mean(axis=1) - if image.ndim == 3: - self.roiCurve.setData(y=data, x=self.tVals) - else: - while coords.ndim > 2: - coords = coords[:,:,0] - coords = coords - coords[:,0,np.newaxis] - xvals = (coords**2).sum(axis=0) ** 0.5 - self.roiCurve.setData(y=data, x=xvals) - - #self.ui.roiPlot.replot() - - - @staticmethod - def quickMinMax(data): - while data.size > 1e6: - ax = np.argmax(data.shape) - sl = [slice(None)] * data.ndim - sl[ax] = slice(None, None, 2) - data = data[sl] - return data.min(), data.max() - - def normalize(self, image): - - if self.ui.normOffRadio.isChecked(): - return image - - div = self.ui.normDivideRadio.isChecked() - norm = image.view(np.ndarray).copy() - #if div: - #norm = ones(image.shape) - #else: - #norm = zeros(image.shape) - if div: - norm = norm.astype(np.float32) - - if self.ui.normTimeRangeCheck.isChecked() and image.ndim == 3: - (sind, start) = self.timeIndex(self.normRgn.lines[0]) - (eind, end) = self.timeIndex(self.normRgn.lines[1]) - #print start, end, sind, eind - n = image[sind:eind+1].mean(axis=0) - n.shape = (1,) + n.shape - if div: - norm /= n - else: - norm -= n - - if self.ui.normFrameCheck.isChecked() and image.ndim == 3: - n = image.mean(axis=1).mean(axis=1) - n.shape = n.shape + (1, 1) - if div: - norm /= n - else: - norm -= n - - if self.ui.normROICheck.isChecked() and image.ndim == 3: - n = self.normRoi.getArrayRegion(norm, self.imageItem, (1, 2)).mean(axis=1).mean(axis=1) - n = n[:,np.newaxis,np.newaxis] - #print start, end, sind, eind - if div: - norm /= n - else: - norm -= n - - return norm - - def timeLineChanged(self): - #(ind, time) = self.timeIndex(self.ui.timeSlider) - if self.ignoreTimeLine: - return - self.play(0) - (ind, time) = self.timeIndex(self.timeLine) - if ind != self.currentIndex: - self.currentIndex = ind - self.updateImage() - #self.timeLine.setPos(time) - #self.emit(QtCore.SIGNAL('timeChanged'), ind, time) - self.sigTimeChanged.emit(ind, time) - - def updateImage(self, autoHistogramRange=True): - ## Redraw image on screen - if self.image is None: - return - - image = self.getProcessedImage() - - if autoHistogramRange: - self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax) - if self.axes['t'] is None: - self.imageItem.updateImage(image) - else: - self.ui.roiPlot.show() - self.imageItem.updateImage(image[self.currentIndex]) - - - def timeIndex(self, slider): - ## Return the time and frame index indicated by a slider - if self.image is None: - return (0,0) - - t = slider.value() - - xv = self.tVals - if xv is None: - ind = int(t) - else: - if len(xv) < 2: - return (0,0) - totTime = xv[-1] + (xv[-1]-xv[-2]) - inds = np.argwhere(xv < t) - if len(inds) < 1: - return (0,t) - ind = inds[-1,0] - return ind, t - - def getView(self): - """Return the ViewBox (or other compatible object) which displays the ImageItem""" - return self.view - - def getImageItem(self): - """Return the ImageItem for this ImageView.""" - return self.imageItem - - def getRoiPlot(self): - """Return the ROI PlotWidget for this ImageView""" - return self.ui.roiPlot - - def getHistogramWidget(self): - """Return the HistogramLUTWidget for this ImageView""" - return self.ui.histogram diff --git a/pyqtgraph/imageview/ImageViewTemplate.ui b/pyqtgraph/imageview/ImageViewTemplate.ui deleted file mode 100644 index 497c0c59..00000000 --- a/pyqtgraph/imageview/ImageViewTemplate.ui +++ /dev/null @@ -1,252 +0,0 @@ - - - Form - - - - 0 - 0 - 726 - 588 - - - - Form - - - - 0 - - - 0 - - - - - Qt::Vertical - - - - - 0 - - - - - - - - - - - - 0 - 1 - - - - ROI - - - true - - - - - - - - 0 - 1 - - - - Norm - - - true - - - - - - - - - 0 - 0 - - - - - 0 - 40 - - - - - - - - - Normalization - - - - 0 - - - 0 - - - - - Subtract - - - - - - - Divide - - - false - - - - - - - - 75 - true - - - - Operation: - - - - - - - - 75 - true - - - - Mean: - - - - - - - - 75 - true - - - - Blur: - - - - - - - ROI - - - - - - - - - - X - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Y - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - T - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Off - - - true - - - - - - - Time range - - - - - - - Frame - - - - - - - - - - - - - - PlotWidget - QWidget -
pyqtgraph.widgets.PlotWidget
- 1 -
- - GraphicsView - QGraphicsView -
pyqtgraph.widgets.GraphicsView
-
- - HistogramLUTWidget - QGraphicsView -
pyqtgraph.widgets.HistogramLUTWidget
-
-
- - -
diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyqt.py b/pyqtgraph/imageview/ImageViewTemplate_pyqt.py deleted file mode 100644 index e6423276..00000000 --- a/pyqtgraph/imageview/ImageViewTemplate_pyqt.py +++ /dev/null @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './imageview/ImageViewTemplate.ui' -# -# Created: Sun Sep 9 14:41:30 2012 -# by: PyQt4 UI code generator 4.9.1 -# -# WARNING! All changes made in this file will be lost! - -from PyQt4 import QtCore, QtGui - -try: - _fromUtf8 = QtCore.QString.fromUtf8 -except AttributeError: - _fromUtf8 = lambda s: s - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName(_fromUtf8("Form")) - Form.resize(726, 588) - self.gridLayout_3 = QtGui.QGridLayout(Form) - self.gridLayout_3.setMargin(0) - self.gridLayout_3.setSpacing(0) - self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) - self.splitter = QtGui.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Vertical) - self.splitter.setObjectName(_fromUtf8("splitter")) - self.layoutWidget = QtGui.QWidget(self.splitter) - self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) - self.gridLayout = QtGui.QGridLayout(self.layoutWidget) - self.gridLayout.setSpacing(0) - self.gridLayout.setMargin(0) - self.gridLayout.setObjectName(_fromUtf8("gridLayout")) - self.graphicsView = GraphicsView(self.layoutWidget) - self.graphicsView.setObjectName(_fromUtf8("graphicsView")) - self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) - self.histogram = HistogramLUTWidget(self.layoutWidget) - self.histogram.setObjectName(_fromUtf8("histogram")) - self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) - self.roiBtn = QtGui.QPushButton(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) - self.roiBtn.setSizePolicy(sizePolicy) - self.roiBtn.setCheckable(True) - self.roiBtn.setObjectName(_fromUtf8("roiBtn")) - self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) - self.normBtn = QtGui.QPushButton(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.normBtn.sizePolicy().hasHeightForWidth()) - self.normBtn.setSizePolicy(sizePolicy) - self.normBtn.setCheckable(True) - self.normBtn.setObjectName(_fromUtf8("normBtn")) - self.gridLayout.addWidget(self.normBtn, 1, 2, 1, 1) - self.roiPlot = PlotWidget(self.splitter) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) - self.roiPlot.setSizePolicy(sizePolicy) - self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) - self.roiPlot.setObjectName(_fromUtf8("roiPlot")) - self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) - self.normGroup = QtGui.QGroupBox(Form) - self.normGroup.setObjectName(_fromUtf8("normGroup")) - self.gridLayout_2 = QtGui.QGridLayout(self.normGroup) - self.gridLayout_2.setMargin(0) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) - self.normSubtractRadio = QtGui.QRadioButton(self.normGroup) - self.normSubtractRadio.setObjectName(_fromUtf8("normSubtractRadio")) - self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) - self.normDivideRadio = QtGui.QRadioButton(self.normGroup) - self.normDivideRadio.setChecked(False) - self.normDivideRadio.setObjectName(_fromUtf8("normDivideRadio")) - self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) - self.label_5 = QtGui.QLabel(self.normGroup) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.label_5.setFont(font) - self.label_5.setObjectName(_fromUtf8("label_5")) - self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) - self.label_3 = QtGui.QLabel(self.normGroup) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.label_3.setFont(font) - self.label_3.setObjectName(_fromUtf8("label_3")) - self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) - self.label_4 = QtGui.QLabel(self.normGroup) - font = QtGui.QFont() - font.setBold(True) - font.setWeight(75) - self.label_4.setFont(font) - self.label_4.setObjectName(_fromUtf8("label_4")) - self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) - self.normROICheck = QtGui.QCheckBox(self.normGroup) - self.normROICheck.setObjectName(_fromUtf8("normROICheck")) - self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) - self.normXBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) - self.normXBlurSpin.setObjectName(_fromUtf8("normXBlurSpin")) - self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) - self.label_8 = QtGui.QLabel(self.normGroup) - self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_8.setObjectName(_fromUtf8("label_8")) - self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) - self.label_9 = QtGui.QLabel(self.normGroup) - self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_9.setObjectName(_fromUtf8("label_9")) - self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) - self.normYBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) - self.normYBlurSpin.setObjectName(_fromUtf8("normYBlurSpin")) - self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) - self.label_10 = QtGui.QLabel(self.normGroup) - self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_10.setObjectName(_fromUtf8("label_10")) - self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) - self.normOffRadio = QtGui.QRadioButton(self.normGroup) - self.normOffRadio.setChecked(True) - self.normOffRadio.setObjectName(_fromUtf8("normOffRadio")) - self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) - self.normTimeRangeCheck = QtGui.QCheckBox(self.normGroup) - self.normTimeRangeCheck.setObjectName(_fromUtf8("normTimeRangeCheck")) - self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) - self.normFrameCheck = QtGui.QCheckBox(self.normGroup) - self.normFrameCheck.setObjectName(_fromUtf8("normFrameCheck")) - self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) - self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) - self.normTBlurSpin.setObjectName(_fromUtf8("normTBlurSpin")) - self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) - self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) - self.normBtn.setText(QtGui.QApplication.translate("Form", "Norm", None, QtGui.QApplication.UnicodeUTF8)) - self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8)) - self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8)) - self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8)) - self.label_5.setText(QtGui.QApplication.translate("Form", "Operation:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_3.setText(QtGui.QApplication.translate("Form", "Mean:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_4.setText(QtGui.QApplication.translate("Form", "Blur:", None, QtGui.QApplication.UnicodeUTF8)) - self.normROICheck.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) - self.label_8.setText(QtGui.QApplication.translate("Form", "X", None, QtGui.QApplication.UnicodeUTF8)) - self.label_9.setText(QtGui.QApplication.translate("Form", "Y", None, QtGui.QApplication.UnicodeUTF8)) - self.label_10.setText(QtGui.QApplication.translate("Form", "T", None, QtGui.QApplication.UnicodeUTF8)) - self.normOffRadio.setText(QtGui.QApplication.translate("Form", "Off", None, QtGui.QApplication.UnicodeUTF8)) - self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8)) - self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8)) - -from pyqtgraph.widgets.GraphicsView import GraphicsView -from pyqtgraph.widgets.PlotWidget import PlotWidget -from pyqtgraph.widgets.HistogramLUTWidget import HistogramLUTWidget diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyside.py b/pyqtgraph/imageview/ImageViewTemplate_pyside.py deleted file mode 100644 index c17bbfe1..00000000 --- a/pyqtgraph/imageview/ImageViewTemplate_pyside.py +++ /dev/null @@ -1,155 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file './imageview/ImageViewTemplate.ui' -# -# Created: Sun Sep 9 14:41:31 2012 -# by: pyside-uic 0.2.13 running on PySide 1.1.0 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_Form(object): - def setupUi(self, Form): - Form.setObjectName("Form") - Form.resize(726, 588) - self.gridLayout_3 = QtGui.QGridLayout(Form) - self.gridLayout_3.setContentsMargins(0, 0, 0, 0) - self.gridLayout_3.setSpacing(0) - self.gridLayout_3.setObjectName("gridLayout_3") - self.splitter = QtGui.QSplitter(Form) - self.splitter.setOrientation(QtCore.Qt.Vertical) - self.splitter.setObjectName("splitter") - self.layoutWidget = QtGui.QWidget(self.splitter) - self.layoutWidget.setObjectName("layoutWidget") - self.gridLayout = QtGui.QGridLayout(self.layoutWidget) - self.gridLayout.setSpacing(0) - self.gridLayout.setContentsMargins(0, 0, 0, 0) - self.gridLayout.setObjectName("gridLayout") - self.graphicsView = GraphicsView(self.layoutWidget) - self.graphicsView.setObjectName("graphicsView") - self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) - self.histogram = HistogramLUTWidget(self.layoutWidget) - self.histogram.setObjectName("histogram") - self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) - self.roiBtn = QtGui.QPushButton(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) - self.roiBtn.setSizePolicy(sizePolicy) - self.roiBtn.setCheckable(True) - self.roiBtn.setObjectName("roiBtn") - self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) - self.normBtn = QtGui.QPushButton(self.layoutWidget) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(1) - sizePolicy.setHeightForWidth(self.normBtn.sizePolicy().hasHeightForWidth()) - self.normBtn.setSizePolicy(sizePolicy) - self.normBtn.setCheckable(True) - self.normBtn.setObjectName("normBtn") - self.gridLayout.addWidget(self.normBtn, 1, 2, 1, 1) - self.roiPlot = PlotWidget(self.splitter) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) - self.roiPlot.setSizePolicy(sizePolicy) - self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) - self.roiPlot.setObjectName("roiPlot") - self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) - self.normGroup = QtGui.QGroupBox(Form) - self.normGroup.setObjectName("normGroup") - self.gridLayout_2 = QtGui.QGridLayout(self.normGroup) - self.gridLayout_2.setContentsMargins(0, 0, 0, 0) - self.gridLayout_2.setSpacing(0) - self.gridLayout_2.setObjectName("gridLayout_2") - self.normSubtractRadio = QtGui.QRadioButton(self.normGroup) - self.normSubtractRadio.setObjectName("normSubtractRadio") - self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) - self.normDivideRadio = QtGui.QRadioButton(self.normGroup) - self.normDivideRadio.setChecked(False) - self.normDivideRadio.setObjectName("normDivideRadio") - self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) - self.label_5 = QtGui.QLabel(self.normGroup) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.label_5.setFont(font) - self.label_5.setObjectName("label_5") - self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) - self.label_3 = QtGui.QLabel(self.normGroup) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.label_3.setFont(font) - self.label_3.setObjectName("label_3") - self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) - self.label_4 = QtGui.QLabel(self.normGroup) - font = QtGui.QFont() - font.setWeight(75) - font.setBold(True) - self.label_4.setFont(font) - self.label_4.setObjectName("label_4") - self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) - self.normROICheck = QtGui.QCheckBox(self.normGroup) - self.normROICheck.setObjectName("normROICheck") - self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) - self.normXBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) - self.normXBlurSpin.setObjectName("normXBlurSpin") - self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) - self.label_8 = QtGui.QLabel(self.normGroup) - self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_8.setObjectName("label_8") - self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) - self.label_9 = QtGui.QLabel(self.normGroup) - self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_9.setObjectName("label_9") - self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) - self.normYBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) - self.normYBlurSpin.setObjectName("normYBlurSpin") - self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) - self.label_10 = QtGui.QLabel(self.normGroup) - self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) - self.label_10.setObjectName("label_10") - self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) - self.normOffRadio = QtGui.QRadioButton(self.normGroup) - self.normOffRadio.setChecked(True) - self.normOffRadio.setObjectName("normOffRadio") - self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) - self.normTimeRangeCheck = QtGui.QCheckBox(self.normGroup) - self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") - self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) - self.normFrameCheck = QtGui.QCheckBox(self.normGroup) - self.normFrameCheck.setObjectName("normFrameCheck") - self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) - self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) - self.normTBlurSpin.setObjectName("normTBlurSpin") - self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) - self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) - - self.retranslateUi(Form) - QtCore.QMetaObject.connectSlotsByName(Form) - - def retranslateUi(self, Form): - Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) - self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) - self.normBtn.setText(QtGui.QApplication.translate("Form", "Norm", None, QtGui.QApplication.UnicodeUTF8)) - self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8)) - self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8)) - self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8)) - self.label_5.setText(QtGui.QApplication.translate("Form", "Operation:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_3.setText(QtGui.QApplication.translate("Form", "Mean:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_4.setText(QtGui.QApplication.translate("Form", "Blur:", None, QtGui.QApplication.UnicodeUTF8)) - self.normROICheck.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) - self.label_8.setText(QtGui.QApplication.translate("Form", "X", None, QtGui.QApplication.UnicodeUTF8)) - self.label_9.setText(QtGui.QApplication.translate("Form", "Y", None, QtGui.QApplication.UnicodeUTF8)) - self.label_10.setText(QtGui.QApplication.translate("Form", "T", None, QtGui.QApplication.UnicodeUTF8)) - self.normOffRadio.setText(QtGui.QApplication.translate("Form", "Off", None, QtGui.QApplication.UnicodeUTF8)) - self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8)) - self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8)) - -from pyqtgraph.widgets.GraphicsView import GraphicsView -from pyqtgraph.widgets.PlotWidget import PlotWidget -from pyqtgraph.widgets.HistogramLUTWidget import HistogramLUTWidget diff --git a/pyqtgraph/imageview/__init__.py b/pyqtgraph/imageview/__init__.py deleted file mode 100644 index 0060e823..00000000 --- a/pyqtgraph/imageview/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Widget used for display and analysis of 2D and 3D image data. -Includes ROI plotting over time and image normalization. -""" - -from .ImageView import ImageView diff --git a/pyqtgraph/metaarray/MetaArray.py b/pyqtgraph/metaarray/MetaArray.py deleted file mode 100644 index f55c60dc..00000000 --- a/pyqtgraph/metaarray/MetaArray.py +++ /dev/null @@ -1,1474 +0,0 @@ -# -*- coding: utf-8 -*- -""" -MetaArray.py - Class encapsulating ndarray with meta data -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. - -MetaArray is an array class based on numpy.ndarray that allows storage of per-axis meta data -such as axis values, names, units, column names, etc. It also enables several -new methods for slicing and indexing the array based on this meta data. -More info at http://www.scipy.org/Cookbook/MetaArray -""" - -import numpy as np -import types, copy, threading, os, re -import pickle -from functools import reduce -#import traceback - -## By default, the library will use HDF5 when writing files. -## This can be overridden by setting USE_HDF5 = False -USE_HDF5 = True -try: - import h5py - HAVE_HDF5 = True -except: - USE_HDF5 = False - HAVE_HDF5 = False - - -def axis(name=None, cols=None, values=None, units=None): - """Convenience function for generating axis descriptions when defining MetaArrays""" - ax = {} - cNameOrder = ['name', 'units', 'title'] - if name is not None: - ax['name'] = name - if values is not None: - ax['values'] = values - if units is not None: - ax['units'] = units - if cols is not None: - ax['cols'] = [] - for c in cols: - if type(c) != list and type(c) != tuple: - c = [c] - col = {} - for i in range(0,len(c)): - col[cNameOrder[i]] = c[i] - ax['cols'].append(col) - return ax - -class sliceGenerator(object): - """Just a compact way to generate tuples of slice objects.""" - def __getitem__(self, arg): - return arg - def __getslice__(self, arg): - return arg -SLICER = sliceGenerator() - - -class MetaArray(object): - """N-dimensional array with meta data such as axis titles, units, and column names. - - May be initialized with a file name, a tuple representing the dimensions of the array, - or any arguments that could be passed on to numpy.array() - - The info argument sets the metadata for the entire array. It is composed of a list - of axis descriptions where each axis may have a name, title, units, and a list of column - descriptions. An additional dict at the end of the axis list may specify parameters - that apply to values in the entire array. - - For example: - A 2D array of altitude values for a topographical map might look like - info=[ - {'name': 'lat', 'title': 'Lattitude'}, - {'name': 'lon', 'title': 'Longitude'}, - {'title': 'Altitude', 'units': 'm'} - ] - In this case, every value in the array represents the altitude in feet at the lat, lon - position represented by the array index. All of the following return the - value at lat=10, lon=5: - array[10, 5] - array['lon':5, 'lat':10] - array['lat':10][5] - Now suppose we want to combine this data with another array of equal dimensions that - represents the average rainfall for each location. We could easily store these as two - separate arrays or combine them into a 3D array with this description: - info=[ - {'name': 'vals', 'cols': [ - {'name': 'altitude', 'units': 'm'}, - {'name': 'rainfall', 'units': 'cm/year'} - ]}, - {'name': 'lat', 'title': 'Lattitude'}, - {'name': 'lon', 'title': 'Longitude'} - ] - We can now access the altitude values with array[0] or array['altitude'], and the - rainfall values with array[1] or array['rainfall']. All of the following return - the rainfall value at lat=10, lon=5: - array[1, 10, 5] - array['lon':5, 'lat':10, 'val': 'rainfall'] - array['rainfall', 'lon':5, 'lat':10] - Notice that in the second example, there is no need for an extra (4th) axis description - since the actual values are described (name and units) in the column info for the first axis. - """ - - version = '2' - - ## Types allowed as axis or column names - nameTypes = [basestring, tuple] - @staticmethod - def isNameType(var): - return any([isinstance(var, t) for t in MetaArray.nameTypes]) - - - ## methods to wrap from embedded ndarray / HDF5 - wrapMethods = set(['__eq__', '__ne__', '__le__', '__lt__', '__ge__', '__gt__']) - - def __init__(self, data=None, info=None, dtype=None, file=None, copy=False, **kwargs): - object.__init__(self) - #self._infoOwned = False - self._isHDF = False - - if file is not None: - self._data = None - self.readFile(file, **kwargs) - if self._data is None: - raise Exception("File read failed: %s" % file) - else: - self._info = info - if (hasattr(data, 'implements') and data.implements('MetaArray')): - self._info = data._info - self._data = data.asarray() - elif isinstance(data, tuple): ## create empty array with specified shape - self._data = np.empty(data, dtype=dtype) - else: - self._data = np.array(data, dtype=dtype, copy=copy) - - ## run sanity checks on info structure - self.checkInfo() - - def checkInfo(self): - info = self._info - if info is None: - if self._data is None: - return - else: - self._info = [{} for i in range(self.ndim)] - return - else: - try: - info = list(info) - except: - raise Exception("Info must be a list of axis specifications") - if len(info) < self.ndim+1: - info.extend([{}]*(self.ndim+1-len(info))) - elif len(info) > self.ndim+1: - raise Exception("Info parameter must be list of length ndim+1 or less.") - for i in range(len(info)): - if not isinstance(info[i], dict): - if info[i] is None: - info[i] = {} - else: - raise Exception("Axis specification must be Dict or None") - if i < self.ndim and 'values' in info[i]: - if type(info[i]['values']) is list: - info[i]['values'] = np.array(info[i]['values']) - elif type(info[i]['values']) is not np.ndarray: - raise Exception("Axis values must be specified as list or ndarray") - if info[i]['values'].ndim != 1 or info[i]['values'].shape[0] != self.shape[i]: - raise Exception("Values array for axis %d has incorrect shape. (given %s, but should be %s)" % (i, str(info[i]['values'].shape), str((self.shape[i],)))) - if i < self.ndim and 'cols' in info[i]: - if not isinstance(info[i]['cols'], list): - info[i]['cols'] = list(info[i]['cols']) - if len(info[i]['cols']) != self.shape[i]: - raise Exception('Length of column list for axis %d does not match data. (given %d, but should be %d)' % (i, len(info[i]['cols']), self.shape[i])) - - def implements(self, name=None): - ## Rather than isinstance(obj, MetaArray) use object.implements('MetaArray') - if name is None: - return ['MetaArray'] - else: - return name == 'MetaArray' - - #def __array_finalize__(self,obj): - ### array_finalize is called every time a MetaArray is created - ### (whereas __new__ is not necessarily called every time) - - ### obj is the object from which this array was generated (for example, when slicing or view()ing) - - ## We use the getattr method to set a default if 'obj' doesn't have the 'info' attribute - ##print "Create new MA from object", str(type(obj)) - ##import traceback - ##traceback.print_stack() - ##print "finalize", type(self), type(obj) - #if not hasattr(self, '_info'): - ##if isinstance(obj, MetaArray): - ##print " copy info:", obj._info - #self._info = getattr(obj, '_info', [{}]*(obj.ndim+1)) - #self._infoOwned = False ## Do not make changes to _info until it is copied at least once - ##print " self info:", self._info - - ## We could have checked first whether self._info was already defined: - ##if not hasattr(self, 'info'): - ## self._info = getattr(obj, 'info', {}) - - - def __getitem__(self, ind): - #print "getitem:", ind - - ## should catch scalar requests as early as possible to speed things up (?) - - nInd = self._interpretIndexes(ind) - - #a = np.ndarray.__getitem__(self, nInd) - a = self._data[nInd] - if len(nInd) == self.ndim: - if np.all([not isinstance(ind, slice) for ind in nInd]): ## no slices; we have requested a single value from the array - return a - #if type(a) != type(self._data) and not isinstance(a, np.ndarray): ## indexing returned single value - #return a - - ## indexing returned a sub-array; generate new info array to go with it - #print " new MA:", type(a), a.shape - info = [] - extraInfo = self._info[-1].copy() - for i in range(0, len(nInd)): ## iterate over all axes - #print " axis", i - if type(nInd[i]) in [slice, list] or isinstance(nInd[i], np.ndarray): ## If the axis is sliced, keep the info but chop if necessary - #print " slice axis", i, nInd[i] - #a._info[i] = self._axisSlice(i, nInd[i]) - #print " info:", a._info[i] - info.append(self._axisSlice(i, nInd[i])) - else: ## If the axis is indexed, then move the information from that single index to the last info dictionary - #print "indexed:", i, nInd[i], type(nInd[i]) - newInfo = self._axisSlice(i, nInd[i]) - name = None - colName = None - for k in newInfo: - if k == 'cols': - if 'cols' not in extraInfo: - extraInfo['cols'] = [] - extraInfo['cols'].append(newInfo[k]) - if 'units' in newInfo[k]: - extraInfo['units'] = newInfo[k]['units'] - if 'name' in newInfo[k]: - colName = newInfo[k]['name'] - elif k == 'name': - name = newInfo[k] - else: - if k not in extraInfo: - extraInfo[k] = newInfo[k] - extraInfo[k] = newInfo[k] - if 'name' not in extraInfo: - if name is None: - if colName is not None: - extraInfo['name'] = colName - else: - if colName is not None: - extraInfo['name'] = str(name) + ': ' + str(colName) - else: - extraInfo['name'] = name - - - #print "Lost info:", newInfo - #a._info[i] = None - #if 'name' in newInfo: - #a._info[-1][newInfo['name']] = newInfo - info.append(extraInfo) - - #self._infoOwned = False - #while None in a._info: - #a._info.remove(None) - return MetaArray(a, info=info) - - @property - def ndim(self): - return len(self.shape) ## hdf5 objects do not have ndim property. - - @property - def shape(self): - return self._data.shape - - @property - def dtype(self): - return self._data.dtype - - def __len__(self): - return len(self._data) - - def __getslice__(self, *args): - return self.__getitem__(slice(*args)) - - def __setitem__(self, ind, val): - nInd = self._interpretIndexes(ind) - try: - self._data[nInd] = val - except: - print(self, nInd, val) - raise - - def __getattr__(self, attr): - if attr in self.wrapMethods: - return getattr(self._data, attr) - else: - raise AttributeError(attr) - #return lambda *args, **kwargs: MetaArray(getattr(a.view(ndarray), attr)(*args, **kwargs) - - def __eq__(self, b): - return self._binop('__eq__', b) - - def __ne__(self, b): - return self._binop('__ne__', b) - #if isinstance(b, MetaArray): - #b = b.asarray() - #return self.asarray() != b - - def __sub__(self, b): - return self._binop('__sub__', b) - #if isinstance(b, MetaArray): - #b = b.asarray() - #return MetaArray(self.asarray() - b, info=self.infoCopy()) - - def __add__(self, b): - return self._binop('__add__', b) - - def __mul__(self, b): - return self._binop('__mul__', b) - - def __div__(self, b): - return self._binop('__div__', b) - - def __truediv__(self, b): - return self._binop('__truediv__', b) - - def _binop(self, op, b): - if isinstance(b, MetaArray): - b = b.asarray() - a = self.asarray() - c = getattr(a, op)(b) - if c.shape != a.shape: - raise Exception("Binary operators with MetaArray must return an array of the same shape (this shape is %s, result shape was %s)" % (a.shape, c.shape)) - return MetaArray(c, info=self.infoCopy()) - - def asarray(self): - if isinstance(self._data, np.ndarray): - return self._data - else: - return np.array(self._data) - - def __array__(self): - ## supports np.array(metaarray_instance) - return self.asarray() - - def view(self, typ): - ## deprecated; kept for backward compatibility - if typ is np.ndarray: - return self.asarray() - else: - raise Exception('invalid view type: %s' % str(typ)) - - def axisValues(self, axis): - """Return the list of values for an axis""" - ax = self._interpretAxis(axis) - if 'values' in self._info[ax]: - return self._info[ax]['values'] - else: - raise Exception('Array axis %s (%d) has no associated values.' % (str(axis), ax)) - - def xvals(self, axis): - """Synonym for axisValues()""" - return self.axisValues(axis) - - def axisHasValues(self, axis): - ax = self._interpretAxis(axis) - return 'values' in self._info[ax] - - def axisHasColumns(self, axis): - ax = self._interpretAxis(axis) - return 'cols' in self._info[ax] - - def axisUnits(self, axis): - """Return the units for axis""" - ax = self._info[self._interpretAxis(axis)] - if 'units' in ax: - return ax['units'] - - def hasColumn(self, axis, col): - ax = self._info[self._interpretAxis(axis)] - if 'cols' in ax: - for c in ax['cols']: - if c['name'] == col: - return True - return False - - def listColumns(self, axis=None): - """Return a list of column names for axis. If axis is not specified, then return a dict of {axisName: (column names), ...}.""" - if axis is None: - ret = {} - for i in range(self.ndim): - if 'cols' in self._info[i]: - cols = [c['name'] for c in self._info[i]['cols']] - else: - cols = [] - ret[self.axisName(i)] = cols - return ret - else: - axis = self._interpretAxis(axis) - return [c['name'] for c in self._info[axis]['cols']] - - def columnName(self, axis, col): - ax = self._info[self._interpretAxis(axis)] - return ax['cols'][col]['name'] - - def axisName(self, n): - return self._info[n].get('name', n) - - def columnUnits(self, axis, column): - """Return the units for column in axis""" - ax = self._info[self._interpretAxis(axis)] - if 'cols' in ax: - for c in ax['cols']: - if c['name'] == column: - return c['units'] - raise Exception("Axis %s has no column named %s" % (str(axis), str(column))) - else: - raise Exception("Axis %s has no column definitions" % str(axis)) - - def rowsort(self, axis, key=0): - """Return this object with all records sorted along axis using key as the index to the values to compare. Does not yet modify meta info.""" - ## make sure _info is copied locally before modifying it! - - keyList = self[key] - order = keyList.argsort() - if type(axis) == int: - ind = [slice(None)]*axis - ind.append(order) - elif isinstance(axis, basestring): - ind = (slice(axis, order),) - return self[tuple(ind)] - - def append(self, val, axis): - """Return this object with val appended along axis. Does not yet combine meta info.""" - ## make sure _info is copied locally before modifying it! - - s = list(self.shape) - axis = self._interpretAxis(axis) - s[axis] += 1 - n = MetaArray(tuple(s), info=self._info, dtype=self.dtype) - ind = [slice(None)]*self.ndim - ind[axis] = slice(None,-1) - n[tuple(ind)] = self - ind[axis] = -1 - n[tuple(ind)] = val - return n - - def extend(self, val, axis): - """Return the concatenation along axis of this object and val. Does not yet combine meta info.""" - ## make sure _info is copied locally before modifying it! - - axis = self._interpretAxis(axis) - return MetaArray(np.concatenate(self, val, axis), info=self._info) - - def infoCopy(self, axis=None): - """Return a deep copy of the axis meta info for this object""" - if axis is None: - return copy.deepcopy(self._info) - else: - return copy.deepcopy(self._info[self._interpretAxis(axis)]) - - def copy(self): - return MetaArray(self._data.copy(), info=self.infoCopy()) - - - def _interpretIndexes(self, ind): - #print "interpret", ind - if not isinstance(ind, tuple): - ## a list of slices should be interpreted as a tuple of slices. - if isinstance(ind, list) and len(ind) > 0 and isinstance(ind[0], slice): - ind = tuple(ind) - ## everything else can just be converted to a length-1 tuple - else: - ind = (ind,) - - nInd = [slice(None)]*self.ndim - numOk = True ## Named indices not started yet; numbered sill ok - for i in range(0,len(ind)): - (axis, index, isNamed) = self._interpretIndex(ind[i], i, numOk) - #try: - nInd[axis] = index - #except: - #print "ndim:", self.ndim - #print "axis:", axis - #print "index spec:", ind[i] - #print "index num:", index - #raise - if isNamed: - numOk = False - return tuple(nInd) - - def _interpretAxis(self, axis): - if isinstance(axis, basestring) or isinstance(axis, tuple): - return self._getAxis(axis) - else: - return axis - - def _interpretIndex(self, ind, pos, numOk): - #print "Interpreting index", ind, pos, numOk - - ## should probably check for int first to speed things up.. - if type(ind) is int: - if not numOk: - raise Exception("string and integer indexes may not follow named indexes") - #print " normal numerical index" - return (pos, ind, False) - if MetaArray.isNameType(ind): - if not numOk: - raise Exception("string and integer indexes may not follow named indexes") - #print " String index, column is ", self._getIndex(pos, ind) - return (pos, self._getIndex(pos, ind), False) - elif type(ind) is slice: - #print " Slice index" - if MetaArray.isNameType(ind.start) or MetaArray.isNameType(ind.stop): ## Not an actual slice! - #print " ..not a real slice" - axis = self._interpretAxis(ind.start) - #print " axis is", axis - - ## x[Axis:Column] - if MetaArray.isNameType(ind.stop): - #print " column name, column is ", self._getIndex(axis, ind.stop) - index = self._getIndex(axis, ind.stop) - - ## x[Axis:min:max] - elif (isinstance(ind.stop, float) or isinstance(ind.step, float)) and ('values' in self._info[axis]): - #print " axis value range" - if ind.stop is None: - mask = self.xvals(axis) < ind.step - elif ind.step is None: - mask = self.xvals(axis) >= ind.stop - else: - mask = (self.xvals(axis) >= ind.stop) * (self.xvals(axis) < ind.step) - ##print "mask:", mask - index = mask - - ## x[Axis:columnIndex] - elif isinstance(ind.stop, int) or isinstance(ind.step, int): - #print " normal slice after named axis" - if ind.step is None: - index = ind.stop - else: - index = slice(ind.stop, ind.step) - - ## x[Axis: [list]] - elif type(ind.stop) is list: - #print " list of indexes from named axis" - index = [] - for i in ind.stop: - if type(i) is int: - index.append(i) - elif MetaArray.isNameType(i): - index.append(self._getIndex(axis, i)) - else: - ## unrecognized type, try just passing on to array - index = ind.stop - break - - else: - #print " other type.. forward on to array for handling", type(ind.stop) - index = ind.stop - #print "Axis %s (%s) : %s" % (ind.start, str(axis), str(type(index))) - #if type(index) is np.ndarray: - #print " ", index.shape - return (axis, index, True) - else: - #print " Looks like a real slice, passing on to array" - return (pos, ind, False) - elif type(ind) is list: - #print " List index., interpreting each element individually" - indList = [self._interpretIndex(i, pos, numOk)[1] for i in ind] - return (pos, indList, False) - else: - if not numOk: - raise Exception("string and integer indexes may not follow named indexes") - #print " normal numerical index" - return (pos, ind, False) - - def _getAxis(self, name): - for i in range(0, len(self._info)): - axis = self._info[i] - if 'name' in axis and axis['name'] == name: - return i - raise Exception("No axis named %s.\n info=%s" % (name, self._info)) - - def _getIndex(self, axis, name): - ax = self._info[axis] - if ax is not None and 'cols' in ax: - for i in range(0, len(ax['cols'])): - if 'name' in ax['cols'][i] and ax['cols'][i]['name'] == name: - return i - raise Exception("Axis %d has no column named %s.\n info=%s" % (axis, name, self._info)) - - def _axisCopy(self, i): - return copy.deepcopy(self._info[i]) - - def _axisSlice(self, i, cols): - #print "axisSlice", i, cols - if 'cols' in self._info[i] or 'values' in self._info[i]: - ax = self._axisCopy(i) - if 'cols' in ax: - #print " slicing columns..", array(ax['cols']), cols - sl = np.array(ax['cols'])[cols] - if isinstance(sl, np.ndarray): - sl = list(sl) - ax['cols'] = sl - #print " result:", ax['cols'] - if 'values' in ax: - ax['values'] = np.array(ax['values'])[cols] - else: - ax = self._info[i] - #print " ", ax - return ax - - def prettyInfo(self): - s = '' - titles = [] - maxl = 0 - for i in range(len(self._info)-1): - ax = self._info[i] - axs = '' - if 'name' in ax: - axs += '"%s"' % str(ax['name']) - else: - axs += "%d" % i - if 'units' in ax: - axs += " (%s)" % str(ax['units']) - titles.append(axs) - if len(axs) > maxl: - maxl = len(axs) - - for i in range(min(self.ndim, len(self._info)-1)): - ax = self._info[i] - axs = titles[i] - axs += '%s[%d] :' % (' ' * (maxl + 2 - len(axs)), self.shape[i]) - if 'values' in ax: - v0 = ax['values'][0] - v1 = ax['values'][-1] - axs += " values: [%g ... %g] (step %g)" % (v0, v1, (v1-v0)/(self.shape[i]-1)) - if 'cols' in ax: - axs += " columns: " - colstrs = [] - for c in range(len(ax['cols'])): - col = ax['cols'][c] - cs = str(col.get('name', c)) - if 'units' in col: - cs += " (%s)" % col['units'] - colstrs.append(cs) - axs += '[' + ', '.join(colstrs) + ']' - s += axs + "\n" - s += str(self._info[-1]) - return s - - def __repr__(self): - return "%s\n-----------------------------------------------\n%s" % (self.view(np.ndarray).__repr__(), self.prettyInfo()) - - def __str__(self): - return self.__repr__() - - - def axisCollapsingFn(self, fn, axis=None, *args, **kargs): - #arr = self.view(np.ndarray) - fn = getattr(self._data, fn) - if axis is None: - return fn(axis, *args, **kargs) - else: - info = self.infoCopy() - axis = self._interpretAxis(axis) - info.pop(axis) - return MetaArray(fn(axis, *args, **kargs), info=info) - - def mean(self, axis=None, *args, **kargs): - return self.axisCollapsingFn('mean', axis, *args, **kargs) - - - def min(self, axis=None, *args, **kargs): - return self.axisCollapsingFn('min', axis, *args, **kargs) - - def max(self, axis=None, *args, **kargs): - return self.axisCollapsingFn('max', axis, *args, **kargs) - - def transpose(self, *args): - if len(args) == 1 and hasattr(args[0], '__iter__'): - order = args[0] - else: - order = args - - order = [self._interpretAxis(ax) for ax in order] - infoOrder = order + list(range(len(order), len(self._info))) - info = [self._info[i] for i in infoOrder] - order = order + list(range(len(order), self.ndim)) - - try: - if self._isHDF: - return MetaArray(np.array(self._data).transpose(order), info=info) - else: - return MetaArray(self._data.transpose(order), info=info) - except: - print(order) - raise - - #### File I/O Routines - def readFile(self, filename, **kwargs): - """Load the data and meta info stored in *filename* - Different arguments are allowed depending on the type of file. - For HDF5 files: - - *writable* (bool) if True, then any modifications to data in the array will be stored to disk. - *readAllData* (bool) if True, then all data in the array is immediately read from disk - and the file is closed (this is the default for files < 500MB). Otherwise, the file will - be left open and data will be read only as requested (this is - the default for files >= 500MB). - - - """ - ## decide which read function to use - fd = open(filename, 'rb') - magic = fd.read(8) - if magic == '\x89HDF\r\n\x1a\n': - fd.close() - self._readHDF5(filename, **kwargs) - self._isHDF = True - else: - fd.seek(0) - meta = MetaArray._readMeta(fd) - if 'version' in meta: - ver = meta['version'] - else: - ver = 1 - rFuncName = '_readData%s' % str(ver) - if not hasattr(MetaArray, rFuncName): - raise Exception("This MetaArray library does not support array version '%s'" % ver) - rFunc = getattr(self, rFuncName) - rFunc(fd, meta, **kwargs) - self._isHDF = False - - @staticmethod - def _readMeta(fd): - """Read meta array from the top of a file. Read lines until a blank line is reached. - This function should ideally work for ALL versions of MetaArray. - """ - meta = '' - ## Read meta information until the first blank line - while True: - line = fd.readline().strip() - if line == '': - break - meta += line - ret = eval(meta) - #print ret - return ret - - def _readData1(self, fd, meta, mmap=False): - ## Read array data from the file descriptor for MetaArray v1 files - ## read in axis values for any axis that specifies a length - frameSize = 1 - for ax in meta['info']: - if 'values_len' in ax: - ax['values'] = np.fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) - frameSize *= ax['values_len'] - del ax['values_len'] - del ax['values_type'] - ## the remaining data is the actual array - if mmap: - subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape']) - else: - subarr = np.fromstring(fd.read(), dtype=meta['type']) - subarr.shape = meta['shape'] - self._info = meta['info'] - self._data = subarr - - def _readData2(self, fd, meta, mmap=False, subset=None): - ## read in axis values - dynAxis = None - frameSize = 1 - ## read in axis values for any axis that specifies a length - for i in range(len(meta['info'])): - ax = meta['info'][i] - if 'values_len' in ax: - if ax['values_len'] == 'dynamic': - if dynAxis is not None: - raise Exception("MetaArray has more than one dynamic axis! (this is not allowed)") - dynAxis = i - else: - ax['values'] = np.fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) - frameSize *= ax['values_len'] - del ax['values_len'] - del ax['values_type'] - - ## No axes are dynamic, just read the entire array in at once - if dynAxis is None: - #if rewriteDynamic is not None: - #raise Exception("") - if meta['type'] == 'object': - if mmap: - raise Exception('memmap not supported for arrays with dtype=object') - subarr = pickle.loads(fd.read()) - else: - if mmap: - subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape']) - else: - subarr = np.fromstring(fd.read(), dtype=meta['type']) - #subarr = subarr.view(subtype) - subarr.shape = meta['shape'] - #subarr._info = meta['info'] - ## One axis is dynamic, read in a frame at a time - else: - if mmap: - raise Exception('memmap not supported for non-contiguous arrays. Use rewriteContiguous() to convert.') - ax = meta['info'][dynAxis] - xVals = [] - frames = [] - frameShape = list(meta['shape']) - frameShape[dynAxis] = 1 - frameSize = reduce(lambda a,b: a*b, frameShape) - n = 0 - while True: - ## Extract one non-blank line - while True: - line = fd.readline() - if line != '\n': - break - if line == '': - break - - ## evaluate line - inf = eval(line) - - ## read data block - #print "read %d bytes as %s" % (inf['len'], meta['type']) - if meta['type'] == 'object': - data = pickle.loads(fd.read(inf['len'])) - else: - data = np.fromstring(fd.read(inf['len']), dtype=meta['type']) - - if data.size != frameSize * inf['numFrames']: - #print data.size, frameSize, inf['numFrames'] - raise Exception("Wrong frame size in MetaArray file! (frame %d)" % n) - - ## read in data block - shape = list(frameShape) - shape[dynAxis] = inf['numFrames'] - data.shape = shape - if subset is not None: - dSlice = subset[dynAxis] - if dSlice.start is None: - dStart = 0 - else: - dStart = max(0, dSlice.start - n) - if dSlice.stop is None: - dStop = data.shape[dynAxis] - else: - dStop = min(data.shape[dynAxis], dSlice.stop - n) - newSubset = list(subset[:]) - newSubset[dynAxis] = slice(dStart, dStop) - if dStop > dStart: - #print n, data.shape, " => ", newSubset, data[tuple(newSubset)].shape - frames.append(data[tuple(newSubset)].copy()) - else: - #data = data[subset].copy() ## what's this for?? - frames.append(data) - - n += inf['numFrames'] - if 'xVals' in inf: - xVals.extend(inf['xVals']) - subarr = np.concatenate(frames, axis=dynAxis) - if len(xVals)> 0: - ax['values'] = np.array(xVals, dtype=ax['values_type']) - del ax['values_len'] - del ax['values_type'] - #subarr = subarr.view(subtype) - #subarr._info = meta['info'] - self._info = meta['info'] - self._data = subarr - #raise Exception() ## stress-testing - #return subarr - - def _readHDF5(self, fileName, readAllData=None, writable=False, **kargs): - if 'close' in kargs and readAllData is None: ## for backward compatibility - readAllData = kargs['close'] - - if readAllData is True and writable is True: - raise Exception("Incompatible arguments: readAllData=True and writable=True") - - if not HAVE_HDF5: - try: - assert writable==False - assert readAllData != False - self._readHDF5Remote(fileName) - return - except: - raise Exception("The file '%s' is HDF5-formatted, but the HDF5 library (h5py) was not found." % fileName) - - ## by default, readAllData=True for files < 500MB - if readAllData is None: - size = os.stat(fileName).st_size - readAllData = (size < 500e6) - - if writable is True: - mode = 'r+' - else: - mode = 'r' - f = h5py.File(fileName, mode) - - ver = f.attrs['MetaArray'] - if ver > MetaArray.version: - print("Warning: This file was written with MetaArray version %s, but you are using version %s. (Will attempt to read anyway)" % (str(ver), str(MetaArray.version))) - meta = MetaArray.readHDF5Meta(f['info']) - self._info = meta - - if writable or not readAllData: ## read all data, convert to ndarray, close file - self._data = f['data'] - self._openFile = f - else: - self._data = f['data'][:] - f.close() - - def _readHDF5Remote(self, fileName): - ## Used to read HDF5 files via remote process. - ## This is needed in the case that HDF5 is not importable due to the use of python-dbg. - proc = getattr(MetaArray, '_hdf5Process', None) - - if proc == False: - raise Exception('remote read failed') - if proc == None: - import pyqtgraph.multiprocess as mp - #print "new process" - proc = mp.Process(executable='/usr/bin/python') - proc.setProxyOptions(deferGetattr=True) - MetaArray._hdf5Process = proc - MetaArray._h5py_metaarray = proc._import('pyqtgraph.metaarray') - ma = MetaArray._h5py_metaarray.MetaArray(file=fileName) - self._data = ma.asarray()._getValue() - self._info = ma._info._getValue() - #print MetaArray._hdf5Process - #import inspect - #print MetaArray, id(MetaArray), inspect.getmodule(MetaArray) - - - - @staticmethod - def mapHDF5Array(data, writable=False): - off = data.id.get_offset() - if writable: - mode = 'r+' - else: - mode = 'r' - if off is None: - raise Exception("This dataset uses chunked storage; it can not be memory-mapped. (store using mappable=True)") - return np.memmap(filename=data.file.filename, offset=off, dtype=data.dtype, shape=data.shape, mode=mode) - - - - - @staticmethod - def readHDF5Meta(root, mmap=False): - data = {} - - ## Pull list of values from attributes and child objects - for k in root.attrs: - val = root.attrs[k] - if isinstance(val, basestring): ## strings need to be re-evaluated to their original types - try: - val = eval(val) - except: - raise Exception('Can not evaluate string: "%s"' % val) - data[k] = val - for k in root: - obj = root[k] - if isinstance(obj, h5py.highlevel.Group): - val = MetaArray.readHDF5Meta(obj) - elif isinstance(obj, h5py.highlevel.Dataset): - if mmap: - val = MetaArray.mapHDF5Array(obj) - else: - val = obj[:] - else: - raise Exception("Don't know what to do with type '%s'" % str(type(obj))) - data[k] = val - - typ = root.attrs['_metaType_'] - del data['_metaType_'] - - if typ == 'dict': - return data - elif typ == 'list' or typ == 'tuple': - d2 = [None]*len(data) - for k in data: - d2[int(k)] = data[k] - if typ == 'tuple': - d2 = tuple(d2) - return d2 - else: - raise Exception("Don't understand metaType '%s'" % typ) - - - def write(self, fileName, **opts): - """Write this object to a file. The object can be restored by calling MetaArray(file=fileName) - opts: - appendAxis: the name (or index) of the appendable axis. Allows the array to grow. - compression: None, 'gzip' (good compression), 'lzf' (fast compression), etc. - chunks: bool or tuple specifying chunk shape - """ - - if USE_HDF5 and HAVE_HDF5: - return self.writeHDF5(fileName, **opts) - else: - return self.writeMa(fileName, **opts) - - def writeMeta(self, fileName): - """Used to re-write meta info to the given file. - This feature is only available for HDF5 files.""" - f = h5py.File(fileName, 'r+') - if f.attrs['MetaArray'] != MetaArray.version: - raise Exception("The file %s was created with a different version of MetaArray. Will not modify." % fileName) - del f['info'] - - self.writeHDF5Meta(f, 'info', self._info) - f.close() - - - def writeHDF5(self, fileName, **opts): - ## default options for writing datasets - dsOpts = { - 'compression': 'lzf', - 'chunks': True, - } - - ## if there is an appendable axis, then we can guess the desired chunk shape (optimized for appending) - appAxis = opts.get('appendAxis', None) - if appAxis is not None: - appAxis = self._interpretAxis(appAxis) - cs = [min(100000, x) for x in self.shape] - cs[appAxis] = 1 - dsOpts['chunks'] = tuple(cs) - - ## if there are columns, then we can guess a different chunk shape - ## (read one column at a time) - else: - cs = [min(100000, x) for x in self.shape] - for i in range(self.ndim): - if 'cols' in self._info[i]: - cs[i] = 1 - dsOpts['chunks'] = tuple(cs) - - ## update options if they were passed in - for k in dsOpts: - if k in opts: - dsOpts[k] = opts[k] - - - ## If mappable is in options, it disables chunking/compression - if opts.get('mappable', False): - dsOpts = { - 'chunks': None, - 'compression': None - } - - - ## set maximum shape to allow expansion along appendAxis - append = False - if appAxis is not None: - maxShape = list(self.shape) - ax = self._interpretAxis(appAxis) - maxShape[ax] = None - if os.path.exists(fileName): - append = True - dsOpts['maxshape'] = tuple(maxShape) - else: - dsOpts['maxshape'] = None - - if append: - f = h5py.File(fileName, 'r+') - if f.attrs['MetaArray'] != MetaArray.version: - raise Exception("The file %s was created with a different version of MetaArray. Will not modify." % fileName) - - ## resize data and write in new values - data = f['data'] - shape = list(data.shape) - shape[ax] += self.shape[ax] - data.resize(tuple(shape)) - sl = [slice(None)] * len(data.shape) - sl[ax] = slice(-self.shape[ax], None) - data[tuple(sl)] = self.view(np.ndarray) - - ## add axis values if they are present. - axInfo = f['info'][str(ax)] - if 'values' in axInfo: - v = axInfo['values'] - v2 = self._info[ax]['values'] - shape = list(v.shape) - shape[0] += v2.shape[0] - v.resize(shape) - v[-v2.shape[0]:] = v2 - f.close() - else: - f = h5py.File(fileName, 'w') - f.attrs['MetaArray'] = MetaArray.version - #print dsOpts - f.create_dataset('data', data=self.view(np.ndarray), **dsOpts) - - ## dsOpts is used when storing meta data whenever an array is encountered - ## however, 'chunks' will no longer be valid for these arrays if it specifies a chunk shape. - ## 'maxshape' is right-out. - if isinstance(dsOpts['chunks'], tuple): - dsOpts['chunks'] = True - if 'maxshape' in dsOpts: - del dsOpts['maxshape'] - self.writeHDF5Meta(f, 'info', self._info, **dsOpts) - f.close() - - def writeHDF5Meta(self, root, name, data, **dsOpts): - if isinstance(data, np.ndarray): - dsOpts['maxshape'] = (None,) + data.shape[1:] - root.create_dataset(name, data=data, **dsOpts) - elif isinstance(data, list) or isinstance(data, tuple): - gr = root.create_group(name) - if isinstance(data, list): - gr.attrs['_metaType_'] = 'list' - else: - gr.attrs['_metaType_'] = 'tuple' - #n = int(np.log10(len(data))) + 1 - for i in range(len(data)): - self.writeHDF5Meta(gr, str(i), data[i], **dsOpts) - elif isinstance(data, dict): - gr = root.create_group(name) - gr.attrs['_metaType_'] = 'dict' - for k, v in data.items(): - self.writeHDF5Meta(gr, k, v, **dsOpts) - elif isinstance(data, int) or isinstance(data, float) or isinstance(data, np.integer) or isinstance(data, np.floating): - root.attrs[name] = data - else: - try: ## strings, bools, None are stored as repr() strings - root.attrs[name] = repr(data) - except: - print("Can not store meta data of type '%s' in HDF5. (key is '%s')" % (str(type(data)), str(name))) - raise - - - def writeMa(self, fileName, appendAxis=None, newFile=False): - """Write an old-style .ma file""" - meta = {'shape':self.shape, 'type':str(self.dtype), 'info':self.infoCopy(), 'version':MetaArray.version} - axstrs = [] - - ## copy out axis values for dynamic axis if requested - if appendAxis is not None: - if MetaArray.isNameType(appendAxis): - appendAxis = self._interpretAxis(appendAxis) - - - ax = meta['info'][appendAxis] - ax['values_len'] = 'dynamic' - if 'values' in ax: - ax['values_type'] = str(ax['values'].dtype) - dynXVals = ax['values'] - del ax['values'] - else: - dynXVals = None - - ## Generate axis data string, modify axis info so we know how to read it back in later - for ax in meta['info']: - if 'values' in ax: - axstrs.append(ax['values'].tostring()) - ax['values_len'] = len(axstrs[-1]) - ax['values_type'] = str(ax['values'].dtype) - del ax['values'] - - ## Decide whether to output the meta block for a new file - if not newFile: - ## If the file does not exist or its size is 0, then we must write the header - newFile = (not os.path.exists(fileName)) or (os.stat(fileName).st_size == 0) - - ## write data to file - if appendAxis is None or newFile: - fd = open(fileName, 'wb') - fd.write(str(meta) + '\n\n') - for ax in axstrs: - fd.write(ax) - else: - fd = open(fileName, 'ab') - - if self.dtype != object: - dataStr = self.view(np.ndarray).tostring() - else: - dataStr = pickle.dumps(self.view(np.ndarray)) - #print self.size, len(dataStr), self.dtype - if appendAxis is not None: - frameInfo = {'len':len(dataStr), 'numFrames':self.shape[appendAxis]} - if dynXVals is not None: - frameInfo['xVals'] = list(dynXVals) - fd.write('\n'+str(frameInfo)+'\n') - fd.write(dataStr) - fd.close() - - def writeCsv(self, fileName=None): - """Write 2D array to CSV file or return the string if no filename is given""" - if self.ndim > 2: - raise Exception("CSV Export is only for 2D arrays") - if fileName is not None: - file = open(fileName, 'w') - ret = '' - if 'cols' in self._info[0]: - s = ','.join([x['name'] for x in self._info[0]['cols']]) + '\n' - if fileName is not None: - file.write(s) - else: - ret += s - for row in range(0, self.shape[1]): - s = ','.join(["%g" % x for x in self[:, row]]) + '\n' - if fileName is not None: - file.write(s) - else: - ret += s - if fileName is not None: - file.close() - else: - return ret - - - -#class H5MetaList(): - - -#def rewriteContiguous(fileName, newName): - #"""Rewrite a dynamic array file as contiguous""" - #def _readData2(fd, meta, subtype, mmap): - ### read in axis values - #dynAxis = None - #frameSize = 1 - ### read in axis values for any axis that specifies a length - #for i in range(len(meta['info'])): - #ax = meta['info'][i] - #if ax.has_key('values_len'): - #if ax['values_len'] == 'dynamic': - #if dynAxis is not None: - #raise Exception("MetaArray has more than one dynamic axis! (this is not allowed)") - #dynAxis = i - #else: - #ax['values'] = fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) - #frameSize *= ax['values_len'] - #del ax['values_len'] - #del ax['values_type'] - - ### No axes are dynamic, just read the entire array in at once - #if dynAxis is None: - #raise Exception('Array has no dynamic axes.') - ### One axis is dynamic, read in a frame at a time - #else: - #if mmap: - #raise Exception('memmap not supported for non-contiguous arrays. Use rewriteContiguous() to convert.') - #ax = meta['info'][dynAxis] - #xVals = [] - #frames = [] - #frameShape = list(meta['shape']) - #frameShape[dynAxis] = 1 - #frameSize = reduce(lambda a,b: a*b, frameShape) - #n = 0 - #while True: - ### Extract one non-blank line - #while True: - #line = fd.readline() - #if line != '\n': - #break - #if line == '': - #break - - ### evaluate line - #inf = eval(line) - - ### read data block - ##print "read %d bytes as %s" % (inf['len'], meta['type']) - #if meta['type'] == 'object': - #data = pickle.loads(fd.read(inf['len'])) - #else: - #data = fromstring(fd.read(inf['len']), dtype=meta['type']) - - #if data.size != frameSize * inf['numFrames']: - ##print data.size, frameSize, inf['numFrames'] - #raise Exception("Wrong frame size in MetaArray file! (frame %d)" % n) - - ### read in data block - #shape = list(frameShape) - #shape[dynAxis] = inf['numFrames'] - #data.shape = shape - #frames.append(data) - - #n += inf['numFrames'] - #if 'xVals' in inf: - #xVals.extend(inf['xVals']) - #subarr = np.concatenate(frames, axis=dynAxis) - #if len(xVals)> 0: - #ax['values'] = array(xVals, dtype=ax['values_type']) - #del ax['values_len'] - #del ax['values_type'] - #subarr = subarr.view(subtype) - #subarr._info = meta['info'] - #return subarr - - - - - -if __name__ == '__main__': - ## Create an array with every option possible - - arr = np.zeros((2, 5, 3, 5), dtype=int) - for i in range(arr.shape[0]): - for j in range(arr.shape[1]): - for k in range(arr.shape[2]): - for l in range(arr.shape[3]): - arr[i,j,k,l] = (i+1)*1000 + (j+1)*100 + (k+1)*10 + (l+1) - - info = [ - axis('Axis1'), - axis('Axis2', values=[1,2,3,4,5]), - axis('Axis3', cols=[ - ('Ax3Col1'), - ('Ax3Col2', 'mV', 'Axis3 Column2'), - (('Ax3','Col3'), 'A', 'Axis3 Column3')]), - {'name': 'Axis4', 'values': np.array([1.1, 1.2, 1.3, 1.4, 1.5]), 'units': 's'}, - {'extra': 'info'} - ] - - ma = MetaArray(arr, info=info) - - print("==== Original Array =======") - print(ma) - print("\n\n") - - #### Tests follow: - - - #### Index/slice tests: check that all values and meta info are correct after slice - print("\n -- normal integer indexing\n") - - print("\n ma[1]") - print(ma[1]) - - print("\n ma[1, 2:4]") - print(ma[1, 2:4]) - - print("\n ma[1, 1:5:2]") - print(ma[1, 1:5:2]) - - print("\n -- named axis indexing\n") - - print("\n ma['Axis2':3]") - print(ma['Axis2':3]) - - print("\n ma['Axis2':3:5]") - print(ma['Axis2':3:5]) - - print("\n ma[1, 'Axis2':3]") - print(ma[1, 'Axis2':3]) - - print("\n ma[:, 'Axis2':3]") - print(ma[:, 'Axis2':3]) - - print("\n ma['Axis2':3, 'Axis4':0:2]") - print(ma['Axis2':3, 'Axis4':0:2]) - - - print("\n -- column name indexing\n") - - print("\n ma['Axis3':'Ax3Col1']") - print(ma['Axis3':'Ax3Col1']) - - print("\n ma['Axis3':('Ax3','Col3')]") - print(ma['Axis3':('Ax3','Col3')]) - - print("\n ma[:, :, 'Ax3Col2']") - print(ma[:, :, 'Ax3Col2']) - - print("\n ma[:, :, ('Ax3','Col3')]") - print(ma[:, :, ('Ax3','Col3')]) - - - print("\n -- axis value range indexing\n") - - print("\n ma['Axis2':1.5:4.5]") - print(ma['Axis2':1.5:4.5]) - - print("\n ma['Axis4':1.15:1.45]") - print(ma['Axis4':1.15:1.45]) - - print("\n ma['Axis4':1.15:1.25]") - print(ma['Axis4':1.15:1.25]) - - - - print("\n -- list indexing\n") - - print("\n ma[:, [0,2,4]]") - print(ma[:, [0,2,4]]) - - print("\n ma['Axis4':[0,2,4]]") - print(ma['Axis4':[0,2,4]]) - - print("\n ma['Axis3':[0, ('Ax3','Col3')]]") - print(ma['Axis3':[0, ('Ax3','Col3')]]) - - - - print("\n -- boolean indexing\n") - - print("\n ma[:, array([True, True, False, True, False])]") - print(ma[:, np.array([True, True, False, True, False])]) - - print("\n ma['Axis4':array([True, False, False, False])]") - print(ma['Axis4':np.array([True, False, False, False])]) - - - - - - #### Array operations - # - Concatenate - # - Append - # - Extend - # - Rowsort - - - - - #### File I/O tests - - print("\n================ File I/O Tests ===================\n") - import tempfile - tf = tempfile.mktemp() - tf = 'test.ma' - # write whole array - - print("\n -- write/read test") - ma.write(tf) - ma2 = MetaArray(file=tf) - - #print ma2 - print("\nArrays are equivalent:", (ma == ma2).all()) - #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() - os.remove(tf) - - # CSV write - - # append mode - - - print("\n================append test (%s)===============" % tf) - ma['Axis2':0:2].write(tf, appendAxis='Axis2') - for i in range(2,ma.shape[1]): - ma['Axis2':[i]].write(tf, appendAxis='Axis2') - - ma2 = MetaArray(file=tf) - - #print ma2 - print("\nArrays are equivalent:", (ma == ma2).all()) - #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() - - os.remove(tf) - - - - ## memmap test - print("\n==========Memmap test============") - ma.write(tf, mappable=True) - ma2 = MetaArray(file=tf, mmap=True) - print("\nArrays are equivalent:", (ma == ma2).all()) - os.remove(tf) - \ No newline at end of file diff --git a/pyqtgraph/metaarray/__init__.py b/pyqtgraph/metaarray/__init__.py deleted file mode 100644 index a12f40d5..00000000 --- a/pyqtgraph/metaarray/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .MetaArray import * diff --git a/pyqtgraph/metaarray/license.txt b/pyqtgraph/metaarray/license.txt deleted file mode 100644 index 7ef3e5e9..00000000 --- a/pyqtgraph/metaarray/license.txt +++ /dev/null @@ -1,8 +0,0 @@ -Copyright (c) 2010 Luke Campagnola ('luke.campagnola@%s.com' % 'gmail') -The MIT License -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/pyqtgraph/metaarray/readMeta.m b/pyqtgraph/metaarray/readMeta.m deleted file mode 100644 index b18ad49d..00000000 --- a/pyqtgraph/metaarray/readMeta.m +++ /dev/null @@ -1,86 +0,0 @@ -function f = readMeta(file) -info = hdf5info(file); -f = readMetaRecursive(info.GroupHierarchy.Groups(1)); -end - - -function f = readMetaRecursive(root) -typ = 0; -for i = 1:length(root.Attributes) - if strcmp(root.Attributes(i).Shortname, '_metaType_') - typ = root.Attributes(i).Value.Data; - break - end -end -if typ == 0 - printf('group has no _metaType_') - typ = 'dict'; -end - -list = 0; -if strcmp(typ, 'list') || strcmp(typ, 'tuple') - data = {}; - list = 1; -elseif strcmp(typ, 'dict') - data = struct(); -else - printf('Unrecognized meta type %s', typ); - data = struct(); -end - -for i = 1:length(root.Attributes) - name = root.Attributes(i).Shortname; - if strcmp(name, '_metaType_') - continue - end - val = root.Attributes(i).Value; - if isa(val, 'hdf5.h5string') - val = val.Data; - end - if list - ind = str2num(name)+1; - data{ind} = val; - else - data.(name) = val; - end -end - -for i = 1:length(root.Datasets) - fullName = root.Datasets(i).Name; - name = stripName(fullName); - file = root.Datasets(i).Filename; - data2 = hdf5read(file, fullName); - if list - ind = str2num(name)+1; - data{ind} = data2; - else - data.(name) = data2; - end -end - -for i = 1:length(root.Groups) - name = stripName(root.Groups(i).Name); - data2 = readMetaRecursive(root.Groups(i)); - if list - ind = str2num(name)+1; - data{ind} = data2; - else - data.(name) = data2; - end -end -f = data; -return; -end - - -function f = stripName(str) -inds = strfind(str, '/'); -if isempty(inds) - f = str; -else - f = str(inds(length(inds))+1:length(str)); -end -end - - - diff --git a/pyqtgraph/multiprocess/__init__.py b/pyqtgraph/multiprocess/__init__.py deleted file mode 100644 index 843b42a3..00000000 --- a/pyqtgraph/multiprocess/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Multiprocessing utility library -(parallelization done the way I like it) - -Luke Campagnola -2012.06.10 - -This library provides: - - - simple mechanism for starting a new python interpreter process that can be controlled from the original process - (this allows, for example, displaying and manipulating plots in a remote process - while the parent process is free to do other work) - - proxy system that allows objects hosted in the remote process to be used as if they were local - - Qt signal connection between processes - - very simple in-line parallelization (fork only; does not work on windows) for number-crunching - -TODO: - allow remote processes to serve as rendering engines that pass pixmaps back to the parent process for display - (RemoteGraphicsView class) -""" - -from .processes import * -from .parallelizer import Parallelize, CanceledError -from .remoteproxy import proxy \ No newline at end of file diff --git a/pyqtgraph/multiprocess/bootstrap.py b/pyqtgraph/multiprocess/bootstrap.py deleted file mode 100644 index bb71a703..00000000 --- a/pyqtgraph/multiprocess/bootstrap.py +++ /dev/null @@ -1,28 +0,0 @@ -"""For starting up remote processes""" -import sys, pickle, os - -if __name__ == '__main__': - if hasattr(os, 'setpgrp'): - os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process - if sys.version[0] == '3': - #name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin.buffer) - opts = pickle.load(sys.stdin.buffer) - else: - #name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin) - opts = pickle.load(sys.stdin) - #print "key:", ' '.join([str(ord(x)) for x in authkey]) - path = opts.pop('path', None) - if path is not None: - ## rewrite sys.path without assigning a new object--no idea who already has a reference to the existing list. - while len(sys.path) > 0: - sys.path.pop() - sys.path.extend(path) - - if opts.pop('pyside', False): - import PySide - - - targetStr = opts.pop('targetStr') - target = pickle.loads(targetStr) ## unpickling the target should import everything we need - target(**opts) ## Send all other options to the target function - sys.exit(0) diff --git a/pyqtgraph/multiprocess/parallelizer.py b/pyqtgraph/multiprocess/parallelizer.py deleted file mode 100644 index 659b5efc..00000000 --- a/pyqtgraph/multiprocess/parallelizer.py +++ /dev/null @@ -1,330 +0,0 @@ -import os, sys, time, multiprocessing, re -from .processes import ForkedProcess -from .remoteproxy import ClosedError - -class CanceledError(Exception): - """Raised when the progress dialog is canceled during a processing operation.""" - pass - -class Parallelize(object): - """ - Class for ultra-simple inline parallelization on multi-core CPUs - - Example:: - - ## Here is the serial (single-process) task: - - tasks = [1, 2, 4, 8] - results = [] - for task in tasks: - result = processTask(task) - results.append(result) - print(results) - - - ## Here is the parallelized version: - - tasks = [1, 2, 4, 8] - results = [] - with Parallelize(tasks, workers=4, results=results) as tasker: - for task in tasker: - result = processTask(task) - tasker.results.append(result) - print(results) - - - The only major caveat is that *result* in the example above must be picklable, - since it is automatically sent via pipe back to the parent process. - """ - - def __init__(self, tasks=None, workers=None, block=True, progressDialog=None, randomReseed=True, **kwds): - """ - =============== =================================================================== - Arguments: - tasks list of objects to be processed (Parallelize will determine how to - distribute the tasks). If unspecified, then each worker will receive - a single task with a unique id number. - workers number of worker processes or None to use number of CPUs in the - system - progressDialog optional dict of arguments for ProgressDialog - to update while tasks are processed - randomReseed If True, each forked process will reseed its random number generator - to ensure independent results. Works with the built-in random - and numpy.random. - kwds objects to be shared by proxy with child processes (they will - appear as attributes of the tasker) - =============== =================================================================== - """ - - ## Generate progress dialog. - ## Note that we want to avoid letting forked child processes play with progress dialogs.. - self.showProgress = False - if progressDialog is not None: - self.showProgress = True - if isinstance(progressDialog, basestring): - progressDialog = {'labelText': progressDialog} - import pyqtgraph as pg - self.progressDlg = pg.ProgressDialog(**progressDialog) - - if workers is None: - workers = self.suggestedWorkerCount() - if not hasattr(os, 'fork'): - workers = 1 - self.workers = workers - if tasks is None: - tasks = range(workers) - self.tasks = list(tasks) - self.reseed = randomReseed - self.kwds = kwds.copy() - self.kwds['_taskStarted'] = self._taskStarted - - def __enter__(self): - self.proc = None - if self.workers == 1: - return self.runSerial() - else: - return self.runParallel() - - def __exit__(self, *exc_info): - - if self.proc is not None: ## worker - exceptOccurred = exc_info[0] is not None ## hit an exception during processing. - - try: - if exceptOccurred: - sys.excepthook(*exc_info) - finally: - #print os.getpid(), 'exit' - os._exit(1 if exceptOccurred else 0) - - else: ## parent - if self.showProgress: - self.progressDlg.__exit__(None, None, None) - - def runSerial(self): - if self.showProgress: - self.progressDlg.__enter__() - self.progressDlg.setMaximum(len(self.tasks)) - self.progress = {os.getpid(): []} - return Tasker(self, None, self.tasks, self.kwds) - - - def runParallel(self): - self.childs = [] - - ## break up tasks into one set per worker - workers = self.workers - chunks = [[] for i in xrange(workers)] - i = 0 - for i in range(len(self.tasks)): - chunks[i%workers].append(self.tasks[i]) - - ## fork and assign tasks to each worker - for i in range(workers): - proc = ForkedProcess(target=None, preProxy=self.kwds, randomReseed=self.reseed) - if not proc.isParent: - self.proc = proc - return Tasker(self, proc, chunks[i], proc.forkedProxies) - else: - self.childs.append(proc) - - ## Keep track of the progress of each worker independently. - self.progress = dict([(ch.childPid, []) for ch in self.childs]) - ## for each child process, self.progress[pid] is a list - ## of task indexes. The last index is the task currently being - ## processed; all others are finished. - - - try: - if self.showProgress: - self.progressDlg.__enter__() - self.progressDlg.setMaximum(len(self.tasks)) - ## process events from workers until all have exited. - - activeChilds = self.childs[:] - self.exitCodes = [] - pollInterval = 0.01 - while len(activeChilds) > 0: - waitingChildren = 0 - rem = [] - for ch in activeChilds: - try: - n = ch.processRequests() - if n > 0: - waitingChildren += 1 - except ClosedError: - #print ch.childPid, 'process finished' - rem.append(ch) - if self.showProgress: - self.progressDlg += 1 - #print "remove:", [ch.childPid for ch in rem] - for ch in rem: - activeChilds.remove(ch) - while True: - try: - pid, exitcode = os.waitpid(ch.childPid, 0) - self.exitCodes.append(exitcode) - break - except OSError as ex: - if ex.errno == 4: ## If we get this error, just try again - continue - #print "Ignored system call interruption" - else: - raise - - #print [ch.childPid for ch in activeChilds] - - if self.showProgress and self.progressDlg.wasCanceled(): - for ch in activeChilds: - ch.kill() - raise CanceledError() - - ## adjust polling interval--prefer to get exactly 1 event per poll cycle. - if waitingChildren > 1: - pollInterval *= 0.7 - elif waitingChildren == 0: - pollInterval /= 0.7 - pollInterval = max(min(pollInterval, 0.5), 0.0005) ## but keep it within reasonable limits - - time.sleep(pollInterval) - finally: - if self.showProgress: - self.progressDlg.__exit__(None, None, None) - if len(self.exitCodes) < len(self.childs): - raise Exception("Parallelizer started %d processes but only received exit codes from %d." % (len(self.childs), len(self.exitCodes))) - for code in self.exitCodes: - if code != 0: - raise Exception("Error occurred in parallel-executed subprocess (console output may have more information).") - return [] ## no tasks for parent process. - - - @staticmethod - def suggestedWorkerCount(): - if 'linux' in sys.platform: - ## I think we can do a little better here.. - ## cpu_count does not consider that there is little extra benefit to using hyperthreaded cores. - try: - cores = {} - pid = None - - for line in open('/proc/cpuinfo'): - m = re.match(r'physical id\s+:\s+(\d+)', line) - if m is not None: - pid = m.groups()[0] - m = re.match(r'cpu cores\s+:\s+(\d+)', line) - if m is not None: - cores[pid] = int(m.groups()[0]) - return sum(cores.values()) - except: - return multiprocessing.cpu_count() - - else: - return multiprocessing.cpu_count() - - def _taskStarted(self, pid, i, **kwds): - ## called remotely by tasker to indicate it has started working on task i - #print pid, 'reported starting task', i - if self.showProgress: - if len(self.progress[pid]) > 0: - self.progressDlg += 1 - if pid == os.getpid(): ## single-worker process - if self.progressDlg.wasCanceled(): - raise CanceledError() - self.progress[pid].append(i) - - -class Tasker(object): - def __init__(self, parallelizer, process, tasks, kwds): - self.proc = process - self.par = parallelizer - self.tasks = tasks - for k, v in kwds.iteritems(): - setattr(self, k, v) - - def __iter__(self): - ## we could fix this up such that tasks are retrieved from the parent process one at a time.. - for i, task in enumerate(self.tasks): - self.index = i - #print os.getpid(), 'starting task', i - self._taskStarted(os.getpid(), i, _callSync='off') - yield task - if self.proc is not None: - #print os.getpid(), 'no more tasks' - self.proc.close() - - def process(self): - """ - Process requests from parent. - Usually it is not necessary to call this unless you would like to - receive messages (such as exit requests) during an iteration. - """ - if self.proc is not None: - self.proc.processRequests() - - def numWorkers(self): - """ - Return the number of parallel workers - """ - return self.par.workers - -#class Parallelizer: - #""" - #Use:: - - #p = Parallelizer() - #with p(4) as i: - #p.finish(do_work(i)) - #print p.results() - - #""" - #def __init__(self): - #pass - - #def __call__(self, n): - #self.replies = [] - #self.conn = None ## indicates this is the parent process - #return Session(self, n) - - #def finish(self, data): - #if self.conn is None: - #self.replies.append((self.i, data)) - #else: - ##print "send", self.i, data - #self.conn.send((self.i, data)) - #os._exit(0) - - #def result(self): - #print self.replies - -#class Session: - #def __init__(self, par, n): - #self.par = par - #self.n = n - - #def __enter__(self): - #self.childs = [] - #for i in range(1, self.n): - #c1, c2 = multiprocessingTest.Pipe() - #pid = os.fork() - #if pid == 0: ## child - #self.par.i = i - #self.par.conn = c2 - #self.childs = None - #c1.close() - #return i - #else: - #self.childs.append(c1) - #c2.close() - #self.par.i = 0 - #return 0 - - - - #def __exit__(self, *exc_info): - #if exc_info[0] is not None: - #sys.excepthook(*exc_info) - #if self.childs is not None: - #self.par.replies.extend([conn.recv() for conn in self.childs]) - #else: - #self.par.finish(None) - diff --git a/pyqtgraph/multiprocess/processes.py b/pyqtgraph/multiprocess/processes.py deleted file mode 100644 index 6d32a5a1..00000000 --- a/pyqtgraph/multiprocess/processes.py +++ /dev/null @@ -1,472 +0,0 @@ -from .remoteproxy import RemoteEventHandler, ClosedError, NoResultError, LocalObjectProxy, ObjectProxy -import subprocess, atexit, os, sys, time, random, socket, signal -import multiprocessing.connection -import pyqtgraph as pg -try: - import cPickle as pickle -except ImportError: - import pickle - -__all__ = ['Process', 'QtProcess', 'ForkedProcess', 'ClosedError', 'NoResultError'] - -class Process(RemoteEventHandler): - """ - Bases: RemoteEventHandler - - This class is used to spawn and control a new python interpreter. - It uses subprocess.Popen to start the new process and communicates with it - using multiprocessingTest.Connection objects over a network socket. - - By default, the remote process will immediately enter an event-processing - loop that carries out requests send from the parent process. - - Remote control works mainly through proxy objects:: - - proc = Process() ## starts process, returns handle - rsys = proc._import('sys') ## asks remote process to import 'sys', returns - ## a proxy which references the imported module - rsys.stdout.write('hello\n') ## This message will be printed from the remote - ## process. Proxy objects can usually be used - ## exactly as regular objects are. - proc.close() ## Request the remote process shut down - - Requests made via proxy objects may be synchronous or asynchronous and may - return objects either by proxy or by value (if they are picklable). See - ProxyObject for more information. - """ - - def __init__(self, name=None, target=None, executable=None, copySysPath=True, debug=False, timeout=20, wrapStdout=None): - """ - ============ ============================================================= - Arguments: - name Optional name for this process used when printing messages - from the remote process. - target Optional function to call after starting remote process. - By default, this is startEventLoop(), which causes the remote - process to process requests from the parent process until it - is asked to quit. If you wish to specify a different target, - it must be picklable (bound methods are not). - copySysPath If True, copy the contents of sys.path to the remote process - debug If True, print detailed information about communication - with the child process. - wrapStdout If True (default on windows) then stdout and stderr from the - child process will be caught by the parent process and - forwarded to its stdout/stderr. This provides a workaround - for a python bug: http://bugs.python.org/issue3905 - but has the side effect that child output is significantly - delayed relative to the parent output. - ============ ============================================================= - """ - if target is None: - target = startEventLoop - if name is None: - name = str(self) - if executable is None: - executable = sys.executable - self.debug = debug - - ## random authentication key - authkey = os.urandom(20) - - ## Windows seems to have a hard time with hmac - if sys.platform.startswith('win'): - authkey = None - - #print "key:", ' '.join([str(ord(x)) for x in authkey]) - ## Listen for connection from remote process (and find free port number) - port = 10000 - while True: - try: - l = multiprocessing.connection.Listener(('localhost', int(port)), authkey=authkey) - break - except socket.error as ex: - if ex.errno != 98 and ex.errno != 10048: # unix=98, win=10048 - raise - port += 1 - - - ## start remote process, instruct it to run target function - sysPath = sys.path if copySysPath else None - bootstrap = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bootstrap.py')) - self.debugMsg('Starting child process (%s %s)' % (executable, bootstrap)) - - if wrapStdout is None: - wrapStdout = sys.platform.startswith('win') - - if wrapStdout: - ## note: we need all three streams to have their own PIPE due to this bug: - ## http://bugs.python.org/issue3905 - stdout = subprocess.PIPE - stderr = subprocess.PIPE - self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE, stdout=stdout, stderr=stderr) - ## to circumvent the bug and still make the output visible, we use - ## background threads to pass data from pipes to stdout/stderr - self._stdoutForwarder = FileForwarder(self.proc.stdout, "stdout") - self._stderrForwarder = FileForwarder(self.proc.stderr, "stderr") - else: - self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE) - - targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to - ## set its sys.path properly before unpickling the target - pid = os.getpid() # we must send pid to child because windows does not have getppid - - ## Send everything the remote process needs to start correctly - data = dict( - name=name+'_child', - port=port, - authkey=authkey, - ppid=pid, - targetStr=targetStr, - path=sysPath, - pyside=pg.Qt.USE_PYSIDE, - debug=debug - ) - pickle.dump(data, self.proc.stdin) - self.proc.stdin.close() - - ## open connection for remote process - self.debugMsg('Listening for child process on port %d, authkey=%s..' % (port, repr(authkey))) - while True: - try: - conn = l.accept() - break - except IOError as err: - if err.errno == 4: # interrupted; try again - continue - else: - raise - - RemoteEventHandler.__init__(self, conn, name+'_parent', pid=self.proc.pid, debug=debug) - self.debugMsg('Connected to child process.') - - atexit.register(self.join) - - - def join(self, timeout=10): - self.debugMsg('Joining child process..') - if self.proc.poll() is None: - self.close() - start = time.time() - while self.proc.poll() is None: - if timeout is not None and time.time() - start > timeout: - raise Exception('Timed out waiting for remote process to end.') - time.sleep(0.05) - self.debugMsg('Child process exited. (%d)' % self.proc.returncode) - - def debugMsg(self, msg): - if hasattr(self, '_stdoutForwarder'): - ## Lock output from subprocess to make sure we do not get line collisions - with self._stdoutForwarder.lock: - with self._stderrForwarder.lock: - RemoteEventHandler.debugMsg(self, msg) - else: - RemoteEventHandler.debugMsg(self, msg) - - -def startEventLoop(name, port, authkey, ppid, debug=False): - if debug: - import os - print('[%d] connecting to server at port localhost:%d, authkey=%s..' % (os.getpid(), port, repr(authkey))) - conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) - if debug: - print('[%d] connected; starting remote proxy.' % os.getpid()) - global HANDLER - #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() - HANDLER = RemoteEventHandler(conn, name, ppid, debug=debug) - while True: - try: - HANDLER.processRequests() # exception raised when the loop should exit - time.sleep(0.01) - except ClosedError: - break - - -class ForkedProcess(RemoteEventHandler): - """ - ForkedProcess is a substitute for Process that uses os.fork() to generate a new process. - This is much faster than starting a completely new interpreter and child processes - automatically have a copy of the entire program state from before the fork. This - makes it an appealing approach when parallelizing expensive computations. (see - also Parallelizer) - - However, fork() comes with some caveats and limitations: - - - fork() is not available on Windows. - - It is not possible to have a QApplication in both parent and child process - (unless both QApplications are created _after_ the call to fork()) - Attempts by the forked process to access Qt GUI elements created by the parent - will most likely cause the child to crash. - - Likewise, database connections are unlikely to function correctly in a forked child. - - Threads are not copied by fork(); the new process - will have only one thread that starts wherever fork() was called in the parent process. - - Forked processes are unceremoniously terminated when join() is called; they are not - given any opportunity to clean up. (This prevents them calling any cleanup code that - was only intended to be used by the parent process) - - Normally when fork()ing, open file handles are shared with the parent process, - which is potentially dangerous. ForkedProcess is careful to close all file handles - that are not explicitly needed--stdout, stderr, and a single pipe to the parent - process. - - """ - - def __init__(self, name=None, target=0, preProxy=None, randomReseed=True): - """ - When initializing, an optional target may be given. - If no target is specified, self.eventLoop will be used. - If None is given, no target will be called (and it will be up - to the caller to properly shut down the forked process) - - preProxy may be a dict of values that will appear as ObjectProxy - in the remote process (but do not need to be sent explicitly since - they are available immediately before the call to fork(). - Proxies will be availabe as self.proxies[name]. - - If randomReseed is True, the built-in random and numpy.random generators - will be reseeded in the child process. - """ - self.hasJoined = False - if target == 0: - target = self.eventLoop - if name is None: - name = str(self) - - conn, remoteConn = multiprocessing.Pipe() - - proxyIDs = {} - if preProxy is not None: - for k, v in preProxy.iteritems(): - proxyId = LocalObjectProxy.registerObject(v) - proxyIDs[k] = proxyId - - ppid = os.getpid() # write this down now; windows doesn't have getppid - pid = os.fork() - if pid == 0: - self.isParent = False - ## We are now in the forked process; need to be extra careful what we touch while here. - ## - no reading/writing file handles/sockets owned by parent process (stdout is ok) - ## - don't touch QtGui or QApplication at all; these are landmines. - ## - don't let the process call exit handlers - - os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process - - ## close all file handles we do not want shared with parent - conn.close() - sys.stdin.close() ## otherwise we screw with interactive prompts. - fid = remoteConn.fileno() - os.closerange(3, fid) - os.closerange(fid+1, 4096) ## just guessing on the maximum descriptor count.. - - ## Override any custom exception hooks - def excepthook(*args): - import traceback - traceback.print_exception(*args) - sys.excepthook = excepthook - - ## Make it harder to access QApplication instance - if 'PyQt4.QtGui' in sys.modules: - sys.modules['PyQt4.QtGui'].QApplication = None - sys.modules.pop('PyQt4.QtGui', None) - sys.modules.pop('PyQt4.QtCore', None) - - ## sabotage atexit callbacks - atexit._exithandlers = [] - atexit.register(lambda: os._exit(0)) - - if randomReseed: - if 'numpy.random' in sys.modules: - sys.modules['numpy.random'].seed(os.getpid() ^ int(time.time()*10000%10000)) - if 'random' in sys.modules: - sys.modules['random'].seed(os.getpid() ^ int(time.time()*10000%10000)) - - #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() - RemoteEventHandler.__init__(self, remoteConn, name+'_child', pid=ppid) - - self.forkedProxies = {} - for name, proxyId in proxyIDs.iteritems(): - self.forkedProxies[name] = ObjectProxy(ppid, proxyId=proxyId, typeStr=repr(preProxy[name])) - - if target is not None: - target() - - else: - self.isParent = True - self.childPid = pid - remoteConn.close() - RemoteEventHandler.handlers = {} ## don't want to inherit any of this from the parent. - - RemoteEventHandler.__init__(self, conn, name+'_parent', pid=pid) - atexit.register(self.join) - - - def eventLoop(self): - while True: - try: - self.processRequests() # exception raised when the loop should exit - time.sleep(0.01) - except ClosedError: - break - except: - print("Error occurred in forked event loop:") - sys.excepthook(*sys.exc_info()) - sys.exit(0) - - def join(self, timeout=10): - if self.hasJoined: - return - #os.kill(pid, 9) - try: - self.close(callSync='sync', timeout=timeout, noCleanup=True) ## ask the child process to exit and require that it return a confirmation. - os.waitpid(self.childPid, 0) - except IOError: ## probably remote process has already quit - pass - self.hasJoined = True - - def kill(self): - """Immediately kill the forked remote process. - This is generally safe because forked processes are already - expected to _avoid_ any cleanup at exit.""" - os.kill(self.childPid, signal.SIGKILL) - self.hasJoined = True - - - -##Special set of subclasses that implement a Qt event loop instead. - -class RemoteQtEventHandler(RemoteEventHandler): - def __init__(self, *args, **kwds): - RemoteEventHandler.__init__(self, *args, **kwds) - - def startEventTimer(self): - from pyqtgraph.Qt import QtGui, QtCore - self.timer = QtCore.QTimer() - self.timer.timeout.connect(self.processRequests) - self.timer.start(10) - - def processRequests(self): - try: - RemoteEventHandler.processRequests(self) - except ClosedError: - from pyqtgraph.Qt import QtGui, QtCore - QtGui.QApplication.instance().quit() - self.timer.stop() - #raise SystemExit - -class QtProcess(Process): - """ - QtProcess is essentially the same as Process, with two major differences: - - - The remote process starts by running startQtEventLoop() which creates a - QApplication in the remote process and uses a QTimer to trigger - remote event processing. This allows the remote process to have its own - GUI. - - A QTimer is also started on the parent process which polls for requests - from the child process. This allows Qt signals emitted within the child - process to invoke slots on the parent process and vice-versa. This can - be disabled using processRequests=False in the constructor. - - Example:: - - proc = QtProcess() - rQtGui = proc._import('PyQt4.QtGui') - btn = rQtGui.QPushButton('button on child process') - btn.show() - - def slot(): - print('slot invoked on parent process') - btn.clicked.connect(proxy(slot)) # be sure to send a proxy of the slot - """ - - def __init__(self, **kwds): - if 'target' not in kwds: - kwds['target'] = startQtEventLoop - self._processRequests = kwds.pop('processRequests', True) - Process.__init__(self, **kwds) - self.startEventTimer() - - def startEventTimer(self): - from pyqtgraph.Qt import QtGui, QtCore ## avoid module-level import to keep bootstrap snappy. - self.timer = QtCore.QTimer() - if self._processRequests: - app = QtGui.QApplication.instance() - if app is None: - raise Exception("Must create QApplication before starting QtProcess, or use QtProcess(processRequests=False)") - self.startRequestProcessing() - - def startRequestProcessing(self, interval=0.01): - """Start listening for requests coming from the child process. - This allows signals to be connected from the child process to the parent. - """ - self.timer.timeout.connect(self.processRequests) - self.timer.start(interval*1000) - - def stopRequestProcessing(self): - self.timer.stop() - - def processRequests(self): - try: - Process.processRequests(self) - except ClosedError: - self.timer.stop() - -def startQtEventLoop(name, port, authkey, ppid, debug=False): - if debug: - import os - print('[%d] connecting to server at port localhost:%d, authkey=%s..' % (os.getpid(), port, repr(authkey))) - conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) - if debug: - print('[%d] connected; starting remote proxy.' % os.getpid()) - from pyqtgraph.Qt import QtGui, QtCore - #from PyQt4 import QtGui, QtCore - app = QtGui.QApplication.instance() - #print app - if app is None: - app = QtGui.QApplication([]) - app.setQuitOnLastWindowClosed(False) ## generally we want the event loop to stay open - ## until it is explicitly closed by the parent process. - - global HANDLER - #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() - HANDLER = RemoteQtEventHandler(conn, name, ppid, debug=debug) - HANDLER.startEventTimer() - app.exec_() - -import threading -class FileForwarder(threading.Thread): - """ - Background thread that forwards data from one pipe to another. - This is used to catch data from stdout/stderr of the child process - and print it back out to stdout/stderr. We need this because this - bug: http://bugs.python.org/issue3905 _requires_ us to catch - stdout/stderr. - - *output* may be a file or 'stdout' or 'stderr'. In the latter cases, - sys.stdout/stderr are retrieved once for every line that is output, - which ensures that the correct behavior is achieved even if - sys.stdout/stderr are replaced at runtime. - """ - def __init__(self, input, output): - threading.Thread.__init__(self) - self.input = input - self.output = output - self.lock = threading.Lock() - self.start() - - def run(self): - if self.output == 'stdout': - while True: - line = self.input.readline() - with self.lock: - sys.stdout.write(line) - elif self.output == 'stderr': - while True: - line = self.input.readline() - with self.lock: - sys.stderr.write(line) - else: - while True: - line = self.input.readline() - with self.lock: - self.output.write(line) - - - diff --git a/pyqtgraph/multiprocess/remoteproxy.py b/pyqtgraph/multiprocess/remoteproxy.py deleted file mode 100644 index eba42ef3..00000000 --- a/pyqtgraph/multiprocess/remoteproxy.py +++ /dev/null @@ -1,1069 +0,0 @@ -import os, time, sys, traceback, weakref -import numpy as np -try: - import __builtin__ as builtins - import cPickle as pickle -except ImportError: - import builtins - import pickle - -class ClosedError(Exception): - """Raised when an event handler receives a request to close the connection - or discovers that the connection has been closed.""" - pass - -class NoResultError(Exception): - """Raised when a request for the return value of a remote call fails - because the call has not yet returned.""" - pass - - -class RemoteEventHandler(object): - """ - This class handles communication between two processes. One instance is present on - each process and listens for communication from the other process. This enables - (amongst other things) ObjectProxy instances to look up their attributes and call - their methods. - - This class is responsible for carrying out actions on behalf of the remote process. - Each instance holds one end of a Connection which allows python - objects to be passed between processes. - - For the most common operations, see _import(), close(), and transfer() - - To handle and respond to incoming requests, RemoteEventHandler requires that its - processRequests method is called repeatedly (this is usually handled by the Process - classes defined in multiprocess.processes). - - - - - """ - handlers = {} ## maps {process ID : handler}. This allows unpickler to determine which process - ## an object proxy belongs to - - def __init__(self, connection, name, pid, debug=False): - self.debug = debug - self.conn = connection - self.name = name - self.results = {} ## reqId: (status, result); cache of request results received from the remote process - ## status is either 'result' or 'error' - ## if 'error', then result will be (exception, formatted exceprion) - ## where exception may be None if it could not be passed through the Connection. - - self.proxies = {} ## maps {weakref(proxy): proxyId}; used to inform the remote process when a proxy has been deleted. - - ## attributes that affect the behavior of the proxy. - ## See ObjectProxy._setProxyOptions for description - self.proxyOptions = { - 'callSync': 'sync', ## 'sync', 'async', 'off' - 'timeout': 10, ## float - 'returnType': 'auto', ## 'proxy', 'value', 'auto' - 'autoProxy': False, ## bool - 'deferGetattr': False, ## True, False - 'noProxyTypes': [ type(None), str, int, float, tuple, list, dict, LocalObjectProxy, ObjectProxy ], - } - - self.nextRequestId = 0 - self.exited = False - - RemoteEventHandler.handlers[pid] = self ## register this handler as the one communicating with pid - - @classmethod - def getHandler(cls, pid): - try: - return cls.handlers[pid] - except: - print(pid, cls.handlers) - raise - - def debugMsg(self, msg): - if not self.debug: - return - print("[%d] %s" % (os.getpid(), str(msg))) - - def getProxyOption(self, opt): - return self.proxyOptions[opt] - - def setProxyOptions(self, **kwds): - """ - Set the default behavior options for object proxies. - See ObjectProxy._setProxyOptions for more info. - """ - self.proxyOptions.update(kwds) - - def processRequests(self): - """Process all pending requests from the pipe, return - after no more events are immediately available. (non-blocking) - Returns the number of events processed. - """ - if self.exited: - self.debugMsg(' processRequests: exited already; raise ClosedError.') - raise ClosedError() - - numProcessed = 0 - while self.conn.poll(): - try: - self.handleRequest() - numProcessed += 1 - except ClosedError: - self.debugMsg('processRequests: got ClosedError from handleRequest; setting exited=True.') - self.exited = True - raise - #except IOError as err: ## let handleRequest take care of this. - #self.debugMsg(' got IOError from handleRequest; try again.') - #if err.errno == 4: ## interrupted system call; try again - #continue - #else: - #raise - except: - print("Error in process %s" % self.name) - sys.excepthook(*sys.exc_info()) - - if numProcessed > 0: - self.debugMsg('processRequests: finished %d requests' % numProcessed) - return numProcessed - - def handleRequest(self): - """Handle a single request from the remote process. - Blocks until a request is available.""" - result = None - while True: - try: - ## args, kwds are double-pickled to ensure this recv() call never fails - cmd, reqId, nByteMsgs, optStr = self.conn.recv() - break - except EOFError: - self.debugMsg(' handleRequest: got EOFError from recv; raise ClosedError.') - ## remote process has shut down; end event loop - raise ClosedError() - except IOError as err: - if err.errno == 4: ## interrupted system call; try again - self.debugMsg(' handleRequest: got IOError 4 from recv; try again.') - continue - else: - self.debugMsg(' handleRequest: got IOError %d from recv (%s); raise ClosedError.' % (err.errno, err.strerror)) - raise ClosedError() - - self.debugMsg(" handleRequest: received %s %s" % (str(cmd), str(reqId))) - - ## read byte messages following the main request - byteData = [] - if nByteMsgs > 0: - self.debugMsg(" handleRequest: reading %d byte messages" % nByteMsgs) - for i in range(nByteMsgs): - while True: - try: - byteData.append(self.conn.recv_bytes()) - break - except EOFError: - self.debugMsg(" handleRequest: got EOF while reading byte messages; raise ClosedError.") - raise ClosedError() - except IOError as err: - if err.errno == 4: - self.debugMsg(" handleRequest: got IOError 4 while reading byte messages; try again.") - continue - else: - self.debugMsg(" handleRequest: got IOError while reading byte messages; raise ClosedError.") - raise ClosedError() - - - try: - if cmd == 'result' or cmd == 'error': - resultId = reqId - reqId = None ## prevents attempt to return information from this request - ## (this is already a return from a previous request) - - opts = pickle.loads(optStr) - self.debugMsg(" handleRequest: id=%s opts=%s" % (str(reqId), str(opts))) - #print os.getpid(), "received request:", cmd, reqId, opts - returnType = opts.get('returnType', 'auto') - - if cmd == 'result': - self.results[resultId] = ('result', opts['result']) - elif cmd == 'error': - self.results[resultId] = ('error', (opts['exception'], opts['excString'])) - elif cmd == 'getObjAttr': - result = getattr(opts['obj'], opts['attr']) - elif cmd == 'callObj': - obj = opts['obj'] - fnargs = opts['args'] - fnkwds = opts['kwds'] - - ## If arrays were sent as byte messages, they must be re-inserted into the - ## arguments - if len(byteData) > 0: - for i,arg in enumerate(fnargs): - if isinstance(arg, tuple) and len(arg) > 0 and arg[0] == '__byte_message__': - ind = arg[1] - dtype, shape = arg[2] - fnargs[i] = np.fromstring(byteData[ind], dtype=dtype).reshape(shape) - for k,arg in fnkwds.items(): - if isinstance(arg, tuple) and len(arg) > 0 and arg[0] == '__byte_message__': - ind = arg[1] - dtype, shape = arg[2] - fnkwds[k] = np.fromstring(byteData[ind], dtype=dtype).reshape(shape) - - if len(fnkwds) == 0: ## need to do this because some functions do not allow keyword arguments. - try: - result = obj(*fnargs) - except: - print("Failed to call object %s: %d, %s" % (obj, len(fnargs), fnargs[1:])) - raise - else: - result = obj(*fnargs, **fnkwds) - - elif cmd == 'getObjValue': - result = opts['obj'] ## has already been unpickled into its local value - returnType = 'value' - elif cmd == 'transfer': - result = opts['obj'] - returnType = 'proxy' - elif cmd == 'transferArray': - ## read array data from next message: - result = np.fromstring(byteData[0], dtype=opts['dtype']).reshape(opts['shape']) - returnType = 'proxy' - elif cmd == 'import': - name = opts['module'] - fromlist = opts.get('fromlist', []) - mod = builtins.__import__(name, fromlist=fromlist) - - if len(fromlist) == 0: - parts = name.lstrip('.').split('.') - result = mod - for part in parts[1:]: - result = getattr(result, part) - else: - result = map(mod.__getattr__, fromlist) - - elif cmd == 'del': - LocalObjectProxy.releaseProxyId(opts['proxyId']) - #del self.proxiedObjects[opts['objId']] - - elif cmd == 'close': - if reqId is not None: - result = True - returnType = 'value' - - exc = None - except: - exc = sys.exc_info() - - - - if reqId is not None: - if exc is None: - self.debugMsg(" handleRequest: sending return value for %d: %s" % (reqId, str(result))) - #print "returnValue:", returnValue, result - if returnType == 'auto': - result = self.autoProxy(result, self.proxyOptions['noProxyTypes']) - elif returnType == 'proxy': - result = LocalObjectProxy(result) - - try: - self.replyResult(reqId, result) - except: - sys.excepthook(*sys.exc_info()) - self.replyError(reqId, *sys.exc_info()) - else: - self.debugMsg(" handleRequest: returning exception for %d" % reqId) - self.replyError(reqId, *exc) - - elif exc is not None: - sys.excepthook(*exc) - - if cmd == 'close': - if opts.get('noCleanup', False) is True: - os._exit(0) ## exit immediately, do not pass GO, do not collect $200. - ## (more importantly, do not call any code that would - ## normally be invoked at exit) - else: - raise ClosedError() - - - - def replyResult(self, reqId, result): - self.send(request='result', reqId=reqId, callSync='off', opts=dict(result=result)) - - def replyError(self, reqId, *exc): - print("error: %s %s %s" % (self.name, str(reqId), str(exc[1]))) - excStr = traceback.format_exception(*exc) - try: - self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=exc[1], excString=excStr)) - except: - self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=None, excString=excStr)) - - def send(self, request, opts=None, reqId=None, callSync='sync', timeout=10, returnType=None, byteData=None, **kwds): - """Send a request or return packet to the remote process. - Generally it is not necessary to call this method directly; it is for internal use. - (The docstring has information that is nevertheless useful to the programmer - as it describes the internal protocol used to communicate between processes) - - ========== ==================================================================== - Arguments: - request String describing the type of request being sent (see below) - reqId Integer uniquely linking a result back to the request that generated - it. (most requests leave this blank) - callSync 'sync': return the actual result of the request - 'async': return a Request object which can be used to look up the - result later - 'off': return no result - timeout Time in seconds to wait for a response when callSync=='sync' - opts Extra arguments sent to the remote process that determine the way - the request will be handled (see below) - returnType 'proxy', 'value', or 'auto' - byteData If specified, this is a list of objects to be sent as byte messages - to the remote process. - This is used to send large arrays without the cost of pickling. - ========== ==================================================================== - - Description of request strings and options allowed for each: - - ============= ============= ======================================================== - request option description - ------------- ------------- -------------------------------------------------------- - getObjAttr Request the remote process return (proxy to) an - attribute of an object. - obj reference to object whose attribute should be - returned - attr string name of attribute to return - returnValue bool or 'auto' indicating whether to return a proxy or - the actual value. - - callObj Request the remote process call a function or - method. If a request ID is given, then the call's - return value will be sent back (or information - about the error that occurred while running the - function) - obj the (reference to) object to call - args tuple of arguments to pass to callable - kwds dict of keyword arguments to pass to callable - returnValue bool or 'auto' indicating whether to return a proxy or - the actual value. - - getObjValue Request the remote process return the value of - a proxied object (must be picklable) - obj reference to object whose value should be returned - - transfer Copy an object to the remote process and request - it return a proxy for the new object. - obj The object to transfer. - - import Request the remote process import new symbols - and return proxy(ies) to the imported objects - module the string name of the module to import - fromlist optional list of string names to import from module - - del Inform the remote process that a proxy has been - released (thus the remote process may be able to - release the original object) - proxyId id of proxy which is no longer referenced by - remote host - - close Instruct the remote process to stop its event loop - and exit. Optionally, this request may return a - confirmation. - - result Inform the remote process that its request has - been processed - result return value of a request - - error Inform the remote process that its request failed - exception the Exception that was raised (or None if the - exception could not be pickled) - excString string-formatted version of the exception and - traceback - ============= ===================================================================== - """ - #if len(kwds) > 0: - #print "Warning: send() ignored args:", kwds - - if opts is None: - opts = {} - - assert callSync in ['off', 'sync', 'async'], 'callSync must be one of "off", "sync", or "async"' - if reqId is None: - if callSync != 'off': ## requested return value; use the next available request ID - reqId = self.nextRequestId - self.nextRequestId += 1 - else: - ## If requestId is provided, this _must_ be a response to a previously received request. - assert request in ['result', 'error'] - - if returnType is not None: - opts['returnType'] = returnType - - #print os.getpid(), "send request:", request, reqId, opts - - ## double-pickle args to ensure that at least status and request ID get through - try: - optStr = pickle.dumps(opts) - except: - print("==== Error pickling this object: ====") - print(opts) - print("=======================================") - raise - - nByteMsgs = 0 - if byteData is not None: - nByteMsgs = len(byteData) - - ## Send primary request - request = (request, reqId, nByteMsgs, optStr) - self.debugMsg('send request: cmd=%s nByteMsgs=%d id=%s opts=%s' % (str(request[0]), nByteMsgs, str(reqId), str(opts))) - self.conn.send(request) - - ## follow up by sending byte messages - if byteData is not None: - for obj in byteData: ## Remote process _must_ be prepared to read the same number of byte messages! - self.conn.send_bytes(obj) - self.debugMsg(' sent %d byte messages' % len(byteData)) - - self.debugMsg(' call sync: %s' % callSync) - if callSync == 'off': - return - - req = Request(self, reqId, description=str(request), timeout=timeout) - if callSync == 'async': - return req - - if callSync == 'sync': - try: - return req.result() - except NoResultError: - return req - - def close(self, callSync='off', noCleanup=False, **kwds): - self.send(request='close', opts=dict(noCleanup=noCleanup), callSync=callSync, **kwds) - - def getResult(self, reqId): - ## raises NoResultError if the result is not available yet - #print self.results.keys(), os.getpid() - if reqId not in self.results: - try: - self.processRequests() - except ClosedError: ## even if remote connection has closed, we may have - ## received new data during this call to processRequests() - pass - if reqId not in self.results: - raise NoResultError() - status, result = self.results.pop(reqId) - if status == 'result': - return result - elif status == 'error': - #print ''.join(result) - exc, excStr = result - if exc is not None: - print("===== Remote process raised exception on request: =====") - print(''.join(excStr)) - print("===== Local Traceback to request follows: =====") - raise exc - else: - print(''.join(excStr)) - raise Exception("Error getting result. See above for exception from remote process.") - - else: - raise Exception("Internal error.") - - def _import(self, mod, **kwds): - """ - Request the remote process import a module (or symbols from a module) - and return the proxied results. Uses built-in __import__() function, but - adds a bit more processing: - - _import('module') => returns module - _import('module.submodule') => returns submodule - (note this differs from behavior of __import__) - _import('module', fromlist=[name1, name2, ...]) => returns [module.name1, module.name2, ...] - (this also differs from behavior of __import__) - - """ - return self.send(request='import', callSync='sync', opts=dict(module=mod), **kwds) - - def getObjAttr(self, obj, attr, **kwds): - return self.send(request='getObjAttr', opts=dict(obj=obj, attr=attr), **kwds) - - def getObjValue(self, obj, **kwds): - return self.send(request='getObjValue', opts=dict(obj=obj), **kwds) - - def callObj(self, obj, args, kwds, **opts): - opts = opts.copy() - args = list(args) - - ## Decide whether to send arguments by value or by proxy - noProxyTypes = opts.pop('noProxyTypes', None) - if noProxyTypes is None: - noProxyTypes = self.proxyOptions['noProxyTypes'] - - autoProxy = opts.pop('autoProxy', self.proxyOptions['autoProxy']) - if autoProxy is True: - args = [self.autoProxy(v, noProxyTypes) for v in args] - for k, v in kwds.iteritems(): - opts[k] = self.autoProxy(v, noProxyTypes) - - byteMsgs = [] - - ## If there are arrays in the arguments, send those as byte messages. - ## We do this because pickling arrays is too expensive. - for i,arg in enumerate(args): - if arg.__class__ == np.ndarray: - args[i] = ("__byte_message__", len(byteMsgs), (arg.dtype, arg.shape)) - byteMsgs.append(arg) - for k,v in kwds.items(): - if v.__class__ == np.ndarray: - kwds[k] = ("__byte_message__", len(byteMsgs), (v.dtype, v.shape)) - byteMsgs.append(v) - - return self.send(request='callObj', opts=dict(obj=obj, args=args, kwds=kwds), byteData=byteMsgs, **opts) - - def registerProxy(self, proxy): - ref = weakref.ref(proxy, self.deleteProxy) - self.proxies[ref] = proxy._proxyId - - def deleteProxy(self, ref): - proxyId = self.proxies.pop(ref) - try: - self.send(request='del', opts=dict(proxyId=proxyId), callSync='off') - except IOError: ## if remote process has closed down, there is no need to send delete requests anymore - pass - - def transfer(self, obj, **kwds): - """ - Transfer an object by value to the remote host (the object must be picklable) - and return a proxy for the new remote object. - """ - if obj.__class__ is np.ndarray: - opts = {'dtype': obj.dtype, 'shape': obj.shape} - return self.send(request='transferArray', opts=opts, byteData=[obj], **kwds) - else: - return self.send(request='transfer', opts=dict(obj=obj), **kwds) - - def autoProxy(self, obj, noProxyTypes): - ## Return object wrapped in LocalObjectProxy _unless_ its type is in noProxyTypes. - for typ in noProxyTypes: - if isinstance(obj, typ): - return obj - return LocalObjectProxy(obj) - - -class Request(object): - """ - Request objects are returned when calling an ObjectProxy in asynchronous mode - or if a synchronous call has timed out. Use hasResult() to ask whether - the result of the call has been returned yet. Use result() to get - the returned value. - """ - def __init__(self, process, reqId, description=None, timeout=10): - self.proc = process - self.description = description - self.reqId = reqId - self.gotResult = False - self._result = None - self.timeout = timeout - - def result(self, block=True, timeout=None): - """ - Return the result for this request. - - If block is True, wait until the result has arrived or *timeout* seconds passes. - If the timeout is reached, raise NoResultError. (use timeout=None to disable) - If block is False, raise NoResultError immediately if the result has not arrived yet. - - If the process's connection has closed before the result arrives, raise ClosedError. - """ - - if self.gotResult: - return self._result - - if timeout is None: - timeout = self.timeout - - if block: - start = time.time() - while not self.hasResult(): - if self.proc.exited: - raise ClosedError() - time.sleep(0.005) - if timeout >= 0 and time.time() - start > timeout: - print("Request timed out: %s" % self.description) - import traceback - traceback.print_stack() - raise NoResultError() - return self._result - else: - self._result = self.proc.getResult(self.reqId) ## raises NoResultError if result is not available yet - self.gotResult = True - return self._result - - def hasResult(self): - """Returns True if the result for this request has arrived.""" - try: - self.result(block=False) - except NoResultError: - pass - - return self.gotResult - -class LocalObjectProxy(object): - """ - Used for wrapping local objects to ensure that they are send by proxy to a remote host. - Note that 'proxy' is just a shorter alias for LocalObjectProxy. - - For example:: - - data = [1,2,3,4,5] - remotePlot.plot(data) ## by default, lists are pickled and sent by value - remotePlot.plot(proxy(data)) ## force the object to be sent by proxy - - """ - nextProxyId = 0 - proxiedObjects = {} ## maps {proxyId: object} - - - @classmethod - def registerObject(cls, obj): - ## assign it a unique ID so we can keep a reference to the local object - - pid = cls.nextProxyId - cls.nextProxyId += 1 - cls.proxiedObjects[pid] = obj - #print "register:", cls.proxiedObjects - return pid - - @classmethod - def lookupProxyId(cls, pid): - return cls.proxiedObjects[pid] - - @classmethod - def releaseProxyId(cls, pid): - del cls.proxiedObjects[pid] - #print "release:", cls.proxiedObjects - - def __init__(self, obj, **opts): - """ - Create a 'local' proxy object that, when sent to a remote host, - will appear as a normal ObjectProxy to *obj*. - Any extra keyword arguments are passed to proxy._setProxyOptions() - on the remote side. - """ - self.processId = os.getpid() - #self.objectId = id(obj) - self.typeStr = repr(obj) - #self.handler = handler - self.obj = obj - self.opts = opts - - def __reduce__(self): - ## a proxy is being pickled and sent to a remote process. - ## every time this happens, a new proxy will be generated in the remote process, - ## so we keep a new ID so we can track when each is released. - pid = LocalObjectProxy.registerObject(self.obj) - return (unpickleObjectProxy, (self.processId, pid, self.typeStr, None, self.opts)) - -## alias -proxy = LocalObjectProxy - -def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None, opts=None): - if processId == os.getpid(): - obj = LocalObjectProxy.lookupProxyId(proxyId) - if attributes is not None: - for attr in attributes: - obj = getattr(obj, attr) - return obj - else: - proxy = ObjectProxy(processId, proxyId=proxyId, typeStr=typeStr) - if opts is not None: - proxy._setProxyOptions(**opts) - return proxy - -class ObjectProxy(object): - """ - Proxy to an object stored by the remote process. Proxies are created - by calling Process._import(), Process.transfer(), or by requesting/calling - attributes on existing proxy objects. - - For the most part, this object can be used exactly as if it - were a local object:: - - rsys = proc._import('sys') # returns proxy to sys module on remote process - rsys.stdout # proxy to remote sys.stdout - rsys.stdout.write # proxy to remote sys.stdout.write - rsys.stdout.write('hello') # calls sys.stdout.write('hello') on remote machine - # and returns the result (None) - - When calling a proxy to a remote function, the call can be made synchronous - (result of call is returned immediately), asynchronous (result is returned later), - or return can be disabled entirely:: - - ros = proc._import('os') - - ## synchronous call; result is returned immediately - pid = ros.getpid() - - ## asynchronous call - request = ros.getpid(_callSync='async') - while not request.hasResult(): - time.sleep(0.01) - pid = request.result() - - ## disable return when we know it isn't needed - rsys.stdout.write('hello', _callSync='off') - - Additionally, values returned from a remote function call are automatically - returned either by value (must be picklable) or by proxy. - This behavior can be forced:: - - rnp = proc._import('numpy') - arrProxy = rnp.array([1,2,3,4], _returnType='proxy') - arrValue = rnp.array([1,2,3,4], _returnType='value') - - The default callSync and returnType behaviors (as well as others) can be set - for each proxy individually using ObjectProxy._setProxyOptions() or globally using - proc.setProxyOptions(). - - """ - def __init__(self, processId, proxyId, typeStr='', parent=None): - object.__init__(self) - ## can't set attributes directly because setattr is overridden. - self.__dict__['_processId'] = processId - self.__dict__['_typeStr'] = typeStr - self.__dict__['_proxyId'] = proxyId - self.__dict__['_attributes'] = () - ## attributes that affect the behavior of the proxy. - ## in all cases, a value of None causes the proxy to ask - ## its parent event handler to make the decision - self.__dict__['_proxyOptions'] = { - 'callSync': None, ## 'sync', 'async', None - 'timeout': None, ## float, None - 'returnType': None, ## 'proxy', 'value', 'auto', None - 'deferGetattr': None, ## True, False, None - 'noProxyTypes': None, ## list of types to send by value instead of by proxy - } - - self.__dict__['_handler'] = RemoteEventHandler.getHandler(processId) - self.__dict__['_handler'].registerProxy(self) ## handler will watch proxy; inform remote process when the proxy is deleted. - - def _setProxyOptions(self, **kwds): - """ - Change the behavior of this proxy. For all options, a value of None - will cause the proxy to instead use the default behavior defined - by its parent Process. - - Options are: - - ============= ============================================================= - callSync 'sync', 'async', 'off', or None. - If 'async', then calling methods will return a Request object - which can be used to inquire later about the result of the - method call. - If 'sync', then calling a method - will block until the remote process has returned its result - or the timeout has elapsed (in this case, a Request object - is returned instead). - If 'off', then the remote process is instructed _not_ to - reply and the method call will return None immediately. - returnType 'auto', 'proxy', 'value', or None. - If 'proxy', then the value returned when calling a method - will be a proxy to the object on the remote process. - If 'value', then attempt to pickle the returned object and - send it back. - If 'auto', then the decision is made by consulting the - 'noProxyTypes' option. - autoProxy bool or None. If True, arguments to __call__ are - automatically converted to proxy unless their type is - listed in noProxyTypes (see below). If False, arguments - are left untouched. Use proxy(obj) to manually convert - arguments before sending. - timeout float or None. Length of time to wait during synchronous - requests before returning a Request object instead. - deferGetattr True, False, or None. - If False, all attribute requests will be sent to the remote - process immediately and will block until a response is - received (or timeout has elapsed). - If True, requesting an attribute from the proxy returns a - new proxy immediately. The remote process is _not_ contacted - to make this request. This is faster, but it is possible to - request an attribute that does not exist on the proxied - object. In this case, AttributeError will not be raised - until an attempt is made to look up the attribute on the - remote process. - noProxyTypes List of object types that should _not_ be proxied when - sent to the remote process. - ============= ============================================================= - """ - self._proxyOptions.update(kwds) - - def _getValue(self): - """ - Return the value of the proxied object - (the remote object must be picklable) - """ - return self._handler.getObjValue(self) - - def _getProxyOption(self, opt): - val = self._proxyOptions[opt] - if val is None: - return self._handler.getProxyOption(opt) - return val - - def _getProxyOptions(self): - return dict([(k, self._getProxyOption(k)) for k in self._proxyOptions]) - - def __reduce__(self): - return (unpickleObjectProxy, (self._processId, self._proxyId, self._typeStr, self._attributes)) - - def __repr__(self): - #objRepr = self.__getattr__('__repr__')(callSync='value') - return "" % (self._processId, self._proxyId, self._typeStr) - - - def __getattr__(self, attr, **kwds): - """ - Calls __getattr__ on the remote object and returns the attribute - by value or by proxy depending on the options set (see - ObjectProxy._setProxyOptions and RemoteEventHandler.setProxyOptions) - - If the option 'deferGetattr' is True for this proxy, then a new proxy object - is returned _without_ asking the remote object whether the named attribute exists. - This can save time when making multiple chained attribute requests, - but may also defer a possible AttributeError until later, making - them more difficult to debug. - """ - opts = self._getProxyOptions() - for k in opts: - if '_'+k in kwds: - opts[k] = kwds.pop('_'+k) - if opts['deferGetattr'] is True: - return self._deferredAttr(attr) - else: - #opts = self._getProxyOptions() - return self._handler.getObjAttr(self, attr, **opts) - - def _deferredAttr(self, attr): - return DeferredObjectProxy(self, attr) - - def __call__(self, *args, **kwds): - """ - Attempts to call the proxied object from the remote process. - Accepts extra keyword arguments: - - _callSync 'off', 'sync', or 'async' - _returnType 'value', 'proxy', or 'auto' - - If the remote call raises an exception on the remote process, - it will be re-raised on the local process. - - """ - opts = self._getProxyOptions() - for k in opts: - if '_'+k in kwds: - opts[k] = kwds.pop('_'+k) - return self._handler.callObj(obj=self, args=args, kwds=kwds, **opts) - - - ## Explicitly proxy special methods. Is there a better way to do this?? - - def _getSpecialAttr(self, attr): - ## this just gives us an easy way to change the behavior of the special methods - return self._deferredAttr(attr) - - def __getitem__(self, *args): - return self._getSpecialAttr('__getitem__')(*args) - - def __setitem__(self, *args): - return self._getSpecialAttr('__setitem__')(*args, _callSync='off') - - def __setattr__(self, *args): - return self._getSpecialAttr('__setattr__')(*args, _callSync='off') - - def __str__(self, *args): - return self._getSpecialAttr('__str__')(*args, _returnType='value') - - def __len__(self, *args): - return self._getSpecialAttr('__len__')(*args) - - def __add__(self, *args): - return self._getSpecialAttr('__add__')(*args) - - def __sub__(self, *args): - return self._getSpecialAttr('__sub__')(*args) - - def __div__(self, *args): - return self._getSpecialAttr('__div__')(*args) - - def __truediv__(self, *args): - return self._getSpecialAttr('__truediv__')(*args) - - def __floordiv__(self, *args): - return self._getSpecialAttr('__floordiv__')(*args) - - def __mul__(self, *args): - return self._getSpecialAttr('__mul__')(*args) - - def __pow__(self, *args): - return self._getSpecialAttr('__pow__')(*args) - - def __iadd__(self, *args): - return self._getSpecialAttr('__iadd__')(*args, _callSync='off') - - def __isub__(self, *args): - return self._getSpecialAttr('__isub__')(*args, _callSync='off') - - def __idiv__(self, *args): - return self._getSpecialAttr('__idiv__')(*args, _callSync='off') - - def __itruediv__(self, *args): - return self._getSpecialAttr('__itruediv__')(*args, _callSync='off') - - def __ifloordiv__(self, *args): - return self._getSpecialAttr('__ifloordiv__')(*args, _callSync='off') - - def __imul__(self, *args): - return self._getSpecialAttr('__imul__')(*args, _callSync='off') - - def __ipow__(self, *args): - return self._getSpecialAttr('__ipow__')(*args, _callSync='off') - - def __rshift__(self, *args): - return self._getSpecialAttr('__rshift__')(*args) - - def __lshift__(self, *args): - return self._getSpecialAttr('__lshift__')(*args) - - def __irshift__(self, *args): - return self._getSpecialAttr('__irshift__')(*args, _callSync='off') - - def __ilshift__(self, *args): - return self._getSpecialAttr('__ilshift__')(*args, _callSync='off') - - def __eq__(self, *args): - return self._getSpecialAttr('__eq__')(*args) - - def __ne__(self, *args): - return self._getSpecialAttr('__ne__')(*args) - - def __lt__(self, *args): - return self._getSpecialAttr('__lt__')(*args) - - def __gt__(self, *args): - return self._getSpecialAttr('__gt__')(*args) - - def __le__(self, *args): - return self._getSpecialAttr('__le__')(*args) - - def __ge__(self, *args): - return self._getSpecialAttr('__ge__')(*args) - - def __and__(self, *args): - return self._getSpecialAttr('__and__')(*args) - - def __or__(self, *args): - return self._getSpecialAttr('__or__')(*args) - - def __xor__(self, *args): - return self._getSpecialAttr('__xor__')(*args) - - def __iand__(self, *args): - return self._getSpecialAttr('__iand__')(*args, _callSync='off') - - def __ior__(self, *args): - return self._getSpecialAttr('__ior__')(*args, _callSync='off') - - def __ixor__(self, *args): - return self._getSpecialAttr('__ixor__')(*args, _callSync='off') - - def __mod__(self, *args): - return self._getSpecialAttr('__mod__')(*args) - - def __radd__(self, *args): - return self._getSpecialAttr('__radd__')(*args) - - def __rsub__(self, *args): - return self._getSpecialAttr('__rsub__')(*args) - - def __rdiv__(self, *args): - return self._getSpecialAttr('__rdiv__')(*args) - - def __rfloordiv__(self, *args): - return self._getSpecialAttr('__rfloordiv__')(*args) - - def __rtruediv__(self, *args): - return self._getSpecialAttr('__rtruediv__')(*args) - - def __rmul__(self, *args): - return self._getSpecialAttr('__rmul__')(*args) - - def __rpow__(self, *args): - return self._getSpecialAttr('__rpow__')(*args) - - def __rrshift__(self, *args): - return self._getSpecialAttr('__rrshift__')(*args) - - def __rlshift__(self, *args): - return self._getSpecialAttr('__rlshift__')(*args) - - def __rand__(self, *args): - return self._getSpecialAttr('__rand__')(*args) - - def __ror__(self, *args): - return self._getSpecialAttr('__ror__')(*args) - - def __rxor__(self, *args): - return self._getSpecialAttr('__ror__')(*args) - - def __rmod__(self, *args): - return self._getSpecialAttr('__rmod__')(*args) - - def __hash__(self): - ## Required for python3 since __eq__ is defined. - return id(self) - -class DeferredObjectProxy(ObjectProxy): - """ - This class represents an attribute (or sub-attribute) of a proxied object. - It is used to speed up attribute requests. Take the following scenario:: - - rsys = proc._import('sys') - rsys.stdout.write('hello') - - For this simple example, a total of 4 synchronous requests are made to - the remote process: - - 1) import sys - 2) getattr(sys, 'stdout') - 3) getattr(stdout, 'write') - 4) write('hello') - - This takes a lot longer than running the equivalent code locally. To - speed things up, we can 'defer' the two attribute lookups so they are - only carried out when neccessary:: - - rsys = proc._import('sys') - rsys._setProxyOptions(deferGetattr=True) - rsys.stdout.write('hello') - - This example only makes two requests to the remote process; the two - attribute lookups immediately return DeferredObjectProxy instances - immediately without contacting the remote process. When the call - to write() is made, all attribute requests are processed at the same time. - - Note that if the attributes requested do not exist on the remote object, - making the call to write() will raise an AttributeError. - """ - def __init__(self, parentProxy, attribute): - ## can't set attributes directly because setattr is overridden. - for k in ['_processId', '_typeStr', '_proxyId', '_handler']: - self.__dict__[k] = getattr(parentProxy, k) - self.__dict__['_parent'] = parentProxy ## make sure parent stays alive - self.__dict__['_attributes'] = parentProxy._attributes + (attribute,) - self.__dict__['_proxyOptions'] = parentProxy._proxyOptions.copy() - - def __repr__(self): - return ObjectProxy.__repr__(self) + '.' + '.'.join(self._attributes) - - def _undefer(self): - """ - Return a non-deferred ObjectProxy referencing the same object - """ - return self._parent.__getattr__(self._attributes[-1], _deferGetattr=False) - diff --git a/pyqtgraph/numpy_fix.py b/pyqtgraph/numpy_fix.py deleted file mode 100644 index 2fa8ef1f..00000000 --- a/pyqtgraph/numpy_fix.py +++ /dev/null @@ -1,22 +0,0 @@ -try: - import numpy as np - - ## Wrap np.concatenate to catch and avoid a segmentation fault bug - ## (numpy trac issue #2084) - if not hasattr(np, 'concatenate_orig'): - np.concatenate_orig = np.concatenate - def concatenate(vals, *args, **kwds): - """Wrapper around numpy.concatenate (see pyqtgraph/numpy_fix.py)""" - dtypes = [getattr(v, 'dtype', None) for v in vals] - names = [getattr(dt, 'names', None) for dt in dtypes] - if len(dtypes) < 2 or all([n is None for n in names]): - return np.concatenate_orig(vals, *args, **kwds) - if any([dt != dtypes[0] for dt in dtypes[1:]]): - raise TypeError("Cannot concatenate structured arrays of different dtype.") - return np.concatenate_orig(vals, *args, **kwds) - - np.concatenate = concatenate - -except ImportError: - pass - diff --git a/pyqtgraph/opengl/GLGraphicsItem.py b/pyqtgraph/opengl/GLGraphicsItem.py deleted file mode 100644 index 9680fba7..00000000 --- a/pyqtgraph/opengl/GLGraphicsItem.py +++ /dev/null @@ -1,293 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph import Transform3D -from OpenGL.GL import * -from OpenGL import GL - -GLOptions = { - 'opaque': { - GL_DEPTH_TEST: True, - GL_BLEND: False, - GL_ALPHA_TEST: False, - GL_CULL_FACE: False, - }, - 'translucent': { - GL_DEPTH_TEST: True, - GL_BLEND: True, - GL_ALPHA_TEST: False, - GL_CULL_FACE: False, - 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), - }, - 'additive': { - GL_DEPTH_TEST: False, - GL_BLEND: True, - GL_ALPHA_TEST: False, - GL_CULL_FACE: False, - 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE), - }, -} - - -class GLGraphicsItem(QtCore.QObject): - def __init__(self, parentItem=None): - QtCore.QObject.__init__(self) - self.__parent = None - self.__view = None - self.__children = set() - self.__transform = Transform3D() - self.__visible = True - self.setParentItem(parentItem) - self.setDepthValue(0) - self.__glOpts = {} - - def setParentItem(self, item): - """Set this item's parent in the scenegraph hierarchy.""" - if self.__parent is not None: - self.__parent.__children.remove(self) - if item is not None: - item.__children.add(self) - self.__parent = item - - if self.__parent is not None and self.view() is not self.__parent.view(): - if self.view() is not None: - self.view().removeItem(self) - self.__parent.view().addItem(self) - - def setGLOptions(self, opts): - """ - Set the OpenGL state options to use immediately before drawing this item. - (Note that subclasses must call setupGLState before painting for this to work) - - The simplest way to invoke this method is to pass in the name of - a predefined set of options (see the GLOptions variable): - - ============= ====================================================== - opaque Enables depth testing and disables blending - translucent Enables depth testing and blending - Elements must be drawn sorted back-to-front for - translucency to work correctly. - additive Disables depth testing, enables blending. - Colors are added together, so sorting is not required. - ============= ====================================================== - - It is also possible to specify any arbitrary settings as a dictionary. - This may consist of {'functionName': (args...)} pairs where functionName must - be a callable attribute of OpenGL.GL, or {GL_STATE_VAR: bool} pairs - which will be interpreted as calls to glEnable or glDisable(GL_STATE_VAR). - - For example:: - - { - GL_ALPHA_TEST: True, - GL_CULL_FACE: False, - 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), - } - - - """ - if isinstance(opts, basestring): - opts = GLOptions[opts] - self.__glOpts = opts.copy() - self.update() - - def updateGLOptions(self, opts): - """ - Modify the OpenGL state options to use immediately before drawing this item. - *opts* must be a dictionary as specified by setGLOptions. - Values may also be None, in which case the key will be ignored. - """ - self.__glOpts.update(opts) - - - def parentItem(self): - """Return a this item's parent in the scenegraph hierarchy.""" - return self.__parent - - def childItems(self): - """Return a list of this item's children in the scenegraph hierarchy.""" - return list(self.__children) - - def _setView(self, v): - self.__view = v - - def view(self): - return self.__view - - def setDepthValue(self, value): - """ - Sets the depth value of this item. Default is 0. - This controls the order in which items are drawn--those with a greater depth value will be drawn later. - Items with negative depth values are drawn before their parent. - (This is analogous to QGraphicsItem.zValue) - The depthValue does NOT affect the position of the item or the values it imparts to the GL depth buffer. - """ - self.__depthValue = value - - def depthValue(self): - """Return the depth value of this item. See setDepthValue for more information.""" - return self.__depthValue - - def setTransform(self, tr): - """Set the local transform for this object. - Must be a :class:`Transform3D ` instance. This transform - determines how the local coordinate system of the item is mapped to the coordinate - system of its parent.""" - self.__transform = Transform3D(tr) - self.update() - - def resetTransform(self): - """Reset this item's transform to an identity transformation.""" - self.__transform.setToIdentity() - self.update() - - def applyTransform(self, tr, local): - """ - Multiply this object's transform by *tr*. - If local is True, then *tr* is multiplied on the right of the current transform:: - - newTransform = transform * tr - - If local is False, then *tr* is instead multiplied on the left:: - - newTransform = tr * transform - """ - if local: - self.setTransform(self.transform() * tr) - else: - self.setTransform(tr * self.transform()) - - def transform(self): - """Return this item's transform object.""" - return self.__transform - - def viewTransform(self): - """Return the transform mapping this item's local coordinate system to the - view coordinate system.""" - tr = self.__transform - p = self - while True: - p = p.parentItem() - if p is None: - break - tr = p.transform() * tr - return Transform3D(tr) - - def translate(self, dx, dy, dz, local=False): - """ - Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system. - If *local* is True, then translation takes place in local coordinates. - """ - tr = Transform3D() - tr.translate(dx, dy, dz) - self.applyTransform(tr, local=local) - - def rotate(self, angle, x, y, z, local=False): - """ - Rotate the object around the axis specified by (x,y,z). - *angle* is in degrees. - - """ - tr = Transform3D() - tr.rotate(angle, x, y, z) - self.applyTransform(tr, local=local) - - def scale(self, x, y, z, local=True): - """ - Scale the object by (*dx*, *dy*, *dz*) in its local coordinate system. - If *local* is False, then scale takes place in the parent's coordinates. - """ - tr = Transform3D() - tr.scale(x, y, z) - self.applyTransform(tr, local=local) - - - def hide(self): - """Hide this item. - This is equivalent to setVisible(False).""" - self.setVisible(False) - - def show(self): - """Make this item visible if it was previously hidden. - This is equivalent to setVisible(True).""" - self.setVisible(True) - - def setVisible(self, vis): - """Set the visibility of this item.""" - self.__visible = vis - self.update() - - def visible(self): - """Return True if the item is currently set to be visible. - Note that this does not guarantee that the item actually appears in the - view, as it may be obscured or outside of the current view area.""" - return self.__visible - - - def initializeGL(self): - """ - Called after an item is added to a GLViewWidget. - The widget's GL context is made current before this method is called. - (So this would be an appropriate time to generate lists, upload textures, etc.) - """ - pass - - def setupGLState(self): - """ - This method is responsible for preparing the GL state options needed to render - this item (blending, depth testing, etc). The method is called immediately before painting the item. - """ - for k,v in self.__glOpts.items(): - if v is None: - continue - if isinstance(k, basestring): - func = getattr(GL, k) - func(*v) - else: - if v is True: - glEnable(k) - else: - glDisable(k) - - def paint(self): - """ - Called by the GLViewWidget to draw this item. - It is the responsibility of the item to set up its own modelview matrix, - but the caller will take care of pushing/popping. - """ - self.setupGLState() - - def update(self): - """ - Indicates that this item needs to be redrawn, and schedules an update - with the view it is displayed in. - """ - v = self.view() - if v is None: - return - v.update() - - def mapToParent(self, point): - tr = self.transform() - if tr is None: - return point - return tr.map(point) - - def mapFromParent(self, point): - tr = self.transform() - if tr is None: - return point - return tr.inverted()[0].map(point) - - def mapToView(self, point): - tr = self.viewTransform() - if tr is None: - return point - return tr.map(point) - - def mapFromView(self, point): - tr = self.viewTransform() - if tr is None: - return point - return tr.inverted()[0].map(point) - - - \ No newline at end of file diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py deleted file mode 100644 index 89fef92e..00000000 --- a/pyqtgraph/opengl/GLViewWidget.py +++ /dev/null @@ -1,436 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL -from OpenGL.GL import * -import OpenGL.GL.framebufferobjects as glfbo -import numpy as np -from pyqtgraph import Vector -import pyqtgraph.functions as fn - -##Vector = QtGui.QVector3D - -class GLViewWidget(QtOpenGL.QGLWidget): - """ - Basic widget for displaying 3D data - - Rotation/scale controls - - Axis/grid display - - Export options - - """ - - ShareWidget = None - - def __init__(self, parent=None): - if GLViewWidget.ShareWidget is None: - ## create a dummy widget to allow sharing objects (textures, shaders, etc) between views - GLViewWidget.ShareWidget = QtOpenGL.QGLWidget() - - QtOpenGL.QGLWidget.__init__(self, parent, GLViewWidget.ShareWidget) - - self.setFocusPolicy(QtCore.Qt.ClickFocus) - - self.opts = { - 'center': Vector(0,0,0), ## will always appear at the center of the widget - 'distance': 10.0, ## distance of camera from center - 'fov': 60, ## horizontal field of view in degrees - 'elevation': 30, ## camera's angle of elevation in degrees - 'azimuth': 45, ## camera's azimuthal angle in degrees - ## (rotation around z-axis 0 points along x-axis) - 'viewport': None, ## glViewport params; None == whole widget - } - self.items = [] - self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown] - self.keysPressed = {} - self.keyTimer = QtCore.QTimer() - self.keyTimer.timeout.connect(self.evalKeyState) - - self.makeCurrent() - - def addItem(self, item): - self.items.append(item) - if hasattr(item, 'initializeGL'): - self.makeCurrent() - try: - item.initializeGL() - except: - self.checkOpenGLVersion('Error while adding item %s to GLViewWidget.' % str(item)) - - item._setView(self) - #print "set view", item, self, item.view() - self.update() - - def removeItem(self, item): - self.items.remove(item) - item._setView(None) - self.update() - - - def initializeGL(self): - glClearColor(0.0, 0.0, 0.0, 0.0) - self.resizeGL(self.width(), self.height()) - - def getViewport(self): - vp = self.opts['viewport'] - if vp is None: - return (0, 0, self.width(), self.height()) - else: - return vp - - def resizeGL(self, w, h): - pass - #glViewport(*self.getViewport()) - #self.update() - - def setProjection(self, region=None): - m = self.projectionMatrix(region) - glMatrixMode(GL_PROJECTION) - glLoadIdentity() - a = np.array(m.copyDataTo()).reshape((4,4)) - glMultMatrixf(a.transpose()) - - def projectionMatrix(self, region=None): - # Xw = (Xnd + 1) * width/2 + X - if region is None: - region = (0, 0, self.width(), self.height()) - - x0, y0, w, h = self.getViewport() - dist = self.opts['distance'] - fov = self.opts['fov'] - nearClip = dist * 0.001 - farClip = dist * 1000. - - r = nearClip * np.tan(fov * 0.5 * np.pi / 180.) - t = r * h / w - - # convert screen coordinates (region) to normalized device coordinates - # Xnd = (Xw - X0) * 2/width - 1 - ## Note that X0 and width in these equations must be the values used in viewport - left = r * ((region[0]-x0) * (2.0/w) - 1) - right = r * ((region[0]+region[2]-x0) * (2.0/w) - 1) - bottom = t * ((region[1]-y0) * (2.0/h) - 1) - top = t * ((region[1]+region[3]-y0) * (2.0/h) - 1) - - tr = QtGui.QMatrix4x4() - tr.frustum(left, right, bottom, top, nearClip, farClip) - return tr - - def setModelview(self): - glMatrixMode(GL_MODELVIEW) - glLoadIdentity() - m = self.viewMatrix() - a = np.array(m.copyDataTo()).reshape((4,4)) - glMultMatrixf(a.transpose()) - - def viewMatrix(self): - tr = QtGui.QMatrix4x4() - tr.translate( 0.0, 0.0, -self.opts['distance']) - tr.rotate(self.opts['elevation']-90, 1, 0, 0) - tr.rotate(self.opts['azimuth']+90, 0, 0, -1) - center = self.opts['center'] - tr.translate(-center.x(), -center.y(), -center.z()) - return tr - - def itemsAt(self, region=None): - #buf = np.zeros(100000, dtype=np.uint) - buf = glSelectBuffer(100000) - try: - glRenderMode(GL_SELECT) - glInitNames() - glPushName(0) - self._itemNames = {} - self.paintGL(region=region, useItemNames=True) - - finally: - hits = glRenderMode(GL_RENDER) - - items = [(h.near, h.names[0]) for h in hits] - items.sort(key=lambda i: i[0]) - - return [self._itemNames[i[1]] for i in items] - - def paintGL(self, region=None, viewport=None, useItemNames=False): - """ - viewport specifies the arguments to glViewport. If None, then we use self.opts['viewport'] - region specifies the sub-region of self.opts['viewport'] that should be rendered. - Note that we may use viewport != self.opts['viewport'] when exporting. - """ - if viewport is None: - glViewport(*self.getViewport()) - else: - glViewport(*viewport) - self.setProjection(region=region) - self.setModelview() - glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT ) - self.drawItemTree(useItemNames=useItemNames) - - def drawItemTree(self, item=None, useItemNames=False): - if item is None: - items = [x for x in self.items if x.parentItem() is None] - else: - items = item.childItems() - items.append(item) - items.sort(key=lambda a: a.depthValue()) - for i in items: - if not i.visible(): - continue - if i is item: - try: - glPushAttrib(GL_ALL_ATTRIB_BITS) - if useItemNames: - glLoadName(id(i)) - self._itemNames[id(i)] = i - i.paint() - except: - import pyqtgraph.debug - pyqtgraph.debug.printExc() - msg = "Error while drawing item %s." % str(item) - ver = glGetString(GL_VERSION) - if ver is not None: - ver = ver.split()[0] - if int(ver.split(b'.')[0]) < 2: - print(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver) - else: - print(msg) - - finally: - glPopAttrib() - else: - glMatrixMode(GL_MODELVIEW) - glPushMatrix() - try: - tr = i.transform() - a = np.array(tr.copyDataTo()).reshape((4,4)) - glMultMatrixf(a.transpose()) - self.drawItemTree(i, useItemNames=useItemNames) - finally: - glMatrixMode(GL_MODELVIEW) - glPopMatrix() - - def setCameraPosition(self, pos=None, distance=None, elevation=None, azimuth=None): - if distance is not None: - self.opts['distance'] = distance - if elevation is not None: - self.opts['elevation'] = elevation - if azimuth is not None: - self.opts['azimuth'] = azimuth - self.update() - - - - def cameraPosition(self): - """Return current position of camera based on center, dist, elevation, and azimuth""" - center = self.opts['center'] - dist = self.opts['distance'] - elev = self.opts['elevation'] * np.pi/180. - azim = self.opts['azimuth'] * np.pi/180. - - pos = Vector( - center.x() + dist * np.cos(elev) * np.cos(azim), - center.y() + dist * np.cos(elev) * np.sin(azim), - center.z() + dist * np.sin(elev) - ) - - return pos - - def orbit(self, azim, elev): - """Orbits the camera around the center position. *azim* and *elev* are given in degrees.""" - self.opts['azimuth'] += azim - #self.opts['elevation'] += elev - self.opts['elevation'] = np.clip(self.opts['elevation'] + elev, -90, 90) - self.update() - - def pan(self, dx, dy, dz, relative=False): - """ - Moves the center (look-at) position while holding the camera in place. - - If relative=True, then the coordinates are interpreted such that x - if in the global xy plane and points to the right side of the view, y is - in the global xy plane and orthogonal to x, and z points in the global z - direction. Distances are scaled roughly such that a value of 1.0 moves - by one pixel on screen. - - """ - if not relative: - self.opts['center'] += QtGui.QVector3D(dx, dy, dz) - else: - cPos = self.cameraPosition() - cVec = self.opts['center'] - cPos - dist = cVec.length() ## distance from camera to center - xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) ## approx. width of view at distance of center point - xScale = xDist / self.width() - zVec = QtGui.QVector3D(0,0,1) - xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized() - yVec = QtGui.QVector3D.crossProduct(xVec, zVec).normalized() - self.opts['center'] = self.opts['center'] + xVec * xScale * dx + yVec * xScale * dy + zVec * xScale * dz - self.update() - - def pixelSize(self, pos): - """ - Return the approximate size of a screen pixel at the location pos - Pos may be a Vector or an (N,3) array of locations - """ - cam = self.cameraPosition() - if isinstance(pos, np.ndarray): - cam = np.array(cam).reshape((1,)*(pos.ndim-1)+(3,)) - dist = ((pos-cam)**2).sum(axis=-1)**0.5 - else: - dist = (pos-cam).length() - xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) - return xDist / self.width() - - def mousePressEvent(self, ev): - self.mousePos = ev.pos() - - def mouseMoveEvent(self, ev): - diff = ev.pos() - self.mousePos - self.mousePos = ev.pos() - - if ev.buttons() == QtCore.Qt.LeftButton: - self.orbit(-diff.x(), diff.y()) - #print self.opts['azimuth'], self.opts['elevation'] - elif ev.buttons() == QtCore.Qt.MidButton: - if (ev.modifiers() & QtCore.Qt.ControlModifier): - self.pan(diff.x(), 0, diff.y(), relative=True) - else: - self.pan(diff.x(), diff.y(), 0, relative=True) - - def mouseReleaseEvent(self, ev): - pass - - def wheelEvent(self, ev): - if (ev.modifiers() & QtCore.Qt.ControlModifier): - self.opts['fov'] *= 0.999**ev.delta() - else: - self.opts['distance'] *= 0.999**ev.delta() - self.update() - - def keyPressEvent(self, ev): - if ev.key() in self.noRepeatKeys: - ev.accept() - if ev.isAutoRepeat(): - return - self.keysPressed[ev.key()] = 1 - self.evalKeyState() - - def keyReleaseEvent(self, ev): - if ev.key() in self.noRepeatKeys: - ev.accept() - if ev.isAutoRepeat(): - return - try: - del self.keysPressed[ev.key()] - except: - self.keysPressed = {} - self.evalKeyState() - - def evalKeyState(self): - speed = 2.0 - if len(self.keysPressed) > 0: - for key in self.keysPressed: - if key == QtCore.Qt.Key_Right: - self.orbit(azim=-speed, elev=0) - elif key == QtCore.Qt.Key_Left: - self.orbit(azim=speed, elev=0) - elif key == QtCore.Qt.Key_Up: - self.orbit(azim=0, elev=-speed) - elif key == QtCore.Qt.Key_Down: - self.orbit(azim=0, elev=speed) - elif key == QtCore.Qt.Key_PageUp: - pass - elif key == QtCore.Qt.Key_PageDown: - pass - self.keyTimer.start(16) - else: - self.keyTimer.stop() - - def checkOpenGLVersion(self, msg): - ## Only to be called from within exception handler. - ver = glGetString(GL_VERSION).split()[0] - if int(ver.split('.')[0]) < 2: - import pyqtgraph.debug - pyqtgraph.debug.printExc() - raise Exception(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver) - else: - raise - - - - def readQImage(self): - """ - Read the current buffer pixels out as a QImage. - """ - w = self.width() - h = self.height() - self.repaint() - pixels = np.empty((h, w, 4), dtype=np.ubyte) - pixels[:] = 128 - pixels[...,0] = 50 - pixels[...,3] = 255 - - glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels) - - # swap B,R channels for Qt - tmp = pixels[...,0].copy() - pixels[...,0] = pixels[...,2] - pixels[...,2] = tmp - pixels = pixels[::-1] # flip vertical - - img = fn.makeQImage(pixels, transpose=False) - return img - - - def renderToArray(self, size, format=GL_BGRA, type=GL_UNSIGNED_BYTE, textureSize=1024, padding=256): - w,h = map(int, size) - - self.makeCurrent() - tex = None - fb = None - try: - output = np.empty((w, h, 4), dtype=np.ubyte) - fb = glfbo.glGenFramebuffers(1) - glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, fb ) - - glEnable(GL_TEXTURE_2D) - tex = glGenTextures(1) - glBindTexture(GL_TEXTURE_2D, tex) - texwidth = textureSize - data = np.zeros((texwidth,texwidth,4), dtype=np.ubyte) - - ## Test texture dimensions first - glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, None) - if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: - raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) - ## create teture - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.transpose((1,0,2))) - - self.opts['viewport'] = (0, 0, w, h) # viewport is the complete image; this ensures that paintGL(region=...) - # is interpreted correctly. - p2 = 2 * padding - for x in range(-padding, w-padding, texwidth-p2): - for y in range(-padding, h-padding, texwidth-p2): - x2 = min(x+texwidth, w+padding) - y2 = min(y+texwidth, h+padding) - w2 = x2-x - h2 = y2-y - - ## render to texture - glfbo.glFramebufferTexture2D(glfbo.GL_FRAMEBUFFER, glfbo.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0) - - self.paintGL(region=(x, h-y-h2, w2, h2), viewport=(0, 0, w2, h2)) # only render sub-region - - ## read texture back to array - data = glGetTexImage(GL_TEXTURE_2D, 0, format, type) - data = np.fromstring(data, dtype=np.ubyte).reshape(texwidth,texwidth,4).transpose(1,0,2)[:, ::-1] - output[x+padding:x2-padding, y+padding:y2-padding] = data[padding:w2-padding, -(h2-padding):-padding] - - finally: - self.opts['viewport'] = None - glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, 0) - glBindTexture(GL_TEXTURE_2D, 0) - if tex is not None: - glDeleteTextures([tex]) - if fb is not None: - glfbo.glDeleteFramebuffers([fb]) - - return output - - - diff --git a/pyqtgraph/opengl/MeshData.py b/pyqtgraph/opengl/MeshData.py deleted file mode 100644 index 71e566c9..00000000 --- a/pyqtgraph/opengl/MeshData.py +++ /dev/null @@ -1,519 +0,0 @@ -from pyqtgraph.Qt import QtGui -import pyqtgraph.functions as fn -import numpy as np - -class MeshData(object): - """ - Class for storing and operating on 3D mesh data. May contain: - - - list of vertex locations - - list of edges - - list of triangles - - colors per vertex, edge, or tri - - normals per vertex or tri - - This class handles conversion between the standard [list of vertexes, list of faces] - format (suitable for use with glDrawElements) and 'indexed' [list of vertexes] format - (suitable for use with glDrawArrays). It will automatically compute face normal - vectors as well as averaged vertex normal vectors. - - The class attempts to be as efficient as possible in caching conversion results and - avoiding unnecessary conversions. - """ - - def __init__(self, vertexes=None, faces=None, edges=None, vertexColors=None, faceColors=None): - """ - ============= ===================================================== - Arguments - vertexes (Nv, 3) array of vertex coordinates. - If faces is not specified, then this will instead be - interpreted as (Nf, 3, 3) array of coordinates. - faces (Nf, 3) array of indexes into the vertex array. - edges [not available yet] - vertexColors (Nv, 4) array of vertex colors. - If faces is not specified, then this will instead be - interpreted as (Nf, 3, 4) array of colors. - faceColors (Nf, 4) array of face colors. - ============= ===================================================== - - All arguments are optional. - """ - self._vertexes = None # (Nv,3) array of vertex coordinates - self._vertexesIndexedByFaces = None # (Nf, 3, 3) array of vertex coordinates - self._vertexesIndexedByEdges = None # (Ne, 2, 3) array of vertex coordinates - - ## mappings between vertexes, faces, and edges - self._faces = None # Nx3 array of indexes into self._vertexes specifying three vertexes for each face - self._edges = None # Nx2 array of indexes into self._vertexes specifying two vertexes per edge - self._vertexFaces = None ## maps vertex ID to a list of face IDs (inverse mapping of _faces) - self._vertexEdges = None ## maps vertex ID to a list of edge IDs (inverse mapping of _edges) - - ## Per-vertex data - self._vertexNormals = None # (Nv, 3) array of normals, one per vertex - self._vertexNormalsIndexedByFaces = None # (Nf, 3, 3) array of normals - self._vertexColors = None # (Nv, 3) array of colors - self._vertexColorsIndexedByFaces = None # (Nf, 3, 4) array of colors - self._vertexColorsIndexedByEdges = None # (Nf, 2, 4) array of colors - - ## Per-face data - self._faceNormals = None # (Nf, 3) array of face normals - self._faceNormalsIndexedByFaces = None # (Nf, 3, 3) array of face normals - self._faceColors = None # (Nf, 4) array of face colors - self._faceColorsIndexedByFaces = None # (Nf, 3, 4) array of face colors - self._faceColorsIndexedByEdges = None # (Ne, 2, 4) array of face colors - - ## Per-edge data - self._edgeColors = None # (Ne, 4) array of edge colors - self._edgeColorsIndexedByEdges = None # (Ne, 2, 4) array of edge colors - #self._meshColor = (1, 1, 1, 0.1) # default color to use if no face/edge/vertex colors are given - - - - if vertexes is not None: - if faces is None: - self.setVertexes(vertexes, indexed='faces') - if vertexColors is not None: - self.setVertexColors(vertexColors, indexed='faces') - if faceColors is not None: - self.setFaceColors(faceColors, indexed='faces') - else: - self.setVertexes(vertexes) - self.setFaces(faces) - if vertexColors is not None: - self.setVertexColors(vertexColors) - if faceColors is not None: - self.setFaceColors(faceColors) - - #self.setFaces(vertexes=vertexes, faces=faces, vertexColors=vertexColors, faceColors=faceColors) - - - #def setFaces(self, vertexes=None, faces=None, vertexColors=None, faceColors=None): - #""" - #Set the faces in this data set. - #Data may be provided either as an Nx3x3 array of floats (9 float coordinate values per face):: - - #faces = [ [(x, y, z), (x, y, z), (x, y, z)], ... ] - - #or as an Nx3 array of ints (vertex integers) AND an Mx3 array of floats (3 float coordinate values per vertex):: - - #faces = [ (p1, p2, p3), ... ] - #vertexes = [ (x, y, z), ... ] - - #""" - #if not isinstance(vertexes, np.ndarray): - #vertexes = np.array(vertexes) - #if vertexes.dtype != np.float: - #vertexes = vertexes.astype(float) - #if faces is None: - #self._setIndexedFaces(vertexes, vertexColors, faceColors) - #else: - #self._setUnindexedFaces(faces, vertexes, vertexColors, faceColors) - ##print self.vertexes().shape - ##print self.faces().shape - - - #def setMeshColor(self, color): - #"""Set the color of the entire mesh. This removes any per-face or per-vertex colors.""" - #color = fn.Color(color) - #self._meshColor = color.glColor() - #self._vertexColors = None - #self._faceColors = None - - - #def __iter__(self): - #"""Iterate over all faces, yielding a list of three tuples [(position, normal, color), ...] for each face.""" - #vnorms = self.vertexNormals() - #vcolors = self.vertexColors() - #for i in range(self._faces.shape[0]): - #face = [] - #for j in [0,1,2]: - #vind = self._faces[i,j] - #pos = self._vertexes[vind] - #norm = vnorms[vind] - #if vcolors is None: - #color = self._meshColor - #else: - #color = vcolors[vind] - #face.append((pos, norm, color)) - #yield face - - #def __len__(self): - #return len(self._faces) - - def faces(self): - """Return an array (Nf, 3) of vertex indexes, three per triangular face in the mesh.""" - return self._faces - - def edges(self): - """Return an array (Nf, 3) of vertex indexes, two per edge in the mesh.""" - if self._edges is None: - self._computeEdges() - return self._edges - - def setFaces(self, faces): - """Set the (Nf, 3) array of faces. Each rown in the array contains - three indexes into the vertex array, specifying the three corners - of a triangular face.""" - self._faces = faces - self._edges = None - self._vertexFaces = None - self._vertexesIndexedByFaces = None - self.resetNormals() - self._vertexColorsIndexedByFaces = None - self._faceColorsIndexedByFaces = None - - - - def vertexes(self, indexed=None): - """Return an array (N,3) of the positions of vertexes in the mesh. - By default, each unique vertex appears only once in the array. - If indexed is 'faces', then the array will instead contain three vertexes - per face in the mesh (and a single vertex may appear more than once in the array).""" - if indexed is None: - if self._vertexes is None and self._vertexesIndexedByFaces is not None: - self._computeUnindexedVertexes() - return self._vertexes - elif indexed == 'faces': - if self._vertexesIndexedByFaces is None and self._vertexes is not None: - self._vertexesIndexedByFaces = self._vertexes[self.faces()] - return self._vertexesIndexedByFaces - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def setVertexes(self, verts=None, indexed=None, resetNormals=True): - """ - Set the array (Nv, 3) of vertex coordinates. - If indexed=='faces', then the data must have shape (Nf, 3, 3) and is - assumed to be already indexed as a list of faces. - This will cause any pre-existing normal vectors to be cleared - unless resetNormals=False. - """ - if indexed is None: - if verts is not None: - self._vertexes = verts - self._vertexesIndexedByFaces = None - elif indexed=='faces': - self._vertexes = None - if verts is not None: - self._vertexesIndexedByFaces = verts - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - if resetNormals: - self.resetNormals() - - def resetNormals(self): - self._vertexNormals = None - self._vertexNormalsIndexedByFaces = None - self._faceNormals = None - self._faceNormalsIndexedByFaces = None - - - def hasFaceIndexedData(self): - """Return True if this object already has vertex positions indexed by face""" - return self._vertexesIndexedByFaces is not None - - def hasEdgeIndexedData(self): - return self._vertexesIndexedByEdges is not None - - def hasVertexColor(self): - """Return True if this data set has vertex color information""" - for v in (self._vertexColors, self._vertexColorsIndexedByFaces, self._vertexColorsIndexedByEdges): - if v is not None: - return True - return False - - def hasFaceColor(self): - """Return True if this data set has face color information""" - for v in (self._faceColors, self._faceColorsIndexedByFaces, self._faceColorsIndexedByEdges): - if v is not None: - return True - return False - - - def faceNormals(self, indexed=None): - """ - Return an array (Nf, 3) of normal vectors for each face. - If indexed='faces', then instead return an indexed array - (Nf, 3, 3) (this is just the same array with each vector - copied three times). - """ - if self._faceNormals is None: - v = self.vertexes(indexed='faces') - self._faceNormals = np.cross(v[:,1]-v[:,0], v[:,2]-v[:,0]) - - - if indexed is None: - return self._faceNormals - elif indexed == 'faces': - if self._faceNormalsIndexedByFaces is None: - norms = np.empty((self._faceNormals.shape[0], 3, 3)) - norms[:] = self._faceNormals[:,np.newaxis,:] - self._faceNormalsIndexedByFaces = norms - return self._faceNormalsIndexedByFaces - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def vertexNormals(self, indexed=None): - """ - Return an array of normal vectors. - By default, the array will be (N, 3) with one entry per unique vertex in the mesh. - If indexed is 'faces', then the array will contain three normal vectors per face - (and some vertexes may be repeated). - """ - if self._vertexNormals is None: - faceNorms = self.faceNormals() - vertFaces = self.vertexFaces() - self._vertexNormals = np.empty(self._vertexes.shape, dtype=float) - for vindex in xrange(self._vertexes.shape[0]): - norms = faceNorms[vertFaces[vindex]] ## get all face normals - norm = norms.sum(axis=0) ## sum normals - norm /= (norm**2).sum()**0.5 ## and re-normalize - self._vertexNormals[vindex] = norm - - if indexed is None: - return self._vertexNormals - elif indexed == 'faces': - return self._vertexNormals[self.faces()] - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def vertexColors(self, indexed=None): - """ - Return an array (Nv, 4) of vertex colors. - If indexed=='faces', then instead return an indexed array - (Nf, 3, 4). - """ - if indexed is None: - return self._vertexColors - elif indexed == 'faces': - if self._vertexColorsIndexedByFaces is None: - self._vertexColorsIndexedByFaces = self._vertexColors[self.faces()] - return self._vertexColorsIndexedByFaces - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def setVertexColors(self, colors, indexed=None): - """ - Set the vertex color array (Nv, 4). - If indexed=='faces', then the array will be interpreted - as indexed and should have shape (Nf, 3, 4) - """ - if indexed is None: - self._vertexColors = colors - self._vertexColorsIndexedByFaces = None - elif indexed == 'faces': - self._vertexColors = None - self._vertexColorsIndexedByFaces = colors - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def faceColors(self, indexed=None): - """ - Return an array (Nf, 4) of face colors. - If indexed=='faces', then instead return an indexed array - (Nf, 3, 4) (note this is just the same array with each color - repeated three times). - """ - if indexed is None: - return self._faceColors - elif indexed == 'faces': - if self._faceColorsIndexedByFaces is None and self._faceColors is not None: - Nf = self._faceColors.shape[0] - self._faceColorsIndexedByFaces = np.empty((Nf, 3, 4), dtype=self._faceColors.dtype) - self._faceColorsIndexedByFaces[:] = self._faceColors.reshape(Nf, 1, 4) - return self._faceColorsIndexedByFaces - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def setFaceColors(self, colors, indexed=None): - """ - Set the face color array (Nf, 4). - If indexed=='faces', then the array will be interpreted - as indexed and should have shape (Nf, 3, 4) - """ - if indexed is None: - self._faceColors = colors - self._faceColorsIndexedByFaces = None - elif indexed == 'faces': - self._faceColors = None - self._faceColorsIndexedByFaces = colors - else: - raise Exception("Invalid indexing mode. Accepts: None, 'faces'") - - def faceCount(self): - """ - Return the number of faces in the mesh. - """ - if self._faces is not None: - return self._faces.shape[0] - elif self._vertexesIndexedByFaces is not None: - return self._vertexesIndexedByFaces.shape[0] - - def edgeColors(self): - return self._edgeColors - - #def _setIndexedFaces(self, faces, vertexColors=None, faceColors=None): - #self._vertexesIndexedByFaces = faces - #self._vertexColorsIndexedByFaces = vertexColors - #self._faceColorsIndexedByFaces = faceColors - - def _computeUnindexedVertexes(self): - ## Given (Nv, 3, 3) array of vertexes-indexed-by-face, convert backward to unindexed vertexes - ## This is done by collapsing into a list of 'unique' vertexes (difference < 1e-14) - - ## I think generally this should be discouraged.. - - faces = self._vertexesIndexedByFaces - verts = {} ## used to remember the index of each vertex position - self._faces = np.empty(faces.shape[:2], dtype=np.uint) - self._vertexes = [] - self._vertexFaces = [] - self._faceNormals = None - self._vertexNormals = None - for i in xrange(faces.shape[0]): - face = faces[i] - inds = [] - for j in range(face.shape[0]): - pt = face[j] - pt2 = tuple([round(x*1e14) for x in pt]) ## quantize to be sure that nearly-identical points will be merged - index = verts.get(pt2, None) - if index is None: - #self._vertexes.append(QtGui.QVector3D(*pt)) - self._vertexes.append(pt) - self._vertexFaces.append([]) - index = len(self._vertexes)-1 - verts[pt2] = index - self._vertexFaces[index].append(i) # keep track of which vertexes belong to which faces - self._faces[i,j] = index - self._vertexes = np.array(self._vertexes, dtype=float) - - #def _setUnindexedFaces(self, faces, vertexes, vertexColors=None, faceColors=None): - #self._vertexes = vertexes #[QtGui.QVector3D(*v) for v in vertexes] - #self._faces = faces.astype(np.uint) - #self._edges = None - #self._vertexFaces = None - #self._faceNormals = None - #self._vertexNormals = None - #self._vertexColors = vertexColors - #self._faceColors = faceColors - - def vertexFaces(self): - """ - Return list mapping each vertex index to a list of face indexes that use the vertex. - """ - if self._vertexFaces is None: - self._vertexFaces = [None] * len(self.vertexes()) - for i in xrange(self._faces.shape[0]): - face = self._faces[i] - for ind in face: - if self._vertexFaces[ind] is None: - self._vertexFaces[ind] = [] ## need a unique/empty list to fill - self._vertexFaces[ind].append(i) - return self._vertexFaces - - #def reverseNormals(self): - #""" - #Reverses the direction of all normal vectors. - #""" - #pass - - #def generateEdgesFromFaces(self): - #""" - #Generate a set of edges by listing all the edges of faces and removing any duplicates. - #Useful for displaying wireframe meshes. - #""" - #pass - - def _computeEdges(self): - ## generate self._edges from self._faces - #print self._faces - nf = len(self._faces) - edges = np.empty(nf*3, dtype=[('i', np.uint, 2)]) - edges['i'][0:nf] = self._faces[:,:2] - edges['i'][nf:2*nf] = self._faces[:,1:3] - edges['i'][-nf:,0] = self._faces[:,2] - edges['i'][-nf:,1] = self._faces[:,0] - - # sort per-edge - mask = edges['i'][:,0] > edges['i'][:,1] - edges['i'][mask] = edges['i'][mask][:,::-1] - - # remove duplicate entries - self._edges = np.unique(edges)['i'] - #print self._edges - - - def save(self): - """Serialize this mesh to a string appropriate for disk storage""" - import pickle - if self._faces is not None: - names = ['_vertexes', '_faces'] - else: - names = ['_vertexesIndexedByFaces'] - - if self._vertexColors is not None: - names.append('_vertexColors') - elif self._vertexColorsIndexedByFaces is not None: - names.append('_vertexColorsIndexedByFaces') - - if self._faceColors is not None: - names.append('_faceColors') - elif self._faceColorsIndexedByFaces is not None: - names.append('_faceColorsIndexedByFaces') - - state = dict([(n,getattr(self, n)) for n in names]) - return pickle.dumps(state) - - def restore(self, state): - """Restore the state of a mesh previously saved using save()""" - import pickle - state = pickle.loads(state) - for k in state: - if isinstance(state[k], list): - if isinstance(state[k][0], QtGui.QVector3D): - state[k] = [[v.x(), v.y(), v.z()] for v in state[k]] - state[k] = np.array(state[k]) - setattr(self, k, state[k]) - - - - @staticmethod - def sphere(rows, cols, radius=1.0, offset=True): - """ - Return a MeshData instance with vertexes and faces computed - for a spherical surface. - """ - verts = np.empty((rows+1, cols, 3), dtype=float) - - ## compute vertexes - phi = (np.arange(rows+1) * np.pi / rows).reshape(rows+1, 1) - s = radius * np.sin(phi) - verts[...,2] = radius * np.cos(phi) - th = ((np.arange(cols) * 2 * np.pi / cols).reshape(1, cols)) - if offset: - th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1,1)) ## rotate each row by 1/2 column - verts[...,0] = s * np.cos(th) - verts[...,1] = s * np.sin(th) - verts = verts.reshape((rows+1)*cols, 3)[cols-1:-(cols-1)] ## remove redundant vertexes from top and bottom - - ## compute faces - faces = np.empty((rows*cols*2, 3), dtype=np.uint) - rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]]) - rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]]) - for row in range(rows): - start = row * cols * 2 - faces[start:start+cols] = rowtemplate1 + row * cols - faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols - faces = faces[cols:-cols] ## cut off zero-area triangles at top and bottom - - ## adjust for redundant vertexes that were removed from top and bottom - vmin = cols-1 - faces[facesvmax] = vmax - - return MeshData(vertexes=verts, faces=faces) - - \ No newline at end of file diff --git a/pyqtgraph/opengl/__init__.py b/pyqtgraph/opengl/__init__.py deleted file mode 100644 index 5345e187..00000000 --- a/pyqtgraph/opengl/__init__.py +++ /dev/null @@ -1,30 +0,0 @@ -from .GLViewWidget import GLViewWidget - -from pyqtgraph import importAll -#import os -#def importAll(path): - #d = os.path.join(os.path.split(__file__)[0], path) - #files = [] - #for f in os.listdir(d): - #if os.path.isdir(os.path.join(d, f)) and f != '__pycache__': - #files.append(f) - #elif f[-3:] == '.py' and f != '__init__.py': - #files.append(f[:-3]) - - #for modName in files: - #mod = __import__(path+"."+modName, globals(), locals(), fromlist=['*']) - #if hasattr(mod, '__all__'): - #names = mod.__all__ - #else: - #names = [n for n in dir(mod) if n[0] != '_'] - #for k in names: - #if hasattr(mod, k): - #globals()[k] = getattr(mod, k) - -importAll('items', globals(), locals()) -\ -from .MeshData import MeshData -## for backward compatibility: -#MeshData.MeshData = MeshData ## breaks autodoc. - -from . import shaders diff --git a/pyqtgraph/opengl/glInfo.py b/pyqtgraph/opengl/glInfo.py deleted file mode 100644 index 28da1f69..00000000 --- a/pyqtgraph/opengl/glInfo.py +++ /dev/null @@ -1,16 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL -from OpenGL.GL import * -app = QtGui.QApplication([]) - -class GLTest(QtOpenGL.QGLWidget): - def __init__(self): - QtOpenGL.QGLWidget.__init__(self) - self.makeCurrent() - print("GL version:" + glGetString(GL_VERSION)) - print("MAX_TEXTURE_SIZE: %d" % glGetIntegerv(GL_MAX_TEXTURE_SIZE)) - print("MAX_3D_TEXTURE_SIZE: %d" % glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE)) - print("Extensions: " + glGetString(GL_EXTENSIONS)) - -GLTest() - - diff --git a/pyqtgraph/opengl/items/GLAxisItem.py b/pyqtgraph/opengl/items/GLAxisItem.py deleted file mode 100644 index 860ac497..00000000 --- a/pyqtgraph/opengl/items/GLAxisItem.py +++ /dev/null @@ -1,64 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from pyqtgraph import QtGui - -__all__ = ['GLAxisItem'] - -class GLAxisItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays three lines indicating origin and orientation of local coordinate system. - - """ - - def __init__(self, size=None, antialias=True, glOptions='translucent'): - GLGraphicsItem.__init__(self) - if size is None: - size = QtGui.QVector3D(1,1,1) - self.antialias = antialias - self.setSize(size=size) - self.setGLOptions(glOptions) - - def setSize(self, x=None, y=None, z=None, size=None): - """ - Set the size of the axes (in its local coordinate system; this does not affect the transform) - Arguments can be x,y,z or size=QVector3D(). - """ - if size is not None: - x = size.x() - y = size.y() - z = size.z() - self.__size = [x,y,z] - self.update() - - def size(self): - return self.__size[:] - - - def paint(self): - - #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - self.setupGLState() - - if self.antialias: - glEnable(GL_LINE_SMOOTH) - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - - glBegin( GL_LINES ) - - x,y,z = self.size() - glColor4f(0, 1, 0, .6) # z is green - glVertex3f(0, 0, 0) - glVertex3f(0, 0, z) - - glColor4f(1, 1, 0, .6) # y is yellow - glVertex3f(0, 0, 0) - glVertex3f(0, y, 0) - - glColor4f(0, 0, 1, .6) # x is blue - glVertex3f(0, 0, 0) - glVertex3f(x, 0, 0) - glEnd() diff --git a/pyqtgraph/opengl/items/GLBarGraphItem.py b/pyqtgraph/opengl/items/GLBarGraphItem.py deleted file mode 100644 index b3060dc9..00000000 --- a/pyqtgraph/opengl/items/GLBarGraphItem.py +++ /dev/null @@ -1,29 +0,0 @@ -from .GLMeshItem import GLMeshItem -from ..MeshData import MeshData -import numpy as np - -class GLBarGraphItem(GLMeshItem): - def __init__(self, pos, size): - """ - pos is (...,3) array of the bar positions (the corner of each bar) - size is (...,3) array of the sizes of each bar - """ - nCubes = reduce(lambda a,b: a*b, pos.shape[:-1]) - cubeVerts = np.mgrid[0:2,0:2,0:2].reshape(3,8).transpose().reshape(1,8,3) - cubeFaces = np.array([ - [0,1,2], [3,2,1], - [4,5,6], [7,6,5], - [0,1,4], [5,4,1], - [2,3,6], [7,6,3], - [0,2,4], [6,4,2], - [1,3,5], [7,5,3]]).reshape(1,12,3) - size = size.reshape((nCubes, 1, 3)) - pos = pos.reshape((nCubes, 1, 3)) - verts = cubeVerts * size + pos - faces = cubeFaces + (np.arange(nCubes) * 8).reshape(nCubes,1,1) - md = MeshData(verts.reshape(nCubes*8,3), faces.reshape(nCubes*12,3)) - - GLMeshItem.__init__(self, meshdata=md, shader='shaded', smooth=False) - - - \ No newline at end of file diff --git a/pyqtgraph/opengl/items/GLBoxItem.py b/pyqtgraph/opengl/items/GLBoxItem.py deleted file mode 100644 index bc25afd1..00000000 --- a/pyqtgraph/opengl/items/GLBoxItem.py +++ /dev/null @@ -1,88 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from pyqtgraph.Qt import QtGui -import pyqtgraph as pg - -__all__ = ['GLBoxItem'] - -class GLBoxItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays a wire-frame box. - """ - def __init__(self, size=None, color=None, glOptions='translucent'): - GLGraphicsItem.__init__(self) - if size is None: - size = QtGui.QVector3D(1,1,1) - self.setSize(size=size) - if color is None: - color = (255,255,255,80) - self.setColor(color) - self.setGLOptions(glOptions) - - def setSize(self, x=None, y=None, z=None, size=None): - """ - Set the size of the box (in its local coordinate system; this does not affect the transform) - Arguments can be x,y,z or size=QVector3D(). - """ - if size is not None: - x = size.x() - y = size.y() - z = size.z() - self.__size = [x,y,z] - self.update() - - def size(self): - return self.__size[:] - - def setColor(self, *args): - """Set the color of the box. Arguments are the same as those accepted by functions.mkColor()""" - self.__color = pg.Color(*args) - - def color(self): - return self.__color - - def paint(self): - #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - ##glAlphaFunc( GL_ALWAYS,0.5 ) - #glEnable( GL_POINT_SMOOTH ) - #glDisable( GL_DEPTH_TEST ) - self.setupGLState() - - glBegin( GL_LINES ) - - glColor4f(*self.color().glColor()) - x,y,z = self.size() - glVertex3f(0, 0, 0) - glVertex3f(0, 0, z) - glVertex3f(x, 0, 0) - glVertex3f(x, 0, z) - glVertex3f(0, y, 0) - glVertex3f(0, y, z) - glVertex3f(x, y, 0) - glVertex3f(x, y, z) - - glVertex3f(0, 0, 0) - glVertex3f(0, y, 0) - glVertex3f(x, 0, 0) - glVertex3f(x, y, 0) - glVertex3f(0, 0, z) - glVertex3f(0, y, z) - glVertex3f(x, 0, z) - glVertex3f(x, y, z) - - glVertex3f(0, 0, 0) - glVertex3f(x, 0, 0) - glVertex3f(0, y, 0) - glVertex3f(x, y, 0) - glVertex3f(0, 0, z) - glVertex3f(x, 0, z) - glVertex3f(0, y, z) - glVertex3f(x, y, z) - - glEnd() - - \ No newline at end of file diff --git a/pyqtgraph/opengl/items/GLGridItem.py b/pyqtgraph/opengl/items/GLGridItem.py deleted file mode 100644 index 01a2b178..00000000 --- a/pyqtgraph/opengl/items/GLGridItem.py +++ /dev/null @@ -1,58 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from pyqtgraph import QtGui - -__all__ = ['GLGridItem'] - -class GLGridItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays a wire-grame grid. - """ - - def __init__(self, size=None, color=None, antialias=True, glOptions='translucent'): - GLGraphicsItem.__init__(self) - self.setGLOptions(glOptions) - self.antialias = antialias - if size is None: - size = QtGui.QVector3D(1,1,1) - self.setSize(size=size) - - def setSize(self, x=None, y=None, z=None, size=None): - """ - Set the size of the axes (in its local coordinate system; this does not affect the transform) - Arguments can be x,y,z or size=QVector3D(). - """ - if size is not None: - x = size.x() - y = size.y() - z = size.z() - self.__size = [x,y,z] - self.update() - - def size(self): - return self.__size[:] - - - def paint(self): - self.setupGLState() - - if self.antialias: - glEnable(GL_LINE_SMOOTH) - glEnable(GL_BLEND) - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - - glBegin( GL_LINES ) - - x,y,z = self.size() - glColor4f(1, 1, 1, .3) - for x in range(-10, 11): - glVertex3f(x, -10, 0) - glVertex3f(x, 10, 0) - for y in range(-10, 11): - glVertex3f(-10, y, 0) - glVertex3f( 10, y, 0) - - glEnd() diff --git a/pyqtgraph/opengl/items/GLImageItem.py b/pyqtgraph/opengl/items/GLImageItem.py deleted file mode 100644 index aca68f3d..00000000 --- a/pyqtgraph/opengl/items/GLImageItem.py +++ /dev/null @@ -1,90 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from pyqtgraph.Qt import QtGui -import numpy as np - -__all__ = ['GLImageItem'] - -class GLImageItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays image data as a textured quad. - """ - - - def __init__(self, data, smooth=False, glOptions='translucent'): - """ - - ============== ======================================================================================= - **Arguments:** - data Volume data to be rendered. *Must* be 3D numpy array (x, y, RGBA) with dtype=ubyte. - (See functions.makeRGBA) - smooth (bool) If True, the volume slices are rendered with linear interpolation - ============== ======================================================================================= - """ - - self.smooth = smooth - self.data = data - GLGraphicsItem.__init__(self) - self.setGLOptions(glOptions) - - def initializeGL(self): - glEnable(GL_TEXTURE_2D) - self.texture = glGenTextures(1) - glBindTexture(GL_TEXTURE_2D, self.texture) - if self.smooth: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - else: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) - #glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) - shape = self.data.shape - - ## Test texture dimensions first - glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) - if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: - raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((1,0,2))) - glDisable(GL_TEXTURE_2D) - - #self.lists = {} - #for ax in [0,1,2]: - #for d in [-1, 1]: - #l = glGenLists(1) - #self.lists[(ax,d)] = l - #glNewList(l, GL_COMPILE) - #self.drawVolume(ax, d) - #glEndList() - - - def paint(self): - - glEnable(GL_TEXTURE_2D) - glBindTexture(GL_TEXTURE_2D, self.texture) - - self.setupGLState() - - #glEnable(GL_DEPTH_TEST) - ##glDisable(GL_CULL_FACE) - #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - glColor4f(1,1,1,1) - - glBegin(GL_QUADS) - glTexCoord2f(0,0) - glVertex3f(0,0,0) - glTexCoord2f(1,0) - glVertex3f(self.data.shape[0], 0, 0) - glTexCoord2f(1,1) - glVertex3f(self.data.shape[0], self.data.shape[1], 0) - glTexCoord2f(0,1) - glVertex3f(0, self.data.shape[1], 0) - glEnd() - glDisable(GL_TEXTURE_3D) - diff --git a/pyqtgraph/opengl/items/GLLinePlotItem.py b/pyqtgraph/opengl/items/GLLinePlotItem.py deleted file mode 100644 index 23d227c9..00000000 --- a/pyqtgraph/opengl/items/GLLinePlotItem.py +++ /dev/null @@ -1,101 +0,0 @@ -from OpenGL.GL import * -from OpenGL.arrays import vbo -from .. GLGraphicsItem import GLGraphicsItem -from .. import shaders -from pyqtgraph import QtGui -import numpy as np - -__all__ = ['GLLinePlotItem'] - -class GLLinePlotItem(GLGraphicsItem): - """Draws line plots in 3D.""" - - def __init__(self, **kwds): - """All keyword arguments are passed to setData()""" - GLGraphicsItem.__init__(self) - glopts = kwds.pop('glOptions', 'additive') - self.setGLOptions(glopts) - self.pos = None - self.width = 1. - self.color = (1.0,1.0,1.0,1.0) - self.setData(**kwds) - - def setData(self, **kwds): - """ - Update the data displayed by this item. All arguments are optional; - for example it is allowed to update vertex positions while leaving - colors unchanged, etc. - - ==================== ================================================== - Arguments: - ------------------------------------------------------------------------ - pos (N,3) array of floats specifying point locations. - color (N,4) array of floats (0.0-1.0) or - tuple of floats specifying - a single color for the entire item. - width float specifying line width - antialias enables smooth line drawing - ==================== ================================================== - """ - args = ['pos', 'color', 'width', 'connected', 'antialias'] - for k in kwds.keys(): - if k not in args: - raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) - self.antialias = False - for arg in args: - if arg in kwds: - setattr(self, arg, kwds[arg]) - #self.vbo.pop(arg, None) - self.update() - - def initializeGL(self): - pass - - #def setupGLState(self): - #"""Prepare OpenGL state for drawing. This function is called immediately before painting.""" - ##glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly. - #glBlendFunc(GL_SRC_ALPHA, GL_ONE) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - #glDisable( GL_DEPTH_TEST ) - - ##glEnable( GL_POINT_SMOOTH ) - - ##glHint(GL_POINT_SMOOTH_HINT, GL_NICEST) - ##glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3)) - ##glPointParameterfv(GL_POINT_SIZE_MAX, (65500,)) - ##glPointParameterfv(GL_POINT_SIZE_MIN, (0,)) - - def paint(self): - if self.pos is None: - return - self.setupGLState() - - glEnableClientState(GL_VERTEX_ARRAY) - - try: - glVertexPointerf(self.pos) - - if isinstance(self.color, np.ndarray): - glEnableClientState(GL_COLOR_ARRAY) - glColorPointerf(self.color) - else: - if isinstance(self.color, QtGui.QColor): - glColor4f(*fn.glColor(self.color)) - else: - glColor4f(*self.color) - glLineWidth(self.width) - #glPointSize(self.width) - - if self.antialias: - glEnable(GL_LINE_SMOOTH) - glEnable(GL_BLEND) - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); - - glDrawArrays(GL_LINE_STRIP, 0, int(self.pos.size / self.pos.shape[-1])) - finally: - glDisableClientState(GL_COLOR_ARRAY) - glDisableClientState(GL_VERTEX_ARRAY) - - diff --git a/pyqtgraph/opengl/items/GLMeshItem.py b/pyqtgraph/opengl/items/GLMeshItem.py deleted file mode 100644 index 5b245e64..00000000 --- a/pyqtgraph/opengl/items/GLMeshItem.py +++ /dev/null @@ -1,223 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from .. MeshData import MeshData -from pyqtgraph.Qt import QtGui -import pyqtgraph as pg -from .. import shaders -import numpy as np - - - -__all__ = ['GLMeshItem'] - -class GLMeshItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays a 3D triangle mesh. - """ - def __init__(self, **kwds): - """ - ============== ===================================================== - Arguments - meshdata MeshData object from which to determine geometry for - this item. - color Default face color used if no vertex or face colors - are specified. - edgeColor Default edge color to use if no edge colors are - specified in the mesh data. - drawEdges If True, a wireframe mesh will be drawn. - (default=False) - drawFaces If True, mesh faces are drawn. (default=True) - shader Name of shader program to use when drawing faces. - (None for no shader) - smooth If True, normal vectors are computed for each vertex - and interpolated within each face. - computeNormals If False, then computation of normal vectors is - disabled. This can provide a performance boost for - meshes that do not make use of normals. - ============== ===================================================== - """ - self.opts = { - 'meshdata': None, - 'color': (1., 1., 1., 1.), - 'drawEdges': False, - 'drawFaces': True, - 'edgeColor': (0.5, 0.5, 0.5, 1.0), - 'shader': None, - 'smooth': True, - 'computeNormals': True, - } - - GLGraphicsItem.__init__(self) - glopts = kwds.pop('glOptions', 'opaque') - self.setGLOptions(glopts) - shader = kwds.pop('shader', None) - self.setShader(shader) - - self.setMeshData(**kwds) - - ## storage for data compiled from MeshData object - self.vertexes = None - self.normals = None - self.colors = None - self.faces = None - - def setShader(self, shader): - """Set the shader used when rendering faces in the mesh. (see the GL shaders example)""" - self.opts['shader'] = shader - self.update() - - def shader(self): - shader = self.opts['shader'] - if isinstance(shader, shaders.ShaderProgram): - return shader - else: - return shaders.getShaderProgram(shader) - - def setColor(self, c): - """Set the default color to use when no vertex or face colors are specified.""" - self.opts['color'] = c - self.update() - - def setMeshData(self, **kwds): - """ - Set mesh data for this item. This can be invoked two ways: - - 1. Specify *meshdata* argument with a new MeshData object - 2. Specify keyword arguments to be passed to MeshData(..) to create a new instance. - """ - md = kwds.get('meshdata', None) - if md is None: - opts = {} - for k in ['vertexes', 'faces', 'edges', 'vertexColors', 'faceColors']: - try: - opts[k] = kwds.pop(k) - except KeyError: - pass - md = MeshData(**opts) - - self.opts['meshdata'] = md - self.opts.update(kwds) - self.meshDataChanged() - self.update() - - - def meshDataChanged(self): - """ - This method must be called to inform the item that the MeshData object - has been altered. - """ - - self.vertexes = None - self.faces = None - self.normals = None - self.colors = None - self.edges = None - self.edgeColors = None - self.update() - - def parseMeshData(self): - ## interpret vertex / normal data before drawing - ## This can: - ## - automatically generate normals if they were not specified - ## - pull vertexes/noormals/faces from MeshData if that was specified - - if self.vertexes is not None and self.normals is not None: - return - #if self.opts['normals'] is None: - #if self.opts['meshdata'] is None: - #self.opts['meshdata'] = MeshData(vertexes=self.opts['vertexes'], faces=self.opts['faces']) - if self.opts['meshdata'] is not None: - md = self.opts['meshdata'] - if self.opts['smooth'] and not md.hasFaceIndexedData(): - self.vertexes = md.vertexes() - if self.opts['computeNormals']: - self.normals = md.vertexNormals() - self.faces = md.faces() - if md.hasVertexColor(): - self.colors = md.vertexColors() - if md.hasFaceColor(): - self.colors = md.faceColors() - else: - self.vertexes = md.vertexes(indexed='faces') - if self.opts['computeNormals']: - if self.opts['smooth']: - self.normals = md.vertexNormals(indexed='faces') - else: - self.normals = md.faceNormals(indexed='faces') - self.faces = None - if md.hasVertexColor(): - self.colors = md.vertexColors(indexed='faces') - elif md.hasFaceColor(): - self.colors = md.faceColors(indexed='faces') - - if self.opts['drawEdges']: - self.edges = md.edges() - self.edgeVerts = md.vertexes() - return - - def paint(self): - self.setupGLState() - - self.parseMeshData() - - if self.opts['drawFaces']: - with self.shader(): - verts = self.vertexes - norms = self.normals - color = self.colors - faces = self.faces - if verts is None: - return - glEnableClientState(GL_VERTEX_ARRAY) - try: - glVertexPointerf(verts) - - if self.colors is None: - color = self.opts['color'] - if isinstance(color, QtGui.QColor): - glColor4f(*pg.glColor(color)) - else: - glColor4f(*color) - else: - glEnableClientState(GL_COLOR_ARRAY) - glColorPointerf(color) - - - if norms is not None: - glEnableClientState(GL_NORMAL_ARRAY) - glNormalPointerf(norms) - - if faces is None: - glDrawArrays(GL_TRIANGLES, 0, np.product(verts.shape[:-1])) - else: - faces = faces.astype(np.uint).flatten() - glDrawElements(GL_TRIANGLES, faces.shape[0], GL_UNSIGNED_INT, faces) - finally: - glDisableClientState(GL_NORMAL_ARRAY) - glDisableClientState(GL_VERTEX_ARRAY) - glDisableClientState(GL_COLOR_ARRAY) - - if self.opts['drawEdges']: - verts = self.edgeVerts - edges = self.edges - glEnableClientState(GL_VERTEX_ARRAY) - try: - glVertexPointerf(verts) - - if self.edgeColors is None: - color = self.opts['edgeColor'] - if isinstance(color, QtGui.QColor): - glColor4f(*pg.glColor(color)) - else: - glColor4f(*color) - else: - glEnableClientState(GL_COLOR_ARRAY) - glColorPointerf(color) - edges = edges.flatten() - glDrawElements(GL_LINES, edges.shape[0], GL_UNSIGNED_INT, edges) - finally: - glDisableClientState(GL_VERTEX_ARRAY) - glDisableClientState(GL_COLOR_ARRAY) - diff --git a/pyqtgraph/opengl/items/GLScatterPlotItem.py b/pyqtgraph/opengl/items/GLScatterPlotItem.py deleted file mode 100644 index b02a9dda..00000000 --- a/pyqtgraph/opengl/items/GLScatterPlotItem.py +++ /dev/null @@ -1,183 +0,0 @@ -from OpenGL.GL import * -from OpenGL.arrays import vbo -from .. GLGraphicsItem import GLGraphicsItem -from .. import shaders -from pyqtgraph import QtGui -import numpy as np - -__all__ = ['GLScatterPlotItem'] - -class GLScatterPlotItem(GLGraphicsItem): - """Draws points at a list of 3D positions.""" - - def __init__(self, **kwds): - GLGraphicsItem.__init__(self) - glopts = kwds.pop('glOptions', 'additive') - self.setGLOptions(glopts) - self.pos = [] - self.size = 10 - self.color = [1.0,1.0,1.0,0.5] - self.pxMode = True - #self.vbo = {} ## VBO does not appear to improve performance very much. - self.setData(**kwds) - - def setData(self, **kwds): - """ - Update the data displayed by this item. All arguments are optional; - for example it is allowed to update spot positions while leaving - colors unchanged, etc. - - ==================== ================================================== - Arguments: - ------------------------------------------------------------------------ - pos (N,3) array of floats specifying point locations. - color (N,4) array of floats (0.0-1.0) specifying - spot colors OR a tuple of floats specifying - a single color for all spots. - size (N,) array of floats specifying spot sizes or - a single value to apply to all spots. - pxMode If True, spot sizes are expressed in pixels. - Otherwise, they are expressed in item coordinates. - ==================== ================================================== - """ - args = ['pos', 'color', 'size', 'pxMode'] - for k in kwds.keys(): - if k not in args: - raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) - - args.remove('pxMode') - for arg in args: - if arg in kwds: - setattr(self, arg, kwds[arg]) - #self.vbo.pop(arg, None) - - self.pxMode = kwds.get('pxMode', self.pxMode) - self.update() - - def initializeGL(self): - - ## Generate texture for rendering points - w = 64 - def fn(x,y): - r = ((x-w/2.)**2 + (y-w/2.)**2) ** 0.5 - return 200 * (w/2. - np.clip(r, w/2.-1.0, w/2.)) - pData = np.empty((w, w, 4)) - pData[:] = 255 - pData[:,:,3] = np.fromfunction(fn, pData.shape[:2]) - #print pData.shape, pData.min(), pData.max() - pData = pData.astype(np.ubyte) - - self.pointTexture = glGenTextures(1) - glActiveTexture(GL_TEXTURE0) - glEnable(GL_TEXTURE_2D) - glBindTexture(GL_TEXTURE_2D, self.pointTexture) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pData.shape[0], pData.shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, pData) - - self.shader = shaders.getShaderProgram('pointSprite') - - #def getVBO(self, name): - #if name not in self.vbo: - #self.vbo[name] = vbo.VBO(getattr(self, name).astype('f')) - #return self.vbo[name] - - #def setupGLState(self): - #"""Prepare OpenGL state for drawing. This function is called immediately before painting.""" - ##glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly. - #glBlendFunc(GL_SRC_ALPHA, GL_ONE) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - #glDisable( GL_DEPTH_TEST ) - - ##glEnable( GL_POINT_SMOOTH ) - - ##glHint(GL_POINT_SMOOTH_HINT, GL_NICEST) - ##glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3)) - ##glPointParameterfv(GL_POINT_SIZE_MAX, (65500,)) - ##glPointParameterfv(GL_POINT_SIZE_MIN, (0,)) - - def paint(self): - self.setupGLState() - - glEnable(GL_POINT_SPRITE) - - glActiveTexture(GL_TEXTURE0) - glEnable( GL_TEXTURE_2D ) - glBindTexture(GL_TEXTURE_2D, self.pointTexture) - - glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE) - #glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) ## use texture color exactly - #glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ) ## texture modulates current color - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) - glEnable(GL_PROGRAM_POINT_SIZE) - - - with self.shader: - #glUniform1i(self.shader.uniform('texture'), 0) ## inform the shader which texture to use - glEnableClientState(GL_VERTEX_ARRAY) - try: - pos = self.pos - #if pos.ndim > 2: - #pos = pos.reshape((reduce(lambda a,b: a*b, pos.shape[:-1]), pos.shape[-1])) - glVertexPointerf(pos) - - if isinstance(self.color, np.ndarray): - glEnableClientState(GL_COLOR_ARRAY) - glColorPointerf(self.color) - else: - if isinstance(self.color, QtGui.QColor): - glColor4f(*fn.glColor(self.color)) - else: - glColor4f(*self.color) - - if not self.pxMode or isinstance(self.size, np.ndarray): - glEnableClientState(GL_NORMAL_ARRAY) - norm = np.empty(pos.shape) - if self.pxMode: - norm[...,0] = self.size - else: - gpos = self.mapToView(pos.transpose()).transpose() - pxSize = self.view().pixelSize(gpos) - norm[...,0] = self.size / pxSize - - glNormalPointerf(norm) - else: - glNormal3f(self.size, 0, 0) ## vertex shader uses norm.x to determine point size - #glPointSize(self.size) - glDrawArrays(GL_POINTS, 0, int(pos.size / pos.shape[-1])) - finally: - glDisableClientState(GL_NORMAL_ARRAY) - glDisableClientState(GL_VERTEX_ARRAY) - glDisableClientState(GL_COLOR_ARRAY) - #posVBO.unbind() - - #for i in range(len(self.pos)): - #pos = self.pos[i] - - #if isinstance(self.color, np.ndarray): - #color = self.color[i] - #else: - #color = self.color - #if isinstance(self.color, QtGui.QColor): - #color = fn.glColor(self.color) - - #if isinstance(self.size, np.ndarray): - #size = self.size[i] - #else: - #size = self.size - - #pxSize = self.view().pixelSize(QtGui.QVector3D(*pos)) - - #glPointSize(size / pxSize) - #glBegin( GL_POINTS ) - #glColor4f(*color) # x is blue - ##glNormal3f(size, 0, 0) - #glVertex3f(*pos) - #glEnd() - - - - - diff --git a/pyqtgraph/opengl/items/GLSurfacePlotItem.py b/pyqtgraph/opengl/items/GLSurfacePlotItem.py deleted file mode 100644 index 88d50fac..00000000 --- a/pyqtgraph/opengl/items/GLSurfacePlotItem.py +++ /dev/null @@ -1,139 +0,0 @@ -from OpenGL.GL import * -from .GLMeshItem import GLMeshItem -from .. MeshData import MeshData -from pyqtgraph.Qt import QtGui -import pyqtgraph as pg -import numpy as np - - - -__all__ = ['GLSurfacePlotItem'] - -class GLSurfacePlotItem(GLMeshItem): - """ - **Bases:** :class:`GLMeshItem ` - - Displays a surface plot on a regular x,y grid - """ - def __init__(self, x=None, y=None, z=None, colors=None, **kwds): - """ - The x, y, z, and colors arguments are passed to setData(). - All other keyword arguments are passed to GLMeshItem.__init__(). - """ - - self._x = None - self._y = None - self._z = None - self._color = None - self._vertexes = None - self._meshdata = MeshData() - GLMeshItem.__init__(self, meshdata=self._meshdata, **kwds) - - self.setData(x, y, z, colors) - - - - def setData(self, x=None, y=None, z=None, colors=None): - """ - Update the data in this surface plot. - - ========== ===================================================================== - Arguments - x,y 1D arrays of values specifying the x,y positions of vertexes in the - grid. If these are omitted, then the values will be assumed to be - integers. - z 2D array of height values for each grid vertex. - colors (width, height, 4) array of vertex colors. - ========== ===================================================================== - - All arguments are optional. - - Note that if vertex positions are updated, the normal vectors for each triangle must - be recomputed. This is somewhat expensive if the surface was initialized with smooth=False - and very expensive if smooth=True. For faster performance, initialize with - computeNormals=False and use per-vertex colors or a normal-independent shader program. - """ - if x is not None: - if self._x is None or len(x) != len(self._x): - self._vertexes = None - self._x = x - - if y is not None: - if self._y is None or len(y) != len(self._y): - self._vertexes = None - self._y = y - - if z is not None: - #if self._x is None: - #self._x = np.arange(z.shape[0]) - #self._vertexes = None - #if self._y is None: - #self._y = np.arange(z.shape[1]) - #self._vertexes = None - - if self._x is not None and z.shape[0] != len(self._x): - raise Exception('Z values must have shape (len(x), len(y))') - if self._y is not None and z.shape[1] != len(self._y): - raise Exception('Z values must have shape (len(x), len(y))') - self._z = z - if self._vertexes is not None and self._z.shape != self._vertexes.shape[:2]: - self._vertexes = None - - if colors is not None: - self._colors = colors - self._meshdata.setVertexColors(colors) - - if self._z is None: - return - - updateMesh = False - newVertexes = False - - ## Generate vertex and face array - if self._vertexes is None: - newVertexes = True - self._vertexes = np.empty((self._z.shape[0], self._z.shape[1], 3), dtype=float) - self.generateFaces() - self._meshdata.setFaces(self._faces) - updateMesh = True - - ## Copy x, y, z data into vertex array - if newVertexes or x is not None: - if x is None: - if self._x is None: - x = np.arange(self._z.shape[0]) - else: - x = self._x - self._vertexes[:, :, 0] = x.reshape(len(x), 1) - updateMesh = True - - if newVertexes or y is not None: - if y is None: - if self._y is None: - y = np.arange(self._z.shape[1]) - else: - y = self._y - self._vertexes[:, :, 1] = y.reshape(1, len(y)) - updateMesh = True - - if newVertexes or z is not None: - self._vertexes[...,2] = self._z - updateMesh = True - - ## Update MeshData - if updateMesh: - self._meshdata.setVertexes(self._vertexes.reshape(self._vertexes.shape[0]*self._vertexes.shape[1], 3)) - self.meshDataChanged() - - - def generateFaces(self): - cols = self._z.shape[1]-1 - rows = self._z.shape[0]-1 - faces = np.empty((cols*rows*2, 3), dtype=np.uint) - rowtemplate1 = np.arange(cols).reshape(cols, 1) + np.array([[0, 1, cols+1]]) - rowtemplate2 = np.arange(cols).reshape(cols, 1) + np.array([[cols+1, 1, cols+2]]) - for row in range(rows): - start = row * cols * 2 - faces[start:start+cols] = rowtemplate1 + row * (cols+1) - faces[start+cols:start+(cols*2)] = rowtemplate2 + row * (cols+1) - self._faces = faces diff --git a/pyqtgraph/opengl/items/GLVolumeItem.py b/pyqtgraph/opengl/items/GLVolumeItem.py deleted file mode 100644 index 4980239d..00000000 --- a/pyqtgraph/opengl/items/GLVolumeItem.py +++ /dev/null @@ -1,213 +0,0 @@ -from OpenGL.GL import * -from .. GLGraphicsItem import GLGraphicsItem -from pyqtgraph.Qt import QtGui -import numpy as np - -__all__ = ['GLVolumeItem'] - -class GLVolumeItem(GLGraphicsItem): - """ - **Bases:** :class:`GLGraphicsItem ` - - Displays volumetric data. - """ - - - def __init__(self, data, sliceDensity=1, smooth=True, glOptions='translucent'): - """ - ============== ======================================================================================= - **Arguments:** - data Volume data to be rendered. *Must* be 4D numpy array (x, y, z, RGBA) with dtype=ubyte. - sliceDensity Density of slices to render through the volume. A value of 1 means one slice per voxel. - smooth (bool) If True, the volume slices are rendered with linear interpolation - ============== ======================================================================================= - """ - - self.sliceDensity = sliceDensity - self.smooth = smooth - self.data = data - GLGraphicsItem.__init__(self) - self.setGLOptions(glOptions) - - def initializeGL(self): - glEnable(GL_TEXTURE_3D) - self.texture = glGenTextures(1) - glBindTexture(GL_TEXTURE_3D, self.texture) - if self.smooth: - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - else: - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) - glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) - shape = self.data.shape - - ## Test texture dimensions first - glTexImage3D(GL_PROXY_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) - if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_WIDTH) == 0: - raise Exception("OpenGL failed to create 3D texture (%dx%dx%d); too large for this hardware." % shape[:3]) - - glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((2,1,0,3))) - glDisable(GL_TEXTURE_3D) - - self.lists = {} - for ax in [0,1,2]: - for d in [-1, 1]: - l = glGenLists(1) - self.lists[(ax,d)] = l - glNewList(l, GL_COMPILE) - self.drawVolume(ax, d) - glEndList() - - - def paint(self): - self.setupGLState() - - glEnable(GL_TEXTURE_3D) - glBindTexture(GL_TEXTURE_3D, self.texture) - - #glEnable(GL_DEPTH_TEST) - #glDisable(GL_CULL_FACE) - #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) - #glEnable( GL_BLEND ) - #glEnable( GL_ALPHA_TEST ) - glColor4f(1,1,1,1) - - view = self.view() - center = QtGui.QVector3D(*[x/2. for x in self.data.shape[:3]]) - cam = self.mapFromParent(view.cameraPosition()) - center - #print "center", center, "cam", view.cameraPosition(), self.mapFromParent(view.cameraPosition()), "diff", cam - cam = np.array([cam.x(), cam.y(), cam.z()]) - ax = np.argmax(abs(cam)) - d = 1 if cam[ax] > 0 else -1 - glCallList(self.lists[(ax,d)]) ## draw axes - glDisable(GL_TEXTURE_3D) - - def drawVolume(self, ax, d): - N = 5 - - imax = [0,1,2] - imax.remove(ax) - - tp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]] - vp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]] - nudge = [0.5/x for x in self.data.shape] - tp[0][imax[0]] = 0+nudge[imax[0]] - tp[0][imax[1]] = 0+nudge[imax[1]] - tp[1][imax[0]] = 1-nudge[imax[0]] - tp[1][imax[1]] = 0+nudge[imax[1]] - tp[2][imax[0]] = 1-nudge[imax[0]] - tp[2][imax[1]] = 1-nudge[imax[1]] - tp[3][imax[0]] = 0+nudge[imax[0]] - tp[3][imax[1]] = 1-nudge[imax[1]] - - vp[0][imax[0]] = 0 - vp[0][imax[1]] = 0 - vp[1][imax[0]] = self.data.shape[imax[0]] - vp[1][imax[1]] = 0 - vp[2][imax[0]] = self.data.shape[imax[0]] - vp[2][imax[1]] = self.data.shape[imax[1]] - vp[3][imax[0]] = 0 - vp[3][imax[1]] = self.data.shape[imax[1]] - slices = self.data.shape[ax] * self.sliceDensity - r = list(range(slices)) - if d == -1: - r = r[::-1] - - glBegin(GL_QUADS) - tzVals = np.linspace(nudge[ax], 1.0-nudge[ax], slices) - vzVals = np.linspace(0, self.data.shape[ax], slices) - for i in r: - z = tzVals[i] - w = vzVals[i] - - tp[0][ax] = z - tp[1][ax] = z - tp[2][ax] = z - tp[3][ax] = z - - vp[0][ax] = w - vp[1][ax] = w - vp[2][ax] = w - vp[3][ax] = w - - - glTexCoord3f(*tp[0]) - glVertex3f(*vp[0]) - glTexCoord3f(*tp[1]) - glVertex3f(*vp[1]) - glTexCoord3f(*tp[2]) - glVertex3f(*vp[2]) - glTexCoord3f(*tp[3]) - glVertex3f(*vp[3]) - glEnd() - - - - - - - - - - ## Interesting idea: - ## remove projection/modelview matrixes, recreate in texture coords. - ## it _sorta_ works, but needs tweaking. - #mvm = glGetDoublev(GL_MODELVIEW_MATRIX) - #pm = glGetDoublev(GL_PROJECTION_MATRIX) - #m = QtGui.QMatrix4x4(mvm.flatten()).inverted()[0] - #p = QtGui.QMatrix4x4(pm.flatten()).inverted()[0] - - #glMatrixMode(GL_PROJECTION) - #glPushMatrix() - #glLoadIdentity() - #N=1 - #glOrtho(-N,N,-N,N,-100,100) - - #glMatrixMode(GL_MODELVIEW) - #glLoadIdentity() - - - #glMatrixMode(GL_TEXTURE) - #glLoadIdentity() - #glMultMatrixf(m.copyDataTo()) - - #view = self.view() - #w = view.width() - #h = view.height() - #dist = view.opts['distance'] - #fov = view.opts['fov'] - #nearClip = dist * .1 - #farClip = dist * 5. - #r = nearClip * np.tan(fov) - #t = r * h / w - - #p = QtGui.QMatrix4x4() - #p.frustum( -r, r, -t, t, nearClip, farClip) - #glMultMatrixf(p.inverted()[0].copyDataTo()) - - - #glBegin(GL_QUADS) - - #M=1 - #for i in range(500): - #z = i/500. - #w = -i/500. - #glTexCoord3f(-M, -M, z) - #glVertex3f(-N, -N, w) - #glTexCoord3f(M, -M, z) - #glVertex3f(N, -N, w) - #glTexCoord3f(M, M, z) - #glVertex3f(N, N, w) - #glTexCoord3f(-M, M, z) - #glVertex3f(-N, N, w) - #glEnd() - #glDisable(GL_TEXTURE_3D) - - #glMatrixMode(GL_PROJECTION) - #glPopMatrix() - - - diff --git a/pyqtgraph/opengl/items/__init__.py b/pyqtgraph/opengl/items/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pyqtgraph/opengl/shaders.py b/pyqtgraph/opengl/shaders.py deleted file mode 100644 index 8922cd21..00000000 --- a/pyqtgraph/opengl/shaders.py +++ /dev/null @@ -1,402 +0,0 @@ -try: - from OpenGL import NullFunctionError -except ImportError: - from OpenGL.error import NullFunctionError -from OpenGL.GL import * -from OpenGL.GL import shaders -import re - -## For centralizing and managing vertex/fragment shader programs. - -def initShaders(): - global Shaders - Shaders = [ - ShaderProgram(None, []), - - ## increases fragment alpha as the normal turns orthogonal to the view - ## this is useful for viewing shells that enclose a volume (such as isosurfaces) - ShaderProgram('balloon', [ - VertexShader(""" - varying vec3 normal; - void main() { - // compute here for use in fragment shader - normal = normalize(gl_NormalMatrix * gl_Normal); - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - varying vec3 normal; - void main() { - vec4 color = gl_Color; - color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0); - gl_FragColor = color; - } - """) - ]), - - ## colors fragments based on face normals relative to view - ## This means that the colors will change depending on how the view is rotated - ShaderProgram('viewNormalColor', [ - VertexShader(""" - varying vec3 normal; - void main() { - // compute here for use in fragment shader - normal = normalize(gl_NormalMatrix * gl_Normal); - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - varying vec3 normal; - void main() { - vec4 color = gl_Color; - color.x = (normal.x + 1.0) * 0.5; - color.y = (normal.y + 1.0) * 0.5; - color.z = (normal.z + 1.0) * 0.5; - gl_FragColor = color; - } - """) - ]), - - ## colors fragments based on absolute face normals. - ShaderProgram('normalColor', [ - VertexShader(""" - varying vec3 normal; - void main() { - // compute here for use in fragment shader - normal = normalize(gl_Normal); - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - varying vec3 normal; - void main() { - vec4 color = gl_Color; - color.x = (normal.x + 1.0) * 0.5; - color.y = (normal.y + 1.0) * 0.5; - color.z = (normal.z + 1.0) * 0.5; - gl_FragColor = color; - } - """) - ]), - - ## very simple simulation of lighting. - ## The light source position is always relative to the camera. - ShaderProgram('shaded', [ - VertexShader(""" - varying vec3 normal; - void main() { - // compute here for use in fragment shader - normal = normalize(gl_NormalMatrix * gl_Normal); - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - varying vec3 normal; - void main() { - float p = dot(normal, normalize(vec3(1.0, -1.0, -1.0))); - p = p < 0. ? 0. : p * 0.8; - vec4 color = gl_Color; - color.x = color.x * (0.2 + p); - color.y = color.y * (0.2 + p); - color.z = color.z * (0.2 + p); - gl_FragColor = color; - } - """) - ]), - - ## colors get brighter near edges of object - ShaderProgram('edgeHilight', [ - VertexShader(""" - varying vec3 normal; - void main() { - // compute here for use in fragment shader - normal = normalize(gl_NormalMatrix * gl_Normal); - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - varying vec3 normal; - void main() { - vec4 color = gl_Color; - float s = pow(normal.x*normal.x + normal.y*normal.y, 2.0); - color.x = color.x + s * (1.0-color.x); - color.y = color.y + s * (1.0-color.y); - color.z = color.z + s * (1.0-color.z); - gl_FragColor = color; - } - """) - ]), - - ## colors fragments by z-value. - ## This is useful for coloring surface plots by height. - ## This shader uses a uniform called "colorMap" to determine how to map the colors: - ## red = pow(z * colorMap[0] + colorMap[1], colorMap[2]) - ## green = pow(z * colorMap[3] + colorMap[4], colorMap[5]) - ## blue = pow(z * colorMap[6] + colorMap[7], colorMap[8]) - ## (set the values like this: shader['uniformMap'] = array([...]) - ShaderProgram('heightColor', [ - VertexShader(""" - varying vec4 pos; - void main() { - gl_FrontColor = gl_Color; - gl_BackColor = gl_Color; - pos = gl_Vertex; - gl_Position = ftransform(); - } - """), - FragmentShader(""" - uniform float colorMap[9]; - varying vec4 pos; - //out vec4 gl_FragColor; // only needed for later glsl versions - //in vec4 gl_Color; - void main() { - vec4 color = gl_Color; - color.x = colorMap[0] * (pos.z + colorMap[1]); - if (colorMap[2] != 1.0) - color.x = pow(color.x, colorMap[2]); - color.x = color.x < 0. ? 0. : (color.x > 1. ? 1. : color.x); - - color.y = colorMap[3] * (pos.z + colorMap[4]); - if (colorMap[5] != 1.0) - color.y = pow(color.y, colorMap[5]); - color.y = color.y < 0. ? 0. : (color.y > 1. ? 1. : color.y); - - color.z = colorMap[6] * (pos.z + colorMap[7]); - if (colorMap[8] != 1.0) - color.z = pow(color.z, colorMap[8]); - color.z = color.z < 0. ? 0. : (color.z > 1. ? 1. : color.z); - - color.w = 1.0; - gl_FragColor = color; - } - """), - ], uniforms={'colorMap': [1, 1, 1, 1, 0.5, 1, 1, 0, 1]}), - ShaderProgram('pointSprite', [ ## allows specifying point size using normal.x - ## See: - ## - ## http://stackoverflow.com/questions/9609423/applying-part-of-a-texture-sprite-sheet-texture-map-to-a-point-sprite-in-ios - ## http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 - ## - ## - VertexShader(""" - void main() { - gl_FrontColor=gl_Color; - gl_PointSize = gl_Normal.x; - gl_Position = ftransform(); - } - """), - #FragmentShader(""" - ##version 120 - #uniform sampler2D texture; - #void main ( ) - #{ - #gl_FragColor = texture2D(texture, gl_PointCoord) * gl_Color; - #} - #""") - ]), - ] - - -CompiledShaderPrograms = {} - -def getShaderProgram(name): - return ShaderProgram.names[name] - -class Shader(object): - def __init__(self, shaderType, code): - self.shaderType = shaderType - self.code = code - self.compiled = None - - def shader(self): - if self.compiled is None: - try: - self.compiled = shaders.compileShader(self.code, self.shaderType) - except NullFunctionError: - raise Exception("This OpenGL implementation does not support shader programs; many OpenGL features in pyqtgraph will not work.") - except RuntimeError as exc: - ## Format compile errors a bit more nicely - if len(exc.args) == 3: - err, code, typ = exc.args - if not err.startswith('Shader compile failure'): - raise - code = code[0].decode('utf_8').split('\n') - err, c, msgs = err.partition(':') - err = err + '\n' - msgs = re.sub('b\'','',msgs) - msgs = re.sub('\'$','',msgs) - msgs = re.sub('\\\\n','\n',msgs) - msgs = msgs.split('\n') - errNums = [()] * len(code) - for i, msg in enumerate(msgs): - msg = msg.strip() - if msg == '': - continue - m = re.match(r'(\d+\:)?\d+\((\d+)\)', msg) - if m is not None: - line = int(m.groups()[1]) - errNums[line-1] = errNums[line-1] + (str(i+1),) - #code[line-1] = '%d\t%s' % (i+1, code[line-1]) - err = err + "%d %s\n" % (i+1, msg) - errNums = [','.join(n) for n in errNums] - maxlen = max(map(len, errNums)) - code = [errNums[i] + " "*(maxlen-len(errNums[i])) + line for i, line in enumerate(code)] - err = err + '\n'.join(code) - raise Exception(err) - else: - raise - return self.compiled - -class VertexShader(Shader): - def __init__(self, code): - Shader.__init__(self, GL_VERTEX_SHADER, code) - -class FragmentShader(Shader): - def __init__(self, code): - Shader.__init__(self, GL_FRAGMENT_SHADER, code) - - - - -class ShaderProgram(object): - names = {} - - def __init__(self, name, shaders, uniforms=None): - self.name = name - ShaderProgram.names[name] = self - self.shaders = shaders - self.prog = None - self.blockData = {} - self.uniformData = {} - - ## parse extra options from the shader definition - if uniforms is not None: - for k,v in uniforms.items(): - self[k] = v - - def setBlockData(self, blockName, data): - if data is None: - del self.blockData[blockName] - else: - self.blockData[blockName] = data - - def setUniformData(self, uniformName, data): - if data is None: - del self.uniformData[uniformName] - else: - self.uniformData[uniformName] = data - - def __setitem__(self, item, val): - self.setUniformData(item, val) - - def __delitem__(self, item): - self.setUniformData(item, None) - - def program(self): - if self.prog is None: - try: - compiled = [s.shader() for s in self.shaders] ## compile all shaders - self.prog = shaders.compileProgram(*compiled) ## compile program - except: - self.prog = -1 - raise - return self.prog - - def __enter__(self): - if len(self.shaders) > 0 and self.program() != -1: - glUseProgram(self.program()) - - try: - ## load uniform values into program - for uniformName, data in self.uniformData.items(): - loc = self.uniform(uniformName) - if loc == -1: - raise Exception('Could not find uniform variable "%s"' % uniformName) - glUniform1fv(loc, len(data), data) - - ### bind buffer data to program blocks - #if len(self.blockData) > 0: - #bindPoint = 1 - #for blockName, data in self.blockData.items(): - ### Program should have a uniform block declared: - ### - ### layout (std140) uniform blockName { - ### vec4 diffuse; - ### }; - - ### pick any-old binding point. (there are a limited number of these per-program - #bindPoint = 1 - - ### get the block index for a uniform variable in the shader - #blockIndex = glGetUniformBlockIndex(self.program(), blockName) - - ### give the shader block a binding point - #glUniformBlockBinding(self.program(), blockIndex, bindPoint) - - ### create a buffer - #buf = glGenBuffers(1) - #glBindBuffer(GL_UNIFORM_BUFFER, buf) - #glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW) - ### also possible to use glBufferSubData to fill parts of the buffer - - ### bind buffer to the same binding point - #glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf) - except: - glUseProgram(0) - raise - - - - def __exit__(self, *args): - if len(self.shaders) > 0: - glUseProgram(0) - - def uniform(self, name): - """Return the location integer for a uniform variable in this program""" - return glGetUniformLocation(self.program(), name.encode('utf_8')) - - #def uniformBlockInfo(self, blockName): - #blockIndex = glGetUniformBlockIndex(self.program(), blockName) - #count = glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS) - #indices = [] - #for i in range(count): - #indices.append(glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES)) - -class HeightColorShader(ShaderProgram): - def __enter__(self): - ## Program should have a uniform block declared: - ## - ## layout (std140) uniform blockName { - ## vec4 diffuse; - ## vec4 ambient; - ## }; - - ## pick any-old binding point. (there are a limited number of these per-program - bindPoint = 1 - - ## get the block index for a uniform variable in the shader - blockIndex = glGetUniformBlockIndex(self.program(), "blockName") - - ## give the shader block a binding point - glUniformBlockBinding(self.program(), blockIndex, bindPoint) - - ## create a buffer - buf = glGenBuffers(1) - glBindBuffer(GL_UNIFORM_BUFFER, buf) - glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW) - ## also possible to use glBufferSubData to fill parts of the buffer - - ## bind buffer to the same binding point - glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf) - -initShaders() diff --git a/pyqtgraph/ordereddict.py b/pyqtgraph/ordereddict.py deleted file mode 100644 index 7242b506..00000000 --- a/pyqtgraph/ordereddict.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright (c) 2009 Raymond Hettinger -# -# 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. - -from UserDict import DictMixin - -class OrderedDict(dict, DictMixin): - - def __init__(self, *args, **kwds): - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__end - except AttributeError: - self.clear() - self.update(*args, **kwds) - - def clear(self): - self.__end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.__map = {} # key --> [key, prev, next] - dict.clear(self) - - def __setitem__(self, key, value): - if key not in self: - end = self.__end - curr = end[1] - curr[2] = end[1] = self.__map[key] = [key, curr, end] - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - dict.__delitem__(self, key) - key, prev, next = self.__map.pop(key) - prev[2] = next - next[1] = prev - - def __iter__(self): - end = self.__end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self): - end = self.__end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - if last: - key = reversed(self).next() - else: - key = iter(self).next() - value = self.pop(key) - return key, value - - def __reduce__(self): - items = [[k, self[k]] for k in self] - tmp = self.__map, self.__end - del self.__map, self.__end - inst_dict = vars(self).copy() - self.__map, self.__end = tmp - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def keys(self): - return list(self) - - setdefault = DictMixin.setdefault - update = DictMixin.update - pop = DictMixin.pop - values = DictMixin.values - items = DictMixin.items - iterkeys = DictMixin.iterkeys - itervalues = DictMixin.itervalues - iteritems = DictMixin.iteritems - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - if isinstance(other, OrderedDict): - if len(self) != len(other): - return False - for p, q in zip(self.items(), other.items()): - if p != q: - return False - return True - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other diff --git a/pyqtgraph/parametertree/Parameter.py b/pyqtgraph/parametertree/Parameter.py deleted file mode 100644 index 9a7ece25..00000000 --- a/pyqtgraph/parametertree/Parameter.py +++ /dev/null @@ -1,710 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -import os, weakref, re -from pyqtgraph.pgcollections import OrderedDict -from .ParameterItem import ParameterItem - -PARAM_TYPES = {} -PARAM_NAMES = {} - -def registerParameterType(name, cls, override=False): - global PARAM_TYPES - if name in PARAM_TYPES and not override: - raise Exception("Parameter type '%s' already exists (use override=True to replace)" % name) - PARAM_TYPES[name] = cls - PARAM_NAMES[cls] = name - - - -class Parameter(QtCore.QObject): - """ - A Parameter is the basic unit of data in a parameter tree. Each parameter has - a name, a type, a value, and several other properties that modify the behavior of the - Parameter. Parameters may have parent / child / sibling relationships to construct - organized hierarchies. Parameters generally do not have any inherent GUI or visual - interpretation; instead they manage ParameterItem instances which take care of - display and user interaction. - - Note: It is fairly uncommon to use the Parameter class directly; mostly you - will use subclasses which provide specialized type and data handling. The static - pethod Parameter.create(...) is an easy way to generate instances of these subclasses. - - For more Parameter types, see ParameterTree.parameterTypes module. - - =================================== ========================================================= - **Signals:** - sigStateChanged(self, change, info) Emitted when anything changes about this parameter at - all. - The second argument is a string indicating what changed - ('value', 'childAdded', etc..) - The third argument can be any extra information about - the change - sigTreeStateChanged(self, changes) Emitted when any child in the tree changes state - (but only if monitorChildren() is called) - the format of *changes* is [(param, change, info), ...] - sigValueChanged(self, value) Emitted when value is finished changing - sigValueChanging(self, value) Emitted immediately for all value changes, - including during editing. - sigChildAdded(self, child, index) Emitted when a child is added - sigChildRemoved(self, child) Emitted when a child is removed - sigParentChanged(self, parent) Emitted when this parameter's parent has changed - sigLimitsChanged(self, limits) Emitted when this parameter's limits have changed - sigDefaultChanged(self, default) Emitted when this parameter's default value has changed - sigNameChanged(self, name) Emitted when this parameter's name has changed - sigOptionsChanged(self, opts) Emitted when any of this parameter's options have changed - =================================== ========================================================= - """ - ## name, type, limits, etc. - ## can also carry UI hints (slider vs spinbox, etc.) - - sigValueChanged = QtCore.Signal(object, object) ## self, value emitted when value is finished being edited - sigValueChanging = QtCore.Signal(object, object) ## self, value emitted as value is being edited - - sigChildAdded = QtCore.Signal(object, object, object) ## self, child, index - sigChildRemoved = QtCore.Signal(object, object) ## self, child - sigParentChanged = QtCore.Signal(object, object) ## self, parent - sigLimitsChanged = QtCore.Signal(object, object) ## self, limits - sigDefaultChanged = QtCore.Signal(object, object) ## self, default - sigNameChanged = QtCore.Signal(object, object) ## self, name - sigOptionsChanged = QtCore.Signal(object, object) ## self, {opt:val, ...} - - ## Emitted when anything changes about this parameter at all. - ## The second argument is a string indicating what changed ('value', 'childAdded', etc..) - ## The third argument can be any extra information about the change - sigStateChanged = QtCore.Signal(object, object, object) ## self, change, info - - ## emitted when any child in the tree changes state - ## (but only if monitorChildren() is called) - sigTreeStateChanged = QtCore.Signal(object, object) # self, changes - # changes = [(param, change, info), ...] - - # bad planning. - #def __new__(cls, *args, **opts): - #try: - #cls = PARAM_TYPES[opts['type']] - #except KeyError: - #pass - #return QtCore.QObject.__new__(cls, *args, **opts) - - @staticmethod - def create(**opts): - """ - Static method that creates a new Parameter (or subclass) instance using - opts['type'] to select the appropriate class. - - All options are passed directly to the new Parameter's __init__ method. - Use registerParameterType() to add new class types. - """ - typ = opts.get('type', None) - if typ is None: - cls = Parameter - else: - cls = PARAM_TYPES[opts['type']] - return cls(**opts) - - def __init__(self, **opts): - """ - Initialize a Parameter object. Although it is rare to directly create a - Parameter instance, the options available to this method are also allowed - by most Parameter subclasses. - - ================= ========================================================= - Keyword Arguments - name The name to give this Parameter. This is the name that - will appear in the left-most column of a ParameterTree - for this Parameter. - value The value to initially assign to this Parameter. - default The default value for this Parameter (most Parameters - provide an option to 'reset to default'). - children A list of children for this Parameter. Children - may be given either as a Parameter instance or as a - dictionary to pass to Parameter.create(). In this way, - it is possible to specify complex hierarchies of - Parameters from a single nested data structure. - readonly If True, the user will not be allowed to edit this - Parameter. (default=False) - enabled If False, any widget(s) for this parameter will appear - disabled. (default=True) - visible If False, the Parameter will not appear when displayed - in a ParameterTree. (default=True) - renamable If True, the user may rename this Parameter. - (default=False) - removable If True, the user may remove this Parameter. - (default=False) - expanded If True, the Parameter will appear expanded when - displayed in a ParameterTree (its children will be - visible). (default=True) - ================= ========================================================= - """ - - - QtCore.QObject.__init__(self) - - self.opts = { - 'type': None, - 'readonly': False, - 'visible': True, - 'enabled': True, - 'renamable': False, - 'removable': False, - 'strictNaming': False, # forces name to be usable as a python variable - 'expanded': True, - #'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits. - } - self.opts.update(opts) - - self.childs = [] - self.names = {} ## map name:child - self.items = weakref.WeakKeyDictionary() ## keeps track of tree items representing this parameter - self._parent = None - self.treeStateChanges = [] ## cache of tree state changes to be delivered on next emit - self.blockTreeChangeEmit = 0 - #self.monitoringChildren = False ## prevent calling monitorChildren more than once - - if 'value' not in self.opts: - self.opts['value'] = None - - if 'name' not in self.opts or not isinstance(self.opts['name'], basestring): - raise Exception("Parameter must have a string name specified in opts.") - self.setName(opts['name']) - - self.addChildren(self.opts.get('children', [])) - - if 'value' in self.opts and 'default' not in self.opts: - self.opts['default'] = self.opts['value'] - - ## Connect all state changed signals to the general sigStateChanged - self.sigValueChanged.connect(lambda param, data: self.emitStateChanged('value', data)) - self.sigChildAdded.connect(lambda param, *data: self.emitStateChanged('childAdded', data)) - self.sigChildRemoved.connect(lambda param, data: self.emitStateChanged('childRemoved', data)) - self.sigParentChanged.connect(lambda param, data: self.emitStateChanged('parent', data)) - self.sigLimitsChanged.connect(lambda param, data: self.emitStateChanged('limits', data)) - self.sigDefaultChanged.connect(lambda param, data: self.emitStateChanged('default', data)) - self.sigNameChanged.connect(lambda param, data: self.emitStateChanged('name', data)) - self.sigOptionsChanged.connect(lambda param, data: self.emitStateChanged('options', data)) - - #self.watchParam(self) ## emit treechange signals if our own state changes - - def name(self): - """Return the name of this Parameter.""" - return self.opts['name'] - - def setName(self, name): - """Attempt to change the name of this parameter; return the actual name. - (The parameter may reject the name change or automatically pick a different name)""" - if self.opts['strictNaming']: - if len(name) < 1 or re.search(r'\W', name) or re.match(r'\d', name[0]): - raise Exception("Parameter name '%s' is invalid. (Must contain only alphanumeric and underscore characters and may not start with a number)" % name) - parent = self.parent() - if parent is not None: - name = parent._renameChild(self, name) ## first ask parent if it's ok to rename - if self.opts['name'] != name: - self.opts['name'] = name - self.sigNameChanged.emit(self, name) - return name - - def type(self): - """Return the type string for this Parameter.""" - return self.opts['type'] - - def isType(self, typ): - """ - Return True if this parameter type matches the name *typ*. - This can occur either of two ways: - - - If self.type() == *typ* - - If this parameter's class is registered with the name *typ* - """ - if self.type() == typ: - return True - global PARAM_TYPES - cls = PARAM_TYPES.get(typ, None) - if cls is None: - raise Exception("Type name '%s' is not registered." % str(typ)) - return self.__class__ is cls - - def childPath(self, child): - """ - Return the path of parameter names from self to child. - If child is not a (grand)child of self, return None. - """ - path = [] - while child is not self: - path.insert(0, child.name()) - child = child.parent() - if child is None: - return None - return path - - def setValue(self, value, blockSignal=None): - """ - Set the value of this Parameter; return the actual value that was set. - (this may be different from the value that was requested) - """ - try: - if blockSignal is not None: - self.sigValueChanged.disconnect(blockSignal) - if self.opts['value'] == value: - return value - self.opts['value'] = value - self.sigValueChanged.emit(self, value) - finally: - if blockSignal is not None: - self.sigValueChanged.connect(blockSignal) - - return value - - def value(self): - """ - Return the value of this Parameter. - """ - return self.opts['value'] - - def getValues(self): - """Return a tree of all values that are children of this parameter""" - vals = OrderedDict() - for ch in self: - vals[ch.name()] = (ch.value(), ch.getValues()) - return vals - - def saveState(self): - """ - Return a structure representing the entire state of the parameter tree. - The tree state may be restored from this structure using restoreState() - """ - state = self.opts.copy() - state['children'] = OrderedDict([(ch.name(), ch.saveState()) for ch in self]) - if state['type'] is None: - global PARAM_NAMES - state['type'] = PARAM_NAMES.get(type(self), None) - return state - - def restoreState(self, state, recursive=True, addChildren=True, removeChildren=True, blockSignals=True): - """ - Restore the state of this parameter and its children from a structure generated using saveState() - If recursive is True, then attempt to restore the state of child parameters as well. - If addChildren is True, then any children which are referenced in the state object will be - created if they do not already exist. - If removeChildren is True, then any children which are not referenced in the state object will - be removed. - If blockSignals is True, no signals will be emitted until the tree has been completely restored. - This prevents signal handlers from responding to a partially-rebuilt network. - """ - childState = state.get('children', []) - - ## list of children may be stored either as list or dict. - if isinstance(childState, dict): - childState = childState.values() - - - if blockSignals: - self.blockTreeChangeSignal() - - try: - self.setOpts(**state) - - if not recursive: - return - - ptr = 0 ## pointer to first child that has not been restored yet - foundChilds = set() - #print "==============", self.name() - - for ch in childState: - name = ch['name'] - typ = ch['type'] - #print('child: %s, %s' % (self.name()+'.'+name, typ)) - - ## First, see if there is already a child with this name and type - gotChild = False - for i, ch2 in enumerate(self.childs[ptr:]): - #print " ", ch2.name(), ch2.type() - if ch2.name() != name or not ch2.isType(typ): - continue - gotChild = True - #print " found it" - if i != 0: ## move parameter to next position - #self.removeChild(ch2) - self.insertChild(ptr, ch2) - #print " moved to position", ptr - ch2.restoreState(ch, recursive=recursive, addChildren=addChildren, removeChildren=removeChildren) - foundChilds.add(ch2) - - break - - if not gotChild: - if not addChildren: - #print " ignored child" - continue - #print " created new" - ch2 = Parameter.create(**ch) - self.insertChild(ptr, ch2) - foundChilds.add(ch2) - - ptr += 1 - - if removeChildren: - for ch in self.childs[:]: - if ch not in foundChilds: - #print " remove:", ch - self.removeChild(ch) - finally: - if blockSignals: - self.unblockTreeChangeSignal() - - - - def defaultValue(self): - """Return the default value for this parameter.""" - return self.opts['default'] - - def setDefault(self, val): - """Set the default value for this parameter.""" - if self.opts['default'] == val: - return - self.opts['default'] = val - self.sigDefaultChanged.emit(self, val) - - def setToDefault(self): - """Set this parameter's value to the default.""" - if self.hasDefault(): - self.setValue(self.defaultValue()) - - def hasDefault(self): - """Returns True if this parameter has a default value.""" - return 'default' in self.opts - - def valueIsDefault(self): - """Returns True if this parameter's value is equal to the default value.""" - return self.value() == self.defaultValue() - - def setLimits(self, limits): - """Set limits on the acceptable values for this parameter. - The format of limits depends on the type of the parameter and - some parameters do not make use of limits at all.""" - if 'limits' in self.opts and self.opts['limits'] == limits: - return - self.opts['limits'] = limits - self.sigLimitsChanged.emit(self, limits) - return limits - - def writable(self): - """ - Returns True if this parameter's value can be changed by the user. - Note that the value of the parameter can *always* be changed by - calling setValue(). - """ - return not self.opts.get('readonly', False) - - def setWritable(self, writable=True): - """Set whether this Parameter should be editable by the user. (This is - exactly the opposite of setReadonly).""" - self.setOpts(readonly=not writable) - - def setReadonly(self, readonly=True): - """Set whether this Parameter's value may be edited by the user.""" - self.setOpts(readonly=readonly) - - def setOpts(self, **opts): - """ - Set any arbitrary options on this parameter. - The exact behavior of this function will depend on the parameter type, but - most parameters will accept a common set of options: value, name, limits, - default, readonly, removable, renamable, visible, enabled, and expanded. - - See :func:`Parameter.__init__ ` - for more information on default options. - """ - changed = OrderedDict() - for k in opts: - if k == 'value': - self.setValue(opts[k]) - elif k == 'name': - self.setName(opts[k]) - elif k == 'limits': - self.setLimits(opts[k]) - elif k == 'default': - self.setDefault(opts[k]) - elif k not in self.opts or self.opts[k] != opts[k]: - self.opts[k] = opts[k] - changed[k] = opts[k] - - if len(changed) > 0: - self.sigOptionsChanged.emit(self, changed) - - def emitStateChanged(self, changeDesc, data): - ## Emits stateChanged signal and - ## requests emission of new treeStateChanged signal - self.sigStateChanged.emit(self, changeDesc, data) - #self.treeStateChanged(self, changeDesc, data) - self.treeStateChanges.append((self, changeDesc, data)) - self.emitTreeChanges() - - def makeTreeItem(self, depth): - """ - Return a TreeWidgetItem suitable for displaying/controlling the content of - this parameter. This is called automatically when a ParameterTree attempts - to display this Parameter. - Most subclasses will want to override this function. - """ - if hasattr(self, 'itemClass'): - #print "Param:", self, "Make item from itemClass:", self.itemClass - return self.itemClass(self, depth) - else: - return ParameterItem(self, depth=depth) - - - def addChild(self, child): - """Add another parameter to the end of this parameter's child list.""" - return self.insertChild(len(self.childs), child) - - def addChildren(self, children): - ## If children was specified as dict, then assume keys are the names. - if isinstance(children, dict): - ch2 = [] - for name, opts in children.items(): - if isinstance(opts, dict) and 'name' not in opts: - opts = opts.copy() - opts['name'] = name - ch2.append(opts) - children = ch2 - - for chOpts in children: - #print self, "Add child:", type(chOpts), id(chOpts) - self.addChild(chOpts) - - - def insertChild(self, pos, child): - """ - Insert a new child at pos. - If pos is a Parameter, then insert at the position of that Parameter. - If child is a dict, then a parameter is constructed using - :func:`Parameter.create `. - """ - if isinstance(child, dict): - child = Parameter.create(**child) - - name = child.name() - if name in self.names and child is not self.names[name]: - if child.opts.get('autoIncrementName', False): - name = self.incrementName(name) - child.setName(name) - else: - raise Exception("Already have child named %s" % str(name)) - if isinstance(pos, Parameter): - pos = self.childs.index(pos) - - with self.treeChangeBlocker(): - if child.parent() is not None: - child.remove() - - self.names[name] = child - self.childs.insert(pos, child) - - child.parentChanged(self) - self.sigChildAdded.emit(self, child, pos) - child.sigTreeStateChanged.connect(self.treeStateChanged) - return child - - def removeChild(self, child): - """Remove a child parameter.""" - name = child.name() - if name not in self.names or self.names[name] is not child: - raise Exception("Parameter %s is not my child; can't remove." % str(child)) - del self.names[name] - self.childs.pop(self.childs.index(child)) - child.parentChanged(None) - self.sigChildRemoved.emit(self, child) - try: - child.sigTreeStateChanged.disconnect(self.treeStateChanged) - except TypeError: ## already disconnected - pass - - def clearChildren(self): - """Remove all child parameters.""" - for ch in self.childs[:]: - self.removeChild(ch) - - def children(self): - """Return a list of this parameter's children. - Warning: this overrides QObject.children - """ - return self.childs[:] - - def hasChildren(self): - """Return True if this Parameter has children.""" - return len(self.childs) > 0 - - def parentChanged(self, parent): - """This method is called when the parameter's parent has changed. - It may be useful to extend this method in subclasses.""" - self._parent = parent - self.sigParentChanged.emit(self, parent) - - def parent(self): - """Return the parent of this parameter.""" - return self._parent - - def remove(self): - """Remove this parameter from its parent's child list""" - parent = self.parent() - if parent is None: - raise Exception("Cannot remove; no parent.") - parent.removeChild(self) - - def incrementName(self, name): - ## return an unused name by adding a number to the name given - base, num = re.match('(.*)(\d*)', name).groups() - numLen = len(num) - if numLen == 0: - num = 2 - numLen = 1 - else: - num = int(num) - while True: - newName = base + ("%%0%dd"%numLen) % num - if newName not in self.names: - return newName - num += 1 - - def __iter__(self): - for ch in self.childs: - yield ch - - def __getitem__(self, names): - """Get the value of a child parameter. The name may also be a tuple giving - the path to a sub-parameter:: - - value = param[('child', 'grandchild')] - """ - if not isinstance(names, tuple): - names = (names,) - return self.param(*names).value() - - def __setitem__(self, names, value): - """Set the value of a child parameter. The name may also be a tuple giving - the path to a sub-parameter:: - - param[('child', 'grandchild')] = value - """ - if isinstance(names, basestring): - names = (names,) - return self.param(*names).setValue(value) - - def param(self, *names): - """Return a child parameter. - Accepts the name of the child or a tuple (path, to, child)""" - try: - param = self.names[names[0]] - except KeyError: - raise Exception("Parameter %s has no child named %s" % (self.name(), names[0])) - - if len(names) > 1: - return param.param(*names[1:]) - else: - return param - - def __repr__(self): - return "<%s '%s' at 0x%x>" % (self.__class__.__name__, self.name(), id(self)) - - def __getattr__(self, attr): - ## Leaving this undocumented because I might like to remove it in the future.. - #print type(self), attr - - if 'names' not in self.__dict__: - raise AttributeError(attr) - if attr in self.names: - import traceback - traceback.print_stack() - print("Warning: Use of Parameter.subParam is deprecated. Use Parameter.param(name) instead.") - return self.param(attr) - else: - raise AttributeError(attr) - - def _renameChild(self, child, name): - ## Only to be called from Parameter.rename - if name in self.names: - return child.name() - self.names[name] = child - del self.names[child.name()] - return name - - def registerItem(self, item): - self.items[item] = None - - def hide(self): - """Hide this parameter. It and its children will no longer be visible in any ParameterTree - widgets it is connected to.""" - self.show(False) - - def show(self, s=True): - """Show this parameter. """ - self.opts['visible'] = s - self.sigOptionsChanged.emit(self, {'visible': s}) - - - def treeChangeBlocker(self): - """ - Return an object that can be used to temporarily block and accumulate - sigTreeStateChanged signals. This is meant to be used when numerous changes are - about to be made to the tree and only one change signal should be - emitted at the end. - - Example:: - - with param.treeChangeBlocker(): - param.addChild(...) - param.removeChild(...) - param.setValue(...) - """ - return SignalBlocker(self.blockTreeChangeSignal, self.unblockTreeChangeSignal) - - def blockTreeChangeSignal(self): - """ - Used to temporarily block and accumulate tree change signals. - *You must remember to unblock*, so it is advisable to use treeChangeBlocker() instead. - """ - self.blockTreeChangeEmit += 1 - - def unblockTreeChangeSignal(self): - """Unblocks enission of sigTreeStateChanged and flushes the changes out through a single signal.""" - self.blockTreeChangeEmit -= 1 - self.emitTreeChanges() - - - def treeStateChanged(self, param, changes): - """ - Called when the state of any sub-parameter has changed. - - ========== ================================================================ - Arguments: - param The immediate child whose tree state has changed. - note that the change may have originated from a grandchild. - changes List of tuples describing all changes that have been made - in this event: (param, changeDescr, data) - ========== ================================================================ - - This function can be extended to react to tree state changes. - """ - self.treeStateChanges.extend(changes) - self.emitTreeChanges() - - def emitTreeChanges(self): - if self.blockTreeChangeEmit == 0: - changes = self.treeStateChanges - self.treeStateChanges = [] - self.sigTreeStateChanged.emit(self, changes) - - -class SignalBlocker(object): - def __init__(self, enterFn, exitFn): - self.enterFn = enterFn - self.exitFn = exitFn - - def __enter__(self): - self.enterFn() - - def __exit__(self, exc_type, exc_value, tb): - self.exitFn() - - - diff --git a/pyqtgraph/parametertree/ParameterItem.py b/pyqtgraph/parametertree/ParameterItem.py deleted file mode 100644 index 46499fd3..00000000 --- a/pyqtgraph/parametertree/ParameterItem.py +++ /dev/null @@ -1,165 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -import os, weakref, re - -class ParameterItem(QtGui.QTreeWidgetItem): - """ - Abstract ParameterTree item. - Used to represent the state of a Parameter from within a ParameterTree. - - - Sets first column of item to name - - generates context menu if item is renamable or removable - - handles child added / removed events - - provides virtual functions for handling changes from parameter - - For more ParameterItem types, see ParameterTree.parameterTypes module. - """ - - def __init__(self, param, depth=0): - QtGui.QTreeWidgetItem.__init__(self, [param.name(), '']) - - self.param = param - self.param.registerItem(self) ## let parameter know this item is connected to it (for debugging) - self.depth = depth - - param.sigValueChanged.connect(self.valueChanged) - param.sigChildAdded.connect(self.childAdded) - param.sigChildRemoved.connect(self.childRemoved) - param.sigNameChanged.connect(self.nameChanged) - param.sigLimitsChanged.connect(self.limitsChanged) - param.sigDefaultChanged.connect(self.defaultChanged) - param.sigOptionsChanged.connect(self.optsChanged) - param.sigParentChanged.connect(self.parentChanged) - - - opts = param.opts - - ## Generate context menu for renaming/removing parameter - self.contextMenu = QtGui.QMenu() - self.contextMenu.addSeparator() - flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - if opts.get('renamable', False): - flags |= QtCore.Qt.ItemIsEditable - self.contextMenu.addAction('Rename').triggered.connect(self.editName) - if opts.get('removable', False): - self.contextMenu.addAction("Remove").triggered.connect(self.requestRemove) - - ## handle movable / dropEnabled options - if opts.get('movable', False): - flags |= QtCore.Qt.ItemIsDragEnabled - if opts.get('dropEnabled', False): - flags |= QtCore.Qt.ItemIsDropEnabled - self.setFlags(flags) - - ## flag used internally during name editing - self.ignoreNameColumnChange = False - - - def valueChanged(self, param, val): - ## called when the parameter's value has changed - pass - - def isFocusable(self): - """Return True if this item should be included in the tab-focus order""" - return False - - def setFocus(self): - """Give input focus to this item. - Can be reimplemented to display editor widgets, etc. - """ - pass - - def focusNext(self, forward=True): - """Give focus to the next (or previous) focusable item in the parameter tree""" - self.treeWidget().focusNext(self, forward=forward) - - - def treeWidgetChanged(self): - """Called when this item is added or removed from a tree. - Expansion, visibility, and column widgets must all be configured AFTER - the item is added to a tree, not during __init__. - """ - self.setHidden(not self.param.opts.get('visible', True)) - self.setExpanded(self.param.opts.get('expanded', True)) - - def childAdded(self, param, child, pos): - item = child.makeTreeItem(depth=self.depth+1) - self.insertChild(pos, item) - item.treeWidgetChanged() - - for i, ch in enumerate(child): - item.childAdded(child, ch, i) - - def childRemoved(self, param, child): - for i in range(self.childCount()): - item = self.child(i) - if item.param is child: - self.takeChild(i) - break - - def parentChanged(self, param, parent): - ## called when the parameter's parent has changed. - pass - - def contextMenuEvent(self, ev): - if not self.param.opts.get('removable', False) and not self.param.opts.get('renamable', False): - return - - self.contextMenu.popup(ev.globalPos()) - - def columnChangedEvent(self, col): - """Called when the text in a column has been edited. - By default, we only use changes to column 0 to rename the parameter. - """ - if col == 0: - if self.ignoreNameColumnChange: - return - try: - newName = self.param.setName(str(self.text(col))) - except: - self.setText(0, self.param.name()) - raise - - try: - self.ignoreNameColumnChange = True - self.nameChanged(self, newName) ## If the parameter rejects the name change, we need to set it back. - finally: - self.ignoreNameColumnChange = False - - def nameChanged(self, param, name): - ## called when the parameter's name has changed. - self.setText(0, name) - - def limitsChanged(self, param, limits): - """Called when the parameter's limits have changed""" - pass - - def defaultChanged(self, param, default): - """Called when the parameter's default value has changed""" - pass - - def optsChanged(self, param, opts): - """Called when any options are changed that are not - name, value, default, or limits""" - #print opts - if 'visible' in opts: - self.setHidden(not opts['visible']) - - def editName(self): - self.treeWidget().editItem(self, 0) - - def selected(self, sel): - """Called when this item has been selected (sel=True) OR deselected (sel=False)""" - pass - - def requestRemove(self): - ## called when remove is selected from the context menu. - ## we need to delay removal until the action is complete - ## since destroying the menu in mid-action will cause a crash. - QtCore.QTimer.singleShot(0, self.param.remove) - - ## for python 3 support, we need to redefine hash and eq methods. - def __hash__(self): - return id(self) - - def __eq__(self, x): - return x is self diff --git a/pyqtgraph/parametertree/ParameterTree.py b/pyqtgraph/parametertree/ParameterTree.py deleted file mode 100644 index 866875e5..00000000 --- a/pyqtgraph/parametertree/ParameterTree.py +++ /dev/null @@ -1,119 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui -from pyqtgraph.widgets.TreeWidget import TreeWidget -import os, weakref, re -from .ParameterItem import ParameterItem -#import functions as fn - - - -class ParameterTree(TreeWidget): - """Widget used to display or control data from a ParameterSet""" - - def __init__(self, parent=None, showHeader=True): - TreeWidget.__init__(self, parent) - self.setVerticalScrollMode(self.ScrollPerPixel) - self.setHorizontalScrollMode(self.ScrollPerPixel) - self.setAnimated(False) - self.setColumnCount(2) - self.setHeaderLabels(["Parameter", "Value"]) - self.setAlternatingRowColors(True) - self.paramSet = None - self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents) - self.setHeaderHidden(not showHeader) - self.itemChanged.connect(self.itemChangedEvent) - self.lastSel = None - self.setRootIsDecorated(False) - - def setParameters(self, param, showTop=True): - self.clear() - self.addParameters(param, showTop=showTop) - - def addParameters(self, param, root=None, depth=0, showTop=True): - item = param.makeTreeItem(depth=depth) - if root is None: - root = self.invisibleRootItem() - ## Hide top-level item - if not showTop: - item.setText(0, '') - item.setSizeHint(0, QtCore.QSize(1,1)) - item.setSizeHint(1, QtCore.QSize(1,1)) - depth -= 1 - root.addChild(item) - item.treeWidgetChanged() - - for ch in param: - self.addParameters(ch, root=item, depth=depth+1) - - def clear(self): - self.invisibleRootItem().takeChildren() - - - def focusNext(self, item, forward=True): - ## Give input focus to the next (or previous) item after 'item' - while True: - parent = item.parent() - if parent is None: - return - nextItem = self.nextFocusableChild(parent, item, forward=forward) - if nextItem is not None: - nextItem.setFocus() - self.setCurrentItem(nextItem) - return - item = parent - - def focusPrevious(self, item): - self.focusNext(item, forward=False) - - def nextFocusableChild(self, root, startItem=None, forward=True): - if startItem is None: - if forward: - index = 0 - else: - index = root.childCount()-1 - else: - if forward: - index = root.indexOfChild(startItem) + 1 - else: - index = root.indexOfChild(startItem) - 1 - - if forward: - inds = list(range(index, root.childCount())) - else: - inds = list(range(index, -1, -1)) - - for i in inds: - item = root.child(i) - if hasattr(item, 'isFocusable') and item.isFocusable(): - return item - else: - item = self.nextFocusableChild(item, forward=forward) - if item is not None: - return item - return None - - def contextMenuEvent(self, ev): - item = self.currentItem() - if hasattr(item, 'contextMenuEvent'): - item.contextMenuEvent(ev) - - def itemChangedEvent(self, item, col): - if hasattr(item, 'columnChangedEvent'): - item.columnChangedEvent(col) - - def selectionChanged(self, *args): - sel = self.selectedItems() - if len(sel) != 1: - sel = None - if self.lastSel is not None and isinstance(self.lastSel, ParameterItem): - self.lastSel.selected(False) - if sel is None: - self.lastSel = None - return - self.lastSel = sel[0] - if hasattr(sel[0], 'selected'): - sel[0].selected(True) - return TreeWidget.selectionChanged(self, *args) - - def wheelEvent(self, ev): - self.clearSelection() - return TreeWidget.wheelEvent(self, ev) diff --git a/pyqtgraph/parametertree/__init__.py b/pyqtgraph/parametertree/__init__.py deleted file mode 100644 index acdb7a37..00000000 --- a/pyqtgraph/parametertree/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .Parameter import Parameter, registerParameterType -from .ParameterTree import ParameterTree -from .ParameterItem import ParameterItem - -from . import parameterTypes as types \ No newline at end of file diff --git a/pyqtgraph/parametertree/parameterTypes.py b/pyqtgraph/parametertree/parameterTypes.py deleted file mode 100644 index 3300171f..00000000 --- a/pyqtgraph/parametertree/parameterTypes.py +++ /dev/null @@ -1,648 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui -from pyqtgraph.python2_3 import asUnicode -from .Parameter import Parameter, registerParameterType -from .ParameterItem import ParameterItem -from pyqtgraph.widgets.SpinBox import SpinBox -from pyqtgraph.widgets.ColorButton import ColorButton -#from pyqtgraph.widgets.GradientWidget import GradientWidget ## creates import loop -import pyqtgraph as pg -import pyqtgraph.pixmaps as pixmaps -import os -from pyqtgraph.pgcollections import OrderedDict - -class WidgetParameterItem(ParameterItem): - """ - ParameterTree item with: - - * label in second column for displaying value - * simple widget for editing value (displayed instead of label when item is selected) - * button that resets value to default - - ================= ============================================================= - Registered Types: - int Displays a :class:`SpinBox ` in integer - mode. - float Displays a :class:`SpinBox `. - bool Displays a QCheckBox - str Displays a QLineEdit - color Displays a :class:`ColorButton ` - colormap Displays a :class:`GradientWidget ` - ================= ============================================================= - - This class can be subclassed by overriding makeWidget() to provide a custom widget. - """ - def __init__(self, param, depth): - ParameterItem.__init__(self, param, depth) - - self.hideWidget = True ## hide edit widget, replace with label when not selected - ## set this to False to keep the editor widget always visible - - - ## build widget into column 1 with a display label and default button. - w = self.makeWidget() - self.widget = w - self.eventProxy = EventProxy(w, self.widgetEventFilter) - - opts = self.param.opts - if 'tip' in opts: - w.setToolTip(opts['tip']) - - self.defaultBtn = QtGui.QPushButton() - self.defaultBtn.setFixedWidth(20) - self.defaultBtn.setFixedHeight(20) - modDir = os.path.dirname(__file__) - self.defaultBtn.setIcon(QtGui.QIcon(pixmaps.getPixmap('default'))) - self.defaultBtn.clicked.connect(self.defaultClicked) - - self.displayLabel = QtGui.QLabel() - - layout = QtGui.QHBoxLayout() - layout.setContentsMargins(0, 0, 0, 0) - layout.setSpacing(2) - layout.addWidget(w) - layout.addWidget(self.displayLabel) - layout.addWidget(self.defaultBtn) - self.layoutWidget = QtGui.QWidget() - self.layoutWidget.setLayout(layout) - - if w.sigChanged is not None: - w.sigChanged.connect(self.widgetValueChanged) - - if hasattr(w, 'sigChanging'): - w.sigChanging.connect(self.widgetValueChanging) - - ## update value shown in widget. - if opts.get('value', None) is not None: - self.valueChanged(self, opts['value'], force=True) - else: - ## no starting value was given; use whatever the widget has - self.widgetValueChanged() - - - def makeWidget(self): - """ - Return a single widget that should be placed in the second tree column. - The widget must be given three attributes: - - ========== ============================================================ - sigChanged a signal that is emitted when the widget's value is changed - value a function that returns the value - setValue a function that sets the value - ========== ============================================================ - - This is a good function to override in subclasses. - """ - opts = self.param.opts - t = opts['type'] - if t == 'int': - defs = { - 'value': 0, 'min': None, 'max': None, 'int': True, - 'step': 1.0, 'minStep': 1.0, 'dec': False, - 'siPrefix': False, 'suffix': '' - } - defs.update(opts) - if 'limits' in opts: - defs['bounds'] = opts['limits'] - w = SpinBox() - w.setOpts(**defs) - w.sigChanged = w.sigValueChanged - w.sigChanging = w.sigValueChanging - elif t == 'float': - defs = { - 'value': 0, 'min': None, 'max': None, - 'step': 1.0, 'dec': False, - 'siPrefix': False, 'suffix': '' - } - defs.update(opts) - if 'limits' in opts: - defs['bounds'] = opts['limits'] - w = SpinBox() - w.setOpts(**defs) - w.sigChanged = w.sigValueChanged - w.sigChanging = w.sigValueChanging - elif t == 'bool': - w = QtGui.QCheckBox() - w.sigChanged = w.toggled - w.value = w.isChecked - w.setValue = w.setChecked - self.hideWidget = False - elif t == 'str': - w = QtGui.QLineEdit() - w.sigChanged = w.editingFinished - w.value = lambda: asUnicode(w.text()) - w.setValue = lambda v: w.setText(asUnicode(v)) - w.sigChanging = w.textChanged - elif t == 'color': - w = ColorButton() - w.sigChanged = w.sigColorChanged - w.sigChanging = w.sigColorChanging - w.value = w.color - w.setValue = w.setColor - self.hideWidget = False - w.setFlat(True) - elif t == 'colormap': - from pyqtgraph.widgets.GradientWidget import GradientWidget ## need this here to avoid import loop - w = GradientWidget(orientation='bottom') - w.sigChanged = w.sigGradientChangeFinished - w.sigChanging = w.sigGradientChanged - w.value = w.colorMap - w.setValue = w.setColorMap - self.hideWidget = False - else: - raise Exception("Unknown type '%s'" % asUnicode(t)) - return w - - def widgetEventFilter(self, obj, ev): - ## filter widget's events - ## catch TAB to change focus - ## catch focusOut to hide editor - if ev.type() == ev.KeyPress: - if ev.key() == QtCore.Qt.Key_Tab: - self.focusNext(forward=True) - return True ## don't let anyone else see this event - elif ev.key() == QtCore.Qt.Key_Backtab: - self.focusNext(forward=False) - return True ## don't let anyone else see this event - - #elif ev.type() == ev.FocusOut: - #self.hideEditor() - return False - - def setFocus(self): - self.showEditor() - - def isFocusable(self): - return self.param.writable() - - def valueChanged(self, param, val, force=False): - ## called when the parameter's value has changed - ParameterItem.valueChanged(self, param, val) - self.widget.sigChanged.disconnect(self.widgetValueChanged) - try: - if force or val != self.widget.value(): - self.widget.setValue(val) - self.updateDisplayLabel(val) ## always make sure label is updated, even if values match! - finally: - self.widget.sigChanged.connect(self.widgetValueChanged) - self.updateDefaultBtn() - - def updateDefaultBtn(self): - ## enable/disable default btn - self.defaultBtn.setEnabled(not self.param.valueIsDefault() and self.param.writable()) - - def updateDisplayLabel(self, value=None): - """Update the display label to reflect the value of the parameter.""" - if value is None: - value = self.param.value() - opts = self.param.opts - if isinstance(self.widget, QtGui.QAbstractSpinBox): - text = asUnicode(self.widget.lineEdit().text()) - elif isinstance(self.widget, QtGui.QComboBox): - text = self.widget.currentText() - else: - text = asUnicode(value) - self.displayLabel.setText(text) - - def widgetValueChanged(self): - ## called when the widget's value has been changed by the user - val = self.widget.value() - newVal = self.param.setValue(val) - - def widgetValueChanging(self): - """ - Called when the widget's value is changing, but not finalized. - For example: editing text before pressing enter or changing focus. - """ - pass - - def selected(self, sel): - """Called when this item has been selected (sel=True) OR deselected (sel=False)""" - ParameterItem.selected(self, sel) - - if self.widget is None: - return - if sel and self.param.writable(): - self.showEditor() - elif self.hideWidget: - self.hideEditor() - - def showEditor(self): - self.widget.show() - self.displayLabel.hide() - self.widget.setFocus(QtCore.Qt.OtherFocusReason) - - def hideEditor(self): - self.widget.hide() - self.displayLabel.show() - - def limitsChanged(self, param, limits): - """Called when the parameter's limits have changed""" - ParameterItem.limitsChanged(self, param, limits) - - t = self.param.opts['type'] - if t == 'int' or t == 'float': - self.widget.setOpts(bounds=limits) - else: - return ## don't know what to do with any other types.. - - def defaultChanged(self, param, value): - self.updateDefaultBtn() - - def treeWidgetChanged(self): - """Called when this item is added or removed from a tree.""" - ParameterItem.treeWidgetChanged(self) - - ## add all widgets for this item into the tree - if self.widget is not None: - tree = self.treeWidget() - if tree is None: - return - tree.setItemWidget(self, 1, self.layoutWidget) - self.displayLabel.hide() - self.selected(False) - - def defaultClicked(self): - self.param.setToDefault() - - def optsChanged(self, param, opts): - """Called when any options are changed that are not - name, value, default, or limits""" - #print "opts changed:", opts - ParameterItem.optsChanged(self, param, opts) - - if 'readonly' in opts: - self.updateDefaultBtn() - - ## If widget is a SpinBox, pass options straight through - if isinstance(self.widget, SpinBox): - if 'units' in opts and 'suffix' not in opts: - opts['suffix'] = opts['units'] - self.widget.setOpts(**opts) - self.updateDisplayLabel() - -class EventProxy(QtCore.QObject): - def __init__(self, qobj, callback): - QtCore.QObject.__init__(self) - self.callback = callback - qobj.installEventFilter(self) - - def eventFilter(self, obj, ev): - return self.callback(obj, ev) - - - - -class SimpleParameter(Parameter): - itemClass = WidgetParameterItem - - def __init__(self, *args, **kargs): - Parameter.__init__(self, *args, **kargs) - - ## override a few methods for color parameters - if self.opts['type'] == 'color': - self.value = self.colorValue - self.saveState = self.saveColorState - - def colorValue(self): - return pg.mkColor(Parameter.value(self)) - - def saveColorState(self): - state = Parameter.saveState(self) - state['value'] = pg.colorTuple(self.value()) - return state - - -registerParameterType('int', SimpleParameter, override=True) -registerParameterType('float', SimpleParameter, override=True) -registerParameterType('bool', SimpleParameter, override=True) -registerParameterType('str', SimpleParameter, override=True) -registerParameterType('color', SimpleParameter, override=True) -registerParameterType('colormap', SimpleParameter, override=True) - - - - -class GroupParameterItem(ParameterItem): - """ - Group parameters are used mainly as a generic parent item that holds (and groups!) a set - of child parameters. It also provides a simple mechanism for displaying a button or combo - that can be used to add new parameters to the group. - """ - def __init__(self, param, depth): - ParameterItem.__init__(self, param, depth) - self.updateDepth(depth) - - self.addItem = None - if 'addText' in param.opts: - addText = param.opts['addText'] - if 'addList' in param.opts: - self.addWidget = QtGui.QComboBox() - self.addWidget.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) - self.updateAddList() - self.addWidget.currentIndexChanged.connect(self.addChanged) - else: - self.addWidget = QtGui.QPushButton(addText) - self.addWidget.clicked.connect(self.addClicked) - w = QtGui.QWidget() - l = QtGui.QHBoxLayout() - l.setContentsMargins(0,0,0,0) - w.setLayout(l) - l.addWidget(self.addWidget) - l.addStretch() - #l.addItem(QtGui.QSpacerItem(200, 10, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)) - self.addWidgetBox = w - self.addItem = QtGui.QTreeWidgetItem([]) - self.addItem.setFlags(QtCore.Qt.ItemIsEnabled) - ParameterItem.addChild(self, self.addItem) - - def updateDepth(self, depth): - ## Change item's appearance based on its depth in the tree - ## This allows highest-level groups to be displayed more prominently. - if depth == 0: - for c in [0,1]: - self.setBackground(c, QtGui.QBrush(QtGui.QColor(100,100,100))) - self.setForeground(c, QtGui.QBrush(QtGui.QColor(220,220,255))) - font = self.font(c) - font.setBold(True) - font.setPointSize(font.pointSize()+1) - self.setFont(c, font) - self.setSizeHint(0, QtCore.QSize(0, 25)) - else: - for c in [0,1]: - self.setBackground(c, QtGui.QBrush(QtGui.QColor(220,220,220))) - font = self.font(c) - font.setBold(True) - #font.setPointSize(font.pointSize()+1) - self.setFont(c, font) - self.setSizeHint(0, QtCore.QSize(0, 20)) - - def addClicked(self): - """Called when "add new" button is clicked - The parameter MUST have an 'addNew' method defined. - """ - self.param.addNew() - - def addChanged(self): - """Called when "add new" combo is changed - The parameter MUST have an 'addNew' method defined. - """ - if self.addWidget.currentIndex() == 0: - return - typ = asUnicode(self.addWidget.currentText()) - self.param.addNew(typ) - self.addWidget.setCurrentIndex(0) - - def treeWidgetChanged(self): - ParameterItem.treeWidgetChanged(self) - self.treeWidget().setFirstItemColumnSpanned(self, True) - if self.addItem is not None: - self.treeWidget().setItemWidget(self.addItem, 0, self.addWidgetBox) - self.treeWidget().setFirstItemColumnSpanned(self.addItem, True) - - def addChild(self, child): ## make sure added childs are actually inserted before add btn - if self.addItem is not None: - ParameterItem.insertChild(self, self.childCount()-1, child) - else: - ParameterItem.addChild(self, child) - - def optsChanged(self, param, changed): - if 'addList' in changed: - self.updateAddList() - - def updateAddList(self): - self.addWidget.blockSignals(True) - try: - self.addWidget.clear() - self.addWidget.addItem(self.param.opts['addText']) - for t in self.param.opts['addList']: - self.addWidget.addItem(t) - finally: - self.addWidget.blockSignals(False) - -class GroupParameter(Parameter): - """ - Group parameters are used mainly as a generic parent item that holds (and groups!) a set - of child parameters. - - It also provides a simple mechanism for displaying a button or combo - that can be used to add new parameters to the group. To enable this, the group - must be initialized with the 'addText' option (the text will be displayed on - a button which, when clicked, will cause addNew() to be called). If the 'addList' - option is specified as well, then a dropdown-list of addable items will be displayed - instead of a button. - """ - itemClass = GroupParameterItem - - def addNew(self, typ=None): - """ - This method is called when the user has requested to add a new item to the group. - """ - raise Exception("Must override this function in subclass.") - - def setAddList(self, vals): - """Change the list of options available for the user to add to the group.""" - self.setOpts(addList=vals) - - - -registerParameterType('group', GroupParameter, override=True) - - - - - -class ListParameterItem(WidgetParameterItem): - """ - WidgetParameterItem subclass providing comboBox that lets the user select from a list of options. - - """ - def __init__(self, param, depth): - self.targetValue = None - WidgetParameterItem.__init__(self, param, depth) - - - def makeWidget(self): - opts = self.param.opts - t = opts['type'] - w = QtGui.QComboBox() - w.setMaximumHeight(20) ## set to match height of spin box and line edit - w.sigChanged = w.currentIndexChanged - w.value = self.value - w.setValue = self.setValue - self.widget = w ## needs to be set before limits are changed - self.limitsChanged(self.param, self.param.opts['limits']) - if len(self.forward) > 0: - self.setValue(self.param.value()) - return w - - def value(self): - key = asUnicode(self.widget.currentText()) - - return self.forward.get(key, None) - - def setValue(self, val): - self.targetValue = val - if val not in self.reverse[0]: - self.widget.setCurrentIndex(0) - else: - key = self.reverse[1][self.reverse[0].index(val)] - ind = self.widget.findText(key) - self.widget.setCurrentIndex(ind) - - def limitsChanged(self, param, limits): - # set up forward / reverse mappings for name:value - - if len(limits) == 0: - limits = [''] ## Can never have an empty list--there is always at least a singhe blank item. - - self.forward, self.reverse = ListParameter.mapping(limits) - try: - self.widget.blockSignals(True) - val = self.targetValue #asUnicode(self.widget.currentText()) - - self.widget.clear() - for k in self.forward: - self.widget.addItem(k) - if k == val: - self.widget.setCurrentIndex(self.widget.count()-1) - self.updateDisplayLabel() - finally: - self.widget.blockSignals(False) - - - -class ListParameter(Parameter): - itemClass = ListParameterItem - - def __init__(self, **opts): - self.forward = OrderedDict() ## {name: value, ...} - self.reverse = ([], []) ## ([value, ...], [name, ...]) - - ## Parameter uses 'limits' option to define the set of allowed values - if 'values' in opts: - opts['limits'] = opts['values'] - if opts.get('limits', None) is None: - opts['limits'] = [] - Parameter.__init__(self, **opts) - self.setLimits(opts['limits']) - - def setLimits(self, limits): - self.forward, self.reverse = self.mapping(limits) - - Parameter.setLimits(self, limits) - #print self.name(), self.value(), limits - if len(self.reverse) > 0 and self.value() not in self.reverse[0]: - self.setValue(self.reverse[0][0]) - - #def addItem(self, name, value=None): - #if name in self.forward: - #raise Exception("Name '%s' is already in use for this parameter" % name) - #limits = self.opts['limits'] - #if isinstance(limits, dict): - #limits = limits.copy() - #limits[name] = value - #self.setLimits(limits) - #else: - #if value is not None: - #raise Exception ## raise exception or convert to dict? - #limits = limits[:] - #limits.append(name) - ## what if limits == None? - - @staticmethod - def mapping(limits): - ## Return forward and reverse mapping objects given a limit specification - forward = OrderedDict() ## {name: value, ...} - reverse = ([], []) ## ([value, ...], [name, ...]) - if isinstance(limits, dict): - for k, v in limits.items(): - forward[k] = v - reverse[0].append(v) - reverse[1].append(k) - else: - for v in limits: - n = asUnicode(v) - forward[n] = v - reverse[0].append(v) - reverse[1].append(n) - return forward, reverse - -registerParameterType('list', ListParameter, override=True) - - - -class ActionParameterItem(ParameterItem): - def __init__(self, param, depth): - ParameterItem.__init__(self, param, depth) - self.layoutWidget = QtGui.QWidget() - self.layout = QtGui.QHBoxLayout() - self.layoutWidget.setLayout(self.layout) - self.button = QtGui.QPushButton(param.name()) - #self.layout.addSpacing(100) - self.layout.addWidget(self.button) - self.layout.addStretch() - self.button.clicked.connect(self.buttonClicked) - param.sigNameChanged.connect(self.paramRenamed) - self.setText(0, '') - - def treeWidgetChanged(self): - ParameterItem.treeWidgetChanged(self) - tree = self.treeWidget() - if tree is None: - return - - tree.setFirstItemColumnSpanned(self, True) - tree.setItemWidget(self, 0, self.layoutWidget) - - def paramRenamed(self, param, name): - self.button.setText(name) - - def buttonClicked(self): - self.param.activate() - -class ActionParameter(Parameter): - """Used for displaying a button within the tree.""" - itemClass = ActionParameterItem - sigActivated = QtCore.Signal(object) - - def activate(self): - self.sigActivated.emit(self) - self.emitStateChanged('activated', None) - -registerParameterType('action', ActionParameter, override=True) - - - -class TextParameterItem(WidgetParameterItem): - def __init__(self, param, depth): - WidgetParameterItem.__init__(self, param, depth) - self.hideWidget = False - self.subItem = QtGui.QTreeWidgetItem() - self.addChild(self.subItem) - - def treeWidgetChanged(self): - ## TODO: fix so that superclass method can be called - ## (WidgetParameter should just natively support this style) - #WidgetParameterItem.treeWidgetChanged(self) - self.treeWidget().setFirstItemColumnSpanned(self.subItem, True) - self.treeWidget().setItemWidget(self.subItem, 0, self.textBox) - - # for now, these are copied from ParameterItem.treeWidgetChanged - self.setHidden(not self.param.opts.get('visible', True)) - self.setExpanded(self.param.opts.get('expanded', True)) - - def makeWidget(self): - self.textBox = QtGui.QTextEdit() - self.textBox.setMaximumHeight(100) - self.textBox.value = lambda: str(self.textBox.toPlainText()) - self.textBox.setValue = self.textBox.setPlainText - self.textBox.sigChanged = self.textBox.textChanged - return self.textBox - -class TextParameter(Parameter): - """Editable string; displayed as large text box in the tree.""" - itemClass = TextParameterItem - - - -registerParameterType('text', TextParameter, override=True) diff --git a/pyqtgraph/pgcollections.py b/pyqtgraph/pgcollections.py deleted file mode 100644 index 76850622..00000000 --- a/pyqtgraph/pgcollections.py +++ /dev/null @@ -1,477 +0,0 @@ -# -*- coding: utf-8 -*- -""" -advancedTypes.py - Basic data structures not included with python -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. - -Includes: - - OrderedDict - Dictionary which preserves the order of its elements - - BiDict, ReverseDict - Bi-directional dictionaries - - ThreadsafeDict, ThreadsafeList - Self-mutexed data structures -""" - -import threading, sys, copy, collections -#from debug import * - -try: - from collections import OrderedDict -except ImportError: - # fallback: try to use the ordereddict backport when using python 2.6 - from ordereddict import OrderedDict - - -class ReverseDict(dict): - """extends dict so that reverse lookups are possible by requesting the key as a list of length 1: - d = BiDict({'x': 1, 'y': 2}) - d['x'] - 1 - d[[2]] - 'y' - """ - def __init__(self, data=None): - if data is None: - data = {} - self.reverse = {} - for k in data: - self.reverse[data[k]] = k - dict.__init__(self, data) - - def __getitem__(self, item): - if type(item) is list: - return self.reverse[item[0]] - else: - return dict.__getitem__(self, item) - - def __setitem__(self, item, value): - self.reverse[value] = item - dict.__setitem__(self, item, value) - - def __deepcopy__(self, memo): - raise Exception("deepcopy not implemented") - - -class BiDict(dict): - """extends dict so that reverse lookups are possible by adding each reverse combination to the dict. - This only works if all values and keys are unique.""" - def __init__(self, data=None): - if data is None: - data = {} - dict.__init__(self) - for k in data: - self[data[k]] = k - - def __setitem__(self, item, value): - dict.__setitem__(self, item, value) - dict.__setitem__(self, value, item) - - def __deepcopy__(self, memo): - raise Exception("deepcopy not implemented") - -class ThreadsafeDict(dict): - """Extends dict so that getitem, setitem, and contains are all thread-safe. - Also adds lock/unlock functions for extended exclusive operations - Converts all sub-dicts and lists to threadsafe as well. - """ - - def __init__(self, *args, **kwargs): - self.mutex = threading.RLock() - dict.__init__(self, *args, **kwargs) - for k in self: - if type(self[k]) is dict: - self[k] = ThreadsafeDict(self[k]) - - def __getitem__(self, attr): - self.lock() - try: - val = dict.__getitem__(self, attr) - finally: - self.unlock() - return val - - def __setitem__(self, attr, val): - if type(val) is dict: - val = ThreadsafeDict(val) - self.lock() - try: - dict.__setitem__(self, attr, val) - finally: - self.unlock() - - def __contains__(self, attr): - self.lock() - try: - val = dict.__contains__(self, attr) - finally: - self.unlock() - return val - - def __len__(self): - self.lock() - try: - val = dict.__len__(self) - finally: - self.unlock() - return val - - def clear(self): - self.lock() - try: - dict.clear(self) - finally: - self.unlock() - - def lock(self): - self.mutex.acquire() - - def unlock(self): - self.mutex.release() - - def __deepcopy__(self, memo): - raise Exception("deepcopy not implemented") - -class ThreadsafeList(list): - """Extends list so that getitem, setitem, and contains are all thread-safe. - Also adds lock/unlock functions for extended exclusive operations - Converts all sub-lists and dicts to threadsafe as well. - """ - - def __init__(self, *args, **kwargs): - self.mutex = threading.RLock() - list.__init__(self, *args, **kwargs) - for k in self: - self[k] = mkThreadsafe(self[k]) - - def __getitem__(self, attr): - self.lock() - try: - val = list.__getitem__(self, attr) - finally: - self.unlock() - return val - - def __setitem__(self, attr, val): - val = makeThreadsafe(val) - self.lock() - try: - list.__setitem__(self, attr, val) - finally: - self.unlock() - - def __contains__(self, attr): - self.lock() - try: - val = list.__contains__(self, attr) - finally: - self.unlock() - return val - - def __len__(self): - self.lock() - try: - val = list.__len__(self) - finally: - self.unlock() - return val - - def lock(self): - self.mutex.acquire() - - def unlock(self): - self.mutex.release() - - def __deepcopy__(self, memo): - raise Exception("deepcopy not implemented") - - -def makeThreadsafe(obj): - if type(obj) is dict: - return ThreadsafeDict(obj) - elif type(obj) is list: - return ThreadsafeList(obj) - elif type(obj) in [str, int, float, bool, tuple]: - return obj - else: - raise Exception("Not sure how to make object of type %s thread-safe" % str(type(obj))) - - -class Locker(object): - def __init__(self, lock): - self.lock = lock - self.lock.acquire() - def __del__(self): - try: - self.lock.release() - except: - pass - -class CaselessDict(OrderedDict): - """Case-insensitive dict. Values can be set and retrieved using keys of any case. - Note that when iterating, the original case is returned for each key.""" - def __init__(self, *args): - OrderedDict.__init__(self, {}) ## requirement for the empty {} here seems to be a python bug? - self.keyMap = OrderedDict([(k.lower(), k) for k in OrderedDict.keys(self)]) - if len(args) == 0: - return - elif len(args) == 1 and isinstance(args[0], dict): - for k in args[0]: - self[k] = args[0][k] - else: - raise Exception("CaselessDict may only be instantiated with a single dict.") - - #def keys(self): - #return self.keyMap.values() - - def __setitem__(self, key, val): - kl = key.lower() - if kl in self.keyMap: - OrderedDict.__setitem__(self, self.keyMap[kl], val) - else: - OrderedDict.__setitem__(self, key, val) - self.keyMap[kl] = key - - def __getitem__(self, key): - kl = key.lower() - if kl not in self.keyMap: - raise KeyError(key) - return OrderedDict.__getitem__(self, self.keyMap[kl]) - - def __contains__(self, key): - return key.lower() in self.keyMap - - def update(self, d): - for k, v in d.iteritems(): - self[k] = v - - def copy(self): - return CaselessDict(OrderedDict.copy(self)) - - def __delitem__(self, key): - kl = key.lower() - if kl not in self.keyMap: - raise KeyError(key) - OrderedDict.__delitem__(self, self.keyMap[kl]) - del self.keyMap[kl] - - def __deepcopy__(self, memo): - raise Exception("deepcopy not implemented") - - def clear(self): - OrderedDict.clear(self) - self.keyMap.clear() - - - -class ProtectedDict(dict): - """ - A class allowing read-only 'view' of a dict. - The object can be treated like a normal dict, but will never modify the original dict it points to. - Any values accessed from the dict will also be read-only. - """ - def __init__(self, data): - self._data_ = data - - ## List of methods to directly wrap from _data_ - wrapMethods = ['_cmp_', '__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'has_key', 'iterkeys', 'keys', ] - - ## List of methods which wrap from _data_ but return protected results - protectMethods = ['__getitem__', '__iter__', 'get', 'items', 'values'] - - ## List of methods to disable - disableMethods = ['__delitem__', '__setitem__', 'clear', 'pop', 'popitem', 'setdefault', 'update'] - - - ## Template methods - def wrapMethod(methodName): - return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) - - def protectMethod(methodName): - return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) - - def error(self, *args, **kargs): - raise Exception("Can not modify read-only list.") - - - ## Directly (and explicitly) wrap some methods from _data_ - ## Many of these methods can not be intercepted using __getattribute__, so they - ## must be implemented explicitly - for methodName in wrapMethods: - locals()[methodName] = wrapMethod(methodName) - - ## Wrap some methods from _data_ with the results converted to protected objects - for methodName in protectMethods: - locals()[methodName] = protectMethod(methodName) - - ## Disable any methods that could change data in the list - for methodName in disableMethods: - locals()[methodName] = error - - - ## Add a few extra methods. - def copy(self): - raise Exception("It is not safe to copy protected dicts! (instead try deepcopy, but be careful.)") - - def itervalues(self): - for v in self._data_.itervalues(): - yield protect(v) - - def iteritems(self): - for k, v in self._data_.iteritems(): - yield (k, protect(v)) - - def deepcopy(self): - return copy.deepcopy(self._data_) - - def __deepcopy__(self, memo): - return copy.deepcopy(self._data_, memo) - - - -class ProtectedList(collections.Sequence): - """ - A class allowing read-only 'view' of a list or dict. - The object can be treated like a normal list, but will never modify the original list it points to. - Any values accessed from the list will also be read-only. - - Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. - However, doing this causes tuple(obj) to return unprotected results (importantly, this means - unpacking into function arguments will also fail) - """ - def __init__(self, data): - self._data_ = data - #self.__mro__ = (ProtectedList, object) - - ## List of methods to directly wrap from _data_ - wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] - - ## List of methods which wrap from _data_ but return protected results - protectMethods = ['__getitem__', '__getslice__', '__mul__', '__reversed__', '__rmul__'] - - ## List of methods to disable - disableMethods = ['__delitem__', '__delslice__', '__iadd__', '__imul__', '__setitem__', '__setslice__', 'append', 'extend', 'insert', 'pop', 'remove', 'reverse', 'sort'] - - - ## Template methods - def wrapMethod(methodName): - return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) - - def protectMethod(methodName): - return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) - - def error(self, *args, **kargs): - raise Exception("Can not modify read-only list.") - - - ## Directly (and explicitly) wrap some methods from _data_ - ## Many of these methods can not be intercepted using __getattribute__, so they - ## must be implemented explicitly - for methodName in wrapMethods: - locals()[methodName] = wrapMethod(methodName) - - ## Wrap some methods from _data_ with the results converted to protected objects - for methodName in protectMethods: - locals()[methodName] = protectMethod(methodName) - - ## Disable any methods that could change data in the list - for methodName in disableMethods: - locals()[methodName] = error - - - ## Add a few extra methods. - def __iter__(self): - for item in self._data_: - yield protect(item) - - - def __add__(self, op): - if isinstance(op, ProtectedList): - return protect(self._data_.__add__(op._data_)) - elif isinstance(op, list): - return protect(self._data_.__add__(op)) - else: - raise TypeError("Argument must be a list.") - - def __radd__(self, op): - if isinstance(op, ProtectedList): - return protect(op._data_.__add__(self._data_)) - elif isinstance(op, list): - return protect(op.__add__(self._data_)) - else: - raise TypeError("Argument must be a list.") - - def deepcopy(self): - return copy.deepcopy(self._data_) - - def __deepcopy__(self, memo): - return copy.deepcopy(self._data_, memo) - - def poop(self): - raise Exception("This is a list. It does not poop.") - - -class ProtectedTuple(collections.Sequence): - """ - A class allowing read-only 'view' of a tuple. - The object can be treated like a normal tuple, but its contents will be returned as protected objects. - - Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. - However, doing this causes tuple(obj) to return unprotected results (importantly, this means - unpacking into function arguments will also fail) - """ - def __init__(self, data): - self._data_ = data - - ## List of methods to directly wrap from _data_ - wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__getnewargs__', '__gt__', '__hash__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] - - ## List of methods which wrap from _data_ but return protected results - protectMethods = ['__getitem__', '__getslice__', '__iter__', '__add__', '__mul__', '__reversed__', '__rmul__'] - - - ## Template methods - def wrapMethod(methodName): - return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) - - def protectMethod(methodName): - return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) - - - ## Directly (and explicitly) wrap some methods from _data_ - ## Many of these methods can not be intercepted using __getattribute__, so they - ## must be implemented explicitly - for methodName in wrapMethods: - locals()[methodName] = wrapMethod(methodName) - - ## Wrap some methods from _data_ with the results converted to protected objects - for methodName in protectMethods: - locals()[methodName] = protectMethod(methodName) - - - ## Add a few extra methods. - def deepcopy(self): - return copy.deepcopy(self._data_) - - def __deepcopy__(self, memo): - return copy.deepcopy(self._data_, memo) - - - -def protect(obj): - if isinstance(obj, dict): - return ProtectedDict(obj) - elif isinstance(obj, list): - return ProtectedList(obj) - elif isinstance(obj, tuple): - return ProtectedTuple(obj) - else: - return obj - - -if __name__ == '__main__': - d = {'x': 1, 'y': [1,2], 'z': ({'a': 2, 'b': [3,4], 'c': (5,6)}, 1, 2)} - dp = protect(d) - - l = [1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}] - lp = protect(l) - - t = (1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}) - tp = protect(t) diff --git a/pyqtgraph/pixmaps/__init__.py b/pyqtgraph/pixmaps/__init__.py deleted file mode 100644 index c26e4a6b..00000000 --- a/pyqtgraph/pixmaps/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Allows easy loading of pixmaps used in UI elements. -Provides support for frozen environments as well. -""" - -import os, sys, pickle -from ..functions import makeQImage -from ..Qt import QtGui -if sys.version_info[0] == 2: - from . import pixmapData_2 as pixmapData -else: - from . import pixmapData_3 as pixmapData - - -def getPixmap(name): - """ - Return a QPixmap corresponding to the image file with the given name. - (eg. getPixmap('auto') loads pyqtgraph/pixmaps/auto.png) - """ - key = name+'.png' - data = pixmapData.pixmapData[key] - if isinstance(data, basestring) or isinstance(data, bytes): - pixmapData.pixmapData[key] = pickle.loads(data) - arr = pixmapData.pixmapData[key] - return QtGui.QPixmap(makeQImage(arr, alpha=True)) - diff --git a/pyqtgraph/pixmaps/compile.py b/pyqtgraph/pixmaps/compile.py deleted file mode 100644 index fa0d2408..00000000 --- a/pyqtgraph/pixmaps/compile.py +++ /dev/null @@ -1,19 +0,0 @@ -import numpy as np -from PyQt4 import QtGui -import os, pickle, sys - -path = os.path.abspath(os.path.split(__file__)[0]) -pixmaps = {} -for f in os.listdir(path): - if not f.endswith('.png'): - continue - print(f) - img = QtGui.QImage(os.path.join(path, f)) - ptr = img.bits() - ptr.setsize(img.byteCount()) - arr = np.asarray(ptr).reshape(img.height(), img.width(), 4).transpose(1,0,2) - pixmaps[f] = pickle.dumps(arr) -ver = sys.version_info[0] -fh = open(os.path.join(path, 'pixmapData_%d.py' %ver), 'w') -fh.write("import numpy as np; pixmapData=%s" % repr(pixmaps)) - diff --git a/pyqtgraph/pixmaps/pixmapData_2.py b/pyqtgraph/pixmaps/pixmapData_2.py deleted file mode 100644 index 7813b6a6..00000000 --- a/pyqtgraph/pixmaps/pixmapData_2.py +++ /dev/null @@ -1 +0,0 @@ -import numpy as np; pixmapData={'lock.png': "cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I32\nI32\nI4\ntp6\ncnumpy\ndtype\np7\n(S'u1'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'|'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS'\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xad\\xad\\xad\\x19\\xa8\\xa8\\xa8\\x8d\\xa9\\xa9\\xa9\\xc1\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xaa\\xaa\\xaa\\xc2\\xa9\\xa9\\xa9\\x8e\\xad\\xad\\xad\\x19\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xa8\\xa8\\xa8X\\xa9\\xa9\\xa9\\xed\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xed\\xa8\\xa8\\xa8X\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x0c\\x0c\\x0c\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xd2\\xd2\\xd2\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe1\\xe1\\xe1\\xff{{{\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x0e\\x0e\\x0e\\xff***\\xff+++\\xff+++\\xff\\xaf\\xaf\\xaf\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe2\\xe2\\xe2\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x1e\\x1e\\x1e\\xff\\x93\\x93\\x93\\xff\\xc6\\xc6\\xc6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffaaa\\xff\\xdc\\xdc\\xdc\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\\\\\\\\\\\\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe2\\xe2\\xe2\\xff\\xbb\\xbb\\xbb\\xff\\x9f\\x9f\\x9f\\xff\\x9f\\x9f\\x9f\\xff\\x9f\\x9f\\x9f\\xff\\xd7\\xd7\\xd7\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x1c\\x1c\\x1c\\xff\\xda\\xda\\xda\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x91\\x91\\x91\\xff\\x0f\\x0f\\x0f\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x87\\x87\\x87\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x98\\x98\\x98\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xba\\xba\\xba\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x19\\x19\\x19\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x08\\x08\\x08\\xff\\xe2\\xe2\\xe2\\xff\\xe6\\xe6\\xe6\\xff\\xcc\\xcc\\xcc\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x08\\x08\\x08\\xff\\xe2\\xe2\\xe2\\xff\\xe6\\xe6\\xe6\\xff\\xcc\\xcc\\xcc\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xba\\xba\\xba\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x19\\x19\\x19\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x85\\x85\\x85\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x98\\x98\\x98\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x19\\x19\\x19\\xff\\xd9\\xd9\\xd9\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x91\\x91\\x91\\xff\\x0f\\x0f\\x0f\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb4\\xb4\\xb4\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffZZZ\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe2\\xe2\\xe2\\xff\\xbc\\xbc\\xbc\\xff\\x9f\\x9f\\x9f\\xff\\x9f\\x9f\\x9f\\xff\\x9f\\x9f\\x9f\\xff\\xd7\\xd7\\xd7\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffaaa\\xff\\xdc\\xdc\\xdc\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x1e\\x1e\\x1e\\xff\\x93\\x93\\x93\\xff\\xc6\\xc6\\xc6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\x1d\\x1d\\x1d\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x0e\\x0e\\x0e\\xff***\\xff+++\\xff+++\\xff\\xaf\\xaf\\xaf\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe2\\xe2\\xe2\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xd2\\xd2\\xd2\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe6\\xe6\\xe6\\xff\\xe1\\xe1\\xe1\\xff{{{\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x16\\x16\\x16\\xff\\x0c\\x0c\\x0c\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00'\np13\ntp14\nb.", 'default.png': 'cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS\'b\'\np3\ntp4\nRp5\n(I1\n(I16\nI16\nI4\ntp6\ncnumpy\ndtype\np7\n(S\'u1\'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS\'|\'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS\'\\x00\\x7f\\xa6\\x1b\\x0c\\x8a\\xad\\xdc\\r\\x91\\xb0\\xf3\\r\\x91\\xb0\\xf3\\r\\x91\\xb0\\xf4\\r\\x91\\xb1\\xf4\\r\\x90\\xb0\\xf4\\x05\\x85\\xa9\\xef\\x00\\x7f\\xa6<\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6!\\x1d\\x9c\\xb9\\xf5g\\xd9\\xf1\\xffi\\xd9\\xf3\\xffd\\xd1\\xee\\xff]\\xcb\\xeb\\xff@\\xbb\\xe3\\xff\\x16\\x9c\\xc2\\xf8\\x00\\x7f\\xa6\\xb4\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6U\\\'\\xac\\xc5\\xf9i\\xd9\\xf3\\xffc\\xd3\\xef\\xff\\\\\\xcf\\xeb\\xffP\\xc8\\xe6\\xff\\x17\\x9f\\xc4\\xfd\\x00\\x7f\\xa6\\xfc\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x02\\x83\\xa8lH\\xc5\\xdd\\xfah\\xdc\\xf3\\xffc\\xd4\\xef\\xffV\\xce\\xe9\\xffN\\xcf\\xe7\\xff&\\xaa\\xca\\xfd\\x00\\x7f\\xa6\\xff\\x03\\x81\\xc7\\x01\\x04\\x8d\\xda\\x01\\t\\x94\\xd9\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6"$\\xa9\\xc4\\xf7g\\xdf\\xf5\\xfff\\xdb\\xf3\\xffU\\xcd\\xeb\\xff\\x16\\xb3\\xda\\xff.\\xc9\\xe1\\xff(\\xb2\\xd0\\xfe\\x01\\x7f\\xa6\\xff\\x04\\x84\\xc9\\x05\\t\\x94\\xd9\\x06\\x10\\x9c\\xd7\\x01\\x16\\xa2\\xd6\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x02\\x83\\xa9\\x81T\\xd3\\xeb\\xffg\\xe5\\xf7\\xffe\\xda\\xf3\\xff!\\xaa\\xde\\xff\\x11\\x9d\\xc3\\xfe\\x11\\xba\\xd7\\xff \\xb9\\xd5\\xfe\\x00\\x7f\\xa6\\xff\\x16u\\x8d\\x03\\x14\\x84\\xae\\x05\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x10\\x92\\xb4\\xc0d\\xde\\xf3\\xffg\\xe5\\xf7\\xff_\\xcc\\xef\\xff\\x0e\\x9c\\xd5\\xff\\rx\\x95\\xf6\\x0e\\x89\\xab\\xf4\\x18\\xb2\\xd1\\xfc\\x00\\x7f\\xa6\\xff\\xff\\xff\\xff\\x00\\x1a~\\x91\\x01\\x1d\\xa5\\xce\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x005\\xa9\\xc3\\xefq\\xec\\xf9\\xffg\\xe5\\xf7\\xff>\\xb7\\xe8\\xff\\x14\\x96\\xc8\\xfe\\x02}\\xa3\\xb1\\x00\\x7f\\xa6Q\\x03\\x82\\xa9\\xe8\\x00\\x7f\\xa6\\xe9\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x11\\x1c\\x98\\xb8\\x04%\\xb5\\xd3\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00D\\xad\\xc8\\xf3r\\xec\\xf9\\xffg\\xe5\\xf7\\xff:\\xb7\\xe8\\xff\\x19\\x90\\xc5\\xfe\\x03{\\xa0\\xa6\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6*\\x00\\x7f\\xa6*\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x98\\x0f\\x8f\\xb1\\x13&\\xb5\\xd3\\x04.\\xc0\\xd1\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x19\\x93\\xb7\\xc6i\\xdf\\xf4\\xffg\\xe5\\xf7\\xffT\\xc8\\xee\\xff\\x06\\x88\\xcd\\xff\\x08g\\x85\\xf7\\x00\\x7f\\xa6\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x1b\\x01\\x80\\xa7\\xeb\\x1d\\xa3\\xca\\x16#\\xb2\\xd4\\n*\\xbb\\xd2\\x04.\\xbc\\xd7\\x01\\xff\\xff\\xff\\x00\\x01\\x81\\xa7\\x88Y\\xd1\\xee\\xffg\\xe5\\xf7\\xfff\\xd9\\xf3\\xff\\\'\\xa2\\xe2\\xff\\x05e\\x99\\xf9\\x06~\\xa5\\xf3\\x01\\x81\\xa8\\x9c\\x01\\x80\\xa8\\x9f\\x04\\x85\\xad\\xef\\x08\\x8f\\xb9\\x92\\x17\\xa4\\xd6*\\x1e\\xac\\xd5\\x1a$\\xb3\\xd3\\x0c\\x19\\xa7\\xd5\\x02\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6+!\\xa3\\xc8\\xf5i\\xe0\\xf5\\xffe\\xd9\\xf3\\xff\\\\\\xca\\xee\\xff\\x1f\\x9c\\xe0\\xfa\\x03\\x84\\xca\\xd6\\x07\\x8b\\xc5\\xca\\x06\\x88\\xc1\\xb8\\x08\\x8e\\xd0l\\x0b\\x96\\xd8I\\x11\\x9e\\xd74\\x17\\xa5\\xd6 \\xab\\xd7\\x0b\\x17\\xa2\\xdc\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x01\\x80\\xa8~?\\xb9\\xe0\\xf9h\\xda\\xf3\\xff_\\xcc\\xef\\xffV\\xc1\\xec\\xfd3\\xa7\\xe3\\xe3\\x1a\\x96\\xde\\xae\\x04\\x8b\\xdb\\x89\\x00\\x89\\xdao\\x05\\x8f\\xd9T\\x0b\\x96\\xd8<\\x11\\x9b\\xd7\\x1d\\x18\\x95\\xc9\\x0c\\x00\\x80\\xd5\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x04\\x03\\x83\\xaa\\xcd5\\xa2\\xc9\\xf9[\\xc6\\xea\\xffU\\xc1\\xec\\xffH\\xb4\\xe8\\xf39\\xa8\\xe4\\xc5\\x0b\\x8f\\xdc\\x9f\\x00\\x89\\xda{\\x00\\x89\\xda_\\x07\\x87\\xc4I\\x05|\\xa5s\\x05m\\xa3\\x02\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x06\\x01\\x7f\\xa6\\x89\\x12x\\x9e\\xf63\\x88\\xae\\xfe6\\x93\\xc3\\xfe4\\x9d\\xd6\\xdf\\x08\\x82\\xc7\\xb8\\x03k\\xa2\\xab\\x04k\\x97\\xa8\\x02w\\x9e\\xeb\\x00\\x7f\\xa6j\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa67\\x00~\\xa5\\x95\\x03v\\x9c\\xd4\\x03h\\x8c\\xfa\\x02i\\x8e\\xf9\\x01x\\x9f\\xcc\\x00\\x7f\\xa6\\x92\\x00\\x7f\\xa63\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\'\np13\ntp14\nb.', 'ctrl.png': "cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I32\nI32\nI4\ntp6\ncnumpy\ndtype\np7\n(S'u1'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'|'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS'\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xad\\xad\\xad\\x19\\xa8\\xa8\\xa8\\x8d\\xa9\\xa9\\xa9\\xc1\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xaa\\xaa\\xaa\\xc2\\xa9\\xa9\\xa9\\x8e\\xad\\xad\\xad\\x19\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xa8\\xa8\\xa8X\\xa9\\xa9\\xa9\\xed\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xed\\xa8\\xa8\\xa8X\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff555\\xffPPP\\xff\\x13\\x13\\x13\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x01\\x01\\x01\\xff\\xb2\\xb2\\xb2\\xff\\xe3\\xe3\\xe3\\xff\\xd9\\xd9\\xd9\\xff]]]\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x13\\x13\\x13\\xff\\xbb\\xbb\\xbb\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xffFFF\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x13\\x13\\x13\\xff\\xbb\\xbb\\xbb\\xff\\xe3\\xe3\\xe3\\xff\\xc4\\xc4\\xc4\\xff\\x06\\x06\\x06\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff```\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff:::\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff666\\xff\\xaf\\xaf\\xaf\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9b\\x9b\\x9b\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff@@@\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffSSS\\xff\\xe3\\xe3\\xe3\\xff\\xb7\\xb7\\xb7\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x04\\x04\\x04\\xff\\xd5\\xd5\\xd5\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xffXXX\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x17\\x17\\x17\\xff\\xdb\\xdb\\xdb\\xff\\xe3\\xe3\\xe3\\xff\\xb7\\xb7\\xb7\\xff[[[\\xff\\x97\\x97\\x97\\xff\\xd4\\xd4\\xd4\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff```\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffHHH\\xff\\xc6\\xc6\\xc6\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x07\\x07\\x07\\xff;;;\\xffAAA\\xff\\\\\\\\\\\\\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xc7\\xc7\\xc7\\xffZZZ\\xff~~~\\xff\\xd9\\xd9\\xd9\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xffXXX\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb0\\xb0\\xb0\\xfffff\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xffyyy\\xff\\x00\\x00\\x00\\xff\\x06\\x06\\x06\\xff\\xcd\\xcd\\xcd\\xfffff\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xda\\xda\\xda\\xff\\xaf\\xaf\\xaf\\xff\\xcd\\xcd\\xcd\\xff\\xd7\\xd7\\xd7\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x12\\x12\\x12\\xffiii\\xffccc\\xff\\x0e\\x0e\\x0e\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00'\np13\ntp14\nb.", 'auto.png': "cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I32\nI32\nI4\ntp6\ncnumpy\ndtype\np7\n(S'u1'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'|'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS'\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xad\\xad\\xad\\x19\\xa8\\xa8\\xa8\\x8d\\xa9\\xa9\\xa9\\xc1\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xaa\\xaa\\xaa\\xc2\\xa9\\xa9\\xa9\\x8e\\xad\\xad\\xad\\x19\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xa8\\xa8\\xa8X\\xa9\\xa9\\xa9\\xed\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xed\\xa8\\xa8\\xa8X\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x19\\x19\\x19\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x04\\x04\\x04\\xffHHH\\xff\\xa4\\xa4\\xa4\\xff\\xe5\\xe5\\xe5\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff \\xffyyy\\xff\\xd1\\xd1\\xd1\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x06\\x06\\x06\\xffPPP\\xff\\xab\\xab\\xab\\xff\\xe6\\xe6\\xe6\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff&&&\\xff\\x82\\x82\\x82\\xff\\xd6\\xd6\\xd6\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\t\\t\\t\\xffWWW\\xff\\xb2\\xb2\\xb2\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe5\\xe5\\xe5\\xff\\xa8\\xa8\\xa8\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff---\\xff\\x89\\x89\\x89\\xff\\xda\\xda\\xda\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xc1\\xc1\\xc1\\xfflll\\xff\\x18\\x18\\x18\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\r\\r\\r\\xff^^^\\xff\\xba\\xba\\xba\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xda\\xda\\xda\\xff...\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff555\\xff\\x90\\x90\\x90\\xff\\xde\\xde\\xde\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe2\\xe2\\xe2\\xff\\xe3\\xe3\\xe3\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff;;;\\xff\\xc1\\xc1\\xc1\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xb7\\xb7\\xb7\\xffbbb\\xff\\x12\\x12\\x12\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xcd\\xcd\\xcd\\xffyyy\\xff$$$\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe3\\xe3\\xe3\\xff\\x91\\x91\\x91\\xff<<<\\xff\\x01\\x01\\x01\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xc3\\xc3\\xc3\\xfflll\\xff\\x18\\x18\\x18\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe4\\xe4\\xe4\\xff\\xa6\\xa6\\xa6\\xffOOO\\xff\\x07\\x07\\x07\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff555\\xff\\xb4\\xb4\\xb4\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd9\\xd9\\xd9\\xff\\x8a\\x8a\\x8a\\xff333\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff+++\\xff\\x88\\x88\\x88\\xff\\xda\\xda\\xda\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\n\\n\\n\\xff[[[\\xff\\xb8\\xb8\\xb8\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xdc\\xdc\\xdc\\xffAAA\\xff\\x02\\x02\\x02\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff...\\xff\\x8c\\x8c\\x8c\\xff\\xdc\\xdc\\xdc\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xcc\\xcc\\xcc\\xffsss\\xff\\x1a\\x1a\\x1a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x0c\\x0c\\x0c\\xff___\\xff\\xbc\\xbc\\xbc\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe5\\xe5\\xe5\\xff\\xa5\\xa5\\xa5\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff222\\xff\\x8f\\x8f\\x8f\\xff\\xde\\xde\\xde\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x0e\\x0e\\x0e\\xffccc\\xff\\xc0\\xc0\\xc0\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff555\\xff\\x94\\x94\\x94\\xff\\xe0\\xe0\\xe0\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x10\\x10\\x10\\xfffff\\xff\\xc4\\xc4\\xc4\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff:::\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00'\np13\ntp14\nb."} \ No newline at end of file diff --git a/pyqtgraph/pixmaps/pixmapData_3.py b/pyqtgraph/pixmaps/pixmapData_3.py deleted file mode 100644 index bb512029..00000000 --- a/pyqtgraph/pixmaps/pixmapData_3.py +++ /dev/null @@ -1 +0,0 @@ -import numpy as np; pixmapData={'lock.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K K K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x10\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xad\xad\xad\x19\xa8\xa8\xa8\x8d\xa9\xa9\xa9\xc1\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xaa\xaa\xaa\xc2\xa9\xa9\xa9\x8e\xad\xad\xad\x19\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xa8\xa8\xa8X\xa9\xa9\xa9\xed\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xed\xa8\xa8\xa8X\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x0c\x0c\x0c\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xd2\xd2\xd2\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe1\xe1\xe1\xff{{{\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0e\x0e\x0e\xff***\xff+++\xff+++\xff\xaf\xaf\xaf\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\x10\x10\x10\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x1e\x1e\x1e\xff\x93\x93\x93\xff\xc6\xc6\xc6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffaaa\xff\xdc\xdc\xdc\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\\\\\\\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\xbb\xbb\xbb\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\xd7\xd7\xd7\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x1c\x1c\x1c\xff\xda\xda\xda\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x91\x91\x91\xff\x0f\x0f\x0f\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x87\x87\x87\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x98\x98\x98\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\xba\xba\xba\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x19\x19\x19\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x08\x08\x08\xff\xe2\xe2\xe2\xff\xe6\xe6\xe6\xff\xcc\xcc\xcc\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x08\x08\x08\xff\xe2\xe2\xe2\xff\xe6\xe6\xe6\xff\xcc\xcc\xcc\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\xba\xba\xba\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x19\x19\x19\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x85\x85\x85\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x98\x98\x98\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x19\x19\x19\xff\xd9\xd9\xd9\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x91\x91\x91\xff\x0f\x0f\x0f\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffZZZ\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\xbc\xbc\xbc\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\xd7\xd7\xd7\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffaaa\xff\xdc\xdc\xdc\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x1e\x1e\x1e\xff\x93\x93\x93\xff\xc6\xc6\xc6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0e\x0e\x0e\xff***\xff+++\xff+++\xff\xaf\xaf\xaf\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\x10\x10\x10\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xd2\xd2\xd2\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe1\xe1\xe1\xff{{{\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x0c\x0c\x0c\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbd\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbe\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.', 'default.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K\x10K\x10K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x04\x00\x00\x00\x7f\xa6\x1b\x0c\x8a\xad\xdc\r\x91\xb0\xf3\r\x91\xb0\xf3\r\x91\xb0\xf4\r\x91\xb1\xf4\r\x90\xb0\xf4\x05\x85\xa9\xef\x00\x7f\xa6<\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6!\x1d\x9c\xb9\xf5g\xd9\xf1\xffi\xd9\xf3\xffd\xd1\xee\xff]\xcb\xeb\xff@\xbb\xe3\xff\x16\x9c\xc2\xf8\x00\x7f\xa6\xb4\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6U\'\xac\xc5\xf9i\xd9\xf3\xffc\xd3\xef\xff\\\xcf\xeb\xffP\xc8\xe6\xff\x17\x9f\xc4\xfd\x00\x7f\xa6\xfc\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x02\x83\xa8lH\xc5\xdd\xfah\xdc\xf3\xffc\xd4\xef\xffV\xce\xe9\xffN\xcf\xe7\xff&\xaa\xca\xfd\x00\x7f\xa6\xff\x03\x81\xc7\x01\x04\x8d\xda\x01\t\x94\xd9\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6"$\xa9\xc4\xf7g\xdf\xf5\xfff\xdb\xf3\xffU\xcd\xeb\xff\x16\xb3\xda\xff.\xc9\xe1\xff(\xb2\xd0\xfe\x01\x7f\xa6\xff\x04\x84\xc9\x05\t\x94\xd9\x06\x10\x9c\xd7\x01\x16\xa2\xd6\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x02\x83\xa9\x81T\xd3\xeb\xffg\xe5\xf7\xffe\xda\xf3\xff!\xaa\xde\xff\x11\x9d\xc3\xfe\x11\xba\xd7\xff \xb9\xd5\xfe\x00\x7f\xa6\xff\x16u\x8d\x03\x14\x84\xae\x05\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x10\x92\xb4\xc0d\xde\xf3\xffg\xe5\xf7\xff_\xcc\xef\xff\x0e\x9c\xd5\xff\rx\x95\xf6\x0e\x89\xab\xf4\x18\xb2\xd1\xfc\x00\x7f\xa6\xff\xff\xff\xff\x00\x1a~\x91\x01\x1d\xa5\xce\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x005\xa9\xc3\xefq\xec\xf9\xffg\xe5\xf7\xff>\xb7\xe8\xff\x14\x96\xc8\xfe\x02}\xa3\xb1\x00\x7f\xa6Q\x03\x82\xa9\xe8\x00\x7f\xa6\xe9\xff\xff\xff\x00\x00\x7f\xa6\x11\x1c\x98\xb8\x04%\xb5\xd3\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00D\xad\xc8\xf3r\xec\xf9\xffg\xe5\xf7\xff:\xb7\xe8\xff\x19\x90\xc5\xfe\x03{\xa0\xa6\xff\xff\xff\x00\x00\x7f\xa6*\x00\x7f\xa6*\xff\xff\xff\x00\x00\x7f\xa6\x98\x0f\x8f\xb1\x13&\xb5\xd3\x04.\xc0\xd1\x01\xff\xff\xff\x00\xff\xff\xff\x00\x19\x93\xb7\xc6i\xdf\xf4\xffg\xe5\xf7\xffT\xc8\xee\xff\x06\x88\xcd\xff\x08g\x85\xf7\x00\x7f\xa6\x15\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6\x1b\x01\x80\xa7\xeb\x1d\xa3\xca\x16#\xb2\xd4\n*\xbb\xd2\x04.\xbc\xd7\x01\xff\xff\xff\x00\x01\x81\xa7\x88Y\xd1\xee\xffg\xe5\xf7\xfff\xd9\xf3\xff\'\xa2\xe2\xff\x05e\x99\xf9\x06~\xa5\xf3\x01\x81\xa8\x9c\x01\x80\xa8\x9f\x04\x85\xad\xef\x08\x8f\xb9\x92\x17\xa4\xd6*\x1e\xac\xd5\x1a$\xb3\xd3\x0c\x19\xa7\xd5\x02\xff\xff\xff\x00\x00\x7f\xa6+!\xa3\xc8\xf5i\xe0\xf5\xffe\xd9\xf3\xff\\\xca\xee\xff\x1f\x9c\xe0\xfa\x03\x84\xca\xd6\x07\x8b\xc5\xca\x06\x88\xc1\xb8\x08\x8e\xd0l\x0b\x96\xd8I\x11\x9e\xd74\x17\xa5\xd6 \xab\xd7\x0b\x17\xa2\xdc\x01\xff\xff\xff\x00\xff\xff\xff\x00\x01\x80\xa8~?\xb9\xe0\xf9h\xda\xf3\xff_\xcc\xef\xffV\xc1\xec\xfd3\xa7\xe3\xe3\x1a\x96\xde\xae\x04\x8b\xdb\x89\x00\x89\xdao\x05\x8f\xd9T\x0b\x96\xd8<\x11\x9b\xd7\x1d\x18\x95\xc9\x0c\x00\x80\xd5\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6\x04\x03\x83\xaa\xcd5\xa2\xc9\xf9[\xc6\xea\xffU\xc1\xec\xffH\xb4\xe8\xf39\xa8\xe4\xc5\x0b\x8f\xdc\x9f\x00\x89\xda{\x00\x89\xda_\x07\x87\xc4I\x05|\xa5s\x05m\xa3\x02\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6\x06\x01\x7f\xa6\x89\x12x\x9e\xf63\x88\xae\xfe6\x93\xc3\xfe4\x9d\xd6\xdf\x08\x82\xc7\xb8\x03k\xa2\xab\x04k\x97\xa8\x02w\x9e\xeb\x00\x7f\xa6j\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa67\x00~\xa5\x95\x03v\x9c\xd4\x03h\x8c\xfa\x02i\x8e\xf9\x01x\x9f\xcc\x00\x7f\xa6\x92\x00\x7f\xa63\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.', 'ctrl.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K K K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x10\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xad\xad\xad\x19\xa8\xa8\xa8\x8d\xa9\xa9\xa9\xc1\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xaa\xaa\xaa\xc2\xa9\xa9\xa9\x8e\xad\xad\xad\x19\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xa8\xa8\xa8X\xa9\xa9\xa9\xed\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xed\xa8\xa8\xa8X\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff555\xffPPP\xff\x13\x13\x13\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x01\x01\x01\xff\xb2\xb2\xb2\xff\xe3\xe3\xe3\xff\xd9\xd9\xd9\xff]]]\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x13\x13\x13\xff\xbb\xbb\xbb\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xffFFF\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x13\x13\x13\xff\xbb\xbb\xbb\xff\xe3\xe3\xe3\xff\xc4\xc4\xc4\xff\x06\x06\x06\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff```\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff:::\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff666\xff\xaf\xaf\xaf\xff\x10\x10\x10\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9b\x9b\x9b\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff@@@\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xffSSS\xff\xe3\xe3\xe3\xff\xb7\xb7\xb7\xff\x10\x10\x10\xff\x00\x00\x00\xff\x00\x00\x00\xff\x04\x04\x04\xff\xd5\xd5\xd5\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xffXXX\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x17\x17\x17\xff\xdb\xdb\xdb\xff\xe3\xe3\xe3\xff\xb7\xb7\xb7\xff[[[\xff\x97\x97\x97\xff\xd4\xd4\xd4\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff```\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffHHH\xff\xc6\xc6\xc6\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x07\x07\x07\xff;;;\xffAAA\xff\\\\\\\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xc7\xc7\xc7\xffZZZ\xff~~~\xff\xd9\xd9\xd9\xff\x10\x10\x10\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xffXXX\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb0\xb0\xb0\xfffff\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xffyyy\xff\x00\x00\x00\xff\x06\x06\x06\xff\xcd\xcd\xcd\xfffff\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xda\xda\xda\xff\xaf\xaf\xaf\xff\xcd\xcd\xcd\xff\xd7\xd7\xd7\xff\x10\x10\x10\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x12\x12\x12\xffiii\xffccc\xff\x0e\x0e\x0e\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbd\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbe\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.', 'auto.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K K K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x10\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xad\xad\xad\x19\xa8\xa8\xa8\x8d\xa9\xa9\xa9\xc1\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xaa\xaa\xaa\xc2\xa9\xa9\xa9\x8e\xad\xad\xad\x19\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xa8\xa8\xa8X\xa9\xa9\xa9\xed\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xed\xa8\xa8\xa8X\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x19\x19\x19\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x04\x04\x04\xffHHH\xff\xa4\xa4\xa4\xff\xe5\xe5\xe5\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff \xffyyy\xff\xd1\xd1\xd1\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x06\x06\x06\xffPPP\xff\xab\xab\xab\xff\xe6\xe6\xe6\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff&&&\xff\x82\x82\x82\xff\xd6\xd6\xd6\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\t\t\t\xffWWW\xff\xb2\xb2\xb2\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe5\xe5\xe5\xff\xa8\xa8\xa8\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff---\xff\x89\x89\x89\xff\xda\xda\xda\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xc1\xc1\xc1\xfflll\xff\x18\x18\x18\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\r\r\r\xff^^^\xff\xba\xba\xba\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xda\xda\xda\xff...\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff555\xff\x90\x90\x90\xff\xde\xde\xde\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe2\xe2\xe2\xff\xe3\xe3\xe3\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff;;;\xff\xc1\xc1\xc1\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xb7\xb7\xb7\xffbbb\xff\x12\x12\x12\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xcd\xcd\xcd\xffyyy\xff$$$\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe3\xe3\xe3\xff\x91\x91\x91\xff<<<\xff\x01\x01\x01\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xc3\xc3\xc3\xfflll\xff\x18\x18\x18\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe4\xe4\xe4\xff\xa6\xa6\xa6\xffOOO\xff\x07\x07\x07\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff555\xff\xb4\xb4\xb4\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd9\xd9\xd9\xff\x8a\x8a\x8a\xff333\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff+++\xff\x88\x88\x88\xff\xda\xda\xda\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\n\n\n\xff[[[\xff\xb8\xb8\xb8\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xdc\xdc\xdc\xffAAA\xff\x02\x02\x02\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff...\xff\x8c\x8c\x8c\xff\xdc\xdc\xdc\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xcc\xcc\xcc\xffsss\xff\x1a\x1a\x1a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0c\x0c\x0c\xff___\xff\xbc\xbc\xbc\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe5\xe5\xe5\xff\xa5\xa5\xa5\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff222\xff\x8f\x8f\x8f\xff\xde\xde\xde\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0e\x0e\x0e\xffccc\xff\xc0\xc0\xc0\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff555\xff\x94\x94\x94\xff\xe0\xe0\xe0\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x10\x10\x10\xfffff\xff\xc4\xc4\xc4\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbd\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff:::\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbe\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.'} \ No newline at end of file diff --git a/pyqtgraph/ptime.py b/pyqtgraph/ptime.py deleted file mode 100644 index 1de8282f..00000000 --- a/pyqtgraph/ptime.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -""" -ptime.py - Precision time function made os-independent (should have been taken care of by python) -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - - -import sys -import time as systime -START_TIME = None -time = None - -def winTime(): - """Return the current time in seconds with high precision (windows version, use Manager.time() to stay platform independent).""" - return systime.clock() + START_TIME - #return systime.time() - -def unixTime(): - """Return the current time in seconds with high precision (unix version, use Manager.time() to stay platform independent).""" - return systime.time() - -if sys.platform.startswith('win'): - cstart = systime.clock() ### Required to start the clock in windows - START_TIME = systime.time() - cstart - - time = winTime -else: - time = unixTime - diff --git a/pyqtgraph/python2_3.py b/pyqtgraph/python2_3.py deleted file mode 100644 index 2182d3a1..00000000 --- a/pyqtgraph/python2_3.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -Helper functions which smooth out the differences between python 2 and 3. -""" -import sys - -def asUnicode(x): - if sys.version_info[0] == 2: - if isinstance(x, unicode): - return x - elif isinstance(x, str): - return x.decode('UTF-8') - else: - return unicode(x) - else: - return str(x) - -def cmpToKey(mycmp): - 'Convert a cmp= function into a key= function' - class K(object): - def __init__(self, obj, *args): - self.obj = obj - def __lt__(self, other): - return mycmp(self.obj, other.obj) < 0 - def __gt__(self, other): - return mycmp(self.obj, other.obj) > 0 - def __eq__(self, other): - return mycmp(self.obj, other.obj) == 0 - def __le__(self, other): - return mycmp(self.obj, other.obj) <= 0 - def __ge__(self, other): - return mycmp(self.obj, other.obj) >= 0 - def __ne__(self, other): - return mycmp(self.obj, other.obj) != 0 - return K - -def sortList(l, cmpFunc): - if sys.version_info[0] == 2: - l.sort(cmpFunc) - else: - l.sort(key=cmpToKey(cmpFunc)) - -if sys.version_info[0] == 3: - import builtins - builtins.basestring = str - #builtins.asUnicode = asUnicode - #builtins.sortList = sortList - basestring = str - def cmp(a,b): - if a>b: - return 1 - elif b > a: - return -1 - else: - return 0 - builtins.cmp = cmp - builtins.xrange = range -#else: ## don't use __builtin__ -- this confuses things like pyshell and ActiveState's lazy import recipe - #import __builtin__ - #__builtin__.asUnicode = asUnicode - #__builtin__.sortList = sortList diff --git a/pyqtgraph/reload.py b/pyqtgraph/reload.py deleted file mode 100644 index ccf83913..00000000 --- a/pyqtgraph/reload.py +++ /dev/null @@ -1,516 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Magic Reload Library -Luke Campagnola 2010 - -Python reload function that actually works (the way you expect it to) - - No re-importing necessary - - Modules can be reloaded in any order - - Replaces functions and methods with their updated code - - Changes instances to use updated classes - - Automatically decides which modules to update by comparing file modification times - -Does NOT: - - re-initialize exting instances, even if __init__ changes - - update references to any module-level objects - ie, this does not reload correctly: - from module import someObject - print someObject - ..but you can use this instead: (this works even for the builtin reload) - import module - print module.someObject -""" - - -import inspect, os, sys, gc, traceback -try: - import __builtin__ as builtins -except ImportError: - import builtins -from .debug import printExc - -def reloadAll(prefix=None, debug=False): - """Automatically reload everything whose __file__ begins with prefix. - - Skips reload if the file has not been updated (if .pyc is newer than .py) - - if prefix is None, checks all loaded modules - """ - failed = [] - changed = [] - for modName, mod in list(sys.modules.items()): ## don't use iteritems; size may change during reload - if not inspect.ismodule(mod): - continue - if modName == '__main__': - continue - - ## Ignore if the file name does not start with prefix - if not hasattr(mod, '__file__') or os.path.splitext(mod.__file__)[1] not in ['.py', '.pyc']: - continue - if prefix is not None and mod.__file__[:len(prefix)] != prefix: - continue - - ## ignore if the .pyc is newer than the .py (or if there is no pyc or py) - py = os.path.splitext(mod.__file__)[0] + '.py' - pyc = py + 'c' - if py not in changed and os.path.isfile(pyc) and os.path.isfile(py) and os.stat(pyc).st_mtime >= os.stat(py).st_mtime: - #if debug: - #print "Ignoring module %s; unchanged" % str(mod) - continue - changed.append(py) ## keep track of which modules have changed to insure that duplicate-import modules get reloaded. - - try: - reload(mod, debug=debug) - except: - printExc("Error while reloading module %s, skipping\n" % mod) - failed.append(mod.__name__) - - if len(failed) > 0: - raise Exception("Some modules failed to reload: %s" % ', '.join(failed)) - -def reload(module, debug=False, lists=False, dicts=False): - """Replacement for the builtin reload function: - - Reloads the module as usual - - Updates all old functions and class methods to use the new code - - Updates all instances of each modified class to use the new class - - Can update lists and dicts, but this is disabled by default - - Requires that class and function names have not changed - """ - if debug: - print("Reloading %s" % str(module)) - - ## make a copy of the old module dictionary, reload, then grab the new module dictionary for comparison - oldDict = module.__dict__.copy() - builtins.reload(module) - newDict = module.__dict__ - - ## Allow modules access to the old dictionary after they reload - if hasattr(module, '__reload__'): - module.__reload__(oldDict) - - ## compare old and new elements from each dict; update where appropriate - for k in oldDict: - old = oldDict[k] - new = newDict.get(k, None) - if old is new or new is None: - continue - - if inspect.isclass(old): - if debug: - print(" Updating class %s.%s (0x%x -> 0x%x)" % (module.__name__, k, id(old), id(new))) - updateClass(old, new, debug) - - elif inspect.isfunction(old): - depth = updateFunction(old, new, debug) - if debug: - extra = "" - if depth > 0: - extra = " (and %d previous versions)" % depth - print(" Updating function %s.%s%s" % (module.__name__, k, extra)) - elif lists and isinstance(old, list): - l = old.len() - old.extend(new) - for i in range(l): - old.pop(0) - elif dicts and isinstance(old, dict): - old.update(new) - for k in old: - if k not in new: - del old[k] - - - -## For functions: -## 1) update the code and defaults to new versions. -## 2) keep a reference to the previous version so ALL versions get updated for every reload -def updateFunction(old, new, debug, depth=0, visited=None): - #if debug and depth > 0: - #print " -> also updating previous version", old, " -> ", new - - old.__code__ = new.__code__ - old.__defaults__ = new.__defaults__ - - if visited is None: - visited = [] - if old in visited: - return - visited.append(old) - - ## finally, update any previous versions still hanging around.. - if hasattr(old, '__previous_reload_version__'): - maxDepth = updateFunction(old.__previous_reload_version__, new, debug, depth=depth+1, visited=visited) - else: - maxDepth = depth - - ## We need to keep a pointer to the previous version so we remember to update BOTH - ## when the next reload comes around. - if depth == 0: - new.__previous_reload_version__ = old - return maxDepth - - - -## For classes: -## 1) find all instances of the old class and set instance.__class__ to the new class -## 2) update all old class methods to use code from the new class methods -def updateClass(old, new, debug): - - ## Track town all instances and subclasses of old - refs = gc.get_referrers(old) - for ref in refs: - try: - if isinstance(ref, old) and ref.__class__ is old: - ref.__class__ = new - if debug: - print(" Changed class for %s" % safeStr(ref)) - elif inspect.isclass(ref) and issubclass(ref, old) and old in ref.__bases__: - ind = ref.__bases__.index(old) - - ## Does not work: - #ref.__bases__ = ref.__bases__[:ind] + (new,) + ref.__bases__[ind+1:] - ## reason: Even though we change the code on methods, they remain bound - ## to their old classes (changing im_class is not allowed). Instead, - ## we have to update the __bases__ such that this class will be allowed - ## as an argument to older methods. - - ## This seems to work. Is there any reason not to? - ## Note that every time we reload, the class hierarchy becomes more complex. - ## (and I presume this may slow things down?) - ref.__bases__ = ref.__bases__[:ind] + (new,old) + ref.__bases__[ind+1:] - if debug: - print(" Changed superclass for %s" % safeStr(ref)) - #else: - #if debug: - #print " Ignoring reference", type(ref) - except: - print("Error updating reference (%s) for class change (%s -> %s)" % (safeStr(ref), safeStr(old), safeStr(new))) - raise - - ## update all class methods to use new code. - ## Generally this is not needed since instances already know about the new class, - ## but it fixes a few specific cases (pyqt signals, for one) - for attr in dir(old): - oa = getattr(old, attr) - if inspect.ismethod(oa): - try: - na = getattr(new, attr) - except AttributeError: - if debug: - print(" Skipping method update for %s; new class does not have this attribute" % attr) - continue - - if hasattr(oa, 'im_func') and hasattr(na, 'im_func') and oa.__func__ is not na.__func__: - depth = updateFunction(oa.__func__, na.__func__, debug) - #oa.im_class = new ## bind old method to new class ## not allowed - if debug: - extra = "" - if depth > 0: - extra = " (and %d previous versions)" % depth - print(" Updating method %s%s" % (attr, extra)) - - ## And copy in new functions that didn't exist previously - for attr in dir(new): - if not hasattr(old, attr): - if debug: - print(" Adding missing attribute %s" % attr) - setattr(old, attr, getattr(new, attr)) - - ## finally, update any previous versions still hanging around.. - if hasattr(old, '__previous_reload_version__'): - updateClass(old.__previous_reload_version__, new, debug) - - -## It is possible to build classes for which str(obj) just causes an exception. -## Avoid thusly: -def safeStr(obj): - try: - s = str(obj) - except: - try: - s = repr(obj) - except: - s = "" % (safeStr(type(obj)), id(obj)) - return s - - - - - -## Tests: -# write modules to disk, import, then re-write and run again -if __name__ == '__main__': - doQtTest = True - try: - from PyQt4 import QtCore - if not hasattr(QtCore, 'Signal'): - QtCore.Signal = QtCore.pyqtSignal - #app = QtGui.QApplication([]) - class Btn(QtCore.QObject): - sig = QtCore.Signal() - def emit(self): - self.sig.emit() - btn = Btn() - except: - raise - print("Error; skipping Qt tests") - doQtTest = False - - - - import os - if not os.path.isdir('test1'): - os.mkdir('test1') - open('test1/__init__.py', 'w') - modFile1 = "test1/test1.py" - modCode1 = """ -import sys -class A(object): - def __init__(self, msg): - object.__init__(self) - self.msg = msg - def fn(self, pfx = ""): - print(pfx+"A class: %%s %%s" %% (str(self.__class__), str(id(self.__class__)))) - print(pfx+" %%s: %d" %% self.msg) - -class B(A): - def fn(self, pfx=""): - print(pfx+"B class:", self.__class__, id(self.__class__)) - print(pfx+" %%s: %d" %% self.msg) - print(pfx+" calling superclass.. (%%s)" %% id(A) ) - A.fn(self, " ") -""" - - modFile2 = "test2.py" - modCode2 = """ -from test1.test1 import A -from test1.test1 import B - -a1 = A("ax1") -b1 = B("bx1") -class C(A): - def __init__(self, msg): - #print "| C init:" - #print "| C.__bases__ = ", map(id, C.__bases__) - #print "| A:", id(A) - #print "| A.__init__ = ", id(A.__init__.im_func), id(A.__init__.im_func.__code__), id(A.__init__.im_class) - A.__init__(self, msg + "(init from C)") - -def fn(): - print("fn: %s") -""" - - open(modFile1, 'w').write(modCode1%(1,1)) - open(modFile2, 'w').write(modCode2%"message 1") - import test1.test1 as test1 - import test2 - print("Test 1 originals:") - A1 = test1.A - B1 = test1.B - a1 = test1.A("a1") - b1 = test1.B("b1") - a1.fn() - b1.fn() - #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) - - - from test2 import fn, C - - if doQtTest: - print("Button test before:") - btn.sig.connect(fn) - btn.sig.connect(a1.fn) - btn.emit() - #btn.sig.emit() - print("") - - #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) - - - print("Test2 before reload:") - - fn() - oldfn = fn - test2.a1.fn() - test2.b1.fn() - c1 = test2.C('c1') - c1.fn() - - os.remove(modFile1+'c') - open(modFile1, 'w').write(modCode1%(2,2)) - print("\n----RELOAD test1-----\n") - reloadAll(os.path.abspath(__file__)[:10], debug=True) - - - print("Subclass test:") - c2 = test2.C('c2') - c2.fn() - - - os.remove(modFile2+'c') - open(modFile2, 'w').write(modCode2%"message 2") - print("\n----RELOAD test2-----\n") - reloadAll(os.path.abspath(__file__)[:10], debug=True) - - if doQtTest: - print("Button test after:") - btn.emit() - #btn.sig.emit() - - #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) - - print("Test2 after reload:") - fn() - test2.a1.fn() - test2.b1.fn() - - print("\n==> Test 1 Old instances:") - a1.fn() - b1.fn() - c1.fn() - #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) - - print("\n==> Test 1 New instances:") - a2 = test1.A("a2") - b2 = test1.B("b2") - a2.fn() - b2.fn() - c2 = test2.C('c2') - c2.fn() - #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) - - - - - os.remove(modFile1+'c') - os.remove(modFile2+'c') - open(modFile1, 'w').write(modCode1%(3,3)) - open(modFile2, 'w').write(modCode2%"message 3") - - print("\n----RELOAD-----\n") - reloadAll(os.path.abspath(__file__)[:10], debug=True) - - if doQtTest: - print("Button test after:") - btn.emit() - #btn.sig.emit() - - #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) - - print("Test2 after reload:") - fn() - test2.a1.fn() - test2.b1.fn() - - print("\n==> Test 1 Old instances:") - a1.fn() - b1.fn() - print("function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.__func__), id(a1.fn.__self__.__class__), id(b1.fn.__func__), id(b1.fn.__self__.__class__))) - - print("\n==> Test 1 New instances:") - a2 = test1.A("a2") - b2 = test1.B("b2") - a2.fn() - b2.fn() - print("function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.__func__), id(a1.fn.__self__.__class__), id(b1.fn.__func__), id(b1.fn.__self__.__class__))) - - - os.remove(modFile1) - os.remove(modFile2) - os.remove(modFile1+'c') - os.remove(modFile2+'c') - os.system('rm -r test1') - - - - - - - - -# -# Failure graveyard ahead: -# - - -"""Reload Importer: -Hooks into import system to -1) keep a record of module dependencies as they are imported -2) make sure modules are always reloaded in correct order -3) update old classes and functions to use reloaded code""" - -#import imp, sys - -## python's import hook mechanism doesn't work since we need to be -## informed every time there is an import statement, not just for new imports -#class ReloadImporter: - #def __init__(self): - #self.depth = 0 - - #def find_module(self, name, path): - #print " "*self.depth + "find: ", name, path - ##if name == 'PyQt4' and path is None: - ##print "PyQt4 -> PySide" - ##self.modData = imp.find_module('PySide') - ##return self - ##return None ## return none to allow the import to proceed normally; return self to intercept with load_module - #self.modData = imp.find_module(name, path) - #self.depth += 1 - ##sys.path_importer_cache = {} - #return self - - #def load_module(self, name): - #mod = imp.load_module(name, *self.modData) - #self.depth -= 1 - #print " "*self.depth + "load: ", name - #return mod - -#def pathHook(path): - #print "path hook:", path - #raise ImportError -#sys.path_hooks.append(pathHook) - -#sys.meta_path.append(ReloadImporter()) - - -### replace __import__ with a wrapper that tracks module dependencies -#modDeps = {} -#reloadModule = None -#origImport = __builtins__.__import__ -#def _import(name, globals=None, locals=None, fromlist=None, level=-1, stack=[]): - ### Note that stack behaves as a static variable. - ##print " "*len(importStack) + "import %s" % args[0] - #stack.append(set()) - #mod = origImport(name, globals, locals, fromlist, level) - #deps = stack.pop() - #if len(stack) > 0: - #stack[-1].add(mod) - #elif reloadModule is not None: ## If this is the top level import AND we're inside a module reload - #modDeps[reloadModule].add(mod) - - #if mod in modDeps: - #modDeps[mod] |= deps - #else: - #modDeps[mod] = deps - - - #return mod - -#__builtins__.__import__ = _import - -### replace -#origReload = __builtins__.reload -#def _reload(mod): - #reloadModule = mod - #ret = origReload(mod) - #reloadModule = None - #return ret -#__builtins__.reload = _reload - - -#def reload(mod, visited=None): - #if visited is None: - #visited = set() - #if mod in visited: - #return - #visited.add(mod) - #for dep in modDeps.get(mod, []): - #reload(dep, visited) - #__builtins__.reload(mod) diff --git a/pyqtgraph/units.py b/pyqtgraph/units.py deleted file mode 100644 index 6b7f3099..00000000 --- a/pyqtgraph/units.py +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 -*- -## Very simple unit support: -## - creates variable names like 'mV' and 'kHz' -## - the value assigned to the variable corresponds to the scale prefix -## (mV = 0.001) -## - the actual units are purely cosmetic for making code clearer: -## -## x = 20*pA is identical to x = 20*1e-12 - -## No unicode variable names (μ,Ω) allowed until python 3 - -SI_PREFIXES = 'yzafpnum kMGTPEZY' -UNITS = 'm,s,g,W,J,V,A,F,T,Hz,Ohm,S,N,C,px,b,B'.split(',') -allUnits = {} - -def addUnit(p, n): - g = globals() - v = 1000**n - for u in UNITS: - g[p+u] = v - allUnits[p+u] = v - -for p in SI_PREFIXES: - if p == ' ': - p = '' - n = 0 - elif p == 'u': - n = -2 - else: - n = SI_PREFIXES.index(p) - 8 - - addUnit(p, n) - -cm = 0.01 - - - - - - -def evalUnits(unitStr): - """ - Evaluate a unit string into ([numerators,...], [denominators,...]) - Examples: - N m/s^2 => ([N, m], [s, s]) - A*s / V => ([A, s], [V,]) - """ - pass - -def formatUnits(units): - """ - Format a unit specification ([numerators,...], [denominators,...]) - into a string (this is the inverse of evalUnits) - """ - pass - -def simplify(units): - """ - Cancel units that appear in both numerator and denominator, then attempt to replace - groups of units with single units where possible (ie, J/s => W) - """ - pass - - \ No newline at end of file diff --git a/pyqtgraph/widgets/BusyCursor.py b/pyqtgraph/widgets/BusyCursor.py deleted file mode 100644 index b013dda0..00000000 --- a/pyqtgraph/widgets/BusyCursor.py +++ /dev/null @@ -1,24 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore - -__all__ = ['BusyCursor'] - -class BusyCursor(object): - """Class for displaying a busy mouse cursor during long operations. - Usage:: - - with pyqtgraph.BusyCursor(): - doLongOperation() - - May be nested. - """ - active = [] - - def __enter__(self): - QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) - BusyCursor.active.append(self) - - def __exit__(self, *args): - BusyCursor.active.pop(-1) - if len(BusyCursor.active) == 0: - QtGui.QApplication.restoreOverrideCursor() - \ No newline at end of file diff --git a/pyqtgraph/widgets/CheckTable.py b/pyqtgraph/widgets/CheckTable.py deleted file mode 100644 index dd33fd75..00000000 --- a/pyqtgraph/widgets/CheckTable.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore -from . import VerticalLabel - -__all__ = ['CheckTable'] - -class CheckTable(QtGui.QWidget): - - sigStateChanged = QtCore.Signal(object, object, object) # (row, col, state) - - def __init__(self, columns): - QtGui.QWidget.__init__(self) - self.layout = QtGui.QGridLayout() - self.layout.setSpacing(0) - self.setLayout(self.layout) - self.headers = [] - self.columns = columns - col = 1 - for c in columns: - label = VerticalLabel.VerticalLabel(c, orientation='vertical') - self.headers.append(label) - self.layout.addWidget(label, 0, col) - col += 1 - - self.rowNames = [] - self.rowWidgets = [] - self.oldRows = {} ## remember settings from removed rows; reapply if they reappear. - - - def updateRows(self, rows): - for r in self.rowNames[:]: - if r not in rows: - self.removeRow(r) - for r in rows: - if r not in self.rowNames: - self.addRow(r) - - def addRow(self, name): - label = QtGui.QLabel(name) - row = len(self.rowNames)+1 - self.layout.addWidget(label, row, 0) - checks = [] - col = 1 - for c in self.columns: - check = QtGui.QCheckBox('') - check.col = c - check.row = name - self.layout.addWidget(check, row, col) - checks.append(check) - if name in self.oldRows: - check.setChecked(self.oldRows[name][col]) - col += 1 - #QtCore.QObject.connect(check, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) - check.stateChanged.connect(self.checkChanged) - self.rowNames.append(name) - self.rowWidgets.append([label] + checks) - - def removeRow(self, name): - row = self.rowNames.index(name) - self.oldRows[name] = self.saveState()['rows'][row] ## save for later - self.rowNames.pop(row) - for w in self.rowWidgets[row]: - w.setParent(None) - #QtCore.QObject.disconnect(w, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) - if isinstance(w, QtGui.QCheckBox): - w.stateChanged.disconnect(self.checkChanged) - self.rowWidgets.pop(row) - for i in range(row, len(self.rowNames)): - widgets = self.rowWidgets[i] - for j in range(len(widgets)): - widgets[j].setParent(None) - self.layout.addWidget(widgets[j], i+1, j) - - def checkChanged(self, state): - check = QtCore.QObject.sender(self) - #self.emit(QtCore.SIGNAL('stateChanged'), check.row, check.col, state) - self.sigStateChanged.emit(check.row, check.col, state) - - def saveState(self): - rows = [] - for i in range(len(self.rowNames)): - row = [self.rowNames[i]] + [c.isChecked() for c in self.rowWidgets[i][1:]] - rows.append(row) - return {'cols': self.columns, 'rows': rows} - - def restoreState(self, state): - rows = [r[0] for r in state['rows']] - self.updateRows(rows) - for r in state['rows']: - rowNum = self.rowNames.index(r[0]) - for i in range(1, len(r)): - self.rowWidgets[rowNum][i].setChecked(r[i]) - diff --git a/pyqtgraph/widgets/ColorButton.py b/pyqtgraph/widgets/ColorButton.py deleted file mode 100644 index ee91801a..00000000 --- a/pyqtgraph/widgets/ColorButton.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph.functions as functions - -__all__ = ['ColorButton'] - -class ColorButton(QtGui.QPushButton): - """ - **Bases:** QtGui.QPushButton - - Button displaying a color and allowing the user to select a new color. - - ====================== ============================================================ - **Signals**: - sigColorChanging(self) emitted whenever a new color is picked in the color dialog - sigColorChanged(self) emitted when the selected color is accepted (user clicks OK) - ====================== ============================================================ - """ - sigColorChanging = QtCore.Signal(object) ## emitted whenever a new color is picked in the color dialog - sigColorChanged = QtCore.Signal(object) ## emitted when the selected color is accepted (user clicks OK) - - def __init__(self, parent=None, color=(128,128,128)): - QtGui.QPushButton.__init__(self, parent) - self.setColor(color) - self.colorDialog = QtGui.QColorDialog() - self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) - self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) - self.colorDialog.currentColorChanged.connect(self.dialogColorChanged) - self.colorDialog.rejected.connect(self.colorRejected) - self.colorDialog.colorSelected.connect(self.colorSelected) - #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged) - #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected) - self.clicked.connect(self.selectColor) - self.setMinimumHeight(15) - self.setMinimumWidth(15) - - def paintEvent(self, ev): - QtGui.QPushButton.paintEvent(self, ev) - p = QtGui.QPainter(self) - rect = self.rect().adjusted(6, 6, -6, -6) - ## draw white base, then texture for indicating transparency, then actual color - p.setBrush(functions.mkBrush('w')) - p.drawRect(rect) - p.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) - p.drawRect(rect) - p.setBrush(functions.mkBrush(self._color)) - p.drawRect(rect) - p.end() - - def setColor(self, color, finished=True): - """Sets the button's color and emits both sigColorChanged and sigColorChanging.""" - self._color = functions.mkColor(color) - if finished: - self.sigColorChanged.emit(self) - else: - self.sigColorChanging.emit(self) - self.update() - - def selectColor(self): - self.origColor = self.color() - self.colorDialog.setCurrentColor(self.color()) - self.colorDialog.open() - - def dialogColorChanged(self, color): - if color.isValid(): - self.setColor(color, finished=False) - - def colorRejected(self): - self.setColor(self.origColor, finished=False) - - def colorSelected(self, color): - self.setColor(self._color, finished=True) - - def saveState(self): - return functions.colorTuple(self._color) - - def restoreState(self, state): - self.setColor(state) - - def color(self, mode='qcolor'): - color = functions.mkColor(self._color) - if mode == 'qcolor': - return color - elif mode == 'byte': - return (color.red(), color.green(), color.blue(), color.alpha()) - elif mode == 'float': - return (color.red()/255., color.green()/255., color.blue()/255., color.alpha()/255.) - - def widgetGroupInterface(self): - return (self.sigColorChanged, ColorButton.saveState, ColorButton.restoreState) - diff --git a/pyqtgraph/widgets/ColorMapWidget.py b/pyqtgraph/widgets/ColorMapWidget.py deleted file mode 100644 index 26539d7e..00000000 --- a/pyqtgraph/widgets/ColorMapWidget.py +++ /dev/null @@ -1,218 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph.parametertree as ptree -import numpy as np -from pyqtgraph.pgcollections import OrderedDict -import pyqtgraph.functions as fn - -__all__ = ['ColorMapWidget'] - -class ColorMapWidget(ptree.ParameterTree): - """ - This class provides a widget allowing the user to customize color mapping - for multi-column data. Given a list of field names, the user may specify - multiple criteria for assigning colors to each record in a numpy record array. - Multiple criteria are evaluated and combined into a single color for each - record by user-defined compositing methods. - - For simpler color mapping using a single gradient editor, see - :class:`GradientWidget ` - """ - sigColorMapChanged = QtCore.Signal(object) - - def __init__(self): - ptree.ParameterTree.__init__(self, showHeader=False) - - self.params = ColorMapParameter() - self.setParameters(self.params) - self.params.sigTreeStateChanged.connect(self.mapChanged) - - ## wrap a couple methods - self.setFields = self.params.setFields - self.map = self.params.map - - def mapChanged(self): - self.sigColorMapChanged.emit(self) - - -class ColorMapParameter(ptree.types.GroupParameter): - sigColorMapChanged = QtCore.Signal(object) - - def __init__(self): - self.fields = {} - ptree.types.GroupParameter.__init__(self, name='Color Map', addText='Add Mapping..', addList=[]) - self.sigTreeStateChanged.connect(self.mapChanged) - - def mapChanged(self): - self.sigColorMapChanged.emit(self) - - def addNew(self, name): - mode = self.fields[name].get('mode', 'range') - if mode == 'range': - self.addChild(RangeColorMapItem(name, self.fields[name])) - elif mode == 'enum': - self.addChild(EnumColorMapItem(name, self.fields[name])) - - def fieldNames(self): - return self.fields.keys() - - def setFields(self, fields): - """ - Set the list of fields to be used by the mapper. - - The format of *fields* is:: - - [ (fieldName, {options}), ... ] - - ============== ============================================================ - Field Options: - mode Either 'range' or 'enum' (default is range). For 'range', - The user may specify a gradient of colors to be applied - linearly across a specific range of values. For 'enum', - the user specifies a single color for each unique value - (see *values* option). - units String indicating the units of the data for this field. - values List of unique values for which the user may assign a - color when mode=='enum'. Optionally may specify a dict - instead {value: name}. - ============== ============================================================ - """ - self.fields = OrderedDict(fields) - #self.fields = fields - #self.fields.sort() - names = self.fieldNames() - self.setAddList(names) - - def map(self, data, mode='byte'): - """ - Return an array of colors corresponding to *data*. - - ========= ================================================================= - Arguments - data A numpy record array where the fields in data.dtype match those - defined by a prior call to setFields(). - mode Either 'byte' or 'float'. For 'byte', the method returns an array - of dtype ubyte with values scaled 0-255. For 'float', colors are - returned as 0.0-1.0 float values. - ========= ================================================================= - """ - colors = np.zeros((len(data),4)) - for item in self.children(): - if not item['Enabled']: - continue - chans = item.param('Channels..') - mask = np.empty((len(data), 4), dtype=bool) - for i,f in enumerate(['Red', 'Green', 'Blue', 'Alpha']): - mask[:,i] = chans[f] - - colors2 = item.map(data) - - op = item['Operation'] - if op == 'Add': - colors[mask] = colors[mask] + colors2[mask] - elif op == 'Multiply': - colors[mask] *= colors2[mask] - elif op == 'Overlay': - a = colors2[:,3:4] - c3 = colors * (1-a) + colors2 * a - c3[:,3:4] = colors[:,3:4] + (1-colors[:,3:4]) * a - colors = c3 - elif op == 'Set': - colors[mask] = colors2[mask] - - - colors = np.clip(colors, 0, 1) - if mode == 'byte': - colors = (colors * 255).astype(np.ubyte) - - return colors - - -class RangeColorMapItem(ptree.types.SimpleParameter): - def __init__(self, name, opts): - self.fieldName = name - units = opts.get('units', '') - ptree.types.SimpleParameter.__init__(self, - name=name, autoIncrementName=True, type='colormap', removable=True, renamable=True, - children=[ - #dict(name="Field", type='list', value=name, values=fields), - dict(name='Min', type='float', value=0.0, suffix=units, siPrefix=True), - dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True), - dict(name='Operation', type='list', value='Overlay', values=['Overlay', 'Add', 'Multiply', 'Set']), - dict(name='Channels..', type='group', expanded=False, children=[ - dict(name='Red', type='bool', value=True), - dict(name='Green', type='bool', value=True), - dict(name='Blue', type='bool', value=True), - dict(name='Alpha', type='bool', value=True), - ]), - dict(name='Enabled', type='bool', value=True), - dict(name='NaN', type='color'), - ]) - - def map(self, data): - data = data[self.fieldName] - - - - scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) - cmap = self.value() - colors = cmap.map(scaled, mode='float') - - mask = np.isnan(data) | np.isinf(data) - nanColor = self['NaN'] - nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.) - colors[mask] = nanColor - - return colors - - -class EnumColorMapItem(ptree.types.GroupParameter): - def __init__(self, name, opts): - self.fieldName = name - vals = opts.get('values', []) - if isinstance(vals, list): - vals = OrderedDict([(v,str(v)) for v in vals]) - childs = [{'name': v, 'type': 'color'} for v in vals] - - childs = [] - for val,vname in vals.items(): - ch = ptree.Parameter.create(name=vname, type='color') - ch.maskValue = val - childs.append(ch) - - ptree.types.GroupParameter.__init__(self, - name=name, autoIncrementName=True, removable=True, renamable=True, - children=[ - dict(name='Values', type='group', children=childs), - dict(name='Operation', type='list', value='Overlay', values=['Overlay', 'Add', 'Multiply', 'Set']), - dict(name='Channels..', type='group', expanded=False, children=[ - dict(name='Red', type='bool', value=True), - dict(name='Green', type='bool', value=True), - dict(name='Blue', type='bool', value=True), - dict(name='Alpha', type='bool', value=True), - ]), - dict(name='Enabled', type='bool', value=True), - dict(name='Default', type='color'), - ]) - - def map(self, data): - data = data[self.fieldName] - colors = np.empty((len(data), 4)) - default = np.array(fn.colorTuple(self['Default'])) / 255. - colors[:] = default - - for v in self.param('Values'): - mask = data == v.maskValue - c = np.array(fn.colorTuple(v.value())) / 255. - colors[mask] = c - #scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) - #cmap = self.value() - #colors = cmap.map(scaled, mode='float') - - #mask = np.isnan(data) | np.isinf(data) - #nanColor = self['NaN'] - #nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.) - #colors[mask] = nanColor - - return colors - - diff --git a/pyqtgraph/widgets/ComboBox.py b/pyqtgraph/widgets/ComboBox.py deleted file mode 100644 index 1884648c..00000000 --- a/pyqtgraph/widgets/ComboBox.py +++ /dev/null @@ -1,41 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.SignalProxy import SignalProxy - - -class ComboBox(QtGui.QComboBox): - """Extends QComboBox to add extra functionality. - - updateList() - updates the items in the comboBox while blocking signals, remembers and resets to the previous values if it's still in the list - """ - - - def __init__(self, parent=None, items=None, default=None): - QtGui.QComboBox.__init__(self, parent) - - #self.value = default - - if items is not None: - self.addItems(items) - if default is not None: - self.setValue(default) - - def setValue(self, value): - ind = self.findText(value) - if ind == -1: - return - #self.value = value - self.setCurrentIndex(ind) - - def updateList(self, items): - prevVal = str(self.currentText()) - try: - self.blockSignals(True) - self.clear() - self.addItems(items) - self.setValue(prevVal) - - finally: - self.blockSignals(False) - - if str(self.currentText()) != prevVal: - self.currentIndexChanged.emit(self.currentIndex()) - \ No newline at end of file diff --git a/pyqtgraph/widgets/DataFilterWidget.py b/pyqtgraph/widgets/DataFilterWidget.py deleted file mode 100644 index c94f6c68..00000000 --- a/pyqtgraph/widgets/DataFilterWidget.py +++ /dev/null @@ -1,150 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph.parametertree as ptree -import numpy as np -from pyqtgraph.pgcollections import OrderedDict -import pyqtgraph as pg - -__all__ = ['DataFilterWidget'] - -class DataFilterWidget(ptree.ParameterTree): - """ - This class allows the user to filter multi-column data sets by specifying - multiple criteria - """ - - sigFilterChanged = QtCore.Signal(object) - - def __init__(self): - ptree.ParameterTree.__init__(self, showHeader=False) - self.params = DataFilterParameter() - - self.setParameters(self.params) - self.params.sigTreeStateChanged.connect(self.filterChanged) - - self.setFields = self.params.setFields - self.filterData = self.params.filterData - self.describe = self.params.describe - - def filterChanged(self): - self.sigFilterChanged.emit(self) - - def parameters(self): - return self.params - - -class DataFilterParameter(ptree.types.GroupParameter): - - sigFilterChanged = QtCore.Signal(object) - - def __init__(self): - self.fields = {} - ptree.types.GroupParameter.__init__(self, name='Data Filter', addText='Add filter..', addList=[]) - self.sigTreeStateChanged.connect(self.filterChanged) - - def filterChanged(self): - self.sigFilterChanged.emit(self) - - def addNew(self, name): - mode = self.fields[name].get('mode', 'range') - if mode == 'range': - self.addChild(RangeFilterItem(name, self.fields[name])) - elif mode == 'enum': - self.addChild(EnumFilterItem(name, self.fields[name])) - - - def fieldNames(self): - return self.fields.keys() - - def setFields(self, fields): - self.fields = OrderedDict(fields) - names = self.fieldNames() - self.setAddList(names) - - def filterData(self, data): - if len(data) == 0: - return data - return data[self.generateMask(data)] - - def generateMask(self, data): - mask = np.ones(len(data), dtype=bool) - if len(data) == 0: - return mask - for fp in self: - if fp.value() is False: - continue - mask &= fp.generateMask(data, mask.copy()) - #key, mn, mx = fp.fieldName, fp['Min'], fp['Max'] - - #vals = data[key] - #mask &= (vals >= mn) - #mask &= (vals < mx) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections - return mask - - def describe(self): - """Return a list of strings describing the currently enabled filters.""" - desc = [] - for fp in self: - if fp.value() is False: - continue - desc.append(fp.describe()) - return desc - -class RangeFilterItem(ptree.types.SimpleParameter): - def __init__(self, name, opts): - self.fieldName = name - units = opts.get('units', '') - self.units = units - ptree.types.SimpleParameter.__init__(self, - name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True, - children=[ - #dict(name="Field", type='list', value=name, values=fields), - dict(name='Min', type='float', value=0.0, suffix=units, siPrefix=True), - dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True), - ]) - - def generateMask(self, data, mask): - vals = data[self.fieldName][mask] - mask[mask] = (vals >= self['Min']) & (vals < self['Max']) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections - return mask - - def describe(self): - return "%s < %s < %s" % (pg.siFormat(self['Min'], suffix=self.units), self.fieldName, pg.siFormat(self['Max'], suffix=self.units)) - -class EnumFilterItem(ptree.types.SimpleParameter): - def __init__(self, name, opts): - self.fieldName = name - vals = opts.get('values', []) - childs = [] - if isinstance(vals, list): - vals = OrderedDict([(v,str(v)) for v in vals]) - for val,vname in vals.items(): - ch = ptree.Parameter.create(name=vname, type='bool', value=True) - ch.maskValue = val - childs.append(ch) - ch = ptree.Parameter.create(name='(other)', type='bool', value=True) - ch.maskValue = '__other__' - childs.append(ch) - - ptree.types.SimpleParameter.__init__(self, - name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True, - children=childs) - - def generateMask(self, data, startMask): - vals = data[self.fieldName][startMask] - mask = np.ones(len(vals), dtype=bool) - otherMask = np.ones(len(vals), dtype=bool) - for c in self: - key = c.maskValue - if key == '__other__': - m = ~otherMask - else: - m = vals != key - otherMask &= m - if c.value() is False: - mask &= m - startMask[startMask] = mask - return startMask - - def describe(self): - vals = [ch.name() for ch in self if ch.value() is True] - return "%s: %s" % (self.fieldName, ', '.join(vals)) \ No newline at end of file diff --git a/pyqtgraph/widgets/DataTreeWidget.py b/pyqtgraph/widgets/DataTreeWidget.py deleted file mode 100644 index a6b5cac8..00000000 --- a/pyqtgraph/widgets/DataTreeWidget.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.pgcollections import OrderedDict -import types, traceback -import numpy as np - -try: - import metaarray - HAVE_METAARRAY = True -except: - HAVE_METAARRAY = False - -__all__ = ['DataTreeWidget'] - -class DataTreeWidget(QtGui.QTreeWidget): - """ - Widget for displaying hierarchical python data structures - (eg, nested dicts, lists, and arrays) - """ - - - def __init__(self, parent=None, data=None): - QtGui.QTreeWidget.__init__(self, parent) - self.setVerticalScrollMode(self.ScrollPerPixel) - self.setData(data) - self.setColumnCount(3) - self.setHeaderLabels(['key / index', 'type', 'value']) - - def setData(self, data, hideRoot=False): - """data should be a dictionary.""" - self.clear() - self.buildTree(data, self.invisibleRootItem(), hideRoot=hideRoot) - #node = self.mkNode('', data) - #while node.childCount() > 0: - #c = node.child(0) - #node.removeChild(c) - #self.invisibleRootItem().addChild(c) - self.expandToDepth(3) - self.resizeColumnToContents(0) - - def buildTree(self, data, parent, name='', hideRoot=False): - if hideRoot: - node = parent - else: - typeStr = type(data).__name__ - if typeStr == 'instance': - typeStr += ": " + data.__class__.__name__ - node = QtGui.QTreeWidgetItem([name, typeStr, ""]) - parent.addChild(node) - - if isinstance(data, types.TracebackType): ## convert traceback to a list of strings - data = list(map(str.strip, traceback.format_list(traceback.extract_tb(data)))) - elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): - data = { - 'data': data.view(np.ndarray), - 'meta': data.infoCopy() - } - - if isinstance(data, dict): - for k in data: - self.buildTree(data[k], node, str(k)) - elif isinstance(data, list) or isinstance(data, tuple): - for i in range(len(data)): - self.buildTree(data[i], node, str(i)) - else: - node.setText(2, str(data)) - - - #def mkNode(self, name, v): - #if type(v) is list and len(v) > 0 and isinstance(v[0], dict): - #inds = map(unicode, range(len(v))) - #v = OrderedDict(zip(inds, v)) - #if isinstance(v, dict): - ##print "\nadd tree", k, v - #node = QtGui.QTreeWidgetItem([name]) - #for k in v: - #newNode = self.mkNode(k, v[k]) - #node.addChild(newNode) - #else: - ##print "\nadd value", k, str(v) - #node = QtGui.QTreeWidgetItem([unicode(name), unicode(v)]) - #return node - diff --git a/pyqtgraph/widgets/FeedbackButton.py b/pyqtgraph/widgets/FeedbackButton.py deleted file mode 100644 index f788f4b6..00000000 --- a/pyqtgraph/widgets/FeedbackButton.py +++ /dev/null @@ -1,163 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtCore, QtGui - -__all__ = ['FeedbackButton'] - -class FeedbackButton(QtGui.QPushButton): - """ - QPushButton which flashes success/failure indication for slow or asynchronous procedures. - """ - - - ### For thread-safetyness - sigCallSuccess = QtCore.Signal(object, object, object) - sigCallFailure = QtCore.Signal(object, object, object) - sigCallProcess = QtCore.Signal(object, object, object) - sigReset = QtCore.Signal() - - def __init__(self, *args): - QtGui.QPushButton.__init__(self, *args) - self.origStyle = None - self.origText = self.text() - self.origStyle = self.styleSheet() - self.origTip = self.toolTip() - self.limitedTime = True - - - #self.textTimer = QtCore.QTimer() - #self.tipTimer = QtCore.QTimer() - #self.textTimer.timeout.connect(self.setText) - #self.tipTimer.timeout.connect(self.setToolTip) - - self.sigCallSuccess.connect(self.success) - self.sigCallFailure.connect(self.failure) - self.sigCallProcess.connect(self.processing) - self.sigReset.connect(self.reset) - - - def feedback(self, success, message=None, tip="", limitedTime=True): - """Calls success() or failure(). If you want the message to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action.Threadsafe.""" - if success: - self.success(message, tip, limitedTime=limitedTime) - else: - self.failure(message, tip, limitedTime=limitedTime) - - def success(self, message=None, tip="", limitedTime=True): - """Displays specified message on button and flashes button green to let user know action was successful. If you want the success to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action. Threadsafe.""" - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - self.setEnabled(True) - #print "success" - self.startBlink("#0F0", message, tip, limitedTime=limitedTime) - else: - self.sigCallSuccess.emit(message, tip, limitedTime) - - def failure(self, message=None, tip="", limitedTime=True): - """Displays specified message on button and flashes button red to let user know there was an error. If you want the error to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action. Threadsafe. """ - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - self.setEnabled(True) - #print "fail" - self.startBlink("#F00", message, tip, limitedTime=limitedTime) - else: - self.sigCallFailure.emit(message, tip, limitedTime) - - def processing(self, message="Processing..", tip="", processEvents=True): - """Displays specified message on button to let user know the action is in progress. Threadsafe. """ - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - self.setEnabled(False) - self.setText(message, temporary=True) - self.setToolTip(tip, temporary=True) - if processEvents: - QtGui.QApplication.processEvents() - else: - self.sigCallProcess.emit(message, tip, processEvents) - - - def reset(self): - """Resets the button to its original text and style. Threadsafe.""" - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - if isGuiThread: - self.limitedTime = True - self.setText() - self.setToolTip() - self.setStyleSheet() - else: - self.sigReset.emit() - - def startBlink(self, color, message=None, tip="", limitedTime=True): - #if self.origStyle is None: - #self.origStyle = self.styleSheet() - #self.origText = self.text() - self.setFixedHeight(self.height()) - - if message is not None: - self.setText(message, temporary=True) - self.setToolTip(tip, temporary=True) - self.count = 0 - #self.indStyle = "QPushButton {border: 2px solid %s; border-radius: 5px}" % color - self.indStyle = "QPushButton {background-color: %s}" % color - self.limitedTime = limitedTime - self.borderOn() - if limitedTime: - QtCore.QTimer.singleShot(2000, self.setText) - QtCore.QTimer.singleShot(10000, self.setToolTip) - - def borderOn(self): - self.setStyleSheet(self.indStyle, temporary=True) - if self.limitedTime or self.count <=2: - QtCore.QTimer.singleShot(100, self.borderOff) - - - def borderOff(self): - self.setStyleSheet() - self.count += 1 - if self.count >= 2: - if self.limitedTime: - return - QtCore.QTimer.singleShot(30, self.borderOn) - - - def setText(self, text=None, temporary=False): - if text is None: - text = self.origText - #print text - QtGui.QPushButton.setText(self, text) - if not temporary: - self.origText = text - - def setToolTip(self, text=None, temporary=False): - if text is None: - text = self.origTip - QtGui.QPushButton.setToolTip(self, text) - if not temporary: - self.origTip = text - - def setStyleSheet(self, style=None, temporary=False): - if style is None: - style = self.origStyle - QtGui.QPushButton.setStyleSheet(self, style) - if not temporary: - self.origStyle = style - - -if __name__ == '__main__': - import time - app = QtGui.QApplication([]) - win = QtGui.QMainWindow() - btn = FeedbackButton("Button") - fail = True - def click(): - btn.processing("Hold on..") - time.sleep(2.0) - - global fail - fail = not fail - if fail: - btn.failure(message="FAIL.", tip="There was a failure. Get over it.") - else: - btn.success(message="Bueno!") - btn.clicked.connect(click) - win.setCentralWidget(btn) - win.show() \ No newline at end of file diff --git a/pyqtgraph/widgets/FileDialog.py b/pyqtgraph/widgets/FileDialog.py deleted file mode 100644 index 33b838a2..00000000 --- a/pyqtgraph/widgets/FileDialog.py +++ /dev/null @@ -1,14 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -import sys - -__all__ = ['FileDialog'] - -class FileDialog(QtGui.QFileDialog): - ## Compatibility fix for OSX: - ## For some reason the native dialog doesn't show up when you set AcceptMode to AcceptSave on OS X, so we don't use the native dialog - - def __init__(self, *args): - QtGui.QFileDialog.__init__(self, *args) - - if sys.platform == 'darwin': - self.setOption(QtGui.QFileDialog.DontUseNativeDialog) \ No newline at end of file diff --git a/pyqtgraph/widgets/GradientWidget.py b/pyqtgraph/widgets/GradientWidget.py deleted file mode 100644 index 1723a94b..00000000 --- a/pyqtgraph/widgets/GradientWidget.py +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore -from .GraphicsView import GraphicsView -from pyqtgraph.graphicsItems.GradientEditorItem import GradientEditorItem -import weakref -import numpy as np - -__all__ = ['TickSlider', 'GradientWidget', 'BlackWhiteSlider'] - - -class GradientWidget(GraphicsView): - """ - Widget displaying an editable color gradient. The user may add, move, recolor, - or remove colors from the gradient. Additionally, a context menu allows the - user to select from pre-defined gradients. - """ - sigGradientChanged = QtCore.Signal(object) - sigGradientChangeFinished = QtCore.Signal(object) - - def __init__(self, parent=None, orientation='bottom', *args, **kargs): - """ - The *orientation* argument may be 'bottom', 'top', 'left', or 'right' - indicating whether the gradient is displayed horizontally (top, bottom) - or vertically (left, right) and on what side of the gradient the editable - ticks will appear. - - All other arguments are passed to - :func:`GradientEditorItem.__init__ `. - - Note: For convenience, this class wraps methods from - :class:`GradientEditorItem `. - """ - GraphicsView.__init__(self, parent, useOpenGL=False, background=None) - self.maxDim = 31 - kargs['tickPen'] = 'k' - self.item = GradientEditorItem(*args, **kargs) - self.item.sigGradientChanged.connect(self.sigGradientChanged) - self.item.sigGradientChangeFinished.connect(self.sigGradientChangeFinished) - self.setCentralItem(self.item) - self.setOrientation(orientation) - self.setCacheMode(self.CacheNone) - self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing) - self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) - #self.setBackgroundRole(QtGui.QPalette.NoRole) - #self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.NoBrush)) - #self.setAutoFillBackground(False) - #self.setAttribute(QtCore.Qt.WA_PaintOnScreen, False) - #self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, True) - - def setOrientation(self, ort): - """Set the orientation of the widget. May be one of 'bottom', 'top', - 'left', or 'right'.""" - self.item.setOrientation(ort) - self.orientation = ort - self.setMaxDim() - - def setMaxDim(self, mx=None): - if mx is None: - mx = self.maxDim - else: - self.maxDim = mx - - if self.orientation in ['bottom', 'top']: - self.setFixedHeight(mx) - self.setMaximumWidth(16777215) - else: - self.setFixedWidth(mx) - self.setMaximumHeight(16777215) - - def __getattr__(self, attr): - ### wrap methods from GradientEditorItem - return getattr(self.item, attr) - - diff --git a/pyqtgraph/widgets/GraphicsLayoutWidget.py b/pyqtgraph/widgets/GraphicsLayoutWidget.py deleted file mode 100644 index 1e667278..00000000 --- a/pyqtgraph/widgets/GraphicsLayoutWidget.py +++ /dev/null @@ -1,12 +0,0 @@ -from pyqtgraph.Qt import QtGui -from pyqtgraph.graphicsItems.GraphicsLayout import GraphicsLayout -from .GraphicsView import GraphicsView - -__all__ = ['GraphicsLayoutWidget'] -class GraphicsLayoutWidget(GraphicsView): - def __init__(self, parent=None, **kargs): - GraphicsView.__init__(self, parent) - self.ci = GraphicsLayout(**kargs) - for n in ['nextRow', 'nextCol', 'nextColumn', 'addPlot', 'addViewBox', 'addItem', 'getItem', 'addLabel', 'addLayout', 'addLabel', 'addViewBox', 'removeItem', 'itemIndex', 'clear']: - setattr(self, n, getattr(self.ci, n)) - self.setCentralItem(self.ci) diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py deleted file mode 100644 index fb535929..00000000 --- a/pyqtgraph/widgets/GraphicsView.py +++ /dev/null @@ -1,393 +0,0 @@ -# -*- coding: utf-8 -*- -""" -GraphicsView.py - Extension of QGraphicsView -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - -from pyqtgraph.Qt import QtCore, QtGui -import pyqtgraph as pg - -try: - from pyqtgraph.Qt import QtOpenGL - HAVE_OPENGL = True -except ImportError: - HAVE_OPENGL = False - -from pyqtgraph.Point import Point -import sys, os -from .FileDialog import FileDialog -from pyqtgraph.GraphicsScene import GraphicsScene -import numpy as np -import pyqtgraph.functions as fn -import pyqtgraph.debug as debug -import pyqtgraph - -__all__ = ['GraphicsView'] - -class GraphicsView(QtGui.QGraphicsView): - """Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the - viewed coordinate range. Also automatically creates a GraphicsScene and a central QGraphicsWidget - that is automatically scaled to the full view geometry. - - This widget is the basis for :class:`PlotWidget `, - :class:`GraphicsLayoutWidget `, and the view widget in - :class:`ImageView `. - - By default, the view coordinate system matches the widget's pixel coordinates and - automatically updates when the view is resized. This can be overridden by setting - autoPixelRange=False. The exact visible range can be set with setRange(). - - The view can be panned using the middle mouse button and scaled using the right mouse button if - enabled via enableMouse() (but ordinarily, we use ViewBox for this functionality).""" - - sigRangeChanged = QtCore.Signal(object, object) - sigTransformChanged = QtCore.Signal(object) - sigMouseReleased = QtCore.Signal(object) - sigSceneMouseMoved = QtCore.Signal(object) - #sigRegionChanged = QtCore.Signal(object) - sigScaleChanged = QtCore.Signal(object) - lastFileDir = None - - def __init__(self, parent=None, useOpenGL=None, background='default'): - """ - ============ ============================================================ - Arguments: - parent Optional parent widget - useOpenGL If True, the GraphicsView will use OpenGL to do all of its - rendering. This can improve performance on some systems, - but may also introduce bugs (the combination of - QGraphicsView and QGLWidget is still an 'experimental' - feature of Qt) - background Set the background color of the GraphicsView. Accepts any - single argument accepted by - :func:`mkColor `. By - default, the background color is determined using the - 'backgroundColor' configuration option (see - :func:`setConfigOption `. - ============ ============================================================ - """ - - self.closed = False - - QtGui.QGraphicsView.__init__(self, parent) - - if useOpenGL is None: - useOpenGL = pyqtgraph.getConfigOption('useOpenGL') - - self.useOpenGL(useOpenGL) - - self.setCacheMode(self.CacheBackground) - - ## This might help, but it's probably dangerous in the general case.. - #self.setOptimizationFlag(self.DontSavePainterState, True) - - self.setBackgroundRole(QtGui.QPalette.NoRole) - self.setBackground(background) - - self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setFrameShape(QtGui.QFrame.NoFrame) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor) - self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) - self.setViewportUpdateMode(QtGui.QGraphicsView.MinimalViewportUpdate) - - - self.lockedViewports = [] - self.lastMousePos = None - self.setMouseTracking(True) - self.aspectLocked = False - self.range = QtCore.QRectF(0, 0, 1, 1) - self.autoPixelRange = True - self.currentItem = None - self.clearMouse() - self.updateMatrix() - self.sceneObj = GraphicsScene() - self.setScene(self.sceneObj) - - ## Workaround for PySide crash - ## This ensures that the scene will outlive the view. - if pyqtgraph.Qt.USE_PYSIDE: - self.sceneObj._view_ref_workaround = self - - ## by default we set up a central widget with a grid layout. - ## this can be replaced if needed. - self.centralWidget = None - self.setCentralItem(QtGui.QGraphicsWidget()) - self.centralLayout = QtGui.QGraphicsGridLayout() - self.centralWidget.setLayout(self.centralLayout) - - self.mouseEnabled = False - self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False) - self.clickAccepted = False - - def setAntialiasing(self, aa): - """Enable or disable default antialiasing. - Note that this will only affect items that do not specify their own antialiasing options.""" - if aa: - self.setRenderHints(self.renderHints() | QtGui.QPainter.Antialiasing) - else: - self.setRenderHints(self.renderHints() & ~QtGui.QPainter.Antialiasing) - - def setBackground(self, background): - """ - Set the background color of the GraphicsView. - To use the defaults specified py pyqtgraph.setConfigOption, use background='default'. - To make the background transparent, use background=None. - """ - self._background = background - if background == 'default': - background = pyqtgraph.getConfigOption('background') - brush = fn.mkBrush(background) - self.setBackgroundBrush(brush) - - def paintEvent(self, ev): - self.scene().prepareForPaint() - #print "GV: paint", ev.rect() - return QtGui.QGraphicsView.paintEvent(self, ev) - - def render(self, *args, **kwds): - self.scene().prepareForPaint() - return QtGui.QGraphicsView.render(self, *args, **kwds) - - - def close(self): - self.centralWidget = None - self.scene().clear() - self.currentItem = None - self.sceneObj = None - self.closed = True - self.setViewport(None) - - def useOpenGL(self, b=True): - if b: - if not HAVE_OPENGL: - raise Exception("Requested to use OpenGL with QGraphicsView, but QtOpenGL module is not available.") - v = QtOpenGL.QGLWidget() - else: - v = QtGui.QWidget() - - self.setViewport(v) - - def keyPressEvent(self, ev): - self.scene().keyPressEvent(ev) ## bypass view, hand event directly to scene - ## (view likes to eat arrow key events) - - - def setCentralItem(self, item): - return self.setCentralWidget(item) - - def setCentralWidget(self, item): - """Sets a QGraphicsWidget to automatically fill the entire view (the item will be automatically - resize whenever the GraphicsView is resized).""" - if self.centralWidget is not None: - self.scene().removeItem(self.centralWidget) - self.centralWidget = item - if item is not None: - self.sceneObj.addItem(item) - self.resizeEvent(None) - - def addItem(self, *args): - return self.scene().addItem(*args) - - def removeItem(self, *args): - return self.scene().removeItem(*args) - - def enableMouse(self, b=True): - self.mouseEnabled = b - self.autoPixelRange = (not b) - - def clearMouse(self): - self.mouseTrail = [] - self.lastButtonReleased = None - - def resizeEvent(self, ev): - if self.closed: - return - if self.autoPixelRange: - self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height()) - GraphicsView.setRange(self, self.range, padding=0, disableAutoPixel=False) ## we do this because some subclasses like to redefine setRange in an incompatible way. - self.updateMatrix() - - def updateMatrix(self, propagate=True): - self.setSceneRect(self.range) - if self.autoPixelRange: - self.resetTransform() - else: - if self.aspectLocked: - self.fitInView(self.range, QtCore.Qt.KeepAspectRatio) - else: - self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio) - - self.sigRangeChanged.emit(self, self.range) - self.sigTransformChanged.emit(self) - - if propagate: - for v in self.lockedViewports: - v.setXRange(self.range, padding=0) - - def viewRect(self): - """Return the boundaries of the view in scene coordinates""" - ## easier to just return self.range ? - r = QtCore.QRectF(self.rect()) - return self.viewportTransform().inverted()[0].mapRect(r) - - def visibleRange(self): - ## for backward compatibility - return self.viewRect() - - def translate(self, dx, dy): - self.range.adjust(dx, dy, dx, dy) - self.updateMatrix() - - def scale(self, sx, sy, center=None): - scale = [sx, sy] - if self.aspectLocked: - scale[0] = scale[1] - - if self.scaleCenter: - center = None - if center is None: - center = self.range.center() - - w = self.range.width() / scale[0] - h = self.range.height() / scale[1] - self.range = QtCore.QRectF(center.x() - (center.x()-self.range.left()) / scale[0], center.y() - (center.y()-self.range.top()) /scale[1], w, h) - - - self.updateMatrix() - self.sigScaleChanged.emit(self) - - def setRange(self, newRect=None, padding=0.05, lockAspect=None, propagate=True, disableAutoPixel=True): - if disableAutoPixel: - self.autoPixelRange=False - if newRect is None: - newRect = self.visibleRange() - padding = 0 - - padding = Point(padding) - newRect = QtCore.QRectF(newRect) - pw = newRect.width() * padding[0] - ph = newRect.height() * padding[1] - newRect = newRect.adjusted(-pw, -ph, pw, ph) - scaleChanged = False - if self.range.width() != newRect.width() or self.range.height() != newRect.height(): - scaleChanged = True - self.range = newRect - #print "New Range:", self.range - if self.centralWidget is not None: - self.centralWidget.setGeometry(self.range) - self.updateMatrix(propagate) - if scaleChanged: - self.sigScaleChanged.emit(self) - - def scaleToImage(self, image): - """Scales such that pixels in image are the same size as screen pixels. This may result in a significant performance increase.""" - pxSize = image.pixelSize() - image.setPxMode(True) - try: - self.sigScaleChanged.disconnect(image.setScaledMode) - except TypeError: - pass - tl = image.sceneBoundingRect().topLeft() - w = self.size().width() * pxSize[0] - h = self.size().height() * pxSize[1] - range = QtCore.QRectF(tl.x(), tl.y(), w, h) - GraphicsView.setRange(self, range, padding=0) - self.sigScaleChanged.connect(image.setScaledMode) - - - - def lockXRange(self, v1): - if not v1 in self.lockedViewports: - self.lockedViewports.append(v1) - - def setXRange(self, r, padding=0.05): - r1 = QtCore.QRectF(self.range) - r1.setLeft(r.left()) - r1.setRight(r.right()) - GraphicsView.setRange(self, r1, padding=[padding, 0], propagate=False) - - def setYRange(self, r, padding=0.05): - r1 = QtCore.QRectF(self.range) - r1.setTop(r.top()) - r1.setBottom(r.bottom()) - GraphicsView.setRange(self, r1, padding=[0, padding], propagate=False) - - def wheelEvent(self, ev): - QtGui.QGraphicsView.wheelEvent(self, ev) - if not self.mouseEnabled: - return - sc = 1.001 ** ev.delta() - #self.scale *= sc - #self.updateMatrix() - self.scale(sc, sc) - - def setAspectLocked(self, s): - self.aspectLocked = s - - def leaveEvent(self, ev): - self.scene().leaveEvent(ev) ## inform scene when mouse leaves - - def mousePressEvent(self, ev): - QtGui.QGraphicsView.mousePressEvent(self, ev) - - - if not self.mouseEnabled: - return - self.lastMousePos = Point(ev.pos()) - self.mousePressPos = ev.pos() - self.clickAccepted = ev.isAccepted() - if not self.clickAccepted: - self.scene().clearSelection() - return ## Everything below disabled for now.. - - def mouseReleaseEvent(self, ev): - QtGui.QGraphicsView.mouseReleaseEvent(self, ev) - if not self.mouseEnabled: - return - self.sigMouseReleased.emit(ev) - self.lastButtonReleased = ev.button() - return ## Everything below disabled for now.. - - def mouseMoveEvent(self, ev): - if self.lastMousePos is None: - self.lastMousePos = Point(ev.pos()) - delta = Point(ev.pos() - self.lastMousePos) - self.lastMousePos = Point(ev.pos()) - - QtGui.QGraphicsView.mouseMoveEvent(self, ev) - if not self.mouseEnabled: - return - self.sigSceneMouseMoved.emit(self.mapToScene(ev.pos())) - - if self.clickAccepted: ## Ignore event if an item in the scene has already claimed it. - return - - if ev.buttons() == QtCore.Qt.RightButton: - delta = Point(np.clip(delta[0], -50, 50), np.clip(-delta[1], -50, 50)) - scale = 1.01 ** delta - self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos)) - self.sigRangeChanged.emit(self, self.range) - - elif ev.buttons() in [QtCore.Qt.MidButton, QtCore.Qt.LeftButton]: ## Allow panning by left or mid button. - px = self.pixelSize() - tr = -delta * px - - self.translate(tr[0], tr[1]) - self.sigRangeChanged.emit(self, self.range) - - def pixelSize(self): - """Return vector with the length and width of one view pixel in scene coordinates""" - p0 = Point(0,0) - p1 = Point(1,1) - tr = self.transform().inverted()[0] - p01 = tr.map(p0) - p11 = tr.map(p1) - return Point(p11 - p01) - - def dragEnterEvent(self, ev): - ev.ignore() ## not sure why, but for some reason this class likes to consume drag events - - diff --git a/pyqtgraph/widgets/HistogramLUTWidget.py b/pyqtgraph/widgets/HistogramLUTWidget.py deleted file mode 100644 index cbe8eb61..00000000 --- a/pyqtgraph/widgets/HistogramLUTWidget.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Widget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. -This is a wrapper around HistogramLUTItem -""" - -from pyqtgraph.Qt import QtGui, QtCore -from .GraphicsView import GraphicsView -from pyqtgraph.graphicsItems.HistogramLUTItem import HistogramLUTItem - -__all__ = ['HistogramLUTWidget'] - - -class HistogramLUTWidget(GraphicsView): - - def __init__(self, parent=None, *args, **kargs): - background = kargs.get('background', 'default') - GraphicsView.__init__(self, parent, useOpenGL=False, background=background) - self.item = HistogramLUTItem(*args, **kargs) - self.setCentralItem(self.item) - self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) - self.setMinimumWidth(95) - - - def sizeHint(self): - return QtCore.QSize(115, 200) - - - - def __getattr__(self, attr): - return getattr(self.item, attr) - - - diff --git a/pyqtgraph/widgets/JoystickButton.py b/pyqtgraph/widgets/JoystickButton.py deleted file mode 100644 index 201a957a..00000000 --- a/pyqtgraph/widgets/JoystickButton.py +++ /dev/null @@ -1,95 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore - - -__all__ = ['JoystickButton'] - -class JoystickButton(QtGui.QPushButton): - sigStateChanged = QtCore.Signal(object, object) ## self, state - - def __init__(self, parent=None): - QtGui.QPushButton.__init__(self, parent) - self.radius = 200 - self.setCheckable(True) - self.state = None - self.setState(0,0) - self.setFixedWidth(50) - self.setFixedHeight(50) - - - def mousePressEvent(self, ev): - self.setChecked(True) - self.pressPos = ev.pos() - ev.accept() - - def mouseMoveEvent(self, ev): - dif = ev.pos()-self.pressPos - self.setState(dif.x(), -dif.y()) - - def mouseReleaseEvent(self, ev): - self.setChecked(False) - self.setState(0,0) - - def wheelEvent(self, ev): - ev.accept() - - - def doubleClickEvent(self, ev): - ev.accept() - - def getState(self): - return self.state - - def setState(self, *xy): - xy = list(xy) - d = (xy[0]**2 + xy[1]**2)**0.5 - nxy = [0,0] - for i in [0,1]: - if xy[i] == 0: - nxy[i] = 0 - else: - nxy[i] = xy[i]/d - - if d > self.radius: - d = self.radius - d = (d/self.radius)**2 - xy = [nxy[0]*d, nxy[1]*d] - - w2 = self.width()/2. - h2 = self.height()/2 - self.spotPos = QtCore.QPoint(w2*(1+xy[0]), h2*(1-xy[1])) - self.update() - if self.state == xy: - return - self.state = xy - self.sigStateChanged.emit(self, self.state) - - def paintEvent(self, ev): - QtGui.QPushButton.paintEvent(self, ev) - p = QtGui.QPainter(self) - p.setBrush(QtGui.QBrush(QtGui.QColor(0,0,0))) - p.drawEllipse(self.spotPos.x()-3,self.spotPos.y()-3,6,6) - - def resizeEvent(self, ev): - self.setState(*self.state) - QtGui.QPushButton.resizeEvent(self, ev) - - - -if __name__ == '__main__': - app = QtGui.QApplication([]) - w = QtGui.QMainWindow() - b = JoystickButton() - w.setCentralWidget(b) - w.show() - w.resize(100, 100) - - def fn(b, s): - print("state changed:", s) - - b.sigStateChanged.connect(fn) - - ## Start Qt event loop unless running in interactive mode. - import sys - if sys.flags.interactive != 1: - app.exec_() - \ No newline at end of file diff --git a/pyqtgraph/widgets/LayoutWidget.py b/pyqtgraph/widgets/LayoutWidget.py deleted file mode 100644 index f567ad74..00000000 --- a/pyqtgraph/widgets/LayoutWidget.py +++ /dev/null @@ -1,101 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore - -__all__ = ['LayoutWidget'] -class LayoutWidget(QtGui.QWidget): - """ - Convenience class used for laying out QWidgets in a grid. - (It's just a little less effort to use than QGridLayout) - """ - - def __init__(self, parent=None): - QtGui.QWidget.__init__(self, parent) - self.layout = QtGui.QGridLayout() - self.setLayout(self.layout) - self.items = {} - self.rows = {} - self.currentRow = 0 - self.currentCol = 0 - - def nextRow(self): - """Advance to next row for automatic widget placement""" - self.currentRow += 1 - self.currentCol = 0 - - def nextColumn(self, colspan=1): - """Advance to next column, while returning the current column number - (generally only for internal use--called by addWidget)""" - self.currentCol += colspan - return self.currentCol-colspan - - def nextCol(self, *args, **kargs): - """Alias of nextColumn""" - return self.nextColumn(*args, **kargs) - - - def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create a QLabel with *text* and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to QLabel(). - Returns the created widget. - """ - text = QtGui.QLabel(text, **kargs) - self.addItem(text, row, col, rowspan, colspan) - return text - - def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs): - """ - Create an empty LayoutWidget and place it in the next available cell (or in the cell specified) - All extra keyword arguments are passed to :func:`LayoutWidget.__init__ ` - Returns the created widget. - """ - layout = LayoutWidget(**kargs) - self.addItem(layout, row, col, rowspan, colspan) - return layout - - def addWidget(self, item, row=None, col=None, rowspan=1, colspan=1): - """ - Add a widget to the layout and place it in the next available cell (or in the cell specified). - """ - if row == 'next': - self.nextRow() - row = self.currentRow - elif row is None: - row = self.currentRow - - - if col is None: - col = self.nextCol(colspan) - - if row not in self.rows: - self.rows[row] = {} - self.rows[row][col] = item - self.items[item] = (row, col) - - self.layout.addWidget(item, row, col, rowspan, colspan) - - def getWidget(self, row, col): - """Return the widget in (*row*, *col*)""" - return self.row[row][col] - - #def itemIndex(self, item): - #for i in range(self.layout.count()): - #if self.layout.itemAt(i).graphicsItem() is item: - #return i - #raise Exception("Could not determine index of item " + str(item)) - - #def removeItem(self, item): - #"""Remove *item* from the layout.""" - #ind = self.itemIndex(item) - #self.layout.removeAt(ind) - #self.scene().removeItem(item) - #r,c = self.items[item] - #del self.items[item] - #del self.rows[r][c] - #self.update() - - #def clear(self): - #items = [] - #for i in list(self.items.keys()): - #self.removeItem(i) - - diff --git a/pyqtgraph/widgets/MatplotlibWidget.py b/pyqtgraph/widgets/MatplotlibWidget.py deleted file mode 100644 index 6a22c973..00000000 --- a/pyqtgraph/widgets/MatplotlibWidget.py +++ /dev/null @@ -1,41 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE -import matplotlib - -if USE_PYSIDE: - matplotlib.rcParams['backend.qt4']='PySide' - -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar -from matplotlib.figure import Figure - -class MatplotlibWidget(QtGui.QWidget): - """ - Implements a Matplotlib figure inside a QWidget. - Use getFigure() and redraw() to interact with matplotlib. - - Example:: - - mw = MatplotlibWidget() - subplot = mw.getFigure().add_subplot(111) - subplot.plot(x,y) - mw.draw() - """ - - def __init__(self, size=(5.0, 4.0), dpi=100): - QtGui.QWidget.__init__(self) - self.fig = Figure(size, dpi=dpi) - self.canvas = FigureCanvas(self.fig) - self.canvas.setParent(self) - self.toolbar = NavigationToolbar(self.canvas, self) - - self.vbox = QtGui.QVBoxLayout() - self.vbox.addWidget(self.toolbar) - self.vbox.addWidget(self.canvas) - - self.setLayout(self.vbox) - - def getFigure(self): - return self.fig - - def draw(self): - self.canvas.draw() diff --git a/pyqtgraph/widgets/MultiPlotWidget.py b/pyqtgraph/widgets/MultiPlotWidget.py deleted file mode 100644 index 400bee92..00000000 --- a/pyqtgraph/widgets/MultiPlotWidget.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- -""" -MultiPlotWidget.py - Convenience class--GraphicsView widget displaying a MultiPlotItem -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - -from .GraphicsView import GraphicsView -import pyqtgraph.graphicsItems.MultiPlotItem as MultiPlotItem - -__all__ = ['MultiPlotWidget'] -class MultiPlotWidget(GraphicsView): - """Widget implementing a graphicsView with a single PlotItem inside.""" - def __init__(self, parent=None): - GraphicsView.__init__(self, parent) - self.enableMouse(False) - self.mPlotItem = MultiPlotItem.MultiPlotItem() - self.setCentralItem(self.mPlotItem) - ## Explicitly wrap methods from mPlotItem - #for m in ['setData']: - #setattr(self, m, getattr(self.mPlotItem, m)) - - def __getattr__(self, attr): ## implicitly wrap methods from plotItem - if hasattr(self.mPlotItem, attr): - m = getattr(self.mPlotItem, attr) - if hasattr(m, '__call__'): - return m - raise NameError(attr) - - def widgetGroupInterface(self): - return (None, MultiPlotWidget.saveState, MultiPlotWidget.restoreState) - - def saveState(self): - return {} - #return self.plotItem.saveState() - - def restoreState(self, state): - pass - #return self.plotItem.restoreState(state) - - def close(self): - self.mPlotItem.close() - self.mPlotItem = None - self.setParent(None) - GraphicsView.close(self) \ No newline at end of file diff --git a/pyqtgraph/widgets/PathButton.py b/pyqtgraph/widgets/PathButton.py deleted file mode 100644 index 7950a53d..00000000 --- a/pyqtgraph/widgets/PathButton.py +++ /dev/null @@ -1,50 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -import pyqtgraph as pg -__all__ = ['PathButton'] - - -class PathButton(QtGui.QPushButton): - """Simple PushButton extension which paints a QPainterPath on its face""" - def __init__(self, parent=None, path=None, pen='default', brush=None, size=(30,30)): - QtGui.QPushButton.__init__(self, parent) - self.path = None - if pen == 'default': - pen = 'k' - self.setPen(pen) - self.setBrush(brush) - if path is not None: - self.setPath(path) - if size is not None: - self.setFixedWidth(size[0]) - self.setFixedHeight(size[1]) - - - def setBrush(self, brush): - self.brush = pg.mkBrush(brush) - - def setPen(self, pen): - self.pen = pg.mkPen(pen) - - def setPath(self, path): - self.path = path - self.update() - - def paintEvent(self, ev): - QtGui.QPushButton.paintEvent(self, ev) - margin = 7 - geom = QtCore.QRectF(0, 0, self.width(), self.height()).adjusted(margin, margin, -margin, -margin) - rect = self.path.boundingRect() - scale = min(geom.width() / float(rect.width()), geom.height() / float(rect.height())) - - p = QtGui.QPainter(self) - p.setRenderHint(p.Antialiasing) - p.translate(geom.center()) - p.scale(scale, scale) - p.translate(-rect.center()) - p.setPen(self.pen) - p.setBrush(self.brush) - p.drawPath(self.path) - p.end() - - - \ No newline at end of file diff --git a/pyqtgraph/widgets/PlotWidget.py b/pyqtgraph/widgets/PlotWidget.py deleted file mode 100644 index 7b3c685c..00000000 --- a/pyqtgraph/widgets/PlotWidget.py +++ /dev/null @@ -1,93 +0,0 @@ -# -*- coding: utf-8 -*- -""" -PlotWidget.py - Convenience class--GraphicsView widget displaying a single PlotItem -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. -""" - -from pyqtgraph.Qt import QtCore, QtGui -from .GraphicsView import * -from pyqtgraph.graphicsItems.PlotItem import * - -__all__ = ['PlotWidget'] -class PlotWidget(GraphicsView): - - #sigRangeChanged = QtCore.Signal(object, object) ## already defined in GraphicsView - - """ - :class:`GraphicsView ` widget with a single - :class:`PlotItem ` inside. - - The following methods are wrapped directly from PlotItem: - :func:`addItem `, - :func:`removeItem `, - :func:`clear `, - :func:`setXRange `, - :func:`setYRange `, - :func:`setRange `, - :func:`autoRange `, - :func:`setXLink `, - :func:`setYLink `, - :func:`viewRect `, - :func:`setMouseEnabled `, - :func:`enableAutoRange `, - :func:`disableAutoRange `, - :func:`setAspectLocked `, - :func:`register `, - :func:`unregister ` - - - For all - other methods, use :func:`getPlotItem `. - """ - def __init__(self, parent=None, background='default', **kargs): - """When initializing PlotWidget, *parent* and *background* are passed to - :func:`GraphicsWidget.__init__() ` - and all others are passed - to :func:`PlotItem.__init__() `.""" - GraphicsView.__init__(self, parent, background=background) - self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - self.enableMouse(False) - self.plotItem = PlotItem(**kargs) - self.setCentralItem(self.plotItem) - ## Explicitly wrap methods from plotItem - ## NOTE: If you change this list, update the documentation above as well. - for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setXRange', 'setYRange', 'setRange', 'setAspectLocked', 'setMouseEnabled', 'setXLink', 'setYLink', 'enableAutoRange', 'disableAutoRange', 'register', 'unregister', 'viewRect']: - setattr(self, m, getattr(self.plotItem, m)) - #QtCore.QObject.connect(self.plotItem, QtCore.SIGNAL('viewChanged'), self.viewChanged) - self.plotItem.sigRangeChanged.connect(self.viewRangeChanged) - - def close(self): - self.plotItem.close() - self.plotItem = None - #self.scene().clear() - #self.mPlotItem.close() - self.setParent(None) - GraphicsView.close(self) - - def __getattr__(self, attr): ## implicitly wrap methods from plotItem - if hasattr(self.plotItem, attr): - m = getattr(self.plotItem, attr) - if hasattr(m, '__call__'): - return m - raise NameError(attr) - - def viewRangeChanged(self, view, range): - #self.emit(QtCore.SIGNAL('viewChanged'), *args) - self.sigRangeChanged.emit(self, range) - - def widgetGroupInterface(self): - return (None, PlotWidget.saveState, PlotWidget.restoreState) - - def saveState(self): - return self.plotItem.saveState() - - def restoreState(self, state): - return self.plotItem.restoreState(state) - - def getPlotItem(self): - """Return the PlotItem contained within.""" - return self.plotItem - - - \ No newline at end of file diff --git a/pyqtgraph/widgets/ProgressDialog.py b/pyqtgraph/widgets/ProgressDialog.py deleted file mode 100644 index 0f55e227..00000000 --- a/pyqtgraph/widgets/ProgressDialog.py +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore - -__all__ = ['ProgressDialog'] -class ProgressDialog(QtGui.QProgressDialog): - """ - Extends QProgressDialog for use in 'with' statements. - - Example:: - - with ProgressDialog("Processing..", minVal, maxVal) as dlg: - # do stuff - dlg.setValue(i) ## could also use dlg += 1 - if dlg.wasCanceled(): - raise Exception("Processing canceled by user") - """ - def __init__(self, labelText, minimum=0, maximum=100, cancelText='Cancel', parent=None, wait=250, busyCursor=False, disable=False): - """ - ============== ================================================================ - **Arguments:** - labelText (required) - cancelText Text to display on cancel button, or None to disable it. - minimum - maximum - parent - wait Length of time (im ms) to wait before displaying dialog - busyCursor If True, show busy cursor until dialog finishes - disable If True, the progress dialog will not be displayed - and calls to wasCanceled() will always return False. - If ProgressDialog is entered from a non-gui thread, it will - always be disabled. - ============== ================================================================ - """ - isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() - self.disabled = disable or (not isGuiThread) - if self.disabled: - return - - noCancel = False - if cancelText is None: - cancelText = '' - noCancel = True - - self.busyCursor = busyCursor - - QtGui.QProgressDialog.__init__(self, labelText, cancelText, minimum, maximum, parent) - self.setMinimumDuration(wait) - self.setWindowModality(QtCore.Qt.WindowModal) - self.setValue(self.minimum()) - if noCancel: - self.setCancelButton(None) - - - def __enter__(self): - if self.disabled: - return self - if self.busyCursor: - QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) - return self - - def __exit__(self, exType, exValue, exTrace): - if self.disabled: - return - if self.busyCursor: - QtGui.QApplication.restoreOverrideCursor() - self.setValue(self.maximum()) - - def __iadd__(self, val): - """Use inplace-addition operator for easy incrementing.""" - if self.disabled: - return self - self.setValue(self.value()+val) - return self - - - ## wrap all other functions to make sure they aren't being called from non-gui threads - - def setValue(self, val): - if self.disabled: - return - QtGui.QProgressDialog.setValue(self, val) - - def setLabelText(self, val): - if self.disabled: - return - QtGui.QProgressDialog.setLabelText(self, val) - - def setMaximum(self, val): - if self.disabled: - return - QtGui.QProgressDialog.setMaximum(self, val) - - def setMinimum(self, val): - if self.disabled: - return - QtGui.QProgressDialog.setMinimum(self, val) - - def wasCanceled(self): - if self.disabled: - return False - return QtGui.QProgressDialog.wasCanceled(self) - - def maximum(self): - if self.disabled: - return 0 - return QtGui.QProgressDialog.maximum(self) - - def minimum(self): - if self.disabled: - return 0 - return QtGui.QProgressDialog.minimum(self) - diff --git a/pyqtgraph/widgets/RawImageWidget.py b/pyqtgraph/widgets/RawImageWidget.py deleted file mode 100644 index 517f4f99..00000000 --- a/pyqtgraph/widgets/RawImageWidget.py +++ /dev/null @@ -1,140 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui -try: - from pyqtgraph.Qt import QtOpenGL - from OpenGL.GL import * - HAVE_OPENGL = True -except ImportError: - HAVE_OPENGL = False - -import pyqtgraph.functions as fn -import numpy as np - -class RawImageWidget(QtGui.QWidget): - """ - Widget optimized for very fast video display. - Generally using an ImageItem inside GraphicsView is fast enough. - On some systems this may provide faster video. See the VideoSpeedTest example for benchmarking. - """ - def __init__(self, parent=None, scaled=False): - """ - Setting scaled=True will cause the entire image to be displayed within the boundaries of the widget. This also greatly reduces the speed at which it will draw frames. - """ - QtGui.QWidget.__init__(self, parent=None) - self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)) - self.scaled = scaled - self.opts = None - self.image = None - - def setImage(self, img, *args, **kargs): - """ - img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). - Extra arguments are sent to functions.makeARGB - """ - self.opts = (img, args, kargs) - self.image = None - self.update() - - def paintEvent(self, ev): - if self.opts is None: - return - if self.image is None: - argb, alpha = fn.makeARGB(self.opts[0], *self.opts[1], **self.opts[2]) - self.image = fn.makeQImage(argb, alpha) - self.opts = () - #if self.pixmap is None: - #self.pixmap = QtGui.QPixmap.fromImage(self.image) - p = QtGui.QPainter(self) - if self.scaled: - rect = self.rect() - ar = rect.width() / float(rect.height()) - imar = self.image.width() / float(self.image.height()) - if ar > imar: - rect.setWidth(int(rect.width() * imar/ar)) - else: - rect.setHeight(int(rect.height() * ar/imar)) - - p.drawImage(rect, self.image) - else: - p.drawImage(QtCore.QPointF(), self.image) - #p.drawPixmap(self.rect(), self.pixmap) - p.end() - -if HAVE_OPENGL: - class RawImageGLWidget(QtOpenGL.QGLWidget): - """ - Similar to RawImageWidget, but uses a GL widget to do all drawing. - Perfomance varies between platforms; see examples/VideoSpeedTest for benchmarking. - """ - def __init__(self, parent=None, scaled=False): - QtOpenGL.QGLWidget.__init__(self, parent=None) - self.scaled = scaled - self.image = None - self.uploaded = False - self.smooth = False - self.opts = None - - def setImage(self, img, *args, **kargs): - """ - img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). - Extra arguments are sent to functions.makeARGB - """ - self.opts = (img, args, kargs) - self.image = None - self.uploaded = False - self.update() - - def initializeGL(self): - self.texture = glGenTextures(1) - - def uploadTexture(self): - glEnable(GL_TEXTURE_2D) - glBindTexture(GL_TEXTURE_2D, self.texture) - if self.smooth: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) - else: - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) - #glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) - shape = self.image.shape - - ### Test texture dimensions first - #glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) - #if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: - #raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.image.transpose((1,0,2))) - glDisable(GL_TEXTURE_2D) - - def paintGL(self): - if self.image is None: - if self.opts is None: - return - img, args, kwds = self.opts - kwds['useRGBA'] = True - self.image, alpha = fn.makeARGB(img, *args, **kwds) - - if not self.uploaded: - self.uploadTexture() - - glViewport(0, 0, self.width(), self.height()) - glEnable(GL_TEXTURE_2D) - glBindTexture(GL_TEXTURE_2D, self.texture) - glColor4f(1,1,1,1) - - glBegin(GL_QUADS) - glTexCoord2f(0,0) - glVertex3f(-1,-1,0) - glTexCoord2f(1,0) - glVertex3f(1, -1, 0) - glTexCoord2f(1,1) - glVertex3f(1, 1, 0) - glTexCoord2f(0,1) - glVertex3f(-1, 1, 0) - glEnd() - glDisable(GL_TEXTURE_3D) - - - diff --git a/pyqtgraph/widgets/RemoteGraphicsView.py b/pyqtgraph/widgets/RemoteGraphicsView.py deleted file mode 100644 index d44fd1c3..00000000 --- a/pyqtgraph/widgets/RemoteGraphicsView.py +++ /dev/null @@ -1,261 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE -if not USE_PYSIDE: - import sip -import pyqtgraph.multiprocess as mp -import pyqtgraph as pg -from .GraphicsView import GraphicsView -import numpy as np -import mmap, tempfile, ctypes, atexit, sys, random - -__all__ = ['RemoteGraphicsView'] - -class RemoteGraphicsView(QtGui.QWidget): - """ - Replacement for GraphicsView that does all scene management and rendering on a remote process, - while displaying on the local widget. - - GraphicsItems must be created by proxy to the remote process. - - """ - def __init__(self, parent=None, *args, **kwds): - """ - The keyword arguments 'useOpenGL' and 'backgound', if specified, are passed to the remote - GraphicsView.__init__(). All other keyword arguments are passed to multiprocess.QtProcess.__init__(). - """ - self._img = None - self._imgReq = None - self._sizeHint = (640,480) ## no clue why this is needed, but it seems to be the default sizeHint for GraphicsView. - ## without it, the widget will not compete for space against another GraphicsView. - QtGui.QWidget.__init__(self) - - # separate local keyword arguments from remote. - remoteKwds = {} - for kwd in ['useOpenGL', 'background']: - if kwd in kwds: - remoteKwds[kwd] = kwds.pop(kwd) - - self._proc = mp.QtProcess(**kwds) - self.pg = self._proc._import('pyqtgraph') - self.pg.setConfigOptions(**self.pg.CONFIG_OPTIONS) - rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView') - self._view = rpgRemote.Renderer(*args, **remoteKwds) - self._view._setProxyOptions(deferGetattr=True) - - self.setFocusPolicy(QtCore.Qt.StrongFocus) - self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - self.setMouseTracking(True) - self.shm = None - shmFileName = self._view.shmFileName() - if sys.platform.startswith('win'): - self.shmtag = shmFileName - else: - self.shmFile = open(shmFileName, 'r') - - self._view.sceneRendered.connect(mp.proxy(self.remoteSceneChanged)) #, callSync='off')) - ## Note: we need synchronous signals - ## even though there is no return value-- - ## this informs the renderer that it is - ## safe to begin rendering again. - - for method in ['scene', 'setCentralItem']: - setattr(self, method, getattr(self._view, method)) - - def resizeEvent(self, ev): - ret = QtGui.QWidget.resizeEvent(self, ev) - self._view.resize(self.size(), _callSync='off') - return ret - - def sizeHint(self): - return QtCore.QSize(*self._sizeHint) - - def remoteSceneChanged(self, data): - w, h, size, newfile = data - #self._sizeHint = (whint, hhint) - if self.shm is None or self.shm.size != size: - if self.shm is not None: - self.shm.close() - if sys.platform.startswith('win'): - self.shmtag = newfile ## on windows, we create a new tag for every resize - self.shm = mmap.mmap(-1, size, self.shmtag) ## can't use tmpfile on windows because the file can only be opened once. - else: - self.shm = mmap.mmap(self.shmFile.fileno(), size, mmap.MAP_SHARED, mmap.PROT_READ) - self.shm.seek(0) - data = self.shm.read(w*h*4) - self._img = QtGui.QImage(data, w, h, QtGui.QImage.Format_ARGB32) - self._img.data = data # data must be kept alive or PySide 1.2.1 (and probably earlier) will crash. - self.update() - - def paintEvent(self, ev): - if self._img is None: - return - p = QtGui.QPainter(self) - p.drawImage(self.rect(), self._img, QtCore.QRect(0, 0, self._img.width(), self._img.height())) - p.end() - - def mousePressEvent(self, ev): - self._view.mousePressEvent(int(ev.type()), ev.pos(), ev.globalPos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') - ev.accept() - return QtGui.QWidget.mousePressEvent(self, ev) - - def mouseReleaseEvent(self, ev): - self._view.mouseReleaseEvent(int(ev.type()), ev.pos(), ev.globalPos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') - ev.accept() - return QtGui.QWidget.mouseReleaseEvent(self, ev) - - def mouseMoveEvent(self, ev): - self._view.mouseMoveEvent(int(ev.type()), ev.pos(), ev.globalPos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') - ev.accept() - return QtGui.QWidget.mouseMoveEvent(self, ev) - - def wheelEvent(self, ev): - self._view.wheelEvent(ev.pos(), ev.globalPos(), ev.delta(), int(ev.buttons()), int(ev.modifiers()), ev.orientation(), _callSync='off') - ev.accept() - return QtGui.QWidget.wheelEvent(self, ev) - - def keyEvent(self, ev): - if self._view.keyEvent(int(ev.type()), int(ev.modifiers()), text, autorep, count): - ev.accept() - return QtGui.QWidget.keyEvent(self, ev) - - def enterEvent(self, ev): - self._view.enterEvent(int(ev.type()), _callSync='off') - return QtGui.QWidget.enterEvent(self, ev) - - def leaveEvent(self, ev): - self._view.leaveEvent(int(ev.type()), _callSync='off') - return QtGui.QWidget.leaveEvent(self, ev) - - def remoteProcess(self): - """Return the remote process handle. (see multiprocess.remoteproxy.RemoteEventHandler)""" - return self._proc - - def close(self): - """Close the remote process. After this call, the widget will no longer be updated.""" - self._proc.close() - - -class Renderer(GraphicsView): - ## Created by the remote process to handle render requests - - sceneRendered = QtCore.Signal(object) - - def __init__(self, *args, **kwds): - ## Create shared memory for rendered image - #pg.dbg(namespace={'r': self}) - if sys.platform.startswith('win'): - self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)]) - self.shm = mmap.mmap(-1, mmap.PAGESIZE, self.shmtag) # use anonymous mmap on windows - else: - self.shmFile = tempfile.NamedTemporaryFile(prefix='pyqtgraph_shmem_') - self.shmFile.write(b'\x00' * (mmap.PAGESIZE+1)) - fd = self.shmFile.fileno() - self.shm = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE) - atexit.register(self.close) - - GraphicsView.__init__(self, *args, **kwds) - self.scene().changed.connect(self.update) - self.img = None - self.renderTimer = QtCore.QTimer() - self.renderTimer.timeout.connect(self.renderView) - self.renderTimer.start(16) - - def close(self): - self.shm.close() - if not sys.platform.startswith('win'): - self.shmFile.close() - - def shmFileName(self): - if sys.platform.startswith('win'): - return self.shmtag - else: - return self.shmFile.name - - def update(self): - self.img = None - return GraphicsView.update(self) - - def resize(self, size): - oldSize = self.size() - GraphicsView.resize(self, size) - self.resizeEvent(QtGui.QResizeEvent(size, oldSize)) - self.update() - - def renderView(self): - if self.img is None: - ## make sure shm is large enough and get its address - if self.width() == 0 or self.height() == 0: - return - size = self.width() * self.height() * 4 - if size > self.shm.size(): - if sys.platform.startswith('win'): - ## windows says "WindowsError: [Error 87] the parameter is incorrect" if we try to resize the mmap - self.shm.close() - ## it also says (sometimes) 'access is denied' if we try to reuse the tag. - self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)]) - self.shm = mmap.mmap(-1, size, self.shmtag) - else: - self.shm.resize(size) - - ## render the scene directly to shared memory - if USE_PYSIDE: - ch = ctypes.c_char.from_buffer(self.shm, 0) - #ch = ctypes.c_char_p(address) - self.img = QtGui.QImage(ch, self.width(), self.height(), QtGui.QImage.Format_ARGB32) - else: - address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0)) - - # different versions of pyqt have different requirements here.. - try: - self.img = QtGui.QImage(sip.voidptr(address), self.width(), self.height(), QtGui.QImage.Format_ARGB32) - except TypeError: - try: - self.img = QtGui.QImage(memoryview(buffer(self.shm)), self.width(), self.height(), QtGui.QImage.Format_ARGB32) - except TypeError: - # Works on PyQt 4.9.6 - self.img = QtGui.QImage(address, self.width(), self.height(), QtGui.QImage.Format_ARGB32) - self.img.fill(0xffffffff) - p = QtGui.QPainter(self.img) - self.render(p, self.viewRect(), self.rect()) - p.end() - self.sceneRendered.emit((self.width(), self.height(), self.shm.size(), self.shmFileName())) - - def mousePressEvent(self, typ, pos, gpos, btn, btns, mods): - typ = QtCore.QEvent.Type(typ) - btn = QtCore.Qt.MouseButton(btn) - btns = QtCore.Qt.MouseButtons(btns) - mods = QtCore.Qt.KeyboardModifiers(mods) - return GraphicsView.mousePressEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) - - def mouseMoveEvent(self, typ, pos, gpos, btn, btns, mods): - typ = QtCore.QEvent.Type(typ) - btn = QtCore.Qt.MouseButton(btn) - btns = QtCore.Qt.MouseButtons(btns) - mods = QtCore.Qt.KeyboardModifiers(mods) - return GraphicsView.mouseMoveEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) - - def mouseReleaseEvent(self, typ, pos, gpos, btn, btns, mods): - typ = QtCore.QEvent.Type(typ) - btn = QtCore.Qt.MouseButton(btn) - btns = QtCore.Qt.MouseButtons(btns) - mods = QtCore.Qt.KeyboardModifiers(mods) - return GraphicsView.mouseReleaseEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) - - def wheelEvent(self, pos, gpos, d, btns, mods, ori): - btns = QtCore.Qt.MouseButtons(btns) - mods = QtCore.Qt.KeyboardModifiers(mods) - return GraphicsView.wheelEvent(self, QtGui.QWheelEvent(pos, gpos, d, btns, mods, ori)) - - def keyEvent(self, typ, mods, text, autorep, count): - typ = QtCore.QEvent.Type(typ) - mods = QtCore.Qt.KeyboardModifiers(mods) - GraphicsView.keyEvent(self, QtGui.QKeyEvent(typ, mods, text, autorep, count)) - return ev.accepted() - - def enterEvent(self, typ): - ev = QtCore.QEvent(QtCore.QEvent.Type(typ)) - return GraphicsView.enterEvent(self, ev) - - def leaveEvent(self, typ): - ev = QtCore.QEvent(QtCore.QEvent.Type(typ)) - return GraphicsView.leaveEvent(self, ev) - diff --git a/pyqtgraph/widgets/ScatterPlotWidget.py b/pyqtgraph/widgets/ScatterPlotWidget.py deleted file mode 100644 index e9e24dd7..00000000 --- a/pyqtgraph/widgets/ScatterPlotWidget.py +++ /dev/null @@ -1,216 +0,0 @@ -from pyqtgraph.Qt import QtGui, QtCore -from .PlotWidget import PlotWidget -from .DataFilterWidget import DataFilterParameter -from .ColorMapWidget import ColorMapParameter -import pyqtgraph.parametertree as ptree -import pyqtgraph.functions as fn -import numpy as np -from pyqtgraph.pgcollections import OrderedDict -import pyqtgraph as pg - -__all__ = ['ScatterPlotWidget'] - -class ScatterPlotWidget(QtGui.QSplitter): - """ - Given a record array, display a scatter plot of a specific set of data. - This widget includes controls for selecting the columns to plot, - filtering data, and determining symbol color and shape. This widget allows - the user to explore relationships between columns in a record array. - - The widget consists of four components: - - 1) A list of column names from which the user may select 1 or 2 columns - to plot. If one column is selected, the data for that column will be - plotted in a histogram-like manner by using :func:`pseudoScatter() - `. If two columns are selected, then the - scatter plot will be generated with x determined by the first column - that was selected and y by the second. - 2) A DataFilter that allows the user to select a subset of the data by - specifying multiple selection criteria. - 3) A ColorMap that allows the user to determine how points are colored by - specifying multiple criteria. - 4) A PlotWidget for displaying the data. - """ - def __init__(self, parent=None): - QtGui.QSplitter.__init__(self, QtCore.Qt.Horizontal) - self.ctrlPanel = QtGui.QSplitter(QtCore.Qt.Vertical) - self.addWidget(self.ctrlPanel) - self.fieldList = QtGui.QListWidget() - self.fieldList.setSelectionMode(self.fieldList.ExtendedSelection) - self.ptree = ptree.ParameterTree(showHeader=False) - self.filter = DataFilterParameter() - self.colorMap = ColorMapParameter() - self.params = ptree.Parameter.create(name='params', type='group', children=[self.filter, self.colorMap]) - self.ptree.setParameters(self.params, showTop=False) - - self.plot = PlotWidget() - self.ctrlPanel.addWidget(self.fieldList) - self.ctrlPanel.addWidget(self.ptree) - self.addWidget(self.plot) - - bg = pg.mkColor(pg.getConfigOption('background')) - bg.setAlpha(150) - self.filterText = pg.TextItem(border=pg.getConfigOption('foreground'), color=bg) - self.filterText.setPos(60,20) - self.filterText.setParentItem(self.plot.plotItem) - - self.data = None - self.mouseOverField = None - self.scatterPlot = None - self.style = dict(pen=None, symbol='o') - - self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged) - self.filter.sigFilterChanged.connect(self.filterChanged) - self.colorMap.sigColorMapChanged.connect(self.updatePlot) - - def setFields(self, fields, mouseOverField=None): - """ - Set the list of field names/units to be processed. - - The format of *fields* is the same as used by - :func:`ColorMapWidget.setFields ` - """ - self.fields = OrderedDict(fields) - self.mouseOverField = mouseOverField - self.fieldList.clear() - for f,opts in fields: - item = QtGui.QListWidgetItem(f) - item.opts = opts - item = self.fieldList.addItem(item) - self.filter.setFields(fields) - self.colorMap.setFields(fields) - - def setData(self, data): - """ - Set the data to be processed and displayed. - Argument must be a numpy record array. - """ - self.data = data - self.filtered = None - self.updatePlot() - - def fieldSelectionChanged(self): - sel = self.fieldList.selectedItems() - if len(sel) > 2: - self.fieldList.blockSignals(True) - try: - for item in sel[1:-1]: - item.setSelected(False) - finally: - self.fieldList.blockSignals(False) - - self.updatePlot() - - def filterChanged(self, f): - self.filtered = None - self.updatePlot() - desc = self.filter.describe() - if len(desc) == 0: - self.filterText.setVisible(False) - else: - self.filterText.setText('\n'.join(desc)) - self.filterText.setVisible(True) - - - def updatePlot(self): - self.plot.clear() - if self.data is None: - return - - if self.filtered is None: - self.filtered = self.filter.filterData(self.data) - data = self.filtered - if len(data) == 0: - return - - colors = np.array([fn.mkBrush(*x) for x in self.colorMap.map(data)]) - - style = self.style.copy() - - ## Look up selected columns and units - sel = list([str(item.text()) for item in self.fieldList.selectedItems()]) - units = list([item.opts.get('units', '') for item in self.fieldList.selectedItems()]) - if len(sel) == 0: - self.plot.setTitle('') - return - - - if len(sel) == 1: - self.plot.setLabels(left=('N', ''), bottom=(sel[0], units[0]), title='') - if len(data) == 0: - return - #x = data[sel[0]] - #y = None - xy = [data[sel[0]], None] - elif len(sel) == 2: - self.plot.setLabels(left=(sel[1],units[1]), bottom=(sel[0],units[0])) - if len(data) == 0: - return - - xy = [data[sel[0]], data[sel[1]]] - #xydata = [] - #for ax in [0,1]: - #d = data[sel[ax]] - ### scatter catecorical values just a bit so they show up better in the scatter plot. - ##if sel[ax] in ['MorphologyBSMean', 'MorphologyTDMean', 'FIType']: - ##d += np.random.normal(size=len(cells), scale=0.1) - - #xydata.append(d) - #x,y = xydata - - ## convert enum-type fields to float, set axis labels - enum = [False, False] - for i in [0,1]: - axis = self.plot.getAxis(['bottom', 'left'][i]) - if xy[i] is not None and (self.fields[sel[i]].get('mode', None) == 'enum' or xy[i].dtype.kind in ('S', 'O')): - vals = self.fields[sel[i]].get('values', list(set(xy[i]))) - xy[i] = np.array([vals.index(x) if x in vals else len(vals) for x in xy[i]], dtype=float) - axis.setTicks([list(enumerate(vals))]) - enum[i] = True - else: - axis.setTicks(None) # reset to automatic ticking - - ## mask out any nan values - mask = np.ones(len(xy[0]), dtype=bool) - if xy[0].dtype.kind == 'f': - mask &= ~np.isnan(xy[0]) - if xy[1] is not None and xy[1].dtype.kind == 'f': - mask &= ~np.isnan(xy[1]) - - xy[0] = xy[0][mask] - style['symbolBrush'] = colors[mask] - - ## Scatter y-values for a histogram-like appearance - if xy[1] is None: - ## column scatter plot - xy[1] = fn.pseudoScatter(xy[0]) - else: - ## beeswarm plots - xy[1] = xy[1][mask] - for ax in [0,1]: - if not enum[ax]: - continue - imax = int(xy[ax].max()) if len(xy[ax]) > 0 else 0 - for i in range(imax+1): - keymask = xy[ax] == i - scatter = pg.pseudoScatter(xy[1-ax][keymask], bidir=True) - if len(scatter) == 0: - continue - smax = np.abs(scatter).max() - if smax != 0: - scatter *= 0.2 / smax - xy[ax][keymask] += scatter - - if self.scatterPlot is not None: - try: - self.scatterPlot.sigPointsClicked.disconnect(self.plotClicked) - except: - pass - self.scatterPlot = self.plot.plot(xy[0], xy[1], data=data[mask], **style) - self.scatterPlot.sigPointsClicked.connect(self.plotClicked) - - - def plotClicked(self, plot, points): - pass - - diff --git a/pyqtgraph/widgets/SpinBox.py b/pyqtgraph/widgets/SpinBox.py deleted file mode 100644 index 57e4f1ed..00000000 --- a/pyqtgraph/widgets/SpinBox.py +++ /dev/null @@ -1,503 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.python2_3 import asUnicode -from pyqtgraph.SignalProxy import SignalProxy - -import pyqtgraph.functions as fn -from math import log -from decimal import Decimal as D ## Use decimal to avoid accumulating floating-point errors -from decimal import * -import weakref - -__all__ = ['SpinBox'] -class SpinBox(QtGui.QAbstractSpinBox): - """ - **Bases:** QtGui.QAbstractSpinBox - - QSpinBox widget on steroids. Allows selection of numerical value, with extra features: - - - SI prefix notation (eg, automatically display "300 mV" instead of "0.003 V") - - Float values with linear and decimal stepping (1-9, 10-90, 100-900, etc.) - - Option for unbounded values - - Delayed signals (allows multiple rapid changes with only one change signal) - - ============================= ============================================== - **Signals:** - valueChanged(value) Same as QSpinBox; emitted every time the value - has changed. - sigValueChanged(self) Emitted when value has changed, but also combines - multiple rapid changes into one signal (eg, - when rolling the mouse wheel). - sigValueChanging(self, value) Emitted immediately for all value changes. - ============================= ============================================== - """ - - ## There's a PyQt bug that leaks a reference to the - ## QLineEdit returned from QAbstractSpinBox.lineEdit() - ## This makes it possible to crash the entire program - ## by making accesses to the LineEdit after the spinBox has been deleted. - ## I have no idea how to get around this.. - - - valueChanged = QtCore.Signal(object) # (value) for compatibility with QSpinBox - sigValueChanged = QtCore.Signal(object) # (self) - sigValueChanging = QtCore.Signal(object, object) # (self, value) sent immediately; no delay. - - def __init__(self, parent=None, value=0.0, **kwargs): - """ - ============== ======================================================================== - **Arguments:** - parent Sets the parent widget for this SpinBox (optional) - value (float/int) initial value - bounds (min,max) Minimum and maximum values allowed in the SpinBox. - Either may be None to leave the value unbounded. - suffix (str) suffix (units) to display after the numerical value - siPrefix (bool) If True, then an SI prefix is automatically prepended - to the units and the value is scaled accordingly. For example, - if value=0.003 and suffix='V', then the SpinBox will display - "300 mV" (but a call to SpinBox.value will still return 0.003). - step (float) The size of a single step. This is used when clicking the up/ - down arrows, when rolling the mouse wheel, or when pressing - keyboard arrows while the widget has keyboard focus. Note that - the interpretation of this value is different when specifying - the 'dec' argument. - dec (bool) If True, then the step value will be adjusted to match - the current size of the variable (for example, a value of 15 - might step in increments of 1 whereas a value of 1500 would - step in increments of 100). In this case, the 'step' argument - is interpreted *relative* to the current value. The most common - 'step' values when dec=True are 0.1, 0.2, 0.5, and 1.0. - minStep (float) When dec=True, this specifies the minimum allowable step size. - int (bool) if True, the value is forced to integer type - decimals (int) Number of decimal values to display - ============== ======================================================================== - """ - QtGui.QAbstractSpinBox.__init__(self, parent) - self.lastValEmitted = None - self.lastText = '' - self.textValid = True ## If false, we draw a red border - self.setMinimumWidth(0) - self.setMaximumHeight(20) - self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) - self.opts = { - 'bounds': [None, None], - - ## Log scaling options #### Log mode is no longer supported. - #'step': 0.1, - #'minStep': 0.001, - #'log': True, - #'dec': False, - - ## decimal scaling option - example - #'step': 0.1, - #'minStep': .001, - #'log': False, - #'dec': True, - - ## normal arithmetic step - 'step': D('0.01'), ## if 'dec' is false, the spinBox steps by 'step' every time - ## if 'dec' is True, the step size is relative to the value - ## 'step' needs to be an integral divisor of ten, ie 'step'*n=10 for some integer value of n (but only if dec is True) - 'log': False, - 'dec': False, ## if true, does decimal stepping. ie from 1-10 it steps by 'step', from 10 to 100 it steps by 10*'step', etc. - ## if true, minStep must be set in order to cross zero. - - - 'int': False, ## Set True to force value to be integer - - 'suffix': '', - 'siPrefix': False, ## Set to True to display numbers with SI prefix (ie, 100pA instead of 1e-10A) - - 'delay': 0.3, ## delay sending wheel update signals for 300ms - - 'delayUntilEditFinished': True, ## do not send signals until text editing has finished - - ## for compatibility with QDoubleSpinBox and QSpinBox - 'decimals': 2, - - } - - self.decOpts = ['step', 'minStep'] - - self.val = D(asUnicode(value)) ## Value is precise decimal. Ordinary math not allowed. - self.updateText() - self.skipValidate = False - self.setCorrectionMode(self.CorrectToPreviousValue) - self.setKeyboardTracking(False) - self.setOpts(**kwargs) - - - self.editingFinished.connect(self.editingFinishedEvent) - self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange, delay=self.opts['delay']) - - def event(self, ev): - ret = QtGui.QAbstractSpinBox.event(self, ev) - if ev.type() == QtCore.QEvent.KeyPress and ev.key() == QtCore.Qt.Key_Return: - ret = True ## For some reason, spinbox pretends to ignore return key press - return ret - - ##lots of config options, just gonna stuff 'em all in here rather than do the get/set crap. - def setOpts(self, **opts): - """ - Changes the behavior of the SpinBox. Accepts most of the arguments - allowed in :func:`__init__ `. - - """ - #print opts - for k in opts: - if k == 'bounds': - #print opts[k] - self.setMinimum(opts[k][0], update=False) - self.setMaximum(opts[k][1], update=False) - #for i in [0,1]: - #if opts[k][i] is None: - #self.opts[k][i] = None - #else: - #self.opts[k][i] = D(unicode(opts[k][i])) - elif k in ['step', 'minStep']: - self.opts[k] = D(asUnicode(opts[k])) - elif k == 'value': - pass ## don't set value until bounds have been set - else: - self.opts[k] = opts[k] - if 'value' in opts: - self.setValue(opts['value']) - - ## If bounds have changed, update value to match - if 'bounds' in opts and 'value' not in opts: - self.setValue() - - ## sanity checks: - if self.opts['int']: - if 'step' in opts: - step = opts['step'] - ## not necessary.. - #if int(step) != step: - #raise Exception('Integer SpinBox must have integer step size.') - else: - self.opts['step'] = int(self.opts['step']) - - if 'minStep' in opts: - step = opts['minStep'] - if int(step) != step: - raise Exception('Integer SpinBox must have integer minStep size.') - else: - ms = int(self.opts.get('minStep', 1)) - if ms < 1: - ms = 1 - self.opts['minStep'] = ms - - if 'delay' in opts: - self.proxy.setDelay(opts['delay']) - - self.updateText() - - - - def setMaximum(self, m, update=True): - """Set the maximum allowed value (or None for no limit)""" - if m is not None: - m = D(asUnicode(m)) - self.opts['bounds'][1] = m - if update: - self.setValue() - - def setMinimum(self, m, update=True): - """Set the minimum allowed value (or None for no limit)""" - if m is not None: - m = D(asUnicode(m)) - self.opts['bounds'][0] = m - if update: - self.setValue() - - def setPrefix(self, p): - self.setOpts(prefix=p) - - def setRange(self, r0, r1): - self.setOpts(bounds = [r0,r1]) - - def setProperty(self, prop, val): - ## for QSpinBox compatibility - if prop == 'value': - #if type(val) is QtCore.QVariant: - #val = val.toDouble()[0] - self.setValue(val) - else: - print("Warning: SpinBox.setProperty('%s', ..) not supported." % prop) - - def setSuffix(self, suf): - self.setOpts(suffix=suf) - - def setSingleStep(self, step): - self.setOpts(step=step) - - def setDecimals(self, decimals): - self.setOpts(decimals=decimals) - - def value(self): - """ - Return the value of this SpinBox. - - """ - if self.opts['int']: - return int(self.val) - else: - return float(self.val) - - def setValue(self, value=None, update=True, delaySignal=False): - """ - Set the value of this spin. - If the value is out of bounds, it will be clipped to the nearest boundary. - If the spin is integer type, the value will be coerced to int. - Returns the actual value set. - - If value is None, then the current value is used (this is for resetting - the value after bounds, etc. have changed) - """ - - if value is None: - value = self.value() - - bounds = self.opts['bounds'] - if bounds[0] is not None and value < bounds[0]: - value = bounds[0] - if bounds[1] is not None and value > bounds[1]: - value = bounds[1] - - if self.opts['int']: - value = int(value) - - value = D(asUnicode(value)) - if value == self.val: - return - prev = self.val - - self.val = value - if update: - self.updateText(prev=prev) - - self.sigValueChanging.emit(self, float(self.val)) ## change will be emitted in 300ms if there are no subsequent changes. - if not delaySignal: - self.emitChanged() - - return value - - - def emitChanged(self): - self.lastValEmitted = self.val - self.valueChanged.emit(float(self.val)) - self.sigValueChanged.emit(self) - - def delayedChange(self): - try: - if self.val != self.lastValEmitted: - self.emitChanged() - except RuntimeError: - pass ## This can happen if we try to handle a delayed signal after someone else has already deleted the underlying C++ object. - - def widgetGroupInterface(self): - return (self.valueChanged, SpinBox.value, SpinBox.setValue) - - def sizeHint(self): - return QtCore.QSize(120, 0) - - - def stepEnabled(self): - return self.StepUpEnabled | self.StepDownEnabled - - #def fixup(self, *args): - #print "fixup:", args - - def stepBy(self, n): - n = D(int(n)) ## n must be integral number of steps. - s = [D(-1), D(1)][n >= 0] ## determine sign of step - val = self.val - - for i in range(int(abs(n))): - - if self.opts['log']: - raise Exception("Log mode no longer supported.") - # step = abs(val) * self.opts['step'] - # if 'minStep' in self.opts: - # step = max(step, self.opts['minStep']) - # val += step * s - if self.opts['dec']: - if val == 0: - step = self.opts['minStep'] - exp = None - else: - vs = [D(-1), D(1)][val >= 0] - #exp = D(int(abs(val*(D('1.01')**(s*vs))).log10())) - fudge = D('1.01')**(s*vs) ## fudge factor. at some places, the step size depends on the step sign. - exp = abs(val * fudge).log10().quantize(1, ROUND_FLOOR) - step = self.opts['step'] * D(10)**exp - if 'minStep' in self.opts: - step = max(step, self.opts['minStep']) - val += s * step - #print "Exp:", exp, "step", step, "val", val - else: - val += s*self.opts['step'] - - if 'minStep' in self.opts and abs(val) < self.opts['minStep']: - val = D(0) - self.setValue(val, delaySignal=True) ## note all steps (arrow buttons, wheel, up/down keys..) emit delayed signals only. - - - def valueInRange(self, value): - bounds = self.opts['bounds'] - if bounds[0] is not None and value < bounds[0]: - return False - if bounds[1] is not None and value > bounds[1]: - return False - if self.opts.get('int', False): - if int(value) != value: - return False - return True - - - def updateText(self, prev=None): - #print "Update text." - self.skipValidate = True - if self.opts['siPrefix']: - if self.val == 0 and prev is not None: - (s, p) = fn.siScale(prev) - txt = "0.0 %s%s" % (p, self.opts['suffix']) - else: - txt = fn.siFormat(float(self.val), suffix=self.opts['suffix']) - else: - txt = '%g%s' % (self.val , self.opts['suffix']) - self.lineEdit().setText(txt) - self.lastText = txt - self.skipValidate = False - - def validate(self, strn, pos): - if self.skipValidate: - #print "skip validate" - #self.textValid = False - ret = QtGui.QValidator.Acceptable - else: - try: - ## first make sure we didn't mess with the suffix - suff = self.opts.get('suffix', '') - if len(suff) > 0 and asUnicode(strn)[-len(suff):] != suff: - #print '"%s" != "%s"' % (unicode(strn)[-len(suff):], suff) - ret = QtGui.QValidator.Invalid - - ## next see if we actually have an interpretable value - else: - val = self.interpret() - if val is False: - #print "can't interpret" - #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') - #self.textValid = False - ret = QtGui.QValidator.Intermediate - else: - if self.valueInRange(val): - if not self.opts['delayUntilEditFinished']: - self.setValue(val, update=False) - #print " OK:", self.val - #self.setStyleSheet('') - #self.textValid = True - - ret = QtGui.QValidator.Acceptable - else: - ret = QtGui.QValidator.Intermediate - - except: - #print " BAD" - #import sys - #sys.excepthook(*sys.exc_info()) - #self.textValid = False - #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') - ret = QtGui.QValidator.Intermediate - - ## draw / clear border - if ret == QtGui.QValidator.Intermediate: - self.textValid = False - elif ret == QtGui.QValidator.Acceptable: - self.textValid = True - ## note: if text is invalid, we don't change the textValid flag - ## since the text will be forced to its previous state anyway - self.update() - - ## support 2 different pyqt APIs. Bleh. - if hasattr(QtCore, 'QString'): - return (ret, pos) - else: - return (ret, strn, pos) - - def paintEvent(self, ev): - QtGui.QAbstractSpinBox.paintEvent(self, ev) - - ## draw red border if text is invalid - if not self.textValid: - p = QtGui.QPainter(self) - p.setRenderHint(p.Antialiasing) - p.setPen(fn.mkPen((200,50,50), width=2)) - p.drawRoundedRect(self.rect().adjusted(2, 2, -2, -2), 4, 4) - p.end() - - - def interpret(self): - """Return value of text. Return False if text is invalid, raise exception if text is intermediate""" - strn = self.lineEdit().text() - suf = self.opts['suffix'] - if len(suf) > 0: - if strn[-len(suf):] != suf: - return False - #raise Exception("Units are invalid.") - strn = strn[:-len(suf)] - try: - val = fn.siEval(strn) - except: - #sys.excepthook(*sys.exc_info()) - #print "invalid" - return False - #print val - return val - - #def interpretText(self, strn=None): - #print "Interpret:", strn - #if strn is None: - #strn = self.lineEdit().text() - #self.setValue(siEval(strn), update=False) - ##QtGui.QAbstractSpinBox.interpretText(self) - - - def editingFinishedEvent(self): - """Edit has finished; set value.""" - #print "Edit finished." - if asUnicode(self.lineEdit().text()) == self.lastText: - #print "no text change." - return - try: - val = self.interpret() - except: - return - - if val is False: - #print "value invalid:", str(self.lineEdit().text()) - return - if val == self.val: - #print "no value change:", val, self.val - return - self.setValue(val, delaySignal=False) ## allow text update so that values are reformatted pretty-like - - #def textChanged(self): - #print "Text changed." - - -### Drop-in replacement for SpinBox; just for crash-testing -#class SpinBox(QtGui.QDoubleSpinBox): - #valueChanged = QtCore.Signal(object) # (value) for compatibility with QSpinBox - #sigValueChanged = QtCore.Signal(object) # (self) - #sigValueChanging = QtCore.Signal(object) # (value) - #def __init__(self, parent=None, *args, **kargs): - #QtGui.QSpinBox.__init__(self, parent) - - #def __getattr__(self, attr): - #return lambda *args, **kargs: None - - #def widgetGroupInterface(self): - #return (self.valueChanged, SpinBox.value, SpinBox.setValue) - diff --git a/pyqtgraph/widgets/TableWidget.py b/pyqtgraph/widgets/TableWidget.py deleted file mode 100644 index 8ffe7291..00000000 --- a/pyqtgraph/widgets/TableWidget.py +++ /dev/null @@ -1,288 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore -from pyqtgraph.python2_3 import asUnicode - -import numpy as np -try: - import metaarray - HAVE_METAARRAY = True -except ImportError: - HAVE_METAARRAY = False - -__all__ = ['TableWidget'] -class TableWidget(QtGui.QTableWidget): - """Extends QTableWidget with some useful functions for automatic data handling - and copy / export context menu. Can automatically format and display a variety - of data types (see :func:`setData() ` for more - information. - """ - - def __init__(self, *args, **kwds): - QtGui.QTableWidget.__init__(self, *args) - self.setVerticalScrollMode(self.ScrollPerPixel) - self.setSelectionMode(QtGui.QAbstractItemView.ContiguousSelection) - self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) - self.setSortingEnabled(True) - self.clear() - editable = kwds.get('editable', False) - self.setEditable(editable) - self.contextMenu = QtGui.QMenu() - self.contextMenu.addAction('Copy Selection').triggered.connect(self.copySel) - self.contextMenu.addAction('Copy All').triggered.connect(self.copyAll) - self.contextMenu.addAction('Save Selection').triggered.connect(self.saveSel) - self.contextMenu.addAction('Save All').triggered.connect(self.saveAll) - - def clear(self): - """Clear all contents from the table.""" - QtGui.QTableWidget.clear(self) - self.verticalHeadersSet = False - self.horizontalHeadersSet = False - self.items = [] - self.setRowCount(0) - self.setColumnCount(0) - - def setData(self, data): - """Set the data displayed in the table. - Allowed formats are: - - * numpy arrays - * numpy record arrays - * metaarrays - * list-of-lists [[1,2,3], [4,5,6]] - * dict-of-lists {'x': [1,2,3], 'y': [4,5,6]} - * list-of-dicts [{'x': 1, 'y': 4}, {'x': 2, 'y': 5}, ...] - """ - self.clear() - self.appendData(data) - self.resizeColumnsToContents() - - def appendData(self, data): - """Types allowed: - 1 or 2D numpy array or metaArray - 1D numpy record array - list-of-lists, list-of-dicts or dict-of-lists - """ - fn0, header0 = self.iteratorFn(data) - if fn0 is None: - self.clear() - return - it0 = fn0(data) - try: - first = next(it0) - except StopIteration: - return - fn1, header1 = self.iteratorFn(first) - if fn1 is None: - self.clear() - return - - firstVals = [x for x in fn1(first)] - self.setColumnCount(len(firstVals)) - - if not self.verticalHeadersSet and header0 is not None: - self.setRowCount(len(header0)) - self.setVerticalHeaderLabels(header0) - self.verticalHeadersSet = True - if not self.horizontalHeadersSet and header1 is not None: - self.setHorizontalHeaderLabels(header1) - self.horizontalHeadersSet = True - - self.setRow(0, firstVals) - i = 1 - for row in it0: - self.setRow(i, [x for x in fn1(row)]) - i += 1 - - def setEditable(self, editable=True): - self.editable = editable - for item in self.items: - item.setEditable(editable) - - def iteratorFn(self, data): - ## Return 1) a function that will provide an iterator for data and 2) a list of header strings - if isinstance(data, list) or isinstance(data, tuple): - return lambda d: d.__iter__(), None - elif isinstance(data, dict): - return lambda d: iter(d.values()), list(map(str, data.keys())) - elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): - if data.axisHasColumns(0): - header = [str(data.columnName(0, i)) for i in range(data.shape[0])] - elif data.axisHasValues(0): - header = list(map(str, data.xvals(0))) - else: - header = None - return self.iterFirstAxis, header - elif isinstance(data, np.ndarray): - return self.iterFirstAxis, None - elif isinstance(data, np.void): - return self.iterate, list(map(str, data.dtype.names)) - elif data is None: - return (None,None) - else: - msg = "Don't know how to iterate over data type: {!s}".format(type(data)) - raise TypeError(msg) - - def iterFirstAxis(self, data): - for i in range(data.shape[0]): - yield data[i] - - def iterate(self, data): - # for numpy.void, which can be iterated but mysteriously - # has no __iter__ (??) - for x in data: - yield x - - def appendRow(self, data): - self.appendData([data]) - - def addRow(self, vals): - row = self.rowCount() - self.setRowCount(row + 1) - self.setRow(row, vals) - - def setRow(self, row, vals): - if row > self.rowCount() - 1: - self.setRowCount(row + 1) - for col in range(len(vals)): - val = vals[col] - item = TableWidgetItem(val) - item.setEditable(self.editable) - self.items.append(item) - self.setItem(row, col, item) - - def sizeHint(self): - # based on http://stackoverflow.com/a/7195443/54056 - width = sum(self.columnWidth(i) for i in range(self.columnCount())) - width += self.verticalHeader().sizeHint().width() - width += self.verticalScrollBar().sizeHint().width() - width += self.frameWidth() * 2 - height = sum(self.rowHeight(i) for i in range(self.rowCount())) - height += self.verticalHeader().sizeHint().height() - height += self.horizontalScrollBar().sizeHint().height() - return QtCore.QSize(width, height) - - def serialize(self, useSelection=False): - """Convert entire table (or just selected area) into tab-separated text values""" - if useSelection: - selection = self.selectedRanges()[0] - rows = list(range(selection.topRow(), - selection.bottomRow() + 1)) - columns = list(range(selection.leftColumn(), - selection.rightColumn() + 1)) - else: - rows = list(range(self.rowCount())) - columns = list(range(self.columnCount())) - - - data = [] - if self.horizontalHeadersSet: - row = [] - if self.verticalHeadersSet: - row.append(asUnicode('')) - - for c in columns: - row.append(asUnicode(self.horizontalHeaderItem(c).text())) - data.append(row) - - for r in rows: - row = [] - if self.verticalHeadersSet: - row.append(asUnicode(self.verticalHeaderItem(r).text())) - for c in columns: - item = self.item(r, c) - if item is not None: - row.append(asUnicode(item.value)) - else: - row.append(asUnicode('')) - data.append(row) - - s = '' - for row in data: - s += ('\t'.join(row) + '\n') - return s - - def copySel(self): - """Copy selected data to clipboard.""" - QtGui.QApplication.clipboard().setText(self.serialize(useSelection=True)) - - def copyAll(self): - """Copy all data to clipboard.""" - QtGui.QApplication.clipboard().setText(self.serialize(useSelection=False)) - - def saveSel(self): - """Save selected data to file.""" - self.save(self.serialize(useSelection=True)) - - def saveAll(self): - """Save all data to file.""" - self.save(self.serialize(useSelection=False)) - - def save(self, data): - fileName = QtGui.QFileDialog.getSaveFileName(self, "Save As..", "", "Tab-separated values (*.tsv)") - if fileName == '': - return - open(fileName, 'w').write(data) - - - def contextMenuEvent(self, ev): - self.contextMenu.popup(ev.globalPos()) - - def keyPressEvent(self, ev): - if ev.text() == 'c' and ev.modifiers() == QtCore.Qt.ControlModifier: - ev.accept() - self.copy() - else: - ev.ignore() - -class TableWidgetItem(QtGui.QTableWidgetItem): - def __init__(self, val): - if isinstance(val, float) or isinstance(val, np.floating): - s = "%0.3g" % val - else: - s = str(val) - QtGui.QTableWidgetItem.__init__(self, s) - self.value = val - flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled - self.setFlags(flags) - - def setEditable(self, editable): - if editable: - self.setFlags(self.flags() | QtCore.Qt.ItemIsEditable) - else: - self.setFlags(self.flags() & ~QtCore.Qt.ItemIsEditable) - - def __lt__(self, other): - if hasattr(other, 'value'): - return self.value < other.value - else: - return self.text() < other.text() - - -if __name__ == '__main__': - app = QtGui.QApplication([]) - win = QtGui.QMainWindow() - t = TableWidget() - win.setCentralWidget(t) - win.resize(800,600) - win.show() - - ll = [[1,2,3,4,5]] * 20 - ld = [{'x': 1, 'y': 2, 'z': 3}] * 20 - dl = {'x': list(range(20)), 'y': list(range(20)), 'z': list(range(20))} - - a = np.ones((20, 5)) - ra = np.ones((20,), dtype=[('x', int), ('y', int), ('z', int)]) - - t.setData(ll) - - if HAVE_METAARRAY: - ma = metaarray.MetaArray(np.ones((20, 3)), info=[ - {'values': np.linspace(1, 5, 20)}, - {'cols': [ - {'name': 'x'}, - {'name': 'y'}, - {'name': 'z'}, - ]} - ]) - t.setData(ma) - diff --git a/pyqtgraph/widgets/TreeWidget.py b/pyqtgraph/widgets/TreeWidget.py deleted file mode 100644 index 97fbe953..00000000 --- a/pyqtgraph/widgets/TreeWidget.py +++ /dev/null @@ -1,284 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore -from weakref import * - -__all__ = ['TreeWidget', 'TreeWidgetItem'] -class TreeWidget(QtGui.QTreeWidget): - """Extends QTreeWidget to allow internal drag/drop with widgets in the tree. - Also maintains the expanded state of subtrees as they are moved. - This class demonstrates the absurd lengths one must go to to make drag/drop work.""" - - sigItemMoved = QtCore.Signal(object, object, object) # (item, parent, index) - - def __init__(self, parent=None): - QtGui.QTreeWidget.__init__(self, parent) - #self.itemWidgets = WeakKeyDictionary() - self.setAcceptDrops(True) - self.setDragEnabled(True) - self.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed|QtGui.QAbstractItemView.SelectedClicked) - self.placeholders = [] - self.childNestingLimit = None - - def setItemWidget(self, item, col, wid): - """ - Overrides QTreeWidget.setItemWidget such that widgets are added inside an invisible wrapper widget. - This makes it possible to move the item in and out of the tree without its widgets being automatically deleted. - """ - w = QtGui.QWidget() ## foster parent / surrogate child widget - l = QtGui.QVBoxLayout() - l.setContentsMargins(0,0,0,0) - w.setLayout(l) - w.setSizePolicy(wid.sizePolicy()) - w.setMinimumHeight(wid.minimumHeight()) - w.setMinimumWidth(wid.minimumWidth()) - l.addWidget(wid) - w.realChild = wid - self.placeholders.append(w) - QtGui.QTreeWidget.setItemWidget(self, item, col, w) - - def itemWidget(self, item, col): - w = QtGui.QTreeWidget.itemWidget(self, item, col) - if w is not None: - w = w.realChild - return w - - def dropMimeData(self, parent, index, data, action): - item = self.currentItem() - p = parent - #print "drop", item, "->", parent, index - while True: - if p is None: - break - if p is item: - return False - #raise Exception("Can not move item into itself.") - p = p.parent() - - if not self.itemMoving(item, parent, index): - return False - - currentParent = item.parent() - if currentParent is None: - currentParent = self.invisibleRootItem() - if parent is None: - parent = self.invisibleRootItem() - - if currentParent is parent and index > parent.indexOfChild(item): - index -= 1 - - self.prepareMove(item) - - currentParent.removeChild(item) - #print " insert child to index", index - parent.insertChild(index, item) ## index will not be correct - self.setCurrentItem(item) - - self.recoverMove(item) - #self.emit(QtCore.SIGNAL('itemMoved'), item, parent, index) - self.sigItemMoved.emit(item, parent, index) - return True - - def itemMoving(self, item, parent, index): - """Called when item has been dropped elsewhere in the tree. - Return True to accept the move, False to reject.""" - return True - - def prepareMove(self, item): - item.__widgets = [] - item.__expanded = item.isExpanded() - for i in range(self.columnCount()): - w = self.itemWidget(item, i) - item.__widgets.append(w) - if w is None: - continue - w.setParent(None) - for i in range(item.childCount()): - self.prepareMove(item.child(i)) - - def recoverMove(self, item): - for i in range(self.columnCount()): - w = item.__widgets[i] - if w is None: - continue - self.setItemWidget(item, i, w) - for i in range(item.childCount()): - self.recoverMove(item.child(i)) - - item.setExpanded(False) ## Items do not re-expand correctly unless they are collapsed first. - QtGui.QApplication.instance().processEvents() - item.setExpanded(item.__expanded) - - def collapseTree(self, item): - item.setExpanded(False) - for i in range(item.childCount()): - self.collapseTree(item.child(i)) - - def removeTopLevelItem(self, item): - for i in range(self.topLevelItemCount()): - if self.topLevelItem(i) is item: - self.takeTopLevelItem(i) - return - raise Exception("Item '%s' not in top-level items." % str(item)) - - def listAllItems(self, item=None): - items = [] - if item != None: - items.append(item) - else: - item = self.invisibleRootItem() - - for cindex in range(item.childCount()): - foundItems = self.listAllItems(item=item.child(cindex)) - for f in foundItems: - items.append(f) - return items - - def dropEvent(self, ev): - QtGui.QTreeWidget.dropEvent(self, ev) - self.updateDropFlags() - - - def updateDropFlags(self): - ### intended to put a limit on how deep nests of children can go. - ### self.childNestingLimit is upheld when moving items without children, but if the item being moved has children/grandchildren, the children/grandchildren - ### can end up over the childNestingLimit. - if self.childNestingLimit == None: - pass # enable drops in all items (but only if there are drops that aren't enabled? for performance...) - else: - items = self.listAllItems() - for item in items: - parentCount = 0 - p = item.parent() - while p is not None: - parentCount += 1 - p = p.parent() - if parentCount >= self.childNestingLimit: - item.setFlags(item.flags() & (~QtCore.Qt.ItemIsDropEnabled)) - else: - item.setFlags(item.flags() | QtCore.Qt.ItemIsDropEnabled) - - @staticmethod - def informTreeWidgetChange(item): - if hasattr(item, 'treeWidgetChanged'): - item.treeWidgetChanged() - else: - for i in xrange(item.childCount()): - TreeWidget.informTreeWidgetChange(item.child(i)) - - - def addTopLevelItem(self, item): - QtGui.QTreeWidget.addTopLevelItem(self, item) - self.informTreeWidgetChange(item) - - def addTopLevelItems(self, items): - QtGui.QTreeWidget.addTopLevelItems(self, items) - for item in items: - self.informTreeWidgetChange(item) - - def insertTopLevelItem(self, index, item): - QtGui.QTreeWidget.insertTopLevelItem(self, index, item) - self.informTreeWidgetChange(item) - - def insertTopLevelItems(self, index, items): - QtGui.QTreeWidget.insertTopLevelItems(self, index, items) - for item in items: - self.informTreeWidgetChange(item) - - def takeTopLevelItem(self, index): - item = self.topLevelItem(index) - if item is not None: - self.prepareMove(item) - item = QtGui.QTreeWidget.takeTopLevelItem(self, index) - self.prepareMove(item) - self.informTreeWidgetChange(item) - return item - - def topLevelItems(self): - return map(self.topLevelItem, xrange(self.topLevelItemCount())) - - def clear(self): - items = self.topLevelItems() - for item in items: - self.prepareMove(item) - QtGui.QTreeWidget.clear(self) - - ## Why do we want to do this? It causes RuntimeErrors. - #for item in items: - #self.informTreeWidgetChange(item) - - -class TreeWidgetItem(QtGui.QTreeWidgetItem): - """ - TreeWidgetItem that keeps track of its own widgets. - Widgets may be added to columns before the item is added to a tree. - """ - def __init__(self, *args): - QtGui.QTreeWidgetItem.__init__(self, *args) - self._widgets = {} # col: widget - self._tree = None - - - def setChecked(self, column, checked): - self.setCheckState(column, QtCore.Qt.Checked if checked else QtCore.Qt.Unchecked) - - def setWidget(self, column, widget): - if column in self._widgets: - self.removeWidget(column) - self._widgets[column] = widget - tree = self.treeWidget() - if tree is None: - return - else: - tree.setItemWidget(self, column, widget) - - def removeWidget(self, column): - del self._widgets[column] - tree = self.treeWidget() - if tree is None: - return - tree.removeItemWidget(self, column) - - def treeWidgetChanged(self): - tree = self.treeWidget() - if self._tree is tree: - return - self._tree = self.treeWidget() - if tree is None: - return - for col, widget in self._widgets.items(): - tree.setItemWidget(self, col, widget) - - def addChild(self, child): - QtGui.QTreeWidgetItem.addChild(self, child) - TreeWidget.informTreeWidgetChange(child) - - def addChildren(self, childs): - QtGui.QTreeWidgetItem.addChildren(self, childs) - for child in childs: - TreeWidget.informTreeWidgetChange(child) - - def insertChild(self, index, child): - QtGui.QTreeWidgetItem.insertChild(self, index, child) - TreeWidget.informTreeWidgetChange(child) - - def insertChildren(self, index, childs): - QtGui.QTreeWidgetItem.addChildren(self, index, childs) - for child in childs: - TreeWidget.informTreeWidgetChange(child) - - def removeChild(self, child): - QtGui.QTreeWidgetItem.removeChild(self, child) - TreeWidget.informTreeWidgetChange(child) - - def takeChild(self, index): - child = QtGui.QTreeWidgetItem.takeChild(self, index) - TreeWidget.informTreeWidgetChange(child) - return child - - def takeChildren(self): - childs = QtGui.QTreeWidgetItem.takeChildren(self) - for child in childs: - TreeWidget.informTreeWidgetChange(child) - return childs - - diff --git a/pyqtgraph/widgets/ValueLabel.py b/pyqtgraph/widgets/ValueLabel.py deleted file mode 100644 index 7f6fa84b..00000000 --- a/pyqtgraph/widgets/ValueLabel.py +++ /dev/null @@ -1,73 +0,0 @@ -from pyqtgraph.Qt import QtCore, QtGui -from pyqtgraph.ptime import time -import pyqtgraph as pg -from functools import reduce - -__all__ = ['ValueLabel'] - -class ValueLabel(QtGui.QLabel): - """ - QLabel specifically for displaying numerical values. - Extends QLabel adding some extra functionality: - - - displaying units with si prefix - - built-in exponential averaging - """ - - def __init__(self, parent=None, suffix='', siPrefix=False, averageTime=0, formatStr=None): - """ - ============ ================================================================================== - Arguments - suffix (str or None) The suffix to place after the value - siPrefix (bool) Whether to add an SI prefix to the units and display a scaled value - averageTime (float) The length of time in seconds to average values. If this value - is 0, then no averaging is performed. As this value increases - the display value will appear to change more slowly and smoothly. - formatStr (str) Optionally, provide a format string to use when displaying text. The text - will be generated by calling formatStr.format(value=, avgValue=, suffix=) - (see Python documentation on str.format) - This option is not compatible with siPrefix - ============ ================================================================================== - """ - QtGui.QLabel.__init__(self, parent) - self.values = [] - self.averageTime = averageTime ## no averaging by default - self.suffix = suffix - self.siPrefix = siPrefix - if formatStr is None: - formatStr = '{avgValue:0.2g} {suffix}' - self.formatStr = formatStr - - def setValue(self, value): - now = time() - self.values.append((now, value)) - cutoff = now - self.averageTime - while len(self.values) > 0 and self.values[0][0] < cutoff: - self.values.pop(0) - self.update() - - def setFormatStr(self, text): - self.formatStr = text - self.update() - - def setAverageTime(self, t): - self.averageTime = t - - def averageValue(self): - return reduce(lambda a,b: a+b, [v[1] for v in self.values]) / float(len(self.values)) - - - def paintEvent(self, ev): - self.setText(self.generateText()) - return QtGui.QLabel.paintEvent(self, ev) - - def generateText(self): - if len(self.values) == 0: - return '' - avg = self.averageValue() - val = self.values[-1][1] - if self.siPrefix: - return pg.siFormat(avg, suffix=self.suffix) - else: - return self.formatStr.format(value=val, avgValue=avg, suffix=self.suffix) - diff --git a/pyqtgraph/widgets/VerticalLabel.py b/pyqtgraph/widgets/VerticalLabel.py deleted file mode 100644 index fa45ae5d..00000000 --- a/pyqtgraph/widgets/VerticalLabel.py +++ /dev/null @@ -1,99 +0,0 @@ -# -*- coding: utf-8 -*- -from pyqtgraph.Qt import QtGui, QtCore - -__all__ = ['VerticalLabel'] -#class VerticalLabel(QtGui.QLabel): - #def paintEvent(self, ev): - #p = QtGui.QPainter(self) - #p.rotate(-90) - #self.hint = p.drawText(QtCore.QRect(-self.height(), 0, self.height(), self.width()), QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter, self.text()) - #p.end() - #self.setMinimumWidth(self.hint.height()) - #self.setMinimumHeight(self.hint.width()) - - #def sizeHint(self): - #if hasattr(self, 'hint'): - #return QtCore.QSize(self.hint.height(), self.hint.width()) - #else: - #return QtCore.QSize(16, 50) - -class VerticalLabel(QtGui.QLabel): - def __init__(self, text, orientation='vertical', forceWidth=True): - QtGui.QLabel.__init__(self, text) - self.forceWidth = forceWidth - self.orientation = None - self.setOrientation(orientation) - - def setOrientation(self, o): - if self.orientation == o: - return - self.orientation = o - self.update() - self.updateGeometry() - - def paintEvent(self, ev): - p = QtGui.QPainter(self) - #p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200))) - #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100))) - #p.drawRect(self.rect().adjusted(0, 0, -1, -1)) - - #p.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255))) - - if self.orientation == 'vertical': - p.rotate(-90) - rgn = QtCore.QRect(-self.height(), 0, self.height(), self.width()) - else: - rgn = self.contentsRect() - align = self.alignment() - #align = QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter - - self.hint = p.drawText(rgn, align, self.text()) - p.end() - - if self.orientation == 'vertical': - self.setMaximumWidth(self.hint.height()) - self.setMinimumWidth(0) - self.setMaximumHeight(16777215) - if self.forceWidth: - self.setMinimumHeight(self.hint.width()) - else: - self.setMinimumHeight(0) - else: - self.setMaximumHeight(self.hint.height()) - self.setMinimumHeight(0) - self.setMaximumWidth(16777215) - if self.forceWidth: - self.setMinimumWidth(self.hint.width()) - else: - self.setMinimumWidth(0) - - def sizeHint(self): - if self.orientation == 'vertical': - if hasattr(self, 'hint'): - return QtCore.QSize(self.hint.height(), self.hint.width()) - else: - return QtCore.QSize(19, 50) - else: - if hasattr(self, 'hint'): - return QtCore.QSize(self.hint.width(), self.hint.height()) - else: - return QtCore.QSize(50, 19) - - -if __name__ == '__main__': - app = QtGui.QApplication([]) - win = QtGui.QMainWindow() - w = QtGui.QWidget() - l = QtGui.QGridLayout() - w.setLayout(l) - - l1 = VerticalLabel("text 1", orientation='horizontal') - l2 = VerticalLabel("text 2") - l3 = VerticalLabel("text 3") - l4 = VerticalLabel("text 4", orientation='horizontal') - l.addWidget(l1, 0, 0) - l.addWidget(l2, 1, 1) - l.addWidget(l3, 2, 2) - l.addWidget(l4, 3, 3) - win.setCentralWidget(w) - win.show() \ No newline at end of file diff --git a/pyqtgraph/widgets/__init__.py b/pyqtgraph/widgets/__init__.py deleted file mode 100644 index a81fe391..00000000 --- a/pyqtgraph/widgets/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -## just import everything from sub-modules - -#import os - -#d = os.path.split(__file__)[0] -#files = [] -#for f in os.listdir(d): - #if os.path.isdir(os.path.join(d, f)): - #files.append(f) - #elif f[-3:] == '.py' and f != '__init__.py': - #files.append(f[:-3]) - -#for modName in files: - #mod = __import__(modName, globals(), locals(), fromlist=['*']) - #if hasattr(mod, '__all__'): - #names = mod.__all__ - #else: - #names = [n for n in dir(mod) if n[0] != '_'] - #for k in names: - #print modName, k - #globals()[k] = getattr(mod, k) From a6df51f4c444858e94da00cd30234e9be7923124 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 30 Dec 2014 12:02:58 +0100 Subject: [PATCH 003/260] added pyqtgraph 0.9.10 --- papi/gui/gui_api.py | 2 +- papi/gui/gui_event_processing.py | 2 +- papi/gui/plugin_api.py | 2 +- papi/gui/qt_dev/gui_main.py | 6 +- papi/gui/qt_new/main.py | 4 +- papi/last_active_papi.xml | 405 ++- papi/plugin/visual/Plot/Plot.py | 4 +- .../visual/WizardExample/WizardExample.py | 2 +- papi/pyqtgraph/GraphicsScene/GraphicsScene.py | 553 ++++ papi/pyqtgraph/GraphicsScene/__init__.py | 1 + papi/pyqtgraph/GraphicsScene/exportDialog.py | 141 ++ .../GraphicsScene/exportDialogTemplate.ui | 100 + .../exportDialogTemplate_pyqt.py | 77 + .../exportDialogTemplate_pyside.py | 63 + papi/pyqtgraph/GraphicsScene/mouseEvents.py | 382 +++ papi/pyqtgraph/PlotData.py | 56 + papi/pyqtgraph/Point.py | 155 ++ papi/pyqtgraph/Qt.py | 135 + papi/pyqtgraph/SRTTransform.py | 258 ++ papi/pyqtgraph/SRTTransform3D.py | 315 +++ papi/pyqtgraph/SignalProxy.py | 119 + papi/pyqtgraph/ThreadsafeTimer.py | 41 + papi/pyqtgraph/Transform3D.py | 35 + papi/pyqtgraph/Vector.py | 87 + papi/pyqtgraph/WidgetGroup.py | 298 +++ papi/pyqtgraph/__init__.py | 438 ++++ papi/pyqtgraph/canvas/Canvas.py | 604 +++++ papi/pyqtgraph/canvas/CanvasItem.py | 512 ++++ papi/pyqtgraph/canvas/CanvasManager.py | 76 + papi/pyqtgraph/canvas/CanvasTemplate.ui | 135 + papi/pyqtgraph/canvas/CanvasTemplate_pyqt.py | 92 + .../pyqtgraph/canvas/CanvasTemplate_pyside.py | 95 + papi/pyqtgraph/canvas/TransformGuiTemplate.ui | 75 + .../canvas/TransformGuiTemplate_pyqt.py | 69 + .../canvas/TransformGuiTemplate_pyside.py | 55 + papi/pyqtgraph/canvas/__init__.py | 3 + papi/pyqtgraph/colormap.py | 250 ++ papi/pyqtgraph/configfile.py | 217 ++ papi/pyqtgraph/console/CmdInput.py | 62 + papi/pyqtgraph/console/Console.py | 386 +++ papi/pyqtgraph/console/__init__.py | 1 + papi/pyqtgraph/console/template.ui | 194 ++ papi/pyqtgraph/console/template_pyqt.py | 127 + papi/pyqtgraph/console/template_pyside.py | 106 + papi/pyqtgraph/debug.py | 1169 +++++++++ papi/pyqtgraph/dockarea/Container.py | 277 ++ papi/pyqtgraph/dockarea/Dock.py | 345 +++ papi/pyqtgraph/dockarea/DockArea.py | 319 +++ papi/pyqtgraph/dockarea/DockDrop.py | 128 + papi/pyqtgraph/dockarea/__init__.py | 2 + papi/pyqtgraph/dockarea/tests/test_dock.py | 16 + papi/pyqtgraph/exceptionHandling.py | 106 + papi/pyqtgraph/exporters/CSVExporter.py | 83 + papi/pyqtgraph/exporters/Exporter.py | 139 + papi/pyqtgraph/exporters/HDF5Exporter.py | 58 + papi/pyqtgraph/exporters/ImageExporter.py | 102 + papi/pyqtgraph/exporters/Matplotlib.py | 128 + papi/pyqtgraph/exporters/PrintExporter.py | 68 + papi/pyqtgraph/exporters/SVGExporter.py | 477 ++++ papi/pyqtgraph/exporters/__init__.py | 11 + papi/pyqtgraph/exporters/tests/test_csv.py | 49 + papi/pyqtgraph/exporters/tests/test_svg.py | 67 + papi/pyqtgraph/flowchart/Flowchart.py | 932 +++++++ .../flowchart/FlowchartCtrlTemplate.ui | 120 + .../flowchart/FlowchartCtrlTemplate_pyqt.py | 80 + .../flowchart/FlowchartCtrlTemplate_pyside.py | 66 + .../flowchart/FlowchartGraphicsView.py | 109 + papi/pyqtgraph/flowchart/FlowchartTemplate.ui | 98 + .../flowchart/FlowchartTemplate_pyqt.py | 68 + .../flowchart/FlowchartTemplate_pyside.py | 54 + papi/pyqtgraph/flowchart/Node.py | 644 +++++ papi/pyqtgraph/flowchart/NodeLibrary.py | 86 + papi/pyqtgraph/flowchart/Terminal.py | 634 +++++ papi/pyqtgraph/flowchart/__init__.py | 4 + papi/pyqtgraph/flowchart/eq.py | 36 + papi/pyqtgraph/flowchart/library/Data.py | 356 +++ papi/pyqtgraph/flowchart/library/Display.py | 312 +++ papi/pyqtgraph/flowchart/library/Filters.py | 346 +++ papi/pyqtgraph/flowchart/library/Operators.py | 74 + papi/pyqtgraph/flowchart/library/__init__.py | 28 + papi/pyqtgraph/flowchart/library/common.py | 184 ++ papi/pyqtgraph/flowchart/library/functions.py | 355 +++ papi/pyqtgraph/frozenSupport.py | 52 + papi/pyqtgraph/functions.py | 2252 +++++++++++++++++ papi/pyqtgraph/graphicsItems/ArrowItem.py | 126 + papi/pyqtgraph/graphicsItems/AxisItem.py | 1076 ++++++++ papi/pyqtgraph/graphicsItems/BarGraphItem.py | 168 ++ papi/pyqtgraph/graphicsItems/ButtonItem.py | 58 + papi/pyqtgraph/graphicsItems/CurvePoint.py | 117 + papi/pyqtgraph/graphicsItems/ErrorBarItem.py | 149 ++ .../graphicsItems/FillBetweenItem.py | 73 + .../graphicsItems/GradientEditorItem.py | 927 +++++++ .../pyqtgraph/graphicsItems/GradientLegend.py | 114 + papi/pyqtgraph/graphicsItems/GraphItem.py | 147 ++ papi/pyqtgraph/graphicsItems/GraphicsItem.py | 585 +++++ .../pyqtgraph/graphicsItems/GraphicsLayout.py | 171 ++ .../pyqtgraph/graphicsItems/GraphicsObject.py | 39 + .../pyqtgraph/graphicsItems/GraphicsWidget.py | 59 + .../graphicsItems/GraphicsWidgetAnchor.py | 110 + papi/pyqtgraph/graphicsItems/GridItem.py | 120 + .../graphicsItems/HistogramLUTItem.py | 205 ++ papi/pyqtgraph/graphicsItems/ImageItem.py | 528 ++++ papi/pyqtgraph/graphicsItems/InfiniteLine.py | 275 ++ papi/pyqtgraph/graphicsItems/IsocurveItem.py | 117 + papi/pyqtgraph/graphicsItems/ItemGroup.py | 23 + papi/pyqtgraph/graphicsItems/LabelItem.py | 142 ++ papi/pyqtgraph/graphicsItems/LegendItem.py | 174 ++ .../graphicsItems/LinearRegionItem.py | 290 +++ papi/pyqtgraph/graphicsItems/MultiPlotItem.py | 62 + papi/pyqtgraph/graphicsItems/PlotCurveItem.py | 598 +++++ papi/pyqtgraph/graphicsItems/PlotDataItem.py | 866 +++++++ .../graphicsItems/PlotItem/PlotItem.py | 1265 +++++++++ .../graphicsItems/PlotItem/__init__.py | 1 + .../PlotItem/plotConfigTemplate.ui | 343 +++ .../PlotItem/plotConfigTemplate_pyqt.py | 182 ++ .../PlotItem/plotConfigTemplate_pyside.py | 168 ++ papi/pyqtgraph/graphicsItems/ROI.py | 2246 ++++++++++++++++ papi/pyqtgraph/graphicsItems/ScaleBar.py | 71 + .../graphicsItems/ScatterPlotItem.py | 971 +++++++ papi/pyqtgraph/graphicsItems/TextItem.py | 152 ++ .../pyqtgraph/graphicsItems/UIGraphicsItem.py | 124 + papi/pyqtgraph/graphicsItems/VTickGroup.py | 99 + .../graphicsItems/ViewBox/ViewBox.py | 1774 +++++++++++++ .../graphicsItems/ViewBox/ViewBoxMenu.py | 267 ++ .../graphicsItems/ViewBox/__init__.py | 1 + .../graphicsItems/ViewBox/axisCtrlTemplate.ui | 161 ++ .../ViewBox/axisCtrlTemplate_pyqt.py | 102 + .../ViewBox/axisCtrlTemplate_pyside.py | 88 + .../ViewBox/tests/test_ViewBox.py | 85 + papi/pyqtgraph/graphicsItems/__init__.py | 21 + .../graphicsItems/tests/test_GraphicsItem.py | 47 + .../tests/test_ScatterPlotItem.py | 86 + papi/pyqtgraph/graphicsWindows.py | 83 + papi/pyqtgraph/imageview/ImageView.py | 720 ++++++ papi/pyqtgraph/imageview/ImageViewTemplate.ui | 249 ++ .../imageview/ImageViewTemplate_pyqt.py | 168 ++ .../imageview/ImageViewTemplate_pyside.py | 154 ++ papi/pyqtgraph/imageview/__init__.py | 6 + .../imageview/tests/test_imageview.py | 11 + papi/pyqtgraph/metaarray/MetaArray.py | 1498 +++++++++++ papi/pyqtgraph/metaarray/__init__.py | 1 + papi/pyqtgraph/metaarray/license.txt | 8 + papi/pyqtgraph/metaarray/readMeta.m | 86 + papi/pyqtgraph/multiprocess/__init__.py | 24 + papi/pyqtgraph/multiprocess/bootstrap.py | 28 + papi/pyqtgraph/multiprocess/parallelizer.py | 330 +++ papi/pyqtgraph/multiprocess/processes.py | 478 ++++ papi/pyqtgraph/multiprocess/remoteproxy.py | 1117 ++++++++ papi/pyqtgraph/numpy_fix.py | 22 + papi/pyqtgraph/opengl/GLGraphicsItem.py | 298 +++ papi/pyqtgraph/opengl/GLViewWidget.py | 464 ++++ papi/pyqtgraph/opengl/MeshData.py | 504 ++++ papi/pyqtgraph/opengl/__init__.py | 22 + papi/pyqtgraph/opengl/glInfo.py | 16 + papi/pyqtgraph/opengl/items/GLAxisItem.py | 64 + papi/pyqtgraph/opengl/items/GLBarGraphItem.py | 29 + papi/pyqtgraph/opengl/items/GLBoxItem.py | 88 + papi/pyqtgraph/opengl/items/GLGridItem.py | 78 + papi/pyqtgraph/opengl/items/GLImageItem.py | 99 + papi/pyqtgraph/opengl/items/GLLinePlotItem.py | 112 + papi/pyqtgraph/opengl/items/GLMeshItem.py | 227 ++ .../opengl/items/GLScatterPlotItem.py | 183 ++ .../opengl/items/GLSurfacePlotItem.py | 138 + papi/pyqtgraph/opengl/items/GLVolumeItem.py | 230 ++ papi/pyqtgraph/opengl/items/__init__.py | 0 papi/pyqtgraph/opengl/shaders.py | 402 +++ papi/pyqtgraph/ordereddict.py | 127 + papi/pyqtgraph/parametertree/Parameter.py | 766 ++++++ papi/pyqtgraph/parametertree/ParameterItem.py | 171 ++ .../parametertree/ParameterSystem.py | 127 + papi/pyqtgraph/parametertree/ParameterTree.py | 154 ++ papi/pyqtgraph/parametertree/SystemSolver.py | 381 +++ papi/pyqtgraph/parametertree/__init__.py | 5 + .../pyqtgraph/parametertree/parameterTypes.py | 663 +++++ .../tests/test_parametertypes.py | 18 + papi/pyqtgraph/pgcollections.py | 477 ++++ papi/pyqtgraph/pixmaps/__init__.py | 26 + papi/pyqtgraph/pixmaps/auto.png | Bin 0 -> 1022 bytes papi/pyqtgraph/pixmaps/compile.py | 19 + papi/pyqtgraph/pixmaps/ctrl.png | Bin 0 -> 934 bytes papi/pyqtgraph/pixmaps/default.png | Bin 0 -> 810 bytes papi/pyqtgraph/pixmaps/icons.svg | 135 + papi/pyqtgraph/pixmaps/lock.png | Bin 0 -> 913 bytes papi/pyqtgraph/pixmaps/pixmapData_2.py | 1 + papi/pyqtgraph/pixmaps/pixmapData_3.py | 1 + papi/pyqtgraph/ptime.py | 30 + papi/pyqtgraph/python2_3.py | 60 + papi/pyqtgraph/reload.py | 516 ++++ papi/pyqtgraph/tests/test_exit_crash.py | 38 + papi/pyqtgraph/tests/test_functions.py | 81 + papi/pyqtgraph/tests/test_qt.py | 23 + papi/pyqtgraph/tests/test_ref_cycles.py | 77 + papi/pyqtgraph/tests/test_srttransform3d.py | 39 + papi/pyqtgraph/tests/test_stability.py | 160 ++ papi/pyqtgraph/tests/uictest.ui | 53 + papi/pyqtgraph/units.py | 64 + papi/pyqtgraph/util/__init__.py | 0 papi/pyqtgraph/util/colorama/LICENSE.txt | 28 + papi/pyqtgraph/util/colorama/README.txt | 304 +++ papi/pyqtgraph/util/colorama/__init__.py | 0 papi/pyqtgraph/util/colorama/win32.py | 137 + papi/pyqtgraph/util/colorama/winterm.py | 120 + papi/pyqtgraph/util/cprint.py | 101 + papi/pyqtgraph/util/garbage_collector.py | 50 + papi/pyqtgraph/util/lru_cache.py | 121 + papi/pyqtgraph/util/mutex.py | 94 + papi/pyqtgraph/util/pil_fix.py | 64 + papi/pyqtgraph/util/tests/test_lru_cache.py | 50 + papi/pyqtgraph/widgets/BusyCursor.py | 24 + papi/pyqtgraph/widgets/CheckTable.py | 93 + papi/pyqtgraph/widgets/ColorButton.py | 91 + papi/pyqtgraph/widgets/ColorMapWidget.py | 249 ++ papi/pyqtgraph/widgets/ComboBox.py | 217 ++ papi/pyqtgraph/widgets/DataFilterWidget.py | 150 ++ papi/pyqtgraph/widgets/DataTreeWidget.py | 83 + papi/pyqtgraph/widgets/FeedbackButton.py | 163 ++ papi/pyqtgraph/widgets/FileDialog.py | 14 + papi/pyqtgraph/widgets/GradientWidget.py | 74 + .../pyqtgraph/widgets/GraphicsLayoutWidget.py | 30 + papi/pyqtgraph/widgets/GraphicsView.py | 399 +++ papi/pyqtgraph/widgets/HistogramLUTWidget.py | 33 + papi/pyqtgraph/widgets/JoystickButton.py | 95 + papi/pyqtgraph/widgets/LayoutWidget.py | 101 + papi/pyqtgraph/widgets/MatplotlibWidget.py | 41 + papi/pyqtgraph/widgets/MultiPlotWidget.py | 78 + papi/pyqtgraph/widgets/PathButton.py | 50 + papi/pyqtgraph/widgets/PlotWidget.py | 99 + papi/pyqtgraph/widgets/ProgressDialog.py | 112 + papi/pyqtgraph/widgets/RawImageWidget.py | 140 + papi/pyqtgraph/widgets/RemoteGraphicsView.py | 262 ++ papi/pyqtgraph/widgets/ScatterPlotWidget.py | 217 ++ papi/pyqtgraph/widgets/SpinBox.py | 518 ++++ papi/pyqtgraph/widgets/TableWidget.py | 504 ++++ papi/pyqtgraph/widgets/TreeWidget.py | 284 +++ papi/pyqtgraph/widgets/ValueLabel.py | 73 + papi/pyqtgraph/widgets/VerticalLabel.py | 99 + papi/pyqtgraph/widgets/__init__.py | 21 + papi/pyqtgraph/widgets/tests/test_combobox.py | 44 + .../widgets/tests/test_tablewidget.py | 128 + 239 files changed, 50232 insertions(+), 219 deletions(-) create mode 100644 papi/pyqtgraph/GraphicsScene/GraphicsScene.py create mode 100644 papi/pyqtgraph/GraphicsScene/__init__.py create mode 100644 papi/pyqtgraph/GraphicsScene/exportDialog.py create mode 100644 papi/pyqtgraph/GraphicsScene/exportDialogTemplate.ui create mode 100644 papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py create mode 100644 papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py create mode 100644 papi/pyqtgraph/GraphicsScene/mouseEvents.py create mode 100644 papi/pyqtgraph/PlotData.py create mode 100644 papi/pyqtgraph/Point.py create mode 100644 papi/pyqtgraph/Qt.py create mode 100644 papi/pyqtgraph/SRTTransform.py create mode 100644 papi/pyqtgraph/SRTTransform3D.py create mode 100644 papi/pyqtgraph/SignalProxy.py create mode 100644 papi/pyqtgraph/ThreadsafeTimer.py create mode 100644 papi/pyqtgraph/Transform3D.py create mode 100644 papi/pyqtgraph/Vector.py create mode 100644 papi/pyqtgraph/WidgetGroup.py create mode 100644 papi/pyqtgraph/__init__.py create mode 100644 papi/pyqtgraph/canvas/Canvas.py create mode 100644 papi/pyqtgraph/canvas/CanvasItem.py create mode 100644 papi/pyqtgraph/canvas/CanvasManager.py create mode 100644 papi/pyqtgraph/canvas/CanvasTemplate.ui create mode 100644 papi/pyqtgraph/canvas/CanvasTemplate_pyqt.py create mode 100644 papi/pyqtgraph/canvas/CanvasTemplate_pyside.py create mode 100644 papi/pyqtgraph/canvas/TransformGuiTemplate.ui create mode 100644 papi/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py create mode 100644 papi/pyqtgraph/canvas/TransformGuiTemplate_pyside.py create mode 100644 papi/pyqtgraph/canvas/__init__.py create mode 100644 papi/pyqtgraph/colormap.py create mode 100644 papi/pyqtgraph/configfile.py create mode 100644 papi/pyqtgraph/console/CmdInput.py create mode 100644 papi/pyqtgraph/console/Console.py create mode 100644 papi/pyqtgraph/console/__init__.py create mode 100644 papi/pyqtgraph/console/template.ui create mode 100644 papi/pyqtgraph/console/template_pyqt.py create mode 100644 papi/pyqtgraph/console/template_pyside.py create mode 100644 papi/pyqtgraph/debug.py create mode 100644 papi/pyqtgraph/dockarea/Container.py create mode 100644 papi/pyqtgraph/dockarea/Dock.py create mode 100644 papi/pyqtgraph/dockarea/DockArea.py create mode 100644 papi/pyqtgraph/dockarea/DockDrop.py create mode 100644 papi/pyqtgraph/dockarea/__init__.py create mode 100644 papi/pyqtgraph/dockarea/tests/test_dock.py create mode 100644 papi/pyqtgraph/exceptionHandling.py create mode 100644 papi/pyqtgraph/exporters/CSVExporter.py create mode 100644 papi/pyqtgraph/exporters/Exporter.py create mode 100644 papi/pyqtgraph/exporters/HDF5Exporter.py create mode 100644 papi/pyqtgraph/exporters/ImageExporter.py create mode 100644 papi/pyqtgraph/exporters/Matplotlib.py create mode 100644 papi/pyqtgraph/exporters/PrintExporter.py create mode 100644 papi/pyqtgraph/exporters/SVGExporter.py create mode 100644 papi/pyqtgraph/exporters/__init__.py create mode 100644 papi/pyqtgraph/exporters/tests/test_csv.py create mode 100644 papi/pyqtgraph/exporters/tests/test_svg.py create mode 100644 papi/pyqtgraph/flowchart/Flowchart.py create mode 100644 papi/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui create mode 100644 papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py create mode 100644 papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py create mode 100644 papi/pyqtgraph/flowchart/FlowchartGraphicsView.py create mode 100644 papi/pyqtgraph/flowchart/FlowchartTemplate.ui create mode 100644 papi/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py create mode 100644 papi/pyqtgraph/flowchart/FlowchartTemplate_pyside.py create mode 100644 papi/pyqtgraph/flowchart/Node.py create mode 100644 papi/pyqtgraph/flowchart/NodeLibrary.py create mode 100644 papi/pyqtgraph/flowchart/Terminal.py create mode 100644 papi/pyqtgraph/flowchart/__init__.py create mode 100644 papi/pyqtgraph/flowchart/eq.py create mode 100644 papi/pyqtgraph/flowchart/library/Data.py create mode 100644 papi/pyqtgraph/flowchart/library/Display.py create mode 100644 papi/pyqtgraph/flowchart/library/Filters.py create mode 100644 papi/pyqtgraph/flowchart/library/Operators.py create mode 100644 papi/pyqtgraph/flowchart/library/__init__.py create mode 100644 papi/pyqtgraph/flowchart/library/common.py create mode 100644 papi/pyqtgraph/flowchart/library/functions.py create mode 100644 papi/pyqtgraph/frozenSupport.py create mode 100644 papi/pyqtgraph/functions.py create mode 100644 papi/pyqtgraph/graphicsItems/ArrowItem.py create mode 100644 papi/pyqtgraph/graphicsItems/AxisItem.py create mode 100644 papi/pyqtgraph/graphicsItems/BarGraphItem.py create mode 100644 papi/pyqtgraph/graphicsItems/ButtonItem.py create mode 100644 papi/pyqtgraph/graphicsItems/CurvePoint.py create mode 100644 papi/pyqtgraph/graphicsItems/ErrorBarItem.py create mode 100644 papi/pyqtgraph/graphicsItems/FillBetweenItem.py create mode 100644 papi/pyqtgraph/graphicsItems/GradientEditorItem.py create mode 100644 papi/pyqtgraph/graphicsItems/GradientLegend.py create mode 100644 papi/pyqtgraph/graphicsItems/GraphItem.py create mode 100644 papi/pyqtgraph/graphicsItems/GraphicsItem.py create mode 100644 papi/pyqtgraph/graphicsItems/GraphicsLayout.py create mode 100644 papi/pyqtgraph/graphicsItems/GraphicsObject.py create mode 100644 papi/pyqtgraph/graphicsItems/GraphicsWidget.py create mode 100644 papi/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py create mode 100644 papi/pyqtgraph/graphicsItems/GridItem.py create mode 100644 papi/pyqtgraph/graphicsItems/HistogramLUTItem.py create mode 100644 papi/pyqtgraph/graphicsItems/ImageItem.py create mode 100644 papi/pyqtgraph/graphicsItems/InfiniteLine.py create mode 100644 papi/pyqtgraph/graphicsItems/IsocurveItem.py create mode 100644 papi/pyqtgraph/graphicsItems/ItemGroup.py create mode 100644 papi/pyqtgraph/graphicsItems/LabelItem.py create mode 100644 papi/pyqtgraph/graphicsItems/LegendItem.py create mode 100644 papi/pyqtgraph/graphicsItems/LinearRegionItem.py create mode 100644 papi/pyqtgraph/graphicsItems/MultiPlotItem.py create mode 100644 papi/pyqtgraph/graphicsItems/PlotCurveItem.py create mode 100644 papi/pyqtgraph/graphicsItems/PlotDataItem.py create mode 100644 papi/pyqtgraph/graphicsItems/PlotItem/PlotItem.py create mode 100644 papi/pyqtgraph/graphicsItems/PlotItem/__init__.py create mode 100644 papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui create mode 100644 papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py create mode 100644 papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py create mode 100644 papi/pyqtgraph/graphicsItems/ROI.py create mode 100644 papi/pyqtgraph/graphicsItems/ScaleBar.py create mode 100644 papi/pyqtgraph/graphicsItems/ScatterPlotItem.py create mode 100644 papi/pyqtgraph/graphicsItems/TextItem.py create mode 100644 papi/pyqtgraph/graphicsItems/UIGraphicsItem.py create mode 100644 papi/pyqtgraph/graphicsItems/VTickGroup.py create mode 100644 papi/pyqtgraph/graphicsItems/ViewBox/ViewBox.py create mode 100644 papi/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py create mode 100644 papi/pyqtgraph/graphicsItems/ViewBox/__init__.py create mode 100644 papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui create mode 100644 papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py create mode 100644 papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py create mode 100644 papi/pyqtgraph/graphicsItems/ViewBox/tests/test_ViewBox.py create mode 100644 papi/pyqtgraph/graphicsItems/__init__.py create mode 100644 papi/pyqtgraph/graphicsItems/tests/test_GraphicsItem.py create mode 100644 papi/pyqtgraph/graphicsItems/tests/test_ScatterPlotItem.py create mode 100644 papi/pyqtgraph/graphicsWindows.py create mode 100644 papi/pyqtgraph/imageview/ImageView.py create mode 100644 papi/pyqtgraph/imageview/ImageViewTemplate.ui create mode 100644 papi/pyqtgraph/imageview/ImageViewTemplate_pyqt.py create mode 100644 papi/pyqtgraph/imageview/ImageViewTemplate_pyside.py create mode 100644 papi/pyqtgraph/imageview/__init__.py create mode 100644 papi/pyqtgraph/imageview/tests/test_imageview.py create mode 100644 papi/pyqtgraph/metaarray/MetaArray.py create mode 100644 papi/pyqtgraph/metaarray/__init__.py create mode 100644 papi/pyqtgraph/metaarray/license.txt create mode 100644 papi/pyqtgraph/metaarray/readMeta.m create mode 100644 papi/pyqtgraph/multiprocess/__init__.py create mode 100644 papi/pyqtgraph/multiprocess/bootstrap.py create mode 100644 papi/pyqtgraph/multiprocess/parallelizer.py create mode 100644 papi/pyqtgraph/multiprocess/processes.py create mode 100644 papi/pyqtgraph/multiprocess/remoteproxy.py create mode 100644 papi/pyqtgraph/numpy_fix.py create mode 100644 papi/pyqtgraph/opengl/GLGraphicsItem.py create mode 100644 papi/pyqtgraph/opengl/GLViewWidget.py create mode 100644 papi/pyqtgraph/opengl/MeshData.py create mode 100644 papi/pyqtgraph/opengl/__init__.py create mode 100644 papi/pyqtgraph/opengl/glInfo.py create mode 100644 papi/pyqtgraph/opengl/items/GLAxisItem.py create mode 100644 papi/pyqtgraph/opengl/items/GLBarGraphItem.py create mode 100644 papi/pyqtgraph/opengl/items/GLBoxItem.py create mode 100644 papi/pyqtgraph/opengl/items/GLGridItem.py create mode 100644 papi/pyqtgraph/opengl/items/GLImageItem.py create mode 100644 papi/pyqtgraph/opengl/items/GLLinePlotItem.py create mode 100644 papi/pyqtgraph/opengl/items/GLMeshItem.py create mode 100644 papi/pyqtgraph/opengl/items/GLScatterPlotItem.py create mode 100644 papi/pyqtgraph/opengl/items/GLSurfacePlotItem.py create mode 100644 papi/pyqtgraph/opengl/items/GLVolumeItem.py create mode 100644 papi/pyqtgraph/opengl/items/__init__.py create mode 100644 papi/pyqtgraph/opengl/shaders.py create mode 100644 papi/pyqtgraph/ordereddict.py create mode 100644 papi/pyqtgraph/parametertree/Parameter.py create mode 100644 papi/pyqtgraph/parametertree/ParameterItem.py create mode 100644 papi/pyqtgraph/parametertree/ParameterSystem.py create mode 100644 papi/pyqtgraph/parametertree/ParameterTree.py create mode 100644 papi/pyqtgraph/parametertree/SystemSolver.py create mode 100644 papi/pyqtgraph/parametertree/__init__.py create mode 100644 papi/pyqtgraph/parametertree/parameterTypes.py create mode 100644 papi/pyqtgraph/parametertree/tests/test_parametertypes.py create mode 100644 papi/pyqtgraph/pgcollections.py create mode 100644 papi/pyqtgraph/pixmaps/__init__.py create mode 100644 papi/pyqtgraph/pixmaps/auto.png create mode 100644 papi/pyqtgraph/pixmaps/compile.py create mode 100644 papi/pyqtgraph/pixmaps/ctrl.png create mode 100644 papi/pyqtgraph/pixmaps/default.png create mode 100644 papi/pyqtgraph/pixmaps/icons.svg create mode 100644 papi/pyqtgraph/pixmaps/lock.png create mode 100644 papi/pyqtgraph/pixmaps/pixmapData_2.py create mode 100644 papi/pyqtgraph/pixmaps/pixmapData_3.py create mode 100644 papi/pyqtgraph/ptime.py create mode 100644 papi/pyqtgraph/python2_3.py create mode 100644 papi/pyqtgraph/reload.py create mode 100644 papi/pyqtgraph/tests/test_exit_crash.py create mode 100644 papi/pyqtgraph/tests/test_functions.py create mode 100644 papi/pyqtgraph/tests/test_qt.py create mode 100644 papi/pyqtgraph/tests/test_ref_cycles.py create mode 100644 papi/pyqtgraph/tests/test_srttransform3d.py create mode 100644 papi/pyqtgraph/tests/test_stability.py create mode 100644 papi/pyqtgraph/tests/uictest.ui create mode 100644 papi/pyqtgraph/units.py create mode 100644 papi/pyqtgraph/util/__init__.py create mode 100644 papi/pyqtgraph/util/colorama/LICENSE.txt create mode 100644 papi/pyqtgraph/util/colorama/README.txt create mode 100644 papi/pyqtgraph/util/colorama/__init__.py create mode 100644 papi/pyqtgraph/util/colorama/win32.py create mode 100644 papi/pyqtgraph/util/colorama/winterm.py create mode 100644 papi/pyqtgraph/util/cprint.py create mode 100644 papi/pyqtgraph/util/garbage_collector.py create mode 100644 papi/pyqtgraph/util/lru_cache.py create mode 100644 papi/pyqtgraph/util/mutex.py create mode 100644 papi/pyqtgraph/util/pil_fix.py create mode 100644 papi/pyqtgraph/util/tests/test_lru_cache.py create mode 100644 papi/pyqtgraph/widgets/BusyCursor.py create mode 100644 papi/pyqtgraph/widgets/CheckTable.py create mode 100644 papi/pyqtgraph/widgets/ColorButton.py create mode 100644 papi/pyqtgraph/widgets/ColorMapWidget.py create mode 100644 papi/pyqtgraph/widgets/ComboBox.py create mode 100644 papi/pyqtgraph/widgets/DataFilterWidget.py create mode 100644 papi/pyqtgraph/widgets/DataTreeWidget.py create mode 100644 papi/pyqtgraph/widgets/FeedbackButton.py create mode 100644 papi/pyqtgraph/widgets/FileDialog.py create mode 100644 papi/pyqtgraph/widgets/GradientWidget.py create mode 100644 papi/pyqtgraph/widgets/GraphicsLayoutWidget.py create mode 100644 papi/pyqtgraph/widgets/GraphicsView.py create mode 100644 papi/pyqtgraph/widgets/HistogramLUTWidget.py create mode 100644 papi/pyqtgraph/widgets/JoystickButton.py create mode 100644 papi/pyqtgraph/widgets/LayoutWidget.py create mode 100644 papi/pyqtgraph/widgets/MatplotlibWidget.py create mode 100644 papi/pyqtgraph/widgets/MultiPlotWidget.py create mode 100644 papi/pyqtgraph/widgets/PathButton.py create mode 100644 papi/pyqtgraph/widgets/PlotWidget.py create mode 100644 papi/pyqtgraph/widgets/ProgressDialog.py create mode 100644 papi/pyqtgraph/widgets/RawImageWidget.py create mode 100644 papi/pyqtgraph/widgets/RemoteGraphicsView.py create mode 100644 papi/pyqtgraph/widgets/ScatterPlotWidget.py create mode 100644 papi/pyqtgraph/widgets/SpinBox.py create mode 100644 papi/pyqtgraph/widgets/TableWidget.py create mode 100644 papi/pyqtgraph/widgets/TreeWidget.py create mode 100644 papi/pyqtgraph/widgets/ValueLabel.py create mode 100644 papi/pyqtgraph/widgets/VerticalLabel.py create mode 100644 papi/pyqtgraph/widgets/__init__.py create mode 100644 papi/pyqtgraph/widgets/tests/test_combobox.py create mode 100644 papi/pyqtgraph/widgets/tests/test_tablewidget.py diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 012b494b..b0d9dca3 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -35,7 +35,7 @@ from papi.constants import GUI_PROCESS_CONSOLE_IDENTIFIER, GUI_PROCESS_CONSOLE_LOG_LEVEL, CONFIG_LOADER_SUBCRIBE_DELAY, \ CONFIG_ROOT_ELEMENT_NAME, CORE_PAPI_VERSION, PLUGIN_PCP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER -from pyqtgraph import QtCore +from papi.pyqtgraph import QtCore import papi.error_codes as ERROR diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 21859012..b54ab5b0 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -49,7 +49,7 @@ import importlib.machinery from papi.data.DPlugin import DPlugin -from pyqtgraph import QtCore +from papi.pyqtgraph import QtCore __author__ = 'Stefan' diff --git a/papi/gui/plugin_api.py b/papi/gui/plugin_api.py index f1a51afb..f08ddd0f 100644 --- a/papi/gui/plugin_api.py +++ b/papi/gui/plugin_api.py @@ -35,7 +35,7 @@ from papi.constants import PLUGIN_API_CONSOLE_IDENTIFIER, PLUGIN_API_CONSOLE_LOG_LEVEL, CONFIG_LOADER_SUBCRIBE_DELAY, \ CONFIG_ROOT_ELEMENT_NAME, CORE_PAPI_VERSION, PLUGIN_PCP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER -from pyqtgraph import QtCore +from papi.pyqtgraph import QtCore from papi.gui.gui_api import Gui_api import papi.error_codes as ERROR diff --git a/papi/gui/qt_dev/gui_main.py b/papi/gui/qt_dev/gui_main.py index 90504976..3158325f 100644 --- a/papi/gui/qt_dev/gui_main.py +++ b/papi/gui/qt_dev/gui_main.py @@ -48,13 +48,13 @@ from papi.constants import CONFIG_DEFAULT_FILE from papi.gui.gui_api import Gui_api from papi.gui.gui_event_processing import GuiEventProcessing -import pyqtgraph as pg -from pyqtgraph import QtCore +import papi.pyqtgraph as pq +from papi.pyqtgraph import QtCore # Enable antialiasing for prettier plots -pg.setConfigOptions(antialias=False) +pq.setConfigOptions(antialias=False) class GUI(QMainWindow, Ui_MainGUI): diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index 25ef827b..6d206c13 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -49,8 +49,8 @@ from papi.gui.gui_api import Gui_api from papi.gui.gui_event_processing import GuiEventProcessing -import pyqtgraph as pg -from pyqtgraph import QtCore, QtGui +import papi.pyqtgraph as pg +from papi.pyqtgraph import QtCore, QtGui from papi.gui.qt_new.create_plugin_menu import CreatePluginMenu from papi.gui.qt_new.overview_menu import OverviewPluginMenu diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 2c6cf544..905abd8c 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,292 +1,281 @@ - + - - ORTD_UDP - - - ORTDXUDP - - - 127.0.0.1 - 1 - - - 20001 - 1 - - - 20000 - 1 - - - /home/control/PycharmProjects/PaPI/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json - file - 0 - - - 0 - 1 - - - - 0 - 0 - 1 - 0 - 0 - 9 - 0 - 0 - 0 - 50 - 0 - 10 - - - - Button - - Oscillator input - - - - Plot - - VisualPlugin - Used display name + + [0 1 2 3 4] + Color + ^\[(\s*\d\s*)+\] + 1 - + 0 bool + Rolling Plot ^(1|0)$ - Grid-X + + + time, s + Label-X + \w+,\s*\w+ - (0,0) Determine position: (x,y) + (0,0) \(([0-9]+),([0-9]+)\) 1 - - [0 0 0 0 0] - ^\[(\s*\d\s*)+\] - Style - 1 - - - 1 + + 0 bool + Grid-Y ^(1|0)$ - Rolling Plot - - Plot + + Used display name + VisualPlugin + + + 1 + (\d+) + + + amplitude, V + Label-Y + \w+,\s+\w+ - 50 - ^([1-9][0-9]{0,3}|10000)$ + 1000 Buffersize + ^([1-9][0-9]{0,3}|10000)$ 1 - - [0 1 2 3 4] + + [0 0 0 0 0] + Style ^\[(\s*\d\s*)+\] - Color 1 - - amplitude, V - \w+,\s+\w+ - Label-Y - - + 0 bool + Grid-X ^(1|0)$ - Grid-Y - - - 1 - (\d+) - - - time, s - \w+,\s*\w+ - Label-X - (311,696) Determine size: (height,width) + (300,300) \(([0-9]+),([0-9]+)\) 1 + + Plot + + 1000 + 1 0 + [0 0 0 0 0] 0 - 50 [0 1 2 3 4] 0 - 1 - [0 0 0 0 0] - - - ORTDXUDP - - - 15 - 16 - 18 - HalloWelt14 - Sig2nal - Sign12al - Sign3al - Signal1 - Signal10 - Signal11 - Signal19 - Signal20 - Signal21 - Signal22 - Signal5 - Signal6 - Signal7 - Signal8 - Signal9 - Signal_13 - Test17 - _Sig4nal - - - + - - Plot + + Button - - VisualPlugin - Used display name + + 0.1 + 0 - - 0 - bool - ^(1|0)$ - Grid-X + + Button - (320,0) Determine position: (x,y) + (0,0) \(([0-9]+),([0-9]+)\) 1 - - [0 0 0 0 0] - ^\[(\s*\d\s*)+\] - Style + + Determine size: (height,width) + (300,300) + \(([0-9]+),([0-9]+)\) 1 - + 1 - bool - ^(1|0)$ - Rolling Plot + 0 - - PlotX2 + + Button + 0 - - 200 - ^([1-9][0-9]{0,3}|10000)$ - Buffersize + + + + + + Slider + + + Determine size: (height,width) + (300,300) + \(([0-9]+),([0-9]+)\) 1 - - [0 1 2 3 4] - ^\[(\s*\d\s*)+\] - Color + + Determine position: (x,y) + (316,111) + \(([0-9]+),([0-9]+)\) 1 - - amplitude, V - \w+,\s+\w+ - Label-Y + + Slider - - 0 - bool - ^(1|0)$ - Grid-Y + + Used display name + VisualPlugin - + 1 - (\d+) - - time, s - \w+,\s*\w+ - Label-X + + + + + + ToHDD_CSV + + + log + 0 - - (300,300) - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) + + 1 + + ToHDDXCSV + + + 1 + [0-9]+ + 1 + + + + + + + Add + + + Add + + + + + + + CPU_Load + + + CPUXLoad + - 0 - 0 - 200 - [0 1 2 3 4] - 0 - 1 - [0 0 0 0 0] + 0.01 - - - ORTDXUDP - - - V - X - - - + - - Button + + Fourier_Rect + + 9999 + \d{1,5} + 1 + + + FourierXRect + - Button - 0 + Fourier - - 0.5 + + 130.149.155.73 + \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} + 1 + + + + + + + Sinus + + + 3 + [0-9]+ + + + Sinus + + + 1 + \d+.{0,1}\d* + + + + 0.6 + + + + + ORTD_UDP + + + ORTDXUDP + + + 20001 + 1 + + + papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json + file 0 - - (127,58) - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) + + 0 1 - - (311,305) - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) + + 20000 1 - - Button + + 127.0.0.1 + 1 - - 0 - 0 + + + 0 + 0 + 0 + + + + + Fourier_Rect_MOD + + + FourierXRectXMOD diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 20b02dc4..809cef0b 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -28,7 +28,7 @@ __author__ = 'Stefan' -import pyqtgraph as pq +import papi.pyqtgraph as pq from papi.plugin.base_classes.vip_base import vip_base from papi.data.DParameter import DParameter @@ -39,7 +39,7 @@ current_milli_time = lambda: int(round(time.time() * 1000)) -from pyqtgraph.Qt import QtCore +from papi.pyqtgraph.Qt import QtCore class Plot(vip_base): diff --git a/papi/plugin/visual/WizardExample/WizardExample.py b/papi/plugin/visual/WizardExample/WizardExample.py index 41116c94..d9c80975 100644 --- a/papi/plugin/visual/WizardExample/WizardExample.py +++ b/papi/plugin/visual/WizardExample/WizardExample.py @@ -30,7 +30,7 @@ from papi.plugin.base_classes.vip_base import vip_base from PySide.QtGui import QMdiSubWindow -from pyqtgraph.Qt import QtCore, QtGui +from papi.pyqtgraph.Qt import QtCore, QtGui class WizardExample(vip_base): diff --git a/papi/pyqtgraph/GraphicsScene/GraphicsScene.py b/papi/pyqtgraph/GraphicsScene/GraphicsScene.py new file mode 100644 index 00000000..6f5354dc --- /dev/null +++ b/papi/pyqtgraph/GraphicsScene/GraphicsScene.py @@ -0,0 +1,553 @@ +from ..Qt import QtCore, QtGui +from ..python2_3 import sortList +import weakref +from ..Point import Point +from .. import functions as fn +from .. import ptime as ptime +from .mouseEvents import * +from .. import debug as debug + +if hasattr(QtCore, 'PYQT_VERSION'): + try: + import sip + HAVE_SIP = True + except ImportError: + HAVE_SIP = False +else: + HAVE_SIP = False + + +__all__ = ['GraphicsScene'] + +class GraphicsScene(QtGui.QGraphicsScene): + """ + Extension of QGraphicsScene that implements a complete, parallel mouse event system. + (It would have been preferred to just alter the way QGraphicsScene creates and delivers + events, but this turned out to be impossible because the constructor for QGraphicsMouseEvent + is private) + + * Generates MouseClicked events in addition to the usual press/move/release events. + (This works around a problem where it is impossible to have one item respond to a + drag if another is watching for a click.) + * Adjustable radius around click that will catch objects so you don't have to click *exactly* over small/thin objects + * Global context menu--if an item implements a context menu, then its parent(s) may also add items to the menu. + * Allows items to decide _before_ a mouse click which item will be the recipient of mouse events. + This lets us indicate unambiguously to the user which item they are about to click/drag on + * Eats mouseMove events that occur too soon after a mouse press. + * Reimplements items() and itemAt() to circumvent PyQt bug + + Mouse interaction is as follows: + + 1) Every time the mouse moves, the scene delivers both the standard hoverEnter/Move/LeaveEvents + as well as custom HoverEvents. + 2) Items are sent HoverEvents in Z-order and each item may optionally call event.acceptClicks(button), + acceptDrags(button) or both. If this method call returns True, this informs the item that _if_ + the user clicks/drags the specified mouse button, the item is guaranteed to be the + recipient of click/drag events (the item may wish to change its appearance to indicate this). + If the call to acceptClicks/Drags returns False, then the item is guaranteed to *not* receive + the requested event (because another item has already accepted it). + 3) If the mouse is clicked, a mousePressEvent is generated as usual. If any items accept this press event, then + No click/drag events will be generated and mouse interaction proceeds as defined by Qt. This allows + items to function properly if they are expecting the usual press/move/release sequence of events. + (It is recommended that items do NOT accept press events, and instead use click/drag events) + Note: The default implementation of QGraphicsItem.mousePressEvent will *accept* the event if the + item is has its Selectable or Movable flags enabled. You may need to override this behavior. + 4) If no item accepts the mousePressEvent, then the scene will begin delivering mouseDrag and/or mouseClick events. + If the mouse is moved a sufficient distance (or moved slowly enough) before the button is released, + then a mouseDragEvent is generated. + If no drag events are generated before the button is released, then a mouseClickEvent is generated. + 5) Click/drag events are delivered to the item that called acceptClicks/acceptDrags on the HoverEvent + in step 1. If no such items exist, then the scene attempts to deliver the events to items near the event. + ClickEvents may be delivered in this way even if no + item originally claimed it could accept the click. DragEvents may only be delivered this way if it is the initial + move in a drag. + """ + + sigMouseHover = QtCore.Signal(object) ## emits a list of objects hovered over + sigMouseMoved = QtCore.Signal(object) ## emits position of mouse on every move + sigMouseClicked = QtCore.Signal(object) ## emitted when mouse is clicked. Check for event.isAccepted() to see whether the event has already been acted on. + + sigPrepareForPaint = QtCore.Signal() ## emitted immediately before the scene is about to be rendered + + _addressCache = weakref.WeakValueDictionary() + + ExportDirectory = None + + @classmethod + def registerObject(cls, obj): + """ + Workaround for PyQt bug in qgraphicsscene.items() + All subclasses of QGraphicsObject must register themselves with this function. + (otherwise, mouse interaction with those objects will likely fail) + """ + if HAVE_SIP and isinstance(obj, sip.wrapper): + cls._addressCache[sip.unwrapinstance(sip.cast(obj, QtGui.QGraphicsItem))] = obj + + + def __init__(self, clickRadius=2, moveDistance=5, parent=None): + QtGui.QGraphicsScene.__init__(self, parent) + self.setClickRadius(clickRadius) + self.setMoveDistance(moveDistance) + self.exportDirectory = None + + self.clickEvents = [] + self.dragButtons = [] + self.mouseGrabber = None + self.dragItem = None + self.lastDrag = None + self.hoverItems = weakref.WeakKeyDictionary() + self.lastHoverEvent = None + + self.contextMenu = [QtGui.QAction("Export...", self)] + self.contextMenu[0].triggered.connect(self.showExportDialog) + + self.exportDialog = None + + def render(self, *args): + self.prepareForPaint() + return QtGui.QGraphicsScene.render(self, *args) + + def prepareForPaint(self): + """Called before every render. This method will inform items that the scene is about to + be rendered by emitting sigPrepareForPaint. + + This allows items to delay expensive processing until they know a paint will be required.""" + self.sigPrepareForPaint.emit() + + + def setClickRadius(self, r): + """ + Set the distance away from mouse clicks to search for interacting items. + When clicking, the scene searches first for items that directly intersect the click position + followed by any other items that are within a rectangle that extends r pixels away from the + click position. + """ + self._clickRadius = r + + def setMoveDistance(self, d): + """ + Set the distance the mouse must move after a press before mouseMoveEvents will be delivered. + This ensures that clicks with a small amount of movement are recognized as clicks instead of + drags. + """ + self._moveDistance = d + + def mousePressEvent(self, ev): + #print 'scenePress' + QtGui.QGraphicsScene.mousePressEvent(self, ev) + if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events + if self.lastHoverEvent is not None: + # If the mouse has moved since the last hover event, send a new one. + # This can happen if a context menu is open while the mouse is moving. + if ev.scenePos() != self.lastHoverEvent.scenePos(): + self.sendHoverEvents(ev) + + self.clickEvents.append(MouseClickEvent(ev)) + + ## set focus on the topmost focusable item under this click + items = self.items(ev.scenePos()) + for i in items: + if i.isEnabled() and i.isVisible() and int(i.flags() & i.ItemIsFocusable) > 0: + i.setFocus(QtCore.Qt.MouseFocusReason) + break + + def mouseMoveEvent(self, ev): + self.sigMouseMoved.emit(ev.scenePos()) + + ## First allow QGraphicsScene to deliver hoverEnter/Move/ExitEvents + QtGui.QGraphicsScene.mouseMoveEvent(self, ev) + + ## Next deliver our own HoverEvents + self.sendHoverEvents(ev) + + if int(ev.buttons()) != 0: ## button is pressed; send mouseMoveEvents and mouseDragEvents + QtGui.QGraphicsScene.mouseMoveEvent(self, ev) + if self.mouseGrabberItem() is None: + now = ptime.time() + init = False + ## keep track of which buttons are involved in dragging + for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MidButton, QtCore.Qt.RightButton]: + if int(ev.buttons() & btn) == 0: + continue + if int(btn) not in self.dragButtons: ## see if we've dragged far enough yet + cev = [e for e in self.clickEvents if int(e.button()) == int(btn)][0] + dist = Point(ev.screenPos() - cev.screenPos()) + if dist.length() < self._moveDistance and now - cev.time() < 0.5: + continue + init = init or (len(self.dragButtons) == 0) ## If this is the first button to be dragged, then init=True + self.dragButtons.append(int(btn)) + + ## If we have dragged buttons, deliver a drag event + if len(self.dragButtons) > 0: + if self.sendDragEvent(ev, init=init): + ev.accept() + + def leaveEvent(self, ev): ## inform items that mouse is gone + if len(self.dragButtons) == 0: + self.sendHoverEvents(ev, exitOnly=True) + + + def mouseReleaseEvent(self, ev): + #print 'sceneRelease' + if self.mouseGrabberItem() is None: + if ev.button() in self.dragButtons: + if self.sendDragEvent(ev, final=True): + #print "sent drag event" + ev.accept() + self.dragButtons.remove(ev.button()) + else: + cev = [e for e in self.clickEvents if int(e.button()) == int(ev.button())] + if self.sendClickEvent(cev[0]): + #print "sent click event" + ev.accept() + self.clickEvents.remove(cev[0]) + + if int(ev.buttons()) == 0: + self.dragItem = None + self.dragButtons = [] + self.clickEvents = [] + self.lastDrag = None + QtGui.QGraphicsScene.mouseReleaseEvent(self, ev) + + self.sendHoverEvents(ev) ## let items prepare for next click/drag + + def mouseDoubleClickEvent(self, ev): + QtGui.QGraphicsScene.mouseDoubleClickEvent(self, ev) + if self.mouseGrabberItem() is None: ## nobody claimed press; we are free to generate drag/click events + self.clickEvents.append(MouseClickEvent(ev, double=True)) + + def sendHoverEvents(self, ev, exitOnly=False): + ## if exitOnly, then just inform all previously hovered items that the mouse has left. + + if exitOnly: + acceptable=False + items = [] + event = HoverEvent(None, acceptable) + else: + acceptable = int(ev.buttons()) == 0 ## if we are in mid-drag, do not allow items to accept the hover event. + event = HoverEvent(ev, acceptable) + items = self.itemsNearEvent(event, hoverable=True) + self.sigMouseHover.emit(items) + + prevItems = list(self.hoverItems.keys()) + + #print "hover prev items:", prevItems + #print "hover test items:", items + for item in items: + if hasattr(item, 'hoverEvent'): + event.currentItem = item + if item not in self.hoverItems: + self.hoverItems[item] = None + event.enter = True + else: + prevItems.remove(item) + event.enter = False + + try: + item.hoverEvent(event) + except: + debug.printExc("Error sending hover event:") + + event.enter = False + event.exit = True + #print "hover exit items:", prevItems + for item in prevItems: + event.currentItem = item + try: + item.hoverEvent(event) + except: + debug.printExc("Error sending hover exit event:") + finally: + del self.hoverItems[item] + + # Update last hover event unless: + # - mouse is dragging (move+buttons); in this case we want the dragged + # item to continue receiving events until the drag is over + # - event is not a mouse event (QEvent.Leave sometimes appears here) + if (ev.type() == ev.GraphicsSceneMousePress or + (ev.type() == ev.GraphicsSceneMouseMove and int(ev.buttons()) == 0)): + self.lastHoverEvent = event ## save this so we can ask about accepted events later. + + def sendDragEvent(self, ev, init=False, final=False): + ## Send a MouseDragEvent to the current dragItem or to + ## items near the beginning of the drag + event = MouseDragEvent(ev, self.clickEvents[0], self.lastDrag, start=init, finish=final) + #print "dragEvent: init=", init, 'final=', final, 'self.dragItem=', self.dragItem + if init and self.dragItem is None: + if self.lastHoverEvent is not None: + acceptedItem = self.lastHoverEvent.dragItems().get(event.button(), None) + else: + acceptedItem = None + + if acceptedItem is not None: + #print "Drag -> pre-selected item:", acceptedItem + self.dragItem = acceptedItem + event.currentItem = self.dragItem + try: + self.dragItem.mouseDragEvent(event) + except: + debug.printExc("Error sending drag event:") + + else: + #print "drag -> new item" + for item in self.itemsNearEvent(event): + #print "check item:", item + if not item.isVisible() or not item.isEnabled(): + continue + if hasattr(item, 'mouseDragEvent'): + event.currentItem = item + try: + item.mouseDragEvent(event) + except: + debug.printExc("Error sending drag event:") + if event.isAccepted(): + #print " --> accepted" + self.dragItem = item + if int(item.flags() & item.ItemIsFocusable) > 0: + item.setFocus(QtCore.Qt.MouseFocusReason) + break + elif self.dragItem is not None: + event.currentItem = self.dragItem + try: + self.dragItem.mouseDragEvent(event) + except: + debug.printExc("Error sending hover exit event:") + + self.lastDrag = event + + return event.isAccepted() + + + def sendClickEvent(self, ev): + ## if we are in mid-drag, click events may only go to the dragged item. + if self.dragItem is not None and hasattr(self.dragItem, 'mouseClickEvent'): + ev.currentItem = self.dragItem + self.dragItem.mouseClickEvent(ev) + + ## otherwise, search near the cursor + else: + if self.lastHoverEvent is not None: + acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None) + else: + acceptedItem = None + if acceptedItem is not None: + ev.currentItem = acceptedItem + try: + acceptedItem.mouseClickEvent(ev) + except: + debug.printExc("Error sending click event:") + else: + for item in self.itemsNearEvent(ev): + if not item.isVisible() or not item.isEnabled(): + continue + if hasattr(item, 'mouseClickEvent'): + ev.currentItem = item + try: + item.mouseClickEvent(ev) + except: + debug.printExc("Error sending click event:") + + if ev.isAccepted(): + if int(item.flags() & item.ItemIsFocusable) > 0: + item.setFocus(QtCore.Qt.MouseFocusReason) + break + self.sigMouseClicked.emit(ev) + return ev.isAccepted() + + def items(self, *args): + #print 'args:', args + items = QtGui.QGraphicsScene.items(self, *args) + ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, + ## then the object returned will be different than the actual item that was originally added to the scene + items2 = list(map(self.translateGraphicsItem, items)) + #if HAVE_SIP and isinstance(self, sip.wrapper): + #items2 = [] + #for i in items: + #addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem)) + #i2 = GraphicsScene._addressCache.get(addr, i) + ##print i, "==>", i2 + #items2.append(i2) + #print 'items:', items + return items2 + + def selectedItems(self, *args): + items = QtGui.QGraphicsScene.selectedItems(self, *args) + ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, + ## then the object returned will be different than the actual item that was originally added to the scene + #if HAVE_SIP and isinstance(self, sip.wrapper): + #items2 = [] + #for i in items: + #addr = sip.unwrapinstance(sip.cast(i, QtGui.QGraphicsItem)) + #i2 = GraphicsScene._addressCache.get(addr, i) + ##print i, "==>", i2 + #items2.append(i2) + items2 = list(map(self.translateGraphicsItem, items)) + + #print 'items:', items + return items2 + + def itemAt(self, *args): + item = QtGui.QGraphicsScene.itemAt(self, *args) + + ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, + ## then the object returned will be different than the actual item that was originally added to the scene + #if HAVE_SIP and isinstance(self, sip.wrapper): + #addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem)) + #item = GraphicsScene._addressCache.get(addr, item) + #return item + return self.translateGraphicsItem(item) + + def itemsNearEvent(self, event, selMode=QtCore.Qt.IntersectsItemShape, sortOrder=QtCore.Qt.DescendingOrder, hoverable=False): + """ + Return an iterator that iterates first through the items that directly intersect point (in Z order) + followed by any other items that are within the scene's click radius. + """ + #tr = self.getViewWidget(event.widget()).transform() + view = self.views()[0] + tr = view.viewportTransform() + r = self._clickRadius + rect = view.mapToScene(QtCore.QRect(0, 0, 2*r, 2*r)).boundingRect() + + seen = set() + if hasattr(event, 'buttonDownScenePos'): + point = event.buttonDownScenePos() + else: + point = event.scenePos() + w = rect.width() + h = rect.height() + rgn = QtCore.QRectF(point.x()-w, point.y()-h, 2*w, 2*h) + #self.searchRect.setRect(rgn) + + + items = self.items(point, selMode, sortOrder, tr) + + ## remove items whose shape does not contain point (scene.items() apparently sucks at this) + items2 = [] + for item in items: + if hoverable and not hasattr(item, 'hoverEvent'): + continue + shape = item.shape() # Note: default shape() returns boundingRect() + if shape is None: + continue + if shape.contains(item.mapFromScene(point)): + items2.append(item) + + ## Sort by descending Z-order (don't trust scene.itms() to do this either) + ## use 'absolute' z value, which is the sum of all item/parent ZValues + def absZValue(item): + if item is None: + return 0 + return item.zValue() + absZValue(item.parentItem()) + + sortList(items2, lambda a,b: cmp(absZValue(b), absZValue(a))) + + return items2 + + #for item in items: + ##seen.add(item) + + #shape = item.mapToScene(item.shape()) + #if not shape.contains(point): + #continue + #yield item + #for item in self.items(rgn, selMode, sortOrder, tr): + ##if item not in seen: + #yield item + + def getViewWidget(self): + return self.views()[0] + + #def getViewWidget(self, widget): + ### same pyqt bug -- mouseEvent.widget() doesn't give us the original python object. + ### [[doesn't seem to work correctly]] + #if HAVE_SIP and isinstance(self, sip.wrapper): + #addr = sip.unwrapinstance(sip.cast(widget, QtGui.QWidget)) + ##print "convert", widget, addr + #for v in self.views(): + #addr2 = sip.unwrapinstance(sip.cast(v, QtGui.QWidget)) + ##print " check:", v, addr2 + #if addr2 == addr: + #return v + #else: + #return widget + + def addParentContextMenus(self, item, menu, event): + """ + Can be called by any item in the scene to expand its context menu to include parent context menus. + Parents may implement getContextMenus to add new menus / actions to the existing menu. + getContextMenus must accept 1 argument (the event that generated the original menu) and + return a single QMenu or a list of QMenus. + + The final menu will look like: + + | Original Item 1 + | Original Item 2 + | ... + | Original Item N + | ------------------ + | Parent Item 1 + | Parent Item 2 + | ... + | Grandparent Item 1 + | ... + + + ============== ================================================== + **Arguments:** + item The item that initially created the context menu + (This is probably the item making the call to this function) + menu The context menu being shown by the item + event The original event that triggered the menu to appear. + ============== ================================================== + """ + + menusToAdd = [] + while item is not self: + item = item.parentItem() + if item is None: + item = self + if not hasattr(item, "getContextMenus"): + continue + subMenus = item.getContextMenus(event) or [] + if isinstance(subMenus, list): ## so that some items (like FlowchartViewBox) can return multiple menus + menusToAdd.extend(subMenus) + else: + menusToAdd.append(subMenus) + + if menusToAdd: + menu.addSeparator() + + for m in menusToAdd: + if isinstance(m, QtGui.QMenu): + menu.addMenu(m) + elif isinstance(m, QtGui.QAction): + menu.addAction(m) + else: + raise Exception("Cannot add object %s (type=%s) to QMenu." % (str(m), str(type(m)))) + + return menu + + def getContextMenus(self, event): + self.contextMenuItem = event.acceptedItem + return self.contextMenu + + def showExportDialog(self): + if self.exportDialog is None: + from . import exportDialog + self.exportDialog = exportDialog.ExportDialog(self) + self.exportDialog.show(self.contextMenuItem) + + @staticmethod + def translateGraphicsItem(item): + ## for fixing pyqt bugs where the wrong item is returned + if HAVE_SIP and isinstance(item, sip.wrapper): + addr = sip.unwrapinstance(sip.cast(item, QtGui.QGraphicsItem)) + item = GraphicsScene._addressCache.get(addr, item) + return item + + @staticmethod + def translateGraphicsItems(items): + return list(map(GraphicsScene.translateGraphicsItem, items)) + + + diff --git a/papi/pyqtgraph/GraphicsScene/__init__.py b/papi/pyqtgraph/GraphicsScene/__init__.py new file mode 100644 index 00000000..abe42c6f --- /dev/null +++ b/papi/pyqtgraph/GraphicsScene/__init__.py @@ -0,0 +1 @@ +from .GraphicsScene import * diff --git a/papi/pyqtgraph/GraphicsScene/exportDialog.py b/papi/pyqtgraph/GraphicsScene/exportDialog.py new file mode 100644 index 00000000..5efb7c44 --- /dev/null +++ b/papi/pyqtgraph/GraphicsScene/exportDialog.py @@ -0,0 +1,141 @@ +from ..Qt import QtCore, QtGui, USE_PYSIDE +from .. import exporters as exporters +from .. import functions as fn +from ..graphicsItems.ViewBox import ViewBox +from ..graphicsItems.PlotItem import PlotItem + +if USE_PYSIDE: + from . import exportDialogTemplate_pyside as exportDialogTemplate +else: + from . import exportDialogTemplate_pyqt as exportDialogTemplate + + +class ExportDialog(QtGui.QWidget): + def __init__(self, scene): + QtGui.QWidget.__init__(self) + self.setVisible(False) + self.setWindowTitle("Export") + self.shown = False + self.currentExporter = None + self.scene = scene + + self.selectBox = QtGui.QGraphicsRectItem() + self.selectBox.setPen(fn.mkPen('y', width=3, style=QtCore.Qt.DashLine)) + self.selectBox.hide() + self.scene.addItem(self.selectBox) + + self.ui = exportDialogTemplate.Ui_Form() + self.ui.setupUi(self) + + self.ui.closeBtn.clicked.connect(self.close) + self.ui.exportBtn.clicked.connect(self.exportClicked) + self.ui.copyBtn.clicked.connect(self.copyClicked) + self.ui.itemTree.currentItemChanged.connect(self.exportItemChanged) + self.ui.formatList.currentItemChanged.connect(self.exportFormatChanged) + + + def show(self, item=None): + if item is not None: + ## Select next exportable parent of the item originally clicked on + while not isinstance(item, ViewBox) and not isinstance(item, PlotItem) and item is not None: + item = item.parentItem() + ## if this is a ViewBox inside a PlotItem, select the parent instead. + if isinstance(item, ViewBox) and isinstance(item.parentItem(), PlotItem): + item = item.parentItem() + self.updateItemList(select=item) + self.setVisible(True) + self.activateWindow() + self.raise_() + self.selectBox.setVisible(True) + + if not self.shown: + self.shown = True + vcenter = self.scene.getViewWidget().geometry().center() + self.setGeometry(vcenter.x()-self.width()/2, vcenter.y()-self.height()/2, self.width(), self.height()) + + def updateItemList(self, select=None): + self.ui.itemTree.clear() + si = QtGui.QTreeWidgetItem(["Entire Scene"]) + si.gitem = self.scene + self.ui.itemTree.addTopLevelItem(si) + self.ui.itemTree.setCurrentItem(si) + si.setExpanded(True) + for child in self.scene.items(): + if child.parentItem() is None: + self.updateItemTree(child, si, select=select) + + def updateItemTree(self, item, treeItem, select=None): + si = None + if isinstance(item, ViewBox): + si = QtGui.QTreeWidgetItem(['ViewBox']) + elif isinstance(item, PlotItem): + si = QtGui.QTreeWidgetItem(['Plot']) + + if si is not None: + si.gitem = item + treeItem.addChild(si) + treeItem = si + if si.gitem is select: + self.ui.itemTree.setCurrentItem(si) + + for ch in item.childItems(): + self.updateItemTree(ch, treeItem, select=select) + + + def exportItemChanged(self, item, prev): + if item is None: + return + if item.gitem is self.scene: + newBounds = self.scene.views()[0].viewRect() + else: + newBounds = item.gitem.sceneBoundingRect() + self.selectBox.setRect(newBounds) + self.selectBox.show() + self.updateFormatList() + + def updateFormatList(self): + current = self.ui.formatList.currentItem() + if current is not None: + current = str(current.text()) + self.ui.formatList.clear() + self.exporterClasses = {} + gotCurrent = False + for exp in exporters.listExporters(): + self.ui.formatList.addItem(exp.Name) + self.exporterClasses[exp.Name] = exp + if exp.Name == current: + self.ui.formatList.setCurrentRow(self.ui.formatList.count()-1) + gotCurrent = True + + if not gotCurrent: + self.ui.formatList.setCurrentRow(0) + + def exportFormatChanged(self, item, prev): + if item is None: + self.currentExporter = None + self.ui.paramTree.clear() + return + expClass = self.exporterClasses[str(item.text())] + exp = expClass(item=self.ui.itemTree.currentItem().gitem) + params = exp.parameters() + if params is None: + self.ui.paramTree.clear() + else: + self.ui.paramTree.setParameters(params) + self.currentExporter = exp + self.ui.copyBtn.setEnabled(exp.allowCopy) + + def exportClicked(self): + self.selectBox.hide() + self.currentExporter.export() + + def copyClicked(self): + self.selectBox.hide() + self.currentExporter.export(copy=True) + + def close(self): + self.selectBox.setVisible(False) + self.setVisible(False) + + + diff --git a/papi/pyqtgraph/GraphicsScene/exportDialogTemplate.ui b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate.ui new file mode 100644 index 00000000..eacacd88 --- /dev/null +++ b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate.ui @@ -0,0 +1,100 @@ + + + Form + + + + 0 + 0 + 241 + 367 + + + + Export + + + + 0 + + + + + Item to export: + + + + + + + false + + + + 1 + + + + + + + + Export format + + + + + + + + + + Export + + + + + + + Close + + + + + + + false + + + + 1 + + + + + + + + Export options + + + + + + + Copy + + + + + + + + ParameterTree + QTreeWidget +
..parametertree
+
+
+ + +
diff --git a/papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py new file mode 100644 index 00000000..ad7361ab --- /dev/null +++ b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/GraphicsScene/exportDialogTemplate.ui' +# +# Created: Mon Dec 23 10:10:52 2013 +# by: PyQt4 UI code generator 4.10 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(241, 367) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.label = QtGui.QLabel(Form) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout.addWidget(self.label, 0, 0, 1, 3) + self.itemTree = QtGui.QTreeWidget(Form) + self.itemTree.setObjectName(_fromUtf8("itemTree")) + self.itemTree.headerItem().setText(0, _fromUtf8("1")) + self.itemTree.header().setVisible(False) + self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) + self.label_2 = QtGui.QLabel(Form) + self.label_2.setObjectName(_fromUtf8("label_2")) + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) + self.formatList = QtGui.QListWidget(Form) + self.formatList.setObjectName(_fromUtf8("formatList")) + self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) + self.exportBtn = QtGui.QPushButton(Form) + self.exportBtn.setObjectName(_fromUtf8("exportBtn")) + self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) + self.closeBtn = QtGui.QPushButton(Form) + self.closeBtn.setObjectName(_fromUtf8("closeBtn")) + self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) + self.paramTree = ParameterTree(Form) + self.paramTree.setObjectName(_fromUtf8("paramTree")) + self.paramTree.headerItem().setText(0, _fromUtf8("1")) + self.paramTree.header().setVisible(False) + self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) + self.label_3 = QtGui.QLabel(Form) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) + self.copyBtn = QtGui.QPushButton(Form) + self.copyBtn.setObjectName(_fromUtf8("copyBtn")) + self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(_translate("Form", "Export", None)) + self.label.setText(_translate("Form", "Item to export:", None)) + self.label_2.setText(_translate("Form", "Export format", None)) + self.exportBtn.setText(_translate("Form", "Export", None)) + self.closeBtn.setText(_translate("Form", "Close", None)) + self.label_3.setText(_translate("Form", "Export options", None)) + self.copyBtn.setText(_translate("Form", "Copy", None)) + +from ..parametertree import ParameterTree diff --git a/papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py new file mode 100644 index 00000000..f2e8dc70 --- /dev/null +++ b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/GraphicsScene/exportDialogTemplate.ui' +# +# Created: Mon Dec 23 10:10:53 2013 +# by: pyside-uic 0.2.14 running on PySide 1.1.2 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(241, 367) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.label = QtGui.QLabel(Form) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 0, 0, 1, 3) + self.itemTree = QtGui.QTreeWidget(Form) + self.itemTree.setObjectName("itemTree") + self.itemTree.headerItem().setText(0, "1") + self.itemTree.header().setVisible(False) + self.gridLayout.addWidget(self.itemTree, 1, 0, 1, 3) + self.label_2 = QtGui.QLabel(Form) + self.label_2.setObjectName("label_2") + self.gridLayout.addWidget(self.label_2, 2, 0, 1, 3) + self.formatList = QtGui.QListWidget(Form) + self.formatList.setObjectName("formatList") + self.gridLayout.addWidget(self.formatList, 3, 0, 1, 3) + self.exportBtn = QtGui.QPushButton(Form) + self.exportBtn.setObjectName("exportBtn") + self.gridLayout.addWidget(self.exportBtn, 6, 1, 1, 1) + self.closeBtn = QtGui.QPushButton(Form) + self.closeBtn.setObjectName("closeBtn") + self.gridLayout.addWidget(self.closeBtn, 6, 2, 1, 1) + self.paramTree = ParameterTree(Form) + self.paramTree.setObjectName("paramTree") + self.paramTree.headerItem().setText(0, "1") + self.paramTree.header().setVisible(False) + self.gridLayout.addWidget(self.paramTree, 5, 0, 1, 3) + self.label_3 = QtGui.QLabel(Form) + self.label_3.setObjectName("label_3") + self.gridLayout.addWidget(self.label_3, 4, 0, 1, 3) + self.copyBtn = QtGui.QPushButton(Form) + self.copyBtn.setObjectName("copyBtn") + self.gridLayout.addWidget(self.copyBtn, 6, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("Form", "Item to export:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_2.setText(QtGui.QApplication.translate("Form", "Export format", None, QtGui.QApplication.UnicodeUTF8)) + self.exportBtn.setText(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8)) + self.closeBtn.setText(QtGui.QApplication.translate("Form", "Close", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8)) + self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8)) + +from ..parametertree import ParameterTree diff --git a/papi/pyqtgraph/GraphicsScene/mouseEvents.py b/papi/pyqtgraph/GraphicsScene/mouseEvents.py new file mode 100644 index 00000000..2e472e04 --- /dev/null +++ b/papi/pyqtgraph/GraphicsScene/mouseEvents.py @@ -0,0 +1,382 @@ +from ..Point import Point +from ..Qt import QtCore, QtGui +import weakref +from .. import ptime as ptime + +class MouseDragEvent(object): + """ + Instances of this class are delivered to items in a :class:`GraphicsScene ` via their mouseDragEvent() method when the item is being mouse-dragged. + + """ + + + + def __init__(self, moveEvent, pressEvent, lastEvent, start=False, finish=False): + self.start = start + self.finish = finish + self.accepted = False + self.currentItem = None + self._buttonDownScenePos = {} + self._buttonDownScreenPos = {} + for btn in [QtCore.Qt.LeftButton, QtCore.Qt.MidButton, QtCore.Qt.RightButton]: + self._buttonDownScenePos[int(btn)] = moveEvent.buttonDownScenePos(btn) + self._buttonDownScreenPos[int(btn)] = moveEvent.buttonDownScreenPos(btn) + self._scenePos = moveEvent.scenePos() + self._screenPos = moveEvent.screenPos() + if lastEvent is None: + self._lastScenePos = pressEvent.scenePos() + self._lastScreenPos = pressEvent.screenPos() + else: + self._lastScenePos = lastEvent.scenePos() + self._lastScreenPos = lastEvent.screenPos() + self._buttons = moveEvent.buttons() + self._button = pressEvent.button() + self._modifiers = moveEvent.modifiers() + self.acceptedItem = None + + def accept(self): + """An item should call this method if it can handle the event. This will prevent the event being delivered to any other items.""" + self.accepted = True + self.acceptedItem = self.currentItem + + def ignore(self): + """An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items.""" + self.accepted = False + + def isAccepted(self): + return self.accepted + + def scenePos(self): + """Return the current scene position of the mouse.""" + return Point(self._scenePos) + + def screenPos(self): + """Return the current screen position (pixels relative to widget) of the mouse.""" + return Point(self._screenPos) + + def buttonDownScenePos(self, btn=None): + """ + Return the scene position of the mouse at the time *btn* was pressed. + If *btn* is omitted, then the button that initiated the drag is assumed. + """ + if btn is None: + btn = self.button() + return Point(self._buttonDownScenePos[int(btn)]) + + def buttonDownScreenPos(self, btn=None): + """ + Return the screen position (pixels relative to widget) of the mouse at the time *btn* was pressed. + If *btn* is omitted, then the button that initiated the drag is assumed. + """ + if btn is None: + btn = self.button() + return Point(self._buttonDownScreenPos[int(btn)]) + + def lastScenePos(self): + """ + Return the scene position of the mouse immediately prior to this event. + """ + return Point(self._lastScenePos) + + def lastScreenPos(self): + """ + Return the screen position of the mouse immediately prior to this event. + """ + return Point(self._lastScreenPos) + + def buttons(self): + """ + Return the buttons currently pressed on the mouse. + (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) + """ + return self._buttons + + def button(self): + """Return the button that initiated the drag (may be different from the buttons currently pressed) + (see QGraphicsSceneMouseEvent::button in the Qt documentation) + + """ + return self._button + + def pos(self): + """ + Return the current position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._scenePos)) + + def lastPos(self): + """ + Return the previous position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._lastScenePos)) + + def buttonDownPos(self, btn=None): + """ + Return the position of the mouse at the time the drag was initiated + in the coordinate system of the item that the event was delivered to. + """ + if btn is None: + btn = self.button() + return Point(self.currentItem.mapFromScene(self._buttonDownScenePos[int(btn)])) + + def isStart(self): + """Returns True if this event is the first since a drag was initiated.""" + return self.start + + def isFinish(self): + """Returns False if this is the last event in a drag. Note that this + event will have the same position as the previous one.""" + return self.finish + + def __repr__(self): + if self.currentItem is None: + lp = self._lastScenePos + p = self._scenePos + else: + lp = self.lastPos() + p = self.pos() + return "(%g,%g) buttons=%d start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isStart()), str(self.isFinish())) + + def modifiers(self): + """Return any keyboard modifiers currently pressed. + (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) + + """ + return self._modifiers + + + +class MouseClickEvent(object): + """ + Instances of this class are delivered to items in a :class:`GraphicsScene ` via their mouseClickEvent() method when the item is clicked. + + + """ + + def __init__(self, pressEvent, double=False): + self.accepted = False + self.currentItem = None + self._double = double + self._scenePos = pressEvent.scenePos() + self._screenPos = pressEvent.screenPos() + self._button = pressEvent.button() + self._buttons = pressEvent.buttons() + self._modifiers = pressEvent.modifiers() + self._time = ptime.time() + self.acceptedItem = None + + def accept(self): + """An item should call this method if it can handle the event. This will prevent the event being delivered to any other items.""" + self.accepted = True + self.acceptedItem = self.currentItem + + def ignore(self): + """An item should call this method if it cannot handle the event. This will allow the event to be delivered to other items.""" + self.accepted = False + + def isAccepted(self): + return self.accepted + + def scenePos(self): + """Return the current scene position of the mouse.""" + return Point(self._scenePos) + + def screenPos(self): + """Return the current screen position (pixels relative to widget) of the mouse.""" + return Point(self._screenPos) + + def buttons(self): + """ + Return the buttons currently pressed on the mouse. + (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) + """ + return self._buttons + + def button(self): + """Return the mouse button that generated the click event. + (see QGraphicsSceneMouseEvent::button in the Qt documentation) + """ + return self._button + + def double(self): + """Return True if this is a double-click.""" + return self._double + + def pos(self): + """ + Return the current position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._scenePos)) + + def lastPos(self): + """ + Return the previous position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._lastScenePos)) + + def modifiers(self): + """Return any keyboard modifiers currently pressed. + (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) + """ + return self._modifiers + + def __repr__(self): + try: + if self.currentItem is None: + p = self._scenePos + else: + p = self.pos() + return "" % (p.x(), p.y(), int(self.button())) + except: + return "" % (int(self.button())) + + def time(self): + return self._time + + + +class HoverEvent(object): + """ + Instances of this class are delivered to items in a :class:`GraphicsScene ` via their hoverEvent() method when the mouse is hovering over the item. + This event class both informs items that the mouse cursor is nearby and allows items to + communicate with one another about whether each item will accept *potential* mouse events. + + It is common for multiple overlapping items to receive hover events and respond by changing + their appearance. This can be misleading to the user since, in general, only one item will + respond to mouse events. To avoid this, items make calls to event.acceptClicks(button) + and/or acceptDrags(button). + + Each item may make multiple calls to acceptClicks/Drags, each time for a different button. + If the method returns True, then the item is guaranteed to be + the recipient of the claimed event IF the user presses the specified mouse button before + moving. If claimEvent returns False, then this item is guaranteed NOT to get the specified + event (because another has already claimed it) and the item should change its appearance + accordingly. + + event.isEnter() returns True if the mouse has just entered the item's shape; + event.isExit() returns True if the mouse has just left. + """ + def __init__(self, moveEvent, acceptable): + self.enter = False + self.acceptable = acceptable + self.exit = False + self.__clickItems = weakref.WeakValueDictionary() + self.__dragItems = weakref.WeakValueDictionary() + self.currentItem = None + if moveEvent is not None: + self._scenePos = moveEvent.scenePos() + self._screenPos = moveEvent.screenPos() + self._lastScenePos = moveEvent.lastScenePos() + self._lastScreenPos = moveEvent.lastScreenPos() + self._buttons = moveEvent.buttons() + self._modifiers = moveEvent.modifiers() + else: + self.exit = True + + + + def isEnter(self): + """Returns True if the mouse has just entered the item's shape""" + return self.enter + + def isExit(self): + """Returns True if the mouse has just exited the item's shape""" + return self.exit + + def acceptClicks(self, button): + """Inform the scene that the item (that the event was delivered to) + would accept a mouse click event if the user were to click before + moving the mouse again. + + Returns True if the request is successful, otherwise returns False (indicating + that some other item would receive an incoming click). + """ + if not self.acceptable: + return False + if button not in self.__clickItems: + self.__clickItems[button] = self.currentItem + return True + return False + + def acceptDrags(self, button): + """Inform the scene that the item (that the event was delivered to) + would accept a mouse drag event if the user were to drag before + the next hover event. + + Returns True if the request is successful, otherwise returns False (indicating + that some other item would receive an incoming drag event). + """ + if not self.acceptable: + return False + if button not in self.__dragItems: + self.__dragItems[button] = self.currentItem + return True + return False + + def scenePos(self): + """Return the current scene position of the mouse.""" + return Point(self._scenePos) + + def screenPos(self): + """Return the current screen position of the mouse.""" + return Point(self._screenPos) + + def lastScenePos(self): + """Return the previous scene position of the mouse.""" + return Point(self._lastScenePos) + + def lastScreenPos(self): + """Return the previous screen position of the mouse.""" + return Point(self._lastScreenPos) + + def buttons(self): + """ + Return the buttons currently pressed on the mouse. + (see QGraphicsSceneMouseEvent::buttons in the Qt documentation) + """ + return self._buttons + + def pos(self): + """ + Return the current position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._scenePos)) + + def lastPos(self): + """ + Return the previous position of the mouse in the coordinate system of the item + that the event was delivered to. + """ + return Point(self.currentItem.mapFromScene(self._lastScenePos)) + + def __repr__(self): + if self.exit: + return "" + + if self.currentItem is None: + lp = self._lastScenePos + p = self._scenePos + else: + lp = self.lastPos() + p = self.pos() + return "(%g,%g) buttons=%d enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isEnter()), str(self.isExit())) + + def modifiers(self): + """Return any keyboard modifiers currently pressed. + (see QGraphicsSceneMouseEvent::modifiers in the Qt documentation) + """ + return self._modifiers + + def clickItems(self): + return self.__clickItems + + def dragItems(self): + return self.__dragItems + + + \ No newline at end of file diff --git a/papi/pyqtgraph/PlotData.py b/papi/pyqtgraph/PlotData.py new file mode 100644 index 00000000..e5faadda --- /dev/null +++ b/papi/pyqtgraph/PlotData.py @@ -0,0 +1,56 @@ + + +class PlotData(object): + """ + Class used for managing plot data + - allows data sharing between multiple graphics items (curve, scatter, graph..) + - each item may define the columns it needs + - column groupings ('pos' or x, y, z) + - efficiently appendable + - log, fft transformations + - color mode conversion (float/byte/qcolor) + - pen/brush conversion + - per-field cached masking + - allows multiple masking fields (different graphics need to mask on different criteria) + - removal of nan/inf values + - option for single value shared by entire column + - cached downsampling + - cached min / max / hasnan / isuniform + """ + def __init__(self): + self.fields = {} + + self.maxVals = {} ## cache for max/min + self.minVals = {} + + def addFields(self, **fields): + for f in fields: + if f not in self.fields: + self.fields[f] = None + + def hasField(self, f): + return f in self.fields + + def __getitem__(self, field): + return self.fields[field] + + def __setitem__(self, field, val): + self.fields[field] = val + + def max(self, field): + mx = self.maxVals.get(field, None) + if mx is None: + mx = np.max(self[field]) + self.maxVals[field] = mx + return mx + + def min(self, field): + mn = self.minVals.get(field, None) + if mn is None: + mn = np.min(self[field]) + self.minVals[field] = mn + return mn + + + + \ No newline at end of file diff --git a/papi/pyqtgraph/Point.py b/papi/pyqtgraph/Point.py new file mode 100644 index 00000000..4d04f01c --- /dev/null +++ b/papi/pyqtgraph/Point.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +""" +Point.py - Extension of QPointF which adds a few missing methods. +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from .Qt import QtCore +import numpy as np + +def clip(x, mn, mx): + if x > mx: + return mx + if x < mn: + return mn + return x + +class Point(QtCore.QPointF): + """Extension of QPointF which adds a few missing methods.""" + + def __init__(self, *args): + if len(args) == 1: + if isinstance(args[0], QtCore.QSizeF): + QtCore.QPointF.__init__(self, float(args[0].width()), float(args[0].height())) + return + elif isinstance(args[0], float) or isinstance(args[0], int): + QtCore.QPointF.__init__(self, float(args[0]), float(args[0])) + return + elif hasattr(args[0], '__getitem__'): + QtCore.QPointF.__init__(self, float(args[0][0]), float(args[0][1])) + return + elif len(args) == 2: + QtCore.QPointF.__init__(self, args[0], args[1]) + return + QtCore.QPointF.__init__(self, *args) + + def __len__(self): + return 2 + + def __reduce__(self): + return (Point, (self.x(), self.y())) + + def __getitem__(self, i): + if i == 0: + return self.x() + elif i == 1: + return self.y() + else: + raise IndexError("Point has no index %s" % str(i)) + + def __setitem__(self, i, x): + if i == 0: + return self.setX(x) + elif i == 1: + return self.setY(x) + else: + raise IndexError("Point has no index %s" % str(i)) + + def __radd__(self, a): + return self._math_('__radd__', a) + + def __add__(self, a): + return self._math_('__add__', a) + + def __rsub__(self, a): + return self._math_('__rsub__', a) + + def __sub__(self, a): + return self._math_('__sub__', a) + + def __rmul__(self, a): + return self._math_('__rmul__', a) + + def __mul__(self, a): + return self._math_('__mul__', a) + + def __rdiv__(self, a): + return self._math_('__rdiv__', a) + + def __div__(self, a): + return self._math_('__div__', a) + + def __truediv__(self, a): + return self._math_('__truediv__', a) + + def __rtruediv__(self, a): + return self._math_('__rtruediv__', a) + + def __rpow__(self, a): + return self._math_('__rpow__', a) + + def __pow__(self, a): + return self._math_('__pow__', a) + + def _math_(self, op, x): + #print "point math:", op + #try: + #fn = getattr(QtCore.QPointF, op) + #pt = fn(self, x) + #print fn, pt, self, x + #return Point(pt) + #except AttributeError: + x = Point(x) + return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1])) + + def length(self): + """Returns the vector length of this Point.""" + return (self[0]**2 + self[1]**2) ** 0.5 + + def norm(self): + """Returns a vector in the same direction with unit length.""" + return self / self.length() + + def angle(self, a): + """Returns the angle in degrees between this vector and the vector a.""" + n1 = self.length() + n2 = a.length() + if n1 == 0. or n2 == 0.: + return None + ## Probably this should be done with arctan2 instead.. + ang = np.arccos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians + c = self.cross(a) + if c > 0: + ang *= -1. + return ang * 180. / np.pi + + def dot(self, a): + """Returns the dot product of a and this Point.""" + a = Point(a) + return self[0]*a[0] + self[1]*a[1] + + def cross(self, a): + a = Point(a) + return self[0]*a[1] - self[1]*a[0] + + def proj(self, b): + """Return the projection of this vector onto the vector b""" + b1 = b / b.length() + return self.dot(b1) * b1 + + def __repr__(self): + return "Point(%f, %f)" % (self[0], self[1]) + + + def min(self): + return min(self[0], self[1]) + + def max(self): + return max(self[0], self[1]) + + def copy(self): + return Point(self) + + def toQPoint(self): + return QtCore.QPoint(*self) diff --git a/papi/pyqtgraph/Qt.py b/papi/pyqtgraph/Qt.py new file mode 100644 index 00000000..efbe66c4 --- /dev/null +++ b/papi/pyqtgraph/Qt.py @@ -0,0 +1,135 @@ +""" +This module exists to smooth out some of the differences between PySide and PyQt4: + +* Automatically import either PyQt4 or PySide depending on availability +* Allow to import QtCore/QtGui pyqtgraph.Qt without specifying which Qt wrapper + you want to use. +* Declare QtCore.Signal, .Slot in PyQt4 +* Declare loadUiType function for Pyside + +""" + +import sys, re + +from .python2_3 import asUnicode + +## Automatically determine whether to use PyQt or PySide. +## This is done by first checking to see whether one of the libraries +## is already imported. If not, then attempt to import PyQt4, then PySide. +if 'PyQt4' in sys.modules: + USE_PYSIDE = False +elif 'PySide' in sys.modules: + USE_PYSIDE = True +else: + try: + import PyQt4 + USE_PYSIDE = False + except ImportError: + try: + import PySide + USE_PYSIDE = True + except ImportError: + raise Exception("PyQtGraph requires either PyQt4 or PySide; neither package could be imported.") + +if USE_PYSIDE: + from PySide import QtGui, QtCore, QtOpenGL, QtSvg + try: + from PySide import QtTest + except ImportError: + pass + import PySide + try: + from PySide import shiboken + isQObjectAlive = shiboken.isValid + except ImportError: + def isQObjectAlive(obj): + try: + if hasattr(obj, 'parent'): + obj.parent() + elif hasattr(obj, 'parentItem'): + obj.parentItem() + else: + raise Exception("Cannot determine whether Qt object %s is still alive." % obj) + except RuntimeError: + return False + else: + return True + + VERSION_INFO = 'PySide ' + PySide.__version__ + + # Make a loadUiType function like PyQt has + + # Credit: + # http://stackoverflow.com/questions/4442286/python-code-genration-with-pyside-uic/14195313#14195313 + + class StringIO(object): + """Alternative to built-in StringIO needed to circumvent unicode/ascii issues""" + def __init__(self): + self.data = [] + + def write(self, data): + self.data.append(data) + + def getvalue(self): + return ''.join(map(asUnicode, self.data)).encode('utf8') + + def loadUiType(uiFile): + """ + Pyside "loadUiType" command like PyQt4 has one, so we have to convert the ui file to py code in-memory first and then execute it in a special frame to retrieve the form_class. + """ + import pysideuic + import xml.etree.ElementTree as xml + #from io import StringIO + + parsed = xml.parse(uiFile) + widget_class = parsed.find('widget').get('class') + form_class = parsed.find('class').text + + with open(uiFile, 'r') as f: + o = StringIO() + frame = {} + + pysideuic.compileUi(f, o, indent=0) + pyc = compile(o.getvalue(), '', 'exec') + exec(pyc, frame) + + #Fetch the base_class and form class based on their type in the xml from designer + form_class = frame['Ui_%s'%form_class] + base_class = eval('QtGui.%s'%widget_class) + + return form_class, base_class + + +else: + from PyQt4 import QtGui, QtCore, uic + try: + from PyQt4 import QtSvg + except ImportError: + pass + try: + from PyQt4 import QtOpenGL + except ImportError: + pass + try: + from PyQt4 import QtTest + except ImportError: + pass + + + import sip + def isQObjectAlive(obj): + return not sip.isdeleted(obj) + loadUiType = uic.loadUiType + + QtCore.Signal = QtCore.pyqtSignal + VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR + + +## Make sure we have Qt >= 4.7 +versionReq = [4, 7] +QtVersion = PySide.QtCore.__version__ if USE_PYSIDE else QtCore.QT_VERSION_STR +m = re.match(r'(\d+)\.(\d+).*', QtVersion) +if m is not None and list(map(int, m.groups())) < versionReq: + print(list(map(int, m.groups()))) + raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion)) + diff --git a/papi/pyqtgraph/SRTTransform.py b/papi/pyqtgraph/SRTTransform.py new file mode 100644 index 00000000..23281343 --- /dev/null +++ b/papi/pyqtgraph/SRTTransform.py @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- +from .Qt import QtCore, QtGui +from .Point import Point +import numpy as np + +class SRTTransform(QtGui.QTransform): + """Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate + This transform has no shear; angles are always preserved. + """ + def __init__(self, init=None): + QtGui.QTransform.__init__(self) + self.reset() + + if init is None: + return + elif isinstance(init, dict): + self.restoreState(init) + elif isinstance(init, SRTTransform): + self._state = { + 'pos': Point(init._state['pos']), + 'scale': Point(init._state['scale']), + 'angle': init._state['angle'] + } + self.update() + elif isinstance(init, QtGui.QTransform): + self.setFromQTransform(init) + elif isinstance(init, QtGui.QMatrix4x4): + self.setFromMatrix4x4(init) + else: + raise Exception("Cannot create SRTTransform from input type: %s" % str(type(init))) + + + def getScale(self): + return self._state['scale'] + + def getAngle(self): + ## deprecated; for backward compatibility + return self.getRotation() + + def getRotation(self): + return self._state['angle'] + + def getTranslation(self): + return self._state['pos'] + + def reset(self): + self._state = { + 'pos': Point(0,0), + 'scale': Point(1,1), + 'angle': 0.0 ## in degrees + } + self.update() + + def setFromQTransform(self, tr): + p1 = Point(tr.map(0., 0.)) + p2 = Point(tr.map(1., 0.)) + p3 = Point(tr.map(0., 1.)) + + dp2 = Point(p2-p1) + dp3 = Point(p3-p1) + + ## detect flipped axes + if dp2.angle(dp3) > 0: + #da = 180 + da = 0 + sy = -1.0 + else: + da = 0 + sy = 1.0 + + self._state = { + 'pos': Point(p1), + 'scale': Point(dp2.length(), dp3.length() * sy), + 'angle': (np.arctan2(dp2[1], dp2[0]) * 180. / np.pi) + da + } + self.update() + + def setFromMatrix4x4(self, m): + m = SRTTransform3D(m) + angle, axis = m.getRotation() + if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1): + print("angle: %s axis: %s" % (str(angle), str(axis))) + raise Exception("Can only convert 4x4 matrix to 3x3 if rotation is around Z-axis.") + self._state = { + 'pos': Point(m.getTranslation()), + 'scale': Point(m.getScale()), + 'angle': angle + } + self.update() + + def translate(self, *args): + """Acceptable arguments are: + x, y + [x, y] + Point(x,y)""" + t = Point(*args) + self.setTranslate(self._state['pos']+t) + + def setTranslate(self, *args): + """Acceptable arguments are: + x, y + [x, y] + Point(x,y)""" + self._state['pos'] = Point(*args) + self.update() + + def scale(self, *args): + """Acceptable arguments are: + x, y + [x, y] + Point(x,y)""" + s = Point(*args) + self.setScale(self._state['scale'] * s) + + def setScale(self, *args): + """Acceptable arguments are: + x, y + [x, y] + Point(x,y)""" + self._state['scale'] = Point(*args) + self.update() + + def rotate(self, angle): + """Rotate the transformation by angle (in degrees)""" + self.setRotate(self._state['angle'] + angle) + + def setRotate(self, angle): + """Set the transformation rotation to angle (in degrees)""" + self._state['angle'] = angle + self.update() + + def __truediv__(self, t): + """A / B == B^-1 * A""" + dt = t.inverted()[0] * self + return SRTTransform(dt) + + def __div__(self, t): + return self.__truediv__(t) + + def __mul__(self, t): + return SRTTransform(QtGui.QTransform.__mul__(self, t)) + + def saveState(self): + p = self._state['pos'] + s = self._state['scale'] + #if s[0] == 0: + #raise Exception('Invalid scale: %s' % str(s)) + return {'pos': (p[0], p[1]), 'scale': (s[0], s[1]), 'angle': self._state['angle']} + + def restoreState(self, state): + self._state['pos'] = Point(state.get('pos', (0,0))) + self._state['scale'] = Point(state.get('scale', (1.,1.))) + self._state['angle'] = state.get('angle', 0) + self.update() + + def update(self): + QtGui.QTransform.reset(self) + ## modifications to the transform are multiplied on the right, so we need to reverse order here. + QtGui.QTransform.translate(self, *self._state['pos']) + QtGui.QTransform.rotate(self, self._state['angle']) + QtGui.QTransform.scale(self, *self._state['scale']) + + def __repr__(self): + return str(self.saveState()) + + def matrix(self): + return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]]) + +if __name__ == '__main__': + from . import widgets + import GraphicsView + from .functions import * + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + win.show() + cw = GraphicsView.GraphicsView() + #cw.enableMouse() + win.setCentralWidget(cw) + s = QtGui.QGraphicsScene() + cw.setScene(s) + win.resize(600,600) + cw.enableMouse() + cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) + + class Item(QtGui.QGraphicsItem): + def __init__(self): + QtGui.QGraphicsItem.__init__(self) + self.b = QtGui.QGraphicsRectItem(20, 20, 20, 20, self) + self.b.setPen(QtGui.QPen(mkPen('y'))) + self.t1 = QtGui.QGraphicsTextItem(self) + self.t1.setHtml('R') + self.t1.translate(20, 20) + self.l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0, self) + self.l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10, self) + self.l1.setPen(QtGui.QPen(mkPen('y'))) + self.l2.setPen(QtGui.QPen(mkPen('y'))) + def boundingRect(self): + return QtCore.QRectF() + def paint(self, *args): + pass + + #s.addItem(b) + #s.addItem(t1) + item = Item() + s.addItem(item) + l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0) + l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10) + l1.setPen(QtGui.QPen(mkPen('r'))) + l2.setPen(QtGui.QPen(mkPen('r'))) + s.addItem(l1) + s.addItem(l2) + + tr1 = SRTTransform() + tr2 = SRTTransform() + tr3 = QtGui.QTransform() + tr3.translate(20, 0) + tr3.rotate(45) + print("QTransform -> Transform:", SRTTransform(tr3)) + + print("tr1:", tr1) + + tr2.translate(20, 0) + tr2.rotate(45) + print("tr2:", tr2) + + dt = tr2/tr1 + print("tr2 / tr1 = ", dt) + + print("tr2 * tr1 = ", tr2*tr1) + + tr4 = SRTTransform() + tr4.scale(-1, 1) + tr4.rotate(30) + print("tr1 * tr4 = ", tr1*tr4) + + w1 = widgets.TestROI((19,19), (22, 22), invertible=True) + #w2 = widgets.TestROI((0,0), (150, 150)) + w1.setZValue(10) + s.addItem(w1) + #s.addItem(w2) + w1Base = w1.getState() + #w2Base = w2.getState() + def update(): + tr1 = w1.getGlobalTransform(w1Base) + #tr2 = w2.getGlobalTransform(w2Base) + item.setTransform(tr1) + + #def update2(): + #tr1 = w1.getGlobalTransform(w1Base) + #tr2 = w2.getGlobalTransform(w2Base) + #t1.setTransform(tr1) + #w1.setState(w1Base) + #w1.applyGlobalTransform(tr2) + + w1.sigRegionChanged.connect(update) + #w2.sigRegionChanged.connect(update2) + +from .SRTTransform3D import SRTTransform3D diff --git a/papi/pyqtgraph/SRTTransform3D.py b/papi/pyqtgraph/SRTTransform3D.py new file mode 100644 index 00000000..9b54843b --- /dev/null +++ b/papi/pyqtgraph/SRTTransform3D.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- +from .Qt import QtCore, QtGui +from .Vector import Vector +from .Transform3D import Transform3D +from .Vector import Vector +import numpy as np + +class SRTTransform3D(Transform3D): + """4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate + This transform has no shear; angles are always preserved. + """ + def __init__(self, init=None): + Transform3D.__init__(self) + self.reset() + if init is None: + return + if init.__class__ is QtGui.QTransform: + init = SRTTransform(init) + + if isinstance(init, dict): + self.restoreState(init) + elif isinstance(init, SRTTransform3D): + self._state = { + 'pos': Vector(init._state['pos']), + 'scale': Vector(init._state['scale']), + 'angle': init._state['angle'], + 'axis': Vector(init._state['axis']), + } + self.update() + elif isinstance(init, SRTTransform): + self._state = { + 'pos': Vector(init._state['pos']), + 'scale': Vector(init._state['scale']), + 'angle': init._state['angle'], + 'axis': Vector(0, 0, 1), + } + self._state['scale'][2] = 1.0 + self.update() + elif isinstance(init, QtGui.QMatrix4x4): + self.setFromMatrix(init) + else: + raise Exception("Cannot build SRTTransform3D from argument type:", type(init)) + + + def getScale(self): + return Vector(self._state['scale']) + + def getRotation(self): + """Return (angle, axis) of rotation""" + return self._state['angle'], Vector(self._state['axis']) + + def getTranslation(self): + return Vector(self._state['pos']) + + def reset(self): + self._state = { + 'pos': Vector(0,0,0), + 'scale': Vector(1,1,1), + 'angle': 0.0, ## in degrees + 'axis': (0, 0, 1) + } + self.update() + + def translate(self, *args): + """Adjust the translation of this transform""" + t = Vector(*args) + self.setTranslate(self._state['pos']+t) + + def setTranslate(self, *args): + """Set the translation of this transform""" + self._state['pos'] = Vector(*args) + self.update() + + def scale(self, *args): + """adjust the scale of this transform""" + ## try to prevent accidentally setting 0 scale on z axis + if len(args) == 1 and hasattr(args[0], '__len__'): + args = args[0] + if len(args) == 2: + args = args + (1,) + + s = Vector(*args) + self.setScale(self._state['scale'] * s) + + def setScale(self, *args): + """Set the scale of this transform""" + if len(args) == 1 and hasattr(args[0], '__len__'): + args = args[0] + if len(args) == 2: + args = args + (1,) + self._state['scale'] = Vector(*args) + self.update() + + def rotate(self, angle, axis=(0,0,1)): + """Adjust the rotation of this transform""" + origAxis = self._state['axis'] + if axis[0] == origAxis[0] and axis[1] == origAxis[1] and axis[2] == origAxis[2]: + self.setRotate(self._state['angle'] + angle) + else: + m = QtGui.QMatrix4x4() + m.translate(*self._state['pos']) + m.rotate(self._state['angle'], *self._state['axis']) + m.rotate(angle, *axis) + m.scale(*self._state['scale']) + self.setFromMatrix(m) + + def setRotate(self, angle, axis=(0,0,1)): + """Set the transformation rotation to angle (in degrees)""" + + self._state['angle'] = angle + self._state['axis'] = Vector(axis) + self.update() + + def setFromMatrix(self, m): + """ + Set this transform mased on the elements of *m* + The input matrix must be affine AND have no shear, + otherwise the conversion will most likely fail. + """ + import numpy.linalg + for i in range(4): + self.setRow(i, m.row(i)) + m = self.matrix().reshape(4,4) + ## translation is 4th column + self._state['pos'] = m[:3,3] + + ## scale is vector-length of first three columns + scale = (m[:3,:3]**2).sum(axis=0)**0.5 + ## see whether there is an inversion + z = np.cross(m[0, :3], m[1, :3]) + if np.dot(z, m[2, :3]) < 0: + scale[1] *= -1 ## doesn't really matter which axis we invert + self._state['scale'] = scale + + ## rotation axis is the eigenvector with eigenvalue=1 + r = m[:3, :3] / scale[np.newaxis, :] + try: + evals, evecs = numpy.linalg.eig(r) + except: + print("Rotation matrix: %s" % str(r)) + print("Scale: %s" % str(scale)) + print("Original matrix: %s" % str(m)) + raise + eigIndex = np.argwhere(np.abs(evals-1) < 1e-6) + if len(eigIndex) < 1: + print("eigenvalues: %s" % str(evals)) + print("eigenvectors: %s" % str(evecs)) + print("index: %s, %s" % (str(eigIndex), str(evals-1))) + raise Exception("Could not determine rotation axis.") + axis = evecs[:,eigIndex[0,0]].real + axis /= ((axis**2).sum())**0.5 + self._state['axis'] = axis + + ## trace(r) == 2 cos(angle) + 1, so: + cos = (r.trace()-1)*0.5 ## this only gets us abs(angle) + + ## The off-diagonal values can be used to correct the angle ambiguity, + ## but we need to figure out which element to use: + axisInd = np.argmax(np.abs(axis)) + rInd,sign = [((1,2), -1), ((0,2), 1), ((0,1), -1)][axisInd] + + ## Then we have r-r.T = sin(angle) * 2 * sign * axis[axisInd]; + ## solve for sin(angle) + sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd]) + + ## finally, we get the complete angle from arctan(sin/cos) + self._state['angle'] = np.arctan2(sin, cos) * 180 / np.pi + if self._state['angle'] == 0: + self._state['axis'] = (0,0,1) + + def as2D(self): + """Return a QTransform representing the x,y portion of this transform (if possible)""" + return SRTTransform(self) + + #def __div__(self, t): + #"""A / B == B^-1 * A""" + #dt = t.inverted()[0] * self + #return SRTTransform(dt) + + #def __mul__(self, t): + #return SRTTransform(QtGui.QTransform.__mul__(self, t)) + + def saveState(self): + p = self._state['pos'] + s = self._state['scale'] + ax = self._state['axis'] + #if s[0] == 0: + #raise Exception('Invalid scale: %s' % str(s)) + return { + 'pos': (p[0], p[1], p[2]), + 'scale': (s[0], s[1], s[2]), + 'angle': self._state['angle'], + 'axis': (ax[0], ax[1], ax[2]) + } + + def restoreState(self, state): + self._state['pos'] = Vector(state.get('pos', (0.,0.,0.))) + scale = state.get('scale', (1.,1.,1.)) + scale = tuple(scale) + (1.,) * (3-len(scale)) + self._state['scale'] = Vector(scale) + self._state['angle'] = state.get('angle', 0.) + self._state['axis'] = state.get('axis', (0, 0, 1)) + self.update() + + def update(self): + Transform3D.setToIdentity(self) + ## modifications to the transform are multiplied on the right, so we need to reverse order here. + Transform3D.translate(self, *self._state['pos']) + Transform3D.rotate(self, self._state['angle'], *self._state['axis']) + Transform3D.scale(self, *self._state['scale']) + + def __repr__(self): + return str(self.saveState()) + + def matrix(self, nd=3): + if nd == 3: + return np.array(self.copyDataTo()).reshape(4,4) + elif nd == 2: + m = np.array(self.copyDataTo()).reshape(4,4) + m[2] = m[3] + m[:,2] = m[:,3] + return m[:3,:3] + else: + raise Exception("Argument 'nd' must be 2 or 3") + +if __name__ == '__main__': + import widgets + import GraphicsView + from functions import * + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + win.show() + cw = GraphicsView.GraphicsView() + #cw.enableMouse() + win.setCentralWidget(cw) + s = QtGui.QGraphicsScene() + cw.setScene(s) + win.resize(600,600) + cw.enableMouse() + cw.setRange(QtCore.QRectF(-100., -100., 200., 200.)) + + class Item(QtGui.QGraphicsItem): + def __init__(self): + QtGui.QGraphicsItem.__init__(self) + self.b = QtGui.QGraphicsRectItem(20, 20, 20, 20, self) + self.b.setPen(QtGui.QPen(mkPen('y'))) + self.t1 = QtGui.QGraphicsTextItem(self) + self.t1.setHtml('R') + self.t1.translate(20, 20) + self.l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0, self) + self.l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10, self) + self.l1.setPen(QtGui.QPen(mkPen('y'))) + self.l2.setPen(QtGui.QPen(mkPen('y'))) + def boundingRect(self): + return QtCore.QRectF() + def paint(self, *args): + pass + + #s.addItem(b) + #s.addItem(t1) + item = Item() + s.addItem(item) + l1 = QtGui.QGraphicsLineItem(10, 0, -10, 0) + l2 = QtGui.QGraphicsLineItem(0, 10, 0, -10) + l1.setPen(QtGui.QPen(mkPen('r'))) + l2.setPen(QtGui.QPen(mkPen('r'))) + s.addItem(l1) + s.addItem(l2) + + tr1 = SRTTransform() + tr2 = SRTTransform() + tr3 = QtGui.QTransform() + tr3.translate(20, 0) + tr3.rotate(45) + print("QTransform -> Transform: %s" % str(SRTTransform(tr3))) + + print("tr1: %s" % str(tr1)) + + tr2.translate(20, 0) + tr2.rotate(45) + print("tr2: %s" % str(tr2)) + + dt = tr2/tr1 + print("tr2 / tr1 = %s" % str(dt)) + + print("tr2 * tr1 = %s" % str(tr2*tr1)) + + tr4 = SRTTransform() + tr4.scale(-1, 1) + tr4.rotate(30) + print("tr1 * tr4 = %s" % str(tr1*tr4)) + + w1 = widgets.TestROI((19,19), (22, 22), invertible=True) + #w2 = widgets.TestROI((0,0), (150, 150)) + w1.setZValue(10) + s.addItem(w1) + #s.addItem(w2) + w1Base = w1.getState() + #w2Base = w2.getState() + def update(): + tr1 = w1.getGlobalTransform(w1Base) + #tr2 = w2.getGlobalTransform(w2Base) + item.setTransform(tr1) + + #def update2(): + #tr1 = w1.getGlobalTransform(w1Base) + #tr2 = w2.getGlobalTransform(w2Base) + #t1.setTransform(tr1) + #w1.setState(w1Base) + #w1.applyGlobalTransform(tr2) + + w1.sigRegionChanged.connect(update) + #w2.sigRegionChanged.connect(update2) + +from .SRTTransform import SRTTransform diff --git a/papi/pyqtgraph/SignalProxy.py b/papi/pyqtgraph/SignalProxy.py new file mode 100644 index 00000000..d36282fa --- /dev/null +++ b/papi/pyqtgraph/SignalProxy.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +from .Qt import QtCore +from .ptime import time +from . import ThreadsafeTimer +import weakref + +__all__ = ['SignalProxy'] + +class SignalProxy(QtCore.QObject): + """Object which collects rapid-fire signals and condenses them + into a single signal or a rate-limited stream of signals. + Used, for example, to prevent a SpinBox from generating multiple + signals when the mouse wheel is rolled over it. + + Emits sigDelayed after input signals have stopped for a certain period of time. + """ + + sigDelayed = QtCore.Signal(object) + + def __init__(self, signal, delay=0.3, rateLimit=0, slot=None): + """Initialization arguments: + signal - a bound Signal or pyqtSignal instance + delay - Time (in seconds) to wait for signals to stop before emitting (default 0.3s) + slot - Optional function to connect sigDelayed to. + rateLimit - (signals/sec) if greater than 0, this allows signals to stream out at a + steady rate while they are being received. + """ + + QtCore.QObject.__init__(self) + signal.connect(self.signalReceived) + self.signal = signal + self.delay = delay + self.rateLimit = rateLimit + self.args = None + self.timer = ThreadsafeTimer.ThreadsafeTimer() + self.timer.timeout.connect(self.flush) + self.block = False + self.slot = weakref.ref(slot) + self.lastFlushTime = None + if slot is not None: + self.sigDelayed.connect(slot) + + def setDelay(self, delay): + self.delay = delay + + def signalReceived(self, *args): + """Received signal. Cancel previous timer and store args to be forwarded later.""" + if self.block: + return + self.args = args + if self.rateLimit == 0: + self.timer.stop() + self.timer.start((self.delay*1000)+1) + else: + now = time() + if self.lastFlushTime is None: + leakTime = 0 + else: + lastFlush = self.lastFlushTime + leakTime = max(0, (lastFlush + (1.0 / self.rateLimit)) - now) + + self.timer.stop() + self.timer.start((min(leakTime, self.delay)*1000)+1) + + + def flush(self): + """If there is a signal queued up, send it now.""" + if self.args is None or self.block: + return False + #self.emit(self.signal, *self.args) + self.sigDelayed.emit(self.args) + self.args = None + self.timer.stop() + self.lastFlushTime = time() + return True + + def disconnect(self): + self.block = True + try: + self.signal.disconnect(self.signalReceived) + except: + pass + try: + self.sigDelayed.disconnect(self.slot()) + except: + pass + + + +#def proxyConnect(source, signal, slot, delay=0.3): + #"""Connect a signal to a slot with delay. Returns the SignalProxy + #object that was created. Be sure to store this object so it is not + #garbage-collected immediately.""" + #sp = SignalProxy(source, signal, delay) + #if source is None: + #sp.connect(sp, QtCore.SIGNAL('signal'), slot) + #else: + #sp.connect(sp, signal, slot) + #return sp + + +if __name__ == '__main__': + from .Qt import QtGui + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + spin = QtGui.QSpinBox() + win.setCentralWidget(spin) + win.show() + + def fn(*args): + print("Raw signal:", args) + def fn2(*args): + print("Delayed signal:", args) + + + spin.valueChanged.connect(fn) + #proxy = proxyConnect(spin, QtCore.SIGNAL('valueChanged(int)'), fn) + proxy = SignalProxy(spin.valueChanged, delay=0.5, slot=fn2) + \ No newline at end of file diff --git a/papi/pyqtgraph/ThreadsafeTimer.py b/papi/pyqtgraph/ThreadsafeTimer.py new file mode 100644 index 00000000..201469de --- /dev/null +++ b/papi/pyqtgraph/ThreadsafeTimer.py @@ -0,0 +1,41 @@ +from .Qt import QtCore, QtGui + +class ThreadsafeTimer(QtCore.QObject): + """ + Thread-safe replacement for QTimer. + """ + + timeout = QtCore.Signal() + sigTimerStopRequested = QtCore.Signal() + sigTimerStartRequested = QtCore.Signal(object) + + def __init__(self): + QtCore.QObject.__init__(self) + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.timerFinished) + self.timer.moveToThread(QtCore.QCoreApplication.instance().thread()) + self.moveToThread(QtCore.QCoreApplication.instance().thread()) + self.sigTimerStopRequested.connect(self.stop, QtCore.Qt.QueuedConnection) + self.sigTimerStartRequested.connect(self.start, QtCore.Qt.QueuedConnection) + + + def start(self, timeout): + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + #print "start timer", self, "from gui thread" + self.timer.start(timeout) + else: + #print "start timer", self, "from remote thread" + self.sigTimerStartRequested.emit(timeout) + + def stop(self): + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + #print "stop timer", self, "from gui thread" + self.timer.stop() + else: + #print "stop timer", self, "from remote thread" + self.sigTimerStopRequested.emit() + + def timerFinished(self): + self.timeout.emit() \ No newline at end of file diff --git a/papi/pyqtgraph/Transform3D.py b/papi/pyqtgraph/Transform3D.py new file mode 100644 index 00000000..43b12de3 --- /dev/null +++ b/papi/pyqtgraph/Transform3D.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +from .Qt import QtCore, QtGui +from . import functions as fn +import numpy as np + +class Transform3D(QtGui.QMatrix4x4): + """ + Extension of QMatrix4x4 with some helpful methods added. + """ + def __init__(self, *args): + QtGui.QMatrix4x4.__init__(self, *args) + + def matrix(self, nd=3): + if nd == 3: + return np.array(self.copyDataTo()).reshape(4,4) + elif nd == 2: + m = np.array(self.copyDataTo()).reshape(4,4) + m[2] = m[3] + m[:,2] = m[:,3] + return m[:3,:3] + else: + raise Exception("Argument 'nd' must be 2 or 3") + + def map(self, obj): + """ + Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates + """ + if isinstance(obj, np.ndarray) and obj.ndim >= 2 and obj.shape[0] in (2,3): + return fn.transformCoordinates(self, obj) + else: + return QtGui.QMatrix4x4.map(self, obj) + + def inverted(self): + inv, b = QtGui.QMatrix4x4.inverted(self) + return Transform3D(inv), b \ No newline at end of file diff --git a/papi/pyqtgraph/Vector.py b/papi/pyqtgraph/Vector.py new file mode 100644 index 00000000..f2898e80 --- /dev/null +++ b/papi/pyqtgraph/Vector.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- +""" +Vector.py - Extension of QVector3D which adds a few missing methods. +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from .Qt import QtGui, QtCore, USE_PYSIDE +import numpy as np + +class Vector(QtGui.QVector3D): + """Extension of QVector3D which adds a few helpful methods.""" + + def __init__(self, *args): + if len(args) == 1: + if isinstance(args[0], QtCore.QSizeF): + QtGui.QVector3D.__init__(self, float(args[0].width()), float(args[0].height()), 0) + return + elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF): + QtGui.QVector3D.__init__(self, float(args[0].x()), float(args[0].y()), 0) + elif hasattr(args[0], '__getitem__'): + vals = list(args[0]) + if len(vals) == 2: + vals.append(0) + if len(vals) != 3: + raise Exception('Cannot init Vector with sequence of length %d' % len(args[0])) + QtGui.QVector3D.__init__(self, *vals) + return + elif len(args) == 2: + QtGui.QVector3D.__init__(self, args[0], args[1], 0) + return + QtGui.QVector3D.__init__(self, *args) + + def __len__(self): + return 3 + + def __add__(self, b): + # workaround for pyside bug. see https://bugs.launchpad.net/pyqtgraph/+bug/1223173 + if USE_PYSIDE and isinstance(b, QtGui.QVector3D): + b = Vector(b) + return QtGui.QVector3D.__add__(self, b) + + #def __reduce__(self): + #return (Point, (self.x(), self.y())) + + def __getitem__(self, i): + if i == 0: + return self.x() + elif i == 1: + return self.y() + elif i == 2: + return self.z() + else: + raise IndexError("Point has no index %s" % str(i)) + + def __setitem__(self, i, x): + if i == 0: + return self.setX(x) + elif i == 1: + return self.setY(x) + elif i == 2: + return self.setZ(x) + else: + raise IndexError("Point has no index %s" % str(i)) + + def __iter__(self): + yield(self.x()) + yield(self.y()) + yield(self.z()) + + def angle(self, a): + """Returns the angle in degrees between this vector and the vector a.""" + n1 = self.length() + n2 = a.length() + if n1 == 0. or n2 == 0.: + return None + ## Probably this should be done with arctan2 instead.. + ang = np.arccos(np.clip(QtGui.QVector3D.dotProduct(self, a) / (n1 * n2), -1.0, 1.0)) ### in radians +# c = self.crossProduct(a) +# if c > 0: +# ang *= -1. + return ang * 180. / np.pi + + def __abs__(self): + return Vector(abs(self.x()), abs(self.y()), abs(self.z())) + + \ No newline at end of file diff --git a/papi/pyqtgraph/WidgetGroup.py b/papi/pyqtgraph/WidgetGroup.py new file mode 100644 index 00000000..29541454 --- /dev/null +++ b/papi/pyqtgraph/WidgetGroup.py @@ -0,0 +1,298 @@ +# -*- coding: utf-8 -*- +""" +WidgetGroup.py - WidgetGroup class for easily managing lots of Qt widgets +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +This class addresses the problem of having to save and restore the state +of a large group of widgets. +""" + +from .Qt import QtCore, QtGui +import weakref, inspect +from .python2_3 import asUnicode + + +__all__ = ['WidgetGroup'] + +def splitterState(w): + s = str(w.saveState().toPercentEncoding()) + return s + +def restoreSplitter(w, s): + if type(s) is list: + w.setSizes(s) + elif type(s) is str: + w.restoreState(QtCore.QByteArray.fromPercentEncoding(s)) + else: + print("Can't configure QSplitter using object of type", type(s)) + if w.count() > 0: ## make sure at least one item is not collapsed + for i in w.sizes(): + if i > 0: + return + w.setSizes([50] * w.count()) + +def comboState(w): + ind = w.currentIndex() + data = w.itemData(ind) + #if not data.isValid(): + if data is not None: + try: + if not data.isValid(): + data = None + else: + data = data.toInt()[0] + except AttributeError: + pass + if data is None: + return asUnicode(w.itemText(ind)) + else: + return data + +def setComboState(w, v): + if type(v) is int: + #ind = w.findData(QtCore.QVariant(v)) + ind = w.findData(v) + if ind > -1: + w.setCurrentIndex(ind) + return + w.setCurrentIndex(w.findText(str(v))) + + +class WidgetGroup(QtCore.QObject): + """This class takes a list of widgets and keeps an internal record of their state which is always up to date. Allows reading and writing from groups of widgets simultaneously.""" + + ## List of widget types which can be handled by WidgetGroup. + ## The value for each type is a tuple (change signal function, get function, set function, [auto-add children]) + ## The change signal function that takes an object and returns a signal that is emitted any time the state of the widget changes, not just + ## when it is changed by user interaction. (for example, 'clicked' is not a valid signal here) + ## If the change signal is None, the value of the widget is not cached. + ## Custom widgets not in this list can be made to work with WidgetGroup by giving them a 'widgetGroupInterface' method + ## which returns the tuple. + classes = { + QtGui.QSpinBox: + (lambda w: w.valueChanged, + QtGui.QSpinBox.value, + QtGui.QSpinBox.setValue), + QtGui.QDoubleSpinBox: + (lambda w: w.valueChanged, + QtGui.QDoubleSpinBox.value, + QtGui.QDoubleSpinBox.setValue), + QtGui.QSplitter: + (None, + splitterState, + restoreSplitter, + True), + QtGui.QCheckBox: + (lambda w: w.stateChanged, + QtGui.QCheckBox.isChecked, + QtGui.QCheckBox.setChecked), + QtGui.QComboBox: + (lambda w: w.currentIndexChanged, + comboState, + setComboState), + QtGui.QGroupBox: + (lambda w: w.toggled, + QtGui.QGroupBox.isChecked, + QtGui.QGroupBox.setChecked, + True), + QtGui.QLineEdit: + (lambda w: w.editingFinished, + lambda w: str(w.text()), + QtGui.QLineEdit.setText), + QtGui.QRadioButton: + (lambda w: w.toggled, + QtGui.QRadioButton.isChecked, + QtGui.QRadioButton.setChecked), + QtGui.QSlider: + (lambda w: w.valueChanged, + QtGui.QSlider.value, + QtGui.QSlider.setValue), + } + + sigChanged = QtCore.Signal(str, object) + + + def __init__(self, widgetList=None): + """Initialize WidgetGroup, adding specified widgets into this group. + widgetList can be: + - a list of widget specifications (widget, [name], [scale]) + - a dict of name: widget pairs + - any QObject, and all compatible child widgets will be added recursively. + + The 'scale' parameter for each widget allows QSpinBox to display a different value than the value recorded + in the group state (for example, the program may set a spin box value to 100e-6 and have it displayed as 100 to the user) + """ + QtCore.QObject.__init__(self) + self.widgetList = weakref.WeakKeyDictionary() # Make sure widgets don't stick around just because they are listed here + self.scales = weakref.WeakKeyDictionary() + self.cache = {} ## name:value pairs + self.uncachedWidgets = weakref.WeakKeyDictionary() + if isinstance(widgetList, QtCore.QObject): + self.autoAdd(widgetList) + elif isinstance(widgetList, list): + for w in widgetList: + self.addWidget(*w) + elif isinstance(widgetList, dict): + for name, w in widgetList.items(): + self.addWidget(w, name) + elif widgetList is None: + return + else: + raise Exception("Wrong argument type %s" % type(widgetList)) + + def addWidget(self, w, name=None, scale=None): + if not self.acceptsType(w): + raise Exception("Widget type %s not supported by WidgetGroup" % type(w)) + if name is None: + name = str(w.objectName()) + if name == '': + raise Exception("Cannot add widget '%s' without a name." % str(w)) + self.widgetList[w] = name + self.scales[w] = scale + self.readWidget(w) + + if type(w) in WidgetGroup.classes: + signal = WidgetGroup.classes[type(w)][0] + else: + signal = w.widgetGroupInterface()[0] + + if signal is not None: + if inspect.isfunction(signal) or inspect.ismethod(signal): + signal = signal(w) + signal.connect(self.mkChangeCallback(w)) + else: + self.uncachedWidgets[w] = None + + def findWidget(self, name): + for w in self.widgetList: + if self.widgetList[w] == name: + return w + return None + + def interface(self, obj): + t = type(obj) + if t in WidgetGroup.classes: + return WidgetGroup.classes[t] + else: + return obj.widgetGroupInterface() + + def checkForChildren(self, obj): + """Return true if we should automatically search the children of this object for more.""" + iface = self.interface(obj) + return (len(iface) > 3 and iface[3]) + + def autoAdd(self, obj): + ## Find all children of this object and add them if possible. + accepted = self.acceptsType(obj) + if accepted: + #print "%s auto add %s" % (self.objectName(), obj.objectName()) + self.addWidget(obj) + + if not accepted or self.checkForChildren(obj): + for c in obj.children(): + self.autoAdd(c) + + def acceptsType(self, obj): + for c in WidgetGroup.classes: + if isinstance(obj, c): + return True + if hasattr(obj, 'widgetGroupInterface'): + return True + return False + #return (type(obj) in WidgetGroup.classes) + + def setScale(self, widget, scale): + val = self.readWidget(widget) + self.scales[widget] = scale + self.setWidget(widget, val) + #print "scaling %f to %f" % (val, self.readWidget(widget)) + + + def mkChangeCallback(self, w): + return lambda *args: self.widgetChanged(w, *args) + + def widgetChanged(self, w, *args): + #print "widget changed" + n = self.widgetList[w] + v1 = self.cache[n] + v2 = self.readWidget(w) + if v1 != v2: + #print "widget", n, " = ", v2 + self.emit(QtCore.SIGNAL('changed'), self.widgetList[w], v2) + self.sigChanged.emit(self.widgetList[w], v2) + + def state(self): + for w in self.uncachedWidgets: + self.readWidget(w) + + #cc = self.cache.copy() + #if 'averageGroup' in cc: + #val = cc['averageGroup'] + #w = self.findWidget('averageGroup') + #self.readWidget(w) + #if val != self.cache['averageGroup']: + #print " AverageGroup did not match cached value!" + #else: + #print " AverageGroup OK" + return self.cache.copy() + + def setState(self, s): + #print "SET STATE", self, s + for w in self.widgetList: + n = self.widgetList[w] + #print " restore %s?" % n + if n not in s: + continue + #print " restore state", w, n, s[n] + self.setWidget(w, s[n]) + + def readWidget(self, w): + if type(w) in WidgetGroup.classes: + getFunc = WidgetGroup.classes[type(w)][1] + else: + getFunc = w.widgetGroupInterface()[1] + + if getFunc is None: + return None + + ## if the getter function provided in the interface is a bound method, + ## then just call the method directly. Otherwise, pass in the widget as the first arg + ## to the function. + if inspect.ismethod(getFunc) and getFunc.__self__ is not None: + val = getFunc() + else: + val = getFunc(w) + + if self.scales[w] is not None: + val /= self.scales[w] + #if isinstance(val, QtCore.QString): + #val = str(val) + n = self.widgetList[w] + self.cache[n] = val + return val + + def setWidget(self, w, v): + v1 = v + if self.scales[w] is not None: + v *= self.scales[w] + + if type(w) in WidgetGroup.classes: + setFunc = WidgetGroup.classes[type(w)][2] + else: + setFunc = w.widgetGroupInterface()[2] + + ## if the setter function provided in the interface is a bound method, + ## then just call the method directly. Otherwise, pass in the widget as the first arg + ## to the function. + if inspect.ismethod(setFunc) and setFunc.__self__ is not None: + setFunc(v) + else: + setFunc(w, v) + + #name = self.widgetList[w] + #if name in self.cache and (self.cache[name] != v1): + #print "%s: Cached value %s != set value %s" % (name, str(self.cache[name]), str(v1)) + + + \ No newline at end of file diff --git a/papi/pyqtgraph/__init__.py b/papi/pyqtgraph/__init__.py new file mode 100644 index 00000000..1c152d46 --- /dev/null +++ b/papi/pyqtgraph/__init__.py @@ -0,0 +1,438 @@ +# -*- coding: utf-8 -*- +""" +PyQtGraph - Scientific Graphics and GUI Library for Python +www.pyqtgraph.org +""" + +__version__ = '0.9.10' + +### import all the goodies and add some helper functions for easy CLI use + +## 'Qt' is a local module; it is intended mainly to cover up the differences +## between PyQt4 and PySide. +from .Qt import QtGui + +## not really safe--If we accidentally create another QApplication, the process hangs (and it is very difficult to trace the cause) +#if QtGui.QApplication.instance() is None: + #app = QtGui.QApplication([]) + +import numpy ## pyqtgraph requires numpy + ## (import here to avoid massive error dump later on if numpy is not available) + +import os, sys + +## check python version +## Allow anything >= 2.7 +if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] < 6): + raise Exception("Pyqtgraph requires Python version 2.6 or greater (this is %d.%d)" % (sys.version_info[0], sys.version_info[1])) + +## helpers for 2/3 compatibility +from . import python2_3 + +## install workarounds for numpy bugs +from . import numpy_fix + +## in general openGL is poorly supported with Qt+GraphicsView. +## we only enable it where the performance benefit is critical. +## Note this only applies to 2D graphics; 3D graphics always use OpenGL. +if 'linux' in sys.platform: ## linux has numerous bugs in opengl implementation + useOpenGL = False +elif 'darwin' in sys.platform: ## openGL can have a major impact on mac, but also has serious bugs + useOpenGL = False + if QtGui.QApplication.instance() is not None: + print('Warning: QApplication was created before pyqtgraph was imported; there may be problems (to avoid bugs, call QApplication.setGraphicsSystem("raster") before the QApplication is created).') + QtGui.QApplication.setGraphicsSystem('raster') ## work around a variety of bugs in the native graphics system +else: + useOpenGL = False ## on windows there's a more even performance / bugginess tradeoff. + +CONFIG_OPTIONS = { + 'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl. + 'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox + 'foreground': 'd', ## default foreground color for axes, labels, etc. + 'background': 'k', ## default background for GraphicsWidget + 'antialias': False, + 'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets + 'useWeave': False, ## Use weave to speed up some operations, if it is available + 'weaveDebug': False, ## Print full error message if weave compile fails + 'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide + 'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code) + 'crashWarning': False, # If True, print warnings about situations that may result in a crash +} + + +def setConfigOption(opt, value): + CONFIG_OPTIONS[opt] = value + +def setConfigOptions(**opts): + CONFIG_OPTIONS.update(opts) + +def getConfigOption(opt): + return CONFIG_OPTIONS[opt] + + +def systemInfo(): + print("sys.platform: %s" % sys.platform) + print("sys.version: %s" % sys.version) + from .Qt import VERSION_INFO + print("qt bindings: %s" % VERSION_INFO) + + global __version__ + rev = None + if __version__ is None: ## this code was probably checked out from bzr; look up the last-revision file + lastRevFile = os.path.join(os.path.dirname(__file__), '..', '.bzr', 'branch', 'last-revision') + if os.path.exists(lastRevFile): + rev = open(lastRevFile, 'r').read().strip() + + print("pyqtgraph: %s; %s" % (__version__, rev)) + print("config:") + import pprint + pprint.pprint(CONFIG_OPTIONS) + +## Rename orphaned .pyc files. This is *probably* safe :) +## We only do this if __version__ is None, indicating the code was probably pulled +## from the repository. +def renamePyc(startDir): + ### Used to rename orphaned .pyc files + ### When a python file changes its location in the repository, usually the .pyc file + ### is left behind, possibly causing mysterious and difficult to track bugs. + + ### Note that this is no longer necessary for python 3.2; from PEP 3147: + ### "If the py source file is missing, the pyc file inside __pycache__ will be ignored. + ### This eliminates the problem of accidental stale pyc file imports." + + printed = False + startDir = os.path.abspath(startDir) + for path, dirs, files in os.walk(startDir): + if '__pycache__' in path: + continue + for f in files: + fileName = os.path.join(path, f) + base, ext = os.path.splitext(fileName) + py = base + ".py" + if ext == '.pyc' and not os.path.isfile(py): + if not printed: + print("NOTE: Renaming orphaned .pyc files:") + printed = True + n = 1 + while True: + name2 = fileName + ".renamed%d" % n + if not os.path.exists(name2): + break + n += 1 + print(" " + fileName + " ==>") + print(" " + name2) + os.rename(fileName, name2) + +path = os.path.split(__file__)[0] +if __version__ is None and not hasattr(sys, 'frozen') and sys.version_info[0] == 2: ## If we are frozen, there's a good chance we don't have the original .py files anymore. + renamePyc(path) + + +## Import almost everything to make it available from a single namespace +## don't import the more complex systems--canvas, parametertree, flowchart, dockarea +## these must be imported separately. +#from . import frozenSupport +#def importModules(path, globals, locals, excludes=()): + #"""Import all modules residing within *path*, return a dict of name: module pairs. + + #Note that *path* MUST be relative to the module doing the import. + #""" + #d = os.path.join(os.path.split(globals['__file__'])[0], path) + #files = set() + #for f in frozenSupport.listdir(d): + #if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']: + #files.add(f) + #elif f[-3:] == '.py' and f != '__init__.py': + #files.add(f[:-3]) + #elif f[-4:] == '.pyc' and f != '__init__.pyc': + #files.add(f[:-4]) + + #mods = {} + #path = path.replace(os.sep, '.') + #for modName in files: + #if modName in excludes: + #continue + #try: + #if len(path) > 0: + #modName = path + '.' + modName + #print( "from .%s import * " % modName) + #mod = __import__(modName, globals, locals, ['*'], 1) + #mods[modName] = mod + #except: + #import traceback + #traceback.print_stack() + #sys.excepthook(*sys.exc_info()) + #print("[Error importing module: %s]" % modName) + + #return mods + +#def importAll(path, globals, locals, excludes=()): + #"""Given a list of modules, import all names from each module into the global namespace.""" + #mods = importModules(path, globals, locals, excludes) + #for mod in mods.values(): + #if hasattr(mod, '__all__'): + #names = mod.__all__ + #else: + #names = [n for n in dir(mod) if n[0] != '_'] + #for k in names: + #if hasattr(mod, k): + #globals[k] = getattr(mod, k) + +# Dynamic imports are disabled. This causes too many problems. +#importAll('graphicsItems', globals(), locals()) +#importAll('widgets', globals(), locals(), + #excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView']) + +from .graphicsItems.VTickGroup import * +from .graphicsItems.GraphicsWidget import * +from .graphicsItems.ScaleBar import * +from .graphicsItems.PlotDataItem import * +from .graphicsItems.GraphItem import * +from .graphicsItems.TextItem import * +from .graphicsItems.GraphicsLayout import * +from .graphicsItems.UIGraphicsItem import * +from .graphicsItems.GraphicsObject import * +from .graphicsItems.PlotItem import * +from .graphicsItems.ROI import * +from .graphicsItems.InfiniteLine import * +from .graphicsItems.HistogramLUTItem import * +from .graphicsItems.GridItem import * +from .graphicsItems.GradientLegend import * +from .graphicsItems.GraphicsItem import * +from .graphicsItems.BarGraphItem import * +from .graphicsItems.ViewBox import * +from .graphicsItems.ArrowItem import * +from .graphicsItems.ImageItem import * +from .graphicsItems.AxisItem import * +from .graphicsItems.LabelItem import * +from .graphicsItems.CurvePoint import * +from .graphicsItems.GraphicsWidgetAnchor import * +from .graphicsItems.PlotCurveItem import * +from .graphicsItems.ButtonItem import * +from .graphicsItems.GradientEditorItem import * +from .graphicsItems.MultiPlotItem import * +from .graphicsItems.ErrorBarItem import * +from .graphicsItems.IsocurveItem import * +from .graphicsItems.LinearRegionItem import * +from .graphicsItems.FillBetweenItem import * +from .graphicsItems.LegendItem import * +from .graphicsItems.ScatterPlotItem import * +from .graphicsItems.ItemGroup import * + +from .widgets.MultiPlotWidget import * +from .widgets.ScatterPlotWidget import * +from .widgets.ColorMapWidget import * +from .widgets.FileDialog import * +from .widgets.ValueLabel import * +from .widgets.HistogramLUTWidget import * +from .widgets.CheckTable import * +from .widgets.BusyCursor import * +from .widgets.PlotWidget import * +from .widgets.ComboBox import * +from .widgets.GradientWidget import * +from .widgets.DataFilterWidget import * +from .widgets.SpinBox import * +from .widgets.JoystickButton import * +from .widgets.GraphicsLayoutWidget import * +from .widgets.TreeWidget import * +from .widgets.PathButton import * +from .widgets.VerticalLabel import * +from .widgets.FeedbackButton import * +from .widgets.ColorButton import * +from .widgets.DataTreeWidget import * +from .widgets.GraphicsView import * +from .widgets.LayoutWidget import * +from .widgets.TableWidget import * +from .widgets.ProgressDialog import * + +from .imageview import * +from .WidgetGroup import * +from .Point import Point +from .Vector import Vector +from .SRTTransform import SRTTransform +from .Transform3D import Transform3D +from .SRTTransform3D import SRTTransform3D +from .functions import * +from .graphicsWindows import * +from .SignalProxy import * +from .colormap import * +from .ptime import time +from .Qt import isQObjectAlive + + +############################################################## +## PyQt and PySide both are prone to crashing on exit. +## There are two general approaches to dealing with this: +## 1. Install atexit handlers that assist in tearing down to avoid crashes. +## This helps, but is never perfect. +## 2. Terminate the process before python starts tearing down +## This is potentially dangerous + +## Attempts to work around exit crashes: +import atexit +_cleanupCalled = False +def cleanup(): + global _cleanupCalled + if _cleanupCalled: + return + + if not getConfigOption('exitCleanup'): + return + + ViewBox.quit() ## tell ViewBox that it doesn't need to deregister views anymore. + + ## Workaround for Qt exit crash: + ## ALL QGraphicsItems must have a scene before they are deleted. + ## This is potentially very expensive, but preferred over crashing. + ## Note: this appears to be fixed in PySide as of 2012.12, but it should be left in for a while longer.. + if QtGui.QApplication.instance() is None: + return + import gc + s = QtGui.QGraphicsScene() + for o in gc.get_objects(): + try: + if isinstance(o, QtGui.QGraphicsItem) and isQObjectAlive(o) and o.scene() is None: + if getConfigOption('crashWarning'): + sys.stderr.write('Error: graphics item without scene. ' + 'Make sure ViewBox.close() and GraphicsView.close() ' + 'are properly called before app shutdown (%s)\n' % (o,)) + + s.addItem(o) + except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object + continue + _cleanupCalled = True + +atexit.register(cleanup) + +# Call cleanup when QApplication quits. This is necessary because sometimes +# the QApplication will quit before the atexit callbacks are invoked. +# Note: cannot connect this function until QApplication has been created, so +# instead we have GraphicsView.__init__ call this for us. +_cleanupConnected = False +def _connectCleanup(): + global _cleanupConnected + if _cleanupConnected: + return + QtGui.QApplication.instance().aboutToQuit.connect(cleanup) + _cleanupConnected = True + + +## Optional function for exiting immediately (with some manual teardown) +def exit(): + """ + Causes python to exit without garbage-collecting any objects, and thus avoids + calling object destructor methods. This is a sledgehammer workaround for + a variety of bugs in PyQt and Pyside that cause crashes on exit. + + This function does the following in an attempt to 'safely' terminate + the process: + + * Invoke atexit callbacks + * Close all open file handles + * os._exit() + + Note: there is some potential for causing damage with this function if you + are using objects that _require_ their destructors to be called (for example, + to properly terminate log files, disconnect from devices, etc). Situations + like this are probably quite rare, but use at your own risk. + """ + + ## first disable our own cleanup function; won't be needing it. + setConfigOptions(exitCleanup=False) + + ## invoke atexit callbacks + atexit._run_exitfuncs() + + ## close file handles + if sys.platform == 'darwin': + for fd in xrange(3, 4096): + if fd not in [7]: # trying to close 7 produces an illegal instruction on the Mac. + os.close(fd) + else: + os.closerange(3, 4096) ## just guessing on the maximum descriptor count.. + + os._exit(0) + + + +## Convenience functions for command-line use + +plots = [] +images = [] +QAPP = None + +def plot(*args, **kargs): + """ + Create and return a :class:`PlotWindow ` + (this is just a window with :class:`PlotWidget ` inside), plot data in it. + Accepts a *title* argument to set the title of the window. + All other arguments are used to plot data. (see :func:`PlotItem.plot() `) + """ + mkQApp() + #if 'title' in kargs: + #w = PlotWindow(title=kargs['title']) + #del kargs['title'] + #else: + #w = PlotWindow() + #if len(args)+len(kargs) > 0: + #w.plot(*args, **kargs) + + pwArgList = ['title', 'labels', 'name', 'left', 'right', 'top', 'bottom', 'background'] + pwArgs = {} + dataArgs = {} + for k in kargs: + if k in pwArgList: + pwArgs[k] = kargs[k] + else: + dataArgs[k] = kargs[k] + + w = PlotWindow(**pwArgs) + if len(args) > 0 or len(dataArgs) > 0: + w.plot(*args, **dataArgs) + plots.append(w) + w.show() + return w + +def image(*args, **kargs): + """ + Create and return an :class:`ImageWindow ` + (this is just a window with :class:`ImageView ` widget inside), show image data inside. + Will show 2D or 3D image data. + Accepts a *title* argument to set the title of the window. + All other arguments are used to show data. (see :func:`ImageView.setImage() `) + """ + mkQApp() + w = ImageWindow(*args, **kargs) + images.append(w) + w.show() + return w +show = image ## for backward compatibility + +def dbg(*args, **kwds): + """ + Create a console window and begin watching for exceptions. + + All arguments are passed to :func:`ConsoleWidget.__init__() `. + """ + mkQApp() + from . import console + c = console.ConsoleWidget(*args, **kwds) + c.catchAllExceptions() + c.show() + global consoles + try: + consoles.append(c) + except NameError: + consoles = [c] + return c + + +def mkQApp(): + global QAPP + inst = QtGui.QApplication.instance() + if inst is None: + QAPP = QtGui.QApplication([]) + else: + QAPP = inst + return QAPP + diff --git a/papi/pyqtgraph/canvas/Canvas.py b/papi/pyqtgraph/canvas/Canvas.py new file mode 100644 index 00000000..4de891f7 --- /dev/null +++ b/papi/pyqtgraph/canvas/Canvas.py @@ -0,0 +1,604 @@ +# -*- coding: utf-8 -*- +if __name__ == '__main__': + import sys, os + md = os.path.dirname(os.path.abspath(__file__)) + sys.path = [os.path.dirname(md), os.path.join(md, '..', '..', '..')] + sys.path + +from ..Qt import QtGui, QtCore, USE_PYSIDE +from ..graphicsItems.ROI import ROI +from ..graphicsItems.ViewBox import ViewBox +from ..graphicsItems.GridItem import GridItem + +if USE_PYSIDE: + from .CanvasTemplate_pyside import * +else: + from .CanvasTemplate_pyqt import * + +import numpy as np +from .. import debug +import weakref +from .CanvasManager import CanvasManager +from .CanvasItem import CanvasItem, GroupCanvasItem + +class Canvas(QtGui.QWidget): + + sigSelectionChanged = QtCore.Signal(object, object) + sigItemTransformChanged = QtCore.Signal(object, object) + sigItemTransformChangeFinished = QtCore.Signal(object, object) + + def __init__(self, parent=None, allowTransforms=True, hideCtrl=False, name=None): + QtGui.QWidget.__init__(self, parent) + self.ui = Ui_Form() + self.ui.setupUi(self) + #self.view = self.ui.view + self.view = ViewBox() + self.ui.view.setCentralItem(self.view) + self.itemList = self.ui.itemList + self.itemList.setSelectionMode(self.itemList.ExtendedSelection) + self.allowTransforms = allowTransforms + self.multiSelectBox = SelectBox() + self.view.addItem(self.multiSelectBox) + self.multiSelectBox.hide() + self.multiSelectBox.setZValue(1e6) + self.ui.mirrorSelectionBtn.hide() + self.ui.reflectSelectionBtn.hide() + self.ui.resetTransformsBtn.hide() + + self.redirect = None ## which canvas to redirect items to + self.items = [] + + #self.view.enableMouse() + self.view.setAspectLocked(True) + #self.view.invertY() + + grid = GridItem() + self.grid = CanvasItem(grid, name='Grid', movable=False) + self.addItem(self.grid) + + self.hideBtn = QtGui.QPushButton('>', self) + self.hideBtn.setFixedWidth(20) + self.hideBtn.setFixedHeight(20) + self.ctrlSize = 200 + self.sizeApplied = False + self.hideBtn.clicked.connect(self.hideBtnClicked) + self.ui.splitter.splitterMoved.connect(self.splitterMoved) + + self.ui.itemList.itemChanged.connect(self.treeItemChanged) + self.ui.itemList.sigItemMoved.connect(self.treeItemMoved) + self.ui.itemList.itemSelectionChanged.connect(self.treeItemSelected) + self.ui.autoRangeBtn.clicked.connect(self.autoRange) + #self.ui.storeSvgBtn.clicked.connect(self.storeSvg) + #self.ui.storePngBtn.clicked.connect(self.storePng) + self.ui.redirectCheck.toggled.connect(self.updateRedirect) + self.ui.redirectCombo.currentIndexChanged.connect(self.updateRedirect) + self.multiSelectBox.sigRegionChanged.connect(self.multiSelectBoxChanged) + self.multiSelectBox.sigRegionChangeFinished.connect(self.multiSelectBoxChangeFinished) + self.ui.mirrorSelectionBtn.clicked.connect(self.mirrorSelectionClicked) + self.ui.reflectSelectionBtn.clicked.connect(self.reflectSelectionClicked) + self.ui.resetTransformsBtn.clicked.connect(self.resetTransformsClicked) + + self.resizeEvent() + if hideCtrl: + self.hideBtnClicked() + + if name is not None: + self.registeredName = CanvasManager.instance().registerCanvas(self, name) + self.ui.redirectCombo.setHostName(self.registeredName) + + self.menu = QtGui.QMenu() + #self.menu.setTitle("Image") + remAct = QtGui.QAction("Remove item", self.menu) + remAct.triggered.connect(self.removeClicked) + self.menu.addAction(remAct) + self.menu.remAct = remAct + self.ui.itemList.contextMenuEvent = self.itemListContextMenuEvent + + + #def storeSvg(self): + #from pyqtgraph.GraphicsScene.exportDialog import ExportDialog + #ex = ExportDialog(self.ui.view) + #ex.show() + + #def storePng(self): + #self.ui.view.writeImage() + + def splitterMoved(self): + self.resizeEvent() + + def hideBtnClicked(self): + ctrlSize = self.ui.splitter.sizes()[1] + if ctrlSize == 0: + cs = self.ctrlSize + w = self.ui.splitter.size().width() + if cs > w: + cs = w - 20 + self.ui.splitter.setSizes([w-cs, cs]) + self.hideBtn.setText('>') + else: + self.ctrlSize = ctrlSize + self.ui.splitter.setSizes([100, 0]) + self.hideBtn.setText('<') + self.resizeEvent() + + def autoRange(self): + self.view.autoRange() + + def resizeEvent(self, ev=None): + if ev is not None: + QtGui.QWidget.resizeEvent(self, ev) + self.hideBtn.move(self.ui.view.size().width() - self.hideBtn.width(), 0) + + if not self.sizeApplied: + self.sizeApplied = True + s = min(self.width(), max(100, min(200, self.width()*0.25))) + s2 = self.width()-s + self.ui.splitter.setSizes([s2, s]) + + + def updateRedirect(self, *args): + ### Decide whether/where to redirect items and make it so + cname = str(self.ui.redirectCombo.currentText()) + man = CanvasManager.instance() + if self.ui.redirectCheck.isChecked() and cname != '': + redirect = man.getCanvas(cname) + else: + redirect = None + + if self.redirect is redirect: + return + + self.redirect = redirect + if redirect is None: + self.reclaimItems() + else: + self.redirectItems(redirect) + + + def redirectItems(self, canvas): + for i in self.items: + if i is self.grid: + continue + li = i.listItem + parent = li.parent() + if parent is None: + tree = li.treeWidget() + if tree is None: + print("Skipping item", i, i.name) + continue + tree.removeTopLevelItem(li) + else: + parent.removeChild(li) + canvas.addItem(i) + + + def reclaimItems(self): + items = self.items + #self.items = {'Grid': items['Grid']} + #del items['Grid'] + self.items = [self.grid] + items.remove(self.grid) + + for i in items: + i.canvas.removeItem(i) + self.addItem(i) + + def treeItemChanged(self, item, col): + #gi = self.items.get(item.name, None) + #if gi is None: + #return + try: + citem = item.canvasItem() + except AttributeError: + return + if item.checkState(0) == QtCore.Qt.Checked: + for i in range(item.childCount()): + item.child(i).setCheckState(0, QtCore.Qt.Checked) + citem.show() + else: + for i in range(item.childCount()): + item.child(i).setCheckState(0, QtCore.Qt.Unchecked) + citem.hide() + + def treeItemSelected(self): + sel = self.selectedItems() + #sel = [] + #for listItem in self.itemList.selectedItems(): + #if hasattr(listItem, 'canvasItem') and listItem.canvasItem is not None: + #sel.append(listItem.canvasItem) + #sel = [self.items[item.name] for item in sel] + + if len(sel) == 0: + #self.selectWidget.hide() + return + + multi = len(sel) > 1 + for i in self.items: + #i.ctrlWidget().hide() + ## updated the selected state of every item + i.selectionChanged(i in sel, multi) + + if len(sel)==1: + #item = sel[0] + #item.ctrlWidget().show() + self.multiSelectBox.hide() + self.ui.mirrorSelectionBtn.hide() + self.ui.reflectSelectionBtn.hide() + self.ui.resetTransformsBtn.hide() + elif len(sel) > 1: + self.showMultiSelectBox() + + #if item.isMovable(): + #self.selectBox.setPos(item.item.pos()) + #self.selectBox.setSize(item.item.sceneBoundingRect().size()) + #self.selectBox.show() + #else: + #self.selectBox.hide() + + #self.emit(QtCore.SIGNAL('itemSelected'), self, item) + self.sigSelectionChanged.emit(self, sel) + + def selectedItems(self): + """ + Return list of all selected canvasItems + """ + return [item.canvasItem() for item in self.itemList.selectedItems() if item.canvasItem() is not None] + + #def selectedItem(self): + #sel = self.itemList.selectedItems() + #if sel is None or len(sel) < 1: + #return + #return self.items.get(sel[0].name, None) + + def selectItem(self, item): + li = item.listItem + #li = self.getListItem(item.name()) + #print "select", li + self.itemList.setCurrentItem(li) + + + + def showMultiSelectBox(self): + ## Get list of selected canvas items + items = self.selectedItems() + + rect = self.view.itemBoundingRect(items[0].graphicsItem()) + for i in items: + if not i.isMovable(): ## all items in selection must be movable + return + br = self.view.itemBoundingRect(i.graphicsItem()) + rect = rect|br + + self.multiSelectBox.blockSignals(True) + self.multiSelectBox.setPos([rect.x(), rect.y()]) + self.multiSelectBox.setSize(rect.size()) + self.multiSelectBox.setAngle(0) + self.multiSelectBox.blockSignals(False) + + self.multiSelectBox.show() + + self.ui.mirrorSelectionBtn.show() + self.ui.reflectSelectionBtn.show() + self.ui.resetTransformsBtn.show() + #self.multiSelectBoxBase = self.multiSelectBox.getState().copy() + + def mirrorSelectionClicked(self): + for ci in self.selectedItems(): + ci.mirrorY() + self.showMultiSelectBox() + + def reflectSelectionClicked(self): + for ci in self.selectedItems(): + ci.mirrorXY() + self.showMultiSelectBox() + + def resetTransformsClicked(self): + for i in self.selectedItems(): + i.resetTransformClicked() + self.showMultiSelectBox() + + def multiSelectBoxChanged(self): + self.multiSelectBoxMoved() + + def multiSelectBoxChangeFinished(self): + for ci in self.selectedItems(): + ci.applyTemporaryTransform() + ci.sigTransformChangeFinished.emit(ci) + + def multiSelectBoxMoved(self): + transform = self.multiSelectBox.getGlobalTransform() + for ci in self.selectedItems(): + ci.setTemporaryTransform(transform) + ci.sigTransformChanged.emit(ci) + + + def addGraphicsItem(self, item, **opts): + """Add a new GraphicsItem to the scene at pos. + Common options are name, pos, scale, and z + """ + citem = CanvasItem(item, **opts) + item._canvasItem = citem + self.addItem(citem) + return citem + + + def addGroup(self, name, **kargs): + group = GroupCanvasItem(name=name) + self.addItem(group, **kargs) + return group + + + def addItem(self, citem): + """ + Add an item to the canvas. + """ + + ## Check for redirections + if self.redirect is not None: + name = self.redirect.addItem(citem) + self.items.append(citem) + return name + + if not self.allowTransforms: + citem.setMovable(False) + + citem.sigTransformChanged.connect(self.itemTransformChanged) + citem.sigTransformChangeFinished.connect(self.itemTransformChangeFinished) + citem.sigVisibilityChanged.connect(self.itemVisibilityChanged) + + + ## Determine name to use in the item list + name = citem.opts['name'] + if name is None: + name = 'item' + newname = name + + ## If name already exists, append a number to the end + ## NAH. Let items have the same name if they really want. + #c=0 + #while newname in self.items: + #c += 1 + #newname = name + '_%03d' %c + #name = newname + + ## find parent and add item to tree + #currentNode = self.itemList.invisibleRootItem() + insertLocation = 0 + #print "Inserting node:", name + + + ## determine parent list item where this item should be inserted + parent = citem.parentItem() + if parent in (None, self.view.childGroup): + parent = self.itemList.invisibleRootItem() + else: + parent = parent.listItem + + ## set Z value above all other siblings if none was specified + siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())] + z = citem.zValue() + if z is None: + zvals = [i.zValue() for i in siblings] + if parent == self.itemList.invisibleRootItem(): + if len(zvals) == 0: + z = 0 + else: + z = max(zvals)+10 + else: + if len(zvals) == 0: + z = parent.canvasItem().zValue() + else: + z = max(zvals)+1 + citem.setZValue(z) + + ## determine location to insert item relative to its siblings + for i in range(parent.childCount()): + ch = parent.child(i) + zval = ch.canvasItem().graphicsItem().zValue() ## should we use CanvasItem.zValue here? + if zval < z: + insertLocation = i + break + else: + insertLocation = i+1 + + node = QtGui.QTreeWidgetItem([name]) + flags = node.flags() | QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsDragEnabled + if not isinstance(citem, GroupCanvasItem): + flags = flags & ~QtCore.Qt.ItemIsDropEnabled + node.setFlags(flags) + if citem.opts['visible']: + node.setCheckState(0, QtCore.Qt.Checked) + else: + node.setCheckState(0, QtCore.Qt.Unchecked) + + node.name = name + #if citem.opts['parent'] != None: + ## insertLocation is incorrect in this case + parent.insertChild(insertLocation, node) + #else: + #root.insertChild(insertLocation, node) + + citem.name = name + citem.listItem = node + node.canvasItem = weakref.ref(citem) + self.items.append(citem) + + ctrl = citem.ctrlWidget() + ctrl.hide() + self.ui.ctrlLayout.addWidget(ctrl) + + ## inform the canvasItem that its parent canvas has changed + citem.setCanvas(self) + + ## Autoscale to fit the first item added (not including the grid). + if len(self.items) == 2: + self.autoRange() + + + #for n in name: + #nextnode = None + #for x in range(currentNode.childCount()): + #ch = currentNode.child(x) + #if hasattr(ch, 'name'): ## check Z-value of current item to determine insert location + #zval = ch.canvasItem.zValue() + #if zval > z: + ###print " ->", x + #insertLocation = x+1 + #if n == ch.text(0): + #nextnode = ch + #break + #if nextnode is None: ## If name doesn't exist, create it + #nextnode = QtGui.QTreeWidgetItem([n]) + #nextnode.setFlags((nextnode.flags() | QtCore.Qt.ItemIsUserCheckable) & ~QtCore.Qt.ItemIsDropEnabled) + #nextnode.setCheckState(0, QtCore.Qt.Checked) + ### Add node to correct position in list by Z-value + ###print " ==>", insertLocation + #currentNode.insertChild(insertLocation, nextnode) + + #if n == name[-1]: ## This is the leaf; add some extra properties. + #nextnode.name = name + + #if n == name[0]: ## This is the root; make the item movable + #nextnode.setFlags(nextnode.flags() | QtCore.Qt.ItemIsDragEnabled) + #else: + #nextnode.setFlags(nextnode.flags() & ~QtCore.Qt.ItemIsDragEnabled) + + #currentNode = nextnode + return citem + + def treeItemMoved(self, item, parent, index): + ##Item moved in tree; update Z values + if parent is self.itemList.invisibleRootItem(): + item.canvasItem().setParentItem(self.view.childGroup) + else: + item.canvasItem().setParentItem(parent.canvasItem()) + siblings = [parent.child(i).canvasItem() for i in range(parent.childCount())] + + zvals = [i.zValue() for i in siblings] + zvals.sort(reverse=True) + + for i in range(len(siblings)): + item = siblings[i] + item.setZValue(zvals[i]) + #item = self.itemList.topLevelItem(i) + + ##ci = self.items[item.name] + #ci = item.canvasItem + #if ci is None: + #continue + #if ci.zValue() != zvals[i]: + #ci.setZValue(zvals[i]) + + #if self.itemList.topLevelItemCount() < 2: + #return + #name = item.name + #gi = self.items[name] + #if index == 0: + #next = self.itemList.topLevelItem(1) + #z = self.items[next.name].zValue()+1 + #else: + #prev = self.itemList.topLevelItem(index-1) + #z = self.items[prev.name].zValue()-1 + #gi.setZValue(z) + + + + + + + def itemVisibilityChanged(self, item): + listItem = item.listItem + checked = listItem.checkState(0) == QtCore.Qt.Checked + vis = item.isVisible() + if vis != checked: + if vis: + listItem.setCheckState(0, QtCore.Qt.Checked) + else: + listItem.setCheckState(0, QtCore.Qt.Unchecked) + + def removeItem(self, item): + if isinstance(item, QtGui.QTreeWidgetItem): + item = item.canvasItem() + + + if isinstance(item, CanvasItem): + item.setCanvas(None) + listItem = item.listItem + listItem.canvasItem = None + item.listItem = None + self.itemList.removeTopLevelItem(listItem) + self.items.remove(item) + ctrl = item.ctrlWidget() + ctrl.hide() + self.ui.ctrlLayout.removeWidget(ctrl) + else: + if hasattr(item, '_canvasItem'): + self.removeItem(item._canvasItem) + else: + self.view.removeItem(item) + + ## disconnect signals, remove from list, etc.. + + def clear(self): + while len(self.items) > 0: + self.removeItem(self.items[0]) + + + def addToScene(self, item): + self.view.addItem(item) + + def removeFromScene(self, item): + self.view.removeItem(item) + + + def listItems(self): + """Return a dictionary of name:item pairs""" + return self.items + + def getListItem(self, name): + return self.items[name] + + #def scene(self): + #return self.view.scene() + + def itemTransformChanged(self, item): + #self.emit(QtCore.SIGNAL('itemTransformChanged'), self, item) + self.sigItemTransformChanged.emit(self, item) + + def itemTransformChangeFinished(self, item): + #self.emit(QtCore.SIGNAL('itemTransformChangeFinished'), self, item) + self.sigItemTransformChangeFinished.emit(self, item) + + def itemListContextMenuEvent(self, ev): + self.menuItem = self.itemList.itemAt(ev.pos()) + self.menu.popup(ev.globalPos()) + + def removeClicked(self): + #self.removeItem(self.menuItem) + for item in self.selectedItems(): + self.removeItem(item) + self.menuItem = None + import gc + gc.collect() + +class SelectBox(ROI): + def __init__(self, scalable=False): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, [0,0], [1,1]) + center = [0.5, 0.5] + + if scalable: + self.addScaleHandle([1, 1], center, lockAspect=True) + self.addScaleHandle([0, 0], center, lockAspect=True) + self.addRotateHandle([0, 1], center) + self.addRotateHandle([1, 0], center) + + + + + + + + + + + diff --git a/papi/pyqtgraph/canvas/CanvasItem.py b/papi/pyqtgraph/canvas/CanvasItem.py new file mode 100644 index 00000000..b6ecbb39 --- /dev/null +++ b/papi/pyqtgraph/canvas/CanvasItem.py @@ -0,0 +1,512 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore, QtSvg, USE_PYSIDE +from ..graphicsItems.ROI import ROI +from .. import SRTTransform, ItemGroup +if USE_PYSIDE: + from . import TransformGuiTemplate_pyside as TransformGuiTemplate +else: + from . import TransformGuiTemplate_pyqt as TransformGuiTemplate + +from .. import debug + +class SelectBox(ROI): + def __init__(self, scalable=False, rotatable=True): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, [0,0], [1,1], invertible=True) + center = [0.5, 0.5] + + if scalable: + self.addScaleHandle([1, 1], center, lockAspect=True) + self.addScaleHandle([0, 0], center, lockAspect=True) + if rotatable: + self.addRotateHandle([0, 1], center) + self.addRotateHandle([1, 0], center) + +class CanvasItem(QtCore.QObject): + + sigResetUserTransform = QtCore.Signal(object) + sigTransformChangeFinished = QtCore.Signal(object) + sigTransformChanged = QtCore.Signal(object) + + """CanvasItem takes care of managing an item's state--alpha, visibility, z-value, transformations, etc. and + provides a control widget""" + + sigVisibilityChanged = QtCore.Signal(object) + transformCopyBuffer = None + + def __init__(self, item, **opts): + defOpts = {'name': None, 'z': None, 'movable': True, 'scalable': False, 'rotatable': True, 'visible': True, 'parent':None} #'pos': [0,0], 'scale': [1,1], 'angle':0, + defOpts.update(opts) + self.opts = defOpts + self.selectedAlone = False ## whether this item is the only one selected + + QtCore.QObject.__init__(self) + self.canvas = None + self._graphicsItem = item + + parent = self.opts['parent'] + if parent is not None: + self._graphicsItem.setParentItem(parent.graphicsItem()) + self._parentItem = parent + else: + self._parentItem = None + + z = self.opts['z'] + if z is not None: + item.setZValue(z) + + self.ctrl = QtGui.QWidget() + self.layout = QtGui.QGridLayout() + self.layout.setSpacing(0) + self.layout.setContentsMargins(0,0,0,0) + self.ctrl.setLayout(self.layout) + + self.alphaLabel = QtGui.QLabel("Alpha") + self.alphaSlider = QtGui.QSlider() + self.alphaSlider.setMaximum(1023) + self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.alphaSlider.setValue(1023) + self.layout.addWidget(self.alphaLabel, 0, 0) + self.layout.addWidget(self.alphaSlider, 0, 1) + self.resetTransformBtn = QtGui.QPushButton('Reset Transform') + self.copyBtn = QtGui.QPushButton('Copy') + self.pasteBtn = QtGui.QPushButton('Paste') + + self.transformWidget = QtGui.QWidget() + self.transformGui = TransformGuiTemplate.Ui_Form() + self.transformGui.setupUi(self.transformWidget) + self.layout.addWidget(self.transformWidget, 3, 0, 1, 2) + self.transformGui.mirrorImageBtn.clicked.connect(self.mirrorY) + self.transformGui.reflectImageBtn.clicked.connect(self.mirrorXY) + + self.layout.addWidget(self.resetTransformBtn, 1, 0, 1, 2) + self.layout.addWidget(self.copyBtn, 2, 0, 1, 1) + self.layout.addWidget(self.pasteBtn, 2, 1, 1, 1) + self.alphaSlider.valueChanged.connect(self.alphaChanged) + self.alphaSlider.sliderPressed.connect(self.alphaPressed) + self.alphaSlider.sliderReleased.connect(self.alphaReleased) + #self.canvas.sigSelectionChanged.connect(self.selectionChanged) + self.resetTransformBtn.clicked.connect(self.resetTransformClicked) + self.copyBtn.clicked.connect(self.copyClicked) + self.pasteBtn.clicked.connect(self.pasteClicked) + + self.setMovable(self.opts['movable']) ## update gui to reflect this option + + + if 'transform' in self.opts: + self.baseTransform = self.opts['transform'] + else: + self.baseTransform = SRTTransform() + if 'pos' in self.opts and self.opts['pos'] is not None: + self.baseTransform.translate(self.opts['pos']) + if 'angle' in self.opts and self.opts['angle'] is not None: + self.baseTransform.rotate(self.opts['angle']) + if 'scale' in self.opts and self.opts['scale'] is not None: + self.baseTransform.scale(self.opts['scale']) + + ## create selection box (only visible when selected) + tr = self.baseTransform.saveState() + if 'scalable' not in opts and tr['scale'] == (1,1): + self.opts['scalable'] = True + + ## every CanvasItem implements its own individual selection box + ## so that subclasses are free to make their own. + self.selectBox = SelectBox(scalable=self.opts['scalable'], rotatable=self.opts['rotatable']) + #self.canvas.scene().addItem(self.selectBox) + self.selectBox.hide() + self.selectBox.setZValue(1e6) + self.selectBox.sigRegionChanged.connect(self.selectBoxChanged) ## calls selectBoxMoved + self.selectBox.sigRegionChangeFinished.connect(self.selectBoxChangeFinished) + + ## set up the transformations that will be applied to the item + ## (It is not safe to use item.setTransform, since the item might count on that not changing) + self.itemRotation = QtGui.QGraphicsRotation() + self.itemScale = QtGui.QGraphicsScale() + self._graphicsItem.setTransformations([self.itemRotation, self.itemScale]) + + self.tempTransform = SRTTransform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done. + self.userTransform = SRTTransform() ## stores the total transform of the object + self.resetUserTransform() + + ## now happens inside resetUserTransform -> selectBoxToItem + # self.selectBoxBase = self.selectBox.getState().copy() + + + #print "Created canvas item", self + #print " base:", self.baseTransform + #print " user:", self.userTransform + #print " temp:", self.tempTransform + #print " bounds:", self.item.sceneBoundingRect() + + def setMovable(self, m): + self.opts['movable'] = m + + if m: + self.resetTransformBtn.show() + self.copyBtn.show() + self.pasteBtn.show() + else: + self.resetTransformBtn.hide() + self.copyBtn.hide() + self.pasteBtn.hide() + + def setCanvas(self, canvas): + ## Called by canvas whenever the item is added. + ## It is our responsibility to add all graphicsItems to the canvas's scene + ## The canvas will automatically add our graphicsitem, + ## so we just need to take care of the selectbox. + if canvas is self.canvas: + return + + if canvas is None: + self.canvas.removeFromScene(self._graphicsItem) + self.canvas.removeFromScene(self.selectBox) + else: + canvas.addToScene(self._graphicsItem) + canvas.addToScene(self.selectBox) + self.canvas = canvas + + def graphicsItem(self): + """Return the graphicsItem for this canvasItem.""" + return self._graphicsItem + + def parentItem(self): + return self._parentItem + + def setParentItem(self, parent): + self._parentItem = parent + if parent is not None: + if isinstance(parent, CanvasItem): + parent = parent.graphicsItem() + self.graphicsItem().setParentItem(parent) + + #def name(self): + #return self.opts['name'] + + def copyClicked(self): + CanvasItem.transformCopyBuffer = self.saveTransform() + + def pasteClicked(self): + t = CanvasItem.transformCopyBuffer + if t is None: + return + else: + self.restoreTransform(t) + + def mirrorY(self): + if not self.isMovable(): + return + + #flip = self.transformGui.mirrorImageCheck.isChecked() + #tr = self.userTransform.saveState() + + inv = SRTTransform() + inv.scale(-1, 1) + self.userTransform = self.userTransform * inv + self.updateTransform() + self.selectBoxFromUser() + self.sigTransformChangeFinished.emit(self) + #if flip: + #if tr['scale'][0] < 0 xor tr['scale'][1] < 0: + #return + #else: + #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) + #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) + #self.userTransform.setRotate(-tr['angle']) + #self.updateTransform() + #self.selectBoxFromUser() + #return + #elif not flip: + #if tr['scale'][0] > 0 and tr['scale'][1] > 0: + #return + #else: + #self.userTransform.setScale([-tr['scale'][0], tr['scale'][1]]) + #self.userTransform.setTranslate([-tr['pos'][0], tr['pos'][1]]) + #self.userTransform.setRotate(-tr['angle']) + #self.updateTransform() + #self.selectBoxFromUser() + #return + + def mirrorXY(self): + if not self.isMovable(): + return + self.rotate(180.) + # inv = SRTTransform() + # inv.scale(-1, -1) + # self.userTransform = self.userTransform * inv #flip lr/ud + # s=self.updateTransform() + # self.setTranslate(-2*s['pos'][0], -2*s['pos'][1]) + # self.selectBoxFromUser() + + + def hasUserTransform(self): + #print self.userRotate, self.userTranslate + return not self.userTransform.isIdentity() + + def ctrlWidget(self): + return self.ctrl + + def alphaChanged(self, val): + alpha = val / 1023. + self._graphicsItem.setOpacity(alpha) + + def isMovable(self): + return self.opts['movable'] + + + def selectBoxMoved(self): + """The selection box has moved; get its transformation information and pass to the graphics item""" + self.userTransform = self.selectBox.getGlobalTransform(relativeTo=self.selectBoxBase) + self.updateTransform() + + def scale(self, x, y): + self.userTransform.scale(x, y) + self.selectBoxFromUser() + self.updateTransform() + + def rotate(self, ang): + self.userTransform.rotate(ang) + self.selectBoxFromUser() + self.updateTransform() + + def translate(self, x, y): + self.userTransform.translate(x, y) + self.selectBoxFromUser() + self.updateTransform() + + def setTranslate(self, x, y): + self.userTransform.setTranslate(x, y) + self.selectBoxFromUser() + self.updateTransform() + + def setRotate(self, angle): + self.userTransform.setRotate(angle) + self.selectBoxFromUser() + self.updateTransform() + + def setScale(self, x, y): + self.userTransform.setScale(x, y) + self.selectBoxFromUser() + self.updateTransform() + + + def setTemporaryTransform(self, transform): + self.tempTransform = transform + self.updateTransform() + + def applyTemporaryTransform(self): + """Collapses tempTransform into UserTransform, resets tempTransform""" + self.userTransform = self.userTransform * self.tempTransform ## order is important! + self.resetTemporaryTransform() + self.selectBoxFromUser() ## update the selection box to match the new userTransform + + #st = self.userTransform.saveState() + + #self.userTransform = self.userTransform * self.tempTransform ## order is important! + + #### matrix multiplication affects the scale factors, need to reset + #if st['scale'][0] < 0 or st['scale'][1] < 0: + #nst = self.userTransform.saveState() + #self.userTransform.setScale([-nst['scale'][0], -nst['scale'][1]]) + + #self.resetTemporaryTransform() + #self.selectBoxFromUser() + #self.selectBoxChangeFinished() + + + + def resetTemporaryTransform(self): + self.tempTransform = SRTTransform() ## don't use Transform.reset()--this transform might be used elsewhere. + self.updateTransform() + + def transform(self): + return self._graphicsItem.transform() + + def updateTransform(self): + """Regenerate the item position from the base, user, and temp transforms""" + transform = self.baseTransform * self.userTransform * self.tempTransform ## order is important + s = transform.saveState() + self._graphicsItem.setPos(*s['pos']) + + self.itemRotation.setAngle(s['angle']) + self.itemScale.setXScale(s['scale'][0]) + self.itemScale.setYScale(s['scale'][1]) + + self.displayTransform(transform) + return(s) # return the transform state + + def displayTransform(self, transform): + """Updates transform numbers in the ctrl widget.""" + + tr = transform.saveState() + + self.transformGui.translateLabel.setText("Translate: (%f, %f)" %(tr['pos'][0], tr['pos'][1])) + self.transformGui.rotateLabel.setText("Rotate: %f degrees" %tr['angle']) + self.transformGui.scaleLabel.setText("Scale: (%f, %f)" %(tr['scale'][0], tr['scale'][1])) + #self.transformGui.mirrorImageCheck.setChecked(False) + #if tr['scale'][0] < 0: + # self.transformGui.mirrorImageCheck.setChecked(True) + + + def resetUserTransform(self): + #self.userRotate = 0 + #self.userTranslate = pg.Point(0,0) + self.userTransform.reset() + self.updateTransform() + + self.selectBox.blockSignals(True) + self.selectBoxToItem() + self.selectBox.blockSignals(False) + self.sigTransformChanged.emit(self) + self.sigTransformChangeFinished.emit(self) + + def resetTransformClicked(self): + self.resetUserTransform() + self.sigResetUserTransform.emit(self) + + def restoreTransform(self, tr): + try: + #self.userTranslate = pg.Point(tr['trans']) + #self.userRotate = tr['rot'] + self.userTransform = SRTTransform(tr) + self.updateTransform() + + self.selectBoxFromUser() ## move select box to match + self.sigTransformChanged.emit(self) + self.sigTransformChangeFinished.emit(self) + except: + #self.userTranslate = pg.Point([0,0]) + #self.userRotate = 0 + self.userTransform = SRTTransform() + debug.printExc("Failed to load transform:") + #print "set transform", self, self.userTranslate + + def saveTransform(self): + """Return a dict containing the current user transform""" + #print "save transform", self, self.userTranslate + #return {'trans': list(self.userTranslate), 'rot': self.userRotate} + return self.userTransform.saveState() + + def selectBoxFromUser(self): + """Move the selection box to match the current userTransform""" + ## user transform + #trans = QtGui.QTransform() + #trans.translate(*self.userTranslate) + #trans.rotate(-self.userRotate) + + #x2, y2 = trans.map(*self.selectBoxBase['pos']) + + self.selectBox.blockSignals(True) + self.selectBox.setState(self.selectBoxBase) + self.selectBox.applyGlobalTransform(self.userTransform) + #self.selectBox.setAngle(self.userRotate) + #self.selectBox.setPos([x2, y2]) + self.selectBox.blockSignals(False) + + + def selectBoxToItem(self): + """Move/scale the selection box so it fits the item's bounding rect. (assumes item is not rotated)""" + self.itemRect = self._graphicsItem.boundingRect() + rect = self._graphicsItem.mapRectToParent(self.itemRect) + self.selectBox.blockSignals(True) + self.selectBox.setPos([rect.x(), rect.y()]) + self.selectBox.setSize(rect.size()) + self.selectBox.setAngle(0) + self.selectBoxBase = self.selectBox.getState().copy() + self.selectBox.blockSignals(False) + + def zValue(self): + return self.opts['z'] + + def setZValue(self, z): + self.opts['z'] = z + if z is not None: + self._graphicsItem.setZValue(z) + + #def selectionChanged(self, canvas, items): + #self.selected = len(items) == 1 and (items[0] is self) + #self.showSelectBox() + + + def selectionChanged(self, sel, multi): + """ + Inform the item that its selection state has changed. + ============== ========================================================= + **Arguments:** + sel (bool) whether the item is currently selected + multi (bool) whether there are multiple items currently + selected + ============== ========================================================= + """ + self.selectedAlone = sel and not multi + self.showSelectBox() + if self.selectedAlone: + self.ctrlWidget().show() + else: + self.ctrlWidget().hide() + + def showSelectBox(self): + """Display the selection box around this item if it is selected and movable""" + if self.selectedAlone and self.isMovable() and self.isVisible(): #and len(self.canvas.itemList.selectedItems())==1: + self.selectBox.show() + else: + self.selectBox.hide() + + def hideSelectBox(self): + self.selectBox.hide() + + + def selectBoxChanged(self): + self.selectBoxMoved() + #self.updateTransform(self.selectBox) + #self.emit(QtCore.SIGNAL('transformChanged'), self) + self.sigTransformChanged.emit(self) + + def selectBoxChangeFinished(self): + #self.emit(QtCore.SIGNAL('transformChangeFinished'), self) + self.sigTransformChangeFinished.emit(self) + + def alphaPressed(self): + """Hide selection box while slider is moving""" + self.hideSelectBox() + + def alphaReleased(self): + self.showSelectBox() + + def show(self): + if self.opts['visible']: + return + self.opts['visible'] = True + self._graphicsItem.show() + self.showSelectBox() + self.sigVisibilityChanged.emit(self) + + def hide(self): + if not self.opts['visible']: + return + self.opts['visible'] = False + self._graphicsItem.hide() + self.hideSelectBox() + self.sigVisibilityChanged.emit(self) + + def setVisible(self, vis): + if vis: + self.show() + else: + self.hide() + + def isVisible(self): + return self.opts['visible'] + + +class GroupCanvasItem(CanvasItem): + """ + Canvas item used for grouping others + """ + + def __init__(self, **opts): + defOpts = {'movable': False, 'scalable': False} + defOpts.update(opts) + item = ItemGroup() + CanvasItem.__init__(self, item, **defOpts) + diff --git a/papi/pyqtgraph/canvas/CanvasManager.py b/papi/pyqtgraph/canvas/CanvasManager.py new file mode 100644 index 00000000..28188039 --- /dev/null +++ b/papi/pyqtgraph/canvas/CanvasManager.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtCore, QtGui +if not hasattr(QtCore, 'Signal'): + QtCore.Signal = QtCore.pyqtSignal +import weakref + +class CanvasManager(QtCore.QObject): + SINGLETON = None + + sigCanvasListChanged = QtCore.Signal() + + def __init__(self): + if CanvasManager.SINGLETON is not None: + raise Exception("Can only create one canvas manager.") + CanvasManager.SINGLETON = self + QtCore.QObject.__init__(self) + self.canvases = weakref.WeakValueDictionary() + + @classmethod + def instance(cls): + return CanvasManager.SINGLETON + + def registerCanvas(self, canvas, name): + n2 = name + i = 0 + while n2 in self.canvases: + n2 = "%s_%03d" % (name, i) + i += 1 + self.canvases[n2] = canvas + self.sigCanvasListChanged.emit() + return n2 + + def unregisterCanvas(self, name): + c = self.canvases[name] + del self.canvases[name] + self.sigCanvasListChanged.emit() + + def listCanvases(self): + return list(self.canvases.keys()) + + def getCanvas(self, name): + return self.canvases[name] + + +manager = CanvasManager() + + +class CanvasCombo(QtGui.QComboBox): + def __init__(self, parent=None): + QtGui.QComboBox.__init__(self, parent) + man = CanvasManager.instance() + man.sigCanvasListChanged.connect(self.updateCanvasList) + self.hostName = None + self.updateCanvasList() + + def updateCanvasList(self): + canvases = CanvasManager.instance().listCanvases() + canvases.insert(0, "") + if self.hostName in canvases: + canvases.remove(self.hostName) + + sel = self.currentText() + if sel in canvases: + self.blockSignals(True) ## change does not affect current selection; block signals during update + self.clear() + for i in canvases: + self.addItem(i) + if i == sel: + self.setCurrentIndex(self.count()) + + self.blockSignals(False) + + def setHostName(self, name): + self.hostName = name + self.updateCanvasList() + diff --git a/papi/pyqtgraph/canvas/CanvasTemplate.ui b/papi/pyqtgraph/canvas/CanvasTemplate.ui new file mode 100644 index 00000000..9bea8f89 --- /dev/null +++ b/papi/pyqtgraph/canvas/CanvasTemplate.ui @@ -0,0 +1,135 @@ + + + Form + + + + 0 + 0 + 490 + 414 + + + + Form + + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + + 0 + 1 + + + + Auto Range + + + + + + + 0 + + + + + Check to display all local items in a remote canvas. + + + Redirect + + + + + + + + + + + + + 0 + 100 + + + + true + + + + 1 + + + + + + + + 0 + + + + + + + Reset Transforms + + + + + + + Mirror Selection + + + + + + + MirrorXY + + + + + + + + + + + + TreeWidget + QTreeWidget +
..widgets.TreeWidget
+
+ + GraphicsView + QGraphicsView +
..widgets.GraphicsView
+
+ + CanvasCombo + QComboBox +
CanvasManager
+
+
+ + +
diff --git a/papi/pyqtgraph/canvas/CanvasTemplate_pyqt.py b/papi/pyqtgraph/canvas/CanvasTemplate_pyqt.py new file mode 100644 index 00000000..557354e0 --- /dev/null +++ b/papi/pyqtgraph/canvas/CanvasTemplate_pyqt.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'acq4/pyqtgraph/canvas/CanvasTemplate.ui' +# +# Created: Thu Jan 2 11:13:07 2014 +# by: PyQt4 UI code generator 4.9 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + _fromUtf8 = lambda s: s + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(490, 414) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName(_fromUtf8("splitter")) + self.view = GraphicsView(self.splitter) + self.view.setObjectName(_fromUtf8("view")) + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) + self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget) + self.gridLayout_2.setMargin(0) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) + self.autoRangeBtn.setSizePolicy(sizePolicy) + self.autoRangeBtn.setObjectName(_fromUtf8("autoRangeBtn")) + self.gridLayout_2.addWidget(self.autoRangeBtn, 2, 0, 1, 2) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.redirectCheck = QtGui.QCheckBox(self.layoutWidget) + self.redirectCheck.setObjectName(_fromUtf8("redirectCheck")) + self.horizontalLayout.addWidget(self.redirectCheck) + self.redirectCombo = CanvasCombo(self.layoutWidget) + self.redirectCombo.setObjectName(_fromUtf8("redirectCombo")) + self.horizontalLayout.addWidget(self.redirectCombo) + self.gridLayout_2.addLayout(self.horizontalLayout, 5, 0, 1, 2) + self.itemList = TreeWidget(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(100) + sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) + self.itemList.setSizePolicy(sizePolicy) + self.itemList.setHeaderHidden(True) + self.itemList.setObjectName(_fromUtf8("itemList")) + self.itemList.headerItem().setText(0, _fromUtf8("1")) + self.gridLayout_2.addWidget(self.itemList, 6, 0, 1, 2) + self.ctrlLayout = QtGui.QGridLayout() + self.ctrlLayout.setSpacing(0) + self.ctrlLayout.setObjectName(_fromUtf8("ctrlLayout")) + self.gridLayout_2.addLayout(self.ctrlLayout, 10, 0, 1, 2) + self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget) + self.resetTransformsBtn.setObjectName(_fromUtf8("resetTransformsBtn")) + self.gridLayout_2.addWidget(self.resetTransformsBtn, 7, 0, 1, 1) + self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget) + self.mirrorSelectionBtn.setObjectName(_fromUtf8("mirrorSelectionBtn")) + self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 3, 0, 1, 1) + self.reflectSelectionBtn = QtGui.QPushButton(self.layoutWidget) + self.reflectSelectionBtn.setObjectName(_fromUtf8("reflectSelectionBtn")) + self.gridLayout_2.addWidget(self.reflectSelectionBtn, 3, 1, 1, 1) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8)) + self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8)) + self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8)) + self.resetTransformsBtn.setText(QtGui.QApplication.translate("Form", "Reset Transforms", None, QtGui.QApplication.UnicodeUTF8)) + self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8)) + self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8)) + +from ..widgets.TreeWidget import TreeWidget +from CanvasManager import CanvasCombo +from ..widgets.GraphicsView import GraphicsView diff --git a/papi/pyqtgraph/canvas/CanvasTemplate_pyside.py b/papi/pyqtgraph/canvas/CanvasTemplate_pyside.py new file mode 100644 index 00000000..56d1ff47 --- /dev/null +++ b/papi/pyqtgraph/canvas/CanvasTemplate_pyside.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/canvas/CanvasTemplate.ui' +# +# Created: Mon Dec 23 10:10:52 2013 +# by: pyside-uic 0.2.14 running on PySide 1.1.2 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(490, 414) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Horizontal) + self.splitter.setObjectName("splitter") + self.view = GraphicsView(self.splitter) + self.view.setObjectName("view") + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName("layoutWidget") + self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.storeSvgBtn = QtGui.QPushButton(self.layoutWidget) + self.storeSvgBtn.setObjectName("storeSvgBtn") + self.gridLayout_2.addWidget(self.storeSvgBtn, 1, 0, 1, 1) + self.storePngBtn = QtGui.QPushButton(self.layoutWidget) + self.storePngBtn.setObjectName("storePngBtn") + self.gridLayout_2.addWidget(self.storePngBtn, 1, 1, 1, 1) + self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth()) + self.autoRangeBtn.setSizePolicy(sizePolicy) + self.autoRangeBtn.setObjectName("autoRangeBtn") + self.gridLayout_2.addWidget(self.autoRangeBtn, 3, 0, 1, 2) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setSpacing(0) + self.horizontalLayout.setObjectName("horizontalLayout") + self.redirectCheck = QtGui.QCheckBox(self.layoutWidget) + self.redirectCheck.setObjectName("redirectCheck") + self.horizontalLayout.addWidget(self.redirectCheck) + self.redirectCombo = CanvasCombo(self.layoutWidget) + self.redirectCombo.setObjectName("redirectCombo") + self.horizontalLayout.addWidget(self.redirectCombo) + self.gridLayout_2.addLayout(self.horizontalLayout, 6, 0, 1, 2) + self.itemList = TreeWidget(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(100) + sizePolicy.setHeightForWidth(self.itemList.sizePolicy().hasHeightForWidth()) + self.itemList.setSizePolicy(sizePolicy) + self.itemList.setHeaderHidden(True) + self.itemList.setObjectName("itemList") + self.itemList.headerItem().setText(0, "1") + self.gridLayout_2.addWidget(self.itemList, 7, 0, 1, 2) + self.ctrlLayout = QtGui.QGridLayout() + self.ctrlLayout.setSpacing(0) + self.ctrlLayout.setObjectName("ctrlLayout") + self.gridLayout_2.addLayout(self.ctrlLayout, 11, 0, 1, 2) + self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget) + self.resetTransformsBtn.setObjectName("resetTransformsBtn") + self.gridLayout_2.addWidget(self.resetTransformsBtn, 8, 0, 1, 1) + self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget) + self.mirrorSelectionBtn.setObjectName("mirrorSelectionBtn") + self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1) + self.reflectSelectionBtn = QtGui.QPushButton(self.layoutWidget) + self.reflectSelectionBtn.setObjectName("reflectSelectionBtn") + self.gridLayout_2.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.storeSvgBtn.setText(QtGui.QApplication.translate("Form", "Store SVG", None, QtGui.QApplication.UnicodeUTF8)) + self.storePngBtn.setText(QtGui.QApplication.translate("Form", "Store PNG", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8)) + self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8)) + self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8)) + self.resetTransformsBtn.setText(QtGui.QApplication.translate("Form", "Reset Transforms", None, QtGui.QApplication.UnicodeUTF8)) + self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8)) + self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8)) + +from ..widgets.TreeWidget import TreeWidget +from CanvasManager import CanvasCombo +from ..widgets.GraphicsView import GraphicsView diff --git a/papi/pyqtgraph/canvas/TransformGuiTemplate.ui b/papi/pyqtgraph/canvas/TransformGuiTemplate.ui new file mode 100644 index 00000000..d8312388 --- /dev/null +++ b/papi/pyqtgraph/canvas/TransformGuiTemplate.ui @@ -0,0 +1,75 @@ + + + Form + + + + 0 + 0 + 224 + 117 + + + + + 0 + 0 + + + + Form + + + + 1 + + + 0 + + + + + Translate: + + + + + + + Rotate: + + + + + + + Scale: + + + + + + + + + + + + Mirror + + + + + + + Reflect + + + + + + + + + + diff --git a/papi/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py b/papi/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py new file mode 100644 index 00000000..75c694c0 --- /dev/null +++ b/papi/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/canvas/TransformGuiTemplate.ui' +# +# Created: Mon Dec 23 10:10:52 2013 +# by: PyQt4 UI code generator 4.10 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(224, 117) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + self.verticalLayout = QtGui.QVBoxLayout(Form) + self.verticalLayout.setSpacing(1) + self.verticalLayout.setMargin(0) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.translateLabel = QtGui.QLabel(Form) + self.translateLabel.setObjectName(_fromUtf8("translateLabel")) + self.verticalLayout.addWidget(self.translateLabel) + self.rotateLabel = QtGui.QLabel(Form) + self.rotateLabel.setObjectName(_fromUtf8("rotateLabel")) + self.verticalLayout.addWidget(self.rotateLabel) + self.scaleLabel = QtGui.QLabel(Form) + self.scaleLabel.setObjectName(_fromUtf8("scaleLabel")) + self.verticalLayout.addWidget(self.scaleLabel) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.mirrorImageBtn = QtGui.QPushButton(Form) + self.mirrorImageBtn.setToolTip(_fromUtf8("")) + self.mirrorImageBtn.setObjectName(_fromUtf8("mirrorImageBtn")) + self.horizontalLayout.addWidget(self.mirrorImageBtn) + self.reflectImageBtn = QtGui.QPushButton(Form) + self.reflectImageBtn.setObjectName(_fromUtf8("reflectImageBtn")) + self.horizontalLayout.addWidget(self.reflectImageBtn) + self.verticalLayout.addLayout(self.horizontalLayout) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(_translate("Form", "Form", None)) + self.translateLabel.setText(_translate("Form", "Translate:", None)) + self.rotateLabel.setText(_translate("Form", "Rotate:", None)) + self.scaleLabel.setText(_translate("Form", "Scale:", None)) + self.mirrorImageBtn.setText(_translate("Form", "Mirror", None)) + self.reflectImageBtn.setText(_translate("Form", "Reflect", None)) + diff --git a/papi/pyqtgraph/canvas/TransformGuiTemplate_pyside.py b/papi/pyqtgraph/canvas/TransformGuiTemplate_pyside.py new file mode 100644 index 00000000..bce7b511 --- /dev/null +++ b/papi/pyqtgraph/canvas/TransformGuiTemplate_pyside.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/canvas/TransformGuiTemplate.ui' +# +# Created: Mon Dec 23 10:10:52 2013 +# by: pyside-uic 0.2.14 running on PySide 1.1.2 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(224, 117) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(Form.sizePolicy().hasHeightForWidth()) + Form.setSizePolicy(sizePolicy) + self.verticalLayout = QtGui.QVBoxLayout(Form) + self.verticalLayout.setSpacing(1) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.translateLabel = QtGui.QLabel(Form) + self.translateLabel.setObjectName("translateLabel") + self.verticalLayout.addWidget(self.translateLabel) + self.rotateLabel = QtGui.QLabel(Form) + self.rotateLabel.setObjectName("rotateLabel") + self.verticalLayout.addWidget(self.rotateLabel) + self.scaleLabel = QtGui.QLabel(Form) + self.scaleLabel.setObjectName("scaleLabel") + self.verticalLayout.addWidget(self.scaleLabel) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.mirrorImageBtn = QtGui.QPushButton(Form) + self.mirrorImageBtn.setToolTip("") + self.mirrorImageBtn.setObjectName("mirrorImageBtn") + self.horizontalLayout.addWidget(self.mirrorImageBtn) + self.reflectImageBtn = QtGui.QPushButton(Form) + self.reflectImageBtn.setObjectName("reflectImageBtn") + self.horizontalLayout.addWidget(self.reflectImageBtn) + self.verticalLayout.addLayout(self.horizontalLayout) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.translateLabel.setText(QtGui.QApplication.translate("Form", "Translate:", None, QtGui.QApplication.UnicodeUTF8)) + self.rotateLabel.setText(QtGui.QApplication.translate("Form", "Rotate:", None, QtGui.QApplication.UnicodeUTF8)) + self.scaleLabel.setText(QtGui.QApplication.translate("Form", "Scale:", None, QtGui.QApplication.UnicodeUTF8)) + self.mirrorImageBtn.setText(QtGui.QApplication.translate("Form", "Mirror", None, QtGui.QApplication.UnicodeUTF8)) + self.reflectImageBtn.setText(QtGui.QApplication.translate("Form", "Reflect", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/pyqtgraph/canvas/__init__.py b/papi/pyqtgraph/canvas/__init__.py new file mode 100644 index 00000000..f649d0a1 --- /dev/null +++ b/papi/pyqtgraph/canvas/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- +from .Canvas import * +from .CanvasItem import * \ No newline at end of file diff --git a/papi/pyqtgraph/colormap.py b/papi/pyqtgraph/colormap.py new file mode 100644 index 00000000..c0033708 --- /dev/null +++ b/papi/pyqtgraph/colormap.py @@ -0,0 +1,250 @@ +import numpy as np +from .Qt import QtGui, QtCore + +class ColorMap(object): + """ + A ColorMap defines a relationship between a scalar value and a range of colors. + ColorMaps are commonly used for false-coloring monochromatic images, coloring + scatter-plot points, and coloring surface plots by height. + + Each color map is defined by a set of colors, each corresponding to a + particular scalar value. For example: + + | 0.0 -> black + | 0.2 -> red + | 0.6 -> yellow + | 1.0 -> white + + The colors for intermediate values are determined by interpolating between + the two nearest colors in either RGB or HSV color space. + + To provide user-defined color mappings, see :class:`GradientWidget `. + """ + + + ## color interpolation modes + RGB = 1 + HSV_POS = 2 + HSV_NEG = 3 + + ## boundary modes + CLIP = 1 + REPEAT = 2 + MIRROR = 3 + + ## return types + BYTE = 1 + FLOAT = 2 + QCOLOR = 3 + + enumMap = { + 'rgb': RGB, + 'hsv+': HSV_POS, + 'hsv-': HSV_NEG, + 'clip': CLIP, + 'repeat': REPEAT, + 'mirror': MIRROR, + 'byte': BYTE, + 'float': FLOAT, + 'qcolor': QCOLOR, + } + + def __init__(self, pos, color, mode=None): + """ + =============== ============================================================== + **Arguments:** + pos Array of positions where each color is defined + color Array of RGBA colors. + Integer data types are interpreted as 0-255; float data types + are interpreted as 0.0-1.0 + mode Array of color modes (ColorMap.RGB, HSV_POS, or HSV_NEG) + indicating the color space that should be used when + interpolating between stops. Note that the last mode value is + ignored. By default, the mode is entirely RGB. + =============== ============================================================== + """ + self.pos = np.array(pos) + self.color = np.array(color) + if mode is None: + mode = np.ones(len(pos)) + self.mode = mode + self.stopsCache = {} + + def map(self, data, mode='byte'): + """ + Return an array of colors corresponding to the values in *data*. + Data must be either a scalar position or an array (any shape) of positions. + + The *mode* argument determines the type of data returned: + + =========== =============================================================== + byte (default) Values are returned as 0-255 unsigned bytes. + float Values are returned as 0.0-1.0 floats. + qcolor Values are returned as an array of QColor objects. + =========== =============================================================== + """ + if isinstance(mode, basestring): + mode = self.enumMap[mode.lower()] + + if mode == self.QCOLOR: + pos, color = self.getStops(self.BYTE) + else: + pos, color = self.getStops(mode) + + # don't need this--np.interp takes care of it. + #data = np.clip(data, pos.min(), pos.max()) + + # Interpolate + # TODO: is griddata faster? + # interp = scipy.interpolate.griddata(pos, color, data) + if np.isscalar(data): + interp = np.empty((color.shape[1],), dtype=color.dtype) + else: + if not isinstance(data, np.ndarray): + data = np.array(data) + interp = np.empty(data.shape + (color.shape[1],), dtype=color.dtype) + for i in range(color.shape[1]): + interp[...,i] = np.interp(data, pos, color[:,i]) + + # Convert to QColor if requested + if mode == self.QCOLOR: + if np.isscalar(data): + return QtGui.QColor(*interp) + else: + return [QtGui.QColor(*x) for x in interp] + else: + return interp + + def mapToQColor(self, data): + """Convenience function; see :func:`map() `.""" + return self.map(data, mode=self.QCOLOR) + + def mapToByte(self, data): + """Convenience function; see :func:`map() `.""" + return self.map(data, mode=self.BYTE) + + def mapToFloat(self, data): + """Convenience function; see :func:`map() `.""" + return self.map(data, mode=self.FLOAT) + + def getGradient(self, p1=None, p2=None): + """Return a QLinearGradient object spanning from QPoints p1 to p2.""" + if p1 == None: + p1 = QtCore.QPointF(0,0) + if p2 == None: + p2 = QtCore.QPointF(self.pos.max()-self.pos.min(),0) + g = QtGui.QLinearGradient(p1, p2) + + pos, color = self.getStops(mode=self.BYTE) + color = [QtGui.QColor(*x) for x in color] + g.setStops(zip(pos, color)) + + #if self.colorMode == 'rgb': + #ticks = self.listTicks() + #g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) + #elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop + #ticks = self.listTicks() + #stops = [] + #stops.append((ticks[0][1], ticks[0][0].color)) + #for i in range(1,len(ticks)): + #x1 = ticks[i-1][1] + #x2 = ticks[i][1] + #dx = (x2-x1) / 10. + #for j in range(1,10): + #x = x1 + dx*j + #stops.append((x, self.getColor(x))) + #stops.append((x2, self.getColor(x2))) + #g.setStops(stops) + return g + + def getColors(self, mode=None): + """Return list of all color stops converted to the specified mode. + If mode is None, then no conversion is done.""" + if isinstance(mode, basestring): + mode = self.enumMap[mode.lower()] + + color = self.color + if mode in [self.BYTE, self.QCOLOR] and color.dtype.kind == 'f': + color = (color * 255).astype(np.ubyte) + elif mode == self.FLOAT and color.dtype.kind != 'f': + color = color.astype(float) / 255. + + if mode == self.QCOLOR: + color = [QtGui.QColor(*x) for x in color] + + return color + + def getStops(self, mode): + ## Get fully-expanded set of RGBA stops in either float or byte mode. + if mode not in self.stopsCache: + color = self.color + if mode == self.BYTE and color.dtype.kind == 'f': + color = (color * 255).astype(np.ubyte) + elif mode == self.FLOAT and color.dtype.kind != 'f': + color = color.astype(float) / 255. + + ## to support HSV mode, we need to do a little more work.. + #stops = [] + #for i in range(len(self.pos)): + #pos = self.pos[i] + #color = color[i] + + #imode = self.mode[i] + #if imode == self.RGB: + #stops.append((x,color)) + #else: + #ns = + self.stopsCache[mode] = (self.pos, color) + return self.stopsCache[mode] + + def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode='byte'): + """ + Return an RGB(A) lookup table (ndarray). + + =============== ============================================================================= + **Arguments:** + start The starting value in the lookup table (default=0.0) + stop The final value in the lookup table (default=1.0) + nPts The number of points in the returned lookup table. + alpha True, False, or None - Specifies whether or not alpha values are included + in the table. If alpha is None, it will be automatically determined. + mode Determines return type: 'byte' (0-255), 'float' (0.0-1.0), or 'qcolor'. + See :func:`map() `. + =============== ============================================================================= + """ + if isinstance(mode, basestring): + mode = self.enumMap[mode.lower()] + + if alpha is None: + alpha = self.usesAlpha() + + x = np.linspace(start, stop, nPts) + table = self.map(x, mode) + + if not alpha: + return table[:,:3] + else: + return table + + def usesAlpha(self): + """Return True if any stops have an alpha < 255""" + max = 1.0 if self.color.dtype.kind == 'f' else 255 + return np.any(self.color[:,3] != max) + + def isMapTrivial(self): + """ + Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0. + """ + if len(self.pos) != 2: + return False + if self.pos[0] != 0.0 or self.pos[1] != 1.0: + return False + if self.color.dtype.kind == 'f': + return np.all(self.color == np.array([[0.,0.,0.,1.], [1.,1.,1.,1.]])) + else: + return np.all(self.color == np.array([[0,0,0,255], [255,255,255,255]])) + + def __repr__(self): + pos = repr(self.pos).replace('\n', '') + color = repr(self.color).replace('\n', '') + return "ColorMap(%s, %s)" % (pos, color) diff --git a/papi/pyqtgraph/configfile.py b/papi/pyqtgraph/configfile.py new file mode 100644 index 00000000..c095bba3 --- /dev/null +++ b/papi/pyqtgraph/configfile.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- +""" +configfile.py - Human-readable text configuration file library +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +Used for reading and writing dictionary objects to a python-like configuration +file format. Data structures may be nested and contain any data type as long +as it can be converted to/from a string using repr and eval. +""" + +import re, os, sys +from .pgcollections import OrderedDict +GLOBAL_PATH = None # so not thread safe. +from . import units +from .python2_3 import asUnicode +from .Qt import QtCore +from .Point import Point +from .colormap import ColorMap +import numpy + +class ParseError(Exception): + def __init__(self, message, lineNum, line, fileName=None): + self.lineNum = lineNum + self.line = line + #self.message = message + self.fileName = fileName + Exception.__init__(self, message) + + def __str__(self): + if self.fileName is None: + msg = "Error parsing string at line %d:\n" % self.lineNum + else: + msg = "Error parsing config file '%s' at line %d:\n" % (self.fileName, self.lineNum) + msg += "%s\n%s" % (self.line, self.message) + return msg + #raise Exception() + + +def writeConfigFile(data, fname): + s = genString(data) + fd = open(fname, 'w') + fd.write(s) + fd.close() + +def readConfigFile(fname): + #cwd = os.getcwd() + global GLOBAL_PATH + if GLOBAL_PATH is not None: + fname2 = os.path.join(GLOBAL_PATH, fname) + if os.path.exists(fname2): + fname = fname2 + + GLOBAL_PATH = os.path.dirname(os.path.abspath(fname)) + + try: + #os.chdir(newDir) ## bad. + fd = open(fname) + s = asUnicode(fd.read()) + fd.close() + s = s.replace("\r\n", "\n") + s = s.replace("\r", "\n") + data = parseString(s)[1] + except ParseError: + sys.exc_info()[1].fileName = fname + raise + except: + print("Error while reading config file %s:"% fname) + raise + #finally: + #os.chdir(cwd) + return data + +def appendConfigFile(data, fname): + s = genString(data) + fd = open(fname, 'a') + fd.write(s) + fd.close() + + +def genString(data, indent=''): + s = '' + for k in data: + sk = str(k) + if len(sk) == 0: + print(data) + raise Exception('blank dict keys not allowed (see data above)') + if sk[0] == ' ' or ':' in sk: + print(data) + raise Exception('dict keys must not contain ":" or start with spaces [offending key is "%s"]' % sk) + if isinstance(data[k], dict): + s += indent + sk + ':\n' + s += genString(data[k], indent + ' ') + else: + s += indent + sk + ': ' + repr(data[k]) + '\n' + return s + +def parseString(lines, start=0): + + data = OrderedDict() + if isinstance(lines, basestring): + lines = lines.split('\n') + lines = [l for l in lines if re.search(r'\S', l) and not re.match(r'\s*#', l)] ## remove empty lines + + indent = measureIndent(lines[start]) + ln = start - 1 + + try: + while True: + ln += 1 + #print ln + if ln >= len(lines): + break + + l = lines[ln] + + ## Skip blank lines or lines starting with # + if re.match(r'\s*#', l) or not re.search(r'\S', l): + continue + + ## Measure line indentation, make sure it is correct for this level + lineInd = measureIndent(l) + if lineInd < indent: + ln -= 1 + break + if lineInd > indent: + #print lineInd, indent + raise ParseError('Indentation is incorrect. Expected %d, got %d' % (indent, lineInd), ln+1, l) + + + if ':' not in l: + raise ParseError('Missing colon', ln+1, l) + + (k, p, v) = l.partition(':') + k = k.strip() + v = v.strip() + + ## set up local variables to use for eval + local = units.allUnits.copy() + local['OrderedDict'] = OrderedDict + local['readConfigFile'] = readConfigFile + local['Point'] = Point + local['QtCore'] = QtCore + local['ColorMap'] = ColorMap + # Needed for reconstructing numpy arrays + local['array'] = numpy.array + for dtype in ['int8', 'uint8', + 'int16', 'uint16', 'float16', + 'int32', 'uint32', 'float32', + 'int64', 'uint64', 'float64']: + local[dtype] = getattr(numpy, dtype) + + if len(k) < 1: + raise ParseError('Missing name preceding colon', ln+1, l) + if k[0] == '(' and k[-1] == ')': ## If the key looks like a tuple, try evaluating it. + try: + k1 = eval(k, local) + if type(k1) is tuple: + k = k1 + except: + pass + if re.search(r'\S', v) and v[0] != '#': ## eval the value + try: + val = eval(v, local) + except: + ex = sys.exc_info()[1] + raise ParseError("Error evaluating expression '%s': [%s: %s]" % (v, ex.__class__.__name__, str(ex)), (ln+1), l) + else: + if ln+1 >= len(lines) or measureIndent(lines[ln+1]) <= indent: + #print "blank dict" + val = {} + else: + #print "Going deeper..", ln+1 + (ln, val) = parseString(lines, start=ln+1) + data[k] = val + #print k, repr(val) + except ParseError: + raise + except: + ex = sys.exc_info()[1] + raise ParseError("%s: %s" % (ex.__class__.__name__, str(ex)), ln+1, l) + #print "Returning shallower..", ln+1 + return (ln, data) + +def measureIndent(s): + n = 0 + while n < len(s) and s[n] == ' ': + n += 1 + return n + + + +if __name__ == '__main__': + import tempfile + fn = tempfile.mktemp() + tf = open(fn, 'w') + cf = """ +key: 'value' +key2: ##comment + ##comment + key21: 'value' ## comment + ##comment + key22: [1,2,3] + key23: 234 #comment + """ + tf.write(cf) + tf.close() + print("=== Test:===") + num = 1 + for line in cf.split('\n'): + print("%02d %s" % (num, line)) + num += 1 + print(cf) + print("============") + data = readConfigFile(fn) + print(data) + os.remove(fn) diff --git a/papi/pyqtgraph/console/CmdInput.py b/papi/pyqtgraph/console/CmdInput.py new file mode 100644 index 00000000..24a01e89 --- /dev/null +++ b/papi/pyqtgraph/console/CmdInput.py @@ -0,0 +1,62 @@ +from ..Qt import QtCore, QtGui +from ..python2_3 import asUnicode + +class CmdInput(QtGui.QLineEdit): + + sigExecuteCmd = QtCore.Signal(object) + + def __init__(self, parent): + QtGui.QLineEdit.__init__(self, parent) + self.history = [""] + self.ptr = 0 + #self.lastCmd = None + #self.setMultiline(False) + + def keyPressEvent(self, ev): + #print "press:", ev.key(), QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_Enter + if ev.key() == QtCore.Qt.Key_Up and self.ptr < len(self.history) - 1: + self.setHistory(self.ptr+1) + ev.accept() + return + elif ev.key() == QtCore.Qt.Key_Down and self.ptr > 0: + self.setHistory(self.ptr-1) + ev.accept() + return + elif ev.key() == QtCore.Qt.Key_Return: + self.execCmd() + else: + QtGui.QLineEdit.keyPressEvent(self, ev) + self.history[0] = asUnicode(self.text()) + + def execCmd(self): + cmd = asUnicode(self.text()) + if len(self.history) == 1 or cmd != self.history[1]: + self.history.insert(1, cmd) + #self.lastCmd = cmd + self.history[0] = "" + self.setHistory(0) + self.sigExecuteCmd.emit(cmd) + + def setHistory(self, num): + self.ptr = num + self.setText(self.history[self.ptr]) + + #def setMultiline(self, m): + #height = QtGui.QFontMetrics(self.font()).lineSpacing() + #if m: + #self.setFixedHeight(height*5) + #else: + #self.setFixedHeight(height+15) + #self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + #self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + + #def sizeHint(self): + #hint = QtGui.QPlainTextEdit.sizeHint(self) + #height = QtGui.QFontMetrics(self.font()).lineSpacing() + #hint.setHeight(height) + #return hint + + + + \ No newline at end of file diff --git a/papi/pyqtgraph/console/Console.py b/papi/pyqtgraph/console/Console.py new file mode 100644 index 00000000..896de924 --- /dev/null +++ b/papi/pyqtgraph/console/Console.py @@ -0,0 +1,386 @@ + +from ..Qt import QtCore, QtGui, USE_PYSIDE +import sys, re, os, time, traceback, subprocess +if USE_PYSIDE: + from . import template_pyside as template +else: + from . import template_pyqt as template + +from .. import exceptionHandling as exceptionHandling +import pickle +from .. import getConfigOption + +class ConsoleWidget(QtGui.QWidget): + """ + Widget displaying console output and accepting command input. + Implements: + + - eval python expressions / exec python statements + - storable history of commands + - exception handling allowing commands to be interpreted in the context of any level in the exception stack frame + + Why not just use python in an interactive shell (or ipython) ? There are a few reasons: + + - pyside does not yet allow Qt event processing and interactive shell at the same time + - on some systems, typing in the console _blocks_ the qt event loop until the user presses enter. This can + be baffling and frustrating to users since it would appear the program has frozen. + - some terminals (eg windows cmd.exe) have notoriously unfriendly interfaces + - ability to add extra features like exception stack introspection + - ability to have multiple interactive prompts, including for spawned sub-processes + """ + + def __init__(self, parent=None, namespace=None, historyFile=None, text=None, editor=None): + """ + ============== ============================================================================ + **Arguments:** + namespace dictionary containing the initial variables present in the default namespace + historyFile optional file for storing command history + text initial text to display in the console window + editor optional string for invoking code editor (called when stack trace entries are + double-clicked). May contain {fileName} and {lineNum} format keys. Example:: + + editorCommand --loadfile {fileName} --gotoline {lineNum} + ============== ============================================================================= + """ + QtGui.QWidget.__init__(self, parent) + if namespace is None: + namespace = {} + self.localNamespace = namespace + self.editor = editor + self.multiline = None + self.inCmd = False + + self.ui = template.Ui_Form() + self.ui.setupUi(self) + self.output = self.ui.output + self.input = self.ui.input + self.input.setFocus() + + if text is not None: + self.output.setPlainText(text) + + self.historyFile = historyFile + + history = self.loadHistory() + if history is not None: + self.input.history = [""] + history + self.ui.historyList.addItems(history[::-1]) + self.ui.historyList.hide() + self.ui.exceptionGroup.hide() + + self.input.sigExecuteCmd.connect(self.runCmd) + self.ui.historyBtn.toggled.connect(self.ui.historyList.setVisible) + self.ui.historyList.itemClicked.connect(self.cmdSelected) + self.ui.historyList.itemDoubleClicked.connect(self.cmdDblClicked) + self.ui.exceptionBtn.toggled.connect(self.ui.exceptionGroup.setVisible) + + self.ui.catchAllExceptionsBtn.toggled.connect(self.catchAllExceptions) + self.ui.catchNextExceptionBtn.toggled.connect(self.catchNextException) + self.ui.clearExceptionBtn.clicked.connect(self.clearExceptionClicked) + self.ui.exceptionStackList.itemClicked.connect(self.stackItemClicked) + self.ui.exceptionStackList.itemDoubleClicked.connect(self.stackItemDblClicked) + self.ui.onlyUncaughtCheck.toggled.connect(self.updateSysTrace) + + self.currentTraceback = None + + def loadHistory(self): + """Return the list of previously-invoked command strings (or None).""" + if self.historyFile is not None: + return pickle.load(open(self.historyFile, 'rb')) + + def saveHistory(self, history): + """Store the list of previously-invoked command strings.""" + if self.historyFile is not None: + pickle.dump(open(self.historyFile, 'wb'), history) + + def runCmd(self, cmd): + #cmd = str(self.input.lastCmd) + self.stdout = sys.stdout + self.stderr = sys.stderr + encCmd = re.sub(r'>', '>', re.sub(r'<', '<', cmd)) + encCmd = re.sub(r' ', ' ', encCmd) + + self.ui.historyList.addItem(cmd) + self.saveHistory(self.input.history[1:100]) + + try: + sys.stdout = self + sys.stderr = self + if self.multiline is not None: + self.write("
%s\n"%encCmd, html=True) + self.execMulti(cmd) + else: + self.write("
%s\n"%encCmd, html=True) + self.inCmd = True + self.execSingle(cmd) + + if not self.inCmd: + self.write("
\n", html=True) + + finally: + sys.stdout = self.stdout + sys.stderr = self.stderr + + sb = self.output.verticalScrollBar() + sb.setValue(sb.maximum()) + sb = self.ui.historyList.verticalScrollBar() + sb.setValue(sb.maximum()) + + def globals(self): + frame = self.currentFrame() + if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): + return self.currentFrame().tb_frame.f_globals + else: + return globals() + + def locals(self): + frame = self.currentFrame() + if frame is not None and self.ui.runSelectedFrameCheck.isChecked(): + return self.currentFrame().tb_frame.f_locals + else: + return self.localNamespace + + def currentFrame(self): + ## Return the currently selected exception stack frame (or None if there is no exception) + if self.currentTraceback is None: + return None + index = self.ui.exceptionStackList.currentRow() + tb = self.currentTraceback + for i in range(index): + tb = tb.tb_next + return tb + + def execSingle(self, cmd): + try: + output = eval(cmd, self.globals(), self.locals()) + self.write(repr(output) + '\n') + except SyntaxError: + try: + exec(cmd, self.globals(), self.locals()) + except SyntaxError as exc: + if 'unexpected EOF' in exc.msg: + self.multiline = cmd + else: + self.displayException() + except: + self.displayException() + except: + self.displayException() + + + def execMulti(self, nextLine): + #self.stdout.write(nextLine+"\n") + if nextLine.strip() != '': + self.multiline += "\n" + nextLine + return + else: + cmd = self.multiline + + try: + output = eval(cmd, self.globals(), self.locals()) + self.write(str(output) + '\n') + self.multiline = None + except SyntaxError: + try: + exec(cmd, self.globals(), self.locals()) + self.multiline = None + except SyntaxError as exc: + if 'unexpected EOF' in exc.msg: + self.multiline = cmd + else: + self.displayException() + self.multiline = None + except: + self.displayException() + self.multiline = None + except: + self.displayException() + self.multiline = None + + def write(self, strn, html=False): + self.output.moveCursor(QtGui.QTextCursor.End) + if html: + self.output.textCursor().insertHtml(strn) + else: + if self.inCmd: + self.inCmd = False + self.output.textCursor().insertHtml("

") + #self.stdout.write("

") + self.output.insertPlainText(strn) + #self.stdout.write(strn) + + def displayException(self): + """ + Display the current exception and stack. + """ + tb = traceback.format_exc() + lines = [] + indent = 4 + prefix = '' + for l in tb.split('\n'): + lines.append(" "*indent + prefix + l) + self.write('\n'.join(lines)) + self.exceptionHandler(*sys.exc_info()) + + def cmdSelected(self, item): + index = -(self.ui.historyList.row(item)+1) + self.input.setHistory(index) + self.input.setFocus() + + def cmdDblClicked(self, item): + index = -(self.ui.historyList.row(item)+1) + self.input.setHistory(index) + self.input.execCmd() + + def flush(self): + pass + + def catchAllExceptions(self, catch=True): + """ + If True, the console will catch all unhandled exceptions and display the stack + trace. Each exception caught clears the last. + """ + self.ui.catchAllExceptionsBtn.setChecked(catch) + if catch: + self.ui.catchNextExceptionBtn.setChecked(False) + self.enableExceptionHandling() + self.ui.exceptionBtn.setChecked(True) + else: + self.disableExceptionHandling() + + def catchNextException(self, catch=True): + """ + If True, the console will catch the next unhandled exception and display the stack + trace. + """ + self.ui.catchNextExceptionBtn.setChecked(catch) + if catch: + self.ui.catchAllExceptionsBtn.setChecked(False) + self.enableExceptionHandling() + self.ui.exceptionBtn.setChecked(True) + else: + self.disableExceptionHandling() + + def enableExceptionHandling(self): + exceptionHandling.register(self.exceptionHandler) + self.updateSysTrace() + + def disableExceptionHandling(self): + exceptionHandling.unregister(self.exceptionHandler) + self.updateSysTrace() + + def clearExceptionClicked(self): + self.currentTraceback = None + self.ui.exceptionInfoLabel.setText("[No current exception]") + self.ui.exceptionStackList.clear() + self.ui.clearExceptionBtn.setEnabled(False) + + def stackItemClicked(self, item): + pass + + def stackItemDblClicked(self, item): + editor = self.editor + if editor is None: + editor = getConfigOption('editorCommand') + if editor is None: + return + tb = self.currentFrame() + lineNum = tb.tb_lineno + fileName = tb.tb_frame.f_code.co_filename + subprocess.Popen(self.editor.format(fileName=fileName, lineNum=lineNum), shell=True) + + + #def allExceptionsHandler(self, *args): + #self.exceptionHandler(*args) + + #def nextExceptionHandler(self, *args): + #self.ui.catchNextExceptionBtn.setChecked(False) + #self.exceptionHandler(*args) + + def updateSysTrace(self): + ## Install or uninstall sys.settrace handler + + if not self.ui.catchNextExceptionBtn.isChecked() and not self.ui.catchAllExceptionsBtn.isChecked(): + if sys.gettrace() == self.systrace: + sys.settrace(None) + return + + if self.ui.onlyUncaughtCheck.isChecked(): + if sys.gettrace() == self.systrace: + sys.settrace(None) + else: + if sys.gettrace() is not None and sys.gettrace() != self.systrace: + self.ui.onlyUncaughtCheck.setChecked(False) + raise Exception("sys.settrace is in use; cannot monitor for caught exceptions.") + else: + sys.settrace(self.systrace) + + def exceptionHandler(self, excType, exc, tb): + if self.ui.catchNextExceptionBtn.isChecked(): + self.ui.catchNextExceptionBtn.setChecked(False) + elif not self.ui.catchAllExceptionsBtn.isChecked(): + return + + self.ui.clearExceptionBtn.setEnabled(True) + self.currentTraceback = tb + + excMessage = ''.join(traceback.format_exception_only(excType, exc)) + self.ui.exceptionInfoLabel.setText(excMessage) + self.ui.exceptionStackList.clear() + for index, line in enumerate(traceback.extract_tb(tb)): + self.ui.exceptionStackList.addItem('File "%s", line %s, in %s()\n %s' % line) + + def systrace(self, frame, event, arg): + if event == 'exception' and self.checkException(*arg): + self.exceptionHandler(*arg) + return self.systrace + + def checkException(self, excType, exc, tb): + ## Return True if the exception is interesting; False if it should be ignored. + + filename = tb.tb_frame.f_code.co_filename + function = tb.tb_frame.f_code.co_name + + filterStr = str(self.ui.filterText.text()) + if filterStr != '': + if isinstance(exc, Exception): + msg = exc.message + elif isinstance(exc, basestring): + msg = exc + else: + msg = repr(exc) + match = re.search(filterStr, "%s:%s:%s" % (filename, function, msg)) + return match is not None + + ## Go through a list of common exception points we like to ignore: + if excType is GeneratorExit or excType is StopIteration: + return False + if excType is KeyError: + if filename.endswith('python2.7/weakref.py') and function in ('__contains__', 'get'): + return False + if filename.endswith('python2.7/copy.py') and function == '_keep_alive': + return False + if excType is AttributeError: + if filename.endswith('python2.7/collections.py') and function == '__init__': + return False + if filename.endswith('numpy/core/fromnumeric.py') and function in ('all', '_wrapit', 'transpose', 'sum'): + return False + if filename.endswith('numpy/core/arrayprint.py') and function in ('_array2string'): + return False + if filename.endswith('MetaArray.py') and function == '__getattr__': + for name in ('__array_interface__', '__array_struct__', '__array__'): ## numpy looks for these when converting objects to array + if name in exc: + return False + if filename.endswith('flowchart/eq.py'): + return False + if filename.endswith('pyqtgraph/functions.py') and function == 'makeQImage': + return False + if excType is TypeError: + if filename.endswith('numpy/lib/function_base.py') and function == 'iterable': + return False + if excType is ZeroDivisionError: + if filename.endswith('python2.7/traceback.py'): + return False + + return True + diff --git a/papi/pyqtgraph/console/__init__.py b/papi/pyqtgraph/console/__init__.py new file mode 100644 index 00000000..16436abd --- /dev/null +++ b/papi/pyqtgraph/console/__init__.py @@ -0,0 +1 @@ +from .Console import ConsoleWidget \ No newline at end of file diff --git a/papi/pyqtgraph/console/template.ui b/papi/pyqtgraph/console/template.ui new file mode 100644 index 00000000..1a672c5e --- /dev/null +++ b/papi/pyqtgraph/console/template.ui @@ -0,0 +1,194 @@ + + + Form + + + + 0 + 0 + 694 + 497 + + + + Console + + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + + + + Monospace + + + + true + + + + + + + + + + + + History.. + + + true + + + + + + + Exceptions.. + + + true + + + + + + + + + + + Monospace + + + + + + Exception Handling + + + + 0 + + + 0 + + + 0 + + + + + false + + + Clear Exception + + + + + + + Show All Exceptions + + + true + + + + + + + Show Next Exception + + + true + + + + + + + Only Uncaught Exceptions + + + true + + + + + + + true + + + + + + + Run commands in selected stack frame + + + true + + + + + + + Exception Info + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Filter (regex): + + + + + + + + + + + + + + + CmdInput + QLineEdit +
.CmdInput
+
+
+ + +
diff --git a/papi/pyqtgraph/console/template_pyqt.py b/papi/pyqtgraph/console/template_pyqt.py new file mode 100644 index 00000000..354fb1d6 --- /dev/null +++ b/papi/pyqtgraph/console/template_pyqt.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'template.ui' +# +# Created: Fri May 02 18:55:28 2014 +# by: PyQt4 UI code generator 4.10.4 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(694, 497) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName(_fromUtf8("splitter")) + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) + self.verticalLayout = QtGui.QVBoxLayout(self.layoutWidget) + self.verticalLayout.setMargin(0) + self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) + self.output = QtGui.QPlainTextEdit(self.layoutWidget) + font = QtGui.QFont() + font.setFamily(_fromUtf8("Monospace")) + self.output.setFont(font) + self.output.setReadOnly(True) + self.output.setObjectName(_fromUtf8("output")) + self.verticalLayout.addWidget(self.output) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.input = CmdInput(self.layoutWidget) + self.input.setObjectName(_fromUtf8("input")) + self.horizontalLayout.addWidget(self.input) + self.historyBtn = QtGui.QPushButton(self.layoutWidget) + self.historyBtn.setCheckable(True) + self.historyBtn.setObjectName(_fromUtf8("historyBtn")) + self.horizontalLayout.addWidget(self.historyBtn) + self.exceptionBtn = QtGui.QPushButton(self.layoutWidget) + self.exceptionBtn.setCheckable(True) + self.exceptionBtn.setObjectName(_fromUtf8("exceptionBtn")) + self.horizontalLayout.addWidget(self.exceptionBtn) + self.verticalLayout.addLayout(self.horizontalLayout) + self.historyList = QtGui.QListWidget(self.splitter) + font = QtGui.QFont() + font.setFamily(_fromUtf8("Monospace")) + self.historyList.setFont(font) + self.historyList.setObjectName(_fromUtf8("historyList")) + self.exceptionGroup = QtGui.QGroupBox(self.splitter) + self.exceptionGroup.setObjectName(_fromUtf8("exceptionGroup")) + self.gridLayout_2 = QtGui.QGridLayout(self.exceptionGroup) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.clearExceptionBtn = QtGui.QPushButton(self.exceptionGroup) + self.clearExceptionBtn.setEnabled(False) + self.clearExceptionBtn.setObjectName(_fromUtf8("clearExceptionBtn")) + self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1) + self.catchAllExceptionsBtn = QtGui.QPushButton(self.exceptionGroup) + self.catchAllExceptionsBtn.setCheckable(True) + self.catchAllExceptionsBtn.setObjectName(_fromUtf8("catchAllExceptionsBtn")) + self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) + self.catchNextExceptionBtn = QtGui.QPushButton(self.exceptionGroup) + self.catchNextExceptionBtn.setCheckable(True) + self.catchNextExceptionBtn.setObjectName(_fromUtf8("catchNextExceptionBtn")) + self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) + self.onlyUncaughtCheck = QtGui.QCheckBox(self.exceptionGroup) + self.onlyUncaughtCheck.setChecked(True) + self.onlyUncaughtCheck.setObjectName(_fromUtf8("onlyUncaughtCheck")) + self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1) + self.exceptionStackList = QtGui.QListWidget(self.exceptionGroup) + self.exceptionStackList.setAlternatingRowColors(True) + self.exceptionStackList.setObjectName(_fromUtf8("exceptionStackList")) + self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7) + self.runSelectedFrameCheck = QtGui.QCheckBox(self.exceptionGroup) + self.runSelectedFrameCheck.setChecked(True) + self.runSelectedFrameCheck.setObjectName(_fromUtf8("runSelectedFrameCheck")) + self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7) + self.exceptionInfoLabel = QtGui.QLabel(self.exceptionGroup) + self.exceptionInfoLabel.setObjectName(_fromUtf8("exceptionInfoLabel")) + self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7) + spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem, 0, 5, 1, 1) + self.label = QtGui.QLabel(self.exceptionGroup) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1) + self.filterText = QtGui.QLineEdit(self.exceptionGroup) + self.filterText.setObjectName(_fromUtf8("filterText")) + self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(_translate("Form", "Console", None)) + self.historyBtn.setText(_translate("Form", "History..", None)) + self.exceptionBtn.setText(_translate("Form", "Exceptions..", None)) + self.exceptionGroup.setTitle(_translate("Form", "Exception Handling", None)) + self.clearExceptionBtn.setText(_translate("Form", "Clear Exception", None)) + self.catchAllExceptionsBtn.setText(_translate("Form", "Show All Exceptions", None)) + self.catchNextExceptionBtn.setText(_translate("Form", "Show Next Exception", None)) + self.onlyUncaughtCheck.setText(_translate("Form", "Only Uncaught Exceptions", None)) + self.runSelectedFrameCheck.setText(_translate("Form", "Run commands in selected stack frame", None)) + self.exceptionInfoLabel.setText(_translate("Form", "Exception Info", None)) + self.label.setText(_translate("Form", "Filter (regex):", None)) + +from .CmdInput import CmdInput diff --git a/papi/pyqtgraph/console/template_pyside.py b/papi/pyqtgraph/console/template_pyside.py new file mode 100644 index 00000000..2db8ed95 --- /dev/null +++ b/papi/pyqtgraph/console/template_pyside.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/console/template.ui' +# +# Created: Mon Dec 23 10:10:53 2013 +# by: pyside-uic 0.2.14 running on PySide 1.1.2 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(710, 497) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName("splitter") + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName("layoutWidget") + self.verticalLayout = QtGui.QVBoxLayout(self.layoutWidget) + self.verticalLayout.setContentsMargins(0, 0, 0, 0) + self.verticalLayout.setObjectName("verticalLayout") + self.output = QtGui.QPlainTextEdit(self.layoutWidget) + font = QtGui.QFont() + font.setFamily("Monospace") + self.output.setFont(font) + self.output.setReadOnly(True) + self.output.setObjectName("output") + self.verticalLayout.addWidget(self.output) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setObjectName("horizontalLayout") + self.input = CmdInput(self.layoutWidget) + self.input.setObjectName("input") + self.horizontalLayout.addWidget(self.input) + self.historyBtn = QtGui.QPushButton(self.layoutWidget) + self.historyBtn.setCheckable(True) + self.historyBtn.setObjectName("historyBtn") + self.horizontalLayout.addWidget(self.historyBtn) + self.exceptionBtn = QtGui.QPushButton(self.layoutWidget) + self.exceptionBtn.setCheckable(True) + self.exceptionBtn.setObjectName("exceptionBtn") + self.horizontalLayout.addWidget(self.exceptionBtn) + self.verticalLayout.addLayout(self.horizontalLayout) + self.historyList = QtGui.QListWidget(self.splitter) + font = QtGui.QFont() + font.setFamily("Monospace") + self.historyList.setFont(font) + self.historyList.setObjectName("historyList") + self.exceptionGroup = QtGui.QGroupBox(self.splitter) + self.exceptionGroup.setObjectName("exceptionGroup") + self.gridLayout_2 = QtGui.QGridLayout(self.exceptionGroup) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setContentsMargins(-1, 0, -1, 0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.catchAllExceptionsBtn = QtGui.QPushButton(self.exceptionGroup) + self.catchAllExceptionsBtn.setCheckable(True) + self.catchAllExceptionsBtn.setObjectName("catchAllExceptionsBtn") + self.gridLayout_2.addWidget(self.catchAllExceptionsBtn, 0, 1, 1, 1) + self.catchNextExceptionBtn = QtGui.QPushButton(self.exceptionGroup) + self.catchNextExceptionBtn.setCheckable(True) + self.catchNextExceptionBtn.setObjectName("catchNextExceptionBtn") + self.gridLayout_2.addWidget(self.catchNextExceptionBtn, 0, 0, 1, 1) + self.onlyUncaughtCheck = QtGui.QCheckBox(self.exceptionGroup) + self.onlyUncaughtCheck.setChecked(True) + self.onlyUncaughtCheck.setObjectName("onlyUncaughtCheck") + self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 2, 1, 1) + self.exceptionStackList = QtGui.QListWidget(self.exceptionGroup) + self.exceptionStackList.setAlternatingRowColors(True) + self.exceptionStackList.setObjectName("exceptionStackList") + self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 5) + self.runSelectedFrameCheck = QtGui.QCheckBox(self.exceptionGroup) + self.runSelectedFrameCheck.setChecked(True) + self.runSelectedFrameCheck.setObjectName("runSelectedFrameCheck") + self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 5) + self.exceptionInfoLabel = QtGui.QLabel(self.exceptionGroup) + self.exceptionInfoLabel.setObjectName("exceptionInfoLabel") + self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 5) + self.clearExceptionBtn = QtGui.QPushButton(self.exceptionGroup) + self.clearExceptionBtn.setEnabled(False) + self.clearExceptionBtn.setObjectName("clearExceptionBtn") + self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 4, 1, 1) + spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum) + self.gridLayout_2.addItem(spacerItem, 0, 3, 1, 1) + self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Console", None, QtGui.QApplication.UnicodeUTF8)) + self.historyBtn.setText(QtGui.QApplication.translate("Form", "History..", None, QtGui.QApplication.UnicodeUTF8)) + self.exceptionBtn.setText(QtGui.QApplication.translate("Form", "Exceptions..", None, QtGui.QApplication.UnicodeUTF8)) + self.exceptionGroup.setTitle(QtGui.QApplication.translate("Form", "Exception Handling", None, QtGui.QApplication.UnicodeUTF8)) + self.catchAllExceptionsBtn.setText(QtGui.QApplication.translate("Form", "Show All Exceptions", None, QtGui.QApplication.UnicodeUTF8)) + self.catchNextExceptionBtn.setText(QtGui.QApplication.translate("Form", "Show Next Exception", None, QtGui.QApplication.UnicodeUTF8)) + self.onlyUncaughtCheck.setText(QtGui.QApplication.translate("Form", "Only Uncaught Exceptions", None, QtGui.QApplication.UnicodeUTF8)) + self.runSelectedFrameCheck.setText(QtGui.QApplication.translate("Form", "Run commands in selected stack frame", None, QtGui.QApplication.UnicodeUTF8)) + self.exceptionInfoLabel.setText(QtGui.QApplication.translate("Form", "Exception Info", None, QtGui.QApplication.UnicodeUTF8)) + self.clearExceptionBtn.setText(QtGui.QApplication.translate("Form", "Clear Exception", None, QtGui.QApplication.UnicodeUTF8)) + +from .CmdInput import CmdInput diff --git a/papi/pyqtgraph/debug.py b/papi/pyqtgraph/debug.py new file mode 100644 index 00000000..57c71bc8 --- /dev/null +++ b/papi/pyqtgraph/debug.py @@ -0,0 +1,1169 @@ +# -*- coding: utf-8 -*- +""" +debug.py - Functions to aid in debugging +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from __future__ import print_function + +import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile, threading +from . import ptime +from numpy import ndarray +from .Qt import QtCore, QtGui +from .util.mutex import Mutex +from .util import cprint + +__ftraceDepth = 0 +def ftrace(func): + """Decorator used for marking the beginning and end of function calls. + Automatically indents nested calls. + """ + def w(*args, **kargs): + global __ftraceDepth + pfx = " " * __ftraceDepth + print(pfx + func.__name__ + " start") + __ftraceDepth += 1 + try: + rv = func(*args, **kargs) + finally: + __ftraceDepth -= 1 + print(pfx + func.__name__ + " done") + return rv + return w + + +class Tracer(object): + """ + Prints every function enter/exit. Useful for debugging crashes / lockups. + """ + def __init__(self): + self.count = 0 + self.stack = [] + + def trace(self, frame, event, arg): + self.count += 1 + # If it has been a long time since we saw the top of the stack, + # print a reminder + if self.count % 1000 == 0: + print("----- current stack: -----") + for line in self.stack: + print(line) + if event == 'call': + line = " " * len(self.stack) + ">> " + self.frameInfo(frame) + print(line) + self.stack.append(line) + elif event == 'return': + self.stack.pop() + line = " " * len(self.stack) + "<< " + self.frameInfo(frame) + print(line) + if len(self.stack) == 0: + self.count = 0 + + return self.trace + + def stop(self): + sys.settrace(None) + + def start(self): + sys.settrace(self.trace) + + def frameInfo(self, fr): + filename = fr.f_code.co_filename + funcname = fr.f_code.co_name + lineno = fr.f_lineno + callfr = sys._getframe(3) + callline = "%s %d" % (callfr.f_code.co_name, callfr.f_lineno) + args, _, _, value_dict = inspect.getargvalues(fr) + if len(args) and args[0] == 'self': + instance = value_dict.get('self', None) + if instance is not None: + cls = getattr(instance, '__class__', None) + if cls is not None: + funcname = cls.__name__ + "." + funcname + return "%s: %s %s: %s" % (callline, filename, lineno, funcname) + +def warnOnException(func): + """Decorator which catches/ignores exceptions and prints a stack trace.""" + def w(*args, **kwds): + try: + func(*args, **kwds) + except: + printExc('Ignored exception:') + return w + +def getExc(indent=4, prefix='| ', skip=1): + lines = (traceback.format_stack()[:-skip] + + [" ---- exception caught ---->\n"] + + traceback.format_tb(sys.exc_info()[2]) + + traceback.format_exception_only(*sys.exc_info()[:2])) + lines2 = [] + for l in lines: + lines2.extend(l.strip('\n').split('\n')) + lines3 = [" "*indent + prefix + l for l in lines2] + return '\n'.join(lines3) + + +def printExc(msg='', indent=4, prefix='|'): + """Print an error message followed by an indented exception backtrace + (This function is intended to be called within except: blocks)""" + exc = getExc(indent, prefix + ' ', skip=2) + print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) + print(" "*indent + prefix + '='*30 + '>>') + print(exc) + print(" "*indent + prefix + '='*30 + '<<') + +def printTrace(msg='', indent=4, prefix='|'): + """Print an error message followed by an indented stack trace""" + trace = backtrace(1) + #exc = getExc(indent, prefix + ' ') + print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) + print(" "*indent + prefix + '='*30 + '>>') + for line in trace.split('\n'): + print(" "*indent + prefix + " " + line) + print(" "*indent + prefix + '='*30 + '<<') + + +def backtrace(skip=0): + return ''.join(traceback.format_stack()[:-(skip+1)]) + + +def listObjs(regex='Q', typ=None): + """List all objects managed by python gc with class name matching regex. + Finds 'Q...' classes by default.""" + if typ is not None: + return [x for x in gc.get_objects() if isinstance(x, typ)] + else: + return [x for x in gc.get_objects() if re.match(regex, type(x).__name__)] + + + +def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ignore=None): + """Determine all paths of object references from startObj to endObj""" + refs = [] + if path is None: + path = [endObj] + if ignore is None: + ignore = {} + ignore[id(sys._getframe())] = None + ignore[id(path)] = None + ignore[id(seen)] = None + prefix = " "*(8-maxLen) + #print prefix + str(map(type, path)) + prefix += " " + if restart: + #gc.collect() + seen.clear() + gc.collect() + newRefs = [r for r in gc.get_referrers(endObj) if id(r) not in ignore] + ignore[id(newRefs)] = None + #fo = allFrameObjs() + #newRefs = [] + #for r in gc.get_referrers(endObj): + #try: + #if r not in fo: + #newRefs.append(r) + #except: + #newRefs.append(r) + + for r in newRefs: + #print prefix+"->"+str(type(r)) + if type(r).__name__ in ['frame', 'function', 'listiterator']: + #print prefix+" FRAME" + continue + try: + if any([r is x for x in path]): + #print prefix+" LOOP", objChainString([r]+path) + continue + except: + print(r) + print(path) + raise + if r is startObj: + refs.append([r]) + print(refPathString([startObj]+path)) + continue + if maxLen == 0: + #print prefix+" END:", objChainString([r]+path) + continue + ## See if we have already searched this node. + ## If not, recurse. + tree = None + try: + cache = seen[id(r)] + if cache[0] >= maxLen: + tree = cache[1] + for p in tree: + print(refPathString(p+path)) + except KeyError: + pass + + ignore[id(tree)] = None + if tree is None: + tree = findRefPath(startObj, r, maxLen-1, restart=False, path=[r]+path, ignore=ignore) + seen[id(r)] = [maxLen, tree] + ## integrate any returned results + if len(tree) == 0: + #print prefix+" EMPTY TREE" + continue + else: + for p in tree: + refs.append(p+[r]) + #seen[id(r)] = [maxLen, refs] + return refs + + +def objString(obj): + """Return a short but descriptive string for any object""" + try: + if type(obj) in [int, float]: + return str(obj) + elif isinstance(obj, dict): + if len(obj) > 5: + return "" % (",".join(list(obj.keys())[:5])) + else: + return "" % (",".join(list(obj.keys()))) + elif isinstance(obj, str): + if len(obj) > 50: + return '"%s..."' % obj[:50] + else: + return obj[:] + elif isinstance(obj, ndarray): + return "" % (str(obj.dtype), str(obj.shape)) + elif hasattr(obj, '__len__'): + if len(obj) > 5: + return "<%s [%s,...]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj[:5]])) + else: + return "<%s [%s]>" % (type(obj).__name__, ",".join([type(o).__name__ for o in obj])) + else: + return "<%s %s>" % (type(obj).__name__, obj.__class__.__name__) + except: + return str(type(obj)) + +def refPathString(chain): + """Given a list of adjacent objects in a reference path, print the 'natural' path + names (ie, attribute names, keys, and indexes) that follow from one object to the next .""" + s = objString(chain[0]) + i = 0 + while i < len(chain)-1: + #print " -> ", i + i += 1 + o1 = chain[i-1] + o2 = chain[i] + cont = False + if isinstance(o1, list) or isinstance(o1, tuple): + if any([o2 is x for x in o1]): + s += "[%d]" % o1.index(o2) + continue + #print " not list" + if isinstance(o2, dict) and hasattr(o1, '__dict__') and o2 == o1.__dict__: + i += 1 + if i >= len(chain): + s += ".__dict__" + continue + o3 = chain[i] + for k in o2: + if o2[k] is o3: + s += '.%s' % k + cont = True + continue + #print " not __dict__" + if isinstance(o1, dict): + try: + if o2 in o1: + s += "[key:%s]" % objString(o2) + continue + except TypeError: + pass + for k in o1: + if o1[k] is o2: + s += "[%s]" % objString(k) + cont = True + continue + #print " not dict" + #for k in dir(o1): ## Not safe to request attributes like this. + #if getattr(o1, k) is o2: + #s += ".%s" % k + #cont = True + #continue + #print " not attr" + if cont: + continue + s += " ? " + sys.stdout.flush() + return s + + +def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False): + """Guess how much memory an object is using""" + ignoreTypes = ['MethodType', 'UnboundMethodType', 'BuiltinMethodType', 'FunctionType', 'BuiltinFunctionType'] + ignoreTypes = [getattr(types, key) for key in ignoreTypes if hasattr(types, key)] + ignoreRegex = re.compile('(method-wrapper|Flag|ItemChange|Option|Mode)') + + + if ignore is None: + ignore = {} + + indent = ' '*depth + + try: + hash(obj) + hsh = obj + except: + hsh = "%s:%d" % (str(type(obj)), id(obj)) + + if hsh in ignore: + return 0 + ignore[hsh] = 1 + + try: + size = sys.getsizeof(obj) + except TypeError: + size = 0 + + if isinstance(obj, ndarray): + try: + size += len(obj.data) + except: + pass + + + if recursive: + if type(obj) in [list, tuple]: + if verbose: + print(indent+"list:") + for o in obj: + s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) + if verbose: + print(indent+' +', s) + size += s + elif isinstance(obj, dict): + if verbose: + print(indent+"list:") + for k in obj: + s = objectSize(obj[k], ignore=ignore, verbose=verbose, depth=depth+1) + if verbose: + print(indent+' +', k, s) + size += s + #elif isinstance(obj, QtCore.QObject): + #try: + #childs = obj.children() + #if verbose: + #print indent+"Qt children:" + #for ch in childs: + #s = objectSize(obj, ignore=ignore, verbose=verbose, depth=depth+1) + #size += s + #if verbose: + #print indent + ' +', ch.objectName(), s + + #except: + #pass + #if isinstance(obj, types.InstanceType): + gc.collect() + if verbose: + print(indent+'attrs:') + for k in dir(obj): + if k in ['__dict__']: + continue + o = getattr(obj, k) + if type(o) in ignoreTypes: + continue + strtyp = str(type(o)) + if ignoreRegex.search(strtyp): + continue + #if isinstance(o, types.ObjectType) and strtyp == "": + #continue + + #if verbose: + #print indent, k, '?' + refs = [r for r in gc.get_referrers(o) if type(r) != types.FrameType] + if len(refs) == 1: + s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) + size += s + if verbose: + print(indent + " +", k, s) + #else: + #if verbose: + #print indent + ' -', k, len(refs) + return size + +class GarbageWatcher(object): + """ + Convenient dictionary for holding weak references to objects. + Mainly used to check whether the objects have been collect yet or not. + + Example: + gw = GarbageWatcher() + gw['objName'] = obj + gw['objName2'] = obj2 + gw.check() + + + """ + def __init__(self): + self.objs = weakref.WeakValueDictionary() + self.allNames = [] + + def add(self, obj, name): + self.objs[name] = obj + self.allNames.append(name) + + def __setitem__(self, name, obj): + self.add(obj, name) + + def check(self): + """Print a list of all watched objects and whether they have been collected.""" + gc.collect() + dead = self.allNames[:] + alive = [] + for k in self.objs: + dead.remove(k) + alive.append(k) + print("Deleted objects:", dead) + print("Live objects:", alive) + + def __getitem__(self, item): + return self.objs[item] + + + + +class Profiler(object): + """Simple profiler allowing measurement of multiple time intervals. + + By default, profilers are disabled. To enable profiling, set the + environment variable `PYQTGRAPHPROFILE` to a comma-separated list of + fully-qualified names of profiled functions. + + Calling a profiler registers a message (defaulting to an increasing + counter) that contains the time elapsed since the last call. When the + profiler is about to be garbage-collected, the messages are passed to the + outer profiler if one is running, or printed to stdout otherwise. + + If `delayed` is set to False, messages are immediately printed instead. + + Example: + def function(...): + profiler = Profiler() + ... do stuff ... + profiler('did stuff') + ... do other stuff ... + profiler('did other stuff') + # profiler is garbage-collected and flushed at function end + + If this function is a method of class C, setting `PYQTGRAPHPROFILE` to + "C.function" (without the module name) will enable this profiler. + + For regular functions, use the qualified name of the function, stripping + only the initial "pyqtgraph." prefix from the module. + """ + + _profilers = os.environ.get("PYQTGRAPHPROFILE", None) + _profilers = _profilers.split(",") if _profilers is not None else [] + + _depth = 0 + _msgs = [] + disable = False # set this flag to disable all or individual profilers at runtime + + class DisabledProfiler(object): + def __init__(self, *args, **kwds): + pass + def __call__(self, *args): + pass + def finish(self): + pass + def mark(self, msg=None): + pass + _disabledProfiler = DisabledProfiler() + + def __new__(cls, msg=None, disabled='env', delayed=True): + """Optionally create a new profiler based on caller's qualname. + """ + if disabled is True or (disabled == 'env' and len(cls._profilers) == 0): + return cls._disabledProfiler + + # determine the qualified name of the caller function + caller_frame = sys._getframe(1) + try: + caller_object_type = type(caller_frame.f_locals["self"]) + except KeyError: # we are in a regular function + qualifier = caller_frame.f_globals["__name__"].split(".", 1)[1] + else: # we are in a method + qualifier = caller_object_type.__name__ + func_qualname = qualifier + "." + caller_frame.f_code.co_name + if disabled == 'env' and func_qualname not in cls._profilers: # don't do anything + return cls._disabledProfiler + # create an actual profiling object + cls._depth += 1 + obj = super(Profiler, cls).__new__(cls) + obj._name = msg or func_qualname + obj._delayed = delayed + obj._markCount = 0 + obj._finished = False + obj._firstTime = obj._lastTime = ptime.time() + obj._newMsg("> Entering " + obj._name) + return obj + + def __call__(self, msg=None): + """Register or print a new message with timing information. + """ + if self.disable: + return + if msg is None: + msg = str(self._markCount) + self._markCount += 1 + newTime = ptime.time() + self._newMsg(" %s: %0.4f ms", + msg, (newTime - self._lastTime) * 1000) + self._lastTime = newTime + + def mark(self, msg=None): + self(msg) + + def _newMsg(self, msg, *args): + msg = " " * (self._depth - 1) + msg + if self._delayed: + self._msgs.append((msg, args)) + else: + self.flush() + print(msg % args) + + def __del__(self): + self.finish() + + def finish(self, msg=None): + """Add a final message; flush the message list if no parent profiler. + """ + if self._finished or self.disable: + return + self._finished = True + if msg is not None: + self(msg) + self._newMsg("< Exiting %s, total time: %0.4f ms", + self._name, (ptime.time() - self._firstTime) * 1000) + type(self)._depth -= 1 + if self._depth < 1: + self.flush() + + def flush(self): + if self._msgs: + print("\n".join([m[0]%m[1] for m in self._msgs])) + type(self)._msgs = [] + + +def profile(code, name='profile_run', sort='cumulative', num=30): + """Common-use for cProfile""" + cProfile.run(code, name) + stats = pstats.Stats(name) + stats.sort_stats(sort) + stats.print_stats(num) + return stats + + + +#### Code for listing (nearly) all objects in the known universe +#### http://utcc.utoronto.ca/~cks/space/blog/python/GetAllObjects +# Recursively expand slist's objects +# into olist, using seen to track +# already processed objects. +def _getr(slist, olist, first=True): + i = 0 + for e in slist: + + oid = id(e) + typ = type(e) + if oid in olist or typ is int: ## or e in olist: ## since we're excluding all ints, there is no longer a need to check for olist keys + continue + olist[oid] = e + if first and (i%1000) == 0: + gc.collect() + tl = gc.get_referents(e) + if tl: + _getr(tl, olist, first=False) + i += 1 +# The public function. +def get_all_objects(): + """Return a list of all live Python objects (excluding int and long), not including the list itself.""" + gc.collect() + gcl = gc.get_objects() + olist = {} + _getr(gcl, olist) + + del olist[id(olist)] + del olist[id(gcl)] + del olist[id(sys._getframe())] + return olist + + +def lookup(oid, objects=None): + """Return an object given its ID, if it exists.""" + if objects is None: + objects = get_all_objects() + return objects[oid] + + + + +class ObjTracker(object): + """ + Tracks all objects under the sun, reporting the changes between snapshots: what objects are created, deleted, and persistent. + This class is very useful for tracking memory leaks. The class goes to great (but not heroic) lengths to avoid tracking + its own internal objects. + + Example: + ot = ObjTracker() # takes snapshot of currently existing objects + ... do stuff ... + ot.diff() # prints lists of objects created and deleted since ot was initialized + ... do stuff ... + ot.diff() # prints lists of objects created and deleted since last call to ot.diff() + # also prints list of items that were created since initialization AND have not been deleted yet + # (if done correctly, this list can tell you about objects that were leaked) + + arrays = ot.findPersistent('ndarray') ## returns all objects matching 'ndarray' (string match, not instance checking) + ## that were considered persistent when the last diff() was run + + describeObj(arrays[0]) ## See if we can determine who has references to this array + """ + + + allObjs = {} ## keep track of all objects created and stored within class instances + allObjs[id(allObjs)] = None + + def __init__(self): + self.startRefs = {} ## list of objects that exist when the tracker is initialized {oid: weakref} + ## (If it is not possible to weakref the object, then the value is None) + self.startCount = {} + self.newRefs = {} ## list of objects that have been created since initialization + self.persistentRefs = {} ## list of objects considered 'persistent' when the last diff() was called + self.objTypes = {} + + ObjTracker.allObjs[id(self)] = None + self.objs = [self.__dict__, self.startRefs, self.startCount, self.newRefs, self.persistentRefs, self.objTypes] + self.objs.append(self.objs) + for v in self.objs: + ObjTracker.allObjs[id(v)] = None + + self.start() + + def findNew(self, regex): + """Return all objects matching regex that were considered 'new' when the last diff() was run.""" + return self.findTypes(self.newRefs, regex) + + def findPersistent(self, regex): + """Return all objects matching regex that were considered 'persistent' when the last diff() was run.""" + return self.findTypes(self.persistentRefs, regex) + + + def start(self): + """ + Remember the current set of objects as the comparison for all future calls to diff() + Called automatically on init, but can be called manually as well. + """ + refs, count, objs = self.collect() + for r in self.startRefs: + self.forgetRef(self.startRefs[r]) + self.startRefs.clear() + self.startRefs.update(refs) + for r in refs: + self.rememberRef(r) + self.startCount.clear() + self.startCount.update(count) + #self.newRefs.clear() + #self.newRefs.update(refs) + + def diff(self, **kargs): + """ + Compute all differences between the current object set and the reference set. + Print a set of reports for created, deleted, and persistent objects + """ + refs, count, objs = self.collect() ## refs contains the list of ALL objects + + ## Which refs have disappeared since call to start() (these are only displayed once, then forgotten.) + delRefs = {} + for i in list(self.startRefs.keys()): + if i not in refs: + delRefs[i] = self.startRefs[i] + del self.startRefs[i] + self.forgetRef(delRefs[i]) + for i in list(self.newRefs.keys()): + if i not in refs: + delRefs[i] = self.newRefs[i] + del self.newRefs[i] + self.forgetRef(delRefs[i]) + #print "deleted:", len(delRefs) + + ## Which refs have appeared since call to start() or diff() + persistentRefs = {} ## created since start(), but before last diff() + createRefs = {} ## created since last diff() + for o in refs: + if o not in self.startRefs: + if o not in self.newRefs: + createRefs[o] = refs[o] ## object has been created since last diff() + else: + persistentRefs[o] = refs[o] ## object has been created since start(), but before last diff() (persistent) + #print "new:", len(newRefs) + + ## self.newRefs holds the entire set of objects created since start() + for r in self.newRefs: + self.forgetRef(self.newRefs[r]) + self.newRefs.clear() + self.newRefs.update(persistentRefs) + self.newRefs.update(createRefs) + for r in self.newRefs: + self.rememberRef(self.newRefs[r]) + #print "created:", len(createRefs) + + ## self.persistentRefs holds all objects considered persistent. + self.persistentRefs.clear() + self.persistentRefs.update(persistentRefs) + + + print("----------- Count changes since start: ----------") + c1 = count.copy() + for k in self.startCount: + c1[k] = c1.get(k, 0) - self.startCount[k] + typs = list(c1.keys()) + #typs.sort(lambda a,b: cmp(c1[a], c1[b])) + typs.sort(key=lambda a: c1[a]) + for t in typs: + if c1[t] == 0: + continue + num = "%d" % c1[t] + print(" " + num + " "*(10-len(num)) + str(t)) + + print("----------- %d Deleted since last diff: ------------" % len(delRefs)) + self.report(delRefs, objs, **kargs) + print("----------- %d Created since last diff: ------------" % len(createRefs)) + self.report(createRefs, objs, **kargs) + print("----------- %d Created since start (persistent): ------------" % len(persistentRefs)) + self.report(persistentRefs, objs, **kargs) + + + def __del__(self): + self.startRefs.clear() + self.startCount.clear() + self.newRefs.clear() + self.persistentRefs.clear() + + del ObjTracker.allObjs[id(self)] + for v in self.objs: + del ObjTracker.allObjs[id(v)] + + @classmethod + def isObjVar(cls, o): + return type(o) is cls or id(o) in cls.allObjs + + def collect(self): + print("Collecting list of all objects...") + gc.collect() + objs = get_all_objects() + frame = sys._getframe() + del objs[id(frame)] ## ignore the current frame + del objs[id(frame.f_code)] + + ignoreTypes = [int] + refs = {} + count = {} + for k in objs: + o = objs[k] + typ = type(o) + oid = id(o) + if ObjTracker.isObjVar(o) or typ in ignoreTypes: + continue + + try: + ref = weakref.ref(obj) + except: + ref = None + refs[oid] = ref + typ = type(o) + typStr = typeStr(o) + self.objTypes[oid] = typStr + ObjTracker.allObjs[id(typStr)] = None + count[typ] = count.get(typ, 0) + 1 + + print("All objects: %d Tracked objects: %d" % (len(objs), len(refs))) + return refs, count, objs + + def forgetRef(self, ref): + if ref is not None: + del ObjTracker.allObjs[id(ref)] + + def rememberRef(self, ref): + ## Record the address of the weakref object so it is not included in future object counts. + if ref is not None: + ObjTracker.allObjs[id(ref)] = None + + + def lookup(self, oid, ref, objs=None): + if ref is None or ref() is None: + try: + obj = lookup(oid, objects=objs) + except: + obj = None + else: + obj = ref() + return obj + + + def report(self, refs, allobjs=None, showIDs=False): + if allobjs is None: + allobjs = get_all_objects() + + count = {} + rev = {} + for oid in refs: + obj = self.lookup(oid, refs[oid], allobjs) + if obj is None: + typ = "[del] " + self.objTypes[oid] + else: + typ = typeStr(obj) + if typ not in rev: + rev[typ] = [] + rev[typ].append(oid) + c = count.get(typ, [0,0]) + count[typ] = [c[0]+1, c[1]+objectSize(obj)] + typs = list(count.keys()) + #typs.sort(lambda a,b: cmp(count[a][1], count[b][1])) + typs.sort(key=lambda a: count[a][1]) + + for t in typs: + line = " %d\t%d\t%s" % (count[t][0], count[t][1], t) + if showIDs: + line += "\t"+",".join(map(str,rev[t])) + print(line) + + def findTypes(self, refs, regex): + allObjs = get_all_objects() + ids = {} + objs = [] + r = re.compile(regex) + for k in refs: + if r.search(self.objTypes[k]): + objs.append(self.lookup(k, refs[k], allObjs)) + return objs + + + + +def describeObj(obj, depth=4, path=None, ignore=None): + """ + Trace all reference paths backward, printing a list of different ways this object can be accessed. + Attempts to answer the question "who has a reference to this object" + """ + if path is None: + path = [obj] + if ignore is None: + ignore = {} ## holds IDs of objects used within the function. + ignore[id(sys._getframe())] = None + ignore[id(path)] = None + gc.collect() + refs = gc.get_referrers(obj) + ignore[id(refs)] = None + printed=False + for ref in refs: + if id(ref) in ignore: + continue + if id(ref) in list(map(id, path)): + print("Cyclic reference: " + refPathString([ref]+path)) + printed = True + continue + newPath = [ref]+path + if len(newPath) >= depth: + refStr = refPathString(newPath) + if '[_]' not in refStr: ## ignore '_' references generated by the interactive shell + print(refStr) + printed = True + else: + describeObj(ref, depth, newPath, ignore) + printed = True + if not printed: + print("Dead end: " + refPathString(path)) + + + +def typeStr(obj): + """Create a more useful type string by making types report their class.""" + typ = type(obj) + if typ == getattr(types, 'InstanceType', None): + return "" % obj.__class__.__name__ + else: + return str(typ) + +def searchRefs(obj, *args): + """Pseudo-interactive function for tracing references backward. + **Arguments:** + + obj: The initial object from which to start searching + args: A set of string or int arguments. + each integer selects one of obj's referrers to be the new 'obj' + each string indicates an action to take on the current 'obj': + t: print the types of obj's referrers + l: print the lengths of obj's referrers (if they have __len__) + i: print the IDs of obj's referrers + o: print obj + ro: return obj + rr: return list of obj's referrers + + Examples:: + + searchRefs(obj, 't') ## Print types of all objects referring to obj + searchRefs(obj, 't', 0, 't') ## ..then select the first referrer and print the types of its referrers + searchRefs(obj, 't', 0, 't', 'l') ## ..also print lengths of the last set of referrers + searchRefs(obj, 0, 1, 'ro') ## Select index 0 from obj's referrer, then select index 1 from the next set of referrers, then return that object + + """ + ignore = {id(sys._getframe()): None} + gc.collect() + refs = gc.get_referrers(obj) + ignore[id(refs)] = None + refs = [r for r in refs if id(r) not in ignore] + for a in args: + + #fo = allFrameObjs() + #refs = [r for r in refs if r not in fo] + + if type(a) is int: + obj = refs[a] + gc.collect() + refs = gc.get_referrers(obj) + ignore[id(refs)] = None + refs = [r for r in refs if id(r) not in ignore] + elif a == 't': + print(list(map(typeStr, refs))) + elif a == 'i': + print(list(map(id, refs))) + elif a == 'l': + def slen(o): + if hasattr(o, '__len__'): + return len(o) + else: + return None + print(list(map(slen, refs))) + elif a == 'o': + print(obj) + elif a == 'ro': + return obj + elif a == 'rr': + return refs + +def allFrameObjs(): + """Return list of frame objects in current stack. Useful if you want to ignore these objects in refernece searches""" + f = sys._getframe() + objs = [] + while f is not None: + objs.append(f) + objs.append(f.f_code) + #objs.append(f.f_locals) + #objs.append(f.f_globals) + #objs.append(f.f_builtins) + f = f.f_back + return objs + + +def findObj(regex): + """Return a list of objects whose typeStr matches regex""" + allObjs = get_all_objects() + objs = [] + r = re.compile(regex) + for i in allObjs: + obj = allObjs[i] + if r.search(typeStr(obj)): + objs.append(obj) + return objs + + + +def listRedundantModules(): + """List modules that have been imported more than once via different paths.""" + mods = {} + for name, mod in sys.modules.items(): + if not hasattr(mod, '__file__'): + continue + mfile = os.path.abspath(mod.__file__) + if mfile[-1] == 'c': + mfile = mfile[:-1] + if mfile in mods: + print("module at %s has 2 names: %s, %s" % (mfile, name, mods[mfile])) + else: + mods[mfile] = name + + +def walkQObjectTree(obj, counts=None, verbose=False, depth=0): + """ + Walk through a tree of QObjects, doing nothing to them. + The purpose of this function is to find dead objects and generate a crash + immediately rather than stumbling upon them later. + Prints a count of the objects encountered, for fun. (or is it?) + """ + + if verbose: + print(" "*depth + typeStr(obj)) + report = False + if counts is None: + counts = {} + report = True + typ = str(type(obj)) + try: + counts[typ] += 1 + except KeyError: + counts[typ] = 1 + for child in obj.children(): + walkQObjectTree(child, counts, verbose, depth+1) + + return counts + +QObjCache = {} +def qObjectReport(verbose=False): + """Generate a report counting all QObjects and their types""" + global qObjCache + count = {} + for obj in findObj('PyQt'): + if isinstance(obj, QtCore.QObject): + oid = id(obj) + if oid not in QObjCache: + QObjCache[oid] = typeStr(obj) + " " + obj.objectName() + try: + QObjCache[oid] += " " + obj.parent().objectName() + QObjCache[oid] += " " + obj.text() + except: + pass + print("check obj", oid, str(QObjCache[oid])) + if obj.parent() is None: + walkQObjectTree(obj, count, verbose) + + typs = list(count.keys()) + typs.sort() + for t in typs: + print(count[t], "\t", t) + + +class PrintDetector(object): + """Find code locations that print to stdout.""" + def __init__(self): + self.stdout = sys.stdout + sys.stdout = self + + def remove(self): + sys.stdout = self.stdout + + def __del__(self): + self.remove() + + def write(self, x): + self.stdout.write(x) + traceback.print_stack() + + def flush(self): + self.stdout.flush() + + +def listQThreads(): + """Prints Thread IDs (Qt's, not OS's) for all QThreads.""" + thr = findObj('[Tt]hread') + thr = [t for t in thr if isinstance(t, QtCore.QThread)] + import sip + for t in thr: + print("--> ", t) + print(" Qt ID: 0x%x" % sip.unwrapinstance(t)) + + +def pretty(data, indent=''): + """Format nested dict/list/tuple structures into a more human-readable string + This function is a bit better than pprint for displaying OrderedDicts. + """ + ret = "" + ind2 = indent + " " + if isinstance(data, dict): + ret = indent+"{\n" + for k, v in data.iteritems(): + ret += ind2 + repr(k) + ": " + pretty(v, ind2).strip() + "\n" + ret += indent+"}\n" + elif isinstance(data, list) or isinstance(data, tuple): + s = repr(data) + if len(s) < 40: + ret += indent + s + else: + if isinstance(data, list): + d = '[]' + else: + d = '()' + ret = indent+d[0]+"\n" + for i, v in enumerate(data): + ret += ind2 + str(i) + ": " + pretty(v, ind2).strip() + "\n" + ret += indent+d[1]+"\n" + else: + ret += indent + repr(data) + return ret + + +class PeriodicTrace(object): + """ + Used to debug freezing by starting a new thread that reports on the + location of the main thread periodically. + """ + class ReportThread(QtCore.QThread): + def __init__(self): + self.frame = None + self.ind = 0 + self.lastInd = None + self.lock = Mutex() + QtCore.QThread.__init__(self) + + def notify(self, frame): + with self.lock: + self.frame = frame + self.ind += 1 + + def run(self): + while True: + time.sleep(1) + with self.lock: + if self.lastInd != self.ind: + print("== Trace %d: ==" % self.ind) + traceback.print_stack(self.frame) + self.lastInd = self.ind + + def __init__(self): + self.mainThread = threading.current_thread() + self.thread = PeriodicTrace.ReportThread() + self.thread.start() + sys.settrace(self.trace) + + def trace(self, frame, event, arg): + if threading.current_thread() is self.mainThread: # and 'threading' not in frame.f_code.co_filename: + self.thread.notify(frame) + # print("== Trace ==", event, arg) + # traceback.print_stack(frame) + return self.trace + + + +class ThreadColor(object): + """ + Wrapper on stdout/stderr that colors text by the current thread ID. + + *stream* must be 'stdout' or 'stderr'. + """ + colors = {} + lock = Mutex() + + def __init__(self, stream): + self.stream = getattr(sys, stream) + self.err = stream == 'stderr' + setattr(sys, stream, self) + + def write(self, msg): + with self.lock: + cprint.cprint(self.stream, self.color(), msg, -1, stderr=self.err) + + def flush(self): + with self.lock: + self.stream.flush() + + def color(self): + tid = threading.current_thread() + if tid not in self.colors: + c = (len(self.colors) % 15) + 1 + self.colors[tid] = c + return self.colors[tid] diff --git a/papi/pyqtgraph/dockarea/Container.py b/papi/pyqtgraph/dockarea/Container.py new file mode 100644 index 00000000..c3225edf --- /dev/null +++ b/papi/pyqtgraph/dockarea/Container.py @@ -0,0 +1,277 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtCore, QtGui +import weakref + +class Container(object): + #sigStretchChanged = QtCore.Signal() ## can't do this here; not a QObject. + + def __init__(self, area): + object.__init__(self) + self.area = area + self._container = None + self._stretch = (10, 10) + self.stretches = weakref.WeakKeyDictionary() + + def container(self): + return self._container + + def containerChanged(self, c): + self._container = c + + def type(self): + return None + + def insert(self, new, pos=None, neighbor=None): + # remove from existing parent first + new.setParent(None) + + if not isinstance(new, list): + new = [new] + if neighbor is None: + if pos == 'before': + index = 0 + else: + index = self.count() + else: + index = self.indexOf(neighbor) + if index == -1: + index = 0 + if pos == 'after': + index += 1 + + for n in new: + #print "change container", n, " -> ", self + n.containerChanged(self) + #print "insert", n, " -> ", self, index + self._insertItem(n, index) + index += 1 + n.sigStretchChanged.connect(self.childStretchChanged) + #print "child added", self + self.updateStretch() + + def apoptose(self, propagate=True): + ##if there is only one (or zero) item in this container, disappear. + cont = self._container + c = self.count() + if c > 1: + return + if self.count() == 1: ## if there is one item, give it to the parent container (unless this is the top) + if self is self.area.topContainer: + return + self.container().insert(self.widget(0), 'before', self) + #print "apoptose:", self + self.close() + if propagate and cont is not None: + cont.apoptose() + + def close(self): + self.area = None + self._container = None + self.setParent(None) + + def childEvent(self, ev): + ch = ev.child() + if ev.removed() and hasattr(ch, 'sigStretchChanged'): + #print "Child", ev.child(), "removed, updating", self + try: + ch.sigStretchChanged.disconnect(self.childStretchChanged) + except: + pass + self.updateStretch() + + def childStretchChanged(self): + #print "child", QtCore.QObject.sender(self), "changed shape, updating", self + self.updateStretch() + + def setStretch(self, x=None, y=None): + #print "setStretch", self, x, y + self._stretch = (x, y) + self.sigStretchChanged.emit() + + def updateStretch(self): + ###Set the stretch values for this container to reflect its contents + pass + + + def stretch(self): + """Return the stretch factors for this container""" + return self._stretch + + +class SplitContainer(Container, QtGui.QSplitter): + """Horizontal or vertical splitter with some changes: + - save/restore works correctly + """ + sigStretchChanged = QtCore.Signal() + + def __init__(self, area, orientation): + QtGui.QSplitter.__init__(self) + self.setOrientation(orientation) + Container.__init__(self, area) + #self.splitterMoved.connect(self.restretchChildren) + + def _insertItem(self, item, index): + self.insertWidget(index, item) + item.show() ## need to show since it may have been previously hidden by tab + + def saveState(self): + sizes = self.sizes() + if all([x == 0 for x in sizes]): + sizes = [10] * len(sizes) + return {'sizes': sizes} + + def restoreState(self, state): + sizes = state['sizes'] + self.setSizes(sizes) + for i in range(len(sizes)): + self.setStretchFactor(i, sizes[i]) + + def childEvent(self, ev): + QtGui.QSplitter.childEvent(self, ev) + Container.childEvent(self, ev) + + #def restretchChildren(self): + #sizes = self.sizes() + #tot = sum(sizes) + + + + +class HContainer(SplitContainer): + def __init__(self, area): + SplitContainer.__init__(self, area, QtCore.Qt.Horizontal) + + def type(self): + return 'horizontal' + + def updateStretch(self): + ##Set the stretch values for this container to reflect its contents + #print "updateStretch", self + x = 0 + y = 0 + sizes = [] + for i in range(self.count()): + wx, wy = self.widget(i).stretch() + x += wx + y = max(y, wy) + sizes.append(wx) + #print " child", self.widget(i), wx, wy + self.setStretch(x, y) + #print sizes + + tot = float(sum(sizes)) + if tot == 0: + scale = 1.0 + else: + scale = self.width() / tot + self.setSizes([int(s*scale) for s in sizes]) + + + +class VContainer(SplitContainer): + def __init__(self, area): + SplitContainer.__init__(self, area, QtCore.Qt.Vertical) + + def type(self): + return 'vertical' + + def updateStretch(self): + ##Set the stretch values for this container to reflect its contents + #print "updateStretch", self + x = 0 + y = 0 + sizes = [] + for i in range(self.count()): + wx, wy = self.widget(i).stretch() + y += wy + x = max(x, wx) + sizes.append(wy) + #print " child", self.widget(i), wx, wy + self.setStretch(x, y) + + #print sizes + tot = float(sum(sizes)) + if tot == 0: + scale = 1.0 + else: + scale = self.height() / tot + self.setSizes([int(s*scale) for s in sizes]) + + +class TContainer(Container, QtGui.QWidget): + sigStretchChanged = QtCore.Signal() + def __init__(self, area): + QtGui.QWidget.__init__(self) + Container.__init__(self, area) + self.layout = QtGui.QGridLayout() + self.layout.setSpacing(0) + self.layout.setContentsMargins(0,0,0,0) + self.setLayout(self.layout) + + self.hTabLayout = QtGui.QHBoxLayout() + self.hTabBox = QtGui.QWidget() + self.hTabBox.setLayout(self.hTabLayout) + self.hTabLayout.setSpacing(2) + self.hTabLayout.setContentsMargins(0,0,0,0) + self.layout.addWidget(self.hTabBox, 0, 1) + + self.stack = QtGui.QStackedWidget() + self.layout.addWidget(self.stack, 1, 1) + self.stack.childEvent = self.stackChildEvent + + + self.setLayout(self.layout) + for n in ['count', 'widget', 'indexOf']: + setattr(self, n, getattr(self.stack, n)) + + + def _insertItem(self, item, index): + if not isinstance(item, Dock.Dock): + raise Exception("Tab containers may hold only docks, not other containers.") + self.stack.insertWidget(index, item) + self.hTabLayout.insertWidget(index, item.label) + #QtCore.QObject.connect(item.label, QtCore.SIGNAL('clicked'), self.tabClicked) + item.label.sigClicked.connect(self.tabClicked) + self.tabClicked(item.label) + + def tabClicked(self, tab, ev=None): + if ev is None or ev.button() == QtCore.Qt.LeftButton: + for i in range(self.count()): + w = self.widget(i) + if w is tab.dock: + w.label.setDim(False) + self.stack.setCurrentIndex(i) + else: + w.label.setDim(True) + + def raiseDock(self, dock): + """Move *dock* to the top of the stack""" + self.stack.currentWidget().label.setDim(True) + self.stack.setCurrentWidget(dock) + dock.label.setDim(False) + + + def type(self): + return 'tab' + + def saveState(self): + return {'index': self.stack.currentIndex()} + + def restoreState(self, state): + self.stack.setCurrentIndex(state['index']) + + def updateStretch(self): + ##Set the stretch values for this container to reflect its contents + x = 0 + y = 0 + for i in range(self.count()): + wx, wy = self.widget(i).stretch() + x = max(x, wx) + y = max(y, wy) + self.setStretch(x, y) + + def stackChildEvent(self, ev): + QtGui.QStackedWidget.childEvent(self.stack, ev) + Container.childEvent(self, ev) + +from . import Dock diff --git a/papi/pyqtgraph/dockarea/Dock.py b/papi/pyqtgraph/dockarea/Dock.py new file mode 100644 index 00000000..28d4244b --- /dev/null +++ b/papi/pyqtgraph/dockarea/Dock.py @@ -0,0 +1,345 @@ +from ..Qt import QtCore, QtGui + +from .DockDrop import * +from ..widgets.VerticalLabel import VerticalLabel +from ..python2_3 import asUnicode + +class Dock(QtGui.QWidget, DockDrop): + + sigStretchChanged = QtCore.Signal() + + def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True, closable=False): + QtGui.QWidget.__init__(self) + DockDrop.__init__(self) + self.area = area + self.label = DockLabel(name, self, closable) + if closable: + self.label.sigCloseClicked.connect(self.close) + self.labelHidden = False + self.moveLabel = True ## If false, the dock is no longer allowed to move the label. + self.autoOrient = autoOrientation + self.orientation = 'horizontal' + #self.label.setAlignment(QtCore.Qt.AlignHCenter) + self.topLayout = QtGui.QGridLayout() + self.topLayout.setContentsMargins(0, 0, 0, 0) + self.topLayout.setSpacing(0) + self.setLayout(self.topLayout) + self.topLayout.addWidget(self.label, 0, 1) + self.widgetArea = QtGui.QWidget() + self.topLayout.addWidget(self.widgetArea, 1, 1) + self.layout = QtGui.QGridLayout() + self.layout.setContentsMargins(0, 0, 0, 0) + self.layout.setSpacing(0) + self.widgetArea.setLayout(self.layout) + self.widgetArea.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.widgets = [] + self.currentRow = 0 + #self.titlePos = 'top' + self.raiseOverlay() + self.hStyle = """ + Dock > QWidget { + border: 1px solid #000; + border-radius: 5px; + border-top-left-radius: 0px; + border-top-right-radius: 0px; + border-top-width: 0px; + }""" + self.vStyle = """ + Dock > QWidget { + border: 1px solid #000; + border-radius: 5px; + border-top-left-radius: 0px; + border-bottom-left-radius: 0px; + border-left-width: 0px; + }""" + self.nStyle = """ + Dock > QWidget { + border: 1px solid #000; + border-radius: 5px; + }""" + self.dragStyle = """ + Dock > QWidget { + border: 4px solid #00F; + border-radius: 5px; + }""" + self.setAutoFillBackground(False) + self.widgetArea.setStyleSheet(self.hStyle) + + self.setStretch(*size) + + if widget is not None: + self.addWidget(widget) + + if hideTitle: + self.hideTitleBar() + + def implements(self, name=None): + if name is None: + return ['dock'] + else: + return name == 'dock' + + def setStretch(self, x=None, y=None): + """ + Set the 'target' size for this Dock. + The actual size will be determined by comparing this Dock's + stretch value to the rest of the docks it shares space with. + """ + #print "setStretch", self, x, y + #self._stretch = (x, y) + if x is None: + x = 0 + if y is None: + y = 0 + #policy = self.sizePolicy() + #policy.setHorizontalStretch(x) + #policy.setVerticalStretch(y) + #self.setSizePolicy(policy) + self._stretch = (x, y) + self.sigStretchChanged.emit() + #print "setStretch", self, x, y, self.stretch() + + def stretch(self): + #policy = self.sizePolicy() + #return policy.horizontalStretch(), policy.verticalStretch() + return self._stretch + + #def stretch(self): + #return self._stretch + + def hideTitleBar(self): + """ + Hide the title bar for this Dock. + This will prevent the Dock being moved by the user. + """ + self.label.hide() + self.labelHidden = True + if 'center' in self.allowedAreas: + self.allowedAreas.remove('center') + self.updateStyle() + + def showTitleBar(self): + """ + Show the title bar for this Dock. + """ + self.label.show() + self.labelHidden = False + self.allowedAreas.add('center') + self.updateStyle() + + def setOrientation(self, o='auto', force=False): + """ + Sets the orientation of the title bar for this Dock. + Must be one of 'auto', 'horizontal', or 'vertical'. + By default ('auto'), the orientation is determined + based on the aspect ratio of the Dock. + """ + #print self.name(), "setOrientation", o, force + if o == 'auto' and self.autoOrient: + if self.container().type() == 'tab': + o = 'horizontal' + elif self.width() > self.height()*1.5: + o = 'vertical' + else: + o = 'horizontal' + if force or self.orientation != o: + self.orientation = o + self.label.setOrientation(o) + self.updateStyle() + + def updateStyle(self): + ## updates orientation and appearance of title bar + #print self.name(), "update style:", self.orientation, self.moveLabel, self.label.isVisible() + if self.labelHidden: + self.widgetArea.setStyleSheet(self.nStyle) + elif self.orientation == 'vertical': + self.label.setOrientation('vertical') + if self.moveLabel: + #print self.name(), "reclaim label" + self.topLayout.addWidget(self.label, 1, 0) + self.widgetArea.setStyleSheet(self.vStyle) + else: + self.label.setOrientation('horizontal') + if self.moveLabel: + #print self.name(), "reclaim label" + self.topLayout.addWidget(self.label, 0, 1) + self.widgetArea.setStyleSheet(self.hStyle) + + def resizeEvent(self, ev): + self.setOrientation() + self.resizeOverlay(self.size()) + + def name(self): + return asUnicode(self.label.text()) + + def container(self): + return self._container + + def addWidget(self, widget, row=None, col=0, rowspan=1, colspan=1): + """ + Add a new widget to the interior of this Dock. + Each Dock uses a QGridLayout to arrange widgets within. + """ + if row is None: + row = self.currentRow + self.currentRow = max(row+1, self.currentRow) + self.widgets.append(widget) + self.layout.addWidget(widget, row, col, rowspan, colspan) + self.raiseOverlay() + + + def startDrag(self): + self.drag = QtGui.QDrag(self) + mime = QtCore.QMimeData() + #mime.setPlainText("asd") + self.drag.setMimeData(mime) + self.widgetArea.setStyleSheet(self.dragStyle) + self.update() + action = self.drag.exec_() + self.updateStyle() + + def float(self): + self.area.floatDock(self) + + def containerChanged(self, c): + #print self.name(), "container changed" + self._container = c + if c.type() != 'tab': + self.moveLabel = True + self.label.setDim(False) + else: + self.moveLabel = False + + self.setOrientation(force=True) + + def raiseDock(self): + """If this Dock is stacked underneath others, raise it to the top.""" + self.container().raiseDock(self) + + + def close(self): + """Remove this dock from the DockArea it lives inside.""" + self.setParent(None) + self.label.setParent(None) + self._container.apoptose() + self._container = None + + def __repr__(self): + return "" % (self.name(), self.stretch()) + + ## PySide bug: We need to explicitly redefine these methods + ## or else drag/drop events will not be delivered. + def dragEnterEvent(self, *args): + DockDrop.dragEnterEvent(self, *args) + + def dragMoveEvent(self, *args): + DockDrop.dragMoveEvent(self, *args) + + def dragLeaveEvent(self, *args): + DockDrop.dragLeaveEvent(self, *args) + + def dropEvent(self, *args): + DockDrop.dropEvent(self, *args) + + +class DockLabel(VerticalLabel): + + sigClicked = QtCore.Signal(object, object) + sigCloseClicked = QtCore.Signal() + + def __init__(self, text, dock, showCloseButton): + self.dim = False + self.fixedWidth = False + VerticalLabel.__init__(self, text, orientation='horizontal', forceWidth=False) + self.setAlignment(QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter) + self.dock = dock + self.updateStyle() + self.setAutoFillBackground(False) + self.startedDrag = False + + self.closeButton = None + if showCloseButton: + self.closeButton = QtGui.QToolButton(self) + self.closeButton.clicked.connect(self.sigCloseClicked) + self.closeButton.setIcon(QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_TitleBarCloseButton)) + + def updateStyle(self): + r = '3px' + if self.dim: + fg = '#aaa' + bg = '#44a' + border = '#339' + else: + fg = '#fff' + bg = '#66c' + border = '#55B' + + if self.orientation == 'vertical': + self.vStyle = """DockLabel { + background-color : %s; + color : %s; + border-top-right-radius: 0px; + border-top-left-radius: %s; + border-bottom-right-radius: 0px; + border-bottom-left-radius: %s; + border-width: 0px; + border-right: 2px solid %s; + padding-top: 3px; + padding-bottom: 3px; + }""" % (bg, fg, r, r, border) + self.setStyleSheet(self.vStyle) + else: + self.hStyle = """DockLabel { + background-color : %s; + color : %s; + border-top-right-radius: %s; + border-top-left-radius: %s; + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; + border-width: 0px; + border-bottom: 2px solid %s; + padding-left: 3px; + padding-right: 3px; + }""" % (bg, fg, r, r, border) + self.setStyleSheet(self.hStyle) + + def setDim(self, d): + if self.dim != d: + self.dim = d + self.updateStyle() + + def setOrientation(self, o): + VerticalLabel.setOrientation(self, o) + self.updateStyle() + + def mousePressEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + self.pressPos = ev.pos() + self.startedDrag = False + ev.accept() + + def mouseMoveEvent(self, ev): + if not self.startedDrag and (ev.pos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance(): + self.dock.startDrag() + ev.accept() + + def mouseReleaseEvent(self, ev): + if not self.startedDrag: + self.sigClicked.emit(self, ev) + ev.accept() + + def mouseDoubleClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + self.dock.float() + + def resizeEvent (self, ev): + if self.closeButton: + if self.orientation == 'vertical': + size = ev.size().width() + pos = QtCore.QPoint(0, 0) + else: + size = ev.size().height() + pos = QtCore.QPoint(ev.size().width() - size, 0) + self.closeButton.setFixedSize(QtCore.QSize(size, size)) + self.closeButton.move(pos) + super(DockLabel,self).resizeEvent(ev) diff --git a/papi/pyqtgraph/dockarea/DockArea.py b/papi/pyqtgraph/dockarea/DockArea.py new file mode 100644 index 00000000..a75d881d --- /dev/null +++ b/papi/pyqtgraph/dockarea/DockArea.py @@ -0,0 +1,319 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtCore, QtGui +from .Container import * +from .DockDrop import * +from .Dock import Dock +from .. import debug as debug +import weakref + +## TODO: +# - containers should be drop areas, not docks. (but every slot within a container must have its own drop areas?) +# - drop between tabs +# - nest splitters inside tab boxes, etc. + + + + +class DockArea(Container, QtGui.QWidget, DockDrop): + def __init__(self, temporary=False, home=None): + Container.__init__(self, self) + QtGui.QWidget.__init__(self) + DockDrop.__init__(self, allowedAreas=['left', 'right', 'top', 'bottom']) + self.layout = QtGui.QVBoxLayout() + self.layout.setContentsMargins(0,0,0,0) + self.layout.setSpacing(0) + self.setLayout(self.layout) + self.docks = weakref.WeakValueDictionary() + self.topContainer = None + self.raiseOverlay() + self.temporary = temporary + self.tempAreas = [] + self.home = home + + def type(self): + return "top" + + def addDock(self, dock=None, position='bottom', relativeTo=None, **kwds): + """Adds a dock to this area. + + ============== ================================================================= + **Arguments:** + dock The new Dock object to add. If None, then a new Dock will be + created. + position 'bottom', 'top', 'left', 'right', 'above', or 'below' + relativeTo If relativeTo is None, then the new Dock is added to fill an + entire edge of the window. If relativeTo is another Dock, then + the new Dock is placed adjacent to it (or in a tabbed + configuration for 'above' and 'below'). + ============== ================================================================= + + All extra keyword arguments are passed to Dock.__init__() if *dock* is + None. + """ + if dock is None: + dock = Dock(**kwds) + + + ## Determine the container to insert this dock into. + ## If there is no neighbor, then the container is the top. + if relativeTo is None or relativeTo is self: + if self.topContainer is None: + container = self + neighbor = None + else: + container = self.topContainer + neighbor = None + else: + if isinstance(relativeTo, basestring): + relativeTo = self.docks[relativeTo] + container = self.getContainer(relativeTo) + neighbor = relativeTo + + ## what container type do we need? + neededContainer = { + 'bottom': 'vertical', + 'top': 'vertical', + 'left': 'horizontal', + 'right': 'horizontal', + 'above': 'tab', + 'below': 'tab' + }[position] + + ## Can't insert new containers into a tab container; insert outside instead. + if neededContainer != container.type() and container.type() == 'tab': + neighbor = container + container = container.container() + + ## Decide if the container we have is suitable. + ## If not, insert a new container inside. + if neededContainer != container.type(): + if neighbor is None: + container = self.addContainer(neededContainer, self.topContainer) + else: + container = self.addContainer(neededContainer, neighbor) + + ## Insert the new dock before/after its neighbor + insertPos = { + 'bottom': 'after', + 'top': 'before', + 'left': 'before', + 'right': 'after', + 'above': 'before', + 'below': 'after' + }[position] + #print "request insert", dock, insertPos, neighbor + container.insert(dock, insertPos, neighbor) + dock.area = self + self.docks[dock.name()] = dock + + return dock + + def moveDock(self, dock, position, neighbor): + """ + Move an existing Dock to a new location. + """ + old = dock.container() + ## Moving to the edge of a tabbed dock causes a drop outside the tab box + if position in ['left', 'right', 'top', 'bottom'] and neighbor is not None and neighbor.container() is not None and neighbor.container().type() == 'tab': + neighbor = neighbor.container() + self.addDock(dock, position, neighbor) + old.apoptose() + + def getContainer(self, obj): + if obj is None: + return self + return obj.container() + + def makeContainer(self, typ): + if typ == 'vertical': + new = VContainer(self) + elif typ == 'horizontal': + new = HContainer(self) + elif typ == 'tab': + new = TContainer(self) + return new + + def addContainer(self, typ, obj): + """Add a new container around obj""" + new = self.makeContainer(typ) + + container = self.getContainer(obj) + container.insert(new, 'before', obj) + #print "Add container:", new, " -> ", container + if obj is not None: + new.insert(obj) + self.raiseOverlay() + return new + + def insert(self, new, pos=None, neighbor=None): + if self.topContainer is not None: + self.topContainer.containerChanged(None) + self.layout.addWidget(new) + self.topContainer = new + #print self, "set top:", new + new._container = self + self.raiseOverlay() + #print "Insert top:", new + + def count(self): + if self.topContainer is None: + return 0 + return 1 + + + #def paintEvent(self, ev): + #self.drawDockOverlay() + + def resizeEvent(self, ev): + self.resizeOverlay(self.size()) + + def addTempArea(self): + if self.home is None: + area = DockArea(temporary=True, home=self) + self.tempAreas.append(area) + win = QtGui.QMainWindow() + win.setCentralWidget(area) + area.win = win + win.show() + else: + area = self.home.addTempArea() + #print "added temp area", area, area.window() + return area + + def floatDock(self, dock): + """Removes *dock* from this DockArea and places it in a new window.""" + area = self.addTempArea() + area.win.resize(dock.size()) + area.moveDock(dock, 'top', None) + + + def removeTempArea(self, area): + self.tempAreas.remove(area) + #print "close window", area.window() + area.window().close() + + def saveState(self): + """ + Return a serialized (storable) representation of the state of + all Docks in this DockArea.""" + state = {'main': self.childState(self.topContainer), 'float': []} + for a in self.tempAreas: + geo = a.win.geometry() + geo = (geo.x(), geo.y(), geo.width(), geo.height()) + state['float'].append((a.saveState(), geo)) + return state + + def childState(self, obj): + if isinstance(obj, Dock): + return ('dock', obj.name(), {}) + else: + childs = [] + for i in range(obj.count()): + childs.append(self.childState(obj.widget(i))) + return (obj.type(), childs, obj.saveState()) + + + def restoreState(self, state): + """ + Restore Dock configuration as generated by saveState. + + Note that this function does not create any Docks--it will only + restore the arrangement of an existing set of Docks. + + """ + + ## 1) make dict of all docks and list of existing containers + containers, docks = self.findAll() + oldTemps = self.tempAreas[:] + #print "found docks:", docks + + ## 2) create container structure, move docks into new containers + self.buildFromState(state['main'], docks, self) + + ## 3) create floating areas, populate + for s in state['float']: + a = self.addTempArea() + a.buildFromState(s[0]['main'], docks, a) + a.win.setGeometry(*s[1]) + + ## 4) Add any remaining docks to the bottom + for d in docks.values(): + self.moveDock(d, 'below', None) + + #print "\nKill old containers:" + ## 5) kill old containers + for c in containers: + c.close() + for a in oldTemps: + a.apoptose() + + + def buildFromState(self, state, docks, root, depth=0): + typ, contents, state = state + pfx = " " * depth + if typ == 'dock': + try: + obj = docks[contents] + del docks[contents] + except KeyError: + raise Exception('Cannot restore dock state; no dock with name "%s"' % contents) + else: + obj = self.makeContainer(typ) + + root.insert(obj, 'after') + #print pfx+"Add:", obj, " -> ", root + + if typ != 'dock': + for o in contents: + self.buildFromState(o, docks, obj, depth+1) + obj.apoptose(propagate=False) + obj.restoreState(state) ## this has to be done later? + + + def findAll(self, obj=None, c=None, d=None): + if obj is None: + obj = self.topContainer + + ## check all temp areas first + if c is None: + c = [] + d = {} + for a in self.tempAreas: + c1, d1 = a.findAll() + c.extend(c1) + d.update(d1) + + if isinstance(obj, Dock): + d[obj.name()] = obj + elif obj is not None: + c.append(obj) + for i in range(obj.count()): + o2 = obj.widget(i) + c2, d2 = self.findAll(o2) + c.extend(c2) + d.update(d2) + return (c, d) + + def apoptose(self): + #print "apoptose area:", self.temporary, self.topContainer, self.topContainer.count() + if self.temporary and self.topContainer.count() == 0: + self.topContainer = None + self.home.removeTempArea(self) + #self.close() + + ## PySide bug: We need to explicitly redefine these methods + ## or else drag/drop events will not be delivered. + def dragEnterEvent(self, *args): + DockDrop.dragEnterEvent(self, *args) + + def dragMoveEvent(self, *args): + DockDrop.dragMoveEvent(self, *args) + + def dragLeaveEvent(self, *args): + DockDrop.dragLeaveEvent(self, *args) + + def dropEvent(self, *args): + DockDrop.dropEvent(self, *args) + + + diff --git a/papi/pyqtgraph/dockarea/DockDrop.py b/papi/pyqtgraph/dockarea/DockDrop.py new file mode 100644 index 00000000..bd364f50 --- /dev/null +++ b/papi/pyqtgraph/dockarea/DockDrop.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtCore, QtGui + +class DockDrop(object): + """Provides dock-dropping methods""" + def __init__(self, allowedAreas=None): + object.__init__(self) + if allowedAreas is None: + allowedAreas = ['center', 'right', 'left', 'top', 'bottom'] + self.allowedAreas = set(allowedAreas) + self.setAcceptDrops(True) + self.dropArea = None + self.overlay = DropAreaOverlay(self) + self.overlay.raise_() + + def resizeOverlay(self, size): + self.overlay.resize(size) + + def raiseOverlay(self): + self.overlay.raise_() + + def dragEnterEvent(self, ev): + src = ev.source() + if hasattr(src, 'implements') and src.implements('dock'): + #print "drag enter accept" + ev.accept() + else: + #print "drag enter ignore" + ev.ignore() + + def dragMoveEvent(self, ev): + #print "drag move" + ld = ev.pos().x() + rd = self.width() - ld + td = ev.pos().y() + bd = self.height() - td + + mn = min(ld, rd, td, bd) + if mn > 30: + self.dropArea = "center" + elif (ld == mn or td == mn) and mn > self.height()/3.: + self.dropArea = "center" + elif (rd == mn or ld == mn) and mn > self.width()/3.: + self.dropArea = "center" + + elif rd == mn: + self.dropArea = "right" + elif ld == mn: + self.dropArea = "left" + elif td == mn: + self.dropArea = "top" + elif bd == mn: + self.dropArea = "bottom" + + if ev.source() is self and self.dropArea == 'center': + #print " no self-center" + self.dropArea = None + ev.ignore() + elif self.dropArea not in self.allowedAreas: + #print " not allowed" + self.dropArea = None + ev.ignore() + else: + #print " ok" + ev.accept() + self.overlay.setDropArea(self.dropArea) + + def dragLeaveEvent(self, ev): + self.dropArea = None + self.overlay.setDropArea(self.dropArea) + + def dropEvent(self, ev): + area = self.dropArea + if area is None: + return + if area == 'center': + area = 'above' + self.area.moveDock(ev.source(), area, self) + self.dropArea = None + self.overlay.setDropArea(self.dropArea) + + + +class DropAreaOverlay(QtGui.QWidget): + """Overlay widget that draws drop areas during a drag-drop operation""" + + def __init__(self, parent): + QtGui.QWidget.__init__(self, parent) + self.dropArea = None + self.hide() + self.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents) + + def setDropArea(self, area): + self.dropArea = area + if area is None: + self.hide() + else: + ## Resize overlay to just the region where drop area should be displayed. + ## This works around a Qt bug--can't display transparent widgets over QGLWidget + prgn = self.parent().rect() + rgn = QtCore.QRect(prgn) + w = min(30, prgn.width()/3.) + h = min(30, prgn.height()/3.) + + if self.dropArea == 'left': + rgn.setWidth(w) + elif self.dropArea == 'right': + rgn.setLeft(rgn.left() + prgn.width() - w) + elif self.dropArea == 'top': + rgn.setHeight(h) + elif self.dropArea == 'bottom': + rgn.setTop(rgn.top() + prgn.height() - h) + elif self.dropArea == 'center': + rgn.adjust(w, h, -w, -h) + self.setGeometry(rgn) + self.show() + + self.update() + + def paintEvent(self, ev): + if self.dropArea is None: + return + p = QtGui.QPainter(self) + rgn = self.rect() + + p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 255, 50))) + p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 150), 3)) + p.drawRect(rgn) diff --git a/papi/pyqtgraph/dockarea/__init__.py b/papi/pyqtgraph/dockarea/__init__.py new file mode 100644 index 00000000..f67c50c3 --- /dev/null +++ b/papi/pyqtgraph/dockarea/__init__.py @@ -0,0 +1,2 @@ +from .DockArea import DockArea +from .Dock import Dock \ No newline at end of file diff --git a/papi/pyqtgraph/dockarea/tests/test_dock.py b/papi/pyqtgraph/dockarea/tests/test_dock.py new file mode 100644 index 00000000..949f3f0e --- /dev/null +++ b/papi/pyqtgraph/dockarea/tests/test_dock.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +#import sip +#sip.setapi('QString', 1) + +import pyqtgraph as pg +pg.mkQApp() + +import pyqtgraph.dockarea as da + +def test_dock(): + name = pg.asUnicode("évènts_zàhéér") + dock = da.Dock(name=name) + # make sure unicode names work correctly + assert dock.name() == name + # no surprises in return type. + assert type(dock.name()) == type(name) diff --git a/papi/pyqtgraph/exceptionHandling.py b/papi/pyqtgraph/exceptionHandling.py new file mode 100644 index 00000000..3182b7eb --- /dev/null +++ b/papi/pyqtgraph/exceptionHandling.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +"""This module installs a wrapper around sys.excepthook which allows multiple +new exception handlers to be registered. + +Optionally, the wrapper also stops exceptions from causing long-term storage +of local stack frames. This has two major effects: + - Unhandled exceptions will no longer cause memory leaks + (If an exception occurs while a lot of data is present on the stack, + such as when loading large files, the data would ordinarily be kept + until the next exception occurs. We would rather release this memory + as soon as possible.) + - Some debuggers may have a hard time handling uncaught exceptions + +The module also provides a callback mechanism allowing others to respond +to exceptions. +""" + +import sys, time +#from lib.Manager import logMsg +import traceback +#from log import * + +#logging = False + +callbacks = [] +clear_tracebacks = False + +def register(fn): + """ + Register a callable to be invoked when there is an unhandled exception. + The callback will be passed the output of sys.exc_info(): (exception type, exception, traceback) + Multiple callbacks will be invoked in the order they were registered. + """ + callbacks.append(fn) + +def unregister(fn): + """Unregister a previously registered callback.""" + callbacks.remove(fn) + +def setTracebackClearing(clear=True): + """ + Enable or disable traceback clearing. + By default, clearing is disabled and Python will indefinitely store unhandled exception stack traces. + This function is provided since Python's default behavior can cause unexpected retention of + large memory-consuming objects. + """ + global clear_tracebacks + clear_tracebacks = clear + +class ExceptionHandler(object): + def __call__(self, *args): + ## Start by extending recursion depth just a bit. + ## If the error we are catching is due to recursion, we don't want to generate another one here. + recursionLimit = sys.getrecursionlimit() + try: + sys.setrecursionlimit(recursionLimit+100) + + + ## call original exception handler first (prints exception) + global original_excepthook, callbacks, clear_tracebacks + try: + print("===== %s =====" % str(time.strftime("%Y.%m.%d %H:%m:%S", time.localtime(time.time())))) + except Exception: + sys.stderr.write("Warning: stdout is broken! Falling back to stderr.\n") + sys.stdout = sys.stderr + + ret = original_excepthook(*args) + + for cb in callbacks: + try: + cb(*args) + except Exception: + print(" --------------------------------------------------------------") + print(" Error occurred during exception callback %s" % str(cb)) + print(" --------------------------------------------------------------") + traceback.print_exception(*sys.exc_info()) + + + ## Clear long-term storage of last traceback to prevent memory-hogging. + ## (If an exception occurs while a lot of data is present on the stack, + ## such as when loading large files, the data would ordinarily be kept + ## until the next exception occurs. We would rather release this memory + ## as soon as possible.) + if clear_tracebacks is True: + sys.last_traceback = None + + finally: + sys.setrecursionlimit(recursionLimit) + + + def implements(self, interface=None): + ## this just makes it easy for us to detect whether an ExceptionHook is already installed. + if interface is None: + return ['ExceptionHandler'] + else: + return interface == 'ExceptionHandler' + + + +## replace built-in excepthook only if this has not already been done +if not (hasattr(sys.excepthook, 'implements') and sys.excepthook.implements('ExceptionHandler')): + original_excepthook = sys.excepthook + sys.excepthook = ExceptionHandler() + + + diff --git a/papi/pyqtgraph/exporters/CSVExporter.py b/papi/pyqtgraph/exporters/CSVExporter.py new file mode 100644 index 00000000..b87f0182 --- /dev/null +++ b/papi/pyqtgraph/exporters/CSVExporter.py @@ -0,0 +1,83 @@ +from ..Qt import QtGui, QtCore +from .Exporter import Exporter +from ..parametertree import Parameter +from .. import PlotItem + +__all__ = ['CSVExporter'] + + +class CSVExporter(Exporter): + Name = "CSV from plot data" + windows = [] + def __init__(self, item): + Exporter.__init__(self, item) + self.params = Parameter(name='params', type='group', children=[ + {'name': 'separator', 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']}, + {'name': 'precision', 'type': 'int', 'value': 10, 'limits': [0, None]}, + {'name': 'columnMode', 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']} + ]) + + def parameters(self): + return self.params + + def export(self, fileName=None): + + if not isinstance(self.item, PlotItem): + raise Exception("Must have a PlotItem selected for CSV export.") + + if fileName is None: + self.fileSaveDialog(filter=["*.csv", "*.tsv"]) + return + + fd = open(fileName, 'w') + data = [] + header = [] + + appendAllX = self.params['columnMode'] == '(x,y) per plot' + + for i, c in enumerate(self.item.curves): + cd = c.getData() + if cd[0] is None: + continue + data.append(cd) + if hasattr(c, 'implements') and c.implements('plotData') and c.name() is not None: + name = c.name().replace('"', '""') + '_' + xName, yName = '"'+name+'x"', '"'+name+'y"' + else: + xName = 'x%04d' % i + yName = 'y%04d' % i + if appendAllX or i == 0: + header.extend([xName, yName]) + else: + header.extend([yName]) + + if self.params['separator'] == 'comma': + sep = ',' + else: + sep = '\t' + + fd.write(sep.join(header) + '\n') + i = 0 + numFormat = '%%0.%dg' % self.params['precision'] + numRows = max([len(d[0]) for d in data]) + for i in range(numRows): + for j, d in enumerate(data): + # write x value if this is the first column, or if we want x + # for all rows + if appendAllX or j == 0: + if d is not None and i < len(d[0]): + fd.write(numFormat % d[0][i] + sep) + else: + fd.write(' %s' % sep) + + # write y value + if d is not None and i < len(d[1]): + fd.write(numFormat % d[1][i] + sep) + else: + fd.write(' %s' % sep) + fd.write('\n') + fd.close() + +CSVExporter.register() + + diff --git a/papi/pyqtgraph/exporters/Exporter.py b/papi/pyqtgraph/exporters/Exporter.py new file mode 100644 index 00000000..64a25294 --- /dev/null +++ b/papi/pyqtgraph/exporters/Exporter.py @@ -0,0 +1,139 @@ +from ..widgets.FileDialog import FileDialog +from ..Qt import QtGui, QtCore, QtSvg +from ..python2_3 import asUnicode +from ..GraphicsScene import GraphicsScene +import os, re +LastExportDirectory = None + + +class Exporter(object): + """ + Abstract class used for exporting graphics to file / printer / whatever. + """ + allowCopy = False # subclasses set this to True if they can use the copy buffer + Exporters = [] + + @classmethod + def register(cls): + """ + Used to register Exporter classes to appear in the export dialog. + """ + Exporter.Exporters.append(cls) + + def __init__(self, item): + """ + Initialize with the item to be exported. + Can be an individual graphics item or a scene. + """ + object.__init__(self) + self.item = item + + def parameters(self): + """Return the parameters used to configure this exporter.""" + raise Exception("Abstract method must be overridden in subclass.") + + def export(self, fileName=None, toBytes=False, copy=False): + """ + If *fileName* is None, pop-up a file dialog. + If *toBytes* is True, return a bytes object rather than writing to file. + If *copy* is True, export to the copy buffer rather than writing to file. + """ + raise Exception("Abstract method must be overridden in subclass.") + + def fileSaveDialog(self, filter=None, opts=None): + ## Show a file dialog, call self.export(fileName) when finished. + if opts is None: + opts = {} + self.fileDialog = FileDialog() + self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + if filter is not None: + if isinstance(filter, basestring): + self.fileDialog.setNameFilter(filter) + elif isinstance(filter, list): + self.fileDialog.setNameFilters(filter) + global LastExportDirectory + exportDir = LastExportDirectory + if exportDir is not None: + self.fileDialog.setDirectory(exportDir) + self.fileDialog.show() + self.fileDialog.opts = opts + self.fileDialog.fileSelected.connect(self.fileSaveFinished) + return + + def fileSaveFinished(self, fileName): + fileName = asUnicode(fileName) + global LastExportDirectory + LastExportDirectory = os.path.split(fileName)[0] + + ## If file name does not match selected extension, append it now + ext = os.path.splitext(fileName)[1].lower().lstrip('.') + selectedExt = re.search(r'\*\.(\w+)\b', asUnicode(self.fileDialog.selectedNameFilter())) + if selectedExt is not None: + selectedExt = selectedExt.groups()[0].lower() + if ext != selectedExt: + fileName = fileName + '.' + selectedExt.lstrip('.') + + self.export(fileName=fileName, **self.fileDialog.opts) + + def getScene(self): + if isinstance(self.item, GraphicsScene): + return self.item + else: + return self.item.scene() + + def getSourceRect(self): + if isinstance(self.item, GraphicsScene): + w = self.item.getViewWidget() + return w.viewportTransform().inverted()[0].mapRect(w.rect()) + else: + return self.item.sceneBoundingRect() + + def getTargetRect(self): + if isinstance(self.item, GraphicsScene): + return self.item.getViewWidget().rect() + else: + return self.item.mapRectToDevice(self.item.boundingRect()) + + def setExportMode(self, export, opts=None): + """ + Call setExportMode(export, opts) on all items that will + be painted during the export. This informs the item + that it is about to be painted for export, allowing it to + alter its appearance temporarily + + + *export* - bool; must be True before exporting and False afterward + *opts* - dict; common parameters are 'antialias' and 'background' + """ + if opts is None: + opts = {} + for item in self.getPaintItems(): + if hasattr(item, 'setExportMode'): + item.setExportMode(export, opts) + + def getPaintItems(self, root=None): + """Return a list of all items that should be painted in the correct order.""" + if root is None: + root = self.item + preItems = [] + postItems = [] + if isinstance(root, QtGui.QGraphicsScene): + childs = [i for i in root.items() if i.parentItem() is None] + rootItem = [] + else: + childs = root.childItems() + rootItem = [root] + childs.sort(key=lambda a: a.zValue()) + while len(childs) > 0: + ch = childs.pop(0) + tree = self.getPaintItems(ch) + if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0): + preItems.extend(tree) + else: + postItems.extend(tree) + + return preItems + rootItem + postItems + + def render(self, painter, targetRect, sourceRect, item=None): + self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) diff --git a/papi/pyqtgraph/exporters/HDF5Exporter.py b/papi/pyqtgraph/exporters/HDF5Exporter.py new file mode 100644 index 00000000..cc8b5733 --- /dev/null +++ b/papi/pyqtgraph/exporters/HDF5Exporter.py @@ -0,0 +1,58 @@ +from ..Qt import QtGui, QtCore +from .Exporter import Exporter +from ..parametertree import Parameter +from .. import PlotItem + +import numpy +try: + import h5py + HAVE_HDF5 = True +except ImportError: + HAVE_HDF5 = False + +__all__ = ['HDF5Exporter'] + + +class HDF5Exporter(Exporter): + Name = "HDF5 Export: plot (x,y)" + windows = [] + allowCopy = False + + def __init__(self, item): + Exporter.__init__(self, item) + self.params = Parameter(name='params', type='group', children=[ + {'name': 'Name', 'type': 'str', 'value': 'Export',}, + {'name': 'columnMode', 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']}, + ]) + + def parameters(self): + return self.params + + def export(self, fileName=None): + if not HAVE_HDF5: + raise RuntimeError("This exporter requires the h5py package, " + "but it was not importable.") + + if not isinstance(self.item, PlotItem): + raise Exception("Must have a PlotItem selected for HDF5 export.") + + if fileName is None: + self.fileSaveDialog(filter=["*.h5", "*.hdf", "*.hd5"]) + return + dsname = self.params['Name'] + fd = h5py.File(fileName, 'a') # forces append to file... 'w' doesn't seem to "delete/overwrite" + data = [] + + appendAllX = self.params['columnMode'] == '(x,y) per plot' + for i,c in enumerate(self.item.curves): + d = c.getData() + if appendAllX or i == 0: + data.append(d[0]) + data.append(d[1]) + + fdata = numpy.array(data).astype('double') + dset = fd.create_dataset(dsname, data=fdata) + fd.close() + +if HAVE_HDF5: + HDF5Exporter.register() diff --git a/papi/pyqtgraph/exporters/ImageExporter.py b/papi/pyqtgraph/exporters/ImageExporter.py new file mode 100644 index 00000000..78d93106 --- /dev/null +++ b/papi/pyqtgraph/exporters/ImageExporter.py @@ -0,0 +1,102 @@ +from .Exporter import Exporter +from ..parametertree import Parameter +from ..Qt import QtGui, QtCore, QtSvg, USE_PYSIDE +from .. import functions as fn +import numpy as np + +__all__ = ['ImageExporter'] + +class ImageExporter(Exporter): + Name = "Image File (PNG, TIF, JPG, ...)" + allowCopy = True + + def __init__(self, item): + Exporter.__init__(self, item) + tr = self.getTargetRect() + if isinstance(item, QtGui.QGraphicsItem): + scene = item.scene() + else: + scene = item + bgbrush = scene.views()[0].backgroundBrush() + bg = bgbrush.color() + if bgbrush.style() == QtCore.Qt.NoBrush: + bg.setAlpha(0) + + self.params = Parameter(name='params', type='group', children=[ + {'name': 'width', 'type': 'int', 'value': tr.width(), 'limits': (0, None)}, + {'name': 'height', 'type': 'int', 'value': tr.height(), 'limits': (0, None)}, + {'name': 'antialias', 'type': 'bool', 'value': True}, + {'name': 'background', 'type': 'color', 'value': bg}, + ]) + self.params.param('width').sigValueChanged.connect(self.widthChanged) + self.params.param('height').sigValueChanged.connect(self.heightChanged) + + def widthChanged(self): + sr = self.getSourceRect() + ar = float(sr.height()) / sr.width() + self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) + + def heightChanged(self): + sr = self.getSourceRect() + ar = float(sr.width()) / sr.height() + self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) + + def parameters(self): + return self.params + + def export(self, fileName=None, toBytes=False, copy=False): + if fileName is None and not toBytes and not copy: + if USE_PYSIDE: + filter = ["*."+str(f) for f in QtGui.QImageWriter.supportedImageFormats()] + else: + filter = ["*."+bytes(f).decode('utf-8') for f in QtGui.QImageWriter.supportedImageFormats()] + preferred = ['*.png', '*.tif', '*.jpg'] + for p in preferred[::-1]: + if p in filter: + filter.remove(p) + filter.insert(0, p) + self.fileSaveDialog(filter=filter) + return + + targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) + sourceRect = self.getSourceRect() + + + #self.png = QtGui.QImage(targetRect.size(), QtGui.QImage.Format_ARGB32) + #self.png.fill(pyqtgraph.mkColor(self.params['background'])) + w, h = self.params['width'], self.params['height'] + if w == 0 or h == 0: + raise Exception("Cannot export image with size=0 (requested export size is %dx%d)" % (w,h)) + bg = np.empty((self.params['width'], self.params['height'], 4), dtype=np.ubyte) + color = self.params['background'] + bg[:,:,0] = color.blue() + bg[:,:,1] = color.green() + bg[:,:,2] = color.red() + bg[:,:,3] = color.alpha() + self.png = fn.makeQImage(bg, alpha=True) + + ## set resolution of image: + origTargetRect = self.getTargetRect() + resolutionScale = targetRect.width() / origTargetRect.width() + #self.png.setDotsPerMeterX(self.png.dotsPerMeterX() * resolutionScale) + #self.png.setDotsPerMeterY(self.png.dotsPerMeterY() * resolutionScale) + + painter = QtGui.QPainter(self.png) + #dtr = painter.deviceTransform() + try: + self.setExportMode(True, {'antialias': self.params['antialias'], 'background': self.params['background'], 'painter': painter, 'resolutionScale': resolutionScale}) + painter.setRenderHint(QtGui.QPainter.Antialiasing, self.params['antialias']) + self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) + finally: + self.setExportMode(False) + painter.end() + + if copy: + QtGui.QApplication.clipboard().setImage(self.png) + elif toBytes: + return self.png + else: + self.png.save(fileName) + +ImageExporter.register() + diff --git a/papi/pyqtgraph/exporters/Matplotlib.py b/papi/pyqtgraph/exporters/Matplotlib.py new file mode 100644 index 00000000..8cec1cef --- /dev/null +++ b/papi/pyqtgraph/exporters/Matplotlib.py @@ -0,0 +1,128 @@ +from ..Qt import QtGui, QtCore +from .Exporter import Exporter +from .. import PlotItem +from .. import functions as fn + +__all__ = ['MatplotlibExporter'] + +""" +It is helpful when using the matplotlib Exporter if your +.matplotlib/matplotlibrc file is configured appropriately. +The following are suggested for getting usable PDF output that +can be edited in Illustrator, etc. + +backend : Qt4Agg +text.usetex : True # Assumes you have a findable LaTeX installation +interactive : False +font.family : sans-serif +font.sans-serif : 'Arial' # (make first in list) +mathtext.default : sf +figure.facecolor : white # personal preference +# next setting allows pdf font to be readable in Adobe Illustrator +pdf.fonttype : 42 # set fonts to TrueType (otherwise it will be 3 + # and the text will be vectorized. +text.dvipnghack : True # primarily to clean up font appearance on Mac + +The advantage is that there is less to do to get an exported file cleaned and ready for +publication. Fonts are not vectorized (outlined), and window colors are white. + +""" + +class MatplotlibExporter(Exporter): + Name = "Matplotlib Window" + windows = [] + def __init__(self, item): + Exporter.__init__(self, item) + + def parameters(self): + return None + + def cleanAxes(self, axl): + if type(axl) is not list: + axl = [axl] + for ax in axl: + if ax is None: + continue + for loc, spine in ax.spines.iteritems(): + if loc in ['left', 'bottom']: + pass + elif loc in ['right', 'top']: + spine.set_color('none') + # do not draw the spine + else: + raise ValueError('Unknown spine location: %s' % loc) + # turn off ticks when there is no spine + ax.xaxis.set_ticks_position('bottom') + + def export(self, fileName=None): + + if isinstance(self.item, PlotItem): + mpw = MatplotlibWindow() + MatplotlibExporter.windows.append(mpw) + + stdFont = 'Arial' + + fig = mpw.getFigure() + + # get labels from the graphic item + xlabel = self.item.axes['bottom']['item'].label.toPlainText() + ylabel = self.item.axes['left']['item'].label.toPlainText() + title = self.item.titleLabel.text + + ax = fig.add_subplot(111, title=title) + ax.clear() + self.cleanAxes(ax) + #ax.grid(True) + for item in self.item.curves: + x, y = item.getData() + opts = item.opts + pen = fn.mkPen(opts['pen']) + if pen.style() == QtCore.Qt.NoPen: + linestyle = '' + else: + linestyle = '-' + color = tuple([c/255. for c in fn.colorTuple(pen.color())]) + symbol = opts['symbol'] + if symbol == 't': + symbol = '^' + symbolPen = fn.mkPen(opts['symbolPen']) + symbolBrush = fn.mkBrush(opts['symbolBrush']) + markeredgecolor = tuple([c/255. for c in fn.colorTuple(symbolPen.color())]) + markerfacecolor = tuple([c/255. for c in fn.colorTuple(symbolBrush.color())]) + markersize = opts['symbolSize'] + + if opts['fillLevel'] is not None and opts['fillBrush'] is not None: + fillBrush = fn.mkBrush(opts['fillBrush']) + fillcolor = tuple([c/255. for c in fn.colorTuple(fillBrush.color())]) + ax.fill_between(x=x, y1=y, y2=opts['fillLevel'], facecolor=fillcolor) + + pl = ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(), + linestyle=linestyle, markeredgecolor=markeredgecolor, markerfacecolor=markerfacecolor, + markersize=markersize) + xr, yr = self.item.viewRange() + ax.set_xbound(*xr) + ax.set_ybound(*yr) + ax.set_xlabel(xlabel) # place the labels. + ax.set_ylabel(ylabel) + mpw.draw() + else: + raise Exception("Matplotlib export currently only works with plot items") + +MatplotlibExporter.register() + + +class MatplotlibWindow(QtGui.QMainWindow): + def __init__(self): + from ..widgets import MatplotlibWidget + QtGui.QMainWindow.__init__(self) + self.mpl = MatplotlibWidget.MatplotlibWidget() + self.setCentralWidget(self.mpl) + self.show() + + def __getattr__(self, attr): + return getattr(self.mpl, attr) + + def closeEvent(self, ev): + MatplotlibExporter.windows.remove(self) + + diff --git a/papi/pyqtgraph/exporters/PrintExporter.py b/papi/pyqtgraph/exporters/PrintExporter.py new file mode 100644 index 00000000..530a1800 --- /dev/null +++ b/papi/pyqtgraph/exporters/PrintExporter.py @@ -0,0 +1,68 @@ +from .Exporter import Exporter +from ..parametertree import Parameter +from ..Qt import QtGui, QtCore, QtSvg +import re + +__all__ = ['PrintExporter'] +#__all__ = [] ## Printer is disabled for now--does not work very well. + +class PrintExporter(Exporter): + Name = "Printer" + def __init__(self, item): + Exporter.__init__(self, item) + tr = self.getTargetRect() + self.params = Parameter(name='params', type='group', children=[ + {'name': 'width', 'type': 'float', 'value': 0.1, 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, + {'name': 'height', 'type': 'float', 'value': (0.1 * tr.height()) / tr.width(), 'limits': (0, None), 'suffix': 'm', 'siPrefix': True}, + ]) + self.params.param('width').sigValueChanged.connect(self.widthChanged) + self.params.param('height').sigValueChanged.connect(self.heightChanged) + + def widthChanged(self): + sr = self.getSourceRect() + ar = sr.height() / sr.width() + self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) + + def heightChanged(self): + sr = self.getSourceRect() + ar = sr.width() / sr.height() + self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) + + def parameters(self): + return self.params + + def export(self, fileName=None): + printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) + dialog = QtGui.QPrintDialog(printer) + dialog.setWindowTitle("Print Document") + if dialog.exec_() != QtGui.QDialog.Accepted: + return + + #dpi = QtGui.QDesktopWidget().physicalDpiX() + + #self.svg.setSize(QtCore.QSize(100,100)) + #self.svg.setResolution(600) + #res = printer.resolution() + sr = self.getSourceRect() + #res = sr.width() * .4 / (self.params['width'] * 100 / 2.54) + res = QtGui.QDesktopWidget().physicalDpiX() + printer.setResolution(res) + rect = printer.pageRect() + center = rect.center() + h = self.params['height'] * res * 100. / 2.54 + w = self.params['width'] * res * 100. / 2.54 + x = center.x() - w/2. + y = center.y() - h/2. + + targetRect = QtCore.QRect(x, y, w, h) + sourceRect = self.getSourceRect() + painter = QtGui.QPainter(printer) + try: + self.setExportMode(True, {'painter': painter}) + self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect)) + finally: + self.setExportMode(False) + painter.end() + + +#PrintExporter.register() diff --git a/papi/pyqtgraph/exporters/SVGExporter.py b/papi/pyqtgraph/exporters/SVGExporter.py new file mode 100644 index 00000000..a91466c8 --- /dev/null +++ b/papi/pyqtgraph/exporters/SVGExporter.py @@ -0,0 +1,477 @@ +from .Exporter import Exporter +from ..python2_3 import asUnicode +from ..parametertree import Parameter +from ..Qt import QtGui, QtCore, QtSvg, USE_PYSIDE +from .. import debug +from .. import functions as fn +import re +import xml.dom.minidom as xml +import numpy as np + + +__all__ = ['SVGExporter'] + +class SVGExporter(Exporter): + Name = "Scalable Vector Graphics (SVG)" + allowCopy=True + + def __init__(self, item): + Exporter.__init__(self, item) + #tr = self.getTargetRect() + self.params = Parameter(name='params', type='group', children=[ + #{'name': 'width', 'type': 'float', 'value': tr.width(), 'limits': (0, None)}, + #{'name': 'height', 'type': 'float', 'value': tr.height(), 'limits': (0, None)}, + #{'name': 'viewbox clipping', 'type': 'bool', 'value': True}, + #{'name': 'normalize coordinates', 'type': 'bool', 'value': True}, + #{'name': 'normalize line width', 'type': 'bool', 'value': True}, + ]) + #self.params.param('width').sigValueChanged.connect(self.widthChanged) + #self.params.param('height').sigValueChanged.connect(self.heightChanged) + + def widthChanged(self): + sr = self.getSourceRect() + ar = sr.height() / sr.width() + self.params.param('height').setValue(self.params['width'] * ar, blockSignal=self.heightChanged) + + def heightChanged(self): + sr = self.getSourceRect() + ar = sr.width() / sr.height() + self.params.param('width').setValue(self.params['height'] * ar, blockSignal=self.widthChanged) + + def parameters(self): + return self.params + + def export(self, fileName=None, toBytes=False, copy=False): + if toBytes is False and copy is False and fileName is None: + self.fileSaveDialog(filter="Scalable Vector Graphics (*.svg)") + return + #self.svg = QtSvg.QSvgGenerator() + #self.svg.setFileName(fileName) + #dpi = QtGui.QDesktopWidget().physicalDpiX() + ### not really sure why this works, but it seems to be important: + #self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.)) + #self.svg.setResolution(dpi) + ##self.svg.setViewBox() + #targetRect = QtCore.QRect(0, 0, self.params['width'], self.params['height']) + #sourceRect = self.getSourceRect() + + #painter = QtGui.QPainter(self.svg) + #try: + #self.setExportMode(True) + #self.render(painter, QtCore.QRectF(targetRect), sourceRect) + #finally: + #self.setExportMode(False) + #painter.end() + + ## Workaround to set pen widths correctly + #data = open(fileName).readlines() + #for i in range(len(data)): + #line = data[i] + #m = re.match(r'( + +pyqtgraph SVG export +Generated with Qt and pyqtgraph +""" + +def generateSvg(item): + global xmlHeader + try: + node, defs = _generateItemSvg(item) + finally: + ## reset export mode for all items in the tree + if isinstance(item, QtGui.QGraphicsScene): + items = item.items() + else: + items = [item] + for i in items: + items.extend(i.childItems()) + for i in items: + if hasattr(i, 'setExportMode'): + i.setExportMode(False) + + cleanXml(node) + + defsXml = "\n" + for d in defs: + defsXml += d.toprettyxml(indent=' ') + defsXml += "\n" + return xmlHeader + defsXml + node.toprettyxml(indent=' ') + "\n\n" + + +def _generateItemSvg(item, nodes=None, root=None): + ## This function is intended to work around some issues with Qt's SVG generator + ## and SVG in general. + ## 1) Qt SVG does not implement clipping paths. This is absurd. + ## The solution is to let Qt generate SVG for each item independently, + ## then glue them together manually with clipping. + ## + ## The format Qt generates for all items looks like this: + ## + ## + ## + ## one or more of: or or + ## + ## + ## one or more of: or or + ## + ## . . . + ## + ## + ## 2) There seems to be wide disagreement over whether path strokes + ## should be scaled anisotropically. + ## see: http://web.mit.edu/jonas/www/anisotropy/ + ## Given that both inkscape and illustrator seem to prefer isotropic + ## scaling, we will optimize for those cases. + ## + ## 3) Qt generates paths using non-scaling-stroke from SVG 1.2, but + ## inkscape only supports 1.1. + ## + ## Both 2 and 3 can be addressed by drawing all items in world coordinates. + + profiler = debug.Profiler() + + if nodes is None: ## nodes maps all node IDs to their XML element. + ## this allows us to ensure all elements receive unique names. + nodes = {} + + if root is None: + root = item + + ## Skip hidden items + if hasattr(item, 'isVisible') and not item.isVisible(): + return None + + ## If this item defines its own SVG generator, use that. + if hasattr(item, 'generateSvg'): + return item.generateSvg(nodes) + + + ## Generate SVG text for just this item (exclude its children; we'll handle them later) + tr = QtGui.QTransform() + if isinstance(item, QtGui.QGraphicsScene): + xmlStr = "\n\n" + doc = xml.parseString(xmlStr) + childs = [i for i in item.items() if i.parentItem() is None] + elif item.__class__.paint == QtGui.QGraphicsItem.paint: + xmlStr = "\n\n" + doc = xml.parseString(xmlStr) + childs = item.childItems() + else: + childs = item.childItems() + tr = itemTransform(item, item.scene()) + + ## offset to corner of root item + if isinstance(root, QtGui.QGraphicsScene): + rootPos = QtCore.QPoint(0,0) + else: + rootPos = root.scenePos() + tr2 = QtGui.QTransform() + tr2.translate(-rootPos.x(), -rootPos.y()) + tr = tr * tr2 + + arr = QtCore.QByteArray() + buf = QtCore.QBuffer(arr) + svg = QtSvg.QSvgGenerator() + svg.setOutputDevice(buf) + dpi = QtGui.QDesktopWidget().physicalDpiX() + svg.setResolution(dpi) + + p = QtGui.QPainter() + p.begin(svg) + if hasattr(item, 'setExportMode'): + item.setExportMode(True, {'painter': p}) + try: + p.setTransform(tr) + item.paint(p, QtGui.QStyleOptionGraphicsItem(), None) + finally: + p.end() + ## Can't do this here--we need to wait until all children have painted as well. + ## this is taken care of in generateSvg instead. + #if hasattr(item, 'setExportMode'): + #item.setExportMode(False) + + if USE_PYSIDE: + xmlStr = str(arr) + else: + xmlStr = bytes(arr).decode('utf-8') + doc = xml.parseString(xmlStr) + + try: + ## Get top-level group for this item + g1 = doc.getElementsByTagName('g')[0] + ## get list of sub-groups + g2 = [n for n in g1.childNodes if isinstance(n, xml.Element) and n.tagName == 'g'] + + defs = doc.getElementsByTagName('defs') + if len(defs) > 0: + defs = [n for n in defs[0].childNodes if isinstance(n, xml.Element)] + except: + print(doc.toxml()) + raise + + profiler('render') + + ## Get rid of group transformation matrices by applying + ## transformation to inner coordinates + correctCoordinates(g1, defs, item) + profiler('correct') + ## make sure g1 has the transformation matrix + #m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32()) + #g1.setAttribute('transform', "matrix(%f,%f,%f,%f,%f,%f)" % m) + + #print "=================",item,"=====================" + #print g1.toprettyxml(indent=" ", newl='') + + ## Inkscape does not support non-scaling-stroke (this is SVG 1.2, inkscape supports 1.1) + ## So we need to correct anything attempting to use this. + #correctStroke(g1, item, root) + + ## decide on a name for this item + baseName = item.__class__.__name__ + i = 1 + while True: + name = baseName + "_%d" % i + if name not in nodes: + break + i += 1 + nodes[name] = g1 + g1.setAttribute('id', name) + + ## If this item clips its children, we need to take care of that. + childGroup = g1 ## add children directly to this node unless we are clipping + if not isinstance(item, QtGui.QGraphicsScene): + ## See if this item clips its children + if int(item.flags() & item.ItemClipsChildrenToShape) > 0: + ## Generate svg for just the path + #if isinstance(root, QtGui.QGraphicsScene): + #path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) + #else: + #path = QtGui.QGraphicsPathItem(root.mapToParent(item.mapToItem(root, item.shape()))) + path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape())) + item.scene().addItem(path) + try: + #pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0] + pathNode = _generateItemSvg(path, root=root)[0].getElementsByTagName('path')[0] + # assume for this path is empty.. possibly problematic. + finally: + item.scene().removeItem(path) + + ## and for the clipPath element + clip = name + '_clip' + clipNode = g1.ownerDocument.createElement('clipPath') + clipNode.setAttribute('id', clip) + clipNode.appendChild(pathNode) + g1.appendChild(clipNode) + + childGroup = g1.ownerDocument.createElement('g') + childGroup.setAttribute('clip-path', 'url(#%s)' % clip) + g1.appendChild(childGroup) + profiler('clipping') + + ## Add all child items as sub-elements. + childs.sort(key=lambda c: c.zValue()) + for ch in childs: + csvg = _generateItemSvg(ch, nodes, root) + if csvg is None: + continue + cg, cdefs = csvg + childGroup.appendChild(cg) ### this isn't quite right--some items draw below their parent (good enough for now) + defs.extend(cdefs) + + profiler('children') + return g1, defs + +def correctCoordinates(node, defs, item): + # TODO: correct gradient coordinates inside defs + + ## Remove transformation matrices from tags by applying matrix to coordinates inside. + ## Each item is represented by a single top-level group with one or more groups inside. + ## Each inner group contains one or more drawing primitives, possibly of different types. + groups = node.getElementsByTagName('g') + + ## Since we leave text unchanged, groups which combine text and non-text primitives must be split apart. + ## (if at some point we start correcting text transforms as well, then it should be safe to remove this) + groups2 = [] + for grp in groups: + subGroups = [grp.cloneNode(deep=False)] + textGroup = None + for ch in grp.childNodes[:]: + if isinstance(ch, xml.Element): + if textGroup is None: + textGroup = ch.tagName == 'text' + if ch.tagName == 'text': + if textGroup is False: + subGroups.append(grp.cloneNode(deep=False)) + textGroup = True + else: + if textGroup is True: + subGroups.append(grp.cloneNode(deep=False)) + textGroup = False + subGroups[-1].appendChild(ch) + groups2.extend(subGroups) + for sg in subGroups: + node.insertBefore(sg, grp) + node.removeChild(grp) + groups = groups2 + + + for grp in groups: + matrix = grp.getAttribute('transform') + match = re.match(r'matrix\((.*)\)', matrix) + if match is None: + vals = [1,0,0,1,0,0] + else: + vals = [float(a) for a in match.groups()[0].split(',')] + tr = np.array([[vals[0], vals[2], vals[4]], [vals[1], vals[3], vals[5]]]) + + removeTransform = False + for ch in grp.childNodes: + if not isinstance(ch, xml.Element): + continue + if ch.tagName == 'polyline': + removeTransform = True + coords = np.array([[float(a) for a in c.split(',')] for c in ch.getAttribute('points').strip().split(' ')]) + coords = fn.transformCoordinates(tr, coords, transpose=True) + ch.setAttribute('points', ' '.join([','.join([str(a) for a in c]) for c in coords])) + elif ch.tagName == 'path': + removeTransform = True + newCoords = '' + oldCoords = ch.getAttribute('d').strip() + if oldCoords == '': + continue + for c in oldCoords.split(' '): + x,y = c.split(',') + if x[0].isalpha(): + t = x[0] + x = x[1:] + else: + t = '' + nc = fn.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True) + newCoords += t+str(nc[0,0])+','+str(nc[0,1])+' ' + ch.setAttribute('d', newCoords) + elif ch.tagName == 'text': + removeTransform = False + ## leave text alone for now. Might need this later to correctly render text with outline. + #c = np.array([ + #[float(ch.getAttribute('x')), float(ch.getAttribute('y'))], + #[float(ch.getAttribute('font-size')), 0], + #[0,0]]) + #c = fn.transformCoordinates(tr, c, transpose=True) + #ch.setAttribute('x', str(c[0,0])) + #ch.setAttribute('y', str(c[0,1])) + #fs = c[1]-c[2] + #fs = (fs**2).sum()**0.5 + #ch.setAttribute('font-size', str(fs)) + + ## Correct some font information + families = ch.getAttribute('font-family').split(',') + if len(families) == 1: + font = QtGui.QFont(families[0].strip('" ')) + if font.style() == font.SansSerif: + families.append('sans-serif') + elif font.style() == font.Serif: + families.append('serif') + elif font.style() == font.Courier: + families.append('monospace') + ch.setAttribute('font-family', ', '.join([f if ' ' not in f else '"%s"'%f for f in families])) + + ## correct line widths if needed + if removeTransform and ch.getAttribute('vector-effect') != 'non-scaling-stroke': + w = float(grp.getAttribute('stroke-width')) + s = fn.transformCoordinates(tr, np.array([[w,0], [0,0]]), transpose=True) + w = ((s[0]-s[1])**2).sum()**0.5 + ch.setAttribute('stroke-width', str(w)) + + if removeTransform: + grp.removeAttribute('transform') + + +SVGExporter.register() + + +def itemTransform(item, root): + ## Return the transformation mapping item to root + ## (actually to parent coordinate system of root) + + if item is root: + tr = QtGui.QTransform() + tr.translate(*item.pos()) + tr = tr * item.transform() + return tr + + + if int(item.flags() & item.ItemIgnoresTransformations) > 0: + pos = item.pos() + parent = item.parentItem() + if parent is not None: + pos = itemTransform(parent, root).map(pos) + tr = QtGui.QTransform() + tr.translate(pos.x(), pos.y()) + tr = item.transform() * tr + else: + ## find next parent that is either the root item or + ## an item that ignores its transformation + nextRoot = item + while True: + nextRoot = nextRoot.parentItem() + if nextRoot is None: + nextRoot = root + break + if nextRoot is root or int(nextRoot.flags() & nextRoot.ItemIgnoresTransformations) > 0: + break + + if isinstance(nextRoot, QtGui.QGraphicsScene): + tr = item.sceneTransform() + else: + tr = itemTransform(nextRoot, root) * item.itemTransform(nextRoot)[0] + + return tr + + +def cleanXml(node): + ## remove extraneous text; let the xml library do the formatting. + hasElement = False + nonElement = [] + for ch in node.childNodes: + if isinstance(ch, xml.Element): + hasElement = True + cleanXml(ch) + else: + nonElement.append(ch) + + if hasElement: + for ch in nonElement: + node.removeChild(ch) + elif node.tagName == 'g': ## remove childless groups + node.parentNode.removeChild(node) diff --git a/papi/pyqtgraph/exporters/__init__.py b/papi/pyqtgraph/exporters/__init__.py new file mode 100644 index 00000000..62ab1331 --- /dev/null +++ b/papi/pyqtgraph/exporters/__init__.py @@ -0,0 +1,11 @@ +from .Exporter import Exporter +from .ImageExporter import * +from .SVGExporter import * +from .Matplotlib import * +from .CSVExporter import * +from .PrintExporter import * +from .HDF5Exporter import * + +def listExporters(): + return Exporter.Exporters[:] + diff --git a/papi/pyqtgraph/exporters/tests/test_csv.py b/papi/pyqtgraph/exporters/tests/test_csv.py new file mode 100644 index 00000000..a98372ec --- /dev/null +++ b/papi/pyqtgraph/exporters/tests/test_csv.py @@ -0,0 +1,49 @@ +""" +SVG export test +""" +import pyqtgraph as pg +import pyqtgraph.exporters +import csv + +app = pg.mkQApp() + +def approxeq(a, b): + return (a-b) <= ((a + b) * 1e-6) + +def test_CSVExporter(): + plt = pg.plot() + y1 = [1,3,2,3,1,6,9,8,4,2] + plt.plot(y=y1, name='myPlot') + + y2 = [3,4,6,1,2,4,2,3,5,3,5,1,3] + x2 = pg.np.linspace(0, 1.0, len(y2)) + plt.plot(x=x2, y=y2) + + y3 = [1,5,2,3,4,6,1,2,4,2,3,5,3] + x3 = pg.np.linspace(0, 1.0, len(y3)+1) + plt.plot(x=x3, y=y3, stepMode=True) + + ex = pg.exporters.CSVExporter(plt.plotItem) + ex.export(fileName='test.csv') + + r = csv.reader(open('test.csv', 'r')) + lines = [line for line in r] + header = lines.pop(0) + assert header == ['myPlot_x', 'myPlot_y', 'x0001', 'y0001', 'x0002', 'y0002'] + + i = 0 + for vals in lines: + vals = list(map(str.strip, vals)) + assert (i >= len(y1) and vals[0] == '') or approxeq(float(vals[0]), i) + assert (i >= len(y1) and vals[1] == '') or approxeq(float(vals[1]), y1[i]) + + assert (i >= len(x2) and vals[2] == '') or approxeq(float(vals[2]), x2[i]) + assert (i >= len(y2) and vals[3] == '') or approxeq(float(vals[3]), y2[i]) + + assert (i >= len(x3) and vals[4] == '') or approxeq(float(vals[4]), x3[i]) + assert (i >= len(y3) and vals[5] == '') or approxeq(float(vals[5]), y3[i]) + i += 1 + +if __name__ == '__main__': + test_CSVExporter() + \ No newline at end of file diff --git a/papi/pyqtgraph/exporters/tests/test_svg.py b/papi/pyqtgraph/exporters/tests/test_svg.py new file mode 100644 index 00000000..871f43c2 --- /dev/null +++ b/papi/pyqtgraph/exporters/tests/test_svg.py @@ -0,0 +1,67 @@ +""" +SVG export test +""" +import pyqtgraph as pg +import pyqtgraph.exporters +app = pg.mkQApp() + +def test_plotscene(): + pg.setConfigOption('foreground', (0,0,0)) + w = pg.GraphicsWindow() + w.show() + p1 = w.addPlot() + p2 = w.addPlot() + p1.plot([1,3,2,3,1,6,9,8,4,2,3,5,3], pen={'color':'k'}) + p1.setXRange(0,5) + p2.plot([1,5,2,3,4,6,1,2,4,2,3,5,3], pen={'color':'k', 'cosmetic':False, 'width': 0.3}) + app.processEvents() + app.processEvents() + + ex = pg.exporters.SVGExporter(w.scene()) + ex.export(fileName='test.svg') + + +def test_simple(): + scene = pg.QtGui.QGraphicsScene() + #rect = pg.QtGui.QGraphicsRectItem(0, 0, 100, 100) + #scene.addItem(rect) + #rect.setPos(20,20) + #rect.translate(50, 50) + #rect.rotate(30) + #rect.scale(0.5, 0.5) + + #rect1 = pg.QtGui.QGraphicsRectItem(0, 0, 100, 100) + #rect1.setParentItem(rect) + #rect1.setFlag(rect1.ItemIgnoresTransformations) + #rect1.setPos(20, 20) + #rect1.scale(2,2) + + #el1 = pg.QtGui.QGraphicsEllipseItem(0, 0, 100, 100) + #el1.setParentItem(rect1) + ##grp = pg.ItemGroup() + #grp.setParentItem(rect) + #grp.translate(200,0) + ##grp.rotate(30) + + #rect2 = pg.QtGui.QGraphicsRectItem(0, 0, 100, 25) + #rect2.setFlag(rect2.ItemClipsChildrenToShape) + #rect2.setParentItem(grp) + #rect2.setPos(0,25) + #rect2.rotate(30) + #el = pg.QtGui.QGraphicsEllipseItem(0, 0, 100, 50) + #el.translate(10,-5) + #el.scale(0.5,2) + #el.setParentItem(rect2) + + grp2 = pg.ItemGroup() + scene.addItem(grp2) + grp2.scale(100,100) + + rect3 = pg.QtGui.QGraphicsRectItem(0,0,2,2) + rect3.setPen(pg.mkPen(width=1, cosmetic=False)) + grp2.addItem(rect3) + + ex = pg.exporters.SVGExporter(scene) + ex.export(fileName='test.svg') + + diff --git a/papi/pyqtgraph/flowchart/Flowchart.py b/papi/pyqtgraph/flowchart/Flowchart.py new file mode 100644 index 00000000..ab5f4a82 --- /dev/null +++ b/papi/pyqtgraph/flowchart/Flowchart.py @@ -0,0 +1,932 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtCore, QtGui, USE_PYSIDE +from .Node import * +from ..pgcollections import OrderedDict +from ..widgets.TreeWidget import * +from .. import FileDialog, DataTreeWidget + +## pyside and pyqt use incompatible ui files. +if USE_PYSIDE: + from . import FlowchartTemplate_pyside as FlowchartTemplate + from . import FlowchartCtrlTemplate_pyside as FlowchartCtrlTemplate +else: + from . import FlowchartTemplate_pyqt as FlowchartTemplate + from . import FlowchartCtrlTemplate_pyqt as FlowchartCtrlTemplate + +from .Terminal import Terminal +from numpy import ndarray +from .library import LIBRARY +from ..debug import printExc +from .. import configfile as configfile +from .. import dockarea as dockarea +from . import FlowchartGraphicsView +from .. import functions as fn + +def strDict(d): + return dict([(str(k), v) for k, v in d.items()]) + + + + +class Flowchart(Node): + sigFileLoaded = QtCore.Signal(object) + sigFileSaved = QtCore.Signal(object) + + + #sigOutputChanged = QtCore.Signal() ## inherited from Node + sigChartLoaded = QtCore.Signal() + sigStateChanged = QtCore.Signal() # called when output is expected to have changed + sigChartChanged = QtCore.Signal(object, object, object) # called when nodes are added, removed, or renamed. + # (self, action, node) + + def __init__(self, terminals=None, name=None, filePath=None, library=None): + self.library = library or LIBRARY + if name is None: + name = "Flowchart" + if terminals is None: + terminals = {} + self.filePath = filePath + Node.__init__(self, name, allowAddInput=True, allowAddOutput=True) ## create node without terminals; we'll add these later + + + self.inputWasSet = False ## flag allows detection of changes in the absence of input change. + self._nodes = {} + self.nextZVal = 10 + #self.connects = [] + #self._chartGraphicsItem = FlowchartGraphicsItem(self) + self._widget = None + self._scene = None + self.processing = False ## flag that prevents recursive node updates + + self.widget() + + self.inputNode = Node('Input', allowRemove=False, allowAddOutput=True) + self.outputNode = Node('Output', allowRemove=False, allowAddInput=True) + self.addNode(self.inputNode, 'Input', [-150, 0]) + self.addNode(self.outputNode, 'Output', [300, 0]) + + self.outputNode.sigOutputChanged.connect(self.outputChanged) + self.outputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) + self.inputNode.sigTerminalRenamed.connect(self.internalTerminalRenamed) + self.outputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) + self.inputNode.sigTerminalRemoved.connect(self.internalTerminalRemoved) + self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) + self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) + + self.viewBox.autoRange(padding = 0.04) + + for name, opts in terminals.items(): + self.addTerminal(name, **opts) + + def setLibrary(self, lib): + self.library = lib + self.widget().chartWidget.buildMenu() + + def setInput(self, **args): + """Set the input values of the flowchart. This will automatically propagate + the new values throughout the flowchart, (possibly) causing the output to change. + """ + #print "setInput", args + #Node.setInput(self, **args) + #print " ....." + self.inputWasSet = True + self.inputNode.setOutput(**args) + + def outputChanged(self): + ## called when output of internal node has changed + vals = self.outputNode.inputValues() + self.widget().outputChanged(vals) + self.setOutput(**vals) + #self.sigOutputChanged.emit(self) + + def output(self): + """Return a dict of the values on the Flowchart's output terminals. + """ + return self.outputNode.inputValues() + + def nodes(self): + return self._nodes + + def addTerminal(self, name, **opts): + term = Node.addTerminal(self, name, **opts) + name = term.name() + if opts['io'] == 'in': ## inputs to the flowchart become outputs on the input node + opts['io'] = 'out' + opts['multi'] = False + self.inputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) + try: + term2 = self.inputNode.addTerminal(name, **opts) + finally: + self.inputNode.sigTerminalAdded.connect(self.internalTerminalAdded) + + else: + opts['io'] = 'in' + #opts['multi'] = False + self.outputNode.sigTerminalAdded.disconnect(self.internalTerminalAdded) + try: + term2 = self.outputNode.addTerminal(name, **opts) + finally: + self.outputNode.sigTerminalAdded.connect(self.internalTerminalAdded) + return term + + def removeTerminal(self, name): + #print "remove:", name + term = self[name] + inTerm = self.internalTerminal(term) + Node.removeTerminal(self, name) + inTerm.node().removeTerminal(inTerm.name()) + + def internalTerminalRenamed(self, term, oldName): + self[oldName].rename(term.name()) + + def internalTerminalAdded(self, node, term): + if term._io == 'in': + io = 'out' + else: + io = 'in' + Node.addTerminal(self, term.name(), io=io, renamable=term.isRenamable(), removable=term.isRemovable(), multiable=term.isMultiable()) + + def internalTerminalRemoved(self, node, term): + try: + Node.removeTerminal(self, term.name()) + except KeyError: + pass + + def terminalRenamed(self, term, oldName): + newName = term.name() + #print "flowchart rename", newName, oldName + #print self.terminals + Node.terminalRenamed(self, self[oldName], oldName) + #print self.terminals + for n in [self.inputNode, self.outputNode]: + if oldName in n.terminals: + n[oldName].rename(newName) + + def createNode(self, nodeType, name=None, pos=None): + if name is None: + n = 0 + while True: + name = "%s.%d" % (nodeType, n) + if name not in self._nodes: + break + n += 1 + + node = self.library.getNodeType(nodeType)(name) + self.addNode(node, name, pos) + return node + + def addNode(self, node, name, pos=None): + if pos is None: + pos = [0, 0] + if type(pos) in [QtCore.QPoint, QtCore.QPointF]: + pos = [pos.x(), pos.y()] + item = node.graphicsItem() + item.setZValue(self.nextZVal*2) + self.nextZVal += 1 + self.viewBox.addItem(item) + item.moveBy(*pos) + self._nodes[name] = node + self.widget().addNode(node) + node.sigClosed.connect(self.nodeClosed) + node.sigRenamed.connect(self.nodeRenamed) + node.sigOutputChanged.connect(self.nodeOutputChanged) + self.sigChartChanged.emit(self, 'add', node) + + def removeNode(self, node): + node.close() + + def nodeClosed(self, node): + del self._nodes[node.name()] + self.widget().removeNode(node) + for signal in ['sigClosed', 'sigRenamed', 'sigOutputChanged']: + try: + getattr(node, signal).disconnect(self.nodeClosed) + except (TypeError, RuntimeError): + pass + self.sigChartChanged.emit(self, 'remove', node) + + def nodeRenamed(self, node, oldName): + del self._nodes[oldName] + self._nodes[node.name()] = node + self.widget().nodeRenamed(node, oldName) + self.sigChartChanged.emit(self, 'rename', node) + + def arrangeNodes(self): + pass + + def internalTerminal(self, term): + """If the terminal belongs to the external Node, return the corresponding internal terminal""" + if term.node() is self: + if term.isInput(): + return self.inputNode[term.name()] + else: + return self.outputNode[term.name()] + else: + return term + + def connectTerminals(self, term1, term2): + """Connect two terminals together within this flowchart.""" + term1 = self.internalTerminal(term1) + term2 = self.internalTerminal(term2) + term1.connectTo(term2) + + + def process(self, **args): + """ + Process data through the flowchart, returning the output. + + Keyword arguments must be the names of input terminals. + The return value is a dict with one key per output terminal. + + """ + data = {} ## Stores terminal:value pairs + + ## determine order of operations + ## order should look like [('p', node1), ('p', node2), ('d', terminal1), ...] + ## Each tuple specifies either (p)rocess this node or (d)elete the result from this terminal + order = self.processOrder() + #print "ORDER:", order + + ## Record inputs given to process() + for n, t in self.inputNode.outputs().items(): + # if n not in args: + # raise Exception("Parameter %s required to process this chart." % n) + if n in args: + data[t] = args[n] + + ret = {} + + ## process all in order + for c, arg in order: + + if c == 'p': ## Process a single node + #print "===> process:", arg + node = arg + if node is self.inputNode: + continue ## input node has already been processed. + + + ## get input and output terminals for this node + outs = list(node.outputs().values()) + ins = list(node.inputs().values()) + + ## construct input value dictionary + args = {} + for inp in ins: + inputs = inp.inputTerminals() + if len(inputs) == 0: + continue + if inp.isMultiValue(): ## multi-input terminals require a dict of all inputs + args[inp.name()] = dict([(i, data[i]) for i in inputs if i in data]) + else: ## single-inputs terminals only need the single input value available + args[inp.name()] = data[inputs[0]] + + if node is self.outputNode: + ret = args ## we now have the return value, but must keep processing in case there are other endpoint nodes in the chart + else: + try: + if node.isBypassed(): + result = node.processBypassed(args) + else: + result = node.process(display=False, **args) + except: + print("Error processing node %s. Args are: %s" % (str(node), str(args))) + raise + for out in outs: + #print " Output:", out, out.name() + #print out.name() + try: + data[out] = result[out.name()] + except KeyError: + pass + elif c == 'd': ## delete a terminal result (no longer needed; may be holding a lot of memory) + #print "===> delete", arg + if arg in data: + del data[arg] + + return ret + + def processOrder(self): + """Return the order of operations required to process this chart. + The order returned should look like [('p', node1), ('p', node2), ('d', terminal1), ...] + where each tuple specifies either (p)rocess this node or (d)elete the result from this terminal + """ + + ## first collect list of nodes/terminals and their dependencies + deps = {} + tdeps = {} ## {terminal: [nodes that depend on terminal]} + for name, node in self._nodes.items(): + deps[node] = node.dependentNodes() + for t in node.outputs().values(): + tdeps[t] = t.dependentNodes() + + #print "DEPS:", deps + ## determine correct node-processing order + #deps[self] = [] + order = fn.toposort(deps) + #print "ORDER1:", order + + ## construct list of operations + ops = [('p', n) for n in order] + + ## determine when it is safe to delete terminal values + dels = [] + for t, nodes in tdeps.items(): + lastInd = 0 + lastNode = None + for n in nodes: ## determine which node is the last to be processed according to order + if n is self: + lastInd = None + break + else: + try: + ind = order.index(n) + except ValueError: + continue + if lastNode is None or ind > lastInd: + lastNode = n + lastInd = ind + #tdeps[t] = lastNode + if lastInd is not None: + dels.append((lastInd+1, t)) + #dels.sort(lambda a,b: cmp(b[0], a[0])) + dels.sort(key=lambda a: a[0], reverse=True) + for i, t in dels: + ops.insert(i, ('d', t)) + return ops + + + def nodeOutputChanged(self, startNode): + """Triggered when a node's output values have changed. (NOT called during process()) + Propagates new data forward through network.""" + ## first collect list of nodes/terminals and their dependencies + + if self.processing: + return + self.processing = True + try: + deps = {} + for name, node in self._nodes.items(): + deps[node] = [] + for t in node.outputs().values(): + deps[node].extend(t.dependentNodes()) + + ## determine order of updates + order = fn.toposort(deps, nodes=[startNode]) + order.reverse() + + ## keep track of terminals that have been updated + terms = set(startNode.outputs().values()) + + #print "======= Updating", startNode + #print "Order:", order + for node in order[1:]: + #print "Processing node", node + for term in list(node.inputs().values()): + #print " checking terminal", term + deps = list(term.connections().keys()) + update = False + for d in deps: + if d in terms: + #print " ..input", d, "changed" + update = True + term.inputChanged(d, process=False) + if update: + #print " processing.." + node.update() + terms |= set(node.outputs().values()) + + finally: + self.processing = False + if self.inputWasSet: + self.inputWasSet = False + else: + self.sigStateChanged.emit() + + + + def chartGraphicsItem(self): + """Return the graphicsItem which displays the internals of this flowchart. + (graphicsItem() still returns the external-view item)""" + #return self._chartGraphicsItem + return self.viewBox + + def widget(self): + if self._widget is None: + self._widget = FlowchartCtrlWidget(self) + self.scene = self._widget.scene() + self.viewBox = self._widget.viewBox() + #self._scene = QtGui.QGraphicsScene() + #self._widget.setScene(self._scene) + #self.scene.addItem(self.chartGraphicsItem()) + + #ci = self.chartGraphicsItem() + #self.viewBox.addItem(ci) + #self.viewBox.autoRange() + return self._widget + + def listConnections(self): + conn = set() + for n in self._nodes.values(): + terms = n.outputs() + for n, t in terms.items(): + for c in t.connections(): + conn.add((t, c)) + return conn + + def saveState(self): + state = Node.saveState(self) + state['nodes'] = [] + state['connects'] = [] + #state['terminals'] = self.saveTerminals() + + for name, node in self._nodes.items(): + cls = type(node) + if hasattr(cls, 'nodeName'): + clsName = cls.nodeName + pos = node.graphicsItem().pos() + ns = {'class': clsName, 'name': name, 'pos': (pos.x(), pos.y()), 'state': node.saveState()} + state['nodes'].append(ns) + + conn = self.listConnections() + for a, b in conn: + state['connects'].append((a.node().name(), a.name(), b.node().name(), b.name())) + + state['inputNode'] = self.inputNode.saveState() + state['outputNode'] = self.outputNode.saveState() + + return state + + def restoreState(self, state, clear=False): + self.blockSignals(True) + try: + if clear: + self.clear() + Node.restoreState(self, state) + nodes = state['nodes'] + #nodes.sort(lambda a, b: cmp(a['pos'][0], b['pos'][0])) + nodes.sort(key=lambda a: a['pos'][0]) + for n in nodes: + if n['name'] in self._nodes: + #self._nodes[n['name']].graphicsItem().moveBy(*n['pos']) + self._nodes[n['name']].restoreState(n['state']) + continue + try: + node = self.createNode(n['class'], name=n['name']) + node.restoreState(n['state']) + except: + printExc("Error creating node %s: (continuing anyway)" % n['name']) + #node.graphicsItem().moveBy(*n['pos']) + + self.inputNode.restoreState(state.get('inputNode', {})) + self.outputNode.restoreState(state.get('outputNode', {})) + + #self.restoreTerminals(state['terminals']) + for n1, t1, n2, t2 in state['connects']: + try: + self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2]) + except: + print(self._nodes[n1].terminals) + print(self._nodes[n2].terminals) + printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2)) + + + finally: + self.blockSignals(False) + + self.sigChartLoaded.emit() + self.outputChanged() + self.sigStateChanged.emit() + #self.sigOutputChanged.emit() + + def loadFile(self, fileName=None, startDir=None): + if fileName is None: + if startDir is None: + startDir = self.filePath + if startDir is None: + startDir = '.' + self.fileDialog = FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") + #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + #self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.loadFile) + return + ## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs.. + #fileName = QtGui.QFileDialog.getOpenFileName(None, "Load Flowchart..", startDir, "Flowchart (*.fc)") + fileName = unicode(fileName) + state = configfile.readConfigFile(fileName) + self.restoreState(state, clear=True) + self.viewBox.autoRange() + #self.emit(QtCore.SIGNAL('fileLoaded'), fileName) + self.sigFileLoaded.emit(fileName) + + def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc'): + if fileName is None: + if startDir is None: + startDir = self.filePath + if startDir is None: + startDir = '.' + self.fileDialog = FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") + #self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + #self.fileDialog.setDirectory(startDir) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.saveFile) + return + #fileName = QtGui.QFileDialog.getSaveFileName(None, "Save Flowchart..", startDir, "Flowchart (*.fc)") + fileName = unicode(fileName) + configfile.writeConfigFile(self.saveState(), fileName) + self.sigFileSaved.emit(fileName) + + def clear(self): + for n in list(self._nodes.values()): + if n is self.inputNode or n is self.outputNode: + continue + n.close() ## calls self.nodeClosed(n) by signal + #self.clearTerminals() + self.widget().clear() + + def clearTerminals(self): + Node.clearTerminals(self) + self.inputNode.clearTerminals() + self.outputNode.clearTerminals() + +#class FlowchartGraphicsItem(QtGui.QGraphicsItem): +class FlowchartGraphicsItem(GraphicsObject): + + def __init__(self, chart): + #print "FlowchartGraphicsItem.__init__" + #QtGui.QGraphicsItem.__init__(self) + GraphicsObject.__init__(self) + self.chart = chart ## chart is an instance of Flowchart() + self.updateTerminals() + + def updateTerminals(self): + #print "FlowchartGraphicsItem.updateTerminals" + self.terminals = {} + bounds = self.boundingRect() + inp = self.chart.inputs() + dy = bounds.height() / (len(inp)+1) + y = dy + for n, t in inp.items(): + item = t.graphicsItem() + self.terminals[n] = item + item.setParentItem(self) + item.setAnchor(bounds.width(), y) + y += dy + out = self.chart.outputs() + dy = bounds.height() / (len(out)+1) + y = dy + for n, t in out.items(): + item = t.graphicsItem() + self.terminals[n] = item + item.setParentItem(self) + item.setAnchor(0, y) + y += dy + + def boundingRect(self): + #print "FlowchartGraphicsItem.boundingRect" + return QtCore.QRectF() + + def paint(self, p, *args): + #print "FlowchartGraphicsItem.paint" + pass + #p.drawRect(self.boundingRect()) + + +class FlowchartCtrlWidget(QtGui.QWidget): + """The widget that contains the list of all the nodes in a flowchart and their controls, as well as buttons for loading/saving flowcharts.""" + + def __init__(self, chart): + self.items = {} + #self.loadDir = loadDir ## where to look initially for chart files + self.currentFileName = None + QtGui.QWidget.__init__(self) + self.chart = chart + self.ui = FlowchartCtrlTemplate.Ui_Form() + self.ui.setupUi(self) + self.ui.ctrlList.setColumnCount(2) + #self.ui.ctrlList.setColumnWidth(0, 200) + self.ui.ctrlList.setColumnWidth(1, 20) + self.ui.ctrlList.setVerticalScrollMode(self.ui.ctrlList.ScrollPerPixel) + self.ui.ctrlList.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + + self.chartWidget = FlowchartWidget(chart, self) + #self.chartWidget.viewBox().autoRange() + self.cwWin = QtGui.QMainWindow() + self.cwWin.setWindowTitle('Flowchart') + self.cwWin.setCentralWidget(self.chartWidget) + self.cwWin.resize(1000,800) + + h = self.ui.ctrlList.header() + h.setResizeMode(0, h.Stretch) + + self.ui.ctrlList.itemChanged.connect(self.itemChanged) + self.ui.loadBtn.clicked.connect(self.loadClicked) + self.ui.saveBtn.clicked.connect(self.saveClicked) + self.ui.saveAsBtn.clicked.connect(self.saveAsClicked) + self.ui.showChartBtn.toggled.connect(self.chartToggled) + self.chart.sigFileLoaded.connect(self.setCurrentFile) + self.ui.reloadBtn.clicked.connect(self.reloadClicked) + self.chart.sigFileSaved.connect(self.fileSaved) + + + + #def resizeEvent(self, ev): + #QtGui.QWidget.resizeEvent(self, ev) + #self.ui.ctrlList.setColumnWidth(0, self.ui.ctrlList.viewport().width()-20) + + def chartToggled(self, b): + if b: + self.cwWin.show() + else: + self.cwWin.hide() + + def reloadClicked(self): + try: + self.chartWidget.reloadLibrary() + self.ui.reloadBtn.success("Reloaded.") + except: + self.ui.reloadBtn.success("Error.") + raise + + + def loadClicked(self): + newFile = self.chart.loadFile() + #self.setCurrentFile(newFile) + + def fileSaved(self, fileName): + self.setCurrentFile(unicode(fileName)) + self.ui.saveBtn.success("Saved.") + + def saveClicked(self): + if self.currentFileName is None: + self.saveAsClicked() + else: + try: + self.chart.saveFile(self.currentFileName) + #self.ui.saveBtn.success("Saved.") + except: + self.ui.saveBtn.failure("Error") + raise + + def saveAsClicked(self): + try: + if self.currentFileName is None: + newFile = self.chart.saveFile() + else: + newFile = self.chart.saveFile(suggestedFileName=self.currentFileName) + #self.ui.saveAsBtn.success("Saved.") + #print "Back to saveAsClicked." + except: + self.ui.saveBtn.failure("Error") + raise + + #self.setCurrentFile(newFile) + + def setCurrentFile(self, fileName): + self.currentFileName = unicode(fileName) + if fileName is None: + self.ui.fileNameLabel.setText("[ new ]") + else: + self.ui.fileNameLabel.setText("%s" % os.path.split(self.currentFileName)[1]) + self.resizeEvent(None) + + def itemChanged(self, *args): + pass + + def scene(self): + return self.chartWidget.scene() ## returns the GraphicsScene object + + def viewBox(self): + return self.chartWidget.viewBox() + + def nodeRenamed(self, node, oldName): + self.items[node].setText(0, node.name()) + + def addNode(self, node): + ctrl = node.ctrlWidget() + #if ctrl is None: + #return + item = QtGui.QTreeWidgetItem([node.name(), '', '']) + self.ui.ctrlList.addTopLevelItem(item) + byp = QtGui.QPushButton('X') + byp.setCheckable(True) + byp.setFixedWidth(20) + item.bypassBtn = byp + self.ui.ctrlList.setItemWidget(item, 1, byp) + byp.node = node + node.bypassButton = byp + byp.setChecked(node.isBypassed()) + byp.clicked.connect(self.bypassClicked) + + if ctrl is not None: + item2 = QtGui.QTreeWidgetItem() + item.addChild(item2) + self.ui.ctrlList.setItemWidget(item2, 0, ctrl) + + self.items[node] = item + + def removeNode(self, node): + if node in self.items: + item = self.items[node] + #self.disconnect(item.bypassBtn, QtCore.SIGNAL('clicked()'), self.bypassClicked) + try: + item.bypassBtn.clicked.disconnect(self.bypassClicked) + except (TypeError, RuntimeError): + pass + self.ui.ctrlList.removeTopLevelItem(item) + + def bypassClicked(self): + btn = QtCore.QObject.sender(self) + btn.node.bypass(btn.isChecked()) + + def chartWidget(self): + return self.chartWidget + + def outputChanged(self, data): + pass + #self.ui.outputTree.setData(data, hideRoot=True) + + def clear(self): + self.chartWidget.clear() + + def select(self, node): + item = self.items[node] + self.ui.ctrlList.setCurrentItem(item) + +class FlowchartWidget(dockarea.DockArea): + """Includes the actual graphical flowchart and debugging interface""" + def __init__(self, chart, ctrl): + #QtGui.QWidget.__init__(self) + dockarea.DockArea.__init__(self) + self.chart = chart + self.ctrl = ctrl + self.hoverItem = None + #self.setMinimumWidth(250) + #self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)) + + #self.ui = FlowchartTemplate.Ui_Form() + #self.ui.setupUi(self) + + ## build user interface (it was easier to do it here than via developer) + self.view = FlowchartGraphicsView.FlowchartGraphicsView(self) + self.viewDock = dockarea.Dock('view', size=(1000,600)) + self.viewDock.addWidget(self.view) + self.viewDock.hideTitleBar() + self.addDock(self.viewDock) + + + self.hoverText = QtGui.QTextEdit() + self.hoverText.setReadOnly(True) + self.hoverDock = dockarea.Dock('Hover Info', size=(1000,20)) + self.hoverDock.addWidget(self.hoverText) + self.addDock(self.hoverDock, 'bottom') + + self.selInfo = QtGui.QWidget() + self.selInfoLayout = QtGui.QGridLayout() + self.selInfo.setLayout(self.selInfoLayout) + self.selDescLabel = QtGui.QLabel() + self.selNameLabel = QtGui.QLabel() + self.selDescLabel.setWordWrap(True) + self.selectedTree = DataTreeWidget() + #self.selectedTree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + #self.selInfoLayout.addWidget(self.selNameLabel) + self.selInfoLayout.addWidget(self.selDescLabel) + self.selInfoLayout.addWidget(self.selectedTree) + self.selDock = dockarea.Dock('Selected Node', size=(1000,200)) + self.selDock.addWidget(self.selInfo) + self.addDock(self.selDock, 'bottom') + + self._scene = self.view.scene() + self._viewBox = self.view.viewBox() + #self._scene = QtGui.QGraphicsScene() + #self._scene = FlowchartGraphicsView.FlowchartGraphicsScene() + #self.view.setScene(self._scene) + + self.buildMenu() + #self.ui.addNodeBtn.mouseReleaseEvent = self.addNodeBtnReleased + + self._scene.selectionChanged.connect(self.selectionChanged) + self._scene.sigMouseHover.connect(self.hoverOver) + #self.view.sigClicked.connect(self.showViewMenu) + #self._scene.sigSceneContextMenu.connect(self.showViewMenu) + #self._viewBox.sigActionPositionChanged.connect(self.menuPosChanged) + + + def reloadLibrary(self): + #QtCore.QObject.disconnect(self.nodeMenu, QtCore.SIGNAL('triggered(QAction*)'), self.nodeMenuTriggered) + self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered) + self.nodeMenu = None + self.subMenus = [] + self.chart.library.reload() + self.buildMenu() + + def buildMenu(self, pos=None): + def buildSubMenu(node, rootMenu, subMenus, pos=None): + for section, node in node.items(): + menu = QtGui.QMenu(section) + rootMenu.addMenu(menu) + if isinstance(node, OrderedDict): + buildSubMenu(node, menu, subMenus, pos=pos) + subMenus.append(menu) + else: + act = rootMenu.addAction(section) + act.nodeType = section + act.pos = pos + self.nodeMenu = QtGui.QMenu() + self.subMenus = [] + buildSubMenu(self.chart.library.getNodeTree(), self.nodeMenu, self.subMenus, pos=pos) + self.nodeMenu.triggered.connect(self.nodeMenuTriggered) + return self.nodeMenu + + def menuPosChanged(self, pos): + self.menuPos = pos + + def showViewMenu(self, ev): + #QtGui.QPushButton.mouseReleaseEvent(self.ui.addNodeBtn, ev) + #if ev.button() == QtCore.Qt.RightButton: + #self.menuPos = self.view.mapToScene(ev.pos()) + #self.nodeMenu.popup(ev.globalPos()) + #print "Flowchart.showViewMenu called" + + #self.menuPos = ev.scenePos() + self.buildMenu(ev.scenePos()) + self.nodeMenu.popup(ev.screenPos()) + + def scene(self): + return self._scene ## the GraphicsScene item + + def viewBox(self): + return self._viewBox ## the viewBox that items should be added to + + def nodeMenuTriggered(self, action): + nodeType = action.nodeType + if action.pos is not None: + pos = action.pos + else: + pos = self.menuPos + pos = self.viewBox().mapSceneToView(pos) + + self.chart.createNode(nodeType, pos=pos) + + + def selectionChanged(self): + #print "FlowchartWidget.selectionChanged called." + items = self._scene.selectedItems() + #print " scene.selectedItems: ", items + if len(items) == 0: + data = None + else: + item = items[0] + if hasattr(item, 'node') and isinstance(item.node, Node): + n = item.node + self.ctrl.select(n) + data = {'outputs': n.outputValues(), 'inputs': n.inputValues()} + self.selNameLabel.setText(n.name()) + if hasattr(n, 'nodeName'): + self.selDescLabel.setText("%s: %s" % (n.nodeName, n.__class__.__doc__)) + else: + self.selDescLabel.setText("") + if n.exception is not None: + data['exception'] = n.exception + else: + data = None + self.selectedTree.setData(data, hideRoot=True) + + def hoverOver(self, items): + #print "FlowchartWidget.hoverOver called." + term = None + for item in items: + if item is self.hoverItem: + return + self.hoverItem = item + if hasattr(item, 'term') and isinstance(item.term, Terminal): + term = item.term + break + if term is None: + self.hoverText.setPlainText("") + else: + val = term.value() + if isinstance(val, ndarray): + val = "%s %s %s" % (type(val).__name__, str(val.shape), str(val.dtype)) + else: + val = str(val) + if len(val) > 400: + val = val[:400] + "..." + self.hoverText.setPlainText("%s.%s = %s" % (term.node().name(), term.name(), val)) + #self.hoverLabel.setCursorPosition(0) + + + + def clear(self): + #self.outputTree.setData(None) + self.selectedTree.setData(None) + self.hoverText.setPlainText('') + self.selNameLabel.setText('') + self.selDescLabel.setText('') + + +class FlowchartNode(Node): + pass + diff --git a/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui new file mode 100644 index 00000000..0361ad3e --- /dev/null +++ b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui @@ -0,0 +1,120 @@ + + + Form + + + + 0 + 0 + 217 + 499 + + + + Form + + + + 0 + + + 0 + + + + + Load.. + + + + + + + + + + + + + + + + Flowchart + + + true + + + + + + + false + + + false + + + false + + + false + + + + 1 + + + + + + + + + 75 + true + + + + + + + Qt::AlignCenter + + + + + + + + TreeWidget + QTreeWidget +
..widgets.TreeWidget
+
+ + FeedbackButton + QPushButton +
..widgets.FeedbackButton
+
+
+ + +
diff --git a/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py new file mode 100644 index 00000000..8afd43f8 --- /dev/null +++ b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartCtrlTemplate.ui' +# +# Created: Mon Dec 23 10:10:50 2013 +# by: PyQt4 UI code generator 4.10 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(217, 499) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setVerticalSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.loadBtn = QtGui.QPushButton(Form) + self.loadBtn.setObjectName(_fromUtf8("loadBtn")) + self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) + self.saveBtn = FeedbackButton(Form) + self.saveBtn.setObjectName(_fromUtf8("saveBtn")) + self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) + self.saveAsBtn = FeedbackButton(Form) + self.saveAsBtn.setObjectName(_fromUtf8("saveAsBtn")) + self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) + self.reloadBtn = FeedbackButton(Form) + self.reloadBtn.setCheckable(False) + self.reloadBtn.setFlat(False) + self.reloadBtn.setObjectName(_fromUtf8("reloadBtn")) + self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) + self.showChartBtn = QtGui.QPushButton(Form) + self.showChartBtn.setCheckable(True) + self.showChartBtn.setObjectName(_fromUtf8("showChartBtn")) + self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) + self.ctrlList = TreeWidget(Form) + self.ctrlList.setObjectName(_fromUtf8("ctrlList")) + self.ctrlList.headerItem().setText(0, _fromUtf8("1")) + self.ctrlList.header().setVisible(False) + self.ctrlList.header().setStretchLastSection(False) + self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) + self.fileNameLabel = QtGui.QLabel(Form) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.fileNameLabel.setFont(font) + self.fileNameLabel.setText(_fromUtf8("")) + self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) + self.fileNameLabel.setObjectName(_fromUtf8("fileNameLabel")) + self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(_translate("Form", "Form", None)) + self.loadBtn.setText(_translate("Form", "Load..", None)) + self.saveBtn.setText(_translate("Form", "Save", None)) + self.saveAsBtn.setText(_translate("Form", "As..", None)) + self.reloadBtn.setText(_translate("Form", "Reload Libs", None)) + self.showChartBtn.setText(_translate("Form", "Flowchart", None)) + +from ..widgets.TreeWidget import TreeWidget +from ..widgets.FeedbackButton import FeedbackButton diff --git a/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py new file mode 100644 index 00000000..b722000e --- /dev/null +++ b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartCtrlTemplate.ui' +# +# Created: Mon Dec 23 10:10:51 2013 +# by: pyside-uic 0.2.14 running on PySide 1.1.2 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(217, 499) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setVerticalSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.loadBtn = QtGui.QPushButton(Form) + self.loadBtn.setObjectName("loadBtn") + self.gridLayout.addWidget(self.loadBtn, 1, 0, 1, 1) + self.saveBtn = FeedbackButton(Form) + self.saveBtn.setObjectName("saveBtn") + self.gridLayout.addWidget(self.saveBtn, 1, 1, 1, 2) + self.saveAsBtn = FeedbackButton(Form) + self.saveAsBtn.setObjectName("saveAsBtn") + self.gridLayout.addWidget(self.saveAsBtn, 1, 3, 1, 1) + self.reloadBtn = FeedbackButton(Form) + self.reloadBtn.setCheckable(False) + self.reloadBtn.setFlat(False) + self.reloadBtn.setObjectName("reloadBtn") + self.gridLayout.addWidget(self.reloadBtn, 4, 0, 1, 2) + self.showChartBtn = QtGui.QPushButton(Form) + self.showChartBtn.setCheckable(True) + self.showChartBtn.setObjectName("showChartBtn") + self.gridLayout.addWidget(self.showChartBtn, 4, 2, 1, 2) + self.ctrlList = TreeWidget(Form) + self.ctrlList.setObjectName("ctrlList") + self.ctrlList.headerItem().setText(0, "1") + self.ctrlList.header().setVisible(False) + self.ctrlList.header().setStretchLastSection(False) + self.gridLayout.addWidget(self.ctrlList, 3, 0, 1, 4) + self.fileNameLabel = QtGui.QLabel(Form) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.fileNameLabel.setFont(font) + self.fileNameLabel.setText("") + self.fileNameLabel.setAlignment(QtCore.Qt.AlignCenter) + self.fileNameLabel.setObjectName("fileNameLabel") + self.gridLayout.addWidget(self.fileNameLabel, 0, 1, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load..", None, QtGui.QApplication.UnicodeUTF8)) + self.saveBtn.setText(QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.saveAsBtn.setText(QtGui.QApplication.translate("Form", "As..", None, QtGui.QApplication.UnicodeUTF8)) + self.reloadBtn.setText(QtGui.QApplication.translate("Form", "Reload Libs", None, QtGui.QApplication.UnicodeUTF8)) + self.showChartBtn.setText(QtGui.QApplication.translate("Form", "Flowchart", None, QtGui.QApplication.UnicodeUTF8)) + +from ..widgets.TreeWidget import TreeWidget +from ..widgets.FeedbackButton import FeedbackButton diff --git a/papi/pyqtgraph/flowchart/FlowchartGraphicsView.py b/papi/pyqtgraph/flowchart/FlowchartGraphicsView.py new file mode 100644 index 00000000..ab4b2914 --- /dev/null +++ b/papi/pyqtgraph/flowchart/FlowchartGraphicsView.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore +from ..widgets.GraphicsView import GraphicsView +from ..GraphicsScene import GraphicsScene +from ..graphicsItems.ViewBox import ViewBox + +#class FlowchartGraphicsView(QtGui.QGraphicsView): +class FlowchartGraphicsView(GraphicsView): + + sigHoverOver = QtCore.Signal(object) + sigClicked = QtCore.Signal(object) + + def __init__(self, widget, *args): + #QtGui.QGraphicsView.__init__(self, *args) + GraphicsView.__init__(self, *args, useOpenGL=False) + #self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(255,255,255))) + self._vb = FlowchartViewBox(widget, lockAspect=True, invertY=True) + self.setCentralItem(self._vb) + #self.scene().addItem(self.vb) + #self.setMouseTracking(True) + #self.lastPos = None + #self.setTransformationAnchor(self.AnchorViewCenter) + #self.setRenderHints(QtGui.QPainter.Antialiasing) + self.setRenderHint(QtGui.QPainter.Antialiasing, True) + #self.setDragMode(QtGui.QGraphicsView.RubberBandDrag) + #self.setRubberBandSelectionMode(QtCore.Qt.ContainsItemBoundingRect) + + def viewBox(self): + return self._vb + + + #def mousePressEvent(self, ev): + #self.moved = False + #self.lastPos = ev.pos() + #return QtGui.QGraphicsView.mousePressEvent(self, ev) + + #def mouseMoveEvent(self, ev): + #self.moved = True + #callSuper = False + #if ev.buttons() & QtCore.Qt.RightButton: + #if self.lastPos is not None: + #dif = ev.pos() - self.lastPos + #self.scale(1.01**-dif.y(), 1.01**-dif.y()) + #elif ev.buttons() & QtCore.Qt.MidButton: + #if self.lastPos is not None: + #dif = ev.pos() - self.lastPos + #self.translate(dif.x(), -dif.y()) + #else: + ##self.emit(QtCore.SIGNAL('hoverOver'), self.items(ev.pos())) + #self.sigHoverOver.emit(self.items(ev.pos())) + #callSuper = True + #self.lastPos = ev.pos() + + #if callSuper: + #QtGui.QGraphicsView.mouseMoveEvent(self, ev) + + #def mouseReleaseEvent(self, ev): + #if not self.moved: + ##self.emit(QtCore.SIGNAL('clicked'), ev) + #self.sigClicked.emit(ev) + #return QtGui.QGraphicsView.mouseReleaseEvent(self, ev) + +class FlowchartViewBox(ViewBox): + + def __init__(self, widget, *args, **kwargs): + ViewBox.__init__(self, *args, **kwargs) + self.widget = widget + #self.menu = None + #self._subMenus = None ## need a place to store the menus otherwise they dissappear (even though they've been added to other menus) ((yes, it doesn't make sense)) + + + + + def getMenu(self, ev): + ## called by ViewBox to create a new context menu + self._fc_menu = QtGui.QMenu() + self._subMenus = self.getContextMenus(ev) + for menu in self._subMenus: + self._fc_menu.addMenu(menu) + return self._fc_menu + + def getContextMenus(self, ev): + ## called by scene to add menus on to someone else's context menu + menu = self.widget.buildMenu(ev.scenePos()) + menu.setTitle("Add node") + return [menu, ViewBox.getMenu(self, ev)] + + + + + + + + + + +##class FlowchartGraphicsScene(QtGui.QGraphicsScene): +#class FlowchartGraphicsScene(GraphicsScene): + + #sigContextMenuEvent = QtCore.Signal(object) + + #def __init__(self, *args): + ##QtGui.QGraphicsScene.__init__(self, *args) + #GraphicsScene.__init__(self, *args) + + #def mouseClickEvent(self, ev): + ##QtGui.QGraphicsScene.contextMenuEvent(self, ev) + #if not ev.button() in [QtCore.Qt.RightButton]: + #self.sigContextMenuEvent.emit(ev) \ No newline at end of file diff --git a/papi/pyqtgraph/flowchart/FlowchartTemplate.ui b/papi/pyqtgraph/flowchart/FlowchartTemplate.ui new file mode 100644 index 00000000..8b0c19da --- /dev/null +++ b/papi/pyqtgraph/flowchart/FlowchartTemplate.ui @@ -0,0 +1,98 @@ + + + Form + + + + 0 + 0 + 529 + 329 + + + + Form + + + + + 260 + 10 + 264 + 222 + + + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + + + + + + 75 + true + + + + + + + + + + + + 1 + + + + + + + + + + 0 + 240 + 521 + 81 + + + + + + + 0 + 0 + 256 + 192 + + + + + + + DataTreeWidget + QTreeWidget +
..widgets.DataTreeWidget
+
+ + FlowchartGraphicsView + QGraphicsView +
..flowchart.FlowchartGraphicsView
+
+
+ + +
diff --git a/papi/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py b/papi/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py new file mode 100644 index 00000000..06b10bfe --- /dev/null +++ b/papi/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartTemplate.ui' +# +# Created: Mon Dec 23 10:10:51 2013 +# by: PyQt4 UI code generator 4.10 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(529, 329) + self.selInfoWidget = QtGui.QWidget(Form) + self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) + self.selInfoWidget.setObjectName(_fromUtf8("selInfoWidget")) + self.gridLayout = QtGui.QGridLayout(self.selInfoWidget) + self.gridLayout.setMargin(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.selDescLabel = QtGui.QLabel(self.selInfoWidget) + self.selDescLabel.setText(_fromUtf8("")) + self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.selDescLabel.setWordWrap(True) + self.selDescLabel.setObjectName(_fromUtf8("selDescLabel")) + self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) + self.selNameLabel = QtGui.QLabel(self.selInfoWidget) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.selNameLabel.setFont(font) + self.selNameLabel.setText(_fromUtf8("")) + self.selNameLabel.setObjectName(_fromUtf8("selNameLabel")) + self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) + self.selectedTree = DataTreeWidget(self.selInfoWidget) + self.selectedTree.setObjectName(_fromUtf8("selectedTree")) + self.selectedTree.headerItem().setText(0, _fromUtf8("1")) + self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) + self.hoverText = QtGui.QTextEdit(Form) + self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) + self.hoverText.setObjectName(_fromUtf8("hoverText")) + self.view = FlowchartGraphicsView(Form) + self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) + self.view.setObjectName(_fromUtf8("view")) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(_translate("Form", "Form", None)) + +from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView +from ..widgets.DataTreeWidget import DataTreeWidget diff --git a/papi/pyqtgraph/flowchart/FlowchartTemplate_pyside.py b/papi/pyqtgraph/flowchart/FlowchartTemplate_pyside.py new file mode 100644 index 00000000..2c693c60 --- /dev/null +++ b/papi/pyqtgraph/flowchart/FlowchartTemplate_pyside.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartTemplate.ui' +# +# Created: Mon Dec 23 10:10:51 2013 +# by: pyside-uic 0.2.14 running on PySide 1.1.2 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(529, 329) + self.selInfoWidget = QtGui.QWidget(Form) + self.selInfoWidget.setGeometry(QtCore.QRect(260, 10, 264, 222)) + self.selInfoWidget.setObjectName("selInfoWidget") + self.gridLayout = QtGui.QGridLayout(self.selInfoWidget) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.selDescLabel = QtGui.QLabel(self.selInfoWidget) + self.selDescLabel.setText("") + self.selDescLabel.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop) + self.selDescLabel.setWordWrap(True) + self.selDescLabel.setObjectName("selDescLabel") + self.gridLayout.addWidget(self.selDescLabel, 0, 0, 1, 1) + self.selNameLabel = QtGui.QLabel(self.selInfoWidget) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.selNameLabel.setFont(font) + self.selNameLabel.setText("") + self.selNameLabel.setObjectName("selNameLabel") + self.gridLayout.addWidget(self.selNameLabel, 0, 1, 1, 1) + self.selectedTree = DataTreeWidget(self.selInfoWidget) + self.selectedTree.setObjectName("selectedTree") + self.selectedTree.headerItem().setText(0, "1") + self.gridLayout.addWidget(self.selectedTree, 1, 0, 1, 2) + self.hoverText = QtGui.QTextEdit(Form) + self.hoverText.setGeometry(QtCore.QRect(0, 240, 521, 81)) + self.hoverText.setObjectName("hoverText") + self.view = FlowchartGraphicsView(Form) + self.view.setGeometry(QtCore.QRect(0, 0, 256, 192)) + self.view.setObjectName("view") + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + +from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView +from ..widgets.DataTreeWidget import DataTreeWidget diff --git a/papi/pyqtgraph/flowchart/Node.py b/papi/pyqtgraph/flowchart/Node.py new file mode 100644 index 00000000..fc7b04d3 --- /dev/null +++ b/papi/pyqtgraph/flowchart/Node.py @@ -0,0 +1,644 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtCore, QtGui +from ..graphicsItems.GraphicsObject import GraphicsObject +from .. import functions as fn +from .Terminal import * +from ..pgcollections import OrderedDict +from ..debug import * +import numpy as np +from .eq import * + + +def strDict(d): + return dict([(str(k), v) for k, v in d.items()]) + +class Node(QtCore.QObject): + """ + Node represents the basic processing unit of a flowchart. + A Node subclass implements at least: + + 1) A list of input / ouptut terminals and their properties + 2) a process() function which takes the names of input terminals as keyword arguments and returns a dict with the names of output terminals as keys. + + A flowchart thus consists of multiple instances of Node subclasses, each of which is connected + to other by wires between their terminals. A flowchart is, itself, also a special subclass of Node. + This allows Nodes within the flowchart to connect to the input/output nodes of the flowchart itself. + + Optionally, a node class can implement the ctrlWidget() method, which must return a QWidget (usually containing other widgets) that will be displayed in the flowchart control panel. Some nodes implement fairly complex control widgets, but most nodes follow a simple form-like pattern: a list of parameter names and a single value (represented as spin box, check box, etc..) for each parameter. To make this easier, the CtrlNode subclass allows you to instead define a simple data structure that CtrlNode will use to automatically generate the control widget. """ + + sigOutputChanged = QtCore.Signal(object) # self + sigClosed = QtCore.Signal(object) + sigRenamed = QtCore.Signal(object, object) + sigTerminalRenamed = QtCore.Signal(object, object) # term, oldName + sigTerminalAdded = QtCore.Signal(object, object) # self, term + sigTerminalRemoved = QtCore.Signal(object, object) # self, term + + + def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True): + """ + ============== ============================================================ + **Arguments:** + name The name of this specific node instance. It can be any + string, but must be unique within a flowchart. Usually, + we simply let the flowchart decide on a name when calling + Flowchart.addNode(...) + terminals Dict-of-dicts specifying the terminals present on this Node. + Terminal specifications look like:: + + 'inputTerminalName': {'io': 'in'} + 'outputTerminalName': {'io': 'out'} + + There are a number of optional parameters for terminals: + multi, pos, renamable, removable, multiable, bypass. See + the Terminal class for more information. + allowAddInput bool; whether the user is allowed to add inputs by the + context menu. + allowAddOutput bool; whether the user is allowed to add outputs by the + context menu. + allowRemove bool; whether the user is allowed to remove this node by the + context menu. + ============== ============================================================ + + """ + QtCore.QObject.__init__(self) + self._name = name + self._bypass = False + self.bypassButton = None ## this will be set by the flowchart ctrl widget.. + self._graphicsItem = None + self.terminals = OrderedDict() + self._inputs = OrderedDict() + self._outputs = OrderedDict() + self._allowAddInput = allowAddInput ## flags to allow the user to add/remove terminals + self._allowAddOutput = allowAddOutput + self._allowRemove = allowRemove + + self.exception = None + if terminals is None: + return + for name, opts in terminals.items(): + self.addTerminal(name, **opts) + + + def nextTerminalName(self, name): + """Return an unused terminal name""" + name2 = name + i = 1 + while name2 in self.terminals: + name2 = "%s.%d" % (name, i) + i += 1 + return name2 + + def addInput(self, name="Input", **args): + """Add a new input terminal to this Node with the given name. Extra + keyword arguments are passed to Terminal.__init__. + + This is a convenience function that just calls addTerminal(io='in', ...)""" + #print "Node.addInput called." + return self.addTerminal(name, io='in', **args) + + def addOutput(self, name="Output", **args): + """Add a new output terminal to this Node with the given name. Extra + keyword arguments are passed to Terminal.__init__. + + This is a convenience function that just calls addTerminal(io='out', ...)""" + return self.addTerminal(name, io='out', **args) + + def removeTerminal(self, term): + """Remove the specified terminal from this Node. May specify either the + terminal's name or the terminal itself. + + Causes sigTerminalRemoved to be emitted.""" + if isinstance(term, Terminal): + name = term.name() + else: + name = term + term = self.terminals[name] + + #print "remove", name + #term.disconnectAll() + term.close() + del self.terminals[name] + if name in self._inputs: + del self._inputs[name] + if name in self._outputs: + del self._outputs[name] + self.graphicsItem().updateTerminals() + self.sigTerminalRemoved.emit(self, term) + + + def terminalRenamed(self, term, oldName): + """Called after a terminal has been renamed + + Causes sigTerminalRenamed to be emitted.""" + newName = term.name() + for d in [self.terminals, self._inputs, self._outputs]: + if oldName not in d: + continue + d[newName] = d[oldName] + del d[oldName] + + self.graphicsItem().updateTerminals() + self.sigTerminalRenamed.emit(term, oldName) + + def addTerminal(self, name, **opts): + """Add a new terminal to this Node with the given name. Extra + keyword arguments are passed to Terminal.__init__. + + Causes sigTerminalAdded to be emitted.""" + name = self.nextTerminalName(name) + term = Terminal(self, name, **opts) + self.terminals[name] = term + if term.isInput(): + self._inputs[name] = term + elif term.isOutput(): + self._outputs[name] = term + self.graphicsItem().updateTerminals() + self.sigTerminalAdded.emit(self, term) + return term + + + def inputs(self): + """Return dict of all input terminals. + Warning: do not modify.""" + return self._inputs + + def outputs(self): + """Return dict of all output terminals. + Warning: do not modify.""" + return self._outputs + + def process(self, **kargs): + """Process data through this node. This method is called any time the flowchart + wants the node to process data. It will be called with one keyword argument + corresponding to each input terminal, and must return a dict mapping the name + of each output terminal to its new value. + + This method is also called with a 'display' keyword argument, which indicates + whether the node should update its display (if it implements any) while processing + this data. This is primarily used to disable expensive display operations + during batch processing. + """ + return {} + + def graphicsItem(self): + """Return the GraphicsItem for this node. Subclasses may re-implement + this method to customize their appearance in the flowchart.""" + if self._graphicsItem is None: + self._graphicsItem = NodeGraphicsItem(self) + return self._graphicsItem + + ## this is just bad planning. Causes too many bugs. + def __getattr__(self, attr): + """Return the terminal with the given name""" + if attr not in self.terminals: + raise AttributeError(attr) + else: + import traceback + traceback.print_stack() + print("Warning: use of node.terminalName is deprecated; use node['terminalName'] instead.") + return self.terminals[attr] + + def __getitem__(self, item): + #return getattr(self, item) + """Return the terminal with the given name""" + if item not in self.terminals: + raise KeyError(item) + else: + return self.terminals[item] + + def name(self): + """Return the name of this node.""" + return self._name + + def rename(self, name): + """Rename this node. This will cause sigRenamed to be emitted.""" + oldName = self._name + self._name = name + #self.emit(QtCore.SIGNAL('renamed'), self, oldName) + self.sigRenamed.emit(self, oldName) + + def dependentNodes(self): + """Return the list of nodes which provide direct input to this node""" + nodes = set() + for t in self.inputs().values(): + nodes |= set([i.node() for i in t.inputTerminals()]) + return nodes + #return set([t.inputTerminals().node() for t in self.listInputs().itervalues()]) + + def __repr__(self): + return "" % (self.name(), id(self)) + + def ctrlWidget(self): + """Return this Node's control widget. + + By default, Nodes have no control widget. Subclasses may reimplement this + method to provide a custom widget. This method is called by Flowcharts + when they are constructing their Node list.""" + return None + + def bypass(self, byp): + """Set whether this node should be bypassed. + + When bypassed, a Node's process() method is never called. In some cases, + data is automatically copied directly from specific input nodes to + output nodes instead (see the bypass argument to Terminal.__init__). + This is usually called when the user disables a node from the flowchart + control panel. + """ + self._bypass = byp + if self.bypassButton is not None: + self.bypassButton.setChecked(byp) + self.update() + + def isBypassed(self): + """Return True if this Node is currently bypassed.""" + return self._bypass + + def setInput(self, **args): + """Set the values on input terminals. For most nodes, this will happen automatically through Terminal.inputChanged. + This is normally only used for nodes with no connected inputs.""" + changed = False + for k, v in args.items(): + term = self._inputs[k] + oldVal = term.value() + if not eq(oldVal, v): + changed = True + term.setValue(v, process=False) + if changed and '_updatesHandled_' not in args: + self.update() + + def inputValues(self): + """Return a dict of all input values currently assigned to this node.""" + vals = {} + for n, t in self.inputs().items(): + vals[n] = t.value() + return vals + + def outputValues(self): + """Return a dict of all output values currently generated by this node.""" + vals = {} + for n, t in self.outputs().items(): + vals[n] = t.value() + return vals + + def connected(self, localTerm, remoteTerm): + """Called whenever one of this node's terminals is connected elsewhere.""" + pass + + def disconnected(self, localTerm, remoteTerm): + """Called whenever one of this node's terminals is disconnected from another.""" + pass + + def update(self, signal=True): + """Collect all input values, attempt to process new output values, and propagate downstream. + Subclasses should call update() whenever thir internal state has changed + (such as when the user interacts with the Node's control widget). Update + is automatically called when the inputs to the node are changed. + """ + vals = self.inputValues() + #print " inputs:", vals + try: + if self.isBypassed(): + out = self.processBypassed(vals) + else: + out = self.process(**strDict(vals)) + #print " output:", out + if out is not None: + if signal: + self.setOutput(**out) + else: + self.setOutputNoSignal(**out) + for n,t in self.inputs().items(): + t.setValueAcceptable(True) + self.clearException() + except: + #printExc( "Exception while processing %s:" % self.name()) + for n,t in self.outputs().items(): + t.setValue(None) + self.setException(sys.exc_info()) + + if signal: + #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data + self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data + + def processBypassed(self, args): + """Called when the flowchart would normally call Node.process, but this node is currently bypassed. + The default implementation looks for output terminals with a bypass connection and returns the + corresponding values. Most Node subclasses will _not_ need to reimplement this method.""" + result = {} + for term in list(self.outputs().values()): + byp = term.bypassValue() + if byp is None: + result[term.name()] = None + else: + result[term.name()] = args.get(byp, None) + return result + + def setOutput(self, **vals): + self.setOutputNoSignal(**vals) + #self.emit(QtCore.SIGNAL('outputChanged'), self) ## triggers flowchart to propagate new data + self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data + + def setOutputNoSignal(self, **vals): + for k, v in vals.items(): + term = self.outputs()[k] + term.setValue(v) + #targets = term.connections() + #for t in targets: ## propagate downstream + #if t is term: + #continue + #t.inputChanged(term) + term.setValueAcceptable(True) + + def setException(self, exc): + self.exception = exc + self.recolor() + + def clearException(self): + self.setException(None) + + def recolor(self): + if self.exception is None: + self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(0, 0, 0))) + else: + self.graphicsItem().setPen(QtGui.QPen(QtGui.QColor(150, 0, 0), 3)) + + def saveState(self): + """Return a dictionary representing the current state of this node + (excluding input / output values). This is used for saving/reloading + flowcharts. The default implementation returns this Node's position, + bypass state, and information about each of its terminals. + + Subclasses may want to extend this method, adding extra keys to the returned + dict.""" + pos = self.graphicsItem().pos() + state = {'pos': (pos.x(), pos.y()), 'bypass': self.isBypassed()} + termsEditable = self._allowAddInput | self._allowAddOutput + for term in self._inputs.values() + self._outputs.values(): + termsEditable |= term._renamable | term._removable | term._multiable + if termsEditable: + state['terminals'] = self.saveTerminals() + return state + + def restoreState(self, state): + """Restore the state of this node from a structure previously generated + by saveState(). """ + pos = state.get('pos', (0,0)) + self.graphicsItem().setPos(*pos) + self.bypass(state.get('bypass', False)) + if 'terminals' in state: + self.restoreTerminals(state['terminals']) + + def saveTerminals(self): + terms = OrderedDict() + for n, t in self.terminals.items(): + terms[n] = (t.saveState()) + return terms + + def restoreTerminals(self, state): + for name in list(self.terminals.keys()): + if name not in state: + self.removeTerminal(name) + for name, opts in state.items(): + if name in self.terminals: + term = self[name] + term.setOpts(**opts) + continue + try: + opts = strDict(opts) + self.addTerminal(name, **opts) + except: + printExc("Error restoring terminal %s (%s):" % (str(name), str(opts))) + + + def clearTerminals(self): + for t in self.terminals.values(): + t.close() + self.terminals = OrderedDict() + self._inputs = OrderedDict() + self._outputs = OrderedDict() + + def close(self): + """Cleans up after the node--removes terminals, graphicsItem, widget""" + self.disconnectAll() + self.clearTerminals() + item = self.graphicsItem() + if item.scene() is not None: + item.scene().removeItem(item) + self._graphicsItem = None + w = self.ctrlWidget() + if w is not None: + w.setParent(None) + #self.emit(QtCore.SIGNAL('closed'), self) + self.sigClosed.emit(self) + + def disconnectAll(self): + for t in self.terminals.values(): + t.disconnectAll() + + +#class NodeGraphicsItem(QtGui.QGraphicsItem): +class NodeGraphicsItem(GraphicsObject): + def __init__(self, node): + #QtGui.QGraphicsItem.__init__(self) + GraphicsObject.__init__(self) + #QObjectWorkaround.__init__(self) + + #self.shadow = QtGui.QGraphicsDropShadowEffect() + #self.shadow.setOffset(5,5) + #self.shadow.setBlurRadius(10) + #self.setGraphicsEffect(self.shadow) + + self.pen = fn.mkPen(0,0,0) + self.selectPen = fn.mkPen(200,200,200,width=2) + self.brush = fn.mkBrush(200, 200, 200, 150) + self.hoverBrush = fn.mkBrush(200, 200, 200, 200) + self.selectBrush = fn.mkBrush(200, 200, 255, 200) + self.hovered = False + + self.node = node + flags = self.ItemIsMovable | self.ItemIsSelectable | self.ItemIsFocusable |self.ItemSendsGeometryChanges + #flags = self.ItemIsFocusable |self.ItemSendsGeometryChanges + + self.setFlags(flags) + self.bounds = QtCore.QRectF(0, 0, 100, 100) + self.nameItem = QtGui.QGraphicsTextItem(self.node.name(), self) + self.nameItem.setDefaultTextColor(QtGui.QColor(50, 50, 50)) + self.nameItem.moveBy(self.bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) + self.nameItem.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) + self.updateTerminals() + #self.setZValue(10) + + self.nameItem.focusOutEvent = self.labelFocusOut + self.nameItem.keyPressEvent = self.labelKeyPress + + self.menu = None + self.buildMenu() + + #self.node.sigTerminalRenamed.connect(self.updateActionMenu) + + #def setZValue(self, z): + #for t, item in self.terminals.itervalues(): + #item.setZValue(z+1) + #GraphicsObject.setZValue(self, z) + + def labelFocusOut(self, ev): + QtGui.QGraphicsTextItem.focusOutEvent(self.nameItem, ev) + self.labelChanged() + + def labelKeyPress(self, ev): + if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: + self.labelChanged() + else: + QtGui.QGraphicsTextItem.keyPressEvent(self.nameItem, ev) + + def labelChanged(self): + newName = str(self.nameItem.toPlainText()) + if newName != self.node.name(): + self.node.rename(newName) + + ### re-center the label + bounds = self.boundingRect() + self.nameItem.setPos(bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0) + + def setPen(self, *args, **kwargs): + self.pen = fn.mkPen(*args, **kwargs) + self.update() + + def setBrush(self, brush): + self.brush = brush + self.update() + + + def updateTerminals(self): + bounds = self.bounds + self.terminals = {} + inp = self.node.inputs() + dy = bounds.height() / (len(inp)+1) + y = dy + for i, t in inp.items(): + item = t.graphicsItem() + item.setParentItem(self) + #item.setZValue(self.zValue()+1) + br = self.bounds + item.setAnchor(0, y) + self.terminals[i] = (t, item) + y += dy + + out = self.node.outputs() + dy = bounds.height() / (len(out)+1) + y = dy + for i, t in out.items(): + item = t.graphicsItem() + item.setParentItem(self) + item.setZValue(self.zValue()) + br = self.bounds + item.setAnchor(bounds.width(), y) + self.terminals[i] = (t, item) + y += dy + + #self.buildMenu() + + + def boundingRect(self): + return self.bounds.adjusted(-5, -5, 5, 5) + + def paint(self, p, *args): + + p.setPen(self.pen) + if self.isSelected(): + p.setPen(self.selectPen) + p.setBrush(self.selectBrush) + else: + p.setPen(self.pen) + if self.hovered: + p.setBrush(self.hoverBrush) + else: + p.setBrush(self.brush) + + p.drawRect(self.bounds) + + + def mousePressEvent(self, ev): + ev.ignore() + + + def mouseClickEvent(self, ev): + #print "Node.mouseClickEvent called." + if int(ev.button()) == int(QtCore.Qt.LeftButton): + ev.accept() + #print " ev.button: left" + sel = self.isSelected() + #ret = QtGui.QGraphicsItem.mousePressEvent(self, ev) + self.setSelected(True) + if not sel and self.isSelected(): + #self.setBrush(QtGui.QBrush(QtGui.QColor(200, 200, 255))) + #self.emit(QtCore.SIGNAL('selected')) + #self.scene().selectionChanged.emit() ## for some reason this doesn't seem to be happening automatically + self.update() + #return ret + + elif int(ev.button()) == int(QtCore.Qt.RightButton): + #print " ev.button: right" + ev.accept() + #pos = ev.screenPos() + self.raiseContextMenu(ev) + #self.menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def mouseDragEvent(self, ev): + #print "Node.mouseDrag" + if ev.button() == QtCore.Qt.LeftButton: + ev.accept() + self.setPos(self.pos()+self.mapToParent(ev.pos())-self.mapToParent(ev.lastPos())) + + def hoverEvent(self, ev): + if not ev.isExit() and ev.acceptClicks(QtCore.Qt.LeftButton): + ev.acceptDrags(QtCore.Qt.LeftButton) + self.hovered = True + else: + self.hovered = False + self.update() + + def keyPressEvent(self, ev): + if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: + ev.accept() + if not self.node._allowRemove: + return + self.node.close() + else: + ev.ignore() + + def itemChange(self, change, val): + if change == self.ItemPositionHasChanged: + for k, t in self.terminals.items(): + t[1].nodeMoved() + return GraphicsObject.itemChange(self, change, val) + + + def getMenu(self): + return self.menu + + def raiseContextMenu(self, ev): + menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def buildMenu(self): + self.menu = QtGui.QMenu() + self.menu.setTitle("Node") + a = self.menu.addAction("Add input", self.addInputFromMenu) + if not self.node._allowAddInput: + a.setEnabled(False) + a = self.menu.addAction("Add output", self.addOutputFromMenu) + if not self.node._allowAddOutput: + a.setEnabled(False) + a = self.menu.addAction("Remove node", self.node.close) + if not self.node._allowRemove: + a.setEnabled(False) + + def addInputFromMenu(self): ## called when add input is clicked in context menu + self.node.addInput(renamable=True, removable=True, multiable=True) + + def addOutputFromMenu(self): ## called when add output is clicked in context menu + self.node.addOutput(renamable=True, removable=True, multiable=False) + diff --git a/papi/pyqtgraph/flowchart/NodeLibrary.py b/papi/pyqtgraph/flowchart/NodeLibrary.py new file mode 100644 index 00000000..8e04e97d --- /dev/null +++ b/papi/pyqtgraph/flowchart/NodeLibrary.py @@ -0,0 +1,86 @@ +from ..pgcollections import OrderedDict +from .Node import Node + +def isNodeClass(cls): + try: + if not issubclass(cls, Node): + return False + except: + return False + return hasattr(cls, 'nodeName') + + + +class NodeLibrary: + """ + A library of flowchart Node types. Custom libraries may be built to provide + each flowchart with a specific set of allowed Node types. + """ + + def __init__(self): + self.nodeList = OrderedDict() + self.nodeTree = OrderedDict() + + def addNodeType(self, nodeClass, paths, override=False): + """ + Register a new node type. If the type's name is already in use, + an exception will be raised (unless override=True). + + ============== ========================================================= + **Arguments:** + + nodeClass a subclass of Node (must have typ.nodeName) + paths list of tuples specifying the location(s) this + type will appear in the library tree. + override if True, overwrite any class having the same name + ============== ========================================================= + """ + if not isNodeClass(nodeClass): + raise Exception("Object %s is not a Node subclass" % str(nodeClass)) + + name = nodeClass.nodeName + if not override and name in self.nodeList: + raise Exception("Node type name '%s' is already registered." % name) + + self.nodeList[name] = nodeClass + for path in paths: + root = self.nodeTree + for n in path: + if n not in root: + root[n] = OrderedDict() + root = root[n] + root[name] = nodeClass + + def getNodeType(self, name): + try: + return self.nodeList[name] + except KeyError: + raise Exception("No node type called '%s'" % name) + + def getNodeTree(self): + return self.nodeTree + + def copy(self): + """ + Return a copy of this library. + """ + lib = NodeLibrary() + lib.nodeList = self.nodeList.copy() + lib.nodeTree = self.treeCopy(self.nodeTree) + return lib + + @staticmethod + def treeCopy(tree): + copy = OrderedDict() + for k,v in tree.items(): + if isNodeClass(v): + copy[k] = v + else: + copy[k] = NodeLibrary.treeCopy(v) + return copy + + def reload(self): + """ + Reload Node classes in this library. + """ + raise NotImplementedError() diff --git a/papi/pyqtgraph/flowchart/Terminal.py b/papi/pyqtgraph/flowchart/Terminal.py new file mode 100644 index 00000000..6a6db62e --- /dev/null +++ b/papi/pyqtgraph/flowchart/Terminal.py @@ -0,0 +1,634 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtCore, QtGui +import weakref +from ..graphicsItems.GraphicsObject import GraphicsObject +from .. import functions as fn +from ..Point import Point +#from PySide import QtCore, QtGui +from .eq import * + +class Terminal(object): + def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None): + """ + Construct a new terminal. + + ============== ================================================================================= + **Arguments:** + node the node to which this terminal belongs + name string, the name of the terminal + io 'in' or 'out' + optional bool, whether the node may process without connection to this terminal + multi bool, for inputs: whether this terminal may make multiple connections + for outputs: whether this terminal creates a different value for each connection + pos [x, y], the position of the terminal within its node's boundaries + renamable (bool) Whether the terminal can be renamed by the user + removable (bool) Whether the terminal can be removed by the user + multiable (bool) Whether the user may toggle the *multi* option for this terminal + bypass (str) Name of the terminal from which this terminal's value is derived + when the Node is in bypass mode. + ============== ================================================================================= + """ + self._io = io + #self._isOutput = opts[0] in ['out', 'io'] + #self._isInput = opts[0]] in ['in', 'io'] + #self._isIO = opts[0]=='io' + self._optional = optional + self._multi = multi + self._node = weakref.ref(node) + self._name = name + self._renamable = renamable + self._removable = removable + self._multiable = multiable + self._connections = {} + self._graphicsItem = TerminalGraphicsItem(self, parent=self._node().graphicsItem()) + self._bypass = bypass + + if multi: + self._value = {} ## dictionary of terminal:value pairs. + else: + self._value = None + + self.valueOk = None + self.recolor() + + def value(self, term=None): + """Return the value this terminal provides for the connected terminal""" + if term is None: + return self._value + + if self.isMultiValue(): + return self._value.get(term, None) + else: + return self._value + + def bypassValue(self): + return self._bypass + + def setValue(self, val, process=True): + """If this is a single-value terminal, val should be a single value. + If this is a multi-value terminal, val should be a dict of terminal:value pairs""" + if not self.isMultiValue(): + if eq(val, self._value): + return + self._value = val + else: + if not isinstance(self._value, dict): + self._value = {} + if val is not None: + self._value.update(val) + + self.setValueAcceptable(None) ## by default, input values are 'unchecked' until Node.update(). + if self.isInput() and process: + self.node().update() + + ## Let the flowchart handle this. + #if self.isOutput(): + #for c in self.connections(): + #if c.isInput(): + #c.inputChanged(self) + self.recolor() + + def setOpts(self, **opts): + self._renamable = opts.get('renamable', self._renamable) + self._removable = opts.get('removable', self._removable) + self._multiable = opts.get('multiable', self._multiable) + if 'multi' in opts: + self.setMultiValue(opts['multi']) + + + def connected(self, term): + """Called whenever this terminal has been connected to another. (note--this function is called on both terminals)""" + if self.isInput() and term.isOutput(): + self.inputChanged(term) + if self.isOutput() and self.isMultiValue(): + self.node().update() + self.node().connected(self, term) + + def disconnected(self, term): + """Called whenever this terminal has been disconnected from another. (note--this function is called on both terminals)""" + if self.isMultiValue() and term in self._value: + del self._value[term] + self.node().update() + #self.recolor() + else: + if self.isInput(): + self.setValue(None) + self.node().disconnected(self, term) + #self.node().update() + + def inputChanged(self, term, process=True): + """Called whenever there is a change to the input value to this terminal. + It may often be useful to override this function.""" + if self.isMultiValue(): + self.setValue({term: term.value(self)}, process=process) + else: + self.setValue(term.value(self), process=process) + + def valueIsAcceptable(self): + """Returns True->acceptable None->unknown False->Unacceptable""" + return self.valueOk + + def setValueAcceptable(self, v=True): + self.valueOk = v + self.recolor() + + def connections(self): + return self._connections + + def node(self): + return self._node() + + def isInput(self): + return self._io == 'in' + + def isMultiValue(self): + return self._multi + + def setMultiValue(self, multi): + """Set whether this is a multi-value terminal.""" + self._multi = multi + if not multi and len(self.inputTerminals()) > 1: + self.disconnectAll() + + for term in self.inputTerminals(): + self.inputChanged(term) + + def isOutput(self): + return self._io == 'out' + + def isRenamable(self): + return self._renamable + + def isRemovable(self): + return self._removable + + def isMultiable(self): + return self._multiable + + def name(self): + return self._name + + def graphicsItem(self): + return self._graphicsItem + + def isConnected(self): + return len(self.connections()) > 0 + + def connectedTo(self, term): + return term in self.connections() + + def hasInput(self): + #conn = self.extendedConnections() + for t in self.connections(): + if t.isOutput(): + return True + return False + + def inputTerminals(self): + """Return the terminal(s) that give input to this one.""" + #terms = self.extendedConnections() + #for t in terms: + #if t.isOutput(): + #return t + return [t for t in self.connections() if t.isOutput()] + + + def dependentNodes(self): + """Return the list of nodes which receive input from this terminal.""" + #conn = self.extendedConnections() + #del conn[self] + return set([t.node() for t in self.connections() if t.isInput()]) + + def connectTo(self, term, connectionItem=None): + try: + if self.connectedTo(term): + raise Exception('Already connected') + if term is self: + raise Exception('Not connecting terminal to self') + if term.node() is self.node(): + raise Exception("Can't connect to terminal on same node.") + for t in [self, term]: + if t.isInput() and not t._multi and len(t.connections()) > 0: + raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, list(t.connections().keys()))) + #if self.hasInput() and term.hasInput(): + #raise Exception('Target terminal already has input') + + #if term in self.node().terminals.values(): + #if self.isOutput() or term.isOutput(): + #raise Exception('Can not connect an output back to the same node.') + except: + if connectionItem is not None: + connectionItem.close() + raise + + if connectionItem is None: + connectionItem = ConnectionItem(self.graphicsItem(), term.graphicsItem()) + #self.graphicsItem().scene().addItem(connectionItem) + self.graphicsItem().getViewBox().addItem(connectionItem) + #connectionItem.setParentItem(self.graphicsItem().parent().parent()) + self._connections[term] = connectionItem + term._connections[self] = connectionItem + + self.recolor() + + #if self.isOutput() and term.isInput(): + #term.inputChanged(self) + #if term.isInput() and term.isOutput(): + #self.inputChanged(term) + self.connected(term) + term.connected(self) + + return connectionItem + + def disconnectFrom(self, term): + if not self.connectedTo(term): + return + item = self._connections[term] + #print "removing connection", item + #item.scene().removeItem(item) + item.close() + del self._connections[term] + del term._connections[self] + self.recolor() + term.recolor() + + self.disconnected(term) + term.disconnected(self) + #if self.isOutput() and term.isInput(): + #term.inputChanged(self) + #if term.isInput() and term.isOutput(): + #self.inputChanged(term) + + + def disconnectAll(self): + for t in list(self._connections.keys()): + self.disconnectFrom(t) + + def recolor(self, color=None, recurse=True): + if color is None: + if not self.isConnected(): ## disconnected terminals are black + color = QtGui.QColor(0,0,0) + elif self.isInput() and not self.hasInput(): ## input terminal with no connected output terminals + color = QtGui.QColor(200,200,0) + elif self._value is None or eq(self._value, {}): ## terminal is connected but has no data (possibly due to processing error) + color = QtGui.QColor(255,255,255) + elif self.valueIsAcceptable() is None: ## terminal has data, but it is unknown if the data is ok + color = QtGui.QColor(200, 200, 0) + elif self.valueIsAcceptable() is True: ## terminal has good input, all ok + color = QtGui.QColor(0, 200, 0) + else: ## terminal has bad input + color = QtGui.QColor(200, 0, 0) + self.graphicsItem().setBrush(QtGui.QBrush(color)) + + if recurse: + for t in self.connections(): + t.recolor(color, recurse=False) + + + def rename(self, name): + oldName = self._name + self._name = name + self.node().terminalRenamed(self, oldName) + self.graphicsItem().termRenamed(name) + + def __repr__(self): + return "" % (str(self.node().name()), str(self.name())) + + #def extendedConnections(self, terms=None): + #"""Return list of terminals (including this one) that are directly or indirectly wired to this.""" + #if terms is None: + #terms = {} + #terms[self] = None + #for t in self._connections: + #if t in terms: + #continue + #terms.update(t.extendedConnections(terms)) + #return terms + + def __hash__(self): + return id(self) + + def close(self): + self.disconnectAll() + item = self.graphicsItem() + if item.scene() is not None: + item.scene().removeItem(item) + + def saveState(self): + return {'io': self._io, 'multi': self._multi, 'optional': self._optional, 'renamable': self._renamable, 'removable': self._removable, 'multiable': self._multiable} + + +#class TerminalGraphicsItem(QtGui.QGraphicsItem): +class TerminalGraphicsItem(GraphicsObject): + + def __init__(self, term, parent=None): + self.term = term + #QtGui.QGraphicsItem.__init__(self, parent) + GraphicsObject.__init__(self, parent) + self.brush = fn.mkBrush(0,0,0) + self.box = QtGui.QGraphicsRectItem(0, 0, 10, 10, self) + self.label = QtGui.QGraphicsTextItem(self.term.name(), self) + self.label.scale(0.7, 0.7) + #self.setAcceptHoverEvents(True) + self.newConnection = None + self.setFiltersChildEvents(True) ## to pick up mouse events on the rectitem + if self.term.isRenamable(): + self.label.setTextInteractionFlags(QtCore.Qt.TextEditorInteraction) + self.label.focusOutEvent = self.labelFocusOut + self.label.keyPressEvent = self.labelKeyPress + self.setZValue(1) + self.menu = None + + + def labelFocusOut(self, ev): + QtGui.QGraphicsTextItem.focusOutEvent(self.label, ev) + self.labelChanged() + + def labelKeyPress(self, ev): + if ev.key() == QtCore.Qt.Key_Enter or ev.key() == QtCore.Qt.Key_Return: + self.labelChanged() + else: + QtGui.QGraphicsTextItem.keyPressEvent(self.label, ev) + + def labelChanged(self): + newName = str(self.label.toPlainText()) + if newName != self.term.name(): + self.term.rename(newName) + + def termRenamed(self, name): + self.label.setPlainText(name) + + def setBrush(self, brush): + self.brush = brush + self.box.setBrush(brush) + + def disconnect(self, target): + self.term.disconnectFrom(target.term) + + def boundingRect(self): + br = self.box.mapRectToParent(self.box.boundingRect()) + lr = self.label.mapRectToParent(self.label.boundingRect()) + return br | lr + + def paint(self, p, *args): + pass + + def setAnchor(self, x, y): + pos = QtCore.QPointF(x, y) + self.anchorPos = pos + br = self.box.mapRectToParent(self.box.boundingRect()) + lr = self.label.mapRectToParent(self.label.boundingRect()) + + + if self.term.isInput(): + self.box.setPos(pos.x(), pos.y()-br.height()/2.) + self.label.setPos(pos.x() + br.width(), pos.y() - lr.height()/2.) + else: + self.box.setPos(pos.x()-br.width(), pos.y()-br.height()/2.) + self.label.setPos(pos.x()-br.width()-lr.width(), pos.y()-lr.height()/2.) + self.updateConnections() + + def updateConnections(self): + for t, c in self.term.connections().items(): + c.updateLine() + + def mousePressEvent(self, ev): + #ev.accept() + ev.ignore() ## necessary to allow click/drag events to process correctly + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + ev.accept() + self.label.setFocus(QtCore.Qt.MouseFocusReason) + elif ev.button() == QtCore.Qt.RightButton: + ev.accept() + self.raiseContextMenu(ev) + + def raiseContextMenu(self, ev): + ## only raise menu if this terminal is removable + menu = self.getMenu() + menu = self.scene().addParentContextMenus(self, menu, ev) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def getMenu(self): + if self.menu is None: + self.menu = QtGui.QMenu() + self.menu.setTitle("Terminal") + remAct = QtGui.QAction("Remove terminal", self.menu) + remAct.triggered.connect(self.removeSelf) + self.menu.addAction(remAct) + self.menu.remAct = remAct + if not self.term.isRemovable(): + remAct.setEnabled(False) + multiAct = QtGui.QAction("Multi-value", self.menu) + multiAct.setCheckable(True) + multiAct.setChecked(self.term.isMultiValue()) + multiAct.setEnabled(self.term.isMultiable()) + + multiAct.triggered.connect(self.toggleMulti) + self.menu.addAction(multiAct) + self.menu.multiAct = multiAct + if self.term.isMultiable(): + multiAct.setEnabled = False + return self.menu + + def toggleMulti(self): + multi = self.menu.multiAct.isChecked() + self.term.setMultiValue(multi) + + def removeSelf(self): + self.term.node().removeTerminal(self.term) + + def mouseDragEvent(self, ev): + if ev.button() != QtCore.Qt.LeftButton: + ev.ignore() + return + + ev.accept() + if ev.isStart(): + if self.newConnection is None: + self.newConnection = ConnectionItem(self) + #self.scene().addItem(self.newConnection) + self.getViewBox().addItem(self.newConnection) + #self.newConnection.setParentItem(self.parent().parent()) + + self.newConnection.setTarget(self.mapToView(ev.pos())) + elif ev.isFinish(): + if self.newConnection is not None: + items = self.scene().items(ev.scenePos()) + gotTarget = False + for i in items: + if isinstance(i, TerminalGraphicsItem): + self.newConnection.setTarget(i) + try: + self.term.connectTo(i.term, self.newConnection) + gotTarget = True + except: + self.scene().removeItem(self.newConnection) + self.newConnection = None + raise + break + + if not gotTarget: + #print "remove unused connection" + #self.scene().removeItem(self.newConnection) + self.newConnection.close() + self.newConnection = None + else: + if self.newConnection is not None: + self.newConnection.setTarget(self.mapToView(ev.pos())) + + def hoverEvent(self, ev): + if not ev.isExit() and ev.acceptDrags(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. + ev.acceptClicks(QtCore.Qt.RightButton) + self.box.setBrush(fn.mkBrush('w')) + else: + self.box.setBrush(self.brush) + self.update() + + #def hoverEnterEvent(self, ev): + #self.hover = True + + #def hoverLeaveEvent(self, ev): + #self.hover = False + + def connectPoint(self): + ## return the connect position of this terminal in view coords + return self.mapToView(self.mapFromItem(self.box, self.box.boundingRect().center())) + + def nodeMoved(self): + for t, item in self.term.connections().items(): + item.updateLine() + + +#class ConnectionItem(QtGui.QGraphicsItem): +class ConnectionItem(GraphicsObject): + + def __init__(self, source, target=None): + #QtGui.QGraphicsItem.__init__(self) + GraphicsObject.__init__(self) + self.setFlags( + self.ItemIsSelectable | + self.ItemIsFocusable + ) + self.source = source + self.target = target + self.length = 0 + self.hovered = False + self.path = None + self.shapePath = None + self.style = { + 'shape': 'line', + 'color': (100, 100, 250), + 'width': 1.0, + 'hoverColor': (150, 150, 250), + 'hoverWidth': 1.0, + 'selectedColor': (200, 200, 0), + 'selectedWidth': 3.0, + } + #self.line = QtGui.QGraphicsLineItem(self) + self.source.getViewBox().addItem(self) + self.updateLine() + self.setZValue(0) + + def close(self): + if self.scene() is not None: + #self.scene().removeItem(self.line) + self.scene().removeItem(self) + + def setTarget(self, target): + self.target = target + self.updateLine() + + def setStyle(self, **kwds): + self.style.update(kwds) + if 'shape' in kwds: + self.updateLine() + else: + self.update() + + def updateLine(self): + start = Point(self.source.connectPoint()) + if isinstance(self.target, TerminalGraphicsItem): + stop = Point(self.target.connectPoint()) + elif isinstance(self.target, QtCore.QPointF): + stop = Point(self.target) + else: + return + self.prepareGeometryChange() + + self.path = self.generatePath(start, stop) + self.shapePath = None + self.update() + + def generatePath(self, start, stop): + path = QtGui.QPainterPath() + path.moveTo(start) + if self.style['shape'] == 'line': + path.lineTo(stop) + elif self.style['shape'] == 'cubic': + path.cubicTo(Point(stop.x(), start.y()), Point(start.x(), stop.y()), Point(stop.x(), stop.y())) + else: + raise Exception('Invalid shape "%s"; options are "line" or "cubic"' % self.style['shape']) + return path + + def keyPressEvent(self, ev): + if ev.key() == QtCore.Qt.Key_Delete or ev.key() == QtCore.Qt.Key_Backspace: + #if isinstance(self.target, TerminalGraphicsItem): + self.source.disconnect(self.target) + ev.accept() + else: + ev.ignore() + + def mousePressEvent(self, ev): + ev.ignore() + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + ev.accept() + sel = self.isSelected() + self.setSelected(True) + if not sel and self.isSelected(): + self.update() + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.LeftButton): + self.hovered = True + else: + self.hovered = False + self.update() + + + def boundingRect(self): + return self.shape().boundingRect() + ##return self.line.boundingRect() + #px = self.pixelWidth() + #return QtCore.QRectF(-5*px, 0, 10*px, self.length) + def viewRangeChanged(self): + self.shapePath = None + self.prepareGeometryChange() + + def shape(self): + if self.shapePath is None: + if self.path is None: + return QtGui.QPainterPath() + stroker = QtGui.QPainterPathStroker() + px = self.pixelWidth() + stroker.setWidth(px*8) + self.shapePath = stroker.createStroke(self.path) + return self.shapePath + + def paint(self, p, *args): + if self.isSelected(): + p.setPen(fn.mkPen(self.style['selectedColor'], width=self.style['selectedWidth'])) + else: + if self.hovered: + p.setPen(fn.mkPen(self.style['hoverColor'], width=self.style['hoverWidth'])) + else: + p.setPen(fn.mkPen(self.style['color'], width=self.style['width'])) + + #p.drawLine(0, 0, 0, self.length) + + p.drawPath(self.path) diff --git a/papi/pyqtgraph/flowchart/__init__.py b/papi/pyqtgraph/flowchart/__init__.py new file mode 100644 index 00000000..46e04db0 --- /dev/null +++ b/papi/pyqtgraph/flowchart/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +from .Flowchart import * + +from .library import getNodeType, registerNodeType, getNodeTree \ No newline at end of file diff --git a/papi/pyqtgraph/flowchart/eq.py b/papi/pyqtgraph/flowchart/eq.py new file mode 100644 index 00000000..554989b2 --- /dev/null +++ b/papi/pyqtgraph/flowchart/eq.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from numpy import ndarray, bool_ +from ..metaarray import MetaArray + +def eq(a, b): + """The great missing equivalence function: Guaranteed evaluation to a single bool value.""" + if a is b: + return True + + try: + e = a==b + except ValueError: + return False + except AttributeError: + return False + except: + print("a:", str(type(a)), str(a)) + print("b:", str(type(b)), str(b)) + raise + t = type(e) + if t is bool: + return e + elif t is bool_: + return bool(e) + elif isinstance(e, ndarray) or (hasattr(e, 'implements') and e.implements('MetaArray')): + try: ## disaster: if a is an empty array and b is not, then e.all() is True + if a.shape != b.shape: + return False + except: + return False + if (hasattr(e, 'implements') and e.implements('MetaArray')): + return e.asarray().all() + else: + return e.all() + else: + raise Exception("== operator returned type %s" % str(type(e))) diff --git a/papi/pyqtgraph/flowchart/library/Data.py b/papi/pyqtgraph/flowchart/library/Data.py new file mode 100644 index 00000000..5236de8d --- /dev/null +++ b/papi/pyqtgraph/flowchart/library/Data.py @@ -0,0 +1,356 @@ +# -*- coding: utf-8 -*- +from ..Node import Node +from ...Qt import QtGui, QtCore +import numpy as np +from .common import * +from ...SRTTransform import SRTTransform +from ...Point import Point +from ...widgets.TreeWidget import TreeWidget +from ...graphicsItems.LinearRegionItem import LinearRegionItem + +from . import functions + +class ColumnSelectNode(Node): + """Select named columns from a record array or MetaArray.""" + nodeName = "ColumnSelect" + def __init__(self, name): + Node.__init__(self, name, terminals={'In': {'io': 'in'}}) + self.columns = set() + self.columnList = QtGui.QListWidget() + self.axis = 0 + self.columnList.itemChanged.connect(self.itemChanged) + + def process(self, In, display=True): + if display: + self.updateList(In) + + out = {} + if hasattr(In, 'implements') and In.implements('MetaArray'): + for c in self.columns: + out[c] = In[self.axis:c] + elif isinstance(In, np.ndarray) and In.dtype.fields is not None: + for c in self.columns: + out[c] = In[c] + else: + self.In.setValueAcceptable(False) + raise Exception("Input must be MetaArray or ndarray with named fields") + + return out + + def ctrlWidget(self): + return self.columnList + + def updateList(self, data): + if hasattr(data, 'implements') and data.implements('MetaArray'): + cols = data.listColumns() + for ax in cols: ## find first axis with columns + if len(cols[ax]) > 0: + self.axis = ax + cols = set(cols[ax]) + break + else: + cols = list(data.dtype.fields.keys()) + + rem = set() + for c in self.columns: + if c not in cols: + self.removeTerminal(c) + rem.add(c) + self.columns -= rem + + self.columnList.blockSignals(True) + self.columnList.clear() + for c in cols: + item = QtGui.QListWidgetItem(c) + item.setFlags(QtCore.Qt.ItemIsEnabled|QtCore.Qt.ItemIsUserCheckable) + if c in self.columns: + item.setCheckState(QtCore.Qt.Checked) + else: + item.setCheckState(QtCore.Qt.Unchecked) + self.columnList.addItem(item) + self.columnList.blockSignals(False) + + + def itemChanged(self, item): + col = str(item.text()) + if item.checkState() == QtCore.Qt.Checked: + if col not in self.columns: + self.columns.add(col) + self.addOutput(col) + else: + if col in self.columns: + self.columns.remove(col) + self.removeTerminal(col) + self.update() + + def saveState(self): + state = Node.saveState(self) + state['columns'] = list(self.columns) + return state + + def restoreState(self, state): + Node.restoreState(self, state) + self.columns = set(state.get('columns', [])) + for c in self.columns: + self.addOutput(c) + + + +class RegionSelectNode(CtrlNode): + """Returns a slice from a 1-D array. Connect the 'widget' output to a plot to display a region-selection widget.""" + nodeName = "RegionSelect" + uiTemplate = [ + ('start', 'spin', {'value': 0, 'step': 0.1}), + ('stop', 'spin', {'value': 0.1, 'step': 0.1}), + ('display', 'check', {'value': True}), + ('movable', 'check', {'value': True}), + ] + + def __init__(self, name): + self.items = {} + CtrlNode.__init__(self, name, terminals={ + 'data': {'io': 'in'}, + 'selected': {'io': 'out'}, + 'region': {'io': 'out'}, + 'widget': {'io': 'out', 'multi': True} + }) + self.ctrls['display'].toggled.connect(self.displayToggled) + self.ctrls['movable'].toggled.connect(self.movableToggled) + + def displayToggled(self, b): + for item in self.items.values(): + item.setVisible(b) + + def movableToggled(self, b): + for item in self.items.values(): + item.setMovable(b) + + + def process(self, data=None, display=True): + #print "process.." + s = self.stateGroup.state() + region = [s['start'], s['stop']] + + if display: + conn = self['widget'].connections() + for c in conn: + plot = c.node().getPlot() + if plot is None: + continue + if c in self.items: + item = self.items[c] + item.setRegion(region) + #print " set rgn:", c, region + #item.setXVals(events) + else: + item = LinearRegionItem(values=region) + self.items[c] = item + #item.connect(item, QtCore.SIGNAL('regionChanged'), self.rgnChanged) + item.sigRegionChanged.connect(self.rgnChanged) + item.setVisible(s['display']) + item.setMovable(s['movable']) + #print " new rgn:", c, region + #self.items[c].setYRange([0., 0.2], relative=True) + + if self['selected'].isConnected(): + if data is None: + sliced = None + elif (hasattr(data, 'implements') and data.implements('MetaArray')): + sliced = data[0:s['start']:s['stop']] + else: + mask = (data['time'] >= s['start']) * (data['time'] < s['stop']) + sliced = data[mask] + else: + sliced = None + + return {'selected': sliced, 'widget': self.items, 'region': region} + + + def rgnChanged(self, item): + region = item.getRegion() + self.stateGroup.setState({'start': region[0], 'stop': region[1]}) + self.update() + + +class EvalNode(Node): + """Return the output of a string evaluated/executed by the python interpreter. + The string may be either an expression or a python script, and inputs are accessed as the name of the terminal. + For expressions, a single value may be evaluated for a single output, or a dict for multiple outputs. + For a script, the text will be executed as the body of a function.""" + nodeName = 'PythonEval' + + def __init__(self, name): + Node.__init__(self, name, + terminals = { + 'input': {'io': 'in', 'renamable': True, 'multiable': True}, + 'output': {'io': 'out', 'renamable': True, 'multiable': True}, + }, + allowAddInput=True, allowAddOutput=True) + + self.ui = QtGui.QWidget() + self.layout = QtGui.QGridLayout() + #self.addInBtn = QtGui.QPushButton('+Input') + #self.addOutBtn = QtGui.QPushButton('+Output') + self.text = QtGui.QTextEdit() + self.text.setTabStopWidth(30) + self.text.setPlainText("# Access inputs as args['input_name']\nreturn {'output': None} ## one key per output terminal") + #self.layout.addWidget(self.addInBtn, 0, 0) + #self.layout.addWidget(self.addOutBtn, 0, 1) + self.layout.addWidget(self.text, 1, 0, 1, 2) + self.ui.setLayout(self.layout) + + #QtCore.QObject.connect(self.addInBtn, QtCore.SIGNAL('clicked()'), self.addInput) + #self.addInBtn.clicked.connect(self.addInput) + #QtCore.QObject.connect(self.addOutBtn, QtCore.SIGNAL('clicked()'), self.addOutput) + #self.addOutBtn.clicked.connect(self.addOutput) + self.text.focusOutEvent = self.focusOutEvent + self.lastText = None + + def ctrlWidget(self): + return self.ui + + #def addInput(self): + #Node.addInput(self, 'input', renamable=True) + + #def addOutput(self): + #Node.addOutput(self, 'output', renamable=True) + + def focusOutEvent(self, ev): + text = str(self.text.toPlainText()) + if text != self.lastText: + self.lastText = text + self.update() + return QtGui.QTextEdit.focusOutEvent(self.text, ev) + + def process(self, display=True, **args): + l = locals() + l.update(args) + ## try eval first, then exec + try: + text = str(self.text.toPlainText()).replace('\n', ' ') + output = eval(text, globals(), l) + except SyntaxError: + fn = "def fn(**args):\n" + run = "\noutput=fn(**args)\n" + text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run + exec(text) + except: + print("Error processing node: %s" % self.name()) + raise + return output + + def saveState(self): + state = Node.saveState(self) + state['text'] = str(self.text.toPlainText()) + #state['terminals'] = self.saveTerminals() + return state + + def restoreState(self, state): + Node.restoreState(self, state) + self.text.clear() + self.text.insertPlainText(state['text']) + self.restoreTerminals(state['terminals']) + self.update() + +class ColumnJoinNode(Node): + """Concatenates record arrays and/or adds new columns""" + nodeName = 'ColumnJoin' + + def __init__(self, name): + Node.__init__(self, name, terminals = { + 'output': {'io': 'out'}, + }) + + #self.items = [] + + self.ui = QtGui.QWidget() + self.layout = QtGui.QGridLayout() + self.ui.setLayout(self.layout) + + self.tree = TreeWidget() + self.addInBtn = QtGui.QPushButton('+ Input') + self.remInBtn = QtGui.QPushButton('- Input') + + self.layout.addWidget(self.tree, 0, 0, 1, 2) + self.layout.addWidget(self.addInBtn, 1, 0) + self.layout.addWidget(self.remInBtn, 1, 1) + + self.addInBtn.clicked.connect(self.addInput) + self.remInBtn.clicked.connect(self.remInput) + self.tree.sigItemMoved.connect(self.update) + + def ctrlWidget(self): + return self.ui + + def addInput(self): + #print "ColumnJoinNode.addInput called." + term = Node.addInput(self, 'input', renamable=True, removable=True, multiable=True) + #print "Node.addInput returned. term:", term + item = QtGui.QTreeWidgetItem([term.name()]) + item.term = term + term.joinItem = item + #self.items.append((term, item)) + self.tree.addTopLevelItem(item) + + def remInput(self): + sel = self.tree.currentItem() + term = sel.term + term.joinItem = None + sel.term = None + self.tree.removeTopLevelItem(sel) + self.removeTerminal(term) + self.update() + + def process(self, display=True, **args): + order = self.order() + vals = [] + for name in order: + if name not in args: + continue + val = args[name] + if isinstance(val, np.ndarray) and len(val.dtype) > 0: + vals.append(val) + else: + vals.append((name, None, val)) + return {'output': functions.concatenateColumns(vals)} + + def order(self): + return [str(self.tree.topLevelItem(i).text(0)) for i in range(self.tree.topLevelItemCount())] + + def saveState(self): + state = Node.saveState(self) + state['order'] = self.order() + return state + + def restoreState(self, state): + Node.restoreState(self, state) + inputs = self.inputs() + + ## Node.restoreState should have created all of the terminals we need + ## However: to maintain support for some older flowchart files, we need + ## to manually add any terminals that were not taken care of. + for name in [n for n in state['order'] if n not in inputs]: + Node.addInput(self, name, renamable=True, removable=True, multiable=True) + inputs = self.inputs() + + order = [name for name in state['order'] if name in inputs] + for name in inputs: + if name not in order: + order.append(name) + + self.tree.clear() + for name in order: + term = self[name] + item = QtGui.QTreeWidgetItem([name]) + item.term = term + term.joinItem = item + #self.items.append((term, item)) + self.tree.addTopLevelItem(item) + + def terminalRenamed(self, term, oldName): + Node.terminalRenamed(self, term, oldName) + item = term.joinItem + item.setText(0, term.name()) + self.update() + + diff --git a/papi/pyqtgraph/flowchart/library/Display.py b/papi/pyqtgraph/flowchart/library/Display.py new file mode 100644 index 00000000..642e6491 --- /dev/null +++ b/papi/pyqtgraph/flowchart/library/Display.py @@ -0,0 +1,312 @@ +# -*- coding: utf-8 -*- +from ..Node import Node +import weakref +from ...Qt import QtCore, QtGui +from ...graphicsItems.ScatterPlotItem import ScatterPlotItem +from ...graphicsItems.PlotCurveItem import PlotCurveItem +from ... import PlotDataItem, ComboBox + +from .common import * +import numpy as np + +class PlotWidgetNode(Node): + """Connection to PlotWidget. Will plot arrays, metaarrays, and display event lists.""" + nodeName = 'PlotWidget' + sigPlotChanged = QtCore.Signal(object) + + def __init__(self, name): + Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) + self.plot = None # currently selected plot + self.plots = {} # list of available plots user may select from + self.ui = None + self.items = {} + + def disconnected(self, localTerm, remoteTerm): + if localTerm is self['In'] and remoteTerm in self.items: + self.plot.removeItem(self.items[remoteTerm]) + del self.items[remoteTerm] + + def setPlot(self, plot): + #print "======set plot" + if plot == self.plot: + return + + # clear data from previous plot + if self.plot is not None: + for vid in list(self.items.keys()): + self.plot.removeItem(self.items[vid]) + del self.items[vid] + + self.plot = plot + self.updateUi() + self.update() + self.sigPlotChanged.emit(self) + + def getPlot(self): + return self.plot + + def process(self, In, display=True): + if display and self.plot is not None: + items = set() + # Add all new input items to selected plot + for name, vals in In.items(): + if vals is None: + continue + if type(vals) is not list: + vals = [vals] + + for val in vals: + vid = id(val) + if vid in self.items and self.items[vid].scene() is self.plot.scene(): + # Item is already added to the correct scene + # possible bug: what if two plots occupy the same scene? (should + # rarely be a problem because items are removed from a plot before + # switching). + items.add(vid) + else: + # Add the item to the plot, or generate a new item if needed. + if isinstance(val, QtGui.QGraphicsItem): + self.plot.addItem(val) + item = val + else: + item = self.plot.plot(val) + self.items[vid] = item + items.add(vid) + + # Any left-over items that did not appear in the input must be removed + for vid in list(self.items.keys()): + if vid not in items: + self.plot.removeItem(self.items[vid]) + del self.items[vid] + + def processBypassed(self, args): + if self.plot is None: + return + for item in list(self.items.values()): + self.plot.removeItem(item) + self.items = {} + + def ctrlWidget(self): + if self.ui is None: + self.ui = ComboBox() + self.ui.currentIndexChanged.connect(self.plotSelected) + self.updateUi() + return self.ui + + def plotSelected(self, index): + self.setPlot(self.ui.value()) + + def setPlotList(self, plots): + """ + Specify the set of plots (PlotWidget or PlotItem) that the user may + select from. + + *plots* must be a dictionary of {name: plot} pairs. + """ + self.plots = plots + self.updateUi() + + def updateUi(self): + # sets list and automatically preserves previous selection + self.ui.setItems(self.plots) + try: + self.ui.setValue(self.plot) + except ValueError: + pass + + +class CanvasNode(Node): + """Connection to a Canvas widget.""" + nodeName = 'CanvasWidget' + + def __init__(self, name): + Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}}) + self.canvas = None + self.items = {} + + def disconnected(self, localTerm, remoteTerm): + if localTerm is self.In and remoteTerm in self.items: + self.canvas.removeItem(self.items[remoteTerm]) + del self.items[remoteTerm] + + def setCanvas(self, canvas): + self.canvas = canvas + + def getCanvas(self): + return self.canvas + + def process(self, In, display=True): + if display: + items = set() + for name, vals in In.items(): + if vals is None: + continue + if type(vals) is not list: + vals = [vals] + + for val in vals: + vid = id(val) + if vid in self.items: + items.add(vid) + else: + self.canvas.addItem(val) + item = val + self.items[vid] = item + items.add(vid) + for vid in list(self.items.keys()): + if vid not in items: + #print "remove", self.items[vid] + self.canvas.removeItem(self.items[vid]) + del self.items[vid] + + +class PlotCurve(CtrlNode): + """Generates a plot curve from x/y data""" + nodeName = 'PlotCurve' + uiTemplate = [ + ('color', 'color'), + ] + + def __init__(self, name): + CtrlNode.__init__(self, name, terminals={ + 'x': {'io': 'in'}, + 'y': {'io': 'in'}, + 'plot': {'io': 'out'} + }) + self.item = PlotDataItem() + + def process(self, x, y, display=True): + #print "scatterplot process" + if not display: + return {'plot': None} + + self.item.setData(x, y, pen=self.ctrls['color'].color()) + return {'plot': self.item} + + + + +class ScatterPlot(CtrlNode): + """Generates a scatter plot from a record array or nested dicts""" + nodeName = 'ScatterPlot' + uiTemplate = [ + ('x', 'combo', {'values': [], 'index': 0}), + ('y', 'combo', {'values': [], 'index': 0}), + ('sizeEnabled', 'check', {'value': False}), + ('size', 'combo', {'values': [], 'index': 0}), + ('absoluteSize', 'check', {'value': False}), + ('colorEnabled', 'check', {'value': False}), + ('color', 'colormap', {}), + ('borderEnabled', 'check', {'value': False}), + ('border', 'colormap', {}), + ] + + def __init__(self, name): + CtrlNode.__init__(self, name, terminals={ + 'input': {'io': 'in'}, + 'plot': {'io': 'out'} + }) + self.item = ScatterPlotItem() + self.keys = [] + + #self.ui = QtGui.QWidget() + #self.layout = QtGui.QGridLayout() + #self.ui.setLayout(self.layout) + + #self.xCombo = QtGui.QComboBox() + #self.yCombo = QtGui.QComboBox() + + + + def process(self, input, display=True): + #print "scatterplot process" + if not display: + return {'plot': None} + + self.updateKeys(input[0]) + + x = str(self.ctrls['x'].currentText()) + y = str(self.ctrls['y'].currentText()) + size = str(self.ctrls['size'].currentText()) + pen = QtGui.QPen(QtGui.QColor(0,0,0,0)) + points = [] + for i in input: + pt = {'pos': (i[x], i[y])} + if self.ctrls['sizeEnabled'].isChecked(): + pt['size'] = i[size] + if self.ctrls['borderEnabled'].isChecked(): + pt['pen'] = QtGui.QPen(self.ctrls['border'].getColor(i)) + else: + pt['pen'] = pen + if self.ctrls['colorEnabled'].isChecked(): + pt['brush'] = QtGui.QBrush(self.ctrls['color'].getColor(i)) + points.append(pt) + self.item.setPxMode(not self.ctrls['absoluteSize'].isChecked()) + + self.item.setPoints(points) + + return {'plot': self.item} + + + + def updateKeys(self, data): + if isinstance(data, dict): + keys = list(data.keys()) + elif isinstance(data, list) or isinstance(data, tuple): + keys = data + elif isinstance(data, np.ndarray) or isinstance(data, np.void): + keys = data.dtype.names + else: + print("Unknown data type:", type(data), data) + return + + for c in self.ctrls.values(): + c.blockSignals(True) + for c in [self.ctrls['x'], self.ctrls['y'], self.ctrls['size']]: + cur = str(c.currentText()) + c.clear() + for k in keys: + c.addItem(k) + if k == cur: + c.setCurrentIndex(c.count()-1) + for c in [self.ctrls['color'], self.ctrls['border']]: + c.setArgList(keys) + for c in self.ctrls.values(): + c.blockSignals(False) + + self.keys = keys + + + def saveState(self): + state = CtrlNode.saveState(self) + return {'keys': self.keys, 'ctrls': state} + + def restoreState(self, state): + self.updateKeys(state['keys']) + CtrlNode.restoreState(self, state['ctrls']) + +#class ImageItem(Node): + #"""Creates an ImageItem for display in a canvas from a file handle.""" + #nodeName = 'Image' + + #def __init__(self, name): + #Node.__init__(self, name, terminals={ + #'file': {'io': 'in'}, + #'image': {'io': 'out'} + #}) + #self.imageItem = graphicsItems.ImageItem() + #self.handle = None + + #def process(self, file, display=True): + #if not display: + #return {'image': None} + + #if file != self.handle: + #self.handle = file + #data = file.read() + #self.imageItem.updateImage(data) + + #pos = file. + + + diff --git a/papi/pyqtgraph/flowchart/library/Filters.py b/papi/pyqtgraph/flowchart/library/Filters.py new file mode 100644 index 00000000..88a2f6c5 --- /dev/null +++ b/papi/pyqtgraph/flowchart/library/Filters.py @@ -0,0 +1,346 @@ +# -*- coding: utf-8 -*- +from ...Qt import QtCore, QtGui +from ..Node import Node +from . import functions +from ... import functions as pgfn +from .common import * +import numpy as np + +from ... import PolyLineROI +from ... import Point +from ... import metaarray as metaarray + + +class Downsample(CtrlNode): + """Downsample by averaging samples together.""" + nodeName = 'Downsample' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + def processData(self, data): + return functions.downsample(data, self.ctrls['n'].value(), axis=0) + + +class Subsample(CtrlNode): + """Downsample by selecting every Nth sample.""" + nodeName = 'Subsample' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + def processData(self, data): + return data[::self.ctrls['n'].value()] + + +class Bessel(CtrlNode): + """Bessel filter. Input data must have time values.""" + nodeName = 'BesselFilter' + uiTemplate = [ + ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), + ('cutoff', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('order', 'intSpin', {'value': 4, 'min': 1, 'max': 16}), + ('bidir', 'check', {'checked': True}) + ] + + def processData(self, data): + s = self.stateGroup.state() + if s['band'] == 'lowpass': + mode = 'low' + else: + mode = 'high' + return functions.besselFilter(data, bidir=s['bidir'], btype=mode, cutoff=s['cutoff'], order=s['order']) + + +class Butterworth(CtrlNode): + """Butterworth filter""" + nodeName = 'ButterworthFilter' + uiTemplate = [ + ('band', 'combo', {'values': ['lowpass', 'highpass'], 'index': 0}), + ('wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('bidir', 'check', {'checked': True}) + ] + + def processData(self, data): + s = self.stateGroup.state() + if s['band'] == 'lowpass': + mode = 'low' + else: + mode = 'high' + ret = functions.butterworthFilter(data, bidir=s['bidir'], btype=mode, wPass=s['wPass'], wStop=s['wStop'], gPass=s['gPass'], gStop=s['gStop']) + return ret + + +class ButterworthNotch(CtrlNode): + """Butterworth notch filter""" + nodeName = 'ButterworthNotchFilter' + uiTemplate = [ + ('low_wPass', 'spin', {'value': 1000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('low_wStop', 'spin', {'value': 2000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('low_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('low_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('high_wPass', 'spin', {'value': 3000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('high_wStop', 'spin', {'value': 4000., 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'Hz', 'siPrefix': True}), + ('high_gPass', 'spin', {'value': 2.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('high_gStop', 'spin', {'value': 20.0, 'step': 1, 'dec': True, 'range': [0.0, None], 'suffix': 'dB', 'siPrefix': True}), + ('bidir', 'check', {'checked': True}) + ] + + def processData(self, data): + s = self.stateGroup.state() + + low = functions.butterworthFilter(data, bidir=s['bidir'], btype='low', wPass=s['low_wPass'], wStop=s['low_wStop'], gPass=s['low_gPass'], gStop=s['low_gStop']) + high = functions.butterworthFilter(data, bidir=s['bidir'], btype='high', wPass=s['high_wPass'], wStop=s['high_wStop'], gPass=s['high_gPass'], gStop=s['high_gStop']) + return low + high + + +class Mean(CtrlNode): + """Filters data by taking the mean of a sliding window""" + nodeName = 'MeanFilter' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + @metaArrayWrapper + def processData(self, data): + n = self.ctrls['n'].value() + return functions.rollingSum(data, n) / n + + +class Median(CtrlNode): + """Filters data by taking the median of a sliding window""" + nodeName = 'MedianFilter' + uiTemplate = [ + ('n', 'intSpin', {'min': 1, 'max': 1000000}) + ] + + @metaArrayWrapper + def processData(self, data): + try: + import scipy.ndimage + except ImportError: + raise Exception("MedianFilter node requires the package scipy.ndimage.") + return scipy.ndimage.median_filter(data, self.ctrls['n'].value()) + +class Mode(CtrlNode): + """Filters data by taking the mode (histogram-based) of a sliding window""" + nodeName = 'ModeFilter' + uiTemplate = [ + ('window', 'intSpin', {'value': 500, 'min': 1, 'max': 1000000}), + ] + + @metaArrayWrapper + def processData(self, data): + return functions.modeFilter(data, self.ctrls['window'].value()) + + +class Denoise(CtrlNode): + """Removes anomalous spikes from data, replacing with nearby values""" + nodeName = 'DenoiseFilter' + uiTemplate = [ + ('radius', 'intSpin', {'value': 2, 'min': 0, 'max': 1000000}), + ('threshold', 'doubleSpin', {'value': 4.0, 'min': 0, 'max': 1000}) + ] + + def processData(self, data): + #print "DENOISE" + s = self.stateGroup.state() + return functions.denoise(data, **s) + + +class Gaussian(CtrlNode): + """Gaussian smoothing filter.""" + nodeName = 'GaussianFilter' + uiTemplate = [ + ('sigma', 'doubleSpin', {'min': 0, 'max': 1000000}) + ] + + @metaArrayWrapper + def processData(self, data): + try: + import scipy.ndimage + except ImportError: + raise Exception("GaussianFilter node requires the package scipy.ndimage.") + return pgfn.gaussianFilter(data, self.ctrls['sigma'].value()) + + +class Derivative(CtrlNode): + """Returns the pointwise derivative of the input""" + nodeName = 'DerivativeFilter' + + def processData(self, data): + if hasattr(data, 'implements') and data.implements('MetaArray'): + info = data.infoCopy() + if 'values' in info[0]: + info[0]['values'] = info[0]['values'][:-1] + return metaarray.MetaArray(data[1:] - data[:-1], info=info) + else: + return data[1:] - data[:-1] + + +class Integral(CtrlNode): + """Returns the pointwise integral of the input""" + nodeName = 'IntegralFilter' + + @metaArrayWrapper + def processData(self, data): + data[1:] += data[:-1] + return data + + +class Detrend(CtrlNode): + """Removes linear trend from the data""" + nodeName = 'DetrendFilter' + + @metaArrayWrapper + def processData(self, data): + try: + from scipy.signal import detrend + except ImportError: + raise Exception("DetrendFilter node requires the package scipy.signal.") + return detrend(data) + +class RemoveBaseline(PlottingCtrlNode): + """Remove an arbitrary, graphically defined baseline from the data.""" + nodeName = 'RemoveBaseline' + + def __init__(self, name): + ## define inputs and outputs (one output needs to be a plot) + PlottingCtrlNode.__init__(self, name) + self.line = PolyLineROI([[0,0],[1,0]]) + self.line.sigRegionChanged.connect(self.changed) + + ## create a PolyLineROI, add it to a plot -- actually, I think we want to do this after the node is connected to a plot (look at EventDetection.ThresholdEvents node for ideas), and possible after there is data. We will need to update the end positions of the line each time the input data changes + #self.line = None ## will become a PolyLineROI + + def connectToPlot(self, node): + """Define what happens when the node is connected to a plot""" + + if node.plot is None: + return + node.getPlot().addItem(self.line) + + def disconnectFromPlot(self, plot): + """Define what happens when the node is disconnected from a plot""" + plot.removeItem(self.line) + + def processData(self, data): + ## get array of baseline (from PolyLineROI) + h0 = self.line.getHandles()[0] + h1 = self.line.getHandles()[-1] + + timeVals = data.xvals(0) + h0.setPos(timeVals[0], h0.pos()[1]) + h1.setPos(timeVals[-1], h1.pos()[1]) + + pts = self.line.listPoints() ## lists line handles in same coordinates as data + pts, indices = self.adjustXPositions(pts, timeVals) ## maxe sure x positions match x positions of data points + + ## construct an array that represents the baseline + arr = np.zeros(len(data), dtype=float) + n = 1 + arr[0] = pts[0].y() + for i in range(len(pts)-1): + x1 = pts[i].x() + x2 = pts[i+1].x() + y1 = pts[i].y() + y2 = pts[i+1].y() + m = (y2-y1)/(x2-x1) + b = y1 + + times = timeVals[(timeVals > x1)*(timeVals <= x2)] + arr[n:n+len(times)] = (m*(times-times[0]))+b + n += len(times) + + return data - arr ## subract baseline from data + + def adjustXPositions(self, pts, data): + """Return a list of Point() where the x position is set to the nearest x value in *data* for each point in *pts*.""" + points = [] + timeIndices = [] + for p in pts: + x = np.argwhere(abs(data - p.x()) == abs(data - p.x()).min()) + points.append(Point(data[x], p.y())) + timeIndices.append(x) + + return points, timeIndices + + + +class AdaptiveDetrend(CtrlNode): + """Removes baseline from data, ignoring anomalous events""" + nodeName = 'AdaptiveDetrend' + uiTemplate = [ + ('threshold', 'doubleSpin', {'value': 3.0, 'min': 0, 'max': 1000000}) + ] + + def processData(self, data): + return functions.adaptiveDetrend(data, threshold=self.ctrls['threshold'].value()) + +class HistogramDetrend(CtrlNode): + """Removes baseline from data by computing mode (from histogram) of beginning and end of data.""" + nodeName = 'HistogramDetrend' + uiTemplate = [ + ('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), + ('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}), + ('offsetOnly', 'check', {'checked': False}), + ] + + def processData(self, data): + s = self.stateGroup.state() + #ws = self.ctrls['windowSize'].value() + #bn = self.ctrls['numBins'].value() + #offset = self.ctrls['offsetOnly'].checked() + return functions.histogramDetrend(data, window=s['windowSize'], bins=s['numBins'], offsetOnly=s['offsetOnly']) + + + +class RemovePeriodic(CtrlNode): + nodeName = 'RemovePeriodic' + uiTemplate = [ + #('windowSize', 'intSpin', {'value': 500, 'min': 10, 'max': 1000000, 'suffix': 'pts'}), + #('numBins', 'intSpin', {'value': 50, 'min': 3, 'max': 1000000}) + ('f0', 'spin', {'value': 60, 'suffix': 'Hz', 'siPrefix': True, 'min': 0, 'max': None}), + ('harmonics', 'intSpin', {'value': 30, 'min': 0}), + ('samples', 'intSpin', {'value': 1, 'min': 1}), + ] + + def processData(self, data): + times = data.xvals('Time') + dt = times[1]-times[0] + + data1 = data.asarray() + ft = np.fft.fft(data1) + + ## determine frequencies in fft data + df = 1.0 / (len(data1) * dt) + freqs = np.linspace(0.0, (len(ft)-1) * df, len(ft)) + + ## flatten spikes at f0 and harmonics + f0 = self.ctrls['f0'].value() + for i in xrange(1, self.ctrls['harmonics'].value()+2): + f = f0 * i # target frequency + + ## determine index range to check for this frequency + ind1 = int(np.floor(f / df)) + ind2 = int(np.ceil(f / df)) + (self.ctrls['samples'].value()-1) + if ind1 > len(ft)/2.: + break + mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5 + for j in range(ind1, ind2+1): + phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts. + re = mag * np.cos(phase) + im = mag * np.sin(phase) + ft[j] = re + im*1j + ft[len(ft)-j] = re - im*1j + + data2 = np.fft.ifft(ft).real + + ma = metaarray.MetaArray(data2, info=data.infoCopy()) + return ma + + + diff --git a/papi/pyqtgraph/flowchart/library/Operators.py b/papi/pyqtgraph/flowchart/library/Operators.py new file mode 100644 index 00000000..579d2cd2 --- /dev/null +++ b/papi/pyqtgraph/flowchart/library/Operators.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from ..Node import Node + +class UniOpNode(Node): + """Generic node for performing any operation like Out = In.fn()""" + def __init__(self, name, fn): + self.fn = fn + Node.__init__(self, name, terminals={ + 'In': {'io': 'in'}, + 'Out': {'io': 'out', 'bypass': 'In'} + }) + + def process(self, **args): + return {'Out': getattr(args['In'], self.fn)()} + +class BinOpNode(Node): + """Generic node for performing any operation like A.fn(B)""" + def __init__(self, name, fn): + self.fn = fn + Node.__init__(self, name, terminals={ + 'A': {'io': 'in'}, + 'B': {'io': 'in'}, + 'Out': {'io': 'out', 'bypass': 'A'} + }) + + def process(self, **args): + if isinstance(self.fn, tuple): + for name in self.fn: + try: + fn = getattr(args['A'], name) + break + except AttributeError: + pass + else: + fn = getattr(args['A'], self.fn) + out = fn(args['B']) + if out is NotImplemented: + raise Exception("Operation %s not implemented between %s and %s" % (fn, str(type(args['A'])), str(type(args['B'])))) + #print " ", fn, out + return {'Out': out} + + +class AbsNode(UniOpNode): + """Returns abs(Inp). Does not check input types.""" + nodeName = 'Abs' + def __init__(self, name): + UniOpNode.__init__(self, name, '__abs__') + +class AddNode(BinOpNode): + """Returns A + B. Does not check input types.""" + nodeName = 'Add' + def __init__(self, name): + BinOpNode.__init__(self, name, '__add__') + +class SubtractNode(BinOpNode): + """Returns A - B. Does not check input types.""" + nodeName = 'Subtract' + def __init__(self, name): + BinOpNode.__init__(self, name, '__sub__') + +class MultiplyNode(BinOpNode): + """Returns A * B. Does not check input types.""" + nodeName = 'Multiply' + def __init__(self, name): + BinOpNode.__init__(self, name, '__mul__') + +class DivideNode(BinOpNode): + """Returns A / B. Does not check input types.""" + nodeName = 'Divide' + def __init__(self, name): + # try truediv first, followed by div + BinOpNode.__init__(self, name, ('__truediv__', '__div__')) + + diff --git a/papi/pyqtgraph/flowchart/library/__init__.py b/papi/pyqtgraph/flowchart/library/__init__.py new file mode 100644 index 00000000..d8038aa4 --- /dev/null +++ b/papi/pyqtgraph/flowchart/library/__init__.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from ...pgcollections import OrderedDict +import os, types +from ...debug import printExc +from ..NodeLibrary import NodeLibrary, isNodeClass +from ... import reload as reload + + +# Build default library +LIBRARY = NodeLibrary() + +# For backward compatibility, expose the default library's properties here: +NODE_LIST = LIBRARY.nodeList +NODE_TREE = LIBRARY.nodeTree +registerNodeType = LIBRARY.addNodeType +getNodeTree = LIBRARY.getNodeTree +getNodeType = LIBRARY.getNodeType + +# Add all nodes to the default library +from . import Data, Display, Filters, Operators +for mod in [Data, Display, Filters, Operators]: + nodes = [getattr(mod, name) for name in dir(mod) if isNodeClass(getattr(mod, name))] + for node in nodes: + LIBRARY.addNodeType(node, [(mod.__name__.split('.')[-1],)]) + + + + diff --git a/papi/pyqtgraph/flowchart/library/common.py b/papi/pyqtgraph/flowchart/library/common.py new file mode 100644 index 00000000..425fe86c --- /dev/null +++ b/papi/pyqtgraph/flowchart/library/common.py @@ -0,0 +1,184 @@ +# -*- coding: utf-8 -*- +from ...Qt import QtCore, QtGui +from ...widgets.SpinBox import SpinBox +#from ...SignalProxy import SignalProxy +from ...WidgetGroup import WidgetGroup +#from ColorMapper import ColorMapper +from ..Node import Node +import numpy as np +from ...widgets.ColorButton import ColorButton +try: + import metaarray + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + + +def generateUi(opts): + """Convenience function for generating common UI types""" + widget = QtGui.QWidget() + l = QtGui.QFormLayout() + l.setSpacing(0) + widget.setLayout(l) + ctrls = {} + row = 0 + for opt in opts: + if len(opt) == 2: + k, t = opt + o = {} + elif len(opt) == 3: + k, t, o = opt + else: + raise Exception("Widget specification must be (name, type) or (name, type, {opts})") + if t == 'intSpin': + w = QtGui.QSpinBox() + if 'max' in o: + w.setMaximum(o['max']) + if 'min' in o: + w.setMinimum(o['min']) + if 'value' in o: + w.setValue(o['value']) + elif t == 'doubleSpin': + w = QtGui.QDoubleSpinBox() + if 'max' in o: + w.setMaximum(o['max']) + if 'min' in o: + w.setMinimum(o['min']) + if 'value' in o: + w.setValue(o['value']) + elif t == 'spin': + w = SpinBox() + w.setOpts(**o) + elif t == 'check': + w = QtGui.QCheckBox() + if 'checked' in o: + w.setChecked(o['checked']) + elif t == 'combo': + w = QtGui.QComboBox() + for i in o['values']: + w.addItem(i) + #elif t == 'colormap': + #w = ColorMapper() + elif t == 'color': + w = ColorButton() + else: + raise Exception("Unknown widget type '%s'" % str(t)) + if 'tip' in o: + w.setToolTip(o['tip']) + w.setObjectName(k) + l.addRow(k, w) + if o.get('hidden', False): + w.hide() + label = l.labelForField(w) + label.hide() + + ctrls[k] = w + w.rowNum = row + row += 1 + group = WidgetGroup(widget) + return widget, group, ctrls + + +class CtrlNode(Node): + """Abstract class for nodes with auto-generated control UI""" + + sigStateChanged = QtCore.Signal(object) + + def __init__(self, name, ui=None, terminals=None): + if ui is None: + if hasattr(self, 'uiTemplate'): + ui = self.uiTemplate + else: + ui = [] + if terminals is None: + terminals = {'In': {'io': 'in'}, 'Out': {'io': 'out', 'bypass': 'In'}} + Node.__init__(self, name=name, terminals=terminals) + + self.ui, self.stateGroup, self.ctrls = generateUi(ui) + self.stateGroup.sigChanged.connect(self.changed) + + def ctrlWidget(self): + return self.ui + + def changed(self): + self.update() + self.sigStateChanged.emit(self) + + def process(self, In, display=True): + out = self.processData(In) + return {'Out': out} + + def saveState(self): + state = Node.saveState(self) + state['ctrl'] = self.stateGroup.state() + return state + + def restoreState(self, state): + Node.restoreState(self, state) + if self.stateGroup is not None: + self.stateGroup.setState(state.get('ctrl', {})) + + def hideRow(self, name): + w = self.ctrls[name] + l = self.ui.layout().labelForField(w) + w.hide() + l.hide() + + def showRow(self, name): + w = self.ctrls[name] + l = self.ui.layout().labelForField(w) + w.show() + l.show() + + +class PlottingCtrlNode(CtrlNode): + """Abstract class for CtrlNodes that can connect to plots.""" + + def __init__(self, name, ui=None, terminals=None): + #print "PlottingCtrlNode.__init__ called." + CtrlNode.__init__(self, name, ui=ui, terminals=terminals) + self.plotTerminal = self.addOutput('plot', optional=True) + + def connected(self, term, remote): + CtrlNode.connected(self, term, remote) + if term is not self.plotTerminal: + return + node = remote.node() + node.sigPlotChanged.connect(self.connectToPlot) + self.connectToPlot(node) + + def disconnected(self, term, remote): + CtrlNode.disconnected(self, term, remote) + if term is not self.plotTerminal: + return + remote.node().sigPlotChanged.disconnect(self.connectToPlot) + self.disconnectFromPlot(remote.node().getPlot()) + + def connectToPlot(self, node): + """Define what happens when the node is connected to a plot""" + raise Exception("Must be re-implemented in subclass") + + def disconnectFromPlot(self, plot): + """Define what happens when the node is disconnected from a plot""" + raise Exception("Must be re-implemented in subclass") + + def process(self, In, display=True): + out = CtrlNode.process(self, In, display) + out['plot'] = None + return out + + +def metaArrayWrapper(fn): + def newFn(self, data, *args, **kargs): + if HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): + d1 = fn(self, data.view(np.ndarray), *args, **kargs) + info = data.infoCopy() + if d1.shape != data.shape: + for i in range(data.ndim): + if 'values' in info[i]: + info[i]['values'] = info[i]['values'][:d1.shape[i]] + return metaarray.MetaArray(d1, info=info) + else: + return fn(self, data, *args, **kargs) + return newFn + diff --git a/papi/pyqtgraph/flowchart/library/functions.py b/papi/pyqtgraph/flowchart/library/functions.py new file mode 100644 index 00000000..338d25c4 --- /dev/null +++ b/papi/pyqtgraph/flowchart/library/functions.py @@ -0,0 +1,355 @@ +import numpy as np +from ...metaarray import MetaArray + +def downsample(data, n, axis=0, xvals='subsample'): + """Downsample by averaging points together across axis. + If multiple axes are specified, runs once per axis. + If a metaArray is given, then the axis values can be either subsampled + or downsampled to match. + """ + ma = None + if (hasattr(data, 'implements') and data.implements('MetaArray')): + ma = data + data = data.view(np.ndarray) + + + if hasattr(axis, '__len__'): + if not hasattr(n, '__len__'): + n = [n]*len(axis) + for i in range(len(axis)): + data = downsample(data, n[i], axis[i]) + return data + + nPts = int(data.shape[axis] / n) + s = list(data.shape) + s[axis] = nPts + s.insert(axis+1, n) + sl = [slice(None)] * data.ndim + sl[axis] = slice(0, nPts*n) + d1 = data[tuple(sl)] + #print d1.shape, s + d1.shape = tuple(s) + d2 = d1.mean(axis+1) + + if ma is None: + return d2 + else: + info = ma.infoCopy() + if 'values' in info[axis]: + if xvals == 'subsample': + info[axis]['values'] = info[axis]['values'][::n][:nPts] + elif xvals == 'downsample': + info[axis]['values'] = downsample(info[axis]['values'], n) + return MetaArray(d2, info=info) + + +def applyFilter(data, b, a, padding=100, bidir=True): + """Apply a linear filter with coefficients a, b. Optionally pad the data before filtering + and/or run the filter in both directions.""" + try: + import scipy.signal + except ImportError: + raise Exception("applyFilter() requires the package scipy.signal.") + + d1 = data.view(np.ndarray) + + if padding > 0: + d1 = np.hstack([d1[:padding], d1, d1[-padding:]]) + + if bidir: + d1 = scipy.signal.lfilter(b, a, scipy.signal.lfilter(b, a, d1)[::-1])[::-1] + else: + d1 = scipy.signal.lfilter(b, a, d1) + + if padding > 0: + d1 = d1[padding:-padding] + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return MetaArray(d1, info=data.infoCopy()) + else: + return d1 + +def besselFilter(data, cutoff, order=1, dt=None, btype='low', bidir=True): + """return data passed through bessel filter""" + try: + import scipy.signal + except ImportError: + raise Exception("besselFilter() requires the package scipy.signal.") + + if dt is None: + try: + tvals = data.xvals('Time') + dt = (tvals[-1]-tvals[0]) / (len(tvals)-1) + except: + dt = 1.0 + + b,a = scipy.signal.bessel(order, cutoff * dt, btype=btype) + + return applyFilter(data, b, a, bidir=bidir) + #base = data.mean() + #d1 = scipy.signal.lfilter(b, a, data.view(ndarray)-base) + base + #if (hasattr(data, 'implements') and data.implements('MetaArray')): + #return MetaArray(d1, info=data.infoCopy()) + #return d1 + +def butterworthFilter(data, wPass, wStop=None, gPass=2.0, gStop=20.0, order=1, dt=None, btype='low', bidir=True): + """return data passed through bessel filter""" + try: + import scipy.signal + except ImportError: + raise Exception("butterworthFilter() requires the package scipy.signal.") + + if dt is None: + try: + tvals = data.xvals('Time') + dt = (tvals[-1]-tvals[0]) / (len(tvals)-1) + except: + dt = 1.0 + + if wStop is None: + wStop = wPass * 2.0 + ord, Wn = scipy.signal.buttord(wPass*dt*2., wStop*dt*2., gPass, gStop) + #print "butterworth ord %f Wn %f c %f sc %f" % (ord, Wn, cutoff, stopCutoff) + b,a = scipy.signal.butter(ord, Wn, btype=btype) + + return applyFilter(data, b, a, bidir=bidir) + + +def rollingSum(data, n): + d1 = data.copy() + d1[1:] += d1[:-1] # integrate + d2 = np.empty(len(d1) - n + 1, dtype=data.dtype) + d2[0] = d1[n-1] # copy first point + d2[1:] = d1[n:] - d1[:-n] # subtract + return d2 + + +def mode(data, bins=None): + """Returns location max value from histogram.""" + if bins is None: + bins = int(len(data)/10.) + if bins < 2: + bins = 2 + y, x = np.histogram(data, bins=bins) + ind = np.argmax(y) + mode = 0.5 * (x[ind] + x[ind+1]) + return mode + +def modeFilter(data, window=500, step=None, bins=None): + """Filter based on histogram-based mode function""" + d1 = data.view(np.ndarray) + vals = [] + l2 = int(window/2.) + if step is None: + step = l2 + i = 0 + while True: + if i > len(data)-step: + break + vals.append(mode(d1[i:i+window], bins)) + i += step + + chunks = [np.linspace(vals[0], vals[0], l2)] + for i in range(len(vals)-1): + chunks.append(np.linspace(vals[i], vals[i+1], step)) + remain = len(data) - step*(len(vals)-1) - l2 + chunks.append(np.linspace(vals[-1], vals[-1], remain)) + d2 = np.hstack(chunks) + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return MetaArray(d2, info=data.infoCopy()) + return d2 + +def denoise(data, radius=2, threshold=4): + """Very simple noise removal function. Compares a point to surrounding points, + replaces with nearby values if the difference is too large.""" + + + r2 = radius * 2 + d1 = data.view(np.ndarray) + d2 = d1[radius:] - d1[:-radius] #a derivative + #d3 = data[r2:] - data[:-r2] + #d4 = d2 - d3 + stdev = d2.std() + #print "denoise: stdev of derivative:", stdev + mask1 = d2 > stdev*threshold #where derivative is large and positive + mask2 = d2 < -stdev*threshold #where derivative is large and negative + maskpos = mask1[:-radius] * mask2[radius:] #both need to be true + maskneg = mask1[radius:] * mask2[:-radius] + mask = maskpos + maskneg + d5 = np.where(mask, d1[:-r2], d1[radius:-radius]) #where both are true replace the value with the value from 2 points before + d6 = np.empty(d1.shape, dtype=d1.dtype) #add points back to the ends + d6[radius:-radius] = d5 + d6[:radius] = d1[:radius] + d6[-radius:] = d1[-radius:] + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return MetaArray(d6, info=data.infoCopy()) + return d6 + +def adaptiveDetrend(data, x=None, threshold=3.0): + """Return the signal with baseline removed. Discards outliers from baseline measurement.""" + try: + import scipy.signal + except ImportError: + raise Exception("adaptiveDetrend() requires the package scipy.signal.") + + if x is None: + x = data.xvals(0) + + d = data.view(np.ndarray) + + d2 = scipy.signal.detrend(d) + + stdev = d2.std() + mask = abs(d2) < stdev*threshold + #d3 = where(mask, 0, d2) + #d4 = d2 - lowPass(d3, cutoffs[1], dt=dt) + + lr = scipy.stats.linregress(x[mask], d[mask]) + base = lr[1] + lr[0]*x + d4 = d - base + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return MetaArray(d4, info=data.infoCopy()) + return d4 + + +def histogramDetrend(data, window=500, bins=50, threshold=3.0, offsetOnly=False): + """Linear detrend. Works by finding the most common value at the beginning and end of a trace, excluding outliers. + If offsetOnly is True, then only the offset from the beginning of the trace is subtracted. + """ + + d1 = data.view(np.ndarray) + d2 = [d1[:window], d1[-window:]] + v = [0, 0] + for i in [0, 1]: + d3 = d2[i] + stdev = d3.std() + mask = abs(d3-np.median(d3)) < stdev*threshold + d4 = d3[mask] + y, x = np.histogram(d4, bins=bins) + ind = np.argmax(y) + v[i] = 0.5 * (x[ind] + x[ind+1]) + + if offsetOnly: + d3 = data.view(np.ndarray) - v[0] + else: + base = np.linspace(v[0], v[1], len(data)) + d3 = data.view(np.ndarray) - base + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return MetaArray(d3, info=data.infoCopy()) + return d3 + +def concatenateColumns(data): + """Returns a single record array with columns taken from the elements in data. + data should be a list of elements, which can be either record arrays or tuples (name, type, data) + """ + + ## first determine dtype + dtype = [] + names = set() + maxLen = 0 + for element in data: + if isinstance(element, np.ndarray): + ## use existing columns + for i in range(len(element.dtype)): + name = element.dtype.names[i] + dtype.append((name, element.dtype[i])) + maxLen = max(maxLen, len(element)) + else: + name, type, d = element + if type is None: + type = suggestDType(d) + dtype.append((name, type)) + if isinstance(d, list) or isinstance(d, np.ndarray): + maxLen = max(maxLen, len(d)) + if name in names: + raise Exception('Name "%s" repeated' % name) + names.add(name) + + + + ## create empty array + out = np.empty(maxLen, dtype) + + ## fill columns + for element in data: + if isinstance(element, np.ndarray): + for i in range(len(element.dtype)): + name = element.dtype.names[i] + try: + out[name] = element[name] + except: + print("Column:", name) + print("Input shape:", element.shape, element.dtype) + print("Output shape:", out.shape, out.dtype) + raise + else: + name, type, d = element + out[name] = d + + return out + +def suggestDType(x): + """Return a suitable dtype for x""" + if isinstance(x, list) or isinstance(x, tuple): + if len(x) == 0: + raise Exception('can not determine dtype for empty list') + x = x[0] + + if hasattr(x, 'dtype'): + return x.dtype + elif isinstance(x, float): + return float + elif isinstance(x, int): + return int + #elif isinstance(x, basestring): ## don't try to guess correct string length; use object instead. + #return ' len(ft)/2.: + break + mag = (abs(ft[ind1-1]) + abs(ft[ind2+1])) * 0.5 + for j in range(ind1, ind2+1): + phase = np.angle(ft[j]) ## Must preserve the phase of each point, otherwise any transients in the trace might lead to large artifacts. + re = mag * np.cos(phase) + im = mag * np.sin(phase) + ft[j] = re + im*1j + ft[len(ft)-j] = re - im*1j + + data2 = np.fft.ifft(ft).real + + if (hasattr(data, 'implements') and data.implements('MetaArray')): + return metaarray.MetaArray(data2, info=data.infoCopy()) + else: + return data2 + + + \ No newline at end of file diff --git a/papi/pyqtgraph/frozenSupport.py b/papi/pyqtgraph/frozenSupport.py new file mode 100644 index 00000000..c42a12e1 --- /dev/null +++ b/papi/pyqtgraph/frozenSupport.py @@ -0,0 +1,52 @@ +## Definitions helpful in frozen environments (eg py2exe) +import os, sys, zipfile + +def listdir(path): + """Replacement for os.listdir that works in frozen environments.""" + if not hasattr(sys, 'frozen'): + return os.listdir(path) + + (zipPath, archivePath) = splitZip(path) + if archivePath is None: + return os.listdir(path) + + with zipfile.ZipFile(zipPath, "r") as zipobj: + contents = zipobj.namelist() + results = set() + for name in contents: + # components in zip archive paths are always separated by forward slash + if name.startswith(archivePath) and len(name) > len(archivePath): + name = name[len(archivePath):].split('/')[0] + results.add(name) + return list(results) + +def isdir(path): + """Replacement for os.path.isdir that works in frozen environments.""" + if not hasattr(sys, 'frozen'): + return os.path.isdir(path) + + (zipPath, archivePath) = splitZip(path) + if archivePath is None: + return os.path.isdir(path) + with zipfile.ZipFile(zipPath, "r") as zipobj: + contents = zipobj.namelist() + archivePath = archivePath.rstrip('/') + '/' ## make sure there's exactly one '/' at the end + for c in contents: + if c.startswith(archivePath): + return True + return False + + +def splitZip(path): + """Splits a path containing a zip file into (zipfile, subpath). + If there is no zip file, returns (path, None)""" + components = os.path.normpath(path).split(os.sep) + for index, component in enumerate(components): + if component.endswith('.zip'): + zipPath = os.sep.join(components[0:index+1]) + archivePath = ''.join([x+'/' for x in components[index+1:]]) + return (zipPath, archivePath) + else: + return (path, None) + + \ No newline at end of file diff --git a/papi/pyqtgraph/functions.py b/papi/pyqtgraph/functions.py new file mode 100644 index 00000000..897a123d --- /dev/null +++ b/papi/pyqtgraph/functions.py @@ -0,0 +1,2252 @@ +# -*- coding: utf-8 -*- +""" +functions.py - Miscellaneous functions with no other home +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from __future__ import division +from .python2_3 import asUnicode +from .Qt import QtGui, QtCore, USE_PYSIDE +Colors = { + 'b': QtGui.QColor(0,0,255,255), + 'g': QtGui.QColor(0,255,0,255), + 'r': QtGui.QColor(255,0,0,255), + 'c': QtGui.QColor(0,255,255,255), + 'm': QtGui.QColor(255,0,255,255), + 'y': QtGui.QColor(255,255,0,255), + 'k': QtGui.QColor(0,0,0,255), + 'w': QtGui.QColor(255,255,255,255), + 'd': QtGui.QColor(150,150,150,255), + 'l': QtGui.QColor(200,200,200,255), + 's': QtGui.QColor(100,100,150,255), +} + +SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY') +SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY' + + + +from .Qt import QtGui, QtCore, USE_PYSIDE +from . import getConfigOption, setConfigOptions +import numpy as np +import decimal, re +import ctypes +import sys, struct + +from . import debug + +def siScale(x, minVal=1e-25, allowUnicode=True): + """ + Return the recommended scale factor and SI prefix string for x. + + Example:: + + siScale(0.0001) # returns (1e6, 'μ') + # This indicates that the number 0.0001 is best represented as 0.0001 * 1e6 = 100 μUnits + """ + + if isinstance(x, decimal.Decimal): + x = float(x) + + try: + if np.isnan(x) or np.isinf(x): + return(1, '') + except: + print(x, type(x)) + raise + if abs(x) < minVal: + m = 0 + x = 0 + else: + m = int(np.clip(np.floor(np.log(abs(x))/np.log(1000)), -9.0, 9.0)) + + if m == 0: + pref = '' + elif m < -8 or m > 8: + pref = 'e%d' % (m*3) + else: + if allowUnicode: + pref = SI_PREFIXES[m+8] + else: + pref = SI_PREFIXES_ASCII[m+8] + p = .001**m + + return (p, pref) + +def siFormat(x, precision=3, suffix='', space=True, error=None, minVal=1e-25, allowUnicode=True): + """ + Return the number x formatted in engineering notation with SI prefix. + + Example:: + siFormat(0.0001, suffix='V') # returns "100 μV" + """ + + if space is True: + space = ' ' + if space is False: + space = '' + + + (p, pref) = siScale(x, minVal, allowUnicode) + if not (len(pref) > 0 and pref[0] == 'e'): + pref = space + pref + + if error is None: + fmt = "%." + str(precision) + "g%s%s" + return fmt % (x*p, pref, suffix) + else: + if allowUnicode: + plusminus = space + asUnicode("±") + space + else: + plusminus = " +/- " + fmt = "%." + str(precision) + "g%s%s%s%s" + return fmt % (x*p, pref, suffix, plusminus, siFormat(error, precision=precision, suffix=suffix, space=space, minVal=minVal)) + +def siEval(s): + """ + Convert a value written in SI notation to its equivalent prefixless value + + Example:: + + siEval("100 μV") # returns 0.0001 + """ + + s = asUnicode(s) + m = re.match(r'(-?((\d+(\.\d*)?)|(\.\d+))([eE]-?\d+)?)\s*([u' + SI_PREFIXES + r']?).*$', s) + if m is None: + raise Exception("Can't convert string '%s' to number." % s) + v = float(m.groups()[0]) + p = m.groups()[6] + #if p not in SI_PREFIXES: + #raise Exception("Can't convert string '%s' to number--unknown prefix." % s) + if p == '': + n = 0 + elif p == 'u': + n = -2 + else: + n = SI_PREFIXES.index(p) - 8 + return v * 1000**n + + +class Color(QtGui.QColor): + def __init__(self, *args): + QtGui.QColor.__init__(self, mkColor(*args)) + + def glColor(self): + """Return (r,g,b,a) normalized for use in opengl""" + return (self.red()/255., self.green()/255., self.blue()/255., self.alpha()/255.) + + def __getitem__(self, ind): + return (self.red, self.green, self.blue, self.alpha)[ind]() + + +def mkColor(*args): + """ + Convenience function for constructing QColor from a variety of argument types. Accepted arguments are: + + ================ ================================================ + 'c' one of: r, g, b, c, m, y, k, w + R, G, B, [A] integers 0-255 + (R, G, B, [A]) tuple of integers 0-255 + float greyscale, 0.0-1.0 + int see :func:`intColor() ` + (int, hues) see :func:`intColor() ` + "RGB" hexadecimal strings; may begin with '#' + "RGBA" + "RRGGBB" + "RRGGBBAA" + QColor QColor instance; makes a copy. + ================ ================================================ + """ + err = 'Not sure how to make a color from "%s"' % str(args) + if len(args) == 1: + if isinstance(args[0], basestring): + c = args[0] + if c[0] == '#': + c = c[1:] + if len(c) == 1: + try: + return Colors[c] + except KeyError: + raise Exception('No color named "%s"' % c) + if len(c) == 3: + r = int(c[0]*2, 16) + g = int(c[1]*2, 16) + b = int(c[2]*2, 16) + a = 255 + elif len(c) == 4: + r = int(c[0]*2, 16) + g = int(c[1]*2, 16) + b = int(c[2]*2, 16) + a = int(c[3]*2, 16) + elif len(c) == 6: + r = int(c[0:2], 16) + g = int(c[2:4], 16) + b = int(c[4:6], 16) + a = 255 + elif len(c) == 8: + r = int(c[0:2], 16) + g = int(c[2:4], 16) + b = int(c[4:6], 16) + a = int(c[6:8], 16) + elif isinstance(args[0], QtGui.QColor): + return QtGui.QColor(args[0]) + elif isinstance(args[0], float): + r = g = b = int(args[0] * 255) + a = 255 + elif hasattr(args[0], '__len__'): + if len(args[0]) == 3: + (r, g, b) = args[0] + a = 255 + elif len(args[0]) == 4: + (r, g, b, a) = args[0] + elif len(args[0]) == 2: + return intColor(*args[0]) + else: + raise Exception(err) + elif type(args[0]) == int: + return intColor(args[0]) + else: + raise Exception(err) + elif len(args) == 3: + (r, g, b) = args + a = 255 + elif len(args) == 4: + (r, g, b, a) = args + else: + raise Exception(err) + + args = [r,g,b,a] + args = [0 if np.isnan(a) or np.isinf(a) else a for a in args] + args = list(map(int, args)) + return QtGui.QColor(*args) + + +def mkBrush(*args, **kwds): + """ + | Convenience function for constructing Brush. + | This function always constructs a solid brush and accepts the same arguments as :func:`mkColor() ` + | Calling mkBrush(None) returns an invisible brush. + """ + if 'color' in kwds: + color = kwds['color'] + elif len(args) == 1: + arg = args[0] + if arg is None: + return QtGui.QBrush(QtCore.Qt.NoBrush) + elif isinstance(arg, QtGui.QBrush): + return QtGui.QBrush(arg) + else: + color = arg + elif len(args) > 1: + color = args + return QtGui.QBrush(mkColor(color)) + +def mkPen(*args, **kargs): + """ + Convenience function for constructing QPen. + + Examples:: + + mkPen(color) + mkPen(color, width=2) + mkPen(cosmetic=False, width=4.5, color='r') + mkPen({'color': "FF0", width: 2}) + mkPen(None) # (no pen) + + In these examples, *color* may be replaced with any arguments accepted by :func:`mkColor() ` """ + + color = kargs.get('color', None) + width = kargs.get('width', 1) + style = kargs.get('style', None) + dash = kargs.get('dash', None) + cosmetic = kargs.get('cosmetic', True) + hsv = kargs.get('hsv', None) + + if len(args) == 1: + arg = args[0] + if isinstance(arg, dict): + return mkPen(**arg) + if isinstance(arg, QtGui.QPen): + return QtGui.QPen(arg) ## return a copy of this pen + elif arg is None: + style = QtCore.Qt.NoPen + else: + color = arg + if len(args) > 1: + color = args + + if color is None: + color = mkColor('l') + if hsv is not None: + color = hsvColor(*hsv) + else: + color = mkColor(color) + + pen = QtGui.QPen(QtGui.QBrush(color), width) + pen.setCosmetic(cosmetic) + if style is not None: + pen.setStyle(style) + if dash is not None: + pen.setDashPattern(dash) + return pen + +def hsvColor(hue, sat=1.0, val=1.0, alpha=1.0): + """Generate a QColor from HSVa values. (all arguments are float 0.0-1.0)""" + c = QtGui.QColor() + c.setHsvF(hue, sat, val, alpha) + return c + + +def colorTuple(c): + """Return a tuple (R,G,B,A) from a QColor""" + return (c.red(), c.green(), c.blue(), c.alpha()) + +def colorStr(c): + """Generate a hex string code from a QColor""" + return ('%02x'*4) % colorTuple(c) + +def intColor(index, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255, **kargs): + """ + Creates a QColor from a single index. Useful for stepping through a predefined list of colors. + + The argument *index* determines which color from the set will be returned. All other arguments determine what the set of predefined colors will be + + Colors are chosen by cycling across hues while varying the value (brightness). + By default, this selects from a list of 9 hues.""" + hues = int(hues) + values = int(values) + ind = int(index) % (hues * values) + indh = ind % hues + indv = ind / hues + if values > 1: + v = minValue + indv * ((maxValue-minValue) / (values-1)) + else: + v = maxValue + h = minHue + (indh * (maxHue-minHue)) / hues + + c = QtGui.QColor() + c.setHsv(h, sat, v) + c.setAlpha(alpha) + return c + +def glColor(*args, **kargs): + """ + Convert a color to OpenGL color format (r,g,b,a) floats 0.0-1.0 + Accepts same arguments as :func:`mkColor `. + """ + c = mkColor(*args, **kargs) + return (c.red()/255., c.green()/255., c.blue()/255., c.alpha()/255.) + + + +def makeArrowPath(headLen=20, tipAngle=20, tailLen=20, tailWidth=3, baseAngle=0): + """ + Construct a path outlining an arrow with the given dimensions. + The arrow points in the -x direction with tip positioned at 0,0. + If *tipAngle* is supplied (in degrees), it overrides *headWidth*. + If *tailLen* is None, no tail will be drawn. + """ + headWidth = headLen * np.tan(tipAngle * 0.5 * np.pi/180.) + path = QtGui.QPainterPath() + path.moveTo(0,0) + path.lineTo(headLen, -headWidth) + if tailLen is None: + innerY = headLen - headWidth * np.tan(baseAngle*np.pi/180.) + path.lineTo(innerY, 0) + else: + tailWidth *= 0.5 + innerY = headLen - (headWidth-tailWidth) * np.tan(baseAngle*np.pi/180.) + path.lineTo(innerY, -tailWidth) + path.lineTo(headLen + tailLen, -tailWidth) + path.lineTo(headLen + tailLen, tailWidth) + path.lineTo(innerY, tailWidth) + path.lineTo(headLen, headWidth) + path.lineTo(0,0) + return path + + + +def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False, **kargs): + """ + Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data. + + The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates if it is available (see the scipy documentation for more information about this). If scipy is not available, then a slower implementation of map_coordinates is used. + + For a graphical interface to this function, see :func:`ROI.getArrayRegion ` + + ============== ==================================================================================================== + **Arguments:** + *data* (ndarray) the original dataset + *shape* the shape of the slice to take (Note the return value may have more dimensions than len(shape)) + *origin* the location in the original dataset that will become the origin of the sliced data. + *vectors* list of unit vectors which point in the direction of the slice axes. Each vector must have the same + length as *axes*. If the vectors are not unit length, the result will be scaled relative to the + original data. If the vectors are not orthogonal, the result will be sheared relative to the + original data. + *axes* The axes in the original dataset which correspond to the slice *vectors* + *order* The order of spline interpolation. Default is 1 (linear). See scipy.ndimage.map_coordinates + for more information. + *returnCoords* If True, return a tuple (result, coords) where coords is the array of coordinates used to select + values from the original dataset. + *All extra keyword arguments are passed to scipy.ndimage.map_coordinates.* + -------------------------------------------------------------------------------------------------------------------- + ============== ==================================================================================================== + + Note the following must be true: + + | len(shape) == len(vectors) + | len(origin) == len(axes) == len(vectors[i]) + + Example: start with a 4D fMRI data set, take a diagonal-planar slice out of the last 3 axes + + * data = array with dims (time, x, y, z) = (100, 40, 40, 40) + * The plane to pull out is perpendicular to the vector (x,y,z) = (1,1,1) + * The origin of the slice will be at (x,y,z) = (40, 0, 0) + * We will slice a 20x20 plane from each timepoint, giving a final shape (100, 20, 20) + + The call for this example would look like:: + + affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3)) + + """ + try: + import scipy.ndimage + have_scipy = True + except ImportError: + have_scipy = False + have_scipy = False + + # sanity check + if len(shape) != len(vectors): + raise Exception("shape and vectors must have same length.") + if len(origin) != len(axes): + raise Exception("origin and axes must have same length.") + for v in vectors: + if len(v) != len(axes): + raise Exception("each vector must be same length as axes.") + + shape = list(map(np.ceil, shape)) + + ## transpose data so slice axes come first + trAx = list(range(data.ndim)) + for x in axes: + trAx.remove(x) + tr1 = tuple(axes) + tuple(trAx) + data = data.transpose(tr1) + #print "tr1:", tr1 + ## dims are now [(slice axes), (other axes)] + + ## make sure vectors are arrays + if not isinstance(vectors, np.ndarray): + vectors = np.array(vectors) + if not isinstance(origin, np.ndarray): + origin = np.array(origin) + origin.shape = (len(axes),) + (1,)*len(shape) + + ## Build array of sample locations. + grid = np.mgrid[tuple([slice(0,x) for x in shape])] ## mesh grid of indexes + #print shape, grid.shape + x = (grid[np.newaxis,...] * vectors.transpose()[(Ellipsis,) + (np.newaxis,)*len(shape)]).sum(axis=1) ## magic + x += origin + #print "X values:" + #print x + ## iterate manually over unused axes since map_coordinates won't do it for us + if have_scipy: + extraShape = data.shape[len(axes):] + output = np.empty(tuple(shape) + extraShape, dtype=data.dtype) + for inds in np.ndindex(*extraShape): + ind = (Ellipsis,) + inds + output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs) + else: + # map_coordinates expects the indexes as the first axis, whereas + # interpolateArray expects indexes at the last axis. + tr = tuple(range(1,x.ndim)) + (0,) + output = interpolateArray(data, x.transpose(tr)) + + + tr = list(range(output.ndim)) + trb = [] + for i in range(min(axes)): + ind = tr1.index(i) + (len(shape)-len(axes)) + tr.remove(ind) + trb.append(ind) + tr2 = tuple(trb+tr) + + ## Untranspose array before returning + output = output.transpose(tr2) + if returnCoords: + return (output, x) + else: + return output + +def interpolateArray(data, x, default=0.0): + """ + N-dimensional interpolation similar scipy.ndimage.map_coordinates. + + This function returns linearly-interpolated values sampled from a regular + grid of data. + + *data* is an array of any shape containing the values to be interpolated. + *x* is an array with (shape[-1] <= data.ndim) containing the locations + within *data* to interpolate. + + Returns array of shape (x.shape[:-1] + data.shape) + + For example, assume we have the following 2D image data:: + + >>> data = np.array([[1, 2, 4 ], + [10, 20, 40 ], + [100, 200, 400]]) + + To compute a single interpolated point from this data:: + + >>> x = np.array([(0.5, 0.5)]) + >>> interpolateArray(data, x) + array([ 8.25]) + + To compute a 1D list of interpolated locations:: + + >>> x = np.array([(0.5, 0.5), + (1.0, 1.0), + (1.0, 2.0), + (1.5, 0.0)]) + >>> interpolateArray(data, x) + array([ 8.25, 20. , 40. , 55. ]) + + To compute a 2D array of interpolated locations:: + + >>> x = np.array([[(0.5, 0.5), (1.0, 2.0)], + [(1.0, 1.0), (1.5, 0.0)]]) + >>> interpolateArray(data, x) + array([[ 8.25, 40. ], + [ 20. , 55. ]]) + + ..and so on. The *x* argument may have any shape as long as + ```x.shape[-1] <= data.ndim```. In the case that + ```x.shape[-1] < data.ndim```, then the remaining axes are simply + broadcasted as usual. For example, we can interpolate one location + from an entire row of the data:: + + >>> x = np.array([[0.5]]) + >>> interpolateArray(data, x) + array([[ 5.5, 11. , 22. ]]) + + This is useful for interpolating from arrays of colors, vertexes, etc. + """ + + prof = debug.Profiler() + + nd = data.ndim + md = x.shape[-1] + + # First we generate arrays of indexes that are needed to + # extract the data surrounding each point + fields = np.mgrid[(slice(0,2),) * md] + xmin = np.floor(x).astype(int) + xmax = xmin + 1 + indexes = np.concatenate([xmin[np.newaxis, ...], xmax[np.newaxis, ...]]) + fieldInds = [] + totalMask = np.ones(x.shape[:-1], dtype=bool) # keep track of out-of-bound indexes + for ax in range(md): + mask = (xmin[...,ax] >= 0) & (x[...,ax] <= data.shape[ax]-1) + # keep track of points that need to be set to default + totalMask &= mask + + # ..and keep track of indexes that are out of bounds + # (note that when x[...,ax] == data.shape[ax], then xmax[...,ax] will be out + # of bounds, but the interpolation will work anyway) + mask &= (xmax[...,ax] < data.shape[ax]) + axisIndex = indexes[...,ax][fields[ax]] + #axisMask = mask.astype(np.ubyte).reshape((1,)*(fields.ndim-1) + mask.shape) + axisIndex[axisIndex < 0] = 0 + axisIndex[axisIndex >= data.shape[ax]] = 0 + fieldInds.append(axisIndex) + prof() + + # Get data values surrounding each requested point + # fieldData[..., i] contains all 2**nd values needed to interpolate x[i] + fieldData = data[tuple(fieldInds)] + prof() + + ## Interpolate + s = np.empty((md,) + fieldData.shape, dtype=float) + dx = x - xmin + # reshape fields for arithmetic against dx + for ax in range(md): + f1 = fields[ax].reshape(fields[ax].shape + (1,)*(dx.ndim-1)) + sax = f1 * dx[...,ax] + (1-f1) * (1-dx[...,ax]) + sax = sax.reshape(sax.shape + (1,) * (s.ndim-1-sax.ndim)) + s[ax] = sax + s = np.product(s, axis=0) + result = fieldData * s + for i in range(md): + result = result.sum(axis=0) + + prof() + totalMask.shape = totalMask.shape + (1,) * (nd - md) + result[~totalMask] = default + prof() + return result + + +def subArray(data, offset, shape, stride): + """ + Unpack a sub-array from *data* using the specified offset, shape, and stride. + + Note that *stride* is specified in array elements, not bytes. + For example, we have a 2x3 array packed in a 1D array as follows:: + + data = [_, _, 00, 01, 02, _, 10, 11, 12, _] + + Then we can unpack the sub-array with this call:: + + subArray(data, offset=2, shape=(2, 3), stride=(4, 1)) + + ..which returns:: + + [[00, 01, 02], + [10, 11, 12]] + + This function operates only on the first axis of *data*. So changing + the input in the example above to have shape (10, 7) would cause the + output to have shape (2, 3, 7). + """ + #data = data.flatten() + data = data[offset:] + shape = tuple(shape) + stride = tuple(stride) + extraShape = data.shape[1:] + #print data.shape, offset, shape, stride + for i in range(len(shape)): + mask = (slice(None),) * i + (slice(None, shape[i] * stride[i]),) + newShape = shape[:i+1] + if i < len(shape)-1: + newShape += (stride[i],) + newShape += extraShape + #print i, mask, newShape + #print "start:\n", data.shape, data + data = data[mask] + #print "mask:\n", data.shape, data + data = data.reshape(newShape) + #print "reshape:\n", data.shape, data + + return data + + +def transformToArray(tr): + """ + Given a QTransform, return a 3x3 numpy array. + Given a QMatrix4x4, return a 4x4 numpy array. + + Example: map an array of x,y coordinates through a transform:: + + ## coordinates to map are (1,5), (2,6), (3,7), and (4,8) + coords = np.array([[1,2,3,4], [5,6,7,8], [1,1,1,1]]) # the extra '1' coordinate is needed for translation to work + + ## Make an example transform + tr = QtGui.QTransform() + tr.translate(3,4) + tr.scale(2, 0.1) + + ## convert to array + m = pg.transformToArray()[:2] # ignore the perspective portion of the transformation + + ## map coordinates through transform + mapped = np.dot(m, coords) + """ + #return np.array([[tr.m11(), tr.m12(), tr.m13()],[tr.m21(), tr.m22(), tr.m23()],[tr.m31(), tr.m32(), tr.m33()]]) + ## The order of elements given by the method names m11..m33 is misleading-- + ## It is most common for x,y translation to occupy the positions 1,3 and 2,3 in + ## a transformation matrix. However, with QTransform these values appear at m31 and m32. + ## So the correct interpretation is transposed: + if isinstance(tr, QtGui.QTransform): + return np.array([[tr.m11(), tr.m21(), tr.m31()], [tr.m12(), tr.m22(), tr.m32()], [tr.m13(), tr.m23(), tr.m33()]]) + elif isinstance(tr, QtGui.QMatrix4x4): + return np.array(tr.copyDataTo()).reshape(4,4) + else: + raise Exception("Transform argument must be either QTransform or QMatrix4x4.") + +def transformCoordinates(tr, coords, transpose=False): + """ + Map a set of 2D or 3D coordinates through a QTransform or QMatrix4x4. + The shape of coords must be (2,...) or (3,...) + The mapping will _ignore_ any perspective transformations. + + For coordinate arrays with ndim=2, this is basically equivalent to matrix multiplication. + Most arrays, however, prefer to put the coordinate axis at the end (eg. shape=(...,3)). To + allow this, use transpose=True. + + """ + + if transpose: + ## move last axis to beginning. This transposition will be reversed before returning the mapped coordinates. + coords = coords.transpose((coords.ndim-1,) + tuple(range(0,coords.ndim-1))) + + nd = coords.shape[0] + if isinstance(tr, np.ndarray): + m = tr + else: + m = transformToArray(tr) + m = m[:m.shape[0]-1] # remove perspective + + ## If coords are 3D and tr is 2D, assume no change for Z axis + if m.shape == (2,3) and nd == 3: + m2 = np.zeros((3,4)) + m2[:2, :2] = m[:2,:2] + m2[:2, 3] = m[:2,2] + m2[2,2] = 1 + m = m2 + + ## if coords are 2D and tr is 3D, ignore Z axis + if m.shape == (3,4) and nd == 2: + m2 = np.empty((2,3)) + m2[:,:2] = m[:2,:2] + m2[:,2] = m[:2,3] + m = m2 + + ## reshape tr and coords to prepare for multiplication + m = m.reshape(m.shape + (1,)*(coords.ndim-1)) + coords = coords[np.newaxis, ...] + + # separate scale/rotate and translation + translate = m[:,-1] + m = m[:, :-1] + + ## map coordinates and return + mapped = (m*coords).sum(axis=1) ## apply scale/rotate + mapped += translate + + if transpose: + ## move first axis to end. + mapped = mapped.transpose(tuple(range(1,mapped.ndim)) + (0,)) + return mapped + + + + +def solve3DTransform(points1, points2): + """ + Find a 3D transformation matrix that maps points1 onto points2. + Points must be specified as either lists of 4 Vectors or + (4, 3) arrays. + """ + import numpy.linalg + pts = [] + for inp in (points1, points2): + if isinstance(inp, np.ndarray): + A = np.empty((4,4), dtype=float) + A[:,:3] = inp[:,:3] + A[:,3] = 1.0 + else: + A = np.array([[inp[i].x(), inp[i].y(), inp[i].z(), 1] for i in range(4)]) + pts.append(A) + + ## solve 3 sets of linear equations to determine transformation matrix elements + matrix = np.zeros((4,4)) + for i in range(3): + ## solve Ax = B; x is one row of the desired transformation matrix + matrix[i] = numpy.linalg.solve(pts[0], pts[1][:,i]) + + return matrix + +def solveBilinearTransform(points1, points2): + """ + Find a bilinear transformation matrix (2x4) that maps points1 onto points2. + Points must be specified as a list of 4 Vector, Point, QPointF, etc. + + To use this matrix to map a point [x,y]:: + + mapped = np.dot(matrix, [x*y, x, y, 1]) + """ + import numpy.linalg + ## A is 4 rows (points) x 4 columns (xy, x, y, 1) + ## B is 4 rows (points) x 2 columns (x, y) + A = np.array([[points1[i].x()*points1[i].y(), points1[i].x(), points1[i].y(), 1] for i in range(4)]) + B = np.array([[points2[i].x(), points2[i].y()] for i in range(4)]) + + ## solve 2 sets of linear equations to determine transformation matrix elements + matrix = np.zeros((2,4)) + for i in range(2): + matrix[i] = numpy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix + + return matrix + +def rescaleData(data, scale, offset, dtype=None): + """Return data rescaled and optionally cast to a new dtype:: + + data => (data-offset) * scale + + Uses scipy.weave (if available) to improve performance. + """ + if dtype is None: + dtype = data.dtype + else: + dtype = np.dtype(dtype) + + try: + if not getConfigOption('useWeave'): + raise Exception('Weave is disabled; falling back to slower version.') + try: + import scipy.weave + except ImportError: + raise Exception('scipy.weave is not importable; falling back to slower version.') + + ## require native dtype when using weave + if not data.dtype.isnative: + data = data.astype(data.dtype.newbyteorder('=')) + if not dtype.isnative: + weaveDtype = dtype.newbyteorder('=') + else: + weaveDtype = dtype + + newData = np.empty((data.size,), dtype=weaveDtype) + flat = np.ascontiguousarray(data).reshape(data.size) + size = data.size + + code = """ + double sc = (double)scale; + double off = (double)offset; + for( int i=0; i0 and max->*scale*:: + + rescaled = (clip(data, min, max) - min) * (*scale* / (max - min)) + + It is also possible to use a 2D (N,2) array of values for levels. In this case, + it is assumed that each pair of min,max values in the levels array should be + applied to a different subset of the input data (for example, the input data may + already have RGB values and the levels are used to independently scale each + channel). The use of this feature requires that levels.shape[0] == data.shape[-1]. + scale The maximum value to which data will be rescaled before being passed through the + lookup table (or returned if there is no lookup table). By default this will + be set to the length of the lookup table, or 256 is no lookup table is provided. + For OpenGL color specifications (as in GLColor4f) use scale=1.0 + lut Optional lookup table (array with dtype=ubyte). + Values in data will be converted to color by indexing directly from lut. + The output data shape will be input.shape + lut.shape[1:]. + + Note: the output of makeARGB will have the same dtype as the lookup table, so + for conversion to QImage, the dtype must be ubyte. + + Lookup tables can be built using GradientWidget. + useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures). + The default is False, which returns in ARGB order for use with QImage + (Note that 'ARGB' is a term used by the Qt documentation; the _actual_ order + is BGRA). + ============== ================================================================================== + """ + profile = debug.Profiler() + + if lut is not None and not isinstance(lut, np.ndarray): + lut = np.array(lut) + if levels is not None and not isinstance(levels, np.ndarray): + levels = np.array(levels) + + if levels is not None: + if levels.ndim == 1: + if len(levels) != 2: + raise Exception('levels argument must have length 2') + elif levels.ndim == 2: + if lut is not None and lut.ndim > 1: + raise Exception('Cannot make ARGB data when bot levels and lut have ndim > 2') + if levels.shape != (data.shape[-1], 2): + raise Exception('levels must have shape (data.shape[-1], 2)') + else: + print(levels) + raise Exception("levels argument must be 1D or 2D.") + + profile() + + if scale is None: + if lut is not None: + scale = lut.shape[0] + else: + scale = 255. + + ## Apply levels if given + if levels is not None: + + if isinstance(levels, np.ndarray) and levels.ndim == 2: + ## we are going to rescale each channel independently + if levels.shape[0] != data.shape[-1]: + raise Exception("When rescaling multi-channel data, there must be the same number of levels as channels (data.shape[-1] == levels.shape[0])") + newData = np.empty(data.shape, dtype=int) + for i in range(data.shape[-1]): + minVal, maxVal = levels[i] + if minVal == maxVal: + maxVal += 1e-16 + newData[...,i] = rescaleData(data[...,i], scale/(maxVal-minVal), minVal, dtype=int) + data = newData + else: + minVal, maxVal = levels + if minVal == maxVal: + maxVal += 1e-16 + if maxVal == minVal: + data = rescaleData(data, 1, minVal, dtype=int) + else: + data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int) + + profile() + + ## apply LUT if given + if lut is not None: + data = applyLookupTable(data, lut) + else: + if data.dtype is not np.ubyte: + data = np.clip(data, 0, 255).astype(np.ubyte) + + profile() + + ## copy data into ARGB ordered array + imgData = np.empty(data.shape[:2]+(4,), dtype=np.ubyte) + + profile() + + if useRGBA: + order = [0,1,2,3] ## array comes out RGBA + else: + order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image. + + if data.ndim == 2: + # This is tempting: + # imgData[..., :3] = data[..., np.newaxis] + # ..but it turns out this is faster: + for i in range(3): + imgData[..., i] = data + elif data.shape[2] == 1: + for i in range(3): + imgData[..., i] = data[..., 0] + else: + for i in range(0, data.shape[2]): + imgData[..., i] = data[..., order[i]] + + profile() + + if data.ndim == 2 or data.shape[2] == 3: + alpha = False + imgData[..., 3] = 255 + else: + alpha = True + + profile() + return imgData, alpha + + +def makeQImage(imgData, alpha=None, copy=True, transpose=True): + """ + Turn an ARGB array into QImage. + By default, the data is copied; changes to the array will not + be reflected in the image. The image will be given a 'data' attribute + pointing to the array which shares its data to prevent python + freeing that memory while the image is in use. + + ============== =================================================================== + **Arguments:** + imgData Array of data to convert. Must have shape (width, height, 3 or 4) + and dtype=ubyte. The order of values in the 3rd axis must be + (b, g, r, a). + alpha If True, the QImage returned will have format ARGB32. If False, + the format will be RGB32. By default, _alpha_ is True if + array.shape[2] == 4. + copy If True, the data is copied before converting to QImage. + If False, the new QImage points directly to the data in the array. + Note that the array must be contiguous for this to work + (see numpy.ascontiguousarray). + transpose If True (the default), the array x/y axes are transposed before + creating the image. Note that Qt expects the axes to be in + (height, width) order whereas pyqtgraph usually prefers the + opposite. + ============== =================================================================== + """ + ## create QImage from buffer + profile = debug.Profiler() + + ## If we didn't explicitly specify alpha, check the array shape. + if alpha is None: + alpha = (imgData.shape[2] == 4) + + copied = False + if imgData.shape[2] == 3: ## need to make alpha channel (even if alpha==False; QImage requires 32 bpp) + if copy is True: + d2 = np.empty(imgData.shape[:2] + (4,), dtype=imgData.dtype) + d2[:,:,:3] = imgData + d2[:,:,3] = 255 + imgData = d2 + copied = True + else: + raise Exception('Array has only 3 channels; cannot make QImage without copying.') + + if alpha: + imgFormat = QtGui.QImage.Format_ARGB32 + else: + imgFormat = QtGui.QImage.Format_RGB32 + + if transpose: + imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite + + profile() + + if not imgData.flags['C_CONTIGUOUS']: + if copy is False: + extra = ' (try setting transpose=False)' if transpose else '' + raise Exception('Array is not contiguous; cannot make QImage without copying.'+extra) + imgData = np.ascontiguousarray(imgData) + copied = True + + if copy is True and copied is False: + imgData = imgData.copy() + + if USE_PYSIDE: + ch = ctypes.c_char.from_buffer(imgData, 0) + img = QtGui.QImage(ch, imgData.shape[1], imgData.shape[0], imgFormat) + else: + #addr = ctypes.addressof(ctypes.c_char.from_buffer(imgData, 0)) + ## PyQt API for QImage changed between 4.9.3 and 4.9.6 (I don't know exactly which version it was) + ## So we first attempt the 4.9.6 API, then fall back to 4.9.3 + #addr = ctypes.c_char.from_buffer(imgData, 0) + #try: + #img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat) + #except TypeError: + #addr = ctypes.addressof(addr) + #img = QtGui.QImage(addr, imgData.shape[1], imgData.shape[0], imgFormat) + try: + img = QtGui.QImage(imgData.ctypes.data, imgData.shape[1], imgData.shape[0], imgFormat) + except: + if copy: + # does not leak memory, is not mutable + img = QtGui.QImage(buffer(imgData), imgData.shape[1], imgData.shape[0], imgFormat) + else: + # mutable, but leaks memory + img = QtGui.QImage(memoryview(imgData), imgData.shape[1], imgData.shape[0], imgFormat) + + img.data = imgData + return img + #try: + #buf = imgData.data + #except AttributeError: ## happens when image data is non-contiguous + #buf = imgData.data + + #profiler() + #qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat) + #profiler() + #qimage.data = imgData + #return qimage + +def imageToArray(img, copy=False, transpose=True): + """ + Convert a QImage into numpy array. The image must have format RGB32, ARGB32, or ARGB32_Premultiplied. + By default, the image is not copied; changes made to the array will appear in the QImage as well (beware: if + the QImage is collected before the array, there may be trouble). + The array will have shape (width, height, (b,g,r,a)). + """ + fmt = img.format() + ptr = img.bits() + if USE_PYSIDE: + arr = np.frombuffer(ptr, dtype=np.ubyte) + else: + ptr.setsize(img.byteCount()) + arr = np.asarray(ptr) + if img.byteCount() != arr.size * arr.itemsize: + # Required for Python 2.6, PyQt 4.10 + # If this works on all platforms, then there is no need to use np.asarray.. + arr = np.frombuffer(ptr, np.ubyte, img.byteCount()) + + if fmt == img.Format_RGB32: + arr = arr.reshape(img.height(), img.width(), 3) + elif fmt == img.Format_ARGB32 or fmt == img.Format_ARGB32_Premultiplied: + arr = arr.reshape(img.height(), img.width(), 4) + + if copy: + arr = arr.copy() + + if transpose: + return arr.transpose((1,0,2)) + else: + return arr + +def colorToAlpha(data, color): + """ + Given an RGBA image in *data*, convert *color* to be transparent. + *data* must be an array (w, h, 3 or 4) of ubyte values and *color* must be + an array (3) of ubyte values. + This is particularly useful for use with images that have a black or white background. + + Algorithm is taken from Gimp's color-to-alpha function in plug-ins/common/colortoalpha.c + Credit: + /* + * Color To Alpha plug-in v1.0 by Seth Burgess, sjburges@gimp.org 1999/05/14 + * with algorithm by clahey + */ + + """ + data = data.astype(float) + if data.shape[-1] == 3: ## add alpha channel if needed + d2 = np.empty(data.shape[:2]+(4,), dtype=data.dtype) + d2[...,:3] = data + d2[...,3] = 255 + data = d2 + + color = color.astype(float) + alpha = np.zeros(data.shape[:2]+(3,), dtype=float) + output = data.copy() + + for i in [0,1,2]: + d = data[...,i] + c = color[i] + mask = d > c + alpha[...,i][mask] = (d[mask] - c) / (255. - c) + imask = d < c + alpha[...,i][imask] = (c - d[imask]) / c + + output[...,3] = alpha.max(axis=2) * 255. + + mask = output[...,3] >= 1.0 ## avoid zero division while processing alpha channel + correction = 255. / output[...,3][mask] ## increase value to compensate for decreased alpha + for i in [0,1,2]: + output[...,i][mask] = ((output[...,i][mask]-color[i]) * correction) + color[i] + output[...,3][mask] *= data[...,3][mask] / 255. ## combine computed and previous alpha values + + #raise Exception() + return np.clip(output, 0, 255).astype(np.ubyte) + +def gaussianFilter(data, sigma): + """ + Drop-in replacement for scipy.ndimage.gaussian_filter. + + (note: results are only approximately equal to the output of + gaussian_filter) + """ + if np.isscalar(sigma): + sigma = (sigma,) * data.ndim + + baseline = data.mean() + filtered = data - baseline + for ax in range(data.ndim): + s = sigma[ax] + if s == 0: + continue + + # generate 1D gaussian kernel + ksize = int(s * 6) + x = np.arange(-ksize, ksize) + kernel = np.exp(-x**2 / (2*s**2)) + kshape = [1,] * data.ndim + kshape[ax] = len(kernel) + kernel = kernel.reshape(kshape) + + # convolve as product of FFTs + shape = data.shape[ax] + ksize + scale = 1.0 / (abs(s) * (2*np.pi)**0.5) + filtered = scale * np.fft.irfft(np.fft.rfft(filtered, shape, axis=ax) * + np.fft.rfft(kernel, shape, axis=ax), + axis=ax) + + # clip off extra data + sl = [slice(None)] * data.ndim + sl[ax] = slice(filtered.shape[ax]-data.shape[ax],None,None) + filtered = filtered[sl] + return filtered + baseline + + +def downsample(data, n, axis=0, xvals='subsample'): + """Downsample by averaging points together across axis. + If multiple axes are specified, runs once per axis. + If a metaArray is given, then the axis values can be either subsampled + or downsampled to match. + """ + ma = None + if (hasattr(data, 'implements') and data.implements('MetaArray')): + ma = data + data = data.view(np.ndarray) + + + if hasattr(axis, '__len__'): + if not hasattr(n, '__len__'): + n = [n]*len(axis) + for i in range(len(axis)): + data = downsample(data, n[i], axis[i]) + return data + + if n <= 1: + return data + nPts = int(data.shape[axis] / n) + s = list(data.shape) + s[axis] = nPts + s.insert(axis+1, n) + sl = [slice(None)] * data.ndim + sl[axis] = slice(0, nPts*n) + d1 = data[tuple(sl)] + #print d1.shape, s + d1.shape = tuple(s) + d2 = d1.mean(axis+1) + + if ma is None: + return d2 + else: + info = ma.infoCopy() + if 'values' in info[axis]: + if xvals == 'subsample': + info[axis]['values'] = info[axis]['values'][::n][:nPts] + elif xvals == 'downsample': + info[axis]['values'] = downsample(info[axis]['values'], n) + return MetaArray(d2, info=info) + + +def arrayToQPath(x, y, connect='all'): + """Convert an array of x,y coordinats to QPainterPath as efficiently as possible. + The *connect* argument may be 'all', indicating that each point should be + connected to the next; 'pairs', indicating that each pair of points + should be connected, or an array of int32 values (0 or 1) indicating + connections. + """ + + ## Create all vertices in path. The method used below creates a binary format so that all + ## vertices can be read in at once. This binary format may change in future versions of Qt, + ## so the original (slower) method is left here for emergencies: + #path.moveTo(x[0], y[0]) + #if connect == 'all': + #for i in range(1, y.shape[0]): + #path.lineTo(x[i], y[i]) + #elif connect == 'pairs': + #for i in range(1, y.shape[0]): + #if i%2 == 0: + #path.lineTo(x[i], y[i]) + #else: + #path.moveTo(x[i], y[i]) + #elif isinstance(connect, np.ndarray): + #for i in range(1, y.shape[0]): + #if connect[i] == 1: + #path.lineTo(x[i], y[i]) + #else: + #path.moveTo(x[i], y[i]) + #else: + #raise Exception('connect argument must be "all", "pairs", or array') + + ## Speed this up using >> operator + ## Format is: + ## numVerts(i4) 0(i4) + ## x(f8) y(f8) 0(i4) <-- 0 means this vertex does not connect + ## x(f8) y(f8) 1(i4) <-- 1 means this vertex connects to the previous vertex + ## ... + ## 0(i4) + ## + ## All values are big endian--pack using struct.pack('>d') or struct.pack('>i') + + path = QtGui.QPainterPath() + + #profiler = debug.Profiler() + n = x.shape[0] + # create empty array, pad with extra space on either end + arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) + # write first two integers + #profiler('allocate empty') + byteview = arr.view(dtype=np.ubyte) + byteview[:12] = 0 + byteview.data[12:20] = struct.pack('>ii', n, 0) + #profiler('pack header') + # Fill array with vertex values + arr[1:-1]['x'] = x + arr[1:-1]['y'] = y + + # decide which points are connected by lines + if connect == 'pairs': + connect = np.empty((n/2,2), dtype=np.int32) + if connect.size != n: + raise Exception("x,y array lengths must be multiple of 2 to use connect='pairs'") + connect[:,0] = 1 + connect[:,1] = 0 + connect = connect.flatten() + if connect == 'finite': + connect = np.isfinite(x) & np.isfinite(y) + arr[1:-1]['c'] = connect + if connect == 'all': + arr[1:-1]['c'] = 1 + elif isinstance(connect, np.ndarray): + arr[1:-1]['c'] = connect + else: + raise Exception('connect argument must be "all", "pairs", or array') + + #profiler('fill array') + # write last 0 + lastInd = 20*(n+1) + byteview.data[lastInd:lastInd+4] = struct.pack('>i', 0) + #profiler('footer') + # create datastream object and stream into path + + ## Avoiding this method because QByteArray(str) leaks memory in PySide + #buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here + + path.strn = byteview.data[12:lastInd+4] # make sure data doesn't run away + try: + buf = QtCore.QByteArray.fromRawData(path.strn) + except TypeError: + buf = QtCore.QByteArray(bytes(path.strn)) + #profiler('create buffer') + ds = QtCore.QDataStream(buf) + + ds >> path + #profiler('load') + + return path + +#def isosurface(data, level): + #""" + #Generate isosurface from volumetric data using marching tetrahedra algorithm. + #See Paul Bourke, "Polygonising a Scalar Field Using Tetrahedrons" (http://local.wasp.uwa.edu.au/~pbourke/geometry/polygonise/) + + #*data* 3D numpy array of scalar values + #*level* The level at which to generate an isosurface + #""" + + #facets = [] + + ### mark everything below the isosurface level + #mask = data < level + + #### make eight sub-fields + #fields = np.empty((2,2,2), dtype=object) + #slices = [slice(0,-1), slice(1,None)] + #for i in [0,1]: + #for j in [0,1]: + #for k in [0,1]: + #fields[i,j,k] = mask[slices[i], slices[j], slices[k]] + + + + ### split each cell into 6 tetrahedra + ### these all have the same 'orienation'; points 1,2,3 circle + ### clockwise around point 0 + #tetrahedra = [ + #[(0,1,0), (1,1,1), (0,1,1), (1,0,1)], + #[(0,1,0), (0,1,1), (0,0,1), (1,0,1)], + #[(0,1,0), (0,0,1), (0,0,0), (1,0,1)], + #[(0,1,0), (0,0,0), (1,0,0), (1,0,1)], + #[(0,1,0), (1,0,0), (1,1,0), (1,0,1)], + #[(0,1,0), (1,1,0), (1,1,1), (1,0,1)] + #] + + ### each tetrahedron will be assigned an index + ### which determines how to generate its facets. + ### this structure is: + ### facets[index][facet1, facet2, ...] + ### where each facet is triangular and its points are each + ### interpolated between two points on the tetrahedron + ### facet = [(p1a, p1b), (p2a, p2b), (p3a, p3b)] + ### facet points always circle clockwise if you are looking + ### at them from below the isosurface. + #indexFacets = [ + #[], ## all above + #[[(0,1), (0,2), (0,3)]], # 0 below + #[[(1,0), (1,3), (1,2)]], # 1 below + #[[(0,2), (1,3), (1,2)], [(0,2), (0,3), (1,3)]], # 0,1 below + #[[(2,0), (2,1), (2,3)]], # 2 below + #[[(0,3), (1,2), (2,3)], [(0,3), (0,1), (1,2)]], # 0,2 below + #[[(1,0), (2,3), (2,0)], [(1,0), (1,3), (2,3)]], # 1,2 below + #[[(3,0), (3,1), (3,2)]], # 3 above + #[[(3,0), (3,2), (3,1)]], # 3 below + #[[(1,0), (2,0), (2,3)], [(1,0), (2,3), (1,3)]], # 0,3 below + #[[(0,3), (2,3), (1,2)], [(0,3), (1,2), (0,1)]], # 1,3 below + #[[(2,0), (2,3), (2,1)]], # 0,1,3 below + #[[(0,2), (1,2), (1,3)], [(0,2), (1,3), (0,3)]], # 2,3 below + #[[(1,0), (1,2), (1,3)]], # 0,2,3 below + #[[(0,1), (0,3), (0,2)]], # 1,2,3 below + #[] ## all below + #] + + #for tet in tetrahedra: + + ### get the 4 fields for this tetrahedron + #tetFields = [fields[c] for c in tet] + + ### generate an index for each grid cell + #index = tetFields[0] + tetFields[1]*2 + tetFields[2]*4 + tetFields[3]*8 + + ### add facets + #for i in xrange(index.shape[0]): # data x-axis + #for j in xrange(index.shape[1]): # data y-axis + #for k in xrange(index.shape[2]): # data z-axis + #for f in indexFacets[index[i,j,k]]: # faces to generate for this tet + #pts = [] + #for l in [0,1,2]: # points in this face + #p1 = tet[f[l][0]] # tet corner 1 + #p2 = tet[f[l][1]] # tet corner 2 + #pts.append([(p1[x]+p2[x])*0.5+[i,j,k][x]+0.5 for x in [0,1,2]]) ## interpolate between tet corners + #facets.append(pts) + + #return facets + + +def isocurve(data, level, connected=False, extendToEdge=False, path=False): + """ + Generate isocurve from 2D data using marching squares algorithm. + + ============== ========================================================= + **Arguments:** + data 2D numpy array of scalar values + level The level at which to generate an isosurface + connected If False, return a single long list of point pairs + If True, return multiple long lists of connected point + locations. (This is slower but better for drawing + continuous lines) + extendToEdge If True, extend the curves to reach the exact edges of + the data. + path if True, return a QPainterPath rather than a list of + vertex coordinates. This forces connected=True. + ============== ========================================================= + + This function is SLOW; plenty of room for optimization here. + """ + + if path is True: + connected = True + + if extendToEdge: + d2 = np.empty((data.shape[0]+2, data.shape[1]+2), dtype=data.dtype) + d2[1:-1, 1:-1] = data + d2[0, 1:-1] = data[0] + d2[-1, 1:-1] = data[-1] + d2[1:-1, 0] = data[:, 0] + d2[1:-1, -1] = data[:, -1] + d2[0,0] = d2[0,1] + d2[0,-1] = d2[1,-1] + d2[-1,0] = d2[-1,1] + d2[-1,-1] = d2[-1,-2] + data = d2 + + sideTable = [ + [], + [0,1], + [1,2], + [0,2], + [0,3], + [1,3], + [0,1,2,3], + [2,3], + [2,3], + [0,1,2,3], + [1,3], + [0,3], + [0,2], + [1,2], + [0,1], + [] + ] + + edgeKey=[ + [(0,1), (0,0)], + [(0,0), (1,0)], + [(1,0), (1,1)], + [(1,1), (0,1)] + ] + + + lines = [] + + ## mark everything below the isosurface level + mask = data < level + + ### make four sub-fields and compute indexes for grid cells + index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) + fields = np.empty((2,2), dtype=object) + slices = [slice(0,-1), slice(1,None)] + for i in [0,1]: + for j in [0,1]: + fields[i,j] = mask[slices[i], slices[j]] + #vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme + vertIndex = i+2*j + #print i,j,k," : ", fields[i,j,k], 2**vertIndex + index += fields[i,j] * 2**vertIndex + #print index + #print index + + ## add lines + for i in range(index.shape[0]): # data x-axis + for j in range(index.shape[1]): # data y-axis + sides = sideTable[index[i,j]] + for l in range(0, len(sides), 2): ## faces for this grid cell + edges = sides[l:l+2] + pts = [] + for m in [0,1]: # points in this face + p1 = edgeKey[edges[m]][0] # p1, p2 are points at either side of an edge + p2 = edgeKey[edges[m]][1] + v1 = data[i+p1[0], j+p1[1]] # v1 and v2 are the values at p1 and p2 + v2 = data[i+p2[0], j+p2[1]] + f = (level-v1) / (v2-v1) + fi = 1.0 - f + p = ( ## interpolate between corners + p1[0]*fi + p2[0]*f + i + 0.5, + p1[1]*fi + p2[1]*f + j + 0.5 + ) + if extendToEdge: + ## check bounds + p = ( + min(data.shape[0]-2, max(0, p[0]-1)), + min(data.shape[1]-2, max(0, p[1]-1)), + ) + if connected: + gridKey = i + (1 if edges[m]==2 else 0), j + (1 if edges[m]==3 else 0), edges[m]%2 + pts.append((p, gridKey)) ## give the actual position and a key identifying the grid location (for connecting segments) + else: + pts.append(p) + + lines.append(pts) + + if not connected: + return lines + + ## turn disjoint list of segments into continuous lines + + #lines = [[2,5], [5,4], [3,4], [1,3], [6,7], [7,8], [8,6], [11,12], [12,15], [11,13], [13,14]] + #lines = [[(float(a), a), (float(b), b)] for a,b in lines] + points = {} ## maps each point to its connections + for a,b in lines: + if a[1] not in points: + points[a[1]] = [] + points[a[1]].append([a,b]) + if b[1] not in points: + points[b[1]] = [] + points[b[1]].append([b,a]) + + ## rearrange into chains + for k in list(points.keys()): + try: + chains = points[k] + except KeyError: ## already used this point elsewhere + continue + #print "===========", k + for chain in chains: + #print " chain:", chain + x = None + while True: + if x == chain[-1][1]: + break ## nothing left to do on this chain + + x = chain[-1][1] + if x == k: + break ## chain has looped; we're done and can ignore the opposite chain + y = chain[-2][1] + connects = points[x] + for conn in connects[:]: + if conn[1][1] != y: + #print " ext:", conn + chain.extend(conn[1:]) + #print " del:", x + del points[x] + if chain[0][1] == chain[-1][1]: # looped chain; no need to continue the other direction + chains.pop() + break + + + ## extract point locations + lines = [] + for chain in points.values(): + if len(chain) == 2: + chain = chain[1][1:][::-1] + chain[0] # join together ends of chain + else: + chain = chain[0] + lines.append([p[0] for p in chain]) + + if not path: + return lines ## a list of pairs of points + + path = QtGui.QPainterPath() + for line in lines: + path.moveTo(*line[0]) + for p in line[1:]: + path.lineTo(*p) + + return path + + +def traceImage(image, values, smooth=0.5): + """ + Convert an image to a set of QPainterPath curves. + One curve will be generated for each item in *values*; each curve outlines the area + of the image that is closer to its value than to any others. + + If image is RGB or RGBA, then the shape of values should be (nvals, 3/4) + The parameter *smooth* is expressed in pixels. + """ + try: + import scipy.ndimage as ndi + except ImportError: + raise Exception("traceImage() requires the package scipy.ndimage, but it is not importable.") + + if values.ndim == 2: + values = values.T + values = values[np.newaxis, np.newaxis, ...].astype(float) + image = image[..., np.newaxis].astype(float) + diff = np.abs(image-values) + if values.ndim == 4: + diff = diff.sum(axis=2) + + labels = np.argmin(diff, axis=2) + + paths = [] + for i in range(diff.shape[-1]): + d = (labels==i).astype(float) + d = gaussianFilter(d, (smooth, smooth)) + lines = isocurve(d, 0.5, connected=True, extendToEdge=True) + path = QtGui.QPainterPath() + for line in lines: + path.moveTo(*line[0]) + for p in line[1:]: + path.lineTo(*p) + + paths.append(path) + return paths + + + +IsosurfaceDataCache = None +def isosurface(data, level): + """ + Generate isosurface from volumetric data using marching cubes algorithm. + See Paul Bourke, "Polygonising a Scalar Field" + (http://paulbourke.net/geometry/polygonise/) + + *data* 3D numpy array of scalar values + *level* The level at which to generate an isosurface + + Returns an array of vertex coordinates (Nv, 3) and an array of + per-face vertex indexes (Nf, 3) + """ + ## For improvement, see: + ## + ## Efficient implementation of Marching Cubes' cases with topological guarantees. + ## Thomas Lewiner, Helio Lopes, Antonio Wilson Vieira and Geovan Tavares. + ## Journal of Graphics Tools 8(2): pp. 1-15 (december 2003) + + ## Precompute lookup tables on the first run + global IsosurfaceDataCache + if IsosurfaceDataCache is None: + ## map from grid cell index to edge index. + ## grid cell index tells us which corners are below the isosurface, + ## edge index tells us which edges are cut by the isosurface. + ## (Data stolen from Bourk; see above.) + edgeTable = np.array([ + 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, + 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, + 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c, + 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac, + 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c, + 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc, + 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c, + 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc , + 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, + 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, + 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, + 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, + 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, + 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, + 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, + 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, + 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 + ], dtype=np.uint16) + + ## Table of triangles to use for filling each grid cell. + ## Each set of three integers tells us which three edges to + ## draw a triangle between. + ## (Data stolen from Bourk; see above.) + triTable = [ + [], + [0, 8, 3], + [0, 1, 9], + [1, 8, 3, 9, 8, 1], + [1, 2, 10], + [0, 8, 3, 1, 2, 10], + [9, 2, 10, 0, 2, 9], + [2, 8, 3, 2, 10, 8, 10, 9, 8], + [3, 11, 2], + [0, 11, 2, 8, 11, 0], + [1, 9, 0, 2, 3, 11], + [1, 11, 2, 1, 9, 11, 9, 8, 11], + [3, 10, 1, 11, 10, 3], + [0, 10, 1, 0, 8, 10, 8, 11, 10], + [3, 9, 0, 3, 11, 9, 11, 10, 9], + [9, 8, 10, 10, 8, 11], + [4, 7, 8], + [4, 3, 0, 7, 3, 4], + [0, 1, 9, 8, 4, 7], + [4, 1, 9, 4, 7, 1, 7, 3, 1], + [1, 2, 10, 8, 4, 7], + [3, 4, 7, 3, 0, 4, 1, 2, 10], + [9, 2, 10, 9, 0, 2, 8, 4, 7], + [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4], + [8, 4, 7, 3, 11, 2], + [11, 4, 7, 11, 2, 4, 2, 0, 4], + [9, 0, 1, 8, 4, 7, 2, 3, 11], + [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1], + [3, 10, 1, 3, 11, 10, 7, 8, 4], + [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4], + [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3], + [4, 7, 11, 4, 11, 9, 9, 11, 10], + [9, 5, 4], + [9, 5, 4, 0, 8, 3], + [0, 5, 4, 1, 5, 0], + [8, 5, 4, 8, 3, 5, 3, 1, 5], + [1, 2, 10, 9, 5, 4], + [3, 0, 8, 1, 2, 10, 4, 9, 5], + [5, 2, 10, 5, 4, 2, 4, 0, 2], + [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8], + [9, 5, 4, 2, 3, 11], + [0, 11, 2, 0, 8, 11, 4, 9, 5], + [0, 5, 4, 0, 1, 5, 2, 3, 11], + [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5], + [10, 3, 11, 10, 1, 3, 9, 5, 4], + [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10], + [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3], + [5, 4, 8, 5, 8, 10, 10, 8, 11], + [9, 7, 8, 5, 7, 9], + [9, 3, 0, 9, 5, 3, 5, 7, 3], + [0, 7, 8, 0, 1, 7, 1, 5, 7], + [1, 5, 3, 3, 5, 7], + [9, 7, 8, 9, 5, 7, 10, 1, 2], + [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3], + [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2], + [2, 10, 5, 2, 5, 3, 3, 5, 7], + [7, 9, 5, 7, 8, 9, 3, 11, 2], + [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11], + [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7], + [11, 2, 1, 11, 1, 7, 7, 1, 5], + [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11], + [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0], + [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0], + [11, 10, 5, 7, 11, 5], + [10, 6, 5], + [0, 8, 3, 5, 10, 6], + [9, 0, 1, 5, 10, 6], + [1, 8, 3, 1, 9, 8, 5, 10, 6], + [1, 6, 5, 2, 6, 1], + [1, 6, 5, 1, 2, 6, 3, 0, 8], + [9, 6, 5, 9, 0, 6, 0, 2, 6], + [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8], + [2, 3, 11, 10, 6, 5], + [11, 0, 8, 11, 2, 0, 10, 6, 5], + [0, 1, 9, 2, 3, 11, 5, 10, 6], + [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11], + [6, 3, 11, 6, 5, 3, 5, 1, 3], + [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6], + [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9], + [6, 5, 9, 6, 9, 11, 11, 9, 8], + [5, 10, 6, 4, 7, 8], + [4, 3, 0, 4, 7, 3, 6, 5, 10], + [1, 9, 0, 5, 10, 6, 8, 4, 7], + [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4], + [6, 1, 2, 6, 5, 1, 4, 7, 8], + [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7], + [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6], + [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9], + [3, 11, 2, 7, 8, 4, 10, 6, 5], + [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11], + [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6], + [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6], + [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6], + [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11], + [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7], + [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9], + [10, 4, 9, 6, 4, 10], + [4, 10, 6, 4, 9, 10, 0, 8, 3], + [10, 0, 1, 10, 6, 0, 6, 4, 0], + [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10], + [1, 4, 9, 1, 2, 4, 2, 6, 4], + [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4], + [0, 2, 4, 4, 2, 6], + [8, 3, 2, 8, 2, 4, 4, 2, 6], + [10, 4, 9, 10, 6, 4, 11, 2, 3], + [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6], + [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10], + [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1], + [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3], + [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1], + [3, 11, 6, 3, 6, 0, 0, 6, 4], + [6, 4, 8, 11, 6, 8], + [7, 10, 6, 7, 8, 10, 8, 9, 10], + [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10], + [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0], + [10, 6, 7, 10, 7, 1, 1, 7, 3], + [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7], + [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9], + [7, 8, 0, 7, 0, 6, 6, 0, 2], + [7, 3, 2, 6, 7, 2], + [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7], + [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7], + [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11], + [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1], + [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6], + [0, 9, 1, 11, 6, 7], + [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0], + [7, 11, 6], + [7, 6, 11], + [3, 0, 8, 11, 7, 6], + [0, 1, 9, 11, 7, 6], + [8, 1, 9, 8, 3, 1, 11, 7, 6], + [10, 1, 2, 6, 11, 7], + [1, 2, 10, 3, 0, 8, 6, 11, 7], + [2, 9, 0, 2, 10, 9, 6, 11, 7], + [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8], + [7, 2, 3, 6, 2, 7], + [7, 0, 8, 7, 6, 0, 6, 2, 0], + [2, 7, 6, 2, 3, 7, 0, 1, 9], + [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6], + [10, 7, 6, 10, 1, 7, 1, 3, 7], + [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8], + [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7], + [7, 6, 10, 7, 10, 8, 8, 10, 9], + [6, 8, 4, 11, 8, 6], + [3, 6, 11, 3, 0, 6, 0, 4, 6], + [8, 6, 11, 8, 4, 6, 9, 0, 1], + [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6], + [6, 8, 4, 6, 11, 8, 2, 10, 1], + [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6], + [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9], + [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3], + [8, 2, 3, 8, 4, 2, 4, 6, 2], + [0, 4, 2, 4, 6, 2], + [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8], + [1, 9, 4, 1, 4, 2, 2, 4, 6], + [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1], + [10, 1, 0, 10, 0, 6, 6, 0, 4], + [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3], + [10, 9, 4, 6, 10, 4], + [4, 9, 5, 7, 6, 11], + [0, 8, 3, 4, 9, 5, 11, 7, 6], + [5, 0, 1, 5, 4, 0, 7, 6, 11], + [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5], + [9, 5, 4, 10, 1, 2, 7, 6, 11], + [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5], + [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2], + [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6], + [7, 2, 3, 7, 6, 2, 5, 4, 9], + [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7], + [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0], + [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8], + [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7], + [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4], + [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10], + [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10], + [6, 9, 5, 6, 11, 9, 11, 8, 9], + [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5], + [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11], + [6, 11, 3, 6, 3, 5, 5, 3, 1], + [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6], + [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10], + [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5], + [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3], + [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2], + [9, 5, 6, 9, 6, 0, 0, 6, 2], + [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8], + [1, 5, 6, 2, 1, 6], + [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6], + [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0], + [0, 3, 8, 5, 6, 10], + [10, 5, 6], + [11, 5, 10, 7, 5, 11], + [11, 5, 10, 11, 7, 5, 8, 3, 0], + [5, 11, 7, 5, 10, 11, 1, 9, 0], + [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1], + [11, 1, 2, 11, 7, 1, 7, 5, 1], + [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11], + [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7], + [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2], + [2, 5, 10, 2, 3, 5, 3, 7, 5], + [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5], + [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2], + [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2], + [1, 3, 5, 3, 7, 5], + [0, 8, 7, 0, 7, 1, 1, 7, 5], + [9, 0, 3, 9, 3, 5, 5, 3, 7], + [9, 8, 7, 5, 9, 7], + [5, 8, 4, 5, 10, 8, 10, 11, 8], + [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0], + [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5], + [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4], + [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8], + [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11], + [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5], + [9, 4, 5, 2, 11, 3], + [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4], + [5, 10, 2, 5, 2, 4, 4, 2, 0], + [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9], + [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2], + [8, 4, 5, 8, 5, 3, 3, 5, 1], + [0, 4, 5, 1, 0, 5], + [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5], + [9, 4, 5], + [4, 11, 7, 4, 9, 11, 9, 10, 11], + [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11], + [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11], + [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4], + [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2], + [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3], + [11, 7, 4, 11, 4, 2, 2, 4, 0], + [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4], + [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9], + [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7], + [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10], + [1, 10, 2, 8, 7, 4], + [4, 9, 1, 4, 1, 7, 7, 1, 3], + [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1], + [4, 0, 3, 7, 4, 3], + [4, 8, 7], + [9, 10, 8, 10, 11, 8], + [3, 0, 9, 3, 9, 11, 11, 9, 10], + [0, 1, 10, 0, 10, 8, 8, 10, 11], + [3, 1, 10, 11, 3, 10], + [1, 2, 11, 1, 11, 9, 9, 11, 8], + [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9], + [0, 2, 11, 8, 0, 11], + [3, 2, 11], + [2, 3, 8, 2, 8, 10, 10, 8, 9], + [9, 10, 2, 0, 9, 2], + [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8], + [1, 10, 2], + [1, 3, 8, 9, 1, 8], + [0, 9, 1], + [0, 3, 8], + [] + ] + edgeShifts = np.array([ ## maps edge ID (0-11) to (x,y,z) cell offset and edge ID (0-2) + [0, 0, 0, 0], + [1, 0, 0, 1], + [0, 1, 0, 0], + [0, 0, 0, 1], + [0, 0, 1, 0], + [1, 0, 1, 1], + [0, 1, 1, 0], + [0, 0, 1, 1], + [0, 0, 0, 2], + [1, 0, 0, 2], + [1, 1, 0, 2], + [0, 1, 0, 2], + #[9, 9, 9, 9] ## fake + ], dtype=np.uint16) # don't use ubyte here! This value gets added to cell index later; will need the extra precision. + nTableFaces = np.array([len(f)/3 for f in triTable], dtype=np.ubyte) + faceShiftTables = [None] + for i in range(1,6): + ## compute lookup table of index: vertexes mapping + faceTableI = np.zeros((len(triTable), i*3), dtype=np.ubyte) + faceTableInds = np.argwhere(nTableFaces == i) + faceTableI[faceTableInds[:,0]] = np.array([triTable[j] for j in faceTableInds]) + faceTableI = faceTableI.reshape((len(triTable), i, 3)) + faceShiftTables.append(edgeShifts[faceTableI]) + + ## Let's try something different: + #faceTable = np.empty((256, 5, 3, 4), dtype=np.ubyte) # (grid cell index, faces, vertexes, edge lookup) + #for i,f in enumerate(triTable): + #f = np.array(f + [12] * (15-len(f))).reshape(5,3) + #faceTable[i] = edgeShifts[f] + + + IsosurfaceDataCache = (faceShiftTables, edgeShifts, edgeTable, nTableFaces) + else: + faceShiftTables, edgeShifts, edgeTable, nTableFaces = IsosurfaceDataCache + + + + ## mark everything below the isosurface level + mask = data < level + + ### make eight sub-fields and compute indexes for grid cells + index = np.zeros([x-1 for x in data.shape], dtype=np.ubyte) + fields = np.empty((2,2,2), dtype=object) + slices = [slice(0,-1), slice(1,None)] + for i in [0,1]: + for j in [0,1]: + for k in [0,1]: + fields[i,j,k] = mask[slices[i], slices[j], slices[k]] + vertIndex = i - 2*j*i + 3*j + 4*k ## this is just to match Bourk's vertex numbering scheme + index += fields[i,j,k] * 2**vertIndex + + ### Generate table of edges that have been cut + cutEdges = np.zeros([x+1 for x in index.shape]+[3], dtype=np.uint32) + edges = edgeTable[index] + for i, shift in enumerate(edgeShifts[:12]): + slices = [slice(shift[j],cutEdges.shape[j]+(shift[j]-1)) for j in range(3)] + cutEdges[slices[0], slices[1], slices[2], shift[3]] += edges & 2**i + + ## for each cut edge, interpolate to see where exactly the edge is cut and generate vertex positions + m = cutEdges > 0 + vertexInds = np.argwhere(m) ## argwhere is slow! + vertexes = vertexInds[:,:3].astype(np.float32) + dataFlat = data.reshape(data.shape[0]*data.shape[1]*data.shape[2]) + + ## re-use the cutEdges array as a lookup table for vertex IDs + cutEdges[vertexInds[:,0], vertexInds[:,1], vertexInds[:,2], vertexInds[:,3]] = np.arange(vertexInds.shape[0]) + + for i in [0,1,2]: + vim = vertexInds[:,3] == i + vi = vertexInds[vim, :3] + viFlat = (vi * (np.array(data.strides[:3]) // data.itemsize)[np.newaxis,:]).sum(axis=1) + v1 = dataFlat[viFlat] + v2 = dataFlat[viFlat + data.strides[i]//data.itemsize] + vertexes[vim,i] += (level-v1) / (v2-v1) + + ### compute the set of vertex indexes for each face. + + ## This works, but runs a bit slower. + #cells = np.argwhere((index != 0) & (index != 255)) ## all cells with at least one face + #cellInds = index[cells[:,0], cells[:,1], cells[:,2]] + #verts = faceTable[cellInds] + #mask = verts[...,0,0] != 9 + #verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges + #verts = verts[mask] + #faces = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want. + + + ## To allow this to be vectorized efficiently, we count the number of faces in each + ## grid cell and handle each group of cells with the same number together. + ## determine how many faces to assign to each grid cell + nFaces = nTableFaces[index] + totFaces = nFaces.sum() + faces = np.empty((totFaces, 3), dtype=np.uint32) + ptr = 0 + #import debug + #p = debug.Profiler() + + ## this helps speed up an indexing operation later on + cs = np.array(cutEdges.strides)//cutEdges.itemsize + cutEdges = cutEdges.flatten() + + ## this, strangely, does not seem to help. + #ins = np.array(index.strides)/index.itemsize + #index = index.flatten() + + for i in range(1,6): + ### expensive: + #profiler() + cells = np.argwhere(nFaces == i) ## all cells which require i faces (argwhere is expensive) + #profiler() + if cells.shape[0] == 0: + continue + cellInds = index[cells[:,0], cells[:,1], cells[:,2]] ## index values of cells to process for this round + #profiler() + + ### expensive: + verts = faceShiftTables[i][cellInds] + #profiler() + verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges + verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:]) + #profiler() + + ### expensive: + verts = (verts * cs[np.newaxis, np.newaxis, :]).sum(axis=2) + vertInds = cutEdges[verts] + #profiler() + nv = vertInds.shape[0] + #profiler() + faces[ptr:ptr+nv] = vertInds #.reshape((nv, 3)) + #profiler() + ptr += nv + + return vertexes, faces + + + +def invertQTransform(tr): + """Return a QTransform that is the inverse of *tr*. + Rasises an exception if tr is not invertible. + + Note that this function is preferred over QTransform.inverted() due to + bugs in that method. (specifically, Qt has floating-point precision issues + when determining whether a matrix is invertible) + """ + try: + import numpy.linalg + arr = np.array([[tr.m11(), tr.m12(), tr.m13()], [tr.m21(), tr.m22(), tr.m23()], [tr.m31(), tr.m32(), tr.m33()]]) + inv = numpy.linalg.inv(arr) + return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1]) + except ImportError: + inv = tr.inverted() + if inv[1] is False: + raise Exception("Transform is not invertible.") + return inv[0] + + +def pseudoScatter(data, spacing=None, shuffle=True, bidir=False): + """ + Used for examining the distribution of values in a set. Produces scattering as in beeswarm or column scatter plots. + + Given a list of x-values, construct a set of y-values such that an x,y scatter-plot + will not have overlapping points (it will look similar to a histogram). + """ + inds = np.arange(len(data)) + if shuffle: + np.random.shuffle(inds) + + data = data[inds] + + if spacing is None: + spacing = 2.*np.std(data)/len(data)**0.5 + s2 = spacing**2 + + yvals = np.empty(len(data)) + if len(data) == 0: + return yvals + yvals[0] = 0 + for i in range(1,len(data)): + x = data[i] # current x value to be placed + x0 = data[:i] # all x values already placed + y0 = yvals[:i] # all y values already placed + y = 0 + + dx = (x0-x)**2 # x-distance to each previous point + xmask = dx < s2 # exclude anything too far away + + if xmask.sum() > 0: + if bidir: + dirs = [-1, 1] + else: + dirs = [1] + yopts = [] + for direction in dirs: + y = 0 + dx2 = dx[xmask] + dy = (s2 - dx2)**0.5 + limits = np.empty((2,len(dy))) # ranges of y-values to exclude + limits[0] = y0[xmask] - dy + limits[1] = y0[xmask] + dy + while True: + # ignore anything below this y-value + if direction > 0: + mask = limits[1] >= y + else: + mask = limits[0] <= y + + limits2 = limits[:,mask] + + # are we inside an excluded region? + mask = (limits2[0] < y) & (limits2[1] > y) + if mask.sum() == 0: + break + + if direction > 0: + y = limits2[:,mask].max() + else: + y = limits2[:,mask].min() + yopts.append(y) + if bidir: + y = yopts[0] if -yopts[0] < yopts[1] else yopts[1] + else: + y = yopts[0] + yvals[i] = y + + return yvals[np.argsort(inds)] ## un-shuffle values before returning + + + +def toposort(deps, nodes=None, seen=None, stack=None, depth=0): + """Topological sort. Arguments are: + deps dictionary describing dependencies where a:[b,c] means "a depends on b and c" + nodes optional, specifies list of starting nodes (these should be the nodes + which are not depended on by any other nodes). Other candidate starting + nodes will be ignored. + + Example:: + + # Sort the following graph: + # + # B ──┬─────> C <── D + # │ │ + # E <─┴─> A <─┘ + # + deps = {'a': ['b', 'c'], 'c': ['b', 'd'], 'e': ['b']} + toposort(deps) + => ['b', 'd', 'c', 'a', 'e'] + """ + # fill in empty dep lists + deps = deps.copy() + for k,v in list(deps.items()): + for k in v: + if k not in deps: + deps[k] = [] + + if nodes is None: + ## run through deps to find nodes that are not depended upon + rem = set() + for dep in deps.values(): + rem |= set(dep) + nodes = set(deps.keys()) - rem + if seen is None: + seen = set() + stack = [] + sorted = [] + for n in nodes: + if n in stack: + raise Exception("Cyclic dependency detected", stack + [n]) + if n in seen: + continue + seen.add(n) + sorted.extend( toposort(deps, deps[n], seen, stack+[n], depth=depth+1)) + sorted.append(n) + return sorted diff --git a/papi/pyqtgraph/graphicsItems/ArrowItem.py b/papi/pyqtgraph/graphicsItems/ArrowItem.py new file mode 100644 index 00000000..77e6195f --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ArrowItem.py @@ -0,0 +1,126 @@ +from ..Qt import QtGui, QtCore +from .. import functions as fn +import numpy as np +__all__ = ['ArrowItem'] + +class ArrowItem(QtGui.QGraphicsPathItem): + """ + For displaying scale-invariant arrows. + For arrows pointing to a location on a curve, see CurveArrow + + """ + + + def __init__(self, **opts): + """ + Arrows can be initialized with any keyword arguments accepted by + the setStyle() method. + """ + self.opts = {} + QtGui.QGraphicsPathItem.__init__(self, opts.get('parent', None)) + + if 'size' in opts: + opts['headLen'] = opts['size'] + if 'width' in opts: + opts['headWidth'] = opts['width'] + defaultOpts = { + 'pxMode': True, + 'angle': -150, ## If the angle is 0, the arrow points left + 'pos': (0,0), + 'headLen': 20, + 'tipAngle': 25, + 'baseAngle': 0, + 'tailLen': None, + 'tailWidth': 3, + 'pen': (200,200,200), + 'brush': (50,50,200), + } + defaultOpts.update(opts) + + self.setStyle(**defaultOpts) + + self.rotate(self.opts['angle']) + self.moveBy(*self.opts['pos']) + + def setStyle(self, **opts): + """ + Changes the appearance of the arrow. + All arguments are optional: + + ====================== ================================================= + **Keyword Arguments:** + angle Orientation of the arrow in degrees. Default is + 0; arrow pointing to the left. + headLen Length of the arrow head, from tip to base. + default=20 + headWidth Width of the arrow head at its base. + tipAngle Angle of the tip of the arrow in degrees. Smaller + values make a 'sharper' arrow. If tipAngle is + specified, ot overrides headWidth. default=25 + baseAngle Angle of the base of the arrow head. Default is + 0, which means that the base of the arrow head + is perpendicular to the arrow tail. + tailLen Length of the arrow tail, measured from the base + of the arrow head to the end of the tail. If + this value is None, no tail will be drawn. + default=None + tailWidth Width of the tail. default=3 + pen The pen used to draw the outline of the arrow. + brush The brush used to fill the arrow. + ====================== ================================================= + """ + self.opts.update(opts) + + opt = dict([(k,self.opts[k]) for k in ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth']]) + self.path = fn.makeArrowPath(**opt) + self.setPath(self.path) + + self.setPen(fn.mkPen(self.opts['pen'])) + self.setBrush(fn.mkBrush(self.opts['brush'])) + + if self.opts['pxMode']: + self.setFlags(self.flags() | self.ItemIgnoresTransformations) + else: + self.setFlags(self.flags() & ~self.ItemIgnoresTransformations) + + def paint(self, p, *args): + p.setRenderHint(QtGui.QPainter.Antialiasing) + QtGui.QGraphicsPathItem.paint(self, p, *args) + + #p.setPen(fn.mkPen('r')) + #p.setBrush(fn.mkBrush(None)) + #p.drawRect(self.boundingRect()) + + def shape(self): + #if not self.opts['pxMode']: + #return QtGui.QGraphicsPathItem.shape(self) + return self.path + + ## dataBounds and pixelPadding methods are provided to ensure ViewBox can + ## properly auto-range + def dataBounds(self, ax, frac, orthoRange=None): + pw = 0 + pen = self.pen() + if not pen.isCosmetic(): + pw = pen.width() * 0.7072 + if self.opts['pxMode']: + return [0,0] + else: + br = self.boundingRect() + if ax == 0: + return [br.left()-pw, br.right()+pw] + else: + return [br.top()-pw, br.bottom()+pw] + + def pixelPadding(self): + pad = 0 + if self.opts['pxMode']: + br = self.boundingRect() + pad += (br.width()**2 + br.height()**2) ** 0.5 + pen = self.pen() + if pen.isCosmetic(): + pad += max(1, pen.width()) * 0.7072 + return pad + + + diff --git a/papi/pyqtgraph/graphicsItems/AxisItem.py b/papi/pyqtgraph/graphicsItems/AxisItem.py new file mode 100644 index 00000000..b125cb7e --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/AxisItem.py @@ -0,0 +1,1076 @@ +from ..Qt import QtGui, QtCore +from ..python2_3 import asUnicode +import numpy as np +from ..Point import Point +from .. import debug as debug +import weakref +from .. import functions as fn +from .. import getConfigOption +from .GraphicsWidget import GraphicsWidget + +__all__ = ['AxisItem'] +class AxisItem(GraphicsWidget): + """ + GraphicsItem showing a single plot axis with ticks, values, and label. + Can be configured to fit on any side of a plot, and can automatically synchronize its displayed scale with ViewBox items. + Ticks can be extended to draw a grid. + If maxTickLength is negative, ticks point into the plot. + """ + + def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLength=-5, showValues=True): + """ + ============== =============================================================== + **Arguments:** + orientation one of 'left', 'right', 'top', or 'bottom' + maxTickLength (px) maximum length of ticks to draw. Negative values draw + into the plot, positive values draw outward. + linkView (ViewBox) causes the range of values displayed in the axis + to be linked to the visible range of a ViewBox. + showValues (bool) Whether to display values adjacent to ticks + pen (QPen) Pen used when drawing ticks. + ============== =============================================================== + """ + + GraphicsWidget.__init__(self, parent) + self.label = QtGui.QGraphicsTextItem(self) + self.picture = None + self.orientation = orientation + if orientation not in ['left', 'right', 'top', 'bottom']: + raise Exception("Orientation argument must be one of 'left', 'right', 'top', or 'bottom'.") + if orientation in ['left', 'right']: + self.label.rotate(-90) + + self.style = { + 'tickTextOffset': [5, 2], ## (horizontal, vertical) spacing between text and axis + 'tickTextWidth': 30, ## space reserved for tick text + 'tickTextHeight': 18, + 'autoExpandTextSpace': True, ## automatically expand text space if needed + 'tickFont': None, + 'stopAxisAtTick': (False, False), ## whether axis is drawn to edge of box or to last tick + 'textFillLimits': [ ## how much of the axis to fill up with tick text, maximally. + (0, 0.8), ## never fill more than 80% of the axis + (2, 0.6), ## If we already have 2 ticks with text, fill no more than 60% of the axis + (4, 0.4), ## If we already have 4 ticks with text, fill no more than 40% of the axis + (6, 0.2), ## If we already have 6 ticks with text, fill no more than 20% of the axis + ], + 'showValues': showValues, + 'tickLength': maxTickLength, + 'maxTickLevel': 2, + 'maxTextLevel': 2, + } + + self.textWidth = 30 ## Keeps track of maximum width / height of tick text + self.textHeight = 18 + + # If the user specifies a width / height, remember that setting + # indefinitely. + self.fixedWidth = None + self.fixedHeight = None + + self.labelText = '' + self.labelUnits = '' + self.labelUnitPrefix='' + self.labelStyle = {} + self.logMode = False + self.tickFont = None + + self._tickLevels = None ## used to override the automatic ticking system with explicit ticks + self._tickSpacing = None # used to override default tickSpacing method + self.scale = 1.0 + self.autoSIPrefix = True + self.autoSIPrefixScale = 1.0 + + self.setRange(0, 1) + + if pen is None: + self.setPen() + else: + self.setPen(pen) + + self._linkedView = None + if linkView is not None: + self.linkToView(linkView) + + self.showLabel(False) + + self.grid = False + #self.setCacheMode(self.DeviceCoordinateCache) + + def setStyle(self, **kwds): + """ + Set various style options. + + =================== ======================================================= + Keyword Arguments: + tickLength (int) The maximum length of ticks in pixels. + Positive values point toward the text; negative + values point away. + tickTextOffset (int) reserved spacing between text and axis in px + tickTextWidth (int) Horizontal space reserved for tick text in px + tickTextHeight (int) Vertical space reserved for tick text in px + autoExpandTextSpace (bool) Automatically expand text space if the tick + strings become too long. + tickFont (QFont or None) Determines the font used for tick + values. Use None for the default font. + stopAxisAtTick (tuple: (bool min, bool max)) If True, the axis + line is drawn only as far as the last tick. + Otherwise, the line is drawn to the edge of the + AxisItem boundary. + textFillLimits (list of (tick #, % fill) tuples). This structure + determines how the AxisItem decides how many ticks + should have text appear next to them. Each tuple in + the list specifies what fraction of the axis length + may be occupied by text, given the number of ticks + that already have text displayed. For example:: + + [(0, 0.8), # Never fill more than 80% of the axis + (2, 0.6), # If we already have 2 ticks with text, + # fill no more than 60% of the axis + (4, 0.4), # If we already have 4 ticks with text, + # fill no more than 40% of the axis + (6, 0.2)] # If we already have 6 ticks with text, + # fill no more than 20% of the axis + + showValues (bool) indicates whether text is displayed adjacent + to ticks. + =================== ======================================================= + + Added in version 0.9.9 + """ + for kwd,value in kwds.items(): + if kwd not in self.style: + raise NameError("%s is not a valid style argument." % kwd) + + if kwd in ('tickLength', 'tickTextOffset', 'tickTextWidth', 'tickTextHeight'): + if not isinstance(value, int): + raise ValueError("Argument '%s' must be int" % kwd) + + if kwd == 'tickTextOffset': + if self.orientation in ('left', 'right'): + self.style['tickTextOffset'][0] = value + else: + self.style['tickTextOffset'][1] = value + elif kwd == 'stopAxisAtTick': + try: + assert len(value) == 2 and isinstance(value[0], bool) and isinstance(value[1], bool) + except: + raise ValueError("Argument 'stopAxisAtTick' must have type (bool, bool)") + self.style[kwd] = value + else: + self.style[kwd] = value + + self.picture = None + self._adjustSize() + self.update() + + def close(self): + self.scene().removeItem(self.label) + self.label = None + self.scene().removeItem(self) + + def setGrid(self, grid): + """Set the alpha value (0-255) for the grid, or False to disable. + + When grid lines are enabled, the axis tick lines are extended to cover + the extent of the linked ViewBox, if any. + """ + self.grid = grid + self.picture = None + self.prepareGeometryChange() + self.update() + + def setLogMode(self, log): + """ + If *log* is True, then ticks are displayed on a logarithmic scale and values + are adjusted accordingly. (This is usually accessed by changing the log mode + of a :func:`PlotItem `) + """ + self.logMode = log + self.picture = None + self.update() + + def setTickFont(self, font): + self.tickFont = font + self.picture = None + self.prepareGeometryChange() + ## Need to re-allocate space depending on font size? + + self.update() + + def resizeEvent(self, ev=None): + #s = self.size() + + ## Set the position of the label + nudge = 5 + br = self.label.boundingRect() + p = QtCore.QPointF(0, 0) + if self.orientation == 'left': + p.setY(int(self.size().height()/2 + br.width()/2)) + p.setX(-nudge) + elif self.orientation == 'right': + p.setY(int(self.size().height()/2 + br.width()/2)) + p.setX(int(self.size().width()-br.height()+nudge)) + elif self.orientation == 'top': + p.setY(-nudge) + p.setX(int(self.size().width()/2. - br.width()/2.)) + elif self.orientation == 'bottom': + p.setX(int(self.size().width()/2. - br.width()/2.)) + p.setY(int(self.size().height()-br.height()+nudge)) + self.label.setPos(p) + self.picture = None + + def showLabel(self, show=True): + """Show/hide the label text for this axis.""" + #self.drawLabel = show + self.label.setVisible(show) + if self.orientation in ['left', 'right']: + self._updateWidth() + else: + self._updateHeight() + if self.autoSIPrefix: + self.updateAutoSIPrefix() + + def setLabel(self, text=None, units=None, unitPrefix=None, **args): + """Set the text displayed adjacent to the axis. + + ============== ============================================================= + **Arguments:** + text The text (excluding units) to display on the label for this + axis. + units The units for this axis. Units should generally be given + without any scaling prefix (eg, 'V' instead of 'mV'). The + scaling prefix will be automatically prepended based on the + range of data displayed. + **args All extra keyword arguments become CSS style options for + the tag which will surround the axis label and units. + ============== ============================================================= + + The final text generated for the label will look like:: + + {text} (prefix{units}) + + Each extra keyword argument will become a CSS option in the above template. + For example, you can set the font size and color of the label:: + + labelStyle = {'color': '#FFF', 'font-size': '14pt'} + axis.setLabel('label text', units='V', **labelStyle) + + """ + if text is not None: + self.labelText = text + self.showLabel() + if units is not None: + self.labelUnits = units + self.showLabel() + if unitPrefix is not None: + self.labelUnitPrefix = unitPrefix + if len(args) > 0: + self.labelStyle = args + self.label.setHtml(self.labelString()) + self._adjustSize() + self.picture = None + self.update() + + def labelString(self): + if self.labelUnits == '': + if not self.autoSIPrefix or self.autoSIPrefixScale == 1.0: + units = '' + else: + units = asUnicode('(x%g)') % (1.0/self.autoSIPrefixScale) + else: + #print repr(self.labelUnitPrefix), repr(self.labelUnits) + units = asUnicode('(%s%s)') % (asUnicode(self.labelUnitPrefix), asUnicode(self.labelUnits)) + + s = asUnicode('%s %s') % (asUnicode(self.labelText), asUnicode(units)) + + style = ';'.join(['%s: %s' % (k, self.labelStyle[k]) for k in self.labelStyle]) + + return asUnicode("%s") % (style, asUnicode(s)) + + def _updateMaxTextSize(self, x): + ## Informs that the maximum tick size orthogonal to the axis has + ## changed; we use this to decide whether the item needs to be resized + ## to accomodate. + if self.orientation in ['left', 'right']: + mx = max(self.textWidth, x) + if mx > self.textWidth or mx < self.textWidth-10: + self.textWidth = mx + if self.style['autoExpandTextSpace'] is True: + self._updateWidth() + #return True ## size has changed + else: + mx = max(self.textHeight, x) + if mx > self.textHeight or mx < self.textHeight-10: + self.textHeight = mx + if self.style['autoExpandTextSpace'] is True: + self._updateHeight() + #return True ## size has changed + + def _adjustSize(self): + if self.orientation in ['left', 'right']: + self._updateWidth() + else: + self._updateHeight() + + def setHeight(self, h=None): + """Set the height of this axis reserved for ticks and tick labels. + The height of the axis label is automatically added. + + If *height* is None, then the value will be determined automatically + based on the size of the tick text.""" + self.fixedHeight = h + self._updateHeight() + + def _updateHeight(self): + if not self.isVisible(): + h = 0 + else: + if self.fixedHeight is None: + if not self.style['showValues']: + h = 0 + elif self.style['autoExpandTextSpace'] is True: + h = self.textHeight + else: + h = self.style['tickTextHeight'] + h += self.style['tickTextOffset'][1] if self.style['showValues'] else 0 + h += max(0, self.style['tickLength']) + if self.label.isVisible(): + h += self.label.boundingRect().height() * 0.8 + else: + h = self.fixedHeight + + self.setMaximumHeight(h) + self.setMinimumHeight(h) + self.picture = None + + def setWidth(self, w=None): + """Set the width of this axis reserved for ticks and tick labels. + The width of the axis label is automatically added. + + If *width* is None, then the value will be determined automatically + based on the size of the tick text.""" + self.fixedWidth = w + self._updateWidth() + + def _updateWidth(self): + if not self.isVisible(): + w = 0 + else: + if self.fixedWidth is None: + if not self.style['showValues']: + w = 0 + elif self.style['autoExpandTextSpace'] is True: + w = self.textWidth + else: + w = self.style['tickTextWidth'] + w += self.style['tickTextOffset'][0] if self.style['showValues'] else 0 + w += max(0, self.style['tickLength']) + if self.label.isVisible(): + w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate + else: + w = self.fixedWidth + + self.setMaximumWidth(w) + self.setMinimumWidth(w) + self.picture = None + + def pen(self): + if self._pen is None: + return fn.mkPen(getConfigOption('foreground')) + return fn.mkPen(self._pen) + + def setPen(self, *args, **kwargs): + """ + Set the pen used for drawing text, axes, ticks, and grid lines. + If no arguments are given, the default foreground color will be used + (see :func:`setConfigOption `). + """ + self.picture = None + if args or kwargs: + self._pen = fn.mkPen(*args, **kwargs) + else: + self._pen = fn.mkPen(getConfigOption('foreground')) + self.labelStyle['color'] = '#' + fn.colorStr(self._pen.color())[:6] + self.setLabel() + self.update() + + def setScale(self, scale=None): + """ + Set the value scaling for this axis. + + Setting this value causes the axis to draw ticks and tick labels as if + the view coordinate system were scaled. By default, the axis scaling is + 1.0. + """ + # Deprecated usage, kept for backward compatibility + if scale is None: + scale = 1.0 + self.enableAutoSIPrefix(True) + + if scale != self.scale: + self.scale = scale + self.setLabel() + self.picture = None + self.update() + + def enableAutoSIPrefix(self, enable=True): + """ + Enable (or disable) automatic SI prefix scaling on this axis. + + When enabled, this feature automatically determines the best SI prefix + to prepend to the label units, while ensuring that axis values are scaled + accordingly. + + For example, if the axis spans values from -0.1 to 0.1 and has units set + to 'V' then the axis would display values -100 to 100 + and the units would appear as 'mV' + + This feature is enabled by default, and is only available when a suffix + (unit string) is provided to display on the label. + """ + self.autoSIPrefix = enable + self.updateAutoSIPrefix() + + def updateAutoSIPrefix(self): + if self.label.isVisible(): + (scale, prefix) = fn.siScale(max(abs(self.range[0]*self.scale), abs(self.range[1]*self.scale))) + if self.labelUnits == '' and prefix in ['k', 'm']: ## If we are not showing units, wait until 1e6 before scaling. + scale = 1.0 + prefix = '' + self.setLabel(unitPrefix=prefix) + else: + scale = 1.0 + + self.autoSIPrefixScale = scale + self.picture = None + self.update() + + + def setRange(self, mn, mx): + """Set the range of values displayed by the axis. + Usually this is handled automatically by linking the axis to a ViewBox with :func:`linkToView `""" + if any(np.isinf((mn, mx))) or any(np.isnan((mn, mx))): + raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx))) + self.range = [mn, mx] + if self.autoSIPrefix: + self.updateAutoSIPrefix() + self.picture = None + self.update() + + def linkedView(self): + """Return the ViewBox this axis is linked to""" + if self._linkedView is None: + return None + else: + return self._linkedView() + + def linkToView(self, view): + """Link this axis to a ViewBox, causing its displayed range to match the visible range of the view.""" + oldView = self.linkedView() + self._linkedView = weakref.ref(view) + if self.orientation in ['right', 'left']: + if oldView is not None: + oldView.sigYRangeChanged.disconnect(self.linkedViewChanged) + view.sigYRangeChanged.connect(self.linkedViewChanged) + else: + if oldView is not None: + oldView.sigXRangeChanged.disconnect(self.linkedViewChanged) + view.sigXRangeChanged.connect(self.linkedViewChanged) + + if oldView is not None: + oldView.sigResized.disconnect(self.linkedViewChanged) + view.sigResized.connect(self.linkedViewChanged) + + def linkedViewChanged(self, view, newRange=None): + if self.orientation in ['right', 'left']: + if newRange is None: + newRange = view.viewRange()[1] + if view.yInverted(): + self.setRange(*newRange[::-1]) + else: + self.setRange(*newRange) + else: + if newRange is None: + newRange = view.viewRange()[0] + if view.xInverted(): + self.setRange(*newRange[::-1]) + else: + self.setRange(*newRange) + + def boundingRect(self): + linkedView = self.linkedView() + if linkedView is None or self.grid is False: + rect = self.mapRectFromParent(self.geometry()) + ## extend rect if ticks go in negative direction + ## also extend to account for text that flows past the edges + tl = self.style['tickLength'] + if self.orientation == 'left': + rect = rect.adjusted(0, -15, -min(0,tl), 15) + elif self.orientation == 'right': + rect = rect.adjusted(min(0,tl), -15, 0, 15) + elif self.orientation == 'top': + rect = rect.adjusted(-15, 0, 15, -min(0,tl)) + elif self.orientation == 'bottom': + rect = rect.adjusted(-15, min(0,tl), 15, 0) + return rect + else: + return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect()) + + def paint(self, p, opt, widget): + profiler = debug.Profiler() + if self.picture is None: + try: + picture = QtGui.QPicture() + painter = QtGui.QPainter(picture) + specs = self.generateDrawSpecs(painter) + profiler('generate specs') + if specs is not None: + self.drawPicture(painter, *specs) + profiler('draw picture') + finally: + painter.end() + self.picture = picture + #p.setRenderHint(p.Antialiasing, False) ## Sometimes we get a segfault here ??? + #p.setRenderHint(p.TextAntialiasing, True) + self.picture.play(p) + + def setTicks(self, ticks): + """Explicitly determine which ticks to display. + This overrides the behavior specified by tickSpacing(), tickValues(), and tickStrings() + The format for *ticks* looks like:: + + [ + [ (majorTickValue1, majorTickString1), (majorTickValue2, majorTickString2), ... ], + [ (minorTickValue1, minorTickString1), (minorTickValue2, minorTickString2), ... ], + ... + ] + + If *ticks* is None, then the default tick system will be used instead. + """ + self._tickLevels = ticks + self.picture = None + self.update() + + def setTickSpacing(self, major=None, minor=None, levels=None): + """ + Explicitly determine the spacing of major and minor ticks. This + overrides the default behavior of the tickSpacing method, and disables + the effect of setTicks(). Arguments may be either *major* and *minor*, + or *levels* which is a list of (spacing, offset) tuples for each + tick level desired. + + If no arguments are given, then the default behavior of tickSpacing + is enabled. + + Examples:: + + # two levels, all offsets = 0 + axis.setTickSpacing(5, 1) + # three levels, all offsets = 0 + axis.setTickSpacing([(3, 0), (1, 0), (0.25, 0)]) + # reset to default + axis.setTickSpacing() + """ + + if levels is None: + if major is None: + levels = None + else: + levels = [(major, 0), (minor, 0)] + self._tickSpacing = levels + self.picture = None + self.update() + + + def tickSpacing(self, minVal, maxVal, size): + """Return values describing the desired spacing and offset of ticks. + + This method is called whenever the axis needs to be redrawn and is a + good method to override in subclasses that require control over tick locations. + + The return value must be a list of tuples, one for each set of ticks:: + + [ + (major tick spacing, offset), + (minor tick spacing, offset), + (sub-minor tick spacing, offset), + ... + ] + """ + # First check for override tick spacing + if self._tickSpacing is not None: + return self._tickSpacing + + dif = abs(maxVal - minVal) + if dif == 0: + return [] + + ## decide optimal minor tick spacing in pixels (this is just aesthetics) + optimalTickCount = max(2., np.log(size)) + + ## optimal minor tick spacing + optimalSpacing = dif / optimalTickCount + + ## the largest power-of-10 spacing which is smaller than optimal + p10unit = 10 ** np.floor(np.log10(optimalSpacing)) + + ## Determine major/minor tick spacings which flank the optimal spacing. + intervals = np.array([1., 2., 10., 20., 100.]) * p10unit + minorIndex = 0 + while intervals[minorIndex+1] <= optimalSpacing: + minorIndex += 1 + + levels = [ + (intervals[minorIndex+2], 0), + (intervals[minorIndex+1], 0), + #(intervals[minorIndex], 0) ## Pretty, but eats up CPU + ] + + if self.style['maxTickLevel'] >= 2: + ## decide whether to include the last level of ticks + minSpacing = min(size / 20., 30.) + maxTickCount = size / minSpacing + if dif / intervals[minorIndex] <= maxTickCount: + levels.append((intervals[minorIndex], 0)) + return levels + + + + ##### This does not work -- switching between 2/5 confuses the automatic text-level-selection + ### Determine major/minor tick spacings which flank the optimal spacing. + #intervals = np.array([1., 2., 5., 10., 20., 50., 100.]) * p10unit + #minorIndex = 0 + #while intervals[minorIndex+1] <= optimalSpacing: + #minorIndex += 1 + + ### make sure we never see 5 and 2 at the same time + #intIndexes = [ + #[0,1,3], + #[0,2,3], + #[2,3,4], + #[3,4,6], + #[3,5,6], + #][minorIndex] + + #return [ + #(intervals[intIndexes[2]], 0), + #(intervals[intIndexes[1]], 0), + #(intervals[intIndexes[0]], 0) + #] + + def tickValues(self, minVal, maxVal, size): + """ + Return the values and spacing of ticks to draw:: + + [ + (spacing, [major ticks]), + (spacing, [minor ticks]), + ... + ] + + By default, this method calls tickSpacing to determine the correct tick locations. + This is a good method to override in subclasses. + """ + minVal, maxVal = sorted((minVal, maxVal)) + + + minVal *= self.scale + maxVal *= self.scale + #size *= self.scale + + ticks = [] + tickLevels = self.tickSpacing(minVal, maxVal, size) + allValues = np.array([]) + for i in range(len(tickLevels)): + spacing, offset = tickLevels[i] + + ## determine starting tick + start = (np.ceil((minVal-offset) / spacing) * spacing) + offset + + ## determine number of ticks + num = int((maxVal-start) / spacing) + 1 + values = (np.arange(num) * spacing + start) / self.scale + ## remove any ticks that were present in higher levels + ## we assume here that if the difference between a tick value and a previously seen tick value + ## is less than spacing/100, then they are 'equal' and we can ignore the new tick. + values = list(filter(lambda x: all(np.abs(allValues-x) > spacing*0.01), values) ) + allValues = np.concatenate([allValues, values]) + ticks.append((spacing/self.scale, values)) + + if self.logMode: + return self.logTickValues(minVal, maxVal, size, ticks) + + + #nticks = [] + #for t in ticks: + #nvals = [] + #for v in t[1]: + #nvals.append(v/self.scale) + #nticks.append((t[0]/self.scale,nvals)) + #ticks = nticks + + return ticks + + def logTickValues(self, minVal, maxVal, size, stdTicks): + + ## start with the tick spacing given by tickValues(). + ## Any level whose spacing is < 1 needs to be converted to log scale + + ticks = [] + for (spacing, t) in stdTicks: + if spacing >= 1.0: + ticks.append((spacing, t)) + + if len(ticks) < 3: + v1 = int(np.floor(minVal)) + v2 = int(np.ceil(maxVal)) + #major = list(range(v1+1, v2)) + + minor = [] + for v in range(v1, v2): + minor.extend(v + np.log10(np.arange(1, 10))) + minor = [x for x in minor if x>minVal and x= 10000: + vstr = "%g" % vs + else: + vstr = ("%%0.%df" % places) % vs + strings.append(vstr) + return strings + + def logTickStrings(self, values, scale, spacing): + return ["%0.1g"%x for x in 10 ** np.array(values).astype(float)] + + def generateDrawSpecs(self, p): + """ + Calls tickValues() and tickStrings() to determine where and how ticks should + be drawn, then generates from this a set of drawing commands to be + interpreted by drawPicture(). + """ + profiler = debug.Profiler() + + #bounds = self.boundingRect() + bounds = self.mapRectFromParent(self.geometry()) + + linkedView = self.linkedView() + if linkedView is None or self.grid is False: + tickBounds = bounds + else: + tickBounds = linkedView.mapRectToItem(self, linkedView.boundingRect()) + + if self.orientation == 'left': + span = (bounds.topRight(), bounds.bottomRight()) + tickStart = tickBounds.right() + tickStop = bounds.right() + tickDir = -1 + axis = 0 + elif self.orientation == 'right': + span = (bounds.topLeft(), bounds.bottomLeft()) + tickStart = tickBounds.left() + tickStop = bounds.left() + tickDir = 1 + axis = 0 + elif self.orientation == 'top': + span = (bounds.bottomLeft(), bounds.bottomRight()) + tickStart = tickBounds.bottom() + tickStop = bounds.bottom() + tickDir = -1 + axis = 1 + elif self.orientation == 'bottom': + span = (bounds.topLeft(), bounds.topRight()) + tickStart = tickBounds.top() + tickStop = bounds.top() + tickDir = 1 + axis = 1 + #print tickStart, tickStop, span + + ## determine size of this item in pixels + points = list(map(self.mapToDevice, span)) + if None in points: + return + lengthInPixels = Point(points[1] - points[0]).length() + if lengthInPixels == 0: + return + + # Determine major / minor / subminor axis ticks + if self._tickLevels is None: + tickLevels = self.tickValues(self.range[0], self.range[1], lengthInPixels) + tickStrings = None + else: + ## parse self.tickLevels into the formats returned by tickLevels() and tickStrings() + tickLevels = [] + tickStrings = [] + for level in self._tickLevels: + values = [] + strings = [] + tickLevels.append((None, values)) + tickStrings.append(strings) + for val, strn in level: + values.append(val) + strings.append(strn) + + ## determine mapping between tick values and local coordinates + dif = self.range[1] - self.range[0] + if dif == 0: + xScale = 1 + offset = 0 + else: + if axis == 0: + xScale = -bounds.height() / dif + offset = self.range[0] * xScale - bounds.height() + else: + xScale = bounds.width() / dif + offset = self.range[0] * xScale + + xRange = [x * xScale - offset for x in self.range] + xMin = min(xRange) + xMax = max(xRange) + + profiler('init') + + tickPositions = [] # remembers positions of previously drawn ticks + + ## compute coordinates to draw ticks + ## draw three different intervals, long ticks first + tickSpecs = [] + for i in range(len(tickLevels)): + tickPositions.append([]) + ticks = tickLevels[i][1] + + ## length of tick + tickLength = self.style['tickLength'] / ((i*0.5)+1.0) + + lineAlpha = 255 / (i+1) + if self.grid is not False: + lineAlpha *= self.grid/255. * np.clip((0.05 * lengthInPixels / (len(ticks)+1)), 0., 1.) + + for v in ticks: + ## determine actual position to draw this tick + x = (v * xScale) - offset + if x < xMin or x > xMax: ## last check to make sure no out-of-bounds ticks are drawn + tickPositions[i].append(None) + continue + tickPositions[i].append(x) + + p1 = [x, x] + p2 = [x, x] + p1[axis] = tickStart + p2[axis] = tickStop + if self.grid is False: + p2[axis] += tickLength*tickDir + tickPen = self.pen() + color = tickPen.color() + color.setAlpha(lineAlpha) + tickPen.setColor(color) + tickSpecs.append((tickPen, Point(p1), Point(p2))) + profiler('compute ticks') + + + if self.style['stopAxisAtTick'][0] is True: + stop = max(span[0].y(), min(map(min, tickPositions))) + if axis == 0: + span[0].setY(stop) + else: + span[0].setX(stop) + if self.style['stopAxisAtTick'][1] is True: + stop = min(span[1].y(), max(map(max, tickPositions))) + if axis == 0: + span[1].setY(stop) + else: + span[1].setX(stop) + axisSpec = (self.pen(), span[0], span[1]) + + + textOffset = self.style['tickTextOffset'][axis] ## spacing between axis and text + #if self.style['autoExpandTextSpace'] is True: + #textWidth = self.textWidth + #textHeight = self.textHeight + #else: + #textWidth = self.style['tickTextWidth'] ## space allocated for horizontal text + #textHeight = self.style['tickTextHeight'] ## space allocated for horizontal text + + textSize2 = 0 + textRects = [] + textSpecs = [] ## list of draw + + # If values are hidden, return early + if not self.style['showValues']: + return (axisSpec, tickSpecs, textSpecs) + + for i in range(min(len(tickLevels), self.style['maxTextLevel']+1)): + ## Get the list of strings to display for this level + if tickStrings is None: + spacing, values = tickLevels[i] + strings = self.tickStrings(values, self.autoSIPrefixScale * self.scale, spacing) + else: + strings = tickStrings[i] + + if len(strings) == 0: + continue + + ## ignore strings belonging to ticks that were previously ignored + for j in range(len(strings)): + if tickPositions[i][j] is None: + strings[j] = None + + ## Measure density of text; decide whether to draw this level + rects = [] + for s in strings: + if s is None: + rects.append(None) + else: + br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, asUnicode(s)) + ## boundingRect is usually just a bit too large + ## (but this probably depends on per-font metrics?) + br.setHeight(br.height() * 0.8) + + rects.append(br) + textRects.append(rects[-1]) + + if len(textRects) > 0: + ## measure all text, make sure there's enough room + if axis == 0: + textSize = np.sum([r.height() for r in textRects]) + textSize2 = np.max([r.width() for r in textRects]) + else: + textSize = np.sum([r.width() for r in textRects]) + textSize2 = np.max([r.height() for r in textRects]) + else: + textSize = 0 + textSize2 = 0 + + if i > 0: ## always draw top level + ## If the strings are too crowded, stop drawing text now. + ## We use three different crowding limits based on the number + ## of texts drawn so far. + textFillRatio = float(textSize) / lengthInPixels + finished = False + for nTexts, limit in self.style['textFillLimits']: + if len(textSpecs) >= nTexts and textFillRatio >= limit: + finished = True + break + if finished: + break + + #spacing, values = tickLevels[best] + #strings = self.tickStrings(values, self.scale, spacing) + # Determine exactly where tick text should be drawn + for j in range(len(strings)): + vstr = strings[j] + if vstr is None: ## this tick was ignored because it is out of bounds + continue + vstr = asUnicode(vstr) + x = tickPositions[i][j] + #textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, vstr) + textRect = rects[j] + height = textRect.height() + width = textRect.width() + #self.textHeight = height + offset = max(0,self.style['tickLength']) + textOffset + if self.orientation == 'left': + textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter + rect = QtCore.QRectF(tickStop-offset-width, x-(height/2), width, height) + elif self.orientation == 'right': + textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter + rect = QtCore.QRectF(tickStop+offset, x-(height/2), width, height) + elif self.orientation == 'top': + textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignCenter|QtCore.Qt.AlignBottom + rect = QtCore.QRectF(x-width/2., tickStop-offset-height, width, height) + elif self.orientation == 'bottom': + textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignCenter|QtCore.Qt.AlignTop + rect = QtCore.QRectF(x-width/2., tickStop+offset, width, height) + + #p.setPen(self.pen()) + #p.drawText(rect, textFlags, vstr) + textSpecs.append((rect, textFlags, vstr)) + profiler('compute text') + + ## update max text size if needed. + self._updateMaxTextSize(textSize2) + + return (axisSpec, tickSpecs, textSpecs) + + def drawPicture(self, p, axisSpec, tickSpecs, textSpecs): + profiler = debug.Profiler() + + p.setRenderHint(p.Antialiasing, False) + p.setRenderHint(p.TextAntialiasing, True) + + ## draw long line along axis + pen, p1, p2 = axisSpec + p.setPen(pen) + p.drawLine(p1, p2) + p.translate(0.5,0) ## resolves some damn pixel ambiguity + + ## draw ticks + for pen, p1, p2 in tickSpecs: + p.setPen(pen) + p.drawLine(p1, p2) + profiler('draw ticks') + + ## Draw all text + if self.tickFont is not None: + p.setFont(self.tickFont) + p.setPen(self.pen()) + for rect, flags, text in textSpecs: + p.drawText(rect, flags, text) + #p.drawRect(rect) + profiler('draw text') + + def show(self): + GraphicsWidget.show(self) + if self.orientation in ['left', 'right']: + self._updateWidth() + else: + self._updateHeight() + + def hide(self): + GraphicsWidget.hide(self) + if self.orientation in ['left', 'right']: + self._updateWidth() + else: + self._updateHeight() + + def wheelEvent(self, ev): + if self.linkedView() is None: + return + if self.orientation in ['left', 'right']: + self.linkedView().wheelEvent(ev, axis=1) + else: + self.linkedView().wheelEvent(ev, axis=0) + ev.accept() + + def mouseDragEvent(self, event): + if self.linkedView() is None: + return + if self.orientation in ['left', 'right']: + return self.linkedView().mouseDragEvent(event, axis=1) + else: + return self.linkedView().mouseDragEvent(event, axis=0) + + def mouseClickEvent(self, event): + if self.linkedView() is None: + return + return self.linkedView().mouseClickEvent(event) diff --git a/papi/pyqtgraph/graphicsItems/BarGraphItem.py b/papi/pyqtgraph/graphicsItems/BarGraphItem.py new file mode 100644 index 00000000..a1d5d029 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/BarGraphItem.py @@ -0,0 +1,168 @@ +from ..Qt import QtGui, QtCore +from .GraphicsObject import GraphicsObject +from .. import getConfigOption +from .. import functions as fn +import numpy as np + + +__all__ = ['BarGraphItem'] + +class BarGraphItem(GraphicsObject): + def __init__(self, **opts): + """ + Valid keyword options are: + x, x0, x1, y, y0, y1, width, height, pen, brush + + x specifies the x-position of the center of the bar. + x0, x1 specify left and right edges of the bar, respectively. + width specifies distance from x0 to x1. + You may specify any combination: + + x, width + x0, width + x1, width + x0, x1 + + Likewise y, y0, y1, and height. + If only height is specified, then y0 will be set to 0 + + Example uses: + + BarGraphItem(x=range(5), height=[1,5,2,4,3], width=0.5) + + + """ + GraphicsObject.__init__(self) + self.opts = dict( + x=None, + y=None, + x0=None, + y0=None, + x1=None, + y1=None, + height=None, + width=None, + pen=None, + brush=None, + pens=None, + brushes=None, + ) + self._shape = None + self.picture = None + self.setOpts(**opts) + + def setOpts(self, **opts): + self.opts.update(opts) + self.picture = None + self._shape = None + self.update() + self.informViewBoundsChanged() + + def drawPicture(self): + self.picture = QtGui.QPicture() + self._shape = QtGui.QPainterPath() + p = QtGui.QPainter(self.picture) + + pen = self.opts['pen'] + pens = self.opts['pens'] + + if pen is None and pens is None: + pen = getConfigOption('foreground') + + brush = self.opts['brush'] + brushes = self.opts['brushes'] + if brush is None and brushes is None: + brush = (128, 128, 128) + + def asarray(x): + if x is None or np.isscalar(x) or isinstance(x, np.ndarray): + return x + return np.array(x) + + + x = asarray(self.opts.get('x')) + x0 = asarray(self.opts.get('x0')) + x1 = asarray(self.opts.get('x1')) + width = asarray(self.opts.get('width')) + + if x0 is None: + if width is None: + raise Exception('must specify either x0 or width') + if x1 is not None: + x0 = x1 - width + elif x is not None: + x0 = x - width/2. + else: + raise Exception('must specify at least one of x, x0, or x1') + if width is None: + if x1 is None: + raise Exception('must specify either x1 or width') + width = x1 - x0 + + y = asarray(self.opts.get('y')) + y0 = asarray(self.opts.get('y0')) + y1 = asarray(self.opts.get('y1')) + height = asarray(self.opts.get('height')) + + if y0 is None: + if height is None: + y0 = 0 + elif y1 is not None: + y0 = y1 - height + elif y is not None: + y0 = y - height/2. + else: + y0 = 0 + if height is None: + if y1 is None: + raise Exception('must specify either y1 or height') + height = y1 - y0 + + p.setPen(fn.mkPen(pen)) + p.setBrush(fn.mkBrush(brush)) + for i in range(len(x0)): + if pens is not None: + p.setPen(fn.mkPen(pens[i])) + if brushes is not None: + p.setBrush(fn.mkBrush(brushes[i])) + + if np.isscalar(x0): + x = x0 + else: + x = x0[i] + if np.isscalar(y0): + y = y0 + else: + y = y0[i] + if np.isscalar(width): + w = width + else: + w = width[i] + if np.isscalar(height): + h = height + else: + h = height[i] + + + rect = QtCore.QRectF(x, y, w, h) + p.drawRect(rect) + self._shape.addRect(rect) + + p.end() + self.prepareGeometryChange() + + + def paint(self, p, *args): + if self.picture is None: + self.drawPicture() + self.picture.play(p) + + def boundingRect(self): + if self.picture is None: + self.drawPicture() + return QtCore.QRectF(self.picture.boundingRect()) + + def shape(self): + if self.picture is None: + self.drawPicture() + return self._shape diff --git a/papi/pyqtgraph/graphicsItems/ButtonItem.py b/papi/pyqtgraph/graphicsItems/ButtonItem.py new file mode 100644 index 00000000..1c796823 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ButtonItem.py @@ -0,0 +1,58 @@ +from ..Qt import QtGui, QtCore +from .GraphicsObject import GraphicsObject + +__all__ = ['ButtonItem'] +class ButtonItem(GraphicsObject): + """Button graphicsItem displaying an image.""" + + clicked = QtCore.Signal(object) + + def __init__(self, imageFile=None, width=None, parentItem=None, pixmap=None): + self.enabled = True + GraphicsObject.__init__(self) + if imageFile is not None: + self.setImageFile(imageFile) + elif pixmap is not None: + self.setPixmap(pixmap) + + if width is not None: + s = float(width) / self.pixmap.width() + self.scale(s, s) + if parentItem is not None: + self.setParentItem(parentItem) + self.setOpacity(0.7) + + def setImageFile(self, imageFile): + self.setPixmap(QtGui.QPixmap(imageFile)) + + def setPixmap(self, pixmap): + self.pixmap = pixmap + self.update() + + def mouseClickEvent(self, ev): + if self.enabled: + self.clicked.emit(self) + + def mouseHoverEvent(self, ev): + if not self.enabled: + return + if ev.isEnter(): + self.setOpacity(1.0) + else: + self.setOpacity(0.7) + + def disable(self): + self.enabled = False + self.setOpacity(0.4) + + def enable(self): + self.enabled = True + self.setOpacity(0.7) + + def paint(self, p, *args): + p.setRenderHint(p.Antialiasing) + p.drawPixmap(0, 0, self.pixmap) + + def boundingRect(self): + return QtCore.QRectF(self.pixmap.rect()) + diff --git a/papi/pyqtgraph/graphicsItems/CurvePoint.py b/papi/pyqtgraph/graphicsItems/CurvePoint.py new file mode 100644 index 00000000..bb6beebc --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/CurvePoint.py @@ -0,0 +1,117 @@ +from ..Qt import QtGui, QtCore +from . import ArrowItem +import numpy as np +from ..Point import Point +import weakref +from .GraphicsObject import GraphicsObject + +__all__ = ['CurvePoint', 'CurveArrow'] +class CurvePoint(GraphicsObject): + """A GraphicsItem that sets its location to a point on a PlotCurveItem. + Also rotates to be tangent to the curve. + The position along the curve is a Qt property, and thus can be easily animated. + + Note: This class does not display anything; see CurveArrow for an applied example + """ + + def __init__(self, curve, index=0, pos=None, rotate=True): + """Position can be set either as an index referring to the sample number or + the position 0.0 - 1.0 + If *rotate* is True, then the item rotates to match the tangent of the curve. + """ + + GraphicsObject.__init__(self) + #QObjectWorkaround.__init__(self) + self._rotate = rotate + self.curve = weakref.ref(curve) + self.setParentItem(curve) + self.setProperty('position', 0.0) + self.setProperty('index', 0) + + if hasattr(self, 'ItemHasNoContents'): + self.setFlags(self.flags() | self.ItemHasNoContents) + + if pos is not None: + self.setPos(pos) + else: + self.setIndex(index) + + def setPos(self, pos): + self.setProperty('position', float(pos))## cannot use numpy types here, MUST be python float. + + def setIndex(self, index): + self.setProperty('index', int(index)) ## cannot use numpy types here, MUST be python int. + + def event(self, ev): + if not isinstance(ev, QtCore.QDynamicPropertyChangeEvent) or self.curve() is None: + return False + + if ev.propertyName() == 'index': + index = self.property('index') + if 'QVariant' in repr(index): + index = index.toInt()[0] + elif ev.propertyName() == 'position': + index = None + else: + return False + + (x, y) = self.curve().getData() + if index is None: + #print ev.propertyName(), self.property('position').toDouble()[0], self.property('position').typeName() + pos = self.property('position') + if 'QVariant' in repr(pos): ## need to support 2 APIs :( + pos = pos.toDouble()[0] + index = (len(x)-1) * np.clip(pos, 0.0, 1.0) + + if index != int(index): ## interpolate floating-point values + i1 = int(index) + i2 = np.clip(i1+1, 0, len(x)-1) + s2 = index-i1 + s1 = 1.0-s2 + newPos = (x[i1]*s1+x[i2]*s2, y[i1]*s1+y[i2]*s2) + else: + index = int(index) + i1 = np.clip(index-1, 0, len(x)-1) + i2 = np.clip(index+1, 0, len(x)-1) + newPos = (x[index], y[index]) + + p1 = self.parentItem().mapToScene(QtCore.QPointF(x[i1], y[i1])) + p2 = self.parentItem().mapToScene(QtCore.QPointF(x[i2], y[i2])) + ang = np.arctan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians + self.resetTransform() + if self._rotate: + self.rotate(180+ ang * 180 / np.pi) ## takes degrees + QtGui.QGraphicsItem.setPos(self, *newPos) + return True + + def boundingRect(self): + return QtCore.QRectF() + + def paint(self, *args): + pass + + def makeAnimation(self, prop='position', start=0.0, end=1.0, duration=10000, loop=1): + anim = QtCore.QPropertyAnimation(self, prop) + anim.setDuration(duration) + anim.setStartValue(start) + anim.setEndValue(end) + anim.setLoopCount(loop) + return anim + + +class CurveArrow(CurvePoint): + """Provides an arrow that points to any specific sample on a PlotCurveItem. + Provides properties that can be animated.""" + + def __init__(self, curve, index=0, pos=None, **opts): + CurvePoint.__init__(self, curve, index=index, pos=pos) + if opts.get('pxMode', True): + opts['pxMode'] = False + self.setFlags(self.flags() | self.ItemIgnoresTransformations) + opts['angle'] = 0 + self.arrow = ArrowItem.ArrowItem(**opts) + self.arrow.setParentItem(self) + + def setStyle(self, **opts): + return self.arrow.setStyle(**opts) + diff --git a/papi/pyqtgraph/graphicsItems/ErrorBarItem.py b/papi/pyqtgraph/graphicsItems/ErrorBarItem.py new file mode 100644 index 00000000..986c5140 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ErrorBarItem.py @@ -0,0 +1,149 @@ +from ..Qt import QtGui, QtCore +from .GraphicsObject import GraphicsObject +from .. import getConfigOption +from .. import functions as fn + +__all__ = ['ErrorBarItem'] + +class ErrorBarItem(GraphicsObject): + def __init__(self, **opts): + """ + All keyword arguments are passed to setData(). + """ + GraphicsObject.__init__(self) + self.opts = dict( + x=None, + y=None, + height=None, + width=None, + top=None, + bottom=None, + left=None, + right=None, + beam=None, + pen=None + ) + self.setData(**opts) + + def setData(self, **opts): + """ + Update the data in the item. All arguments are optional. + + Valid keyword options are: + x, y, height, width, top, bottom, left, right, beam, pen + + * x and y must be numpy arrays specifying the coordinates of data points. + * height, width, top, bottom, left, right, and beam may be numpy arrays, + single values, or None to disable. All values should be positive. + * top, bottom, left, and right specify the lengths of bars extending + in each direction. + * If height is specified, it overrides top and bottom. + * If width is specified, it overrides left and right. + * beam specifies the width of the beam at the end of each bar. + * pen may be any single argument accepted by pg.mkPen(). + + This method was added in version 0.9.9. For prior versions, use setOpts. + """ + self.opts.update(opts) + self.path = None + self.update() + self.prepareGeometryChange() + self.informViewBoundsChanged() + + def setOpts(self, **opts): + # for backward compatibility + self.setData(**opts) + + def drawPath(self): + p = QtGui.QPainterPath() + + x, y = self.opts['x'], self.opts['y'] + if x is None or y is None: + return + + beam = self.opts['beam'] + + + height, top, bottom = self.opts['height'], self.opts['top'], self.opts['bottom'] + if height is not None or top is not None or bottom is not None: + ## draw vertical error bars + if height is not None: + y1 = y - height/2. + y2 = y + height/2. + else: + if bottom is None: + y1 = y + else: + y1 = y - bottom + if top is None: + y2 = y + else: + y2 = y + top + + for i in range(len(x)): + p.moveTo(x[i], y1[i]) + p.lineTo(x[i], y2[i]) + + if beam is not None and beam > 0: + x1 = x - beam/2. + x2 = x + beam/2. + if height is not None or top is not None: + for i in range(len(x)): + p.moveTo(x1[i], y2[i]) + p.lineTo(x2[i], y2[i]) + if height is not None or bottom is not None: + for i in range(len(x)): + p.moveTo(x1[i], y1[i]) + p.lineTo(x2[i], y1[i]) + + width, right, left = self.opts['width'], self.opts['right'], self.opts['left'] + if width is not None or right is not None or left is not None: + ## draw vertical error bars + if width is not None: + x1 = x - width/2. + x2 = x + width/2. + else: + if left is None: + x1 = x + else: + x1 = x - left + if right is None: + x2 = x + else: + x2 = x + right + + for i in range(len(x)): + p.moveTo(x1[i], y[i]) + p.lineTo(x2[i], y[i]) + + if beam is not None and beam > 0: + y1 = y - beam/2. + y2 = y + beam/2. + if width is not None or right is not None: + for i in range(len(x)): + p.moveTo(x2[i], y1[i]) + p.lineTo(x2[i], y2[i]) + if width is not None or left is not None: + for i in range(len(x)): + p.moveTo(x1[i], y1[i]) + p.lineTo(x1[i], y2[i]) + + self.path = p + self.prepareGeometryChange() + + + def paint(self, p, *args): + if self.path is None: + self.drawPath() + pen = self.opts['pen'] + if pen is None: + pen = getConfigOption('foreground') + p.setPen(fn.mkPen(pen)) + p.drawPath(self.path) + + def boundingRect(self): + if self.path is None: + self.drawPath() + return self.path.boundingRect() + + \ No newline at end of file diff --git a/papi/pyqtgraph/graphicsItems/FillBetweenItem.py b/papi/pyqtgraph/graphicsItems/FillBetweenItem.py new file mode 100644 index 00000000..15a14f86 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/FillBetweenItem.py @@ -0,0 +1,73 @@ +from ..Qt import QtGui +from .. import functions as fn +from .PlotDataItem import PlotDataItem +from .PlotCurveItem import PlotCurveItem + +class FillBetweenItem(QtGui.QGraphicsPathItem): + """ + GraphicsItem filling the space between two PlotDataItems. + """ + def __init__(self, curve1=None, curve2=None, brush=None): + QtGui.QGraphicsPathItem.__init__(self) + self.curves = None + if curve1 is not None and curve2 is not None: + self.setCurves(curve1, curve2) + elif curve1 is not None or curve2 is not None: + raise Exception("Must specify two curves to fill between.") + + if brush is not None: + self.setBrush(fn.mkBrush(brush)) + self.updatePath() + + def setCurves(self, curve1, curve2): + """Set the curves to fill between. + + Arguments must be instances of PlotDataItem or PlotCurveItem. + + Added in version 0.9.9 + """ + + if self.curves is not None: + for c in self.curves: + try: + c.sigPlotChanged.disconnect(self.curveChanged) + except (TypeError, RuntimeError): + pass + + curves = [curve1, curve2] + for c in curves: + if not isinstance(c, PlotDataItem) and not isinstance(c, PlotCurveItem): + raise TypeError("Curves must be PlotDataItem or PlotCurveItem.") + self.curves = curves + curve1.sigPlotChanged.connect(self.curveChanged) + curve2.sigPlotChanged.connect(self.curveChanged) + self.setZValue(min(curve1.zValue(), curve2.zValue())-1) + self.curveChanged() + + def setBrush(self, *args, **kwds): + """Change the fill brush. Acceps the same arguments as pg.mkBrush()""" + QtGui.QGraphicsPathItem.setBrush(self, fn.mkBrush(*args, **kwds)) + + def curveChanged(self): + self.updatePath() + + def updatePath(self): + if self.curves is None: + self.setPath(QtGui.QPainterPath()) + return + paths = [] + for c in self.curves: + if isinstance(c, PlotDataItem): + paths.append(c.curve.getPath()) + elif isinstance(c, PlotCurveItem): + paths.append(c.getPath()) + + path = QtGui.QPainterPath() + p1 = paths[0].toSubpathPolygons() + p2 = paths[1].toReversed().toSubpathPolygons() + if len(p1) == 0 or len(p2) == 0: + self.setPath(QtGui.QPainterPath()) + return + + path.addPolygon(p1[0] + p2[0]) + self.setPath(path) diff --git a/papi/pyqtgraph/graphicsItems/GradientEditorItem.py b/papi/pyqtgraph/graphicsItems/GradientEditorItem.py new file mode 100644 index 00000000..a151798a --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/GradientEditorItem.py @@ -0,0 +1,927 @@ +from ..Qt import QtGui, QtCore +from ..python2_3 import sortList +from .. import functions as fn +from .GraphicsObject import GraphicsObject +from .GraphicsWidget import GraphicsWidget +from ..widgets.SpinBox import SpinBox +import weakref +from ..pgcollections import OrderedDict +from ..colormap import ColorMap + +import numpy as np + +__all__ = ['TickSliderItem', 'GradientEditorItem'] + + +Gradients = OrderedDict([ + ('thermal', {'ticks': [(0.3333, (185, 0, 0, 255)), (0.6666, (255, 220, 0, 255)), (1, (255, 255, 255, 255)), (0, (0, 0, 0, 255))], 'mode': 'rgb'}), + ('flame', {'ticks': [(0.2, (7, 0, 220, 255)), (0.5, (236, 0, 134, 255)), (0.8, (246, 246, 0, 255)), (1.0, (255, 255, 255, 255)), (0.0, (0, 0, 0, 255))], 'mode': 'rgb'}), + ('yellowy', {'ticks': [(0.0, (0, 0, 0, 255)), (0.2328863796753704, (32, 0, 129, 255)), (0.8362738179251941, (255, 255, 0, 255)), (0.5257586450247, (115, 15, 255, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'} ), + ('bipolar', {'ticks': [(0.0, (0, 255, 255, 255)), (1.0, (255, 255, 0, 255)), (0.5, (0, 0, 0, 255)), (0.25, (0, 0, 255, 255)), (0.75, (255, 0, 0, 255))], 'mode': 'rgb'}), + ('spectrum', {'ticks': [(1.0, (255, 0, 255, 255)), (0.0, (255, 0, 0, 255))], 'mode': 'hsv'}), + ('cyclic', {'ticks': [(0.0, (255, 0, 4, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'hsv'}), + ('greyclip', {'ticks': [(0.0, (0, 0, 0, 255)), (0.99, (255, 255, 255, 255)), (1.0, (255, 0, 0, 255))], 'mode': 'rgb'}), + ('grey', {'ticks': [(0.0, (0, 0, 0, 255)), (1.0, (255, 255, 255, 255))], 'mode': 'rgb'}), +]) + + + + + +class TickSliderItem(GraphicsWidget): + ## public class + """**Bases:** :class:`GraphicsWidget ` + + A rectangular item with tick marks along its length that can (optionally) be moved by the user.""" + + def __init__(self, orientation='bottom', allowAdd=True, **kargs): + """ + ============== ================================================================================= + **Arguments:** + orientation Set the orientation of the gradient. Options are: 'left', 'right' + 'top', and 'bottom'. + allowAdd Specifies whether ticks can be added to the item by the user. + tickPen Default is white. Specifies the color of the outline of the ticks. + Can be any of the valid arguments for :func:`mkPen ` + ============== ================================================================================= + """ + ## public + GraphicsWidget.__init__(self) + self.orientation = orientation + self.length = 100 + self.tickSize = 15 + self.ticks = {} + self.maxDim = 20 + self.allowAdd = allowAdd + if 'tickPen' in kargs: + self.tickPen = fn.mkPen(kargs['tickPen']) + else: + self.tickPen = fn.mkPen('w') + + self.orientations = { + 'left': (90, 1, 1), + 'right': (90, 1, 1), + 'top': (0, 1, -1), + 'bottom': (0, 1, 1) + } + + self.setOrientation(orientation) + #self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) + #self.setBackgroundRole(QtGui.QPalette.NoRole) + #self.setMouseTracking(True) + + #def boundingRect(self): + #return self.mapRectFromParent(self.geometry()).normalized() + + #def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. + #p = QtGui.QPainterPath() + #p.addRect(self.boundingRect()) + #return p + + def paint(self, p, opt, widget): + #p.setPen(fn.mkPen('g', width=3)) + #p.drawRect(self.boundingRect()) + return + + def keyPressEvent(self, ev): + ev.ignore() + + def setMaxDim(self, mx=None): + if mx is None: + mx = self.maxDim + else: + self.maxDim = mx + + if self.orientation in ['bottom', 'top']: + self.setFixedHeight(mx) + self.setMaximumWidth(16777215) + else: + self.setFixedWidth(mx) + self.setMaximumHeight(16777215) + + + def setOrientation(self, orientation): + ## public + """Set the orientation of the TickSliderItem. + + ============== =================================================================== + **Arguments:** + orientation Options are: 'left', 'right', 'top', 'bottom' + The orientation option specifies which side of the slider the + ticks are on, as well as whether the slider is vertical ('right' + and 'left') or horizontal ('top' and 'bottom'). + ============== =================================================================== + """ + self.orientation = orientation + self.setMaxDim() + self.resetTransform() + ort = orientation + if ort == 'top': + self.scale(1, -1) + self.translate(0, -self.height()) + elif ort == 'left': + self.rotate(270) + self.scale(1, -1) + self.translate(-self.height(), -self.maxDim) + elif ort == 'right': + self.rotate(270) + self.translate(-self.height(), 0) + #self.setPos(0, -self.height()) + elif ort != 'bottom': + raise Exception("%s is not a valid orientation. Options are 'left', 'right', 'top', and 'bottom'" %str(ort)) + + self.translate(self.tickSize/2., 0) + + def addTick(self, x, color=None, movable=True): + ## public + """ + Add a tick to the item. + + ============== ================================================================== + **Arguments:** + x Position where tick should be added. + color Color of added tick. If color is not specified, the color will be + white. + movable Specifies whether the tick is movable with the mouse. + ============== ================================================================== + """ + + if color is None: + color = QtGui.QColor(255,255,255) + tick = Tick(self, [x*self.length, 0], color, movable, self.tickSize, pen=self.tickPen) + self.ticks[tick] = x + tick.setParentItem(self) + return tick + + def removeTick(self, tick): + ## public + """ + Removes the specified tick. + """ + del self.ticks[tick] + tick.setParentItem(None) + if self.scene() is not None: + self.scene().removeItem(tick) + + def tickMoved(self, tick, pos): + #print "tick changed" + ## Correct position of tick if it has left bounds. + newX = min(max(0, pos.x()), self.length) + pos.setX(newX) + tick.setPos(pos) + self.ticks[tick] = float(newX) / self.length + + def tickMoveFinished(self, tick): + pass + + def tickClicked(self, tick, ev): + if ev.button() == QtCore.Qt.RightButton: + self.removeTick(tick) + + def widgetLength(self): + if self.orientation in ['bottom', 'top']: + return self.width() + else: + return self.height() + + def resizeEvent(self, ev): + wlen = max(40, self.widgetLength()) + self.setLength(wlen-self.tickSize-2) + self.setOrientation(self.orientation) + #bounds = self.scene().itemsBoundingRect() + #bounds.setLeft(min(-self.tickSize*0.5, bounds.left())) + #bounds.setRight(max(self.length + self.tickSize, bounds.right())) + #self.setSceneRect(bounds) + #self.fitInView(bounds, QtCore.Qt.KeepAspectRatio) + + def setLength(self, newLen): + #private + for t, x in list(self.ticks.items()): + t.setPos(x * newLen + 1, t.pos().y()) + self.length = float(newLen) + + #def mousePressEvent(self, ev): + #QtGui.QGraphicsView.mousePressEvent(self, ev) + #self.ignoreRelease = False + #for i in self.items(ev.pos()): + #if isinstance(i, Tick): + #self.ignoreRelease = True + #break + ##if len(self.items(ev.pos())) > 0: ## Let items handle their own clicks + ##self.ignoreRelease = True + + #def mouseReleaseEvent(self, ev): + #QtGui.QGraphicsView.mouseReleaseEvent(self, ev) + #if self.ignoreRelease: + #return + + #pos = self.mapToScene(ev.pos()) + + #if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: + #if pos.x() < 0 or pos.x() > self.length: + #return + #if pos.y() < 0 or pos.y() > self.tickSize: + #return + #pos.setX(min(max(pos.x(), 0), self.length)) + #self.addTick(pos.x()/self.length) + #elif ev.button() == QtCore.Qt.RightButton: + #self.showMenu(ev) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton and self.allowAdd: + pos = ev.pos() + if pos.x() < 0 or pos.x() > self.length: + return + if pos.y() < 0 or pos.y() > self.tickSize: + return + pos.setX(min(max(pos.x(), 0), self.length)) + self.addTick(pos.x()/self.length) + elif ev.button() == QtCore.Qt.RightButton: + self.showMenu(ev) + + #if ev.button() == QtCore.Qt.RightButton: + #if self.moving: + #ev.accept() + #self.setPos(self.startPosition) + #self.moving = False + #self.sigMoving.emit(self) + #self.sigMoved.emit(self) + #else: + #pass + #self.view().tickClicked(self, ev) + ###remove + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptClicks(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.RightButton) + ## show ghost tick + #self.currentPen = fn.mkPen(255, 0,0) + #else: + #self.currentPen = self.pen + #self.update() + + def showMenu(self, ev): + pass + + def setTickColor(self, tick, color): + """Set the color of the specified tick. + + ============== ================================================================== + **Arguments:** + tick Can be either an integer corresponding to the index of the tick + or a Tick object. Ex: if you had a slider with 3 ticks and you + wanted to change the middle tick, the index would be 1. + color The color to make the tick. Can be any argument that is valid for + :func:`mkBrush ` + ============== ================================================================== + """ + tick = self.getTick(tick) + tick.color = color + tick.update() + #tick.setBrush(QtGui.QBrush(QtGui.QColor(tick.color))) + + def setTickValue(self, tick, val): + ## public + """ + Set the position (along the slider) of the tick. + + ============== ================================================================== + **Arguments:** + tick Can be either an integer corresponding to the index of the tick + or a Tick object. Ex: if you had a slider with 3 ticks and you + wanted to change the middle tick, the index would be 1. + val The desired position of the tick. If val is < 0, position will be + set to 0. If val is > 1, position will be set to 1. + ============== ================================================================== + """ + tick = self.getTick(tick) + val = min(max(0.0, val), 1.0) + x = val * self.length + pos = tick.pos() + pos.setX(x) + tick.setPos(pos) + self.ticks[tick] = val + self.updateGradient() + + def tickValue(self, tick): + ## public + """Return the value (from 0.0 to 1.0) of the specified tick. + + ============== ================================================================== + **Arguments:** + tick Can be either an integer corresponding to the index of the tick + or a Tick object. Ex: if you had a slider with 3 ticks and you + wanted the value of the middle tick, the index would be 1. + ============== ================================================================== + """ + tick = self.getTick(tick) + return self.ticks[tick] + + def getTick(self, tick): + ## public + """Return the Tick object at the specified index. + + ============== ================================================================== + **Arguments:** + tick An integer corresponding to the index of the desired tick. If the + argument is not an integer it will be returned unchanged. + ============== ================================================================== + """ + if type(tick) is int: + tick = self.listTicks()[tick][0] + return tick + + #def mouseMoveEvent(self, ev): + #QtGui.QGraphicsView.mouseMoveEvent(self, ev) + + def listTicks(self): + """Return a sorted list of all the Tick objects on the slider.""" + ## public + ticks = list(self.ticks.items()) + sortList(ticks, lambda a,b: cmp(a[1], b[1])) ## see pyqtgraph.python2_3.sortList + return ticks + + +class GradientEditorItem(TickSliderItem): + """ + **Bases:** :class:`TickSliderItem ` + + An item that can be used to define a color gradient. Implements common pre-defined gradients that are + customizable by the user. :class: `GradientWidget ` provides a widget + with a GradientEditorItem that can be added to a GUI. + + ================================ =========================================================== + **Signals:** + sigGradientChanged(self) Signal is emitted anytime the gradient changes. The signal + is emitted in real time while ticks are being dragged or + colors are being changed. + sigGradientChangeFinished(self) Signal is emitted when the gradient is finished changing. + ================================ =========================================================== + + """ + + sigGradientChanged = QtCore.Signal(object) + sigGradientChangeFinished = QtCore.Signal(object) + + def __init__(self, *args, **kargs): + """ + Create a new GradientEditorItem. + All arguments are passed to :func:`TickSliderItem.__init__ ` + + =============== ================================================================================= + **Arguments:** + orientation Set the orientation of the gradient. Options are: 'left', 'right' + 'top', and 'bottom'. + allowAdd Default is True. Specifies whether ticks can be added to the item. + tickPen Default is white. Specifies the color of the outline of the ticks. + Can be any of the valid arguments for :func:`mkPen ` + =============== ================================================================================= + """ + self.currentTick = None + self.currentTickColor = None + self.rectSize = 15 + self.gradRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, self.rectSize, 100, self.rectSize)) + self.backgroundRect = QtGui.QGraphicsRectItem(QtCore.QRectF(0, -self.rectSize, 100, self.rectSize)) + self.backgroundRect.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) + self.colorMode = 'rgb' + + TickSliderItem.__init__(self, *args, **kargs) + + self.colorDialog = QtGui.QColorDialog() + self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) + self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) + + self.colorDialog.currentColorChanged.connect(self.currentColorChanged) + self.colorDialog.rejected.connect(self.currentColorRejected) + self.colorDialog.accepted.connect(self.currentColorAccepted) + + self.backgroundRect.setParentItem(self) + self.gradRect.setParentItem(self) + + self.setMaxDim(self.rectSize + self.tickSize) + + self.rgbAction = QtGui.QAction('RGB', self) + self.rgbAction.setCheckable(True) + self.rgbAction.triggered.connect(lambda: self.setColorMode('rgb')) + self.hsvAction = QtGui.QAction('HSV', self) + self.hsvAction.setCheckable(True) + self.hsvAction.triggered.connect(lambda: self.setColorMode('hsv')) + + self.menu = QtGui.QMenu() + + ## build context menu of gradients + l = self.length + self.length = 100 + global Gradients + for g in Gradients: + px = QtGui.QPixmap(100, 15) + p = QtGui.QPainter(px) + self.restoreState(Gradients[g]) + grad = self.getGradient() + brush = QtGui.QBrush(grad) + p.fillRect(QtCore.QRect(0, 0, 100, 15), brush) + p.end() + label = QtGui.QLabel() + label.setPixmap(px) + label.setContentsMargins(1, 1, 1, 1) + act = QtGui.QWidgetAction(self) + act.setDefaultWidget(label) + act.triggered.connect(self.contextMenuClicked) + act.name = g + self.menu.addAction(act) + self.length = l + self.menu.addSeparator() + self.menu.addAction(self.rgbAction) + self.menu.addAction(self.hsvAction) + + + for t in list(self.ticks.keys()): + self.removeTick(t) + self.addTick(0, QtGui.QColor(0,0,0), True) + self.addTick(1, QtGui.QColor(255,0,0), True) + self.setColorMode('rgb') + self.updateGradient() + + def setOrientation(self, orientation): + ## public + """ + Set the orientation of the GradientEditorItem. + + ============== =================================================================== + **Arguments:** + orientation Options are: 'left', 'right', 'top', 'bottom' + The orientation option specifies which side of the gradient the + ticks are on, as well as whether the gradient is vertical ('right' + and 'left') or horizontal ('top' and 'bottom'). + ============== =================================================================== + """ + TickSliderItem.setOrientation(self, orientation) + self.translate(0, self.rectSize) + + def showMenu(self, ev): + #private + self.menu.popup(ev.screenPos().toQPoint()) + + def contextMenuClicked(self, b=None): + #private + #global Gradients + act = self.sender() + self.loadPreset(act.name) + + def loadPreset(self, name): + """ + Load a predefined gradient. + + """ ## TODO: provide image with names of defined gradients + #global Gradients + self.restoreState(Gradients[name]) + + def setColorMode(self, cm): + """ + Set the color mode for the gradient. Options are: 'hsv', 'rgb' + + """ + + ## public + if cm not in ['rgb', 'hsv']: + raise Exception("Unknown color mode %s. Options are 'rgb' and 'hsv'." % str(cm)) + + try: + self.rgbAction.blockSignals(True) + self.hsvAction.blockSignals(True) + self.rgbAction.setChecked(cm == 'rgb') + self.hsvAction.setChecked(cm == 'hsv') + finally: + self.rgbAction.blockSignals(False) + self.hsvAction.blockSignals(False) + self.colorMode = cm + self.updateGradient() + + def colorMap(self): + """Return a ColorMap object representing the current state of the editor.""" + if self.colorMode == 'hsv': + raise NotImplementedError('hsv colormaps not yet supported') + pos = [] + color = [] + for t,x in self.listTicks(): + pos.append(x) + c = t.color + color.append([c.red(), c.green(), c.blue(), c.alpha()]) + return ColorMap(np.array(pos), np.array(color, dtype=np.ubyte)) + + def updateGradient(self): + #private + self.gradient = self.getGradient() + self.gradRect.setBrush(QtGui.QBrush(self.gradient)) + self.sigGradientChanged.emit(self) + + def setLength(self, newLen): + #private (but maybe public) + TickSliderItem.setLength(self, newLen) + self.backgroundRect.setRect(1, -self.rectSize, newLen, self.rectSize) + self.gradRect.setRect(1, -self.rectSize, newLen, self.rectSize) + self.updateGradient() + + def currentColorChanged(self, color): + #private + if color.isValid() and self.currentTick is not None: + self.setTickColor(self.currentTick, color) + self.updateGradient() + + def currentColorRejected(self): + #private + self.setTickColor(self.currentTick, self.currentTickColor) + self.updateGradient() + + def currentColorAccepted(self): + self.sigGradientChangeFinished.emit(self) + + def tickClicked(self, tick, ev): + #private + if ev.button() == QtCore.Qt.LeftButton: + self.raiseColorDialog(tick) + elif ev.button() == QtCore.Qt.RightButton: + self.raiseTickContextMenu(tick, ev) + + def raiseColorDialog(self, tick): + if not tick.colorChangeAllowed: + return + self.currentTick = tick + self.currentTickColor = tick.color + self.colorDialog.setCurrentColor(tick.color) + self.colorDialog.open() + + def raiseTickContextMenu(self, tick, ev): + self.tickMenu = TickMenu(tick, self) + self.tickMenu.popup(ev.screenPos().toQPoint()) + + def tickMoved(self, tick, pos): + #private + TickSliderItem.tickMoved(self, tick, pos) + self.updateGradient() + + def tickMoveFinished(self, tick): + self.sigGradientChangeFinished.emit(self) + + + def getGradient(self): + """Return a QLinearGradient object.""" + g = QtGui.QLinearGradient(QtCore.QPointF(0,0), QtCore.QPointF(self.length,0)) + if self.colorMode == 'rgb': + ticks = self.listTicks() + g.setStops([(x, QtGui.QColor(t.color)) for t,x in ticks]) + elif self.colorMode == 'hsv': ## HSV mode is approximated for display by interpolating 10 points between each stop + ticks = self.listTicks() + stops = [] + stops.append((ticks[0][1], ticks[0][0].color)) + for i in range(1,len(ticks)): + x1 = ticks[i-1][1] + x2 = ticks[i][1] + dx = (x2-x1) / 10. + for j in range(1,10): + x = x1 + dx*j + stops.append((x, self.getColor(x))) + stops.append((x2, self.getColor(x2))) + g.setStops(stops) + return g + + def getColor(self, x, toQColor=True): + """ + Return a color for a given value. + + ============== ================================================================== + **Arguments:** + x Value (position on gradient) of requested color. + toQColor If true, returns a QColor object, else returns a (r,g,b,a) tuple. + ============== ================================================================== + """ + ticks = self.listTicks() + if x <= ticks[0][1]: + c = ticks[0][0].color + if toQColor: + return QtGui.QColor(c) # always copy colors before handing them out + else: + return (c.red(), c.green(), c.blue(), c.alpha()) + if x >= ticks[-1][1]: + c = ticks[-1][0].color + if toQColor: + return QtGui.QColor(c) # always copy colors before handing them out + else: + return (c.red(), c.green(), c.blue(), c.alpha()) + + x2 = ticks[0][1] + for i in range(1,len(ticks)): + x1 = x2 + x2 = ticks[i][1] + if x1 <= x and x2 >= x: + break + + dx = (x2-x1) + if dx == 0: + f = 0. + else: + f = (x-x1) / dx + c1 = ticks[i-1][0].color + c2 = ticks[i][0].color + if self.colorMode == 'rgb': + r = c1.red() * (1.-f) + c2.red() * f + g = c1.green() * (1.-f) + c2.green() * f + b = c1.blue() * (1.-f) + c2.blue() * f + a = c1.alpha() * (1.-f) + c2.alpha() * f + if toQColor: + return QtGui.QColor(int(r), int(g), int(b), int(a)) + else: + return (r,g,b,a) + elif self.colorMode == 'hsv': + h1,s1,v1,_ = c1.getHsv() + h2,s2,v2,_ = c2.getHsv() + h = h1 * (1.-f) + h2 * f + s = s1 * (1.-f) + s2 * f + v = v1 * (1.-f) + v2 * f + c = QtGui.QColor() + c.setHsv(h,s,v) + if toQColor: + return c + else: + return (c.red(), c.green(), c.blue(), c.alpha()) + + def getLookupTable(self, nPts, alpha=None): + """ + Return an RGB(A) lookup table (ndarray). + + ============== ============================================================================ + **Arguments:** + nPts The number of points in the returned lookup table. + alpha True, False, or None - Specifies whether or not alpha values are included + in the table.If alpha is None, alpha will be automatically determined. + ============== ============================================================================ + """ + if alpha is None: + alpha = self.usesAlpha() + if alpha: + table = np.empty((nPts,4), dtype=np.ubyte) + else: + table = np.empty((nPts,3), dtype=np.ubyte) + + for i in range(nPts): + x = float(i)/(nPts-1) + color = self.getColor(x, toQColor=False) + table[i] = color[:table.shape[1]] + + return table + + def usesAlpha(self): + """Return True if any ticks have an alpha < 255""" + + ticks = self.listTicks() + for t in ticks: + if t[0].color.alpha() < 255: + return True + + return False + + def isLookupTrivial(self): + """Return True if the gradient has exactly two stops in it: black at 0.0 and white at 1.0""" + ticks = self.listTicks() + if len(ticks) != 2: + return False + if ticks[0][1] != 0.0 or ticks[1][1] != 1.0: + return False + c1 = fn.colorTuple(ticks[0][0].color) + c2 = fn.colorTuple(ticks[1][0].color) + if c1 != (0,0,0,255) or c2 != (255,255,255,255): + return False + return True + + + def mouseReleaseEvent(self, ev): + #private + TickSliderItem.mouseReleaseEvent(self, ev) + self.updateGradient() + + def addTick(self, x, color=None, movable=True, finish=True): + """ + Add a tick to the gradient. Return the tick. + + ============== ================================================================== + **Arguments:** + x Position where tick should be added. + color Color of added tick. If color is not specified, the color will be + the color of the gradient at the specified position. + movable Specifies whether the tick is movable with the mouse. + ============== ================================================================== + """ + + + if color is None: + color = self.getColor(x) + t = TickSliderItem.addTick(self, x, color=color, movable=movable) + t.colorChangeAllowed = True + t.removeAllowed = True + + if finish: + self.sigGradientChangeFinished.emit(self) + return t + + + def removeTick(self, tick, finish=True): + TickSliderItem.removeTick(self, tick) + if finish: + self.updateGradient() + self.sigGradientChangeFinished.emit(self) + + + def saveState(self): + """ + Return a dictionary with parameters for rebuilding the gradient. Keys will include: + + - 'mode': hsv or rgb + - 'ticks': a list of tuples (pos, (r,g,b,a)) + """ + ## public + ticks = [] + for t in self.ticks: + c = t.color + ticks.append((self.ticks[t], (c.red(), c.green(), c.blue(), c.alpha()))) + state = {'mode': self.colorMode, 'ticks': ticks} + return state + + def restoreState(self, state): + """ + Restore the gradient specified in state. + + ============== ==================================================================== + **Arguments:** + state A dictionary with same structure as those returned by + :func:`saveState ` + + Keys must include: + + - 'mode': hsv or rgb + - 'ticks': a list of tuples (pos, (r,g,b,a)) + ============== ==================================================================== + """ + ## public + self.setColorMode(state['mode']) + for t in list(self.ticks.keys()): + self.removeTick(t, finish=False) + for t in state['ticks']: + c = QtGui.QColor(*t[1]) + self.addTick(t[0], c, finish=False) + self.updateGradient() + self.sigGradientChangeFinished.emit(self) + + def setColorMap(self, cm): + self.setColorMode('rgb') + for t in list(self.ticks.keys()): + self.removeTick(t, finish=False) + colors = cm.getColors(mode='qcolor') + for i in range(len(cm.pos)): + x = cm.pos[i] + c = colors[i] + self.addTick(x, c, finish=False) + self.updateGradient() + self.sigGradientChangeFinished.emit(self) + + +class Tick(QtGui.QGraphicsObject): ## NOTE: Making this a subclass of GraphicsObject instead results in + ## activating this bug: https://bugreports.qt-project.org/browse/PYSIDE-86 + ## private class + + sigMoving = QtCore.Signal(object) + sigMoved = QtCore.Signal(object) + + def __init__(self, view, pos, color, movable=True, scale=10, pen='w'): + self.movable = movable + self.moving = False + self.view = weakref.ref(view) + self.scale = scale + self.color = color + self.pen = fn.mkPen(pen) + self.hoverPen = fn.mkPen(255,255,0) + self.currentPen = self.pen + self.pg = QtGui.QPainterPath(QtCore.QPointF(0,0)) + self.pg.lineTo(QtCore.QPointF(-scale/3**0.5, scale)) + self.pg.lineTo(QtCore.QPointF(scale/3**0.5, scale)) + self.pg.closeSubpath() + + QtGui.QGraphicsObject.__init__(self) + self.setPos(pos[0], pos[1]) + if self.movable: + self.setZValue(1) + else: + self.setZValue(0) + + def boundingRect(self): + return self.pg.boundingRect() + + def shape(self): + return self.pg + + def paint(self, p, *args): + p.setRenderHints(QtGui.QPainter.Antialiasing) + p.fillPath(self.pg, fn.mkBrush(self.color)) + + p.setPen(self.currentPen) + p.drawPath(self.pg) + + + def mouseDragEvent(self, ev): + if self.movable and ev.button() == QtCore.Qt.LeftButton: + if ev.isStart(): + self.moving = True + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + self.startPosition = self.pos() + ev.accept() + + if not self.moving: + return + + newPos = self.cursorOffset + self.mapToParent(ev.pos()) + newPos.setY(self.pos().y()) + + self.setPos(newPos) + self.view().tickMoved(self, newPos) + self.sigMoving.emit(self) + if ev.isFinish(): + self.moving = False + self.sigMoved.emit(self) + self.view().tickMoveFinished(self) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton and self.moving: + ev.accept() + self.setPos(self.startPosition) + self.view().tickMoved(self, self.startPosition) + self.moving = False + self.sigMoving.emit(self) + self.sigMoved.emit(self) + else: + self.view().tickClicked(self, ev) + ##remove + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.LeftButton) + ev.acceptClicks(QtCore.Qt.RightButton) + self.currentPen = self.hoverPen + else: + self.currentPen = self.pen + self.update() + + +class TickMenu(QtGui.QMenu): + + def __init__(self, tick, sliderItem): + QtGui.QMenu.__init__(self) + + self.tick = weakref.ref(tick) + self.sliderItem = weakref.ref(sliderItem) + + self.removeAct = self.addAction("Remove Tick", lambda: self.sliderItem().removeTick(tick)) + if (not self.tick().removeAllowed) or len(self.sliderItem().ticks) < 3: + self.removeAct.setEnabled(False) + + positionMenu = self.addMenu("Set Position") + w = QtGui.QWidget() + l = QtGui.QGridLayout() + w.setLayout(l) + + value = sliderItem.tickValue(tick) + self.fracPosSpin = SpinBox() + self.fracPosSpin.setOpts(value=value, bounds=(0.0, 1.0), step=0.01, decimals=2) + #self.dataPosSpin = SpinBox(value=dataVal) + #self.dataPosSpin.setOpts(decimals=3, siPrefix=True) + + l.addWidget(QtGui.QLabel("Position:"), 0,0) + l.addWidget(self.fracPosSpin, 0, 1) + #l.addWidget(QtGui.QLabel("Position (data units):"), 1, 0) + #l.addWidget(self.dataPosSpin, 1,1) + + #if self.sliderItem().dataParent is None: + # self.dataPosSpin.setEnabled(False) + + a = QtGui.QWidgetAction(self) + a.setDefaultWidget(w) + positionMenu.addAction(a) + + self.fracPosSpin.sigValueChanging.connect(self.fractionalValueChanged) + #self.dataPosSpin.valueChanged.connect(self.dataValueChanged) + + colorAct = self.addAction("Set Color", lambda: self.sliderItem().raiseColorDialog(self.tick())) + if not self.tick().colorChangeAllowed: + colorAct.setEnabled(False) + + def fractionalValueChanged(self, x): + self.sliderItem().setTickValue(self.tick(), self.fracPosSpin.value()) + #if self.sliderItem().dataParent is not None: + # self.dataPosSpin.blockSignals(True) + # self.dataPosSpin.setValue(self.sliderItem().tickDataValue(self.tick())) + # self.dataPosSpin.blockSignals(False) + + #def dataValueChanged(self, val): + # self.sliderItem().setTickValue(self.tick(), val, dataUnits=True) + # self.fracPosSpin.blockSignals(True) + # self.fracPosSpin.setValue(self.sliderItem().tickValue(self.tick())) + # self.fracPosSpin.blockSignals(False) + diff --git a/papi/pyqtgraph/graphicsItems/GradientLegend.py b/papi/pyqtgraph/graphicsItems/GradientLegend.py new file mode 100644 index 00000000..28c2cd63 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/GradientLegend.py @@ -0,0 +1,114 @@ +from ..Qt import QtGui, QtCore +from .UIGraphicsItem import * +from .. import functions as fn + +__all__ = ['GradientLegend'] + +class GradientLegend(UIGraphicsItem): + """ + Draws a color gradient rectangle along with text labels denoting the value at specific + points along the gradient. + """ + + def __init__(self, size, offset): + self.size = size + self.offset = offset + UIGraphicsItem.__init__(self) + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + self.brush = QtGui.QBrush(QtGui.QColor(200,0,0)) + self.pen = QtGui.QPen(QtGui.QColor(0,0,0)) + self.labels = {'max': 1, 'min': 0} + self.gradient = QtGui.QLinearGradient() + self.gradient.setColorAt(0, QtGui.QColor(0,0,0)) + self.gradient.setColorAt(1, QtGui.QColor(255,0,0)) + + def setGradient(self, g): + self.gradient = g + self.update() + + def setIntColorScale(self, minVal, maxVal, *args, **kargs): + colors = [fn.intColor(i, maxVal-minVal, *args, **kargs) for i in range(minVal, maxVal)] + g = QtGui.QLinearGradient() + for i in range(len(colors)): + x = float(i)/len(colors) + g.setColorAt(x, colors[i]) + self.setGradient(g) + if 'labels' not in kargs: + self.setLabels({str(minVal/10.): 0, str(maxVal): 1}) + else: + self.setLabels({kargs['labels'][0]:0, kargs['labels'][1]:1}) + + def setLabels(self, l): + """Defines labels to appear next to the color scale. Accepts a dict of {text: value} pairs""" + self.labels = l + self.update() + + def paint(self, p, opt, widget): + UIGraphicsItem.paint(self, p, opt, widget) + rect = self.boundingRect() ## Boundaries of visible area in scene coords. + unit = self.pixelSize() ## Size of one view pixel in scene coords. + if unit[0] is None: + return + + ## determine max width of all labels + labelWidth = 0 + labelHeight = 0 + for k in self.labels: + b = p.boundingRect(QtCore.QRectF(0, 0, 0, 0), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) + labelWidth = max(labelWidth, b.width()) + labelHeight = max(labelHeight, b.height()) + + labelWidth *= unit[0] + labelHeight *= unit[1] + + textPadding = 2 # in px + + if self.offset[0] < 0: + x3 = rect.right() + unit[0] * self.offset[0] + x2 = x3 - labelWidth - unit[0]*textPadding*2 + x1 = x2 - unit[0] * self.size[0] + else: + x1 = rect.left() + unit[0] * self.offset[0] + x2 = x1 + unit[0] * self.size[0] + x3 = x2 + labelWidth + unit[0]*textPadding*2 + if self.offset[1] < 0: + y2 = rect.top() - unit[1] * self.offset[1] + y1 = y2 + unit[1] * self.size[1] + else: + y1 = rect.bottom() - unit[1] * self.offset[1] + y2 = y1 - unit[1] * self.size[1] + self.b = [x1,x2,x3,y1,y2,labelWidth] + + ## Draw background + p.setPen(self.pen) + p.setBrush(QtGui.QBrush(QtGui.QColor(255,255,255,100))) + rect = QtCore.QRectF( + QtCore.QPointF(x1 - unit[0]*textPadding, y1 + labelHeight/2 + unit[1]*textPadding), + QtCore.QPointF(x3, y2 - labelHeight/2 - unit[1]*textPadding) + ) + p.drawRect(rect) + + + ## Have to scale painter so that text and gradients are correct size. Bleh. + p.scale(unit[0], unit[1]) + + ## Draw color bar + self.gradient.setStart(0, y1/unit[1]) + self.gradient.setFinalStop(0, y2/unit[1]) + p.setBrush(self.gradient) + rect = QtCore.QRectF( + QtCore.QPointF(x1/unit[0], y1/unit[1]), + QtCore.QPointF(x2/unit[0], y2/unit[1]) + ) + p.drawRect(rect) + + + ## draw labels + p.setPen(QtGui.QPen(QtGui.QColor(0,0,0))) + tx = x2 + unit[0]*textPadding + lh = labelHeight/unit[1] + for k in self.labels: + y = y1 + self.labels[k] * (y2-y1) + p.drawText(QtCore.QRectF(tx/unit[0], y/unit[1] - lh/2.0, 1000, lh), QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter, str(k)) + + diff --git a/papi/pyqtgraph/graphicsItems/GraphItem.py b/papi/pyqtgraph/graphicsItems/GraphItem.py new file mode 100644 index 00000000..c80138fb --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/GraphItem.py @@ -0,0 +1,147 @@ +from .. import functions as fn +from .GraphicsObject import GraphicsObject +from .ScatterPlotItem import ScatterPlotItem +from ..Qt import QtGui, QtCore +import numpy as np +from .. import getConfigOption + +__all__ = ['GraphItem'] + + +class GraphItem(GraphicsObject): + """A GraphItem displays graph information as + a set of nodes connected by lines (as in 'graph theory', not 'graphics'). + Useful for drawing networks, trees, etc. + """ + + def __init__(self, **kwds): + GraphicsObject.__init__(self) + self.scatter = ScatterPlotItem() + self.scatter.setParentItem(self) + self.adjacency = None + self.pos = None + self.picture = None + self.pen = 'default' + self.setData(**kwds) + + def setData(self, **kwds): + """ + Change the data displayed by the graph. + + ============== ======================================================================= + **Arguments:** + pos (N,2) array of the positions of each node in the graph. + adj (M,2) array of connection data. Each row contains indexes + of two nodes that are connected. + pen The pen to use when drawing lines between connected + nodes. May be one of: + + * QPen + * a single argument to pass to pg.mkPen + * a record array of length M + with fields (red, green, blue, alpha, width). Note + that using this option may have a significant performance + cost. + * None (to disable connection drawing) + * 'default' to use the default foreground color. + + symbolPen The pen(s) used for drawing nodes. + symbolBrush The brush(es) used for drawing nodes. + ``**opts`` All other keyword arguments are given to + :func:`ScatterPlotItem.setData() ` + to affect the appearance of nodes (symbol, size, brush, + etc.) + ============== ======================================================================= + """ + if 'adj' in kwds: + self.adjacency = kwds.pop('adj') + if self.adjacency.dtype.kind not in 'iu': + raise Exception("adjacency array must have int or unsigned type.") + self._update() + if 'pos' in kwds: + self.pos = kwds['pos'] + self._update() + if 'pen' in kwds: + self.setPen(kwds.pop('pen')) + self._update() + + if 'symbolPen' in kwds: + kwds['pen'] = kwds.pop('symbolPen') + if 'symbolBrush' in kwds: + kwds['brush'] = kwds.pop('symbolBrush') + self.scatter.setData(**kwds) + self.informViewBoundsChanged() + + def _update(self): + self.picture = None + self.prepareGeometryChange() + self.update() + + def setPen(self, *args, **kwargs): + """ + Set the pen used to draw graph lines. + May be: + + * None to disable line drawing + * Record array with fields (red, green, blue, alpha, width) + * Any set of arguments and keyword arguments accepted by + :func:`mkPen `. + * 'default' to use the default foreground color. + """ + if len(args) == 1 and len(kwargs) == 0: + self.pen = args[0] + else: + self.pen = fn.mkPen(*args, **kwargs) + self.picture = None + self.update() + + def generatePicture(self): + self.picture = QtGui.QPicture() + if self.pen is None or self.pos is None or self.adjacency is None: + return + + p = QtGui.QPainter(self.picture) + try: + pts = self.pos[self.adjacency] + pen = self.pen + if isinstance(pen, np.ndarray): + lastPen = None + for i in range(pts.shape[0]): + pen = self.pen[i] + if np.any(pen != lastPen): + lastPen = pen + if pen.dtype.fields is None: + p.setPen(fn.mkPen(color=(pen[0], pen[1], pen[2], pen[3]), width=1)) + else: + p.setPen(fn.mkPen(color=(pen['red'], pen['green'], pen['blue'], pen['alpha']), width=pen['width'])) + p.drawLine(QtCore.QPointF(*pts[i][0]), QtCore.QPointF(*pts[i][1])) + else: + if pen == 'default': + pen = getConfigOption('foreground') + p.setPen(fn.mkPen(pen)) + pts = pts.reshape((pts.shape[0]*pts.shape[1], pts.shape[2])) + path = fn.arrayToQPath(x=pts[:,0], y=pts[:,1], connect='pairs') + p.drawPath(path) + finally: + p.end() + + def paint(self, p, *args): + if self.picture == None: + self.generatePicture() + if getConfigOption('antialias') is True: + p.setRenderHint(p.Antialiasing) + self.picture.play(p) + + def boundingRect(self): + return self.scatter.boundingRect() + + def dataBounds(self, *args, **kwds): + return self.scatter.dataBounds(*args, **kwds) + + def pixelPadding(self): + return self.scatter.pixelPadding() + + + + + diff --git a/papi/pyqtgraph/graphicsItems/GraphicsItem.py b/papi/pyqtgraph/graphicsItems/GraphicsItem.py new file mode 100644 index 00000000..2ca35193 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/GraphicsItem.py @@ -0,0 +1,585 @@ +from ..Qt import QtGui, QtCore, isQObjectAlive +from ..GraphicsScene import GraphicsScene +from ..Point import Point +from .. import functions as fn +import weakref +import operator +from ..util.lru_cache import LRUCache + + +class GraphicsItem(object): + """ + **Bases:** :class:`object` + + Abstract class providing useful methods to GraphicsObject and GraphicsWidget. + (This is required because we cannot have multiple inheritance with QObject subclasses.) + + A note about Qt's GraphicsView framework: + + The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task. + """ + _pixelVectorGlobalCache = LRUCache(100, 70) + + def __init__(self, register=True): + if not hasattr(self, '_qtBaseClass'): + for b in self.__class__.__bases__: + if issubclass(b, QtGui.QGraphicsItem): + self.__class__._qtBaseClass = b + break + if not hasattr(self, '_qtBaseClass'): + raise Exception('Could not determine Qt base class for GraphicsItem: %s' % str(self)) + + self._pixelVectorCache = [None, None] + self._viewWidget = None + self._viewBox = None + self._connectedView = None + self._exportOpts = False ## If False, not currently exporting. Otherwise, contains dict of export options. + if register: + GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() + + + + + def getViewWidget(self): + """ + Return the view widget for this item. + + If the scene has multiple views, only the first view is returned. + The return value is cached; clear the cached value with forgetViewWidget(). + If the view has been deleted by Qt, return None. + """ + if self._viewWidget is None: + scene = self.scene() + if scene is None: + return None + views = scene.views() + if len(views) < 1: + return None + self._viewWidget = weakref.ref(self.scene().views()[0]) + + v = self._viewWidget() + if v is not None and not isQObjectAlive(v): + return None + + return v + + def forgetViewWidget(self): + self._viewWidget = None + + def getViewBox(self): + """ + Return the first ViewBox or GraphicsView which bounds this item's visible space. + If this item is not contained within a ViewBox, then the GraphicsView is returned. + If the item is contained inside nested ViewBoxes, then the inner-most ViewBox is returned. + The result is cached; clear the cache with forgetViewBox() + """ + if self._viewBox is None: + p = self + while True: + try: + p = p.parentItem() + except RuntimeError: ## sometimes happens as items are being removed from a scene and collected. + return None + if p is None: + vb = self.getViewWidget() + if vb is None: + return None + else: + self._viewBox = weakref.ref(vb) + break + if hasattr(p, 'implements') and p.implements('ViewBox'): + self._viewBox = weakref.ref(p) + break + return self._viewBox() ## If we made it this far, _viewBox is definitely not None + + def forgetViewBox(self): + self._viewBox = None + + + def deviceTransform(self, viewportTransform=None): + """ + Return the transform that converts local item coordinates to device coordinates (usually pixels). + Extends deviceTransform to automatically determine the viewportTransform. + """ + if self._exportOpts is not False and 'painter' in self._exportOpts: ## currently exporting; device transform may be different. + return self._exportOpts['painter'].deviceTransform() * self.sceneTransform() + + if viewportTransform is None: + view = self.getViewWidget() + if view is None: + return None + viewportTransform = view.viewportTransform() + dt = self._qtBaseClass.deviceTransform(self, viewportTransform) + + #xmag = abs(dt.m11())+abs(dt.m12()) + #ymag = abs(dt.m21())+abs(dt.m22()) + #if xmag * ymag == 0: + if dt.determinant() == 0: ## occurs when deviceTransform is invalid because widget has not been displayed + return None + else: + return dt + + def viewTransform(self): + """Return the transform that maps from local coordinates to the item's ViewBox coordinates + If there is no ViewBox, return the scene transform. + Returns None if the item does not have a view.""" + view = self.getViewBox() + if view is None: + return None + if hasattr(view, 'implements') and view.implements('ViewBox'): + tr = self.itemTransform(view.innerSceneItem()) + if isinstance(tr, tuple): + tr = tr[0] ## difference between pyside and pyqt + return tr + else: + return self.sceneTransform() + #return self.deviceTransform(view.viewportTransform()) + + + + def getBoundingParents(self): + """Return a list of parents to this item that have child clipping enabled.""" + p = self + parents = [] + while True: + p = p.parentItem() + if p is None: + break + if p.flags() & self.ItemClipsChildrenToShape: + parents.append(p) + return parents + + def viewRect(self): + """Return the bounds (in item coordinates) of this item's ViewBox or GraphicsWidget""" + view = self.getViewBox() + if view is None: + return None + bounds = self.mapRectFromView(view.viewRect()) + if bounds is None: + return None + + bounds = bounds.normalized() + + ## nah. + #for p in self.getBoundingParents(): + #bounds &= self.mapRectFromScene(p.sceneBoundingRect()) + + return bounds + + + + def pixelVectors(self, direction=None): + """Return vectors in local coordinates representing the width and height of a view pixel. + If direction is specified, then return vectors parallel and orthogonal to it. + + Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed) + or if pixel size is below floating-point precision limit. + """ + + ## This is an expensive function that gets called very frequently. + ## We have two levels of cache to try speeding things up. + + dt = self.deviceTransform() + if dt is None: + return None, None + + ## Ignore translation. If the translation is much larger than the scale + ## (such as when looking at unix timestamps), we can get floating-point errors. + dt.setMatrix(dt.m11(), dt.m12(), 0, dt.m21(), dt.m22(), 0, 0, 0, 1) + + ## check local cache + if direction is None and dt == self._pixelVectorCache[0]: + return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy* + + ## check global cache + #key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32()) + key = (dt.m11(), dt.m21(), dt.m12(), dt.m22()) + pv = self._pixelVectorGlobalCache.get(key, None) + if direction is None and pv is not None: + self._pixelVectorCache = [dt, pv] + return tuple(map(Point,pv)) ## return a *copy* + + + if direction is None: + direction = QtCore.QPointF(1, 0) + if direction.manhattanLength() == 0: + raise Exception("Cannot compute pixel length for 0-length vector.") + + ## attempt to re-scale direction vector to fit within the precision of the coordinate system + ## Here's the problem: we need to map the vector 'direction' from the item to the device, via transform 'dt'. + ## In some extreme cases, this mapping can fail unless the length of 'direction' is cleverly chosen. + ## Example: + ## dt = [ 1, 0, 2 + ## 0, 2, 1e20 + ## 0, 0, 1 ] + ## Then we map the origin (0,0) and direction (0,1) and get: + ## o' = 2,1e20 + ## d' = 2,1e20 <-- should be 1e20+2, but this can't be represented with a 32-bit float + ## + ## |o' - d'| == 0 <-- this is the problem. + + ## Perhaps the easiest solution is to exclude the transformation column from dt. Does this cause any other problems? + + #if direction.x() == 0: + #r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22())) + ##r = 1.0/(abs(dt.m12()) + abs(dt.m22())) + #elif direction.y() == 0: + #r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21())) + ##r = 1.0/(abs(dt.m11()) + abs(dt.m21())) + #else: + #r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5 + #if r == 0: + #r = 1. ## shouldn't need to do this; probably means the math above is wrong? + #directionr = direction * r + directionr = direction + + ## map direction vector onto device + #viewDir = Point(dt.map(directionr) - dt.map(Point(0,0))) + #mdirection = dt.map(directionr) + dirLine = QtCore.QLineF(QtCore.QPointF(0,0), directionr) + viewDir = dt.map(dirLine) + if viewDir.length() == 0: + return None, None ## pixel size cannot be represented on this scale + + ## get unit vector and orthogonal vector (length of pixel) + #orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space + try: + normView = viewDir.unitVector() + #normView = viewDir.norm() ## direction of one pixel orthogonal to line + normOrtho = normView.normalVector() + #normOrtho = orthoDir.norm() + except: + raise Exception("Invalid direction %s" %directionr) + + ## map back to item + dti = fn.invertQTransform(dt) + #pv = Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0))) + pv = Point(dti.map(normView).p2()), Point(dti.map(normOrtho).p2()) + self._pixelVectorCache[1] = pv + self._pixelVectorCache[0] = dt + self._pixelVectorGlobalCache[key] = pv + return self._pixelVectorCache[1] + + + def pixelLength(self, direction, ortho=False): + """Return the length of one pixel in the direction indicated (in local coordinates) + If ortho=True, then return the length of one pixel orthogonal to the direction indicated. + + Return None if pixel size is not yet defined (usually because the item has not yet been displayed). + """ + normV, orthoV = self.pixelVectors(direction) + if normV == None or orthoV == None: + return None + if ortho: + return orthoV.length() + return normV.length() + + + def pixelSize(self): + ## deprecated + v = self.pixelVectors() + if v == (None, None): + return None, None + return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5 + + def pixelWidth(self): + ## deprecated + vt = self.deviceTransform() + if vt is None: + return 0 + vt = fn.invertQTransform(vt) + return vt.map(QtCore.QLineF(0, 0, 1, 0)).length() + + def pixelHeight(self): + ## deprecated + vt = self.deviceTransform() + if vt is None: + return 0 + vt = fn.invertQTransform(vt) + return vt.map(QtCore.QLineF(0, 0, 0, 1)).length() + #return Point(vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).length() + + + def mapToDevice(self, obj): + """ + Return *obj* mapped from local coordinates to device coordinates (pixels). + If there is no device mapping available, return None. + """ + vt = self.deviceTransform() + if vt is None: + return None + return vt.map(obj) + + def mapFromDevice(self, obj): + """ + Return *obj* mapped from device coordinates (pixels) to local coordinates. + If there is no device mapping available, return None. + """ + vt = self.deviceTransform() + if vt is None: + return None + if isinstance(obj, QtCore.QPoint): + obj = QtCore.QPointF(obj) + vt = fn.invertQTransform(vt) + return vt.map(obj) + + def mapRectToDevice(self, rect): + """ + Return *rect* mapped from local coordinates to device coordinates (pixels). + If there is no device mapping available, return None. + """ + vt = self.deviceTransform() + if vt is None: + return None + return vt.mapRect(rect) + + def mapRectFromDevice(self, rect): + """ + Return *rect* mapped from device coordinates (pixels) to local coordinates. + If there is no device mapping available, return None. + """ + vt = self.deviceTransform() + if vt is None: + return None + vt = fn.invertQTransform(vt) + return vt.mapRect(rect) + + def mapToView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + return vt.map(obj) + + def mapRectToView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + return vt.mapRect(obj) + + def mapFromView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + vt = fn.invertQTransform(vt) + return vt.map(obj) + + def mapRectFromView(self, obj): + vt = self.viewTransform() + if vt is None: + return None + vt = fn.invertQTransform(vt) + return vt.mapRect(obj) + + def pos(self): + return Point(self._qtBaseClass.pos(self)) + + def viewPos(self): + return self.mapToView(self.mapFromParent(self.pos())) + + def parentItem(self): + ## PyQt bug -- some items are returned incorrectly. + return GraphicsScene.translateGraphicsItem(self._qtBaseClass.parentItem(self)) + + def setParentItem(self, parent): + ## Workaround for Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 + if parent is not None: + pscene = parent.scene() + if pscene is not None and self.scene() is not pscene: + pscene.addItem(self) + return self._qtBaseClass.setParentItem(self, parent) + + def childItems(self): + ## PyQt bug -- some child items are returned incorrectly. + return list(map(GraphicsScene.translateGraphicsItem, self._qtBaseClass.childItems(self))) + + + def sceneTransform(self): + ## Qt bug: do no allow access to sceneTransform() until + ## the item has a scene. + + if self.scene() is None: + return self.transform() + else: + return self._qtBaseClass.sceneTransform(self) + + + def transformAngle(self, relativeItem=None): + """Return the rotation produced by this item's transform (this assumes there is no shear in the transform) + If relativeItem is given, then the angle is determined relative to that item. + """ + if relativeItem is None: + relativeItem = self.parentItem() + + + tr = self.itemTransform(relativeItem) + if isinstance(tr, tuple): ## difference between pyside and pyqt + tr = tr[0] + #vec = tr.map(Point(1,0)) - tr.map(Point(0,0)) + vec = tr.map(QtCore.QLineF(0,0,1,0)) + #return Point(vec).angle(Point(1,0)) + return vec.angleTo(QtCore.QLineF(vec.p1(), vec.p1()+QtCore.QPointF(1,0))) + + #def itemChange(self, change, value): + #ret = self._qtBaseClass.itemChange(self, change, value) + #if change == self.ItemParentHasChanged or change == self.ItemSceneHasChanged: + #print "Item scene changed:", self + #self.setChildScene(self) ## This is bizarre. + #return ret + + #def setChildScene(self, ch): + #scene = self.scene() + #for ch2 in ch.childItems(): + #if ch2.scene() is not scene: + #print "item", ch2, "has different scene:", ch2.scene(), scene + #scene.addItem(ch2) + #QtGui.QApplication.processEvents() + #print " --> ", ch2.scene() + #self.setChildScene(ch2) + + def parentChanged(self): + """Called when the item's parent has changed. + This method handles connecting / disconnecting from ViewBox signals + to make sure viewRangeChanged works properly. It should generally be + extended, not overridden.""" + self._updateView() + + + def _updateView(self): + ## called to see whether this item has a new view to connect to + ## NOTE: This is called from GraphicsObject.itemChange or GraphicsWidget.itemChange. + + ## It is possible this item has moved to a different ViewBox or widget; + ## clear out previously determined references to these. + self.forgetViewBox() + self.forgetViewWidget() + + ## check for this item's current viewbox or view widget + view = self.getViewBox() + #if view is None: + ##print " no view" + #return + + oldView = None + if self._connectedView is not None: + oldView = self._connectedView() + + if view is oldView: + #print " already have view", view + return + + ## disconnect from previous view + if oldView is not None: + for signal, slot in [('sigRangeChanged', self.viewRangeChanged), + ('sigDeviceRangeChanged', self.viewRangeChanged), + ('sigTransformChanged', self.viewTransformChanged), + ('sigDeviceTransformChanged', self.viewTransformChanged)]: + try: + getattr(oldView, signal).disconnect(slot) + except (TypeError, AttributeError, RuntimeError): + # TypeError and RuntimeError are from pyqt and pyside, respectively + pass + + self._connectedView = None + + ## connect to new view + if view is not None: + #print "connect:", self, view + if hasattr(view, 'sigDeviceRangeChanged'): + # connect signals from GraphicsView + view.sigDeviceRangeChanged.connect(self.viewRangeChanged) + view.sigDeviceTransformChanged.connect(self.viewTransformChanged) + else: + # connect signals from ViewBox + view.sigRangeChanged.connect(self.viewRangeChanged) + view.sigTransformChanged.connect(self.viewTransformChanged) + self._connectedView = weakref.ref(view) + self.viewRangeChanged() + self.viewTransformChanged() + + ## inform children that their view might have changed + self._replaceView(oldView) + + self.viewChanged(view, oldView) + + def viewChanged(self, view, oldView): + """Called when this item's view has changed + (ie, the item has been added to or removed from a ViewBox)""" + pass + + def _replaceView(self, oldView, item=None): + if item is None: + item = self + for child in item.childItems(): + if isinstance(child, GraphicsItem): + if child.getViewBox() is oldView: + child._updateView() + #self._replaceView(oldView, child) + else: + self._replaceView(oldView, child) + + + + def viewRangeChanged(self): + """ + Called whenever the view coordinates of the ViewBox containing this item have changed. + """ + pass + + def viewTransformChanged(self): + """ + Called whenever the transformation matrix of the view has changed. + (eg, the view range has changed or the view was resized) + """ + pass + + #def prepareGeometryChange(self): + #self._qtBaseClass.prepareGeometryChange(self) + #self.informViewBoundsChanged() + + def informViewBoundsChanged(self): + """ + Inform this item's container ViewBox that the bounds of this item have changed. + This is used by ViewBox to react if auto-range is enabled. + """ + view = self.getViewBox() + if view is not None and hasattr(view, 'implements') and view.implements('ViewBox'): + view.itemBoundsChanged(self) ## inform view so it can update its range if it wants + + def childrenShape(self): + """Return the union of the shapes of all descendants of this item in local coordinates.""" + childs = self.allChildItems() + shapes = [self.mapFromItem(c, c.shape()) for c in self.allChildItems()] + return reduce(operator.add, shapes) + + def allChildItems(self, root=None): + """Return list of the entire item tree descending from this item.""" + if root is None: + root = self + tree = [] + for ch in root.childItems(): + tree.append(ch) + tree.extend(self.allChildItems(ch)) + return tree + + + def setExportMode(self, export, opts=None): + """ + This method is called by exporters to inform items that they are being drawn for export + with a specific set of options. Items access these via self._exportOptions. + When exporting is complete, _exportOptions is set to False. + """ + if opts is None: + opts = {} + if export: + self._exportOpts = opts + #if 'antialias' not in opts: + #self._exportOpts['antialias'] = True + else: + self._exportOpts = False + + #def update(self): + #self._qtBaseClass.update(self) + #print "Update:", self + + def getContextMenus(self, event): + return [self.getMenu()] if hasattr(self, "getMenu") else [] diff --git a/papi/pyqtgraph/graphicsItems/GraphicsLayout.py b/papi/pyqtgraph/graphicsItems/GraphicsLayout.py new file mode 100644 index 00000000..6ec38fb5 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/GraphicsLayout.py @@ -0,0 +1,171 @@ +from ..Qt import QtGui, QtCore +from .. import functions as fn +from .GraphicsWidget import GraphicsWidget +## Must be imported at the end to avoid cyclic-dependency hell: +from .ViewBox import ViewBox +from .PlotItem import PlotItem +from .LabelItem import LabelItem + +__all__ = ['GraphicsLayout'] +class GraphicsLayout(GraphicsWidget): + """ + Used for laying out GraphicsWidgets in a grid. + This is usually created automatically as part of a :class:`GraphicsWindow ` or :class:`GraphicsLayoutWidget `. + """ + + + def __init__(self, parent=None, border=None): + GraphicsWidget.__init__(self, parent) + if border is True: + border = (100,100,100) + self.border = border + self.layout = QtGui.QGraphicsGridLayout() + self.setLayout(self.layout) + self.items = {} ## item: [(row, col), (row, col), ...] lists all cells occupied by the item + self.rows = {} ## row: {col1: item1, col2: item2, ...} maps cell location to item + self.currentRow = 0 + self.currentCol = 0 + self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) + + #def resizeEvent(self, ev): + #ret = GraphicsWidget.resizeEvent(self, ev) + #print self.pos(), self.mapToDevice(self.rect().topLeft()) + #return ret + + def setBorder(self, *args, **kwds): + """ + Set the pen used to draw border between cells. + + See :func:`mkPen ` for arguments. + """ + self.border = fn.mkPen(*args, **kwds) + self.update() + + def nextRow(self): + """Advance to next row for automatic item placement""" + self.currentRow += 1 + self.currentCol = -1 + self.nextColumn() + + def nextColumn(self): + """Advance to next available column + (generally only for internal use--called by addItem)""" + self.currentCol += 1 + while self.getItem(self.currentRow, self.currentCol) is not None: + self.currentCol += 1 + + def nextCol(self, *args, **kargs): + """Alias of nextColumn""" + return self.nextColumn(*args, **kargs) + + def addPlot(self, row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create a PlotItem and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to :func:`PlotItem.__init__ ` + Returns the created item. + """ + plot = PlotItem(**kargs) + self.addItem(plot, row, col, rowspan, colspan) + return plot + + def addViewBox(self, row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create a ViewBox and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to :func:`ViewBox.__init__ ` + Returns the created item. + """ + vb = ViewBox(**kargs) + self.addItem(vb, row, col, rowspan, colspan) + return vb + + def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create a LabelItem with *text* and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to :func:`LabelItem.__init__ ` + Returns the created item. + + To create a vertical label, use *angle* = -90. + """ + text = LabelItem(text, **kargs) + self.addItem(text, row, col, rowspan, colspan) + return text + + def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create an empty GraphicsLayout and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to :func:`GraphicsLayout.__init__ ` + Returns the created item. + """ + layout = GraphicsLayout(**kargs) + self.addItem(layout, row, col, rowspan, colspan) + return layout + + def addItem(self, item, row=None, col=None, rowspan=1, colspan=1): + """ + Add an item to the layout and place it in the next available cell (or in the cell specified). + The item must be an instance of a QGraphicsWidget subclass. + """ + if row is None: + row = self.currentRow + if col is None: + col = self.currentCol + + self.items[item] = [] + for i in range(rowspan): + for j in range(colspan): + row2 = row + i + col2 = col + j + if row2 not in self.rows: + self.rows[row2] = {} + self.rows[row2][col2] = item + self.items[item].append((row2, col2)) + + self.layout.addItem(item, row, col, rowspan, colspan) + self.nextColumn() + + def getItem(self, row, col): + """Return the item in (*row*, *col*). If the cell is empty, return None.""" + return self.rows.get(row, {}).get(col, None) + + def boundingRect(self): + return self.rect() + + def paint(self, p, *args): + if self.border is None: + return + p.setPen(fn.mkPen(self.border)) + for i in self.items: + r = i.mapRectToParent(i.boundingRect()) + p.drawRect(r) + + def itemIndex(self, item): + for i in range(self.layout.count()): + if self.layout.itemAt(i).graphicsItem() is item: + return i + raise Exception("Could not determine index of item " + str(item)) + + def removeItem(self, item): + """Remove *item* from the layout.""" + ind = self.itemIndex(item) + self.layout.removeAt(ind) + self.scene().removeItem(item) + + for r,c in self.items[item]: + del self.rows[r][c] + del self.items[item] + self.update() + + def clear(self): + items = [] + for i in list(self.items.keys()): + self.removeItem(i) + + def setContentsMargins(self, *args): + # Wrap calls to layout. This should happen automatically, but there + # seems to be a Qt bug: + # http://stackoverflow.com/questions/27092164/margins-in-pyqtgraphs-graphicslayout + self.layout.setContentsMargins(*args) + + def setSpacing(self, *args): + self.layout.setSpacing(*args) + \ No newline at end of file diff --git a/papi/pyqtgraph/graphicsItems/GraphicsObject.py b/papi/pyqtgraph/graphicsItems/GraphicsObject.py new file mode 100644 index 00000000..015a78c6 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/GraphicsObject.py @@ -0,0 +1,39 @@ +from ..Qt import QtGui, QtCore, USE_PYSIDE +if not USE_PYSIDE: + import sip +from .GraphicsItem import GraphicsItem + +__all__ = ['GraphicsObject'] +class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject): + """ + **Bases:** :class:`GraphicsItem `, :class:`QtGui.QGraphicsObject` + + Extension of QGraphicsObject with some useful methods (provided by :class:`GraphicsItem `) + """ + _qtBaseClass = QtGui.QGraphicsObject + def __init__(self, *args): + self.__inform_view_on_changes = True + QtGui.QGraphicsObject.__init__(self, *args) + self.setFlag(self.ItemSendsGeometryChanges) + GraphicsItem.__init__(self) + + def itemChange(self, change, value): + ret = QtGui.QGraphicsObject.itemChange(self, change, value) + if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: + self.parentChanged() + try: + inform_view_on_change = self.__inform_view_on_changes + except AttributeError: + # It's possible that the attribute was already collected when the itemChange happened + # (if it was triggered during the gc of the object). + pass + else: + if inform_view_on_change and change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]: + self.informViewBoundsChanged() + + ## workaround for pyqt bug: + ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html + if not USE_PYSIDE and change == self.ItemParentChange and isinstance(ret, QtGui.QGraphicsItem): + ret = sip.cast(ret, QtGui.QGraphicsItem) + + return ret diff --git a/papi/pyqtgraph/graphicsItems/GraphicsWidget.py b/papi/pyqtgraph/graphicsItems/GraphicsWidget.py new file mode 100644 index 00000000..c379ce8e --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/GraphicsWidget.py @@ -0,0 +1,59 @@ +from ..Qt import QtGui, QtCore +from ..GraphicsScene import GraphicsScene +from .GraphicsItem import GraphicsItem + +__all__ = ['GraphicsWidget'] + +class GraphicsWidget(GraphicsItem, QtGui.QGraphicsWidget): + + _qtBaseClass = QtGui.QGraphicsWidget + def __init__(self, *args, **kargs): + """ + **Bases:** :class:`GraphicsItem `, :class:`QtGui.QGraphicsWidget` + + Extends QGraphicsWidget with several helpful methods and workarounds for PyQt bugs. + Most of the extra functionality is inherited from :class:`GraphicsItem `. + """ + QtGui.QGraphicsWidget.__init__(self, *args, **kargs) + GraphicsItem.__init__(self) + + ## done by GraphicsItem init + #GraphicsScene.registerObject(self) ## workaround for pyqt bug in graphicsscene.items() + + # Removed due to https://bugreports.qt-project.org/browse/PYSIDE-86 + #def itemChange(self, change, value): + ## BEWARE: Calling QGraphicsWidget.itemChange can lead to crashing! + ##ret = QtGui.QGraphicsWidget.itemChange(self, change, value) ## segv occurs here + ## The default behavior is just to return the value argument, so we'll do that + ## without calling the original method. + #ret = value + #if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: + #self._updateView() + #return ret + + def setFixedHeight(self, h): + self.setMaximumHeight(h) + self.setMinimumHeight(h) + + def setFixedWidth(self, h): + self.setMaximumWidth(h) + self.setMinimumWidth(h) + + def height(self): + return self.geometry().height() + + def width(self): + return self.geometry().width() + + def boundingRect(self): + br = self.mapRectFromParent(self.geometry()).normalized() + #print "bounds:", br + return br + + def shape(self): ## No idea why this is necessary, but rotated items do not receive clicks otherwise. + p = QtGui.QPainterPath() + p.addRect(self.boundingRect()) + #print "shape:", p.boundingRect() + return p + + diff --git a/papi/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py b/papi/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py new file mode 100644 index 00000000..251bc0c8 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py @@ -0,0 +1,110 @@ +from ..Qt import QtGui, QtCore +from ..Point import Point + + +class GraphicsWidgetAnchor(object): + """ + Class used to allow GraphicsWidgets to anchor to a specific position on their + parent. The item will be automatically repositioned if the parent is resized. + This is used, for example, to anchor a LegendItem to a corner of its parent + PlotItem. + + """ + + def __init__(self): + self.__parent = None + self.__parentAnchor = None + self.__itemAnchor = None + self.__offset = (0,0) + if hasattr(self, 'geometryChanged'): + self.geometryChanged.connect(self.__geometryChanged) + + def anchor(self, itemPos, parentPos, offset=(0,0)): + """ + Anchors the item at its local itemPos to the item's parent at parentPos. + Both positions are expressed in values relative to the size of the item or parent; + a value of 0 indicates left or top edge, while 1 indicates right or bottom edge. + + Optionally, offset may be specified to introduce an absolute offset. + + Example: anchor a box such that its upper-right corner is fixed 10px left + and 10px down from its parent's upper-right corner:: + + box.anchor(itemPos=(1,0), parentPos=(1,0), offset=(-10,10)) + """ + parent = self.parentItem() + if parent is None: + raise Exception("Cannot anchor; parent is not set.") + + if self.__parent is not parent: + if self.__parent is not None: + self.__parent.geometryChanged.disconnect(self.__geometryChanged) + + self.__parent = parent + parent.geometryChanged.connect(self.__geometryChanged) + + self.__itemAnchor = itemPos + self.__parentAnchor = parentPos + self.__offset = offset + self.__geometryChanged() + + + def autoAnchor(self, pos, relative=True): + """ + Set the position of this item relative to its parent by automatically + choosing appropriate anchor settings. + + If relative is True, one corner of the item will be anchored to + the appropriate location on the parent with no offset. The anchored + corner will be whichever is closest to the parent's boundary. + + If relative is False, one corner of the item will be anchored to the same + corner of the parent, with an absolute offset to achieve the correct + position. + """ + pos = Point(pos) + br = self.mapRectToParent(self.boundingRect()).translated(pos - self.pos()) + pbr = self.parentItem().boundingRect() + anchorPos = [0,0] + parentPos = Point() + itemPos = Point() + if abs(br.left() - pbr.left()) < abs(br.right() - pbr.right()): + anchorPos[0] = 0 + parentPos[0] = pbr.left() + itemPos[0] = br.left() + else: + anchorPos[0] = 1 + parentPos[0] = pbr.right() + itemPos[0] = br.right() + + if abs(br.top() - pbr.top()) < abs(br.bottom() - pbr.bottom()): + anchorPos[1] = 0 + parentPos[1] = pbr.top() + itemPos[1] = br.top() + else: + anchorPos[1] = 1 + parentPos[1] = pbr.bottom() + itemPos[1] = br.bottom() + + if relative: + relPos = [(itemPos[0]-pbr.left()) / pbr.width(), (itemPos[1]-pbr.top()) / pbr.height()] + self.anchor(anchorPos, relPos) + else: + offset = itemPos - parentPos + self.anchor(anchorPos, anchorPos, offset) + + def __geometryChanged(self): + if self.__parent is None: + return + if self.__itemAnchor is None: + return + + o = self.mapToParent(Point(0,0)) + a = self.boundingRect().bottomRight() * Point(self.__itemAnchor) + a = self.mapToParent(a) + p = self.__parent.boundingRect().bottomRight() * Point(self.__parentAnchor) + off = Point(self.__offset) + pos = p + (o-a) + off + self.setPos(pos) + + \ No newline at end of file diff --git a/papi/pyqtgraph/graphicsItems/GridItem.py b/papi/pyqtgraph/graphicsItems/GridItem.py new file mode 100644 index 00000000..87f90a62 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/GridItem.py @@ -0,0 +1,120 @@ +from ..Qt import QtGui, QtCore +from .UIGraphicsItem import * +import numpy as np +from ..Point import Point +from .. import functions as fn + +__all__ = ['GridItem'] +class GridItem(UIGraphicsItem): + """ + **Bases:** :class:`UIGraphicsItem ` + + Displays a rectangular grid of lines indicating major divisions within a coordinate system. + Automatically determines what divisions to use. + """ + + def __init__(self): + UIGraphicsItem.__init__(self) + #QtGui.QGraphicsItem.__init__(self, *args) + #self.setFlag(QtGui.QGraphicsItem.ItemClipsToShape) + #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) + + self.picture = None + + + def viewRangeChanged(self): + UIGraphicsItem.viewRangeChanged(self) + self.picture = None + #UIGraphicsItem.viewRangeChanged(self) + #self.update() + + def paint(self, p, opt, widget): + #p.setPen(QtGui.QPen(QtGui.QColor(100, 100, 100))) + #p.drawRect(self.boundingRect()) + #UIGraphicsItem.paint(self, p, opt, widget) + ### draw picture + if self.picture is None: + #print "no pic, draw.." + self.generatePicture() + p.drawPicture(QtCore.QPointF(0, 0), self.picture) + #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) + #p.drawLine(0, -100, 0, 100) + #p.drawLine(-100, 0, 100, 0) + #print "drawing Grid." + + + def generatePicture(self): + self.picture = QtGui.QPicture() + p = QtGui.QPainter() + p.begin(self.picture) + + dt = fn.invertQTransform(self.viewTransform()) + vr = self.getViewWidget().rect() + unit = self.pixelWidth(), self.pixelHeight() + dim = [vr.width(), vr.height()] + lvr = self.boundingRect() + ul = np.array([lvr.left(), lvr.top()]) + br = np.array([lvr.right(), lvr.bottom()]) + + texts = [] + + if ul[1] > br[1]: + x = ul[1] + ul[1] = br[1] + br[1] = x + for i in [2,1,0]: ## Draw three different scales of grid + dist = br-ul + nlTarget = 10.**i + d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5) + ul1 = np.floor(ul / d) * d + br1 = np.ceil(br / d) * d + dist = br1-ul1 + nl = (dist / d) + 0.5 + #print "level", i + #print " dim", dim + #print " dist", dist + #print " d", d + #print " nl", nl + for ax in range(0,2): ## Draw grid for both axes + ppl = dim[ax] / nl[ax] + c = np.clip(3.*(ppl-3), 0., 30.) + linePen = QtGui.QPen(QtGui.QColor(255, 255, 255, c)) + textPen = QtGui.QPen(QtGui.QColor(255, 255, 255, c*2)) + #linePen.setCosmetic(True) + #linePen.setWidth(1) + bx = (ax+1) % 2 + for x in range(0, int(nl[ax])): + linePen.setCosmetic(False) + if ax == 0: + linePen.setWidthF(self.pixelWidth()) + #print "ax 0 height", self.pixelHeight() + else: + linePen.setWidthF(self.pixelHeight()) + #print "ax 1 width", self.pixelWidth() + p.setPen(linePen) + p1 = np.array([0.,0.]) + p2 = np.array([0.,0.]) + p1[ax] = ul1[ax] + x * d[ax] + p2[ax] = p1[ax] + p1[bx] = ul[bx] + p2[bx] = br[bx] + ## don't draw lines that are out of bounds. + if p1[ax] < min(ul[ax], br[ax]) or p1[ax] > max(ul[ax], br[ax]): + continue + p.drawLine(QtCore.QPointF(p1[0], p1[1]), QtCore.QPointF(p2[0], p2[1])) + if i < 2: + p.setPen(textPen) + if ax == 0: + x = p1[0] + unit[0] + y = ul[1] + unit[1] * 8. + else: + x = ul[0] + unit[0]*3 + y = p1[1] + unit[1] + texts.append((QtCore.QPointF(x, y), "%g"%p1[ax])) + tr = self.deviceTransform() + #tr.scale(1.5, 1.5) + p.setWorldTransform(fn.invertQTransform(tr)) + for t in texts: + x = tr.map(t[0]) + Point(0.5, 0.5) + p.drawText(x, t[1]) + p.end() diff --git a/papi/pyqtgraph/graphicsItems/HistogramLUTItem.py b/papi/pyqtgraph/graphicsItems/HistogramLUTItem.py new file mode 100644 index 00000000..89ebef3e --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/HistogramLUTItem.py @@ -0,0 +1,205 @@ +""" +GraphicsWidget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. +""" + + +from ..Qt import QtGui, QtCore +from .. import functions as fn +from .GraphicsWidget import GraphicsWidget +from .ViewBox import * +from .GradientEditorItem import * +from .LinearRegionItem import * +from .PlotDataItem import * +from .AxisItem import * +from .GridItem import * +from ..Point import Point +from .. import functions as fn +import numpy as np +from .. import debug as debug + +import weakref + +__all__ = ['HistogramLUTItem'] + + +class HistogramLUTItem(GraphicsWidget): + """ + This is a graphicsWidget which provides controls for adjusting the display of an image. + Includes: + + - Image histogram + - Movable region over histogram to select black/white levels + - Gradient editor to define color lookup table for single-channel images + """ + + sigLookupTableChanged = QtCore.Signal(object) + sigLevelsChanged = QtCore.Signal(object) + sigLevelChangeFinished = QtCore.Signal(object) + + def __init__(self, image=None, fillHistogram=True): + """ + If *image* (ImageItem) is provided, then the control will be automatically linked to the image and changes to the control will be immediately reflected in the image's appearance. + By default, the histogram is rendered with a fill. For performance, set *fillHistogram* = False. + """ + GraphicsWidget.__init__(self) + self.lut = None + self.imageItem = lambda: None # fake a dead weakref + + self.layout = QtGui.QGraphicsGridLayout() + self.setLayout(self.layout) + self.layout.setContentsMargins(1,1,1,1) + self.layout.setSpacing(0) + self.vb = ViewBox(parent=self) + self.vb.setMaximumWidth(152) + self.vb.setMinimumWidth(45) + self.vb.setMouseEnabled(x=False, y=True) + self.gradient = GradientEditorItem() + self.gradient.setOrientation('right') + self.gradient.loadPreset('grey') + self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal) + self.region.setZValue(1000) + self.vb.addItem(self.region) + self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, parent=self) + self.layout.addItem(self.axis, 0, 0) + self.layout.addItem(self.vb, 0, 1) + self.layout.addItem(self.gradient, 0, 2) + self.range = None + self.gradient.setFlag(self.gradient.ItemStacksBehindParent) + self.vb.setFlag(self.gradient.ItemStacksBehindParent) + + #self.grid = GridItem() + #self.vb.addItem(self.grid) + + self.gradient.sigGradientChanged.connect(self.gradientChanged) + self.region.sigRegionChanged.connect(self.regionChanging) + self.region.sigRegionChangeFinished.connect(self.regionChanged) + self.vb.sigRangeChanged.connect(self.viewRangeChanged) + self.plot = PlotDataItem() + self.plot.rotate(90) + self.fillHistogram(fillHistogram) + + self.vb.addItem(self.plot) + self.autoHistogramRange() + + if image is not None: + self.setImageItem(image) + #self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + + def fillHistogram(self, fill=True, level=0.0, color=(100, 100, 200)): + if fill: + self.plot.setFillLevel(level) + self.plot.setFillBrush(color) + else: + self.plot.setFillLevel(None) + + #def sizeHint(self, *args): + #return QtCore.QSizeF(115, 200) + + def paint(self, p, *args): + pen = self.region.lines[0].pen + rgn = self.getLevels() + p1 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[0])) + p2 = self.vb.mapFromViewToItem(self, Point(self.vb.viewRect().center().x(), rgn[1])) + gradRect = self.gradient.mapRectToParent(self.gradient.gradRect.rect()) + for pen in [fn.mkPen('k', width=3), pen]: + p.setPen(pen) + p.drawLine(p1, gradRect.bottomLeft()) + p.drawLine(p2, gradRect.topLeft()) + p.drawLine(gradRect.topLeft(), gradRect.topRight()) + p.drawLine(gradRect.bottomLeft(), gradRect.bottomRight()) + #p.drawRect(self.boundingRect()) + + + def setHistogramRange(self, mn, mx, padding=0.1): + """Set the Y range on the histogram plot. This disables auto-scaling.""" + self.vb.enableAutoRange(self.vb.YAxis, False) + self.vb.setYRange(mn, mx, padding) + + #d = mx-mn + #mn -= d*padding + #mx += d*padding + #self.range = [mn,mx] + #self.updateRange() + #self.vb.setMouseEnabled(False, True) + #self.region.setBounds([mn,mx]) + + def autoHistogramRange(self): + """Enable auto-scaling on the histogram plot.""" + self.vb.enableAutoRange(self.vb.XYAxes) + #self.range = None + #self.updateRange() + #self.vb.setMouseEnabled(False, False) + + #def updateRange(self): + #self.vb.autoRange() + #if self.range is not None: + #self.vb.setYRange(*self.range) + #vr = self.vb.viewRect() + + #self.region.setBounds([vr.top(), vr.bottom()]) + + def setImageItem(self, img): + self.imageItem = weakref.ref(img) + img.sigImageChanged.connect(self.imageChanged) + img.setLookupTable(self.getLookupTable) ## send function pointer, not the result + #self.gradientChanged() + self.regionChanged() + self.imageChanged(autoLevel=True) + #self.vb.autoRange() + + def viewRangeChanged(self): + self.update() + + def gradientChanged(self): + if self.imageItem() is not None: + if self.gradient.isLookupTrivial(): + self.imageItem().setLookupTable(None) #lambda x: x.astype(np.uint8)) + else: + self.imageItem().setLookupTable(self.getLookupTable) ## send function pointer, not the result + + self.lut = None + #if self.imageItem is not None: + #self.imageItem.setLookupTable(self.gradient.getLookupTable(512)) + self.sigLookupTableChanged.emit(self) + + def getLookupTable(self, img=None, n=None, alpha=None): + if n is None: + if img.dtype == np.uint8: + n = 256 + else: + n = 512 + if self.lut is None: + self.lut = self.gradient.getLookupTable(n, alpha=alpha) + return self.lut + + def regionChanged(self): + #if self.imageItem is not None: + #self.imageItem.setLevels(self.region.getRegion()) + self.sigLevelChangeFinished.emit(self) + #self.update() + + def regionChanging(self): + if self.imageItem() is not None: + self.imageItem().setLevels(self.region.getRegion()) + self.sigLevelsChanged.emit(self) + self.update() + + def imageChanged(self, autoLevel=False, autoRange=False): + profiler = debug.Profiler() + h = self.imageItem().getHistogram() + profiler('get histogram') + if h[0] is None: + return + self.plot.setData(*h) + profiler('set plot') + if autoLevel: + mn = h[0][0] + mx = h[0][-1] + self.region.setRegion([mn, mx]) + profiler('set region') + + def getLevels(self): + return self.region.getRegion() + + def setLevels(self, mn, mx): + self.region.setRegion([mn, mx]) diff --git a/papi/pyqtgraph/graphicsItems/ImageItem.py b/papi/pyqtgraph/graphicsItems/ImageItem.py new file mode 100644 index 00000000..5b041433 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ImageItem.py @@ -0,0 +1,528 @@ +from __future__ import division + +from ..Qt import QtGui, QtCore +import numpy as np +import collections +from .. import functions as fn +from .. import debug as debug +from .GraphicsObject import GraphicsObject +from ..Point import Point + +__all__ = ['ImageItem'] + + +class ImageItem(GraphicsObject): + """ + **Bases:** :class:`GraphicsObject ` + + GraphicsObject displaying an image. Optimized for rapid update (ie video display). + This item displays either a 2D numpy array (height, width) or + a 3D array (height, width, RGBa). This array is optionally scaled (see + :func:`setLevels `) and/or colored + with a lookup table (see :func:`setLookupTable `) + before being displayed. + + ImageItem is frequently used in conjunction with + :class:`HistogramLUTItem ` or + :class:`HistogramLUTWidget ` to provide a GUI + for controlling the levels and lookup table used to display the image. + """ + + + sigImageChanged = QtCore.Signal() + sigRemoveRequested = QtCore.Signal(object) # self; emitted when 'remove' is selected from context menu + + def __init__(self, image=None, **kargs): + """ + See :func:`setImage ` for all allowed initialization arguments. + """ + GraphicsObject.__init__(self) + self.menu = None + self.image = None ## original image data + self.qimage = None ## rendered image for display + + self.paintMode = None + + self.levels = None ## [min, max] or [[redMin, redMax], ...] + self.lut = None + self.autoDownsample = False + + self.drawKernel = None + self.border = None + self.removable = False + + if image is not None: + self.setImage(image, **kargs) + else: + self.setOpts(**kargs) + + def setCompositionMode(self, mode): + """Change the composition mode of the item (see QPainter::CompositionMode + in the Qt documentation). This is useful when overlaying multiple ImageItems. + + ============================================ ============================================================ + **Most common arguments:** + QtGui.QPainter.CompositionMode_SourceOver Default; image replaces the background if it + is opaque. Otherwise, it uses the alpha channel to blend + the image with the background. + QtGui.QPainter.CompositionMode_Overlay The image color is mixed with the background color to + reflect the lightness or darkness of the background. + QtGui.QPainter.CompositionMode_Plus Both the alpha and color of the image and background pixels + are added together. + QtGui.QPainter.CompositionMode_Multiply The output is the image color multiplied by the background. + ============================================ ============================================================ + """ + self.paintMode = mode + self.update() + + ## use setOpacity instead. + #def setAlpha(self, alpha): + #self.setOpacity(alpha) + #self.updateImage() + + def setBorder(self, b): + self.border = fn.mkPen(b) + self.update() + + def width(self): + if self.image is None: + return None + return self.image.shape[0] + + def height(self): + if self.image is None: + return None + return self.image.shape[1] + + def boundingRect(self): + if self.image is None: + return QtCore.QRectF(0., 0., 0., 0.) + return QtCore.QRectF(0., 0., float(self.width()), float(self.height())) + + #def setClipLevel(self, level=None): + #self.clipLevel = level + #self.updateImage() + + #def paint(self, p, opt, widget): + #pass + #if self.pixmap is not None: + #p.drawPixmap(0, 0, self.pixmap) + #print "paint" + + def setLevels(self, levels, update=True): + """ + Set image scaling levels. Can be one of: + + * [blackLevel, whiteLevel] + * [[minRed, maxRed], [minGreen, maxGreen], [minBlue, maxBlue]] + + Only the first format is compatible with lookup tables. See :func:`makeARGB ` + for more details on how levels are applied. + """ + self.levels = levels + if update: + self.updateImage() + + def getLevels(self): + return self.levels + #return self.whiteLevel, self.blackLevel + + def setLookupTable(self, lut, update=True): + """ + Set the lookup table (numpy array) to use for this image. (see + :func:`makeARGB ` for more information on how this is used). + Optionally, lut can be a callable that accepts the current image as an + argument and returns the lookup table to use. + + Ordinarily, this table is supplied by a :class:`HistogramLUTItem ` + or :class:`GradientEditorItem `. + """ + self.lut = lut + if update: + self.updateImage() + + def setAutoDownsample(self, ads): + """ + Set the automatic downsampling mode for this ImageItem. + + Added in version 0.9.9 + """ + self.autoDownsample = ads + self.qimage = None + self.update() + + def setOpts(self, update=True, **kargs): + + if 'lut' in kargs: + self.setLookupTable(kargs['lut'], update=update) + if 'levels' in kargs: + self.setLevels(kargs['levels'], update=update) + #if 'clipLevel' in kargs: + #self.setClipLevel(kargs['clipLevel']) + if 'opacity' in kargs: + self.setOpacity(kargs['opacity']) + if 'compositionMode' in kargs: + self.setCompositionMode(kargs['compositionMode']) + if 'border' in kargs: + self.setBorder(kargs['border']) + if 'removable' in kargs: + self.removable = kargs['removable'] + self.menu = None + if 'autoDownsample' in kargs: + self.setAutoDownsample(kargs['autoDownsample']) + if update: + self.update() + + def setRect(self, rect): + """Scale and translate the image to fit within rect (must be a QRect or QRectF).""" + self.resetTransform() + self.translate(rect.left(), rect.top()) + self.scale(rect.width() / self.width(), rect.height() / self.height()) + + def clear(self): + self.image = None + self.prepareGeometryChange() + self.informViewBoundsChanged() + self.update() + + def setImage(self, image=None, autoLevels=None, **kargs): + """ + Update the image displayed by this item. For more information on how the image + is processed before displaying, see :func:`makeARGB ` + + ================= ========================================================================= + **Arguments:** + image (numpy array) Specifies the image data. May be 2D (width, height) or + 3D (width, height, RGBa). The array dtype must be integer or floating + point of any bit depth. For 3D arrays, the third dimension must + be of length 3 (RGB) or 4 (RGBA). + autoLevels (bool) If True, this forces the image to automatically select + levels based on the maximum and minimum values in the data. + By default, this argument is true unless the levels argument is + given. + lut (numpy array) The color lookup table to use when displaying the image. + See :func:`setLookupTable `. + levels (min, max) The minimum and maximum values to use when rescaling the image + data. By default, this will be set to the minimum and maximum values + in the image. If the image array has dtype uint8, no rescaling is necessary. + opacity (float 0.0-1.0) + compositionMode see :func:`setCompositionMode ` + border Sets the pen used when drawing the image border. Default is None. + autoDownsample (bool) If True, the image is automatically downsampled to match the + screen resolution. This improves performance for large images and + reduces aliasing. + ================= ========================================================================= + """ + profile = debug.Profiler() + + gotNewData = False + if image is None: + if self.image is None: + return + else: + gotNewData = True + shapeChanged = (self.image is None or image.shape != self.image.shape) + self.image = image.view(np.ndarray) + if self.image.shape[0] > 2**15-1 or self.image.shape[1] > 2**15-1: + if 'autoDownsample' not in kargs: + kargs['autoDownsample'] = True + if shapeChanged: + self.prepareGeometryChange() + self.informViewBoundsChanged() + + profile() + + if autoLevels is None: + if 'levels' in kargs: + autoLevels = False + else: + autoLevels = True + if autoLevels: + img = self.image + while img.size > 2**16: + img = img[::2, ::2] + mn, mx = img.min(), img.max() + if mn == mx: + mn = 0 + mx = 255 + kargs['levels'] = [mn,mx] + + profile() + + self.setOpts(update=False, **kargs) + + profile() + + self.qimage = None + self.update() + + profile() + + if gotNewData: + self.sigImageChanged.emit() + + + def updateImage(self, *args, **kargs): + ## used for re-rendering qimage from self.image. + + ## can we make any assumptions here that speed things up? + ## dtype, range, size are all the same? + defaults = { + 'autoLevels': False, + } + defaults.update(kargs) + return self.setImage(*args, **defaults) + + def render(self): + # Convert data to QImage for display. + + profile = debug.Profiler() + if self.image is None or self.image.size == 0: + return + if isinstance(self.lut, collections.Callable): + lut = self.lut(self.image) + else: + lut = self.lut + + if self.autoDownsample: + # reduce dimensions of image based on screen resolution + o = self.mapToDevice(QtCore.QPointF(0,0)) + x = self.mapToDevice(QtCore.QPointF(1,0)) + y = self.mapToDevice(QtCore.QPointF(0,1)) + w = Point(x-o).length() + h = Point(y-o).length() + xds = max(1, int(1/w)) + yds = max(1, int(1/h)) + image = fn.downsample(self.image, xds, axis=0) + image = fn.downsample(image, yds, axis=1) + else: + image = self.image + + argb, alpha = fn.makeARGB(image.transpose((1, 0, 2)[:image.ndim]), lut=lut, levels=self.levels) + self.qimage = fn.makeQImage(argb, alpha, transpose=False) + + def paint(self, p, *args): + profile = debug.Profiler() + if self.image is None: + return + if self.qimage is None: + self.render() + if self.qimage is None: + return + profile('render QImage') + if self.paintMode is not None: + p.setCompositionMode(self.paintMode) + profile('set comp mode') + + p.drawImage(QtCore.QRectF(0,0,self.image.shape[0],self.image.shape[1]), self.qimage) + profile('p.drawImage') + if self.border is not None: + p.setPen(self.border) + p.drawRect(self.boundingRect()) + + def save(self, fileName, *args): + """Save this image to file. Note that this saves the visible image (after scale/color changes), not the original data.""" + if self.qimage is None: + self.render() + self.qimage.save(fileName, *args) + + def getHistogram(self, bins='auto', step='auto', targetImageSize=200, targetHistogramSize=500, **kwds): + """Returns x and y arrays containing the histogram values for the current image. + For an explanation of the return format, see numpy.histogram(). + + The *step* argument causes pixels to be skipped when computing the histogram to save time. + If *step* is 'auto', then a step is chosen such that the analyzed data has + dimensions roughly *targetImageSize* for each axis. + + The *bins* argument and any extra keyword arguments are passed to + np.histogram(). If *bins* is 'auto', then a bin number is automatically + chosen based on the image characteristics: + + * Integer images will have approximately *targetHistogramSize* bins, + with each bin having an integer width. + * All other types will have *targetHistogramSize* bins. + + This method is also used when automatically computing levels. + """ + if self.image is None: + return None,None + if step == 'auto': + step = (np.ceil(self.image.shape[0] / targetImageSize), + np.ceil(self.image.shape[1] / targetImageSize)) + if np.isscalar(step): + step = (step, step) + stepData = self.image[::step[0], ::step[1]] + + if bins == 'auto': + if stepData.dtype.kind in "ui": + mn = stepData.min() + mx = stepData.max() + step = np.ceil((mx-mn) / 500.) + bins = np.arange(mn, mx+1.01*step, step, dtype=np.int) + if len(bins) == 0: + bins = [mn, mx] + else: + bins = 500 + + kwds['bins'] = bins + hist = np.histogram(stepData, **kwds) + + return hist[1][:-1], hist[0] + + def setPxMode(self, b): + """ + Set whether the item ignores transformations and draws directly to screen pixels. + If True, the item will not inherit any scale or rotation transformations from its + parent items, but its position will be transformed as usual. + (see GraphicsItem::ItemIgnoresTransformations in the Qt documentation) + """ + self.setFlag(self.ItemIgnoresTransformations, b) + + def setScaledMode(self): + self.setPxMode(False) + + def getPixmap(self): + if self.qimage is None: + self.render() + if self.qimage is None: + return None + return QtGui.QPixmap.fromImage(self.qimage) + + def pixelSize(self): + """return scene-size of a single pixel in the image""" + br = self.sceneBoundingRect() + if self.image is None: + return 1,1 + return br.width()/self.width(), br.height()/self.height() + + def viewTransformChanged(self): + if self.autoDownsample: + self.qimage = None + self.update() + + #def mousePressEvent(self, ev): + #if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: + #self.drawAt(ev.pos(), ev) + #ev.accept() + #else: + #ev.ignore() + + #def mouseMoveEvent(self, ev): + ##print "mouse move", ev.pos() + #if self.drawKernel is not None: + #self.drawAt(ev.pos(), ev) + + #def mouseReleaseEvent(self, ev): + #pass + + def mouseDragEvent(self, ev): + if ev.button() != QtCore.Qt.LeftButton: + ev.ignore() + return + elif self.drawKernel is not None: + ev.accept() + self.drawAt(ev.pos(), ev) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton: + if self.raiseContextMenu(ev): + ev.accept() + if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton: + self.drawAt(ev.pos(), ev) + + def raiseContextMenu(self, ev): + menu = self.getMenu() + if menu is None: + return False + menu = self.scene().addParentContextMenus(self, menu, ev) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + return True + + def getMenu(self): + if self.menu is None: + if not self.removable: + return None + self.menu = QtGui.QMenu() + self.menu.setTitle("Image") + remAct = QtGui.QAction("Remove image", self.menu) + remAct.triggered.connect(self.removeClicked) + self.menu.addAction(remAct) + self.menu.remAct = remAct + return self.menu + + + def hoverEvent(self, ev): + if not ev.isExit() and self.drawKernel is not None and ev.acceptDrags(QtCore.Qt.LeftButton): + ev.acceptClicks(QtCore.Qt.LeftButton) ## we don't use the click, but we also don't want anyone else to use it. + ev.acceptClicks(QtCore.Qt.RightButton) + #self.box.setBrush(fn.mkBrush('w')) + elif not ev.isExit() and self.removable: + ev.acceptClicks(QtCore.Qt.RightButton) ## accept context menu clicks + #else: + #self.box.setBrush(self.brush) + #self.update() + + + + def tabletEvent(self, ev): + print(ev.device()) + print(ev.pointerType()) + print(ev.pressure()) + + def drawAt(self, pos, ev=None): + pos = [int(pos.x()), int(pos.y())] + dk = self.drawKernel + kc = self.drawKernelCenter + sx = [0,dk.shape[0]] + sy = [0,dk.shape[1]] + tx = [pos[0] - kc[0], pos[0] - kc[0]+ dk.shape[0]] + ty = [pos[1] - kc[1], pos[1] - kc[1]+ dk.shape[1]] + + for i in [0,1]: + dx1 = -min(0, tx[i]) + dx2 = min(0, self.image.shape[0]-tx[i]) + tx[i] += dx1+dx2 + sx[i] += dx1+dx2 + + dy1 = -min(0, ty[i]) + dy2 = min(0, self.image.shape[1]-ty[i]) + ty[i] += dy1+dy2 + sy[i] += dy1+dy2 + + ts = (slice(tx[0],tx[1]), slice(ty[0],ty[1])) + ss = (slice(sx[0],sx[1]), slice(sy[0],sy[1])) + mask = self.drawMask + src = dk + + if isinstance(self.drawMode, collections.Callable): + self.drawMode(dk, self.image, mask, ss, ts, ev) + else: + src = src[ss] + if self.drawMode == 'set': + if mask is not None: + mask = mask[ss] + self.image[ts] = self.image[ts] * (1-mask) + src * mask + else: + self.image[ts] = src + elif self.drawMode == 'add': + self.image[ts] += src + else: + raise Exception("Unknown draw mode '%s'" % self.drawMode) + self.updateImage() + + def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'): + self.drawKernel = kernel + self.drawKernelCenter = center + self.drawMode = mode + self.drawMask = mask + + def removeClicked(self): + ## Send remove event only after we have exited the menu event handler + self.removeTimer = QtCore.QTimer() + self.removeTimer.timeout.connect(self.emitRemoveRequested) + self.removeTimer.start(0) + + def emitRemoveRequested(self): + self.removeTimer.timeout.disconnect(self.emitRemoveRequested) + self.sigRemoveRequested.emit(self) diff --git a/papi/pyqtgraph/graphicsItems/InfiniteLine.py b/papi/pyqtgraph/graphicsItems/InfiniteLine.py new file mode 100644 index 00000000..8108c3cf --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/InfiniteLine.py @@ -0,0 +1,275 @@ +from ..Qt import QtGui, QtCore +from ..Point import Point +from .GraphicsObject import GraphicsObject +from .. import functions as fn +import numpy as np +import weakref + + +__all__ = ['InfiniteLine'] +class InfiniteLine(GraphicsObject): + """ + **Bases:** :class:`GraphicsObject ` + + Displays a line of infinite length. + This line may be dragged to indicate a position in data coordinates. + + =============================== =================================================== + **Signals:** + sigDragged(self) + sigPositionChangeFinished(self) + sigPositionChanged(self) + =============================== =================================================== + """ + + sigDragged = QtCore.Signal(object) + sigPositionChangeFinished = QtCore.Signal(object) + sigPositionChanged = QtCore.Signal(object) + + def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None): + """ + =============== ================================================================== + **Arguments:** + pos Position of the line. This can be a QPointF or a single value for + vertical/horizontal lines. + angle Angle of line in degrees. 0 is horizontal, 90 is vertical. + pen Pen to use when drawing line. Can be any arguments that are valid + for :func:`mkPen `. Default pen is transparent + yellow. + movable If True, the line can be dragged to a new position by the user. + bounds Optional [min, max] bounding values. Bounds are only valid if the + line is vertical or horizontal. + =============== ================================================================== + """ + + GraphicsObject.__init__(self) + + if bounds is None: ## allowed value boundaries for orthogonal lines + self.maxRange = [None, None] + else: + self.maxRange = bounds + self.moving = False + self.setMovable(movable) + self.mouseHovering = False + self.p = [0, 0] + self.setAngle(angle) + if pos is None: + pos = Point(0,0) + self.setPos(pos) + + if pen is None: + pen = (200, 200, 100) + + self.setPen(pen) + self.setHoverPen(color=(255,0,0), width=self.pen.width()) + self.currentPen = self.pen + #self.setFlag(self.ItemSendsScenePositionChanges) + + def setMovable(self, m): + """Set whether the line is movable by the user.""" + self.movable = m + self.setAcceptHoverEvents(m) + + def setBounds(self, bounds): + """Set the (minimum, maximum) allowable values when dragging.""" + self.maxRange = bounds + self.setValue(self.value()) + + def setPen(self, *args, **kwargs): + """Set the pen for drawing the line. Allowable arguments are any that are valid + for :func:`mkPen `.""" + self.pen = fn.mkPen(*args, **kwargs) + if not self.mouseHovering: + self.currentPen = self.pen + self.update() + + def setHoverPen(self, *args, **kwargs): + """Set the pen for drawing the line while the mouse hovers over it. + Allowable arguments are any that are valid + for :func:`mkPen `. + + If the line is not movable, then hovering is also disabled. + + Added in version 0.9.9.""" + self.hoverPen = fn.mkPen(*args, **kwargs) + if self.mouseHovering: + self.currentPen = self.hoverPen + self.update() + + def setAngle(self, angle): + """ + Takes angle argument in degrees. + 0 is horizontal; 90 is vertical. + + Note that the use of value() and setValue() changes if the line is + not vertical or horizontal. + """ + self.angle = ((angle+45) % 180) - 45 ## -45 <= angle < 135 + self.resetTransform() + self.rotate(self.angle) + self.update() + + def setPos(self, pos): + + if type(pos) in [list, tuple]: + newPos = pos + elif isinstance(pos, QtCore.QPointF): + newPos = [pos.x(), pos.y()] + else: + if self.angle == 90: + newPos = [pos, 0] + elif self.angle == 0: + newPos = [0, pos] + else: + raise Exception("Must specify 2D coordinate for non-orthogonal lines.") + + ## check bounds (only works for orthogonal lines) + if self.angle == 90: + if self.maxRange[0] is not None: + newPos[0] = max(newPos[0], self.maxRange[0]) + if self.maxRange[1] is not None: + newPos[0] = min(newPos[0], self.maxRange[1]) + elif self.angle == 0: + if self.maxRange[0] is not None: + newPos[1] = max(newPos[1], self.maxRange[0]) + if self.maxRange[1] is not None: + newPos[1] = min(newPos[1], self.maxRange[1]) + + if self.p != newPos: + self.p = newPos + GraphicsObject.setPos(self, Point(self.p)) + self.update() + self.sigPositionChanged.emit(self) + + def getXPos(self): + return self.p[0] + + def getYPos(self): + return self.p[1] + + def getPos(self): + return self.p + + def value(self): + """Return the value of the line. Will be a single number for horizontal and + vertical lines, and a list of [x,y] values for diagonal lines.""" + if self.angle%180 == 0: + return self.getYPos() + elif self.angle%180 == 90: + return self.getXPos() + else: + return self.getPos() + + def setValue(self, v): + """Set the position of the line. If line is horizontal or vertical, v can be + a single value. Otherwise, a 2D coordinate must be specified (list, tuple and + QPointF are all acceptable).""" + self.setPos(v) + + ## broken in 4.7 + #def itemChange(self, change, val): + #if change in [self.ItemScenePositionHasChanged, self.ItemSceneHasChanged]: + #self.updateLine() + #print "update", change + #print self.getBoundingParents() + #else: + #print "ignore", change + #return GraphicsObject.itemChange(self, change, val) + + def boundingRect(self): + #br = UIGraphicsItem.boundingRect(self) + br = self.viewRect() + ## add a 4-pixel radius around the line for mouse interaction. + + px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line + if px is None: + px = 0 + w = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * px + br.setBottom(-w) + br.setTop(w) + return br.normalized() + + def paint(self, p, *args): + br = self.boundingRect() + p.setPen(self.currentPen) + p.drawLine(Point(br.right(), 0), Point(br.left(), 0)) + #p.drawRect(self.boundingRect()) + + def dataBounds(self, axis, frac=1.0, orthoRange=None): + if axis == 0: + return None ## x axis should never be auto-scaled + else: + return (0,0) + + def mouseDragEvent(self, ev): + if self.movable and ev.button() == QtCore.Qt.LeftButton: + if ev.isStart(): + self.moving = True + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + self.startPosition = self.pos() + ev.accept() + + if not self.moving: + return + + #pressDelta = self.mapToParent(ev.buttonDownPos()) - Point(self.p) + self.setPos(self.cursorOffset + self.mapToParent(ev.pos())) + self.sigDragged.emit(self) + if ev.isFinish(): + self.moving = False + self.sigPositionChangeFinished.emit(self) + #else: + #print ev + + + def mouseClickEvent(self, ev): + if self.moving and ev.button() == QtCore.Qt.RightButton: + ev.accept() + self.setPos(self.startPosition) + self.moving = False + self.sigDragged.emit(self) + self.sigPositionChangeFinished.emit(self) + + def hoverEvent(self, ev): + if (not ev.isExit()) and self.movable and ev.acceptDrags(QtCore.Qt.LeftButton): + self.setMouseHover(True) + else: + self.setMouseHover(False) + + def setMouseHover(self, hover): + ## Inform the item that the mouse is (not) hovering over it + if self.mouseHovering == hover: + return + self.mouseHovering = hover + if hover: + self.currentPen = self.hoverPen + else: + self.currentPen = self.pen + self.update() + + #def hoverEnterEvent(self, ev): + #print "line hover enter" + #ev.ignore() + #self.updateHoverPen() + + #def hoverMoveEvent(self, ev): + #print "line hover move" + #ev.ignore() + #self.updateHoverPen() + + #def hoverLeaveEvent(self, ev): + #print "line hover leave" + #ev.ignore() + #self.updateHoverPen(False) + + #def updateHoverPen(self, hover=None): + #if hover is None: + #scene = self.scene() + #hover = scene.claimEvent(self, QtCore.Qt.LeftButton, scene.Drag) + + #if hover: + #self.currentPen = fn.mkPen(255, 0,0) + #else: + #self.currentPen = self.pen + #self.update() + diff --git a/papi/pyqtgraph/graphicsItems/IsocurveItem.py b/papi/pyqtgraph/graphicsItems/IsocurveItem.py new file mode 100644 index 00000000..4474e29a --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/IsocurveItem.py @@ -0,0 +1,117 @@ + + +from .GraphicsObject import * +from .. import functions as fn +from ..Qt import QtGui, QtCore + + +class IsocurveItem(GraphicsObject): + """ + **Bases:** :class:`GraphicsObject ` + + Item displaying an isocurve of a 2D array.To align this item correctly with an + ImageItem,call isocurve.setParentItem(image) + """ + + + def __init__(self, data=None, level=0, pen='w'): + """ + Create a new isocurve item. + + ============== =============================================================== + **Arguments:** + data A 2-dimensional ndarray. Can be initialized as None, and set + later using :func:`setData ` + level The cutoff value at which to draw the isocurve. + pen The color of the curve item. Can be anything valid for + :func:`mkPen ` + ============== =============================================================== + """ + GraphicsObject.__init__(self) + + self.level = level + self.data = None + self.path = None + self.setPen(pen) + self.setData(data, level) + + + def setData(self, data, level=None): + """ + Set the data/image to draw isocurves for. + + ============== ======================================================================== + **Arguments:** + data A 2-dimensional ndarray. + level The cutoff value at which to draw the curve. If level is not specified, + the previously set level is used. + ============== ======================================================================== + """ + if level is None: + level = self.level + self.level = level + self.data = data + self.path = None + self.prepareGeometryChange() + self.update() + + + def setLevel(self, level): + """Set the level at which the isocurve is drawn.""" + self.level = level + self.path = None + self.prepareGeometryChange() + self.update() + + + def setPen(self, *args, **kwargs): + """Set the pen used to draw the isocurve. Arguments can be any that are valid + for :func:`mkPen `""" + self.pen = fn.mkPen(*args, **kwargs) + self.update() + + def setBrush(self, *args, **kwargs): + """Set the brush used to draw the isocurve. Arguments can be any that are valid + for :func:`mkBrush `""" + self.brush = fn.mkBrush(*args, **kwargs) + self.update() + + + def updateLines(self, data, level): + ##print "data:", data + ##print "level", level + #lines = fn.isocurve(data, level) + ##print len(lines) + #self.path = QtGui.QPainterPath() + #for line in lines: + #self.path.moveTo(*line[0]) + #self.path.lineTo(*line[1]) + #self.update() + self.setData(data, level) + + def boundingRect(self): + if self.data is None: + return QtCore.QRectF() + if self.path is None: + self.generatePath() + return self.path.boundingRect() + + def generatePath(self): + if self.data is None: + self.path = None + return + lines = fn.isocurve(self.data, self.level, connected=True, extendToEdge=True) + self.path = QtGui.QPainterPath() + for line in lines: + self.path.moveTo(*line[0]) + for p in line[1:]: + self.path.lineTo(*p) + + def paint(self, p, *args): + if self.data is None: + return + if self.path is None: + self.generatePath() + p.setPen(self.pen) + p.drawPath(self.path) + \ No newline at end of file diff --git a/papi/pyqtgraph/graphicsItems/ItemGroup.py b/papi/pyqtgraph/graphicsItems/ItemGroup.py new file mode 100644 index 00000000..4eb0ee0d --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ItemGroup.py @@ -0,0 +1,23 @@ +from ..Qt import QtGui, QtCore +from .GraphicsObject import GraphicsObject + +__all__ = ['ItemGroup'] +class ItemGroup(GraphicsObject): + """ + Replacement for QGraphicsItemGroup + """ + + def __init__(self, *args): + GraphicsObject.__init__(self, *args) + if hasattr(self, "ItemHasNoContents"): + self.setFlag(self.ItemHasNoContents) + + def boundingRect(self): + return QtCore.QRectF() + + def paint(self, *args): + pass + + def addItem(self, item): + item.setParentItem(self) + diff --git a/papi/pyqtgraph/graphicsItems/LabelItem.py b/papi/pyqtgraph/graphicsItems/LabelItem.py new file mode 100644 index 00000000..37980ee3 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/LabelItem.py @@ -0,0 +1,142 @@ +from ..Qt import QtGui, QtCore +from .. import functions as fn +from .GraphicsWidget import GraphicsWidget +from .GraphicsWidgetAnchor import GraphicsWidgetAnchor +from .. import getConfigOption + + +__all__ = ['LabelItem'] + +class LabelItem(GraphicsWidget, GraphicsWidgetAnchor): + """ + GraphicsWidget displaying text. + Used mainly as axis labels, titles, etc. + + Note: To display text inside a scaled view (ViewBox, PlotWidget, etc) use TextItem + """ + + + def __init__(self, text=' ', parent=None, angle=0, **args): + GraphicsWidget.__init__(self, parent) + GraphicsWidgetAnchor.__init__(self) + self.item = QtGui.QGraphicsTextItem(self) + self.opts = { + 'color': None, + 'justify': 'center' + } + self.opts.update(args) + self._sizeHint = {} + self.setText(text) + self.setAngle(angle) + + def setAttr(self, attr, value): + """Set default text properties. See setText() for accepted parameters.""" + self.opts[attr] = value + + def setText(self, text, **args): + """Set the text and text properties in the label. Accepts optional arguments for auto-generating + a CSS style string: + + ==================== ============================== + **Style Arguments:** + color (str) example: 'CCFF00' + size (str) example: '8pt' + bold (bool) + italic (bool) + ==================== ============================== + """ + self.text = text + opts = self.opts + for k in args: + opts[k] = args[k] + + optlist = [] + + color = self.opts['color'] + if color is None: + color = getConfigOption('foreground') + color = fn.mkColor(color) + optlist.append('color: #' + fn.colorStr(color)[:6]) + if 'size' in opts: + optlist.append('font-size: ' + opts['size']) + if 'bold' in opts and opts['bold'] in [True, False]: + optlist.append('font-weight: ' + {True:'bold', False:'normal'}[opts['bold']]) + if 'italic' in opts and opts['italic'] in [True, False]: + optlist.append('font-style: ' + {True:'italic', False:'normal'}[opts['italic']]) + full = "%s" % ('; '.join(optlist), text) + #print full + self.item.setHtml(full) + self.updateMin() + self.resizeEvent(None) + self.updateGeometry() + + def resizeEvent(self, ev): + #c1 = self.boundingRect().center() + #c2 = self.item.mapToParent(self.item.boundingRect().center()) # + self.item.pos() + #dif = c1 - c2 + #self.item.moveBy(dif.x(), dif.y()) + #print c1, c2, dif, self.item.pos() + self.item.setPos(0,0) + bounds = self.itemRect() + left = self.mapFromItem(self.item, QtCore.QPointF(0,0)) - self.mapFromItem(self.item, QtCore.QPointF(1,0)) + rect = self.rect() + + if self.opts['justify'] == 'left': + if left.x() != 0: + bounds.moveLeft(rect.left()) + if left.y() < 0: + bounds.moveTop(rect.top()) + elif left.y() > 0: + bounds.moveBottom(rect.bottom()) + + elif self.opts['justify'] == 'center': + bounds.moveCenter(rect.center()) + #bounds = self.itemRect() + #self.item.setPos(self.width()/2. - bounds.width()/2., 0) + elif self.opts['justify'] == 'right': + if left.x() != 0: + bounds.moveRight(rect.right()) + if left.y() < 0: + bounds.moveBottom(rect.bottom()) + elif left.y() > 0: + bounds.moveTop(rect.top()) + #bounds = self.itemRect() + #self.item.setPos(self.width() - bounds.width(), 0) + + self.item.setPos(bounds.topLeft() - self.itemRect().topLeft()) + self.updateMin() + + def setAngle(self, angle): + self.angle = angle + self.item.resetTransform() + self.item.rotate(angle) + self.updateMin() + + + def updateMin(self): + bounds = self.itemRect() + self.setMinimumWidth(bounds.width()) + self.setMinimumHeight(bounds.height()) + + self._sizeHint = { + QtCore.Qt.MinimumSize: (bounds.width(), bounds.height()), + QtCore.Qt.PreferredSize: (bounds.width(), bounds.height()), + QtCore.Qt.MaximumSize: (-1, -1), #bounds.width()*2, bounds.height()*2), + QtCore.Qt.MinimumDescent: (0, 0) ##?? what is this? + } + self.updateGeometry() + + def sizeHint(self, hint, constraint): + if hint not in self._sizeHint: + return QtCore.QSizeF(0, 0) + return QtCore.QSizeF(*self._sizeHint[hint]) + + def itemRect(self): + return self.item.mapRectToParent(self.item.boundingRect()) + + #def paint(self, p, *args): + #p.setPen(fn.mkPen('r')) + #p.drawRect(self.rect()) + #p.setPen(fn.mkPen('g')) + #p.drawRect(self.itemRect()) + diff --git a/papi/pyqtgraph/graphicsItems/LegendItem.py b/papi/pyqtgraph/graphicsItems/LegendItem.py new file mode 100644 index 00000000..20d6416e --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/LegendItem.py @@ -0,0 +1,174 @@ +from .GraphicsWidget import GraphicsWidget +from .LabelItem import LabelItem +from ..Qt import QtGui, QtCore +from .. import functions as fn +from ..Point import Point +from .ScatterPlotItem import ScatterPlotItem, drawSymbol +from .PlotDataItem import PlotDataItem +from .GraphicsWidgetAnchor import GraphicsWidgetAnchor +__all__ = ['LegendItem'] + +class LegendItem(GraphicsWidget, GraphicsWidgetAnchor): + """ + Displays a legend used for describing the contents of a plot. + LegendItems are most commonly created by calling PlotItem.addLegend(). + + Note that this item should not be added directly to a PlotItem. Instead, + Make it a direct descendant of the PlotItem:: + + legend.setParentItem(plotItem) + + """ + def __init__(self, size=None, offset=None): + """ + ============== =============================================================== + **Arguments:** + size Specifies the fixed size (width, height) of the legend. If + this argument is omitted, the legend will autimatically resize + to fit its contents. + offset Specifies the offset position relative to the legend's parent. + Positive values offset from the left or top; negative values + offset from the right or bottom. If offset is None, the + legend must be anchored manually by calling anchor() or + positioned by calling setPos(). + ============== =============================================================== + + """ + + + GraphicsWidget.__init__(self) + GraphicsWidgetAnchor.__init__(self) + self.setFlag(self.ItemIgnoresTransformations) + self.layout = QtGui.QGraphicsGridLayout() + self.setLayout(self.layout) + self.items = [] + self.size = size + self.offset = offset + if size is not None: + self.setGeometry(QtCore.QRectF(0, 0, self.size[0], self.size[1])) + + def setParentItem(self, p): + ret = GraphicsWidget.setParentItem(self, p) + if self.offset is not None: + offset = Point(self.offset) + anchorx = 1 if offset[0] <= 0 else 0 + anchory = 1 if offset[1] <= 0 else 0 + anchor = (anchorx, anchory) + self.anchor(itemPos=anchor, parentPos=anchor, offset=offset) + return ret + + def addItem(self, item, name): + """ + Add a new entry to the legend. + + ============== ======================================================== + **Arguments:** + item A PlotDataItem from which the line and point style + of the item will be determined or an instance of + ItemSample (or a subclass), allowing the item display + to be customized. + title The title to display for this item. Simple HTML allowed. + ============== ======================================================== + """ + label = LabelItem(name) + if isinstance(item, ItemSample): + sample = item + else: + sample = ItemSample(item) + row = self.layout.rowCount() + self.items.append((sample, label)) + self.layout.addItem(sample, row, 0) + self.layout.addItem(label, row, 1) + self.updateSize() + + def removeItem(self, name): + """ + Removes one item from the legend. + + ============== ======================================================== + **Arguments:** + title The title displayed for this item. + ============== ======================================================== + """ + # Thanks, Ulrich! + # cycle for a match + for sample, label in self.items: + if label.text == name: # hit + self.items.remove( (sample, label) ) # remove from itemlist + self.layout.removeItem(sample) # remove from layout + sample.close() # remove from drawing + self.layout.removeItem(label) + label.close() + self.updateSize() # redraq box + + def updateSize(self): + if self.size is not None: + return + + height = 0 + width = 0 + #print("-------") + for sample, label in self.items: + height += max(sample.height(), label.height()) + 3 + width = max(width, sample.width()+label.width()) + #print(width, height) + #print width, height + self.setGeometry(0, 0, width+25, height) + + def boundingRect(self): + return QtCore.QRectF(0, 0, self.width(), self.height()) + + def paint(self, p, *args): + p.setPen(fn.mkPen(255,255,255,100)) + p.setBrush(fn.mkBrush(100,100,100,50)) + p.drawRect(self.boundingRect()) + + def hoverEvent(self, ev): + ev.acceptDrags(QtCore.Qt.LeftButton) + + def mouseDragEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + dpos = ev.pos() - ev.lastPos() + self.autoAnchor(self.pos() + dpos) + +class ItemSample(GraphicsWidget): + """ Class responsible for drawing a single item in a LegendItem (sans label). + + This may be subclassed to draw custom graphics in a Legend. + """ + ## Todo: make this more generic; let each item decide how it should be represented. + def __init__(self, item): + GraphicsWidget.__init__(self) + self.item = item + + def boundingRect(self): + return QtCore.QRectF(0, 0, 20, 20) + + def paint(self, p, *args): + #p.setRenderHint(p.Antialiasing) # only if the data is antialiased. + opts = self.item.opts + + if opts.get('fillLevel',None) is not None and opts.get('fillBrush',None) is not None: + p.setBrush(fn.mkBrush(opts['fillBrush'])) + p.setPen(fn.mkPen(None)) + p.drawPolygon(QtGui.QPolygonF([QtCore.QPointF(2,18), QtCore.QPointF(18,2), QtCore.QPointF(18,18)])) + + if not isinstance(self.item, ScatterPlotItem): + p.setPen(fn.mkPen(opts['pen'])) + p.drawLine(2, 18, 18, 2) + + symbol = opts.get('symbol', None) + if symbol is not None: + if isinstance(self.item, PlotDataItem): + opts = self.item.scatter.opts + + pen = fn.mkPen(opts['pen']) + brush = fn.mkBrush(opts['brush']) + size = opts['size'] + + p.translate(10,10) + path = drawSymbol(p, symbol, size, pen, brush) + + + + diff --git a/papi/pyqtgraph/graphicsItems/LinearRegionItem.py b/papi/pyqtgraph/graphicsItems/LinearRegionItem.py new file mode 100644 index 00000000..e139190b --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/LinearRegionItem.py @@ -0,0 +1,290 @@ +from ..Qt import QtGui, QtCore +from .UIGraphicsItem import UIGraphicsItem +from .InfiniteLine import InfiniteLine +from .. import functions as fn +from .. import debug as debug + +__all__ = ['LinearRegionItem'] + +class LinearRegionItem(UIGraphicsItem): + """ + **Bases:** :class:`UIGraphicsItem ` + + Used for marking a horizontal or vertical region in plots. + The region can be dragged and is bounded by lines which can be dragged individually. + + =============================== ============================================================================= + **Signals:** + sigRegionChangeFinished(self) Emitted when the user has finished dragging the region (or one of its lines) + and when the region is changed programatically. + sigRegionChanged(self) Emitted while the user is dragging the region (or one of its lines) + and when the region is changed programatically. + =============================== ============================================================================= + """ + + sigRegionChangeFinished = QtCore.Signal(object) + sigRegionChanged = QtCore.Signal(object) + Vertical = 0 + Horizontal = 1 + + def __init__(self, values=[0,1], orientation=None, brush=None, movable=True, bounds=None): + """Create a new LinearRegionItem. + + ============== ===================================================================== + **Arguments:** + values A list of the positions of the lines in the region. These are not + limits; limits can be set by specifying bounds. + orientation Options are LinearRegionItem.Vertical or LinearRegionItem.Horizontal. + If not specified it will be vertical. + brush Defines the brush that fills the region. Can be any arguments that + are valid for :func:`mkBrush `. Default is + transparent blue. + movable If True, the region and individual lines are movable by the user; if + False, they are static. + bounds Optional [min, max] bounding values for the region + ============== ===================================================================== + """ + + UIGraphicsItem.__init__(self) + if orientation is None: + orientation = LinearRegionItem.Vertical + self.orientation = orientation + self.bounds = QtCore.QRectF() + self.blockLineSignal = False + self.moving = False + self.mouseHovering = False + + if orientation == LinearRegionItem.Horizontal: + self.lines = [ + InfiniteLine(QtCore.QPointF(0, values[0]), 0, movable=movable, bounds=bounds), + InfiniteLine(QtCore.QPointF(0, values[1]), 0, movable=movable, bounds=bounds)] + elif orientation == LinearRegionItem.Vertical: + self.lines = [ + InfiniteLine(QtCore.QPointF(values[1], 0), 90, movable=movable, bounds=bounds), + InfiniteLine(QtCore.QPointF(values[0], 0), 90, movable=movable, bounds=bounds)] + else: + raise Exception('Orientation must be one of LinearRegionItem.Vertical or LinearRegionItem.Horizontal') + + + for l in self.lines: + l.setParentItem(self) + l.sigPositionChangeFinished.connect(self.lineMoveFinished) + l.sigPositionChanged.connect(self.lineMoved) + + if brush is None: + brush = QtGui.QBrush(QtGui.QColor(0, 0, 255, 50)) + self.setBrush(brush) + + self.setMovable(movable) + + def getRegion(self): + """Return the values at the edges of the region.""" + #if self.orientation[0] == 'h': + #r = (self.bounds.top(), self.bounds.bottom()) + #else: + #r = (self.bounds.left(), self.bounds.right()) + r = [self.lines[0].value(), self.lines[1].value()] + return (min(r), max(r)) + + def setRegion(self, rgn): + """Set the values for the edges of the region. + + ============== ============================================== + **Arguments:** + rgn A list or tuple of the lower and upper values. + ============== ============================================== + """ + if self.lines[0].value() == rgn[0] and self.lines[1].value() == rgn[1]: + return + self.blockLineSignal = True + self.lines[0].setValue(rgn[0]) + self.blockLineSignal = False + self.lines[1].setValue(rgn[1]) + #self.blockLineSignal = False + self.lineMoved() + self.lineMoveFinished() + + def setBrush(self, *br, **kargs): + """Set the brush that fills the region. Can have any arguments that are valid + for :func:`mkBrush `. + """ + self.brush = fn.mkBrush(*br, **kargs) + self.currentBrush = self.brush + + def setBounds(self, bounds): + """Optional [min, max] bounding values for the region. To have no bounds on the + region use [None, None]. + Does not affect the current position of the region unless it is outside the new bounds. + See :func:`setRegion ` to set the position + of the region.""" + for l in self.lines: + l.setBounds(bounds) + + def setMovable(self, m): + """Set lines to be movable by the user, or not. If lines are movable, they will + also accept HoverEvents.""" + for l in self.lines: + l.setMovable(m) + self.movable = m + self.setAcceptHoverEvents(m) + + def boundingRect(self): + br = UIGraphicsItem.boundingRect(self) + rng = self.getRegion() + if self.orientation == LinearRegionItem.Vertical: + br.setLeft(rng[0]) + br.setRight(rng[1]) + else: + br.setTop(rng[0]) + br.setBottom(rng[1]) + return br.normalized() + + def paint(self, p, *args): + profiler = debug.Profiler() + UIGraphicsItem.paint(self, p, *args) + p.setBrush(self.currentBrush) + p.setPen(fn.mkPen(None)) + p.drawRect(self.boundingRect()) + + def dataBounds(self, axis, frac=1.0, orthoRange=None): + if axis == self.orientation: + return self.getRegion() + else: + return None + + def lineMoved(self): + if self.blockLineSignal: + return + self.prepareGeometryChange() + #self.emit(QtCore.SIGNAL('regionChanged'), self) + self.sigRegionChanged.emit(self) + + def lineMoveFinished(self): + #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) + self.sigRegionChangeFinished.emit(self) + + + #def updateBounds(self): + #vb = self.view().viewRect() + #vals = [self.lines[0].value(), self.lines[1].value()] + #if self.orientation[0] == 'h': + #vb.setTop(min(vals)) + #vb.setBottom(max(vals)) + #else: + #vb.setLeft(min(vals)) + #vb.setRight(max(vals)) + #if vb != self.bounds: + #self.bounds = vb + #self.rect.setRect(vb) + + #def mousePressEvent(self, ev): + #if not self.movable: + #ev.ignore() + #return + #for l in self.lines: + #l.mousePressEvent(ev) ## pass event to both lines so they move together + ##if self.movable and ev.button() == QtCore.Qt.LeftButton: + ##ev.accept() + ##self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p) + ##else: + ##ev.ignore() + + #def mouseReleaseEvent(self, ev): + #for l in self.lines: + #l.mouseReleaseEvent(ev) + + #def mouseMoveEvent(self, ev): + ##print "move", ev.pos() + #if not self.movable: + #return + #self.lines[0].blockSignals(True) # only want to update once + #for l in self.lines: + #l.mouseMoveEvent(ev) + #self.lines[0].blockSignals(False) + ##self.setPos(self.mapToParent(ev.pos()) - self.pressDelta) + ##self.emit(QtCore.SIGNAL('dragged'), self) + + def mouseDragEvent(self, ev): + if not self.movable or int(ev.button() & QtCore.Qt.LeftButton) == 0: + return + ev.accept() + + if ev.isStart(): + bdp = ev.buttonDownPos() + self.cursorOffsets = [l.pos() - bdp for l in self.lines] + self.startPositions = [l.pos() for l in self.lines] + self.moving = True + + if not self.moving: + return + + #delta = ev.pos() - ev.lastPos() + self.lines[0].blockSignals(True) # only want to update once + for i, l in enumerate(self.lines): + l.setPos(self.cursorOffsets[i] + ev.pos()) + #l.setPos(l.pos()+delta) + #l.mouseDragEvent(ev) + self.lines[0].blockSignals(False) + self.prepareGeometryChange() + + if ev.isFinish(): + self.moving = False + self.sigRegionChangeFinished.emit(self) + else: + self.sigRegionChanged.emit(self) + + def mouseClickEvent(self, ev): + if self.moving and ev.button() == QtCore.Qt.RightButton: + ev.accept() + for i, l in enumerate(self.lines): + l.setPos(self.startPositions[i]) + self.moving = False + self.sigRegionChanged.emit(self) + self.sigRegionChangeFinished.emit(self) + + + def hoverEvent(self, ev): + if self.movable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + self.setMouseHover(True) + else: + self.setMouseHover(False) + + def setMouseHover(self, hover): + ## Inform the item that the mouse is(not) hovering over it + if self.mouseHovering == hover: + return + self.mouseHovering = hover + if hover: + c = self.brush.color() + c.setAlpha(c.alpha() * 2) + self.currentBrush = fn.mkBrush(c) + else: + self.currentBrush = self.brush + self.update() + + #def hoverEnterEvent(self, ev): + #print "rgn hover enter" + #ev.ignore() + #self.updateHoverBrush() + + #def hoverMoveEvent(self, ev): + #print "rgn hover move" + #ev.ignore() + #self.updateHoverBrush() + + #def hoverLeaveEvent(self, ev): + #print "rgn hover leave" + #ev.ignore() + #self.updateHoverBrush(False) + + #def updateHoverBrush(self, hover=None): + #if hover is None: + #scene = self.scene() + #hover = scene.claimEvent(self, QtCore.Qt.LeftButton, scene.Drag) + + #if hover: + #self.currentBrush = fn.mkBrush(255, 0,0,100) + #else: + #self.currentBrush = self.brush + #self.update() + diff --git a/papi/pyqtgraph/graphicsItems/MultiPlotItem.py b/papi/pyqtgraph/graphicsItems/MultiPlotItem.py new file mode 100644 index 00000000..be775d4a --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/MultiPlotItem.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +""" +MultiPlotItem.py - Graphics item used for displaying an array of PlotItems +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from numpy import ndarray +from . import GraphicsLayout +from ..metaarray import * + + +__all__ = ['MultiPlotItem'] +class MultiPlotItem(GraphicsLayout.GraphicsLayout): + """ + Automatically generates a grid of plots from a multi-dimensional array + """ + def __init__(self, *args, **kwds): + GraphicsLayout.GraphicsLayout.__init__(self, *args, **kwds) + self.plots = [] + + + def plot(self, data): + #self.layout.clear() + + if hasattr(data, 'implements') and data.implements('MetaArray'): + if data.ndim != 2: + raise Exception("MultiPlot currently only accepts 2D MetaArray.") + ic = data.infoCopy() + ax = 0 + for i in [0, 1]: + if 'cols' in ic[i]: + ax = i + break + #print "Plotting using axis %d as columns (%d plots)" % (ax, data.shape[ax]) + for i in range(data.shape[ax]): + pi = self.addPlot() + self.nextRow() + sl = [slice(None)] * 2 + sl[ax] = i + pi.plot(data[tuple(sl)]) + #self.layout.addItem(pi, i, 0) + self.plots.append((pi, i, 0)) + info = ic[ax]['cols'][i] + title = info.get('title', info.get('name', None)) + units = info.get('units', None) + pi.setLabel('left', text=title, units=units) + info = ic[1-ax] + title = info.get('title', info.get('name', None)) + units = info.get('units', None) + pi.setLabel('bottom', text=title, units=units) + else: + raise Exception("Data type %s not (yet?) supported for MultiPlot." % type(data)) + + def close(self): + for p in self.plots: + p[0].close() + self.plots = None + self.clear() + + + diff --git a/papi/pyqtgraph/graphicsItems/PlotCurveItem.py b/papi/pyqtgraph/graphicsItems/PlotCurveItem.py new file mode 100644 index 00000000..3d3e969d --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/PlotCurveItem.py @@ -0,0 +1,598 @@ +from ..Qt import QtGui, QtCore +try: + from ..Qt import QtOpenGL + HAVE_OPENGL = True +except: + HAVE_OPENGL = False + +import numpy as np +from .GraphicsObject import GraphicsObject +from .. import functions as fn +from ..Point import Point +import struct, sys +from .. import getConfigOption +from .. import debug + +__all__ = ['PlotCurveItem'] +class PlotCurveItem(GraphicsObject): + + + """ + Class representing a single plot curve. Instances of this class are created + automatically as part of PlotDataItem; these rarely need to be instantiated + directly. + + Features: + + - Fast data update + - Fill under curve + - Mouse interaction + + ==================== =============================================== + **Signals:** + sigPlotChanged(self) Emitted when the data being plotted has changed + sigClicked(self) Emitted when the curve is clicked + ==================== =============================================== + """ + + sigPlotChanged = QtCore.Signal(object) + sigClicked = QtCore.Signal(object) + + def __init__(self, *args, **kargs): + """ + Forwards all arguments to :func:`setData `. + + Some extra arguments are accepted as well: + + ============== ======================================================= + **Arguments:** + parent The parent GraphicsObject (optional) + clickable If True, the item will emit sigClicked when it is + clicked on. Defaults to False. + ============== ======================================================= + """ + GraphicsObject.__init__(self, kargs.get('parent', None)) + self.clear() + + ## this is disastrous for performance. + #self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache) + + self.metaData = {} + self.opts = { + 'pen': fn.mkPen('w'), + 'shadowPen': None, + 'fillLevel': None, + 'brush': None, + 'stepMode': False, + 'name': None, + 'antialias': getConfigOption('antialias'), + 'connect': 'all', + 'mouseWidth': 8, # width of shape responding to mouse click + } + self.setClickable(kargs.get('clickable', False)) + self.setData(*args, **kargs) + + def implements(self, interface=None): + ints = ['plotData'] + if interface is None: + return ints + return interface in ints + + def name(self): + return self.opts.get('name', None) + + def setClickable(self, s, width=None): + """Sets whether the item responds to mouse clicks. + + The *width* argument specifies the width in pixels orthogonal to the + curve that will respond to a mouse click. + """ + self.clickable = s + if width is not None: + self.opts['mouseWidth'] = width + self._mouseShape = None + self._boundingRect = None + + + def getData(self): + return self.xData, self.yData + + def dataBounds(self, ax, frac=1.0, orthoRange=None): + ## Need this to run as fast as possible. + ## check cache first: + cache = self._boundsCache[ax] + if cache is not None and cache[0] == (frac, orthoRange): + return cache[1] + + (x, y) = self.getData() + if x is None or len(x) == 0: + return (None, None) + + if ax == 0: + d = x + d2 = y + elif ax == 1: + d = y + d2 = x + + ## If an orthogonal range is specified, mask the data now + if orthoRange is not None: + mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) + d = d[mask] + #d2 = d2[mask] + + if len(d) == 0: + return (None, None) + + ## Get min/max (or percentiles) of the requested data range + if frac >= 1.0: + b = (np.nanmin(d), np.nanmax(d)) + elif frac <= 0.0: + raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) + else: + mask = np.isfinite(d) + d = d[mask] + b = np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) + + ## adjust for fill level + if ax == 1 and self.opts['fillLevel'] is not None: + b = (min(b[0], self.opts['fillLevel']), max(b[1], self.opts['fillLevel'])) + + ## Add pen width only if it is non-cosmetic. + pen = self.opts['pen'] + spen = self.opts['shadowPen'] + if not pen.isCosmetic(): + b = (b[0] - pen.widthF()*0.7072, b[1] + pen.widthF()*0.7072) + if spen is not None and not spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen: + b = (b[0] - spen.widthF()*0.7072, b[1] + spen.widthF()*0.7072) + + self._boundsCache[ax] = [(frac, orthoRange), b] + return b + + def pixelPadding(self): + pen = self.opts['pen'] + spen = self.opts['shadowPen'] + w = 0 + if pen.isCosmetic(): + w += pen.widthF()*0.7072 + if spen is not None and spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen: + w = max(w, spen.widthF()*0.7072) + if self.clickable: + w = max(w, self.opts['mouseWidth']//2 + 1) + return w + + def boundingRect(self): + if self._boundingRect is None: + (xmn, xmx) = self.dataBounds(ax=0) + (ymn, ymx) = self.dataBounds(ax=1) + if xmn is None: + return QtCore.QRectF() + + px = py = 0.0 + pxPad = self.pixelPadding() + if pxPad > 0: + # determine length of pixel in local x, y directions + px, py = self.pixelVectors() + try: + px = 0 if px is None else px.length() + except OverflowError: + px = 0 + try: + py = 0 if py is None else py.length() + except OverflowError: + py = 0 + + # return bounds expanded by pixel size + px *= pxPad + py *= pxPad + #px += self._maxSpotWidth * 0.5 + #py += self._maxSpotWidth * 0.5 + self._boundingRect = QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) + + return self._boundingRect + + def viewTransformChanged(self): + self.invalidateBounds() + self.prepareGeometryChange() + + #def boundingRect(self): + #if self._boundingRect is None: + #(x, y) = self.getData() + #if x is None or y is None or len(x) == 0 or len(y) == 0: + #return QtCore.QRectF() + + + #if self.opts['shadowPen'] is not None: + #lineWidth = (max(self.opts['pen'].width(), self.opts['shadowPen'].width()) + 1) + #else: + #lineWidth = (self.opts['pen'].width()+1) + + + #pixels = self.pixelVectors() + #if pixels == (None, None): + #pixels = [Point(0,0), Point(0,0)] + + #xmin = x.min() + #xmax = x.max() + #ymin = y.min() + #ymax = y.max() + + #if self.opts['fillLevel'] is not None: + #ymin = min(ymin, self.opts['fillLevel']) + #ymax = max(ymax, self.opts['fillLevel']) + + #xmin -= pixels[0].x() * lineWidth + #xmax += pixels[0].x() * lineWidth + #ymin -= abs(pixels[1].y()) * lineWidth + #ymax += abs(pixels[1].y()) * lineWidth + + #self._boundingRect = QtCore.QRectF(xmin, ymin, xmax-xmin, ymax-ymin) + #return self._boundingRect + + + def invalidateBounds(self): + self._boundingRect = None + self._boundsCache = [None, None] + + def setPen(self, *args, **kargs): + """Set the pen used to draw the curve.""" + self.opts['pen'] = fn.mkPen(*args, **kargs) + self.invalidateBounds() + self.update() + + def setShadowPen(self, *args, **kargs): + """Set the shadow pen used to draw behind tyhe primary pen. + This pen must have a larger width than the primary + pen to be visible. + """ + self.opts['shadowPen'] = fn.mkPen(*args, **kargs) + self.invalidateBounds() + self.update() + + def setBrush(self, *args, **kargs): + """Set the brush used when filling the area under the curve""" + self.opts['brush'] = fn.mkBrush(*args, **kargs) + self.invalidateBounds() + self.update() + + def setFillLevel(self, level): + """Set the level filled to when filling under the curve""" + self.opts['fillLevel'] = level + self.fillPath = None + self.invalidateBounds() + self.update() + + def setData(self, *args, **kargs): + """ + ============== ======================================================== + **Arguments:** + x, y (numpy arrays) Data to show + pen Pen to use when drawing. Any single argument accepted by + :func:`mkPen ` is allowed. + shadowPen Pen for drawing behind the primary pen. Usually this + is used to emphasize the curve by providing a + high-contrast border. Any single argument accepted by + :func:`mkPen ` is allowed. + fillLevel (float or None) Fill the area 'under' the curve to + *fillLevel* + brush QBrush to use when filling. Any single argument accepted + by :func:`mkBrush ` is allowed. + antialias (bool) Whether to use antialiasing when drawing. This + is disabled by default because it decreases performance. + stepMode If True, two orthogonal lines are drawn for each sample + as steps. This is commonly used when drawing histograms. + Note that in this case, len(x) == len(y) + 1 + connect Argument specifying how vertexes should be connected + by line segments. Default is "all", indicating full + connection. "pairs" causes only even-numbered segments + to be drawn. "finite" causes segments to be omitted if + they are attached to nan or inf values. For any other + connectivity, specify an array of boolean values. + ============== ======================================================== + + If non-keyword arguments are used, they will be interpreted as + setData(y) for a single argument and setData(x, y) for two + arguments. + + + """ + self.updateData(*args, **kargs) + + def updateData(self, *args, **kargs): + profiler = debug.Profiler() + + if len(args) == 1: + kargs['y'] = args[0] + elif len(args) == 2: + kargs['x'] = args[0] + kargs['y'] = args[1] + + if 'y' not in kargs or kargs['y'] is None: + kargs['y'] = np.array([]) + if 'x' not in kargs or kargs['x'] is None: + kargs['x'] = np.arange(len(kargs['y'])) + + for k in ['x', 'y']: + data = kargs[k] + if isinstance(data, list): + data = np.array(data) + kargs[k] = data + if not isinstance(data, np.ndarray) or data.ndim > 1: + raise Exception("Plot data must be 1D ndarray.") + if 'complex' in str(data.dtype): + raise Exception("Can not plot complex data types.") + + profiler("data checks") + + #self.setCacheMode(QtGui.QGraphicsItem.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly + ## Test this bug with test_PlotWidget and zoom in on the animated plot + self.invalidateBounds() + self.prepareGeometryChange() + self.informViewBoundsChanged() + self.yData = kargs['y'].view(np.ndarray) + self.xData = kargs['x'].view(np.ndarray) + + profiler('copy') + + if 'stepMode' in kargs: + self.opts['stepMode'] = kargs['stepMode'] + + if self.opts['stepMode'] is True: + if len(self.xData) != len(self.yData)+1: ## allow difference of 1 for step mode plots + raise Exception("len(X) must be len(Y)+1 since stepMode=True (got %s and %s)" % (self.xData.shape, self.yData.shape)) + else: + if self.xData.shape != self.yData.shape: ## allow difference of 1 for step mode plots + raise Exception("X and Y arrays must be the same shape--got %s and %s." % (self.xData.shape, self.yData.shape)) + + self.path = None + self.fillPath = None + self._mouseShape = None + #self.xDisp = self.yDisp = None + + if 'name' in kargs: + self.opts['name'] = kargs['name'] + if 'connect' in kargs: + self.opts['connect'] = kargs['connect'] + if 'pen' in kargs: + self.setPen(kargs['pen']) + if 'shadowPen' in kargs: + self.setShadowPen(kargs['shadowPen']) + if 'fillLevel' in kargs: + self.setFillLevel(kargs['fillLevel']) + if 'brush' in kargs: + self.setBrush(kargs['brush']) + if 'antialias' in kargs: + self.opts['antialias'] = kargs['antialias'] + + + profiler('set') + self.update() + profiler('update') + self.sigPlotChanged.emit(self) + profiler('emit') + + def generatePath(self, x, y): + if self.opts['stepMode']: + ## each value in the x/y arrays generates 2 points. + x2 = np.empty((len(x),2), dtype=x.dtype) + x2[:] = x[:,np.newaxis] + if self.opts['fillLevel'] is None: + x = x2.reshape(x2.size)[1:-1] + y2 = np.empty((len(y),2), dtype=y.dtype) + y2[:] = y[:,np.newaxis] + y = y2.reshape(y2.size) + else: + ## If we have a fill level, add two extra points at either end + x = x2.reshape(x2.size) + y2 = np.empty((len(y)+2,2), dtype=y.dtype) + y2[1:-1] = y[:,np.newaxis] + y = y2.reshape(y2.size)[1:-1] + y[0] = self.opts['fillLevel'] + y[-1] = self.opts['fillLevel'] + + path = fn.arrayToQPath(x, y, connect=self.opts['connect']) + + return path + + + def getPath(self): + if self.path is None: + x,y = self.getData() + if x is None or len(x) == 0 or y is None or len(y) == 0: + self.path = QtGui.QPainterPath() + else: + self.path = self.generatePath(*self.getData()) + self.fillPath = None + self._mouseShape = None + + return self.path + + @debug.warnOnException ## raising an exception here causes crash + def paint(self, p, opt, widget): + profiler = debug.Profiler() + if self.xData is None or len(self.xData) == 0: + return + + if HAVE_OPENGL and getConfigOption('enableExperimental') and isinstance(widget, QtOpenGL.QGLWidget): + self.paintGL(p, opt, widget) + return + + x = None + y = None + path = self.getPath() + + profiler('generate path') + + if self._exportOpts is not False: + aa = self._exportOpts.get('antialias', True) + else: + aa = self.opts['antialias'] + + p.setRenderHint(p.Antialiasing, aa) + + + if self.opts['brush'] is not None and self.opts['fillLevel'] is not None: + if self.fillPath is None: + if x is None: + x,y = self.getData() + p2 = QtGui.QPainterPath(self.path) + p2.lineTo(x[-1], self.opts['fillLevel']) + p2.lineTo(x[0], self.opts['fillLevel']) + p2.lineTo(x[0], y[0]) + p2.closeSubpath() + self.fillPath = p2 + + profiler('generate fill path') + p.fillPath(self.fillPath, self.opts['brush']) + profiler('draw fill path') + + sp = fn.mkPen(self.opts['shadowPen']) + cp = fn.mkPen(self.opts['pen']) + + ## Copy pens and apply alpha adjustment + #sp = QtGui.QPen(self.opts['shadowPen']) + #cp = QtGui.QPen(self.opts['pen']) + #for pen in [sp, cp]: + #if pen is None: + #continue + #c = pen.color() + #c.setAlpha(c.alpha() * self.opts['alphaHint']) + #pen.setColor(c) + ##pen.setCosmetic(True) + + + + if sp is not None and sp.style() != QtCore.Qt.NoPen: + p.setPen(sp) + p.drawPath(path) + p.setPen(cp) + p.drawPath(path) + profiler('drawPath') + + #print "Render hints:", int(p.renderHints()) + #p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) + #p.drawRect(self.boundingRect()) + + def paintGL(self, p, opt, widget): + p.beginNativePainting() + import OpenGL.GL as gl + + ## set clipping viewport + view = self.getViewBox() + if view is not None: + rect = view.mapRectToItem(self, view.boundingRect()) + #gl.glViewport(int(rect.x()), int(rect.y()), int(rect.width()), int(rect.height())) + + #gl.glTranslate(-rect.x(), -rect.y(), 0) + + gl.glEnable(gl.GL_STENCIL_TEST) + gl.glColorMask(gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE, gl.GL_FALSE) # disable drawing to frame buffer + gl.glDepthMask(gl.GL_FALSE) # disable drawing to depth buffer + gl.glStencilFunc(gl.GL_NEVER, 1, 0xFF) + gl.glStencilOp(gl.GL_REPLACE, gl.GL_KEEP, gl.GL_KEEP) + + ## draw stencil pattern + gl.glStencilMask(0xFF) + gl.glClear(gl.GL_STENCIL_BUFFER_BIT) + gl.glBegin(gl.GL_TRIANGLES) + gl.glVertex2f(rect.x(), rect.y()) + gl.glVertex2f(rect.x()+rect.width(), rect.y()) + gl.glVertex2f(rect.x(), rect.y()+rect.height()) + gl.glVertex2f(rect.x()+rect.width(), rect.y()+rect.height()) + gl.glVertex2f(rect.x()+rect.width(), rect.y()) + gl.glVertex2f(rect.x(), rect.y()+rect.height()) + gl.glEnd() + + gl.glColorMask(gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE, gl.GL_TRUE) + gl.glDepthMask(gl.GL_TRUE) + gl.glStencilMask(0x00) + gl.glStencilFunc(gl.GL_EQUAL, 1, 0xFF) + + try: + x, y = self.getData() + pos = np.empty((len(x), 2)) + pos[:,0] = x + pos[:,1] = y + gl.glEnableClientState(gl.GL_VERTEX_ARRAY) + try: + gl.glVertexPointerf(pos) + pen = fn.mkPen(self.opts['pen']) + color = pen.color() + gl.glColor4f(color.red()/255., color.green()/255., color.blue()/255., color.alpha()/255.) + width = pen.width() + if pen.isCosmetic() and width < 1: + width = 1 + gl.glPointSize(width) + gl.glEnable(gl.GL_LINE_SMOOTH) + gl.glEnable(gl.GL_BLEND) + gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) + gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST) + gl.glDrawArrays(gl.GL_LINE_STRIP, 0, pos.size / pos.shape[-1]) + finally: + gl.glDisableClientState(gl.GL_VERTEX_ARRAY) + finally: + p.endNativePainting() + + def clear(self): + self.xData = None ## raw values + self.yData = None + self.xDisp = None ## display values (after log / fft) + self.yDisp = None + self.path = None + self.fillPath = None + self._mouseShape = None + self._mouseBounds = None + self._boundsCache = [None, None] + #del self.xData, self.yData, self.xDisp, self.yDisp, self.path + + def mouseShape(self): + """ + Return a QPainterPath representing the clickable shape of the curve + + """ + if self._mouseShape is None: + view = self.getViewBox() + if view is None: + return QtGui.QPainterPath() + stroker = QtGui.QPainterPathStroker() + path = self.getPath() + path = self.mapToItem(view, path) + stroker.setWidth(self.opts['mouseWidth']) + mousePath = stroker.createStroke(path) + self._mouseShape = self.mapFromItem(view, mousePath) + return self._mouseShape + + def mouseClickEvent(self, ev): + if not self.clickable or ev.button() != QtCore.Qt.LeftButton: + return + if self.mouseShape().contains(ev.pos()): + ev.accept() + self.sigClicked.emit(self) + + + +class ROIPlotItem(PlotCurveItem): + """Plot curve that monitors an ROI and image for changes to automatically replot.""" + def __init__(self, roi, data, img, axes=(0,1), xVals=None, color=None): + self.roi = roi + self.roiData = data + self.roiImg = img + self.axes = axes + self.xVals = xVals + PlotCurveItem.__init__(self, self.getRoiData(), x=self.xVals, color=color) + #roi.connect(roi, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent) + roi.sigRegionChanged.connect(self.roiChangedEvent) + #self.roiChangedEvent() + + def getRoiData(self): + d = self.roi.getArrayRegion(self.roiData, self.roiImg, axes=self.axes) + if d is None: + return + while d.ndim > 1: + d = d.mean(axis=1) + return d + + def roiChangedEvent(self): + d = self.getRoiData() + self.updateData(d, self.xVals) + diff --git a/papi/pyqtgraph/graphicsItems/PlotDataItem.py b/papi/pyqtgraph/graphicsItems/PlotDataItem.py new file mode 100644 index 00000000..6148989d --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/PlotDataItem.py @@ -0,0 +1,866 @@ +from .. import metaarray as metaarray +from ..Qt import QtCore +from .GraphicsObject import GraphicsObject +from .PlotCurveItem import PlotCurveItem +from .ScatterPlotItem import ScatterPlotItem +import numpy as np +from .. import functions as fn +from .. import debug as debug +from .. import getConfigOption + +class PlotDataItem(GraphicsObject): + """ + **Bases:** :class:`GraphicsObject ` + + GraphicsItem for displaying plot curves, scatter plots, or both. + While it is possible to use :class:`PlotCurveItem ` or + :class:`ScatterPlotItem ` individually, this class + provides a unified interface to both. Instances of :class:`PlotDataItem` are + usually created by plot() methods such as :func:`pyqtgraph.plot` and + :func:`PlotItem.plot() `. + + ============================== ============================================== + **Signals:** + sigPlotChanged(self) Emitted when the data in this item is updated. + sigClicked(self) Emitted when the item is clicked. + sigPointsClicked(self, points) Emitted when a plot point is clicked + Sends the list of points under the mouse. + ============================== ============================================== + """ + + sigPlotChanged = QtCore.Signal(object) + sigClicked = QtCore.Signal(object) + sigPointsClicked = QtCore.Signal(object, object) + + def __init__(self, *args, **kargs): + """ + There are many different ways to create a PlotDataItem: + + **Data initialization arguments:** (x,y data only) + + =================================== ====================================== + PlotDataItem(xValues, yValues) x and y values may be any sequence (including ndarray) of real numbers + PlotDataItem(yValues) y values only -- x will be automatically set to range(len(y)) + PlotDataItem(x=xValues, y=yValues) x and y given by keyword arguments + PlotDataItem(ndarray(Nx2)) numpy array with shape (N, 2) where x=data[:,0] and y=data[:,1] + =================================== ====================================== + + **Data initialization arguments:** (x,y data AND may include spot style) + + =========================== ========================================= + PlotDataItem(recarray) numpy array with dtype=[('x', float), ('y', float), ...] + PlotDataItem(list-of-dicts) [{'x': x, 'y': y, ...}, ...] + PlotDataItem(dict-of-lists) {'x': [...], 'y': [...], ...} + PlotDataItem(MetaArray) 1D array of Y values with X sepecified as axis values + OR 2D array with a column 'y' and extra columns as needed. + =========================== ========================================= + + **Line style keyword arguments:** + + ========== ============================================================================== + connect Specifies how / whether vertexes should be connected. See + :func:`arrayToQPath() ` + pen Pen to use for drawing line between points. + Default is solid grey, 1px width. Use None to disable line drawing. + May be any single argument accepted by :func:`mkPen() ` + shadowPen Pen for secondary line to draw behind the primary line. disabled by default. + May be any single argument accepted by :func:`mkPen() ` + fillLevel Fill the area between the curve and fillLevel + fillBrush Fill to use when fillLevel is specified. + May be any single argument accepted by :func:`mkBrush() ` + stepMode If True, two orthogonal lines are drawn for each sample + as steps. This is commonly used when drawing histograms. + Note that in this case, `len(x) == len(y) + 1` + (added in version 0.9.9) + ========== ============================================================================== + + **Point style keyword arguments:** (see :func:`ScatterPlotItem.setData() ` for more information) + + ============ ===================================================== + symbol Symbol to use for drawing points OR list of symbols, + one per point. Default is no symbol. + Options are o, s, t, d, +, or any QPainterPath + symbolPen Outline pen for drawing points OR list of pens, one + per point. May be any single argument accepted by + :func:`mkPen() ` + symbolBrush Brush for filling points OR list of brushes, one per + point. May be any single argument accepted by + :func:`mkBrush() ` + symbolSize Diameter of symbols OR list of diameters. + pxMode (bool) If True, then symbolSize is specified in + pixels. If False, then symbolSize is + specified in data coordinates. + ============ ===================================================== + + **Optimization keyword arguments:** + + ================ ===================================================================== + antialias (bool) By default, antialiasing is disabled to improve performance. + Note that in some cases (in particluar, when pxMode=True), points + will be rendered antialiased even if this is set to False. + decimate deprecated. + downsample (int) Reduce the number of samples displayed by this value + downsampleMethod 'subsample': Downsample by taking the first of N samples. + This method is fastest and least accurate. + 'mean': Downsample by taking the mean of N samples. + 'peak': Downsample by drawing a saw wave that follows the min + and max of the original data. This method produces the best + visual representation of the data but is slower. + autoDownsample (bool) If True, resample the data before plotting to avoid plotting + multiple line segments per pixel. This can improve performance when + viewing very high-density data, but increases the initial overhead + and memory usage. + clipToView (bool) If True, only plot data that is visible within the X range of + the containing ViewBox. This can improve performance when plotting + very large data sets where only a fraction of the data is visible + at any time. + identical *deprecated* + ================ ===================================================================== + + **Meta-info keyword arguments:** + + ========== ================================================ + name name of dataset. This would appear in a legend + ========== ================================================ + """ + GraphicsObject.__init__(self) + self.setFlag(self.ItemHasNoContents) + self.xData = None + self.yData = None + self.xDisp = None + self.yDisp = None + #self.dataMask = None + #self.curves = [] + #self.scatters = [] + self.curve = PlotCurveItem() + self.scatter = ScatterPlotItem() + self.curve.setParentItem(self) + self.scatter.setParentItem(self) + + self.curve.sigClicked.connect(self.curveClicked) + self.scatter.sigClicked.connect(self.scatterClicked) + + + #self.clear() + self.opts = { + 'connect': 'all', + + 'fftMode': False, + 'logMode': [False, False], + 'alphaHint': 1.0, + 'alphaMode': False, + + 'pen': (200,200,200), + 'shadowPen': None, + 'fillLevel': None, + 'fillBrush': None, + 'stepMode': None, + + 'symbol': None, + 'symbolSize': 10, + 'symbolPen': (200,200,200), + 'symbolBrush': (50, 50, 150), + 'pxMode': True, + + 'antialias': getConfigOption('antialias'), + 'pointMode': None, + + 'downsample': 1, + 'autoDownsample': False, + 'downsampleMethod': 'peak', + 'autoDownsampleFactor': 5., # draw ~5 samples per pixel + 'clipToView': False, + + 'data': None, + } + self.setData(*args, **kargs) + + def implements(self, interface=None): + ints = ['plotData'] + if interface is None: + return ints + return interface in ints + + def name(self): + return self.opts.get('name', None) + + def boundingRect(self): + return QtCore.QRectF() ## let child items handle this + + def setAlpha(self, alpha, auto): + if self.opts['alphaHint'] == alpha and self.opts['alphaMode'] == auto: + return + self.opts['alphaHint'] = alpha + self.opts['alphaMode'] = auto + self.setOpacity(alpha) + #self.update() + + def setFftMode(self, mode): + if self.opts['fftMode'] == mode: + return + self.opts['fftMode'] = mode + self.xDisp = self.yDisp = None + self.xClean = self.yClean = None + self.updateItems() + self.informViewBoundsChanged() + + def setLogMode(self, xMode, yMode): + if self.opts['logMode'] == [xMode, yMode]: + return + self.opts['logMode'] = [xMode, yMode] + self.xDisp = self.yDisp = None + self.xClean = self.yClean = None + self.updateItems() + self.informViewBoundsChanged() + + def setPointMode(self, mode): + if self.opts['pointMode'] == mode: + return + self.opts['pointMode'] = mode + self.update() + + def setPen(self, *args, **kargs): + """ + | Sets the pen used to draw lines between points. + | *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() ` + """ + pen = fn.mkPen(*args, **kargs) + self.opts['pen'] = pen + #self.curve.setPen(pen) + #for c in self.curves: + #c.setPen(pen) + #self.update() + self.updateItems() + + def setShadowPen(self, *args, **kargs): + """ + | Sets the shadow pen used to draw lines between points (this is for enhancing contrast or + emphacizing data). + | This line is drawn behind the primary pen (see :func:`setPen() `) + and should generally be assigned greater width than the primary pen. + | *pen* can be a QPen or any argument accepted by :func:`pyqtgraph.mkPen() ` + """ + pen = fn.mkPen(*args, **kargs) + self.opts['shadowPen'] = pen + #for c in self.curves: + #c.setPen(pen) + #self.update() + self.updateItems() + + def setFillBrush(self, *args, **kargs): + brush = fn.mkBrush(*args, **kargs) + if self.opts['fillBrush'] == brush: + return + self.opts['fillBrush'] = brush + self.updateItems() + + def setBrush(self, *args, **kargs): + return self.setFillBrush(*args, **kargs) + + def setFillLevel(self, level): + if self.opts['fillLevel'] == level: + return + self.opts['fillLevel'] = level + self.updateItems() + + def setSymbol(self, symbol): + if self.opts['symbol'] == symbol: + return + self.opts['symbol'] = symbol + #self.scatter.setSymbol(symbol) + self.updateItems() + + def setSymbolPen(self, *args, **kargs): + pen = fn.mkPen(*args, **kargs) + if self.opts['symbolPen'] == pen: + return + self.opts['symbolPen'] = pen + #self.scatter.setSymbolPen(pen) + self.updateItems() + + + + def setSymbolBrush(self, *args, **kargs): + brush = fn.mkBrush(*args, **kargs) + if self.opts['symbolBrush'] == brush: + return + self.opts['symbolBrush'] = brush + #self.scatter.setSymbolBrush(brush) + self.updateItems() + + + def setSymbolSize(self, size): + if self.opts['symbolSize'] == size: + return + self.opts['symbolSize'] = size + #self.scatter.setSymbolSize(symbolSize) + self.updateItems() + + def setDownsampling(self, ds=None, auto=None, method=None): + """ + Set the downsampling mode of this item. Downsampling reduces the number + of samples drawn to increase performance. + + ============== ================================================================= + **Arguments:** + ds (int) Reduce visible plot samples by this factor. To disable, + set ds=1. + auto (bool) If True, automatically pick *ds* based on visible range + mode 'subsample': Downsample by taking the first of N samples. + This method is fastest and least accurate. + 'mean': Downsample by taking the mean of N samples. + 'peak': Downsample by drawing a saw wave that follows the min + and max of the original data. This method produces the best + visual representation of the data but is slower. + ============== ================================================================= + """ + changed = False + if ds is not None: + if self.opts['downsample'] != ds: + changed = True + self.opts['downsample'] = ds + + if auto is not None and self.opts['autoDownsample'] != auto: + self.opts['autoDownsample'] = auto + changed = True + + if method is not None: + if self.opts['downsampleMethod'] != method: + changed = True + self.opts['downsampleMethod'] = method + + if changed: + self.xDisp = self.yDisp = None + self.updateItems() + + def setClipToView(self, clip): + if self.opts['clipToView'] == clip: + return + self.opts['clipToView'] = clip + self.xDisp = self.yDisp = None + self.updateItems() + + + def setData(self, *args, **kargs): + """ + Clear any data displayed by this item and display new data. + See :func:`__init__() ` for details; it accepts the same arguments. + """ + #self.clear() + profiler = debug.Profiler() + y = None + x = None + if len(args) == 1: + data = args[0] + dt = dataType(data) + if dt == 'empty': + pass + elif dt == 'listOfValues': + y = np.array(data) + elif dt == 'Nx2array': + x = data[:,0] + y = data[:,1] + elif dt == 'recarray' or dt == 'dictOfLists': + if 'x' in data: + x = np.array(data['x']) + if 'y' in data: + y = np.array(data['y']) + elif dt == 'listOfDicts': + if 'x' in data[0]: + x = np.array([d.get('x',None) for d in data]) + if 'y' in data[0]: + y = np.array([d.get('y',None) for d in data]) + for k in ['data', 'symbolSize', 'symbolPen', 'symbolBrush', 'symbolShape']: + if k in data: + kargs[k] = [d.get(k, None) for d in data] + elif dt == 'MetaArray': + y = data.view(np.ndarray) + x = data.xvals(0).view(np.ndarray) + else: + raise Exception('Invalid data type %s' % type(data)) + + elif len(args) == 2: + seq = ('listOfValues', 'MetaArray', 'empty') + dtyp = dataType(args[0]), dataType(args[1]) + if dtyp[0] not in seq or dtyp[1] not in seq: + raise Exception('When passing two unnamed arguments, both must be a list or array of values. (got %s, %s)' % (str(type(args[0])), str(type(args[1])))) + if not isinstance(args[0], np.ndarray): + #x = np.array(args[0]) + if dtyp[0] == 'MetaArray': + x = args[0].asarray() + else: + x = np.array(args[0]) + else: + x = args[0].view(np.ndarray) + if not isinstance(args[1], np.ndarray): + #y = np.array(args[1]) + if dtyp[1] == 'MetaArray': + y = args[1].asarray() + else: + y = np.array(args[1]) + else: + y = args[1].view(np.ndarray) + + if 'x' in kargs: + x = kargs['x'] + if 'y' in kargs: + y = kargs['y'] + + profiler('interpret data') + ## pull in all style arguments. + ## Use self.opts to fill in anything not present in kargs. + + if 'name' in kargs: + self.opts['name'] = kargs['name'] + if 'connect' in kargs: + self.opts['connect'] = kargs['connect'] + + ## if symbol pen/brush are given with no symbol, then assume symbol is 'o' + + if 'symbol' not in kargs and ('symbolPen' in kargs or 'symbolBrush' in kargs or 'symbolSize' in kargs): + kargs['symbol'] = 'o' + + if 'brush' in kargs: + kargs['fillBrush'] = kargs['brush'] + + for k in list(self.opts.keys()): + if k in kargs: + self.opts[k] = kargs[k] + + #curveArgs = {} + #for k in ['pen', 'shadowPen', 'fillLevel', 'brush']: + #if k in kargs: + #self.opts[k] = kargs[k] + #curveArgs[k] = self.opts[k] + + #scatterArgs = {} + #for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol')]: + #if k in kargs: + #self.opts[k] = kargs[k] + #scatterArgs[v] = self.opts[k] + + + if y is None: + return + if y is not None and x is None: + x = np.arange(len(y)) + + if isinstance(x, list): + x = np.array(x) + if isinstance(y, list): + y = np.array(y) + + self.xData = x.view(np.ndarray) ## one last check to make sure there are no MetaArrays getting by + self.yData = y.view(np.ndarray) + self.xClean = self.yClean = None + self.xDisp = None + self.yDisp = None + profiler('set data') + + self.updateItems() + profiler('update items') + + self.informViewBoundsChanged() + #view = self.getViewBox() + #if view is not None: + #view.itemBoundsChanged(self) ## inform view so it can update its range if it wants + + self.sigPlotChanged.emit(self) + profiler('emit') + + def updateItems(self): + + curveArgs = {} + for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillBrush', 'brush'), ('antialias', 'antialias'), ('connect', 'connect'), ('stepMode', 'stepMode')]: + curveArgs[v] = self.opts[k] + + scatterArgs = {} + for k,v in [('symbolPen','pen'), ('symbolBrush','brush'), ('symbol','symbol'), ('symbolSize', 'size'), ('data', 'data'), ('pxMode', 'pxMode'), ('antialias', 'antialias')]: + if k in self.opts: + scatterArgs[v] = self.opts[k] + + x,y = self.getData() + #scatterArgs['mask'] = self.dataMask + + if curveArgs['pen'] is not None or (curveArgs['brush'] is not None and curveArgs['fillLevel'] is not None): + self.curve.setData(x=x, y=y, **curveArgs) + self.curve.show() + else: + self.curve.hide() + + if scatterArgs['symbol'] is not None: + self.scatter.setData(x=x, y=y, **scatterArgs) + self.scatter.show() + else: + self.scatter.hide() + + + def getData(self): + if self.xData is None: + return (None, None) + + #if self.xClean is None: + #nanMask = np.isnan(self.xData) | np.isnan(self.yData) | np.isinf(self.xData) | np.isinf(self.yData) + #if nanMask.any(): + #self.dataMask = ~nanMask + #self.xClean = self.xData[self.dataMask] + #self.yClean = self.yData[self.dataMask] + #else: + #self.dataMask = None + #self.xClean = self.xData + #self.yClean = self.yData + + if self.xDisp is None: + x = self.xData + y = self.yData + + + #ds = self.opts['downsample'] + #if isinstance(ds, int) and ds > 1: + #x = x[::ds] + ##y = resample(y[:len(x)*ds], len(x)) ## scipy.signal.resample causes nasty ringing + #y = y[::ds] + if self.opts['fftMode']: + x,y = self._fourierTransform(x, y) + if self.opts['logMode'][0]: + x = np.log10(x) + if self.opts['logMode'][1]: + y = np.log10(y) + #if any(self.opts['logMode']): ## re-check for NANs after log + #nanMask = np.isinf(x) | np.isinf(y) | np.isnan(x) | np.isnan(y) + #if any(nanMask): + #self.dataMask = ~nanMask + #x = x[self.dataMask] + #y = y[self.dataMask] + #else: + #self.dataMask = None + + ds = self.opts['downsample'] + if not isinstance(ds, int): + ds = 1 + + if self.opts['autoDownsample']: + # this option presumes that x-values have uniform spacing + range = self.viewRect() + if range is not None: + dx = float(x[-1]-x[0]) / (len(x)-1) + x0 = (range.left()-x[0]) / dx + x1 = (range.right()-x[0]) / dx + width = self.getViewBox().width() + if width != 0.0: + ds = int(max(1, int((x1-x0) / (width*self.opts['autoDownsampleFactor'])))) + ## downsampling is expensive; delay until after clipping. + + if self.opts['clipToView']: + view = self.getViewBox() + if view is None or not view.autoRangeEnabled()[0]: + # this option presumes that x-values have uniform spacing + range = self.viewRect() + if range is not None and len(x) > 1: + dx = float(x[-1]-x[0]) / (len(x)-1) + # clip to visible region extended by downsampling value + x0 = np.clip(int((range.left()-x[0])/dx)-1*ds , 0, len(x)-1) + x1 = np.clip(int((range.right()-x[0])/dx)+2*ds , 0, len(x)-1) + x = x[x0:x1] + y = y[x0:x1] + + if ds > 1: + if self.opts['downsampleMethod'] == 'subsample': + x = x[::ds] + y = y[::ds] + elif self.opts['downsampleMethod'] == 'mean': + n = len(x) / ds + x = x[:n*ds:ds] + y = y[:n*ds].reshape(n,ds).mean(axis=1) + elif self.opts['downsampleMethod'] == 'peak': + n = len(x) / ds + x1 = np.empty((n,2)) + x1[:] = x[:n*ds:ds,np.newaxis] + x = x1.reshape(n*2) + y1 = np.empty((n,2)) + y2 = y[:n*ds].reshape((n, ds)) + y1[:,0] = y2.max(axis=1) + y1[:,1] = y2.min(axis=1) + y = y1.reshape(n*2) + + + self.xDisp = x + self.yDisp = y + #print self.yDisp.shape, self.yDisp.min(), self.yDisp.max() + #print self.xDisp.shape, self.xDisp.min(), self.xDisp.max() + return self.xDisp, self.yDisp + + def dataBounds(self, ax, frac=1.0, orthoRange=None): + """ + Returns the range occupied by the data (along a specific axis) in this item. + This method is called by ViewBox when auto-scaling. + + =============== ============================================================= + **Arguments:** + ax (0 or 1) the axis for which to return this item's data range + frac (float 0.0-1.0) Specifies what fraction of the total data + range to return. By default, the entire range is returned. + This allows the ViewBox to ignore large spikes in the data + when auto-scaling. + orthoRange ([min,max] or None) Specifies that only the data within the + given range (orthogonal to *ax*) should me measured when + returning the data range. (For example, a ViewBox might ask + what is the y-range of all data with x-values between min + and max) + =============== ============================================================= + """ + + range = [None, None] + if self.curve.isVisible(): + range = self.curve.dataBounds(ax, frac, orthoRange) + elif self.scatter.isVisible(): + r2 = self.scatter.dataBounds(ax, frac, orthoRange) + range = [ + r2[0] if range[0] is None else (range[0] if r2[0] is None else min(r2[0], range[0])), + r2[1] if range[1] is None else (range[1] if r2[1] is None else min(r2[1], range[1])) + ] + return range + + def pixelPadding(self): + """ + Return the size in pixels that this item may draw beyond the values returned by dataBounds(). + This method is called by ViewBox when auto-scaling. + """ + pad = 0 + if self.curve.isVisible(): + pad = max(pad, self.curve.pixelPadding()) + elif self.scatter.isVisible(): + pad = max(pad, self.scatter.pixelPadding()) + return pad + + + def clear(self): + #for i in self.curves+self.scatters: + #if i.scene() is not None: + #i.scene().removeItem(i) + #self.curves = [] + #self.scatters = [] + self.xData = None + self.yData = None + #self.xClean = None + #self.yClean = None + self.xDisp = None + self.yDisp = None + self.curve.setData([]) + self.scatter.setData([]) + + def appendData(self, *args, **kargs): + pass + + def curveClicked(self): + self.sigClicked.emit(self) + + def scatterClicked(self, plt, points): + self.sigClicked.emit(self) + self.sigPointsClicked.emit(self, points) + + def viewRangeChanged(self): + # view range has changed; re-plot if needed + if self.opts['clipToView'] or self.opts['autoDownsample']: + self.xDisp = self.yDisp = None + self.updateItems() + + def _fourierTransform(self, x, y): + ## Perform fourier transform. If x values are not sampled uniformly, + ## then use np.interp to resample before taking fft. + dx = np.diff(x) + uniform = not np.any(np.abs(dx-dx[0]) > (abs(dx[0]) / 1000.)) + if not uniform: + x2 = np.linspace(x[0], x[-1], len(x)) + y = np.interp(x2, x, y) + x = x2 + f = np.fft.fft(y) / len(y) + y = abs(f[1:len(f)/2]) + dt = x[-1] - x[0] + x = np.linspace(0, 0.5*len(x)/dt, len(y)) + return x, y + +def dataType(obj): + if hasattr(obj, '__len__') and len(obj) == 0: + return 'empty' + if isinstance(obj, dict): + return 'dictOfLists' + elif isSequence(obj): + first = obj[0] + + if (hasattr(obj, 'implements') and obj.implements('MetaArray')): + return 'MetaArray' + elif isinstance(obj, np.ndarray): + if obj.ndim == 1: + if obj.dtype.names is None: + return 'listOfValues' + else: + return 'recarray' + elif obj.ndim == 2 and obj.dtype.names is None and obj.shape[1] == 2: + return 'Nx2array' + else: + raise Exception('array shape must be (N,) or (N,2); got %s instead' % str(obj.shape)) + elif isinstance(first, dict): + return 'listOfDicts' + else: + return 'listOfValues' + + +def isSequence(obj): + return hasattr(obj, '__iter__') or isinstance(obj, np.ndarray) or (hasattr(obj, 'implements') and obj.implements('MetaArray')) + + + +#class TableData: + #""" + #Class for presenting multiple forms of tabular data through a consistent interface. + #May contain: + #- numpy record array + #- list-of-dicts (all dicts are _not_ required to have the same keys) + #- dict-of-lists + #- dict (single record) + #Note: if all the values in this record are lists, it will be interpreted as multiple records + + #Data can be accessed and modified by column, by row, or by value + #data[columnName] + #data[rowId] + #data[columnName, rowId] = value + #data[columnName] = [value, value, ...] + #data[rowId] = {columnName: value, ...} + #""" + + #def __init__(self, data): + #self.data = data + #if isinstance(data, np.ndarray): + #self.mode = 'array' + #elif isinstance(data, list): + #self.mode = 'list' + #elif isinstance(data, dict): + #types = set(map(type, data.values())) + ### dict may be a dict-of-lists or a single record + #types -= set([list, np.ndarray]) ## if dict contains any non-sequence values, it is probably a single record. + #if len(types) != 0: + #self.data = [self.data] + #self.mode = 'list' + #else: + #self.mode = 'dict' + #elif isinstance(data, TableData): + #self.data = data.data + #self.mode = data.mode + #else: + #raise TypeError(type(data)) + + #for fn in ['__getitem__', '__setitem__']: + #setattr(self, fn, getattr(self, '_TableData'+fn+self.mode)) + + #def originalData(self): + #return self.data + + #def toArray(self): + #if self.mode == 'array': + #return self.data + #if len(self) < 1: + ##return np.array([]) ## need to return empty array *with correct columns*, but this is very difficult, so just return None + #return None + #rec1 = self[0] + #dtype = functions.suggestRecordDType(rec1) + ##print rec1, dtype + #arr = np.empty(len(self), dtype=dtype) + #arr[0] = tuple(rec1.values()) + #for i in xrange(1, len(self)): + #arr[i] = tuple(self[i].values()) + #return arr + + #def __getitem__array(self, arg): + #if isinstance(arg, tuple): + #return self.data[arg[0]][arg[1]] + #else: + #return self.data[arg] + + #def __getitem__list(self, arg): + #if isinstance(arg, basestring): + #return [d.get(arg, None) for d in self.data] + #elif isinstance(arg, int): + #return self.data[arg] + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #return self.data[arg[0]][arg[1]] + #else: + #raise TypeError(type(arg)) + + #def __getitem__dict(self, arg): + #if isinstance(arg, basestring): + #return self.data[arg] + #elif isinstance(arg, int): + #return dict([(k, v[arg]) for k, v in self.data.iteritems()]) + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #return self.data[arg[1]][arg[0]] + #else: + #raise TypeError(type(arg)) + + #def __setitem__array(self, arg, val): + #if isinstance(arg, tuple): + #self.data[arg[0]][arg[1]] = val + #else: + #self.data[arg] = val + + #def __setitem__list(self, arg, val): + #if isinstance(arg, basestring): + #if len(val) != len(self.data): + #raise Exception("Values (%d) and data set (%d) are not the same length." % (len(val), len(self.data))) + #for i, rec in enumerate(self.data): + #rec[arg] = val[i] + #elif isinstance(arg, int): + #self.data[arg] = val + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #self.data[arg[0]][arg[1]] = val + #else: + #raise TypeError(type(arg)) + + #def __setitem__dict(self, arg, val): + #if isinstance(arg, basestring): + #if len(val) != len(self.data[arg]): + #raise Exception("Values (%d) and data set (%d) are not the same length." % (len(val), len(self.data[arg]))) + #self.data[arg] = val + #elif isinstance(arg, int): + #for k in self.data: + #self.data[k][arg] = val[k] + #elif isinstance(arg, tuple): + #arg = self._orderArgs(arg) + #self.data[arg[1]][arg[0]] = val + #else: + #raise TypeError(type(arg)) + + #def _orderArgs(self, args): + ### return args in (int, str) order + #if isinstance(args[0], basestring): + #return (args[1], args[0]) + #else: + #return args + + #def __iter__(self): + #for i in xrange(len(self)): + #yield self[i] + + #def __len__(self): + #if self.mode == 'array' or self.mode == 'list': + #return len(self.data) + #else: + #return max(map(len, self.data.values())) + + #def columnNames(self): + #"""returns column names in no particular order""" + #if self.mode == 'array': + #return self.data.dtype.names + #elif self.mode == 'list': + #names = set() + #for row in self.data: + #names.update(row.keys()) + #return list(names) + #elif self.mode == 'dict': + #return self.data.keys() + + #def keys(self): + #return self.columnNames() diff --git a/papi/pyqtgraph/graphicsItems/PlotItem/PlotItem.py b/papi/pyqtgraph/graphicsItems/PlotItem/PlotItem.py new file mode 100644 index 00000000..4f10b0e3 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/PlotItem/PlotItem.py @@ -0,0 +1,1265 @@ +# -*- coding: utf-8 -*- +""" +PlotItem.py - Graphics item implementing a scalable ViewBox with plotting powers. +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +This class is one of the workhorses of pyqtgraph. It implements a graphics item with +plots, labels, and scales which can be viewed inside a QGraphicsScene. If you want +a widget that can be added to your GUI, see PlotWidget instead. + +This class is very heavily featured: + - Automatically creates and manages PlotCurveItems + - Fast display and update of plots + - Manages zoom/pan ViewBox, scale, and label elements + - Automatic scaling when data changes + - Control panel with a huge feature set including averaging, decimation, + display, power spectrum, svg/png export, plot linking, and more. +""" +from ...Qt import QtGui, QtCore, QtSvg, USE_PYSIDE +from ... import pixmaps +import sys + +if USE_PYSIDE: + from .plotConfigTemplate_pyside import * +else: + from .plotConfigTemplate_pyqt import * + +from ... import functions as fn +from ...widgets.FileDialog import FileDialog +import weakref +import numpy as np +import os +from .. PlotDataItem import PlotDataItem +from .. ViewBox import ViewBox +from .. AxisItem import AxisItem +from .. LabelItem import LabelItem +from .. LegendItem import LegendItem +from .. GraphicsWidget import GraphicsWidget +from .. ButtonItem import ButtonItem +from .. InfiniteLine import InfiniteLine +from ...WidgetGroup import WidgetGroup + +__all__ = ['PlotItem'] + +try: + from metaarray import * + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + + + + +class PlotItem(GraphicsWidget): + + """ + **Bases:** :class:`GraphicsWidget ` + + Plot graphics item that can be added to any graphics scene. Implements axes, titles, and interactive viewbox. + PlotItem also provides some basic analysis functionality that may be accessed from the context menu. + Use :func:`plot() ` to create a new PlotDataItem and add it to the view. + Use :func:`addItem() ` to add any QGraphicsItem to the view. + + This class wraps several methods from its internal ViewBox: + :func:`setXRange `, + :func:`setYRange `, + :func:`setRange `, + :func:`autoRange `, + :func:`setXLink `, + :func:`setYLink `, + :func:`setAutoPan `, + :func:`setAutoVisible `, + :func:`setLimits `, + :func:`viewRect `, + :func:`viewRange `, + :func:`setMouseEnabled `, + :func:`enableAutoRange `, + :func:`disableAutoRange `, + :func:`setAspectLocked `, + :func:`invertY `, + :func:`invertX `, + :func:`register `, + :func:`unregister ` + + The ViewBox itself can be accessed by calling :func:`getViewBox() ` + + ==================== ======================================================================= + **Signals:** + sigYRangeChanged wrapped from :class:`ViewBox ` + sigXRangeChanged wrapped from :class:`ViewBox ` + sigRangeChanged wrapped from :class:`ViewBox ` + ==================== ======================================================================= + """ + + sigRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox range has changed + sigYRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox Y range has changed + sigXRangeChanged = QtCore.Signal(object, object) ## Emitted when the ViewBox X range has changed + + + lastFileDir = None + + def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs): + """ + Create a new PlotItem. All arguments are optional. + Any extra keyword arguments are passed to PlotItem.plot(). + + ============== ========================================================================================== + **Arguments:** + *title* Title to display at the top of the item. Html is allowed. + *labels* A dictionary specifying the axis labels to display:: + + {'left': (args), 'bottom': (args), ...} + + The name of each axis and the corresponding arguments are passed to + :func:`PlotItem.setLabel() ` + Optionally, PlotItem my also be initialized with the keyword arguments left, + right, top, or bottom to achieve the same effect. + *name* Registers a name for this view so that others may link to it + *viewBox* If specified, the PlotItem will be constructed with this as its ViewBox. + *axisItems* Optional dictionary instructing the PlotItem to use pre-constructed items + for its axes. The dict keys must be axis names ('left', 'bottom', 'right', 'top') + and the values must be instances of AxisItem (or at least compatible with AxisItem). + ============== ========================================================================================== + """ + + GraphicsWidget.__init__(self, parent) + + self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + + ## Set up control buttons + path = os.path.dirname(__file__) + #self.autoImageFile = os.path.join(path, 'auto.png') + #self.lockImageFile = os.path.join(path, 'lock.png') + self.autoBtn = ButtonItem(pixmaps.getPixmap('auto'), 14, self) + self.autoBtn.mode = 'auto' + self.autoBtn.clicked.connect(self.autoBtnClicked) + #self.autoBtn.hide() + self.buttonsHidden = False ## whether the user has requested buttons to be hidden + self.mouseHovering = False + + self.layout = QtGui.QGraphicsGridLayout() + self.layout.setContentsMargins(1,1,1,1) + self.setLayout(self.layout) + self.layout.setHorizontalSpacing(0) + self.layout.setVerticalSpacing(0) + + if viewBox is None: + viewBox = ViewBox(parent=self) + self.vb = viewBox + self.vb.sigStateChanged.connect(self.viewStateChanged) + self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus + + if name is not None: + self.vb.register(name) + self.vb.sigRangeChanged.connect(self.sigRangeChanged) + self.vb.sigXRangeChanged.connect(self.sigXRangeChanged) + self.vb.sigYRangeChanged.connect(self.sigYRangeChanged) + + self.layout.addItem(self.vb, 2, 1) + self.alpha = 1.0 + self.autoAlpha = True + self.spectrumMode = False + + self.legend = None + + ## Create and place axis items + if axisItems is None: + axisItems = {} + self.axes = {} + for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))): + axis = axisItems.get(k, AxisItem(orientation=k, parent=self)) + axis.linkToView(self.vb) + self.axes[k] = {'item': axis, 'pos': pos} + self.layout.addItem(axis, *pos) + axis.setZValue(-1000) + axis.setFlag(axis.ItemNegativeZStacksBehindParent) + + self.titleLabel = LabelItem('', size='11pt', parent=self) + self.layout.addItem(self.titleLabel, 0, 1) + self.setTitle(None) ## hide + + + for i in range(4): + self.layout.setRowPreferredHeight(i, 0) + self.layout.setRowMinimumHeight(i, 0) + self.layout.setRowSpacing(i, 0) + self.layout.setRowStretchFactor(i, 1) + + for i in range(3): + self.layout.setColumnPreferredWidth(i, 0) + self.layout.setColumnMinimumWidth(i, 0) + self.layout.setColumnSpacing(i, 0) + self.layout.setColumnStretchFactor(i, 1) + self.layout.setRowStretchFactor(2, 100) + self.layout.setColumnStretchFactor(1, 100) + + + self.items = [] + self.curves = [] + self.itemMeta = weakref.WeakKeyDictionary() + self.dataItems = [] + self.paramList = {} + self.avgCurves = {} + + ### Set up context menu + + w = QtGui.QWidget() + self.ctrl = c = Ui_Form() + c.setupUi(w) + dv = QtGui.QDoubleValidator(self) + + menuItems = [ + ('Transforms', c.transformGroup), + ('Downsample', c.decimateGroup), + ('Average', c.averageGroup), + ('Alpha', c.alphaGroup), + ('Grid', c.gridGroup), + ('Points', c.pointsGroup), + ] + + + self.ctrlMenu = QtGui.QMenu() + + self.ctrlMenu.setTitle('Plot Options') + self.subMenus = [] + for name, grp in menuItems: + sm = QtGui.QMenu(name) + act = QtGui.QWidgetAction(self) + act.setDefaultWidget(grp) + sm.addAction(act) + self.subMenus.append(sm) + self.ctrlMenu.addMenu(sm) + + self.stateGroup = WidgetGroup() + for name, w in menuItems: + self.stateGroup.autoAdd(w) + + self.fileDialog = None + + c.alphaGroup.toggled.connect(self.updateAlpha) + c.alphaSlider.valueChanged.connect(self.updateAlpha) + c.autoAlphaCheck.toggled.connect(self.updateAlpha) + + c.xGridCheck.toggled.connect(self.updateGrid) + c.yGridCheck.toggled.connect(self.updateGrid) + c.gridAlphaSlider.valueChanged.connect(self.updateGrid) + + c.fftCheck.toggled.connect(self.updateSpectrumMode) + c.logXCheck.toggled.connect(self.updateLogMode) + c.logYCheck.toggled.connect(self.updateLogMode) + + c.downsampleSpin.valueChanged.connect(self.updateDownsampling) + c.downsampleCheck.toggled.connect(self.updateDownsampling) + c.autoDownsampleCheck.toggled.connect(self.updateDownsampling) + c.subsampleRadio.toggled.connect(self.updateDownsampling) + c.meanRadio.toggled.connect(self.updateDownsampling) + c.clipToViewCheck.toggled.connect(self.updateDownsampling) + + self.ctrl.avgParamList.itemClicked.connect(self.avgParamListClicked) + self.ctrl.averageGroup.toggled.connect(self.avgToggled) + + self.ctrl.maxTracesCheck.toggled.connect(self.updateDecimation) + self.ctrl.maxTracesSpin.valueChanged.connect(self.updateDecimation) + + self.hideAxis('right') + self.hideAxis('top') + self.showAxis('left') + self.showAxis('bottom') + + if labels is None: + labels = {} + for label in list(self.axes.keys()): + if label in kargs: + labels[label] = kargs[label] + del kargs[label] + for k in labels: + if isinstance(labels[k], basestring): + labels[k] = (labels[k],) + self.setLabel(k, *labels[k]) + + if title is not None: + self.setTitle(title) + + if len(kargs) > 0: + self.plot(**kargs) + + + def implements(self, interface=None): + return interface in ['ViewBoxWrapper'] + + def getViewBox(self): + """Return the :class:`ViewBox ` contained within.""" + return self.vb + + + ## Wrap a few methods from viewBox. + #Important: don't use a settattr(m, getattr(self.vb, m)) as we'd be leaving the viebox alive + #because we had a reference to an instance method (creating wrapper methods at runtime instead). + + for m in ['setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', # NOTE: + 'setAutoVisible', 'setRange', 'autoRange', 'viewRect', 'viewRange', # If you update this list, please + 'setMouseEnabled', 'setLimits', 'enableAutoRange', 'disableAutoRange', # update the class docstring + 'setAspectLocked', 'invertY', 'invertX', 'register', 'unregister']: # as well. + + def _create_method(name): + def method(self, *args, **kwargs): + return getattr(self.vb, name)(*args, **kwargs) + method.__name__ = name + return method + + locals()[m] = _create_method(m) + + del _create_method + + + def setLogMode(self, x=None, y=None): + """ + Set log scaling for x and/or y axes. + This informs PlotDataItems to transform logarithmically and switches + the axes to use log ticking. + + Note that *no other items* in the scene will be affected by + this; there is (currently) no generic way to redisplay a GraphicsItem + with log coordinates. + + """ + if x is not None: + self.ctrl.logXCheck.setChecked(x) + if y is not None: + self.ctrl.logYCheck.setChecked(y) + + def showGrid(self, x=None, y=None, alpha=None): + """ + Show or hide the grid for either axis. + + ============== ===================================== + **Arguments:** + x (bool) Whether to show the X grid + y (bool) Whether to show the Y grid + alpha (0.0-1.0) Opacity of the grid + ============== ===================================== + """ + if x is None and y is None and alpha is None: + raise Exception("Must specify at least one of x, y, or alpha.") ## prevent people getting confused if they just call showGrid() + + if x is not None: + self.ctrl.xGridCheck.setChecked(x) + if y is not None: + self.ctrl.yGridCheck.setChecked(y) + if alpha is not None: + v = np.clip(alpha, 0, 1)*self.ctrl.gridAlphaSlider.maximum() + self.ctrl.gridAlphaSlider.setValue(v) + + #def paint(self, *args): + #prof = debug.Profiler() + #QtGui.QGraphicsWidget.paint(self, *args) + + ## bad idea. + #def __getattr__(self, attr): ## wrap ms + #return getattr(self.vb, attr) + + def close(self): + #print "delete", self + ## Most of this crap is needed to avoid PySide trouble. + ## The problem seems to be whenever scene.clear() leads to deletion of widgets (either through proxies or qgraphicswidgets) + ## the solution is to manually remove all widgets before scene.clear() is called + if self.ctrlMenu is None: ## already shut down + return + self.ctrlMenu.setParent(None) + self.ctrlMenu = None + + self.autoBtn.setParent(None) + self.autoBtn = None + + for k in self.axes: + i = self.axes[k]['item'] + i.close() + + self.axes = None + self.scene().removeItem(self.vb) + self.vb = None + + def registerPlot(self, name): ## for backward compatibility + self.vb.register(name) + + def updateGrid(self, *args): + alpha = self.ctrl.gridAlphaSlider.value() + x = alpha if self.ctrl.xGridCheck.isChecked() else False + y = alpha if self.ctrl.yGridCheck.isChecked() else False + self.getAxis('top').setGrid(x) + self.getAxis('bottom').setGrid(x) + self.getAxis('left').setGrid(y) + self.getAxis('right').setGrid(y) + + def viewGeometry(self): + """Return the screen geometry of the viewbox""" + v = self.scene().views()[0] + b = self.vb.mapRectToScene(self.vb.boundingRect()) + wr = v.mapFromScene(b).boundingRect() + pos = v.mapToGlobal(v.pos()) + wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) + return wr + + + def avgToggled(self, b): + if b: + self.recomputeAverages() + for k in self.avgCurves: + self.avgCurves[k][1].setVisible(b) + + def avgParamListClicked(self, item): + name = str(item.text()) + self.paramList[name] = (item.checkState() == QtCore.Qt.Checked) + self.recomputeAverages() + + def recomputeAverages(self): + if not self.ctrl.averageGroup.isChecked(): + return + for k in self.avgCurves: + self.removeItem(self.avgCurves[k][1]) + self.avgCurves = {} + for c in self.curves: + self.addAvgCurve(c) + self.replot() + + def addAvgCurve(self, curve): + ## Add a single curve into the pool of curves averaged together + + ## If there are plot parameters, then we need to determine which to average together. + remKeys = [] + addKeys = [] + if self.ctrl.avgParamList.count() > 0: + + ### First determine the key of the curve to which this new data should be averaged + for i in range(self.ctrl.avgParamList.count()): + item = self.ctrl.avgParamList.item(i) + if item.checkState() == QtCore.Qt.Checked: + remKeys.append(str(item.text())) + else: + addKeys.append(str(item.text())) + + if len(remKeys) < 1: ## In this case, there would be 1 average plot for each data plot; not useful. + return + + p = self.itemMeta.get(curve,{}).copy() + for k in p: + if type(k) is tuple: + p['.'.join(k)] = p[k] + del p[k] + for rk in remKeys: + if rk in p: + del p[rk] + for ak in addKeys: + if ak not in p: + p[ak] = None + key = tuple(p.items()) + + ### Create a new curve if needed + if key not in self.avgCurves: + plot = PlotDataItem() + plot.setPen(fn.mkPen([0, 200, 0])) + plot.setShadowPen(fn.mkPen([0, 0, 0, 100], width=3)) + plot.setAlpha(1.0, False) + plot.setZValue(100) + self.addItem(plot, skipAverage=True) + self.avgCurves[key] = [0, plot] + self.avgCurves[key][0] += 1 + (n, plot) = self.avgCurves[key] + + ### Average data together + (x, y) = curve.getData() + if plot.yData is not None and y.shape == plot.yData.shape: + # note that if shapes do not match, then the average resets. + newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n) + plot.setData(plot.xData, newData) + else: + plot.setData(x, y) + + def autoBtnClicked(self): + if self.autoBtn.mode == 'auto': + self.enableAutoRange() + self.autoBtn.hide() + else: + self.disableAutoRange() + + def viewStateChanged(self): + self.updateButtons() + + def enableAutoScale(self): + """ + Enable auto-scaling. The plot will continuously scale to fit the boundaries of its data. + """ + print("Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead.") + self.vb.enableAutoRange(self.vb.XYAxes) + + def addItem(self, item, *args, **kargs): + """ + Add a graphics item to the view box. + If the item has plot data (PlotDataItem, PlotCurveItem, ScatterPlotItem), it may + be included in analysis performed by the PlotItem. + """ + self.items.append(item) + vbargs = {} + if 'ignoreBounds' in kargs: + vbargs['ignoreBounds'] = kargs['ignoreBounds'] + self.vb.addItem(item, *args, **vbargs) + name = None + if hasattr(item, 'implements') and item.implements('plotData'): + name = item.name() + self.dataItems.append(item) + #self.plotChanged() + + params = kargs.get('params', {}) + self.itemMeta[item] = params + #item.setMeta(params) + self.curves.append(item) + #self.addItem(c) + + if hasattr(item, 'setLogMode'): + item.setLogMode(self.ctrl.logXCheck.isChecked(), self.ctrl.logYCheck.isChecked()) + + if isinstance(item, PlotDataItem): + ## configure curve for this plot + (alpha, auto) = self.alphaState() + item.setAlpha(alpha, auto) + item.setFftMode(self.ctrl.fftCheck.isChecked()) + item.setDownsampling(*self.downsampleMode()) + item.setClipToView(self.clipToViewMode()) + item.setPointMode(self.pointMode()) + + ## Hide older plots if needed + self.updateDecimation() + + ## Add to average if needed + self.updateParamList() + if self.ctrl.averageGroup.isChecked() and 'skipAverage' not in kargs: + self.addAvgCurve(item) + + #c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged) + #item.sigPlotChanged.connect(self.plotChanged) + #self.plotChanged() + #name = kargs.get('name', getattr(item, 'opts', {}).get('name', None)) + if name is not None and hasattr(self, 'legend') and self.legend is not None: + self.legend.addItem(item, name=name) + + + def addDataItem(self, item, *args): + print("PlotItem.addDataItem is deprecated. Use addItem instead.") + self.addItem(item, *args) + + def listDataItems(self): + """Return a list of all data items (PlotDataItem, PlotCurveItem, ScatterPlotItem, etc) + contained in this PlotItem.""" + return self.dataItems[:] + + def addCurve(self, c, params=None): + print("PlotItem.addCurve is deprecated. Use addItem instead.") + self.addItem(c, params) + + def addLine(self, x=None, y=None, z=None, **kwds): + """ + Create an InfiniteLine and add to the plot. + + If *x* is specified, + the line will be vertical. If *y* is specified, the line will be + horizontal. All extra keyword arguments are passed to + :func:`InfiniteLine.__init__() `. + Returns the item created. + """ + pos = kwds.get('pos', x if x is not None else y) + angle = kwds.get('angle', 0 if x is None else 90) + line = InfiniteLine(pos, angle, **kwds) + self.addItem(line) + if z is not None: + line.setZValue(z) + return line + + + + def removeItem(self, item): + """ + Remove an item from the internal ViewBox. + """ + if not item in self.items: + return + self.items.remove(item) + if item in self.dataItems: + self.dataItems.remove(item) + + if item.scene() is not None: + self.vb.removeItem(item) + if item in self.curves: + self.curves.remove(item) + self.updateDecimation() + self.updateParamList() + #item.connect(item, QtCore.SIGNAL('plotChanged'), self.plotChanged) + #item.sigPlotChanged.connect(self.plotChanged) + + def clear(self): + """ + Remove all items from the ViewBox. + """ + for i in self.items[:]: + self.removeItem(i) + self.avgCurves = {} + + def clearPlots(self): + for i in self.curves[:]: + self.removeItem(i) + self.avgCurves = {} + + + def plot(self, *args, **kargs): + """ + Add and return a new plot. + See :func:`PlotDataItem.__init__ ` for data arguments + + Extra allowed arguments are: + clear - clear all plots before displaying new data + params - meta-parameters to associate with this data + """ + + + clear = kargs.get('clear', False) + params = kargs.get('params', None) + + if clear: + self.clear() + + item = PlotDataItem(*args, **kargs) + + if params is None: + params = {} + self.addItem(item, params=params) + + return item + + def addLegend(self, size=None, offset=(30, 30)): + """ + Create a new LegendItem and anchor it over the internal ViewBox. + Plots will be automatically displayed in the legend if they + are created with the 'name' argument. + """ + self.legend = LegendItem(size, offset) + self.legend.setParentItem(self.vb) + return self.legend + + def scatterPlot(self, *args, **kargs): + if 'pen' in kargs: + kargs['symbolPen'] = kargs['pen'] + kargs['pen'] = None + + if 'brush' in kargs: + kargs['symbolBrush'] = kargs['brush'] + del kargs['brush'] + + if 'size' in kargs: + kargs['symbolSize'] = kargs['size'] + del kargs['size'] + + return self.plot(*args, **kargs) + + def replot(self): + self.update() + + def updateParamList(self): + self.ctrl.avgParamList.clear() + ## Check to see that each parameter for each curve is present in the list + for c in self.curves: + for p in list(self.itemMeta.get(c, {}).keys()): + if type(p) is tuple: + p = '.'.join(p) + + ## If the parameter is not in the list, add it. + matches = self.ctrl.avgParamList.findItems(p, QtCore.Qt.MatchExactly) + if len(matches) == 0: + i = QtGui.QListWidgetItem(p) + if p in self.paramList and self.paramList[p] is True: + i.setCheckState(QtCore.Qt.Checked) + else: + i.setCheckState(QtCore.Qt.Unchecked) + self.ctrl.avgParamList.addItem(i) + else: + i = matches[0] + + self.paramList[p] = (i.checkState() == QtCore.Qt.Checked) + + + ## Qt's SVG-writing capabilities are pretty terrible. + def writeSvgCurves(self, fileName=None): + if fileName is None: + self.fileDialog = FileDialog() + if PlotItem.lastFileDir is not None: + self.fileDialog.setDirectory(PlotItem.lastFileDir) + self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile) + self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave) + self.fileDialog.show() + self.fileDialog.fileSelected.connect(self.writeSvg) + return + #if fileName is None: + #fileName = QtGui.QFileDialog.getSaveFileName() + if isinstance(fileName, tuple): + raise Exception("Not implemented yet..") + fileName = str(fileName) + PlotItem.lastFileDir = os.path.dirname(fileName) + + rect = self.vb.viewRect() + xRange = rect.left(), rect.right() + + svg = "" + fh = open(fileName, 'w') + + dx = max(rect.right(),0) - min(rect.left(),0) + ymn = min(rect.top(), rect.bottom()) + ymx = max(rect.top(), rect.bottom()) + dy = max(ymx,0) - min(ymn,0) + sx = 1. + sy = 1. + while dx*sx < 10: + sx *= 1000 + while dy*sy < 10: + sy *= 1000 + sy *= -1 + + #fh.write('\n' % (rect.left()*sx, rect.top()*sx, rect.width()*sy, rect.height()*sy)) + fh.write('\n') + fh.write('\n' % (rect.left()*sx, rect.right()*sx)) + fh.write('\n' % (rect.top()*sy, rect.bottom()*sy)) + + + for item in self.curves: + if isinstance(item, PlotCurveItem): + color = fn.colorStr(item.pen.color()) + opacity = item.pen.color().alpha() / 255. + color = color[:6] + x, y = item.getData() + mask = (x > xRange[0]) * (x < xRange[1]) + mask[:-1] += mask[1:] + m2 = mask.copy() + mask[1:] += m2[:-1] + x = x[mask] + y = y[mask] + + x *= sx + y *= sy + + #fh.write('\n' % color) + fh.write('') + #fh.write("") + for item in self.dataItems: + if isinstance(item, ScatterPlotItem): + + pRect = item.boundingRect() + vRect = pRect.intersected(rect) + + for point in item.points(): + pos = point.pos() + if not rect.contains(pos): + continue + color = fn.colorStr(point.brush.color()) + opacity = point.brush.color().alpha() / 255. + color = color[:6] + x = pos.x() * sx + y = pos.y() * sy + + fh.write('\n' % (x, y, color, opacity)) + #fh.write('') + + ## get list of curves, scatter plots + + + fh.write("\n") + + + + def writeSvg(self, fileName=None): + if fileName is None: + fileName = QtGui.QFileDialog.getSaveFileName() + fileName = str(fileName) + PlotItem.lastFileDir = os.path.dirname(fileName) + + self.svg = QtSvg.QSvgGenerator() + self.svg.setFileName(fileName) + res = 120. + view = self.scene().views()[0] + bounds = view.viewport().rect() + bounds = QtCore.QRectF(0, 0, bounds.width(), bounds.height()) + + self.svg.setResolution(res) + self.svg.setViewBox(bounds) + + self.svg.setSize(QtCore.QSize(bounds.width(), bounds.height())) + + painter = QtGui.QPainter(self.svg) + view.render(painter, bounds) + + painter.end() + + ## Workaround to set pen widths correctly + import re + data = open(fileName).readlines() + for i in range(len(data)): + line = data[i] + m = re.match(r'(= split: + curves[i].show() + else: + if self.ctrl.forgetTracesCheck.isChecked(): + curves[i].clear() + self.removeItem(curves[i]) + else: + curves[i].hide() + + + def updateAlpha(self, *args): + (alpha, auto) = self.alphaState() + for c in self.curves: + c.setAlpha(alpha**2, auto) + + def alphaState(self): + enabled = self.ctrl.alphaGroup.isChecked() + auto = self.ctrl.autoAlphaCheck.isChecked() + alpha = float(self.ctrl.alphaSlider.value()) / self.ctrl.alphaSlider.maximum() + if auto: + alpha = 1.0 ## should be 1/number of overlapping plots + if not enabled: + auto = False + alpha = 1.0 + return (alpha, auto) + + def pointMode(self): + if self.ctrl.pointsGroup.isChecked(): + if self.ctrl.autoPointsCheck.isChecked(): + mode = None + else: + mode = True + else: + mode = False + return mode + + + def resizeEvent(self, ev): + if self.autoBtn is None: ## already closed down + return + btnRect = self.mapRectFromItem(self.autoBtn, self.autoBtn.boundingRect()) + y = self.size().height() - btnRect.height() + self.autoBtn.setPos(0, y) + + + def getMenu(self): + return self.ctrlMenu + + def getContextMenus(self, event): + ## called when another item is displaying its context menu; we get to add extras to the end of the menu. + if self.menuEnabled(): + return self.ctrlMenu + else: + return None + + def setMenuEnabled(self, enableMenu=True, enableViewBoxMenu='same'): + """ + Enable or disable the context menu for this PlotItem. + By default, the ViewBox's context menu will also be affected. + (use enableViewBoxMenu=None to leave the ViewBox unchanged) + """ + self._menuEnabled = enableMenu + if enableViewBoxMenu is None: + return + if enableViewBoxMenu is 'same': + enableViewBoxMenu = enableMenu + self.vb.setMenuEnabled(enableViewBoxMenu) + + def menuEnabled(self): + return self._menuEnabled + + def hoverEvent(self, ev): + if ev.enter: + self.mouseHovering = True + if ev.exit: + self.mouseHovering = False + + self.updateButtons() + + + def getLabel(self, key): + pass + + def _checkScaleKey(self, key): + if key not in self.axes: + raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.axes.keys())))) + + def getScale(self, key): + return self.getAxis(key) + + def getAxis(self, name): + """Return the specified AxisItem. + *name* should be 'left', 'bottom', 'top', or 'right'.""" + self._checkScaleKey(name) + return self.axes[name]['item'] + + def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args): + """ + Set the label for an axis. Basic HTML formatting is allowed. + + ============== ================================================================= + **Arguments:** + axis must be one of 'left', 'bottom', 'right', or 'top' + text text to display along the axis. HTML allowed. + units units to display after the title. If units are given, + then an SI prefix will be automatically appended + and the axis values will be scaled accordingly. + (ie, use 'V' instead of 'mV'; 'm' will be added automatically) + ============== ================================================================= + """ + self.getAxis(axis).setLabel(text=text, units=units, **args) + self.showAxis(axis) + + def setLabels(self, **kwds): + """ + Convenience function allowing multiple labels and/or title to be set in one call. + Keyword arguments can be 'title', 'left', 'bottom', 'right', or 'top'. + Values may be strings or a tuple of arguments to pass to setLabel. + """ + for k,v in kwds.items(): + if k == 'title': + self.setTitle(v) + else: + if isinstance(v, basestring): + v = (v,) + self.setLabel(k, *v) + + + def showLabel(self, axis, show=True): + """ + Show or hide one of the plot's axis labels (the axis itself will be unaffected). + axis must be one of 'left', 'bottom', 'right', or 'top' + """ + self.getScale(axis).showLabel(show) + + def setTitle(self, title=None, **args): + """ + Set the title of the plot. Basic HTML formatting is allowed. + If title is None, then the title will be hidden. + """ + if title is None: + self.titleLabel.setVisible(False) + self.layout.setRowFixedHeight(0, 0) + self.titleLabel.setMaximumHeight(0) + else: + self.titleLabel.setMaximumHeight(30) + self.layout.setRowFixedHeight(0, 30) + self.titleLabel.setVisible(True) + self.titleLabel.setText(title, **args) + + def showAxis(self, axis, show=True): + """ + Show or hide one of the plot's axes. + axis must be one of 'left', 'bottom', 'right', or 'top' + """ + s = self.getScale(axis) + p = self.axes[axis]['pos'] + if show: + s.show() + else: + s.hide() + + def hideAxis(self, axis): + """Hide one of the PlotItem's axes. ('left', 'bottom', 'right', or 'top')""" + self.showAxis(axis, False) + + def showScale(self, *args, **kargs): + print("Deprecated. use showAxis() instead") + return self.showAxis(*args, **kargs) + + def hideButtons(self): + """Causes auto-scale button ('A' in lower-left corner) to be hidden for this PlotItem""" + #self.ctrlBtn.hide() + self.buttonsHidden = True + self.updateButtons() + + def showButtons(self): + """Causes auto-scale button ('A' in lower-left corner) to be visible for this PlotItem""" + #self.ctrlBtn.hide() + self.buttonsHidden = False + self.updateButtons() + + def updateButtons(self): + try: + if self._exportOpts is False and self.mouseHovering and not self.buttonsHidden and not all(self.vb.autoRangeEnabled()): + self.autoBtn.show() + else: + self.autoBtn.hide() + except RuntimeError: + pass # this can happen if the plot has been deleted. + + def _plotArray(self, arr, x=None, **kargs): + if arr.ndim != 1: + raise Exception("Array must be 1D to plot (shape is %s)" % arr.shape) + if x is None: + x = np.arange(arr.shape[0]) + if x.ndim != 1: + raise Exception("X array must be 1D to plot (shape is %s)" % x.shape) + c = PlotCurveItem(arr, x=x, **kargs) + return c + + + + def _plotMetaArray(self, arr, x=None, autoLabel=True, **kargs): + inf = arr.infoCopy() + if arr.ndim != 1: + raise Exception('can only automatically plot 1 dimensional arrays.') + ## create curve + try: + xv = arr.xvals(0) + except: + if x is None: + xv = np.arange(arr.shape[0]) + else: + xv = x + c = PlotCurveItem(**kargs) + c.setData(x=xv, y=arr.view(np.ndarray)) + + if autoLabel: + name = arr._info[0].get('name', None) + units = arr._info[0].get('units', None) + self.setLabel('bottom', text=name, units=units) + + name = arr._info[1].get('name', None) + units = arr._info[1].get('units', None) + self.setLabel('left', text=name, units=units) + + return c + + + def setExportMode(self, export, opts=None): + GraphicsWidget.setExportMode(self, export, opts) + self.updateButtons() + #if export: + #self.autoBtn.hide() + #else: + #self.autoBtn.show() + diff --git a/papi/pyqtgraph/graphicsItems/PlotItem/__init__.py b/papi/pyqtgraph/graphicsItems/PlotItem/__init__.py new file mode 100644 index 00000000..d797978c --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/PlotItem/__init__.py @@ -0,0 +1 @@ +from .PlotItem import PlotItem diff --git a/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui new file mode 100644 index 00000000..dffc62d0 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui @@ -0,0 +1,343 @@ + + + Form + + + + 0 + 0 + 481 + 840 + + + + Form + + + + + 0 + 640 + 242 + 182 + + + + Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available). + + + Average + + + true + + + false + + + + 0 + + + 0 + + + + + + + + + + 10 + 140 + 191 + 171 + + + + + 0 + + + 0 + + + + + Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced. + + + Clip to View + + + + + + + If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed. + + + Max Traces: + + + + + + + Downsample + + + + + + + Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower. + + + Peak + + + true + + + + + + + If multiple curves are displayed in this plot, check "Max Traces" and set this value to limit the number of traces that are displayed. + + + + + + + If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden). + + + Forget hidden traces + + + + + + + Downsample by taking the mean of N samples. + + + Mean + + + + + + + Downsample by taking the first of N samples. This method is fastest and least accurate. + + + Subsample + + + + + + + Automatically downsample data based on the visible range. This assumes X values are uniformly spaced. + + + Auto + + + true + + + + + + + Qt::Horizontal + + + QSizePolicy::Maximum + + + + 30 + 20 + + + + + + + + Downsample data before plotting. (plot every Nth sample) + + + x + + + 1 + + + 100000 + + + 1 + + + + + + + + + 0 + 0 + 154 + 79 + + + + + + + Power Spectrum (FFT) + + + + + + + Log X + + + + + + + Log Y + + + + + + + + + 10 + 550 + 234 + 58 + + + + Points + + + true + + + + + + Auto + + + true + + + + + + + + + 10 + 460 + 221 + 81 + + + + + + + Show X Grid + + + + + + + Show Y Grid + + + + + + + 255 + + + 128 + + + Qt::Horizontal + + + + + + + Opacity + + + + + + + + + 10 + 390 + 234 + 60 + + + + Alpha + + + true + + + + + + Auto + + + false + + + + + + + 1000 + + + 1000 + + + Qt::Horizontal + + + + + + + + + diff --git a/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py new file mode 100644 index 00000000..e09c9978 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui' +# +# Created: Mon Dec 23 10:10:51 2013 +# by: PyQt4 UI code generator 4.10 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(481, 840) + self.averageGroup = QtGui.QGroupBox(Form) + self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) + self.averageGroup.setCheckable(True) + self.averageGroup.setChecked(False) + self.averageGroup.setObjectName(_fromUtf8("averageGroup")) + self.gridLayout_5 = QtGui.QGridLayout(self.averageGroup) + self.gridLayout_5.setMargin(0) + self.gridLayout_5.setSpacing(0) + self.gridLayout_5.setObjectName(_fromUtf8("gridLayout_5")) + self.avgParamList = QtGui.QListWidget(self.averageGroup) + self.avgParamList.setObjectName(_fromUtf8("avgParamList")) + self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) + self.decimateGroup = QtGui.QFrame(Form) + self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) + self.decimateGroup.setObjectName(_fromUtf8("decimateGroup")) + self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) + self.gridLayout_4.setMargin(0) + self.gridLayout_4.setSpacing(0) + self.gridLayout_4.setObjectName(_fromUtf8("gridLayout_4")) + self.clipToViewCheck = QtGui.QCheckBox(self.decimateGroup) + self.clipToViewCheck.setObjectName(_fromUtf8("clipToViewCheck")) + self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) + self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup) + self.maxTracesCheck.setObjectName(_fromUtf8("maxTracesCheck")) + self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) + self.downsampleCheck = QtGui.QCheckBox(self.decimateGroup) + self.downsampleCheck.setObjectName(_fromUtf8("downsampleCheck")) + self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) + self.peakRadio = QtGui.QRadioButton(self.decimateGroup) + self.peakRadio.setChecked(True) + self.peakRadio.setObjectName(_fromUtf8("peakRadio")) + self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) + self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup) + self.maxTracesSpin.setObjectName(_fromUtf8("maxTracesSpin")) + self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) + self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup) + self.forgetTracesCheck.setObjectName(_fromUtf8("forgetTracesCheck")) + self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) + self.meanRadio = QtGui.QRadioButton(self.decimateGroup) + self.meanRadio.setObjectName(_fromUtf8("meanRadio")) + self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) + self.subsampleRadio = QtGui.QRadioButton(self.decimateGroup) + self.subsampleRadio.setObjectName(_fromUtf8("subsampleRadio")) + self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) + self.autoDownsampleCheck = QtGui.QCheckBox(self.decimateGroup) + self.autoDownsampleCheck.setChecked(True) + self.autoDownsampleCheck.setObjectName(_fromUtf8("autoDownsampleCheck")) + self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) + spacerItem = QtGui.QSpacerItem(30, 20, QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Minimum) + self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) + self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) + self.downsampleSpin.setMinimum(1) + self.downsampleSpin.setMaximum(100000) + self.downsampleSpin.setProperty("value", 1) + self.downsampleSpin.setObjectName(_fromUtf8("downsampleSpin")) + self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) + self.transformGroup = QtGui.QFrame(Form) + self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79)) + self.transformGroup.setObjectName(_fromUtf8("transformGroup")) + self.gridLayout = QtGui.QGridLayout(self.transformGroup) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.fftCheck = QtGui.QCheckBox(self.transformGroup) + self.fftCheck.setObjectName(_fromUtf8("fftCheck")) + self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) + self.logXCheck = QtGui.QCheckBox(self.transformGroup) + self.logXCheck.setObjectName(_fromUtf8("logXCheck")) + self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) + self.logYCheck = QtGui.QCheckBox(self.transformGroup) + self.logYCheck.setObjectName(_fromUtf8("logYCheck")) + self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) + self.pointsGroup = QtGui.QGroupBox(Form) + self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) + self.pointsGroup.setCheckable(True) + self.pointsGroup.setObjectName(_fromUtf8("pointsGroup")) + self.verticalLayout_5 = QtGui.QVBoxLayout(self.pointsGroup) + self.verticalLayout_5.setObjectName(_fromUtf8("verticalLayout_5")) + self.autoPointsCheck = QtGui.QCheckBox(self.pointsGroup) + self.autoPointsCheck.setChecked(True) + self.autoPointsCheck.setObjectName(_fromUtf8("autoPointsCheck")) + self.verticalLayout_5.addWidget(self.autoPointsCheck) + self.gridGroup = QtGui.QFrame(Form) + self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) + self.gridGroup.setObjectName(_fromUtf8("gridGroup")) + self.gridLayout_2 = QtGui.QGridLayout(self.gridGroup) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.xGridCheck = QtGui.QCheckBox(self.gridGroup) + self.xGridCheck.setObjectName(_fromUtf8("xGridCheck")) + self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) + self.yGridCheck = QtGui.QCheckBox(self.gridGroup) + self.yGridCheck.setObjectName(_fromUtf8("yGridCheck")) + self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) + self.gridAlphaSlider = QtGui.QSlider(self.gridGroup) + self.gridAlphaSlider.setMaximum(255) + self.gridAlphaSlider.setProperty("value", 128) + self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.gridAlphaSlider.setObjectName(_fromUtf8("gridAlphaSlider")) + self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) + self.label = QtGui.QLabel(self.gridGroup) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) + self.alphaGroup = QtGui.QGroupBox(Form) + self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) + self.alphaGroup.setCheckable(True) + self.alphaGroup.setObjectName(_fromUtf8("alphaGroup")) + self.horizontalLayout = QtGui.QHBoxLayout(self.alphaGroup) + self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout")) + self.autoAlphaCheck = QtGui.QCheckBox(self.alphaGroup) + self.autoAlphaCheck.setChecked(False) + self.autoAlphaCheck.setObjectName(_fromUtf8("autoAlphaCheck")) + self.horizontalLayout.addWidget(self.autoAlphaCheck) + self.alphaSlider = QtGui.QSlider(self.alphaGroup) + self.alphaSlider.setMaximum(1000) + self.alphaSlider.setProperty("value", 1000) + self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.alphaSlider.setObjectName(_fromUtf8("alphaSlider")) + self.horizontalLayout.addWidget(self.alphaSlider) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(_translate("Form", "Form", None)) + self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None)) + self.averageGroup.setTitle(_translate("Form", "Average", None)) + self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None)) + self.clipToViewCheck.setText(_translate("Form", "Clip to View", None)) + self.maxTracesCheck.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None)) + self.maxTracesCheck.setText(_translate("Form", "Max Traces:", None)) + self.downsampleCheck.setText(_translate("Form", "Downsample", None)) + self.peakRadio.setToolTip(_translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None)) + self.peakRadio.setText(_translate("Form", "Peak", None)) + self.maxTracesSpin.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None)) + self.forgetTracesCheck.setToolTip(_translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None)) + self.forgetTracesCheck.setText(_translate("Form", "Forget hidden traces", None)) + self.meanRadio.setToolTip(_translate("Form", "Downsample by taking the mean of N samples.", None)) + self.meanRadio.setText(_translate("Form", "Mean", None)) + self.subsampleRadio.setToolTip(_translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None)) + self.subsampleRadio.setText(_translate("Form", "Subsample", None)) + self.autoDownsampleCheck.setToolTip(_translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None)) + self.autoDownsampleCheck.setText(_translate("Form", "Auto", None)) + self.downsampleSpin.setToolTip(_translate("Form", "Downsample data before plotting. (plot every Nth sample)", None)) + self.downsampleSpin.setSuffix(_translate("Form", "x", None)) + self.fftCheck.setText(_translate("Form", "Power Spectrum (FFT)", None)) + self.logXCheck.setText(_translate("Form", "Log X", None)) + self.logYCheck.setText(_translate("Form", "Log Y", None)) + self.pointsGroup.setTitle(_translate("Form", "Points", None)) + self.autoPointsCheck.setText(_translate("Form", "Auto", None)) + self.xGridCheck.setText(_translate("Form", "Show X Grid", None)) + self.yGridCheck.setText(_translate("Form", "Show Y Grid", None)) + self.label.setText(_translate("Form", "Opacity", None)) + self.alphaGroup.setTitle(_translate("Form", "Alpha", None)) + self.autoAlphaCheck.setText(_translate("Form", "Auto", None)) + diff --git a/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py new file mode 100644 index 00000000..aff31211 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui' +# +# Created: Mon Dec 23 10:10:52 2013 +# by: pyside-uic 0.2.14 running on PySide 1.1.2 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(481, 840) + self.averageGroup = QtGui.QGroupBox(Form) + self.averageGroup.setGeometry(QtCore.QRect(0, 640, 242, 182)) + self.averageGroup.setCheckable(True) + self.averageGroup.setChecked(False) + self.averageGroup.setObjectName("averageGroup") + self.gridLayout_5 = QtGui.QGridLayout(self.averageGroup) + self.gridLayout_5.setContentsMargins(0, 0, 0, 0) + self.gridLayout_5.setSpacing(0) + self.gridLayout_5.setObjectName("gridLayout_5") + self.avgParamList = QtGui.QListWidget(self.averageGroup) + self.avgParamList.setObjectName("avgParamList") + self.gridLayout_5.addWidget(self.avgParamList, 0, 0, 1, 1) + self.decimateGroup = QtGui.QFrame(Form) + self.decimateGroup.setGeometry(QtCore.QRect(10, 140, 191, 171)) + self.decimateGroup.setObjectName("decimateGroup") + self.gridLayout_4 = QtGui.QGridLayout(self.decimateGroup) + self.gridLayout_4.setContentsMargins(0, 0, 0, 0) + self.gridLayout_4.setSpacing(0) + self.gridLayout_4.setObjectName("gridLayout_4") + self.clipToViewCheck = QtGui.QCheckBox(self.decimateGroup) + self.clipToViewCheck.setObjectName("clipToViewCheck") + self.gridLayout_4.addWidget(self.clipToViewCheck, 7, 0, 1, 3) + self.maxTracesCheck = QtGui.QCheckBox(self.decimateGroup) + self.maxTracesCheck.setObjectName("maxTracesCheck") + self.gridLayout_4.addWidget(self.maxTracesCheck, 8, 0, 1, 2) + self.downsampleCheck = QtGui.QCheckBox(self.decimateGroup) + self.downsampleCheck.setObjectName("downsampleCheck") + self.gridLayout_4.addWidget(self.downsampleCheck, 0, 0, 1, 3) + self.peakRadio = QtGui.QRadioButton(self.decimateGroup) + self.peakRadio.setChecked(True) + self.peakRadio.setObjectName("peakRadio") + self.gridLayout_4.addWidget(self.peakRadio, 6, 1, 1, 2) + self.maxTracesSpin = QtGui.QSpinBox(self.decimateGroup) + self.maxTracesSpin.setObjectName("maxTracesSpin") + self.gridLayout_4.addWidget(self.maxTracesSpin, 8, 2, 1, 1) + self.forgetTracesCheck = QtGui.QCheckBox(self.decimateGroup) + self.forgetTracesCheck.setObjectName("forgetTracesCheck") + self.gridLayout_4.addWidget(self.forgetTracesCheck, 9, 0, 1, 3) + self.meanRadio = QtGui.QRadioButton(self.decimateGroup) + self.meanRadio.setObjectName("meanRadio") + self.gridLayout_4.addWidget(self.meanRadio, 3, 1, 1, 2) + self.subsampleRadio = QtGui.QRadioButton(self.decimateGroup) + self.subsampleRadio.setObjectName("subsampleRadio") + self.gridLayout_4.addWidget(self.subsampleRadio, 2, 1, 1, 2) + self.autoDownsampleCheck = QtGui.QCheckBox(self.decimateGroup) + self.autoDownsampleCheck.setChecked(True) + self.autoDownsampleCheck.setObjectName("autoDownsampleCheck") + self.gridLayout_4.addWidget(self.autoDownsampleCheck, 1, 2, 1, 1) + spacerItem = QtGui.QSpacerItem(30, 20, QtGui.QSizePolicy.Maximum, QtGui.QSizePolicy.Minimum) + self.gridLayout_4.addItem(spacerItem, 2, 0, 1, 1) + self.downsampleSpin = QtGui.QSpinBox(self.decimateGroup) + self.downsampleSpin.setMinimum(1) + self.downsampleSpin.setMaximum(100000) + self.downsampleSpin.setProperty("value", 1) + self.downsampleSpin.setObjectName("downsampleSpin") + self.gridLayout_4.addWidget(self.downsampleSpin, 1, 1, 1, 1) + self.transformGroup = QtGui.QFrame(Form) + self.transformGroup.setGeometry(QtCore.QRect(0, 0, 154, 79)) + self.transformGroup.setObjectName("transformGroup") + self.gridLayout = QtGui.QGridLayout(self.transformGroup) + self.gridLayout.setObjectName("gridLayout") + self.fftCheck = QtGui.QCheckBox(self.transformGroup) + self.fftCheck.setObjectName("fftCheck") + self.gridLayout.addWidget(self.fftCheck, 0, 0, 1, 1) + self.logXCheck = QtGui.QCheckBox(self.transformGroup) + self.logXCheck.setObjectName("logXCheck") + self.gridLayout.addWidget(self.logXCheck, 1, 0, 1, 1) + self.logYCheck = QtGui.QCheckBox(self.transformGroup) + self.logYCheck.setObjectName("logYCheck") + self.gridLayout.addWidget(self.logYCheck, 2, 0, 1, 1) + self.pointsGroup = QtGui.QGroupBox(Form) + self.pointsGroup.setGeometry(QtCore.QRect(10, 550, 234, 58)) + self.pointsGroup.setCheckable(True) + self.pointsGroup.setObjectName("pointsGroup") + self.verticalLayout_5 = QtGui.QVBoxLayout(self.pointsGroup) + self.verticalLayout_5.setObjectName("verticalLayout_5") + self.autoPointsCheck = QtGui.QCheckBox(self.pointsGroup) + self.autoPointsCheck.setChecked(True) + self.autoPointsCheck.setObjectName("autoPointsCheck") + self.verticalLayout_5.addWidget(self.autoPointsCheck) + self.gridGroup = QtGui.QFrame(Form) + self.gridGroup.setGeometry(QtCore.QRect(10, 460, 221, 81)) + self.gridGroup.setObjectName("gridGroup") + self.gridLayout_2 = QtGui.QGridLayout(self.gridGroup) + self.gridLayout_2.setObjectName("gridLayout_2") + self.xGridCheck = QtGui.QCheckBox(self.gridGroup) + self.xGridCheck.setObjectName("xGridCheck") + self.gridLayout_2.addWidget(self.xGridCheck, 0, 0, 1, 2) + self.yGridCheck = QtGui.QCheckBox(self.gridGroup) + self.yGridCheck.setObjectName("yGridCheck") + self.gridLayout_2.addWidget(self.yGridCheck, 1, 0, 1, 2) + self.gridAlphaSlider = QtGui.QSlider(self.gridGroup) + self.gridAlphaSlider.setMaximum(255) + self.gridAlphaSlider.setProperty("value", 128) + self.gridAlphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.gridAlphaSlider.setObjectName("gridAlphaSlider") + self.gridLayout_2.addWidget(self.gridAlphaSlider, 2, 1, 1, 1) + self.label = QtGui.QLabel(self.gridGroup) + self.label.setObjectName("label") + self.gridLayout_2.addWidget(self.label, 2, 0, 1, 1) + self.alphaGroup = QtGui.QGroupBox(Form) + self.alphaGroup.setGeometry(QtCore.QRect(10, 390, 234, 60)) + self.alphaGroup.setCheckable(True) + self.alphaGroup.setObjectName("alphaGroup") + self.horizontalLayout = QtGui.QHBoxLayout(self.alphaGroup) + self.horizontalLayout.setObjectName("horizontalLayout") + self.autoAlphaCheck = QtGui.QCheckBox(self.alphaGroup) + self.autoAlphaCheck.setChecked(False) + self.autoAlphaCheck.setObjectName("autoAlphaCheck") + self.horizontalLayout.addWidget(self.autoAlphaCheck) + self.alphaSlider = QtGui.QSlider(self.alphaGroup) + self.alphaSlider.setMaximum(1000) + self.alphaSlider.setProperty("value", 1000) + self.alphaSlider.setOrientation(QtCore.Qt.Horizontal) + self.alphaSlider.setObjectName("alphaSlider") + self.horizontalLayout.addWidget(self.alphaSlider) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8)) + self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8)) + self.clipToViewCheck.setToolTip(QtGui.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) + self.clipToViewCheck.setText(QtGui.QApplication.translate("Form", "Clip to View", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8)) + self.downsampleCheck.setText(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8)) + self.peakRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None, QtGui.QApplication.UnicodeUTF8)) + self.peakRadio.setText(QtGui.QApplication.translate("Form", "Peak", None, QtGui.QApplication.UnicodeUTF8)) + self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8)) + self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8)) + self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8)) + self.meanRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the mean of N samples.", None, QtGui.QApplication.UnicodeUTF8)) + self.meanRadio.setText(QtGui.QApplication.translate("Form", "Mean", None, QtGui.QApplication.UnicodeUTF8)) + self.subsampleRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None, QtGui.QApplication.UnicodeUTF8)) + self.subsampleRadio.setText(QtGui.QApplication.translate("Form", "Subsample", None, QtGui.QApplication.UnicodeUTF8)) + self.autoDownsampleCheck.setToolTip(QtGui.QApplication.translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8)) + self.autoDownsampleCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.downsampleSpin.setToolTip(QtGui.QApplication.translate("Form", "Downsample data before plotting. (plot every Nth sample)", None, QtGui.QApplication.UnicodeUTF8)) + self.downsampleSpin.setSuffix(QtGui.QApplication.translate("Form", "x", None, QtGui.QApplication.UnicodeUTF8)) + self.fftCheck.setText(QtGui.QApplication.translate("Form", "Power Spectrum (FFT)", None, QtGui.QApplication.UnicodeUTF8)) + self.logXCheck.setText(QtGui.QApplication.translate("Form", "Log X", None, QtGui.QApplication.UnicodeUTF8)) + self.logYCheck.setText(QtGui.QApplication.translate("Form", "Log Y", None, QtGui.QApplication.UnicodeUTF8)) + self.pointsGroup.setTitle(QtGui.QApplication.translate("Form", "Points", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPointsCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.xGridCheck.setText(QtGui.QApplication.translate("Form", "Show X Grid", None, QtGui.QApplication.UnicodeUTF8)) + self.yGridCheck.setText(QtGui.QApplication.translate("Form", "Show Y Grid", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("Form", "Opacity", None, QtGui.QApplication.UnicodeUTF8)) + self.alphaGroup.setTitle(QtGui.QApplication.translate("Form", "Alpha", None, QtGui.QApplication.UnicodeUTF8)) + self.autoAlphaCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/pyqtgraph/graphicsItems/ROI.py b/papi/pyqtgraph/graphicsItems/ROI.py new file mode 100644 index 00000000..7707466a --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ROI.py @@ -0,0 +1,2246 @@ +# -*- coding: utf-8 -*- +""" +ROI.py - Interactive graphics items for GraphicsView (ROI widgets) +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +Implements a series of graphics items which display movable/scalable/rotatable shapes +for use as region-of-interest markers. ROI class automatically handles extraction +of array data from ImageItems. + +The ROI class is meant to serve as the base for more specific types; see several examples +of how to build an ROI at the bottom of the file. +""" + +from ..Qt import QtCore, QtGui +import numpy as np +#from numpy.linalg import norm +from ..Point import * +from ..SRTTransform import SRTTransform +from math import cos, sin +from .. import functions as fn +from .GraphicsObject import GraphicsObject +from .UIGraphicsItem import UIGraphicsItem + +__all__ = [ + 'ROI', + 'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI', + 'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI', 'CrosshairROI', +] + + +def rectStr(r): + return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height()) + +class ROI(GraphicsObject): + """ + Generic region-of-interest widget. + + Can be used for implementing many types of selection box with + rotate/translate/scale handles. + ROIs can be customized to have a variety of shapes (by subclassing or using + any of the built-in subclasses) and any combination of draggable handles + that allow the user to manipulate the ROI. + + + + ================ =========================================================== + **Arguments** + pos (length-2 sequence) Indicates the position of the ROI's + origin. For most ROIs, this is the lower-left corner of + its bounding rectangle. + size (length-2 sequence) Indicates the width and height of the + ROI. + angle (float) The rotation of the ROI in degrees. Default is 0. + invertible (bool) If True, the user may resize the ROI to have + negative width or height (assuming the ROI has scale + handles). Default is False. + maxBounds (QRect, QRectF, or None) Specifies boundaries that the ROI + cannot be dragged outside of by the user. Default is None. + snapSize (float) The spacing of snap positions used when *scaleSnap* + or *translateSnap* are enabled. Default is 1.0. + scaleSnap (bool) If True, the width and height of the ROI are forced + to be integer multiples of *snapSize* when being resized + by the user. Default is False. + translateSnap (bool) If True, the x and y positions of the ROI are forced + to be integer multiples of *snapSize* when being resized + by the user. Default is False. + rotateSnap (bool) If True, the ROI angle is forced to a multiple of + 15 degrees when rotated by the user. Default is False. + parent (QGraphicsItem) The graphics item parent of this ROI. It + is generally not necessary to specify the parent. + pen (QPen or argument to pg.mkPen) The pen to use when drawing + the shape of the ROI. + movable (bool) If True, the ROI can be moved by dragging anywhere + inside the ROI. Default is True. + removable (bool) If True, the ROI will be given a context menu with + an option to remove the ROI. The ROI emits + sigRemoveRequested when this menu action is selected. + Default is False. + ================ =========================================================== + + + + ======================= ==================================================== + **Signals** + sigRegionChangeFinished Emitted when the user stops dragging the ROI (or + one of its handles) or if the ROI is changed + programatically. + sigRegionChangeStarted Emitted when the user starts dragging the ROI (or + one of its handles). + sigRegionChanged Emitted any time the position of the ROI changes, + including while it is being dragged by the user. + sigHoverEvent Emitted when the mouse hovers over the ROI. + sigClicked Emitted when the user clicks on the ROI. + Note that clicking is disabled by default to prevent + stealing clicks from objects behind the ROI. To + enable clicking, call + roi.setAcceptedMouseButtons(QtCore.Qt.LeftButton). + See QtGui.QGraphicsItem documentation for more + details. + sigRemoveRequested Emitted when the user selects 'remove' from the + ROI's context menu (if available). + ======================= ==================================================== + """ + + sigRegionChangeFinished = QtCore.Signal(object) + sigRegionChangeStarted = QtCore.Signal(object) + sigRegionChanged = QtCore.Signal(object) + sigHoverEvent = QtCore.Signal(object) + sigClicked = QtCore.Signal(object, object) + sigRemoveRequested = QtCore.Signal(object) + + def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None, movable=True, removable=False): + #QObjectWorkaround.__init__(self) + GraphicsObject.__init__(self, parent) + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + pos = Point(pos) + size = Point(size) + self.aspectLocked = False + self.translatable = movable + self.rotateAllowed = True + self.removable = removable + self.menu = None + + self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted. + self.mouseHovering = False + if pen is None: + pen = (255, 255, 255) + self.setPen(pen) + + self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255)) + self.handles = [] + self.state = {'pos': Point(0,0), 'size': Point(1,1), 'angle': 0} ## angle is in degrees for ease of Qt integration + self.lastState = None + self.setPos(pos) + self.setAngle(angle) + self.setSize(size) + self.setZValue(10) + self.isMoving = False + + self.handleSize = 5 + self.invertible = invertible + self.maxBounds = maxBounds + + self.snapSize = snapSize + self.translateSnap = translateSnap + self.rotateSnap = rotateSnap + self.scaleSnap = scaleSnap + #self.setFlag(self.ItemIsSelectable, True) + + def getState(self): + return self.stateCopy() + + def stateCopy(self): + sc = {} + sc['pos'] = Point(self.state['pos']) + sc['size'] = Point(self.state['size']) + sc['angle'] = self.state['angle'] + return sc + + def saveState(self): + """Return the state of the widget in a format suitable for storing to + disk. (Points are converted to tuple) + + Combined with setState(), this allows ROIs to be easily saved and + restored.""" + state = {} + state['pos'] = tuple(self.state['pos']) + state['size'] = tuple(self.state['size']) + state['angle'] = self.state['angle'] + return state + + def setState(self, state, update=True): + """ + Set the state of the ROI from a structure generated by saveState() or + getState(). + """ + self.setPos(state['pos'], update=False) + self.setSize(state['size'], update=False) + self.setAngle(state['angle'], update=update) + + def setZValue(self, z): + QtGui.QGraphicsItem.setZValue(self, z) + for h in self.handles: + h['item'].setZValue(z+1) + + def parentBounds(self): + """ + Return the bounding rectangle of this ROI in the coordinate system + of its parent. + """ + return self.mapToParent(self.boundingRect()).boundingRect() + + def setPen(self, *args, **kwargs): + """ + Set the pen to use when drawing the ROI shape. + For arguments, see :func:`mkPen `. + """ + self.pen = fn.mkPen(*args, **kwargs) + self.currentPen = self.pen + self.update() + + def size(self): + """Return the size (w,h) of the ROI.""" + return self.getState()['size'] + + def pos(self): + """Return the position (x,y) of the ROI's origin. + For most ROIs, this will be the lower-left corner.""" + return self.getState()['pos'] + + def angle(self): + """Return the angle of the ROI in degrees.""" + return self.getState()['angle'] + + def setPos(self, pos, update=True, finish=True): + """Set the position of the ROI (in the parent's coordinate system). + By default, this will cause both sigRegionChanged and sigRegionChangeFinished to be emitted. + + If finish is False, then sigRegionChangeFinished will not be emitted. You can then use + stateChangeFinished() to cause the signal to be emitted after a series of state changes. + + If update is False, the state change will be remembered but not processed and no signals + will be emitted. You can then use stateChanged() to complete the state change. This allows + multiple change functions to be called sequentially while minimizing processing overhead + and repeated signals. Setting update=False also forces finish=False. + """ + + pos = Point(pos) + self.state['pos'] = pos + QtGui.QGraphicsItem.setPos(self, pos) + if update: + self.stateChanged(finish=finish) + + def setSize(self, size, update=True, finish=True): + """Set the size of the ROI. May be specified as a QPoint, Point, or list of two values. + See setPos() for an explanation of the update and finish arguments. + """ + size = Point(size) + self.prepareGeometryChange() + self.state['size'] = size + if update: + self.stateChanged(finish=finish) + + def setAngle(self, angle, update=True, finish=True): + """Set the angle of rotation (in degrees) for this ROI. + See setPos() for an explanation of the update and finish arguments. + """ + self.state['angle'] = angle + tr = QtGui.QTransform() + #tr.rotate(-angle * 180 / np.pi) + tr.rotate(angle) + self.setTransform(tr) + if update: + self.stateChanged(finish=finish) + + def scale(self, s, center=[0,0], update=True, finish=True): + """ + Resize the ROI by scaling relative to *center*. + See setPos() for an explanation of the *update* and *finish* arguments. + """ + c = self.mapToParent(Point(center) * self.state['size']) + self.prepareGeometryChange() + newSize = self.state['size'] * s + c1 = self.mapToParent(Point(center) * newSize) + newPos = self.state['pos'] + c - c1 + + self.setSize(newSize, update=False) + self.setPos(newPos, update=update, finish=finish) + + + def translate(self, *args, **kargs): + """ + Move the ROI to a new position. + Accepts either (x, y, snap) or ([x,y], snap) as arguments + If the ROI is bounded and the move would exceed boundaries, then the ROI + is moved to the nearest acceptable position instead. + + *snap* can be: + + =============== ========================================================================== + None (default) use self.translateSnap and self.snapSize to determine whether/how to snap + False do not snap + Point(w,h) snap to rectangular grid with spacing (w,h) + True snap using self.snapSize (and ignoring self.translateSnap) + =============== ========================================================================== + + Also accepts *update* and *finish* arguments (see setPos() for a description of these). + """ + + if len(args) == 1: + pt = args[0] + else: + pt = args + + newState = self.stateCopy() + newState['pos'] = newState['pos'] + pt + + ## snap position + #snap = kargs.get('snap', None) + #if (snap is not False) and not (snap is None and self.translateSnap is False): + + snap = kargs.get('snap', None) + if snap is None: + snap = self.translateSnap + if snap is not False: + newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap) + + #d = ev.scenePos() - self.mapToScene(self.pressPos) + if self.maxBounds is not None: + r = self.stateRect(newState) + #r0 = self.sceneTransform().mapRect(self.boundingRect()) + d = Point(0,0) + if self.maxBounds.left() > r.left(): + d[0] = self.maxBounds.left() - r.left() + elif self.maxBounds.right() < r.right(): + d[0] = self.maxBounds.right() - r.right() + if self.maxBounds.top() > r.top(): + d[1] = self.maxBounds.top() - r.top() + elif self.maxBounds.bottom() < r.bottom(): + d[1] = self.maxBounds.bottom() - r.bottom() + newState['pos'] += d + + #self.state['pos'] = newState['pos'] + update = kargs.get('update', True) + finish = kargs.get('finish', True) + self.setPos(newState['pos'], update=update, finish=finish) + #if 'update' not in kargs or kargs['update'] is True: + #self.stateChanged() + + def rotate(self, angle, update=True, finish=True): + """ + Rotate the ROI by *angle* degrees. + + Also accepts *update* and *finish* arguments (see setPos() for a + description of these). + """ + self.setAngle(self.angle()+angle, update=update, finish=finish) + + def handleMoveStarted(self): + self.preMoveState = self.getState() + + def addTranslateHandle(self, pos, axes=None, item=None, name=None, index=None): + """ + Add a new translation handle to the ROI. Dragging the handle will move + the entire ROI without changing its angle or shape. + + Note that, by default, ROIs may be moved by dragging anywhere inside the + ROI. However, for larger ROIs it may be desirable to disable this and + instead provide one or more translation handles. + + =================== ==================================================== + **Arguments** + pos (length-2 sequence) The position of the handle + relative to the shape of the ROI. A value of (0,0) + indicates the origin, whereas (1, 1) indicates the + upper-right corner, regardless of the ROI's size. + item The Handle instance to add. If None, a new handle + will be created. + name The name of this handle (optional). Handles are + identified by name when calling + getLocalHandlePositions and getSceneHandlePositions. + =================== ==================================================== + """ + pos = Point(pos) + return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item}, index=index) + + def addFreeHandle(self, pos=None, axes=None, item=None, name=None, index=None): + """ + Add a new free handle to the ROI. Dragging free handles has no effect + on the position or shape of the ROI. + + =================== ==================================================== + **Arguments** + pos (length-2 sequence) The position of the handle + relative to the shape of the ROI. A value of (0,0) + indicates the origin, whereas (1, 1) indicates the + upper-right corner, regardless of the ROI's size. + item The Handle instance to add. If None, a new handle + will be created. + name The name of this handle (optional). Handles are + identified by name when calling + getLocalHandlePositions and getSceneHandlePositions. + =================== ==================================================== + """ + if pos is not None: + pos = Point(pos) + return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item}, index=index) + + def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspect=False, index=None): + """ + Add a new scale handle to the ROI. Dragging a scale handle allows the + user to change the height and/or width of the ROI. + + =================== ==================================================== + **Arguments** + pos (length-2 sequence) The position of the handle + relative to the shape of the ROI. A value of (0,0) + indicates the origin, whereas (1, 1) indicates the + upper-right corner, regardless of the ROI's size. + center (length-2 sequence) The center point around which + scaling takes place. If the center point has the + same x or y value as the handle position, then + scaling will be disabled for that axis. + item The Handle instance to add. If None, a new handle + will be created. + name The name of this handle (optional). Handles are + identified by name when calling + getLocalHandlePositions and getSceneHandlePositions. + =================== ==================================================== + """ + pos = Point(pos) + center = Point(center) + info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item, 'lockAspect': lockAspect} + if pos.x() == center.x(): + info['xoff'] = True + if pos.y() == center.y(): + info['yoff'] = True + return self.addHandle(info, index=index) + + def addRotateHandle(self, pos, center, item=None, name=None, index=None): + """ + Add a new rotation handle to the ROI. Dragging a rotation handle allows + the user to change the angle of the ROI. + + =================== ==================================================== + **Arguments** + pos (length-2 sequence) The position of the handle + relative to the shape of the ROI. A value of (0,0) + indicates the origin, whereas (1, 1) indicates the + upper-right corner, regardless of the ROI's size. + center (length-2 sequence) The center point around which + rotation takes place. + item The Handle instance to add. If None, a new handle + will be created. + name The name of this handle (optional). Handles are + identified by name when calling + getLocalHandlePositions and getSceneHandlePositions. + =================== ==================================================== + """ + pos = Point(pos) + center = Point(center) + return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item}, index=index) + + def addScaleRotateHandle(self, pos, center, item=None, name=None, index=None): + """ + Add a new scale+rotation handle to the ROI. When dragging a handle of + this type, the user can simultaneously rotate the ROI around an + arbitrary center point as well as scale the ROI by dragging the handle + toward or away from the center point. + + =================== ==================================================== + **Arguments** + pos (length-2 sequence) The position of the handle + relative to the shape of the ROI. A value of (0,0) + indicates the origin, whereas (1, 1) indicates the + upper-right corner, regardless of the ROI's size. + center (length-2 sequence) The center point around which + scaling and rotation take place. + item The Handle instance to add. If None, a new handle + will be created. + name The name of this handle (optional). Handles are + identified by name when calling + getLocalHandlePositions and getSceneHandlePositions. + =================== ==================================================== + """ + pos = Point(pos) + center = Point(center) + if pos[0] != center[0] and pos[1] != center[1]: + raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.") + return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item}, index=index) + + def addRotateFreeHandle(self, pos, center, axes=None, item=None, name=None, index=None): + """ + Add a new rotation+free handle to the ROI. When dragging a handle of + this type, the user can rotate the ROI around an + arbitrary center point, while moving toward or away from the center + point has no effect on the shape of the ROI. + + =================== ==================================================== + **Arguments** + pos (length-2 sequence) The position of the handle + relative to the shape of the ROI. A value of (0,0) + indicates the origin, whereas (1, 1) indicates the + upper-right corner, regardless of the ROI's size. + center (length-2 sequence) The center point around which + rotation takes place. + item The Handle instance to add. If None, a new handle + will be created. + name The name of this handle (optional). Handles are + identified by name when calling + getLocalHandlePositions and getSceneHandlePositions. + =================== ==================================================== + """ + pos = Point(pos) + center = Point(center) + return self.addHandle({'name': name, 'type': 'rf', 'center': center, 'pos': pos, 'item': item}, index=index) + + def addHandle(self, info, index=None): + ## If a Handle was not supplied, create it now + if 'item' not in info or info['item'] is None: + h = Handle(self.handleSize, typ=info['type'], pen=self.handlePen, parent=self) + h.setPos(info['pos'] * self.state['size']) + info['item'] = h + else: + h = info['item'] + if info['pos'] is None: + info['pos'] = h.pos() + + ## connect the handle to this ROI + #iid = len(self.handles) + h.connectROI(self) + if index is None: + self.handles.append(info) + else: + self.handles.insert(index, info) + + h.setZValue(self.zValue()+1) + self.stateChanged() + return h + + def indexOfHandle(self, handle): + """ + Return the index of *handle* in the list of this ROI's handles. + """ + if isinstance(handle, Handle): + index = [i for i, info in enumerate(self.handles) if info['item'] is handle] + if len(index) == 0: + raise Exception("Cannot remove handle; it is not attached to this ROI") + return index[0] + else: + return handle + + def removeHandle(self, handle): + """Remove a handle from this ROI. Argument may be either a Handle + instance or the integer index of the handle.""" + index = self.indexOfHandle(handle) + + handle = self.handles[index]['item'] + self.handles.pop(index) + handle.disconnectROI(self) + if len(handle.rois) == 0: + self.scene().removeItem(handle) + self.stateChanged() + + def replaceHandle(self, oldHandle, newHandle): + """Replace one handle in the ROI for another. This is useful when + connecting multiple ROIs together. + + *oldHandle* may be a Handle instance or the index of a handle to be + replaced.""" + index = self.indexOfHandle(oldHandle) + info = self.handles[index] + self.removeHandle(index) + info['item'] = newHandle + info['pos'] = newHandle.pos() + self.addHandle(info, index=index) + + def checkRemoveHandle(self, handle): + ## This is used when displaying a Handle's context menu to determine + ## whether removing is allowed. + ## Subclasses may wish to override this to disable the menu entry. + ## Note: by default, handles are not user-removable even if this method returns True. + return True + + + def getLocalHandlePositions(self, index=None): + """Returns the position of handles in the ROI's coordinate system. + + The format returned is a list of (name, pos) tuples. + """ + if index == None: + positions = [] + for h in self.handles: + positions.append((h['name'], h['pos'])) + return positions + else: + return (self.handles[index]['name'], self.handles[index]['pos']) + + def getSceneHandlePositions(self, index=None): + """Returns the position of handles in the scene coordinate system. + + The format returned is a list of (name, pos) tuples. + """ + if index == None: + positions = [] + for h in self.handles: + positions.append((h['name'], h['item'].scenePos())) + return positions + else: + return (self.handles[index]['name'], self.handles[index]['item'].scenePos()) + + def getHandles(self): + """ + Return a list of this ROI's Handles. + """ + return [h['item'] for h in self.handles] + + def mapSceneToParent(self, pt): + return self.mapToParent(self.mapFromScene(pt)) + + def setSelected(self, s): + QtGui.QGraphicsItem.setSelected(self, s) + #print "select", self, s + if s: + for h in self.handles: + h['item'].show() + else: + for h in self.handles: + h['item'].hide() + + + def hoverEvent(self, ev): + hover = False + if not ev.isExit(): + if self.translatable and ev.acceptDrags(QtCore.Qt.LeftButton): + hover=True + + for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MidButton]: + if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn): + hover=True + if self.contextMenuEnabled(): + ev.acceptClicks(QtCore.Qt.RightButton) + + if hover: + self.setMouseHover(True) + self.sigHoverEvent.emit(self) + ev.acceptClicks(QtCore.Qt.LeftButton) ## If the ROI is hilighted, we should accept all clicks to avoid confusion. + ev.acceptClicks(QtCore.Qt.RightButton) + ev.acceptClicks(QtCore.Qt.MidButton) + else: + self.setMouseHover(False) + + def setMouseHover(self, hover): + ## Inform the ROI that the mouse is(not) hovering over it + if self.mouseHovering == hover: + return + self.mouseHovering = hover + if hover: + self.currentPen = fn.mkPen(255, 255, 0) + else: + self.currentPen = self.pen + self.update() + + def contextMenuEnabled(self): + return self.removable + + def raiseContextMenu(self, ev): + if not self.contextMenuEnabled(): + return + menu = self.getMenu() + menu = self.scene().addParentContextMenus(self, menu, ev) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def getMenu(self): + if self.menu is None: + self.menu = QtGui.QMenu() + self.menu.setTitle("ROI") + remAct = QtGui.QAction("Remove ROI", self.menu) + remAct.triggered.connect(self.removeClicked) + self.menu.addAction(remAct) + self.menu.remAct = remAct + return self.menu + + def removeClicked(self): + ## Send remove event only after we have exited the menu event handler + QtCore.QTimer.singleShot(0, lambda: self.sigRemoveRequested.emit(self)) + + def mouseDragEvent(self, ev): + if ev.isStart(): + #p = ev.pos() + #if not self.isMoving and not self.shape().contains(p): + #ev.ignore() + #return + if ev.button() == QtCore.Qt.LeftButton: + self.setSelected(True) + if self.translatable: + self.isMoving = True + self.preMoveState = self.getState() + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + self.sigRegionChangeStarted.emit(self) + ev.accept() + else: + ev.ignore() + + elif ev.isFinish(): + if self.translatable: + if self.isMoving: + self.stateChangeFinished() + self.isMoving = False + return + + if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton: + snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None + newPos = self.mapToParent(ev.pos()) + self.cursorOffset + self.translate(newPos - self.pos(), snap=snap, finish=False) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton and self.isMoving: + ev.accept() + self.cancelMove() + if ev.button() == QtCore.Qt.RightButton and self.contextMenuEnabled(): + self.raiseContextMenu(ev) + ev.accept() + elif int(ev.button() & self.acceptedMouseButtons()) > 0: + ev.accept() + self.sigClicked.emit(self, ev) + else: + ev.ignore() + + def cancelMove(self): + self.isMoving = False + self.setState(self.preMoveState) + + def checkPointMove(self, handle, pos, modifiers): + """When handles move, they must ask the ROI if the move is acceptable. + By default, this always returns True. Subclasses may wish override. + """ + return True + + def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True, coords='parent'): + ## called by Handles when they are moved. + ## pos is the new position of the handle in scene coords, as requested by the handle. + + newState = self.stateCopy() + index = self.indexOfHandle(handle) + h = self.handles[index] + p0 = self.mapToParent(h['pos'] * self.state['size']) + p1 = Point(pos) + + if coords == 'parent': + pass + elif coords == 'scene': + p1 = self.mapSceneToParent(p1) + else: + raise Exception("New point location must be given in either 'parent' or 'scene' coordinates.") + + + ## transform p0 and p1 into parent's coordinates (same as scene coords if there is no parent). I forget why. + #p0 = self.mapSceneToParent(p0) + #p1 = self.mapSceneToParent(p1) + + ## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1) + if 'center' in h: + c = h['center'] + cs = c * self.state['size'] + lp0 = self.mapFromParent(p0) - cs + lp1 = self.mapFromParent(p1) - cs + + if h['type'] == 't': + snap = True if (modifiers & QtCore.Qt.ControlModifier) else None + #if self.translateSnap or (): + #snap = Point(self.snapSize, self.snapSize) + self.translate(p1-p0, snap=snap, update=False) + + elif h['type'] == 'f': + newPos = self.mapFromParent(p1) + h['item'].setPos(newPos) + h['pos'] = newPos + self.freeHandleMoved = True + #self.sigRegionChanged.emit(self) ## should be taken care of by call to stateChanged() + + elif h['type'] == 's': + ## If a handle and its center have the same x or y value, we can't scale across that axis. + if h['center'][0] == h['pos'][0]: + lp1[0] = 0 + if h['center'][1] == h['pos'][1]: + lp1[1] = 0 + + ## snap + if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): + lp1[0] = round(lp1[0] / self.snapSize) * self.snapSize + lp1[1] = round(lp1[1] / self.snapSize) * self.snapSize + + ## preserve aspect ratio (this can override snapping) + if h['lockAspect'] or (modifiers & QtCore.Qt.AltModifier): + #arv = Point(self.preMoveState['size']) - + lp1 = lp1.proj(lp0) + + ## determine scale factors and new size of ROI + hs = h['pos'] - c + if hs[0] == 0: + hs[0] = 1 + if hs[1] == 0: + hs[1] = 1 + newSize = lp1 / hs + + ## Perform some corrections and limit checks + if newSize[0] == 0: + newSize[0] = newState['size'][0] + if newSize[1] == 0: + newSize[1] = newState['size'][1] + if not self.invertible: + if newSize[0] < 0: + newSize[0] = newState['size'][0] + if newSize[1] < 0: + newSize[1] = newState['size'][1] + if self.aspectLocked: + newSize[0] = newSize[1] + + ## Move ROI so the center point occupies the same scene location after the scale + s0 = c * self.state['size'] + s1 = c * newSize + cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0)) + + ## update state, do more boundary checks + newState['size'] = newSize + newState['pos'] = newState['pos'] + cc + if self.maxBounds is not None: + r = self.stateRect(newState) + if not self.maxBounds.contains(r): + return + + self.setPos(newState['pos'], update=False) + self.setSize(newState['size'], update=False) + + elif h['type'] in ['r', 'rf']: + if h['type'] == 'rf': + self.freeHandleMoved = True + + if not self.rotateAllowed: + return + ## If the handle is directly over its center point, we can't compute an angle. + try: + if lp1.length() == 0 or lp0.length() == 0: + return + except OverflowError: + return + + ## determine new rotation angle, constrained if necessary + ang = newState['angle'] - lp0.angle(lp1) + if ang is None: ## this should never happen.. + return + if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): + ang = round(ang / 15.) * 15. ## 180/12 = 15 + + ## create rotation transform + tr = QtGui.QTransform() + tr.rotate(ang) + + ## move ROI so that center point remains stationary after rotate + cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos']) + newState['angle'] = ang + newState['pos'] = newState['pos'] + cc + + ## check boundaries, update + if self.maxBounds is not None: + r = self.stateRect(newState) + if not self.maxBounds.contains(r): + return + #self.setTransform(tr) + self.setPos(newState['pos'], update=False) + self.setAngle(ang, update=False) + #self.state = newState + + ## If this is a free-rotate handle, its distance from the center may change. + + if h['type'] == 'rf': + h['item'].setPos(self.mapFromScene(p1)) ## changes ROI coordinates of handle + + elif h['type'] == 'sr': + if h['center'][0] == h['pos'][0]: + scaleAxis = 1 + nonScaleAxis=0 + else: + scaleAxis = 0 + nonScaleAxis=1 + + try: + if lp1.length() == 0 or lp0.length() == 0: + return + except OverflowError: + return + + ang = newState['angle'] - lp0.angle(lp1) + if ang is None: + return + if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): + #ang = round(ang / (np.pi/12.)) * (np.pi/12.) + ang = round(ang / 15.) * 15. + + hs = abs(h['pos'][scaleAxis] - c[scaleAxis]) + newState['size'][scaleAxis] = lp1.length() / hs + #if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): + if self.scaleSnap: ## use CTRL only for angular snap here. + newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize + if newState['size'][scaleAxis] == 0: + newState['size'][scaleAxis] = 1 + if self.aspectLocked: + newState['size'][nonScaleAxis] = newState['size'][scaleAxis] + + c1 = c * newState['size'] + tr = QtGui.QTransform() + tr.rotate(ang) + + cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos']) + newState['angle'] = ang + newState['pos'] = newState['pos'] + cc + if self.maxBounds is not None: + r = self.stateRect(newState) + if not self.maxBounds.contains(r): + return + #self.setTransform(tr) + #self.setPos(newState['pos'], update=False) + #self.prepareGeometryChange() + #self.state = newState + self.setState(newState, update=False) + + self.stateChanged(finish=finish) + + def stateChanged(self, finish=True): + """Process changes to the state of the ROI. + If there are any changes, then the positions of handles are updated accordingly + and sigRegionChanged is emitted. If finish is True, then + sigRegionChangeFinished will also be emitted.""" + + changed = False + if self.lastState is None: + changed = True + else: + for k in list(self.state.keys()): + if self.state[k] != self.lastState[k]: + changed = True + + self.prepareGeometryChange() + if changed: + ## Move all handles to match the current configuration of the ROI + for h in self.handles: + if h['item'] in self.childItems(): + p = h['pos'] + h['item'].setPos(h['pos'] * self.state['size']) + #else: + # trans = self.state['pos']-self.lastState['pos'] + # h['item'].setPos(h['pos'] + h['item'].parentItem().mapFromParent(trans)) + + self.update() + self.sigRegionChanged.emit(self) + elif self.freeHandleMoved: + self.sigRegionChanged.emit(self) + + self.freeHandleMoved = False + self.lastState = self.stateCopy() + + if finish: + self.stateChangeFinished() + + def stateChangeFinished(self): + self.sigRegionChangeFinished.emit(self) + + def stateRect(self, state): + r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1]) + tr = QtGui.QTransform() + #tr.rotate(-state['angle'] * 180 / np.pi) + tr.rotate(-state['angle']) + r = tr.mapRect(r) + return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1]) + + + def getSnapPosition(self, pos, snap=None): + ## Given that pos has been requested, return the nearest snap-to position + ## optionally, snap may be passed in to specify a rectangular snap grid. + ## override this function for more interesting snap functionality.. + + if snap is None or snap is True: + if self.snapSize is None: + return pos + snap = Point(self.snapSize, self.snapSize) + + return Point( + round(pos[0] / snap[0]) * snap[0], + round(pos[1] / snap[1]) * snap[1] + ) + + def boundingRect(self): + return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() + + def paint(self, p, opt, widget): + # p.save() + # Note: don't use self.boundingRect here, because subclasses may need to redefine it. + r = QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() + + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + p.translate(r.left(), r.top()) + p.scale(r.width(), r.height()) + p.drawRect(0, 0, 1, 1) + # p.restore() + + def getArraySlice(self, data, img, axes=(0,1), returnSlice=True): + """Return a tuple of slice objects that can be used to slice the region from data covered by this ROI. + Also returns the transform which maps the ROI into data coordinates. + + If returnSlice is set to False, the function returns a pair of tuples with the values that would have + been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop)) + + If the slice can not be computed (usually because the scene/transforms are not properly + constructed yet), then the method returns None. + """ + #print "getArraySlice" + + ## Determine shape of array along ROI axes + dShape = (data.shape[axes[0]], data.shape[axes[1]]) + #print " dshape", dShape + + ## Determine transform that maps ROI bounding box to image coordinates + try: + tr = self.sceneTransform() * fn.invertQTransform(img.sceneTransform()) + except np.linalg.linalg.LinAlgError: + return None + + ## Modify transform to scale from image coords to data coords + #m = QtGui.QTransform() + tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height()) + #tr = tr * m + + ## Transform ROI bounds into data bounds + dataBounds = tr.mapRect(self.boundingRect()) + #print " boundingRect:", self.boundingRect() + #print " dataBounds:", dataBounds + + ## Intersect transformed ROI bounds with data bounds + intBounds = dataBounds.intersect(QtCore.QRectF(0, 0, dShape[0], dShape[1])) + #print " intBounds:", intBounds + + ## Determine index values to use when referencing the array. + bounds = ( + (int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))), + (int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top()))) + ) + #print " bounds:", bounds + + if returnSlice: + ## Create slice objects + sl = [slice(None)] * data.ndim + sl[axes[0]] = slice(*bounds[0]) + sl[axes[1]] = slice(*bounds[1]) + return tuple(sl), tr + else: + return bounds, tr + + def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds): + """Use the position and orientation of this ROI relative to an imageItem + to pull a slice from an array. + + =================== ==================================================== + **Arguments** + data The array to slice from. Note that this array does + *not* have to be the same data that is represented + in *img*. + img (ImageItem or other suitable QGraphicsItem) + Used to determine the relationship between the + ROI and the boundaries of *data*. + axes (length-2 tuple) Specifies the axes in *data* that + correspond to the x and y axes of *img*. + returnMappedCoords (bool) If True, the array slice is returned along + with a corresponding array of coordinates that were + used to extract data from the original array. + \**kwds All keyword arguments are passed to + :func:`affineSlice `. + =================== ==================================================== + + This method uses :func:`affineSlice ` to generate + the slice from *data* and uses :func:`getAffineSliceParams ` to determine the parameters to + pass to :func:`affineSlice `. + + If *returnMappedCoords* is True, then the method returns a tuple (result, coords) + such that coords is the set of coordinates used to interpolate values from the original + data, mapped into the parent coordinate system of the image. This is useful, when slicing + data from images that have been transformed, for determining the location of each value + in the sliced data. + + All extra keyword arguments are passed to :func:`affineSlice `. + """ + + shape, vectors, origin = self.getAffineSliceParams(data, img, axes) + if not returnMappedCoords: + return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) + else: + kwds['returnCoords'] = True + result, coords = fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, **kwds) + #tr = fn.transformToArray(img.transform())[:2] ## remove perspective transform values + + ### separate translation from scale/rotate + #translate = tr[:,2] + #tr = tr[:,:2] + #tr = tr.reshape((2,2) + (1,)*(coords.ndim-1)) + #coords = coords[np.newaxis, ...] + + ### map coordinates and return + #mapped = (tr*coords).sum(axis=0) ## apply scale/rotate + #mapped += translate.reshape((2,1,1)) + mapped = fn.transformCoordinates(img.transform(), coords) + return result, mapped + + def getAffineSliceParams(self, data, img, axes=(0,1)): + """ + Returns the parameters needed to use :func:`affineSlice ` to + extract a subset of *data* using this ROI and *img* to specify the subset. + + See :func:`getArrayRegion ` for more information. + """ + if self.scene() is not img.scene(): + raise Exception("ROI and target item must be members of the same scene.") + + shape = self.state['size'] + + origin = self.mapToItem(img, QtCore.QPointF(0, 0)) + + ## vx and vy point in the directions of the slice axes, but must be scaled properly + vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin + vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin + + lvx = np.sqrt(vx.x()**2 + vx.y()**2) + lvy = np.sqrt(vy.x()**2 + vy.y()**2) + pxLen = img.width() / float(data.shape[axes[0]]) + #img.width is number of pixels or width of item? + #need pxWidth and pxHeight instead of pxLen ? + sx = pxLen / lvx + sy = pxLen / lvy + + vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) + shape = self.state['size'] + shape = [abs(shape[0]/sx), abs(shape[1]/sy)] + + origin = (origin.x(), origin.y()) + return shape, vectors, origin + + def getGlobalTransform(self, relativeTo=None): + """Return global transformation (rotation angle+translation) required to move + from relative state to current state. If relative state isn't specified, + then we use the state of the ROI when mouse is pressed.""" + if relativeTo == None: + relativeTo = self.preMoveState + st = self.getState() + + ## this is only allowed because we will be comparing the two + relativeTo['scale'] = relativeTo['size'] + st['scale'] = st['size'] + + + + t1 = SRTTransform(relativeTo) + t2 = SRTTransform(st) + return t2/t1 + + + #st = self.getState() + + ### rotation + #ang = (st['angle']-relativeTo['angle']) * 180. / 3.14159265358 + #rot = QtGui.QTransform() + #rot.rotate(-ang) + + ### We need to come up with a universal transformation--one that can be applied to other objects + ### such that all maintain alignment. + ### More specifically, we need to turn the ROI's position and angle into + ### a rotation _around the origin_ and a translation. + + #p0 = Point(relativeTo['pos']) + + ### base position, rotated + #p1 = rot.map(p0) + + #trans = Point(st['pos']) - p1 + #return trans, ang + + def applyGlobalTransform(self, tr): + st = self.getState() + + st['scale'] = st['size'] + st = SRTTransform(st) + st = (st * tr).saveState() + st['size'] = st['scale'] + self.setState(st) + + +class Handle(UIGraphicsItem): + """ + Handle represents a single user-interactable point attached to an ROI. They + are usually created by a call to one of the ROI.add___Handle() methods. + + Handles are represented as a square, diamond, or circle, and are drawn with + fixed pixel size regardless of the scaling of the view they are displayed in. + + Handles may be dragged to change the position, size, orientation, or other + properties of the ROI they are attached to. + + + """ + types = { ## defines number of sides, start angle for each handle type + 't': (4, np.pi/4), + 'f': (4, np.pi/4), + 's': (4, 0), + 'r': (12, 0), + 'sr': (12, 0), + 'rf': (12, 0), + } + + sigClicked = QtCore.Signal(object, object) # self, event + sigRemoveRequested = QtCore.Signal(object) # self + + def __init__(self, radius, typ=None, pen=(200, 200, 220), parent=None, deletable=False): + #print " create item with parent", parent + #self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10) + #self.setFlags(self.ItemIgnoresTransformations | self.ItemSendsScenePositionChanges) + self.rois = [] + self.radius = radius + self.typ = typ + self.pen = fn.mkPen(pen) + self.currentPen = self.pen + self.pen.setWidth(0) + self.pen.setCosmetic(True) + self.isMoving = False + self.sides, self.startAng = self.types[typ] + self.buildPath() + self._shape = None + self.menu = self.buildMenu() + + UIGraphicsItem.__init__(self, parent=parent) + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + self.deletable = deletable + if deletable: + self.setAcceptedMouseButtons(QtCore.Qt.RightButton) + #self.updateShape() + self.setZValue(11) + + def connectROI(self, roi): + ### roi is the "parent" roi, i is the index of the handle in roi.handles + self.rois.append(roi) + + def disconnectROI(self, roi): + self.rois.remove(roi) + #for i, r in enumerate(self.roi): + #if r[0] == roi: + #self.roi.pop(i) + + #def close(self): + #for r in self.roi: + #r.removeHandle(self) + + def setDeletable(self, b): + self.deletable = b + if b: + self.setAcceptedMouseButtons(self.acceptedMouseButtons() | QtCore.Qt.RightButton) + else: + self.setAcceptedMouseButtons(self.acceptedMouseButtons() & ~QtCore.Qt.RightButton) + + def removeClicked(self): + self.sigRemoveRequested.emit(self) + + def hoverEvent(self, ev): + hover = False + if not ev.isExit(): + if ev.acceptDrags(QtCore.Qt.LeftButton): + hover=True + for btn in [QtCore.Qt.LeftButton, QtCore.Qt.RightButton, QtCore.Qt.MidButton]: + if int(self.acceptedMouseButtons() & btn) > 0 and ev.acceptClicks(btn): + hover=True + + if hover: + self.currentPen = fn.mkPen(255, 255,0) + else: + self.currentPen = self.pen + self.update() + #if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + #self.currentPen = fn.mkPen(255, 255,0) + #else: + #self.currentPen = self.pen + #self.update() + + + + def mouseClickEvent(self, ev): + ## right-click cancels drag + if ev.button() == QtCore.Qt.RightButton and self.isMoving: + self.isMoving = False ## prevents any further motion + self.movePoint(self.startPos, finish=True) + #for r in self.roi: + #r[0].cancelMove() + ev.accept() + elif int(ev.button() & self.acceptedMouseButtons()) > 0: + ev.accept() + if ev.button() == QtCore.Qt.RightButton and self.deletable: + self.raiseContextMenu(ev) + self.sigClicked.emit(self, ev) + else: + ev.ignore() + + #elif self.deletable: + #ev.accept() + #self.raiseContextMenu(ev) + #else: + #ev.ignore() + + def buildMenu(self): + menu = QtGui.QMenu() + menu.setTitle("Handle") + self.removeAction = menu.addAction("Remove handle", self.removeClicked) + return menu + + def getMenu(self): + return self.menu + + def raiseContextMenu(self, ev): + menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) + + ## Make sure it is still ok to remove this handle + removeAllowed = all([r.checkRemoveHandle(self) for r in self.rois]) + self.removeAction.setEnabled(removeAllowed) + pos = ev.screenPos() + menu.popup(QtCore.QPoint(pos.x(), pos.y())) + + def mouseDragEvent(self, ev): + if ev.button() != QtCore.Qt.LeftButton: + return + ev.accept() + + ## Inform ROIs that a drag is happening + ## note: the ROI is informed that the handle has moved using ROI.movePoint + ## this is for other (more nefarious) purposes. + #for r in self.roi: + #r[0].pointDragEvent(r[1], ev) + + if ev.isFinish(): + if self.isMoving: + for r in self.rois: + r.stateChangeFinished() + self.isMoving = False + elif ev.isStart(): + for r in self.rois: + r.handleMoveStarted() + self.isMoving = True + self.startPos = self.scenePos() + self.cursorOffset = self.scenePos() - ev.buttonDownScenePos() + + if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click. + pos = ev.scenePos() + self.cursorOffset + self.movePoint(pos, ev.modifiers(), finish=False) + + def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True): + for r in self.rois: + if not r.checkPointMove(self, pos, modifiers): + return + #print "point moved; inform %d ROIs" % len(self.roi) + # A handle can be used by multiple ROIs; tell each to update its handle position + for r in self.rois: + r.movePoint(self, pos, modifiers, finish=finish, coords='scene') + + def buildPath(self): + size = self.radius + self.path = QtGui.QPainterPath() + ang = self.startAng + dt = 2*np.pi / self.sides + for i in range(0, self.sides+1): + x = size * cos(ang) + y = size * sin(ang) + ang += dt + if i == 0: + self.path.moveTo(x, y) + else: + self.path.lineTo(x, y) + + def paint(self, p, opt, widget): + ### determine rotation of transform + #m = self.sceneTransform() + ##mi = m.inverted()[0] + #v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0)) + #va = np.arctan2(v.y(), v.x()) + + ### Determine length of unit vector in painter's coords + ##size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0)) + ##size = (size.x()*size.x() + size.y() * size.y()) ** 0.5 + #size = self.radius + + #bounds = QtCore.QRectF(-size, -size, size*2, size*2) + #if bounds != self.bounds: + #self.bounds = bounds + #self.prepareGeometryChange() + p.setRenderHints(p.Antialiasing, True) + p.setPen(self.currentPen) + + #p.rotate(va * 180. / 3.1415926) + #p.drawPath(self.path) + p.drawPath(self.shape()) + #ang = self.startAng + va + #dt = 2*np.pi / self.sides + #for i in range(0, self.sides): + #x1 = size * cos(ang) + #y1 = size * sin(ang) + #x2 = size * cos(ang+dt) + #y2 = size * sin(ang+dt) + #ang += dt + #p.drawLine(Point(x1, y1), Point(x2, y2)) + + def shape(self): + if self._shape is None: + s = self.generateShape() + if s is None: + return self.path + self._shape = s + self.prepareGeometryChange() ## beware--this can cause the view to adjust, which would immediately invalidate the shape. + return self._shape + + def boundingRect(self): + #print 'roi:', self.roi + s1 = self.shape() + #print " s1:", s1 + #s2 = self.shape() + #print " s2:", s2 + + return self.shape().boundingRect() + + def generateShape(self): + ## determine rotation of transform + #m = self.sceneTransform() ## Qt bug: do not access sceneTransform() until we know this object has a scene. + #mi = m.inverted()[0] + dt = self.deviceTransform() + + if dt is None: + self._shape = self.path + return None + + v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) + va = np.arctan2(v.y(), v.x()) + + dti = fn.invertQTransform(dt) + devPos = dt.map(QtCore.QPointF(0,0)) + tr = QtGui.QTransform() + tr.translate(devPos.x(), devPos.y()) + tr.rotate(va * 180. / 3.1415926) + + return dti.map(tr.map(self.path)) + + + def viewTransformChanged(self): + GraphicsObject.viewTransformChanged(self) + self._shape = None ## invalidate shape, recompute later if requested. + self.update() + + #def itemChange(self, change, value): + #if change == self.ItemScenePositionHasChanged: + #self.updateShape() + + +class TestROI(ROI): + def __init__(self, pos, size, **args): + #QtGui.QGraphicsRectItem.__init__(self, pos[0], pos[1], size[0], size[1]) + ROI.__init__(self, pos, size, **args) + #self.addTranslateHandle([0, 0]) + self.addTranslateHandle([0.5, 0.5]) + self.addScaleHandle([1, 1], [0, 0]) + self.addScaleHandle([0, 0], [1, 1]) + self.addScaleRotateHandle([1, 0.5], [0.5, 0.5]) + self.addScaleHandle([0.5, 1], [0.5, 0.5]) + self.addRotateHandle([1, 0], [0, 0]) + self.addRotateHandle([0, 1], [1, 1]) + + + +class RectROI(ROI): + """ + Rectangular ROI subclass with a single scale handle at the top-right corner. + + ============== ============================================================= + **Arguments** + pos (length-2 sequence) The position of the ROI origin. + See ROI(). + size (length-2 sequence) The size of the ROI. See ROI(). + centered (bool) If True, scale handles affect the ROI relative to its + center, rather than its origin. + sideScalers (bool) If True, extra scale handles are added at the top and + right edges. + \**args All extra keyword arguments are passed to ROI() + ============== ============================================================= + + """ + def __init__(self, pos, size, centered=False, sideScalers=False, **args): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, pos, size, **args) + if centered: + center = [0.5, 0.5] + else: + center = [0, 0] + + #self.addTranslateHandle(center) + self.addScaleHandle([1, 1], center) + if sideScalers: + self.addScaleHandle([1, 0.5], [center[0], 0.5]) + self.addScaleHandle([0.5, 1], [0.5, center[1]]) + +class LineROI(ROI): + """ + Rectangular ROI subclass with scale-rotate handles on either side. This + allows the ROI to be positioned as if moving the ends of a line segment. + A third handle controls the width of the ROI orthogonal to its "line" axis. + + ============== ============================================================= + **Arguments** + pos1 (length-2 sequence) The position of the center of the ROI's + left edge. + pos2 (length-2 sequence) The position of the center of the ROI's + right edge. + width (float) The width of the ROI. + \**args All extra keyword arguments are passed to ROI() + ============== ============================================================= + + """ + def __init__(self, pos1, pos2, width, **args): + pos1 = Point(pos1) + pos2 = Point(pos2) + d = pos2-pos1 + l = d.length() + ang = Point(1, 0).angle(d) + ra = ang * np.pi / 180. + c = Point(-width/2. * sin(ra), -width/2. * cos(ra)) + pos1 = pos1 + c + + ROI.__init__(self, pos1, size=Point(l, width), angle=ang, **args) + self.addScaleRotateHandle([0, 0.5], [1, 0.5]) + self.addScaleRotateHandle([1, 0.5], [0, 0.5]) + self.addScaleHandle([0.5, 1], [0.5, 0.5]) + + + +class MultiRectROI(QtGui.QGraphicsObject): + """ + Chain of rectangular ROIs connected by handles. + + This is generally used to mark a curved path through + an image similarly to PolyLineROI. It differs in that each segment + of the chain is rectangular instead of linear and thus has width. + + ============== ============================================================= + **Arguments** + points (list of length-2 sequences) The list of points in the path. + width (float) The width of the ROIs orthogonal to the path. + \**args All extra keyword arguments are passed to ROI() + ============== ============================================================= + """ + sigRegionChangeFinished = QtCore.Signal(object) + sigRegionChangeStarted = QtCore.Signal(object) + sigRegionChanged = QtCore.Signal(object) + + def __init__(self, points, width, pen=None, **args): + QtGui.QGraphicsObject.__init__(self) + self.pen = pen + self.roiArgs = args + self.lines = [] + if len(points) < 2: + raise Exception("Must start with at least 2 points") + + ## create first segment + self.addSegment(points[1], connectTo=points[0], scaleHandle=True) + + ## create remaining segments + for p in points[2:]: + self.addSegment(p) + + + def paint(self, *args): + pass + + def boundingRect(self): + return QtCore.QRectF() + + def roiChangedEvent(self): + w = self.lines[0].state['size'][1] + for l in self.lines[1:]: + w0 = l.state['size'][1] + if w == w0: + continue + l.scale([1.0, w/w0], center=[0.5,0.5]) + self.sigRegionChanged.emit(self) + + def roiChangeStartedEvent(self): + self.sigRegionChangeStarted.emit(self) + + def roiChangeFinishedEvent(self): + self.sigRegionChangeFinished.emit(self) + + def getHandlePositions(self): + """Return the positions of all handles in local coordinates.""" + pos = [self.mapFromScene(self.lines[0].getHandles()[0].scenePos())] + for l in self.lines: + pos.append(self.mapFromScene(l.getHandles()[1].scenePos())) + return pos + + def getArrayRegion(self, arr, img=None, axes=(0,1)): + rgns = [] + for l in self.lines: + rgn = l.getArrayRegion(arr, img, axes=axes) + if rgn is None: + continue + #return None + rgns.append(rgn) + #print l.state['size'] + + ## make sure orthogonal axis is the same size + ## (sometimes fp errors cause differences) + ms = min([r.shape[axes[1]] for r in rgns]) + sl = [slice(None)] * rgns[0].ndim + sl[axes[1]] = slice(0,ms) + rgns = [r[sl] for r in rgns] + #print [r.shape for r in rgns], axes + + return np.concatenate(rgns, axis=axes[0]) + + def addSegment(self, pos=(0,0), scaleHandle=False, connectTo=None): + """ + Add a new segment to the ROI connecting from the previous endpoint to *pos*. + (pos is specified in the parent coordinate system of the MultiRectROI) + """ + + ## by default, connect to the previous endpoint + if connectTo is None: + connectTo = self.lines[-1].getHandles()[1] + + ## create new ROI + newRoi = ROI((0,0), [1, 5], parent=self, pen=self.pen, **self.roiArgs) + self.lines.append(newRoi) + + ## Add first SR handle + if isinstance(connectTo, Handle): + self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=connectTo) + newRoi.movePoint(connectTo, connectTo.scenePos(), coords='scene') + else: + h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5]) + newRoi.movePoint(h, connectTo, coords='scene') + + ## add second SR handle + h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5]) + newRoi.movePoint(h, pos) + + ## optionally add scale handle (this MUST come after the two SR handles) + if scaleHandle: + newRoi.addScaleHandle([0.5, 1], [0.5, 0.5]) + + newRoi.translatable = False + newRoi.sigRegionChanged.connect(self.roiChangedEvent) + newRoi.sigRegionChangeStarted.connect(self.roiChangeStartedEvent) + newRoi.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent) + self.sigRegionChanged.emit(self) + + + def removeSegment(self, index=-1): + """Remove a segment from the ROI.""" + roi = self.lines[index] + self.lines.pop(index) + self.scene().removeItem(roi) + roi.sigRegionChanged.disconnect(self.roiChangedEvent) + roi.sigRegionChangeStarted.disconnect(self.roiChangeStartedEvent) + roi.sigRegionChangeFinished.disconnect(self.roiChangeFinishedEvent) + + self.sigRegionChanged.emit(self) + + +class MultiLineROI(MultiRectROI): + def __init__(self, *args, **kwds): + MultiRectROI.__init__(self, *args, **kwds) + print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)") + +class EllipseROI(ROI): + """ + Elliptical ROI subclass with one scale handle and one rotation handle. + + + ============== ============================================================= + **Arguments** + pos (length-2 sequence) The position of the ROI's origin. + size (length-2 sequence) The size of the ROI's bounding rectangle. + \**args All extra keyword arguments are passed to ROI() + ============== ============================================================= + + """ + def __init__(self, pos, size, **args): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, pos, size, **args) + self.addRotateHandle([1.0, 0.5], [0.5, 0.5]) + self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) + + def paint(self, p, opt, widget): + r = self.boundingRect() + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + + p.scale(r.width(), r.height())## workaround for GL bug + r = QtCore.QRectF(r.x()/r.width(), r.y()/r.height(), 1,1) + + p.drawEllipse(r) + + def getArrayRegion(self, arr, img=None): + """ + Return the result of ROI.getArrayRegion() masked by the elliptical shape + of the ROI. Regions outside the ellipse are set to 0. + """ + arr = ROI.getArrayRegion(self, arr, img) + if arr is None or arr.shape[0] == 0 or arr.shape[1] == 0: + return None + w = arr.shape[0] + h = arr.shape[1] + ## generate an ellipsoidal mask + mask = np.fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h)) + + return arr * mask + + def shape(self): + self.path = QtGui.QPainterPath() + self.path.addEllipse(self.boundingRect()) + return self.path + + +class CircleROI(EllipseROI): + """ + Circular ROI subclass. Behaves exactly as EllipseROI, but may only be scaled + proportionally to maintain its aspect ratio. + + ============== ============================================================= + **Arguments** + pos (length-2 sequence) The position of the ROI's origin. + size (length-2 sequence) The size of the ROI's bounding rectangle. + \**args All extra keyword arguments are passed to ROI() + ============== ============================================================= + + """ + def __init__(self, pos, size, **args): + ROI.__init__(self, pos, size, **args) + self.aspectLocked = True + #self.addTranslateHandle([0.5, 0.5]) + self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) + + +class PolygonROI(ROI): + ## deprecated. Use PloyLineROI instead. + + def __init__(self, positions, pos=None, **args): + if pos is None: + pos = [0,0] + ROI.__init__(self, pos, [1,1], **args) + #ROI.__init__(self, positions[0]) + for p in positions: + self.addFreeHandle(p) + self.setZValue(1000) + print("Warning: PolygonROI is deprecated. Use PolyLineROI instead.") + + + def listPoints(self): + return [p['item'].pos() for p in self.handles] + + #def movePoint(self, *args, **kargs): + #ROI.movePoint(self, *args, **kargs) + #self.prepareGeometryChange() + #for h in self.handles: + #h['pos'] = h['item'].pos() + + def paint(self, p, *args): + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + for i in range(len(self.handles)): + h1 = self.handles[i]['item'].pos() + h2 = self.handles[i-1]['item'].pos() + p.drawLine(h1, h2) + + def boundingRect(self): + r = QtCore.QRectF() + for h in self.handles: + r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs + return r + + def shape(self): + p = QtGui.QPainterPath() + p.moveTo(self.handles[0]['item'].pos()) + for i in range(len(self.handles)): + p.lineTo(self.handles[i]['item'].pos()) + return p + + def stateCopy(self): + sc = {} + sc['pos'] = Point(self.state['pos']) + sc['size'] = Point(self.state['size']) + sc['angle'] = self.state['angle'] + #sc['handles'] = self.handles + return sc + +class PolyLineROI(ROI): + """ + Container class for multiple connected LineSegmentROIs. + + This class allows the user to draw paths of multiple line segments. + + ============== ============================================================= + **Arguments** + positions (list of length-2 sequences) The list of points in the path. + Note that, unlike the handle positions specified in other + ROIs, these positions must be expressed in the normal + coordinate system of the ROI, rather than (0 to 1) relative + to the size of the ROI. + closed (bool) if True, an extra LineSegmentROI is added connecting + the beginning and end points. + \**args All extra keyword arguments are passed to ROI() + ============== ============================================================= + + """ + def __init__(self, positions, closed=False, pos=None, **args): + + if pos is None: + pos = [0,0] + + self.closed = closed + self.segments = [] + ROI.__init__(self, pos, size=[1,1], **args) + + self.setPoints(positions) + #for p in positions: + #self.addFreeHandle(p) + + #start = -1 if self.closed else 0 + #for i in range(start, len(self.handles)-1): + #self.addSegment(self.handles[i]['item'], self.handles[i+1]['item']) + + def setPoints(self, points, closed=None): + """ + Set the complete sequence of points displayed by this ROI. + + ============= ========================================================= + **Arguments** + points List of (x,y) tuples specifying handle locations to set. + closed If bool, then this will set whether the ROI is closed + (the last point is connected to the first point). If + None, then the closed mode is left unchanged. + ============= ========================================================= + + """ + if closed is not None: + self.closed = closed + + for p in points: + self.addFreeHandle(p) + + start = -1 if self.closed else 0 + for i in range(start, len(self.handles)-1): + self.addSegment(self.handles[i]['item'], self.handles[i+1]['item']) + + + def clearPoints(self): + """ + Remove all handles and segments. + """ + while len(self.handles) > 0: + self.removeHandle(self.handles[0]['item']) + + def saveState(self): + state = ROI.saveState(self) + state['closed'] = self.closed + state['points'] = [tuple(h.pos()) for h in self.getHandles()] + return state + + def setState(self, state): + ROI.setState(self, state) + self.clearPoints() + self.setPoints(state['points'], closed=state['closed']) + + def addSegment(self, h1, h2, index=None): + seg = LineSegmentROI(handles=(h1, h2), pen=self.pen, parent=self, movable=False) + if index is None: + self.segments.append(seg) + else: + self.segments.insert(index, seg) + seg.sigClicked.connect(self.segmentClicked) + seg.setAcceptedMouseButtons(QtCore.Qt.LeftButton) + seg.setZValue(self.zValue()+1) + for h in seg.handles: + h['item'].setDeletable(True) + h['item'].setAcceptedMouseButtons(h['item'].acceptedMouseButtons() | QtCore.Qt.LeftButton) ## have these handles take left clicks too, so that handles cannot be added on top of other handles + + def setMouseHover(self, hover): + ## Inform all the ROI's segments that the mouse is(not) hovering over it + ROI.setMouseHover(self, hover) + for s in self.segments: + s.setMouseHover(hover) + + def addHandle(self, info, index=None): + h = ROI.addHandle(self, info, index=index) + h.sigRemoveRequested.connect(self.removeHandle) + return h + + def segmentClicked(self, segment, ev=None, pos=None): ## pos should be in this item's coordinate system + if ev != None: + pos = segment.mapToParent(ev.pos()) + elif pos != None: + pos = pos + else: + raise Exception("Either an event or a position must be given.") + h1 = segment.handles[0]['item'] + h2 = segment.handles[1]['item'] + + i = self.segments.index(segment) + h3 = self.addFreeHandle(pos, index=self.indexOfHandle(h2)) + self.addSegment(h3, h2, index=i+1) + segment.replaceHandle(h2, h3) + + def removeHandle(self, handle, updateSegments=True): + ROI.removeHandle(self, handle) + handle.sigRemoveRequested.disconnect(self.removeHandle) + + if not updateSegments: + return + segments = handle.rois[:] + + if len(segments) == 1: + self.removeSegment(segments[0]) + else: + handles = [h['item'] for h in segments[1].handles] + handles.remove(handle) + segments[0].replaceHandle(handle, handles[0]) + self.removeSegment(segments[1]) + + def removeSegment(self, seg): + for handle in seg.handles[:]: + seg.removeHandle(handle['item']) + self.segments.remove(seg) + seg.sigClicked.disconnect(self.segmentClicked) + self.scene().removeItem(seg) + + def checkRemoveHandle(self, h): + ## called when a handle is about to display its context menu + if self.closed: + return len(self.handles) > 3 + else: + return len(self.handles) > 2 + + def paint(self, p, *args): + #for s in self.segments: + #s.update() + #p.setPen(self.currentPen) + #p.setPen(fn.mkPen('w')) + #p.drawRect(self.boundingRect()) + #p.drawPath(self.shape()) + pass + + def boundingRect(self): + return self.shape().boundingRect() + #r = QtCore.QRectF() + #for h in self.handles: + #r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs + #return r + + def shape(self): + p = QtGui.QPainterPath() + if len(self.handles) == 0: + return p + p.moveTo(self.handles[0]['item'].pos()) + for i in range(len(self.handles)): + p.lineTo(self.handles[i]['item'].pos()) + p.lineTo(self.handles[0]['item'].pos()) + return p + + def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds): + """ + Return the result of ROI.getArrayRegion(), masked by the shape of the + ROI. Values outside the ROI shape are set to 0. + """ + sl = self.getArraySlice(data, img, axes=(0,1)) + if sl is None: + return None + sliced = data[sl[0]] + im = QtGui.QImage(sliced.shape[axes[0]], sliced.shape[axes[1]], QtGui.QImage.Format_ARGB32) + im.fill(0x0) + p = QtGui.QPainter(im) + p.setPen(fn.mkPen(None)) + p.setBrush(fn.mkBrush('w')) + p.setTransform(self.itemTransform(img)[0]) + bounds = self.mapRectToItem(img, self.boundingRect()) + p.translate(-bounds.left(), -bounds.top()) + p.drawPath(self.shape()) + p.end() + mask = fn.imageToArray(im)[:,:,0].astype(float) / 255. + shape = [1] * data.ndim + shape[axes[0]] = sliced.shape[axes[0]] + shape[axes[1]] = sliced.shape[axes[1]] + return sliced * mask.reshape(shape) + + def setPen(self, *args, **kwds): + ROI.setPen(self, *args, **kwds) + for seg in self.segments: + seg.setPen(*args, **kwds) + + + +class LineSegmentROI(ROI): + """ + ROI subclass with two freely-moving handles defining a line. + + ============== ============================================================= + **Arguments** + positions (list of two length-2 sequences) The endpoints of the line + segment. Note that, unlike the handle positions specified in + other ROIs, these positions must be expressed in the normal + coordinate system of the ROI, rather than (0 to 1) relative + to the size of the ROI. + \**args All extra keyword arguments are passed to ROI() + ============== ============================================================= + """ + + def __init__(self, positions=(None, None), pos=None, handles=(None,None), **args): + if pos is None: + pos = [0,0] + + ROI.__init__(self, pos, [1,1], **args) + #ROI.__init__(self, positions[0]) + if len(positions) > 2: + raise Exception("LineSegmentROI must be defined by exactly 2 positions. For more points, use PolyLineROI.") + + for i, p in enumerate(positions): + self.addFreeHandle(p, item=handles[i]) + + + def listPoints(self): + return [p['item'].pos() for p in self.handles] + + def paint(self, p, *args): + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + h1 = self.handles[0]['item'].pos() + h2 = self.handles[1]['item'].pos() + p.drawLine(h1, h2) + + def boundingRect(self): + return self.shape().boundingRect() + + def shape(self): + p = QtGui.QPainterPath() + + h1 = self.handles[0]['item'].pos() + h2 = self.handles[1]['item'].pos() + dh = h2-h1 + if dh.length() == 0: + return p + pxv = self.pixelVectors(dh)[1] + if pxv is None: + return p + + pxv *= 4 + + p.moveTo(h1+pxv) + p.lineTo(h2+pxv) + p.lineTo(h2-pxv) + p.lineTo(h1-pxv) + p.lineTo(h1+pxv) + + return p + + def getArrayRegion(self, data, img, axes=(0,1)): + """ + Use the position of this ROI relative to an imageItem to pull a slice + from an array. + + Since this pulls 1D data from a 2D coordinate system, the return value + will have ndim = data.ndim-1 + + See ROI.getArrayRegion() for a description of the arguments. + """ + + imgPts = [self.mapToItem(img, h['item'].pos()) for h in self.handles] + rgns = [] + for i in range(len(imgPts)-1): + d = Point(imgPts[i+1] - imgPts[i]) + o = Point(imgPts[i]) + r = fn.affineSlice(data, shape=(int(d.length()),), vectors=[Point(d.norm())], origin=o, axes=axes, order=1) + rgns.append(r) + + return np.concatenate(rgns, axis=axes[0]) + + +class SpiralROI(ROI): + def __init__(self, pos=None, size=None, **args): + if size == None: + size = [100e-6,100e-6] + if pos == None: + pos = [0,0] + ROI.__init__(self, pos, size, **args) + self.translateSnap = False + self.addFreeHandle([0.25,0], name='a') + self.addRotateFreeHandle([1,0], [0,0], name='r') + #self.getRadius() + #QtCore.connect(self, QtCore.SIGNAL('regionChanged'), self. + + + def getRadius(self): + radius = Point(self.handles[1]['item'].pos()).length() + #r2 = radius[1] + #r3 = r2[0] + return radius + + def boundingRect(self): + r = self.getRadius() + return QtCore.QRectF(-r*1.1, -r*1.1, 2.2*r, 2.2*r) + #return self.bounds + + #def movePoint(self, *args, **kargs): + #ROI.movePoint(self, *args, **kargs) + #self.prepareGeometryChange() + #for h in self.handles: + #h['pos'] = h['item'].pos()/self.state['size'][0] + + def stateChanged(self, finish=True): + ROI.stateChanged(self, finish=finish) + if len(self.handles) > 1: + self.path = QtGui.QPainterPath() + h0 = Point(self.handles[0]['item'].pos()).length() + a = h0/(2.0*np.pi) + theta = 30.0*(2.0*np.pi)/360.0 + self.path.moveTo(QtCore.QPointF(a*theta*cos(theta), a*theta*sin(theta))) + x0 = a*theta*cos(theta) + y0 = a*theta*sin(theta) + radius = self.getRadius() + theta += 20.0*(2.0*np.pi)/360.0 + i = 0 + while Point(x0, y0).length() < radius and i < 1000: + x1 = a*theta*cos(theta) + y1 = a*theta*sin(theta) + self.path.lineTo(QtCore.QPointF(x1,y1)) + theta += 20.0*(2.0*np.pi)/360.0 + x0 = x1 + y0 = y1 + i += 1 + + + return self.path + + + def shape(self): + p = QtGui.QPainterPath() + p.addEllipse(self.boundingRect()) + return p + + def paint(self, p, *args): + p.setRenderHint(QtGui.QPainter.Antialiasing) + #path = self.shape() + p.setPen(self.currentPen) + p.drawPath(self.path) + p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) + p.drawPath(self.shape()) + p.setPen(QtGui.QPen(QtGui.QColor(0,0,255))) + p.drawRect(self.boundingRect()) + + +class CrosshairROI(ROI): + """A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable.""" + + def __init__(self, pos=None, size=None, **kargs): + if size == None: + #size = [100e-6,100e-6] + size=[1,1] + if pos == None: + pos = [0,0] + self._shape = None + ROI.__init__(self, pos, size, **kargs) + + self.sigRegionChanged.connect(self.invalidate) + self.addScaleRotateHandle(Point(1, 0), Point(0, 0)) + self.aspectLocked = True + + def invalidate(self): + self._shape = None + self.prepareGeometryChange() + + def boundingRect(self): + #size = self.size() + #return QtCore.QRectF(-size[0]/2., -size[1]/2., size[0], size[1]).normalized() + return self.shape().boundingRect() + + #def getRect(self): + ### same as boundingRect -- for internal use so that boundingRect can be re-implemented in subclasses + #size = self.size() + #return QtCore.QRectF(-size[0]/2., -size[1]/2., size[0], size[1]).normalized() + + + def shape(self): + if self._shape is None: + radius = self.getState()['size'][1] + p = QtGui.QPainterPath() + p.moveTo(Point(0, -radius)) + p.lineTo(Point(0, radius)) + p.moveTo(Point(-radius, 0)) + p.lineTo(Point(radius, 0)) + p = self.mapToDevice(p) + stroker = QtGui.QPainterPathStroker() + stroker.setWidth(10) + outline = stroker.createStroke(p) + self._shape = self.mapFromDevice(outline) + + + ##h1 = self.handles[0]['item'].pos() + ##h2 = self.handles[1]['item'].pos() + #w1 = Point(-0.5, 0)*self.size() + #w2 = Point(0.5, 0)*self.size() + #h1 = Point(0, -0.5)*self.size() + #h2 = Point(0, 0.5)*self.size() + + #dh = h2-h1 + #dw = w2-w1 + #if dh.length() == 0 or dw.length() == 0: + #return p + #pxv = self.pixelVectors(dh)[1] + #if pxv is None: + #return p + + #pxv *= 4 + + #p.moveTo(h1+pxv) + #p.lineTo(h2+pxv) + #p.lineTo(h2-pxv) + #p.lineTo(h1-pxv) + #p.lineTo(h1+pxv) + + #pxv = self.pixelVectors(dw)[1] + #if pxv is None: + #return p + + #pxv *= 4 + + #p.moveTo(w1+pxv) + #p.lineTo(w2+pxv) + #p.lineTo(w2-pxv) + #p.lineTo(w1-pxv) + #p.lineTo(w1+pxv) + + return self._shape + + def paint(self, p, *args): + #p.save() + #r = self.getRect() + radius = self.getState()['size'][1] + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + #p.translate(r.left(), r.top()) + #p.scale(r.width()/10., r.height()/10.) ## need to scale up a little because drawLine has trouble dealing with 0.5 + #p.drawLine(0,5, 10,5) + #p.drawLine(5,0, 5,10) + #p.restore() + + p.drawLine(Point(0, -radius), Point(0, radius)) + p.drawLine(Point(-radius, 0), Point(radius, 0)) + + diff --git a/papi/pyqtgraph/graphicsItems/ScaleBar.py b/papi/pyqtgraph/graphicsItems/ScaleBar.py new file mode 100644 index 00000000..8ba546f7 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ScaleBar.py @@ -0,0 +1,71 @@ +from ..Qt import QtGui, QtCore +from .GraphicsObject import * +from .GraphicsWidgetAnchor import * +from .TextItem import TextItem +import numpy as np +from .. import functions as fn +from .. import getConfigOption +from ..Point import Point + +__all__ = ['ScaleBar'] + +class ScaleBar(GraphicsObject, GraphicsWidgetAnchor): + """ + Displays a rectangular bar to indicate the relative scale of objects on the view. + """ + def __init__(self, size, width=5, brush=None, pen=None, suffix='m', offset=None): + GraphicsObject.__init__(self) + GraphicsWidgetAnchor.__init__(self) + self.setFlag(self.ItemHasNoContents) + self.setAcceptedMouseButtons(QtCore.Qt.NoButton) + + if brush is None: + brush = getConfigOption('foreground') + self.brush = fn.mkBrush(brush) + self.pen = fn.mkPen(pen) + self._width = width + self.size = size + if offset == None: + offset = (0,0) + self.offset = offset + + self.bar = QtGui.QGraphicsRectItem() + self.bar.setPen(self.pen) + self.bar.setBrush(self.brush) + self.bar.setParentItem(self) + + self.text = TextItem(text=fn.siFormat(size, suffix=suffix), anchor=(0.5,1)) + self.text.setParentItem(self) + + def parentChanged(self): + view = self.parentItem() + if view is None: + return + view.sigRangeChanged.connect(self.updateBar) + self.updateBar() + + + def updateBar(self): + view = self.parentItem() + if view is None: + return + p1 = view.mapFromViewToItem(self, QtCore.QPointF(0,0)) + p2 = view.mapFromViewToItem(self, QtCore.QPointF(self.size,0)) + w = (p2-p1).x() + self.bar.setRect(QtCore.QRectF(-w, 0, w, self._width)) + self.text.setPos(-w/2., 0) + + def boundingRect(self): + return QtCore.QRectF() + + def setParentItem(self, p): + ret = GraphicsObject.setParentItem(self, p) + if self.offset is not None: + offset = Point(self.offset) + anchorx = 1 if offset[0] <= 0 else 0 + anchory = 1 if offset[1] <= 0 else 0 + anchor = (anchorx, anchory) + self.anchor(itemPos=anchor, parentPos=anchor, offset=offset) + return ret + + diff --git a/papi/pyqtgraph/graphicsItems/ScatterPlotItem.py b/papi/pyqtgraph/graphicsItems/ScatterPlotItem.py new file mode 100644 index 00000000..faae8632 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ScatterPlotItem.py @@ -0,0 +1,971 @@ +from ..Qt import QtGui, QtCore, USE_PYSIDE +from ..Point import Point +from .. import functions as fn +from .GraphicsItem import GraphicsItem +from .GraphicsObject import GraphicsObject +from itertools import starmap, repeat +try: + from itertools import imap +except ImportError: + imap = map +import numpy as np +import weakref +from .. import getConfigOption +from .. import debug as debug +from ..pgcollections import OrderedDict +from .. import debug + +__all__ = ['ScatterPlotItem', 'SpotItem'] + + +## Build all symbol paths +Symbols = OrderedDict([(name, QtGui.QPainterPath()) for name in ['o', 's', 't', 'd', '+', 'x']]) +Symbols['o'].addEllipse(QtCore.QRectF(-0.5, -0.5, 1, 1)) +Symbols['s'].addRect(QtCore.QRectF(-0.5, -0.5, 1, 1)) +coords = { + 't': [(-0.5, -0.5), (0, 0.5), (0.5, -0.5)], + 'd': [(0., -0.5), (-0.4, 0.), (0, 0.5), (0.4, 0)], + '+': [ + (-0.5, -0.05), (-0.5, 0.05), (-0.05, 0.05), (-0.05, 0.5), + (0.05, 0.5), (0.05, 0.05), (0.5, 0.05), (0.5, -0.05), + (0.05, -0.05), (0.05, -0.5), (-0.05, -0.5), (-0.05, -0.05) + ], +} +for k, c in coords.items(): + Symbols[k].moveTo(*c[0]) + for x,y in c[1:]: + Symbols[k].lineTo(x, y) + Symbols[k].closeSubpath() +tr = QtGui.QTransform() +tr.rotate(45) +Symbols['x'] = tr.map(Symbols['+']) + + +def drawSymbol(painter, symbol, size, pen, brush): + if symbol is None: + return + painter.scale(size, size) + painter.setPen(pen) + painter.setBrush(brush) + if isinstance(symbol, basestring): + symbol = Symbols[symbol] + if np.isscalar(symbol): + symbol = list(Symbols.values())[symbol % len(Symbols)] + painter.drawPath(symbol) + + +def renderSymbol(symbol, size, pen, brush, device=None): + """ + Render a symbol specification to QImage. + Symbol may be either a QPainterPath or one of the keys in the Symbols dict. + If *device* is None, a new QPixmap will be returned. Otherwise, + the symbol will be rendered into the device specified (See QPainter documentation + for more information). + """ + ## Render a spot with the given parameters to a pixmap + penPxWidth = max(np.ceil(pen.widthF()), 1) + if device is None: + device = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32) + device.fill(0) + p = QtGui.QPainter(device) + try: + p.setRenderHint(p.Antialiasing) + p.translate(device.width()*0.5, device.height()*0.5) + drawSymbol(p, symbol, size, pen, brush) + finally: + p.end() + return device + +def makeSymbolPixmap(size, pen, brush, symbol): + ## deprecated + img = renderSymbol(symbol, size, pen, brush) + return QtGui.QPixmap(img) + +class SymbolAtlas(object): + """ + Used to efficiently construct a single QPixmap containing all rendered symbols + for a ScatterPlotItem. This is required for fragment rendering. + + Use example: + atlas = SymbolAtlas() + sc1 = atlas.getSymbolCoords('o', 5, QPen(..), QBrush(..)) + sc2 = atlas.getSymbolCoords('t', 10, QPen(..), QBrush(..)) + pm = atlas.getAtlas() + + """ + def __init__(self): + # symbol key : QRect(...) coordinates where symbol can be found in atlas. + # note that the coordinate list will always be the same list object as + # long as the symbol is in the atlas, but the coordinates may + # change if the atlas is rebuilt. + # weak value; if all external refs to this list disappear, + # the symbol will be forgotten. + self.symbolMap = weakref.WeakValueDictionary() + + self.atlasData = None # numpy array of atlas image + self.atlas = None # atlas as QPixmap + self.atlasValid = False + self.max_width=0 + + def getSymbolCoords(self, opts): + """ + Given a list of spot records, return an object representing the coordinates of that symbol within the atlas + """ + sourceRect = np.empty(len(opts), dtype=object) + keyi = None + sourceRecti = None + for i, rec in enumerate(opts): + key = (rec[3], rec[2], id(rec[4]), id(rec[5])) # TODO: use string indexes? + if key == keyi: + sourceRect[i] = sourceRecti + else: + try: + sourceRect[i] = self.symbolMap[key] + except KeyError: + newRectSrc = QtCore.QRectF() + newRectSrc.pen = rec['pen'] + newRectSrc.brush = rec['brush'] + self.symbolMap[key] = newRectSrc + self.atlasValid = False + sourceRect[i] = newRectSrc + keyi = key + sourceRecti = newRectSrc + return sourceRect + + def buildAtlas(self): + # get rendered array for all symbols, keep track of avg/max width + rendered = {} + avgWidth = 0.0 + maxWidth = 0 + images = [] + for key, sourceRect in self.symbolMap.items(): + if sourceRect.width() == 0: + img = renderSymbol(key[0], key[1], sourceRect.pen, sourceRect.brush) + images.append(img) ## we only need this to prevent the images being garbage collected immediately + arr = fn.imageToArray(img, copy=False, transpose=False) + else: + (y,x,h,w) = sourceRect.getRect() + arr = self.atlasData[x:x+w, y:y+w] + rendered[key] = arr + w = arr.shape[0] + avgWidth += w + maxWidth = max(maxWidth, w) + + nSymbols = len(rendered) + if nSymbols > 0: + avgWidth /= nSymbols + width = max(maxWidth, avgWidth * (nSymbols**0.5)) + else: + avgWidth = 0 + width = 0 + + # sort symbols by height + symbols = sorted(rendered.keys(), key=lambda x: rendered[x].shape[1], reverse=True) + + self.atlasRows = [] + + x = width + y = 0 + rowheight = 0 + for key in symbols: + arr = rendered[key] + w,h = arr.shape[:2] + if x+w > width: + y += rowheight + x = 0 + rowheight = h + self.atlasRows.append([y, rowheight, 0]) + self.symbolMap[key].setRect(y, x, h, w) + x += w + self.atlasRows[-1][2] = x + height = y + rowheight + + self.atlasData = np.zeros((width, height, 4), dtype=np.ubyte) + for key in symbols: + y, x, h, w = self.symbolMap[key].getRect() + self.atlasData[x:x+w, y:y+h] = rendered[key] + self.atlas = None + self.atlasValid = True + self.max_width = maxWidth + + def getAtlas(self): + if not self.atlasValid: + self.buildAtlas() + if self.atlas is None: + if len(self.atlasData) == 0: + return QtGui.QPixmap(0,0) + img = fn.makeQImage(self.atlasData, copy=False, transpose=False) + self.atlas = QtGui.QPixmap(img) + return self.atlas + + + + +class ScatterPlotItem(GraphicsObject): + """ + Displays a set of x/y points. Instances of this class are created + automatically as part of PlotDataItem; these rarely need to be instantiated + directly. + + The size, shape, pen, and fill brush may be set for each point individually + or for all points. + + + ======================== =============================================== + **Signals:** + sigPlotChanged(self) Emitted when the data being plotted has changed + sigClicked(self, points) Emitted when the curve is clicked. Sends a list + of all the points under the mouse pointer. + ======================== =============================================== + + """ + #sigPointClicked = QtCore.Signal(object, object) + sigClicked = QtCore.Signal(object, object) ## self, points + sigPlotChanged = QtCore.Signal(object) + def __init__(self, *args, **kargs): + """ + Accepts the same arguments as setData() + """ + profiler = debug.Profiler() + GraphicsObject.__init__(self) + + self.picture = None # QPicture used for rendering when pxmode==False + self.fragmentAtlas = SymbolAtlas() + + self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('data', object), ('item', object), ('sourceRect', object), ('targetRect', object), ('width', float)]) + self.bounds = [None, None] ## caches data bounds + self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots + self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots + self.opts = { + 'pxMode': True, + 'useCache': True, ## If useCache is False, symbols are re-drawn on every paint. + 'antialias': getConfigOption('antialias'), + 'name': None, + } + + self.setPen(fn.mkPen(getConfigOption('foreground')), update=False) + self.setBrush(fn.mkBrush(100,100,150), update=False) + self.setSymbol('o', update=False) + self.setSize(7, update=False) + profiler() + self.setData(*args, **kargs) + profiler('setData') + + #self.setCacheMode(self.DeviceCoordinateCache) + + def setData(self, *args, **kargs): + """ + **Ordered Arguments:** + + * If there is only one unnamed argument, it will be interpreted like the 'spots' argument. + * If there are two unnamed arguments, they will be interpreted as sequences of x and y values. + + ====================== =============================================================================================== + **Keyword Arguments:** + *spots* Optional list of dicts. Each dict specifies parameters for a single spot: + {'pos': (x,y), 'size', 'pen', 'brush', 'symbol'}. This is just an alternate method + of passing in data for the corresponding arguments. + *x*,*y* 1D arrays of x,y values. + *pos* 2D structure of x,y pairs (such as Nx2 array or list of tuples) + *pxMode* If True, spots are always the same size regardless of scaling, and size is given in px. + Otherwise, size is in scene coordinates and the spots scale with the view. + Default is True + *symbol* can be one (or a list) of: + * 'o' circle (default) + * 's' square + * 't' triangle + * 'd' diamond + * '+' plus + * any QPainterPath to specify custom symbol shapes. To properly obey the position and size, + custom symbols should be centered at (0,0) and width and height of 1.0. Note that it is also + possible to 'install' custom shapes by setting ScatterPlotItem.Symbols[key] = shape. + *pen* The pen (or list of pens) to use for drawing spot outlines. + *brush* The brush (or list of brushes) to use for filling spots. + *size* The size (or list of sizes) of spots. If *pxMode* is True, this value is in pixels. Otherwise, + it is in the item's local coordinate system. + *data* a list of python objects used to uniquely identify each spot. + *identical* *Deprecated*. This functionality is handled automatically now. + *antialias* Whether to draw symbols with antialiasing. Note that if pxMode is True, symbols are + always rendered with antialiasing (since the rendered symbols can be cached, this + incurs very little performance cost) + *name* The name of this item. Names are used for automatically + generating LegendItem entries and by some exporters. + ====================== =============================================================================================== + """ + oldData = self.data ## this causes cached pixmaps to be preserved while new data is registered. + self.clear() ## clear out all old data + self.addPoints(*args, **kargs) + + def addPoints(self, *args, **kargs): + """ + Add new points to the scatter plot. + Arguments are the same as setData() + """ + + ## deal with non-keyword arguments + if len(args) == 1: + kargs['spots'] = args[0] + elif len(args) == 2: + kargs['x'] = args[0] + kargs['y'] = args[1] + elif len(args) > 2: + raise Exception('Only accepts up to two non-keyword arguments.') + + ## convert 'pos' argument to 'x' and 'y' + if 'pos' in kargs: + pos = kargs['pos'] + if isinstance(pos, np.ndarray): + kargs['x'] = pos[:,0] + kargs['y'] = pos[:,1] + else: + x = [] + y = [] + for p in pos: + if isinstance(p, QtCore.QPointF): + x.append(p.x()) + y.append(p.y()) + else: + x.append(p[0]) + y.append(p[1]) + kargs['x'] = x + kargs['y'] = y + + ## determine how many spots we have + if 'spots' in kargs: + numPts = len(kargs['spots']) + elif 'y' in kargs and kargs['y'] is not None: + numPts = len(kargs['y']) + else: + kargs['x'] = [] + kargs['y'] = [] + numPts = 0 + + ## Extend record array + oldData = self.data + self.data = np.empty(len(oldData)+numPts, dtype=self.data.dtype) + ## note that np.empty initializes object fields to None and string fields to '' + + self.data[:len(oldData)] = oldData + #for i in range(len(oldData)): + #oldData[i]['item']._data = self.data[i] ## Make sure items have proper reference to new array + + newData = self.data[len(oldData):] + newData['size'] = -1 ## indicates to use default size + + if 'spots' in kargs: + spots = kargs['spots'] + for i in range(len(spots)): + spot = spots[i] + for k in spot: + if k == 'pos': + pos = spot[k] + if isinstance(pos, QtCore.QPointF): + x,y = pos.x(), pos.y() + else: + x,y = pos[0], pos[1] + newData[i]['x'] = x + newData[i]['y'] = y + elif k == 'pen': + newData[i][k] = fn.mkPen(spot[k]) + elif k == 'brush': + newData[i][k] = fn.mkBrush(spot[k]) + elif k in ['x', 'y', 'size', 'symbol', 'brush', 'data']: + newData[i][k] = spot[k] + else: + raise Exception("Unknown spot parameter: %s" % k) + elif 'y' in kargs: + newData['x'] = kargs['x'] + newData['y'] = kargs['y'] + + if 'pxMode' in kargs: + self.setPxMode(kargs['pxMode']) + if 'antialias' in kargs: + self.opts['antialias'] = kargs['antialias'] + + ## Set any extra parameters provided in keyword arguments + for k in ['pen', 'brush', 'symbol', 'size']: + if k in kargs: + setMethod = getattr(self, 'set' + k[0].upper() + k[1:]) + setMethod(kargs[k], update=False, dataSet=newData, mask=kargs.get('mask', None)) + + if 'data' in kargs: + self.setPointData(kargs['data'], dataSet=newData) + + self.prepareGeometryChange() + self.informViewBoundsChanged() + self.bounds = [None, None] + self.invalidate() + self.updateSpots(newData) + self.sigPlotChanged.emit(self) + + def invalidate(self): + ## clear any cached drawing state + self.picture = None + self.update() + + def getData(self): + return self.data['x'], self.data['y'] + + def setPoints(self, *args, **kargs): + ##Deprecated; use setData + return self.setData(*args, **kargs) + + def implements(self, interface=None): + ints = ['plotData'] + if interface is None: + return ints + return interface in ints + + def name(self): + return self.opts.get('name', None) + + def setPen(self, *args, **kargs): + """Set the pen(s) used to draw the outline around each spot. + If a list or array is provided, then the pen for each spot will be set separately. + Otherwise, the arguments are passed to pg.mkPen and used as the default pen for + all spots which do not have a pen explicitly set.""" + update = kargs.pop('update', True) + dataSet = kargs.pop('dataSet', self.data) + + if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)): + pens = args[0] + if 'mask' in kargs and kargs['mask'] is not None: + pens = pens[kargs['mask']] + if len(pens) != len(dataSet): + raise Exception("Number of pens does not match number of points (%d != %d)" % (len(pens), len(dataSet))) + dataSet['pen'] = pens + else: + self.opts['pen'] = fn.mkPen(*args, **kargs) + + dataSet['sourceRect'] = None + if update: + self.updateSpots(dataSet) + + def setBrush(self, *args, **kargs): + """Set the brush(es) used to fill the interior of each spot. + If a list or array is provided, then the brush for each spot will be set separately. + Otherwise, the arguments are passed to pg.mkBrush and used as the default brush for + all spots which do not have a brush explicitly set.""" + update = kargs.pop('update', True) + dataSet = kargs.pop('dataSet', self.data) + + if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)): + brushes = args[0] + if 'mask' in kargs and kargs['mask'] is not None: + brushes = brushes[kargs['mask']] + if len(brushes) != len(dataSet): + raise Exception("Number of brushes does not match number of points (%d != %d)" % (len(brushes), len(dataSet))) + #for i in xrange(len(brushes)): + #self.data[i]['brush'] = fn.mkBrush(brushes[i], **kargs) + dataSet['brush'] = brushes + else: + self.opts['brush'] = fn.mkBrush(*args, **kargs) + #self._spotPixmap = None + + dataSet['sourceRect'] = None + if update: + self.updateSpots(dataSet) + + def setSymbol(self, symbol, update=True, dataSet=None, mask=None): + """Set the symbol(s) used to draw each spot. + If a list or array is provided, then the symbol for each spot will be set separately. + Otherwise, the argument will be used as the default symbol for + all spots which do not have a symbol explicitly set.""" + if dataSet is None: + dataSet = self.data + + if isinstance(symbol, np.ndarray) or isinstance(symbol, list): + symbols = symbol + if mask is not None: + symbols = symbols[mask] + if len(symbols) != len(dataSet): + raise Exception("Number of symbols does not match number of points (%d != %d)" % (len(symbols), len(dataSet))) + dataSet['symbol'] = symbols + else: + self.opts['symbol'] = symbol + self._spotPixmap = None + + dataSet['sourceRect'] = None + if update: + self.updateSpots(dataSet) + + def setSize(self, size, update=True, dataSet=None, mask=None): + """Set the size(s) used to draw each spot. + If a list or array is provided, then the size for each spot will be set separately. + Otherwise, the argument will be used as the default size for + all spots which do not have a size explicitly set.""" + if dataSet is None: + dataSet = self.data + + if isinstance(size, np.ndarray) or isinstance(size, list): + sizes = size + if mask is not None: + sizes = sizes[mask] + if len(sizes) != len(dataSet): + raise Exception("Number of sizes does not match number of points (%d != %d)" % (len(sizes), len(dataSet))) + dataSet['size'] = sizes + else: + self.opts['size'] = size + self._spotPixmap = None + + dataSet['sourceRect'] = None + if update: + self.updateSpots(dataSet) + + def setPointData(self, data, dataSet=None, mask=None): + if dataSet is None: + dataSet = self.data + + if isinstance(data, np.ndarray) or isinstance(data, list): + if mask is not None: + data = data[mask] + if len(data) != len(dataSet): + raise Exception("Length of meta data does not match number of points (%d != %d)" % (len(data), len(dataSet))) + + ## Bug: If data is a numpy record array, then items from that array must be copied to dataSet one at a time. + ## (otherwise they are converted to tuples and thus lose their field names. + if isinstance(data, np.ndarray) and (data.dtype.fields is not None)and len(data.dtype.fields) > 1: + for i, rec in enumerate(data): + dataSet['data'][i] = rec + else: + dataSet['data'] = data + + def setPxMode(self, mode): + if self.opts['pxMode'] == mode: + return + + self.opts['pxMode'] = mode + self.invalidate() + + def updateSpots(self, dataSet=None): + if dataSet is None: + dataSet = self.data + + invalidate = False + if self.opts['pxMode']: + mask = np.equal(dataSet['sourceRect'], None) + if np.any(mask): + invalidate = True + opts = self.getSpotOpts(dataSet[mask]) + sourceRect = self.fragmentAtlas.getSymbolCoords(opts) + dataSet['sourceRect'][mask] = sourceRect + + self.fragmentAtlas.getAtlas() # generate atlas so source widths are available. + + dataSet['width'] = np.array(list(imap(QtCore.QRectF.width, dataSet['sourceRect'])))/2 + dataSet['targetRect'] = None + self._maxSpotPxWidth = self.fragmentAtlas.max_width + else: + self._maxSpotWidth = 0 + self._maxSpotPxWidth = 0 + self.measureSpotSizes(dataSet) + + if invalidate: + self.invalidate() + + def getSpotOpts(self, recs, scale=1.0): + if recs.ndim == 0: + rec = recs + symbol = rec['symbol'] + if symbol is None: + symbol = self.opts['symbol'] + size = rec['size'] + if size < 0: + size = self.opts['size'] + pen = rec['pen'] + if pen is None: + pen = self.opts['pen'] + brush = rec['brush'] + if brush is None: + brush = self.opts['brush'] + return (symbol, size*scale, fn.mkPen(pen), fn.mkBrush(brush)) + else: + recs = recs.copy() + recs['symbol'][np.equal(recs['symbol'], None)] = self.opts['symbol'] + recs['size'][np.equal(recs['size'], -1)] = self.opts['size'] + recs['size'] *= scale + recs['pen'][np.equal(recs['pen'], None)] = fn.mkPen(self.opts['pen']) + recs['brush'][np.equal(recs['brush'], None)] = fn.mkBrush(self.opts['brush']) + return recs + + + + def measureSpotSizes(self, dataSet): + for rec in dataSet: + ## keep track of the maximum spot size and pixel size + symbol, size, pen, brush = self.getSpotOpts(rec) + width = 0 + pxWidth = 0 + if self.opts['pxMode']: + pxWidth = size + pen.widthF() + else: + width = size + if pen.isCosmetic(): + pxWidth += pen.widthF() + else: + width += pen.widthF() + self._maxSpotWidth = max(self._maxSpotWidth, width) + self._maxSpotPxWidth = max(self._maxSpotPxWidth, pxWidth) + self.bounds = [None, None] + + + def clear(self): + """Remove all spots from the scatter plot""" + #self.clearItems() + self.data = np.empty(0, dtype=self.data.dtype) + self.bounds = [None, None] + self.invalidate() + + def dataBounds(self, ax, frac=1.0, orthoRange=None): + if frac >= 1.0 and orthoRange is None and self.bounds[ax] is not None: + return self.bounds[ax] + + #self.prepareGeometryChange() + if self.data is None or len(self.data) == 0: + return (None, None) + + if ax == 0: + d = self.data['x'] + d2 = self.data['y'] + elif ax == 1: + d = self.data['y'] + d2 = self.data['x'] + + if orthoRange is not None: + mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) + d = d[mask] + d2 = d2[mask] + + if frac >= 1.0: + self.bounds[ax] = (np.nanmin(d) - self._maxSpotWidth*0.7072, np.nanmax(d) + self._maxSpotWidth*0.7072) + return self.bounds[ax] + elif frac <= 0.0: + raise Exception("Value for parameter 'frac' must be > 0. (got %s)" % str(frac)) + else: + mask = np.isfinite(d) + d = d[mask] + return np.percentile(d, [50 * (1 - frac), 50 * (1 + frac)]) + + def pixelPadding(self): + return self._maxSpotPxWidth*0.7072 + + def boundingRect(self): + (xmn, xmx) = self.dataBounds(ax=0) + (ymn, ymx) = self.dataBounds(ax=1) + if xmn is None or xmx is None: + xmn = 0 + xmx = 0 + if ymn is None or ymx is None: + ymn = 0 + ymx = 0 + + px = py = 0.0 + pxPad = self.pixelPadding() + if pxPad > 0: + # determine length of pixel in local x, y directions + px, py = self.pixelVectors() + try: + px = 0 if px is None else px.length() + except OverflowError: + px = 0 + try: + py = 0 if py is None else py.length() + except OverflowError: + py = 0 + + # return bounds expanded by pixel size + px *= pxPad + py *= pxPad + return QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn) + + def viewTransformChanged(self): + self.prepareGeometryChange() + GraphicsObject.viewTransformChanged(self) + self.bounds = [None, None] + self.data['targetRect'] = None + + def setExportMode(self, *args, **kwds): + GraphicsObject.setExportMode(self, *args, **kwds) + self.invalidate() + + + def mapPointsToDevice(self, pts): + # Map point locations to device + tr = self.deviceTransform() + if tr is None: + return None + + #pts = np.empty((2,len(self.data['x']))) + #pts[0] = self.data['x'] + #pts[1] = self.data['y'] + pts = fn.transformCoordinates(tr, pts) + pts -= self.data['width'] + pts = np.clip(pts, -2**30, 2**30) ## prevent Qt segmentation fault. + + return pts + + def getViewMask(self, pts): + # Return bool mask indicating all points that are within viewbox + # pts is expressed in *device coordiantes* + vb = self.getViewBox() + if vb is None: + return None + viewBounds = vb.mapRectToDevice(vb.boundingRect()) + w = self.data['width'] + mask = ((pts[0] + w > viewBounds.left()) & + (pts[0] - w < viewBounds.right()) & + (pts[1] + w > viewBounds.top()) & + (pts[1] - w < viewBounds.bottom())) ## remove out of view points + return mask + + + @debug.warnOnException ## raising an exception here causes crash + def paint(self, p, *args): + + #p.setPen(fn.mkPen('r')) + #p.drawRect(self.boundingRect()) + + if self._exportOpts is not False: + aa = self._exportOpts.get('antialias', True) + scale = self._exportOpts.get('resolutionScale', 1.0) ## exporting to image; pixel resolution may have changed + else: + aa = self.opts['antialias'] + scale = 1.0 + + if self.opts['pxMode'] is True: + p.resetTransform() + + # Map point coordinates to device + pts = np.vstack([self.data['x'], self.data['y']]) + pts = self.mapPointsToDevice(pts) + if pts is None: + return + + # Cull points that are outside view + viewMask = self.getViewMask(pts) + #pts = pts[:,mask] + #data = self.data[mask] + + if self.opts['useCache'] and self._exportOpts is False: + # Draw symbols from pre-rendered atlas + atlas = self.fragmentAtlas.getAtlas() + + # Update targetRects if necessary + updateMask = viewMask & np.equal(self.data['targetRect'], None) + if np.any(updateMask): + updatePts = pts[:,updateMask] + width = self.data[updateMask]['width']*2 + self.data['targetRect'][updateMask] = list(imap(QtCore.QRectF, updatePts[0,:], updatePts[1,:], width, width)) + + data = self.data[viewMask] + if USE_PYSIDE: + list(imap(p.drawPixmap, data['targetRect'], repeat(atlas), data['sourceRect'])) + else: + p.drawPixmapFragments(data['targetRect'].tolist(), data['sourceRect'].tolist(), atlas) + else: + # render each symbol individually + p.setRenderHint(p.Antialiasing, aa) + + data = self.data[viewMask] + pts = pts[:,viewMask] + for i, rec in enumerate(data): + p.resetTransform() + p.translate(pts[0,i] + rec['width'], pts[1,i] + rec['width']) + drawSymbol(p, *self.getSpotOpts(rec, scale)) + else: + if self.picture is None: + self.picture = QtGui.QPicture() + p2 = QtGui.QPainter(self.picture) + for rec in self.data: + if scale != 1.0: + rec = rec.copy() + rec['size'] *= scale + p2.resetTransform() + p2.translate(rec['x'], rec['y']) + drawSymbol(p2, *self.getSpotOpts(rec, scale)) + p2.end() + + p.setRenderHint(p.Antialiasing, aa) + self.picture.play(p) + + def points(self): + for rec in self.data: + if rec['item'] is None: + rec['item'] = SpotItem(rec, self) + return self.data['item'] + + def pointsAt(self, pos): + x = pos.x() + y = pos.y() + pw = self.pixelWidth() + ph = self.pixelHeight() + pts = [] + for s in self.points(): + sp = s.pos() + ss = s.size() + sx = sp.x() + sy = sp.y() + s2x = s2y = ss * 0.5 + if self.opts['pxMode']: + s2x *= pw + s2y *= ph + if x > sx-s2x and x < sx+s2x and y > sy-s2y and y < sy+s2y: + pts.append(s) + #print "HIT:", x, y, sx, sy, s2x, s2y + #else: + #print "No hit:", (x, y), (sx, sy) + #print " ", (sx-s2x, sy-s2y), (sx+s2x, sy+s2y) + #pts.sort(lambda a,b: cmp(b.zValue(), a.zValue())) + return pts[::-1] + + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.LeftButton: + pts = self.pointsAt(ev.pos()) + if len(pts) > 0: + self.ptsClicked = pts + self.sigClicked.emit(self, self.ptsClicked) + ev.accept() + else: + #print "no spots" + ev.ignore() + else: + ev.ignore() + + +class SpotItem(object): + """ + Class referring to individual spots in a scatter plot. + These can be retrieved by calling ScatterPlotItem.points() or + by connecting to the ScatterPlotItem's click signals. + """ + + def __init__(self, data, plot): + #GraphicsItem.__init__(self, register=False) + self._data = data + self._plot = plot + #self.setParentItem(plot) + #self.setPos(QtCore.QPointF(data['x'], data['y'])) + #self.updateItem() + + def data(self): + """Return the user data associated with this spot.""" + return self._data['data'] + + def size(self): + """Return the size of this spot. + If the spot has no explicit size set, then return the ScatterPlotItem's default size instead.""" + if self._data['size'] == -1: + return self._plot.opts['size'] + else: + return self._data['size'] + + def pos(self): + return Point(self._data['x'], self._data['y']) + + def viewPos(self): + return self._plot.mapToView(self.pos()) + + def setSize(self, size): + """Set the size of this spot. + If the size is set to -1, then the ScatterPlotItem's default size + will be used instead.""" + self._data['size'] = size + self.updateItem() + + def symbol(self): + """Return the symbol of this spot. + If the spot has no explicit symbol set, then return the ScatterPlotItem's default symbol instead. + """ + symbol = self._data['symbol'] + if symbol is None: + symbol = self._plot.opts['symbol'] + try: + n = int(symbol) + symbol = list(Symbols.keys())[n % len(Symbols)] + except: + pass + return symbol + + def setSymbol(self, symbol): + """Set the symbol for this spot. + If the symbol is set to '', then the ScatterPlotItem's default symbol will be used instead.""" + self._data['symbol'] = symbol + self.updateItem() + + def pen(self): + pen = self._data['pen'] + if pen is None: + pen = self._plot.opts['pen'] + return fn.mkPen(pen) + + def setPen(self, *args, **kargs): + """Set the outline pen for this spot""" + pen = fn.mkPen(*args, **kargs) + self._data['pen'] = pen + self.updateItem() + + def resetPen(self): + """Remove the pen set for this spot; the scatter plot's default pen will be used instead.""" + self._data['pen'] = None ## Note this is NOT the same as calling setPen(None) + self.updateItem() + + def brush(self): + brush = self._data['brush'] + if brush is None: + brush = self._plot.opts['brush'] + return fn.mkBrush(brush) + + def setBrush(self, *args, **kargs): + """Set the fill brush for this spot""" + brush = fn.mkBrush(*args, **kargs) + self._data['brush'] = brush + self.updateItem() + + def resetBrush(self): + """Remove the brush set for this spot; the scatter plot's default brush will be used instead.""" + self._data['brush'] = None ## Note this is NOT the same as calling setBrush(None) + self.updateItem() + + def setData(self, data): + """Set the user-data associated with this spot""" + self._data['data'] = data + + def updateItem(self): + self._data['sourceRect'] = None + self._plot.updateSpots(self._data.reshape(1)) + self._plot.invalidate() + +#class PixmapSpotItem(SpotItem, QtGui.QGraphicsPixmapItem): + #def __init__(self, data, plot): + #QtGui.QGraphicsPixmapItem.__init__(self) + #self.setFlags(self.flags() | self.ItemIgnoresTransformations) + #SpotItem.__init__(self, data, plot) + + #def setPixmap(self, pixmap): + #QtGui.QGraphicsPixmapItem.setPixmap(self, pixmap) + #self.setOffset(-pixmap.width()/2.+0.5, -pixmap.height()/2.) + + #def updateItem(self): + #symbolOpts = (self._data['pen'], self._data['brush'], self._data['size'], self._data['symbol']) + + ### If all symbol options are default, use default pixmap + #if symbolOpts == (None, None, -1, ''): + #pixmap = self._plot.defaultSpotPixmap() + #else: + #pixmap = makeSymbolPixmap(size=self.size(), pen=self.pen(), brush=self.brush(), symbol=self.symbol()) + #self.setPixmap(pixmap) + + +#class PathSpotItem(SpotItem, QtGui.QGraphicsPathItem): + #def __init__(self, data, plot): + #QtGui.QGraphicsPathItem.__init__(self) + #SpotItem.__init__(self, data, plot) + + #def updateItem(self): + #QtGui.QGraphicsPathItem.setPath(self, Symbols[self.symbol()]) + #QtGui.QGraphicsPathItem.setPen(self, self.pen()) + #QtGui.QGraphicsPathItem.setBrush(self, self.brush()) + #size = self.size() + #self.resetTransform() + #self.scale(size, size) diff --git a/papi/pyqtgraph/graphicsItems/TextItem.py b/papi/pyqtgraph/graphicsItems/TextItem.py new file mode 100644 index 00000000..d3c98006 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/TextItem.py @@ -0,0 +1,152 @@ +from ..Qt import QtCore, QtGui +from ..Point import Point +from .UIGraphicsItem import * +from .. import functions as fn + +class TextItem(UIGraphicsItem): + """ + GraphicsItem displaying unscaled text (the text will always appear normal even inside a scaled ViewBox). + """ + def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0), border=None, fill=None, angle=0): + """ + ============== ================================================================================= + **Arguments:** + *text* The text to display + *color* The color of the text (any format accepted by pg.mkColor) + *html* If specified, this overrides both *text* and *color* + *anchor* A QPointF or (x,y) sequence indicating what region of the text box will + be anchored to the item's position. A value of (0,0) sets the upper-left corner + of the text box to be at the position specified by setPos(), while a value of (1,1) + sets the lower-right corner. + *border* A pen to use when drawing the border + *fill* A brush to use when filling within the border + ============== ================================================================================= + """ + + ## not working yet + #*angle* Angle in degrees to rotate text (note that the rotation assigned in this item's + #transformation will be ignored) + + self.anchor = Point(anchor) + #self.angle = 0 + UIGraphicsItem.__init__(self) + self.textItem = QtGui.QGraphicsTextItem() + self.textItem.setParentItem(self) + self.lastTransform = None + self._bounds = QtCore.QRectF() + if html is None: + self.setText(text, color) + else: + self.setHtml(html) + self.fill = fn.mkBrush(fill) + self.border = fn.mkPen(border) + self.rotate(angle) + self.setFlag(self.ItemIgnoresTransformations) ## This is required to keep the text unscaled inside the viewport + + def setText(self, text, color=(200,200,200)): + """ + Set the text and color of this item. + + This method sets the plain text of the item; see also setHtml(). + """ + color = fn.mkColor(color) + self.textItem.setDefaultTextColor(color) + self.textItem.setPlainText(text) + self.updateText() + #html = '%s' % (color, text) + #self.setHtml(html) + + def updateAnchor(self): + pass + #self.resetTransform() + #self.translate(0, 20) + + def setPlainText(self, *args): + """ + Set the plain text to be rendered by this item. + + See QtGui.QGraphicsTextItem.setPlainText(). + """ + self.textItem.setPlainText(*args) + self.updateText() + + def setHtml(self, *args): + """ + Set the HTML code to be rendered by this item. + + See QtGui.QGraphicsTextItem.setHtml(). + """ + self.textItem.setHtml(*args) + self.updateText() + + def setTextWidth(self, *args): + """ + Set the width of the text. + + If the text requires more space than the width limit, then it will be + wrapped into multiple lines. + + See QtGui.QGraphicsTextItem.setTextWidth(). + """ + self.textItem.setTextWidth(*args) + self.updateText() + + def setFont(self, *args): + """ + Set the font for this text. + + See QtGui.QGraphicsTextItem.setFont(). + """ + self.textItem.setFont(*args) + self.updateText() + + #def setAngle(self, angle): + #self.angle = angle + #self.updateText() + + + def updateText(self): + + ## Needed to maintain font size when rendering to image with increased resolution + self.textItem.resetTransform() + #self.textItem.rotate(self.angle) + if self._exportOpts is not False and 'resolutionScale' in self._exportOpts: + s = self._exportOpts['resolutionScale'] + self.textItem.scale(s, s) + + #br = self.textItem.mapRectToParent(self.textItem.boundingRect()) + self.textItem.setPos(0,0) + br = self.textItem.boundingRect() + apos = self.textItem.mapToParent(Point(br.width()*self.anchor.x(), br.height()*self.anchor.y())) + #print br, apos + self.textItem.setPos(-apos.x(), -apos.y()) + + #def textBoundingRect(self): + ### return the bounds of the text box in device coordinates + #pos = self.mapToDevice(QtCore.QPointF(0,0)) + #if pos is None: + #return None + #tbr = self.textItem.boundingRect() + #return QtCore.QRectF(pos.x() - tbr.width()*self.anchor.x(), pos.y() - tbr.height()*self.anchor.y(), tbr.width(), tbr.height()) + + + def viewRangeChanged(self): + self.updateText() + + def boundingRect(self): + return self.textItem.mapToParent(self.textItem.boundingRect()).boundingRect() + + def paint(self, p, *args): + tr = p.transform() + if self.lastTransform is not None: + if tr != self.lastTransform: + self.viewRangeChanged() + self.lastTransform = tr + + if self.border.style() != QtCore.Qt.NoPen or self.fill.style() != QtCore.Qt.NoBrush: + p.setPen(self.border) + p.setBrush(self.fill) + p.setRenderHint(p.Antialiasing, True) + p.drawPolygon(self.textItem.mapToParent(self.textItem.boundingRect())) + + \ No newline at end of file diff --git a/papi/pyqtgraph/graphicsItems/UIGraphicsItem.py b/papi/pyqtgraph/graphicsItems/UIGraphicsItem.py new file mode 100644 index 00000000..6f756334 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/UIGraphicsItem.py @@ -0,0 +1,124 @@ +from ..Qt import QtGui, QtCore, USE_PYSIDE +import weakref +from .GraphicsObject import GraphicsObject +if not USE_PYSIDE: + import sip + +__all__ = ['UIGraphicsItem'] +class UIGraphicsItem(GraphicsObject): + """ + Base class for graphics items with boundaries relative to a GraphicsView or ViewBox. + The purpose of this class is to allow the creation of GraphicsItems which live inside + a scalable view, but whose boundaries will always stay fixed relative to the view's boundaries. + For example: GridItem, InfiniteLine + + The view can be specified on initialization or it can be automatically detected when the item is painted. + + NOTE: Only the item's boundingRect is affected; the item is not transformed in any way. Use viewRangeChanged + to respond to changes in the view. + """ + + #sigViewChanged = QtCore.Signal(object) ## emitted whenever the viewport coords have changed + + def __init__(self, bounds=None, parent=None): + """ + ============== ============================================================================= + **Arguments:** + bounds QRectF with coordinates relative to view box. The default is QRectF(0,0,1,1), + which means the item will have the same bounds as the view. + ============== ============================================================================= + """ + GraphicsObject.__init__(self, parent) + self.setFlag(self.ItemSendsScenePositionChanges) + + if bounds is None: + self._bounds = QtCore.QRectF(0, 0, 1, 1) + else: + self._bounds = bounds + + self._boundingRect = None + self._updateView() + + def paint(self, *args): + ## check for a new view object every time we paint. + #self.updateView() + pass + + def itemChange(self, change, value): + ret = GraphicsObject.itemChange(self, change, value) + + ## workaround for pyqt bug: + ## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html + if not USE_PYSIDE and change == self.ItemParentChange and isinstance(ret, QtGui.QGraphicsItem): + ret = sip.cast(ret, QtGui.QGraphicsItem) + + if change == self.ItemScenePositionHasChanged: + self.setNewBounds() + return ret + + #def updateView(self): + ### called to see whether this item has a new view to connect to + + ### check for this item's current viewbox or view widget + #view = self.getViewBox() + #if view is None: + ##print " no view" + #return + + #if self._connectedView is not None and view is self._connectedView(): + ##print " already have view", view + #return + + ### disconnect from previous view + #if self._connectedView is not None: + #cv = self._connectedView() + #if cv is not None: + ##print "disconnect:", self + #cv.sigRangeChanged.disconnect(self.viewRangeChanged) + + ### connect to new view + ##print "connect:", self + #view.sigRangeChanged.connect(self.viewRangeChanged) + #self._connectedView = weakref.ref(view) + #self.setNewBounds() + + def boundingRect(self): + if self._boundingRect is None: + br = self.viewRect() + if br is None: + return QtCore.QRectF() + else: + self._boundingRect = br + return QtCore.QRectF(self._boundingRect) + + def dataBounds(self, axis, frac=1.0, orthoRange=None): + """Called by ViewBox for determining the auto-range bounds. + By default, UIGraphicsItems are excluded from autoRange.""" + return None + + def viewRangeChanged(self): + """Called when the view widget/viewbox is resized/rescaled""" + self.setNewBounds() + self.update() + + def setNewBounds(self): + """Update the item's bounding rect to match the viewport""" + self._boundingRect = None ## invalidate bounding rect, regenerate later if needed. + self.prepareGeometryChange() + + + def setPos(self, *args): + GraphicsObject.setPos(self, *args) + self.setNewBounds() + + def mouseShape(self): + """Return the shape of this item after expanding by 2 pixels""" + shape = self.shape() + ds = self.mapToDevice(shape) + stroker = QtGui.QPainterPathStroker() + stroker.setWidh(2) + ds2 = stroker.createStroke(ds).united(ds) + return self.mapFromDevice(ds2) + + + diff --git a/papi/pyqtgraph/graphicsItems/VTickGroup.py b/papi/pyqtgraph/graphicsItems/VTickGroup.py new file mode 100644 index 00000000..1db4a4a2 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/VTickGroup.py @@ -0,0 +1,99 @@ +if __name__ == '__main__': + import os, sys + path = os.path.abspath(os.path.dirname(__file__)) + sys.path.insert(0, os.path.join(path, '..', '..')) + +from ..Qt import QtGui, QtCore +from .. import functions as fn +import weakref +from .UIGraphicsItem import UIGraphicsItem + +__all__ = ['VTickGroup'] +class VTickGroup(UIGraphicsItem): + """ + **Bases:** :class:`UIGraphicsItem ` + + Draws a set of tick marks which always occupy the same vertical range of the view, + but have x coordinates relative to the data within the view. + + """ + def __init__(self, xvals=None, yrange=None, pen=None): + """ + ============== =================================================================== + **Arguments:** + xvals A list of x values (in data coordinates) at which to draw ticks. + yrange A list of [low, high] limits for the tick. 0 is the bottom of + the view, 1 is the top. [0.8, 1] would draw ticks in the top + fifth of the view. + pen The pen to use for drawing ticks. Default is grey. Can be specified + as any argument valid for :func:`mkPen` + ============== =================================================================== + """ + if yrange is None: + yrange = [0, 1] + if xvals is None: + xvals = [] + + UIGraphicsItem.__init__(self) + + if pen is None: + pen = (200, 200, 200) + + self.path = QtGui.QGraphicsPathItem() + + self.ticks = [] + self.xvals = [] + self.yrange = [0,1] + self.setPen(pen) + self.setYRange(yrange) + self.setXVals(xvals) + + def setPen(self, *args, **kwargs): + """Set the pen to use for drawing ticks. Can be specified as any arguments valid + for :func:`mkPen`""" + self.pen = fn.mkPen(*args, **kwargs) + + def setXVals(self, vals): + """Set the x values for the ticks. + + ============== ===================================================================== + **Arguments:** + vals A list of x values (in data/plot coordinates) at which to draw ticks. + ============== ===================================================================== + """ + self.xvals = vals + self.rebuildTicks() + #self.valid = False + + def setYRange(self, vals): + """Set the y range [low, high] that the ticks are drawn on. 0 is the bottom of + the view, 1 is the top.""" + self.yrange = vals + self.rebuildTicks() + + def dataBounds(self, *args, **kargs): + return None ## item should never affect view autoscaling + + def yRange(self): + return self.yrange + + def rebuildTicks(self): + self.path = QtGui.QPainterPath() + yrange = self.yRange() + for x in self.xvals: + self.path.moveTo(x, 0.) + self.path.lineTo(x, 1.) + + def paint(self, p, *args): + UIGraphicsItem.paint(self, p, *args) + + br = self.boundingRect() + h = br.height() + br.setY(br.y() + self.yrange[0] * h) + br.setHeight(h - (1.0-self.yrange[1]) * h) + p.translate(0, br.y()) + p.scale(1.0, br.height()) + p.setPen(self.pen) + p.drawPath(self.path) + + \ No newline at end of file diff --git a/papi/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/papi/pyqtgraph/graphicsItems/ViewBox/ViewBox.py new file mode 100644 index 00000000..900c2038 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -0,0 +1,1774 @@ +from ...Qt import QtGui, QtCore +from ...python2_3 import sortList +import numpy as np +from ...Point import Point +from ... import functions as fn +from .. ItemGroup import ItemGroup +from .. GraphicsWidget import GraphicsWidget +import weakref +from copy import deepcopy +from ... import debug as debug +from ... import getConfigOption +import sys +from ...Qt import isQObjectAlive + +__all__ = ['ViewBox'] + +class WeakList(object): + + def __init__(self): + self._items = [] + + def append(self, obj): + #Add backwards to iterate backwards (to make iterating more efficient on removal). + self._items.insert(0, weakref.ref(obj)) + + def __iter__(self): + i = len(self._items)-1 + while i >= 0: + ref = self._items[i] + d = ref() + if d is None: + del self._items[i] + else: + yield d + i -= 1 + +class ChildGroup(ItemGroup): + + def __init__(self, parent): + ItemGroup.__init__(self, parent) + + # Used as callback to inform ViewBox when items are added/removed from + # the group. + # Note 1: We would prefer to override itemChange directly on the + # ViewBox, but this causes crashes on PySide. + # Note 2: We might also like to use a signal rather than this callback + # mechanism, but this causes a different PySide crash. + self.itemsChangedListeners = WeakList() + + # excempt from telling view when transform changes + self._GraphicsObject__inform_view_on_change = False + + def itemChange(self, change, value): + ret = ItemGroup.itemChange(self, change, value) + if change == self.ItemChildAddedChange or change == self.ItemChildRemovedChange: + try: + itemsChangedListeners = self.itemsChangedListeners + except AttributeError: + # It's possible that the attribute was already collected when the itemChange happened + # (if it was triggered during the gc of the object). + pass + else: + for listener in itemsChangedListeners: + listener.itemsChanged() + return ret + + +class ViewBox(GraphicsWidget): + """ + **Bases:** :class:`GraphicsWidget ` + + Box that allows internal scaling/panning of children by mouse drag. + This class is usually created automatically as part of a :class:`PlotItem ` or :class:`Canvas ` or with :func:`GraphicsLayout.addViewBox() `. + + Features: + + * Scaling contents by mouse or auto-scale when contents change + * View linking--multiple views display the same data ranges + * Configurable by context menu + * Item coordinate mapping methods + + """ + + sigYRangeChanged = QtCore.Signal(object, object) + sigXRangeChanged = QtCore.Signal(object, object) + sigRangeChangedManually = QtCore.Signal(object) + sigRangeChanged = QtCore.Signal(object, object) + #sigActionPositionChanged = QtCore.Signal(object) + sigStateChanged = QtCore.Signal(object) + sigTransformChanged = QtCore.Signal(object) + sigResized = QtCore.Signal(object) + + ## mouse modes + PanMode = 3 + RectMode = 1 + + ## axes + XAxis = 0 + YAxis = 1 + XYAxes = 2 + + ## for linking views together + NamedViews = weakref.WeakValueDictionary() # name: ViewBox + AllViews = weakref.WeakKeyDictionary() # ViewBox: None + + def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu=True, name=None, invertX=False): + """ + ============== ============================================================= + **Arguments:** + *parent* (QGraphicsWidget) Optional parent widget + *border* (QPen) Do draw a border around the view, give any + single argument accepted by :func:`mkPen ` + *lockAspect* (False or float) The aspect ratio to lock the view + coorinates to. (or False to allow the ratio to change) + *enableMouse* (bool) Whether mouse can be used to scale/pan the view + *invertY* (bool) See :func:`invertY ` + *invertX* (bool) See :func:`invertX ` + *enableMenu* (bool) Whether to display a context menu when + right-clicking on the ViewBox background. + *name* (str) Used to register this ViewBox so that it appears + in the "Link axis" dropdown inside other ViewBox + context menus. This allows the user to manually link + the axes of any other view to this one. + ============== ============================================================= + """ + + GraphicsWidget.__init__(self, parent) + self.name = None + self.linksBlocked = False + self.addedItems = [] + #self.gView = view + #self.showGrid = showGrid + self._matrixNeedsUpdate = True ## indicates that range has changed, but matrix update was deferred + self._autoRangeNeedsUpdate = True ## indicates auto-range needs to be recomputed. + + self._lastScene = None ## stores reference to the last known scene this view was a part of. + + self.state = { + + ## separating targetRange and viewRange allows the view to be resized + ## while keeping all previously viewed contents visible + 'targetRange': [[0,1], [0,1]], ## child coord. range visible [[xmin, xmax], [ymin, ymax]] + 'viewRange': [[0,1], [0,1]], ## actual range viewed + + 'yInverted': invertY, + 'xInverted': invertX, + 'aspectLocked': False, ## False if aspect is unlocked, otherwise float specifies the locked ratio. + 'autoRange': [True, True], ## False if auto range is disabled, + ## otherwise float gives the fraction of data that is visible + 'autoPan': [False, False], ## whether to only pan (do not change scaling) when auto-range is enabled + 'autoVisibleOnly': [False, False], ## whether to auto-range only to the visible portion of a plot + 'linkedViews': [None, None], ## may be None, "viewName", or weakref.ref(view) + ## a name string indicates that the view *should* link to another, but no view with that name exists yet. + + 'mouseEnabled': [enableMouse, enableMouse], + 'mouseMode': ViewBox.PanMode if getConfigOption('leftButtonPan') else ViewBox.RectMode, + 'enableMenu': enableMenu, + 'wheelScaleFactor': -1.0 / 8.0, + + 'background': None, + + # Limits + 'limits': { + 'xLimits': [None, None], # Maximum and minimum visible X values + 'yLimits': [None, None], # Maximum and minimum visible Y values + 'xRange': [None, None], # Maximum and minimum X range + 'yRange': [None, None], # Maximum and minimum Y range + } + + } + self._updatingRange = False ## Used to break recursive loops. See updateAutoRange. + self._itemBoundsCache = weakref.WeakKeyDictionary() + + self.locateGroup = None ## items displayed when using ViewBox.locate(item) + + self.setFlag(self.ItemClipsChildrenToShape) + self.setFlag(self.ItemIsFocusable, True) ## so we can receive key presses + + ## childGroup is required so that ViewBox has local coordinates similar to device coordinates. + ## this is a workaround for a Qt + OpenGL bug that causes improper clipping + ## https://bugreports.qt.nokia.com/browse/QTBUG-23723 + self.childGroup = ChildGroup(self) + self.childGroup.itemsChangedListeners.append(self) + + self.background = QtGui.QGraphicsRectItem(self.rect()) + self.background.setParentItem(self) + self.background.setZValue(-1e6) + self.background.setPen(fn.mkPen(None)) + self.updateBackground() + + #self.useLeftButtonPan = pyqtgraph.getConfigOption('leftButtonPan') # normally use left button to pan + # this also enables capture of keyPressEvents. + + ## Make scale box that is shown when dragging on the view + self.rbScaleBox = QtGui.QGraphicsRectItem(0, 0, 1, 1) + self.rbScaleBox.setPen(fn.mkPen((255,255,100), width=1)) + self.rbScaleBox.setBrush(fn.mkBrush(255,255,0,100)) + self.rbScaleBox.setZValue(1e9) + self.rbScaleBox.hide() + self.addItem(self.rbScaleBox, ignoreBounds=True) + + ## show target rect for debugging + self.target = QtGui.QGraphicsRectItem(0, 0, 1, 1) + self.target.setPen(fn.mkPen('r')) + self.target.setParentItem(self) + self.target.hide() + + self.axHistory = [] # maintain a history of zoom locations + self.axHistoryPointer = -1 # pointer into the history. Allows forward/backward movement, not just "undo" + + self.setZValue(-100) + self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)) + + self.setAspectLocked(lockAspect) + + self.border = fn.mkPen(border) + self.menu = ViewBoxMenu(self) + + self.register(name) + if name is None: + self.updateViewLists() + + def register(self, name): + """ + Add this ViewBox to the registered list of views. + + This allows users to manually link the axes of any other ViewBox to + this one. The specified *name* will appear in the drop-down lists for + axis linking in the context menus of all other views. + + The same can be accomplished by initializing the ViewBox with the *name* attribute. + """ + ViewBox.AllViews[self] = None + if self.name is not None: + del ViewBox.NamedViews[self.name] + self.name = name + if name is not None: + ViewBox.NamedViews[name] = self + ViewBox.updateAllViewLists() + sid = id(self) + self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if (ViewBox is not None and 'sid' in locals() and 'name' in locals()) else None) + #self.destroyed.connect(self.unregister) + + def unregister(self): + """ + Remove this ViewBox from the list of linkable views. (see :func:`register() `) + """ + del ViewBox.AllViews[self] + if self.name is not None: + del ViewBox.NamedViews[self.name] + + def close(self): + self.clear() + self.unregister() + + def implements(self, interface): + return interface == 'ViewBox' + + # removed due to https://bugreports.qt-project.org/browse/PYSIDE-86 + #def itemChange(self, change, value): + ## Note: Calling QWidget.itemChange causes segv in python 3 + PyQt + ##ret = QtGui.QGraphicsItem.itemChange(self, change, value) + #ret = GraphicsWidget.itemChange(self, change, value) + #if change == self.ItemSceneChange: + #scene = self.scene() + #if scene is not None and hasattr(scene, 'sigPrepareForPaint'): + #scene.sigPrepareForPaint.disconnect(self.prepareForPaint) + #elif change == self.ItemSceneHasChanged: + #scene = self.scene() + #if scene is not None and hasattr(scene, 'sigPrepareForPaint'): + #scene.sigPrepareForPaint.connect(self.prepareForPaint) + #return ret + + def checkSceneChange(self): + # ViewBox needs to receive sigPrepareForPaint from its scene before + # being painted. However, we have no way of being informed when the + # scene has changed in order to make this connection. The usual way + # to do this is via itemChange(), but bugs prevent this approach + # (see above). Instead, we simply check at every paint to see whether + # (the scene has changed. + scene = self.scene() + if scene == self._lastScene: + return + if self._lastScene is not None and hasattr(self.lastScene, 'sigPrepareForPaint'): + self._lastScene.sigPrepareForPaint.disconnect(self.prepareForPaint) + if scene is not None and hasattr(scene, 'sigPrepareForPaint'): + scene.sigPrepareForPaint.connect(self.prepareForPaint) + self.prepareForPaint() + self._lastScene = scene + + + + + def prepareForPaint(self): + #autoRangeEnabled = (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False) + # don't check whether auto range is enabled here--only check when setting dirty flag. + if self._autoRangeNeedsUpdate: # and autoRangeEnabled: + self.updateAutoRange() + if self._matrixNeedsUpdate: + self.updateMatrix() + + def getState(self, copy=True): + """Return the current state of the ViewBox. + Linked views are always converted to view names in the returned state.""" + state = self.state.copy() + views = [] + for v in state['linkedViews']: + if isinstance(v, weakref.ref): + v = v() + if v is None or isinstance(v, basestring): + views.append(v) + else: + views.append(v.name) + state['linkedViews'] = views + if copy: + return deepcopy(state) + else: + return state + + def setState(self, state): + """Restore the state of this ViewBox. + (see also getState)""" + state = state.copy() + self.setXLink(state['linkedViews'][0]) + self.setYLink(state['linkedViews'][1]) + del state['linkedViews'] + + self.state.update(state) + #self.updateMatrix() + self.updateViewRange() + self.sigStateChanged.emit(self) + + def setBackgroundColor(self, color): + """ + Set the background color of the ViewBox. + + If color is None, then no background will be drawn. + + Added in version 0.9.9 + """ + self.background.setVisible(color is not None) + self.state['background'] = color + self.updateBackground() + + def setMouseMode(self, mode): + """ + Set the mouse interaction mode. *mode* must be either ViewBox.PanMode or ViewBox.RectMode. + In PanMode, the left mouse button pans the view and the right button scales. + In RectMode, the left button draws a rectangle which updates the visible region (this mode is more suitable for single-button mice) + """ + if mode not in [ViewBox.PanMode, ViewBox.RectMode]: + raise Exception("Mode must be ViewBox.PanMode or ViewBox.RectMode") + self.state['mouseMode'] = mode + self.sigStateChanged.emit(self) + + #def toggleLeftAction(self, act): ## for backward compatibility + #if act.text() is 'pan': + #self.setLeftButtonAction('pan') + #elif act.text() is 'zoom': + #self.setLeftButtonAction('rect') + + def setLeftButtonAction(self, mode='rect'): ## for backward compatibility + if mode.lower() == 'rect': + self.setMouseMode(ViewBox.RectMode) + elif mode.lower() == 'pan': + self.setMouseMode(ViewBox.PanMode) + else: + raise Exception('graphicsItems:ViewBox:setLeftButtonAction: unknown mode = %s (Options are "pan" and "rect")' % mode) + + def innerSceneItem(self): + return self.childGroup + + def setMouseEnabled(self, x=None, y=None): + """ + Set whether each axis is enabled for mouse interaction. *x*, *y* arguments must be True or False. + This allows the user to pan/scale one axis of the view while leaving the other axis unchanged. + """ + if x is not None: + self.state['mouseEnabled'][0] = x + if y is not None: + self.state['mouseEnabled'][1] = y + self.sigStateChanged.emit(self) + + def mouseEnabled(self): + return self.state['mouseEnabled'][:] + + def setMenuEnabled(self, enableMenu=True): + self.state['enableMenu'] = enableMenu + self.sigStateChanged.emit(self) + + def menuEnabled(self): + return self.state.get('enableMenu', True) + + def addItem(self, item, ignoreBounds=False): + """ + Add a QGraphicsItem to this view. The view will include this item when determining how to set its range + automatically unless *ignoreBounds* is True. + """ + if item.zValue() < self.zValue(): + item.setZValue(self.zValue()+1) + scene = self.scene() + if scene is not None and scene is not item.scene(): + scene.addItem(item) ## Necessary due to Qt bug: https://bugreports.qt-project.org/browse/QTBUG-18616 + item.setParentItem(self.childGroup) + if not ignoreBounds: + self.addedItems.append(item) + self.updateAutoRange() + #print "addItem:", item, item.boundingRect() + + def removeItem(self, item): + """Remove an item from this view.""" + try: + self.addedItems.remove(item) + except: + pass + self.scene().removeItem(item) + self.updateAutoRange() + + def clear(self): + for i in self.addedItems[:]: + self.removeItem(i) + for ch in self.childGroup.childItems(): + ch.setParentItem(None) + + def resizeEvent(self, ev): + self.linkedXChanged() + self.linkedYChanged() + self.updateAutoRange() + self.updateViewRange() + self._matrixNeedsUpdate = True + self.sigStateChanged.emit(self) + self.background.setRect(self.rect()) + self.sigResized.emit(self) + + def viewRange(self): + """Return a the view's visible range as a list: [[xmin, xmax], [ymin, ymax]]""" + return [x[:] for x in self.state['viewRange']] ## return copy + + def viewRect(self): + """Return a QRectF bounding the region visible within the ViewBox""" + try: + vr0 = self.state['viewRange'][0] + vr1 = self.state['viewRange'][1] + return QtCore.QRectF(vr0[0], vr1[0], vr0[1]-vr0[0], vr1[1] - vr1[0]) + except: + print("make qrectf failed:", self.state['viewRange']) + raise + + def targetRange(self): + return [x[:] for x in self.state['targetRange']] ## return copy + + def targetRect(self): + """ + Return the region which has been requested to be visible. + (this is not necessarily the same as the region that is *actually* visible-- + resizing and aspect ratio constraints can cause targetRect() and viewRect() to differ) + """ + try: + tr0 = self.state['targetRange'][0] + tr1 = self.state['targetRange'][1] + return QtCore.QRectF(tr0[0], tr1[0], tr0[1]-tr0[0], tr1[1] - tr1[0]) + except: + print("make qrectf failed:", self.state['targetRange']) + raise + + def _resetTarget(self): + # Reset target range to exactly match current view range. + # This is used during mouse interaction to prevent unpredictable + # behavior (because the user is unaware of targetRange). + if self.state['aspectLocked'] is False: # (interferes with aspect locking) + self.state['targetRange'] = [self.state['viewRange'][0][:], self.state['viewRange'][1][:]] + + def setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=True, disableAutoRange=True): + """ + Set the visible range of the ViewBox. + Must specify at least one of *rect*, *xRange*, or *yRange*. + + ================== ===================================================================== + **Arguments:** + *rect* (QRectF) The full range that should be visible in the view box. + *xRange* (min,max) The range that should be visible along the x-axis. + *yRange* (min,max) The range that should be visible along the y-axis. + *padding* (float) Expand the view by a fraction of the requested range. + By default, this value is set between 0.02 and 0.1 depending on + the size of the ViewBox. + *update* (bool) If True, update the range of the ViewBox immediately. + Otherwise, the update is deferred until before the next render. + *disableAutoRange* (bool) If True, auto-ranging is diabled. Otherwise, it is left + unchanged. + ================== ===================================================================== + + """ + #print self.name, "ViewBox.setRange", rect, xRange, yRange, padding + #import traceback + #traceback.print_stack() + + changes = {} # axes + setRequested = [False, False] + + if rect is not None: + changes = {0: [rect.left(), rect.right()], 1: [rect.top(), rect.bottom()]} + setRequested = [True, True] + if xRange is not None: + changes[0] = xRange + setRequested[0] = True + if yRange is not None: + changes[1] = yRange + setRequested[1] = True + + if len(changes) == 0: + print(rect) + raise Exception("Must specify at least one of rect, xRange, or yRange. (gave rect=%s)" % str(type(rect))) + + # Update axes one at a time + changed = [False, False] + for ax, range in changes.items(): + mn = min(range) + mx = max(range) + + # If we requested 0 range, try to preserve previous scale. + # Otherwise just pick an arbitrary scale. + if mn == mx: + dy = self.state['viewRange'][ax][1] - self.state['viewRange'][ax][0] + if dy == 0: + dy = 1 + mn -= dy*0.5 + mx += dy*0.5 + xpad = 0.0 + + # Make sure no nan/inf get through + if not all(np.isfinite([mn, mx])): + raise Exception("Cannot set range [%s, %s]" % (str(mn), str(mx))) + + # Apply padding + if padding is None: + xpad = self.suggestPadding(ax) + else: + xpad = padding + p = (mx-mn) * xpad + mn -= p + mx += p + + # Set target range + if self.state['targetRange'][ax] != [mn, mx]: + self.state['targetRange'][ax] = [mn, mx] + changed[ax] = True + + # Update viewRange to match targetRange as closely as possible while + # accounting for aspect ratio constraint + lockX, lockY = setRequested + if lockX and lockY: + lockX = False + lockY = False + self.updateViewRange(lockX, lockY) + + # Disable auto-range for each axis that was requested to be set + if disableAutoRange: + xOff = False if setRequested[0] else None + yOff = False if setRequested[1] else None + self.enableAutoRange(x=xOff, y=yOff) + changed.append(True) + + # If nothing has changed, we are done. + if any(changed): + #if update and self.matrixNeedsUpdate: + #self.updateMatrix(changed) + #return + + self.sigStateChanged.emit(self) + + # Update target rect for debugging + if self.target.isVisible(): + self.target.setRect(self.mapRectFromItem(self.childGroup, self.targetRect())) + + # If ortho axes have auto-visible-only, update them now + # Note that aspect ratio constraints and auto-visible probably do not work together.. + if changed[0] and self.state['autoVisibleOnly'][1] and (self.state['autoRange'][0] is not False): + self._autoRangeNeedsUpdate = True + #self.updateAutoRange() ## Maybe just indicate that auto range needs to be updated? + elif changed[1] and self.state['autoVisibleOnly'][0] and (self.state['autoRange'][1] is not False): + self._autoRangeNeedsUpdate = True + #self.updateAutoRange() + + ## Update view matrix only if requested + #if update: + #self.updateMatrix(changed) + ## Otherwise, indicate that the matrix needs to be updated + #else: + #self.matrixNeedsUpdate = True + + ## Inform linked views that the range has changed <> + #for ax, range in changes.items(): + #link = self.linkedView(ax) + #if link is not None: + #link.linkedViewChanged(self, ax) + + + + def setYRange(self, min, max, padding=None, update=True): + """ + Set the visible Y range of the view to [*min*, *max*]. + The *padding* argument causes the range to be set larger by the fraction specified. + (by default, this value is between 0.02 and 0.1 depending on the size of the ViewBox) + """ + self.setRange(yRange=[min, max], update=update, padding=padding) + + def setXRange(self, min, max, padding=None, update=True): + """ + Set the visible X range of the view to [*min*, *max*]. + The *padding* argument causes the range to be set larger by the fraction specified. + (by default, this value is between 0.02 and 0.1 depending on the size of the ViewBox) + """ + self.setRange(xRange=[min, max], update=update, padding=padding) + + def autoRange(self, padding=None, items=None, item=None): + """ + Set the range of the view box to make all children visible. + Note that this is not the same as enableAutoRange, which causes the view to + automatically auto-range whenever its contents are changed. + + ============== ============================================================ + **Arguments:** + padding The fraction of the total data range to add on to the final + visible range. By default, this value is set between 0.02 + and 0.1 depending on the size of the ViewBox. + items If specified, this is a list of items to consider when + determining the visible range. + ============== ============================================================ + """ + if item is None: + bounds = self.childrenBoundingRect(items=items) + else: + print("Warning: ViewBox.autoRange(item=__) is deprecated. Use 'items' argument instead.") + bounds = self.mapFromItemToView(item, item.boundingRect()).boundingRect() + + if bounds is not None: + self.setRange(bounds, padding=padding) + + def suggestPadding(self, axis): + l = self.width() if axis==0 else self.height() + if l > 0: + padding = np.clip(1./(l**0.5), 0.02, 0.1) + else: + padding = 0.02 + return padding + + def setLimits(self, **kwds): + """ + Set limits that constrain the possible view ranges. + + **Panning limits**. The following arguments define the region within the + viewbox coordinate system that may be accessed by panning the view. + + =========== ============================================================ + xMin Minimum allowed x-axis value + xMax Maximum allowed x-axis value + yMin Minimum allowed y-axis value + yMax Maximum allowed y-axis value + =========== ============================================================ + + **Scaling limits**. These arguments prevent the view being zoomed in or + out too far. + + =========== ============================================================ + minXRange Minimum allowed left-to-right span across the view. + maxXRange Maximum allowed left-to-right span across the view. + minYRange Minimum allowed top-to-bottom span across the view. + maxYRange Maximum allowed top-to-bottom span across the view. + =========== ============================================================ + + Added in version 0.9.9 + """ + update = False + allowed = ['xMin', 'xMax', 'yMin', 'yMax', 'minXRange', 'maxXRange', 'minYRange', 'maxYRange'] + for kwd in kwds: + if kwd not in allowed: + raise ValueError("Invalid keyword argument '%s'." % kwd) + #for kwd in ['xLimits', 'yLimits', 'minRange', 'maxRange']: + #if kwd in kwds and self.state['limits'][kwd] != kwds[kwd]: + #self.state['limits'][kwd] = kwds[kwd] + #update = True + for axis in [0,1]: + for mnmx in [0,1]: + kwd = [['xMin', 'xMax'], ['yMin', 'yMax']][axis][mnmx] + lname = ['xLimits', 'yLimits'][axis] + if kwd in kwds and self.state['limits'][lname][mnmx] != kwds[kwd]: + self.state['limits'][lname][mnmx] = kwds[kwd] + update = True + kwd = [['minXRange', 'maxXRange'], ['minYRange', 'maxYRange']][axis][mnmx] + lname = ['xRange', 'yRange'][axis] + if kwd in kwds and self.state['limits'][lname][mnmx] != kwds[kwd]: + self.state['limits'][lname][mnmx] = kwds[kwd] + update = True + + if update: + self.updateViewRange() + + + + + def scaleBy(self, s=None, center=None, x=None, y=None): + """ + Scale by *s* around given center point (or center of view). + *s* may be a Point or tuple (x, y). + + Optionally, x or y may be specified individually. This allows the other + axis to be left unaffected (note that using a scale factor of 1.0 may + cause slight changes due to floating-point error). + """ + if s is not None: + scale = Point(s) + else: + scale = [x, y] + + affect = [True, True] + if scale[0] is None and scale[1] is None: + return + elif scale[0] is None: + affect[0] = False + scale[0] = 1.0 + elif scale[1] is None: + affect[1] = False + scale[1] = 1.0 + + scale = Point(scale) + + if self.state['aspectLocked'] is not False: + scale[0] = scale[1] + + vr = self.targetRect() + if center is None: + center = Point(vr.center()) + else: + center = Point(center) + + tl = center + (vr.topLeft()-center) * scale + br = center + (vr.bottomRight()-center) * scale + + if not affect[0]: + self.setYRange(tl.y(), br.y(), padding=0) + elif not affect[1]: + self.setXRange(tl.x(), br.x(), padding=0) + else: + self.setRange(QtCore.QRectF(tl, br), padding=0) + + def translateBy(self, t=None, x=None, y=None): + """ + Translate the view by *t*, which may be a Point or tuple (x, y). + + Alternately, x or y may be specified independently, leaving the other + axis unchanged (note that using a translation of 0 may still cause + small changes due to floating-point error). + """ + vr = self.targetRect() + if t is not None: + t = Point(t) + self.setRange(vr.translated(t), padding=0) + else: + if x is not None: + x = vr.left()+x, vr.right()+x + if y is not None: + y = vr.top()+y, vr.bottom()+y + if x is not None or y is not None: + self.setRange(xRange=x, yRange=y, padding=0) + + + + def enableAutoRange(self, axis=None, enable=True, x=None, y=None): + """ + Enable (or disable) auto-range for *axis*, which may be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes for both + (if *axis* is omitted, both axes will be changed). + When enabled, the axis will automatically rescale when items are added/removed or change their shape. + The argument *enable* may optionally be a float (0.0-1.0) which indicates the fraction of the data that should + be visible (this only works with items implementing a dataRange method, such as PlotDataItem). + """ + #print "autorange:", axis, enable + #if not enable: + #import traceback + #traceback.print_stack() + + # support simpler interface: + if x is not None or y is not None: + if x is not None: + self.enableAutoRange(ViewBox.XAxis, x) + if y is not None: + self.enableAutoRange(ViewBox.YAxis, y) + return + + if enable is True: + enable = 1.0 + + if axis is None: + axis = ViewBox.XYAxes + + needAutoRangeUpdate = False + + if axis == ViewBox.XYAxes or axis == 'xy': + axes = [0, 1] + elif axis == ViewBox.XAxis or axis == 'x': + axes = [0] + elif axis == ViewBox.YAxis or axis == 'y': + axes = [1] + else: + raise Exception('axis argument must be ViewBox.XAxis, ViewBox.YAxis, or ViewBox.XYAxes.') + + for ax in axes: + if self.state['autoRange'][ax] != enable: + # If we are disabling, do one last auto-range to make sure that + # previously scheduled auto-range changes are enacted + if enable is False and self._autoRangeNeedsUpdate: + self.updateAutoRange() + + self.state['autoRange'][ax] = enable + self._autoRangeNeedsUpdate |= (enable is not False) + self.update() + + + #if needAutoRangeUpdate: + # self.updateAutoRange() + + self.sigStateChanged.emit(self) + + def disableAutoRange(self, axis=None): + """Disables auto-range. (See enableAutoRange)""" + self.enableAutoRange(axis, enable=False) + + def autoRangeEnabled(self): + return self.state['autoRange'][:] + + def setAutoPan(self, x=None, y=None): + if x is not None: + self.state['autoPan'][0] = x + if y is not None: + self.state['autoPan'][1] = y + if None not in [x,y]: + self.updateAutoRange() + + def setAutoVisible(self, x=None, y=None): + if x is not None: + self.state['autoVisibleOnly'][0] = x + if x is True: + self.state['autoVisibleOnly'][1] = False + if y is not None: + self.state['autoVisibleOnly'][1] = y + if y is True: + self.state['autoVisibleOnly'][0] = False + + if x is not None or y is not None: + self.updateAutoRange() + + def updateAutoRange(self): + ## Break recursive loops when auto-ranging. + ## This is needed because some items change their size in response + ## to a view change. + if self._updatingRange: + return + + self._updatingRange = True + try: + targetRect = self.viewRange() + if not any(self.state['autoRange']): + return + + fractionVisible = self.state['autoRange'][:] + for i in [0,1]: + if type(fractionVisible[i]) is bool: + fractionVisible[i] = 1.0 + + childRange = None + + order = [0,1] + if self.state['autoVisibleOnly'][0] is True: + order = [1,0] + + args = {} + for ax in order: + if self.state['autoRange'][ax] is False: + continue + if self.state['autoVisibleOnly'][ax]: + oRange = [None, None] + oRange[ax] = targetRect[1-ax] + childRange = self.childrenBounds(frac=fractionVisible, orthoRange=oRange) + + else: + if childRange is None: + childRange = self.childrenBounds(frac=fractionVisible) + + ## Make corrections to range + xr = childRange[ax] + if xr is not None: + if self.state['autoPan'][ax]: + x = sum(xr) * 0.5 + w2 = (targetRect[ax][1]-targetRect[ax][0]) / 2. + childRange[ax] = [x-w2, x+w2] + else: + padding = self.suggestPadding(ax) + wp = (xr[1] - xr[0]) * padding + childRange[ax][0] -= wp + childRange[ax][1] += wp + targetRect[ax] = childRange[ax] + args['xRange' if ax == 0 else 'yRange'] = targetRect[ax] + if len(args) == 0: + return + args['padding'] = 0 + args['disableAutoRange'] = False + + # check for and ignore bad ranges + for k in ['xRange', 'yRange']: + if k in args: + if not np.all(np.isfinite(args[k])): + r = args.pop(k) + #print("Warning: %s is invalid: %s" % (k, str(r)) + + self.setRange(**args) + finally: + self._autoRangeNeedsUpdate = False + self._updatingRange = False + + def setXLink(self, view): + """Link this view's X axis to another view. (see LinkView)""" + self.linkView(self.XAxis, view) + + def setYLink(self, view): + """Link this view's Y axis to another view. (see LinkView)""" + self.linkView(self.YAxis, view) + + + def linkView(self, axis, view): + """ + Link X or Y axes of two views and unlink any previously connected axes. *axis* must be ViewBox.XAxis or ViewBox.YAxis. + If view is None, the axis is left unlinked. + """ + if isinstance(view, basestring): + if view == '': + view = None + else: + view = ViewBox.NamedViews.get(view, view) ## convert view name to ViewBox if possible + + if hasattr(view, 'implements') and view.implements('ViewBoxWrapper'): + view = view.getViewBox() + + ## used to connect/disconnect signals between a pair of views + if axis == ViewBox.XAxis: + signal = 'sigXRangeChanged' + slot = self.linkedXChanged + else: + signal = 'sigYRangeChanged' + slot = self.linkedYChanged + + + oldLink = self.linkedView(axis) + if oldLink is not None: + try: + getattr(oldLink, signal).disconnect(slot) + oldLink.sigResized.disconnect(slot) + except (TypeError, RuntimeError): + ## This can occur if the view has been deleted already + pass + + + if view is None or isinstance(view, basestring): + self.state['linkedViews'][axis] = view + else: + self.state['linkedViews'][axis] = weakref.ref(view) + getattr(view, signal).connect(slot) + view.sigResized.connect(slot) + if view.autoRangeEnabled()[axis] is not False: + self.enableAutoRange(axis, False) + slot() + else: + if self.autoRangeEnabled()[axis] is False: + slot() + + + self.sigStateChanged.emit(self) + + def blockLink(self, b): + self.linksBlocked = b ## prevents recursive plot-change propagation + + def linkedXChanged(self): + ## called when x range of linked view has changed + view = self.linkedView(0) + self.linkedViewChanged(view, ViewBox.XAxis) + + def linkedYChanged(self): + ## called when y range of linked view has changed + view = self.linkedView(1) + self.linkedViewChanged(view, ViewBox.YAxis) + + def linkedView(self, ax): + ## Return the linked view for axis *ax*. + ## this method _always_ returns either a ViewBox or None. + v = self.state['linkedViews'][ax] + if v is None or isinstance(v, basestring): + return None + else: + return v() ## dereference weakref pointer. If the reference is dead, this returns None + + def linkedViewChanged(self, view, axis): + if self.linksBlocked or view is None: + return + + #print self.name, "ViewBox.linkedViewChanged", axis, view.viewRange()[axis] + vr = view.viewRect() + vg = view.screenGeometry() + sg = self.screenGeometry() + if vg is None or sg is None: + return + + view.blockLink(True) + try: + if axis == ViewBox.XAxis: + overlap = min(sg.right(), vg.right()) - max(sg.left(), vg.left()) + if overlap < min(vg.width()/3, sg.width()/3): ## if less than 1/3 of views overlap, + ## then just replicate the view + x1 = vr.left() + x2 = vr.right() + else: ## views overlap; line them up + upp = float(vr.width()) / vg.width() + if self.xInverted(): + x1 = vr.left() + (sg.right()-vg.right()) * upp + else: + x1 = vr.left() + (sg.x()-vg.x()) * upp + x2 = x1 + sg.width() * upp + self.enableAutoRange(ViewBox.XAxis, False) + self.setXRange(x1, x2, padding=0) + else: + overlap = min(sg.bottom(), vg.bottom()) - max(sg.top(), vg.top()) + if overlap < min(vg.height()/3, sg.height()/3): ## if less than 1/3 of views overlap, + ## then just replicate the view + y1 = vr.top() + y2 = vr.bottom() + else: ## views overlap; line them up + upp = float(vr.height()) / vg.height() + if self.yInverted(): + y2 = vr.bottom() + (sg.bottom()-vg.bottom()) * upp + else: + y2 = vr.bottom() + (sg.top()-vg.top()) * upp + y1 = y2 - sg.height() * upp + self.enableAutoRange(ViewBox.YAxis, False) + self.setYRange(y1, y2, padding=0) + finally: + view.blockLink(False) + + + def screenGeometry(self): + """return the screen geometry of the viewbox""" + v = self.getViewWidget() + if v is None: + return None + b = self.sceneBoundingRect() + wr = v.mapFromScene(b).boundingRect() + pos = v.mapToGlobal(v.pos()) + wr.adjust(pos.x(), pos.y(), pos.x(), pos.y()) + return wr + + + + def itemsChanged(self): + ## called when items are added/removed from self.childGroup + self.updateAutoRange() + + def itemBoundsChanged(self, item): + self._itemBoundsCache.pop(item, None) + if (self.state['autoRange'][0] is not False) or (self.state['autoRange'][1] is not False): + self._autoRangeNeedsUpdate = True + self.update() + #self.updateAutoRange() + + def invertY(self, b=True): + """ + By default, the positive y-axis points upward on the screen. Use invertY(True) to reverse the y-axis. + """ + if self.state['yInverted'] == b: + return + + self.state['yInverted'] = b + self._matrixNeedsUpdate = True # updateViewRange won't detect this for us + self.updateViewRange() + self.sigStateChanged.emit(self) + self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1])) + + def yInverted(self): + return self.state['yInverted'] + + def invertX(self, b=True): + """ + By default, the positive x-axis points rightward on the screen. Use invertX(True) to reverse the x-axis. + """ + if self.state['xInverted'] == b: + return + + self.state['xInverted'] = b + #self.updateMatrix(changed=(False, True)) + self.updateViewRange() + self.sigStateChanged.emit(self) + self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0])) + + def xInverted(self): + return self.state['xInverted'] + + def setAspectLocked(self, lock=True, ratio=1): + """ + If the aspect ratio is locked, view scaling must always preserve the aspect ratio. + By default, the ratio is set to 1; x and y both have the same scaling. + This ratio can be overridden (xScale/yScale), or use None to lock in the current ratio. + """ + + if not lock: + if self.state['aspectLocked'] == False: + return + self.state['aspectLocked'] = False + else: + rect = self.rect() + vr = self.viewRect() + if rect.height() == 0 or vr.width() == 0 or vr.height() == 0: + currentRatio = 1.0 + else: + currentRatio = (rect.width()/float(rect.height())) / (vr.width()/vr.height()) + if ratio is None: + ratio = currentRatio + if self.state['aspectLocked'] == ratio: # nothing to change + return + self.state['aspectLocked'] = ratio + if ratio != currentRatio: ## If this would change the current range, do that now + #self.setRange(0, self.state['viewRange'][0][0], self.state['viewRange'][0][1]) + self.updateViewRange() + + self.updateAutoRange() + self.updateViewRange() + self.sigStateChanged.emit(self) + + def childTransform(self): + """ + Return the transform that maps from child(item in the childGroup) coordinates to local coordinates. + (This maps from inside the viewbox to outside) + """ + if self._matrixNeedsUpdate: + self.updateMatrix() + m = self.childGroup.transform() + #m1 = QtGui.QTransform() + #m1.translate(self.childGroup.pos().x(), self.childGroup.pos().y()) + return m #*m1 + + def mapToView(self, obj): + """Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox""" + m = fn.invertQTransform(self.childTransform()) + return m.map(obj) + + def mapFromView(self, obj): + """Maps from the coordinate system displayed inside the ViewBox to the local coordinates of the ViewBox""" + m = self.childTransform() + return m.map(obj) + + def mapSceneToView(self, obj): + """Maps from scene coordinates to the coordinate system displayed inside the ViewBox""" + return self.mapToView(self.mapFromScene(obj)) + + def mapViewToScene(self, obj): + """Maps from the coordinate system displayed inside the ViewBox to scene coordinates""" + return self.mapToScene(self.mapFromView(obj)) + + def mapFromItemToView(self, item, obj): + """Maps *obj* from the local coordinate system of *item* to the view coordinates""" + return self.childGroup.mapFromItem(item, obj) + #return self.mapSceneToView(item.mapToScene(obj)) + + def mapFromViewToItem(self, item, obj): + """Maps *obj* from view coordinates to the local coordinate system of *item*.""" + return self.childGroup.mapToItem(item, obj) + #return item.mapFromScene(self.mapViewToScene(obj)) + + def mapViewToDevice(self, obj): + return self.mapToDevice(self.mapFromView(obj)) + + def mapDeviceToView(self, obj): + return self.mapToView(self.mapFromDevice(obj)) + + def viewPixelSize(self): + """Return the (width, height) of a screen pixel in view coordinates.""" + o = self.mapToView(Point(0,0)) + px, py = [Point(self.mapToView(v) - o) for v in self.pixelVectors()] + return (px.length(), py.length()) + + + def itemBoundingRect(self, item): + """Return the bounding rect of the item in view coordinates""" + return self.mapSceneToView(item.sceneBoundingRect()).boundingRect() + + #def viewScale(self): + #vr = self.viewRect() + ##print "viewScale:", self.range + #xd = vr.width() + #yd = vr.height() + #if xd == 0 or yd == 0: + #print "Warning: 0 range in view:", xd, yd + #return np.array([1,1]) + + ##cs = self.canvas().size() + #cs = self.boundingRect() + #scale = np.array([cs.width() / xd, cs.height() / yd]) + ##print "view scale:", scale + #return scale + + def wheelEvent(self, ev, axis=None): + mask = np.array(self.state['mouseEnabled'], dtype=np.float) + if axis is not None and axis >= 0 and axis < len(mask): + mv = mask[axis] + mask[:] = 0 + mask[axis] = mv + s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor + + center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos())) + #center = ev.pos() + + self._resetTarget() + self.scaleBy(s, center) + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + ev.accept() + + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton and self.menuEnabled(): + ev.accept() + self.raiseContextMenu(ev) + + def raiseContextMenu(self, ev): + menu = self.getMenu(ev) + self.scene().addParentContextMenus(self, menu, ev) + menu.popup(ev.screenPos().toPoint()) + + def getMenu(self, ev): + return self.menu + + def getContextMenus(self, event): + return self.menu.actions() if self.menuEnabled() else [] + + def mouseDragEvent(self, ev, axis=None): + ## if axis is specified, event will only affect that axis. + ev.accept() ## we accept all buttons + + pos = ev.pos() + lastPos = ev.lastPos() + dif = pos - lastPos + dif = dif * -1 + + ## Ignore axes if mouse is disabled + mouseEnabled = np.array(self.state['mouseEnabled'], dtype=np.float) + mask = mouseEnabled.copy() + if axis is not None: + mask[1-axis] = 0.0 + + ## Scale or translate based on mouse button + if ev.button() & (QtCore.Qt.LeftButton | QtCore.Qt.MidButton): + if self.state['mouseMode'] == ViewBox.RectMode: + if ev.isFinish(): ## This is the final move in the drag; change the view scale now + #print "finish" + self.rbScaleBox.hide() + #ax = QtCore.QRectF(Point(self.pressPos), Point(self.mousePos)) + ax = QtCore.QRectF(Point(ev.buttonDownPos(ev.button())), Point(pos)) + ax = self.childGroup.mapRectFromParent(ax) + self.showAxRect(ax) + self.axHistoryPointer += 1 + self.axHistory = self.axHistory[:self.axHistoryPointer] + [ax] + else: + ## update shape of scale box + self.updateScaleBox(ev.buttonDownPos(), ev.pos()) + else: + tr = dif*mask + tr = self.mapToView(tr) - self.mapToView(Point(0,0)) + x = tr.x() if mask[0] == 1 else None + y = tr.y() if mask[1] == 1 else None + + self._resetTarget() + if x is not None or y is not None: + self.translateBy(x=x, y=y) + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + elif ev.button() & QtCore.Qt.RightButton: + #print "vb.rightDrag" + if self.state['aspectLocked'] is not False: + mask[0] = 0 + + dif = ev.screenPos() - ev.lastScreenPos() + dif = np.array([dif.x(), dif.y()]) + dif[0] *= -1 + s = ((mask * 0.02) + 1) ** dif + + tr = self.childGroup.transform() + tr = fn.invertQTransform(tr) + + x = s[0] if mouseEnabled[0] == 1 else None + y = s[1] if mouseEnabled[1] == 1 else None + + center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton))) + self._resetTarget() + self.scaleBy(x=x, y=y, center=center) + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + + def keyPressEvent(self, ev): + """ + This routine should capture key presses in the current view box. + Key presses are used only when mouse mode is RectMode + The following events are implemented: + ctrl-A : zooms out to the default "full" view of the plot + ctrl-+ : moves forward in the zooming stack (if it exists) + ctrl-- : moves backward in the zooming stack (if it exists) + + """ + #print ev.key() + #print 'I intercepted a key press, but did not accept it' + + ## not implemented yet ? + #self.keypress.sigkeyPressEvent.emit() + + ev.accept() + if ev.text() == '-': + self.scaleHistory(-1) + elif ev.text() in ['+', '=']: + self.scaleHistory(1) + elif ev.key() == QtCore.Qt.Key_Backspace: + self.scaleHistory(len(self.axHistory)) + else: + ev.ignore() + + def scaleHistory(self, d): + if len(self.axHistory) == 0: + return + ptr = max(0, min(len(self.axHistory)-1, self.axHistoryPointer+d)) + if ptr != self.axHistoryPointer: + self.axHistoryPointer = ptr + self.showAxRect(self.axHistory[ptr]) + + + def updateScaleBox(self, p1, p2): + r = QtCore.QRectF(p1, p2) + r = self.childGroup.mapRectFromParent(r) + self.rbScaleBox.setPos(r.topLeft()) + self.rbScaleBox.resetTransform() + self.rbScaleBox.scale(r.width(), r.height()) + self.rbScaleBox.show() + + def showAxRect(self, ax): + self.setRange(ax.normalized()) # be sure w, h are correct coordinates + self.sigRangeChangedManually.emit(self.state['mouseEnabled']) + + #def mouseRect(self): + #vs = self.viewScale() + #vr = self.state['viewRange'] + ## Convert positions from screen (view) pixel coordinates to axis coordinates + #ax = QtCore.QRectF(self.pressPos[0]/vs[0]+vr[0][0], -(self.pressPos[1]/vs[1]-vr[1][1]), + #(self.mousePos[0]-self.pressPos[0])/vs[0], -(self.mousePos[1]-self.pressPos[1])/vs[1]) + #return(ax) + + def allChildren(self, item=None): + """Return a list of all children and grandchildren of this ViewBox""" + if item is None: + item = self.childGroup + + children = [item] + for ch in item.childItems(): + children.extend(self.allChildren(ch)) + return children + + + + def childrenBounds(self, frac=None, orthoRange=(None,None), items=None): + """Return the bounding range of all children. + [[xmin, xmax], [ymin, ymax]] + Values may be None if there are no specific bounds for an axis. + """ + profiler = debug.Profiler() + if items is None: + items = self.addedItems + + ## measure pixel dimensions in view box + px, py = [v.length() if v is not None else 0 for v in self.childGroup.pixelVectors()] + + ## First collect all boundary information + itemBounds = [] + for item in items: + if not item.isVisible(): + continue + + useX = True + useY = True + + if hasattr(item, 'dataBounds'): + #bounds = self._itemBoundsCache.get(item, None) + #if bounds is None: + if frac is None: + frac = (1.0, 1.0) + xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) + yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) + pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() + if xr is None or (xr[0] is None and xr[1] is None) or np.isnan(xr).any() or np.isinf(xr).any(): + useX = False + xr = (0,0) + if yr is None or (yr[0] is None and yr[1] is None) or np.isnan(yr).any() or np.isinf(yr).any(): + useY = False + yr = (0,0) + + bounds = QtCore.QRectF(xr[0], yr[0], xr[1]-xr[0], yr[1]-yr[0]) + bounds = self.mapFromItemToView(item, bounds).boundingRect() + + if not any([useX, useY]): + continue + + ## If we are ignoring only one axis, we need to check for rotations + if useX != useY: ## != means xor + ang = round(item.transformAngle()) + if ang == 0 or ang == 180: + pass + elif ang == 90 or ang == 270: + useX, useY = useY, useX + else: + ## Item is rotated at non-orthogonal angle, ignore bounds entirely. + ## Not really sure what is the expected behavior in this case. + continue ## need to check for item rotations and decide how best to apply this boundary. + + + itemBounds.append((bounds, useX, useY, pxPad)) + #self._itemBoundsCache[item] = (bounds, useX, useY) + #else: + #bounds, useX, useY = bounds + else: + if int(item.flags() & item.ItemHasNoContents) > 0: + continue + else: + bounds = item.boundingRect() + bounds = self.mapFromItemToView(item, bounds).boundingRect() + itemBounds.append((bounds, True, True, 0)) + + #print itemBounds + + ## determine tentative new range + range = [None, None] + for bounds, useX, useY, px in itemBounds: + if useY: + if range[1] is not None: + range[1] = [min(bounds.top(), range[1][0]), max(bounds.bottom(), range[1][1])] + else: + range[1] = [bounds.top(), bounds.bottom()] + if useX: + if range[0] is not None: + range[0] = [min(bounds.left(), range[0][0]), max(bounds.right(), range[0][1])] + else: + range[0] = [bounds.left(), bounds.right()] + profiler() + + #print "range", range + + ## Now expand any bounds that have a pixel margin + ## This must be done _after_ we have a good estimate of the new range + ## to ensure that the pixel size is roughly accurate. + w = self.width() + h = self.height() + #print "w:", w, "h:", h + if w > 0 and range[0] is not None: + pxSize = (range[0][1] - range[0][0]) / w + for bounds, useX, useY, px in itemBounds: + if px == 0 or not useX: + continue + range[0][0] = min(range[0][0], bounds.left() - px*pxSize) + range[0][1] = max(range[0][1], bounds.right() + px*pxSize) + if h > 0 and range[1] is not None: + pxSize = (range[1][1] - range[1][0]) / h + for bounds, useX, useY, px in itemBounds: + if px == 0 or not useY: + continue + range[1][0] = min(range[1][0], bounds.top() - px*pxSize) + range[1][1] = max(range[1][1], bounds.bottom() + px*pxSize) + + return range + + def childrenBoundingRect(self, *args, **kwds): + range = self.childrenBounds(*args, **kwds) + tr = self.targetRange() + if range[0] is None: + range[0] = tr[0] + if range[1] is None: + range[1] = tr[1] + + bounds = QtCore.QRectF(range[0][0], range[1][0], range[0][1]-range[0][0], range[1][1]-range[1][0]) + return bounds + + def updateViewRange(self, forceX=False, forceY=False): + ## Update viewRange to match targetRange as closely as possible, given + ## aspect ratio constraints. The *force* arguments are used to indicate + ## which axis (if any) should be unchanged when applying constraints. + viewRange = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]] + changed = [False, False] + + #-------- Make correction for aspect ratio constraint ---------- + + # aspect is (widget w/h) / (view range w/h) + aspect = self.state['aspectLocked'] # size ratio / view ratio + tr = self.targetRect() + bounds = self.rect() + if aspect is not False and 0 not in [aspect, tr.height(), bounds.height(), bounds.width()]: + + ## This is the view range aspect ratio we have requested + targetRatio = tr.width() / tr.height() if tr.height() != 0 else 1 + ## This is the view range aspect ratio we need to obey aspect constraint + viewRatio = (bounds.width() / bounds.height() if bounds.height() != 0 else 1) / aspect + viewRatio = 1 if viewRatio == 0 else viewRatio + + # Decide which range to keep unchanged + #print self.name, "aspect:", aspect, "changed:", changed, "auto:", self.state['autoRange'] + if forceX: + ax = 0 + elif forceY: + ax = 1 + else: + # if we are not required to keep a particular axis unchanged, + # then make the entire target range visible + ax = 0 if targetRatio > viewRatio else 1 + + if ax == 0: + ## view range needs to be taller than target + dy = 0.5 * (tr.width() / viewRatio - tr.height()) + if dy != 0: + changed[1] = True + viewRange[1] = [self.state['targetRange'][1][0] - dy, self.state['targetRange'][1][1] + dy] + else: + ## view range needs to be wider than target + dx = 0.5 * (tr.height() * viewRatio - tr.width()) + if dx != 0: + changed[0] = True + viewRange[0] = [self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx] + + + # ----------- Make corrections for view limits ----------- + + limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits']) + minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]] + maxRng = [self.state['limits']['xRange'][1], self.state['limits']['yRange'][1]] + + for axis in [0, 1]: + if limits[axis][0] is None and limits[axis][1] is None and minRng[axis] is None and maxRng[axis] is None: + continue + + # max range cannot be larger than bounds, if they are given + if limits[axis][0] is not None and limits[axis][1] is not None: + if maxRng[axis] is not None: + maxRng[axis] = min(maxRng[axis], limits[axis][1]-limits[axis][0]) + else: + maxRng[axis] = limits[axis][1]-limits[axis][0] + + #print "\nLimits for axis %d: range=%s min=%s max=%s" % (axis, limits[axis], minRng[axis], maxRng[axis]) + #print "Starting range:", viewRange[axis] + + # Apply xRange, yRange + diff = viewRange[axis][1] - viewRange[axis][0] + if maxRng[axis] is not None and diff > maxRng[axis]: + delta = maxRng[axis] - diff + changed[axis] = True + elif minRng[axis] is not None and diff < minRng[axis]: + delta = minRng[axis] - diff + changed[axis] = True + else: + delta = 0 + + viewRange[axis][0] -= delta/2. + viewRange[axis][1] += delta/2. + + #print "after applying min/max:", viewRange[axis] + + # Apply xLimits, yLimits + mn, mx = limits[axis] + if mn is not None and viewRange[axis][0] < mn: + delta = mn - viewRange[axis][0] + viewRange[axis][0] += delta + viewRange[axis][1] += delta + changed[axis] = True + elif mx is not None and viewRange[axis][1] > mx: + delta = mx - viewRange[axis][1] + viewRange[axis][0] += delta + viewRange[axis][1] += delta + changed[axis] = True + + #print "after applying edge limits:", viewRange[axis] + + changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) or (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)] + self.state['viewRange'] = viewRange + + # emit range change signals + if changed[0]: + self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0])) + if changed[1]: + self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1])) + + if any(changed): + self.sigRangeChanged.emit(self, self.state['viewRange']) + self.update() + self._matrixNeedsUpdate = True + + # Inform linked views that the range has changed + for ax in [0, 1]: + if not changed[ax]: + continue + link = self.linkedView(ax) + if link is not None: + link.linkedViewChanged(self, ax) + + def updateMatrix(self, changed=None): + ## Make the childGroup's transform match the requested viewRange. + bounds = self.rect() + + vr = self.viewRect() + if vr.height() == 0 or vr.width() == 0: + return + scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height()) + if not self.state['yInverted']: + scale = scale * Point(1, -1) + if self.state['xInverted']: + scale = scale * Point(-1, 1) + m = QtGui.QTransform() + + ## First center the viewport at 0 + center = bounds.center() + m.translate(center.x(), center.y()) + + ## Now scale and translate properly + m.scale(scale[0], scale[1]) + st = Point(vr.center()) + m.translate(-st[0], -st[1]) + + self.childGroup.setTransform(m) + + self.sigTransformChanged.emit(self) ## segfaults here: 1 + self._matrixNeedsUpdate = False + + def paint(self, p, opt, widget): + self.checkSceneChange() + + if self.border is not None: + bounds = self.shape() + p.setPen(self.border) + #p.fillRect(bounds, QtGui.QColor(0, 0, 0)) + p.drawPath(bounds) + + #p.setPen(fn.mkPen('r')) + #path = QtGui.QPainterPath() + #path.addRect(self.targetRect()) + #tr = self.mapFromView(path) + #p.drawPath(tr) + + def updateBackground(self): + bg = self.state['background'] + if bg is None: + self.background.hide() + else: + self.background.show() + self.background.setBrush(fn.mkBrush(bg)) + + + def updateViewLists(self): + try: + self.window() + except RuntimeError: ## this view has already been deleted; it will probably be collected shortly. + return + + def cmpViews(a, b): + wins = 100 * cmp(a.window() is self.window(), b.window() is self.window()) + alpha = cmp(a.name, b.name) + return wins + alpha + + ## make a sorted list of all named views + nv = list(ViewBox.NamedViews.values()) + #print "new view list:", nv + sortList(nv, cmpViews) ## see pyqtgraph.python2_3.sortList + + if self in nv: + nv.remove(self) + + self.menu.setViewList(nv) + + for ax in [0,1]: + link = self.state['linkedViews'][ax] + if isinstance(link, basestring): ## axis has not been linked yet; see if it's possible now + for v in nv: + if link == v.name: + self.linkView(ax, v) + #print "New view list:", nv + #print "linked views:", self.state['linkedViews'] + + @staticmethod + def updateAllViewLists(): + #print "Update:", ViewBox.AllViews.keys() + #print "Update:", ViewBox.NamedViews.keys() + for v in ViewBox.AllViews: + v.updateViewLists() + + + @staticmethod + def forgetView(vid, name): + if ViewBox is None: ## can happen as python is shutting down + return + if QtGui.QApplication.instance() is None: + return + ## Called with ID and name of view (the view itself is no longer available) + for v in list(ViewBox.AllViews.keys()): + if id(v) == vid: + ViewBox.AllViews.pop(v) + break + ViewBox.NamedViews.pop(name, None) + ViewBox.updateAllViewLists() + + @staticmethod + def quit(): + ## called when the application is about to exit. + ## this disables all callbacks, which might otherwise generate errors if invoked during exit. + for k in ViewBox.AllViews: + if isQObjectAlive(k) and getConfigOption('crashWarning'): + sys.stderr.write('Warning: ViewBox should be closed before application exit.\n') + + try: + k.destroyed.disconnect() + except RuntimeError: ## signal is already disconnected. + pass + except TypeError: ## view has already been deleted (?) + pass + except AttributeError: # PySide has deleted signal + pass + + def locate(self, item, timeout=3.0, children=False): + """ + Temporarily display the bounding rect of an item and lines connecting to the center of the view. + This is useful for determining the location of items that may be out of the range of the ViewBox. + if allChildren is True, then the bounding rect of all item's children will be shown instead. + """ + self.clearLocate() + + if item.scene() is not self.scene(): + raise Exception("Item does not share a scene with this ViewBox.") + + c = self.viewRect().center() + if children: + br = self.mapFromItemToView(item, item.childrenBoundingRect()).boundingRect() + else: + br = self.mapFromItemToView(item, item.boundingRect()).boundingRect() + + g = ItemGroup() + g.setParentItem(self.childGroup) + self.locateGroup = g + g.box = QtGui.QGraphicsRectItem(br) + g.box.setParentItem(g) + g.lines = [] + for p in (br.topLeft(), br.bottomLeft(), br.bottomRight(), br.topRight()): + line = QtGui.QGraphicsLineItem(c.x(), c.y(), p.x(), p.y()) + line.setParentItem(g) + g.lines.append(line) + + for item in g.childItems(): + item.setPen(fn.mkPen(color='y', width=3)) + g.setZValue(1000000) + + if children: + g.path = QtGui.QGraphicsPathItem(g.childrenShape()) + else: + g.path = QtGui.QGraphicsPathItem(g.shape()) + g.path.setParentItem(g) + g.path.setPen(fn.mkPen('g')) + g.path.setZValue(100) + + QtCore.QTimer.singleShot(timeout*1000, self.clearLocate) + + def clearLocate(self): + if self.locateGroup is None: + return + self.scene().removeItem(self.locateGroup) + self.locateGroup = None + +from .ViewBoxMenu import ViewBoxMenu diff --git a/papi/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py b/papi/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py new file mode 100644 index 00000000..0e7d7912 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py @@ -0,0 +1,267 @@ +from ...Qt import QtCore, QtGui, USE_PYSIDE +from ...python2_3 import asUnicode +from ...WidgetGroup import WidgetGroup + +if USE_PYSIDE: + from .axisCtrlTemplate_pyside import Ui_Form as AxisCtrlTemplate +else: + from .axisCtrlTemplate_pyqt import Ui_Form as AxisCtrlTemplate + +import weakref + +class ViewBoxMenu(QtGui.QMenu): + def __init__(self, view): + QtGui.QMenu.__init__(self) + + self.view = weakref.ref(view) ## keep weakref to view to avoid circular reference (don't know why, but this prevents the ViewBox from being collected) + self.valid = False ## tells us whether the ui needs to be updated + self.viewMap = weakref.WeakValueDictionary() ## weakrefs to all views listed in the link combos + + self.setTitle("ViewBox options") + self.viewAll = QtGui.QAction("View All", self) + self.viewAll.triggered.connect(self.autoRange) + self.addAction(self.viewAll) + + self.axes = [] + self.ctrl = [] + self.widgetGroups = [] + self.dv = QtGui.QDoubleValidator(self) + for axis in 'XY': + m = QtGui.QMenu() + m.setTitle("%s Axis" % axis) + w = QtGui.QWidget() + ui = AxisCtrlTemplate() + ui.setupUi(w) + a = QtGui.QWidgetAction(self) + a.setDefaultWidget(w) + m.addAction(a) + self.addMenu(m) + self.axes.append(m) + self.ctrl.append(ui) + wg = WidgetGroup(w) + self.widgetGroups.append(w) + + connects = [ + (ui.mouseCheck.toggled, 'MouseToggled'), + (ui.manualRadio.clicked, 'ManualClicked'), + (ui.minText.editingFinished, 'MinTextChanged'), + (ui.maxText.editingFinished, 'MaxTextChanged'), + (ui.autoRadio.clicked, 'AutoClicked'), + (ui.autoPercentSpin.valueChanged, 'AutoSpinChanged'), + (ui.linkCombo.currentIndexChanged, 'LinkComboChanged'), + (ui.autoPanCheck.toggled, 'AutoPanToggled'), + (ui.visibleOnlyCheck.toggled, 'VisibleOnlyToggled') + ] + + for sig, fn in connects: + sig.connect(getattr(self, axis.lower()+fn)) + + self.ctrl[0].invertCheck.toggled.connect(self.xInvertToggled) + self.ctrl[1].invertCheck.toggled.connect(self.yInvertToggled) + ## exporting is handled by GraphicsScene now + #self.export = QtGui.QMenu("Export") + #self.setExportMethods(view.exportMethods) + #self.addMenu(self.export) + + self.leftMenu = QtGui.QMenu("Mouse Mode") + group = QtGui.QActionGroup(self) + + # This does not work! QAction _must_ be initialized with a permanent + # object as the parent or else it may be collected prematurely. + #pan = self.leftMenu.addAction("3 button", self.set3ButtonMode) + #zoom = self.leftMenu.addAction("1 button", self.set1ButtonMode) + pan = QtGui.QAction("3 button", self.leftMenu) + zoom = QtGui.QAction("1 button", self.leftMenu) + self.leftMenu.addAction(pan) + self.leftMenu.addAction(zoom) + pan.triggered.connect(self.set3ButtonMode) + zoom.triggered.connect(self.set1ButtonMode) + + pan.setCheckable(True) + zoom.setCheckable(True) + pan.setActionGroup(group) + zoom.setActionGroup(group) + self.mouseModes = [pan, zoom] + self.addMenu(self.leftMenu) + + self.view().sigStateChanged.connect(self.viewStateChanged) + + self.updateState() + + def setExportMethods(self, methods): + self.exportMethods = methods + self.export.clear() + for opt, fn in methods.items(): + self.export.addAction(opt, self.exportMethod) + + + def viewStateChanged(self): + self.valid = False + if self.ctrl[0].minText.isVisible() or self.ctrl[1].minText.isVisible(): + self.updateState() + + def updateState(self): + ## Something about the viewbox has changed; update the menu GUI + + state = self.view().getState(copy=False) + if state['mouseMode'] == ViewBox.PanMode: + self.mouseModes[0].setChecked(True) + else: + self.mouseModes[1].setChecked(True) + + for i in [0,1]: # x, y + tr = state['targetRange'][i] + self.ctrl[i].minText.setText("%0.5g" % tr[0]) + self.ctrl[i].maxText.setText("%0.5g" % tr[1]) + if state['autoRange'][i] is not False: + self.ctrl[i].autoRadio.setChecked(True) + if state['autoRange'][i] is not True: + self.ctrl[i].autoPercentSpin.setValue(state['autoRange'][i]*100) + else: + self.ctrl[i].manualRadio.setChecked(True) + self.ctrl[i].mouseCheck.setChecked(state['mouseEnabled'][i]) + + ## Update combo to show currently linked view + c = self.ctrl[i].linkCombo + c.blockSignals(True) + try: + view = state['linkedViews'][i] ## will always be string or None + if view is None: + view = '' + + ind = c.findText(view) + + if ind == -1: + ind = 0 + c.setCurrentIndex(ind) + finally: + c.blockSignals(False) + + self.ctrl[i].autoPanCheck.setChecked(state['autoPan'][i]) + self.ctrl[i].visibleOnlyCheck.setChecked(state['autoVisibleOnly'][i]) + xy = ['x', 'y'][i] + self.ctrl[i].invertCheck.setChecked(state.get(xy+'Inverted', False)) + + self.valid = True + + def popup(self, *args): + if not self.valid: + self.updateState() + QtGui.QMenu.popup(self, *args) + + def autoRange(self): + self.view().autoRange() ## don't let signal call this directly--it'll add an unwanted argument + + def xMouseToggled(self, b): + self.view().setMouseEnabled(x=b) + + def xManualClicked(self): + self.view().enableAutoRange(ViewBox.XAxis, False) + + def xMinTextChanged(self): + self.ctrl[0].manualRadio.setChecked(True) + self.view().setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0) + + def xMaxTextChanged(self): + self.ctrl[0].manualRadio.setChecked(True) + self.view().setXRange(float(self.ctrl[0].minText.text()), float(self.ctrl[0].maxText.text()), padding=0) + + def xAutoClicked(self): + val = self.ctrl[0].autoPercentSpin.value() * 0.01 + self.view().enableAutoRange(ViewBox.XAxis, val) + + def xAutoSpinChanged(self, val): + self.ctrl[0].autoRadio.setChecked(True) + self.view().enableAutoRange(ViewBox.XAxis, val*0.01) + + def xLinkComboChanged(self, ind): + self.view().setXLink(str(self.ctrl[0].linkCombo.currentText())) + + def xAutoPanToggled(self, b): + self.view().setAutoPan(x=b) + + def xVisibleOnlyToggled(self, b): + self.view().setAutoVisible(x=b) + + + def yMouseToggled(self, b): + self.view().setMouseEnabled(y=b) + + def yManualClicked(self): + self.view().enableAutoRange(ViewBox.YAxis, False) + + def yMinTextChanged(self): + self.ctrl[1].manualRadio.setChecked(True) + self.view().setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0) + + def yMaxTextChanged(self): + self.ctrl[1].manualRadio.setChecked(True) + self.view().setYRange(float(self.ctrl[1].minText.text()), float(self.ctrl[1].maxText.text()), padding=0) + + def yAutoClicked(self): + val = self.ctrl[1].autoPercentSpin.value() * 0.01 + self.view().enableAutoRange(ViewBox.YAxis, val) + + def yAutoSpinChanged(self, val): + self.ctrl[1].autoRadio.setChecked(True) + self.view().enableAutoRange(ViewBox.YAxis, val*0.01) + + def yLinkComboChanged(self, ind): + self.view().setYLink(str(self.ctrl[1].linkCombo.currentText())) + + def yAutoPanToggled(self, b): + self.view().setAutoPan(y=b) + + def yVisibleOnlyToggled(self, b): + self.view().setAutoVisible(y=b) + + def yInvertToggled(self, b): + self.view().invertY(b) + + def xInvertToggled(self, b): + self.view().invertX(b) + + def exportMethod(self): + act = self.sender() + self.exportMethods[str(act.text())]() + + def set3ButtonMode(self): + self.view().setLeftButtonAction('pan') + + def set1ButtonMode(self): + self.view().setLeftButtonAction('rect') + + def setViewList(self, views): + names = [''] + self.viewMap.clear() + + ## generate list of views to show in the link combo + for v in views: + name = v.name + if name is None: ## unnamed views do not show up in the view list (although they are linkable) + continue + names.append(name) + self.viewMap[name] = v + + for i in [0,1]: + c = self.ctrl[i].linkCombo + current = asUnicode(c.currentText()) + c.blockSignals(True) + changed = True + try: + c.clear() + for name in names: + c.addItem(name) + if name == current: + changed = False + c.setCurrentIndex(c.count()-1) + finally: + c.blockSignals(False) + + if changed: + c.setCurrentIndex(0) + c.currentIndexChanged.emit(c.currentIndex()) + +from .ViewBox import ViewBox + + diff --git a/papi/pyqtgraph/graphicsItems/ViewBox/__init__.py b/papi/pyqtgraph/graphicsItems/ViewBox/__init__.py new file mode 100644 index 00000000..685a314d --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ViewBox/__init__.py @@ -0,0 +1 @@ +from .ViewBox import ViewBox diff --git a/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui new file mode 100644 index 00000000..297fce75 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui @@ -0,0 +1,161 @@ + + + Form + + + + 0 + 0 + 186 + 154 + + + + + 200 + 16777215 + + + + Form + + + + 0 + + + 0 + + + + + Link Axis: + + + + + + + <html><head/><body><p>Links this axis with another view. When linked, both views will display the same data range.</p></body></html> + + + QComboBox::AdjustToContents + + + + + + + true + + + <html><head/><body><p>Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.</p></body></html> + + + % + + + 1 + + + 100 + + + 1 + + + 100 + + + + + + + <html><head/><body><p>Automatically resize this axis whenever the displayed data is changed.</p></body></html> + + + Auto + + + true + + + + + + + <html><head/><body><p>Set the range for this axis manually. This disables automatic scaling. </p></body></html> + + + Manual + + + + + + + <html><head/><body><p>Minimum value to display for this axis.</p></body></html> + + + 0 + + + + + + + <html><head/><body><p>Maximum value to display for this axis.</p></body></html> + + + 0 + + + + + + + <html><head/><body><p>Inverts the display of this axis. (+y points downward instead of upward)</p></body></html> + + + Invert Axis + + + + + + + <html><head/><body><p>Enables mouse interaction (panning, scaling) for this axis.</p></body></html> + + + Mouse Enabled + + + true + + + + + + + <html><head/><body><p>When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.</p></body></html> + + + Visible Data Only + + + + + + + <html><head/><body><p>When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.</p></body></html> + + + Auto Pan Only + + + + + + + + diff --git a/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py new file mode 100644 index 00000000..d8ef1925 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui' +# +# Created: Mon Dec 23 10:10:51 2013 +# by: PyQt4 UI code generator 4.10 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(186, 154) + Form.setMaximumSize(QtCore.QSize(200, 16777215)) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setMargin(0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.label = QtGui.QLabel(Form) + self.label.setObjectName(_fromUtf8("label")) + self.gridLayout.addWidget(self.label, 7, 0, 1, 2) + self.linkCombo = QtGui.QComboBox(Form) + self.linkCombo.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) + self.linkCombo.setObjectName(_fromUtf8("linkCombo")) + self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) + self.autoPercentSpin = QtGui.QSpinBox(Form) + self.autoPercentSpin.setEnabled(True) + self.autoPercentSpin.setMinimum(1) + self.autoPercentSpin.setMaximum(100) + self.autoPercentSpin.setSingleStep(1) + self.autoPercentSpin.setProperty("value", 100) + self.autoPercentSpin.setObjectName(_fromUtf8("autoPercentSpin")) + self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) + self.autoRadio = QtGui.QRadioButton(Form) + self.autoRadio.setChecked(True) + self.autoRadio.setObjectName(_fromUtf8("autoRadio")) + self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) + self.manualRadio = QtGui.QRadioButton(Form) + self.manualRadio.setObjectName(_fromUtf8("manualRadio")) + self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) + self.minText = QtGui.QLineEdit(Form) + self.minText.setObjectName(_fromUtf8("minText")) + self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) + self.maxText = QtGui.QLineEdit(Form) + self.maxText.setObjectName(_fromUtf8("maxText")) + self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) + self.invertCheck = QtGui.QCheckBox(Form) + self.invertCheck.setObjectName(_fromUtf8("invertCheck")) + self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) + self.mouseCheck = QtGui.QCheckBox(Form) + self.mouseCheck.setChecked(True) + self.mouseCheck.setObjectName(_fromUtf8("mouseCheck")) + self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) + self.visibleOnlyCheck = QtGui.QCheckBox(Form) + self.visibleOnlyCheck.setObjectName(_fromUtf8("visibleOnlyCheck")) + self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) + self.autoPanCheck = QtGui.QCheckBox(Form) + self.autoPanCheck.setObjectName(_fromUtf8("autoPanCheck")) + self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(_translate("Form", "Form", None)) + self.label.setText(_translate("Form", "Link Axis:", None)) + self.linkCombo.setToolTip(_translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

", None)) + self.autoPercentSpin.setToolTip(_translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

", None)) + self.autoPercentSpin.setSuffix(_translate("Form", "%", None)) + self.autoRadio.setToolTip(_translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

", None)) + self.autoRadio.setText(_translate("Form", "Auto", None)) + self.manualRadio.setToolTip(_translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

", None)) + self.manualRadio.setText(_translate("Form", "Manual", None)) + self.minText.setToolTip(_translate("Form", "

Minimum value to display for this axis.

", None)) + self.minText.setText(_translate("Form", "0", None)) + self.maxText.setToolTip(_translate("Form", "

Maximum value to display for this axis.

", None)) + self.maxText.setText(_translate("Form", "0", None)) + self.invertCheck.setToolTip(_translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

", None)) + self.invertCheck.setText(_translate("Form", "Invert Axis", None)) + self.mouseCheck.setToolTip(_translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

", None)) + self.mouseCheck.setText(_translate("Form", "Mouse Enabled", None)) + self.visibleOnlyCheck.setToolTip(_translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

", None)) + self.visibleOnlyCheck.setText(_translate("Form", "Visible Data Only", None)) + self.autoPanCheck.setToolTip(_translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

", None)) + self.autoPanCheck.setText(_translate("Form", "Auto Pan Only", None)) + diff --git a/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py new file mode 100644 index 00000000..9ddeb5d1 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui' +# +# Created: Mon Dec 23 10:10:51 2013 +# by: pyside-uic 0.2.14 running on PySide 1.1.2 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(186, 154) + Form.setMaximumSize(QtCore.QSize(200, 16777215)) + self.gridLayout = QtGui.QGridLayout(Form) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setSpacing(0) + self.gridLayout.setObjectName("gridLayout") + self.label = QtGui.QLabel(Form) + self.label.setObjectName("label") + self.gridLayout.addWidget(self.label, 7, 0, 1, 2) + self.linkCombo = QtGui.QComboBox(Form) + self.linkCombo.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) + self.linkCombo.setObjectName("linkCombo") + self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2) + self.autoPercentSpin = QtGui.QSpinBox(Form) + self.autoPercentSpin.setEnabled(True) + self.autoPercentSpin.setMinimum(1) + self.autoPercentSpin.setMaximum(100) + self.autoPercentSpin.setSingleStep(1) + self.autoPercentSpin.setProperty("value", 100) + self.autoPercentSpin.setObjectName("autoPercentSpin") + self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2) + self.autoRadio = QtGui.QRadioButton(Form) + self.autoRadio.setChecked(True) + self.autoRadio.setObjectName("autoRadio") + self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2) + self.manualRadio = QtGui.QRadioButton(Form) + self.manualRadio.setObjectName("manualRadio") + self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2) + self.minText = QtGui.QLineEdit(Form) + self.minText.setObjectName("minText") + self.gridLayout.addWidget(self.minText, 1, 2, 1, 1) + self.maxText = QtGui.QLineEdit(Form) + self.maxText.setObjectName("maxText") + self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1) + self.invertCheck = QtGui.QCheckBox(Form) + self.invertCheck.setObjectName("invertCheck") + self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4) + self.mouseCheck = QtGui.QCheckBox(Form) + self.mouseCheck.setChecked(True) + self.mouseCheck.setObjectName("mouseCheck") + self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4) + self.visibleOnlyCheck = QtGui.QCheckBox(Form) + self.visibleOnlyCheck.setObjectName("visibleOnlyCheck") + self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2) + self.autoPanCheck = QtGui.QCheckBox(Form) + self.autoPanCheck.setObjectName("autoPanCheck") + self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate("Form", "Link Axis:", None, QtGui.QApplication.UnicodeUTF8)) + self.linkCombo.setToolTip(QtGui.QApplication.translate("Form", "

Links this axis with another view. When linked, both views will display the same data range.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPercentSpin.setToolTip(QtGui.QApplication.translate("Form", "

Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPercentSpin.setSuffix(QtGui.QApplication.translate("Form", "%", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRadio.setToolTip(QtGui.QApplication.translate("Form", "

Automatically resize this axis whenever the displayed data is changed.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoRadio.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.manualRadio.setToolTip(QtGui.QApplication.translate("Form", "

Set the range for this axis manually. This disables automatic scaling.

", None, QtGui.QApplication.UnicodeUTF8)) + self.manualRadio.setText(QtGui.QApplication.translate("Form", "Manual", None, QtGui.QApplication.UnicodeUTF8)) + self.minText.setToolTip(QtGui.QApplication.translate("Form", "

Minimum value to display for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.minText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) + self.maxText.setToolTip(QtGui.QApplication.translate("Form", "

Maximum value to display for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.maxText.setText(QtGui.QApplication.translate("Form", "0", None, QtGui.QApplication.UnicodeUTF8)) + self.invertCheck.setToolTip(QtGui.QApplication.translate("Form", "

Inverts the display of this axis. (+y points downward instead of upward)

", None, QtGui.QApplication.UnicodeUTF8)) + self.invertCheck.setText(QtGui.QApplication.translate("Form", "Invert Axis", None, QtGui.QApplication.UnicodeUTF8)) + self.mouseCheck.setToolTip(QtGui.QApplication.translate("Form", "

Enables mouse interaction (panning, scaling) for this axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.mouseCheck.setText(QtGui.QApplication.translate("Form", "Mouse Enabled", None, QtGui.QApplication.UnicodeUTF8)) + self.visibleOnlyCheck.setToolTip(QtGui.QApplication.translate("Form", "

When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.

", None, QtGui.QApplication.UnicodeUTF8)) + self.visibleOnlyCheck.setText(QtGui.QApplication.translate("Form", "Visible Data Only", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPanCheck.setToolTip(QtGui.QApplication.translate("Form", "

When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.

", None, QtGui.QApplication.UnicodeUTF8)) + self.autoPanCheck.setText(QtGui.QApplication.translate("Form", "Auto Pan Only", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/pyqtgraph/graphicsItems/ViewBox/tests/test_ViewBox.py b/papi/pyqtgraph/graphicsItems/ViewBox/tests/test_ViewBox.py new file mode 100644 index 00000000..f1063e7f --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/ViewBox/tests/test_ViewBox.py @@ -0,0 +1,85 @@ +#import PySide +import pyqtgraph as pg + +app = pg.mkQApp() +qtest = pg.Qt.QtTest.QTest + +def assertMapping(vb, r1, r2): + assert vb.mapFromView(r1.topLeft()) == r2.topLeft() + assert vb.mapFromView(r1.bottomLeft()) == r2.bottomLeft() + assert vb.mapFromView(r1.topRight()) == r2.topRight() + assert vb.mapFromView(r1.bottomRight()) == r2.bottomRight() + +def test_ViewBox(): + global app, win, vb + QRectF = pg.QtCore.QRectF + + win = pg.GraphicsWindow() + win.ci.layout.setContentsMargins(0,0,0,0) + win.resize(200, 200) + win.show() + vb = win.addViewBox() + + # set range before viewbox is shown + vb.setRange(xRange=[0, 10], yRange=[0, 10], padding=0) + + # required to make mapFromView work properly. + qtest.qWaitForWindowShown(win) + + g = pg.GridItem() + vb.addItem(g) + + app.processEvents() + + w = vb.geometry().width() + h = vb.geometry().height() + view1 = QRectF(0, 0, 10, 10) + size1 = QRectF(0, h, w, -h) + assertMapping(vb, view1, size1) + + # test resize + win.resize(400, 400) + app.processEvents() + w = vb.geometry().width() + h = vb.geometry().height() + size1 = QRectF(0, h, w, -h) + assertMapping(vb, view1, size1) + + # now lock aspect + vb.setAspectLocked() + + # test wide resize + win.resize(800, 400) + app.processEvents() + w = vb.geometry().width() + h = vb.geometry().height() + view1 = QRectF(-5, 0, 20, 10) + size1 = QRectF(0, h, w, -h) + assertMapping(vb, view1, size1) + + # test tall resize + win.resize(400, 800) + app.processEvents() + w = vb.geometry().width() + h = vb.geometry().height() + view1 = QRectF(0, -5, 10, 20) + size1 = QRectF(0, h, w, -h) + assertMapping(vb, view1, size1) + + # test limits + resize (aspect ratio constraint has priority over limits + win.resize(400, 400) + app.processEvents() + vb.setLimits(xMin=0, xMax=10, yMin=0, yMax=10) + win.resize(800, 400) + app.processEvents() + w = vb.geometry().width() + h = vb.geometry().height() + view1 = QRectF(-5, 0, 20, 10) + size1 = QRectF(0, h, w, -h) + assertMapping(vb, view1, size1) + + +if __name__ == '__main__': + import user,sys + test_ViewBox() + \ No newline at end of file diff --git a/papi/pyqtgraph/graphicsItems/__init__.py b/papi/pyqtgraph/graphicsItems/__init__.py new file mode 100644 index 00000000..8e411816 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/__init__.py @@ -0,0 +1,21 @@ +### just import everything from sub-modules + +#import os + +#d = os.path.split(__file__)[0] +#files = [] +#for f in os.listdir(d): + #if os.path.isdir(os.path.join(d, f)): + #files.append(f) + #elif f[-3:] == '.py' and f != '__init__.py': + #files.append(f[:-3]) + +#for modName in files: + #mod = __import__(modName, globals(), locals(), fromlist=['*']) + #if hasattr(mod, '__all__'): + #names = mod.__all__ + #else: + #names = [n for n in dir(mod) if n[0] != '_'] + #for k in names: + ##print modName, k + #globals()[k] = getattr(mod, k) diff --git a/papi/pyqtgraph/graphicsItems/tests/test_GraphicsItem.py b/papi/pyqtgraph/graphicsItems/tests/test_GraphicsItem.py new file mode 100644 index 00000000..112dd4d5 --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/tests/test_GraphicsItem.py @@ -0,0 +1,47 @@ +import gc +import weakref +try: + import faulthandler + faulthandler.enable() +except ImportError: + pass + +import pyqtgraph as pg +pg.mkQApp() + +def test_getViewWidget(): + view = pg.PlotWidget() + vref = weakref.ref(view) + item = pg.InfiniteLine() + view.addItem(item) + assert item.getViewWidget() is view + del view + gc.collect() + assert vref() is None + assert item.getViewWidget() is None + +def test_getViewWidget_deleted(): + view = pg.PlotWidget() + item = pg.InfiniteLine() + view.addItem(item) + assert item.getViewWidget() is view + + # Arrange to have Qt automatically delete the view widget + obj = pg.QtGui.QWidget() + view.setParent(obj) + del obj + gc.collect() + + assert not pg.Qt.isQObjectAlive(view) + assert item.getViewWidget() is None + + +#if __name__ == '__main__': + #view = pg.PlotItem() + #vref = weakref.ref(view) + #item = pg.InfiniteLine() + #view.addItem(item) + #del view + #gc.collect() + + \ No newline at end of file diff --git a/papi/pyqtgraph/graphicsItems/tests/test_ScatterPlotItem.py b/papi/pyqtgraph/graphicsItems/tests/test_ScatterPlotItem.py new file mode 100644 index 00000000..8b0ebc8f --- /dev/null +++ b/papi/pyqtgraph/graphicsItems/tests/test_ScatterPlotItem.py @@ -0,0 +1,86 @@ +import pyqtgraph as pg +import numpy as np +app = pg.mkQApp() +plot = pg.plot() +app.processEvents() + +# set view range equal to its bounding rect. +# This causes plots to look the same regardless of pxMode. +plot.setRange(rect=plot.boundingRect()) + + +def test_scatterplotitem(): + for i, pxMode in enumerate([True, False]): + for j, useCache in enumerate([True, False]): + s = pg.ScatterPlotItem() + s.opts['useCache'] = useCache + plot.addItem(s) + s.setData(x=np.array([10,40,20,30])+i*100, y=np.array([40,60,10,30])+j*100, pxMode=pxMode) + s.addPoints(x=np.array([60, 70])+i*100, y=np.array([60, 70])+j*100, size=[20, 30]) + + # Test uniform spot updates + s.setSize(10) + s.setBrush('r') + s.setPen('g') + s.setSymbol('+') + app.processEvents() + + # Test list spot updates + s.setSize([10] * 6) + s.setBrush([pg.mkBrush('r')] * 6) + s.setPen([pg.mkPen('g')] * 6) + s.setSymbol(['+'] * 6) + s.setPointData([s] * 6) + app.processEvents() + + # Test array spot updates + s.setSize(np.array([10] * 6)) + s.setBrush(np.array([pg.mkBrush('r')] * 6)) + s.setPen(np.array([pg.mkPen('g')] * 6)) + s.setSymbol(np.array(['+'] * 6)) + s.setPointData(np.array([s] * 6)) + app.processEvents() + + # Test per-spot updates + spot = s.points()[0] + spot.setSize(20) + spot.setBrush('b') + spot.setPen('g') + spot.setSymbol('o') + spot.setData(None) + app.processEvents() + + plot.clear() + + +def test_init_spots(): + spots = [ + {'x': 0, 'y': 1}, + {'pos': (1, 2), 'pen': None, 'brush': None, 'data': 'zzz'}, + ] + s = pg.ScatterPlotItem(spots=spots) + + # Check we can display without errors + plot.addItem(s) + app.processEvents() + plot.clear() + + # check data is correct + spots = s.points() + + defPen = pg.mkPen(pg.getConfigOption('foreground')) + + assert spots[0].pos().x() == 0 + assert spots[0].pos().y() == 1 + assert spots[0].pen() == defPen + assert spots[0].data() is None + + assert spots[1].pos().x() == 1 + assert spots[1].pos().y() == 2 + assert spots[1].pen() == pg.mkPen(None) + assert spots[1].brush() == pg.mkBrush(None) + assert spots[1].data() == 'zzz' + + +if __name__ == '__main__': + test_scatterplotitem() diff --git a/papi/pyqtgraph/graphicsWindows.py b/papi/pyqtgraph/graphicsWindows.py new file mode 100644 index 00000000..1aa3f3f4 --- /dev/null +++ b/papi/pyqtgraph/graphicsWindows.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +""" +graphicsWindows.py - Convenience classes which create a new window with PlotWidget or ImageView. +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from .Qt import QtCore, QtGui +from .widgets.PlotWidget import * +from .imageview import * +from .widgets.GraphicsLayoutWidget import GraphicsLayoutWidget +from .widgets.GraphicsView import GraphicsView +QAPP = None + +def mkQApp(): + if QtGui.QApplication.instance() is None: + global QAPP + QAPP = QtGui.QApplication([]) + + +class GraphicsWindow(GraphicsLayoutWidget): + """ + Convenience subclass of :class:`GraphicsLayoutWidget + `. This class is intended for use from + the interactive python prompt. + """ + def __init__(self, title=None, size=(800,600), **kargs): + mkQApp() + GraphicsLayoutWidget.__init__(self, **kargs) + self.resize(*size) + if title is not None: + self.setWindowTitle(title) + self.show() + + +class TabWindow(QtGui.QMainWindow): + def __init__(self, title=None, size=(800,600)): + mkQApp() + QtGui.QMainWindow.__init__(self) + self.resize(*size) + self.cw = QtGui.QTabWidget() + self.setCentralWidget(self.cw) + if title is not None: + self.setWindowTitle(title) + self.show() + + def __getattr__(self, attr): + if hasattr(self.cw, attr): + return getattr(self.cw, attr) + else: + raise NameError(attr) + + +class PlotWindow(PlotWidget): + def __init__(self, title=None, **kargs): + mkQApp() + self.win = QtGui.QMainWindow() + PlotWidget.__init__(self, **kargs) + self.win.setCentralWidget(self) + for m in ['resize']: + setattr(self, m, getattr(self.win, m)) + if title is not None: + self.win.setWindowTitle(title) + self.win.show() + + +class ImageWindow(ImageView): + def __init__(self, *args, **kargs): + mkQApp() + self.win = QtGui.QMainWindow() + self.win.resize(800,600) + if 'title' in kargs: + self.win.setWindowTitle(kargs['title']) + del kargs['title'] + ImageView.__init__(self, self.win) + if len(args) > 0 or len(kargs) > 0: + self.setImage(*args, **kargs) + self.win.setCentralWidget(self) + for m in ['resize']: + setattr(self, m, getattr(self.win, m)) + #for m in ['setImage', 'autoRange', 'addItem', 'removeItem', 'blackLevel', 'whiteLevel', 'imageItem']: + #setattr(self, m, getattr(self.cw, m)) + self.win.show() diff --git a/papi/pyqtgraph/imageview/ImageView.py b/papi/pyqtgraph/imageview/ImageView.py new file mode 100644 index 00000000..65252cfe --- /dev/null +++ b/papi/pyqtgraph/imageview/ImageView.py @@ -0,0 +1,720 @@ +# -*- coding: utf-8 -*- +""" +ImageView.py - Widget for basic image dispay and analysis +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +Widget used for displaying 2D or 3D data. Features: + - float or int (including 16-bit int) image display via ImageItem + - zoom/pan via GraphicsView + - black/white level controls + - time slider for 3D data sets + - ROI plotting + - Image normalization through a variety of methods +""" +import os, sys +import numpy as np + +from ..Qt import QtCore, QtGui, USE_PYSIDE +if USE_PYSIDE: + from .ImageViewTemplate_pyside import * +else: + from .ImageViewTemplate_pyqt import * + +from ..graphicsItems.ImageItem import * +from ..graphicsItems.ROI import * +from ..graphicsItems.LinearRegionItem import * +from ..graphicsItems.InfiniteLine import * +from ..graphicsItems.ViewBox import * +from .. import ptime as ptime +from .. import debug as debug +from ..SignalProxy import SignalProxy + +try: + from bottleneck import nanmin, nanmax +except ImportError: + from numpy import nanmin, nanmax + + +class PlotROI(ROI): + def __init__(self, size): + ROI.__init__(self, pos=[0,0], size=size) #, scaleSnap=True, translateSnap=True) + self.addScaleHandle([1, 1], [0, 0]) + self.addRotateHandle([0, 0], [0.5, 0.5]) + + +class ImageView(QtGui.QWidget): + """ + Widget used for display and analysis of image data. + Implements many features: + + * Displays 2D and 3D image data. For 3D data, a z-axis + slider is displayed allowing the user to select which frame is displayed. + * Displays histogram of image data with movable region defining the dark/light levels + * Editable gradient provides a color lookup table + * Frame slider may also be moved using left/right arrow keys as well as pgup, pgdn, home, and end. + * Basic analysis features including: + + * ROI and embedded plot for measuring image values across frames + * Image normalization / background subtraction + + Basic Usage:: + + imv = pg.ImageView() + imv.show() + imv.setImage(data) + + **Keyboard interaction** + + * left/right arrows step forward/backward 1 frame when pressed, + seek at 20fps when held. + * up/down arrows seek at 100fps + * pgup/pgdn seek at 1000fps + * home/end seek immediately to the first/last frame + * space begins playing frames. If time values (in seconds) are given + for each frame, then playback is in realtime. + """ + sigTimeChanged = QtCore.Signal(object, object) + sigProcessingChanged = QtCore.Signal(object) + + def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, *args): + """ + By default, this class creates an :class:`ImageItem ` to display image data + and a :class:`ViewBox ` to contain the ImageItem. + + ============= ========================================================= + **Arguments** + parent (QWidget) Specifies the parent widget to which + this ImageView will belong. If None, then the ImageView + is created with no parent. + name (str) The name used to register both the internal ViewBox + and the PlotItem used to display ROI data. See the *name* + argument to :func:`ViewBox.__init__() + `. + view (ViewBox or PlotItem) If specified, this will be used + as the display area that contains the displayed image. + Any :class:`ViewBox `, + :class:`PlotItem `, or other + compatible object is acceptable. + imageItem (ImageItem) If specified, this object will be used to + display the image. Must be an instance of ImageItem + or other compatible object. + ============= ========================================================= + + Note: to display axis ticks inside the ImageView, instantiate it + with a PlotItem instance as its view:: + + pg.ImageView(view=pg.PlotItem()) + """ + QtGui.QWidget.__init__(self, parent, *args) + self.levelMax = 4096 + self.levelMin = 0 + self.name = name + self.image = None + self.axes = {} + self.imageDisp = None + self.ui = Ui_Form() + self.ui.setupUi(self) + self.scene = self.ui.graphicsView.scene() + + self.ignoreTimeLine = False + + if view is None: + self.view = ViewBox() + else: + self.view = view + self.ui.graphicsView.setCentralItem(self.view) + self.view.setAspectLocked(True) + self.view.invertY() + + if imageItem is None: + self.imageItem = ImageItem() + else: + self.imageItem = imageItem + self.view.addItem(self.imageItem) + self.currentIndex = 0 + + self.ui.histogram.setImageItem(self.imageItem) + + self.menu = None + + self.ui.normGroup.hide() + + self.roi = PlotROI(10) + self.roi.setZValue(20) + self.view.addItem(self.roi) + self.roi.hide() + self.normRoi = PlotROI(10) + self.normRoi.setPen(QtGui.QPen(QtGui.QColor(255,255,0))) + self.normRoi.setZValue(20) + self.view.addItem(self.normRoi) + self.normRoi.hide() + self.roiCurve = self.ui.roiPlot.plot() + self.timeLine = InfiniteLine(0, movable=True) + self.timeLine.setPen(QtGui.QPen(QtGui.QColor(255, 255, 0, 200))) + self.timeLine.setZValue(1) + self.ui.roiPlot.addItem(self.timeLine) + self.ui.splitter.setSizes([self.height()-35, 35]) + self.ui.roiPlot.hideAxis('left') + + self.keysPressed = {} + self.playTimer = QtCore.QTimer() + self.playRate = 0 + self.lastPlayTime = 0 + + self.normRgn = LinearRegionItem() + self.normRgn.setZValue(0) + self.ui.roiPlot.addItem(self.normRgn) + self.normRgn.hide() + + ## wrap functions from view box + for fn in ['addItem', 'removeItem']: + setattr(self, fn, getattr(self.view, fn)) + + ## wrap functions from histogram + for fn in ['setHistogramRange', 'autoHistogramRange', 'getLookupTable', 'getLevels']: + setattr(self, fn, getattr(self.ui.histogram, fn)) + + self.timeLine.sigPositionChanged.connect(self.timeLineChanged) + self.ui.roiBtn.clicked.connect(self.roiClicked) + self.roi.sigRegionChanged.connect(self.roiChanged) + #self.ui.normBtn.toggled.connect(self.normToggled) + self.ui.menuBtn.clicked.connect(self.menuClicked) + self.ui.normDivideRadio.clicked.connect(self.normRadioChanged) + self.ui.normSubtractRadio.clicked.connect(self.normRadioChanged) + self.ui.normOffRadio.clicked.connect(self.normRadioChanged) + self.ui.normROICheck.clicked.connect(self.updateNorm) + self.ui.normFrameCheck.clicked.connect(self.updateNorm) + self.ui.normTimeRangeCheck.clicked.connect(self.updateNorm) + self.playTimer.timeout.connect(self.timeout) + + self.normProxy = SignalProxy(self.normRgn.sigRegionChanged, slot=self.updateNorm) + self.normRoi.sigRegionChangeFinished.connect(self.updateNorm) + + self.ui.roiPlot.registerPlot(self.name + '_ROI') + self.view.register(self.name) + + self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown] + + self.roiClicked() ## initialize roi plot to correct shape / visibility + + def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None, xvals=None, pos=None, scale=None, transform=None, autoHistogramRange=True): + """ + Set the image to be displayed in the widget. + + ================== ======================================================================= + **Arguments:** + img (numpy array) the image to be displayed. + xvals (numpy array) 1D array of z-axis values corresponding to the third axis + in a 3D image. For video, this array should contain the time of each frame. + autoRange (bool) whether to scale/pan the view to fit the image. + autoLevels (bool) whether to update the white/black levels to fit the image. + levels (min, max); the white and black level values to use. + axes Dictionary indicating the interpretation for each axis. + This is only needed to override the default guess. Format is:: + + {'t':0, 'x':1, 'y':2, 'c':3}; + + pos Change the position of the displayed image + scale Change the scale of the displayed image + transform Set the transform of the displayed image. This option overrides *pos* + and *scale*. + autoHistogramRange If True, the histogram y-range is automatically scaled to fit the + image data. + ================== ======================================================================= + """ + profiler = debug.Profiler() + + if hasattr(img, 'implements') and img.implements('MetaArray'): + img = img.asarray() + + if not isinstance(img, np.ndarray): + required = ['dtype', 'max', 'min', 'ndim', 'shape', 'size'] + if not all([hasattr(img, attr) for attr in required]): + raise TypeError("Image must be NumPy array or any object " + "that provides compatible attributes/methods:\n" + " %s" % str(required)) + + self.image = img + self.imageDisp = None + + if xvals is not None: + self.tVals = xvals + elif hasattr(img, 'xvals'): + try: + self.tVals = img.xvals(0) + except: + self.tVals = np.arange(img.shape[0]) + else: + self.tVals = np.arange(img.shape[0]) + + profiler() + + if axes is None: + if img.ndim == 2: + self.axes = {'t': None, 'x': 0, 'y': 1, 'c': None} + elif img.ndim == 3: + if img.shape[2] <= 4: + self.axes = {'t': None, 'x': 0, 'y': 1, 'c': 2} + else: + self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': None} + elif img.ndim == 4: + self.axes = {'t': 0, 'x': 1, 'y': 2, 'c': 3} + else: + raise Exception("Can not interpret image with dimensions %s" % (str(img.shape))) + elif isinstance(axes, dict): + self.axes = axes.copy() + elif isinstance(axes, list) or isinstance(axes, tuple): + self.axes = {} + for i in range(len(axes)): + self.axes[axes[i]] = i + else: + raise Exception("Can not interpret axis specification %s. Must be like {'t': 2, 'x': 0, 'y': 1} or ('t', 'x', 'y', 'c')" % (str(axes))) + + for x in ['t', 'x', 'y', 'c']: + self.axes[x] = self.axes.get(x, None) + + profiler() + + self.currentIndex = 0 + self.updateImage(autoHistogramRange=autoHistogramRange) + if levels is None and autoLevels: + self.autoLevels() + if levels is not None: ## this does nothing since getProcessedImage sets these values again. + self.setLevels(*levels) + + if self.ui.roiBtn.isChecked(): + self.roiChanged() + + profiler() + + if self.axes['t'] is not None: + #self.ui.roiPlot.show() + self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max()) + self.timeLine.setValue(0) + #self.ui.roiPlot.setMouseEnabled(False, False) + if len(self.tVals) > 1: + start = self.tVals.min() + stop = self.tVals.max() + abs(self.tVals[-1] - self.tVals[0]) * 0.02 + elif len(self.tVals) == 1: + start = self.tVals[0] - 0.5 + stop = self.tVals[0] + 0.5 + else: + start = 0 + stop = 1 + for s in [self.timeLine, self.normRgn]: + s.setBounds([start, stop]) + #else: + #self.ui.roiPlot.hide() + profiler() + + self.imageItem.resetTransform() + if scale is not None: + self.imageItem.scale(*scale) + if pos is not None: + self.imageItem.setPos(*pos) + if transform is not None: + self.imageItem.setTransform(transform) + + profiler() + + if autoRange: + self.autoRange() + self.roiClicked() + + profiler() + + def clear(self): + self.image = None + self.imageItem.clear() + + def play(self, rate): + """Begin automatically stepping frames forward at the given rate (in fps). + This can also be accessed by pressing the spacebar.""" + #print "play:", rate + self.playRate = rate + if rate == 0: + self.playTimer.stop() + return + + self.lastPlayTime = ptime.time() + if not self.playTimer.isActive(): + self.playTimer.start(16) + + def autoLevels(self): + """Set the min/max intensity levels automatically to match the image data.""" + self.setLevels(self.levelMin, self.levelMax) + + def setLevels(self, min, max): + """Set the min/max (bright and dark) levels.""" + self.ui.histogram.setLevels(min, max) + + def autoRange(self): + """Auto scale and pan the view around the image such that the image fills the view.""" + image = self.getProcessedImage() + self.view.autoRange() + + def getProcessedImage(self): + """Returns the image data after it has been processed by any normalization options in use. + This method also sets the attributes self.levelMin and self.levelMax + to indicate the range of data in the image.""" + if self.imageDisp is None: + image = self.normalize(self.image) + self.imageDisp = image + self.levelMin, self.levelMax = list(map(float, self.quickMinMax(self.imageDisp))) + + return self.imageDisp + + def close(self): + """Closes the widget nicely, making sure to clear the graphics scene and release memory.""" + self.ui.roiPlot.close() + self.ui.graphicsView.close() + self.scene.clear() + del self.image + del self.imageDisp + self.setParent(None) + + def keyPressEvent(self, ev): + #print ev.key() + if ev.key() == QtCore.Qt.Key_Space: + if self.playRate == 0: + fps = (self.getProcessedImage().shape[0]-1) / (self.tVals[-1] - self.tVals[0]) + self.play(fps) + #print fps + else: + self.play(0) + ev.accept() + elif ev.key() == QtCore.Qt.Key_Home: + self.setCurrentIndex(0) + self.play(0) + ev.accept() + elif ev.key() == QtCore.Qt.Key_End: + self.setCurrentIndex(self.getProcessedImage().shape[0]-1) + self.play(0) + ev.accept() + elif ev.key() in self.noRepeatKeys: + ev.accept() + if ev.isAutoRepeat(): + return + self.keysPressed[ev.key()] = 1 + self.evalKeyState() + else: + QtGui.QWidget.keyPressEvent(self, ev) + + def keyReleaseEvent(self, ev): + if ev.key() in [QtCore.Qt.Key_Space, QtCore.Qt.Key_Home, QtCore.Qt.Key_End]: + ev.accept() + elif ev.key() in self.noRepeatKeys: + ev.accept() + if ev.isAutoRepeat(): + return + try: + del self.keysPressed[ev.key()] + except: + self.keysPressed = {} + self.evalKeyState() + else: + QtGui.QWidget.keyReleaseEvent(self, ev) + + def evalKeyState(self): + if len(self.keysPressed) == 1: + key = list(self.keysPressed.keys())[0] + if key == QtCore.Qt.Key_Right: + self.play(20) + self.jumpFrames(1) + self.lastPlayTime = ptime.time() + 0.2 ## 2ms wait before start + ## This happens *after* jumpFrames, since it might take longer than 2ms + elif key == QtCore.Qt.Key_Left: + self.play(-20) + self.jumpFrames(-1) + self.lastPlayTime = ptime.time() + 0.2 + elif key == QtCore.Qt.Key_Up: + self.play(-100) + elif key == QtCore.Qt.Key_Down: + self.play(100) + elif key == QtCore.Qt.Key_PageUp: + self.play(-1000) + elif key == QtCore.Qt.Key_PageDown: + self.play(1000) + else: + self.play(0) + + def timeout(self): + now = ptime.time() + dt = now - self.lastPlayTime + if dt < 0: + return + n = int(self.playRate * dt) + if n != 0: + self.lastPlayTime += (float(n)/self.playRate) + if self.currentIndex+n > self.image.shape[0]: + self.play(0) + self.jumpFrames(n) + + def setCurrentIndex(self, ind): + """Set the currently displayed frame index.""" + self.currentIndex = np.clip(ind, 0, self.getProcessedImage().shape[0]-1) + self.updateImage() + self.ignoreTimeLine = True + self.timeLine.setValue(self.tVals[self.currentIndex]) + self.ignoreTimeLine = False + + def jumpFrames(self, n): + """Move video frame ahead n frames (may be negative)""" + if self.axes['t'] is not None: + self.setCurrentIndex(self.currentIndex + n) + + def normRadioChanged(self): + self.imageDisp = None + self.updateImage() + self.autoLevels() + self.roiChanged() + self.sigProcessingChanged.emit(self) + + def updateNorm(self): + if self.ui.normTimeRangeCheck.isChecked(): + self.normRgn.show() + else: + self.normRgn.hide() + + if self.ui.normROICheck.isChecked(): + self.normRoi.show() + else: + self.normRoi.hide() + + if not self.ui.normOffRadio.isChecked(): + self.imageDisp = None + self.updateImage() + self.autoLevels() + self.roiChanged() + self.sigProcessingChanged.emit(self) + + def normToggled(self, b): + self.ui.normGroup.setVisible(b) + self.normRoi.setVisible(b and self.ui.normROICheck.isChecked()) + self.normRgn.setVisible(b and self.ui.normTimeRangeCheck.isChecked()) + + def hasTimeAxis(self): + return 't' in self.axes and self.axes['t'] is not None + + def roiClicked(self): + showRoiPlot = False + if self.ui.roiBtn.isChecked(): + showRoiPlot = True + self.roi.show() + #self.ui.roiPlot.show() + self.ui.roiPlot.setMouseEnabled(True, True) + self.ui.splitter.setSizes([self.height()*0.6, self.height()*0.4]) + self.roiCurve.show() + self.roiChanged() + self.ui.roiPlot.showAxis('left') + else: + self.roi.hide() + self.ui.roiPlot.setMouseEnabled(False, False) + self.roiCurve.hide() + self.ui.roiPlot.hideAxis('left') + + if self.hasTimeAxis(): + showRoiPlot = True + mn = self.tVals.min() + mx = self.tVals.max() + self.ui.roiPlot.setXRange(mn, mx, padding=0.01) + self.timeLine.show() + self.timeLine.setBounds([mn, mx]) + self.ui.roiPlot.show() + if not self.ui.roiBtn.isChecked(): + self.ui.splitter.setSizes([self.height()-35, 35]) + else: + self.timeLine.hide() + #self.ui.roiPlot.hide() + + self.ui.roiPlot.setVisible(showRoiPlot) + + def roiChanged(self): + if self.image is None: + return + + image = self.getProcessedImage() + if image.ndim == 2: + axes = (0, 1) + elif image.ndim == 3: + axes = (1, 2) + else: + return + data, coords = self.roi.getArrayRegion(image.view(np.ndarray), self.imageItem, axes, returnMappedCoords=True) + if data is not None: + while data.ndim > 1: + data = data.mean(axis=1) + if image.ndim == 3: + self.roiCurve.setData(y=data, x=self.tVals) + else: + while coords.ndim > 2: + coords = coords[:,:,0] + coords = coords - coords[:,0,np.newaxis] + xvals = (coords**2).sum(axis=0) ** 0.5 + self.roiCurve.setData(y=data, x=xvals) + + def quickMinMax(self, data): + """ + Estimate the min/max values of *data* by subsampling. + """ + while data.size > 1e6: + ax = np.argmax(data.shape) + sl = [slice(None)] * data.ndim + sl[ax] = slice(None, None, 2) + data = data[sl] + return nanmin(data), nanmax(data) + + def normalize(self, image): + """ + Process *image* using the normalization options configured in the + control panel. + + This can be repurposed to process any data through the same filter. + """ + if self.ui.normOffRadio.isChecked(): + return image + + div = self.ui.normDivideRadio.isChecked() + norm = image.view(np.ndarray).copy() + #if div: + #norm = ones(image.shape) + #else: + #norm = zeros(image.shape) + if div: + norm = norm.astype(np.float32) + + if self.ui.normTimeRangeCheck.isChecked() and image.ndim == 3: + (sind, start) = self.timeIndex(self.normRgn.lines[0]) + (eind, end) = self.timeIndex(self.normRgn.lines[1]) + #print start, end, sind, eind + n = image[sind:eind+1].mean(axis=0) + n.shape = (1,) + n.shape + if div: + norm /= n + else: + norm -= n + + if self.ui.normFrameCheck.isChecked() and image.ndim == 3: + n = image.mean(axis=1).mean(axis=1) + n.shape = n.shape + (1, 1) + if div: + norm /= n + else: + norm -= n + + if self.ui.normROICheck.isChecked() and image.ndim == 3: + n = self.normRoi.getArrayRegion(norm, self.imageItem, (1, 2)).mean(axis=1).mean(axis=1) + n = n[:,np.newaxis,np.newaxis] + #print start, end, sind, eind + if div: + norm /= n + else: + norm -= n + + return norm + + def timeLineChanged(self): + #(ind, time) = self.timeIndex(self.ui.timeSlider) + if self.ignoreTimeLine: + return + self.play(0) + (ind, time) = self.timeIndex(self.timeLine) + if ind != self.currentIndex: + self.currentIndex = ind + self.updateImage() + #self.timeLine.setPos(time) + #self.emit(QtCore.SIGNAL('timeChanged'), ind, time) + self.sigTimeChanged.emit(ind, time) + + def updateImage(self, autoHistogramRange=True): + ## Redraw image on screen + if self.image is None: + return + + image = self.getProcessedImage() + + if autoHistogramRange: + self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax) + if self.axes['t'] is None: + self.imageItem.updateImage(image) + else: + self.ui.roiPlot.show() + self.imageItem.updateImage(image[self.currentIndex]) + + + def timeIndex(self, slider): + ## Return the time and frame index indicated by a slider + if self.image is None: + return (0,0) + + t = slider.value() + + xv = self.tVals + if xv is None: + ind = int(t) + else: + if len(xv) < 2: + return (0,0) + totTime = xv[-1] + (xv[-1]-xv[-2]) + inds = np.argwhere(xv < t) + if len(inds) < 1: + return (0,t) + ind = inds[-1,0] + return ind, t + + def getView(self): + """Return the ViewBox (or other compatible object) which displays the ImageItem""" + return self.view + + def getImageItem(self): + """Return the ImageItem for this ImageView.""" + return self.imageItem + + def getRoiPlot(self): + """Return the ROI PlotWidget for this ImageView""" + return self.ui.roiPlot + + def getHistogramWidget(self): + """Return the HistogramLUTWidget for this ImageView""" + return self.ui.histogram + + def export(self, fileName): + """ + Export data from the ImageView to a file, or to a stack of files if + the data is 3D. Saving an image stack will result in index numbers + being added to the file name. Images are saved as they would appear + onscreen, with levels and lookup table applied. + """ + img = self.getProcessedImage() + if self.hasTimeAxis(): + base, ext = os.path.splitext(fileName) + fmt = "%%s%%0%dd%%s" % int(np.log10(img.shape[0])+1) + for i in range(img.shape[0]): + self.imageItem.setImage(img[i], autoLevels=False) + self.imageItem.save(fmt % (base, i, ext)) + self.updateImage() + else: + self.imageItem.save(fileName) + + def exportClicked(self): + fileName = QtGui.QFileDialog.getSaveFileName() + if fileName == '': + return + self.export(fileName) + + def buildMenu(self): + self.menu = QtGui.QMenu() + self.normAction = QtGui.QAction("Normalization", self.menu) + self.normAction.setCheckable(True) + self.normAction.toggled.connect(self.normToggled) + self.menu.addAction(self.normAction) + self.exportAction = QtGui.QAction("Export", self.menu) + self.exportAction.triggered.connect(self.exportClicked) + self.menu.addAction(self.exportAction) + + def menuClicked(self): + if self.menu is None: + self.buildMenu() + self.menu.popup(QtGui.QCursor.pos()) + diff --git a/papi/pyqtgraph/imageview/ImageViewTemplate.ui b/papi/pyqtgraph/imageview/ImageViewTemplate.ui new file mode 100644 index 00000000..927bda30 --- /dev/null +++ b/papi/pyqtgraph/imageview/ImageViewTemplate.ui @@ -0,0 +1,249 @@ + + + Form + + + + 0 + 0 + 726 + 588 + + + + Form + + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + 0 + + + + + + + + + + + + 0 + 1 + + + + ROI + + + true + + + + + + + + 0 + 1 + + + + Menu + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + + + + + Normalization + + + + 0 + + + 0 + + + + + Subtract + + + + + + + Divide + + + false + + + + + + + + 75 + true + + + + Operation: + + + + + + + + 75 + true + + + + Mean: + + + + + + + + 75 + true + + + + Blur: + + + + + + + ROI + + + + + + + + + + X + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Y + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + T + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Off + + + true + + + + + + + Time range + + + + + + + Frame + + + + + + + + + + + + + + PlotWidget + QWidget +
..widgets.PlotWidget
+ 1 +
+ + GraphicsView + QGraphicsView +
..widgets.GraphicsView
+
+ + HistogramLUTWidget + QGraphicsView +
..widgets.HistogramLUTWidget
+
+
+ + +
diff --git a/papi/pyqtgraph/imageview/ImageViewTemplate_pyqt.py b/papi/pyqtgraph/imageview/ImageViewTemplate_pyqt.py new file mode 100644 index 00000000..e728b265 --- /dev/null +++ b/papi/pyqtgraph/imageview/ImageViewTemplate_pyqt.py @@ -0,0 +1,168 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ImageViewTemplate.ui' +# +# Created: Thu May 1 15:20:40 2014 +# by: PyQt4 UI code generator 4.10.4 +# +# WARNING! All changes made in this file will be lost! + +from PyQt4 import QtCore, QtGui + +try: + _fromUtf8 = QtCore.QString.fromUtf8 +except AttributeError: + def _fromUtf8(s): + return s + +try: + _encoding = QtGui.QApplication.UnicodeUTF8 + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig, _encoding) +except AttributeError: + def _translate(context, text, disambig): + return QtGui.QApplication.translate(context, text, disambig) + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName(_fromUtf8("Form")) + Form.resize(726, 588) + self.gridLayout_3 = QtGui.QGridLayout(Form) + self.gridLayout_3.setMargin(0) + self.gridLayout_3.setSpacing(0) + self.gridLayout_3.setObjectName(_fromUtf8("gridLayout_3")) + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName(_fromUtf8("splitter")) + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName(_fromUtf8("layoutWidget")) + self.gridLayout = QtGui.QGridLayout(self.layoutWidget) + self.gridLayout.setSpacing(0) + self.gridLayout.setMargin(0) + self.gridLayout.setObjectName(_fromUtf8("gridLayout")) + self.graphicsView = GraphicsView(self.layoutWidget) + self.graphicsView.setObjectName(_fromUtf8("graphicsView")) + self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) + self.histogram = HistogramLUTWidget(self.layoutWidget) + self.histogram.setObjectName(_fromUtf8("histogram")) + self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) + self.roiBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) + self.roiBtn.setSizePolicy(sizePolicy) + self.roiBtn.setCheckable(True) + self.roiBtn.setObjectName(_fromUtf8("roiBtn")) + self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) + self.menuBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) + self.menuBtn.setSizePolicy(sizePolicy) + self.menuBtn.setObjectName(_fromUtf8("menuBtn")) + self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) + self.roiPlot = PlotWidget(self.splitter) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) + self.roiPlot.setSizePolicy(sizePolicy) + self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) + self.roiPlot.setObjectName(_fromUtf8("roiPlot")) + self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) + self.normGroup = QtGui.QGroupBox(Form) + self.normGroup.setObjectName(_fromUtf8("normGroup")) + self.gridLayout_2 = QtGui.QGridLayout(self.normGroup) + self.gridLayout_2.setMargin(0) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2")) + self.normSubtractRadio = QtGui.QRadioButton(self.normGroup) + self.normSubtractRadio.setObjectName(_fromUtf8("normSubtractRadio")) + self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) + self.normDivideRadio = QtGui.QRadioButton(self.normGroup) + self.normDivideRadio.setChecked(False) + self.normDivideRadio.setObjectName(_fromUtf8("normDivideRadio")) + self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) + self.label_5 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_5.setFont(font) + self.label_5.setObjectName(_fromUtf8("label_5")) + self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) + self.label_3 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_3.setFont(font) + self.label_3.setObjectName(_fromUtf8("label_3")) + self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) + self.label_4 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setBold(True) + font.setWeight(75) + self.label_4.setFont(font) + self.label_4.setObjectName(_fromUtf8("label_4")) + self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) + self.normROICheck = QtGui.QCheckBox(self.normGroup) + self.normROICheck.setObjectName(_fromUtf8("normROICheck")) + self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) + self.normXBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normXBlurSpin.setObjectName(_fromUtf8("normXBlurSpin")) + self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) + self.label_8 = QtGui.QLabel(self.normGroup) + self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_8.setObjectName(_fromUtf8("label_8")) + self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) + self.label_9 = QtGui.QLabel(self.normGroup) + self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_9.setObjectName(_fromUtf8("label_9")) + self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) + self.normYBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normYBlurSpin.setObjectName(_fromUtf8("normYBlurSpin")) + self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) + self.label_10 = QtGui.QLabel(self.normGroup) + self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_10.setObjectName(_fromUtf8("label_10")) + self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) + self.normOffRadio = QtGui.QRadioButton(self.normGroup) + self.normOffRadio.setChecked(True) + self.normOffRadio.setObjectName(_fromUtf8("normOffRadio")) + self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) + self.normTimeRangeCheck = QtGui.QCheckBox(self.normGroup) + self.normTimeRangeCheck.setObjectName(_fromUtf8("normTimeRangeCheck")) + self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) + self.normFrameCheck = QtGui.QCheckBox(self.normGroup) + self.normFrameCheck.setObjectName(_fromUtf8("normFrameCheck")) + self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) + self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normTBlurSpin.setObjectName(_fromUtf8("normTBlurSpin")) + self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) + self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(_translate("Form", "Form", None)) + self.roiBtn.setText(_translate("Form", "ROI", None)) + self.menuBtn.setText(_translate("Form", "Menu", None)) + self.normGroup.setTitle(_translate("Form", "Normalization", None)) + self.normSubtractRadio.setText(_translate("Form", "Subtract", None)) + self.normDivideRadio.setText(_translate("Form", "Divide", None)) + self.label_5.setText(_translate("Form", "Operation:", None)) + self.label_3.setText(_translate("Form", "Mean:", None)) + self.label_4.setText(_translate("Form", "Blur:", None)) + self.normROICheck.setText(_translate("Form", "ROI", None)) + self.label_8.setText(_translate("Form", "X", None)) + self.label_9.setText(_translate("Form", "Y", None)) + self.label_10.setText(_translate("Form", "T", None)) + self.normOffRadio.setText(_translate("Form", "Off", None)) + self.normTimeRangeCheck.setText(_translate("Form", "Time range", None)) + self.normFrameCheck.setText(_translate("Form", "Frame", None)) + +from ..widgets.HistogramLUTWidget import HistogramLUTWidget +from ..widgets.GraphicsView import GraphicsView +from ..widgets.PlotWidget import PlotWidget diff --git a/papi/pyqtgraph/imageview/ImageViewTemplate_pyside.py b/papi/pyqtgraph/imageview/ImageViewTemplate_pyside.py new file mode 100644 index 00000000..6d6c9632 --- /dev/null +++ b/papi/pyqtgraph/imageview/ImageViewTemplate_pyside.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ImageViewTemplate.ui' +# +# Created: Thu May 1 15:20:42 2014 +# by: pyside-uic 0.2.15 running on PySide 1.2.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_Form(object): + def setupUi(self, Form): + Form.setObjectName("Form") + Form.resize(726, 588) + self.gridLayout_3 = QtGui.QGridLayout(Form) + self.gridLayout_3.setContentsMargins(0, 0, 0, 0) + self.gridLayout_3.setSpacing(0) + self.gridLayout_3.setObjectName("gridLayout_3") + self.splitter = QtGui.QSplitter(Form) + self.splitter.setOrientation(QtCore.Qt.Vertical) + self.splitter.setObjectName("splitter") + self.layoutWidget = QtGui.QWidget(self.splitter) + self.layoutWidget.setObjectName("layoutWidget") + self.gridLayout = QtGui.QGridLayout(self.layoutWidget) + self.gridLayout.setSpacing(0) + self.gridLayout.setContentsMargins(0, 0, 0, 0) + self.gridLayout.setObjectName("gridLayout") + self.graphicsView = GraphicsView(self.layoutWidget) + self.graphicsView.setObjectName("graphicsView") + self.gridLayout.addWidget(self.graphicsView, 0, 0, 2, 1) + self.histogram = HistogramLUTWidget(self.layoutWidget) + self.histogram.setObjectName("histogram") + self.gridLayout.addWidget(self.histogram, 0, 1, 1, 2) + self.roiBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.roiBtn.sizePolicy().hasHeightForWidth()) + self.roiBtn.setSizePolicy(sizePolicy) + self.roiBtn.setCheckable(True) + self.roiBtn.setObjectName("roiBtn") + self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1) + self.menuBtn = QtGui.QPushButton(self.layoutWidget) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(1) + sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth()) + self.menuBtn.setSizePolicy(sizePolicy) + self.menuBtn.setObjectName("menuBtn") + self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1) + self.roiPlot = PlotWidget(self.splitter) + sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.roiPlot.sizePolicy().hasHeightForWidth()) + self.roiPlot.setSizePolicy(sizePolicy) + self.roiPlot.setMinimumSize(QtCore.QSize(0, 40)) + self.roiPlot.setObjectName("roiPlot") + self.gridLayout_3.addWidget(self.splitter, 0, 0, 1, 1) + self.normGroup = QtGui.QGroupBox(Form) + self.normGroup.setObjectName("normGroup") + self.gridLayout_2 = QtGui.QGridLayout(self.normGroup) + self.gridLayout_2.setContentsMargins(0, 0, 0, 0) + self.gridLayout_2.setSpacing(0) + self.gridLayout_2.setObjectName("gridLayout_2") + self.normSubtractRadio = QtGui.QRadioButton(self.normGroup) + self.normSubtractRadio.setObjectName("normSubtractRadio") + self.gridLayout_2.addWidget(self.normSubtractRadio, 0, 2, 1, 1) + self.normDivideRadio = QtGui.QRadioButton(self.normGroup) + self.normDivideRadio.setChecked(False) + self.normDivideRadio.setObjectName("normDivideRadio") + self.gridLayout_2.addWidget(self.normDivideRadio, 0, 1, 1, 1) + self.label_5 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.label_5.setFont(font) + self.label_5.setObjectName("label_5") + self.gridLayout_2.addWidget(self.label_5, 0, 0, 1, 1) + self.label_3 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.label_3.setFont(font) + self.label_3.setObjectName("label_3") + self.gridLayout_2.addWidget(self.label_3, 1, 0, 1, 1) + self.label_4 = QtGui.QLabel(self.normGroup) + font = QtGui.QFont() + font.setWeight(75) + font.setBold(True) + self.label_4.setFont(font) + self.label_4.setObjectName("label_4") + self.gridLayout_2.addWidget(self.label_4, 2, 0, 1, 1) + self.normROICheck = QtGui.QCheckBox(self.normGroup) + self.normROICheck.setObjectName("normROICheck") + self.gridLayout_2.addWidget(self.normROICheck, 1, 1, 1, 1) + self.normXBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normXBlurSpin.setObjectName("normXBlurSpin") + self.gridLayout_2.addWidget(self.normXBlurSpin, 2, 2, 1, 1) + self.label_8 = QtGui.QLabel(self.normGroup) + self.label_8.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_8.setObjectName("label_8") + self.gridLayout_2.addWidget(self.label_8, 2, 1, 1, 1) + self.label_9 = QtGui.QLabel(self.normGroup) + self.label_9.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_9.setObjectName("label_9") + self.gridLayout_2.addWidget(self.label_9, 2, 3, 1, 1) + self.normYBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normYBlurSpin.setObjectName("normYBlurSpin") + self.gridLayout_2.addWidget(self.normYBlurSpin, 2, 4, 1, 1) + self.label_10 = QtGui.QLabel(self.normGroup) + self.label_10.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) + self.label_10.setObjectName("label_10") + self.gridLayout_2.addWidget(self.label_10, 2, 5, 1, 1) + self.normOffRadio = QtGui.QRadioButton(self.normGroup) + self.normOffRadio.setChecked(True) + self.normOffRadio.setObjectName("normOffRadio") + self.gridLayout_2.addWidget(self.normOffRadio, 0, 3, 1, 1) + self.normTimeRangeCheck = QtGui.QCheckBox(self.normGroup) + self.normTimeRangeCheck.setObjectName("normTimeRangeCheck") + self.gridLayout_2.addWidget(self.normTimeRangeCheck, 1, 3, 1, 1) + self.normFrameCheck = QtGui.QCheckBox(self.normGroup) + self.normFrameCheck.setObjectName("normFrameCheck") + self.gridLayout_2.addWidget(self.normFrameCheck, 1, 2, 1, 1) + self.normTBlurSpin = QtGui.QDoubleSpinBox(self.normGroup) + self.normTBlurSpin.setObjectName("normTBlurSpin") + self.gridLayout_2.addWidget(self.normTBlurSpin, 2, 6, 1, 1) + self.gridLayout_3.addWidget(self.normGroup, 1, 0, 1, 1) + + self.retranslateUi(Form) + QtCore.QMetaObject.connectSlotsByName(Form) + + def retranslateUi(self, Form): + Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) + self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) + self.menuBtn.setText(QtGui.QApplication.translate("Form", "Menu", None, QtGui.QApplication.UnicodeUTF8)) + self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8)) + self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8)) + self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8)) + self.label_5.setText(QtGui.QApplication.translate("Form", "Operation:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_3.setText(QtGui.QApplication.translate("Form", "Mean:", None, QtGui.QApplication.UnicodeUTF8)) + self.label_4.setText(QtGui.QApplication.translate("Form", "Blur:", None, QtGui.QApplication.UnicodeUTF8)) + self.normROICheck.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8)) + self.label_8.setText(QtGui.QApplication.translate("Form", "X", None, QtGui.QApplication.UnicodeUTF8)) + self.label_9.setText(QtGui.QApplication.translate("Form", "Y", None, QtGui.QApplication.UnicodeUTF8)) + self.label_10.setText(QtGui.QApplication.translate("Form", "T", None, QtGui.QApplication.UnicodeUTF8)) + self.normOffRadio.setText(QtGui.QApplication.translate("Form", "Off", None, QtGui.QApplication.UnicodeUTF8)) + self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8)) + self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8)) + +from ..widgets.HistogramLUTWidget import HistogramLUTWidget +from ..widgets.GraphicsView import GraphicsView +from ..widgets.PlotWidget import PlotWidget diff --git a/papi/pyqtgraph/imageview/__init__.py b/papi/pyqtgraph/imageview/__init__.py new file mode 100644 index 00000000..0060e823 --- /dev/null +++ b/papi/pyqtgraph/imageview/__init__.py @@ -0,0 +1,6 @@ +""" +Widget used for display and analysis of 2D and 3D image data. +Includes ROI plotting over time and image normalization. +""" + +from .ImageView import ImageView diff --git a/papi/pyqtgraph/imageview/tests/test_imageview.py b/papi/pyqtgraph/imageview/tests/test_imageview.py new file mode 100644 index 00000000..2ca1712c --- /dev/null +++ b/papi/pyqtgraph/imageview/tests/test_imageview.py @@ -0,0 +1,11 @@ +import pyqtgraph as pg +import numpy as np + +app = pg.mkQApp() + +def test_nan_image(): + img = np.ones((10,10)) + img[0,0] = np.nan + v = pg.image(img) + app.processEvents() + v.window().close() diff --git a/papi/pyqtgraph/metaarray/MetaArray.py b/papi/pyqtgraph/metaarray/MetaArray.py new file mode 100644 index 00000000..9c3f5b8a --- /dev/null +++ b/papi/pyqtgraph/metaarray/MetaArray.py @@ -0,0 +1,1498 @@ +# -*- coding: utf-8 -*- +""" +MetaArray.py - Class encapsulating ndarray with meta data +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +MetaArray is an array class based on numpy.ndarray that allows storage of per-axis meta data +such as axis values, names, units, column names, etc. It also enables several +new methods for slicing and indexing the array based on this meta data. +More info at http://www.scipy.org/Cookbook/MetaArray +""" + +import numpy as np +import types, copy, threading, os, re +import pickle +from functools import reduce +#import traceback + +## By default, the library will use HDF5 when writing files. +## This can be overridden by setting USE_HDF5 = False +USE_HDF5 = True +try: + import h5py + HAVE_HDF5 = True +except: + USE_HDF5 = False + HAVE_HDF5 = False + + +def axis(name=None, cols=None, values=None, units=None): + """Convenience function for generating axis descriptions when defining MetaArrays""" + ax = {} + cNameOrder = ['name', 'units', 'title'] + if name is not None: + ax['name'] = name + if values is not None: + ax['values'] = values + if units is not None: + ax['units'] = units + if cols is not None: + ax['cols'] = [] + for c in cols: + if type(c) != list and type(c) != tuple: + c = [c] + col = {} + for i in range(0,len(c)): + col[cNameOrder[i]] = c[i] + ax['cols'].append(col) + return ax + +class sliceGenerator(object): + """Just a compact way to generate tuples of slice objects.""" + def __getitem__(self, arg): + return arg + def __getslice__(self, arg): + return arg +SLICER = sliceGenerator() + + +class MetaArray(object): + """N-dimensional array with meta data such as axis titles, units, and column names. + + May be initialized with a file name, a tuple representing the dimensions of the array, + or any arguments that could be passed on to numpy.array() + + The info argument sets the metadata for the entire array. It is composed of a list + of axis descriptions where each axis may have a name, title, units, and a list of column + descriptions. An additional dict at the end of the axis list may specify parameters + that apply to values in the entire array. + + For example: + A 2D array of altitude values for a topographical map might look like + info=[ + {'name': 'lat', 'title': 'Lattitude'}, + {'name': 'lon', 'title': 'Longitude'}, + {'title': 'Altitude', 'units': 'm'} + ] + In this case, every value in the array represents the altitude in feet at the lat, lon + position represented by the array index. All of the following return the + value at lat=10, lon=5: + array[10, 5] + array['lon':5, 'lat':10] + array['lat':10][5] + Now suppose we want to combine this data with another array of equal dimensions that + represents the average rainfall for each location. We could easily store these as two + separate arrays or combine them into a 3D array with this description: + info=[ + {'name': 'vals', 'cols': [ + {'name': 'altitude', 'units': 'm'}, + {'name': 'rainfall', 'units': 'cm/year'} + ]}, + {'name': 'lat', 'title': 'Lattitude'}, + {'name': 'lon', 'title': 'Longitude'} + ] + We can now access the altitude values with array[0] or array['altitude'], and the + rainfall values with array[1] or array['rainfall']. All of the following return + the rainfall value at lat=10, lon=5: + array[1, 10, 5] + array['lon':5, 'lat':10, 'val': 'rainfall'] + array['rainfall', 'lon':5, 'lat':10] + Notice that in the second example, there is no need for an extra (4th) axis description + since the actual values are described (name and units) in the column info for the first axis. + """ + + version = '2' + + # Default hdf5 compression to use when writing + # 'gzip' is widely available and somewhat slow + # 'lzf' is faster, but generally not available outside h5py + # 'szip' is also faster, but lacks write support on windows + # (so by default, we use no compression) + # May also be a tuple (filter, opts), such as ('gzip', 3) + defaultCompression = None + + ## Types allowed as axis or column names + nameTypes = [basestring, tuple] + @staticmethod + def isNameType(var): + return any([isinstance(var, t) for t in MetaArray.nameTypes]) + + + ## methods to wrap from embedded ndarray / HDF5 + wrapMethods = set(['__eq__', '__ne__', '__le__', '__lt__', '__ge__', '__gt__']) + + def __init__(self, data=None, info=None, dtype=None, file=None, copy=False, **kwargs): + object.__init__(self) + #self._infoOwned = False + self._isHDF = False + + if file is not None: + self._data = None + self.readFile(file, **kwargs) + if kwargs.get("readAllData", True) and self._data is None: + raise Exception("File read failed: %s" % file) + else: + self._info = info + if (hasattr(data, 'implements') and data.implements('MetaArray')): + self._info = data._info + self._data = data.asarray() + elif isinstance(data, tuple): ## create empty array with specified shape + self._data = np.empty(data, dtype=dtype) + else: + self._data = np.array(data, dtype=dtype, copy=copy) + + ## run sanity checks on info structure + self.checkInfo() + + def checkInfo(self): + info = self._info + if info is None: + if self._data is None: + return + else: + self._info = [{} for i in range(self.ndim)] + return + else: + try: + info = list(info) + except: + raise Exception("Info must be a list of axis specifications") + if len(info) < self.ndim+1: + info.extend([{}]*(self.ndim+1-len(info))) + elif len(info) > self.ndim+1: + raise Exception("Info parameter must be list of length ndim+1 or less.") + for i in range(len(info)): + if not isinstance(info[i], dict): + if info[i] is None: + info[i] = {} + else: + raise Exception("Axis specification must be Dict or None") + if i < self.ndim and 'values' in info[i]: + if type(info[i]['values']) is list: + info[i]['values'] = np.array(info[i]['values']) + elif type(info[i]['values']) is not np.ndarray: + raise Exception("Axis values must be specified as list or ndarray") + if info[i]['values'].ndim != 1 or info[i]['values'].shape[0] != self.shape[i]: + raise Exception("Values array for axis %d has incorrect shape. (given %s, but should be %s)" % (i, str(info[i]['values'].shape), str((self.shape[i],)))) + if i < self.ndim and 'cols' in info[i]: + if not isinstance(info[i]['cols'], list): + info[i]['cols'] = list(info[i]['cols']) + if len(info[i]['cols']) != self.shape[i]: + raise Exception('Length of column list for axis %d does not match data. (given %d, but should be %d)' % (i, len(info[i]['cols']), self.shape[i])) + + def implements(self, name=None): + ## Rather than isinstance(obj, MetaArray) use object.implements('MetaArray') + if name is None: + return ['MetaArray'] + else: + return name == 'MetaArray' + + #def __array_finalize__(self,obj): + ### array_finalize is called every time a MetaArray is created + ### (whereas __new__ is not necessarily called every time) + + ### obj is the object from which this array was generated (for example, when slicing or view()ing) + + ## We use the getattr method to set a default if 'obj' doesn't have the 'info' attribute + ##print "Create new MA from object", str(type(obj)) + ##import traceback + ##traceback.print_stack() + ##print "finalize", type(self), type(obj) + #if not hasattr(self, '_info'): + ##if isinstance(obj, MetaArray): + ##print " copy info:", obj._info + #self._info = getattr(obj, '_info', [{}]*(obj.ndim+1)) + #self._infoOwned = False ## Do not make changes to _info until it is copied at least once + ##print " self info:", self._info + + ## We could have checked first whether self._info was already defined: + ##if not hasattr(self, 'info'): + ## self._info = getattr(obj, 'info', {}) + + + def __getitem__(self, ind): + #print "getitem:", ind + + ## should catch scalar requests as early as possible to speed things up (?) + + nInd = self._interpretIndexes(ind) + + #a = np.ndarray.__getitem__(self, nInd) + a = self._data[nInd] + if len(nInd) == self.ndim: + if np.all([not isinstance(ind, slice) for ind in nInd]): ## no slices; we have requested a single value from the array + return a + #if type(a) != type(self._data) and not isinstance(a, np.ndarray): ## indexing returned single value + #return a + + ## indexing returned a sub-array; generate new info array to go with it + #print " new MA:", type(a), a.shape + info = [] + extraInfo = self._info[-1].copy() + for i in range(0, len(nInd)): ## iterate over all axes + #print " axis", i + if type(nInd[i]) in [slice, list] or isinstance(nInd[i], np.ndarray): ## If the axis is sliced, keep the info but chop if necessary + #print " slice axis", i, nInd[i] + #a._info[i] = self._axisSlice(i, nInd[i]) + #print " info:", a._info[i] + info.append(self._axisSlice(i, nInd[i])) + else: ## If the axis is indexed, then move the information from that single index to the last info dictionary + #print "indexed:", i, nInd[i], type(nInd[i]) + newInfo = self._axisSlice(i, nInd[i]) + name = None + colName = None + for k in newInfo: + if k == 'cols': + if 'cols' not in extraInfo: + extraInfo['cols'] = [] + extraInfo['cols'].append(newInfo[k]) + if 'units' in newInfo[k]: + extraInfo['units'] = newInfo[k]['units'] + if 'name' in newInfo[k]: + colName = newInfo[k]['name'] + elif k == 'name': + name = newInfo[k] + else: + if k not in extraInfo: + extraInfo[k] = newInfo[k] + extraInfo[k] = newInfo[k] + if 'name' not in extraInfo: + if name is None: + if colName is not None: + extraInfo['name'] = colName + else: + if colName is not None: + extraInfo['name'] = str(name) + ': ' + str(colName) + else: + extraInfo['name'] = name + + + #print "Lost info:", newInfo + #a._info[i] = None + #if 'name' in newInfo: + #a._info[-1][newInfo['name']] = newInfo + info.append(extraInfo) + + #self._infoOwned = False + #while None in a._info: + #a._info.remove(None) + return MetaArray(a, info=info) + + @property + def ndim(self): + return len(self.shape) ## hdf5 objects do not have ndim property. + + @property + def shape(self): + return self._data.shape + + @property + def dtype(self): + return self._data.dtype + + def __len__(self): + return len(self._data) + + def __getslice__(self, *args): + return self.__getitem__(slice(*args)) + + def __setitem__(self, ind, val): + nInd = self._interpretIndexes(ind) + try: + self._data[nInd] = val + except: + print(self, nInd, val) + raise + + def __getattr__(self, attr): + if attr in self.wrapMethods: + return getattr(self._data, attr) + else: + raise AttributeError(attr) + #return lambda *args, **kwargs: MetaArray(getattr(a.view(ndarray), attr)(*args, **kwargs) + + def __eq__(self, b): + return self._binop('__eq__', b) + + def __ne__(self, b): + return self._binop('__ne__', b) + #if isinstance(b, MetaArray): + #b = b.asarray() + #return self.asarray() != b + + def __sub__(self, b): + return self._binop('__sub__', b) + #if isinstance(b, MetaArray): + #b = b.asarray() + #return MetaArray(self.asarray() - b, info=self.infoCopy()) + + def __add__(self, b): + return self._binop('__add__', b) + + def __mul__(self, b): + return self._binop('__mul__', b) + + def __div__(self, b): + return self._binop('__div__', b) + + def __truediv__(self, b): + return self._binop('__truediv__', b) + + def _binop(self, op, b): + if isinstance(b, MetaArray): + b = b.asarray() + a = self.asarray() + c = getattr(a, op)(b) + if c.shape != a.shape: + raise Exception("Binary operators with MetaArray must return an array of the same shape (this shape is %s, result shape was %s)" % (a.shape, c.shape)) + return MetaArray(c, info=self.infoCopy()) + + def asarray(self): + if isinstance(self._data, np.ndarray): + return self._data + else: + return np.array(self._data) + + def __array__(self): + ## supports np.array(metaarray_instance) + return self.asarray() + + def view(self, typ): + ## deprecated; kept for backward compatibility + if typ is np.ndarray: + return self.asarray() + else: + raise Exception('invalid view type: %s' % str(typ)) + + def axisValues(self, axis): + """Return the list of values for an axis""" + ax = self._interpretAxis(axis) + if 'values' in self._info[ax]: + return self._info[ax]['values'] + else: + raise Exception('Array axis %s (%d) has no associated values.' % (str(axis), ax)) + + def xvals(self, axis): + """Synonym for axisValues()""" + return self.axisValues(axis) + + def axisHasValues(self, axis): + ax = self._interpretAxis(axis) + return 'values' in self._info[ax] + + def axisHasColumns(self, axis): + ax = self._interpretAxis(axis) + return 'cols' in self._info[ax] + + def axisUnits(self, axis): + """Return the units for axis""" + ax = self._info[self._interpretAxis(axis)] + if 'units' in ax: + return ax['units'] + + def hasColumn(self, axis, col): + ax = self._info[self._interpretAxis(axis)] + if 'cols' in ax: + for c in ax['cols']: + if c['name'] == col: + return True + return False + + def listColumns(self, axis=None): + """Return a list of column names for axis. If axis is not specified, then return a dict of {axisName: (column names), ...}.""" + if axis is None: + ret = {} + for i in range(self.ndim): + if 'cols' in self._info[i]: + cols = [c['name'] for c in self._info[i]['cols']] + else: + cols = [] + ret[self.axisName(i)] = cols + return ret + else: + axis = self._interpretAxis(axis) + return [c['name'] for c in self._info[axis]['cols']] + + def columnName(self, axis, col): + ax = self._info[self._interpretAxis(axis)] + return ax['cols'][col]['name'] + + def axisName(self, n): + return self._info[n].get('name', n) + + def columnUnits(self, axis, column): + """Return the units for column in axis""" + ax = self._info[self._interpretAxis(axis)] + if 'cols' in ax: + for c in ax['cols']: + if c['name'] == column: + return c['units'] + raise Exception("Axis %s has no column named %s" % (str(axis), str(column))) + else: + raise Exception("Axis %s has no column definitions" % str(axis)) + + def rowsort(self, axis, key=0): + """Return this object with all records sorted along axis using key as the index to the values to compare. Does not yet modify meta info.""" + ## make sure _info is copied locally before modifying it! + + keyList = self[key] + order = keyList.argsort() + if type(axis) == int: + ind = [slice(None)]*axis + ind.append(order) + elif isinstance(axis, basestring): + ind = (slice(axis, order),) + return self[tuple(ind)] + + def append(self, val, axis): + """Return this object with val appended along axis. Does not yet combine meta info.""" + ## make sure _info is copied locally before modifying it! + + s = list(self.shape) + axis = self._interpretAxis(axis) + s[axis] += 1 + n = MetaArray(tuple(s), info=self._info, dtype=self.dtype) + ind = [slice(None)]*self.ndim + ind[axis] = slice(None,-1) + n[tuple(ind)] = self + ind[axis] = -1 + n[tuple(ind)] = val + return n + + def extend(self, val, axis): + """Return the concatenation along axis of this object and val. Does not yet combine meta info.""" + ## make sure _info is copied locally before modifying it! + + axis = self._interpretAxis(axis) + return MetaArray(np.concatenate(self, val, axis), info=self._info) + + def infoCopy(self, axis=None): + """Return a deep copy of the axis meta info for this object""" + if axis is None: + return copy.deepcopy(self._info) + else: + return copy.deepcopy(self._info[self._interpretAxis(axis)]) + + def copy(self): + return MetaArray(self._data.copy(), info=self.infoCopy()) + + + def _interpretIndexes(self, ind): + #print "interpret", ind + if not isinstance(ind, tuple): + ## a list of slices should be interpreted as a tuple of slices. + if isinstance(ind, list) and len(ind) > 0 and isinstance(ind[0], slice): + ind = tuple(ind) + ## everything else can just be converted to a length-1 tuple + else: + ind = (ind,) + + nInd = [slice(None)]*self.ndim + numOk = True ## Named indices not started yet; numbered sill ok + for i in range(0,len(ind)): + (axis, index, isNamed) = self._interpretIndex(ind[i], i, numOk) + #try: + nInd[axis] = index + #except: + #print "ndim:", self.ndim + #print "axis:", axis + #print "index spec:", ind[i] + #print "index num:", index + #raise + if isNamed: + numOk = False + return tuple(nInd) + + def _interpretAxis(self, axis): + if isinstance(axis, basestring) or isinstance(axis, tuple): + return self._getAxis(axis) + else: + return axis + + def _interpretIndex(self, ind, pos, numOk): + #print "Interpreting index", ind, pos, numOk + + ## should probably check for int first to speed things up.. + if type(ind) is int: + if not numOk: + raise Exception("string and integer indexes may not follow named indexes") + #print " normal numerical index" + return (pos, ind, False) + if MetaArray.isNameType(ind): + if not numOk: + raise Exception("string and integer indexes may not follow named indexes") + #print " String index, column is ", self._getIndex(pos, ind) + return (pos, self._getIndex(pos, ind), False) + elif type(ind) is slice: + #print " Slice index" + if MetaArray.isNameType(ind.start) or MetaArray.isNameType(ind.stop): ## Not an actual slice! + #print " ..not a real slice" + axis = self._interpretAxis(ind.start) + #print " axis is", axis + + ## x[Axis:Column] + if MetaArray.isNameType(ind.stop): + #print " column name, column is ", self._getIndex(axis, ind.stop) + index = self._getIndex(axis, ind.stop) + + ## x[Axis:min:max] + elif (isinstance(ind.stop, float) or isinstance(ind.step, float)) and ('values' in self._info[axis]): + #print " axis value range" + if ind.stop is None: + mask = self.xvals(axis) < ind.step + elif ind.step is None: + mask = self.xvals(axis) >= ind.stop + else: + mask = (self.xvals(axis) >= ind.stop) * (self.xvals(axis) < ind.step) + ##print "mask:", mask + index = mask + + ## x[Axis:columnIndex] + elif isinstance(ind.stop, int) or isinstance(ind.step, int): + #print " normal slice after named axis" + if ind.step is None: + index = ind.stop + else: + index = slice(ind.stop, ind.step) + + ## x[Axis: [list]] + elif type(ind.stop) is list: + #print " list of indexes from named axis" + index = [] + for i in ind.stop: + if type(i) is int: + index.append(i) + elif MetaArray.isNameType(i): + index.append(self._getIndex(axis, i)) + else: + ## unrecognized type, try just passing on to array + index = ind.stop + break + + else: + #print " other type.. forward on to array for handling", type(ind.stop) + index = ind.stop + #print "Axis %s (%s) : %s" % (ind.start, str(axis), str(type(index))) + #if type(index) is np.ndarray: + #print " ", index.shape + return (axis, index, True) + else: + #print " Looks like a real slice, passing on to array" + return (pos, ind, False) + elif type(ind) is list: + #print " List index., interpreting each element individually" + indList = [self._interpretIndex(i, pos, numOk)[1] for i in ind] + return (pos, indList, False) + else: + if not numOk: + raise Exception("string and integer indexes may not follow named indexes") + #print " normal numerical index" + return (pos, ind, False) + + def _getAxis(self, name): + for i in range(0, len(self._info)): + axis = self._info[i] + if 'name' in axis and axis['name'] == name: + return i + raise Exception("No axis named %s.\n info=%s" % (name, self._info)) + + def _getIndex(self, axis, name): + ax = self._info[axis] + if ax is not None and 'cols' in ax: + for i in range(0, len(ax['cols'])): + if 'name' in ax['cols'][i] and ax['cols'][i]['name'] == name: + return i + raise Exception("Axis %d has no column named %s.\n info=%s" % (axis, name, self._info)) + + def _axisCopy(self, i): + return copy.deepcopy(self._info[i]) + + def _axisSlice(self, i, cols): + #print "axisSlice", i, cols + if 'cols' in self._info[i] or 'values' in self._info[i]: + ax = self._axisCopy(i) + if 'cols' in ax: + #print " slicing columns..", array(ax['cols']), cols + sl = np.array(ax['cols'])[cols] + if isinstance(sl, np.ndarray): + sl = list(sl) + ax['cols'] = sl + #print " result:", ax['cols'] + if 'values' in ax: + ax['values'] = np.array(ax['values'])[cols] + else: + ax = self._info[i] + #print " ", ax + return ax + + def prettyInfo(self): + s = '' + titles = [] + maxl = 0 + for i in range(len(self._info)-1): + ax = self._info[i] + axs = '' + if 'name' in ax: + axs += '"%s"' % str(ax['name']) + else: + axs += "%d" % i + if 'units' in ax: + axs += " (%s)" % str(ax['units']) + titles.append(axs) + if len(axs) > maxl: + maxl = len(axs) + + for i in range(min(self.ndim, len(self._info)-1)): + ax = self._info[i] + axs = titles[i] + axs += '%s[%d] :' % (' ' * (maxl + 2 - len(axs)), self.shape[i]) + if 'values' in ax: + v0 = ax['values'][0] + v1 = ax['values'][-1] + axs += " values: [%g ... %g] (step %g)" % (v0, v1, (v1-v0)/(self.shape[i]-1)) + if 'cols' in ax: + axs += " columns: " + colstrs = [] + for c in range(len(ax['cols'])): + col = ax['cols'][c] + cs = str(col.get('name', c)) + if 'units' in col: + cs += " (%s)" % col['units'] + colstrs.append(cs) + axs += '[' + ', '.join(colstrs) + ']' + s += axs + "\n" + s += str(self._info[-1]) + return s + + def __repr__(self): + return "%s\n-----------------------------------------------\n%s" % (self.view(np.ndarray).__repr__(), self.prettyInfo()) + + def __str__(self): + return self.__repr__() + + + def axisCollapsingFn(self, fn, axis=None, *args, **kargs): + #arr = self.view(np.ndarray) + fn = getattr(self._data, fn) + if axis is None: + return fn(axis, *args, **kargs) + else: + info = self.infoCopy() + axis = self._interpretAxis(axis) + info.pop(axis) + return MetaArray(fn(axis, *args, **kargs), info=info) + + def mean(self, axis=None, *args, **kargs): + return self.axisCollapsingFn('mean', axis, *args, **kargs) + + + def min(self, axis=None, *args, **kargs): + return self.axisCollapsingFn('min', axis, *args, **kargs) + + def max(self, axis=None, *args, **kargs): + return self.axisCollapsingFn('max', axis, *args, **kargs) + + def transpose(self, *args): + if len(args) == 1 and hasattr(args[0], '__iter__'): + order = args[0] + else: + order = args + + order = [self._interpretAxis(ax) for ax in order] + infoOrder = order + list(range(len(order), len(self._info))) + info = [self._info[i] for i in infoOrder] + order = order + list(range(len(order), self.ndim)) + + try: + if self._isHDF: + return MetaArray(np.array(self._data).transpose(order), info=info) + else: + return MetaArray(self._data.transpose(order), info=info) + except: + print(order) + raise + + #### File I/O Routines + def readFile(self, filename, **kwargs): + """Load the data and meta info stored in *filename* + Different arguments are allowed depending on the type of file. + For HDF5 files: + + *writable* (bool) if True, then any modifications to data in the array will be stored to disk. + *readAllData* (bool) if True, then all data in the array is immediately read from disk + and the file is closed (this is the default for files < 500MB). Otherwise, the file will + be left open and data will be read only as requested (this is + the default for files >= 500MB). + + + """ + ## decide which read function to use + with open(filename, 'rb') as fd: + magic = fd.read(8) + if magic == '\x89HDF\r\n\x1a\n': + fd.close() + self._readHDF5(filename, **kwargs) + self._isHDF = True + else: + fd.seek(0) + meta = MetaArray._readMeta(fd) + + if not kwargs.get("readAllData", True): + self._data = np.empty(meta['shape'], dtype=meta['type']) + if 'version' in meta: + ver = meta['version'] + else: + ver = 1 + rFuncName = '_readData%s' % str(ver) + if not hasattr(MetaArray, rFuncName): + raise Exception("This MetaArray library does not support array version '%s'" % ver) + rFunc = getattr(self, rFuncName) + rFunc(fd, meta, **kwargs) + self._isHDF = False + + @staticmethod + def _readMeta(fd): + """Read meta array from the top of a file. Read lines until a blank line is reached. + This function should ideally work for ALL versions of MetaArray. + """ + meta = '' + ## Read meta information until the first blank line + while True: + line = fd.readline().strip() + if line == '': + break + meta += line + ret = eval(meta) + #print ret + return ret + + def _readData1(self, fd, meta, mmap=False, **kwds): + ## Read array data from the file descriptor for MetaArray v1 files + ## read in axis values for any axis that specifies a length + frameSize = 1 + for ax in meta['info']: + if 'values_len' in ax: + ax['values'] = np.fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) + frameSize *= ax['values_len'] + del ax['values_len'] + del ax['values_type'] + self._info = meta['info'] + if not kwds.get("readAllData", True): + return + ## the remaining data is the actual array + if mmap: + subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape']) + else: + subarr = np.fromstring(fd.read(), dtype=meta['type']) + subarr.shape = meta['shape'] + self._data = subarr + + def _readData2(self, fd, meta, mmap=False, subset=None, **kwds): + ## read in axis values + dynAxis = None + frameSize = 1 + ## read in axis values for any axis that specifies a length + for i in range(len(meta['info'])): + ax = meta['info'][i] + if 'values_len' in ax: + if ax['values_len'] == 'dynamic': + if dynAxis is not None: + raise Exception("MetaArray has more than one dynamic axis! (this is not allowed)") + dynAxis = i + else: + ax['values'] = np.fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) + frameSize *= ax['values_len'] + del ax['values_len'] + del ax['values_type'] + self._info = meta['info'] + if not kwds.get("readAllData", True): + return + + ## No axes are dynamic, just read the entire array in at once + if dynAxis is None: + #if rewriteDynamic is not None: + #raise Exception("") + if meta['type'] == 'object': + if mmap: + raise Exception('memmap not supported for arrays with dtype=object') + subarr = pickle.loads(fd.read()) + else: + if mmap: + subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape']) + else: + subarr = np.fromstring(fd.read(), dtype=meta['type']) + #subarr = subarr.view(subtype) + subarr.shape = meta['shape'] + #subarr._info = meta['info'] + ## One axis is dynamic, read in a frame at a time + else: + if mmap: + raise Exception('memmap not supported for non-contiguous arrays. Use rewriteContiguous() to convert.') + ax = meta['info'][dynAxis] + xVals = [] + frames = [] + frameShape = list(meta['shape']) + frameShape[dynAxis] = 1 + frameSize = reduce(lambda a,b: a*b, frameShape) + n = 0 + while True: + ## Extract one non-blank line + while True: + line = fd.readline() + if line != '\n': + break + if line == '': + break + + ## evaluate line + inf = eval(line) + + ## read data block + #print "read %d bytes as %s" % (inf['len'], meta['type']) + if meta['type'] == 'object': + data = pickle.loads(fd.read(inf['len'])) + else: + data = np.fromstring(fd.read(inf['len']), dtype=meta['type']) + + if data.size != frameSize * inf['numFrames']: + #print data.size, frameSize, inf['numFrames'] + raise Exception("Wrong frame size in MetaArray file! (frame %d)" % n) + + ## read in data block + shape = list(frameShape) + shape[dynAxis] = inf['numFrames'] + data.shape = shape + if subset is not None: + dSlice = subset[dynAxis] + if dSlice.start is None: + dStart = 0 + else: + dStart = max(0, dSlice.start - n) + if dSlice.stop is None: + dStop = data.shape[dynAxis] + else: + dStop = min(data.shape[dynAxis], dSlice.stop - n) + newSubset = list(subset[:]) + newSubset[dynAxis] = slice(dStart, dStop) + if dStop > dStart: + #print n, data.shape, " => ", newSubset, data[tuple(newSubset)].shape + frames.append(data[tuple(newSubset)].copy()) + else: + #data = data[subset].copy() ## what's this for?? + frames.append(data) + + n += inf['numFrames'] + if 'xVals' in inf: + xVals.extend(inf['xVals']) + subarr = np.concatenate(frames, axis=dynAxis) + if len(xVals)> 0: + ax['values'] = np.array(xVals, dtype=ax['values_type']) + del ax['values_len'] + del ax['values_type'] + #subarr = subarr.view(subtype) + #subarr._info = meta['info'] + self._info = meta['info'] + self._data = subarr + #raise Exception() ## stress-testing + #return subarr + + def _readHDF5(self, fileName, readAllData=None, writable=False, **kargs): + if 'close' in kargs and readAllData is None: ## for backward compatibility + readAllData = kargs['close'] + + if readAllData is True and writable is True: + raise Exception("Incompatible arguments: readAllData=True and writable=True") + + if not HAVE_HDF5: + try: + assert writable==False + assert readAllData != False + self._readHDF5Remote(fileName) + return + except: + raise Exception("The file '%s' is HDF5-formatted, but the HDF5 library (h5py) was not found." % fileName) + + ## by default, readAllData=True for files < 500MB + if readAllData is None: + size = os.stat(fileName).st_size + readAllData = (size < 500e6) + + if writable is True: + mode = 'r+' + else: + mode = 'r' + f = h5py.File(fileName, mode) + + ver = f.attrs['MetaArray'] + if ver > MetaArray.version: + print("Warning: This file was written with MetaArray version %s, but you are using version %s. (Will attempt to read anyway)" % (str(ver), str(MetaArray.version))) + meta = MetaArray.readHDF5Meta(f['info']) + self._info = meta + + if writable or not readAllData: ## read all data, convert to ndarray, close file + self._data = f['data'] + self._openFile = f + else: + self._data = f['data'][:] + f.close() + + def _readHDF5Remote(self, fileName): + ## Used to read HDF5 files via remote process. + ## This is needed in the case that HDF5 is not importable due to the use of python-dbg. + proc = getattr(MetaArray, '_hdf5Process', None) + + if proc == False: + raise Exception('remote read failed') + if proc == None: + from .. import multiprocess as mp + #print "new process" + proc = mp.Process(executable='/usr/bin/python') + proc.setProxyOptions(deferGetattr=True) + MetaArray._hdf5Process = proc + MetaArray._h5py_metaarray = proc._import('pyqtgraph.metaarray') + ma = MetaArray._h5py_metaarray.MetaArray(file=fileName) + self._data = ma.asarray()._getValue() + self._info = ma._info._getValue() + #print MetaArray._hdf5Process + #import inspect + #print MetaArray, id(MetaArray), inspect.getmodule(MetaArray) + + + + @staticmethod + def mapHDF5Array(data, writable=False): + off = data.id.get_offset() + if writable: + mode = 'r+' + else: + mode = 'r' + if off is None: + raise Exception("This dataset uses chunked storage; it can not be memory-mapped. (store using mappable=True)") + return np.memmap(filename=data.file.filename, offset=off, dtype=data.dtype, shape=data.shape, mode=mode) + + + + + @staticmethod + def readHDF5Meta(root, mmap=False): + data = {} + + ## Pull list of values from attributes and child objects + for k in root.attrs: + val = root.attrs[k] + if isinstance(val, basestring): ## strings need to be re-evaluated to their original types + try: + val = eval(val) + except: + raise Exception('Can not evaluate string: "%s"' % val) + data[k] = val + for k in root: + obj = root[k] + if isinstance(obj, h5py.highlevel.Group): + val = MetaArray.readHDF5Meta(obj) + elif isinstance(obj, h5py.highlevel.Dataset): + if mmap: + val = MetaArray.mapHDF5Array(obj) + else: + val = obj[:] + else: + raise Exception("Don't know what to do with type '%s'" % str(type(obj))) + data[k] = val + + typ = root.attrs['_metaType_'] + del data['_metaType_'] + + if typ == 'dict': + return data + elif typ == 'list' or typ == 'tuple': + d2 = [None]*len(data) + for k in data: + d2[int(k)] = data[k] + if typ == 'tuple': + d2 = tuple(d2) + return d2 + else: + raise Exception("Don't understand metaType '%s'" % typ) + + + def write(self, fileName, **opts): + """Write this object to a file. The object can be restored by calling MetaArray(file=fileName) + opts: + appendAxis: the name (or index) of the appendable axis. Allows the array to grow. + compression: None, 'gzip' (good compression), 'lzf' (fast compression), etc. + chunks: bool or tuple specifying chunk shape + """ + + if USE_HDF5 and HAVE_HDF5: + return self.writeHDF5(fileName, **opts) + else: + return self.writeMa(fileName, **opts) + + def writeMeta(self, fileName): + """Used to re-write meta info to the given file. + This feature is only available for HDF5 files.""" + f = h5py.File(fileName, 'r+') + if f.attrs['MetaArray'] != MetaArray.version: + raise Exception("The file %s was created with a different version of MetaArray. Will not modify." % fileName) + del f['info'] + + self.writeHDF5Meta(f, 'info', self._info) + f.close() + + + def writeHDF5(self, fileName, **opts): + ## default options for writing datasets + comp = self.defaultCompression + if isinstance(comp, tuple): + comp, copts = comp + else: + copts = None + + dsOpts = { + 'compression': comp, + 'chunks': True, + } + if copts is not None: + dsOpts['compression_opts'] = copts + + ## if there is an appendable axis, then we can guess the desired chunk shape (optimized for appending) + appAxis = opts.get('appendAxis', None) + if appAxis is not None: + appAxis = self._interpretAxis(appAxis) + cs = [min(100000, x) for x in self.shape] + cs[appAxis] = 1 + dsOpts['chunks'] = tuple(cs) + + ## if there are columns, then we can guess a different chunk shape + ## (read one column at a time) + else: + cs = [min(100000, x) for x in self.shape] + for i in range(self.ndim): + if 'cols' in self._info[i]: + cs[i] = 1 + dsOpts['chunks'] = tuple(cs) + + ## update options if they were passed in + for k in dsOpts: + if k in opts: + dsOpts[k] = opts[k] + + + ## If mappable is in options, it disables chunking/compression + if opts.get('mappable', False): + dsOpts = { + 'chunks': None, + 'compression': None + } + + + ## set maximum shape to allow expansion along appendAxis + append = False + if appAxis is not None: + maxShape = list(self.shape) + ax = self._interpretAxis(appAxis) + maxShape[ax] = None + if os.path.exists(fileName): + append = True + dsOpts['maxshape'] = tuple(maxShape) + else: + dsOpts['maxshape'] = None + + if append: + f = h5py.File(fileName, 'r+') + if f.attrs['MetaArray'] != MetaArray.version: + raise Exception("The file %s was created with a different version of MetaArray. Will not modify." % fileName) + + ## resize data and write in new values + data = f['data'] + shape = list(data.shape) + shape[ax] += self.shape[ax] + data.resize(tuple(shape)) + sl = [slice(None)] * len(data.shape) + sl[ax] = slice(-self.shape[ax], None) + data[tuple(sl)] = self.view(np.ndarray) + + ## add axis values if they are present. + axInfo = f['info'][str(ax)] + if 'values' in axInfo: + v = axInfo['values'] + v2 = self._info[ax]['values'] + shape = list(v.shape) + shape[0] += v2.shape[0] + v.resize(shape) + v[-v2.shape[0]:] = v2 + f.close() + else: + f = h5py.File(fileName, 'w') + f.attrs['MetaArray'] = MetaArray.version + #print dsOpts + f.create_dataset('data', data=self.view(np.ndarray), **dsOpts) + + ## dsOpts is used when storing meta data whenever an array is encountered + ## however, 'chunks' will no longer be valid for these arrays if it specifies a chunk shape. + ## 'maxshape' is right-out. + if isinstance(dsOpts['chunks'], tuple): + dsOpts['chunks'] = True + if 'maxshape' in dsOpts: + del dsOpts['maxshape'] + self.writeHDF5Meta(f, 'info', self._info, **dsOpts) + f.close() + + def writeHDF5Meta(self, root, name, data, **dsOpts): + if isinstance(data, np.ndarray): + dsOpts['maxshape'] = (None,) + data.shape[1:] + root.create_dataset(name, data=data, **dsOpts) + elif isinstance(data, list) or isinstance(data, tuple): + gr = root.create_group(name) + if isinstance(data, list): + gr.attrs['_metaType_'] = 'list' + else: + gr.attrs['_metaType_'] = 'tuple' + #n = int(np.log10(len(data))) + 1 + for i in range(len(data)): + self.writeHDF5Meta(gr, str(i), data[i], **dsOpts) + elif isinstance(data, dict): + gr = root.create_group(name) + gr.attrs['_metaType_'] = 'dict' + for k, v in data.items(): + self.writeHDF5Meta(gr, k, v, **dsOpts) + elif isinstance(data, int) or isinstance(data, float) or isinstance(data, np.integer) or isinstance(data, np.floating): + root.attrs[name] = data + else: + try: ## strings, bools, None are stored as repr() strings + root.attrs[name] = repr(data) + except: + print("Can not store meta data of type '%s' in HDF5. (key is '%s')" % (str(type(data)), str(name))) + raise + + + def writeMa(self, fileName, appendAxis=None, newFile=False): + """Write an old-style .ma file""" + meta = {'shape':self.shape, 'type':str(self.dtype), 'info':self.infoCopy(), 'version':MetaArray.version} + axstrs = [] + + ## copy out axis values for dynamic axis if requested + if appendAxis is not None: + if MetaArray.isNameType(appendAxis): + appendAxis = self._interpretAxis(appendAxis) + + + ax = meta['info'][appendAxis] + ax['values_len'] = 'dynamic' + if 'values' in ax: + ax['values_type'] = str(ax['values'].dtype) + dynXVals = ax['values'] + del ax['values'] + else: + dynXVals = None + + ## Generate axis data string, modify axis info so we know how to read it back in later + for ax in meta['info']: + if 'values' in ax: + axstrs.append(ax['values'].tostring()) + ax['values_len'] = len(axstrs[-1]) + ax['values_type'] = str(ax['values'].dtype) + del ax['values'] + + ## Decide whether to output the meta block for a new file + if not newFile: + ## If the file does not exist or its size is 0, then we must write the header + newFile = (not os.path.exists(fileName)) or (os.stat(fileName).st_size == 0) + + ## write data to file + if appendAxis is None or newFile: + fd = open(fileName, 'wb') + fd.write(str(meta) + '\n\n') + for ax in axstrs: + fd.write(ax) + else: + fd = open(fileName, 'ab') + + if self.dtype != object: + dataStr = self.view(np.ndarray).tostring() + else: + dataStr = pickle.dumps(self.view(np.ndarray)) + #print self.size, len(dataStr), self.dtype + if appendAxis is not None: + frameInfo = {'len':len(dataStr), 'numFrames':self.shape[appendAxis]} + if dynXVals is not None: + frameInfo['xVals'] = list(dynXVals) + fd.write('\n'+str(frameInfo)+'\n') + fd.write(dataStr) + fd.close() + + def writeCsv(self, fileName=None): + """Write 2D array to CSV file or return the string if no filename is given""" + if self.ndim > 2: + raise Exception("CSV Export is only for 2D arrays") + if fileName is not None: + file = open(fileName, 'w') + ret = '' + if 'cols' in self._info[0]: + s = ','.join([x['name'] for x in self._info[0]['cols']]) + '\n' + if fileName is not None: + file.write(s) + else: + ret += s + for row in range(0, self.shape[1]): + s = ','.join(["%g" % x for x in self[:, row]]) + '\n' + if fileName is not None: + file.write(s) + else: + ret += s + if fileName is not None: + file.close() + else: + return ret + + + +#class H5MetaList(): + + +#def rewriteContiguous(fileName, newName): + #"""Rewrite a dynamic array file as contiguous""" + #def _readData2(fd, meta, subtype, mmap): + ### read in axis values + #dynAxis = None + #frameSize = 1 + ### read in axis values for any axis that specifies a length + #for i in range(len(meta['info'])): + #ax = meta['info'][i] + #if ax.has_key('values_len'): + #if ax['values_len'] == 'dynamic': + #if dynAxis is not None: + #raise Exception("MetaArray has more than one dynamic axis! (this is not allowed)") + #dynAxis = i + #else: + #ax['values'] = fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) + #frameSize *= ax['values_len'] + #del ax['values_len'] + #del ax['values_type'] + + ### No axes are dynamic, just read the entire array in at once + #if dynAxis is None: + #raise Exception('Array has no dynamic axes.') + ### One axis is dynamic, read in a frame at a time + #else: + #if mmap: + #raise Exception('memmap not supported for non-contiguous arrays. Use rewriteContiguous() to convert.') + #ax = meta['info'][dynAxis] + #xVals = [] + #frames = [] + #frameShape = list(meta['shape']) + #frameShape[dynAxis] = 1 + #frameSize = reduce(lambda a,b: a*b, frameShape) + #n = 0 + #while True: + ### Extract one non-blank line + #while True: + #line = fd.readline() + #if line != '\n': + #break + #if line == '': + #break + + ### evaluate line + #inf = eval(line) + + ### read data block + ##print "read %d bytes as %s" % (inf['len'], meta['type']) + #if meta['type'] == 'object': + #data = pickle.loads(fd.read(inf['len'])) + #else: + #data = fromstring(fd.read(inf['len']), dtype=meta['type']) + + #if data.size != frameSize * inf['numFrames']: + ##print data.size, frameSize, inf['numFrames'] + #raise Exception("Wrong frame size in MetaArray file! (frame %d)" % n) + + ### read in data block + #shape = list(frameShape) + #shape[dynAxis] = inf['numFrames'] + #data.shape = shape + #frames.append(data) + + #n += inf['numFrames'] + #if 'xVals' in inf: + #xVals.extend(inf['xVals']) + #subarr = np.concatenate(frames, axis=dynAxis) + #if len(xVals)> 0: + #ax['values'] = array(xVals, dtype=ax['values_type']) + #del ax['values_len'] + #del ax['values_type'] + #subarr = subarr.view(subtype) + #subarr._info = meta['info'] + #return subarr + + + + + +if __name__ == '__main__': + ## Create an array with every option possible + + arr = np.zeros((2, 5, 3, 5), dtype=int) + for i in range(arr.shape[0]): + for j in range(arr.shape[1]): + for k in range(arr.shape[2]): + for l in range(arr.shape[3]): + arr[i,j,k,l] = (i+1)*1000 + (j+1)*100 + (k+1)*10 + (l+1) + + info = [ + axis('Axis1'), + axis('Axis2', values=[1,2,3,4,5]), + axis('Axis3', cols=[ + ('Ax3Col1'), + ('Ax3Col2', 'mV', 'Axis3 Column2'), + (('Ax3','Col3'), 'A', 'Axis3 Column3')]), + {'name': 'Axis4', 'values': np.array([1.1, 1.2, 1.3, 1.4, 1.5]), 'units': 's'}, + {'extra': 'info'} + ] + + ma = MetaArray(arr, info=info) + + print("==== Original Array =======") + print(ma) + print("\n\n") + + #### Tests follow: + + + #### Index/slice tests: check that all values and meta info are correct after slice + print("\n -- normal integer indexing\n") + + print("\n ma[1]") + print(ma[1]) + + print("\n ma[1, 2:4]") + print(ma[1, 2:4]) + + print("\n ma[1, 1:5:2]") + print(ma[1, 1:5:2]) + + print("\n -- named axis indexing\n") + + print("\n ma['Axis2':3]") + print(ma['Axis2':3]) + + print("\n ma['Axis2':3:5]") + print(ma['Axis2':3:5]) + + print("\n ma[1, 'Axis2':3]") + print(ma[1, 'Axis2':3]) + + print("\n ma[:, 'Axis2':3]") + print(ma[:, 'Axis2':3]) + + print("\n ma['Axis2':3, 'Axis4':0:2]") + print(ma['Axis2':3, 'Axis4':0:2]) + + + print("\n -- column name indexing\n") + + print("\n ma['Axis3':'Ax3Col1']") + print(ma['Axis3':'Ax3Col1']) + + print("\n ma['Axis3':('Ax3','Col3')]") + print(ma['Axis3':('Ax3','Col3')]) + + print("\n ma[:, :, 'Ax3Col2']") + print(ma[:, :, 'Ax3Col2']) + + print("\n ma[:, :, ('Ax3','Col3')]") + print(ma[:, :, ('Ax3','Col3')]) + + + print("\n -- axis value range indexing\n") + + print("\n ma['Axis2':1.5:4.5]") + print(ma['Axis2':1.5:4.5]) + + print("\n ma['Axis4':1.15:1.45]") + print(ma['Axis4':1.15:1.45]) + + print("\n ma['Axis4':1.15:1.25]") + print(ma['Axis4':1.15:1.25]) + + + + print("\n -- list indexing\n") + + print("\n ma[:, [0,2,4]]") + print(ma[:, [0,2,4]]) + + print("\n ma['Axis4':[0,2,4]]") + print(ma['Axis4':[0,2,4]]) + + print("\n ma['Axis3':[0, ('Ax3','Col3')]]") + print(ma['Axis3':[0, ('Ax3','Col3')]]) + + + + print("\n -- boolean indexing\n") + + print("\n ma[:, array([True, True, False, True, False])]") + print(ma[:, np.array([True, True, False, True, False])]) + + print("\n ma['Axis4':array([True, False, False, False])]") + print(ma['Axis4':np.array([True, False, False, False])]) + + + + + + #### Array operations + # - Concatenate + # - Append + # - Extend + # - Rowsort + + + + + #### File I/O tests + + print("\n================ File I/O Tests ===================\n") + import tempfile + tf = tempfile.mktemp() + tf = 'test.ma' + # write whole array + + print("\n -- write/read test") + ma.write(tf) + ma2 = MetaArray(file=tf) + + #print ma2 + print("\nArrays are equivalent:", (ma == ma2).all()) + #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() + os.remove(tf) + + # CSV write + + # append mode + + + print("\n================append test (%s)===============" % tf) + ma['Axis2':0:2].write(tf, appendAxis='Axis2') + for i in range(2,ma.shape[1]): + ma['Axis2':[i]].write(tf, appendAxis='Axis2') + + ma2 = MetaArray(file=tf) + + #print ma2 + print("\nArrays are equivalent:", (ma == ma2).all()) + #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() + + os.remove(tf) + + + + ## memmap test + print("\n==========Memmap test============") + ma.write(tf, mappable=True) + ma2 = MetaArray(file=tf, mmap=True) + print("\nArrays are equivalent:", (ma == ma2).all()) + os.remove(tf) + diff --git a/papi/pyqtgraph/metaarray/__init__.py b/papi/pyqtgraph/metaarray/__init__.py new file mode 100644 index 00000000..a12f40d5 --- /dev/null +++ b/papi/pyqtgraph/metaarray/__init__.py @@ -0,0 +1 @@ +from .MetaArray import * diff --git a/papi/pyqtgraph/metaarray/license.txt b/papi/pyqtgraph/metaarray/license.txt new file mode 100644 index 00000000..7ef3e5e9 --- /dev/null +++ b/papi/pyqtgraph/metaarray/license.txt @@ -0,0 +1,8 @@ +Copyright (c) 2010 Luke Campagnola ('luke.campagnola@%s.com' % 'gmail') +The MIT License +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/papi/pyqtgraph/metaarray/readMeta.m b/papi/pyqtgraph/metaarray/readMeta.m new file mode 100644 index 00000000..b18ad49d --- /dev/null +++ b/papi/pyqtgraph/metaarray/readMeta.m @@ -0,0 +1,86 @@ +function f = readMeta(file) +info = hdf5info(file); +f = readMetaRecursive(info.GroupHierarchy.Groups(1)); +end + + +function f = readMetaRecursive(root) +typ = 0; +for i = 1:length(root.Attributes) + if strcmp(root.Attributes(i).Shortname, '_metaType_') + typ = root.Attributes(i).Value.Data; + break + end +end +if typ == 0 + printf('group has no _metaType_') + typ = 'dict'; +end + +list = 0; +if strcmp(typ, 'list') || strcmp(typ, 'tuple') + data = {}; + list = 1; +elseif strcmp(typ, 'dict') + data = struct(); +else + printf('Unrecognized meta type %s', typ); + data = struct(); +end + +for i = 1:length(root.Attributes) + name = root.Attributes(i).Shortname; + if strcmp(name, '_metaType_') + continue + end + val = root.Attributes(i).Value; + if isa(val, 'hdf5.h5string') + val = val.Data; + end + if list + ind = str2num(name)+1; + data{ind} = val; + else + data.(name) = val; + end +end + +for i = 1:length(root.Datasets) + fullName = root.Datasets(i).Name; + name = stripName(fullName); + file = root.Datasets(i).Filename; + data2 = hdf5read(file, fullName); + if list + ind = str2num(name)+1; + data{ind} = data2; + else + data.(name) = data2; + end +end + +for i = 1:length(root.Groups) + name = stripName(root.Groups(i).Name); + data2 = readMetaRecursive(root.Groups(i)); + if list + ind = str2num(name)+1; + data{ind} = data2; + else + data.(name) = data2; + end +end +f = data; +return; +end + + +function f = stripName(str) +inds = strfind(str, '/'); +if isempty(inds) + f = str; +else + f = str(inds(length(inds))+1:length(str)); +end +end + + + diff --git a/papi/pyqtgraph/multiprocess/__init__.py b/papi/pyqtgraph/multiprocess/__init__.py new file mode 100644 index 00000000..843b42a3 --- /dev/null +++ b/papi/pyqtgraph/multiprocess/__init__.py @@ -0,0 +1,24 @@ +""" +Multiprocessing utility library +(parallelization done the way I like it) + +Luke Campagnola +2012.06.10 + +This library provides: + + - simple mechanism for starting a new python interpreter process that can be controlled from the original process + (this allows, for example, displaying and manipulating plots in a remote process + while the parent process is free to do other work) + - proxy system that allows objects hosted in the remote process to be used as if they were local + - Qt signal connection between processes + - very simple in-line parallelization (fork only; does not work on windows) for number-crunching + +TODO: + allow remote processes to serve as rendering engines that pass pixmaps back to the parent process for display + (RemoteGraphicsView class) +""" + +from .processes import * +from .parallelizer import Parallelize, CanceledError +from .remoteproxy import proxy \ No newline at end of file diff --git a/papi/pyqtgraph/multiprocess/bootstrap.py b/papi/pyqtgraph/multiprocess/bootstrap.py new file mode 100644 index 00000000..bb71a703 --- /dev/null +++ b/papi/pyqtgraph/multiprocess/bootstrap.py @@ -0,0 +1,28 @@ +"""For starting up remote processes""" +import sys, pickle, os + +if __name__ == '__main__': + if hasattr(os, 'setpgrp'): + os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process + if sys.version[0] == '3': + #name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin.buffer) + opts = pickle.load(sys.stdin.buffer) + else: + #name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin) + opts = pickle.load(sys.stdin) + #print "key:", ' '.join([str(ord(x)) for x in authkey]) + path = opts.pop('path', None) + if path is not None: + ## rewrite sys.path without assigning a new object--no idea who already has a reference to the existing list. + while len(sys.path) > 0: + sys.path.pop() + sys.path.extend(path) + + if opts.pop('pyside', False): + import PySide + + + targetStr = opts.pop('targetStr') + target = pickle.loads(targetStr) ## unpickling the target should import everything we need + target(**opts) ## Send all other options to the target function + sys.exit(0) diff --git a/papi/pyqtgraph/multiprocess/parallelizer.py b/papi/pyqtgraph/multiprocess/parallelizer.py new file mode 100644 index 00000000..f4ddd95c --- /dev/null +++ b/papi/pyqtgraph/multiprocess/parallelizer.py @@ -0,0 +1,330 @@ +import os, sys, time, multiprocessing, re +from .processes import ForkedProcess +from .remoteproxy import ClosedError + +class CanceledError(Exception): + """Raised when the progress dialog is canceled during a processing operation.""" + pass + +class Parallelize(object): + """ + Class for ultra-simple inline parallelization on multi-core CPUs + + Example:: + + ## Here is the serial (single-process) task: + + tasks = [1, 2, 4, 8] + results = [] + for task in tasks: + result = processTask(task) + results.append(result) + print(results) + + + ## Here is the parallelized version: + + tasks = [1, 2, 4, 8] + results = [] + with Parallelize(tasks, workers=4, results=results) as tasker: + for task in tasker: + result = processTask(task) + tasker.results.append(result) + print(results) + + + The only major caveat is that *result* in the example above must be picklable, + since it is automatically sent via pipe back to the parent process. + """ + + def __init__(self, tasks=None, workers=None, block=True, progressDialog=None, randomReseed=True, **kwds): + """ + =============== =================================================================== + **Arguments:** + tasks list of objects to be processed (Parallelize will determine how to + distribute the tasks). If unspecified, then each worker will receive + a single task with a unique id number. + workers number of worker processes or None to use number of CPUs in the + system + progressDialog optional dict of arguments for ProgressDialog + to update while tasks are processed + randomReseed If True, each forked process will reseed its random number generator + to ensure independent results. Works with the built-in random + and numpy.random. + kwds objects to be shared by proxy with child processes (they will + appear as attributes of the tasker) + =============== =================================================================== + """ + + ## Generate progress dialog. + ## Note that we want to avoid letting forked child processes play with progress dialogs.. + self.showProgress = False + if progressDialog is not None: + self.showProgress = True + if isinstance(progressDialog, basestring): + progressDialog = {'labelText': progressDialog} + from ..widgets.ProgressDialog import ProgressDialog + self.progressDlg = ProgressDialog(**progressDialog) + + if workers is None: + workers = self.suggestedWorkerCount() + if not hasattr(os, 'fork'): + workers = 1 + self.workers = workers + if tasks is None: + tasks = range(workers) + self.tasks = list(tasks) + self.reseed = randomReseed + self.kwds = kwds.copy() + self.kwds['_taskStarted'] = self._taskStarted + + def __enter__(self): + self.proc = None + if self.workers == 1: + return self.runSerial() + else: + return self.runParallel() + + def __exit__(self, *exc_info): + + if self.proc is not None: ## worker + exceptOccurred = exc_info[0] is not None ## hit an exception during processing. + + try: + if exceptOccurred: + sys.excepthook(*exc_info) + finally: + #print os.getpid(), 'exit' + os._exit(1 if exceptOccurred else 0) + + else: ## parent + if self.showProgress: + self.progressDlg.__exit__(None, None, None) + + def runSerial(self): + if self.showProgress: + self.progressDlg.__enter__() + self.progressDlg.setMaximum(len(self.tasks)) + self.progress = {os.getpid(): []} + return Tasker(self, None, self.tasks, self.kwds) + + + def runParallel(self): + self.childs = [] + + ## break up tasks into one set per worker + workers = self.workers + chunks = [[] for i in xrange(workers)] + i = 0 + for i in range(len(self.tasks)): + chunks[i%workers].append(self.tasks[i]) + + ## fork and assign tasks to each worker + for i in range(workers): + proc = ForkedProcess(target=None, preProxy=self.kwds, randomReseed=self.reseed) + if not proc.isParent: + self.proc = proc + return Tasker(self, proc, chunks[i], proc.forkedProxies) + else: + self.childs.append(proc) + + ## Keep track of the progress of each worker independently. + self.progress = dict([(ch.childPid, []) for ch in self.childs]) + ## for each child process, self.progress[pid] is a list + ## of task indexes. The last index is the task currently being + ## processed; all others are finished. + + + try: + if self.showProgress: + self.progressDlg.__enter__() + self.progressDlg.setMaximum(len(self.tasks)) + ## process events from workers until all have exited. + + activeChilds = self.childs[:] + self.exitCodes = [] + pollInterval = 0.01 + while len(activeChilds) > 0: + waitingChildren = 0 + rem = [] + for ch in activeChilds: + try: + n = ch.processRequests() + if n > 0: + waitingChildren += 1 + except ClosedError: + #print ch.childPid, 'process finished' + rem.append(ch) + if self.showProgress: + self.progressDlg += 1 + #print "remove:", [ch.childPid for ch in rem] + for ch in rem: + activeChilds.remove(ch) + while True: + try: + pid, exitcode = os.waitpid(ch.childPid, 0) + self.exitCodes.append(exitcode) + break + except OSError as ex: + if ex.errno == 4: ## If we get this error, just try again + continue + #print "Ignored system call interruption" + else: + raise + + #print [ch.childPid for ch in activeChilds] + + if self.showProgress and self.progressDlg.wasCanceled(): + for ch in activeChilds: + ch.kill() + raise CanceledError() + + ## adjust polling interval--prefer to get exactly 1 event per poll cycle. + if waitingChildren > 1: + pollInterval *= 0.7 + elif waitingChildren == 0: + pollInterval /= 0.7 + pollInterval = max(min(pollInterval, 0.5), 0.0005) ## but keep it within reasonable limits + + time.sleep(pollInterval) + finally: + if self.showProgress: + self.progressDlg.__exit__(None, None, None) + if len(self.exitCodes) < len(self.childs): + raise Exception("Parallelizer started %d processes but only received exit codes from %d." % (len(self.childs), len(self.exitCodes))) + for code in self.exitCodes: + if code != 0: + raise Exception("Error occurred in parallel-executed subprocess (console output may have more information).") + return [] ## no tasks for parent process. + + + @staticmethod + def suggestedWorkerCount(): + if 'linux' in sys.platform: + ## I think we can do a little better here.. + ## cpu_count does not consider that there is little extra benefit to using hyperthreaded cores. + try: + cores = {} + pid = None + + for line in open('/proc/cpuinfo'): + m = re.match(r'physical id\s+:\s+(\d+)', line) + if m is not None: + pid = m.groups()[0] + m = re.match(r'cpu cores\s+:\s+(\d+)', line) + if m is not None: + cores[pid] = int(m.groups()[0]) + return sum(cores.values()) + except: + return multiprocessing.cpu_count() + + else: + return multiprocessing.cpu_count() + + def _taskStarted(self, pid, i, **kwds): + ## called remotely by tasker to indicate it has started working on task i + #print pid, 'reported starting task', i + if self.showProgress: + if len(self.progress[pid]) > 0: + self.progressDlg += 1 + if pid == os.getpid(): ## single-worker process + if self.progressDlg.wasCanceled(): + raise CanceledError() + self.progress[pid].append(i) + + +class Tasker(object): + def __init__(self, parallelizer, process, tasks, kwds): + self.proc = process + self.par = parallelizer + self.tasks = tasks + for k, v in kwds.iteritems(): + setattr(self, k, v) + + def __iter__(self): + ## we could fix this up such that tasks are retrieved from the parent process one at a time.. + for i, task in enumerate(self.tasks): + self.index = i + #print os.getpid(), 'starting task', i + self._taskStarted(os.getpid(), i, _callSync='off') + yield task + if self.proc is not None: + #print os.getpid(), 'no more tasks' + self.proc.close() + + def process(self): + """ + Process requests from parent. + Usually it is not necessary to call this unless you would like to + receive messages (such as exit requests) during an iteration. + """ + if self.proc is not None: + self.proc.processRequests() + + def numWorkers(self): + """ + Return the number of parallel workers + """ + return self.par.workers + +#class Parallelizer: + #""" + #Use:: + + #p = Parallelizer() + #with p(4) as i: + #p.finish(do_work(i)) + #print p.results() + + #""" + #def __init__(self): + #pass + + #def __call__(self, n): + #self.replies = [] + #self.conn = None ## indicates this is the parent process + #return Session(self, n) + + #def finish(self, data): + #if self.conn is None: + #self.replies.append((self.i, data)) + #else: + ##print "send", self.i, data + #self.conn.send((self.i, data)) + #os._exit(0) + + #def result(self): + #print self.replies + +#class Session: + #def __init__(self, par, n): + #self.par = par + #self.n = n + + #def __enter__(self): + #self.childs = [] + #for i in range(1, self.n): + #c1, c2 = multiprocessing.Pipe() + #pid = os.fork() + #if pid == 0: ## child + #self.par.i = i + #self.par.conn = c2 + #self.childs = None + #c1.close() + #return i + #else: + #self.childs.append(c1) + #c2.close() + #self.par.i = 0 + #return 0 + + + + #def __exit__(self, *exc_info): + #if exc_info[0] is not None: + #sys.excepthook(*exc_info) + #if self.childs is not None: + #self.par.replies.extend([conn.recv() for conn in self.childs]) + #else: + #self.par.finish(None) + diff --git a/papi/pyqtgraph/multiprocess/processes.py b/papi/pyqtgraph/multiprocess/processes.py new file mode 100644 index 00000000..0dfb80b9 --- /dev/null +++ b/papi/pyqtgraph/multiprocess/processes.py @@ -0,0 +1,478 @@ +import subprocess, atexit, os, sys, time, random, socket, signal +import multiprocessing.connection +try: + import cPickle as pickle +except ImportError: + import pickle + +from .remoteproxy import RemoteEventHandler, ClosedError, NoResultError, LocalObjectProxy, ObjectProxy +from ..Qt import USE_PYSIDE +from ..util import cprint # color printing for debugging + + +__all__ = ['Process', 'QtProcess', 'ForkedProcess', 'ClosedError', 'NoResultError'] + +class Process(RemoteEventHandler): + """ + Bases: RemoteEventHandler + + This class is used to spawn and control a new python interpreter. + It uses subprocess.Popen to start the new process and communicates with it + using multiprocessing.Connection objects over a network socket. + + By default, the remote process will immediately enter an event-processing + loop that carries out requests send from the parent process. + + Remote control works mainly through proxy objects:: + + proc = Process() ## starts process, returns handle + rsys = proc._import('sys') ## asks remote process to import 'sys', returns + ## a proxy which references the imported module + rsys.stdout.write('hello\n') ## This message will be printed from the remote + ## process. Proxy objects can usually be used + ## exactly as regular objects are. + proc.close() ## Request the remote process shut down + + Requests made via proxy objects may be synchronous or asynchronous and may + return objects either by proxy or by value (if they are picklable). See + ProxyObject for more information. + """ + _process_count = 1 # just used for assigning colors to each process for debugging + + def __init__(self, name=None, target=None, executable=None, copySysPath=True, debug=False, timeout=20, wrapStdout=None): + """ + ============== ============================================================= + **Arguments:** + name Optional name for this process used when printing messages + from the remote process. + target Optional function to call after starting remote process. + By default, this is startEventLoop(), which causes the remote + process to process requests from the parent process until it + is asked to quit. If you wish to specify a different target, + it must be picklable (bound methods are not). + copySysPath If True, copy the contents of sys.path to the remote process + debug If True, print detailed information about communication + with the child process. + wrapStdout If True (default on windows) then stdout and stderr from the + child process will be caught by the parent process and + forwarded to its stdout/stderr. This provides a workaround + for a python bug: http://bugs.python.org/issue3905 + but has the side effect that child output is significantly + delayed relative to the parent output. + ============== ============================================================= + """ + if target is None: + target = startEventLoop + if name is None: + name = str(self) + if executable is None: + executable = sys.executable + self.debug = 7 if debug is True else False # 7 causes printing in white + + ## random authentication key + authkey = os.urandom(20) + + ## Windows seems to have a hard time with hmac + if sys.platform.startswith('win'): + authkey = None + + #print "key:", ' '.join([str(ord(x)) for x in authkey]) + ## Listen for connection from remote process (and find free port number) + l = multiprocessing.connection.Listener(('localhost', 0), authkey=authkey) + port = l.address[1] + + ## start remote process, instruct it to run target function + sysPath = sys.path if copySysPath else None + bootstrap = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bootstrap.py')) + self.debugMsg('Starting child process (%s %s)' % (executable, bootstrap)) + + # Decide on printing color for this process + if debug: + procDebug = (Process._process_count%6) + 1 # pick a color for this process to print in + Process._process_count += 1 + else: + procDebug = False + + if wrapStdout is None: + wrapStdout = sys.platform.startswith('win') + + if wrapStdout: + ## note: we need all three streams to have their own PIPE due to this bug: + ## http://bugs.python.org/issue3905 + stdout = subprocess.PIPE + stderr = subprocess.PIPE + self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE, stdout=stdout, stderr=stderr) + ## to circumvent the bug and still make the output visible, we use + ## background threads to pass data from pipes to stdout/stderr + self._stdoutForwarder = FileForwarder(self.proc.stdout, "stdout", procDebug) + self._stderrForwarder = FileForwarder(self.proc.stderr, "stderr", procDebug) + else: + self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE) + + targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to + ## set its sys.path properly before unpickling the target + pid = os.getpid() # we must send pid to child because windows does not have getppid + + ## Send everything the remote process needs to start correctly + data = dict( + name=name+'_child', + port=port, + authkey=authkey, + ppid=pid, + targetStr=targetStr, + path=sysPath, + pyside=USE_PYSIDE, + debug=procDebug + ) + pickle.dump(data, self.proc.stdin) + self.proc.stdin.close() + + ## open connection for remote process + self.debugMsg('Listening for child process on port %d, authkey=%s..' % (port, repr(authkey))) + while True: + try: + conn = l.accept() + break + except IOError as err: + if err.errno == 4: # interrupted; try again + continue + else: + raise + + RemoteEventHandler.__init__(self, conn, name+'_parent', pid=self.proc.pid, debug=self.debug) + self.debugMsg('Connected to child process.') + + atexit.register(self.join) + + + def join(self, timeout=10): + self.debugMsg('Joining child process..') + if self.proc.poll() is None: + self.close() + start = time.time() + while self.proc.poll() is None: + if timeout is not None and time.time() - start > timeout: + raise Exception('Timed out waiting for remote process to end.') + time.sleep(0.05) + self.debugMsg('Child process exited. (%d)' % self.proc.returncode) + + def debugMsg(self, msg): + if hasattr(self, '_stdoutForwarder'): + ## Lock output from subprocess to make sure we do not get line collisions + with self._stdoutForwarder.lock: + with self._stderrForwarder.lock: + RemoteEventHandler.debugMsg(self, msg) + else: + RemoteEventHandler.debugMsg(self, msg) + + +def startEventLoop(name, port, authkey, ppid, debug=False): + if debug: + import os + cprint.cout(debug, '[%d] connecting to server at port localhost:%d, authkey=%s..\n' + % (os.getpid(), port, repr(authkey)), -1) + conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) + if debug: + cprint.cout(debug, '[%d] connected; starting remote proxy.\n' % os.getpid(), -1) + global HANDLER + #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() + HANDLER = RemoteEventHandler(conn, name, ppid, debug=debug) + while True: + try: + HANDLER.processRequests() # exception raised when the loop should exit + time.sleep(0.01) + except ClosedError: + break + + +class ForkedProcess(RemoteEventHandler): + """ + ForkedProcess is a substitute for Process that uses os.fork() to generate a new process. + This is much faster than starting a completely new interpreter and child processes + automatically have a copy of the entire program state from before the fork. This + makes it an appealing approach when parallelizing expensive computations. (see + also Parallelizer) + + However, fork() comes with some caveats and limitations: + + - fork() is not available on Windows. + - It is not possible to have a QApplication in both parent and child process + (unless both QApplications are created _after_ the call to fork()) + Attempts by the forked process to access Qt GUI elements created by the parent + will most likely cause the child to crash. + - Likewise, database connections are unlikely to function correctly in a forked child. + - Threads are not copied by fork(); the new process + will have only one thread that starts wherever fork() was called in the parent process. + - Forked processes are unceremoniously terminated when join() is called; they are not + given any opportunity to clean up. (This prevents them calling any cleanup code that + was only intended to be used by the parent process) + - Normally when fork()ing, open file handles are shared with the parent process, + which is potentially dangerous. ForkedProcess is careful to close all file handles + that are not explicitly needed--stdout, stderr, and a single pipe to the parent + process. + + """ + + def __init__(self, name=None, target=0, preProxy=None, randomReseed=True): + """ + When initializing, an optional target may be given. + If no target is specified, self.eventLoop will be used. + If None is given, no target will be called (and it will be up + to the caller to properly shut down the forked process) + + preProxy may be a dict of values that will appear as ObjectProxy + in the remote process (but do not need to be sent explicitly since + they are available immediately before the call to fork(). + Proxies will be availabe as self.proxies[name]. + + If randomReseed is True, the built-in random and numpy.random generators + will be reseeded in the child process. + """ + self.hasJoined = False + if target == 0: + target = self.eventLoop + if name is None: + name = str(self) + + conn, remoteConn = multiprocessing.Pipe() + + proxyIDs = {} + if preProxy is not None: + for k, v in preProxy.iteritems(): + proxyId = LocalObjectProxy.registerObject(v) + proxyIDs[k] = proxyId + + ppid = os.getpid() # write this down now; windows doesn't have getppid + pid = os.fork() + if pid == 0: + self.isParent = False + ## We are now in the forked process; need to be extra careful what we touch while here. + ## - no reading/writing file handles/sockets owned by parent process (stdout is ok) + ## - don't touch QtGui or QApplication at all; these are landmines. + ## - don't let the process call exit handlers + + os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process + + ## close all file handles we do not want shared with parent + conn.close() + sys.stdin.close() ## otherwise we screw with interactive prompts. + fid = remoteConn.fileno() + os.closerange(3, fid) + os.closerange(fid+1, 4096) ## just guessing on the maximum descriptor count.. + + ## Override any custom exception hooks + def excepthook(*args): + import traceback + traceback.print_exception(*args) + sys.excepthook = excepthook + + ## Make it harder to access QApplication instance + if 'PyQt4.QtGui' in sys.modules: + sys.modules['PyQt4.QtGui'].QApplication = None + sys.modules.pop('PyQt4.QtGui', None) + sys.modules.pop('PyQt4.QtCore', None) + + ## sabotage atexit callbacks + atexit._exithandlers = [] + atexit.register(lambda: os._exit(0)) + + if randomReseed: + if 'numpy.random' in sys.modules: + sys.modules['numpy.random'].seed(os.getpid() ^ int(time.time()*10000%10000)) + if 'random' in sys.modules: + sys.modules['random'].seed(os.getpid() ^ int(time.time()*10000%10000)) + + #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() + RemoteEventHandler.__init__(self, remoteConn, name+'_child', pid=ppid) + + self.forkedProxies = {} + for name, proxyId in proxyIDs.iteritems(): + self.forkedProxies[name] = ObjectProxy(ppid, proxyId=proxyId, typeStr=repr(preProxy[name])) + + if target is not None: + target() + + else: + self.isParent = True + self.childPid = pid + remoteConn.close() + RemoteEventHandler.handlers = {} ## don't want to inherit any of this from the parent. + + RemoteEventHandler.__init__(self, conn, name+'_parent', pid=pid) + atexit.register(self.join) + + + def eventLoop(self): + while True: + try: + self.processRequests() # exception raised when the loop should exit + time.sleep(0.01) + except ClosedError: + break + except: + print("Error occurred in forked event loop:") + sys.excepthook(*sys.exc_info()) + sys.exit(0) + + def join(self, timeout=10): + if self.hasJoined: + return + #os.kill(pid, 9) + try: + self.close(callSync='sync', timeout=timeout, noCleanup=True) ## ask the child process to exit and require that it return a confirmation. + os.waitpid(self.childPid, 0) + except IOError: ## probably remote process has already quit + pass + self.hasJoined = True + + def kill(self): + """Immediately kill the forked remote process. + This is generally safe because forked processes are already + expected to _avoid_ any cleanup at exit.""" + os.kill(self.childPid, signal.SIGKILL) + self.hasJoined = True + + + +##Special set of subclasses that implement a Qt event loop instead. + +class RemoteQtEventHandler(RemoteEventHandler): + def __init__(self, *args, **kwds): + RemoteEventHandler.__init__(self, *args, **kwds) + + def startEventTimer(self): + from ..Qt import QtGui, QtCore + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.processRequests) + self.timer.start(10) + + def processRequests(self): + try: + RemoteEventHandler.processRequests(self) + except ClosedError: + from ..Qt import QtGui, QtCore + QtGui.QApplication.instance().quit() + self.timer.stop() + #raise SystemExit + +class QtProcess(Process): + """ + QtProcess is essentially the same as Process, with two major differences: + + - The remote process starts by running startQtEventLoop() which creates a + QApplication in the remote process and uses a QTimer to trigger + remote event processing. This allows the remote process to have its own + GUI. + - A QTimer is also started on the parent process which polls for requests + from the child process. This allows Qt signals emitted within the child + process to invoke slots on the parent process and vice-versa. This can + be disabled using processRequests=False in the constructor. + + Example:: + + proc = QtProcess() + rQtGui = proc._import('PyQt4.QtGui') + btn = rQtGui.QPushButton('button on child process') + btn.show() + + def slot(): + print('slot invoked on parent process') + btn.clicked.connect(proxy(slot)) # be sure to send a proxy of the slot + """ + + def __init__(self, **kwds): + if 'target' not in kwds: + kwds['target'] = startQtEventLoop + from ..Qt import QtGui ## avoid module-level import to keep bootstrap snappy. + self._processRequests = kwds.pop('processRequests', True) + if self._processRequests and QtGui.QApplication.instance() is None: + raise Exception("Must create QApplication before starting QtProcess, or use QtProcess(processRequests=False)") + Process.__init__(self, **kwds) + self.startEventTimer() + + def startEventTimer(self): + from ..Qt import QtCore ## avoid module-level import to keep bootstrap snappy. + self.timer = QtCore.QTimer() + if self._processRequests: + self.startRequestProcessing() + + def startRequestProcessing(self, interval=0.01): + """Start listening for requests coming from the child process. + This allows signals to be connected from the child process to the parent. + """ + self.timer.timeout.connect(self.processRequests) + self.timer.start(interval*1000) + + def stopRequestProcessing(self): + self.timer.stop() + + def processRequests(self): + try: + Process.processRequests(self) + except ClosedError: + self.timer.stop() + +def startQtEventLoop(name, port, authkey, ppid, debug=False): + if debug: + import os + cprint.cout(debug, '[%d] connecting to server at port localhost:%d, authkey=%s..\n' % (os.getpid(), port, repr(authkey)), -1) + conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) + if debug: + cprint.cout(debug, '[%d] connected; starting remote proxy.\n' % os.getpid(), -1) + from ..Qt import QtGui, QtCore + #from PyQt4 import QtGui, QtCore + app = QtGui.QApplication.instance() + #print app + if app is None: + app = QtGui.QApplication([]) + app.setQuitOnLastWindowClosed(False) ## generally we want the event loop to stay open + ## until it is explicitly closed by the parent process. + + global HANDLER + #ppid = 0 if not hasattr(os, 'getppid') else os.getppid() + HANDLER = RemoteQtEventHandler(conn, name, ppid, debug=debug) + HANDLER.startEventTimer() + app.exec_() + +import threading +class FileForwarder(threading.Thread): + """ + Background thread that forwards data from one pipe to another. + This is used to catch data from stdout/stderr of the child process + and print it back out to stdout/stderr. We need this because this + bug: http://bugs.python.org/issue3905 _requires_ us to catch + stdout/stderr. + + *output* may be a file or 'stdout' or 'stderr'. In the latter cases, + sys.stdout/stderr are retrieved once for every line that is output, + which ensures that the correct behavior is achieved even if + sys.stdout/stderr are replaced at runtime. + """ + def __init__(self, input, output, color): + threading.Thread.__init__(self) + self.input = input + self.output = output + self.lock = threading.Lock() + self.daemon = True + self.color = color + self.start() + + def run(self): + if self.output == 'stdout': + while True: + line = self.input.readline() + with self.lock: + cprint.cout(self.color, line, -1) + elif self.output == 'stderr': + while True: + line = self.input.readline() + with self.lock: + cprint.cerr(self.color, line, -1) + else: + while True: + line = self.input.readline() + with self.lock: + self.output.write(line) + + + diff --git a/papi/pyqtgraph/multiprocess/remoteproxy.py b/papi/pyqtgraph/multiprocess/remoteproxy.py new file mode 100644 index 00000000..4f484b74 --- /dev/null +++ b/papi/pyqtgraph/multiprocess/remoteproxy.py @@ -0,0 +1,1117 @@ +import os, time, sys, traceback, weakref +import numpy as np +import threading +try: + import __builtin__ as builtins + import cPickle as pickle +except ImportError: + import builtins + import pickle + +# color printing for debugging +from ..util import cprint + +class ClosedError(Exception): + """Raised when an event handler receives a request to close the connection + or discovers that the connection has been closed.""" + pass + +class NoResultError(Exception): + """Raised when a request for the return value of a remote call fails + because the call has not yet returned.""" + pass + + +class RemoteEventHandler(object): + """ + This class handles communication between two processes. One instance is present on + each process and listens for communication from the other process. This enables + (amongst other things) ObjectProxy instances to look up their attributes and call + their methods. + + This class is responsible for carrying out actions on behalf of the remote process. + Each instance holds one end of a Connection which allows python + objects to be passed between processes. + + For the most common operations, see _import(), close(), and transfer() + + To handle and respond to incoming requests, RemoteEventHandler requires that its + processRequests method is called repeatedly (this is usually handled by the Process + classes defined in multiprocess.processes). + + + + + """ + handlers = {} ## maps {process ID : handler}. This allows unpickler to determine which process + ## an object proxy belongs to + + def __init__(self, connection, name, pid, debug=False): + self.debug = debug + self.conn = connection + self.name = name + self.results = {} ## reqId: (status, result); cache of request results received from the remote process + ## status is either 'result' or 'error' + ## if 'error', then result will be (exception, formatted exceprion) + ## where exception may be None if it could not be passed through the Connection. + self.resultLock = threading.RLock() + + self.proxies = {} ## maps {weakref(proxy): proxyId}; used to inform the remote process when a proxy has been deleted. + self.proxyLock = threading.RLock() + + ## attributes that affect the behavior of the proxy. + ## See ObjectProxy._setProxyOptions for description + self.proxyOptions = { + 'callSync': 'sync', ## 'sync', 'async', 'off' + 'timeout': 10, ## float + 'returnType': 'auto', ## 'proxy', 'value', 'auto' + 'autoProxy': False, ## bool + 'deferGetattr': False, ## True, False + 'noProxyTypes': [ type(None), str, int, float, tuple, list, dict, LocalObjectProxy, ObjectProxy ], + } + self.optsLock = threading.RLock() + + self.nextRequestId = 0 + self.exited = False + + # Mutexes to help prevent issues when multiple threads access the same RemoteEventHandler + self.processLock = threading.RLock() + self.sendLock = threading.RLock() + + RemoteEventHandler.handlers[pid] = self ## register this handler as the one communicating with pid + + @classmethod + def getHandler(cls, pid): + try: + return cls.handlers[pid] + except: + print(pid, cls.handlers) + raise + + def debugMsg(self, msg): + if not self.debug: + return + cprint.cout(self.debug, "[%d] %s\n" % (os.getpid(), str(msg)), -1) + + def getProxyOption(self, opt): + with self.optsLock: + return self.proxyOptions[opt] + + def setProxyOptions(self, **kwds): + """ + Set the default behavior options for object proxies. + See ObjectProxy._setProxyOptions for more info. + """ + with self.optsLock: + self.proxyOptions.update(kwds) + + def processRequests(self): + """Process all pending requests from the pipe, return + after no more events are immediately available. (non-blocking) + Returns the number of events processed. + """ + with self.processLock: + + if self.exited: + self.debugMsg(' processRequests: exited already; raise ClosedError.') + raise ClosedError() + + numProcessed = 0 + + while self.conn.poll(): + #try: + #poll = self.conn.poll() + #if not poll: + #break + #except IOError: # this can happen if the remote process dies. + ## might it also happen in other circumstances? + #raise ClosedError() + + try: + self.handleRequest() + numProcessed += 1 + except ClosedError: + self.debugMsg('processRequests: got ClosedError from handleRequest; setting exited=True.') + self.exited = True + raise + #except IOError as err: ## let handleRequest take care of this. + #self.debugMsg(' got IOError from handleRequest; try again.') + #if err.errno == 4: ## interrupted system call; try again + #continue + #else: + #raise + except: + print("Error in process %s" % self.name) + sys.excepthook(*sys.exc_info()) + + if numProcessed > 0: + self.debugMsg('processRequests: finished %d requests' % numProcessed) + return numProcessed + + def handleRequest(self): + """Handle a single request from the remote process. + Blocks until a request is available.""" + result = None + while True: + try: + ## args, kwds are double-pickled to ensure this recv() call never fails + cmd, reqId, nByteMsgs, optStr = self.conn.recv() + break + except EOFError: + self.debugMsg(' handleRequest: got EOFError from recv; raise ClosedError.') + ## remote process has shut down; end event loop + raise ClosedError() + except IOError as err: + if err.errno == 4: ## interrupted system call; try again + self.debugMsg(' handleRequest: got IOError 4 from recv; try again.') + continue + else: + self.debugMsg(' handleRequest: got IOError %d from recv (%s); raise ClosedError.' % (err.errno, err.strerror)) + raise ClosedError() + + self.debugMsg(" handleRequest: received %s %s" % (str(cmd), str(reqId))) + + ## read byte messages following the main request + byteData = [] + if nByteMsgs > 0: + self.debugMsg(" handleRequest: reading %d byte messages" % nByteMsgs) + for i in range(nByteMsgs): + while True: + try: + byteData.append(self.conn.recv_bytes()) + break + except EOFError: + self.debugMsg(" handleRequest: got EOF while reading byte messages; raise ClosedError.") + raise ClosedError() + except IOError as err: + if err.errno == 4: + self.debugMsg(" handleRequest: got IOError 4 while reading byte messages; try again.") + continue + else: + self.debugMsg(" handleRequest: got IOError while reading byte messages; raise ClosedError.") + raise ClosedError() + + + try: + if cmd == 'result' or cmd == 'error': + resultId = reqId + reqId = None ## prevents attempt to return information from this request + ## (this is already a return from a previous request) + + opts = pickle.loads(optStr) + self.debugMsg(" handleRequest: id=%s opts=%s" % (str(reqId), str(opts))) + #print os.getpid(), "received request:", cmd, reqId, opts + returnType = opts.get('returnType', 'auto') + + if cmd == 'result': + with self.resultLock: + self.results[resultId] = ('result', opts['result']) + elif cmd == 'error': + with self.resultLock: + self.results[resultId] = ('error', (opts['exception'], opts['excString'])) + elif cmd == 'getObjAttr': + result = getattr(opts['obj'], opts['attr']) + elif cmd == 'callObj': + obj = opts['obj'] + fnargs = opts['args'] + fnkwds = opts['kwds'] + + ## If arrays were sent as byte messages, they must be re-inserted into the + ## arguments + if len(byteData) > 0: + for i,arg in enumerate(fnargs): + if isinstance(arg, tuple) and len(arg) > 0 and arg[0] == '__byte_message__': + ind = arg[1] + dtype, shape = arg[2] + fnargs[i] = np.fromstring(byteData[ind], dtype=dtype).reshape(shape) + for k,arg in fnkwds.items(): + if isinstance(arg, tuple) and len(arg) > 0 and arg[0] == '__byte_message__': + ind = arg[1] + dtype, shape = arg[2] + fnkwds[k] = np.fromstring(byteData[ind], dtype=dtype).reshape(shape) + + if len(fnkwds) == 0: ## need to do this because some functions do not allow keyword arguments. + try: + result = obj(*fnargs) + except: + print("Failed to call object %s: %d, %s" % (obj, len(fnargs), fnargs[1:])) + raise + else: + result = obj(*fnargs, **fnkwds) + + elif cmd == 'getObjValue': + result = opts['obj'] ## has already been unpickled into its local value + returnType = 'value' + elif cmd == 'transfer': + result = opts['obj'] + returnType = 'proxy' + elif cmd == 'transferArray': + ## read array data from next message: + result = np.fromstring(byteData[0], dtype=opts['dtype']).reshape(opts['shape']) + returnType = 'proxy' + elif cmd == 'import': + name = opts['module'] + fromlist = opts.get('fromlist', []) + mod = builtins.__import__(name, fromlist=fromlist) + + if len(fromlist) == 0: + parts = name.lstrip('.').split('.') + result = mod + for part in parts[1:]: + result = getattr(result, part) + else: + result = map(mod.__getattr__, fromlist) + + elif cmd == 'del': + LocalObjectProxy.releaseProxyId(opts['proxyId']) + #del self.proxiedObjects[opts['objId']] + + elif cmd == 'close': + if reqId is not None: + result = True + returnType = 'value' + + exc = None + except: + exc = sys.exc_info() + + + + if reqId is not None: + if exc is None: + self.debugMsg(" handleRequest: sending return value for %d: %s" % (reqId, str(result))) + #print "returnValue:", returnValue, result + if returnType == 'auto': + with self.optsLock: + noProxyTypes = self.proxyOptions['noProxyTypes'] + result = self.autoProxy(result, noProxyTypes) + elif returnType == 'proxy': + result = LocalObjectProxy(result) + + try: + self.replyResult(reqId, result) + except: + sys.excepthook(*sys.exc_info()) + self.replyError(reqId, *sys.exc_info()) + else: + self.debugMsg(" handleRequest: returning exception for %d" % reqId) + self.replyError(reqId, *exc) + + elif exc is not None: + sys.excepthook(*exc) + + if cmd == 'close': + if opts.get('noCleanup', False) is True: + os._exit(0) ## exit immediately, do not pass GO, do not collect $200. + ## (more importantly, do not call any code that would + ## normally be invoked at exit) + else: + raise ClosedError() + + + + def replyResult(self, reqId, result): + self.send(request='result', reqId=reqId, callSync='off', opts=dict(result=result)) + + def replyError(self, reqId, *exc): + print("error: %s %s %s" % (self.name, str(reqId), str(exc[1]))) + excStr = traceback.format_exception(*exc) + try: + self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=exc[1], excString=excStr)) + except: + self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=None, excString=excStr)) + + def send(self, request, opts=None, reqId=None, callSync='sync', timeout=10, returnType=None, byteData=None, **kwds): + """Send a request or return packet to the remote process. + Generally it is not necessary to call this method directly; it is for internal use. + (The docstring has information that is nevertheless useful to the programmer + as it describes the internal protocol used to communicate between processes) + + ============== ==================================================================== + **Arguments:** + request String describing the type of request being sent (see below) + reqId Integer uniquely linking a result back to the request that generated + it. (most requests leave this blank) + callSync 'sync': return the actual result of the request + 'async': return a Request object which can be used to look up the + result later + 'off': return no result + timeout Time in seconds to wait for a response when callSync=='sync' + opts Extra arguments sent to the remote process that determine the way + the request will be handled (see below) + returnType 'proxy', 'value', or 'auto' + byteData If specified, this is a list of objects to be sent as byte messages + to the remote process. + This is used to send large arrays without the cost of pickling. + ============== ==================================================================== + + Description of request strings and options allowed for each: + + ============= ============= ======================================================== + request option description + ------------- ------------- -------------------------------------------------------- + getObjAttr Request the remote process return (proxy to) an + attribute of an object. + obj reference to object whose attribute should be + returned + attr string name of attribute to return + returnValue bool or 'auto' indicating whether to return a proxy or + the actual value. + + callObj Request the remote process call a function or + method. If a request ID is given, then the call's + return value will be sent back (or information + about the error that occurred while running the + function) + obj the (reference to) object to call + args tuple of arguments to pass to callable + kwds dict of keyword arguments to pass to callable + returnValue bool or 'auto' indicating whether to return a proxy or + the actual value. + + getObjValue Request the remote process return the value of + a proxied object (must be picklable) + obj reference to object whose value should be returned + + transfer Copy an object to the remote process and request + it return a proxy for the new object. + obj The object to transfer. + + import Request the remote process import new symbols + and return proxy(ies) to the imported objects + module the string name of the module to import + fromlist optional list of string names to import from module + + del Inform the remote process that a proxy has been + released (thus the remote process may be able to + release the original object) + proxyId id of proxy which is no longer referenced by + remote host + + close Instruct the remote process to stop its event loop + and exit. Optionally, this request may return a + confirmation. + + result Inform the remote process that its request has + been processed + result return value of a request + + error Inform the remote process that its request failed + exception the Exception that was raised (or None if the + exception could not be pickled) + excString string-formatted version of the exception and + traceback + ============= ===================================================================== + """ + if self.exited: + self.debugMsg(' send: exited already; raise ClosedError.') + raise ClosedError() + + with self.sendLock: + #if len(kwds) > 0: + #print "Warning: send() ignored args:", kwds + + if opts is None: + opts = {} + + assert callSync in ['off', 'sync', 'async'], 'callSync must be one of "off", "sync", or "async"' + if reqId is None: + if callSync != 'off': ## requested return value; use the next available request ID + reqId = self.nextRequestId + self.nextRequestId += 1 + else: + ## If requestId is provided, this _must_ be a response to a previously received request. + assert request in ['result', 'error'] + + if returnType is not None: + opts['returnType'] = returnType + + #print os.getpid(), "send request:", request, reqId, opts + + ## double-pickle args to ensure that at least status and request ID get through + try: + optStr = pickle.dumps(opts) + except: + print("==== Error pickling this object: ====") + print(opts) + print("=======================================") + raise + + nByteMsgs = 0 + if byteData is not None: + nByteMsgs = len(byteData) + + ## Send primary request + request = (request, reqId, nByteMsgs, optStr) + self.debugMsg('send request: cmd=%s nByteMsgs=%d id=%s opts=%s' % (str(request[0]), nByteMsgs, str(reqId), str(opts))) + self.conn.send(request) + + ## follow up by sending byte messages + if byteData is not None: + for obj in byteData: ## Remote process _must_ be prepared to read the same number of byte messages! + self.conn.send_bytes(obj) + self.debugMsg(' sent %d byte messages' % len(byteData)) + + self.debugMsg(' call sync: %s' % callSync) + if callSync == 'off': + return + + req = Request(self, reqId, description=str(request), timeout=timeout) + if callSync == 'async': + return req + + if callSync == 'sync': + try: + return req.result() + except NoResultError: + return req + + def close(self, callSync='off', noCleanup=False, **kwds): + try: + self.send(request='close', opts=dict(noCleanup=noCleanup), callSync=callSync, **kwds) + self.exited = True + except ClosedError: + pass + + def getResult(self, reqId): + ## raises NoResultError if the result is not available yet + #print self.results.keys(), os.getpid() + with self.resultLock: + haveResult = reqId in self.results + + if not haveResult: + try: + self.processRequests() + except ClosedError: ## even if remote connection has closed, we may have + ## received new data during this call to processRequests() + pass + + with self.resultLock: + if reqId not in self.results: + raise NoResultError() + status, result = self.results.pop(reqId) + + if status == 'result': + return result + elif status == 'error': + #print ''.join(result) + exc, excStr = result + if exc is not None: + print("===== Remote process raised exception on request: =====") + print(''.join(excStr)) + print("===== Local Traceback to request follows: =====") + raise exc + else: + print(''.join(excStr)) + raise Exception("Error getting result. See above for exception from remote process.") + + else: + raise Exception("Internal error.") + + def _import(self, mod, **kwds): + """ + Request the remote process import a module (or symbols from a module) + and return the proxied results. Uses built-in __import__() function, but + adds a bit more processing: + + _import('module') => returns module + _import('module.submodule') => returns submodule + (note this differs from behavior of __import__) + _import('module', fromlist=[name1, name2, ...]) => returns [module.name1, module.name2, ...] + (this also differs from behavior of __import__) + + """ + return self.send(request='import', callSync='sync', opts=dict(module=mod), **kwds) + + def getObjAttr(self, obj, attr, **kwds): + return self.send(request='getObjAttr', opts=dict(obj=obj, attr=attr), **kwds) + + def getObjValue(self, obj, **kwds): + return self.send(request='getObjValue', opts=dict(obj=obj), **kwds) + + def callObj(self, obj, args, kwds, **opts): + opts = opts.copy() + args = list(args) + + ## Decide whether to send arguments by value or by proxy + with self.optsLock: + noProxyTypes = opts.pop('noProxyTypes', None) + if noProxyTypes is None: + noProxyTypes = self.proxyOptions['noProxyTypes'] + + autoProxy = opts.pop('autoProxy', self.proxyOptions['autoProxy']) + + if autoProxy is True: + args = [self.autoProxy(v, noProxyTypes) for v in args] + for k, v in kwds.iteritems(): + opts[k] = self.autoProxy(v, noProxyTypes) + + byteMsgs = [] + + ## If there are arrays in the arguments, send those as byte messages. + ## We do this because pickling arrays is too expensive. + for i,arg in enumerate(args): + if arg.__class__ == np.ndarray: + args[i] = ("__byte_message__", len(byteMsgs), (arg.dtype, arg.shape)) + byteMsgs.append(arg) + for k,v in kwds.items(): + if v.__class__ == np.ndarray: + kwds[k] = ("__byte_message__", len(byteMsgs), (v.dtype, v.shape)) + byteMsgs.append(v) + + return self.send(request='callObj', opts=dict(obj=obj, args=args, kwds=kwds), byteData=byteMsgs, **opts) + + def registerProxy(self, proxy): + with self.proxyLock: + ref = weakref.ref(proxy, self.deleteProxy) + self.proxies[ref] = proxy._proxyId + + def deleteProxy(self, ref): + with self.proxyLock: + proxyId = self.proxies.pop(ref) + + try: + self.send(request='del', opts=dict(proxyId=proxyId), callSync='off') + except IOError: ## if remote process has closed down, there is no need to send delete requests anymore + pass + + def transfer(self, obj, **kwds): + """ + Transfer an object by value to the remote host (the object must be picklable) + and return a proxy for the new remote object. + """ + if obj.__class__ is np.ndarray: + opts = {'dtype': obj.dtype, 'shape': obj.shape} + return self.send(request='transferArray', opts=opts, byteData=[obj], **kwds) + else: + return self.send(request='transfer', opts=dict(obj=obj), **kwds) + + def autoProxy(self, obj, noProxyTypes): + ## Return object wrapped in LocalObjectProxy _unless_ its type is in noProxyTypes. + for typ in noProxyTypes: + if isinstance(obj, typ): + return obj + return LocalObjectProxy(obj) + + +class Request(object): + """ + Request objects are returned when calling an ObjectProxy in asynchronous mode + or if a synchronous call has timed out. Use hasResult() to ask whether + the result of the call has been returned yet. Use result() to get + the returned value. + """ + def __init__(self, process, reqId, description=None, timeout=10): + self.proc = process + self.description = description + self.reqId = reqId + self.gotResult = False + self._result = None + self.timeout = timeout + + def result(self, block=True, timeout=None): + """ + Return the result for this request. + + If block is True, wait until the result has arrived or *timeout* seconds passes. + If the timeout is reached, raise NoResultError. (use timeout=None to disable) + If block is False, raise NoResultError immediately if the result has not arrived yet. + + If the process's connection has closed before the result arrives, raise ClosedError. + """ + + if self.gotResult: + return self._result + + if timeout is None: + timeout = self.timeout + + if block: + start = time.time() + while not self.hasResult(): + if self.proc.exited: + raise ClosedError() + time.sleep(0.005) + if timeout >= 0 and time.time() - start > timeout: + print("Request timed out: %s" % self.description) + import traceback + traceback.print_stack() + raise NoResultError() + return self._result + else: + self._result = self.proc.getResult(self.reqId) ## raises NoResultError if result is not available yet + self.gotResult = True + return self._result + + def hasResult(self): + """Returns True if the result for this request has arrived.""" + try: + self.result(block=False) + except NoResultError: + pass + + return self.gotResult + +class LocalObjectProxy(object): + """ + Used for wrapping local objects to ensure that they are send by proxy to a remote host. + Note that 'proxy' is just a shorter alias for LocalObjectProxy. + + For example:: + + data = [1,2,3,4,5] + remotePlot.plot(data) ## by default, lists are pickled and sent by value + remotePlot.plot(proxy(data)) ## force the object to be sent by proxy + + """ + nextProxyId = 0 + proxiedObjects = {} ## maps {proxyId: object} + + + @classmethod + def registerObject(cls, obj): + ## assign it a unique ID so we can keep a reference to the local object + + pid = cls.nextProxyId + cls.nextProxyId += 1 + cls.proxiedObjects[pid] = obj + #print "register:", cls.proxiedObjects + return pid + + @classmethod + def lookupProxyId(cls, pid): + return cls.proxiedObjects[pid] + + @classmethod + def releaseProxyId(cls, pid): + del cls.proxiedObjects[pid] + #print "release:", cls.proxiedObjects + + def __init__(self, obj, **opts): + """ + Create a 'local' proxy object that, when sent to a remote host, + will appear as a normal ObjectProxy to *obj*. + Any extra keyword arguments are passed to proxy._setProxyOptions() + on the remote side. + """ + self.processId = os.getpid() + #self.objectId = id(obj) + self.typeStr = repr(obj) + #self.handler = handler + self.obj = obj + self.opts = opts + + def __reduce__(self): + ## a proxy is being pickled and sent to a remote process. + ## every time this happens, a new proxy will be generated in the remote process, + ## so we keep a new ID so we can track when each is released. + pid = LocalObjectProxy.registerObject(self.obj) + return (unpickleObjectProxy, (self.processId, pid, self.typeStr, None, self.opts)) + +## alias +proxy = LocalObjectProxy + +def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None, opts=None): + if processId == os.getpid(): + obj = LocalObjectProxy.lookupProxyId(proxyId) + if attributes is not None: + for attr in attributes: + obj = getattr(obj, attr) + return obj + else: + proxy = ObjectProxy(processId, proxyId=proxyId, typeStr=typeStr) + if opts is not None: + proxy._setProxyOptions(**opts) + return proxy + +class ObjectProxy(object): + """ + Proxy to an object stored by the remote process. Proxies are created + by calling Process._import(), Process.transfer(), or by requesting/calling + attributes on existing proxy objects. + + For the most part, this object can be used exactly as if it + were a local object:: + + rsys = proc._import('sys') # returns proxy to sys module on remote process + rsys.stdout # proxy to remote sys.stdout + rsys.stdout.write # proxy to remote sys.stdout.write + rsys.stdout.write('hello') # calls sys.stdout.write('hello') on remote machine + # and returns the result (None) + + When calling a proxy to a remote function, the call can be made synchronous + (result of call is returned immediately), asynchronous (result is returned later), + or return can be disabled entirely:: + + ros = proc._import('os') + + ## synchronous call; result is returned immediately + pid = ros.getpid() + + ## asynchronous call + request = ros.getpid(_callSync='async') + while not request.hasResult(): + time.sleep(0.01) + pid = request.result() + + ## disable return when we know it isn't needed + rsys.stdout.write('hello', _callSync='off') + + Additionally, values returned from a remote function call are automatically + returned either by value (must be picklable) or by proxy. + This behavior can be forced:: + + rnp = proc._import('numpy') + arrProxy = rnp.array([1,2,3,4], _returnType='proxy') + arrValue = rnp.array([1,2,3,4], _returnType='value') + + The default callSync and returnType behaviors (as well as others) can be set + for each proxy individually using ObjectProxy._setProxyOptions() or globally using + proc.setProxyOptions(). + + """ + def __init__(self, processId, proxyId, typeStr='', parent=None): + object.__init__(self) + ## can't set attributes directly because setattr is overridden. + self.__dict__['_processId'] = processId + self.__dict__['_typeStr'] = typeStr + self.__dict__['_proxyId'] = proxyId + self.__dict__['_attributes'] = () + ## attributes that affect the behavior of the proxy. + ## in all cases, a value of None causes the proxy to ask + ## its parent event handler to make the decision + self.__dict__['_proxyOptions'] = { + 'callSync': None, ## 'sync', 'async', None + 'timeout': None, ## float, None + 'returnType': None, ## 'proxy', 'value', 'auto', None + 'deferGetattr': None, ## True, False, None + 'noProxyTypes': None, ## list of types to send by value instead of by proxy + } + + self.__dict__['_handler'] = RemoteEventHandler.getHandler(processId) + self.__dict__['_handler'].registerProxy(self) ## handler will watch proxy; inform remote process when the proxy is deleted. + + def _setProxyOptions(self, **kwds): + """ + Change the behavior of this proxy. For all options, a value of None + will cause the proxy to instead use the default behavior defined + by its parent Process. + + Options are: + + ============= ============================================================= + callSync 'sync', 'async', 'off', or None. + If 'async', then calling methods will return a Request object + which can be used to inquire later about the result of the + method call. + If 'sync', then calling a method + will block until the remote process has returned its result + or the timeout has elapsed (in this case, a Request object + is returned instead). + If 'off', then the remote process is instructed _not_ to + reply and the method call will return None immediately. + returnType 'auto', 'proxy', 'value', or None. + If 'proxy', then the value returned when calling a method + will be a proxy to the object on the remote process. + If 'value', then attempt to pickle the returned object and + send it back. + If 'auto', then the decision is made by consulting the + 'noProxyTypes' option. + autoProxy bool or None. If True, arguments to __call__ are + automatically converted to proxy unless their type is + listed in noProxyTypes (see below). If False, arguments + are left untouched. Use proxy(obj) to manually convert + arguments before sending. + timeout float or None. Length of time to wait during synchronous + requests before returning a Request object instead. + deferGetattr True, False, or None. + If False, all attribute requests will be sent to the remote + process immediately and will block until a response is + received (or timeout has elapsed). + If True, requesting an attribute from the proxy returns a + new proxy immediately. The remote process is _not_ contacted + to make this request. This is faster, but it is possible to + request an attribute that does not exist on the proxied + object. In this case, AttributeError will not be raised + until an attempt is made to look up the attribute on the + remote process. + noProxyTypes List of object types that should _not_ be proxied when + sent to the remote process. + ============= ============================================================= + """ + self._proxyOptions.update(kwds) + + def _getValue(self): + """ + Return the value of the proxied object + (the remote object must be picklable) + """ + return self._handler.getObjValue(self) + + def _getProxyOption(self, opt): + val = self._proxyOptions[opt] + if val is None: + return self._handler.getProxyOption(opt) + return val + + def _getProxyOptions(self): + return dict([(k, self._getProxyOption(k)) for k in self._proxyOptions]) + + def __reduce__(self): + return (unpickleObjectProxy, (self._processId, self._proxyId, self._typeStr, self._attributes)) + + def __repr__(self): + #objRepr = self.__getattr__('__repr__')(callSync='value') + return "" % (self._processId, self._proxyId, self._typeStr) + + + def __getattr__(self, attr, **kwds): + """ + Calls __getattr__ on the remote object and returns the attribute + by value or by proxy depending on the options set (see + ObjectProxy._setProxyOptions and RemoteEventHandler.setProxyOptions) + + If the option 'deferGetattr' is True for this proxy, then a new proxy object + is returned _without_ asking the remote object whether the named attribute exists. + This can save time when making multiple chained attribute requests, + but may also defer a possible AttributeError until later, making + them more difficult to debug. + """ + opts = self._getProxyOptions() + for k in opts: + if '_'+k in kwds: + opts[k] = kwds.pop('_'+k) + if opts['deferGetattr'] is True: + return self._deferredAttr(attr) + else: + #opts = self._getProxyOptions() + return self._handler.getObjAttr(self, attr, **opts) + + def _deferredAttr(self, attr): + return DeferredObjectProxy(self, attr) + + def __call__(self, *args, **kwds): + """ + Attempts to call the proxied object from the remote process. + Accepts extra keyword arguments: + + _callSync 'off', 'sync', or 'async' + _returnType 'value', 'proxy', or 'auto' + + If the remote call raises an exception on the remote process, + it will be re-raised on the local process. + + """ + opts = self._getProxyOptions() + for k in opts: + if '_'+k in kwds: + opts[k] = kwds.pop('_'+k) + return self._handler.callObj(obj=self, args=args, kwds=kwds, **opts) + + + ## Explicitly proxy special methods. Is there a better way to do this?? + + def _getSpecialAttr(self, attr): + ## this just gives us an easy way to change the behavior of the special methods + return self._deferredAttr(attr) + + def __getitem__(self, *args): + return self._getSpecialAttr('__getitem__')(*args) + + def __setitem__(self, *args): + return self._getSpecialAttr('__setitem__')(*args, _callSync='off') + + def __setattr__(self, *args): + return self._getSpecialAttr('__setattr__')(*args, _callSync='off') + + def __str__(self, *args): + return self._getSpecialAttr('__str__')(*args, _returnType='value') + + def __len__(self, *args): + return self._getSpecialAttr('__len__')(*args) + + def __add__(self, *args): + return self._getSpecialAttr('__add__')(*args) + + def __sub__(self, *args): + return self._getSpecialAttr('__sub__')(*args) + + def __div__(self, *args): + return self._getSpecialAttr('__div__')(*args) + + def __truediv__(self, *args): + return self._getSpecialAttr('__truediv__')(*args) + + def __floordiv__(self, *args): + return self._getSpecialAttr('__floordiv__')(*args) + + def __mul__(self, *args): + return self._getSpecialAttr('__mul__')(*args) + + def __pow__(self, *args): + return self._getSpecialAttr('__pow__')(*args) + + def __iadd__(self, *args): + return self._getSpecialAttr('__iadd__')(*args, _callSync='off') + + def __isub__(self, *args): + return self._getSpecialAttr('__isub__')(*args, _callSync='off') + + def __idiv__(self, *args): + return self._getSpecialAttr('__idiv__')(*args, _callSync='off') + + def __itruediv__(self, *args): + return self._getSpecialAttr('__itruediv__')(*args, _callSync='off') + + def __ifloordiv__(self, *args): + return self._getSpecialAttr('__ifloordiv__')(*args, _callSync='off') + + def __imul__(self, *args): + return self._getSpecialAttr('__imul__')(*args, _callSync='off') + + def __ipow__(self, *args): + return self._getSpecialAttr('__ipow__')(*args, _callSync='off') + + def __rshift__(self, *args): + return self._getSpecialAttr('__rshift__')(*args) + + def __lshift__(self, *args): + return self._getSpecialAttr('__lshift__')(*args) + + def __irshift__(self, *args): + return self._getSpecialAttr('__irshift__')(*args, _callSync='off') + + def __ilshift__(self, *args): + return self._getSpecialAttr('__ilshift__')(*args, _callSync='off') + + def __eq__(self, *args): + return self._getSpecialAttr('__eq__')(*args) + + def __ne__(self, *args): + return self._getSpecialAttr('__ne__')(*args) + + def __lt__(self, *args): + return self._getSpecialAttr('__lt__')(*args) + + def __gt__(self, *args): + return self._getSpecialAttr('__gt__')(*args) + + def __le__(self, *args): + return self._getSpecialAttr('__le__')(*args) + + def __ge__(self, *args): + return self._getSpecialAttr('__ge__')(*args) + + def __and__(self, *args): + return self._getSpecialAttr('__and__')(*args) + + def __or__(self, *args): + return self._getSpecialAttr('__or__')(*args) + + def __xor__(self, *args): + return self._getSpecialAttr('__xor__')(*args) + + def __iand__(self, *args): + return self._getSpecialAttr('__iand__')(*args, _callSync='off') + + def __ior__(self, *args): + return self._getSpecialAttr('__ior__')(*args, _callSync='off') + + def __ixor__(self, *args): + return self._getSpecialAttr('__ixor__')(*args, _callSync='off') + + def __mod__(self, *args): + return self._getSpecialAttr('__mod__')(*args) + + def __radd__(self, *args): + return self._getSpecialAttr('__radd__')(*args) + + def __rsub__(self, *args): + return self._getSpecialAttr('__rsub__')(*args) + + def __rdiv__(self, *args): + return self._getSpecialAttr('__rdiv__')(*args) + + def __rfloordiv__(self, *args): + return self._getSpecialAttr('__rfloordiv__')(*args) + + def __rtruediv__(self, *args): + return self._getSpecialAttr('__rtruediv__')(*args) + + def __rmul__(self, *args): + return self._getSpecialAttr('__rmul__')(*args) + + def __rpow__(self, *args): + return self._getSpecialAttr('__rpow__')(*args) + + def __rrshift__(self, *args): + return self._getSpecialAttr('__rrshift__')(*args) + + def __rlshift__(self, *args): + return self._getSpecialAttr('__rlshift__')(*args) + + def __rand__(self, *args): + return self._getSpecialAttr('__rand__')(*args) + + def __ror__(self, *args): + return self._getSpecialAttr('__ror__')(*args) + + def __rxor__(self, *args): + return self._getSpecialAttr('__ror__')(*args) + + def __rmod__(self, *args): + return self._getSpecialAttr('__rmod__')(*args) + + def __hash__(self): + ## Required for python3 since __eq__ is defined. + return id(self) + +class DeferredObjectProxy(ObjectProxy): + """ + This class represents an attribute (or sub-attribute) of a proxied object. + It is used to speed up attribute requests. Take the following scenario:: + + rsys = proc._import('sys') + rsys.stdout.write('hello') + + For this simple example, a total of 4 synchronous requests are made to + the remote process: + + 1) import sys + 2) getattr(sys, 'stdout') + 3) getattr(stdout, 'write') + 4) write('hello') + + This takes a lot longer than running the equivalent code locally. To + speed things up, we can 'defer' the two attribute lookups so they are + only carried out when neccessary:: + + rsys = proc._import('sys') + rsys._setProxyOptions(deferGetattr=True) + rsys.stdout.write('hello') + + This example only makes two requests to the remote process; the two + attribute lookups immediately return DeferredObjectProxy instances + immediately without contacting the remote process. When the call + to write() is made, all attribute requests are processed at the same time. + + Note that if the attributes requested do not exist on the remote object, + making the call to write() will raise an AttributeError. + """ + def __init__(self, parentProxy, attribute): + ## can't set attributes directly because setattr is overridden. + for k in ['_processId', '_typeStr', '_proxyId', '_handler']: + self.__dict__[k] = getattr(parentProxy, k) + self.__dict__['_parent'] = parentProxy ## make sure parent stays alive + self.__dict__['_attributes'] = parentProxy._attributes + (attribute,) + self.__dict__['_proxyOptions'] = parentProxy._proxyOptions.copy() + + def __repr__(self): + return ObjectProxy.__repr__(self) + '.' + '.'.join(self._attributes) + + def _undefer(self): + """ + Return a non-deferred ObjectProxy referencing the same object + """ + return self._parent.__getattr__(self._attributes[-1], _deferGetattr=False) + diff --git a/papi/pyqtgraph/numpy_fix.py b/papi/pyqtgraph/numpy_fix.py new file mode 100644 index 00000000..2fa8ef1f --- /dev/null +++ b/papi/pyqtgraph/numpy_fix.py @@ -0,0 +1,22 @@ +try: + import numpy as np + + ## Wrap np.concatenate to catch and avoid a segmentation fault bug + ## (numpy trac issue #2084) + if not hasattr(np, 'concatenate_orig'): + np.concatenate_orig = np.concatenate + def concatenate(vals, *args, **kwds): + """Wrapper around numpy.concatenate (see pyqtgraph/numpy_fix.py)""" + dtypes = [getattr(v, 'dtype', None) for v in vals] + names = [getattr(dt, 'names', None) for dt in dtypes] + if len(dtypes) < 2 or all([n is None for n in names]): + return np.concatenate_orig(vals, *args, **kwds) + if any([dt != dtypes[0] for dt in dtypes[1:]]): + raise TypeError("Cannot concatenate structured arrays of different dtype.") + return np.concatenate_orig(vals, *args, **kwds) + + np.concatenate = concatenate + +except ImportError: + pass + diff --git a/papi/pyqtgraph/opengl/GLGraphicsItem.py b/papi/pyqtgraph/opengl/GLGraphicsItem.py new file mode 100644 index 00000000..12c5b707 --- /dev/null +++ b/papi/pyqtgraph/opengl/GLGraphicsItem.py @@ -0,0 +1,298 @@ +from ..Qt import QtGui, QtCore +from .. import Transform3D +from OpenGL.GL import * +from OpenGL import GL + +GLOptions = { + 'opaque': { + GL_DEPTH_TEST: True, + GL_BLEND: False, + GL_ALPHA_TEST: False, + GL_CULL_FACE: False, + }, + 'translucent': { + GL_DEPTH_TEST: True, + GL_BLEND: True, + GL_ALPHA_TEST: False, + GL_CULL_FACE: False, + 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), + }, + 'additive': { + GL_DEPTH_TEST: False, + GL_BLEND: True, + GL_ALPHA_TEST: False, + GL_CULL_FACE: False, + 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE), + }, +} + + +class GLGraphicsItem(QtCore.QObject): + _nextId = 0 + + def __init__(self, parentItem=None): + QtCore.QObject.__init__(self) + self._id = GLGraphicsItem._nextId + GLGraphicsItem._nextId += 1 + + self.__parent = None + self.__view = None + self.__children = set() + self.__transform = Transform3D() + self.__visible = True + self.setParentItem(parentItem) + self.setDepthValue(0) + self.__glOpts = {} + + def setParentItem(self, item): + """Set this item's parent in the scenegraph hierarchy.""" + if self.__parent is not None: + self.__parent.__children.remove(self) + if item is not None: + item.__children.add(self) + self.__parent = item + + if self.__parent is not None and self.view() is not self.__parent.view(): + if self.view() is not None: + self.view().removeItem(self) + self.__parent.view().addItem(self) + + def setGLOptions(self, opts): + """ + Set the OpenGL state options to use immediately before drawing this item. + (Note that subclasses must call setupGLState before painting for this to work) + + The simplest way to invoke this method is to pass in the name of + a predefined set of options (see the GLOptions variable): + + ============= ====================================================== + opaque Enables depth testing and disables blending + translucent Enables depth testing and blending + Elements must be drawn sorted back-to-front for + translucency to work correctly. + additive Disables depth testing, enables blending. + Colors are added together, so sorting is not required. + ============= ====================================================== + + It is also possible to specify any arbitrary settings as a dictionary. + This may consist of {'functionName': (args...)} pairs where functionName must + be a callable attribute of OpenGL.GL, or {GL_STATE_VAR: bool} pairs + which will be interpreted as calls to glEnable or glDisable(GL_STATE_VAR). + + For example:: + + { + GL_ALPHA_TEST: True, + GL_CULL_FACE: False, + 'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), + } + + + """ + if isinstance(opts, basestring): + opts = GLOptions[opts] + self.__glOpts = opts.copy() + self.update() + + def updateGLOptions(self, opts): + """ + Modify the OpenGL state options to use immediately before drawing this item. + *opts* must be a dictionary as specified by setGLOptions. + Values may also be None, in which case the key will be ignored. + """ + self.__glOpts.update(opts) + + + def parentItem(self): + """Return a this item's parent in the scenegraph hierarchy.""" + return self.__parent + + def childItems(self): + """Return a list of this item's children in the scenegraph hierarchy.""" + return list(self.__children) + + def _setView(self, v): + self.__view = v + + def view(self): + return self.__view + + def setDepthValue(self, value): + """ + Sets the depth value of this item. Default is 0. + This controls the order in which items are drawn--those with a greater depth value will be drawn later. + Items with negative depth values are drawn before their parent. + (This is analogous to QGraphicsItem.zValue) + The depthValue does NOT affect the position of the item or the values it imparts to the GL depth buffer. + """ + self.__depthValue = value + + def depthValue(self): + """Return the depth value of this item. See setDepthValue for more information.""" + return self.__depthValue + + def setTransform(self, tr): + """Set the local transform for this object. + Must be a :class:`Transform3D ` instance. This transform + determines how the local coordinate system of the item is mapped to the coordinate + system of its parent.""" + self.__transform = Transform3D(tr) + self.update() + + def resetTransform(self): + """Reset this item's transform to an identity transformation.""" + self.__transform.setToIdentity() + self.update() + + def applyTransform(self, tr, local): + """ + Multiply this object's transform by *tr*. + If local is True, then *tr* is multiplied on the right of the current transform:: + + newTransform = transform * tr + + If local is False, then *tr* is instead multiplied on the left:: + + newTransform = tr * transform + """ + if local: + self.setTransform(self.transform() * tr) + else: + self.setTransform(tr * self.transform()) + + def transform(self): + """Return this item's transform object.""" + return self.__transform + + def viewTransform(self): + """Return the transform mapping this item's local coordinate system to the + view coordinate system.""" + tr = self.__transform + p = self + while True: + p = p.parentItem() + if p is None: + break + tr = p.transform() * tr + return Transform3D(tr) + + def translate(self, dx, dy, dz, local=False): + """ + Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system. + If *local* is True, then translation takes place in local coordinates. + """ + tr = Transform3D() + tr.translate(dx, dy, dz) + self.applyTransform(tr, local=local) + + def rotate(self, angle, x, y, z, local=False): + """ + Rotate the object around the axis specified by (x,y,z). + *angle* is in degrees. + + """ + tr = Transform3D() + tr.rotate(angle, x, y, z) + self.applyTransform(tr, local=local) + + def scale(self, x, y, z, local=True): + """ + Scale the object by (*dx*, *dy*, *dz*) in its local coordinate system. + If *local* is False, then scale takes place in the parent's coordinates. + """ + tr = Transform3D() + tr.scale(x, y, z) + self.applyTransform(tr, local=local) + + + def hide(self): + """Hide this item. + This is equivalent to setVisible(False).""" + self.setVisible(False) + + def show(self): + """Make this item visible if it was previously hidden. + This is equivalent to setVisible(True).""" + self.setVisible(True) + + def setVisible(self, vis): + """Set the visibility of this item.""" + self.__visible = vis + self.update() + + def visible(self): + """Return True if the item is currently set to be visible. + Note that this does not guarantee that the item actually appears in the + view, as it may be obscured or outside of the current view area.""" + return self.__visible + + + def initializeGL(self): + """ + Called after an item is added to a GLViewWidget. + The widget's GL context is made current before this method is called. + (So this would be an appropriate time to generate lists, upload textures, etc.) + """ + pass + + def setupGLState(self): + """ + This method is responsible for preparing the GL state options needed to render + this item (blending, depth testing, etc). The method is called immediately before painting the item. + """ + for k,v in self.__glOpts.items(): + if v is None: + continue + if isinstance(k, basestring): + func = getattr(GL, k) + func(*v) + else: + if v is True: + glEnable(k) + else: + glDisable(k) + + def paint(self): + """ + Called by the GLViewWidget to draw this item. + It is the responsibility of the item to set up its own modelview matrix, + but the caller will take care of pushing/popping. + """ + self.setupGLState() + + def update(self): + """ + Indicates that this item needs to be redrawn, and schedules an update + with the view it is displayed in. + """ + v = self.view() + if v is None: + return + v.update() + + def mapToParent(self, point): + tr = self.transform() + if tr is None: + return point + return tr.map(point) + + def mapFromParent(self, point): + tr = self.transform() + if tr is None: + return point + return tr.inverted()[0].map(point) + + def mapToView(self, point): + tr = self.viewTransform() + if tr is None: + return point + return tr.map(point) + + def mapFromView(self, point): + tr = self.viewTransform() + if tr is None: + return point + return tr.inverted()[0].map(point) + + + \ No newline at end of file diff --git a/papi/pyqtgraph/opengl/GLViewWidget.py b/papi/pyqtgraph/opengl/GLViewWidget.py new file mode 100644 index 00000000..992aa73e --- /dev/null +++ b/papi/pyqtgraph/opengl/GLViewWidget.py @@ -0,0 +1,464 @@ +from ..Qt import QtCore, QtGui, QtOpenGL +from OpenGL.GL import * +import OpenGL.GL.framebufferobjects as glfbo +import numpy as np +from .. import Vector +from .. import functions as fn + +##Vector = QtGui.QVector3D + +ShareWidget = None + +class GLViewWidget(QtOpenGL.QGLWidget): + """ + Basic widget for displaying 3D data + - Rotation/scale controls + - Axis/grid display + - Export options + + """ + + def __init__(self, parent=None): + global ShareWidget + + if ShareWidget is None: + ## create a dummy widget to allow sharing objects (textures, shaders, etc) between views + ShareWidget = QtOpenGL.QGLWidget() + + QtOpenGL.QGLWidget.__init__(self, parent, ShareWidget) + + self.setFocusPolicy(QtCore.Qt.ClickFocus) + + self.opts = { + 'center': Vector(0,0,0), ## will always appear at the center of the widget + 'distance': 10.0, ## distance of camera from center + 'fov': 60, ## horizontal field of view in degrees + 'elevation': 30, ## camera's angle of elevation in degrees + 'azimuth': 45, ## camera's azimuthal angle in degrees + ## (rotation around z-axis 0 points along x-axis) + 'viewport': None, ## glViewport params; None == whole widget + } + self.setBackgroundColor('k') + self.items = [] + self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown] + self.keysPressed = {} + self.keyTimer = QtCore.QTimer() + self.keyTimer.timeout.connect(self.evalKeyState) + + self.makeCurrent() + + def addItem(self, item): + self.items.append(item) + if hasattr(item, 'initializeGL'): + self.makeCurrent() + try: + item.initializeGL() + except: + self.checkOpenGLVersion('Error while adding item %s to GLViewWidget.' % str(item)) + + item._setView(self) + #print "set view", item, self, item.view() + self.update() + + def removeItem(self, item): + self.items.remove(item) + item._setView(None) + self.update() + + + def initializeGL(self): + self.resizeGL(self.width(), self.height()) + + def setBackgroundColor(self, *args, **kwds): + """ + Set the background color of the widget. Accepts the same arguments as + pg.mkColor(). + """ + self.opts['bgcolor'] = fn.mkColor(*args, **kwds) + self.update() + + def getViewport(self): + vp = self.opts['viewport'] + if vp is None: + return (0, 0, self.width(), self.height()) + else: + return vp + + def resizeGL(self, w, h): + pass + #glViewport(*self.getViewport()) + #self.update() + + def setProjection(self, region=None): + m = self.projectionMatrix(region) + glMatrixMode(GL_PROJECTION) + glLoadIdentity() + a = np.array(m.copyDataTo()).reshape((4,4)) + glMultMatrixf(a.transpose()) + + def projectionMatrix(self, region=None): + # Xw = (Xnd + 1) * width/2 + X + if region is None: + region = (0, 0, self.width(), self.height()) + + x0, y0, w, h = self.getViewport() + dist = self.opts['distance'] + fov = self.opts['fov'] + nearClip = dist * 0.001 + farClip = dist * 1000. + + r = nearClip * np.tan(fov * 0.5 * np.pi / 180.) + t = r * h / w + + # convert screen coordinates (region) to normalized device coordinates + # Xnd = (Xw - X0) * 2/width - 1 + ## Note that X0 and width in these equations must be the values used in viewport + left = r * ((region[0]-x0) * (2.0/w) - 1) + right = r * ((region[0]+region[2]-x0) * (2.0/w) - 1) + bottom = t * ((region[1]-y0) * (2.0/h) - 1) + top = t * ((region[1]+region[3]-y0) * (2.0/h) - 1) + + tr = QtGui.QMatrix4x4() + tr.frustum(left, right, bottom, top, nearClip, farClip) + return tr + + def setModelview(self): + glMatrixMode(GL_MODELVIEW) + glLoadIdentity() + m = self.viewMatrix() + a = np.array(m.copyDataTo()).reshape((4,4)) + glMultMatrixf(a.transpose()) + + def viewMatrix(self): + tr = QtGui.QMatrix4x4() + tr.translate( 0.0, 0.0, -self.opts['distance']) + tr.rotate(self.opts['elevation']-90, 1, 0, 0) + tr.rotate(self.opts['azimuth']+90, 0, 0, -1) + center = self.opts['center'] + tr.translate(-center.x(), -center.y(), -center.z()) + return tr + + def itemsAt(self, region=None): + """ + Return a list of the items displayed in the region (x, y, w, h) + relative to the widget. + """ + region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3]) + + #buf = np.zeros(100000, dtype=np.uint) + buf = glSelectBuffer(100000) + try: + glRenderMode(GL_SELECT) + glInitNames() + glPushName(0) + self._itemNames = {} + self.paintGL(region=region, useItemNames=True) + + finally: + hits = glRenderMode(GL_RENDER) + + items = [(h.near, h.names[0]) for h in hits] + items.sort(key=lambda i: i[0]) + return [self._itemNames[i[1]] for i in items] + + def paintGL(self, region=None, viewport=None, useItemNames=False): + """ + viewport specifies the arguments to glViewport. If None, then we use self.opts['viewport'] + region specifies the sub-region of self.opts['viewport'] that should be rendered. + Note that we may use viewport != self.opts['viewport'] when exporting. + """ + if viewport is None: + glViewport(*self.getViewport()) + else: + glViewport(*viewport) + self.setProjection(region=region) + self.setModelview() + bgcolor = self.opts['bgcolor'] + glClearColor(bgcolor.red(), bgcolor.green(), bgcolor.blue(), 1.0) + glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT ) + self.drawItemTree(useItemNames=useItemNames) + + def drawItemTree(self, item=None, useItemNames=False): + if item is None: + items = [x for x in self.items if x.parentItem() is None] + else: + items = item.childItems() + items.append(item) + items.sort(key=lambda a: a.depthValue()) + for i in items: + if not i.visible(): + continue + if i is item: + try: + glPushAttrib(GL_ALL_ATTRIB_BITS) + if useItemNames: + glLoadName(i._id) + self._itemNames[i._id] = i + i.paint() + except: + from .. import debug + debug.printExc() + msg = "Error while drawing item %s." % str(item) + ver = glGetString(GL_VERSION) + if ver is not None: + ver = ver.split()[0] + if int(ver.split(b'.')[0]) < 2: + print(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver) + else: + print(msg) + + finally: + glPopAttrib() + else: + glMatrixMode(GL_MODELVIEW) + glPushMatrix() + try: + tr = i.transform() + a = np.array(tr.copyDataTo()).reshape((4,4)) + glMultMatrixf(a.transpose()) + self.drawItemTree(i, useItemNames=useItemNames) + finally: + glMatrixMode(GL_MODELVIEW) + glPopMatrix() + + def setCameraPosition(self, pos=None, distance=None, elevation=None, azimuth=None): + if distance is not None: + self.opts['distance'] = distance + if elevation is not None: + self.opts['elevation'] = elevation + if azimuth is not None: + self.opts['azimuth'] = azimuth + self.update() + + + + def cameraPosition(self): + """Return current position of camera based on center, dist, elevation, and azimuth""" + center = self.opts['center'] + dist = self.opts['distance'] + elev = self.opts['elevation'] * np.pi/180. + azim = self.opts['azimuth'] * np.pi/180. + + pos = Vector( + center.x() + dist * np.cos(elev) * np.cos(azim), + center.y() + dist * np.cos(elev) * np.sin(azim), + center.z() + dist * np.sin(elev) + ) + + return pos + + def orbit(self, azim, elev): + """Orbits the camera around the center position. *azim* and *elev* are given in degrees.""" + self.opts['azimuth'] += azim + #self.opts['elevation'] += elev + self.opts['elevation'] = np.clip(self.opts['elevation'] + elev, -90, 90) + self.update() + + def pan(self, dx, dy, dz, relative=False): + """ + Moves the center (look-at) position while holding the camera in place. + + If relative=True, then the coordinates are interpreted such that x + if in the global xy plane and points to the right side of the view, y is + in the global xy plane and orthogonal to x, and z points in the global z + direction. Distances are scaled roughly such that a value of 1.0 moves + by one pixel on screen. + + """ + if not relative: + self.opts['center'] += QtGui.QVector3D(dx, dy, dz) + else: + cPos = self.cameraPosition() + cVec = self.opts['center'] - cPos + dist = cVec.length() ## distance from camera to center + xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) ## approx. width of view at distance of center point + xScale = xDist / self.width() + zVec = QtGui.QVector3D(0,0,1) + xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized() + yVec = QtGui.QVector3D.crossProduct(xVec, zVec).normalized() + self.opts['center'] = self.opts['center'] + xVec * xScale * dx + yVec * xScale * dy + zVec * xScale * dz + self.update() + + def pixelSize(self, pos): + """ + Return the approximate size of a screen pixel at the location pos + Pos may be a Vector or an (N,3) array of locations + """ + cam = self.cameraPosition() + if isinstance(pos, np.ndarray): + cam = np.array(cam).reshape((1,)*(pos.ndim-1)+(3,)) + dist = ((pos-cam)**2).sum(axis=-1)**0.5 + else: + dist = (pos-cam).length() + xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) + return xDist / self.width() + + def mousePressEvent(self, ev): + self.mousePos = ev.pos() + + def mouseMoveEvent(self, ev): + diff = ev.pos() - self.mousePos + self.mousePos = ev.pos() + + if ev.buttons() == QtCore.Qt.LeftButton: + self.orbit(-diff.x(), diff.y()) + #print self.opts['azimuth'], self.opts['elevation'] + elif ev.buttons() == QtCore.Qt.MidButton: + if (ev.modifiers() & QtCore.Qt.ControlModifier): + self.pan(diff.x(), 0, diff.y(), relative=True) + else: + self.pan(diff.x(), diff.y(), 0, relative=True) + + def mouseReleaseEvent(self, ev): + pass + # Example item selection code: + #region = (ev.pos().x()-5, ev.pos().y()-5, 10, 10) + #print(self.itemsAt(region)) + + ## debugging code: draw the picking region + #glViewport(*self.getViewport()) + #glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT ) + #region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3]) + #self.paintGL(region=region) + #self.swapBuffers() + + + def wheelEvent(self, ev): + if (ev.modifiers() & QtCore.Qt.ControlModifier): + self.opts['fov'] *= 0.999**ev.delta() + else: + self.opts['distance'] *= 0.999**ev.delta() + self.update() + + def keyPressEvent(self, ev): + if ev.key() in self.noRepeatKeys: + ev.accept() + if ev.isAutoRepeat(): + return + self.keysPressed[ev.key()] = 1 + self.evalKeyState() + + def keyReleaseEvent(self, ev): + if ev.key() in self.noRepeatKeys: + ev.accept() + if ev.isAutoRepeat(): + return + try: + del self.keysPressed[ev.key()] + except: + self.keysPressed = {} + self.evalKeyState() + + def evalKeyState(self): + speed = 2.0 + if len(self.keysPressed) > 0: + for key in self.keysPressed: + if key == QtCore.Qt.Key_Right: + self.orbit(azim=-speed, elev=0) + elif key == QtCore.Qt.Key_Left: + self.orbit(azim=speed, elev=0) + elif key == QtCore.Qt.Key_Up: + self.orbit(azim=0, elev=-speed) + elif key == QtCore.Qt.Key_Down: + self.orbit(azim=0, elev=speed) + elif key == QtCore.Qt.Key_PageUp: + pass + elif key == QtCore.Qt.Key_PageDown: + pass + self.keyTimer.start(16) + else: + self.keyTimer.stop() + + def checkOpenGLVersion(self, msg): + ## Only to be called from within exception handler. + ver = glGetString(GL_VERSION).split()[0] + if int(ver.split('.')[0]) < 2: + from .. import debug + pyqtgraph.debug.printExc() + raise Exception(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver) + else: + raise + + + + def readQImage(self): + """ + Read the current buffer pixels out as a QImage. + """ + w = self.width() + h = self.height() + self.repaint() + pixels = np.empty((h, w, 4), dtype=np.ubyte) + pixels[:] = 128 + pixels[...,0] = 50 + pixels[...,3] = 255 + + glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels) + + # swap B,R channels for Qt + tmp = pixels[...,0].copy() + pixels[...,0] = pixels[...,2] + pixels[...,2] = tmp + pixels = pixels[::-1] # flip vertical + + img = fn.makeQImage(pixels, transpose=False) + return img + + + def renderToArray(self, size, format=GL_BGRA, type=GL_UNSIGNED_BYTE, textureSize=1024, padding=256): + w,h = map(int, size) + + self.makeCurrent() + tex = None + fb = None + try: + output = np.empty((w, h, 4), dtype=np.ubyte) + fb = glfbo.glGenFramebuffers(1) + glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, fb ) + + glEnable(GL_TEXTURE_2D) + tex = glGenTextures(1) + glBindTexture(GL_TEXTURE_2D, tex) + texwidth = textureSize + data = np.zeros((texwidth,texwidth,4), dtype=np.ubyte) + + ## Test texture dimensions first + glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, None) + if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: + raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) + ## create teture + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.transpose((1,0,2))) + + self.opts['viewport'] = (0, 0, w, h) # viewport is the complete image; this ensures that paintGL(region=...) + # is interpreted correctly. + p2 = 2 * padding + for x in range(-padding, w-padding, texwidth-p2): + for y in range(-padding, h-padding, texwidth-p2): + x2 = min(x+texwidth, w+padding) + y2 = min(y+texwidth, h+padding) + w2 = x2-x + h2 = y2-y + + ## render to texture + glfbo.glFramebufferTexture2D(glfbo.GL_FRAMEBUFFER, glfbo.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0) + + self.paintGL(region=(x, h-y-h2, w2, h2), viewport=(0, 0, w2, h2)) # only render sub-region + + ## read texture back to array + data = glGetTexImage(GL_TEXTURE_2D, 0, format, type) + data = np.fromstring(data, dtype=np.ubyte).reshape(texwidth,texwidth,4).transpose(1,0,2)[:, ::-1] + output[x+padding:x2-padding, y+padding:y2-padding] = data[padding:w2-padding, -(h2-padding):-padding] + + finally: + self.opts['viewport'] = None + glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, 0) + glBindTexture(GL_TEXTURE_2D, 0) + if tex is not None: + glDeleteTextures([tex]) + if fb is not None: + glfbo.glDeleteFramebuffers([fb]) + + return output + + + diff --git a/papi/pyqtgraph/opengl/MeshData.py b/papi/pyqtgraph/opengl/MeshData.py new file mode 100644 index 00000000..5adf4b64 --- /dev/null +++ b/papi/pyqtgraph/opengl/MeshData.py @@ -0,0 +1,504 @@ +from ..Qt import QtGui +from .. import functions as fn +import numpy as np + +class MeshData(object): + """ + Class for storing and operating on 3D mesh data. May contain: + + - list of vertex locations + - list of edges + - list of triangles + - colors per vertex, edge, or tri + - normals per vertex or tri + + This class handles conversion between the standard [list of vertexes, list of faces] + format (suitable for use with glDrawElements) and 'indexed' [list of vertexes] format + (suitable for use with glDrawArrays). It will automatically compute face normal + vectors as well as averaged vertex normal vectors. + + The class attempts to be as efficient as possible in caching conversion results and + avoiding unnecessary conversions. + """ + + def __init__(self, vertexes=None, faces=None, edges=None, vertexColors=None, faceColors=None): + """ + ============== ===================================================== + **Arguments:** + vertexes (Nv, 3) array of vertex coordinates. + If faces is not specified, then this will instead be + interpreted as (Nf, 3, 3) array of coordinates. + faces (Nf, 3) array of indexes into the vertex array. + edges [not available yet] + vertexColors (Nv, 4) array of vertex colors. + If faces is not specified, then this will instead be + interpreted as (Nf, 3, 4) array of colors. + faceColors (Nf, 4) array of face colors. + ============== ===================================================== + + All arguments are optional. + """ + self._vertexes = None # (Nv,3) array of vertex coordinates + self._vertexesIndexedByFaces = None # (Nf, 3, 3) array of vertex coordinates + self._vertexesIndexedByEdges = None # (Ne, 2, 3) array of vertex coordinates + + ## mappings between vertexes, faces, and edges + self._faces = None # Nx3 array of indexes into self._vertexes specifying three vertexes for each face + self._edges = None # Nx2 array of indexes into self._vertexes specifying two vertexes per edge + self._vertexFaces = None ## maps vertex ID to a list of face IDs (inverse mapping of _faces) + self._vertexEdges = None ## maps vertex ID to a list of edge IDs (inverse mapping of _edges) + + ## Per-vertex data + self._vertexNormals = None # (Nv, 3) array of normals, one per vertex + self._vertexNormalsIndexedByFaces = None # (Nf, 3, 3) array of normals + self._vertexColors = None # (Nv, 3) array of colors + self._vertexColorsIndexedByFaces = None # (Nf, 3, 4) array of colors + self._vertexColorsIndexedByEdges = None # (Nf, 2, 4) array of colors + + ## Per-face data + self._faceNormals = None # (Nf, 3) array of face normals + self._faceNormalsIndexedByFaces = None # (Nf, 3, 3) array of face normals + self._faceColors = None # (Nf, 4) array of face colors + self._faceColorsIndexedByFaces = None # (Nf, 3, 4) array of face colors + self._faceColorsIndexedByEdges = None # (Ne, 2, 4) array of face colors + + ## Per-edge data + self._edgeColors = None # (Ne, 4) array of edge colors + self._edgeColorsIndexedByEdges = None # (Ne, 2, 4) array of edge colors + #self._meshColor = (1, 1, 1, 0.1) # default color to use if no face/edge/vertex colors are given + + + + if vertexes is not None: + if faces is None: + self.setVertexes(vertexes, indexed='faces') + if vertexColors is not None: + self.setVertexColors(vertexColors, indexed='faces') + if faceColors is not None: + self.setFaceColors(faceColors, indexed='faces') + else: + self.setVertexes(vertexes) + self.setFaces(faces) + if vertexColors is not None: + self.setVertexColors(vertexColors) + if faceColors is not None: + self.setFaceColors(faceColors) + + def faces(self): + """Return an array (Nf, 3) of vertex indexes, three per triangular face in the mesh. + + If faces have not been computed for this mesh, the function returns None. + """ + return self._faces + + def edges(self): + """Return an array (Nf, 3) of vertex indexes, two per edge in the mesh.""" + if self._edges is None: + self._computeEdges() + return self._edges + + def setFaces(self, faces): + """Set the (Nf, 3) array of faces. Each rown in the array contains + three indexes into the vertex array, specifying the three corners + of a triangular face.""" + self._faces = faces + self._edges = None + self._vertexFaces = None + self._vertexesIndexedByFaces = None + self.resetNormals() + self._vertexColorsIndexedByFaces = None + self._faceColorsIndexedByFaces = None + + def vertexes(self, indexed=None): + """Return an array (N,3) of the positions of vertexes in the mesh. + By default, each unique vertex appears only once in the array. + If indexed is 'faces', then the array will instead contain three vertexes + per face in the mesh (and a single vertex may appear more than once in the array).""" + if indexed is None: + if self._vertexes is None and self._vertexesIndexedByFaces is not None: + self._computeUnindexedVertexes() + return self._vertexes + elif indexed == 'faces': + if self._vertexesIndexedByFaces is None and self._vertexes is not None: + self._vertexesIndexedByFaces = self._vertexes[self.faces()] + return self._vertexesIndexedByFaces + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def setVertexes(self, verts=None, indexed=None, resetNormals=True): + """ + Set the array (Nv, 3) of vertex coordinates. + If indexed=='faces', then the data must have shape (Nf, 3, 3) and is + assumed to be already indexed as a list of faces. + This will cause any pre-existing normal vectors to be cleared + unless resetNormals=False. + """ + if indexed is None: + if verts is not None: + self._vertexes = verts + self._vertexesIndexedByFaces = None + elif indexed=='faces': + self._vertexes = None + if verts is not None: + self._vertexesIndexedByFaces = verts + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + if resetNormals: + self.resetNormals() + + def resetNormals(self): + self._vertexNormals = None + self._vertexNormalsIndexedByFaces = None + self._faceNormals = None + self._faceNormalsIndexedByFaces = None + + def hasFaceIndexedData(self): + """Return True if this object already has vertex positions indexed by face""" + return self._vertexesIndexedByFaces is not None + + def hasEdgeIndexedData(self): + return self._vertexesIndexedByEdges is not None + + def hasVertexColor(self): + """Return True if this data set has vertex color information""" + for v in (self._vertexColors, self._vertexColorsIndexedByFaces, self._vertexColorsIndexedByEdges): + if v is not None: + return True + return False + + def hasFaceColor(self): + """Return True if this data set has face color information""" + for v in (self._faceColors, self._faceColorsIndexedByFaces, self._faceColorsIndexedByEdges): + if v is not None: + return True + return False + + def faceNormals(self, indexed=None): + """ + Return an array (Nf, 3) of normal vectors for each face. + If indexed='faces', then instead return an indexed array + (Nf, 3, 3) (this is just the same array with each vector + copied three times). + """ + if self._faceNormals is None: + v = self.vertexes(indexed='faces') + self._faceNormals = np.cross(v[:,1]-v[:,0], v[:,2]-v[:,0]) + + if indexed is None: + return self._faceNormals + elif indexed == 'faces': + if self._faceNormalsIndexedByFaces is None: + norms = np.empty((self._faceNormals.shape[0], 3, 3)) + norms[:] = self._faceNormals[:,np.newaxis,:] + self._faceNormalsIndexedByFaces = norms + return self._faceNormalsIndexedByFaces + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def vertexNormals(self, indexed=None): + """ + Return an array of normal vectors. + By default, the array will be (N, 3) with one entry per unique vertex in the mesh. + If indexed is 'faces', then the array will contain three normal vectors per face + (and some vertexes may be repeated). + """ + if self._vertexNormals is None: + faceNorms = self.faceNormals() + vertFaces = self.vertexFaces() + self._vertexNormals = np.empty(self._vertexes.shape, dtype=float) + for vindex in xrange(self._vertexes.shape[0]): + faces = vertFaces[vindex] + if len(faces) == 0: + self._vertexNormals[vindex] = (0,0,0) + continue + norms = faceNorms[faces] ## get all face normals + norm = norms.sum(axis=0) ## sum normals + norm /= (norm**2).sum()**0.5 ## and re-normalize + self._vertexNormals[vindex] = norm + + if indexed is None: + return self._vertexNormals + elif indexed == 'faces': + return self._vertexNormals[self.faces()] + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def vertexColors(self, indexed=None): + """ + Return an array (Nv, 4) of vertex colors. + If indexed=='faces', then instead return an indexed array + (Nf, 3, 4). + """ + if indexed is None: + return self._vertexColors + elif indexed == 'faces': + if self._vertexColorsIndexedByFaces is None: + self._vertexColorsIndexedByFaces = self._vertexColors[self.faces()] + return self._vertexColorsIndexedByFaces + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def setVertexColors(self, colors, indexed=None): + """ + Set the vertex color array (Nv, 4). + If indexed=='faces', then the array will be interpreted + as indexed and should have shape (Nf, 3, 4) + """ + if indexed is None: + self._vertexColors = colors + self._vertexColorsIndexedByFaces = None + elif indexed == 'faces': + self._vertexColors = None + self._vertexColorsIndexedByFaces = colors + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def faceColors(self, indexed=None): + """ + Return an array (Nf, 4) of face colors. + If indexed=='faces', then instead return an indexed array + (Nf, 3, 4) (note this is just the same array with each color + repeated three times). + """ + if indexed is None: + return self._faceColors + elif indexed == 'faces': + if self._faceColorsIndexedByFaces is None and self._faceColors is not None: + Nf = self._faceColors.shape[0] + self._faceColorsIndexedByFaces = np.empty((Nf, 3, 4), dtype=self._faceColors.dtype) + self._faceColorsIndexedByFaces[:] = self._faceColors.reshape(Nf, 1, 4) + return self._faceColorsIndexedByFaces + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def setFaceColors(self, colors, indexed=None): + """ + Set the face color array (Nf, 4). + If indexed=='faces', then the array will be interpreted + as indexed and should have shape (Nf, 3, 4) + """ + if indexed is None: + self._faceColors = colors + self._faceColorsIndexedByFaces = None + elif indexed == 'faces': + self._faceColors = None + self._faceColorsIndexedByFaces = colors + else: + raise Exception("Invalid indexing mode. Accepts: None, 'faces'") + + def faceCount(self): + """ + Return the number of faces in the mesh. + """ + if self._faces is not None: + return self._faces.shape[0] + elif self._vertexesIndexedByFaces is not None: + return self._vertexesIndexedByFaces.shape[0] + + def edgeColors(self): + return self._edgeColors + + #def _setIndexedFaces(self, faces, vertexColors=None, faceColors=None): + #self._vertexesIndexedByFaces = faces + #self._vertexColorsIndexedByFaces = vertexColors + #self._faceColorsIndexedByFaces = faceColors + + def _computeUnindexedVertexes(self): + ## Given (Nv, 3, 3) array of vertexes-indexed-by-face, convert backward to unindexed vertexes + ## This is done by collapsing into a list of 'unique' vertexes (difference < 1e-14) + + ## I think generally this should be discouraged.. + faces = self._vertexesIndexedByFaces + verts = {} ## used to remember the index of each vertex position + self._faces = np.empty(faces.shape[:2], dtype=np.uint) + self._vertexes = [] + self._vertexFaces = [] + self._faceNormals = None + self._vertexNormals = None + for i in xrange(faces.shape[0]): + face = faces[i] + inds = [] + for j in range(face.shape[0]): + pt = face[j] + pt2 = tuple([round(x*1e14) for x in pt]) ## quantize to be sure that nearly-identical points will be merged + index = verts.get(pt2, None) + if index is None: + #self._vertexes.append(QtGui.QVector3D(*pt)) + self._vertexes.append(pt) + self._vertexFaces.append([]) + index = len(self._vertexes)-1 + verts[pt2] = index + self._vertexFaces[index].append(i) # keep track of which vertexes belong to which faces + self._faces[i,j] = index + self._vertexes = np.array(self._vertexes, dtype=float) + + #def _setUnindexedFaces(self, faces, vertexes, vertexColors=None, faceColors=None): + #self._vertexes = vertexes #[QtGui.QVector3D(*v) for v in vertexes] + #self._faces = faces.astype(np.uint) + #self._edges = None + #self._vertexFaces = None + #self._faceNormals = None + #self._vertexNormals = None + #self._vertexColors = vertexColors + #self._faceColors = faceColors + + def vertexFaces(self): + """ + Return list mapping each vertex index to a list of face indexes that use the vertex. + """ + if self._vertexFaces is None: + self._vertexFaces = [[] for i in xrange(len(self.vertexes()))] + for i in xrange(self._faces.shape[0]): + face = self._faces[i] + for ind in face: + self._vertexFaces[ind].append(i) + return self._vertexFaces + + #def reverseNormals(self): + #""" + #Reverses the direction of all normal vectors. + #""" + #pass + + #def generateEdgesFromFaces(self): + #""" + #Generate a set of edges by listing all the edges of faces and removing any duplicates. + #Useful for displaying wireframe meshes. + #""" + #pass + + def _computeEdges(self): + if not self.hasFaceIndexedData: + ## generate self._edges from self._faces + nf = len(self._faces) + edges = np.empty(nf*3, dtype=[('i', np.uint, 2)]) + edges['i'][0:nf] = self._faces[:,:2] + edges['i'][nf:2*nf] = self._faces[:,1:3] + edges['i'][-nf:,0] = self._faces[:,2] + edges['i'][-nf:,1] = self._faces[:,0] + + # sort per-edge + mask = edges['i'][:,0] > edges['i'][:,1] + edges['i'][mask] = edges['i'][mask][:,::-1] + + # remove duplicate entries + self._edges = np.unique(edges)['i'] + #print self._edges + elif self._vertexesIndexedByFaces is not None: + verts = self._vertexesIndexedByFaces + edges = np.empty((verts.shape[0], 3, 2), dtype=np.uint) + nf = verts.shape[0] + edges[:,0,0] = np.arange(nf) * 3 + edges[:,0,1] = edges[:,0,0] + 1 + edges[:,1,0] = edges[:,0,1] + edges[:,1,1] = edges[:,1,0] + 1 + edges[:,2,0] = edges[:,1,1] + edges[:,2,1] = edges[:,0,0] + self._edges = edges + else: + raise Exception("MeshData cannot generate edges--no faces in this data.") + + + def save(self): + """Serialize this mesh to a string appropriate for disk storage""" + import pickle + if self._faces is not None: + names = ['_vertexes', '_faces'] + else: + names = ['_vertexesIndexedByFaces'] + + if self._vertexColors is not None: + names.append('_vertexColors') + elif self._vertexColorsIndexedByFaces is not None: + names.append('_vertexColorsIndexedByFaces') + + if self._faceColors is not None: + names.append('_faceColors') + elif self._faceColorsIndexedByFaces is not None: + names.append('_faceColorsIndexedByFaces') + + state = dict([(n,getattr(self, n)) for n in names]) + return pickle.dumps(state) + + def restore(self, state): + """Restore the state of a mesh previously saved using save()""" + import pickle + state = pickle.loads(state) + for k in state: + if isinstance(state[k], list): + if isinstance(state[k][0], QtGui.QVector3D): + state[k] = [[v.x(), v.y(), v.z()] for v in state[k]] + state[k] = np.array(state[k]) + setattr(self, k, state[k]) + + + + @staticmethod + def sphere(rows, cols, radius=1.0, offset=True): + """ + Return a MeshData instance with vertexes and faces computed + for a spherical surface. + """ + verts = np.empty((rows+1, cols, 3), dtype=float) + + ## compute vertexes + phi = (np.arange(rows+1) * np.pi / rows).reshape(rows+1, 1) + s = radius * np.sin(phi) + verts[...,2] = radius * np.cos(phi) + th = ((np.arange(cols) * 2 * np.pi / cols).reshape(1, cols)) + if offset: + th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1,1)) ## rotate each row by 1/2 column + verts[...,0] = s * np.cos(th) + verts[...,1] = s * np.sin(th) + verts = verts.reshape((rows+1)*cols, 3)[cols-1:-(cols-1)] ## remove redundant vertexes from top and bottom + + ## compute faces + faces = np.empty((rows*cols*2, 3), dtype=np.uint) + rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]]) + rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]]) + for row in range(rows): + start = row * cols * 2 + faces[start:start+cols] = rowtemplate1 + row * cols + faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols + faces = faces[cols:-cols] ## cut off zero-area triangles at top and bottom + + ## adjust for redundant vertexes that were removed from top and bottom + vmin = cols-1 + faces[facesvmax] = vmax + + return MeshData(vertexes=verts, faces=faces) + + @staticmethod + def cylinder(rows, cols, radius=[1.0, 1.0], length=1.0, offset=False): + """ + Return a MeshData instance with vertexes and faces computed + for a cylindrical surface. + The cylinder may be tapered with different radii at each end (truncated cone) + """ + verts = np.empty((rows+1, cols, 3), dtype=float) + if isinstance(radius, int): + radius = [radius, radius] # convert to list + ## compute vertexes + th = np.linspace(2 * np.pi, 0, cols).reshape(1, cols) + r = np.linspace(radius[0],radius[1],num=rows+1, endpoint=True).reshape(rows+1, 1) # radius as a function of z + verts[...,2] = np.linspace(0, length, num=rows+1, endpoint=True).reshape(rows+1, 1) # z + if offset: + th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1,1)) ## rotate each row by 1/2 column + verts[...,0] = r * np.cos(th) # x = r cos(th) + verts[...,1] = r * np.sin(th) # y = r sin(th) + verts = verts.reshape((rows+1)*cols, 3) # just reshape: no redundant vertices... + ## compute faces + faces = np.empty((rows*cols*2, 3), dtype=np.uint) + rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]]) + rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]]) + for row in range(rows): + start = row * cols * 2 + faces[start:start+cols] = rowtemplate1 + row * cols + faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols + + return MeshData(vertexes=verts, faces=faces) + diff --git a/papi/pyqtgraph/opengl/__init__.py b/papi/pyqtgraph/opengl/__init__.py new file mode 100644 index 00000000..931003e4 --- /dev/null +++ b/papi/pyqtgraph/opengl/__init__.py @@ -0,0 +1,22 @@ +from .GLViewWidget import GLViewWidget + +## dynamic imports cause too many problems. +#from .. import importAll +#importAll('items', globals(), locals()) + +from .items.GLGridItem import * +from .items.GLBarGraphItem import * +from .items.GLScatterPlotItem import * +from .items.GLMeshItem import * +from .items.GLLinePlotItem import * +from .items.GLAxisItem import * +from .items.GLImageItem import * +from .items.GLSurfacePlotItem import * +from .items.GLBoxItem import * +from .items.GLVolumeItem import * + +from .MeshData import MeshData +## for backward compatibility: +#MeshData.MeshData = MeshData ## breaks autodoc. + +from . import shaders diff --git a/papi/pyqtgraph/opengl/glInfo.py b/papi/pyqtgraph/opengl/glInfo.py new file mode 100644 index 00000000..84346d81 --- /dev/null +++ b/papi/pyqtgraph/opengl/glInfo.py @@ -0,0 +1,16 @@ +from ..Qt import QtCore, QtGui, QtOpenGL +from OpenGL.GL import * +app = QtGui.QApplication([]) + +class GLTest(QtOpenGL.QGLWidget): + def __init__(self): + QtOpenGL.QGLWidget.__init__(self) + self.makeCurrent() + print("GL version:" + glGetString(GL_VERSION)) + print("MAX_TEXTURE_SIZE: %d" % glGetIntegerv(GL_MAX_TEXTURE_SIZE)) + print("MAX_3D_TEXTURE_SIZE: %d" % glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE)) + print("Extensions: " + glGetString(GL_EXTENSIONS)) + +GLTest() + + diff --git a/papi/pyqtgraph/opengl/items/GLAxisItem.py b/papi/pyqtgraph/opengl/items/GLAxisItem.py new file mode 100644 index 00000000..989a44ca --- /dev/null +++ b/papi/pyqtgraph/opengl/items/GLAxisItem.py @@ -0,0 +1,64 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from ... import QtGui + +__all__ = ['GLAxisItem'] + +class GLAxisItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays three lines indicating origin and orientation of local coordinate system. + + """ + + def __init__(self, size=None, antialias=True, glOptions='translucent'): + GLGraphicsItem.__init__(self) + if size is None: + size = QtGui.QVector3D(1,1,1) + self.antialias = antialias + self.setSize(size=size) + self.setGLOptions(glOptions) + + def setSize(self, x=None, y=None, z=None, size=None): + """ + Set the size of the axes (in its local coordinate system; this does not affect the transform) + Arguments can be x,y,z or size=QVector3D(). + """ + if size is not None: + x = size.x() + y = size.y() + z = size.z() + self.__size = [x,y,z] + self.update() + + def size(self): + return self.__size[:] + + + def paint(self): + + #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + self.setupGLState() + + if self.antialias: + glEnable(GL_LINE_SMOOTH) + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) + + glBegin( GL_LINES ) + + x,y,z = self.size() + glColor4f(0, 1, 0, .6) # z is green + glVertex3f(0, 0, 0) + glVertex3f(0, 0, z) + + glColor4f(1, 1, 0, .6) # y is yellow + glVertex3f(0, 0, 0) + glVertex3f(0, y, 0) + + glColor4f(0, 0, 1, .6) # x is blue + glVertex3f(0, 0, 0) + glVertex3f(x, 0, 0) + glEnd() diff --git a/papi/pyqtgraph/opengl/items/GLBarGraphItem.py b/papi/pyqtgraph/opengl/items/GLBarGraphItem.py new file mode 100644 index 00000000..b3060dc9 --- /dev/null +++ b/papi/pyqtgraph/opengl/items/GLBarGraphItem.py @@ -0,0 +1,29 @@ +from .GLMeshItem import GLMeshItem +from ..MeshData import MeshData +import numpy as np + +class GLBarGraphItem(GLMeshItem): + def __init__(self, pos, size): + """ + pos is (...,3) array of the bar positions (the corner of each bar) + size is (...,3) array of the sizes of each bar + """ + nCubes = reduce(lambda a,b: a*b, pos.shape[:-1]) + cubeVerts = np.mgrid[0:2,0:2,0:2].reshape(3,8).transpose().reshape(1,8,3) + cubeFaces = np.array([ + [0,1,2], [3,2,1], + [4,5,6], [7,6,5], + [0,1,4], [5,4,1], + [2,3,6], [7,6,3], + [0,2,4], [6,4,2], + [1,3,5], [7,5,3]]).reshape(1,12,3) + size = size.reshape((nCubes, 1, 3)) + pos = pos.reshape((nCubes, 1, 3)) + verts = cubeVerts * size + pos + faces = cubeFaces + (np.arange(nCubes) * 8).reshape(nCubes,1,1) + md = MeshData(verts.reshape(nCubes*8,3), faces.reshape(nCubes*12,3)) + + GLMeshItem.__init__(self, meshdata=md, shader='shaded', smooth=False) + + + \ No newline at end of file diff --git a/papi/pyqtgraph/opengl/items/GLBoxItem.py b/papi/pyqtgraph/opengl/items/GLBoxItem.py new file mode 100644 index 00000000..f0a6ae6c --- /dev/null +++ b/papi/pyqtgraph/opengl/items/GLBoxItem.py @@ -0,0 +1,88 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from ...Qt import QtGui +from ... import functions as fn + +__all__ = ['GLBoxItem'] + +class GLBoxItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays a wire-frame box. + """ + def __init__(self, size=None, color=None, glOptions='translucent'): + GLGraphicsItem.__init__(self) + if size is None: + size = QtGui.QVector3D(1,1,1) + self.setSize(size=size) + if color is None: + color = (255,255,255,80) + self.setColor(color) + self.setGLOptions(glOptions) + + def setSize(self, x=None, y=None, z=None, size=None): + """ + Set the size of the box (in its local coordinate system; this does not affect the transform) + Arguments can be x,y,z or size=QVector3D(). + """ + if size is not None: + x = size.x() + y = size.y() + z = size.z() + self.__size = [x,y,z] + self.update() + + def size(self): + return self.__size[:] + + def setColor(self, *args): + """Set the color of the box. Arguments are the same as those accepted by functions.mkColor()""" + self.__color = fn.Color(*args) + + def color(self): + return self.__color + + def paint(self): + #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + ##glAlphaFunc( GL_ALWAYS,0.5 ) + #glEnable( GL_POINT_SMOOTH ) + #glDisable( GL_DEPTH_TEST ) + self.setupGLState() + + glBegin( GL_LINES ) + + glColor4f(*self.color().glColor()) + x,y,z = self.size() + glVertex3f(0, 0, 0) + glVertex3f(0, 0, z) + glVertex3f(x, 0, 0) + glVertex3f(x, 0, z) + glVertex3f(0, y, 0) + glVertex3f(0, y, z) + glVertex3f(x, y, 0) + glVertex3f(x, y, z) + + glVertex3f(0, 0, 0) + glVertex3f(0, y, 0) + glVertex3f(x, 0, 0) + glVertex3f(x, y, 0) + glVertex3f(0, 0, z) + glVertex3f(0, y, z) + glVertex3f(x, 0, z) + glVertex3f(x, y, z) + + glVertex3f(0, 0, 0) + glVertex3f(x, 0, 0) + glVertex3f(0, y, 0) + glVertex3f(x, y, 0) + glVertex3f(0, 0, z) + glVertex3f(x, 0, z) + glVertex3f(0, y, z) + glVertex3f(x, y, z) + + glEnd() + + \ No newline at end of file diff --git a/papi/pyqtgraph/opengl/items/GLGridItem.py b/papi/pyqtgraph/opengl/items/GLGridItem.py new file mode 100644 index 00000000..4d6bc9d6 --- /dev/null +++ b/papi/pyqtgraph/opengl/items/GLGridItem.py @@ -0,0 +1,78 @@ +import numpy as np + +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from ... import QtGui + +__all__ = ['GLGridItem'] + +class GLGridItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays a wire-grame grid. + """ + + def __init__(self, size=None, color=None, antialias=True, glOptions='translucent'): + GLGraphicsItem.__init__(self) + self.setGLOptions(glOptions) + self.antialias = antialias + if size is None: + size = QtGui.QVector3D(20,20,1) + self.setSize(size=size) + self.setSpacing(1, 1, 1) + + def setSize(self, x=None, y=None, z=None, size=None): + """ + Set the size of the axes (in its local coordinate system; this does not affect the transform) + Arguments can be x,y,z or size=QVector3D(). + """ + if size is not None: + x = size.x() + y = size.y() + z = size.z() + self.__size = [x,y,z] + self.update() + + def size(self): + return self.__size[:] + + def setSpacing(self, x=None, y=None, z=None, spacing=None): + """ + Set the spacing between grid lines. + Arguments can be x,y,z or spacing=QVector3D(). + """ + if spacing is not None: + x = spacing.x() + y = spacing.y() + z = spacing.z() + self.__spacing = [x,y,z] + self.update() + + def spacing(self): + return self.__spacing[:] + + def paint(self): + self.setupGLState() + + if self.antialias: + glEnable(GL_LINE_SMOOTH) + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) + + glBegin( GL_LINES ) + + x,y,z = self.size() + xs,ys,zs = self.spacing() + xvals = np.arange(-x/2., x/2. + xs*0.001, xs) + yvals = np.arange(-y/2., y/2. + ys*0.001, ys) + glColor4f(1, 1, 1, .3) + for x in xvals: + glVertex3f(x, yvals[0], 0) + glVertex3f(x, yvals[-1], 0) + for y in yvals: + glVertex3f(xvals[0], y, 0) + glVertex3f(xvals[-1], y, 0) + + glEnd() diff --git a/papi/pyqtgraph/opengl/items/GLImageItem.py b/papi/pyqtgraph/opengl/items/GLImageItem.py new file mode 100644 index 00000000..59ddaf6f --- /dev/null +++ b/papi/pyqtgraph/opengl/items/GLImageItem.py @@ -0,0 +1,99 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from ...Qt import QtGui +import numpy as np + +__all__ = ['GLImageItem'] + +class GLImageItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays image data as a textured quad. + """ + + + def __init__(self, data, smooth=False, glOptions='translucent'): + """ + + ============== ======================================================================================= + **Arguments:** + data Volume data to be rendered. *Must* be 3D numpy array (x, y, RGBA) with dtype=ubyte. + (See functions.makeRGBA) + smooth (bool) If True, the volume slices are rendered with linear interpolation + ============== ======================================================================================= + """ + + self.smooth = smooth + self._needUpdate = False + GLGraphicsItem.__init__(self) + self.setData(data) + self.setGLOptions(glOptions) + + def initializeGL(self): + glEnable(GL_TEXTURE_2D) + self.texture = glGenTextures(1) + + def setData(self, data): + self.data = data + self._needUpdate = True + self.update() + + def _updateTexture(self): + glBindTexture(GL_TEXTURE_2D, self.texture) + if self.smooth: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + else: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) + #glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) + shape = self.data.shape + + ## Test texture dimensions first + glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) + if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: + raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((1,0,2))) + glDisable(GL_TEXTURE_2D) + + #self.lists = {} + #for ax in [0,1,2]: + #for d in [-1, 1]: + #l = glGenLists(1) + #self.lists[(ax,d)] = l + #glNewList(l, GL_COMPILE) + #self.drawVolume(ax, d) + #glEndList() + + + def paint(self): + if self._needUpdate: + self._updateTexture() + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.texture) + + self.setupGLState() + + #glEnable(GL_DEPTH_TEST) + ##glDisable(GL_CULL_FACE) + #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + glColor4f(1,1,1,1) + + glBegin(GL_QUADS) + glTexCoord2f(0,0) + glVertex3f(0,0,0) + glTexCoord2f(1,0) + glVertex3f(self.data.shape[0], 0, 0) + glTexCoord2f(1,1) + glVertex3f(self.data.shape[0], self.data.shape[1], 0) + glTexCoord2f(0,1) + glVertex3f(0, self.data.shape[1], 0) + glEnd() + glDisable(GL_TEXTURE_3D) + diff --git a/papi/pyqtgraph/opengl/items/GLLinePlotItem.py b/papi/pyqtgraph/opengl/items/GLLinePlotItem.py new file mode 100644 index 00000000..f5cb7545 --- /dev/null +++ b/papi/pyqtgraph/opengl/items/GLLinePlotItem.py @@ -0,0 +1,112 @@ +from OpenGL.GL import * +from OpenGL.arrays import vbo +from .. GLGraphicsItem import GLGraphicsItem +from .. import shaders +from ... import QtGui +import numpy as np + +__all__ = ['GLLinePlotItem'] + +class GLLinePlotItem(GLGraphicsItem): + """Draws line plots in 3D.""" + + def __init__(self, **kwds): + """All keyword arguments are passed to setData()""" + GLGraphicsItem.__init__(self) + glopts = kwds.pop('glOptions', 'additive') + self.setGLOptions(glopts) + self.pos = None + self.mode = 'line_strip' + self.width = 1. + self.color = (1.0,1.0,1.0,1.0) + self.setData(**kwds) + + def setData(self, **kwds): + """ + Update the data displayed by this item. All arguments are optional; + for example it is allowed to update vertex positions while leaving + colors unchanged, etc. + + ==================== ================================================== + **Arguments:** + ------------------------------------------------------------------------ + pos (N,3) array of floats specifying point locations. + color (N,4) array of floats (0.0-1.0) or + tuple of floats specifying + a single color for the entire item. + width float specifying line width + antialias enables smooth line drawing + mode 'lines': Each pair of vertexes draws a single line + segment. + 'line_strip': All vertexes are drawn as a + continuous set of line segments. + ==================== ================================================== + """ + args = ['pos', 'color', 'width', 'mode', 'antialias'] + for k in kwds.keys(): + if k not in args: + raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) + self.antialias = False + for arg in args: + if arg in kwds: + setattr(self, arg, kwds[arg]) + #self.vbo.pop(arg, None) + self.update() + + def initializeGL(self): + pass + + #def setupGLState(self): + #"""Prepare OpenGL state for drawing. This function is called immediately before painting.""" + ##glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly. + #glBlendFunc(GL_SRC_ALPHA, GL_ONE) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + #glDisable( GL_DEPTH_TEST ) + + ##glEnable( GL_POINT_SMOOTH ) + + ##glHint(GL_POINT_SMOOTH_HINT, GL_NICEST) + ##glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3)) + ##glPointParameterfv(GL_POINT_SIZE_MAX, (65500,)) + ##glPointParameterfv(GL_POINT_SIZE_MIN, (0,)) + + def paint(self): + if self.pos is None: + return + self.setupGLState() + + glEnableClientState(GL_VERTEX_ARRAY) + + try: + glVertexPointerf(self.pos) + + if isinstance(self.color, np.ndarray): + glEnableClientState(GL_COLOR_ARRAY) + glColorPointerf(self.color) + else: + if isinstance(self.color, QtGui.QColor): + glColor4f(*fn.glColor(self.color)) + else: + glColor4f(*self.color) + glLineWidth(self.width) + #glPointSize(self.width) + + if self.antialias: + glEnable(GL_LINE_SMOOTH) + glEnable(GL_BLEND) + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + glHint(GL_LINE_SMOOTH_HINT, GL_NICEST) + + if self.mode == 'line_strip': + glDrawArrays(GL_LINE_STRIP, 0, int(self.pos.size / self.pos.shape[-1])) + elif self.mode == 'lines': + glDrawArrays(GL_LINES, 0, int(self.pos.size / self.pos.shape[-1])) + else: + raise Exception("Unknown line mode '%s'. (must be 'lines' or 'line_strip')" % self.mode) + + finally: + glDisableClientState(GL_COLOR_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + + diff --git a/papi/pyqtgraph/opengl/items/GLMeshItem.py b/papi/pyqtgraph/opengl/items/GLMeshItem.py new file mode 100644 index 00000000..55e75942 --- /dev/null +++ b/papi/pyqtgraph/opengl/items/GLMeshItem.py @@ -0,0 +1,227 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from .. MeshData import MeshData +from ...Qt import QtGui +from .. import shaders +from ... import functions as fn +import numpy as np + + + +__all__ = ['GLMeshItem'] + +class GLMeshItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays a 3D triangle mesh. + """ + def __init__(self, **kwds): + """ + ============== ===================================================== + **Arguments:** + meshdata MeshData object from which to determine geometry for + this item. + color Default face color used if no vertex or face colors + are specified. + edgeColor Default edge color to use if no edge colors are + specified in the mesh data. + drawEdges If True, a wireframe mesh will be drawn. + (default=False) + drawFaces If True, mesh faces are drawn. (default=True) + shader Name of shader program to use when drawing faces. + (None for no shader) + smooth If True, normal vectors are computed for each vertex + and interpolated within each face. + computeNormals If False, then computation of normal vectors is + disabled. This can provide a performance boost for + meshes that do not make use of normals. + ============== ===================================================== + """ + self.opts = { + 'meshdata': None, + 'color': (1., 1., 1., 1.), + 'drawEdges': False, + 'drawFaces': True, + 'edgeColor': (0.5, 0.5, 0.5, 1.0), + 'shader': None, + 'smooth': True, + 'computeNormals': True, + } + + GLGraphicsItem.__init__(self) + glopts = kwds.pop('glOptions', 'opaque') + self.setGLOptions(glopts) + shader = kwds.pop('shader', None) + self.setShader(shader) + + self.setMeshData(**kwds) + + ## storage for data compiled from MeshData object + self.vertexes = None + self.normals = None + self.colors = None + self.faces = None + + def setShader(self, shader): + """Set the shader used when rendering faces in the mesh. (see the GL shaders example)""" + self.opts['shader'] = shader + self.update() + + def shader(self): + shader = self.opts['shader'] + if isinstance(shader, shaders.ShaderProgram): + return shader + else: + return shaders.getShaderProgram(shader) + + def setColor(self, c): + """Set the default color to use when no vertex or face colors are specified.""" + self.opts['color'] = c + self.update() + + def setMeshData(self, **kwds): + """ + Set mesh data for this item. This can be invoked two ways: + + 1. Specify *meshdata* argument with a new MeshData object + 2. Specify keyword arguments to be passed to MeshData(..) to create a new instance. + """ + md = kwds.get('meshdata', None) + if md is None: + opts = {} + for k in ['vertexes', 'faces', 'edges', 'vertexColors', 'faceColors']: + try: + opts[k] = kwds.pop(k) + except KeyError: + pass + md = MeshData(**opts) + + self.opts['meshdata'] = md + self.opts.update(kwds) + self.meshDataChanged() + self.update() + + + def meshDataChanged(self): + """ + This method must be called to inform the item that the MeshData object + has been altered. + """ + + self.vertexes = None + self.faces = None + self.normals = None + self.colors = None + self.edges = None + self.edgeColors = None + self.update() + + def parseMeshData(self): + ## interpret vertex / normal data before drawing + ## This can: + ## - automatically generate normals if they were not specified + ## - pull vertexes/noormals/faces from MeshData if that was specified + + if self.vertexes is not None and self.normals is not None: + return + #if self.opts['normals'] is None: + #if self.opts['meshdata'] is None: + #self.opts['meshdata'] = MeshData(vertexes=self.opts['vertexes'], faces=self.opts['faces']) + if self.opts['meshdata'] is not None: + md = self.opts['meshdata'] + if self.opts['smooth'] and not md.hasFaceIndexedData(): + self.vertexes = md.vertexes() + if self.opts['computeNormals']: + self.normals = md.vertexNormals() + self.faces = md.faces() + if md.hasVertexColor(): + self.colors = md.vertexColors() + if md.hasFaceColor(): + self.colors = md.faceColors() + else: + self.vertexes = md.vertexes(indexed='faces') + if self.opts['computeNormals']: + if self.opts['smooth']: + self.normals = md.vertexNormals(indexed='faces') + else: + self.normals = md.faceNormals(indexed='faces') + self.faces = None + if md.hasVertexColor(): + self.colors = md.vertexColors(indexed='faces') + elif md.hasFaceColor(): + self.colors = md.faceColors(indexed='faces') + + if self.opts['drawEdges']: + if not md.hasFaceIndexedData(): + self.edges = md.edges() + self.edgeVerts = md.vertexes() + else: + self.edges = md.edges() + self.edgeVerts = md.vertexes(indexed='faces') + return + + def paint(self): + self.setupGLState() + + self.parseMeshData() + + if self.opts['drawFaces']: + with self.shader(): + verts = self.vertexes + norms = self.normals + color = self.colors + faces = self.faces + if verts is None: + return + glEnableClientState(GL_VERTEX_ARRAY) + try: + glVertexPointerf(verts) + + if self.colors is None: + color = self.opts['color'] + if isinstance(color, QtGui.QColor): + glColor4f(*fn.glColor(color)) + else: + glColor4f(*color) + else: + glEnableClientState(GL_COLOR_ARRAY) + glColorPointerf(color) + + + if norms is not None: + glEnableClientState(GL_NORMAL_ARRAY) + glNormalPointerf(norms) + + if faces is None: + glDrawArrays(GL_TRIANGLES, 0, np.product(verts.shape[:-1])) + else: + faces = faces.astype(np.uint).flatten() + glDrawElements(GL_TRIANGLES, faces.shape[0], GL_UNSIGNED_INT, faces) + finally: + glDisableClientState(GL_NORMAL_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + glDisableClientState(GL_COLOR_ARRAY) + + if self.opts['drawEdges']: + verts = self.edgeVerts + edges = self.edges + glEnableClientState(GL_VERTEX_ARRAY) + try: + glVertexPointerf(verts) + + if self.edgeColors is None: + color = self.opts['edgeColor'] + if isinstance(color, QtGui.QColor): + glColor4f(*fn.glColor(color)) + else: + glColor4f(*color) + else: + glEnableClientState(GL_COLOR_ARRAY) + glColorPointerf(color) + edges = edges.flatten() + glDrawElements(GL_LINES, edges.shape[0], GL_UNSIGNED_INT, edges) + finally: + glDisableClientState(GL_VERTEX_ARRAY) + glDisableClientState(GL_COLOR_ARRAY) + diff --git a/papi/pyqtgraph/opengl/items/GLScatterPlotItem.py b/papi/pyqtgraph/opengl/items/GLScatterPlotItem.py new file mode 100644 index 00000000..dc4b298a --- /dev/null +++ b/papi/pyqtgraph/opengl/items/GLScatterPlotItem.py @@ -0,0 +1,183 @@ +from OpenGL.GL import * +from OpenGL.arrays import vbo +from .. GLGraphicsItem import GLGraphicsItem +from .. import shaders +from ... import QtGui +import numpy as np + +__all__ = ['GLScatterPlotItem'] + +class GLScatterPlotItem(GLGraphicsItem): + """Draws points at a list of 3D positions.""" + + def __init__(self, **kwds): + GLGraphicsItem.__init__(self) + glopts = kwds.pop('glOptions', 'additive') + self.setGLOptions(glopts) + self.pos = [] + self.size = 10 + self.color = [1.0,1.0,1.0,0.5] + self.pxMode = True + #self.vbo = {} ## VBO does not appear to improve performance very much. + self.setData(**kwds) + + def setData(self, **kwds): + """ + Update the data displayed by this item. All arguments are optional; + for example it is allowed to update spot positions while leaving + colors unchanged, etc. + + ==================== ================================================== + **Arguments:** + pos (N,3) array of floats specifying point locations. + color (N,4) array of floats (0.0-1.0) specifying + spot colors OR a tuple of floats specifying + a single color for all spots. + size (N,) array of floats specifying spot sizes or + a single value to apply to all spots. + pxMode If True, spot sizes are expressed in pixels. + Otherwise, they are expressed in item coordinates. + ==================== ================================================== + """ + args = ['pos', 'color', 'size', 'pxMode'] + for k in kwds.keys(): + if k not in args: + raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) + + args.remove('pxMode') + for arg in args: + if arg in kwds: + setattr(self, arg, kwds[arg]) + #self.vbo.pop(arg, None) + + self.pxMode = kwds.get('pxMode', self.pxMode) + self.update() + + def initializeGL(self): + + ## Generate texture for rendering points + w = 64 + def fn(x,y): + r = ((x-w/2.)**2 + (y-w/2.)**2) ** 0.5 + return 255 * (w/2. - np.clip(r, w/2.-1.0, w/2.)) + pData = np.empty((w, w, 4)) + pData[:] = 255 + pData[:,:,3] = np.fromfunction(fn, pData.shape[:2]) + #print pData.shape, pData.min(), pData.max() + pData = pData.astype(np.ubyte) + + if getattr(self, "pointTexture", None) is None: + self.pointTexture = glGenTextures(1) + glActiveTexture(GL_TEXTURE0) + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.pointTexture) + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pData.shape[0], pData.shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, pData) + + self.shader = shaders.getShaderProgram('pointSprite') + + #def getVBO(self, name): + #if name not in self.vbo: + #self.vbo[name] = vbo.VBO(getattr(self, name).astype('f')) + #return self.vbo[name] + + #def setupGLState(self): + #"""Prepare OpenGL state for drawing. This function is called immediately before painting.""" + ##glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly. + #glBlendFunc(GL_SRC_ALPHA, GL_ONE) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + #glDisable( GL_DEPTH_TEST ) + + ##glEnable( GL_POINT_SMOOTH ) + + ##glHint(GL_POINT_SMOOTH_HINT, GL_NICEST) + ##glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3)) + ##glPointParameterfv(GL_POINT_SIZE_MAX, (65500,)) + ##glPointParameterfv(GL_POINT_SIZE_MIN, (0,)) + + def paint(self): + self.setupGLState() + + glEnable(GL_POINT_SPRITE) + + glActiveTexture(GL_TEXTURE0) + glEnable( GL_TEXTURE_2D ) + glBindTexture(GL_TEXTURE_2D, self.pointTexture) + + glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE) + #glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) ## use texture color exactly + #glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ) ## texture modulates current color + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + glEnable(GL_PROGRAM_POINT_SIZE) + + + with self.shader: + #glUniform1i(self.shader.uniform('texture'), 0) ## inform the shader which texture to use + glEnableClientState(GL_VERTEX_ARRAY) + try: + pos = self.pos + #if pos.ndim > 2: + #pos = pos.reshape((reduce(lambda a,b: a*b, pos.shape[:-1]), pos.shape[-1])) + glVertexPointerf(pos) + + if isinstance(self.color, np.ndarray): + glEnableClientState(GL_COLOR_ARRAY) + glColorPointerf(self.color) + else: + if isinstance(self.color, QtGui.QColor): + glColor4f(*fn.glColor(self.color)) + else: + glColor4f(*self.color) + + if not self.pxMode or isinstance(self.size, np.ndarray): + glEnableClientState(GL_NORMAL_ARRAY) + norm = np.empty(pos.shape) + if self.pxMode: + norm[...,0] = self.size + else: + gpos = self.mapToView(pos.transpose()).transpose() + pxSize = self.view().pixelSize(gpos) + norm[...,0] = self.size / pxSize + + glNormalPointerf(norm) + else: + glNormal3f(self.size, 0, 0) ## vertex shader uses norm.x to determine point size + #glPointSize(self.size) + glDrawArrays(GL_POINTS, 0, int(pos.size / pos.shape[-1])) + finally: + glDisableClientState(GL_NORMAL_ARRAY) + glDisableClientState(GL_VERTEX_ARRAY) + glDisableClientState(GL_COLOR_ARRAY) + #posVBO.unbind() + + #for i in range(len(self.pos)): + #pos = self.pos[i] + + #if isinstance(self.color, np.ndarray): + #color = self.color[i] + #else: + #color = self.color + #if isinstance(self.color, QtGui.QColor): + #color = fn.glColor(self.color) + + #if isinstance(self.size, np.ndarray): + #size = self.size[i] + #else: + #size = self.size + + #pxSize = self.view().pixelSize(QtGui.QVector3D(*pos)) + + #glPointSize(size / pxSize) + #glBegin( GL_POINTS ) + #glColor4f(*color) # x is blue + ##glNormal3f(size, 0, 0) + #glVertex3f(*pos) + #glEnd() + + + + + diff --git a/papi/pyqtgraph/opengl/items/GLSurfacePlotItem.py b/papi/pyqtgraph/opengl/items/GLSurfacePlotItem.py new file mode 100644 index 00000000..e39ef3bb --- /dev/null +++ b/papi/pyqtgraph/opengl/items/GLSurfacePlotItem.py @@ -0,0 +1,138 @@ +from OpenGL.GL import * +from .GLMeshItem import GLMeshItem +from .. MeshData import MeshData +from ...Qt import QtGui +import numpy as np + + + +__all__ = ['GLSurfacePlotItem'] + +class GLSurfacePlotItem(GLMeshItem): + """ + **Bases:** :class:`GLMeshItem ` + + Displays a surface plot on a regular x,y grid + """ + def __init__(self, x=None, y=None, z=None, colors=None, **kwds): + """ + The x, y, z, and colors arguments are passed to setData(). + All other keyword arguments are passed to GLMeshItem.__init__(). + """ + + self._x = None + self._y = None + self._z = None + self._color = None + self._vertexes = None + self._meshdata = MeshData() + GLMeshItem.__init__(self, meshdata=self._meshdata, **kwds) + + self.setData(x, y, z, colors) + + + + def setData(self, x=None, y=None, z=None, colors=None): + """ + Update the data in this surface plot. + + ============== ===================================================================== + **Arguments:** + x,y 1D arrays of values specifying the x,y positions of vertexes in the + grid. If these are omitted, then the values will be assumed to be + integers. + z 2D array of height values for each grid vertex. + colors (width, height, 4) array of vertex colors. + ============== ===================================================================== + + All arguments are optional. + + Note that if vertex positions are updated, the normal vectors for each triangle must + be recomputed. This is somewhat expensive if the surface was initialized with smooth=False + and very expensive if smooth=True. For faster performance, initialize with + computeNormals=False and use per-vertex colors or a normal-independent shader program. + """ + if x is not None: + if self._x is None or len(x) != len(self._x): + self._vertexes = None + self._x = x + + if y is not None: + if self._y is None or len(y) != len(self._y): + self._vertexes = None + self._y = y + + if z is not None: + #if self._x is None: + #self._x = np.arange(z.shape[0]) + #self._vertexes = None + #if self._y is None: + #self._y = np.arange(z.shape[1]) + #self._vertexes = None + + if self._x is not None and z.shape[0] != len(self._x): + raise Exception('Z values must have shape (len(x), len(y))') + if self._y is not None and z.shape[1] != len(self._y): + raise Exception('Z values must have shape (len(x), len(y))') + self._z = z + if self._vertexes is not None and self._z.shape != self._vertexes.shape[:2]: + self._vertexes = None + + if colors is not None: + self._colors = colors + self._meshdata.setVertexColors(colors) + + if self._z is None: + return + + updateMesh = False + newVertexes = False + + ## Generate vertex and face array + if self._vertexes is None: + newVertexes = True + self._vertexes = np.empty((self._z.shape[0], self._z.shape[1], 3), dtype=float) + self.generateFaces() + self._meshdata.setFaces(self._faces) + updateMesh = True + + ## Copy x, y, z data into vertex array + if newVertexes or x is not None: + if x is None: + if self._x is None: + x = np.arange(self._z.shape[0]) + else: + x = self._x + self._vertexes[:, :, 0] = x.reshape(len(x), 1) + updateMesh = True + + if newVertexes or y is not None: + if y is None: + if self._y is None: + y = np.arange(self._z.shape[1]) + else: + y = self._y + self._vertexes[:, :, 1] = y.reshape(1, len(y)) + updateMesh = True + + if newVertexes or z is not None: + self._vertexes[...,2] = self._z + updateMesh = True + + ## Update MeshData + if updateMesh: + self._meshdata.setVertexes(self._vertexes.reshape(self._vertexes.shape[0]*self._vertexes.shape[1], 3)) + self.meshDataChanged() + + + def generateFaces(self): + cols = self._z.shape[1]-1 + rows = self._z.shape[0]-1 + faces = np.empty((cols*rows*2, 3), dtype=np.uint) + rowtemplate1 = np.arange(cols).reshape(cols, 1) + np.array([[0, 1, cols+1]]) + rowtemplate2 = np.arange(cols).reshape(cols, 1) + np.array([[cols+1, 1, cols+2]]) + for row in range(rows): + start = row * cols * 2 + faces[start:start+cols] = rowtemplate1 + row * (cols+1) + faces[start+cols:start+(cols*2)] = rowtemplate2 + row * (cols+1) + self._faces = faces diff --git a/papi/pyqtgraph/opengl/items/GLVolumeItem.py b/papi/pyqtgraph/opengl/items/GLVolumeItem.py new file mode 100644 index 00000000..cbe22db9 --- /dev/null +++ b/papi/pyqtgraph/opengl/items/GLVolumeItem.py @@ -0,0 +1,230 @@ +from OpenGL.GL import * +from .. GLGraphicsItem import GLGraphicsItem +from ...Qt import QtGui +import numpy as np +from ... import debug + +__all__ = ['GLVolumeItem'] + +class GLVolumeItem(GLGraphicsItem): + """ + **Bases:** :class:`GLGraphicsItem ` + + Displays volumetric data. + """ + + + def __init__(self, data, sliceDensity=1, smooth=True, glOptions='translucent'): + """ + ============== ======================================================================================= + **Arguments:** + data Volume data to be rendered. *Must* be 4D numpy array (x, y, z, RGBA) with dtype=ubyte. + sliceDensity Density of slices to render through the volume. A value of 1 means one slice per voxel. + smooth (bool) If True, the volume slices are rendered with linear interpolation + ============== ======================================================================================= + """ + + self.sliceDensity = sliceDensity + self.smooth = smooth + self.data = None + self._needUpload = False + self.texture = None + GLGraphicsItem.__init__(self) + self.setGLOptions(glOptions) + self.setData(data) + + def setData(self, data): + self.data = data + self._needUpload = True + self.update() + + def _uploadData(self): + glEnable(GL_TEXTURE_3D) + if self.texture is None: + self.texture = glGenTextures(1) + glBindTexture(GL_TEXTURE_3D, self.texture) + if self.smooth: + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + else: + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) + glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) + shape = self.data.shape + + ## Test texture dimensions first + glTexImage3D(GL_PROXY_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) + if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_3D, 0, GL_TEXTURE_WIDTH) == 0: + raise Exception("OpenGL failed to create 3D texture (%dx%dx%d); too large for this hardware." % shape[:3]) + + glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, shape[0], shape[1], shape[2], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.data.transpose((2,1,0,3))) + glDisable(GL_TEXTURE_3D) + + self.lists = {} + for ax in [0,1,2]: + for d in [-1, 1]: + l = glGenLists(1) + self.lists[(ax,d)] = l + glNewList(l, GL_COMPILE) + self.drawVolume(ax, d) + glEndList() + + self._needUpload = False + + def paint(self): + if self.data is None: + return + + if self._needUpload: + self._uploadData() + + self.setupGLState() + + glEnable(GL_TEXTURE_3D) + glBindTexture(GL_TEXTURE_3D, self.texture) + + #glEnable(GL_DEPTH_TEST) + #glDisable(GL_CULL_FACE) + #glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) + #glEnable( GL_BLEND ) + #glEnable( GL_ALPHA_TEST ) + glColor4f(1,1,1,1) + + view = self.view() + center = QtGui.QVector3D(*[x/2. for x in self.data.shape[:3]]) + cam = self.mapFromParent(view.cameraPosition()) - center + #print "center", center, "cam", view.cameraPosition(), self.mapFromParent(view.cameraPosition()), "diff", cam + cam = np.array([cam.x(), cam.y(), cam.z()]) + ax = np.argmax(abs(cam)) + d = 1 if cam[ax] > 0 else -1 + glCallList(self.lists[(ax,d)]) ## draw axes + glDisable(GL_TEXTURE_3D) + + def drawVolume(self, ax, d): + N = 5 + + imax = [0,1,2] + imax.remove(ax) + + tp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]] + vp = [[0,0,0],[0,0,0],[0,0,0],[0,0,0]] + nudge = [0.5/x for x in self.data.shape] + tp[0][imax[0]] = 0+nudge[imax[0]] + tp[0][imax[1]] = 0+nudge[imax[1]] + tp[1][imax[0]] = 1-nudge[imax[0]] + tp[1][imax[1]] = 0+nudge[imax[1]] + tp[2][imax[0]] = 1-nudge[imax[0]] + tp[2][imax[1]] = 1-nudge[imax[1]] + tp[3][imax[0]] = 0+nudge[imax[0]] + tp[3][imax[1]] = 1-nudge[imax[1]] + + vp[0][imax[0]] = 0 + vp[0][imax[1]] = 0 + vp[1][imax[0]] = self.data.shape[imax[0]] + vp[1][imax[1]] = 0 + vp[2][imax[0]] = self.data.shape[imax[0]] + vp[2][imax[1]] = self.data.shape[imax[1]] + vp[3][imax[0]] = 0 + vp[3][imax[1]] = self.data.shape[imax[1]] + slices = self.data.shape[ax] * self.sliceDensity + r = list(range(slices)) + if d == -1: + r = r[::-1] + + glBegin(GL_QUADS) + tzVals = np.linspace(nudge[ax], 1.0-nudge[ax], slices) + vzVals = np.linspace(0, self.data.shape[ax], slices) + for i in r: + z = tzVals[i] + w = vzVals[i] + + tp[0][ax] = z + tp[1][ax] = z + tp[2][ax] = z + tp[3][ax] = z + + vp[0][ax] = w + vp[1][ax] = w + vp[2][ax] = w + vp[3][ax] = w + + + glTexCoord3f(*tp[0]) + glVertex3f(*vp[0]) + glTexCoord3f(*tp[1]) + glVertex3f(*vp[1]) + glTexCoord3f(*tp[2]) + glVertex3f(*vp[2]) + glTexCoord3f(*tp[3]) + glVertex3f(*vp[3]) + glEnd() + + + + + + + + + + ## Interesting idea: + ## remove projection/modelview matrixes, recreate in texture coords. + ## it _sorta_ works, but needs tweaking. + #mvm = glGetDoublev(GL_MODELVIEW_MATRIX) + #pm = glGetDoublev(GL_PROJECTION_MATRIX) + #m = QtGui.QMatrix4x4(mvm.flatten()).inverted()[0] + #p = QtGui.QMatrix4x4(pm.flatten()).inverted()[0] + + #glMatrixMode(GL_PROJECTION) + #glPushMatrix() + #glLoadIdentity() + #N=1 + #glOrtho(-N,N,-N,N,-100,100) + + #glMatrixMode(GL_MODELVIEW) + #glLoadIdentity() + + + #glMatrixMode(GL_TEXTURE) + #glLoadIdentity() + #glMultMatrixf(m.copyDataTo()) + + #view = self.view() + #w = view.width() + #h = view.height() + #dist = view.opts['distance'] + #fov = view.opts['fov'] + #nearClip = dist * .1 + #farClip = dist * 5. + #r = nearClip * np.tan(fov) + #t = r * h / w + + #p = QtGui.QMatrix4x4() + #p.frustum( -r, r, -t, t, nearClip, farClip) + #glMultMatrixf(p.inverted()[0].copyDataTo()) + + + #glBegin(GL_QUADS) + + #M=1 + #for i in range(500): + #z = i/500. + #w = -i/500. + #glTexCoord3f(-M, -M, z) + #glVertex3f(-N, -N, w) + #glTexCoord3f(M, -M, z) + #glVertex3f(N, -N, w) + #glTexCoord3f(M, M, z) + #glVertex3f(N, N, w) + #glTexCoord3f(-M, M, z) + #glVertex3f(-N, N, w) + #glEnd() + #glDisable(GL_TEXTURE_3D) + + #glMatrixMode(GL_PROJECTION) + #glPopMatrix() + + + diff --git a/papi/pyqtgraph/opengl/items/__init__.py b/papi/pyqtgraph/opengl/items/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/papi/pyqtgraph/opengl/shaders.py b/papi/pyqtgraph/opengl/shaders.py new file mode 100644 index 00000000..8922cd21 --- /dev/null +++ b/papi/pyqtgraph/opengl/shaders.py @@ -0,0 +1,402 @@ +try: + from OpenGL import NullFunctionError +except ImportError: + from OpenGL.error import NullFunctionError +from OpenGL.GL import * +from OpenGL.GL import shaders +import re + +## For centralizing and managing vertex/fragment shader programs. + +def initShaders(): + global Shaders + Shaders = [ + ShaderProgram(None, []), + + ## increases fragment alpha as the normal turns orthogonal to the view + ## this is useful for viewing shells that enclose a volume (such as isosurfaces) + ShaderProgram('balloon', [ + VertexShader(""" + varying vec3 normal; + void main() { + // compute here for use in fragment shader + normal = normalize(gl_NormalMatrix * gl_Normal); + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + varying vec3 normal; + void main() { + vec4 color = gl_Color; + color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0); + gl_FragColor = color; + } + """) + ]), + + ## colors fragments based on face normals relative to view + ## This means that the colors will change depending on how the view is rotated + ShaderProgram('viewNormalColor', [ + VertexShader(""" + varying vec3 normal; + void main() { + // compute here for use in fragment shader + normal = normalize(gl_NormalMatrix * gl_Normal); + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + varying vec3 normal; + void main() { + vec4 color = gl_Color; + color.x = (normal.x + 1.0) * 0.5; + color.y = (normal.y + 1.0) * 0.5; + color.z = (normal.z + 1.0) * 0.5; + gl_FragColor = color; + } + """) + ]), + + ## colors fragments based on absolute face normals. + ShaderProgram('normalColor', [ + VertexShader(""" + varying vec3 normal; + void main() { + // compute here for use in fragment shader + normal = normalize(gl_Normal); + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + varying vec3 normal; + void main() { + vec4 color = gl_Color; + color.x = (normal.x + 1.0) * 0.5; + color.y = (normal.y + 1.0) * 0.5; + color.z = (normal.z + 1.0) * 0.5; + gl_FragColor = color; + } + """) + ]), + + ## very simple simulation of lighting. + ## The light source position is always relative to the camera. + ShaderProgram('shaded', [ + VertexShader(""" + varying vec3 normal; + void main() { + // compute here for use in fragment shader + normal = normalize(gl_NormalMatrix * gl_Normal); + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + varying vec3 normal; + void main() { + float p = dot(normal, normalize(vec3(1.0, -1.0, -1.0))); + p = p < 0. ? 0. : p * 0.8; + vec4 color = gl_Color; + color.x = color.x * (0.2 + p); + color.y = color.y * (0.2 + p); + color.z = color.z * (0.2 + p); + gl_FragColor = color; + } + """) + ]), + + ## colors get brighter near edges of object + ShaderProgram('edgeHilight', [ + VertexShader(""" + varying vec3 normal; + void main() { + // compute here for use in fragment shader + normal = normalize(gl_NormalMatrix * gl_Normal); + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + varying vec3 normal; + void main() { + vec4 color = gl_Color; + float s = pow(normal.x*normal.x + normal.y*normal.y, 2.0); + color.x = color.x + s * (1.0-color.x); + color.y = color.y + s * (1.0-color.y); + color.z = color.z + s * (1.0-color.z); + gl_FragColor = color; + } + """) + ]), + + ## colors fragments by z-value. + ## This is useful for coloring surface plots by height. + ## This shader uses a uniform called "colorMap" to determine how to map the colors: + ## red = pow(z * colorMap[0] + colorMap[1], colorMap[2]) + ## green = pow(z * colorMap[3] + colorMap[4], colorMap[5]) + ## blue = pow(z * colorMap[6] + colorMap[7], colorMap[8]) + ## (set the values like this: shader['uniformMap'] = array([...]) + ShaderProgram('heightColor', [ + VertexShader(""" + varying vec4 pos; + void main() { + gl_FrontColor = gl_Color; + gl_BackColor = gl_Color; + pos = gl_Vertex; + gl_Position = ftransform(); + } + """), + FragmentShader(""" + uniform float colorMap[9]; + varying vec4 pos; + //out vec4 gl_FragColor; // only needed for later glsl versions + //in vec4 gl_Color; + void main() { + vec4 color = gl_Color; + color.x = colorMap[0] * (pos.z + colorMap[1]); + if (colorMap[2] != 1.0) + color.x = pow(color.x, colorMap[2]); + color.x = color.x < 0. ? 0. : (color.x > 1. ? 1. : color.x); + + color.y = colorMap[3] * (pos.z + colorMap[4]); + if (colorMap[5] != 1.0) + color.y = pow(color.y, colorMap[5]); + color.y = color.y < 0. ? 0. : (color.y > 1. ? 1. : color.y); + + color.z = colorMap[6] * (pos.z + colorMap[7]); + if (colorMap[8] != 1.0) + color.z = pow(color.z, colorMap[8]); + color.z = color.z < 0. ? 0. : (color.z > 1. ? 1. : color.z); + + color.w = 1.0; + gl_FragColor = color; + } + """), + ], uniforms={'colorMap': [1, 1, 1, 1, 0.5, 1, 1, 0, 1]}), + ShaderProgram('pointSprite', [ ## allows specifying point size using normal.x + ## See: + ## + ## http://stackoverflow.com/questions/9609423/applying-part-of-a-texture-sprite-sheet-texture-map-to-a-point-sprite-in-ios + ## http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 + ## + ## + VertexShader(""" + void main() { + gl_FrontColor=gl_Color; + gl_PointSize = gl_Normal.x; + gl_Position = ftransform(); + } + """), + #FragmentShader(""" + ##version 120 + #uniform sampler2D texture; + #void main ( ) + #{ + #gl_FragColor = texture2D(texture, gl_PointCoord) * gl_Color; + #} + #""") + ]), + ] + + +CompiledShaderPrograms = {} + +def getShaderProgram(name): + return ShaderProgram.names[name] + +class Shader(object): + def __init__(self, shaderType, code): + self.shaderType = shaderType + self.code = code + self.compiled = None + + def shader(self): + if self.compiled is None: + try: + self.compiled = shaders.compileShader(self.code, self.shaderType) + except NullFunctionError: + raise Exception("This OpenGL implementation does not support shader programs; many OpenGL features in pyqtgraph will not work.") + except RuntimeError as exc: + ## Format compile errors a bit more nicely + if len(exc.args) == 3: + err, code, typ = exc.args + if not err.startswith('Shader compile failure'): + raise + code = code[0].decode('utf_8').split('\n') + err, c, msgs = err.partition(':') + err = err + '\n' + msgs = re.sub('b\'','',msgs) + msgs = re.sub('\'$','',msgs) + msgs = re.sub('\\\\n','\n',msgs) + msgs = msgs.split('\n') + errNums = [()] * len(code) + for i, msg in enumerate(msgs): + msg = msg.strip() + if msg == '': + continue + m = re.match(r'(\d+\:)?\d+\((\d+)\)', msg) + if m is not None: + line = int(m.groups()[1]) + errNums[line-1] = errNums[line-1] + (str(i+1),) + #code[line-1] = '%d\t%s' % (i+1, code[line-1]) + err = err + "%d %s\n" % (i+1, msg) + errNums = [','.join(n) for n in errNums] + maxlen = max(map(len, errNums)) + code = [errNums[i] + " "*(maxlen-len(errNums[i])) + line for i, line in enumerate(code)] + err = err + '\n'.join(code) + raise Exception(err) + else: + raise + return self.compiled + +class VertexShader(Shader): + def __init__(self, code): + Shader.__init__(self, GL_VERTEX_SHADER, code) + +class FragmentShader(Shader): + def __init__(self, code): + Shader.__init__(self, GL_FRAGMENT_SHADER, code) + + + + +class ShaderProgram(object): + names = {} + + def __init__(self, name, shaders, uniforms=None): + self.name = name + ShaderProgram.names[name] = self + self.shaders = shaders + self.prog = None + self.blockData = {} + self.uniformData = {} + + ## parse extra options from the shader definition + if uniforms is not None: + for k,v in uniforms.items(): + self[k] = v + + def setBlockData(self, blockName, data): + if data is None: + del self.blockData[blockName] + else: + self.blockData[blockName] = data + + def setUniformData(self, uniformName, data): + if data is None: + del self.uniformData[uniformName] + else: + self.uniformData[uniformName] = data + + def __setitem__(self, item, val): + self.setUniformData(item, val) + + def __delitem__(self, item): + self.setUniformData(item, None) + + def program(self): + if self.prog is None: + try: + compiled = [s.shader() for s in self.shaders] ## compile all shaders + self.prog = shaders.compileProgram(*compiled) ## compile program + except: + self.prog = -1 + raise + return self.prog + + def __enter__(self): + if len(self.shaders) > 0 and self.program() != -1: + glUseProgram(self.program()) + + try: + ## load uniform values into program + for uniformName, data in self.uniformData.items(): + loc = self.uniform(uniformName) + if loc == -1: + raise Exception('Could not find uniform variable "%s"' % uniformName) + glUniform1fv(loc, len(data), data) + + ### bind buffer data to program blocks + #if len(self.blockData) > 0: + #bindPoint = 1 + #for blockName, data in self.blockData.items(): + ### Program should have a uniform block declared: + ### + ### layout (std140) uniform blockName { + ### vec4 diffuse; + ### }; + + ### pick any-old binding point. (there are a limited number of these per-program + #bindPoint = 1 + + ### get the block index for a uniform variable in the shader + #blockIndex = glGetUniformBlockIndex(self.program(), blockName) + + ### give the shader block a binding point + #glUniformBlockBinding(self.program(), blockIndex, bindPoint) + + ### create a buffer + #buf = glGenBuffers(1) + #glBindBuffer(GL_UNIFORM_BUFFER, buf) + #glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW) + ### also possible to use glBufferSubData to fill parts of the buffer + + ### bind buffer to the same binding point + #glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf) + except: + glUseProgram(0) + raise + + + + def __exit__(self, *args): + if len(self.shaders) > 0: + glUseProgram(0) + + def uniform(self, name): + """Return the location integer for a uniform variable in this program""" + return glGetUniformLocation(self.program(), name.encode('utf_8')) + + #def uniformBlockInfo(self, blockName): + #blockIndex = glGetUniformBlockIndex(self.program(), blockName) + #count = glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS) + #indices = [] + #for i in range(count): + #indices.append(glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES)) + +class HeightColorShader(ShaderProgram): + def __enter__(self): + ## Program should have a uniform block declared: + ## + ## layout (std140) uniform blockName { + ## vec4 diffuse; + ## vec4 ambient; + ## }; + + ## pick any-old binding point. (there are a limited number of these per-program + bindPoint = 1 + + ## get the block index for a uniform variable in the shader + blockIndex = glGetUniformBlockIndex(self.program(), "blockName") + + ## give the shader block a binding point + glUniformBlockBinding(self.program(), blockIndex, bindPoint) + + ## create a buffer + buf = glGenBuffers(1) + glBindBuffer(GL_UNIFORM_BUFFER, buf) + glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW) + ## also possible to use glBufferSubData to fill parts of the buffer + + ## bind buffer to the same binding point + glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf) + +initShaders() diff --git a/papi/pyqtgraph/ordereddict.py b/papi/pyqtgraph/ordereddict.py new file mode 100644 index 00000000..7242b506 --- /dev/null +++ b/papi/pyqtgraph/ordereddict.py @@ -0,0 +1,127 @@ +# Copyright (c) 2009 Raymond Hettinger +# +# 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. + +from UserDict import DictMixin + +class OrderedDict(dict, DictMixin): + + def __init__(self, *args, **kwds): + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__end + except AttributeError: + self.clear() + self.update(*args, **kwds) + + def clear(self): + self.__end = end = [] + end += [None, end, end] # sentinel node for doubly linked list + self.__map = {} # key --> [key, prev, next] + dict.clear(self) + + def __setitem__(self, key, value): + if key not in self: + end = self.__end + curr = end[1] + curr[2] = end[1] = self.__map[key] = [key, curr, end] + dict.__setitem__(self, key, value) + + def __delitem__(self, key): + dict.__delitem__(self, key) + key, prev, next = self.__map.pop(key) + prev[2] = next + next[1] = prev + + def __iter__(self): + end = self.__end + curr = end[2] + while curr is not end: + yield curr[0] + curr = curr[2] + + def __reversed__(self): + end = self.__end + curr = end[1] + while curr is not end: + yield curr[0] + curr = curr[1] + + def popitem(self, last=True): + if not self: + raise KeyError('dictionary is empty') + if last: + key = reversed(self).next() + else: + key = iter(self).next() + value = self.pop(key) + return key, value + + def __reduce__(self): + items = [[k, self[k]] for k in self] + tmp = self.__map, self.__end + del self.__map, self.__end + inst_dict = vars(self).copy() + self.__map, self.__end = tmp + if inst_dict: + return (self.__class__, (items,), inst_dict) + return self.__class__, (items,) + + def keys(self): + return list(self) + + setdefault = DictMixin.setdefault + update = DictMixin.update + pop = DictMixin.pop + values = DictMixin.values + items = DictMixin.items + iterkeys = DictMixin.iterkeys + itervalues = DictMixin.itervalues + iteritems = DictMixin.iteritems + + def __repr__(self): + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, self.items()) + + def copy(self): + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + def __eq__(self, other): + if isinstance(other, OrderedDict): + if len(self) != len(other): + return False + for p, q in zip(self.items(), other.items()): + if p != q: + return False + return True + return dict.__eq__(self, other) + + def __ne__(self, other): + return not self == other diff --git a/papi/pyqtgraph/parametertree/Parameter.py b/papi/pyqtgraph/parametertree/Parameter.py new file mode 100644 index 00000000..5f37ccdc --- /dev/null +++ b/papi/pyqtgraph/parametertree/Parameter.py @@ -0,0 +1,766 @@ +from ..Qt import QtGui, QtCore +import os, weakref, re +from ..pgcollections import OrderedDict +from ..python2_3 import asUnicode +from .ParameterItem import ParameterItem + +PARAM_TYPES = {} +PARAM_NAMES = {} + +def registerParameterType(name, cls, override=False): + global PARAM_TYPES + if name in PARAM_TYPES and not override: + raise Exception("Parameter type '%s' already exists (use override=True to replace)" % name) + PARAM_TYPES[name] = cls + PARAM_NAMES[cls] = name + +def __reload__(old): + PARAM_TYPES.update(old.get('PARAM_TYPES', {})) + PARAM_NAMES.update(old.get('PARAM_NAMES', {})) + +class Parameter(QtCore.QObject): + """ + A Parameter is the basic unit of data in a parameter tree. Each parameter has + a name, a type, a value, and several other properties that modify the behavior of the + Parameter. Parameters may have parent / child / sibling relationships to construct + organized hierarchies. Parameters generally do not have any inherent GUI or visual + interpretation; instead they manage ParameterItem instances which take care of + display and user interaction. + + Note: It is fairly uncommon to use the Parameter class directly; mostly you + will use subclasses which provide specialized type and data handling. The static + pethod Parameter.create(...) is an easy way to generate instances of these subclasses. + + For more Parameter types, see ParameterTree.parameterTypes module. + + =================================== ========================================================= + **Signals:** + sigStateChanged(self, change, info) Emitted when anything changes about this parameter at + all. + The second argument is a string indicating what changed + ('value', 'childAdded', etc..) + The third argument can be any extra information about + the change + sigTreeStateChanged(self, changes) Emitted when any child in the tree changes state + (but only if monitorChildren() is called) + the format of *changes* is [(param, change, info), ...] + sigValueChanged(self, value) Emitted when value is finished changing + sigValueChanging(self, value) Emitted immediately for all value changes, + including during editing. + sigChildAdded(self, child, index) Emitted when a child is added + sigChildRemoved(self, child) Emitted when a child is removed + sigRemoved(self) Emitted when this parameter is removed + sigParentChanged(self, parent) Emitted when this parameter's parent has changed + sigLimitsChanged(self, limits) Emitted when this parameter's limits have changed + sigDefaultChanged(self, default) Emitted when this parameter's default value has changed + sigNameChanged(self, name) Emitted when this parameter's name has changed + sigOptionsChanged(self, opts) Emitted when any of this parameter's options have changed + =================================== ========================================================= + """ + ## name, type, limits, etc. + ## can also carry UI hints (slider vs spinbox, etc.) + + sigValueChanged = QtCore.Signal(object, object) ## self, value emitted when value is finished being edited + sigValueChanging = QtCore.Signal(object, object) ## self, value emitted as value is being edited + + sigChildAdded = QtCore.Signal(object, object, object) ## self, child, index + sigChildRemoved = QtCore.Signal(object, object) ## self, child + sigRemoved = QtCore.Signal(object) ## self + sigParentChanged = QtCore.Signal(object, object) ## self, parent + sigLimitsChanged = QtCore.Signal(object, object) ## self, limits + sigDefaultChanged = QtCore.Signal(object, object) ## self, default + sigNameChanged = QtCore.Signal(object, object) ## self, name + sigOptionsChanged = QtCore.Signal(object, object) ## self, {opt:val, ...} + + ## Emitted when anything changes about this parameter at all. + ## The second argument is a string indicating what changed ('value', 'childAdded', etc..) + ## The third argument can be any extra information about the change + sigStateChanged = QtCore.Signal(object, object, object) ## self, change, info + + ## emitted when any child in the tree changes state + ## (but only if monitorChildren() is called) + sigTreeStateChanged = QtCore.Signal(object, object) # self, changes + # changes = [(param, change, info), ...] + + # bad planning. + #def __new__(cls, *args, **opts): + #try: + #cls = PARAM_TYPES[opts['type']] + #except KeyError: + #pass + #return QtCore.QObject.__new__(cls, *args, **opts) + + @staticmethod + def create(**opts): + """ + Static method that creates a new Parameter (or subclass) instance using + opts['type'] to select the appropriate class. + + All options are passed directly to the new Parameter's __init__ method. + Use registerParameterType() to add new class types. + """ + typ = opts.get('type', None) + if typ is None: + cls = Parameter + else: + cls = PARAM_TYPES[opts['type']] + return cls(**opts) + + def __init__(self, **opts): + """ + Initialize a Parameter object. Although it is rare to directly create a + Parameter instance, the options available to this method are also allowed + by most Parameter subclasses. + + ======================= ========================================================= + **Keyword Arguments:** + name The name to give this Parameter. This is the name that + will appear in the left-most column of a ParameterTree + for this Parameter. + value The value to initially assign to this Parameter. + default The default value for this Parameter (most Parameters + provide an option to 'reset to default'). + children A list of children for this Parameter. Children + may be given either as a Parameter instance or as a + dictionary to pass to Parameter.create(). In this way, + it is possible to specify complex hierarchies of + Parameters from a single nested data structure. + readonly If True, the user will not be allowed to edit this + Parameter. (default=False) + enabled If False, any widget(s) for this parameter will appear + disabled. (default=True) + visible If False, the Parameter will not appear when displayed + in a ParameterTree. (default=True) + renamable If True, the user may rename this Parameter. + (default=False) + removable If True, the user may remove this Parameter. + (default=False) + expanded If True, the Parameter will appear expanded when + displayed in a ParameterTree (its children will be + visible). (default=True) + title (str or None) If specified, then the parameter will be + displayed to the user using this string as its name. + However, the parameter will still be referred to + internally using the *name* specified above. Note that + this option is not compatible with renamable=True. + (default=None; added in version 0.9.9) + ======================= ========================================================= + """ + + + QtCore.QObject.__init__(self) + + self.opts = { + 'type': None, + 'readonly': False, + 'visible': True, + 'enabled': True, + 'renamable': False, + 'removable': False, + 'strictNaming': False, # forces name to be usable as a python variable + 'expanded': True, + 'title': None, + #'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits. + } + self.opts.update(opts) + + self.childs = [] + self.names = {} ## map name:child + self.items = weakref.WeakKeyDictionary() ## keeps track of tree items representing this parameter + self._parent = None + self.treeStateChanges = [] ## cache of tree state changes to be delivered on next emit + self.blockTreeChangeEmit = 0 + #self.monitoringChildren = False ## prevent calling monitorChildren more than once + + if 'value' not in self.opts: + self.opts['value'] = None + + if 'name' not in self.opts or not isinstance(self.opts['name'], basestring): + raise Exception("Parameter must have a string name specified in opts.") + self.setName(opts['name']) + + self.addChildren(self.opts.get('children', [])) + + if 'value' in self.opts and 'default' not in self.opts: + self.opts['default'] = self.opts['value'] + + ## Connect all state changed signals to the general sigStateChanged + self.sigValueChanged.connect(lambda param, data: self.emitStateChanged('value', data)) + self.sigChildAdded.connect(lambda param, *data: self.emitStateChanged('childAdded', data)) + self.sigChildRemoved.connect(lambda param, data: self.emitStateChanged('childRemoved', data)) + self.sigParentChanged.connect(lambda param, data: self.emitStateChanged('parent', data)) + self.sigLimitsChanged.connect(lambda param, data: self.emitStateChanged('limits', data)) + self.sigDefaultChanged.connect(lambda param, data: self.emitStateChanged('default', data)) + self.sigNameChanged.connect(lambda param, data: self.emitStateChanged('name', data)) + self.sigOptionsChanged.connect(lambda param, data: self.emitStateChanged('options', data)) + + #self.watchParam(self) ## emit treechange signals if our own state changes + + def name(self): + """Return the name of this Parameter.""" + return self.opts['name'] + + def setName(self, name): + """Attempt to change the name of this parameter; return the actual name. + (The parameter may reject the name change or automatically pick a different name)""" + if self.opts['strictNaming']: + if len(name) < 1 or re.search(r'\W', name) or re.match(r'\d', name[0]): + raise Exception("Parameter name '%s' is invalid. (Must contain only alphanumeric and underscore characters and may not start with a number)" % name) + parent = self.parent() + if parent is not None: + name = parent._renameChild(self, name) ## first ask parent if it's ok to rename + if self.opts['name'] != name: + self.opts['name'] = name + self.sigNameChanged.emit(self, name) + return name + + def type(self): + """Return the type string for this Parameter.""" + return self.opts['type'] + + def isType(self, typ): + """ + Return True if this parameter type matches the name *typ*. + This can occur either of two ways: + + - If self.type() == *typ* + - If this parameter's class is registered with the name *typ* + """ + if self.type() == typ: + return True + global PARAM_TYPES + cls = PARAM_TYPES.get(typ, None) + if cls is None: + raise Exception("Type name '%s' is not registered." % str(typ)) + return self.__class__ is cls + + def childPath(self, child): + """ + Return the path of parameter names from self to child. + If child is not a (grand)child of self, return None. + """ + path = [] + while child is not self: + path.insert(0, child.name()) + child = child.parent() + if child is None: + return None + return path + + def setValue(self, value, blockSignal=None): + """ + Set the value of this Parameter; return the actual value that was set. + (this may be different from the value that was requested) + """ + try: + if blockSignal is not None: + self.sigValueChanged.disconnect(blockSignal) + if self.opts['value'] == value: + return value + self.opts['value'] = value + self.sigValueChanged.emit(self, value) + finally: + if blockSignal is not None: + self.sigValueChanged.connect(blockSignal) + + return value + + def value(self): + """ + Return the value of this Parameter. + """ + return self.opts['value'] + + def getValues(self): + """Return a tree of all values that are children of this parameter""" + vals = OrderedDict() + for ch in self: + vals[ch.name()] = (ch.value(), ch.getValues()) + return vals + + def saveState(self, filter=None): + """ + Return a structure representing the entire state of the parameter tree. + The tree state may be restored from this structure using restoreState(). + + If *filter* is set to 'user', then only user-settable data will be included in the + returned state. + """ + if filter is None: + state = self.opts.copy() + if state['type'] is None: + global PARAM_NAMES + state['type'] = PARAM_NAMES.get(type(self), None) + elif filter == 'user': + state = {'value': self.value()} + else: + raise ValueError("Unrecognized filter argument: '%s'" % filter) + + ch = OrderedDict([(ch.name(), ch.saveState(filter=filter)) for ch in self]) + if len(ch) > 0: + state['children'] = ch + return state + + def restoreState(self, state, recursive=True, addChildren=True, removeChildren=True, blockSignals=True): + """ + Restore the state of this parameter and its children from a structure generated using saveState() + If recursive is True, then attempt to restore the state of child parameters as well. + If addChildren is True, then any children which are referenced in the state object will be + created if they do not already exist. + If removeChildren is True, then any children which are not referenced in the state object will + be removed. + If blockSignals is True, no signals will be emitted until the tree has been completely restored. + This prevents signal handlers from responding to a partially-rebuilt network. + """ + childState = state.get('children', []) + + ## list of children may be stored either as list or dict. + if isinstance(childState, dict): + cs = [] + for k,v in childState.items(): + cs.append(v.copy()) + cs[-1].setdefault('name', k) + childState = cs + + if blockSignals: + self.blockTreeChangeSignal() + + try: + self.setOpts(**state) + + if not recursive: + return + + ptr = 0 ## pointer to first child that has not been restored yet + foundChilds = set() + #print "==============", self.name() + + for ch in childState: + name = ch['name'] + #typ = ch.get('type', None) + #print('child: %s, %s' % (self.name()+'.'+name, typ)) + + ## First, see if there is already a child with this name + gotChild = False + for i, ch2 in enumerate(self.childs[ptr:]): + #print " ", ch2.name(), ch2.type() + if ch2.name() != name: # or not ch2.isType(typ): + continue + gotChild = True + #print " found it" + if i != 0: ## move parameter to next position + #self.removeChild(ch2) + self.insertChild(ptr, ch2) + #print " moved to position", ptr + ch2.restoreState(ch, recursive=recursive, addChildren=addChildren, removeChildren=removeChildren) + foundChilds.add(ch2) + + break + + if not gotChild: + if not addChildren: + #print " ignored child" + continue + #print " created new" + ch2 = Parameter.create(**ch) + self.insertChild(ptr, ch2) + foundChilds.add(ch2) + + ptr += 1 + + if removeChildren: + for ch in self.childs[:]: + if ch not in foundChilds: + #print " remove:", ch + self.removeChild(ch) + finally: + if blockSignals: + self.unblockTreeChangeSignal() + + + + def defaultValue(self): + """Return the default value for this parameter.""" + return self.opts['default'] + + def setDefault(self, val): + """Set the default value for this parameter.""" + if self.opts['default'] == val: + return + self.opts['default'] = val + self.sigDefaultChanged.emit(self, val) + + def setToDefault(self): + """Set this parameter's value to the default.""" + if self.hasDefault(): + self.setValue(self.defaultValue()) + + def hasDefault(self): + """Returns True if this parameter has a default value.""" + return 'default' in self.opts + + def valueIsDefault(self): + """Returns True if this parameter's value is equal to the default value.""" + return self.value() == self.defaultValue() + + def setLimits(self, limits): + """Set limits on the acceptable values for this parameter. + The format of limits depends on the type of the parameter and + some parameters do not make use of limits at all.""" + if 'limits' in self.opts and self.opts['limits'] == limits: + return + self.opts['limits'] = limits + self.sigLimitsChanged.emit(self, limits) + return limits + + def writable(self): + """ + Returns True if this parameter's value can be changed by the user. + Note that the value of the parameter can *always* be changed by + calling setValue(). + """ + return not self.readonly() + + def setWritable(self, writable=True): + """Set whether this Parameter should be editable by the user. (This is + exactly the opposite of setReadonly).""" + self.setOpts(readonly=not writable) + + def readonly(self): + """ + Return True if this parameter is read-only. (this is the opposite of writable()) + """ + return self.opts.get('readonly', False) + + def setReadonly(self, readonly=True): + """Set whether this Parameter's value may be edited by the user + (this is the opposite of setWritable()).""" + self.setOpts(readonly=readonly) + + def setOpts(self, **opts): + """ + Set any arbitrary options on this parameter. + The exact behavior of this function will depend on the parameter type, but + most parameters will accept a common set of options: value, name, limits, + default, readonly, removable, renamable, visible, enabled, and expanded. + + See :func:`Parameter.__init__ ` + for more information on default options. + """ + changed = OrderedDict() + for k in opts: + if k == 'value': + self.setValue(opts[k]) + elif k == 'name': + self.setName(opts[k]) + elif k == 'limits': + self.setLimits(opts[k]) + elif k == 'default': + self.setDefault(opts[k]) + elif k not in self.opts or self.opts[k] != opts[k]: + self.opts[k] = opts[k] + changed[k] = opts[k] + + if len(changed) > 0: + self.sigOptionsChanged.emit(self, changed) + + def emitStateChanged(self, changeDesc, data): + ## Emits stateChanged signal and + ## requests emission of new treeStateChanged signal + self.sigStateChanged.emit(self, changeDesc, data) + #self.treeStateChanged(self, changeDesc, data) + self.treeStateChanges.append((self, changeDesc, data)) + self.emitTreeChanges() + + def makeTreeItem(self, depth): + """ + Return a TreeWidgetItem suitable for displaying/controlling the content of + this parameter. This is called automatically when a ParameterTree attempts + to display this Parameter. + Most subclasses will want to override this function. + """ + if hasattr(self, 'itemClass'): + #print "Param:", self, "Make item from itemClass:", self.itemClass + return self.itemClass(self, depth) + else: + return ParameterItem(self, depth=depth) + + + def addChild(self, child, autoIncrementName=None): + """ + Add another parameter to the end of this parameter's child list. + + See insertChild() for a description of the *autoIncrementName* + argument. + """ + return self.insertChild(len(self.childs), child, autoIncrementName=autoIncrementName) + + def addChildren(self, children): + """ + Add a list or dict of children to this parameter. This method calls + addChild once for each value in *children*. + """ + ## If children was specified as dict, then assume keys are the names. + if isinstance(children, dict): + ch2 = [] + for name, opts in children.items(): + if isinstance(opts, dict) and 'name' not in opts: + opts = opts.copy() + opts['name'] = name + ch2.append(opts) + children = ch2 + + for chOpts in children: + #print self, "Add child:", type(chOpts), id(chOpts) + self.addChild(chOpts) + + + def insertChild(self, pos, child, autoIncrementName=None): + """ + Insert a new child at pos. + If pos is a Parameter, then insert at the position of that Parameter. + If child is a dict, then a parameter is constructed using + :func:`Parameter.create `. + + By default, the child's 'autoIncrementName' option determines whether + the name will be adjusted to avoid prior name collisions. This + behavior may be overridden by specifying the *autoIncrementName* + argument. This argument was added in version 0.9.9. + """ + if isinstance(child, dict): + child = Parameter.create(**child) + + name = child.name() + if name in self.names and child is not self.names[name]: + if autoIncrementName is True or (autoIncrementName is None and child.opts.get('autoIncrementName', False)): + name = self.incrementName(name) + child.setName(name) + else: + raise Exception("Already have child named %s" % str(name)) + if isinstance(pos, Parameter): + pos = self.childs.index(pos) + + with self.treeChangeBlocker(): + if child.parent() is not None: + child.remove() + + self.names[name] = child + self.childs.insert(pos, child) + + child.parentChanged(self) + self.sigChildAdded.emit(self, child, pos) + child.sigTreeStateChanged.connect(self.treeStateChanged) + return child + + def removeChild(self, child): + """Remove a child parameter.""" + name = child.name() + if name not in self.names or self.names[name] is not child: + raise Exception("Parameter %s is not my child; can't remove." % str(child)) + del self.names[name] + self.childs.pop(self.childs.index(child)) + child.parentChanged(None) + self.sigChildRemoved.emit(self, child) + try: + child.sigTreeStateChanged.disconnect(self.treeStateChanged) + except (TypeError, RuntimeError): ## already disconnected + pass + + def clearChildren(self): + """Remove all child parameters.""" + for ch in self.childs[:]: + self.removeChild(ch) + + def children(self): + """Return a list of this parameter's children. + Warning: this overrides QObject.children + """ + return self.childs[:] + + def hasChildren(self): + """Return True if this Parameter has children.""" + return len(self.childs) > 0 + + def parentChanged(self, parent): + """This method is called when the parameter's parent has changed. + It may be useful to extend this method in subclasses.""" + self._parent = parent + self.sigParentChanged.emit(self, parent) + + def parent(self): + """Return the parent of this parameter.""" + return self._parent + + def remove(self): + """Remove this parameter from its parent's child list""" + parent = self.parent() + if parent is None: + raise Exception("Cannot remove; no parent.") + parent.removeChild(self) + self.sigRemoved.emit(self) + + def incrementName(self, name): + ## return an unused name by adding a number to the name given + base, num = re.match('(.*)(\d*)', name).groups() + numLen = len(num) + if numLen == 0: + num = 2 + numLen = 1 + else: + num = int(num) + while True: + newName = base + ("%%0%dd"%numLen) % num + if newName not in self.names: + return newName + num += 1 + + def __iter__(self): + for ch in self.childs: + yield ch + + def __getitem__(self, names): + """Get the value of a child parameter. The name may also be a tuple giving + the path to a sub-parameter:: + + value = param[('child', 'grandchild')] + """ + if not isinstance(names, tuple): + names = (names,) + return self.param(*names).value() + + def __setitem__(self, names, value): + """Set the value of a child parameter. The name may also be a tuple giving + the path to a sub-parameter:: + + param[('child', 'grandchild')] = value + """ + if isinstance(names, basestring): + names = (names,) + return self.param(*names).setValue(value) + + def child(self, *names): + """Return a child parameter. + Accepts the name of the child or a tuple (path, to, child) + + Added in version 0.9.9. Ealier versions used the 'param' method, which is still + implemented for backward compatibility.""" + try: + param = self.names[names[0]] + except KeyError: + raise Exception("Parameter %s has no child named %s" % (self.name(), names[0])) + + if len(names) > 1: + return param.param(*names[1:]) + else: + return param + + def param(self, *names): + # for backward compatibility. + return self.child(*names) + + def __repr__(self): + return asUnicode("<%s '%s' at 0x%x>") % (self.__class__.__name__, self.name(), id(self)) + + def __getattr__(self, attr): + ## Leaving this undocumented because I might like to remove it in the future.. + #print type(self), attr + + if 'names' not in self.__dict__: + raise AttributeError(attr) + if attr in self.names: + import traceback + traceback.print_stack() + print("Warning: Use of Parameter.subParam is deprecated. Use Parameter.param(name) instead.") + return self.param(attr) + else: + raise AttributeError(attr) + + def _renameChild(self, child, name): + ## Only to be called from Parameter.rename + if name in self.names: + return child.name() + self.names[name] = child + del self.names[child.name()] + return name + + def registerItem(self, item): + self.items[item] = None + + def hide(self): + """Hide this parameter. It and its children will no longer be visible in any ParameterTree + widgets it is connected to.""" + self.show(False) + + def show(self, s=True): + """Show this parameter. """ + self.opts['visible'] = s + self.sigOptionsChanged.emit(self, {'visible': s}) + + + def treeChangeBlocker(self): + """ + Return an object that can be used to temporarily block and accumulate + sigTreeStateChanged signals. This is meant to be used when numerous changes are + about to be made to the tree and only one change signal should be + emitted at the end. + + Example:: + + with param.treeChangeBlocker(): + param.addChild(...) + param.removeChild(...) + param.setValue(...) + """ + return SignalBlocker(self.blockTreeChangeSignal, self.unblockTreeChangeSignal) + + def blockTreeChangeSignal(self): + """ + Used to temporarily block and accumulate tree change signals. + *You must remember to unblock*, so it is advisable to use treeChangeBlocker() instead. + """ + self.blockTreeChangeEmit += 1 + + def unblockTreeChangeSignal(self): + """Unblocks enission of sigTreeStateChanged and flushes the changes out through a single signal.""" + self.blockTreeChangeEmit -= 1 + self.emitTreeChanges() + + + def treeStateChanged(self, param, changes): + """ + Called when the state of any sub-parameter has changed. + + ============== ================================================================ + **Arguments:** + param The immediate child whose tree state has changed. + note that the change may have originated from a grandchild. + changes List of tuples describing all changes that have been made + in this event: (param, changeDescr, data) + ============== ================================================================ + + This function can be extended to react to tree state changes. + """ + self.treeStateChanges.extend(changes) + self.emitTreeChanges() + + def emitTreeChanges(self): + if self.blockTreeChangeEmit == 0: + changes = self.treeStateChanges + self.treeStateChanges = [] + if len(changes) > 0: + self.sigTreeStateChanged.emit(self, changes) + + +class SignalBlocker(object): + def __init__(self, enterFn, exitFn): + self.enterFn = enterFn + self.exitFn = exitFn + + def __enter__(self): + self.enterFn() + + def __exit__(self, exc_type, exc_value, tb): + self.exitFn() + + + diff --git a/papi/pyqtgraph/parametertree/ParameterItem.py b/papi/pyqtgraph/parametertree/ParameterItem.py new file mode 100644 index 00000000..c149c411 --- /dev/null +++ b/papi/pyqtgraph/parametertree/ParameterItem.py @@ -0,0 +1,171 @@ +from ..Qt import QtGui, QtCore +from ..python2_3 import asUnicode +import os, weakref, re + +class ParameterItem(QtGui.QTreeWidgetItem): + """ + Abstract ParameterTree item. + Used to represent the state of a Parameter from within a ParameterTree. + + - Sets first column of item to name + - generates context menu if item is renamable or removable + - handles child added / removed events + - provides virtual functions for handling changes from parameter + + For more ParameterItem types, see ParameterTree.parameterTypes module. + """ + + def __init__(self, param, depth=0): + title = param.opts.get('title', None) + if title is None: + title = param.name() + QtGui.QTreeWidgetItem.__init__(self, [title, '']) + + self.param = param + self.param.registerItem(self) ## let parameter know this item is connected to it (for debugging) + self.depth = depth + + param.sigValueChanged.connect(self.valueChanged) + param.sigChildAdded.connect(self.childAdded) + param.sigChildRemoved.connect(self.childRemoved) + param.sigNameChanged.connect(self.nameChanged) + param.sigLimitsChanged.connect(self.limitsChanged) + param.sigDefaultChanged.connect(self.defaultChanged) + param.sigOptionsChanged.connect(self.optsChanged) + param.sigParentChanged.connect(self.parentChanged) + + opts = param.opts + + ## Generate context menu for renaming/removing parameter + self.contextMenu = QtGui.QMenu() + self.contextMenu.addSeparator() + flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled + if opts.get('renamable', False): + if param.opts.get('title', None) is not None: + raise Exception("Cannot make parameter with both title != None and renamable == True.") + flags |= QtCore.Qt.ItemIsEditable + self.contextMenu.addAction('Rename').triggered.connect(self.editName) + if opts.get('removable', False): + self.contextMenu.addAction("Remove").triggered.connect(self.requestRemove) + + ## handle movable / dropEnabled options + if opts.get('movable', False): + flags |= QtCore.Qt.ItemIsDragEnabled + if opts.get('dropEnabled', False): + flags |= QtCore.Qt.ItemIsDropEnabled + self.setFlags(flags) + + ## flag used internally during name editing + self.ignoreNameColumnChange = False + + + def valueChanged(self, param, val): + ## called when the parameter's value has changed + pass + + def isFocusable(self): + """Return True if this item should be included in the tab-focus order""" + return False + + def setFocus(self): + """Give input focus to this item. + Can be reimplemented to display editor widgets, etc. + """ + pass + + def focusNext(self, forward=True): + """Give focus to the next (or previous) focusable item in the parameter tree""" + self.treeWidget().focusNext(self, forward=forward) + + + def treeWidgetChanged(self): + """Called when this item is added or removed from a tree. + Expansion, visibility, and column widgets must all be configured AFTER + the item is added to a tree, not during __init__. + """ + self.setHidden(not self.param.opts.get('visible', True)) + self.setExpanded(self.param.opts.get('expanded', True)) + + def childAdded(self, param, child, pos): + item = child.makeTreeItem(depth=self.depth+1) + self.insertChild(pos, item) + item.treeWidgetChanged() + + for i, ch in enumerate(child): + item.childAdded(child, ch, i) + + def childRemoved(self, param, child): + for i in range(self.childCount()): + item = self.child(i) + if item.param is child: + self.takeChild(i) + break + + def parentChanged(self, param, parent): + ## called when the parameter's parent has changed. + pass + + def contextMenuEvent(self, ev): + if not self.param.opts.get('removable', False) and not self.param.opts.get('renamable', False): + return + + self.contextMenu.popup(ev.globalPos()) + + def columnChangedEvent(self, col): + """Called when the text in a column has been edited (or otherwise changed). + By default, we only use changes to column 0 to rename the parameter. + """ + if col == 0 and (self.param.opts.get('title', None) is None): + if self.ignoreNameColumnChange: + return + try: + newName = self.param.setName(asUnicode(self.text(col))) + except Exception: + self.setText(0, self.param.name()) + raise + + try: + self.ignoreNameColumnChange = True + self.nameChanged(self, newName) ## If the parameter rejects the name change, we need to set it back. + finally: + self.ignoreNameColumnChange = False + + def nameChanged(self, param, name): + ## called when the parameter's name has changed. + if self.param.opts.get('title', None) is None: + self.setText(0, name) + + def limitsChanged(self, param, limits): + """Called when the parameter's limits have changed""" + pass + + def defaultChanged(self, param, default): + """Called when the parameter's default value has changed""" + pass + + def optsChanged(self, param, opts): + """Called when any options are changed that are not + name, value, default, or limits""" + #print opts + if 'visible' in opts: + self.setHidden(not opts['visible']) + + def editName(self): + self.treeWidget().editItem(self, 0) + + def selected(self, sel): + """Called when this item has been selected (sel=True) OR deselected (sel=False)""" + pass + + def requestRemove(self): + ## called when remove is selected from the context menu. + ## we need to delay removal until the action is complete + ## since destroying the menu in mid-action will cause a crash. + QtCore.QTimer.singleShot(0, self.param.remove) + + ## for python 3 support, we need to redefine hash and eq methods. + def __hash__(self): + return id(self) + + def __eq__(self, x): + return x is self diff --git a/papi/pyqtgraph/parametertree/ParameterSystem.py b/papi/pyqtgraph/parametertree/ParameterSystem.py new file mode 100644 index 00000000..33bb2de8 --- /dev/null +++ b/papi/pyqtgraph/parametertree/ParameterSystem.py @@ -0,0 +1,127 @@ +from .parameterTypes import GroupParameter +from .. import functions as fn +from .SystemSolver import SystemSolver + + +class ParameterSystem(GroupParameter): + """ + ParameterSystem is a subclass of GroupParameter that manages a tree of + sub-parameters with a set of interdependencies--changing any one parameter + may affect other parameters in the system. + + See parametertree/SystemSolver for more information. + + NOTE: This API is experimental and may change substantially across minor + version numbers. + """ + def __init__(self, *args, **kwds): + GroupParameter.__init__(self, *args, **kwds) + self._system = None + self._fixParams = [] # all auto-generated 'fixed' params + sys = kwds.pop('system', None) + if sys is not None: + self.setSystem(sys) + self._ignoreChange = [] # params whose changes should be ignored temporarily + self.sigTreeStateChanged.connect(self.updateSystem) + + def setSystem(self, sys): + self._system = sys + + # auto-generate defaults to match child parameters + defaults = {} + vals = {} + for param in self: + name = param.name() + constraints = '' + if hasattr(sys, '_' + name): + constraints += 'n' + + if not param.readonly(): + constraints += 'f' + if 'n' in constraints: + ch = param.addChild(dict(name='fixed', type='bool', value=False)) + self._fixParams.append(ch) + param.setReadonly(True) + param.setOpts(expanded=False) + else: + vals[name] = param.value() + ch = param.addChild(dict(name='fixed', type='bool', value=True, readonly=True)) + #self._fixParams.append(ch) + + defaults[name] = [None, param.type(), None, constraints] + + sys.defaultState.update(defaults) + sys.reset() + for name, value in vals.items(): + setattr(sys, name, value) + + self.updateAllParams() + + def updateSystem(self, param, changes): + changes = [ch for ch in changes if ch[0] not in self._ignoreChange] + + #resets = [ch[0] for ch in changes if ch[1] == 'setToDefault'] + sets = [ch[0] for ch in changes if ch[1] == 'value'] + #for param in resets: + #setattr(self._system, param.name(), None) + + for param in sets: + #if param in resets: + #continue + + #if param in self._fixParams: + #param.parent().setWritable(param.value()) + #else: + if param in self._fixParams: + parent = param.parent() + if param.value(): + setattr(self._system, parent.name(), parent.value()) + else: + setattr(self._system, parent.name(), None) + else: + setattr(self._system, param.name(), param.value()) + + self.updateAllParams() + + def updateAllParams(self): + try: + self.sigTreeStateChanged.disconnect(self.updateSystem) + for name, state in self._system._vars.items(): + param = self.child(name) + try: + v = getattr(self._system, name) + if self._system._vars[name][2] is None: + self.updateParamState(self.child(name), 'autoSet') + param.setValue(v) + else: + self.updateParamState(self.child(name), 'fixed') + except RuntimeError: + self.updateParamState(param, 'autoUnset') + finally: + self.sigTreeStateChanged.connect(self.updateSystem) + + def updateParamState(self, param, state): + if state == 'autoSet': + bg = fn.mkBrush((200, 255, 200, 255)) + bold = False + readonly = True + elif state == 'autoUnset': + bg = fn.mkBrush(None) + bold = False + readonly = False + elif state == 'fixed': + bg = fn.mkBrush('y') + bold = True + readonly = False + + param.setReadonly(readonly) + + #for item in param.items: + #item.setBackground(0, bg) + #f = item.font(0) + #f.setWeight(f.Bold if bold else f.Normal) + #item.setFont(0, f) + + + + diff --git a/papi/pyqtgraph/parametertree/ParameterTree.py b/papi/pyqtgraph/parametertree/ParameterTree.py new file mode 100644 index 00000000..ef7c1030 --- /dev/null +++ b/papi/pyqtgraph/parametertree/ParameterTree.py @@ -0,0 +1,154 @@ +from ..Qt import QtCore, QtGui +from ..widgets.TreeWidget import TreeWidget +import os, weakref, re +from .ParameterItem import ParameterItem +#import functions as fn + + + +class ParameterTree(TreeWidget): + """Widget used to display or control data from a hierarchy of Parameters""" + + def __init__(self, parent=None, showHeader=True): + """ + ============== ======================================================== + **Arguments:** + parent (QWidget) An optional parent widget + showHeader (bool) If True, then the QTreeView header is displayed. + ============== ======================================================== + """ + TreeWidget.__init__(self, parent) + self.setVerticalScrollMode(self.ScrollPerPixel) + self.setHorizontalScrollMode(self.ScrollPerPixel) + self.setAnimated(False) + self.setColumnCount(2) + self.setHeaderLabels(["Parameter", "Value"]) + self.setAlternatingRowColors(True) + self.paramSet = None + self.header().setResizeMode(QtGui.QHeaderView.ResizeToContents) + self.setHeaderHidden(not showHeader) + self.itemChanged.connect(self.itemChangedEvent) + self.lastSel = None + self.setRootIsDecorated(False) + + def setParameters(self, param, showTop=True): + """ + Set the top-level :class:`Parameter ` + to be displayed in this ParameterTree. + + If *showTop* is False, then the top-level parameter is hidden and only + its children will be visible. This is a convenience method equivalent + to:: + + tree.clear() + tree.addParameters(param, showTop) + """ + self.clear() + self.addParameters(param, showTop=showTop) + + def addParameters(self, param, root=None, depth=0, showTop=True): + """ + Adds one top-level :class:`Parameter ` + to the view. + + ============== ========================================================== + **Arguments:** + param The :class:`Parameter ` + to add. + root The item within the tree to which *param* should be added. + By default, *param* is added as a top-level item. + showTop If False, then *param* will be hidden, and only its + children will be visible in the tree. + ============== ========================================================== + """ + item = param.makeTreeItem(depth=depth) + if root is None: + root = self.invisibleRootItem() + ## Hide top-level item + if not showTop: + item.setText(0, '') + item.setSizeHint(0, QtCore.QSize(1,1)) + item.setSizeHint(1, QtCore.QSize(1,1)) + depth -= 1 + root.addChild(item) + item.treeWidgetChanged() + + for ch in param: + self.addParameters(ch, root=item, depth=depth+1) + + def clear(self): + """ + Remove all parameters from the tree. + """ + self.invisibleRootItem().takeChildren() + + def focusNext(self, item, forward=True): + """Give input focus to the next (or previous) item after *item* + """ + while True: + parent = item.parent() + if parent is None: + return + nextItem = self.nextFocusableChild(parent, item, forward=forward) + if nextItem is not None: + nextItem.setFocus() + self.setCurrentItem(nextItem) + return + item = parent + + def focusPrevious(self, item): + self.focusNext(item, forward=False) + + def nextFocusableChild(self, root, startItem=None, forward=True): + if startItem is None: + if forward: + index = 0 + else: + index = root.childCount()-1 + else: + if forward: + index = root.indexOfChild(startItem) + 1 + else: + index = root.indexOfChild(startItem) - 1 + + if forward: + inds = list(range(index, root.childCount())) + else: + inds = list(range(index, -1, -1)) + + for i in inds: + item = root.child(i) + if hasattr(item, 'isFocusable') and item.isFocusable(): + return item + else: + item = self.nextFocusableChild(item, forward=forward) + if item is not None: + return item + return None + + def contextMenuEvent(self, ev): + item = self.currentItem() + if hasattr(item, 'contextMenuEvent'): + item.contextMenuEvent(ev) + + def itemChangedEvent(self, item, col): + if hasattr(item, 'columnChangedEvent'): + item.columnChangedEvent(col) + + def selectionChanged(self, *args): + sel = self.selectedItems() + if len(sel) != 1: + sel = None + if self.lastSel is not None and isinstance(self.lastSel, ParameterItem): + self.lastSel.selected(False) + if sel is None: + self.lastSel = None + return + self.lastSel = sel[0] + if hasattr(sel[0], 'selected'): + sel[0].selected(True) + return TreeWidget.selectionChanged(self, *args) + + def wheelEvent(self, ev): + self.clearSelection() + return TreeWidget.wheelEvent(self, ev) diff --git a/papi/pyqtgraph/parametertree/SystemSolver.py b/papi/pyqtgraph/parametertree/SystemSolver.py new file mode 100644 index 00000000..0a889dfa --- /dev/null +++ b/papi/pyqtgraph/parametertree/SystemSolver.py @@ -0,0 +1,381 @@ +from collections import OrderedDict +import numpy as np + +class SystemSolver(object): + """ + This abstract class is used to formalize and manage user interaction with a + complex system of equations (related to "constraint satisfaction problems"). + It is often the case that devices must be controlled + through a large number of free variables, and interactions between these + variables make the system difficult to manage and conceptualize as a user + interface. This class does _not_ attempt to numerically solve the system + of equations. Rather, it provides a framework for subdividing the system + into manageable pieces and specifying closed-form solutions to these small + pieces. + + For an example, see the simple Camera class below. + + Theory of operation: Conceptualize the system as 1) a set of variables + whose values may be either user-specified or automatically generated, and + 2) a set of functions that define *how* each variable should be generated. + When a variable is accessed (as an instance attribute), the solver first + checks to see if it already has a value (either user-supplied, or cached + from a previous calculation). If it does not, then the solver calls a + method on itself (the method must be named `_variableName`) that will + either return the calculated value (which usually involves acccessing + other variables in the system), or raise RuntimeError if it is unable to + calculate the value (usually because the user has not provided sufficient + input to fully constrain the system). + + Each method that calculates a variable value may include multiple + try/except blocks, so that if one method generates a RuntimeError, it may + fall back on others. + In this way, the system may be solved by recursively searching the tree of + possible relationships between variables. This allows the user flexibility + in deciding which variables are the most important to specify, while + avoiding the apparent combinatorial explosion of calculation pathways + that must be considered by the developer. + + Solved values are cached for efficiency, and automatically cleared when + a state change invalidates the cache. The rules for this are simple: any + time a value is set, it invalidates the cache *unless* the previous value + was None (which indicates that no other variable has yet requested that + value). More complex cache management may be defined in subclasses. + + + Subclasses must define: + + 1) The *defaultState* class attribute: This is a dict containing a + description of the variables in the system--their default values, + data types, and the ways they can be constrained. The format is:: + + { name: [value, type, constraint, allowed_constraints], ...} + + * *value* is the default value. May be None if it has not been specified + yet. + * *type* may be float, int, bool, np.ndarray, ... + * *constraint* may be None, single value, or (min, max) + * None indicates that the value is not constrained--it may be + automatically generated if the value is requested. + * *allowed_constraints* is a string composed of (n)one, (f)ixed, and (r)ange. + + Note: do not put mutable objects inside defaultState! + + 2) For each variable that may be automatically determined, a method must + be defined with the name `_variableName`. This method may either return + the + """ + + defaultState = OrderedDict() + + def __init__(self): + self.__dict__['_vars'] = OrderedDict() + self.__dict__['_currentGets'] = set() + self.reset() + + def reset(self): + """ + Reset all variables in the solver to their default state. + """ + self._currentGets.clear() + for k in self.defaultState: + self._vars[k] = self.defaultState[k][:] + + def __getattr__(self, name): + if name in self._vars: + return self.get(name) + raise AttributeError(name) + + def __setattr__(self, name, value): + """ + Set the value of a state variable. + If None is given for the value, then the constraint will also be set to None. + If a tuple is given for a scalar variable, then the tuple is used as a range constraint instead of a value. + Otherwise, the constraint is set to 'fixed'. + + """ + # First check this is a valid attribute + if name in self._vars: + if value is None: + self.set(name, value, None) + elif isinstance(value, tuple) and self._vars[name][1] is not np.ndarray: + self.set(name, None, value) + else: + self.set(name, value, 'fixed') + else: + # also allow setting any other pre-existing attribute + if hasattr(self, name): + object.__setattr__(self, name, value) + else: + raise AttributeError(name) + + def get(self, name): + """ + Return the value for parameter *name*. + + If the value has not been specified, then attempt to compute it from + other interacting parameters. + + If no value can be determined, then raise RuntimeError. + """ + if name in self._currentGets: + raise RuntimeError("Cyclic dependency while calculating '%s'." % name) + self._currentGets.add(name) + try: + v = self._vars[name][0] + if v is None: + cfunc = getattr(self, '_' + name, None) + if cfunc is None: + v = None + else: + v = cfunc() + if v is None: + raise RuntimeError("Parameter '%s' is not specified." % name) + v = self.set(name, v) + finally: + self._currentGets.remove(name) + + return v + + def set(self, name, value=None, constraint=True): + """ + Set a variable *name* to *value*. The actual set value is returned (in + some cases, the value may be cast into another type). + + If *value* is None, then the value is left to be determined in the + future. At any time, the value may be re-assigned arbitrarily unless + a constraint is given. + + If *constraint* is True (the default), then supplying a value that + violates a previously specified constraint will raise an exception. + + If *constraint* is 'fixed', then the value is set (if provided) and + the variable will not be updated automatically in the future. + + If *constraint* is a tuple, then the value is constrained to be within the + given (min, max). Either constraint may be None to disable + it. In some cases, a constraint cannot be satisfied automatically, + and the user will be forced to resolve the constraint manually. + + If *constraint* is None, then any constraints are removed for the variable. + """ + var = self._vars[name] + if constraint is None: + if 'n' not in var[3]: + raise TypeError("Empty constraints not allowed for '%s'" % name) + var[2] = constraint + elif constraint == 'fixed': + if 'f' not in var[3]: + raise TypeError("Fixed constraints not allowed for '%s'" % name) + var[2] = constraint + elif isinstance(constraint, tuple): + if 'r' not in var[3]: + raise TypeError("Range constraints not allowed for '%s'" % name) + assert len(constraint) == 2 + var[2] = constraint + elif constraint is not True: + raise TypeError("constraint must be None, True, 'fixed', or tuple. (got %s)" % constraint) + + # type checking / massaging + if var[1] is np.ndarray: + value = np.array(value, dtype=float) + elif var[1] in (int, float, tuple) and value is not None: + value = var[1](value) + + # constraint checks + if constraint is True and not self.check_constraint(name, value): + raise ValueError("Setting %s = %s violates constraint %s" % (name, value, var[2])) + + # invalidate other dependent values + if var[0] is not None: + # todo: we can make this more clever..(and might need to) + # we just know that a value of None cannot have dependencies + # (because if anyone else had asked for this value, it wouldn't be + # None anymore) + self.resetUnfixed() + + var[0] = value + return value + + def check_constraint(self, name, value): + c = self._vars[name][2] + if c is None or value is None: + return True + if isinstance(c, tuple): + return ((c[0] is None or c[0] <= value) and + (c[1] is None or c[1] >= value)) + else: + return value == c + + def saveState(self): + """ + Return a serializable description of the solver's current state. + """ + state = OrderedDict() + for name, var in self._vars.items(): + state[name] = (var[0], var[2]) + return state + + def restoreState(self, state): + """ + Restore the state of all values and constraints in the solver. + """ + self.reset() + for name, var in state.items(): + self.set(name, var[0], var[1]) + + def resetUnfixed(self): + """ + For any variable that does not have a fixed value, reset + its value to None. + """ + for var in self._vars.values(): + if var[2] != 'fixed': + var[0] = None + + def solve(self): + for k in self._vars: + getattr(self, k) + + def __repr__(self): + state = OrderedDict() + for name, var in self._vars.items(): + if var[2] == 'fixed': + state[name] = var[0] + state = ', '.join(["%s=%s" % (n, v) for n,v in state.items()]) + return "<%s %s>" % (self.__class__.__name__, state) + + + + + +if __name__ == '__main__': + + class Camera(SystemSolver): + """ + Consider a simple SLR camera. The variables we will consider that + affect the camera's behavior while acquiring a photo are aperture, shutter speed, + ISO, and flash (of course there are many more, but let's keep the example simple). + + In rare cases, the user wants to manually specify each of these variables and + no more work needs to be done to take the photo. More often, the user wants to + specify more interesting constraints like depth of field, overall exposure, + or maximum allowed ISO value. + + If we add a simple light meter measurement into this system and an 'exposure' + variable that indicates the desired exposure (0 is "perfect", -1 is one stop + darker, etc), then the system of equations governing the camera behavior would + have the following variables: + + aperture, shutter, iso, flash, exposure, light meter + + The first four variables are the "outputs" of the system (they directly drive + the camera), the last is a constant (the camera itself cannot affect the + reading on the light meter), and 'exposure' specifies a desired relationship + between other variables in the system. + + So the question is: how can I formalize a system like this as a user interface? + Typical cameras have a fairly limited approach: provide the user with a list + of modes, each of which defines a particular set of constraints. For example: + + manual: user provides aperture, shutter, iso, and flash + aperture priority: user provides aperture and exposure, camera selects + iso, shutter, and flash automatically + shutter priority: user provides shutter and exposure, camera selects + iso, aperture, and flash + program: user specifies exposure, camera selects all other variables + automatically + action: camera selects all variables while attempting to maximize + shutter speed + portrait: camera selects all variables while attempting to minimize + aperture + + A more general approach might allow the user to provide more explicit + constraints on each variable (for example: I want a shutter speed of 1/30 or + slower, an ISO no greater than 400, an exposure between -1 and 1, and the + smallest aperture possible given all other constraints) and have the camera + solve the system of equations, with a warning if no solution is found. This + is exactly what we will implement in this example class. + """ + + defaultState = OrderedDict([ + # Field stop aperture + ('aperture', [None, float, None, 'nf']), + # Duration that shutter is held open. + ('shutter', [None, float, None, 'nf']), + # ISO (sensitivity) value. 100, 200, 400, 800, 1600.. + ('iso', [None, int, None, 'nf']), + + # Flash is a value indicating the brightness of the flash. A table + # is used to decide on "balanced" settings for each flash level: + # 0: no flash + # 1: s=1/60, a=2.0, iso=100 + # 2: s=1/60, a=4.0, iso=100 ..and so on.. + ('flash', [None, float, None, 'nf']), + + # exposure is a value indicating how many stops brighter (+1) or + # darker (-1) the photographer would like the photo to appear from + # the 'balanced' settings indicated by the light meter (see below). + ('exposure', [None, float, None, 'f']), + + # Let's define this as an external light meter (not affected by + # aperture) with logarithmic output. We arbitrarily choose the + # following settings as "well balanced" for each light meter value: + # -1: s=1/60, a=2.0, iso=100 + # 0: s=1/60, a=4.0, iso=100 + # 1: s=1/120, a=4.0, iso=100 ..and so on.. + # Note that the only allowed constraint mode is (f)ixed, since the + # camera never _computes_ the light meter value, it only reads it. + ('lightMeter', [None, float, None, 'f']), + + # Indicates the camera's final decision on how it thinks the photo will + # look, given the chosen settings. This value is _only_ determined + # automatically. + ('balance', [None, float, None, 'n']), + ]) + + def _aperture(self): + """ + Determine aperture automatically under a variety of conditions. + """ + iso = self.iso + exp = self.exposure + light = self.lightMeter + + try: + # shutter-priority mode + sh = self.shutter # this raises RuntimeError if shutter has not + # been specified + ap = 4.0 * (sh / (1./60.)) * (iso / 100.) * (2 ** exp) * (2 ** light) + ap = np.clip(ap, 2.0, 16.0) + except RuntimeError: + # program mode; we can select a suitable shutter + # value at the same time. + sh = (1./60.) + raise + + + + return ap + + def _balance(self): + iso = self.iso + light = self.lightMeter + sh = self.shutter + ap = self.aperture + fl = self.flash + + bal = (4.0 / ap) * (sh / (1./60.)) * (iso / 100.) * (2 ** light) + return np.log2(bal) + + camera = Camera() + + camera.iso = 100 + camera.exposure = 0 + camera.lightMeter = 2 + camera.shutter = 1./60. + camera.flash = 0 + + camera.solve() + print(camera.saveState()) + \ No newline at end of file diff --git a/papi/pyqtgraph/parametertree/__init__.py b/papi/pyqtgraph/parametertree/__init__.py new file mode 100644 index 00000000..722410d5 --- /dev/null +++ b/papi/pyqtgraph/parametertree/__init__.py @@ -0,0 +1,5 @@ +from .Parameter import Parameter, registerParameterType +from .ParameterTree import ParameterTree +from .ParameterItem import ParameterItem +from .ParameterSystem import ParameterSystem, SystemSolver +from . import parameterTypes as types \ No newline at end of file diff --git a/papi/pyqtgraph/parametertree/parameterTypes.py b/papi/pyqtgraph/parametertree/parameterTypes.py new file mode 100644 index 00000000..7b1c5ee6 --- /dev/null +++ b/papi/pyqtgraph/parametertree/parameterTypes.py @@ -0,0 +1,663 @@ +from ..Qt import QtCore, QtGui +from ..python2_3 import asUnicode +from .Parameter import Parameter, registerParameterType +from .ParameterItem import ParameterItem +from ..widgets.SpinBox import SpinBox +from ..widgets.ColorButton import ColorButton +#from ..widgets.GradientWidget import GradientWidget ## creates import loop +from .. import pixmaps as pixmaps +from .. import functions as fn +import os +from ..pgcollections import OrderedDict + +class WidgetParameterItem(ParameterItem): + """ + ParameterTree item with: + + * label in second column for displaying value + * simple widget for editing value (displayed instead of label when item is selected) + * button that resets value to default + + ========================== ============================================================= + **Registered Types:** + int Displays a :class:`SpinBox ` in integer + mode. + float Displays a :class:`SpinBox `. + bool Displays a QCheckBox + str Displays a QLineEdit + color Displays a :class:`ColorButton ` + colormap Displays a :class:`GradientWidget ` + ========================== ============================================================= + + This class can be subclassed by overriding makeWidget() to provide a custom widget. + """ + def __init__(self, param, depth): + ParameterItem.__init__(self, param, depth) + + self.hideWidget = True ## hide edit widget, replace with label when not selected + ## set this to False to keep the editor widget always visible + + + ## build widget into column 1 with a display label and default button. + w = self.makeWidget() + self.widget = w + self.eventProxy = EventProxy(w, self.widgetEventFilter) + + opts = self.param.opts + if 'tip' in opts: + w.setToolTip(opts['tip']) + + self.defaultBtn = QtGui.QPushButton() + self.defaultBtn.setFixedWidth(20) + self.defaultBtn.setFixedHeight(20) + modDir = os.path.dirname(__file__) + self.defaultBtn.setIcon(QtGui.QIcon(pixmaps.getPixmap('default'))) + self.defaultBtn.clicked.connect(self.defaultClicked) + + self.displayLabel = QtGui.QLabel() + + layout = QtGui.QHBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(2) + layout.addWidget(w) + layout.addWidget(self.displayLabel) + layout.addWidget(self.defaultBtn) + self.layoutWidget = QtGui.QWidget() + self.layoutWidget.setLayout(layout) + + if w.sigChanged is not None: + w.sigChanged.connect(self.widgetValueChanged) + + if hasattr(w, 'sigChanging'): + w.sigChanging.connect(self.widgetValueChanging) + + ## update value shown in widget. + if opts.get('value', None) is not None: + self.valueChanged(self, opts['value'], force=True) + else: + ## no starting value was given; use whatever the widget has + self.widgetValueChanged() + + self.updateDefaultBtn() + + def makeWidget(self): + """ + Return a single widget that should be placed in the second tree column. + The widget must be given three attributes: + + ========== ============================================================ + sigChanged a signal that is emitted when the widget's value is changed + value a function that returns the value + setValue a function that sets the value + ========== ============================================================ + + This is a good function to override in subclasses. + """ + opts = self.param.opts + t = opts['type'] + if t == 'int': + defs = { + 'value': 0, 'min': None, 'max': None, 'int': True, + 'step': 1.0, 'minStep': 1.0, 'dec': False, + 'siPrefix': False, 'suffix': '' + } + defs.update(opts) + if 'limits' in opts: + defs['bounds'] = opts['limits'] + w = SpinBox() + w.setOpts(**defs) + w.sigChanged = w.sigValueChanged + w.sigChanging = w.sigValueChanging + elif t == 'float': + defs = { + 'value': 0, 'min': None, 'max': None, + 'step': 1.0, 'dec': False, + 'siPrefix': False, 'suffix': '' + } + defs.update(opts) + if 'limits' in opts: + defs['bounds'] = opts['limits'] + w = SpinBox() + w.setOpts(**defs) + w.sigChanged = w.sigValueChanged + w.sigChanging = w.sigValueChanging + elif t == 'bool': + w = QtGui.QCheckBox() + w.sigChanged = w.toggled + w.value = w.isChecked + w.setValue = w.setChecked + w.setEnabled(not opts.get('readonly', False)) + self.hideWidget = False + elif t == 'str': + w = QtGui.QLineEdit() + w.sigChanged = w.editingFinished + w.value = lambda: asUnicode(w.text()) + w.setValue = lambda v: w.setText(asUnicode(v)) + w.sigChanging = w.textChanged + elif t == 'color': + w = ColorButton() + w.sigChanged = w.sigColorChanged + w.sigChanging = w.sigColorChanging + w.value = w.color + w.setValue = w.setColor + self.hideWidget = False + w.setFlat(True) + w.setEnabled(not opts.get('readonly', False)) + elif t == 'colormap': + from ..widgets.GradientWidget import GradientWidget ## need this here to avoid import loop + w = GradientWidget(orientation='bottom') + w.sigChanged = w.sigGradientChangeFinished + w.sigChanging = w.sigGradientChanged + w.value = w.colorMap + w.setValue = w.setColorMap + self.hideWidget = False + else: + raise Exception("Unknown type '%s'" % asUnicode(t)) + return w + + def widgetEventFilter(self, obj, ev): + ## filter widget's events + ## catch TAB to change focus + ## catch focusOut to hide editor + if ev.type() == ev.KeyPress: + if ev.key() == QtCore.Qt.Key_Tab: + self.focusNext(forward=True) + return True ## don't let anyone else see this event + elif ev.key() == QtCore.Qt.Key_Backtab: + self.focusNext(forward=False) + return True ## don't let anyone else see this event + + #elif ev.type() == ev.FocusOut: + #self.hideEditor() + return False + + def setFocus(self): + self.showEditor() + + def isFocusable(self): + return self.param.writable() + + def valueChanged(self, param, val, force=False): + ## called when the parameter's value has changed + ParameterItem.valueChanged(self, param, val) + self.widget.sigChanged.disconnect(self.widgetValueChanged) + try: + if force or val != self.widget.value(): + self.widget.setValue(val) + self.updateDisplayLabel(val) ## always make sure label is updated, even if values match! + finally: + self.widget.sigChanged.connect(self.widgetValueChanged) + self.updateDefaultBtn() + + def updateDefaultBtn(self): + ## enable/disable default btn + self.defaultBtn.setEnabled(not self.param.valueIsDefault() and self.param.writable()) + + # hide / show + self.defaultBtn.setVisible(not self.param.readonly()) + + def updateDisplayLabel(self, value=None): + """Update the display label to reflect the value of the parameter.""" + if value is None: + value = self.param.value() + opts = self.param.opts + if isinstance(self.widget, QtGui.QAbstractSpinBox): + text = asUnicode(self.widget.lineEdit().text()) + elif isinstance(self.widget, QtGui.QComboBox): + text = self.widget.currentText() + else: + text = asUnicode(value) + self.displayLabel.setText(text) + + def widgetValueChanged(self): + ## called when the widget's value has been changed by the user + val = self.widget.value() + newVal = self.param.setValue(val) + + def widgetValueChanging(self, *args): + """ + Called when the widget's value is changing, but not finalized. + For example: editing text before pressing enter or changing focus. + """ + # This is a bit sketchy: assume the last argument of each signal is + # the value.. + self.param.sigValueChanging.emit(self.param, args[-1]) + + def selected(self, sel): + """Called when this item has been selected (sel=True) OR deselected (sel=False)""" + ParameterItem.selected(self, sel) + + if self.widget is None: + return + if sel and self.param.writable(): + self.showEditor() + elif self.hideWidget: + self.hideEditor() + + def showEditor(self): + self.widget.show() + self.displayLabel.hide() + self.widget.setFocus(QtCore.Qt.OtherFocusReason) + if isinstance(self.widget, SpinBox): + self.widget.selectNumber() # select the numerical portion of the text for quick editing + + def hideEditor(self): + self.widget.hide() + self.displayLabel.show() + + def limitsChanged(self, param, limits): + """Called when the parameter's limits have changed""" + ParameterItem.limitsChanged(self, param, limits) + + t = self.param.opts['type'] + if t == 'int' or t == 'float': + self.widget.setOpts(bounds=limits) + else: + return ## don't know what to do with any other types.. + + def defaultChanged(self, param, value): + self.updateDefaultBtn() + + def treeWidgetChanged(self): + """Called when this item is added or removed from a tree.""" + ParameterItem.treeWidgetChanged(self) + + ## add all widgets for this item into the tree + if self.widget is not None: + tree = self.treeWidget() + if tree is None: + return + tree.setItemWidget(self, 1, self.layoutWidget) + self.displayLabel.hide() + self.selected(False) + + def defaultClicked(self): + self.param.setToDefault() + + def optsChanged(self, param, opts): + """Called when any options are changed that are not + name, value, default, or limits""" + #print "opts changed:", opts + ParameterItem.optsChanged(self, param, opts) + + if 'readonly' in opts: + self.updateDefaultBtn() + if isinstance(self.widget, (QtGui.QCheckBox,ColorButton)): + self.widget.setEnabled(not opts['readonly']) + + ## If widget is a SpinBox, pass options straight through + if isinstance(self.widget, SpinBox): + if 'units' in opts and 'suffix' not in opts: + opts['suffix'] = opts['units'] + self.widget.setOpts(**opts) + self.updateDisplayLabel() + + + + +class EventProxy(QtCore.QObject): + def __init__(self, qobj, callback): + QtCore.QObject.__init__(self) + self.callback = callback + qobj.installEventFilter(self) + + def eventFilter(self, obj, ev): + return self.callback(obj, ev) + + + + +class SimpleParameter(Parameter): + itemClass = WidgetParameterItem + + def __init__(self, *args, **kargs): + Parameter.__init__(self, *args, **kargs) + + ## override a few methods for color parameters + if self.opts['type'] == 'color': + self.value = self.colorValue + self.saveState = self.saveColorState + + def colorValue(self): + return fn.mkColor(Parameter.value(self)) + + def saveColorState(self, *args, **kwds): + state = Parameter.saveState(self, *args, **kwds) + state['value'] = fn.colorTuple(self.value()) + return state + + +registerParameterType('int', SimpleParameter, override=True) +registerParameterType('float', SimpleParameter, override=True) +registerParameterType('bool', SimpleParameter, override=True) +registerParameterType('str', SimpleParameter, override=True) +registerParameterType('color', SimpleParameter, override=True) +registerParameterType('colormap', SimpleParameter, override=True) + + + + +class GroupParameterItem(ParameterItem): + """ + Group parameters are used mainly as a generic parent item that holds (and groups!) a set + of child parameters. It also provides a simple mechanism for displaying a button or combo + that can be used to add new parameters to the group. + """ + def __init__(self, param, depth): + ParameterItem.__init__(self, param, depth) + self.updateDepth(depth) + + self.addItem = None + if 'addText' in param.opts: + addText = param.opts['addText'] + if 'addList' in param.opts: + self.addWidget = QtGui.QComboBox() + self.addWidget.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents) + self.updateAddList() + self.addWidget.currentIndexChanged.connect(self.addChanged) + else: + self.addWidget = QtGui.QPushButton(addText) + self.addWidget.clicked.connect(self.addClicked) + w = QtGui.QWidget() + l = QtGui.QHBoxLayout() + l.setContentsMargins(0,0,0,0) + w.setLayout(l) + l.addWidget(self.addWidget) + l.addStretch() + #l.addItem(QtGui.QSpacerItem(200, 10, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)) + self.addWidgetBox = w + self.addItem = QtGui.QTreeWidgetItem([]) + self.addItem.setFlags(QtCore.Qt.ItemIsEnabled) + ParameterItem.addChild(self, self.addItem) + + def updateDepth(self, depth): + ## Change item's appearance based on its depth in the tree + ## This allows highest-level groups to be displayed more prominently. + if depth == 0: + for c in [0,1]: + self.setBackground(c, QtGui.QBrush(QtGui.QColor(100,100,100))) + self.setForeground(c, QtGui.QBrush(QtGui.QColor(220,220,255))) + font = self.font(c) + font.setBold(True) + font.setPointSize(font.pointSize()+1) + self.setFont(c, font) + self.setSizeHint(0, QtCore.QSize(0, 25)) + else: + for c in [0,1]: + self.setBackground(c, QtGui.QBrush(QtGui.QColor(220,220,220))) + font = self.font(c) + font.setBold(True) + #font.setPointSize(font.pointSize()+1) + self.setFont(c, font) + self.setSizeHint(0, QtCore.QSize(0, 20)) + + def addClicked(self): + """Called when "add new" button is clicked + The parameter MUST have an 'addNew' method defined. + """ + self.param.addNew() + + def addChanged(self): + """Called when "add new" combo is changed + The parameter MUST have an 'addNew' method defined. + """ + if self.addWidget.currentIndex() == 0: + return + typ = asUnicode(self.addWidget.currentText()) + self.param.addNew(typ) + self.addWidget.setCurrentIndex(0) + + def treeWidgetChanged(self): + ParameterItem.treeWidgetChanged(self) + self.treeWidget().setFirstItemColumnSpanned(self, True) + if self.addItem is not None: + self.treeWidget().setItemWidget(self.addItem, 0, self.addWidgetBox) + self.treeWidget().setFirstItemColumnSpanned(self.addItem, True) + + def addChild(self, child): ## make sure added childs are actually inserted before add btn + if self.addItem is not None: + ParameterItem.insertChild(self, self.childCount()-1, child) + else: + ParameterItem.addChild(self, child) + + def optsChanged(self, param, changed): + if 'addList' in changed: + self.updateAddList() + + def updateAddList(self): + self.addWidget.blockSignals(True) + try: + self.addWidget.clear() + self.addWidget.addItem(self.param.opts['addText']) + for t in self.param.opts['addList']: + self.addWidget.addItem(t) + finally: + self.addWidget.blockSignals(False) + +class GroupParameter(Parameter): + """ + Group parameters are used mainly as a generic parent item that holds (and groups!) a set + of child parameters. + + It also provides a simple mechanism for displaying a button or combo + that can be used to add new parameters to the group. To enable this, the group + must be initialized with the 'addText' option (the text will be displayed on + a button which, when clicked, will cause addNew() to be called). If the 'addList' + option is specified as well, then a dropdown-list of addable items will be displayed + instead of a button. + """ + itemClass = GroupParameterItem + + def addNew(self, typ=None): + """ + This method is called when the user has requested to add a new item to the group. + """ + raise Exception("Must override this function in subclass.") + + def setAddList(self, vals): + """Change the list of options available for the user to add to the group.""" + self.setOpts(addList=vals) + + + +registerParameterType('group', GroupParameter, override=True) + + + + + +class ListParameterItem(WidgetParameterItem): + """ + WidgetParameterItem subclass providing comboBox that lets the user select from a list of options. + + """ + def __init__(self, param, depth): + self.targetValue = None + WidgetParameterItem.__init__(self, param, depth) + + + def makeWidget(self): + opts = self.param.opts + t = opts['type'] + w = QtGui.QComboBox() + w.setMaximumHeight(20) ## set to match height of spin box and line edit + w.sigChanged = w.currentIndexChanged + w.value = self.value + w.setValue = self.setValue + self.widget = w ## needs to be set before limits are changed + self.limitsChanged(self.param, self.param.opts['limits']) + if len(self.forward) > 0: + self.setValue(self.param.value()) + return w + + def value(self): + key = asUnicode(self.widget.currentText()) + + return self.forward.get(key, None) + + def setValue(self, val): + self.targetValue = val + if val not in self.reverse[0]: + self.widget.setCurrentIndex(0) + else: + key = self.reverse[1][self.reverse[0].index(val)] + ind = self.widget.findText(key) + self.widget.setCurrentIndex(ind) + + def limitsChanged(self, param, limits): + # set up forward / reverse mappings for name:value + + if len(limits) == 0: + limits = [''] ## Can never have an empty list--there is always at least a singhe blank item. + + self.forward, self.reverse = ListParameter.mapping(limits) + try: + self.widget.blockSignals(True) + val = self.targetValue #asUnicode(self.widget.currentText()) + + self.widget.clear() + for k in self.forward: + self.widget.addItem(k) + if k == val: + self.widget.setCurrentIndex(self.widget.count()-1) + self.updateDisplayLabel() + finally: + self.widget.blockSignals(False) + + + +class ListParameter(Parameter): + itemClass = ListParameterItem + + def __init__(self, **opts): + self.forward = OrderedDict() ## {name: value, ...} + self.reverse = ([], []) ## ([value, ...], [name, ...]) + + ## Parameter uses 'limits' option to define the set of allowed values + if 'values' in opts: + opts['limits'] = opts['values'] + if opts.get('limits', None) is None: + opts['limits'] = [] + Parameter.__init__(self, **opts) + self.setLimits(opts['limits']) + + def setLimits(self, limits): + self.forward, self.reverse = self.mapping(limits) + + Parameter.setLimits(self, limits) + if len(self.reverse[0]) > 0 and self.value() not in self.reverse[0]: + self.setValue(self.reverse[0][0]) + + #def addItem(self, name, value=None): + #if name in self.forward: + #raise Exception("Name '%s' is already in use for this parameter" % name) + #limits = self.opts['limits'] + #if isinstance(limits, dict): + #limits = limits.copy() + #limits[name] = value + #self.setLimits(limits) + #else: + #if value is not None: + #raise Exception ## raise exception or convert to dict? + #limits = limits[:] + #limits.append(name) + ## what if limits == None? + + @staticmethod + def mapping(limits): + ## Return forward and reverse mapping objects given a limit specification + forward = OrderedDict() ## {name: value, ...} + reverse = ([], []) ## ([value, ...], [name, ...]) + if isinstance(limits, dict): + for k, v in limits.items(): + forward[k] = v + reverse[0].append(v) + reverse[1].append(k) + else: + for v in limits: + n = asUnicode(v) + forward[n] = v + reverse[0].append(v) + reverse[1].append(n) + return forward, reverse + +registerParameterType('list', ListParameter, override=True) + + + +class ActionParameterItem(ParameterItem): + def __init__(self, param, depth): + ParameterItem.__init__(self, param, depth) + self.layoutWidget = QtGui.QWidget() + self.layout = QtGui.QHBoxLayout() + self.layoutWidget.setLayout(self.layout) + self.button = QtGui.QPushButton(param.name()) + #self.layout.addSpacing(100) + self.layout.addWidget(self.button) + self.layout.addStretch() + self.button.clicked.connect(self.buttonClicked) + param.sigNameChanged.connect(self.paramRenamed) + self.setText(0, '') + + def treeWidgetChanged(self): + ParameterItem.treeWidgetChanged(self) + tree = self.treeWidget() + if tree is None: + return + + tree.setFirstItemColumnSpanned(self, True) + tree.setItemWidget(self, 0, self.layoutWidget) + + def paramRenamed(self, param, name): + self.button.setText(name) + + def buttonClicked(self): + self.param.activate() + +class ActionParameter(Parameter): + """Used for displaying a button within the tree.""" + itemClass = ActionParameterItem + sigActivated = QtCore.Signal(object) + + def activate(self): + self.sigActivated.emit(self) + self.emitStateChanged('activated', None) + +registerParameterType('action', ActionParameter, override=True) + + + +class TextParameterItem(WidgetParameterItem): + def __init__(self, param, depth): + WidgetParameterItem.__init__(self, param, depth) + self.hideWidget = False + self.subItem = QtGui.QTreeWidgetItem() + self.addChild(self.subItem) + + def treeWidgetChanged(self): + ## TODO: fix so that superclass method can be called + ## (WidgetParameter should just natively support this style) + #WidgetParameterItem.treeWidgetChanged(self) + self.treeWidget().setFirstItemColumnSpanned(self.subItem, True) + self.treeWidget().setItemWidget(self.subItem, 0, self.textBox) + + # for now, these are copied from ParameterItem.treeWidgetChanged + self.setHidden(not self.param.opts.get('visible', True)) + self.setExpanded(self.param.opts.get('expanded', True)) + + def makeWidget(self): + self.textBox = QtGui.QTextEdit() + self.textBox.setMaximumHeight(100) + self.textBox.setReadOnly(self.param.opts.get('readonly', False)) + self.textBox.value = lambda: str(self.textBox.toPlainText()) + self.textBox.setValue = self.textBox.setPlainText + self.textBox.sigChanged = self.textBox.textChanged + return self.textBox + +class TextParameter(Parameter): + """Editable string; displayed as large text box in the tree.""" + itemClass = TextParameterItem + + + +registerParameterType('text', TextParameter, override=True) diff --git a/papi/pyqtgraph/parametertree/tests/test_parametertypes.py b/papi/pyqtgraph/parametertree/tests/test_parametertypes.py new file mode 100644 index 00000000..c7cd2cb3 --- /dev/null +++ b/papi/pyqtgraph/parametertree/tests/test_parametertypes.py @@ -0,0 +1,18 @@ +import pyqtgraph.parametertree as pt +import pyqtgraph as pg +app = pg.mkQApp() + +def test_opts(): + paramSpec = [ + dict(name='bool', type='bool', readonly=True), + dict(name='color', type='color', readonly=True), + ] + + param = pt.Parameter.create(name='params', type='group', children=paramSpec) + tree = pt.ParameterTree() + tree.setParameters(param) + + assert param.param('bool').items.keys()[0].widget.isEnabled() is False + assert param.param('color').items.keys()[0].widget.isEnabled() is False + + diff --git a/papi/pyqtgraph/pgcollections.py b/papi/pyqtgraph/pgcollections.py new file mode 100644 index 00000000..76850622 --- /dev/null +++ b/papi/pyqtgraph/pgcollections.py @@ -0,0 +1,477 @@ +# -*- coding: utf-8 -*- +""" +advancedTypes.py - Basic data structures not included with python +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +Includes: + - OrderedDict - Dictionary which preserves the order of its elements + - BiDict, ReverseDict - Bi-directional dictionaries + - ThreadsafeDict, ThreadsafeList - Self-mutexed data structures +""" + +import threading, sys, copy, collections +#from debug import * + +try: + from collections import OrderedDict +except ImportError: + # fallback: try to use the ordereddict backport when using python 2.6 + from ordereddict import OrderedDict + + +class ReverseDict(dict): + """extends dict so that reverse lookups are possible by requesting the key as a list of length 1: + d = BiDict({'x': 1, 'y': 2}) + d['x'] + 1 + d[[2]] + 'y' + """ + def __init__(self, data=None): + if data is None: + data = {} + self.reverse = {} + for k in data: + self.reverse[data[k]] = k + dict.__init__(self, data) + + def __getitem__(self, item): + if type(item) is list: + return self.reverse[item[0]] + else: + return dict.__getitem__(self, item) + + def __setitem__(self, item, value): + self.reverse[value] = item + dict.__setitem__(self, item, value) + + def __deepcopy__(self, memo): + raise Exception("deepcopy not implemented") + + +class BiDict(dict): + """extends dict so that reverse lookups are possible by adding each reverse combination to the dict. + This only works if all values and keys are unique.""" + def __init__(self, data=None): + if data is None: + data = {} + dict.__init__(self) + for k in data: + self[data[k]] = k + + def __setitem__(self, item, value): + dict.__setitem__(self, item, value) + dict.__setitem__(self, value, item) + + def __deepcopy__(self, memo): + raise Exception("deepcopy not implemented") + +class ThreadsafeDict(dict): + """Extends dict so that getitem, setitem, and contains are all thread-safe. + Also adds lock/unlock functions for extended exclusive operations + Converts all sub-dicts and lists to threadsafe as well. + """ + + def __init__(self, *args, **kwargs): + self.mutex = threading.RLock() + dict.__init__(self, *args, **kwargs) + for k in self: + if type(self[k]) is dict: + self[k] = ThreadsafeDict(self[k]) + + def __getitem__(self, attr): + self.lock() + try: + val = dict.__getitem__(self, attr) + finally: + self.unlock() + return val + + def __setitem__(self, attr, val): + if type(val) is dict: + val = ThreadsafeDict(val) + self.lock() + try: + dict.__setitem__(self, attr, val) + finally: + self.unlock() + + def __contains__(self, attr): + self.lock() + try: + val = dict.__contains__(self, attr) + finally: + self.unlock() + return val + + def __len__(self): + self.lock() + try: + val = dict.__len__(self) + finally: + self.unlock() + return val + + def clear(self): + self.lock() + try: + dict.clear(self) + finally: + self.unlock() + + def lock(self): + self.mutex.acquire() + + def unlock(self): + self.mutex.release() + + def __deepcopy__(self, memo): + raise Exception("deepcopy not implemented") + +class ThreadsafeList(list): + """Extends list so that getitem, setitem, and contains are all thread-safe. + Also adds lock/unlock functions for extended exclusive operations + Converts all sub-lists and dicts to threadsafe as well. + """ + + def __init__(self, *args, **kwargs): + self.mutex = threading.RLock() + list.__init__(self, *args, **kwargs) + for k in self: + self[k] = mkThreadsafe(self[k]) + + def __getitem__(self, attr): + self.lock() + try: + val = list.__getitem__(self, attr) + finally: + self.unlock() + return val + + def __setitem__(self, attr, val): + val = makeThreadsafe(val) + self.lock() + try: + list.__setitem__(self, attr, val) + finally: + self.unlock() + + def __contains__(self, attr): + self.lock() + try: + val = list.__contains__(self, attr) + finally: + self.unlock() + return val + + def __len__(self): + self.lock() + try: + val = list.__len__(self) + finally: + self.unlock() + return val + + def lock(self): + self.mutex.acquire() + + def unlock(self): + self.mutex.release() + + def __deepcopy__(self, memo): + raise Exception("deepcopy not implemented") + + +def makeThreadsafe(obj): + if type(obj) is dict: + return ThreadsafeDict(obj) + elif type(obj) is list: + return ThreadsafeList(obj) + elif type(obj) in [str, int, float, bool, tuple]: + return obj + else: + raise Exception("Not sure how to make object of type %s thread-safe" % str(type(obj))) + + +class Locker(object): + def __init__(self, lock): + self.lock = lock + self.lock.acquire() + def __del__(self): + try: + self.lock.release() + except: + pass + +class CaselessDict(OrderedDict): + """Case-insensitive dict. Values can be set and retrieved using keys of any case. + Note that when iterating, the original case is returned for each key.""" + def __init__(self, *args): + OrderedDict.__init__(self, {}) ## requirement for the empty {} here seems to be a python bug? + self.keyMap = OrderedDict([(k.lower(), k) for k in OrderedDict.keys(self)]) + if len(args) == 0: + return + elif len(args) == 1 and isinstance(args[0], dict): + for k in args[0]: + self[k] = args[0][k] + else: + raise Exception("CaselessDict may only be instantiated with a single dict.") + + #def keys(self): + #return self.keyMap.values() + + def __setitem__(self, key, val): + kl = key.lower() + if kl in self.keyMap: + OrderedDict.__setitem__(self, self.keyMap[kl], val) + else: + OrderedDict.__setitem__(self, key, val) + self.keyMap[kl] = key + + def __getitem__(self, key): + kl = key.lower() + if kl not in self.keyMap: + raise KeyError(key) + return OrderedDict.__getitem__(self, self.keyMap[kl]) + + def __contains__(self, key): + return key.lower() in self.keyMap + + def update(self, d): + for k, v in d.iteritems(): + self[k] = v + + def copy(self): + return CaselessDict(OrderedDict.copy(self)) + + def __delitem__(self, key): + kl = key.lower() + if kl not in self.keyMap: + raise KeyError(key) + OrderedDict.__delitem__(self, self.keyMap[kl]) + del self.keyMap[kl] + + def __deepcopy__(self, memo): + raise Exception("deepcopy not implemented") + + def clear(self): + OrderedDict.clear(self) + self.keyMap.clear() + + + +class ProtectedDict(dict): + """ + A class allowing read-only 'view' of a dict. + The object can be treated like a normal dict, but will never modify the original dict it points to. + Any values accessed from the dict will also be read-only. + """ + def __init__(self, data): + self._data_ = data + + ## List of methods to directly wrap from _data_ + wrapMethods = ['_cmp_', '__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'has_key', 'iterkeys', 'keys', ] + + ## List of methods which wrap from _data_ but return protected results + protectMethods = ['__getitem__', '__iter__', 'get', 'items', 'values'] + + ## List of methods to disable + disableMethods = ['__delitem__', '__setitem__', 'clear', 'pop', 'popitem', 'setdefault', 'update'] + + + ## Template methods + def wrapMethod(methodName): + return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) + + def protectMethod(methodName): + return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) + + def error(self, *args, **kargs): + raise Exception("Can not modify read-only list.") + + + ## Directly (and explicitly) wrap some methods from _data_ + ## Many of these methods can not be intercepted using __getattribute__, so they + ## must be implemented explicitly + for methodName in wrapMethods: + locals()[methodName] = wrapMethod(methodName) + + ## Wrap some methods from _data_ with the results converted to protected objects + for methodName in protectMethods: + locals()[methodName] = protectMethod(methodName) + + ## Disable any methods that could change data in the list + for methodName in disableMethods: + locals()[methodName] = error + + + ## Add a few extra methods. + def copy(self): + raise Exception("It is not safe to copy protected dicts! (instead try deepcopy, but be careful.)") + + def itervalues(self): + for v in self._data_.itervalues(): + yield protect(v) + + def iteritems(self): + for k, v in self._data_.iteritems(): + yield (k, protect(v)) + + def deepcopy(self): + return copy.deepcopy(self._data_) + + def __deepcopy__(self, memo): + return copy.deepcopy(self._data_, memo) + + + +class ProtectedList(collections.Sequence): + """ + A class allowing read-only 'view' of a list or dict. + The object can be treated like a normal list, but will never modify the original list it points to. + Any values accessed from the list will also be read-only. + + Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. + However, doing this causes tuple(obj) to return unprotected results (importantly, this means + unpacking into function arguments will also fail) + """ + def __init__(self, data): + self._data_ = data + #self.__mro__ = (ProtectedList, object) + + ## List of methods to directly wrap from _data_ + wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__gt__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] + + ## List of methods which wrap from _data_ but return protected results + protectMethods = ['__getitem__', '__getslice__', '__mul__', '__reversed__', '__rmul__'] + + ## List of methods to disable + disableMethods = ['__delitem__', '__delslice__', '__iadd__', '__imul__', '__setitem__', '__setslice__', 'append', 'extend', 'insert', 'pop', 'remove', 'reverse', 'sort'] + + + ## Template methods + def wrapMethod(methodName): + return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) + + def protectMethod(methodName): + return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) + + def error(self, *args, **kargs): + raise Exception("Can not modify read-only list.") + + + ## Directly (and explicitly) wrap some methods from _data_ + ## Many of these methods can not be intercepted using __getattribute__, so they + ## must be implemented explicitly + for methodName in wrapMethods: + locals()[methodName] = wrapMethod(methodName) + + ## Wrap some methods from _data_ with the results converted to protected objects + for methodName in protectMethods: + locals()[methodName] = protectMethod(methodName) + + ## Disable any methods that could change data in the list + for methodName in disableMethods: + locals()[methodName] = error + + + ## Add a few extra methods. + def __iter__(self): + for item in self._data_: + yield protect(item) + + + def __add__(self, op): + if isinstance(op, ProtectedList): + return protect(self._data_.__add__(op._data_)) + elif isinstance(op, list): + return protect(self._data_.__add__(op)) + else: + raise TypeError("Argument must be a list.") + + def __radd__(self, op): + if isinstance(op, ProtectedList): + return protect(op._data_.__add__(self._data_)) + elif isinstance(op, list): + return protect(op.__add__(self._data_)) + else: + raise TypeError("Argument must be a list.") + + def deepcopy(self): + return copy.deepcopy(self._data_) + + def __deepcopy__(self, memo): + return copy.deepcopy(self._data_, memo) + + def poop(self): + raise Exception("This is a list. It does not poop.") + + +class ProtectedTuple(collections.Sequence): + """ + A class allowing read-only 'view' of a tuple. + The object can be treated like a normal tuple, but its contents will be returned as protected objects. + + Note: It would be nice if we could inherit from list or tuple so that isinstance checks would work. + However, doing this causes tuple(obj) to return unprotected results (importantly, this means + unpacking into function arguments will also fail) + """ + def __init__(self, data): + self._data_ = data + + ## List of methods to directly wrap from _data_ + wrapMethods = ['__contains__', '__eq__', '__format__', '__ge__', '__getnewargs__', '__gt__', '__hash__', '__le__', '__len__', '__lt__', '__ne__', '__reduce__', '__reduce_ex__', '__repr__', '__str__', 'count', 'index'] + + ## List of methods which wrap from _data_ but return protected results + protectMethods = ['__getitem__', '__getslice__', '__iter__', '__add__', '__mul__', '__reversed__', '__rmul__'] + + + ## Template methods + def wrapMethod(methodName): + return lambda self, *a, **k: getattr(self._data_, methodName)(*a, **k) + + def protectMethod(methodName): + return lambda self, *a, **k: protect(getattr(self._data_, methodName)(*a, **k)) + + + ## Directly (and explicitly) wrap some methods from _data_ + ## Many of these methods can not be intercepted using __getattribute__, so they + ## must be implemented explicitly + for methodName in wrapMethods: + locals()[methodName] = wrapMethod(methodName) + + ## Wrap some methods from _data_ with the results converted to protected objects + for methodName in protectMethods: + locals()[methodName] = protectMethod(methodName) + + + ## Add a few extra methods. + def deepcopy(self): + return copy.deepcopy(self._data_) + + def __deepcopy__(self, memo): + return copy.deepcopy(self._data_, memo) + + + +def protect(obj): + if isinstance(obj, dict): + return ProtectedDict(obj) + elif isinstance(obj, list): + return ProtectedList(obj) + elif isinstance(obj, tuple): + return ProtectedTuple(obj) + else: + return obj + + +if __name__ == '__main__': + d = {'x': 1, 'y': [1,2], 'z': ({'a': 2, 'b': [3,4], 'c': (5,6)}, 1, 2)} + dp = protect(d) + + l = [1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}] + lp = protect(l) + + t = (1, 'x', ['a', 'b'], ('c', 'd'), {'x': 1, 'y': 2}) + tp = protect(t) diff --git a/papi/pyqtgraph/pixmaps/__init__.py b/papi/pyqtgraph/pixmaps/__init__.py new file mode 100644 index 00000000..c26e4a6b --- /dev/null +++ b/papi/pyqtgraph/pixmaps/__init__.py @@ -0,0 +1,26 @@ +""" +Allows easy loading of pixmaps used in UI elements. +Provides support for frozen environments as well. +""" + +import os, sys, pickle +from ..functions import makeQImage +from ..Qt import QtGui +if sys.version_info[0] == 2: + from . import pixmapData_2 as pixmapData +else: + from . import pixmapData_3 as pixmapData + + +def getPixmap(name): + """ + Return a QPixmap corresponding to the image file with the given name. + (eg. getPixmap('auto') loads pyqtgraph/pixmaps/auto.png) + """ + key = name+'.png' + data = pixmapData.pixmapData[key] + if isinstance(data, basestring) or isinstance(data, bytes): + pixmapData.pixmapData[key] = pickle.loads(data) + arr = pixmapData.pixmapData[key] + return QtGui.QPixmap(makeQImage(arr, alpha=True)) + diff --git a/papi/pyqtgraph/pixmaps/auto.png b/papi/pyqtgraph/pixmaps/auto.png new file mode 100644 index 0000000000000000000000000000000000000000..a27ff4f82b6e31f2819061a53ba72d4b322dc26d GIT binary patch literal 1022 zcmV^J4|8T_gkr2rEb%1y+EsAi5$F z6^a6Zm<{4b{(-IT$`sImw8eJYQn(>RHV8WC_#q^8EE0#dLYAnABJEZo1<0|z1#9Gq zacovOaj)`x-@JLB@te`i5W_HFnj}eY4a4{wz#jlc0K7tTtODRGfHOkKXF(9Yn+{?E zNRsryFpMt%-ZUwxll>rsd=vzs_y|xe7PEwqg0+{XX{c7KP01Jvh2Zn~D2))36$GJ3 zwzjsqYPH%o0Ivby`uZB8C_+&bsH)l&0ES_}aU62F9QykDO!)^P`E-26-k`O8e6 z&*!0OTEhY3@i>Nuhbis!^b}`jXAR?gJ|9+BSFI4A{DhEEvn7h6YjOZsTU$dU5}~y7 z^K&GV$%b*7ra=@%tgNh<65j&gz3EgG#Wgw4^S0ywFfuZNXf)~?uPBOjlA|s7wbiQC zs;8RI&(Ayh78e&?<5g9)F78(?t`Qashr>vvQjY%V>1hN4f#(c~X91+sX|OE&*sj%T zHo$Z`{oesBEG$s%?d@&by75wm-vWrmVu;0JlqN|Mip3(O4Gj%paB#5s0QIvkl}b1{ zIe{$8wynFgwA5SxmSt_Vm1P-*VW3tb{Th(sdbd7jd&vj>3t`+Mx}?owJb8nvAl zzXedg_*7NJ&d!dlUws(>Z-2qx$l2Q3icBU$X@n5$@9(<~3;>gplUQC}ww1>-z{JD^ zIyyS&0KXqSJw2WPSeAt#2v}cVckI)!1dR?JujW<;$3M{9+lzQSPHDHdx465zYdBya z5I`^(d~6R441ni($IFT%z}(y%2qBb~$z*VOdD##kkw{=;V}sIixtt@wlO^!`{m5ps zly-G><(eD-4i67;b8|y!$z&4k?d{$H#>dCe+1W{H^?TpRbX`ZGKwYrW*4BpE*;z+` z3h;1sW*7#5eedhK4q28x0RTXuP=K!Mlx7}K48vH@iAoD0#OVy1KfMNF=D> zWd~F$C0o$RZJh6k)418x_AmtgMxg zXCY%qyWNsP1VI$676i93_`#s^5(Wz!3mXNE#qQf;A$9{Irg9?;At)qHW{d1(<1@;8 z$%y-}X3jnLp5MLq+?jiXX_{D4RrP~un!f-8z)ye&dz1q{1Aho1ehq~}e_IX00#H>o zY?|h8z;`wUeMwm%#LuBnC|LnWCX*k95TB~O48tIw&)X7Fk|dl?=M$R{;$tWjN{WMn zgYO?7AJaewz}3|iilU%t8pUGK767-~jnC&pmSuW-do6QT2=Rk+YHDgp2r*#=Uszb+ z_V$)isbouVfKsVMHk;+-E)a>Z%*W zFbot$sW$aHfFG?!P19cNJU>6r+S(fD=jY7L&D9N|XZYWaNr)jf|yBBax4 zT3T9IUS4K$aCcN1JqoBySqD9R#pJ; z`~8)yuMQ67E!f)HVtjnuUI2jI-Cd&5DA(85uMW^C#H+{f`1qKKi3!5tFtf9>#9}cX z9v*CksF?%RDijJ_TwGM%Z*Feb+1a7BwY4ThG#dS;ocQVwk)>2BMI;ijZ6&@Lz;1}T z0PKXQ8^A7zh5+nY;0^WG&Dpmmjl4(^HtXfe>zOl{A|6+viz)o zs8_nK6OYG<$K&X_4wNS;%W}2b@9$oiynku-zbH;Ey+?We4}W%#BJQ5X8UO$Q07*qo IM6N<$f(oslRsaA1 literal 0 HcmV?d00001 diff --git a/papi/pyqtgraph/pixmaps/default.png b/papi/pyqtgraph/pixmaps/default.png new file mode 100644 index 0000000000000000000000000000000000000000..f12394214dadb66bd90e7c671d87a5810c1571a6 GIT binary patch literal 810 zcmV+_1J(SAP)zz1OUPe zo)1aVX}s1#$L?N9pk7vg&d^@u+F#8>SvWgp*|R{@KeW?!3fxz47$#1qCH6_Y)^y5H zFib~-aq$M`CM?fzU}@|U$SOTo#9WkucPXo&vB2K&eIgE|bw0-T(%|)#nK)Sc{AX5X zIWXIK##ob14q*wyDlklNU<++L7^Y&uFc|~-!AO{IKaDvBOR`X$$6{h|77ub^ps^|y z@hyVorLkzxE2BU^9tFCgFqp3K1eL%6WF<}}X}+szD2o9fg+8#Ph}aT;>%R=;NxN6^ zTbv-8ut4|b3g|{6U|~2MCR^ChTgEaKC)ux7-lsJ^PbRA}nwbcU4XA6=i!6g4>T`XP|J5%#wT3-^=I~qQWyqB2C1H_ z_Me+?4L0-^9XAQ?kmH0TldgX)VE^c=aNbnZcyEq2A2q2uJxpWGE*p|Gavqm`%@Q#W z38dLJis=%!hEL*?QgvCt%%ga6-E0rSv|=_efIkj~u&`hia8X517NcV-o8oKpY%2!R zm>szsnsM8u7~qjeox1WMKkXTaknYw+SY5C^YeS z2csdwiC&Q6hH#OEDXr!rU1uJ%@Lh>*$V47J6bmAsBv4|U6>byYGSUbfk$D~#xucHK z{Zv!lu04uMceeV4v#+L{6@Vo{eGhCx%J0&2Sm19dTTu(y$TBP2jv!^5H#OdTc&DoF zfSX$6?4fzaau?mB-@@gq<{k6mUm5H6LvOo$DP@b7ou<~qR$lC4S9y(2GiMgTKheDM oL#k?5npWtHR9tJvD)=vc0s@|Dx>pGq$N&HU07*qoM6N<$f=L-|8vp + + + + + + + + + image/svg+xml + + + + + + + + + + + A + + + + + + + + + + + diff --git a/papi/pyqtgraph/pixmaps/lock.png b/papi/pyqtgraph/pixmaps/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..3f85dde05d29a80e9cba8d9d339f88d7f2a960e8 GIT binary patch literal 913 zcmV;C18)3@P)w!Ln*0d{ zafdMobt(epI8-eN0v0C530PdEPE`<#8{tZYt^V$cKpKk$7d8?&Dy;LOM!Qmq-0sN` zt$aQuPT{AT_hX)UmYLakUon|X@L-ze*OSTQHSj&415p-(55PO%tq|guTrT&g7Z9ER z(==a9CX+Wnd_jxOLSG2+V=k9#_yQV@#!Df@Z_`<>>tfsXLRM5&6-kor`GgQJbGckY zoSdAjI*#)P@D;%2$=qI^=a34A>dmtXjxVu!S3!ZXJ=>R^Lf(g zG_ous%QES7nnIyKyWM7IXD5)uvaIQk%!c@Q)oj}i-19=Az~SK`s;c_FZnsOf+x6{8 zB*M|r5rslwCeLUznm*joP@vq8NG6jQhT+@IW)of4$!4=;vsrXqr`c@!al-8uWiiq z;m!GC0nuobR4N6)#l;1K!C+?YU@)N5=>U*QrHDr7|H@L%Csz_Wm7 z0U`cw8c\\xb7\\xe8\\xff\\x14\\x96\\xc8\\xfe\\x02}\\xa3\\xb1\\x00\\x7f\\xa6Q\\x03\\x82\\xa9\\xe8\\x00\\x7f\\xa6\\xe9\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x11\\x1c\\x98\\xb8\\x04%\\xb5\\xd3\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00D\\xad\\xc8\\xf3r\\xec\\xf9\\xffg\\xe5\\xf7\\xff:\\xb7\\xe8\\xff\\x19\\x90\\xc5\\xfe\\x03{\\xa0\\xa6\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6*\\x00\\x7f\\xa6*\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x98\\x0f\\x8f\\xb1\\x13&\\xb5\\xd3\\x04.\\xc0\\xd1\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x19\\x93\\xb7\\xc6i\\xdf\\xf4\\xffg\\xe5\\xf7\\xffT\\xc8\\xee\\xff\\x06\\x88\\xcd\\xff\\x08g\\x85\\xf7\\x00\\x7f\\xa6\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x1b\\x01\\x80\\xa7\\xeb\\x1d\\xa3\\xca\\x16#\\xb2\\xd4\\n*\\xbb\\xd2\\x04.\\xbc\\xd7\\x01\\xff\\xff\\xff\\x00\\x01\\x81\\xa7\\x88Y\\xd1\\xee\\xffg\\xe5\\xf7\\xfff\\xd9\\xf3\\xff\\\'\\xa2\\xe2\\xff\\x05e\\x99\\xf9\\x06~\\xa5\\xf3\\x01\\x81\\xa8\\x9c\\x01\\x80\\xa8\\x9f\\x04\\x85\\xad\\xef\\x08\\x8f\\xb9\\x92\\x17\\xa4\\xd6*\\x1e\\xac\\xd5\\x1a$\\xb3\\xd3\\x0c\\x19\\xa7\\xd5\\x02\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6+!\\xa3\\xc8\\xf5i\\xe0\\xf5\\xffe\\xd9\\xf3\\xff\\\\\\xca\\xee\\xff\\x1f\\x9c\\xe0\\xfa\\x03\\x84\\xca\\xd6\\x07\\x8b\\xc5\\xca\\x06\\x88\\xc1\\xb8\\x08\\x8e\\xd0l\\x0b\\x96\\xd8I\\x11\\x9e\\xd74\\x17\\xa5\\xd6 \\xab\\xd7\\x0b\\x17\\xa2\\xdc\\x01\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x01\\x80\\xa8~?\\xb9\\xe0\\xf9h\\xda\\xf3\\xff_\\xcc\\xef\\xffV\\xc1\\xec\\xfd3\\xa7\\xe3\\xe3\\x1a\\x96\\xde\\xae\\x04\\x8b\\xdb\\x89\\x00\\x89\\xdao\\x05\\x8f\\xd9T\\x0b\\x96\\xd8<\\x11\\x9b\\xd7\\x1d\\x18\\x95\\xc9\\x0c\\x00\\x80\\xd5\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x04\\x03\\x83\\xaa\\xcd5\\xa2\\xc9\\xf9[\\xc6\\xea\\xffU\\xc1\\xec\\xffH\\xb4\\xe8\\xf39\\xa8\\xe4\\xc5\\x0b\\x8f\\xdc\\x9f\\x00\\x89\\xda{\\x00\\x89\\xda_\\x07\\x87\\xc4I\\x05|\\xa5s\\x05m\\xa3\\x02\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa6\\x06\\x01\\x7f\\xa6\\x89\\x12x\\x9e\\xf63\\x88\\xae\\xfe6\\x93\\xc3\\xfe4\\x9d\\xd6\\xdf\\x08\\x82\\xc7\\xb8\\x03k\\xa2\\xab\\x04k\\x97\\xa8\\x02w\\x9e\\xeb\\x00\\x7f\\xa6j\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\x00\\x7f\\xa67\\x00~\\xa5\\x95\\x03v\\x9c\\xd4\\x03h\\x8c\\xfa\\x02i\\x8e\\xf9\\x01x\\x9f\\xcc\\x00\\x7f\\xa6\\x92\\x00\\x7f\\xa63\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\'\np13\ntp14\nb.', 'ctrl.png': "cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I32\nI32\nI4\ntp6\ncnumpy\ndtype\np7\n(S'u1'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'|'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS'\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xad\\xad\\xad\\x19\\xa8\\xa8\\xa8\\x8d\\xa9\\xa9\\xa9\\xc1\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xaa\\xaa\\xaa\\xc2\\xa9\\xa9\\xa9\\x8e\\xad\\xad\\xad\\x19\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xa8\\xa8\\xa8X\\xa9\\xa9\\xa9\\xed\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xed\\xa8\\xa8\\xa8X\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff555\\xffPPP\\xff\\x13\\x13\\x13\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x01\\x01\\x01\\xff\\xb2\\xb2\\xb2\\xff\\xe3\\xe3\\xe3\\xff\\xd9\\xd9\\xd9\\xff]]]\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x13\\x13\\x13\\xff\\xbb\\xbb\\xbb\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xffFFF\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x13\\x13\\x13\\xff\\xbb\\xbb\\xbb\\xff\\xe3\\xe3\\xe3\\xff\\xc4\\xc4\\xc4\\xff\\x06\\x06\\x06\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff```\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff:::\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff666\\xff\\xaf\\xaf\\xaf\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9b\\x9b\\x9b\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff@@@\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffSSS\\xff\\xe3\\xe3\\xe3\\xff\\xb7\\xb7\\xb7\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x04\\x04\\x04\\xff\\xd5\\xd5\\xd5\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xffXXX\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x17\\x17\\x17\\xff\\xdb\\xdb\\xdb\\xff\\xe3\\xe3\\xe3\\xff\\xb7\\xb7\\xb7\\xff[[[\\xff\\x97\\x97\\x97\\xff\\xd4\\xd4\\xd4\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff```\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xffHHH\\xff\\xc6\\xc6\\xc6\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x07\\x07\\x07\\xff;;;\\xffAAA\\xff\\\\\\\\\\\\\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xda\\xda\\xda\\xff;;;\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xff\\xe3\\xe3\\xe3\\xff\\xc7\\xc7\\xc7\\xffZZZ\\xff~~~\\xff\\xd9\\xd9\\xd9\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xff\\xe3\\xe3\\xe3\\xffXXX\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xb0\\xb0\\xb0\\xfffff\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xdd\\xdd\\xdd\\xffyyy\\xff\\x00\\x00\\x00\\xff\\x06\\x06\\x06\\xff\\xcd\\xcd\\xcd\\xfffff\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff@@@\\xff\\xda\\xda\\xda\\xff\\xaf\\xaf\\xaf\\xff\\xcd\\xcd\\xcd\\xff\\xd7\\xd7\\xd7\\xff\\x10\\x10\\x10\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x12\\x12\\x12\\xffiii\\xffccc\\xff\\x0e\\x0e\\x0e\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00'\np13\ntp14\nb.", 'auto.png': "cnumpy.core.multiarray\n_reconstruct\np0\n(cnumpy\nndarray\np1\n(I0\ntp2\nS'b'\np3\ntp4\nRp5\n(I1\n(I32\nI32\nI4\ntp6\ncnumpy\ndtype\np7\n(S'u1'\np8\nI0\nI1\ntp9\nRp10\n(I3\nS'|'\np11\nNNNI-1\nI-1\nI0\ntp12\nbI00\nS'\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xad\\xad\\xad\\x19\\xa8\\xa8\\xa8\\x8d\\xa9\\xa9\\xa9\\xc1\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xaa\\xaa\\xaa\\xc2\\xa9\\xa9\\xa9\\x8e\\xad\\xad\\xad\\x19\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xa8\\xa8\\xa8X\\xa9\\xa9\\xa9\\xed\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xed\\xa8\\xa8\\xa8X\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x19\\x19\\x19\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x04\\x04\\x04\\xffHHH\\xff\\xa4\\xa4\\xa4\\xff\\xe5\\xe5\\xe5\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff \\xffyyy\\xff\\xd1\\xd1\\xd1\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x06\\x06\\x06\\xffPPP\\xff\\xab\\xab\\xab\\xff\\xe6\\xe6\\xe6\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff&&&\\xff\\x82\\x82\\x82\\xff\\xd6\\xd6\\xd6\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\t\\t\\t\\xffWWW\\xff\\xb2\\xb2\\xb2\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe5\\xe5\\xe5\\xff\\xa8\\xa8\\xa8\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff---\\xff\\x89\\x89\\x89\\xff\\xda\\xda\\xda\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xc1\\xc1\\xc1\\xfflll\\xff\\x18\\x18\\x18\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\r\\r\\r\\xff^^^\\xff\\xba\\xba\\xba\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xda\\xda\\xda\\xff...\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff555\\xff\\x90\\x90\\x90\\xff\\xde\\xde\\xde\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe2\\xe2\\xe2\\xff\\xe3\\xe3\\xe3\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff;;;\\xff\\xc1\\xc1\\xc1\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xb7\\xb7\\xb7\\xffbbb\\xff\\x12\\x12\\x12\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xcd\\xcd\\xcd\\xffyyy\\xff$$$\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe3\\xe3\\xe3\\xff\\x91\\x91\\x91\\xff<<<\\xff\\x01\\x01\\x01\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xc3\\xc3\\xc3\\xfflll\\xff\\x18\\x18\\x18\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xffmmm\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe4\\xe4\\xe4\\xff\\xa6\\xa6\\xa6\\xffOOO\\xff\\x07\\x07\\x07\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff555\\xff\\xb4\\xb4\\xb4\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd9\\xd9\\xd9\\xff\\x8a\\x8a\\x8a\\xff333\\xff\\xcb\\xcb\\xcb\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff+++\\xff\\x88\\x88\\x88\\xff\\xda\\xda\\xda\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xd2\\xd2\\xd2\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\n\\n\\n\\xff[[[\\xff\\xb8\\xb8\\xb8\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xdc\\xdc\\xdc\\xffAAA\\xff\\x02\\x02\\x02\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff...\\xff\\x8c\\x8c\\x8c\\xff\\xdc\\xdc\\xdc\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xcc\\xcc\\xcc\\xffsss\\xff\\x1a\\x1a\\x1a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x0c\\x0c\\x0c\\xff___\\xff\\xbc\\xbc\\xbc\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe5\\xe5\\xe5\\xff\\xa5\\xa5\\xa5\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff222\\xff\\x8f\\x8f\\x8f\\xff\\xde\\xde\\xde\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x9a\\x9a\\x9a\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x0e\\x0e\\x0e\\xffccc\\xff\\xc0\\xc0\\xc0\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x9a\\x9a\\x9a\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff555\\xff\\x94\\x94\\x94\\xff\\xe0\\xe0\\xe0\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff)))\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x10\\x10\\x10\\xfffff\\xff\\xc4\\xc4\\xc4\\xff\\xe7\\xe7\\xe7\\xff\\x00\\x00\\x00\\xff)))\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff\\x03\\x03\\x03\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff:::\\xff\\x03\\x03\\x03\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\x88\\x88\\x88\\xff)))\\xff\\x05\\x05\\x05\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x00\\x00\\x00\\xff\\x05\\x05\\x05\\xff)))\\xff\\x88\\x88\\x88\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa6\\xa6\\xa6\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\x9a\\x9a\\x9a\\xff\\xa6\\xa6\\xa6\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaaW\\xa9\\xa9\\xa9\\xeb\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xeb\\xaa\\xaa\\xaaW\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xaa\\xaa\\xaa\\x15\\xa9\\xa9\\xa9\\x88\\xa9\\xa9\\xa9\\xbd\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xff\\xa9\\xa9\\xa9\\xf1\\xa9\\xa9\\xa9\\xbe\\xa9\\xa9\\xa9\\x88\\xaa\\xaa\\xaa\\x15\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00\\xff\\xff\\xff\\x00'\np13\ntp14\nb."} \ No newline at end of file diff --git a/papi/pyqtgraph/pixmaps/pixmapData_3.py b/papi/pyqtgraph/pixmaps/pixmapData_3.py new file mode 100644 index 00000000..bb512029 --- /dev/null +++ b/papi/pyqtgraph/pixmaps/pixmapData_3.py @@ -0,0 +1 @@ +import numpy as np; pixmapData={'lock.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K K K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x10\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xad\xad\xad\x19\xa8\xa8\xa8\x8d\xa9\xa9\xa9\xc1\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xaa\xaa\xaa\xc2\xa9\xa9\xa9\x8e\xad\xad\xad\x19\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xa8\xa8\xa8X\xa9\xa9\xa9\xed\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xed\xa8\xa8\xa8X\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x0c\x0c\x0c\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xd2\xd2\xd2\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe1\xe1\xe1\xff{{{\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0e\x0e\x0e\xff***\xff+++\xff+++\xff\xaf\xaf\xaf\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\x10\x10\x10\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x1e\x1e\x1e\xff\x93\x93\x93\xff\xc6\xc6\xc6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffaaa\xff\xdc\xdc\xdc\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\\\\\\\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\xbb\xbb\xbb\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\xd7\xd7\xd7\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x1c\x1c\x1c\xff\xda\xda\xda\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x91\x91\x91\xff\x0f\x0f\x0f\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x87\x87\x87\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x98\x98\x98\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\xba\xba\xba\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x19\x19\x19\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x08\x08\x08\xff\xe2\xe2\xe2\xff\xe6\xe6\xe6\xff\xcc\xcc\xcc\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x08\x08\x08\xff\xe2\xe2\xe2\xff\xe6\xe6\xe6\xff\xcc\xcc\xcc\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\xba\xba\xba\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x19\x19\x19\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x85\x85\x85\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x98\x98\x98\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x19\x19\x19\xff\xd9\xd9\xd9\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x91\x91\x91\xff\x0f\x0f\x0f\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb4\xb4\xb4\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffZZZ\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\xbc\xbc\xbc\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\x9f\x9f\x9f\xff\xd7\xd7\xd7\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffaaa\xff\xdc\xdc\xdc\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x1e\x1e\x1e\xff\x93\x93\x93\xff\xc6\xc6\xc6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\x1d\x1d\x1d\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0e\x0e\x0e\xff***\xff+++\xff+++\xff\xaf\xaf\xaf\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe2\xe2\xe2\xff\x10\x10\x10\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xd2\xd2\xd2\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe6\xe6\xe6\xff\xe1\xe1\xe1\xff{{{\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x16\x16\x16\xff\x0c\x0c\x0c\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbd\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbe\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.', 'default.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K\x10K\x10K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x04\x00\x00\x00\x7f\xa6\x1b\x0c\x8a\xad\xdc\r\x91\xb0\xf3\r\x91\xb0\xf3\r\x91\xb0\xf4\r\x91\xb1\xf4\r\x90\xb0\xf4\x05\x85\xa9\xef\x00\x7f\xa6<\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6!\x1d\x9c\xb9\xf5g\xd9\xf1\xffi\xd9\xf3\xffd\xd1\xee\xff]\xcb\xeb\xff@\xbb\xe3\xff\x16\x9c\xc2\xf8\x00\x7f\xa6\xb4\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6U\'\xac\xc5\xf9i\xd9\xf3\xffc\xd3\xef\xff\\\xcf\xeb\xffP\xc8\xe6\xff\x17\x9f\xc4\xfd\x00\x7f\xa6\xfc\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x02\x83\xa8lH\xc5\xdd\xfah\xdc\xf3\xffc\xd4\xef\xffV\xce\xe9\xffN\xcf\xe7\xff&\xaa\xca\xfd\x00\x7f\xa6\xff\x03\x81\xc7\x01\x04\x8d\xda\x01\t\x94\xd9\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6"$\xa9\xc4\xf7g\xdf\xf5\xfff\xdb\xf3\xffU\xcd\xeb\xff\x16\xb3\xda\xff.\xc9\xe1\xff(\xb2\xd0\xfe\x01\x7f\xa6\xff\x04\x84\xc9\x05\t\x94\xd9\x06\x10\x9c\xd7\x01\x16\xa2\xd6\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x02\x83\xa9\x81T\xd3\xeb\xffg\xe5\xf7\xffe\xda\xf3\xff!\xaa\xde\xff\x11\x9d\xc3\xfe\x11\xba\xd7\xff \xb9\xd5\xfe\x00\x7f\xa6\xff\x16u\x8d\x03\x14\x84\xae\x05\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x10\x92\xb4\xc0d\xde\xf3\xffg\xe5\xf7\xff_\xcc\xef\xff\x0e\x9c\xd5\xff\rx\x95\xf6\x0e\x89\xab\xf4\x18\xb2\xd1\xfc\x00\x7f\xa6\xff\xff\xff\xff\x00\x1a~\x91\x01\x1d\xa5\xce\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x005\xa9\xc3\xefq\xec\xf9\xffg\xe5\xf7\xff>\xb7\xe8\xff\x14\x96\xc8\xfe\x02}\xa3\xb1\x00\x7f\xa6Q\x03\x82\xa9\xe8\x00\x7f\xa6\xe9\xff\xff\xff\x00\x00\x7f\xa6\x11\x1c\x98\xb8\x04%\xb5\xd3\x01\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00D\xad\xc8\xf3r\xec\xf9\xffg\xe5\xf7\xff:\xb7\xe8\xff\x19\x90\xc5\xfe\x03{\xa0\xa6\xff\xff\xff\x00\x00\x7f\xa6*\x00\x7f\xa6*\xff\xff\xff\x00\x00\x7f\xa6\x98\x0f\x8f\xb1\x13&\xb5\xd3\x04.\xc0\xd1\x01\xff\xff\xff\x00\xff\xff\xff\x00\x19\x93\xb7\xc6i\xdf\xf4\xffg\xe5\xf7\xffT\xc8\xee\xff\x06\x88\xcd\xff\x08g\x85\xf7\x00\x7f\xa6\x15\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6\x1b\x01\x80\xa7\xeb\x1d\xa3\xca\x16#\xb2\xd4\n*\xbb\xd2\x04.\xbc\xd7\x01\xff\xff\xff\x00\x01\x81\xa7\x88Y\xd1\xee\xffg\xe5\xf7\xfff\xd9\xf3\xff\'\xa2\xe2\xff\x05e\x99\xf9\x06~\xa5\xf3\x01\x81\xa8\x9c\x01\x80\xa8\x9f\x04\x85\xad\xef\x08\x8f\xb9\x92\x17\xa4\xd6*\x1e\xac\xd5\x1a$\xb3\xd3\x0c\x19\xa7\xd5\x02\xff\xff\xff\x00\x00\x7f\xa6+!\xa3\xc8\xf5i\xe0\xf5\xffe\xd9\xf3\xff\\\xca\xee\xff\x1f\x9c\xe0\xfa\x03\x84\xca\xd6\x07\x8b\xc5\xca\x06\x88\xc1\xb8\x08\x8e\xd0l\x0b\x96\xd8I\x11\x9e\xd74\x17\xa5\xd6 \xab\xd7\x0b\x17\xa2\xdc\x01\xff\xff\xff\x00\xff\xff\xff\x00\x01\x80\xa8~?\xb9\xe0\xf9h\xda\xf3\xff_\xcc\xef\xffV\xc1\xec\xfd3\xa7\xe3\xe3\x1a\x96\xde\xae\x04\x8b\xdb\x89\x00\x89\xdao\x05\x8f\xd9T\x0b\x96\xd8<\x11\x9b\xd7\x1d\x18\x95\xc9\x0c\x00\x80\xd5\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6\x04\x03\x83\xaa\xcd5\xa2\xc9\xf9[\xc6\xea\xffU\xc1\xec\xffH\xb4\xe8\xf39\xa8\xe4\xc5\x0b\x8f\xdc\x9f\x00\x89\xda{\x00\x89\xda_\x07\x87\xc4I\x05|\xa5s\x05m\xa3\x02\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa6\x06\x01\x7f\xa6\x89\x12x\x9e\xf63\x88\xae\xfe6\x93\xc3\xfe4\x9d\xd6\xdf\x08\x82\xc7\xb8\x03k\xa2\xab\x04k\x97\xa8\x02w\x9e\xeb\x00\x7f\xa6j\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\x00\x7f\xa67\x00~\xa5\x95\x03v\x9c\xd4\x03h\x8c\xfa\x02i\x8e\xf9\x01x\x9f\xcc\x00\x7f\xa6\x92\x00\x7f\xa63\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.', 'ctrl.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K K K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x10\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xad\xad\xad\x19\xa8\xa8\xa8\x8d\xa9\xa9\xa9\xc1\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xaa\xaa\xaa\xc2\xa9\xa9\xa9\x8e\xad\xad\xad\x19\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xa8\xa8\xa8X\xa9\xa9\xa9\xed\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xed\xa8\xa8\xa8X\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff555\xffPPP\xff\x13\x13\x13\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x01\x01\x01\xff\xb2\xb2\xb2\xff\xe3\xe3\xe3\xff\xd9\xd9\xd9\xff]]]\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x13\x13\x13\xff\xbb\xbb\xbb\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xffFFF\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x13\x13\x13\xff\xbb\xbb\xbb\xff\xe3\xe3\xe3\xff\xc4\xc4\xc4\xff\x06\x06\x06\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff```\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff:::\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff666\xff\xaf\xaf\xaf\xff\x10\x10\x10\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9b\x9b\x9b\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff@@@\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xffSSS\xff\xe3\xe3\xe3\xff\xb7\xb7\xb7\xff\x10\x10\x10\xff\x00\x00\x00\xff\x00\x00\x00\xff\x04\x04\x04\xff\xd5\xd5\xd5\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xffXXX\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x17\x17\x17\xff\xdb\xdb\xdb\xff\xe3\xe3\xe3\xff\xb7\xb7\xb7\xff[[[\xff\x97\x97\x97\xff\xd4\xd4\xd4\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff```\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xffHHH\xff\xc6\xc6\xc6\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x07\x07\x07\xff;;;\xffAAA\xff\\\\\\\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xda\xda\xda\xff;;;\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xff\xe3\xe3\xe3\xff\xc7\xc7\xc7\xffZZZ\xff~~~\xff\xd9\xd9\xd9\xff\x10\x10\x10\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xff\xe3\xe3\xe3\xffXXX\xff\x00\x00\x00\xff\x00\x00\x00\xff\xb0\xb0\xb0\xfffff\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xdd\xdd\xdd\xffyyy\xff\x00\x00\x00\xff\x06\x06\x06\xff\xcd\xcd\xcd\xfffff\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff@@@\xff\xda\xda\xda\xff\xaf\xaf\xaf\xff\xcd\xcd\xcd\xff\xd7\xd7\xd7\xff\x10\x10\x10\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x12\x12\x12\xffiii\xffccc\xff\x0e\x0e\x0e\xff\x00\x00\x00\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbd\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbe\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.', 'auto.png': b'\x80\x03cnumpy.core.multiarray\n_reconstruct\nq\x00cnumpy\nndarray\nq\x01K\x00\x85q\x02C\x01bq\x03\x87q\x04Rq\x05(K\x01K K K\x04\x87q\x06cnumpy\ndtype\nq\x07X\x02\x00\x00\x00u1q\x08K\x00K\x01\x87q\tRq\n(K\x03X\x01\x00\x00\x00|q\x0bNNNJ\xff\xff\xff\xffJ\xff\xff\xff\xffK\x00tq\x0cb\x89B\x00\x10\x00\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xad\xad\xad\x19\xa8\xa8\xa8\x8d\xa9\xa9\xa9\xc1\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xaa\xaa\xaa\xc2\xa9\xa9\xa9\x8e\xad\xad\xad\x19\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xa8\xa8\xa8X\xa9\xa9\xa9\xed\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xed\xa8\xa8\xa8X\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x19\x19\x19\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x04\x04\x04\xffHHH\xff\xa4\xa4\xa4\xff\xe5\xe5\xe5\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbe\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff \xffyyy\xff\xd1\xd1\xd1\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x06\x06\x06\xffPPP\xff\xab\xab\xab\xff\xe6\xe6\xe6\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff&&&\xff\x82\x82\x82\xff\xd6\xd6\xd6\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\t\t\t\xffWWW\xff\xb2\xb2\xb2\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe5\xe5\xe5\xff\xa8\xa8\xa8\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff---\xff\x89\x89\x89\xff\xda\xda\xda\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xc1\xc1\xc1\xfflll\xff\x18\x18\x18\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\r\r\r\xff^^^\xff\xba\xba\xba\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xda\xda\xda\xff...\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff555\xff\x90\x90\x90\xff\xde\xde\xde\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe2\xe2\xe2\xff\xe3\xe3\xe3\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff;;;\xff\xc1\xc1\xc1\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xb7\xb7\xb7\xffbbb\xff\x12\x12\x12\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xcd\xcd\xcd\xffyyy\xff$$$\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe3\xe3\xe3\xff\x91\x91\x91\xff<<<\xff\x01\x01\x01\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xc3\xc3\xc3\xfflll\xff\x18\x18\x18\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xffmmm\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe4\xe4\xe4\xff\xa6\xa6\xa6\xffOOO\xff\x07\x07\x07\xff\x00\x00\x00\xff\x00\x00\x00\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff555\xff\xb4\xb4\xb4\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd9\xd9\xd9\xff\x8a\x8a\x8a\xff333\xff\xcb\xcb\xcb\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff+++\xff\x88\x88\x88\xff\xda\xda\xda\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xd2\xd2\xd2\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\n\n\n\xff[[[\xff\xb8\xb8\xb8\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xdc\xdc\xdc\xffAAA\xff\x02\x02\x02\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff...\xff\x8c\x8c\x8c\xff\xdc\xdc\xdc\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xcc\xcc\xcc\xffsss\xff\x1a\x1a\x1a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0c\x0c\x0c\xff___\xff\xbc\xbc\xbc\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe5\xe5\xe5\xff\xa5\xa5\xa5\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff222\xff\x8f\x8f\x8f\xff\xde\xde\xde\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x9a\x9a\x9a\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x0e\x0e\x0e\xffccc\xff\xc0\xc0\xc0\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x00\x00\x00\xff\x9a\x9a\x9a\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff555\xff\x94\x94\x94\xff\xe0\xe0\xe0\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff\x05\x05\x05\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff)))\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x10\x10\x10\xfffff\xff\xc4\xc4\xc4\xff\xe7\xe7\xe7\xff\x00\x00\x00\xff)))\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xbd\xa9\xa9\xa9\x88\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff\x03\x03\x03\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff:::\xff\x03\x03\x03\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\x88\x88\x88\xff)))\xff\x05\x05\x05\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x05\x05\x05\xff)))\xff\x88\x88\x88\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaa\x15\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa6\xa6\xa6\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\x9a\x9a\x9a\xff\xa6\xa6\xa6\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaaW\xa9\xa9\xa9\xeb\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xeb\xaa\xaa\xaaW\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00\xaa\xaa\xaa\x15\xa9\xa9\xa9\x88\xa9\xa9\xa9\xbd\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xff\xa9\xa9\xa9\xf1\xa9\xa9\xa9\xbe\xa9\xa9\xa9\x88\xaa\xaa\xaa\x15\xff\xff\xff\x00\xff\xff\xff\x00\xff\xff\xff\x00q\rtq\x0eb.'} \ No newline at end of file diff --git a/papi/pyqtgraph/ptime.py b/papi/pyqtgraph/ptime.py new file mode 100644 index 00000000..1de8282f --- /dev/null +++ b/papi/pyqtgraph/ptime.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +""" +ptime.py - Precision time function made os-independent (should have been taken care of by python) +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + + +import sys +import time as systime +START_TIME = None +time = None + +def winTime(): + """Return the current time in seconds with high precision (windows version, use Manager.time() to stay platform independent).""" + return systime.clock() + START_TIME + #return systime.time() + +def unixTime(): + """Return the current time in seconds with high precision (unix version, use Manager.time() to stay platform independent).""" + return systime.time() + +if sys.platform.startswith('win'): + cstart = systime.clock() ### Required to start the clock in windows + START_TIME = systime.time() - cstart + + time = winTime +else: + time = unixTime + diff --git a/papi/pyqtgraph/python2_3.py b/papi/pyqtgraph/python2_3.py new file mode 100644 index 00000000..b1c46f26 --- /dev/null +++ b/papi/pyqtgraph/python2_3.py @@ -0,0 +1,60 @@ +""" +Helper functions that smooth out the differences between python 2 and 3. +""" +import sys + +def asUnicode(x): + if sys.version_info[0] == 2: + if isinstance(x, unicode): + return x + elif isinstance(x, str): + return x.decode('UTF-8') + else: + return unicode(x) + else: + return str(x) + +def cmpToKey(mycmp): + 'Convert a cmp= function into a key= function' + class K(object): + def __init__(self, obj, *args): + self.obj = obj + def __lt__(self, other): + return mycmp(self.obj, other.obj) < 0 + def __gt__(self, other): + return mycmp(self.obj, other.obj) > 0 + def __eq__(self, other): + return mycmp(self.obj, other.obj) == 0 + def __le__(self, other): + return mycmp(self.obj, other.obj) <= 0 + def __ge__(self, other): + return mycmp(self.obj, other.obj) >= 0 + def __ne__(self, other): + return mycmp(self.obj, other.obj) != 0 + return K + +def sortList(l, cmpFunc): + if sys.version_info[0] == 2: + l.sort(cmpFunc) + else: + l.sort(key=cmpToKey(cmpFunc)) + +if sys.version_info[0] == 3: + import builtins + builtins.basestring = str + #builtins.asUnicode = asUnicode + #builtins.sortList = sortList + basestring = str + def cmp(a,b): + if a>b: + return 1 + elif b > a: + return -1 + else: + return 0 + builtins.cmp = cmp + builtins.xrange = range +#else: ## don't use __builtin__ -- this confuses things like pyshell and ActiveState's lazy import recipe + #import __builtin__ + #__builtin__.asUnicode = asUnicode + #__builtin__.sortList = sortList diff --git a/papi/pyqtgraph/reload.py b/papi/pyqtgraph/reload.py new file mode 100644 index 00000000..ccf83913 --- /dev/null +++ b/papi/pyqtgraph/reload.py @@ -0,0 +1,516 @@ +# -*- coding: utf-8 -*- +""" +Magic Reload Library +Luke Campagnola 2010 + +Python reload function that actually works (the way you expect it to) + - No re-importing necessary + - Modules can be reloaded in any order + - Replaces functions and methods with their updated code + - Changes instances to use updated classes + - Automatically decides which modules to update by comparing file modification times + +Does NOT: + - re-initialize exting instances, even if __init__ changes + - update references to any module-level objects + ie, this does not reload correctly: + from module import someObject + print someObject + ..but you can use this instead: (this works even for the builtin reload) + import module + print module.someObject +""" + + +import inspect, os, sys, gc, traceback +try: + import __builtin__ as builtins +except ImportError: + import builtins +from .debug import printExc + +def reloadAll(prefix=None, debug=False): + """Automatically reload everything whose __file__ begins with prefix. + - Skips reload if the file has not been updated (if .pyc is newer than .py) + - if prefix is None, checks all loaded modules + """ + failed = [] + changed = [] + for modName, mod in list(sys.modules.items()): ## don't use iteritems; size may change during reload + if not inspect.ismodule(mod): + continue + if modName == '__main__': + continue + + ## Ignore if the file name does not start with prefix + if not hasattr(mod, '__file__') or os.path.splitext(mod.__file__)[1] not in ['.py', '.pyc']: + continue + if prefix is not None and mod.__file__[:len(prefix)] != prefix: + continue + + ## ignore if the .pyc is newer than the .py (or if there is no pyc or py) + py = os.path.splitext(mod.__file__)[0] + '.py' + pyc = py + 'c' + if py not in changed and os.path.isfile(pyc) and os.path.isfile(py) and os.stat(pyc).st_mtime >= os.stat(py).st_mtime: + #if debug: + #print "Ignoring module %s; unchanged" % str(mod) + continue + changed.append(py) ## keep track of which modules have changed to insure that duplicate-import modules get reloaded. + + try: + reload(mod, debug=debug) + except: + printExc("Error while reloading module %s, skipping\n" % mod) + failed.append(mod.__name__) + + if len(failed) > 0: + raise Exception("Some modules failed to reload: %s" % ', '.join(failed)) + +def reload(module, debug=False, lists=False, dicts=False): + """Replacement for the builtin reload function: + - Reloads the module as usual + - Updates all old functions and class methods to use the new code + - Updates all instances of each modified class to use the new class + - Can update lists and dicts, but this is disabled by default + - Requires that class and function names have not changed + """ + if debug: + print("Reloading %s" % str(module)) + + ## make a copy of the old module dictionary, reload, then grab the new module dictionary for comparison + oldDict = module.__dict__.copy() + builtins.reload(module) + newDict = module.__dict__ + + ## Allow modules access to the old dictionary after they reload + if hasattr(module, '__reload__'): + module.__reload__(oldDict) + + ## compare old and new elements from each dict; update where appropriate + for k in oldDict: + old = oldDict[k] + new = newDict.get(k, None) + if old is new or new is None: + continue + + if inspect.isclass(old): + if debug: + print(" Updating class %s.%s (0x%x -> 0x%x)" % (module.__name__, k, id(old), id(new))) + updateClass(old, new, debug) + + elif inspect.isfunction(old): + depth = updateFunction(old, new, debug) + if debug: + extra = "" + if depth > 0: + extra = " (and %d previous versions)" % depth + print(" Updating function %s.%s%s" % (module.__name__, k, extra)) + elif lists and isinstance(old, list): + l = old.len() + old.extend(new) + for i in range(l): + old.pop(0) + elif dicts and isinstance(old, dict): + old.update(new) + for k in old: + if k not in new: + del old[k] + + + +## For functions: +## 1) update the code and defaults to new versions. +## 2) keep a reference to the previous version so ALL versions get updated for every reload +def updateFunction(old, new, debug, depth=0, visited=None): + #if debug and depth > 0: + #print " -> also updating previous version", old, " -> ", new + + old.__code__ = new.__code__ + old.__defaults__ = new.__defaults__ + + if visited is None: + visited = [] + if old in visited: + return + visited.append(old) + + ## finally, update any previous versions still hanging around.. + if hasattr(old, '__previous_reload_version__'): + maxDepth = updateFunction(old.__previous_reload_version__, new, debug, depth=depth+1, visited=visited) + else: + maxDepth = depth + + ## We need to keep a pointer to the previous version so we remember to update BOTH + ## when the next reload comes around. + if depth == 0: + new.__previous_reload_version__ = old + return maxDepth + + + +## For classes: +## 1) find all instances of the old class and set instance.__class__ to the new class +## 2) update all old class methods to use code from the new class methods +def updateClass(old, new, debug): + + ## Track town all instances and subclasses of old + refs = gc.get_referrers(old) + for ref in refs: + try: + if isinstance(ref, old) and ref.__class__ is old: + ref.__class__ = new + if debug: + print(" Changed class for %s" % safeStr(ref)) + elif inspect.isclass(ref) and issubclass(ref, old) and old in ref.__bases__: + ind = ref.__bases__.index(old) + + ## Does not work: + #ref.__bases__ = ref.__bases__[:ind] + (new,) + ref.__bases__[ind+1:] + ## reason: Even though we change the code on methods, they remain bound + ## to their old classes (changing im_class is not allowed). Instead, + ## we have to update the __bases__ such that this class will be allowed + ## as an argument to older methods. + + ## This seems to work. Is there any reason not to? + ## Note that every time we reload, the class hierarchy becomes more complex. + ## (and I presume this may slow things down?) + ref.__bases__ = ref.__bases__[:ind] + (new,old) + ref.__bases__[ind+1:] + if debug: + print(" Changed superclass for %s" % safeStr(ref)) + #else: + #if debug: + #print " Ignoring reference", type(ref) + except: + print("Error updating reference (%s) for class change (%s -> %s)" % (safeStr(ref), safeStr(old), safeStr(new))) + raise + + ## update all class methods to use new code. + ## Generally this is not needed since instances already know about the new class, + ## but it fixes a few specific cases (pyqt signals, for one) + for attr in dir(old): + oa = getattr(old, attr) + if inspect.ismethod(oa): + try: + na = getattr(new, attr) + except AttributeError: + if debug: + print(" Skipping method update for %s; new class does not have this attribute" % attr) + continue + + if hasattr(oa, 'im_func') and hasattr(na, 'im_func') and oa.__func__ is not na.__func__: + depth = updateFunction(oa.__func__, na.__func__, debug) + #oa.im_class = new ## bind old method to new class ## not allowed + if debug: + extra = "" + if depth > 0: + extra = " (and %d previous versions)" % depth + print(" Updating method %s%s" % (attr, extra)) + + ## And copy in new functions that didn't exist previously + for attr in dir(new): + if not hasattr(old, attr): + if debug: + print(" Adding missing attribute %s" % attr) + setattr(old, attr, getattr(new, attr)) + + ## finally, update any previous versions still hanging around.. + if hasattr(old, '__previous_reload_version__'): + updateClass(old.__previous_reload_version__, new, debug) + + +## It is possible to build classes for which str(obj) just causes an exception. +## Avoid thusly: +def safeStr(obj): + try: + s = str(obj) + except: + try: + s = repr(obj) + except: + s = "" % (safeStr(type(obj)), id(obj)) + return s + + + + + +## Tests: +# write modules to disk, import, then re-write and run again +if __name__ == '__main__': + doQtTest = True + try: + from PyQt4 import QtCore + if not hasattr(QtCore, 'Signal'): + QtCore.Signal = QtCore.pyqtSignal + #app = QtGui.QApplication([]) + class Btn(QtCore.QObject): + sig = QtCore.Signal() + def emit(self): + self.sig.emit() + btn = Btn() + except: + raise + print("Error; skipping Qt tests") + doQtTest = False + + + + import os + if not os.path.isdir('test1'): + os.mkdir('test1') + open('test1/__init__.py', 'w') + modFile1 = "test1/test1.py" + modCode1 = """ +import sys +class A(object): + def __init__(self, msg): + object.__init__(self) + self.msg = msg + def fn(self, pfx = ""): + print(pfx+"A class: %%s %%s" %% (str(self.__class__), str(id(self.__class__)))) + print(pfx+" %%s: %d" %% self.msg) + +class B(A): + def fn(self, pfx=""): + print(pfx+"B class:", self.__class__, id(self.__class__)) + print(pfx+" %%s: %d" %% self.msg) + print(pfx+" calling superclass.. (%%s)" %% id(A) ) + A.fn(self, " ") +""" + + modFile2 = "test2.py" + modCode2 = """ +from test1.test1 import A +from test1.test1 import B + +a1 = A("ax1") +b1 = B("bx1") +class C(A): + def __init__(self, msg): + #print "| C init:" + #print "| C.__bases__ = ", map(id, C.__bases__) + #print "| A:", id(A) + #print "| A.__init__ = ", id(A.__init__.im_func), id(A.__init__.im_func.__code__), id(A.__init__.im_class) + A.__init__(self, msg + "(init from C)") + +def fn(): + print("fn: %s") +""" + + open(modFile1, 'w').write(modCode1%(1,1)) + open(modFile2, 'w').write(modCode2%"message 1") + import test1.test1 as test1 + import test2 + print("Test 1 originals:") + A1 = test1.A + B1 = test1.B + a1 = test1.A("a1") + b1 = test1.B("b1") + a1.fn() + b1.fn() + #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) + + + from test2 import fn, C + + if doQtTest: + print("Button test before:") + btn.sig.connect(fn) + btn.sig.connect(a1.fn) + btn.emit() + #btn.sig.emit() + print("") + + #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) + + + print("Test2 before reload:") + + fn() + oldfn = fn + test2.a1.fn() + test2.b1.fn() + c1 = test2.C('c1') + c1.fn() + + os.remove(modFile1+'c') + open(modFile1, 'w').write(modCode1%(2,2)) + print("\n----RELOAD test1-----\n") + reloadAll(os.path.abspath(__file__)[:10], debug=True) + + + print("Subclass test:") + c2 = test2.C('c2') + c2.fn() + + + os.remove(modFile2+'c') + open(modFile2, 'w').write(modCode2%"message 2") + print("\n----RELOAD test2-----\n") + reloadAll(os.path.abspath(__file__)[:10], debug=True) + + if doQtTest: + print("Button test after:") + btn.emit() + #btn.sig.emit() + + #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) + + print("Test2 after reload:") + fn() + test2.a1.fn() + test2.b1.fn() + + print("\n==> Test 1 Old instances:") + a1.fn() + b1.fn() + c1.fn() + #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) + + print("\n==> Test 1 New instances:") + a2 = test1.A("a2") + b2 = test1.B("b2") + a2.fn() + b2.fn() + c2 = test2.C('c2') + c2.fn() + #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) + + + + + os.remove(modFile1+'c') + os.remove(modFile2+'c') + open(modFile1, 'w').write(modCode1%(3,3)) + open(modFile2, 'w').write(modCode2%"message 3") + + print("\n----RELOAD-----\n") + reloadAll(os.path.abspath(__file__)[:10], debug=True) + + if doQtTest: + print("Button test after:") + btn.emit() + #btn.sig.emit() + + #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) + + print("Test2 after reload:") + fn() + test2.a1.fn() + test2.b1.fn() + + print("\n==> Test 1 Old instances:") + a1.fn() + b1.fn() + print("function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.__func__), id(a1.fn.__self__.__class__), id(b1.fn.__func__), id(b1.fn.__self__.__class__))) + + print("\n==> Test 1 New instances:") + a2 = test1.A("a2") + b2 = test1.B("b2") + a2.fn() + b2.fn() + print("function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.__func__), id(a1.fn.__self__.__class__), id(b1.fn.__func__), id(b1.fn.__self__.__class__))) + + + os.remove(modFile1) + os.remove(modFile2) + os.remove(modFile1+'c') + os.remove(modFile2+'c') + os.system('rm -r test1') + + + + + + + + +# +# Failure graveyard ahead: +# + + +"""Reload Importer: +Hooks into import system to +1) keep a record of module dependencies as they are imported +2) make sure modules are always reloaded in correct order +3) update old classes and functions to use reloaded code""" + +#import imp, sys + +## python's import hook mechanism doesn't work since we need to be +## informed every time there is an import statement, not just for new imports +#class ReloadImporter: + #def __init__(self): + #self.depth = 0 + + #def find_module(self, name, path): + #print " "*self.depth + "find: ", name, path + ##if name == 'PyQt4' and path is None: + ##print "PyQt4 -> PySide" + ##self.modData = imp.find_module('PySide') + ##return self + ##return None ## return none to allow the import to proceed normally; return self to intercept with load_module + #self.modData = imp.find_module(name, path) + #self.depth += 1 + ##sys.path_importer_cache = {} + #return self + + #def load_module(self, name): + #mod = imp.load_module(name, *self.modData) + #self.depth -= 1 + #print " "*self.depth + "load: ", name + #return mod + +#def pathHook(path): + #print "path hook:", path + #raise ImportError +#sys.path_hooks.append(pathHook) + +#sys.meta_path.append(ReloadImporter()) + + +### replace __import__ with a wrapper that tracks module dependencies +#modDeps = {} +#reloadModule = None +#origImport = __builtins__.__import__ +#def _import(name, globals=None, locals=None, fromlist=None, level=-1, stack=[]): + ### Note that stack behaves as a static variable. + ##print " "*len(importStack) + "import %s" % args[0] + #stack.append(set()) + #mod = origImport(name, globals, locals, fromlist, level) + #deps = stack.pop() + #if len(stack) > 0: + #stack[-1].add(mod) + #elif reloadModule is not None: ## If this is the top level import AND we're inside a module reload + #modDeps[reloadModule].add(mod) + + #if mod in modDeps: + #modDeps[mod] |= deps + #else: + #modDeps[mod] = deps + + + #return mod + +#__builtins__.__import__ = _import + +### replace +#origReload = __builtins__.reload +#def _reload(mod): + #reloadModule = mod + #ret = origReload(mod) + #reloadModule = None + #return ret +#__builtins__.reload = _reload + + +#def reload(mod, visited=None): + #if visited is None: + #visited = set() + #if mod in visited: + #return + #visited.add(mod) + #for dep in modDeps.get(mod, []): + #reload(dep, visited) + #__builtins__.reload(mod) diff --git a/papi/pyqtgraph/tests/test_exit_crash.py b/papi/pyqtgraph/tests/test_exit_crash.py new file mode 100644 index 00000000..69181f21 --- /dev/null +++ b/papi/pyqtgraph/tests/test_exit_crash.py @@ -0,0 +1,38 @@ +import os, sys, subprocess, tempfile +import pyqtgraph as pg + + +code = """ +import sys +sys.path.insert(0, '{path}') +import pyqtgraph as pg +app = pg.mkQApp() +w = pg.{classname}({args}) +""" + + +def test_exit_crash(): + # For each Widget subclass, run a simple python script that creates an + # instance and then shuts down. The intent is to check for segmentation + # faults when each script exits. + tmp = tempfile.mktemp(".py") + path = os.path.dirname(pg.__file__) + + initArgs = { + 'CheckTable': "[]", + 'ProgressDialog': '"msg"', + 'VerticalLabel': '"msg"', + } + + for name in dir(pg): + obj = getattr(pg, name) + if not isinstance(obj, type) or not issubclass(obj, pg.QtGui.QWidget): + continue + + print name + argstr = initArgs.get(name, "") + open(tmp, 'w').write(code.format(path=path, classname=name, args=argstr)) + proc = subprocess.Popen([sys.executable, tmp]) + assert proc.wait() == 0 + + os.remove(tmp) diff --git a/papi/pyqtgraph/tests/test_functions.py b/papi/pyqtgraph/tests/test_functions.py new file mode 100644 index 00000000..f622dd87 --- /dev/null +++ b/papi/pyqtgraph/tests/test_functions.py @@ -0,0 +1,81 @@ +import pyqtgraph as pg +import numpy as np +from numpy.testing import assert_array_almost_equal, assert_almost_equal + +np.random.seed(12345) + +def testSolve3D(): + p1 = np.array([[0,0,0,1], + [1,0,0,1], + [0,1,0,1], + [0,0,1,1]], dtype=float) + + # transform points through random matrix + tr = np.random.normal(size=(4, 4)) + tr[3] = (0,0,0,1) + p2 = np.dot(tr, p1.T).T[:,:3] + + # solve to see if we can recover the transformation matrix. + tr2 = pg.solve3DTransform(p1, p2) + + assert_array_almost_equal(tr[:3], tr2[:3]) + + +def test_interpolateArray(): + data = np.array([[ 1., 2., 4. ], + [ 10., 20., 40. ], + [ 100., 200., 400.]]) + + x = np.array([[ 0.3, 0.6], + [ 1. , 1. ], + [ 0.5, 1. ], + [ 0.5, 2.5], + [ 10. , 10. ]]) + + result = pg.interpolateArray(data, x) + + #import scipy.ndimage + #spresult = scipy.ndimage.map_coordinates(data, x.T, order=1) + spresult = np.array([ 5.92, 20. , 11. , 0. , 0. ]) # generated with the above line + + assert_array_almost_equal(result, spresult) + + # test mapping when x.shape[-1] < data.ndim + x = np.array([[ 0.3, 0], + [ 0.3, 1], + [ 0.3, 2]]) + + r1 = pg.interpolateArray(data, x) + r2 = pg.interpolateArray(data, x[0,:1]) + assert_array_almost_equal(r1, r2) + + + # test mapping 2D array of locations + x = np.array([[[0.5, 0.5], [0.5, 1.0], [0.5, 1.5]], + [[1.5, 0.5], [1.5, 1.0], [1.5, 1.5]]]) + + r1 = pg.interpolateArray(data, x) + #r2 = scipy.ndimage.map_coordinates(data, x.transpose(2,0,1), order=1) + r2 = np.array([[ 8.25, 11. , 16.5 ], # generated with the above line + [ 82.5 , 110. , 165. ]]) + + assert_array_almost_equal(r1, r2) + +def test_subArray(): + a = np.array([0, 0, 111, 112, 113, 0, 121, 122, 123, 0, 0, 0, 211, 212, 213, 0, 221, 222, 223, 0, 0, 0, 0]) + b = pg.subArray(a, offset=2, shape=(2,2,3), stride=(10,4,1)) + c = np.array([[[111,112,113], [121,122,123]], [[211,212,213], [221,222,223]]]) + assert np.all(b == c) + + # operate over first axis; broadcast over the rest + aa = np.vstack([a, a/100.]).T + cc = np.empty(c.shape + (2,)) + cc[..., 0] = c + cc[..., 1] = c / 100. + bb = pg.subArray(aa, offset=2, shape=(2,2,3), stride=(10,4,1)) + assert np.all(bb == cc) + + + +if __name__ == '__main__': + test_interpolateArray() \ No newline at end of file diff --git a/papi/pyqtgraph/tests/test_qt.py b/papi/pyqtgraph/tests/test_qt.py new file mode 100644 index 00000000..729bf695 --- /dev/null +++ b/papi/pyqtgraph/tests/test_qt.py @@ -0,0 +1,23 @@ +import pyqtgraph as pg +import gc, os + +app = pg.mkQApp() + +def test_isQObjectAlive(): + o1 = pg.QtCore.QObject() + o2 = pg.QtCore.QObject() + o2.setParent(o1) + del o1 + gc.collect() + assert not pg.Qt.isQObjectAlive(o2) + + +def test_loadUiType(): + path = os.path.dirname(__file__) + formClass, baseClass = pg.Qt.loadUiType(os.path.join(path, 'uictest.ui')) + w = baseClass() + ui = formClass() + ui.setupUi(w) + w.show() + app.processEvents() + diff --git a/papi/pyqtgraph/tests/test_ref_cycles.py b/papi/pyqtgraph/tests/test_ref_cycles.py new file mode 100644 index 00000000..0284852c --- /dev/null +++ b/papi/pyqtgraph/tests/test_ref_cycles.py @@ -0,0 +1,77 @@ +""" +Test for unwanted reference cycles + +""" +import pyqtgraph as pg +import numpy as np +import gc, weakref +app = pg.mkQApp() + +def assert_alldead(refs): + for ref in refs: + assert ref() is None + +def qObjectTree(root): + """Return root and its entire tree of qobject children""" + childs = [root] + for ch in pg.QtCore.QObject.children(root): + childs += qObjectTree(ch) + return childs + +def mkrefs(*objs): + """Return a list of weakrefs to each object in *objs. + QObject instances are expanded to include all child objects. + """ + allObjs = {} + for obj in objs: + if isinstance(obj, pg.QtCore.QObject): + obj = qObjectTree(obj) + else: + obj = [obj] + for o in obj: + allObjs[id(o)] = o + + return map(weakref.ref, allObjs.values()) + +def test_PlotWidget(): + def mkobjs(*args, **kwds): + w = pg.PlotWidget(*args, **kwds) + data = pg.np.array([1,5,2,4,3]) + c = w.plot(data, name='stuff') + w.addLegend() + + # test that connections do not keep objects alive + w.plotItem.vb.sigRangeChanged.connect(mkrefs) + app.focusChanged.connect(w.plotItem.vb.invertY) + + # return weakrefs to a bunch of objects that should die when the scope exits. + return mkrefs(w, c, data, w.plotItem, w.plotItem.vb, w.plotItem.getMenu(), w.plotItem.getAxis('left')) + + for i in range(5): + assert_alldead(mkobjs()) + +def test_ImageView(): + def mkobjs(): + iv = pg.ImageView() + data = np.zeros((10,10,5)) + iv.setImage(data) + + return mkrefs(iv, iv.imageItem, iv.view, iv.ui.histogram, data) + + for i in range(5): + assert_alldead(mkobjs()) + +def test_GraphicsWindow(): + def mkobjs(): + w = pg.GraphicsWindow() + p1 = w.addPlot() + v1 = w.addViewBox() + return mkrefs(w, p1, v1) + + for i in range(5): + assert_alldead(mkobjs()) + + + +if __name__ == '__main__': + ot = test_PlotItem() diff --git a/papi/pyqtgraph/tests/test_srttransform3d.py b/papi/pyqtgraph/tests/test_srttransform3d.py new file mode 100644 index 00000000..88aa1581 --- /dev/null +++ b/papi/pyqtgraph/tests/test_srttransform3d.py @@ -0,0 +1,39 @@ +import pyqtgraph as pg +from pyqtgraph.Qt import QtCore, QtGui +import numpy as np +from numpy.testing import assert_array_almost_equal, assert_almost_equal + +testPoints = np.array([ + [0, 0, 0], + [1, 0, 0], + [0, 1, 0], + [0, 0, 1], + [-1, -1, 0], + [0, -1, -1]]) + + +def testMatrix(): + """ + SRTTransform3D => Transform3D => SRTTransform3D + """ + tr = pg.SRTTransform3D() + tr.setRotate(45, (0, 0, 1)) + tr.setScale(0.2, 0.4, 1) + tr.setTranslate(10, 20, 40) + assert tr.getRotation() == (45, QtGui.QVector3D(0, 0, 1)) + assert tr.getScale() == QtGui.QVector3D(0.2, 0.4, 1) + assert tr.getTranslation() == QtGui.QVector3D(10, 20, 40) + + tr2 = pg.Transform3D(tr) + assert np.all(tr.matrix() == tr2.matrix()) + + # This is the most important test: + # The transition from Transform3D to SRTTransform3D is a tricky one. + tr3 = pg.SRTTransform3D(tr2) + assert_array_almost_equal(tr.matrix(), tr3.matrix()) + assert_almost_equal(tr3.getRotation()[0], tr.getRotation()[0]) + assert_array_almost_equal(tr3.getRotation()[1], tr.getRotation()[1]) + assert_array_almost_equal(tr3.getScale(), tr.getScale()) + assert_array_almost_equal(tr3.getTranslation(), tr.getTranslation()) + + diff --git a/papi/pyqtgraph/tests/test_stability.py b/papi/pyqtgraph/tests/test_stability.py new file mode 100644 index 00000000..a64e30e4 --- /dev/null +++ b/papi/pyqtgraph/tests/test_stability.py @@ -0,0 +1,160 @@ +""" +PyQt/PySide stress test: + +Create lots of random widgets and graphics items, connect them together randomly, +the tear them down repeatedly. + +The purpose of this is to attempt to generate segmentation faults. +""" +from PyQt4.QtTest import QTest +import pyqtgraph as pg +from random import seed, randint +import sys, gc, weakref + +app = pg.mkQApp() + +seed(12345) + +widgetTypes = [ + pg.PlotWidget, + pg.ImageView, + pg.GraphicsView, + pg.QtGui.QWidget, + pg.QtGui.QTreeWidget, + pg.QtGui.QPushButton, + ] + +itemTypes = [ + pg.PlotCurveItem, + pg.ImageItem, + pg.PlotDataItem, + pg.ViewBox, + pg.QtGui.QGraphicsRectItem + ] + +widgets = [] +items = [] +allWidgets = weakref.WeakSet() + + +def crashtest(): + global allWidgets + try: + gc.disable() + actions = [ + createWidget, + #setParent, + forgetWidget, + showWidget, + processEvents, + #raiseException, + #addReference, + ] + + thread = WorkThread() + thread.start() + + while True: + try: + action = randItem(actions) + action() + print('[%d widgets alive, %d zombie]' % (len(allWidgets), len(allWidgets) - len(widgets))) + except KeyboardInterrupt: + print("Caught interrupt; send another to exit.") + try: + for i in range(100): + QTest.qWait(100) + except KeyboardInterrupt: + thread.terminate() + break + except: + sys.excepthook(*sys.exc_info()) + finally: + gc.enable() + + + +class WorkThread(pg.QtCore.QThread): + '''Intended to give the gc an opportunity to run from a non-gui thread.''' + def run(self): + i = 0 + while True: + i += 1 + if (i % 1000000) == 0: + print('--worker--') + + +def randItem(items): + return items[randint(0, len(items)-1)] + +def p(msg): + print(msg) + sys.stdout.flush() + +def createWidget(): + p('create widget') + global widgets, allWidgets + if len(widgets) > 50: + return + widget = randItem(widgetTypes)() + widget.setWindowTitle(widget.__class__.__name__) + widgets.append(widget) + allWidgets.add(widget) + p(" %s" % widget) + return widget + +def setParent(): + p('set parent') + global widgets + if len(widgets) < 2: + return + child = parent = None + while child is parent: + child = randItem(widgets) + parent = randItem(widgets) + p(" %s parent of %s" % (parent, child)) + child.setParent(parent) + +def forgetWidget(): + p('forget widget') + global widgets + if len(widgets) < 1: + return + widget = randItem(widgets) + p(' %s' % widget) + widgets.remove(widget) + +def showWidget(): + p('show widget') + global widgets + if len(widgets) < 1: + return + widget = randItem(widgets) + p(' %s' % widget) + widget.show() + +def processEvents(): + p('process events') + QTest.qWait(25) + +class TstException(Exception): + pass + +def raiseException(): + p('raise exception') + raise TstException("A test exception") + +def addReference(): + p('add reference') + global widgets + if len(widgets) < 1: + return + obj1 = randItem(widgets) + obj2 = randItem(widgets) + p(' %s -> %s' % (obj1, obj2)) + obj1._testref = obj2 + + + +if __name__ == '__main__': + test_stability() \ No newline at end of file diff --git a/papi/pyqtgraph/tests/uictest.ui b/papi/pyqtgraph/tests/uictest.ui new file mode 100644 index 00000000..25d14f2b --- /dev/null +++ b/papi/pyqtgraph/tests/uictest.ui @@ -0,0 +1,53 @@ + + + Form + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + 10 + 10 + 120 + 80 + + + + + + + 10 + 110 + 120 + 80 + + + + + + + PlotWidget + QWidget +
pyqtgraph
+ 1 +
+ + ImageView + QWidget +
pyqtgraph
+ 1 +
+
+ + +
diff --git a/papi/pyqtgraph/units.py b/papi/pyqtgraph/units.py new file mode 100644 index 00000000..6b7f3099 --- /dev/null +++ b/papi/pyqtgraph/units.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +## Very simple unit support: +## - creates variable names like 'mV' and 'kHz' +## - the value assigned to the variable corresponds to the scale prefix +## (mV = 0.001) +## - the actual units are purely cosmetic for making code clearer: +## +## x = 20*pA is identical to x = 20*1e-12 + +## No unicode variable names (μ,Ω) allowed until python 3 + +SI_PREFIXES = 'yzafpnum kMGTPEZY' +UNITS = 'm,s,g,W,J,V,A,F,T,Hz,Ohm,S,N,C,px,b,B'.split(',') +allUnits = {} + +def addUnit(p, n): + g = globals() + v = 1000**n + for u in UNITS: + g[p+u] = v + allUnits[p+u] = v + +for p in SI_PREFIXES: + if p == ' ': + p = '' + n = 0 + elif p == 'u': + n = -2 + else: + n = SI_PREFIXES.index(p) - 8 + + addUnit(p, n) + +cm = 0.01 + + + + + + +def evalUnits(unitStr): + """ + Evaluate a unit string into ([numerators,...], [denominators,...]) + Examples: + N m/s^2 => ([N, m], [s, s]) + A*s / V => ([A, s], [V,]) + """ + pass + +def formatUnits(units): + """ + Format a unit specification ([numerators,...], [denominators,...]) + into a string (this is the inverse of evalUnits) + """ + pass + +def simplify(units): + """ + Cancel units that appear in both numerator and denominator, then attempt to replace + groups of units with single units where possible (ie, J/s => W) + """ + pass + + \ No newline at end of file diff --git a/papi/pyqtgraph/util/__init__.py b/papi/pyqtgraph/util/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/papi/pyqtgraph/util/colorama/LICENSE.txt b/papi/pyqtgraph/util/colorama/LICENSE.txt new file mode 100644 index 00000000..5f567799 --- /dev/null +++ b/papi/pyqtgraph/util/colorama/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright (c) 2010 Jonathan Hartley +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holders, nor those of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/papi/pyqtgraph/util/colorama/README.txt b/papi/pyqtgraph/util/colorama/README.txt new file mode 100644 index 00000000..8910ba5b --- /dev/null +++ b/papi/pyqtgraph/util/colorama/README.txt @@ -0,0 +1,304 @@ +Download and docs: + http://pypi.python.org/pypi/colorama +Development: + http://code.google.com/p/colorama +Discussion group: + https://groups.google.com/forum/#!forum/python-colorama + +Description +=========== + +Makes ANSI escape character sequences for producing colored terminal text and +cursor positioning work under MS Windows. + +ANSI escape character sequences have long been used to produce colored terminal +text and cursor positioning on Unix and Macs. Colorama makes this work on +Windows, too, by wrapping stdout, stripping ANSI sequences it finds (which +otherwise show up as gobbledygook in your output), and converting them into the +appropriate win32 calls to modify the state of the terminal. On other platforms, +Colorama does nothing. + +Colorama also provides some shortcuts to help generate ANSI sequences +but works fine in conjunction with any other ANSI sequence generation library, +such as Termcolor (http://pypi.python.org/pypi/termcolor.) + +This has the upshot of providing a simple cross-platform API for printing +colored terminal text from Python, and has the happy side-effect that existing +applications or libraries which use ANSI sequences to produce colored output on +Linux or Macs can now also work on Windows, simply by calling +``colorama.init()``. + +An alternative approach is to install 'ansi.sys' on Windows machines, which +provides the same behaviour for all applications running in terminals. Colorama +is intended for situations where that isn't easy (e.g. maybe your app doesn't +have an installer.) + +Demo scripts in the source code repository prints some colored text using +ANSI sequences. Compare their output under Gnome-terminal's built in ANSI +handling, versus on Windows Command-Prompt using Colorama: + +.. image:: http://colorama.googlecode.com/hg/screenshots/ubuntu-demo.png + :width: 661 + :height: 357 + :alt: ANSI sequences on Ubuntu under gnome-terminal. + +.. image:: http://colorama.googlecode.com/hg/screenshots/windows-demo.png + :width: 668 + :height: 325 + :alt: Same ANSI sequences on Windows, using Colorama. + +These screengrabs show that Colorama on Windows does not support ANSI 'dim +text': it looks the same as 'normal text'. + + +License +======= + +Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. + + +Dependencies +============ + +None, other than Python. Tested on Python 2.5.5, 2.6.5, 2.7, 3.1.2, and 3.2 + +Usage +===== + +Initialisation +-------------- + +Applications should initialise Colorama using:: + + from colorama import init + init() + +If you are on Windows, the call to ``init()`` will start filtering ANSI escape +sequences out of any text sent to stdout or stderr, and will replace them with +equivalent Win32 calls. + +Calling ``init()`` has no effect on other platforms (unless you request other +optional functionality, see keyword args below.) The intention is that +applications can call ``init()`` unconditionally on all platforms, after which +ANSI output should just work. + +To stop using colorama before your program exits, simply call ``deinit()``. +This will restore stdout and stderr to their original values, so that Colorama +is disabled. To start using Colorama again, call ``reinit()``, which wraps +stdout and stderr again, but is cheaper to call than doing ``init()`` all over +again. + + +Colored Output +-------------- + +Cross-platform printing of colored text can then be done using Colorama's +constant shorthand for ANSI escape sequences:: + + from colorama import Fore, Back, Style + print(Fore.RED + 'some red text') + print(Back.GREEN + 'and with a green background') + print(Style.DIM + 'and in dim text') + print(Fore.RESET + Back.RESET + Style.RESET_ALL) + print('back to normal now') + +or simply by manually printing ANSI sequences from your own code:: + + print('/033[31m' + 'some red text') + print('/033[30m' # and reset to default color) + +or Colorama can be used happily in conjunction with existing ANSI libraries +such as Termcolor:: + + from colorama import init + from termcolor import colored + + # use Colorama to make Termcolor work on Windows too + init() + + # then use Termcolor for all colored text output + print(colored('Hello, World!', 'green', 'on_red')) + +Available formatting constants are:: + + Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Back: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET. + Style: DIM, NORMAL, BRIGHT, RESET_ALL + +Style.RESET_ALL resets foreground, background and brightness. Colorama will +perform this reset automatically on program exit. + + +Cursor Positioning +------------------ + +ANSI codes to reposition the cursor are supported. See demos/demo06.py for +an example of how to generate them. + + +Init Keyword Args +----------------- + +``init()`` accepts some kwargs to override default behaviour. + +init(autoreset=False): + If you find yourself repeatedly sending reset sequences to turn off color + changes at the end of every print, then ``init(autoreset=True)`` will + automate that:: + + from colorama import init + init(autoreset=True) + print(Fore.RED + 'some red text') + print('automatically back to default color again') + +init(strip=None): + Pass ``True`` or ``False`` to override whether ansi codes should be + stripped from the output. The default behaviour is to strip if on Windows. + +init(convert=None): + Pass ``True`` or ``False`` to override whether to convert ansi codes in the + output into win32 calls. The default behaviour is to convert if on Windows + and output is to a tty (terminal). + +init(wrap=True): + On Windows, colorama works by replacing ``sys.stdout`` and ``sys.stderr`` + with proxy objects, which override the .write() method to do their work. If + this wrapping causes you problems, then this can be disabled by passing + ``init(wrap=False)``. The default behaviour is to wrap if autoreset or + strip or convert are True. + + When wrapping is disabled, colored printing on non-Windows platforms will + continue to work as normal. To do cross-platform colored output, you can + use Colorama's ``AnsiToWin32`` proxy directly:: + + import sys + from colorama import init, AnsiToWin32 + init(wrap=False) + stream = AnsiToWin32(sys.stderr).stream + + # Python 2 + print >>stream, Fore.BLUE + 'blue text on stderr' + + # Python 3 + print(Fore.BLUE + 'blue text on stderr', file=stream) + + +Status & Known Problems +======================= + +I've personally only tested it on WinXP (CMD, Console2), Ubuntu +(gnome-terminal, xterm), and OSX. + +Some presumably valid ANSI sequences aren't recognised (see details below) +but to my knowledge nobody has yet complained about this. Puzzling. + +See outstanding issues and wishlist at: +http://code.google.com/p/colorama/issues/list + +If anything doesn't work for you, or doesn't do what you expected or hoped for, +I'd love to hear about it on that issues list, would be delighted by patches, +and would be happy to grant commit access to anyone who submits a working patch +or two. + + +Recognised ANSI Sequences +========================= + +ANSI sequences generally take the form: + + ESC [ ; ... + +Where is an integer, and is a single letter. Zero or more +params are passed to a . If no params are passed, it is generally +synonymous with passing a single zero. No spaces exist in the sequence, they +have just been inserted here to make it easy to read. + +The only ANSI sequences that colorama converts into win32 calls are:: + + ESC [ 0 m # reset all (colors and brightness) + ESC [ 1 m # bright + ESC [ 2 m # dim (looks same as normal brightness) + ESC [ 22 m # normal brightness + + # FOREGROUND: + ESC [ 30 m # black + ESC [ 31 m # red + ESC [ 32 m # green + ESC [ 33 m # yellow + ESC [ 34 m # blue + ESC [ 35 m # magenta + ESC [ 36 m # cyan + ESC [ 37 m # white + ESC [ 39 m # reset + + # BACKGROUND + ESC [ 40 m # black + ESC [ 41 m # red + ESC [ 42 m # green + ESC [ 43 m # yellow + ESC [ 44 m # blue + ESC [ 45 m # magenta + ESC [ 46 m # cyan + ESC [ 47 m # white + ESC [ 49 m # reset + + # cursor positioning + ESC [ y;x H # position cursor at x across, y down + + # clear the screen + ESC [ mode J # clear the screen. Only mode 2 (clear entire screen) + # is supported. It should be easy to add other modes, + # let me know if that would be useful. + +Multiple numeric params to the 'm' command can be combined into a single +sequence, eg:: + + ESC [ 36 ; 45 ; 1 m # bright cyan text on magenta background + +All other ANSI sequences of the form ``ESC [ ; ... `` +are silently stripped from the output on Windows. + +Any other form of ANSI sequence, such as single-character codes or alternative +initial characters, are not recognised nor stripped. It would be cool to add +them though. Let me know if it would be useful for you, via the issues on +google code. + + +Development +=========== + +Help and fixes welcome! Ask Jonathan for commit rights, you'll get them. + +Running tests requires: + +- Michael Foord's 'mock' module to be installed. +- Tests are written using the 2010 era updates to 'unittest', and require to + be run either using Python2.7 or greater, or else to have Michael Foord's + 'unittest2' module installed. + +unittest2 test discovery doesn't work for colorama, so I use 'nose':: + + nosetests -s + +The -s is required because 'nosetests' otherwise applies a proxy of its own to +stdout, which confuses the unit tests. + + +Contact +======= + +Created by Jonathan Hartley, tartley@tartley.com + + +Thanks +====== +| Ben Hoyt, for a magnificent fix under 64-bit Windows. +| Jesse@EmptySquare for submitting a fix for examples in the README. +| User 'jamessp', an observant documentation fix for cursor positioning. +| User 'vaal1239', Dave Mckee & Lackner Kristof for a tiny but much-needed Win7 fix. +| Julien Stuyck, for wisely suggesting Python3 compatible updates to README. +| Daniel Griffith for multiple fabulous patches. +| Oscar Lesta for valuable fix to stop ANSI chars being sent to non-tty output. +| Roger Binns, for many suggestions, valuable feedback, & bug reports. +| Tim Golden for thought and much appreciated feedback on the initial idea. + diff --git a/papi/pyqtgraph/util/colorama/__init__.py b/papi/pyqtgraph/util/colorama/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/papi/pyqtgraph/util/colorama/win32.py b/papi/pyqtgraph/util/colorama/win32.py new file mode 100644 index 00000000..c86ce180 --- /dev/null +++ b/papi/pyqtgraph/util/colorama/win32.py @@ -0,0 +1,137 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. + +# from winbase.h +STDOUT = -11 +STDERR = -12 + +try: + from ctypes import windll + from ctypes import wintypes +except ImportError: + windll = None + SetConsoleTextAttribute = lambda *_: None +else: + from ctypes import ( + byref, Structure, c_char, c_short, c_int, c_uint32, c_ushort, c_void_p, POINTER + ) + + class CONSOLE_SCREEN_BUFFER_INFO(Structure): + """struct in wincon.h.""" + _fields_ = [ + ("dwSize", wintypes._COORD), + ("dwCursorPosition", wintypes._COORD), + ("wAttributes", wintypes.WORD), + ("srWindow", wintypes.SMALL_RECT), + ("dwMaximumWindowSize", wintypes._COORD), + ] + def __str__(self): + return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( + self.dwSize.Y, self.dwSize.X + , self.dwCursorPosition.Y, self.dwCursorPosition.X + , self.wAttributes + , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right + , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X + ) + + _GetStdHandle = windll.kernel32.GetStdHandle + _GetStdHandle.argtypes = [ + wintypes.DWORD, + ] + _GetStdHandle.restype = wintypes.HANDLE + + _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo + _GetConsoleScreenBufferInfo.argtypes = [ + wintypes.HANDLE, + c_void_p, + #POINTER(CONSOLE_SCREEN_BUFFER_INFO), + ] + _GetConsoleScreenBufferInfo.restype = wintypes.BOOL + + _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute + _SetConsoleTextAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + ] + _SetConsoleTextAttribute.restype = wintypes.BOOL + + _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition + _SetConsoleCursorPosition.argtypes = [ + wintypes.HANDLE, + c_int, + #wintypes._COORD, + ] + _SetConsoleCursorPosition.restype = wintypes.BOOL + + _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA + _FillConsoleOutputCharacterA.argtypes = [ + wintypes.HANDLE, + c_char, + wintypes.DWORD, + wintypes._COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputCharacterA.restype = wintypes.BOOL + + _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute + _FillConsoleOutputAttribute.argtypes = [ + wintypes.HANDLE, + wintypes.WORD, + wintypes.DWORD, + c_int, + #wintypes._COORD, + POINTER(wintypes.DWORD), + ] + _FillConsoleOutputAttribute.restype = wintypes.BOOL + + handles = { + STDOUT: _GetStdHandle(STDOUT), + STDERR: _GetStdHandle(STDERR), + } + + def GetConsoleScreenBufferInfo(stream_id=STDOUT): + handle = handles[stream_id] + csbi = CONSOLE_SCREEN_BUFFER_INFO() + success = _GetConsoleScreenBufferInfo( + handle, byref(csbi)) + return csbi + + def SetConsoleTextAttribute(stream_id, attrs): + handle = handles[stream_id] + return _SetConsoleTextAttribute(handle, attrs) + + def SetConsoleCursorPosition(stream_id, position): + position = wintypes._COORD(*position) + # If the position is out of range, do nothing. + if position.Y <= 0 or position.X <= 0: + return + # Adjust for Windows' SetConsoleCursorPosition: + # 1. being 0-based, while ANSI is 1-based. + # 2. expecting (x,y), while ANSI uses (y,x). + adjusted_position = wintypes._COORD(position.Y - 1, position.X - 1) + # Adjust for viewport's scroll position + sr = GetConsoleScreenBufferInfo(STDOUT).srWindow + adjusted_position.Y += sr.Top + adjusted_position.X += sr.Left + # Resume normal processing + handle = handles[stream_id] + return _SetConsoleCursorPosition(handle, adjusted_position) + + def FillConsoleOutputCharacter(stream_id, char, length, start): + handle = handles[stream_id] + char = c_char(char) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + success = _FillConsoleOutputCharacterA( + handle, char, length, start, byref(num_written)) + return num_written.value + + def FillConsoleOutputAttribute(stream_id, attr, length, start): + ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' + handle = handles[stream_id] + attribute = wintypes.WORD(attr) + length = wintypes.DWORD(length) + num_written = wintypes.DWORD(0) + # Note that this is hard-coded for ANSI (vs wide) bytes. + return _FillConsoleOutputAttribute( + handle, attribute, length, start, byref(num_written)) diff --git a/papi/pyqtgraph/util/colorama/winterm.py b/papi/pyqtgraph/util/colorama/winterm.py new file mode 100644 index 00000000..9c1c8185 --- /dev/null +++ b/papi/pyqtgraph/util/colorama/winterm.py @@ -0,0 +1,120 @@ +# Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. +from . import win32 + + +# from wincon.h +class WinColor(object): + BLACK = 0 + BLUE = 1 + GREEN = 2 + CYAN = 3 + RED = 4 + MAGENTA = 5 + YELLOW = 6 + GREY = 7 + +# from wincon.h +class WinStyle(object): + NORMAL = 0x00 # dim text, dim background + BRIGHT = 0x08 # bright text, dim background + + +class WinTerm(object): + + def __init__(self): + self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes + self.set_attrs(self._default) + self._default_fore = self._fore + self._default_back = self._back + self._default_style = self._style + + def get_attrs(self): + return self._fore + self._back * 16 + self._style + + def set_attrs(self, value): + self._fore = value & 7 + self._back = (value >> 4) & 7 + self._style = value & WinStyle.BRIGHT + + def reset_all(self, on_stderr=None): + self.set_attrs(self._default) + self.set_console(attrs=self._default) + + def fore(self, fore=None, on_stderr=False): + if fore is None: + fore = self._default_fore + self._fore = fore + self.set_console(on_stderr=on_stderr) + + def back(self, back=None, on_stderr=False): + if back is None: + back = self._default_back + self._back = back + self.set_console(on_stderr=on_stderr) + + def style(self, style=None, on_stderr=False): + if style is None: + style = self._default_style + self._style = style + self.set_console(on_stderr=on_stderr) + + def set_console(self, attrs=None, on_stderr=False): + if attrs is None: + attrs = self.get_attrs() + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleTextAttribute(handle, attrs) + + def get_position(self, handle): + position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition + # Because Windows coordinates are 0-based, + # and win32.SetConsoleCursorPosition expects 1-based. + position.X += 1 + position.Y += 1 + return position + + def set_cursor_position(self, position=None, on_stderr=False): + if position is None: + #I'm not currently tracking the position, so there is no default. + #position = self.get_position() + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + win32.SetConsoleCursorPosition(handle, position) + + def cursor_up(self, num_rows=0, on_stderr=False): + if num_rows == 0: + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + position = self.get_position(handle) + adjusted_position = (position.Y - num_rows, position.X) + self.set_cursor_position(adjusted_position, on_stderr) + + def erase_data(self, mode=0, on_stderr=False): + # 0 (or None) should clear from the cursor to the end of the screen. + # 1 should clear from the cursor to the beginning of the screen. + # 2 should clear the entire screen. (And maybe move cursor to (1,1)?) + # + # At the moment, I only support mode 2. From looking at the API, it + # should be possible to calculate a different number of bytes to clear, + # and to do so relative to the cursor position. + if mode[0] not in (2,): + return + handle = win32.STDOUT + if on_stderr: + handle = win32.STDERR + # here's where we'll home the cursor + coord_screen = win32.COORD(0,0) + csbi = win32.GetConsoleScreenBufferInfo(handle) + # get the number of character cells in the current buffer + dw_con_size = csbi.dwSize.X * csbi.dwSize.Y + # fill the entire screen with blanks + win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen) + # now set the buffer's attributes accordingly + win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen ) + # put the cursor at (0, 0) + win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y)) diff --git a/papi/pyqtgraph/util/cprint.py b/papi/pyqtgraph/util/cprint.py new file mode 100644 index 00000000..e88bfd1a --- /dev/null +++ b/papi/pyqtgraph/util/cprint.py @@ -0,0 +1,101 @@ +""" +Cross-platform color text printing + +Based on colorama (see pyqtgraph/util/colorama/README.txt) +""" +import sys, re + +from .colorama.winterm import WinTerm, WinColor, WinStyle +from .colorama.win32 import windll + +_WIN = sys.platform.startswith('win') +if windll is not None: + winterm = WinTerm() +else: + _WIN = False + +def winset(reset=False, fore=None, back=None, style=None, stderr=False): + if reset: + winterm.reset_all() + if fore is not None: + winterm.fore(fore, stderr) + if back is not None: + winterm.back(back, stderr) + if style is not None: + winterm.style(style, stderr) + +ANSI = {} +WIN = {} +for i,color in enumerate(['BLACK', 'RED', 'GREEN', 'YELLOW', 'BLUE', 'MAGENTA', 'CYAN', 'WHITE']): + globals()[color] = i + globals()['BR_' + color] = i + 8 + globals()['BACK_' + color] = i + 40 + ANSI[i] = "\033[%dm" % (30+i) + ANSI[i+8] = "\033[2;%dm" % (30+i) + ANSI[i+40] = "\033[%dm" % (40+i) + color = 'GREY' if color == 'WHITE' else color + WIN[i] = {'fore': getattr(WinColor, color), 'style': WinStyle.NORMAL} + WIN[i+8] = {'fore': getattr(WinColor, color), 'style': WinStyle.BRIGHT} + WIN[i+40] = {'back': getattr(WinColor, color)} + +RESET = -1 +ANSI[RESET] = "\033[0m" +WIN[RESET] = {'reset': True} + + +def cprint(stream, *args, **kwds): + """ + Print with color. Examples:: + + # colors are BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE + cprint('stdout', RED, 'This is in red. ', RESET, 'and this is normal\n') + + # Adding BR_ before the color manes it bright + cprint('stdout', BR_GREEN, 'This is bright green.\n', RESET) + + # Adding BACK_ changes background color + cprint('stderr', BACK_BLUE, WHITE, 'This is white-on-blue.', -1) + + # Integers 0-7 for normal, 8-15 for bright, and 40-47 for background. + # -1 to reset. + cprint('stderr', 1, 'This is in red.', -1) + + """ + if isinstance(stream, basestring): + stream = kwds.get('stream', 'stdout') + err = stream == 'stderr' + stream = getattr(sys, stream) + else: + err = kwds.get('stderr', False) + + if hasattr(stream, 'isatty') and stream.isatty(): + if _WIN: + # convert to win32 calls + for arg in args: + if isinstance(arg, basestring): + stream.write(arg) + else: + kwds = WIN[arg] + winset(stderr=err, **kwds) + else: + # convert to ANSI + for arg in args: + if isinstance(arg, basestring): + stream.write(arg) + else: + stream.write(ANSI[arg]) + else: + # ignore colors + for arg in args: + if isinstance(arg, basestring): + stream.write(arg) + +def cout(*args): + """Shorthand for cprint('stdout', ...)""" + cprint('stdout', *args) + +def cerr(*args): + """Shorthand for cprint('stderr', ...)""" + cprint('stderr', *args) + + diff --git a/papi/pyqtgraph/util/garbage_collector.py b/papi/pyqtgraph/util/garbage_collector.py new file mode 100644 index 00000000..979e66c5 --- /dev/null +++ b/papi/pyqtgraph/util/garbage_collector.py @@ -0,0 +1,50 @@ +import gc + +from ..Qt import QtCore + +class GarbageCollector(object): + ''' + Disable automatic garbage collection and instead collect manually + on a timer. + + This is done to ensure that garbage collection only happens in the GUI + thread, as otherwise Qt can crash. + + Credit: Erik Janssens + Source: http://pydev.blogspot.com/2014/03/should-python-garbage-collector-be.html + ''' + + def __init__(self, interval=1.0, debug=False): + self.debug = debug + if debug: + gc.set_debug(gc.DEBUG_LEAK) + + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.check) + + self.threshold = gc.get_threshold() + gc.disable() + self.timer.start(interval * 1000) + + def check(self): + #return self.debug_cycles() # uncomment to just debug cycles + l0, l1, l2 = gc.get_count() + if self.debug: + print('gc_check called:', l0, l1, l2) + if l0 > self.threshold[0]: + num = gc.collect(0) + if self.debug: + print('collecting gen 0, found: %d unreachable' % num) + if l1 > self.threshold[1]: + num = gc.collect(1) + if self.debug: + print('collecting gen 1, found: %d unreachable' % num) + if l2 > self.threshold[2]: + num = gc.collect(2) + if self.debug: + print('collecting gen 2, found: %d unreachable' % num) + + def debug_cycles(self): + gc.collect() + for obj in gc.garbage: + print (obj, repr(obj), type(obj)) diff --git a/papi/pyqtgraph/util/lru_cache.py b/papi/pyqtgraph/util/lru_cache.py new file mode 100644 index 00000000..9c04abf3 --- /dev/null +++ b/papi/pyqtgraph/util/lru_cache.py @@ -0,0 +1,121 @@ +import operator +import sys +import itertools + + +_IS_PY3 = sys.version_info[0] == 3 + +class LRUCache(object): + ''' + This LRU cache should be reasonable for short collections (until around 100 items), as it does a + sort on the items if the collection would become too big (so, it is very fast for getting and + setting but when its size would become higher than the max size it does one sort based on the + internal time to decide which items should be removed -- which should be Ok if the resizeTo + isn't too close to the maxSize so that it becomes an operation that doesn't happen all the + time). + ''' + + def __init__(self, maxSize=100, resizeTo=70): + ''' + ============== ========================================================= + **Arguments:** + maxSize (int) This is the maximum size of the cache. When some + item is added and the cache would become bigger than + this, it's resized to the value passed on resizeTo. + resizeTo (int) When a resize operation happens, this is the size + of the final cache. + ============== ========================================================= + ''' + assert resizeTo < maxSize + self.maxSize = maxSize + self.resizeTo = resizeTo + self._counter = 0 + self._dict = {} + if _IS_PY3: + self._nextTime = itertools.count(0).__next__ + else: + self._nextTime = itertools.count(0).next + + def __getitem__(self, key): + item = self._dict[key] + item[2] = self._nextTime() + return item[1] + + def __len__(self): + return len(self._dict) + + def __setitem__(self, key, value): + item = self._dict.get(key) + if item is None: + if len(self._dict) + 1 > self.maxSize: + self._resizeTo() + + item = [key, value, self._nextTime()] + self._dict[key] = item + else: + item[1] = value + item[2] = self._nextTime() + + def __delitem__(self, key): + del self._dict[key] + + def get(self, key, default=None): + try: + return self[key] + except KeyError: + return default + + def clear(self): + self._dict.clear() + + if _IS_PY3: + def values(self): + return [i[1] for i in self._dict.values()] + + def keys(self): + return [x[0] for x in self._dict.values()] + + def _resizeTo(self): + ordered = sorted(self._dict.values(), key=operator.itemgetter(2))[:self.resizeTo] + for i in ordered: + del self._dict[i[0]] + + def iteritems(self, accessTime=False): + ''' + :param bool accessTime: + If True sorts the returned items by the internal access time. + ''' + if accessTime: + for x in sorted(self._dict.values(), key=operator.itemgetter(2)): + yield x[0], x[1] + else: + for x in self._dict.items(): + yield x[0], x[1] + + else: + def values(self): + return [i[1] for i in self._dict.itervalues()] + + def keys(self): + return [x[0] for x in self._dict.itervalues()] + + + def _resizeTo(self): + ordered = sorted(self._dict.itervalues(), key=operator.itemgetter(2))[:self.resizeTo] + for i in ordered: + del self._dict[i[0]] + + def iteritems(self, accessTime=False): + ''' + ============= ====================================================== + **Arguments** + accessTime (bool) If True sorts the returned items by the + internal access time. + ============= ====================================================== + ''' + if accessTime: + for x in sorted(self._dict.itervalues(), key=operator.itemgetter(2)): + yield x[0], x[1] + else: + for x in self._dict.iteritems(): + yield x[0], x[1] diff --git a/papi/pyqtgraph/util/mutex.py b/papi/pyqtgraph/util/mutex.py new file mode 100644 index 00000000..4a193127 --- /dev/null +++ b/papi/pyqtgraph/util/mutex.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtCore +import traceback + +class Mutex(QtCore.QMutex): + """ + Subclass of QMutex that provides useful debugging information during + deadlocks--tracebacks are printed for both the code location that is + attempting to lock the mutex as well as the location that has already + acquired the lock. + + Also provides __enter__ and __exit__ methods for use in "with" statements. + """ + def __init__(self, *args, **kargs): + if kargs.get('recursive', False): + args = (QtCore.QMutex.Recursive,) + QtCore.QMutex.__init__(self, *args) + self.l = QtCore.QMutex() ## for serializing access to self.tb + self.tb = [] + self.debug = True ## True to enable debugging functions + + def tryLock(self, timeout=None, id=None): + if timeout is None: + locked = QtCore.QMutex.tryLock(self) + else: + locked = QtCore.QMutex.tryLock(self, timeout) + + if self.debug and locked: + self.l.lock() + try: + if id is None: + self.tb.append(''.join(traceback.format_stack()[:-1])) + else: + self.tb.append(" " + str(id)) + #print 'trylock', self, len(self.tb) + finally: + self.l.unlock() + return locked + + def lock(self, id=None): + c = 0 + waitTime = 5000 # in ms + while True: + if self.tryLock(waitTime, id): + break + c += 1 + if self.debug: + self.l.lock() + try: + print("Waiting for mutex lock (%0.1f sec). Traceback follows:" + % (c*waitTime/1000.)) + traceback.print_stack() + if len(self.tb) > 0: + print("Mutex is currently locked from:\n") + print(self.tb[-1]) + else: + print("Mutex is currently locked from [???]") + finally: + self.l.unlock() + #print 'lock', self, len(self.tb) + + def unlock(self): + QtCore.QMutex.unlock(self) + if self.debug: + self.l.lock() + try: + #print 'unlock', self, len(self.tb) + if len(self.tb) > 0: + self.tb.pop() + else: + raise Exception("Attempt to unlock mutex before it has been locked") + finally: + self.l.unlock() + + def depth(self): + self.l.lock() + n = len(self.tb) + self.l.unlock() + return n + + def traceback(self): + self.l.lock() + try: + ret = self.tb[:] + finally: + self.l.unlock() + return ret + + def __exit__(self, *args): + self.unlock() + + def __enter__(self): + self.lock() + return self \ No newline at end of file diff --git a/papi/pyqtgraph/util/pil_fix.py b/papi/pyqtgraph/util/pil_fix.py new file mode 100644 index 00000000..da1c52b3 --- /dev/null +++ b/papi/pyqtgraph/util/pil_fix.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +""" +Importing this module installs support for 16-bit images in PIL. +This works by patching objects in the PIL namespace; no files are +modified. +""" + +from PIL import Image + +if Image.VERSION == '1.1.7': + Image._MODE_CONV["I;16"] = ('%su2' % Image._ENDIAN, None) + Image._fromarray_typemap[((1, 1), " ndmax: + raise ValueError("Too many dimensions.") + + size = shape[:2][::-1] + if strides is not None: + obj = obj.tostring() + + return frombuffer(mode, size, obj, "raw", mode, 0, 1) + + Image.fromarray=fromarray \ No newline at end of file diff --git a/papi/pyqtgraph/util/tests/test_lru_cache.py b/papi/pyqtgraph/util/tests/test_lru_cache.py new file mode 100644 index 00000000..c0cf9f8a --- /dev/null +++ b/papi/pyqtgraph/util/tests/test_lru_cache.py @@ -0,0 +1,50 @@ +from pyqtgraph.util.lru_cache import LRUCache + +def testLRU(): + lru = LRUCache(2, 1) + # check twice + checkLru(lru) + checkLru(lru) + +def checkLru(lru): + lru[1] = 1 + lru[2] = 2 + lru[3] = 3 + + assert len(lru) == 2 + assert set([2, 3]) == set(lru.keys()) + assert set([2, 3]) == set(lru.values()) + + lru[2] = 2 + assert set([2, 3]) == set(lru.values()) + + lru[1] = 1 + set([2, 1]) == set(lru.values()) + + #Iterates from the used in the last access to others based on access time. + assert [(2, 2), (1, 1)] == list(lru.iteritems(accessTime=True)) + lru[2] = 2 + assert [(1, 1), (2, 2)] == list(lru.iteritems(accessTime=True)) + + del lru[2] + assert [(1, 1), ] == list(lru.iteritems(accessTime=True)) + + lru[2] = 2 + assert [(1, 1), (2, 2)] == list(lru.iteritems(accessTime=True)) + + _a = lru[1] + assert [(2, 2), (1, 1)] == list(lru.iteritems(accessTime=True)) + + _a = lru[2] + assert [(1, 1), (2, 2)] == list(lru.iteritems(accessTime=True)) + + assert lru.get(2) == 2 + assert lru.get(3) == None + assert [(1, 1), (2, 2)] == list(lru.iteritems(accessTime=True)) + + lru.clear() + assert [] == list(lru.iteritems()) + + +if __name__ == '__main__': + testLRU() diff --git a/papi/pyqtgraph/widgets/BusyCursor.py b/papi/pyqtgraph/widgets/BusyCursor.py new file mode 100644 index 00000000..d99fe589 --- /dev/null +++ b/papi/pyqtgraph/widgets/BusyCursor.py @@ -0,0 +1,24 @@ +from ..Qt import QtGui, QtCore + +__all__ = ['BusyCursor'] + +class BusyCursor(object): + """Class for displaying a busy mouse cursor during long operations. + Usage:: + + with pyqtgraph.BusyCursor(): + doLongOperation() + + May be nested. + """ + active = [] + + def __enter__(self): + QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) + BusyCursor.active.append(self) + + def __exit__(self, *args): + BusyCursor.active.pop(-1) + if len(BusyCursor.active) == 0: + QtGui.QApplication.restoreOverrideCursor() + \ No newline at end of file diff --git a/papi/pyqtgraph/widgets/CheckTable.py b/papi/pyqtgraph/widgets/CheckTable.py new file mode 100644 index 00000000..22015126 --- /dev/null +++ b/papi/pyqtgraph/widgets/CheckTable.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore +from . import VerticalLabel + +__all__ = ['CheckTable'] + +class CheckTable(QtGui.QWidget): + + sigStateChanged = QtCore.Signal(object, object, object) # (row, col, state) + + def __init__(self, columns): + QtGui.QWidget.__init__(self) + self.layout = QtGui.QGridLayout() + self.layout.setSpacing(0) + self.setLayout(self.layout) + self.headers = [] + self.columns = columns + col = 1 + for c in columns: + label = VerticalLabel.VerticalLabel(c, orientation='vertical') + self.headers.append(label) + self.layout.addWidget(label, 0, col) + col += 1 + + self.rowNames = [] + self.rowWidgets = [] + self.oldRows = {} ## remember settings from removed rows; reapply if they reappear. + + + def updateRows(self, rows): + for r in self.rowNames[:]: + if r not in rows: + self.removeRow(r) + for r in rows: + if r not in self.rowNames: + self.addRow(r) + + def addRow(self, name): + label = QtGui.QLabel(name) + row = len(self.rowNames)+1 + self.layout.addWidget(label, row, 0) + checks = [] + col = 1 + for c in self.columns: + check = QtGui.QCheckBox('') + check.col = c + check.row = name + self.layout.addWidget(check, row, col) + checks.append(check) + if name in self.oldRows: + check.setChecked(self.oldRows[name][col]) + col += 1 + #QtCore.QObject.connect(check, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) + check.stateChanged.connect(self.checkChanged) + self.rowNames.append(name) + self.rowWidgets.append([label] + checks) + + def removeRow(self, name): + row = self.rowNames.index(name) + self.oldRows[name] = self.saveState()['rows'][row] ## save for later + self.rowNames.pop(row) + for w in self.rowWidgets[row]: + w.setParent(None) + #QtCore.QObject.disconnect(w, QtCore.SIGNAL('stateChanged(int)'), self.checkChanged) + if isinstance(w, QtGui.QCheckBox): + w.stateChanged.disconnect(self.checkChanged) + self.rowWidgets.pop(row) + for i in range(row, len(self.rowNames)): + widgets = self.rowWidgets[i] + for j in range(len(widgets)): + widgets[j].setParent(None) + self.layout.addWidget(widgets[j], i+1, j) + + def checkChanged(self, state): + check = QtCore.QObject.sender(self) + #self.emit(QtCore.SIGNAL('stateChanged'), check.row, check.col, state) + self.sigStateChanged.emit(check.row, check.col, state) + + def saveState(self): + rows = [] + for i in range(len(self.rowNames)): + row = [self.rowNames[i]] + [c.isChecked() for c in self.rowWidgets[i][1:]] + rows.append(row) + return {'cols': self.columns, 'rows': rows} + + def restoreState(self, state): + rows = [r[0] for r in state['rows']] + self.updateRows(rows) + for r in state['rows']: + rowNum = self.rowNames.index(r[0]) + for i in range(1, len(r)): + self.rowWidgets[rowNum][i].setChecked(r[i]) + diff --git a/papi/pyqtgraph/widgets/ColorButton.py b/papi/pyqtgraph/widgets/ColorButton.py new file mode 100644 index 00000000..a0bb0c8e --- /dev/null +++ b/papi/pyqtgraph/widgets/ColorButton.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore +from .. import functions as functions + +__all__ = ['ColorButton'] + +class ColorButton(QtGui.QPushButton): + """ + **Bases:** QtGui.QPushButton + + Button displaying a color and allowing the user to select a new color. + + ====================== ============================================================ + **Signals:** + sigColorChanging(self) emitted whenever a new color is picked in the color dialog + sigColorChanged(self) emitted when the selected color is accepted (user clicks OK) + ====================== ============================================================ + """ + sigColorChanging = QtCore.Signal(object) ## emitted whenever a new color is picked in the color dialog + sigColorChanged = QtCore.Signal(object) ## emitted when the selected color is accepted (user clicks OK) + + def __init__(self, parent=None, color=(128,128,128)): + QtGui.QPushButton.__init__(self, parent) + self.setColor(color) + self.colorDialog = QtGui.QColorDialog() + self.colorDialog.setOption(QtGui.QColorDialog.ShowAlphaChannel, True) + self.colorDialog.setOption(QtGui.QColorDialog.DontUseNativeDialog, True) + self.colorDialog.currentColorChanged.connect(self.dialogColorChanged) + self.colorDialog.rejected.connect(self.colorRejected) + self.colorDialog.colorSelected.connect(self.colorSelected) + #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('currentColorChanged(const QColor&)'), self.currentColorChanged) + #QtCore.QObject.connect(self.colorDialog, QtCore.SIGNAL('rejected()'), self.currentColorRejected) + self.clicked.connect(self.selectColor) + self.setMinimumHeight(15) + self.setMinimumWidth(15) + + def paintEvent(self, ev): + QtGui.QPushButton.paintEvent(self, ev) + p = QtGui.QPainter(self) + rect = self.rect().adjusted(6, 6, -6, -6) + ## draw white base, then texture for indicating transparency, then actual color + p.setBrush(functions.mkBrush('w')) + p.drawRect(rect) + p.setBrush(QtGui.QBrush(QtCore.Qt.DiagCrossPattern)) + p.drawRect(rect) + p.setBrush(functions.mkBrush(self._color)) + p.drawRect(rect) + p.end() + + def setColor(self, color, finished=True): + """Sets the button's color and emits both sigColorChanged and sigColorChanging.""" + self._color = functions.mkColor(color) + if finished: + self.sigColorChanged.emit(self) + else: + self.sigColorChanging.emit(self) + self.update() + + def selectColor(self): + self.origColor = self.color() + self.colorDialog.setCurrentColor(self.color()) + self.colorDialog.open() + + def dialogColorChanged(self, color): + if color.isValid(): + self.setColor(color, finished=False) + + def colorRejected(self): + self.setColor(self.origColor, finished=False) + + def colorSelected(self, color): + self.setColor(self._color, finished=True) + + def saveState(self): + return functions.colorTuple(self._color) + + def restoreState(self, state): + self.setColor(state) + + def color(self, mode='qcolor'): + color = functions.mkColor(self._color) + if mode == 'qcolor': + return color + elif mode == 'byte': + return (color.red(), color.green(), color.blue(), color.alpha()) + elif mode == 'float': + return (color.red()/255., color.green()/255., color.blue()/255., color.alpha()/255.) + + def widgetGroupInterface(self): + return (self.sigColorChanged, ColorButton.saveState, ColorButton.restoreState) + diff --git a/papi/pyqtgraph/widgets/ColorMapWidget.py b/papi/pyqtgraph/widgets/ColorMapWidget.py new file mode 100644 index 00000000..f6e28960 --- /dev/null +++ b/papi/pyqtgraph/widgets/ColorMapWidget.py @@ -0,0 +1,249 @@ +from ..Qt import QtGui, QtCore +from .. import parametertree as ptree +import numpy as np +from ..pgcollections import OrderedDict +from .. import functions as fn + +__all__ = ['ColorMapWidget'] + +class ColorMapWidget(ptree.ParameterTree): + """ + This class provides a widget allowing the user to customize color mapping + for multi-column data. Given a list of field names, the user may specify + multiple criteria for assigning colors to each record in a numpy record array. + Multiple criteria are evaluated and combined into a single color for each + record by user-defined compositing methods. + + For simpler color mapping using a single gradient editor, see + :class:`GradientWidget ` + """ + sigColorMapChanged = QtCore.Signal(object) + + def __init__(self, parent=None): + ptree.ParameterTree.__init__(self, parent=parent, showHeader=False) + + self.params = ColorMapParameter() + self.setParameters(self.params) + self.params.sigTreeStateChanged.connect(self.mapChanged) + + ## wrap a couple methods + self.setFields = self.params.setFields + self.map = self.params.map + + def mapChanged(self): + self.sigColorMapChanged.emit(self) + + def widgetGroupInterface(self): + return (self.sigColorMapChanged, self.saveState, self.restoreState) + + def saveState(self): + return self.params.saveState() + + def restoreState(self, state): + self.params.restoreState(state) + + +class ColorMapParameter(ptree.types.GroupParameter): + sigColorMapChanged = QtCore.Signal(object) + + def __init__(self): + self.fields = {} + ptree.types.GroupParameter.__init__(self, name='Color Map', addText='Add Mapping..', addList=[]) + self.sigTreeStateChanged.connect(self.mapChanged) + + def mapChanged(self): + self.sigColorMapChanged.emit(self) + + def addNew(self, name): + mode = self.fields[name].get('mode', 'range') + if mode == 'range': + item = RangeColorMapItem(name, self.fields[name]) + elif mode == 'enum': + item = EnumColorMapItem(name, self.fields[name]) + self.addChild(item) + return item + + def fieldNames(self): + return self.fields.keys() + + def setFields(self, fields): + """ + Set the list of fields to be used by the mapper. + + The format of *fields* is:: + + [ (fieldName, {options}), ... ] + + ============== ============================================================ + Field Options: + mode Either 'range' or 'enum' (default is range). For 'range', + The user may specify a gradient of colors to be applied + linearly across a specific range of values. For 'enum', + the user specifies a single color for each unique value + (see *values* option). + units String indicating the units of the data for this field. + values List of unique values for which the user may assign a + color when mode=='enum'. Optionally may specify a dict + instead {value: name}. + ============== ============================================================ + """ + self.fields = OrderedDict(fields) + #self.fields = fields + #self.fields.sort() + names = self.fieldNames() + self.setAddList(names) + + def map(self, data, mode='byte'): + """ + Return an array of colors corresponding to *data*. + + ============== ================================================================= + **Arguments:** + data A numpy record array where the fields in data.dtype match those + defined by a prior call to setFields(). + mode Either 'byte' or 'float'. For 'byte', the method returns an array + of dtype ubyte with values scaled 0-255. For 'float', colors are + returned as 0.0-1.0 float values. + ============== ================================================================= + """ + if isinstance(data, dict): + data = np.array([tuple(data.values())], dtype=[(k, float) for k in data.keys()]) + + colors = np.zeros((len(data),4)) + for item in self.children(): + if not item['Enabled']: + continue + chans = item.param('Channels..') + mask = np.empty((len(data), 4), dtype=bool) + for i,f in enumerate(['Red', 'Green', 'Blue', 'Alpha']): + mask[:,i] = chans[f] + + colors2 = item.map(data) + + op = item['Operation'] + if op == 'Add': + colors[mask] = colors[mask] + colors2[mask] + elif op == 'Multiply': + colors[mask] *= colors2[mask] + elif op == 'Overlay': + a = colors2[:,3:4] + c3 = colors * (1-a) + colors2 * a + c3[:,3:4] = colors[:,3:4] + (1-colors[:,3:4]) * a + colors = c3 + elif op == 'Set': + colors[mask] = colors2[mask] + + + colors = np.clip(colors, 0, 1) + if mode == 'byte': + colors = (colors * 255).astype(np.ubyte) + + return colors + + def saveState(self): + items = OrderedDict() + for item in self: + itemState = item.saveState(filter='user') + itemState['field'] = item.fieldName + items[item.name()] = itemState + state = {'fields': self.fields, 'items': items} + return state + + def restoreState(self, state): + if 'fields' in state: + self.setFields(state['fields']) + for itemState in state['items']: + item = self.addNew(itemState['field']) + item.restoreState(itemState) + + +class RangeColorMapItem(ptree.types.SimpleParameter): + mapType = 'range' + + def __init__(self, name, opts): + self.fieldName = name + units = opts.get('units', '') + ptree.types.SimpleParameter.__init__(self, + name=name, autoIncrementName=True, type='colormap', removable=True, renamable=True, + children=[ + #dict(name="Field", type='list', value=name, values=fields), + dict(name='Min', type='float', value=0.0, suffix=units, siPrefix=True), + dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True), + dict(name='Operation', type='list', value='Overlay', values=['Overlay', 'Add', 'Multiply', 'Set']), + dict(name='Channels..', type='group', expanded=False, children=[ + dict(name='Red', type='bool', value=True), + dict(name='Green', type='bool', value=True), + dict(name='Blue', type='bool', value=True), + dict(name='Alpha', type='bool', value=True), + ]), + dict(name='Enabled', type='bool', value=True), + dict(name='NaN', type='color'), + ]) + + def map(self, data): + data = data[self.fieldName] + + scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) + cmap = self.value() + colors = cmap.map(scaled, mode='float') + + mask = np.isnan(data) | np.isinf(data) + nanColor = self['NaN'] + nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.) + colors[mask] = nanColor + + return colors + +class EnumColorMapItem(ptree.types.GroupParameter): + mapType = 'enum' + + def __init__(self, name, opts): + self.fieldName = name + vals = opts.get('values', []) + if isinstance(vals, list): + vals = OrderedDict([(v,str(v)) for v in vals]) + childs = [{'name': v, 'type': 'color'} for v in vals] + + childs = [] + for val,vname in vals.items(): + ch = ptree.Parameter.create(name=vname, type='color') + ch.maskValue = val + childs.append(ch) + + ptree.types.GroupParameter.__init__(self, + name=name, autoIncrementName=True, removable=True, renamable=True, + children=[ + dict(name='Values', type='group', children=childs), + dict(name='Operation', type='list', value='Overlay', values=['Overlay', 'Add', 'Multiply', 'Set']), + dict(name='Channels..', type='group', expanded=False, children=[ + dict(name='Red', type='bool', value=True), + dict(name='Green', type='bool', value=True), + dict(name='Blue', type='bool', value=True), + dict(name='Alpha', type='bool', value=True), + ]), + dict(name='Enabled', type='bool', value=True), + dict(name='Default', type='color'), + ]) + + def map(self, data): + data = data[self.fieldName] + colors = np.empty((len(data), 4)) + default = np.array(fn.colorTuple(self['Default'])) / 255. + colors[:] = default + + for v in self.param('Values'): + mask = data == v.maskValue + c = np.array(fn.colorTuple(v.value())) / 255. + colors[mask] = c + #scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) + #cmap = self.value() + #colors = cmap.map(scaled, mode='float') + + #mask = np.isnan(data) | np.isinf(data) + #nanColor = self['NaN'] + #nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.) + #colors[mask] = nanColor + + return colors + + diff --git a/papi/pyqtgraph/widgets/ComboBox.py b/papi/pyqtgraph/widgets/ComboBox.py new file mode 100644 index 00000000..5cf6f918 --- /dev/null +++ b/papi/pyqtgraph/widgets/ComboBox.py @@ -0,0 +1,217 @@ +from ..Qt import QtGui, QtCore +from ..SignalProxy import SignalProxy +import sys +from ..pgcollections import OrderedDict +from ..python2_3 import asUnicode + +class ComboBox(QtGui.QComboBox): + """Extends QComboBox to add extra functionality. + + * Handles dict mappings -- user selects a text key, and the ComboBox indicates + the selected value. + * Requires item strings to be unique + * Remembers selected value if list is cleared and subsequently repopulated + * setItems() replaces the items in the ComboBox and blocks signals if the + value ultimately does not change. + """ + + + def __init__(self, parent=None, items=None, default=None): + QtGui.QComboBox.__init__(self, parent) + self.currentIndexChanged.connect(self.indexChanged) + self._ignoreIndexChange = False + + #self.value = default + if 'darwin' in sys.platform: ## because MacOSX can show names that are wider than the comboBox + self.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToMinimumContentsLength) + #self.setMinimumContentsLength(10) + self._chosenText = None + self._items = OrderedDict() + + if items is not None: + self.setItems(items) + if default is not None: + self.setValue(default) + + def setValue(self, value): + """Set the selected item to the first one having the given value.""" + text = None + for k,v in self._items.items(): + if v == value: + text = k + break + if text is None: + raise ValueError(value) + + self.setText(text) + + def setText(self, text): + """Set the selected item to the first one having the given text.""" + ind = self.findText(text) + if ind == -1: + raise ValueError(text) + #self.value = value + self.setCurrentIndex(ind) + + def value(self): + """ + If items were given as a list of strings, then return the currently + selected text. If items were given as a dict, then return the value + corresponding to the currently selected key. If the combo list is empty, + return None. + """ + if self.count() == 0: + return None + text = asUnicode(self.currentText()) + return self._items[text] + + def ignoreIndexChange(func): + # Decorator that prevents updates to self._chosenText + def fn(self, *args, **kwds): + prev = self._ignoreIndexChange + self._ignoreIndexChange = True + try: + ret = func(self, *args, **kwds) + finally: + self._ignoreIndexChange = prev + return ret + return fn + + def blockIfUnchanged(func): + # decorator that blocks signal emission during complex operations + # and emits currentIndexChanged only if the value has actually + # changed at the end. + def fn(self, *args, **kwds): + prevVal = self.value() + blocked = self.signalsBlocked() + self.blockSignals(True) + try: + ret = func(self, *args, **kwds) + finally: + self.blockSignals(blocked) + + # only emit if the value has changed + if self.value() != prevVal: + self.currentIndexChanged.emit(self.currentIndex()) + + return ret + return fn + + @ignoreIndexChange + @blockIfUnchanged + def setItems(self, items): + """ + *items* may be a list or a dict. + If a dict is given, then the keys are used to populate the combo box + and the values will be used for both value() and setValue(). + """ + prevVal = self.value() + + self.blockSignals(True) + try: + self.clear() + self.addItems(items) + finally: + self.blockSignals(False) + + # only emit if we were not able to re-set the original value + if self.value() != prevVal: + self.currentIndexChanged.emit(self.currentIndex()) + + def items(self): + return self.items.copy() + + def updateList(self, items): + # for backward compatibility + return self.setItems(items) + + def indexChanged(self, index): + # current index has changed; need to remember new 'chosen text' + if self._ignoreIndexChange: + return + self._chosenText = asUnicode(self.currentText()) + + def setCurrentIndex(self, index): + QtGui.QComboBox.setCurrentIndex(self, index) + + def itemsChanged(self): + # try to set the value to the last one selected, if it is available. + if self._chosenText is not None: + try: + self.setText(self._chosenText) + except ValueError: + pass + + @ignoreIndexChange + def insertItem(self, *args): + raise NotImplementedError() + #QtGui.QComboBox.insertItem(self, *args) + #self.itemsChanged() + + @ignoreIndexChange + def insertItems(self, *args): + raise NotImplementedError() + #QtGui.QComboBox.insertItems(self, *args) + #self.itemsChanged() + + @ignoreIndexChange + def addItem(self, *args, **kwds): + # Need to handle two different function signatures for QComboBox.addItem + try: + if isinstance(args[0], basestring): + text = args[0] + if len(args) == 2: + value = args[1] + else: + value = kwds.get('value', text) + else: + text = args[1] + if len(args) == 3: + value = args[2] + else: + value = kwds.get('value', text) + + except IndexError: + raise TypeError("First or second argument of addItem must be a string.") + + if text in self._items: + raise Exception('ComboBox already has item named "%s".' % text) + + self._items[text] = value + QtGui.QComboBox.addItem(self, *args) + self.itemsChanged() + + def setItemValue(self, name, value): + if name not in self._items: + self.addItem(name, value) + else: + self._items[name] = value + + @ignoreIndexChange + @blockIfUnchanged + def addItems(self, items): + if isinstance(items, list): + texts = items + items = dict([(x, x) for x in items]) + elif isinstance(items, dict): + texts = list(items.keys()) + else: + raise TypeError("items argument must be list or dict (got %s)." % type(items)) + + for t in texts: + if t in self._items: + raise Exception('ComboBox already has item named "%s".' % t) + + + for k,v in items.items(): + self._items[k] = v + QtGui.QComboBox.addItems(self, list(texts)) + + self.itemsChanged() + + @ignoreIndexChange + def clear(self): + self._items = OrderedDict() + QtGui.QComboBox.clear(self) + self.itemsChanged() + diff --git a/papi/pyqtgraph/widgets/DataFilterWidget.py b/papi/pyqtgraph/widgets/DataFilterWidget.py new file mode 100644 index 00000000..cae8be86 --- /dev/null +++ b/papi/pyqtgraph/widgets/DataFilterWidget.py @@ -0,0 +1,150 @@ +from ..Qt import QtGui, QtCore +from .. import parametertree as ptree +import numpy as np +from ..pgcollections import OrderedDict +from .. import functions as fn + +__all__ = ['DataFilterWidget'] + +class DataFilterWidget(ptree.ParameterTree): + """ + This class allows the user to filter multi-column data sets by specifying + multiple criteria + """ + + sigFilterChanged = QtCore.Signal(object) + + def __init__(self): + ptree.ParameterTree.__init__(self, showHeader=False) + self.params = DataFilterParameter() + + self.setParameters(self.params) + self.params.sigTreeStateChanged.connect(self.filterChanged) + + self.setFields = self.params.setFields + self.filterData = self.params.filterData + self.describe = self.params.describe + + def filterChanged(self): + self.sigFilterChanged.emit(self) + + def parameters(self): + return self.params + + +class DataFilterParameter(ptree.types.GroupParameter): + + sigFilterChanged = QtCore.Signal(object) + + def __init__(self): + self.fields = {} + ptree.types.GroupParameter.__init__(self, name='Data Filter', addText='Add filter..', addList=[]) + self.sigTreeStateChanged.connect(self.filterChanged) + + def filterChanged(self): + self.sigFilterChanged.emit(self) + + def addNew(self, name): + mode = self.fields[name].get('mode', 'range') + if mode == 'range': + self.addChild(RangeFilterItem(name, self.fields[name])) + elif mode == 'enum': + self.addChild(EnumFilterItem(name, self.fields[name])) + + + def fieldNames(self): + return self.fields.keys() + + def setFields(self, fields): + self.fields = OrderedDict(fields) + names = self.fieldNames() + self.setAddList(names) + + def filterData(self, data): + if len(data) == 0: + return data + return data[self.generateMask(data)] + + def generateMask(self, data): + mask = np.ones(len(data), dtype=bool) + if len(data) == 0: + return mask + for fp in self: + if fp.value() is False: + continue + mask &= fp.generateMask(data, mask.copy()) + #key, mn, mx = fp.fieldName, fp['Min'], fp['Max'] + + #vals = data[key] + #mask &= (vals >= mn) + #mask &= (vals < mx) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections + return mask + + def describe(self): + """Return a list of strings describing the currently enabled filters.""" + desc = [] + for fp in self: + if fp.value() is False: + continue + desc.append(fp.describe()) + return desc + +class RangeFilterItem(ptree.types.SimpleParameter): + def __init__(self, name, opts): + self.fieldName = name + units = opts.get('units', '') + self.units = units + ptree.types.SimpleParameter.__init__(self, + name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True, + children=[ + #dict(name="Field", type='list', value=name, values=fields), + dict(name='Min', type='float', value=0.0, suffix=units, siPrefix=True), + dict(name='Max', type='float', value=1.0, suffix=units, siPrefix=True), + ]) + + def generateMask(self, data, mask): + vals = data[self.fieldName][mask] + mask[mask] = (vals >= self['Min']) & (vals < self['Max']) ## Use inclusive minimum and non-inclusive maximum. This makes it easier to create non-overlapping selections + return mask + + def describe(self): + return "%s < %s < %s" % (fn.siFormat(self['Min'], suffix=self.units), self.fieldName, fn.siFormat(self['Max'], suffix=self.units)) + +class EnumFilterItem(ptree.types.SimpleParameter): + def __init__(self, name, opts): + self.fieldName = name + vals = opts.get('values', []) + childs = [] + if isinstance(vals, list): + vals = OrderedDict([(v,str(v)) for v in vals]) + for val,vname in vals.items(): + ch = ptree.Parameter.create(name=vname, type='bool', value=True) + ch.maskValue = val + childs.append(ch) + ch = ptree.Parameter.create(name='(other)', type='bool', value=True) + ch.maskValue = '__other__' + childs.append(ch) + + ptree.types.SimpleParameter.__init__(self, + name=name, autoIncrementName=True, type='bool', value=True, removable=True, renamable=True, + children=childs) + + def generateMask(self, data, startMask): + vals = data[self.fieldName][startMask] + mask = np.ones(len(vals), dtype=bool) + otherMask = np.ones(len(vals), dtype=bool) + for c in self: + key = c.maskValue + if key == '__other__': + m = ~otherMask + else: + m = vals != key + otherMask &= m + if c.value() is False: + mask &= m + startMask[startMask] = mask + return startMask + + def describe(self): + vals = [ch.name() for ch in self if ch.value() is True] + return "%s: %s" % (self.fieldName, ', '.join(vals)) \ No newline at end of file diff --git a/papi/pyqtgraph/widgets/DataTreeWidget.py b/papi/pyqtgraph/widgets/DataTreeWidget.py new file mode 100644 index 00000000..29e60319 --- /dev/null +++ b/papi/pyqtgraph/widgets/DataTreeWidget.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore +from ..pgcollections import OrderedDict +import types, traceback +import numpy as np + +try: + import metaarray + HAVE_METAARRAY = True +except: + HAVE_METAARRAY = False + +__all__ = ['DataTreeWidget'] + +class DataTreeWidget(QtGui.QTreeWidget): + """ + Widget for displaying hierarchical python data structures + (eg, nested dicts, lists, and arrays) + """ + + + def __init__(self, parent=None, data=None): + QtGui.QTreeWidget.__init__(self, parent) + self.setVerticalScrollMode(self.ScrollPerPixel) + self.setData(data) + self.setColumnCount(3) + self.setHeaderLabels(['key / index', 'type', 'value']) + + def setData(self, data, hideRoot=False): + """data should be a dictionary.""" + self.clear() + self.buildTree(data, self.invisibleRootItem(), hideRoot=hideRoot) + #node = self.mkNode('', data) + #while node.childCount() > 0: + #c = node.child(0) + #node.removeChild(c) + #self.invisibleRootItem().addChild(c) + self.expandToDepth(3) + self.resizeColumnToContents(0) + + def buildTree(self, data, parent, name='', hideRoot=False): + if hideRoot: + node = parent + else: + typeStr = type(data).__name__ + if typeStr == 'instance': + typeStr += ": " + data.__class__.__name__ + node = QtGui.QTreeWidgetItem([name, typeStr, ""]) + parent.addChild(node) + + if isinstance(data, types.TracebackType): ## convert traceback to a list of strings + data = list(map(str.strip, traceback.format_list(traceback.extract_tb(data)))) + elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): + data = { + 'data': data.view(np.ndarray), + 'meta': data.infoCopy() + } + + if isinstance(data, dict): + for k in data.keys(): + self.buildTree(data[k], node, str(k)) + elif isinstance(data, list) or isinstance(data, tuple): + for i in range(len(data)): + self.buildTree(data[i], node, str(i)) + else: + node.setText(2, str(data)) + + + #def mkNode(self, name, v): + #if type(v) is list and len(v) > 0 and isinstance(v[0], dict): + #inds = map(unicode, range(len(v))) + #v = OrderedDict(zip(inds, v)) + #if isinstance(v, dict): + ##print "\nadd tree", k, v + #node = QtGui.QTreeWidgetItem([name]) + #for k in v: + #newNode = self.mkNode(k, v[k]) + #node.addChild(newNode) + #else: + ##print "\nadd value", k, str(v) + #node = QtGui.QTreeWidgetItem([unicode(name), unicode(v)]) + #return node + diff --git a/papi/pyqtgraph/widgets/FeedbackButton.py b/papi/pyqtgraph/widgets/FeedbackButton.py new file mode 100644 index 00000000..30114d4e --- /dev/null +++ b/papi/pyqtgraph/widgets/FeedbackButton.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtCore, QtGui + +__all__ = ['FeedbackButton'] + +class FeedbackButton(QtGui.QPushButton): + """ + QPushButton which flashes success/failure indication for slow or asynchronous procedures. + """ + + + ### For thread-safetyness + sigCallSuccess = QtCore.Signal(object, object, object) + sigCallFailure = QtCore.Signal(object, object, object) + sigCallProcess = QtCore.Signal(object, object, object) + sigReset = QtCore.Signal() + + def __init__(self, *args): + QtGui.QPushButton.__init__(self, *args) + self.origStyle = None + self.origText = self.text() + self.origStyle = self.styleSheet() + self.origTip = self.toolTip() + self.limitedTime = True + + + #self.textTimer = QtCore.QTimer() + #self.tipTimer = QtCore.QTimer() + #self.textTimer.timeout.connect(self.setText) + #self.tipTimer.timeout.connect(self.setToolTip) + + self.sigCallSuccess.connect(self.success) + self.sigCallFailure.connect(self.failure) + self.sigCallProcess.connect(self.processing) + self.sigReset.connect(self.reset) + + + def feedback(self, success, message=None, tip="", limitedTime=True): + """Calls success() or failure(). If you want the message to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action.Threadsafe.""" + if success: + self.success(message, tip, limitedTime=limitedTime) + else: + self.failure(message, tip, limitedTime=limitedTime) + + def success(self, message=None, tip="", limitedTime=True): + """Displays specified message on button and flashes button green to let user know action was successful. If you want the success to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action. Threadsafe.""" + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + self.setEnabled(True) + #print "success" + self.startBlink("#0F0", message, tip, limitedTime=limitedTime) + else: + self.sigCallSuccess.emit(message, tip, limitedTime) + + def failure(self, message=None, tip="", limitedTime=True): + """Displays specified message on button and flashes button red to let user know there was an error. If you want the error to be displayed until the user takes an action, set limitedTime to False. Then call self.reset() after the desired action. Threadsafe. """ + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + self.setEnabled(True) + #print "fail" + self.startBlink("#F00", message, tip, limitedTime=limitedTime) + else: + self.sigCallFailure.emit(message, tip, limitedTime) + + def processing(self, message="Processing..", tip="", processEvents=True): + """Displays specified message on button to let user know the action is in progress. Threadsafe. """ + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + self.setEnabled(False) + self.setText(message, temporary=True) + self.setToolTip(tip, temporary=True) + if processEvents: + QtGui.QApplication.processEvents() + else: + self.sigCallProcess.emit(message, tip, processEvents) + + + def reset(self): + """Resets the button to its original text and style. Threadsafe.""" + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + if isGuiThread: + self.limitedTime = True + self.setText() + self.setToolTip() + self.setStyleSheet() + else: + self.sigReset.emit() + + def startBlink(self, color, message=None, tip="", limitedTime=True): + #if self.origStyle is None: + #self.origStyle = self.styleSheet() + #self.origText = self.text() + self.setFixedHeight(self.height()) + + if message is not None: + self.setText(message, temporary=True) + self.setToolTip(tip, temporary=True) + self.count = 0 + #self.indStyle = "QPushButton {border: 2px solid %s; border-radius: 5px}" % color + self.indStyle = "QPushButton {background-color: %s}" % color + self.limitedTime = limitedTime + self.borderOn() + if limitedTime: + QtCore.QTimer.singleShot(2000, self.setText) + QtCore.QTimer.singleShot(10000, self.setToolTip) + + def borderOn(self): + self.setStyleSheet(self.indStyle, temporary=True) + if self.limitedTime or self.count <=2: + QtCore.QTimer.singleShot(100, self.borderOff) + + + def borderOff(self): + self.setStyleSheet() + self.count += 1 + if self.count >= 2: + if self.limitedTime: + return + QtCore.QTimer.singleShot(30, self.borderOn) + + + def setText(self, text=None, temporary=False): + if text is None: + text = self.origText + #print text + QtGui.QPushButton.setText(self, text) + if not temporary: + self.origText = text + + def setToolTip(self, text=None, temporary=False): + if text is None: + text = self.origTip + QtGui.QPushButton.setToolTip(self, text) + if not temporary: + self.origTip = text + + def setStyleSheet(self, style=None, temporary=False): + if style is None: + style = self.origStyle + QtGui.QPushButton.setStyleSheet(self, style) + if not temporary: + self.origStyle = style + + +if __name__ == '__main__': + import time + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + btn = FeedbackButton("Button") + fail = True + def click(): + btn.processing("Hold on..") + time.sleep(2.0) + + global fail + fail = not fail + if fail: + btn.failure(message="FAIL.", tip="There was a failure. Get over it.") + else: + btn.success(message="Bueno!") + btn.clicked.connect(click) + win.setCentralWidget(btn) + win.show() \ No newline at end of file diff --git a/papi/pyqtgraph/widgets/FileDialog.py b/papi/pyqtgraph/widgets/FileDialog.py new file mode 100644 index 00000000..faa0994c --- /dev/null +++ b/papi/pyqtgraph/widgets/FileDialog.py @@ -0,0 +1,14 @@ +from ..Qt import QtGui, QtCore +import sys + +__all__ = ['FileDialog'] + +class FileDialog(QtGui.QFileDialog): + ## Compatibility fix for OSX: + ## For some reason the native dialog doesn't show up when you set AcceptMode to AcceptSave on OS X, so we don't use the native dialog + + def __init__(self, *args): + QtGui.QFileDialog.__init__(self, *args) + + if sys.platform == 'darwin': + self.setOption(QtGui.QFileDialog.DontUseNativeDialog) \ No newline at end of file diff --git a/papi/pyqtgraph/widgets/GradientWidget.py b/papi/pyqtgraph/widgets/GradientWidget.py new file mode 100644 index 00000000..ce0cbeb9 --- /dev/null +++ b/papi/pyqtgraph/widgets/GradientWidget.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore +from .GraphicsView import GraphicsView +from ..graphicsItems.GradientEditorItem import GradientEditorItem +import weakref +import numpy as np + +__all__ = ['GradientWidget'] + + +class GradientWidget(GraphicsView): + """ + Widget displaying an editable color gradient. The user may add, move, recolor, + or remove colors from the gradient. Additionally, a context menu allows the + user to select from pre-defined gradients. + """ + sigGradientChanged = QtCore.Signal(object) + sigGradientChangeFinished = QtCore.Signal(object) + + def __init__(self, parent=None, orientation='bottom', *args, **kargs): + """ + The *orientation* argument may be 'bottom', 'top', 'left', or 'right' + indicating whether the gradient is displayed horizontally (top, bottom) + or vertically (left, right) and on what side of the gradient the editable + ticks will appear. + + All other arguments are passed to + :func:`GradientEditorItem.__init__ `. + + Note: For convenience, this class wraps methods from + :class:`GradientEditorItem `. + """ + GraphicsView.__init__(self, parent, useOpenGL=False, background=None) + self.maxDim = 31 + kargs['tickPen'] = 'k' + self.item = GradientEditorItem(*args, **kargs) + self.item.sigGradientChanged.connect(self.sigGradientChanged) + self.item.sigGradientChangeFinished.connect(self.sigGradientChangeFinished) + self.setCentralItem(self.item) + self.setOrientation(orientation) + self.setCacheMode(self.CacheNone) + self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.TextAntialiasing) + self.setFrameStyle(QtGui.QFrame.NoFrame | QtGui.QFrame.Plain) + #self.setBackgroundRole(QtGui.QPalette.NoRole) + #self.setBackgroundBrush(QtGui.QBrush(QtCore.Qt.NoBrush)) + #self.setAutoFillBackground(False) + #self.setAttribute(QtCore.Qt.WA_PaintOnScreen, False) + #self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, True) + + def setOrientation(self, ort): + """Set the orientation of the widget. May be one of 'bottom', 'top', + 'left', or 'right'.""" + self.item.setOrientation(ort) + self.orientation = ort + self.setMaxDim() + + def setMaxDim(self, mx=None): + if mx is None: + mx = self.maxDim + else: + self.maxDim = mx + + if self.orientation in ['bottom', 'top']: + self.setFixedHeight(mx) + self.setMaximumWidth(16777215) + else: + self.setFixedWidth(mx) + self.setMaximumHeight(16777215) + + def __getattr__(self, attr): + ### wrap methods from GradientEditorItem + return getattr(self.item, attr) + + diff --git a/papi/pyqtgraph/widgets/GraphicsLayoutWidget.py b/papi/pyqtgraph/widgets/GraphicsLayoutWidget.py new file mode 100644 index 00000000..ec7b9e0d --- /dev/null +++ b/papi/pyqtgraph/widgets/GraphicsLayoutWidget.py @@ -0,0 +1,30 @@ +from ..Qt import QtGui +from ..graphicsItems.GraphicsLayout import GraphicsLayout +from .GraphicsView import GraphicsView + +__all__ = ['GraphicsLayoutWidget'] +class GraphicsLayoutWidget(GraphicsView): + """ + Convenience class consisting of a :class:`GraphicsView + ` with a single :class:`GraphicsLayout + ` as its central item. + + This class wraps several methods from its internal GraphicsLayout: + :func:`nextRow ` + :func:`nextColumn ` + :func:`addPlot ` + :func:`addViewBox ` + :func:`addItem ` + :func:`getItem ` + :func:`addLabel ` + :func:`addLayout ` + :func:`removeItem ` + :func:`itemIndex ` + :func:`clear ` + """ + def __init__(self, parent=None, **kargs): + GraphicsView.__init__(self, parent) + self.ci = GraphicsLayout(**kargs) + for n in ['nextRow', 'nextCol', 'nextColumn', 'addPlot', 'addViewBox', 'addItem', 'getItem', 'addLayout', 'addLabel', 'removeItem', 'itemIndex', 'clear']: + setattr(self, n, getattr(self.ci, n)) + self.setCentralItem(self.ci) diff --git a/papi/pyqtgraph/widgets/GraphicsView.py b/papi/pyqtgraph/widgets/GraphicsView.py new file mode 100644 index 00000000..4062be94 --- /dev/null +++ b/papi/pyqtgraph/widgets/GraphicsView.py @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- +""" +GraphicsView.py - Extension of QGraphicsView +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from ..Qt import QtCore, QtGui, USE_PYSIDE + +try: + from ..Qt import QtOpenGL + HAVE_OPENGL = True +except ImportError: + HAVE_OPENGL = False + +from ..Point import Point +import sys, os +from .FileDialog import FileDialog +from ..GraphicsScene import GraphicsScene +import numpy as np +from .. import functions as fn +from .. import debug as debug +from .. import getConfigOption + +__all__ = ['GraphicsView'] + +class GraphicsView(QtGui.QGraphicsView): + """Re-implementation of QGraphicsView that removes scrollbars and allows unambiguous control of the + viewed coordinate range. Also automatically creates a GraphicsScene and a central QGraphicsWidget + that is automatically scaled to the full view geometry. + + This widget is the basis for :class:`PlotWidget `, + :class:`GraphicsLayoutWidget `, and the view widget in + :class:`ImageView `. + + By default, the view coordinate system matches the widget's pixel coordinates and + automatically updates when the view is resized. This can be overridden by setting + autoPixelRange=False. The exact visible range can be set with setRange(). + + The view can be panned using the middle mouse button and scaled using the right mouse button if + enabled via enableMouse() (but ordinarily, we use ViewBox for this functionality).""" + + sigDeviceRangeChanged = QtCore.Signal(object, object) + sigDeviceTransformChanged = QtCore.Signal(object) + sigMouseReleased = QtCore.Signal(object) + sigSceneMouseMoved = QtCore.Signal(object) + #sigRegionChanged = QtCore.Signal(object) + sigScaleChanged = QtCore.Signal(object) + lastFileDir = None + + def __init__(self, parent=None, useOpenGL=None, background='default'): + """ + ============== ============================================================ + **Arguments:** + parent Optional parent widget + useOpenGL If True, the GraphicsView will use OpenGL to do all of its + rendering. This can improve performance on some systems, + but may also introduce bugs (the combination of + QGraphicsView and QGLWidget is still an 'experimental' + feature of Qt) + background Set the background color of the GraphicsView. Accepts any + single argument accepted by + :func:`mkColor `. By + default, the background color is determined using the + 'backgroundColor' configuration option (see + :func:`setConfigOption `. + ============== ============================================================ + """ + + self.closed = False + + QtGui.QGraphicsView.__init__(self, parent) + + # This connects a cleanup function to QApplication.aboutToQuit. It is + # called from here because we have no good way to react when the + # QApplication is created by the user. + # See pyqtgraph.__init__.py + from .. import _connectCleanup + _connectCleanup() + + if useOpenGL is None: + useOpenGL = getConfigOption('useOpenGL') + + self.useOpenGL(useOpenGL) + + self.setCacheMode(self.CacheBackground) + + ## This might help, but it's probably dangerous in the general case.. + #self.setOptimizationFlag(self.DontSavePainterState, True) + + self.setBackgroundRole(QtGui.QPalette.NoRole) + self.setBackground(background) + + self.setFocusPolicy(QtCore.Qt.StrongFocus) + self.setFrameShape(QtGui.QFrame.NoFrame) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setTransformationAnchor(QtGui.QGraphicsView.NoAnchor) + self.setResizeAnchor(QtGui.QGraphicsView.AnchorViewCenter) + self.setViewportUpdateMode(QtGui.QGraphicsView.MinimalViewportUpdate) + + + self.lockedViewports = [] + self.lastMousePos = None + self.setMouseTracking(True) + self.aspectLocked = False + self.range = QtCore.QRectF(0, 0, 1, 1) + self.autoPixelRange = True + self.currentItem = None + self.clearMouse() + self.updateMatrix() + # GraphicsScene must have parent or expect crashes! + self.sceneObj = GraphicsScene(parent=self) + self.setScene(self.sceneObj) + + ## Workaround for PySide crash + ## This ensures that the scene will outlive the view. + if USE_PYSIDE: + self.sceneObj._view_ref_workaround = self + + ## by default we set up a central widget with a grid layout. + ## this can be replaced if needed. + self.centralWidget = None + self.setCentralItem(QtGui.QGraphicsWidget()) + self.centralLayout = QtGui.QGraphicsGridLayout() + self.centralWidget.setLayout(self.centralLayout) + + self.mouseEnabled = False + self.scaleCenter = False ## should scaling center around view center (True) or mouse click (False) + self.clickAccepted = False + + def setAntialiasing(self, aa): + """Enable or disable default antialiasing. + Note that this will only affect items that do not specify their own antialiasing options.""" + if aa: + self.setRenderHints(self.renderHints() | QtGui.QPainter.Antialiasing) + else: + self.setRenderHints(self.renderHints() & ~QtGui.QPainter.Antialiasing) + + def setBackground(self, background): + """ + Set the background color of the GraphicsView. + To use the defaults specified py pyqtgraph.setConfigOption, use background='default'. + To make the background transparent, use background=None. + """ + self._background = background + if background == 'default': + background = getConfigOption('background') + brush = fn.mkBrush(background) + self.setBackgroundBrush(brush) + + def paintEvent(self, ev): + self.scene().prepareForPaint() + return QtGui.QGraphicsView.paintEvent(self, ev) + + def render(self, *args, **kwds): + self.scene().prepareForPaint() + return QtGui.QGraphicsView.render(self, *args, **kwds) + + + def close(self): + self.centralWidget = None + self.scene().clear() + self.currentItem = None + self.sceneObj = None + self.closed = True + self.setViewport(None) + + def useOpenGL(self, b=True): + if b: + if not HAVE_OPENGL: + raise Exception("Requested to use OpenGL with QGraphicsView, but QtOpenGL module is not available.") + v = QtOpenGL.QGLWidget() + else: + v = QtGui.QWidget() + + self.setViewport(v) + + def keyPressEvent(self, ev): + self.scene().keyPressEvent(ev) ## bypass view, hand event directly to scene + ## (view likes to eat arrow key events) + + + def setCentralItem(self, item): + return self.setCentralWidget(item) + + def setCentralWidget(self, item): + """Sets a QGraphicsWidget to automatically fill the entire view (the item will be automatically + resize whenever the GraphicsView is resized).""" + if self.centralWidget is not None: + self.scene().removeItem(self.centralWidget) + self.centralWidget = item + if item is not None: + self.sceneObj.addItem(item) + self.resizeEvent(None) + + def addItem(self, *args): + return self.scene().addItem(*args) + + def removeItem(self, *args): + return self.scene().removeItem(*args) + + def enableMouse(self, b=True): + self.mouseEnabled = b + self.autoPixelRange = (not b) + + def clearMouse(self): + self.mouseTrail = [] + self.lastButtonReleased = None + + def resizeEvent(self, ev): + if self.closed: + return + if self.autoPixelRange: + self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height()) + GraphicsView.setRange(self, self.range, padding=0, disableAutoPixel=False) ## we do this because some subclasses like to redefine setRange in an incompatible way. + self.updateMatrix() + + def updateMatrix(self, propagate=True): + self.setSceneRect(self.range) + if self.autoPixelRange: + self.resetTransform() + else: + if self.aspectLocked: + self.fitInView(self.range, QtCore.Qt.KeepAspectRatio) + else: + self.fitInView(self.range, QtCore.Qt.IgnoreAspectRatio) + + self.sigDeviceRangeChanged.emit(self, self.range) + self.sigDeviceTransformChanged.emit(self) + + if propagate: + for v in self.lockedViewports: + v.setXRange(self.range, padding=0) + + def viewRect(self): + """Return the boundaries of the view in scene coordinates""" + ## easier to just return self.range ? + r = QtCore.QRectF(self.rect()) + return self.viewportTransform().inverted()[0].mapRect(r) + + def visibleRange(self): + ## for backward compatibility + return self.viewRect() + + def translate(self, dx, dy): + self.range.adjust(dx, dy, dx, dy) + self.updateMatrix() + + def scale(self, sx, sy, center=None): + scale = [sx, sy] + if self.aspectLocked: + scale[0] = scale[1] + + if self.scaleCenter: + center = None + if center is None: + center = self.range.center() + + w = self.range.width() / scale[0] + h = self.range.height() / scale[1] + self.range = QtCore.QRectF(center.x() - (center.x()-self.range.left()) / scale[0], center.y() - (center.y()-self.range.top()) /scale[1], w, h) + + + self.updateMatrix() + self.sigScaleChanged.emit(self) + + def setRange(self, newRect=None, padding=0.05, lockAspect=None, propagate=True, disableAutoPixel=True): + if disableAutoPixel: + self.autoPixelRange=False + if newRect is None: + newRect = self.visibleRange() + padding = 0 + + padding = Point(padding) + newRect = QtCore.QRectF(newRect) + pw = newRect.width() * padding[0] + ph = newRect.height() * padding[1] + newRect = newRect.adjusted(-pw, -ph, pw, ph) + scaleChanged = False + if self.range.width() != newRect.width() or self.range.height() != newRect.height(): + scaleChanged = True + self.range = newRect + #print "New Range:", self.range + if self.centralWidget is not None: + self.centralWidget.setGeometry(self.range) + self.updateMatrix(propagate) + if scaleChanged: + self.sigScaleChanged.emit(self) + + def scaleToImage(self, image): + """Scales such that pixels in image are the same size as screen pixels. This may result in a significant performance increase.""" + pxSize = image.pixelSize() + image.setPxMode(True) + try: + self.sigScaleChanged.disconnect(image.setScaledMode) + except (TypeError, RuntimeError): + pass + tl = image.sceneBoundingRect().topLeft() + w = self.size().width() * pxSize[0] + h = self.size().height() * pxSize[1] + range = QtCore.QRectF(tl.x(), tl.y(), w, h) + GraphicsView.setRange(self, range, padding=0) + self.sigScaleChanged.connect(image.setScaledMode) + + + + def lockXRange(self, v1): + if not v1 in self.lockedViewports: + self.lockedViewports.append(v1) + + def setXRange(self, r, padding=0.05): + r1 = QtCore.QRectF(self.range) + r1.setLeft(r.left()) + r1.setRight(r.right()) + GraphicsView.setRange(self, r1, padding=[padding, 0], propagate=False) + + def setYRange(self, r, padding=0.05): + r1 = QtCore.QRectF(self.range) + r1.setTop(r.top()) + r1.setBottom(r.bottom()) + GraphicsView.setRange(self, r1, padding=[0, padding], propagate=False) + + def wheelEvent(self, ev): + QtGui.QGraphicsView.wheelEvent(self, ev) + if not self.mouseEnabled: + return + sc = 1.001 ** ev.delta() + #self.scale *= sc + #self.updateMatrix() + self.scale(sc, sc) + + def setAspectLocked(self, s): + self.aspectLocked = s + + def leaveEvent(self, ev): + self.scene().leaveEvent(ev) ## inform scene when mouse leaves + + def mousePressEvent(self, ev): + QtGui.QGraphicsView.mousePressEvent(self, ev) + + + if not self.mouseEnabled: + return + self.lastMousePos = Point(ev.pos()) + self.mousePressPos = ev.pos() + self.clickAccepted = ev.isAccepted() + if not self.clickAccepted: + self.scene().clearSelection() + return ## Everything below disabled for now.. + + def mouseReleaseEvent(self, ev): + QtGui.QGraphicsView.mouseReleaseEvent(self, ev) + if not self.mouseEnabled: + return + self.sigMouseReleased.emit(ev) + self.lastButtonReleased = ev.button() + return ## Everything below disabled for now.. + + def mouseMoveEvent(self, ev): + if self.lastMousePos is None: + self.lastMousePos = Point(ev.pos()) + delta = Point(ev.pos() - self.lastMousePos) + self.lastMousePos = Point(ev.pos()) + + QtGui.QGraphicsView.mouseMoveEvent(self, ev) + if not self.mouseEnabled: + return + self.sigSceneMouseMoved.emit(self.mapToScene(ev.pos())) + + if self.clickAccepted: ## Ignore event if an item in the scene has already claimed it. + return + + if ev.buttons() == QtCore.Qt.RightButton: + delta = Point(np.clip(delta[0], -50, 50), np.clip(-delta[1], -50, 50)) + scale = 1.01 ** delta + self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos)) + self.sigDeviceRangeChanged.emit(self, self.range) + + elif ev.buttons() in [QtCore.Qt.MidButton, QtCore.Qt.LeftButton]: ## Allow panning by left or mid button. + px = self.pixelSize() + tr = -delta * px + + self.translate(tr[0], tr[1]) + self.sigDeviceRangeChanged.emit(self, self.range) + + def pixelSize(self): + """Return vector with the length and width of one view pixel in scene coordinates""" + p0 = Point(0,0) + p1 = Point(1,1) + tr = self.transform().inverted()[0] + p01 = tr.map(p0) + p11 = tr.map(p1) + return Point(p11 - p01) + + def dragEnterEvent(self, ev): + ev.ignore() ## not sure why, but for some reason this class likes to consume drag events + + diff --git a/papi/pyqtgraph/widgets/HistogramLUTWidget.py b/papi/pyqtgraph/widgets/HistogramLUTWidget.py new file mode 100644 index 00000000..9aec837c --- /dev/null +++ b/papi/pyqtgraph/widgets/HistogramLUTWidget.py @@ -0,0 +1,33 @@ +""" +Widget displaying an image histogram along with gradient editor. Can be used to adjust the appearance of images. +This is a wrapper around HistogramLUTItem +""" + +from ..Qt import QtGui, QtCore +from .GraphicsView import GraphicsView +from ..graphicsItems.HistogramLUTItem import HistogramLUTItem + +__all__ = ['HistogramLUTWidget'] + + +class HistogramLUTWidget(GraphicsView): + + def __init__(self, parent=None, *args, **kargs): + background = kargs.get('background', 'default') + GraphicsView.__init__(self, parent, useOpenGL=False, background=background) + self.item = HistogramLUTItem(*args, **kargs) + self.setCentralItem(self.item) + self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding) + self.setMinimumWidth(95) + + + def sizeHint(self): + return QtCore.QSize(115, 200) + + + + def __getattr__(self, attr): + return getattr(self.item, attr) + + + diff --git a/papi/pyqtgraph/widgets/JoystickButton.py b/papi/pyqtgraph/widgets/JoystickButton.py new file mode 100644 index 00000000..6f73c8dc --- /dev/null +++ b/papi/pyqtgraph/widgets/JoystickButton.py @@ -0,0 +1,95 @@ +from ..Qt import QtGui, QtCore + + +__all__ = ['JoystickButton'] + +class JoystickButton(QtGui.QPushButton): + sigStateChanged = QtCore.Signal(object, object) ## self, state + + def __init__(self, parent=None): + QtGui.QPushButton.__init__(self, parent) + self.radius = 200 + self.setCheckable(True) + self.state = None + self.setState(0,0) + self.setFixedWidth(50) + self.setFixedHeight(50) + + + def mousePressEvent(self, ev): + self.setChecked(True) + self.pressPos = ev.pos() + ev.accept() + + def mouseMoveEvent(self, ev): + dif = ev.pos()-self.pressPos + self.setState(dif.x(), -dif.y()) + + def mouseReleaseEvent(self, ev): + self.setChecked(False) + self.setState(0,0) + + def wheelEvent(self, ev): + ev.accept() + + + def doubleClickEvent(self, ev): + ev.accept() + + def getState(self): + return self.state + + def setState(self, *xy): + xy = list(xy) + d = (xy[0]**2 + xy[1]**2)**0.5 + nxy = [0,0] + for i in [0,1]: + if xy[i] == 0: + nxy[i] = 0 + else: + nxy[i] = xy[i]/d + + if d > self.radius: + d = self.radius + d = (d/self.radius)**2 + xy = [nxy[0]*d, nxy[1]*d] + + w2 = self.width()/2. + h2 = self.height()/2 + self.spotPos = QtCore.QPoint(w2*(1+xy[0]), h2*(1-xy[1])) + self.update() + if self.state == xy: + return + self.state = xy + self.sigStateChanged.emit(self, self.state) + + def paintEvent(self, ev): + QtGui.QPushButton.paintEvent(self, ev) + p = QtGui.QPainter(self) + p.setBrush(QtGui.QBrush(QtGui.QColor(0,0,0))) + p.drawEllipse(self.spotPos.x()-3,self.spotPos.y()-3,6,6) + + def resizeEvent(self, ev): + self.setState(*self.state) + QtGui.QPushButton.resizeEvent(self, ev) + + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + w = QtGui.QMainWindow() + b = JoystickButton() + w.setCentralWidget(b) + w.show() + w.resize(100, 100) + + def fn(b, s): + print("state changed:", s) + + b.sigStateChanged.connect(fn) + + ## Start Qt event loop unless running in interactive mode. + import sys + if sys.flags.interactive != 1: + app.exec_() + \ No newline at end of file diff --git a/papi/pyqtgraph/widgets/LayoutWidget.py b/papi/pyqtgraph/widgets/LayoutWidget.py new file mode 100644 index 00000000..65d04d3f --- /dev/null +++ b/papi/pyqtgraph/widgets/LayoutWidget.py @@ -0,0 +1,101 @@ +from ..Qt import QtGui, QtCore + +__all__ = ['LayoutWidget'] +class LayoutWidget(QtGui.QWidget): + """ + Convenience class used for laying out QWidgets in a grid. + (It's just a little less effort to use than QGridLayout) + """ + + def __init__(self, parent=None): + QtGui.QWidget.__init__(self, parent) + self.layout = QtGui.QGridLayout() + self.setLayout(self.layout) + self.items = {} + self.rows = {} + self.currentRow = 0 + self.currentCol = 0 + + def nextRow(self): + """Advance to next row for automatic widget placement""" + self.currentRow += 1 + self.currentCol = 0 + + def nextColumn(self, colspan=1): + """Advance to next column, while returning the current column number + (generally only for internal use--called by addWidget)""" + self.currentCol += colspan + return self.currentCol-colspan + + def nextCol(self, *args, **kargs): + """Alias of nextColumn""" + return self.nextColumn(*args, **kargs) + + + def addLabel(self, text=' ', row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create a QLabel with *text* and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to QLabel(). + Returns the created widget. + """ + text = QtGui.QLabel(text, **kargs) + self.addItem(text, row, col, rowspan, colspan) + return text + + def addLayout(self, row=None, col=None, rowspan=1, colspan=1, **kargs): + """ + Create an empty LayoutWidget and place it in the next available cell (or in the cell specified) + All extra keyword arguments are passed to :func:`LayoutWidget.__init__ ` + Returns the created widget. + """ + layout = LayoutWidget(**kargs) + self.addItem(layout, row, col, rowspan, colspan) + return layout + + def addWidget(self, item, row=None, col=None, rowspan=1, colspan=1): + """ + Add a widget to the layout and place it in the next available cell (or in the cell specified). + """ + if row == 'next': + self.nextRow() + row = self.currentRow + elif row is None: + row = self.currentRow + + + if col is None: + col = self.nextCol(colspan) + + if row not in self.rows: + self.rows[row] = {} + self.rows[row][col] = item + self.items[item] = (row, col) + + self.layout.addWidget(item, row, col, rowspan, colspan) + + def getWidget(self, row, col): + """Return the widget in (*row*, *col*)""" + return self.row[row][col] + + #def itemIndex(self, item): + #for i in range(self.layout.count()): + #if self.layout.itemAt(i).graphicsItem() is item: + #return i + #raise Exception("Could not determine index of item " + str(item)) + + #def removeItem(self, item): + #"""Remove *item* from the layout.""" + #ind = self.itemIndex(item) + #self.layout.removeAt(ind) + #self.scene().removeItem(item) + #r,c = self.items[item] + #del self.items[item] + #del self.rows[r][c] + #self.update() + + #def clear(self): + #items = [] + #for i in list(self.items.keys()): + #self.removeItem(i) + + diff --git a/papi/pyqtgraph/widgets/MatplotlibWidget.py b/papi/pyqtgraph/widgets/MatplotlibWidget.py new file mode 100644 index 00000000..959e188a --- /dev/null +++ b/papi/pyqtgraph/widgets/MatplotlibWidget.py @@ -0,0 +1,41 @@ +from ..Qt import QtGui, QtCore, USE_PYSIDE +import matplotlib + +if USE_PYSIDE: + matplotlib.rcParams['backend.qt4']='PySide' + +from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar +from matplotlib.figure import Figure + +class MatplotlibWidget(QtGui.QWidget): + """ + Implements a Matplotlib figure inside a QWidget. + Use getFigure() and redraw() to interact with matplotlib. + + Example:: + + mw = MatplotlibWidget() + subplot = mw.getFigure().add_subplot(111) + subplot.plot(x,y) + mw.draw() + """ + + def __init__(self, size=(5.0, 4.0), dpi=100): + QtGui.QWidget.__init__(self) + self.fig = Figure(size, dpi=dpi) + self.canvas = FigureCanvas(self.fig) + self.canvas.setParent(self) + self.toolbar = NavigationToolbar(self.canvas, self) + + self.vbox = QtGui.QVBoxLayout() + self.vbox.addWidget(self.toolbar) + self.vbox.addWidget(self.canvas) + + self.setLayout(self.vbox) + + def getFigure(self): + return self.fig + + def draw(self): + self.canvas.draw() diff --git a/papi/pyqtgraph/widgets/MultiPlotWidget.py b/papi/pyqtgraph/widgets/MultiPlotWidget.py new file mode 100644 index 00000000..d1f56034 --- /dev/null +++ b/papi/pyqtgraph/widgets/MultiPlotWidget.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +""" +MultiPlotWidget.py - Convenience class--GraphicsView widget displaying a MultiPlotItem +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" +from ..Qt import QtCore +from .GraphicsView import GraphicsView +from ..graphicsItems import MultiPlotItem as MultiPlotItem + +__all__ = ['MultiPlotWidget'] +class MultiPlotWidget(GraphicsView): + """Widget implementing a graphicsView with a single MultiPlotItem inside.""" + def __init__(self, parent=None): + self.minPlotHeight = 50 + self.mPlotItem = MultiPlotItem.MultiPlotItem() + GraphicsView.__init__(self, parent) + self.enableMouse(False) + self.setCentralItem(self.mPlotItem) + ## Explicitly wrap methods from mPlotItem + #for m in ['setData']: + #setattr(self, m, getattr(self.mPlotItem, m)) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + + def __getattr__(self, attr): ## implicitly wrap methods from plotItem + if hasattr(self.mPlotItem, attr): + m = getattr(self.mPlotItem, attr) + if hasattr(m, '__call__'): + return m + raise AttributeError(attr) + + def setMinimumPlotHeight(self, min): + """Set the minimum height for each sub-plot displayed. + + If the total height of all plots is greater than the height of the + widget, then a scroll bar will appear to provide access to the entire + set of plots. + + Added in version 0.9.9 + """ + self.minPlotHeight = min + self.resizeEvent(None) + + def widgetGroupInterface(self): + return (None, MultiPlotWidget.saveState, MultiPlotWidget.restoreState) + + def saveState(self): + return {} + #return self.plotItem.saveState() + + def restoreState(self, state): + pass + #return self.plotItem.restoreState(state) + + def close(self): + self.mPlotItem.close() + self.mPlotItem = None + self.setParent(None) + GraphicsView.close(self) + + def setRange(self, *args, **kwds): + GraphicsView.setRange(self, *args, **kwds) + if self.centralWidget is not None: + r = self.range + minHeight = len(self.mPlotItem.plots) * self.minPlotHeight + if r.height() < minHeight: + r.setHeight(minHeight) + r.setWidth(r.width() - self.verticalScrollBar().width()) + self.centralWidget.setGeometry(r) + + def resizeEvent(self, ev): + if self.closed: + return + if self.autoPixelRange: + self.range = QtCore.QRectF(0, 0, self.size().width(), self.size().height()) + MultiPlotWidget.setRange(self, self.range, padding=0, disableAutoPixel=False) ## we do this because some subclasses like to redefine setRange in an incompatible way. + self.updateMatrix() diff --git a/papi/pyqtgraph/widgets/PathButton.py b/papi/pyqtgraph/widgets/PathButton.py new file mode 100644 index 00000000..52c60e20 --- /dev/null +++ b/papi/pyqtgraph/widgets/PathButton.py @@ -0,0 +1,50 @@ +from ..Qt import QtGui, QtCore +from .. import functions as fn + +__all__ = ['PathButton'] + + +class PathButton(QtGui.QPushButton): + """Simple PushButton extension which paints a QPainterPath on its face""" + def __init__(self, parent=None, path=None, pen='default', brush=None, size=(30,30)): + QtGui.QPushButton.__init__(self, parent) + self.path = None + if pen == 'default': + pen = 'k' + self.setPen(pen) + self.setBrush(brush) + if path is not None: + self.setPath(path) + if size is not None: + self.setFixedWidth(size[0]) + self.setFixedHeight(size[1]) + + + def setBrush(self, brush): + self.brush = fn.mkBrush(brush) + + def setPen(self, *args, **kwargs): + self.pen = fn.mkPen(*args, **kwargs) + + def setPath(self, path): + self.path = path + self.update() + + def paintEvent(self, ev): + QtGui.QPushButton.paintEvent(self, ev) + margin = 7 + geom = QtCore.QRectF(0, 0, self.width(), self.height()).adjusted(margin, margin, -margin, -margin) + rect = self.path.boundingRect() + scale = min(geom.width() / float(rect.width()), geom.height() / float(rect.height())) + + p = QtGui.QPainter(self) + p.setRenderHint(p.Antialiasing) + p.translate(geom.center()) + p.scale(scale, scale) + p.translate(-rect.center()) + p.setPen(self.pen) + p.setBrush(self.brush) + p.drawPath(self.path) + p.end() + + diff --git a/papi/pyqtgraph/widgets/PlotWidget.py b/papi/pyqtgraph/widgets/PlotWidget.py new file mode 100644 index 00000000..e27bce60 --- /dev/null +++ b/papi/pyqtgraph/widgets/PlotWidget.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +""" +PlotWidget.py - Convenience class--GraphicsView widget displaying a single PlotItem +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. +""" + +from ..Qt import QtCore, QtGui +from .GraphicsView import * +from ..graphicsItems.PlotItem import * + +__all__ = ['PlotWidget'] +class PlotWidget(GraphicsView): + + # signals wrapped from PlotItem / ViewBox + sigRangeChanged = QtCore.Signal(object, object) + sigTransformChanged = QtCore.Signal(object) + + """ + :class:`GraphicsView ` widget with a single + :class:`PlotItem ` inside. + + The following methods are wrapped directly from PlotItem: + :func:`addItem `, + :func:`removeItem `, + :func:`clear `, + :func:`setXRange `, + :func:`setYRange `, + :func:`setRange `, + :func:`autoRange `, + :func:`setXLink `, + :func:`setYLink `, + :func:`viewRect `, + :func:`setMouseEnabled `, + :func:`enableAutoRange `, + :func:`disableAutoRange `, + :func:`setAspectLocked `, + :func:`setLimits `, + :func:`register `, + :func:`unregister ` + + + For all + other methods, use :func:`getPlotItem `. + """ + def __init__(self, parent=None, background='default', **kargs): + """When initializing PlotWidget, *parent* and *background* are passed to + :func:`GraphicsWidget.__init__() ` + and all others are passed + to :func:`PlotItem.__init__() `.""" + GraphicsView.__init__(self, parent, background=background) + self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.enableMouse(False) + self.plotItem = PlotItem(**kargs) + self.setCentralItem(self.plotItem) + ## Explicitly wrap methods from plotItem + ## NOTE: If you change this list, update the documentation above as well. + for m in ['addItem', 'removeItem', 'autoRange', 'clear', 'setXRange', + 'setYRange', 'setRange', 'setAspectLocked', 'setMouseEnabled', + 'setXLink', 'setYLink', 'enableAutoRange', 'disableAutoRange', + 'setLimits', 'register', 'unregister', 'viewRect']: + setattr(self, m, getattr(self.plotItem, m)) + #QtCore.QObject.connect(self.plotItem, QtCore.SIGNAL('viewChanged'), self.viewChanged) + self.plotItem.sigRangeChanged.connect(self.viewRangeChanged) + + def close(self): + self.plotItem.close() + self.plotItem = None + #self.scene().clear() + #self.mPlotItem.close() + self.setParent(None) + GraphicsView.close(self) + + def __getattr__(self, attr): ## implicitly wrap methods from plotItem + if hasattr(self.plotItem, attr): + m = getattr(self.plotItem, attr) + if hasattr(m, '__call__'): + return m + raise NameError(attr) + + def viewRangeChanged(self, view, range): + #self.emit(QtCore.SIGNAL('viewChanged'), *args) + self.sigRangeChanged.emit(self, range) + + def widgetGroupInterface(self): + return (None, PlotWidget.saveState, PlotWidget.restoreState) + + def saveState(self): + return self.plotItem.saveState() + + def restoreState(self, state): + return self.plotItem.restoreState(state) + + def getPlotItem(self): + """Return the PlotItem contained within.""" + return self.plotItem + + + \ No newline at end of file diff --git a/papi/pyqtgraph/widgets/ProgressDialog.py b/papi/pyqtgraph/widgets/ProgressDialog.py new file mode 100644 index 00000000..8c669be4 --- /dev/null +++ b/papi/pyqtgraph/widgets/ProgressDialog.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore + +__all__ = ['ProgressDialog'] +class ProgressDialog(QtGui.QProgressDialog): + """ + Extends QProgressDialog for use in 'with' statements. + + Example:: + + with ProgressDialog("Processing..", minVal, maxVal) as dlg: + # do stuff + dlg.setValue(i) ## could also use dlg += 1 + if dlg.wasCanceled(): + raise Exception("Processing canceled by user") + """ + def __init__(self, labelText, minimum=0, maximum=100, cancelText='Cancel', parent=None, wait=250, busyCursor=False, disable=False): + """ + ============== ================================================================ + **Arguments:** + labelText (required) + cancelText Text to display on cancel button, or None to disable it. + minimum + maximum + parent + wait Length of time (im ms) to wait before displaying dialog + busyCursor If True, show busy cursor until dialog finishes + disable If True, the progress dialog will not be displayed + and calls to wasCanceled() will always return False. + If ProgressDialog is entered from a non-gui thread, it will + always be disabled. + ============== ================================================================ + """ + isGuiThread = QtCore.QThread.currentThread() == QtCore.QCoreApplication.instance().thread() + self.disabled = disable or (not isGuiThread) + if self.disabled: + return + + noCancel = False + if cancelText is None: + cancelText = '' + noCancel = True + + self.busyCursor = busyCursor + + QtGui.QProgressDialog.__init__(self, labelText, cancelText, minimum, maximum, parent) + self.setMinimumDuration(wait) + self.setWindowModality(QtCore.Qt.WindowModal) + self.setValue(self.minimum()) + if noCancel: + self.setCancelButton(None) + + + def __enter__(self): + if self.disabled: + return self + if self.busyCursor: + QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor)) + return self + + def __exit__(self, exType, exValue, exTrace): + if self.disabled: + return + if self.busyCursor: + QtGui.QApplication.restoreOverrideCursor() + self.setValue(self.maximum()) + + def __iadd__(self, val): + """Use inplace-addition operator for easy incrementing.""" + if self.disabled: + return self + self.setValue(self.value()+val) + return self + + + ## wrap all other functions to make sure they aren't being called from non-gui threads + + def setValue(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setValue(self, val) + + def setLabelText(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setLabelText(self, val) + + def setMaximum(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setMaximum(self, val) + + def setMinimum(self, val): + if self.disabled: + return + QtGui.QProgressDialog.setMinimum(self, val) + + def wasCanceled(self): + if self.disabled: + return False + return QtGui.QProgressDialog.wasCanceled(self) + + def maximum(self): + if self.disabled: + return 0 + return QtGui.QProgressDialog.maximum(self) + + def minimum(self): + if self.disabled: + return 0 + return QtGui.QProgressDialog.minimum(self) + diff --git a/papi/pyqtgraph/widgets/RawImageWidget.py b/papi/pyqtgraph/widgets/RawImageWidget.py new file mode 100644 index 00000000..970b570b --- /dev/null +++ b/papi/pyqtgraph/widgets/RawImageWidget.py @@ -0,0 +1,140 @@ +from ..Qt import QtCore, QtGui +try: + from ..Qt import QtOpenGL + from OpenGL.GL import * + HAVE_OPENGL = True +except ImportError: + HAVE_OPENGL = False + +from .. import functions as fn +import numpy as np + +class RawImageWidget(QtGui.QWidget): + """ + Widget optimized for very fast video display. + Generally using an ImageItem inside GraphicsView is fast enough. + On some systems this may provide faster video. See the VideoSpeedTest example for benchmarking. + """ + def __init__(self, parent=None, scaled=False): + """ + Setting scaled=True will cause the entire image to be displayed within the boundaries of the widget. This also greatly reduces the speed at which it will draw frames. + """ + QtGui.QWidget.__init__(self, parent=None) + self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Expanding)) + self.scaled = scaled + self.opts = None + self.image = None + + def setImage(self, img, *args, **kargs): + """ + img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). + Extra arguments are sent to functions.makeARGB + """ + self.opts = (img, args, kargs) + self.image = None + self.update() + + def paintEvent(self, ev): + if self.opts is None: + return + if self.image is None: + argb, alpha = fn.makeARGB(self.opts[0], *self.opts[1], **self.opts[2]) + self.image = fn.makeQImage(argb, alpha) + self.opts = () + #if self.pixmap is None: + #self.pixmap = QtGui.QPixmap.fromImage(self.image) + p = QtGui.QPainter(self) + if self.scaled: + rect = self.rect() + ar = rect.width() / float(rect.height()) + imar = self.image.width() / float(self.image.height()) + if ar > imar: + rect.setWidth(int(rect.width() * imar/ar)) + else: + rect.setHeight(int(rect.height() * ar/imar)) + + p.drawImage(rect, self.image) + else: + p.drawImage(QtCore.QPointF(), self.image) + #p.drawPixmap(self.rect(), self.pixmap) + p.end() + +if HAVE_OPENGL: + class RawImageGLWidget(QtOpenGL.QGLWidget): + """ + Similar to RawImageWidget, but uses a GL widget to do all drawing. + Perfomance varies between platforms; see examples/VideoSpeedTest for benchmarking. + """ + def __init__(self, parent=None, scaled=False): + QtOpenGL.QGLWidget.__init__(self, parent=None) + self.scaled = scaled + self.image = None + self.uploaded = False + self.smooth = False + self.opts = None + + def setImage(self, img, *args, **kargs): + """ + img must be ndarray of shape (x,y), (x,y,3), or (x,y,4). + Extra arguments are sent to functions.makeARGB + """ + self.opts = (img, args, kargs) + self.image = None + self.uploaded = False + self.update() + + def initializeGL(self): + self.texture = glGenTextures(1) + + def uploadTexture(self): + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.texture) + if self.smooth: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + else: + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER) + #glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER) + shape = self.image.shape + + ### Test texture dimensions first + #glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, None) + #if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0: + #raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2]) + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, shape[0], shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, self.image.transpose((1,0,2))) + glDisable(GL_TEXTURE_2D) + + def paintGL(self): + if self.image is None: + if self.opts is None: + return + img, args, kwds = self.opts + kwds['useRGBA'] = True + self.image, alpha = fn.makeARGB(img, *args, **kwds) + + if not self.uploaded: + self.uploadTexture() + + glViewport(0, 0, self.width(), self.height()) + glEnable(GL_TEXTURE_2D) + glBindTexture(GL_TEXTURE_2D, self.texture) + glColor4f(1,1,1,1) + + glBegin(GL_QUADS) + glTexCoord2f(0,0) + glVertex3f(-1,-1,0) + glTexCoord2f(1,0) + glVertex3f(1, -1, 0) + glTexCoord2f(1,1) + glVertex3f(1, 1, 0) + glTexCoord2f(0,1) + glVertex3f(-1, 1, 0) + glEnd() + glDisable(GL_TEXTURE_3D) + + + diff --git a/papi/pyqtgraph/widgets/RemoteGraphicsView.py b/papi/pyqtgraph/widgets/RemoteGraphicsView.py new file mode 100644 index 00000000..75ce90b0 --- /dev/null +++ b/papi/pyqtgraph/widgets/RemoteGraphicsView.py @@ -0,0 +1,262 @@ +from ..Qt import QtGui, QtCore, USE_PYSIDE +if not USE_PYSIDE: + import sip +from .. import multiprocess as mp +from .GraphicsView import GraphicsView +from .. import CONFIG_OPTIONS +import numpy as np +import mmap, tempfile, ctypes, atexit, sys, random + +__all__ = ['RemoteGraphicsView'] + +class RemoteGraphicsView(QtGui.QWidget): + """ + Replacement for GraphicsView that does all scene management and rendering on a remote process, + while displaying on the local widget. + + GraphicsItems must be created by proxy to the remote process. + + """ + def __init__(self, parent=None, *args, **kwds): + """ + The keyword arguments 'useOpenGL' and 'backgound', if specified, are passed to the remote + GraphicsView.__init__(). All other keyword arguments are passed to multiprocess.QtProcess.__init__(). + """ + self._img = None + self._imgReq = None + self._sizeHint = (640,480) ## no clue why this is needed, but it seems to be the default sizeHint for GraphicsView. + ## without it, the widget will not compete for space against another GraphicsView. + QtGui.QWidget.__init__(self) + + # separate local keyword arguments from remote. + remoteKwds = {} + for kwd in ['useOpenGL', 'background']: + if kwd in kwds: + remoteKwds[kwd] = kwds.pop(kwd) + + self._proc = mp.QtProcess(**kwds) + self.pg = self._proc._import('pyqtgraph') + self.pg.setConfigOptions(**CONFIG_OPTIONS) + rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView') + self._view = rpgRemote.Renderer(*args, **remoteKwds) + self._view._setProxyOptions(deferGetattr=True) + + self.setFocusPolicy(QtCore.Qt.StrongFocus) + self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) + self.setMouseTracking(True) + self.shm = None + shmFileName = self._view.shmFileName() + if sys.platform.startswith('win'): + self.shmtag = shmFileName + else: + self.shmFile = open(shmFileName, 'r') + + self._view.sceneRendered.connect(mp.proxy(self.remoteSceneChanged)) #, callSync='off')) + ## Note: we need synchronous signals + ## even though there is no return value-- + ## this informs the renderer that it is + ## safe to begin rendering again. + + for method in ['scene', 'setCentralItem']: + setattr(self, method, getattr(self._view, method)) + + def resizeEvent(self, ev): + ret = QtGui.QWidget.resizeEvent(self, ev) + self._view.resize(self.size(), _callSync='off') + return ret + + def sizeHint(self): + return QtCore.QSize(*self._sizeHint) + + def remoteSceneChanged(self, data): + w, h, size, newfile = data + #self._sizeHint = (whint, hhint) + if self.shm is None or self.shm.size != size: + if self.shm is not None: + self.shm.close() + if sys.platform.startswith('win'): + self.shmtag = newfile ## on windows, we create a new tag for every resize + self.shm = mmap.mmap(-1, size, self.shmtag) ## can't use tmpfile on windows because the file can only be opened once. + else: + self.shm = mmap.mmap(self.shmFile.fileno(), size, mmap.MAP_SHARED, mmap.PROT_READ) + self.shm.seek(0) + data = self.shm.read(w*h*4) + self._img = QtGui.QImage(data, w, h, QtGui.QImage.Format_ARGB32) + self._img.data = data # data must be kept alive or PySide 1.2.1 (and probably earlier) will crash. + self.update() + + def paintEvent(self, ev): + if self._img is None: + return + p = QtGui.QPainter(self) + p.drawImage(self.rect(), self._img, QtCore.QRect(0, 0, self._img.width(), self._img.height())) + p.end() + + def mousePressEvent(self, ev): + self._view.mousePressEvent(int(ev.type()), ev.pos(), ev.globalPos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') + ev.accept() + return QtGui.QWidget.mousePressEvent(self, ev) + + def mouseReleaseEvent(self, ev): + self._view.mouseReleaseEvent(int(ev.type()), ev.pos(), ev.globalPos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') + ev.accept() + return QtGui.QWidget.mouseReleaseEvent(self, ev) + + def mouseMoveEvent(self, ev): + self._view.mouseMoveEvent(int(ev.type()), ev.pos(), ev.globalPos(), int(ev.button()), int(ev.buttons()), int(ev.modifiers()), _callSync='off') + ev.accept() + return QtGui.QWidget.mouseMoveEvent(self, ev) + + def wheelEvent(self, ev): + self._view.wheelEvent(ev.pos(), ev.globalPos(), ev.delta(), int(ev.buttons()), int(ev.modifiers()), int(ev.orientation()), _callSync='off') + ev.accept() + return QtGui.QWidget.wheelEvent(self, ev) + + def keyEvent(self, ev): + if self._view.keyEvent(int(ev.type()), int(ev.modifiers()), text, autorep, count): + ev.accept() + return QtGui.QWidget.keyEvent(self, ev) + + def enterEvent(self, ev): + self._view.enterEvent(int(ev.type()), _callSync='off') + return QtGui.QWidget.enterEvent(self, ev) + + def leaveEvent(self, ev): + self._view.leaveEvent(int(ev.type()), _callSync='off') + return QtGui.QWidget.leaveEvent(self, ev) + + def remoteProcess(self): + """Return the remote process handle. (see multiprocess.remoteproxy.RemoteEventHandler)""" + return self._proc + + def close(self): + """Close the remote process. After this call, the widget will no longer be updated.""" + self._proc.close() + + +class Renderer(GraphicsView): + ## Created by the remote process to handle render requests + + sceneRendered = QtCore.Signal(object) + + def __init__(self, *args, **kwds): + ## Create shared memory for rendered image + #pg.dbg(namespace={'r': self}) + if sys.platform.startswith('win'): + self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)]) + self.shm = mmap.mmap(-1, mmap.PAGESIZE, self.shmtag) # use anonymous mmap on windows + else: + self.shmFile = tempfile.NamedTemporaryFile(prefix='pyqtgraph_shmem_') + self.shmFile.write(b'\x00' * (mmap.PAGESIZE+1)) + fd = self.shmFile.fileno() + self.shm = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE) + atexit.register(self.close) + + GraphicsView.__init__(self, *args, **kwds) + self.scene().changed.connect(self.update) + self.img = None + self.renderTimer = QtCore.QTimer() + self.renderTimer.timeout.connect(self.renderView) + self.renderTimer.start(16) + + def close(self): + self.shm.close() + if not sys.platform.startswith('win'): + self.shmFile.close() + + def shmFileName(self): + if sys.platform.startswith('win'): + return self.shmtag + else: + return self.shmFile.name + + def update(self): + self.img = None + return GraphicsView.update(self) + + def resize(self, size): + oldSize = self.size() + GraphicsView.resize(self, size) + self.resizeEvent(QtGui.QResizeEvent(size, oldSize)) + self.update() + + def renderView(self): + if self.img is None: + ## make sure shm is large enough and get its address + if self.width() == 0 or self.height() == 0: + return + size = self.width() * self.height() * 4 + if size > self.shm.size(): + if sys.platform.startswith('win'): + ## windows says "WindowsError: [Error 87] the parameter is incorrect" if we try to resize the mmap + self.shm.close() + ## it also says (sometimes) 'access is denied' if we try to reuse the tag. + self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)]) + self.shm = mmap.mmap(-1, size, self.shmtag) + else: + self.shm.resize(size) + + ## render the scene directly to shared memory + if USE_PYSIDE: + ch = ctypes.c_char.from_buffer(self.shm, 0) + #ch = ctypes.c_char_p(address) + self.img = QtGui.QImage(ch, self.width(), self.height(), QtGui.QImage.Format_ARGB32) + else: + address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0)) + + # different versions of pyqt have different requirements here.. + try: + self.img = QtGui.QImage(sip.voidptr(address), self.width(), self.height(), QtGui.QImage.Format_ARGB32) + except TypeError: + try: + self.img = QtGui.QImage(memoryview(buffer(self.shm)), self.width(), self.height(), QtGui.QImage.Format_ARGB32) + except TypeError: + # Works on PyQt 4.9.6 + self.img = QtGui.QImage(address, self.width(), self.height(), QtGui.QImage.Format_ARGB32) + self.img.fill(0xffffffff) + p = QtGui.QPainter(self.img) + self.render(p, self.viewRect(), self.rect()) + p.end() + self.sceneRendered.emit((self.width(), self.height(), self.shm.size(), self.shmFileName())) + + def mousePressEvent(self, typ, pos, gpos, btn, btns, mods): + typ = QtCore.QEvent.Type(typ) + btn = QtCore.Qt.MouseButton(btn) + btns = QtCore.Qt.MouseButtons(btns) + mods = QtCore.Qt.KeyboardModifiers(mods) + return GraphicsView.mousePressEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) + + def mouseMoveEvent(self, typ, pos, gpos, btn, btns, mods): + typ = QtCore.QEvent.Type(typ) + btn = QtCore.Qt.MouseButton(btn) + btns = QtCore.Qt.MouseButtons(btns) + mods = QtCore.Qt.KeyboardModifiers(mods) + return GraphicsView.mouseMoveEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) + + def mouseReleaseEvent(self, typ, pos, gpos, btn, btns, mods): + typ = QtCore.QEvent.Type(typ) + btn = QtCore.Qt.MouseButton(btn) + btns = QtCore.Qt.MouseButtons(btns) + mods = QtCore.Qt.KeyboardModifiers(mods) + return GraphicsView.mouseReleaseEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods)) + + def wheelEvent(self, pos, gpos, d, btns, mods, ori): + btns = QtCore.Qt.MouseButtons(btns) + mods = QtCore.Qt.KeyboardModifiers(mods) + ori = (None, QtCore.Qt.Horizontal, QtCore.Qt.Vertical)[ori] + return GraphicsView.wheelEvent(self, QtGui.QWheelEvent(pos, gpos, d, btns, mods, ori)) + + def keyEvent(self, typ, mods, text, autorep, count): + typ = QtCore.QEvent.Type(typ) + mods = QtCore.Qt.KeyboardModifiers(mods) + GraphicsView.keyEvent(self, QtGui.QKeyEvent(typ, mods, text, autorep, count)) + return ev.accepted() + + def enterEvent(self, typ): + ev = QtCore.QEvent(QtCore.QEvent.Type(typ)) + return GraphicsView.enterEvent(self, ev) + + def leaveEvent(self, typ): + ev = QtCore.QEvent(QtCore.QEvent.Type(typ)) + return GraphicsView.leaveEvent(self, ev) + diff --git a/papi/pyqtgraph/widgets/ScatterPlotWidget.py b/papi/pyqtgraph/widgets/ScatterPlotWidget.py new file mode 100644 index 00000000..02f260ca --- /dev/null +++ b/papi/pyqtgraph/widgets/ScatterPlotWidget.py @@ -0,0 +1,217 @@ +from ..Qt import QtGui, QtCore +from .PlotWidget import PlotWidget +from .DataFilterWidget import DataFilterParameter +from .ColorMapWidget import ColorMapParameter +from .. import parametertree as ptree +from .. import functions as fn +from .. import getConfigOption +from ..graphicsItems.TextItem import TextItem +import numpy as np +from ..pgcollections import OrderedDict + +__all__ = ['ScatterPlotWidget'] + +class ScatterPlotWidget(QtGui.QSplitter): + """ + Given a record array, display a scatter plot of a specific set of data. + This widget includes controls for selecting the columns to plot, + filtering data, and determining symbol color and shape. This widget allows + the user to explore relationships between columns in a record array. + + The widget consists of four components: + + 1) A list of column names from which the user may select 1 or 2 columns + to plot. If one column is selected, the data for that column will be + plotted in a histogram-like manner by using :func:`pseudoScatter() + `. If two columns are selected, then the + scatter plot will be generated with x determined by the first column + that was selected and y by the second. + 2) A DataFilter that allows the user to select a subset of the data by + specifying multiple selection criteria. + 3) A ColorMap that allows the user to determine how points are colored by + specifying multiple criteria. + 4) A PlotWidget for displaying the data. + """ + def __init__(self, parent=None): + QtGui.QSplitter.__init__(self, QtCore.Qt.Horizontal) + self.ctrlPanel = QtGui.QSplitter(QtCore.Qt.Vertical) + self.addWidget(self.ctrlPanel) + self.fieldList = QtGui.QListWidget() + self.fieldList.setSelectionMode(self.fieldList.ExtendedSelection) + self.ptree = ptree.ParameterTree(showHeader=False) + self.filter = DataFilterParameter() + self.colorMap = ColorMapParameter() + self.params = ptree.Parameter.create(name='params', type='group', children=[self.filter, self.colorMap]) + self.ptree.setParameters(self.params, showTop=False) + + self.plot = PlotWidget() + self.ctrlPanel.addWidget(self.fieldList) + self.ctrlPanel.addWidget(self.ptree) + self.addWidget(self.plot) + + bg = fn.mkColor(getConfigOption('background')) + bg.setAlpha(150) + self.filterText = TextItem(border=getConfigOption('foreground'), color=bg) + self.filterText.setPos(60,20) + self.filterText.setParentItem(self.plot.plotItem) + + self.data = None + self.mouseOverField = None + self.scatterPlot = None + self.style = dict(pen=None, symbol='o') + + self.fieldList.itemSelectionChanged.connect(self.fieldSelectionChanged) + self.filter.sigFilterChanged.connect(self.filterChanged) + self.colorMap.sigColorMapChanged.connect(self.updatePlot) + + def setFields(self, fields, mouseOverField=None): + """ + Set the list of field names/units to be processed. + + The format of *fields* is the same as used by + :func:`ColorMapWidget.setFields ` + """ + self.fields = OrderedDict(fields) + self.mouseOverField = mouseOverField + self.fieldList.clear() + for f,opts in fields: + item = QtGui.QListWidgetItem(f) + item.opts = opts + item = self.fieldList.addItem(item) + self.filter.setFields(fields) + self.colorMap.setFields(fields) + + def setData(self, data): + """ + Set the data to be processed and displayed. + Argument must be a numpy record array. + """ + self.data = data + self.filtered = None + self.updatePlot() + + def fieldSelectionChanged(self): + sel = self.fieldList.selectedItems() + if len(sel) > 2: + self.fieldList.blockSignals(True) + try: + for item in sel[1:-1]: + item.setSelected(False) + finally: + self.fieldList.blockSignals(False) + + self.updatePlot() + + def filterChanged(self, f): + self.filtered = None + self.updatePlot() + desc = self.filter.describe() + if len(desc) == 0: + self.filterText.setVisible(False) + else: + self.filterText.setText('\n'.join(desc)) + self.filterText.setVisible(True) + + + def updatePlot(self): + self.plot.clear() + if self.data is None: + return + + if self.filtered is None: + self.filtered = self.filter.filterData(self.data) + data = self.filtered + if len(data) == 0: + return + + colors = np.array([fn.mkBrush(*x) for x in self.colorMap.map(data)]) + + style = self.style.copy() + + ## Look up selected columns and units + sel = list([str(item.text()) for item in self.fieldList.selectedItems()]) + units = list([item.opts.get('units', '') for item in self.fieldList.selectedItems()]) + if len(sel) == 0: + self.plot.setTitle('') + return + + + if len(sel) == 1: + self.plot.setLabels(left=('N', ''), bottom=(sel[0], units[0]), title='') + if len(data) == 0: + return + #x = data[sel[0]] + #y = None + xy = [data[sel[0]], None] + elif len(sel) == 2: + self.plot.setLabels(left=(sel[1],units[1]), bottom=(sel[0],units[0])) + if len(data) == 0: + return + + xy = [data[sel[0]], data[sel[1]]] + #xydata = [] + #for ax in [0,1]: + #d = data[sel[ax]] + ### scatter catecorical values just a bit so they show up better in the scatter plot. + ##if sel[ax] in ['MorphologyBSMean', 'MorphologyTDMean', 'FIType']: + ##d += np.random.normal(size=len(cells), scale=0.1) + + #xydata.append(d) + #x,y = xydata + + ## convert enum-type fields to float, set axis labels + enum = [False, False] + for i in [0,1]: + axis = self.plot.getAxis(['bottom', 'left'][i]) + if xy[i] is not None and (self.fields[sel[i]].get('mode', None) == 'enum' or xy[i].dtype.kind in ('S', 'O')): + vals = self.fields[sel[i]].get('values', list(set(xy[i]))) + xy[i] = np.array([vals.index(x) if x in vals else len(vals) for x in xy[i]], dtype=float) + axis.setTicks([list(enumerate(vals))]) + enum[i] = True + else: + axis.setTicks(None) # reset to automatic ticking + + ## mask out any nan values + mask = np.ones(len(xy[0]), dtype=bool) + if xy[0].dtype.kind == 'f': + mask &= ~np.isnan(xy[0]) + if xy[1] is not None and xy[1].dtype.kind == 'f': + mask &= ~np.isnan(xy[1]) + + xy[0] = xy[0][mask] + style['symbolBrush'] = colors[mask] + + ## Scatter y-values for a histogram-like appearance + if xy[1] is None: + ## column scatter plot + xy[1] = fn.pseudoScatter(xy[0]) + else: + ## beeswarm plots + xy[1] = xy[1][mask] + for ax in [0,1]: + if not enum[ax]: + continue + imax = int(xy[ax].max()) if len(xy[ax]) > 0 else 0 + for i in range(imax+1): + keymask = xy[ax] == i + scatter = fn.pseudoScatter(xy[1-ax][keymask], bidir=True) + if len(scatter) == 0: + continue + smax = np.abs(scatter).max() + if smax != 0: + scatter *= 0.2 / smax + xy[ax][keymask] += scatter + + if self.scatterPlot is not None: + try: + self.scatterPlot.sigPointsClicked.disconnect(self.plotClicked) + except: + pass + self.scatterPlot = self.plot.plot(xy[0], xy[1], data=data[mask], **style) + self.scatterPlot.sigPointsClicked.connect(self.plotClicked) + + + def plotClicked(self, plot, points): + pass + + diff --git a/papi/pyqtgraph/widgets/SpinBox.py b/papi/pyqtgraph/widgets/SpinBox.py new file mode 100644 index 00000000..47101405 --- /dev/null +++ b/papi/pyqtgraph/widgets/SpinBox.py @@ -0,0 +1,518 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore +from ..python2_3 import asUnicode +from ..SignalProxy import SignalProxy + +from .. import functions as fn +from math import log +from decimal import Decimal as D ## Use decimal to avoid accumulating floating-point errors +from decimal import * +import weakref + +__all__ = ['SpinBox'] +class SpinBox(QtGui.QAbstractSpinBox): + """ + **Bases:** QtGui.QAbstractSpinBox + + QSpinBox widget on steroids. Allows selection of numerical value, with extra features: + + - SI prefix notation (eg, automatically display "300 mV" instead of "0.003 V") + - Float values with linear and decimal stepping (1-9, 10-90, 100-900, etc.) + - Option for unbounded values + - Delayed signals (allows multiple rapid changes with only one change signal) + + ============================= ============================================== + **Signals:** + valueChanged(value) Same as QSpinBox; emitted every time the value + has changed. + sigValueChanged(self) Emitted when value has changed, but also combines + multiple rapid changes into one signal (eg, + when rolling the mouse wheel). + sigValueChanging(self, value) Emitted immediately for all value changes. + ============================= ============================================== + """ + + ## There's a PyQt bug that leaks a reference to the + ## QLineEdit returned from QAbstractSpinBox.lineEdit() + ## This makes it possible to crash the entire program + ## by making accesses to the LineEdit after the spinBox has been deleted. + ## I have no idea how to get around this.. + + + valueChanged = QtCore.Signal(object) # (value) for compatibility with QSpinBox + sigValueChanged = QtCore.Signal(object) # (self) + sigValueChanging = QtCore.Signal(object, object) # (self, value) sent immediately; no delay. + + def __init__(self, parent=None, value=0.0, **kwargs): + """ + ============== ======================================================================== + **Arguments:** + parent Sets the parent widget for this SpinBox (optional). Default is None. + value (float/int) initial value. Default is 0.0. + bounds (min,max) Minimum and maximum values allowed in the SpinBox. + Either may be None to leave the value unbounded. By default, values are unbounded. + suffix (str) suffix (units) to display after the numerical value. By default, suffix is an empty str. + siPrefix (bool) If True, then an SI prefix is automatically prepended + to the units and the value is scaled accordingly. For example, + if value=0.003 and suffix='V', then the SpinBox will display + "300 mV" (but a call to SpinBox.value will still return 0.003). Default is False. + step (float) The size of a single step. This is used when clicking the up/ + down arrows, when rolling the mouse wheel, or when pressing + keyboard arrows while the widget has keyboard focus. Note that + the interpretation of this value is different when specifying + the 'dec' argument. Default is 0.01. + dec (bool) If True, then the step value will be adjusted to match + the current size of the variable (for example, a value of 15 + might step in increments of 1 whereas a value of 1500 would + step in increments of 100). In this case, the 'step' argument + is interpreted *relative* to the current value. The most common + 'step' values when dec=True are 0.1, 0.2, 0.5, and 1.0. Default is False. + minStep (float) When dec=True, this specifies the minimum allowable step size. + int (bool) if True, the value is forced to integer type. Default is False + decimals (int) Number of decimal values to display. Default is 2. + ============== ======================================================================== + """ + QtGui.QAbstractSpinBox.__init__(self, parent) + self.lastValEmitted = None + self.lastText = '' + self.textValid = True ## If false, we draw a red border + self.setMinimumWidth(0) + self.setMaximumHeight(20) + self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Preferred) + self.opts = { + 'bounds': [None, None], + + ## Log scaling options #### Log mode is no longer supported. + #'step': 0.1, + #'minStep': 0.001, + #'log': True, + #'dec': False, + + ## decimal scaling option - example + #'step': 0.1, + #'minStep': .001, + #'log': False, + #'dec': True, + + ## normal arithmetic step + 'step': D('0.01'), ## if 'dec' is false, the spinBox steps by 'step' every time + ## if 'dec' is True, the step size is relative to the value + ## 'step' needs to be an integral divisor of ten, ie 'step'*n=10 for some integer value of n (but only if dec is True) + 'log': False, + 'dec': False, ## if true, does decimal stepping. ie from 1-10 it steps by 'step', from 10 to 100 it steps by 10*'step', etc. + ## if true, minStep must be set in order to cross zero. + + + 'int': False, ## Set True to force value to be integer + + 'suffix': '', + 'siPrefix': False, ## Set to True to display numbers with SI prefix (ie, 100pA instead of 1e-10A) + + 'delay': 0.3, ## delay sending wheel update signals for 300ms + + 'delayUntilEditFinished': True, ## do not send signals until text editing has finished + + ## for compatibility with QDoubleSpinBox and QSpinBox + 'decimals': 2, + + } + + self.decOpts = ['step', 'minStep'] + + self.val = D(asUnicode(value)) ## Value is precise decimal. Ordinary math not allowed. + self.updateText() + self.skipValidate = False + self.setCorrectionMode(self.CorrectToPreviousValue) + self.setKeyboardTracking(False) + self.setOpts(**kwargs) + + + self.editingFinished.connect(self.editingFinishedEvent) + self.proxy = SignalProxy(self.sigValueChanging, slot=self.delayedChange, delay=self.opts['delay']) + + def event(self, ev): + ret = QtGui.QAbstractSpinBox.event(self, ev) + if ev.type() == QtCore.QEvent.KeyPress and ev.key() == QtCore.Qt.Key_Return: + ret = True ## For some reason, spinbox pretends to ignore return key press + return ret + + ##lots of config options, just gonna stuff 'em all in here rather than do the get/set crap. + def setOpts(self, **opts): + """ + Changes the behavior of the SpinBox. Accepts most of the arguments + allowed in :func:`__init__ `. + + """ + #print opts + for k in opts: + if k == 'bounds': + #print opts[k] + self.setMinimum(opts[k][0], update=False) + self.setMaximum(opts[k][1], update=False) + #for i in [0,1]: + #if opts[k][i] is None: + #self.opts[k][i] = None + #else: + #self.opts[k][i] = D(unicode(opts[k][i])) + elif k in ['step', 'minStep']: + self.opts[k] = D(asUnicode(opts[k])) + elif k == 'value': + pass ## don't set value until bounds have been set + else: + self.opts[k] = opts[k] + if 'value' in opts: + self.setValue(opts['value']) + + ## If bounds have changed, update value to match + if 'bounds' in opts and 'value' not in opts: + self.setValue() + + ## sanity checks: + if self.opts['int']: + if 'step' in opts: + step = opts['step'] + ## not necessary.. + #if int(step) != step: + #raise Exception('Integer SpinBox must have integer step size.') + else: + self.opts['step'] = int(self.opts['step']) + + if 'minStep' in opts: + step = opts['minStep'] + if int(step) != step: + raise Exception('Integer SpinBox must have integer minStep size.') + else: + ms = int(self.opts.get('minStep', 1)) + if ms < 1: + ms = 1 + self.opts['minStep'] = ms + + if 'delay' in opts: + self.proxy.setDelay(opts['delay']) + + self.updateText() + + + + def setMaximum(self, m, update=True): + """Set the maximum allowed value (or None for no limit)""" + if m is not None: + m = D(asUnicode(m)) + self.opts['bounds'][1] = m + if update: + self.setValue() + + def setMinimum(self, m, update=True): + """Set the minimum allowed value (or None for no limit)""" + if m is not None: + m = D(asUnicode(m)) + self.opts['bounds'][0] = m + if update: + self.setValue() + + def setPrefix(self, p): + self.setOpts(prefix=p) + + def setRange(self, r0, r1): + self.setOpts(bounds = [r0,r1]) + + def setProperty(self, prop, val): + ## for QSpinBox compatibility + if prop == 'value': + #if type(val) is QtCore.QVariant: + #val = val.toDouble()[0] + self.setValue(val) + else: + print("Warning: SpinBox.setProperty('%s', ..) not supported." % prop) + + def setSuffix(self, suf): + self.setOpts(suffix=suf) + + def setSingleStep(self, step): + self.setOpts(step=step) + + def setDecimals(self, decimals): + self.setOpts(decimals=decimals) + + def selectNumber(self): + """ + Select the numerical portion of the text to allow quick editing by the user. + """ + le = self.lineEdit() + text = asUnicode(le.text()) + if self.opts['suffix'] == '': + le.setSelection(0, len(text)) + else: + try: + index = text.index(' ') + except ValueError: + return + le.setSelection(0, index) + + def value(self): + """ + Return the value of this SpinBox. + + """ + if self.opts['int']: + return int(self.val) + else: + return float(self.val) + + def setValue(self, value=None, update=True, delaySignal=False): + """ + Set the value of this spin. + If the value is out of bounds, it will be clipped to the nearest boundary. + If the spin is integer type, the value will be coerced to int. + Returns the actual value set. + + If value is None, then the current value is used (this is for resetting + the value after bounds, etc. have changed) + """ + + if value is None: + value = self.value() + + bounds = self.opts['bounds'] + if bounds[0] is not None and value < bounds[0]: + value = bounds[0] + if bounds[1] is not None and value > bounds[1]: + value = bounds[1] + + if self.opts['int']: + value = int(value) + + value = D(asUnicode(value)) + if value == self.val: + return + prev = self.val + + self.val = value + if update: + self.updateText(prev=prev) + + self.sigValueChanging.emit(self, float(self.val)) ## change will be emitted in 300ms if there are no subsequent changes. + if not delaySignal: + self.emitChanged() + + return value + + + def emitChanged(self): + self.lastValEmitted = self.val + self.valueChanged.emit(float(self.val)) + self.sigValueChanged.emit(self) + + def delayedChange(self): + try: + if self.val != self.lastValEmitted: + self.emitChanged() + except RuntimeError: + pass ## This can happen if we try to handle a delayed signal after someone else has already deleted the underlying C++ object. + + def widgetGroupInterface(self): + return (self.valueChanged, SpinBox.value, SpinBox.setValue) + + def sizeHint(self): + return QtCore.QSize(120, 0) + + + def stepEnabled(self): + return self.StepUpEnabled | self.StepDownEnabled + + #def fixup(self, *args): + #print "fixup:", args + + def stepBy(self, n): + n = D(int(n)) ## n must be integral number of steps. + s = [D(-1), D(1)][n >= 0] ## determine sign of step + val = self.val + + for i in range(int(abs(n))): + + if self.opts['log']: + raise Exception("Log mode no longer supported.") + # step = abs(val) * self.opts['step'] + # if 'minStep' in self.opts: + # step = max(step, self.opts['minStep']) + # val += step * s + if self.opts['dec']: + if val == 0: + step = self.opts['minStep'] + exp = None + else: + vs = [D(-1), D(1)][val >= 0] + #exp = D(int(abs(val*(D('1.01')**(s*vs))).log10())) + fudge = D('1.01')**(s*vs) ## fudge factor. at some places, the step size depends on the step sign. + exp = abs(val * fudge).log10().quantize(1, ROUND_FLOOR) + step = self.opts['step'] * D(10)**exp + if 'minStep' in self.opts: + step = max(step, self.opts['minStep']) + val += s * step + #print "Exp:", exp, "step", step, "val", val + else: + val += s*self.opts['step'] + + if 'minStep' in self.opts and abs(val) < self.opts['minStep']: + val = D(0) + self.setValue(val, delaySignal=True) ## note all steps (arrow buttons, wheel, up/down keys..) emit delayed signals only. + + + def valueInRange(self, value): + bounds = self.opts['bounds'] + if bounds[0] is not None and value < bounds[0]: + return False + if bounds[1] is not None and value > bounds[1]: + return False + if self.opts.get('int', False): + if int(value) != value: + return False + return True + + + def updateText(self, prev=None): + #print "Update text." + self.skipValidate = True + if self.opts['siPrefix']: + if self.val == 0 and prev is not None: + (s, p) = fn.siScale(prev) + txt = "0.0 %s%s" % (p, self.opts['suffix']) + else: + txt = fn.siFormat(float(self.val), suffix=self.opts['suffix']) + else: + txt = '%g%s' % (self.val , self.opts['suffix']) + self.lineEdit().setText(txt) + self.lastText = txt + self.skipValidate = False + + def validate(self, strn, pos): + if self.skipValidate: + #print "skip validate" + #self.textValid = False + ret = QtGui.QValidator.Acceptable + else: + try: + ## first make sure we didn't mess with the suffix + suff = self.opts.get('suffix', '') + if len(suff) > 0 and asUnicode(strn)[-len(suff):] != suff: + #print '"%s" != "%s"' % (unicode(strn)[-len(suff):], suff) + ret = QtGui.QValidator.Invalid + + ## next see if we actually have an interpretable value + else: + val = self.interpret() + if val is False: + #print "can't interpret" + #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') + #self.textValid = False + ret = QtGui.QValidator.Intermediate + else: + if self.valueInRange(val): + if not self.opts['delayUntilEditFinished']: + self.setValue(val, update=False) + #print " OK:", self.val + #self.setStyleSheet('') + #self.textValid = True + + ret = QtGui.QValidator.Acceptable + else: + ret = QtGui.QValidator.Intermediate + + except: + #print " BAD" + #import sys + #sys.excepthook(*sys.exc_info()) + #self.textValid = False + #self.setStyleSheet('SpinBox {border: 2px solid #C55;}') + ret = QtGui.QValidator.Intermediate + + ## draw / clear border + if ret == QtGui.QValidator.Intermediate: + self.textValid = False + elif ret == QtGui.QValidator.Acceptable: + self.textValid = True + ## note: if text is invalid, we don't change the textValid flag + ## since the text will be forced to its previous state anyway + self.update() + + ## support 2 different pyqt APIs. Bleh. + if hasattr(QtCore, 'QString'): + return (ret, pos) + else: + return (ret, strn, pos) + + def paintEvent(self, ev): + QtGui.QAbstractSpinBox.paintEvent(self, ev) + + ## draw red border if text is invalid + if not self.textValid: + p = QtGui.QPainter(self) + p.setRenderHint(p.Antialiasing) + p.setPen(fn.mkPen((200,50,50), width=2)) + p.drawRoundedRect(self.rect().adjusted(2, 2, -2, -2), 4, 4) + p.end() + + + def interpret(self): + """Return value of text. Return False if text is invalid, raise exception if text is intermediate""" + strn = self.lineEdit().text() + suf = self.opts['suffix'] + if len(suf) > 0: + if strn[-len(suf):] != suf: + return False + #raise Exception("Units are invalid.") + strn = strn[:-len(suf)] + try: + val = fn.siEval(strn) + except: + #sys.excepthook(*sys.exc_info()) + #print "invalid" + return False + #print val + return val + + #def interpretText(self, strn=None): + #print "Interpret:", strn + #if strn is None: + #strn = self.lineEdit().text() + #self.setValue(siEval(strn), update=False) + ##QtGui.QAbstractSpinBox.interpretText(self) + + + def editingFinishedEvent(self): + """Edit has finished; set value.""" + #print "Edit finished." + if asUnicode(self.lineEdit().text()) == self.lastText: + #print "no text change." + return + try: + val = self.interpret() + except: + return + + if val is False: + #print "value invalid:", str(self.lineEdit().text()) + return + if val == self.val: + #print "no value change:", val, self.val + return + self.setValue(val, delaySignal=False) ## allow text update so that values are reformatted pretty-like + + #def textChanged(self): + #print "Text changed." + + +### Drop-in replacement for SpinBox; just for crash-testing +#class SpinBox(QtGui.QDoubleSpinBox): + #valueChanged = QtCore.Signal(object) # (value) for compatibility with QSpinBox + #sigValueChanged = QtCore.Signal(object) # (self) + #sigValueChanging = QtCore.Signal(object) # (value) + #def __init__(self, parent=None, *args, **kargs): + #QtGui.QSpinBox.__init__(self, parent) + + #def __getattr__(self, attr): + #return lambda *args, **kargs: None + + #def widgetGroupInterface(self): + #return (self.valueChanged, SpinBox.value, SpinBox.setValue) + diff --git a/papi/pyqtgraph/widgets/TableWidget.py b/papi/pyqtgraph/widgets/TableWidget.py new file mode 100644 index 00000000..69085a20 --- /dev/null +++ b/papi/pyqtgraph/widgets/TableWidget.py @@ -0,0 +1,504 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore +from ..python2_3 import asUnicode + +import numpy as np +try: + import metaarray + HAVE_METAARRAY = True +except ImportError: + HAVE_METAARRAY = False + + +__all__ = ['TableWidget'] + + +def _defersort(fn): + def defersort(self, *args, **kwds): + # may be called recursively; only the first call needs to block sorting + setSorting = False + if self._sorting is None: + self._sorting = self.isSortingEnabled() + setSorting = True + self.setSortingEnabled(False) + try: + return fn(self, *args, **kwds) + finally: + if setSorting: + self.setSortingEnabled(self._sorting) + self._sorting = None + + return defersort + + +class TableWidget(QtGui.QTableWidget): + """Extends QTableWidget with some useful functions for automatic data handling + and copy / export context menu. Can automatically format and display a variety + of data types (see :func:`setData() ` for more + information. + """ + + def __init__(self, *args, **kwds): + """ + All positional arguments are passed to QTableWidget.__init__(). + + ===================== ================================================= + **Keyword Arguments** + editable (bool) If True, cells in the table can be edited + by the user. Default is False. + sortable (bool) If True, the table may be soted by + clicking on column headers. Note that this also + causes rows to appear initially shuffled until + a sort column is selected. Default is True. + *(added in version 0.9.9)* + ===================== ================================================= + """ + + QtGui.QTableWidget.__init__(self, *args) + + self.itemClass = TableWidgetItem + + self.setVerticalScrollMode(self.ScrollPerPixel) + self.setSelectionMode(QtGui.QAbstractItemView.ContiguousSelection) + self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred) + self.clear() + + kwds.setdefault('sortable', True) + kwds.setdefault('editable', False) + self.setEditable(kwds.pop('editable')) + self.setSortingEnabled(kwds.pop('sortable')) + + if len(kwds) > 0: + raise TypeError("Invalid keyword arguments '%s'" % kwds.keys()) + + self._sorting = None # used when temporarily disabling sorting + + self._formats = {None: None} # stores per-column formats and entire table format + self.sortModes = {} # stores per-column sort mode + + self.itemChanged.connect(self.handleItemChanged) + + self.contextMenu = QtGui.QMenu() + self.contextMenu.addAction('Copy Selection').triggered.connect(self.copySel) + self.contextMenu.addAction('Copy All').triggered.connect(self.copyAll) + self.contextMenu.addAction('Save Selection').triggered.connect(self.saveSel) + self.contextMenu.addAction('Save All').triggered.connect(self.saveAll) + + def clear(self): + """Clear all contents from the table.""" + QtGui.QTableWidget.clear(self) + self.verticalHeadersSet = False + self.horizontalHeadersSet = False + self.items = [] + self.setRowCount(0) + self.setColumnCount(0) + self.sortModes = {} + + def setData(self, data): + """Set the data displayed in the table. + Allowed formats are: + + * numpy arrays + * numpy record arrays + * metaarrays + * list-of-lists [[1,2,3], [4,5,6]] + * dict-of-lists {'x': [1,2,3], 'y': [4,5,6]} + * list-of-dicts [{'x': 1, 'y': 4}, {'x': 2, 'y': 5}, ...] + """ + self.clear() + self.appendData(data) + self.resizeColumnsToContents() + + @_defersort + def appendData(self, data): + """ + Add new rows to the table. + + See :func:`setData() ` for accepted + data types. + """ + startRow = self.rowCount() + + fn0, header0 = self.iteratorFn(data) + if fn0 is None: + self.clear() + return + it0 = fn0(data) + try: + first = next(it0) + except StopIteration: + return + fn1, header1 = self.iteratorFn(first) + if fn1 is None: + self.clear() + return + + firstVals = [x for x in fn1(first)] + self.setColumnCount(len(firstVals)) + + if not self.verticalHeadersSet and header0 is not None: + labels = [self.verticalHeaderItem(i).text() for i in range(self.rowCount())] + self.setRowCount(startRow + len(header0)) + self.setVerticalHeaderLabels(labels + header0) + self.verticalHeadersSet = True + if not self.horizontalHeadersSet and header1 is not None: + self.setHorizontalHeaderLabels(header1) + self.horizontalHeadersSet = True + + i = startRow + self.setRow(i, firstVals) + for row in it0: + i += 1 + self.setRow(i, [x for x in fn1(row)]) + + if self._sorting and self.horizontalHeader().sortIndicatorSection() >= self.columnCount(): + self.sortByColumn(0, QtCore.Qt.AscendingOrder) + + def setEditable(self, editable=True): + self.editable = editable + for item in self.items: + item.setEditable(editable) + + def setFormat(self, format, column=None): + """ + Specify the default text formatting for the entire table, or for a + single column if *column* is specified. + + If a string is specified, it is used as a format string for converting + float values (and all other types are converted using str). If a + function is specified, it will be called with the item as its only + argument and must return a string. Setting format = None causes the + default formatter to be used instead. + + Added in version 0.9.9. + + """ + if format is not None and not isinstance(format, basestring) and not callable(format): + raise ValueError("Format argument must string, callable, or None. (got %s)" % format) + + self._formats[column] = format + + + if column is None: + # update format of all items that do not have a column format + # specified + for c in range(self.columnCount()): + if self._formats.get(c, None) is None: + for r in range(self.rowCount()): + item = self.item(r, c) + if item is None: + continue + item.setFormat(format) + else: + # set all items in the column to use this format, or the default + # table format if None was specified. + if format is None: + format = self._formats[None] + for r in range(self.rowCount()): + item = self.item(r, column) + if item is None: + continue + item.setFormat(format) + + + def iteratorFn(self, data): + ## Return 1) a function that will provide an iterator for data and 2) a list of header strings + if isinstance(data, list) or isinstance(data, tuple): + return lambda d: d.__iter__(), None + elif isinstance(data, dict): + return lambda d: iter(d.values()), list(map(asUnicode, data.keys())) + elif HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')): + if data.axisHasColumns(0): + header = [asUnicode(data.columnName(0, i)) for i in range(data.shape[0])] + elif data.axisHasValues(0): + header = list(map(asUnicode, data.xvals(0))) + else: + header = None + return self.iterFirstAxis, header + elif isinstance(data, np.ndarray): + return self.iterFirstAxis, None + elif isinstance(data, np.void): + return self.iterate, list(map(asUnicode, data.dtype.names)) + elif data is None: + return (None,None) + else: + msg = "Don't know how to iterate over data type: {!s}".format(type(data)) + raise TypeError(msg) + + def iterFirstAxis(self, data): + for i in range(data.shape[0]): + yield data[i] + + def iterate(self, data): + # for numpy.void, which can be iterated but mysteriously + # has no __iter__ (??) + for x in data: + yield x + + def appendRow(self, data): + self.appendData([data]) + + @_defersort + def addRow(self, vals): + row = self.rowCount() + self.setRowCount(row + 1) + self.setRow(row, vals) + + @_defersort + def setRow(self, row, vals): + if row > self.rowCount() - 1: + self.setRowCount(row + 1) + for col in range(len(vals)): + val = vals[col] + item = self.itemClass(val, row) + item.setEditable(self.editable) + sortMode = self.sortModes.get(col, None) + if sortMode is not None: + item.setSortMode(sortMode) + format = self._formats.get(col, self._formats[None]) + item.setFormat(format) + self.items.append(item) + self.setItem(row, col, item) + item.setValue(val) # Required--the text-change callback is invoked + # when we call setItem. + + def setSortMode(self, column, mode): + """ + Set the mode used to sort *column*. + + ============== ======================================================== + **Sort Modes** + value Compares item.value if available; falls back to text + comparison. + text Compares item.text() + index Compares by the order in which items were inserted. + ============== ======================================================== + + Added in version 0.9.9 + """ + for r in range(self.rowCount()): + item = self.item(r, column) + if hasattr(item, 'setSortMode'): + item.setSortMode(mode) + self.sortModes[column] = mode + + def sizeHint(self): + # based on http://stackoverflow.com/a/7195443/54056 + width = sum(self.columnWidth(i) for i in range(self.columnCount())) + width += self.verticalHeader().sizeHint().width() + width += self.verticalScrollBar().sizeHint().width() + width += self.frameWidth() * 2 + height = sum(self.rowHeight(i) for i in range(self.rowCount())) + height += self.verticalHeader().sizeHint().height() + height += self.horizontalScrollBar().sizeHint().height() + return QtCore.QSize(width, height) + + def serialize(self, useSelection=False): + """Convert entire table (or just selected area) into tab-separated text values""" + if useSelection: + selection = self.selectedRanges()[0] + rows = list(range(selection.topRow(), + selection.bottomRow() + 1)) + columns = list(range(selection.leftColumn(), + selection.rightColumn() + 1)) + else: + rows = list(range(self.rowCount())) + columns = list(range(self.columnCount())) + + data = [] + if self.horizontalHeadersSet: + row = [] + if self.verticalHeadersSet: + row.append(asUnicode('')) + + for c in columns: + row.append(asUnicode(self.horizontalHeaderItem(c).text())) + data.append(row) + + for r in rows: + row = [] + if self.verticalHeadersSet: + row.append(asUnicode(self.verticalHeaderItem(r).text())) + for c in columns: + item = self.item(r, c) + if item is not None: + row.append(asUnicode(item.value)) + else: + row.append(asUnicode('')) + data.append(row) + + s = '' + for row in data: + s += ('\t'.join(row) + '\n') + return s + + def copySel(self): + """Copy selected data to clipboard.""" + QtGui.QApplication.clipboard().setText(self.serialize(useSelection=True)) + + def copyAll(self): + """Copy all data to clipboard.""" + QtGui.QApplication.clipboard().setText(self.serialize(useSelection=False)) + + def saveSel(self): + """Save selected data to file.""" + self.save(self.serialize(useSelection=True)) + + def saveAll(self): + """Save all data to file.""" + self.save(self.serialize(useSelection=False)) + + def save(self, data): + fileName = QtGui.QFileDialog.getSaveFileName(self, "Save As..", "", "Tab-separated values (*.tsv)") + if fileName == '': + return + open(fileName, 'w').write(data) + + def contextMenuEvent(self, ev): + self.contextMenu.popup(ev.globalPos()) + + def keyPressEvent(self, ev): + if ev.text() == 'c' and ev.modifiers() == QtCore.Qt.ControlModifier: + ev.accept() + self.copy() + else: + ev.ignore() + + def handleItemChanged(self, item): + item.itemChanged() + + +class TableWidgetItem(QtGui.QTableWidgetItem): + def __init__(self, val, index, format=None): + QtGui.QTableWidgetItem.__init__(self, '') + self._blockValueChange = False + self._format = None + self._defaultFormat = '%0.3g' + self.sortMode = 'value' + self.index = index + flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled + self.setFlags(flags) + self.setValue(val) + self.setFormat(format) + + def setEditable(self, editable): + """ + Set whether this item is user-editable. + """ + if editable: + self.setFlags(self.flags() | QtCore.Qt.ItemIsEditable) + else: + self.setFlags(self.flags() & ~QtCore.Qt.ItemIsEditable) + + def setSortMode(self, mode): + """ + Set the mode used to sort this item against others in its column. + + ============== ======================================================== + **Sort Modes** + value Compares item.value if available; falls back to text + comparison. + text Compares item.text() + index Compares by the order in which items were inserted. + ============== ======================================================== + """ + modes = ('value', 'text', 'index', None) + if mode not in modes: + raise ValueError('Sort mode must be one of %s' % str(modes)) + self.sortMode = mode + + def setFormat(self, fmt): + """Define the conversion from item value to displayed text. + + If a string is specified, it is used as a format string for converting + float values (and all other types are converted using str). If a + function is specified, it will be called with the item as its only + argument and must return a string. + + Added in version 0.9.9. + """ + if fmt is not None and not isinstance(fmt, basestring) and not callable(fmt): + raise ValueError("Format argument must string, callable, or None. (got %s)" % fmt) + self._format = fmt + self._updateText() + + def _updateText(self): + self._blockValueChange = True + try: + self._text = self.format() + self.setText(self._text) + finally: + self._blockValueChange = False + + def setValue(self, value): + self.value = value + self._updateText() + + def itemChanged(self): + """Called when the data of this item has changed.""" + if self.text() != self._text: + self.textChanged() + + def textChanged(self): + """Called when this item's text has changed for any reason.""" + self._text = self.text() + + if self._blockValueChange: + # text change was result of value or format change; do not + # propagate. + return + + try: + + self.value = type(self.value)(self.text()) + except ValueError: + self.value = str(self.text()) + + def format(self): + if callable(self._format): + return self._format(self) + if isinstance(self.value, (float, np.floating)): + if self._format is None: + return self._defaultFormat % self.value + else: + return self._format % self.value + else: + return asUnicode(self.value) + + def __lt__(self, other): + if self.sortMode == 'index' and hasattr(other, 'index'): + return self.index < other.index + if self.sortMode == 'value' and hasattr(other, 'value'): + return self.value < other.value + else: + return self.text() < other.text() + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + t = TableWidget() + win.setCentralWidget(t) + win.resize(800,600) + win.show() + + ll = [[1,2,3,4,5]] * 20 + ld = [{'x': 1, 'y': 2, 'z': 3}] * 20 + dl = {'x': list(range(20)), 'y': list(range(20)), 'z': list(range(20))} + + a = np.ones((20, 5)) + ra = np.ones((20,), dtype=[('x', int), ('y', int), ('z', int)]) + + t.setData(ll) + + if HAVE_METAARRAY: + ma = metaarray.MetaArray(np.ones((20, 3)), info=[ + {'values': np.linspace(1, 5, 20)}, + {'cols': [ + {'name': 'x'}, + {'name': 'y'}, + {'name': 'z'}, + ]} + ]) + t.setData(ma) + diff --git a/papi/pyqtgraph/widgets/TreeWidget.py b/papi/pyqtgraph/widgets/TreeWidget.py new file mode 100644 index 00000000..ec2c35cf --- /dev/null +++ b/papi/pyqtgraph/widgets/TreeWidget.py @@ -0,0 +1,284 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore +from weakref import * + +__all__ = ['TreeWidget', 'TreeWidgetItem'] +class TreeWidget(QtGui.QTreeWidget): + """Extends QTreeWidget to allow internal drag/drop with widgets in the tree. + Also maintains the expanded state of subtrees as they are moved. + This class demonstrates the absurd lengths one must go to to make drag/drop work.""" + + sigItemMoved = QtCore.Signal(object, object, object) # (item, parent, index) + + def __init__(self, parent=None): + QtGui.QTreeWidget.__init__(self, parent) + #self.itemWidgets = WeakKeyDictionary() + self.setAcceptDrops(True) + self.setDragEnabled(True) + self.setEditTriggers(QtGui.QAbstractItemView.EditKeyPressed|QtGui.QAbstractItemView.SelectedClicked) + self.placeholders = [] + self.childNestingLimit = None + + def setItemWidget(self, item, col, wid): + """ + Overrides QTreeWidget.setItemWidget such that widgets are added inside an invisible wrapper widget. + This makes it possible to move the item in and out of the tree without its widgets being automatically deleted. + """ + w = QtGui.QWidget() ## foster parent / surrogate child widget + l = QtGui.QVBoxLayout() + l.setContentsMargins(0,0,0,0) + w.setLayout(l) + w.setSizePolicy(wid.sizePolicy()) + w.setMinimumHeight(wid.minimumHeight()) + w.setMinimumWidth(wid.minimumWidth()) + l.addWidget(wid) + w.realChild = wid + self.placeholders.append(w) + QtGui.QTreeWidget.setItemWidget(self, item, col, w) + + def itemWidget(self, item, col): + w = QtGui.QTreeWidget.itemWidget(self, item, col) + if w is not None: + w = w.realChild + return w + + def dropMimeData(self, parent, index, data, action): + item = self.currentItem() + p = parent + #print "drop", item, "->", parent, index + while True: + if p is None: + break + if p is item: + return False + #raise Exception("Can not move item into itself.") + p = p.parent() + + if not self.itemMoving(item, parent, index): + return False + + currentParent = item.parent() + if currentParent is None: + currentParent = self.invisibleRootItem() + if parent is None: + parent = self.invisibleRootItem() + + if currentParent is parent and index > parent.indexOfChild(item): + index -= 1 + + self.prepareMove(item) + + currentParent.removeChild(item) + #print " insert child to index", index + parent.insertChild(index, item) ## index will not be correct + self.setCurrentItem(item) + + self.recoverMove(item) + #self.emit(QtCore.SIGNAL('itemMoved'), item, parent, index) + self.sigItemMoved.emit(item, parent, index) + return True + + def itemMoving(self, item, parent, index): + """Called when item has been dropped elsewhere in the tree. + Return True to accept the move, False to reject.""" + return True + + def prepareMove(self, item): + item.__widgets = [] + item.__expanded = item.isExpanded() + for i in range(self.columnCount()): + w = self.itemWidget(item, i) + item.__widgets.append(w) + if w is None: + continue + w.setParent(None) + for i in range(item.childCount()): + self.prepareMove(item.child(i)) + + def recoverMove(self, item): + for i in range(self.columnCount()): + w = item.__widgets[i] + if w is None: + continue + self.setItemWidget(item, i, w) + for i in range(item.childCount()): + self.recoverMove(item.child(i)) + + item.setExpanded(False) ## Items do not re-expand correctly unless they are collapsed first. + QtGui.QApplication.instance().processEvents() + item.setExpanded(item.__expanded) + + def collapseTree(self, item): + item.setExpanded(False) + for i in range(item.childCount()): + self.collapseTree(item.child(i)) + + def removeTopLevelItem(self, item): + for i in range(self.topLevelItemCount()): + if self.topLevelItem(i) is item: + self.takeTopLevelItem(i) + return + raise Exception("Item '%s' not in top-level items." % str(item)) + + def listAllItems(self, item=None): + items = [] + if item != None: + items.append(item) + else: + item = self.invisibleRootItem() + + for cindex in range(item.childCount()): + foundItems = self.listAllItems(item=item.child(cindex)) + for f in foundItems: + items.append(f) + return items + + def dropEvent(self, ev): + QtGui.QTreeWidget.dropEvent(self, ev) + self.updateDropFlags() + + + def updateDropFlags(self): + ### intended to put a limit on how deep nests of children can go. + ### self.childNestingLimit is upheld when moving items without children, but if the item being moved has children/grandchildren, the children/grandchildren + ### can end up over the childNestingLimit. + if self.childNestingLimit == None: + pass # enable drops in all items (but only if there are drops that aren't enabled? for performance...) + else: + items = self.listAllItems() + for item in items: + parentCount = 0 + p = item.parent() + while p is not None: + parentCount += 1 + p = p.parent() + if parentCount >= self.childNestingLimit: + item.setFlags(item.flags() & (~QtCore.Qt.ItemIsDropEnabled)) + else: + item.setFlags(item.flags() | QtCore.Qt.ItemIsDropEnabled) + + @staticmethod + def informTreeWidgetChange(item): + if hasattr(item, 'treeWidgetChanged'): + item.treeWidgetChanged() + else: + for i in xrange(item.childCount()): + TreeWidget.informTreeWidgetChange(item.child(i)) + + + def addTopLevelItem(self, item): + QtGui.QTreeWidget.addTopLevelItem(self, item) + self.informTreeWidgetChange(item) + + def addTopLevelItems(self, items): + QtGui.QTreeWidget.addTopLevelItems(self, items) + for item in items: + self.informTreeWidgetChange(item) + + def insertTopLevelItem(self, index, item): + QtGui.QTreeWidget.insertTopLevelItem(self, index, item) + self.informTreeWidgetChange(item) + + def insertTopLevelItems(self, index, items): + QtGui.QTreeWidget.insertTopLevelItems(self, index, items) + for item in items: + self.informTreeWidgetChange(item) + + def takeTopLevelItem(self, index): + item = self.topLevelItem(index) + if item is not None: + self.prepareMove(item) + item = QtGui.QTreeWidget.takeTopLevelItem(self, index) + self.prepareMove(item) + self.informTreeWidgetChange(item) + return item + + def topLevelItems(self): + return map(self.topLevelItem, xrange(self.topLevelItemCount())) + + def clear(self): + items = self.topLevelItems() + for item in items: + self.prepareMove(item) + QtGui.QTreeWidget.clear(self) + + ## Why do we want to do this? It causes RuntimeErrors. + #for item in items: + #self.informTreeWidgetChange(item) + + +class TreeWidgetItem(QtGui.QTreeWidgetItem): + """ + TreeWidgetItem that keeps track of its own widgets. + Widgets may be added to columns before the item is added to a tree. + """ + def __init__(self, *args): + QtGui.QTreeWidgetItem.__init__(self, *args) + self._widgets = {} # col: widget + self._tree = None + + + def setChecked(self, column, checked): + self.setCheckState(column, QtCore.Qt.Checked if checked else QtCore.Qt.Unchecked) + + def setWidget(self, column, widget): + if column in self._widgets: + self.removeWidget(column) + self._widgets[column] = widget + tree = self.treeWidget() + if tree is None: + return + else: + tree.setItemWidget(self, column, widget) + + def removeWidget(self, column): + del self._widgets[column] + tree = self.treeWidget() + if tree is None: + return + tree.removeItemWidget(self, column) + + def treeWidgetChanged(self): + tree = self.treeWidget() + if self._tree is tree: + return + self._tree = self.treeWidget() + if tree is None: + return + for col, widget in self._widgets.items(): + tree.setItemWidget(self, col, widget) + + def addChild(self, child): + QtGui.QTreeWidgetItem.addChild(self, child) + TreeWidget.informTreeWidgetChange(child) + + def addChildren(self, childs): + QtGui.QTreeWidgetItem.addChildren(self, childs) + for child in childs: + TreeWidget.informTreeWidgetChange(child) + + def insertChild(self, index, child): + QtGui.QTreeWidgetItem.insertChild(self, index, child) + TreeWidget.informTreeWidgetChange(child) + + def insertChildren(self, index, childs): + QtGui.QTreeWidgetItem.addChildren(self, index, childs) + for child in childs: + TreeWidget.informTreeWidgetChange(child) + + def removeChild(self, child): + QtGui.QTreeWidgetItem.removeChild(self, child) + TreeWidget.informTreeWidgetChange(child) + + def takeChild(self, index): + child = QtGui.QTreeWidgetItem.takeChild(self, index) + TreeWidget.informTreeWidgetChange(child) + return child + + def takeChildren(self): + childs = QtGui.QTreeWidgetItem.takeChildren(self) + for child in childs: + TreeWidget.informTreeWidgetChange(child) + return childs + + diff --git a/papi/pyqtgraph/widgets/ValueLabel.py b/papi/pyqtgraph/widgets/ValueLabel.py new file mode 100644 index 00000000..4e5b3011 --- /dev/null +++ b/papi/pyqtgraph/widgets/ValueLabel.py @@ -0,0 +1,73 @@ +from ..Qt import QtCore, QtGui +from ..ptime import time +from .. import functions as fn +from functools import reduce + +__all__ = ['ValueLabel'] + +class ValueLabel(QtGui.QLabel): + """ + QLabel specifically for displaying numerical values. + Extends QLabel adding some extra functionality: + + - displaying units with si prefix + - built-in exponential averaging + """ + + def __init__(self, parent=None, suffix='', siPrefix=False, averageTime=0, formatStr=None): + """ + ============== ================================================================================== + **Arguments:** + suffix (str or None) The suffix to place after the value + siPrefix (bool) Whether to add an SI prefix to the units and display a scaled value + averageTime (float) The length of time in seconds to average values. If this value + is 0, then no averaging is performed. As this value increases + the display value will appear to change more slowly and smoothly. + formatStr (str) Optionally, provide a format string to use when displaying text. The text + will be generated by calling formatStr.format(value=, avgValue=, suffix=) + (see Python documentation on str.format) + This option is not compatible with siPrefix + ============== ================================================================================== + """ + QtGui.QLabel.__init__(self, parent) + self.values = [] + self.averageTime = averageTime ## no averaging by default + self.suffix = suffix + self.siPrefix = siPrefix + if formatStr is None: + formatStr = '{avgValue:0.2g} {suffix}' + self.formatStr = formatStr + + def setValue(self, value): + now = time() + self.values.append((now, value)) + cutoff = now - self.averageTime + while len(self.values) > 0 and self.values[0][0] < cutoff: + self.values.pop(0) + self.update() + + def setFormatStr(self, text): + self.formatStr = text + self.update() + + def setAverageTime(self, t): + self.averageTime = t + + def averageValue(self): + return reduce(lambda a,b: a+b, [v[1] for v in self.values]) / float(len(self.values)) + + + def paintEvent(self, ev): + self.setText(self.generateText()) + return QtGui.QLabel.paintEvent(self, ev) + + def generateText(self): + if len(self.values) == 0: + return '' + avg = self.averageValue() + val = self.values[-1][1] + if self.siPrefix: + return fn.siFormat(avg, suffix=self.suffix) + else: + return self.formatStr.format(value=val, avgValue=avg, suffix=self.suffix) + diff --git a/papi/pyqtgraph/widgets/VerticalLabel.py b/papi/pyqtgraph/widgets/VerticalLabel.py new file mode 100644 index 00000000..c8cc80bd --- /dev/null +++ b/papi/pyqtgraph/widgets/VerticalLabel.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +from ..Qt import QtGui, QtCore + +__all__ = ['VerticalLabel'] +#class VerticalLabel(QtGui.QLabel): + #def paintEvent(self, ev): + #p = QtGui.QPainter(self) + #p.rotate(-90) + #self.hint = p.drawText(QtCore.QRect(-self.height(), 0, self.height(), self.width()), QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter, self.text()) + #p.end() + #self.setMinimumWidth(self.hint.height()) + #self.setMinimumHeight(self.hint.width()) + + #def sizeHint(self): + #if hasattr(self, 'hint'): + #return QtCore.QSize(self.hint.height(), self.hint.width()) + #else: + #return QtCore.QSize(16, 50) + +class VerticalLabel(QtGui.QLabel): + def __init__(self, text, orientation='vertical', forceWidth=True): + QtGui.QLabel.__init__(self, text) + self.forceWidth = forceWidth + self.orientation = None + self.setOrientation(orientation) + + def setOrientation(self, o): + if self.orientation == o: + return + self.orientation = o + self.update() + self.updateGeometry() + + def paintEvent(self, ev): + p = QtGui.QPainter(self) + #p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200))) + #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100))) + #p.drawRect(self.rect().adjusted(0, 0, -1, -1)) + + #p.setPen(QtGui.QPen(QtGui.QColor(255, 255, 255))) + + if self.orientation == 'vertical': + p.rotate(-90) + rgn = QtCore.QRect(-self.height(), 0, self.height(), self.width()) + else: + rgn = self.contentsRect() + align = self.alignment() + #align = QtCore.Qt.AlignTop|QtCore.Qt.AlignHCenter + + self.hint = p.drawText(rgn, align, self.text()) + p.end() + + if self.orientation == 'vertical': + self.setMaximumWidth(self.hint.height()) + self.setMinimumWidth(0) + self.setMaximumHeight(16777215) + if self.forceWidth: + self.setMinimumHeight(self.hint.width()) + else: + self.setMinimumHeight(0) + else: + self.setMaximumHeight(self.hint.height()) + self.setMinimumHeight(0) + self.setMaximumWidth(16777215) + if self.forceWidth: + self.setMinimumWidth(self.hint.width()) + else: + self.setMinimumWidth(0) + + def sizeHint(self): + if self.orientation == 'vertical': + if hasattr(self, 'hint'): + return QtCore.QSize(self.hint.height(), self.hint.width()) + else: + return QtCore.QSize(19, 50) + else: + if hasattr(self, 'hint'): + return QtCore.QSize(self.hint.width(), self.hint.height()) + else: + return QtCore.QSize(50, 19) + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + win = QtGui.QMainWindow() + w = QtGui.QWidget() + l = QtGui.QGridLayout() + w.setLayout(l) + + l1 = VerticalLabel("text 1", orientation='horizontal') + l2 = VerticalLabel("text 2") + l3 = VerticalLabel("text 3") + l4 = VerticalLabel("text 4", orientation='horizontal') + l.addWidget(l1, 0, 0) + l.addWidget(l2, 1, 1) + l.addWidget(l3, 2, 2) + l.addWidget(l4, 3, 3) + win.setCentralWidget(w) + win.show() \ No newline at end of file diff --git a/papi/pyqtgraph/widgets/__init__.py b/papi/pyqtgraph/widgets/__init__.py new file mode 100644 index 00000000..a81fe391 --- /dev/null +++ b/papi/pyqtgraph/widgets/__init__.py @@ -0,0 +1,21 @@ +## just import everything from sub-modules + +#import os + +#d = os.path.split(__file__)[0] +#files = [] +#for f in os.listdir(d): + #if os.path.isdir(os.path.join(d, f)): + #files.append(f) + #elif f[-3:] == '.py' and f != '__init__.py': + #files.append(f[:-3]) + +#for modName in files: + #mod = __import__(modName, globals(), locals(), fromlist=['*']) + #if hasattr(mod, '__all__'): + #names = mod.__all__ + #else: + #names = [n for n in dir(mod) if n[0] != '_'] + #for k in names: + #print modName, k + #globals()[k] = getattr(mod, k) diff --git a/papi/pyqtgraph/widgets/tests/test_combobox.py b/papi/pyqtgraph/widgets/tests/test_combobox.py new file mode 100644 index 00000000..f511331c --- /dev/null +++ b/papi/pyqtgraph/widgets/tests/test_combobox.py @@ -0,0 +1,44 @@ +import pyqtgraph as pg +pg.mkQApp() + +def test_combobox(): + cb = pg.ComboBox() + items = {'a': 1, 'b': 2, 'c': 3} + cb.setItems(items) + cb.setValue(2) + assert str(cb.currentText()) == 'b' + assert cb.value() == 2 + + # Clear item list; value should be None + cb.clear() + assert cb.value() == None + + # Reset item list; value should be set automatically + cb.setItems(items) + assert cb.value() == 2 + + # Clear item list; repopulate with same names and new values + items = {'a': 4, 'b': 5, 'c': 6} + cb.clear() + cb.setItems(items) + assert cb.value() == 5 + + # Set list instead of dict + cb.setItems(list(items.keys())) + assert str(cb.currentText()) == 'b' + + cb.setValue('c') + assert cb.value() == str(cb.currentText()) + assert cb.value() == 'c' + + cb.setItemValue('c', 7) + assert cb.value() == 7 + + +if __name__ == '__main__': + cb = pg.ComboBox() + cb.show() + cb.setItems({'': None, 'a': 1, 'b': 2, 'c': 3}) + def fn(ind): + print("New value: %s" % cb.value()) + cb.currentIndexChanged.connect(fn) \ No newline at end of file diff --git a/papi/pyqtgraph/widgets/tests/test_tablewidget.py b/papi/pyqtgraph/widgets/tests/test_tablewidget.py new file mode 100644 index 00000000..11416430 --- /dev/null +++ b/papi/pyqtgraph/widgets/tests/test_tablewidget.py @@ -0,0 +1,128 @@ +import pyqtgraph as pg +import numpy as np +from pyqtgraph.pgcollections import OrderedDict + +app = pg.mkQApp() + + +listOfTuples = [('text_%d' % i, i, i/9.) for i in range(12)] +listOfLists = [list(row) for row in listOfTuples] +plainArray = np.array(listOfLists, dtype=object) +recordArray = np.array(listOfTuples, dtype=[('string', object), + ('integer', int), + ('floating', float)]) +dictOfLists = OrderedDict([(name, list(recordArray[name])) for name in recordArray.dtype.names]) +listOfDicts = [OrderedDict([(name, rec[name]) for name in recordArray.dtype.names]) for rec in recordArray] +transposed = [[row[col] for row in listOfTuples] for col in range(len(listOfTuples[0]))] + +def assertTableData(table, data): + assert len(data) == table.rowCount() + rows = list(range(table.rowCount())) + columns = list(range(table.columnCount())) + for r in rows: + assert len(data[r]) == table.columnCount() + row = [] + for c in columns: + item = table.item(r, c) + if item is not None: + row.append(item.value) + else: + row.append(None) + assert row == list(data[r]) + + +def test_TableWidget(): + w = pg.TableWidget(sortable=False) + + # Test all input data types + w.setData(listOfTuples) + assertTableData(w, listOfTuples) + + w.setData(listOfLists) + assertTableData(w, listOfTuples) + + w.setData(plainArray) + assertTableData(w, listOfTuples) + + w.setData(recordArray) + assertTableData(w, listOfTuples) + + w.setData(dictOfLists) + assertTableData(w, transposed) + + w.appendData(dictOfLists) + assertTableData(w, transposed * 2) + + w.setData(listOfDicts) + assertTableData(w, listOfTuples) + + w.appendData(listOfDicts) + assertTableData(w, listOfTuples * 2) + + # Test sorting + w.setData(listOfTuples) + w.sortByColumn(0, pg.QtCore.Qt.AscendingOrder) + assertTableData(w, sorted(listOfTuples, key=lambda a: a[0])) + + w.sortByColumn(1, pg.QtCore.Qt.AscendingOrder) + assertTableData(w, sorted(listOfTuples, key=lambda a: a[1])) + + w.sortByColumn(2, pg.QtCore.Qt.AscendingOrder) + assertTableData(w, sorted(listOfTuples, key=lambda a: a[2])) + + w.setSortMode(1, 'text') + w.sortByColumn(1, pg.QtCore.Qt.AscendingOrder) + assertTableData(w, sorted(listOfTuples, key=lambda a: str(a[1]))) + + w.setSortMode(1, 'index') + w.sortByColumn(1, pg.QtCore.Qt.AscendingOrder) + assertTableData(w, listOfTuples) + + # Test formatting + item = w.item(0, 2) + assert item.text() == ('%0.3g' % item.value) + + w.setFormat('%0.6f') + assert item.text() == ('%0.6f' % item.value) + + w.setFormat('X%0.7f', column=2) + assert isinstance(item.value, float) + assert item.text() == ('X%0.7f' % item.value) + + # test setting items that do not exist yet + w.setFormat('X%0.7f', column=3) + + # test append uses correct formatting + w.appendRow(('x', 10, 7.3)) + item = w.item(w.rowCount()-1, 2) + assert isinstance(item.value, float) + assert item.text() == ('X%0.7f' % item.value) + + # test reset back to defaults + w.setFormat(None, column=2) + assert isinstance(item.value, float) + assert item.text() == ('%0.6f' % item.value) + + w.setFormat(None) + assert isinstance(item.value, float) + assert item.text() == ('%0.3g' % item.value) + + # test function formatter + def fmt(item): + if isinstance(item.value, float): + return "%d %f" % (item.index, item.value) + else: + return pg.asUnicode(item.value) + w.setFormat(fmt) + assert isinstance(item.value, float) + assert isinstance(item.index, int) + assert item.text() == ("%d %f" % (item.index, item.value)) + + + +if __name__ == '__main__': + w = pg.TableWidget(editable=True) + w.setData(listOfTuples) + w.resize(600, 600) + w.show() + From 5feae6d8698b1d3ce7df8b7f033006cd45b870ea Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 30 Dec 2014 12:12:05 +0100 Subject: [PATCH 004/260] pyqtgraph 0.9.10 --- papi/last_active_papi.xml | 282 +++++++------------------------------- 1 file changed, 53 insertions(+), 229 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 905abd8c..c7b1c340 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,185 +1,97 @@ - + Plot + + Plot + - [0 1 2 3 4] Color - ^\[(\s*\d\s*)+\] 1 - - - 0 - bool - Rolling Plot - ^(1|0)$ + [0 1 2 3 4] + ^\[(\s*\d\s*)+\] - time, s Label-X + time, s \w+,\s*\w+ - - Determine position: (x,y) - (0,0) - \(([0-9]+),([0-9]+)\) - 1 - - - 0 - bool - Grid-Y - ^(1|0)$ - - - Used display name - VisualPlugin - - - 1 - (\d+) - - amplitude, V Label-Y + amplitude, V \w+,\s+\w+ - - 1000 - Buffersize - ^([1-9][0-9]{0,3}|10000)$ - 1 - - - [0 0 0 0 0] - Style - ^\[(\s*\d\s*)+\] - 1 - + ^(1|0)$ + Grid-X 0 bool - Grid-X - ^(1|0)$ - Determine size: (height,width) - (300,300) \(([0-9]+),([0-9]+)\) 1 + (565,368) + Determine size: (height,width) - - Plot - - - - 1000 - 1 - 0 - [0 0 0 0 0] - 0 - [0 1 2 3 4] - 0 - - - - - Button - - - 0.1 - 0 - - - Button + + ^(1|0)$ + Grid-Y + 0 + bool - - Determine position: (x,y) - (0,0) - \(([0-9]+),([0-9]+)\) - 1 + + ^(1|0)$ + Rolling Plot + 0 + bool - - Determine size: (height,width) - (300,300) - \(([0-9]+),([0-9]+)\) + + Style 1 + [0 0 0 0 0] + ^\[(\s*\d\s*)+\] - + 1 - 0 - - - Button - 0 - - - - - - - Slider - - - Determine size: (height,width) - (300,300) - \(([0-9]+),([0-9]+)\) - 1 + (\d+) - Determine position: (x,y) - (316,111) \(([0-9]+),([0-9]+)\) 1 - - - Slider + (0,0) + Determine position: (x,y) - Used display name VisualPlugin + Used display name - - 1 - - - - - - - ToHDD_CSV - - - log - 0 - - - - 1 - - - ToHDDXCSV - - - 1 - [0-9]+ + + Buffersize 1 + 1000 + ^([1-9][0-9]{0,3}|10000)$ - - - - - Add - - - Add - - - - + + 0 + [0 0 0 0 0] + [0 1 2 3 4] + 1 + 0 + 0 + 1000 + + + + CPUXLoad + + + load_in_percent + + + CPU_Load @@ -193,92 +105,4 @@ - - Fourier_Rect - - - 9999 - \d{1,5} - 1 - - - FourierXRect - - - Fourier - - - 130.149.155.73 - \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} - 1 - - - - - - - Sinus - - - 3 - [0-9]+ - - - Sinus - - - 1 - \d+.{0,1}\d* - - - - 0.6 - - - - - ORTD_UDP - - - ORTDXUDP - - - 20001 - 1 - - - papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json - file - 0 - - - 0 - 1 - - - 20000 - 1 - - - 127.0.0.1 - 1 - - - - 0 - 0 - 0 - - - - - Fourier_Rect_MOD - - - FourierXRectXMOD - - - - - From c4832ddc358dd21f49b44ee9ead1f51804ca39ac Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 30 Dec 2014 12:52:56 +0100 Subject: [PATCH 005/260] changed yapsy directory embedded it to papi --- papi/core.py | 8 +- papi/gui/gui_event_processing.py | 20 +--- papi/gui/qt_dev/add_plugin.py | 6 +- papi/gui/qt_dev/manager.py | 9 +- papi/gui/qt_new/create_plugin_dialog.py | 13 +-- papi/gui/qt_new/create_plugin_menu.py | 8 +- papi/last_active_papi.xml | 106 +++++++++++++++++- papi/plugin/base_classes/base_plugin.py | 3 +- .../yapsy}/AutoInstallPluginManager.py | 7 +- .../yapsy}/ConfigurablePluginManager.py | 9 +- .../yapsy}/FilteredPluginManager.py | 6 +- {yapsy => papi/yapsy}/IPlugin.py | 0 {yapsy => papi/yapsy}/IPluginLocator.py | 2 +- {yapsy => papi/yapsy}/PluginFileLocator.py | 10 +- {yapsy => papi/yapsy}/PluginInfo.py | 0 {yapsy => papi/yapsy}/PluginManager.py | 17 ++- .../yapsy}/PluginManagerDecorator.py | 9 +- .../yapsy}/VersionedPluginManager.py | 7 +- {yapsy => papi/yapsy}/__init__.py | 2 +- 19 files changed, 157 insertions(+), 85 deletions(-) rename {yapsy => papi/yapsy}/AutoInstallPluginManager.py (98%) rename {yapsy => papi/yapsy}/ConfigurablePluginManager.py (98%) rename {yapsy => papi/yapsy}/FilteredPluginManager.py (97%) rename {yapsy => papi/yapsy}/IPlugin.py (100%) rename {yapsy => papi/yapsy}/IPluginLocator.py (99%) rename {yapsy => papi/yapsy}/PluginFileLocator.py (99%) rename {yapsy => papi/yapsy}/PluginInfo.py (100%) rename {yapsy => papi/yapsy}/PluginManager.py (98%) rename {yapsy => papi/yapsy}/PluginManagerDecorator.py (94%) rename {yapsy => papi/yapsy}/VersionedPluginManager.py (96%) rename {yapsy => papi/yapsy}/__init__.py (98%) diff --git a/papi/core.py b/papi/core.py index e0e3d1aa..1217496e 100644 --- a/papi/core.py +++ b/papi/core.py @@ -30,26 +30,24 @@ import os from multiprocessing import Process, Queue - -from yapsy.PluginManager import PluginManager from threading import Timer -from yapsy.PluginManager import PluginManager +from papi.yapsy.PluginManager import PluginManager from papi.PapiEvent import PapiEvent from papi.data.DCore import DCore from papi.data.DPlugin import DPlugin from papi.ConsoleLog import ConsoleLog -from papi.gui.qt_dev.gui_main import startGUI from papi.data.DOptionalData import DOptionalData + # import contants from papi.constants import CORE_PROCESS_CONSOLE_IDENTIFIER, CORE_CONSOLE_LOG_LEVEL, CORE_PAPI_CONSOLE_START_MESSAGE, \ CORE_CORE_CONSOLE_START_MESSAGE, CORE_ALIVE_CHECK_ENABLED, \ CORE_STOP_CONSOLE_MESSAGE, CORE_ALIVE_CHECK_INTERVAL, CORE_ALIVE_MAX_COUNT from papi.constants import PLUGIN_ROOT_FOLDER_LIST, PLUGIN_VIP_IDENTIFIER, PLUGIN_PCP_IDENTIFIER, \ - PLUGIN_DPP_IDENTIFIER, PLUGIN_IOP_IDENTIFIER, PLUGIN_STATE_PAUSE, PLUGIN_STATE_RESUMED, \ + PLUGIN_DPP_IDENTIFIER, PLUGIN_STATE_PAUSE, PLUGIN_STATE_RESUMED, \ PLUGIN_STATE_START_SUCCESFUL, PLUGIN_STATE_START_FAILED, PLUGIN_STATE_ALIVE, PLUGIN_STATE_STOPPED, \ PLUGIN_STATE_DEAD diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index b54ab5b0..70cd636d 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -26,28 +26,20 @@ + + + CPU_Load + + + CPUXLoad + + + + 0.01 + + + + + Plot + + + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + (300,300) + 1 + + + \w+,\s*\w+ + time, s + Label-X + + + Used display name + VisualPlugin + + + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] + 1 + Color + + + bool + ^(1|0)$ + 0 + Grid-X + + + ^\[(\s*\d\s*)+\] + [0 0 0 0 0] + 1 + Style + + + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + (0,0) + 1 + + + ^([1-9][0-9]{0,3}|10000)$ + 1000 + 1 + Buffersize + + + Plot + + + bool + ^(1|0)$ + 0 + Grid-Y + + + (\d+) + 1 + + + \w+,\s+\w+ + amplitude, V + Label-Y + + + bool + ^(1|0)$ + 0 + Rolling Plot + + + + 1000 + 0 + 1 + [0 1 2 3 4] + 0 + 0 + [0 0 0 0 0] + + + + CPUXLoad + + + load_in_percent + + + + diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py index cf0619ae..564321c2 100644 --- a/papi/plugin/base_classes/base_plugin.py +++ b/papi/plugin/base_classes/base_plugin.py @@ -26,12 +26,11 @@ Stefan Ruppin """ -from yapsy.IPlugin import IPlugin from papi.data.DPlugin import DBlock import papi.event as Event from papi.exceptions.block_exceptions import Wrong_type, Wrong_length from papi.data.DOptionalData import DOptionalData - +from papi.yapsy.IPlugin import IPlugin class base_plugin(IPlugin): diff --git a/yapsy/AutoInstallPluginManager.py b/papi/yapsy/AutoInstallPluginManager.py similarity index 98% rename from yapsy/AutoInstallPluginManager.py rename to papi/yapsy/AutoInstallPluginManager.py index d8ded989..9d1e7018 100644 --- a/yapsy/AutoInstallPluginManager.py +++ b/papi/yapsy/AutoInstallPluginManager.py @@ -13,15 +13,14 @@ === """ -import sys import os import shutil import zipfile import io -from yapsy.IPlugin import IPlugin -from yapsy.PluginManagerDecorator import PluginManagerDecorator -from yapsy import log +from papi.yapsy.IPlugin import IPlugin +from papi.yapsy.PluginManagerDecorator import PluginManagerDecorator +from papi.yapsy import log class AutoInstallPluginManager(PluginManagerDecorator): diff --git a/yapsy/ConfigurablePluginManager.py b/papi/yapsy/ConfigurablePluginManager.py similarity index 98% rename from yapsy/ConfigurablePluginManager.py rename to papi/yapsy/ConfigurablePluginManager.py index 57e03879..cd87600b 100644 --- a/yapsy/ConfigurablePluginManager.py +++ b/papi/yapsy/ConfigurablePluginManager.py @@ -12,12 +12,9 @@ === """ -from yapsy.IPlugin import IPlugin - - -from yapsy.PluginManagerDecorator import PluginManagerDecorator -from yapsy.PluginManager import PLUGIN_NAME_FORBIDEN_STRING - +from papi.yapsy import PLUGIN_NAME_FORBIDEN_STRING +from papi.yapsy.IPlugin import IPlugin +from papi.yapsy.PluginManagerDecorator import PluginManagerDecorator class ConfigurablePluginManager(PluginManagerDecorator): """ diff --git a/yapsy/FilteredPluginManager.py b/papi/yapsy/FilteredPluginManager.py similarity index 97% rename from yapsy/FilteredPluginManager.py rename to papi/yapsy/FilteredPluginManager.py index 05b03ef1..2edae2e2 100644 --- a/yapsy/FilteredPluginManager.py +++ b/papi/yapsy/FilteredPluginManager.py @@ -25,11 +25,9 @@ API === """ - - -from yapsy.IPlugin import IPlugin -from yapsy.PluginManagerDecorator import PluginManagerDecorator +from papi.yapsy.IPlugin import IPlugin +from papi.yapsy.PluginManagerDecorator import PluginManagerDecorator class FilteredPluginManager(PluginManagerDecorator): """ diff --git a/yapsy/IPlugin.py b/papi/yapsy/IPlugin.py similarity index 100% rename from yapsy/IPlugin.py rename to papi/yapsy/IPlugin.py diff --git a/yapsy/IPluginLocator.py b/papi/yapsy/IPluginLocator.py similarity index 99% rename from yapsy/IPluginLocator.py rename to papi/yapsy/IPluginLocator.py index fb128925..a5995c6b 100644 --- a/yapsy/IPluginLocator.py +++ b/papi/yapsy/IPluginLocator.py @@ -17,7 +17,7 @@ """ -from yapsy import log +from papi.yapsy import log class IPluginLocator(object): """ diff --git a/yapsy/PluginFileLocator.py b/papi/yapsy/PluginFileLocator.py similarity index 99% rename from yapsy/PluginFileLocator.py rename to papi/yapsy/PluginFileLocator.py index 24b3ed9d..77dcf106 100644 --- a/yapsy/PluginFileLocator.py +++ b/papi/yapsy/PluginFileLocator.py @@ -51,14 +51,12 @@ import os import re -from yapsy import log import configparser -from yapsy.PluginInfo import PluginInfo -from yapsy import PLUGIN_NAME_FORBIDEN_STRING -from yapsy.IPluginLocator import IPluginLocator - - +from papi.yapsy import log +from papi.yapsy.PluginInfo import PluginInfo +from papi.yapsy import PLUGIN_NAME_FORBIDEN_STRING +from papi.yapsy.IPluginLocator import IPluginLocator class IPluginFileAnalyzer(object): diff --git a/yapsy/PluginInfo.py b/papi/yapsy/PluginInfo.py similarity index 100% rename from yapsy/PluginInfo.py rename to papi/yapsy/PluginInfo.py diff --git a/yapsy/PluginManager.py b/papi/yapsy/PluginManager.py similarity index 98% rename from yapsy/PluginManager.py rename to papi/yapsy/PluginManager.py index 9ac966ba..defd1a00 100644 --- a/yapsy/PluginManager.py +++ b/papi/yapsy/PluginManager.py @@ -131,20 +131,20 @@ import os import imp -from yapsy import log -from yapsy import NormalizePluginNameForModuleName +from papi.yapsy import log +from papi.yapsy import NormalizePluginNameForModuleName +from papi.yapsy.IPluginLocator import IPluginLocator + + -from yapsy.IPlugin import IPlugin -from yapsy.IPluginLocator import IPluginLocator # The follozing two imports are used to implement the default behaviour -from yapsy.PluginFileLocator import PluginFileAnalyzerWithInfoFile -from yapsy.PluginFileLocator import PluginFileLocator +from papi.yapsy.PluginFileLocator import PluginFileAnalyzerWithInfoFile # imported for backward compatibility (this variable was defined here # before 1.10) -from yapsy import PLUGIN_NAME_FORBIDEN_STRING # imported for backward compatibility (this PluginInfo was imported # here before 1.10) -from yapsy.PluginInfo import PluginInfo +from papi.yapsy.IPlugin import IPlugin +from papi.yapsy.PluginFileLocator import PluginFileLocator class PluginManager(object): @@ -659,5 +659,4 @@ def get(self): # For backward compatility import the most basic decorator (it changed # place as of v1.8) -from yapsy.PluginManagerDecorator import PluginManagerDecorator diff --git a/yapsy/PluginManagerDecorator.py b/papi/yapsy/PluginManagerDecorator.py similarity index 94% rename from yapsy/PluginManagerDecorator.py rename to papi/yapsy/PluginManagerDecorator.py index 893ba5a9..5eabc1ce 100644 --- a/yapsy/PluginManagerDecorator.py +++ b/papi/yapsy/PluginManagerDecorator.py @@ -28,9 +28,9 @@ import os -from yapsy.IPlugin import IPlugin -from yapsy import log - +from papi.yapsy import log +from papi.yapsy.IPlugin import IPlugin +from papi.yapsy.PluginManager import PluginManager class PluginManagerDecorator(object): """ @@ -74,8 +74,7 @@ def __init__(self, decorated_object=None, if decorated_object is None: log.debug("Creating a default PluginManager instance to be decorated.") - from yapsy.PluginManager import PluginManager - decorated_object = PluginManager(categories_filter, + decorated_object = PluginManager(categories_filter, directories_list, plugin_info_ext) self._component = decorated_object diff --git a/yapsy/VersionedPluginManager.py b/papi/yapsy/VersionedPluginManager.py similarity index 96% rename from yapsy/VersionedPluginManager.py rename to papi/yapsy/VersionedPluginManager.py index abee6d70..aa1746a8 100644 --- a/yapsy/VersionedPluginManager.py +++ b/papi/yapsy/VersionedPluginManager.py @@ -12,12 +12,11 @@ === """ - from distutils.version import StrictVersion -from yapsy.PluginInfo import PluginInfo -from yapsy.IPlugin import IPlugin -from yapsy.PluginManagerDecorator import PluginManagerDecorator +from papi.yapsy.PluginInfo import PluginInfo +from papi.yapsy.PluginManagerDecorator import PluginManagerDecorator +from papi.yapsy.IPlugin import IPlugin class VersionedPluginInfo(PluginInfo): diff --git a/yapsy/__init__.py b/papi/yapsy/__init__.py similarity index 98% rename from yapsy/__init__.py rename to papi/yapsy/__init__.py index 693fad84..b91ea441 100644 --- a/yapsy/__init__.py +++ b/papi/yapsy/__init__.py @@ -60,7 +60,7 @@ # provide a default named log for package-wide use import logging -log = logging.getLogger('yapsy') +log = logging.getLogger('papi.yapsy') # Some constants concerning the plugins PLUGIN_NAME_FORBIDEN_STRING=";;" From c52e0104d7edaa8e7aa20fd0c1eaf4c84c22894c Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 30 Dec 2014 12:57:52 +0100 Subject: [PATCH 006/260] removed small bug in plot --- papi/last_active_papi.xml | 115 ++++- papi/plugin/visual/Plot/Plot.py | 2 +- yapsy/AutoInstallPluginManager.py | 201 --------- yapsy/ConfigurablePluginManager.py | 277 ------------ yapsy/FilteredPluginManager.py | 138 ------ yapsy/IPlugin.py | 60 --- yapsy/IPluginLocator.py | 105 ----- yapsy/PluginFileLocator.py | 533 ----------------------- yapsy/PluginInfo.py | 214 ---------- yapsy/PluginManager.py | 663 ----------------------------- yapsy/PluginManagerDecorator.py | 102 ----- yapsy/VersionedPluginManager.py | 137 ------ yapsy/__init__.py | 88 ---- 13 files changed, 115 insertions(+), 2520 deletions(-) delete mode 100644 yapsy/AutoInstallPluginManager.py delete mode 100644 yapsy/ConfigurablePluginManager.py delete mode 100644 yapsy/FilteredPluginManager.py delete mode 100644 yapsy/IPlugin.py delete mode 100644 yapsy/IPluginLocator.py delete mode 100644 yapsy/PluginFileLocator.py delete mode 100644 yapsy/PluginInfo.py delete mode 100644 yapsy/PluginManager.py delete mode 100644 yapsy/PluginManagerDecorator.py delete mode 100644 yapsy/VersionedPluginManager.py delete mode 100644 yapsy/__init__.py diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index ce64a708..8912bf72 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,4 +1,117 @@ - + + + Plot + + + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] + 1 + Color + + + ^([1-9][0-9]{0,3}|10000)$ + 1000 + 1 + Buffersize + + + bool + ^(1|0)$ + 0 + Rolling Plot + + + \w+,\s*\w+ + time, s + Label-X + + + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + (729,551) + 1 + + + \w+,\s+\w+ + amplitude, V + Label-Y + + + bool + ^(1|0)$ + 0 + Grid-X + + + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + (0,0) + 1 + + + ^\[(\s*\d\s*)+\] + [0 0 0 0 0] + 1 + Style + + + (\d+) + 1 + + + Plot + + + bool + ^(1|0)$ + 0 + Grid-Y + + + Used display name + VisualPlugin + + + + 0 + [0 1 2 3 4] + 1000 + 0 + 1 + 0 + [0 0 0 0 0] + + + + Sinus + + + f3_1 + f3_2 + + + + + + Sinus + + + [0-9]+ + 3 + + + Sinus + + + \d+.{0,1}\d* + 1 + + + + 0.6 + + + diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 809cef0b..27f8a1e8 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -497,7 +497,7 @@ def add_databuffer(self, signal_name, signal_id): buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION legend_name = str(signal_id) + "# " + signal_name - curve = self.__plotWidget__.plot([0, 1], [0, 1], name=legend_name, clipToView=True) + curve = self.__plotWidget__.plot([0, 1], [0, 1], name=legend_name) self.signals[signal_name]['buffer'] = buffer self.signals[signal_name]['curve'] = curve diff --git a/yapsy/AutoInstallPluginManager.py b/yapsy/AutoInstallPluginManager.py deleted file mode 100644 index d8ded989..00000000 --- a/yapsy/AutoInstallPluginManager.py +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - -""" -Role -==== - -Defines plugin managers that can handle the installation of plugin -files into the right place. Then the end-user does not have to browse -to the plugin directory to install them. - -API -=== -""" - -import sys -import os -import shutil -import zipfile -import io - -from yapsy.IPlugin import IPlugin -from yapsy.PluginManagerDecorator import PluginManagerDecorator -from yapsy import log - - -class AutoInstallPluginManager(PluginManagerDecorator): - """ - A plugin manager that also manages the installation of the plugin - files into the appropriate directory. - """ - - - def __init__(self, - plugin_install_dir=None, - decorated_manager=None, - # The following args will only be used if we need to - # create a default PluginManager - categories_filter={"Default":IPlugin}, - directories_list=None, - plugin_info_ext="yapsy-plugin"): - """ - Create the plugin manager and set up the directory where to - install new plugins. - - Arguments - - ``plugin_install_dir`` - The directory where new plugins to be installed will be copied. - - .. warning:: If ``plugin_install_dir`` does not correspond to - an element of the ``directories_list``, it is - appended to the later. - - """ - # Create the base decorator class - PluginManagerDecorator.__init__(self, - decorated_manager, - categories_filter, - directories_list, - plugin_info_ext) - # set the directory for new plugins - self.plugins_places=[] - self.setInstallDir(plugin_install_dir) - - def setInstallDir(self,plugin_install_dir): - """ - Set the directory where to install new plugins. - """ - if not (plugin_install_dir in self.plugins_places): - self.plugins_places.append(plugin_install_dir) - self.install_dir = plugin_install_dir - - def getInstallDir(self): - """ - Return the directory where new plugins should be installed. - """ - return self.install_dir - - def install(self, directory, plugin_info_filename): - """ - Giving the plugin's info file (e.g. ``myplugin.yapsy-plugin``), - and the directory where it is located, get all the files that - define the plugin and copy them into the correct directory. - - Return ``True`` if the installation is a success, ``False`` if - it is a failure. - """ - # start collecting essential info about the new plugin - plugin_info, config_parser = self._gatherCorePluginInfo(directory, plugin_info_filename) - # now determine the path of the file to execute, - # depending on wether the path indicated is a - # directory or a file - if not (os.path.exists(plugin_info.path) or os.path.exists(plugin_info.path+".py") ): - log.warning("Could not find the plugin's implementation for %s." % plugin_info.name) - return False - if os.path.isdir(plugin_info.path): - try: - shutil.copytree(plugin_info.path, - os.path.join(self.install_dir,os.path.basename(plugin_info.path))) - shutil.copy(os.path.join(directory, plugin_info_filename), - self.install_dir) - except: - log.error("Could not install plugin: %s." % plugin_info.name) - return False - else: - return True - elif os.path.isfile(plugin_info.path+".py"): - try: - shutil.copy(plugin_info.path+".py", - self.install_dir) - shutil.copy(os.path.join(directory, plugin_info_filename), - self.install_dir) - except: - log.error("Could not install plugin: %s." % plugin_info.name) - return False - else: - return True - else: - return False - - - def installFromZIP(self, plugin_ZIP_filename): - """ - Giving the plugin's zip file (e.g. ``myplugin.zip``), check - that their is a valid info file in it and correct all the - plugin files into the correct directory. - - .. warning:: Only available for python 2.6 and later. - - Return ``True`` if the installation is a success, ``False`` if - it is a failure. - """ - if not os.path.isfile(plugin_ZIP_filename): - log.warning("Could not find the plugin's zip file at '%s'." % plugin_ZIP_filename) - return False - try: - candidateZipFile = zipfile.ZipFile(plugin_ZIP_filename) - first_bad_file = candidateZipFile.testzip() - if first_bad_file: - raise Exception("Corrupted ZIP with first bad file '%s'" % first_bad_file) - except Exception as e: - log.warning("Invalid zip file '%s' (error: %s)." % (plugin_ZIP_filename,e)) - return False - zipContent = candidateZipFile.namelist() - log.info("Investigating the content of a zip file containing: '%s'" % zipContent) - log.info("Sanity checks on zip's contained files (looking for hazardous path symbols).") - # check absence of root path and ".." shortcut that would - # send the file oustide the desired directory - for containedFileName in zipContent: - # WARNING: the sanity checks below are certainly not - # exhaustive (maybe we could do something a bit smarter by - # using os.path.expanduser, os.path.expandvars and - # os.path.normpath) - if containedFileName.startswith("/"): - log.warning("Unsecure zip file, rejected because one of its file paths ('%s') starts with '/'" % containedFileName) - return False - if containedFileName.startswith(r"\\") or containedFileName.startswith("//"): - log.warning(r"Unsecure zip file, rejected because one of its file paths ('%s') starts with '\\'" % containedFileName) - return False - if os.path.splitdrive(containedFileName)[0]: - log.warning("Unsecure zip file, rejected because one of its file paths ('%s') starts with a drive letter" % containedFileName) - return False - if os.path.isabs(containedFileName): - log.warning("Unsecure zip file, rejected because one of its file paths ('%s') is absolute" % containedFileName) - return False - pathComponent = os.path.split(containedFileName) - if ".." in pathComponent: - log.warning("Unsecure zip file, rejected because one of its file paths ('%s') contains '..'" % containedFileName) - return False - if "~" in pathComponent: - log.warning("Unsecure zip file, rejected because one of its file paths ('%s') contains '~'" % containedFileName) - return False - infoFileCandidates = [filename for filename in zipContent if os.path.dirname(filename)==""] - if not infoFileCandidates: - log.warning("Zip file structure seems wrong in '%s', no info file found." % plugin_ZIP_filename) - return False - isValid = False - log.info("Looking for the zipped plugin's info file among '%s'" % infoFileCandidates) - for infoFileName in infoFileCandidates: - infoFile = candidateZipFile.read(infoFileName) - log.info("Assuming the zipped plugin info file to be '%s'" % infoFileName) - pluginName,moduleName,_ = self._getPluginNameAndModuleFromStream(io.StringIO(str(infoFile,encoding="utf-8"))) - if moduleName is None: - continue - log.info("Checking existence of the expected module '%s' in the zip file" % moduleName) - if moduleName in zipContent or os.path.join(moduleName,"__init__.py") in zipContent: - isValid = True - break - if not isValid: - log.warning("Zip file structure seems wrong in '%s', " - "could not match info file with the implementation of plugin '%s'." % (plugin_ZIP_filename,pluginName)) - return False - else: - try: - candidateZipFile.extractall(self.install_dir) - return True - except Exception as e: - log.error("Could not install plugin '%s' from zip file '%s' (exception: '%s')." % (pluginName,plugin_ZIP_filename,e)) - return False - diff --git a/yapsy/ConfigurablePluginManager.py b/yapsy/ConfigurablePluginManager.py deleted file mode 100644 index 57e03879..00000000 --- a/yapsy/ConfigurablePluginManager.py +++ /dev/null @@ -1,277 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - -""" -Role -==== - -Defines plugin managers that can handle configuration files similar to -the ini files manipulated by Python's ConfigParser module. - -API -=== -""" - -from yapsy.IPlugin import IPlugin - - -from yapsy.PluginManagerDecorator import PluginManagerDecorator -from yapsy.PluginManager import PLUGIN_NAME_FORBIDEN_STRING - - -class ConfigurablePluginManager(PluginManagerDecorator): - """ - A plugin manager that also manages a configuration file. - - The configuration file will be accessed through a ``ConfigParser`` - derivated object. The file can be used for other purpose by the - application using this plugin manager as it will only add a new - specific section ``[Plugin Management]`` for itself and also new - sections for some plugins that will start with ``[Plugin:...]`` - (only the plugins that explicitly requires to save configuration - options will have this kind of section). - - .. warning:: when giving/building the list of plugins to activate - by default, there must not be any space in the list - (neither in the names nor in between) - """ - - CONFIG_SECTION_NAME = "Plugin Management" - - - def __init__(self, - configparser_instance=None, - config_change_trigger= lambda x:True, - decorated_manager=None, - # The following args will only be used if we need to - # create a default PluginManager - categories_filter={"Default":IPlugin}, - directories_list=None, - plugin_info_ext="yapsy-plugin"): - """ - Create the plugin manager and record the ConfigParser instance - that will be used afterwards. - - The ``config_change_trigger`` argument can be used to set a - specific method to call when the configuration is - altered. This will let the client application manage the way - they want the configuration to be updated (e.g. write on file - at each change or at precise time intervalls or whatever....) - """ - # Create the base decorator class - PluginManagerDecorator.__init__(self,decorated_manager, - categories_filter, - directories_list, - plugin_info_ext) - self.setConfigParser(configparser_instance, config_change_trigger) - - - def setConfigParser(self,configparser_instance,config_change_trigger): - """ - Set the ConfigParser instance. - """ - self.config_parser = configparser_instance - # set the (optional) fucntion to be called when the - # configuration is changed: - self.config_has_changed = config_change_trigger - - def __getCategoryPluginsListFromConfig(self, plugin_list_str): - """ - Parse the string describing the list of plugins to activate, - to discover their actual names and return them. - """ - return plugin_list_str.strip(" ").split("%s"%PLUGIN_NAME_FORBIDEN_STRING) - - def __getCategoryPluginsConfigFromList(self, plugin_list): - """ - Compose a string describing the list of plugins to activate - """ - return PLUGIN_NAME_FORBIDEN_STRING.join(plugin_list) - - def __getCategoryOptionsName(self,category_name): - """ - Return the appropirately formated version of the category's - option. - """ - return "%s_plugins_to_load" % category_name.replace(" ","_") - - def __addPluginToConfig(self,category_name, plugin_name): - """ - Utility function to add a plugin to the list of plugin to be - activated. - """ - # check that the section is here - if not self.config_parser.has_section(self.CONFIG_SECTION_NAME): - self.config_parser.add_section(self.CONFIG_SECTION_NAME) - # check that the category's list of activated plugins is here too - option_name = self.__getCategoryOptionsName(category_name) - if not self.config_parser.has_option(self.CONFIG_SECTION_NAME, option_name): - # if there is no list yet add a new one - self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,plugin_name) - return self.config_has_changed() - else: - # get the already existing list and append the new - # activated plugin to it. - past_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME,option_name) - past_list = self.__getCategoryPluginsListFromConfig(past_list_str) - # make sure we don't add it twice - if plugin_name not in past_list: - past_list.append(plugin_name) - new_list_str = self.__getCategoryPluginsConfigFromList(past_list) - self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,new_list_str) - return self.config_has_changed() - - def __removePluginFromConfig(self,category_name, plugin_name): - """ - Utility function to add a plugin to the list of plugin to be - activated. - """ - # check that the section is here - if not self.config_parser.has_section(self.CONFIG_SECTION_NAME): - # then nothing to remove :) - return - # check that the category's list of activated plugins is here too - option_name = self.__getCategoryOptionsName(category_name) - if not self.config_parser.has_option(self.CONFIG_SECTION_NAME, option_name): - # if there is no list still nothing to do - return - else: - # get the already existing list - past_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME,option_name) - past_list = self.__getCategoryPluginsListFromConfig(past_list_str) - if plugin_name in past_list: - past_list.remove(plugin_name) - new_list_str = self.__getCategoryPluginsConfigFromList(past_list) - self.config_parser.set(self.CONFIG_SECTION_NAME,option_name,new_list_str) - self.config_has_changed() - - - - def registerOptionFromPlugin(self, - category_name, plugin_name, - option_name, option_value): - """ - To be called from a plugin object, register a given option in - the name of a given plugin. - """ - section_name = "%s Plugin: %s" % (category_name,plugin_name) - # if the plugin's section is not here yet, create it - if not self.config_parser.has_section(section_name): - self.config_parser.add_section(section_name) - # set the required option - self.config_parser.set(section_name,option_name,option_value) - self.config_has_changed() - - def hasOptionFromPlugin(self, - category_name, plugin_name, option_name): - """ - To be called from a plugin object, return True if the option - has already been registered. - """ - section_name = "%s Plugin: %s" % (category_name,plugin_name) - return self.config_parser.has_section(section_name) and self.config_parser.has_option(section_name,option_name) - - def readOptionFromPlugin(self, - category_name, plugin_name, option_name): - """ - To be called from a plugin object, read a given option in - the name of a given plugin. - """ - section_name = "%s Plugin: %s" % (category_name,plugin_name) - return self.config_parser.get(section_name,option_name) - - - def __decoratePluginObject(self, category_name, plugin_name, plugin_object): - """ - Add two methods to the plugin objects that will make it - possible for it to benefit from this class's api concerning - the management of the options. - """ - plugin_object.setConfigOption = lambda x,y: self.registerOptionFromPlugin(category_name, - plugin_name, - x,y) - plugin_object.setConfigOption.__doc__ = self.registerOptionFromPlugin.__doc__ - plugin_object.getConfigOption = lambda x: self.readOptionFromPlugin(category_name, - plugin_name, - x) - plugin_object.getConfigOption.__doc__ = self.readOptionFromPlugin.__doc__ - plugin_object.hasConfigOption = lambda x: self.hasOptionFromPlugin(category_name, - plugin_name, - x) - plugin_object.hasConfigOption.__doc__ = self.hasOptionFromPlugin.__doc__ - - def activatePluginByName(self, plugin_name, category_name="Default", save_state=True): - """ - Activate a plugin, , and remember it (in the config file). - - If you want the plugin to benefit from the configuration - utility defined by this manager, it is crucial to use this - method to activate a plugin and not call the plugin object's - ``activate`` method. In fact, this method will also "decorate" - the plugin object so that it can use this class's methods to - register its own options. - - By default, the plugin's activation is registered in the - config file but if you d'ont want this set the 'save_state' - argument to False. - """ - # first decorate the plugin - pta = self._component.getPluginByName(plugin_name,category_name) - if pta is None: - return None - self.__decoratePluginObject(category_name,plugin_name,pta.plugin_object) - # activate the plugin - plugin_object = self._component.activatePluginByName(plugin_name,category_name) - # check the activation and then optionally set the config option - if plugin_object.is_activated: - if save_state: - self.__addPluginToConfig(category_name,plugin_name) - return plugin_object - return None - - def deactivatePluginByName(self, plugin_name, category_name="Default", save_state=True): - """ - Deactivate a plugin, and remember it (in the config file). - - By default, the plugin's deactivation is registered in the - config file but if you d'ont want this set the ``save_state`` - argument to False. - """ - # activate the plugin - plugin_object = self._component.deactivatePluginByName(plugin_name,category_name) - if plugin_object is None: - return None - # check the deactivation and then optionnally set the config option - if not plugin_object.is_activated: - if save_state: - self.__removePluginFromConfig(category_name,plugin_name) - return plugin_object - return None - - def loadPlugins(self,callback=None): - """ - Walk through the plugins' places and look for plugins. Then - for each plugin candidate look for its category, load it and - stores it in the appropriate slot of the ``category_mapping``. - """ - self._component.loadPlugins(callback) - # now load the plugins according to the recorded configuration - if self.config_parser.has_section(self.CONFIG_SECTION_NAME): - # browse all the categories - for category_name in list(self._component.category_mapping.keys()): - # get the list of plugins to be activated for this - # category - option_name = "%s_plugins_to_load"%category_name - if self.config_parser.has_option(self.CONFIG_SECTION_NAME, - option_name): - plugin_list_str = self.config_parser.get(self.CONFIG_SECTION_NAME, - option_name) - plugin_list = self.__getCategoryPluginsListFromConfig(plugin_list_str) - # activate all the plugins that should be - # activated - for plugin_name in plugin_list: - self.activatePluginByName(plugin_name,category_name) - - - - diff --git a/yapsy/FilteredPluginManager.py b/yapsy/FilteredPluginManager.py deleted file mode 100644 index 05b03ef1..00000000 --- a/yapsy/FilteredPluginManager.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - -""" -Role -==== - -Defines the basic mechanisms to have a plugin manager filter the -available list of plugins after locating them and before loading them. - -One use fo this would be to prevent untrusted plugins from entering -the system. - -To use it properly you must reimplement or monkey patch the -``IsPluginOk`` method, as in the following example:: - - # define a plugin manager (with you prefered options) - pm = PluginManager(...) - # decorate it with the Filtering mechanics - pm = FilteredPluginManager(pm) - # define a custom predicate that filters out plugins without descriptions - pm.isPluginOk = lambda x: x.description!="" - - -API -=== -""" - - -from yapsy.IPlugin import IPlugin -from yapsy.PluginManagerDecorator import PluginManagerDecorator - - -class FilteredPluginManager(PluginManagerDecorator): - """ - Base class for decorators which filter the plugins list - before they are loaded. - """ - - def __init__(self, - decorated_manager=None, - categories_filter={"Default":IPlugin}, - directories_list=None, - plugin_info_ext="yapsy-plugin"): - """ - """ - # Create the base decorator class - PluginManagerDecorator.__init__(self,decorated_manager, - categories_filter, - directories_list, - plugin_info_ext) - # prepare the mapping of the latest version of each plugin - self.rejectedPlugins = [ ] - - - - def filterPlugins(self): - """ - Go through the currently available candidates, and and either - leaves them, or moves them into the list of rejected Plugins. - - Can be overridden if overriding ``isPluginOk`` sentinel is not - powerful enough. - """ - self.rejectedPlugins = [ ] - for candidate_infofile, candidate_filepath, plugin_info in self._component.getPluginCandidates(): - if not self.isPluginOk( plugin_info): - self.rejectPluginCandidate((candidate_infofile, candidate_filepath, plugin_info) ) - - def rejectPluginCandidate(self,pluginTuple): - """ - Move a plugin from the candidates list to the rejected List. - """ - if pluginTuple in self.getPluginCandidates(): - self._component.removePluginCandidate(pluginTuple) - if not pluginTuple in self.rejectedPlugins: - self.rejectedPlugins.append(pluginTuple) - - def unrejectPluginCandidate(self,pluginTuple): - """ - Move a plugin from the rejected list to into the candidates - list. - """ - if not pluginTuple in self.getPluginCandidates(): - self._component.appendPluginCandidate(pluginTuple) - if pluginTuple in self.rejectedPlugins: - self.rejectedPlugins.remove(pluginTuple) - - def removePluginCandidate(self,pluginTuple): - """ - Remove a plugin from the list of candidates. - """ - if pluginTuple in self.getPluginCandidates(): - self._component.removePluginCandidate(pluginTuple) - if pluginTuple in self.rejectedPlugins: - self.rejectedPlugins.remove(pluginTuple) - - - def appendPluginCandidate(self,pluginTuple): - """ - Add a new candidate. - """ - if self.isPluginOk(pluginTuple[2]): - if pluginTuple not in self.getPluginCandidates(): - self._component.appendPluginCandidate(pluginTuple) - else: - if not pluginTuple in self.rejectedPlugins: - self.rejectedPlugins.append(pluginTuple) - - def isPluginOk(self,info): - """ - Sentinel function to detect if a plugin should be filtered. - - ``info`` is an instance of a ``PluginInfo`` and this method is - expected to return True if the corresponding plugin can be - accepted, and False if it must be filtered out. - - Subclasses should override this function and return false for - any plugin which they do not want to be loadable. - """ - return True - - def locatePlugins(self): - """ - locate and filter plugins. - """ - #Reset Catalogue - self.setCategoriesFilter(self._component.categories_interfaces) - #Reread and filter. - self._component.locatePlugins() - self.filterPlugins() - return len(self._component.getPluginCandidates()) - - def getRejectedPlugins(self): - """ - Return the list of rejected plugins. - """ - return self.rejectedPlugins[:] diff --git a/yapsy/IPlugin.py b/yapsy/IPlugin.py deleted file mode 100644 index cbe2d327..00000000 --- a/yapsy/IPlugin.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - - -""" -Role -==== - -Defines the basic interfaces for a plugin. These interfaces are -inherited by the *core* class of a plugin. The *core* class of a -plugin is then the one that will be notified the -activation/deactivation of a plugin via the ``activate/deactivate`` -methods. - - -For simple (near trivial) plugin systems, one can directly use the -following interfaces. - -Extensibility -============= - -In your own software, you'll probably want to build derived classes of -the ``IPlugin`` class as it is a mere interface with no specific -functionality. - -Your software's plugins should then inherit your very own plugin class -(itself derived from ``IPlugin``). - -Where and how to code these plugins is explained in the section about -the :doc:`PluginManager`. - - -API -=== -""" - - -class IPlugin(object): - """ - The most simple interface to be inherited when creating a plugin. - """ - - def __init__(self): - """ - Set the basic variables. - """ - self.is_activated = False - - def activate(self): - """ - Called at plugin activation. - """ - self.is_activated = True - - def deactivate(self): - """ - Called when the plugin is disabled. - """ - self.is_activated = False - diff --git a/yapsy/IPluginLocator.py b/yapsy/IPluginLocator.py deleted file mode 100644 index fb128925..00000000 --- a/yapsy/IPluginLocator.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - - - -""" -Role -==== - -``IPluginLocator`` defines the basic interface expected by a -``PluginManager`` to be able to locate plugins and get basic info -about each discovered plugin (name, version etc). - -API -=== - -""" - - -from yapsy import log - -class IPluginLocator(object): - """ - Plugin Locator interface with some methods already implemented to - manage the awkward backward compatible stuff. - """ - - def locatePlugins(self): - """ - Walk through the plugins' places and look for plugins. - - Return the discovered plugins as a list of - ``(candidate_infofile_path, candidate_file_path,plugin_info_instance)`` - and their number. - """ - raise NotImplementedError("locatePlugins must be reimplemented by %s" % self) - - def gatherCorePluginInfo(self, directory, filename): - """ - Return a ``PluginInfo`` as well as the ``ConfigParser`` used to build it. - - If filename is a valid plugin discovered by any of the known - strategy in use. Returns None,None otherwise. - """ - raise NotImplementedError("gatherPluginInfo must be reimplemented by %s" % self) - - # -------------------------------------------------------------------- - # Below are backward compatibility methods: if you inherit from - # IPluginLocator it's ok not to reimplement them, there will only - # be a warning message logged if they are called and not - # reimplemented. - # -------------------------------------------------------------------- - - def getPluginNameAndModuleFromStream(self,fileobj): - """ - DEPRECATED(>1.9): kept for backward compatibility - with existing PluginManager child classes. - - Return a 3-uple with the name of the plugin, its - module and the config_parser used to gather the core - data *in a tuple*, if the required info could be - localised, else return ``(None,None,None)``. - """ - log.warn("setPluginInfoClass was called but '%s' doesn't implement it." % self) - return None,None,None - - - def setPluginInfoClass(self, picls, names=None): - """ - DEPRECATED(>1.9): kept for backward compatibility - with existing PluginManager child classes. - - Set the class that holds PluginInfo. The class should inherit - from ``PluginInfo``. - """ - log.warn("setPluginInfoClass was called but '%s' doesn't implement it." % self) - - def getPluginInfoClass(self): - """ - DEPRECATED(>1.9): kept for backward compatibility - with existing PluginManager child classes. - - Get the class that holds PluginInfo. - """ - log.warn("getPluginInfoClass was called but '%s' doesn't implement it." % self) - return None - - def setPluginPlaces(self, directories_list): - """ - DEPRECATED(>1.9): kept for backward compatibility - with existing PluginManager child classes. - - Set the list of directories where to look for plugin places. - """ - log.warn("setPluginPlaces was called but '%s' doesn't implement it." % self) - - def updatePluginPlaces(self, directories_list): - """ - DEPRECATED(>1.9): kept for backward compatibility - with existing PluginManager child classes. - - Updates the list of directories where to look for plugin places. - """ - log.warn("updatePluginPlaces was called but '%s' doesn't implement it." % self) - diff --git a/yapsy/PluginFileLocator.py b/yapsy/PluginFileLocator.py deleted file mode 100644 index 24b3ed9d..00000000 --- a/yapsy/PluginFileLocator.py +++ /dev/null @@ -1,533 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - - -""" -Role -==== - -The ``PluginFileLocator`` locates plugins when they are accessible via the filesystem. - -It's default behaviour is to look for text files with the -'.yapsy-plugin' extensions and to read the plugin's decription in -them. - - -Customization -------------- - -The behaviour of a ``PluginFileLocator`` can be customized by instanciating it with a specific 'analyzer'. - -Two analyzers are already implemented and provided here: - - ``PluginFileAnalyzerWithInfoFile`` - - the default 'analyzer' that looks for plugin 'info files' as - text file with a predefined extension. This implements the way - yapsy looks for plugin since version 1. - - ``PluginFileAnalyzerMathingRegex`` - - look for files matching a regex and considers them as being - the plugin itself. - -All analyzers must enforce the - -It enforces the ``plugin locator`` policy as defined by ``IPluginLocator`` and used by ``PluginManager``. - - ``info_ext`` - - expects a plugin to be discovered through its *plugin info file*. - User just needs to provide an extension (without '.') to look - for *plugin_info_file*. - - ``regexp`` - - looks for file matching the given regular pattern expression. - User just needs to provide the regular pattern expression. - -All analyzers must enforce the policy represented by the ``IPluginFileAnalyzer`` interface. -""" - -import os -import re -from yapsy import log -import configparser - -from yapsy.PluginInfo import PluginInfo -from yapsy import PLUGIN_NAME_FORBIDEN_STRING -from yapsy.IPluginLocator import IPluginLocator - - - - -class IPluginFileAnalyzer(object): - """ - Define the methods expected by PluginFileLocator for its 'analyzer'. - """ - - def __init__(self,name): - self.name = name - - def isValidPlugin(self, filename): - """ - Check if the resource found at filename is a valid plugin. - """ - raise NotImplementedError("'isValidPlugin' must be reimplemented by %s" % self) - - - def getInfosDictFromPlugin(self, dirpath, filename): - """ - Returns the extracted plugin informations as a dictionary. - This function ensures that "name" and "path" are provided. - - *dirpath* is the full path to the directory where the plugin file is - - *filename* is the name (ie the basename) of the plugin file. - - If *callback* function has not been provided for this strategy, - we use the filename alone to extract minimal informations. - """ - raise NotImplementedError("'getInfosDictFromPlugin' must be reimplemented by %s" % self) - - -class PluginFileAnalyzerWithInfoFile(IPluginFileAnalyzer): - """ - Consider plugins described by a textual description file. - - A plugin is expected to be described by a text file ('ini' format) with a specific extension (.yapsy-plugin by default). - - This file must contain at least the following information:: - - [Core] - Name = name of the module - Module = relative_path/to/python_file_or_directory - - Optionnally the description file may also contain the following section (in addition to the above one):: - - [Documentation] - Author = Author Name - Version = Major.minor - Website = url_for_plugin - Description = A simple one-sentence description - - """ - def __init__(self, name, extensions="yapsy-plugin"): - """ - Creates a new analyzer named *name* and dedicated to check and analyze plugins described by a textual "info file". - - *name* name of the plugin. - - *extensions* the expected extensions for the plugin info file. May be a string or a tuple of strings if several extensions are expected. - """ - IPluginFileAnalyzer.__init__(self,name) - self.setPluginInfoExtension(extensions) - - - def setPluginInfoExtension(self,extensions): - """ - Set the extension that will identify a plugin info file. - - *extensions* May be a string or a tuple of strings if several extensions are expected. - """ - # Make sure extension is a tuple - if not isinstance(extensions, tuple): - extensions = (extensions, ) - self.expectedExtensions = extensions - - - def isValidPlugin(self, filename): - """ - Check if it is a valid plugin based on the given plugin info file extension(s). - If several extensions are provided, the first matching will cause the function - to exit successfully. - """ - res = False - for ext in self.expectedExtensions: - if filename.endswith(".%s" % ext): - res = True - break - return res - - def getPluginNameAndModuleFromStream(self, infoFileObject, candidate_infofile=None): - """ - Extract the name and module of a plugin from the - content of the info file that describes it and which - is stored in ``infoFileObject``. - - .. note:: Prefer using ``_extractCorePluginInfo`` - instead, whenever possible... - - .. warning:: ``infoFileObject`` must be a file-like object: - either an opened file for instance or a string - buffer wrapped in a StringIO instance as another - example. - - .. note:: ``candidate_infofile`` must be provided - whenever possible to get better error messages. - - Return a 3-uple with the name of the plugin, its - module and the config_parser used to gather the core - data *in a tuple*, if the required info could be - localised, else return ``(None,None,None)``. - - .. note:: This is supposed to be used internally by subclasses - and decorators. - """ - # parse the information buffer to get info about the plugin - config_parser = configparser.ConfigParser() - try: - config_parser.read_file(infoFileObject) - except Exception as e: - log.debug("Could not parse the plugin file '%s' (exception raised was '%s')" % (candidate_infofile,e)) - return (None, None, None) - # check if the basic info is available - if not config_parser.has_section("Core"): - log.debug("Plugin info file has no 'Core' section (in '%s')" % candidate_infofile) - return (None, None, None) - if not config_parser.has_option("Core","Name") or not config_parser.has_option("Core","Module"): - log.debug("Plugin info file has no 'Name' or 'Module' section (in '%s')" % candidate_infofile) - return (None, None, None) - # check that the given name is valid - name = config_parser.get("Core", "Name") - name = name.strip() - if PLUGIN_NAME_FORBIDEN_STRING in name: - log.debug("Plugin name contains forbiden character: %s (in '%s')" % (PLUGIN_NAME_FORBIDEN_STRING, - candidate_infofile)) - return (None, None, None) - return (name, config_parser.get("Core", "Module"), config_parser) - - def _extractCorePluginInfo(self,directory, filename): - """ - Gather the core information (name, and module to be loaded) - about a plugin described by it's info file (found at - 'directory/filename'). - - Return a dictionary with name and path of the plugin as well - as the ConfigParser instance used to collect these info. - - .. note:: This is supposed to be used internally by subclasses - and decorators. - """ - # now we can consider the file as a serious candidate - if not isinstance(filename, str): - # filename is a file object: use it - name, moduleName, config_parser = self.getPluginNameAndModuleFromStream(filename) - else: - candidate_infofile_path = os.path.join(directory, filename) - # parse the information file to get info about the plugin - with open(candidate_infofile_path) as candidate_infofile: - name, moduleName, config_parser = self.getPluginNameAndModuleFromStream(candidate_infofile,candidate_infofile_path) - if (name, moduleName, config_parser) == (None, None, None): - return (None,None) - infos = {"name":name, "path":os.path.join(directory, moduleName)} - return infos, config_parser - - def _extractBasicPluginInfo(self,directory, filename): - """ - Gather some basic documentation about the plugin described by - it's info file (found at 'directory/filename'). - - Return a dictionary containing the core information (name and - path) as well as as the 'documentation' info (version, author, - description etc). - - See also: - - ``self._extractCorePluginInfo`` - """ - infos, config_parser = self._extractCorePluginInfo(directory, filename) - # collect additional (but usually quite usefull) information - if infos and config_parser and config_parser.has_section("Documentation"): - if config_parser.has_option("Documentation","Author"): - infos["author"] = config_parser.get("Documentation", "Author") - if config_parser.has_option("Documentation","Version"): - infos["version"] = config_parser.get("Documentation", "Version") - if config_parser.has_option("Documentation","Website"): - infos["website"] = config_parser.get("Documentation", "Website") - if config_parser.has_option("Documentation","Copyright"): - infos["copyright"] = config_parser.get("Documentation", "Copyright") - if config_parser.has_option("Documentation","Description"): - infos["description"] = config_parser.get("Documentation", "Description") - return infos, config_parser - - def getInfosDictFromPlugin(self, dirpath, filename): - """ - Returns the extracted plugin informations as a dictionary. - This function ensures that "name" and "path" are provided. - - If *callback* function has not been provided for this strategy, - we use the filename alone to extract minimal informations. - """ - infos, config_parser = self._extractBasicPluginInfo(dirpath, filename) - if not infos or infos.get("name", None) is None: - raise ValueError("Missing *name* of the plugin in extracted infos.") - if not infos or infos.get("path", None) is None: - raise ValueError("Missing *path* of the plugin in extracted infos.") - return infos, config_parser - - -class PluginFileAnalyzerMathingRegex(IPluginFileAnalyzer): - """ - An analyzer that targets plugins decribed by files whose name match a given regex. - """ - def __init__(self, name, regexp): - IPluginFileAnalyzer.__init__(self,name) - self.regexp = regexp - - def isValidPlugin(self, filename): - """ - Checks if the given filename is a valid plugin for this Strategy - """ - reg = re.compile(self.regexp) - if reg.match(filename) is not None: - return True - return False - - def getInfosDictFromPlugin(self, dirpath, filename): - """ - Returns the extracted plugin informations as a dictionary. - This function ensures that "name" and "path" are provided. - """ - # use the filename alone to extract minimal informations. - infos = {} - module_name = os.path.splitext(filename)[0] - plugin_filename = os.path.join(dirpath,filename) - if module_name == "__init__": - module_name = os.path.basename(dirpath) - plugin_filename = dirpath - infos["name"] = "%s" % module_name - infos["path"] = plugin_filename - cf_parser = configparser.ConfigParser() - cf_parser.add_section("Core") - cf_parser.set("Core","Name",infos["name"]) - cf_parser.set("Core","Module",infos["path"]) - return infos,cf_parser - - - -class PluginFileLocator(IPluginLocator): - """ - Locates plugins on the file system using a set of analyzers to - determine what files actually corresponds to plugins. - - If more than one analyzer is being used, the first that will discover a - new plugin will avoid other strategies to find it too. - - By default each directory set as a "plugin place" is scanned - recursively. You can change that by a call to - ``disableRecursiveScan``. - """ - def __init__(self, analyzers=None, plugin_info_cls=PluginInfo): - """ - Defines the strategies, and the places for plugins to look into. - """ - IPluginLocator.__init__(self) - self._discovered_plugins = {} - self.setPluginPlaces(None) - self._analyzers = analyzers # analyzers used to locate plugins - if self._analyzers is None: - self._analyzers = [PluginFileAnalyzerWithInfoFile("info_ext")] - self._default_plugin_info_cls = PluginInfo - self._plugin_info_cls_map = {} - self._max_size = 1e3*1024 # in octets (by default 1 Mo) - self.recursive = True - - def disableRecursiveScan(self): - """ - Disable recursive scan of the directories given as plugin places. - """ - self.recursive = False - - def setAnalyzers(self, analyzers): - """ - Sets a new set of analyzers. - - .. warning:: the new analyzers won't be aware of the plugin - info class that may have been set via a previous - call to ``setPluginInfoClass``. - """ - self._analyzers = analyzers - - def removeAnalyzers(self, name): - """ - Removes analyzers of a given name. - """ - analyzersListCopy = self._analyzers[:] - foundAndRemoved = False - for obj in analyzersListCopy: - if obj.name == name: - self._analyzers.remove(obj) - foundAndRemoved = True - if not foundAndRemoved: - log.debug("'%s' is not a known strategy name: can't remove it." % name) - - def removeAllAnalyzer(self): - """ - Remove all analyzers. - """ - self._analyzers = [] - - def appendAnalyzer(self, analyzer): - """ - Append an analyzer to the existing list. - """ - self._analyzers.append(analyzer) - - - def _getInfoForPluginFromAnalyzer(self,analyzer,dirpath, filename): - """ - Return an instance of plugin_info_cls filled with data extracted by the analyzer. - - May return None if the analyzer fails to extract any info. - """ - plugin_info_dict,config_parser = analyzer.getInfosDictFromPlugin(dirpath, filename) - if plugin_info_dict is None: - return None - plugin_info_cls = self._plugin_info_cls_map.get(analyzer.name,self._default_plugin_info_cls) - plugin_info = plugin_info_cls(plugin_info_dict["name"],plugin_info_dict["path"]) - plugin_info.details = config_parser - return plugin_info - - def locatePlugins(self): - """ - Walk through the plugins' places and look for plugins. - - Return the candidates and number of plugins found. - """ -# print "%s.locatePlugins" % self.__class__ - _candidates = [] - _discovered = {} - for directory in map(os.path.abspath, self.plugins_places): - # first of all, is it a directory :) - if not os.path.isdir(directory): - log.debug("%s skips %s (not a directory)" % (self.__class__.__name__, directory)) - continue - if self.recursive: - debug_txt_mode = "recursively" - walk_iter = os.walk(directory, followlinks=True) - else: - debug_txt_mode = "non-recursively" - walk_iter = [(directory,[],os.listdir(directory))] - # iteratively walks through the directory - log.debug("%s walks (%s) into directory: %s" % (self.__class__.__name__, debug_txt_mode, directory)) - for item in walk_iter: - dirpath = item[0] - for filename in item[2]: - # print("testing candidate file %s" % filename) - for analyzer in self._analyzers: - # print("... with analyzer %s" % analyzer.name) - # eliminate the obvious non plugin files - if not analyzer.isValidPlugin(filename): - log.debug("%s is not a valid plugin for strategy %s" % (filename, analyzer.name)) - continue - candidate_infofile = os.path.join(dirpath, filename) - if candidate_infofile in _discovered: - log.debug("%s (with strategy %s) rejected because already discovered" % (candidate_infofile, analyzer.name)) - continue - log.debug("%s found a candidate:\n %s" % (self.__class__.__name__, candidate_infofile)) -# print candidate_infofile - plugin_info = self._getInfoForPluginFromAnalyzer(analyzer, dirpath, filename) - if plugin_info is None: - log.warning("Plugin candidate '%s' rejected by strategy '%s'" % (candidate_infofile, analyzer.name)) - break # we consider this was the good strategy to use for: it failed -> not a plugin -> don't try another strategy - # now determine the path of the file to execute, - # depending on wether the path indicated is a - # directory or a file -# print plugin_info.path - # Remember all the files belonging to a discovered - # plugin, so that strategies (if several in use) won't - # collide - if os.path.isdir(plugin_info.path): - candidate_filepath = os.path.join(plugin_info.path, "__init__") - # it is a package, adds all the files concerned - for _file in os.listdir(plugin_info.path): - if _file.endswith(".py"): - self._discovered_plugins[os.path.join(plugin_info.path, _file)] = candidate_filepath - _discovered[os.path.join(plugin_info.path, _file)] = candidate_filepath - elif (plugin_info.path.endswith(".py") and os.path.isfile(plugin_info.path)) or os.path.isfile(plugin_info.path+".py"): - candidate_filepath = plugin_info.path - if candidate_filepath.endswith(".py"): - candidate_filepath = candidate_filepath[:-3] - # it is a file, adds it - self._discovered_plugins[".".join((plugin_info.path, "py"))] = candidate_filepath - _discovered[".".join((plugin_info.path, "py"))] = candidate_filepath - else: - log.error("Plugin candidate rejected: cannot find the file or directory module for '%s'" % (candidate_infofile)) - break -# print candidate_filepath - _candidates.append((candidate_infofile, candidate_filepath, plugin_info)) - # finally the candidate_infofile must not be discovered again - _discovered[candidate_infofile] = candidate_filepath - self._discovered_plugins[candidate_infofile] = candidate_filepath -# print "%s found by strategy %s" % (candidate_filepath, analyzer.name) - return _candidates, len(_candidates) - - def gatherCorePluginInfo(self, directory, filename): - """ - Return a ``PluginInfo`` as well as the ``ConfigParser`` used to build it. - - If filename is a valid plugin discovered by any of the known - strategy in use. Returns None,None otherwise. - """ - for analyzer in self._analyzers: - # eliminate the obvious non plugin files - if not analyzer.isValidPlugin(filename): - continue - plugin_info = self._getInfoForPluginFromAnalyzer(analyzer,directory, filename) - return plugin_info,plugin_info.details - return None,None - - # ----------------------------------------------- - # Backward compatible methods - # Note: their implementation must be conform to their - # counterpart in yapsy<1.10 - # ----------------------------------------------- - - def getPluginNameAndModuleFromStream(self, infoFileObject, candidate_infofile=None): - for analyzer in self._analyzers: - if analyzer.name == "info_ext": - return analyzer.getPluginNameAndModuleFromStream(infoFileObject) - else: - raise RuntimeError("No current file analyzer is able to provide plugin information from stream") - - def setPluginInfoClass(self, picls, name=None): - """ - Set the class that holds PluginInfo. The class should inherit - from ``PluginInfo``. - - If name is given, then the class will be used only by the corresponding analyzer. - - If name is None, the class will be set for all analyzers. - """ - if name is None: - self._default_plugin_info_cls = picls - self._plugin_info_cls_map = {} - else: - self._plugin_info_cls_map[name] = picls - - def setPluginPlaces(self, directories_list): - """ - Set the list of directories where to look for plugin places. - """ - if directories_list is None: - directories_list = [os.path.dirname(__file__)] - self.plugins_places = directories_list - - def updatePluginPlaces(self, directories_list): - """ - Updates the list of directories where to look for plugin places. - """ - self.plugins_places = list(set.union(set(directories_list), set(self.plugins_places))) - - def setPluginInfoExtension(self, ext): - """ - DEPRECATED(>1.9): for backward compatibility. Directly configure the - IPluginLocator instance instead ! - - This will only work if the strategy "info_ext" is active - for locating plugins. - """ - for analyzer in self._analyzers: - if analyzer.name == "info_ext": - analyzer.setPluginInfoExtension(ext) diff --git a/yapsy/PluginInfo.py b/yapsy/PluginInfo.py deleted file mode 100644 index c3cc8d51..00000000 --- a/yapsy/PluginInfo.py +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - - -""" -Role -==== - -Encapsulate a plugin instance as well as some metadata. - -API -=== -""" - -from configparser import ConfigParser -from distutils.version import StrictVersion - - -class PluginInfo(object): - """Representation of the most basic set of information related to a - given plugin such as its name, author, description... - - Any additional information can be stored ad retrieved in a - PluginInfo, when this one is created with a - ``ConfigParser.ConfigParser`` instance. - - This typically means that when metadata is read from a text file - (the original way for yapsy to describe plugins), all info that is - not part of the basic variables (name, path, version etc), can - still be accessed though the ``details`` member variables that - behaves like Python's ``ConfigParser.ConfigParser``. - - Warning: the instance associated with the ``details`` member - variable is never copied and used to store all plugin infos. If - you set it to a custom instance, it will be modified as soon as - another member variale of the plugin info is - changed. Alternatively, if you change the instance "outside" the - plugin info, it will also change the plugin info. - """ - - def __init__(self, plugin_name, plugin_path): - """ - Set the basic information (at least name and path) about the - plugin as well as the default values for other usefull - variables. - - *plugin_name* is a simple string describing the name of - the plugin. - - *plugin_path* describe the location where the plugin can be - found. - - .. warning:: The ``path`` attribute is the full path to the - plugin if it is organised as a directory or the - full path to a file without the ``.py`` extension - if the plugin is defined by a simple file. In the - later case, the actual plugin is reached via - ``plugin_info.path+'.py'``. - """ - self.__details = ConfigParser() - self.name = plugin_name - self.path = plugin_path - self._ensureDetailsDefaultsAreBackwardCompatible() - # Storage for stuff created during the plugin lifetime - self.plugin_object = None - self.categories = [] - self.error = None - - - def __setDetails(self,cfDetails): - """ - Fill in all details by storing a ``ConfigParser`` instance. - - .. warning: The values for ``plugin_name`` and - ``plugin_path`` given a init time will superseed - any value found in ``cfDetails`` in section - 'Core' for the options 'Name' and 'Module' (this - is mostly for backward compatibility). - """ - bkp_name = self.name - bkp_path = self.path - self.__details = cfDetails - self.name = bkp_name - self.path = bkp_path - self._ensureDetailsDefaultsAreBackwardCompatible() - - def __getDetails(self): - return self.__details - - def __getName(self): - return self.details.get("Core","Name") - - def __setName(self, name): - if not self.details.has_section("Core"): - self.details.add_section("Core") - self.details.set("Core","Name",name) - - - def __getPath(self): - return self.details.get("Core","Module") - - def __setPath(self,path): - if not self.details.has_section("Core"): - self.details.add_section("Core") - self.details.set("Core","Module",path) - - - def __getVersion(self): - return StrictVersion(self.details.get("Documentation","Version")) - - def setVersion(self, vstring): - """ - Set the version of the plugin. - - Used by subclasses to provide different handling of the - version number. - """ - if isinstance(vstring,StrictVersion): - vstring = str(vstring) - if not self.details.has_section("Documentation"): - self.details.add_section("Documentation") - self.details.set("Documentation","Version",vstring) - - def __getAuthor(self): - return self.details.get("Documentation","Author") - - def __setAuthor(self,author): - if not self.details.has_section("Documentation"): - self.details.add_section("Documentation") - self.details.set("Documentation","Author",author) - - - def __getCopyright(self): - return self.details.get("Documentation","Copyright") - - def __setCopyright(self,copyrightTxt): - if not self.details.has_section("Documentation"): - self.details.add_section("Documentation") - self.details.set("Documentation","Copyright",copyrightTxt) - - - def __getWebsite(self): - return self.details.get("Documentation","Website") - - def __setWebsite(self,website): - if not self.details.has_section("Documentation"): - self.details.add_section("Documentation") - self.details.set("Documentation","Website",website) - - - def __getDescription(self): - return self.details.get("Documentation","Description") - - def __setDescription(self,description): - if not self.details.has_section("Documentation"): - self.details.add_section("Documentation") - return self.details.set("Documentation","Description",description) - - - def __getCategory(self): - """ - DEPRECATED (>1.9): Mimic former behaviour when what is - noz the first category was considered as the only one the - plugin belonged to. - """ - if self.categories: - return self.categories[0] - else: - return "UnknownCategory" - - def __setCategory(self,c): - """ - DEPRECATED (>1.9): Mimic former behaviour by making so - that if a category is set as it it was the only category to - which the plugin belongs, then a __getCategory will return - this newly set category. - """ - self.categories = [c] + self.categories - - name = property(fget=__getName,fset=__setName) - path = property(fget=__getPath,fset=__setPath) - version = property(fget=__getVersion,fset=setVersion) - author = property(fget=__getAuthor,fset=__setAuthor) - copyright = property(fget=__getCopyright,fset=__setCopyright) - website = property(fget=__getWebsite,fset=__setWebsite) - description = property(fget=__getDescription,fset=__setDescription) - details = property(fget=__getDetails,fset=__setDetails) - # deprecated (>1.9): plugins are not longer associated to a - # single category ! - category = property(fget=__getCategory,fset=__setCategory) - - def _getIsActivated(self): - """ - Return the activated state of the plugin object. - Makes it possible to define a property. - """ - return self.plugin_object.is_activated - - is_activated = property(fget=_getIsActivated) - - def _ensureDetailsDefaultsAreBackwardCompatible(self): - """ - Internal helper function. - """ - if not self.details.has_option("Documentation","Author"): - self.author = "Unknown" - if not self.details.has_option("Documentation","Version"): - self.version = "0.0" - if not self.details.has_option("Documentation","Website"): - self.website = "None" - if not self.details.has_option("Documentation","Copyright"): - self.copyright = "Unknown" - if not self.details.has_option("Documentation","Description"): - self.description = "" diff --git a/yapsy/PluginManager.py b/yapsy/PluginManager.py deleted file mode 100644 index 9ac966ba..00000000 --- a/yapsy/PluginManager.py +++ /dev/null @@ -1,663 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - -""" -Role -==== - -The ``PluginManager`` loads plugins that enforce the `Plugin -Description Policy`_, and offers the most simple methods to activate -and deactivate the plugins once they are loaded. - -.. note:: It may also classify the plugins in various categories, but - this behaviour is optional and if not specified elseway all - plugins are stored in the same default category. - -.. note:: It is often more useful to have the plugin manager behave - like singleton, this functionality is provided by - ``PluginManagerSingleton`` - - -Plugin Description Policy -========================= - -When creating a ``PluginManager`` instance, one should provide it with -a list of directories where plugins may be found. In each directory, -a plugin should contain the following elements: - -For a *Standard* plugin: - - ``myplugin.yapsy-plugin`` - - A *plugin info file* identical to the one previously described. - - ``myplugin`` - - A directory ontaining an actual Python plugin (ie with a - ``__init__.py`` file that makes it importable). The upper - namespace of the plugin should present a class inheriting the - ``IPlugin`` interface (the same remarks apply here as in the - previous case). - - -For a *Single file* plugin: - - ``myplugin.yapsy-plugin`` - - A *plugin info file* which is identified thanks to its extension, - see the `Plugin Info File Format`_ to see what should be in this - file. - - The extension is customisable at the ``PluginManager``'s - instanciation, since one may usually prefer the extension to bear - the application name. - - ``myplugin.py`` - - The source of the plugin. This file should at least define a class - inheriting the ``IPlugin`` interface. This class will be - instanciated at plugin loading and it will be notified the - activation/deactivation events. - - -Plugin Info File Format ------------------------ - -The plugin info file is a text file *encoded in ASCII or UTF-8* and -gathering, as its name suggests, some basic information about the -plugin. - -- it gives crucial information needed to be able to load the plugin - -- it provides some documentation like information like the plugin - author's name and a short description fo the plugin functionality. - -Here is an example of what such a file should contain:: - - [Core] - Name = My plugin Name - Module = the_name_of_the_pluginto_load_with_no_py_ending - - [Documentation] - Description = What my plugin broadly does - Author = My very own name - Version = the_version_number_of_the_plugin - Website = My very own website - - - -.. note:: From such plugin descriptions, the ``PluginManager`` will - built its own representations of the plugins as instances of - the :doc:`PluginInfo` class. - -Changing the default behaviour -============================== - -The default behaviour for locating and loading plugins can be changed -using the various options exposed on the interface via getters. - -The plugin detection, in particular, can be fully customized by -settting a custom plugin locator. See ``IPluginLocator`` for more -details on this. - - -Extensibility -============= - -Several mechanisms have been put up to help extending the basic -functionalities of the proivided classes. - -A few *hints* to help you extend those classes: - -If the new functionalities do not overlap the ones already -implemented, then they should be implemented as a Decorator class of the -base plugin. This should be done by inheriting the -``PluginManagerDecorator``. - -If this previous way is not possible, then the functionalities should -be added as a subclass of ``PluginManager``. - -.. note:: The first method is highly prefered since it makes it - possible to have a more flexible design where one can pick - several functionalities and litterally *add* them to get an - object corresponding to one's precise needs. - -API -=== - -""" - -import sys -import os -import imp - -from yapsy import log -from yapsy import NormalizePluginNameForModuleName - -from yapsy.IPlugin import IPlugin -from yapsy.IPluginLocator import IPluginLocator -# The follozing two imports are used to implement the default behaviour -from yapsy.PluginFileLocator import PluginFileAnalyzerWithInfoFile -from yapsy.PluginFileLocator import PluginFileLocator -# imported for backward compatibility (this variable was defined here -# before 1.10) -from yapsy import PLUGIN_NAME_FORBIDEN_STRING -# imported for backward compatibility (this PluginInfo was imported -# here before 1.10) -from yapsy.PluginInfo import PluginInfo - - -class PluginManager(object): - """ - Manage several plugins by ordering them in categories. - - The mechanism for searching and loading the plugins is already - implemented in this class so that it can be used directly (hence - it can be considered as a bit more than a mere interface) - - The file describing a plugin must be written in the syntax - compatible with Python's ConfigParser module as in the - `Plugin Info File Format`_ - """ - - def __init__(self, - categories_filter=None, - directories_list=None, - plugin_info_ext=None, - plugin_locator=None): - """ - Initialize the mapping of the categories and set the list of - directories where plugins may be. This can also be set by - direct call the methods: - - - ``setCategoriesFilter`` for ``categories_filter`` - - ``setPluginPlaces`` for ``directories_list`` - - ``setPluginInfoExtension`` for ``plugin_info_ext`` - - You may look at these function's documentation for the meaning - of each corresponding arguments. - """ - # as a good practice we don't use mutable objects as default - # values (these objects would become like static variables) - # for function/method arguments, but rather use None. - if categories_filter is None: - categories_filter = {"Default":IPlugin} - self.setCategoriesFilter(categories_filter) - plugin_locator = self._locatorDecide(plugin_info_ext, plugin_locator) - # plugin_locator could be either a dict defining strategies, or directly - # an IPluginLocator object - self.setPluginLocator(plugin_locator, directories_list) - - def _locatorDecide(self, plugin_info_ext, plugin_locator): - """ - For backward compatibility, we kept the *plugin_info_ext* argument. - Thus we may use it if provided. Returns the (possibly modified) - *plugin_locator*. - """ - specific_info_ext = plugin_info_ext is not None - specific_locator = plugin_locator is not None - if not specific_info_ext and not specific_locator: - # use the default behavior - res = PluginFileLocator() - elif not specific_info_ext and specific_locator: - # plugin_info_ext not used - res = plugin_locator - elif not specific_locator and specific_info_ext: - # plugin_locator not used, and plugin_info_ext provided - # -> compatibility mode - res = PluginFileLocator() - res.setAnalyzers([PluginFileAnalyzerWithInfoFile("info_ext",plugin_info_ext)]) - elif specific_info_ext and specific_locator: - # both provided... issue a warning that tells "plugin_info_ext" - # will be ignored - msg = ("Two incompatible arguments (%s) provided:", - "'plugin_info_ext' and 'plugin_locator'). Ignoring", - "'plugin_info_ext'.") - raise ValueError(" ".join(msg) % self.__class__.__name__) - return res - - def setCategoriesFilter(self, categories_filter): - """ - Set the categories of plugins to be looked for as well as the - way to recognise them. - - The ``categories_filter`` first defines the various categories - in which the plugins will be stored via its keys and it also - defines the interface tha has to be inherited by the actual - plugin class belonging to each category. - """ - self.categories_interfaces = categories_filter.copy() - # prepare the mapping from categories to plugin lists - self.category_mapping = {} - # also maps the plugin info files (useful to avoid loading - # twice the same plugin...) - self._category_file_mapping = {} - for categ in categories_filter: - self.category_mapping[categ] = [] - self._category_file_mapping[categ] = [] - - - def setPluginPlaces(self, directories_list): - """ - DEPRECATED(>1.9): directly configure the IPluginLocator instance instead ! - - Convenience method (actually call the IPluginLocator method) - """ - self.getPluginLocator().setPluginPlaces(directories_list) - - def updatePluginPlaces(self, directories_list): - """ - DEPRECATED(>1.9): directly configure the IPluginLocator instance instead ! - - Convenience method (actually call the IPluginLocator method) - """ - self.getPluginLocator().updatePluginPlaces(directories_list) - - def setPluginInfoExtension(self, ext): - """ - DEPRECATED(>1.9): for backward compatibility. Directly configure the - IPluginLocator instance instead ! - - .. warning:: This will only work if the strategy "info_ext" is - active for locating plugins. - """ - try: - self.getPluginLocator().setPluginInfoExtension(ext) - except KeyError: - log.error("Current plugin locator doesn't support setting the plugin info extension.") - - def setPluginInfoClass(self, picls, strategies=None): - """ - DEPRECATED(>1.9): directly configure the IPluginLocator instance instead ! - - Convenience method (actually call self.getPluginLocator().setPluginInfoClass) - - When using a ``PluginFileLocator`` you may restrict the - strategies to which the change of PluginInfo class will occur - by just giving the list of strategy names in the argument - "strategies" - """ - if strategies: - for name in strategies: - self.getPluginLocator().setPluginInfoClass(picls, name) - else: - self.getPluginLocator().setPluginInfoClass(picls) - - def getPluginInfoClass(self): - """ - DEPRECATED(>1.9): directly control that with the IPluginLocator - instance instead ! - - Get the class that holds PluginInfo. - """ - return self.getPluginLocator().getPluginInfoClass() - - def setPluginLocator(self, plugin_locator, dir_list=None, picls=None): - """ - Sets the strategy used to locate the basic information. - - See ``IPluginLocator`` for the policy that plugin_locator must enforce. - """ - if isinstance(plugin_locator, IPluginLocator): - self._plugin_locator = plugin_locator - if dir_list is not None: - self._plugin_locator.updatePluginPlaces(dir_list) - if picls is not None: - self.setPluginInfoClass(picls) - else: - raise TypeError("Unexpected format for plugin_locator ('%s' is not an instance of IPluginLocator)" % plugin_locator) - - def getPluginLocator(self): - """ - Grant direct access to the plugin locator. - """ - return self._plugin_locator - - def _gatherCorePluginInfo(self, directory, plugin_info_filename): - """ - DEPRECATED(>1.9): please use a specific plugin - locator if you need such information. - - Gather the core information (name, and module to be loaded) - about a plugin described by it's info file (found at - 'directory/filename'). - - Return an instance of ``PluginInfo`` and the - config_parser used to gather the core data *in a tuple*, if the - required info could be localised, else return ``(None,None)``. - - .. note:: This is supposed to be used internally by subclasses - and decorators. - - """ - return self.getPluginLocator().gatherCorePluginInfo(directory,plugin_info_filename) - - def _getPluginNameAndModuleFromStream(self,infoFileObject,candidate_infofile=""): - """ - DEPRECATED(>1.9): please use a specific plugin - locator if you need such information. - - Extract the name and module of a plugin from the - content of the info file that describes it and which - is stored in infoFileObject. - - .. note:: Prefer using ``_gatherCorePluginInfo`` - instead, whenever possible... - - .. warning:: ``infoFileObject`` must be a file-like - object: either an opened file for instance or a string - buffer wrapped in a StringIO instance as another - example. - - .. note:: ``candidate_infofile`` must be provided - whenever possible to get better error messages. - - Return a 3-uple with the name of the plugin, its - module and the config_parser used to gather the core - data *in a tuple*, if the required info could be - localised, else return ``(None,None,None)``. - - .. note:: This is supposed to be used internally by subclasses - and decorators. - """ - return self.getPluginLocator().getPluginNameAndModuleFromStream(infoFileObject, candidate_infofile) - - - def getCategories(self): - """ - Return the list of all categories. - """ - return list(self.category_mapping.keys()) - - def removePluginFromCategory(self, plugin,category_name): - """ - Remove a plugin from the category where it's assumed to belong. - """ - self.category_mapping[category_name].remove(plugin) - - - def appendPluginToCategory(self, plugin, category_name): - """ - Append a new plugin to the given category. - """ - self.category_mapping[category_name].append(plugin) - - def getPluginsOfCategory(self, category_name): - """ - Return the list of all plugins belonging to a category. - """ - return self.category_mapping[category_name][:] - - def getAllPlugins(self): - """ - Return the list of all plugins (belonging to all categories). - """ - allPlugins = set() - for pluginsOfOneCategory in self.category_mapping.values(): - allPlugins.update(pluginsOfOneCategory) - return list(allPlugins) - - def getPluginCandidates(self): - """ - Return the list of possible plugins. - - Each possible plugin (ie a candidate) is described by a 3-uple: - (info file path, python file path, plugin info instance) - - .. warning: locatePlugins must be called before ! - """ - if not hasattr(self, '_candidates'): - raise RuntimeError("locatePlugins must be called before getPluginCandidates") - return self._candidates[:] - - def removePluginCandidate(self,candidateTuple): - """ - Remove a given candidate from the list of plugins that should be loaded. - - The candidate must be represented by the same tuple described - in ``getPluginCandidates``. - - .. warning: locatePlugins must be called before ! - """ - if not hasattr(self, '_candidates'): - raise ValueError("locatePlugins must be called before removePluginCandidate") - self._candidates.remove(candidateTuple) - - def appendPluginCandidate(self, candidateTuple): - """ - Append a new candidate to the list of plugins that should be loaded. - - The candidate must be represented by the same tuple described - in ``getPluginCandidates``. - - .. warning: locatePlugins must be called before ! - """ - if not hasattr(self, '_candidates'): - raise ValueError("locatePlugins must be called before removePluginCandidate") - self._candidates.append(candidateTuple) - - def locatePlugins(self): - """ - Convenience method (actually call the IPluginLocator method) - """ - self._candidates, npc = self.getPluginLocator().locatePlugins() - - def loadPlugins(self, callback=None): - """ - Load the candidate plugins that have been identified through a - previous call to locatePlugins. For each plugin candidate - look for its category, load it and store it in the appropriate - slot of the ``category_mapping``. - - If a callback function is specified, call it before every load - attempt. The ``plugin_info`` instance is passed as an argument to - the callback. - """ -# print "%s.loadPlugins" % self.__class__ - if not hasattr(self, '_candidates'): - raise ValueError("locatePlugins must be called before loadPlugins") - - processed_plugins = [] - for candidate_infofile, candidate_filepath, plugin_info in self._candidates: - # make sure to attribute a unique module name to the one - # that is about to be loaded - plugin_module_name_template = NormalizePluginNameForModuleName("yapsy_loaded_plugin_" + plugin_info.name) + "_%d" - for plugin_name_suffix in range(len(sys.modules)): - plugin_module_name = plugin_module_name_template % plugin_name_suffix - if plugin_module_name not in sys.modules: - break - - # tolerance on the presence (or not) of the py extensions - if candidate_filepath.endswith(".py"): - candidate_filepath = candidate_filepath[:-3] - # if a callback exists, call it before attempting to load - # the plugin so that a message can be displayed to the - # user - if callback is not None: - callback(plugin_info) - # cover the case when the __init__ of a package has been - # explicitely indicated - if "__init__" in os.path.basename(candidate_filepath): - candidate_filepath = os.path.dirname(candidate_filepath) - try: - # use imp to correctly load the plugin as a module - if os.path.isdir(candidate_filepath): - candidate_module = imp.load_module(plugin_module_name,None,candidate_filepath,("py","r",imp.PKG_DIRECTORY)) - else: - with open(candidate_filepath+".py","r") as plugin_file: - candidate_module = imp.load_module(plugin_module_name,plugin_file,candidate_filepath+".py",("py","r",imp.PY_SOURCE)) - except Exception: - exc_info = sys.exc_info() - log.error("Unable to import plugin: %s" % candidate_filepath, exc_info=exc_info) - plugin_info.error = exc_info - processed_plugins.append(plugin_info) - continue - processed_plugins.append(plugin_info) - if "__init__" in os.path.basename(candidate_filepath): - sys.path.remove(plugin_info.path) - # now try to find and initialise the first subclass of the correct plugin interface - for element in (getattr(candidate_module,name) for name in dir(candidate_module)): - plugin_info_reference = None - for category_name in self.categories_interfaces: - try: - is_correct_subclass = issubclass(element, self.categories_interfaces[category_name]) - except TypeError: - continue - if is_correct_subclass and element is not self.categories_interfaces[category_name]: - current_category = category_name - if candidate_infofile not in self._category_file_mapping[current_category]: - # we found a new plugin: initialise it and search for the next one - if not plugin_info_reference: - plugin_info.plugin_object = element() - plugin_info_reference = plugin_info - plugin_info.categories.append(current_category) - self.category_mapping[current_category].append(plugin_info_reference) - self._category_file_mapping[current_category].append(candidate_infofile) - # Remove candidates list since we don't need them any more and - # don't need to take up the space - delattr(self, '_candidates') - return processed_plugins - - def collectPlugins(self): - """ - Walk through the plugins' places and look for plugins. Then - for each plugin candidate look for its category, load it and - stores it in the appropriate slot of the category_mapping. - """ -# print "%s.collectPlugins" % self.__class__ - self.locatePlugins() - self.loadPlugins() - - - def getPluginByName(self,name,category="Default"): - """ - Get the plugin correspoding to a given category and name - """ - if category in self.category_mapping: - for item in self.category_mapping[category]: - if item.name == name: - return item - return None - - def activatePluginByName(self,name,category="Default"): - """ - Activate a plugin corresponding to a given category + name. - """ - pta_item = self.getPluginByName(name,category) - if pta_item is not None: - plugin_to_activate = pta_item.plugin_object - if plugin_to_activate is not None: - log.debug("Activating plugin: %s.%s"% (category,name)) - plugin_to_activate.activate() - return plugin_to_activate - return None - - - def deactivatePluginByName(self,name,category="Default"): - """ - Desactivate a plugin corresponding to a given category + name. - """ - if category in self.category_mapping: - plugin_to_deactivate = None - for item in self.category_mapping[category]: - if item.name == name: - plugin_to_deactivate = item.plugin_object - break - if plugin_to_deactivate is not None: - log.debug("Deactivating plugin: %s.%s"% (category,name)) - plugin_to_deactivate.deactivate() - return plugin_to_deactivate - return None - - -class PluginManagerSingleton(object): - """ - Singleton version of the most basic plugin manager. - - Being a singleton, this class should not be initialised explicitly - and the ``get`` classmethod must be called instead. - - To call one of this class's methods you have to use the ``get`` - method in the following way: - ``PluginManagerSingleton.get().themethodname(theargs)`` - - To set up the various coonfigurables variables of the - PluginManager's behaviour please call explicitly the following - methods: - - - ``setCategoriesFilter`` for ``categories_filter`` - - ``setPluginPlaces`` for ``directories_list`` - - ``setPluginInfoExtension`` for ``plugin_info_ext`` - """ - - __instance = None - - __decoration_chain = None - - def __init__(self): - """ - Initialisation: this class should not be initialised - explicitly and the ``get`` classmethod must be called instead. - - To set up the various configurables variables of the - PluginManager's behaviour please call explicitly the following - methods: - - - ``setCategoriesFilter`` for ``categories_filter`` - - ``setPluginPlaces`` for ``directories_list`` - - ``setPluginInfoExtension`` for ``plugin_info_ext`` - """ - if self.__instance is not None: - raise Exception("Singleton can't be created twice !") - - def setBehaviour(self,list_of_pmd): - """ - Set the functionalities handled by the plugin manager by - giving a list of ``PluginManager`` decorators. - - This function shouldn't be called several time in a same - process, but if it is only the first call will have an effect. - - It also has an effect only if called before the initialisation - of the singleton. - - In cases where the function is indeed going to change anything - the ``True`` value is return, in all other cases, the ``False`` - value is returned. - """ - if self.__decoration_chain is None and self.__instance is None: - log.debug("Setting up a specific behaviour for the PluginManagerSingleton") - self.__decoration_chain = list_of_pmd - return True - else: - log.debug("Useless call to setBehaviour: the singleton is already instanciated of already has a behaviour.") - return False - setBehaviour = classmethod(setBehaviour) - - - def get(self): - """ - Actually create an instance - """ - if self.__instance is None: - if self.__decoration_chain is not None: - # Get the object to be decorated -# print self.__decoration_chain - pm = self.__decoration_chain[0]() - for cls_item in self.__decoration_chain[1:]: -# print cls_item - pm = cls_item(decorated_manager=pm) - # Decorate the whole object - self.__instance = pm - else: - # initialise the 'inner' PluginManagerDecorator - self.__instance = PluginManager() - log.debug("PluginManagerSingleton initialised") - return self.__instance - get = classmethod(get) - - -# For backward compatility import the most basic decorator (it changed -# place as of v1.8) -from yapsy.PluginManagerDecorator import PluginManagerDecorator - diff --git a/yapsy/PluginManagerDecorator.py b/yapsy/PluginManagerDecorator.py deleted file mode 100644 index 893ba5a9..00000000 --- a/yapsy/PluginManagerDecorator.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - - -""" -Role -==== - -Provide an easy way to build a chain of decorators extending the -functionalities of the default plugin manager, when it comes to -activating, deactivating or looking into loaded plugins. - -The ``PluginManagerDecorator`` is the base class to be inherited by -each element of the chain of decorator. - -.. warning:: If you want to customise the way the plugins are detected - and loaded, you should not try to do it by implementing a - new ``PluginManagerDecorator``. Instead, you'll have to - reimplement the :doc:`PluginManager` itself. And if you - do so by enforcing the ``PluginManager`` interface, just - giving an instance of your new manager class to the - ``PluginManagerDecorator`` should be transparent to the - "stantard" decorators. - -API -=== -""" - -import os - -from yapsy.IPlugin import IPlugin -from yapsy import log - - -class PluginManagerDecorator(object): - """ - Add several responsibilities to a plugin manager object in a - more flexible way than by mere subclassing. This is indeed an - implementation of the Decorator Design Patterns. - - - There is also an additional mechanism that allows for the - automatic creation of the object to be decorated when this object - is an instance of PluginManager (and not an instance of its - subclasses). This way we can keep the plugin managers creation - simple when the user don't want to mix a lot of 'enhancements' on - the base class. - """ - - def __init__(self, decorated_object=None, - # The following args will only be used if we need to - # create a default PluginManager - categories_filter={"Default":IPlugin}, - directories_list=[os.path.dirname(__file__)], - plugin_info_ext="yapsy-plugin"): - """ - Mimics the PluginManager's __init__ method and wraps an - instance of this class into this decorator class. - - - *If the decorated_object is not specified*, then we use the - PluginManager class to create the 'base' manager, and to do - so we will use the arguments: ``categories_filter``, - ``directories_list``, and ``plugin_info_ext`` or their - default value if they are not given. - - - *If the decorated object is given*, these last arguments are - simply **ignored** ! - - All classes (and especially subclasses of this one) that want - to be a decorator must accept the decorated manager as an - object passed to the init function under the exact keyword - ``decorated_object``. - """ - - if decorated_object is None: - log.debug("Creating a default PluginManager instance to be decorated.") - from yapsy.PluginManager import PluginManager - decorated_object = PluginManager(categories_filter, - directories_list, - plugin_info_ext) - self._component = decorated_object - - def __getattr__(self,name): - """ - Decorator trick copied from: - http://www.pasteur.fr/formation/infobio/python/ch18s06.html - """ -# print "looking for %s in %s" % (name, self.__class__) - return getattr(self._component,name) - - - def collectPlugins(self): - """ - This function will usually be a shortcut to successively call - ``self.locatePlugins`` and then ``self.loadPlugins`` which are - very likely to be redefined in each new decorator. - - So in order for this to keep on being a "shortcut" and not a - real pain, I'm redefining it here. - """ - self.locatePlugins() - self.loadPlugins() diff --git a/yapsy/VersionedPluginManager.py b/yapsy/VersionedPluginManager.py deleted file mode 100644 index abee6d70..00000000 --- a/yapsy/VersionedPluginManager.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - -""" -Role -==== - -Defines the basic interface for a plugin manager that also keeps track -of versions of plugins - -API -=== -""" - - -from distutils.version import StrictVersion - -from yapsy.PluginInfo import PluginInfo -from yapsy.IPlugin import IPlugin -from yapsy.PluginManagerDecorator import PluginManagerDecorator - - -class VersionedPluginInfo(PluginInfo): - """ - Gather some info about a plugin such as its name, author, - description... - """ - - def __init__(self, plugin_name, plugin_path): - """ - Set the name and path of the plugin as well as the default - values for other usefull variables. - """ - PluginInfo.__init__(self, plugin_name, plugin_path) - # version number is now required to be a StrictVersion object - self.version = StrictVersion("0.0") - - def setVersion(self, vstring): - self.version = StrictVersion(vstring) - - -class VersionedPluginManager(PluginManagerDecorator): - """ - Handle plugin versioning by making sure that when several - versions are present for a same plugin, only the latest version is - manipulated via the standard methods (eg for activation and - deactivation) - - More precisely, for operations that must be applied on a single - named plugin at a time (``getPluginByName``, - ``activatePluginByName``, ``deactivatePluginByName`` etc) the - targetted plugin will always be the one with the latest version. - - .. note:: The older versions of a given plugin are still reachable - via the ``getPluginsOfCategoryFromAttic`` method. - """ - - def __init__(self, - decorated_manager=None, - categories_filter={"Default":IPlugin}, - directories_list=None, - plugin_info_ext="yapsy-plugin"): - """ - Create the plugin manager and record the ConfigParser instance - that will be used afterwards. - - The ``config_change_trigger`` argument can be used to set a - specific method to call when the configuration is - altered. This will let the client application manage the way - they want the configuration to be updated (e.g. write on file - at each change or at precise time intervalls or whatever....) - """ - # Create the base decorator class - PluginManagerDecorator.__init__(self,decorated_manager, - categories_filter, - directories_list, - plugin_info_ext) - self.setPluginInfoClass(VersionedPluginInfo) - # prepare the storage for the early version of the plugins, - # for which only the latest version is the one that will be - # kept in the "core" plugin storage. - self._prepareAttic() - - def _prepareAttic(self): - """ - Create and correctly initialize the storage where the wrong - version of the plugins will be stored. - """ - self._attic = {} - for categ in self.getCategories(): - self._attic[categ] = [] - - - def getLatestPluginsOfCategory(self,category_name): - """ - DEPRECATED(>1.8): Please consider using getPluginsOfCategory - instead. - - Return the list of all plugins belonging to a category. - """ - return self.getPluginsOfCategory(category_name) - - def loadPlugins(self, callback=None): - """ - Load the candidate plugins that have been identified through a - previous call to locatePlugins. - - In addition to the baseclass functionality, this subclass also - needs to find the latest version of each plugin. - """ - self._component.loadPlugins(callback) - for categ in self.getCategories(): - latest_plugins = {} - allPlugins = self.getPluginsOfCategory(categ) - # identify the latest version of each plugin - for plugin in allPlugins: - name = plugin.name - version = plugin.version - if name in latest_plugins: - if version > latest_plugins[name].version: - older_plugin = latest_plugins[name] - latest_plugins[name] = plugin - self.removePluginFromCategory(older_plugin,categ) - self._attic[categ].append(older_plugin) - else: - self.removePluginFromCategory(plugin,categ) - self._attic[categ].append(plugin) - else: - latest_plugins[name] = plugin - - def getPluginsOfCategoryFromAttic(self,categ): - """ - Access the older version of plugins for which only the latest - version is available through standard methods. - """ - return self._attic[categ] - diff --git a/yapsy/__init__.py b/yapsy/__init__.py deleted file mode 100644 index 693fad84..00000000 --- a/yapsy/__init__.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t; python-indent: 4 -*- - -""" - -Overview -======== - -Yapsy's main purpose is to offer a way to easily design a plugin -system in Python, and motivated by the fact that many other Python -plugin system are either too complicated for a basic use or depend on -a lot of libraries. Yapsy only depends on Python's standard library. - -|yapsy| basically defines two core classes: - -- a fully functional though very simple ``PluginManager`` class - -- an interface ``IPlugin`` which defines the interface of plugin - instances handled by the ``PluginManager`` - - -Getting started -=============== - -The basic classes defined by |yapsy| should work "as is" and enable -you to load and activate your plugins. So that the following code -should get you a fully working plugin management system:: - - from yapsy.PluginManager import PluginManager - - # Build the manager - simplePluginManager = PluginManager() - # Tell it the default place(s) where to find plugins - simplePluginManager.setPluginPlaces(["path/to/myplugins"]) - # Load all plugins - simplePluginManager.collectPlugins() - - # Activate all loaded plugins - for pluginInfo in simplePluginManager.getAllPlugins(): - simplePluginManager.activatePluginByName(pluginInfo.name) - - -.. note:: The ``plugin_info`` object (typically an instance of - ``IPlugin``) plays as *the entry point of each - plugin*. That's also where |yapsy| ceases to guide you: it's - up to you to define what your plugins can do and how you - want to talk to them ! Talking to your plugin will then look - very much like the following:: - - # Trigger 'some action' from the loaded plugins - for pluginInfo in simplePluginManager.getAllPlugins(): - pluginInfo.plugin_object.doSomething(...) - -""" - -__version__="1.10.423" - -# tell epydoc that the documentation is in the reStructuredText format -__docformat__ = "restructuredtext en" - -# provide a default named log for package-wide use -import logging -log = logging.getLogger('yapsy') - -# Some constants concerning the plugins -PLUGIN_NAME_FORBIDEN_STRING=";;" -""" -.. warning:: This string (';;' by default) is forbidden in plugin - names, and will be usable to describe lists of plugins - for instance (see :doc:`ConfigurablePluginManager`) -""" - -import re - -RE_NON_ALPHANUM = re.compile("\W") - -def NormalizePluginNameForModuleName(pluginName): - """ - Normalize a plugin name into a safer name for a module name. - - .. note:: may do a little more modifications than strictly - necessary and is not optimized for speed. - """ - if len(pluginName)==0: - return "_" - if pluginName[0].isdigit(): - pluginName = "_" + pluginName - return RE_NON_ALPHANUM.sub("_",pluginName) From 71bc5933445f54b04112477f4a49332f75a37af5 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 30 Dec 2014 14:58:49 +0100 Subject: [PATCH 007/260] legend item will be removed if a signal for the plot plugin is unsubcribed --- papi/gui/qt_new/overview_menu.py | 2 +- papi/last_active_papi.xml | 128 ++++++++++++++------------ papi/plugin/visual/Plot/Plot.py | 151 +++++++++++++++++-------------- 3 files changed, 154 insertions(+), 127 deletions(-) diff --git a/papi/gui/qt_new/overview_menu.py b/papi/gui/qt_new/overview_menu.py index 2e32eef3..604a29fb 100644 --- a/papi/gui/qt_new/overview_menu.py +++ b/papi/gui/qt_new/overview_menu.py @@ -282,7 +282,7 @@ def plugin_item_changed(self, index): subscription = dblock_names[dblock_name] - for signal_uname in subscription.get_signals(): + for signal_uname in sorted(subscription.get_signals()): signal_item = QStandardItem(signal_uname) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 7a9b1308..b4a06e09 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,108 +1,118 @@ - + - - CPU_Load - - - CPUXLoad - - - - 0.01 - - - Plot - - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - (300,300) - 1 - - \w+,\s*\w+ time, s + \w+,\s*\w+ Label-X - - Used display name - VisualPlugin - - - ^\[(\s*\d\s*)+\] - [0 1 2 3 4] + + (300,300) + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) 1 - Color - + + Plot + + + 0 bool ^(1|0)$ - 0 - Grid-X + Rolling Plot - ^\[(\s*\d\s*)+\] [0 0 0 0 0] + ^\[(\s*\d\s*)+\] 1 Style - - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - (0,0) - 1 + + 0 + bool + ^(1|0)$ + Grid-Y - ^([1-9][0-9]{0,3}|10000)$ 1000 + ^([1-9][0-9]{0,3}|10000)$ 1 Buffersize - - Plot - - - bool - ^(1|0)$ - 0 - Grid-Y + + [0 1 2 3 4] + ^\[(\s*\d\s*)+\] + 1 + Color - - (\d+) - 1 + + (0,0) + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + 1 - \w+,\s+\w+ amplitude, V + \w+,\s+\w+ Label-Y - + + 0 bool ^(1|0)$ - 0 - Rolling Plot + Grid-X + + + VisualPlugin + Used display name + + + 1 + (\d+) 1000 - 0 - 1 [0 1 2 3 4] - 0 0 + 0 + 0 [0 0 0 0 0] + 1 - CPUXLoad - + Sinus + - load_in_percent + f3_1 + f3_scalar + f3_2 + + Sinus + + + Sinus + + + 3 + [0-9]+ + + + 1 + \d+.{0,1}\d* + + + + 0.6 + + + diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 27f8a1e8..a89a5808 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -356,67 +356,6 @@ def update_plot_single_timestamp(self, data): pass - def quit(self): - """ - Function quit plugin - - :return: - """ - print('PlotPerformance: will quit') - - def get_plugin_configuration(self): - """ - Function get plugin configuration - - :return {}: - """ - config = { - 'label_y': { - 'value': "amplitude, V", - 'regex': '\w+,\s+\w+', - 'display_text': 'Label-Y' - }, 'label_x': { - 'value': "time, s", - 'regex': '\w+,\s*\w+', - 'display_text': 'Label-X' - }, 'x-grid': { - 'value': "0", - 'regex': '^(1|0)$', - 'type': 'bool', - 'display_text': 'Grid-X' - }, 'y-grid': { - 'value': "0", - 'regex': '^(1|0)$', - 'type': 'bool', - 'display_text': 'Grid-Y' - }, 'color': { - 'value': "[0 1 2 3 4]", - 'regex': '^\[(\s*\d\s*)+\]', - 'advanced': '1', - 'display_text': 'Color' - }, 'style': { - 'value': "[0 0 0 0 0]", - 'regex': '^\[(\s*\d\s*)+\]', - 'advanced': '1', - 'display_text': 'Style' - }, 'buffersize': { - 'value': "1000", - 'regex': '^([1-9][0-9]{0,3}|10000)$', - 'advanced': '1', - 'display_text': 'Buffersize' - }, 'downsampling_rate': { - 'value': "1", - 'regex': '(\d+)' - }, 'rolling_plot': { - 'value': '0', - 'regex': '^(1|0)$', - 'type': 'bool', - 'display_text': 'Rolling Plot' - } - } - # http://www.regexr.com/ - return config - def set_buffer_size(self, new_size): """ Function set buffer size @@ -469,6 +408,7 @@ def plugin_meta_updated(self): for signal in subscription.get_signals(): current_signals.append(signal) + current_signals = sorted(current_signals) # Add missing buffers for signal_name in current_signals: @@ -480,6 +420,9 @@ def plugin_meta_updated(self): if signal_name not in current_signals: self.remove_databuffer(signal_name) + self.update_pens() + self.update_legend() + def add_databuffer(self, signal_name, signal_id): """ Create new buffer for signal_name. @@ -496,16 +439,15 @@ def add_databuffer(self, signal_name, signal_id): buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION - legend_name = str(signal_id) + "# " + signal_name + #legend_name = str(signal_id) + "# " + signal_name + legend_name = signal_name + curve = self.__plotWidget__.plot([0, 1], [0, 1], name=legend_name) self.signals[signal_name]['buffer'] = buffer self.signals[signal_name]['curve'] = curve self.signals[signal_name]['id'] = signal_id - - self.__legend__.addItem(curve, legend_name) - - self.update_pens() + self.signals[signal_name]['legend_name'] = legend_name def remove_databuffer(self, signal_name): """ @@ -517,8 +459,9 @@ def remove_databuffer(self, signal_name): if signal_name in self.signals: curve = self.signals[signal_name]['curve'] + legend_name = self.signals[signal_name]['legend_name'] curve.clear() - self.__legend__.removeItem(signal_name) + #self.__legend__.removeItem(legend_name) del self.signals[signal_name] def get_pen(self, index): @@ -547,3 +490,77 @@ def get_pen(self, index): color = self.colors[1] return pq.mkPen(color=color, style=style) + + def update_legend(self): +# self.__plotWidget__.removeItem(self.__legend__) + self.__legend__.scene().removeItem(self.__legend__) + del self.__legend__ + + self.__legend__ = pq.LegendItem((100, 40), offset=(40, 1)) # args are (size, offset) + self.__legend__.setParentItem(self.__plotWidget__.graphicsItem()) + + for signal_name in sorted(self.signals.keys()): + curve = self.signals[signal_name]['curve'] + legend_name = self.signals[signal_name]['legend_name'] + self.__legend__.addItem(curve, legend_name) + + def quit(self): + """ + Function quit plugin + + :return: + """ + print('PlotPerformance: will quit') + + def get_plugin_configuration(self): + """ + Function get plugin configuration + + :return {}: + """ + config = { + 'label_y': { + 'value': "amplitude, V", + 'regex': '\w+,\s+\w+', + 'display_text': 'Label-Y' + }, 'label_x': { + 'value': "time, s", + 'regex': '\w+,\s*\w+', + 'display_text': 'Label-X' + }, 'x-grid': { + 'value': "0", + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'Grid-X' + }, 'y-grid': { + 'value': "0", + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'Grid-Y' + }, 'color': { + 'value': "[0 1 2 3 4]", + 'regex': '^\[(\s*\d\s*)+\]', + 'advanced': '1', + 'display_text': 'Color' + }, 'style': { + 'value': "[0 0 0 0 0]", + 'regex': '^\[(\s*\d\s*)+\]', + 'advanced': '1', + 'display_text': 'Style' + }, 'buffersize': { + 'value': "1000", + 'regex': '^([1-9][0-9]{0,3}|10000)$', + 'advanced': '1', + 'display_text': 'Buffersize' + }, 'downsampling_rate': { + 'value': "1", + 'regex': '(\d+)' + }, 'rolling_plot': { + 'value': '0', + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'Rolling Plot' + } + } + # http://www.regexr.com/ + return config From 8761bbf047b7f7d22b2bbad8b8fa114b07445a49 Mon Sep 17 00:00:00 2001 From: SvKn Date: Tue, 30 Dec 2014 15:11:22 +0100 Subject: [PATCH 008/260] Update README.md --- README.md | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/README.md b/README.md index 29d8ee32..94bf572b 100644 --- a/README.md +++ b/README.md @@ -31,26 +31,3 @@ Documentation Sphinx doc on GitHub: https://tub-control.github.io/PaPI/ PaPI wiki on GitHub: https://github.com/TUB-Control/PaPI/wiki - - -Changelog ------- - -v.0.8 ---- - -* Use plugin as wizards for configurations -* Use ESC and RETURN for window interaction -* New file dialog to avoid performance issues -* [fix] signal names instead of id in overview -* Run/Edit mode -* Set/load backgorund and save it to config -* [fix] When plugin in gui crashs, gui stays alive and plugin will be stopped - - -v.0.8.2 ---- - * New minor feature: PaPI will save a cfg on close. One is able to load this cfg after startup using 'ReloadConfig' - * Big bugfix: signal and signal name relation had an order bug. There is now a new back end structure handling signals - * Signal unsubscribe is now possible using the gui - * Signals, parameter and plugins in overview are sorted now \ No newline at end of file From cd27767293b6cd8f4764c6447fed09876159aa08 Mon Sep 17 00:00:00 2001 From: SvKn Date: Tue, 30 Dec 2014 15:11:58 +0100 Subject: [PATCH 009/260] Update CHANGENLOG.md --- CHANGENLOG.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index b07c8787..67369275 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -1,2 +1,22 @@ -CHANGENLOG -================== \ No newline at end of file +Changelog +------ + +v.0.8.2 +--- + * New minor feature: PaPI will save a cfg on close. One is able to load this cfg after startup using 'ReloadConfig' + * Big bugfix: signal and signal name relation had an order bug. There is now a new back end structure handling signals + * Signal unsubscribe is now possible using the gui + * Signals, parameter and plugins in overview are sorted now + +v.0.8 +--- + +* Use plugin as wizards for configurations +* Use ESC and RETURN for window interaction +* New file dialog to avoid performance issues +* [fix] signal names instead of id in overview +* Run/Edit mode +* Set/load backgorund and save it to config +* [fix] When plugin in gui crashs, gui stays alive and plugin will be stopped + + From a5ccac2a3d853bdb1846f831dc78abfac6427bcf Mon Sep 17 00:00:00 2001 From: SvKn Date: Tue, 30 Dec 2014 15:15:20 +0100 Subject: [PATCH 010/260] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 94bf572b..cb00934b 100644 --- a/README.md +++ b/README.md @@ -31,3 +31,10 @@ Documentation Sphinx doc on GitHub: https://tub-control.github.io/PaPI/ PaPI wiki on GitHub: https://github.com/TUB-Control/PaPI/wiki + +Embedded Packages +------ + +Yapsy 1.10.423 published under BSD-License, http://yapsy.sourceforge.net/#license + +pyqtgraph-0.9.8 published under MIT-License From 0d4d3641ea2558637f64925b56035b771b73310f Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 30 Dec 2014 16:02:27 +0100 Subject: [PATCH 011/260] accept button is now 'save' labeled in the save dialog --- papi/gui/qt_new/main.py | 6 +- papi/gui/qt_new/overview_menu.py | 6 +- papi/last_active_papi.xml | 116 +------------------------------ 3 files changed, 11 insertions(+), 117 deletions(-) diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index 6d206c13..72494ad0 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -262,9 +262,10 @@ def load_triggered(self): fileNames = '' dialog = QFileDialog(self) - dialog.setFileMode(QFileDialog.AnyFile) + dialog.setFileMode(QFileDialog.ExistingFile) dialog.setNameFilter( self.tr("PaPI-Cfg (*.xml)")) dialog.setDirectory(CONFIG_DEFAULT_DIRECTORY) + dialog.setWindowTitle("Load Configuration") if dialog.exec_(): fileNames = dialog.selectedFiles() @@ -282,6 +283,8 @@ def save_triggered(self): dialog.setFileMode(QFileDialog.AnyFile) dialog.setNameFilter( self.tr("PaPI-Cfg (*.xml)")) dialog.setDirectory(CONFIG_DEFAULT_DIRECTORY) + dialog.setWindowTitle("Save Configuration") + dialog.setAcceptMode(QFileDialog.AcceptSave) if dialog.exec_(): fileNames = dialog.selectedFiles() @@ -290,6 +293,7 @@ def save_triggered(self): if fileNames[0] != '': self.gui_api.do_save_xml_config(fileNames[0]) + def save_triggered_thread(self): QtCore.QTimer.singleShot(0, self.save_triggered) diff --git a/papi/gui/qt_new/overview_menu.py b/papi/gui/qt_new/overview_menu.py index 604a29fb..e4b20cee 100644 --- a/papi/gui/qt_new/overview_menu.py +++ b/papi/gui/qt_new/overview_menu.py @@ -728,4 +728,8 @@ def data_changed_parameter_model(self, index, n): def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: - self.close() \ No newline at end of file + self.close() + + if self.pluginTree.hasFocus() and \ + event.key() in [Qt.Key_Return, Qt.Key_Down, Qt.Key_Up, Qt.Key_Left, Qt.Key_Right]: + self.plugin_item_changed(self.pluginTree.currentIndex()) \ No newline at end of file diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index b4a06e09..90dc6a2f 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,118 +1,4 @@ - + - - Plot - - - time, s - \w+,\s*\w+ - Label-X - - - (300,300) - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - 1 - - - Plot - - - 0 - bool - ^(1|0)$ - Rolling Plot - - - [0 0 0 0 0] - ^\[(\s*\d\s*)+\] - 1 - Style - - - 0 - bool - ^(1|0)$ - Grid-Y - - - 1000 - ^([1-9][0-9]{0,3}|10000)$ - 1 - Buffersize - - - [0 1 2 3 4] - ^\[(\s*\d\s*)+\] - 1 - Color - - - (0,0) - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - 1 - - - amplitude, V - \w+,\s+\w+ - Label-Y - - - 0 - bool - ^(1|0)$ - Grid-X - - - VisualPlugin - Used display name - - - 1 - (\d+) - - - - 1000 - [0 1 2 3 4] - 0 - 0 - 0 - [0 0 0 0 0] - 1 - - - - Sinus - - - f3_1 - f3_scalar - f3_2 - - - - - - Sinus - - - Sinus - - - 3 - [0-9]+ - - - 1 - \d+.{0,1}\d* - - - - 0.6 - - - From de0b1869815b97f06e677e6350ec4654270fb5f2 Mon Sep 17 00:00:00 2001 From: SvKn Date: Tue, 30 Dec 2014 16:20:10 +0100 Subject: [PATCH 012/260] Update CHANGENLOG.md --- CHANGENLOG.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 67369275..914739db 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -1,7 +1,21 @@ Changelog ------ -v.0.8.2 +v.0.9 +--- + * New ContextMenu for Plot + * Plot Plugin: Speedup by use of downsampling, more useable parameter, more dynamic legend + * Signals are now stored and managed as object + * Support of configuration types [file, ] + * Unsubscription of single signals became possible + * Use of pyqtgraph 0.9.10 + * Sorting in the overview menu is enabled + * First pictures for plugins + * Error Dialogs pops up if an plugin error occures -> Whole GUI wont die anymore + * [fix] in configloader due to invalid xml messages + * [fix] minor bug fixes in general + +v.0.8.1 --- * New minor feature: PaPI will save a cfg on close. One is able to load this cfg after startup using 'ReloadConfig' * Big bugfix: signal and signal name relation had an order bug. There is now a new back end structure handling signals @@ -16,7 +30,7 @@ v.0.8 * New file dialog to avoid performance issues * [fix] signal names instead of id in overview * Run/Edit mode -* Set/load backgorund and save it to config +* Set/load background and save it to config * [fix] When plugin in gui crashs, gui stays alive and plugin will be stopped From cc1ad0c8d4b0d5609ec4688981a721d01f8ac857 Mon Sep 17 00:00:00 2001 From: SvKn Date: Tue, 30 Dec 2014 16:21:02 +0100 Subject: [PATCH 013/260] Update CHANGENLOG.md --- CHANGENLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 914739db..e7311dda 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -11,7 +11,7 @@ v.0.9 * Use of pyqtgraph 0.9.10 * Sorting in the overview menu is enabled * First pictures for plugins - * Error Dialogs pops up if an plugin error occures -> Whole GUI wont die anymore + * Error Dialogs pops up if a plugin error occurs -> Whole GUI wont die anymore * [fix] in configloader due to invalid xml messages * [fix] minor bug fixes in general From 5eb35eecef58078ff93b4954cd97cd20e5c39c46 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 30 Dec 2014 16:59:54 +0100 Subject: [PATCH 014/260] build dev verion of new conttext menu of plot plugin --- cfg_collection/FourCfg.xml | 120 +++++++++++++++---------- papi/gui/plugin_api.py | 16 +++- papi/gui/qt_new/overview_menu.py | 4 + papi/last_active_papi.xml | 128 ++++++++++++++++----------- papi/plugin/visual/Plot/Plot.py | 95 +++++++++++++++++++- papi/yapsy/PluginManagerDecorator.py | 55 ++++++------ 6 files changed, 285 insertions(+), 133 deletions(-) diff --git a/cfg_collection/FourCfg.xml b/cfg_collection/FourCfg.xml index 2d9a91d2..eb34f4b4 100644 --- a/cfg_collection/FourCfg.xml +++ b/cfg_collection/FourCfg.xml @@ -1,4 +1,4 @@ - + @@ -14,86 +14,94 @@ Plot - - \w+,\s*\w+ - Label-X - time, s + + bool + ^(1|0)$ + Grid-Y + 0 - - ^([1-9][0-9]{0,3}|10000)$ - Buffersize - 1000 + + [0 0 0 0 0] + ^\[(\s*\d\s*)+\] + Style 1 - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) (0,0) + \(([0-9]+),([0-9]+)\) 1 + Determine position: (x,y) + + + Plot - (\d+) 1 + (\d+) - - ^(1|0)$ - bool - Grid-X - 0 + + (300,300) + \(([0-9]+),([0-9]+)\) + 1 + Determine size: (height,width) - - Used display name - VisualPlugin + + amplitude, V + \w+,\s+\w+ + Label-Y - - ^(1|0)$ - bool - Grid-Y - 0 + + time, s + \w+,\s*\w+ + Label-X + [0 1 2 3 4] ^\[(\s*\d\s*)+\] Color - [0 1 2 3 4] 1 - - ^\[(\s*\d\s*)+\] - Style - [0 0 0 0 0] - 1 + + VisualPlugin + Used display name - - Plot + + 1000 + ^([1-9][0-9]{0,3}|10000)$ + Buffersize + 1 - ^(1|0)$ bool + ^(1|0)$ Rolling Plot 0 - - \w+,\s+\w+ - Label-Y - amplitude, V - - - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - (300,300) - 1 + + bool + ^(1|0)$ + Grid-X + 0 - [0 1 2 3 4] - [0 0 0 0 0] 0 + 0 + [0 0 0 0 0] + [0 1 2 3 4] 1 1000 0 - 0 - + + + Add + + + Sum + + + Add @@ -103,6 +111,20 @@ - + + + FourierXRectXMOD + + + rect3 + rect4 + rect5 + rect6 + rect7 + rect8 + rect9 + + + diff --git a/papi/gui/plugin_api.py b/papi/gui/plugin_api.py index f08ddd0f..1e53ac12 100644 --- a/papi/gui/plugin_api.py +++ b/papi/gui/plugin_api.py @@ -109,4 +109,18 @@ def do_delete_plugin_uname(self, uname): :type uname: basestring :return: """ - self.__default_api.do_delete_plugin_uname(uname) \ No newline at end of file + self.__default_api.do_delete_plugin_uname(uname) + + def do_set_parameter(self, plugin_id, parameter_name, value): + """ + Something like a callback function for gui triggered events. + User wants to change a parameter of a plugin + :param plugin_id: id of plugin which owns the parameter + + :type plugin_id: int + :param parameter_name: name of parameter to change + :type parameter_name: basestring + :param value: new parameter value to set + :type value: + """ + self.__default_api.do_set_parameter(plugin_id,parameter_name,value) \ No newline at end of file diff --git a/papi/gui/qt_new/overview_menu.py b/papi/gui/qt_new/overview_menu.py index 2e32eef3..e9afecb2 100644 --- a/papi/gui/qt_new/overview_menu.py +++ b/papi/gui/qt_new/overview_menu.py @@ -38,6 +38,7 @@ from papi.constants import PLUGIN_PCP_IDENTIFIER, PLUGIN_DPP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER, PLUGIN_IOP_IDENTIFIER, \ PLUGIN_STATE_DEAD, PLUGIN_STATE_STOPPED, PLUGIN_STATE_PAUSE, PLUGIN_STATE_RESUMED, PLUGIN_STATE_START_SUCCESFUL +import copy from PySide.QtCore import * from PySide.QtGui import QLineEdit @@ -552,6 +553,7 @@ def add_subscription_action(self, dplugin_uname): indexes = self.blockTree.selectedIndexes() + for index in indexes: if index.isValid(): signal = self.blockTree.model().data(index, Qt.UserRole) @@ -568,6 +570,8 @@ def add_subscription_action(self, dplugin_uname): self.gui_api.do_subscribe(dplugin.id, dplugin_source.id, dblock.name, signals) + #self.blockTree.scrollTo(indexes[-1]) + def remove_subscriber_action(self, subscriber: DPlugin, dblock: DBlock): """ diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 7a9b1308..2c43de57 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,106 +1,128 @@ - + - - CPU_Load + + Fourier_Rect_MOD - CPUXLoad + FourierXRectXMOD - - 0.01 - + Plot - - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - (300,300) - 1 - \w+,\s*\w+ - time, s Label-X + time, s + + + ^(1|0)$ + bool + Grid-Y + 0 Used display name VisualPlugin - - ^\[(\s*\d\s*)+\] - [0 1 2 3 4] + + ^([1-9][0-9]{0,3}|10000)$ 1 - Color - - - bool - ^(1|0)$ - 0 - Grid-X + Buffersize + 1000 ^\[(\s*\d\s*)+\] - [0 0 0 0 0] 1 Style + [0 0 0 0 0] \(([0-9]+),([0-9]+)\) + 1 Determine position: (x,y) (0,0) - 1 - - - ^([1-9][0-9]{0,3}|10000)$ - 1000 - 1 - Buffersize - - - Plot - - bool - ^(1|0)$ - 0 - Grid-Y + + \w+,\s+\w+ + Label-Y + amplitude, V (\d+) 1 - - \w+,\s+\w+ - amplitude, V - Label-Y + + ^(1|0)$ + bool + Grid-X + 1 + + + \(([0-9]+),([0-9]+)\) + 1 + Determine size: (height,width) + (300,300) + + + ^\[(\s*\d\s*)+\] + 1 + Color + [0 1 2 3 4] + + + Plot - bool ^(1|0)$ - 0 + bool Rolling Plot + 0 - 1000 + [0 0 0 0 0] 0 - 1 + 1000 [0 1 2 3 4] 0 - 0 - [0 0 0 0 0] + 1 + 1 - CPUXLoad - + Add + + + Sum + + + + + + Add + + + Add + + + + + + FourierXRectXMOD + - load_in_percent + rect3 + rect4 + rect5 + rect6 + rect7 + rect8 + rect9 diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 27f8a1e8..e8bd82e5 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -39,7 +39,7 @@ current_milli_time = lambda: int(round(time.time() * 1000)) -from papi.pyqtgraph.Qt import QtCore +from papi.pyqtgraph.Qt import QtCore, QtGui class Plot(vip_base): @@ -188,8 +188,14 @@ def initiate_layer_0(self, config=None): self.__update_intervall__ = 25 # in milliseconds + self.setup_context_menu() + + + + return True + def pause(self): """ Function pause @@ -215,7 +221,6 @@ def execute(self, Data=None, block_name=None): :return: """ t = Data['t'] - self.__input_size__ = len(t) self.__tbuffer__.extend(t) self.__new_added_data__ += len(t) @@ -547,3 +552,89 @@ def get_pen(self, index): color = self.colors[1] return pq.mkPen(color=color, style=style) + + + + def setup_context_menu(self): + custMenu = QtGui.QMenu("Options") + axesMenu = QtGui.QMenu('Axes') + + + xAutoAction = QtGui.QAction('Use auto range for X',self.__plotWidget__) + xAutoAction.triggered.connect(lambda : self.use_autorange_x()) + + axesMenu.addAction(xAutoAction) + + + # --------------------------------------------------------- + # Y-Range Actions + axesMenu.addSeparator().setText("Y range") + yAutoAction = QtGui.QAction('&Use auto range for Y', + self.__plotWidget__, + checkable=True, + triggered=self.use_autorange_y ) + self.use_autorange_y() + yAutoAction.setChecked(True) + + yManAction = QtGui.QAction('&Use manual range for Y', + self.__plotWidget__, + checkable=True, + triggered= self.use_man_range_y) + + self.minEdit = QtGui.QLineEdit() + self.minEdit.setFixedWidth(100) + + minEditA = QtGui.QWidgetAction(self.__plotWidget__) + minEditA.setDefaultWidget(self.minEdit) + + + self.maxEdit = QtGui.QLineEdit() + self.maxEdit.setFixedWidth(100) + + maxEditA = QtGui.QWidgetAction(self.__plotWidget__) + maxEditA.setDefaultWidget(self.maxEdit) + + # --------------------------------------------------------- + # Set group for automatic uncheck + rangeMode = QtGui.QActionGroup(self.__plotWidget__) + rangeMode.addAction(yAutoAction) + rangeMode.addAction(yManAction) + + # --------------------------------------------------------- + # add actions to menu + axesMenu.addAction(yManAction) + axesMenu.addAction(minEditA) + axesMenu.addAction(maxEditA) + axesMenu.addAction(yAutoAction) + + + custMenu.addMenu(axesMenu) + self.__plotWidget__.getPlotItem().getViewBox().menu.clear() + self.__plotWidget__.getPlotItem().ctrlMenu = custMenu + + + + + + def use_man_range_y(self): + # TODO: save para + + mi = float(self.minEdit.text()) + ma = float(self.maxEdit.text()) + + self.control_api.do_set_parameter(self.__id__, 'x-grid', '1') + + self.__plotWidget__.getPlotItem().getViewBox().setYRange(mi,ma) + + def use_man_range_x(self): + # TODO: save para + self.__plotWidget__.getPlotItem().getViewBox().setXRange(0,1) + + + def use_autorange_y(self): + # TODO: save para + self.__plotWidget__.getPlotItem().getViewBox().menu.yAutoClicked() + + def use_autorange_x(self): + # TODO: save para + self.__plotWidget__.getPlotItem().getViewBox().menu.xAutoClicked() \ No newline at end of file diff --git a/papi/yapsy/PluginManagerDecorator.py b/papi/yapsy/PluginManagerDecorator.py index 5eabc1ce..06de4f71 100644 --- a/papi/yapsy/PluginManagerDecorator.py +++ b/papi/yapsy/PluginManagerDecorator.py @@ -48,36 +48,35 @@ class PluginManagerDecorator(object): """ def __init__(self, decorated_object=None, - # The following args will only be used if we need to - # create a default PluginManager - categories_filter={"Default":IPlugin}, - directories_list=[os.path.dirname(__file__)], - plugin_info_ext="yapsy-plugin"): - """ - Mimics the PluginManager's __init__ method and wraps an - instance of this class into this decorator class. - - - *If the decorated_object is not specified*, then we use the - PluginManager class to create the 'base' manager, and to do - so we will use the arguments: ``categories_filter``, - ``directories_list``, and ``plugin_info_ext`` or their - default value if they are not given. + # The following args will only be used if we need to + # create a default PluginManager + categories_filter={"Default":IPlugin}, + directories_list=[os.path.dirname(__file__)], + plugin_info_ext="yapsy-plugin"): + """ + Mimics the PluginManager's __init__ method and wraps an + instance of this class into this decorator class. - - *If the decorated object is given*, these last arguments are - simply **ignored** ! + - *If the decorated_object is not specified*, then we use the + PluginManager class to create the 'base' manager, and to do + so we will use the arguments: ``categories_filter``, + ``directories_list``, and ``plugin_info_ext`` or their + default value if they are not given. - All classes (and especially subclasses of this one) that want - to be a decorator must accept the decorated manager as an - object passed to the init function under the exact keyword - ``decorated_object``. - """ - - if decorated_object is None: - log.debug("Creating a default PluginManager instance to be decorated.") - decorated_object = PluginManager(categories_filter, - directories_list, - plugin_info_ext) - self._component = decorated_object + - *If the decorated object is given*, these last arguments are + simply **ignored** ! + + All classes (and especially subclasses of this one) that want + to be a decorator must accept the decorated manager as an + object passed to the init function under the exact keyword + ``decorated_object``. + """ + if decorated_object is None: + log.debug("Creating a default PluginManager instance to be decorated.") + decorated_object = PluginManager(categories_filter, + directories_list, + plugin_info_ext) + self._component = decorated_object def __getattr__(self,name): """ From 00ed42c8bf595403866d79522fac10c82a9938a9 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 30 Dec 2014 17:00:49 +0100 Subject: [PATCH 015/260] small changes due to example description, new version in setup.py --- cfg_collection/ExampleFourier.xml | 245 ++++++++++++++++++ .../{wizard.xml => ExampleWizard.xml} | 0 papi/last_active_papi.xml | 101 +++++++- .../visual/WizardExample/WizardExample.py | 2 +- setup.py | 2 +- 5 files changed, 347 insertions(+), 3 deletions(-) create mode 100644 cfg_collection/ExampleFourier.xml rename cfg_collection/{wizard.xml => ExampleWizard.xml} (100%) diff --git a/cfg_collection/ExampleFourier.xml b/cfg_collection/ExampleFourier.xml new file mode 100644 index 00000000..40b3aed7 --- /dev/null +++ b/cfg_collection/ExampleFourier.xml @@ -0,0 +1,245 @@ + + + + + Add + + + Add + + + + + + Fourier + + + rect1 + rect2 + rect3 + rect4 + rect5 + rect6 + rect7 + rect8 + rect9 + + + + + + Plot + + + Used display name + VisualPlugin + + + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + (438,411) + + + ^(1|0)$ + Grid-Y + bool + 0 + + + 1 + ^([1-9][0-9]{0,3}|10000)$ + Buffersize + 1000 + + + ^(1|0)$ + Rolling Plot + bool + 1 + + + 1 + ^\[(\s*\d\s*)+\] + Style + [0 0 0 0 0] + + + \w+,\s+\w+ + Label-Y + amplitude, V + + + (\d+) + 10 + + + ^(1|0)$ + Grid-X + bool + 0 + + + PlotFourier + + + 1 + ^\[(\s*\d\s*)+\] + Color + [0 1 2 3 4] + + + \w+,\s*\w+ + Label-X + time, s + + + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + (320,2) + + + + 0 + [0 1 2 3 4] + 0 + 10 + 1000 + [0 0 0 0 0] + 0 + + + + Fourier + + + rect1 + rect2 + rect3 + rect4 + rect5 + rect6 + rect7 + rect8 + rect9 + + + + + + Plot + + + Used display name + VisualPlugin + + + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + (300,300) + + + ^(1|0)$ + Grid-Y + bool + 1 + + + 1 + ^([1-9][0-9]{0,3}|10000)$ + Buffersize + 1000 + + + ^(1|0)$ + Rolling Plot + bool + 0 + + + 1 + ^\[(\s*\d\s*)+\] + Style + [0 0 0 0 0] + + + \w+,\s+\w+ + Label-Y + amplitude, V + + + (\d+) + 10 + + + ^(1|0)$ + Grid-X + bool + 1 + + + Plot + + + 1 + ^\[(\s*\d\s*)+\] + Color + [0 1 2 3 4] + + + \w+,\s*\w+ + Label-X + time, s + + + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + (0,0) + + + + 0 + [0 1 2 3 4] + 0 + 10 + 1000 + [0 0 0 0 0] + 0 + + + + Add + + + Sum + + + + + + Fourier_Rect + + + 1 + \d{1,5} + 9999 + + + 1 + \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} + 127.0.0.1 + + + Fourier + + + Fourier + + + + + + diff --git a/cfg_collection/wizard.xml b/cfg_collection/ExampleWizard.xml similarity index 100% rename from cfg_collection/wizard.xml rename to cfg_collection/ExampleWizard.xml diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 90dc6a2f..e23f03cb 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,4 +1,103 @@ - + + + Sinus + + + 0.6 + + + + + Plot + + + 1 + Style + ^\[(\s*\d\s*)+\] + [0 0 0 0 0] + + + Used display name + VisualPlugin + + + (\d+) + 1 + + + bool + Grid-Y + ^(1|0)$ + 0 + + + 1 + Buffersize + ^([1-9][0-9]{0,3}|10000)$ + 1000 + + + bool + Grid-X + ^(1|0)$ + 0 + + + 1 + Color + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] + + + bool + Rolling Plot + ^(1|0)$ + 0 + + + Label-Y + \w+,\s+\w+ + amplitude, V + + + Determine size: (height,width) + 1 + \(([0-9]+),([0-9]+)\) + (300,300) + + + Determine position: (x,y) + 1 + \(([0-9]+),([0-9]+)\) + (400,100) + + + Label-X + \w+,\s*\w+ + time, s + + + + 0 + [0 0 0 0 0] + [0 1 2 3 4] + 0 + 0 + 1000 + 1 + + + + Sin1 + + + f3_1 + f3_2 + f3_scalar + + + + diff --git a/papi/plugin/visual/WizardExample/WizardExample.py b/papi/plugin/visual/WizardExample/WizardExample.py index d9c80975..0e7f56f7 100644 --- a/papi/plugin/visual/WizardExample/WizardExample.py +++ b/papi/plugin/visual/WizardExample/WizardExample.py @@ -260,6 +260,6 @@ def __init__(self, controlAPI,name,parent = None): self.setLayout(layout) def validatePage(self): - self.control_api.do_subscribe_uname('Plot1','Sin1','SinMit_f3',signal_index=[2]) + self.control_api.do_subscribe_uname('Plot1','Sin1','SinMit_f3', signals=['f3_1', 'f3_2']) self.control_api.do_delete_plugin_uname(self.uname) return True diff --git a/setup.py b/setup.py index 7b55a51f..178285f8 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ setup( name='PaPI', - version='0.01', + version='0.9', packages=['papi'], url='https://github.com/TUB-Control/PaPI', license='LGPL v3', From 4d1a870975685413ef98ed5befecdf6d24365ea9 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 30 Dec 2014 17:02:22 +0100 Subject: [PATCH 016/260] small typo in constant name, new version in constant.py --- papi/constants.py | 8 ++++---- papi/gui/gui_api.py | 4 ++-- papi/gui/plugin_api.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/papi/constants.py b/papi/constants.py index 1ff2f643..fddcba52 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -34,7 +34,7 @@ CORE_CONSOLE_LOG_LEVEL = 1 -CORE_PAPI_VERSION = 'v_0.8' # no spaces allowed +CORE_PAPI_VERSION = 'v_0.9' # no spaces allowed CORE_CORE_VERSION = 'v_0.9' # no spaces allowed CORE_PAPI_CONSOLE_START_MESSAGE = 'PaPI - Plugin based Process Interaction' + ' Version: ' + CORE_PAPI_VERSION CORE_CORE_CONSOLE_START_MESSAGE = 'PaPI Core Modul ' + CORE_CORE_VERSION + ' started' @@ -53,7 +53,7 @@ # GUI CONSTANTS GUI_PROCESS_CONSOLE_IDENTIFIER = 'Gui Process: ' GUI_PROCESS_CONSOLE_LOG_LEVEL = 1 -GUI_VERSION = 'v_0.8' +GUI_VERSION = 'v_0.9' GUI_START_CONSOLE_MESSAGE = 'PaPI GUI Modul ' + GUI_VERSION + ' started' GUI_PAPI_WINDOW_TITLE = 'PaPI - Plugin based Process Interaction' @@ -64,7 +64,7 @@ GUI_DEFAULT_HEIGHT = 800 # PLUGIN LOCATION CONSTANTS -PLUGIN_ROOT_FOLDER_LIST = ['plugin','papi/plugin', '../plugin'] +PLUGIN_ROOT_FOLDER_LIST = ['plugin', 'papi/plugin', '../plugin'] PLUGIN_IOP_FOLDER = '' PLUGIN_VIP_FOLDER = '' PLUGIN_DPP_FOLDER = '' @@ -96,4 +96,4 @@ CONFIG_DEFAULT_DIRECTORY = 'cfg_collection/' CONFIG_DEFAULT_FILE = 'cfg_collection/testcfg.xml' CONFIG_ROOT_ELEMENT_NAME = 'PaPiConfig' # for xml save -CONFIG_LOADER_SUBCRIBE_DELAY = 1000 # ms \ No newline at end of file +CONFIG_LOADER_SUBSCRIBE_DELAY = 1000 # ms \ No newline at end of file diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index b0d9dca3..5fd8baab 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -32,7 +32,7 @@ from papi.data.DOptionalData import DOptionalData from papi.ConsoleLog import ConsoleLog -from papi.constants import GUI_PROCESS_CONSOLE_IDENTIFIER, GUI_PROCESS_CONSOLE_LOG_LEVEL, CONFIG_LOADER_SUBCRIBE_DELAY, \ +from papi.constants import GUI_PROCESS_CONSOLE_IDENTIFIER, GUI_PROCESS_CONSOLE_LOG_LEVEL, CONFIG_LOADER_SUBSCRIBE_DELAY, \ CONFIG_ROOT_ELEMENT_NAME, CORE_PAPI_VERSION, PLUGIN_PCP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER from papi.pyqtgraph import QtCore @@ -502,7 +502,7 @@ def do_load_xml(self, path): # 0: ident, 1: uname, 2: config self.do_create_plugin(pl[0], pl[1], pl[2]) - QtCore.QTimer.singleShot(CONFIG_LOADER_SUBCRIBE_DELAY,\ + QtCore.QTimer.singleShot(CONFIG_LOADER_SUBSCRIBE_DELAY,\ lambda: self.config_loader_subs(plugins_to_start, subs_to_make, parameters_to_change) ) def change_uname_to_uniqe(self, uname): diff --git a/papi/gui/plugin_api.py b/papi/gui/plugin_api.py index f08ddd0f..45592b62 100644 --- a/papi/gui/plugin_api.py +++ b/papi/gui/plugin_api.py @@ -32,7 +32,7 @@ from papi.data.DOptionalData import DOptionalData from papi.ConsoleLog import ConsoleLog -from papi.constants import PLUGIN_API_CONSOLE_IDENTIFIER, PLUGIN_API_CONSOLE_LOG_LEVEL, CONFIG_LOADER_SUBCRIBE_DELAY, \ +from papi.constants import PLUGIN_API_CONSOLE_IDENTIFIER, PLUGIN_API_CONSOLE_LOG_LEVEL, CONFIG_LOADER_SUBSCRIBE_DELAY, \ CONFIG_ROOT_ELEMENT_NAME, CORE_PAPI_VERSION, PLUGIN_PCP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER from papi.pyqtgraph import QtCore From 6d8711da2ec65bd7048735cbca194ab6550c3463 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 30 Dec 2014 17:05:28 +0100 Subject: [PATCH 017/260] build dev verion of new conttext menu of plot plugin --- papi/last_active_papi.xml | 85 ++++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 2c43de57..9b972660 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,4 +1,4 @@ - + @@ -19,86 +19,87 @@ Label-X time, s - - ^(1|0)$ - bool - Grid-Y - 0 + + (\d+) + 1 + + + \w+,\s+\w+ + Label-Y + amplitude, V Used display name VisualPlugin + + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + (426,319) + 1 + + + ^\[(\s*\d\s*)+\] + Color + [0 1 2 3 4] + 1 + ^([1-9][0-9]{0,3}|10000)$ - 1 Buffersize 1000 + 1 ^\[(\s*\d\s*)+\] - 1 Style [0 0 0 0 0] - - - \(([0-9]+),([0-9]+)\) 1 - Determine position: (x,y) - (0,0) - - \w+,\s+\w+ - Label-Y - amplitude, V + + Plot - - (\d+) - 1 + + ^(1|0)$ + Grid-Y + 0 + bool ^(1|0)$ - bool Grid-X 1 + bool - + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + (0,0) 1 - Determine size: (height,width) - (300,300) - - - ^\[(\s*\d\s*)+\] - 1 - Color - [0 1 2 3 4] - - - Plot ^(1|0)$ - bool Rolling Plot 0 + bool - [0 0 0 0 0] - 0 - 1000 [0 1 2 3 4] - 0 - 1 + 1000 1 + 0 + 1 + [0 0 0 0 0] + 0 - Add - + FourierXRectXMOD + - Sum + rect11 + rect12 From 61bcc2f6fa9f534040fe67c603d1aa59e4d9ff56 Mon Sep 17 00:00:00 2001 From: SvKn Date: Mon, 5 Jan 2015 10:29:14 +0100 Subject: [PATCH 018/260] Update CHANGENLOG.md --- CHANGENLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index e7311dda..686d40bf 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -8,7 +8,7 @@ v.0.9 * Signals are now stored and managed as object * Support of configuration types [file, ] * Unsubscription of single signals became possible - * Use of pyqtgraph 0.9.10 + * Use of pyqtgraph 0.9.10 -> removed dependency for scipy * Sorting in the overview menu is enabled * First pictures for plugins * Error Dialogs pops up if a plugin error occurs -> Whole GUI wont die anymore From 0877ddc131834d985fea468aa1c6dae32e8a45e0 Mon Sep 17 00:00:00 2001 From: SvKn Date: Mon, 5 Jan 2015 10:29:43 +0100 Subject: [PATCH 019/260] Update CHANGENLOG.md --- CHANGENLOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 686d40bf..7d9d0388 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -3,8 +3,7 @@ Changelog v.0.9 --- - * New ContextMenu for Plot - * Plot Plugin: Speedup by use of downsampling, more useable parameter, more dynamic legend + * Plot Plugin: Speedup by use of downsampling, more useable parameter, more dynamic legend, new ContextMenu * Signals are now stored and managed as object * Support of configuration types [file, ] * Unsubscription of single signals became possible From 2111a8204cd2ff54b14d7a1b38310c5a66091fb3 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 5 Jan 2015 15:37:06 +0100 Subject: [PATCH 020/260] display name are now editable in the overviewmenu -> new papi event, new core processing function, changes in gui --- papi/core.py | 40 ++++- papi/data/DPlugin.py | 13 ++ papi/data/DSignal.py | 7 +- papi/event/data/EditDPlugin.py | 40 +++++ papi/event/data/__init__.py | 3 +- papi/gui/gui_api.py | 257 +++++++++++++++++++-------- papi/gui/qt_new/item.py | 85 ++++++++- papi/gui/qt_new/overview_menu.py | 31 +++- papi/last_active_papi.xml | 140 +++++++++------ papi/ui/gui/qt_dev/add_plugin.py | 2 +- papi/ui/gui/qt_dev/add_subscriber.py | 2 +- papi/ui/gui/qt_dev/main.py | 2 +- papi/ui/gui/qt_dev/manager.py | 2 +- papi/ui/gui/qt_new/create.py | 2 +- papi/ui/gui/qt_new/create_dialog.py | 2 +- papi/ui/gui/qt_new/main.py | 2 +- papi/ui/gui/qt_new/overview.py | 9 +- ui/gui/qt_new/overview.ui | 12 +- 18 files changed, 505 insertions(+), 146 deletions(-) create mode 100644 papi/event/data/EditDPlugin.py diff --git a/papi/core.py b/papi/core.py index 1217496e..a66f8f48 100644 --- a/papi/core.py +++ b/papi/core.py @@ -35,7 +35,8 @@ from papi.yapsy.PluginManager import PluginManager from papi.PapiEvent import PapiEvent from papi.data.DCore import DCore -from papi.data.DPlugin import DPlugin +from papi.data.DPlugin import DPlugin, DBlock +from papi.data.DSignal import DSignal from papi.ConsoleLog import ConsoleLog from papi.data.DOptionalData import DOptionalData @@ -81,7 +82,8 @@ def __init__(self, gui_start_function, use_gui = True): self.__process_data_event_l__ = { 'new_data': self.__process_new_data__, 'new_block': self.__process_new_block__, - 'new_parameter': self.__process_new_parameter__ + 'new_parameter': self.__process_new_parameter__, + 'edit_dplugin' : self.__process_edit_dplugin, } self.__process_instr_event_l__ = { 'create_plugin': self.__process_create_plugin__, @@ -593,6 +595,40 @@ def BACKUP__process_new_data__(self,event): self.log.printText(1,'new_data, Plugin with id '+str(oID)+' does not exist in DCore') return -1 + def __process_edit_dplugin(self, event): + """ + Process edit_dplugin event from gui. + + :param event: event to process + :type event: PapiEvent + :type tar_plug: DPlugin + """ + eObject = event.editedObject + cRequest = event.changeRequest + + dID = event.get_destinatioID() + + pl = self.core_data.get_dplugin_by_id(dID) + + # DBlock of DPlugin should be edited + if isinstance(eObject, DBlock): + + dblock = pl.get_dblock_by_name(eObject.name) + + if "edit" in cRequest: + cObject = cRequest["edit"] + + # Signal should be modified in DBlock + if isinstance(cObject, DSignal): + dsignal = dblock.get_signal_by_uname(cObject.uname) + + dsignal.dname = cObject.dname + + self.log.printText(1,'edit_dplugin, Edited Dblock '+dblock.name+' of DPlugin '+pl.uname+ " : DSignal " + dsignal.uname + " to dname -> " + dsignal.dname ) + + + self.update_meta_data_to_gui(pl.id) + # ------- Event processing second stage: instr events --------- def __process_create_plugin__(self,event): """ diff --git a/papi/data/DPlugin.py b/papi/data/DPlugin.py index 58d26b5e..e400089a 100644 --- a/papi/data/DPlugin.py +++ b/papi/data/DPlugin.py @@ -103,6 +103,19 @@ def get_subscribers(self): """ return copy.deepcopy(self.subscribers) + def get_signal_by_uname(self, uname): + """ + + :param uname: + :return DSignal: + """ + + for signal in self.signals: + if signal.uname == uname: + return signal + + return None + def get_signals(self): """ DEPRECATED diff --git a/papi/data/DSignal.py b/papi/data/DSignal.py index b8541bca..ba211bcd 100644 --- a/papi/data/DSignal.py +++ b/papi/data/DSignal.py @@ -33,7 +33,10 @@ class DSignal(DObject): - def __init__(self, uname): + def __init__(self, uname, dname = None): super(DObject, self).__init__() self.uname = uname - self.dname = uname + if dname is None: + self.dname = uname + else: + self.dname = dname \ No newline at end of file diff --git a/papi/event/data/EditDPlugin.py b/papi/event/data/EditDPlugin.py new file mode 100644 index 00000000..137d5dcd --- /dev/null +++ b/papi/event/data/EditDPlugin.py @@ -0,0 +1,40 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Sven Knuth +""" + +__author__ = 'Sven Knuth' + +from papi.event.data.DataBase import DataBase + + +class EditDPlugin(DataBase): + def __init__(self, oID, destID, eObject, changeRequest): + super().__init__(oID, destID, 'edit_dplugin', None) + #Object of DPlugin, which should be changed + self.editedObject = eObject + #Kind of action. e.g. {'edit': DSignal} + self.changeRequest = changeRequest diff --git a/papi/event/data/__init__.py b/papi/event/data/__init__.py index 0c1c917a..9deaa469 100644 --- a/papi/event/data/__init__.py +++ b/papi/event/data/__init__.py @@ -3,4 +3,5 @@ from .NewData import NewData from .NewBlock import NewBlock -from .NewParameter import NewParameter \ No newline at end of file +from .NewParameter import NewParameter +from .EditDPlugin import EditDPlugin \ No newline at end of file diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 5fd8baab..24b19160 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -27,7 +27,6 @@ """ __author__ = 'stefan' - import papi.event as Event from papi.data.DOptionalData import DOptionalData @@ -37,6 +36,8 @@ from papi.pyqtgraph import QtCore +from papi.data.DSignal import DSignal +from papi.data.DPlugin import DBlock import papi.error_codes as ERROR @@ -45,13 +46,13 @@ import xml.etree.cElementTree as ET -class Gui_api(QtCore.QObject): +class Gui_api(QtCore.QObject): resize_gui = QtCore.Signal(int, int) set_bg_gui = QtCore.Signal(str) - def __init__(self, gui_data, core_queue, gui_id, LOG_IDENT = GUI_PROCESS_CONSOLE_IDENTIFIER): + def __init__(self, gui_data, core_queue, gui_id, LOG_IDENT=GUI_PROCESS_CONSOLE_IDENTIFIER): super(Gui_api, self).__init__() self.gui_id = gui_id self.gui_data = gui_data @@ -62,8 +63,7 @@ def __init__(self, gui_data, core_queue, gui_id, LOG_IDENT = GUI_PROCESS_CONSOLE self.gui_bg_path = None - - def do_create_plugin(self, plugin_identifier, uname, config={}, autostart = True): + def do_create_plugin(self, plugin_identifier, uname, config={}, autostart=True): """ Something like a callback function for gui triggered events e.a. when a user wants to create a new plugin. @@ -98,7 +98,7 @@ def do_delete_plugin(self, id): :type id: int :return: """ - event = Event.instruction.StopPlugin(self.gui_id, id, None ) + event = Event.instruction.StopPlugin(self.gui_id, id, None) self.core_queue.put(event) @@ -117,6 +117,41 @@ def do_delete_plugin_uname(self, uname): else: self.log.printText(1, " Do delete plugin with uname " + uname + ' failed') + def do_edit_plugin(self, pl_id, eObject, changeRequest): + """ + Edit plugin with given plugin id. Specify attribute of plugin by eObject which should + be edited e.g. DBlock. + Specify action by changeRequest e.g. {'edit' : DSignal}. + Currently only possible to change a DSignal for a given dplugin and dblock. + + :param pl_id: Plugin id to delete + :type pl_id: int + :return: + """ + event = Event.data.EditDPlugin(self.gui_id, pl_id, eObject, changeRequest) + + self.core_queue.put(event) + + def do_edit_plugin_uname(self, uname, eObject, changeRequest): + """ + Edit plugin with given plugin uname. Specify attribute of plugin by eObject which should + be edited e.g. DBlock. + Specify action by changeRequest e.g. {'edit' : DSignal}. + Currently only possible to change a DSignal for a given dplugin and dblock. + + :param uname: + :param eObject: + :param changeRequest: + :return: + """ + + pl_id = self.do_get_plugin_id_from_uname(uname) + + if pl_id is not None: + self.do_edit_plugin(pl_id, eObject, changeRequest) + else: + self.log.printText(1, " Do edit plugin with uname " + uname + ' failed') + def do_stopReset_pluign(self, id): """ Stop and reset plugin with given id without deleting it. @@ -172,7 +207,7 @@ def do_start_plugin_uname(self, uname): self.log.printText(1, " Do start_plugin with uname " + uname + ' failed') return ERROR.NOT_EXISTING - def do_subscribe(self, subscriber_id, source_id, block_name, signals=None, sub_alias = None): + def do_subscribe(self, subscriber_id, source_id, block_name, signals=None, sub_alias=None): """ Something like a callback function for gui triggered events. In this case, user wants one plugin to subscribe another @@ -186,16 +221,16 @@ def do_subscribe(self, subscriber_id, source_id, block_name, signals=None, sub_a :return: """ # build optional data object and add id and block name to it - opt = DOptionalData() - opt.source_ID = source_id - opt.block_name = block_name - opt.signals = signals - opt.subscription_alias = sub_alias + opt = DOptionalData() + opt.source_ID = source_id + opt.block_name = block_name + opt.signals = signals + opt.subscription_alias = sub_alias # send event with subscriber id as the origin to CORE event = Event.instruction.Subscribe(subscriber_id, 0, opt) self.core_queue.put(event) - def do_subscribe_uname(self,subscriber_uname,source_uname,block_name, signals=None, sub_alias = None): + def do_subscribe_uname(self, subscriber_uname, source_uname, block_name, signals=None, sub_alias=None): """ Something like a callback function for gui triggered events. In this case, user wants one plugin to subscribe another @@ -237,10 +272,10 @@ def do_unsubscribe(self, subscriber_id, source_id, block_name, signal_index=None :return: """ # create optional data with source id and block_name - opt = DOptionalData() - opt.source_ID = source_id - opt.block_name = block_name - opt.signals = signal_index + opt = DOptionalData() + opt.source_ID = source_id + opt.block_name = block_name + opt.signals = signal_index # sent event to Core with origin subscriber_id event = Event.instruction.Unsubscribe(subscriber_id, 0, opt) self.core_queue.put(event) @@ -355,7 +390,7 @@ def do_pause_plugin_by_uname(self, plugin_uname): # # get plugin from DGui with given uname # # purpose: get its id # dplug = self.gui_data.get_dplugin_by_uname(plugin_uname) - # # check for existance + # # check for existance # if dplug is not None: # # it does exist, so get its id # self.do_pause_plugin_by_id(dplug.id) @@ -399,7 +434,7 @@ def do_resume_plugin_by_uname(self, plugin_uname): # # get plugin from DGui with given uname # # purpose: get its id # dplug = self.gui_data.get_dplugin_by_uname(plugin_uname) - # # check for existance + # # check for existance # if dplug is not None: # # it does exist, so get its id # self.do_resume_plugin_by_id(dplug.id) @@ -425,7 +460,7 @@ def do_get_plugin_id_from_uname(self, uname): :return: None: plugin with uname does not exist, id: id of plugin """ dplugin = self.gui_data.get_dplugin_by_uname(uname) - # check for existance + # check for existance if dplugin is not None: # it does exist, so get its id return dplugin.id @@ -443,21 +478,20 @@ def do_load_xml(self, path): root = tree.getroot() - - plugins_to_start = [] subs_to_make = [] parameters_to_change = [] + signals_to_change = [] for plugin_xml in root: - if plugin_xml.tag == 'Size': + if plugin_xml.tag == 'Size': w = int(plugin_xml.attrib['w']) h = int(plugin_xml.attrib['h']) - self.resize_gui.emit(w,h) - else: + self.resize_gui.emit(w, h) + else: if plugin_xml.tag == 'Background': path = str(plugin_xml.attrib['image']) - if path != '' and path is not None and path !='default': + if path != '' and path is not None and path != 'default': self.set_bg_gui.emit(path) else: pl_uname = plugin_xml.attrib['uname'] @@ -469,12 +503,16 @@ def do_load_xml(self, path): config_hash[para_name] = {} for detail_xml in parameter_xml: detail_name = detail_xml.tag - config_hash[para_name][detail_name]= detail_xml.text + config_hash[para_name][detail_name] = detail_xml.text pl_uname_new = self.change_uname_to_uniqe(pl_uname) plugins_to_start.append([identifier, pl_uname_new, config_hash]) + # -------------------------------- + # Load Subscriptions + # -------------------------------- + subs_xml = plugin_xml.find('Subscriptions') for sub_xml in subs_xml.findall('Subscription'): data_source = sub_xml.find('data_source').text @@ -487,8 +525,11 @@ def do_load_xml(self, path): alias = alias_xml.text pl_uname_new = self.change_uname_to_uniqe(pl_uname) data_source_new = self.change_uname_to_uniqe(data_source) - subs_to_make.append([pl_uname_new,data_source_new,block_name,signals, alias]) + subs_to_make.append([pl_uname_new, data_source_new, block_name, signals, alias]) + # -------------------------------- + # Load PreviousParameters + # -------------------------------- prev_parameters_xml = plugin_xml.find('PreviousParameters') for prev_parameter_xml in prev_parameters_xml.findall('Parameter'): @@ -498,12 +539,28 @@ def do_load_xml(self, path): # TODO validate NO FLOAT in parameter parameters_to_change.append([pl_uname_new, para_name, para_value]) + # -------------------------------- + # Load DBlocks due to signals name + # -------------------------------- + + dblocks_xml = plugin_xml.find('DBlocks') + for dblock_xml in dblocks_xml: + dblock_name = dblock_xml.attrib['Name'] + print('DBlock ' + dblock_name) + dsignals_xml = dblock_xml.findall('DSignal') + for dsignal_xml in dsignals_xml: + dsignal_uname = dsignal_xml.attrib['uname'] + dsignal_dname = dsignal_xml.find('dname').text + print('Signal' + dsignal_uname + ' with ' + dsignal_dname) + signals_to_change.append([pl_uname, dblock_name, dsignal_uname, dsignal_dname]) + for pl in plugins_to_start: # 0: ident, 1: uname, 2: config self.do_create_plugin(pl[0], pl[1], pl[2]) - QtCore.QTimer.singleShot(CONFIG_LOADER_SUBSCRIBE_DELAY,\ - lambda: self.config_loader_subs(plugins_to_start, subs_to_make, parameters_to_change) ) + QtCore.QTimer.singleShot(CONFIG_LOADER_SUBSCRIBE_DELAY, \ + lambda: self.config_loader_subs(plugins_to_start, subs_to_make, \ + parameters_to_change, signals_to_change)) def change_uname_to_uniqe(self, uname): """ @@ -513,33 +570,42 @@ def change_uname_to_uniqe(self, uname): :type uname: basestring :return: uname """ - i=1 + i = 1 while self.gui_data.get_dplugin_by_uname(uname) is not None: - i = i+1 - if i == 2: - uname = uname + 'X' +str(i) - else: - uname = uname[:-1] + str(i) + i = i + 1 + if i == 2: + uname = uname + 'X' + str(i) + else: + uname = uname[:-1] + str(i) return uname - def config_loader_subs(self, pl_to_start, subs_to_make, parameters_to_change ): + def config_loader_subs(self, pl_to_start, subs_to_make, parameters_to_change, signals_to_change): for sub in subs_to_make: self.do_subscribe_uname(sub[0], sub[1], sub[2], sub[3], sub[4]) for para in parameters_to_change: self.do_set_parameter(para[0], para[1], para[2]) + for sig in signals_to_change: + plugin_uname = sig[0] + dblock_name = sig[1] + dsignal_uname = sig[2] + dsignal_dname = sig[3] + + self.do_edit_plugin_uname(plugin_uname, DBlock(dblock_name), + {'edit': DSignal(dsignal_uname, dsignal_dname)}) + def do_save_xml_config(self, path): root = ET.Element(CONFIG_ROOT_ELEMENT_NAME) root.set('Date', datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')) - root.set('PaPI_version',CORE_PAPI_VERSION) + root.set('PaPI_version', CORE_PAPI_VERSION) size_xml = ET.SubElement(root, 'Size') - size_xml.set('w',str(self.gui_size_width)) - size_xml.set('h',str(self.gui_size_height)) + size_xml.set('w', str(self.gui_size_width)) + size_xml.set('h', str(self.gui_size_height)) bg_xml = ET.SubElement(root, 'Background') - bg_xml.set('image',str(self.gui_bg_path)) + bg_xml.set('image', str(self.gui_bg_path)) # get plugins # plugins = self.gui_data.get_all_plugins() @@ -549,28 +615,65 @@ def do_save_xml_config(self, path): if dplugin.type == PLUGIN_PCP_IDENTIFIER or dplugin.type == PLUGIN_VIP_IDENTIFIER: dplugin.startup_config = dplugin.plugin.get_current_config() - pl_xml = ET.SubElement(root,'Plugin') - pl_xml.set('uname',dplugin.uname) + pl_xml = ET.SubElement(root, 'Plugin') + pl_xml.set('uname', dplugin.uname) - identifier_xml =ET.SubElement(pl_xml,'Identifier') + identifier_xml = ET.SubElement(pl_xml, 'Identifier') identifier_xml.text = dplugin.plugin_identifier - cfg_xml = ET.SubElement(pl_xml,'StartConfig') + # --------------------------------------- + # Save all current config as startup config + # for the next start + # --------------------------------------- + + cfg_xml = ET.SubElement(pl_xml, 'StartConfig') for parameter in dplugin.startup_config: para_xml = ET.SubElement(cfg_xml, 'Parameter') - para_xml.set('Name',parameter) + para_xml.set('Name', parameter) for detail in dplugin.startup_config[parameter]: detail_xml = ET.SubElement(para_xml, detail) detail_xml.text = dplugin.startup_config[parameter][detail] + # --------------------------------------- + # Save all current values for all + # parameter + # --------------------------------------- + last_paras_xml = ET.SubElement(pl_xml, 'PreviousParameters') allparas = dplugin.get_parameters() for para_key in allparas: para = allparas[para_key] - last_para_xml = ET.SubElement(last_paras_xml,'Parameter') - last_para_xml.set('Name',para_key) + last_para_xml = ET.SubElement(last_paras_xml, 'Parameter') + last_para_xml.set('Name', para_key) last_para_xml.text = str(para.value) + # --------------------------------------- + # Save all current values for all + # signals of all dblocks + # --------------------------------------- + + dblocks_xml = ET.SubElement(pl_xml, 'DBlocks') + + alldblock_names = dplugin.get_dblocks() + + for dblock_name in alldblock_names: + dblock = alldblock_names[dblock_name] + dblock_xml = ET.SubElement(dblocks_xml, 'DBlock') + dblock_xml.set('Name', dblock.name) + + alldsignals = dblock.get_signals() + + for dsignal in alldsignals: + dsignal_xml = ET.SubElement(dblock_xml, 'DSignal') + dsignal_xml.set('uname', dsignal.uname) + + dname_xml = ET.SubElement(dsignal_xml, 'dname') + dname_xml.text = dsignal.dname + + # --------------------------------------- + # Save all subscriptions for this plugin + # --------------------------------------- + subs_xml = ET.SubElement(pl_xml, 'Subscriptions') subs = dplugin.get_subscribtions() for sub in subs: @@ -579,34 +682,36 @@ def do_save_xml_config(self, path): source_xml.text = self.gui_data.get_dplugin_by_id(sub).uname for block in subs[sub]: block_xml = ET.SubElement(sub_xml, 'block') - block_xml.set('Name',block) + block_xml.set('Name', block) - alias_xml = ET.SubElement(block_xml,'alias') - alias_xml.text = subs[sub][block].alias + dsubscription = subs[sub][block] - for s in subs[sub][block].get_signals(): - signal_xml = ET.SubElement(block_xml,'Signal') + alias_xml = ET.SubElement(block_xml, 'alias') + alias_xml.text = dsubscription.alias + + for s in dsubscription.get_signals(): + signal_xml = ET.SubElement(block_xml, 'Signal') signal_xml.text = str(s) self.indent(root) tree = ET.ElementTree(root) tree.write(path) - def indent(self,elem, level=0): - # copied from http://effbot.org/zone/element-lib.htm#prettyprint 06.10.2014 15:53 - i = "\n" + level*" " - if len(elem): - if not elem.text or not elem.text.strip(): - elem.text = i + " " - if not elem.tail or not elem.tail.strip(): - elem.tail = i - for elem in elem: - self.indent(elem, level+1) - if not elem.tail or not elem.tail.strip(): - elem.tail = i - else: - if level and (not elem.tail or not elem.tail.strip()): - elem.tail = i + def indent(self, elem, level=0): + # copied from http://effbot.org/zone/element-lib.htm#prettyprint 06.10.2014 15:53 + i = "\n" + level * " " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + if not elem.tail or not elem.tail.strip(): + elem.tail = i + for elem in elem: + self.indent(elem, level + 1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i def do_reset_papi(self): """ @@ -632,12 +737,12 @@ def do_test_name_to_be_unique(self, name): :type name: basestring :return: True or False """ - reg =QtCore.QRegExp('\S[^_][^\W_]+') + reg = QtCore.QRegExp('\S[^_][^\W_]+') if reg.exactMatch(name): - if self.gui_data.get_dplugin_by_uname(name) is None: - return True - else: - return False + if self.gui_data.get_dplugin_by_uname(name) is None: + return True + else: + return False else: return False @@ -652,9 +757,9 @@ def do_change_string_to_be_uname(self, name): """ uname = name - #TODO: get more inteligence here! + # TODO: get more inteligence here! - forbidden = ['_',',','.','`',' '] + forbidden = ['_', ',', '.', '`', ' '] for c in forbidden: - uname = uname.replace(c,'X') + uname = uname.replace(c, 'X') return uname \ No newline at end of file diff --git a/papi/gui/qt_new/item.py b/papi/gui/qt_new/item.py index d8a596da..644098aa 100644 --- a/papi/gui/qt_new/item.py +++ b/papi/gui/qt_new/item.py @@ -195,20 +195,49 @@ def __init__(self, dblock: DBlock): self.dblock = dblock self.setSelectable(False) self.setEditable(False) + self.tool_tip = "DBlock: " + dblock.name def get_decoration(self): return None + class DSignalTreeItem(PaPITreeItem): - def __init__(self, dsignal: DSignal): + def __init__(self, dsignal: DSignal, check_box): super(DSignalTreeItem, self).__init__(dsignal, dsignal.dname) self.dsignal = dsignal self.setSelectable(False) self.setEditable(False) + self.tool_tip = "InternalName: " + dsignal.uname + self.check_box = check_box def get_decoration(self): return None + def data(self, role): + """ + For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum' + :param role: + :return: + """ + + if role == Qt.ToolTipRole: + return self.tool_tip + + if role == Qt.DisplayRole: + if self.check_box.isChecked(): + return self.dsignal.uname + else: + return self.dsignal.dname + + if role == Qt.DecorationRole: + return self.get_decoration() + + if role == Qt.UserRole: + return self.object + + + return None + # ------------------------------------ # Model Custom # ------------------------------------ @@ -333,6 +362,58 @@ def setData(self, index, value, role): class DBlockTreeModel(PaPITreeModel): - def __init__(self, parent=None): + def __init__(self, check_box, parent=None): super(DBlockTreeModel, self).__init__(parent) + self.check_box = check_box + + def flags(self, index): + """ + This function returns the flags for a specific index. + + For Qt.ItemFlags see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemFlag-enum' + :param index: + :return: + """ + row = index.row() + col = index.column() + + parent = index.parent() + + if not parent.isValid(): + return ~Qt.ItemIsSelectable & ~Qt.ItemIsEditable + + if parent.isValid(): + + if self.check_box.isChecked(): + return ~Qt.ItemIsEditable + else: + return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable + + + def setData(self, index, value, role): + """ + This function is called when a content in a row is edited by the user. + + :param index: Current selected index. + :param value: New value from user + :param role: + :return: + """ + + if not index.isValid(): + return None + + row = index.row() + col = index.column() + + if role == Qt.EditRole: + + dsignal = super(DBlockTreeModel, self).data(index, Qt.UserRole) + if value != dsignal.dname: + dsignal.dname = value + self.dataChanged.emit(index, None) + + return True + + return False diff --git a/papi/gui/qt_new/overview_menu.py b/papi/gui/qt_new/overview_menu.py index e4b20cee..06a64461 100644 --- a/papi/gui/qt_new/overview_menu.py +++ b/papi/gui/qt_new/overview_menu.py @@ -94,11 +94,14 @@ def __init__(self, gui_api, parent=None): # Build structure of block tree # ----------------------------------- - self.bModel = DBlockTreeModel() + self.bModel = DBlockTreeModel(self.showInternalNameCheckBox) self.bModel.setHorizontalHeaderLabels(['Name']) + self.bModel.setColumnCount(2) self.blockTree.setModel(self.bModel) self.blockTree.setUniformRowHeights(True) + self.bModel.dataChanged.connect(self.data_changed_block_model) + self.showInternalNameCheckBox.clicked.connect(self.show_internal_name_callback) # ----------------------------------- # Build structure of subscriber tree @@ -236,7 +239,8 @@ def plugin_item_changed(self, index): # ------------------------- for signal in dblock.get_signals(): - signal_item = DSignalTreeItem(signal) + signal_item = DSignalTreeItem(signal, self.showInternalNameCheckBox) + block_item.appendRow(signal_item) block_item.sortChildren(0) @@ -711,6 +715,13 @@ def stop_start_button_callback(self): self.playButton.setDisabled(False) self.stopButton.setDisabled(False) + def show_internal_name_callback(self): + """ + Callback function for 'showInternalNameCheckBox' + :return: + """ + self.plugin_item_changed(self.pluginTree.currentIndex()) + def data_changed_parameter_model(self, index, n): """ This function is called when a dparameter value is changed by editing the 'value'-column. @@ -726,6 +737,22 @@ def data_changed_parameter_model(self, index, n): self.gui_api.do_set_parameter(dplugin.id, dparameter.name, dparameter.value) + def data_changed_block_model(self, index, n): + """ + This function is called when a dblock child, a disgnal, is changed. + :param index: Index of current changed dsignal object + :param n: None + :return: + """ + + dsignal = self.blockTree.model().data(index, Qt.UserRole) + dblock = self.blockTree.model().data(index.parent(), Qt.UserRole) + + dplugin = self.pluginTree.model().data(self.pluginTree.currentIndex(), Qt.UserRole) + + self.gui_api.do_edit_plugin_uname(dplugin.uname, dblock, {"edit" : dsignal}) + + def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: self.close() diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index e23f03cb..c72bd290 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,103 +1,141 @@ - + - - Sinus - - - 0.6 - - - - + Plot - - 1 - Style - ^\[(\s*\d\s*)+\] - [0 0 0 0 0] + + \w+,\s*\w+ + Label-X + time, s - - Used display name - VisualPlugin + + Plot (\d+) 1 - - bool - Grid-Y - ^(1|0)$ - 0 + + \w+,\s+\w+ + Label-Y + amplitude, V - + 1 - Buffersize - ^([1-9][0-9]{0,3}|10000)$ - 1000 + ^\[(\s*\d\s*)+\] + Style + [0 0 0 0 0] bool - Grid-X ^(1|0)$ + Grid-X 0 1 - Color ^\[(\s*\d\s*)+\] + Color [0 1 2 3 4] + + 1 + ^([1-9][0-9]{0,3}|10000)$ + Buffersize + 1000 + bool - Rolling Plot ^(1|0)$ + Rolling Plot 0 - - Label-Y - \w+,\s+\w+ - amplitude, V + + Used display name + VisualPlugin - - Determine size: (height,width) - 1 - \(([0-9]+),([0-9]+)\) - (300,300) + + bool + ^(1|0)$ + Grid-Y + 0 - Determine position: (x,y) 1 + Determine position: (x,y) \(([0-9]+),([0-9]+)\) - (400,100) + (0,0) - - Label-X - \w+,\s*\w+ - time, s + + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + (300,300) - 0 - [0 0 0 0 0] [0 1 2 3 4] - 0 - 0 1000 1 + 0 + 0 + [0 0 0 0 0] + 0 + - Sin1 + Sinus f3_1 f3_2 - f3_scalar + + Sinus + + + [0-9]+ + 3 + + + Sinus + + + \d+.{0,1}\d* + 1 + + + + 0.6 + + + + + 3 + + + 4 + + + 5 + + + + + 2 + + + + + 123 + + + + + diff --git a/papi/ui/gui/qt_dev/add_plugin.py b/papi/ui/gui/qt_dev/add_plugin.py index fade27e9..7490d97f 100644 --- a/papi/ui/gui/qt_dev/add_plugin.py +++ b/papi/ui/gui/qt_dev/add_plugin.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_dev/add_plugin.ui' # -# Created: Mon Dec 15 17:36:09 2014 +# Created: Mon Jan 5 14:03:51 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_dev/add_subscriber.py b/papi/ui/gui/qt_dev/add_subscriber.py index 2d5c9597..da29c209 100644 --- a/papi/ui/gui/qt_dev/add_subscriber.py +++ b/papi/ui/gui/qt_dev/add_subscriber.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_dev/add_subscriber.ui' # -# Created: Mon Dec 15 17:36:09 2014 +# Created: Mon Jan 5 14:03:51 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_dev/main.py b/papi/ui/gui/qt_dev/main.py index 67aed5d5..0ec63f10 100644 --- a/papi/ui/gui/qt_dev/main.py +++ b/papi/ui/gui/qt_dev/main.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_dev/main.ui' # -# Created: Mon Dec 15 17:36:09 2014 +# Created: Mon Jan 5 14:03:51 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_dev/manager.py b/papi/ui/gui/qt_dev/manager.py index ae879bbf..82b9114a 100644 --- a/papi/ui/gui/qt_dev/manager.py +++ b/papi/ui/gui/qt_dev/manager.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_dev/manager.ui' # -# Created: Mon Dec 15 17:36:09 2014 +# Created: Mon Jan 5 14:03:51 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/create.py b/papi/ui/gui/qt_new/create.py index e5469d62..5493c017 100644 --- a/papi/ui/gui/qt_new/create.py +++ b/papi/ui/gui/qt_new/create.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/create.ui' # -# Created: Mon Dec 15 17:36:08 2014 +# Created: Mon Jan 5 14:03:50 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/create_dialog.py b/papi/ui/gui/qt_new/create_dialog.py index 6ad939e0..144d0931 100644 --- a/papi/ui/gui/qt_new/create_dialog.py +++ b/papi/ui/gui/qt_new/create_dialog.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/create_dialog.ui' # -# Created: Mon Dec 15 17:36:09 2014 +# Created: Mon Jan 5 14:03:51 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/main.py b/papi/ui/gui/qt_new/main.py index c34f0f54..59854c51 100644 --- a/papi/ui/gui/qt_new/main.py +++ b/papi/ui/gui/qt_new/main.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/main.ui' # -# Created: Mon Dec 15 17:36:08 2014 +# Created: Mon Jan 5 14:03:51 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/overview.py b/papi/ui/gui/qt_new/overview.py index 16045a48..435796ff 100644 --- a/papi/ui/gui/qt_new/overview.py +++ b/papi/ui/gui/qt_new/overview.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/overview.ui' # -# Created: Mon Dec 15 17:36:08 2014 +# Created: Mon Jan 5 14:03:51 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! @@ -31,6 +31,7 @@ def setupUi(self, Overview): self.verticalLayout_2.setObjectName("verticalLayout_2") self.formLayout = QtGui.QFormLayout() self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) + self.formLayout.setHorizontalSpacing(6) self.formLayout.setObjectName("formLayout") self.unameLabel = QtGui.QLabel(self.pluginTab) self.unameLabel.setObjectName("unameLabel") @@ -77,6 +78,9 @@ def setupUi(self, Overview): self.blockTab.setObjectName("blockTab") self.verticalLayout_5 = QtGui.QVBoxLayout(self.blockTab) self.verticalLayout_5.setObjectName("verticalLayout_5") + self.showInternalNameCheckBox = QtGui.QCheckBox(self.blockTab) + self.showInternalNameCheckBox.setObjectName("showInternalNameCheckBox") + self.verticalLayout_5.addWidget(self.showInternalNameCheckBox) self.blockTree = QtGui.QTreeView(self.blockTab) self.blockTree.setObjectName("blockTree") self.verticalLayout_5.addWidget(self.blockTree) @@ -132,7 +136,7 @@ def setupUi(self, Overview): self.retranslateUi(Overview) self.tabWidget.setCurrentIndex(0) - self.tabWidget_2.setCurrentIndex(0) + self.tabWidget_2.setCurrentIndex(1) QtCore.QMetaObject.connectSlotsByName(Overview) def retranslateUi(self, Overview): @@ -143,6 +147,7 @@ def retranslateUi(self, Overview): self.typeLabel.setText(QtGui.QApplication.translate("Overview", "Type", None, QtGui.QApplication.UnicodeUTF8)) self.alivestateLabel.setText(QtGui.QApplication.translate("Overview", "Alive state", None, QtGui.QApplication.UnicodeUTF8)) self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.parameterTab), QtGui.QApplication.translate("Overview", "Parameters", None, QtGui.QApplication.UnicodeUTF8)) + self.showInternalNameCheckBox.setText(QtGui.QApplication.translate("Overview", "Show internal signal names", None, QtGui.QApplication.UnicodeUTF8)) self.tabWidget_2.setTabText(self.tabWidget_2.indexOf(self.blockTab), QtGui.QApplication.translate("Overview", "Blocks", None, QtGui.QApplication.UnicodeUTF8)) self.pauseButton.setText(QtGui.QApplication.translate("Overview", "PAUSE", None, QtGui.QApplication.UnicodeUTF8)) self.stopButton.setText(QtGui.QApplication.translate("Overview", "STOP", None, QtGui.QApplication.UnicodeUTF8)) diff --git a/ui/gui/qt_new/overview.ui b/ui/gui/qt_new/overview.ui index e34f8a42..7bed8b4a 100644 --- a/ui/gui/qt_new/overview.ui +++ b/ui/gui/qt_new/overview.ui @@ -38,6 +38,9 @@ QFormLayout::AllNonFixedFieldsGrow + + 6 + @@ -93,7 +96,7 @@ - 0 + 1 @@ -110,6 +113,13 @@ Blocks + + + + Show internal signal names + + + From 95a3796212b2909534fe1d23b8a1d407ff89eceb Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 5 Jan 2015 15:38:27 +0100 Subject: [PATCH 021/260] release of custom context menu for Plot plugin and control menu for all vips --- README.md | 2 +- cfg_collection/ExampleFourier.xml | 256 +++++++-------------- papi/gui/plugin_api.py | 22 ++ papi/last_active_papi.xml | 151 +++++++------ papi/plugin/base_classes/vip_base.py | 34 ++- papi/plugin/visual/Plot/Plot.py | 326 +++++++++++++++++++++------ 6 files changed, 472 insertions(+), 319 deletions(-) diff --git a/README.md b/README.md index cb00934b..578a383f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Installation ------ Basic installation on Ubuntu 14.04 64Bit, using python 3.4 -`sudo apt-get install python3.4 git python3-pyside python3-numpy python3-scipy` +`sudo apt-get install python3.4 git python3-pyside python3-numpy` `git clone https://github.com/TUB-Control/PaPI.git PaPI` diff --git a/cfg_collection/ExampleFourier.xml b/cfg_collection/ExampleFourier.xml index 40b3aed7..cf850ab3 100644 --- a/cfg_collection/ExampleFourier.xml +++ b/cfg_collection/ExampleFourier.xml @@ -1,213 +1,110 @@ - + - - Add + + Fourier_Rect + + Fourier + - Add + FourierXRect + + + 130.149.155.73 + 1 + \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} + + + 9999 + 1 + \d{1,5} - - - Fourier - - - rect1 - rect2 - rect3 - rect4 - rect5 - rect6 - rect7 - rect8 - rect9 - - - + - + Plot - Used display name VisualPlugin - - - 1 - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - (438,411) + Used display name + 1 ^(1|0)$ - Grid-Y bool - 0 + Grid-Y - - 1 - ^([1-9][0-9]{0,3}|10000)$ - Buffersize - 1000 + + amplitude, V + \w+,\s+\w+ + Label-Y + 0 ^(1|0)$ - Rolling Plot bool + Rolling Plot + + 1 + (\d+) + [0 0 0 0 0] 1 ^\[(\s*\d\s*)+\] Style - [0 0 0 0 0] - - - \w+,\s+\w+ - Label-Y - amplitude, V - - - (\d+) - 10 - ^(1|0)$ - Grid-X - bool 0 - - - PlotFourier - - - 1 - ^\[(\s*\d\s*)+\] - Color - [0 1 2 3 4] - - - \w+,\s*\w+ - Label-X - time, s - - - 1 - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - (320,2) - - - - 0 - [0 1 2 3 4] - 0 - 10 - 1000 - [0 0 0 0 0] - 0 - - - - Fourier - - - rect1 - rect2 - rect3 - rect4 - rect5 - rect6 - rect7 - rect8 - rect9 - - - - - - Plot - - - Used display name - VisualPlugin - - - 1 - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - (300,300) - - ^(1|0)$ - Grid-Y bool - 1 + Grid-X + 1000 1 ^([1-9][0-9]{0,3}|10000)$ Buffersize - 1000 - - - ^(1|0)$ - Rolling Plot - bool - 0 - - - 1 - ^\[(\s*\d\s*)+\] - Style - [0 0 0 0 0] - - - \w+,\s+\w+ - Label-Y - amplitude, V - - - (\d+) - 10 - - - ^(1|0)$ - Grid-X - bool - 1 Plot - + + (300,300) 1 - ^\[(\s*\d\s*)+\] - Color - [0 1 2 3 4] + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + (0,0) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + time, s \w+,\s*\w+ Label-X - time, s - + + [0 1 2 3 4] 1 - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - (0,0) + ^\[(\s*\d\s*)+\] + Color - 0 - [0 1 2 3 4] 0 - 10 + 1 + 0 + [0 1 2 3 4] 1000 [0 0 0 0 0] - 0 + 0 @@ -219,27 +116,40 @@ - - Fourier_Rect + + Add - - 1 - \d{1,5} - 9999 - - - 1 - \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} - 127.0.0.1 - - Fourier - - - Fourier + Add - + + + FourierXRect + + + rect1 + rect10 + rect11 + rect12 + rect13 + rect14 + rect15 + rect16 + rect17 + rect18 + rect19 + rect2 + rect3 + rect4 + rect5 + rect6 + rect7 + rect8 + rect9 + + + diff --git a/papi/gui/plugin_api.py b/papi/gui/plugin_api.py index 7392adcf..4ee5cd15 100644 --- a/papi/gui/plugin_api.py +++ b/papi/gui/plugin_api.py @@ -124,3 +124,25 @@ def do_set_parameter(self, plugin_id, parameter_name, value): :type value: """ self.__default_api.do_set_parameter(plugin_id,parameter_name,value) + + + def do_resume_plugin_by_uname(self, plugin_uname): + """ + Something like a callback function for gui triggered events. + User wants to resume a plugin, so this method will send an event to core. + + :param plugin_uname: uname of plugin to resume + :type plugin_uname: basestring + """ + self.__default_api.do_resume_plugin_by_uname(plugin_uname) + + + def do_pause_plugin_by_uname(self, plugin_uname): + """ + Something like a callback function for gui triggered events. + User wants to pause a plugin, so this method will send an event to core. + + :param plugin_uname: uname of plugin to pause + :type plugin_uname: basestring + """ + self.__default_api.do_pause_plugin_by_uname(plugin_uname) \ No newline at end of file diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 9b972660..4eaf6e1e 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,131 +1,134 @@ - + - - Fourier_Rect_MOD - - - FourierXRectXMOD - - - - - Plot + + \w+,\s+\w+ + amplitude, V + Label-Y + \w+,\s*\w+ - Label-X time, s + Label-X - - (\d+) + + ^(1|0)$ 1 + bool + xRange-auto - - \w+,\s+\w+ - Label-Y - amplitude, V + + ^(1|0)$ + 1 + bool + yRange-auto + + + ^(1|0)$ + 0 + bool + Grid-X + + + Plot - Used display name VisualPlugin - - - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - (426,319) - 1 + Used display name ^\[(\s*\d\s*)+\] - Color - [0 1 2 3 4] 1 + [0 1 2 3 4] + Color - - ^([1-9][0-9]{0,3}|10000)$ - Buffersize - 1000 - 1 + + (\d+\.\d+) + [0.0 1.5] + xRange-auto ^\[(\s*\d\s*)+\] - Style - [0 0 0 0 0] 1 - - - Plot + [0 0 0 0 0] + Style ^(1|0)$ - Grid-Y 0 bool + Grid-Y - - ^(1|0)$ - Grid-X - 1 - bool + + ^([1-9][0-9]{0,3}|10000)$ + 1 + 300 + Buffersize + + + (\d+\.\d+) + [0.0 15.0] + yRange-auto \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - (0,0) 1 + (0,0) + Determine position: (x,y) ^(1|0)$ - Rolling Plot 0 bool + Rolling Plot + + + \(([0-9]+),([0-9]+)\) + 1 + (300,300) + Determine size: (height,width) + + + (\d+) + 1 - [0 1 2 3 4] - 1000 - 1 + 1 + 0 0 - 1 + 300 + [0,1] + [0,1] + 1 + [0 1 2 3 4] + 0 [0 0 0 0 0] - 0 + 1 - FourierXRectXMOD - + CPUXLoad + - rect11 - rect12 + load_in_percent - - Add + + CPU_Load - Add + CPUXLoad - - - - FourierXRectXMOD - - - rect3 - rect4 - rect5 - rect6 - rect7 - rect8 - rect9 - - - + + 0.01 + + diff --git a/papi/plugin/base_classes/vip_base.py b/papi/plugin/base_classes/vip_base.py index ec143268..f6a57468 100644 --- a/papi/plugin/base_classes/vip_base.py +++ b/papi/plugin/base_classes/vip_base.py @@ -30,6 +30,8 @@ from papi.constants import PLUGIN_VIP_IDENTIFIER +from papi.pyqtgraph.Qt import QtGui + class vip_base(base_visual): def initiate_layer_1(self, config): @@ -39,4 +41,34 @@ def initiate_layer_0(self, config): raise NotImplementedError("Please Implement this method") def get_type(self): - return PLUGIN_VIP_IDENTIFIER \ No newline at end of file + return PLUGIN_VIP_IDENTIFIER + + def create_control_context_menu(self): + ctrlMenu = QtGui.QMenu("Control") + + del_action = QtGui.QAction('Exit plugin',self.widget) + del_action.triggered.connect(self.ctlrMenu_exit) + + pause_action = QtGui.QAction('Pause plugin',self.widget) + pause_action.triggered.connect(self.ctlrMenu_pause) + + resume_action = QtGui.QAction('Resume plugin',self.widget) + resume_action.triggered.connect(self.ctlrMenu_resume) + + subMenu_action = QtGui.QAction('Open Signal Manager',self.widget) + #subMenu_action.triggered.connect(self.ctlrMenu_resume) + + ctrlMenu.addAction(subMenu_action) + ctrlMenu.addAction(resume_action) + ctrlMenu.addAction(pause_action) + ctrlMenu.addAction(del_action) + return ctrlMenu + + def ctlrMenu_exit(self): + self.control_api.do_delete_plugin_uname(self.dplugin_info.uname) + + def ctlrMenu_pause(self): + self.control_api.do_pause_plugin_by_uname(self.dplugin_info.uname) + + def ctlrMenu_resume(self): + self.control_api.do_resume_plugin_by_uname(self.dplugin_info.uname) \ No newline at end of file diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index b5f39e79..aa79c3b7 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -133,6 +133,7 @@ def initiate_layer_0(self, config=None): self.__downsampling_rate__ = int(int_re.findall(self.config['downsampling_rate']['value'])[0]) + # ---------------------------- # Create internal variables # ---------------------------- @@ -159,6 +160,17 @@ def initiate_layer_0(self, config=None): self.set_widget_for_internal_usage(self.__plotWidget__) + # + if self.config['xRange-auto']['value']=='1': + pass + else: + self.use_range_for_x(self.config['xRange']['value']) + + if self.config['yRange-auto']['value']== '1': + pass + else: + self.use_range_for_y(self.config['yRange']['value']) + # --------------------------- # Create Parameters # --------------------------- @@ -175,6 +187,12 @@ def initiate_layer_0(self, config=None): 1, Regex='^([1-9][0-9]?|100)$') self.__parameters__['buffersize'] = DParameter(None, 'buffersize', self.__buffer_size__, [1, 100], 1, Regex='^([1-9][0-9]{0,3}|10000)$') + + self.__parameters__['xRange-auto'] = DParameter(None,'xRange-auto','1',None,1, Regex='^(1|0){1}$') + self.__parameters__['xRange'] = DParameter(None,'xRange','[0,1]',None,1, Regex='(\d+\.\d+)') + self.__parameters__['yRange-auto'] = DParameter(None,'yRange-auto','1',None,1, Regex='^(1|0){1}$') + self.__parameters__['yRange'] = DParameter(None,'yRange','[0,1]',None,1, Regex='(\d+\.\d+)') + self.send_new_parameter_list(list(self.__parameters__.values())) # --------------------------- @@ -195,7 +213,6 @@ def initiate_layer_0(self, config=None): return True - def pause(self): """ Function pause @@ -291,6 +308,30 @@ def set_parameter(self, name, value): self.config['buffersize']['value'] = value self.set_buffer_size(value) + if name == 'xRange-auto': + self.config['xRange-auto']['value'] = value + if int(value) == 1: + self.__plotWidget__.getPlotItem().getViewBox().menu.xAutoClicked() + else: + self.use_range_for_x(self.config['xRange']['value']) + + if name == 'yRange-auto': + self.config['yRange-auto']['value'] = value + if int(value) == 1: + self.__plotWidget__.getPlotItem().getViewBox().menu.yAutoClicked() + else: + self.use_range_for_y(self.config['yRange']['value']) + + if name == 'xRange': + self.config['xRange']['value'] = value + if self.config['xRange-auto']['value']=='0': + self.use_range_for_x(value) + + if name == 'yRange': + self.config['yRange']['value'] = value + if self.config['yRange-auto']['value']=='0': + self.use_range_for_y(value) + def update_pens(self): """ Function update pens @@ -497,92 +538,219 @@ def get_pen(self, index): return pq.mkPen(color=color, style=style) + def use_range_for_x(self,value): + reg = re.compile(r'(\d+\.\d+)') + range =reg.findall(value) + if len(range) == 2: + self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]),float(range[1])) + def use_range_for_y(self,value): + reg = re.compile(r'(\d+\.\d+)') + range =reg.findall(value) + if len(range) == 2: + self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]),float(range[1])) - def setup_context_menu(self): - custMenu = QtGui.QMenu("Options") - axesMenu = QtGui.QMenu('Axes') - - - xAutoAction = QtGui.QAction('Use auto range for X',self.__plotWidget__) - xAutoAction.triggered.connect(lambda : self.use_autorange_x()) - - axesMenu.addAction(xAutoAction) - - - # --------------------------------------------------------- - # Y-Range Actions - axesMenu.addSeparator().setText("Y range") - yAutoAction = QtGui.QAction('&Use auto range for Y', - self.__plotWidget__, - checkable=True, - triggered=self.use_autorange_y ) - self.use_autorange_y() - yAutoAction.setChecked(True) - - yManAction = QtGui.QAction('&Use manual range for Y', - self.__plotWidget__, - checkable=True, - triggered= self.use_man_range_y) - - self.minEdit = QtGui.QLineEdit() - self.minEdit.setFixedWidth(100) - minEditA = QtGui.QWidgetAction(self.__plotWidget__) - minEditA.setDefaultWidget(self.minEdit) - self.maxEdit = QtGui.QLineEdit() - self.maxEdit.setFixedWidth(100) - maxEditA = QtGui.QWidgetAction(self.__plotWidget__) - maxEditA.setDefaultWidget(self.maxEdit) + def setup_context_menu(self): + self.custMenu = QtGui.QMenu("Options") + self.axesMenu = QtGui.QMenu('Axes') + self.gridMenu = QtGui.QMenu('Grid') - # --------------------------------------------------------- - # Set group for automatic uncheck - rangeMode = QtGui.QActionGroup(self.__plotWidget__) - rangeMode.addAction(yAutoAction) - rangeMode.addAction(yManAction) # --------------------------------------------------------- - # add actions to menu - axesMenu.addAction(yManAction) - axesMenu.addAction(minEditA) - axesMenu.addAction(maxEditA) - axesMenu.addAction(yAutoAction) - - - custMenu.addMenu(axesMenu) + ##### X-Range Actions + self.xRange_Widget = QtGui.QWidget() + self.xRange_Layout = QtGui.QVBoxLayout(self.xRange_Widget) + self.xRange_Layout.setContentsMargins(2,2,2,2) + self.xRange_Layout.setSpacing(1) + + self.xRange_AutoCheckbox = QtGui.QCheckBox() + self.xRange_AutoCheckbox.stateChanged.connect(self.contextMenu_xRange_toogle) + self.xRange_AutoCheckbox.setText('X-Autorange') + if self.config['xRange-auto']['value'] == '1': + self.xRange_AutoCheckbox.setChecked(True) + self.xRange_Layout.addWidget(self.xRange_AutoCheckbox) + + ##### X Line Edits + # Layout + self.xRange_EditWidget = QtGui.QWidget() + self.xRange_EditLayout = QtGui.QHBoxLayout(self.xRange_EditWidget) + self.xRange_EditLayout.setContentsMargins(2,2,2,2) + self.xRange_EditLayout.setSpacing(1) + + # get old values; + reg = re.compile(r'(\d+\.\d+)') + range =reg.findall(self.config['xRange']['value']) + if len(range) == 2: + x_min = range[0] + x_max = range[1] + else: + x_min = '0.0' + x_max = '1.0' + + + # Min + self.xRange_minEdit = QtGui.QLineEdit() + self.xRange_minEdit.setFixedWidth(80) + self.xRange_minEdit.setText(x_min) + self.xRange_minEdit.editingFinished.connect(self.contextMenu_xRange_toogle) + # Max + self.xRange_maxEdit = QtGui.QLineEdit() + self.xRange_maxEdit.setFixedWidth(80) + self.xRange_maxEdit.setText(x_max) + self.xRange_maxEdit.editingFinished.connect(self.contextMenu_xRange_toogle) + # addTo Layout + self.xRange_EditLayout.addWidget(self.xRange_minEdit) + self.xRange_EditLayout.addWidget(QtGui.QLabel('<')) + self.xRange_EditLayout.addWidget(self.xRange_maxEdit) + self.xRange_Layout.addWidget(self.xRange_EditWidget) + + # build Action + self.xRange_Action = QtGui.QWidgetAction(self.__plotWidget__) + self.xRange_Action.setDefaultWidget(self.xRange_Widget) + + + # --------------------------------------------------------------- + ##### Y-Range Actions + self.yRange_Widget = QtGui.QWidget() + self.yRange_Layout = QtGui.QVBoxLayout(self.yRange_Widget) + self.yRange_Layout.setContentsMargins(2,2,2,2) + self.yRange_Layout.setSpacing(1) + + self.yRange_AutoCheckbox = QtGui.QCheckBox() + self.yRange_AutoCheckbox.stateChanged.connect(self.contextMenu_yRange_toogle) + self.yRange_AutoCheckbox.setText('Y-Autorange') + self.yRange_Layout.addWidget(self.yRange_AutoCheckbox) + if self.config['yRange-auto']['value'] == '1': + self.yRange_AutoCheckbox.setChecked(True) + ##### Y Line Edits + # Layout + self.yRange_EditWidget = QtGui.QWidget() + self.yRange_EditLayout = QtGui.QHBoxLayout(self.yRange_EditWidget) + self.yRange_EditLayout.setContentsMargins(2,2,2,2) + self.yRange_EditLayout.setSpacing(1) + + # get old values; + reg = re.compile(r'(\d+\.\d+)') + range =reg.findall(self.config['yRange']['value']) + if len(range) == 2: + y_min = range[0] + y_max = range[1] + else: + y_min = '0.0' + y_max = '1.0' + + # Min + self.yRange_minEdit = QtGui.QLineEdit() + self.yRange_minEdit.setFixedWidth(80) + self.yRange_minEdit.setText(y_min) + self.yRange_minEdit.editingFinished.connect(self.contextMenu_yRange_toogle) + + # Max + self.yRange_maxEdit = QtGui.QLineEdit() + self.yRange_maxEdit.setFixedWidth(80) + self.yRange_maxEdit.setText(y_max) + self.yRange_maxEdit.editingFinished.connect(self.contextMenu_yRange_toogle) + # addTo Layout + self.yRange_EditLayout.addWidget(self.yRange_minEdit) + self.yRange_EditLayout.addWidget(QtGui.QLabel('<')) + self.yRange_EditLayout.addWidget(self.yRange_maxEdit) + self.yRange_Layout.addWidget(self.yRange_EditWidget) + + # build Action + self.yRange_Action = QtGui.QWidgetAction(self.__plotWidget__) + self.yRange_Action.setDefaultWidget(self.yRange_Widget) + + ##### Rolling Plot + self.rolling_Checkbox = QtGui.QCheckBox() + self.rolling_Checkbox.setText('Rolling plot') + self.rolling_Checkbox.setChecked(self.config['rolling_plot']['value']=='1') + self.rolling_Checkbox.stateChanged.connect(self.contextMenu_rolling_toogled) + self.rolling_Checkbox_Action = QtGui.QWidgetAction(self.__plotWidget__) + self.rolling_Checkbox_Action.setDefaultWidget(self.rolling_Checkbox) + + + + ##### Build axes menu + self.axesMenu.addAction(self.xRange_Action) + self.axesMenu.addSeparator().setText("Y-Range") + self.axesMenu.addAction(self.yRange_Action) + + # Grid Menu: + # ----------------------------------------------------------- + # Y-Grid checkbox + self.xGrid_Checkbox = QtGui.QCheckBox() + self.xGrid_Checkbox.stateChanged.connect(self.contextMenu_xGrid_toogle) + self.xGrid_Checkbox.setText('X-Grid') + self.xGrid_Action = QtGui.QWidgetAction(self.__plotWidget__) + self.xGrid_Action.setDefaultWidget(self.xGrid_Checkbox) + self.gridMenu.addAction(self.xGrid_Action) + # Check config for startup state + if self.__show_grid_x__: + self.xGrid_Checkbox.setChecked(True) + + # X-Grid checkbox + self.yGrid_Checkbox = QtGui.QCheckBox() + self.yGrid_Checkbox.stateChanged.connect(self.contextMenu_yGrid_toogle) + self.yGrid_Checkbox.setText('Y-Grid') + self.yGrid_Action = QtGui.QWidgetAction(self.__plotWidget__) + self.yGrid_Action.setDefaultWidget(self.yGrid_Checkbox) + self.gridMenu.addAction(self.yGrid_Action) + # Check config for startup state + if self.__show_grid_y__: + self.yGrid_Checkbox.setChecked(True) + + # add Menus + self.custMenu.addMenu(self.axesMenu) + self.custMenu.addMenu(self.gridMenu) + self.custMenu.addSeparator().setText("Rolling Plot") + self.custMenu.addAction(self.rolling_Checkbox_Action) self.__plotWidget__.getPlotItem().getViewBox().menu.clear() - self.__plotWidget__.getPlotItem().ctrlMenu = custMenu - - - - + self.__plotWidget__.getPlotItem().ctrlMenu = [ self.create_control_context_menu(), self.custMenu] - def use_man_range_y(self): - # TODO: save para - - mi = float(self.minEdit.text()) - ma = float(self.maxEdit.text()) + def contextMenu_rolling_toogled(self): + if self.rolling_Checkbox.isChecked(): + self.control_api.do_set_parameter(self.__id__,'rolling','1') + else: + self.control_api.do_set_parameter(self.__id__,'rolling','0') - self.control_api.do_set_parameter(self.__id__, 'x-grid', '1') + def contextMenu_xGrid_toogle(self): + if self.xGrid_Checkbox.isChecked(): + self.control_api.do_set_parameter(self.__id__, 'x-grid', '1') + else: + self.control_api.do_set_parameter(self.__id__, 'x-grid', '0') - self.__plotWidget__.getPlotItem().getViewBox().setYRange(mi,ma) + def contextMenu_yGrid_toogle(self): + if self.yGrid_Checkbox.isChecked(): + self.control_api.do_set_parameter(self.__id__, 'y-grid', '1') + else: + self.control_api.do_set_parameter(self.__id__, 'y-grid', '0') - def use_man_range_x(self): - # TODO: save para - self.__plotWidget__.getPlotItem().getViewBox().setXRange(0,1) + def contextMenu_xRange_toogle(self): + if self.xRange_AutoCheckbox.isChecked(): + # do autorange + self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '1') + else: + mi = self.xRange_minEdit.text() + ma = self.xRange_maxEdit.text() + self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '0') + self.control_api.do_set_parameter(self.__id__, 'xRange', '['+mi +' '+ma+']' ) + def contextMenu_yRange_toogle(self): + if self.yRange_AutoCheckbox.isChecked(): + # do autorange + self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '1') - def use_autorange_y(self): - # TODO: save para - self.__plotWidget__.getPlotItem().getViewBox().menu.yAutoClicked() + else: + # do man range + mi = self.yRange_minEdit.text() + ma = self.yRange_maxEdit.text() + self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '0') + self.control_api.do_set_parameter(self.__id__, 'yRange', '['+mi +' '+ma+']' ) - def use_autorange_x(self): - # TODO: save para - self.__plotWidget__.getPlotItem().getViewBox().menu.xAutoClicked() - def update_legend(self): # self.__plotWidget__.removeItem(self.__legend__) @@ -653,6 +821,24 @@ def get_plugin_configuration(self): 'regex': '^(1|0)$', 'type': 'bool', 'display_text': 'Rolling Plot' + }, 'xRange-auto': { + 'value': '1', + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'xRange-auto' + }, 'yRange-auto': { + 'value': '1', + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'yRange-auto' + }, 'xRange': { + 'value': '[0.0 1.0]', + 'regex': '(\d+\.\d+)', + 'display_text': 'xRange-auto' + }, 'yRange': { + 'value': '[0.0 1.0]', + 'regex': '(\d+\.\d+)', + 'display_text': 'yRange-auto' } } # http://www.regexr.com/ From 3425bc02b74998cbbd3747770911b80f86db9311 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 5 Jan 2015 15:45:32 +0100 Subject: [PATCH 022/260] test update due to new name of plot plugin --- papi/tests/TestCoreMock.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/papi/tests/TestCoreMock.py b/papi/tests/TestCoreMock.py index 45febd35..1f731a06 100644 --- a/papi/tests/TestCoreMock.py +++ b/papi/tests/TestCoreMock.py @@ -14,7 +14,7 @@ from threading import Thread from multiprocessing import Queue import papi.constants as const -from pyqtgraph import QtCore +from papi.pyqtgraph import QtCore import time @@ -31,7 +31,7 @@ class TestCoreMock(unittest.TestCase): def test_create_plugin_sub(self): # create a Plot and Sinus plugin - self.gui_api.do_create_plugin('PlotPerformance','Plot1') + self.gui_api.do_create_plugin('Plot','Plot1') self.gui_api.do_create_plugin('Sinus','Sin1') @@ -54,7 +54,7 @@ def test_create_plugin_sub(self): def test_do_create_api(self): - self.gui_api.do_create_plugin('PlotPerformance','Plot1') + self.gui_api.do_create_plugin('Plot','Plot1') self.gui_api.do_create_plugin('Sinus','Sin1') @@ -94,7 +94,7 @@ def test_delete_plugin_no_VIP_PCP(self): def test_delete_plugin_is_VIP_PCP(self): PL_1_NAME = 'Plot1' - PL_1_IDENT = 'PlotPerformance' + PL_1_IDENT = 'Plot' self.gui_api.do_create_plugin(PL_1_IDENT,PL_1_NAME) time.sleep(TestCoreMock.DELAY_TIME) @@ -115,7 +115,7 @@ def test_delete_plugin_is_VIP_PCP(self): def test_reset_papi_1(self): PL_1_NAME = 'Plot1' - PL_1_IDENT = 'PlotPerformance' + PL_1_IDENT = 'Plot' self.gui_api.do_create_plugin(PL_1_IDENT,PL_1_NAME) time.sleep(TestCoreMock.DELAY_TIME) @@ -136,7 +136,7 @@ def test_reset_papi_1(self): def test_reset_papi_2(self): - Plugins = [ ['Plot1', 'PlotPerformance'], ['Sinus1', 'Sinus'], ['Add1', 'Add'], ['Butt', 'Button'] ] + Plugins = [ ['Plot1', 'Plot'], ['Sinus1', 'Sinus'], ['Add1', 'Add'], ['Butt', 'Button'] ] for pl in Plugins: self.gui_api.do_create_plugin(pl[1], pl[0]) @@ -167,7 +167,7 @@ def test_reset_papi_2(self): def test_stopReset_iop(self): - Plugins = [ ['Plot1', 'PlotPerformance'], ['Sinus1', 'Sinus'], ['Add1', 'Add'], ['Butt', 'Button'] ] + Plugins = [ ['Plot1', 'Plot'], ['Sinus1', 'Sinus'], ['Add1', 'Add'], ['Butt', 'Button'] ] for pl in Plugins: self.gui_api.do_create_plugin(pl[1], pl[0]) @@ -270,7 +270,7 @@ def test_gui_api_valid_name(self): self.assertFalse( self.gui_api.do_test_name_to_be_unique('tt tt') ) def test_do_subscribe(self): - Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'PlotPerformance' ] ] + Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'Plot' ] ] for pl in Plugins: self.gui_api.do_create_plugin(pl[1], pl[0]) @@ -295,7 +295,7 @@ def test_do_subscribe(self): self.assertEqual('Sinus1',dsub.uname) def test_do_unsubscribe(self): - Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'PlotPerformance' ] ] + Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'Plot' ] ] for pl in Plugins: self.gui_api.do_create_plugin(pl[1], pl[0]) @@ -328,7 +328,7 @@ def test_do_unsubscribe(self): self.assertEqual(len(subs.keys()), 0) def test_do_set_parameter(self): - Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'PlotPerformance' ] ] + Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'Plot' ] ] for pl in Plugins: self.gui_api.do_create_plugin(pl[1], pl[0]) @@ -360,7 +360,7 @@ def test_do_set_parameter(self): self.gui_data.get_dplugin_by_uname('Sinus1').get_parameters()['Frequenz Block SinMit_f3'].value) def test_pause_IOP(self): - Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'PlotPerformance' ] ] + Plugins = [ ['Sinus1', 'Sinus'], ['Plot1', 'Plot' ] ] for pl in Plugins: self.gui_api.do_create_plugin(pl[1], pl[0]) From 7c3ec98f548755e64d833db0d3ee5a83e6e0b74f Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 5 Jan 2015 16:16:31 +0100 Subject: [PATCH 023/260] fixes a bug in config loader. Set paramenter by uname was needed. See Issue #13 --- papi/gui/gui_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 24b19160..0b97f06d 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -584,7 +584,8 @@ def config_loader_subs(self, pl_to_start, subs_to_make, parameters_to_change, si self.do_subscribe_uname(sub[0], sub[1], sub[2], sub[3], sub[4]) for para in parameters_to_change: - self.do_set_parameter(para[0], para[1], para[2]) + self.do_set_parameter_uname(para[0], para[1], para[2]) + print(para) for sig in signals_to_change: plugin_uname = sig[0] From 246c8b536979808390421153da4110b44673b276 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 5 Jan 2015 16:17:20 +0100 Subject: [PATCH 024/260] some cfg improvement --- papi/last_active_papi.xml | 130 +++++++++++++++++--------------- papi/plugin/visual/Plot/Plot.py | 15 ++-- 2 files changed, 78 insertions(+), 67 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 4eaf6e1e..2ee67e6e 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,114 +1,115 @@ - + Plot - - \w+,\s+\w+ - amplitude, V - Label-Y - - - \w+,\s*\w+ - time, s - Label-X - - - ^(1|0)$ - 1 - bool - xRange-auto - - - ^(1|0)$ - 1 - bool - yRange-auto - - - ^(1|0)$ + 0 bool - Grid-X + ^(1|0)$ + Rolling Plot - - Plot + + (0,0) + \(([0-9]+),([0-9]+)\) + 1 + Determine position: (x,y) - - VisualPlugin - Used display name + + [0,1] + (\d+\.\d+) + xRange-auto + [0 1 2 3 4] ^\[(\s*\d\s*)+\] 1 - [0 1 2 3 4] Color - - (\d+\.\d+) - [0.0 1.5] + + 1 + bool + ^(1|0)$ xRange-auto + [0 0 0 0 0] ^\[(\s*\d\s*)+\] 1 - [0 0 0 0 0] Style - - ^(1|0)$ - 0 - bool - Grid-Y + + amplitude, V + \w+,\s+\w+ + Label-Y + 1000 ^([1-9][0-9]{0,3}|10000)$ 1 - 300 Buffersize + + 0 + bool + ^(1|0)$ + yRange-auto + + + 0 + bool + ^(1|0)$ + Grid-Y + + [1.0 2.0] (\d+\.\d+) - [0.0 15.0] yRange-auto - - \(([0-9]+),([0-9]+)\) - 1 - (0,0) - Determine position: (x,y) + + 1 + (\d+) - - ^(1|0)$ + 0 bool - Rolling Plot + ^(1|0)$ + Grid-X + + + Plot + + + time, s + \w+,\s*\w+ + Label-X + + + VisualPlugin + Used display name + (300,300) \(([0-9]+),([0-9]+)\) 1 - (300,300) Determine size: (height,width) - - (\d+) - 1 - - 1 + 1 0 0 - 300 [0,1] - [0,1] - 1 + 1000 [0 1 2 3 4] 0 + [1.0 2.0] + 1 [0 0 0 0 0] - 1 + 0 + CPUXLoad @@ -129,6 +130,13 @@ 0.01 + + + + percentage + + + diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index aa79c3b7..0bebc0f0 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -269,14 +269,17 @@ def set_parameter(self, name, value): :param value: :return: """ + print(name, value) + if name == 'x-grid': self.config['x-grid']['value'] = value self.__plotWidget__.showGrid(x=value == '1') + self.xGrid_Checkbox.setChecked(value=='1') if name == 'y-grid': - self.config['y-grid']['value'] = value self.__plotWidget__.showGrid(y=value == '1') + self.yGrid_Checkbox.setChecked(value=='1') if name == 'downsampling_rate': self.config['downsampling_rate']['value'] = value @@ -286,7 +289,7 @@ def set_parameter(self, name, value): if name == 'rolling': self.__rolling_plot__ = int(float(value)) == int('1') self.config['rolling_plot']['value'] = value - + self.rolling_Checkbox.setChecked(value=='1') if self.__rolling_plot__: # if self.__vertical_line__ not in self.__plotWidget__.listDataItems(): @@ -310,6 +313,7 @@ def set_parameter(self, name, value): if name == 'xRange-auto': self.config['xRange-auto']['value'] = value + self.xRange_AutoCheckbox.setChecked(value=='1') if int(value) == 1: self.__plotWidget__.getPlotItem().getViewBox().menu.xAutoClicked() else: @@ -317,6 +321,7 @@ def set_parameter(self, name, value): if name == 'yRange-auto': self.config['yRange-auto']['value'] = value + self.yRange_AutoCheckbox.setChecked(value=='1') if int(value) == 1: self.__plotWidget__.getPlotItem().getViewBox().menu.yAutoClicked() else: @@ -332,6 +337,8 @@ def set_parameter(self, name, value): if self.config['yRange-auto']['value']=='0': self.use_range_for_y(value) + + def update_pens(self): """ Function update pens @@ -550,10 +557,6 @@ def use_range_for_y(self,value): if len(range) == 2: self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]),float(range[1])) - - - - def setup_context_menu(self): self.custMenu = QtGui.QMenu("Options") self.axesMenu = QtGui.QMenu('Axes') From 15f8c8c1f0231b93764cfa08a52414998d814b13 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 5 Jan 2015 16:19:41 +0100 Subject: [PATCH 025/260] . --- papi/gui/gui_api.py | 1 - 1 file changed, 1 deletion(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 0b97f06d..4e8c839b 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -585,7 +585,6 @@ def config_loader_subs(self, pl_to_start, subs_to_make, parameters_to_change, si for para in parameters_to_change: self.do_set_parameter_uname(para[0], para[1], para[2]) - print(para) for sig in signals_to_change: plugin_uname = sig[0] From 9070fa55849e98a2bf0bbf0f16ff9345803a6d6b Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 5 Jan 2015 16:20:03 +0100 Subject: [PATCH 026/260] deleted a print --- papi/plugin/visual/Plot/Plot.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 0bebc0f0..b4137f14 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -269,8 +269,6 @@ def set_parameter(self, name, value): :param value: :return: """ - print(name, value) - if name == 'x-grid': self.config['x-grid']['value'] = value self.__plotWidget__.showGrid(x=value == '1') From 5643a0f3ec1e06cb42ffe858f49fc96ff61df814 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 5 Jan 2015 16:22:10 +0100 Subject: [PATCH 027/260] . --- papi/plugin/visual/Plot/Plot.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index b4137f14..17b181aa 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -335,8 +335,6 @@ def set_parameter(self, name, value): if self.config['yRange-auto']['value']=='0': self.use_range_for_y(value) - - def update_pens(self): """ Function update pens @@ -752,7 +750,6 @@ def contextMenu_yRange_toogle(self): self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '0') self.control_api.do_set_parameter(self.__id__, 'yRange', '['+mi +' '+ma+']' ) - def update_legend(self): # self.__plotWidget__.removeItem(self.__legend__) self.__legend__.scene().removeItem(self.__legend__) From 9e8fbd87e3e14056ddfb91534a0ad382f7c02115 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 5 Jan 2015 17:57:04 +0100 Subject: [PATCH 028/260] improved context menu --- cfg_collection/ExampleFourier.xml | 271 ++++++++++++++++++---------- papi/last_active_papi.xml | 282 ++++++++++++++++++++---------- papi/plugin/visual/Plot/Plot.py | 56 ++++-- 3 files changed, 402 insertions(+), 207 deletions(-) diff --git a/cfg_collection/ExampleFourier.xml b/cfg_collection/ExampleFourier.xml index cf850ab3..5e356ed8 100644 --- a/cfg_collection/ExampleFourier.xml +++ b/cfg_collection/ExampleFourier.xml @@ -1,111 +1,228 @@ - - + + - - Fourier_Rect + + Fourier_Rect_MOD - - Fourier - - FourierXRect - - - 130.149.155.73 - 1 - \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} - - - 9999 - 1 - \d{1,5} + FourierXRectXMOD + + + + rect1 + + + rect2 + + + rect3 + + + rect4 + + + rect5 + + + rect6 + + + rect7 + + + rect8 + + + rect9 + + + rect10 + + + rect11 + + + rect12 + + + rect13 + + + rect14 + + + rect15 + + + rect16 + + + rect17 + + + rect18 + + + rect19 + + + + + Add + + + Add + + + + + + + Sum + + + + + + FourierXRectXMOD + + + rect1 + rect10 + rect11 + rect12 + rect13 + rect14 + rect15 + rect16 + rect17 + rect18 + rect19 + rect2 + rect3 + rect4 + rect5 + rect6 + rect7 + rect8 + + + + Plot + + [0,1] + xRange-auto + (\d+\.\d+) + VisualPlugin Used display name - - 1 - ^(1|0)$ - bool - Grid-Y + + [0 0 0 0 0] + 1 + Style + ^\[(\s*\d\s*)+\] + + + 3000 + 1 + Buffersize + ^([1-9][0-9]{0,3}|10000)$ + + + Plot amplitude, V - \w+,\s+\w+ Label-Y + \w+,\s+\w+ - + + bool 0 + Grid-X ^(1|0)$ - bool - Rolling Plot 1 (\d+) - - [0 0 0 0 0] - 1 - ^\[(\s*\d\s*)+\] - Style + + bool + 1 + xRange-auto + ^(1|0)$ - + + [0,1] + yRange-auto + (\d+\.\d+) + + + bool 0 + Grid-Y ^(1|0)$ - bool - Grid-X - - 1000 + + (0,0) + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) 1 - ^([1-9][0-9]{0,3}|10000)$ - Buffersize - - Plot + + [0 1 2 3 4] + 1 + Color + ^\[(\s*\d\s*)+\] - (300,300) - 1 + (1517,1065) Determine size: (height,width) \(([0-9]+),([0-9]+)\) - - - (0,0) 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) time, s - \w+,\s*\w+ Label-X + \w+,\s*\w+ - - [0 1 2 3 4] - 1 - ^\[(\s*\d\s*)+\] - Color + + bool + 1 + Rolling Plot + ^(1|0)$ + + + bool + 1 + yRange-auto + ^(1|0)$ + [0,1] 0 - 1 - 0 - [0 1 2 3 4] - 1000 + 1 + 1 [0 0 0 0 0] - 0 + 3000 + [0 1 2 3 4] + 0 + 5 + 1 + [0,1] + Add @@ -116,40 +233,4 @@ - - Add - - - Add - - - - - - FourierXRect - - - rect1 - rect10 - rect11 - rect12 - rect13 - rect14 - rect15 - rect16 - rect17 - rect18 - rect19 - rect2 - rect3 - rect4 - rect5 - rect6 - rect7 - rect8 - rect9 - - - - diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 2ee67e6e..914fe92b 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,142 +1,236 @@ - - + + + + Fourier_Rect_MOD + + + FourierXRectXMOD + + + + + + + rect1 + + + rect2 + + + rect3 + + + rect4 + + + rect5 + + + rect6 + + + rect7 + + + rect8 + + + rect9 + + + rect10 + + + rect11 + + + rect12 + + + rect13 + + + rect14 + + + rect15 + + + rect16 + + + rect17 + + + rect18 + + + rect19 + + + + + + + Add + + + Add + + + + + + + Sum + + + + + + FourierXRectXMOD + + + rect1 + rect10 + rect11 + rect12 + rect13 + rect14 + rect15 + rect16 + rect17 + rect18 + rect19 + rect2 + rect3 + rect4 + rect5 + rect6 + rect7 + rect8 + + + + Plot - - 0 - bool - ^(1|0)$ - Rolling Plot + + ^\[(\s*\d\s*)+\] + [0 0 0 0 0] + Style + 1 - - (0,0) - \(([0-9]+),([0-9]+)\) + + VisualPlugin + Used display name + + + \w+,\s*\w+ + Label-X + time, s + + + ^([1-9][0-9]{0,3}|10000)$ + 3000 + Buffersize 1 - Determine position: (x,y) - - [0,1] - (\d+\.\d+) - xRange-auto + + Plot + + + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 + (1517,1065) - [0 1 2 3 4] ^\[(\s*\d\s*)+\] - 1 + [0 1 2 3 4] Color + 1 + xRange-auto + ^(1|0)$ + bool 1 + + + Rolling Plot + ^(1|0)$ bool + 1 + + + Grid-X ^(1|0)$ - xRange-auto + bool + 0 - - [0 0 0 0 0] - ^\[(\s*\d\s*)+\] - 1 - Style + + (\d+\.\d+) + yRange-auto + [0,1] - amplitude, V \w+,\s+\w+ Label-Y + amplitude, V - - 1000 - ^([1-9][0-9]{0,3}|10000)$ + + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) 1 - Buffersize + (0,0) - - 0 - bool - ^(1|0)$ - yRange-auto + + (\d+\.\d+) + xRange-auto + [0,1] - 0 - bool - ^(1|0)$ Grid-Y - - - [1.0 2.0] - (\d+\.\d+) - yRange-auto + ^(1|0)$ + bool + 0 - 1 (\d+) + 1 - - 0 - bool + + yRange-auto ^(1|0)$ - Grid-X - - - Plot - - - time, s - \w+,\s*\w+ - Label-X - - - VisualPlugin - Used display name - - - (300,300) - \(([0-9]+),([0-9]+)\) - 1 - Determine size: (height,width) + bool + 1 - 1 - 0 - 0 - [0,1] - 1000 [0 1 2 3 4] + 3000 + 1 0 - [1.0 2.0] - 1 + [0,1] + 0 [0 0 0 0 0] - 0 + [0,1] + 1 + 5 + 1 - CPUXLoad - + Add + - load_in_percent + Sum - - CPU_Load - - - CPUXLoad - - - - 0.01 - - - - - percentage - - - - - diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 17b181aa..0e750b25 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -160,16 +160,6 @@ def initiate_layer_0(self, config=None): self.set_widget_for_internal_usage(self.__plotWidget__) - # - if self.config['xRange-auto']['value']=='1': - pass - else: - self.use_range_for_x(self.config['xRange']['value']) - - if self.config['yRange-auto']['value']== '1': - pass - else: - self.use_range_for_y(self.config['yRange']['value']) # --------------------------- # Create Parameters @@ -208,8 +198,16 @@ def initiate_layer_0(self, config=None): self.setup_context_menu() + # + if self.config['xRange-auto']['value']=='1': + pass + else: + self.use_range_for_x(self.config['xRange']['value']) - + if self.config['yRange-auto']['value']== '1': + pass + else: + self.use_range_for_y(self.config['yRange']['value']) return True @@ -282,8 +280,10 @@ def set_parameter(self, name, value): if name == 'downsampling_rate': self.config['downsampling_rate']['value'] = value self.__downsampling_rate__ = int(value) + #self.__plotWidget__.getPlotItem().setDownsampling(ds=int(value),auto=False,mode='mean') self.__new_added_data__ = 0 + if name == 'rolling': self.__rolling_plot__ = int(float(value)) == int('1') self.config['rolling_plot']['value'] = value @@ -313,16 +313,24 @@ def set_parameter(self, name, value): self.config['xRange-auto']['value'] = value self.xRange_AutoCheckbox.setChecked(value=='1') if int(value) == 1: + self.xRange_minEdit.setDisabled(True) + self.xRange_maxEdit.setDisabled(True) self.__plotWidget__.getPlotItem().getViewBox().menu.xAutoClicked() else: self.use_range_for_x(self.config['xRange']['value']) + self.xRange_minEdit.setDisabled(False) + self.xRange_maxEdit.setDisabled(False) if name == 'yRange-auto': self.config['yRange-auto']['value'] = value self.yRange_AutoCheckbox.setChecked(value=='1') if int(value) == 1: + self.yRange_minEdit.setDisabled(True) + self.yRange_maxEdit.setDisabled(True) self.__plotWidget__.getPlotItem().getViewBox().menu.yAutoClicked() else: + self.yRange_minEdit.setDisabled(False) + self.yRange_maxEdit.setDisabled(False) self.use_range_for_y(self.config['yRange']['value']) if name == 'xRange': @@ -545,6 +553,8 @@ def use_range_for_x(self,value): reg = re.compile(r'(\d+\.\d+)') range =reg.findall(value) if len(range) == 2: + self.xRange_minEdit.setText(range[0]) + self.xRange_maxEdit.setText(range[1]) self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]),float(range[1])) def use_range_for_y(self,value): @@ -566,11 +576,9 @@ def setup_context_menu(self): self.xRange_Layout.setContentsMargins(2,2,2,2) self.xRange_Layout.setSpacing(1) - self.xRange_AutoCheckbox = QtGui.QCheckBox() + self.xRange_AutoCheckbox = QtGui.QCheckBox(checked= self.config['xRange-auto']['value'] == '1') self.xRange_AutoCheckbox.stateChanged.connect(self.contextMenu_xRange_toogle) self.xRange_AutoCheckbox.setText('X-Autorange') - if self.config['xRange-auto']['value'] == '1': - self.xRange_AutoCheckbox.setChecked(True) self.xRange_Layout.addWidget(self.xRange_AutoCheckbox) ##### X Line Edits @@ -619,12 +627,11 @@ def setup_context_menu(self): self.yRange_Layout.setContentsMargins(2,2,2,2) self.yRange_Layout.setSpacing(1) - self.yRange_AutoCheckbox = QtGui.QCheckBox() + self.yRange_AutoCheckbox = QtGui.QCheckBox(checked= self.config['xRange-auto']['value'] == '1') self.yRange_AutoCheckbox.stateChanged.connect(self.contextMenu_yRange_toogle) self.yRange_AutoCheckbox.setText('Y-Autorange') self.yRange_Layout.addWidget(self.yRange_AutoCheckbox) - if self.config['yRange-auto']['value'] == '1': - self.yRange_AutoCheckbox.setChecked(True) + ##### Y Line Edits # Layout self.yRange_EditWidget = QtGui.QWidget() @@ -710,6 +717,12 @@ def setup_context_menu(self): self.__plotWidget__.getPlotItem().getViewBox().menu.clear() self.__plotWidget__.getPlotItem().ctrlMenu = [ self.create_control_context_menu(), self.custMenu] + + #self.__plotWidget__.getPlotItem().getViewBox() + + def range_changed(self): + print('r') + def contextMenu_rolling_toogled(self): if self.rolling_Checkbox.isChecked(): self.control_api.do_set_parameter(self.__id__,'rolling','1') @@ -732,7 +745,11 @@ def contextMenu_xRange_toogle(self): if self.xRange_AutoCheckbox.isChecked(): # do autorange self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '1') + self.xRange_minEdit.setDisabled(True) + self.xRange_maxEdit.setDisabled(True) else: + self.xRange_minEdit.setDisabled(False) + self.xRange_maxEdit.setDisabled(False) mi = self.xRange_minEdit.text() ma = self.xRange_maxEdit.text() self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '0') @@ -742,8 +759,11 @@ def contextMenu_yRange_toogle(self): if self.yRange_AutoCheckbox.isChecked(): # do autorange self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '1') - + self.yRange_minEdit.setDisabled(True) + self.yRange_maxEdit.setDisabled(True) else: + self.yRange_minEdit.setDisabled(False) + self.yRange_maxEdit.setDisabled(False) # do man range mi = self.yRange_minEdit.text() ma = self.yRange_maxEdit.text() From a3c48cd9bd65243f9305388e52276e037891f00d Mon Sep 17 00:00:00 2001 From: SvKn Date: Wed, 7 Jan 2015 15:53:30 +0100 Subject: [PATCH 029/260] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 578a383f..3017b8f4 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ Basic installation on Ubuntu 14.04 64Bit, using python 3.4 `python3.4 main.py` +Contribution +------ + +To get started, sign the Contributor License Agreement. Documentation ------ From 787080450d790f3d231fd13c4a0de74b934978e3 Mon Sep 17 00:00:00 2001 From: SvKn Date: Wed, 7 Jan 2015 16:10:26 +0100 Subject: [PATCH 030/260] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3017b8f4..6c4789a4 100644 --- a/README.md +++ b/README.md @@ -41,4 +41,4 @@ Embedded Packages Yapsy 1.10.423 published under BSD-License, http://yapsy.sourceforge.net/#license -pyqtgraph-0.9.8 published under MIT-License +pyqtgraph-0.9.10 published under MIT-License From 34583dc56bc597fdad5be6e952e8900f5bcbc7cc Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 12:19:39 +0100 Subject: [PATCH 031/260] added merge of configs in core --- papi/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/papi/core.py b/papi/core.py index a66f8f48..d49fe16d 100644 --- a/papi/core.py +++ b/papi/core.py @@ -680,6 +680,8 @@ def __process_create_plugin__(self,event): if plugin_config is None or plugin_config =={}: plugin_config = plugin.plugin_object.get_startup_configuration() + else: + plugin_config = dict(list(plugin_config.items()) + list(plugin.plugin_object.get_startup_configuration().items())) # create Process object for new plugin # set parameter for work function of plugin, such as queues, id and eventTriggered From 33c4b3aadadf70d8ebee553e620c9aa13366e391 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 14:06:54 +0100 Subject: [PATCH 032/260] changes meta_update --- papi/core.py | 19 +++- papi/data/DPlugin.py | 26 ++++- papi/gui/gui_api.py | 55 ++++----- papi/gui/qt_new/item.py | 8 ++ papi/last_active_papi.xml | 132 +--------------------- papi/plugin/templates/IOP_DPP_template.py | 3 +- papi/plugin/visual/Plot/Plot.py | 132 ++++++++++++---------- 7 files changed, 155 insertions(+), 220 deletions(-) diff --git a/papi/core.py b/papi/core.py index a66f8f48..2a66c0af 100644 --- a/papi/core.py +++ b/papi/core.py @@ -259,11 +259,12 @@ def check_alive_callback(self): self.alive_timer = Timer(self.alive_intervall,self.check_alive_callback) self.alive_timer.start() - def update_meta_data_to_gui(self,pl_id): + def update_meta_data_to_gui(self,pl_id, inform_subscriber=False): """ On call this function will send the meta information of pl_id to GUI :param pl_id: id of plugin with new meta information + :param inform_subscriber: flag used to determine if a meta_update for all subscriber should be initiated :return: """ # get DPlugin object with id and check if it exists @@ -280,6 +281,19 @@ def update_meta_data_to_gui(self,pl_id): # check if plugin got some subscribers which run in own process if dplugin.own_process is True: dplugin.queue.put(eventMeta) + + # --------------------------------------------- + # Inform all subscriber if needed and wished + # --------------------------------------------- + if inform_subscriber: + dblocks = dplugin.get_dblocks() + for dblock_name in dblocks: + dblock = dblocks[dblock_name] + for subscriber_id in dblock.get_subscribers(): + dplugin_sub = self.core_data.get_dplugin_by_id(subscriber_id) + if dplugin_sub.own_process is False: + self.update_meta_data_to_gui(dplugin_sub.id, False) + return 1 else: # Dplugin object with pl_id does not exist in DCore of core @@ -626,8 +640,7 @@ def __process_edit_dplugin(self, event): self.log.printText(1,'edit_dplugin, Edited Dblock '+dblock.name+' of DPlugin '+pl.uname+ " : DSignal " + dsignal.uname + " to dname -> " + dsignal.dname ) - - self.update_meta_data_to_gui(pl.id) + self.update_meta_data_to_gui(pl.id, True) # ------- Event processing second stage: instr events --------- def __process_create_plugin__(self,event): diff --git a/papi/data/DPlugin.py b/papi/data/DPlugin.py index e400089a..6d5cf667 100644 --- a/papi/data/DPlugin.py +++ b/papi/data/DPlugin.py @@ -92,7 +92,10 @@ def rm_signal(self, signal): :return: """ if signal in self.signals: + signal.uname = signal.uname + "_deleted" + signal.dname = signal.dname + "_deleted" self.signals.remove(signal) + return True return False @@ -125,7 +128,6 @@ def get_signals(self): """ return copy.deepcopy(self.signals) - #NOT NEEDED ANYMORE !!! def get_signal_name(self, signal: int): """ @@ -308,6 +310,7 @@ def rm_dblock(self, dblock): :rtype boolean: """ if dblock.name in self.__blocks: + self.__blocks[dblock.name].name = dblock.name + "_deleted" del self.__blocks[dblock.name] return True else: @@ -362,6 +365,10 @@ def update_meta(self, meta): :return: """ + # -------------------------- + # Update DPlugin Attributes + # -------------------------- + self.id = meta.id self.pid = meta.pid self.state = meta.state @@ -370,8 +377,21 @@ def update_meta(self, meta): self.uname = meta.uname self.type = meta.type + # ----------------------------- + # Update DParameters of DPlugin + # ----------------------------- + self.__parameters = meta.__parameters + + # ----------------------------- + # Update DSubscriptions of DPlugin + # ----------------------------- + self.__subscriptions = meta.__subscriptions + + # ----------------------------- + # Update DBlocks of DPlugin + # ----------------------------- self.__blocks = meta.__blocks @@ -379,6 +399,7 @@ class DSubscription(DObject): def __init__(self, dblock): self.dblock = dblock + self.dblock_name = dblock.name self.alias = None self.signals = [] @@ -407,6 +428,9 @@ def rm_signal(self, signal): return False + def get_dblock(self): + return self.dblock + def get_signals(self): return copy.copy(self.signals) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 24b19160..3c506e3f 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -514,45 +514,46 @@ def do_load_xml(self, path): # -------------------------------- subs_xml = plugin_xml.find('Subscriptions') - for sub_xml in subs_xml.findall('Subscription'): - data_source = sub_xml.find('data_source').text - for block_xml in sub_xml.findall('block'): - block_name = block_xml.attrib['Name'] - signals = [] - for sig_xml in block_xml.findall('Signal'): - signals.append(str(sig_xml.text)) - alias_xml = block_xml.find('alias') - alias = alias_xml.text - pl_uname_new = self.change_uname_to_uniqe(pl_uname) - data_source_new = self.change_uname_to_uniqe(data_source) - subs_to_make.append([pl_uname_new, data_source_new, block_name, signals, alias]) + if subs_xml is not None: + for sub_xml in subs_xml.findall('Subscription'): + data_source = sub_xml.find('data_source').text + for block_xml in sub_xml.findall('block'): + block_name = block_xml.attrib['Name'] + signals = [] + for sig_xml in block_xml.findall('Signal'): + signals.append(str(sig_xml.text)) + alias_xml = block_xml.find('alias') + alias = alias_xml.text + pl_uname_new = self.change_uname_to_uniqe(pl_uname) + data_source_new = self.change_uname_to_uniqe(data_source) + subs_to_make.append([pl_uname_new, data_source_new, block_name, signals, alias]) # -------------------------------- # Load PreviousParameters # -------------------------------- prev_parameters_xml = plugin_xml.find('PreviousParameters') - for prev_parameter_xml in prev_parameters_xml.findall('Parameter'): - para_name = prev_parameter_xml.attrib['Name'] - para_value = prev_parameter_xml.text - pl_uname_new = self.change_uname_to_uniqe(pl_uname) - # TODO validate NO FLOAT in parameter - parameters_to_change.append([pl_uname_new, para_name, para_value]) + if prev_parameters_xml is not None: + for prev_parameter_xml in prev_parameters_xml.findall('Parameter'): + para_name = prev_parameter_xml.attrib['Name'] + para_value = prev_parameter_xml.text + pl_uname_new = self.change_uname_to_uniqe(pl_uname) + # TODO validate NO FLOAT in parameter + parameters_to_change.append([pl_uname_new, para_name, para_value]) # -------------------------------- # Load DBlocks due to signals name # -------------------------------- dblocks_xml = plugin_xml.find('DBlocks') - for dblock_xml in dblocks_xml: - dblock_name = dblock_xml.attrib['Name'] - print('DBlock ' + dblock_name) - dsignals_xml = dblock_xml.findall('DSignal') - for dsignal_xml in dsignals_xml: - dsignal_uname = dsignal_xml.attrib['uname'] - dsignal_dname = dsignal_xml.find('dname').text - print('Signal' + dsignal_uname + ' with ' + dsignal_dname) - signals_to_change.append([pl_uname, dblock_name, dsignal_uname, dsignal_dname]) + if dblocks_xml is not None: + for dblock_xml in dblocks_xml: + dblock_name = dblock_xml.attrib['Name'] + dsignals_xml = dblock_xml.findall('DSignal') + for dsignal_xml in dsignals_xml: + dsignal_uname = dsignal_xml.attrib['uname'] + dsignal_dname = dsignal_xml.find('dname').text + signals_to_change.append([pl_uname, dblock_name, dsignal_uname, dsignal_dname]) for pl in plugins_to_start: # 0: ident, 1: uname, 2: config diff --git a/papi/gui/qt_new/item.py b/papi/gui/qt_new/item.py index 644098aa..34534ffa 100644 --- a/papi/gui/qt_new/item.py +++ b/papi/gui/qt_new/item.py @@ -235,6 +235,8 @@ def data(self, role): if role == Qt.UserRole: return self.object + if role == Qt.EditRole: + return self.dsignal.dname return None @@ -324,6 +326,12 @@ def data(self, index, role): if role == Qt.UserRole: return super(DParameterTreeModel, self).data(index, Qt.UserRole) + if role == Qt.EditRole: + if col == 1: + index_sibling = index.sibling(row, col-1) + dparameter = super(DParameterTreeModel, self).data(index_sibling, Qt.UserRole) + return str(dparameter.value) + return None def setData(self, index, value, role): diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 4eaf6e1e..c05ac3fc 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,134 +1,4 @@ - + - - Plot - - - \w+,\s+\w+ - amplitude, V - Label-Y - - - \w+,\s*\w+ - time, s - Label-X - - - ^(1|0)$ - 1 - bool - xRange-auto - - - ^(1|0)$ - 1 - bool - yRange-auto - - - ^(1|0)$ - 0 - bool - Grid-X - - - Plot - - - VisualPlugin - Used display name - - - ^\[(\s*\d\s*)+\] - 1 - [0 1 2 3 4] - Color - - - (\d+\.\d+) - [0.0 1.5] - xRange-auto - - - ^\[(\s*\d\s*)+\] - 1 - [0 0 0 0 0] - Style - - - ^(1|0)$ - 0 - bool - Grid-Y - - - ^([1-9][0-9]{0,3}|10000)$ - 1 - 300 - Buffersize - - - (\d+\.\d+) - [0.0 15.0] - yRange-auto - - - \(([0-9]+),([0-9]+)\) - 1 - (0,0) - Determine position: (x,y) - - - ^(1|0)$ - 0 - bool - Rolling Plot - - - \(([0-9]+),([0-9]+)\) - 1 - (300,300) - Determine size: (height,width) - - - (\d+) - 1 - - - - 1 - 0 - 0 - 300 - [0,1] - [0,1] - 1 - [0 1 2 3 4] - 0 - [0 0 0 0 0] - 1 - - - - CPUXLoad - - - load_in_percent - - - - - - CPU_Load - - - CPUXLoad - - - - 0.01 - - - diff --git a/papi/plugin/templates/IOP_DPP_template.py b/papi/plugin/templates/IOP_DPP_template.py index 9905a353..58bca726 100644 --- a/papi/plugin/templates/IOP_DPP_template.py +++ b/papi/plugin/templates/IOP_DPP_template.py @@ -143,7 +143,8 @@ def get_plugin_configuration(self): def plugin_meta_updated(self): """ - Whenever the meta information is updated this function is called (if implemented). + Whenever the meta information is updated this function is called. + If this function is called there is no guarantee anymore that previous used reference are still used. :return: """ diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index aa79c3b7..588e20d5 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -161,12 +161,12 @@ def initiate_layer_0(self, config=None): self.set_widget_for_internal_usage(self.__plotWidget__) # - if self.config['xRange-auto']['value']=='1': + if self.config['xRange-auto']['value'] == '1': pass else: self.use_range_for_x(self.config['xRange']['value']) - if self.config['yRange-auto']['value']== '1': + if self.config['yRange-auto']['value'] == '1': pass else: self.use_range_for_y(self.config['yRange']['value']) @@ -188,10 +188,10 @@ def initiate_layer_0(self, config=None): self.__parameters__['buffersize'] = DParameter(None, 'buffersize', self.__buffer_size__, [1, 100], 1, Regex='^([1-9][0-9]{0,3}|10000)$') - self.__parameters__['xRange-auto'] = DParameter(None,'xRange-auto','1',None,1, Regex='^(1|0){1}$') - self.__parameters__['xRange'] = DParameter(None,'xRange','[0,1]',None,1, Regex='(\d+\.\d+)') - self.__parameters__['yRange-auto'] = DParameter(None,'yRange-auto','1',None,1, Regex='^(1|0){1}$') - self.__parameters__['yRange'] = DParameter(None,'yRange','[0,1]',None,1, Regex='(\d+\.\d+)') + self.__parameters__['xRange-auto'] = DParameter(None, 'xRange-auto', '1', None, 1, Regex='^(1|0){1}$') + self.__parameters__['xRange'] = DParameter(None, 'xRange', '[0,1]', None, 1, Regex='(\d+\.\d+)') + self.__parameters__['yRange-auto'] = DParameter(None, 'yRange-auto', '1', None, 1, Regex='^(1|0){1}$') + self.__parameters__['yRange'] = DParameter(None, 'yRange', '[0,1]', None, 1, Regex='(\d+\.\d+)') self.send_new_parameter_list(list(self.__parameters__.values())) @@ -208,9 +208,6 @@ def initiate_layer_0(self, config=None): self.setup_context_menu() - - - return True def pause(self): @@ -274,7 +271,6 @@ def set_parameter(self, name, value): self.__plotWidget__.showGrid(x=value == '1') if name == 'y-grid': - self.config['y-grid']['value'] = value self.__plotWidget__.showGrid(y=value == '1') @@ -288,7 +284,7 @@ def set_parameter(self, name, value): self.config['rolling_plot']['value'] = value if self.__rolling_plot__: - # if self.__vertical_line__ not in self.__plotWidget__.listDataItems(): + # if self.__vertical_line__ not in self.__plotWidget__.listDataItems(): self.__plotWidget__.addItem(self.__vertical_line__) @@ -324,12 +320,12 @@ def set_parameter(self, name, value): if name == 'xRange': self.config['xRange']['value'] = value - if self.config['xRange-auto']['value']=='0': + if self.config['xRange-auto']['value'] == '0': self.use_range_for_x(value) if name == 'yRange': self.config['yRange']['value'] = value - if self.config['yRange-auto']['value']=='0': + if self.config['yRange-auto']['value'] == '0': self.use_range_for_y(value) def update_pens(self): @@ -354,7 +350,6 @@ def update_plot(self): """ shift_data = 0 - for last_tvalue in self.__tdata_old__: if last_tvalue in self.__tbuffer__: shift_data = list(self.__tbuffer__).index(last_tvalue) @@ -374,7 +369,7 @@ def update_plot(self): if self.__rolling_plot__: data = np.roll(data, int(self.__append_at__)) - self.__vertical_line__.setValue(tdata[int(self.__append_at__)-1]) + self.__vertical_line__.setValue(tdata[int(self.__append_at__) - 1]) else: self.__vertical_line__.setValue(tdata[0]) @@ -430,7 +425,7 @@ def set_buffer_size(self, new_size): buffer_new = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION buffer_old = self.signals[signal_name]['buffer'] - #buffer_new.extend(buffer_old) + # buffer_new.extend(buffer_old) self.signals[signal_name]['buffer'] = buffer_new @@ -445,7 +440,8 @@ def plugin_meta_updated(self): subscriptions = self.dplugin_info.get_subscribtions() - current_signals = [] + current_signals = {} + index = 0 for dpluginsub_id in subscriptions: for dblock_name in subscriptions[dpluginsub_id]: @@ -453,24 +449,31 @@ def plugin_meta_updated(self): # get subscription for dblock subscription = subscriptions[dpluginsub_id][dblock_name] - for signal in subscription.get_signals(): - current_signals.append(signal) - current_signals = sorted(current_signals) + for signal_name in subscription.get_signals(): + signal = subscription.get_dblock().get_signal_by_uname(signal_name) + index += 1 + current_signals[signal_name] = {} + current_signals[signal_name]['signal'] = signal + current_signals[signal_name]['index'] = index + + # current_signals = sorted(current_signals) # Add missing buffers - for signal_name in current_signals: + for signal_name in sorted(current_signals.keys()): if signal_name not in self.signals: - self.add_databuffer(signal_name, current_signals.index(signal_name)) + signal = current_signals[signal_name]['signal'] + self.add_databuffer(signal, current_signals[signal_name]['index']) # Delete old buffers for signal_name in self.signals.copy(): if signal_name not in current_signals: - self.remove_databuffer(signal_name) + signal = self.signals[signal_name]['signal'] + self.remove_databuffer(signal) self.update_pens() self.update_legend() - def add_databuffer(self, signal_name, signal_id): + def add_databuffer(self, signal, signal_id): """ Create new buffer for signal_name. @@ -479,6 +482,8 @@ def add_databuffer(self, signal_name, signal_id): :return: """ + signal_name = signal.uname + if signal_name not in self.signals: self.signals[signal_name] = {} @@ -486,17 +491,15 @@ def add_databuffer(self, signal_name, signal_id): buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION - #legend_name = str(signal_id) + "# " + signal_name - legend_name = signal_name - - curve = self.__plotWidget__.plot([0, 1], [0, 1], name=legend_name) + curve = self.__plotWidget__.plot([0, 1], [0, 1]) self.signals[signal_name]['buffer'] = buffer self.signals[signal_name]['curve'] = curve self.signals[signal_name]['id'] = signal_id - self.signals[signal_name]['legend_name'] = legend_name + self.signals[signal_name]['signal'] = signal + - def remove_databuffer(self, signal_name): + def remove_databuffer(self, signal): """ Remove the databuffer for signal_name. @@ -504,11 +507,12 @@ def remove_databuffer(self, signal_name): :return: """ + signal_name = signal.uname + if signal_name in self.signals: curve = self.signals[signal_name]['curve'] - legend_name = self.signals[signal_name]['legend_name'] curve.clear() - #self.__legend__.removeItem(legend_name) + # self.__legend__.removeItem(legend_name) del self.signals[signal_name] def get_pen(self, index): @@ -538,20 +542,17 @@ def get_pen(self, index): return pq.mkPen(color=color, style=style) - def use_range_for_x(self,value): + def use_range_for_x(self, value): reg = re.compile(r'(\d+\.\d+)') - range =reg.findall(value) + range = reg.findall(value) if len(range) == 2: - self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]),float(range[1])) + self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]), float(range[1])) - def use_range_for_y(self,value): + def use_range_for_y(self, value): reg = re.compile(r'(\d+\.\d+)') - range =reg.findall(value) + range = reg.findall(value) if len(range) == 2: - self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]),float(range[1])) - - - + self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]), float(range[1])) def setup_context_menu(self): @@ -561,10 +562,10 @@ def setup_context_menu(self): # --------------------------------------------------------- - ##### X-Range Actions + # #### X-Range Actions self.xRange_Widget = QtGui.QWidget() self.xRange_Layout = QtGui.QVBoxLayout(self.xRange_Widget) - self.xRange_Layout.setContentsMargins(2,2,2,2) + self.xRange_Layout.setContentsMargins(2, 2, 2, 2) self.xRange_Layout.setSpacing(1) self.xRange_AutoCheckbox = QtGui.QCheckBox() @@ -578,12 +579,12 @@ def setup_context_menu(self): # Layout self.xRange_EditWidget = QtGui.QWidget() self.xRange_EditLayout = QtGui.QHBoxLayout(self.xRange_EditWidget) - self.xRange_EditLayout.setContentsMargins(2,2,2,2) + self.xRange_EditLayout.setContentsMargins(2, 2, 2, 2) self.xRange_EditLayout.setSpacing(1) # get old values; reg = re.compile(r'(\d+\.\d+)') - range =reg.findall(self.config['xRange']['value']) + range = reg.findall(self.config['xRange']['value']) if len(range) == 2: x_min = range[0] x_max = range[1] @@ -617,7 +618,7 @@ def setup_context_menu(self): ##### Y-Range Actions self.yRange_Widget = QtGui.QWidget() self.yRange_Layout = QtGui.QVBoxLayout(self.yRange_Widget) - self.yRange_Layout.setContentsMargins(2,2,2,2) + self.yRange_Layout.setContentsMargins(2, 2, 2, 2) self.yRange_Layout.setSpacing(1) self.yRange_AutoCheckbox = QtGui.QCheckBox() @@ -630,12 +631,12 @@ def setup_context_menu(self): # Layout self.yRange_EditWidget = QtGui.QWidget() self.yRange_EditLayout = QtGui.QHBoxLayout(self.yRange_EditWidget) - self.yRange_EditLayout.setContentsMargins(2,2,2,2) + self.yRange_EditLayout.setContentsMargins(2, 2, 2, 2) self.yRange_EditLayout.setSpacing(1) # get old values; reg = re.compile(r'(\d+\.\d+)') - range =reg.findall(self.config['yRange']['value']) + range = reg.findall(self.config['yRange']['value']) if len(range) == 2: y_min = range[0] y_max = range[1] @@ -667,7 +668,7 @@ def setup_context_menu(self): ##### Rolling Plot self.rolling_Checkbox = QtGui.QCheckBox() self.rolling_Checkbox.setText('Rolling plot') - self.rolling_Checkbox.setChecked(self.config['rolling_plot']['value']=='1') + self.rolling_Checkbox.setChecked(self.config['rolling_plot']['value'] == '1') self.rolling_Checkbox.stateChanged.connect(self.contextMenu_rolling_toogled) self.rolling_Checkbox_Action = QtGui.QWidgetAction(self.__plotWidget__) self.rolling_Checkbox_Action.setDefaultWidget(self.rolling_Checkbox) @@ -709,13 +710,13 @@ def setup_context_menu(self): self.custMenu.addSeparator().setText("Rolling Plot") self.custMenu.addAction(self.rolling_Checkbox_Action) self.__plotWidget__.getPlotItem().getViewBox().menu.clear() - self.__plotWidget__.getPlotItem().ctrlMenu = [ self.create_control_context_menu(), self.custMenu] + self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] def contextMenu_rolling_toogled(self): if self.rolling_Checkbox.isChecked(): - self.control_api.do_set_parameter(self.__id__,'rolling','1') + self.control_api.do_set_parameter(self.__id__, 'rolling', '1') else: - self.control_api.do_set_parameter(self.__id__,'rolling','0') + self.control_api.do_set_parameter(self.__id__, 'rolling', '0') def contextMenu_xGrid_toogle(self): if self.xGrid_Checkbox.isChecked(): @@ -737,7 +738,7 @@ def contextMenu_xRange_toogle(self): mi = self.xRange_minEdit.text() ma = self.xRange_maxEdit.text() self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '0') - self.control_api.do_set_parameter(self.__id__, 'xRange', '['+mi +' '+ma+']' ) + self.control_api.do_set_parameter(self.__id__, 'xRange', '[' + mi + ' ' + ma + ']') def contextMenu_yRange_toogle(self): if self.yRange_AutoCheckbox.isChecked(): @@ -749,20 +750,37 @@ def contextMenu_yRange_toogle(self): mi = self.yRange_minEdit.text() ma = self.yRange_maxEdit.text() self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '0') - self.control_api.do_set_parameter(self.__id__, 'yRange', '['+mi +' '+ma+']' ) + self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') + + def update_signals(self): + subscriptions = self.dplugin_info.get_subscribtions() + + for dpluginsub_id in subscriptions: + for dblock_name in subscriptions[dpluginsub_id]: + + # get subscription for dblock + subscription = subscriptions[dpluginsub_id][dblock_name] + + for signal_name in subscription.get_signals(): + signal = subscription.get_dblock().get_signal_by_uname(signal_name) + + self.signals[signal_name]['signal'] = signal - def update_legend(self): -# self.__plotWidget__.removeItem(self.__legend__) + # self.__plotWidget__.removeItem(self.__legend__) self.__legend__.scene().removeItem(self.__legend__) del self.__legend__ self.__legend__ = pq.LegendItem((100, 40), offset=(40, 1)) # args are (size, offset) self.__legend__.setParentItem(self.__plotWidget__.graphicsItem()) + self.update_signals() + for signal_name in sorted(self.signals.keys()): curve = self.signals[signal_name]['curve'] - legend_name = self.signals[signal_name]['legend_name'] + signal = self.signals[signal_name]['signal'] + legend_name = signal.dname + self.__legend__.addItem(curve, legend_name) def quit(self): From 1e36073183f4a4600aeeb7a824280b46551e90e1 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 14:17:00 +0100 Subject: [PATCH 033/260] add '.xml' to a path if necessary --- papi/gui/gui_api.py | 4 + papi/last_active_papi.xml | 236 +------------------------------------- 2 files changed, 6 insertions(+), 234 deletions(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 49e23b5d..a43ffd99 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -597,6 +597,10 @@ def config_loader_subs(self, pl_to_start, subs_to_make, parameters_to_change, si {'edit': DSignal(dsignal_uname, dsignal_dname)}) def do_save_xml_config(self, path): + + if path[-4:] != '.xml': + path += '.xml' + root = ET.Element(CONFIG_ROOT_ELEMENT_NAME) root.set('Date', datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')) root.set('PaPI_version', CORE_PAPI_VERSION) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 914fe92b..d2b12a85 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,236 +1,4 @@ - - + + - - Fourier_Rect_MOD - - - FourierXRectXMOD - - - - - - - rect1 - - - rect2 - - - rect3 - - - rect4 - - - rect5 - - - rect6 - - - rect7 - - - rect8 - - - rect9 - - - rect10 - - - rect11 - - - rect12 - - - rect13 - - - rect14 - - - rect15 - - - rect16 - - - rect17 - - - rect18 - - - rect19 - - - - - - - Add - - - Add - - - - - - - Sum - - - - - - FourierXRectXMOD - - - rect1 - rect10 - rect11 - rect12 - rect13 - rect14 - rect15 - rect16 - rect17 - rect18 - rect19 - rect2 - rect3 - rect4 - rect5 - rect6 - rect7 - rect8 - - - - - - Plot - - - ^\[(\s*\d\s*)+\] - [0 0 0 0 0] - Style - 1 - - - VisualPlugin - Used display name - - - \w+,\s*\w+ - Label-X - time, s - - - ^([1-9][0-9]{0,3}|10000)$ - 3000 - Buffersize - 1 - - - Plot - - - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - 1 - (1517,1065) - - - ^\[(\s*\d\s*)+\] - [0 1 2 3 4] - Color - 1 - - - xRange-auto - ^(1|0)$ - bool - 1 - - - Rolling Plot - ^(1|0)$ - bool - 1 - - - Grid-X - ^(1|0)$ - bool - 0 - - - (\d+\.\d+) - yRange-auto - [0,1] - - - \w+,\s+\w+ - Label-Y - amplitude, V - - - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - 1 - (0,0) - - - (\d+\.\d+) - xRange-auto - [0,1] - - - Grid-Y - ^(1|0)$ - bool - 0 - - - (\d+) - 1 - - - yRange-auto - ^(1|0)$ - bool - 1 - - - - [0 1 2 3 4] - 3000 - 1 - 0 - [0,1] - 0 - [0 0 0 0 0] - [0,1] - 1 - 5 - 1 - - - - - Add - - - Sum - - - - From 878ced729c7355bebf6390e85c6a472428f37b8f Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 14:38:17 +0100 Subject: [PATCH 034/260] implemented better representation in case of plugin runtime error --- papi/gui/gui_api.py | 151 +++++++++++++++--------------- papi/gui/qt_new/main.py | 20 +++- papi/last_active_papi.xml | 156 ++++++++++++++++++++++++++++++- papi/plugin/pcp/button/Button.py | 1 - 4 files changed, 249 insertions(+), 79 deletions(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index a43ffd99..a1086331 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -27,6 +27,10 @@ """ __author__ = 'stefan' +import datetime +import time +import traceback + import papi.event as Event from papi.data.DOptionalData import DOptionalData @@ -41,16 +45,13 @@ import papi.error_codes as ERROR -import datetime -import time - import xml.etree.cElementTree as ET class Gui_api(QtCore.QObject): resize_gui = QtCore.Signal(int, int) set_bg_gui = QtCore.Signal(str) - + error_occured = QtCore.Signal(str, str, str) def __init__(self, gui_data, core_queue, gui_id, LOG_IDENT=GUI_PROCESS_CONSOLE_IDENTIFIER): super(Gui_api, self).__init__() @@ -483,77 +484,81 @@ def do_load_xml(self, path): parameters_to_change = [] signals_to_change = [] - for plugin_xml in root: - if plugin_xml.tag == 'Size': - w = int(plugin_xml.attrib['w']) - h = int(plugin_xml.attrib['h']) - self.resize_gui.emit(w, h) - else: - if plugin_xml.tag == 'Background': - path = str(plugin_xml.attrib['image']) - if path != '' and path is not None and path != 'default': - self.set_bg_gui.emit(path) + try: + for plugin_xml in root: + if plugin_xml.tag == 'Size': + w = int(plugin_xml.attrib['w']) + h = int(plugin_xml.attrib['h']) + self.resize_gui.emit(w, h) else: - pl_uname = plugin_xml.attrib['uname'] - identifier = plugin_xml.find('Identifier').text - config_xml = plugin_xml.find('StartConfig') - config_hash = {} - for parameter_xml in config_xml.findall('Parameter'): - para_name = parameter_xml.attrib['Name'] - config_hash[para_name] = {} - for detail_xml in parameter_xml: - detail_name = detail_xml.tag - config_hash[para_name][detail_name] = detail_xml.text - - pl_uname_new = self.change_uname_to_uniqe(pl_uname) - - plugins_to_start.append([identifier, pl_uname_new, config_hash]) - - # -------------------------------- - # Load Subscriptions - # -------------------------------- - - subs_xml = plugin_xml.find('Subscriptions') - if subs_xml is not None: - for sub_xml in subs_xml.findall('Subscription'): - data_source = sub_xml.find('data_source').text - for block_xml in sub_xml.findall('block'): - block_name = block_xml.attrib['Name'] - signals = [] - for sig_xml in block_xml.findall('Signal'): - signals.append(str(sig_xml.text)) - alias_xml = block_xml.find('alias') - alias = alias_xml.text + if plugin_xml.tag == 'Background': + path = str(plugin_xml.attrib['image']) + if path != '' and path is not None and path != 'default': + self.set_bg_gui.emit(path) + else: + pl_uname = plugin_xml.attrib['uname'] + identifier = plugin_xml.find('Identifier').text + config_xml = plugin_xml.find('StartConfig') + config_hash = {} + for parameter_xml in config_xml.findall('Parameter'): + para_name = parameter_xml.attrib['Name'] + config_hash[para_name] = {} + for detail_xml in parameter_xml: + detail_name = detail_xml.tag + config_hash[para_name][detail_name] = detail_xml.text + + pl_uname_new = self.change_uname_to_uniqe(pl_uname) + + plugins_to_start.append([identifier, pl_uname_new, config_hash]) + + # -------------------------------- + # Load Subscriptions + # -------------------------------- + + subs_xml = plugin_xml.find('Subscriptions') + if subs_xml is not None: + for sub_xml in subs_xml.findall('Subscription'): + data_source = sub_xml.find('data_source').text + for block_xml in sub_xml.findall('block'): + block_name = block_xml.attrib['Name'] + signals = [] + for sig_xml in block_xml.findall('Signal'): + signals.append(str(sig_xml.text)) + alias_xml = block_xml.find('alias') + alias = alias_xml.text + pl_uname_new = self.change_uname_to_uniqe(pl_uname) + data_source_new = self.change_uname_to_uniqe(data_source) + subs_to_make.append([pl_uname_new, data_source_new, block_name, signals, alias]) + + # -------------------------------- + # Load PreviousParameters + # -------------------------------- + + prev_parameters_xml = plugin_xml.find('PreviousParameters') + if prev_parameters_xml is not None: + for prev_parameter_xml in prev_parameters_xml.findall('Parameter'): + para_name = prev_parameter_xml.attrib['Name'] + para_value = prev_parameter_xml.text pl_uname_new = self.change_uname_to_uniqe(pl_uname) - data_source_new = self.change_uname_to_uniqe(data_source) - subs_to_make.append([pl_uname_new, data_source_new, block_name, signals, alias]) - - # -------------------------------- - # Load PreviousParameters - # -------------------------------- - - prev_parameters_xml = plugin_xml.find('PreviousParameters') - if prev_parameters_xml is not None: - for prev_parameter_xml in prev_parameters_xml.findall('Parameter'): - para_name = prev_parameter_xml.attrib['Name'] - para_value = prev_parameter_xml.text - pl_uname_new = self.change_uname_to_uniqe(pl_uname) - # TODO validate NO FLOAT in parameter - parameters_to_change.append([pl_uname_new, para_name, para_value]) - - # -------------------------------- - # Load DBlocks due to signals name - # -------------------------------- - - dblocks_xml = plugin_xml.find('DBlocks') - if dblocks_xml is not None: - for dblock_xml in dblocks_xml: - dblock_name = dblock_xml.attrib['Name'] - dsignals_xml = dblock_xml.findall('DSignal') - for dsignal_xml in dsignals_xml: - dsignal_uname = dsignal_xml.attrib['uname'] - dsignal_dname = dsignal_xml.find('dname').text - signals_to_change.append([pl_uname, dblock_name, dsignal_uname, dsignal_dname]) + # TODO validate NO FLOAT in parameter + parameters_to_change.append([pl_uname_new, para_name, para_value]) + + # -------------------------------- + # Load DBlocks due to signals name + # -------------------------------- + + dblocks_xml = plugin_xml.find('DBlocks') + if dblocks_xml is not None: + for dblock_xml in dblocks_xml: + dblock_name = dblock_xml.attrib['Name'] + dsignals_xml = dblock_xml.findall('DSignal') + for dsignal_xml in dsignals_xml: + dsignal_uname = dsignal_xml.attrib['uname'] + dsignal_dname = dsignal_xml.find('dname').text + signals_to_change.append([pl_uname, dblock_name, dsignal_uname, dsignal_dname]) + except Exception as E: + tb = traceback.format_exc() + self.error_occured.emit("Error: Config Loader", "Not loadable: " + path, tb) for pl in plugins_to_start: # 0: ident, 1: uname, 2: config diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index 72494ad0..e69330f7 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -81,6 +81,8 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): self.gui_event_processing.dgui_changed.connect(self.changed_dgui) self.gui_event_processing.plugin_died.connect(self.plugin_died) + self.gui_api.error_occured.connect(self.error_occured) + self.gui_api.resize_gui.connect(self.resize_gui_window) self.setWindowTitle(GUI_PAPI_WINDOW_TITLE) @@ -340,15 +342,25 @@ def changed_dgui(self): self.overview_menu.refresh_action() def plugin_died(self, dplugin, e, msg): - dplugin.state = PLUGIN_STATE_PAUSE self.gui_api.do_stopReset_plugin_uname(dplugin.uname) - errMsg = QtGui.QErrorMessage(self) + errMsg = QtGui.QMessageBox() + errMsg.setFixedWidth(650) + errMsg.setWindowTitle("Plugin: " + dplugin.uname + " // " + str(e)) + errMsg.setText("Error in plugin" + dplugin.uname + " // " + str(e)) + errMsg.setDetailedText(str(msg)) + errMsg.exec_() + + def error_occured(self, title, msg, detailed_msg): + + errMsg = QtGui.QMessageBox() errMsg.setFixedWidth(650) - errMsg.setWindowTitle("Error in" + dplugin.uname + " // " + str(e)) - errMsg.showMessage(str(msg)) + errMsg.setWindowTitle(title) + errMsg.setText(str(msg)) + errMsg.setDetailedText(str(detailed_msg)) + errMsg.exec_() def toggle_run_mode(self): if self.in_run_mode: diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index d2b12a85..a6b8824c 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,4 +1,158 @@ - + + + Plot + + + [0 1 2 3 4] + 1 + ^\[(\s*\d\s*)+\] + Color + + + 0 + ^(1|0)$ + Grid-Y + bool + + + amplitude, V + \w+,\s+\w+ + Label-Y + + + [0 0 0 0 0] + 1 + ^\[(\s*\d\s*)+\] + Style + + + VisualPlugin + Used display name + + + (300,300) + \(([0-9]+),([0-9]+)\) + 1 + Determine size: (height,width) + + + 1000 + 1 + ^([1-9][0-9]{0,3}|10000)$ + Buffersize + + + time, s + \w+,\s*\w+ + Label-X + + + 1 + (\d+) + + + 1 + ^(1|0)$ + yRange-auto + bool + + + [0,1] + (\d+\.\d+) + xRange-auto + + + [0,1] + (\d+\.\d+) + yRange-auto + + + 0 + ^(1|0)$ + Grid-X + bool + + + 0 + ^(1|0)$ + Rolling Plot + bool + + + Plot + + + (0,0) + \(([0-9]+),([0-9]+)\) + 1 + Determine position: (x,y) + + + 1 + ^(1|0)$ + xRange-auto + bool + + + + 1 + 1 + [0 1 2 3 4] + 0 + [0 0 0 0 0] + [0,1] + 0 + [0,1] + 1000 + 0 + 1 + + + + + + Sinus + + + Sinus + + + 1 + \d+.{0,1}\d* + + + 3 + [0-9]+ + + + + 0.6 + + + + + f2_1 + + + + + f1_f1DNAME + + + + + f3_harhar + + + f3_dudu + + + f3_quark + + + + + diff --git a/papi/plugin/pcp/button/Button.py b/papi/plugin/pcp/button/Button.py index 7cb91ad5..65efe10e 100644 --- a/papi/plugin/pcp/button/Button.py +++ b/papi/plugin/pcp/button/Button.py @@ -58,7 +58,6 @@ def create_widget(self): """ button = QPushButton('Click') button.clicked.connect(self.clicked) - return button def clicked(self): From e63e6e8943efe2078850ab5d4644b321e5050d71 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 15:14:31 +0100 Subject: [PATCH 035/260] added error message for the case: error raise due to call of plugin_meta_update --- papi/constants.py | 1 - papi/gui/gui_event_processing.py | 13 ++- papi/gui/qt_new/main.py | 4 +- papi/last_active_papi.xml | 160 +++++++++++++++---------------- papi/plugin/visual/Plot/Plot.py | 1 - 5 files changed, 93 insertions(+), 86 deletions(-) diff --git a/papi/constants.py b/papi/constants.py index fddcba52..510b25ba 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -86,7 +86,6 @@ PLUGIN_STATE_ADDED = 'added' PLUGIN_STATE_STOPPED = 'stopped' - # PLUGIN_API_CONSOLE_IDENTIFIER = 'Plugin API: ' PLUGIN_API_CONSOLE_LOG_LEVEL = 1 diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 70cd636d..41f7839a 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -284,9 +284,11 @@ def process_create_plugin(self, event): self.core_queue.put( Event.status.StartFailed(dplugin.id, 0, None)) # first set meta to plugin (meta infos in plugin) - dplugin.plugin.update_plugin_meta(dplugin.get_meta()) + if dplugin.state not in [PLUGIN_STATE_STOPPED]: + dplugin.plugin.update_plugin_meta(dplugin.get_meta()) except Exception as E: + dplugin.state = PLUGIN_STATE_STOPPED tb = traceback.format_exc() self.plugin_died.emit(dplugin, E, tb) @@ -351,7 +353,14 @@ def process_update_meta(self, event): dplugin.update_meta(opt.plugin_object) # check if plugin runs in gui to update its copy of meta informations if dplugin.own_process is False: - dplugin.plugin.update_plugin_meta(dplugin.get_meta()) + print('STATE ' + str(dplugin.state)) + if dplugin.state not in [PLUGIN_STATE_STOPPED]: + try: + dplugin.plugin.update_plugin_meta(dplugin.get_meta()) + except Exception as E: + dplugin.state = PLUGIN_STATE_STOPPED + tb = traceback.format_exc() + #self.plugin_died.emit(dplugin, E, tb) self.dgui_changed.emit() else: diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index e69330f7..f7c2968c 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -43,7 +43,7 @@ from papi.constants import GUI_PAPI_WINDOW_TITLE, GUI_WOKRING_INTERVAL, GUI_PROCESS_CONSOLE_IDENTIFIER, \ GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_START_CONSOLE_MESSAGE, GUI_WAIT_TILL_RELOAD, GUI_DEFAULT_HEIGHT, GUI_DEFAULT_WIDTH, \ - PLUGIN_STATE_PAUSE + PLUGIN_STATE_PAUSE, PLUGIN_STATE_STOPPED from papi.constants import CONFIG_DEFAULT_FILE, PLUGIN_VIP_IDENTIFIER, PLUGIN_PCP_IDENTIFIER, CONFIG_DEFAULT_DIRECTORY @@ -342,7 +342,7 @@ def changed_dgui(self): self.overview_menu.refresh_action() def plugin_died(self, dplugin, e, msg): - dplugin.state = PLUGIN_STATE_PAUSE + dplugin.state = PLUGIN_STATE_STOPPED self.gui_api.do_stopReset_plugin_uname(dplugin.uname) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index a6b8824c..d0c95a8e 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,113 +1,113 @@ - + Plot - - [0 1 2 3 4] - 1 - ^\[(\s*\d\s*)+\] - Color - - - 0 - ^(1|0)$ - Grid-Y - bool - - - amplitude, V - \w+,\s+\w+ - Label-Y - - - [0 0 0 0 0] + + \(([0-9]+),([0-9]+)\) + (0,0) 1 - ^\[(\s*\d\s*)+\] - Style - - - VisualPlugin - Used display name + Determine position: (x,y) - (300,300) \(([0-9]+),([0-9]+)\) + (300,300) 1 Determine size: (height,width) - - 1000 - 1 - ^([1-9][0-9]{0,3}|10000)$ - Buffersize - time, s \w+,\s*\w+ Label-X - - 1 - (\d+) + + 0 + bool + ^(1|0)$ + Grid-Y 1 + bool ^(1|0)$ yRange-auto + + + 0 bool + ^(1|0)$ + Grid-X + + + 1 + bool + ^(1|0)$ + xRange-auto - [0,1] + [0.0 1.0] (\d+\.\d+) xRange-auto - - [0,1] - (\d+\.\d+) - yRange-auto + + ^\[(\s*\d\s*)+\] + [0 0 0 0 0] + 1 + Style - - 0 - ^(1|0)$ - Grid-X - bool + + Plot 0 + bool ^(1|0)$ Rolling Plot - bool - - Plot + + 1 + (\d+) - - (0,0) - \(([0-9]+),([0-9]+)\) + + amplitude, V + \w+,\s+\w+ + Label-Y + + + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] 1 - Determine position: (x,y) + Color - - 1 - ^(1|0)$ - xRange-auto - bool + + ^([1-9][0-9]{0,3}|10000)$ + 1000 + 1 + Buffersize + + + VisualPlugin + Used display name + + + [0.0 1.0] + (\d+\.\d+) + yRange-auto - 1 - 1 [0 1 2 3 4] - 0 [0 0 0 0 0] - [0,1] + 0 + 1 0 - [0,1] 1000 0 + 1 1 + [0,1] + [0,1] @@ -115,25 +115,31 @@ Sinus - - Sinus + + 3 + [0-9]+ 1 \d+.{0,1}\d* - - 3 - [0-9]+ + + Sinus 0.6 - - - f2_1 + + + f3_1 + + + f3_2 + + + f3_scalar @@ -141,15 +147,9 @@ f1_f1DNAME - - - f3_harhar - - - f3_dudu - - - f3_quark + + + f2_1 diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 0b8c0cf3..bfa3d119 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -462,7 +462,6 @@ def plugin_meta_updated(self): :return: """ - subscriptions = self.dplugin_info.get_subscribtions() current_signals = {} From 6001c852bb79cd1447ca8bfd95386984c44ee02b Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 15:15:29 +0100 Subject: [PATCH 036/260] removed print --- papi/gui/gui_event_processing.py | 1 - papi/last_active_papi.xml | 174 ++++++++++++++++--------------- 2 files changed, 92 insertions(+), 83 deletions(-) diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 41f7839a..80e59feb 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -353,7 +353,6 @@ def process_update_meta(self, event): dplugin.update_meta(opt.plugin_object) # check if plugin runs in gui to update its copy of meta informations if dplugin.own_process is False: - print('STATE ' + str(dplugin.state)) if dplugin.state not in [PLUGIN_STATE_STOPPED]: try: dplugin.plugin.update_plugin_meta(dplugin.get_meta()) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index d0c95a8e..283bfc2c 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,128 +1,138 @@ - + Plot - - \(([0-9]+),([0-9]+)\) - (0,0) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) + Determine size: (height,width) (300,300) 1 - Determine size: (height,width) - - time, s - \w+,\s*\w+ - Label-X + + xRange-auto + (\d+\.\d+) + [0,1] - - 0 - bool - ^(1|0)$ - Grid-Y + + Style + [0 0 0 0 0] + 1 + ^\[(\s*\d\s*)+\] - - 1 - bool - ^(1|0)$ + + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + (0,0) + 1 + + yRange-auto + (\d+\.\d+) + [0,1] - - 0 - bool + + yRange-auto ^(1|0)$ - Grid-X - - 1 bool - ^(1|0)$ - xRange-auto - - [0.0 1.0] - (\d+\.\d+) - xRange-auto + + Label-X + \w+,\s*\w+ + time, s - - ^\[(\s*\d\s*)+\] - [0 0 0 0 0] - 1 - Style + + (\d+) + 1 Plot - - 0 - bool + + xRange-auto ^(1|0)$ - Rolling Plot - - 1 - (\d+) + bool - - amplitude, V - \w+,\s+\w+ - Label-Y + + Buffersize + 1000 + 1 + ^([1-9][0-9]{0,3}|10000)$ + + + Grid-X + ^(1|0)$ + 0 + bool - ^\[(\s*\d\s*)+\] + Color [0 1 2 3 4] 1 - Color + ^\[(\s*\d\s*)+\] - - ^([1-9][0-9]{0,3}|10000)$ - 1000 - 1 - Buffersize + + Grid-Y + ^(1|0)$ + 0 + bool + + + Rolling Plot + ^(1|0)$ + 0 + bool + + + Label-Y + \w+,\s+\w+ + amplitude, V - VisualPlugin Used display name - - - [0.0 1.0] - (\d+\.\d+) - yRange-auto + VisualPlugin [0 1 2 3 4] + 1 + 0 + 0 [0 0 0 0 0] 0 - 1 - 0 - 1000 - 0 - 1 - 1 - [0,1] [0,1] + [0,1] + 1 + 1000 + 1 - + + + Sinus + + + f3_1 + f3_2 + f3_scalar + + + Sinus - - 3 - [0-9]+ - 1 \d+.{0,1}\d* + + 3 + [0-9]+ + Sinus @@ -131,20 +141,20 @@ 0.6 + + + f1_f1DNAME + + - f3_1 + f3_harhar - f3_2 + f3_dudu - f3_scalar - - - - - f1_f1DNAME + f3_quark From 2101af2f11c299214bbf7030ba428e7ad35bed0c Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 15:33:08 +0100 Subject: [PATCH 037/260] added new json file and ORTDcontroller plugin some minor fixes in template and base files --- papi/gui/gui_event_processing.py | 5 +- papi/gui/qt_new/overview_menu.py | 4 +- papi/last_active_papi.xml | 273 ++++------------- papi/plugin/base_classes/base_plugin.py | 2 +- papi/plugin/base_classes/base_visual.py | 5 +- .../DataSourceExample/ProtocollConfig.json | 65 +++- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 95 +++++- papi/plugin/templates/visual_template.py | 8 +- .../visual/OrtdController/OrtdController.py | 289 ++++++++++++++++++ .../OrtdController.yapsy-plugin | 9 + 10 files changed, 525 insertions(+), 230 deletions(-) create mode 100644 papi/plugin/visual/OrtdController/OrtdController.py create mode 100644 papi/plugin/visual/OrtdController/OrtdController.yapsy-plugin diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 70cd636d..7ed0c828 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -143,7 +143,8 @@ def process_new_data_event(self, event): # check if new_data is a parameter or new raw data try: if opt.is_parameter is False: - dplugin.plugin.execute(dplugin.plugin.demux(opt.data_source_id, opt.block_name, opt.data)) + dplugin.plugin.execute(Data=dplugin.plugin.demux(opt.data_source_id, opt.block_name, opt.data), + block_name=opt.block_name) else: dplugin.plugin.set_parameter_internal(opt.parameter_alias, opt.data) except Exception as E: @@ -276,7 +277,7 @@ def process_create_plugin(self, event): # call the plugin developers init function with config try: - dplugin.plugin.init_plugin(self.core_queue, self.gui_queue, dplugin.id, api) + dplugin.plugin.init_plugin(self.core_queue, self.gui_queue, dplugin.id, api, dpluginInfo=dplugin.get_meta()) if dplugin.plugin.start_init(copy.deepcopy(config)) is True: #start succcessfull self.core_queue.put( Event.status.StartSuccessfull(dplugin.id, 0, None)) diff --git a/papi/gui/qt_new/overview_menu.py b/papi/gui/qt_new/overview_menu.py index 764d5a72..62360022 100644 --- a/papi/gui/qt_new/overview_menu.py +++ b/papi/gui/qt_new/overview_menu.py @@ -534,15 +534,15 @@ def add_pcp_subscription_action(self, dplugin: DPlugin, dparameter: DParameter, dblock_pcp: DBlock): """ This function is used to create a subscription for a process control plugin. + :param dplugin: Subscriber of a pcp plugin :param dparameter: Parameter of the subscriber which should be controlled by the pcp plugin. :param dplugin_pcp: The pcp plugin :param dblock_pcp: Block of the pcp plugin which is used to control the subscriber's parameter. :return: """ - self.gui_api.do_subscribe(dplugin.id, dplugin_pcp.id, dblock_pcp.name, [], dparameter.name) - pass + def add_subscription_action(self, dplugin_uname): """ diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 914fe92b..e245517d 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,236 +1,97 @@ - - + + - - Fourier_Rect_MOD + + OrtdController - - FourierXRectXMOD + + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + (0,0) + 1 + + + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + (300,300) + 1 + + + Used display name + VisualPlugin - - - - - - rect1 - - - rect2 - - - rect3 - - - rect4 - - - rect5 - - - rect6 - - - rect7 - - - rect8 - - - rect9 - - - rect10 - - - rect11 - - - rect12 - - - rect13 - - - rect14 - - - rect15 - - - rect16 - - - rect17 - - - rect18 - - - rect19 - - - - - - - Add - - Add + OrtdController - - - - Sum - - - + - FourierXRectXMOD - + ORTDPlugin1 + - rect1 - rect10 - rect11 - rect12 - rect13 - rect14 - rect15 - rect16 - rect17 - rect18 - rect19 - rect2 - rect3 - rect4 - rect5 - rect6 - rect7 - rect8 + ControlSignalCreate + ControlSignalSub + ControllerSignalParameter - - Plot + + ORTD_UDP - - ^\[(\s*\d\s*)+\] - [0 0 0 0 0] - Style + + 20001 1 - - VisualPlugin - Used display name - - - \w+,\s*\w+ - Label-X - time, s - - - ^([1-9][0-9]{0,3}|10000)$ - 3000 - Buffersize + + 127.0.0.1 1 - - Plot - - - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - 1 - (1517,1065) - - - ^\[(\s*\d\s*)+\] - [0 1 2 3 4] - Color - 1 - - - xRange-auto - ^(1|0)$ - bool - 1 - - - Rolling Plot - ^(1|0)$ - bool - 1 - - - Grid-X - ^(1|0)$ - bool - 0 - - - (\d+\.\d+) - yRange-auto - [0,1] - - - \w+,\s+\w+ - Label-Y - amplitude, V - - - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) + + 20000 1 - (0,0) - - - (\d+\.\d+) - xRange-auto - [0,1] - - - Grid-Y - ^(1|0)$ - bool - 0 - - - (\d+) - 1 - - - yRange-auto - ^(1|0)$ - bool - 1 - [0 1 2 3 4] - 3000 - 1 - 0 - [0,1] - 0 - [0 0 0 0 0] - [0,1] - 1 - 5 - 1 + 0 + 0 + 0 + 0 - + + + + X + + + V + + + + + ControlSignalCreate + + + ControlSignalSub + + + ControllerSignalParameter + + + - Add - - - Sum + Butt1 + + Oscillator input - + + Button + + + \ No newline at end of file diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py index 564321c2..ba6d4c8a 100644 --- a/papi/plugin/base_classes/base_plugin.py +++ b/papi/plugin/base_classes/base_plugin.py @@ -36,7 +36,7 @@ class base_plugin(IPlugin): - def papi_init(self): + def papi_init(self, ): #self.__dplugin_ids__ = {} Not sure where needed TODO self.dplugin_info = None diff --git a/papi/plugin/base_classes/base_visual.py b/papi/plugin/base_classes/base_visual.py index 3ca37e85..fc6e7c5e 100644 --- a/papi/plugin/base_classes/base_visual.py +++ b/papi/plugin/base_classes/base_visual.py @@ -34,12 +34,13 @@ class base_visual(base_plugin): - def init_plugin(self, CoreQueue, pluginQueue, id, control_api): + def init_plugin(self, CoreQueue, pluginQueue, id, control_api, dpluginInfo = None): + super(base_visual, self).papi_init() self._Core_event_queue__ = CoreQueue self.__plugin_queue__ = pluginQueue self.__id__ = id self.control_api = control_api - super(base_visual, self).papi_init() + self.dplugin_info = dpluginInfo def start_init(self, config=None): diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json b/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json index 1d79619e..410ea98c 100644 --- a/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json @@ -1,5 +1,62 @@ - {"SourcesConfig" : { - "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257" } , "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257" } } , - "ParametersConfig" : { - "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } } + { +"SourcesConfig" : { + "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257" } , + "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257" } } , +"ParametersConfig" : { + "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , + "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , + "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } }, +"PaPIConfig": { + "ToCreate": { + "Plot1":{ + "identifier": { + "value": "Plot" + }, + "config": { + "x-grid": { + "value": "0" + }, + "size": { + "value": "(300,300)" + }, + "position": { + "value": "(300,0)" + }, + "name": { + "value": "TestPlot" + } + } + }, + "Butt1":{ + "identifier": { + "value": "Button" + }, + "config": { + "size": { + "value": "(150,50)" + }, + "position": { + "value": "(600,0)" + }, + "name": { + "value": "Disturbance" + } + + } + } + }, + + "ToSub": { + "Plot1": { + "block": "SourceGroup0", + "signals": ["V"] + } + }, + "ToControl": { + "Butt1": { + "block": "Click_Event", + "parameter" : "Oscillator input" + } + } } + } \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 22acb2c1..1fee5609 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -152,10 +152,15 @@ def start_init(self, config=None): - #self.block1 = DBlock(None, 1, 2, 'SourceGroup0', names) - self.send_new_block_list([self.block1]) + self.ControlBlock = DBlock('ControllerSignals') + self.ControlBlock.add_signal(DSignal('ControlSignalCreate')) + self.ControlBlock.add_signal(DSignal('ControlSignalSub')) + self.ControlBlock.add_signal(DSignal('ControllerSignalParameter')) + + #self.block1 = DBlock(None, 1, 2, 'SourceGroup0', names) + self.send_new_block_list([self.block1, self.ControlBlock]) # Register parameters self.Parameter_List = [] @@ -168,6 +173,9 @@ def start_init(self, config=None): Parameter = DParameter('', para_name, 0, 0, OptionalObject=opt_object) self.Parameter_List.append(Parameter) + self.ControlParameter = DParameter('','triggerConfiguration',0,0) + self.Parameter_List.append(self.ControlParameter) + self.send_new_parameter_list(self.Parameter_List) self.t = 0 @@ -267,12 +275,16 @@ def execute(self, Data=None, block_name=None): raise Exception('Should not be called!') def set_parameter(self, name, value): - for para in self.Parameter_List: - if para.name == name: - Pid = para.OptionalObject.ORTD_par_id - Counter = 111 - data = struct.pack('. + +Contributors: + Date: Mon, 12 Jan 2015 15:37:32 +0100 Subject: [PATCH 038/260] some changes due to new way to call error messages --- papi/gui/qt_new/main.py | 12 ++-- papi/last_active_papi.xml | 138 +++++++++++++++++++------------------- 2 files changed, 77 insertions(+), 73 deletions(-) diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index f7c2968c..c60f0d84 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -346,21 +346,25 @@ def plugin_died(self, dplugin, e, msg): self.gui_api.do_stopReset_plugin_uname(dplugin.uname) - errMsg = QtGui.QMessageBox() + errMsg = QtGui.QMessageBox(self) errMsg.setFixedWidth(650) errMsg.setWindowTitle("Plugin: " + dplugin.uname + " // " + str(e)) errMsg.setText("Error in plugin" + dplugin.uname + " // " + str(e)) errMsg.setDetailedText(str(msg)) - errMsg.exec_() + errMsg.setWindowModality(Qt.NonModal) + errMsg.show() def error_occured(self, title, msg, detailed_msg): - errMsg = QtGui.QMessageBox() + errMsg = QtGui.QMessageBox(self) errMsg.setFixedWidth(650) errMsg.setWindowTitle(title) errMsg.setText(str(msg)) errMsg.setDetailedText(str(detailed_msg)) - errMsg.exec_() + errMsg.setWindowModality(Qt.NonModal) + errMsg.show() +# errMsg.setSizePolicy(Qt.Polic) + def toggle_run_mode(self): if self.in_run_mode: diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 283bfc2c..483d862f 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,113 +1,113 @@ - + Plot - - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - (300,300) - 1 - - xRange-auto (\d+\.\d+) + xRange-auto [0,1] - - Style - [0 0 0 0 0] + + ^([1-9][0-9]{0,3}|10000)$ + Buffersize + 1000 1 - ^\[(\s*\d\s*)+\] + + + (\d+) + 1 \(([0-9]+),([0-9]+)\) - Determine position: (x,y) (0,0) + Determine position: (x,y) 1 - - yRange-auto - (\d+\.\d+) - [0,1] - - yRange-auto ^(1|0)$ - 1 + yRange-auto bool + 1 - - Label-X - \w+,\s*\w+ - time, s + + ^(1|0)$ + Rolling Plot + bool + 0 - - (\d+) - 1 + + (\d+\.\d+) + yRange-auto + [0,1] Plot - - xRange-auto - ^(1|0)$ - 1 - bool - - - Buffersize - 1000 + + \(([0-9]+),([0-9]+)\) + (300,300) + Determine size: (height,width) 1 - ^([1-9][0-9]{0,3}|10000)$ + + + Used display name + VisualPlugin - Grid-X ^(1|0)$ - 0 + Grid-X bool + 0 + + + ^\[(\s*\d\s*)+\] + Style + [0 0 0 0 0] + 1 + + + \w+,\s*\w+ + Label-X + time, s + + + \w+,\s+\w+ + Label-Y + amplitude, V + ^\[(\s*\d\s*)+\] Color [0 1 2 3 4] 1 - ^\[(\s*\d\s*)+\] - - Grid-Y + ^(1|0)$ - 0 + xRange-auto bool + 1 - - Rolling Plot + ^(1|0)$ - 0 + Grid-Y bool - - - Label-Y - \w+,\s+\w+ - amplitude, V - - - Used display name - VisualPlugin + 0 - [0 1 2 3 4] - 1 0 0 + 1000 [0 0 0 0 0] - 0 [0,1] - [0,1] - 1 - 1000 1 + 1 + [0 1 2 3 4] + [0,1] + 1 + 0 @@ -126,12 +126,12 @@ Sinus - 1 \d+.{0,1}\d* + 1 - 3 [0-9]+ + 3 Sinus @@ -141,9 +141,9 @@ 0.6 - - - f1_f1DNAME + + + f2_1 @@ -157,9 +157,9 @@ f3_quark - - - f2_1 + + + f1_f1DNAME From e155b45fd1378224dba50ec392bce8c50ae67d15 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 15:38:38 +0100 Subject: [PATCH 039/260] no --- papi/last_active_papi.xml | 166 +------------------------------------- 1 file changed, 1 insertion(+), 165 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 483d862f..6a91ae78 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,168 +1,4 @@ - + - - Plot - - - (\d+\.\d+) - xRange-auto - [0,1] - - - ^([1-9][0-9]{0,3}|10000)$ - Buffersize - 1000 - 1 - - - (\d+) - 1 - - - \(([0-9]+),([0-9]+)\) - (0,0) - Determine position: (x,y) - 1 - - - ^(1|0)$ - yRange-auto - bool - 1 - - - ^(1|0)$ - Rolling Plot - bool - 0 - - - (\d+\.\d+) - yRange-auto - [0,1] - - - Plot - - - \(([0-9]+),([0-9]+)\) - (300,300) - Determine size: (height,width) - 1 - - - Used display name - VisualPlugin - - - ^(1|0)$ - Grid-X - bool - 0 - - - ^\[(\s*\d\s*)+\] - Style - [0 0 0 0 0] - 1 - - - \w+,\s*\w+ - Label-X - time, s - - - \w+,\s+\w+ - Label-Y - amplitude, V - - - ^\[(\s*\d\s*)+\] - Color - [0 1 2 3 4] - 1 - - - ^(1|0)$ - xRange-auto - bool - 1 - - - ^(1|0)$ - Grid-Y - bool - 0 - - - - 0 - 0 - 1000 - [0 0 0 0 0] - [0,1] - 1 - 1 - [0 1 2 3 4] - [0,1] - 1 - 0 - - - - - Sinus - - - f3_1 - f3_2 - f3_scalar - - - - - - Sinus - - - \d+.{0,1}\d* - 1 - - - [0-9]+ - 3 - - - Sinus - - - - 0.6 - - - - - f2_1 - - - - - f3_harhar - - - f3_dudu - - - f3_quark - - - - - f1_f1DNAME - - - - - From 77f1275953549ac19a40ed2a5b65cc486a482762 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 15:54:20 +0100 Subject: [PATCH 040/260] added new json file and ORTDcontroller plugin some minor fixes in template and base files --- papi/last_active_papi.xml | 96 ++++--------------- papi/plugin/base_classes/base_visual.py | 2 +- .../visual/OrtdController/OrtdController.py | 13 ++- 3 files changed, 29 insertions(+), 82 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index e245517d..ed5e0303 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,97 +1,35 @@ - + OrtdController - - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - (0,0) - 1 - - Determine size: (height,width) + 1 \(([0-9]+),([0-9]+)\) (300,300) - 1 - - - Used display name - VisualPlugin + Determine size: (height,width) OrtdController - - - - - - ORTDPlugin1 - - - ControlSignalCreate - ControlSignalSub - ControllerSignalParameter - - - - - - ORTD_UDP - - - 20001 - 1 + + ORTDController - - 127.0.0.1 - 1 + + 0 + ORTDPlugin1 + Uname to use for ortd plugin instance - - 20000 + 1 + \(([0-9]+),([0-9]+)\) + (0,0) + Determine position: (x,y) - - 0 - 0 - 0 - 0 - - - - - X - - - V - - - - - ControlSignalCreate - - - ControlSignalSub - - - ControllerSignalParameter - - - - - - Butt1 - - Oscillator input - - - + + + - - Button - - - \ No newline at end of file + diff --git a/papi/plugin/base_classes/base_visual.py b/papi/plugin/base_classes/base_visual.py index fc6e7c5e..1281f542 100644 --- a/papi/plugin/base_classes/base_visual.py +++ b/papi/plugin/base_classes/base_visual.py @@ -80,7 +80,7 @@ def get_configuration_base(self): }, 'name': { 'value': 'VisualPlugin', - 'tooltip': 'Used display name' + 'tooltip': 'Used for window title' }} return config diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index 18c39a08..43854967 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -165,7 +165,7 @@ def initiate_layer_0(self, config=None): # --------------------------- # Read configuration # --------------------------- - self.ortd_uname = 'ORTDPlugin1' + self.ortd_uname = config['ORTD_Plugin_uname']['value'] self.alreadyConfigured = False # -------------------------------- # Create Widget @@ -275,7 +275,16 @@ def get_plugin_configuration(self): # 'value': "1", # 'regex': '\d+.{0,1}\d*' # }} - config = {} + config = { + "ORTD_Plugin_uname": { + 'value': 'ORTDPlugin1', + 'display_text': 'Uname to use for ortd plugin instance', + 'advanced': "0" + }, + 'name': { + 'value': 'ORTDController' + } + } return config def plugin_meta_updated(self): From 4e35202aa4d0218c17d9fd7954899d4e4a0ac0a8 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 16:03:06 +0100 Subject: [PATCH 041/260] added ortd delete ability --- papi/last_active_papi.xml | 201 ++++++++++++++++-- .../DataSourceExample/ProtocollConfig.json | 20 ++ papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 12 +- .../visual/OrtdController/OrtdController.py | 11 +- 4 files changed, 227 insertions(+), 17 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index ed5e0303..5c5d970a 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,18 +1,18 @@ - + OrtdController - - 1 - \(([0-9]+),([0-9]+)\) - (300,300) - Determine size: (height,width) - OrtdController + + 1 + Determine position: (x,y) + (0,0) + \(([0-9]+),([0-9]+)\) + ORTDController @@ -21,15 +21,192 @@ ORTDPlugin1 Uname to use for ortd plugin instance - + 1 + Determine size: (height,width) + (300,300) \(([0-9]+),([0-9]+)\) - (0,0) - Determine position: (x,y) - + + + ORTDPlugin1 + + + ControlSignalCreate + ControlSignalSub + ControllerSignalParameter + ControllerSignalClose + + + + + + ORTD_UDP + + + 1 + 20000 + + + 1 + 127.0.0.1 + + + 1 + 20001 + + + + 0 + 0 + 0 + 0 + + + + + V + + + X + + + + + ControlSignalCreate + + + ControlSignalSub + + + ControllerSignalParameter + + + ControllerSignalClose + + + + + + Butt1 + + Oscillator input + + + + + + Plot + + + time, s + Label-X + \w+,\s*\w+ + + + (300,0) + + + TestPlot + + + [0.0 1.0] + xRange-auto + (\d+\.\d+) + + + (300,300) + + + amplitude, V + Label-Y + \w+,\s+\w+ + + + 1 + xRange-auto + bool + ^(1|0)$ + + + 1 + [0 1 2 3 4] + Color + ^\[(\s*\d\s*)+\] + + + 1 + yRange-auto + bool + ^(1|0)$ + + + [0.0 1.0] + yRange-auto + (\d+\.\d+) + + + 0 + + + 1 + 1000 + Buffersize + ^([1-9][0-9]{0,3}|10000)$ + + + 0 + Rolling Plot + bool + ^(1|0)$ + + + 0 + Grid-Y + bool + ^(1|0)$ + + + 1 + [0 0 0 0 0] + Style + ^\[(\s*\d\s*)+\] + + + 1 + (\d+) + + + + [0,1] + 0 + 0 + [0,1] + [0 1 2 3 4] + 0 + [0 0 0 0 0] + 1 + 1 + 1 + 1000 + + + + + ORTDPlugin1 + + + V + + + - + + Button + + + 0 + \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json b/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json index 410ea98c..ba8f4fdf 100644 --- a/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json @@ -42,6 +42,23 @@ "value": "Disturbance" } + } + }, + "Butt2":{ + "identifier": { + "value": "Button" + }, + "config": { + "size": { + "value": "(150,50)" + }, + "position": { + "value": "(600,50)" + }, + "name": { + "value": "ToDelete" + } + } } }, @@ -57,6 +74,9 @@ "block": "Click_Event", "parameter" : "Oscillator input" } + }, + "ToClose": { + "Butt2": {} } } } \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 1fee5609..463ae350 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -158,6 +158,7 @@ def start_init(self, config=None): self.ControlBlock.add_signal(DSignal('ControlSignalCreate')) self.ControlBlock.add_signal(DSignal('ControlSignalSub')) self.ControlBlock.add_signal(DSignal('ControllerSignalParameter')) + self.ControlBlock.add_signal(DSignal('ControllerSignalClose')) #self.block1 = DBlock(None, 1, 2, 'SourceGroup0', names) self.send_new_block_list([self.block1, self.ControlBlock]) @@ -276,8 +277,11 @@ def execute(self, Data=None, block_name=None): def set_parameter(self, name, value): if name == 'triggerConfiguration': - cfg, subs, para = self.plconf() - self.send_new_data('ControllerSignals', [1], {'ControlSignalCreate':cfg, 'ControlSignalSub':subs, 'ControllerSignalParameter':para}) + cfg, subs, para, close = self.plconf() + self.send_new_data('ControllerSignals', [1], {'ControlSignalCreate':cfg, + 'ControlSignalSub':subs, + 'ControllerSignalParameter':para, + 'ControllerSignalClose':close}) else: for para in self.Parameter_List: if para.name == name: @@ -359,5 +363,5 @@ def plconf(self): cfg = self.ProtocolConfig['PaPIConfig']['ToCreate'] subs = self.ProtocolConfig['PaPIConfig']['ToSub'] paras = self.ProtocolConfig['PaPIConfig']['ToControl'] - - return cfg, subs, paras \ No newline at end of file + close = self.ProtocolConfig['PaPIConfig']['ToClose'] + return cfg, subs, paras, close \ No newline at end of file diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index 43854967..613745c6 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -133,7 +133,8 @@ def subscribe_control_signal(self): time.sleep(0.5) self.api.do_subscribe_uname(self.uname,self.ortd_uname, 'ControllerSignals', signals=['ControlSignalCreate', 'ControlSignalSub', - 'ControllerSignalParameter']) + 'ControllerSignalParameter', + 'ControllerSignalClose']) self.api.do_set_parameter_uname(self.ortd_uname, 'triggerConfiguration', 0) @@ -245,6 +246,14 @@ def execute_cfg(self,Data): para = pl_cfg['parameter'] self.control_api.do_subscribe_uname(self.ortd_uname,pl_uname, pl_cfg['block'], signals=[], sub_alias= para) + ############################ + # Close plugin # + ############################ + if 'ControllerSignalClose' in Data: + cfg = Data['ControllerSignalClose'] + for pl_uname in cfg: + self.control_api.do_delete_plugin_uname(pl_uname) + def set_parameter(self, name, value): # attetion: value is a string and need to be processed ! From 4ac2c3035b7a3c1883d0d81e3d752e212b59a6aa Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 16:33:02 +0100 Subject: [PATCH 042/260] some minor improve --- papi/last_active_papi.xml | 171 ++++++---------------------- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 19 +++- 2 files changed, 48 insertions(+), 142 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 5c5d970a..c4c229cd 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,31 +1,31 @@ - + OrtdController - - OrtdController - - - 1 - Determine position: (x,y) - (0,0) + \(([0-9]+),([0-9]+)\) + (300,300) + Determine size: (height,width) + 1 ORTDController - 0 - ORTDPlugin1 Uname to use for ortd plugin instance + ORTDPlugin1 + 0 - - 1 - Determine size: (height,width) - (300,300) + \(([0-9]+),([0-9]+)\) + (0,0) + Determine position: (x,y) + 1 + + + OrtdController @@ -46,34 +46,26 @@ ORTD_UDP - + + 20001 1 - 20000 - 1 127.0.0.1 + 1 - + + 20000 1 - 20001 - 0 0 - 0 0 + 0 + 0 - - - V - - - X - - ControlSignalCreate @@ -88,6 +80,14 @@ ControllerSignalClose + + + V + + + X + + @@ -98,115 +98,14 @@ - - Plot + + Button - - time, s - Label-X - \w+,\s*\w+ - - - (300,0) - - - TestPlot - - - [0.0 1.0] - xRange-auto - (\d+\.\d+) - - (300,300) - - - amplitude, V - Label-Y - \w+,\s+\w+ - - - 1 - xRange-auto - bool - ^(1|0)$ - - - 1 - [0 1 2 3 4] - Color - ^\[(\s*\d\s*)+\] - - - 1 - yRange-auto - bool - ^(1|0)$ - - - [0.0 1.0] - yRange-auto - (\d+\.\d+) - - - 0 + (150,50) - - 1 - 1000 - Buffersize - ^([1-9][0-9]{0,3}|10000)$ - - - 0 - Rolling Plot - bool - ^(1|0)$ - - - 0 - Grid-Y - bool - ^(1|0)$ - - - 1 - [0 0 0 0 0] - Style - ^\[(\s*\d\s*)+\] - - - 1 - (\d+) + + Disturbance - - - [0,1] - 0 - 0 - [0,1] - [0 1 2 3 4] - 0 - [0 0 0 0 0] - 1 - 1 - 1 - 1000 - - - - - ORTDPlugin1 - - - V - - - - - - Button - - - 0 + \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 463ae350..69103fa0 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -358,10 +358,17 @@ def plconf(self): # 'parameter' : 'Oscillator input' # } # } - - - cfg = self.ProtocolConfig['PaPIConfig']['ToCreate'] - subs = self.ProtocolConfig['PaPIConfig']['ToSub'] - paras = self.ProtocolConfig['PaPIConfig']['ToControl'] - close = self.ProtocolConfig['PaPIConfig']['ToClose'] + cfg = {} + subs = {} + paras = {} + close = {} + if 'PaPIConfig' in self.ProtocolConfig: + if 'ToCreate' in self.ProtocolConfig['PaPIConfig']: + cfg = self.ProtocolConfig['PaPIConfig']['ToCreate'] + if 'ToSub' in self.ProtocolConfig['PaPIConfig']: + subs = self.ProtocolConfig['PaPIConfig']['ToSub'] + if 'ToControl' in self.ProtocolConfig['PaPIConfig']: + paras = self.ProtocolConfig['PaPIConfig']['ToControl'] + if 'ToClose' in self.ProtocolConfig['PaPIConfig']: + close = self.ProtocolConfig['PaPIConfig']['ToClose'] return cfg, subs, paras, close \ No newline at end of file From 3cdf44a0e2c3066df919c12d75b5bb7df7173051 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 16:33:21 +0100 Subject: [PATCH 043/260] some minor improve --- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 54 ----------------------------- 1 file changed, 54 deletions(-) diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 69103fa0..dc6ac6fc 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -304,60 +304,6 @@ def plugin_meta_updated(self): def plconf(self): - # cfg = { - # 'Plot1':{ - # 'identifier': { - # 'value': 'Plot', - # }, - # 'config': { - # 'x-grid': { - # 'value': "0", - # }, - # 'size': { - # 'value': "(300,300)", - # - # }, - # 'position': { - # 'value': "(300,0)", - # }, - # 'name': { - # 'value': 'TestPlot' - # } - # } - # }, - # 'Butt1':{ - # 'identifier': { - # 'value': 'Button', - # }, - # 'config': { - # 'size': { - # 'value': "(150,50)", - # }, - # 'position': { - # 'value': "(600,0)", - # }, - # 'name': { - # 'value': 'Disturbance' - # } - # - # } - # } - # - # } - # - # subs = { - # 'Plot1': { - # 'block': 'SourceGroup0', - # 'signals': ['V'] - # } - # } - # - # paras = { - # 'Butt1': { - # 'block': 'Click_Event', - # 'parameter' : 'Oscillator input' - # } - # } cfg = {} subs = {} paras = {} From 268815608b1c67d8cb1e9a5fb85fb453d4c03641 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 16:42:27 +0100 Subject: [PATCH 044/260] some cleanup --- README.md | 2 +- papi/last_active_papi.xml | 111 +------- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 3 - .../visual/OrtdController/OrtdController.py | 255 +++++++++--------- 4 files changed, 124 insertions(+), 247 deletions(-) diff --git a/README.md b/README.md index 6c4789a4..7953f3f2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -PaPI v. 0.8 +PaPI v. 0.9 ================== Plugin based Process Interaction diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index c4c229cd..ef9377ad 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,111 +1,4 @@ - + - - OrtdController - - - \(([0-9]+),([0-9]+)\) - (300,300) - Determine size: (height,width) - 1 - - - ORTDController - - - Uname to use for ortd plugin instance - ORTDPlugin1 - 0 - - - \(([0-9]+),([0-9]+)\) - (0,0) - Determine position: (x,y) - 1 - - - OrtdController - - - - - - - ORTDPlugin1 - - - ControlSignalCreate - ControlSignalSub - ControllerSignalParameter - ControllerSignalClose - - - - - - ORTD_UDP - - - 20001 - 1 - - - 127.0.0.1 - 1 - - - 20000 - 1 - - - - 0 - 0 - 0 - 0 - - - - - ControlSignalCreate - - - ControlSignalSub - - - ControllerSignalParameter - - - ControllerSignalClose - - - - - V - - - X - - - - - - Butt1 - - Oscillator input - - - - - - Button - - - (150,50) - - - Disturbance - - - \ No newline at end of file + diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index dc6ac6fc..f1411708 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -53,7 +53,6 @@ import numpy as np import threading -import pickle import os @@ -301,8 +300,6 @@ def quit(self): def plugin_meta_updated(self): pass - - def plconf(self): cfg = {} subs = {} diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index 613745c6..ba9c62ee 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -28,136 +28,14 @@ __author__ = 'Stefan' -from PySide.QtGui import QMdiSubWindow -import papi.pyqtgraph as pq - from papi.plugin.base_classes.vip_base import vip_base from papi.data.DParameter import DParameter -#from papi.pyqtgraph.Qt import QtGui, QtCore from PySide import QtGui, QtCore -from PySide.QtGui import QDialog, QLineEdit, QRegExpValidator, QCheckBox +from PySide.QtGui import QRegExpValidator import threading, time -class ControllerIntroPage(QtGui.QWizardPage): - def __init__(self,parent = None): - QtGui.QWizardPage.__init__(self, parent) - self.setTitle("ORTD Controller") - label = QtGui.QLabel("This is the ORTD Controller plugin.") - label.setWordWrap(True) - - label2 = QtGui.QLabel("Click next to start the configuration") - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - layout.addWidget(label2) - - self.setLayout(layout) - - def validatePage(self): - print('intro: next clicked') - return True - - -class ControllerOrtdStart(QtGui.QWizardPage): - def __init__(self,api = None, uname= None, parent = None, ortd_uname = None): - QtGui.QWizardPage.__init__(self, parent) - self.uname = uname - self.api = api - self.ortd_uname = ortd_uname - self.setTitle("ORTD Controller") - label = QtGui.QLabel("Please configure the ORTD plugin.") - label.setWordWrap(True) - - # ----------- # - # IP line edit# - # ----------- # - self.ip_line_edit = QtGui.QLineEdit() - ip_label = QtGui.QLabel('IP-Address:') - ip_label.setBuddy(self.ip_line_edit) - regex = '\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}' - rx = QtCore.QRegExp(regex) - validator = QRegExpValidator(rx, self) - self.ip_line_edit.setValidator(validator) - self.ip_line_edit.setText('127.0.0.1') - - # ----------- # - # Port line edit# - # ----------- # - self.port_line_edit = QtGui.QLineEdit() - port_label = QtGui.QLabel('Port:') - port_label.setBuddy(self.port_line_edit) - regex = '\d{1,5}' - rx = QtCore.QRegExp(regex) - validator = QRegExpValidator(rx, self) - self.port_line_edit.setValidator(validator) - self.port_line_edit.setText('20000') - - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - layout.addWidget(ip_label) - layout.addWidget(self.ip_line_edit) - layout.addWidget(port_label) - layout.addWidget(self.port_line_edit) - - self.setLayout(layout) - - def validatePage(self): - print('Start: next clicked') - IP = self.ip_line_edit.text() - port = self.port_line_edit.text() - cfg ={ - 'address': { - 'value': IP, - 'advanced': '1' - }, - 'source_port': { - 'value': port, - 'advanced': '1' - }, - 'out_port': { - 'value': '20001', - 'advanced': '1' - } - } - self.api.do_create_plugin('ORTD_UDP', self.ortd_uname, cfg, True) - - self.thread = threading.Thread(target=self.subscribe_control_signal) - self.thread.start() - - return True - - def subscribe_control_signal(self): - time.sleep(0.5) - self.api.do_subscribe_uname(self.uname,self.ortd_uname, 'ControllerSignals', signals=['ControlSignalCreate', - 'ControlSignalSub', - 'ControllerSignalParameter', - 'ControllerSignalClose']) - self.api.do_set_parameter_uname(self.ortd_uname, 'triggerConfiguration', 0) - - -class ControllerWorking(QtGui.QWizardPage): - def __init__(self,api = None, uname= None, parent = None): - QtGui.QWizardPage.__init__(self, parent) - self. api = api - - self.setTitle("ORTD Controller") - label = QtGui.QLabel("Controller plugin is working") - label.setWordWrap(True) - - label2 = QtGui.QLabel("") - - layout = QtGui.QVBoxLayout() - layout.addWidget(label) - layout.addWidget(label2) - - self.setLayout(layout) - - def validatePage(self): - return False - class OrtdController(vip_base): @@ -273,17 +151,6 @@ def get_plugin_configuration(self): # config[config_parameter_name]['value'] NEEDS TO BE IMPLEMENTED # configs can be marked as advanced for create dialog # http://utilitymill.com/utility/Regex_For_Range - - # config = { - # "amax": { - # 'value': 3, - # 'regex': '[0-9]+', - # 'display_text' : 'This AMax', - # 'advanced' : '1' - # }, 'f': { - # 'value': "1", - # 'regex': '\d+.{0,1}\d*' - # }} config = { "ORTD_Plugin_uname": { 'value': 'ORTDPlugin1', @@ -305,3 +172,123 @@ def plugin_meta_updated(self): #dplugin_info = self.dplugin_info pass + + + +class ControllerIntroPage(QtGui.QWizardPage): + def __init__(self,parent = None): + QtGui.QWizardPage.__init__(self, parent) + self.setTitle("ORTD Controller") + label = QtGui.QLabel("This is the ORTD Controller plugin.") + label.setWordWrap(True) + + label2 = QtGui.QLabel("Click next to start the configuration") + + layout = QtGui.QVBoxLayout() + layout.addWidget(label) + layout.addWidget(label2) + + self.setLayout(layout) + + def validatePage(self): + print('intro: next clicked') + return True + + +class ControllerOrtdStart(QtGui.QWizardPage): + def __init__(self,api = None, uname= None, parent = None, ortd_uname = None): + QtGui.QWizardPage.__init__(self, parent) + self.uname = uname + self.api = api + self.ortd_uname = ortd_uname + self.setTitle("ORTD Controller") + label = QtGui.QLabel("Please configure the ORTD plugin.") + label.setWordWrap(True) + + # ----------- # + # IP line edit# + # ----------- # + self.ip_line_edit = QtGui.QLineEdit() + ip_label = QtGui.QLabel('IP-Address:') + ip_label.setBuddy(self.ip_line_edit) + regex = '\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}' + rx = QtCore.QRegExp(regex) + validator = QRegExpValidator(rx, self) + self.ip_line_edit.setValidator(validator) + self.ip_line_edit.setText('127.0.0.1') + + # ----------- # + # Port line edit# + # ----------- # + self.port_line_edit = QtGui.QLineEdit() + port_label = QtGui.QLabel('Port:') + port_label.setBuddy(self.port_line_edit) + regex = '\d{1,5}' + rx = QtCore.QRegExp(regex) + validator = QRegExpValidator(rx, self) + self.port_line_edit.setValidator(validator) + self.port_line_edit.setText('20000') + + + layout = QtGui.QVBoxLayout() + layout.addWidget(label) + layout.addWidget(ip_label) + layout.addWidget(self.ip_line_edit) + layout.addWidget(port_label) + layout.addWidget(self.port_line_edit) + + self.setLayout(layout) + + def validatePage(self): + print('Start: next clicked') + IP = self.ip_line_edit.text() + port = self.port_line_edit.text() + cfg ={ + 'address': { + 'value': IP, + 'advanced': '1' + }, + 'source_port': { + 'value': port, + 'advanced': '1' + }, + 'out_port': { + 'value': '20001', + 'advanced': '1' + } + } + self.api.do_create_plugin('ORTD_UDP', self.ortd_uname, cfg, True) + + self.thread = threading.Thread(target=self.subscribe_control_signal) + self.thread.start() + + return True + + def subscribe_control_signal(self): + time.sleep(0.5) + self.api.do_subscribe_uname(self.uname,self.ortd_uname, 'ControllerSignals', signals=['ControlSignalCreate', + 'ControlSignalSub', + 'ControllerSignalParameter', + 'ControllerSignalClose']) + self.api.do_set_parameter_uname(self.ortd_uname, 'triggerConfiguration', 0) + + +class ControllerWorking(QtGui.QWizardPage): + def __init__(self,api = None, uname= None, parent = None): + QtGui.QWizardPage.__init__(self, parent) + self. api = api + + self.setTitle("ORTD Controller") + label = QtGui.QLabel("Controller plugin is working") + label.setWordWrap(True) + + label2 = QtGui.QLabel("") + + layout = QtGui.QVBoxLayout() + layout.addWidget(label) + layout.addWidget(label2) + + self.setLayout(layout) + + def validatePage(self): + return False From 3b3b7e73a4aee5a0c856000ac968d99f3b3663c1 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 16:50:58 +0100 Subject: [PATCH 045/260] some cleanup --- papi/gui/gui_api.py | 3 +++ papi/last_active_papi.xml | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index a1086331..cba1bdc1 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -30,6 +30,7 @@ import datetime import time import traceback +import os import papi.event as Event @@ -475,6 +476,8 @@ def do_close_program(self): self.core_queue.put(event) def do_load_xml(self, path): + if path is None or not os.path.isfile(path): + return False tree = ET.parse(path) root = tree.getroot() diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index ef9377ad..4641204e 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,4 +1,35 @@ - + + + OrtdController + + + (300,300) + Determine size: (height,width) + 1 + \(([0-9]+),([0-9]+)\) + + + Uname to use for ortd plugin instance + 0 + ORTDPlugin1 + + + (0,0) + Determine position: (x,y) + 1 + \(([0-9]+),([0-9]+)\) + + + ORTDController + + + OrtdController + + + + + + From c093d68bbc332314e858cdf15b086aa8f888db84 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 16:56:21 +0100 Subject: [PATCH 046/260] some cleanup --- papi/gui/gui_api.py | 6 +++--- papi/last_active_papi.xml | 35 ----------------------------------- 2 files changed, 3 insertions(+), 38 deletions(-) delete mode 100644 papi/last_active_papi.xml diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index cba1bdc1..1ff79e5a 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -495,9 +495,9 @@ def do_load_xml(self, path): self.resize_gui.emit(w, h) else: if plugin_xml.tag == 'Background': - path = str(plugin_xml.attrib['image']) - if path != '' and path is not None and path != 'default': - self.set_bg_gui.emit(path) + bg_path = str(plugin_xml.attrib['image']) + if bg_path != '' and bg_path is not None and bg_path != 'default' and bg_path !='None': + self.set_bg_gui.emit(bg_path) else: pl_uname = plugin_xml.attrib['uname'] identifier = plugin_xml.find('Identifier').text diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml deleted file mode 100644 index 4641204e..00000000 --- a/papi/last_active_papi.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - OrtdController - - - (300,300) - Determine size: (height,width) - 1 - \(([0-9]+),([0-9]+)\) - - - Uname to use for ortd plugin instance - 0 - ORTDPlugin1 - - - (0,0) - Determine position: (x,y) - 1 - \(([0-9]+),([0-9]+)\) - - - ORTDController - - - OrtdController - - - - - - - From 58da40f3d09d34454360dab9d497be309042b32f Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 16:59:45 +0100 Subject: [PATCH 047/260] some cleanup --- papi/gui/gui_api.py | 206 +++++++++++++++++++++++--------------------- 1 file changed, 106 insertions(+), 100 deletions(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 1ff79e5a..ee7df477 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -609,106 +609,112 @@ def do_save_xml_config(self, path): if path[-4:] != '.xml': path += '.xml' - root = ET.Element(CONFIG_ROOT_ELEMENT_NAME) - root.set('Date', datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')) - root.set('PaPI_version', CORE_PAPI_VERSION) - - size_xml = ET.SubElement(root, 'Size') - size_xml.set('w', str(self.gui_size_width)) - size_xml.set('h', str(self.gui_size_height)) - - bg_xml = ET.SubElement(root, 'Background') - bg_xml.set('image', str(self.gui_bg_path)) - - # get plugins # - plugins = self.gui_data.get_all_plugins() - for dplugin_id in plugins: - dplugin = plugins[dplugin_id] - - if dplugin.type == PLUGIN_PCP_IDENTIFIER or dplugin.type == PLUGIN_VIP_IDENTIFIER: - dplugin.startup_config = dplugin.plugin.get_current_config() - - pl_xml = ET.SubElement(root, 'Plugin') - pl_xml.set('uname', dplugin.uname) - - identifier_xml = ET.SubElement(pl_xml, 'Identifier') - identifier_xml.text = dplugin.plugin_identifier - - # --------------------------------------- - # Save all current config as startup config - # for the next start - # --------------------------------------- - - cfg_xml = ET.SubElement(pl_xml, 'StartConfig') - for parameter in dplugin.startup_config: - para_xml = ET.SubElement(cfg_xml, 'Parameter') - para_xml.set('Name', parameter) - for detail in dplugin.startup_config[parameter]: - detail_xml = ET.SubElement(para_xml, detail) - detail_xml.text = dplugin.startup_config[parameter][detail] - - # --------------------------------------- - # Save all current values for all - # parameter - # --------------------------------------- - - last_paras_xml = ET.SubElement(pl_xml, 'PreviousParameters') - allparas = dplugin.get_parameters() - for para_key in allparas: - para = allparas[para_key] - last_para_xml = ET.SubElement(last_paras_xml, 'Parameter') - last_para_xml.set('Name', para_key) - last_para_xml.text = str(para.value) - - # --------------------------------------- - # Save all current values for all - # signals of all dblocks - # --------------------------------------- - - dblocks_xml = ET.SubElement(pl_xml, 'DBlocks') - - alldblock_names = dplugin.get_dblocks() - - for dblock_name in alldblock_names: - dblock = alldblock_names[dblock_name] - dblock_xml = ET.SubElement(dblocks_xml, 'DBlock') - dblock_xml.set('Name', dblock.name) - - alldsignals = dblock.get_signals() - - for dsignal in alldsignals: - dsignal_xml = ET.SubElement(dblock_xml, 'DSignal') - dsignal_xml.set('uname', dsignal.uname) - - dname_xml = ET.SubElement(dsignal_xml, 'dname') - dname_xml.text = dsignal.dname - - # --------------------------------------- - # Save all subscriptions for this plugin - # --------------------------------------- - - subs_xml = ET.SubElement(pl_xml, 'Subscriptions') - subs = dplugin.get_subscribtions() - for sub in subs: - sub_xml = ET.SubElement(subs_xml, 'Subscription') - source_xml = ET.SubElement(sub_xml, 'data_source') - source_xml.text = self.gui_data.get_dplugin_by_id(sub).uname - for block in subs[sub]: - block_xml = ET.SubElement(sub_xml, 'block') - block_xml.set('Name', block) - - dsubscription = subs[sub][block] - - alias_xml = ET.SubElement(block_xml, 'alias') - alias_xml.text = dsubscription.alias - - for s in dsubscription.get_signals(): - signal_xml = ET.SubElement(block_xml, 'Signal') - signal_xml.text = str(s) - - self.indent(root) - tree = ET.ElementTree(root) - tree.write(path) + try: + root = ET.Element(CONFIG_ROOT_ELEMENT_NAME) + root.set('Date', datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')) + root.set('PaPI_version', CORE_PAPI_VERSION) + + size_xml = ET.SubElement(root, 'Size') + size_xml.set('w', str(self.gui_size_width)) + size_xml.set('h', str(self.gui_size_height)) + + bg_xml = ET.SubElement(root, 'Background') + bg_xml.set('image', str(self.gui_bg_path)) + + # get plugins # + plugins = self.gui_data.get_all_plugins() + for dplugin_id in plugins: + dplugin = plugins[dplugin_id] + + if dplugin.type == PLUGIN_PCP_IDENTIFIER or dplugin.type == PLUGIN_VIP_IDENTIFIER: + dplugin.startup_config = dplugin.plugin.get_current_config() + + pl_xml = ET.SubElement(root, 'Plugin') + pl_xml.set('uname', dplugin.uname) + + identifier_xml = ET.SubElement(pl_xml, 'Identifier') + identifier_xml.text = dplugin.plugin_identifier + + # --------------------------------------- + # Save all current config as startup config + # for the next start + # --------------------------------------- + + cfg_xml = ET.SubElement(pl_xml, 'StartConfig') + for parameter in dplugin.startup_config: + para_xml = ET.SubElement(cfg_xml, 'Parameter') + para_xml.set('Name', parameter) + for detail in dplugin.startup_config[parameter]: + detail_xml = ET.SubElement(para_xml, detail) + detail_xml.text = dplugin.startup_config[parameter][detail] + + # --------------------------------------- + # Save all current values for all + # parameter + # --------------------------------------- + + last_paras_xml = ET.SubElement(pl_xml, 'PreviousParameters') + allparas = dplugin.get_parameters() + for para_key in allparas: + para = allparas[para_key] + last_para_xml = ET.SubElement(last_paras_xml, 'Parameter') + last_para_xml.set('Name', para_key) + last_para_xml.text = str(para.value) + + # --------------------------------------- + # Save all current values for all + # signals of all dblocks + # --------------------------------------- + + dblocks_xml = ET.SubElement(pl_xml, 'DBlocks') + + alldblock_names = dplugin.get_dblocks() + + for dblock_name in alldblock_names: + dblock = alldblock_names[dblock_name] + dblock_xml = ET.SubElement(dblocks_xml, 'DBlock') + dblock_xml.set('Name', dblock.name) + + alldsignals = dblock.get_signals() + + for dsignal in alldsignals: + dsignal_xml = ET.SubElement(dblock_xml, 'DSignal') + dsignal_xml.set('uname', dsignal.uname) + + dname_xml = ET.SubElement(dsignal_xml, 'dname') + dname_xml.text = dsignal.dname + + # --------------------------------------- + # Save all subscriptions for this plugin + # --------------------------------------- + + subs_xml = ET.SubElement(pl_xml, 'Subscriptions') + subs = dplugin.get_subscribtions() + for sub in subs: + sub_xml = ET.SubElement(subs_xml, 'Subscription') + source_xml = ET.SubElement(sub_xml, 'data_source') + source_xml.text = self.gui_data.get_dplugin_by_id(sub).uname + for block in subs[sub]: + block_xml = ET.SubElement(sub_xml, 'block') + block_xml.set('Name', block) + + dsubscription = subs[sub][block] + + alias_xml = ET.SubElement(block_xml, 'alias') + alias_xml.text = dsubscription.alias + + for s in dsubscription.get_signals(): + signal_xml = ET.SubElement(block_xml, 'Signal') + signal_xml.text = str(s) + + self.indent(root) + tree = ET.ElementTree(root) + tree.write(path) + + except Exception as E: + tb = traceback.format_exc() + self.error_occured.emit("Error: Config Loader", "Not saveable: " + path, tb) + def indent(self, elem, level=0): # copied from http://effbot.org/zone/element-lib.htm#prettyprint 06.10.2014 15:53 From 2b510b8aad3cea0100f1cb0707b645abd3b7d501 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 17:00:38 +0100 Subject: [PATCH 048/260] some cleanup --- papi/gui/gui_api.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index ee7df477..529aa0d3 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -64,7 +64,6 @@ def __init__(self, gui_data, core_queue, gui_id, LOG_IDENT=GUI_PROCESS_CONSOLE_I self.gui_size_height = None self.gui_bg_path = None - def do_create_plugin(self, plugin_identifier, uname, config={}, autostart=True): """ Something like a callback function for gui triggered events e.a. when a user wants to create a new plugin. @@ -715,7 +714,6 @@ def do_save_xml_config(self, path): tb = traceback.format_exc() self.error_occured.emit("Error: Config Loader", "Not saveable: " + path, tb) - def indent(self, elem, level=0): # copied from http://effbot.org/zone/element-lib.htm#prettyprint 06.10.2014 15:53 i = "\n" + level * " " @@ -765,7 +763,6 @@ def do_test_name_to_be_unique(self, name): else: return False - def do_change_string_to_be_uname(self, name): """ This method will take a string and convert him according to some rules to be an uname From 59b5a8280ca1758d727d5a0e2dd4fe8294dd7cd7 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 17:06:56 +0100 Subject: [PATCH 049/260] Merge branch 'development' of https://github.com/TUB-Control/PaPI into development Conflicts: papi/last_active_papi.xml added about message, changes in gui due to about action, --- papi/constants.py | 25 +++++++++++++++++-- papi/gui/qt_new/main.py | 25 +++++++++++++++---- papi/ui/gui/qt_dev/add_plugin.py | 2 +- papi/ui/gui/qt_dev/add_subscriber.py | 2 +- papi/ui/gui/qt_dev/main.py | 2 +- papi/ui/gui/qt_dev/manager.py | 2 +- papi/ui/gui/qt_new/create.py | 2 +- papi/ui/gui/qt_new/create_dialog.py | 2 +- papi/ui/gui/qt_new/main.py | 26 +++++++++++++++++++- papi/ui/gui/qt_new/overview.py | 2 +- ui/gui/qt_new/main.ui | 36 ++++++++++++++++++++++++++++ 11 files changed, 111 insertions(+), 15 deletions(-) diff --git a/papi/constants.py b/papi/constants.py index 510b25ba..9f0ad839 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -34,8 +34,8 @@ CORE_CONSOLE_LOG_LEVEL = 1 -CORE_PAPI_VERSION = 'v_0.9' # no spaces allowed -CORE_CORE_VERSION = 'v_0.9' # no spaces allowed +CORE_PAPI_VERSION = '0.9' # no spaces allowed +CORE_CORE_VERSION = '0.9' # no spaces allowed CORE_PAPI_CONSOLE_START_MESSAGE = 'PaPI - Plugin based Process Interaction' + ' Version: ' + CORE_PAPI_VERSION CORE_CORE_CONSOLE_START_MESSAGE = 'PaPI Core Modul ' + CORE_CORE_VERSION + ' started' CORE_STOP_CONSOLE_MESSAGE = 'Core and PaPI finished operation cleanly' @@ -45,6 +45,27 @@ CORE_ALIVE_CHECK_INTERVAL = 2 # seconds CORE_ALIVE_MAX_COUNT = 2 +PAPI_COPYRIGHT = '© 2014' +PAPI_ABOUT_TITLE = 'About PaPI' +PAPI_ABOUT_TEXT = """ + +

PaPI

+Version """ + CORE_PAPI_VERSION + """ +
+
+Copyright """ + PAPI_COPYRIGHT + """ Control Systems Group, TU-Berlin.
+Published under LGPL Version 3. Hosted on GitHub +

+PaPI uses: +
    +
  • Yapsy 1.10.423 published under BSD-License, License
  • +
  • pyqtgraph-0.9.10 published under MIT-License, License
  • +
+ +""" + + # EVENT CONSTANTS # TODO # somethink like: EVENT_TYPE_STATUS = 'status_event' and EVENT_OPERATION_CHECK_ALIVE = 'check_alive' diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index c60f0d84..21f9c904 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -33,9 +33,9 @@ import os import traceback -from PySide.QtGui import QMainWindow, QApplication, QFileDialog +from PySide.QtGui import QMainWindow, QApplication, QFileDialog, QDesktopServices from PySide.QtGui import QIcon -from PySide.QtCore import QSize, Qt, QThread +from PySide.QtCore import QSize, Qt, QThread, QUrl from papi.ui.gui.qt_new.main import Ui_QtNewMain from papi.data.DGui import DGui @@ -43,7 +43,7 @@ from papi.constants import GUI_PAPI_WINDOW_TITLE, GUI_WOKRING_INTERVAL, GUI_PROCESS_CONSOLE_IDENTIFIER, \ GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_START_CONSOLE_MESSAGE, GUI_WAIT_TILL_RELOAD, GUI_DEFAULT_HEIGHT, GUI_DEFAULT_WIDTH, \ - PLUGIN_STATE_PAUSE, PLUGIN_STATE_STOPPED + PLUGIN_STATE_PAUSE, PLUGIN_STATE_STOPPED, PAPI_ABOUT_TEXT, PAPI_ABOUT_TITLE from papi.constants import CONFIG_DEFAULT_FILE, PLUGIN_VIP_IDENTIFIER, PLUGIN_PCP_IDENTIFIER, CONFIG_DEFAULT_DIRECTORY @@ -148,6 +148,11 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): self.actionSetBackground.triggered.connect(self.set_background_for_gui) + self.actionPaPI_Wiki.triggered.connect(self.papi_wiki_triggerd) + + self.actionPaPI_Doc.triggered.connect(self.papi_doc_triggerd) + self.actionAbout.triggered.connect(self.papi_about_triggerd) + self.actionAbout_Qt.triggered.connect(self.papi_about_qt_triggerd) # ------------------------------------- # Create Icons for buttons # ------------------------------------- @@ -363,8 +368,6 @@ def error_occured(self, title, msg, detailed_msg): errMsg.setDetailedText(str(detailed_msg)) errMsg.setWindowModality(Qt.NonModal) errMsg.show() -# errMsg.setSizePolicy(Qt.Polic) - def toggle_run_mode(self): if self.in_run_mode: @@ -418,6 +421,18 @@ def reset_papi(self): self.setGeometry(self.geometry().x(),self.geometry().y(),w,h) self.gui_api.do_reset_papi() + def papi_wiki_triggerd(self): + QDesktopServices.openUrl(QUrl("https://github.com/TUB-Control/PaPI/wiki", QUrl.TolerantMode)) + + def papi_doc_triggerd(self): + QDesktopServices.openUrl(QUrl("http://tub-control.github.io/PaPI/", QUrl.TolerantMode)) + + def papi_about_triggerd(self): + QtGui.QMessageBox.about(self,PAPI_ABOUT_TITLE, PAPI_ABOUT_TEXT) + + def papi_about_qt_triggerd(self): + QtGui.QMessageBox.aboutQt(self) + def startGUI(CoreQueue, GUIQueue,gui_id): """ Function to call to start gui operation diff --git a/papi/ui/gui/qt_dev/add_plugin.py b/papi/ui/gui/qt_dev/add_plugin.py index 7490d97f..e4e35d1f 100644 --- a/papi/ui/gui/qt_dev/add_plugin.py +++ b/papi/ui/gui/qt_dev/add_plugin.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_dev/add_plugin.ui' # -# Created: Mon Jan 5 14:03:51 2015 +# Created: Mon Jan 12 16:53:32 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_dev/add_subscriber.py b/papi/ui/gui/qt_dev/add_subscriber.py index da29c209..27ea8fa0 100644 --- a/papi/ui/gui/qt_dev/add_subscriber.py +++ b/papi/ui/gui/qt_dev/add_subscriber.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_dev/add_subscriber.ui' # -# Created: Mon Jan 5 14:03:51 2015 +# Created: Mon Jan 12 16:53:31 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_dev/main.py b/papi/ui/gui/qt_dev/main.py index 0ec63f10..d28a302a 100644 --- a/papi/ui/gui/qt_dev/main.py +++ b/papi/ui/gui/qt_dev/main.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_dev/main.ui' # -# Created: Mon Jan 5 14:03:51 2015 +# Created: Mon Jan 12 16:53:31 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_dev/manager.py b/papi/ui/gui/qt_dev/manager.py index 82b9114a..938491bd 100644 --- a/papi/ui/gui/qt_dev/manager.py +++ b/papi/ui/gui/qt_dev/manager.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_dev/manager.ui' # -# Created: Mon Jan 5 14:03:51 2015 +# Created: Mon Jan 12 16:53:32 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/create.py b/papi/ui/gui/qt_new/create.py index 5493c017..d142aeb0 100644 --- a/papi/ui/gui/qt_new/create.py +++ b/papi/ui/gui/qt_new/create.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/create.ui' # -# Created: Mon Jan 5 14:03:50 2015 +# Created: Mon Jan 12 16:53:31 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/create_dialog.py b/papi/ui/gui/qt_new/create_dialog.py index 144d0931..0330b4f5 100644 --- a/papi/ui/gui/qt_new/create_dialog.py +++ b/papi/ui/gui/qt_new/create_dialog.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/create_dialog.ui' # -# Created: Mon Jan 5 14:03:51 2015 +# Created: Mon Jan 12 16:53:31 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/main.py b/papi/ui/gui/qt_new/main.py index 59854c51..359a511c 100644 --- a/papi/ui/gui/qt_new/main.py +++ b/papi/ui/gui/qt_new/main.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/main.ui' # -# Created: Mon Jan 5 14:03:51 2015 +# Created: Mon Jan 12 16:53:31 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! @@ -44,6 +44,8 @@ def setupUi(self, QtNewMain): self.menuPlugin.setObjectName("menuPlugin") self.menuView = QtGui.QMenu(self.menubar) self.menuView.setObjectName("menuView") + self.menuHelp = QtGui.QMenu(self.menubar) + self.menuHelp.setObjectName("menuHelp") QtNewMain.setMenuBar(self.menubar) self.statusbar = QtGui.QStatusBar(QtNewMain) self.statusbar.setObjectName("statusbar") @@ -66,6 +68,16 @@ def setupUi(self, QtNewMain): self.actionRunMode.setObjectName("actionRunMode") self.actionSetBackground = QtGui.QAction(QtNewMain) self.actionSetBackground.setObjectName("actionSetBackground") + self.actionPaPI_Wiki = QtGui.QAction(QtNewMain) + self.actionPaPI_Wiki.setObjectName("actionPaPI_Wiki") + self.actionPaPI_Doc = QtGui.QAction(QtNewMain) + self.actionPaPI_Doc.setObjectName("actionPaPI_Doc") + self.actionAbout = QtGui.QAction(QtNewMain) + self.actionAbout.setObjectName("actionAbout") + self.actionAbout_Qt = QtGui.QAction(QtNewMain) + self.actionAbout_Qt.setObjectName("actionAbout_Qt") + self.actionAbout_PySide = QtGui.QAction(QtNewMain) + self.actionAbout_PySide.setObjectName("actionAbout_PySide") self.menuPaPI.addAction(self.actionLoad) self.menuPaPI.addAction(self.actionSave) self.menuPaPI.addAction(self.actionExit) @@ -75,9 +87,15 @@ def setupUi(self, QtNewMain): self.menuPlugin.addAction(self.actionCreate) self.menuView.addAction(self.actionRunMode) self.menuView.addAction(self.actionSetBackground) + self.menuHelp.addAction(self.actionPaPI_Wiki) + self.menuHelp.addAction(self.actionPaPI_Doc) + self.menuHelp.addSeparator() + self.menuHelp.addAction(self.actionAbout) + self.menuHelp.addAction(self.actionAbout_Qt) self.menubar.addAction(self.menuPaPI.menuAction()) self.menubar.addAction(self.menuPlugin.menuAction()) self.menubar.addAction(self.menuView.menuAction()) + self.menubar.addAction(self.menuHelp.menuAction()) self.retranslateUi(QtNewMain) QtCore.QObject.connect(self.actionExit, QtCore.SIGNAL("triggered()"), QtNewMain.close) @@ -90,6 +108,7 @@ def retranslateUi(self, QtNewMain): self.menuPaPI.setTitle(QtGui.QApplication.translate("QtNewMain", "PaPI", None, QtGui.QApplication.UnicodeUTF8)) self.menuPlugin.setTitle(QtGui.QApplication.translate("QtNewMain", "Plugin", None, QtGui.QApplication.UnicodeUTF8)) self.menuView.setTitle(QtGui.QApplication.translate("QtNewMain", "View", None, QtGui.QApplication.UnicodeUTF8)) + self.menuHelp.setTitle(QtGui.QApplication.translate("QtNewMain", "Help", None, QtGui.QApplication.UnicodeUTF8)) self.actionLoad.setText(QtGui.QApplication.translate("QtNewMain", "Load", None, QtGui.QApplication.UnicodeUTF8)) self.actionSave.setText(QtGui.QApplication.translate("QtNewMain", "Save", None, QtGui.QApplication.UnicodeUTF8)) self.actionOverview.setText(QtGui.QApplication.translate("QtNewMain", "Overview", None, QtGui.QApplication.UnicodeUTF8)) @@ -99,4 +118,9 @@ def retranslateUi(self, QtNewMain): self.actionResetPaPI.setText(QtGui.QApplication.translate("QtNewMain", "ResetPaPI", None, QtGui.QApplication.UnicodeUTF8)) self.actionRunMode.setText(QtGui.QApplication.translate("QtNewMain", "RunMode", None, QtGui.QApplication.UnicodeUTF8)) self.actionSetBackground.setText(QtGui.QApplication.translate("QtNewMain", "SetBackground", None, QtGui.QApplication.UnicodeUTF8)) + self.actionPaPI_Wiki.setText(QtGui.QApplication.translate("QtNewMain", "PaPI Wiki", None, QtGui.QApplication.UnicodeUTF8)) + self.actionPaPI_Doc.setText(QtGui.QApplication.translate("QtNewMain", "PaPI Doc", None, QtGui.QApplication.UnicodeUTF8)) + self.actionAbout.setText(QtGui.QApplication.translate("QtNewMain", "About", None, QtGui.QApplication.UnicodeUTF8)) + self.actionAbout_Qt.setText(QtGui.QApplication.translate("QtNewMain", "About Qt", None, QtGui.QApplication.UnicodeUTF8)) + self.actionAbout_PySide.setText(QtGui.QApplication.translate("QtNewMain", "About PySide", None, QtGui.QApplication.UnicodeUTF8)) diff --git a/papi/ui/gui/qt_new/overview.py b/papi/ui/gui/qt_new/overview.py index 435796ff..152ade5e 100644 --- a/papi/ui/gui/qt_new/overview.py +++ b/papi/ui/gui/qt_new/overview.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/overview.ui' # -# Created: Mon Jan 5 14:03:51 2015 +# Created: Mon Jan 12 16:53:31 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/ui/gui/qt_new/main.ui b/ui/gui/qt_new/main.ui index 9b73ba39..963c1a85 100644 --- a/ui/gui/qt_new/main.ui +++ b/ui/gui/qt_new/main.ui @@ -82,9 +82,20 @@ + + + Help + + + + + + + + @@ -132,6 +143,31 @@ SetBackground + + + PaPI Wiki + + + + + PaPI Doc + + + + + About + + + + + About Qt + + + + + About PySide + + From 8478452eef2d467adc6c45fb6ac7d2e6c54c0b13 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 17:13:20 +0100 Subject: [PATCH 050/260] some cleanup --- papi/gui/gui_api.py | 66 ++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 529aa0d3..c73575c5 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -145,7 +145,6 @@ def do_edit_plugin_uname(self, uname, eObject, changeRequest): :param changeRequest: :return: """ - pl_id = self.do_get_plugin_id_from_uname(uname) if pl_id is not None: @@ -161,7 +160,6 @@ def do_stopReset_pluign(self, id): :type id: int :return: """ - event = Event.instruction.StopPlugin(self.gui_id, id, None, delete=False) self.core_queue.put(event) @@ -371,7 +369,6 @@ def do_pause_plugin_by_id(self, plugin_id): :param plugin_id: id of plugin to pause :type plugin_id: int """ - if self.gui_data.get_dplugin_by_id(plugin_id) is not None: opt = DOptionalData() event = Event.instruction.PausePlugin(self.gui_id, plugin_id, opt) @@ -388,18 +385,6 @@ def do_pause_plugin_by_uname(self, plugin_uname): :param plugin_uname: uname of plugin to pause :type plugin_uname: basestring """ - # # get plugin from DGui with given uname - # # purpose: get its id - # dplug = self.gui_data.get_dplugin_by_uname(plugin_uname) - # # check for existance - # if dplug is not None: - # # it does exist, so get its id - # self.do_pause_plugin_by_id(dplug.id) - # else: - # # plugin with uname does not exist - # self.log.printText(1, 'do_pause, plugin uname worng') - # return -1 - plugin_id = self.do_get_plugin_id_from_uname(plugin_uname) if plugin_id is not None: return self.do_pause_plugin_by_id(plugin_id) @@ -432,18 +417,6 @@ def do_resume_plugin_by_uname(self, plugin_uname): :param plugin_uname: uname of plugin to resume :type plugin_uname: basestring """ - # # get plugin from DGui with given uname - # # purpose: get its id - # dplug = self.gui_data.get_dplugin_by_uname(plugin_uname) - # # check for existance - # if dplug is not None: - # # it does exist, so get its id - # self.do_resume_plugin_by_id(dplug.id) - # else: - # # plugin with uname does not exist - # self.log.printText(1, 'do_resume, plugin uname worng') - # return -1 - plugin_id = self.do_get_plugin_id_from_uname(plugin_uname) if plugin_id is not None: return self.do_resume_plugin_by_id(plugin_id) @@ -469,12 +442,23 @@ def do_get_plugin_id_from_uname(self, uname): return None def do_close_program(self): + """ + Tell core to close papi. Core will respond and will close all open plugins. + + """ opt = DOptionalData() opt.reason = 'User clicked close Button' event = Event.instruction.CloseProgram(self.gui_id, 0, opt) self.core_queue.put(event) def do_load_xml(self, path): + """ + Function to load a xml config to papi and apply the configuration. + + :param path: path to xml file to load. + :type path: basestring + :return: + """ if path is None or not os.path.isfile(path): return False tree = ET.parse(path) @@ -588,6 +572,19 @@ def change_uname_to_uniqe(self, uname): return uname def config_loader_subs(self, pl_to_start, subs_to_make, parameters_to_change, signals_to_change): + """ + Function for callback when timer finished to apply subscriptions and parameter changed of config. + + :param pl_to_start: list of plugins to start + :type pl_to_start: list + :param subs_to_make: list of subscriptions to make + :type subs_to_make: list + :param parameters_to_change: parameter changes to apply + :type parameters_to_change: list + :param signals_to_change: signal name changes to apply + :type signals_to_change list + :return: + """ for sub in subs_to_make: self.do_subscribe_uname(sub[0], sub[1], sub[2], sub[3], sub[4]) @@ -604,7 +601,13 @@ def config_loader_subs(self, pl_to_start, subs_to_make, parameters_to_change, si {'edit': DSignal(dsignal_uname, dsignal_dname)}) def do_save_xml_config(self, path): + """ + This function will save papis current state to a xml file provided by path. + :param path: path to save xml to. + :type path: basestring + :return: + """ if path[-4:] != '.xml': path += '.xml' @@ -715,7 +718,14 @@ def do_save_xml_config(self, path): self.error_occured.emit("Error: Config Loader", "Not saveable: " + path, tb) def indent(self, elem, level=0): - # copied from http://effbot.org/zone/element-lib.htm#prettyprint 06.10.2014 15:53 + """ + Function which will apply a nice looking indentiation to xml structure before save. Better readability. + copied from http://effbot.org/zone/element-lib.htm#prettyprint 06.10.2014 15:53 + + :param elem: + :param level: + :return: + """ i = "\n" + level * " " if len(elem): if not elem.text or not elem.text.strip(): From dbd7eeb14c85070ae1710fa66778f735d8755e10 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 17:14:39 +0100 Subject: [PATCH 051/260] some cleanup --- papi/gui/gui_api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index c73575c5..898bd065 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -479,7 +479,7 @@ def do_load_xml(self, path): else: if plugin_xml.tag == 'Background': bg_path = str(plugin_xml.attrib['image']) - if bg_path != '' and bg_path is not None and bg_path != 'default' and bg_path !='None': + if bg_path != '' and bg_path is not None and bg_path != 'default' and bg_path != 'None': self.set_bg_gui.emit(bg_path) else: pl_uname = plugin_xml.attrib['uname'] @@ -544,7 +544,7 @@ def do_load_xml(self, path): signals_to_change.append([pl_uname, dblock_name, dsignal_uname, dsignal_dname]) except Exception as E: tb = traceback.format_exc() - self.error_occured.emit("Error: Config Loader", "Not loadable: " + path, tb) + self.error_occured.emit("Error: Config Loader", "Not loadable: " + path, tb) for pl in plugins_to_start: # 0: ident, 1: uname, 2: config @@ -715,7 +715,7 @@ def do_save_xml_config(self, path): except Exception as E: tb = traceback.format_exc() - self.error_occured.emit("Error: Config Loader", "Not saveable: " + path, tb) + self.error_occured.emit("Error: Config Loader", "Not saveable: " + path, tb) def indent(self, elem, level=0): """ From 3d1d2d323866a6e170320f95ea8a479f28576e89 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 17:16:36 +0100 Subject: [PATCH 052/260] added comments, formatting --- papi/gui/gui_event_processing.py | 106 ++++++++++++++++++------------- 1 file changed, 62 insertions(+), 44 deletions(-) diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index ac7ca163..1e338814 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -48,7 +48,6 @@ class GuiEventProcessing(QtCore.QObject): - added_dplugin = QtCore.Signal(DPlugin) removed_dplugin = QtCore.Signal(DPlugin) dgui_changed = QtCore.Signal() @@ -65,17 +64,17 @@ def __init__(self, gui_data, core_queue, gui_id, gui_queue): self.gui_queue = gui_queue # switch case for event processing - self.process_event = { 'new_data': self.process_new_data_event, - 'close_programm': self.process_close_program_event, - 'check_alive_status': self.process_check_alive_status, - 'create_plugin': self.process_create_plugin, - 'update_meta': self.process_update_meta, - 'plugin_closed': self.process_plugin_closed, - 'set_parameter': self.process_set_parameter, - 'pause_plugin': self.process_pause_plugin, - 'resume_plugin': self.process_resume_plugin, - 'stop_plugin': self.process_stop_plugin, - 'start_plugin': self.process_start_plugin + self.process_event = {'new_data': self.process_new_data_event, + 'close_programm': self.process_close_program_event, + 'check_alive_status': self.process_check_alive_status, + 'create_plugin': self.process_create_plugin, + 'update_meta': self.process_update_meta, + 'plugin_closed': self.process_plugin_closed, + 'set_parameter': self.process_set_parameter, + 'pause_plugin': self.process_pause_plugin, + 'resume_plugin': self.process_resume_plugin, + 'stop_plugin': self.process_stop_plugin, + 'start_plugin': self.process_start_plugin } def gui_working(self, close_mock): @@ -91,7 +90,7 @@ def gui_working(self, close_mock): isEvent = True # event object, if there is an event event = None - while(isEvent): + while (isEvent): # look at queue and try to get a new element try: event = self.gui_queue.get_nowait() @@ -102,11 +101,11 @@ def gui_working(self, close_mock): isEvent = False # check if there was a new element to process it - if(isEvent): - # get the event operation + if isEvent: + # get the event operation op = event.get_event_operation() # debug out - self.log.printText(2,'Event: ' + op) + self.log.printText(2, 'Event: ' + op) # process this event if op == 'test_close': close_mock() @@ -127,7 +126,7 @@ def process_new_data_event(self, event): :type dplugin: DPlugin """ # debug print - self.log.printText(2,'new data event') + self.log.printText(2, 'new data event') # get list of destination IDs dID_list = event.get_destinatioID() # get optional data of event @@ -143,8 +142,9 @@ def process_new_data_event(self, event): # check if new_data is a parameter or new raw data try: if opt.is_parameter is False: - dplugin.plugin.execute(Data=dplugin.plugin.demux(opt.data_source_id, opt.block_name, opt.data), - block_name=opt.block_name) + dplugin.plugin.execute( + Data=dplugin.plugin.demux(opt.data_source_id, opt.block_name, opt.data), + block_name=opt.block_name) else: dplugin.plugin.set_parameter_internal(opt.parameter_alias, opt.data) except Exception as E: @@ -154,7 +154,7 @@ def process_new_data_event(self, event): else: # plugin does not exist in DGUI - self.log.printText(1,'new_data, Plugin with id '+str(dID)+' does not exist in DGui') + self.log.printText(1, 'new_data, Plugin with id ' + str(dID) + ' does not exist in DGui') def process_plugin_closed(self, event): """ @@ -177,13 +177,20 @@ def process_plugin_closed(self, event): self.plugin_died.emit(dplugin, E, tb) if self.gui_data.rm_dplugin(opt.plugin_id) == ERROR.NO_ERROR: - self.log.printText(1,'plugin_closed, Plugin with id: '+str(opt.plugin_id)+' was removed in GUI') + self.log.printText(1, 'plugin_closed, Plugin with id: ' + str(opt.plugin_id) + ' was removed in GUI') self.dgui_changed.emit() self.removed_dplugin.emit(dplugin) else: - self.log.printText(1,'plugin_closed, Plugin with id: '+str(opt.plugin_id)+' was NOT removed in GUI') + self.log.printText(1, 'plugin_closed, Plugin with id: ' + str(opt.plugin_id) + ' was NOT removed in GUI') def process_stop_plugin(self, event): + """ + Processes plugin_stop events. + Quit plugin, emit DPlugin was removed -> necessary signal for the GUI. + + :param event: + :return: + """ id = event.get_destinatioID() dplugin = self.gui_data.get_dplugin_by_id(id) if dplugin is not None: @@ -197,6 +204,13 @@ def process_stop_plugin(self, event): self.plugin_died.emit(dplugin, E, tb) def process_start_plugin(self, event): + """ + Processes plugin_start event. + Used to start a plugin after the plugin was stopped. Emit DPlugin was added -> necessary signal for the GUI. + + :param event: + :return: + """ id = event.get_destinatioID() dplugin = self.gui_data.get_dplugin_by_id(id) if dplugin is not None: @@ -213,9 +227,6 @@ def process_start_plugin(self, event): self.dgui_changed.emit() - - - def process_create_plugin(self, event): """ Processes the create Plugin event. This event got sent by core to GUI. @@ -234,7 +245,8 @@ def process_create_plugin(self, event): config = opt.plugin_config # debug print - self.log.printText(2,'create_plugin, Try to create plugin with Name '+plugin_identifier+ " and UName " + uname ) + self.log.printText(2, + 'create_plugin, Try to create plugin with Name ' + plugin_identifier + " and UName " + uname) # collect plugin in folder for yapsy manager self.plugin_manager.collectPlugins() @@ -244,7 +256,8 @@ def process_create_plugin(self, event): # check for existance if plugin_orginal is None: # plugin with given identifier does not exist - self.log.printText(1, 'create_plugin, Plugin with Name ' + plugin_identifier + ' does not exist in file system') + self.log.printText(1, + 'create_plugin, Plugin with Name ' + plugin_identifier + ' does not exist in file system') # end function return -1 @@ -260,29 +273,30 @@ def process_create_plugin(self, event): plugin = getattr(current_modul, class_name)() # get default startup configuration for merge with user defined startup_configuration start_config = plugin.get_startup_configuration() - config = dict(list(start_config.items()) + list(config.items()) ) + config = dict(list(start_config.items()) + list(config.items())) # check if plugin in ViP (includes pcp) or something which is not running in the gui process if plugin.get_type() == PLUGIN_VIP_IDENTIFIER or plugin.get_type() == PLUGIN_PCP_IDENTIFIER: # plugin in running in gui process # add a new dplugin object to DGui and set its type and uname - dplugin =self.gui_data.add_plugin(None, None, False, self.gui_queue,plugin,id) + dplugin = self.gui_data.add_plugin(None, None, False, self.gui_queue, plugin, id) dplugin.uname = uname dplugin.type = opt.plugin_type dplugin.plugin_identifier = plugin_identifier dplugin.startup_config = config # call the init function of plugin and set queues and id - api = Plugin_api(self.gui_data,self.core_queue,self.gui_id, uname + ' API:') + api = Plugin_api(self.gui_data, self.core_queue, self.gui_id, uname + ' API:') # call the plugin developers init function with config try: - dplugin.plugin.init_plugin(self.core_queue, self.gui_queue, dplugin.id, api, dpluginInfo=dplugin.get_meta()) + dplugin.plugin.init_plugin(self.core_queue, self.gui_queue, dplugin.id, api, + dpluginInfo=dplugin.get_meta()) if dplugin.plugin.start_init(copy.deepcopy(config)) is True: - #start succcessfull - self.core_queue.put( Event.status.StartSuccessfull(dplugin.id, 0, None)) + # start succcessfull + self.core_queue.put(Event.status.StartSuccessfull(dplugin.id, 0, None)) else: - self.core_queue.put( Event.status.StartFailed(dplugin.id, 0, None)) + self.core_queue.put(Event.status.StartFailed(dplugin.id, 0, None)) # first set meta to plugin (meta infos in plugin) if dplugin.state not in [PLUGIN_STATE_STOPPED]: @@ -296,29 +310,31 @@ def process_create_plugin(self, event): # debug print - self.log.printText(1,'create_plugin, Plugin with name '+str(uname)+' was started as ViP') + self.log.printText(1, 'create_plugin, Plugin with name ' + str(uname) + ' was started as ViP') else: # plugin will not be running in gui process, so we just need to add information to DGui # so add a new dplugin to DGUI and set name und type - dplugin =self.gui_data.add_plugin(None,None,True,None,plugin,id) + dplugin = self.gui_data.add_plugin(None, None, True, None, plugin, id) dplugin.plugin_identifier = plugin_identifier dplugin.uname = uname dplugin.startup_config = opt.plugin_config dplugin.type = opt.plugin_type # debug print - self.log.printText(1,'create_plugin, Plugin with name '+str(uname)+' was added as non ViP') + self.log.printText(1, 'create_plugin, Plugin with name ' + str(uname) + ' was added as non ViP') self.added_dplugin.emit(dplugin) self.dgui_changed.emit() def process_close_program_event(self, event): """ + Processes close programm event. + Nothing important happens. :param event: event to process :type event: PapiEvent :type dplugin: DPlugin """ - self.log.printText(1,'event: close_progam was received but there is no action for it') + self.log.printText(1, 'event: close_progam was received but there is no action for it') pass def process_check_alive_status(self, event): @@ -330,7 +346,7 @@ def process_check_alive_status(self, event): :type dplugin: DPlugin """ # send event from GUI to Core - event = PapiEvent(1,0,'status_event','alive',None) + event = PapiEvent(1, 0, 'status_event', 'alive', None) self.core_queue.put(event) def process_update_meta(self, event): @@ -360,21 +376,23 @@ def process_update_meta(self, event): except Exception as E: dplugin.state = PLUGIN_STATE_STOPPED tb = traceback.format_exc() - #self.plugin_died.emit(dplugin, E, tb) + # self.plugin_died.emit(dplugin, E, tb) self.dgui_changed.emit() else: # plugin does not exist - self.log.printText(1,'update_meta, Plugin with id '+str(pl_id)+' does not exist') + self.log.printText(1, 'update_meta, Plugin with id ' + str(pl_id) + ' does not exist') - def process_set_parameter(self,event): + def process_set_parameter(self, event): """ + Processes set_parameter event. + Used to handle parameter sets by the gui or other plugins. :param event: :return: """ # debug print - self.log.printText(2,'set parameter event') + self.log.printText(2, 'set parameter event') dID = event.get_destinatioID() # get optional data of event @@ -389,7 +407,7 @@ def process_set_parameter(self,event): dplugin.plugin.set_parameter_internal(opt.parameter_alias, opt.data) else: # plugin does not exist in DGUI - self.log.printText(1,'set_parameter, Plugin with id '+str(dID)+' does not exist in DGui') + self.log.printText(1, 'set_parameter, Plugin with id ' + str(dID) + ' does not exist in DGui') def process_pause_plugin(self, event): """ From 82c0b19e02bdb4973a796f8a7cb73da76e815a3a Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 17:19:44 +0100 Subject: [PATCH 053/260] added comments, formatting --- papi/core.py | 306 ++++++++++++++++++++++++++++----------------------- 1 file changed, 167 insertions(+), 139 deletions(-) diff --git a/papi/core.py b/papi/core.py index 7bfa5014..2df079eb 100644 --- a/papi/core.py +++ b/papi/core.py @@ -43,7 +43,7 @@ # import contants -from papi.constants import CORE_PROCESS_CONSOLE_IDENTIFIER, CORE_CONSOLE_LOG_LEVEL, CORE_PAPI_CONSOLE_START_MESSAGE, \ +from papi.constants import CORE_PROCESS_CONSOLE_IDENTIFIER, CORE_CONSOLE_LOG_LEVEL, CORE_PAPI_CONSOLE_START_MESSAGE, \ CORE_CORE_CONSOLE_START_MESSAGE, CORE_ALIVE_CHECK_ENABLED, \ CORE_STOP_CONSOLE_MESSAGE, CORE_ALIVE_CHECK_INTERVAL, CORE_ALIVE_MAX_COUNT @@ -56,8 +56,9 @@ import papi.event as Event + class Core: - def __init__(self, gui_start_function, use_gui = True): + def __init__(self, gui_start_function, use_gui=True): """ Init funciton of core. Will create all data needed to use core and core.run() function @@ -68,77 +69,79 @@ def __init__(self, gui_start_function, use_gui = True): self.gui_start_function = gui_start_function # switch case structure for processing incoming events - self.__process_event_by_type__ = { 'status_event': self.__process_status_event__, - 'data_event': self.__process_data_event__, - 'instr_event': self.__process_instr_event__, + self.__process_event_by_type__ = {'status_event': self.__process_status_event__, + 'data_event': self.__process_data_event__, + 'instr_event': self.__process_instr_event__, } - self.__process_status_event_l__ = { 'start_successfull': self.__process_start_successfull__, - 'start_failed': self.__process_start_failed__, - 'alive': self.__process_alive__, - 'join_request': self.__process_join_request__, - 'plugin_stopped': self.__process_plugin_stopped__ + self.__process_status_event_l__ = {'start_successfull': self.__process_start_successfull__, + 'start_failed': self.__process_start_failed__, + 'alive': self.__process_alive__, + 'join_request': self.__process_join_request__, + 'plugin_stopped': self.__process_plugin_stopped__ } - self.__process_data_event_l__ = { 'new_data': self.__process_new_data__, - 'new_block': self.__process_new_block__, - 'new_parameter': self.__process_new_parameter__, - 'edit_dplugin' : self.__process_edit_dplugin, + self.__process_data_event_l__ = {'new_data': self.__process_new_data__, + 'new_block': self.__process_new_block__, + 'new_parameter': self.__process_new_parameter__, + 'edit_dplugin': self.__process_edit_dplugin, } - self.__process_instr_event_l__ = { 'create_plugin': self.__process_create_plugin__, - 'stop_plugin': self.__process_stop_plugin__, - 'close_program': self.__process_close_programm__, - 'subscribe': self.__process_subscribe__, - 'unsubscribe': self.__process_unsubsribe__, - 'set_parameter': self.__process_set_parameter__, - 'pause_plugin': self.__process_pause_plugin__, - 'resume_plugin': self.__process_resume_plugin__, - 'start_plugin': self.__process_start_plugin__ + self.__process_instr_event_l__ = {'create_plugin': self.__process_create_plugin__, + 'stop_plugin': self.__process_stop_plugin__, + 'close_program': self.__process_close_programm__, + 'subscribe': self.__process_subscribe__, + 'unsubscribe': self.__process_unsubsribe__, + 'set_parameter': self.__process_set_parameter__, + 'pause_plugin': self.__process_pause_plugin__, + 'resume_plugin': self.__process_resume_plugin__, + 'start_plugin': self.__process_start_plugin__ } # creating the main core data object DCore and core queue - self.core_data = DCore() - self.core_event_queue = Queue() - self.core_goOn = 1 - self.core_id = 0 + self.core_data = DCore() + self.core_event_queue = Queue() + self.core_goOn = 1 + self.core_id = 0 # setting up the yapsy plguin manager for directory structure - self.plugin_manager = PluginManager() + self.plugin_manager = PluginManager() self.plugin_manager.setPluginPlaces(PLUGIN_ROOT_FOLDER_LIST) # define gui information. ID and alive status as well as gui queue for events - self.gui_event_queue = Queue() - self.gui_id = self.core_data.create_id() - self.gui_alive = False - self.use_gui = use_gui + self.gui_event_queue = Queue() + self.gui_id = self.core_data.create_id() + self.gui_alive = False + self.use_gui = use_gui # set information for console logging part (debug information) - self.log = ConsoleLog(CORE_CONSOLE_LOG_LEVEL, CORE_PROCESS_CONSOLE_IDENTIFIER) + self.log = ConsoleLog(CORE_CONSOLE_LOG_LEVEL, CORE_PROCESS_CONSOLE_IDENTIFIER) # define variables for check alive system, e.a. timer and counts - self.alive_intervall = CORE_ALIVE_CHECK_INTERVAL - self.alive_count_max = CORE_ALIVE_MAX_COUNT - self.alive_timer = Timer(self.alive_intervall,self.check_alive_callback) - self.alive_count = 0 - self.gui_alive_count = 0 + self.alive_intervall = CORE_ALIVE_CHECK_INTERVAL + self.alive_count_max = CORE_ALIVE_MAX_COUNT + self.alive_timer = Timer(self.alive_intervall, self.check_alive_callback) + self.alive_count = 0 + self.gui_alive_count = 0 def run(self): """ Main operation function of core. Event loop is in here. - """ + :return: + """ # some start up information - self.log.printText(1,CORE_PAPI_CONSOLE_START_MESSAGE) - self.log.printText(1,CORE_CORE_CONSOLE_START_MESSAGE + ' .. Process id: '+str(os.getpid())) + self.log.printText(1, CORE_PAPI_CONSOLE_START_MESSAGE) + self.log.printText(1, CORE_CORE_CONSOLE_START_MESSAGE + ' .. Process id: ' + str(os.getpid())) # start the GUI process to show GUI, set GUI alive status to TRUE if self.use_gui is True: - self.gui_process = Process(target=self.gui_start_function, args=(self.core_event_queue,self.gui_event_queue,self.gui_id)) + self.gui_process = Process(target=self.gui_start_function, + args=(self.core_event_queue, self.gui_event_queue, self.gui_id)) self.gui_process.start() self.gui_alive = True - + # start the check alive timer if CORE_ALIVE_CHECK_ENABLED is True: @@ -151,7 +154,7 @@ def run(self): event = self.core_event_queue.get() # debung out - self.log.printText(2,'Event->'+event.get_eventtype()+' '+event.get_event_operation()) + self.log.printText(2, 'Event->' + event.get_eventtype() + ' ' + event.get_event_operation()) # process the next event of queue self.__process_event__(event) @@ -166,9 +169,13 @@ def run(self): self.log.printText(1, CORE_STOP_CONSOLE_MESSAGE) def send_alive_check_events(self): - """Function for check_alive_status timer to send check_alive events to all running processes + """ + Function for check_alive_status timer to send check_alive events to all running processes This will trigger all correctly running processes to send an answer to core + + :return: """ + # get list of all plugins [hash: id->dplug], type DPlugin dplugs = self.core_data.get_all_plugins() for id in dplugs: @@ -191,7 +198,7 @@ def handle_alive_situation(self): :return: """ - #get all plugins from core data [hash: id->DPlugin] + # get all plugins from core data [hash: id->DPlugin] dplugs = self.core_data.get_all_plugins() if dplugs is not None: for id in dplugs: @@ -201,7 +208,7 @@ def handle_alive_situation(self): if plug.own_process is True: # check if counts are equal: equal indicates that plugin is alive if plug.alive_count is self.alive_count: - self.log.printText(2,'Plugin '+plug.uname+' is still alive') + self.log.printText(2, 'Plugin ' + plug.uname + ' is still alive') # change plugin state in DCore plug.alive_state = PLUGIN_STATE_ALIVE else: @@ -210,17 +217,19 @@ def handle_alive_situation(self): # check for gui status and do error handling if self.gui_alive_count is self.alive_count: - self.log.printText(2,'GUI is still ALIVE') + self.log.printText(2, 'GUI is still ALIVE') else: self.gui_is_dead_error_handler() def gui_is_dead_error_handler(self): """ error handler when gui is dead + :return: """ - self.log.printText(1,'GUI is DEAD') - self.log.printText(1,'core count: '+str(self.alive_count)+' vs. '+ str(self.gui_alive_count)+' :gui count') + self.log.printText(1, 'GUI is DEAD') + self.log.printText(1, + 'core count: ' + str(self.alive_count) + ' vs. ' + str(self.gui_alive_count) + ' :gui count') def plugin_process_is_dead_error_handler(self, dplug): """ @@ -230,9 +239,10 @@ def plugin_process_is_dead_error_handler(self, dplug): :type dplug: DPlugin :return: """ - self.log.printText(1,'Plugin '+dplug.uname+' is DEAD') - self.log.printText(1,'core count: '+str(self.alive_count)+' vs. '+ str(dplug.alive_count)+' :plugin count') - dplug.alive_state = PLUGIN_STATE_DEAD + self.log.printText(1, 'Plugin ' + dplug.uname + ' is DEAD') + self.log.printText(1, + 'core count: ' + str(self.alive_count) + ' vs. ' + str(dplug.alive_count) + ' :plugin count') + dplug.alive_state = PLUGIN_STATE_DEAD self.update_meta_data_to_gui(dplug.id) def check_alive_callback(self): @@ -242,7 +252,7 @@ def check_alive_callback(self): :return: """ - self.log.printText(2,'check alive') + self.log.printText(2, 'check alive') # check for answer status of all plugins self.handle_alive_situation() @@ -256,10 +266,10 @@ def check_alive_callback(self): # start a new one shot timer with this callback function if CORE_ALIVE_CHECK_ENABLED is True: - self.alive_timer = Timer(self.alive_intervall,self.check_alive_callback) + self.alive_timer = Timer(self.alive_intervall, self.check_alive_callback) self.alive_timer.start() - def update_meta_data_to_gui(self,pl_id, inform_subscriber=False): + def update_meta_data_to_gui(self, pl_id, inform_subscriber=False): """ On call this function will send the meta information of pl_id to GUI @@ -297,7 +307,9 @@ def update_meta_data_to_gui(self,pl_id, inform_subscriber=False): return 1 else: # Dplugin object with pl_id does not exist in DCore of core - self.log.printText(1,'update_meta, cannot update meta information because there is no plugin with id: '+str(pl_id)) + self.log.printText(1, + 'update_meta, cannot update meta information because there is no plugin with id: ' + str( + pl_id)) return -1 def update_meta_data_to_gui_for_all(self): @@ -329,7 +341,7 @@ def handle_parameter_change(self, plugin, parameter_name, value): # ------- Event processing initial stage --------- - def __process_event__(self,event): + def __process_event__(self, event): """ Initial stage of event processing, dividing to event type @@ -340,7 +352,7 @@ def __process_event__(self,event): self.__process_event_by_type__[t](event) # ------- Event processing first stage --------- - def __process_status_event__(self,event): + def __process_status_event__(self, event): """ First stage of event processing, deciding which status_event this is @@ -350,7 +362,7 @@ def __process_status_event__(self,event): op = event.get_event_operation() return self.__process_status_event_l__[op](event) - def __process_data_event__(self,event): + def __process_data_event__(self, event): """ First stage of event processing, deciding which data_event this is @@ -360,7 +372,7 @@ def __process_data_event__(self,event): op = event.get_event_operation() return self.__process_data_event_l__[op](event) - def __process_instr_event__(self,event): + def __process_instr_event__(self, event): """ First stage of event processing, deciding which instr_event this is @@ -371,7 +383,7 @@ def __process_instr_event__(self,event): return self.__process_instr_event_l__[op](event) # ------- Event processing second stage: status events --------- - def __process_start_successfull__(self,event): + def __process_start_successfull__(self, event): """ Process start_successfull event @@ -387,10 +399,11 @@ def __process_start_successfull__(self,event): return 1 else: # plugin does not exist - self.log.printText(1,'start_successfull_event, Event with id ' +str(event.get_originID())+ ' but plugin does not exist') + self.log.printText(1, 'start_successfull_event, Event with id ' + str( + event.get_originID()) + ' but plugin does not exist') return -1 - def __process_start_failed__(self,event): + def __process_start_failed__(self, event): """ Process start failed event and do error handling @@ -405,10 +418,11 @@ def __process_start_failed__(self,event): return 1 else: # plugin does not exist in DCore - self.log.printText(1,'start_failed_event, Event with id ' +str(event.get_originID())+ ' but plugin does not exist') + self.log.printText(1, 'start_failed_event, Event with id ' + str( + event.get_originID()) + ' but plugin does not exist') return -1 - def __process_alive__(self,event): + def __process_alive__(self, event): """ Processes alive response from processes/plugins and GUI, organising the counter @@ -431,7 +445,7 @@ def __process_alive__(self,event): dplug.alive_count = dplug.alive_count % self.alive_count_max return True - def __process_join_request__(self,event): + def __process_join_request__(self, event): """ Process join requests of processes @@ -449,7 +463,7 @@ def __process_join_request__(self,event): # remove plugin from DCore, because process was joined if self.core_data.rm_dplugin(dplugin.id) is False: # remove failed - self.log.printText(1,'join request, remove plugin with id '+str(dplugin.id)+'failed') + self.log.printText(1, 'join request, remove plugin with id ' + str(dplugin.id) + 'failed') return -1 else: # remove from DCore in core successfull @@ -457,19 +471,21 @@ def __process_join_request__(self,event): # GUI is still alive, so tell GUI, that this plugin was closed opt = DOptionalData() opt.plugin_id = dplugin.id - # event = PapiEvent(self.core_id,self.gui_id,'status_event','plugin_closed',opt) + # event = PapiEvent(self.core_id,self.gui_id,'status_event','plugin_closed',opt) event = Event.status.PluginClosed(self.core_id, self.gui_id, opt) self.gui_event_queue.put(event) - self.log.printText(1, 'join_request, plugin with id '+str(dplugin.id) + ' was joined (uname: ' +dplugin.uname+' )') + self.log.printText(1, 'join_request, plugin with id ' + str( + dplugin.id) + ' was joined (uname: ' + dplugin.uname + ' )') return ERROR.NO_ERROR else: # Plug does not exist - self.log.printText(1,'join_request, Event with id ' +str(event.get_originID())+ ' but plugin does not exist') + self.log.printText(1, 'join_request, Event with id ' + str( + event.get_originID()) + ' but plugin does not exist') return -1 # ------- Event processing second stage: data events --------- - def __process_new_data__(self,event): + def __process_new_data__(self, event): """ Process new_data event from plugins. Will do the routing: Subscriber/Subscription @@ -489,7 +505,7 @@ def __process_new_data__(self,event): # check for existence if dplug is not None: # get data block of DPlugin with block_name from event - block= dplug.get_dblock_by_name(opt.block_name) + block = dplug.get_dblock_by_name(opt.block_name) # check for existence of block with block_name if block is not None: # get subscriber list of block @@ -516,13 +532,14 @@ def __process_new_data__(self,event): self.handle_parameter_change(pl, opt.parameter_alias, opt.data) else: # pluign with sub_id does not exist in DCore of core - self.log.printText(1, 'new_data, subscriber plugin with id '+str(sub_id)+' does not exists') + self.log.printText(1, 'new_data, subscriber plugin with id ' + str( + sub_id) + ' does not exists') return -1 # check if our list with id is greater than 1, which will indicate that there is at least one ViP # which will need to get this new data event - if len(id_list)> 0: + if len(id_list) > 0: # send new_data event to GUI with id_list of destinations - opt = event.get_optional_parameter() + opt = event.get_optional_parameter() opt.parameter_alias = pl.get_subscribtions()[oID][opt.block_name].alias new_event = Event.data.NewData(oID, id_list, opt) self.gui_event_queue.put(new_event) @@ -530,14 +547,14 @@ def __process_new_data__(self,event): return 1 else: # block is None - self.log.printText(1, 'new_data, block with name '+opt.block_name+' does not exists') + self.log.printText(1, 'new_data, block with name ' + opt.block_name + ' does not exists') return -1 else: # Plugin of event origin does not exist in DCore of core - self.log.printText(1,'new_data, Plugin with id '+str(oID)+' does not exist in DCore') + self.log.printText(1, 'new_data, Plugin with id ' + str(oID) + ' does not exist in DCore') return -1 - def BACKUP__process_new_data__(self,event): + def BACKUP__process_new_data__(self, event): """ Process new_data event from plugins. Will do the routing: Subscriber/Subscription @@ -558,7 +575,7 @@ def BACKUP__process_new_data__(self,event): # check for existence if dplug is not None: # get data block of DPlugin with block_name from event - block= dplug.get_dblock_by_name(opt.block_name) + block = dplug.get_dblock_by_name(opt.block_name) # check for existence of block with block_name if block is not None: # get subscriber list of block @@ -580,7 +597,7 @@ def BACKUP__process_new_data__(self,event): # plugin exists, check whether it is a ViP or not if pl.type == PLUGIN_VIP_IDENTIFIER or pl.type == PLUGIN_PCP_IDENTIFIER: # Plugin runs in GUI - opt = event.get_optional_parameter() + opt = event.get_optional_parameter() opt.parameter_alias = pl.get_subscribtions()[oID][opt.block_name].alias new_event = Event.data.NewData(oID, [pl.id], opt) self.gui_event_queue.put(new_event) @@ -595,18 +612,19 @@ def BACKUP__process_new_data__(self,event): self.handle_parameter_change(pl, opt.parameter_alias, opt.data) else: # pluign with sub_id does not exist in DCore of core - self.log.printText(1, 'new_data, subscriber plugin with id '+str(sub_id)+' does not exists') + self.log.printText(1, 'new_data, subscriber plugin with id ' + str( + sub_id) + ' does not exists') return -1 # process new_data seemed correct return 1 else: # block is None - self.log.printText(1, 'new_data, block with name '+opt.block_name+' does not exists') + self.log.printText(1, 'new_data, block with name ' + opt.block_name + ' does not exists') return -1 else: # Plugin of event origin does not exist in DCore of core - self.log.printText(1,'new_data, Plugin with id '+str(oID)+' does not exist in DCore') + self.log.printText(1, 'new_data, Plugin with id ' + str(oID) + ' does not exist in DCore') return -1 def __process_edit_dplugin(self, event): @@ -638,12 +656,13 @@ def __process_edit_dplugin(self, event): dsignal.dname = cObject.dname - self.log.printText(1,'edit_dplugin, Edited Dblock '+dblock.name+' of DPlugin '+pl.uname+ " : DSignal " + dsignal.uname + " to dname -> " + dsignal.dname ) + self.log.printText(1, + 'edit_dplugin, Edited Dblock ' + dblock.name + ' of DPlugin ' + pl.uname + " : DSignal " + dsignal.uname + " to dname -> " + dsignal.dname) self.update_meta_data_to_gui(pl.id, True) # ------- Event processing second stage: instr events --------- - def __process_create_plugin__(self,event): + def __process_create_plugin__(self, event): """ Processes create_plugin event. So it will create a plugin, start a process if needed, send events to GUI to create a plugin and do the pre configuration @@ -656,7 +675,8 @@ def __process_create_plugin__(self,event): """ # get optData to get information about which plugin to start optData = event.get_optional_parameter() - self.log.printText(2,'create_plugin, Try to create plugin with Name '+optData.plugin_identifier+ " and UName " + optData.plugin_uname ) + self.log.printText(2, + 'create_plugin, Try to create plugin with Name ' + optData.plugin_identifier + " and UName " + optData.plugin_uname) # search yapsy plugin object with plugin_idientifier and check if it exists plugin = self.plugin_manager.getPluginByName(optData.plugin_identifier) @@ -666,10 +686,11 @@ def __process_create_plugin__(self,event): self.plugin_manager.collectPlugins() plugin = self.plugin_manager.getPluginByName(optData.plugin_identifier) if plugin is None: - self.log.printText(1,'create_plugin, Plugin with Name '+optData.plugin_identifier+' does not exist in file system') + self.log.printText(1, + 'create_plugin, Plugin with Name ' + optData.plugin_identifier + ' does not exist in file system') return -1 - #creates a new plugin id because plugin exsits + # creates a new plugin id because plugin exsits plugin_id = self.core_data.create_id() # checks if plugin is of not of type ViP or PCP, because these two will run in GUI process @@ -682,7 +703,7 @@ def __process_create_plugin__(self,event): plugin_queue = Queue() # decide if plugin will need to get Data from another plugin - if plugin.plugin_object.get_type()== PLUGIN_DPP_IDENTIFIER: + if plugin.plugin_object.get_type() == PLUGIN_DPP_IDENTIFIER: # plugin will get data from another, so make its execution triggered by events eventTriggered = True else: @@ -691,14 +712,15 @@ def __process_create_plugin__(self,event): plugin_config = optData.plugin_config - if plugin_config is None or plugin_config =={}: + if plugin_config is None or plugin_config == {}: plugin_config = plugin.plugin_object.get_startup_configuration() else: - plugin_config = dict(list(plugin_config.items()) + list(plugin.plugin_object.get_startup_configuration().items())) + plugin_config = dict( + list(plugin_config.items()) + list(plugin.plugin_object.get_startup_configuration().items())) # create Process object for new plugin # set parameter for work function of plugin, such as queues, id and eventTriggered - PluginProcess = Process(target=plugin.plugin_object.work_process,\ + PluginProcess = Process(target=plugin.plugin_object.work_process, \ args=(self.core_event_queue, plugin_queue, plugin_id, eventTriggered, \ plugin_config, optData.autostart)) PluginProcess.start() @@ -724,7 +746,8 @@ def __process_create_plugin__(self,event): # Plugin will run in GUI, thats why core does not need to create a new process # Adding plugin information to DCore of core for organisation - dplug = self.core_data.add_plugin(self.gui_process, self.gui_process.pid, False, self.gui_event_queue, plugin, plugin_id) + dplug = self.core_data.add_plugin(self.gui_process, self.gui_process.pid, False, self.gui_event_queue, + plugin, plugin_id) dplug.uname = optData.plugin_uname dplug.type = plugin.plugin_object.get_type() dplug.plugin_identifier = plugin.name @@ -738,10 +761,10 @@ def __process_create_plugin__(self,event): # send create_plugin event to GUI for local creation, now with new information like id and type event = Event.instruction.CreatePlugin(0, self.gui_id, optData) self.gui_event_queue.put(event) - self.log.printText(2,'core sent create event to gui for plugin: '+str(optData.plugin_uname)) + self.log.printText(2, 'core sent create event to gui for plugin: ' + str(optData.plugin_uname)) return 1 - def __process_stop_plugin__(self,event): + def __process_stop_plugin__(self, event): """ Process stop_plugin event. Will send an event to destination plugin to close itself. Will lead to a join request of this plugin. @@ -772,7 +795,7 @@ def __process_stop_plugin__(self,event): # tell gui to close plugin opt = DOptionalData() opt.plugin_id = id - dplugin.queue.put( Event.status.PluginClosed(self.core_id, self.gui_id, opt)) + dplugin.queue.put(Event.status.PluginClosed(self.core_id, self.gui_id, opt)) # remove plugin from DCore if self.core_data.rm_dplugin(id) != ERROR.NO_ERROR: @@ -780,7 +803,7 @@ def __process_stop_plugin__(self,event): return ERROR.UNKNOWN_ERROR else: - dplugin.queue.put( Event.instruction.StopPlugin(self.core_id, id, None, delete=False)) + dplugin.queue.put(Event.instruction.StopPlugin(self.core_id, id, None, delete=False)) dplugin.state = PLUGIN_STATE_STOPPED self.core_data.unsubscribe_all(dplugin.id) self.core_data.rm_all_subscribers(dplugin.id) @@ -789,11 +812,11 @@ def __process_stop_plugin__(self,event): return ERROR.NO_ERROR else: # DPlugin does not exist - self.log.printText(1,'stop_plugin, plugin with id '+str(id)+' not found') + self.log.printText(1, 'stop_plugin, plugin with id ' + str(id) + ' not found') return ERROR.UNKNOWN_ERROR def __process_start_plugin__(self, event): - # get destination id + # get destination id id = event.get_destinatioID() # get DPlugin object and check if plugin exists @@ -810,7 +833,6 @@ def __process_start_plugin__(self, event): self.update_meta_data_to_gui(id) - def __process_plugin_stopped__(self, event): """ Process plugin_stopped event. @@ -850,7 +872,7 @@ def __process_plugin_stopped__(self, event): # update to gui self.update_meta_data_to_gui(pl_id) - def __process_close_programm__(self,event): + def __process_close_programm__(self, event): """ This functions processes a close_programm event from GUI and sends events to all processes to close themselves @@ -871,7 +893,7 @@ def __process_close_programm__(self,event): # iterate through all plugins to send an event to tell them to quit and # response with a join_request - toDelete =[] + toDelete = [] for dplugin_key in all_plugins: dplugin = all_plugins[dplugin_key] # just send event to plugins running in own process @@ -884,7 +906,7 @@ def __process_close_programm__(self,event): for dplugin_ID in toDelete: self.core_data.rm_dplugin(dplugin_ID) - def __process_subscribe__(self,event): + def __process_subscribe__(self, event): """ Process subscribe_event. Will set a new route in DCore for this two plugins to route new data events. Update of meta will be send to GUI. @@ -898,7 +920,6 @@ def __process_subscribe__(self,event): opt = event.get_optional_parameter() oID = event.get_originID() - already_sub = False # test if already subscribed source_pl = self.core_data.get_dplugin_by_id(opt.source_ID) @@ -914,7 +935,8 @@ def __process_subscribe__(self,event): dsubscription = self.core_data.subscribe(oID, opt.source_ID, opt.block_name) if dsubscription is None: # subscribtion failed - self.log.printText(1,'subscribe, something failed in subsription process with subscriber id: '+str(oID)+'..target id:'+str(opt.source_ID)+'..and block '+str(opt.block_name)) + self.log.printText(1, 'subscribe, something failed in subsription process with subscriber id: ' + str( + oID) + '..target id:' + str(opt.source_ID) + '..and block ' + str(opt.block_name)) return -1 else: # subscribtion correct @@ -922,17 +944,19 @@ def __process_subscribe__(self,event): if opt.signals != []: if self.core_data.subscribe_signals(oID, opt.source_ID, opt.block_name, opt.signals) is None: - # subscribtion failed - self.log.printText(1,'subscribe, something failed in subsription process with subscriber id: '+str(oID)+'..target id:'+str(opt.source_ID)+'..and block '+str(opt.block_name)) + # subscribtion failed + self.log.printText(1, 'subscribe, something failed in subsription process with subscriber id: ' + str( + oID) + '..target id:' + str(opt.source_ID) + '..and block ' + str(opt.block_name)) return -1 else: pass - self.log.printText(1,'subscribe, subscribtion correct: '+str(oID)+'->('+str(opt.source_ID)+','+str(opt.block_name)+')') + self.log.printText(1, 'subscribe, subscribtion correct: ' + str(oID) + '->(' + str(opt.source_ID) + ',' + str( + opt.block_name) + ')') self.update_meta_data_to_gui(oID) self.update_meta_data_to_gui(opt.source_ID) - def __process_unsubsribe__(self,event): + def __process_unsubsribe__(self, event): """ Process unsubscribe_event. Will try to remove a subscription from DCore @@ -949,18 +973,23 @@ def __process_unsubsribe__(self,event): # try to unsubscribe if self.core_data.unsubscribe(oID, opt.source_ID, opt.block_name) is False: # unsubscribe failed - self.log.printText(1,'unsubscribe, something failed in unsubsription process with subscriber id: '+str(oID)+'..target id:'+str(opt.source_ID)+'..and block '+str(opt.block_name)) + self.log.printText(1, + 'unsubscribe, something failed in unsubsription process with subscriber id: ' + str( + oID) + '..target id:' + str(opt.source_ID) + '..and block ' + str( + opt.block_name)) return -1 else: if self.core_data.unsubscribe_signals(oID, opt.source_ID, opt.block_name, opt.signals) is False: return -1 # unsubscribe correct - self.log.printText(1,'unsubscribe, unsubscribtion correct: '+str(oID)+'->('+str(opt.source_ID)+','+str(opt.block_name)+')') + self.log.printText(1, + 'unsubscribe, unsubscribtion correct: ' + str(oID) + '->(' + str(opt.source_ID) + ',' + str( + opt.block_name) + ')') self.update_meta_data_to_gui(oID) self.update_meta_data_to_gui(opt.source_ID) - def __process_set_parameter__(self,event): + def __process_set_parameter__(self, event): """ Process set_parameter event. Core will just route this event from GUI to destination plugin and update DCore @@ -980,22 +1009,21 @@ def __process_set_parameter__(self,event): # get parameter list of plugin [hash] parameters = dplugin.get_parameters() - if opt.parameter_alias in parameters: para = parameters[opt.parameter_alias] para.value = opt.data # route the event to the destination plugin queue dplugin.queue.put(event) - #change event type for plugin + # change event type for plugin #update GUI self.update_meta_data_to_gui(pl_id) return 1 else: # destination plugin does not exist - self.log.printText(1,'set_paramenter, plugin with id '+str(pl_id)+' not found') + self.log.printText(1, 'set_paramenter, plugin with id ' + str(pl_id) + ' not found') return -1 - def __process_new_block__(self,event): + def __process_new_block__(self, event): """ Processes new_block event. Will try to add a new data block to a DPlugin object @@ -1009,7 +1037,7 @@ def __process_new_block__(self,event): opt = event.get_optional_parameter() pl_id = event.get_originID() # get DPlugin object with origin id of event to add new parameter to THIS DPlugin - dplugin = self.core_data.get_dplugin_by_id(pl_id) + dplugin = self.core_data.get_dplugin_by_id(pl_id) # check for existence if dplugin is not None: # dplugin exists, so add blocks to DPlugin @@ -1020,10 +1048,10 @@ def __process_new_block__(self,event): return 1 else: # plugin does not exist - self.log.printText(1,'new_block, plugin with id '+str(pl_id)+' not found') + self.log.printText(1, 'new_block, plugin with id ' + str(pl_id) + ' not found') return -1 - def __process_new_parameter__(self,event): + def __process_new_parameter__(self, event): """ Processes new parameter event. Adding a new parameter to DPluign in DCore and updating GUI information @@ -1048,7 +1076,7 @@ def __process_new_parameter__(self,event): return 1 else: # plugin does not exist - self.log.printText(1,'new_parameter, plugin with id '+str(pl_id)+' not found') + self.log.printText(1, 'new_parameter, plugin with id ' + str(pl_id) + ' not found') return -1 def __process_pause_plugin__(self, event): @@ -1097,21 +1125,21 @@ def __process_resume_plugin__(self, event): # update meta for Gui self.update_meta_data_to_gui(pl_id) - # def __process_plugin_paused__(self, event): - # """ - # Processes plugin_paused event from GUI. Will add information that a plugin was paused in gui and - # send update meta. - # :param event: event to process - # :type event: PapiEvent - # :type dplugin: DPlugin - # """ - # id = event.get_originID() - # - # dplugin = self.core_data.get_dplugin_by_id(id) - # if dplugin is not None: - # dplugin.state = PLUGIN_STATE_PAUSE - # - # self.update_meta_data_to_gui(id) - # - # def __process_plugin_resumed__(self, event): - # pass \ No newline at end of file + # def __process_plugin_paused__(self, event): + # """ + # Processes plugin_paused event from GUI. Will add information that a plugin was paused in gui and + # send update meta. + # :param event: event to process + # :type event: PapiEvent + # :type dplugin: DPlugin + # """ + # id = event.get_originID() + # + # dplugin = self.core_data.get_dplugin_by_id(id) + # if dplugin is not None: + # dplugin.state = PLUGIN_STATE_PAUSE + # + # self.update_meta_data_to_gui(id) + # + # def __process_plugin_resumed__(self, event): + # pass \ No newline at end of file From 357b3e5e16b8c4b1df07f5d5365bca9024b5c021 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 17:20:19 +0100 Subject: [PATCH 054/260] added comments, formatting --- papi/gui/plugin_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/papi/gui/plugin_api.py b/papi/gui/plugin_api.py index 4ee5cd15..eaa68f4c 100644 --- a/papi/gui/plugin_api.py +++ b/papi/gui/plugin_api.py @@ -87,8 +87,8 @@ def do_set_parameter_uname(self, plugin_uname, parameter_name, value): """ Something like a callback function for gui triggered events. User wants to change a parameter of a plugin - :param plugin_uname: name of plugin which owns the parameter + :param plugin_uname: name of plugin which owns the parameter :type plugin_uname: basestring :param parameter_name: name of parameter to change :type parameter_name: basestring @@ -115,8 +115,8 @@ def do_set_parameter(self, plugin_id, parameter_name, value): """ Something like a callback function for gui triggered events. User wants to change a parameter of a plugin - :param plugin_id: id of plugin which owns the parameter + :param plugin_id: id of plugin which owns the parameter :type plugin_id: int :param parameter_name: name of parameter to change :type parameter_name: basestring From d593ca79feac3daf45770f9e6217eed1ab49df0f Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 17:23:43 +0100 Subject: [PATCH 055/260] added comments, formatting --- papi/gui/qt_new/overview_menu.py | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/papi/gui/qt_new/overview_menu.py b/papi/gui/qt_new/overview_menu.py index 62360022..7392fed0 100644 --- a/papi/gui/qt_new/overview_menu.py +++ b/papi/gui/qt_new/overview_menu.py @@ -158,6 +158,7 @@ def __init__(self, gui_api, parent=None): def clear(self): """ This function will clear this window. + :return: """ self.bModel.clear() @@ -179,6 +180,7 @@ def plugin_item_changed(self, index): """ Used to display all known information for a DPlugin which is accessible in the pluginTree by its index. + :param index: Current selected index :return: """ @@ -321,6 +323,7 @@ def open_context_menu_dplugin_tree(self, position): """ This callback function is called to create a context menu for the dplugin tree + :param position: :return: """ @@ -347,6 +350,7 @@ def open_context_menu_block_tree(self, position): """ This callback function is called to create a context menu for the block tree + :param position: :return: """ @@ -389,6 +393,7 @@ def open_context_menu_subscriber_tree(self, position): """ This callback function is called to create a context menu for the subscriper tree + :param position: :return: """ @@ -419,6 +424,7 @@ def open_context_menu_subscription_tree(self, position): """ This callback function is called to create a context menu for the subscription tree + :param position: :return: """ @@ -487,6 +493,7 @@ def open_context_menu_parameter_tree(self, position): """ This callback function is called to create a context menu for the parameter tree + :param position: :return: """ @@ -546,8 +553,9 @@ def add_pcp_subscription_action(self, dplugin: DPlugin, dparameter: DParameter, def add_subscription_action(self, dplugin_uname): """ + Used to add subscription for a specific dplugin - :param dplugin_uname: + :param dplugin_uname: Add Subscription for this DPlugin :return: """ @@ -578,9 +586,10 @@ def add_subscription_action(self, dplugin_uname): def remove_subscriber_action(self, subscriber: DPlugin, dblock: DBlock): """ + Used to remove a subscriber of the dplugin selected in the DPlugin tree. - :param subscriber: - :param dblock: + :param subscriber: Subscriber which is effected + :param dblock: DBlock which should be unsubscribed by Subscriber :return: """ index = self.pluginTree.currentIndex() @@ -592,6 +601,7 @@ def remove_subscriber_action(self, subscriber: DPlugin, dblock: DBlock): def refresh_action(self, new_dplugin: DPlugin=None): """ Used to refresh the overview menu view. + :param new_dplugin: New dplugin which should be added in self.dpluginTreev. :return: """ @@ -636,6 +646,7 @@ def refresh_action(self, new_dplugin: DPlugin=None): def cancel_subscription_action(self, source: DPlugin, dblock: DBlock, signals: []): """ Action called to cancel a subscription of the current selected dplugin. + :param source: :param dblock: :return: @@ -648,6 +659,7 @@ def cancel_subscription_action(self, source: DPlugin, dblock: DBlock, signals: [ def showEvent(self, *args, **kwargs): """ ShowEvent of this class. + :param args: :param kwargs: :return: @@ -673,6 +685,7 @@ def showEvent(self, *args, **kwargs): def play_button_callback(self): """ Callback function for the play button. + :return: """ index = self.pluginTree.currentIndex() @@ -722,6 +735,7 @@ def stop_start_button_callback(self): def show_internal_name_callback(self): """ Callback function for 'showInternalNameCheckBox' + :return: """ self.plugin_item_changed(self.pluginTree.currentIndex()) @@ -729,6 +743,7 @@ def show_internal_name_callback(self): def data_changed_parameter_model(self, index, n): """ This function is called when a dparameter value is changed by editing the 'value'-column. + :param index: Index of current changed dparameter :param n: None :return: @@ -744,6 +759,7 @@ def data_changed_parameter_model(self, index, n): def data_changed_block_model(self, index, n): """ This function is called when a dblock child, a disgnal, is changed. + :param index: Index of current changed dsignal object :param n: None :return: @@ -756,8 +772,13 @@ def data_changed_block_model(self, index, n): self.gui_api.do_edit_plugin_uname(dplugin.uname, dblock, {"edit" : dsignal}) - def keyPressEvent(self, event): + """ + Used to handle key events for this gui element. + + :param event: KeyEvent + :return: + """ if event.key() == Qt.Key_Escape: self.close() From 42439bfc52cfa30e5373734694f64fe74bb58c92 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 12 Jan 2015 17:59:22 +0100 Subject: [PATCH 056/260] added comments, formatting --- papi/gui/qt_new/main.py | 91 ++++++++++++++++++++++++++++++++--------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index 21f9c904..b016ec4d 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -62,8 +62,20 @@ class GUI(QMainWindow, Ui_QtNewMain): - + """ + Used to create the qt based PaPI gui. + """ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): + """ + Init function + + :param core_queue: Queue used to send papi events to Core + :param gui_queue: GUI queue which contains papi events for the gui + :param gui_id: Unique ID for this gui + :param gui_data: Contains all data for the current session + :param parent: parent element + :return: + """ super(GUI, self).__init__(parent) self.setupUi(self) @@ -123,7 +135,7 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): # Create callback functions for buttons # ------------------------------------- self.loadButton.clicked.connect(self.load_triggered) - self.saveButton.clicked.connect(self.save_triggered_thread) + self.saveButton.clicked.connect(self.save_triggered) # self.buttonCreatePlugin.clicked.connect(self.create_plugin) # self.buttonCreateSubscription.clicked.connect(self.create_subscription) @@ -136,7 +148,7 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): # ------------------------------------- self.actionLoad.triggered.connect(self.load_triggered) - self.actionSave.triggered.connect(self.save_triggered_thread) + self.actionSave.triggered.connect(self.save_triggered) self.actionOverview.triggered.connect(self.show_overview_menu) self.actionCreate.triggered.connect(self.show_create_plugin_menu) @@ -212,6 +224,11 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): # self.buttonShowOverview.setText('') def set_background_for_gui(self): + """ + Used to handle request for setting background image. + + :return: + """ fileNames = '' dialog = QFileDialog(self) @@ -230,26 +247,31 @@ def set_background_for_gui(self): self.widgetArea.setBackground(pixmap) def update_background(self, path): + """ + Used to change the background by giving a path to a picture. + + :param path: Path of a picture. + :return: + """ pixmap = QtGui.QPixmap(path) self.widgetArea.setBackground(pixmap) - def run(self): - # create a timer and set interval for processing events with working loop - - QtCore.QTimer.singleShot(GUI_WOKRING_INTERVAL, lambda: self.gui_event_processing.gui_working(self.closeEvent)) - + """ - def dbg(self): - print("Action") - def menu_license(self): - pass + :return: + """ + # create a timer and set interval for processing events with working loop - def menu_quit(self): - pass + QtCore.QTimer.singleShot(GUI_WOKRING_INTERVAL, lambda: self.gui_event_processing.gui_working(self.closeEvent)) def show_create_plugin_menu(self): + """ + + + :return: + """ self.create_plugin_menu = CreatePluginMenu(self.gui_api) self.create_plugin_menu.show() @@ -261,11 +283,20 @@ def show_create_plugin_menu(self): # self.create_plugin_menu = None def show_overview_menu(self): + """ + Used to show the overview menu. + + :return: + """ self.overview_menu = OverviewPluginMenu(self.gui_api) self.overview_menu.show() def load_triggered(self): + """ + Used to start the 'load config' dialog. + :return: + """ fileNames = '' dialog = QFileDialog(self) @@ -283,7 +314,11 @@ def load_triggered(self): self.gui_api.do_load_xml(fileNames[0]) def save_triggered(self): + """ + Used to start the 'save config' dialog. + :return: + """ fileNames = '' dialog = QFileDialog(self) @@ -300,15 +335,20 @@ def save_triggered(self): if fileNames[0] != '': self.gui_api.do_save_xml_config(fileNames[0]) - - def save_triggered_thread(self): - QtCore.QTimer.singleShot(0, self.save_triggered) - def closeEvent(self, *args, **kwargs): + """ + Handle close event. + Saves current session as 'papi/last_active_papi.xml' + Closes all opened windows. + + :param args: + :param kwargs: + :return: + """ try: self.gui_api.do_save_xml_config('papi/last_active_papi.xml') except Exception as E: - tb = traceback.format_exc() + tb = traceback.format_exc() self.gui_api.do_close_program() if self.create_plugin_menu is not None: @@ -320,7 +360,13 @@ def closeEvent(self, *args, **kwargs): self.close() def add_dplugin(self, dplugin): + """ + Callback function called by 'DPlugin added signal' + Used to add a DPlugin SubWindow on the GUI if possible. + :param dplugin: + :return: + """ if dplugin.type == PLUGIN_VIP_IDENTIFIER or dplugin.type == PLUGIN_PCP_IDENTIFIER: sub_window = dplugin.plugin.get_sub_window() self.widgetArea.addSubWindow(sub_window) @@ -339,6 +385,13 @@ def add_dplugin(self, dplugin): self.overview_menu.refresh_action(dplugin) def remove_dplugin(self, dplugin): + """ + Callback function called by 'DPlugin removed signal' + Used to removed a DPlugin SubWindow from the GUI if possible. + + :param dplugin: + :return: + """ if dplugin.type == PLUGIN_VIP_IDENTIFIER or dplugin.type == PLUGIN_PCP_IDENTIFIER: self.widgetArea.removeSubWindow(dplugin.plugin.get_sub_window()) From 26b70ac79454b05c50f7268ca0e9f1129d79d759 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 12 Jan 2015 18:04:28 +0100 Subject: [PATCH 057/260] core: cfg merge was inverted --- cfg_collection/ExampleFourier.xml | 325 +++++++++--------- papi/core.py | 3 +- .../ProtocollConfig.json | 5 +- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 2 - 4 files changed, 168 insertions(+), 167 deletions(-) diff --git a/cfg_collection/ExampleFourier.xml b/cfg_collection/ExampleFourier.xml index 5e356ed8..a870b489 100644 --- a/cfg_collection/ExampleFourier.xml +++ b/cfg_collection/ExampleFourier.xml @@ -1,6 +1,166 @@ - - + + + + Plot + + + Label-X + time, s + \w+,\s*\w+ + + + xRange-auto + [0.0 1.0] + (\d+\.\d+) + + + 1 + Determine position: (x,y) + (0,0) + \(([0-9]+),([0-9]+)\) + + + 1 + (\d+) + + + Label-Y + amplitude, V + \w+,\s+\w+ + + + Used for window title + VisualPlugin + + + 1 + Color + [0 1 2 3 4] + ^\[(\s*\d\s*)+\] + + + bool + xRange-auto + 1 + ^(1|0)$ + + + bool + yRange-auto + 1 + ^(1|0)$ + + + yRange-auto + [0.0 1.0] + (\d+\.\d+) + + + 1 + Determine size: (height,width) + (300,300) + \(([0-9]+),([0-9]+)\) + + + 1 + Style + [0 0 0 0 0] + ^\[(\s*\d\s*)+\] + + + bool + Grid-X + 0 + ^(1|0)$ + + + bool + Rolling Plot + 0 + ^(1|0)$ + + + Plot + + + bool + Grid-Y + 0 + ^(1|0)$ + + + 1 + Buffersize + 1000 + ^([1-9][0-9]{0,3}|10000)$ + + + + [0,1] + 1000 + 0 + [0 0 0 0 0] + [0 1 2 3 4] + 0 + 1 + 1 + 1 + 0 + [0,1] + + + + + Add + + + Sum + + + + + + Add + + + Add + + + + + + + Sum + + + + + + FourierXRectXMOD + + + rect1 + rect10 + rect11 + rect12 + rect13 + rect14 + rect15 + rect16 + rect17 + rect18 + rect19 + rect2 + rect3 + rect4 + rect5 + rect6 + rect7 + + + + Fourier_Rect_MOD @@ -72,165 +232,4 @@
- - Add - - - Add - - - - - - - Sum - - - - - - FourierXRectXMOD - - - rect1 - rect10 - rect11 - rect12 - rect13 - rect14 - rect15 - rect16 - rect17 - rect18 - rect19 - rect2 - rect3 - rect4 - rect5 - rect6 - rect7 - rect8 - - - - - - Plot - - - [0,1] - xRange-auto - (\d+\.\d+) - - - VisualPlugin - Used display name - - - [0 0 0 0 0] - 1 - Style - ^\[(\s*\d\s*)+\] - - - 3000 - 1 - Buffersize - ^([1-9][0-9]{0,3}|10000)$ - - - Plot - - - amplitude, V - Label-Y - \w+,\s+\w+ - - - bool - 0 - Grid-X - ^(1|0)$ - - - 1 - (\d+) - - - bool - 1 - xRange-auto - ^(1|0)$ - - - [0,1] - yRange-auto - (\d+\.\d+) - - - bool - 0 - Grid-Y - ^(1|0)$ - - - (0,0) - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - 1 - - - [0 1 2 3 4] - 1 - Color - ^\[(\s*\d\s*)+\] - - - (1517,1065) - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - 1 - - - time, s - Label-X - \w+,\s*\w+ - - - bool - 1 - Rolling Plot - ^(1|0)$ - - - bool - 1 - yRange-auto - ^(1|0)$ - - - - [0,1] - 0 - 1 - 1 - [0 0 0 0 0] - 3000 - [0 1 2 3 4] - 0 - 5 - 1 - [0,1] - - - - - Add - - - Sum - - - -
diff --git a/papi/core.py b/papi/core.py index 7bfa5014..4bd800eb 100644 --- a/papi/core.py +++ b/papi/core.py @@ -694,7 +694,8 @@ def __process_create_plugin__(self,event): if plugin_config is None or plugin_config =={}: plugin_config = plugin.plugin_object.get_startup_configuration() else: - plugin_config = dict(list(plugin_config.items()) + list(plugin.plugin_object.get_startup_configuration().items())) + plugin_config = dict(list(plugin.plugin_object.get_startup_configuration().items())+list(plugin_config.items())) + # create Process object for new plugin # set parameter for work function of plugin, such as queues, id and eventTriggered diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json index 1916cd7a..d891794b 100644 --- a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json @@ -1,5 +1,8 @@ {"SourcesConfig" : { - "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257" } , "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257" } , "2" : { "SourceName" : "Signal1" , "NValues_send" : "1", "datatype" : "257" } , "3" : { "SourceName" : "Sig2nal" , "NValues_send" : "1", "datatype" : "257" } , "4" : { "SourceName" : "Sign3al" , "NValues_send" : "1", "datatype" : "257" } , "5" : { "SourceName" : "_Sig4nal" , "NValues_send" : "1", "datatype" : "257" } , "6" : { "SourceName" : "Signal5" , "NValues_send" : "1", "datatype" : "257" } , "7" : { "SourceName" : "Signal6" , "NValues_send" : "1", "datatype" : "257" } , "8" : { "SourceName" : "Signal7" , "NValues_send" : "1", "datatype" : "257" } , "9" : { "SourceName" : "Signal8" , "NValues_send" : "1", "datatype" : "257" } , "10" : { "SourceName" : "Signal9" , "NValues_send" : "1", "datatype" : "257" } , "11" : { "SourceName" : "Signal10" , "NValues_send" : "1", "datatype" : "257" } , "12" : { "SourceName" : "Signal11" , "NValues_send" : "1", "datatype" : "257" } , "13" : { "SourceName" : "Sign12al" , "NValues_send" : "1", "datatype" : "257" } , "14" : { "SourceName" : "Signal_13" , "NValues_send" : "1", "datatype" : "257" } , "15" : { "SourceName" : "HalloWelt14" , "NValues_send" : "1", "datatype" : "257" } , "16" : { "SourceName" : "15" , "NValues_send" : "1", "datatype" : "257" } , "17" : { "SourceName" : "16" , "NValues_send" : "1", "datatype" : "257" } , "18" : { "SourceName" : "Test17" , "NValues_send" : "1", "datatype" : "257" } , "19" : { "SourceName" : "18" , "NValues_send" : "1", "datatype" : "257" } , "20" : { "SourceName" : "Signal19" , "NValues_send" : "1", "datatype" : "257" } , "21" : { "SourceName" : "Signal20" , "NValues_send" : "1", "datatype" : "257" } , "22" : { "SourceName" : "Signal21" , "NValues_send" : "1", "datatype" : "257" } , "23" : { "SourceName" : "Signal22" , "NValues_send" : "1", "datatype" : "257" } } , + "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257" } , + "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257" } , + "2" : { "SourceName" : "Signal1" , "NValues_send" : "1", "datatype" : "257" } , + "3" : { "SourceName" : "Sig2nal" , "NValues_send" : "1", "datatype" : "257" } , "4" : { "SourceName" : "Sign3al" , "NValues_send" : "1", "datatype" : "257" } , "5" : { "SourceName" : "_Sig4nal" , "NValues_send" : "1", "datatype" : "257" } , "6" : { "SourceName" : "Signal5" , "NValues_send" : "1", "datatype" : "257" } , "7" : { "SourceName" : "Signal6" , "NValues_send" : "1", "datatype" : "257" } , "8" : { "SourceName" : "Signal7" , "NValues_send" : "1", "datatype" : "257" } , "9" : { "SourceName" : "Signal8" , "NValues_send" : "1", "datatype" : "257" } , "10" : { "SourceName" : "Signal9" , "NValues_send" : "1", "datatype" : "257" } , "11" : { "SourceName" : "Signal10" , "NValues_send" : "1", "datatype" : "257" } , "12" : { "SourceName" : "Signal11" , "NValues_send" : "1", "datatype" : "257" } , "13" : { "SourceName" : "Sign12al" , "NValues_send" : "1", "datatype" : "257" } , "14" : { "SourceName" : "Signal_13" , "NValues_send" : "1", "datatype" : "257" } , "15" : { "SourceName" : "HalloWelt14" , "NValues_send" : "1", "datatype" : "257" } , "16" : { "SourceName" : "15" , "NValues_send" : "1", "datatype" : "257" } , "17" : { "SourceName" : "16" , "NValues_send" : "1", "datatype" : "257" } , "18" : { "SourceName" : "Test17" , "NValues_send" : "1", "datatype" : "257" } , "19" : { "SourceName" : "18" , "NValues_send" : "1", "datatype" : "257" } , "20" : { "SourceName" : "Signal19" , "NValues_send" : "1", "datatype" : "257" } , "21" : { "SourceName" : "Signal20" , "NValues_send" : "1", "datatype" : "257" } , "22" : { "SourceName" : "Signal21" , "NValues_send" : "1", "datatype" : "257" } , "23" : { "SourceName" : "Signal22" , "NValues_send" : "1", "datatype" : "257" } } , "ParametersConfig" : { "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } , "3" : { "ParameterName" : "P1" , "NValues" : "1", "datatype" : "257" } , "4" : { "ParameterName" : "P2" , "NValues" : "1", "datatype" : "257" } , "5" : { "ParameterName" : "P3" , "NValues" : "1", "datatype" : "257" } , "6" : { "ParameterName" : "P4" , "NValues" : "1", "datatype" : "257" } , "7" : { "ParameterName" : "Test5" , "NValues" : "1", "datatype" : "257" } , "8" : { "ParameterName" : "P6" , "NValues" : "1", "datatype" : "257" } , "9" : { "ParameterName" : "P7" , "NValues" : "1", "datatype" : "257" } , "10" : { "ParameterName" : "P8" , "NValues" : "1", "datatype" : "257" } , "11" : { "ParameterName" : "P9" , "NValues" : "1", "datatype" : "257" } } } \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index f1411708..a51ae6ac 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -113,12 +113,10 @@ def start_init(self, config=None): path = config['Cfg_Path']['value'] f = open(path, 'r') self.ProtocolConfig = json.load(f) - self.Sources = self.ProtocolConfig['SourcesConfig'] self.Parameters = self.ProtocolConfig['ParametersConfig'] - # For each group:: loop through all sources (=signals) in the group and register the signals # Register signals From 1649f5e96f173c0fbd9b13e6eab2b30a62b3e3a4 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 13 Jan 2015 12:01:21 +0100 Subject: [PATCH 058/260] moved ORTD-Datasources from `papi/plugin/io/ORTD_UDP` to `data_sources` --- .../ORTD}/DataSourceExample/ProtocollConfig.json | 0 .../io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/README | 0 .../ORTD_UDP => data_sources/ORTD}/DataSourceExample/UDPio.ipar | 0 .../ORTD_UDP => data_sources/ORTD}/DataSourceExample/UDPio.rpar | 0 .../io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/UDPio.sce | 0 .../ORTD_UDP => data_sources/ORTD}/DataSourceExample/run_UDPio.sh | 0 .../ORTD}/DataSourceExample/webinterface/PacketFramework.sce | 0 .../ORTD}/DataSourceExample/webinterface/html/mainAuto.html | 0 .../ORTD}/DataSourceExample/webinterface/html/main_Plot.html | 0 .../ORTD}/DataSourceExample/webinterface/install_nodejs.sh | 0 .../ORTD}/DataSourceExample/webinterface/webappUDP.js | 0 .../ORTD}/DataSourceExample_Groups/PF.sci | 0 .../ORTD}/DataSourceExample_Groups/ProtocollConfig.json | 0 .../ORTD}/DataSourceExample_Groups/README | 0 .../ORTD}/DataSourceExample_Groups/UDPio.ipar | 0 .../ORTD}/DataSourceExample_Groups/UDPio.rpar | 0 .../ORTD}/DataSourceExample_Groups/UDPio.sce | 0 .../ORTD}/DataSourceExample_Groups/run_UDPio.sh | 0 .../DataSourceExample_Groups/webinterface/PacketFramework.sce | 0 .../DataSourceExample_Groups/webinterface/html/mainAuto.html | 0 .../DataSourceExample_Groups/webinterface/html/main_Plot.html | 0 .../ORTD}/DataSourceExample_Groups/webinterface/install_nodejs.sh | 0 .../ORTD}/DataSourceExample_Groups/webinterface/webappUDP.js | 0 .../ORTD}/DataSourceExample_extended/ProtocollConfig.json | 0 .../ORTD}/DataSourceExample_extended/README | 0 .../ORTD}/DataSourceExample_extended/UDPio.ipar | 0 .../ORTD}/DataSourceExample_extended/UDPio.rpar | 0 .../ORTD}/DataSourceExample_extended/UDPio.sce | 0 .../ORTD}/DataSourceExample_extended/run_UDPio.sh | 0 .../DataSourceExample_extended/webinterface/PacketFramework.sce | 0 .../DataSourceExample_extended/webinterface/html/mainAuto.html | 0 .../DataSourceExample_extended/webinterface/html/main_Plot.html | 0 .../DataSourceExample_extended/webinterface/install_nodejs.sh | 0 .../ORTD}/DataSourceExample_extended/webinterface/webappUDP.js | 0 34 files changed, 0 insertions(+), 0 deletions(-) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/ProtocollConfig.json (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/README (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/UDPio.ipar (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/UDPio.rpar (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/UDPio.sce (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/run_UDPio.sh (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/webinterface/PacketFramework.sce (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/webinterface/html/mainAuto.html (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/webinterface/html/main_Plot.html (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/webinterface/install_nodejs.sh (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample/webinterface/webappUDP.js (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/PF.sci (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/ProtocollConfig.json (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/README (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/UDPio.ipar (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/UDPio.rpar (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/UDPio.sce (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/run_UDPio.sh (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/webinterface/PacketFramework.sce (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/webinterface/html/mainAuto.html (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/webinterface/html/main_Plot.html (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/webinterface/install_nodejs.sh (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_Groups/webinterface/webappUDP.js (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/ProtocollConfig.json (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/README (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/UDPio.ipar (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/UDPio.rpar (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/UDPio.sce (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/run_UDPio.sh (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/webinterface/PacketFramework.sce (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/webinterface/html/mainAuto.html (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/webinterface/html/main_Plot.html (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/webinterface/install_nodejs.sh (100%) rename {papi/plugin/io/ORTD_UDP => data_sources/ORTD}/DataSourceExample_extended/webinterface/webappUDP.js (100%) diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json b/data_sources/ORTD/DataSourceExample/ProtocollConfig.json similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json rename to data_sources/ORTD/DataSourceExample/ProtocollConfig.json diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/README b/data_sources/ORTD/DataSourceExample/README similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/README rename to data_sources/ORTD/DataSourceExample/README diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.ipar b/data_sources/ORTD/DataSourceExample/UDPio.ipar similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.ipar rename to data_sources/ORTD/DataSourceExample/UDPio.ipar diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.rpar b/data_sources/ORTD/DataSourceExample/UDPio.rpar similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.rpar rename to data_sources/ORTD/DataSourceExample/UDPio.rpar diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.sce b/data_sources/ORTD/DataSourceExample/UDPio.sce similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/UDPio.sce rename to data_sources/ORTD/DataSourceExample/UDPio.sce diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/run_UDPio.sh b/data_sources/ORTD/DataSourceExample/run_UDPio.sh similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/run_UDPio.sh rename to data_sources/ORTD/DataSourceExample/run_UDPio.sh diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceExample/webinterface/PacketFramework.sce similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/PacketFramework.sce rename to data_sources/ORTD/DataSourceExample/webinterface/PacketFramework.sce diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/mainAuto.html b/data_sources/ORTD/DataSourceExample/webinterface/html/mainAuto.html similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/mainAuto.html rename to data_sources/ORTD/DataSourceExample/webinterface/html/mainAuto.html diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/main_Plot.html b/data_sources/ORTD/DataSourceExample/webinterface/html/main_Plot.html similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/html/main_Plot.html rename to data_sources/ORTD/DataSourceExample/webinterface/html/main_Plot.html diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/install_nodejs.sh b/data_sources/ORTD/DataSourceExample/webinterface/install_nodejs.sh similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/install_nodejs.sh rename to data_sources/ORTD/DataSourceExample/webinterface/install_nodejs.sh diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/webappUDP.js b/data_sources/ORTD/DataSourceExample/webinterface/webappUDP.js similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample/webinterface/webappUDP.js rename to data_sources/ORTD/DataSourceExample/webinterface/webappUDP.js diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/PF.sci b/data_sources/ORTD/DataSourceExample_Groups/PF.sci similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/PF.sci rename to data_sources/ORTD/DataSourceExample_Groups/PF.sci diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/ProtocollConfig.json b/data_sources/ORTD/DataSourceExample_Groups/ProtocollConfig.json similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/ProtocollConfig.json rename to data_sources/ORTD/DataSourceExample_Groups/ProtocollConfig.json diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/README b/data_sources/ORTD/DataSourceExample_Groups/README similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/README rename to data_sources/ORTD/DataSourceExample_Groups/README diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.ipar b/data_sources/ORTD/DataSourceExample_Groups/UDPio.ipar similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.ipar rename to data_sources/ORTD/DataSourceExample_Groups/UDPio.ipar diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.rpar b/data_sources/ORTD/DataSourceExample_Groups/UDPio.rpar similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.rpar rename to data_sources/ORTD/DataSourceExample_Groups/UDPio.rpar diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.sce b/data_sources/ORTD/DataSourceExample_Groups/UDPio.sce similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/UDPio.sce rename to data_sources/ORTD/DataSourceExample_Groups/UDPio.sce diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/run_UDPio.sh b/data_sources/ORTD/DataSourceExample_Groups/run_UDPio.sh similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/run_UDPio.sh rename to data_sources/ORTD/DataSourceExample_Groups/run_UDPio.sh diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceExample_Groups/webinterface/PacketFramework.sce similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/PacketFramework.sce rename to data_sources/ORTD/DataSourceExample_Groups/webinterface/PacketFramework.sce diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/mainAuto.html b/data_sources/ORTD/DataSourceExample_Groups/webinterface/html/mainAuto.html similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/mainAuto.html rename to data_sources/ORTD/DataSourceExample_Groups/webinterface/html/mainAuto.html diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/main_Plot.html b/data_sources/ORTD/DataSourceExample_Groups/webinterface/html/main_Plot.html similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/html/main_Plot.html rename to data_sources/ORTD/DataSourceExample_Groups/webinterface/html/main_Plot.html diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/install_nodejs.sh b/data_sources/ORTD/DataSourceExample_Groups/webinterface/install_nodejs.sh similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/install_nodejs.sh rename to data_sources/ORTD/DataSourceExample_Groups/webinterface/install_nodejs.sh diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/webappUDP.js b/data_sources/ORTD/DataSourceExample_Groups/webinterface/webappUDP.js similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/webinterface/webappUDP.js rename to data_sources/ORTD/DataSourceExample_Groups/webinterface/webappUDP.js diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json b/data_sources/ORTD/DataSourceExample_extended/ProtocollConfig.json similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json rename to data_sources/ORTD/DataSourceExample_extended/ProtocollConfig.json diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/README b/data_sources/ORTD/DataSourceExample_extended/README similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/README rename to data_sources/ORTD/DataSourceExample_extended/README diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.ipar b/data_sources/ORTD/DataSourceExample_extended/UDPio.ipar similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.ipar rename to data_sources/ORTD/DataSourceExample_extended/UDPio.ipar diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.rpar b/data_sources/ORTD/DataSourceExample_extended/UDPio.rpar similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.rpar rename to data_sources/ORTD/DataSourceExample_extended/UDPio.rpar diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.sce b/data_sources/ORTD/DataSourceExample_extended/UDPio.sce similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/UDPio.sce rename to data_sources/ORTD/DataSourceExample_extended/UDPio.sce diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/run_UDPio.sh b/data_sources/ORTD/DataSourceExample_extended/run_UDPio.sh similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/run_UDPio.sh rename to data_sources/ORTD/DataSourceExample_extended/run_UDPio.sh diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceExample_extended/webinterface/PacketFramework.sce similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/PacketFramework.sce rename to data_sources/ORTD/DataSourceExample_extended/webinterface/PacketFramework.sce diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/mainAuto.html b/data_sources/ORTD/DataSourceExample_extended/webinterface/html/mainAuto.html similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/mainAuto.html rename to data_sources/ORTD/DataSourceExample_extended/webinterface/html/mainAuto.html diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/main_Plot.html b/data_sources/ORTD/DataSourceExample_extended/webinterface/html/main_Plot.html similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/html/main_Plot.html rename to data_sources/ORTD/DataSourceExample_extended/webinterface/html/main_Plot.html diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/install_nodejs.sh b/data_sources/ORTD/DataSourceExample_extended/webinterface/install_nodejs.sh similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/install_nodejs.sh rename to data_sources/ORTD/DataSourceExample_extended/webinterface/install_nodejs.sh diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/webappUDP.js b/data_sources/ORTD/DataSourceExample_extended/webinterface/webappUDP.js similarity index 100% rename from papi/plugin/io/ORTD_UDP/DataSourceExample_extended/webinterface/webappUDP.js rename to data_sources/ORTD/DataSourceExample_extended/webinterface/webappUDP.js From ead2581879be4d4c9bafdee4b0f5afbd4aec46f1 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 13 Jan 2015 12:06:55 +0100 Subject: [PATCH 059/260] some cleanup --- papi/core.py | 11 +++++++---- .../DataSourceExample_Groups/ProtocollConfig.json | 1 + 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/papi/core.py b/papi/core.py index 5ba2715c..432246b0 100644 --- a/papi/core.py +++ b/papi/core.py @@ -656,8 +656,9 @@ def __process_edit_dplugin(self, event): dsignal.dname = cObject.dname - self.log.printText(1, - 'edit_dplugin, Edited Dblock ' + dblock.name + ' of DPlugin ' + pl.uname + " : DSignal " + dsignal.uname + " to dname -> " + dsignal.dname) + self.log.printText(3, + 'edit_dplugin, Edited Dblock ' + dblock.name + ' of DPlugin ' + pl.uname + + " : DSignal " + dsignal.uname + " to dname -> " + dsignal.dname) self.update_meta_data_to_gui(pl.id, True) @@ -676,7 +677,8 @@ def __process_create_plugin__(self, event): # get optData to get information about which plugin to start optData = event.get_optional_parameter() self.log.printText(2, - 'create_plugin, Try to create plugin with Name ' + optData.plugin_identifier + " and UName " + optData.plugin_uname) + 'create_plugin, Try to create plugin with Name ' + optData.plugin_identifier + + " and UName " + optData.plugin_uname) # search yapsy plugin object with plugin_idientifier and check if it exists plugin = self.plugin_manager.getPluginByName(optData.plugin_identifier) @@ -687,7 +689,8 @@ def __process_create_plugin__(self, event): plugin = self.plugin_manager.getPluginByName(optData.plugin_identifier) if plugin is None: self.log.printText(1, - 'create_plugin, Plugin with Name ' + optData.plugin_identifier + ' does not exist in file system') + 'create_plugin, Plugin with Name ' + optData.plugin_identifier + + ' does not exist in file system') return -1 # creates a new plugin id because plugin exsits diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/ProtocollConfig.json b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/ProtocollConfig.json index 9b532128..ab7b2e64 100644 --- a/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/ProtocollConfig.json +++ b/papi/plugin/io/ORTD_UDP/DataSourceExample_Groups/ProtocollConfig.json @@ -6,4 +6,5 @@ "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } , "GroupsConfig" : { "1" : { "Name" : "G1" } , "2" : { "Name" : "G2" } } +} } \ No newline at end of file From 41db8fa9271fe9dea66607fb2ad5a58fdedce9de Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 13 Jan 2015 12:13:23 +0100 Subject: [PATCH 060/260] added error case for unknown id in ORTDPlugin --- cfg_collection/ORTD_bigSource.xml | 2 +- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 31 ++++++++++++++++------------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/cfg_collection/ORTD_bigSource.xml b/cfg_collection/ORTD_bigSource.xml index 1b1cb55b..ff36db90 100644 --- a/cfg_collection/ORTD_bigSource.xml +++ b/cfg_collection/ORTD_bigSource.xml @@ -26,7 +26,7 @@ file 0 - /home/control/PycharmProjects/PaPI/papi/plugin/io/ORTD_UDP/DataSourceExample_extended/ProtocollConfig.json + /home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample_extended/ProtocollConfig.json diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index a51ae6ac..c2d95d70 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -84,7 +84,7 @@ def get_plugin_configuration(self): 'advanced': '1' }, 'Cfg_Path': { - 'value': 'papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json', + 'value': '/home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample/ProtocollConfig.json', 'type': 'file', 'advanced': '0' }, @@ -248,19 +248,22 @@ def thread_execute(self): else: # Received a data packet # Lookup the Source behind the given SourceId - Source = self.Sources[str(SourceId)] - NValues = int(Source['NValues_send']) - - # Read NVales from the received packet - val = [] - for i in range(NValues): - # TODO: why try except? - try: - val.append(struct.unpack_from(' Date: Tue, 13 Jan 2015 12:14:10 +0100 Subject: [PATCH 061/260] DParameter constructer, remove unnecessary attributes; fixed bug in DPlugin remove DBlock function --- papi/data/DParameter.py | 5 +---- papi/data/DPlugin.py | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/papi/data/DParameter.py b/papi/data/DParameter.py index c5275761..31704288 100644 --- a/papi/data/DParameter.py +++ b/papi/data/DParameter.py @@ -33,14 +33,11 @@ class DParameter(DObject): - def __init__(self, ptype, name, default=0, prange=0, live=1, Regex = None, OptionalObject=None): + def __init__(self, name, default=0, Regex = None, OptionalObject=None): super(DParameter, self).__init__() - self.type = ptype self.default = default self.value = default - self.range = prange - self.live = live self.name = name self.plugin_id = None self.plugin_identifier = None diff --git a/papi/data/DPlugin.py b/papi/data/DPlugin.py index 6d5cf667..ea54a891 100644 --- a/papi/data/DPlugin.py +++ b/papi/data/DPlugin.py @@ -310,8 +310,8 @@ def rm_dblock(self, dblock): :rtype boolean: """ if dblock.name in self.__blocks: - self.__blocks[dblock.name].name = dblock.name + "_deleted" del self.__blocks[dblock.name] + dblock.name += "_deleted" return True else: return False From c86e58c2666355389807d5d716d4a3d28ddd0139 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 13 Jan 2015 12:19:45 +0100 Subject: [PATCH 062/260] fixed and cleanup for DParameter --- papi/plugin/dpp/add/Add.py | 4 ---- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 4 ++-- papi/plugin/io/cpu_load/CPU_Load.py | 2 +- papi/plugin/io/sinus/Sinus.py | 3 +-- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/papi/plugin/dpp/add/Add.py b/papi/plugin/dpp/add/Add.py index aa5ea0b2..6fd6d838 100644 --- a/papi/plugin/dpp/add/Add.py +++ b/papi/plugin/dpp/add/Add.py @@ -58,11 +58,7 @@ def start_init(self, config=None): self.block1.add_signal(signal) - - #self.para1 = DParameter(None,'Count',1, [0, 1] ,1) - self.send_new_block_list([self.block1]) - #self.send_new_parameter_list([self.para1]) return True diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index c2d95d70..60866590 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -168,10 +168,10 @@ def start_init(self, config=None): para_name = Para['ParameterName'] val_count = Para['NValues'] opt_object = OptionalObject(Pid, val_count) - Parameter = DParameter('', para_name, 0, 0, OptionalObject=opt_object) + Parameter = DParameter(para_name, default=0, OptionalObject=opt_object) self.Parameter_List.append(Parameter) - self.ControlParameter = DParameter('','triggerConfiguration',0,0) + self.ControlParameter = DParameter('triggerConfiguration',default=0) self.Parameter_List.append(self.ControlParameter) self.send_new_parameter_list(self.Parameter_List) diff --git a/papi/plugin/io/cpu_load/CPU_Load.py b/papi/plugin/io/cpu_load/CPU_Load.py index 0251430e..ec5fa0f1 100644 --- a/papi/plugin/io/cpu_load/CPU_Load.py +++ b/papi/plugin/io/cpu_load/CPU_Load.py @@ -21,7 +21,7 @@ class CPU_Load(iop_base): def start_init(self, config=None): self.t = 0 self.delta_t = 0.01 - self.para_delta_t = DParameter('', 'Delta_t', 0.01, [0,2],1) + self.para_delta_t = DParameter('Delta_t', default=0.01) self.send_new_parameter_list([self.para_delta_t]) diff --git a/papi/plugin/io/sinus/Sinus.py b/papi/plugin/io/sinus/Sinus.py index 4b89ba96..87d4206e 100644 --- a/papi/plugin/io/sinus/Sinus.py +++ b/papi/plugin/io/sinus/Sinus.py @@ -68,8 +68,7 @@ def start_init(self, config=None): blockList = [self.block1, self.block2, self.block3] self.send_new_block_list(blockList) - self.para3 = DParameter(None,'Frequenz Block SinMit_f3', 0.6, [0,1],1, Regex='[0-9]+.[0-9]+') - self.para3.id = 1 + self.para3 = DParameter('Frequenz Block SinMit_f3', default= 0.6, Regex='[0-9]+.[0-9]+') para_l = [self.para3] self.send_new_parameter_list(para_l) From e664073c27916431e94e07b62c5b7ce0f4989a63 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 13 Jan 2015 12:20:48 +0100 Subject: [PATCH 063/260] changes due to new DParameter constructor : --- papi/plugin/templates/IOP_DPP_template.py | 4 +-- papi/plugin/templates/visual_template.py | 4 +-- .../visual/OrtdController/OrtdController.py | 1 - papi/plugin/visual/Plot/Plot.py | 31 +++++++++---------- .../visual/WizardExample/WizardExample.py | 4 +-- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/papi/plugin/templates/IOP_DPP_template.py b/papi/plugin/templates/IOP_DPP_template.py index 58bca726..37a344ab 100644 --- a/papi/plugin/templates/IOP_DPP_template.py +++ b/papi/plugin/templates/IOP_DPP_template.py @@ -59,8 +59,8 @@ def start_init(self, config=None): # self.send_new_block_list([block1, block2, block3]) # create a parameter object - # self.para1 = DParameter('type','ParameterName',InitWert,RangeArray,1) - # self.para2 = DParameter('type','ParameterName',InitWert,RangeArray,1) + # self.para1 = DParameter('ParameterName',default=0) + # self.para2 = DParameter('ParameterName',default=0) # build parameter list to send to Core # para_list = [self.para1 self.para2] diff --git a/papi/plugin/templates/visual_template.py b/papi/plugin/templates/visual_template.py index ffa11be5..43fe0289 100644 --- a/papi/plugin/templates/visual_template.py +++ b/papi/plugin/templates/visual_template.py @@ -75,8 +75,8 @@ def initiate_layer_0(self, config=None): # Create Parameters # --------------------------- # create a parameter object - # self.para1 = DParameter('type','ParameterName',InitWert,RangeArray,1) - # self.para2 = DParameter('type','ParameterName',InitWert,RangeArray,1) + # self.para1 = DParameter('ParameterName',default=0) + # self.para2 = DParameter('ParameterName',default=0) # build parameter list to send to Core # para_list = [self.para1 self.para2] diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index ba9c62ee..70d149d5 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -29,7 +29,6 @@ __author__ = 'Stefan' from papi.plugin.base_classes.vip_base import vip_base -from papi.data.DParameter import DParameter from PySide import QtGui, QtCore from PySide.QtGui import QRegExpValidator diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index bfa3d119..f82b620b 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -175,23 +175,20 @@ def initiate_layer_0(self, config=None): # Create Parameters # --------------------------- - self.__parameters__['x-grid'] = DParameter(None, 'x-grid', 0, [0, 1], 1, Regex='^(1|0){1}$') - self.__parameters__['y-grid'] = DParameter(None, 'y-grid', 0, [0, 1], 1, Regex='^(1|0){1}$') - - self.__parameters__['color'] = DParameter(None, 'color', '[0 1 2 3 4]', [0, 1], 1, Regex='^\[(\s*\d\s*)+\]') - self.__parameters__['style'] = DParameter(None, 'style', '[0 0 0 0 0]', [0, 1], 1, Regex='^\[(\s*\d\s*)+\]') - self.__parameters__['rolling'] = DParameter(None, 'rolling', '0', [0, 1], 1, Regex='^(1|0){1}') - - self.__parameters__['downsampling_rate'] = DParameter(None, 'downsampling_rate', self.__downsampling_rate__, - [1, 100], - 1, Regex='^([1-9][0-9]?|100)$') - self.__parameters__['buffersize'] = DParameter(None, 'buffersize', self.__buffer_size__, [1, 100], - 1, Regex='^([1-9][0-9]{0,3}|10000)$') - - self.__parameters__['xRange-auto'] = DParameter(None, 'xRange-auto', '1', None, 1, Regex='^(1|0){1}$') - self.__parameters__['xRange'] = DParameter(None, 'xRange', '[0,1]', None, 1, Regex='(\d+\.\d+)') - self.__parameters__['yRange-auto'] = DParameter(None, 'yRange-auto', '1', None, 1, Regex='^(1|0){1}$') - self.__parameters__['yRange'] = DParameter(None, 'yRange', '[0,1]', None, 1, Regex='(\d+\.\d+)') + self.__parameters__['x-grid'] = DParameter('x-grid', 0, Regex='^(1|0){1}$') + self.__parameters__['y-grid'] = DParameter('y-grid', 0, Regex='^(1|0){1}$') + + self.__parameters__['color'] = DParameter('color', '[0 1 2 3 4]', Regex='^\[(\s*\d\s*)+\]') + self.__parameters__['style'] = DParameter('style', '[0 0 0 0 0]', Regex='^\[(\s*\d\s*)+\]') + self.__parameters__['rolling'] = DParameter('rolling', '0', Regex='^(1|0){1}') + + self.__parameters__['downsampling_rate'] = DParameter('downsampling_rate', self.__downsampling_rate__, Regex='^([1-9][0-9]?|100)$') + self.__parameters__['buffersize'] = DParameter('buffersize', self.__buffer_size__, Regex='^([1-9][0-9]{0,3}|10000)$') + + self.__parameters__['xRange-auto'] = DParameter('xRange-auto', '1', Regex='^(1|0){1}$') + self.__parameters__['xRange'] = DParameter('xRange', '[0,1]', Regex='(\d+\.\d+)') + self.__parameters__['yRange-auto'] = DParameter('yRange-auto', '1', Regex='^(1|0){1}$') + self.__parameters__['yRange'] = DParameter('yRange', '[0,1]', Regex='(\d+\.\d+)') self.send_new_parameter_list(list(self.__parameters__.values())) diff --git a/papi/plugin/visual/WizardExample/WizardExample.py b/papi/plugin/visual/WizardExample/WizardExample.py index 0e7f56f7..a91904b1 100644 --- a/papi/plugin/visual/WizardExample/WizardExample.py +++ b/papi/plugin/visual/WizardExample/WizardExample.py @@ -94,8 +94,8 @@ def initiate_layer_0(self, config=None): # Create Parameters # --------------------------- # create a parameter object - # self.para1 = DParameter('type','ParameterName',InitWert,RangeArray,1) - # self.para2 = DParameter('type','ParameterName',InitWert,RangeArray,1) + # self.para1 = DParameter('ParameterName',InitWert,1) + # self.para2 = DParameter('ParameterName',InitWert,1) # build parameter list to send to Core # para_list = [self.para1 self.para2] From 55de76e711e72fbab7e46028f24d47fa1a0b25d3 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 13 Jan 2015 12:21:38 +0100 Subject: [PATCH 064/260] deleted old development gui --- papi/gui/qt_dev/__init__.py | 29 -- papi/gui/qt_dev/add_pcp_subscriber.py | 180 ---------- papi/gui/qt_dev/add_plugin.py | 180 ---------- papi/gui/qt_dev/add_subscriber.py | 170 ---------- papi/gui/qt_dev/gui_main.py | 332 ------------------- papi/gui/qt_dev/manager.py | 457 -------------------------- 6 files changed, 1348 deletions(-) delete mode 100644 papi/gui/qt_dev/__init__.py delete mode 100644 papi/gui/qt_dev/add_pcp_subscriber.py delete mode 100644 papi/gui/qt_dev/add_plugin.py delete mode 100644 papi/gui/qt_dev/add_subscriber.py delete mode 100644 papi/gui/qt_dev/gui_main.py delete mode 100644 papi/gui/qt_dev/manager.py diff --git a/papi/gui/qt_dev/__init__.py b/papi/gui/qt_dev/__init__.py deleted file mode 100644 index 7c97c23d..00000000 --- a/papi/gui/qt_dev/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors -Sven Knuth -""" - -__author__ = 'knuths' diff --git a/papi/gui/qt_dev/add_pcp_subscriber.py b/papi/gui/qt_dev/add_pcp_subscriber.py deleted file mode 100644 index 27216765..00000000 --- a/papi/gui/qt_dev/add_pcp_subscriber.py +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors -Sven Knuth -""" - -__author__ = 'knuths' - -from papi.ui.gui.qt_dev.add_subscriber import Ui_AddSubscriber -from PySide.QtGui import QDialog, QAbstractButton, QDialogButtonBox -from PySide.QtGui import QTreeWidgetItem -from papi.constants import PLUGIN_PCP_IDENTIFIER - -class AddPCPSubscriber(QDialog, Ui_AddSubscriber): - - def __init__(self, callback_functions, parent=None): - super(AddPCPSubscriber, self).__init__(parent) - self.setupUi(self) - self.dgui = None - - self.treeSubscriber.currentItemChanged.connect(self.subscriberItemChanged) - self.treeTarget.currentItemChanged.connect(self.targetItemChanged) - self.treeBlock.currentItemChanged.connect(self.blockItemChanged) - - self.treeSubscriber.setHeaderLabel('PCP') - self.treeTarget.setHeaderLabel('PCP_BLOCK') - self.treeBlock.setHeaderLabel('Plugin') - self.treeSignal.setHeaderLabel('Parameter') - - self.buttonBox.clicked.connect(self.button_clicked) - - self.subscriberID = None - self.targetID = None - self.blockName = None - self.signalName = None - self.signalIndex = [] - self.setWindowTitle("Create Subscribtion") - - self.callback_functions = callback_functions - - def setDGui(self, dgui): - self.dgui = dgui - - def getDGui(self): - """ - - :return: - :rtype DGui: - """ - return self.dgui - - def subscriberItemChanged(self, item): - self.treeTarget.clear() - if hasattr(item, 'dplugin'): - - dplugin = item.dplugin - dblock_ids = dplugin.get_dblocks() - - for dblock_id in dblock_ids: - dblock = dblock_ids[dblock_id] - block_item = QTreeWidgetItem(self.treeTarget) - block_item.dblock = dblock - block_item.setText(0, dblock.name) - block_item.dplugin = dplugin - - def targetItemChanged(self, item): - - self.treeBlock.clear() - - if hasattr(item, 'dplugin'): - - subscriber = self.treeSubscriber.currentItem().dplugin - - dplugin_ids = self.dgui.get_all_plugins() - - for dplugin_id in dplugin_ids: - dplugin = dplugin_ids[dplugin_id] - - # ------------------------------ - # Sort DPluginItem in TreeWidget of Subscriber and Target - # ------------------------------ - - if subscriber.id is not dplugin_id and len(dplugin.get_parameters()) is not 0 and dplugin.type != PLUGIN_PCP_IDENTIFIER: - - target_item = QTreeWidgetItem(self.treeBlock) - - target_item.dplugin = dplugin - target_item.setText(0, str(dplugin.uname) ) - - - def blockItemChanged(self, item): - self.treeSignal.clear() - - if hasattr(item, 'dplugin'): - dplugin = item.dplugin - dparameters = dplugin.get_parameters() - - for dparameter_key in dparameters: - dparameter = dparameters[dparameter_key] - parameter_item = QTreeWidgetItem(self.treeSignal) - parameter_item.dparameter = dparameter - parameter_item.setText(0, dparameter.name) - - - def showEvent(self, *args, **kwargs): - - self.treeSubscriber.clear() - self.treeTarget.clear() - self.treeBlock.clear() - - dplugin_ids = self.dgui.get_all_plugins() - - for dplugin_id in dplugin_ids: - dplugin = dplugin_ids[dplugin_id] - - # ------------------------------ - # Sort DPluginItem in TreeWidget of Subscriber and Target - # ------------------------------ - - if dplugin.type == PLUGIN_PCP_IDENTIFIER: - - subscriber_item = QTreeWidgetItem(self.treeSubscriber) - - subscriber_item.dplugin = dplugin - subscriber_item.setText(0, str(dplugin.uname) ) - - # # ------------------------------- - # # Set amount of blocks and parameters as meta information - # # ------------------------------- - # dblock_ids = dplugin.get_dblocks() - # - # plugin_item.setText(self.get_column_by_name("#BLOCKS"), str(len(dblock_ids.keys()))) - - def button_clicked(self, button): - - if self.buttonBox.buttonRole(button) == QDialogButtonBox.ApplyRole: - - subscriber_item = self.treeSubscriber.currentItem() - target_item = self.treeTarget.currentItem() - block_item = self.treeBlock.currentItem() - signal_item = self.treeSignal.currentItem() - - self.pcpID = subscriber_item.dplugin.id - self.pcpBlock = target_item.dblock - - self.pluginID = block_item.dplugin.id - - - self.parameter = signal_item.dparameter - - - button.setFocus() - print(self.pluginID) - print(self.pcpID) - print(self.pcpBlock) - print(self.parameter) - - self.callback_functions['do_subscribe'](self.pluginID, self.pcpID, self.pcpBlock.name , [], self.parameter.name) \ No newline at end of file diff --git a/papi/gui/qt_dev/add_plugin.py b/papi/gui/qt_dev/add_plugin.py deleted file mode 100644 index dee10da0..00000000 --- a/papi/gui/qt_dev/add_plugin.py +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors -Sven Knuth -""" - -__author__ = 'knuths' - -from PySide.QtGui import QDialog, QDialogButtonBox -from PySide.QtGui import QTreeWidgetItem, QRegExpValidator -from PySide import QtGui -from PySide.QtCore import QRegExp - -from papi.ui.gui.qt_dev.add_plugin import Ui_AddPlugin -from papi.constants import PLUGIN_ROOT_FOLDER_LIST -from papi.yapsy.PluginManager import PluginManager - - -class AddPlugin(QDialog, Ui_AddPlugin): - - def __init__(self, callback_function, parent=None): - super(AddPlugin, self).__init__(parent) - self.setupUi(self) - self.dgui = None - - self.callback_functions = callback_function - - self.treePlugin.currentItemChanged.connect(self.pluginItemChanged) - - - self.buttonBox.clicked.connect(self.button_clicked) - - self.subscriberID = None - self.targetID = None - self.blockName = None - self.setWindowTitle("Add Plugin") - - self.plugin_manager = PluginManager() - self.plugin_path = "../plugin/" - - self.plugin_manager.setPluginPlaces( - PLUGIN_ROOT_FOLDER_LIST - ) - self.setWindowTitle('Available Plugins') - - - self.visual_root = QTreeWidgetItem(self.treePlugin) - self.visual_root.setText(0, 'ViP') - self.io_root = QTreeWidgetItem(self.treePlugin) - self.io_root.setText(0, 'IOP') - self.dpp_root = QTreeWidgetItem(self.treePlugin) - self.dpp_root.setText(0, 'DPP') - self.pcb_root = QTreeWidgetItem(self.treePlugin) - self.pcb_root.setText(0, 'PCB') - - self.plugin_uname = None - self.plugin_name = None - - self.configuration_inputs = {} - - def setDGui(self, dgui): - self.dgui = dgui - - def getDGui(self): - """ - - :return: - :rtype DGui: - """ - return self.dgui - - def pluginItemChanged(self, item): - if hasattr(item, 'pluginfo'): - pluginfo = item.pluginfo - - self.le_path.setText(pluginfo.path) - - startup_config = pluginfo.plugin_object.get_startup_configuration() - - position = 0; - - self.configuration_inputs.clear() - - self.clear_layout(self.customFormLayout) - - for attr in startup_config: - value = startup_config[attr]['value'] - label = QtGui.QLabel(self.formLayoutWidget_2) - label.setText(attr) - label.setObjectName(attr + "_label") - - line_edit = QtGui.QLineEdit(self.formLayoutWidget_2) - line_edit.setText(str(value)) - line_edit.setObjectName(attr + "_line_edit") - - self.customFormLayout.setWidget(position, QtGui.QFormLayout.LabelRole, label) - self.customFormLayout.setWidget(position, QtGui.QFormLayout.FieldRole, line_edit) - - # ------------------------------- - # Check for regex description - # ------------------------------- - - if 'regex' in startup_config[attr]: - regex = startup_config[attr]['regex'] - rx = QRegExp(regex) - validator = QRegExpValidator(rx, self) - line_edit.setValidator(validator) - - self.configuration_inputs[attr] = line_edit - - position+=1 - - def showEvent(self, *args, **kwargs): - self.plugin_manager.collectPlugins() - for pluginfo in self.plugin_manager.getAllPlugins(): - - plugin_item = None - - if '/visual/' in pluginfo.path: - plugin_item = QTreeWidgetItem(self.visual_root) - if '/io/' in pluginfo.path: - plugin_item = QTreeWidgetItem(self.io_root) - if '/dpp/' in pluginfo.path: - plugin_item = QTreeWidgetItem(self.dpp_root) - if '/pcp/' in pluginfo.path: - plugin_item = QTreeWidgetItem(self.pcb_root) - - if plugin_item is not None: - plugin_item.pluginfo = pluginfo - plugin_item.setText(0, pluginfo.name) - - def button_clicked(self, button): - - if self.buttonBox.buttonRole(button) == QDialogButtonBox.ApplyRole: - - plugin_item = self.treePlugin.currentItem() - - self.plugin_name = plugin_item.pluginfo.name - self.plugin_uname = self.le_uname.text() - - config = plugin_item.pluginfo.plugin_object.get_startup_configuration() - - for attr in self.configuration_inputs: - config[attr]['value'] = self.configuration_inputs[attr].text() - - self.callback_functions['do_create_plugin'](self.plugin_name, self.plugin_uname, config=config) - - self.le_uname.setText('') - - button.setFocus() - - def clear_layout(self, layout): - while layout.count(): - child = layout.takeAt(0) - if child.widget() is not None: - child.widget().deleteLater() - elif child.layout() is not None: - self.clear_layout(child.layout()) \ No newline at end of file diff --git a/papi/gui/qt_dev/add_subscriber.py b/papi/gui/qt_dev/add_subscriber.py deleted file mode 100644 index ee0677cc..00000000 --- a/papi/gui/qt_dev/add_subscriber.py +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors -Sven Knuth -""" - -__author__ = 'knuths' - -from papi.ui.gui.qt_dev.add_subscriber import Ui_AddSubscriber -from PySide.QtGui import QDialog, QAbstractButton, QDialogButtonBox -from PySide.QtGui import QTreeWidgetItem -from papi.constants import PLUGIN_PCP_IDENTIFIER - -class AddSubscriber(QDialog, Ui_AddSubscriber): - - def __init__(self, callback_functions, parent=None): - super(AddSubscriber, self).__init__(parent) - self.setupUi(self) - self.dgui = None - - self.treeSubscriber.currentItemChanged.connect(self.subscriberItemChanged) - self.treeTarget.currentItemChanged.connect(self.targetItemChanged) - self.treeBlock.currentItemChanged.connect(self.blockItemChanged) - - self.buttonBox.clicked.connect(self.button_clicked) - - self.subscriberID = None - self.targetID = None - self.blockName = None - self.signalName = None - self.signalIndex = [] - self.setWindowTitle("Create Subscribtion") - - self.callback_functions = callback_functions - - def setDGui(self, dgui): - self.dgui = dgui - - def getDGui(self): - """ - - :return: - :rtype DGui: - """ - return self.dgui - - def subscriberItemChanged(self, item): - self.treeTarget.clear() - if hasattr(item, 'dplugin'): - - subscriber = self.treeSubscriber.currentItem().dplugin - - dplugin_ids = self.dgui.get_all_plugins() - - for dplugin_id in dplugin_ids: - dplugin = dplugin_ids[dplugin_id] - - # ------------------------------ - # Sort DPluginItem in TreeWidget of Subscriber and Target - # ------------------------------ - - if subscriber.id is not dplugin_id and len(dplugin.get_dblocks()) is not 0 and dplugin.type != PLUGIN_PCP_IDENTIFIER: - - target_item = QTreeWidgetItem(self.treeTarget) - - target_item.dplugin = dplugin - target_item.setText(0, str(dplugin.uname) ) - - def targetItemChanged(self, item): - - self.treeBlock.clear() - - if hasattr(item, 'dplugin'): - dplugin = item.dplugin - dblock_ids = dplugin.get_dblocks() - - for dblock_id in dblock_ids: - dblock = dblock_ids[dblock_id] - block_item = QTreeWidgetItem(self.treeBlock) - block_item.dblock = dblock - block_item.setText(0, dblock.name) - - def blockItemChanged(self, item): - self.treeSignal.clear() - - if hasattr(item, 'dblock'): - dblock = item.dblock - - signal_names = dblock.get_signals() - - for signal_name in signal_names: - signal_item = QTreeWidgetItem(self.treeSignal) - signal_item.setText(0, signal_name) - - def showEvent(self, *args, **kwargs): - - self.treeSubscriber.clear() - self.treeTarget.clear() - self.treeBlock.clear() - - dplugin_ids = self.dgui.get_all_plugins() - - for dplugin_id in dplugin_ids: - dplugin = dplugin_ids[dplugin_id] - - # ------------------------------ - # Sort DPluginItem in TreeWidget of Subscriber and Target - # ------------------------------ - - if dplugin.type != PLUGIN_PCP_IDENTIFIER: - - subscriber_item = QTreeWidgetItem(self.treeSubscriber) - - subscriber_item.dplugin = dplugin - subscriber_item.setText(0, str(dplugin.uname) ) - - # # ------------------------------- - # # Set amount of blocks and parameters as meta information - # # ------------------------------- - # dblock_ids = dplugin.get_dblocks() - # - # plugin_item.setText(self.get_column_by_name("#BLOCKS"), str(len(dblock_ids.keys()))) - - def button_clicked(self, button): - - if self.buttonBox.buttonRole(button) == QDialogButtonBox.ApplyRole: - - subscriber_item = self.treeSubscriber.currentItem() - target_item = self.treeTarget.currentItem() - block_item = self.treeBlock.currentItem() - signal_item = self.treeSignal.currentItem() - - self.subscriberID = subscriber_item.dplugin.id - self.targetID = target_item.dplugin.id - self.blockName = block_item.dblock.name - - self.signalIndex = [] - - for item in self.treeSignal.selectedItems(): - signalName = item.text(0) - - index = block_item.dblock.get_signals().index(signalName) - - self.signalIndex.append(index) - - button.setFocus() - - self.callback_functions['do_subscribe'](self.subscriberID, self.targetID, self.blockName, self.signalIndex) \ No newline at end of file diff --git a/papi/gui/qt_dev/gui_main.py b/papi/gui/qt_dev/gui_main.py deleted file mode 100644 index 3158325f..00000000 --- a/papi/gui/qt_dev/gui_main.py +++ /dev/null @@ -1,332 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors -Sven Knuth, Stefan Ruppin -""" - -__author__ = 'knuths' - -import sys -import time -import os - -from PySide.QtGui import QMainWindow, QApplication -from PySide.QtGui import QIcon -from PySide.QtCore import QSize - -from papi.ui.gui.qt_dev.main import Ui_MainGUI -from papi.gui.qt_dev.manager import Overview -from papi.data.DGui import DGui -from papi.ConsoleLog import ConsoleLog -from papi.gui.qt_dev.add_plugin import AddPlugin -from papi.gui.qt_dev.add_subscriber import AddSubscriber -from papi.gui.qt_dev.add_pcp_subscriber import AddPCPSubscriber -from papi.constants import GUI_PAPI_WINDOW_TITLE, GUI_WOKRING_INTERVAL, GUI_PROCESS_CONSOLE_IDENTIFIER, \ - GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_START_CONSOLE_MESSAGE -from papi.constants import CONFIG_DEFAULT_FILE -from papi.gui.gui_api import Gui_api -from papi.gui.gui_event_processing import GuiEventProcessing -import papi.pyqtgraph as pq -from papi.pyqtgraph import QtCore - - - -# Enable antialiasing for prettier plots -pq.setConfigOptions(antialias=False) - -class GUI(QMainWindow, Ui_MainGUI): - - def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): - super(GUI, self).__init__(parent) - self.setupUi(self) - - self.create_actions() - - if gui_data is None: - self.gui_data = DGui() - else: - self.gui_data = gui_data - - # TODO: - # callback functions for create plugin (intead scopeArea parameter) and for delete Plugin in ..GuiEventProcessing - self.gui_api = Gui_api(self.gui_data, core_queue, gui_id) - self.gui_event_processing = GuiEventProcessing(self.gui_data, core_queue, gui_id, gui_queue) - - self.callback_functions = { - 'do_create_plugin' : self.gui_api.do_create_plugin, - 'do_delete_plugin' : self.gui_api.do_delete_plugin, - 'do_set_parameter' : self.gui_api.do_set_parameter, - 'do_subscribe' : self.gui_api.do_subscribe, - 'do_unsubscribe' : self.gui_api.do_unsubscribe, - 'do_set_parameter' : self.gui_api.do_set_parameter_uname - } - - self.manager_overview = Overview(self.callback_functions) - self.manager_overview.dgui = self.gui_data - self.setWindowTitle(GUI_PAPI_WINDOW_TITLE) - - self.core_queue = core_queue - self.gui_queue = gui_queue - - self.gui_id = gui_id - - self.count = 0 - - self.log = ConsoleLog(GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_PROCESS_CONSOLE_IDENTIFIER) - - self.AddSub = None - self.AddPlu = None - - self.log.printText(1,GUI_START_CONSOLE_MESSAGE + ' .. Process id: '+str(os.getpid())) - - self.stefans_text_field.setText('1') - # ------------------------------------- - # Create actions for buttons - # ------------------------------------- - - self.buttonCreatePlugin.clicked.connect(self.create_plugin) - self.buttonCreateSubscription.clicked.connect(self.create_subscription) - self.buttonCreatePCPSubscription.clicked.connect(self.create_pcp_subscription) - self.buttonShowOverview.clicked.connect(self.ap_overview) - self.buttonExit.clicked.connect(self.close) - - # ------------------------------------- - # Create Icons for buttons - # ------------------------------------- - - addplugin_icon = QIcon.fromTheme("list-add") - close_icon = QIcon.fromTheme("application-exit") - overview_icon = QIcon.fromTheme("view-fullscreen") - addsubscription_icon = QIcon.fromTheme("list-add") - - # ------------------------------------- - # Set Icons for buttons - # ------------------------------------- - - self.buttonCreatePlugin.setIcon(addplugin_icon) - self.buttonCreatePlugin.setIconSize(QSize(30, 30)) - - self.buttonExit.setIcon(close_icon) - self.buttonExit.setIconSize(QSize(30, 30)) - - self.buttonShowOverview.setIcon(overview_icon) - self.buttonShowOverview.setIconSize(QSize(30, 30)) - - self.buttonCreateSubscription.setIcon(addsubscription_icon) - self.buttonCreateSubscription.setIconSize(QSize(30, 30)) - - self.buttonCreatePCPSubscription.setIcon(addsubscription_icon) - self.buttonCreatePCPSubscription.setIconSize(QSize(30, 30)) - - # ------------------------------------- - # Set Tooltipps for buttons - # ------------------------------------- - - self.buttonExit.setToolTip("Exit PaPI") - self.buttonCreatePlugin.setToolTip("Add New Plugin") - self.buttonCreateSubscription.setToolTip("Create New Subscription") - self.buttonCreatePCPSubscription.setToolTip("Create New PCP Subscription") - - self.buttonShowOverview.setToolTip("Show Overview") - - # ------------------------------------- - # Set TextName to '' - # ------------------------------------- - - self.buttonExit.setText('') - self.buttonCreatePlugin.setText('') - self.buttonCreateSubscription.setText('') - self.buttonShowLicence.setText('') - self.buttonShowOverview.setText('') - - def run(self): - # create a timer and set interval for processing events with working loop - QtCore.QTimer.singleShot(GUI_WOKRING_INTERVAL, lambda: self.gui_event_processing.gui_working(self.closeEvent)) - - def set_dgui_data(self, dgui): - self.gui_data = dgui - self.manager_overview.dgui =dgui - - def dbg(self): - print("Action") - - def create_actions(self): - self.actionM_License.triggered.connect(self.menu_license) - self.actionM_Quit.triggered.connect(self.menu_quit) - - self.actionP_Overview.triggered.connect(self.ap_overview) - - - self.stefans_button.clicked.connect(self.stefan) - self.stefans_button_2.clicked.connect(self.stefan_at_his_best) - - def create_plugin(self): - """ - This function is called to create an QDialog, which is used to create Plugins - :return: - """ - self.AddPlu = AddPlugin(self.callback_functions) - self.AddPlu.setDGui(self.gui_data) - self.AddPlu.show() - self.AddPlu.raise_() - self.AddPlu.activateWindow() - r = self.AddPlu.exec_() - - del self.AddPlu - self.AddPlu = None - - def create_subscription(self): - """ - This function is called to create an QDialog, which is used to create a subscription for a single Plugin - :return: - """ - - self.AddSub = AddSubscriber(self.callback_functions) - - self.AddSub.setDGui(self.gui_data) - self.AddSub.show() - self.AddSub.raise_() - self.AddSub.activateWindow() - r = self.AddSub.exec_() - - del self.AddSub - self.AddSub = None - - def create_pcp_subscription(self): - """ - This function is called to create an QDialog, which is used to create a subscription for a single Plugin - :return: - """ - - self.AddPCPSub = AddPCPSubscriber(self.callback_functions) - - self.AddPCPSub.setDGui(self.gui_data) - self.AddPCPSub.show() - self.AddPCPSub.raise_() - self.AddPCPSub.activateWindow() - r = self.AddPCPSub.exec_() - - del self.AddPCPSub - self.AddPCPSub = None - - - def menu_license(self): - pass - - def menu_quit(self): - self.close() - pass - - def ap_overview(self): - self.manager_overview.show() - pass - - def closeEvent(self, *args, **kwargs): - self.gui_api.do_close_program() - - self.manager_overview.close() - - if self.AddPlu is not None: - self.AddPlu.close() - - if self.AddSub is not None: - self.AddSub.close() - - self.close() - - - - def stefan_at_his_best(self): - - self.gui_api.do_load_xml(CONFIG_DEFAULT_FILE) - - def stefan(self): - self.count += 1 - - op= 0 - - if op == 0: - self.gui_api.do_save_xml_config(CONFIG_DEFAULT_FILE) - - if op == 1: - self.gui_api.do_delete_plugin_uname('Plot1') - self.gui_api.do_delete_plugin_uname('SInus1') - self.gui_api.do_delete_plugin_uname('Butt') - - if op == 2: - self.do_create_plugin('Sinus','Sinus1') #id 2 - self.do_create_plugin('Plot','Plot1') #id 3 - #self.do_create_plugin('Plot','Plot2') #id 4 - - time.sleep(0.1) - - #self.do_subscribe(3,2,'SinMit_f3',2) - #self.do_subsribe(4,2,'SinMit_f3') - - - -def startGUI(CoreQueue, GUIQueue,gui_id): - """ - Function to call to start gui operation - :param CoreQueue: link to queue of core - :type CoreQueue: Queue - :param GUIQueue: queue where gui receives messages - :type GUIQueue: Queue - :param gui_id: id of gui for events - :type gui_id: int - :return: - """ - app = QApplication(sys.argv) - gui = GUI(CoreQueue, GUIQueue,gui_id) - gui.run() - gui.show() - app.exec_() - -def startGUI_TESTMOCK(CoreQueue, GUIQueue,gui_id, data_mock): - """ - Function to call to start gui operation - :param CoreQueue: link to queue of core - :type CoreQueue: Queue - :param GUIQueue: queue where gui receives messages - :type GUIQueue: Queue - :param gui_id: id of gui for events - :type gui_id: int - :return: - """ - app = QApplication(sys.argv) - app.aboutToQuit.connect(app.deleteLater) - - gui = GUI(CoreQueue, GUIQueue,gui_id, data_mock) - - gui.run() - gui.show() - app.exec_() - -if __name__ == '__main__': - # main of GUI, just for stand alone gui testing - app = QApplication(sys.argv) - frame = GUI(None,None,None) - frame.show() - app.exec_() diff --git a/papi/gui/qt_dev/manager.py b/papi/gui/qt_dev/manager.py deleted file mode 100644 index 4d45a8be..00000000 --- a/papi/gui/qt_dev/manager.py +++ /dev/null @@ -1,457 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors -Sven Knuth -""" - -__author__ = 'knuths' - -from PySide.QtGui import QMainWindow, QTreeWidgetItem, QTableWidgetItem -from PySide.QtCore import Qt - -from papi.ui.gui.qt_dev.manager import Ui_Manager -from papi.yapsy.PluginManager import PluginManager -from papi.constants import PLUGIN_PCP_IDENTIFIER, PLUGIN_DPP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER, PLUGIN_IOP_IDENTIFIER - - -class Overview(QMainWindow, Ui_Manager): - def __init__(self, callback_functions, parent=None): - """ - - :param dgui: - :param parent: - :return: - """ - - super(Overview, self).__init__(parent) - self.setupUi(self) - - self.setWindowTitle('Overview Plugins') - - self.callback_functions = callback_functions - - self.treePlugin.currentItemChanged.connect(self.plugin_item_changed) - self.treeBlock.currentItemChanged.connect(self.block_item_changed) - #self.tableParameter.cellChanged.connect(self.parameterCellChanged) - # ---------------------------------------- - # Create Root Elements for TreeWidget - # ---------------------------------------- - self.visual_root = QTreeWidgetItem(self.treePlugin) - self.visual_root.setText(self.get_column_by_name("PLUGIN"), '[ViP]') - self.io_root = QTreeWidgetItem(self.treePlugin) - self.io_root.setText(self.get_column_by_name("PLUGIN"), '[IOP]') - self.dpp_root = QTreeWidgetItem(self.treePlugin) - self.dpp_root.setText(self.get_column_by_name("PLUGIN"), '[DPP]') - self.pcb_root = QTreeWidgetItem(self.treePlugin) - self.pcb_root.setText(self.get_column_by_name("PLUGIN"), '[PCB]') - self.dgui = None - -# self.subscribeButton.clicked.connect(self.subscribe_action) - - # --------------------------------- - # Search for PCP Plugins - # --------------------------------- - - self.plugin_manager = PluginManager() - self.plugin_path = "../plugin/" - - self.plugin_manager.setPluginPlaces( - [ - # self.plugin_path + "visual", 'plugin/visual', - # self.plugin_path + "io", 'plugin/io', - # self.plugin_path + "dpp", 'plugin/dpp', - self.plugin_path + "pcp", 'plugin/pcp' - ] - ) - - # myAction = QAction('RechtsKLick', self) - # self.setContextMenuPolicy(Qt.ActionsContextMenu) - # - # self.addAction(myAction) - # - # myAction.triggered.connect( lambda : self.contextMenu(self.treePlugin.currentItem())) - - - # def contextMenu(self, item): - # if hasattr(item, 'object'): - # print(item.object) - # print('itemContextMenu') - - # def add_plugin(self): - # AddPlu = AddPlugin() - # AddPlu.setDGui(self.dgui) - # AddPlu.show() - # AddPlu.raise_() - # AddPlu.activateWindow() - # r = AddPlu.exec_() - # - # if r == 1 : - # self.callback_functions['do_create_plugin'](AddPlu.plugin_name, AddPlu.plugin_uname) - # - # print("ReturnCode ", str(r)) - - # def add_subscribtion(self): - # - # AddSub = AddSubscriber() - # AddSub.setDGui(self.dgui) - # AddSub.show() - # AddSub.raise_() - # AddSub.activateWindow() - # r = AddSub.exec_() - # - # if r == 1 : - # subscriber_id = AddSub.subscriberID - # target_id = AddSub.targetID - # block_name = AddSub.blockName - # - # self.callback_functions['do_subscribe'](subscriber_id, target_id, block_name) - # - # print("ReturnCode " , str(r)) - -# def subscribe_action(self): -# item = self.treePlugin.currentItem() -# if hasattr(item, 'object'): -# print(item.object) -# sub_id = item.object.id -# target_id = int(self.target_id.text()) -# block_name = str(self.block_name.text()) -# # print(sub_id + " - " + target_id + " - " + block_name) -# self.callback_functions['do_subscribe'](sub_id, target_id, block_name) -# -# print('itemCreate') - - def plugin_item_changed(self, item): - """ - Function is called when a new item/plugin is selected - :param item: - :return: - """ - self.clean() - - # ------------------------------ - # Remove slot for signal cellChanged - # if possible - # ------------------------------ - try: - self.tableParameter.cellChanged.disconnect(self.parameterCellChanged) - except: - pass - - if hasattr(item, 'dplugin'): - - dplugin = item.dplugin - - - - # ------------------------------ - # Add Meta information of DPlugin - # ------------------------------ - self.le_ID.setText(str(dplugin.id)) - self.le_Type.setText(str(dplugin.type)) - - # ------------------------------ - # Add DBlocksItem for DPluginItem - # ------------------------------ - dblock_root = self.treeBlock - - dblock_ids = dplugin.get_dblocks() - - for dblock_id in dblock_ids: - dblock = dblock_ids[dblock_id] - block_item = QTreeWidgetItem(dblock_root) - block_item.dblock = dblock - block_item.setText(self.get_column_by_name("BLOCK"), dblock.name) - - # --------------------------- - # Add Subscribers of DBlock - # --------------------------- - - subscriber_ids = dblock.get_subscribers() - - for subscriber_id in subscriber_ids: - subscriber_item = QTreeWidgetItem(block_item) - - subscriber = self.dgui.get_dplugin_by_id(subscriber_id) - - subscriber_item.setText(self.get_column_by_name("SUBSCRIBER"), str(subscriber.uname)) - - # ------------------------------ - # Add DParameterItem for DPluginItem - # ------------------------------ - - dparameter_table = self.tableParameter - - dparameter_names = dplugin.get_parameters() - - row = 0 - - dparameter_table.setRowCount(len(dparameter_names.keys())) - - for dparameter_name in dparameter_names: - - dparameter = dparameter_names[dparameter_name] - # --------------------- - # Set Parameter Name - # --------------------- - parameter_item_name = QTableWidgetItem( str(dparameter.name) ) - parameter_item_name.setFlags( Qt.ItemIsSelectable and not Qt.ItemIsEditable and Qt.ItemIsEnabled) - dparameter_table.setItem(row, 0, parameter_item_name) - - # --------------------- - # Set PCPs in table - # --------------------- -# combo_box = QComboBox() -# -# #TODO: Erzeugt Fehler, da objekte beim Wechsel von Plugins geloescht werden. -# # combo_box.currentIndexChanged.connect( lambda : self.combo_box_parameter_changed(dplugin, dparameter, combo_box)) -# # -# dparameter_table.setCellWidget(row, 1, combo_box) -# -# self.plugin_manager.collectPlugins() -# -# combo_box.addItem("None", None) -# -# for pluginfo in self.plugin_manager.getAllPlugins(): -# -# combo_box.addItem(pluginfo.name, pluginfo) - - # --------------------- - # Set Current Value in table - # --------------------- - parameter_item_value = QTableWidgetItem( str(dparameter.value) ) - parameter_item_value.dplugin = dplugin - parameter_item_value.dparameter = dparameter - dparameter_table.setItem(row, 2, parameter_item_value) - - # --------------------------------- - # Add slot for signal cellChanged - # --------------------------------- - - self.tableParameter.cellChanged.connect(self.parameterCellChanged) - - row+=1 - - def parameterCellChanged(self, row, column): - """ - This function is always called when - an (text)-item within the table is changed - :param row: - :param column: - :return: - """ - # ----------------------------------------------- - # Only interested in changes of the third column - # ----------------------------------------------- - if column == 0: - return 0 - - item = self.tableParameter.item(row, column) - nvalue = item.text() - - self.callback_functions['do_set_parameter'](item.dplugin.uname, item.dparameter.name, float(nvalue)) - - #print('Parameter Change Request for ' + item.dparameter.name + " of Plugin " + item.dplugin.uname + " Value: " + str(nvalue)) - - # def combo_box_parameter_changed(self, dplugin, dparameter, box): - # """ - # This function is always called when - # an item within a combox box is changed - # - # :param dplugin: - # :param dparameter: - # :param box: - # :return: - # """ - # # if dplugin == None or dparameter == None or box == None: - # # return 0 - # - # dparameter_name = dparameter.name - # index = box.currentIndex() - # - # pcp = box.itemData(index) - # - # if pcp is None: - # return 0 - # - # config={ - # 'dplugin_id' : {}, - # 'default' : {}, - # 'name' : {} - # } - # - # config['dplugin_id']['value']= str(dplugin.id) - # config['default']['value']= str(dparameter.default) - # config['name']['value'] = dparameter_name - # - # - # self.callback_functions['do_create_plugin'](pcp.name, dparameter.name + "_" + pcp.name, config=config) - # - # # if pcp is not None: - # # print('GUI:Manager: PCP Change Request for Parameter ' + dparameter.name + " of Plugin " + dplugin.uname + " PCB " + pcp.name ) - # # else: - # # print('GUI:Manager: PCP Change Request for Parameter ' + dparameter.name + " of Plugin " + dplugin.uname + " PCB None " ) - # # pass - - def block_item_changed(self, item): - self.treeSignal.clear() - - if hasattr(item, 'dblock'): - dblock = item.dblock - - signals = dblock.get_signals() - - for signal in signals: - print(signal) - signal_item = QTreeWidgetItem(self.treeSignal) - signal_item.setText(self.get_column_by_name("SIGNAL"), signal) - - def showEvent(self, *args, **kwargs): - dplugin_ids = self.dgui.get_all_plugins() - - #Add DPlugins in QTree - - for dplugin_id in dplugin_ids: - dplugin = dplugin_ids[dplugin_id] - - # ------------------------------ - # Sort DPluginItem in TreeWidget - # ------------------------------ - - if dplugin.type == PLUGIN_VIP_IDENTIFIER: - plugin_item = QTreeWidgetItem(self.visual_root) - if dplugin.type == PLUGIN_IOP_IDENTIFIER: - plugin_item = QTreeWidgetItem(self.io_root) - if dplugin.type == PLUGIN_DPP_IDENTIFIER: - plugin_item = QTreeWidgetItem(self.dpp_root) - if dplugin.type == PLUGIN_PCP_IDENTIFIER: - plugin_item = QTreeWidgetItem(self.pcb_root) - - plugin_item.dplugin = dplugin - plugin_item.setText(self.get_column_by_name("PLUGIN"), str(dplugin.uname) ) - - # ------------------------------- - # Set amount of blocks and parameters as meta information - # ------------------------------- - dparameter_names = dplugin.get_parameters() - dblock_ids = dplugin.get_dblocks() - - plugin_item.setText(self.get_column_by_name("#PARAMETERS"), str(len(dparameter_names.keys()))) - plugin_item.setText(self.get_column_by_name("#BLOCKS"), str(len(dblock_ids.keys()))) - - def clean(self): - """ - This function is called to remove old values in form fields or similar - :return: - """ - - #------------------ - # Remove content in form fields - #------------------ - - self.le_ID.setText("") - self.le_Type.setText("") - self.le_Path.setText("") - - #------------------ - # Remove items in parameter tree - #------------------ - - self.treeBlock.clear() - - #------------------ - # Remove items in block tree - #------------------ - - self.treeSignal.clear() - - self.tableParameter.clear() - - #self.treeParameter.clear() - - #self.tableParameter.clear() - - def hideEvent(self, *args, **kwargs): - """ - This event is used to clear the TreeWidget - :param args: - :param kwargs: - :return: - """ - - item = self.io_root.child(0) - - while item is not None: - self.io_root.removeChild(item) - item = self.io_root.child(0) - - item = self.visual_root.child(0) - - while item is not None: - self.visual_root.removeChild(item) - item = self.visual_root.child(0) - - item = self.dpp_root.child(0) - - while item is not None: - self.dpp_root.removeChild(item) - item = self.dpp_root.child(0) - - item = self.pcb_root.child(0) - - while item is not None: - self.pcb_root.removeChild(item) - item = self.pcb_root.child(0) - - def resizeEvent(self, event): - h = event.size().height() - w = event.size().width() - - - def get_column_by_name(self, name): - """ - Returns column number by name - :param name: - :return: - """ - if name == "PLUGIN": - return 0 - - if name == "#PARAMETERS": - return 1 - - if name == "#BLOCKS": - return 2 - - if name == "SUBSCRIBER": - return 1 - - if name == "BLOCK": - return 0 - - if name == "PARAMETER": - return 0 - - if name == "SIGNAL": - return 0 From a9d1d958e535b192fd51206c4cdd78e43739c8a3 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 13 Jan 2015 12:23:24 +0100 Subject: [PATCH 065/260] deleted old development gui --- papi/main.py | 1 - 1 file changed, 1 deletion(-) diff --git a/papi/main.py b/papi/main.py index c3f00f5b..a135d63c 100644 --- a/papi/main.py +++ b/papi/main.py @@ -30,7 +30,6 @@ import sys from papi.core import Core -from papi.gui.qt_dev.gui_main import startGUI as dev_startGui from papi.gui.qt_new.main import startGUI as new_startGui From 497dac768a92af481c479e793da67dcc1cc45227 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 13 Jan 2015 12:24:53 +0100 Subject: [PATCH 066/260] deleted old development gui --- main.py | 1 - papi/main.py | 42 ------------------------------------------ 2 files changed, 43 deletions(-) delete mode 100644 papi/main.py diff --git a/main.py b/main.py index c3f00f5b..a135d63c 100644 --- a/main.py +++ b/main.py @@ -30,7 +30,6 @@ import sys from papi.core import Core -from papi.gui.qt_dev.gui_main import startGUI as dev_startGui from papi.gui.qt_new.main import startGUI as new_startGui diff --git a/papi/main.py b/papi/main.py deleted file mode 100644 index a135d63c..00000000 --- a/papi/main.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors -Sven Knuth -""" - -__author__ = 'control' - -import sys -from papi.core import Core -from papi.gui.qt_new.main import startGUI as new_startGui - - -def main(): - core = Core(new_startGui) - core.run() - - -if __name__ == '__main__': - sys.exit(main()) From 2a27779b6ce529592f63cfcf266a68d28fdfb4e2 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 13 Jan 2015 12:25:41 +0100 Subject: [PATCH 067/260] better names and order for plot starup parameters --- papi/plugin/visual/Plot/Plot.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index f82b620b..5a93a33d 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -875,20 +875,24 @@ def get_plugin_configuration(self): 'value': '1', 'regex': '^(1|0)$', 'type': 'bool', - 'display_text': 'xRange-auto' + 'advanced': '1', + 'display_text': 'x: auto range' }, 'yRange-auto': { 'value': '1', 'regex': '^(1|0)$', 'type': 'bool', - 'display_text': 'yRange-auto' + 'advanced': '1', + 'display_text': 'y: auto range' }, 'xRange': { 'value': '[0.0 1.0]', 'regex': '(\d+\.\d+)', - 'display_text': 'xRange-auto' + 'advanced': '1', + 'display_text': 'x: range' }, 'yRange': { 'value': '[0.0 1.0]', 'regex': '(\d+\.\d+)', - 'display_text': 'yRange-auto' + 'advanced': '1', + 'display_text': 'y: range' } } # http://www.regexr.com/ From 50d990325947499b8b6a218308d2fcc161d1cc2c Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 13 Jan 2015 15:53:33 +0100 Subject: [PATCH 068/260] added documentation --- papi/data/DParameter.py | 14 +++++++++++- papi/data/DPlugin.py | 50 ++++++++++++++++++++++++++--------------- papi/data/DSignal.py | 12 +++++++++- 3 files changed, 56 insertions(+), 20 deletions(-) diff --git a/papi/data/DParameter.py b/papi/data/DParameter.py index 31704288..8b4e15c3 100644 --- a/papi/data/DParameter.py +++ b/papi/data/DParameter.py @@ -32,8 +32,20 @@ class DParameter(DObject): - + """ + This object is used to describe parameters provided by plugins. + Parameters are used to interact with + """ def __init__(self, name, default=0, Regex = None, OptionalObject=None): + """ + A Parameter must be described by a name. It optionally possible to set a default value, a Regex object and an OptionalObject. + + :param name: Parameter name + :param default: Default value + :param Regex: Used to limit user input + :param OptionalObject: Can be used by plugin developer to store other internal information + :return: + """ super(DParameter, self).__init__() self.default = default diff --git a/papi/data/DPlugin.py b/papi/data/DPlugin.py index ea54a891..017f5e4a 100644 --- a/papi/data/DPlugin.py +++ b/papi/data/DPlugin.py @@ -35,8 +35,16 @@ class DBlock(DObject): - + """ + Contains a bunch of signals of a the same data source. + """ def __init__(self, name): + """ + A block is described by name, which has to be unique within the context of the plugin owning this block. + + :param name: name of the block + :return: + """ super(DObject, self).__init__() self.subscribers = {} @@ -101,6 +109,9 @@ def rm_signal(self, signal): def get_subscribers(self): """ + Returns all subscribers of this plugin. Returns a copy that means + changes have no effect on the PaPI data structure. + :return: :rtype []: """ @@ -108,6 +119,7 @@ def get_subscribers(self): def get_signal_by_uname(self, uname): """ + Returns a signal object by a signal's uname. :param uname: :return DSignal: @@ -121,27 +133,24 @@ def get_signal_by_uname(self, uname): def get_signals(self): """ - DEPRECATED - Returns a copy of the internal signal names - :return: - """ - return copy.deepcopy(self.signals) - - #NOT NEEDED ANYMORE !!! - def get_signal_name(self, signal: int): - """ - DEPRECATED - :param signal: :return: """ - raise NotImplementedError("Stop Using this function.") + return copy.deepcopy(self.signals) class DPlugin(DObject): + """ + Used to describe a single plugin. + """ def __init__(self): + """ + Used to create the plugin data object. + + :return: + """ super(DPlugin, self).__init__() self.process = None self.pid = None @@ -163,7 +172,8 @@ def __init__(self): def subscribe_signals(self, dblock, signals): """ - This function is used to subscribe a bunch of signals + This function is used to subscribe a bunch of signals. + :param dblock: :param signals: :return: @@ -183,7 +193,8 @@ def subscribe_signals(self, dblock, signals): def unsubscribe_signals(self, dblock, signals): """ - This function is used to unsubscribe a bunch of signals + This function is used to unsubscribe a bunch of signals. + :param dblock: :param signals: :return: @@ -204,7 +215,8 @@ def unsubscribe_signals(self, dblock, signals): def subscribe(self, dblock): """ - This plugins subscribes 'dblock' by remembering the dblog id + This plugins subscribes 'dblock' by remembering the dblog id. + :param dblock: DBlock which should be subscribed :return: :rtype boolean: @@ -224,7 +236,8 @@ def subscribe(self, dblock): def unsubscribe(self, dblock): """ - This plugins unsubscribes 'dblock' by forgetting the dblog id + This plugins unsubscribes 'dblock' by forgetting the dblog id. + :param dblock: DBlock which should be unsubscribed :return: :rtype boolean: @@ -246,7 +259,8 @@ def unsubscribe(self, dblock): def get_subscribtions(self): """ - Returns a dictionary of all susbcribtions + Returns a dictionary of all susbcribtions. + :return {}{} of DPlugin ids to DBlock names : :rtype: {}{} """ diff --git a/papi/data/DSignal.py b/papi/data/DSignal.py index ba211bcd..38df5213 100644 --- a/papi/data/DSignal.py +++ b/papi/data/DSignal.py @@ -32,8 +32,18 @@ class DSignal(DObject): - + """ + This object is used to describe a single signal. + """ def __init__(self, uname, dname = None): + """ + A signal is described by uname, a unique identifier in the context of DBlock. + The display name dname is used as visual representation. If now dname is given, the uname is used. + + :param uname: Unique name in the context of this signals block + :param dname: Display name + :return: + """ super(DObject, self).__init__() self.uname = uname if dname is None: From 911c37085a0ee5a9a5759be48377d3d0a40efa5b Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Thu, 15 Jan 2015 11:35:34 +0100 Subject: [PATCH 069/260] added documentation --- papi/data/DCore.py | 37 ++++++++++++++++++------------- papi/data/DGui.py | 1 - papi/data/DObject.py | 9 +++++++- papi/data/DOptionalData.py | 8 +++++++ papi/data/DParameter.py | 4 ++-- papi/data/DPlugin.py | 45 ++++++++++++++++++++++++++++++++------ papi/data/DSignal.py | 3 ++- papi/data/__init__.py | 2 +- 8 files changed, 81 insertions(+), 28 deletions(-) diff --git a/papi/data/DCore.py b/papi/data/DCore.py index fd3873c7..f303bcc3 100644 --- a/papi/data/DCore.py +++ b/papi/data/DCore.py @@ -25,18 +25,24 @@ Contributors Sven Knuth """ -from papi.data.DPlugin import DPlugin, DBlock +from papi.data.DPlugin import DPlugin from papi.ConsoleLog import ConsoleLog import papi.error_codes as ERROR -__author__ = 'control' - -import copy +__author__ = 'knuth' class DCore(): + """ + DCore contains and manages the internal data structure + """ def __init__(self): + """ + Used to initialize this object. An empty dictionary of DPlugin is created + + :return: + """ self.__DPlugins = {} self.__newid = 0 @@ -44,9 +50,9 @@ def __init__(self): def create_id(self): """ - Creates and returns random unique 64 bit integer + Creates and returns unique IDs - :returns: 64bit random integer + :returns: unique ID :rtype: int """ self.__newid += 1 @@ -143,21 +149,23 @@ def get_dplugin_by_uname(self, plugin_uname): return None - def get_all_plugins(self): """ + Returns a dictionary of all known dplugins :return: + :rtype: {} """ return self.__DPlugins def subscribe(self, subscriber_id, target_id, dblock_name): """ + Used to create a subscription. :param subscriber_id: DPlugin which likes to subscribes dblock :param target_id: DPlugin which contains the dblock for subscribtion - :param dblock_name: DBlock for subscribtion + :param dblock_name: DBlock identified by its unique name for subscribtion :return: """ @@ -198,10 +206,11 @@ def subscribe(self, subscriber_id, target_id, dblock_name): def unsubscribe(self, subscriber_id, target_id, dblock_name): """ + Used to remove a subscription. :param subscriber_id: DPlugin which likes to unsubscribes dblock :param target_id: DPlugin which contains the dblock for subscribtion - :param dblock_name: DBlock for unsubscribtion + :param dblock_name: DBlock identified by its unique name for unsubscribtion :return: :rtype boolean: """ @@ -238,16 +247,14 @@ def unsubscribe_all(self, dplugin_id): """ This function is used to cancel all subscription of the DPlugin with the dplugin_id. - :param dplugin_id: + :param dplugin_id: dplugin identifed by dplugin_id whose subscription should be canceled. :return: """ dplugin = self.get_dplugin_by_id(dplugin_id) - subscribtion_ids = dplugin.get_subscribtions().copy() - #Iterate over all DPlugins, which own a subscribed DBlock for sub_id in subscribtion_ids: sub = self.get_dplugin_by_id(sub_id) @@ -271,7 +278,7 @@ def rm_all_subscribers(self, dplugin_id): """ This function is used to remove all subscribers of all DBlocks, which are hold by the DPlugin with the dplugin_id. - :param dplugin_id: + :param dplugin_id: dplugin identifed by dplugin_id whose subscribers should be removed. :return: """ @@ -302,7 +309,7 @@ def subscribe_signals(self, subscriber_id, target_id, dblock_name, signals): :param subscriber_id: DPlugin which likes to subscribes signals of the chosen dblock :param target_id: DPlugin which contains the dblock for subscribtion - :param dblock_name: DBlock for subscribtion + :param dblock_name: DBlock identified by its unique name for subscribtion :param signals: List of signals which are needed to be added :return: """ @@ -337,7 +344,7 @@ def unsubscribe_signals(self, subscriber_id, target_id, dblock_name, signals): :param subscriber_id: DPlugin which likes to unsubscribes signals of the chosen dblock :param target_id: DPlugin which contains the dblock for subscribtion - :param dblock_name: DBlock for subscribtion + :param dblock_name: DBlock identified by its unique name for subscribtion :param signals: List of signals which are needed to be added :return: """ diff --git a/papi/data/DGui.py b/papi/data/DGui.py index c789809a..550bf691 100644 --- a/papi/data/DGui.py +++ b/papi/data/DGui.py @@ -32,5 +32,4 @@ class DGui(DCore): - pass \ No newline at end of file diff --git a/papi/data/DObject.py b/papi/data/DObject.py index 450ede5f..acac4b6d 100644 --- a/papi/data/DObject.py +++ b/papi/data/DObject.py @@ -30,6 +30,13 @@ class DObject(): - + """ + Base class for all PaPI-objects + """ def __init__(self): + """ + Every object is initialized with id = 0 + + :return: + """ self.id = 0 diff --git a/papi/data/DOptionalData.py b/papi/data/DOptionalData.py index 1107db31..30c813cf 100644 --- a/papi/data/DOptionalData.py +++ b/papi/data/DOptionalData.py @@ -30,8 +30,16 @@ class DOptionalData(object): + """ + """ def __init__(self,DATA=None,pluginID=None): + """ + + :param DATA: + :param pluginID: + :return: + """ self.data = DATA self.data_source_id = None self.plugin_identifier = None diff --git a/papi/data/DParameter.py b/papi/data/DParameter.py index 8b4e15c3..7da9a151 100644 --- a/papi/data/DParameter.py +++ b/papi/data/DParameter.py @@ -33,8 +33,8 @@ class DParameter(DObject): """ - This object is used to describe parameters provided by plugins. - Parameters are used to interact with + DParameter is used for the internal description of a parameter which are provided by a plugin. + Parameters are used to interact with plugin. """ def __init__(self, name, default=0, Regex = None, OptionalObject=None): """ diff --git a/papi/data/DPlugin.py b/papi/data/DPlugin.py index 017f5e4a..d939a2f2 100644 --- a/papi/data/DPlugin.py +++ b/papi/data/DPlugin.py @@ -36,6 +36,7 @@ class DBlock(DObject): """ + DBlock is used for the internal description of a block. Contains a bunch of signals of a the same data source. """ def __init__(self, name): @@ -142,7 +143,7 @@ def get_signals(self): class DPlugin(DObject): """ - Used to describe a single plugin. + DPlugin is used for the internal description of a plugin. """ def __init__(self): @@ -215,7 +216,7 @@ def unsubscribe_signals(self, dblock, signals): def subscribe(self, dblock): """ - This plugins subscribes 'dblock' by remembering the dblog id. + This plugin subscribes a 'dblock' by remembering the dblog id. :param dblock: DBlock which should be subscribed :return: @@ -236,7 +237,7 @@ def subscribe(self, dblock): def unsubscribe(self, dblock): """ - This plugins unsubscribes 'dblock' by forgetting the dblog id. + This plugin unsubscribes a 'dblock' by forgetting the dblog id. :param dblock: DBlock which should be unsubscribed :return: @@ -259,7 +260,7 @@ def unsubscribe(self, dblock): def get_subscribtions(self): """ - Returns a dictionary of all susbcribtions. + Returns a copy of a dictionary of all subcribtions. :return {}{} of DPlugin ids to DBlock names : :rtype: {}{} @@ -269,6 +270,9 @@ def get_subscribtions(self): def add_parameter(self, parameter): """ + Used to add a parameter for this plugin. + Returns true if parameter doesn't existed and was added, + returns false if parameter already exists. :param parameter: :return: @@ -282,8 +286,11 @@ def add_parameter(self, parameter): def rm_parameter(self, parameter): """ + Used to remove a parameter for this plugin. + Returns true if parameter existed and was deleted, + returns false if parameter doesn't exist. - :param parameter_id: + :param parameter: Parameter which should be removed. :return: :rtype boolean: """ @@ -296,6 +303,7 @@ def rm_parameter(self, parameter): def get_parameters(self): """ + Returns a list of all parameters. :return: :rtype {}: @@ -304,8 +312,11 @@ def get_parameters(self): def add_dblock(self, dblock): """ + Used to add a block for this plugin. + Returns true if parameter doesn't existed and was deleted, + returns false if parameter already exists. - :param dblock: + :param dblock: Block which should be added. :return: :rtype boolean: """ @@ -318,6 +329,9 @@ def add_dblock(self, dblock): def rm_dblock(self, dblock): """ + Used to remove a block for this plugin. + Returns true if block existed and was deleted, + returns false if block doesn't exist. :param dblock: :return: @@ -332,6 +346,7 @@ def rm_dblock(self, dblock): def get_dblocks(self): """ + Returns a list of all blocks. :return: :rtype {}: @@ -340,9 +355,10 @@ def get_dblocks(self): def get_dblock_by_name(self, dblock_name): """ + Returns a single block by its unique name of all parameters. + :param dblock_name: Uniqueder identifier for this block. :return: - :rtype DBlock: """ if dblock_name in self.__blocks: @@ -410,7 +426,10 @@ def update_meta(self, meta): class DSubscription(DObject): + """ + DSubscription is used for the internal description of a subscription. + """ def __init__(self, dblock): self.dblock = dblock self.dblock_name = dblock.name @@ -443,9 +462,21 @@ def rm_signal(self, signal): return False def get_dblock(self): + """ + Returns the block whose signals are subscribed. + + :return: + :rtype: DBlock + """ return self.dblock def get_signals(self): + """ + Returns a copy of all signals which are subscribed. + + :return: + :rtype: [] + """ return copy.copy(self.signals) def attach_signal(self, signal): diff --git a/papi/data/DSignal.py b/papi/data/DSignal.py index 38df5213..d93314c8 100644 --- a/papi/data/DSignal.py +++ b/papi/data/DSignal.py @@ -33,7 +33,8 @@ class DSignal(DObject): """ - This object is used to describe a single signal. + DSignal is used for the internal description of a signal. + """ def __init__(self, uname, dname = None): """ diff --git a/papi/data/__init__.py b/papi/data/__init__.py index bc47e275..7c97c23d 100644 --- a/papi/data/__init__.py +++ b/papi/data/__init__.py @@ -26,4 +26,4 @@ Sven Knuth """ -__author__ = 'control' +__author__ = 'knuths' From b7d3a358ad038a862052dbbf516b6ab64cd44f03 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Thu, 15 Jan 2015 11:41:34 +0100 Subject: [PATCH 070/260] deleted all usages of PapiEvent( the old one) --- cfg_collection/ExampleFourier.xml | 136 ++++++++++++++------------- cfg_collection/FourCfg.xml | 130 ------------------------- docs/papi.data.rst | 8 ++ docs/papi.gui.rst | 9 +- docs/papi.rst | 26 +---- papi/PapiEvent.py | 69 -------------- papi/core.py | 71 +++++--------- papi/event/instruction/UpdateMeta.py | 35 +++++++ papi/event/instruction/__init__.py | 3 +- papi/gui/gui_event_processing.py | 22 ++--- papi/tests/TestCore.py | 123 ------------------------ papi/tests/TestCoreMock.py | 8 +- 12 files changed, 161 insertions(+), 479 deletions(-) delete mode 100644 cfg_collection/FourCfg.xml delete mode 100644 papi/PapiEvent.py create mode 100644 papi/event/instruction/UpdateMeta.py delete mode 100644 papi/tests/TestCore.py diff --git a/cfg_collection/ExampleFourier.xml b/cfg_collection/ExampleFourier.xml index a870b489..9985640a 100644 --- a/cfg_collection/ExampleFourier.xml +++ b/cfg_collection/ExampleFourier.xml @@ -1,113 +1,113 @@ - + Plot - Label-X - time, s \w+,\s*\w+ + time, s + Label-X - - xRange-auto - [0.0 1.0] - (\d+\.\d+) - - - 1 - Determine position: (x,y) - (0,0) - \(([0-9]+),([0-9]+)\) - - - 1 - (\d+) - - - Label-Y - amplitude, V - \w+,\s+\w+ - - - Used for window title - VisualPlugin - - - 1 - Color - [0 1 2 3 4] - ^\[(\s*\d\s*)+\] - - + bool - xRange-auto 1 + yRange-auto ^(1|0)$ - + bool - yRange-auto - 1 + 0 + Grid-Y ^(1|0)$ - - yRange-auto - [0.0 1.0] - (\d+\.\d+) + + bool + 0 + Rolling Plot + ^(1|0)$ + \(([0-9]+),([0-9]+)\) + (300,300) 1 Determine size: (height,width) - (300,300) - \(([0-9]+),([0-9]+)\) - + + ^([1-9][0-9]{0,3}|10000)$ + 1000 1 - Style - [0 0 0 0 0] - ^\[(\s*\d\s*)+\] + Buffersize bool - Grid-X 0 + Grid-X ^(1|0)$ - - bool - Rolling Plot - 0 - ^(1|0)$ + + \w+,\s+\w+ + amplitude, V + Label-Y + + + (\d+\.\d+) + [0,1] + yRange-auto Plot - + + \(([0-9]+),([0-9]+)\) + (0,0) + 1 + Determine position: (x,y) + + bool - Grid-Y - 0 + 1 + xRange-auto ^(1|0)$ - + + ^\[(\s*\d\s*)+\] + [0 0 0 0 0] 1 - Buffersize - 1000 - ^([1-9][0-9]{0,3}|10000)$ + Style + + + (\d+) + 1 + + + (\d+\.\d+) + [0,1] + xRange-auto + + + VisualPlugin + Used for window title + + + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] + 1 + Color + 1 + 0 [0,1] + [0 0 0 0 0] + 0 1000 0 - [0 0 0 0 0] - [0 1 2 3 4] - 0 - 1 - 1 1 - 0 [0,1] + 1 + [0 1 2 3 4] @@ -157,6 +157,8 @@ rect5 rect6 rect7 + rect8 + rect9 diff --git a/cfg_collection/FourCfg.xml b/cfg_collection/FourCfg.xml deleted file mode 100644 index eb34f4b4..00000000 --- a/cfg_collection/FourCfg.xml +++ /dev/null @@ -1,130 +0,0 @@ - - - - - Fourier_Rect_MOD - - - FourierXRectXMOD - - - - - - - Plot - - - bool - ^(1|0)$ - Grid-Y - 0 - - - [0 0 0 0 0] - ^\[(\s*\d\s*)+\] - Style - 1 - - - (0,0) - \(([0-9]+),([0-9]+)\) - 1 - Determine position: (x,y) - - - Plot - - - 1 - (\d+) - - - (300,300) - \(([0-9]+),([0-9]+)\) - 1 - Determine size: (height,width) - - - amplitude, V - \w+,\s+\w+ - Label-Y - - - time, s - \w+,\s*\w+ - Label-X - - - [0 1 2 3 4] - ^\[(\s*\d\s*)+\] - Color - 1 - - - VisualPlugin - Used display name - - - 1000 - ^([1-9][0-9]{0,3}|10000)$ - Buffersize - 1 - - - bool - ^(1|0)$ - Rolling Plot - 0 - - - bool - ^(1|0)$ - Grid-X - 0 - - - - 0 - 0 - [0 0 0 0 0] - [0 1 2 3 4] - 1 - 1000 - 0 - - - - Add - - - Sum - - - - - - Add - - - Add - - - - - - FourierXRectXMOD - - - rect3 - rect4 - rect5 - rect6 - rect7 - rect8 - rect9 - - - - - diff --git a/docs/papi.data.rst b/docs/papi.data.rst index 577b05fc..958b1e67 100644 --- a/docs/papi.data.rst +++ b/docs/papi.data.rst @@ -52,6 +52,14 @@ papi.data.DPlugin module :undoc-members: :show-inheritance: +papi.data.DSignal module +------------------------ + +.. automodule:: papi.data.DSignal + :members: + :undoc-members: + :show-inheritance: + Module contents --------------- diff --git a/docs/papi.gui.rst b/docs/papi.gui.rst index 0c29f862..d8521eb0 100644 --- a/docs/papi.gui.rst +++ b/docs/papi.gui.rst @@ -6,7 +6,6 @@ Subpackages .. toctree:: - papi.gui.qt_dev papi.gui.qt_new Submodules @@ -28,6 +27,14 @@ papi.gui.gui_event_processing module :undoc-members: :show-inheritance: +papi.gui.plugin_api module +-------------------------- + +.. automodule:: papi.gui.plugin_api + :members: + :undoc-members: + :show-inheritance: + Module contents --------------- diff --git a/docs/papi.rst b/docs/papi.rst index f89220fb..9c8030b7 100644 --- a/docs/papi.rst +++ b/docs/papi.rst @@ -11,8 +11,10 @@ Subpackages papi.exceptions papi.gui papi.plugin + papi.pyqtgraph papi.tests papi.ui + papi.yapsy Submodules ---------- @@ -25,14 +27,6 @@ papi.ConsoleLog module :undoc-members: :show-inheritance: -papi.DebugOut module --------------------- - -.. automodule:: papi.DebugOut - :members: - :undoc-members: - :show-inheritance: - papi.PapiEvent module --------------------- @@ -65,22 +59,6 @@ papi.error_codes module :undoc-members: :show-inheritance: -papi.main module ----------------- - -.. automodule:: papi.main - :members: - :undoc-members: - :show-inheritance: - -papi.main_2 module ------------------- - -.. automodule:: papi.main_2 - :members: - :undoc-members: - :show-inheritance: - Module contents --------------- diff --git a/papi/PapiEvent.py b/papi/PapiEvent.py deleted file mode 100644 index 1294f081..00000000 --- a/papi/PapiEvent.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/python3 -#-*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors: -Stefan Ruppin -""" - -__author__ = 'control' - -class PapiEvent(object): - count = 0 - - def __init__(self,orID,destID,type_,op,optParameter): - """ - Function used to create a new Event ready to send. - - :param orID: plugin id of sender - :type orID: int - :param destID: plugin id of destination - :type destID: int - :param type_: event type, see list - :type type_: string - :param optParameter: optinalParameterObject for information - :type: optParameter: DOptionlaData - """ - - self.__originID__ = orID - self.__destID__ = destID - self.__eventtype__ = type_ - self.__operation__ = op - self.__optional_parameter__ = optParameter - PapiEvent.count += 1 - - def get_originID(self): - return self.__originID__ - - def get_destinatioID(self): - return self.__destID__ - - def get_eventtype(self): - return self.__eventtype__ - - def get_event_operation(self): - return self.__operation__ - - def get_optional_parameter(self): - return self.__optional_parameter__ - diff --git a/papi/core.py b/papi/core.py index 432246b0..464b298e 100644 --- a/papi/core.py +++ b/papi/core.py @@ -33,7 +33,6 @@ from threading import Timer from papi.yapsy.PluginManager import PluginManager -from papi.PapiEvent import PapiEvent from papi.data.DCore import DCore from papi.data.DPlugin import DPlugin, DBlock from papi.data.DSignal import DSignal @@ -54,6 +53,7 @@ import papi.error_codes as ERROR +from papi.event.event_base import PapiEventBase import papi.event as Event @@ -285,7 +285,8 @@ def update_meta_data_to_gui(self, pl_id, inform_subscriber=False): # get meta information of DPlugin o.plugin_object = dplugin.get_meta() # build event and send it to GUI with meta information - eventMeta = PapiEvent(pl_id, self.gui_id, 'instr_event', 'update_meta', o) + #eventMeta = PapiEvent(pl_id, self.gui_id, 'instr_event', 'update_meta', o) + eventMeta = Event.instruction.UpdateMeta(pl_id,self.gui_id,o) self.gui_event_queue.put(eventMeta) # check if plugin got some subscribers which run in own process @@ -346,7 +347,7 @@ def __process_event__(self, event): Initial stage of event processing, dividing to event type :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase """ t = event.get_eventtype() self.__process_event_by_type__[t](event) @@ -357,7 +358,7 @@ def __process_status_event__(self, event): First stage of event processing, deciding which status_event this is :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase """ op = event.get_event_operation() return self.__process_status_event_l__[op](event) @@ -367,7 +368,7 @@ def __process_data_event__(self, event): First stage of event processing, deciding which data_event this is :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase """ op = event.get_event_operation() return self.__process_data_event_l__[op](event) @@ -377,7 +378,7 @@ def __process_instr_event__(self, event): First stage of event processing, deciding which instr_event this is :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase """ op = event.get_event_operation() return self.__process_instr_event_l__[op](event) @@ -388,7 +389,7 @@ def __process_start_successfull__(self, event): Process start_successfull event :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase """ # get plugin from DCore with id of event origin and check if it exists dplug = self.core_data.get_dplugin_by_id(event.get_originID()) @@ -408,7 +409,7 @@ def __process_start_failed__(self, event): Process start failed event and do error handling :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase """ # get plugin from DCore with id of event origin and check if it exists dplug = self.core_data.get_dplugin_by_id(event.get_originID()) @@ -427,7 +428,7 @@ def __process_alive__(self, event): Processes alive response from processes/plugins and GUI, organising the counter :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase """ # get event origin oID = event.get_originID() @@ -450,7 +451,7 @@ def __process_join_request__(self, event): Process join requests of processes :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ # get event origin id and its corresponding plugin object @@ -471,7 +472,6 @@ def __process_join_request__(self, event): # GUI is still alive, so tell GUI, that this plugin was closed opt = DOptionalData() opt.plugin_id = dplugin.id - # event = PapiEvent(self.core_id,self.gui_id,'status_event','plugin_closed',opt) event = Event.status.PluginClosed(self.core_id, self.gui_id, opt) self.gui_event_queue.put(event) @@ -491,7 +491,7 @@ def __process_new_data__(self, event): Will do the routing: Subscriber/Subscription :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type tar_plug: DPlugin """ # just proceed with new_data events if GUI is still alive (indicates that program will close) @@ -560,7 +560,7 @@ def BACKUP__process_new_data__(self, event): Will do the routing: Subscriber/Subscription :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type tar_plug: DPlugin """ # just proceed with new_data events if GUI is still alive (indicates that program will close) @@ -632,7 +632,7 @@ def __process_edit_dplugin(self, event): Process edit_dplugin event from gui. :param event: event to process - :type event: PapiEvent + :type event: PapiEventBaseBase :type tar_plug: DPlugin """ eObject = event.editedObject @@ -670,7 +670,7 @@ def __process_create_plugin__(self, event): :param event: :param optData: optional Data Object of event - :type event: PapiEvent + :type event: PapiEventBase :type optData: DOptionalData :return: -1: Error """ @@ -773,7 +773,7 @@ def __process_stop_plugin__(self, event): Will send an event to destination plugin to close itself. Will lead to a join request of this plugin. :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ # get destination id @@ -881,7 +881,7 @@ def __process_close_programm__(self, event): sends events to all processes to close themselves :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase """ # GUI wants to close, so join process @@ -915,7 +915,7 @@ def __process_subscribe__(self, event): Will set a new route in DCore for this two plugins to route new data events. Update of meta will be send to GUI. :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin_sub: DPlugin :type dplugin_source: DPlugin """ @@ -964,7 +964,7 @@ def __process_unsubsribe__(self, event): Process unsubscribe_event. Will try to remove a subscription from DCore :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin_sub: DPlugin :type dplugin_source: DPlugin """ @@ -997,7 +997,7 @@ def __process_set_parameter__(self, event): Process set_parameter event. Core will just route this event from GUI to destination plugin and update DCore :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin_sub: DPlugin :type dplugin_source: DPlugin """ @@ -1032,7 +1032,7 @@ def __process_new_block__(self, event): Will try to add a new data block to a DPlugin object :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin_sub: DPlugin :type dplugin_source: DPlugin """ @@ -1059,7 +1059,7 @@ def __process_new_parameter__(self, event): Processes new parameter event. Adding a new parameter to DPluign in DCore and updating GUI information :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin_sub: DPlugin :type dplugin_source: DPlugin """ @@ -1087,7 +1087,7 @@ def __process_pause_plugin__(self, event): Processes pause_plugin event. Will add information that a plugin is paused and send event to plugin to pause it. :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ pl_id = event.get_destinatioID() @@ -1098,7 +1098,6 @@ def __process_pause_plugin__(self, event): # set pause info dplugin.state = PLUGIN_STATE_PAUSE # send event to plugin - # event = PapiEvent(self.core_id, pl_id, 'instr_event', 'pause_plugin', None) event = Event.instruction.PausePlugin(self.core_id, pl_id, None) dplugin.queue.put(event) @@ -1110,7 +1109,7 @@ def __process_resume_plugin__(self, event): Processes resume_plugin event. Will add information that a plugin is resumed and send event to plugin to resume it. :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ pl_id = event.get_destinatioID() @@ -1121,28 +1120,8 @@ def __process_resume_plugin__(self, event): # set resume info dplugin.state = PLUGIN_STATE_RESUMED # send event to plugin - # event = PapiEvent(self.core_id, pl_id, 'instr_event', 'resume_plugin', None) event = Event.instruction.ResumePlugin(self.core_id, pl_id, None) dplugin.queue.put(event) # update meta for Gui - self.update_meta_data_to_gui(pl_id) - - # def __process_plugin_paused__(self, event): - # """ - # Processes plugin_paused event from GUI. Will add information that a plugin was paused in gui and - # send update meta. - # :param event: event to process - # :type event: PapiEvent - # :type dplugin: DPlugin - # """ - # id = event.get_originID() - # - # dplugin = self.core_data.get_dplugin_by_id(id) - # if dplugin is not None: - # dplugin.state = PLUGIN_STATE_PAUSE - # - # self.update_meta_data_to_gui(id) - # - # def __process_plugin_resumed__(self, event): - # pass \ No newline at end of file + self.update_meta_data_to_gui(pl_id) \ No newline at end of file diff --git a/papi/event/instruction/UpdateMeta.py b/papi/event/instruction/UpdateMeta.py new file mode 100644 index 00000000..1bc385de --- /dev/null +++ b/papi/event/instruction/UpdateMeta.py @@ -0,0 +1,35 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class UpdateMeta(InstructionBase): + def __init__(self, oID, destID, opt): + super().__init__(oID, destID, 'update_meta', opt) diff --git a/papi/event/instruction/__init__.py b/papi/event/instruction/__init__.py index 9fe43b23..1ef00e1e 100644 --- a/papi/event/instruction/__init__.py +++ b/papi/event/instruction/__init__.py @@ -9,4 +9,5 @@ from .Subscribe import Subscribe from .Unsubscribe import Unsubscribe from .SetParameter import SetParameter -from .CloseProgram import CloseProgram \ No newline at end of file +from .CloseProgram import CloseProgram +from .UpdateMeta import UpdateMeta \ No newline at end of file diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 1e338814..a7bcc109 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -37,7 +37,7 @@ from papi.gui.plugin_api import Plugin_api import papi.error_codes as ERROR import papi.event as Event -from papi.PapiEvent import PapiEvent +from papi.event.event_base import PapiEventBase from papi.ConsoleLog import ConsoleLog from papi.yapsy.PluginManager import PluginManager from papi.data.DPlugin import DPlugin @@ -83,7 +83,7 @@ def gui_working(self, close_mock): Will process all events of the queue at the time of call. Procedure was built this way, so that the processing of an event is not covered by the try/except structure. - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ # event flag, true for first loop iteration to enter loop @@ -122,7 +122,7 @@ def process_new_data_event(self, event): with the new data. :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ # debug print @@ -162,7 +162,7 @@ def process_plugin_closed(self, event): Gui now knows, that a plugin was closed by core and needs to update its DGui data base :param event: - :type event: PapiEvent + :type event: PapiEventBase :return: """ opt = event.get_optional_parameter() @@ -233,7 +233,7 @@ def process_create_plugin(self, event): Gui now needs to add a new plugin to DGUI and decide whether it is a plugin running in the GUI process or not. :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ # get optional data: the plugin id, identifier and uname @@ -331,7 +331,7 @@ def process_close_program_event(self, event): Nothing important happens. :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ self.log.printText(1, 'event: close_progam was received but there is no action for it') @@ -342,11 +342,11 @@ def process_check_alive_status(self, event): Gui received check_alive request form core, so gui will respond to it :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ # send event from GUI to Core - event = PapiEvent(1, 0, 'status_event', 'alive', None) + event = Event.status.Alive(1,0,None) self.core_queue.put(event) def process_update_meta(self, event): @@ -354,7 +354,7 @@ def process_update_meta(self, event): Core sent new meta information of an existing plugin. This function will update DGui with these information :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ # get information of event @@ -414,7 +414,7 @@ def process_pause_plugin(self, event): Core sent event to pause a plugin in GUI, so call the pause function of this plugin :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ pl_id = event.get_destinatioID() @@ -428,7 +428,7 @@ def process_resume_plugin(self, event): Core sent event to resume a plugin in GUI, so call the resume function of this plugin :param event: event to process - :type event: PapiEvent + :type event: PapiEventBase :type dplugin: DPlugin """ pl_id = event.get_destinatioID() diff --git a/papi/tests/TestCore.py b/papi/tests/TestCore.py deleted file mode 100644 index 057c17f0..00000000 --- a/papi/tests/TestCore.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/python3 -#-*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors: -Stefan Ruppin -""" - - -import unittest -from papi.core import Core -from papi.PapiEvent import PapiEvent -from papi.data.DCore import DPlugin,DCore -from multiprocessing import Process, Array, Lock, Queue -import time -from papi.main import main - -class TestCore(unittest.TestCase): - - def setUp(self): - self.core = Core() - - - def test_process_event_alive(self): - self.core.__debugLevel__ = 0 - event = PapiEvent(1,2,'status_event','alive','') - self.core.__process_event__(event) - assert self.core.__debug_var__ == 'alive' - - def test_process_event_check_alive(self): - self.core.__debugLevel__ = 0 - event = PapiEvent(1,2,'status_event','check_alive_status','') - self.core.__process_event__(event) - assert self.core.__debug_var__ == 'check_alive_status' - - def test_process_event_start_successfull(self): - self.core.__debugLevel__ = 0 - print('Test start successfull -----------------------') - event = PapiEvent(1,2,'status_event','start_successfull','') - self.core.__process_event__(event) - assert self.core.__debug_var__ == 'start_successfull' - - def test_process_event_join_request(self): - print('Test join request -----------------------') - event = PapiEvent(1,0,'instr_event','create_plugin','TestPL1') - self.core.plugin_manager.setPluginPlaces(["plugin","../plugin"]) - self.core.plugin_manager.collectPlugins() - self.core.__process_event__(event) - - pl = self.core.core_data.dbg_get_first_dplugin() - - self.assert_(pl.process.is_alive()) - event = PapiEvent(id,0,'instr_event','stop','') - pl.queue.put(event) - time.sleep(1) - assert pl.process.is_alive() == False - - - def test_process_event_start_failed(self): - self.core.__debugLevel__ = 0 - event = PapiEvent(1,2,'status_event','start_failed','') - self.core.__process_event__(event) - assert self.core.__debug_var__ == 'start_failed' - - def test_process_event_new_data(self): - self.core.__debugLevel__ = 0 - event = PapiEvent(1,2,'data_event','new_data','') - self.core.__process_event__(event) - assert self.core.__debug_var__ == 'new_data' - - def test_process_event_get_output_size(self): - self.core.__debugLevel__ = 0 - event = PapiEvent(1,2,'data_event','get_output_size','') - self.core.__process_event__(event) - assert self.core.__debug_var__ == 'get_output_size' - - def test_process_event_response_output_size(self): - self.core.__debugLevel__ = 0 - event = PapiEvent(1,2,'data_event','response_output_size','') - self.core.__process_event__(event) - assert self.core.__debug_var__ == 'response_output_size' - - def test_process_event_create_plugin(self): - self.core.__debugLevel__ = 0 - return True - event = PapiEvent(1,2,'instr_event','create_plugin','TestPL1') - self.core.plugin_manager.setPluginPlaces(["plugin","../plugin"]) - self.core.plugin_manager.collectPlugins() - self.core.__process_event__(event) - assert self.core.__debug_var__ == 'create_plugin' - - def test_process_event_stop_plugin(self): - self.core.__debugLevel__ = 0 - event = PapiEvent(1,2,'instr_event','stop_plugin','') - self.core.__process_event__(event) - assert self.core.__debug_var__ == 'stop_plugin' - - - - -if __name__ == "__main__": - unittest.main(); - diff --git a/papi/tests/TestCoreMock.py b/papi/tests/TestCoreMock.py index 1f731a06..b270d8fd 100644 --- a/papi/tests/TestCoreMock.py +++ b/papi/tests/TestCoreMock.py @@ -1,20 +1,14 @@ __author__ = 'stefan' import unittest -from threading import Thread -import time - from papi.data.DGui import DGui from papi.core import Core from papi.gui.qt_new.main import startGUI_TESTMOCK from papi.gui.gui_api import Gui_api -from papi.gui.gui_event_processing import GuiEventProcessing -from papi.PapiEvent import PapiEvent from threading import Thread from multiprocessing import Queue import papi.constants as const -from papi.pyqtgraph import QtCore import time @@ -425,7 +419,7 @@ def setUp(self): def tearDown(self): # close Gui - self.gui_queue.put( PapiEvent(1,1,'instr_event','test_close', None) ) + # wait for close # time.sleep(1) From a12c81a5d55f54e6704cba0e9cf0922cb7fb00e0 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Thu, 15 Jan 2015 11:51:16 +0100 Subject: [PATCH 071/260] added documentation --- docs/conf.py | 4 ++-- docs/index.rst | 23 ----------------------- docs/papi.data.dcore.rst | 22 ---------------------- docs/papi.data.rst | 8 ++++++++ docs/papi.gui.rst | 9 ++++++++- docs/papi.rst | 26 ++------------------------ papi/tests/TestDCore.py | 3 ++- 7 files changed, 22 insertions(+), 73 deletions(-) delete mode 100644 docs/index.rst delete mode 100644 docs/papi.data.dcore.rst diff --git a/docs/conf.py b/docs/conf.py index 5dda444a..95f6eae5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -76,7 +76,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build', 'yapsy', 'pyqtgraph'] +exclude_patterns = ['_build', 'papi.yapsy', 'papi.pyqtgraph'] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -107,7 +107,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'sphinxdoc' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index 074aa323..00000000 --- a/docs/index.rst +++ /dev/null @@ -1,23 +0,0 @@ -.. papi documentation master file, created by - sphinx-quickstart on Fri Jun 20 15:28:01 2014. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to papi's documentation! -================================ - -Contents: - -.. toctree:: - :maxdepth: 4 - - papi - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/docs/papi.data.dcore.rst b/docs/papi.data.dcore.rst deleted file mode 100644 index 1c610d17..00000000 --- a/docs/papi.data.dcore.rst +++ /dev/null @@ -1,22 +0,0 @@ -papi.data.dcore package -======================= - -Submodules ----------- - -papi.data.dcore.DPlugin module ------------------------------- - -.. automodule:: papi.data.dcore.DPlugin - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: papi.data.dcore - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/papi.data.rst b/docs/papi.data.rst index 577b05fc..958b1e67 100644 --- a/docs/papi.data.rst +++ b/docs/papi.data.rst @@ -52,6 +52,14 @@ papi.data.DPlugin module :undoc-members: :show-inheritance: +papi.data.DSignal module +------------------------ + +.. automodule:: papi.data.DSignal + :members: + :undoc-members: + :show-inheritance: + Module contents --------------- diff --git a/docs/papi.gui.rst b/docs/papi.gui.rst index 0c29f862..d8521eb0 100644 --- a/docs/papi.gui.rst +++ b/docs/papi.gui.rst @@ -6,7 +6,6 @@ Subpackages .. toctree:: - papi.gui.qt_dev papi.gui.qt_new Submodules @@ -28,6 +27,14 @@ papi.gui.gui_event_processing module :undoc-members: :show-inheritance: +papi.gui.plugin_api module +-------------------------- + +.. automodule:: papi.gui.plugin_api + :members: + :undoc-members: + :show-inheritance: + Module contents --------------- diff --git a/docs/papi.rst b/docs/papi.rst index f89220fb..9c8030b7 100644 --- a/docs/papi.rst +++ b/docs/papi.rst @@ -11,8 +11,10 @@ Subpackages papi.exceptions papi.gui papi.plugin + papi.pyqtgraph papi.tests papi.ui + papi.yapsy Submodules ---------- @@ -25,14 +27,6 @@ papi.ConsoleLog module :undoc-members: :show-inheritance: -papi.DebugOut module --------------------- - -.. automodule:: papi.DebugOut - :members: - :undoc-members: - :show-inheritance: - papi.PapiEvent module --------------------- @@ -65,22 +59,6 @@ papi.error_codes module :undoc-members: :show-inheritance: -papi.main module ----------------- - -.. automodule:: papi.main - :members: - :undoc-members: - :show-inheritance: - -papi.main_2 module ------------------- - -.. automodule:: papi.main_2 - :members: - :undoc-members: - :show-inheritance: - Module contents --------------- diff --git a/papi/tests/TestDCore.py b/papi/tests/TestDCore.py index f6f70ac2..a674ee17 100644 --- a/papi/tests/TestDCore.py +++ b/papi/tests/TestDCore.py @@ -31,7 +31,8 @@ import unittest from papi.data.DCore import DCore -from papi.data.DCore import DPlugin, DBlock +from papi.data.DCore import DPlugin +from papi.data.DPlugin import DBlock class TestDCore(unittest.TestCase): From d8aeab23c9eda8db05accf0c12b8f9ba7e2df71d Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 19 Jan 2015 10:19:36 +0100 Subject: [PATCH 072/260] removed rst from git --- docs/conf.py | 2 +- docs/modules.rst | 7 ----- docs/papi.data.rst | 70 ------------------------------------------- docs/papi.gui.rst | 45 ---------------------------- docs/papi.plugin.rst | 17 ----------- docs/papi.rst | 69 ------------------------------------------ docs/papi.tests.rst | 70 ------------------------------------------- docs/papi.ui.gui.rst | 18 ----------- docs/papi.ui.rst | 17 ----------- papi/tests/TestGUI.py | 69 ------------------------------------------ 10 files changed, 1 insertion(+), 383 deletions(-) delete mode 100644 docs/modules.rst delete mode 100644 docs/papi.data.rst delete mode 100644 docs/papi.gui.rst delete mode 100644 docs/papi.plugin.rst delete mode 100644 docs/papi.rst delete mode 100644 docs/papi.tests.rst delete mode 100644 docs/papi.ui.gui.rst delete mode 100644 docs/papi.ui.rst delete mode 100644 papi/tests/TestGUI.py diff --git a/docs/conf.py b/docs/conf.py index 95f6eae5..b18768ab 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -107,7 +107,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinxdoc' +html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/modules.rst b/docs/modules.rst deleted file mode 100644 index 0761b96f..00000000 --- a/docs/modules.rst +++ /dev/null @@ -1,7 +0,0 @@ -papi -==== - -.. toctree:: - :maxdepth: 4 - - papi diff --git a/docs/papi.data.rst b/docs/papi.data.rst deleted file mode 100644 index 958b1e67..00000000 --- a/docs/papi.data.rst +++ /dev/null @@ -1,70 +0,0 @@ -papi.data package -================= - -Submodules ----------- - -papi.data.DCore module ----------------------- - -.. automodule:: papi.data.DCore - :members: - :undoc-members: - :show-inheritance: - -papi.data.DGui module ---------------------- - -.. automodule:: papi.data.DGui - :members: - :undoc-members: - :show-inheritance: - -papi.data.DObject module ------------------------- - -.. automodule:: papi.data.DObject - :members: - :undoc-members: - :show-inheritance: - -papi.data.DOptionalData module ------------------------------- - -.. automodule:: papi.data.DOptionalData - :members: - :undoc-members: - :show-inheritance: - -papi.data.DParameter module ---------------------------- - -.. automodule:: papi.data.DParameter - :members: - :undoc-members: - :show-inheritance: - -papi.data.DPlugin module ------------------------- - -.. automodule:: papi.data.DPlugin - :members: - :undoc-members: - :show-inheritance: - -papi.data.DSignal module ------------------------- - -.. automodule:: papi.data.DSignal - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: papi.data - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/papi.gui.rst b/docs/papi.gui.rst deleted file mode 100644 index d8521eb0..00000000 --- a/docs/papi.gui.rst +++ /dev/null @@ -1,45 +0,0 @@ -papi.gui package -================ - -Subpackages ------------ - -.. toctree:: - - papi.gui.qt_new - -Submodules ----------- - -papi.gui.gui_api module ------------------------ - -.. automodule:: papi.gui.gui_api - :members: - :undoc-members: - :show-inheritance: - -papi.gui.gui_event_processing module ------------------------------------- - -.. automodule:: papi.gui.gui_event_processing - :members: - :undoc-members: - :show-inheritance: - -papi.gui.plugin_api module --------------------------- - -.. automodule:: papi.gui.plugin_api - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: papi.gui - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/papi.plugin.rst b/docs/papi.plugin.rst deleted file mode 100644 index d627f70e..00000000 --- a/docs/papi.plugin.rst +++ /dev/null @@ -1,17 +0,0 @@ -papi.plugin package -=================== - -Subpackages ------------ - -.. toctree:: - - papi.plugin.base_classes - -Module contents ---------------- - -.. automodule:: papi.plugin - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/papi.rst b/docs/papi.rst deleted file mode 100644 index 9c8030b7..00000000 --- a/docs/papi.rst +++ /dev/null @@ -1,69 +0,0 @@ -papi package -============ - -Subpackages ------------ - -.. toctree:: - - papi.data - papi.event - papi.exceptions - papi.gui - papi.plugin - papi.pyqtgraph - papi.tests - papi.ui - papi.yapsy - -Submodules ----------- - -papi.ConsoleLog module ----------------------- - -.. automodule:: papi.ConsoleLog - :members: - :undoc-members: - :show-inheritance: - -papi.PapiEvent module ---------------------- - -.. automodule:: papi.PapiEvent - :members: - :undoc-members: - :show-inheritance: - -papi.constants module ---------------------- - -.. automodule:: papi.constants - :members: - :undoc-members: - :show-inheritance: - -papi.core module ----------------- - -.. automodule:: papi.core - :members: - :undoc-members: - :show-inheritance: - -papi.error_codes module ------------------------ - -.. automodule:: papi.error_codes - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: papi - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/papi.tests.rst b/docs/papi.tests.rst deleted file mode 100644 index 550bf469..00000000 --- a/docs/papi.tests.rst +++ /dev/null @@ -1,70 +0,0 @@ -papi.tests package -================== - -Submodules ----------- - -papi.tests.TestCore module --------------------------- - -.. automodule:: papi.tests.TestCore - :members: - :undoc-members: - :show-inheritance: - -papi.tests.TestCoreMock module ------------------------------- - -.. automodule:: papi.tests.TestCoreMock - :members: - :undoc-members: - :show-inheritance: - -papi.tests.TestDBlock module ----------------------------- - -.. automodule:: papi.tests.TestDBlock - :members: - :undoc-members: - :show-inheritance: - -papi.tests.TestDCore module ---------------------------- - -.. automodule:: papi.tests.TestDCore - :members: - :undoc-members: - :show-inheritance: - -papi.tests.TestDPlugin module ------------------------------ - -.. automodule:: papi.tests.TestDPlugin - :members: - :undoc-members: - :show-inheritance: - -papi.tests.TestDSubscription module ------------------------------------ - -.. automodule:: papi.tests.TestDSubscription - :members: - :undoc-members: - :show-inheritance: - -papi.tests.TestGUI module -------------------------- - -.. automodule:: papi.tests.TestGUI - :members: - :undoc-members: - :show-inheritance: - - -Module contents ---------------- - -.. automodule:: papi.tests - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/papi.ui.gui.rst b/docs/papi.ui.gui.rst deleted file mode 100644 index 7b9086e4..00000000 --- a/docs/papi.ui.gui.rst +++ /dev/null @@ -1,18 +0,0 @@ -papi.ui.gui package -=================== - -Subpackages ------------ - -.. toctree:: - - papi.ui.gui.qt_dev - papi.ui.gui.qt_new - -Module contents ---------------- - -.. automodule:: papi.ui.gui - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/papi.ui.rst b/docs/papi.ui.rst deleted file mode 100644 index 9f54fbbb..00000000 --- a/docs/papi.ui.rst +++ /dev/null @@ -1,17 +0,0 @@ -papi.ui package -=============== - -Subpackages ------------ - -.. toctree:: - - papi.ui.gui - -Module contents ---------------- - -.. automodule:: papi.ui - :members: - :undoc-members: - :show-inheritance: diff --git a/papi/tests/TestGUI.py b/papi/tests/TestGUI.py deleted file mode 100644 index 96f1e6c7..00000000 --- a/papi/tests/TestGUI.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors -Sven Knuth -""" - -__author__ = 'knuths' - -import sys - -from PySide.QtGui import QApplication - -from papi.gui.qt_dev.gui_main import GUI -from papi.data.DGui import DGui -from papi.data.DCore import DBlock - - -def get_gui_data(): - """ - - :return: - :rtype DGui: - """ - dgui = DGui() - #Create dplugins - d_pl_1 = dgui.add_plugin(None, 1, None, None, None, dgui.create_id()) - d_pl_2 = dgui.add_plugin(None, 1, None, None, None, dgui.create_id()) - d_pl_3 = dgui.add_plugin(None, 1, None, None, None, dgui.create_id()) - - #Create dblocks - d_bl_1 = DBlock(None, 0, 0, 'Block_1') - d_bl_2 = DBlock(None, 0, 0, 'Block_2') - d_bl_3 = DBlock(None, 0, 0, 'Block_3') - - #assign dblocks to DPlugin d_pl_1 - d_pl_1.add_dblock(d_bl_1) - d_pl_1.add_dblock(d_bl_2) - d_pl_1.add_dblock(d_bl_3) - return dgui - -if __name__ == '__main__': - app = QApplication(sys.argv) -# mw = QtGui.QMainWindow - frame = GUI(None,None,None) - frame.set_dgui_data(get_gui_data()) - frame.show() - app.exec_() \ No newline at end of file From 2b627b6059d4ac62441deac049c73ed320510f8e Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 19 Jan 2015 10:34:02 +0100 Subject: [PATCH 073/260] added installation tip --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7953f3f2..e2868d95 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ Basic installation on Ubuntu 14.04 64Bit, using python 3.4 `python3.4 main.py` +Tip: +If python3.4 interpreter cannot find or locate the installed packages (pyside or numpy), please make sure that the right +python interpreter is called (in case there are multiple installed). + Contribution ------ From b567fa972ac341bf224dc11d2865f5aa5042ffaf Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 19 Jan 2015 10:57:31 +0100 Subject: [PATCH 074/260] added some comments --- papi/core.py | 3 --- papi/gui/gui_event_processing.py | 19 +++++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/papi/core.py b/papi/core.py index 464b298e..c37a0574 100644 --- a/papi/core.py +++ b/papi/core.py @@ -39,9 +39,6 @@ from papi.ConsoleLog import ConsoleLog from papi.data.DOptionalData import DOptionalData - - -# import contants from papi.constants import CORE_PROCESS_CONSOLE_IDENTIFIER, CORE_CONSOLE_LOG_LEVEL, CORE_PAPI_CONSOLE_START_MESSAGE, \ CORE_CORE_CONSOLE_START_MESSAGE, CORE_ALIVE_CHECK_ENABLED, \ CORE_STOP_CONSOLE_MESSAGE, CORE_ALIVE_CHECK_INTERVAL, CORE_ALIVE_MAX_COUNT diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index a7bcc109..4538246a 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -48,12 +48,31 @@ class GuiEventProcessing(QtCore.QObject): + """ + This class will do all the event handling for a GUI process. It should be created and initialized with database and + queues. + To get all the functionality, one should link the callback functions (slots) for the needed signals. + + """ added_dplugin = QtCore.Signal(DPlugin) removed_dplugin = QtCore.Signal(DPlugin) dgui_changed = QtCore.Signal() plugin_died = QtCore.Signal(DPlugin, Exception, str) def __init__(self, gui_data, core_queue, gui_id, gui_queue): + """ + Init for eventProcessing + + :param gui_data: Data object for all the plugin data + :type gui_data: DGui + :param core_queue: multiprocessing queue for process interaction with core process + :type core_queue: multiprocessing.Queue + :param gui_id: id of gui process + :type gui_id: int + :param gui_queue: multiprocessing queue for process interaction with gui process + :type gui_queue: multiprocessing.Queue + :return: + """ super(GuiEventProcessing, self).__init__() self.gui_data = gui_data self.core_queue = core_queue From 479fb469ca79cd42c6bf92f773d0c3625cd9d325 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 19 Jan 2015 11:57:24 +0100 Subject: [PATCH 075/260] removed ui files for dev gui, changes in Makefile due to new docs creation --- Makefile | 7 +- docs/conf.py | 2 +- docs/index.rst | 21 ++ papi/ui/gui/qt_dev/__init__.py | 1 - papi/ui/gui/qt_dev/add_plugin.py | 67 ------ papi/ui/gui/qt_dev/add_subscriber.py | 54 ----- papi/ui/gui/qt_dev/main.py | 157 -------------- papi/ui/gui/qt_dev/manager.py | 128 ------------ ui/gui/qt_dev/add_plugin.ui | 149 -------------- ui/gui/qt_dev/add_subscriber.ui | 125 ------------ ui/gui/qt_dev/main.ui | 293 --------------------------- ui/gui/qt_dev/manager.ui | 233 --------------------- 12 files changed, 27 insertions(+), 1210 deletions(-) create mode 100644 docs/index.rst delete mode 100644 papi/ui/gui/qt_dev/__init__.py delete mode 100644 papi/ui/gui/qt_dev/add_plugin.py delete mode 100644 papi/ui/gui/qt_dev/add_subscriber.py delete mode 100644 papi/ui/gui/qt_dev/main.py delete mode 100644 papi/ui/gui/qt_dev/manager.py delete mode 100644 ui/gui/qt_dev/add_plugin.ui delete mode 100644 ui/gui/qt_dev/add_subscriber.ui delete mode 100644 ui/gui/qt_dev/main.ui delete mode 100644 ui/gui/qt_dev/manager.ui diff --git a/Makefile b/Makefile index a1c455c1..d3cb1fcf 100644 --- a/Makefile +++ b/Makefile @@ -36,5 +36,8 @@ $(UI_FILES): else echo "__author__ = '$(AUTHOR)'" > $(DES_DIR)$(dir $@)__init__.py ; \ fi -rst: - sphinx-apidoc -f -o docs papi +create_rst: + sphinx-apidoc -f -o docs papi ./papi/pyqtgraph/ ./papi/yapsy/ + +docs: create_rst + make -C docs html diff --git a/docs/conf.py b/docs/conf.py index b18768ab..6b7d4f9a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -76,7 +76,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build', 'papi.yapsy', 'papi.pyqtgraph'] +exclude_patterns = ['_build', 'papi.yapsy*', 'papi.pyqtgraph*', 'papi.ui.gui.qt_dev*'] # The reST default role (used for this markup: `text`) to use for all # documents. diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..3b14a6b6 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,21 @@ +.. PaPI documentation master file, created by + sphinx-quickstart on Mon Jan 19 11:26:45 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to PaPI's documentation! +================================ + +Contents: + +.. toctree:: + :maxdepth: 2 + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`search` + diff --git a/papi/ui/gui/qt_dev/__init__.py b/papi/ui/gui/qt_dev/__init__.py deleted file mode 100644 index aa673a4c..00000000 --- a/papi/ui/gui/qt_dev/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__author__ = 'control' diff --git a/papi/ui/gui/qt_dev/add_plugin.py b/papi/ui/gui/qt_dev/add_plugin.py deleted file mode 100644 index e4e35d1f..00000000 --- a/papi/ui/gui/qt_dev/add_plugin.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'ui/gui/qt_dev/add_plugin.ui' -# -# Created: Mon Jan 12 16:53:32 2015 -# by: pyside-uic 0.2.15 running on PySide 1.2.1 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_AddPlugin(object): - def setupUi(self, AddPlugin): - AddPlugin.setObjectName("AddPlugin") - AddPlugin.resize(579, 558) - self.buttonBox = QtGui.QDialogButtonBox(AddPlugin) - self.buttonBox.setGeometry(QtCore.QRect(230, 510, 341, 32)) - self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Apply|QtGui.QDialogButtonBox.Ok) - self.buttonBox.setCenterButtons(True) - self.buttonBox.setObjectName("buttonBox") - self.treePlugin = QtGui.QTreeWidget(AddPlugin) - self.treePlugin.setGeometry(QtCore.QRect(10, 10, 561, 171)) - self.treePlugin.setObjectName("treePlugin") - self.formLayoutWidget = QtGui.QWidget(AddPlugin) - self.formLayoutWidget.setGeometry(QtCore.QRect(10, 190, 561, 62)) - self.formLayoutWidget.setObjectName("formLayoutWidget") - self.formLayout = QtGui.QFormLayout(self.formLayoutWidget) - self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) - self.formLayout.setContentsMargins(0, 0, 0, 0) - self.formLayout.setObjectName("formLayout") - self.label = QtGui.QLabel(self.formLayoutWidget) - self.label.setObjectName("label") - self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.label) - self.le_uname = QtGui.QLineEdit(self.formLayoutWidget) - self.le_uname.setObjectName("le_uname") - self.formLayout.setWidget(0, QtGui.QFormLayout.FieldRole, self.le_uname) - self.label_2 = QtGui.QLabel(self.formLayoutWidget) - self.label_2.setObjectName("label_2") - self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.label_2) - self.le_path = QtGui.QLineEdit(self.formLayoutWidget) - self.le_path.setReadOnly(True) - self.le_path.setObjectName("le_path") - self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.le_path) - self.formLayoutWidget_2 = QtGui.QWidget(AddPlugin) - self.formLayoutWidget_2.setGeometry(QtCore.QRect(10, 270, 561, 241)) - self.formLayoutWidget_2.setObjectName("formLayoutWidget_2") - self.customFormLayout = QtGui.QFormLayout(self.formLayoutWidget_2) - self.customFormLayout.setContentsMargins(0, 0, 0, 0) - self.customFormLayout.setObjectName("customFormLayout") - self.line = QtGui.QFrame(AddPlugin) - self.line.setGeometry(QtCore.QRect(7, 250, 561, 20)) - self.line.setFrameShape(QtGui.QFrame.HLine) - self.line.setFrameShadow(QtGui.QFrame.Sunken) - self.line.setObjectName("line") - - self.retranslateUi(AddPlugin) - QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), AddPlugin.accept) - QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), AddPlugin.reject) - QtCore.QMetaObject.connectSlotsByName(AddPlugin) - - def retranslateUi(self, AddPlugin): - AddPlugin.setWindowTitle(QtGui.QApplication.translate("AddPlugin", "Dialog", None, QtGui.QApplication.UnicodeUTF8)) - self.treePlugin.headerItem().setText(0, QtGui.QApplication.translate("AddPlugin", "Plugin", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("AddPlugin", "UName", None, QtGui.QApplication.UnicodeUTF8)) - self.label_2.setText(QtGui.QApplication.translate("AddPlugin", "Path", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/papi/ui/gui/qt_dev/add_subscriber.py b/papi/ui/gui/qt_dev/add_subscriber.py deleted file mode 100644 index 27ea8fa0..00000000 --- a/papi/ui/gui/qt_dev/add_subscriber.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'ui/gui/qt_dev/add_subscriber.ui' -# -# Created: Mon Jan 12 16:53:31 2015 -# by: pyside-uic 0.2.15 running on PySide 1.2.1 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_AddSubscriber(object): - def setupUi(self, AddSubscriber): - AddSubscriber.setObjectName("AddSubscriber") - AddSubscriber.resize(531, 197) - self.buttonBox = QtGui.QDialogButtonBox(AddSubscriber) - self.buttonBox.setGeometry(QtCore.QRect(10, 160, 511, 32)) - self.buttonBox.setAutoFillBackground(False) - self.buttonBox.setOrientation(QtCore.Qt.Horizontal) - self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Apply|QtGui.QDialogButtonBox.Ok) - self.buttonBox.setCenterButtons(True) - self.buttonBox.setObjectName("buttonBox") - self.horizontalLayoutWidget = QtGui.QWidget(AddSubscriber) - self.horizontalLayoutWidget.setGeometry(QtCore.QRect(10, 10, 511, 141)) - self.horizontalLayoutWidget.setObjectName("horizontalLayoutWidget") - self.horizontalLayout = QtGui.QHBoxLayout(self.horizontalLayoutWidget) - self.horizontalLayout.setContentsMargins(0, 0, 0, 0) - self.horizontalLayout.setObjectName("horizontalLayout") - self.treeSubscriber = QtGui.QTreeWidget(self.horizontalLayoutWidget) - self.treeSubscriber.setObjectName("treeSubscriber") - self.horizontalLayout.addWidget(self.treeSubscriber) - self.treeTarget = QtGui.QTreeWidget(self.horizontalLayoutWidget) - self.treeTarget.setObjectName("treeTarget") - self.horizontalLayout.addWidget(self.treeTarget) - self.treeBlock = QtGui.QTreeWidget(self.horizontalLayoutWidget) - self.treeBlock.setObjectName("treeBlock") - self.horizontalLayout.addWidget(self.treeBlock) - self.treeSignal = QtGui.QTreeWidget(self.horizontalLayoutWidget) - self.treeSignal.setSelectionMode(QtGui.QAbstractItemView.MultiSelection) - self.treeSignal.setObjectName("treeSignal") - self.horizontalLayout.addWidget(self.treeSignal) - - self.retranslateUi(AddSubscriber) - QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), AddSubscriber.accept) - QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), AddSubscriber.reject) - QtCore.QMetaObject.connectSlotsByName(AddSubscriber) - - def retranslateUi(self, AddSubscriber): - AddSubscriber.setWindowTitle(QtGui.QApplication.translate("AddSubscriber", "Dialog", None, QtGui.QApplication.UnicodeUTF8)) - self.treeSubscriber.headerItem().setText(0, QtGui.QApplication.translate("AddSubscriber", "Subscriber", None, QtGui.QApplication.UnicodeUTF8)) - self.treeTarget.headerItem().setText(0, QtGui.QApplication.translate("AddSubscriber", "Target", None, QtGui.QApplication.UnicodeUTF8)) - self.treeBlock.headerItem().setText(0, QtGui.QApplication.translate("AddSubscriber", "Block", None, QtGui.QApplication.UnicodeUTF8)) - self.treeSignal.headerItem().setText(0, QtGui.QApplication.translate("AddSubscriber", "Signal", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/papi/ui/gui/qt_dev/main.py b/papi/ui/gui/qt_dev/main.py deleted file mode 100644 index d28a302a..00000000 --- a/papi/ui/gui/qt_dev/main.py +++ /dev/null @@ -1,157 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'ui/gui/qt_dev/main.ui' -# -# Created: Mon Jan 12 16:53:31 2015 -# by: pyside-uic 0.2.15 running on PySide 1.2.1 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_MainGUI(object): - def setupUi(self, MainGUI): - MainGUI.setObjectName("MainGUI") - MainGUI.resize(979, 918) - self.centralwidget = QtGui.QWidget(MainGUI) - self.centralwidget.setObjectName("centralwidget") - self.verticalLayout_2 = QtGui.QVBoxLayout(self.centralwidget) - self.verticalLayout_2.setObjectName("verticalLayout_2") - self.horizontalLayout = QtGui.QHBoxLayout() - self.horizontalLayout.setObjectName("horizontalLayout") - self.buttonExit = QtGui.QPushButton(self.centralwidget) - self.buttonExit.setObjectName("buttonExit") - self.horizontalLayout.addWidget(self.buttonExit) - self.buttonCreatePlugin = QtGui.QPushButton(self.centralwidget) - self.buttonCreatePlugin.setObjectName("buttonCreatePlugin") - self.horizontalLayout.addWidget(self.buttonCreatePlugin) - self.buttonShowOverview = QtGui.QPushButton(self.centralwidget) - self.buttonShowOverview.setObjectName("buttonShowOverview") - self.horizontalLayout.addWidget(self.buttonShowOverview) - self.buttonCreateSubscription = QtGui.QPushButton(self.centralwidget) - self.buttonCreateSubscription.setObjectName("buttonCreateSubscription") - self.horizontalLayout.addWidget(self.buttonCreateSubscription) - self.buttonCreatePCPSubscription = QtGui.QPushButton(self.centralwidget) - self.buttonCreatePCPSubscription.setObjectName("buttonCreatePCPSubscription") - self.horizontalLayout.addWidget(self.buttonCreatePCPSubscription) - self.buttonShowLicence = QtGui.QPushButton(self.centralwidget) - self.buttonShowLicence.setObjectName("buttonShowLicence") - self.horizontalLayout.addWidget(self.buttonShowLicence) - self.verticalLayout_2.addLayout(self.horizontalLayout) - self.stefans_button = QtGui.QPushButton(self.centralwidget) - self.stefans_button.setObjectName("stefans_button") - self.verticalLayout_2.addWidget(self.stefans_button) - self.stefans_button_2 = QtGui.QPushButton(self.centralwidget) - self.stefans_button_2.setObjectName("stefans_button_2") - self.verticalLayout_2.addWidget(self.stefans_button_2) - self.stefans_text_field = QtGui.QLineEdit(self.centralwidget) - self.stefans_text_field.setObjectName("stefans_text_field") - self.verticalLayout_2.addWidget(self.stefans_text_field) - self.scopeArea = QtGui.QMdiArea(self.centralwidget) - self.scopeArea.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) - self.scopeArea.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) - self.scopeArea.setObjectName("scopeArea") - self.verticalLayout_2.addWidget(self.scopeArea) - MainGUI.setCentralWidget(self.centralwidget) - self.statusbar = QtGui.QStatusBar(MainGUI) - self.statusbar.setObjectName("statusbar") - MainGUI.setStatusBar(self.statusbar) - self.menubar = QtGui.QMenuBar(MainGUI) - self.menubar.setGeometry(QtCore.QRect(0, 0, 979, 25)) - self.menubar.setObjectName("menubar") - self.menuMenu = QtGui.QMenu(self.menubar) - self.menuMenu.setObjectName("menuMenu") - self.menuAvailablePlugins = QtGui.QMenu(self.menubar) - self.menuAvailablePlugins.setObjectName("menuAvailablePlugins") - MainGUI.setMenuBar(self.menubar) - self.dockWidget_3 = QtGui.QDockWidget(MainGUI) - self.dockWidget_3.setEnabled(True) - self.dockWidget_3.setMinimumSize(QtCore.QSize(100, 41)) - self.dockWidget_3.setFloating(False) - self.dockWidget_3.setObjectName("dockWidget_3") - self.dockWidgetContents_3 = QtGui.QWidget() - self.dockWidgetContents_3.setObjectName("dockWidgetContents_3") - self.toolBox = QtGui.QToolBox(self.dockWidgetContents_3) - self.toolBox.setGeometry(QtCore.QRect(12, 4, 161, 831)) - self.toolBox.setMinimumSize(QtCore.QSize(100, 0)) - self.toolBox.setObjectName("toolBox") - self.vip = QtGui.QWidget() - self.vip.setGeometry(QtCore.QRect(0, 0, 161, 707)) - self.vip.setObjectName("vip") - self.treeWidget = QtGui.QTreeWidget(self.vip) - self.treeWidget.setGeometry(QtCore.QRect(0, 0, 161, 701)) - self.treeWidget.setObjectName("treeWidget") - self.toolBox.addItem(self.vip, "") - self.pcp = QtGui.QWidget() - self.pcp.setGeometry(QtCore.QRect(0, 0, 161, 707)) - self.pcp.setObjectName("pcp") - self.treeWidget_2 = QtGui.QTreeWidget(self.pcp) - self.treeWidget_2.setGeometry(QtCore.QRect(0, 0, 161, 701)) - self.treeWidget_2.setObjectName("treeWidget_2") - self.treeWidget_2.headerItem().setText(0, "1") - self.toolBox.addItem(self.pcp, "") - self.dpp = QtGui.QWidget() - self.dpp.setObjectName("dpp") - self.treeWidget_3 = QtGui.QTreeWidget(self.dpp) - self.treeWidget_3.setGeometry(QtCore.QRect(0, 10, 161, 691)) - self.treeWidget_3.setObjectName("treeWidget_3") - self.treeWidget_3.headerItem().setText(0, "1") - self.toolBox.addItem(self.dpp, "") - self.iop = QtGui.QWidget() - self.iop.setObjectName("iop") - self.treeWidget_4 = QtGui.QTreeWidget(self.iop) - self.treeWidget_4.setGeometry(QtCore.QRect(0, 0, 161, 701)) - self.treeWidget_4.setObjectName("treeWidget_4") - self.treeWidget_4.headerItem().setText(0, "1") - self.toolBox.addItem(self.iop, "") - self.dockWidget_3.setWidget(self.dockWidgetContents_3) - MainGUI.addDockWidget(QtCore.Qt.DockWidgetArea(1), self.dockWidget_3) - self.actionP_Available = QtGui.QAction(MainGUI) - self.actionP_Available.setObjectName("actionP_Available") - self.actionAP_IO = QtGui.QAction(MainGUI) - self.actionAP_IO.setObjectName("actionAP_IO") - self.actionRP_Visual = QtGui.QAction(MainGUI) - self.actionRP_Visual.setObjectName("actionRP_Visual") - self.actionRP_IO = QtGui.QAction(MainGUI) - self.actionRP_IO.setObjectName("actionRP_IO") - self.actionM_License = QtGui.QAction(MainGUI) - self.actionM_License.setObjectName("actionM_License") - self.actionM_Quit = QtGui.QAction(MainGUI) - self.actionM_Quit.setObjectName("actionM_Quit") - self.actionAP_Parameter = QtGui.QAction(MainGUI) - self.actionAP_Parameter.setObjectName("actionAP_Parameter") - self.actionP_Overview = QtGui.QAction(MainGUI) - self.actionP_Overview.setObjectName("actionP_Overview") - self.menubar.addAction(self.menuMenu.menuAction()) - self.menubar.addAction(self.menuAvailablePlugins.menuAction()) - - self.retranslateUi(MainGUI) - self.toolBox.setCurrentIndex(2) - QtCore.QMetaObject.connectSlotsByName(MainGUI) - - def retranslateUi(self, MainGUI): - MainGUI.setWindowTitle(QtGui.QApplication.translate("MainGUI", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) - self.buttonExit.setText(QtGui.QApplication.translate("MainGUI", "Exit", None, QtGui.QApplication.UnicodeUTF8)) - self.buttonCreatePlugin.setText(QtGui.QApplication.translate("MainGUI", "CreatePlugin", None, QtGui.QApplication.UnicodeUTF8)) - self.buttonShowOverview.setText(QtGui.QApplication.translate("MainGUI", "ShowOverview", None, QtGui.QApplication.UnicodeUTF8)) - self.buttonCreateSubscription.setText(QtGui.QApplication.translate("MainGUI", "CreateSubscription", None, QtGui.QApplication.UnicodeUTF8)) - self.buttonCreatePCPSubscription.setText(QtGui.QApplication.translate("MainGUI", "CreatePCPSubscription", None, QtGui.QApplication.UnicodeUTF8)) - self.buttonShowLicence.setText(QtGui.QApplication.translate("MainGUI", "ShowLicence", None, QtGui.QApplication.UnicodeUTF8)) - self.stefans_button.setText(QtGui.QApplication.translate("MainGUI", "Save", None, QtGui.QApplication.UnicodeUTF8)) - self.stefans_button_2.setText(QtGui.QApplication.translate("MainGUI", "Load", None, QtGui.QApplication.UnicodeUTF8)) - self.menuMenu.setTitle(QtGui.QApplication.translate("MainGUI", "Menu", None, QtGui.QApplication.UnicodeUTF8)) - self.menuAvailablePlugins.setTitle(QtGui.QApplication.translate("MainGUI", "Plugins", None, QtGui.QApplication.UnicodeUTF8)) - self.treeWidget.headerItem().setText(0, QtGui.QApplication.translate("MainGUI", "Plugin", None, QtGui.QApplication.UnicodeUTF8)) - self.toolBox.setItemText(self.toolBox.indexOf(self.vip), QtGui.QApplication.translate("MainGUI", "Visual Plugins", None, QtGui.QApplication.UnicodeUTF8)) - self.toolBox.setItemText(self.toolBox.indexOf(self.pcp), QtGui.QApplication.translate("MainGUI", "Process Con. Plugins", None, QtGui.QApplication.UnicodeUTF8)) - self.toolBox.setItemText(self.toolBox.indexOf(self.dpp), QtGui.QApplication.translate("MainGUI", "Data Pro. Plugin", None, QtGui.QApplication.UnicodeUTF8)) - self.toolBox.setItemText(self.toolBox.indexOf(self.iop), QtGui.QApplication.translate("MainGUI", "IO Plugin", None, QtGui.QApplication.UnicodeUTF8)) - self.actionP_Available.setText(QtGui.QApplication.translate("MainGUI", "Available", None, QtGui.QApplication.UnicodeUTF8)) - self.actionAP_IO.setText(QtGui.QApplication.translate("MainGUI", "IO", None, QtGui.QApplication.UnicodeUTF8)) - self.actionRP_Visual.setText(QtGui.QApplication.translate("MainGUI", "Visual", None, QtGui.QApplication.UnicodeUTF8)) - self.actionRP_IO.setText(QtGui.QApplication.translate("MainGUI", "IO", None, QtGui.QApplication.UnicodeUTF8)) - self.actionM_License.setText(QtGui.QApplication.translate("MainGUI", "License", None, QtGui.QApplication.UnicodeUTF8)) - self.actionM_Quit.setText(QtGui.QApplication.translate("MainGUI", "Quit", None, QtGui.QApplication.UnicodeUTF8)) - self.actionAP_Parameter.setText(QtGui.QApplication.translate("MainGUI", "Parameter", None, QtGui.QApplication.UnicodeUTF8)) - self.actionP_Overview.setText(QtGui.QApplication.translate("MainGUI", "Overview", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/papi/ui/gui/qt_dev/manager.py b/papi/ui/gui/qt_dev/manager.py deleted file mode 100644 index 938491bd..00000000 --- a/papi/ui/gui/qt_dev/manager.py +++ /dev/null @@ -1,128 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'ui/gui/qt_dev/manager.ui' -# -# Created: Mon Jan 12 16:53:32 2015 -# by: pyside-uic 0.2.15 running on PySide 1.2.1 -# -# WARNING! All changes made in this file will be lost! - -from PySide import QtCore, QtGui - -class Ui_Manager(object): - def setupUi(self, Manager): - Manager.setObjectName("Manager") - Manager.resize(883, 748) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(Manager.sizePolicy().hasHeightForWidth()) - Manager.setSizePolicy(sizePolicy) - self.centralwidget = QtGui.QWidget(Manager) - self.centralwidget.setEnabled(True) - sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth()) - self.centralwidget.setSizePolicy(sizePolicy) - self.centralwidget.setMaximumSize(QtCore.QSize(791, 16777215)) - self.centralwidget.setLayoutDirection(QtCore.Qt.LeftToRight) - self.centralwidget.setObjectName("centralwidget") - self.verticalLayoutWidget = QtGui.QWidget(self.centralwidget) - self.verticalLayoutWidget.setGeometry(QtCore.QRect(10, 0, 771, 701)) - self.verticalLayoutWidget.setObjectName("verticalLayoutWidget") - self.horizontalLayout = QtGui.QHBoxLayout(self.verticalLayoutWidget) - self.horizontalLayout.setSizeConstraint(QtGui.QLayout.SetNoConstraint) - self.horizontalLayout.setContentsMargins(0, 0, 0, 0) - self.horizontalLayout.setObjectName("horizontalLayout") - self.treePlugin = QtGui.QTreeWidget(self.verticalLayoutWidget) - self.treePlugin.setAnimated(True) - self.treePlugin.setAllColumnsShowFocus(True) - self.treePlugin.setObjectName("treePlugin") - self.treePlugin.header().setVisible(True) - self.treePlugin.header().setCascadingSectionResizes(False) - self.treePlugin.header().setDefaultSectionSize(100) - self.treePlugin.header().setHighlightSections(True) - self.treePlugin.header().setMinimumSectionSize(30) - self.treePlugin.header().setSortIndicatorShown(True) - self.treePlugin.header().setStretchLastSection(True) - self.horizontalLayout.addWidget(self.treePlugin) - self.verticalLayout = QtGui.QVBoxLayout() - self.verticalLayout.setObjectName("verticalLayout") - self.formLayout = QtGui.QFormLayout() - self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow) - self.formLayout.setHorizontalSpacing(6) - self.formLayout.setObjectName("formLayout") - self.label = QtGui.QLabel(self.verticalLayoutWidget) - self.label.setObjectName("label") - self.formLayout.setWidget(0, QtGui.QFormLayout.LabelRole, self.label) - self.le_ID = QtGui.QLineEdit(self.verticalLayoutWidget) - self.le_ID.setReadOnly(True) - self.le_ID.setObjectName("le_ID") - self.formLayout.setWidget(0, QtGui.QFormLayout.FieldRole, self.le_ID) - self.label_2 = QtGui.QLabel(self.verticalLayoutWidget) - self.label_2.setObjectName("label_2") - self.formLayout.setWidget(1, QtGui.QFormLayout.LabelRole, self.label_2) - self.label_3 = QtGui.QLabel(self.verticalLayoutWidget) - self.label_3.setObjectName("label_3") - self.formLayout.setWidget(2, QtGui.QFormLayout.LabelRole, self.label_3) - self.le_Type = QtGui.QLineEdit(self.verticalLayoutWidget) - self.le_Type.setReadOnly(True) - self.le_Type.setObjectName("le_Type") - self.formLayout.setWidget(1, QtGui.QFormLayout.FieldRole, self.le_Type) - self.le_Path = QtGui.QLineEdit(self.verticalLayoutWidget) - self.le_Path.setEnabled(True) - self.le_Path.setReadOnly(True) - self.le_Path.setObjectName("le_Path") - self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.le_Path) - self.verticalLayout.addLayout(self.formLayout) - self.tableParameter = QtGui.QTableWidget(self.verticalLayoutWidget) - self.tableParameter.setRowCount(0) - self.tableParameter.setColumnCount(3) - self.tableParameter.setObjectName("tableParameter") - self.tableParameter.setColumnCount(3) - self.tableParameter.setRowCount(0) - item = QtGui.QTableWidgetItem() - self.tableParameter.setHorizontalHeaderItem(0, item) - item = QtGui.QTableWidgetItem() - self.tableParameter.setHorizontalHeaderItem(1, item) - item = QtGui.QTableWidgetItem() - self.tableParameter.setHorizontalHeaderItem(2, item) - self.tableParameter.horizontalHeader().setDefaultSectionSize(100) - self.verticalLayout.addWidget(self.tableParameter) - self.treeBlock = QtGui.QTreeWidget(self.verticalLayoutWidget) - self.treeBlock.setObjectName("treeBlock") - self.treeBlock.header().setDefaultSectionSize(80) - self.verticalLayout.addWidget(self.treeBlock) - self.treeSignal = QtGui.QTreeWidget(self.verticalLayoutWidget) - self.treeSignal.setObjectName("treeSignal") - self.verticalLayout.addWidget(self.treeSignal) - self.horizontalLayout.addLayout(self.verticalLayout) - Manager.setCentralWidget(self.centralwidget) - self.statusbar = QtGui.QStatusBar(Manager) - self.statusbar.setObjectName("statusbar") - Manager.setStatusBar(self.statusbar) - self.menuBar = QtGui.QMenuBar(Manager) - self.menuBar.setGeometry(QtCore.QRect(0, 0, 883, 25)) - self.menuBar.setObjectName("menuBar") - Manager.setMenuBar(self.menuBar) - - self.retranslateUi(Manager) - QtCore.QMetaObject.connectSlotsByName(Manager) - - def retranslateUi(self, Manager): - Manager.setWindowTitle(QtGui.QApplication.translate("Manager", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) - self.treePlugin.setSortingEnabled(True) - self.treePlugin.headerItem().setText(0, QtGui.QApplication.translate("Manager", "Plugin", None, QtGui.QApplication.UnicodeUTF8)) - self.treePlugin.headerItem().setText(1, QtGui.QApplication.translate("Manager", "#Parameters", None, QtGui.QApplication.UnicodeUTF8)) - self.treePlugin.headerItem().setText(2, QtGui.QApplication.translate("Manager", "#Blocks", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("Manager", "ID", None, QtGui.QApplication.UnicodeUTF8)) - self.label_2.setText(QtGui.QApplication.translate("Manager", "Type", None, QtGui.QApplication.UnicodeUTF8)) - self.label_3.setText(QtGui.QApplication.translate("Manager", "Path", None, QtGui.QApplication.UnicodeUTF8)) - self.tableParameter.horizontalHeaderItem(0).setText(QtGui.QApplication.translate("Manager", "Parameter", None, QtGui.QApplication.UnicodeUTF8)) - self.tableParameter.horizontalHeaderItem(1).setText(QtGui.QApplication.translate("Manager", "PCP", None, QtGui.QApplication.UnicodeUTF8)) - self.tableParameter.horizontalHeaderItem(2).setText(QtGui.QApplication.translate("Manager", "Value", None, QtGui.QApplication.UnicodeUTF8)) - self.treeBlock.headerItem().setText(0, QtGui.QApplication.translate("Manager", "Block", None, QtGui.QApplication.UnicodeUTF8)) - self.treeBlock.headerItem().setText(1, QtGui.QApplication.translate("Manager", "Subscriber", None, QtGui.QApplication.UnicodeUTF8)) - self.treeSignal.headerItem().setText(0, QtGui.QApplication.translate("Manager", "Signal", None, QtGui.QApplication.UnicodeUTF8)) - diff --git a/ui/gui/qt_dev/add_plugin.ui b/ui/gui/qt_dev/add_plugin.ui deleted file mode 100644 index bf7f0b32..00000000 --- a/ui/gui/qt_dev/add_plugin.ui +++ /dev/null @@ -1,149 +0,0 @@ - - - AddPlugin - - - - 0 - 0 - 579 - 558 - - - - Dialog - - - - - 230 - 510 - 341 - 32 - - - - Qt::Horizontal - - - QDialogButtonBox::Apply|QDialogButtonBox::Ok - - - true - - - - - - 10 - 10 - 561 - 171 - - - - - Plugin - - - - - - - 10 - 190 - 561 - 62 - - - - - QFormLayout::AllNonFixedFieldsGrow - - - - - UName - - - - - - - - - - Path - - - - - - - true - - - - - - - - - 10 - 270 - 561 - 241 - - - - - - - - 7 - 250 - 561 - 20 - - - - Qt::Horizontal - - - - - - - buttonBox - accepted() - AddPlugin - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - AddPlugin - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/ui/gui/qt_dev/add_subscriber.ui b/ui/gui/qt_dev/add_subscriber.ui deleted file mode 100644 index 432e97d8..00000000 --- a/ui/gui/qt_dev/add_subscriber.ui +++ /dev/null @@ -1,125 +0,0 @@ - - - AddSubscriber - - - - 0 - 0 - 531 - 197 - - - - Dialog - - - - - 10 - 160 - 511 - 32 - - - - false - - - Qt::Horizontal - - - QDialogButtonBox::Apply|QDialogButtonBox::Ok - - - true - - - - - - 10 - 10 - 511 - 141 - - - - - - - - Subscriber - - - - - - - - - Target - - - - - - - - - Block - - - - - - - - QAbstractItemView::MultiSelection - - - - Signal - - - - - - - - - - - buttonBox - accepted() - AddSubscriber - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - AddSubscriber - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/ui/gui/qt_dev/main.ui b/ui/gui/qt_dev/main.ui deleted file mode 100644 index 6172b53a..00000000 --- a/ui/gui/qt_dev/main.ui +++ /dev/null @@ -1,293 +0,0 @@ - - - MainGUI - - - - 0 - 0 - 979 - 918 - - - - MainWindow - - - - - - - - - Exit - - - - - - - CreatePlugin - - - - - - - ShowOverview - - - - - - - CreateSubscription - - - - - - - CreatePCPSubscription - - - - - - - ShowLicence - - - - - - - - - Save - - - - - - - Load - - - - - - - - - - Qt::ScrollBarAsNeeded - - - Qt::ScrollBarAsNeeded - - - - - - - - - - 0 - 0 - 979 - 25 - - - - - Menu - - - - - Plugins - - - - - - - - true - - - - 100 - 41 - - - - false - - - 1 - - - - - - 12 - 4 - 161 - 831 - - - - - 100 - 0 - - - - 2 - - - - - 0 - 0 - 161 - 707 - - - - Visual Plugins - - - - - 0 - 0 - 161 - 701 - - - - - Plugin - - - - - - - - 0 - 0 - 161 - 707 - - - - Process Con. Plugins - - - - - 0 - 0 - 161 - 701 - - - - - 1 - - - - - - - Data Pro. Plugin - - - - - 0 - 10 - 161 - 691 - - - - - 1 - - - - - - - IO Plugin - - - - - 0 - 0 - 161 - 701 - - - - - 1 - - - - - - - - - - Available - - - - - IO - - - - - Visual - - - - - IO - - - - - License - - - - - Quit - - - - - Parameter - - - - - Overview - - - - - - diff --git a/ui/gui/qt_dev/manager.ui b/ui/gui/qt_dev/manager.ui deleted file mode 100644 index 39bdc62d..00000000 --- a/ui/gui/qt_dev/manager.ui +++ /dev/null @@ -1,233 +0,0 @@ - - - Manager - - - - 0 - 0 - 883 - 748 - - - - - 0 - 0 - - - - MainWindow - - - - true - - - - 0 - 0 - - - - - 791 - 16777215 - - - - Qt::LeftToRight - - - - - 10 - 0 - 771 - 701 - - - - - QLayout::SetNoConstraint - - - - - true - - - true - - - true - - - true - - - false - - - 100 - - - true - - - 30 - - - true - - - true - - - - Plugin - - - - - #Parameters - - - - - #Blocks - - - - - - - - - - QFormLayout::AllNonFixedFieldsGrow - - - 6 - - - - - ID - - - - - - - true - - - - - - - Type - - - - - - - Path - - - - - - - true - - - - - - - true - - - true - - - - - - - - - 0 - - - 3 - - - 100 - - - - Parameter - - - - - PCP - - - - - Value - - - - - - - - 80 - - - - Block - - - - - Subscriber - - - - - - - - - Signal - - - - - - - - - - - - - - 0 - 0 - 883 - 25 - - - - - - - From 54294b5c63867e877f80f948db14696d1834e913 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 19 Jan 2015 12:00:05 +0100 Subject: [PATCH 076/260] removed ui files for dev gui, changes in Makefile due to new docs creation, template change --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 6b7d4f9a..acf23767 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -107,7 +107,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'sphinxdoc' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the From 326b798d26d81d7c6f2a942ce3fc7496c1f07ea2 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 19 Jan 2015 12:06:34 +0100 Subject: [PATCH 077/260] added some comments --- papi/core.py | 2 +- papi/gui/qt_new/main.py | 63 ++++++----------------------------------- 2 files changed, 9 insertions(+), 56 deletions(-) diff --git a/papi/core.py b/papi/core.py index c37a0574..ea4d5d83 100644 --- a/papi/core.py +++ b/papi/core.py @@ -61,7 +61,7 @@ def __init__(self, gui_start_function, use_gui=True): Will create all data needed to use core and core.run() function .. document private functions - .. automethod:: _* + .. automethod:: __* """ self.gui_start_function = gui_start_function diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index b016ec4d..8a21c6f7 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -29,14 +29,18 @@ __author__ = 'knuths' import sys -import time import os import traceback +import cProfile +import re from PySide.QtGui import QMainWindow, QApplication, QFileDialog, QDesktopServices from PySide.QtGui import QIcon from PySide.QtCore import QSize, Qt, QThread, QUrl +import papi.pyqtgraph as pg +from papi.pyqtgraph import QtCore, QtGui + from papi.ui.gui.qt_new.main import Ui_QtNewMain from papi.data.DGui import DGui from papi.ConsoleLog import ConsoleLog @@ -49,21 +53,19 @@ from papi.gui.gui_api import Gui_api from papi.gui.gui_event_processing import GuiEventProcessing -import papi.pyqtgraph as pg -from papi.pyqtgraph import QtCore, QtGui from papi.gui.qt_new.create_plugin_menu import CreatePluginMenu from papi.gui.qt_new.overview_menu import OverviewPluginMenu -import cProfile -import re -# Enable antialiasing for prettier plots + +# Disable antialiasing for prettier plots pg.setConfigOptions(antialias=False) class GUI(QMainWindow, Ui_QtNewMain): """ Used to create the qt based PaPI gui. + """ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): """ @@ -137,16 +139,9 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): self.loadButton.clicked.connect(self.load_triggered) self.saveButton.clicked.connect(self.save_triggered) - # self.buttonCreatePlugin.clicked.connect(self.create_plugin) - # self.buttonCreateSubscription.clicked.connect(self.create_subscription) - # self.buttonCreatePCPSubscription.clicked.connect(self.create_pcp_subscription) - # self.buttonShowOverview.clicked.connect(self.ap_overview) - # self.buttonExit.clicked.connect(self.close) - # ------------------------------------- # Create actions # ------------------------------------- - self.actionLoad.triggered.connect(self.load_triggered) self.actionSave.triggered.connect(self.save_triggered) @@ -168,60 +163,18 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): # ------------------------------------- # Create Icons for buttons # ------------------------------------- - load_icon = QIcon.fromTheme("document-open") save_icon = QIcon.fromTheme("document-save") - # addplugin_icon = QIcon.fromTheme("list-add") - # close_icon = QIcon.fromTheme("application-exit") - # overview_icon = QIcon.fromTheme("view-fullscreen") - # addsubscription_icon = QIcon.fromTheme("list-add") - # ------------------------------------- # Set Icons for buttons # ------------------------------------- - self.loadButton.setIconSize(QSize(30, 30)) self.loadButton.setIcon(load_icon) self.saveButton.setIconSize(QSize(30, 30)) self.saveButton.setIcon(save_icon) - # self.buttonCreatePlugin.setIconSize(QSize(30, 30)) - # self.buttonCreatePlugin.setIcon(addplugin_icon) - # - # self.buttonExit.setIcon(close_icon) - # self.buttonExit.setIconSize(QSize(30, 30)) - # - # self.buttonShowOverview.setIcon(overview_icon) - # self.buttonShowOverview.setIconSize(QSize(30, 30)) - # - # self.buttonCreateSubscription.setIcon(addsubscription_icon) - # self.buttonCreateSubscription.setIconSize(QSize(30, 30)) - # - # self.buttonCreatePCPSubscription.setIcon(addsubscription_icon) - # self.buttonCreatePCPSubscription.setIconSize(QSize(30, 30)) - - # ------------------------------------- - # Set Tooltipps for buttons - # ------------------------------------- - - # self.buttonExit.setToolTip("Exit PaPI") - # self.buttonCreatePlugin.setToolTip("Add New Plugin") - # self.buttonCreateSubscription.setToolTip("Create New Subscription") - # self.buttonCreatePCPSubscription.setToolTip("Create New PCP Subscription") - # - # self.buttonShowOverview.setToolTip("Show Overview") - - # ------------------------------------- - # Set TextName to '' - # ------------------------------------- - - # self.buttonExit.setText('') - # self.buttonCreatePlugin.setText('') - # self.buttonCreateSubscription.setText('') - # self.buttonShowLicence.setText('') - # self.buttonShowOverview.setText('') def set_background_for_gui(self): """ From 69c738940897cce3c707064cd74a3ef45d23decb Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 19 Jan 2015 14:27:07 +0100 Subject: [PATCH 078/260] added some comments --- papi/core.py | 6 +++--- papi/gui/gui_event_processing.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/papi/core.py b/papi/core.py index ea4d5d83..10b4fb91 100644 --- a/papi/core.py +++ b/papi/core.py @@ -1,9 +1,9 @@ #!/usr/bin/python3 -# -*- coding: latin-1 -*- +# -*- coding: utf-8 -*- """ -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, Fachgebiet Regelungssysteme, Einsteinufer 17, D-10587 Berlin, Germany diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 4538246a..76b4739c 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -43,7 +43,6 @@ from papi.data.DPlugin import DPlugin from papi.pyqtgraph import QtCore - __author__ = 'Stefan' From 467f4602e5952236528336caec753797a447e3c0 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 19 Jan 2015 14:42:35 +0100 Subject: [PATCH 079/260] changed sphinx configuration: header will now be removed --- Makefile | 3 +++ docs/conf.py | 57 ++++++++++++++++++++++++++++++++++------- docs/index.rst | 6 +++-- papi/gui/qt_new/main.py | 5 ++-- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index d3cb1fcf..1b4b15a6 100644 --- a/Makefile +++ b/Makefile @@ -41,3 +41,6 @@ create_rst: docs: create_rst make -C docs html + +html: + make -C docs html diff --git a/docs/conf.py b/docs/conf.py index acf23767..8ceffb5b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,7 +36,7 @@ # ones. extensions = [ 'sphinx.ext.autodoc', - 'sphinx.ext.viewcode', + 'sphinx.ext.viewcode' ] # Add any paths that contain templates here, relative to this directory. @@ -53,7 +53,7 @@ # General information about the project. project = u'papi' -copyright = u'2014, Author' +copyright = u'2014, Control Systems, TU-Berlin' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -76,7 +76,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build', 'papi.yapsy*', 'papi.pyqtgraph*', 'papi.ui.gui.qt_dev*'] +exclude_patterns = ['_build', 'papi.yapsy*', 'papi.pyqtgraph*', 'papi.ui.*'] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -107,7 +107,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'sphinxdoc' +html_theme = 'nature' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -206,7 +206,7 @@ # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'papi.tex', u'papi Documentation', - u'Author', 'manual'), + u'Control Systems, TU-Berlin', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -236,7 +236,7 @@ # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'papi', u'papi Documentation', - [u'Author'], 1) + [u'Control Systems, TU-Berlin'], 1) ] # If true, show URL addresses after external links. @@ -271,9 +271,9 @@ # Bibliographic Dublin Core info. epub_title = u'papi' -epub_author = u'Author' -epub_publisher = u'Author' -epub_copyright = u'2014, Author' +epub_author = u'Control Systems, TU-Berlin' +epub_publisher = u'Control Systems, TU-Berlin' +epub_copyright = u'2014, Control Systems, TU-Berlin' # The basename for the epub file. It defaults to the project name. #epub_basename = u'papi' @@ -335,3 +335,42 @@ # If false, no index is generated. #epub_use_index = True + +# We want to remove all private (i.e. _. or __.__) members +# that are not in the list of accepted functions + +def member_function_test(app, what, name, obj, skip, options): + # test if we have a private function + + if len(name) > 1 and name[0] == '_': + # test if this private function should be allowed + if name.startswith('__process'): + + # omit privat functions that are not in the list of accepted private functions + return False + else: + # test if the method is documented + if not hasattr(obj, '__doc__') or not obj.__doc__: + return True + elif name.startswith('BACKUP'): + return True + +def autodoc_process_docstring(app, what, name, obj, options, lines): + # Remove Copyright Header + authors = [] + if what == "module": + for i in range(len(lines)): + if lines[i].startswith('Contributors'): + authors = lines[i:-1] + + del lines[:] + + for i in range(len(authors)): + lines.append(authors[i]) +# lines.append( "**Contributers:** " + authors[i] + "\n") + + + +def setup(app): + app.connect('autodoc-skip-member', member_function_test) + app.connect('autodoc-process-docstring', autodoc_process_docstring) \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 3b14a6b6..bf406f3d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,7 +9,9 @@ Welcome to PaPI's documentation! Contents: .. toctree:: - :maxdepth: 2 + :maxdepth: 3 + + papi @@ -17,5 +19,5 @@ Indices and tables ================== * :ref:`genindex` +* :ref:`modindex` * :ref:`search` - diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index b016ec4d..0028d918 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -29,13 +29,12 @@ __author__ = 'knuths' import sys -import time import os import traceback from PySide.QtGui import QMainWindow, QApplication, QFileDialog, QDesktopServices from PySide.QtGui import QIcon -from PySide.QtCore import QSize, Qt, QThread, QUrl +from PySide.QtCore import QSize, Qt, QUrl from papi.ui.gui.qt_new.main import Ui_QtNewMain from papi.data.DGui import DGui @@ -54,7 +53,7 @@ from papi.gui.qt_new.create_plugin_menu import CreatePluginMenu from papi.gui.qt_new.overview_menu import OverviewPluginMenu -import cProfile + import re # Enable antialiasing for prettier plots From 4ac7eb96e6d5ca09e8832d7ee2325e6df3140ad3 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 19 Jan 2015 15:08:24 +0100 Subject: [PATCH 080/260] added new default background changed path definition for background and last cfg. now to find in constants.py --- papi/constants.py | 3 +++ papi/gui/qt_new/main.py | 7 +++++-- papi/media/default_background.xcf | Bin 0 -> 92951 bytes papi/media/default_bg.png | Bin 0 -> 3481 bytes 4 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 papi/media/default_background.xcf create mode 100644 papi/media/default_bg.png diff --git a/papi/constants.py b/papi/constants.py index 9f0ad839..5691ca2e 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -45,6 +45,9 @@ CORE_ALIVE_CHECK_INTERVAL = 2 # seconds CORE_ALIVE_MAX_COUNT = 2 +PAPI_LAST_CFG_PATH = 'papi/last_active_papi.xml' +PAPI_DEFAULT_BG_PATH = 'papi/media/default_bg.png' + PAPI_COPYRIGHT = '© 2014' PAPI_ABOUT_TITLE = 'About PaPI' PAPI_ABOUT_TEXT = """ diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index 8a21c6f7..efc06e7a 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -47,7 +47,7 @@ from papi.constants import GUI_PAPI_WINDOW_TITLE, GUI_WOKRING_INTERVAL, GUI_PROCESS_CONSOLE_IDENTIFIER, \ GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_START_CONSOLE_MESSAGE, GUI_WAIT_TILL_RELOAD, GUI_DEFAULT_HEIGHT, GUI_DEFAULT_WIDTH, \ - PLUGIN_STATE_PAUSE, PLUGIN_STATE_STOPPED, PAPI_ABOUT_TEXT, PAPI_ABOUT_TITLE + PLUGIN_STATE_PAUSE, PLUGIN_STATE_STOPPED, PAPI_ABOUT_TEXT, PAPI_ABOUT_TITLE, PAPI_DEFAULT_BG_PATH, PAPI_LAST_CFG_PATH from papi.constants import CONFIG_DEFAULT_FILE, PLUGIN_VIP_IDENTIFIER, PLUGIN_PCP_IDENTIFIER, CONFIG_DEFAULT_DIRECTORY @@ -125,7 +125,10 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): self.log.printText(1,GUI_START_CONSOLE_MESSAGE + ' .. Process id: '+str(os.getpid())) - self.last_config = 'papi/last_active_papi.xml' + self.last_config = PAPI_LAST_CFG_PATH + + self.update_background(PAPI_DEFAULT_BG_PATH) + self.in_run_mode = False # ------------------------------------- diff --git a/papi/media/default_background.xcf b/papi/media/default_background.xcf new file mode 100644 index 0000000000000000000000000000000000000000..2a0dc040a96030b32ef665fba423ff4126a68ac7 GIT binary patch literal 92951 zcmeHQeQX>@6@Tm4NzTC==L@0`#Fe-vPMS0osubtTAxIR|QV^m7K?OLD;~q`yU_07G zUAQ15{{t0}h(8cT5vusBeDxoy2-+BdfVM&qgerjmp`t?JAIhhQ^TnH)o!Pndoqd;^ zy_>mtC%v1uvwORofzLWFjUF+^ZIRNm65tRD@-t<9~ zF@SAMrcfRO82Sv#;{bPGK)HzW6u`Yqo&nhZEXwl$Z~qC}GTy_Hy8zxbiEFTVtE zZZis&b?(C`p9c8+6Dad2{{=XI9pFL{;46nvjsjf7zIXAHD4zoO#%EE!0Pxh8QNE7y zEr6$)TmpFJGRlhp&;AVM*C>Ak_|czH{t58>{{UXVc6b5H|4SxY0bY6w3f{->cA|`; z;61#IWq%pV`|1sVzrGO#>+SESQLw)L^EApY0IvNCjC~X3 z#}G_>0ObVWr`!)QN|cDw*T+|{oIiK&{FQh(zBT|b5bvYZt9lrW*GGvBM8grcfoLD4 z%m%!vhrx(!z;~M;WZ0l^VLRc1=Q!9OZzo*v90&U&vH{<5p}ztb4#hXG#O1Y@<3rKS zD-kM@QWj_);rIMW7ywNoyaf$10Iz0^i;+>EL2RTMgy%Defp{a$AiMN zlYXtT4pht_cEk&uafo*$?vYYKymWp&TxY|h9g%4NXh-52DHZOZ^Xq}ypOH$P-_Et{ znYX__N6_^Ro{yG&=iLY64W5sceIF+dMq~kg%?z>@U&%oa5hAqeNhExha1(p-OQx;5 z?vmH(kB0~mSPiw}IV9Hd>^d@S)peJ=PJcue&@x1jEicn0TPEwd^V+UtpY{0yuNQh+ z)o+Y!g=e&)ALP})=)5iSuVhC?M>0AVZ1UR+dXItw@g|xncw%L@JBeH7aNr-;NX<_abGheme0^y1x^RD(B<0@2G3G{#a-*8LaT(5q z@^^{OkCwV5tcT-;Iksko5!8vYFJ9;Y=$U3YrWN7lZkH^^VBhfSssO{xSA~0(c4vt9 z!V1bJ5~fn-f4rNRq$KumB#Z&WArfU@#Esz;%VCvlQfY4JhvC1<7AmYF;?}?x*;s*1 zq+U)^Qes;fgHp&Ud|iS>vKw(~Ih1jQ1jlgU@M;0ybJJB|$dYJlLs!awyNGK3cMEJV zF~`OZQW2Q%4jqnR!^#3GY=IW(8pJ~9L+ykKnB1uxA}@R)gY}t4>th)IbL0t^t$bXUkl2|Cen;*m#NXgeY@^2rP9+S z(0qq14qrzurO4FCV7}e;k<#YvE{Oxzmn|>T4zU0o5r|6W{?EIIsw4urLx4NrFP!ub z?$F+30r`y8C?4Ez43Jn*Bejr7%cmcZf1!G9(VJl$87b&91tNC+MhSNaY4YRw_;GiL zXtIF3EI>yDM~G&km7AuO1@fiw@Gh~8L@FDP+o$HIc<^%Xd^R4udmdvT=CkqO-SZd+ zQ6pDG>LtJ9QG3bJQYXSXpGgOgSzi=c$Ya*o)YK+AgpqGC5!ZPQX?dJ_HA}GX>0M64 z5`j&YAYz;_)*CS>K?;sM5;Yj!NSz4od=SL7*mNSg^YR<6MI#g8O_m^*$B_8013}m^ z7{Og&QQn2P+sD@XH z=*@X~H9D_t^{X0QDYlhRmZu(kA!41^mT?SoEkd0L_Pji;YjMd$w38(W1i^^!I%X(D z#0vyT32GzTDlS9*s{=jM?TDH7Q$pUh* z038tw63wjD7@aCp)_NAwt44_A4!AGVEFZtcbO@>xwW6CUJ1C}#bf z)f6*XKuat@M+AcuGxy+zV}d(?$`$i3`|8Cs$H+Ucznq;IR=RWl)ciy-kHj~IHiZX5 z@m*efI39^IIZ>4y@e){mG=J^u+QWF1)FeIa&?~PwaJfFTx2^msP3_PtuW109!vHj; ztH~04TYRd#-@#~PN$ikBwmUDoUSJPY&c(xVs`LQ#lvs{wMNA}Y^YeL^CndMtdGWO{ zhG;lS6>bbH$Fx$}!?Mdgow}V8-eeWkjLKK5SQ=kODqbx9TzqpbKFGexy(2jD(O)S3 zTy%3TI>^3oAI@%S?~`6xy&G34a*s7BkNrS@EmNBJn2Tvgf?YMdQmpHRJp2POL8dg{ zF*@H-4X+fk0E{#9;Ng>{aYxebuDv2V@3Fma=TGT(*Itp`sq=_--B(J(lOmVYTWq$Y>SziJygBU(W-V=ipK%6!ezKaw%=}f$pE-7?)-+XWgy%D z^yjs7T~h774WsFP1yH3Fd%K3QbY0PL+f@%Kwcf50&~@8w0XiZ`0-Zdh@Q}XMLrRuY zzie;6Y*+oK#CW?(K-X=r>QqknE{Pb5T1x*&;lcC((|6k=r0<1W$dPQ0?&{%r~|vgdJ^@Fq(z zYhg%y*MT7A73DPUJGiAsgcmNyxHpab4sPjj--XN3(tBIxIc5B|Oq-<7Rj{g3T6QN* zmfXs6wt`@n@!K-xPswLJWBuEz;gw>xXVza5x^A7+v21ynk_9wb1Ewsm9t?7oe5d$z zvxws^2~SbWru>CCkGo_%S%PUxs0TqDCF@n}vQj9Dz2PW!m-$-N?n;*kIDKR+IFg5TR~b|LeQD%Pvn=V9_ys) zEw_6e3tT0p32n@vjk|<2`C~Kw*j*x;EWnfn=!oDb(M+^*!?dzMkTm+;WtNdh<%UUR z;XE=NHupqN+BSN=oz>iNq52VcA`BKR<$N9-jjQPRAbt5CJ; z!>ffZfUZKgS7~>K88$GJo&V}t`tRs0 zUWxr*db>(e>$)oPEm5{$y>1R0YsOk`xbgh-f5Ulw;s1uWt0c9qOZC;*>VNh6Vja91 z5Ysw*^?q720L{AoQLEpyHlKoX8msSG^;N5%((0(_r|GI8-&VmLs9fXoTUtmGhL~CS mp^C~>NUfEz#T5k*oI#le@%aHJmlFM>m_D57cVoJn>Hi0XwoRY_ literal 0 HcmV?d00001 diff --git a/papi/media/default_bg.png b/papi/media/default_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..ddb529b6b521df6b9113c98739ec23172f1f1e86 GIT binary patch literal 3481 zcmeAS@N?(olHy`uVBq!ia0y~yVEO>WPdS)?BD(ioS%DO1fk$L90|U1(2s1Lwnj--e zWH0gbb!ETLD9p!Xw1EHgWT22_iEBiObAE1aYF-J0b5UwyNotBhd1gt5g1e`0KzJjc zIM5PdPZ!6Kid%2*7-lJZiZnb7T3ja08f;L_&=_!O`3K%9CfsR$h9`|q#wAR)JeCkq z!f}7G@$zFqs>hbPt>x`$6`ZZ3^XbZq-;17SoT--hw{n4cecf*{jSKJN_uaXF-`>`? zRlu(PUrllG=kNXU*B&+PP|&NGDL#i|R?*$H*K6;$3rsuxH1~RB2RBga!O^%4oA?+O zlr*t12$(Q104awHaEe2Ufx(fP0ZJ)kFfg$2GC-+@1&kmaP-;{o`GNCb`h*}WNoZi6 z(>_*V1P)_hz#|1UFyN7b8Y!xPkpqe`q&RYP7ECi-HYM))V`xMcxSiL`-G2Le!aF@eDwcOizX0zvfuG_!8 zuj$F0^4)hI7H}x~s7+qIdiAm88C^y^&gUv+jte-2hWcK3+TlVtrDt!xY$^MHvF5kg1-1y0OuPG_Hgo{X#llw9$LA$fuSAbom} z72jqFT^2|#ve^COo0f2LOXI~J1qO;mhROEkQ_UZy}R z2#fU+k57WjBs?1MduhaKd{A$adHdVPkA}~4#8V~P6rbDlC37rXwQ5=Bt6kf~#E-BR z9z4BgfjB6#FXwID)anGSxW&4UB30ZMxOo`@Y*6cGjKUeIEJR8tkV*qqSS``nEO9^n zf)gZJXk0GaZOg~c&o34EAZ6a{vei>{MPB9YefQh0WkZBc-tKcwz%ug)tNyp9xD1AdN1P~4 z5~P+1FvBA?X@HJHN}Y5MC1^Q@Tttlwr#5iI8Yz$h7`c%GEt1J9nGe((5-!yqTybkK twwB>r{(tK%pSp+j-!Ijr{}A}k@aKL&^>(3-9$+7e!PC{xWt~$(695^K6Mg^y literal 0 HcmV?d00001 From 67c974e45417eb2da709cfd36d15c6471527d11c Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 19 Jan 2015 15:11:43 +0100 Subject: [PATCH 081/260] added new default size for button --- papi/plugin/pcp/button/Button.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/papi/plugin/pcp/button/Button.py b/papi/plugin/pcp/button/Button.py index 65efe10e..4e5fe381 100644 --- a/papi/plugin/pcp/button/Button.py +++ b/papi/plugin/pcp/button/Button.py @@ -81,7 +81,12 @@ def get_plugin_configuration(self): },"name": { 'value' : "Button", 'advanced' : '0' - }} + },'size': { + 'value': "(150,50)", + 'regex': '\(([0-9]+),([0-9]+)\)', + 'advanced': '1', + 'tooltip': 'Determine size: (height,width)' + } } return config def plugin_meta_updated(self): From 32a5df7c51826bc3b5fc7ec3c8433a5847fb0128 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 19 Jan 2015 15:21:08 +0100 Subject: [PATCH 082/260] removed design folder, small changes in sphinx configuration --- design/CoreDataStructure.asta | Bin 17776 -> 0 bytes design/PaPI.asta | Bin 74250 -> 0 bytes docs/conf.py | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 design/CoreDataStructure.asta delete mode 100644 design/PaPI.asta diff --git a/design/CoreDataStructure.asta b/design/CoreDataStructure.asta deleted file mode 100644 index 42a3c8f1b9a4ea96808a742e5e86d439637b3e92..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17776 zcmV*7KytrOO9KQH00;;O0D@c$MF0Q*000000000001E&B07Y(eX>@s0bZ>HH?R^V) zBUgFoNVaUtZ+jokJDcooLdve!k}XSSv&nAOS+c1UHk(aIwiGkcjOlJK{UEmMq(&DE-`gr1P_uTxcAk%e_*^nwp5}%XtnQM8eD2Tbsnc)mugj+&d z;JG70mOsc0W!Z}Hp*d!w@zq0;$dyvOBr~IP$JjEvE5qg%c3mw?LT+LA95Z^X#PRBz zmkC8#lvZSBgYsla7BagoV~dMduzB+6#-b?6JZF7)*zoWixw*TNXBcKX!vtRZ@S8Us zh0ik6v2T1o!N&+ZIu4JHtL=umzx~r}j%)j~qdv^yDILLGsh2uPe6IkwqYl*z{ zCkXF2pkZ7^!@Xa<_Q{`~`N|guNxSfLP-ceqi`i@`C&(*XrpFWG6EYK=j6m$GiUJOV1t&GQl|}M5rOH6qr}z7eE|u$MV%Y;I0Tyb=d|S zkePl$%42*=AuEAommQK)*2Gj<((U*)c;jF;|DIR<^0W(HwEx~96PROqit@eq5x3v>iy0fL88n=J@p2N2aS@e5s4 z!F~dNk=!$~O>dEmix*|mvwCN=Sjy)KP9+;k`($=uf#@wZQ>s4PQ&Ks%O8puPZ==3W z__tkh(=8vkDh>)9bV0bUqojVUIO-7S4K>-N zm6s%oyi{hB8UBl-xBc_@Gfx(XGCRuj@|F42BGFnrQHX(j1@(i>Y>gIoPI0jqn@&tb z6OlwL9^;cU@$}SWoKHp5$;d>MQ%DwIw|dp2-GVrE>iq_M-enfWeG{?X!luOFO5 zLF`P5+=_w=!WxVNbP_Y+r7PgPAN}el$D|uRHS5sB2+$55C+fbGl1mc5Duj&SjS&Lm ztSgY0R%AX0tZ)Snv=fS10`3u=9|vghJ#*aaum`d45|MWS^nP{R?_KSFUxS7_WYuy{iZhOUe z%X#>bC9lIDF`|APypSpeI*Fw6wjQFM`RYtt597M!(D1z@McnG*4pt`aV6r6hJH$Nk zR6q=N@VQbJFY{=a*_>>Hie2#v)w%Vc8b@HNpxpx`*>gF0WXpcIKhP_)(gH8k)b#zD}zX<~eLD6Z&^OHibxl^y$u{~(E(9s9t&9_TF#MIk9<$Rm5wY^KN$ z^h&DU*#o$Tein**RELH%59rCV($c)3^+5a-Atk5t1HBnG$!GT1FcY8fikb83Vn!oS z6+1;8JNv4?X&5rx@jh7Mf)SlcwBC-gm?8HveC<)DnmbC3`KqH_)<<~-FE5ImHpJ>z z@8c`3PbLx&1S!;# z5IVo;BI~Hn6}=Ank6Z_BRwiWhyf*qR)t_AWn0iE7gZ80qO?Xz>o9$zRKC9!HtFJH| zn9eR}8Xwvyk!%9{5opH^Kvn_&DG~r5$HxqhG>IQ~Xms3B6toiS)J`L$vv41 zvZaiSzSo`s!_<&`!FFW6B3J2e8iSY>w9<3A`gd zEl5)Z#g4h8C}wyzS4G}l19-J@H+`%LwjXe36pbq0-e|#_@fn*5u#Vl4V`v9&?x_yz zuwI9MhV$?hWl$aXt(Ji+zqWSB)jnzuHsRU&o$WkWP36@hwDy3L4X}>d@KZwrH*{8e z?2$#UV?WDz>{TUS9r_zAL)X2n){k88tMzsZzk}KQUk2~HGXLezpB;pdU04e%QpF{e@P!r&hA^Tr z{0neS%rORsN@gzf6z#lLd-IQl9JkvR14&Z2jSAn;M;~Vec0ppZ1U2V8e&qK5{@`c- z0aq8aSU$A3agI!$)v#y)p&AP~FBTYAq7IN|`9p>{>-g*qfAjD)14&pCaeaFZIKue$ z{U?tV?s(e`y9wovGX0#muOy2QV?NFwHR61Z2_Z9800E4g++Om2`vkpyhs=($rkzjVn&p-H7a&l-i3E|yLWau`E2a$FHcCE z+)}kCeMKOgB^nmOY1N-XNEuZ%b;BY6fwsD0VUcltIRUO`F!>5Di!!#bHGF@kJ>Cr( zDoZkrWL$qawZpFHzO_;1dfCz^GZV3+(`(O}g6mWQo( zD(eq`ChyJCz1Lvv9WBwDnG9?;GEB6MWeFS699BlUM!cZFyJ? zW(=k#*QOO9)pc=Nv8hIMZA&fi!`rpzL->3MzTQJHHdS8-qpt#~4w6ys|N3u=(css< zP2~Qxqs(SDlhOP@qvXR6FR4b-j6a;IMG^NqDDk7r8QSj$i35ZSCeE*4?Wl3awBL_d zez_nCDg?)LgwN)Q?+F?o7Fu};92MiU+v_}Qv^Zf57DG=&*MauFQnfq>nE`DAtNh#} z6p3d@<~T>05uu1pL$3#AHY22nSr&?y2^K zUv1Nhq78YcK@R3wdC~aTMu{h^m9xHmx?CGoN10Q~CXek}gBLO)wH6yU|+#MkG=r3n?MbYDpuVdE)k}pZg@cLj`KPEyYO;PNQdB-xPdV4it$XTSX|=PvEJ z7V{pA)2Y17bmfUhBc!wus9A7`)ke^w*tA~1X5fh;`^pV3 z``M{?+;;({OZ2e|3lhJe&NlXVn!xYVU&R@`P0z_poK`;fnO?-Esvyr9eE)t7sjwL} z3nSqP-b*VWvM|el2~(7M!h|!D6@s}__RZb>m1LSq|R zyTb79Zp;vaklWQMf-5Sj8=AtGRG2XEA~Tb;Y@@5*I!Tu{H0#KfUi+l+X}k5?zXgp!R9kyd*deNKwJN5CaqbxG|p#EwuV zM%1hAFpClY{-@q^=lfs&-%ee2=ENK{pYYPWQLc_%_xsO1{H70nC}@s)j2PwiFhHYf zA3dT$df%4IW_GekZe}VLnc*fA2{xHXPDi+@G?(B~sj0*yn~vM<6S)xx;q4PfN*_-Y ze8ky3vxgMcwpmXCs6_8M1cu?$cbWmW4r~|R>9T>w^1=)iXn|pT_X+d6(>}s~H$Pt} z@tC_9IHwhwg-uVD=`>4Vs{#~!445?uQdebWgTTX0OuY;Alb#|INoi*yl4N5XJ2R6^ zbCK8-n@+`J(P=&rOT{J=Gby&Nm>3K|JkL$}h)jL6mzK!{?F>CO*)`R4!2JK5l{wlw6JBk#Ut#n7!jeUKjcw)pMeT=g){ z1GG%}Q()nSnB$VVE8l=WL{RLLIkaSWW; z{8U2HMOTtGS4ILIln*0&b~L4PibnEQ0F~}_U1w5Dr4rY$S}K(&5`EgHmO`5OzM&;b zsXRrQ@)@|XF{KodCz|HmvgJ%$t#;kzpOCY)CNNT1YkX~!f$dU-iFWO6qG7v%oG8!d zcrb{pv|<}1fmZh5lDww{F3G8Q=Ttjr`51)*j!@b7i@ZxqyVer{e*`!`+8PxLm!=J6 zwb11qR^QuvqpBilw*D!uz!7uMpLhYk`ty4aeDj|#*nnO@Kdx)4E&^2y(7XU*a4;b8 zB5z+0QGJPUvAVniH@@UygYRKJqvT90NoaT8CA@mqo+o}*U9dO0B1ASQLP+hD4JXl0 zc+g7UaM4OSL;R65Ix)54a>MpRg2I`fe&pdta{F!y!k+vdSfvJ^l#pb9WBVsfjfc-W z{rf-q*4G}u0P_%L;&7%kKUfWeKB@Am41wJj^CDp_3FE_(z9my_(vWYo-@IengWvpO zZH3e1H^2U+_uuz}H|{(TWI~?y(mQUPN1in^grBCEok^l7n&U7to^-dq78dwMg=$sZ zFqx|I#{O5_e*Wt|#yw!4yg|Et?%>dl4Vt^(K?*PRsFocTkYNk*S?|96?q6Jg7pmwk zWum6?R99M*vdXtF+t+ld*09VPTzzx56Jz>kl2PwRx*I{f5wy)N$S^6lF$I~bkn}4g zna6~>svF*ASbes1K)E5Y+CjlIePJFnI7WsQ<3}qV_)TBgvT+{ax9{KZ_)S0m)cZ~g zLS2JVHj@#TRemhVpb9;#IXX4}h3?_KvG2e5wP+Ih)i8s)=1Q?ftnjN-OaoGE6?xEu z7Gs!@qCIUiJ;BrLeux9tQ*4rb&Tzf%ylS_?cq^>&Ti_4I;Y-U4{~ry%rqD~vlpAP* z7ih2qhpVFAZCo(2v@3XS*Im{F!@#G((~(?XHTOpmL|R*iS6Zg15>QZQ;K99=4llg$ z19LZNKVc&9i?wy4f_s5w0?#zh<-t2A%qXX@oNe=52cc%yh1Ip(i<}sdavca6k#%9j z^{__M!x{?P)`7_M~Bpt=%0Rqq;@;%hZB zD4jeAn!MrXiV3xIv4IMwE6<%U#bBl=>aVm-D|CWZuaPm*832M+l+TknvUvJ}-3 z+NS))mLh$&oyxE^!K6}_ zA}s<~xZ?~B7Co3WU=k3g)+y2$EUmkQl&qR#TYQotLAGs*V{q1vlDkaMF4Hfec1*TF zW7Klqlc^$w1CCJPyHA+jl_=&UZ6iWg9%%ru&Ne}b*!Vih#VGQm5LXAB_*%{CLh?Fi zbxlnlUqz}Z^C*?;8b~{%Fd!IHB&ViV)>9<)N1Rh6&GuI}J{@Q1?NpPzY4*XWdlgMv z0S{*L=NC%v{bKsGsUS0W6sj1Ng#}nrI?8m55(f)L=5377^N-<{-j2*nHLwwJw`so} zH2a^H9f)x6Cy@3lru`fTD)WPkg=-}=tK{l|^d!RpZtu;o@44Sf@? zzMdS?iv^*+|GJsa{GM+{K=hDr)^SG*oF&z7MoEL49g-P7U#;6YqmW`Fu7<*QHCvlN zqkm>mAQeOXD0dP%Wb4cI2wO%_Sm8rC5P)GTlA6L``2c zp>T;*0MB=ZH7_%pN;%b{^V}BA1E67~JT0tNBDH^o_qk0ZVD~RlQN*SSf-Q5DiTFQ^K!*Eg8a>39Eufat}4c&2$2xB3rG^3v9c zNZp-Qj1%TOOhP()+9E4UO_7zQ24zLv%1G-rJY%;=r0>eQMbaPHTIIae+apxi4Y>(~ z)7O1DBT;k!Ti_-v#AtF?*VJeb5GfWSHPv@T8&n?w$=!2tR(&T=o_q)VQ(AGakKm~K zJi7^u37vj-3<-!7dE*+zQ_Z8e)lEQ#auS-h7UgXWg%rz(8VZZ;5gPX#4jQ3xUj}v* z8gF5S&XnCWk-jOCUDi#L;W>KK)R414C_-^Y&pJgKqKH-RZA&f5JK3q&M0_R@PjV9z zlT%ZP2*+}fnTcsG!6#!pn{3Y%2?X@hR;9NMCM2%#X*%!Y`2J(|LaM9Me#oMo^@{qz z1L0~3K2@lu>wOj;sgf zfQ-z}&$h&;a1g7U?{Qc3fV(G}hdYf!J0FJ234^>8njtSz1M-5+o=_&r58>w72I|3Y zpIVMk6i0&&|H~OqT^H}gKD(CTC-@hV8U1YAaV=eboD(EvVRA{xapT5Akvf&?>b+?p zlNnDbr|EQWy>$Pj`y-dg%%D(&A9$|%llu5sTKVH~@;m8aKDjb)YIXlnSl!6`T9qT_iGf#v>?%nBYmxZ?aZEo+58XA|LA%D{q4{E zJjnDOWx9moK8I2`15m(nznJ6HGd1XouWMy{pu$f|%%w__#OG4P>g@AYnx4&M_Uo^! zwcxw5c)B9s3^IK*LuWW-gD|SBL0(kv4&)`?c5}>dUdKzUEqSPxaf$Ve7J}r#FTCc= z;ooieUXTeM#nOt>${Z8UoA#!nm+}h}=?iTaojM!;$e8D~{Nd2Gtx&1yl`N%7eVHyo zQM(GQYPHajfYxU>_SsFO0 zn8wm6_9J~foQz{*wcVClECA+D=9o@!IjN4qF^HT7@@&;{HJ-o#Ek9-5C%r9Q;e5lg zx_UL~(@L6BpThg-JsfF@g+Q)_qopqKlJeT6~p81sHc{IwoU8JM)>hrlI zWCb{y>Sup?@MquZ{J^V}lm0xNtDv>9yu0hrzyABatCTi8mHDcEeDuDR0 zRcpdKXs?V1?RC$pwO36;U73QO$J~LW`*mywhP*0mVwe^_Y()@8^ou+uI(qNq@0Ybce8@9%~Z% zq0`i|8CVQmXMky?nDCK=_AT)8d@bdR~-@tUi zw8*raUvBLDB}E!2$Lu+>xtuSz-Cj^We11LS(hS$z;SmFXO80>KdyI~{&>Tly^pT_9 zkry}zC!Hd0S5TewuFDFobJ8h7$0(PV)pye2`cFuS+YFQP_Q&<>&_T$_>vMAd%*k`j z5%sc3Jum@-`I*O0PuxM2mejXhE^U4lhBbMAwH_O^bCS&^V^_iDuWG21~Fq z*&NN`pJgSjijo7ndRSWOdbEYAgCY$FRmY=^sSfu6Anl3g# zQzd!{E%mlO%!qh}%d6n>ap?6hT>cy`?}p2_VCgG$BHnMcMs=I*jI=@?*&KH)#W5!hJrowpl8|#Igr9u)BGN3lWV%3)=>j zy?Q9L!i3xmMJ!BEOjG@!h=s_62MS!#GDR#zC?;x1wkzt$he}h6A{HjxV;40PF|>Jh zOI2_8EL>jd;yG1oM;5GgM3ND|AEHD+q&Qcqpn9j>OYo`1dxHr`?mNDnSk|;Rn6%{@CebP{zOP!8jL@C+q+st& z?U5Jv<1}gH#l0e%U0$$VW42VBr8#A$O*AAk^GnObLst%~w-b}4sT8dd^L;H4g9xZR&l%Kl=%2(X0?%6Gnt^*tM*zy(s*sGcp_hP1=ZGo9G2+fb2gvLL|b=E|ohbmG% zz3mYi_bQGwLgQY+%TZ{&l}zk@)?RRj^xgjH?Q_W-IeG=HEP+HQ(*6n5Iz^g#V+|I@ z^i{9!Pd0?@dWz#_2R2Pc+JDf;^My?T=Sz)gk0izMA?svmj4U>AzE+@s!(^5Sil(ep05RU&;;#5>lllHoaetAuL1 zA!mV*g+p}GBHCZp6ps&#;LUZglgMx*Ep3r%wX zwL6AfB=Z5%Y07gH!Y{;kpD@4cpFAemi>_^b2w%9GUTdBpy}(i&go4PrNRw~-3DQVk z=M$u(`I%LmAWc&zvp~6~Oh8GjpCGM2;(UU%O`Ia^3S9|tMVvy{F{XF@Qy|@1Q!%{Q z3Wi749A| zZK+TB25IYc?$!hrXQR=N{F-U7w2ehD+h4S#Hjf4K83)0vco57FuUar`CEiQ16?Zrg zkPlafLs6xPh(6M!TJjEle$+tZQUgS;CggYZwS*}_??c79@N3}mV1o(y(P`Q5$vy-` z567a0|Fyuh{!H-1@lq4c1Wz0w&ip_5`))>>1XV!ohJ{1eTixT~{3K=XYrY`jb{ z|JAVf`S!?*dnkrRUfeS(9p%MaP@(q|4rzn&a{IG*pD$Jk)pkS90wGKLHzPcc$Pyl_ z1D(G8DtxSAlv?_1?rLIHlSoX0@~FWV^EqB(Gs5xe37;2# zW7~49EaJhFr_M3m#ZnTMtz>2o?a_mJFUbslX@<}8c*bYASjzK~dO%>`M$Cozy0MPJ z40 zQ`OA0X`!Z40X*Ls*1XJYD&>lKJ_U=x+?E_$78XEYs1!*f=7i8lT&IyI8i5dBv|57b z%Xp!e6`=RSvOeG+-7&rPp)TuR;7T(pa2c_DOE}Oo(C%_FFg3$ zMipQsX~!EGCWW5|EZo0agC#huz$`PphPu@bO;fBqTbj0% zJa!;wV7KPSD>}*U$8U>D$9J;nI5)vZQ#0vEDmEF5rDr13Gl{9>Of*V>nVe`M?PT?| z9cRk*UvJeL+oHt@clb25aTwpHIh?+RF(ds&+KCSwbe-LDNg>yy-EvHW)~x?9-VOYg zK$8=56ftI6h!m^F7MtoF{wWyic#|zP8;kF{)#x zp0W7Lwpf<;4XY7PEtwHhOZLJ$vFsuhyD@O$ZU1*y&2F_eY){80-Qhsj{JYg5)!VS$ ze|<%P8g*``%=8GkqRi$}ievQ{6vL5(GArZvKp0+;@gwZyPKcZ!* zH!$2>{cb$6bbDlh_mhU|!!t{r)fB**_JFptTcxLfeiTBOM<8@Qc-pR*G;Q(nfVER3 zJl2JPU)phEuEHF*5Hlh1a!JZ*T1=_BwHQw{pbwq>-#?gq9iPoaTM^MBqokP4BFF5j z%0fSzl1pr6zRYISIM!B~>71Pt7aUrrh%6afxAUVT)1Od$zlR|Ihq)(T31Ek|(Zlz5 z+R;V;n_9|HN$kM&&M57=crSL@zYISOlYhjFpT)|uBqU2RKb{scGB3?1SLVw?QAi3I zLB>eH^)jPrB3_tJ!5GAGq=4$?t0z`Us=^9j>~qA7dF; zmV%s!uvgVFrV5?WnEo0rztJt%blzP%*6`%fBpU0x`Ny@CnGPW*x5)d3OG})LMxgV- zwBN%##mPGM|K5FO2YgE5BRpq3q18|Abj58{ugS=gC-ur0U!{n1v`u_F(-L0*Kp$#_ zR!y=JrB?kyBU+WTUsy$WWZ+FrlN$m_-ZxbBuh=X|6w56IN3Uc-qF|-AiA~?tr6lZs zODd7Rfu~y`J&Vy}l+yF7hNNdPDy%|!C`(vVNzY=`KRVIB(j!b$OpG+?F&3;QuTo5b z+a^6jP_MdZlAyuUTOmPWlp?MOC>uPJPJ&)eQL|c#`3BWGUztkdO0;uBz|4fsjH{Ig zL^QUHSxu%nji2V6-APf*@N8~i!~7} z<{OzsJD|R;nZ=DN*J6Q!D>eMTzh%m`Scpt`&?^;3+Z(kt-hZtX2(unq<4c`z*X==Odm%@oQrJ&E~4r zhcvaSL`6^t3%ux4Aq0^B9WFC)$-w2EaJkPsH8HGG?(@=D2i|~Vw+7Ed6@@5zu1;6< z(G0u_E^v0$z_04fUeq^iLR32Lr%7&?n&D&>q|)(e`uwILre*fAbetkekw+M6sX4`B z7}{JCN;{J=HXi5MWHORY#;2y^6Ny+n8lRd zIy=$|LM#!6aD@X2u~8#LC~CDg2xVx>VsC1S$_mAD{F>UE(h7w|)TnboQ|-;rMkhDE zwBjBfrf}m+D-;_}*4~XTtx#m_Y53pWIyJRInNv5U@8q<5V+ut>|5Px!@ud~_u2j`k`DMRp6YET_fHF)c3ii1Q5NL%0FwasmwW; zN&u-5TZuB%;t86AW(9B?F$8vI;j_;P)yikRsMfn%C((SCR<+(ut6H}$6sCMM2N|?4 z#hWvB3Na6pkoyX&orKidx*(J@l#`JA^hwD11{By@JjSQJx461#4KS2{HagYN)_j&? z(NClJBhA&;_c>~7KHJDHNGI(3X=g#LsSUvC9y!!W@SRrbKMKo@g!@h%k4B-YWpV?J zyGQ<=x#aHl&u#$RMr^QNSyQPg;824iDN_X8)>0>Ig98DnD*+7s&V?Uq)u4a_1O0@D zHLgK1^t;y7pfDya%l?_<)#5z5xgB!e@EOfBMvh2siLz+~Iv*r$nHaSWG_`izCT0KZ zVygG!>fF|(%+eXq;fpj-2Ch;-CLY4o%HB7JW+}GrTbiZXYHRAlPleG;s4!~?il7U; z1WS>_*R)GR=;AT#aHh)hM@`g5m*yBLhR&hnc%Be7a zZENfs4KKI4J{O?e_kqcF77&i9}RXzrs86xDg< zcSK=!A+h(yH{PT?XuVU}d)+j?vG;oA&3s_>MRkCveoB_Mu%E@Nq00zYyqZ6ljf}zY zo?M+?>cl&pcKD7*=r(YuO!x$2dY59l&^9V`st2r&oZ12-O$M8y+&(sPdL1jRP}tE* zA5XdOldEl|n^^t1_mn&$W?1UsXw}8Sk+Zy3H zPAfbI8Wf)6{t=$z>%!wMJkg0HMKIY}c*wgHOL%P)o^Fm$v!x7_a~ly`AwJQGEM@JK zkwQb_LpoSZ@swtY4*_MpdJ2X3L}<$C7{tf?E`>I3pWl`d%4mAt))Lu?@Dy2sC_C?H zOm-p^6U;WrPK0I_{d|)hb2wMu0H@6N&C!)k>MJ$P;>yqbV2VP}75~lqr%ApFbYtXsY!dWToV@`t%OWSE49{ zR!}|2(Oa8q)Ym;oT2`g-AjK?2#C>gNHLGNoWvzdzPtd#t;J~wD_AT5X^ z(9%VN6};WQYP{7-WjiiW9y~L8zi01J^+k~IPR#9}RX0dG6qCpJlsqqRPE77$PwMEy zts)ixLb23>)OxrMwMOsPcNk(YTXD9edP1p8KC`s_a+X`w7RPnw#a1$I5Y+xTebP-&hgl9glqx*01 zMZkaH8!GBVl3lsEZ3r3$UthQ`HZL64F zVSR%udy!Hb;<77AdcI|VHjzN`Y+pv>71Ql%z zbwP9KitEsv)iBui!Ea)MENuD@Lgka-LAWfz1xkAM{R46HiY~#`#nlTsn;4hSl}%QU4I z2xwyubm&Si0QNi34VPX`^Arm4ooteunTkbbxXDCsm6|6XEcQ4f49)UK_n_kXm#o0`DT1&?6X_{_Iue_i ziq1r4;#^#D3hi7+UY&7&wRucecV}bQC8cy4&pX+0=Q_)+Ez9AJ#|TaGrDR6H1(=t? z1!~3jzZWhKk;^d5l83k3^k5_+&_(YY^dNcHPjSkj%h=-Lg|pi79&qQU*12=d6(XLJ zFnklajQp5fMl+SF%jT_dV8XZP)_k3)Z6~T81rOXvE?qZLQjNloCCa3DeBF`NAF(;I zI_H}lib;to$`nw}iM-DAYQabGv|cgKOROx2xe0P-V_#E{jRP;#4qPElL=#SmzQIYs zF&-P8q&Rq^I_`~U6q*)~h2{pdC|u}uD8c;*k=#HOuEpU(95;Zxy;e1;{@wap-FO$~ z_&vSk(%C~Uy}0hxAsHMVc7YdsGx5V8ZT=hqks_oBZ$Zk$##r<0sTSSnz$mR2D*=$) zr`k@E+3~uQCr`cu{wb}vhjbK#;Z={eD5|eM^jL+U<96)z4Q6yM_@Cm79w`3D&7-)3 zU$8OeBs3HO^0h{x>;Z*;(jK9anj$pC28CvrT4<1~@-~N#^j&B>-90#THJPM^4juEL zl?|;Xw&RO5T$85-%u12=$1R^P6tU?&ZfR!P=X#HTnDn6c?rwwLBOuMJUhk1s*!DXM z+){#K`D=|So;dD-;vZ`s#jVbQle|p$9(yw}57_%$d*o%ZDe^Ma7C+%*cB@4Cj;>oJ z{gI|xB~;rDISYg=iFLBHh%7ecx4;Z)@Y>&{Cnj$pr32Dwk+^*op-V3X>1&57^Rjz+r9W~SXsUj zE)FZpPDY{olko6Xik=Fz%g__a!p=k_$;LQ#W+s{DBC#npor=ez(|jV9icKbFQf!l# zp<^C6IS^ZeW$2ipd)-2rBIw0}d!RY5Yky;IGkMqD|7u@4Wl#XSwTYrMnyX0ScVk{M zE7L3>0-iR#S;x5{FFB^z(P7P)P8Uph$>mbgm3tj^Q2^bm&}}-f=gzcU4r&vnL&(W3 zVtlx?S=;$Z^R7lkG^%9@o3iUlR)54<$&yRgIUtl!87uTNtIpjvlSu50PH;2nB+DhI zr;{@=ekPKNOeT|wsZ=taicX}`MwM%NHRk?m^YC9cm$)OZDBSP1K;dpEta>*4ONAqMTt*B`?TO4y%;;*m_En;gMzs9yiBnp|g@@J@>DLWZCpW z_qh=H8v6CBTunXB+V^g_Y+BnIKLK>T-b^lCWAN#F6ovx6J`L|)*~I8YF1xh8Q_0NS z?mLyXC$kfjsb`8+FqOZ^PEahPw#mGc`BR21%KI1j6cmfu{JGWiTeE^imUCaFRaEpt zI_f=Cu*e@D>Q9%Vr9jB zsDP)j5e@NO-8wm9x~A>?LP1@V>pY=p+RE}mV{3BQ+NKXs2~$^%LJKBgsw}71mt8Ev zWLb9U{4BW)eF`pTfmm&T3!d2YFXYnyq*cncWik{s_YFd3deuMG-(}{EtJPwLum`)i zo+H%F{lwN0g)%{e~_DRT9$GX+J^bXQVp{X_Lym!^oSzC8f9uz)yZynvK z$h)N^J!#ur-0c)?aYg#72_A!!Q^p=3m(hQOO9C!00E-Nd4E+zdIGiPrOi>&lV=&H) z>D`KtSTihT=eZTfkh~BdIBh5hDYgXqIK(9^ts8Z_w;$ngK)#C8bIK*?SVj>m#< zoclQ~Yo~aDiHhL+^)vFM!Lbpp*X|E?=veVq|DtKYF4AO3{XYQ&4jX zI^mJt&({Ggj5pvwf}#81vaz6^PcXJvP!I6ILm-E8h-m=s?B}X;rWgLgWx~x+Di!OJ zkByMa@Pj}cI1yxQPd)!lZ>0l{APIk%T)I)IB;f)?9D=Vy->Csg*Ts8L0=kyrC-?^o zo*_tm2;o9*kho7<50Jn8`{8RB@z{5eKlt3Eey-#LH=aECYmGNP!YF^4Dpo(nSpJrW zt#=e?9@v}zK7QSMz&Hp{A=;k-dDX5Z{!02C-Yzc|CoTOS-hqE??7Q-N?`ke zqk!_0jl0rSES`c`+(Q0smYFlugxqmfmL(xsBA?1aQAi3IL0&=p!ZU1YiCy5wwHk!> zXU^8&FOms7FG7Z}{jsz1k|+}=m*;9Xrju4?PSu-rZg7j<3}kJxlAPspvi-Sp^@fC; zAPa0}yv$}wPA%$B)@{+yv&73KDOUr+t@?9Xe51yr8Zqi1`gA3avMw$3-|EpqBWoSf zpKU~Mt|&{a;Dn3Q^gH8ALXN9T_&VKH2Uq8jvz*XrdulsdEQ%>Xc`iqu8s`LwbnnKy zwFg;|nSuQo((IO5fn6XSgGj;B67wxkY^HF1XwPmk+?bU%&+(37>>^#%#{T zG+&{}&4ahj#CJEC-=#=7Y^n=YZ{fci5rp~tr|fM_44|>v$R_>Ap#BR~l`9gl8*S3- z;qo@P$m9}$plc5-Zgl?~K5YO^4BDv^K6U&jI0`R?%K}{B46MLuTn6C+ zRa<+2$$DU)XV3qHH=vKsr{Dswg<&UL4_J@z7INtV=cEe;GXNMFo+p=)QTT+X(}IN_ zy}cPIeg&c!Ea3GrGg@3^IdOTMh#sHeL75h-8mN@b5RuD>5~^A;M}C{HZW+-{I?>)` zfnOdcQpcf0&nJoIT9CvN(KJQUCa4ve2~5b$Ih-P#<2)zGq*5t@n--5&g5DlW+7|*eG~j{uob8=3Fsjfj?N%v zSbsmA5trxFLXOSYP%x-J1!mLu)2IQLk}RZ2yT+eSwIFX%l!W7APG$jxQKF$O^jG=F zNc;#HC70&ZPRMg?k$_$cK1s~*HX0ZZpga|?K_-sp^Ye=Wbg<&yN}@(9ESjTvJS)h( zm6-b@)2<|)Bu@|%CzIjVYGl&2ktvz!BFHD=P^*n8*ER^X!bA*siEsj6tC@(gnbS-x zoMlssL`Lk$bL@?9P)`!H1uNBrIa-5LGHlMh1Hvdwt%fID-%jA$jz7ilCxiGAU77(2 zbl^V62Bwv^iI1d^Y~0$dp=~6H%9W*}dDKn~#A~;$bbGqqwtD6&Y7aZnoSKN$ZhcBY zx8)~`%pN#UKg?EA38)@R3@e()^Rclg%Q&rE<8)WU_c1Mho;?@Za+>Y(8PE)#XhRF^zfZKg#iH{sVq&>PI#aVx~=x3uY2A3TgXP`JGmpvM!DF^bv!qk?+Y(>(Xz zK;zE*BPVtK7z8vd=H$+*6`b>p|Bz;o;|#vG2LYNq|B@skRx1DZYk1Y!G)w6?!x_hp zoWwIT8jX`axFkH2+F=eHx#~OAQ^?#RxGS-$Gux`U;uZT5@eM@^ewMrMhXge9?A@Sr zvsZi4ajvW>r4nv8zaDO?dS`sj;UmX&UWYD)z8Cu&820O~sI1vB;U;go^_Rncwn%db z{JY*%b?I~4>j&CCf`yZIF&eU1NVM&6`~R>>>V|CSw?(2?N`vM^0DI#L*tZrBx z>)_p?%C6wolvu)3SmvN1-~Zg|sZa&)CmzWgOg0?BkIp)*&CAd-V7K41cY)rCvO^Y5 z2ecv-`P9$s@HXIhUfs&5T5&@BT#cb!v-Ymp4E1vlSpMCx&4A~QUCZ-m2j&Wg6ACh? zP1s(mINIp5d}!MIRiL3Q{T`d6mO+L9%eBKLx^t#{uVb=mU*`7kiXjhYB7-ddNj~N! z6;);5e=~^9X*j%Gpuw6Yb6T=9>(+b068DZ~{yFxRwNx~Pr{F%T%fZJL8}}%<$yOz9 zW#PYZc+JK<1y}Jk%!xOW1m?YNIKc9G5->RO)eo4nwcmXFai4@Q$395~Ry{t33}=BF zxz;6H7$00#c=EwW;PWAl6AEI_nB$Ta_B3v+z0=vE$aP1cQC*3*p`}d8;edXGeI-kP zIm-@tHlwn;fdQ|>9(Enp2#^v_E11b{?7w?qF;6qkKGAoIevPix2^W$D+z&o&5v*|T z+|?Ks_~`kDl*U8>o1#6BC0{VR-D8*Fcd!-YX`a)>F73Q0mBX$zaVsaY&W>M(M%;7H zGh}QKYju>d*|VB^%T0wlhaX%Nc%aXsn>l{+Lt5B<2ru;O}N!rs6&J>Q;x5dCnScgb6( z7ay-!+nlIoGw=HjZ0L9BE1%iyBLxm^W=q%+p@MJvUaq ziH)H%`9P5Kft!mJp2sd=byBFTXiBtyafC16ti&~G)=8y`n zwmXV_aCpxcaJFJWx%nrH2>Y1Sqo0KgPjoV#4QxminP9=;#5>esyeS)jWA z(|?8lZ+4FAR*#++E({E-#hHM7MkWyk#1SdTa-btpPyy0`DFNQBY#>QSAhZP1ir~{# E08GY9S^xk5 diff --git a/design/PaPI.asta b/design/PaPI.asta deleted file mode 100644 index 3487f173664c938d0f1379633805e3308ef5b0ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 74250 zcmYhBQ*@+l*R5lAY}>YNr(!1^+qRRAZQC|FwrzEij_q{t*Zb|0T?aLuldAFDTKAmS zn#ywE5a=KgwQZcIgN0i$C)8Et_U4)3%M*1_~OE z%zWNvYJ2F`9%BoS6-ChSa{YX*(beYTvnXSfV8@(`Ey@C07-~-(lpu_nL`3QY0uCFB z3RcJ+YHclwK=EN={%`ww=W^HJfapU8b7yN~CzI=8_a&94y-zMx^#(xU6vS)oWs%FJ zGd4QpW-xkkxbCU1$xgrO7FI0Csn!l=pvjCv+21Dhv9R~`QQAhd+jjh0rK%_w_d)>% z3=aj6d8kb?D?v6@2_T_Fl*!3A@w4e<-cpSH!6xHZh-jVjwV+6HF6XH&QZnl5$|4}| z7x@d~d@T|T3wHXtVAPeux8~!Bm61_0OfV6bE>%8KufiV(4XMt^xZt$IQw>?0$iOqD z&Jmh;R&kU%iZ`W5Hcf%wU6=1ewxh;Ep#u* z?0v?eLHQO9^eAwL= zm8_#^SOWo~^oP*jC?I^i6)2#bgK+YZ7Z&2o{zhyOuF69Yt8;KQ4o;B>Qza;O*cZn! zkRrKRGZ!u7S&^^G16^T(wQd(0tUoykO?nzM`Vf4b*8c_kzTjCtx?9+dd?ZnRQQvnU zAv`jkys$w1O?W5l5Boak?LE=EB`EaKqhc)%&5`y?yoi*n%FrtQaXhx_u|cFPA{y@g z2C$0^4M%TmVmmx0+2hCByNMS&ED>}-S|obc=#ThATp2PkYp1cNc^9u1dZi}4S{tcK zyI#H7ABBw7=AZHYXqOwxEv`mn(8B0@AcSUPflf8aP^wOYihyRb6XI%c>X{izzj&f` z(-X(IX9a*%7P^8Tvr;>A4`M^N5AlZZcJQF!%Sm_%Df^t;5(^JwAl+;-!;=&foF79) zi&x$h478vIA;MF`ZweC1 zc}0XPfSMD{Nk~3Ov4gbe7n@a;*#24qrEZSi@vml4q(j(7qbLIb)d;BJuP6-^IAPy}OhF@Kf))SpqZ7xY@mI7M|JWZLhj`TRNWzf->c5 zq<$)tG)iI0;V@>x*)7>?Rzdu>IA+sh7k_;u>Yyzo> zLPG0kM`1|I(+^9BiFCjvtRN2&oKci5bHAuwr_Ta(7N0ijncz}YjtQ2Pi8)EL(Iu>* z&ZL*%S+UK;{P59`Ga95h5`OqP+PY+?+u7RKSl_+*P0J%~v749|J78{c4^1_kK?NZM z3(rha(^Sb;u+bKH<&gz*<**mw#746!Ew52cfE%Hjsqh6^H$2c(_!)e@Wa|(9{fC+9 zTPTZDicrT4HZb~C5I1JdzW#8D*~otF+a#C@guL27#T+|Kj$;~ohjV*CXmawWw^tm&Ht?gOdBmh}_w zX=y8#zuN_PpV(@Ob$7oaHng`+P!Os%`*bW>?{P0gPukV;f$UDv%-W5%=3x+}(P%jz zHWGVHYk9z(8`k$Hk$s-NECQ&}e)OzTBp|Z#lYV|nKriRPkz{_Q{~3Nj3Io4F?mKg( ze|;(7c*zfo^YiV_skvVh6M!L-1`jbJq2|SOf)*mY(X311IZmpfs!Cc`~Jc*PRcr01oqpttx&XIj-)^b+@?715sI8KH%`we0^Dbc{4VJRS zZhnVrr!cJB^E`KpNKxS^kYWdyzDQ&bA6K5(jhR|6DIoaZLBOs9;oX3thxLuYUS_e? z!>l_eeho$bL%K8BoTZ?O0Tph=hmUt(>pX1pdWd|BU^yCiL&u3l*_tH+QLhS`M=};C zyU=?3a;a9!0BT6ec3Ucl8=abS_q0Y-SP#1lGeaaU_V}Uc!Tc8X=gM-Touu0dLLj^)j*Q;$;t}<(RbJ z@b+8qXbt!4DQ=KvV%kj#-1G4C2`Q$l;-^aGHc##&`x#`281%8`VG6n5?&m4fJAEZ7 zOBIBmjzxoUD8DvK0<->m&zr*jbv$_O9yUA$n4T#a^vK}U=%LP?OvNkts|$^ht{hay z0jWGpVSzzGXCbt0ckzo43hA_rSC{!xxInpl4Us-vZb+j%H3UMGrT7xmel8o#t`|SD z2WMr#!$))drl8Hn+Z^>LUwddnrj5Fo`ga$J15;$3-_=AuS+=n}Q!vA)pNr5^LANRh z*7OqR)!T4B}L!8JWGSD1= z5cAJyj9Nb0U>x3r>n&k~XiAVkX~U3`eyA$avd{AZN=uOBd+yh&x1El^1@nsqP6aF3 z7NG(VQNjh7e67B%PH^&5Xm!2r*EUNA0T8aB4KP}LMC-7m9QoVpVTps5;+=p##U!zL zj^iK*vezaj(DXs9K(1IRd-Qpdt}HqKzl8;}i`*EMBO5rUNu+`1iR?WG7sUj?{qz|# zMU)HKuMgpj78suXu*z^9RM&3XJY=IR2F?uABM}0%uE3KKV|_X9l`G~Jo=>T(n%)3T zJ#kmZ@UQ1Qy;=`61;5IKM4;4w1XxrqEW0jo)d%G>jAnz;G+s|_J?dP~`{Jl7h3jl! z2`aoQ^LxF}zk!A;XvUoyVj40snjY_5x^c?iLCTz>r(|1h&MU)Qn6Y}lmX8pmVe$3h zCG&~xW2k!Or|+pm3zHR&?Yh~#z;~4R2o|KFLmEGBt^)W!Qf?HwP$slFvO@u9+^mK* z>;;`I43Uyz^k~{Ozm42wh3h*Rkm-vQvrJc{u0t11p|2iMAM@?%BBfOw&>ZB%-Oun7 zcgy}biPscbXkyNb5r%L@>qwH>m|oK0`0sJtd^}GSQeUf&j9@*1x+Fhv z3K>O&Wx%JyJ9g6~cuw6V1Fu0Y};v+SIF^6dl1z!>&G>~H8u?WGQ%J3dq6wiaHwg0>7X z(Au}8U3Me4D2gAq?a`w1fe%f8p3jg66(~8*4%!G5=~!Xq=kh&Q^8~)neN+av*wi5c zZHM0{l@HkoR#yg1&3S#NL?~l}-?ESn_HI{YOJl34FY~n(!7=s3@@YRyPG}&VWRvU@ zVebPJSkd(v$b%+6V(=1i89nx7=hG3&$WD=^$i`PqiKqQJBrO6-eD)II4L?u9!3`ys zO7@R9z3k7ut+}xbDIy0fiQ(vnpVY$op_I#-X!C9#3qVk7GEL)m9dfQzS{AcY@h;^z z*|2B}Wz{yB#qrVP<~79?>tuie={kuApFvV!ftff?+Erd#ZrAsg5SrPp4E*gZ@WTk*?+=}KC(&HZqe zUI(lUWf4#U1-7k}t_kB$U4QNZ5UAXdT&rNBEdjIgR_kT8=ynA1J@ zR9HMLd)TfP6JA&z36YP%sAPR5`*r(Gp*2tPMJEJ(23k`fDnkiDbC%`wK6DxJ9AIj8 zwk2k$nFjv@U>jqzkbMfO^97{{h)pCw^_nWsnU|I^cG#v=D4y>Hlxew6>0NBFQ)WTM zEokpR+^H;Ovcr)9=A;LiUS{{5RBS9;JshJJXLt8i_nDfRGge9GwMJFS&zZAna0 z6AD}&_Nvq?{CkzUY4Nm&I@XKs(wk0wX()*!tEzleIz(NXp1?VY8Ngrre0##1`~1{` ziS%W} zvXML`#^n+c(p4;FOj`5d!vftx%p;KmOkrQ!9SfynL+FGrf^*_iS7i!u>PgkFEkvC* z?!C2-i$licoP~`O!ppHKWahr*Npr{_l;e6*l)br%QbFGD&Bf!BjhRSww|pfA*t(V|DNj@;of*BL-aYrb~Jb*!e=cfxj9CXXA_UXyS+Ll-0)U!?n2yo>5 zdXeZ6A;JAc`G-I))wNz=kxb=kLorWx=pMEM-^m@C1FVN@AT&js)%I@!VwL2SpEcS+ zRmp@;1za?~67wQzk)t!pWQBD;He7Pm<{T~~nEV_JKCC6BG~3x?V;Bh~VMKHXWXB`@ z^yr~H2+R%`UkrR0HWG2+vfXDF#vX5?rlHUJ(2&ghtfjfJ~SM8p%xa z#Z}PX^ll# zYA+T(x#zCK!w|Vn*sm#fOZm6-Hw87uCefMSnRf2@VT-K#@o(m{v{{5`l{8Tf7YI$e z*ReoX`IBpqC+k;-RYz6Jbgd%>ADy;?HF^szf#{{kC&BJ)2(%gB zJ^BWD@>WVPwgdw;bV-|f6=W>>njXWRd~gqOVi;11w)t}pl4;zGT1KpDio3g=uR9h7 zjsy~Y{#>401<2+v06Tnss@5-3rLz*YQjd2tDmNoSpR4>l1DM}IfC5Q60nvkxPR-x| zp_2C9BIjLcmeG9K`0-+{zZV{KUnNf%VV`uPuV`C#Hf368zP-FxUr^KNbh-HwA2KRA zt9Rv#Bngs)0p11#)Nc!Up9Lf z-{ODul14hXk_Oj-R!<5Uz2zLn^Nm(N%?h11(Ot4`T`tWG_W;xMTlY8}d|c60HfA0Q zSyEEcEhDNF0X{icpD7P+d!c z){83DJw7YIV#cYgYCd0P8(d(-yS$wa#Zg(W{L8gs zV+OV4SHe+uSk|}7+c5}-&vAb;XCSj|QXYv?-#WGWG&)DOb}+TyuChysrqRJs^ynre zY+pWx{K;Lw8KXv8#!reTLKF@6=8@+PIKUgLQ6qDnvTX)a~ zR&WUG#Dmkd6qR{tCj>eZ(6t8VeDE4Q{}a0vk6lR*p~AK$>BgW`O@iBgKIo6UznoR| ztSZY6yIuQ>H;f%Ah)BRwmojKXv3IEvxhn#kdHIOcxw4^2zY1}k8u`bBpU2tuKaZhl z`*J)pd?+L>!C{ImRWZO-My4BCMfNpM=7v%lRh}6EyIs#65ZOiwfCmOmfNFd^@A=+@ zG8=$Dk5_yqj=$h<_A}rC$7b0V-r!3%CTnhB>rGsNm3eu;m8t@hT}5xI9PY<}jsUCK zXAStW9?QR+-8N0jgA0+;T*)Ye<({jBH##P(;x};hwEwP;`rlV~HU@(EnDy%LxHMsz zuS5>y>ok;_1R~1wBABV0xhTwHexb9+Z1$jk|It?CgP7|f_FJ1k1FzCJ&|k>OWoj8< z<`wlBIa6<674!Od$?m>&SI5HzIV!1XIIHhyDC{g=U6;O!>GQ7M3^_7(2sHt2SX$rq za5YG1pf(lt9oq{xC!kvSjYu^itNrhqo^RkR4ZmR>EMRNc1p+bDs8_Eop?V-79TLaO zzZ8HKqvLNJrE#5?NH+nu7vUuTboFox7y+Yi#FU&1u}$W5q(uV5AfW})MSoP0zC%{REzSlUPzjuibxm4QV#Boor`fTJ(nn}H z{qL)+lbCB=Lxz~R@$e8=yy3T&nWg7WF^-0SC%x_m#n%!2`b%zW85(u_3u37yRlI{0 zN0m%$hO+r_7%L9#+J0|sM#?Vpuys#)u0`nNzf`tbN=GV^6N3s^{wF!zelJ-w`?TQq zqo^7Dp|#B|Qbe3~OG>{k0W7k}l+(om@1}&83nKrXPZ?I9Pl@)AHH>{8-8}j7pQ$r5 zWqJ}OAbnceGxvRAS=lVp!F?Xr$pk-?u3{h0kd|cmV$t!QPh;Wbu0JpAyi;WUF#?-Y9$0l`FmPqfQtc1-?ud>gG^@66mg;IuHMYEXtyHoN-XK;X zkDRoWpS*bLDxeM(IW9(-v=?hFg`t3!9rj}yFUoGKXYq}qg?Ca8GZbdSf<{gFm!%qk z8cYHh6`Rh^vLd^ksFTwKXPBZQ3DuK<`&(N%uR6ao#l))1Byi=1z^5v@7W0+Ss_Fn! z7UGnA^6I+D%gT3-N1R+wk(eUPl*e5A6%5IN#?~d4lQVPzzcAa&cv2`zv6QxgJP;*=y6P^mJu}Ps zof&36RpU@RmS zxT_gsWNIP1D*-7eNSy%}6VCycC-XM=3>LD>)pr2xX+IkezKUt6U-#xwl zfq6l))Bm{)#e#=~!4Z<(n5=P6bf@s!PT2Wg9rBhu6(`AuR%D_FO@&kmD zCzi&HhA%{FGMJ@OgE3My=`nMW=<%=45m-r=zdJ|r*s|mFTZiaAoZ^};f;ZJIj{oCVT5$g&FkdxPLgv+RcE8Rf z`Hoff@dQa!o;$XO{cC@fcXId=TTm9`z3UrJm75EMXGy<6rH{@pKtW*{U4tH-U*SCZ zye%j`@%eTjG{N%@nx$ul9Zy;e=o+F#95?Zx8Z#5`;2h!3dRG1YtsCeP3{V+zZj`A2 z@jEYH4*eZc{sJU;vh&359pbyrd%Hhf81q1YcaDA7aX+!kMJPV=mo2nZ5!QEmrZ#sR z;p=RxGan0_mlh+ZS?qUI`b{~PLpjDSE)A^%($>>D;f;>BT>GRhSt;>P9;KE{Beq#R zHwcjd_A?x`r?TXU%B1CTNI5VQuwQ*|742F5a7;>f>)R_eihld1gpbfr$0Ff?q{pUs za?vbRqQECL;?Mw@#aU7}>73Zt$8!GVCQHat?Aq8c6;V{)CxIy|@~X>v^ZIIzseu$C zljE}AR?MVb@FwQzA5tS7&gpFzWsB35bPmk|<+ES}<)^#}AOy`knX`zzv_7S@S{bjd z$YN5NMb@{nzjBu0aA{;NV{vX*5wPuCFNBHq;l`&4Qx;{=1!R75Dgrif9{oO_^5)@P z?{%yvk8gH=0QLDaVphmyAA2j>sEoz6h0=wpOC^72)Fcu8* zf*QE~(|L{>92_UGlT9v>h?xzmI3tWZ)K(t-hxq!6e2Q?IgKzc*cG8elEoU$3-pegwMQmzYkU$?#GU_1wZ|MOfCdqY>h{io?{B%C+3Y`30Mc$KHm3^}d`S z>o!KIrag_eR^xER1ImB!$Pz7%{~EG!J=LZi&;3idJvUtV7A@(Rv>WBeuB1%>xkT0j zS7s8!R}FK7?OdlF1kZkG9lZk*_Cr&>ta$VYZk^)1bsmin=GeY+$!imH_g-S4}V8J4P(Ch+_s5X5mRUIIth1)@WFrfWiS%nN+_`ub;P)?8wcSs78+L=ZZZNeMlp&-5+g39$UwPE z5~H{{FK&tVN<3kpon#_Rfbk6(renjt{2Oh>G(=&u&p$FUxh>8SOJ--ZLwF|@#=I%q z(8e|H6;c2~PkCh-8>N+BQbKLoZqz!2uA%T(F00>~%TUciRLwUNI#S;l&v=STSL{;7 zGXRcd`0qb?7||p0S*%WwMn41wd#h(0L}GVgYxR#a9kaiEiN@f@W>B!jj=Pg7=(~9d zgIj0~9}0pbZS`xlwO|Cm0f_uT;AzRtm@J5(n)&FXA)A%NgK6UG;nkf%wYba8*rzJ* zY+xwJLRE`EkR(|Toj}V~&v>=o090u(RdeNjFpubCFN5Z_U8wNLuB_dkA4M+a-a^*; zzI!<_w>y{^=yCddZcJRIAH0*R+6#R#8z2`~K<=fCZ}xD<6je11-39Yx3$F}PVF%|2Z2#drRx2+LcGp+4uO;~oeCzfP^p7b^d^{f|4R z#|)Or%(18WlEl8^2)TeT$aC8~;bFDNcjLj6;Qtq4oV;l^2`HS8C2+D-VrTTBkzUE-co)(LyJe# zrM_r`j0p5_-h_fsH}Tje6cilCgqc7C*@G-9UB*xA5O@Po>Vws+xF#Qh$m+}Z`hO9N ziZRCE)TU5wWeqi|bF8%moHuaA=#&}g&z&451OFLaS1|OKvYp(f_ShfPgl!;EHSQj% zs&tP@pq<2uGLUex*{?nEn&nb=&OdbXvd5=zXn7z8=>(Ct_a%1$A?a9%lT&f~Xl$Ni zcoo{GyjIrd6w>NtTV3kq-_{=)1Y8i1di7BD7eC zFwgfhcqOYfC^(7w1D+vz;sJS*oIGgK37mFzWE(ARr$X{0V9b+N$~?M>+-ih3546X@ z+!Qi5p8BM=Tz^RrKJn>`{241nqTTclD&i4V7-GUr z7PzUJEE`<4LmL!W=|s*$Qge_f@1xii~#Wfhm`1{7`l6%$%`|G_WU#btK=??-oK z(8RnFzD%i8y0DepZejg6euFsUZKG??HED(pC}9nL7CaV}Aoaw)6S%WTj`1(Ocg7o1 z{fm4g76Q7Pge@EkwBOWv4`%));%22Lk?gi9@Ga;fNSNls7T_X^yT(4n+l`T&8s@a< z$?;F%Q>yY7X)!Nw^w{#J)UNDUBlrA|g+zRipOcm5_5b)sqEk~H(xL>9VA+#t^1aRv zvqJbEO+xvJs&pEXX4?dJwM(XMk#E%)7ogxh+cM(7%H-8&lWdKoP!lFcx;TAsVYc&n zTD@}UiRIQ>%hTg0N9rb}j`^FZm5!aU9+Q6q;f{YW>wYswlkYIzhpdS}k!sBE>pwhd ztJXDAIdN{Uj?#_I5LJx5KYPh(T`y+NM$J5O{GFT+T1(X?ysdv=87@tQiOGlB#lNm& zQ-@w2v2o;LOf(U7^Zem(Q4z9VdPm`xLoTOjY1Hdv$j-wD(eK#)*gt`^O*lI!0z*rR zKaE>E)3@>@_8LS>^Dn@=I?ysc85`QY;Jub~#dmen>qQb0qN9B=zN@N+E&x(*;vZ^i2jDx*-^j zfR|Z}wk~lYv1z6tJL_ zGI5<}!A|X7YeXPd9x2fgWHoSpN)ZTbXd}&g$$jpUuvoC|LFh>CG|bGC2d~yKmZ&#;en^& z!G9F#FTpVZ6qAm$$;>!-byhu+K@0j4PF5MA*2;&i@k45Q(Se#*Cm$fZjyWi(=OkLQ|YIqk}N5rk|Y;zF{09whaA0d2) zBzj5TI?-$aPKNQ2vR#JTb%;Z4!AQoaF1W<;)$}dK8vV8i74&~p zP2|Ula-?aa*Z-(mmRX_lp>8;DmAf=AXYQiO_~4p4R%X2SqVgCSZ90A^uV_<=^$=9C zWg+4QU`3Rm2X%=#+CK0vmgeJSEq(h zjt=EEB+|ax&vObD9E|7;uRb6jxV3^yIxi=!WDec2VH+hQ#VNzECiz2h0c1_n#z+yW zA99ANY=Q^v15oHw4A+ilBDy>^s^9__73BtR3oSpudBnQ@?nM_+d#>gQi+nhGp&e5` zfUv0K4bA{OwyPb9por{a_HxkwoxAP9*gB&?;ySsqhT!v6L4`$vV>KdWAR78J`fwAw zb2q1Y3hE}uA0rxr#|Cx%{c;a}1{&Ke88y0r5cIxOSJd8OmBXP77mTN`Mxx^yo#UT$I@VushB5JV11|?{Mira0G!3G3 z2`-KLP!AQcvz0NkEhL8E+S06*nmQt#&_CK$Rw%Od=FJCPSi1u%E~lm1&H}@8O&p2c zT}mf|56w{%8tfS~xDjk1T{hHZLSqJp}5ead5QBx>>}-s#XqtqYMf7fjRP zPbXyhL&eJI;_)Ysjl~^W@|ChVkc`j?RZM~DipEm=@wc2e(BWYy(XX$rkvFWOOTyta~6I1OX1$WOTH(h08F z3D`#7=a^yh@7~LN)XtW?QuK@yi!+UI+q>$o3ft*fA5$C)>tu;rK3i z2Pi}bTSQxZ4Xh*JZ(a$T+8hpqo<=`QDsqNlB)gQ$7(phwpDUB|hh%uXS$?0U{Ot~* zX3f#jx{5dF1JR8{Qci-}@9sjhJY#%ckzctnHyQ4+8+@|G*+nOfMJEnNT8e)yLq1FD z3uf`vlHvUEofSd%;;CBAvVF;vx1->k>4Xo}Saa`g9C8QhnLXD#5GfVMbbFmjI6&iR zKtHtYhBe(?1|#wjIPME_|CjsF6Hxh|CgeKw9QYT)Z9xGyyAdro$U~&<2(*x$OvGagT3c`rg?rcx? zp)s7x@6ni?a}U>E$-sC^@)I;7u0p&=uZYY4UcLi!4y1%}-$ZV>cICIQ(&m^HZ1U;D z+9c2yvj`II{YLyKp8K~^Fvv_MaeVpV<9@a2maVw(Gi7l9@W*QHAw0e-xQGZen{yS> z3`{JkRD%a$eTVLB;T;lf2)=M}K*aLB%Zj!MwnT_z;Xr?m{5-XDtwc#$JZp&L6}BAI zc4%|}pV%)DESB5~)z7$Y&$kX7gLA+#K~D?fOrKZ`dEcCIC<=Ouo@ycsyAlkKJ+Tbh zfUi3%#&ZR(bb2Q@x57_^|7cM6V+zNVA%4jmLv<>=F}gxnE`Q)xbiyz_rxmN6RL_Nn z!o!&huev!N4Q=xp{pYE}$?Q@kT#O2~qm`Aj&2!2s8xRCB?HA>?sqH{BVHMGqDg<+( z$b%lFGFQ2TdPyHACD(=?#Y}B%=21z?Ce&mG5BI<*}_H2em-3>g|Uk z)Q1b`Dz33TBy#mX{EG0Cxg7r+3zM^ErY*X5v|?mF>6!c;N6#Upv*v7Q^l%&+S_?d> zFXyvQnOxRE>ac-^aO@!ODRnNO#rQ+qSixy!F*`#l?f-D@OF4G?lr&jDZyaF}cJ!a1 z_mHkyHNQk=?`w}-c8=|l6uD27Ph>b_Y;r0?oHtL_G;OwMIoQs!Z;Skk7&vvyavexQ zzRq1^{)+M!Yyv7bk0s~$Cr8L#{$;=uLAe0WP4w?2?tuP&k)@(M*s%9v-3+7>Ygzi*RPYdF5gOK0;XW zL!zayflq^^=<7#O4Miu#f`eQVq(YLeH|{;n#O;OLQe|&xnV6O%?ab{@S8Yh9f7v|# zLteQF8Cqea>I8pCZT0!bhqV%ELxjkeZUum89ZM8+c_+2^(P_DBJs*S}|G|87g4ZoY zBL!phk;_ZGzAqt=*VAM!ez!ZUZs?ytDM5jS9FsRXT^up~1Y)w-=9b%YvuX3?de82C zWk#i>_ko4Nj#jMIK{%A2)$p8`t!Uj5ZBS`a8&uTfzU`$sEA-<0KFp-P3T#B)f|k$h z7lmCw2M23>CYXn~5I>~BA@w^2UYedAXh2zLjMY&<%3NYr7DE;z<99=4Df3qoDa7lu z#VRA77e4HO>~opcUWzM;;&5Ys6KJcbNENHrh66~^Y>((B3x zXfMg{YnPpNemQe~5pa$-A3xDIu3^>-E#Ki@=jvVdd@$7_)LZs8RVLGy&og7szf8VD5t9mMFPANc10I`DxQ7pYtNlR^uoqhRkQQylRbC19OZ95Xo|qa8}MT zEI+`PD06yTZ~9!zisS*mv3XC%c?`e1!s0jrC|xj%(aAd@$XhBMcZ8MaCz}&kW>|;V zKg3`q7Au}O9f)0)65ZK|9{x5$2w}mkcoi2`1X^T0S8Y4*C;8r_KT^1S%~#?HVoPL4 zo#3!L(u$SAHjvae?*8EQu1T`e8IFuW|76c~ET`V8`9R;f5ae*v_&O7{oWH%oVhCKk zAj6LS76Li$J2<8=F7O52oh4I7{j-PL<*>|0G$&x=g%E>=LsJJ@O0{{F z7Vf|Cdl$u?=W77ifpAsI>~LP|!wdxSI+cP;LB}b8;mo!y{BFs#c1#hJp)o6wA5lvB zbRQOJRiX-Z!Q(s3vgv;pQUQjt`NfAPxD(&3l0v;fHRSN0W$Ub{`ZESnspMZtqK(5B z4^MR(Ez&6BV;2w2ZtOO*t^GpAZmA@WoI%ICCYjiJ_dh_6MOH#(xWTqDP59(%nH zL9k*PakAZ!A*hrn`Kcd1p7r3V%V^GdQizH!4>$@PPglZP+LAynqgtBjMI^qjZ3|%Z zye91`|LBP`{PqPAElCuGOVkbNLo&GKt#x5xiJIKtGZTa=OT%yIhI9t|iNfub)$&O9+wayQQDa0>lE&WGNWztmLl)XB0xfFk#duIB>H> zQSANxmzn>`EczfvIRN{jY#x*1TfE*P9IuNw>iAF2@9b#i!XW!!HZJeo=79pzbr`Hf zjA)MXN5Rdx7YCW;!BHPg42O!^Xq;u1AQiJ-c`LK5h&e43q~U>gPysPZ_?h^ffm$~E zEk|>A#E;+4rYHNo#rI>NlfRr~1WnwiCc)v0EfEQu*ZQ*`5eY5*iXzpTvV@OFiyBxI z!0u2y&>~+lDUpdhVEof|^=V1H@pPjiKRfeG9$;8V&0XA)MMO>tl|)ac2~h-QAlgtf z8Go;Vk{STxbyN9u1!C}Neot`CxSUJUB$C5^d;cyM;=jb8?H93a3M^w{<38>b*j#ZD zi_sxadSmT*))0ZOY}AFrNp!E025aMpQ)Plf&msjWU1{6(6v-dtGew7%np=DXO*_|= z@jfS5fyhZVVl0MgDN>MMQ8CfTA(0^|BXkHQA2-C@Nx3#u?(v-qR-n5&9nxXTDtozC5xvg zDAJ4vT1nzI9%*F3X>}%Gb_Tjply?rT(B?hNx%OvMm~-`Ip!GQ?&4%s1@K|3`iJGq3 zFMw<3hypL7wrw(4b_nLvZt-2DrkHt^NU-Rf$U$E{(B7>FqRkQnO@yl4~g~LlgIC`814T^NwXNy9OKQ}m3AlqF6rYuPo(KB zZpOZ#A>eKI_?%lU+ZEcy<;Q?vM~|~ck;~I()g&=hw;qBl!=8J&9&)+m`4*p%kz!d$ zCr`MMkD?~Rg{w`;7^D_p*KveNe5keO_-C19BZygZ{JyUeD}rARTuLtZ>FhoV z9MXSwX#MVF+wZn%?#yn=<`@_^T$;x#r9x58im6mH;NlSae8lru()FnB;Z#vGAo~0> zvY_9hh+>>FC~P5j_+yPP9GGV~56y&Mx^m^$gj|4M$^+~QH?R@Z<3C*Di#Ja++E~cB z|JSz~P#h~L9ciH}!PmBB4I5X)B33D{I~kf9DdX-$jEnuL^q?UKP#4xD0#onfiniDA z=LJ0JE++;7lC%bL@_%gYEK53;5oSteUI?pwQF47h>2v}+b}GeM5ALdP4qTZJoTX^! zcl43g4(Al>Y0Uq{otR^->vhs4TQwmuJ`GwKa|6(GWQ+<1+h1oqK|Ygna)$1Z%7MLqR-{z!FrqBv*9s$NT zUHvuJ8Pvha_u49Q&v$xm(!K>190Wf91`zi9A^n$&Tz#{q`uLN~7T}@ZYjA=`qkIt4 zW5C!ek+Si&a`lb#((hNmF1g^{2_cX!b+vZ|k5MNF%+xG>JuE?tR;>cbrR}>>&ZTcn z701TLV}N?+5eU(8A}P=vMcJ#yDL^5bZGH)0Q#qd9hvOW!G&dBnXc&nzTKoSI215p%uORC15!Rs$R8NoE_Vc<4geA+37U164tWhb z!kAzSOK1NCCoUnf(=^;12gC7ph0`pZ?+A*=Fe=+qgMOkPJQgLUlk*jRPXRYK@OggF zy;mP1rSLK6p>j<9Q_*non#{>cy}mJD(k5gQCkoa`Cn$xlI1p0Xf2(YOgA=Kk%N!4Yo8(jOZD~3Kp*teuv#)ke4=cYGE*!r0+R_kA<^q#F4nA{8WKdqTf!e(O96iLT&snhZBiP#Wpx20fFWDXLy@n6j5@q^1k`!i(%9t zq)?N_>rZ_@81ye)ZP(X*s7@|m>Y00l_lh8!n>rpPCb-<3jacm2yJrf^$%a(HOg#6% zHOAG#F=Y@pC%C*9a^-rEdRR5c?D_Ty9DB%A7S6&(m0LIZNry0bSHEVjwjgO00ce*s zMM^2o@F7aN(@Rvhtzzt6;}uk?S;41z3R*2S-7{BUopk&2^%z1kb56 z5_UV-2kcT_(3w|We|m6yIja&490l^HpMXX9((8)%%{#$6MCUWXS(;rJ}L9E2Z=gFWm98akab*0`e~zvxH;{)~3UY z`c(Ko=sR#yoBjBz(&QS7@n!(4;NY~fJPUm}s z|4>Pzvo&v9&6$sl)!fu%G&4{D;`F`#QCi!p!?Olz*U1UKC63}9f1mksO2kP|X;8yf6EBUVm_W#g4Gx?SFc*I8Z4oqyyo%k9%K8 zgmyUA2b{>)!cfnaR$cB92q8`&{n0r_$qB}5#|az_D^PM-t~Ik06LJb9BC>xSl8a|7 zT$|Ix4hGoPxN#R6Pbm?-Zh~0Jz2zk3KJZB zHYn(di|27lmGtLt?U3LMVQ@cL(#NIkc8&WT43g~3S~!B_f~{T}r{lOa7LtrE$#egu z=}Kh}YFl_Bt5uVb4t215t1z8=D@9O3{)!`b<>bGEV;X}zS2BeSaU4|cnT~sRK{HCt zW8(T@Q1Wl^-LQW)YFB~jOZ(YCrYY#ziL4hsaIVn@&g7_@_|x)WqkC2i)m%3caLR!dGcgr^xb>AOiJgT2|*Wa7zcd_CY053 z*4~k!djM`698H+tm(+S>2G&|lKklxl2F2o9E_zFxSn#|rWxECqk}zehJyzO z>n?g^gk`!2auUAe#X7BQv^5F5&*s1S=vgm{H%<$s(SmwqIoR+LWbFNpb51wQ2{f&LnA4CZ+F=qAN#Rm}^nu7!Wta#Srq|q-UOU<*()3^YqVZZrQyy zxylAZH;rQP{VpSz*WdFLw)m2JZOpV(pPV1P$lO}7Yh5FqYejWX-R+tWU%$p}v~lC? zLg;G3??P=B`B($UKJH4Z^8U5{K0^LI-t|@!xOE)2($rHMb!LEukQ^ZtC}U!uw8e0l zcOTNx#yzFtEB6wb?(WS^8VPdpC;ZacC3M=EJrSyCQf!R4&xWZ)m2S(`%Ei7zbGcxG zQl%5#b$s(EU*bwgqOJl1o47C1X}<34;oz=3O;{xb>I(Umg#W;#j3G8WdzHfnIMK7f z?j5j_6Roh|?4jFGxxC_fZ$CQ>Hh{$GOPX?KMH#jcB?Hz2u@^Ob)`TRXoU$ed=n|Sm z=tWHSO9-!ilIGorZJBsvF!Z?8k=#0sC9LEqdR-94Z6C+&nC3+p;f(Sbmj(t-KHgd+ zdm9xVSTnrY`bc_Xo!*D0`H0$)#;aMiR)1K3sf=CW&7#-mt|y#>QiN2S|00V3c!(hI z+dA8%wFI)nhANq+B;h1qOh}SGv~37Hg^D=|NiBJ0CIgh;cZ$iq3Nd!g<`Eei*G8Va z{;7KXUGqpa)IoZa}tSJ6UD+|$Q^`B?Et8b6e3&@T1rJQI#zev4a& zE+w-!gc}H5eAro38z*jY**Cj3yoR0H8OOInAxDN|tUKLSD4iTulKO;q-aOod1m?k9 z=c(G`U!pzm2l35gW5KNrv@42p;%890Pvu1y6u-%xIEypF>tm8PrjYy0h~h3R^2)C}2CnumX&LRC+Sd%2Q%5BE{$rLVEw6 z6X>+2>by-hl_o15T~tu>BaJOm^{eH)4mB$@IiN|iRI}IftEdHZQ_o1Ww z+!_DM7+>D9q_F<~R3^k^P-1*E)ew?3O8N)-c=_9W_SgGA-!86`?Et|8iKR&RLlZIm zfNOn%KNeU~FY%X`nn#CvUAf8?OOa#+^{t7Xv#(Het?5DE zkXD^qu)s3&!h-@<5rYS0`5*emz?jhtf3OV!+&Gv9UxgR!5I)*Wq=rHj5ZL0(r<#}u z<*W$lfyjSS=yi5_lQ0JKiHa`yJ>sz1MCQ-qF!?G2r~gjVkg{SG)|S$9WgJDH2;Z;b ze4ZYO{Pd{UoPV<+Q(<9r=`Hg(_Qt5(oK}2jQOUl2y8|zdF!x&V_JKV>9G#C%Q;WYN zep3RI^}Pt7KatJRsajV0u9VMv0FCBy=KBm4w!nO<>7sgZ%)< zmJoi5hl`2ktIBt20_~5Af^hr6yGF_VE(*?E5sZPV@6$3C(&jG`fVrg20a1H*!gKs) z#EQR(5q*g!FYtyXa+PjGcm&V?)eFB}(b+EfXRXicCNOijz{;c=8;2S7QJDPw=OA8z z>L^?>mbU4`W!f|=g7<*wpubJ)+ItiG9Tl!?=SuxygumzCi#e4F^rc#9*q#tlz5bF2 zu0D(7?3FwAK3Tg0xa2J5qgaMuD-dT zzdOW4DfQT=C`Vk-bm&40OMM(MndUWMbiB4t-Cj6l%fF;5#w*Q0K*J+Wc3<^qfp=RfIUz0d9p@xqAH`srhFlL3~|B`Ji zK=1g8eML7m21?I>CB`I%UuDuzp8p9Cfj%?*r?f+A)C(0IybbN&XmGMGB3XdeVC1lG3Q;% z6GK)sNW@~Sle}^+4zGz!VJXH4apnv8ZeMrNo3FcoLu-H3>G<@$E^ z-^=Y(u;>q>OFMa$+jepG0C%4`{@Uq*#htwJ?NKA-ohJdA!1d%pAyu3}iHi#1#pLCQ zX3*uBNPh+crb^dJF_|dIRu`tKd2PII9N%Ugu*~7k+{)wqNlrr8KNl5L+UI6!zeEs1 zSAk%cJQ$Agmaetx$5h)&b8wK2hVpw>Imh#c^Kuglryt!Yp0H4W-`i%)=@&>v%S>u2 z)QeIW+gic$gCt@u{nzq5OVz5wz{<}KpyLde%{?7$M_yS-@ZeJx(`CP4GHe6?M0hBb z7p)Ahh4humBj8&1)d%Ng1Y0=%MrByk+X>XmBL<|}uJGGM{^RU|O;68f z6_T&I52Ohsl==)de5qQx|Kz}V)^Ofz(CZE{&VZ-9qW<>-+i(d(VfdH1@<{Q@=^i*3 zVi~&zEU0irD*|YohD{F$Gi#K-WZqjji;INwAfo%Pu}Pay?=ZH3^9pVa!h} zi(+MpGJ2`11k2mSteG2D#@?Bk`Hx!#P9<4pi~!k_3A%ZQ$!*@&`#HiY@~I&23PE5M z3xc@-F8wqy%08YS=&&Qgp3((o?i6$mEmkIT=k9@hK&CPEOYBvd2L*PI`9$iKvQmhzPS@iQ$;zQE1m z%C{M2%bsdpeUjoujimGVpO9UzLp`tk!6~?LNm&F`62vF81RNYx{h6d@t4^F{7_SGf ze>%PoRC*acjkG8+^&-?-FYo9b{hKcPX@09tcQu`J=`=^)jVRbPgtt`&HpY9niSd(t zb%O*`ykF3x)bd;HvQ<%)j!&gER{y@vrwG#&(Xaf8=XF(qeq@IHA?8~PaJJ?tn3pGl zvdY_BS+GCeFY4+7f6_8jJgDPXcjwWMyk+*n}xXFydk+S&jg`|BzzDa`mJ&DxUN=m zduA1rNpmLPq)}y@1ec9>3FJ5C2drTG!+)-woJI(dC!SkC-GNT49bT<&&$dtp z9$LHzrK(Z2iGB1}ni2CZ6on7mm-@g^FI=ZTzXv-EZ+kHQ;b7Sg1B>W%vnymktU90x z%gSgXdnCEx6c$P3da2=+)$ZuW;~}AP&gbUa?g5?at9FeY?$-+VWmc9kDbV+cDJkc( zH*BmXGKhUJ?I+zlWwVl!4P+`)mGgjAqgo^soCf{C%5V^z^YY{_qQzjcPd$-0wcN*r z4VD1M>Gi2UK+&17Bjct=lW; zmgk-GpQxvSZuWFk$-UEJ8z1orzcCtNiMnopa)P5Xwvewa;fLQdV?rxB@#tHEY@vDR zOx24x)i}kX)^q3~soQ$@DaxFk+xp03nP;~GQ7qCVj?<<#Pd`;@CUc{i`7VhnfV+dG zZ^qZ*BgCE&i)v+>%?0rZn!Jj3{Gv?ldiCc>?@DO2e3gON;Oe#|Y4KR0ICR_hJ>Lw|SH`aL%nWUb2={-O!!Fhmw?O-|AHcy(XCtJmRKEq>Sd+gRy^S-5?e#fl1IpZGUOI_TqD?Xk4DSvmO3@m5$wI6}%6zAK`8dGkW;; z-mOvU{rse6ZR#Ew9qtARd*(@gmRPF`!oiVq-?bzzWV2tWWV@nSr+r^DUu6613_}Nx za|ss=2AcdO}f{bt~t?iMmvub(+kCHjU>-kE2{xOuxMn?7PW+vYn(V zP9@1}8#NPS95mci4|9Ef^RV1fcb=`g0sY+zrJG8pxuh^TWR;=$X6~N7Ru2#61@L_n zlaib=96e|`WO9w}7>sw1c#opM7|o*4E%XhXMo#EOPdDu_ zDGmPM=(804jPVf~FR)8swJtY|l7n3RIqZ$*!SyxzRLC?U1`(mM{U`p2noU=uzx^t< zIw2(^w*OkB?dV=V6EyV~uEsx3^Lf31*e~3A;`;UNOgx?bFUXjYoM7`&%pU4%k|0Mh z()h=%;OoYpaO0>YuD^c&w-%8J8LR8N-lo@qLNXr^$geCNrB>4=#iY5h28?KE&}~?W ze1f>9KF>{%3u|+(6RRgLf0JkySwT^*%>F>oh@VQvo;PsPuX_ADgQDBCCB;MTn5(}l zqsF>KJltCpl6tgd9L45^LyF;<);-)f7b*BL0XEK`O(&;LVk-{=<~j$W zcuaDq*acx={|_3$1g& z62=5lhhKprtDTVz=6q4r#<*SCR2~ovgBx8ev;O(|6Vhc$CAiKUqyQB^TAk5G1T$C5 zeWKMw90))g1>ST?{#F*Dvpq#Xp%6>R8P<|h1*~biRI_8D*Zkx-Cmn5=a8P!xsP6Ti z|4H<*(h%qUjd@EQ9mwOnwRM?TR@GbPbZRV_nl!dcsxicY0zI$leE>@5887iGlD8*8 z;&Cv%F*eqo7DCkYV`b`kLw~0fo9_#j|@A zpz&@bnBEK3_cYFJlBOdRINDkG9uD6`c7fd^&!^8>?sJc7JZt^apxJj7F&Yq@A;ED- z)!&3Y0un-Yu!h|NlVdw{3Z9Tx%j@|&y9-f; zD{MxZ|ME(-6=mhCzmd(kTPNe^_kAOEwqEq#l#Ui6nCH9-dJtpa^k%~U4x7*<6?NJB zZrfkco9WWH8NjB(Cc&xeh5YH}zxmDoZiX{&-}AKi_d5ejw%59Gp#R&6PIqo+UGoqb zAdzIh4<8Bka*qPs=ba2WgD+>PJi-~Ta694MXmL4|ZC7aB ze*B^m<_DP`qC9MAZglP80a#Qo&ssWHotZ@5PwR$Uc7MbkkntfRs7R^0bsrIEMA0(}k@3tr4Z=jtMyUk1*qfV}6p5YF&-jx5qqt&rD*Ho(8T3Z`;R0l5Q6>@U1i6sR;Df1Rp0XH2KcB zRH@T)7N*#h*wWvzLC1ryMY~6!%5|&;iPtN9BvBg^C~Q04BnNybg9h~Fhkpi-QL){s zv3isn!@mn0xB90*!6kw$y{?}};Z>&?_ ziq15n3y)C_$MvNDYv|VRJiYu_Us*5f=|N@Mp8XR`2#nT?h7qy$d5KdGrgJFPQ8HwW z8Tu~_%@j@P`mLcOT+~XNIs#D3c3f!B;J2kxWp2S)PzXYENA~33imgR&9A=kuRM}B-g~>5*nviT>cc( zKG^bCD28qaJ)2~`!ft^z2uLwqO|pB#FAfgCullo0mpW6GSI79olB;MM&NAFz9LP8y zqI2PR?#!S#HQsfD=Od}l?|?+s;&J7#R#2q|D+j0PAAG3o+H7Yq2+1>k6y<)1jROW2 z^jt^wNM_rbfm1Y)zL7S5Gj5t@KM{Ie!lOIuU+*s7RJ=j+5)Hj~KF%Mjgt^U~l4*ts zfDEgXT<2q6Cy!73r80?vNz#C$2a)4xhFU)^`!lKZcU9g8EWVe=b>W~GKSW!Qg7)?+ z0c)CmSx`3Hzn!+C)5QcV+a>ll*3yQXn+>sgv zB#BjDxI*NMz~ML8Ua6s?gv$b=GoO*4+&sg{RYFkt5&`H>`7VhL`1v&&IyR zQ!zOZ?_SxLtp3|ULX}DM^Ws#fZ~RP5G7`q)-eH$|xUcDLe8lw5n!DkS zIVTVxT_#@s!!LoWC=-8$#(DPi*ixAkf8I&m+sKKk3@R3_*hRd2C>?0CH7_nN;li(L zHbS8Vt(flNXz%eLA=;R7>8^RWavgT{Ru{AJ^@^LzluY5H#iSW{W=lfTQ+E3HuF_(~!FEAyIUs%&!c<;Zn!3}O{gcW9x?x&c6EjtDTsdx$?bT}zu60>&C{j<<9y)k+HY z&okVJiuyW_s5I)ylzhN+k@B3V4pC;oZp*NeBX&H9) z^H?nU`#MwD`fj@e^B-6dDnj26LXtT|e|03b#gC!23RmWL+m=}4x!iyz8e46=876Or zDJK!MmXh%p_`B^kc|jm}MP9GFzL3u9VfYiP00u?G%f%EA5dmCyK)-3qXnSs3|K1)z zzVKYV%nktS7jr^aT#uH)-he~Ta<1CBd1@{Hk$A#S(L)fb^toqu!C^E^-iNOscONK8 z1_1S*ItM#%7n)T$7f*YeS22NT?H^DNP)BppzgrC3t3wqomo26duh&{~TsBXk3a zL}oIxK8I^Vlc;kGIljy(%@02q&kX@sR*{*px7sgFI;JK;?t;Wb$=LT)X5$Ldr%y<_ha~^@w%mCq4SO)7~@7!$eT?2cNo=YCG!|1Sgz;_w z`Warr8?E|akHLuOrnZ}Mm7?V8?Pfig$pze3p^a&V$?VYLiaZJiMsB@bu^qoimncx@ zoh(x)o#cx!^Dcdc9-Uas!O!RKc|=R4gjQ6nJ3glG&P>EU7FHOENaoI(1Lf{DPR^kL z{yZhBzgZ#K#E6X5nvo`i4bvo*&HUj$BoPTHpiPZX{&{aR`V~kEBZf@bKnlXGwy>ea z%VQF$jEw{h{LKiV*3}J`iPvN3x76GvYHt!v-%=N42=6iSTJw2RWi}n}I6@-^?zU+` zts_Ax6KyUHS%|O(`O~tRgd6w!h{#+e25Pw;#h(3T)tAywPWTDVuP|s%K^A2CNzBVU zzdM40x4c`3XU1p>IiB-ao)t)ppTR@gsagh3*_z{UBst#}RV>tsx)883Q#%lKPt!A; zd=HHYq?t_ptuA~%nZ5{1Mwl=wl#_@>=Ho+jYsU+nUZae%%sIpIro1e~UOaBTt|0Ew zA_jE!*e~daRi{6wcD39&L{&DalFBFcDk70X3J!j@OP45)DvoMHXHk`mTmcD7Jk-c} z;yC>t@sDGOLq2Nt>I9+GW@OlXq#LVgc0=>qEaP!ObDK$e^C4Sre&%?EdOm7bC~ez3OQgg(Q`F4V5@*$s&hvxkBsGn zwW-$6lEl_XPYFDrE7fua$7NB2z7c(VY);OWP2NuQ;3Kr-+Y(yVl6am28dt?Kg6kKX z$Yhkw$-gK}57Xb)$PI27Aw=&un2We?bU=-W_JAVCsu;9rOc9aIi6;fTUXRovF(SYf zXO+pxMT|hxWS-p~r%e!&GUb#@k)Txeo7DEQksMnZuTT;KQhMl(qczyG`%Fe+p`v+K z>JmV=p?%2l6yht6rtsgNQXHYk*(I<(-{)R@^_vAID+k8v zp{|q{veUZDIm)?6K)FqrS~6yAR;2o8bUO{GML`>$29g*rL^~TgLdZ6eGnGus6=feD zTAWCLGZ6^-SL3gZMiVKT@fK@|!eDKAp|*D=O7V5aM=LJP+ERfiP8ce>WDLz%cD=!Q zL_TxazXDQJM|nnZYx9?uu56l7zOrW%o|#)YFQ(gEcH4)x!u%38JWj6|IEB1glHbC> zdL_JBq3LtO&)n_&nS+bg|-PA*)7YGCKwWW_~>j{PRAy zYBu>>@dHwP_Gg@qKLpe^IGKkOc6VnX@A50Ipy8id4orOvAJ;d9y%7g^!%%PVu7uar zEH=4PASwT%jI6y~sbL`Laf0NWOzdl1bqDV3xT!-zUJ3<0$opu+aEIRSmS^?85rh>7 zzxc_UhGw@FjJPTEr`0~C$k8cdV1f;&5CU8*w;7>CPy($p2V}+&8lNPV)4~{;nNhq` zh%^`DsJdG8lg3zU2T_GFlT$9GyMtsGE>X}7a$-7H=ul(P*>9>SVa7f4eca z#Inqcm?v{;6-tUY3-Q%|Us2%NfPUnKP?(vBvshA^LR0VX+&w8*&VK)1UsG|%fjyUG$a})F3&wHD98gAxQojd4X z&qF`KaBdD%OcgE(Fa^K(ey?ejOIyELcerV#_rT5du?-H7ac>#L5y|ndHQIO$7`?4C z1A|v+fahmD9DLN9N#1YuHR}BrCKFUL8;#U}Jp0^2J6c#QWBq%HKkC5U%|Lm7M^Pg< zFg(^B#G9)!JsDmvrH-eYBTkp`n=Nn=U1E(i_o^Yk{9!oDh$x;8 zm((L=VW*qN*ydF2K2O8Z0%`83cU(e|q8h(_B-woNREECG6Q0-gN7f{#YCJ2w*C;yu z1+6V)WBVoS5uvZ-U54&|%?%t`t~@}RG*>_@>!L$FZAyH{=zF+Sg9>@Jw;y*;I9r4_ z?Py-DLexe=_%ZyXg6pxu_LhNa9~x>TgXUUt$Z0HcwGpHg*ym84VJZDsuXO#(DNQ)% z1}>Pp{hF#$glmEefl{F`=Tfr#!RcE5MC;bfU23TW%8cNPsQD-b(2y|pw4?f&=s=SL z!z1lmmMKKh1whslBI%AUlmShMuBABn)?(vzcVRAs#lU(T$^mE3`Xl0`)ldDA3wckO z=sYV0M^N#yV)ekzH4hO-F`fi@#%h+}Wo_#6Am*b5Ud|SmJ}TBe&;c18d~tMG_>1M= z`<0y!efRxBYe{Oe9@uMSFZ8|;6*4Zw_wy=221Q|=813_J+wS&gIjsX+LJDY23dQ!veU8-R>985=S&se4rjlv$Qv`$Wn zfWjMJ@n5myTeT|(jZ1EjzZB^TX4LhP8zKa+TNH@wcvAx8$~$*b#J2*z+chZ%fk^}D zG5Ep6DE7kW84bO-RkStID|zU;xpSyegXn#oUj+2GS^lj%ooceY+> zgSt9B2xgm+PL55PsL=5HJ6&%HLrcG(igk)%r1!A7+7zd7zr6t6XxS%sAwKkUFP-H! zhjP7g!?^Ucgt-X;DA&b3fAMmlbe!=(TwArVZ+ z+o<(3U6__M+9XPA^zhYu{yz4lxYgrOLuDuoCZQp~4jd{#J!$3e7U8fVc%OT|zxqb@ zu(pP3xI%!?ia!Bw6;h`q2mTCuL=5N!ph3S^xwv0J3NjnaIMq8K84sWbdt^k|yN~Qo zNlg-s`CjEj5QqW<zwhzX}G-q63Wu(@dZNjf-$fia*y`rADl-lJW#_L$&bMYN)08 zOZ;+=Op++J{>PSOk3PsYDYD6buq$WbtaUkX=o9u{zGP*01Z*q`8kGEX14hC{gjx4YpQO>Ugr z{-M&CCbvu`%`p;)LAg3)7Uvx86{|-T4}LItZ4zD^o1bI-RQ+WSjk6unpO)JsXJa1x zA1k=IIhB*s@1UNFvkk2I`;E(~{1Fyrbu@F|=_aczSc_xcFo z*t8#vQKT|!m7UUPKSw!@Oy9E|Ra?a@HBM%5IY)P-%eet#5XvyX>>5fOw z3rhK83{sVQ9;vmc&4pX=748RL*2B5f!pP5W4xYO>4ngY}q=tDJ|C(|zTZ+}=XBvHE zQW5w0P+Nl&F`j`JfvJDE4+Q9Xq{W}2xE-BbjntE3{xOi#obN3M&ywvszArVPIBJ8Z zxyXvoh45QjcpD3oVL$y0HCP0=BsN_~3~_L2`vLK7HUQ?8D?F}i_;S=(3!nx;Hl zq($^195srW8%@x-DyrT1jS;YiG0<=ho^W4PuPB*o!7(VA+whKWK@4~6rTffT1ftHZ zOlyr5ZM#WZ5-rsT_Np|hoOf13r^-&s8!l0(VI?dD%Ax%s%guP$HtT2|{mHPss-+>J z=j)-8Cj;l2vCd>M!OCA5_rU^ImQiK~P3?2S#+oZj&m9RCXWaqF#1*wJE(tRO)^8}H z5_k-(%KucC=9jJM5A1ze=|5^{v@xh`iP4r_0Kko_HFL~((FE2$_8mW9_??!*zqsIq z?}q0{_^DCbCp~G?c@~N$qKvmdXjO@x3<^5Mv7x(PBDvIX1zti7Ki@rsinPrMRg7f} z{hDAn#0XSa?%~!&;=u^Z@Wl&TCPUS_~kCWd7;Kic)@x zJ|$KF4O<6D^yV26l6ykK_(}6d)YE-+nc1`>eXkNikaR9YC$WC=4xg9|*QC2wTBH1= zyDMhw6Z-lMm-)Yau`BkG*QV;+PJyiL6JxiS9TV>p23R}Psnc(iUacuM-b{uWhRcd|P__t_bUT);R z!a!;5dPP@KvnEG~&vIQ6JgYwt|AkDy7e?2&D$KH6U zSs%O?qpFN%eoEkJLb?)wWalb1jZ497mvyBD%|Y~TQ9BAi&Th>8Xz9Gw0WhqR{w*Cf z){`xS4@1JePB^Iy)PiYyE5AyOG(Z2XUP#!WA_XJz3tbC_gm0ZtN)@OFtPGc!UgX2b zksn1LRF^$v;4NDS2xcJC)okjyj;o(f1$ed2V@lYdPGAE4!tUbih*udaj6e2w5+j|L zr_MBCBb*lvWle_xR0!MNiOk zn)pl)pWts$Js=gt^&Iaz3Da*;@jT;&0`>uhpQprbcG34a!Gp z%nll1{v9>DRI=wSg(qle%Kj6hfAc{A#A->uvel>d=Vx_em6fI>e<19C#0rj1KVr$> zEAU^U6$mqr_l~9{U(i|LDEVQ=TeMhHpv<~|74G^=v3pyd8vUG3pJdJq_auYBE32ZV zS0}nY2=n11{6*&|-PSeZi@&5RFhbGAkLyLMPI(gNEZZHmUluQ6=HK2d0vVaO@S=cr z0uZ_REB+%JzeU^1927G88CX(!kt%;0A;E)oLl**Q)eeVr<5#zW%KmBaT29z&fhH&mz!1@<=Jm7f>;%T>ROUl(f zB%pI+8lRVUA`7KVOmP~eHgAIrQY)V=CC*G4JW)xlo$0qeDF+*DHTOtN#VA@`wgl&! z^ML{6D@Tz*nRegBN}BTx=68Zy8++fvq`^J|Z5=da_P+pDHt`inRpEcJU_jVDifxyb zR!im{YuTLqnj649VZ_4umV)MdzIM4=K7#N}GOQ8e0gz54c4NIB2e@3eT`xz2mZ*^xBs;(0G6Lv$PX3KR=h|JU$)j@inX}S7X)p z7oI(7@ukb#CvVwzYFxJ*&+SlNce@%l!^bD#_glYDe0K~I&B;8jaB1NKZ-(vK>>1P^ zZ^j?QQ!%Qxe=0>w%yOpYl~qlQ3J}hK|5JS^-fD~hWjhO4n9^waEtKzvH!w=ZuWU-c z7etRymsOH%B_pa>K0`;y@2zXjd}Fhp~SlZA*B2|_GdLYz9s1!8NPC9Plm0Fp3kQ7gfl)| zbRP;i=OEI~#9{pTeY~|4+FX{A;e>Q-d^?4kTEvZs`CPnTVr6{VTwLoPY!r%Da0g9% z)})^N9=TMg5a7%OqjSBIvkMS9e-lc6zd3WHGvcAjQ-JUe|H*+`yM}lgf4~|s^KmDV z$}(3p1$^6(O#p{T}F#MO{NyT&ji#WtEIjA6L*Q?p990% z7tA+pnqSbn?<>YP{Q1^0R+pq=u8i>CP^uyv@Hj25uAM->Nv7yLw$nOAM&qeF)qmQ;*B^FJ!qRhf{(@vP7e^3Daq0> zP{+Rg91rG8z>}$=sCVwH+V1i!r&QuY;;Q)FnrpNqDik9s{s2mU^j;WW6KdsF)Z@bC zGHKDmuQ*XBRIB)H;X`BB~* z4R_&$q_#uYiM|uxw55AckB=PCT(0yMzp}3o7SZ&o_YE=$946_F*tzsTuWS$Cq+`|~ z^q5VMnYg!X@XCx|ZotlMn@C;c*cTm?Fu2S_HrXg|5d7sd$M$DXcV{6TM+7BW;Otj&nwqTyb1PI9~F(PK#nS9!{Bn z2ckaCi!t3&!ODQ2K-+Rc4t<#NlqTuG3f^L{7#jFau{*Y^`AmHsFkf0|%!%T{J}A|u z4r_K~20rOl%?wjfq*=U8ObG_u@P;;AV^*BVD+NNvHQoJ$xbPo`+y8n+j1G4Y;e5b1 zPXBl|LGL>%X$jd)_og(;EcN2z-3hQbcVL!5ZjQk&_Ze0ae*X=cVuV{=YQ&FTW<g9h-k!ftV)LL11q-#4ZFwMA2z zzI+(;7yiENm{E1KqPT#VIya3lyn)*;Z#_Qnv>@Jz>@i}+G{y@9X8d!E>NNH10Vg8{ z>FG)BQaA)I{Ob|WSzlDTn0?|gDP{#76ntyY;@54qK71Ld{Rudt@N>O|G$O_ZjC3>@ zw=(?82b5&%zs^VfX5>CRWi4wxitgr%$o%QRk-a=c+|&Y#<`myXT&;nlu!swr^nrQ` zB?&qEUtpjgnswp)1CxKlLNGR!ccjQd*?YWi_5SWO=eIZq^&|XL{9vTCh>3T!i-ml3 z`W2LH2g2*OP=p%mAqPXqWU>|%gb<3_2h}hUl+g&qSCX94XzkB}5T-U!Is(PRf?NLj z8B9AGQI_e))GW*n{VE#+4RvTR*<(LRQCx9G0`A`Qb~@~t=q-9zT5I&uk}Mj@Z^5!N zPBJXzE-5~vgtLI;ldzwhZLpHoCXrBGT<9*0!C*?K_{-+9BCBJ@d8QWgKn0uJ)Gw@9 zTPqLhjWgM>cY=LxSQGc*(6v4L0@WNx_6q58^a9~=>8X(p@*VTmENmd6$iRSJ5dU4! z(w8U9u7LQOXKpQ@k`SJf@}nJ0&Y)WJ=cF5)LX|?FJ^!p|UcN(Sc zCv7g9nv-hl|J|(F^!5*y{ta(v-t^<*N3xt%x&dm$KAr#7I$Bl?z00t)XtZ>=t_4R} z+_49jCR8^Z5Oq0n8tvfHHD_dMN`pVvz}NwgVB&O`F1gY?0`yfCPuwVZM=Z_T@irNn z{9*h&$ufBGGK=gyabtUc%H2n)s-2#Ea>Y|NsL};`w902dk6lgY4~V%XHZi+2ohXyD zx7~N_S1(VFJUUD;d-M9q&G+68!IogUCQPxPOik`N>&XHKdvocVvnk|@W2VIyd7JY>E7SQa`Ir-<98c0Uf z@rrqkj7_DvRc0Y;EUijQcjwEpAD;zNMGbu@8|z`Wn(uLgEl8x1R1oBDaZ{`gmvp$v zgn>7Yc(^ni6D;W9w3A4x+iPRGyk6tfOVvmaKS5kWNSIF;mxqLPK%ii}GaTHdHuUnU z>3W}(;4Z|AY^#@x(2SNmnS*9dvn?Z2#upCdgq#jFUR&x;Tixxz?0H};0^;5-M~n}W z-}{2%ccWxsa@{NA6;CEW-s-Y^r{Ho(IM0(ij|ASj(#b`1dli?7IIcEOVNQvvlLQ8b zezCNHe0<3g>tQg*6C}|UgdW84c}K?drzc)An(F^B^^HM}v~Ant9ox2T+qP}(cxK18 zZQHhu9oxo^ZS9+V?(fG}m87bxJE^48ed54*A;6g*0e|yWe2X}{VR9Xtpot+i5&T33 z`V@4GUMVD*|g-lb;dPeI01k%sV&tvK!*Pebu6w~YP^ON2pIIF_k z0NwRcA_i9=%jQoev+?EL<4$W~IPQ?JFT^=n^sO!LTVTCHH6UG?VEZbYErz2}-W`Hy zqCN0gw9tAHqTl&Zdf$!k?)opI{<+m^6e;riA88@>I3XXXR^^(ET~9g7e< zVdrJYk2E0~QK=v?v-CVTXWbBq8dzoF*>vsR^-<0G{s@(qDjc6$s|($yFa{=Ub?T`U zfYX@Eb<*I*mM%`~-Mv0y9T&H`zM4AkUQq0^oEW3^jK17pffAj+2yO6Vlf3;{W-KM| z1ZA;q46F`K00*;}fTRyKb*4P46g=WODe*cve=Miz{r#AcSqI|v=w>9d21)fI(K3mU zenY2sv-;|d#ox25A$wz>YpiijOb6%I{dI=(Q~`GVvogH`?6sU_qlp~(cW^1kfMWDZ zHM%)K@gldbK{ znsla<+`TrK%)RXzKC~d-;S2s>0bc;xuSYcw*ER4Ag(S1&mrYGY9 zDu#UA4kX(5r&Z0$|B+t8z{%(Fz|5$7LWvEmD|y-S>(HhOeo}EXB`PVXT*KeN_Q3N%FO zcpI-H#2!;k0?lFED#;(mU|)CqYsseu(FvN6jcyzS9aw1{iqs4=$8rKsMF)4- zq)0(U4c7|ARo4zsK9Y=Vda=Hf)M1tHiU~c}jI98`Wc`RxFZHgjeKkv}(J(bTH3y<7 zyYOp-EnbzPF-nX=@8kG;X79z>T8LJ)G2|()iFlzGqL=!Nvi2woDso1Jn>%Nm$dwDb z#*+0$^u%>}PF&3re1*)MaOR1c9Kk4dq(B*y4!g4o{`KIe zn~{D;NN9QW274X=F$4Q85^E;U@9~=G<3Z^cusrY%D(@+%hJf8Cej${6SU`<4xX5#QNXuX4zacPp>hoC;pHJ1{a zL(I~MRr`Q+hAo~8P0`OBXC5d#I*O0f&$5}c;5z`lgwlvVTJZiTF3Mn|oEyx9W>Jrd zXap*zp$LV(i|i=8F|S@`H8AkluBHkIW!xqHfbEfw(yGUUWy}m+;R0xW#g#oAvsa$C^(B_?2T;x)iUozyyzp9Y6i#As=6wLfzj-vCm$^(jQ3FF|jjz z)7ViujnT5ug%v3kJ<$4gibr=8^%*etnlThD-!KnGwQ7KC>-3pXBSn7VDKj-0k#9+aURhtgZA4H0~Y zT#Wb^(ds2SA`_OQEFivGNs%qh<}1P6ItTN##G?Yzgb)%#AZB%;PhDR+bfW8LIJ0pcuL=9Pb%&xiSwdG1vU9BSwNYL9d5p~ypfInVSgaL zF@9JaG66UiNdoy#z4mYIsFwO_HrGxRP|IM!6TWLs_lyZS_n?;2EnBl1KBSLdd*u~f z?HEYbH9uQI9ga0=#dYAKffv=C_085StJs2Rs3|_%leQX`SG2$jq;z7K`!%C#Sys3W zTyh+#nAgBWK{&B9={ujTEK-#b)e6hWvv>ZT-Ry7E7=z>^-Jb>>7W$*c6&zGl|888T z+^}Ag39dqPE$j2jgb`{QDJ#HDyWm|_UH5a1$#x>=v#PoU76lIZPRaHuv(96$|GQ{p ziruepcE!5$lrsZcY3@u~sL}c|ka=^Zk#olyD&Ej8gjyE(o0EMPKddi-My^e#=I5?m z&h?HCafkr2&_3O68CA956nfj=s2Hj;?xJBJx$dtyhK~V>>wq*2vt=P)+{!Fh^a+q} z*f1r+EU#nRq;4+sIE@gLJ5lMnk|ePO8%3%mgkwf43;;_ess1IRokCJBaHVv+A+&)0 zX~>5)nO9MKx*VUB#vl|etd4kV!AX46s2mDp^M_)sX}5yPEV(B)VEfHG8QCdrN3=p2 zIP4ycv|;}kiB#)x(d*NNi=EJx{i11>FAh3xNt7(0sw2;$H=~05DR~M<6;7E{ zQ`92ciWY~E;7u|QKR2*flO2`WPM|B|iZm`%yu$xZTpL8b zQSai1g9v-eG^bi~U}fDlsYCG(v`kihhDA(f77k|eD9$12ii?}z;4Zg{?*;zsQrh@L zs0*Y3Co(?edW^8aUb<^&@4N~wZUY`vu;)kjdr@G3+kUK~=m6|mH@=O!Yv!=H#mYG{5G*3pI!kQ9%x7uG<0*{CjIN+6lJq(|1$={JheQwvNWQ~xT|N>~ z)UZ|7H``-z9mm%Z_}0IBow>@^!Nq%0LN4$IvArhn0>vxb*TnHN#}(?zlY6!@W7c3ad%8NpJ` z$+pPWaNA{W3l#h(9XS?uo|7|o^m|RMYYYiIgKvpTR6eKqXMBt~$y`hG#yjkv9WUKo zV~XI1*`=zRxCV?XGJKh6!Ru?YV}cPGCeTtdv5pL>tTnc$tnrJC&EdH6Qn<{qTfm1b zxv~z)GL8QR&nC*Sj%YgrvV$>lOAeL`x6$-=wKC$qrKTaS?%@bdwCfgCxOg#71C=1+ z1SC@H=kn#Ba@c;e-~cKsFXqc`D4&!}XD)CBXkknvOh?mTyz{(^WiqTGXcWoQdrhP7 zgKsnefi|AQKNB^*Mq6=4y@P=F3kbkz{#7pi93}k{#hkkq@sy zG+gDG#lRVB=a$KOWkns8AYRZw-lAz~jm>|hpRt&^h86lV$FyL@wzx>P5iFb#{lvO% zMGo(x`il<)5r5SGnqM2Q(L^C1%JF$<_ zDx6)?Z_J#WNm4w#p3$aUAw8sT^V_uBiCety=lIueb$aMRomZOM9L_0hK^Q81?y27b zPW63?va{YPSqGunhR5a!;(q=EM5pkytemlB4uJV%f+YNl&TCg|Ta2OlHY_R5r4Yjx zB)<+s1)2#cjF*~26rg{b3aUvl2e?h3ucyZv)0dcqb@eKGZZ?;JF99E0T6SK@FyKiG zHM^$jxd1}D;#{P(OvZC`knDl_qZ+qGK_mtEJor^5@4INATXG!zjw+@clB7*7oB_3U zH%>1ET4_=L+gn65OiiXavVxbmy}1_YsyaDHPPQ@T7ILDBWFa=-#-ioT7k7rw!xi~8 zLH!cH_cMg@Qta~V!OWFpAm!ExGH1%2p#)&88T`a&XMZE%oZGjKq$s*az_2OtU&it z|0J~5be;s(#6`=1V8kJ#kjH4u##(+eUAhdyiX*JLV5_0DRfFEYI_V5up)IBU>T zm9YrS95i%Z6UQ{JSy}77H3&+U8mJe|lPa${jX*QXaJETGE9~KYhZNP{a1Hl{t@bzR zez4jLEQnL6Xj9443gqSjndRLOhyE;nltq%I&pZiOu|+}FBxF>B6GY8T+!M&Yd}r$k zhJ?C0IOP=;aBrEFS(zYkYzZfdDk35xKYc_PFE^N)P2?|Q-OL>T;~T+_ezBs+75EC6 zmvgTz>UB@vCfrJHXq~!NlUPXxObO(-br#7YU{iH6 zRaC(<#re`j9Z6~~ut5sti8|;OjkG!CN8H-{Xc6AtOPCg(a-S` z@nn)(J`T6Wh-Ntyjbrj>zB3K*QH7Jz%UKikdG_CZBj+RH${|CO1a#^<=3n!jB)Hzg zs)1ZcV}-Q|a~fv{*#MX(y}BF8g~{-DAhR1r21R?sni*kEQW17#{2+MCvce&46~7c2 z5V#W6^1+D00**3bXMwyjbRoO$hZnBZ5@1`&aYvAO!NMX@YuIBzRzqBj8|QDLfI1!; zK=|UJjbF6u)uL?^7I^BoW6Jg}>bQdi+_NIT5k)JU&XaZyX=WTDZ(q1>qfMujGxV;h zggBg#H#jNK@nfid1yI^FtgO7A+$kR|2q+xx6k*oa_9elts>cuVwZ679hO7|a(O?xF z=feJ=4#nPM{$Tt?`_cx>&ina^?(AW^Vp2Y~aoi9nE-Uqg-uX+fpd;P3yw!rfP$GF? z7j87s?Q?s5c{m$tR2hxM!rxuwyOsz!fKT6Efp+m+j2+N)~)x5L6sS2D)nUP!r zR*6u5tZPuR3G~R5eL~cntUX6g!vKR|cZxrXorMa<`FZ`fE&KLDWpn*CT-f|1>Lwm> zDtMd6yoQylzLlg%S^V;#L!M778xt3+B349%40FXY6cJMlBFa!s zx)lTl7=06hPs8-=|`tEwK$0duS7a5 z_#!2jPQ22=#l0FV6rsLGhe>j_nR#-2s<(+0?HM2F4=X@1ESe4r*GeWjF=zdeS#zULnGT}NnWyrvYKN6WYCzGoNm}eP zJesWt#o~g8fc(pOtLEilqOHR58mAMuL7i&%6YYlyF3ggwqXyYQ@Sn>UD`gc#77_aX z(PWi9bYansQ#qoU6rvHaT@lFdQbnH;$nR(iVM^q?&;!Th)w(}0?krF@2VxPb$y`*J zVKUqc0dhpKXxtdCd-hxN0eyRO0m;T$d|5ILd?FvdUe%(b_!m_8)JmqwF5=d82O6X; z!n~-%vZkMfp6!5ly#RK>Z!bEca}Ay{+|{_URC_h6>O++aQSdeQloeQ$ZJI zk>lwrerL#KID={3ESyT-$EjjTqwgeoB49dFy zs5`gr91kxD_9Lybi1zeUVddo>yhHA>xk{|RD;Y64_qUvota69NQ@-l8%sr+!ZOpQB zbxoIfqj2X`?nG{r{FQPM>mw|w@SRd6Cy+P6M`Vr!%8D(}y0fgv3x-3iEt}r&m@%$2 zCo{H<=9%iF5n7mPsIv#U+6G!P)W=GnrrCvxpi{yk*PpR>i7=_tZ@5vFMW)F|s-4n% zmYRqaO4EeG0KC6)PiIk(qnL1TYT<-ZLZ8c^b)NhKguhbr*sAVd2^p>y=5vaYQDOor zJzPx)nuCOoBKdfadO^L_#=z2q8j(FJ=hUD&asnB4L}^f2*qAFaml$YTGOi8F=-$I= z(L6_dfUM_die1#SGlF$|qmAT3VrOZ@N%qc}`p4A#JvL-lk4>a}pm^h{b3jz68jhUI z3to#5af7I;>cO(007_rc8%itNp)KFet&poCT!R%ey)d?=NxBXxT%RP_k#Td0>q0+C zF2`Y0Fir&O&tU7!^e$Ft_ZHqFTv0*N7Ig$zTYdBMA?|`6yMdjP*@7Ka{e9b7!@Wv+ zDnwPpkcP|h?>g=*vUF~U>QCtA`>UVpq}i=a=@9(vj`IqmR~aNPl~^{X?6uRGY6@qI z-L>>v%0i3YU=4aGYR^=+=7M2w#Yh{KTHrLOhIx=Z3cLAQPpI9zGo&TLBo86=m1P-; zjkWYURu)9n=NzQ^>%QY^A&b_z(w|ft?@YBilX+R%PdH36hOVwXU-3t95%pjwzz6r8 zG?V&jb(!`PV9wo*ZWXDnGUWZK&Jqu4FgkF?tz=ELwP6g=+&`@T@)mB1)rVbR&Hi>G zRV=Wek_O|y7aIcQL{6LR_f+%Ed9pV8A<+mn6?IUJ``Yd?)9k56(k*%07(Z(rAcv7Q z!Wf{yzkG~ox?B}`Ppzlq6g;8W$J7!oW&mxRh%F>b2U<#Ni5F)(V0Gt=uFchRz)T9_ zB7&`)PBhOFuCG)^c;3XIA9Yp&xaM3$a-IP#e7c@s-INLdPCf1HgokGvMT^WmbdD$5 zA2~(ALKY9lkBr^tQKH!rWFjSHiGIT26_|M0Mp4!6ETR0#6TUMQtj;>hb7Hlb;$T_%ZxnuiS6F8yAg6U zJ|3#jpeEjI4bg3jR!`U>Sy0?dC??ddt^@~+4KGjyo6UV;6{BKtCDbhlp3AA4QVrH{ zYltXRJ?L?*THSEK+m!q3meqY1Fd4iJ`fs?M2&4T2>lRU^E z&a+s@g8XviP|M8#wI?oXbY~|C}T1;^b??OWA+0chqssFGoGAeK-9Q|9W=imSG zPW~Z{^(t|_zv@_8*TJH9G@R01L?C3{YjGvLPcASr=9&xufC@4h3YQ6uKVy%Ofu-2iZtj7ZD%`(YHbC?j4Z|Yh>;Bq|8hnynnBBsa zX=6gZ=VX7tN&UiPz&=MhX6jd%zURhBB<_+5v`n#;Y3o4??87{Jy(Rpc#8aD^eAI;HHFB`$8}%ICV0nQsjSE4vJU36 zDhx;2_sVXgg+tNMK=Hc4^;qHg<_DwelL_zv!kjQpa4yP*9r4Q$xsR&97xowP*Q}mIs zU6xsbTiKV4z1px%i33vKA3p>OCRzMobbWFl1>fHQhe2vbsE}rbZA|1VY%iB=B<3-^ zA*@L_PWD;w{Jo+5n*#r%J=`-Y>&4LNRP2y4d?L9ZFkitzV)Cqm@Y*tZoWV(1;2+|Ka{%{B69Vmv~eezx+_{uF> zT51rBPjzyX@6Z}X$XL(D_JT#DizCo|Z4oAtiG4=F;S^r=hyh^;Nj%b2dIo7FF>$Tm zg78`lSs(i9rPHh6>FilM1CL8`IMBw<7Y2HI3V(x-D|;*6Zq#-|-(L`)SzW7d;ijUf z^q&mib0KcJaP$EXnpeg?@QK_DR1x;q(KdXVWRBfZmHx2t#JN~KL!Eg@{)mHAX*Mjq z%6{y5MIT6-Rg1xUPogDqY$vEL`92Y@E5$(Mwo^aHCI)PnO(y8PrE~x8k^Sx4kZa3+ zW~ZR@oCbkyZEU#NNJ8I{t!@l<6jak4I}x|$^fgT~$)OYOtPJ_pqiWv4&IRErt|i%q z1OYIEgn2^4Um1XkZGL9HzCQlS?lEYomE|AFUAeKQqyPxWg|c&Y>$}R{$j%x0N%)FZ z=>$Q36CeL99B8wX&d}-%lmJ1zUEs{vFJcrIfR2krPjGv(R|Ewh=C$uB^zTG1mfW%> zxQ<-^lP~mInVtqeTFg)6Eep_xIwu`Fl?cjx{MbmxBzjLjRWDQ*-vMrggZm1<(r{*N zMIDCToQG>|ko)-j3a|N%So3m%YD*=V+KZA^6iM$GZ0{F)ef&57u7q&q6&k7jiS0|u z-Gp@a_j`Jj5(SY$?Yzx$Kfy`?LA}odMvS+7<;vxNiP^?CjU$g`tXZN-#l$?04C&yaGgdGhr)Z&HUhSh8 z%b&OUF!V($jNa@wt_Qjn`1aEuI-Fq|Lw)T1T%Wc9d@;EAB0?Cw$n?F%;yF@!DJpFI zhg`{&d&p2`!jGEr=LeG;5+r9b6QSpHMRSwlM4xDhXMQ(i-#cXs61FC|DYlSv|A%4A=4+lzoLKnyif+>fA<8m_c)b@%1piCn@NSk z*_F$siKH0Z*H>J2hez2*74}K1rtsl;FGqRXH&)}T84OM8pi>d|wpDlTu~tCIS|M4G zQZi+&X#)h{NE(CS@dZE#0j4gRP!fn(DSE(HAFTf(R|Na;Ff-xrz;6bG&3$P4-neSx z%>(N8>fb7GR*;9$SqbiwAWZww|2j0ou`GZE@juXNLy_-D4$uIAiA@fe9#Mu4piKh7 z7M$47j`m4%*HDE=nztbr+d&OE(Y53gfzO*@)nMAAR`RY0tZpJtyD#d=ojFjipvn~t ztJpgetJS-P_%RA~VV_E@T#oTUh7Q9Wn;S*otdQyUk3WfkbE!C=U{2l{O0$d>0B2LL z>_I)9%PRIh?euE5FX_?ei zWIQchPv%D?nrg1U3(Kqx#K-psEF)YorQ)mfh92cz>G`ZCo znS5~y@;mj@HGs-RLvadaOf+vm|0I;qZ%WB{jn44(DD{@`10 z`CsqmwicCF{9pBV3aI|vzvJrDN&(w}yhe<0o1#DUfV%TL2W1$gB5Gg&&D+BfuXrED zBlR$R?to43X{`eeIhj)k#q!LP_9i^6LHnzNFzGjpe(xk7thfQ@F6{zFt=VmqNx&f^ z2!;4wb`>nGkS~d-E+NWXK*Sgv<+Hl82ryJi3C8Q;grvj%O6f4zGep>vWr4abSFPl4 zBd2pObjt9jTyWP&7zfDZgcUqtXya1bbIl=47gMWn%Jrf7`dd{2vORImA@T6i1e{Yq z8I(Nr67s=ZXzDvBB+DJvD=k%HiU4kOGn`kOd|B`Sm%p_t;$XGX5qTiq|Mkw->%cH( z*m2@1cYp~}dUzW|mRpO>6)$C;p+_S_eMafCxVh#7Y%TZ$@(3 zrQA>wdIiGbt-p*-LS^TFtCC&!yhR}Ysdh3*B2QiLoULYFkU~aF!G()4X&RPTIRgMf z!GL2Bo2CZoY(=?38j(QkzYN^pAS09B(7kp54%8YhGm0n2%lCwg*wPSSD$el?rB_h| zCuPLiLE3m^86s*ZEl1Ay{znCIlfr~6I+ClvaQ{YZHgRAkGM7u$RrH#)ldiF1{S83u zrZ$$1I1z4XhQmR;D)}h_Cz8Lo{u^VIuDbBfG4`hrdgVe&L}vq<4;eW%_&i0W5l*QT zIFY{#*-!8w(>nx0eaN>G`zicrmEh?-LX1Du40$1~<1h z^|^i|S@4=9PnVuqn#(aKL%WKyD4}k%UgAUKoS+C<&Q|=@T95a4;q?5oAC3rp_4uEv zD8dZC>rr4*_ra<{!Lc~FFfG4Y=x-Ib)80-lGNs$ASLQa6lvZ1zu+$_^GrrkY|6pOM|dGRG}IC_d*f@AqDv-Mc;F`qHo@Nt88*QILjZFqy@R_D$=8Ud zdZS+*`7?MrBORT0Rtbm(I*XB;cEwK4ijRYPW_hWnOl4F)Hmy{RK0qkiCW1B>z+@q+ zyfEQ$^S}JmmvX6Cfl|#km4OvtA{W2|xjv77lAc{VG5_}Jt!OX7ngTV?M1GSv!uZg_ zvLQ0Abzo^!8r>rjAN>= zWmb-%LK=_T%J~y&NS0(H(dwqXazqe3P|6_|@R-IC?0VYRO)ECnu$0Wsp$)O4^vzW0zGAout|fxF zr4oRF#@zb6>mRMR`R0NK;yApuz0p=3YRNIq*_k<0MOsLhne6uiMf(4PLune~$;HPY zkxB_RNm|`}wa&(Pn$x{d#ChtFvO>$H3K=^tZzRodny(P3?r1&P)=SQQ^PsiRoSw%(7a1Olg6d7V%XOh5qHu+^3$ZnStq6 zB{W)~Jx!#4Dp3DP?9$Kw(4?TdYynDsWHqn^Q*jaC-vuRNsu|J!@*he2gLv-jmLf6H`*9+e?^VSyFn?v$IWM@r4u+%-HYJD1b; zcrJ>cjW+m7co#P$Xk`aBB&t6Qs|fK){NJ&>Q&n&T;nJS11_5q&7I0v^NV3%w(J{%s z1tmv12CRblc}S*)lJE+%{$%iJXKdB_t66Zuk@@=mCY8hVBX4Fw)j&P8fu*_s$R8~L z`Ll&J{GI<#AJVv_?K4qLC<7cR13Ni_c7SaDe%`(qI2;2ryXr$GfLamN3@+jyQJM;qu?Nvtd@5B|;JHzYkP}hmjT0eF_m&6y_OPom#BcW5jo*dvF%FM=7gI!cKSPn$<`9T8jX|P0(X1E8r>GO#P!SQ zi}xm}ELK`_rSvlkXJp9A5rGzUjYN!H-K*!?n($ih8c_UBk$buKP0xL7=L-O@8?~?gbuMy z2Us?9?b+XRyngO?ZaB#4<#U}@A+T#m3^Tus8#uAoipPw&Z*|Ls!>c!aKp%gUOK|0u zErEBK#s_Q&tycU*n(aTuO0^Zl7#ittIA zrLn2rEYqAW`0ED54=(iYn1U61L6{n#mDoaeS%i}eht(Rsy(%rHvzt=8?+AAn)E@Vmkys?PSCzZl(vR*TqK zg_lA*Hc@Yz0WlmfPNKNkf(K>9f=7dN1ZR<$DhhEW_j7_8fI~$7S)u7`IEV?liKo|k zi(5C)d%^_thmVrKwIJ2Z54Tzk^%K2?5L{{I7Ld`w2dnR86x&LAPoV37nWkE@8J!_kdwHKFKwCefNWK zA8yh)7-by6=LY^JropEs#S;9?xk6)HBkV&3za~SkO1=8}o@;ex(pG$~&P~!7@Zc$e zk&FZBDjOI(2k2Yx&J{Ze8n)fsftxUiwBF?}{ivnKk$QW+xH_So>*`9@^#U{xX+-x* zt3DEdHsiFv$KHPRQIqxhSgmfDQV;@x&zHTZk^m3y?=Iz`>=m)-m^dkK+-8NqjD-;V z^I9;C5M&Z&(cMRl{e{;h*9~|N*(khf>a##5TEVG3X5N~f(prYZ4vZ#z1%-`?>Cn77 zJ7x1shyJdYA2nxgj@?RRJ$CPTmcjY6uQ6hPPLb_c$va{0SFbKbVHksZR-0zKO@j5 zt`V@m{e%HP*>%G(L1KMpT4)` zhits>6DOD+<$fm+qJNU3=;6M;m8sr7i}HNFqiXy`QAG)gJmdJqMXIVQyRM~A&YHXZ$H`m2)VZf2D6(Ksg zjQ7ljUJh{aJD=dF$5i4Qd6MrkOWg&K1;6L>PQF%Q3?!W>`>r45{J_ET36I?841?1T>=W8pp7kut`gLa=1%gt?a-5jL8oN3}C(jsZ zMD!v>-ZVl&p7#I4Ees468LReUO2-eZQ-RZERi2~F;oYKTaJByDEx`FXfT4&qNZ*D>S@#m`~)W>8q= zPHQD%d;~UN#A=?TTJ7Fq78JRV!Q0IYsap*TQz?o2_5Oo-nVT&%?8|lg3JcR_7F>m5 zlN>sV5@EVqDy&&683jFditKbyeNG9FecanYt`LT76Yu@(<0xc2xrP}_QJ=N$l3dWt zls=>lkyOuTn=|MVVys!_KXpX6+F(E#s+6HSY~7%;Gm0{*KFdZC_;XmtOcsHCDrYO0 zjeMb|D!D9Tb7~`~h;DuJ_V~Zv)+T*9Fnc}SIp^`@#7L|)MlFNM?Lr@}uS#-}Htc^> z3cd0u_PWOss_9A;w{+_kM2;r^p+#z*AA?Z*6F1@K-Fdpwca49E)xE?5Si>6}Zr>{| zc-J`RoP&`b__<3#+Qn6TPN$zoWxnz_bW8*9C5cS}kMTk1@wGrMLXyFI3r>cwXwaJF zC;1*!KcRiKaj^VolL7W~SARD|&yhp+fZo&4zF9CWtrz@Mfe zC^0^qYi_z+DUe1W!S37i$Jx@CeJ?xah5S&YNRYsIK&s(^Gy8QNTG)G0_$o{D&#&9+ z&dmOVFs;&Wiv4cQb4~3zXi$gyl-=*M2fyBj0KTAxMo2SOM}FRbk)k^<@`T^+Gl+$< zGpe~92s5+qUj>BOI^o(ng(ND=D+%GEhiWc=(*io+`RAG#e(HO#|F~%U1OE&QZVYL~ zcW;3f6@*ru>LOk!Ng1Pc&LtX*7Vdz!9a zmFvT~K_5=HexiV<)mjsb6Ca7Cq`I}w`@#vC217D?@AJrm!gZezTw(nu%m-7uA}tCM zYEV_0>LbCB5lbBDm!pKQ&ZHjTUEz_wmsHWVo>Lvr@2H}2160fPjGp?Z@QKRzH6-Bh|8cS zOp|ajIVrMeP)hfJ@aF_#kwVf3n#%kyuRK_YwZ?W~L+t0jbsNpyTQs=?>B zQ5wN}3qq&2QN6~Jkt_RKOkOfYFjMUjqD z(5|4FjI`ub+hkT1R>xVba4K5TftoV*>E=pVl}4Ga1h&Vz%q!Cqy~&aZmX%2)m}QCt zg(s_A2wRg#V=mJhe8^_Kq=);Wy!dby)ei>PpT^##f}rp@r@7U3@w#^j*n#2%5nsxe z1Z=M^e@T>;SW4#vB3+zb?HdmW*s^J|UM1jqf&Z2-e3Gydl_8tp)szS41*Oudfnmt& zGs;d}sZlm39YFpAFDt`tc5Yyz?qCbqnElH(9kM=n@X1@HazE8L zb*@Go@J#iw+|!gbTo^4AMf`@(1NZxdEVRZ1qE4|{0KuT65T7#{pj_~1_lJiy zqfSi3#rm3B8sHFBWd0N4!V{8D3eHr~EGtV7S}LzerxhL^4FboW0MHscgwMq$)DK?m zAIUNcvkH?b{?tuW1dI|pe~!ZEn3NJ~z-FZTsSrNBy1(D|7+d~7n1HRInZ-ROxE)x!qa{rBJNOu%=ZBY1gd=tZ+& zIM~Fo2{ZJubi5@l%d`iOJ5x%=!#zk9e`$V#4AHMVVA0+TOolmOk;FdjGsmF>X=lfL z&>g&L96Zsk-|3r4;NtKv<-f~g+U7zswr+ZH!sPCm!;i#VB~nfh(P|~*zIh=f%eEEP zEu?0yJNKiZ<}uT5$8@L~-*qSYE0N#`AP^FolY?B`pwyO!37zUml5C3R%$9#dW~iWT8g zXLv$j+|}!ZLwtC-Q}fKiVK4`E(X^_Czu;d>RDs3GFGdkit!TI!zUlEi`Ev-xRVWCy z0ffiPE=ZN!td^c=1ez==rFLkq6r0UvStJM{q1Ja1L7v zfVGIO12wz{mn)YTYHzdw%MW4uUCer-0i1t!puww!3Z|gAXiK(09W<$TMW+<9VCaSyP%@z-$Uy`m>w+WLa_J zzTP!{(ERhF*oZxxqFB|Kd^-1-Pw3N5lf_=HWJWEhK5isboZPdprkOmStnC?f6zd~< zRl%HsfddflkUc^i6$v-0ZZzxs&`sJ(T(+gt=_x2~uEC_8G4y*#LuF?7n7JU}zRV%k z6$A(Xh$EpJ`$@))>F{w=cG+gFBa8LdFC6o)-1kW+NndXarXM)5Vz8Re{!t53wF*lHbT|x_t;92C1E~Aet$&%s(m{f&Lrv2r+>Qqp)GNEdmPj_5p5w! znM;I-70S&5iwvD=1K$hU<;s`Y-qA(SZ^u!0Oy%9{3R!KBl9pAl2Hz+>LZXUC{xQ7R79oivR|(ptbIdv~ z`5%Ll^{cjoMVc|!eqvS=9Lo!dhTsp7+SMOQohi^j@_|gs7Z~|sm3sTOO9Lh|9jrTV z+)DKQP2o%I-j&%7+TjNV#Kp%s6R_O~;W+eGZK7!bto1Ip%VlNH3~^@?(W}W&y;re2 zg&9w?zgaIo;HV@M+7EBa{62?gmRYd-zHjVf_@9eNLDltZ8KP_mR`Dtgelq;mQM&B{*A^0vMc$i%))@QU10D>XD@PqTBs+13`l; zC-5J9aoM^FZ3sm!c(bk$<6V0UMfG-8`mC}M`vcI^l9!EStP{xJ+Mmxt)zA6m9pO-o z;Picc&M5eSQTQVnNA{@=J5;Hq7jt3lN9AR523#iHiH9lH4ob7LVO(rPp5tBpEC@)npH@vh+UajYQ8p?PXfPnhH2 zub2a-vNVsxe_Czpo=SY#R2E#Q4wdk|?l^h1JRS$wZcF{oigr}vaq3ML=^hN51;CYDvyx6C@ zYS@L7YhjV^(MQ?pCm9vqd3_6>;?AO)fQ7kj%XS zbO~z|PX3p5c3BH2{;tFP)IGzgpDXk0hb73CYhq zA2hg@g{!Tsgu2nthpTQPALx>&+ft6DvAaMTb?Yuhz?PeIsls!QJ(Y3^nHU|7QZL@E zS>ZclAv85+?s04)2wdVBvP>@VDz(kDs|-WC3~M?8Wi!Lwq&Hw;k${%U`fZ>Dj|vR$ zj5EU=vwKbr(#_>ai&`0WXk)td*1RX`4fTlSwO#462{w%V)nJGUz5;+Bhqr_Rf=xV| zPcHr)jb6reXB{`J`q*g9h5Jkio<+yz?r$A-#aLTSedUUE@{AsLJ-p%jmHMgZ73!+rP1vom ztcDB5$(|`Vmi3q>JdjL$ysnP4Diq@2*Dz63`NEw<7qVLBuT(ewkI578KW1cnx^cAA zIy!TbesAB9ATLm01fgyEL_>PvjoRc@kGhvD&+Z(nD+7mBEt?U^Pd1d`?%lJ(!1y;! z*N|iKz$@L$)+yewdB>{2l_^s69iJ;_21)va)+v;TDBzNFBmNaAI!ylx)fp96p}!R= zQZAo>tA?^gHuN8tu;5n{QC$P=hW?HqE3=70{D8kgjbZTp15JS>SdwBrj6dNUM@L1? zq)Qu0$b}?0@6=oRW?Jair^9*IuY2kqA|Nc7lppS`iSQJAwHkvXx&O!1H%3R=1zX3q zZF6GVwr$(a#I|kQ&cqYjwl#4wNp8>keRr+Nu3s&?&zmxZvmtHSgu z(zDW0bRJjFjweiwNgyC)TiX>>?+C4GRqvvn zdU%)(zvXG;DyN^o2~7$>^LhYIk?Z+4>uY;WzLJFOma*6RY%x4~?7{bE&}deg5C7%g z=w~v>6I&1X1-Nn#FK#95G9uYALR)3!>U2fFx`X-jJ>FQAj<}xmfv4Igsw$gs(?S&_ z!N(QuG$VHG%=_g!ub!$piYhh1bk(jAxs-<{VU^vbE@T+fw-&Xln%qm@VZFe;M&@z| zQ@x;UjMqVo187+()&m$x9klYY>*{?uQHx&XKZFHlrD7+mx-ayD{+cq+4-~d~=g>eM z(7qZbX~Xn8*t)~PnrPDS&nW0b9#nAQ*ZB(l8~>u;d@Y9C)}!_c(_mXct!A@;owzyjWe9GpY;*_eTcQ5eFx;qjvt{vAbwFRE;{AFTNml|E=2&;i?g|bkY3B zzv3psBUkEtG{2*Vgs{FUId1eJRrX$lq{$PHsN>K9K*{sz$i6BZW8{ePlH`jEloAf% zSE<=@{_mdWnG5`N1-qK>2K0)`P&mnS-PV=9$Q&_ zt=|dt^}y+!UY>7gx;QtX0dhgmi>dbM6yV*A-#Do`3LPXjS?UwxR4z&Nu@FyS_T0qD+zUn2z1bK z&ZBFMp3Z*uggn?Ct=WoEW9JH@Ls%GNwfON<=*m~azNSOKqr{9H)DKP%YciFLt zHun7Mz2!u1PdPoJ}5=c^C&G1 z42=>}A!mSBIP(4D?C|iu#nn91NX(dNk!4wc;UdbPX1DIj)Q4j%5&w+bQsX(u#}yQLOGnCrn4n6uaqwOg zC*$|j);jo0L}M)Q1-cu;L6C%QV=U&@SR<*=z>Dd^T5CTk?hMnSB|etWxb zyVT|S#n)wxI!AC&#AYJ=*{?Ptj4gT~RKOJaN3a_pLYqs)zXD`o@a6%$^am{8#)JYa z*o@>h3lYY+PD#EFB%GQKlaMolq0m?PBc_;p8(=TB6s@aK48dy{&aetB4IW++p8-9R z4sX)05kVo)VKRvRJ^pf>-^{1wuDQD^WBZQC+}|$3_fJb|aR-y02=nT7DC1;24~7G$ zL8|@=_~oTXaT5-_bLgqNoDm|&@qWJx{$TpXkCEx$F>2oi*pn0Ux9825UVH`&TBKIJ zeEj*>wIw0Of#k|HEV&IkYsQLLC61sw5ybmGZvv_6IRBv|bwWU&Z1@qoUj)1sxZWQJLn}e4>zV)MN zoXV{jeUb&njVb{UyJw(74ab%R(S6mf0O)+*49Qa+bQV?}fBeg+A6^d(Uz@?^7 ztk2PPD5;mLN}f}Q7udXxX??&M0JamrVaK1q3&f~CurlgX^YM#V1I8R>JpRtPfBECK z-7}zrZ{7G*ux)fZB*sd?8BN3`78Dj9d`@WQ0Izry%go{!ZKCykzLI+fQRtbrM8gGN zLJgA>b}z1TlwZzDQNijE3ahvr>EWU}4AK#=c7LZQXxk@rMAHDjfsl62zlFOX0xAFL z;a;NHky@p18nQ&GdCdyAyYtAFl3$x+}leSl#sUSVO-_M`RRO*h>y`8gBqn4KBHkG7A{@`+M4{!z(j9Rm^4xHw8x zn&bl0Ub`ge=Ql9hO3CfXMNMrCjzt~qtRr>6ts+4_I4l0B>gC-P=X4qVT%G+F|cl zn*m;SVNP_$+8b-+W}TH7ZmawNjyKb|Zff84QpdrBZmN>uIt4R;b%{dL?+qiLzXZTV zIEDJp0Nt#@7$nrC>vRX@6lI6~ZAI9rRG^FiPh*vHteK|NXCdH;<}r`k|m(>25l)? z4Wf9h+nd1=H@}!VS74lt5lVkgF9GO8<0pzHcU3|sQ#me_s!cygkIck^H8x`gXg$4R zj)6%z6zi{+UU8a{hcp5KN9ih}=6iExa;@k{osJO%H8-y!Sy8B#=`tEn!M-iS_S0m) zErXVie_6aOmp9?a#E6({J@3J=&O-Hjm!_`wE_fsJsuUUMZA2nRVd8555=s`47`Gsf z)rGcBVsl=SQG5|w8;na2`~VnXA5SpA>Klb=5}DeFYhoMI;@eV+Dm+Nq<>mY|59q?{w3?=GJQqCy25X}Vq^jMVm^G~wF_&|fUoFRFJV5nZ*oJ+_KfJHWZLj`wp ze5X9%GIzH#DUBo`l5Q*`1llvPEKfa2NZc{!}Y)uHpB;s}2!JXg$?t=KXL6 zkA7dN&hU1`2`VDBB6=`B-F^?rLKnElN?cH=NR$`pLf3DbnZ=Eonk`lzNkmg1{~3K3bwW9thO&l-OvHE_<|1dh!qJjl;_hJ_qppFB8~9nq{~ z2@n&6BvG{wXvB9SxF6fxeVpFSl<%K4n9Ce`GtgFrPr6z3?J2F3`OaIq2TlpT7VQpV z?tN60lJjL@?RLxQTaJB+QBApBC91NRm^-Omi7I*Vr)EjZ8?~^k4u*LYfx>DU4KXN6 z7PNjT~01qvvvI?qAa8_7>!D#rV#Re6{zkm0XqN_`i$lQWBR~lk}Q{W5WOk z97Kmt^6*ZV25Y8a7sFra%WfUS0 zrY=E1H{t2qhrM2(n1M0Cd?$|Jx{4}S_w(V_zN;q$eUfzq`c`}cAPe~HUig)(p}<{u2y zd78VEVL`2~KmepUMf%Dv%E|;q&_$0 zg)4SV8F0>=748{^AP0jAqY=XDb<2D2i5^FTk+AoUfkJ(u0>5@-{`M-AroV+j5xWNtd)}pggq3RT2GvdJ*3UE*=;{!M#*_O{?J>wl7?@1%F?Vj_R zO!iDf;xZ#hpa*Q*=ztHv9zgH|LAz)p$;$qZmPg&Us4W|m*6$&zGt)SuFqdOAPwYBq z?n9NXYSbJBB{Z17A5=r2+_Yg|YcSfb1TuSgY=+=!1?Z+~M+*}$+N?j(Q*>B`lrl@J zP6bs0<7u610CukpbJsLK*sR?GQ1=)(z&AoU145q1Bp)RS^`HiD&ZMevg)jjP!V{&Y zeB&A}Xo+h8Wi>*&gbm(PN%UV+Xhic8)TlpqT~HK5l@(0Dct?|ztCo) zwxF*Ri|$OVOj%BRhx%O82O~rnVa=Wy$X@t15~OGT*w(Y`tF-&<##!4G^EBS z&k&|s-Ox)=$8=QksrfuLQ^$A_S(nw}$cgk{x=iv;f2&oCJ;<_Vn5REgq>Evq@uIe6 zwa(COTg?xg^pfwe#ZP8Yg|d39uNFaPfglw`iYzG6S z-BIG}qIOL20=QL4^+Abg`L}%572+ZTd|e)e_@%+EFB&^wfP`e|lDy zNQUeV{9rO8EdG&AszRr_t;BQ$oIY}cr`3JN#iA0!%1=dW>sg3CO1!e4i}{=41dpkJ z%lxc}$nwXE^lV(KzOJQ9g+)9{;$)UeT({anLgB4>NXGd(^gl*+@!KO^dJsMOZtH)F zB8mf5i6N=nk^jckvQwt40qp-m+&Tu!j*Yb+FOWSN@&9R51l7j1L@PI#uz~xy6MQDE zM#>4P@~&PFoaMVPt6JCoFdKm)EEr-|A&Zv~U_I&ek=$bmm(4kx$jH1H&vaA;3wg>eN6MH_F6Jn!oAtLTdhwB-ccE zvD#b8PZ@H=udr9)h=i{X)?0_IJvOvv`43bQn4!|gVW^F?X{Ki;ub4JLlKEg{lvo^M#t=8~D3KskkR zPZ?&izJiJvRHz* zIi3zNO$OGrA!CcAYAdNh@e7z+7o?CO)#T0uw4-po8SJ<$Z)Eb=P__p$Y;THS>TqOeczGD#Hq>wscK&B1@iT*bt0lDA*N|RGI6kT8$8RrUqtJw(ncY@u*YB% z;IYTRjungIO>gbr_mSdBp<=S~!XV*FwEb+6C2bATr#<~m5v50cI_qvVv{q;w_VQrX zf7B;WI+96*BR%VI`no_1gVb0Q=L4LSSHj_(Uul#yh1C9c{VVZ|ApP%p47sz#g~I)Q z_#*Boi0F$2WX~huQybhDuc5-<*lx2-`|x5tx%l%P#`{Av{LyD0>}YHYYaDM4%i+0OP3?0HK74-6tYq zKceuUg6Ay;e2}F@C_uaKkiv4g7#Z!m-TXK?;)c^C7QsG)dV#m~&;+BoQBwpoKmD#m z_(|UEFQTFsY)4iRYE_S1$1emDBKy%^{EB*x{UiJNuMy;8-{*S0z-!Xj?p=LT*(V6Q zIOQRK-g~cFhP+pVvr@*rsulHAJi3HY--MJA&K#c8N>-e^e;5v~i?WHyjJlCjMPs+c zaumt1e5_d0n)h2(qwvkfT;ECY=*&AS7!;Bc&pA4Hgr<^2B@ps*hQ3l@N7?9TxcL^~ z*H&yRLjj!t1xb2gQ8xAmsFamm+@%bdMm3YCw+be|ATX%PMu);zM8IFIX8LMK#_=&9 zcd#Jz0i4OoW}cY=;7zQ6HX-cFqash56Sv1m9tCCc%sXF-#rgSxGYTsA&4DBQh)&zA z3Hg1E&^17msaB(TO@jXg6SZ1OE=L@kT#?~qtM!nM!-qp5324`Zr#wZ042#u2CFnTs zKl3_=&Ms&eI1iS`sRUxhaotl&FTsG-5O6Te_LwoK%#@nRmfn~73mUo{ljh&aXUZkqaT22I=SZ^m)q z)86{#`bofZAE7EU!G{`}F#%&F)9W{wRMT^l3o&I(fEL*!V}R;0I%>kX_WtTb1h=w{ zf3H`J#gL8r-ro%5rY!wQZ3HEe>KP zC+>BuRv4m5fg$V>1%ZH!vbRjnKcoEMHEH$lF}G9c}U5}}Hl*WFtrwaTGqCyndb^eBQFVU54h zP~4M`AF7|+G#>XJ?0$fCr|j8GQIB+COmnWCL*22K4BQWC2a(WaG8OL&m z_FR9DcyL<=o&$mHHucU6!%iEgQ^+n0ovrzYSt~5~`q-SKe=3Hb zFzul;ZQ6}rRnowzjU0T6d=ZE}mD`IpG+wkwx(0kHtv%JTyQ^;d&n{XuH?0b5``naG zf}1!k=Mrn80^y#yx|kBO=7I|KAS2LVKHB|ydK}C;L>gdoXwPJ&Ee^*~9^`n46wyK4 zSRYcQNuWggXxnP9bx_jEg>loLPQ5*x&%d}1UPBIEmaJJ}zVS7O0~Uv-8V>>i-L@@-ke^dc5REicBm|=XP7? zd^d8Wzu@M7xDi^&iHHG{s4pqe2rhJdgA3T&|LVmy9#ZHuaNuX7H!_mC_WmKg%Ki%Z z#@Lt5Y8svJ$k{0}=W&v3(}4QZDT_Gla+-18VB<-1yJPke5u(-iP!?>f$LqzA{Lf{B0PFWtzf{C-=f`}h&;>16GQLT=vH@kX-Vyxmu`mN} z0=$^ZhmmOI{~gi;IjiVC4C^c;V+Fd(VRT8%$x|M{VcV%5M1n3+HymSH|{j`OS_ znNsweG)td&c3Kiq+|yp1yo*+o1`tsVM-$(@l09~g>j&;SMm*JABlc;egSS(~zpCCC zQ&n*SS0xvHy@8GvSXFk zmLu$Kli)pto{!1=}9Vr;#rBhLj|cL zMq2fZRI2@0Rm-4=Af5jL^gA6O&<*02O~N@T49-f@tsRs3^3v~rcu_KzCDIGGIPDA# z^c8|@e=!3W$-G9yeB>E5rU4H6E3ov&dCNI)*^Q zEu|bHJ6jIdi>iC*Y)91G z=f$$jJ0?&@i^Y4{m|;HJy{E4rCqq^ZgabMJlv0o#pW`?;Eg{eOLOdH&WVNk3=VdkB zmg(ECtrXIMIXQJ_C?sre%siVt-tB)4LBX{VlYvIWt{(TZLzXl(MIh~R-Z-Cmbtxk; zN)`!T%44cRB4ZH{-=nL?eCx;N!z$M?V#wGw2PXsGUR`jkMFE(E7~zF{_f`b}4!7Gf zj$jr1GtHYZ(Cp)kkc%|jm5wGDAl8rNW@M?`O7{oz&UczoG)N9Y<1JWz<~b^FDR90B8C(2Pa>7jNr>-qi(FXuOr&}QIq2Q;J zb{Ws92X#-6JI^sXiG?XW90Kr&T;Ru!C&}7^+vJuY#TP59Q+eW{71n-zr4UnY4||;{ z1u|T!8Mt{xoSxtJr5b2N#$xy{AfnZ;2jBY>dVsB=yOr-g4dg>&7g*^t-Uk7{4%p5X zu-_NiJCAH`0os9Q5SlsPf)JW|GNWIvFQySip{Xe-8L~7xa}69tR6@_$q2ZJtQ^vgc zBKKbB*i1g2uS51(#`FkfSg16mE>a!~%_82;bnp$7f0|^N;eLdrIF1v6A9}ZOLaZHj z;u`ApH!=MpJMyF(vNE05#%KmBN5ArrOXJRu{{67vTqn=gM!Q7k)u;}5_NSRfo(=1C zm5xSaDCjH-ywx`U#m*Old$rpCP}^+TrOQO-MT=G=tB3U+;+)2=-9w8L4ANzbTkX>d zLMUCham`G@zenf@!DpTQR2a?kyj+RB_j$4+xLYlhdlzD}i>2uVT z(PiU(6#Dv@F+{v`1G8C)3HXi~+J6k@F z&T&YWD!v5`?<4Wow5LFD>L9UP>6jj`_6>(s3$e`OwwWh`AMI(ZermvVdb^5H7L%BI zmv$-m9MS2`k~2e|^R(m0(p9o1L1{J7KN#i3kt>5ytZ>)K{l`2qWb7ZDn8QWH_9JUP%47ih7Xgf_>|A#iL%t!UTfNE&EwU# zVf;H&E;j_63v9XM*R(Lhj<&wW^?Fu#@T{!Ab_0e5eT{YsE(Kg`D7KaeZEv4Gx+Qld zsX`_5FWlF^-zrczT{Z|2iNu|X+7IK z%1K!@ce?+5@t}!K&M>cGaKZ4;EFC~I_J_MPSo7RHujkqi^Nu>3RmjnvQ_&D|^&pj4zWaf(!Y1;hTK;)>Y_pm`d3`@QZ|K)O|A z%%$=2mewrNsB5{n$Gm_Pl^^HwHRm4OTAlysdITbhtFFse!*@NlbE~5X_`1)m1R%BT zIla_*_9B(0Id5&apl1(gp2e+RjrN`FjV`^n+xV+S$gpt2h)RW$tj-E+(Ymdw8PziB zb?Lkaa#<5lWU3h0`GR5b(`~$Caq~favUfc%UeRH3M1pQtr=hf|bw_}{a5WH*uWV^U z_1w-$v9_0f#xG++r9BR+QSHqVdH0tXAhn0D_y>ry2jCDfLWr@b!Ht}|1Zl}*#|_X7 zug?E@_0heSj0U!^(x>KKFIf@Bw+IvSBz7O?HJMV4L@C>Hby z?;GQv1&v*>_EV?#O}P2%K%@7W>%dsK4F>L)?j+ik&Bc*^ZQHc&h_g&$sDM@>o#AKKeV_zKkIBC}Gn5NtG3yVKmclJCjM&;(hg zIHOl{QA28H$`ilQ?tyC0_PaemUJ28FQi?)BP3jV!SM ztJK~I?}>4rZouoU06;S8UHWFcg#7uuNLc8}Piji3*A!CrY#3dTPliwJ>E4d!z}AE~ zbzozQ0Rbz%17W&X;WLpU<1nL2t#F7WW)W60tLHL+t30~=4J$_Go^uTMTJD_xA++PQ zdpDxxrU@d-u{Wz*&~N%{H>1yXTz+2@>z?7j+dgiN+w{b6aY>O`nnA#haVP_2gCDJu z5nI$yQ-Ch6EkJoRraab(7BbHWhv4}9{`J}8c8J>;7gu^LM%ww_Y-7j4Hle}8#5(4e z63W4%I+W+@#q(RvGx@A|RU7t6mznNhwJNK&F7q#zuf}iDB3o>S#P~^`t`XUkI%6&g z?(Kg?sNca07#ozS8U3O&_%>ji#Y(}PkOnzP*o24K6X8N+oBA3hw^(O*S}O5BNw!F z*T{C+(sM(eADOiqJYkn6Eq9`S@Ht$q{(e(`i`99K( zoj@|y?5d)#o;QT-{>}veolyAT%c4?HMWsc69qXk-Gm%G}PKQ1U!iBoKM1am%E1=AXj!G0JGxC~jTIs3t!emx1`+5i? z5HFs^gob@Tn#u3V9c3=yfi)mU1J9C^F0+d7$LmD>{nUn~WoN&`^Bro_XMPkz)(f zdRnXeu^VdG6a!?&4tilH?$mE&i2@@HQ(Dv;s_g+g3bOFDmTxI=nt^FqgQPQ1{6)K@ z-mzE?a8>S*>lU?;*5JK5GwKPHafo6j;Pr>D<3^>_ILz`Yo%* z(o*t2$AoKz1efD9vpZ3p_*kliNK3$AcPt0HVao+7;B1aXy3%s_$5Wr3Zt#4({k(6Sg) zP?UFD@QU5%E+n=Udr+@LwCcP2M73gG@S$yer=4Wf$#MEa2p$zh2Lxf`x3)Av>^eB( zQ7H}u!ym4NU`((Gm%AWqWl#ykMcK^x*Oaiy9fcxKJ6ZUzvrgMl2iT}joHC!;V$UD3 z59QMlj2wmngt&Gj=bif{DA}qcz$2U3f8vKf53TFg@+fVcN9?Vjq&;Wp%qof1Z1rQx z{MkyP(21ybz1Y;QCRNI?r4Rl3Rem7U?~9HNu{gWh`uQ?jaNm5&5@=SP8o;~K3Fe%0 z&L~+)MzQ<0nXXV;E4LtOM~)`;teTjIo^;|Y(}!1H1!~qw8NnMtD`=nh@0={D$?5<{ zFjno1L|x?1J*eHXlerVvNH?mj?g$nbbx0|NW#fe7Y+m4k(og>kODo&&eX8{de*xP* z-k)HUR^Jgx-M;7gtnZoh;_oU3m5Qm%8sMO0Xr5@@0&39K%k)0Tom`GRHCj#!^4HOBH zi8q7~R=PPosko~FAcFnuw-;^+EpC^6at0t6%lizI9sK$$$Lb;~5h047={= zp>o~b9rqrV2RNdKz2qyzNFwm;p=sm?W0eV0{5*R|-Ja#A45t6S!%NQd_EXmNti(#{ zA@>iKVe=M_{Wmi^uK2c6CVy}V+^%2YD8BKdBp_tu{NuT3Rs7>NJsL@AY<L{^aXEE+6#_WG^2n zVK!f%_rEL#cFZHLU-8|=ssaSPPWz3x4BMF3NL!gX)s13}r`@##+hF}QfBl-gEz|!A zt#Iit^Vd-!roIBE4@Fky%jLZY;5bWsb5A)7Jkky)hMdH*D8B96OsL6?i7@MsR<$axx)mvWEMNhl*rBa z*5eGp#`9HWe_gFRGRXyub;n?Y7JnIvJA*wc1)UpjTs+D#WiOvMO2F!{VF^oA&t!gR zT-4#LjNs|AjeXIvztm;j&8fQk=;erczXwU^f!(WTJvc58A2DpqrB9FTq;%SvOZIlz zva4o-hF4^UB$v*bv2Z!AYeWy2{TvO1)y1%!x>27#Vzv&)D;m>xaAYqdNhW7Wl$w14 zy+6x9k@Q~Q-B;v1oZ)@+pb6Ld?5lnE@?**=?t(pE(_rzcKo5_R0efQSUBa0K-|VM` zKzt*$(Ay&pVC^QitE`OkGtgG46er-I>U7GC7h1=eWni7_JycV7eeo`~i`JgRQzslt z=c?sbwjdV+$7C7Pz+Hc?0V772o1(jp5Dya}O?C=kMvK*i%4}ZzV;bO_o14JZ*A>C@ zL;a7KF`9;E9~oKO;X=fY9@-o8@aj62VSQ6Wh|VEO*sPk&nj#}R zC9Wj+{ORp3h6V9V>FvG{Yf`++c^~ZKGRNV+s{eN9BY=5==j6qiUcv#~uly^4jK`{!dNKK@{BwGXs;Qugf@%V-VTIWD8;Xk_=U4YX}Rp_to> zeIELv%nnNKJEs)z+fL_yB8XXmbHcH^!8JAthz?2Q^CzvC)2zdz})5oki6Gd?zbCOx6DA4YUb2UzMziJTF=aMFAJ*Pyn}dGcnpJ=gTwwV5=BwI>+X6^FHKrlol1;rHGQ!~=`>s5% zgJ*lLiFaScYuSL4*r5eJk1MkZsSQZ{R4RZvo1z8SsZr|`q9k5V6J-9DiNT(Fq<$0~ z3iO_rBKqT6vWjf!{cW5d)g`XaAm9}wIS1$D zaX%)G+ZW`*^TD4e1{Sdixbn_DE$7ZhyK`ThoaG@pkxSpJ2~>ox5ZG4xxnBNPvxDAa zXuy5$t^YlMA?%e+?9%&cau}~K1%kDq_~p^w_D+$v&Ha6hTO|ne&zYH|WjBN^PZu}V z@H8^db1;2OUkK|y?JAfs5Nhx#oZBlVfQWi5?^=y&`#TkkX*3&yKKF8vEHuAE)@Xj) zYnsynyp>(e+SV)#W7`fpvB1!K>??XF7W-dX0v~jL`29;SKYPtaa4Y<_E3uzB@El+0 z{KWI4>V~$sd27#zH5HW>BVXyoo?>6-P3%V1R)alP->FiwU6G!a6Zxtaf5%OESy{*V ziQzS|2Mp9T%(9zaq-$Bm2N~Cn1g$#0x)>_+TL`Wn{ z6IFucUS2N*a9>Vx|=>0&mh-Y5QR3x+Xww`Yg$T5tso%gX>ot^ehRU}J+11Nl} zeBG6p>DBK<3%cgGk#W_DCTUrZrXHE{8h`lLe|^!}?O29_cAHmIQiKl{2C}Fxs@!PW z6n|Aeq&Tn^Q6@=_eiC0MmRJoXF0EWNbj4uLln{=!v1{(n0NXY3?(xpleu(86_RT=Vyr}Fgfz4*sf1O0_^Kym>3PlHE|$~++DZgH(K4H>dPfT@ z$ty}-A!PfK3kk!7(7m7azvm2B702omJ(r|f<;W#URkQVm9f_;11z?4qWf&c0M{MLsOPf{;qF!2Ao48AC^q<6)Lx+C4KnRaI za(t3Qz3~wp<&Q3T{-qxi{GGp?Us+(`h|&KJT4VW1>wIZb^W+ILCvOhX*az!}kg5=5~Reg}ta(FMBn<<6zhIu?x(< zbZfcTZ2H`fKLU;(4puPxZT{*jC6eW5Xx2Z{%OtsYL73{OG%u4^n$Bw`d4EADmo_&= z_W~JQPHAZ_A5L=7a<=RSap7?~XdWY=#C$l`!G9B{uyL4k(Dn^_j@CZB8!abF$fqMs z)%eLIk)o)=x^a9mKl9fCueO6=HpOm;-k*AXLp!3iF5Q%%?j zn1jqMe{y^3a8rq-eDNZkXfZc>u&JZF;a%2T4fJFYCAe4i$ZIpGdmH-AQYc}H>dI&c z+Ek$I9maBF3B8V1ef`6MoIA!>dv4IY#Hz&9G0+Jf&CH0UVWDMsV3$$HzT)va#k&Um zGu}+KDJ7loSMwxcYj_4lOms21@na|Qw-QOob&ZyDC7xO0D0DmC`L;(xj^ zWFP|y+aYRay}AcyY2iRp*;G}3eq>OYN7!f&3UFU1rJV)P#M|Cao>$*4@!I8ph_0dt zF@BQMH$rd^4t^RJ66%U}Hv1zz{`4#iZuK|ZMdULqB(SX&G`Hv0{P{IME?gtH7;|C- z9#`dL#|;B6Da`|?eus_#hnPlF8J0r{$|et+Lg^*u!`l?&@6FB7hALd5EYD!n@w?jq}6olF~-uo|xQ5%3#EmFYyGt>SI_WW}!c;FG`_4cUwBq zfaR~=c}tZF*PB#>Shu2x0E~wZevy3Nm-|xq-FDJ#mWCOt{kECJC2j3vNqUvbQ(0>- z(o?#C@SjUy5z}T+=E^Kwj~mjX2P%A40&UM6N7LXMd{%FUG(1EU>75hGlYYMz*qTx7 z+DHv63DO6bR+@}onAKgk2BPG*P*=CdqRM869Xn^1t(^SLiG?tX^KO9Wj{0icdrmqw z-mSMRbrUxy2s-F4e7qm}?>R6xqUSP>u#x*bxbFD$_8Kk7|CDvn+~tr!)bYhs&pmsY zBo~&HVn6YDs6X4Tk*6YjcQE_w~i|@8l-zu+8VQI z<~JME3zOrreoZX3a^^x;F*&QKm|6SjEHNP85+hAE z2R?F*iB5+jhuunNjZf7e(AaE}MA_0=#pfhx!SHN*Uh*o#%=24(ERXXjF3#+b-XBFB z65pq&pNjigRO5Z|E624)vN{~5Wck&S$_GTBv=-sgWh`%7huLK;WtAQ+w0|}`15VHh zULkBKt%T-FE{S63v;*SOIlt%&7^KkTU3%OH#|pC=-xdf{O|_J@Ou zWc_~|!4H?sCl-afaJ?Eqx#X*pO85u28GF^)mn|i`CHwp)32#t;!JT4X;f#car+;udoi&h0rO6y&WO`%*Q@|i}$jAdwd z4*4OM-aPDcCg3@zzICYbk8x5g(_4`mQwRU#2JeREW4jO9*w9b#2JbKw;ejejaO-R| z_Px+=I5rU*KxPx5R~$##jc#6WB(e?{v9{jm3m;K1LDVm+;7D*GJJjfnTmyVbLv{@N z&iTt$&{fNAd>q)OiNzESY6vt672*&cg@F-rUj?msA32UMc%4YlwRxYs8G^Ukd`FsFG-s4g>Bya~OXUh9;28eb!I{BofA z*f5$sf<=7c)_*4F1ub|1lDCIiH*l=9A3 zS3oZwUvJCGQZya)qW%PW9}Cj+NH!bx@=)4Ko1 z>IRJUU;{33tQ9Z)`(b=Do_g9L;rI`@EgS}~{H$YG>J}(nlvNr%^L!$C?sBIWYQ5Q# z0rQag2tCFa=pXC~%1MeH|MK6WDxAu)Pf=RpgjUzu>+H*(u6x}YXGCojXG^4v5cB22 ztnR*U*cSZ%xJU|*v66TWsVIJ+yOX4!r#^3Y4m=gHDuHWJecoF07Qb4tN#bp|=3m+r zVD$%=LZK%D(CVTdWLK+}P1 zxuSzetAo^)tk&N0QV^*0Vbn1EOWeovEM|~$to^b`8HBVtvHKe>gHHTXRWUVgFB#Rz zpaiKBQ*bJq{}M8h;6lbb2qpo9Y$h>7lw7+#i=k^~_nNKhqKBu&GN9?JXNz$EGkHx{ z&ApiY_MG}OU#*CE0XBuY#oc5-}!sRciyrT|0+QuHm~e+AQ* ztC0PiBz?{`=om5=im*G2^J)k8Jq9-b8fZs_u-e*^@8~1XJ-(QWF*YLX05rOw?`)hJ z;IYD`Gakg6;V0Z=b)>+lF;W^}*D7SjNCrFXsiX89om*uMUmfY9X(Rmv?now*y>QY7 zqNVgQNV@*Yp`6y6T%^(UCS-BnyP*BHb)GcpIRbUZZmb%+N;H}yc1YVGp$B_dMFGOh zR^2UL7XeLYD~_Nm&rWPE&9y=QpHv;B+%I~1A|Ay>B;yuJ+(ld$ zE%{hXb%CBZ;1DETJ)>81W^{>*GVIqprs*wU&0|2T8Itl%&T@dsQs;?FGFdsRh%{iu zYE-v;jd=eSTy1*d&mqSD5n|j6F}?x9`>ewaFSM`X@TkK)j1P^C$f?duy-a|jM4ctn zWN~`bnE-JPR#(kkpys`#DuAh1qA1rW_T-d?NABhSE{boZ(Mr(`6q)l-OgjTWsD7V@ z@0;oCZ&Cavjs7*-9Gu*NCxS68l#(KYVTYW+?J5TbQQS&%h2n+z$2-|%SX=gMHN#r< zyX-Qojr(Pe+*-=oLNlzXdlDBnJ*Q2t07?1o{~n*-H`GqN<6p{eHr$LvGk0=_!@iAL z!|BL|zeyr9K+N72%7qK&<%^~}FLE5{Tp&Pf()y`f@X~+eFr(Q#Su(2k5L3$nCii~zQznT?ocd}ytnBC&H@-s5anW}5tUi@YbJN?sA`e`W*h!uN3 zi~@SQ^^F=P+HkpD+cp$`EFcDz{{=%nA-N!K{i~R9L0ol{Os6Yx4bf_z!%2Jx~c_zV&q|UajG{6<3kt4GP$t`~C-? za3w(7pQ88+z_2LWcVl{f>eG`njv`Le1r_mPyc}z^{aBvuzl9YCemZx{v#J&9Upg^l zbaHRmJ1ntrf_JRPqgC5XkCx8vL(;SO_+;{|0u-OxJ~yTmijpz(O4{g*sm5U2eXOYd zj!1M{Ly4(;sSeMU64{mYPbu-KkTMOz>%B^NB|vzYYoZlned-=p7=S1j1AhYv z4}Hm_CA^J$c39(m%s=I?>m3}>aLBOy&W+Fg&gZ@Nf%%?!j2gx#!^zM@I1~sau^Hg` zSW2EjjIHi%>mGS&bV18}&%!=W<&ZeLNzXo6ZpfevM#2?AW!TSp`a*wr5*r9rvwfb0 zXBc@WJ)JpmNV?0Np|}#juPT!)OtK2B<#|5@g?Uz)C~QVzqo_y}Mn75zz!?b-gGNTD z{xD`bnO`Vr-ao+z@K9EwNl!za%`}w#4sdri%)B6_>eChJR6TFS4T-wk+{_4|z zH_?CopP!$vYW{6z(N#Jz(@D?5sc6WZ*IhLa^T!lfcW>o9ojo8Zvq7L5{MeogwOSYs z&#nB%o+pOemsl}lc|u{@Ig$+dT`}-%*Jh_4E0}O}@7AgN|39D}yTye}>Ug`w#h65h zevua!aC4pd?p0A$0>s9OV=Gy);RaUheS#I6Kg5a~+gZ^Md$k@bkF9ViZhHj9f3u?I zc0Bf9tT>K+s=b?8u@OI1!(+J)mwOxo&#s3c6)-3!OQ z7w-H`h-=z}1;wV*w z9u)tKmeDmYqPJQeN4?jhcn69vfMM|I*k7XfcQDpRP+&*qILN9B?)>T__=I1V{S7OY zzmpX!5h}d`-Cd2Yt$vObYtY_WjJFE=b5#hguKFw<1G1Lgj{<{P{tqaybFut`XyslM zsJsHP&?~Wrv2p;#O=u52SPLU!Ew-xHpz<12Uh_2GdjZA!QD94B)hAJW8O3ii7=OGq zERm-|XZ)|2pOr{~S~SKl8%!u2{)Ey7&fj^J^RK+^JvRWG-7k_e)fS0*{Y{B{RU3O& zA|GY(47P)TWHb(M@Zia;W;(tv_^Np@$&wii=4#Nj<3l+-rY~X!2Pu~GCD|XA?a5Vf zkvFC^2Hx^K3Y<3D@sU$GAbf~;4590j7WR)He=|2#u_)DO?d-JfhBJ=n8*OXwD2 zEV* z(*;74=kYc`nik{Jxf@W)q}ssH*aQ|=BTKJh|7%O|%ne6YHlQT|R_*d=k^bH~;<-1n}oacXe@Z_#(6RJYlyYDK; zv)9{-uN%~5ySFps$2nJ8-aKc;ng5kPQE6+&8)(gVgF2Jf3$I2`;au&26SniMtmrOd zMdLrQq6glaqjf1&hUS9_trK@qCuZ6Iw zp?Xu0kF!n*hM3BzODd^koj;Nc)%wFBenY&mQnGA<{pO)$ED5+3#A{vS$jZYf7l2BrnUAbPbGS1!`HvYCs z{|$Yz#Gj!HiOHeFOd_d_8-HJc-x)BjL=o9g7h!{}OWUAH&vp9q3a(`NYAj)#qQn`oD0a;>WNn7kC>x?}Y)r&j z6C;fK1EY-7GVyEK5~Bu>iHO4fNcN{%=_%{p@5%afGd*qjQw#lMU_T~i!{9&*s#~Ej z*+cVDC-%(pr@dm^G!ZH_P9~JBZMWFB&4*O2w;dfCV@!UUq(>&D3rz@&+0|w9mL`sN z%conjKHcc>baU3HL6j#a$H$4{tQ%>`+6W{6MU;@ytRNm*>u;1(qjos5Hl z3_5`EcSPZm-BOU#EHFhf<&TH_W@WWXkipQm(FjRk*5l2B`i&+>S;wsO?|M^jc+}|W zvB}^F34R;h7r?%!sW(iO=`vl3O@`tb&x<{tQkVh?P>&l;qb3t15lULfgXIPl&P3x( zh7MnD>_}2cBm;gkm)u}@&uBDcR?n42^%^+iAF);bN_@obNye7qOP3kjWuxYi#+C|k zB5GFahMz=TZu&{XQ%8rOx`>@sJ#Hd1T}dm;!lj8RFWU=eEA#x@IxPLT7b30bcKhR z_1uhK_Z2@MknLeVMSW|88m>rBRlbqV_%J_8_I2X?-Fb` zH~m>mJzigdqI)Aogrs4|UXK+y5YdIyBqxzjW&wsz2jD8P@;<(d6)VuI9&AY=?R9K2fuH-0$+Uk<{T-}Xb`1xZwn_+x6am3FxM zoBuAR?&JF~AI&(|-HW%aK%BuMWN+)dgkN!pXWepq`ZOz+z9y!g7^MCFYRvCEgdMEF z5|%QcZN!=IqX-x%|1nU4(3@3wq6%49d~b`X+y6U&9NMfH+!=$v0tAA~4r*~uuo=zM z@p2zJd8!WsM4(U8f1*R5Lw6A>QHh@R<3+oG`vsMF><;wmXP5xAvJ0fL4Jf`54a~z? z#Pyiu3T%F~WA@8HX8kzeH~=!E2%rjNqUlG3KrLpg4+(52l}!gGbU6s=FbMO+DPRLr z(f3QB4ruDZy0{Ep)(S)vk$b^|m<$hybuCU-_kr3eZOvg&L_Ojg>T%{^Q8h-1;H1SE zQ8{*A%HZ4C^(XiNM7;-FHw{2tBTm=%KqZ#F2~7dni?F$WEdn??;oV(|8T8?dW-|!5 z?8%sVQ2!{%whVN265KfN`j~oxlfokQBmQFbcTfdm>qa&nic8sm0d`_iDnTlA@V^^q zr+AY^*TvK{2bA%r6Ue5(tHp=~p>Pa3<9`@=@+!ehTS4vH@u~i$tXKm!*n+XOgNhH( z5mgjBkyoGw1Y8ROt^?;?ONa$>dV!p>SymhYcYD#42Tv@+i&h}Yi2JRVv@aRCj9xz=y=)M-)bspgKFt+e^p?}*U zr6`M11q2Emt6c$SP&IJcjpnb1cvuhI@BVvM^n+bIkVqap(SZmW3Y4MqyK5ktDd>%k z-|m1^X~)=30nbZL0z+tu_74}L-Xh42Q^4O&2*yE36$-nfL&h}_02Gl!n~28|>(fAc zik|}cfVax<=|nnc=Vo++j{Vl4<=qfL9T;yNB67Cl2l6j%g9sn2jj0JGD43}X5kq_b zGNvY#-1RY5Y=-Js3=X&fRJ;{WbON>v46zG`FZW?0PiN1bJG9 zG*;K(Kz29kU55eA2ZnZn^R|Ncmq6NC^XTb|Kx+^Whrw?<@XA_j z5A65^hJ~ygooJvRWYGZGv;&k~0a?5s@?aJELM}W?spUl{JwvQ$1xmI;6zoRm)EeScjyMh+%`}S1$12l$#o4XFTo@?Ag=5%=F$hwTY)^FbS}IF;-(5b z*os|i9>j^UZ9)*52Uzn#y_aFi+VN`{2~|+ob|_Lhp58?qfW2xuXTJCU#?&L`wC#Nq zvVBQ<$EzNXZ3Hot!(3PgZQqR_D$!IeaXhSo>LjXwJTA=%YmtGF32QD4LZ*{3zw0zkjwUFrLc!vj4 zZ$0L>0(3u!q6O{khdwHU8Fdu$bUUWI8yr6fdA|g6)C&}MK+RHQe@oEurBKXuKq4h=I0n&NN&7)ygEDaZVMvuV@Xgcq5G8@w0Bd9qo~5Wqii7Te zgj)e)ryb*^w0jK@X-gn@`Y}gaJAe&Lw+9v;W$UYi7(M`Nj)FdW&!hJ*V8u3!h=NEL zgD`gkCF@a-!by*kH3uY?LAag5P$}}W2ZK2P0->B0yP-)A!N6Dm;ZaTcoFYHLGyCw$ zDzq{mYJ%cIDFe}JU}H1br2=L=#i-85oHu~aDXm2rCWSHr)BzE`*uV z2+F3&W6DlKnFH2CHg$q8DRQX;)<`$n+k#K6z;iozp%JrR2gSDsBucT=*J2$|54))y z^wNXjZ$VT0Pq1Pc%-?wsX_ThA3qqzAKTzz{0rbiXvDpv)SOKX*c~4sLqHbERLK4#1 z#!8@MJ61r8K||XSo869aGyy|(Faj&+I58Htm7wu1p#2)KI2~6g2fOqTb3!Stg}k4S z>E41D9o>QkAmJ!Y3q@7;quy$eN;^nu9|dh=&`r=d6h=+K*tH;^YE$XA+_rR)J1BkdaN{iNR`9#sD*(>XIHvl5Uj^q zV?8K#H%PgcR(CM&8Yqz}217|Xo1rhaqp3dhh4QM_V;U)=TL-NTu<&fgf@l+@1tmML zMo$}o>?&AVC!xQZaWCB9+GAinlFQfSP8sPAmRx~)^_NqYayiDVT*18 zb#_5~(K&$Cz{>_u;!ddEa?Bf@SfCRDT` z3!=FZ6S@u(q!%+o$Co-F%y&caHG}ObyDeREegwATAmsf*V2FY?kAS%7=*E1Q6*c6A zzzo*HEUSS%*#wH{MDI7?<#Y;UC1zn2Bq0TX?tna9h6SJpywwP+pdLzT7lyDBgI)|b zngp%-%0NE5X_WxpKLu5}1uT9G`JiC_E`}o5 z4&Bp$Ip2i|t%ktsz!0_r2iw549>}^Q)Mt?VVn~5<;E#@=P_q7kRlqYUFNV?44t25; zm3PBsL1$eSVL5UTHVEYyK1P%S3A_mN+dzUAbhQKWstI_hhnii2g~>cLN=Me3V8Si{ zc6UKk9l;!RW3fjE-{`^tFPf?VY!iA)xwPs?Fv7MtiplFm$0-N05B5E!L_P$SwHiW( z5>rx2ueDH0TOb8`Fbk))qc0TH2lA#UVao7T1NqVcgZ@TX;X6SQt)T20@IoW(Y>G*y zBw)2z@m&jE*p0`QL)7ntKwbyRF2lfgT7v50=ekup_Er z7}5d0&5$6AU`HGQsnCh2qmTki0HZTSbUi{ZJ}t$ZFM;HuLqr`QhF!1(dog9@5M-2^ zY&)4Vuu8W;1TBMOl~Pz%U@qxm2Rf70iAk=;MD>8A=z>-+twYajytss{&n3LkXZ-yA5 zBa`zW4=Au~Cor@WR9p#JLdPw)f>&!G6OZ6)H6#d~*rL4Ll)rl=s`O#H8-TEd5F=HP zVIH{eC=c8+Xo$@uCqV*~X08I}LO1wmF&bDwYZv^`gCQ&g&+NdQH-TciVK>pO94kPq zbhwjp$W_2lr=xHSfa0<@rY2_oA5cpN2+Xqq$bumO02inT08mQ-0u%rg00;;O00?dy zMa;7S$bumO02inT01E&B0000000000000000000*ZggpMc~f+6a%E6U1qJ{B00031 P0RTAw006ro0RR91S6=N{ diff --git a/docs/conf.py b/docs/conf.py index 8ceffb5b..26eb6496 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -97,7 +97,7 @@ pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +modindex_common_prefix = ['papi.','papi.event.'] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False From 1c25dacaea9af0ea276e079cb325a026d5f69721 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 19 Jan 2015 15:34:59 +0100 Subject: [PATCH 083/260] added some option to (not finished!) slider changed gui size in constants --- papi/constants.py | 4 +-- papi/plugin/pcp/slider/Slider.py | 43 +++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/papi/constants.py b/papi/constants.py index 5691ca2e..10ee8420 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -84,8 +84,8 @@ GUI_WOKRING_INTERVAL = 16 # in ms GUI_WAIT_TILL_RELOAD = 1000 # in ms -GUI_DEFAULT_WIDTH = 800 -GUI_DEFAULT_HEIGHT = 800 +GUI_DEFAULT_WIDTH = 771 +GUI_DEFAULT_HEIGHT = 853 # PLUGIN LOCATION CONSTANTS PLUGIN_ROOT_FOLDER_LIST = ['plugin', 'papi/plugin', '../plugin'] diff --git a/papi/plugin/pcp/slider/Slider.py b/papi/plugin/pcp/slider/Slider.py index 9de79faa..98e2c9b9 100644 --- a/papi/plugin/pcp/slider/Slider.py +++ b/papi/plugin/pcp/slider/Slider.py @@ -38,8 +38,8 @@ class Slider(pcp_base): def initiate_layer_0(self, config): - block = DBlock('Parameter_1') - signal = DSignal('Parameter') + block = DBlock('SliderBlock') + signal = DSignal('SliderParameter1') block.add_signal(signal) self.send_new_block_list([block]) @@ -52,15 +52,16 @@ def create_widget(self): self.slider.sliderPressed.connect(self.clicked) self.slider.valueChanged.connect(self.value_changed) - self.slider.setMinimum(0) - self.slider.setMaximum(100) - self.slider.setSingleStep(1) - + self.slider.setMinimum(float(self.config['lower_bound']['value'])) + self.slider.setMaximum(float(self.config['upper_bound']['value'])) + self.slider.setSingleStep(float(self.config['step_size']['value'])) + self.slider.setOrientation(QtCore.Qt.Horizontal) self.text_field = QLineEdit() self.text_field.setReadOnly(True) - self.text_field.text = self.config['default']['value'] + self.text_field.setFixedWidth(50) + self.layout = QHBoxLayout(self.central_widget) self.layout.addWidget(self.slider) @@ -69,9 +70,8 @@ def create_widget(self): return self.central_widget def value_changed(self, change): - cur_value = change/100 - self.text_field.setText(str(cur_value)) - self.send_parameter_change(cur_value, 'Parameter_1', 'TESTALIAS') + self.text_field.setText(str(change)) + self.send_parameter_change(change, 'SliderBlock', 'TESTALIAS') def clicked(self): pass @@ -81,10 +81,25 @@ def plugin_meta_updated(self): def get_plugin_configuration(self): config = { - 'default': { - 'value': '1' - } - } + 'lower_bound': { + 'value': '0.0' + }, + 'upper_bound': { + 'value': '1.0' + }, + 'step_size': { + 'value': '0.1' + }, + 'size': { + 'value': "(150,75)", + 'regex': '\(([0-9]+),([0-9]+)\)', + 'advanced': '1', + 'tooltip': 'Determine size: (height,width)' + }, + 'name': { + 'value': 'PaPI Slider', + 'tooltip': 'Used for window title' + } } return config def quit(self): From aff09e1d1ebf0c3ae6dbbcfd348ca5efe49da898 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 19 Jan 2015 15:40:59 +0100 Subject: [PATCH 084/260] minor changes in the button plugin --- papi/plugin/pcp/button/Button.py | 34 +++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/papi/plugin/pcp/button/Button.py b/papi/plugin/pcp/button/Button.py index 4e5fe381..7b1b970f 100644 --- a/papi/plugin/pcp/button/Button.py +++ b/papi/plugin/pcp/button/Button.py @@ -29,12 +29,18 @@ __author__ = 'knuths' from papi.plugin.base_classes.pcp_base import pcp_base -from PySide.QtGui import QPushButton +from PySide.QtGui import QPushButton, QIcon from papi.data.DPlugin import DBlock from papi.data.DSignal import DSignal class Button(pcp_base): + def __init__(self): + super(Button, self).__init__() + + self.name = None + self.cur_value = None + def initiate_layer_0(self, config): #super(Button, self).start_init(config) @@ -42,13 +48,15 @@ def initiate_layer_0(self, config): block = DBlock('Click_Event') signal = DSignal('Parameter') block.add_signal(signal) - self.send_new_block_list([block]) + self.name = config['name']['value'] self.cur_value = 0 - self.set_widget_for_internal_usage(self.create_widget()) + self.send_new_block_list([block]) + + self.button = self.create_widget() + self.set_widget_for_internal_usage(self.button) - self.name = config['name']['value'] def create_widget(self): """ @@ -56,12 +64,14 @@ def create_widget(self): :return: :rtype QWidget: """ - button = QPushButton('Click') + button = QPushButton(self.name) button.clicked.connect(self.clicked) + return button def clicked(self): + if self.cur_value == float(self.config['up']['value']): self.cur_value = float(self.config['low']['value']) else: @@ -73,20 +83,20 @@ def clicked(self): def get_plugin_configuration(self): config = { "low": { - 'value':0.1, + 'value': 0, 'advanced' : '0' - }, "up": { + }, "up": { 'value': 1, 'advanced' : '0' - },"name": { - 'value' : "Button", - 'advanced' : '0' - },'size': { + },"name": { + 'value': "PaPI Button", + 'advanced': '0' + },'size': { 'value': "(150,50)", 'regex': '\(([0-9]+),([0-9]+)\)', 'advanced': '1', 'tooltip': 'Determine size: (height,width)' - } } + } } return config def plugin_meta_updated(self): From 27518b6065acb0234a4abf8c9fe43e01af25bdbd Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 19 Jan 2015 15:43:58 +0100 Subject: [PATCH 085/260] deleted git cfgs --- cfg_collection/ExampleFourier.xml | 237 ------------------------ cfg_collection/ExampleWizard.xml | 29 --- cfg_collection/ORTD_bigSource.xml | 295 ------------------------------ papi/plugin/pcp/slider/Slider.py | 2 +- 4 files changed, 1 insertion(+), 562 deletions(-) delete mode 100644 cfg_collection/ExampleFourier.xml delete mode 100644 cfg_collection/ExampleWizard.xml delete mode 100644 cfg_collection/ORTD_bigSource.xml diff --git a/cfg_collection/ExampleFourier.xml b/cfg_collection/ExampleFourier.xml deleted file mode 100644 index 9985640a..00000000 --- a/cfg_collection/ExampleFourier.xml +++ /dev/null @@ -1,237 +0,0 @@ - - - - - Plot - - - \w+,\s*\w+ - time, s - Label-X - - - bool - 1 - yRange-auto - ^(1|0)$ - - - bool - 0 - Grid-Y - ^(1|0)$ - - - bool - 0 - Rolling Plot - ^(1|0)$ - - - \(([0-9]+),([0-9]+)\) - (300,300) - 1 - Determine size: (height,width) - - - ^([1-9][0-9]{0,3}|10000)$ - 1000 - 1 - Buffersize - - - bool - 0 - Grid-X - ^(1|0)$ - - - \w+,\s+\w+ - amplitude, V - Label-Y - - - (\d+\.\d+) - [0,1] - yRange-auto - - - Plot - - - \(([0-9]+),([0-9]+)\) - (0,0) - 1 - Determine position: (x,y) - - - bool - 1 - xRange-auto - ^(1|0)$ - - - ^\[(\s*\d\s*)+\] - [0 0 0 0 0] - 1 - Style - - - (\d+) - 1 - - - (\d+\.\d+) - [0,1] - xRange-auto - - - VisualPlugin - Used for window title - - - ^\[(\s*\d\s*)+\] - [0 1 2 3 4] - 1 - Color - - - - 1 - 0 - [0,1] - [0 0 0 0 0] - 0 - 1000 - 0 - 1 - [0,1] - 1 - [0 1 2 3 4] - - - - - Add - - - Sum - - - - - - Add - - - Add - - - - - - - Sum - - - - - - FourierXRectXMOD - - - rect1 - rect10 - rect11 - rect12 - rect13 - rect14 - rect15 - rect16 - rect17 - rect18 - rect19 - rect2 - rect3 - rect4 - rect5 - rect6 - rect7 - rect8 - rect9 - - - - - - Fourier_Rect_MOD - - - FourierXRectXMOD - - - - - - - rect1 - - - rect2 - - - rect3 - - - rect4 - - - rect5 - - - rect6 - - - rect7 - - - rect8 - - - rect9 - - - rect10 - - - rect11 - - - rect12 - - - rect13 - - - rect14 - - - rect15 - - - rect16 - - - rect17 - - - rect18 - - - rect19 - - - - - - diff --git a/cfg_collection/ExampleWizard.xml b/cfg_collection/ExampleWizard.xml deleted file mode 100644 index 6366c9a6..00000000 --- a/cfg_collection/ExampleWizard.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - WizardExample - - - WizardExample - - - (300,300) - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - 1 - - - (0,0) - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - 1 - - - VisualPlugin - Used display name - - - - - - diff --git a/cfg_collection/ORTD_bigSource.xml b/cfg_collection/ORTD_bigSource.xml deleted file mode 100644 index ff36db90..00000000 --- a/cfg_collection/ORTD_bigSource.xml +++ /dev/null @@ -1,295 +0,0 @@ - - - - - ORTD_UDP - - - 1 - 0 - - - 1 - 20001 - - - ORTDXUDP - - - 1 - 127.0.0.1 - - - 1 - 20000 - - - file - 0 - /home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample_extended/ProtocollConfig.json - - - - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 0.0 - - - - Button - - Oscillator input - - - - - - Plot - - - ^(1|0)$ - 0 - Grid-Y - bool - - - ^(1|0)$ - 1 - Rolling Plot - bool - - - \w+,\s*\w+ - time, s - Label-X - - - ^([1-9][0-9]{0,3}|10000)$ - 50 - Buffersize - 1 - - - ^(1|0)$ - 0 - Grid-X - bool - - - ^\[(\s*\d\s*)+\] - [0 0 0 0 0] - Style - 1 - - - ^\[(\s*\d\s*)+\] - [0 1 2 3 4] - Color - 1 - - - (\d+) - 1 - - - Plot - - - \(([0-9]+),([0-9]+)\) - (311,696) - 1 - Determine size: (height,width) - - - \w+,\s+\w+ - amplitude, V - Label-Y - - - Used display name - VisualPlugin - - - \(([0-9]+),([0-9]+)\) - (0,0) - 1 - Determine position: (x,y) - - - - 0 - [0 0 0 0 0] - 1 - [0 1 2 3 4] - 1 - 0 - 50 - - - - ORTDXUDP - - - 15 - 16 - 18 - HalloWelt14 - Sig2nal - Sign12al - Sign3al - Signal1 - Signal10 - Signal11 - Signal19 - Signal20 - Signal21 - Signal22 - Signal5 - Signal6 - Signal7 - Signal8 - Signal9 - Signal_13 - Test17 - _Sig4nal - - - - - - Plot - - - ^(1|0)$ - 0 - Grid-Y - bool - - - ^(1|0)$ - 1 - Rolling Plot - bool - - - \w+,\s*\w+ - time, s - Label-X - - - ^([1-9][0-9]{0,3}|10000)$ - 200 - Buffersize - 1 - - - ^(1|0)$ - 0 - Grid-X - bool - - - ^\[(\s*\d\s*)+\] - [0 0 0 0 0] - Style - 1 - - - ^\[(\s*\d\s*)+\] - [0 1 2 3 4] - Color - 1 - - - (\d+) - 1 - - - PlotX2 - - - \(([0-9]+),([0-9]+)\) - (300,300) - 1 - Determine size: (height,width) - - - \w+,\s+\w+ - amplitude, V - Label-Y - - - Used display name - VisualPlugin - - - \(([0-9]+),([0-9]+)\) - (320,0) - 1 - Determine position: (x,y) - - - - 0 - [0 0 0 0 0] - 1 - [0 1 2 3 4] - 1 - 0 - 200 - - - - ORTDXUDP - - - V - X - - - - - - Button - - - 0 - 0 - - - Button - - - \(([0-9]+),([0-9]+)\) - (127,58) - 1 - Determine size: (height,width) - - - Button - 0 - - - 0.5 - 0 - - - \(([0-9]+),([0-9]+)\) - (311,305) - 1 - Determine position: (x,y) - - - - - - diff --git a/papi/plugin/pcp/slider/Slider.py b/papi/plugin/pcp/slider/Slider.py index 98e2c9b9..c5fe5c40 100644 --- a/papi/plugin/pcp/slider/Slider.py +++ b/papi/plugin/pcp/slider/Slider.py @@ -55,7 +55,7 @@ def create_widget(self): self.slider.setMinimum(float(self.config['lower_bound']['value'])) self.slider.setMaximum(float(self.config['upper_bound']['value'])) self.slider.setSingleStep(float(self.config['step_size']['value'])) - + self.slider.setOrientation(QtCore.Qt.Horizontal) self.text_field = QLineEdit() From dc69055a711898f273ed33164fa71b454510797d Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 19 Jan 2015 15:46:07 +0100 Subject: [PATCH 086/260] added submenu for 'remove DPlugin' in the overview menu --- papi/gui/qt_new/overview_menu.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/papi/gui/qt_new/overview_menu.py b/papi/gui/qt_new/overview_menu.py index 7392fed0..36fda587 100644 --- a/papi/gui/qt_new/overview_menu.py +++ b/papi/gui/qt_new/overview_menu.py @@ -337,10 +337,12 @@ def open_context_menu_dplugin_tree(self, position): dplugin = self.pluginTree.model().data(index, Qt.UserRole) - menu = QMenu('Remove') + menu = QMenu('Menu') + submenu = QMenu('Action') + menu.addMenu(submenu) action = QAction('Remove DPlugin', self) - menu.addAction(action) + submenu.addAction(action) action.triggered.connect(lambda p=dplugin.id: self.gui_api.do_delete_plugin(p)) From 4a9515c4ba58cc68d0953e4c34a042579ab1e498 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 19 Jan 2015 15:49:04 +0100 Subject: [PATCH 087/260] removed xlabel.ylabel option for the plot plugin --- papi/plugin/visual/Plot/Plot.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 5a93a33d..20ff3fc1 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -830,15 +830,16 @@ def get_plugin_configuration(self): :return {}: """ config = { - 'label_y': { - 'value': "amplitude, V", - 'regex': '\w+,\s+\w+', - 'display_text': 'Label-Y' - }, 'label_x': { - 'value': "time, s", - 'regex': '\w+,\s*\w+', - 'display_text': 'Label-X' - }, 'x-grid': { + # 'label_y': { + # 'value': "amplitude, V", + # 'regex': '\w+,\s+\w+', + # 'display_text': 'Label-Y' + # }, 'label_x': { + # 'value': "time, s", + # 'regex': '\w+,\s*\w+', + # 'display_text': 'Label-X' + # }, + 'x-grid': { 'value': "0", 'regex': '^(1|0)$', 'type': 'bool', From ad95e764e2391f8007f3b1ba6dcc16ef60c7764c Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 19 Jan 2015 15:56:27 +0100 Subject: [PATCH 088/260] added example cfgs --- cfg_collection/cpu_load_example.xml | 136 +++ .../fourier_add_example_localhost.xml | 347 ++++++++ cfg_collection/sinus_add_example.xml | 300 +++++++ .../sinus_ortd_button_fourier_cpu_example.xml | 832 ++++++++++++++++++ 4 files changed, 1615 insertions(+) create mode 100644 cfg_collection/cpu_load_example.xml create mode 100644 cfg_collection/fourier_add_example_localhost.xml create mode 100644 cfg_collection/sinus_add_example.xml create mode 100644 cfg_collection/sinus_ortd_button_fourier_cpu_example.xml diff --git a/cfg_collection/cpu_load_example.xml b/cfg_collection/cpu_load_example.xml new file mode 100644 index 00000000..31234bec --- /dev/null +++ b/cfg_collection/cpu_load_example.xml @@ -0,0 +1,136 @@ + + + + + CPU_Load + + + CPUXLoad + + + + 0.01 + + + + + load_in_percent + + + + + + + Plot + + + Plot + + + Used for window title + CPU Load Plot + + + Grid-X + 0 + ^(1|0)$ + bool + + + 1 + (\d+) + + + Rolling Plot + 1 + ^(1|0)$ + bool + + + Style + [0 0 0 0 0] + ^\[(\s*\d\s*)+\] + 1 + + + y: range + [0,1] + (\d+\.\d+) + 1 + + + y: auto range + bool + 1 + ^(1|0)$ + 1 + + + x: range + [0,1] + (\d+\.\d+) + 1 + + + Determine position: (x,y) + (0,0) + \(([0-9]+),([0-9]+)\) + 1 + + + Color + [0 1 2 3 4] + ^\[(\s*\d\s*)+\] + 1 + + + x: auto range + bool + 1 + ^(1|0)$ + 1 + + + Buffersize + 500 + ^([1-9][0-9]{0,3}|10000)$ + 1 + + + Grid-Y + 0 + ^(1|0)$ + bool + + + Determine size: (height,width) + (449,449) + \(([0-9]+),([0-9]+)\) + 1 + + + + [0 1 2 3 4] + 1 + 0 + 1 + 500 + 0 + [0 0 0 0 0] + [0,1] + 1 + 1 + [0,1] + + + + + CPUXLoad + + + load_in_percent + + + + + diff --git a/cfg_collection/fourier_add_example_localhost.xml b/cfg_collection/fourier_add_example_localhost.xml new file mode 100644 index 00000000..5f58c201 --- /dev/null +++ b/cfg_collection/fourier_add_example_localhost.xml @@ -0,0 +1,347 @@ + + + + + Plot + + + Fourier RAW + Used for window title + + + [0 0 0 0 0] + Style + ^\[(\s*\d\s*)+\] + 1 + + + 1 + y: auto range + ^(1|0)$ + bool + 1 + + + 1 + (\d+) + + + 0 + Grid-X + ^(1|0)$ + bool + + + 1 + x: auto range + ^(1|0)$ + bool + 1 + + + 1000 + Buffersize + ^([1-9][0-9]{0,3}|10000)$ + 1 + + + (0,0) + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + 1 + + + [0,1] + y: range + (\d+\.\d+) + 1 + + + [0 1 2 3 4] + Color + ^\[(\s*\d\s*)+\] + 1 + + + 0 + Rolling Plot + ^(1|0)$ + bool + + + (300,300) + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 + + + 0 + Grid-Y + ^(1|0)$ + bool + + + Plot + + + [0,1] + x: range + (\d+\.\d+) + 1 + + + + 1 + [0,1] + [0 1 2 3 4] + 0 + 0 + 0 + 1 + 1 + [0,1] + [0 0 0 0 0] + 1000 + + + + + FourierXRect + + + rect1 + rect2 + rect3 + rect4 + rect5 + + + + + + Fourier_Rect + + + Fourier + + + 9999 + \d{1,5} + 1 + + + FourierXRect + + + 127.0.0.1 + \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3} + 1 + + + + + + + rect1 + + + rect2 + + + rect3 + + + rect4 + + + rect5 + + + rect6 + + + rect7 + + + rect8 + + + rect9 + + + rect10 + + + rect11 + + + rect12 + + + rect13 + + + rect14 + + + rect15 + + + rect16 + + + rect17 + + + rect18 + + + rect19 + + + + + + + Add + + + Add + + + + + + + Sum + + + + + + FourierXRect + + + rect1 + rect2 + rect3 + rect4 + rect5 + + + + + + Plot + + + Fourier ADD + Used for window title + + + [0 0 0 0 0] + Style + ^\[(\s*\d\s*)+\] + 1 + + + 1 + y: auto range + ^(1|0)$ + bool + 1 + + + 1 + (\d+) + + + 0 + Grid-X + ^(1|0)$ + bool + + + 1 + x: auto range + ^(1|0)$ + bool + 1 + + + 1000 + Buffersize + ^([1-9][0-9]{0,3}|10000)$ + 1 + + + (447,2) + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + 1 + + + [0,1] + y: range + (\d+\.\d+) + 1 + + + [0 1 2 3 4] + Color + ^\[(\s*\d\s*)+\] + 1 + + + 0 + Rolling Plot + ^(1|0)$ + bool + + + (300,300) + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 + + + 0 + Grid-Y + ^(1|0)$ + bool + + + PlotX2 + + + [0,1] + x: range + (\d+\.\d+) + 1 + + + + 1 + [0,1] + [0 1 2 3 4] + 0 + 0 + 0 + 1 + 1 + [0,1] + [0 0 0 0 0] + 1000 + + + + + Add + + + Sum + + + + + diff --git a/cfg_collection/sinus_add_example.xml b/cfg_collection/sinus_add_example.xml new file mode 100644 index 00000000..5aa1e277 --- /dev/null +++ b/cfg_collection/sinus_add_example.xml @@ -0,0 +1,300 @@ + + + + + Sinus + + + SinusPlugin + + + 5 + [0-9]+ + + + 2 + \d+.{0,1}\d* + + + + 1.0 + + + + + f2_1 + + + + + f3_1 + + + f3_2 + + + f3_scalar + + + + + f1_f1DNAME + + + + + + + Plot + + + (0,0) + \(([0-9]+),([0-9]+)\) + 1 + Determine position: (x,y) + + + 1 + (\d+) + + + Plot + + + Style + [0 0 0 0 0] + ^\[(\s*\d\s*)+\] + 1 + + + bool + 1 + ^(1|0)$ + y: auto range + 1 + + + Color + [0 1 2 3 4] + ^\[(\s*\d\s*)+\] + 1 + + + bool + 0 + ^(1|0)$ + Grid-X + + + bool + 1 + ^(1|0)$ + x: auto range + 1 + + + VisualPlugin + Used for window title + + + x: range + [0.0 1.0] + (\d+\.\d+) + 1 + + + (300,300) + \(([0-9]+),([0-9]+)\) + 1 + Determine size: (height,width) + + + bool + 0 + ^(1|0)$ + Rolling Plot + + + y: range + [0.0 1.0] + (\d+\.\d+) + 1 + + + bool + 0 + ^(1|0)$ + Grid-Y + + + Buffersize + 2000 + ^([1-9][0-9]{0,3}|10000)$ + 1 + + + + [0,1] + 0 + 1 + 0 + [0 0 0 0 0] + 1 + 2000 + [0 1 2 3 4] + [0,1] + 0 + 1 + + + + + SinusPlugin + + + f3_1 + f3_2 + + + + + + Plot + + + (450,0) + \(([0-9]+),([0-9]+)\) + 1 + Determine position: (x,y) + + + 1 + (\d+) + + + PlotX2 + + + Style + [0 0 0 0 0] + ^\[(\s*\d\s*)+\] + 1 + + + bool + 1 + ^(1|0)$ + y: auto range + 1 + + + Color + [0 1 2 3 4] + ^\[(\s*\d\s*)+\] + 1 + + + bool + 0 + ^(1|0)$ + Grid-X + + + bool + 1 + ^(1|0)$ + x: auto range + 1 + + + VisualPlugin + Used for window title + + + x: range + [0.0 1.0] + (\d+\.\d+) + 1 + + + (300,300) + \(([0-9]+),([0-9]+)\) + 1 + Determine size: (height,width) + + + bool + 0 + ^(1|0)$ + Rolling Plot + + + y: range + [0.0 1.0] + (\d+\.\d+) + 1 + + + bool + 0 + ^(1|0)$ + Grid-Y + + + Buffersize + 2000 + ^([1-9][0-9]{0,3}|10000)$ + 1 + + + + [0,1] + 0 + 1 + 0 + [0 0 0 0 0] + 1 + 2000 + [0 1 2 3 4] + [0,1] + 0 + 1 + + + + + Add + + + Sum + + + + + + Add + + + Add + + + + + + + Sum + + + + + + SinusPlugin + + + f3_1 + f3_2 + + + + + diff --git a/cfg_collection/sinus_ortd_button_fourier_cpu_example.xml b/cfg_collection/sinus_ortd_button_fourier_cpu_example.xml new file mode 100644 index 00000000..b2651b9e --- /dev/null +++ b/cfg_collection/sinus_ortd_button_fourier_cpu_example.xml @@ -0,0 +1,832 @@ + + + + + Plot + + + Rolling Plot + ^(1|0)$ + 0 + bool + + + y: auto range + ^(1|0)$ + 1 + 1 + bool + + + Color + ^\[(\s*\d\s*)+\] + 1 + [0 1 2 3 4] + + + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + 1 + (-1,149) + + + Used for window title + VisualPlugin + + + Plot + + + Grid-Y + ^(1|0)$ + 0 + bool + + + (\d+) + 1 + + + x: auto range + ^(1|0)$ + 1 + 1 + bool + + + Label-Y + \w+,\s+\w+ + amplitude, V + + + x: range + (\d+\.\d+) + 1 + [0,1] + + + y: range + (\d+\.\d+) + 1 + [0,1] + + + Buffersize + ^([1-9][0-9]{0,3}|10000)$ + 1 + 1000 + + + Style + ^\[(\s*\d\s*)+\] + 1 + [0 0 0 0 0] + + + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 + (300,300) + + + Grid-X + ^(1|0)$ + 0 + bool + + + + [0,1] + 1 + 1 + [0 1 2 3 4] + 0 + [0,1] + 1000 + [0 0 0 0 0] + 1 + 0 + 0 + + + + + Add + + + Sum + + + + + + Plot + + + Rolling Plot + ^(1|0)$ + 0 + bool + + + y: auto range + ^(1|0)$ + 1 + 1 + bool + + + Color + ^\[(\s*\d\s*)+\] + 1 + [0 1 2 3 4] + + + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + 1 + (301,149) + + + Used for window title + VisualPlugin + + + PlotX2 + + + Grid-Y + ^(1|0)$ + 0 + bool + + + (\d+) + 1 + + + x: auto range + ^(1|0)$ + 1 + 1 + bool + + + Label-Y + \w+,\s+\w+ + amplitude, V + + + x: range + (\d+\.\d+) + 1 + [0,1] + + + y: range + (\d+\.\d+) + 1 + [0,1] + + + Buffersize + ^([1-9][0-9]{0,3}|10000)$ + 1 + 1000 + + + Style + ^\[(\s*\d\s*)+\] + 1 + [0 0 0 0 0] + + + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 + (300,300) + + + Grid-X + ^(1|0)$ + 0 + bool + + + + [0,1] + 1 + 1 + [0 1 2 3 4] + 0 + [0,1] + 1000 + [0 0 0 0 0] + 1 + 0 + 0 + + + + + ORTDXUDP + + + V + X + + + + + + Plot + + + Rolling Plot + ^(1|0)$ + 0 + bool + + + y: auto range + ^(1|0)$ + 1 + 1 + bool + + + Color + ^\[(\s*\d\s*)+\] + 1 + [0 1 2 3 4] + + + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + 1 + (300,447) + + + Used for window title + VisualPlugin + + + PlotX3 + + + Grid-Y + ^(1|0)$ + 0 + bool + + + (\d+) + 1 + + + x: auto range + ^(1|0)$ + 1 + 1 + bool + + + Label-Y + \w+,\s+\w+ + amplitude, V + + + x: range + (\d+\.\d+) + 1 + [0,1] + + + y: range + (\d+\.\d+) + 1 + [0,1] + + + Buffersize + ^([1-9][0-9]{0,3}|10000)$ + 1 + 1000 + + + Style + ^\[(\s*\d\s*)+\] + 1 + [0 0 0 0 0] + + + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 + (300,300) + + + Grid-X + ^(1|0)$ + 0 + bool + + + + [0,1] + 1 + 1 + [0 1 2 3 4] + 0 + [0,1] + 1000 + [0 0 0 0 0] + 1 + 0 + 0 + + + + + Sinus + + + f3_1 + f3_2 + + + + + + Plot + + + Rolling Plot + ^(1|0)$ + 0 + bool + + + y: auto range + ^(1|0)$ + 1 + 1 + bool + + + Color + ^\[(\s*\d\s*)+\] + 1 + [0 1 2 3 4] + + + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + 1 + (0,450) + + + Used for window title + VisualPlugin + + + PlotX4 + + + Grid-Y + ^(1|0)$ + 0 + bool + + + (\d+) + 1 + + + x: auto range + ^(1|0)$ + 1 + 1 + bool + + + x: range + (\d+\.\d+) + 1 + [0,1] + + + y: range + (\d+\.\d+) + 1 + [0,1] + + + Buffersize + ^([1-9][0-9]{0,3}|10000)$ + 1 + 1000 + + + Style + ^\[(\s*\d\s*)+\] + 1 + [0 0 0 0 0] + + + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 + (300,292) + + + Grid-X + ^(1|0)$ + 0 + bool + + + + [0,1] + 1 + 1 + [0 1 2 3 4] + 0 + [0,1] + 1000 + [0 0 0 0 0] + 1 + 0 + 0 + + + + + CPUXLoad + + + load_in_percent + + + + + + CPU_Load + + + CPUXLoad + + + + 0.01 + + + + + load_in_percent + + + + + + + Fourier_Rect_MOD + + + FourierXRectXMOD + + + + + + + rect1 + + + rect2 + + + rect3 + + + rect4 + + + rect5 + + + rect6 + + + rect7 + + + rect8 + + + rect9 + + + rect10 + + + rect11 + + + rect12 + + + rect13 + + + rect14 + + + rect15 + + + rect16 + + + rect17 + + + rect18 + + + rect19 + + + + + + + ORTD_UDP + + + 1 + 20000 + + + 1 + 20001 + + + 0 + file + /home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample_extended/ProtocollConfig.json + + + ORTDXUDP + + + 1 + 127.0.0.1 + + + 1 + 0 + + + + 0 + 0 + 0 + 0 + 0 + 0 + 0.1 + 0 + 0 + 0 + 0 + 0 + 0 + + + + + X + + + Sig2nal + + + V + + + Signal10 + + + _Sig4nal + + + Signal8 + + + Signal22 + + + Signal11 + + + Signal19 + + + Signal20 + + + HalloWelt14 + + + Signal_13 + + + 16 + + + Test17 + + + Signal7 + + + Signal6 + + + Signal1 + + + Sign12al + + + Signal9 + + + Sign3al + + + 15 + + + Signal21 + + + 18 + + + Signal5 + + + + + ControlSignalCreate + + + ControlSignalSub + + + ControllerSignalParameter + + + ControllerSignalClose + + + + + + Button + + Oscillator input + + + + + + Sinus + + + [0-9]+ + 3 + + + Sinus + + + \d+.{0,1}\d* + 1 + + + + 0.6 + + + + + f3_1 + + + f3_2 + + + f3_scalar + + + + + f2_1 + + + + + f1_f1DNAME + + + + + + + Add + + + Add + + + + + + + Sum + + + + + + FourierXRectXMOD + + + rect1 + rect10 + rect11 + rect12 + rect13 + rect14 + rect15 + rect16 + rect17 + rect18 + rect19 + rect2 + rect3 + rect4 + rect5 + rect6 + rect7 + rect8 + rect9 + + + + + + Button + + + 0 + 0.1 + + + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + 1 + (601,300) + + + 0 + Button + + + 0 + 1 + + + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 + (149,55) + + + Button + + + + + + + Parameter + + + + + + From 20c8e723ef51f0e520f2945bcc645167ca0f02bc Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 19 Jan 2015 15:58:08 +0100 Subject: [PATCH 089/260] updated version numbers --- README.md | 2 +- papi/constants.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e2868d95..f7c07c26 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -PaPI v. 0.9 +PaPI v. 1.0.0 ================== Plugin based Process Interaction diff --git a/papi/constants.py b/papi/constants.py index 10ee8420..06481b3e 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -34,8 +34,8 @@ CORE_CONSOLE_LOG_LEVEL = 1 -CORE_PAPI_VERSION = '0.9' # no spaces allowed -CORE_CORE_VERSION = '0.9' # no spaces allowed +CORE_PAPI_VERSION = '1.0.0' # no spaces allowed +CORE_CORE_VERSION = '1.0.0' # no spaces allowed CORE_PAPI_CONSOLE_START_MESSAGE = 'PaPI - Plugin based Process Interaction' + ' Version: ' + CORE_PAPI_VERSION CORE_CORE_CONSOLE_START_MESSAGE = 'PaPI Core Modul ' + CORE_CORE_VERSION + ' started' CORE_STOP_CONSOLE_MESSAGE = 'Core and PaPI finished operation cleanly' @@ -77,7 +77,7 @@ # GUI CONSTANTS GUI_PROCESS_CONSOLE_IDENTIFIER = 'Gui Process: ' GUI_PROCESS_CONSOLE_LOG_LEVEL = 1 -GUI_VERSION = 'v_0.9' +GUI_VERSION = 'v_1.0.0' GUI_START_CONSOLE_MESSAGE = 'PaPI GUI Modul ' + GUI_VERSION + ' started' GUI_PAPI_WINDOW_TITLE = 'PaPI - Plugin based Process Interaction' From a812bcaafa9235d52785ff796ac0fced6de3c57a Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 19 Jan 2015 16:04:41 +0100 Subject: [PATCH 090/260] changed name in config for sinus example --- cfg_collection/sinus_add_example.xml | 4 ++-- papi/data/DCore.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cfg_collection/sinus_add_example.xml b/cfg_collection/sinus_add_example.xml index 5aa1e277..7e8f3616 100644 --- a/cfg_collection/sinus_add_example.xml +++ b/cfg_collection/sinus_add_example.xml @@ -93,7 +93,7 @@ 1 - VisualPlugin + Sinus Used for window title @@ -207,7 +207,7 @@ 1 - VisualPlugin + Add Used for window title diff --git a/papi/data/DCore.py b/papi/data/DCore.py index f303bcc3..0b69e4ce 100644 --- a/papi/data/DCore.py +++ b/papi/data/DCore.py @@ -234,7 +234,7 @@ def unsubscribe(self, subscriber_id, target_id, dblock_name): #Destroy relation between DPlugin and DBlock if subscriber.unsubscribe(dblock) is False: - self.log.printText(1, "Subscriber " + subscriber_id + " has already unsubscribed " + dblock_name) + self.log.printText(1, "Subscriber " + str(subscriber_id) + " has already unsubscribed " + dblock_name) return False if dblock.rm_subscriber(subscriber) is False: From 282893785478586e9ec92452a8f482c234332d58 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 11:31:22 +0100 Subject: [PATCH 091/260] added a LCD Display plugin --- papi/plugin/visual/LCDDisplay/LCDDisplay.py | 173 ++++++++++++++++++ .../visual/LCDDisplay/LCDDisplay.yapsy-plugin | 9 + 2 files changed, 182 insertions(+) create mode 100644 papi/plugin/visual/LCDDisplay/LCDDisplay.py create mode 100644 papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.py b/papi/plugin/visual/LCDDisplay/LCDDisplay.py new file mode 100644 index 00000000..7745fe42 --- /dev/null +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.py @@ -0,0 +1,173 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: + self.time_treshold: + self.last_time = cur_time + t = Data['t'] + keys = list(Data.keys()) + keys.sort() + if Data[keys[0]] != 't': + y = Data[keys[0]][-1] + else: + if len(keys) > 1: + y = Data[keys[1]][-1] + + self.LcdWidget.display(y) + + + def set_parameter(self, name, value): + # attetion: value is a string and need to be processed ! + # if name == 'irgendeinParameter': + # do that .... with value + if name == self.para_treshold.name: + self.time_treshold = int(value) + self.config['updateFrequency']['value'] = value + + + def quit(self): + # do something before plugin will close, e.a. close connections ... + pass + + + def get_plugin_configuration(self): + # + # Implement a own part of the config + # config is a hash of hass object + # config_parameter_name : {} + # config[config_parameter_name]['value'] NEEDS TO BE IMPLEMENTED + # configs can be marked as advanced for create dialog + # http://utilitymill.com/utility/Regex_For_Range + config = { + "updateFrequency": { + 'value': '1000', + 'regex': '[0-9]+', + 'display_text' : 'Minimal time between updates (in ms)', + 'advanced' : '1' + }, 'size': { + 'value': "(150,75)", + 'regex': '\(([0-9]+),([0-9]+)\)', + 'advanced': '1', + 'tooltip': 'Determine size: (height,width)' + }, 'name': { + 'value': 'LCD', + 'tooltip': 'Used for window title' + }} + return config + + def plugin_meta_updated(self): + """ + Whenever the meta information is updated this function is called (if implemented). + + :return: + """ + + #dplugin_info = self.dplugin_info + pass diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin b/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin new file mode 100644 index 00000000..f8b3345b --- /dev/null +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = LCDDisplay +Module = LCDDisplay + +[Documentation] +Author = S.R. +Version = 0.1 +Website = +Description = LCD Display \ No newline at end of file From 5208b8306e66e3c42fae5b57186f7081a10be705 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 11:31:22 +0100 Subject: [PATCH 092/260] added a LCD Display plugin --- papi/plugin/visual/LCDDisplay/LCDDisplay.py | 173 ++++++++++++++++++ .../visual/LCDDisplay/LCDDisplay.yapsy-plugin | 9 + 2 files changed, 182 insertions(+) create mode 100644 papi/plugin/visual/LCDDisplay/LCDDisplay.py create mode 100644 papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.py b/papi/plugin/visual/LCDDisplay/LCDDisplay.py new file mode 100644 index 00000000..7745fe42 --- /dev/null +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.py @@ -0,0 +1,173 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: + self.time_treshold: + self.last_time = cur_time + t = Data['t'] + keys = list(Data.keys()) + keys.sort() + if Data[keys[0]] != 't': + y = Data[keys[0]][-1] + else: + if len(keys) > 1: + y = Data[keys[1]][-1] + + self.LcdWidget.display(y) + + + def set_parameter(self, name, value): + # attetion: value is a string and need to be processed ! + # if name == 'irgendeinParameter': + # do that .... with value + if name == self.para_treshold.name: + self.time_treshold = int(value) + self.config['updateFrequency']['value'] = value + + + def quit(self): + # do something before plugin will close, e.a. close connections ... + pass + + + def get_plugin_configuration(self): + # + # Implement a own part of the config + # config is a hash of hass object + # config_parameter_name : {} + # config[config_parameter_name]['value'] NEEDS TO BE IMPLEMENTED + # configs can be marked as advanced for create dialog + # http://utilitymill.com/utility/Regex_For_Range + config = { + "updateFrequency": { + 'value': '1000', + 'regex': '[0-9]+', + 'display_text' : 'Minimal time between updates (in ms)', + 'advanced' : '1' + }, 'size': { + 'value': "(150,75)", + 'regex': '\(([0-9]+),([0-9]+)\)', + 'advanced': '1', + 'tooltip': 'Determine size: (height,width)' + }, 'name': { + 'value': 'LCD', + 'tooltip': 'Used for window title' + }} + return config + + def plugin_meta_updated(self): + """ + Whenever the meta information is updated this function is called (if implemented). + + :return: + """ + + #dplugin_info = self.dplugin_info + pass diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin b/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin new file mode 100644 index 00000000..f8b3345b --- /dev/null +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = LCDDisplay +Module = LCDDisplay + +[Documentation] +Author = S.R. +Version = 0.1 +Website = +Description = LCD Display \ No newline at end of file From dfda8454c95c8701edc52a3ce06d004e68c40e99 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 12:05:20 +0100 Subject: [PATCH 093/260] added context menu for LCD Display --- papi/plugin/visual/LCDDisplay/LCDDisplay.py | 13 ++++++++++--- .../visual/LCDDisplay/LCDDisplay.yapsy-plugin | 4 ++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.py b/papi/plugin/visual/LCDDisplay/LCDDisplay.py index 7745fe42..1a1239ef 100644 --- a/papi/plugin/visual/LCDDisplay/LCDDisplay.py +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.py @@ -61,10 +61,13 @@ def initiate_layer_0(self, config=None): self.LcdWidget = QtGui.QLCDNumber() self.LcdWidget.setSmallDecimalPoint(True) self.LcdWidget.display(0) + + self.LcdWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.LcdWidget.customContextMenuRequested.connect(self.show_context_menu) # This call is important, because the background structure needs to know the used widget! # In the background the qmidiwindow will becreated and the widget will be added self.set_widget_for_internal_usage( self.LcdWidget ) - + self.cmenu = self.create_control_context_menu() # --------------------------- # Create Parameters @@ -85,17 +88,21 @@ def initiate_layer_0(self, config=None): self.last_time = int(round(time.time() * 1000)) return True + def show_context_menu(self, pos): + gloPos = self.LcdWidget.mapToGlobal(pos) + self.cmenu.exec_(gloPos) + def pause(self): # will be called, when plugin gets paused # can be used to get plugin in a defined state before pause # e.a. close communication ports, files etc. - pass + self.LcdWidget.display('PAUSE') def resume(self): # will be called when plugin gets resumed # can be used to wake up the plugin from defined pause state # e.a. reopen communication ports, files etc. - pass + self.LcdWidget.display('...') def execute(self, Data=None, block_name = None): # Do main work here! diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin b/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin index f8b3345b..a0c47853 100644 --- a/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin @@ -3,7 +3,7 @@ Name = LCDDisplay Module = LCDDisplay [Documentation] -Author = S.R. +Author = S. Ruppin Version = 0.1 Website = -Description = LCD Display \ No newline at end of file +Description = LCD Display for displaying floating point numbers up to 5 digits with sign. Refresh rate is user defined. \ No newline at end of file From 5a9bbe7ee1f023a18a8ecb683b4f31f58c487e6d Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 12:13:02 +0100 Subject: [PATCH 094/260] removed alias in function --- papi/plugin/base_classes/base_plugin.py | 3 +-- papi/plugin/pcp/button/Button.py | 2 +- papi/plugin/pcp/slider/Slider.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py index ba6d4c8a..b0165629 100644 --- a/papi/plugin/base_classes/base_plugin.py +++ b/papi/plugin/base_classes/base_plugin.py @@ -95,12 +95,11 @@ def set_event_trigger_mode(self, mode): self.user_event_triggered = mode - def send_parameter_change(self, data, block_name, alias): + def send_parameter_change(self, data, block_name): opt = DOptionalData(DATA=data) opt.data_source_id = self.__id__ opt.is_parameter = True opt.block_name = block_name - opt.parameter_alias = alias event = Event.data.NewData(self.__id__, 0, opt) self._Core_event_queue__.put(event) diff --git a/papi/plugin/pcp/button/Button.py b/papi/plugin/pcp/button/Button.py index 7b1b970f..c48179c0 100644 --- a/papi/plugin/pcp/button/Button.py +++ b/papi/plugin/pcp/button/Button.py @@ -77,7 +77,7 @@ def clicked(self): else: self.cur_value = float(self.config['up']['value']) - self.send_parameter_change(str(self.cur_value), 'Click_Event', 'TESTALIAS') + self.send_parameter_change(str(self.cur_value), 'Click_Event') def get_plugin_configuration(self): diff --git a/papi/plugin/pcp/slider/Slider.py b/papi/plugin/pcp/slider/Slider.py index c5fe5c40..de43fe74 100644 --- a/papi/plugin/pcp/slider/Slider.py +++ b/papi/plugin/pcp/slider/Slider.py @@ -71,7 +71,7 @@ def create_widget(self): def value_changed(self, change): self.text_field.setText(str(change)) - self.send_parameter_change(change, 'SliderBlock', 'TESTALIAS') + self.send_parameter_change(change, 'SliderBlock') def clicked(self): pass From 24563d55d251299c7cd0368a09bda76538a30811 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 13:47:48 +0100 Subject: [PATCH 095/260] closes #19 --- papi/core.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/papi/core.py b/papi/core.py index 10b4fb91..f13acea8 100644 --- a/papi/core.py +++ b/papi/core.py @@ -514,19 +514,23 @@ def __process_new_data__(self, event): # get plugin with sub_id and check for existence pl = self.core_data.get_dplugin_by_id(sub_id) if pl is not None: - # plugin exists, check whether it is a ViP or not - if pl.type == PLUGIN_VIP_IDENTIFIER or pl.type == PLUGIN_PCP_IDENTIFIER: - # Because its a ViP, we need a list of destination ID for new_data - id_list.append(pl.id) + if pl.state != PLUGIN_STATE_PAUSE or pl.state != PLUGIN_STATE_STOPPED: + # plugin exists, check whether it is a ViP or not + if pl.type == PLUGIN_VIP_IDENTIFIER or pl.type == PLUGIN_PCP_IDENTIFIER: + # Because its a ViP, we need a list of destination ID for new_data + id_list.append(pl.id) + else: + # Plugin is not running in GUI, so just 1:1 relation for event and destinations + opt.parameter_alias = pl.get_subscribtions()[oID][opt.block_name].alias + new_event = Event.data.NewData(oID, [pl.id], opt) + pl.queue.put(new_event) + + # this event will be a new parameter value for a plugin + if opt.is_parameter is True: + self.handle_parameter_change(pl, opt.parameter_alias, opt.data) else: - # Plugin is not running in GUI, so just 1:1 relation for event and destinations - opt.parameter_alias = pl.get_subscribtions()[oID][opt.block_name].alias - new_event = Event.data.NewData(oID, [pl.id], opt) - pl.queue.put(new_event) - - # this event will be a new parameter value for a plugin - if opt.is_parameter is True: - self.handle_parameter_change(pl, opt.parameter_alias, opt.data) + # plugin is paused + pass else: # pluign with sub_id does not exist in DCore of core self.log.printText(1, 'new_data, subscriber plugin with id ' + str( From fba97d3daac3dfe1787a725af6be6f8551004801 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 13:53:44 +0100 Subject: [PATCH 096/260] updated changelog --- CHANGENLOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 7d9d0388..28f8d51a 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -1,6 +1,16 @@ Changelog ------ +v.1.XX +--- + * [fix]: core will check plugin state before routing, so data to paused plugins will not be routed (#19) + * [fix]: oboslete parameter in send_parameter_change was removed (#21) + * [fix]: clean up of DParameter (#18) + + +v.1.0 +--- + v.0.9 --- * Plot Plugin: Speedup by use of downsampling, more useable parameter, more dynamic legend, new ContextMenu From 84ac07eae335dde74aeaef722d59722b313b766b Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 14:17:41 +0100 Subject: [PATCH 097/260] goOn var was not privated, fixes --- papi/plugin/base_classes/ownProcess_base.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/papi/plugin/base_classes/ownProcess_base.py b/papi/plugin/base_classes/ownProcess_base.py index 22a90706..16f41bce 100644 --- a/papi/plugin/base_classes/ownProcess_base.py +++ b/papi/plugin/base_classes/ownProcess_base.py @@ -46,7 +46,7 @@ def work_process(self, CoreQueue, pluginQueue, id, defaultEventTriggered=False, self.papi_init() # working should go at least one time - self.goOn = 1 + self.__goOn = 1 self.plugin_stopped = False @@ -57,7 +57,7 @@ def work_process(self, CoreQueue, pluginQueue, id, defaultEventTriggered=False, self.plugin_stopped = True # main working loop - while self.goOn: + while self.__goOn: self.evaluate_event_trigger(defaultEventTriggered) event = None try: @@ -73,7 +73,7 @@ def work_process(self, CoreQueue, pluginQueue, id, defaultEventTriggered=False, self.quit() if event.delete is True: # delete plugin, so work_progress will stop completely - self.goOn = 0 + self.__goOn = 0 event = Event.status.JoinRequest(self.__id__, 0, None) self._Core_event_queue__.put(event) else: @@ -129,7 +129,7 @@ def starting_sequence(self, config): event = Event.status.StartFailed(self.__id__, 0, None) self._Core_event_queue__.put(event) # end plugin - self.goOn = 0 + self.__goOn = 0 # sent join request to core event = Event.status.JoinRequest(self.__id__, 0, None) self._Core_event_queue__.put(event) From c28fdee7c9aa098176ddba5c2c1e784bd069f5b0 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 14:19:34 +0100 Subject: [PATCH 098/260] close #22 --- .../io/fourier_rect_mod/Fourier_Rect_MOD.py | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py b/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py index 61a631e1..788886b2 100644 --- a/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py +++ b/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py @@ -72,23 +72,31 @@ def start_init(self, config=None): self.set_event_trigger_mode(True) - thread = threading.Thread(target=self.thread_execute, args=(self.HOST,self.PORT) ) - thread.start() + self.goOn = True + + self.thread = threading.Thread(target=self.thread_execute, args=(self.HOST,self.PORT) ) + self.thread.start() + + return True def pause(self): - pass + self.goOn = False + self.thread.join() + def resume(self): - pass + self.goOn = True + self.thread = threading.Thread(target=self.thread_execute, args=(self.HOST,self.PORT) ) + self.thread.start() def thread_execute(self,host,port): - self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + #self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) #self.sock.setblocking(0) vec = numpy.zeros( (self.max_approx, (self.amax) )) - while True: + while self.goOn: self.sock.sendto(b'GET', (self.HOST, self.PORT) ) try: @@ -107,7 +115,7 @@ def thread_execute(self,host,port): self.send_new_data('Rectangle', t, vech) time.sleep(0.001*self.amax ) - + print('Thread ende') def execute(self, Data=None, block_name = None): print("EXECUTE FUNC") @@ -117,7 +125,8 @@ def set_parameter(self, name, value): pass def quit(self): - print('Fourier_Rect: will quit') + self.goOn = False + self.thread.join() def plugin_meta_updated(self): pass From b5afc34c104d4ac38e230a4b6eed0dd79b15d2d1 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 15:15:42 +0100 Subject: [PATCH 099/260] fixed some private/publix mix up of functions --- papi/plugin/base_classes/base_plugin.py | 12 ------ papi/plugin/base_classes/ownProcess_base.py | 46 +++++++++++++-------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py index b0165629..1b1d6e81 100644 --- a/papi/plugin/base_classes/base_plugin.py +++ b/papi/plugin/base_classes/base_plugin.py @@ -82,18 +82,6 @@ def set_parameter_internal(self, name, value): def merge_configs(self, cfg1, cfg2): return dict(list(cfg1.items()) + list(cfg2.items()) ) - def evaluate_event_trigger(self,default): - if self.user_event_triggered == 'default': - self.EventTriggered = default - if self.user_event_triggered is True: - self.EventTriggered = True - if self.user_event_triggered is False: - self.EventTriggered = False - - def set_event_trigger_mode(self, mode): - if mode is True or mode is False or mode == 'default': - self.user_event_triggered = mode - def send_parameter_change(self, data, block_name): opt = DOptionalData(DATA=data) diff --git a/papi/plugin/base_classes/ownProcess_base.py b/papi/plugin/base_classes/ownProcess_base.py index 16f41bce..a1903114 100644 --- a/papi/plugin/base_classes/ownProcess_base.py +++ b/papi/plugin/base_classes/ownProcess_base.py @@ -39,29 +39,29 @@ def work_process(self, CoreQueue, pluginQueue, id, defaultEventTriggered=False, self._Core_event_queue__ = CoreQueue self.__plugin_queue__ = pluginQueue self.__id__ = id - self.EventTriggered = defaultEventTriggered - self.user_event_triggered = 'default' - self.paused = False + self.__EventTriggered = defaultEventTriggered + self.__user_event_triggered = 'default' + self.__paused = False self.papi_init() # working should go at least one time self.__goOn = 1 - self.plugin_stopped = False + self.__plugin_stopped = False if autostart is True: # call start_init function to use developers init self.starting_sequence(config) else: - self.plugin_stopped = True + self.__plugin_stopped = True # main working loop while self.__goOn: self.evaluate_event_trigger(defaultEventTriggered) event = None try: - event = self.__plugin_queue__.get( self.paused or self.EventTriggered or self.plugin_stopped) + event = self.__plugin_queue__.get( self.__paused or self.__EventTriggered or self.__plugin_stopped) #process event except: event = None @@ -79,42 +79,42 @@ def work_process(self, CoreQueue, pluginQueue, id, defaultEventTriggered=False, else: # plugin should stop but is not getting deleted # response to core and rm_all_subs, blocks, paras - self.plugin_stopped = True + self.__plugin_stopped = True event = Event.status.PluginStopped(self.__id__, 0, None) self._Core_event_queue__.put(event) - if op=='start_plugin' and self.plugin_stopped is True: + if op=='start_plugin' and self.__plugin_stopped is True: # maybe new config? self.starting_sequence(config) - self.plugin_stopped = False + self.__plugin_stopped = False - if op=='pause_plugin' and self.paused is False and self.plugin_stopped is False: - self.paused = True + if op=='pause_plugin' and self.__paused is False and self.__plugin_stopped is False: + self.__paused = True self.pause() - if op=='resume_plugin' and self.paused is True and self.plugin_stopped is False: - self.paused = False + if op=='resume_plugin' and self.__paused is True and self.__plugin_stopped is False: + self.__paused = False self.resume() if op=='check_alive_status': alive_event = Event.status.Alive(self.__id__, 0, None) self._Core_event_queue__.put(alive_event) - if op=='new_data' and self.paused is False and self.plugin_stopped is False: + if op=='new_data' and self.__paused is False and self.__plugin_stopped is False: opt = event.get_optional_parameter() if opt.is_parameter is False: data = self.demux(opt.data_source_id, opt.block_name, opt.data) self.execute(Data=data, block_name = opt.block_name) if opt.is_parameter is True: self.set_parameter_internal(opt.parameter_alias, opt.data) - if op == 'set_parameter' and self.plugin_stopped is False: + if op == 'set_parameter' and self.__plugin_stopped is False: opt = event.get_optional_parameter() self.set_parameter_internal(opt.parameter_alias, opt.data) - if op == 'update_meta' and self.plugin_stopped is False: + if op == 'update_meta' and self.__plugin_stopped is False: opt = event.get_optional_parameter() self.update_plugin_meta(opt.plugin_object) else: - if self.paused or self.EventTriggered or self.plugin_stopped: + if self.__paused or self.__EventTriggered or self.__plugin_stopped: pass else: self.execute() @@ -136,3 +136,15 @@ def starting_sequence(self, config): def start_init(self, config): raise NotImplementedError("Please Implement this method") + + def evaluate_event_trigger(self,default): + if self.__user_event_triggered == 'default': + self.__EventTriggered = default + if self.__user_event_triggered is True: + self.__EventTriggered = True + if self.__user_event_triggered is False: + self.__EventTriggered = False + + def set_event_trigger_mode(self, mode): + if mode is True or mode is False or mode == 'default': + self.__user_event_triggered = mode \ No newline at end of file From e14d62e150979a595eb2f4051e9e031d1c9e0854 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 15:15:57 +0100 Subject: [PATCH 100/260] removed a print --- papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py b/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py index 788886b2..9324261a 100644 --- a/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py +++ b/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py @@ -115,7 +115,7 @@ def thread_execute(self,host,port): self.send_new_data('Rectangle', t, vech) time.sleep(0.001*self.amax ) - print('Thread ende') + def execute(self, Data=None, block_name = None): print("EXECUTE FUNC") From 1e5845824bf3b11efc0a648277c89f439874d72f Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 15:25:00 +0100 Subject: [PATCH 101/260] removed a print --- CHANGENLOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 28f8d51a..1c4e89f0 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -6,6 +6,7 @@ v.1.XX * [fix]: core will check plugin state before routing, so data to paused plugins will not be routed (#19) * [fix]: oboslete parameter in send_parameter_change was removed (#21) * [fix]: clean up of DParameter (#18) + * [fix]: renamed some variables of ownProcess_base to be private v.1.0 From 561a1e6edb372bfe8f66d81254c7c051c59cabd5 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 16:32:07 +0100 Subject: [PATCH 102/260] moved get_subscription for demuxing to the update_meta function to be more efficient and improve the performance --- papi/plugin/base_classes/base_plugin.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py index 1b1d6e81..e0a53471 100644 --- a/papi/plugin/base_classes/base_plugin.py +++ b/papi/plugin/base_classes/base_plugin.py @@ -39,7 +39,7 @@ class base_plugin(IPlugin): def papi_init(self, ): #self.__dplugin_ids__ = {} Not sure where needed TODO self.dplugin_info = None - + self.__subscription_for_demux = None @@ -154,19 +154,19 @@ def send_new_parameter_list(self, parameters): def update_plugin_meta(self, dplug): self.dplugin_info = dplug - + self.__subscription_for_demux = self.dplugin_info.get_subscribtions() self.plugin_meta_updated() def plugin_meta_updated(self): raise NotImplementedError("Please Implement this method") def demux(self, source_id, block_name, data): + if self.__subscription_for_demux is None: + self.__subscription_for_demux = self.dplugin_info.get_subscribtions() - subcribtions = self.dplugin_info.get_subscribtions() - sub_object = subcribtions[source_id][block_name] - + sub_object = self.__subscription_for_demux[source_id][block_name] sub_signals = sub_object.signals - sub_signals.append('t') + if 't' not in sub_signals: + sub_signals.append('t') return dict([(i, data[i]) for i in sub_signals if i in data]) - #return data \ No newline at end of file From 3ff69501c46713d2e11d2fefee4feceb25c5591e Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 26 Jan 2015 16:34:58 +0100 Subject: [PATCH 103/260] moved get_subscription for demuxing to the update_meta function to be more efficient and improve the performance --- CHANGENLOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 1c4e89f0..b34e1533 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -7,6 +7,7 @@ v.1.XX * [fix]: oboslete parameter in send_parameter_change was removed (#21) * [fix]: clean up of DParameter (#18) * [fix]: renamed some variables of ownProcess_base to be private + * [improvement]: changed the demux function to in imporve performance v.1.0 From 90aaabdbf9257fc0a9dd1fbf0812540fd03a4038 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 26 Jan 2015 16:50:44 +0100 Subject: [PATCH 104/260] first version of a plot plugin with own process --- papi/plugin/visual/PlotPerf/PlotPerf.py | 915 ++++++++++++++++++ .../visual/PlotPerf/PlotPerf.yapsy-plugin | 9 + papi/plugin/visual/PlotPerf/__init__.py | 29 + papi/plugin/visual/PlotPerf/items.py | 32 + 4 files changed, 985 insertions(+) create mode 100644 papi/plugin/visual/PlotPerf/PlotPerf.py create mode 100644 papi/plugin/visual/PlotPerf/PlotPerf.yapsy-plugin create mode 100644 papi/plugin/visual/PlotPerf/__init__.py create mode 100644 papi/plugin/visual/PlotPerf/items.py diff --git a/papi/plugin/visual/PlotPerf/PlotPerf.py b/papi/plugin/visual/PlotPerf/PlotPerf.py new file mode 100644 index 00000000..e980adf7 --- /dev/null +++ b/papi/plugin/visual/PlotPerf/PlotPerf.py @@ -0,0 +1,915 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +Sven Knuth +""" + +__author__ = 'Stefan' + +import papi.pyqtgraph as pq + +from papi.plugin.base_classes.vip_base import vip_base +from papi.data.DParameter import DParameter +import numpy as np +import collections +import re +import time +import papi.pyqtgraph as pg +from papi.pyqtgraph.widgets.RemoteGraphicsView import RemoteGraphicsView + +current_milli_time = lambda: int(round(time.time() * 1000)) + +from papi.pyqtgraph.Qt import QtCore, QtGui + +class PlotPerf(vip_base): + """ + style_codes: + 0 : QtCore.Qt.SolidLine, + 1 : QtCore.Qt.DashDotDotLine, + 2 : QtCore.Qt.DashDotLine, + 3 : QtCore.Qt.DashLine, + 4 : QtCore.Qt.DotLine + + color_codes: + 0 : (255, 255, 255), + 1 : (255, 0 , 0 ), + 2 : (0 , 255, 0 ), + 3 : (0 , 0 , 255), + 4 : (100, 100, 100) + """ + + def __init__(self): + super(PlotPerf, self).__init__() + """ + Function init + + :param config: + :return: + """ + + self.signals = {} + + self.__buffer_size__ = None + self.__downsampling_rate__ = 1 + self.__tbuffer__ = [] + self.__tdata_old__ = [0] + self.__signals_have_same_length = True + self.__roll_shift__ = None + self.__append_at__ = 1 + self.__new_added_data__ = 0 + self.__input_size__ = 0 + self.__rolling_plot__ = False + self.__colors_selected__ = [] + self.__styles_selected__ = [] + self.__show_grid_x__ = None + self.__show_grid_y__ = None + self.__parameters__ = {} + self.__update_intervall__ = None + self.__last_time__ = None + self.__plotWidget__ = None + self.__legend__ = None + self.__text_item__ = None + self.__vertical_line__ = None + self.__offset_line__ = None + + self.styles = { + 0: QtCore.Qt.SolidLine, + 1: QtCore.Qt.DashDotDotLine, + 2: QtCore.Qt.DashDotLine, + 3: QtCore.Qt.DashLine, + 4: QtCore.Qt.DotLine + } + + self.colors = { + 0: (255, 255, 255), + 1: (255, 0, 0 ), + 2: (0, 255, 0 ), + 3: (0, 0, 255), + 4: (100, 100, 100) + } + + def initiate_layer_0(self, config=None): + """ + Function initiate layer 0 + + :param config: + :return: + """ + + # --------------------------- + # Read configuration + # --------------------------- + int_re = re.compile(r'(\d+)') + + self.__show_grid_x__ = self.config['x-grid']['value'] == '1' + self.__show_grid_y__ = self.config['y-grid']['value'] == '1' + self.__rolling_plot__ = self.config['rolling_plot']['value'] == '1' + + self.__colors_selected__ = int_re.findall(self.config['color']['value']) + self.__styles_selected__ = int_re.findall(self.config['style']['value']) + + self.__buffer_size__ = int(int_re.findall(self.config['buffersize']['value'])[0]) + + self.__downsampling_rate__ = int(int_re.findall(self.config['downsampling_rate']['value'])[0]) + + + # ---------------------------- + # Create internal variables + # ---------------------------- + + self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) + + # -------------------------------- + # Create PlotWidget + # -------------------------------- + + self.__text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(0, 0)) + self.__vertical_line__ = pq.InfiniteLine() + + self.__plotWidget__ = pq.PlotWidget() + self.__plotWidget__.addItem(self.__text_item__) + + if self.__rolling_plot__: + self.__plotWidget__.addItem(self.__vertical_line__) + + self.__text_item__.setPos(0, 0) + + self.__rgv__ = RemoteGraphicsView(debug=False) + + self.set_widget_for_internal_usage(self.__rgv__) + + self.__plotWidget__ = self.__rgv__.pg.PlotItem() + # self.__plot_item__ = self.__plotWidget__.pg.PlotItem() + + self.__plotWidget__.setWindowTitle('PlotPerformanceTitle') + self.__plotWidget__.showGrid(x=self.__show_grid_x__, y=self.__show_grid_y__) + +# self.set_widget_for_internal_usage(self.__plotWidget__) + + # + if self.config['xRange-auto']['value']=='1': + pass + else: + self.use_range_for_x(self.config['xRange']['value']) + + if self.config['yRange-auto']['value']== '1': + pass + else: + self.use_range_for_y(self.config['yRange']['value']) + + # --------------------------- + # Create Parameters + # --------------------------- + + self.__parameters__['x-grid'] = DParameter('x-grid', 0, Regex='^(1|0){1}$') + self.__parameters__['y-grid'] = DParameter('y-grid', 0, Regex='^(1|0){1}$') + + self.__parameters__['color'] = DParameter('color', '[0 1 2 3 4]', Regex='^\[(\s*\d\s*)+\]') + self.__parameters__['style'] = DParameter('style', '[0 0 0 0 0]', Regex='^\[(\s*\d\s*)+\]') + self.__parameters__['rolling'] = DParameter('rolling', '0', Regex='^(1|0){1}') + + self.__parameters__['downsampling_rate'] = DParameter('downsampling_rate', self.__downsampling_rate__, Regex='^([1-9][0-9]?|100)$') + self.__parameters__['buffersize'] = DParameter('buffersize', self.__buffer_size__, Regex='^([1-9][0-9]{0,3}|10000)$') + + self.__parameters__['xRange-auto'] = DParameter('xRange-auto', '1', Regex='^(1|0){1}$') + self.__parameters__['xRange'] = DParameter('xRange', '[0,1]', Regex='(\d+\.\d+)') + self.__parameters__['yRange-auto'] = DParameter('yRange-auto', '1', Regex='^(1|0){1}$') + self.__parameters__['yRange'] = DParameter('yRange', '[0,1]', Regex='(\d+\.\d+)') + + self.send_new_parameter_list(list(self.__parameters__.values())) + + # --------------------------- + # Create Legend + # --------------------------- + + # self.__legend__ = pq.LegendItem((100, 40), offset=(40, 1)) # args are (size, offset) + # self.__legend__.setParentItem(self.__plotWidget__.graphicsItem()) + + self.__last_time__ = current_milli_time() + + self.__update_intervall__ = 40 # in milliseconds + + #self.setup_context_menu() + + # + if self.config['xRange-auto']['value']=='1': + pass + else: + self.use_range_for_x(self.config['xRange']['value']) + + if self.config['yRange-auto']['value']== '1': + pass + else: + self.use_range_for_y(self.config['yRange']['value']) + + return True + + def pause(self): + """ + Function pause + + :return: + """ + print('PlotPerformance paused') + + def resume(self): + """ + Function resume + + :return: + """ + print('PlotPerformance resumed') + + def execute(self, Data=None, block_name=None): + """ + Function execute + + :param Data: + :param block_name: + :return: + """ + print('execute') + return + t = Data['t'] + + self.__input_size__ = len(t) + self.__tbuffer__.extend(t) + self.__new_added_data__ += len(t) + self.__signals_have_same_length = True + + for key in Data: + if key != 't': + y = Data[key] + if key in self.signals: + buffer = self.signals[key]['buffer'] + buffer.extend(y) + self.__signals_have_same_length &= (len(t) == len(y)) + + if self.__input_size__ > 1 or self.__signals_have_same_length: + if current_milli_time() - self.__last_time__ > self.__update_intervall__: + + self.update_plot() + self.__last_time__ = current_milli_time() + self.__new_added_data__ = 0 + else: + self.update_plot_single_timestamp(Data) + + def update_plot(self): + """ + Function update_plot + + :return: + """ + shift_data = 0 + + for last_tvalue in self.__tdata_old__: + if last_tvalue in self.__tbuffer__: + shift_data = list(self.__tbuffer__).index(last_tvalue) + break + + tdata = list(self.__tbuffer__)[shift_data::self.__downsampling_rate__] + + if self.__rolling_plot__: + self.__append_at__ += self.__new_added_data__ / self.__downsampling_rate__ + self.__append_at__ %= len(tdata) + + # -------------------------- + # iterate over all buffers + # -------------------------- + + for signal_name in self.signals: + data = list(self.signals[signal_name]['buffer'])[shift_data::self.__downsampling_rate__] + + if self.__rolling_plot__: + data = np.roll(data, int(self.__append_at__)) + self.__vertical_line__.setValue(tdata[int(self.__append_at__) - 1]) + else: + self.__vertical_line__.setValue(tdata[0]) + + curve = self.signals[signal_name]['curve'] + + curve.setData( np.array(tdata), data) + + self.__tdata_old__ = tdata + + def update_plot_single_timestamp(self, data): + """ + Function update_plot_single_timestamp + + :return: + """ + + self.__text_item__.setText("Time " + str(data['t'][0]), color=(200, 200, 200)) + + for signal_name in data: + if signal_name != 't': + signal_data = data[signal_name] + if signal_name in self.signals: + tdata = np.linspace(1, len(signal_data), len(signal_data)) + + curve = self.signals[signal_name]['curve'] + curve.setData(tdata, signal_data, _callSync='off') + + + def set_parameter(self, name, value): + """ + Function set parameters + + :param name: + :param value: + :return: + """ + return + if name == 'x-grid': + self.config['x-grid']['value'] = value + self.__plotWidget__.showGrid(x=value == '1') + self.xGrid_Checkbox.setChecked(value=='1') + + if name == 'y-grid': + self.config['y-grid']['value'] = value + self.__plotWidget__.showGrid(y=value == '1') + self.yGrid_Checkbox.setChecked(value=='1') + + if name == 'downsampling_rate': + self.config['downsampling_rate']['value'] = value + self.__downsampling_rate__ = int(value) + #self.__plotWidget__.getPlotItem().setDownsampling(ds=int(value),auto=False,mode='mean') + self.__new_added_data__ = 0 + + + if name == 'rolling': + self.__rolling_plot__ = int(float(value)) == int('1') + self.config['rolling_plot']['value'] = value + self.rolling_Checkbox.setChecked(value=='1') + if self.__rolling_plot__: + # if self.__vertical_line__ not in self.__plotWidget__.listDataItems(): + + self.__plotWidget__.addItem(self.__vertical_line__) + + if name == 'color': + self.config['color']['value'] = value + int_re = re.compile(r'(\d+)') + self.__colors_selected__ = int_re.findall(self.config['color']['value']) + self.update_pens() + + if name == 'style': + self.config['style']['value'] = value + int_re = re.compile(r'(\d+)') + self.__styles_selected__ = int_re.findall(self.config['style']['value']) + self.update_pens() + + if name == 'buffersize': + self.config['buffersize']['value'] = value + self.set_buffer_size(value) + + if name == 'xRange-auto': + self.config['xRange-auto']['value'] = value + self.xRange_AutoCheckbox.setChecked(value=='1') + if int(value) == 1: + self.xRange_minEdit.setDisabled(True) + self.xRange_maxEdit.setDisabled(True) + self.__plotWidget__.getPlotItem().getViewBox().menu.xAutoClicked() + else: + self.use_range_for_x(self.config['xRange']['value']) + self.xRange_minEdit.setDisabled(False) + self.xRange_maxEdit.setDisabled(False) + + if name == 'yRange-auto': + self.config['yRange-auto']['value'] = value + self.yRange_AutoCheckbox.setChecked(value=='1') + if int(value) == 1: + self.yRange_minEdit.setDisabled(True) + self.yRange_maxEdit.setDisabled(True) + self.__plotWidget__.getPlotItem().getViewBox().menu.yAutoClicked() + else: + self.yRange_minEdit.setDisabled(False) + self.yRange_maxEdit.setDisabled(False) + self.use_range_for_y(self.config['yRange']['value']) + + if name == 'xRange': + self.config['xRange']['value'] = value + if self.config['xRange-auto']['value'] == '0': + self.use_range_for_x(value) + + if name == 'yRange': + self.config['yRange']['value'] = value + if self.config['yRange-auto']['value'] == '0': + self.use_range_for_y(value) + + def update_pens(self): + """ + Function update pens + + :return: + """ + + for signal_name in self.signals.keys(): + signal_id = self.signals[signal_name]['id'] + + new_pen = self.get_pen(signal_id) + + self.signals[signal_name]['curve'].setPen(new_pen) + + def set_buffer_size(self, new_size): + """ + Function set buffer size + + :param new_size: + :return: + """ + + self.__buffer_size__ = int(new_size) + + # ------------------------------- + # Change Time Buffer + # ------------------------------- + + self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) + + # ------------------------------- + # Change Buffer of current + # plotted signals + # ------------------------------- + + start_size = len(self.__tbuffer__) + + for signal_name in self.signals: + buffer_new = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION + + buffer_old = self.signals[signal_name]['buffer'] + # buffer_new.extend(buffer_old) + + self.signals[signal_name]['buffer'] = buffer_new + + self.__new_added_data__ = 0 + + def plugin_meta_updated(self): + """ + By this function the plot is able to handle more than one input for plotting. + + :return: + """ + subscriptions = self.dplugin_info.get_subscribtions() + + current_signals = {} + index = 0 + + for dpluginsub_id in subscriptions: + for dblock_name in subscriptions[dpluginsub_id]: + + # get subscription for dblock + subscription = subscriptions[dpluginsub_id][dblock_name] + + for signal_name in subscription.get_signals(): + signal = subscription.get_dblock().get_signal_by_uname(signal_name) + index += 1 + current_signals[signal_name] = {} + current_signals[signal_name]['signal'] = signal + current_signals[signal_name]['index'] = index + + # current_signals = sorted(current_signals) + + # Add missing buffers + for signal_name in sorted(current_signals.keys()): + if signal_name not in self.signals: + signal = current_signals[signal_name]['signal'] + self.add_databuffer(signal, current_signals[signal_name]['index']) + + # Delete old buffers + for signal_name in self.signals.copy(): + if signal_name not in current_signals: + signal = self.signals[signal_name]['signal'] + self.remove_databuffer(signal) + + self.update_pens() + #self.update_legend() + + def add_databuffer(self, signal, signal_id): + """ + Create new buffer for signal_name. + + :param signal_name: + :param signal_id: + :return: + """ + + signal_name = signal.uname + + if signal_name not in self.signals: + self.signals[signal_name] = {} + + start_size = len(self.__tbuffer__) + + buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION + + + curve = self.__plotWidget__.plot([0, 1], [0, 1]) + + + self.signals[signal_name]['buffer'] = buffer + self.signals[signal_name]['curve'] = curve + self.signals[signal_name]['id'] = signal_id + self.signals[signal_name]['signal'] = signal + + + def remove_databuffer(self, signal): + """ + Remove the databuffer for signal_name. + + :param signal_name: + :return: + """ + + signal_name = signal.uname + + if signal_name in self.signals: + curve = self.signals[signal_name]['curve'] + curve.clear() + # self.__legend__.removeItem(legend_name) + del self.signals[signal_name] + + def get_pen(self, index): + """ + Function get pen + + :param index: + :return: + """ + index = int(index) + + style_index = index % len(self.__styles_selected__) + style_code = int(self.__styles_selected__[style_index]) + + color_index = index % len(self.__colors_selected__) + color_code = int(self.__colors_selected__[color_index]) + + if style_code in self.styles: + style = self.styles[style_code] + else: + style = self.styles[1] + + if color_code in self.colors: + color = self.colors[color_code] + else: + color = self.colors[1] + + return pq.mkPen(color=color, style=style) + + def use_range_for_x(self, value): + reg = re.compile(r'(\d+\.\d+)') + range = reg.findall(value) + if len(range) == 2: + self.xRange_minEdit.setText(range[0]) + self.xRange_maxEdit.setText(range[1]) + self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]),float(range[1])) + + def use_range_for_y(self, value): + reg = re.compile(r'(\d+\.\d+)') + range = reg.findall(value) + if len(range) == 2: + self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]), float(range[1])) + + def setup_context_menu(self): + self.custMenu = QtGui.QMenu("Options") + self.axesMenu = QtGui.QMenu('Axes') + self.gridMenu = QtGui.QMenu('Grid') + + + # --------------------------------------------------------- + # #### X-Range Actions + self.xRange_Widget = QtGui.QWidget() + self.xRange_Layout = QtGui.QVBoxLayout(self.xRange_Widget) + self.xRange_Layout.setContentsMargins(2, 2, 2, 2) + self.xRange_Layout.setSpacing(1) + + self.xRange_AutoCheckbox = QtGui.QCheckBox(checked= self.config['xRange-auto']['value'] == '1') + self.xRange_AutoCheckbox.stateChanged.connect(self.contextMenu_xRange_toogle) + self.xRange_AutoCheckbox.setText('X-Autorange') + self.xRange_Layout.addWidget(self.xRange_AutoCheckbox) + + ##### X Line Edits + # Layout + self.xRange_EditWidget = QtGui.QWidget() + self.xRange_EditLayout = QtGui.QHBoxLayout(self.xRange_EditWidget) + self.xRange_EditLayout.setContentsMargins(2, 2, 2, 2) + self.xRange_EditLayout.setSpacing(1) + + # get old values; + reg = re.compile(r'(\d+\.\d+)') + range = reg.findall(self.config['xRange']['value']) + if len(range) == 2: + x_min = range[0] + x_max = range[1] + else: + x_min = '0.0' + x_max = '1.0' + + + # Min + self.xRange_minEdit = QtGui.QLineEdit() + self.xRange_minEdit.setFixedWidth(80) + self.xRange_minEdit.setText(x_min) + self.xRange_minEdit.editingFinished.connect(self.contextMenu_xRange_toogle) + # Max + self.xRange_maxEdit = QtGui.QLineEdit() + self.xRange_maxEdit.setFixedWidth(80) + self.xRange_maxEdit.setText(x_max) + self.xRange_maxEdit.editingFinished.connect(self.contextMenu_xRange_toogle) + # addTo Layout + self.xRange_EditLayout.addWidget(self.xRange_minEdit) + self.xRange_EditLayout.addWidget(QtGui.QLabel('<')) + self.xRange_EditLayout.addWidget(self.xRange_maxEdit) + self.xRange_Layout.addWidget(self.xRange_EditWidget) + + # build Action + self.xRange_Action = QtGui.QWidgetAction(self.__plotWidget__) + self.xRange_Action.setDefaultWidget(self.xRange_Widget) + + + # --------------------------------------------------------------- + ##### Y-Range Actions + self.yRange_Widget = QtGui.QWidget() + self.yRange_Layout = QtGui.QVBoxLayout(self.yRange_Widget) + self.yRange_Layout.setContentsMargins(2, 2, 2, 2) + self.yRange_Layout.setSpacing(1) + + self.yRange_AutoCheckbox = QtGui.QCheckBox(checked= self.config['xRange-auto']['value'] == '1') + self.yRange_AutoCheckbox.stateChanged.connect(self.contextMenu_yRange_toogle) + self.yRange_AutoCheckbox.setText('Y-Autorange') + self.yRange_Layout.addWidget(self.yRange_AutoCheckbox) + + ##### Y Line Edits + # Layout + self.yRange_EditWidget = QtGui.QWidget() + self.yRange_EditLayout = QtGui.QHBoxLayout(self.yRange_EditWidget) + self.yRange_EditLayout.setContentsMargins(2, 2, 2, 2) + self.yRange_EditLayout.setSpacing(1) + + # get old values; + reg = re.compile(r'(\d+\.\d+)') + range = reg.findall(self.config['yRange']['value']) + if len(range) == 2: + y_min = range[0] + y_max = range[1] + else: + y_min = '0.0' + y_max = '1.0' + + # Min + self.yRange_minEdit = QtGui.QLineEdit() + self.yRange_minEdit.setFixedWidth(80) + self.yRange_minEdit.setText(y_min) + self.yRange_minEdit.editingFinished.connect(self.contextMenu_yRange_toogle) + + # Max + self.yRange_maxEdit = QtGui.QLineEdit() + self.yRange_maxEdit.setFixedWidth(80) + self.yRange_maxEdit.setText(y_max) + self.yRange_maxEdit.editingFinished.connect(self.contextMenu_yRange_toogle) + # addTo Layout + self.yRange_EditLayout.addWidget(self.yRange_minEdit) + self.yRange_EditLayout.addWidget(QtGui.QLabel('<')) + self.yRange_EditLayout.addWidget(self.yRange_maxEdit) + self.yRange_Layout.addWidget(self.yRange_EditWidget) + + # build Action + self.yRange_Action = QtGui.QWidgetAction(self.__plotWidget__) + self.yRange_Action.setDefaultWidget(self.yRange_Widget) + + ##### Rolling Plot + self.rolling_Checkbox = QtGui.QCheckBox() + self.rolling_Checkbox.setText('Rolling plot') + self.rolling_Checkbox.setChecked(self.config['rolling_plot']['value'] == '1') + self.rolling_Checkbox.stateChanged.connect(self.contextMenu_rolling_toogled) + self.rolling_Checkbox_Action = QtGui.QWidgetAction(self.__plotWidget__) + self.rolling_Checkbox_Action.setDefaultWidget(self.rolling_Checkbox) + + + + ##### Build axes menu + self.axesMenu.addAction(self.xRange_Action) + self.axesMenu.addSeparator().setText("Y-Range") + self.axesMenu.addAction(self.yRange_Action) + + # Grid Menu: + # ----------------------------------------------------------- + # Y-Grid checkbox + self.xGrid_Checkbox = QtGui.QCheckBox() + self.xGrid_Checkbox.stateChanged.connect(self.contextMenu_xGrid_toogle) + self.xGrid_Checkbox.setText('X-Grid') + self.xGrid_Action = QtGui.QWidgetAction(self.__plotWidget__) + self.xGrid_Action.setDefaultWidget(self.xGrid_Checkbox) + self.gridMenu.addAction(self.xGrid_Action) + # Check config for startup state + if self.__show_grid_x__: + self.xGrid_Checkbox.setChecked(True) + + # X-Grid checkbox + self.yGrid_Checkbox = QtGui.QCheckBox() + self.yGrid_Checkbox.stateChanged.connect(self.contextMenu_yGrid_toogle) + self.yGrid_Checkbox.setText('Y-Grid') + self.yGrid_Action = QtGui.QWidgetAction(self.__plotWidget__) + self.yGrid_Action.setDefaultWidget(self.yGrid_Checkbox) + self.gridMenu.addAction(self.yGrid_Action) + # Check config for startup state + if self.__show_grid_y__: + self.yGrid_Checkbox.setChecked(True) + + # add Menus + self.custMenu.addMenu(self.axesMenu) + self.custMenu.addMenu(self.gridMenu) + self.custMenu.addSeparator().setText("Rolling Plot") + self.custMenu.addAction(self.rolling_Checkbox_Action) +# self.__plotWidget__.getPlotItem().getViewBox().menu.clear() +# self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] + + + #self.__plotWidget__.getPlotItem().getViewBox() + + def range_changed(self): + print('r') + + def contextMenu_rolling_toogled(self): + if self.rolling_Checkbox.isChecked(): + self.control_api.do_set_parameter(self.__id__, 'rolling', '1') + else: + self.control_api.do_set_parameter(self.__id__, 'rolling', '0') + + def contextMenu_xGrid_toogle(self): + if self.xGrid_Checkbox.isChecked(): + self.control_api.do_set_parameter(self.__id__, 'x-grid', '1') + else: + self.control_api.do_set_parameter(self.__id__, 'x-grid', '0') + + def contextMenu_yGrid_toogle(self): + if self.yGrid_Checkbox.isChecked(): + self.control_api.do_set_parameter(self.__id__, 'y-grid', '1') + else: + self.control_api.do_set_parameter(self.__id__, 'y-grid', '0') + + def contextMenu_xRange_toogle(self): + if self.xRange_AutoCheckbox.isChecked(): + # do autorange + self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '1') + self.xRange_minEdit.setDisabled(True) + self.xRange_maxEdit.setDisabled(True) + else: + self.xRange_minEdit.setDisabled(False) + self.xRange_maxEdit.setDisabled(False) + mi = self.xRange_minEdit.text() + ma = self.xRange_maxEdit.text() + self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '0') + self.control_api.do_set_parameter(self.__id__, 'xRange', '[' + mi + ' ' + ma + ']') + + def contextMenu_yRange_toogle(self): + if self.yRange_AutoCheckbox.isChecked(): + # do autorange + self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '1') + self.yRange_minEdit.setDisabled(True) + self.yRange_maxEdit.setDisabled(True) + else: + self.yRange_minEdit.setDisabled(False) + self.yRange_maxEdit.setDisabled(False) + # do man range + mi = self.yRange_minEdit.text() + ma = self.yRange_maxEdit.text() + self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '0') + self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') + + def update_signals(self): + subscriptions = self.dplugin_info.get_subscribtions() + + for dpluginsub_id in subscriptions: + for dblock_name in subscriptions[dpluginsub_id]: + + # get subscription for dblock + subscription = subscriptions[dpluginsub_id][dblock_name] + + for signal_name in subscription.get_signals(): + signal = subscription.get_dblock().get_signal_by_uname(signal_name) + + self.signals[signal_name]['signal'] = signal + + def update_legend(self): + # self.__plotWidget__.removeItem(self.__legend__) + self.__legend__.scene().removeItem(self.__legend__) + del self.__legend__ + + self.__legend__ = pq.LegendItem((100, 40), offset=(40, 1)) # args are (size, offset) + self.__legend__.setParentItem(self.__plotWidget__.graphicsItem()) + + self.update_signals() + + for signal_name in sorted(self.signals.keys()): + curve = self.signals[signal_name]['curve'] + signal = self.signals[signal_name]['signal'] + legend_name = signal.dname + + self.__legend__.addItem(curve, legend_name) + + def quit(self): + """ + Function quit plugin + + :return: + """ + print('PlotPerformance: will quit') + + def get_plugin_configuration(self): + """ + Function get plugin configuration + + :return {}: + """ + config = { + # 'label_y': { + # 'value': "amplitude, V", + # 'regex': '\w+,\s+\w+', + # 'display_text': 'Label-Y' + # }, 'label_x': { + # 'value': "time, s", + # 'regex': '\w+,\s*\w+', + # 'display_text': 'Label-X' + # }, + 'x-grid': { + 'value': "0", + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'Grid-X' + }, 'y-grid': { + 'value': "0", + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'Grid-Y' + }, 'color': { + 'value': "[0 1 2 3 4]", + 'regex': '^\[(\s*\d\s*)+\]', + 'advanced': '1', + 'display_text': 'Color' + }, 'style': { + 'value': "[0 0 0 0 0]", + 'regex': '^\[(\s*\d\s*)+\]', + 'advanced': '1', + 'display_text': 'Style' + }, 'buffersize': { + 'value': "1000", + 'regex': '^([1-9][0-9]{0,3}|10000)$', + 'advanced': '1', + 'display_text': 'Buffersize' + }, 'downsampling_rate': { + 'value': "1", + 'regex': '(\d+)' + }, 'rolling_plot': { + 'value': '0', + 'regex': '^(1|0)$', + 'type': 'bool', + 'display_text': 'Rolling Plot' + }, 'xRange-auto': { + 'value': '1', + 'regex': '^(1|0)$', + 'type': 'bool', + 'advanced': '1', + 'display_text': 'x: auto range' + }, 'yRange-auto': { + 'value': '1', + 'regex': '^(1|0)$', + 'type': 'bool', + 'advanced': '1', + 'display_text': 'y: auto range' + }, 'xRange': { + 'value': '[0.0 1.0]', + 'regex': '(\d+\.\d+)', + 'advanced': '1', + 'display_text': 'x: range' + }, 'yRange': { + 'value': '[0.0 1.0]', + 'regex': '(\d+\.\d+)', + 'advanced': '1', + 'display_text': 'y: range' + } + } + # http://www.regexr.com/ + return config \ No newline at end of file diff --git a/papi/plugin/visual/PlotPerf/PlotPerf.yapsy-plugin b/papi/plugin/visual/PlotPerf/PlotPerf.yapsy-plugin new file mode 100644 index 00000000..a6e4be99 --- /dev/null +++ b/papi/plugin/visual/PlotPerf/PlotPerf.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = PlotPerf +Module = PlotPerf + +[Documentation] +Author = S.R., S.K. +Version = 0.7 +Website = +Description = Basic plotting plugin. Supports more than one signals which must have the same time vector. \ No newline at end of file diff --git a/papi/plugin/visual/PlotPerf/__init__.py b/papi/plugin/visual/PlotPerf/__init__.py new file mode 100644 index 00000000..7c97c23d --- /dev/null +++ b/papi/plugin/visual/PlotPerf/__init__.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 +# -*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' diff --git a/papi/plugin/visual/PlotPerf/items.py b/papi/plugin/visual/PlotPerf/items.py new file mode 100644 index 00000000..09077f28 --- /dev/null +++ b/papi/plugin/visual/PlotPerf/items.py @@ -0,0 +1,32 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Sven Knuth +""" + +import papi.pyqtgraph as pg +import numpy as np + + From c4694ae05f7a08a94eea0043d631d28e6f389933 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 26 Jan 2015 16:51:49 +0100 Subject: [PATCH 105/260] fixed incomplete module name --- papi/pyqtgraph/multiprocess/remoteproxy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/papi/pyqtgraph/multiprocess/remoteproxy.py b/papi/pyqtgraph/multiprocess/remoteproxy.py index 4f484b74..8be2add0 100644 --- a/papi/pyqtgraph/multiprocess/remoteproxy.py +++ b/papi/pyqtgraph/multiprocess/remoteproxy.py @@ -252,6 +252,8 @@ def handleRequest(self): elif cmd == 'import': name = opts['module'] fromlist = opts.get('fromlist', []) + + name = "papi." + name mod = builtins.__import__(name, fromlist=fromlist) if len(fromlist) == 0: From 02da0b3002618340c9bbf0c285bbbeffc9d81a36 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Wed, 28 Jan 2015 12:07:32 +0100 Subject: [PATCH 106/260] first attempt to start plot without gui/core --- papi/plugin/visual/Plot/Plot.py | 20 +++++++--- papi/plugin/visual/Plot/perf.py | 66 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 papi/plugin/visual/Plot/perf.py diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 20ff3fc1..9287ff8c 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -36,6 +36,7 @@ import collections import re import time +import papi.pyqtgraph as pg current_milli_time = lambda: int(round(time.time() * 1000)) @@ -59,7 +60,7 @@ class Plot(vip_base): 4 : (100, 100, 100) """ - def __init__(self): + def __init__(self, debug=False): super(Plot, self).__init__() """ Function init @@ -143,8 +144,8 @@ def initiate_layer_0(self, config=None): # -------------------------------- # Create PlotWidget # -------------------------------- - self.__text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(0, 0)) + self.__vertical_line__ = pq.InfiniteLine() self.__plotWidget__ = pq.PlotWidget() @@ -152,14 +153,15 @@ def initiate_layer_0(self, config=None): if self.__rolling_plot__: self.__plotWidget__.addItem(self.__vertical_line__) - self.__text_item__.setPos(0, 0) self.__plotWidget__.setWindowTitle('PlotPerformanceTitle') self.__plotWidget__.showGrid(x=self.__show_grid_x__, y=self.__show_grid_y__) + self.set_widget_for_internal_usage(self.__plotWidget__) + # if self.config['xRange-auto']['value']=='1': pass @@ -386,6 +388,7 @@ def update_plot(self): # -------------------------- # iterate over all buffers # -------------------------- + now = pg.ptime.time() for signal_name in self.signals: data = list(self.signals[signal_name]['buffer'])[shift_data::self.__downsampling_rate__] @@ -396,7 +399,14 @@ def update_plot(self): self.__vertical_line__.setValue(tdata[0]) curve = self.signals[signal_name]['curve'] - curve.setData(tdata, data, _callSync='off') + # if len(tdata) > 0 : + # print(np.array(tdata)) + # MultiLine(np.array(tdata), data) + + new_tdata = np.linspace(0, len(tdata)-1, len(tdata)) + + curve.setData(new_tdata, data, _callSync='off') + print("Plot time: %0.5f sec" % (pg.ptime.time()-now) ) self.__tdata_old__ = tdata @@ -512,7 +522,7 @@ def add_databuffer(self, signal, signal_id): buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION - curve = self.__plotWidget__.plot([0, 1], [0, 1]) + curve = self.__plotWidget__.plot([0, 1], [0, 1], clear=True) self.signals[signal_name]['buffer'] = buffer self.signals[signal_name]['curve'] = curve diff --git a/papi/plugin/visual/Plot/perf.py b/papi/plugin/visual/Plot/perf.py new file mode 100644 index 00000000..a6e29304 --- /dev/null +++ b/papi/plugin/visual/Plot/perf.py @@ -0,0 +1,66 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Sven Knuth +""" + +import sys +import os + +sys.path.insert(0,os.path.abspath('../../../../')) + +print(sys.path) +import papi.pyqtgraph as pq + +from PySide.QtGui import QApplication, QLabel + +import importlib.machinery + +imp_path = "Plot.py" +class_name = "Plot" + +app = QApplication([]) + +loader = importlib.machinery.SourceFileLoader(class_name, imp_path) +current_modul = loader.load_module() + +plugin = getattr(current_modul, class_name)() + +config = plugin.get_plugin_configuration() +plugin.config = config + +# print('1') +# __text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(0, 0)) +# print('2') + + +plugin.initiate_layer_0(config) + + + +window = QLabel('Window from label') +window.show() + +app.exec_() \ No newline at end of file From 4be49613e22e14212b91af02ec3a7766769b91b2 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Wed, 28 Jan 2015 15:25:36 +0100 Subject: [PATCH 107/260] added code needed to analyse plot performance --- papi/plugin/visual/Plot/Plot.py | 76 ++++++++++++++++++++++--- papi/plugin/visual/Plot/perf.py | 40 ++++++++++--- papi/plugin/visual/PlotPerf/PlotPerf.py | 36 +++++++++--- 3 files changed, 127 insertions(+), 25 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 9287ff8c..4f21c631 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -32,6 +32,7 @@ from papi.plugin.base_classes.vip_base import vip_base from papi.data.DParameter import DParameter +from papi.data.DSignal import DSignal import numpy as np import collections import re @@ -43,6 +44,19 @@ from papi.pyqtgraph.Qt import QtCore, QtGui +class MultiLine(pg.QtGui.QGraphicsPathItem): + def __init__(self, x, y): + """x and y are 2D arrays of shape (Nplots, Nsamples)""" + connect = np.ones(x.shape, dtype=bool) + connect[:,-1] = 0 # don't draw the segment between each trace + self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten()) + pg.QtGui.QGraphicsPathItem.__init__(self, self.path) + self.setPen(pg.mkPen('w')) + def shape(self): # override because QGraphicsPathItem.shape is too expensive. + return pg.QtGui.QGraphicsItem.shape(self) + def boundingRect(self): + return self.path.boundingRect() + class Plot(vip_base): """ style_codes: @@ -71,6 +85,7 @@ def __init__(self, debug=False): self.signals = {} + self.__papi_debug__ = debug self.__buffer_size__ = None self.__downsampling_rate__ = 1 self.__tbuffer__ = [] @@ -158,8 +173,8 @@ def initiate_layer_0(self, config=None): self.__plotWidget__.showGrid(x=self.__show_grid_x__, y=self.__show_grid_y__) - - self.set_widget_for_internal_usage(self.__plotWidget__) + if not self.__papi_debug__: + self.set_widget_for_internal_usage(self.__plotWidget__) # @@ -192,7 +207,8 @@ def initiate_layer_0(self, config=None): self.__parameters__['yRange-auto'] = DParameter('yRange-auto', '1', Regex='^(1|0){1}$') self.__parameters__['yRange'] = DParameter('yRange', '[0,1]', Regex='(\d+\.\d+)') - self.send_new_parameter_list(list(self.__parameters__.values())) + if not self.__papi_debug__: + self.send_new_parameter_list(list(self.__parameters__.values())) # --------------------------- # Create Legend @@ -251,6 +267,8 @@ def execute(self, Data=None, block_name=None): self.__new_added_data__ += len(t) self.__signals_have_same_length = True + + for key in Data: if key != 't': y = Data[key] @@ -388,6 +406,7 @@ def update_plot(self): # -------------------------- # iterate over all buffers # -------------------------- + now = pg.ptime.time() for signal_name in self.signals: data = list(self.signals[signal_name]['buffer'])[shift_data::self.__downsampling_rate__] @@ -398,15 +417,32 @@ def update_plot(self): else: self.__vertical_line__.setValue(tdata[0]) + curve = self.signals[signal_name]['curve'] # if len(tdata) > 0 : # print(np.array(tdata)) # MultiLine(np.array(tdata), data) new_tdata = np.linspace(0, len(tdata)-1, len(tdata)) + #print(new_tdata) + + # if signal_name != "signal_1": + # print(signal_name) + # print(data) + #curve.setData(new_tdata, data, _callSync='off') + + #curve.setData(tdata, data, _callSync='off') + + # if self.__papi_debug__: + # print("Plot time: %0.5f sec" % (pg.ptime.time()-now) ) - curve.setData(new_tdata, data, _callSync='off') - print("Plot time: %0.5f sec" % (pg.ptime.time()-now) ) + y = np.random.normal(size=(120,20000), scale=0.2) + np.arange(120)[:,np.newaxis] + x = np.empty((120,20000)) + x[:] = np.arange(20000)[np.newaxis,:] + + lines = MultiLine(x, y) + + self.__plotWidget__.addItem(lines) self.__tdata_old__ = tdata @@ -522,7 +558,7 @@ def add_databuffer(self, signal, signal_id): buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION - curve = self.__plotWidget__.plot([0, 1], [0, 1], clear=True) + curve = self.__plotWidget__.plot([0, 1], [0, 1], clear=False) self.signals[signal_name]['buffer'] = buffer self.signals[signal_name]['curve'] = curve @@ -739,9 +775,11 @@ def setup_context_menu(self): self.custMenu.addSeparator().setText("Rolling Plot") self.custMenu.addAction(self.rolling_Checkbox_Action) self.__plotWidget__.getPlotItem().getViewBox().menu.clear() - self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] - + if not self.__papi_debug__: + self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] + if self.__papi_debug__: + self.__plotWidget__.getPlotItem().ctrlMenu = [self.custMenu] #self.__plotWidget__.getPlotItem().getViewBox() def range_changed(self): @@ -833,6 +871,28 @@ def quit(self): """ print('PlotPerformance: will quit') + def debug_papi(self): + config = self.get_plugin_configuration() + self.config = config + self.__id__ = 0 + self.initiate_layer_0(config) + + signal_1 = DSignal('signal_1') + signal_2 = DSignal('signal_2') + signal_3 = DSignal('signal_3') + signal_4 = DSignal('signal_4') + signal_5 = DSignal('signal_5') + + self.add_databuffer(signal_1, 1) + self.add_databuffer(signal_2, 2) + self.add_databuffer(signal_3, 3) + self.add_databuffer(signal_4, 4) + self.add_databuffer(signal_5, 5) + + print(self.signals) + + pass + def get_plugin_configuration(self): """ Function get plugin configuration diff --git a/papi/plugin/visual/Plot/perf.py b/papi/plugin/visual/Plot/perf.py index a6e29304..a01108ef 100644 --- a/papi/plugin/visual/Plot/perf.py +++ b/papi/plugin/visual/Plot/perf.py @@ -28,6 +28,8 @@ import sys import os +import time +import random sys.path.insert(0,os.path.abspath('../../../../')) @@ -35,9 +37,29 @@ import papi.pyqtgraph as pq from PySide.QtGui import QApplication, QLabel +from PySide import QtCore import importlib.machinery + +def do_fctn(plugin): + t = plugin.__t__ + data = {} + for i in range(10): + data['t'] = [t] + t += 0.01 + data['signal_1'] = [random.randint(0, 5)] + data['signal_2'] = [random.randint(10, 15)] + data['signal_3'] = [random.randint(20, 25)] + data['signal_4'] = [random.randint(30, 35)] + data['signal_5'] = [random.randint(40, 45)] + + + plugin.execute(data) + plugin.__t__ = t + + QtCore.QTimer.singleShot(20, lambda : do_fctn(plugin)) + imp_path = "Plot.py" class_name = "Plot" @@ -46,21 +68,23 @@ loader = importlib.machinery.SourceFileLoader(class_name, imp_path) current_modul = loader.load_module() -plugin = getattr(current_modul, class_name)() - -config = plugin.get_plugin_configuration() -plugin.config = config +plugin = getattr(current_modul, class_name)(debug=True) # print('1') # __text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(0, 0)) # print('2') -plugin.initiate_layer_0(config) +plugin.debug_papi() + +plot_widget = plugin.__plotWidget__ + +plot_widget.show() + +plugin.__t__ = 0 +QtCore.QTimer.singleShot(50, lambda : do_fctn(plugin)) +app.exec_() -window = QLabel('Window from label') -window.show() -app.exec_() \ No newline at end of file diff --git a/papi/plugin/visual/PlotPerf/PlotPerf.py b/papi/plugin/visual/PlotPerf/PlotPerf.py index e980adf7..447c2614 100644 --- a/papi/plugin/visual/PlotPerf/PlotPerf.py +++ b/papi/plugin/visual/PlotPerf/PlotPerf.py @@ -150,6 +150,9 @@ def initiate_layer_0(self, config=None): self.__vertical_line__ = pq.InfiniteLine() self.__plotWidget__ = pq.PlotWidget() + self.__plotWidget__.setAntialiasing(False) + self.__plotWidget__.useOpenGL(True) + #self.__plotWidget__.op self.__plotWidget__.addItem(self.__text_item__) if self.__rolling_plot__: @@ -157,17 +160,27 @@ def initiate_layer_0(self, config=None): self.__text_item__.setPos(0, 0) - self.__rgv__ = RemoteGraphicsView(debug=False) - self.set_widget_for_internal_usage(self.__rgv__) + ######################## + #### Enable Remote Plotting + ######################## + + + # self.__rgv__ = RemoteGraphicsView(debug=True) + # self.__rgv__.show() + # + # self.set_widget_for_internal_usage(self.__rgv__) + # self.__plotWidget__ = self.__rgv__.pg.PlotItem() + # self.__rgv__.setCentralItem(self.__plotWidget__) + + ### - self.__plotWidget__ = self.__rgv__.pg.PlotItem() # self.__plot_item__ = self.__plotWidget__.pg.PlotItem() self.__plotWidget__.setWindowTitle('PlotPerformanceTitle') self.__plotWidget__.showGrid(x=self.__show_grid_x__, y=self.__show_grid_y__) -# self.set_widget_for_internal_usage(self.__plotWidget__) + self.set_widget_for_internal_usage(self.__plotWidget__) # if self.config['xRange-auto']['value']=='1': @@ -251,8 +264,8 @@ def execute(self, Data=None, block_name=None): :param block_name: :return: """ - print('execute') - return +# print('execute') + t = Data['t'] self.__input_size__ = len(t) @@ -311,7 +324,9 @@ def update_plot(self): curve = self.signals[signal_name]['curve'] - curve.setData( np.array(tdata), data) +# self.__plotWidget__.plot(x=tdata, y=data, clear=True) + + curve.setData( np.array(tdata)[:-20], data[:-20]) self.__tdata_old__ = tdata @@ -504,7 +519,7 @@ def plugin_meta_updated(self): signal = self.signals[signal_name]['signal'] self.remove_databuffer(signal) - self.update_pens() + #self.update_pens() #self.update_legend() def add_databuffer(self, signal, signal_id): @@ -526,8 +541,11 @@ def add_databuffer(self, signal, signal_id): buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION - curve = self.__plotWidget__.plot([0, 1], [0, 1]) + curve = self.__plotWidget__.plot([0, 1], [0, 1], clear=True) + +# plot_item = self.__rgv__.pg.PlotItem() +# curve = plot_item.plot([0, 1], [0, 1]) self.signals[signal_name]['buffer'] = buffer self.signals[signal_name]['curve'] = curve From 3c40f300960b2e3f1ec5659e7866ae311d5596ff Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Wed, 28 Jan 2015 16:07:34 +0100 Subject: [PATCH 108/260] modifed code needed to analyse plot performance --- papi/plugin/visual/Plot/Plot.py | 96 +++++++++++++++++++-------------- papi/plugin/visual/Plot/perf.py | 3 +- 2 files changed, 57 insertions(+), 42 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 4f21c631..d7326c4c 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -392,58 +392,76 @@ def update_plot(self): """ shift_data = 0 - for last_tvalue in self.__tdata_old__: - if last_tvalue in self.__tbuffer__: - shift_data = list(self.__tbuffer__).index(last_tvalue) - break - + # for last_tvalue in self.__tdata_old__: + # if last_tvalue in self.__tbuffer__: + # shift_data = list(self.__tbuffer__).index(last_tvalue) + # break + # tdata = list(self.__tbuffer__)[shift_data::self.__downsampling_rate__] - - if self.__rolling_plot__: - self.__append_at__ += self.__new_added_data__ / self.__downsampling_rate__ - self.__append_at__ %= len(tdata) - - # -------------------------- - # iterate over all buffers - # -------------------------- - + # + # if self.__rolling_plot__: + # self.__append_at__ += self.__new_added_data__ / self.__downsampling_rate__ + # self.__append_at__ %= len(tdata) + # + # # -------------------------- + # # iterate over all buffers + # # -------------------------- + # + # + # for signal_name in self.signals: + # data = list(self.signals[signal_name]['buffer'])[shift_data::self.__downsampling_rate__] + # + # if self.__rolling_plot__: + # data = np.roll(data, int(self.__append_at__)) + # self.__vertical_line__.setValue(tdata[int(self.__append_at__) - 1]) + # else: + # self.__vertical_line__.setValue(tdata[0]) + # + # + # curve = self.signals[signal_name]['curve'] + # # if len(tdata) > 0 : + # # print(np.array(tdata)) + # # MultiLine(np.array(tdata), data) + # + # new_tdata = np.linspace(0, len(tdata)-1, len(tdata)) + # #print(new_tdata) + # + # # if signal_name != "signal_1": + # # print(signal_name) + # # print(data) + # #curve.setData(new_tdata, data, _callSync='off') + # + # #curve.setData(tdata, data, _callSync='off') + # + # # if self.__papi_debug__: + # # now = pg.ptime.time() - for signal_name in self.signals: - data = list(self.signals[signal_name]['buffer'])[shift_data::self.__downsampling_rate__] - if self.__rolling_plot__: - data = np.roll(data, int(self.__append_at__)) - self.__vertical_line__.setValue(tdata[int(self.__append_at__) - 1]) - else: - self.__vertical_line__.setValue(tdata[0]) + count = 0 + for signal_name in self.signals: - curve = self.signals[signal_name]['curve'] - # if len(tdata) > 0 : - # print(np.array(tdata)) - # MultiLine(np.array(tdata), data) + data = list(self.signals[signal_name]['buffer']) - new_tdata = np.linspace(0, len(tdata)-1, len(tdata)) - #print(new_tdata) + y = np.random.normal(size=(len(self.signals),len(tdata)), scale=0.001) + np.arange(len(self.signals))[:,np.newaxis] + x = np.empty((len(self.signals),len(tdata))) + x[:] = np.arange(len(tdata))[np.newaxis,:] - # if signal_name != "signal_1": - # print(signal_name) - # print(data) - #curve.setData(new_tdata, data, _callSync='off') - #curve.setData(tdata, data, _callSync='off') + y[count,:] = data + x[count,:] = tdata - # if self.__papi_debug__: - # print("Plot time: %0.5f sec" % (pg.ptime.time()-now) ) + count += 1 - y = np.random.normal(size=(120,20000), scale=0.2) + np.arange(120)[:,np.newaxis] - x = np.empty((120,20000)) - x[:] = np.arange(20000)[np.newaxis,:] + if count > 0: + break lines = MultiLine(x, y) self.__plotWidget__.addItem(lines) + print("Plot time: %0.5f sec" % (pg.ptime.time()-now) ) + self.__tdata_old__ = tdata def update_plot_single_timestamp(self, data): @@ -778,8 +796,6 @@ def setup_context_menu(self): if not self.__papi_debug__: self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] - if self.__papi_debug__: - self.__plotWidget__.getPlotItem().ctrlMenu = [self.custMenu] #self.__plotWidget__.getPlotItem().getViewBox() def range_changed(self): @@ -889,8 +905,6 @@ def debug_papi(self): self.add_databuffer(signal_4, 4) self.add_databuffer(signal_5, 5) - print(self.signals) - pass def get_plugin_configuration(self): diff --git a/papi/plugin/visual/Plot/perf.py b/papi/plugin/visual/Plot/perf.py index a01108ef..853833c4 100644 --- a/papi/plugin/visual/Plot/perf.py +++ b/papi/plugin/visual/Plot/perf.py @@ -58,7 +58,8 @@ def do_fctn(plugin): plugin.execute(data) plugin.__t__ = t - QtCore.QTimer.singleShot(20, lambda : do_fctn(plugin)) + if plugin.__t__ < 10: + QtCore.QTimer.singleShot(20, lambda : do_fctn(plugin)) imp_path = "Plot.py" class_name = "Plot" From e78b53dac7fff21d2298a0749c22dd0751972be0 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Thu, 29 Jan 2015 11:11:52 +0100 Subject: [PATCH 109/260] matrix plotting --- papi/plugin/visual/Plot/Plot.py | 47 ++++++++++++++++++++------------- papi/plugin/visual/Plot/perf.py | 2 +- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index d7326c4c..74c2335d 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -220,6 +220,7 @@ def initiate_layer_0(self, config=None): self.__last_time__ = current_milli_time() self.__update_intervall__ = 25 # in milliseconds + self.__last_plot_time__ = 0 self.setup_context_menu() @@ -278,7 +279,7 @@ def execute(self, Data=None, block_name=None): self.__signals_have_same_length &= (len(t) == len(y)) if self.__input_size__ > 1 or self.__signals_have_same_length: - if current_milli_time() - self.__last_time__ > self.__update_intervall__: + if current_milli_time() - self.__last_time__ > self.__update_intervall__ - self.__last_plot_time__: self.__last_time__ = current_milli_time() self.update_plot() self.__last_time__ = current_milli_time() @@ -439,28 +440,38 @@ def update_plot(self): count = 0 - for signal_name in self.signals: - - data = list(self.signals[signal_name]['buffer']) - - y = np.random.normal(size=(len(self.signals),len(tdata)), scale=0.001) + np.arange(len(self.signals))[:,np.newaxis] - x = np.empty((len(self.signals),len(tdata))) - x[:] = np.arange(len(tdata))[np.newaxis,:] - + # for signal_name in self.signals: + # + # data = list(self.signals[signal_name]['buffer']) + # + # y = np.random.normal(size=(len(self.signals),len(tdata)), scale=0.001) + np.arange(len(self.signals))[:,np.newaxis] + # x = np.empty((len(self.signals),len(tdata))) + # x[:] = np.arange(len(tdata))[np.newaxis,:] + # + # + # y[count,:] = data + # x[count,:] = tdata + # + # count += 1 + # + # if count > 0: + # break - y[count,:] = data - x[count,:] = tdata + self.__plotWidget__.clear() + amount_signal = 3 + len_data = len(tdata) - count += 1 + y = np.random.normal(size=(amount_signal, len_data), scale=0.1) + np.arange(amount_signal)[:,np.newaxis] + x = np.empty((amount_signal, len_data)) + #x[:] = np.arange(len_data)[np.newaxis,:] - if count > 0: - break + x[:] = np.array(tdata)[np.newaxis,:] lines = MultiLine(x, y) self.__plotWidget__.addItem(lines) - - print("Plot time: %0.5f sec" % (pg.ptime.time()-now) ) + self.__last_plot_time__ = pg.ptime.time()-now + print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) self.__tdata_old__ = tdata @@ -576,10 +587,10 @@ def add_databuffer(self, signal, signal_id): buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION - curve = self.__plotWidget__.plot([0, 1], [0, 1], clear=False) + #curve = self.__plotWidget__.plot([0, 1], [0, 1], clear=False) self.signals[signal_name]['buffer'] = buffer - self.signals[signal_name]['curve'] = curve + #self.signals[signal_name]['curve'] = curve self.signals[signal_name]['id'] = signal_id self.signals[signal_name]['signal'] = signal diff --git a/papi/plugin/visual/Plot/perf.py b/papi/plugin/visual/Plot/perf.py index 853833c4..a8479b44 100644 --- a/papi/plugin/visual/Plot/perf.py +++ b/papi/plugin/visual/Plot/perf.py @@ -58,7 +58,7 @@ def do_fctn(plugin): plugin.execute(data) plugin.__t__ = t - if plugin.__t__ < 10: + if plugin.__t__ < 25: QtCore.QTimer.singleShot(20, lambda : do_fctn(plugin)) imp_path = "Plot.py" From 0b1bc365e87dfa5be85c144b443aed460131c356 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Thu, 29 Jan 2015 11:12:04 +0100 Subject: [PATCH 110/260] fixed plot --- papi/plugin/visual/Plot/Plot.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 9287ff8c..17a803a4 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -399,14 +399,8 @@ def update_plot(self): self.__vertical_line__.setValue(tdata[0]) curve = self.signals[signal_name]['curve'] - # if len(tdata) > 0 : - # print(np.array(tdata)) - # MultiLine(np.array(tdata), data) - new_tdata = np.linspace(0, len(tdata)-1, len(tdata)) - - curve.setData(new_tdata, data, _callSync='off') - print("Plot time: %0.5f sec" % (pg.ptime.time()-now) ) + curve.setData(tdata, data, _callSync='off') self.__tdata_old__ = tdata @@ -522,7 +516,7 @@ def add_databuffer(self, signal, signal_id): buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION - curve = self.__plotWidget__.plot([0, 1], [0, 1], clear=True) + curve = self.__plotWidget__.plot([0, 1], [0, 1], clear=False) self.signals[signal_name]['buffer'] = buffer self.signals[signal_name]['curve'] = curve From 1c6d6a91b6959366e3e65e3e0d03b7d17c73fe61 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Thu, 29 Jan 2015 12:10:33 +0100 Subject: [PATCH 111/260] minor modifications --- papi/plugin/visual/Plot/Plot.py | 113 ++++++++++---------------------- 1 file changed, 36 insertions(+), 77 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 74c2335d..a38a2008 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -45,13 +45,14 @@ class MultiLine(pg.QtGui.QGraphicsPathItem): - def __init__(self, x, y): + def __init__(self, x, y, pen): """x and y are 2D arrays of shape (Nplots, Nsamples)""" connect = np.ones(x.shape, dtype=bool) connect[:,-1] = 0 # don't draw the segment between each trace self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten()) pg.QtGui.QGraphicsPathItem.__init__(self, self.path) - self.setPen(pg.mkPen('w')) + self.setPen(pg.mkPen('r')) + #self.setPen(pen) def shape(self): # override because QGraphicsPathItem.shape is too expensive. return pg.QtGui.QGraphicsItem.shape(self) def boundingRect(self): @@ -103,11 +104,14 @@ def __init__(self, debug=False): self.__parameters__ = {} self.__update_intervall__ = None self.__last_time__ = None + self.__last_plot_time__ = None self.__plotWidget__ = None self.__legend__ = None self.__text_item__ = None self.__vertical_line__ = None self.__offset_line__ = None + self.__y_axis__ = None + self.__x_axis__ = None self.styles = { 0: QtCore.Qt.SolidLine, @@ -268,8 +272,6 @@ def execute(self, Data=None, block_name=None): self.__new_added_data__ += len(t) self.__signals_have_same_length = True - - for key in Data: if key != 't': y = Data[key] @@ -325,13 +327,13 @@ def set_parameter(self, name, value): self.config['color']['value'] = value int_re = re.compile(r'(\d+)') self.__colors_selected__ = int_re.findall(self.config['color']['value']) - self.update_pens() +# self.update_pens() if name == 'style': self.config['style']['value'] = value int_re = re.compile(r'(\d+)') self.__styles_selected__ = int_re.findall(self.config['style']['value']) - self.update_pens() +# self.update_pens() if name == 'buffersize': self.config['buffersize']['value'] = value @@ -383,7 +385,7 @@ def update_pens(self): new_pen = self.get_pen(signal_id) - self.signals[signal_name]['curve'].setPen(new_pen) + self.signals[signal_name]['pen'] = new_pen def update_plot(self): """ @@ -393,85 +395,38 @@ def update_plot(self): """ shift_data = 0 - # for last_tvalue in self.__tdata_old__: - # if last_tvalue in self.__tbuffer__: - # shift_data = list(self.__tbuffer__).index(last_tvalue) - # break - # - tdata = list(self.__tbuffer__)[shift_data::self.__downsampling_rate__] - # - # if self.__rolling_plot__: - # self.__append_at__ += self.__new_added_data__ / self.__downsampling_rate__ - # self.__append_at__ %= len(tdata) - # - # # -------------------------- - # # iterate over all buffers - # # -------------------------- - # - # - # for signal_name in self.signals: - # data = list(self.signals[signal_name]['buffer'])[shift_data::self.__downsampling_rate__] - # - # if self.__rolling_plot__: - # data = np.roll(data, int(self.__append_at__)) - # self.__vertical_line__.setValue(tdata[int(self.__append_at__) - 1]) - # else: - # self.__vertical_line__.setValue(tdata[0]) - # - # - # curve = self.signals[signal_name]['curve'] - # # if len(tdata) > 0 : - # # print(np.array(tdata)) - # # MultiLine(np.array(tdata), data) - # - # new_tdata = np.linspace(0, len(tdata)-1, len(tdata)) - # #print(new_tdata) - # - # # if signal_name != "signal_1": - # # print(signal_name) - # # print(data) - # #curve.setData(new_tdata, data, _callSync='off') - # - # #curve.setData(tdata, data, _callSync='off') - # - # # if self.__papi_debug__: - # # + tdata = list(self.__tbuffer__) now = pg.ptime.time() - count = 0 + self.__plotWidget__.clear() - # for signal_name in self.signals: - # - # data = list(self.signals[signal_name]['buffer']) - # - # y = np.random.normal(size=(len(self.signals),len(tdata)), scale=0.001) + np.arange(len(self.signals))[:,np.newaxis] - # x = np.empty((len(self.signals),len(tdata))) - # x[:] = np.arange(len(tdata))[np.newaxis,:] - # - # - # y[count,:] = data - # x[count,:] = tdata - # - # count += 1 - # - # if count > 0: - # break + # Set Y-Axis + + amount_signal = len(self.signals) - self.__plotWidget__.clear() - amount_signal = 3 len_data = len(tdata) + self.__y_axis__ = np.ones((amount_signal, len_data)) + self.__x_axis__ = np.empty((amount_signal, len_data)) - y = np.random.normal(size=(amount_signal, len_data), scale=0.1) + np.arange(amount_signal)[:,np.newaxis] - x = np.empty((amount_signal, len_data)) - #x[:] = np.arange(len_data)[np.newaxis,:] + count = 0 + for signal_name in self.signals: + data = list(self.signals[signal_name]['buffer']) + + self.__y_axis__[count,:] = np.array(data) + + count += 1 - x[:] = np.array(tdata)[np.newaxis,:] + # Set X-Axis - lines = MultiLine(x, y) + self.__x_axis__[:] = np.array(tdata)[np.newaxis,:] + + lines = MultiLine(self.__x_axis__, self.__y_axis__, None) self.__plotWidget__.addItem(lines) self.__last_plot_time__ = pg.ptime.time()-now - print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) + + if self.__papi_debug__: + print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) self.__tdata_old__ = tdata @@ -546,6 +501,7 @@ def plugin_meta_updated(self): subscription = subscriptions[dpluginsub_id][dblock_name] for signal_name in subscription.get_signals(): + signal = subscription.get_dblock().get_signal_by_uname(signal_name) index += 1 current_signals[signal_name] = {} @@ -559,15 +515,18 @@ def plugin_meta_updated(self): if signal_name not in self.signals: signal = current_signals[signal_name]['signal'] self.add_databuffer(signal, current_signals[signal_name]['index']) + print('Add ' + signal_name) # Delete old buffers for signal_name in self.signals.copy(): if signal_name not in current_signals: signal = self.signals[signal_name]['signal'] self.remove_databuffer(signal) + print('Remove ' + signal_name) + - self.update_pens() - self.update_legend() + #self.update_pens() + #self.update_legend() def add_databuffer(self, signal, signal_id): """ From 9abfbe72bc182414e328ef746851d2c2d49b34f2 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Thu, 29 Jan 2015 12:31:01 +0100 Subject: [PATCH 112/260] added auto range button to context menu (axis) --- papi/plugin/visual/Plot/Plot.py | 65 +++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 12 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 17a803a4..ca822a92 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -41,6 +41,8 @@ current_milli_time = lambda: int(round(time.time() * 1000)) from papi.pyqtgraph.Qt import QtCore, QtGui +from PySide.QtGui import QRegExpValidator +from PySide.QtCore import * class Plot(vip_base): @@ -576,11 +578,12 @@ def use_range_for_x(self, value): self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]),float(range[1])) def use_range_for_y(self, value): - reg = re.compile(r'(\d+\.\d+)') + reg = re.compile(r'([-]{0,1}\d+\.\d+)') range = reg.findall(value) if len(range) == 2: self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]), float(range[1])) + def setup_context_menu(self): self.custMenu = QtGui.QMenu("Options") self.axesMenu = QtGui.QMenu('Axes') @@ -650,6 +653,11 @@ def setup_context_menu(self): self.yRange_AutoCheckbox.setText('Y-Autorange') self.yRange_Layout.addWidget(self.yRange_AutoCheckbox) + self.yAutoRangeButton = QtGui.QPushButton() + self.yAutoRangeButton.clicked.connect(self.contextMenu_yAutoRangeButton_clicked) + self.yAutoRangeButton.setText('Set range') + self.yRange_Layout.addWidget(self.yAutoRangeButton) + ##### Y Line Edits # Layout self.yRange_EditWidget = QtGui.QWidget() @@ -667,17 +675,21 @@ def setup_context_menu(self): y_min = '0.0' y_max = '1.0' + rx = QRegExp(r'([-]{0,1}\d+\.\d+)') + validator = QRegExpValidator(rx, self.__plotWidget__) + # Min self.yRange_minEdit = QtGui.QLineEdit() self.yRange_minEdit.setFixedWidth(80) self.yRange_minEdit.setText(y_min) self.yRange_minEdit.editingFinished.connect(self.contextMenu_yRange_toogle) - + self.yRange_minEdit.setValidator(validator) # Max self.yRange_maxEdit = QtGui.QLineEdit() self.yRange_maxEdit.setFixedWidth(80) self.yRange_maxEdit.setText(y_max) self.yRange_maxEdit.editingFinished.connect(self.contextMenu_yRange_toogle) + self.yRange_maxEdit.setValidator(validator) # addTo Layout self.yRange_EditLayout.addWidget(self.yRange_minEdit) self.yRange_EditLayout.addWidget(QtGui.QLabel('<')) @@ -735,8 +747,34 @@ def setup_context_menu(self): self.__plotWidget__.getPlotItem().getViewBox().menu.clear() self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] + def contextMenu_yAutoRangeButton_clicked(self): + mi = None; + ma = None; + for sig in self.signals: + buf = self.signals[sig]['buffer'] + ma_buf = max(buf) + mi_buf = min(buf) + if ma is not None: + if ma_buf > ma: + ma = ma_buf + else: + ma = ma_buf + + if mi is not None: + if mi_buf < mi: + mi = mi_buf + else: + mi = mi_buf + - #self.__plotWidget__.getPlotItem().getViewBox() + ma = str(ma); + mi = str(mi) + print(ma) + print(mi) + self.yRange_maxEdit.setText(ma) + self.yRange_minEdit.setText(mi) + self.yRange_AutoCheckbox.setChecked(False) + self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') def range_changed(self): print('r') @@ -766,12 +804,13 @@ def contextMenu_xRange_toogle(self): self.xRange_minEdit.setDisabled(True) self.xRange_maxEdit.setDisabled(True) else: - self.xRange_minEdit.setDisabled(False) - self.xRange_maxEdit.setDisabled(False) mi = self.xRange_minEdit.text() ma = self.xRange_maxEdit.text() - self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '0') - self.control_api.do_set_parameter(self.__id__, 'xRange', '[' + mi + ' ' + ma + ']') + if float(mi) < float(ma): + self.xRange_minEdit.setDisabled(False) + self.xRange_maxEdit.setDisabled(False) + self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '0') + self.control_api.do_set_parameter(self.__id__, 'xRange', '[' + mi + ' ' + ma + ']') def contextMenu_yRange_toogle(self): if self.yRange_AutoCheckbox.isChecked(): @@ -780,13 +819,15 @@ def contextMenu_yRange_toogle(self): self.yRange_minEdit.setDisabled(True) self.yRange_maxEdit.setDisabled(True) else: - self.yRange_minEdit.setDisabled(False) - self.yRange_maxEdit.setDisabled(False) - # do man range mi = self.yRange_minEdit.text() ma = self.yRange_maxEdit.text() - self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '0') - self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') + if float(mi) < float(ma): + self.yRange_minEdit.setDisabled(False) + self.yRange_maxEdit.setDisabled(False) + # do man range + + self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '0') + self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') def update_signals(self): subscriptions = self.dplugin_info.get_subscribtions() From ef8c758f128d2b10f821f1fca512483cdaf5967d Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Thu, 29 Jan 2015 12:35:09 +0100 Subject: [PATCH 113/260] disabled yAutoCheckbox --- papi/plugin/visual/Plot/Plot.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index ca822a92..545f4619 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -334,6 +334,7 @@ def set_parameter(self, name, value): if name == 'yRange-auto': self.config['yRange-auto']['value'] = value + return self.yRange_AutoCheckbox.setChecked(value=='1') if int(value) == 1: self.yRange_minEdit.setDisabled(True) @@ -648,7 +649,8 @@ def setup_context_menu(self): self.yRange_Layout.setContentsMargins(2, 2, 2, 2) self.yRange_Layout.setSpacing(1) - self.yRange_AutoCheckbox = QtGui.QCheckBox(checked= self.config['xRange-auto']['value'] == '1') + self.yRange_AutoCheckbox = QtGui.QCheckBox(checked= False)#self.config['xRange-auto']['value'] == '1') + self.yRange_AutoCheckbox.setDisabled(True) self.yRange_AutoCheckbox.stateChanged.connect(self.contextMenu_yRange_toogle) self.yRange_AutoCheckbox.setText('Y-Autorange') self.yRange_Layout.addWidget(self.yRange_AutoCheckbox) From 623b8d01f9e1e1bead7870c0b3624efaf4687c1d Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Thu, 29 Jan 2015 15:10:35 +0100 Subject: [PATCH 114/260] disabled yAutoCheckbox --- papi/plugin/visual/Plot/Plot.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 545f4619..9a81626a 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -162,7 +162,8 @@ def initiate_layer_0(self, config=None): self.set_widget_for_internal_usage(self.__plotWidget__) - + self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.YAxis, enable=False) + self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.XAxis, enable=False) # if self.config['xRange-auto']['value']=='1': @@ -347,13 +348,13 @@ def set_parameter(self, name, value): if name == 'xRange': self.config['xRange']['value'] = value - if self.config['xRange-auto']['value'] == '0': - self.use_range_for_x(value) + #if self.config['xRange-auto']['value'] == '0': + self.use_range_for_x(value) if name == 'yRange': self.config['yRange']['value'] = value - if self.config['yRange-auto']['value'] == '0': - self.use_range_for_y(value) + #if self.config['yRange-auto']['value'] == '0': + self.use_range_for_y(value) def update_pens(self): """ @@ -768,19 +769,13 @@ def contextMenu_yAutoRangeButton_clicked(self): else: mi = mi_buf - ma = str(ma); mi = str(mi) - print(ma) - print(mi) self.yRange_maxEdit.setText(ma) self.yRange_minEdit.setText(mi) self.yRange_AutoCheckbox.setChecked(False) self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') - def range_changed(self): - print('r') - def contextMenu_rolling_toogled(self): if self.rolling_Checkbox.isChecked(): self.control_api.do_set_parameter(self.__id__, 'rolling', '1') From a5bf423953be84f6c40cdcb68f064b843a77d6d6 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 2 Feb 2015 14:01:54 +0100 Subject: [PATCH 115/260] cleared context menu, do not needed x axis setup --- papi/plugin/visual/Plot/Plot.py | 208 +++++++++----------------------- 1 file changed, 57 insertions(+), 151 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 9a81626a..7ff38e71 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -165,17 +165,6 @@ def initiate_layer_0(self, config=None): self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.YAxis, enable=False) self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.XAxis, enable=False) - # - if self.config['xRange-auto']['value']=='1': - pass - else: - self.use_range_for_x(self.config['xRange']['value']) - - if self.config['yRange-auto']['value']== '1': - pass - else: - self.use_range_for_y(self.config['yRange']['value']) - # --------------------------- # Create Parameters # --------------------------- @@ -190,9 +179,7 @@ def initiate_layer_0(self, config=None): self.__parameters__['downsampling_rate'] = DParameter('downsampling_rate', self.__downsampling_rate__, Regex='^([1-9][0-9]?|100)$') self.__parameters__['buffersize'] = DParameter('buffersize', self.__buffer_size__, Regex='^([1-9][0-9]{0,3}|10000)$') - self.__parameters__['xRange-auto'] = DParameter('xRange-auto', '1', Regex='^(1|0){1}$') - self.__parameters__['xRange'] = DParameter('xRange', '[0,1]', Regex='(\d+\.\d+)') - self.__parameters__['yRange-auto'] = DParameter('yRange-auto', '1', Regex='^(1|0){1}$') + #self.__parameters__['xRange'] = DParameter('xRange', '[0,1]', Regex='(\d+\.\d+)') self.__parameters__['yRange'] = DParameter('yRange', '[0,1]', Regex='(\d+\.\d+)') self.send_new_parameter_list(list(self.__parameters__.values())) @@ -210,16 +197,8 @@ def initiate_layer_0(self, config=None): self.setup_context_menu() - # - if self.config['xRange-auto']['value']=='1': - pass - else: - self.use_range_for_x(self.config['xRange']['value']) - - if self.config['yRange-auto']['value']== '1': - pass - else: - self.use_range_for_y(self.config['yRange']['value']) + #self.use_range_for_x(self.config['xRange']['value']) + self.use_range_for_y(self.config['yRange']['value']) return True @@ -321,39 +300,12 @@ def set_parameter(self, name, value): self.config['buffersize']['value'] = value self.set_buffer_size(value) - if name == 'xRange-auto': - self.config['xRange-auto']['value'] = value - self.xRange_AutoCheckbox.setChecked(value=='1') - if int(value) == 1: - self.xRange_minEdit.setDisabled(True) - self.xRange_maxEdit.setDisabled(True) - self.__plotWidget__.getPlotItem().getViewBox().menu.xAutoClicked() - else: - self.use_range_for_x(self.config['xRange']['value']) - self.xRange_minEdit.setDisabled(False) - self.xRange_maxEdit.setDisabled(False) - - if name == 'yRange-auto': - self.config['yRange-auto']['value'] = value - return - self.yRange_AutoCheckbox.setChecked(value=='1') - if int(value) == 1: - self.yRange_minEdit.setDisabled(True) - self.yRange_maxEdit.setDisabled(True) - self.__plotWidget__.getPlotItem().getViewBox().menu.yAutoClicked() - else: - self.yRange_minEdit.setDisabled(False) - self.yRange_maxEdit.setDisabled(False) - self.use_range_for_y(self.config['yRange']['value']) - - if name == 'xRange': - self.config['xRange']['value'] = value - #if self.config['xRange-auto']['value'] == '0': - self.use_range_for_x(value) + # if name == 'xRange': + # self.config['xRange']['value'] = value + # self.use_range_for_x(value) if name == 'yRange': self.config['yRange']['value'] = value - #if self.config['yRange-auto']['value'] == '0': self.use_range_for_y(value) def update_pens(self): @@ -405,6 +357,7 @@ def update_plot(self): curve = self.signals[signal_name]['curve'] curve.setData(tdata, data, _callSync='off') + self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0],tdata[-1]) self.__tdata_old__ = tdata @@ -575,72 +528,70 @@ def use_range_for_x(self, value): reg = re.compile(r'(\d+\.\d+)') range = reg.findall(value) if len(range) == 2: - self.xRange_minEdit.setText(range[0]) - self.xRange_maxEdit.setText(range[1]) + #self.xRange_minEdit.setText(range[0]) + #self.xRange_maxEdit.setText(range[1]) self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]),float(range[1])) def use_range_for_y(self, value): reg = re.compile(r'([-]{0,1}\d+\.\d+)') range = reg.findall(value) if len(range) == 2: + self.yRange_minEdit.setText(range[0]) + self.yRange_maxEdit.setText(range[1]) self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]), float(range[1])) def setup_context_menu(self): self.custMenu = QtGui.QMenu("Options") - self.axesMenu = QtGui.QMenu('Axes') + self.axesMenu = QtGui.QMenu('Y-Axis') self.gridMenu = QtGui.QMenu('Grid') # --------------------------------------------------------- # #### X-Range Actions - self.xRange_Widget = QtGui.QWidget() - self.xRange_Layout = QtGui.QVBoxLayout(self.xRange_Widget) - self.xRange_Layout.setContentsMargins(2, 2, 2, 2) - self.xRange_Layout.setSpacing(1) + #self.xRange_Widget = QtGui.QWidget() + #self.xRange_Layout = QtGui.QVBoxLayout(self.xRange_Widget) + #self.xRange_Layout.setContentsMargins(2, 2, 2, 2) + #self.xRange_Layout.setSpacing(1) - self.xRange_AutoCheckbox = QtGui.QCheckBox(checked= self.config['xRange-auto']['value'] == '1') - self.xRange_AutoCheckbox.stateChanged.connect(self.contextMenu_xRange_toogle) - self.xRange_AutoCheckbox.setText('X-Autorange') - self.xRange_Layout.addWidget(self.xRange_AutoCheckbox) ##### X Line Edits # Layout - self.xRange_EditWidget = QtGui.QWidget() - self.xRange_EditLayout = QtGui.QHBoxLayout(self.xRange_EditWidget) - self.xRange_EditLayout.setContentsMargins(2, 2, 2, 2) - self.xRange_EditLayout.setSpacing(1) + #self.xRange_EditWidget = QtGui.QWidget() + #self.xRange_EditLayout = QtGui.QHBoxLayout(self.xRange_EditWidget) + #self.xRange_EditLayout.setContentsMargins(2, 2, 2, 2) + #self.xRange_EditLayout.setSpacing(1) # get old values; - reg = re.compile(r'(\d+\.\d+)') - range = reg.findall(self.config['xRange']['value']) - if len(range) == 2: - x_min = range[0] - x_max = range[1] - else: - x_min = '0.0' - x_max = '1.0' + #reg = re.compile(r'(\d+\.\d+)') + #range = reg.findall(self.config['xRange']['value']) + #if len(range) == 2: + # x_min = range[0] + # x_max = range[1] + #else: + # x_min = '0.0' + # x_max = '1.0' # Min - self.xRange_minEdit = QtGui.QLineEdit() - self.xRange_minEdit.setFixedWidth(80) - self.xRange_minEdit.setText(x_min) - self.xRange_minEdit.editingFinished.connect(self.contextMenu_xRange_toogle) - # Max - self.xRange_maxEdit = QtGui.QLineEdit() - self.xRange_maxEdit.setFixedWidth(80) - self.xRange_maxEdit.setText(x_max) - self.xRange_maxEdit.editingFinished.connect(self.contextMenu_xRange_toogle) - # addTo Layout - self.xRange_EditLayout.addWidget(self.xRange_minEdit) - self.xRange_EditLayout.addWidget(QtGui.QLabel('<')) - self.xRange_EditLayout.addWidget(self.xRange_maxEdit) - self.xRange_Layout.addWidget(self.xRange_EditWidget) - - # build Action - self.xRange_Action = QtGui.QWidgetAction(self.__plotWidget__) - self.xRange_Action.setDefaultWidget(self.xRange_Widget) + # self.xRange_minEdit = QtGui.QLineEdit() + # self.xRange_minEdit.setFixedWidth(80) + # self.xRange_minEdit.setText(x_min) + # self.xRange_minEdit.editingFinished.connect(self.contextMenu_xRange_toogle) + # # Max + # self.xRange_maxEdit = QtGui.QLineEdit() + # self.xRange_maxEdit.setFixedWidth(80) + # self.xRange_maxEdit.setText(x_max) + # self.xRange_maxEdit.editingFinished.connect(self.contextMenu_xRange_toogle) + # # addTo Layout + # self.xRange_EditLayout.addWidget(self.xRange_minEdit) + # self.xRange_EditLayout.addWidget(QtGui.QLabel('<')) + # self.xRange_EditLayout.addWidget(self.xRange_maxEdit) + # self.xRange_Layout.addWidget(self.xRange_EditWidget) + # + # # build Action + # self.xRange_Action = QtGui.QWidgetAction(self.__plotWidget__) + # self.xRange_Action.setDefaultWidget(self.xRange_Widget) # --------------------------------------------------------------- @@ -650,15 +601,9 @@ def setup_context_menu(self): self.yRange_Layout.setContentsMargins(2, 2, 2, 2) self.yRange_Layout.setSpacing(1) - self.yRange_AutoCheckbox = QtGui.QCheckBox(checked= False)#self.config['xRange-auto']['value'] == '1') - self.yRange_AutoCheckbox.setDisabled(True) - self.yRange_AutoCheckbox.stateChanged.connect(self.contextMenu_yRange_toogle) - self.yRange_AutoCheckbox.setText('Y-Autorange') - self.yRange_Layout.addWidget(self.yRange_AutoCheckbox) - self.yAutoRangeButton = QtGui.QPushButton() self.yAutoRangeButton.clicked.connect(self.contextMenu_yAutoRangeButton_clicked) - self.yAutoRangeButton.setText('Set range') + self.yAutoRangeButton.setText('Set y range') self.yRange_Layout.addWidget(self.yAutoRangeButton) ##### Y Line Edits @@ -714,7 +659,7 @@ def setup_context_menu(self): ##### Build axes menu - self.axesMenu.addAction(self.xRange_Action) + #self.axesMenu.addAction(self.xRange_Action) self.axesMenu.addSeparator().setText("Y-Range") self.axesMenu.addAction(self.yRange_Action) @@ -773,7 +718,6 @@ def contextMenu_yAutoRangeButton_clicked(self): mi = str(mi) self.yRange_maxEdit.setText(ma) self.yRange_minEdit.setText(mi) - self.yRange_AutoCheckbox.setChecked(False) self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') def contextMenu_rolling_toogled(self): @@ -794,37 +738,11 @@ def contextMenu_yGrid_toogle(self): else: self.control_api.do_set_parameter(self.__id__, 'y-grid', '0') - def contextMenu_xRange_toogle(self): - if self.xRange_AutoCheckbox.isChecked(): - # do autorange - self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '1') - self.xRange_minEdit.setDisabled(True) - self.xRange_maxEdit.setDisabled(True) - else: - mi = self.xRange_minEdit.text() - ma = self.xRange_maxEdit.text() - if float(mi) < float(ma): - self.xRange_minEdit.setDisabled(False) - self.xRange_maxEdit.setDisabled(False) - self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '0') - self.control_api.do_set_parameter(self.__id__, 'xRange', '[' + mi + ' ' + ma + ']') - def contextMenu_yRange_toogle(self): - if self.yRange_AutoCheckbox.isChecked(): - # do autorange - self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '1') - self.yRange_minEdit.setDisabled(True) - self.yRange_maxEdit.setDisabled(True) - else: - mi = self.yRange_minEdit.text() - ma = self.yRange_maxEdit.text() - if float(mi) < float(ma): - self.yRange_minEdit.setDisabled(False) - self.yRange_maxEdit.setDisabled(False) - # do man range - - self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '0') - self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') + mi = self.yRange_minEdit.text() + ma = self.yRange_maxEdit.text() + if float(mi) < float(ma): + self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') def update_signals(self): subscriptions = self.dplugin_info.get_subscribtions() @@ -914,23 +832,11 @@ def get_plugin_configuration(self): 'regex': '^(1|0)$', 'type': 'bool', 'display_text': 'Rolling Plot' - }, 'xRange-auto': { - 'value': '1', - 'regex': '^(1|0)$', - 'type': 'bool', - 'advanced': '1', - 'display_text': 'x: auto range' - }, 'yRange-auto': { - 'value': '1', - 'regex': '^(1|0)$', - 'type': 'bool', - 'advanced': '1', - 'display_text': 'y: auto range' - }, 'xRange': { - 'value': '[0.0 1.0]', - 'regex': '(\d+\.\d+)', - 'advanced': '1', - 'display_text': 'x: range' + # }, 'xRange': { + # 'value': '[0.0 1.0]', + # 'regex': '(\d+\.\d+)', + # 'advanced': '1', + # 'display_text': 'x: range' }, 'yRange': { 'value': '[0.0 1.0]', 'regex': '(\d+\.\d+)', From e077a4a619a846bbd477f21accf0138fad930543 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 2 Feb 2015 14:53:39 +0100 Subject: [PATCH 116/260] imporved lcd display --- papi/plugin/visual/LCDDisplay/LCDDisplay.py | 53 ++++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.py b/papi/plugin/visual/LCDDisplay/LCDDisplay.py index 1a1239ef..bab4b31a 100644 --- a/papi/plugin/visual/LCDDisplay/LCDDisplay.py +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.py @@ -50,9 +50,10 @@ def initiate_layer_0(self, config=None): # Read configuration # --------------------------- # Note: this cfg items have to exist! - self.time_treshold = int(self.config['updateFrequency']['value']) - - + self.time_treshold = int(self.config['updateFrequency']['value']) + self.value_scale = float(self.config['value_scale']['value']) + self.value_offset = float(self.config['value_offset']['value']) + self.digit_count = int(self.config['digit_count']['value']) # -------------------------------- # Create Widget @@ -74,9 +75,16 @@ def initiate_layer_0(self, config=None): # --------------------------- para_list = [] # create a parameter object - self.para_treshold = DParameter('update_interval',default=self.time_treshold, Regex='[0-9]+') + self.para_treshold = DParameter('update_interval',default=self.time_treshold, Regex='[0-9]+') + self.para_value_scale = DParameter('value_scale',default= self.value_scale, Regex='-?[1-9]+[0-9]*(\.?[0-9]+)?') + self.para_value_offset = DParameter('value_offset',default= self.value_offset, Regex='-?\d+(\.?\d+)?') + self.para_digit_count = DParameter('digit_count',default= self.digit_count, Regex='[3-9]') + para_list.append(self.para_treshold) - # self.para2 = DParameter('ParameterName',default=0) + para_list.append(self.para_value_scale) + para_list.append(self.para_value_offset) + para_list.append(self.para_digit_count) + # build parameter list to send to Core self.send_new_parameter_list(para_list) @@ -127,9 +135,11 @@ def execute(self, Data=None, block_name = None): if len(keys) > 1: y = Data[keys[1]][-1] + y = y*self.value_scale + self.value_offset self.LcdWidget.display(y) + def set_parameter(self, name, value): # attetion: value is a string and need to be processed ! # if name == 'irgendeinParameter': @@ -138,6 +148,20 @@ def set_parameter(self, name, value): self.time_treshold = int(value) self.config['updateFrequency']['value'] = value + if name == self.para_value_scale.name: + self.config[self.para_value_scale.name]['value'] = value + self.value_scale = float(value) + + if name == self.para_value_offset.name: + self.config[self.para_value_offset.name]['value'] = value + self.value_offset = float(value) + + if name == self.para_digit_count.name: + self.config[self.para_digit_count.name]['value'] = value + self.digit_count = int(value) + self.LcdWidget.setDigitCount(self.digit_count) + + def quit(self): # do something before plugin will close, e.a. close connections ... @@ -166,7 +190,24 @@ def get_plugin_configuration(self): }, 'name': { 'value': 'LCD', 'tooltip': 'Used for window title' - }} + }, 'value_scale': { + 'value': '1', + 'tooltip': 'Used to scale displayed value', + 'regex': '-?[1-9]+[0-9]*(\.?[0-9]+)?', + 'advanced': '1' + }, 'value_offset': { + 'value': '0', + 'tooltip': 'Used to offset displayed value', + 'regex': '-?\d+(\.?\d+)?', + 'advanced': '1' + }, 'digit_count': { + 'value': '3', + 'tooltip': 'Number of digits', + 'regex': '[3-9]', + 'advanced': '1' + }, + + } return config def plugin_meta_updated(self): From d54a1913e12ffc2ff75dec3b3649c3a4378d0abd Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 2 Feb 2015 15:18:45 +0100 Subject: [PATCH 117/260] ORTDController added file dialog --- .../DataSourceExample/ProtocollConfig.json | 75 +- .../ProtocollConfigForController.json | 82 ++ .../visual/OrtdController/OrtdController.py | 23 +- papi/plugin/visual/PlotPerf/PlotPerf.py | 915 ------------------ .../visual/PlotPerf/PlotPerf.yapsy-plugin | 9 - papi/plugin/visual/PlotPerf/__init__.py | 29 - papi/plugin/visual/PlotPerf/items.py | 32 - 7 files changed, 103 insertions(+), 1062 deletions(-) create mode 100644 data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json delete mode 100644 papi/plugin/visual/PlotPerf/PlotPerf.py delete mode 100644 papi/plugin/visual/PlotPerf/PlotPerf.yapsy-plugin delete mode 100644 papi/plugin/visual/PlotPerf/__init__.py delete mode 100644 papi/plugin/visual/PlotPerf/items.py diff --git a/data_sources/ORTD/DataSourceExample/ProtocollConfig.json b/data_sources/ORTD/DataSourceExample/ProtocollConfig.json index ba8f4fdf..3a9f0339 100644 --- a/data_sources/ORTD/DataSourceExample/ProtocollConfig.json +++ b/data_sources/ORTD/DataSourceExample/ProtocollConfig.json @@ -5,78 +5,5 @@ "ParametersConfig" : { "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , - "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } }, -"PaPIConfig": { - "ToCreate": { - "Plot1":{ - "identifier": { - "value": "Plot" - }, - "config": { - "x-grid": { - "value": "0" - }, - "size": { - "value": "(300,300)" - }, - "position": { - "value": "(300,0)" - }, - "name": { - "value": "TestPlot" - } - } - }, - "Butt1":{ - "identifier": { - "value": "Button" - }, - "config": { - "size": { - "value": "(150,50)" - }, - "position": { - "value": "(600,0)" - }, - "name": { - "value": "Disturbance" - } - - } - }, - "Butt2":{ - "identifier": { - "value": "Button" - }, - "config": { - "size": { - "value": "(150,50)" - }, - "position": { - "value": "(600,50)" - }, - "name": { - "value": "ToDelete" - } - - } - } - }, - - "ToSub": { - "Plot1": { - "block": "SourceGroup0", - "signals": ["V"] - } - }, - "ToControl": { - "Butt1": { - "block": "Click_Event", - "parameter" : "Oscillator input" - } - }, - "ToClose": { - "Butt2": {} - } } - + "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } } } \ No newline at end of file diff --git a/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json b/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json new file mode 100644 index 00000000..ba8f4fdf --- /dev/null +++ b/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json @@ -0,0 +1,82 @@ + { +"SourcesConfig" : { + "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257" } , + "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257" } } , +"ParametersConfig" : { + "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , + "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , + "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } }, +"PaPIConfig": { + "ToCreate": { + "Plot1":{ + "identifier": { + "value": "Plot" + }, + "config": { + "x-grid": { + "value": "0" + }, + "size": { + "value": "(300,300)" + }, + "position": { + "value": "(300,0)" + }, + "name": { + "value": "TestPlot" + } + } + }, + "Butt1":{ + "identifier": { + "value": "Button" + }, + "config": { + "size": { + "value": "(150,50)" + }, + "position": { + "value": "(600,0)" + }, + "name": { + "value": "Disturbance" + } + + } + }, + "Butt2":{ + "identifier": { + "value": "Button" + }, + "config": { + "size": { + "value": "(150,50)" + }, + "position": { + "value": "(600,50)" + }, + "name": { + "value": "ToDelete" + } + + } + } + }, + + "ToSub": { + "Plot1": { + "block": "SourceGroup0", + "signals": ["V"] + } + }, + "ToControl": { + "Butt1": { + "block": "Click_Event", + "parameter" : "Oscillator input" + } + }, + "ToClose": { + "Butt2": {} + } } + +} \ No newline at end of file diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index 70d149d5..bddc5d50 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -29,7 +29,7 @@ __author__ = 'Stefan' from papi.plugin.base_classes.vip_base import vip_base - +from papi.gui.qt_new.custom import FileLineEdit from PySide import QtGui, QtCore from PySide.QtGui import QRegExpValidator @@ -200,7 +200,7 @@ def __init__(self,api = None, uname= None, parent = None, ortd_uname = None): self.uname = uname self.api = api self.ortd_uname = ortd_uname - self.setTitle("ORTD Controller") + #self.setTitle("ORTD Controller") label = QtGui.QLabel("Please configure the ORTD plugin.") label.setWordWrap(True) @@ -228,6 +228,16 @@ def __init__(self,api = None, uname= None, parent = None, ortd_uname = None): self.port_line_edit.setValidator(validator) self.port_line_edit.setText('20000') + # ----------- # + # File dial. # + # ----------- # + self.file_edit = FileLineEdit() + self.file_edit.setReadOnly(True) + self.file_edit.setText('/home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json') + self.file_label = QtGui.QLabel('ProtocollConfig') + self.file_label.setBuddy(self.file_edit) + + layout = QtGui.QVBoxLayout() layout.addWidget(label) @@ -235,6 +245,8 @@ def __init__(self,api = None, uname= None, parent = None, ortd_uname = None): layout.addWidget(self.ip_line_edit) layout.addWidget(port_label) layout.addWidget(self.port_line_edit) + layout.addWidget(self.file_label) + layout.addWidget(self.file_edit) self.setLayout(layout) @@ -242,6 +254,7 @@ def validatePage(self): print('Start: next clicked') IP = self.ip_line_edit.text() port = self.port_line_edit.text() + json = self.file_edit.text() cfg ={ 'address': { 'value': IP, @@ -254,7 +267,11 @@ def validatePage(self): 'out_port': { 'value': '20001', 'advanced': '1' - } + },'Cfg_Path': { + 'value': json, + 'type': 'file', + 'advanced': '0' + }, } self.api.do_create_plugin('ORTD_UDP', self.ortd_uname, cfg, True) diff --git a/papi/plugin/visual/PlotPerf/PlotPerf.py b/papi/plugin/visual/PlotPerf/PlotPerf.py deleted file mode 100644 index e980adf7..00000000 --- a/papi/plugin/visual/PlotPerf/PlotPerf.py +++ /dev/null @@ -1,915 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors: -Stefan Ruppin -Sven Knuth -""" - -__author__ = 'Stefan' - -import papi.pyqtgraph as pq - -from papi.plugin.base_classes.vip_base import vip_base -from papi.data.DParameter import DParameter -import numpy as np -import collections -import re -import time -import papi.pyqtgraph as pg -from papi.pyqtgraph.widgets.RemoteGraphicsView import RemoteGraphicsView - -current_milli_time = lambda: int(round(time.time() * 1000)) - -from papi.pyqtgraph.Qt import QtCore, QtGui - -class PlotPerf(vip_base): - """ - style_codes: - 0 : QtCore.Qt.SolidLine, - 1 : QtCore.Qt.DashDotDotLine, - 2 : QtCore.Qt.DashDotLine, - 3 : QtCore.Qt.DashLine, - 4 : QtCore.Qt.DotLine - - color_codes: - 0 : (255, 255, 255), - 1 : (255, 0 , 0 ), - 2 : (0 , 255, 0 ), - 3 : (0 , 0 , 255), - 4 : (100, 100, 100) - """ - - def __init__(self): - super(PlotPerf, self).__init__() - """ - Function init - - :param config: - :return: - """ - - self.signals = {} - - self.__buffer_size__ = None - self.__downsampling_rate__ = 1 - self.__tbuffer__ = [] - self.__tdata_old__ = [0] - self.__signals_have_same_length = True - self.__roll_shift__ = None - self.__append_at__ = 1 - self.__new_added_data__ = 0 - self.__input_size__ = 0 - self.__rolling_plot__ = False - self.__colors_selected__ = [] - self.__styles_selected__ = [] - self.__show_grid_x__ = None - self.__show_grid_y__ = None - self.__parameters__ = {} - self.__update_intervall__ = None - self.__last_time__ = None - self.__plotWidget__ = None - self.__legend__ = None - self.__text_item__ = None - self.__vertical_line__ = None - self.__offset_line__ = None - - self.styles = { - 0: QtCore.Qt.SolidLine, - 1: QtCore.Qt.DashDotDotLine, - 2: QtCore.Qt.DashDotLine, - 3: QtCore.Qt.DashLine, - 4: QtCore.Qt.DotLine - } - - self.colors = { - 0: (255, 255, 255), - 1: (255, 0, 0 ), - 2: (0, 255, 0 ), - 3: (0, 0, 255), - 4: (100, 100, 100) - } - - def initiate_layer_0(self, config=None): - """ - Function initiate layer 0 - - :param config: - :return: - """ - - # --------------------------- - # Read configuration - # --------------------------- - int_re = re.compile(r'(\d+)') - - self.__show_grid_x__ = self.config['x-grid']['value'] == '1' - self.__show_grid_y__ = self.config['y-grid']['value'] == '1' - self.__rolling_plot__ = self.config['rolling_plot']['value'] == '1' - - self.__colors_selected__ = int_re.findall(self.config['color']['value']) - self.__styles_selected__ = int_re.findall(self.config['style']['value']) - - self.__buffer_size__ = int(int_re.findall(self.config['buffersize']['value'])[0]) - - self.__downsampling_rate__ = int(int_re.findall(self.config['downsampling_rate']['value'])[0]) - - - # ---------------------------- - # Create internal variables - # ---------------------------- - - self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) - - # -------------------------------- - # Create PlotWidget - # -------------------------------- - - self.__text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(0, 0)) - self.__vertical_line__ = pq.InfiniteLine() - - self.__plotWidget__ = pq.PlotWidget() - self.__plotWidget__.addItem(self.__text_item__) - - if self.__rolling_plot__: - self.__plotWidget__.addItem(self.__vertical_line__) - - self.__text_item__.setPos(0, 0) - - self.__rgv__ = RemoteGraphicsView(debug=False) - - self.set_widget_for_internal_usage(self.__rgv__) - - self.__plotWidget__ = self.__rgv__.pg.PlotItem() - # self.__plot_item__ = self.__plotWidget__.pg.PlotItem() - - self.__plotWidget__.setWindowTitle('PlotPerformanceTitle') - self.__plotWidget__.showGrid(x=self.__show_grid_x__, y=self.__show_grid_y__) - -# self.set_widget_for_internal_usage(self.__plotWidget__) - - # - if self.config['xRange-auto']['value']=='1': - pass - else: - self.use_range_for_x(self.config['xRange']['value']) - - if self.config['yRange-auto']['value']== '1': - pass - else: - self.use_range_for_y(self.config['yRange']['value']) - - # --------------------------- - # Create Parameters - # --------------------------- - - self.__parameters__['x-grid'] = DParameter('x-grid', 0, Regex='^(1|0){1}$') - self.__parameters__['y-grid'] = DParameter('y-grid', 0, Regex='^(1|0){1}$') - - self.__parameters__['color'] = DParameter('color', '[0 1 2 3 4]', Regex='^\[(\s*\d\s*)+\]') - self.__parameters__['style'] = DParameter('style', '[0 0 0 0 0]', Regex='^\[(\s*\d\s*)+\]') - self.__parameters__['rolling'] = DParameter('rolling', '0', Regex='^(1|0){1}') - - self.__parameters__['downsampling_rate'] = DParameter('downsampling_rate', self.__downsampling_rate__, Regex='^([1-9][0-9]?|100)$') - self.__parameters__['buffersize'] = DParameter('buffersize', self.__buffer_size__, Regex='^([1-9][0-9]{0,3}|10000)$') - - self.__parameters__['xRange-auto'] = DParameter('xRange-auto', '1', Regex='^(1|0){1}$') - self.__parameters__['xRange'] = DParameter('xRange', '[0,1]', Regex='(\d+\.\d+)') - self.__parameters__['yRange-auto'] = DParameter('yRange-auto', '1', Regex='^(1|0){1}$') - self.__parameters__['yRange'] = DParameter('yRange', '[0,1]', Regex='(\d+\.\d+)') - - self.send_new_parameter_list(list(self.__parameters__.values())) - - # --------------------------- - # Create Legend - # --------------------------- - - # self.__legend__ = pq.LegendItem((100, 40), offset=(40, 1)) # args are (size, offset) - # self.__legend__.setParentItem(self.__plotWidget__.graphicsItem()) - - self.__last_time__ = current_milli_time() - - self.__update_intervall__ = 40 # in milliseconds - - #self.setup_context_menu() - - # - if self.config['xRange-auto']['value']=='1': - pass - else: - self.use_range_for_x(self.config['xRange']['value']) - - if self.config['yRange-auto']['value']== '1': - pass - else: - self.use_range_for_y(self.config['yRange']['value']) - - return True - - def pause(self): - """ - Function pause - - :return: - """ - print('PlotPerformance paused') - - def resume(self): - """ - Function resume - - :return: - """ - print('PlotPerformance resumed') - - def execute(self, Data=None, block_name=None): - """ - Function execute - - :param Data: - :param block_name: - :return: - """ - print('execute') - return - t = Data['t'] - - self.__input_size__ = len(t) - self.__tbuffer__.extend(t) - self.__new_added_data__ += len(t) - self.__signals_have_same_length = True - - for key in Data: - if key != 't': - y = Data[key] - if key in self.signals: - buffer = self.signals[key]['buffer'] - buffer.extend(y) - self.__signals_have_same_length &= (len(t) == len(y)) - - if self.__input_size__ > 1 or self.__signals_have_same_length: - if current_milli_time() - self.__last_time__ > self.__update_intervall__: - - self.update_plot() - self.__last_time__ = current_milli_time() - self.__new_added_data__ = 0 - else: - self.update_plot_single_timestamp(Data) - - def update_plot(self): - """ - Function update_plot - - :return: - """ - shift_data = 0 - - for last_tvalue in self.__tdata_old__: - if last_tvalue in self.__tbuffer__: - shift_data = list(self.__tbuffer__).index(last_tvalue) - break - - tdata = list(self.__tbuffer__)[shift_data::self.__downsampling_rate__] - - if self.__rolling_plot__: - self.__append_at__ += self.__new_added_data__ / self.__downsampling_rate__ - self.__append_at__ %= len(tdata) - - # -------------------------- - # iterate over all buffers - # -------------------------- - - for signal_name in self.signals: - data = list(self.signals[signal_name]['buffer'])[shift_data::self.__downsampling_rate__] - - if self.__rolling_plot__: - data = np.roll(data, int(self.__append_at__)) - self.__vertical_line__.setValue(tdata[int(self.__append_at__) - 1]) - else: - self.__vertical_line__.setValue(tdata[0]) - - curve = self.signals[signal_name]['curve'] - - curve.setData( np.array(tdata), data) - - self.__tdata_old__ = tdata - - def update_plot_single_timestamp(self, data): - """ - Function update_plot_single_timestamp - - :return: - """ - - self.__text_item__.setText("Time " + str(data['t'][0]), color=(200, 200, 200)) - - for signal_name in data: - if signal_name != 't': - signal_data = data[signal_name] - if signal_name in self.signals: - tdata = np.linspace(1, len(signal_data), len(signal_data)) - - curve = self.signals[signal_name]['curve'] - curve.setData(tdata, signal_data, _callSync='off') - - - def set_parameter(self, name, value): - """ - Function set parameters - - :param name: - :param value: - :return: - """ - return - if name == 'x-grid': - self.config['x-grid']['value'] = value - self.__plotWidget__.showGrid(x=value == '1') - self.xGrid_Checkbox.setChecked(value=='1') - - if name == 'y-grid': - self.config['y-grid']['value'] = value - self.__plotWidget__.showGrid(y=value == '1') - self.yGrid_Checkbox.setChecked(value=='1') - - if name == 'downsampling_rate': - self.config['downsampling_rate']['value'] = value - self.__downsampling_rate__ = int(value) - #self.__plotWidget__.getPlotItem().setDownsampling(ds=int(value),auto=False,mode='mean') - self.__new_added_data__ = 0 - - - if name == 'rolling': - self.__rolling_plot__ = int(float(value)) == int('1') - self.config['rolling_plot']['value'] = value - self.rolling_Checkbox.setChecked(value=='1') - if self.__rolling_plot__: - # if self.__vertical_line__ not in self.__plotWidget__.listDataItems(): - - self.__plotWidget__.addItem(self.__vertical_line__) - - if name == 'color': - self.config['color']['value'] = value - int_re = re.compile(r'(\d+)') - self.__colors_selected__ = int_re.findall(self.config['color']['value']) - self.update_pens() - - if name == 'style': - self.config['style']['value'] = value - int_re = re.compile(r'(\d+)') - self.__styles_selected__ = int_re.findall(self.config['style']['value']) - self.update_pens() - - if name == 'buffersize': - self.config['buffersize']['value'] = value - self.set_buffer_size(value) - - if name == 'xRange-auto': - self.config['xRange-auto']['value'] = value - self.xRange_AutoCheckbox.setChecked(value=='1') - if int(value) == 1: - self.xRange_minEdit.setDisabled(True) - self.xRange_maxEdit.setDisabled(True) - self.__plotWidget__.getPlotItem().getViewBox().menu.xAutoClicked() - else: - self.use_range_for_x(self.config['xRange']['value']) - self.xRange_minEdit.setDisabled(False) - self.xRange_maxEdit.setDisabled(False) - - if name == 'yRange-auto': - self.config['yRange-auto']['value'] = value - self.yRange_AutoCheckbox.setChecked(value=='1') - if int(value) == 1: - self.yRange_minEdit.setDisabled(True) - self.yRange_maxEdit.setDisabled(True) - self.__plotWidget__.getPlotItem().getViewBox().menu.yAutoClicked() - else: - self.yRange_minEdit.setDisabled(False) - self.yRange_maxEdit.setDisabled(False) - self.use_range_for_y(self.config['yRange']['value']) - - if name == 'xRange': - self.config['xRange']['value'] = value - if self.config['xRange-auto']['value'] == '0': - self.use_range_for_x(value) - - if name == 'yRange': - self.config['yRange']['value'] = value - if self.config['yRange-auto']['value'] == '0': - self.use_range_for_y(value) - - def update_pens(self): - """ - Function update pens - - :return: - """ - - for signal_name in self.signals.keys(): - signal_id = self.signals[signal_name]['id'] - - new_pen = self.get_pen(signal_id) - - self.signals[signal_name]['curve'].setPen(new_pen) - - def set_buffer_size(self, new_size): - """ - Function set buffer size - - :param new_size: - :return: - """ - - self.__buffer_size__ = int(new_size) - - # ------------------------------- - # Change Time Buffer - # ------------------------------- - - self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) - - # ------------------------------- - # Change Buffer of current - # plotted signals - # ------------------------------- - - start_size = len(self.__tbuffer__) - - for signal_name in self.signals: - buffer_new = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION - - buffer_old = self.signals[signal_name]['buffer'] - # buffer_new.extend(buffer_old) - - self.signals[signal_name]['buffer'] = buffer_new - - self.__new_added_data__ = 0 - - def plugin_meta_updated(self): - """ - By this function the plot is able to handle more than one input for plotting. - - :return: - """ - subscriptions = self.dplugin_info.get_subscribtions() - - current_signals = {} - index = 0 - - for dpluginsub_id in subscriptions: - for dblock_name in subscriptions[dpluginsub_id]: - - # get subscription for dblock - subscription = subscriptions[dpluginsub_id][dblock_name] - - for signal_name in subscription.get_signals(): - signal = subscription.get_dblock().get_signal_by_uname(signal_name) - index += 1 - current_signals[signal_name] = {} - current_signals[signal_name]['signal'] = signal - current_signals[signal_name]['index'] = index - - # current_signals = sorted(current_signals) - - # Add missing buffers - for signal_name in sorted(current_signals.keys()): - if signal_name not in self.signals: - signal = current_signals[signal_name]['signal'] - self.add_databuffer(signal, current_signals[signal_name]['index']) - - # Delete old buffers - for signal_name in self.signals.copy(): - if signal_name not in current_signals: - signal = self.signals[signal_name]['signal'] - self.remove_databuffer(signal) - - self.update_pens() - #self.update_legend() - - def add_databuffer(self, signal, signal_id): - """ - Create new buffer for signal_name. - - :param signal_name: - :param signal_id: - :return: - """ - - signal_name = signal.uname - - if signal_name not in self.signals: - self.signals[signal_name] = {} - - start_size = len(self.__tbuffer__) - - buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION - - - curve = self.__plotWidget__.plot([0, 1], [0, 1]) - - - self.signals[signal_name]['buffer'] = buffer - self.signals[signal_name]['curve'] = curve - self.signals[signal_name]['id'] = signal_id - self.signals[signal_name]['signal'] = signal - - - def remove_databuffer(self, signal): - """ - Remove the databuffer for signal_name. - - :param signal_name: - :return: - """ - - signal_name = signal.uname - - if signal_name in self.signals: - curve = self.signals[signal_name]['curve'] - curve.clear() - # self.__legend__.removeItem(legend_name) - del self.signals[signal_name] - - def get_pen(self, index): - """ - Function get pen - - :param index: - :return: - """ - index = int(index) - - style_index = index % len(self.__styles_selected__) - style_code = int(self.__styles_selected__[style_index]) - - color_index = index % len(self.__colors_selected__) - color_code = int(self.__colors_selected__[color_index]) - - if style_code in self.styles: - style = self.styles[style_code] - else: - style = self.styles[1] - - if color_code in self.colors: - color = self.colors[color_code] - else: - color = self.colors[1] - - return pq.mkPen(color=color, style=style) - - def use_range_for_x(self, value): - reg = re.compile(r'(\d+\.\d+)') - range = reg.findall(value) - if len(range) == 2: - self.xRange_minEdit.setText(range[0]) - self.xRange_maxEdit.setText(range[1]) - self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]),float(range[1])) - - def use_range_for_y(self, value): - reg = re.compile(r'(\d+\.\d+)') - range = reg.findall(value) - if len(range) == 2: - self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]), float(range[1])) - - def setup_context_menu(self): - self.custMenu = QtGui.QMenu("Options") - self.axesMenu = QtGui.QMenu('Axes') - self.gridMenu = QtGui.QMenu('Grid') - - - # --------------------------------------------------------- - # #### X-Range Actions - self.xRange_Widget = QtGui.QWidget() - self.xRange_Layout = QtGui.QVBoxLayout(self.xRange_Widget) - self.xRange_Layout.setContentsMargins(2, 2, 2, 2) - self.xRange_Layout.setSpacing(1) - - self.xRange_AutoCheckbox = QtGui.QCheckBox(checked= self.config['xRange-auto']['value'] == '1') - self.xRange_AutoCheckbox.stateChanged.connect(self.contextMenu_xRange_toogle) - self.xRange_AutoCheckbox.setText('X-Autorange') - self.xRange_Layout.addWidget(self.xRange_AutoCheckbox) - - ##### X Line Edits - # Layout - self.xRange_EditWidget = QtGui.QWidget() - self.xRange_EditLayout = QtGui.QHBoxLayout(self.xRange_EditWidget) - self.xRange_EditLayout.setContentsMargins(2, 2, 2, 2) - self.xRange_EditLayout.setSpacing(1) - - # get old values; - reg = re.compile(r'(\d+\.\d+)') - range = reg.findall(self.config['xRange']['value']) - if len(range) == 2: - x_min = range[0] - x_max = range[1] - else: - x_min = '0.0' - x_max = '1.0' - - - # Min - self.xRange_minEdit = QtGui.QLineEdit() - self.xRange_minEdit.setFixedWidth(80) - self.xRange_minEdit.setText(x_min) - self.xRange_minEdit.editingFinished.connect(self.contextMenu_xRange_toogle) - # Max - self.xRange_maxEdit = QtGui.QLineEdit() - self.xRange_maxEdit.setFixedWidth(80) - self.xRange_maxEdit.setText(x_max) - self.xRange_maxEdit.editingFinished.connect(self.contextMenu_xRange_toogle) - # addTo Layout - self.xRange_EditLayout.addWidget(self.xRange_minEdit) - self.xRange_EditLayout.addWidget(QtGui.QLabel('<')) - self.xRange_EditLayout.addWidget(self.xRange_maxEdit) - self.xRange_Layout.addWidget(self.xRange_EditWidget) - - # build Action - self.xRange_Action = QtGui.QWidgetAction(self.__plotWidget__) - self.xRange_Action.setDefaultWidget(self.xRange_Widget) - - - # --------------------------------------------------------------- - ##### Y-Range Actions - self.yRange_Widget = QtGui.QWidget() - self.yRange_Layout = QtGui.QVBoxLayout(self.yRange_Widget) - self.yRange_Layout.setContentsMargins(2, 2, 2, 2) - self.yRange_Layout.setSpacing(1) - - self.yRange_AutoCheckbox = QtGui.QCheckBox(checked= self.config['xRange-auto']['value'] == '1') - self.yRange_AutoCheckbox.stateChanged.connect(self.contextMenu_yRange_toogle) - self.yRange_AutoCheckbox.setText('Y-Autorange') - self.yRange_Layout.addWidget(self.yRange_AutoCheckbox) - - ##### Y Line Edits - # Layout - self.yRange_EditWidget = QtGui.QWidget() - self.yRange_EditLayout = QtGui.QHBoxLayout(self.yRange_EditWidget) - self.yRange_EditLayout.setContentsMargins(2, 2, 2, 2) - self.yRange_EditLayout.setSpacing(1) - - # get old values; - reg = re.compile(r'(\d+\.\d+)') - range = reg.findall(self.config['yRange']['value']) - if len(range) == 2: - y_min = range[0] - y_max = range[1] - else: - y_min = '0.0' - y_max = '1.0' - - # Min - self.yRange_minEdit = QtGui.QLineEdit() - self.yRange_minEdit.setFixedWidth(80) - self.yRange_minEdit.setText(y_min) - self.yRange_minEdit.editingFinished.connect(self.contextMenu_yRange_toogle) - - # Max - self.yRange_maxEdit = QtGui.QLineEdit() - self.yRange_maxEdit.setFixedWidth(80) - self.yRange_maxEdit.setText(y_max) - self.yRange_maxEdit.editingFinished.connect(self.contextMenu_yRange_toogle) - # addTo Layout - self.yRange_EditLayout.addWidget(self.yRange_minEdit) - self.yRange_EditLayout.addWidget(QtGui.QLabel('<')) - self.yRange_EditLayout.addWidget(self.yRange_maxEdit) - self.yRange_Layout.addWidget(self.yRange_EditWidget) - - # build Action - self.yRange_Action = QtGui.QWidgetAction(self.__plotWidget__) - self.yRange_Action.setDefaultWidget(self.yRange_Widget) - - ##### Rolling Plot - self.rolling_Checkbox = QtGui.QCheckBox() - self.rolling_Checkbox.setText('Rolling plot') - self.rolling_Checkbox.setChecked(self.config['rolling_plot']['value'] == '1') - self.rolling_Checkbox.stateChanged.connect(self.contextMenu_rolling_toogled) - self.rolling_Checkbox_Action = QtGui.QWidgetAction(self.__plotWidget__) - self.rolling_Checkbox_Action.setDefaultWidget(self.rolling_Checkbox) - - - - ##### Build axes menu - self.axesMenu.addAction(self.xRange_Action) - self.axesMenu.addSeparator().setText("Y-Range") - self.axesMenu.addAction(self.yRange_Action) - - # Grid Menu: - # ----------------------------------------------------------- - # Y-Grid checkbox - self.xGrid_Checkbox = QtGui.QCheckBox() - self.xGrid_Checkbox.stateChanged.connect(self.contextMenu_xGrid_toogle) - self.xGrid_Checkbox.setText('X-Grid') - self.xGrid_Action = QtGui.QWidgetAction(self.__plotWidget__) - self.xGrid_Action.setDefaultWidget(self.xGrid_Checkbox) - self.gridMenu.addAction(self.xGrid_Action) - # Check config for startup state - if self.__show_grid_x__: - self.xGrid_Checkbox.setChecked(True) - - # X-Grid checkbox - self.yGrid_Checkbox = QtGui.QCheckBox() - self.yGrid_Checkbox.stateChanged.connect(self.contextMenu_yGrid_toogle) - self.yGrid_Checkbox.setText('Y-Grid') - self.yGrid_Action = QtGui.QWidgetAction(self.__plotWidget__) - self.yGrid_Action.setDefaultWidget(self.yGrid_Checkbox) - self.gridMenu.addAction(self.yGrid_Action) - # Check config for startup state - if self.__show_grid_y__: - self.yGrid_Checkbox.setChecked(True) - - # add Menus - self.custMenu.addMenu(self.axesMenu) - self.custMenu.addMenu(self.gridMenu) - self.custMenu.addSeparator().setText("Rolling Plot") - self.custMenu.addAction(self.rolling_Checkbox_Action) -# self.__plotWidget__.getPlotItem().getViewBox().menu.clear() -# self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] - - - #self.__plotWidget__.getPlotItem().getViewBox() - - def range_changed(self): - print('r') - - def contextMenu_rolling_toogled(self): - if self.rolling_Checkbox.isChecked(): - self.control_api.do_set_parameter(self.__id__, 'rolling', '1') - else: - self.control_api.do_set_parameter(self.__id__, 'rolling', '0') - - def contextMenu_xGrid_toogle(self): - if self.xGrid_Checkbox.isChecked(): - self.control_api.do_set_parameter(self.__id__, 'x-grid', '1') - else: - self.control_api.do_set_parameter(self.__id__, 'x-grid', '0') - - def contextMenu_yGrid_toogle(self): - if self.yGrid_Checkbox.isChecked(): - self.control_api.do_set_parameter(self.__id__, 'y-grid', '1') - else: - self.control_api.do_set_parameter(self.__id__, 'y-grid', '0') - - def contextMenu_xRange_toogle(self): - if self.xRange_AutoCheckbox.isChecked(): - # do autorange - self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '1') - self.xRange_minEdit.setDisabled(True) - self.xRange_maxEdit.setDisabled(True) - else: - self.xRange_minEdit.setDisabled(False) - self.xRange_maxEdit.setDisabled(False) - mi = self.xRange_minEdit.text() - ma = self.xRange_maxEdit.text() - self.control_api.do_set_parameter(self.__id__, 'xRange-auto', '0') - self.control_api.do_set_parameter(self.__id__, 'xRange', '[' + mi + ' ' + ma + ']') - - def contextMenu_yRange_toogle(self): - if self.yRange_AutoCheckbox.isChecked(): - # do autorange - self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '1') - self.yRange_minEdit.setDisabled(True) - self.yRange_maxEdit.setDisabled(True) - else: - self.yRange_minEdit.setDisabled(False) - self.yRange_maxEdit.setDisabled(False) - # do man range - mi = self.yRange_minEdit.text() - ma = self.yRange_maxEdit.text() - self.control_api.do_set_parameter(self.__id__, 'yRange-auto', '0') - self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') - - def update_signals(self): - subscriptions = self.dplugin_info.get_subscribtions() - - for dpluginsub_id in subscriptions: - for dblock_name in subscriptions[dpluginsub_id]: - - # get subscription for dblock - subscription = subscriptions[dpluginsub_id][dblock_name] - - for signal_name in subscription.get_signals(): - signal = subscription.get_dblock().get_signal_by_uname(signal_name) - - self.signals[signal_name]['signal'] = signal - - def update_legend(self): - # self.__plotWidget__.removeItem(self.__legend__) - self.__legend__.scene().removeItem(self.__legend__) - del self.__legend__ - - self.__legend__ = pq.LegendItem((100, 40), offset=(40, 1)) # args are (size, offset) - self.__legend__.setParentItem(self.__plotWidget__.graphicsItem()) - - self.update_signals() - - for signal_name in sorted(self.signals.keys()): - curve = self.signals[signal_name]['curve'] - signal = self.signals[signal_name]['signal'] - legend_name = signal.dname - - self.__legend__.addItem(curve, legend_name) - - def quit(self): - """ - Function quit plugin - - :return: - """ - print('PlotPerformance: will quit') - - def get_plugin_configuration(self): - """ - Function get plugin configuration - - :return {}: - """ - config = { - # 'label_y': { - # 'value': "amplitude, V", - # 'regex': '\w+,\s+\w+', - # 'display_text': 'Label-Y' - # }, 'label_x': { - # 'value': "time, s", - # 'regex': '\w+,\s*\w+', - # 'display_text': 'Label-X' - # }, - 'x-grid': { - 'value': "0", - 'regex': '^(1|0)$', - 'type': 'bool', - 'display_text': 'Grid-X' - }, 'y-grid': { - 'value': "0", - 'regex': '^(1|0)$', - 'type': 'bool', - 'display_text': 'Grid-Y' - }, 'color': { - 'value': "[0 1 2 3 4]", - 'regex': '^\[(\s*\d\s*)+\]', - 'advanced': '1', - 'display_text': 'Color' - }, 'style': { - 'value': "[0 0 0 0 0]", - 'regex': '^\[(\s*\d\s*)+\]', - 'advanced': '1', - 'display_text': 'Style' - }, 'buffersize': { - 'value': "1000", - 'regex': '^([1-9][0-9]{0,3}|10000)$', - 'advanced': '1', - 'display_text': 'Buffersize' - }, 'downsampling_rate': { - 'value': "1", - 'regex': '(\d+)' - }, 'rolling_plot': { - 'value': '0', - 'regex': '^(1|0)$', - 'type': 'bool', - 'display_text': 'Rolling Plot' - }, 'xRange-auto': { - 'value': '1', - 'regex': '^(1|0)$', - 'type': 'bool', - 'advanced': '1', - 'display_text': 'x: auto range' - }, 'yRange-auto': { - 'value': '1', - 'regex': '^(1|0)$', - 'type': 'bool', - 'advanced': '1', - 'display_text': 'y: auto range' - }, 'xRange': { - 'value': '[0.0 1.0]', - 'regex': '(\d+\.\d+)', - 'advanced': '1', - 'display_text': 'x: range' - }, 'yRange': { - 'value': '[0.0 1.0]', - 'regex': '(\d+\.\d+)', - 'advanced': '1', - 'display_text': 'y: range' - } - } - # http://www.regexr.com/ - return config \ No newline at end of file diff --git a/papi/plugin/visual/PlotPerf/PlotPerf.yapsy-plugin b/papi/plugin/visual/PlotPerf/PlotPerf.yapsy-plugin deleted file mode 100644 index a6e4be99..00000000 --- a/papi/plugin/visual/PlotPerf/PlotPerf.yapsy-plugin +++ /dev/null @@ -1,9 +0,0 @@ -[Core] -Name = PlotPerf -Module = PlotPerf - -[Documentation] -Author = S.R., S.K. -Version = 0.7 -Website = -Description = Basic plotting plugin. Supports more than one signals which must have the same time vector. \ No newline at end of file diff --git a/papi/plugin/visual/PlotPerf/__init__.py b/papi/plugin/visual/PlotPerf/__init__.py deleted file mode 100644 index 7c97c23d..00000000 --- a/papi/plugin/visual/PlotPerf/__init__.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: latin-1 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors -Sven Knuth -""" - -__author__ = 'knuths' diff --git a/papi/plugin/visual/PlotPerf/items.py b/papi/plugin/visual/PlotPerf/items.py deleted file mode 100644 index 09077f28..00000000 --- a/papi/plugin/visual/PlotPerf/items.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors: -Sven Knuth -""" - -import papi.pyqtgraph as pg -import numpy as np - - From ff1e59f4fd7a05bcb7ba27bee22d53751f29ae8d Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 2 Feb 2015 16:04:52 +0100 Subject: [PATCH 118/260] added reset functionality to ORTDController --- papi/gui/gui_api.py | 7 ++ papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 20 ++++-- .../visual/OrtdController/OrtdController.py | 72 ++++++++++++------- 3 files changed, 70 insertions(+), 29 deletions(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 898bd065..78376b61 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -87,6 +87,13 @@ def do_create_plugin(self, plugin_identifier, uname, config={}, autostart=True): opt.plugin_config = config opt.autostart = autostart + # check if plugin with uname already exists + allPlugins = self.gui_data.get_all_plugins() + for pluginID in allPlugins: + plugin = allPlugins[pluginID] + if plugin.uname == uname: + return False + # create event object and sent it to core event = Event.instruction.CreatePlugin(self.gui_id, 0, opt) self.core_queue.put(event) diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 60866590..d3eb1a48 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -152,6 +152,7 @@ def start_init(self, config=None): self.ControlBlock = DBlock('ControllerSignals') + self.ControlBlock.add_signal(DSignal('ControlSignalReset')) self.ControlBlock.add_signal(DSignal('ControlSignalCreate')) self.ControlBlock.add_signal(DSignal('ControlSignalSub')) self.ControlBlock.add_signal(DSignal('ControllerSignalParameter')) @@ -277,11 +278,20 @@ def execute(self, Data=None, block_name=None): def set_parameter(self, name, value): if name == 'triggerConfiguration': - cfg, subs, para, close = self.plconf() - self.send_new_data('ControllerSignals', [1], {'ControlSignalCreate':cfg, - 'ControlSignalSub':subs, - 'ControllerSignalParameter':para, - 'ControllerSignalClose':close}) + if value == '1': + cfg, subs, para, close = self.plconf() + self.send_new_data('ControllerSignals', [1], {'ControlSignalReset':0, + 'ControlSignalCreate':cfg, + 'ControlSignalSub':subs, + 'ControllerSignalParameter':para, + 'ControllerSignalClose':close}) + if value == '2': + self.send_new_data('ControllerSignals', [1], {'ControlSignalReset': 1, + 'ControlSignalCreate':None, + 'ControlSignalSub':None, + 'ControllerSignalParameter':None, + 'ControllerSignalClose':None}) + else: for para in self.Parameter_List: if para.name == name: diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index bddc5d50..8f9ea979 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -59,6 +59,10 @@ def initiate_layer_0(self, config=None): self.ControllerWidget.addPage(ControllerOrtdStart(api=self.control_api, uname=self.dplugin_info.uname,ortd_uname=self.ortd_uname)) self.ControllerWidget.addPage(ControllerWorking(api=self.control_api, uname=self.dplugin_info.uname)) + self.lock = threading.Lock() + + self.plugin_started_list = [] + return True def pause(self): @@ -83,20 +87,34 @@ def execute(self, Data=None, block_name = None): # hash signal_name: value # Data could have multiple types stored in it e.a. Data['d1'] = int, Data['d2'] = [] - if self.alreadyConfigured is False: - self.thread1 = threading.Thread(target=self.execute_cfg, args=[Data]) - self.alreadyConfigured = True - self.thread1.start() + self.thread1 = threading.Thread(target=self.execute_cfg, args=[Data]) + self.thread1.start() + + + + def execute_cfg(self, Data): + ############################ + # Reset Signal # + ############################ + if 'ControlSignalReset' in Data: + val = Data['ControlSignalReset'] + print(val) + if val == 1: + while len(self.plugin_started_list): + pl_uname = self.plugin_started_list.pop() + self.control_api.do_delete_plugin_uname(pl_uname) - def execute_cfg(self,Data): ############################ # Create Plugins # ############################ if 'ControlSignalCreate' in Data: cfg = Data['ControlSignalCreate'] - for pl_uname in cfg: - pl_cfg = cfg[pl_uname] - self.control_api.do_create_plugin(pl_cfg['identifier']['value'],pl_uname, pl_cfg['config']) + if cfg is not None: + for pl_uname in cfg: + if pl_uname not in self.plugin_started_list: + pl_cfg = cfg[pl_uname] + self.control_api.do_create_plugin(pl_cfg['identifier']['value'],pl_uname, pl_cfg['config']) + self.plugin_started_list.append(pl_uname) time.sleep(0.5) ############################ @@ -104,32 +122,37 @@ def execute_cfg(self,Data): ############################ if 'ControlSignalSub' in Data: cfg = Data['ControlSignalSub'] - for pl_uname in cfg: - pl_cfg = cfg[pl_uname] - sig = [] - if 'signals' in pl_cfg: - sig = pl_cfg['signals'] - self.control_api.do_subscribe_uname(pl_uname,self.ortd_uname, pl_cfg['block'], signals=sig, sub_alias= None) + if cfg is not None: + for pl_uname in cfg: + pl_cfg = cfg[pl_uname] + sig = [] + if 'signals' in pl_cfg: + sig = pl_cfg['signals'] + self.control_api.do_subscribe_uname(pl_uname,self.ortd_uname, pl_cfg['block'], signals=sig, sub_alias= None) ############################ # Set parameter links # ############################ if 'ControllerSignalParameter' in Data: cfg = Data['ControllerSignalParameter'] - for pl_uname in cfg: - pl_cfg = cfg[pl_uname] - para = None - if 'parameter' in pl_cfg: - para = pl_cfg['parameter'] - self.control_api.do_subscribe_uname(self.ortd_uname,pl_uname, pl_cfg['block'], signals=[], sub_alias= para) + if cfg is not None: + for pl_uname in cfg: + pl_cfg = cfg[pl_uname] + para = None + if 'parameter' in pl_cfg: + para = pl_cfg['parameter'] + self.control_api.do_subscribe_uname(self.ortd_uname,pl_uname, pl_cfg['block'], signals=[], sub_alias= para) ############################ # Close plugin # ############################ if 'ControllerSignalClose' in Data: cfg = Data['ControllerSignalClose'] - for pl_uname in cfg: - self.control_api.do_delete_plugin_uname(pl_uname) + if cfg is not None: + for pl_uname in cfg: + if pl_uname in self.plugin_started_list: + self.control_api.do_delete_plugin_uname(pl_uname) + self.plugin_started_list.remove(pl_uname) def set_parameter(self, name, value): @@ -282,11 +305,12 @@ def validatePage(self): def subscribe_control_signal(self): time.sleep(0.5) - self.api.do_subscribe_uname(self.uname,self.ortd_uname, 'ControllerSignals', signals=['ControlSignalCreate', + self.api.do_subscribe_uname(self.uname,self.ortd_uname, 'ControllerSignals', signals=['ControlSignalReset', + 'ControlSignalCreate', 'ControlSignalSub', 'ControllerSignalParameter', 'ControllerSignalClose']) - self.api.do_set_parameter_uname(self.ortd_uname, 'triggerConfiguration', 0) + self.api.do_set_parameter_uname(self.ortd_uname, 'triggerConfiguration', '1') class ControllerWorking(QtGui.QWizardPage): From 8f164110c16c6454d45b049084af08bb8da36021 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 2 Feb 2015 17:02:37 +0100 Subject: [PATCH 119/260] implemented slices which are drawn --- papi/gui/gui_event_processing.py | 1 + papi/plugin/visual/Plot/Plot.py | 174 ++++++++++++++++++++++++------- papi/plugin/visual/Plot/perf.py | 17 ++- 3 files changed, 148 insertions(+), 44 deletions(-) diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 76b4739c..afa27bb7 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -118,6 +118,7 @@ def gui_working(self, close_mock): # there was no new element, so event flag is set to false isEvent = False + # check if there was a new element to process it if isEvent: # get the event operation diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index a38a2008..d8d9b099 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -36,6 +36,7 @@ import numpy as np import collections import re +import copy import time import papi.pyqtgraph as pg @@ -45,19 +46,94 @@ class MultiLine(pg.QtGui.QGraphicsPathItem): - def __init__(self, x, y, pen): + def __init__(self, x, y, pen=pg.mkPen('r')): """x and y are 2D arrays of shape (Nplots, Nsamples)""" connect = np.ones(x.shape, dtype=bool) connect[:,-1] = 0 # don't draw the segment between each trace self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten()) pg.QtGui.QGraphicsPathItem.__init__(self, self.path) - self.setPen(pg.mkPen('r')) - #self.setPen(pen) + self.setPen(pen) + self.drawn = False + def shape(self): # override because QGraphicsPathItem.shape is too expensive. return pg.QtGui.QGraphicsItem.shape(self) + def boundingRect(self): return self.path.boundingRect() + +class PlotItem(object): + + def __init__(self,signal_name, buffers, signal_id, signal, slices): + super(PlotItem,self).__init__() + self.signal_name = signal_name + self.buffers = buffers + self.id = signal_id + self.signal = signal + self.position = 0 + self.item_ready = False + self.counter = len(buffers[0]) + self.pen = None + self.slices = slices + self.graphics = [] + self.slice_size = 10 + + def add_data(self, elements, tdata): + tdata = list(tdata) + buffer = self.buffers[0] + + self.counter -= len(elements) + + # It is possible to write all elements to the current buffer + if self.counter >= 0: + buffer.extend(elements) + # Some elements can't be written to the current buffer + if self.counter <= 0: + buffer.extend(elements[:self.counter]) + if len(tdata) < self.slice_size: + x_axis = np.array(tdata)[np.newaxis,:] + else: + x_axis = np.array(tdata[-self.slice_size-1:])[np.newaxis,:] + + self.create_graphic(x_axis) + +# position = (self.position + 1) & self.slices + buffer.extend(elements[self.counter:]) + + self.counter = self.slice_size + + + + def create_graphic(self, x_axis): + + y_axis = np.array(list(self.buffers[0])[-self.slice_size-1:]) + + if len(y_axis) == 0: + return None + if len(y_axis) != len(x_axis[0]): + return None + + graphic = MultiLine(x_axis, y_axis, self.pen) + + self.graphics.append(graphic) + #print(self.graphics) + + def get_graphic(self, tmp): + + if len(self.graphics) > 0: + graphic = self.graphics[-1] + + if not graphic.drawn: + graphic.drawn = True + return graphic + return None + + def get_old_graphic(self): + if len(self.graphics) > self.slices: + graphic = self.graphics.pop(0) + return graphic + return None + class Plot(vip_base): """ style_codes: @@ -112,6 +188,7 @@ def __init__(self, debug=False): self.__offset_line__ = None self.__y_axis__ = None self.__x_axis__ = None + self.__amount_of_slices__ = None self.styles = { 0: QtCore.Qt.SolidLine, @@ -137,6 +214,7 @@ def initiate_layer_0(self, config=None): :return: """ + # --------------------------- # Read configuration # --------------------------- @@ -150,7 +228,7 @@ def initiate_layer_0(self, config=None): self.__styles_selected__ = int_re.findall(self.config['style']['value']) self.__buffer_size__ = int(int_re.findall(self.config['buffersize']['value'])[0]) - + self.__buffer_size__ = 1000 self.__downsampling_rate__ = int(int_re.findall(self.config['downsampling_rate']['value'])[0]) @@ -159,6 +237,7 @@ def initiate_layer_0(self, config=None): # ---------------------------- self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) + self.__amount_of_slices__ = 10 # -------------------------------- # Create PlotWidget @@ -176,6 +255,8 @@ def initiate_layer_0(self, config=None): self.__plotWidget__.setWindowTitle('PlotPerformanceTitle') self.__plotWidget__.showGrid(x=self.__show_grid_x__, y=self.__show_grid_y__) + self.__plotWidget__.getPlotItem().getViewBox().disableAutoRange() + self.__plotWidget__.getPlotItem().getViewBox().setYRange(0,6) if not self.__papi_debug__: self.set_widget_for_internal_usage(self.__plotWidget__) @@ -223,7 +304,7 @@ def initiate_layer_0(self, config=None): self.__last_time__ = current_milli_time() - self.__update_intervall__ = 25 # in milliseconds + self.__update_intervall__ = 100 # in milliseconds self.__last_plot_time__ = 0 self.setup_context_menu() @@ -265,6 +346,7 @@ def execute(self, Data=None, block_name=None): :param block_name: :return: """ + t = Data['t'] self.__input_size__ = len(t) @@ -272,16 +354,22 @@ def execute(self, Data=None, block_name=None): self.__new_added_data__ += len(t) self.__signals_have_same_length = True + now = pg.ptime.time() + for key in Data: if key != 't': y = Data[key] if key in self.signals: - buffer = self.signals[key]['buffer'] - buffer.extend(y) + self.signals[key].add_data(y, self.__tbuffer__) + + # buffer = self.signals[key]['buffer'] + # buffer.extend(y) self.__signals_have_same_length &= (len(t) == len(y)) if self.__input_size__ > 1 or self.__signals_have_same_length: + if current_milli_time() - self.__last_time__ > self.__update_intervall__ - self.__last_plot_time__: + self.__last_time__ = current_milli_time() self.update_plot() self.__last_time__ = current_milli_time() @@ -289,6 +377,8 @@ def execute(self, Data=None, block_name=None): else: self.update_plot_single_timestamp(Data) + #print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) + def set_parameter(self, name, value): """ Function set parameters @@ -327,13 +417,13 @@ def set_parameter(self, name, value): self.config['color']['value'] = value int_re = re.compile(r'(\d+)') self.__colors_selected__ = int_re.findall(self.config['color']['value']) -# self.update_pens() + self.update_pens() if name == 'style': self.config['style']['value'] = value int_re = re.compile(r'(\d+)') self.__styles_selected__ = int_re.findall(self.config['style']['value']) -# self.update_pens() + self.update_pens() if name == 'buffersize': self.config['buffersize']['value'] = value @@ -381,11 +471,11 @@ def update_pens(self): """ for signal_name in self.signals.keys(): - signal_id = self.signals[signal_name]['id'] + signal_id = self.signals[signal_name].id new_pen = self.get_pen(signal_id) - self.signals[signal_name]['pen'] = new_pen + self.signals[signal_name].pen = new_pen def update_plot(self): """ @@ -393,42 +483,46 @@ def update_plot(self): :return: """ + shift_data = 0 tdata = list(self.__tbuffer__) now = pg.ptime.time() - self.__plotWidget__.clear() + #self.__plotWidget__.clear() # Set Y-Axis - amount_signal = len(self.signals) + count = 0 - len_data = len(tdata) - self.__y_axis__ = np.ones((amount_signal, len_data)) - self.__x_axis__ = np.empty((amount_signal, len_data)) + x_axis = np.array(tdata[-100:])[np.newaxis,:] + + #self.__plotWidget__.getPlotItem().getViewBox().disableAutoRange() - count = 0 for signal_name in self.signals: - data = list(self.signals[signal_name]['buffer']) - self.__y_axis__[count,:] = np.array(data) - count += 1 + #y_axis = np.array(data) + # - # Set X-Axis + graphic = self.signals[signal_name].get_graphic(x_axis) + #line = MultiLine(x_axis, y_axis, pen) + if graphic is not None: + self.__plotWidget__.addItem(graphic) - self.__x_axis__[:] = np.array(tdata)[np.newaxis,:] + graphic = self.signals[signal_name].get_old_graphic() - lines = MultiLine(self.__x_axis__, self.__y_axis__, None) + #if graphic is not None: + #self.__plotWidget__.clear() + # self.__plotWidget__.removeItem(graphic) + count += 1 - self.__plotWidget__.addItem(lines) self.__last_plot_time__ = pg.ptime.time()-now - if self.__papi_debug__: - print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) + self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0],tdata[-1]) - self.__tdata_old__ = tdata + # if self.__papi_debug__: + # print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) def update_plot_single_timestamp(self, data): """ @@ -464,7 +558,7 @@ def set_buffer_size(self, new_size): # Change Time Buffer # ------------------------------- - self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) +# self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) # ------------------------------- # Change Buffer of current @@ -476,10 +570,10 @@ def set_buffer_size(self, new_size): for signal_name in self.signals: buffer_new = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION - buffer_old = self.signals[signal_name]['buffer'] +# buffer_old = self.signals[signal_name]['buffer'] # buffer_new.extend(buffer_old) - self.signals[signal_name]['buffer'] = buffer_new +# self.signals[signal_name]['buffer'] = buffer_new self.__new_added_data__ = 0 @@ -525,7 +619,7 @@ def plugin_meta_updated(self): print('Remove ' + signal_name) - #self.update_pens() + self.update_pens() #self.update_legend() def add_databuffer(self, signal, signal_id): @@ -544,15 +638,15 @@ def add_databuffer(self, signal, signal_id): start_size = len(self.__tbuffer__) - buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION + buffers = [] - #curve = self.__plotWidget__.plot([0, 1], [0, 1], clear=False) + for i in range(0,self.__amount_of_slices__): + buffer = collections.deque([0.0] * start_size, int(self.__buffer_size__ ) ) # COLLECTION + buffers.append(buffer) - self.signals[signal_name]['buffer'] = buffer - #self.signals[signal_name]['curve'] = curve - self.signals[signal_name]['id'] = signal_id - self.signals[signal_name]['signal'] = signal + plot_item = PlotItem(signal_name, buffers,signal_id,signal, self.__amount_of_slices__) + self.signals[signal_name] = plot_item def remove_databuffer(self, signal): """ @@ -565,8 +659,8 @@ def remove_databuffer(self, signal): signal_name = signal.uname if signal_name in self.signals: - curve = self.signals[signal_name]['curve'] - curve.clear() + # curve = self.signals[signal_name]['curve'] + # curve.clear() # self.__legend__.removeItem(legend_name) del self.signals[signal_name] @@ -875,6 +969,8 @@ def debug_papi(self): self.add_databuffer(signal_4, 4) self.add_databuffer(signal_5, 5) + self.update_pens() + pass def get_plugin_configuration(self): diff --git a/papi/plugin/visual/Plot/perf.py b/papi/plugin/visual/Plot/perf.py index a8479b44..f7bad4b6 100644 --- a/papi/plugin/visual/Plot/perf.py +++ b/papi/plugin/visual/Plot/perf.py @@ -48,19 +48,26 @@ def do_fctn(plugin): for i in range(10): data['t'] = [t] t += 0.01 - data['signal_1'] = [random.randint(0, 5)] - data['signal_2'] = [random.randint(10, 15)] - data['signal_3'] = [random.randint(20, 25)] - data['signal_4'] = [random.randint(30, 35)] - data['signal_5'] = [random.randint(40, 45)] + # data['signal_1'] = [random.randint(0, 5)] + # data['signal_2'] = [random.randint(10, 15)] + # data['signal_3'] = [random.randint(20, 25)] + # data['signal_4'] = [random.randint(30, 35)] + # data['signal_5'] = [random.randint(40, 45)] + data['signal_1'] = [1] + data['signal_2'] = [2] + data['signal_3'] = [3] + data['signal_4'] = [4] + data['signal_5'] = [5] + plugin.execute(data) plugin.__t__ = t if plugin.__t__ < 25: QtCore.QTimer.singleShot(20, lambda : do_fctn(plugin)) + imp_path = "Plot.py" class_name = "Plot" From 656f8ece284a8c1868a95fec4f9200de59006895 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 9 Feb 2015 11:57:10 +0100 Subject: [PATCH 120/260] fixed memory leak caused by signle shot QTimer fixed #25 --- papi/gui/gui_event_processing.py | 8 +++++--- papi/gui/qt_new/main.py | 9 ++++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 76b4739c..c4dc53f1 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -29,6 +29,7 @@ import copy import traceback import importlib.machinery +import time from papi.constants import PLUGIN_STATE_PAUSE, PLUGIN_VIP_IDENTIFIER, PLUGIN_PCP_IDENTIFIER, \ GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_PROCESS_CONSOLE_IDENTIFIER, GUI_WOKRING_INTERVAL, \ @@ -95,7 +96,7 @@ def __init__(self, gui_data, core_queue, gui_id, gui_queue): 'start_plugin': self.process_start_plugin } - def gui_working(self, close_mock): + def gui_working(self, close_mock, workingTimer): """ Event processing loop of gui. Build to get called every 40ms after a run through. Will process all events of the queue at the time of call. @@ -129,10 +130,11 @@ def gui_working(self, close_mock): close_mock() else: self.process_event[op](event) - # after the loop ended, which means that there are no more new events, a new timer will be created to start # this method again in a specific time - QtCore.QTimer.singleShot(GUI_WOKRING_INTERVAL, lambda: self.gui_working(close_mock)) + + workingTimer.start(GUI_WOKRING_INTERVAL) + #QtCore.QTimer.singleShot(GUI_WOKRING_INTERVAL, lambda: self.gui_working(close_mock)) def process_new_data_event(self, event): """ diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index a1ae1b51..e110e3b0 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -33,6 +33,7 @@ import traceback import cProfile import re +import threading from PySide.QtGui import QMainWindow, QApplication, QFileDialog, QDesktopServices from PySide.QtGui import QIcon @@ -220,7 +221,13 @@ def run(self): """ # create a timer and set interval for processing events with working loop - QtCore.QTimer.singleShot(GUI_WOKRING_INTERVAL, lambda: self.gui_event_processing.gui_working(self.closeEvent)) + #QtCore.QTimer.singleShot(GUI_WOKRING_INTERVAL, lambda: self.gui_event_processing.gui_working(self.closeEvent)) + self.workingTimer = QtCore.QTimer(self) + self.workingTimer.timeout.connect(lambda: self.gui_event_processing.gui_working(self.closeEvent, self.workingTimer)) + self.workingTimer.start(GUI_WOKRING_INTERVAL) + + + def show_create_plugin_menu(self): """ From 7c03620e69a74de83fc02fe3f570e8fe481a13ba Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 9 Feb 2015 12:03:00 +0100 Subject: [PATCH 121/260] major improvements --- papi/plugin/visual/Plot/Plot.py | 160 +++++++++++++++++++++----------- papi/plugin/visual/Plot/perf.py | 24 ++--- 2 files changed, 120 insertions(+), 64 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index d8d9b099..86bfe91b 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -54,70 +54,116 @@ def __init__(self, x, y, pen=pg.mkPen('r')): pg.QtGui.QGraphicsPathItem.__init__(self, self.path) self.setPen(pen) self.drawn = False + self.counter = len(y) def shape(self): # override because QGraphicsPathItem.shape is too expensive. return pg.QtGui.QGraphicsItem.shape(self) + def length(self): + return self.counter + def boundingRect(self): return self.path.boundingRect() class PlotItem(object): - def __init__(self,signal_name, buffers, signal_id, signal, slices): + def __init__(self,signal_name, buffer, signal_id, signal, slices): super(PlotItem,self).__init__() self.signal_name = signal_name - self.buffers = buffers + self.buffer = [] # buffer self.id = signal_id self.signal = signal self.position = 0 self.item_ready = False - self.counter = len(buffers[0]) + self.amount_elements = 0 + + self.max_elements = 1000 #len(buffer) + self.pen = None - self.slices = slices + self.slices = 20 #slices self.graphics = [] - self.slice_size = 10 + self.slice_size = 50 + def add_data(self, elements, tdata): tdata = list(tdata) - buffer = self.buffers[0] - - self.counter -= len(elements) - - # It is possible to write all elements to the current buffer - if self.counter >= 0: - buffer.extend(elements) - # Some elements can't be written to the current buffer - if self.counter <= 0: - buffer.extend(elements[:self.counter]) - if len(tdata) < self.slice_size: - x_axis = np.array(tdata)[np.newaxis,:] - else: - x_axis = np.array(tdata[-self.slice_size-1:])[np.newaxis,:] - - self.create_graphic(x_axis) - -# position = (self.position + 1) & self.slices - buffer.extend(elements[self.counter:]) + buffer = self.buffer + + #count elements we are adding + #self.counter += len(elements) + + #append elements to our buffer + buffer.extend(elements) + + +# def add_data_old(self, elements, tdata): +# tdata = list(tdata) +# buffer = self.buffers[0] +# +# self.counter -= len(elements) +# +# # It is possible to write all elements to the current buffer +# if self.counter >= 0: +# buffer.extend(elements) +# # Some elements can't be written to the current buffer +# if self.counter <= 0: +# buffer.extend(elements[:self.counter]) +# if len(tdata) < self.slice_size: +# x_axis = np.array(tdata)[np.newaxis,:] +# else: +# x_axis = np.array(tdata[-self.slice_size-1:])[np.newaxis,:] +# +# self.create_graphic(x_axis) +# +# # position = (self.position + 1) & self.slices +# buffer.extend(elements[self.counter:]) +# +# self.counter = self.slice_size +# + + def create_graphic(self, tdata): + + # get amount of elements in our buffer + counter = len(self.buffer) + self.amount_elements += counter + x_axis = np.array(tdata[-counter:])[np.newaxis,:] + + y_axis = np.array(list(self.buffer)) - self.counter = self.slice_size + graphic = MultiLine(x_axis, y_axis, self.pen) + # print('print new graphic') + # print(len(x_axis[0])) + # print(len(y_axis)) + #Clear buffer + del self.buffer + self.buffer = [] +# self.graphics.append(graphic) - def create_graphic(self, x_axis): - y_axis = np.array(list(self.buffers[0])[-self.slice_size-1:]) - if len(y_axis) == 0: - return None - if len(y_axis) != len(x_axis[0]): - return None + self.graphics.append(graphic) - graphic = MultiLine(x_axis, y_axis, self.pen) + return graphic - self.graphics.append(graphic) #print(self.graphics) + # def create_graphic(self, x_axis): + # + # y_axis = np.array(list(self.buffers[0])[-self.slice_size-1:]) + # + # if len(y_axis) == 0: + # return None + # if len(y_axis) != len(x_axis[0]): + # return None + # + # graphic = MultiLine(x_axis, y_axis, self.pen) + # + # self.graphics.append(graphic) + # #print(self.graphics) + def get_graphic(self, tmp): if len(self.graphics) > 0: @@ -129,8 +175,10 @@ def get_graphic(self, tmp): return None def get_old_graphic(self): - if len(self.graphics) > self.slices: + + if self.amount_elements > self.max_elements: graphic = self.graphics.pop(0) + self.amount_elements -= graphic.length() return graphic return None @@ -258,6 +306,8 @@ def initiate_layer_0(self, config=None): self.__plotWidget__.getPlotItem().getViewBox().disableAutoRange() self.__plotWidget__.getPlotItem().getViewBox().setYRange(0,6) + #self.__plotWidget__.getPlotItem().setDownsampling(auto=True) + if not self.__papi_debug__: self.set_widget_for_internal_usage(self.__plotWidget__) @@ -483,6 +533,7 @@ def update_plot(self): :return: """ + self.__plotWidget__.getPlotItem().getViewBox().disableAutoRange() shift_data = 0 @@ -493,36 +544,43 @@ def update_plot(self): # Set Y-Axis - count = 0 - x_axis = np.array(tdata[-100:])[np.newaxis,:] + # x_axis = np.array(tdata[-100:])[np.newaxis,:] #self.__plotWidget__.getPlotItem().getViewBox().disableAutoRange() + for signal_name in self.signals: #y_axis = np.array(data) # + graphic = self.signals[signal_name].get_old_graphic() + if graphic is not None: + #self.__plotWidget__.clear() + self.__plotWidget__.removeItem(graphic) + + # graphic = self.signals[signal_name].get_graphic(tdata) - graphic = self.signals[signal_name].get_graphic(x_axis) + graphic = self.signals[signal_name].create_graphic(tdata) #line = MultiLine(x_axis, y_axis, pen) - if graphic is not None: - self.__plotWidget__.addItem(graphic) - graphic = self.signals[signal_name].get_old_graphic() + self.__plotWidget__.addItem(graphic) + # + # + # + # if graphic is not None: + # #self.__plotWidget__.clear() + # self.__plotWidget__.removeItem(graphic) - #if graphic is not None: - #self.__plotWidget__.clear() - # self.__plotWidget__.removeItem(graphic) - count += 1 self.__last_plot_time__ = pg.ptime.time()-now - self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0],tdata[-1]) + self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0], tdata[-1]) + + #if self.__papi_debug__: - # if self.__papi_debug__: - # print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) + print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) def update_plot_single_timestamp(self, data): """ @@ -638,13 +696,11 @@ def add_databuffer(self, signal, signal_id): start_size = len(self.__tbuffer__) - buffers = [] - for i in range(0,self.__amount_of_slices__): - buffer = collections.deque([0.0] * start_size, int(self.__buffer_size__ ) ) # COLLECTION - buffers.append(buffer) + buffer = collections.deque([0.0] * start_size, int(self.__buffer_size__ ) ) # COLLECTION + - plot_item = PlotItem(signal_name, buffers,signal_id,signal, self.__amount_of_slices__) + plot_item = PlotItem(signal_name, buffer, signal_id, signal, self.__amount_of_slices__) self.signals[signal_name] = plot_item diff --git a/papi/plugin/visual/Plot/perf.py b/papi/plugin/visual/Plot/perf.py index f7bad4b6..e973cf79 100644 --- a/papi/plugin/visual/Plot/perf.py +++ b/papi/plugin/visual/Plot/perf.py @@ -48,18 +48,18 @@ def do_fctn(plugin): for i in range(10): data['t'] = [t] t += 0.01 - # data['signal_1'] = [random.randint(0, 5)] - # data['signal_2'] = [random.randint(10, 15)] - # data['signal_3'] = [random.randint(20, 25)] - # data['signal_4'] = [random.randint(30, 35)] - # data['signal_5'] = [random.randint(40, 45)] - - - data['signal_1'] = [1] - data['signal_2'] = [2] - data['signal_3'] = [3] - data['signal_4'] = [4] - data['signal_5'] = [5] + data['signal_1'] = [random.randint(0, 5)] + data['signal_2'] = [random.randint(10, 15)] + data['signal_3'] = [random.randint(20, 25)] + data['signal_4'] = [random.randint(30, 35)] + data['signal_5'] = [random.randint(40, 45)] + + + # data['signal_1'] = [1] + # data['signal_2'] = [2] + # data['signal_3'] = [3] + # data['signal_4'] = [4] + # data['signal_5'] = [5] plugin.execute(data) plugin.__t__ = t From 81ec3fd069dbd9b0735987567aade251ac9dd9fa Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 9 Feb 2015 12:22:04 +0100 Subject: [PATCH 122/260] added #25 to changelog --- CHANGENLOG.md | 1 + papi/plugin/visual/Plot/perf.py | 66 --------------------------------- 2 files changed, 1 insertion(+), 66 deletions(-) delete mode 100644 papi/plugin/visual/Plot/perf.py diff --git a/CHANGENLOG.md b/CHANGENLOG.md index b34e1533..0cde184b 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -7,6 +7,7 @@ v.1.XX * [fix]: oboslete parameter in send_parameter_change was removed (#21) * [fix]: clean up of DParameter (#18) * [fix]: renamed some variables of ownProcess_base to be private + * [fix]: fixed memory leak of gui event processing timer loop (#25) * [improvement]: changed the demux function to in imporve performance diff --git a/papi/plugin/visual/Plot/perf.py b/papi/plugin/visual/Plot/perf.py deleted file mode 100644 index a6e29304..00000000 --- a/papi/plugin/visual/Plot/perf.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/python3 -# -*- coding: utf-8 -*- - -""" -Copyright (C) 2014 Technische Universität Berlin, -Fakultät IV - Elektrotechnik und Informatik, -Fachgebiet Regelungssysteme, -Einsteinufer 17, D-10587 Berlin, Germany - -This file is part of PaPI. - -PaPI is free software: you can redistribute it and/or modify -it under the terms of the GNU Lesser General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -PaPI is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public License -along with PaPI. If not, see . - -Contributors: -Sven Knuth -""" - -import sys -import os - -sys.path.insert(0,os.path.abspath('../../../../')) - -print(sys.path) -import papi.pyqtgraph as pq - -from PySide.QtGui import QApplication, QLabel - -import importlib.machinery - -imp_path = "Plot.py" -class_name = "Plot" - -app = QApplication([]) - -loader = importlib.machinery.SourceFileLoader(class_name, imp_path) -current_modul = loader.load_module() - -plugin = getattr(current_modul, class_name)() - -config = plugin.get_plugin_configuration() -plugin.config = config - -# print('1') -# __text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(0, 0)) -# print('2') - - -plugin.initiate_layer_0(config) - - - -window = QLabel('Window from label') -window.show() - -app.exec_() \ No newline at end of file From 786e995261a17c84a4258fb695cc1bf069eaba6a Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 9 Feb 2015 12:48:44 +0100 Subject: [PATCH 123/260] removed deepcopy and fixed resulting problem in unsubscribe_all with list size changing closed #20 --- CHANGENLOG.md | 1 + papi/data/DCore.py | 5 +++-- papi/data/DPlugin.py | 5 ++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 0cde184b..e3560d21 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -9,6 +9,7 @@ v.1.XX * [fix]: renamed some variables of ownProcess_base to be private * [fix]: fixed memory leak of gui event processing timer loop (#25) * [improvement]: changed the demux function to in imporve performance + * [improvement]: imporved core performance while processing new data (see #20) v.1.0 diff --git a/papi/data/DCore.py b/papi/data/DCore.py index 0b69e4ce..cb3c0dca 100644 --- a/papi/data/DCore.py +++ b/papi/data/DCore.py @@ -27,7 +27,7 @@ """ from papi.data.DPlugin import DPlugin from papi.ConsoleLog import ConsoleLog - +import copy import papi.error_codes as ERROR __author__ = 'knuth' @@ -253,7 +253,8 @@ def unsubscribe_all(self, dplugin_id): dplugin = self.get_dplugin_by_id(dplugin_id) - subscribtion_ids = dplugin.get_subscribtions().copy() + # copy subscription for iteration and deletion + subscribtion_ids = copy.deepcopy( dplugin.get_subscribtions() ) #Iterate over all DPlugins, which own a subscribed DBlock for sub_id in subscribtion_ids: diff --git a/papi/data/DPlugin.py b/papi/data/DPlugin.py index d939a2f2..121d2252 100644 --- a/papi/data/DPlugin.py +++ b/papi/data/DPlugin.py @@ -260,13 +260,12 @@ def unsubscribe(self, dblock): def get_subscribtions(self): """ - Returns a copy of a dictionary of all subcribtions. + Returns a reference to a dictionary of all subcribtions. :return {}{} of DPlugin ids to DBlock names : :rtype: {}{} """ - - return copy.deepcopy(self.__subscriptions.copy()) + return self.__subscriptions def add_parameter(self, parameter): """ From 1245884bfec7c185c06bd12c01a62c29452c2ae1 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 9 Feb 2015 13:45:13 +0100 Subject: [PATCH 124/260] changed count --- papi/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papi/constants.py b/papi/constants.py index 06481b3e..9cde874b 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -43,7 +43,7 @@ CORE_ALIVE_CHECK_ENABLED = True CORE_ALIVE_CHECK_INTERVAL = 2 # seconds -CORE_ALIVE_MAX_COUNT = 2 +CORE_ALIVE_MAX_COUNT = 10 PAPI_LAST_CFG_PATH = 'papi/last_active_papi.xml' PAPI_DEFAULT_BG_PATH = 'papi/media/default_bg.png' From f1fe35b4ac2790e6c16b8daedebf05152b4c57ac Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 9 Feb 2015 15:39:53 +0100 Subject: [PATCH 125/260] added tabs --- papi/data/DPlugin.py | 1 + papi/gui/qt_new/PapiTabManger.py | 125 ++++++++++++++++++++++++++++ papi/gui/qt_new/main.py | 19 ++++- papi/ui/gui/qt_new/create.py | 2 +- papi/ui/gui/qt_new/create_dialog.py | 2 +- papi/ui/gui/qt_new/main.py | 12 +-- papi/ui/gui/qt_new/overview.py | 2 +- ui/gui/qt_new/main.ui | 20 ++--- 8 files changed, 161 insertions(+), 22 deletions(-) create mode 100644 papi/gui/qt_new/PapiTabManger.py diff --git a/papi/data/DPlugin.py b/papi/data/DPlugin.py index 121d2252..81f54a7c 100644 --- a/papi/data/DPlugin.py +++ b/papi/data/DPlugin.py @@ -170,6 +170,7 @@ def __init__(self): self.__blocks = {} self.type = None self.alive_count = 0 + self.on_tab = 0 def subscribe_signals(self, dblock, signals): """ diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py new file mode 100644 index 00000000..f6c583e3 --- /dev/null +++ b/papi/gui/qt_new/PapiTabManger.py @@ -0,0 +1,125 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: + - - - - Qt::ScrollBarAsNeeded - - - Qt::ScrollBarAsNeeded - - - + + + + Qt::DefaultContextMenu + + + -1 + + + From 6579e5232100a08b3ba38f1b28e8e95cd7d1ee3a Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 9 Feb 2015 17:46:10 +0100 Subject: [PATCH 126/260] added tabs --- papi/data/DPlugin.py | 2 +- papi/gui/gui_event_processing.py | 6 +- papi/gui/qt_new/PapiTabManger.py | 92 ++++++++++++--------- papi/gui/qt_new/create_plugin_dialog.py | 31 ++++++- papi/gui/qt_new/main.py | 28 ++++--- papi/plugin/base_classes/base_visual.py | 8 +- papi/plugin/base_classes/vip_base.py | 21 +++++ papi/plugin/visual/LCDDisplay/LCDDisplay.py | 3 +- papi/plugin/visual/Plot/Plot.py | 6 ++ 9 files changed, 140 insertions(+), 57 deletions(-) diff --git a/papi/data/DPlugin.py b/papi/data/DPlugin.py index 81f54a7c..2ffc2723 100644 --- a/papi/data/DPlugin.py +++ b/papi/data/DPlugin.py @@ -170,7 +170,7 @@ def __init__(self): self.__blocks = {} self.type = None self.alive_count = 0 - self.on_tab = 0 + def subscribe_signals(self, dblock, signals): """ diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index c4dc53f1..58cd839b 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -59,7 +59,7 @@ class GuiEventProcessing(QtCore.QObject): dgui_changed = QtCore.Signal() plugin_died = QtCore.Signal(DPlugin, Exception, str) - def __init__(self, gui_data, core_queue, gui_id, gui_queue): + def __init__(self, gui_data, core_queue, gui_id, gui_queue, TabManager): """ Init for eventProcessing @@ -81,7 +81,7 @@ def __init__(self, gui_data, core_queue, gui_id, gui_queue): self.plugin_manager = PluginManager() self.plugin_manager.setPluginPlaces(PLUGIN_ROOT_FOLDER_LIST) self.gui_queue = gui_queue - + self.TabManger = TabManager # switch case for event processing self.process_event = {'new_data': self.process_new_data_event, 'close_programm': self.process_close_program_event, @@ -311,7 +311,7 @@ def process_create_plugin(self, event): # call the plugin developers init function with config try: dplugin.plugin.init_plugin(self.core_queue, self.gui_queue, dplugin.id, api, - dpluginInfo=dplugin.get_meta()) + dpluginInfo=dplugin.get_meta(),TabManger=self.TabManger) if dplugin.plugin.start_init(copy.deepcopy(config)) is True: # start succcessfull self.core_queue.put(Event.status.StartSuccessfull(dplugin.id, 0, None)) diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index f6c583e3..a92f7b40 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -44,55 +44,52 @@ def __init__(self, tabWigdet = None, parent=None): self.tabWidget.customContextMenuRequested.connect(self.show_context_menu) self.cmenu = self.create_context_menu() + # make tabs movable self.tabWidget.setMovable(True) + self.tabWidget.setTabsClosable(True) # create dict for saving tabs - self.tab_dict_id = {} self.tab_dict_uname = {} - self.add_tab() - self.add_tab() self.add_tab(name='Tab') - def get_tabs_by_id(self): - return self.tab_dict_id - def get_tabs_by_uname(self): return self.tab_dict_uname - def add_tab(self, name = None): - # check tab name for existance - if name is None: - tab_name = 'Tab' + def add_tab(self, name): + if name in self.tab_dict_uname: + print('Tab with name already exists') else: - tab_name = name + newTab = TabObject( name) + newTab.index = self.tabWidget.addTab(newTab,newTab.name) - # check if unique - while tab_name in self.tab_dict_uname: - tab_name = tab_name + 'X' + self.tab_dict_uname[newTab.name] = newTab + return newTab - area = QtGui.QMdiArea() - id = self.tabWidget.addTab(area,tab_name) - newTab = TabObject(id, area, tab_name) + def rename_tab(self, tabObject, new_name): + if new_name not in self.tab_dict_uname: + # rename it + self.tab_dict_uname.pop(tabObject.name) + tabObject.name = new_name + self.tab_dict_uname[tabObject.name] = tabObject + ind = self.tabWidget.indexOf(tabObject) + self.tabWidget.setTabText(ind,tabObject.name) - if id not in self.tab_dict_id: - self.tab_dict_id[id] = newTab - else: - print('tab id already in list') - if newTab.name not in self.tab_dict_uname: - self.tab_dict_uname[newTab.name] = newTab - else: - print('tab uname already in list') + def get_first_tab(self): + return self.tabWidget.widget(0) - def rename_tab(self, id, new_name ): - self.name = new_name - if id in self.tab_dict: - tab = self.tab_dict[id] - tab.name = new_name - self.tabWidget.setTabText(id,tab.name) + def moveFromTo(self, start, dest, subWindow, posX=0, posY=0): + if start in self.tab_dict_uname and dest in self.tab_dict_uname: + startTab = self.tab_dict_uname[start] + destTab = self.tab_dict_uname[dest] + startTab.removeSubWindow(subWindow) + destTab.addSubWindow(subWindow) + subWindow.move(posX, posY) + subWindow.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowMinMaxButtonsHint | Qt.WindowTitleHint ) + return True def show_context_menu(self, pos): gloPos = self.tabWidget.mapToGlobal(pos) @@ -100,25 +97,46 @@ def show_context_menu(self, pos): def create_context_menu(self): ctrlMenu = QtGui.QMenu("Control") + new_tab_action = QtGui.QAction('New Tab',self.tabWidget) new_tab_action.triggered.connect(self.cmenu_new_tab) + close_tab_action = QtGui.QAction('Close Tab',self.tabWidget) + close_tab_action.triggered.connect(self.cmenu_close_tab) + + rename_tab_action = QtGui.QAction('Rename Tab',self.tabWidget) + rename_tab_action.triggered.connect(self.cmenu_rename_tab) + ctrlMenu.addAction(new_tab_action) ctrlMenu.addAction(close_tab_action) + ctrlMenu.addAction(rename_tab_action) + return ctrlMenu def cmenu_new_tab(self): - print('TODO: new tab') + name = 'Tab' + while name in self.tab_dict_uname: + name = name + 'X' + self.add_tab(name) def cmenu_close_tab(self): - print('TODO: close tab') + ind = self.tabWidget.currentIndex() + + + def cmenu_rename_tab(self): + tabOb = self.tabWidget.currentWidget() + self.rename_tab(tabOb,'NEW NAME') + + + + + -class TabObject(QObject): - def __init__(self, id, widgetArea, name, parent=None): +class TabObject(QtGui.QMdiArea): + def __init__(self, name, parent=None): super(TabObject, self).__init__(parent) - self.id = id - self.widgetArea = widgetArea + self.index = None self.name = name self.background = None diff --git a/papi/gui/qt_new/create_plugin_dialog.py b/papi/gui/qt_new/create_plugin_dialog.py index d90668e6..34f6e8ac 100644 --- a/papi/gui/qt_new/create_plugin_dialog.py +++ b/papi/gui/qt_new/create_plugin_dialog.py @@ -55,6 +55,7 @@ def set_plugin(self, plugin): self.cfg['uname'] = {} self.cfg['uname']['value'] = '' + def accept(self): config = self.cfg @@ -123,11 +124,37 @@ def showEvent(self, *args, **kwargs): position += 1 - startup_config_sorted = sorted(startup_config.items(), key=operator.itemgetter(0)) + if 'tab' in startup_config.keys(): + value = startup_config['tab']['value'] + + display_text = 'Tab' + + if 'display_text' in startup_config['tab'].keys(): + display_text = startup_config['tab']['display_text'] + + #uname = self.gui_api.do_change_string_to_be_uname(self.plugin_name) + #uname = self.gui_api.change_uname_to_uniqe(uname) + + + editable_field = QLineEdit(str(value)) + editable_field.setText(value) + editable_field.setObjectName('Tab' + "_line_edit") + + self.formSimple.addRow(str(display_text) , editable_field) + + self.configuration_inputs['tab'] = editable_field + + + #line_edit.selectAll() + #line_edit.setFocus() + + position += 1 + + startup_config_sorted = sorted(startup_config.items(), key=operator.itemgetter(0)) for attr in startup_config_sorted: attr = attr[0] - if attr != 'uname': + if attr != 'uname' and attr !='tab': value = startup_config[attr]['value'] display_text = attr diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index a61ea967..f329cf5d 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -88,9 +88,11 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): else: self.gui_data = gui_data + self.TabManager = PapiTabManger(self.widgetTabs) + self.gui_api = Gui_api(self.gui_data, core_queue, gui_id) - self.gui_event_processing = GuiEventProcessing(self.gui_data, core_queue, gui_id, gui_queue) + self.gui_event_processing = GuiEventProcessing(self.gui_data, core_queue, gui_id, gui_queue, TabManager=self.TabManager) self.gui_event_processing.added_dplugin.connect(self.add_dplugin) self.gui_event_processing.removed_dplugin.connect(self.remove_dplugin) @@ -103,7 +105,7 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): self.setWindowTitle(GUI_PAPI_WINDOW_TITLE) - self.TabManager = PapiTabManger(self.widgetTabs) + self.gui_api.set_bg_gui.connect(self.update_background) @@ -336,19 +338,19 @@ def add_dplugin(self, dplugin): """ if dplugin.type == PLUGIN_VIP_IDENTIFIER or dplugin.type == PLUGIN_PCP_IDENTIFIER: sub_window = dplugin.plugin.get_sub_window() - if dplugin.on_tab == 0: - area = self.TabManager.get_tabs_by_id()[0].widgetArea + config = dplugin.startup_config + tab_name = config['tab']['value'] + if tab_name in self.TabManager.get_tabs_by_uname(): + area = self.TabManager.get_tabs_by_uname()[tab_name] else: - tab_name = dplugin.on_tab - if tab_name in self.TabManager.get_tabs_by_uname(): - area = self.TabManager.get_tabs_by_uname()[tab_name].widgetArea - else: - self.log.printText(1,'add dplugin: no tab with tab_id of dplugin') + self.log.printText(1,'add dplugin: no tab with tab_id of dplugin') + area = self.TabManager.add_tab(tab_name) + area.addSubWindow(sub_window) sub_window.show() size_re = re.compile(r'([0-9]+)') - config = dplugin.startup_config + pos = config['position']['value'] window_pos = size_re.findall(pos) sub_window.move(int(window_pos[0]), int(window_pos[1])) @@ -369,7 +371,11 @@ def remove_dplugin(self, dplugin): :return: """ if dplugin.type == PLUGIN_VIP_IDENTIFIER or dplugin.type == PLUGIN_PCP_IDENTIFIER: - self.widgetArea.removeSubWindow(dplugin.plugin.get_sub_window()) + config = dplugin.startup_config + tab_name = config['tab']['value'] + if tab_name in self.TabManager.get_tabs_by_uname(): + tabOb = self.TabManager.get_tabs_by_uname()[tab_name] + tabOb.removeSubWindow(dplugin.plugin.get_sub_window()) def changed_dgui(self): if self.overview_menu is not None: diff --git a/papi/plugin/base_classes/base_visual.py b/papi/plugin/base_classes/base_visual.py index 1281f542..a9a0ec7f 100644 --- a/papi/plugin/base_classes/base_visual.py +++ b/papi/plugin/base_classes/base_visual.py @@ -34,14 +34,14 @@ class base_visual(base_plugin): - def init_plugin(self, CoreQueue, pluginQueue, id, control_api, dpluginInfo = None): + def init_plugin(self, CoreQueue, pluginQueue, id, control_api, dpluginInfo = None,TabManger = None): super(base_visual, self).papi_init() self._Core_event_queue__ = CoreQueue self.__plugin_queue__ = pluginQueue self.__id__ = id self.control_api = control_api self.dplugin_info = dpluginInfo - + self.TabManager = TabManger def start_init(self, config=None): self.config = config @@ -81,6 +81,10 @@ def get_configuration_base(self): 'name': { 'value': 'VisualPlugin', 'tooltip': 'Used for window title' + }, + 'tab': { + 'value': 'Tab33', + 'tooltip': 'Used for tabs' }} return config diff --git a/papi/plugin/base_classes/vip_base.py b/papi/plugin/base_classes/vip_base.py index f6a57468..7e88c62c 100644 --- a/papi/plugin/base_classes/vip_base.py +++ b/papi/plugin/base_classes/vip_base.py @@ -58,12 +58,33 @@ def create_control_context_menu(self): subMenu_action = QtGui.QAction('Open Signal Manager',self.widget) #subMenu_action.triggered.connect(self.ctlrMenu_resume) + + tabMenu = QtGui.QMenu('Move to') + tabs = list(self.TabManager.get_tabs_by_uname().keys()) + tab_entrys = [] + for t in tabs: + if t != self.config['tab']['value']: + entry = QtGui.QAction(t, self.widget) + entry.triggered.connect(lambda p=t: self.tabMenu_triggered(p)) + tab_entrys.append(entry) + tabMenu.addAction(entry) + + + ctrlMenu.addMenu(tabMenu) ctrlMenu.addAction(subMenu_action) ctrlMenu.addAction(resume_action) ctrlMenu.addAction(pause_action) ctrlMenu.addAction(del_action) return ctrlMenu + def tabMenu_triggered(self, item): + pos = self._subWindow.pos() + posX = pos.x() + posY = pos.y() + if self.TabManager.moveFromTo(self.config['tab']['value'], item, self._subWindow, posX=posX, posY=posY): + self.config['tab']['value'] = item + + def ctlrMenu_exit(self): self.control_api.do_delete_plugin_uname(self.dplugin_info.uname) diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.py b/papi/plugin/visual/LCDDisplay/LCDDisplay.py index bab4b31a..b6841fd7 100644 --- a/papi/plugin/visual/LCDDisplay/LCDDisplay.py +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.py @@ -68,7 +68,7 @@ def initiate_layer_0(self, config=None): # This call is important, because the background structure needs to know the used widget! # In the background the qmidiwindow will becreated and the widget will be added self.set_widget_for_internal_usage( self.LcdWidget ) - self.cmenu = self.create_control_context_menu() + # --------------------------- # Create Parameters @@ -98,6 +98,7 @@ def initiate_layer_0(self, config=None): def show_context_menu(self, pos): gloPos = self.LcdWidget.mapToGlobal(pos) + self.cmenu = self.create_control_context_menu() self.cmenu.exec_(gloPos) def pause(self): diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 7ff38e71..ac0c633f 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -196,6 +196,8 @@ def initiate_layer_0(self, config=None): self.__update_intervall__ = 25 # in milliseconds self.setup_context_menu() + self.__plotWidget__.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.__plotWidget__.customContextMenuRequested.connect(self.showContextMenu) #self.use_range_for_x(self.config['xRange']['value']) self.use_range_for_y(self.config['yRange']['value']) @@ -695,6 +697,10 @@ def setup_context_menu(self): self.__plotWidget__.getPlotItem().getViewBox().menu.clear() self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] + def showContextMenu(self): + self.setup_context_menu() + + def contextMenu_yAutoRangeButton_clicked(self): mi = None; ma = None; From 6e4264a37a9844ff3533ecf1cbaba3f689260b98 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 9 Feb 2015 18:13:30 +0100 Subject: [PATCH 127/260] added tabs --- papi/gui/qt_new/PapiTabManger.py | 27 ++++++++++++++++++++++++++- papi/gui/qt_new/main.py | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index a92f7b40..8219de17 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -32,10 +32,12 @@ from PySide.QtCore import * from papi.pyqtgraph import QtCore, QtGui +from papi.constants import PLUGIN_PCP_IDENTIFIER, PLUGIN_IOP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER, PLUGIN_DPP_IDENTIFIER + class PapiTabManger(QObject): - def __init__(self, tabWigdet = None, parent=None): + def __init__(self, tabWigdet = None, dgui = None, parent=None): super(PapiTabManger, self).__init__(parent) self.tabWidget = tabWigdet @@ -44,7 +46,9 @@ def __init__(self, tabWigdet = None, parent=None): self.tabWidget.customContextMenuRequested.connect(self.show_context_menu) self.cmenu = self.create_context_menu() + self.tabWidget.tabCloseRequested.connect(self.closeTab) + self.dGui = dgui # make tabs movable self.tabWidget.setMovable(True) self.tabWidget.setTabsClosable(True) @@ -68,6 +72,13 @@ def add_tab(self, name): return newTab + def remove_tab(self,tabObject): + self.tab_dict_uname.pop(tabObject.name) + ind = self.tabWidget.indexOf(tabObject) + self.tabWidget.removeTab(ind) + tabObject.destroy() + + def rename_tab(self, tabObject, new_name): if new_name not in self.tab_dict_uname: # rename it @@ -122,7 +133,21 @@ def cmenu_new_tab(self): def cmenu_close_tab(self): ind = self.tabWidget.currentIndex() + self.closeTab(ind) + + def closeTab(self, ind): + tabOb = self.tabWidget.widget(ind) + tab_name = tabOb.name + + plugins = self.dGui.get_all_plugins() + for pl_id in plugins: + plugin = plugins[pl_id] + if plugin.type == PLUGIN_VIP_IDENTIFIER or plugin.type == PLUGIN_PCP_IDENTIFIER: + if plugin.startup_config['tab']['value'] == tab_name: + self.moveFromTo(tab_name,self.get_first_tab().name, plugin.plugin.get_sub_window()) + plugin.startup_config['tab']['value'] = self.get_first_tab() + self.remove_tab(tabOb) def cmenu_rename_tab(self): tabOb = self.tabWidget.currentWidget() diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index f329cf5d..775376b0 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -88,7 +88,7 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): else: self.gui_data = gui_data - self.TabManager = PapiTabManger(self.widgetTabs) + self.TabManager = PapiTabManger(self.widgetTabs,dgui=self.gui_data) self.gui_api = Gui_api(self.gui_data, core_queue, gui_id) From a498e4c954c68bc64b3b08d896c6775db15d52d8 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 9 Feb 2015 18:24:01 +0100 Subject: [PATCH 128/260] added tabs --- papi/gui/qt_new/PapiTabManger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index 8219de17..97ff07e9 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -143,7 +143,7 @@ def closeTab(self, ind): for pl_id in plugins: plugin = plugins[pl_id] if plugin.type == PLUGIN_VIP_IDENTIFIER or plugin.type == PLUGIN_PCP_IDENTIFIER: - if plugin.startup_config['tab']['value'] == tab_name: + if plugin.plugin.config['tab']['value'] == tab_name: self.moveFromTo(tab_name,self.get_first_tab().name, plugin.plugin.get_sub_window()) plugin.startup_config['tab']['value'] = self.get_first_tab() From 3a5902a9a2a87463b9645d6a3ff7cf18e461f256 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 9 Feb 2015 18:35:47 +0100 Subject: [PATCH 129/260] Merge branch 'development' of https://github.com/TUB-Control/PaPI into plot_performance Conflicts: papi/plugin/visual/Plot/Plot.py papi/plugin/visual/PlotPerf/PlotPerf.py major improvements --- cfg_collection/cpu_load_example.xml | 2 +- papi/plugin/visual/Plot/Plot.py | 605 +++++++++++++++++----------- 2 files changed, 368 insertions(+), 239 deletions(-) diff --git a/cfg_collection/cpu_load_example.xml b/cfg_collection/cpu_load_example.xml index 31234bec..91b9abfe 100644 --- a/cfg_collection/cpu_load_example.xml +++ b/cfg_collection/cpu_load_example.xml @@ -92,7 +92,7 @@ Buffersize - 500 + 50 ^([1-9][0-9]{0,3}|10000)$ 1 diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 462317e6..545ca56d 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -46,144 +46,6 @@ from PySide.QtGui import QRegExpValidator from PySide.QtCore import * - -class MultiLine(pg.QtGui.QGraphicsPathItem): - def __init__(self, x, y, pen=pg.mkPen('r')): - """x and y are 2D arrays of shape (Nplots, Nsamples)""" - connect = np.ones(x.shape, dtype=bool) - connect[:,-1] = 0 # don't draw the segment between each trace - self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten()) - pg.QtGui.QGraphicsPathItem.__init__(self, self.path) - self.setPen(pen) - self.drawn = False - self.counter = len(y) - - def shape(self): # override because QGraphicsPathItem.shape is too expensive. - return pg.QtGui.QGraphicsItem.shape(self) - - def length(self): - return self.counter - - def boundingRect(self): - return self.path.boundingRect() - - -class PlotItem(object): - - def __init__(self,signal_name, buffer, signal_id, signal, slices): - super(PlotItem,self).__init__() - self.signal_name = signal_name - self.buffer = [] # buffer - self.id = signal_id - self.signal = signal - self.position = 0 - self.item_ready = False - self.amount_elements = 0 - - self.max_elements = 1000 #len(buffer) - - self.pen = None - self.slices = 20 #slices - self.graphics = [] - self.slice_size = 50 - - - def add_data(self, elements, tdata): - tdata = list(tdata) - buffer = self.buffer - - #count elements we are adding - #self.counter += len(elements) - - #append elements to our buffer - buffer.extend(elements) - - -# def add_data_old(self, elements, tdata): -# tdata = list(tdata) -# buffer = self.buffers[0] -# -# self.counter -= len(elements) -# -# # It is possible to write all elements to the current buffer -# if self.counter >= 0: -# buffer.extend(elements) -# # Some elements can't be written to the current buffer -# if self.counter <= 0: -# buffer.extend(elements[:self.counter]) -# if len(tdata) < self.slice_size: -# x_axis = np.array(tdata)[np.newaxis,:] -# else: -# x_axis = np.array(tdata[-self.slice_size-1:])[np.newaxis,:] -# -# self.create_graphic(x_axis) -# -# # position = (self.position + 1) & self.slices -# buffer.extend(elements[self.counter:]) -# -# self.counter = self.slice_size -# - - def create_graphic(self, tdata): - - # get amount of elements in our buffer - counter = len(self.buffer) - self.amount_elements += counter - x_axis = np.array(tdata[-counter:])[np.newaxis,:] - - y_axis = np.array(list(self.buffer)) - - graphic = MultiLine(x_axis, y_axis, self.pen) - - # print('print new graphic') - # print(len(x_axis[0])) - # print(len(y_axis)) - #Clear buffer - - del self.buffer - self.buffer = [] -# self.graphics.append(graphic) - - - - self.graphics.append(graphic) - - return graphic - - #print(self.graphics) - - # def create_graphic(self, x_axis): - # - # y_axis = np.array(list(self.buffers[0])[-self.slice_size-1:]) - # - # if len(y_axis) == 0: - # return None - # if len(y_axis) != len(x_axis[0]): - # return None - # - # graphic = MultiLine(x_axis, y_axis, self.pen) - # - # self.graphics.append(graphic) - # #print(self.graphics) - - def get_graphic(self, tmp): - - if len(self.graphics) > 0: - graphic = self.graphics[-1] - - if not graphic.drawn: - graphic.drawn = True - return graphic - return None - - def get_old_graphic(self): - - if self.amount_elements > self.max_elements: - graphic = self.graphics.pop(0) - self.amount_elements -= graphic.length() - return graphic - return None - class Plot(vip_base): """ style_codes: @@ -264,7 +126,6 @@ def initiate_layer_0(self, config=None): :return: """ - # --------------------------- # Read configuration # --------------------------- @@ -278,10 +139,9 @@ def initiate_layer_0(self, config=None): self.__styles_selected__ = int_re.findall(self.config['style']['value']) self.__buffer_size__ = int(int_re.findall(self.config['buffersize']['value'])[0]) - self.__buffer_size__ = 1000 + #self.__buffer_size__ = 1000 self.__downsampling_rate__ = int(int_re.findall(self.config['downsampling_rate']['value'])[0]) - # ---------------------------- # Create internal variables # ---------------------------- @@ -308,12 +168,12 @@ def initiate_layer_0(self, config=None): self.__plotWidget__.getPlotItem().getViewBox().disableAutoRange() self.__plotWidget__.getPlotItem().getViewBox().setYRange(0,6) - #self.__plotWidget__.getPlotItem().setDownsampling(auto=True) + self.__plotWidget__.getPlotItem().setDownsampling(auto=True) if not self.__papi_debug__: self.set_widget_for_internal_usage(self.__plotWidget__) self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.YAxis, enable=False) self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.XAxis, enable=False) - + #self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=False, y=True) # --------------------------- # Create Parameters # --------------------------- @@ -343,7 +203,7 @@ def initiate_layer_0(self, config=None): self.__last_time__ = current_milli_time() - self.__update_intervall__ = 100 # in milliseconds + self.__update_intervall__ = 20 # in milliseconds self.__last_plot_time__ = 0 self.setup_context_menu() @@ -359,6 +219,7 @@ def pause(self): :return: """ + self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=True, y=True) print('PlotPerformance paused') def resume(self): @@ -367,6 +228,7 @@ def resume(self): :return: """ + self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=False, y=True) print('PlotPerformance resumed') def execute(self, Data=None, block_name=None): @@ -393,14 +255,11 @@ def execute(self, Data=None, block_name=None): if key in self.signals: self.signals[key].add_data(y, self.__tbuffer__) - # buffer = self.signals[key]['buffer'] - # buffer.extend(y) self.__signals_have_same_length &= (len(t) == len(y)) if self.__input_size__ > 1 or self.__signals_have_same_length: if current_milli_time() - self.__last_time__ > self.__update_intervall__ - self.__last_plot_time__: - self.__last_time__ = current_milli_time() self.update_plot() self.__last_time__ = current_milli_time() @@ -408,7 +267,7 @@ def execute(self, Data=None, block_name=None): else: self.update_plot_single_timestamp(Data) - #print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) +# print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) def set_parameter(self, name, value): """ @@ -434,31 +293,30 @@ def set_parameter(self, name, value): #self.__plotWidget__.getPlotItem().setDownsampling(ds=int(value),auto=False,mode='mean') self.__new_added_data__ = 0 - if name == 'rolling': - self.__rolling_plot__ = int(float(value)) == int('1') + self.clear() self.config['rolling_plot']['value'] = value - self.rolling_Checkbox.setChecked(value=='1') - if self.__rolling_plot__: - # if self.__vertical_line__ not in self.__plotWidget__.listDataItems(): - - self.__plotWidget__.addItem(self.__vertical_line__) + self.update_rolling_plot() if name == 'color': + self.clear() self.config['color']['value'] = value int_re = re.compile(r'(\d+)') self.__colors_selected__ = int_re.findall(self.config['color']['value']) self.update_pens() + self.update_legend() if name == 'style': + self.clear() self.config['style']['value'] = value int_re = re.compile(r'(\d+)') self.__styles_selected__ = int_re.findall(self.config['style']['value']) self.update_pens() + self.update_legend() if name == 'buffersize': self.config['buffersize']['value'] = value - self.set_buffer_size(value) + self.update_buffer_size(value) # if name == 'xRange': # self.config['xRange']['value'] = value @@ -488,54 +346,46 @@ def update_plot(self): :return: """ - self.__plotWidget__.getPlotItem().getViewBox().disableAutoRange() - - shift_data = 0 - - tdata = list(self.__tbuffer__) - now = pg.ptime.time() - - #self.__plotWidget__.clear() - - # Set Y-Axis + self.__plotWidget__.getPlotItem().setDownsampling(ds=30, auto=False) + if not self.__rolling_plot__: + tdata = list(self.__tbuffer__) + if self.__rolling_plot__: + tdata = list(range(0, len(self.__tbuffer__))) - # x_axis = np.array(tdata[-100:])[np.newaxis,:] - - #self.__plotWidget__.getPlotItem().getViewBox().disableAutoRange() + self.__append_at__ += self.signals[list(self.signals.keys())[0]].get_buffersize() + self.__append_at__ %= len(tdata) + tdata = np.roll(tdata, -int(self.__append_at__)) + now = pg.ptime.time() for signal_name in self.signals: - - #y_axis = np.array(data) - # - graphic = self.signals[signal_name].get_old_graphic() - if graphic is not None: - #self.__plotWidget__.clear() + # get all no more needed graphic items + graphics = self.signals[signal_name].get_old_graphics() + for graphic in graphics: self.__plotWidget__.removeItem(graphic) - # graphic = self.signals[signal_name].get_graphic(tdata) - - graphic = self.signals[signal_name].create_graphic(tdata) - #line = MultiLine(x_axis, y_axis, pen) + # Create new new graphic items + self.signals[signal_name].create_graphics(tdata) - self.__plotWidget__.addItem(graphic) - # - # - # - # if graphic is not None: - # #self.__plotWidget__.clear() - # self.__plotWidget__.removeItem(graphic) + # Get new created graphic item and paint them + graphics = self.signals[signal_name].get_graphics() + for graphic in graphics: + self.__plotWidget__.addItem(graphic) + if self.__rolling_plot__: + self.__vertical_line__.setValue(graphic.last_x) self.__last_plot_time__ = pg.ptime.time()-now - self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0], tdata[-1]) - - #if self.__papi_debug__: + if self.__rolling_plot__: + self.__plotWidget__.getPlotItem().getViewBox().setXRange(0, len(tdata)-1) + else: + self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0], tdata[-1]) - print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) + # if self.__papi_debug__: + # print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) def update_plot_single_timestamp(self, data): """ @@ -550,14 +400,15 @@ def update_plot_single_timestamp(self, data): if signal_name != 't': signal_data = data[signal_name] if signal_name in self.signals: - tdata = np.linspace(1, len(signal_data), len(signal_data)) + pass + #tdata = np.linspace(1, len(signal_data), len(signal_data)) - curve = self.signals[signal_name]['curve'] - curve.setData(tdata, signal_data, _callSync='off') + #curve = self.signals[signal_name]['curve'] + #curve.setData(tdata, signal_data, _callSync='off') pass - def set_buffer_size(self, new_size): + def update_buffer_size(self, new_size): """ Function set buffer size @@ -581,12 +432,14 @@ def set_buffer_size(self, new_size): start_size = len(self.__tbuffer__) for signal_name in self.signals: - buffer_new = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION + self.__tbuffer__ = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION -# buffer_old = self.signals[signal_name]['buffer'] - # buffer_new.extend(buffer_old) + plot_item = self.signals[signal_name] + plot_item.max_elements = self.__buffer_size__ + plot_item.clear() + self.__plotWidget__.clear() -# self.signals[signal_name]['buffer'] = buffer_new + self.update_rolling_plot() self.__new_added_data__ = 0 @@ -596,8 +449,9 @@ def plugin_meta_updated(self): :return: """ - subscriptions = self.dplugin_info.get_subscribtions() + subscriptions = self.dplugin_info.get_subscribtions() + changes = False current_signals = {} index = 0 @@ -621,26 +475,26 @@ def plugin_meta_updated(self): for signal_name in sorted(current_signals.keys()): if signal_name not in self.signals: signal = current_signals[signal_name]['signal'] - self.add_databuffer(signal, current_signals[signal_name]['index']) - print('Add ' + signal_name) + self.add_plot_item(signal, current_signals[signal_name]['index']) + changes = True # Delete old buffers for signal_name in self.signals.copy(): if signal_name not in current_signals: - signal = self.signals[signal_name]['signal'] - self.remove_databuffer(signal) - print('Remove ' + signal_name) - + self.remove_plot_item(signal_name) + changes = True - self.update_pens() - #self.update_legend() + if changes: + self.update_pens() + self.update_legend() + self.update_rolling_plot() - def add_databuffer(self, signal, signal_id): + def add_plot_item(self, signal, signal_id): """ - Create new buffer for signal_name. + Create a new plot item object for a given signal and internal id - :param signal_name: - :param signal_id: + :param signal: DSignal object + :param signal_id: plot internal signal id :return: """ @@ -649,30 +503,30 @@ def add_databuffer(self, signal, signal_id): if signal_name not in self.signals: self.signals[signal_name] = {} - start_size = len(self.__tbuffer__) - - - buffer = collections.deque([0.0] * start_size, int(self.__buffer_size__ ) ) # COLLECTION - - - plot_item = PlotItem(signal_name, buffer, signal_id, signal, self.__amount_of_slices__) + plot_item = PlotItem(signal, signal_id, self.__buffer_size__) + plot_item.set_downsampling_rate(self.__downsampling_rate__) self.signals[signal_name] = plot_item - def remove_databuffer(self, signal): + + def remove_plot_item(self, signal_name): """ - Remove the databuffer for signal_name. + Remove the plot item object for a given signal_name. :param signal_name: :return: """ - signal_name = signal.uname - if signal_name in self.signals: - # curve = self.signals[signal_name]['curve'] - # curve.clear() - # self.__legend__.removeItem(legend_name) + plot_item = self.signals[signal_name] + + # Remove all graphic objects + + for graphic in plot_item.graphics: + self.__plotWidget__.removeItem(graphic) + + # Remove from Legend + self.__legend__.removeItem(plot_item.signal_name) del self.signals[signal_name] def get_pen(self, index): @@ -702,7 +556,31 @@ def get_pen(self, index): return pq.mkPen(color=color, style=style) + def update_rolling_plot(self): + + value = self.config['rolling_plot']['value'] + + self.__rolling_plot__ = int(float(self.config['rolling_plot']['value'])) == int('1') + + self.rolling_Checkbox.setChecked(value == '1') + + self.clear() + + for signal_name in self.signals: + self.signals[signal_name].rolling_plot = self.__rolling_plot__ + + if self.__rolling_plot__: + # if self.__vertical_line__ not in self.__plotWidget__.listDataItems(): + + self.__plotWidget__.addItem(self.__vertical_line__) + + def use_range_for_x(self, value): + """ + + :param value: + :return: + """ reg = re.compile(r'(\d+\.\d+)') range = reg.findall(value) if len(range) == 2: @@ -711,6 +589,11 @@ def use_range_for_x(self, value): self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]),float(range[1])) def use_range_for_y(self, value): + """ + + :param value: + :return: + """ reg = re.compile(r'([-]{0,1}\d+\.\d+)') range = reg.findall(value) if len(range) == 2: @@ -720,6 +603,10 @@ def use_range_for_y(self, value): def setup_context_menu(self): + """ + + :return: + """ self.custMenu = QtGui.QMenu("Options") self.axesMenu = QtGui.QMenu('Y-Axis') self.gridMenu = QtGui.QMenu('Grid') @@ -872,11 +759,18 @@ def setup_context_menu(self): self.custMenu.addAction(self.rolling_Checkbox_Action) self.__plotWidget__.getPlotItem().getViewBox().menu.clear() + if not self.__papi_debug__: + self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] + def contextMenu_yAutoRangeButton_clicked(self): mi = None; ma = None; for sig in self.signals: - buf = self.signals[sig]['buffer'] + graphics = self.signals[sig].graphics + buf = [] + for graphic in graphics: + buf.extend(graphic.y) + ma_buf = max(buf) mi_buf = min(buf) if ma is not None: @@ -933,7 +827,7 @@ def update_signals(self): for signal_name in subscription.get_signals(): signal = subscription.get_dblock().get_signal_by_uname(signal_name) - self.signals[signal_name]['signal'] = signal + self.signals[signal_name].update_signal(signal) def update_legend(self): # self.__plotWidget__.removeItem(self.__legend__) @@ -943,14 +837,18 @@ def update_legend(self): self.__legend__ = pq.LegendItem((100, 40), offset=(40, 1)) # args are (size, offset) self.__legend__.setParentItem(self.__plotWidget__.graphicsItem()) - self.update_signals() + if not self.__papi_debug__: + self.update_signals() for signal_name in sorted(self.signals.keys()): - curve = self.signals[signal_name]['curve'] - signal = self.signals[signal_name]['signal'] - legend_name = signal.dname - self.__legend__.addItem(curve, legend_name) + graphic = self.signals[signal_name].get_legend_item() + + if graphic is not None: + signal = self.signals[signal_name].signal + legend_name = signal.dname + + self.__legend__.addItem(graphic, legend_name) def quit(self): """ @@ -972,13 +870,14 @@ def debug_papi(self): signal_4 = DSignal('signal_4') signal_5 = DSignal('signal_5') - self.add_databuffer(signal_1, 1) - self.add_databuffer(signal_2, 2) - self.add_databuffer(signal_3, 3) - self.add_databuffer(signal_4, 4) - self.add_databuffer(signal_5, 5) + self.add_plot_item(signal_1, 1) + self.add_plot_item(signal_2, 2) + self.add_plot_item(signal_3, 3) + self.add_plot_item(signal_4, 4) + self.add_plot_item(signal_5, 5) self.update_pens() + self.update_legend() pass @@ -1044,4 +943,234 @@ def get_plugin_configuration(self): } } # http://www.regexr.com/ - return config \ No newline at end of file + return config + + def clear(self): + """ + + :return: + """ + self.__plotWidget__.clear() + self.__tbuffer__.clear() + + for signal_name in self.signals: + self.signals[signal_name].clear() + +class GraphicItem(pg.QtGui.QGraphicsPathItem): + """ + + """ + def __init__(self, x, y, counter, pen=pg.mkPen('r')): + """ + + :param x: + :param y: + :param pen: + :return: + """ + + connect = np.ones(x.shape, dtype=bool) + connect[:, -1] = 0 # don't draw the segment between each trace + self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten()) + pg.QtGui.QGraphicsPathItem.__init__(self, self.path) + self.setPen(pen) + self.not_drawn = True + self.counter = counter + self.y = y + self.last_x = x[0][-1] + self.last_y = y[-1] + + def shape(self): # override because QGraphicsPathItem.shape is too expensive. + return pg.QtGui.QGraphicsItem.shape(self) + + def length(self): + return self.counter + + def boundingRect(self): + return self.path.boundingRect() + + +class PlotItem(object): + """ + This object is used to manage the graphic items for a single signal object. + """ + + def __init__(self, signal, signal_id, max_elements): + """ + Initialized by setting a corresponding DSignal object and an internal signal_id + + :param signal: DSignal object + :param signal_id: Plot internal ID + :return: + """ + super(PlotItem,self).__init__() + self.signal_name = signal.dname + self.buffer = [] # buffer + self.id = signal_id + self.signal = signal + + self.amount_elements = 0 + + self.downsampling_rate_start = None; + + self.max_elements = max_elements + + self.pen = None + self.graphics = [] + self.rolling_plot = None + self.last_x = None + self.last_y = None + + def get_legend_item(self): + """ + Returns an item for a legend with the correct pen style + + :return: + """ + + data_item = pg.PlotDataItem() + data_item.setPen(self.pen) + return data_item + + + def add_data(self, elements, tdata): + """ + + :param elements: + :param tdata: + :return: + """ + + buffer = self.buffer + + + # if self.downsampling_rate_start < len(elements): + # ds_elements = elements[self.downsampling_rate_start:self.downsampling_rate:] + # + # if self.downsampling_rate > len(elements): + # self.downsampling_rate_start = self.downsampling_rate - len(elements) + # else: + # self.downsampling_rate_start -= len(elements) + + buffer.extend(elements) + + def update_signal(self, new_signal): + """ + + :param new_signal: + :return: + """ + + self.signal = new_signal + self.signal_name = new_signal.dname + + def set_downsampling_rate(self, rate): + + self.downsampling_rate = 2 + + self.downsampling_rate_start = 0 + + + + def create_graphics(self, tdata): + + # get amount of elements in our buffer + counter = len(self.buffer) + self.amount_elements += counter + + x_axis = np.array(tdata[-counter:])[np.newaxis,:] + y_axis = np.array(list(self.buffer)) + + # TODO: Downsampling before graphic creation + + if self.rolling_plot: + #print('I keep it rolling') + + i_max = np.argmax(x_axis[0]) + i_min = np.argmin(x_axis[0]) + + # Create two graphic objects due plot border + if i_min > i_max: + x_axis_1 = np.array(x_axis[0][:i_max+1])[np.newaxis, :] + x_axis_2 = np.array(x_axis[0][i_min:])[np.newaxis, :] + + y_axis_1 = y_axis[:i_max+1] + y_axis_2 = y_axis[i_min:] + + + if len(self.graphics): + prev_graphic = self.graphics[-1] + prev_last_x = prev_graphic.last_x; + prev_last_y = prev_graphic.last_y; + + x_axis_1 = np.insert(x_axis_1[0], 0, prev_last_x)[np.newaxis,:] + y_axis_1 = np.insert(y_axis_1, 0, prev_last_y) + + + graphic_1 = GraphicItem(x_axis_1, y_axis_1, len(y_axis_1) - 1, self.pen) + graphic_2 = GraphicItem(x_axis_2, y_axis_2, len(y_axis_2), self.pen) + + self.graphics.append(graphic_1) + self.graphics.append(graphic_2) + + del self.buffer + self.buffer = [] + + return + # Connect with previous graphic if a previous graphic exists + + if len(self.graphics): + prev_graphic = self.graphics[-1] + prev_last_x = prev_graphic.last_x; + prev_last_y = prev_graphic.last_y; + + if prev_last_x < x_axis[0][0]: + x_axis = np.insert(x_axis[0], 0, prev_last_x)[np.newaxis,:] + y_axis = np.insert(y_axis, 0, prev_last_y) + + graphic = GraphicItem(x_axis, y_axis, counter, pen=self.pen) + + del self.buffer + self.buffer = [] + + self.graphics.append(graphic) + + def get_graphics(self): + res_graphics = [] + + for graphic in self.graphics: + + if graphic.not_drawn: + res_graphics.append(graphic) + graphic.not_drawn = False + + return res_graphics + + def get_old_graphics(self): + res_graphic = [] + + # print(self.amount_elements) + # print(len(self.graphics)) + + while self.amount_elements >= self.max_elements: + + graphic = self.graphics[0] + + if graphic.not_drawn: + break + + graphic = self.graphics.pop(0) + self.amount_elements -= graphic.length() + + res_graphic.append(graphic) + + return res_graphic + + def clear(self): + self.graphics = [] + self.downsampling_rate_start = 0 + self.buffer = [] + self.amount_elements = 0 + + def get_buffersize(self): + return len(self.buffer) From c70a483305005c80e5c876320a30567ffe49e05b Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Wed, 11 Feb 2015 11:56:02 +0100 Subject: [PATCH 130/260] added tabs --- papi/gui/qt_new/PapiTabManger.py | 47 +++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index 97ff07e9..81354aca 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -89,8 +89,15 @@ def rename_tab(self, tabObject, new_name): self.tabWidget.setTabText(ind,tabObject.name) - def get_first_tab(self): - return self.tabWidget.widget(0) + def get_default_tab(self, NotThisIndex): + if NotThisIndex == 0: + if self.tabWidget.count() > 1: + return self.tabWidget.widget(1) + else: + return self.add_tab('Default') + else: + return self.tabWidget.widget(0) + def moveFromTo(self, start, dest, subWindow, posX=0, posY=0): if start in self.tab_dict_uname and dest in self.tab_dict_uname: @@ -138,16 +145,24 @@ def cmenu_close_tab(self): def closeTab(self, ind): tabOb = self.tabWidget.widget(ind) tab_name = tabOb.name + if tab_name != 'Default': + # not the default tab, just close and move plugins to default tab + plugins = self.dGui.get_all_plugins() + for pl_id in plugins: + plugin = plugins[pl_id] + if plugin.type == PLUGIN_VIP_IDENTIFIER or plugin.type == PLUGIN_PCP_IDENTIFIER: + if plugin.plugin.config['tab']['value'] == tab_name: + self.moveFromTo(tab_name,self.get_default_tab(ind).name, plugin.plugin.get_sub_window()) + plugin.plugin.config['tab']['value'] = self.get_default_tab(ind).name + + self.remove_tab(tabOb) + else: + # default tab: ask if really want to close + diag = DefaultCloseBox()#QtGui.QDialog() + ret = diag.exec_() + if ret == QtGui.QMessageBox.Ok: + print('close default tab') - plugins = self.dGui.get_all_plugins() - for pl_id in plugins: - plugin = plugins[pl_id] - if plugin.type == PLUGIN_VIP_IDENTIFIER or plugin.type == PLUGIN_PCP_IDENTIFIER: - if plugin.plugin.config['tab']['value'] == tab_name: - self.moveFromTo(tab_name,self.get_first_tab().name, plugin.plugin.get_sub_window()) - plugin.startup_config['tab']['value'] = self.get_first_tab() - - self.remove_tab(tabOb) def cmenu_rename_tab(self): tabOb = self.tabWidget.currentWidget() @@ -156,8 +171,6 @@ def cmenu_rename_tab(self): - - class TabObject(QtGui.QMdiArea): def __init__(self, name, parent=None): super(TabObject, self).__init__(parent) @@ -166,3 +179,11 @@ def __init__(self, name, parent=None): self.background = None +class DefaultCloseBox(QtGui.QMessageBox): + + def __init__(self, parent=None): + super(DefaultCloseBox, self).__init__(parent) + self.setWindowTitle('Close default tab?') + self.setText('All plugins in this tab will be closed. Are you sure?') + self.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) + From 20a9e1464ce242853d1e31b1a65afc2f000d1e67 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Wed, 11 Feb 2015 15:38:38 +0100 Subject: [PATCH 131/260] minor changes in plot plugin --- papi/plugin/visual/Plot/Plot.py | 84 +++++++++++++++++++++++++++++++-- papi/plugin/visual/Plot/perf.py | 24 ++++++++-- 2 files changed, 99 insertions(+), 9 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 545ca56d..44a17083 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -139,7 +139,7 @@ def initiate_layer_0(self, config=None): self.__styles_selected__ = int_re.findall(self.config['style']['value']) self.__buffer_size__ = int(int_re.findall(self.config['buffersize']['value'])[0]) - #self.__buffer_size__ = 1000 + self.__downsampling_rate__ = int(int_re.findall(self.config['downsampling_rate']['value'])[0]) # ---------------------------- @@ -156,11 +156,15 @@ def initiate_layer_0(self, config=None): self.__vertical_line__ = pq.InfiniteLine() - self.__plotWidget__ = pq.PlotWidget() +# self.__plotWidget__ = pq.PlotWidget() + + self.__plotWidget__ = PlotWidget() + self.__plotWidget__.addItem(self.__text_item__) if self.__rolling_plot__: self.__plotWidget__.addItem(self.__vertical_line__) + self.__text_item__.setPos(0, 0) self.__plotWidget__.setWindowTitle('PlotPerformanceTitle') @@ -173,7 +177,9 @@ def initiate_layer_0(self, config=None): self.set_widget_for_internal_usage(self.__plotWidget__) self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.YAxis, enable=False) self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.XAxis, enable=False) + #self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=False, y=True) + # --------------------------- # Create Parameters # --------------------------- @@ -384,9 +390,14 @@ def update_plot(self): else: self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0], tdata[-1]) + #self.__plotWidget__.update() + + #self.__plotWidget__.repaint() + # if self.__papi_debug__: # print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) + def update_plot_single_timestamp(self, data): """ Function update_plot_single_timestamp @@ -860,6 +871,20 @@ def quit(self): def debug_papi(self): config = self.get_plugin_configuration() + + config['yRange'] = { + 'value': '[0.0 50.0]', + 'regex': '(\d+\.\d+)', + 'advanced': '1', + 'display_text': 'y: range' + } + config['buffersize'] = { + 'value': '1000', + 'regex': '(\d+\.\d+)', + 'advanced': '1', + 'display_text': 'y: range' + } + self.config = config self.__id__ = 0 self.initiate_layer_0(config) @@ -973,6 +998,7 @@ def __init__(self, x, y, counter, pen=pg.mkPen('r')): connect[:, -1] = 0 # don't draw the segment between each trace self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten()) pg.QtGui.QGraphicsPathItem.__init__(self, self.path) + self.setCacheMode(pg.QtGui.QGraphicsItem.NoCache) self.setPen(pen) self.not_drawn = True self.counter = counter @@ -1032,7 +1058,6 @@ def get_legend_item(self): data_item.setPen(self.pen) return data_item - def add_data(self, elements, tdata): """ @@ -1043,6 +1068,7 @@ def add_data(self, elements, tdata): buffer = self.buffer + # TODO: Downsampling before extending the internal buffer # if self.downsampling_rate_start < len(elements): # ds_elements = elements[self.downsampling_rate_start:self.downsampling_rate:] @@ -1065,6 +1091,11 @@ def update_signal(self, new_signal): self.signal_name = new_signal.dname def set_downsampling_rate(self, rate): + """ + + :param rate: + :return: + """ self.downsampling_rate = 2 @@ -1073,6 +1104,11 @@ def set_downsampling_rate(self, rate): def create_graphics(self, tdata): + """ + + :param tdata: + :return: + """ # get amount of elements in our buffer counter = len(self.buffer) @@ -1081,8 +1117,6 @@ def create_graphics(self, tdata): x_axis = np.array(tdata[-counter:])[np.newaxis,:] y_axis = np.array(list(self.buffer)) - # TODO: Downsampling before graphic creation - if self.rolling_plot: #print('I keep it rolling') @@ -1136,6 +1170,10 @@ def create_graphics(self, tdata): self.graphics.append(graphic) def get_graphics(self): + """ + + :return: + """ res_graphics = [] for graphic in self.graphics: @@ -1147,6 +1185,10 @@ def get_graphics(self): return res_graphics def get_old_graphics(self): + """ + + :return: + """ res_graphic = [] # print(self.amount_elements) @@ -1167,10 +1209,42 @@ def get_old_graphics(self): return res_graphic def clear(self): + """ + + :return: + """ self.graphics = [] self.downsampling_rate_start = 0 self.buffer = [] self.amount_elements = 0 def get_buffersize(self): + """ + + :return: + """ return len(self.buffer) + + +class PlotWidget(pg.PlotWidget): + + def __init__(self): + super(PlotWidget, self).__init__() + self.paint = True + + # def enablePainting(self): + # self.paint = True + # + # def disablePainting(self): + # self.paint = False + # + # def refreshPlot(self): + # self.enablePainting() + # super(PlotWidget, self).repaint() + # self.disablePainting() + + def paintEvent(self, ev): + +# if self.paint: +# print('REPAINT !!') + super(PlotWidget, self).paintEvent(ev) diff --git a/papi/plugin/visual/Plot/perf.py b/papi/plugin/visual/Plot/perf.py index e973cf79..75816aa5 100644 --- a/papi/plugin/visual/Plot/perf.py +++ b/papi/plugin/visual/Plot/perf.py @@ -34,7 +34,7 @@ sys.path.insert(0,os.path.abspath('../../../../')) print(sys.path) -import papi.pyqtgraph as pq +import papi.pyqtgraph as pg from PySide.QtGui import QApplication, QLabel from PySide import QtCore @@ -45,9 +45,12 @@ def do_fctn(plugin): t = plugin.__t__ data = {} + + now = pg.ptime.time() + for i in range(10): data['t'] = [t] - t += 0.01 + t += 0.0025 data['signal_1'] = [random.randint(0, 5)] data['signal_2'] = [random.randint(10, 15)] data['signal_3'] = [random.randint(20, 25)] @@ -61,11 +64,24 @@ def do_fctn(plugin): # data['signal_4'] = [4] # data['signal_5'] = [5] + + plugin.execute(data) + + + +# + plugin.__t__ = t - if plugin.__t__ < 25: - QtCore.QTimer.singleShot(20, lambda : do_fctn(plugin)) + diff_time = pg.ptime.time()-now + +# print("Plot time: %0.5f sec" % (diff_time ) ) + + #print(25 - diff_time * 1000) + + if plugin.__t__ < 10: + QtCore.QTimer.singleShot(25-diff_time* 1000, lambda : do_fctn(plugin)) imp_path = "Plot.py" From 8e104478feaee7ad735b61b2c706676244ed9a5d Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Wed, 11 Feb 2015 16:13:54 +0100 Subject: [PATCH 132/260] added tabs --- papi/gui/qt_new/PapiTabManger.py | 41 ++++++++++++++++++++++++---- papi/gui/qt_new/main.py | 8 ++++-- papi/plugin/base_classes/vip_base.py | 3 ++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index 81354aca..d7e21c7e 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -31,17 +31,17 @@ from PySide.QtGui import QDialog, QLineEdit, QRegExpValidator, QCheckBox , QTabWidget from PySide.QtCore import * from papi.pyqtgraph import QtCore, QtGui - +from papi.gui.qt_new.custom import FileLineEdit from papi.constants import PLUGIN_PCP_IDENTIFIER, PLUGIN_IOP_IDENTIFIER, PLUGIN_VIP_IDENTIFIER, PLUGIN_DPP_IDENTIFIER class PapiTabManger(QObject): - def __init__(self, tabWigdet = None, dgui = None, parent=None): + def __init__(self, tabWigdet = None, dgui = None,gui_api = None, parent=None): super(PapiTabManger, self).__init__(parent) self.tabWidget = tabWigdet - + self.gui_api = gui_api self.tabWidget.setContextMenuPolicy(Qt.CustomContextMenu) self.tabWidget.customContextMenuRequested.connect(self.show_context_menu) self.cmenu = self.create_context_menu() @@ -105,6 +105,7 @@ def moveFromTo(self, start, dest, subWindow, posX=0, posY=0): destTab = self.tab_dict_uname[dest] startTab.removeSubWindow(subWindow) destTab.addSubWindow(subWindow) + subWindow.show() subWindow.move(posX, posY) subWindow.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowMinMaxButtonsHint | Qt.WindowTitleHint ) return True @@ -125,9 +126,13 @@ def create_context_menu(self): rename_tab_action = QtGui.QAction('Rename Tab',self.tabWidget) rename_tab_action.triggered.connect(self.cmenu_rename_tab) + bg_action = QtGui.QAction('Set background',self.tabWidget) + bg_action.triggered.connect(self.cmenu_set_bg) + ctrlMenu.addAction(new_tab_action) ctrlMenu.addAction(close_tab_action) - ctrlMenu.addAction(rename_tab_action) + #ctrlMenu.addAction(rename_tab_action) + ctrlMenu.addAction(bg_action) return ctrlMenu @@ -137,6 +142,23 @@ def cmenu_new_tab(self): name = name + 'X' self.add_tab(name) + def cmenu_set_bg(self): + fileNames = '' + + dialog = QtGui.QFileDialog(self.tabWidget) + dialog.setFileMode(QtGui.QFileDialog.AnyFile) + + if dialog.exec_(): + fileNames = dialog.selectedFiles() + + if len(fileNames): + if fileNames[0] != '': + path = fileNames[0] + pixmap = QtGui.QPixmap(path) + widgetArea = self.tabWidget.widget(self.tabWidget.currentIndex()) + widgetArea.setBackground(pixmap) + widgetArea.background = path + def cmenu_close_tab(self): ind = self.tabWidget.currentIndex() @@ -161,7 +183,14 @@ def closeTab(self, ind): diag = DefaultCloseBox()#QtGui.QDialog() ret = diag.exec_() if ret == QtGui.QMessageBox.Ok: - print('close default tab') + allPlugins = self.dGui.get_all_plugins() + for pl_id in allPlugins: + dplugin = allPlugins[pl_id] + if dplugin.own_process is False: + self.gui_api.do_delete_plugin(dplugin.id) + self.remove_tab(tabOb) + + def cmenu_rename_tab(self): @@ -176,7 +205,7 @@ def __init__(self, name, parent=None): super(TabObject, self).__init__(parent) self.index = None self.name = name - self.background = None + self.background = 'default' class DefaultCloseBox(QtGui.QMessageBox): diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index 775376b0..938107cc 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -88,10 +88,10 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): else: self.gui_data = gui_data - self.TabManager = PapiTabManger(self.widgetTabs,dgui=self.gui_data) - self.gui_api = Gui_api(self.gui_data, core_queue, gui_id) + self.TabManager = PapiTabManger(self.widgetTabs,dgui=self.gui_data, gui_api=self.gui_api) + self.gui_event_processing = GuiEventProcessing(self.gui_data, core_queue, gui_id, gui_queue, TabManager=self.TabManager) self.gui_event_processing.added_dplugin.connect(self.add_dplugin) @@ -190,6 +190,8 @@ def set_background_for_gui(self): :return: """ + print('TO DELETE') + raise Exception fileNames = '' dialog = QFileDialog(self) @@ -371,7 +373,7 @@ def remove_dplugin(self, dplugin): :return: """ if dplugin.type == PLUGIN_VIP_IDENTIFIER or dplugin.type == PLUGIN_PCP_IDENTIFIER: - config = dplugin.startup_config + config = dplugin.plugin.config tab_name = config['tab']['value'] if tab_name in self.TabManager.get_tabs_by_uname(): tabOb = self.TabManager.get_tabs_by_uname()[tab_name] diff --git a/papi/plugin/base_classes/vip_base.py b/papi/plugin/base_classes/vip_base.py index 7e88c62c..36f20ee0 100644 --- a/papi/plugin/base_classes/vip_base.py +++ b/papi/plugin/base_classes/vip_base.py @@ -87,6 +87,9 @@ def tabMenu_triggered(self, item): def ctlrMenu_exit(self): self.control_api.do_delete_plugin_uname(self.dplugin_info.uname) + print(self.config['tab']['value']) + + def ctlrMenu_pause(self): self.control_api.do_pause_plugin_by_uname(self.dplugin_info.uname) From 0409878743738a3e4d6e29518539f3aa9e14a805 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Wed, 11 Feb 2015 16:21:43 +0100 Subject: [PATCH 133/260] minor changes in plot plugin --- papi/plugin/visual/Plot/Plot.py | 45 ++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 44a17083..9f9d152e 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -352,6 +352,10 @@ def update_plot(self): :return: """ + + + + self.__plotWidget__.getPlotItem().setDownsampling(ds=30, auto=False) if not self.__rolling_plot__: @@ -994,8 +998,11 @@ def __init__(self, x, y, counter, pen=pg.mkPen('r')): :return: """ + + x = np.array(x[:])[np.newaxis, :] + connect = np.ones(x.shape, dtype=bool) - connect[:, -1] = 0 # don't draw the segment between each trace + connect[:,-1] = 0 # don't draw the segment between each trace self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten()) pg.QtGui.QGraphicsPathItem.__init__(self, self.path) self.setCacheMode(pg.QtGui.QGraphicsItem.NoCache) @@ -1114,19 +1121,19 @@ def create_graphics(self, tdata): counter = len(self.buffer) self.amount_elements += counter - x_axis = np.array(tdata[-counter:])[np.newaxis,:] + x_axis = np.array(tdata[-counter:]) y_axis = np.array(list(self.buffer)) if self.rolling_plot: #print('I keep it rolling') - i_max = np.argmax(x_axis[0]) - i_min = np.argmin(x_axis[0]) + i_max = np.argmax(x_axis) + i_min = np.argmin(x_axis) # Create two graphic objects due plot border if i_min > i_max: - x_axis_1 = np.array(x_axis[0][:i_max+1])[np.newaxis, :] - x_axis_2 = np.array(x_axis[0][i_min:])[np.newaxis, :] + x_axis_1 = np.array(x_axis[:i_max+1]) + x_axis_2 = np.array(x_axis[i_min:]) y_axis_1 = y_axis[:i_max+1] y_axis_2 = y_axis[i_min:] @@ -1137,10 +1144,9 @@ def create_graphics(self, tdata): prev_last_x = prev_graphic.last_x; prev_last_y = prev_graphic.last_y; - x_axis_1 = np.insert(x_axis_1[0], 0, prev_last_x)[np.newaxis,:] + x_axis_1 = np.insert(x_axis_1, 0, prev_last_x) y_axis_1 = np.insert(y_axis_1, 0, prev_last_y) - graphic_1 = GraphicItem(x_axis_1, y_axis_1, len(y_axis_1) - 1, self.pen) graphic_2 = GraphicItem(x_axis_2, y_axis_2, len(y_axis_2), self.pen) @@ -1158,8 +1164,8 @@ def create_graphics(self, tdata): prev_last_x = prev_graphic.last_x; prev_last_y = prev_graphic.last_y; - if prev_last_x < x_axis[0][0]: - x_axis = np.insert(x_axis[0], 0, prev_last_x)[np.newaxis,:] + if prev_last_x < x_axis[0]: + x_axis = np.insert(x_axis, 0, prev_last_x) y_axis = np.insert(y_axis, 0, prev_last_y) graphic = GraphicItem(x_axis, y_axis, counter, pen=self.pen) @@ -1232,19 +1238,22 @@ def __init__(self): super(PlotWidget, self).__init__() self.paint = True - # def enablePainting(self): - # self.paint = True - # - # def disablePainting(self): - # self.paint = False - # + def enablePainting(self): + self.paint = True + + def disablePainting(self): + self.paint = False + # def refreshPlot(self): # self.enablePainting() # super(PlotWidget, self).repaint() # self.disablePainting() def paintEvent(self, ev): - + if self.paint: + self.scene().prepareForPaint() + return QtGui.QGraphicsView.paintEvent(self, ev) # if self.paint: # print('REPAINT !!') - super(PlotWidget, self).paintEvent(ev) + # super(PlotWidget, self).paintEvent(ev) + From 81223e1c24de9c75c414cae70b9cf8f07b63f547 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 16 Feb 2015 16:45:16 +0100 Subject: [PATCH 134/260] added tabs to cfg --- papi/constants.py | 2 + papi/gui/gui_api.py | 183 +++++++++++++----------- papi/gui/qt_new/PapiTabManger.py | 167 +++++++++++++++------ papi/gui/qt_new/create_plugin_dialog.py | 23 ++- papi/gui/qt_new/create_plugin_menu.py | 5 +- papi/gui/qt_new/main.py | 93 +++++++----- papi/plugin/base_classes/base_visual.py | 2 +- 7 files changed, 299 insertions(+), 176 deletions(-) diff --git a/papi/constants.py b/papi/constants.py index 9cde874b..52c262c7 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -87,6 +87,8 @@ GUI_DEFAULT_WIDTH = 771 GUI_DEFAULT_HEIGHT = 853 +GUI_DEFAULT_TAB = 'PaPI-Tab' + # PLUGIN LOCATION CONSTANTS PLUGIN_ROOT_FOLDER_LIST = ['plugin', 'papi/plugin', '../plugin'] PLUGIN_IOP_FOLDER = '' diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 78376b61..c8419f9c 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -50,19 +50,16 @@ class Gui_api(QtCore.QObject): - resize_gui = QtCore.Signal(int, int) - set_bg_gui = QtCore.Signal(str) error_occured = QtCore.Signal(str, str, str) - def __init__(self, gui_data, core_queue, gui_id, LOG_IDENT=GUI_PROCESS_CONSOLE_IDENTIFIER): + def __init__(self, gui_data, core_queue, gui_id, get_gui_config_function = None, set_gui_config_function = None, LOG_IDENT=GUI_PROCESS_CONSOLE_IDENTIFIER): super(Gui_api, self).__init__() self.gui_id = gui_id self.gui_data = gui_data self.core_queue = core_queue self.log = ConsoleLog(GUI_PROCESS_CONSOLE_LOG_LEVEL, LOG_IDENT) - self.gui_size_width = None - self.gui_size_height = None - self.gui_bg_path = None + self.get_gui_config_function = get_gui_config_function + self.set_gui_config_function = set_gui_config_function def do_create_plugin(self, plugin_identifier, uname, config={}, autostart=True): """ @@ -479,76 +476,84 @@ def do_load_xml(self, path): try: for plugin_xml in root: - if plugin_xml.tag == 'Size': - w = int(plugin_xml.attrib['w']) - h = int(plugin_xml.attrib['h']) - self.resize_gui.emit(w, h) - else: - if plugin_xml.tag == 'Background': - bg_path = str(plugin_xml.attrib['image']) - if bg_path != '' and bg_path is not None and bg_path != 'default' and bg_path != 'None': - self.set_bg_gui.emit(bg_path) - else: - pl_uname = plugin_xml.attrib['uname'] - identifier = plugin_xml.find('Identifier').text - config_xml = plugin_xml.find('StartConfig') - config_hash = {} - for parameter_xml in config_xml.findall('Parameter'): - para_name = parameter_xml.attrib['Name'] - config_hash[para_name] = {} - for detail_xml in parameter_xml: - detail_name = detail_xml.tag - config_hash[para_name][detail_name] = detail_xml.text - - pl_uname_new = self.change_uname_to_uniqe(pl_uname) - - plugins_to_start.append([identifier, pl_uname_new, config_hash]) - - # -------------------------------- - # Load Subscriptions - # -------------------------------- - - subs_xml = plugin_xml.find('Subscriptions') - if subs_xml is not None: - for sub_xml in subs_xml.findall('Subscription'): - data_source = sub_xml.find('data_source').text - for block_xml in sub_xml.findall('block'): - block_name = block_xml.attrib['Name'] - signals = [] - for sig_xml in block_xml.findall('Signal'): - signals.append(str(sig_xml.text)) - alias_xml = block_xml.find('alias') - alias = alias_xml.text - pl_uname_new = self.change_uname_to_uniqe(pl_uname) - data_source_new = self.change_uname_to_uniqe(data_source) - subs_to_make.append([pl_uname_new, data_source_new, block_name, signals, alias]) - - # -------------------------------- - # Load PreviousParameters - # -------------------------------- - - prev_parameters_xml = plugin_xml.find('PreviousParameters') - if prev_parameters_xml is not None: - for prev_parameter_xml in prev_parameters_xml.findall('Parameter'): - para_name = prev_parameter_xml.attrib['Name'] - para_value = prev_parameter_xml.text + ########################## + # Read gui configuration # + ########################## + if plugin_xml.tag == 'guiConfig': + cfg = {} + for property in plugin_xml: + cfg[property.tag] = {} + for attr in property: + cfg[property.tag][attr.tag] = {} + for value in attr: + cfg[property.tag][attr.tag][value.tag] = value.text + + self.set_gui_config_function(cfg) + + ############################# + # Read plugin configuration # + ############################# + if plugin_xml.tag == 'Plugin': + pl_uname = plugin_xml.attrib['uname'] + identifier = plugin_xml.find('Identifier').text + config_xml = plugin_xml.find('StartConfig') + config_hash = {} + for parameter_xml in config_xml.findall('Parameter'): + para_name = parameter_xml.attrib['Name'] + config_hash[para_name] = {} + for detail_xml in parameter_xml: + detail_name = detail_xml.tag + config_hash[para_name][detail_name] = detail_xml.text + + pl_uname_new = self.change_uname_to_uniqe(pl_uname) + + plugins_to_start.append([identifier, pl_uname_new, config_hash]) + + # -------------------------------- + # Load Subscriptions + # -------------------------------- + + subs_xml = plugin_xml.find('Subscriptions') + if subs_xml is not None: + for sub_xml in subs_xml.findall('Subscription'): + data_source = sub_xml.find('data_source').text + for block_xml in sub_xml.findall('block'): + block_name = block_xml.attrib['Name'] + signals = [] + for sig_xml in block_xml.findall('Signal'): + signals.append(str(sig_xml.text)) + alias_xml = block_xml.find('alias') + alias = alias_xml.text pl_uname_new = self.change_uname_to_uniqe(pl_uname) - # TODO validate NO FLOAT in parameter - parameters_to_change.append([pl_uname_new, para_name, para_value]) - - # -------------------------------- - # Load DBlocks due to signals name - # -------------------------------- - - dblocks_xml = plugin_xml.find('DBlocks') - if dblocks_xml is not None: - for dblock_xml in dblocks_xml: - dblock_name = dblock_xml.attrib['Name'] - dsignals_xml = dblock_xml.findall('DSignal') - for dsignal_xml in dsignals_xml: - dsignal_uname = dsignal_xml.attrib['uname'] - dsignal_dname = dsignal_xml.find('dname').text - signals_to_change.append([pl_uname, dblock_name, dsignal_uname, dsignal_dname]) + data_source_new = self.change_uname_to_uniqe(data_source) + subs_to_make.append([pl_uname_new, data_source_new, block_name, signals, alias]) + + # -------------------------------- + # Load PreviousParameters + # -------------------------------- + + prev_parameters_xml = plugin_xml.find('PreviousParameters') + if prev_parameters_xml is not None: + for prev_parameter_xml in prev_parameters_xml.findall('Parameter'): + para_name = prev_parameter_xml.attrib['Name'] + para_value = prev_parameter_xml.text + pl_uname_new = self.change_uname_to_uniqe(pl_uname) + # TODO validate NO FLOAT in parameter + parameters_to_change.append([pl_uname_new, para_name, para_value]) + + # -------------------------------- + # Load DBlocks due to signals name + # -------------------------------- + + dblocks_xml = plugin_xml.find('DBlocks') + if dblocks_xml is not None: + for dblock_xml in dblocks_xml: + dblock_name = dblock_xml.attrib['Name'] + dsignals_xml = dblock_xml.findall('DSignal') + for dsignal_xml in dsignals_xml: + dsignal_uname = dsignal_xml.attrib['uname'] + dsignal_dname = dsignal_xml.find('dname').text + signals_to_change.append([pl_uname, dblock_name, dsignal_uname, dsignal_dname]) except Exception as E: tb = traceback.format_exc() self.error_occured.emit("Error: Config Loader", "Not loadable: " + path, tb) @@ -623,14 +628,26 @@ def do_save_xml_config(self, path): root.set('Date', datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S')) root.set('PaPI_version', CORE_PAPI_VERSION) - size_xml = ET.SubElement(root, 'Size') - size_xml.set('w', str(self.gui_size_width)) - size_xml.set('h', str(self.gui_size_height)) - - bg_xml = ET.SubElement(root, 'Background') - bg_xml.set('image', str(self.gui_bg_path)) - - # get plugins # + ########################## + # Save gui configuration # + ########################## + gui_cfg = self.get_gui_config_function() + gui_cfg_xml = ET.SubElement(root, 'guiConfig') + + for cfg_item in gui_cfg: + item_xml = ET.SubElement(gui_cfg_xml,cfg_item) + item = gui_cfg[cfg_item] + for attr_name in item: + attr_xml = ET.SubElement(item_xml, attr_name) + values = item[attr_name] + for val in values: + value_xml = ET.SubElement(attr_xml, val) + value_xml.text = values[val] + + # --------------------------------------- + # save information of plugins + # for the next start + # --------------------------------------- plugins = self.gui_data.get_all_plugins() for dplugin_id in plugins: dplugin = plugins[dplugin_id] diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index d7e21c7e..9103ca7d 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -46,7 +46,10 @@ def __init__(self, tabWigdet = None, dgui = None,gui_api = None, parent=None): self.tabWidget.customContextMenuRequested.connect(self.show_context_menu) self.cmenu = self.create_context_menu() - self.tabWidget.tabCloseRequested.connect(self.closeTab) + self.tabWidget.tabCloseRequested.connect(self.closeTab_by_ind) + + self.tabWidget.setTabShape(QtGui.QTabWidget.Triangular) + self.tabWidget.setTabPosition(QtGui.QTabWidget.North) self.dGui = dgui # make tabs movable @@ -56,8 +59,6 @@ def __init__(self, tabWigdet = None, dgui = None,gui_api = None, parent=None): # create dict for saving tabs self.tab_dict_uname = {} - self.add_tab(name='Tab') - def get_tabs_by_uname(self): return self.tab_dict_uname @@ -78,27 +79,35 @@ def remove_tab(self,tabObject): self.tabWidget.removeTab(ind) tabObject.destroy() - def rename_tab(self, tabObject, new_name): if new_name not in self.tab_dict_uname: # rename it + old_name = tabObject.name self.tab_dict_uname.pop(tabObject.name) + tabObject.name = new_name self.tab_dict_uname[tabObject.name] = tabObject + ind = self.tabWidget.indexOf(tabObject) self.tabWidget.setTabText(ind,tabObject.name) + allPlugins = self.dGui.get_all_plugins() + for pluign_ind in allPlugins: + dplugin = allPlugins[pluign_ind] + if dplugin.plugin.get_type() == PLUGIN_VIP_IDENTIFIER or dplugin.plugin.get_type() == PLUGIN_PCP_IDENTIFIER: + tabOfPlugin = dplugin.plugin.config['tab']['value'] + if tabOfPlugin == old_name: + dplugin.plugin.config['tab']['value'] = new_name def get_default_tab(self, NotThisIndex): if NotThisIndex == 0: if self.tabWidget.count() > 1: return self.tabWidget.widget(1) else: - return self.add_tab('Default') + raise Exception('no tab open') else: return self.tabWidget.widget(0) - def moveFromTo(self, start, dest, subWindow, posX=0, posY=0): if start in self.tab_dict_uname and dest in self.tab_dict_uname: startTab = self.tab_dict_uname[start] @@ -109,17 +118,84 @@ def moveFromTo(self, start, dest, subWindow, posX=0, posY=0): subWindow.move(posX, posY) subWindow.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowMinMaxButtonsHint | Qt.WindowTitleHint ) return True + else: + return False + + def set_background_for_tab_with_name(self, name, bg): + pixmap = QtGui.QPixmap(bg) + widgetArea = self.tab_dict_uname[name] + widgetArea.setBackground(pixmap) + widgetArea.background = bg + + def set_all_tabs_to_close_when_empty(self, state): + for tabName in self.tab_dict_uname: + tabO = self.tab_dict_uname[tabName] + tabO.closeIfempty = state + + def closeTab_by_ind(self, ind): + tabOb = self.tabWidget.widget(ind) + tab_name = tabOb.name + if self.tabWidget.count() > 1 or len(tabOb.subWindowList()) == 0: #tab_name != 'Default': + # not the default tab, just close and move plugins to default tab + plugins = self.dGui.get_all_plugins() + for pl_id in plugins: + plugin = plugins[pl_id] + if plugin.type == PLUGIN_VIP_IDENTIFIER or plugin.type == PLUGIN_PCP_IDENTIFIER: + if plugin.plugin.config['tab']['value'] == tab_name: + self.moveFromTo(tab_name,self.get_default_tab(ind).name, plugin.plugin.get_sub_window()) + plugin.plugin.config['tab']['value'] = self.get_default_tab(ind).name + + self.remove_tab(tabOb) + else: + # default tab: ask if really want to close + diag = DefaultCloseBox()#QtGui.QDialog() + ret = diag.exec_() + if ret == QtGui.QMessageBox.Ok: + allPlugins = self.dGui.get_all_plugins() + for pl_id in allPlugins: + dplugin = allPlugins[pl_id] + if dplugin.own_process is False: + self.gui_api.do_delete_plugin(dplugin.id) + self.remove_tab(tabOb) + + def closeTab_by_name(self,name): + if name in self.tab_dict_uname: + tabO = self.tab_dict_uname[name] + ind = self.tabWidget.indexOf(tabO) + self.closeTab_by_ind(ind) + + def close_all_empty_tabs(self): + tabs = self.tab_dict_uname + tabs_to_close = [] + for tab in tabs: + tabO = tabs[tab] + if tabO.isEmpty(): + tabs_to_close.append(tab) + + for tab in tabs_to_close: + self.closeTab_by_name(tab) def show_context_menu(self, pos): + self.cmenu = self.create_context_menu() gloPos = self.tabWidget.mapToGlobal(pos) self.cmenu.exec_(gloPos) def create_context_menu(self): - ctrlMenu = QtGui.QMenu("Control") + ctrlMenu = QtGui.QMenu("") + + + title_action = QtGui.QAction('Tab menu', self.tabWidget) + title_action.setDisabled(True) + + sep_action = QtGui.QAction('',self.tabWidget) + sep_action.setSeparator(True) new_tab_action = QtGui.QAction('New Tab',self.tabWidget) new_tab_action.triggered.connect(self.cmenu_new_tab) + new_tab_action_cust_name = QtGui.QAction('New Tab with name',self.tabWidget) + new_tab_action_cust_name.triggered.connect(self.cmenu_new_tab_custom_name) + close_tab_action = QtGui.QAction('Close Tab',self.tabWidget) close_tab_action.triggered.connect(self.cmenu_close_tab) @@ -129,10 +205,15 @@ def create_context_menu(self): bg_action = QtGui.QAction('Set background',self.tabWidget) bg_action.triggered.connect(self.cmenu_set_bg) + ctrlMenu.addAction(title_action) + ctrlMenu.addAction(sep_action) + ctrlMenu.addAction(sep_action) ctrlMenu.addAction(new_tab_action) - ctrlMenu.addAction(close_tab_action) - #ctrlMenu.addAction(rename_tab_action) - ctrlMenu.addAction(bg_action) + ctrlMenu.addAction(new_tab_action_cust_name) + if self.tabWidget.count() > 0: + ctrlMenu.addAction(close_tab_action) + ctrlMenu.addAction(rename_tab_action) + ctrlMenu.addAction(bg_action) return ctrlMenu @@ -142,6 +223,18 @@ def cmenu_new_tab(self): name = name + 'X' self.add_tab(name) + def cmenu_new_tab_custom_name(self): + name = 'Tab' + while name in self.tab_dict_uname: + name = name + 'X' + text, ok = QtGui.QInputDialog.getText(self.tabWidget, 'Tab name',' Name of new tab', QtGui.QLineEdit.Normal,name) + + if ok: + if text in self.tab_dict_uname: + print('Tab name already exists') + else: + self.add_tab(text) + def cmenu_set_bg(self): fileNames = '' @@ -154,49 +247,24 @@ def cmenu_set_bg(self): if len(fileNames): if fileNames[0] != '': path = fileNames[0] - pixmap = QtGui.QPixmap(path) - widgetArea = self.tabWidget.widget(self.tabWidget.currentIndex()) - widgetArea.setBackground(pixmap) - widgetArea.background = path - + self.set_background_for_tab_with_name(self.tabWidget.currentWidget().name, path) def cmenu_close_tab(self): ind = self.tabWidget.currentIndex() - self.closeTab(ind) - - def closeTab(self, ind): - tabOb = self.tabWidget.widget(ind) - tab_name = tabOb.name - if tab_name != 'Default': - # not the default tab, just close and move plugins to default tab - plugins = self.dGui.get_all_plugins() - for pl_id in plugins: - plugin = plugins[pl_id] - if plugin.type == PLUGIN_VIP_IDENTIFIER or plugin.type == PLUGIN_PCP_IDENTIFIER: - if plugin.plugin.config['tab']['value'] == tab_name: - self.moveFromTo(tab_name,self.get_default_tab(ind).name, plugin.plugin.get_sub_window()) - plugin.plugin.config['tab']['value'] = self.get_default_tab(ind).name - - self.remove_tab(tabOb) - else: - # default tab: ask if really want to close - diag = DefaultCloseBox()#QtGui.QDialog() - ret = diag.exec_() - if ret == QtGui.QMessageBox.Ok: - allPlugins = self.dGui.get_all_plugins() - for pl_id in allPlugins: - dplugin = allPlugins[pl_id] - if dplugin.own_process is False: - self.gui_api.do_delete_plugin(dplugin.id) - self.remove_tab(tabOb) - - - + self.closeTab_by_ind(ind) def cmenu_rename_tab(self): tabOb = self.tabWidget.currentWidget() - self.rename_tab(tabOb,'NEW NAME') + text, ok = QtGui.QInputDialog.getText(self.tabWidget, 'Rename a tab','New name for tab: '+ tabOb.name, + QtGui.QLineEdit.Normal,tabOb.name) + + if ok: + if text in self.tab_dict_uname: + #print('Tab name already exists') + pass + else: + self.rename_tab(tabOb,text) @@ -206,13 +274,18 @@ def __init__(self, name, parent=None): self.index = None self.name = name self.background = 'default' + self.closeIfempty = False + + def isEmpty(self): + return len(self.subWindowList()) == 0 class DefaultCloseBox(QtGui.QMessageBox): def __init__(self, parent=None): super(DefaultCloseBox, self).__init__(parent) - self.setWindowTitle('Close default tab?') + self.setWindowTitle('Close last tab?') self.setText('All plugins in this tab will be closed. Are you sure?') self.setStandardButtons(QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel) + diff --git a/papi/gui/qt_new/create_plugin_dialog.py b/papi/gui/qt_new/create_plugin_dialog.py index 34f6e8ac..0c67e632 100644 --- a/papi/gui/qt_new/create_plugin_dialog.py +++ b/papi/gui/qt_new/create_plugin_dialog.py @@ -30,21 +30,23 @@ import operator -from PySide.QtGui import QDialog, QLineEdit, QRegExpValidator, QCheckBox +from PySide.QtGui import QDialog, QLineEdit, QRegExpValidator, QCheckBox, QComboBox from PySide.QtCore import * from papi.ui.gui.qt_new.create_dialog import Ui_CreatePluginDialog from papi.gui.qt_new.custom import FileLineEdit +from papi.constants import GUI_DEFAULT_TAB class CreatePluginDialog(QDialog, Ui_CreatePluginDialog): - def __init__(self, gui_api, parent=None): + def __init__(self, gui_api, TabManager, parent=None): super(CreatePluginDialog, self).__init__(parent) self.setupUi(self) self.cfg = None self.configuration_inputs = {} self.gui_api = gui_api + self.TabManager = TabManager def set_plugin(self, plugin): @@ -62,16 +64,19 @@ def accept(self): for attr in self.configuration_inputs: - if isinstance(self.configuration_inputs[attr], QCheckBox): + if isinstance(self.configuration_inputs[attr], QCheckBox): if self.configuration_inputs[attr].isChecked(): config[attr]['value'] = '1' else: config[attr]['value'] = '0' - if isinstance(self.configuration_inputs[attr], QLineEdit): + if isinstance(self.configuration_inputs[attr], QLineEdit): config[attr]['value'] = self.configuration_inputs[attr].text() + if isinstance(self.configuration_inputs[attr], QComboBox): + config[attr]['value'] = self.configuration_inputs[attr].currentText() + if not self.gui_api.do_test_name_to_be_unique(config['uname']['value']) : self.configuration_inputs['uname'].setStyleSheet("QLineEdit { border : 2px solid red;}") self.configuration_inputs['uname'].setFocus() @@ -136,9 +141,13 @@ def showEvent(self, *args, **kwargs): #uname = self.gui_api.change_uname_to_uniqe(uname) - editable_field = QLineEdit(str(value)) - editable_field.setText(value) - editable_field.setObjectName('Tab' + "_line_edit") + editable_field = QComboBox() + tabs = list(self.TabManager.get_tabs_by_uname().keys()) + if len(tabs) == 0: + tabs = [GUI_DEFAULT_TAB] + tabs.sort(key=str.lower) + editable_field.addItems(tabs) + editable_field.setObjectName('Tab' + "_comboBox") self.formSimple.addRow(str(display_text) , editable_field) diff --git a/papi/gui/qt_new/create_plugin_menu.py b/papi/gui/qt_new/create_plugin_menu.py index f6c5bbb8..746c37f2 100644 --- a/papi/gui/qt_new/create_plugin_menu.py +++ b/papi/gui/qt_new/create_plugin_menu.py @@ -41,10 +41,11 @@ class CreatePluginMenu(QMainWindow, Ui_Create): - def __init__(self, gui_api, parent=None): + def __init__(self, gui_api, TabManger, parent=None): super(CreatePluginMenu, self).__init__(parent) self.setupUi(self) self.dgui = gui_api.gui_data + self.TabManager = TabManger self.gui_api = gui_api @@ -82,7 +83,7 @@ def __init__(self, gui_api, parent=None): self.pluginTree.clicked.connect(self.pluginItemChanged) - self.plugin_create_dialog = CreatePluginDialog(self.gui_api) + self.plugin_create_dialog = CreatePluginDialog(self.gui_api, self.TabManager) self.createButton.clicked.connect(self.show_create_plugin_dialog) def keyPressEvent(self, event): diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index 938107cc..84e6af51 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -88,7 +88,8 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): else: self.gui_data = gui_data - self.gui_api = Gui_api(self.gui_data, core_queue, gui_id) + self.gui_api = Gui_api(self.gui_data, core_queue, gui_id, get_gui_config_function=self.get_gui_config, + set_gui_config_function= self.set_gui_config) self.TabManager = PapiTabManger(self.widgetTabs,dgui=self.gui_data, gui_api=self.gui_api) @@ -101,22 +102,12 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): self.gui_api.error_occured.connect(self.error_occured) - self.gui_api.resize_gui.connect(self.resize_gui_window) self.setWindowTitle(GUI_PAPI_WINDOW_TITLE) - self.gui_api.set_bg_gui.connect(self.update_background) - # set GUI size - size = self.size() - self.gui_api.gui_size_height = size.height() - self.gui_api.gui_size_width = size.width() - - self.original_resize_function = self.resizeEvent - self.resizeEvent = self.user_window_resize - self.setGeometry(self.geometry().x(),self.geometry().y(),GUI_DEFAULT_WIDTH,GUI_DEFAULT_HEIGHT) self.core_queue = core_queue @@ -132,8 +123,6 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): self.last_config = PAPI_LAST_CFG_PATH - self.update_background(PAPI_DEFAULT_BG_PATH) - self.in_run_mode = False # ------------------------------------- @@ -184,6 +173,50 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): self.saveButton.setIcon(save_icon) + def get_gui_config(self): + tabs = {} + tab_dict = self.TabManager.get_tabs_by_uname() + for tab in tab_dict: + tabOb = tab_dict[tab] + tabs[tab]= {} + tabs[tab]['background'] = tabOb.background + + size = {} + size['x'] = {} + size['x']['value'] = str(self.size().width()) + size['y'] = {} + size['y']['value'] = str(self.size().height()) + + cfg = {} + cfg['tabs'] = tabs + cfg['size'] = size + + return cfg + + def set_gui_config(self, cfg): + ################# + # Cfgs for Tabs # + ################# + if 'tabs' in cfg: + for tab in cfg['tabs']: + name = tab + + tabOb = self.TabManager.add_tab(name) + + tabDetails = cfg['tabs'][tab] + if 'background' in tabDetails: + bg = tabDetails['background'] + if bg != 'default': + self.TabManager.set_background_for_tab_with_name(name,bg) + + ################# + # windows size: # + ################# + if 'size' in cfg: + w = int(cfg['size']['x']['value']) + h = int(cfg['size']['y']['value']) + self.resize_gui_window(w,h) + def set_background_for_gui(self): """ Used to handle request for setting background image. @@ -209,18 +242,6 @@ def set_background_for_gui(self): self.gui_api.gui_bg_path = path self.widgetArea.setBackground(pixmap) - def update_background(self, path): - """ - Used to change the background by giving a path to a picture. - - :param path: Path of a picture. - :return: - """ - #TODO - pass - #pixmap = QtGui.QPixmap(path) - #self.widgetArea.setBackground(pixmap) - def run(self): """ @@ -243,7 +264,7 @@ def show_create_plugin_menu(self): :return: """ - self.create_plugin_menu = CreatePluginMenu(self.gui_api) + self.create_plugin_menu = CreatePluginMenu(self.gui_api, self.TabManager) self.create_plugin_menu.show() # self.create_plugin_menu.raise_() @@ -378,6 +399,10 @@ def remove_dplugin(self, dplugin): if tab_name in self.TabManager.get_tabs_by_uname(): tabOb = self.TabManager.get_tabs_by_uname()[tab_name] tabOb.removeSubWindow(dplugin.plugin.get_sub_window()) + if tabOb.closeIfempty is True: + if len(tabOb.subWindowList()) == 0: + self.TabManager.closeTab_by_name(tabOb.name) + def changed_dgui(self): if self.overview_menu is not None: @@ -426,17 +451,7 @@ def keyPressEvent(self, event): self.toggle_run_mode() def resize_gui_window(self, w, h): - self.setGeometry(self.geometry().x(),self.geometry().y(),w,h) - size = self.size() - self.gui_api.gui_size_height = size.height() - self.gui_api.gui_size_width = size.width() - - def user_window_resize(self, event): - size = event.size() - self.gui_api.gui_size_width = size.width() - self.gui_api.gui_size_height = size.height() - self.original_resize_function(event) def reload_config(self): @@ -456,8 +471,14 @@ def reset_papi(self): h = GUI_DEFAULT_HEIGHT w = GUI_DEFAULT_WIDTH self.setGeometry(self.geometry().x(),self.geometry().y(),w,h) + + self.TabManager.set_all_tabs_to_close_when_empty(True) + self.TabManager.close_all_empty_tabs() + self.gui_api.do_reset_papi() + + def papi_wiki_triggerd(self): QDesktopServices.openUrl(QUrl("https://github.com/TUB-Control/PaPI/wiki", QUrl.TolerantMode)) diff --git a/papi/plugin/base_classes/base_visual.py b/papi/plugin/base_classes/base_visual.py index a9a0ec7f..6d1bd523 100644 --- a/papi/plugin/base_classes/base_visual.py +++ b/papi/plugin/base_classes/base_visual.py @@ -83,7 +83,7 @@ def get_configuration_base(self): 'tooltip': 'Used for window title' }, 'tab': { - 'value': 'Tab33', + 'value': 'Tab', 'tooltip': 'Used for tabs' }} return config From d6094aa98fd95fac1f71a66509979276f55fd0f7 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 16 Feb 2015 17:38:00 +0100 Subject: [PATCH 135/260] major changes in plot plugin + new implementation of the rolling plot + new implementation of downsampling + first version of a new implementation for a single timestamp plot --- papi/plugin/visual/Plot/Plot.py | 279 +++++++++++++++++++++++--------- 1 file changed, 206 insertions(+), 73 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 9f9d152e..6ae34839 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -101,6 +101,8 @@ def __init__(self, debug=False): self.__y_axis__ = None self.__x_axis__ = None self.__amount_of_slices__ = None + self.downsampling_rate_start = None; + self.downsampling_rate = None self.styles = { 0: QtCore.Qt.SolidLine, @@ -141,6 +143,8 @@ def initiate_layer_0(self, config=None): self.__buffer_size__ = int(int_re.findall(self.config['buffersize']['value'])[0]) self.__downsampling_rate__ = int(int_re.findall(self.config['downsampling_rate']['value'])[0]) + self.downsampling_rate_start = 0 + self.downsampling_rate = self.__downsampling_rate__ # ---------------------------- # Create internal variables @@ -249,8 +253,9 @@ def execute(self, Data=None, block_name=None): t = Data['t'] self.__input_size__ = len(t) - self.__tbuffer__.extend(t) - self.__new_added_data__ += len(t) + + #print('Execute') + self.__signals_have_same_length = True now = pg.ptime.time() @@ -259,9 +264,23 @@ def execute(self, Data=None, block_name=None): if key != 't': y = Data[key] if key in self.signals: - self.signals[key].add_data(y, self.__tbuffer__) + if self.downsampling_rate_start < len(y): + ds_y = y[self.downsampling_rate_start::self.downsampling_rate] + #print(ds_y) + self.signals[key].add_data(ds_y) - self.__signals_have_same_length &= (len(t) == len(y)) + self.__signals_have_same_length &= ( len(y) == len(t) ) + + if self.downsampling_rate_start >= len(t): + self.downsampling_rate_start -= len(t) + else: + ds_t = t[self.downsampling_rate_start::self.downsampling_rate] + #print(ds_t) + self.downsampling_rate_start += self.downsampling_rate - len(ds_t) + self.__tbuffer__.extend(ds_t) + + + self.__new_added_data__ += len(t) if self.__input_size__ > 1 or self.__signals_have_same_length: @@ -298,6 +317,7 @@ def set_parameter(self, name, value): self.__downsampling_rate__ = int(value) #self.__plotWidget__.getPlotItem().setDownsampling(ds=int(value),auto=False,mode='mean') self.__new_added_data__ = 0 + self.update_downsampling_rate() if name == 'rolling': self.clear() @@ -354,16 +374,15 @@ def update_plot(self): """ - - - self.__plotWidget__.getPlotItem().setDownsampling(ds=30, auto=False) + if len(self.__tbuffer__) == 0: + return if not self.__rolling_plot__: tdata = list(self.__tbuffer__) + if self.__rolling_plot__: tdata = list(range(0, len(self.__tbuffer__))) - - self.__append_at__ += self.signals[list(self.signals.keys())[0]].get_buffersize() + self.__append_at__ += self.signals[list(self.signals.keys())[0]].get_new_added_since_last_drawing() self.__append_at__ %= len(tdata) tdata = np.roll(tdata, -int(self.__append_at__)) @@ -374,7 +393,7 @@ def update_plot(self): # get all no more needed graphic items graphics = self.signals[signal_name].get_old_graphics() for graphic in graphics: - self.__plotWidget__.removeItem(graphic) + self.__plotWidget__.removeItem(graphic) # Create new new graphic items self.signals[signal_name].create_graphics(tdata) @@ -384,6 +403,7 @@ def update_plot(self): for graphic in graphics: self.__plotWidget__.addItem(graphic) + # Set positon for vertical line if self.__rolling_plot__: self.__vertical_line__.setValue(graphic.last_x) @@ -394,14 +414,9 @@ def update_plot(self): else: self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0], tdata[-1]) - #self.__plotWidget__.update() - - #self.__plotWidget__.repaint() - # if self.__papi_debug__: # print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) - def update_plot_single_timestamp(self, data): """ Function update_plot_single_timestamp @@ -410,14 +425,19 @@ def update_plot_single_timestamp(self, data): """ self.__text_item__.setText("Time " + str(data['t'][0]), color=(200, 200, 200)) - + self.__plotWidget__.clear() for signal_name in data: if signal_name != 't': signal_data = data[signal_name] if signal_name in self.signals: pass - #tdata = np.linspace(1, len(signal_data), len(signal_data)) + tdata = np.linspace(1, len(signal_data), len(signal_data)) + + plot_item = self.signals[signal_name] + + graphic = GraphicItem(np.array(tdata), np.array(signal_data), 0, pen=plot_item.pen) + self.__plotWidget__.addItem(graphic) #curve = self.signals[signal_name]['curve'] #curve.setData(tdata, signal_data, _callSync='off') @@ -503,6 +523,7 @@ def plugin_meta_updated(self): self.update_pens() self.update_legend() self.update_rolling_plot() + self.update_downsampling_rate() def add_plot_item(self, signal, signal_id): """ @@ -778,8 +799,8 @@ def setup_context_menu(self): self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] def contextMenu_yAutoRangeButton_clicked(self): - mi = None; - ma = None; + mi = None + ma = None for sig in self.signals: graphics = self.signals[sig].graphics buf = [] @@ -800,7 +821,7 @@ def contextMenu_yAutoRangeButton_clicked(self): else: mi = mi_buf - ma = str(ma); + ma = str(ma) mi = str(mi) self.yRange_maxEdit.setText(ma) self.yRange_minEdit.setText(mi) @@ -865,6 +886,15 @@ def update_legend(self): self.__legend__.addItem(graphic, legend_name) + def update_downsampling_rate(self): + rate = self.__downsampling_rate__ + + self.downsampling_rate_start = 0 + self.downsampling_rate = rate + + for signal_name in self.signals: + self.signals[signal_name].set_downsampling_rate(rate) + def quit(self): """ Function quit plugin @@ -888,6 +918,12 @@ def debug_papi(self): 'advanced': '1', 'display_text': 'y: range' } + config['downsampling_rate'] = { + 'value': '10', + 'regex': '(\d+\.\d+)', + 'advanced': '1', + 'display_text': 'y: range' + } self.config = config self.__id__ = 0 @@ -947,8 +983,8 @@ def get_plugin_configuration(self): 'advanced': '1', 'display_text': 'Style' }, 'buffersize': { - 'value': "1000", - 'regex': '^([1-9][0-9]{0,3}|10000)$', + 'value': "100", + 'regex': '^(\d+)$', 'advanced': '1', 'display_text': 'Buffersize' }, 'downsampling_rate': { @@ -1038,13 +1074,18 @@ def __init__(self, signal, signal_id, max_elements): """ super(PlotItem,self).__init__() self.signal_name = signal.dname - self.buffer = [] # buffer + + #self.buffer = [] # buffer + + self.buffer = collections.deque([0.0] * 0, max_elements) + self.id = signal_id self.signal = signal self.amount_elements = 0 self.downsampling_rate_start = None; + self.downsampling_rate = None self.max_elements = max_elements @@ -1053,6 +1094,7 @@ def __init__(self, signal, signal_id, max_elements): self.rolling_plot = None self.last_x = None self.last_y = None + self.new_added_element = 0 def get_legend_item(self): """ @@ -1065,7 +1107,7 @@ def get_legend_item(self): data_item.setPen(self.pen) return data_item - def add_data(self, elements, tdata): + def add_data(self, elements): """ :param elements: @@ -1077,6 +1119,24 @@ def add_data(self, elements, tdata): # TODO: Downsampling before extending the internal buffer + + # if self.downsampling_rate_start >= len(elements): + # self.downsampling_rate_start -= len(elements) + # else: + # + # # print('AddData') + # # print('Start ' + str(self.downsampling_rate_start)) + # # print('Rate ' + str(self.downsampling_rate)) + # # print('Len ' + str(len(elements))) + # + # ds_elements = elements[self.downsampling_rate_start::self.downsampling_rate] + # self.downsampling_rate_start += self.downsampling_rate - len(ds_elements) + # # print(elements) + # # print(ds_elements) + # buffer.extend(ds_elements) + + self.new_added_element += len(elements) + # if self.downsampling_rate_start < len(elements): # ds_elements = elements[self.downsampling_rate_start:self.downsampling_rate:] # @@ -1103,76 +1163,124 @@ def set_downsampling_rate(self, rate): :param rate: :return: """ - - self.downsampling_rate = 2 + self.downsampling_rate = rate self.downsampling_rate_start = 0 - - - def create_graphics(self, tdata): + def create_graphics(self, xdata): """ - :param tdata: + :param xdata: :return: """ + self.new_added_element = 0 + # get amount of elements in our buffer counter = len(self.buffer) - self.amount_elements += counter - x_axis = np.array(tdata[-counter:]) - y_axis = np.array(list(self.buffer)) + if not counter > 0: + return - if self.rolling_plot: - #print('I keep it rolling') + self.amount_elements += counter - i_max = np.argmax(x_axis) - i_min = np.argmin(x_axis) + # shift_data = 0 + # if self.last_x is not None: + # if self.last_x in xdata: + # shift_data = list(xdata).index(self.last_x) + # + # print(shift_data) - # Create two graphic objects due plot border - if i_min > i_max: - x_axis_1 = np.array(x_axis[:i_max+1]) - x_axis_2 = np.array(x_axis[i_min:]) - y_axis_1 = y_axis[:i_max+1] - y_axis_2 = y_axis[i_min:] + #xdata = xdata[::self.downsampling_rate] + amount_elements = len(xdata) - if len(self.graphics): - prev_graphic = self.graphics[-1] - prev_last_x = prev_graphic.last_x; - prev_last_y = prev_graphic.last_y; + self.last_x = xdata[0] + # print( "Len Buffer " + str(len(self.buffer))) + # print( "TData" + str(len(tdata))) + + x_axis = np.array(xdata) + y_axis = np.array(list(self.buffer)) - x_axis_1 = np.insert(x_axis_1, 0, prev_last_x) - y_axis_1 = np.insert(y_axis_1, 0, prev_last_y) + # print( "Len Buffer " + str(len(x_axis))) + # print( "TData" + str(len(y_axis))) + # - graphic_1 = GraphicItem(x_axis_1, y_axis_1, len(y_axis_1) - 1, self.pen) - graphic_2 = GraphicItem(x_axis_2, y_axis_2, len(y_axis_2), self.pen) + # # Old Solution + # if False: + # + # if self.rolling_plot: + # #print('I keep it rolling') + # + # i_max = np.argmax(x_axis) + # i_min = np.argmin(x_axis) + # + # # Create two graphic objects due plot border + # if i_min > i_max: + # x_axis_1 = np.array(x_axis[:i_max+1]) + # x_axis_2 = np.array(x_axis[i_min:]) + # + # y_axis_1 = y_axis[:i_max+1] + # y_axis_2 = y_axis[i_min:] + # + # if len(self.graphics): + # prev_graphic = self.graphics[-1] + # prev_last_x = prev_graphic.last_x; + # prev_last_y = prev_graphic.last_y; + # + # x_axis_1 = np.insert(x_axis_1, 0, prev_last_x) + # y_axis_1 = np.insert(y_axis_1, 0, prev_last_y) + # + # graphic_1 = GraphicItem(x_axis_1, y_axis_1, len(y_axis_1) - 1, self.pen) + # graphic_2 = GraphicItem(x_axis_2, y_axis_2, len(y_axis_2), self.pen) + # + # self.graphics.append(graphic_1) + # self.graphics.append(graphic_2) + # + # del self.buffer + # self.buffer = [] + # + # return + # # Connect with previous graphic if a previous graphic exists + # + # if len(self.graphics): + # prev_graphic = self.graphics[-1] + # prev_last_x = prev_graphic.last_x; + # prev_last_y = prev_graphic.last_y; + # + # if prev_last_x < x_axis[0]: + # x_axis = np.insert(x_axis, 0, prev_last_x) + # y_axis = np.insert(y_axis, 0, prev_last_y) + # + # graphic = GraphicItem(x_axis, y_axis, counter, pen=self.pen) + # + # del self.buffer + # self.buffer = [] + # + # self.graphics.append(graphic) - self.graphics.append(graphic_1) - self.graphics.append(graphic_2) + if self.rolling_plot: + #print('First X_Value' + str(xdata)) - del self.buffer - self.buffer = [] + i_max = np.argmax(x_axis) + i_min = np.argmin(x_axis) - return - # Connect with previous graphic if a previous graphic exists + x_axis_1 = np.array(x_axis[:i_max+1]) + x_axis_2 = np.array(x_axis[i_min:]) - if len(self.graphics): - prev_graphic = self.graphics[-1] - prev_last_x = prev_graphic.last_x; - prev_last_y = prev_graphic.last_y; + y_axis_1 = y_axis[:i_max+1] + y_axis_2 = y_axis[i_min:] - if prev_last_x < x_axis[0]: - x_axis = np.insert(x_axis, 0, prev_last_x) - y_axis = np.insert(y_axis, 0, prev_last_y) + graphic_1 = GraphicItem(x_axis_1, y_axis_1, len(y_axis_1) - 1, self.pen) + graphic_2 = GraphicItem(x_axis_2, y_axis_2, len(y_axis_2), self.pen) - graphic = GraphicItem(x_axis, y_axis, counter, pen=self.pen) + self.graphics.append(graphic_1) + self.graphics.append(graphic_2) - del self.buffer - self.buffer = [] + return + graphic = GraphicItem(x_axis, y_axis, counter, pen=self.pen) self.graphics.append(graphic) def get_graphics(self): @@ -1195,25 +1303,44 @@ def get_old_graphics(self): :return: """ + + + res_graphic = [] - # print(self.amount_elements) - # print(len(self.graphics)) - while self.amount_elements >= self.max_elements: + # if len(self.graphics): + # res_graphic.append(self.graphics.pop(0)) + for i in range(len(self.graphics)): graphic = self.graphics[0] if graphic.not_drawn: break graphic = self.graphics.pop(0) - self.amount_elements -= graphic.length() res_graphic.append(graphic) return res_graphic + # # print(self.amount_elements) + # # print(len(self.graphics)) + # + # while self.amount_elements >= self.max_elements: + # + # graphic = self.graphics[0] + # + # if graphic.not_drawn: + # break + # + # graphic = self.graphics.pop(0) + # self.amount_elements -= graphic.length() + # + # res_graphic.append(graphic) + # + # return res_graphic + def clear(self): """ @@ -1221,16 +1348,22 @@ def clear(self): """ self.graphics = [] self.downsampling_rate_start = 0 - self.buffer = [] + self.buffer = collections.deque([0.0] * 0, self.max_elements) self.amount_elements = 0 + def get_new_added_since_last_drawing(self): + return self.new_added_element + def get_buffersize(self): """ :return: """ - return len(self.buffer) + if len(self.buffer) < self.max_elements: + return len(self.buffer) + else: + return self.max_elements class PlotWidget(pg.PlotWidget): From 127358bd798966a822beb7ee13ef0dd9137fcc4b Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Wed, 25 Feb 2015 10:24:13 +0100 Subject: [PATCH 136/260] added tabs to changelog --- CHANGENLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index e3560d21..a641aa3f 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -10,7 +10,7 @@ v.1.XX * [fix]: fixed memory leak of gui event processing timer loop (#25) * [improvement]: changed the demux function to in imporve performance * [improvement]: imporved core performance while processing new data (see #20) - + * [feature]: modified PaPI gui to have tabs. User can organise visual plugins in tabs, remove tabs, rename tabs. v.1.0 --- From dc47b31f210bdfefd91aadedec067c3263fc19bf Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Wed, 25 Feb 2015 12:18:22 +0100 Subject: [PATCH 137/260] updated slider and button --- papi/constants.py | 10 ++++- papi/plugin/base_classes/base_visual.py | 58 ++++++++++++++++++++++++- papi/plugin/base_classes/vip_base.py | 54 ----------------------- papi/plugin/pcp/button/Button.py | 8 ++++ papi/plugin/pcp/slider/Slider.py | 34 ++++++++++++--- 5 files changed, 101 insertions(+), 63 deletions(-) diff --git a/papi/constants.py b/papi/constants.py index 52c262c7..7af60c1e 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -121,4 +121,12 @@ CONFIG_DEFAULT_DIRECTORY = 'cfg_collection/' CONFIG_DEFAULT_FILE = 'cfg_collection/testcfg.xml' CONFIG_ROOT_ELEMENT_NAME = 'PaPiConfig' # for xml save -CONFIG_LOADER_SUBSCRIBE_DELAY = 1000 # ms \ No newline at end of file +CONFIG_LOADER_SUBSCRIBE_DELAY = 1000 # ms + + + + +# REGEX Collection + +#REGEX_SINGLE_FLOAT = +REGEX_SINGLE_INT = '[0-9]+' diff --git a/papi/plugin/base_classes/base_visual.py b/papi/plugin/base_classes/base_visual.py index 6d1bd523..43ad7721 100644 --- a/papi/plugin/base_classes/base_visual.py +++ b/papi/plugin/base_classes/base_visual.py @@ -31,7 +31,8 @@ from papi.plugin.base_classes.base_plugin import base_plugin import re from PySide.QtGui import QMdiSubWindow - +from papi.pyqtgraph.Qt import QtGui +from papi.constants import PLUGIN_VIP_IDENTIFIER class base_visual(base_plugin): def init_plugin(self, CoreQueue, pluginQueue, id, control_api, dpluginInfo = None,TabManger = None): @@ -121,3 +122,58 @@ def window_resize(self, event): def get_sub_window(self): return self._subWindow + + def create_control_context_menu(self): + ctrlMenu = QtGui.QMenu("Control") + + del_action = QtGui.QAction('Close plugin',self.widget) + del_action.triggered.connect(self.ctlrMenu_exit) + + pause_action = QtGui.QAction('Pause plugin',self.widget) + pause_action.triggered.connect(self.ctlrMenu_pause) + + resume_action = QtGui.QAction('Resume plugin',self.widget) + resume_action.triggered.connect(self.ctlrMenu_resume) + + subMenu_action = QtGui.QAction('Open Signal Manager',self.widget) + #subMenu_action.triggered.connect(self.ctlrMenu_resume) + + + tabMenu = QtGui.QMenu('Move to') + tabs = list(self.TabManager.get_tabs_by_uname().keys()) + tab_entrys = [] + for t in tabs: + if t != self.config['tab']['value']: + entry = QtGui.QAction(t, self.widget) + entry.triggered.connect(lambda p=t: self.tabMenu_triggered(p)) + tab_entrys.append(entry) + tabMenu.addAction(entry) + + + ctrlMenu.addMenu(tabMenu) + ctrlMenu.addAction(subMenu_action) + if self.get_type() == PLUGIN_VIP_IDENTIFIER: + ctrlMenu.addAction(resume_action) + ctrlMenu.addAction(pause_action) + ctrlMenu.addAction(del_action) + return ctrlMenu + + def tabMenu_triggered(self, item): + pos = self._subWindow.pos() + posX = pos.x() + posY = pos.y() + if self.TabManager.moveFromTo(self.config['tab']['value'], item, self._subWindow, posX=posX, posY=posY): + self.config['tab']['value'] = item + + + def ctlrMenu_exit(self): + self.control_api.do_delete_plugin_uname(self.dplugin_info.uname) + print(self.config['tab']['value']) + + + + def ctlrMenu_pause(self): + self.control_api.do_pause_plugin_by_uname(self.dplugin_info.uname) + + def ctlrMenu_resume(self): + self.control_api.do_resume_plugin_by_uname(self.dplugin_info.uname) \ No newline at end of file diff --git a/papi/plugin/base_classes/vip_base.py b/papi/plugin/base_classes/vip_base.py index 36f20ee0..592d049a 100644 --- a/papi/plugin/base_classes/vip_base.py +++ b/papi/plugin/base_classes/vip_base.py @@ -42,57 +42,3 @@ def initiate_layer_0(self, config): def get_type(self): return PLUGIN_VIP_IDENTIFIER - - def create_control_context_menu(self): - ctrlMenu = QtGui.QMenu("Control") - - del_action = QtGui.QAction('Exit plugin',self.widget) - del_action.triggered.connect(self.ctlrMenu_exit) - - pause_action = QtGui.QAction('Pause plugin',self.widget) - pause_action.triggered.connect(self.ctlrMenu_pause) - - resume_action = QtGui.QAction('Resume plugin',self.widget) - resume_action.triggered.connect(self.ctlrMenu_resume) - - subMenu_action = QtGui.QAction('Open Signal Manager',self.widget) - #subMenu_action.triggered.connect(self.ctlrMenu_resume) - - - tabMenu = QtGui.QMenu('Move to') - tabs = list(self.TabManager.get_tabs_by_uname().keys()) - tab_entrys = [] - for t in tabs: - if t != self.config['tab']['value']: - entry = QtGui.QAction(t, self.widget) - entry.triggered.connect(lambda p=t: self.tabMenu_triggered(p)) - tab_entrys.append(entry) - tabMenu.addAction(entry) - - - ctrlMenu.addMenu(tabMenu) - ctrlMenu.addAction(subMenu_action) - ctrlMenu.addAction(resume_action) - ctrlMenu.addAction(pause_action) - ctrlMenu.addAction(del_action) - return ctrlMenu - - def tabMenu_triggered(self, item): - pos = self._subWindow.pos() - posX = pos.x() - posY = pos.y() - if self.TabManager.moveFromTo(self.config['tab']['value'], item, self._subWindow, posX=posX, posY=posY): - self.config['tab']['value'] = item - - - def ctlrMenu_exit(self): - self.control_api.do_delete_plugin_uname(self.dplugin_info.uname) - print(self.config['tab']['value']) - - - - def ctlrMenu_pause(self): - self.control_api.do_pause_plugin_by_uname(self.dplugin_info.uname) - - def ctlrMenu_resume(self): - self.control_api.do_resume_plugin_by_uname(self.dplugin_info.uname) \ No newline at end of file diff --git a/papi/plugin/pcp/button/Button.py b/papi/plugin/pcp/button/Button.py index c48179c0..6470ddec 100644 --- a/papi/plugin/pcp/button/Button.py +++ b/papi/plugin/pcp/button/Button.py @@ -32,6 +32,7 @@ from PySide.QtGui import QPushButton, QIcon from papi.data.DPlugin import DBlock from papi.data.DSignal import DSignal +from papi.pyqtgraph.Qt import QtGui, QtCore class Button(pcp_base): @@ -56,8 +57,15 @@ def initiate_layer_0(self, config): self.button = self.create_widget() self.set_widget_for_internal_usage(self.button) + self.button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.button.customContextMenuRequested.connect(self.show_context_menu) + def show_context_menu(self, pos): + gloPos = self.button.mapToGlobal(pos) + self.cmenu = self.create_control_context_menu() + self.cmenu.exec_(gloPos) + def create_widget(self): """ diff --git a/papi/plugin/pcp/slider/Slider.py b/papi/plugin/pcp/slider/Slider.py index de43fe74..6e882776 100644 --- a/papi/plugin/pcp/slider/Slider.py +++ b/papi/plugin/pcp/slider/Slider.py @@ -34,6 +34,8 @@ from papi.data.DPlugin import DBlock from papi.data.DSignal import DSignal +from papi.constants import REGEX_SINGLE_INT + class Slider(pcp_base): def initiate_layer_0(self, config): @@ -52,26 +54,43 @@ def create_widget(self): self.slider.sliderPressed.connect(self.clicked) self.slider.valueChanged.connect(self.value_changed) - self.slider.setMinimum(float(self.config['lower_bound']['value'])) - self.slider.setMaximum(float(self.config['upper_bound']['value'])) - self.slider.setSingleStep(float(self.config['step_size']['value'])) + self.value_max = float(self.config['upper_bound']['value']) + self.value_min = float(self.config['lower_bound']['value']) + self.tick_count = float(self.config['step_count']['value']) + + self.tick_width = (self.value_max-self.value_min)/(self.tick_count-1) + + self.slider.setMinimum(0) + self.slider.setMaximum(self.tick_count-1) self.slider.setOrientation(QtCore.Qt.Horizontal) self.text_field = QLineEdit() self.text_field.setReadOnly(True) self.text_field.setFixedWidth(50) + self.text_field.setText('0') + self.layout = QHBoxLayout(self.central_widget) self.layout.addWidget(self.slider) self.layout.addWidget(self.text_field) + self.slider.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.slider.customContextMenuRequested.connect(self.show_context_menu) + return self.central_widget + def show_context_menu(self, pos): + gloPos = self.slider.mapToGlobal(pos) + self.cmenu = self.create_control_context_menu() + self.cmenu.exec_(gloPos) + def value_changed(self, change): - self.text_field.setText(str(change)) - self.send_parameter_change(change, 'SliderBlock') + val = change * self.tick_width + self.value_min + val = round(val, 8) + self.text_field.setText(str(val)) + self.send_parameter_change(val, 'SliderBlock') def clicked(self): pass @@ -87,8 +106,9 @@ def get_plugin_configuration(self): 'upper_bound': { 'value': '1.0' }, - 'step_size': { - 'value': '0.1' + 'step_count': { + 'value': '11', + 'regex': REGEX_SINGLE_INT, }, 'size': { 'value': "(150,75)", From 32210387000cea876827b4714c01a6c3d2ea7cbd Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Wed, 25 Feb 2015 12:46:06 +0100 Subject: [PATCH 138/260] updated button --- papi/plugin/pcp/button/Button.py | 38 ++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/papi/plugin/pcp/button/Button.py b/papi/plugin/pcp/button/Button.py index 6470ddec..60bb4662 100644 --- a/papi/plugin/pcp/button/Button.py +++ b/papi/plugin/pcp/button/Button.py @@ -53,6 +53,12 @@ def initiate_layer_0(self, config): self.name = config['name']['value'] self.cur_value = 0 + self.value_up = float(self.config['state1']['value']) + self.value_down = float(self.config['state2']['value']) + self.text_up = (self.config['state1_text']['value']) + self.text_down = (self.config['state2_text']['value']) + self.button_state = 'up' + self.send_new_block_list([block]) self.button = self.create_widget() @@ -75,27 +81,41 @@ def create_widget(self): button = QPushButton(self.name) button.clicked.connect(self.clicked) + button.setText(self.text_up) + return button def clicked(self): - - - if self.cur_value == float(self.config['up']['value']): - self.cur_value = float(self.config['low']['value']) + if self.button_state == 'up': + # Button is up, goes down now + self.button_state = 'down' + self.button.setText(self.text_down) + val = self.value_down else: - self.cur_value = float(self.config['up']['value']) + # Button is down, goes up now + self.button_state = 'up' + self.button.setText(self.text_up) + val = self.value_up - self.send_parameter_change(str(self.cur_value), 'Click_Event') + self.send_parameter_change(str(val), 'Click_Event') def get_plugin_configuration(self): config = { - "low": { + "state1": { 'value': 0, 'advanced' : '0' - }, "up": { + }, "state2": { 'value': 1, - 'advanced' : '0' + 'advanced' : '0', + }, "state1_text": { + 'value': 'Go to state 2', + 'advanced' : '0', + 'display_text': 'Text for state 1' + }, "state2_text": { + 'value': 'Go to state 1', + 'advanced' : '0', + 'display_text': 'Text for state 2' },"name": { 'value': "PaPI Button", 'advanced': '0' From 3b8df59aef061b3e81e6ff6e5fc34d6706a029e7 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Wed, 25 Feb 2015 12:53:14 +0100 Subject: [PATCH 139/260] updated button --- papi/constants.py | 5 +++-- papi/plugin/pcp/button/Button.py | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/papi/constants.py b/papi/constants.py index 7af60c1e..86bda83b 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -127,6 +127,7 @@ # REGEX Collection - -#REGEX_SINGLE_FLOAT = REGEX_SINGLE_INT = '[0-9]+' +REGEX_BOOL_BIN = '^(1|0)$' +REGEX_SINGLE_UNSIGNED_FLOAT_FORCED = '(\d+\.\d+)' +REGEX_SIGNED_FLOAT = r'([-]{0,1}\d+\.\d+)' \ No newline at end of file diff --git a/papi/plugin/pcp/button/Button.py b/papi/plugin/pcp/button/Button.py index 60bb4662..4865e284 100644 --- a/papi/plugin/pcp/button/Button.py +++ b/papi/plugin/pcp/button/Button.py @@ -34,6 +34,8 @@ from papi.data.DSignal import DSignal from papi.pyqtgraph.Qt import QtGui, QtCore +from papi.constants import REGEX_SIGNED_FLOAT + class Button(pcp_base): def __init__(self): @@ -104,9 +106,11 @@ def get_plugin_configuration(self): config = { "state1": { 'value': 0, + 'regex': REGEX_SIGNED_FLOAT, 'advanced' : '0' }, "state2": { 'value': 1, + 'regex': REGEX_SIGNED_FLOAT, 'advanced' : '0', }, "state1_text": { 'value': 'Go to state 2', From c161562a9a4c52234ecdf7bcc679880c948fc472 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Wed, 25 Feb 2015 14:19:02 +0100 Subject: [PATCH 140/260] major changes in plot plugin + cleanup + functional tests --- papi/plugin/visual/Plot/Plot.py | 428 +++++++++++++------------------- 1 file changed, 169 insertions(+), 259 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 6ae34839..7735aed6 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -101,7 +101,14 @@ def __init__(self, debug=False): self.__y_axis__ = None self.__x_axis__ = None self.__amount_of_slices__ = None - self.downsampling_rate_start = None; + self.__stp_min_x = None + self.__stp_max_x = None + + self.__stp_min_y = None + self.__stp_max_y = None + self.__stp_active__ = None + + self.__downsampling_rate_start__ = None; self.downsampling_rate = None self.styles = { @@ -143,20 +150,32 @@ def initiate_layer_0(self, config=None): self.__buffer_size__ = int(int_re.findall(self.config['buffersize']['value'])[0]) self.__downsampling_rate__ = int(int_re.findall(self.config['downsampling_rate']['value'])[0]) - self.downsampling_rate_start = 0 + self.__downsampling_rate_start__ = 0 self.downsampling_rate = self.__downsampling_rate__ # ---------------------------- - # Create internal variables + # Set internal variables # ---------------------------- self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) self.__amount_of_slices__ = 10 + # ---------------------------- + # Set internal variables used for single timestamp plotting (stp) + # ---------------------------- + + self.__stp_min_x = 0 + self.__stp_max_x = 0 + + self.__stp_min_y = 0 + self.__stp_max_y = 1 + + self.__stp_active__ = False + # -------------------------------- # Create PlotWidget # -------------------------------- - self.__text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(0, 0)) + self.__text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(1, 0)) self.__vertical_line__ = pq.InfiniteLine() @@ -177,29 +196,38 @@ def initiate_layer_0(self, config=None): self.__plotWidget__.getPlotItem().getViewBox().setYRange(0,6) self.__plotWidget__.getPlotItem().setDownsampling(auto=True) + if not self.__papi_debug__: self.set_widget_for_internal_usage(self.__plotWidget__) + self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.YAxis, enable=False) self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.XAxis, enable=False) - #self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=False, y=True) + self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=False, y=True) # --------------------------- # Create Parameters # --------------------------- - self.__parameters__['x-grid'] = DParameter('x-grid', 0, Regex='^(1|0){1}$') - self.__parameters__['y-grid'] = DParameter('y-grid', 0, Regex='^(1|0){1}$') + self.__parameters__['x-grid'] = \ + DParameter('x-grid', 0, Regex='^(1|0){1}$') + self.__parameters__['y-grid'] = \ + DParameter('y-grid', 0, Regex='^(1|0){1}$') - self.__parameters__['color'] = DParameter('color', '[0 1 2 3 4]', Regex='^\[(\s*\d\s*)+\]') - self.__parameters__['style'] = DParameter('style', '[0 0 0 0 0]', Regex='^\[(\s*\d\s*)+\]') - self.__parameters__['rolling'] = DParameter('rolling', '0', Regex='^(1|0){1}') + self.__parameters__['color'] = \ + DParameter('color', '[0 1 2 3 4]', Regex='^\[(\s*\d\s*)+\]') + self.__parameters__['style'] = \ + DParameter('style', '[0 0 0 0 0]', Regex='^\[(\s*\d\s*)+\]') + self.__parameters__['rolling'] = \ + DParameter('rolling', '0', Regex='^(1|0){1}') - self.__parameters__['downsampling_rate'] = DParameter('downsampling_rate', self.__downsampling_rate__, Regex='^([1-9][0-9]?|100)$') - self.__parameters__['buffersize'] = DParameter('buffersize', self.__buffer_size__, Regex='^([1-9][0-9]{0,3}|10000)$') + self.__parameters__['downsampling_rate'] = \ + DParameter('downsampling_rate', self.__downsampling_rate__, Regex='^\d+$') + self.__parameters__['buffersize'] = \ + DParameter('buffersize', self.__buffer_size__, Regex='^\d+$') - #self.__parameters__['xRange'] = DParameter('xRange', '[0,1]', Regex='(\d+\.\d+)') - self.__parameters__['yRange'] = DParameter('yRange', '[0,1]', Regex='(\d+\.\d+)') + self.__parameters__['yRange'] = \ + DParameter('yRange', '[0,1]', Regex='(\d+\.\d+)') if not self.__papi_debug__: self.send_new_parameter_list(list(self.__parameters__.values())) @@ -218,9 +246,14 @@ def initiate_layer_0(self, config=None): self.setup_context_menu() - #self.use_range_for_x(self.config['xRange']['value']) self.use_range_for_y(self.config['yRange']['value']) + # ---------------------------- + # Initiate for default plotting + # ---------------------------- + + self.initate_update_plot() + return True def pause(self): @@ -230,7 +263,7 @@ def pause(self): :return: """ self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=True, y=True) - print('PlotPerformance paused') + #print('PlotPerformance paused') def resume(self): """ @@ -239,7 +272,7 @@ def resume(self): :return: """ self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=False, y=True) - print('PlotPerformance resumed') + #print('PlotPerformance resumed') def execute(self, Data=None, block_name=None): """ @@ -254,8 +287,6 @@ def execute(self, Data=None, block_name=None): self.__input_size__ = len(t) - #print('Execute') - self.__signals_have_same_length = True now = pg.ptime.time() @@ -264,35 +295,49 @@ def execute(self, Data=None, block_name=None): if key != 't': y = Data[key] if key in self.signals: - if self.downsampling_rate_start < len(y): - ds_y = y[self.downsampling_rate_start::self.downsampling_rate] - #print(ds_y) + if self.__downsampling_rate_start__ < len(y): + ds_y = y[self.__downsampling_rate_start__::self.downsampling_rate] + self.signals[key].add_data(ds_y) - self.__signals_have_same_length &= ( len(y) == len(t) ) + self.__signals_have_same_length &= (len(y) == len(t)) - if self.downsampling_rate_start >= len(t): - self.downsampling_rate_start -= len(t) + if self.__downsampling_rate_start__ >= len(t): + self.__downsampling_rate_start__ -= len(t) else: - ds_t = t[self.downsampling_rate_start::self.downsampling_rate] - #print(ds_t) - self.downsampling_rate_start += self.downsampling_rate - len(ds_t) + ds_t = t[self.__downsampling_rate_start__::self.downsampling_rate] + self.__downsampling_rate_start__ += self.downsampling_rate - len(ds_t) self.__tbuffer__.extend(ds_t) - self.__new_added_data__ += len(t) + self.rolling_Checkbox.setDisabled(self.__stp_active__) + if self.__input_size__ > 1 or self.__signals_have_same_length: + if self.__stp_active__: + self.initate_update_plot() + + self.__stp_active__ = False + if current_milli_time() - self.__last_time__ > self.__update_intervall__ - self.__last_plot_time__: self.__last_time__ = current_milli_time() self.update_plot() self.__last_time__ = current_milli_time() self.__new_added_data__ = 0 else: + + if not self.__stp_active__ : + self.initate_update_plot_single_timestamp() + + self.__stp_active__ = True + + self.update_plot_single_timestamp(Data) -# print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) + + # + # print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) def set_parameter(self, name, value): """ @@ -315,7 +360,6 @@ def set_parameter(self, name, value): if name == 'downsampling_rate': self.config['downsampling_rate']['value'] = value self.__downsampling_rate__ = int(value) - #self.__plotWidget__.getPlotItem().setDownsampling(ds=int(value),auto=False,mode='mean') self.__new_added_data__ = 0 self.update_downsampling_rate() @@ -344,10 +388,6 @@ def set_parameter(self, name, value): self.config['buffersize']['value'] = value self.update_buffer_size(value) - # if name == 'xRange': - # self.config['xRange']['value'] = value - # self.use_range_for_x(value) - if name == 'yRange': self.config['yRange']['value'] = value self.use_range_for_y(value) @@ -411,12 +451,29 @@ def update_plot(self): if self.__rolling_plot__: self.__plotWidget__.getPlotItem().getViewBox().setXRange(0, len(tdata)-1) + + axis = self.__plotWidget__.getPlotItem().getAxis('bottom') + axis.setLabel(text=str(self.__tbuffer__[-1]), units='s') + else: self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0], tdata[-1]) # if self.__papi_debug__: # print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) + def initate_update_plot(self): + print('initaed update plot') + axis = self.__plotWidget__.getPlotItem().getAxis('bottom') + axis.setLabel(text='', units='') + axis.setHeight(h=None) + + if self.__rolling_plot__: + self.__plotWidget__.addItem(self.__vertical_line__) + pass + + def initate_update_plot_single_timestamp(self): + pass + def update_plot_single_timestamp(self, data): """ Function update_plot_single_timestamp @@ -426,22 +483,40 @@ def update_plot_single_timestamp(self, data): self.__text_item__.setText("Time " + str(data['t'][0]), color=(200, 200, 200)) self.__plotWidget__.clear() + + cur_max_y = 0 + cur_min_y = 1000 + for signal_name in data: if signal_name != 't': signal_data = data[signal_name] if signal_name in self.signals: - pass + tdata = np.linspace(1, len(signal_data), len(signal_data)) plot_item = self.signals[signal_name] - graphic = GraphicItem(np.array(tdata), np.array(signal_data), 0, pen=plot_item.pen) + if len(tdata) == 1: + graphic = GraphicItem(np.array([self.__stp_min_x, self.__stp_max_x]), np.array([signal_data[0], signal_data[0]]), 0, pen=plot_item.pen) + else: + graphic = GraphicItem(np.array(tdata), np.array(signal_data), 0, pen=plot_item.pen) self.__plotWidget__.addItem(graphic) - #curve = self.signals[signal_name]['curve'] - #curve.setData(tdata, signal_data, _callSync='off') - pass + self.__stp_max_x = max(self.__stp_max_x, max(tdata)) + self.__stp_min_x = min(self.__stp_min_x, min(tdata)) + + cur_max_y = max(cur_max_y, max(signal_data)) + cur_min_y = min(cur_min_y, min(signal_data)) + + self.__stp_max_y = cur_max_y + self.__stp_min_y = cur_min_y + + self.__plotWidget__.getPlotItem().getViewBox().setXRange(self.__stp_min_x, self.__stp_max_x) + + axis = self.__plotWidget__.getPlotItem().getAxis('bottom') + axis.setLabel(text=str(data['t'][0]), units='s') + axis.setHeight(h=0) def update_buffer_size(self, new_size): """ @@ -453,17 +528,6 @@ def update_buffer_size(self, new_size): self.__buffer_size__ = int(new_size) - # ------------------------------- - # Change Time Buffer - # ------------------------------- - -# self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) - - # ------------------------------- - # Change Buffer of current - # plotted signals - # ------------------------------- - start_size = len(self.__tbuffer__) for signal_name in self.signals: @@ -544,7 +608,6 @@ def add_plot_item(self, signal, signal_id): self.signals[signal_name] = plot_item - def remove_plot_item(self, signal_name): """ Remove the plot item object for a given signal_name. @@ -593,6 +656,12 @@ def get_pen(self, index): return pq.mkPen(color=color, style=style) def update_rolling_plot(self): + """ + Used to update the rolling plot by resolving all dependencies. + The configuration for the rolling plot depends on the current value in self.config['rolling_plot']['value'] + + :return: + """ value = self.config['rolling_plot']['value'] @@ -602,6 +671,10 @@ def update_rolling_plot(self): self.clear() + axis = self.__plotWidget__.getPlotItem().getAxis('bottom') + axis.setLabel(text='', units='') + axis.setHeight(h=None) + for signal_name in self.signals: self.signals[signal_name].rolling_plot = self.__rolling_plot__ @@ -610,7 +683,6 @@ def update_rolling_plot(self): self.__plotWidget__.addItem(self.__vertical_line__) - def use_range_for_x(self, value): """ @@ -637,65 +709,16 @@ def use_range_for_y(self, value): self.yRange_maxEdit.setText(range[1]) self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]), float(range[1])) - def setup_context_menu(self): """ :return: """ + self.custMenu = QtGui.QMenu("Options") self.axesMenu = QtGui.QMenu('Y-Axis') self.gridMenu = QtGui.QMenu('Grid') - - # --------------------------------------------------------- - # #### X-Range Actions - #self.xRange_Widget = QtGui.QWidget() - #self.xRange_Layout = QtGui.QVBoxLayout(self.xRange_Widget) - #self.xRange_Layout.setContentsMargins(2, 2, 2, 2) - #self.xRange_Layout.setSpacing(1) - - - ##### X Line Edits - # Layout - #self.xRange_EditWidget = QtGui.QWidget() - #self.xRange_EditLayout = QtGui.QHBoxLayout(self.xRange_EditWidget) - #self.xRange_EditLayout.setContentsMargins(2, 2, 2, 2) - #self.xRange_EditLayout.setSpacing(1) - - # get old values; - #reg = re.compile(r'(\d+\.\d+)') - #range = reg.findall(self.config['xRange']['value']) - #if len(range) == 2: - # x_min = range[0] - # x_max = range[1] - #else: - # x_min = '0.0' - # x_max = '1.0' - - - # Min - # self.xRange_minEdit = QtGui.QLineEdit() - # self.xRange_minEdit.setFixedWidth(80) - # self.xRange_minEdit.setText(x_min) - # self.xRange_minEdit.editingFinished.connect(self.contextMenu_xRange_toogle) - # # Max - # self.xRange_maxEdit = QtGui.QLineEdit() - # self.xRange_maxEdit.setFixedWidth(80) - # self.xRange_maxEdit.setText(x_max) - # self.xRange_maxEdit.editingFinished.connect(self.contextMenu_xRange_toogle) - # # addTo Layout - # self.xRange_EditLayout.addWidget(self.xRange_minEdit) - # self.xRange_EditLayout.addWidget(QtGui.QLabel('<')) - # self.xRange_EditLayout.addWidget(self.xRange_maxEdit) - # self.xRange_Layout.addWidget(self.xRange_EditWidget) - # - # # build Action - # self.xRange_Action = QtGui.QWidgetAction(self.__plotWidget__) - # self.xRange_Action.setDefaultWidget(self.xRange_Widget) - - - # --------------------------------------------------------------- ##### Y-Range Actions self.yRange_Widget = QtGui.QWidget() self.yRange_Layout = QtGui.QVBoxLayout(self.yRange_Widget) @@ -756,7 +779,8 @@ def setup_context_menu(self): self.rolling_Checkbox.stateChanged.connect(self.contextMenu_rolling_toogled) self.rolling_Checkbox_Action = QtGui.QWidgetAction(self.__plotWidget__) self.rolling_Checkbox_Action.setDefaultWidget(self.rolling_Checkbox) - + if self.__stp_active__: + self.rolling_Checkbox.setDisabled(True) ##### Build axes menu @@ -801,28 +825,34 @@ def setup_context_menu(self): def contextMenu_yAutoRangeButton_clicked(self): mi = None ma = None - for sig in self.signals: - graphics = self.signals[sig].graphics - buf = [] - for graphic in graphics: - buf.extend(graphic.y) - ma_buf = max(buf) - mi_buf = min(buf) - if ma is not None: - if ma_buf > ma: + if self.__stp_active__: + mi = self.__stp_min_y + ma = self.__stp_max_y + else: + for sig in self.signals: + graphics = self.signals[sig].graphics + buf = [] + for graphic in graphics: + buf.extend(graphic.y) + + ma_buf = max(buf) + mi_buf = min(buf) + if ma is not None: + if ma_buf > ma: + ma = ma_buf + else: ma = ma_buf - else: - ma = ma_buf - if mi is not None: - if mi_buf < mi: + if mi is not None: + if mi_buf < mi: + mi = mi_buf + else: mi = mi_buf - else: - mi = mi_buf ma = str(ma) mi = str(mi) + self.yRange_maxEdit.setText(ma) self.yRange_minEdit.setText(mi) self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') @@ -852,6 +882,12 @@ def contextMenu_yRange_toogle(self): self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') def update_signals(self): + """ + Used to update the signals as they are described in self.dplugin_info + + :return: + """ + subscriptions = self.dplugin_info.get_subscribtions() for dpluginsub_id in subscriptions: @@ -866,7 +902,12 @@ def update_signals(self): self.signals[signal_name].update_signal(signal) def update_legend(self): - # self.__plotWidget__.removeItem(self.__legend__) + """ + Used to update the legend. + + :return: + """ + self.__legend__.scene().removeItem(self.__legend__) del self.__legend__ @@ -887,9 +928,15 @@ def update_legend(self): self.__legend__.addItem(graphic, legend_name) def update_downsampling_rate(self): + """ + Used to update the downsampling rate by resolving all dependencies. + The new downsampling rate is taken by using the private attribute __downsampling_rate__. + + :return: + """ rate = self.__downsampling_rate__ - self.downsampling_rate_start = 0 + self.__downsampling_rate_start__ = 0 self.downsampling_rate = rate for signal_name in self.signals: @@ -953,15 +1000,6 @@ def get_plugin_configuration(self): :return {}: """ config = { - # 'label_y': { - # 'value': "amplitude, V", - # 'regex': '\w+,\s+\w+', - # 'display_text': 'Label-Y' - # }, 'label_x': { - # 'value': "time, s", - # 'regex': '\w+,\s*\w+', - # 'display_text': 'Label-X' - # }, 'x-grid': { 'value': "0", 'regex': '^(1|0)$', @@ -995,11 +1033,6 @@ def get_plugin_configuration(self): 'regex': '^(1|0)$', 'type': 'bool', 'display_text': 'Rolling Plot' - # }, 'xRange': { - # 'value': '[0.0 1.0]', - # 'regex': '(\d+\.\d+)', - # 'advanced': '1', - # 'display_text': 'x: range' }, 'yRange': { 'value': '[0.0 1.0]', 'regex': '(\d+\.\d+)', @@ -1021,6 +1054,7 @@ def clear(self): for signal_name in self.signals: self.signals[signal_name].clear() + class GraphicItem(pg.QtGui.QGraphicsPathItem): """ @@ -1034,7 +1068,6 @@ def __init__(self, x, y, counter, pen=pg.mkPen('r')): :return: """ - x = np.array(x[:])[np.newaxis, :] connect = np.ones(x.shape, dtype=bool) @@ -1075,8 +1108,6 @@ def __init__(self, signal, signal_id, max_elements): super(PlotItem,self).__init__() self.signal_name = signal.dname - #self.buffer = [] # buffer - self.buffer = collections.deque([0.0] * 0, max_elements) self.id = signal_id @@ -1117,34 +1148,8 @@ def add_data(self, elements): buffer = self.buffer - # TODO: Downsampling before extending the internal buffer - - - # if self.downsampling_rate_start >= len(elements): - # self.downsampling_rate_start -= len(elements) - # else: - # - # # print('AddData') - # # print('Start ' + str(self.downsampling_rate_start)) - # # print('Rate ' + str(self.downsampling_rate)) - # # print('Len ' + str(len(elements))) - # - # ds_elements = elements[self.downsampling_rate_start::self.downsampling_rate] - # self.downsampling_rate_start += self.downsampling_rate - len(ds_elements) - # # print(elements) - # # print(ds_elements) - # buffer.extend(ds_elements) - self.new_added_element += len(elements) - # if self.downsampling_rate_start < len(elements): - # ds_elements = elements[self.downsampling_rate_start:self.downsampling_rate:] - # - # if self.downsampling_rate > len(elements): - # self.downsampling_rate_start = self.downsampling_rate - len(elements) - # else: - # self.downsampling_rate_start -= len(elements) - buffer.extend(elements) def update_signal(self, new_signal): @@ -1184,84 +1189,12 @@ def create_graphics(self, xdata): self.amount_elements += counter - # shift_data = 0 - # if self.last_x is not None: - # if self.last_x in xdata: - # shift_data = list(xdata).index(self.last_x) - # - # print(shift_data) - - - - #xdata = xdata[::self.downsampling_rate] - amount_elements = len(xdata) - self.last_x = xdata[0] - # print( "Len Buffer " + str(len(self.buffer))) - # print( "TData" + str(len(tdata))) x_axis = np.array(xdata) y_axis = np.array(list(self.buffer)) - # print( "Len Buffer " + str(len(x_axis))) - # print( "TData" + str(len(y_axis))) - # - - # # Old Solution - # if False: - # - # if self.rolling_plot: - # #print('I keep it rolling') - # - # i_max = np.argmax(x_axis) - # i_min = np.argmin(x_axis) - # - # # Create two graphic objects due plot border - # if i_min > i_max: - # x_axis_1 = np.array(x_axis[:i_max+1]) - # x_axis_2 = np.array(x_axis[i_min:]) - # - # y_axis_1 = y_axis[:i_max+1] - # y_axis_2 = y_axis[i_min:] - # - # if len(self.graphics): - # prev_graphic = self.graphics[-1] - # prev_last_x = prev_graphic.last_x; - # prev_last_y = prev_graphic.last_y; - # - # x_axis_1 = np.insert(x_axis_1, 0, prev_last_x) - # y_axis_1 = np.insert(y_axis_1, 0, prev_last_y) - # - # graphic_1 = GraphicItem(x_axis_1, y_axis_1, len(y_axis_1) - 1, self.pen) - # graphic_2 = GraphicItem(x_axis_2, y_axis_2, len(y_axis_2), self.pen) - # - # self.graphics.append(graphic_1) - # self.graphics.append(graphic_2) - # - # del self.buffer - # self.buffer = [] - # - # return - # # Connect with previous graphic if a previous graphic exists - # - # if len(self.graphics): - # prev_graphic = self.graphics[-1] - # prev_last_x = prev_graphic.last_x; - # prev_last_y = prev_graphic.last_y; - # - # if prev_last_x < x_axis[0]: - # x_axis = np.insert(x_axis, 0, prev_last_x) - # y_axis = np.insert(y_axis, 0, prev_last_y) - # - # graphic = GraphicItem(x_axis, y_axis, counter, pen=self.pen) - # - # del self.buffer - # self.buffer = [] - # - # self.graphics.append(graphic) - if self.rolling_plot: - #print('First X_Value' + str(xdata)) i_max = np.argmax(x_axis) i_min = np.argmin(x_axis) @@ -1304,14 +1237,8 @@ def get_old_graphics(self): :return: """ - - res_graphic = [] - - # if len(self.graphics): - # res_graphic.append(self.graphics.pop(0)) - for i in range(len(self.graphics)): graphic = self.graphics[0] @@ -1324,23 +1251,6 @@ def get_old_graphics(self): return res_graphic - # # print(self.amount_elements) - # # print(len(self.graphics)) - # - # while self.amount_elements >= self.max_elements: - # - # graphic = self.graphics[0] - # - # if graphic.not_drawn: - # break - # - # graphic = self.graphics.pop(0) - # self.amount_elements -= graphic.length() - # - # res_graphic.append(graphic) - # - # return res_graphic - def clear(self): """ From b85da7cec5f553cdebd724a70665e38dd42cd5a4 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 3 Mar 2015 16:36:58 +0100 Subject: [PATCH 141/260] changes in plot plugin + new kind of rolling plot + minor changes to improve performance --- papi/plugin/visual/Plot/Plot.py | 137 ++++++++++++++++++++------------ 1 file changed, 84 insertions(+), 53 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index 7735aed6..ef64778b 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -96,7 +96,7 @@ def __init__(self, debug=False): self.__plotWidget__ = None self.__legend__ = None self.__text_item__ = None - self.__vertical_line__ = None + self.__offset_line__ = None self.__y_axis__ = None self.__x_axis__ = None @@ -172,23 +172,38 @@ def initiate_layer_0(self, config=None): self.__stp_active__ = False + + # -------------------------------- - # Create PlotWidget + # Create Layout # -------------------------------- - self.__text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(1, 0)) - - self.__vertical_line__ = pq.InfiniteLine() # self.__plotWidget__ = pq.PlotWidget() - self.__plotWidget__ = PlotWidget() + # Create internal layout + self.central_widget = QtGui.QWidget() + self.central_widget.setContentsMargins(0,0,0,0) - self.__plotWidget__.addItem(self.__text_item__) + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setContentsMargins(0,0,0,0) + self.verticalLayout.setSpacing(0) - if self.__rolling_plot__: - self.__plotWidget__.addItem(self.__vertical_line__) + self.time_label = QtGui.QLabel() + self.time_label.setMargin(0) + self.time_label.setAlignment(Qt.AlignHCenter) + self.time_label.setStyleSheet("QLabel { background-color : black; color : grey;" + "border : 1px solid black ; border-bottom-width : 5px }") + + self.central_widget.setLayout(self.verticalLayout) + + # -------------------------------- + # Create PlotWidget + # -------------------------------- + + self.__plotWidget__ = PlotWidget() + self.verticalLayout.addWidget(self.__plotWidget__) + self.verticalLayout.addWidget(self.time_label) - self.__text_item__.setPos(0, 0) self.__plotWidget__.setWindowTitle('PlotPerformanceTitle') self.__plotWidget__.showGrid(x=self.__show_grid_x__, y=self.__show_grid_y__) @@ -197,8 +212,11 @@ def initiate_layer_0(self, config=None): self.__plotWidget__.getPlotItem().setDownsampling(auto=True) + + if not self.__papi_debug__: - self.set_widget_for_internal_usage(self.__plotWidget__) +# self.set_widget_for_internal_usage(self.__plotWidget__) + self.set_widget_for_internal_usage(self.central_widget) self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.YAxis, enable=False) self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.XAxis, enable=False) @@ -252,7 +270,7 @@ def initiate_layer_0(self, config=None): # Initiate for default plotting # ---------------------------- - self.initate_update_plot() + self.initiate_update_plot() return True @@ -316,9 +334,7 @@ def execute(self, Data=None, block_name=None): if self.__input_size__ > 1 or self.__signals_have_same_length: if self.__stp_active__: - self.initate_update_plot() - - self.__stp_active__ = False + self.initiate_update_plot() if current_milli_time() - self.__last_time__ > self.__update_intervall__ - self.__last_plot_time__: self.__last_time__ = current_milli_time() @@ -330,10 +346,14 @@ def execute(self, Data=None, block_name=None): if not self.__stp_active__ : self.initate_update_plot_single_timestamp() - self.__stp_active__ = True + if current_milli_time() - self.__last_time__ > self.__update_intervall__ - self.__last_plot_time__: + self.__last_time__ = current_milli_time() + + self.update_plot_single_timestamp(Data) + self.__last_time__ = current_milli_time() + self.__new_added_data__ = 0 - self.update_plot_single_timestamp(Data) # @@ -403,8 +423,14 @@ def update_pens(self): signal_id = self.signals[signal_name].id new_pen = self.get_pen(signal_id) + other_pen = self.get_pen(signal_id) + + o_color = other_pen.color() + o_color.setAlpha(100) + other_pen.setColor(o_color) self.signals[signal_name].pen = new_pen + self.signals[signal_name].other_pen = other_pen def update_plot(self): """ @@ -413,7 +439,6 @@ def update_plot(self): :return: """ - if len(self.__tbuffer__) == 0: return @@ -443,36 +468,36 @@ def update_plot(self): for graphic in graphics: self.__plotWidget__.addItem(graphic) - # Set positon for vertical line - if self.__rolling_plot__: - self.__vertical_line__.setValue(graphic.last_x) - self.__last_plot_time__ = pg.ptime.time()-now if self.__rolling_plot__: self.__plotWidget__.getPlotItem().getViewBox().setXRange(0, len(tdata)-1) - axis = self.__plotWidget__.getPlotItem().getAxis('bottom') - axis.setLabel(text=str(self.__tbuffer__[-1]), units='s') - + self.time_label.setText(str(self.__tbuffer__[-1]) + ' [s]') else: self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0], tdata[-1]) # if self.__papi_debug__: # print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) - def initate_update_plot(self): - print('initaed update plot') - axis = self.__plotWidget__.getPlotItem().getAxis('bottom') - axis.setLabel(text='', units='') - axis.setHeight(h=None) + def initiate_update_plot(self): + + print('initiate_update_plot') + self.__stp_active__ = False if self.__rolling_plot__: - self.__plotWidget__.addItem(self.__vertical_line__) + self.time_label.setHidden(False) + else: + self.time_label.setHidden(True) + pass def initate_update_plot_single_timestamp(self): - pass + print('initiate_update_plot_single_timestamp') + self.__stp_active__ = True + + self.time_label.setHidden(False) + def update_plot_single_timestamp(self, data): """ @@ -481,7 +506,6 @@ def update_plot_single_timestamp(self, data): :return: """ - self.__text_item__.setText("Time " + str(data['t'][0]), color=(200, 200, 200)) self.__plotWidget__.clear() cur_max_y = 0 @@ -514,9 +538,7 @@ def update_plot_single_timestamp(self, data): self.__plotWidget__.getPlotItem().getViewBox().setXRange(self.__stp_min_x, self.__stp_max_x) - axis = self.__plotWidget__.getPlotItem().getAxis('bottom') - axis.setLabel(text=str(data['t'][0]), units='s') - axis.setHeight(h=0) + self.time_label.setText(str(data['t'][0]) + ' [s]') def update_buffer_size(self, new_size): """ @@ -544,7 +566,8 @@ def update_buffer_size(self, new_size): def plugin_meta_updated(self): """ - By this function the plot is able to handle more than one input for plotting. + This function is called whenever meta information are changed. + This enables the plot to handle more than one input for plotting. :return: """ @@ -568,16 +591,18 @@ def plugin_meta_updated(self): current_signals[signal_name]['signal'] = signal current_signals[signal_name]['index'] = index - # current_signals = sorted(current_signals) - - # Add missing buffers + # ---------------------------- + # Add new subscribed signals + # ---------------------------- for signal_name in sorted(current_signals.keys()): if signal_name not in self.signals: signal = current_signals[signal_name]['signal'] self.add_plot_item(signal, current_signals[signal_name]['index']) changes = True - # Delete old buffers + # ------------------------------- + # Remove unsubscribed signals + # ------------------------------- for signal_name in self.signals.copy(): if signal_name not in current_signals: self.remove_plot_item(signal_name) @@ -585,9 +610,13 @@ def plugin_meta_updated(self): if changes: self.update_pens() + self.update_signals() self.update_legend() self.update_rolling_plot() self.update_downsampling_rate() + else: + self.update_signals() + self.update_legend() def add_plot_item(self, signal, signal_id): """ @@ -671,17 +700,11 @@ def update_rolling_plot(self): self.clear() - axis = self.__plotWidget__.getPlotItem().getAxis('bottom') - axis.setLabel(text='', units='') - axis.setHeight(h=None) for signal_name in self.signals: self.signals[signal_name].rolling_plot = self.__rolling_plot__ - if self.__rolling_plot__: - # if self.__vertical_line__ not in self.__plotWidget__.listDataItems(): - - self.__plotWidget__.addItem(self.__vertical_line__) + self.initiate_update_plot() def use_range_for_x(self, value): """ @@ -704,6 +727,7 @@ def use_range_for_y(self, value): """ reg = re.compile(r'([-]{0,1}\d+\.\d+)') range = reg.findall(value) + print(range) if len(range) == 2: self.yRange_minEdit.setText(range[0]) self.yRange_maxEdit.setText(range[1]) @@ -855,7 +879,7 @@ def contextMenu_yAutoRangeButton_clicked(self): self.yRange_maxEdit.setText(ma) self.yRange_minEdit.setText(mi) - self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') + self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + str(float(mi)) + ' ' + str(float(ma)) + ']') def contextMenu_rolling_toogled(self): if self.rolling_Checkbox.isChecked(): @@ -879,7 +903,7 @@ def contextMenu_yRange_toogle(self): mi = self.yRange_minEdit.text() ma = self.yRange_maxEdit.text() if float(mi) < float(ma): - self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']') + self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + float(mi) + ' ' + float(ma) + ']') def update_signals(self): """ @@ -1057,7 +1081,7 @@ def clear(self): class GraphicItem(pg.QtGui.QGraphicsPathItem): """ - + Represents a single object which is drawn by a plot. """ def __init__(self, x, y, counter, pen=pg.mkPen('r')): """ @@ -1094,7 +1118,7 @@ def boundingRect(self): class PlotItem(object): """ - This object is used to manage the graphic items for a single signal object. + This object is used to manage a single signal object. """ def __init__(self, signal, signal_id, max_elements): @@ -1121,6 +1145,7 @@ def __init__(self, signal, signal_id, max_elements): self.max_elements = max_elements self.pen = None + self.other_pen = None self.graphics = [] self.rolling_plot = None self.last_x = None @@ -1140,6 +1165,7 @@ def get_legend_item(self): def add_data(self, elements): """ + Used to add data which a sent for a given signal object. :param elements: :param tdata: @@ -1154,6 +1180,7 @@ def add_data(self, elements): def update_signal(self, new_signal): """ + Used to update the corresponding signal object. :param new_signal: :return: @@ -1164,6 +1191,7 @@ def update_signal(self, new_signal): def set_downsampling_rate(self, rate): """ + Used to set the current downsampling rate. :param rate: :return: @@ -1174,6 +1202,7 @@ def set_downsampling_rate(self, rate): def create_graphics(self, xdata): """ + Creates the needed graphics which can be drawn by the plot object for a given x-axis described by xdata. :param xdata: :return: @@ -1205,7 +1234,8 @@ def create_graphics(self, xdata): y_axis_1 = y_axis[:i_max+1] y_axis_2 = y_axis[i_min:] - graphic_1 = GraphicItem(x_axis_1, y_axis_1, len(y_axis_1) - 1, self.pen) + graphic_1 = GraphicItem(x_axis_1, y_axis_1, len(y_axis_1) - 1, self.other_pen) + graphic_2 = GraphicItem(x_axis_2, y_axis_2, len(y_axis_2), self.pen) self.graphics.append(graphic_1) @@ -1218,6 +1248,7 @@ def create_graphics(self, xdata): def get_graphics(self): """ + Returns all graphics which still need to be drawn. :return: """ From a76f3a2bdf6c7638e582762c7f2a539e7c16e7e3 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 3 Mar 2015 17:34:57 +0100 Subject: [PATCH 142/260] minor changes, cleanup --- papi/plugin/visual/Plot/Plot.py | 124 ++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 54 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index ef64778b..99f612fa 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -39,6 +39,7 @@ import copy import time import papi.pyqtgraph as pg +import papi.constants as pc current_milli_time = lambda: int(round(time.time() * 1000)) @@ -78,9 +79,9 @@ def __init__(self, debug=False): self.__buffer_size__ = None self.__downsampling_rate__ = 1 self.__tbuffer__ = [] - self.__tdata_old__ = [0] + self.__signals_have_same_length = True - self.__roll_shift__ = None + self.__append_at__ = 1 self.__new_added_data__ = 0 self.__input_size__ = 0 @@ -95,12 +96,7 @@ def __init__(self, debug=False): self.__last_plot_time__ = None self.__plotWidget__ = None self.__legend__ = None - self.__text_item__ = None - self.__offset_line__ = None - self.__y_axis__ = None - self.__x_axis__ = None - self.__amount_of_slices__ = None self.__stp_min_x = None self.__stp_max_x = None @@ -109,7 +105,7 @@ def __init__(self, debug=False): self.__stp_active__ = None self.__downsampling_rate_start__ = None; - self.downsampling_rate = None + self.__downsampling_rate__ = None self.styles = { 0: QtCore.Qt.SolidLine, @@ -151,14 +147,12 @@ def initiate_layer_0(self, config=None): self.__downsampling_rate__ = int(int_re.findall(self.config['downsampling_rate']['value'])[0]) self.__downsampling_rate_start__ = 0 - self.downsampling_rate = self.__downsampling_rate__ # ---------------------------- # Set internal variables # ---------------------------- self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__) - self.__amount_of_slices__ = 10 # ---------------------------- # Set internal variables used for single timestamp plotting (stp) @@ -173,36 +167,54 @@ def initiate_layer_0(self, config=None): self.__stp_active__ = False - # -------------------------------- - # Create Layout + # Create Layout and labels # -------------------------------- -# self.__plotWidget__ = pq.PlotWidget() - - # Create internal layout self.central_widget = QtGui.QWidget() self.central_widget.setContentsMargins(0,0,0,0) + self.label_widget = QtGui.QWidget() + self.label_widget.setContentsMargins(0,0,0,0) + self.verticalLayout = QtGui.QVBoxLayout() self.verticalLayout.setContentsMargins(0,0,0,0) self.verticalLayout.setSpacing(0) + self.horizontalLayout = QtGui.QHBoxLayout() + self.horizontalLayout.setContentsMargins(0,0,0,0) + self.horizontalLayout.setSpacing(0) + + self.space_label = QtGui.QLabel() + self.space_label.setMargin(0) + self.space_label.setAlignment(Qt.AlignJustify) + self.space_label.setStyleSheet("QLabel { background-color : black; color : grey;" + "border : 0px solid black ; border-bottom-width : 5px }") + self.time_label = QtGui.QLabel() self.time_label.setMargin(0) - self.time_label.setAlignment(Qt.AlignHCenter) + self.time_label.setAlignment(Qt.AlignJustify) self.time_label.setStyleSheet("QLabel { background-color : black; color : grey;" - "border : 1px solid black ; border-bottom-width : 5px }") + "border : 0px solid black ; border-bottom-width : 5px }") + self.time_label.setMaximumWidth(55) + + + self.unit_label = QtGui.QLabel() + self.unit_label.setMargin(0) + self.unit_label.setAlignment(Qt.AlignLeft) + self.unit_label.setStyleSheet("QLabel { background-color : black; color : grey;" + "border : 0px solid black ; border-bottom-width : 5px }") + + self.unit_label.setText('[s]') self.central_widget.setLayout(self.verticalLayout) + self.label_widget.setLayout(self.horizontalLayout) # -------------------------------- # Create PlotWidget # -------------------------------- self.__plotWidget__ = PlotWidget() - self.verticalLayout.addWidget(self.__plotWidget__) - self.verticalLayout.addWidget(self.time_label) self.__plotWidget__.setWindowTitle('PlotPerformanceTitle') @@ -212,6 +224,15 @@ def initiate_layer_0(self, config=None): self.__plotWidget__.getPlotItem().setDownsampling(auto=True) + # ------------------------------ + # Add Widget to Layout + # ------------------------------ + self.horizontalLayout.addWidget(self.space_label) + self.horizontalLayout.addWidget(self.time_label) + self.horizontalLayout.addWidget(self.unit_label) + + self.verticalLayout.addWidget(self.__plotWidget__) + self.verticalLayout.addWidget(self.label_widget) if not self.__papi_debug__: @@ -228,16 +249,16 @@ def initiate_layer_0(self, config=None): # --------------------------- self.__parameters__['x-grid'] = \ - DParameter('x-grid', 0, Regex='^(1|0){1}$') + DParameter('x-grid', self.config['x-grid']['value'], Regex='^(1|0){1}$') self.__parameters__['y-grid'] = \ - DParameter('y-grid', 0, Regex='^(1|0){1}$') + DParameter('y-grid', self.config['y-grid']['value'], Regex='^(1|0){1}$') self.__parameters__['color'] = \ - DParameter('color', '[0 1 2 3 4]', Regex='^\[(\s*\d\s*)+\]') + DParameter('color', self.config['color']['value'], Regex='^\[(\s*\d\s*)+\]') self.__parameters__['style'] = \ - DParameter('style', '[0 0 0 0 0]', Regex='^\[(\s*\d\s*)+\]') + DParameter('style', self.config['style']['value'], Regex='^\[(\s*\d\s*)+\]') self.__parameters__['rolling'] = \ - DParameter('rolling', '0', Regex='^(1|0){1}') + DParameter('rolling', self.config['rolling_plot']['value'], Regex='^(1|0){1}') self.__parameters__['downsampling_rate'] = \ DParameter('downsampling_rate', self.__downsampling_rate__, Regex='^\d+$') @@ -245,7 +266,7 @@ def initiate_layer_0(self, config=None): DParameter('buffersize', self.__buffer_size__, Regex='^\d+$') self.__parameters__['yRange'] = \ - DParameter('yRange', '[0,1]', Regex='(\d+\.\d+)') + DParameter('yRange', '[0,1]', Regex='^\[(\d+\.\d+)\s+(\d+\.\d+)\]$') if not self.__papi_debug__: self.send_new_parameter_list(list(self.__parameters__.values())) @@ -281,7 +302,6 @@ def pause(self): :return: """ self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=True, y=True) - #print('PlotPerformance paused') def resume(self): """ @@ -290,7 +310,6 @@ def resume(self): :return: """ self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=False, y=True) - #print('PlotPerformance resumed') def execute(self, Data=None, block_name=None): """ @@ -314,7 +333,7 @@ def execute(self, Data=None, block_name=None): y = Data[key] if key in self.signals: if self.__downsampling_rate_start__ < len(y): - ds_y = y[self.__downsampling_rate_start__::self.downsampling_rate] + ds_y = y[self.__downsampling_rate_start__::self.__downsampling_rate__] self.signals[key].add_data(ds_y) @@ -323,8 +342,8 @@ def execute(self, Data=None, block_name=None): if self.__downsampling_rate_start__ >= len(t): self.__downsampling_rate_start__ -= len(t) else: - ds_t = t[self.__downsampling_rate_start__::self.downsampling_rate] - self.__downsampling_rate_start__ += self.downsampling_rate - len(ds_t) + ds_t = t[self.__downsampling_rate_start__::self.__downsampling_rate__] + self.__downsampling_rate_start__ += self.__downsampling_rate__ - len(ds_t) self.__tbuffer__.extend(ds_t) self.__new_added_data__ += len(t) @@ -344,7 +363,7 @@ def execute(self, Data=None, block_name=None): else: if not self.__stp_active__ : - self.initate_update_plot_single_timestamp() + self.initiate_update_plot_single_timestamp() if current_milli_time() - self.__last_time__ > self.__update_intervall__ - self.__last_plot_time__: self.__last_time__ = current_milli_time() @@ -354,9 +373,6 @@ def execute(self, Data=None, block_name=None): self.__last_time__ = current_milli_time() self.__new_added_data__ = 0 - - - # # print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) def set_parameter(self, name, value): @@ -472,8 +488,7 @@ def update_plot(self): if self.__rolling_plot__: self.__plotWidget__.getPlotItem().getViewBox().setXRange(0, len(tdata)-1) - - self.time_label.setText(str(self.__tbuffer__[-1]) + ' [s]') + self.time_label.setNum(self.__tbuffer__[-1]) else: self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0], tdata[-1]) @@ -481,8 +496,11 @@ def update_plot(self): # print("Plot time: %0.5f sec" % (self.__last_plot_time__) ) def initiate_update_plot(self): + """ + To all needed changes to use default plotting - print('initiate_update_plot') + :return: + """ self.__stp_active__ = False if self.__rolling_plot__: @@ -492,13 +510,17 @@ def initiate_update_plot(self): pass - def initate_update_plot_single_timestamp(self): - print('initiate_update_plot_single_timestamp') + def initiate_update_plot_single_timestamp(self): + """ + To all needed changes to use single timestamp plotting + + :return: + """ + self.__stp_active__ = True self.time_label.setHidden(False) - def update_plot_single_timestamp(self, data): """ Function update_plot_single_timestamp @@ -538,7 +560,7 @@ def update_plot_single_timestamp(self, data): self.__plotWidget__.getPlotItem().getViewBox().setXRange(self.__stp_min_x, self.__stp_max_x) - self.time_label.setText(str(data['t'][0]) + ' [s]') + self.time_label.setNum(data['t'][0]) def update_buffer_size(self, new_size): """ @@ -586,10 +608,10 @@ def plugin_meta_updated(self): for signal_name in subscription.get_signals(): signal = subscription.get_dblock().get_signal_by_uname(signal_name) - index += 1 current_signals[signal_name] = {} current_signals[signal_name]['signal'] = signal current_signals[signal_name]['index'] = index + index += 1 # ---------------------------- # Add new subscribed signals @@ -675,12 +697,12 @@ def get_pen(self, index): if style_code in self.styles: style = self.styles[style_code] else: - style = self.styles[1] + style = self.styles[0] if color_code in self.colors: color = self.colors[color_code] else: - color = self.colors[1] + color = self.colors[0] return pq.mkPen(color=color, style=style) @@ -727,7 +749,7 @@ def use_range_for_y(self, value): """ reg = re.compile(r'([-]{0,1}\d+\.\d+)') range = reg.findall(value) - print(range) + if len(range) == 2: self.yRange_minEdit.setText(range[0]) self.yRange_maxEdit.setText(range[1]) @@ -961,7 +983,7 @@ def update_downsampling_rate(self): rate = self.__downsampling_rate__ self.__downsampling_rate_start__ = 0 - self.downsampling_rate = rate + self.__downsampling_rate__ = rate for signal_name in self.signals: self.signals[signal_name].set_downsampling_rate(rate) @@ -1306,6 +1328,7 @@ def get_buffersize(self): else: return self.max_elements + class PlotWidget(pg.PlotWidget): def __init__(self): @@ -1318,16 +1341,9 @@ def enablePainting(self): def disablePainting(self): self.paint = False - # def refreshPlot(self): - # self.enablePainting() - # super(PlotWidget, self).repaint() - # self.disablePainting() - def paintEvent(self, ev): if self.paint: self.scene().prepareForPaint() return QtGui.QGraphicsView.paintEvent(self, ev) -# if self.paint: -# print('REPAINT !!') - # super(PlotWidget, self).paintEvent(ev) + From 2069f41b883a1f08b1b07083f1ec1eae988709fa Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 3 Mar 2015 18:00:40 +0100 Subject: [PATCH 143/260] updated ORTD --- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 307 ++++++++++++++++++++++------ 1 file changed, 246 insertions(+), 61 deletions(-) diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index d3eb1a48..44ec52bb 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -103,79 +103,79 @@ def start_init(self, config=None): self.HOST = config['address']['value'] self.SOURCE_PORT = int(config['source_port']['value']) self.OUT_PORT = int(config['out_port']['value']) - self.separate = int(config['SeparateSignals']['value']) + #self.separate = int(config['SeparateSignals']['value']) # SOCK_DGRAM is the socket type to use for UDP sockets self.sock_parameter = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock_parameter.setblocking(1) # Load protocol config. - path = config['Cfg_Path']['value'] - f = open(path, 'r') - self.ProtocolConfig = json.load(f) - self.Sources = self.ProtocolConfig['SourcesConfig'] - self.Parameters = self.ProtocolConfig['ParametersConfig'] + #path = config['Cfg_Path']['value'] + #f = open(path, 'r') + #self.ProtocolConfig = json.load(f) + #self.Sources = self.ProtocolConfig['SourcesConfig'] + #self.Parameters = self.ProtocolConfig['ParametersConfig'] # For each group:: loop through all sources (=signals) in the group and register the signals # Register signals - if self.separate == 1: - - self.blocks = {} - - # sort hash keys for usage in right order! - keys = list(self.Sources.keys()) - #keys.sort() - for key in keys: - Source = self.Sources[key] - block = DBlock('SourceGroup' + str(key)) - block.add_signal(DSignal(Source['SourceName'])) - self.blocks[int(key)] = block - - - self.send_new_block_list(list(self.blocks.values())) - - else: - self.block1 = DBlock('SourceGroup0') - - keys = list(self.Sources.keys()) - #keys.sort() - for key in keys: - Source = self.Sources[key] - sig_name = Source['SourceName'] - self.block1.add_signal(DSignal(sig_name)) - - - - - - self.ControlBlock = DBlock('ControllerSignals') - self.ControlBlock.add_signal(DSignal('ControlSignalReset')) - self.ControlBlock.add_signal(DSignal('ControlSignalCreate')) - self.ControlBlock.add_signal(DSignal('ControlSignalSub')) - self.ControlBlock.add_signal(DSignal('ControllerSignalParameter')) - self.ControlBlock.add_signal(DSignal('ControllerSignalClose')) - - #self.block1 = DBlock(None, 1, 2, 'SourceGroup0', names) - self.send_new_block_list([self.block1, self.ControlBlock]) + # if self.separate == 1: + # + # self.blocks = {} + # + # # sort hash keys for usage in right order! + # keys = list(self.Sources.keys()) + # #keys.sort() + # for key in keys: + # Source = self.Sources[key] + # block = DBlock('SourceGroup' + str(key)) + # block.add_signal(DSignal(Source['SourceName'])) + # self.blocks[int(key)] = block + # + # + # self.send_new_block_list(list(self.blocks.values())) + # + # else: + # self.block1 = DBlock('SourceGroup0') + # + # keys = list(self.Sources.keys()) + # #keys.sort() + # for key in keys: + # Source = self.Sources[key] + # sig_name = Source['SourceName'] + # self.block1.add_signal(DSignal(sig_name)) + + + + + + # self.ControlBlock = DBlock('ControllerSignals') + # self.ControlBlock.add_signal(DSignal('ControlSignalReset')) + # self.ControlBlock.add_signal(DSignal('ControlSignalCreate')) + # self.ControlBlock.add_signal(DSignal('ControlSignalSub')) + # self.ControlBlock.add_signal(DSignal('ControllerSignalParameter')) + # self.ControlBlock.add_signal(DSignal('ControllerSignalClose')) + # + # #self.block1 = DBlock(None, 1, 2, 'SourceGroup0', names) + # self.send_new_block_list([self.block1, self.ControlBlock]) # Register parameters - self.Parameter_List = [] - - for Pid in self.Parameters: - Para = self.Parameters[Pid] - para_name = Para['ParameterName'] - val_count = Para['NValues'] - opt_object = OptionalObject(Pid, val_count) - Parameter = DParameter(para_name, default=0, OptionalObject=opt_object) - self.Parameter_List.append(Parameter) - - self.ControlParameter = DParameter('triggerConfiguration',default=0) - self.Parameter_List.append(self.ControlParameter) - - self.send_new_parameter_list(self.Parameter_List) + #self.Parameter_List = [] + # + # for Pid in self.Parameters: + # Para = self.Parameters[Pid] + # para_name = Para['ParameterName'] + # val_count = Para['NValues'] + # opt_object = OptionalObject(Pid, val_count) + # Parameter = DParameter(para_name, default=0, OptionalObject=opt_object) + # self.Parameter_List.append(Parameter) + # + # self.ControlParameter = DParameter('triggerConfiguration',default=0) + # self.Parameter_List.append(self.ControlParameter) + + #self.send_new_parameter_list(self.Parameter_List) self.t = 0 @@ -187,10 +187,13 @@ def start_init(self, config=None): self.thread_goOn = True self.lock = threading.Lock() - # self.thread = threading.Thread(target=self.thread_execute, args=(self.HOST, self.SOURCE_PORT) ) self.thread = threading.Thread(target=self.thread_execute) self.thread.start() + #self.process_received_package(None) + + + return True def pause(self): @@ -206,6 +209,152 @@ def resume(self): def thread_execute(self): goOn = True + signal_values = {} + while goOn: + try: + rev = self.sock_recv.recv(1600) + except socket.timeout: + # print('timeout') + pass + except socket.error: + print('ORTD got socket error') + else: + self.process_received_package(rev) + + # check if thread should go on + self.lock.acquire() + goOn = self.thread_goOn + self.lock.release() + # Thread ended + self.sock_recv.close() + + def process_received_package(self, rev): + SenderId, Counter, SourceId = struct.unpack_from('= 0: + # got data stream + #self.process_data_stream(SourceId, rev, signal_values) + pass + if SourceId == -2: + # new config in ORTD available + # send trigger to get new config + print('newcfg') + Counter = 1 + data = struct.pack(' Date: Tue, 3 Mar 2015 18:02:33 +0100 Subject: [PATCH 144/260] fixed the always close issue fixed #28 --- papi/gui/qt_new/PapiTabManger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index 9103ca7d..b2a16c08 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -156,7 +156,7 @@ def closeTab_by_ind(self, ind): dplugin = allPlugins[pl_id] if dplugin.own_process is False: self.gui_api.do_delete_plugin(dplugin.id) - self.remove_tab(tabOb) + self.remove_tab(tabOb) def closeTab_by_name(self,name): if name in self.tab_dict_uname: From 1efcee7a1c5b2cff7d3bd5867456635cd89bb99d Mon Sep 17 00:00:00 2001 From: SvKn Date: Tue, 3 Mar 2015 18:11:57 +0100 Subject: [PATCH 145/260] Update Plot.yapsy-plugin --- papi/plugin/visual/Plot/Plot.yapsy-plugin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/papi/plugin/visual/Plot/Plot.yapsy-plugin b/papi/plugin/visual/Plot/Plot.yapsy-plugin index a1f8e24d..2c115296 100644 --- a/papi/plugin/visual/Plot/Plot.yapsy-plugin +++ b/papi/plugin/visual/Plot/Plot.yapsy-plugin @@ -4,6 +4,6 @@ Module = Plot [Documentation] Author = S.R., S.K. -Version = 0.7 +Version = 1.0 Website = -Description = Basic plotting plugin. Supports more than one signals which must have the same time vector. \ No newline at end of file +Description = Basic plotting plugin. Supports more than one signals which must have the same time vector. Enabled single timestamp plotting and rolling plot. From 689c7e84cc8d0364885a16261a45c9c8fb0f73f5 Mon Sep 17 00:00:00 2001 From: SvKn Date: Tue, 3 Mar 2015 18:14:09 +0100 Subject: [PATCH 146/260] Update CHANGENLOG.md --- CHANGENLOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index a641aa3f..57ffbcc4 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -9,9 +9,10 @@ v.1.XX * [fix]: renamed some variables of ownProcess_base to be private * [fix]: fixed memory leak of gui event processing timer loop (#25) * [improvement]: changed the demux function to in imporve performance - * [improvement]: imporved core performance while processing new data (see #20) + * [improvement]: improved core performance while processing new data (see #20) + * [improvement]: huge changes in the plotting plugin with performance benefits * [feature]: modified PaPI gui to have tabs. User can organise visual plugins in tabs, remove tabs, rename tabs. - + v.1.0 --- From 9e847534babda1a47c4d11bdc53cc49d71c58878 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 3 Mar 2015 18:32:52 +0100 Subject: [PATCH 147/260] ortd --- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 44ec52bb..4d56b69c 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -232,7 +232,6 @@ def process_received_package(self, rev): SenderId, Counter, SourceId = struct.unpack_from(' Date: Wed, 4 Mar 2015 13:25:33 +0100 Subject: [PATCH 148/260] ortd --- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 159 ++++++++++++++++------------ 1 file changed, 91 insertions(+), 68 deletions(-) diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 4d56b69c..a0a1b67a 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -61,6 +61,7 @@ import struct import json +import pickle class OptionalObject(object): def __init__(self, ORTD_par_id, nvalues): @@ -103,7 +104,7 @@ def start_init(self, config=None): self.HOST = config['address']['value'] self.SOURCE_PORT = int(config['source_port']['value']) self.OUT_PORT = int(config['out_port']['value']) - #self.separate = int(config['SeparateSignals']['value']) + self.separate = int(config['SeparateSignals']['value']) # SOCK_DGRAM is the socket type to use for UDP sockets self.sock_parameter = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) @@ -192,7 +193,14 @@ def start_init(self, config=None): #self.process_received_package(None) + self.blocks = {} + self.Sources = {} + self.parameters = {} + + self.signal_values = {} + + self.block_id = 0 return True @@ -209,17 +217,22 @@ def resume(self): def thread_execute(self): goOn = True + newData = False signal_values = {} while goOn: try: rev = self.sock_recv.recv(1600) except socket.timeout: # print('timeout') - pass + newData = False + except socket.error: print('ORTD got socket error') else: - self.process_received_package(rev) + newData = True + + if newData: + self.process_received_package(rev) # check if thread should go on self.lock.acquire() @@ -230,19 +243,18 @@ def thread_execute(self): def process_received_package(self, rev): SenderId, Counter, SourceId = struct.unpack_from('= 0: # got data stream - #self.process_data_stream(SourceId, rev, signal_values) - pass + self.process_data_stream(SourceId, rev) + if SourceId == -2: # new config in ORTD available @@ -258,71 +270,79 @@ def process_received_package(self, rev): if SourceId == -4: # new configItem # receive new config item and execute cfg in PaPI - - print(rev) - print(len(rev)) - - #SenderId, Counter, SourceId = struct.unpack_from(' Date: Wed, 4 Mar 2015 15:02:46 +0100 Subject: [PATCH 149/260] =?UTF-8?q?Arne's=20Beispiel=20f=C3=BCr=20autom.?= =?UTF-8?q?=20umschaltende=20Schaltbilder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SelectCasePaPiConfig.ipar | 13892 ++++++++++++++++ .../SelectCasePaPiConfig.rpar | 26 + .../SelectCasePaPiConfig.sce | 326 + .../run_SelectCasePaPiConfig.sh | 4 + .../webinterface/PacketFramework.sce | 1108 ++ .../ORTD/DataSourceExample_Groups/PF.sci | 9 +- 6 files changed, 15363 insertions(+), 2 deletions(-) create mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar create mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar create mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce create mode 100755 data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh create mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar new file mode 100644 index 00000000..d76470ea --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar @@ -0,0 +1,13892 @@ + 1 + 1 + 901 + 10 + 0 + 0 + 13884 + 26 + 1 + 3 + 201 + 100 + 0 + 0 + 6 + 0 + 203 + 100 + 6 + 0 + 13846 + 26 + 100 + 101 + 13852 + 26 + 12 + 0 + 60023 + 201 + 0 + 0 + 1 + 0 + 15011 + 203 + 13840 + 26 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 13777 + 0 + 21 + 1 + 13789 + 0 + 1 + 26 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 13776 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 21 + 4 + 4 + 0 + 19 + 0 + 22 + 4 + 23 + 0 + 4 + 0 + 900 + 10 + 27 + 0 + 13705 + 26 + 0 + 0 + 0 + 0 + 18 + 77 + 97 + 105 + 110 + 82 + 101 + 97 + 108 + 116 + 105 + 109 + 101 + 84 + 104 + 114 + 101 + 97 + 100 + 3 + 2 + 0 + -1 + 1 + 6 + 201 + 100 + 0 + 0 + 6 + 1 + 203 + 100 + 6 + 1 + 72 + 0 + 205 + 100 + 78 + 1 + 6 + 1 + 207 + 100 + 84 + 2 + 13540 + 24 + 211 + 100 + 13624 + 26 + 15 + 0 + 100 + 101 + 13639 + 26 + 28 + 0 + 40 + 201 + 0 + 1 + 1 + 0 + 15102 + 203 + 66 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 3 + 0 + 21 + 1 + 15 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 2 + 1 + 0 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 15002 + 207 + 13534 + 24 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 4 + 0 + 12 + 4 + 7 + 0 + 3 + 0 + 13 + 4 + 10 + 0 + 4 + 0 + 20 + 4 + 14 + 0 + 4 + 0 + 21 + 1 + 18 + 0 + 1 + 1 + 900 + 10 + 19 + 1 + 357 + 10 + 901 + 10 + 376 + 11 + 13040 + 11 + 902 + 10 + 13416 + 22 + 62 + 2 + 2 + 1 + 1 + 3 + 1 + 1 + 1 + 2 + 257 + 257 + 3 + 257 + 257 + 257 + 3 + 3 + 1 + 1 + 1 + 1 + 6 + 203 + 100 + 0 + 0 + 6 + 1 + 205 + 100 + 6 + 1 + 6 + 1 + 207 + 100 + 12 + 2 + 227 + 6 + 210 + 100 + 239 + 8 + 6 + 1 + 212 + 100 + 245 + 9 + 6 + 1 + 100 + 101 + 251 + 10 + 68 + 0 + 40 + 203 + 0 + 1 + 1 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 15001 + 207 + 221 + 6 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 3 + 0 + 12 + 4 + 5 + 0 + 2 + 0 + 13 + 4 + 7 + 0 + 3 + 0 + 20 + 4 + 10 + 0 + 4 + 0 + 21 + 4 + 14 + 0 + 37 + 0 + 900 + 10 + 51 + 0 + 54 + 2 + 901 + 10 + 105 + 2 + 66 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 257 + 2 + 257 + 257 + 3 + 2 + 1 + 0 + 36 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 1 + 3 + 202 + 100 + 0 + 0 + 6 + 1 + 204 + 100 + 6 + 1 + 8 + 1 + 100 + 101 + 14 + 2 + 20 + 0 + 40 + 202 + 0 + 1 + 1 + 0 + 60010 + 204 + 2 + 1 + 1 + 0 + 1 + 0 + 0 + 2 + 8 + 4 + 0 + 204 + 204 + 0 + 1 + 0 + 0 + 0 + 0 + 202 + 202 + 0 + 1 + 0 + 0 + 1 + 1 + 4 + 202 + 100 + 0 + 0 + 6 + 1 + 204 + 100 + 6 + 1 + 6 + 1 + 206 + 100 + 12 + 2 + 8 + 2 + 100 + 101 + 20 + 4 + 20 + 0 + 40 + 202 + 0 + 1 + 1 + 0 + 40 + 204 + 0 + 1 + 1 + 0 + 60026 + 206 + 2 + 2 + 1 + 0 + 2 + 10 + 0 + 2 + 8 + 4 + 0 + 204 + 204 + 0 + 1 + 0 + 0 + 0 + 0 + 206 + 206 + 0 + 1 + 0 + 0 + 1 + 40 + 210 + 0 + 1 + 1 + 0 + 60020 + 212 + 0 + 1 + 1 + 0 + 0 + 8 + 8 + 4 + 1 + 0 + 0 + 0 + 0 + 207 + 207 + 0 + 0 + 205 + 205 + 0 + 0 + 207 + 207 + 1 + 0 + 203 + 203 + 0 + 0 + 207 + 207 + 2 + 0 + 207 + 207 + 1 + 0 + 212 + 212 + 0 + 0 + 210 + 210 + 0 + 0 + 212 + 212 + 1 + 0 + 207 + 207 + 0 + 1 + 0 + 0 + 0 + 0 + 212 + 212 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 2 + 1 + 17 + 203 + 100 + 0 + 0 + 6 + 0 + 205 + 100 + 6 + 0 + 12622 + 4 + 208 + 100 + 12628 + 4 + 6 + 0 + 210 + 100 + 12634 + 4 + 7 + 0 + 212 + 100 + 12641 + 4 + 8 + 0 + 215 + 100 + 12649 + 4 + 6 + 0 + 217 + 100 + 12655 + 4 + 28 + 0 + 218 + 100 + 12683 + 4 + 31 + 0 + 219 + 100 + 12714 + 4 + 6 + 1 + 221 + 100 + 12720 + 5 + 24 + 0 + 222 + 100 + 12744 + 5 + 6 + 1 + 224 + 100 + 12750 + 6 + 6 + 1 + 226 + 100 + 12756 + 7 + 6 + 1 + 228 + 100 + 12762 + 8 + 6 + 1 + 230 + 100 + 12768 + 9 + 6 + 1 + 232 + 100 + 12774 + 10 + 6 + 1 + 100 + 101 + 12780 + 11 + 156 + 0 + 60023 + 203 + 0 + 0 + 1 + 0 + 15011 + 205 + 12616 + 4 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 3 + 0 + 12 + 4 + 5 + 0 + 2 + 0 + 13 + 4 + 7 + 0 + 3 + 0 + 14 + 4 + 10 + 0 + 2 + 0 + 15 + 4 + 12 + 0 + 2 + 0 + 20 + 4 + 14 + 0 + 12551 + 0 + 21 + 1 + 12565 + 0 + 1 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 257 + 2 + 2 + 257 + 1 + 1 + 1 + 1 + 12550 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 21 + 4 + 6 + 0 + 12 + 0 + 22 + 4 + 18 + 0 + 4 + 0 + 900 + 10 + 22 + 0 + 12484 + 4 + 0 + 1 + 1 + 0 + 1 + 2 + 11 + 67 + 111 + 109 + 112 + 32 + 84 + 104 + 114 + 101 + 97 + 100 + 3 + 2 + 0 + -1 + 1 + 10 + 201 + 100 + 0 + 0 + 6 + 1 + 203 + 100 + 6 + 1 + 12132 + 0 + 205 + 100 + 12138 + 1 + 44 + 0 + 206 + 100 + 12182 + 1 + 6 + 1 + 208 + 100 + 12188 + 2 + 6 + 1 + 210 + 100 + 12194 + 3 + 8 + 0 + 212 + 100 + 12202 + 3 + 8 + 0 + 214 + 100 + 12210 + 3 + 6 + 1 + 216 + 100 + 12216 + 4 + 130 + 0 + 100 + 101 + 12346 + 4 + 76 + 0 + 40 + 201 + 0 + 1 + 1 + 0 + 22000 + 203 + 12126 + 0 + 1 + 0 + 1 + 20 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 11899 + 105 + 94 + 12 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 102 + 108 + 97 + 103 + 41 + 10 + 32 + 32 + 47 + 47 + 32 + 84 + 104 + 105 + 115 + 32 + 115 + 99 + 105 + 108 + 97 + 98 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 105 + 115 + 32 + 99 + 97 + 108 + 108 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 45 + 116 + 105 + 109 + 101 + 10 + 32 + 32 + 47 + 47 + 32 + 78 + 79 + 84 + 69 + 58 + 32 + 80 + 108 + 101 + 97 + 115 + 101 + 32 + 110 + 111 + 116 + 101 + 32 + 116 + 104 + 97 + 116 + 32 + 116 + 104 + 101 + 32 + 118 + 97 + 114 + 105 + 97 + 98 + 108 + 101 + 115 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 111 + 117 + 116 + 115 + 105 + 100 + 101 + 32 + 116 + 104 + 105 + 115 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 97 + 114 + 101 + 32 + 116 + 121 + 112 + 105 + 99 + 97 + 108 + 108 + 121 + 32 + 110 + 111 + 116 + 32 + 97 + 118 + 97 + 105 + 108 + 97 + 98 + 108 + 101 + 32 + 97 + 116 + 32 + 114 + 117 + 110 + 45 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 84 + 104 + 105 + 115 + 32 + 97 + 108 + 115 + 111 + 32 + 104 + 111 + 108 + 100 + 115 + 32 + 116 + 114 + 117 + 101 + 32 + 102 + 111 + 114 + 32 + 115 + 101 + 108 + 102 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 115 + 33 + 10 + 32 + 32 + 10 + 32 + 32 + 115 + 101 + 108 + 101 + 99 + 116 + 32 + 102 + 108 + 97 + 103 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 49 + 59 + 32 + 47 + 47 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 112 + 108 + 105 + 116 + 32 + 116 + 104 + 101 + 32 + 115 + 101 + 110 + 115 + 111 + 114 + 32 + 100 + 97 + 116 + 97 + 10 + 32 + 32 + 32 + 32 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 112 + 116 + 114 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 43 + 32 + 49 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 103 + 101 + 116 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 10 + 32 + 32 + 32 + 32 + 116 + 114 + 121 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 59 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 116 + 99 + 104 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 105 + 115 + 73 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 32 + 61 + 32 + 37 + 102 + 59 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 115 + 104 + 111 + 117 + 108 + 100 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 32 + 116 + 104 + 101 + 32 + 115 + 101 + 110 + 115 + 111 + 114 + 32 + 100 + 97 + 116 + 97 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 73 + 110 + 102 + 111 + 32 + 61 + 32 + 39 + 84 + 104 + 105 + 115 + 32 + 115 + 99 + 104 + 101 + 109 + 116 + 105 + 99 + 32 + 119 + 97 + 115 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 32 + 105 + 110 + 32 + 105 + 116 + 101 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 35 + 39 + 32 + 43 + 32 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 73 + 110 + 112 + 117 + 116 + 68 + 97 + 116 + 97 + 32 + 61 + 32 + 100 + 97 + 116 + 97 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 32 + 116 + 111 + 32 + 116 + 104 + 105 + 115 + 32 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 97 + 108 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 58 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 100 + 105 + 115 + 112 + 40 + 99 + 102 + 112 + 97 + 114 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 50 + 93 + 59 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 49 + 93 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 93 + 59 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 93 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 61 + 32 + 34 + 65 + 117 + 116 + 111 + 67 + 97 + 108 + 105 + 98 + 68 + 101 + 109 + 111 + 34 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 97 + 112 + 112 + 101 + 110 + 100 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 32 + 116 + 111 + 32 + 116 + 104 + 101 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 115 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 95 + 95 + 32 + 61 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 59 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 95 + 95 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 49 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 95 + 95 + 32 + 61 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 59 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 95 + 95 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 34 + 68 + 101 + 102 + 105 + 110 + 105 + 110 + 103 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 58 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 32 + 117 + 115 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 102 + 111 + 108 + 108 + 111 + 119 + 105 + 110 + 103 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 58 + 92 + 110 + 34 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 100 + 105 + 115 + 112 + 40 + 102 + 117 + 110 + 50 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 41 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 95 + 95 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 105 + 115 + 92 + 110 + 39 + 41 + 59 + 100 + 105 + 115 + 112 + 40 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 114 + 101 + 97 + 116 + 101 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 105 + 114 + 45 + 112 + 97 + 114 + 32 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 46 + 91 + 105 + 44 + 114 + 93 + 112 + 97 + 114 + 32 + 102 + 105 + 108 + 101 + 115 + 10 + 32 + 32 + 32 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 32 + 37 + 116 + 59 + 47 + 47 + 32 + 84 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 105 + 115 + 32 + 99 + 97 + 108 + 108 + 101 + 100 + 32 + 111 + 110 + 108 + 105 + 110 + 101 + 32 + 98 + 101 + 99 + 97 + 117 + 115 + 101 + 32 + 119 + 101 + 32 + 97 + 114 + 101 + 32 + 105 + 110 + 32 + 101 + 109 + 98 + 101 + 100 + 100 + 101 + 100 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 104 + 101 + 114 + 101 + 10 + 32 + 32 + 32 + 32 + 78 + 32 + 61 + 32 + 50 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 91 + 112 + 97 + 114 + 44 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 105 + 109 + 110 + 101 + 115 + 116 + 50 + 95 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 109 + 101 + 110 + 116 + 40 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 95 + 95 + 44 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 95 + 95 + 44 + 32 + 110 + 101 + 115 + 116 + 101 + 100 + 95 + 102 + 110 + 61 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 61 + 108 + 105 + 115 + 116 + 40 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 44 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 44 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 41 + 44 + 32 + 78 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 53 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 95 + 95 + 32 + 78 + 101 + 119 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 105 + 115 + 92 + 110 + 39 + 41 + 59 + 100 + 105 + 115 + 112 + 40 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 97 + 118 + 101 + 32 + 118 + 101 + 99 + 116 + 111 + 114 + 115 + 32 + 116 + 111 + 32 + 97 + 32 + 102 + 105 + 108 + 101 + 10 + 32 + 32 + 32 + 32 + 115 + 97 + 118 + 101 + 95 + 105 + 114 + 112 + 97 + 114 + 97 + 109 + 40 + 112 + 97 + 114 + 44 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 43 + 32 + 39 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 105 + 112 + 97 + 114 + 39 + 44 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 43 + 32 + 39 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 114 + 112 + 97 + 114 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 84 + 101 + 108 + 108 + 32 + 116 + 104 + 97 + 116 + 32 + 101 + 118 + 101 + 114 + 121 + 116 + 104 + 105 + 110 + 103 + 32 + 119 + 101 + 110 + 116 + 32 + 102 + 105 + 110 + 101 + 46 + 10 + 32 + 32 + 32 + 32 + 99 + 111 + 109 + 112 + 114 + 101 + 97 + 100 + 121 + 32 + 61 + 32 + 49 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 82 + 101 + 116 + 117 + 114 + 110 + 86 + 97 + 108 + 32 + 61 + 32 + 48 + 59 + 47 + 47 + 32 + 114 + 117 + 110 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 97 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 112 + 97 + 99 + 107 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 32 + 61 + 32 + 122 + 101 + 114 + 111 + 115 + 40 + 50 + 48 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 40 + 49 + 41 + 32 + 61 + 32 + 99 + 111 + 109 + 112 + 114 + 101 + 97 + 100 + 121 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 40 + 50 + 41 + 32 + 61 + 32 + 67 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 82 + 101 + 116 + 117 + 114 + 110 + 86 + 97 + 108 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 108 + 101 + 97 + 114 + 10 + 32 + 32 + 32 + 32 + 112 + 97 + 114 + 46 + 105 + 112 + 97 + 114 + 32 + 61 + 32 + 91 + 93 + 59 + 10 + 32 + 32 + 32 + 32 + 112 + 97 + 114 + 46 + 114 + 112 + 97 + 114 + 32 + 61 + 32 + 91 + 93 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 112 + 116 + 114 + 40 + 49 + 41 + 32 + 61 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 52 + 59 + 32 + 47 + 47 + 32 + 105 + 110 + 105 + 116 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 48 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 53 + 59 + 32 + 47 + 47 + 32 + 116 + 101 + 114 + 109 + 105 + 110 + 97 + 116 + 101 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 34 + 116 + 101 + 114 + 109 + 105 + 110 + 97 + 116 + 101 + 92 + 110 + 34 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 49 + 48 + 59 + 32 + 47 + 47 + 32 + 99 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 101 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 91 + 115 + 105 + 109 + 44 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 61 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 44 + 32 + 112 + 97 + 114 + 41 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 84 + 104 + 101 + 32 + 110 + 101 + 115 + 116 + 101 + 100 + 32 + 115 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 115 + 32 + 116 + 119 + 111 + 32 + 115 + 117 + 98 + 45 + 115 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 115 + 58 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 49 + 41 + 32 + 65 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 44 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 99 + 111 + 109 + 109 + 111 + 110 + 108 + 121 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 115 + 32 + 110 + 111 + 116 + 104 + 105 + 110 + 103 + 32 + 97 + 110 + 100 + 32 + 105 + 115 + 32 + 115 + 119 + 105 + 116 + 99 + 104 + 101 + 100 + 32 + 116 + 111 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 109 + 101 + 110 + 116 + 32 + 105 + 115 + 32 + 105 + 110 + 32 + 112 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 32 + 40 + 119 + 104 + 105 + 99 + 104 + 32 + 109 + 97 + 121 + 32 + 116 + 97 + 107 + 101 + 32 + 115 + 111 + 109 + 101 + 32 + 116 + 105 + 109 + 101 + 41 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 50 + 41 + 32 + 84 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 44 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 97 + 99 + 116 + 117 + 97 + 108 + 108 + 121 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 115 + 32 + 116 + 104 + 101 + 32 + 97 + 108 + 103 + 111 + 114 + 105 + 116 + 104 + 109 + 32 + 116 + 111 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 72 + 101 + 114 + 101 + 44 + 32 + 116 + 104 + 101 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 32 + 115 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 115 + 32 + 97 + 114 + 101 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 44 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 99 + 97 + 110 + 32 + 116 + 104 + 101 + 110 + 32 + 98 + 101 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 101 + 118 + 32 + 61 + 32 + 48 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 32 + 61 + 32 + 112 + 97 + 114 + 40 + 49 + 41 + 59 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 110 + 117 + 109 + 98 + 101 + 114 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 115 + 116 + 101 + 100 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 115 + 32 + 40 + 111 + 110 + 101 + 32 + 111 + 102 + 32 + 116 + 119 + 111 + 41 + 32 + 34 + 49 + 34 + 32 + 109 + 101 + 97 + 110 + 115 + 32 + 116 + 104 + 101 + 10 + 32 + 32 + 47 + 47 + 32 + 100 + 117 + 109 + 109 + 121 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 105 + 115 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 101 + 100 + 32 + 119 + 104 + 105 + 108 + 101 + 32 + 116 + 104 + 101 + 32 + 50 + 110 + 100 + 32 + 34 + 50 + 34 + 32 + 105 + 115 + 32 + 101 + 120 + 99 + 104 + 97 + 110 + 103 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 10 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 112 + 97 + 114 + 40 + 50 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 32 + 78 + 61 + 37 + 100 + 92 + 110 + 39 + 44 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 49 + 41 + 59 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 50 + 41 + 59 + 10 + 32 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 51 + 41 + 59 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 52 + 41 + 59 + 10 + 32 + 32 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 53 + 41 + 59 + 10 + 32 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 54 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 122 + 101 + 114 + 111 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 48 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 102 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 32 + 61 + 61 + 32 + 49 + 32 + 116 + 104 + 101 + 110 + 32 + 32 + 32 + 47 + 47 + 32 + 105 + 115 + 32 + 116 + 104 + 105 + 115 + 32 + 116 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 50 + 41 + 32 + 63 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 68 + 101 + 102 + 105 + 110 + 101 + 32 + 100 + 117 + 109 + 109 + 121 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 115 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 78 + 79 + 84 + 69 + 58 + 32 + 111 + 110 + 108 + 121 + 32 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 32 + 105 + 115 + 32 + 115 + 117 + 112 + 112 + 111 + 114 + 116 + 101 + 100 + 32 + 98 + 121 + 32 + 116 + 104 + 105 + 115 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 102 + 111 + 114 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 32 + 61 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 44 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 100 + 117 + 109 + 109 + 121 + 79 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 118 + 101 + 99 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 122 + 101 + 114 + 111 + 115 + 40 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 44 + 32 + 49 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 100 + 117 + 109 + 109 + 121 + 79 + 117 + 116 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 122 + 101 + 114 + 111 + 59 + 47 + 47 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 102 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 32 + 61 + 61 + 32 + 50 + 32 + 116 + 104 + 101 + 110 + 32 + 32 + 32 + 47 + 47 + 32 + 105 + 115 + 32 + 116 + 104 + 105 + 115 + 32 + 116 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 50 + 41 + 32 + 63 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 58 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 114 + 117 + 110 + 32 + 116 + 104 + 101 + 32 + 99 + 97 + 108 + 108 + 98 + 97 + 99 + 107 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 102 + 111 + 114 + 32 + 100 + 101 + 102 + 105 + 110 + 105 + 116 + 105 + 111 + 110 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 100 + 105 + 115 + 112 + 40 + 102 + 117 + 110 + 50 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 44 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 44 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 32 + 61 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 44 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 58 + 32 + 100 + 111 + 110 + 101 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 116 + 111 + 114 + 101 + 32 + 116 + 104 + 101 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 111 + 102 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 53 + 41 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 97 + 100 + 100 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 59 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 91 + 115 + 105 + 109 + 44 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 44 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 61 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 44 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 41 + 10 + 32 + 32 + 10 + 32 + 32 + 47 + 47 + 32 + 68 + 101 + 102 + 105 + 110 + 101 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 46 + 32 + 84 + 104 + 101 + 121 + 32 + 109 + 117 + 115 + 116 + 32 + 98 + 101 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 111 + 110 + 99 + 101 + 32 + 97 + 103 + 97 + 105 + 110 + 32 + 97 + 116 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 108 + 97 + 99 + 101 + 44 + 32 + 98 + 101 + 99 + 97 + 117 + 115 + 101 + 32 + 116 + 104 + 105 + 115 + 32 + 119 + 105 + 108 + 108 + 32 + 97 + 108 + 115 + 111 + 32 + 98 + 101 + 32 + 99 + 97 + 108 + 108 + 101 + 100 + 32 + 97 + 116 + 10 + 32 + 32 + 47 + 47 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 78 + 83 + 97 + 109 + 112 + 108 + 101 + 115 + 32 + 61 + 32 + 53 + 48 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 102 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 61 + 32 + 37 + 116 + 32 + 116 + 104 + 101 + 110 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 84 + 104 + 101 + 32 + 99 + 111 + 110 + 116 + 101 + 110 + 116 + 115 + 32 + 111 + 102 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 97 + 114 + 116 + 32 + 119 + 105 + 108 + 108 + 32 + 98 + 101 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 111 + 110 + 45 + 108 + 105 + 110 + 101 + 44 + 32 + 119 + 104 + 105 + 108 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 105 + 115 + 32 + 114 + 117 + 110 + 110 + 105 + 110 + 103 + 46 + 32 + 84 + 104 + 101 + 32 + 97 + 105 + 109 + 32 + 105 + 115 + 32 + 116 + 111 + 32 + 103 + 101 + 110 + 101 + 114 + 97 + 116 + 101 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 102 + 111 + 114 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 46 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 80 + 108 + 101 + 97 + 115 + 101 + 32 + 110 + 111 + 116 + 101 + 58 + 32 + 83 + 105 + 110 + 99 + 101 + 32 + 116 + 104 + 105 + 115 + 32 + 99 + 111 + 100 + 101 + 32 + 105 + 115 + 32 + 111 + 110 + 108 + 121 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 100 + 32 + 111 + 110 + 45 + 108 + 105 + 110 + 101 + 44 + 32 + 109 + 111 + 115 + 116 + 32 + 112 + 111 + 116 + 101 + 110 + 116 + 105 + 97 + 108 + 32 + 101 + 114 + 114 + 111 + 114 + 115 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 111 + 99 + 99 + 117 + 114 + 105 + 110 + 103 + 32 + 105 + 110 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 97 + 114 + 116 + 32 + 98 + 101 + 99 + 111 + 109 + 101 + 32 + 111 + 110 + 108 + 121 + 32 + 118 + 105 + 115 + 105 + 98 + 108 + 101 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 101 + 120 + 101 + 99 + 40 + 39 + 119 + 101 + 98 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 47 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 115 + 99 + 101 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 102 + 117 + 110 + 99 + 112 + 114 + 111 + 116 + 40 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 122 + 32 + 61 + 32 + 112 + 111 + 108 + 121 + 40 + 48 + 44 + 32 + 39 + 122 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 65 + 110 + 100 + 32 + 101 + 120 + 97 + 109 + 112 + 108 + 101 + 45 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 116 + 104 + 97 + 116 + 32 + 105 + 115 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 101 + 100 + 32 + 118 + 105 + 97 + 32 + 85 + 68 + 80 + 32 + 97 + 110 + 100 + 32 + 111 + 110 + 101 + 32 + 115 + 116 + 101 + 112 + 32 + 102 + 117 + 114 + 116 + 104 + 101 + 114 + 32 + 119 + 105 + 116 + 104 + 32 + 116 + 104 + 101 + 32 + 87 + 101 + 98 + 45 + 103 + 117 + 105 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 117 + 112 + 101 + 114 + 98 + 108 + 111 + 99 + 107 + 58 + 32 + 65 + 32 + 109 + 111 + 114 + 101 + 32 + 99 + 111 + 109 + 112 + 108 + 101 + 120 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 119 + 105 + 116 + 104 + 32 + 100 + 97 + 109 + 112 + 105 + 110 + 103 + 10 + 32 + 32 + 32 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 91 + 115 + 105 + 109 + 44 + 32 + 120 + 44 + 118 + 93 + 32 + 61 + 32 + 100 + 97 + 109 + 112 + 101 + 100 + 95 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 117 + 44 + 32 + 84 + 95 + 97 + 41 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 114 + 101 + 97 + 116 + 101 + 32 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 115 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 110 + 101 + 119 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 40 + 115 + 105 + 109 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 110 + 101 + 119 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 40 + 115 + 105 + 109 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 117 + 115 + 101 + 32 + 116 + 104 + 105 + 115 + 32 + 97 + 115 + 32 + 97 + 32 + 110 + 111 + 114 + 109 + 97 + 108 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 97 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 97 + 100 + 100 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 117 + 44 + 32 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 44 + 32 + 91 + 49 + 44 + 32 + 45 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 97 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 97 + 100 + 100 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 97 + 44 + 32 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 44 + 32 + 91 + 49 + 44 + 32 + 45 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 118 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 122 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 97 + 44 + 32 + 49 + 47 + 40 + 122 + 45 + 49 + 41 + 32 + 42 + 32 + 84 + 95 + 97 + 32 + 41 + 59 + 32 + 47 + 47 + 32 + 73 + 110 + 116 + 101 + 103 + 114 + 97 + 116 + 111 + 114 + 32 + 97 + 112 + 112 + 114 + 111 + 120 + 105 + 109 + 97 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 32 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 118 + 95 + 103 + 97 + 105 + 110 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 103 + 97 + 105 + 110 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 118 + 44 + 32 + 48 + 46 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 108 + 111 + 115 + 101 + 32 + 108 + 111 + 111 + 112 + 32 + 118 + 95 + 103 + 97 + 105 + 110 + 32 + 61 + 32 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 99 + 108 + 111 + 115 + 101 + 95 + 108 + 111 + 111 + 112 + 40 + 115 + 105 + 109 + 44 + 32 + 118 + 95 + 103 + 97 + 105 + 110 + 44 + 32 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 120 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 122 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 118 + 44 + 32 + 49 + 47 + 40 + 122 + 45 + 49 + 41 + 32 + 42 + 32 + 84 + 95 + 97 + 32 + 41 + 59 + 32 + 47 + 47 + 32 + 73 + 110 + 116 + 101 + 103 + 114 + 97 + 116 + 111 + 114 + 32 + 97 + 112 + 112 + 114 + 111 + 120 + 105 + 109 + 97 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 32 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 120 + 95 + 103 + 97 + 105 + 110 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 103 + 97 + 105 + 110 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 120 + 44 + 32 + 48 + 46 + 54 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 108 + 111 + 115 + 101 + 32 + 108 + 111 + 111 + 112 + 32 + 120 + 95 + 103 + 97 + 105 + 110 + 32 + 61 + 32 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 99 + 108 + 111 + 115 + 101 + 95 + 108 + 111 + 111 + 112 + 40 + 115 + 105 + 109 + 44 + 32 + 120 + 95 + 103 + 97 + 105 + 110 + 44 + 32 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 105 + 102 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 105 + 115 + 73 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 32 + 61 + 61 + 32 + 37 + 102 + 32 + 116 + 104 + 101 + 110 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 116 + 97 + 116 + 101 + 32 + 118 + 97 + 114 + 105 + 97 + 98 + 108 + 101 + 115 + 32 + 99 + 97 + 110 + 32 + 98 + 101 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 32 + 97 + 116 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 108 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 48 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 105 + 115 + 73 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 32 + 61 + 32 + 37 + 116 + 59 + 47 + 47 + 32 + 112 + 114 + 101 + 118 + 101 + 110 + 116 + 32 + 102 + 114 + 111 + 109 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 118 + 97 + 114 + 105 + 97 + 98 + 108 + 101 + 115 + 32 + 111 + 110 + 99 + 101 + 32 + 97 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 69 + 120 + 97 + 109 + 112 + 108 + 101 + 32 + 102 + 111 + 114 + 32 + 97 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 117 + 112 + 100 + 97 + 116 + 101 + 58 + 32 + 105 + 110 + 99 + 114 + 101 + 97 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 43 + 32 + 49 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 66 + 117 + 105 + 108 + 100 + 32 + 97 + 110 + 32 + 105 + 110 + 102 + 111 + 45 + 115 + 116 + 114 + 105 + 110 + 103 + 10 + 32 + 32 + 32 + 32 + 83 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 73 + 110 + 102 + 111 + 32 + 61 + 32 + 39 + 79 + 110 + 45 + 108 + 105 + 110 + 101 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 105 + 110 + 32 + 105 + 116 + 101 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 35 + 39 + 32 + 43 + 32 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 68 + 101 + 102 + 105 + 110 + 101 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 101 + 114 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 100 + 101 + 112 + 101 + 110 + 100 + 105 + 110 + 103 + 32 + 111 + 110 + 32 + 116 + 104 + 101 + 32 + 99 + 117 + 114 + 114 + 101 + 110 + 116 + 108 + 121 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 32 + 115 + 116 + 97 + 116 + 101 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 105 + 110 + 105 + 116 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 80 + 97 + 80 + 105 + 32 + 85 + 68 + 80 + 32 + 83 + 111 + 99 + 107 + 101 + 116 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 85 + 110 + 100 + 101 + 114 + 108 + 121 + 105 + 110 + 103 + 80 + 114 + 111 + 116 + 111 + 99 + 111 + 108 + 108 + 32 + 61 + 32 + 39 + 85 + 68 + 80 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 68 + 101 + 115 + 116 + 72 + 111 + 115 + 116 + 32 + 61 + 32 + 39 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 68 + 101 + 115 + 116 + 80 + 111 + 114 + 116 + 32 + 61 + 32 + 50 + 48 + 48 + 48 + 48 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 76 + 111 + 99 + 97 + 108 + 83 + 111 + 99 + 107 + 101 + 116 + 72 + 111 + 115 + 116 + 32 + 61 + 32 + 39 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 76 + 111 + 99 + 97 + 108 + 83 + 111 + 99 + 107 + 101 + 116 + 80 + 111 + 114 + 116 + 32 + 61 + 32 + 50 + 48 + 48 + 48 + 49 + 59 + 10 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 100 + 101 + 98 + 117 + 103 + 109 + 111 + 100 + 101 + 32 + 61 + 32 + 37 + 116 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 73 + 110 + 105 + 116 + 73 + 110 + 115 + 116 + 97 + 110 + 99 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 73 + 110 + 115 + 116 + 97 + 110 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 39 + 44 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 122 + 101 + 114 + 111 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 110 + 101 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 100 + 101 + 102 + 97 + 117 + 108 + 116 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 40 + 100 + 117 + 109 + 109 + 121 + 41 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 122 + 101 + 114 + 111 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 72 + 101 + 114 + 101 + 32 + 97 + 32 + 115 + 116 + 97 + 116 + 101 + 45 + 109 + 97 + 99 + 104 + 105 + 110 + 101 + 32 + 105 + 115 + 32 + 105 + 109 + 112 + 108 + 101 + 109 + 101 + 110 + 116 + 101 + 100 + 32 + 116 + 104 + 97 + 116 + 32 + 109 + 97 + 121 + 32 + 98 + 101 + 32 + 117 + 115 + 101 + 100 + 32 + 116 + 111 + 32 + 105 + 109 + 112 + 108 + 101 + 109 + 101 + 110 + 116 + 32 + 115 + 111 + 109 + 101 + 32 + 97 + 117 + 116 + 111 + 109 + 97 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 108 + 111 + 103 + 105 + 99 + 32 + 116 + 104 + 97 + 116 + 32 + 105 + 115 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 32 + 117 + 115 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 101 + 109 + 98 + 101 + 100 + 100 + 101 + 100 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 105 + 110 + 116 + 101 + 114 + 112 + 114 + 101 + 116 + 101 + 114 + 46 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 73 + 110 + 32 + 116 + 104 + 105 + 115 + 32 + 101 + 120 + 97 + 109 + 112 + 108 + 101 + 44 + 32 + 97 + 32 + 99 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 114 + 117 + 110 + 32 + 115 + 117 + 99 + 99 + 101 + 101 + 100 + 101 + 100 + 32 + 98 + 121 + 32 + 116 + 104 + 101 + 32 + 100 + 101 + 115 + 105 + 103 + 110 + 47 + 99 + 111 + 109 + 112 + 105 + 108 + 97 + 116 + 105 + 111 + 110 + 47 + 101 + 120 + 101 + 99 + 117 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 111 + 102 + 32 + 97 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 45 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 105 + 115 + 32 + 105 + 109 + 112 + 108 + 101 + 109 + 101 + 110 + 116 + 101 + 100 + 46 + 32 + 84 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 115 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 105 + 110 + 32 + 101 + 97 + 99 + 104 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 97 + 114 + 101 + 32 + 108 + 111 + 97 + 100 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 97 + 116 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 115 + 101 + 108 + 101 + 99 + 116 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 39 + 59 + 32 + 47 + 47 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 32 + 97 + 110 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 116 + 111 + 32 + 112 + 101 + 114 + 102 + 111 + 114 + 109 + 32 + 97 + 110 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 49 + 32 + 61 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 105 + 110 + 49 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 58 + 32 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 65 + 100 + 100 + 32 + 97 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 32 + 102 + 111 + 114 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 73 + 110 + 112 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 111 + 109 + 101 + 32 + 109 + 111 + 114 + 101 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 65 + 80 + 97 + 114 + 86 + 101 + 99 + 116 + 111 + 114 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 49 + 48 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 65 + 32 + 118 + 101 + 99 + 116 + 111 + 114 + 105 + 97 + 108 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 112 + 97 + 114 + 50 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 50 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 84 + 101 + 115 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 32 + 116 + 104 + 101 + 115 + 101 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 73 + 110 + 112 + 117 + 116 + 44 + 32 + 39 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 32 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 112 + 97 + 114 + 50 + 44 + 32 + 39 + 84 + 101 + 115 + 116 + 32 + 39 + 44 + 32 + 50 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 65 + 80 + 97 + 114 + 86 + 101 + 99 + 116 + 111 + 114 + 44 + 32 + 39 + 65 + 32 + 118 + 101 + 99 + 116 + 111 + 114 + 105 + 97 + 108 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 39 + 44 + 32 + 49 + 48 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 84 + 104 + 101 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 116 + 111 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 84 + 95 + 97 + 32 + 61 + 32 + 48 + 46 + 49 + 59 + 91 + 115 + 105 + 109 + 44 + 120 + 44 + 118 + 93 + 32 + 61 + 32 + 100 + 97 + 109 + 112 + 101 + 100 + 95 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 73 + 110 + 112 + 117 + 116 + 44 + 32 + 84 + 95 + 97 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 116 + 114 + 101 + 97 + 109 + 32 + 116 + 104 + 101 + 32 + 100 + 97 + 116 + 97 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 120 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 88 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 118 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 86 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 109 + 109 + 117 + 110 + 105 + 99 + 97 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 70 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 87 + 97 + 105 + 116 + 32 + 117 + 110 + 116 + 105 + 108 + 32 + 97 + 32 + 110 + 117 + 109 + 98 + 101 + 114 + 32 + 111 + 102 + 32 + 116 + 105 + 109 + 101 + 32 + 115 + 116 + 101 + 112 + 115 + 32 + 104 + 97 + 115 + 32 + 112 + 97 + 115 + 115 + 101 + 100 + 44 + 32 + 116 + 104 + 101 + 110 + 32 + 110 + 111 + 116 + 105 + 102 + 121 + 32 + 116 + 104 + 97 + 116 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 98 + 121 + 32 + 115 + 101 + 116 + 116 + 105 + 110 + 103 + 32 + 34 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 34 + 32 + 116 + 111 + 32 + 49 + 46 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 116 + 101 + 112 + 115 + 50 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 105 + 111 + 110 + 95 + 115 + 105 + 109 + 115 + 116 + 101 + 112 + 115 + 61 + 78 + 83 + 97 + 109 + 112 + 108 + 101 + 115 + 44 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 61 + 91 + 48 + 44 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 49 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 104 + 111 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 116 + 111 + 32 + 101 + 110 + 116 + 101 + 114 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 99 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 39 + 59 + 32 + 47 + 47 + 32 + 100 + 101 + 115 + 105 + 103 + 110 + 32 + 97 + 32 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 32 + 116 + 111 + 32 + 115 + 101 + 110 + 100 + 32 + 116 + 111 + 32 + 80 + 97 + 80 + 105 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 49 + 32 + 61 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 105 + 110 + 49 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 58 + 32 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 116 + 114 + 101 + 97 + 109 + 32 + 97 + 32 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 105 + 110 + 49 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 67 + 111 + 110 + 115 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 109 + 109 + 117 + 110 + 105 + 99 + 97 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 70 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 87 + 97 + 105 + 116 + 32 + 117 + 110 + 116 + 105 + 108 + 32 + 97 + 32 + 110 + 117 + 109 + 98 + 101 + 114 + 32 + 111 + 102 + 32 + 116 + 105 + 109 + 101 + 32 + 115 + 116 + 101 + 112 + 115 + 32 + 104 + 97 + 115 + 32 + 112 + 97 + 115 + 115 + 101 + 100 + 44 + 32 + 116 + 104 + 101 + 110 + 32 + 110 + 111 + 116 + 105 + 102 + 121 + 32 + 116 + 104 + 97 + 116 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 40 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 105 + 110 + 32 + 116 + 104 + 105 + 115 + 32 + 99 + 97 + 115 + 101 + 41 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 46 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 116 + 101 + 112 + 115 + 50 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 105 + 111 + 110 + 95 + 115 + 105 + 109 + 115 + 116 + 101 + 112 + 115 + 61 + 78 + 83 + 97 + 109 + 112 + 108 + 101 + 115 + 44 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 61 + 91 + 48 + 44 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 50 + 50 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 104 + 111 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 116 + 111 + 32 + 101 + 110 + 116 + 101 + 114 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 100 + 101 + 109 + 111 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 39 + 59 + 32 + 47 + 47 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 45 + 32 + 110 + 111 + 116 + 104 + 105 + 110 + 103 + 32 + 116 + 111 + 32 + 100 + 111 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 49 + 32 + 61 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 105 + 110 + 49 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 48 + 41 + 59 + 47 + 47 + 32 + 110 + 101 + 118 + 101 + 114 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 51 + 51 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 47 + 47 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 61 + 32 + 37 + 116 + 10 + 32 + 32 + 10 + 32 + 32 + 47 + 47 + 32 + 87 + 104 + 101 + 110 + 32 + 82 + 84 + 109 + 97 + 105 + 110 + 46 + 115 + 99 + 101 + 32 + 105 + 115 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 100 + 44 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 97 + 114 + 116 + 32 + 119 + 105 + 108 + 108 + 32 + 98 + 101 + 32 + 114 + 117 + 110 + 46 + 32 + 73 + 116 + 32 + 109 + 97 + 121 + 32 + 98 + 101 + 32 + 117 + 115 + 101 + 100 + 32 + 116 + 111 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 32 + 97 + 110 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 105 + 110 + 32 + 97 + 100 + 118 + 97 + 110 + 99 + 101 + 32 + 116 + 111 + 10 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 105 + 111 + 110 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 119 + 104 + 111 + 108 + 101 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 46 + 10 + 32 + 32 + 105 + 102 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 61 + 32 + 37 + 102 + 32 + 116 + 104 + 101 + 110 + 32 + 10 + 32 + 32 + 32 + 32 + 83 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 73 + 110 + 102 + 111 + 32 + 61 + 32 + 39 + 79 + 102 + 102 + 45 + 108 + 105 + 110 + 101 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 100 + 101 + 102 + 97 + 117 + 108 + 116 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 40 + 100 + 117 + 109 + 109 + 121 + 41 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 116 + 101 + 112 + 115 + 50 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 105 + 111 + 110 + 95 + 115 + 105 + 109 + 115 + 116 + 101 + 112 + 115 + 61 + 49 + 48 + 44 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 61 + 91 + 48 + 44 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 118 + 101 + 99 + 115 + 105 + 122 + 101 + 61 + 49 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 118 + 101 + 99 + 115 + 105 + 122 + 101 + 61 + 50 + 48 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 49 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 50 + 53 + 55 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 49 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 50 + 53 + 55 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 61 + 39 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 39 + 32 + 10 + 59 + 10 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 52 + 41 + 59 + 10 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 115 + 99 + 105 + 108 + 97 + 98 + 32 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 97 + 108 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 92 + 110 + 39 + 41 + 59 + 32 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 112 + 116 + 114 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 112 + 116 + 114 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 112 + 116 + 114 + 40 + 49 + 41 + 32 + 61 + 32 + 115 + 99 + 105 + 108 + 97 + 98 + 95 + 105 + 110 + 116 + 101 + 114 + 102 + 46 + 105 + 110 + 118 + 101 + 99 + 49 + 59 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 49 + 41 + 59 + 32 + 115 + 99 + 105 + 108 + 97 + 98 + 95 + 105 + 110 + 116 + 101 + 114 + 102 + 46 + 111 + 117 + 116 + 118 + 101 + 99 + 49 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 112 + 116 + 114 + 40 + 49 + 41 + 59 + 32 + 10 + 10 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 53 + 41 + 59 + 10 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 115 + 99 + 105 + 108 + 97 + 98 + 32 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 97 + 108 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 100 + 101 + 115 + 116 + 114 + 117 + 99 + 116 + 101 + 100 + 92 + 110 + 39 + 41 + 59 + 32 + 10 + 99 + 108 + 101 + 97 + 114 + 32 + 98 + 108 + 111 + 99 + 107 + 59 + 10 + 66 + 85 + 73 + 76 + 68 + 73 + 78 + 95 + 80 + 65 + 84 + 72 + 0 + 170 + 205 + 38 + 0 + 1 + 0 + 20 + 36 + 84 + 104 + 101 + 32 + 102 + 114 + 111 + 109 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 114 + 101 + 116 + 117 + 114 + 110 + 101 + 100 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 32 + 97 + 114 + 101 + 32 + 40 + 206 + 0 + 1 + 1 + 0 + 40 + 208 + 0 + 1 + 1 + 0 + 60009 + 210 + 2 + 0 + 1 + 0 + 20 + 257 + 60009 + 212 + 2 + 0 + 1 + 0 + 20 + 257 + 40 + 214 + 0 + 1 + 1 + 0 + 15003 + 216 + 124 + 0 + 1 + 0 + 0 + 0 + 0 + 41 + 41 + 36 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 105 + 112 + 97 + 114 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 114 + 112 + 97 + 114 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 0 + 9 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 203 + 203 + 0 + 0 + 210 + 210 + 0 + 0 + 206 + 206 + 0 + 0 + 210 + 210 + 1 + 0 + 203 + 203 + 0 + 0 + 212 + 212 + 0 + 0 + 208 + 208 + 0 + 0 + 212 + 212 + 1 + 0 + 210 + 210 + 0 + 0 + 216 + 216 + 0 + 0 + 214 + 214 + 0 + 0 + 216 + 216 + 1 + 0 + 212 + 212 + 0 + 1 + 0 + 0 + 0 + 4 + 60032 + 208 + 0 + 0 + 1 + 0 + 60006 + 210 + 1 + 0 + 1 + 0 + 2 + 60002 + 212 + 2 + 0 + 1 + 0 + 2 + 0 + 60014 + 215 + 0 + 0 + 1 + 0 + 170 + 217 + 22 + 0 + 1 + 0 + 1 + 20 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 95 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 170 + 218 + 25 + 0 + 1 + 0 + 1 + 23 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 95 + 78 + 111 + 116 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 40 + 219 + 0 + 1 + 1 + 0 + 170 + 221 + 18 + 0 + 1 + 0 + 1 + 16 + 99 + 97 + 108 + 99 + 117 + 108 + 97 + 116 + 105 + 110 + 103 + 32 + 46 + 46 + 46 + 32 + 40 + 222 + 0 + 1 + 1 + 0 + 40 + 224 + 0 + 1 + 1 + 0 + 60020 + 226 + 0 + 1 + 1 + 0 + 60020 + 228 + 0 + 1 + 1 + 0 + 60020 + 230 + 0 + 1 + 1 + 0 + 60020 + 232 + 0 + 1 + 1 + 0 + 0 + 19 + 8 + 4 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 205 + 205 + 0 + 0 + 208 + 208 + 0 + 0 + 208 + 208 + 0 + 0 + 210 + 210 + 0 + 0 + 210 + 210 + 0 + 0 + 212 + 212 + 0 + 0 + 205 + 205 + 1 + 0 + 215 + 215 + 0 + 0 + 205 + 205 + 1 + 0 + 217 + 217 + 0 + 0 + 215 + 215 + 0 + 0 + 218 + 218 + 0 + 0 + 219 + 219 + 0 + 0 + 221 + 221 + 0 + 0 + 212 + 212 + 0 + 0 + 226 + 226 + 0 + 0 + 224 + 224 + 0 + 0 + 226 + 226 + 1 + 0 + 212 + 212 + 1 + 0 + 228 + 228 + 0 + 0 + 226 + 226 + 0 + 0 + 228 + 228 + 1 + 0 + 215 + 215 + 0 + 0 + 230 + 230 + 0 + 0 + 228 + 228 + 0 + 0 + 230 + 230 + 1 + 0 + 219 + 219 + 0 + 0 + 232 + 232 + 0 + 0 + 230 + 230 + 0 + 0 + 232 + 232 + 1 + 0 + 222 + 222 + 0 + 1 + 0 + 0 + 0 + 0 + 232 + 232 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 2 + 1 + 3 + 203 + 100 + 0 + 0 + 8 + 1 + 205 + 100 + 8 + 1 + 6 + 1 + 100 + 101 + 14 + 2 + 28 + 0 + 60010 + 203 + 2 + 1 + 1 + 0 + 1 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 0 + 3 + 8 + 4 + 0 + 203 + 203 + 0 + 1 + 0 + 0 + 0 + 0 + 205 + 205 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 2 + 170 + 211 + 9 + 0 + 1 + 0 + 1 + 7 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 0 + 3 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 207 + 207 + 0 + 0 + 207 + 207 + 0 + 0 + 211 + 211 + 0 + 26 + 0 + 1 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar new file mode 100644 index 00000000..0bd666ad --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar @@ -0,0 +1,26 @@ +0.2000000000000000111022302 +1.2339999999999999857891453 +1.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +0.0000000000000000000000000 +2.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +2.0000000000000000000000000 +2.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +3.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce new file mode 100644 index 00000000..5353f956 --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce @@ -0,0 +1,326 @@ +// +// Copyright (C) 2011, 2012, 2013, 2014, 2015 Christian Klauer +// +// This file is part of OpenRTDynamics, the Real-Time Dynamics Framework +// +// OpenRTDynamics is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// OpenRTDynamics is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with OpenRTDynamics. If not, see . +// +funcprot(0); +// The name of the program +ProgramName = 'SelectCasePaPiConfig'; // must be the filename without .sce + + +function [sim, outlist] = AutoConfigExample(sim, Signal) + + function [sim, finished, outlist, userdata] = ExperimentCntrl(sim, ev, inlist, userdata, CalledOnline) + + // Define parameters. They must be defined once again at this place, because this will also be called at + // runtime. + NSamples=50; + + if CalledOnline == %t then + // The contents of this part will be compiled on-line, while the control + // system is running. The aim is to generate a new compiled schematic for + // the experiment. + // Please note: Since this code is only executed on-line, most potential errors + // occuring in this part become only visible during runtime. + + printf("Compiling a new control system\n"); + exec('webinterface/PacketFramework.sce'); + funcprot(0); + z = poly(0,'z'); + // And example-system that is controlled via UDP and one step further with the Web-gui + // Superblock: A more complex oscillator with damping + function [sim, x,v] = damped_oscillator(sim, u, T_a) + // create feedback signals + [sim,x_feedback] = libdyn_new_feedback(sim); + + [sim,v_feedback] = libdyn_new_feedback(sim); + + // use this as a normal signal + [sim,a] = ld_add(sim, ev, list(u, x_feedback), [1, -1]); + [sim,a] = ld_add(sim, ev, list(a, v_feedback), [1, -1]); + + [sim,v] = ld_ztf(sim, ev, a, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,v_gain] = ld_gain(sim, ev, v, 0.1); + + // close loop v_gain = v_feedback + [sim] = libdyn_close_loop(sim, v_gain, v_feedback); + + + [sim,x] = ld_ztf(sim, ev, v, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,x_gain] = ld_gain(sim, ev, x, 0.6); + + // close loop x_gain = x_feedback + [sim] = libdyn_close_loop(sim, x_gain, x_feedback); + endfunction + + if userdata.isInitialised == %f then + // + // State variables can be initialise at this place + // + userdata.Acounter = 0; + userdata.State = "oscillator"; + + userdata.isInitialised = %t; // prevent from initialising the variables once again + end + + // + // Example for a state update: increase the counter + // + userdata.Acounter = userdata.Acounter + 1; + + // Build an info-string + SchematicInfo = "On-line compiled in iteration #" + string(userdata.Acounter); + + // + // Define a new experiment controller schematic depending on the currently active state + // + + // initalise the PaPi UDP Socket + Configuration.UnderlyingProtocoll = "UDP"; + Configuration.DestHost = "127.0.0.1"; + Configuration.DestPort = 20000; + Configuration.LocalSocketHost = "127.0.0.1"; + Configuration.LocalSocketPort = 20001; + PacketFramework.Configuration.debugmode = %t; + [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="TrockenofenRemoteControl", Configuration); + + [sim, zero] = ld_const(sim, ev, 0); + [sim, one] = ld_const(sim, 0, 1); + + // default output (dummy) + outlist=list(zero); + + // + // Here a state-machine is implemented that may be used to implement some automation + // logic that is executed during runtime using the embedded Scilab interpreter. + // In this example, a calibration run succeeded by the design/compilation/execution + // of a control-system is implemented. The schematics defined in each state are loaded + // at runtime. + // + select userdata.State + case "oscillator" // define an experiment to perform an oscillator experiment + in1 = inlist(1); + [sim] = ld_printf(sim, 0, in1, "Case oscillator active: ", 1); + + // Add a parameter for controlling the oscillator + [sim, PacketFramework, Input]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Oscillator input"); + + // some more parameters + [sim, PacketFramework, AParVector]=ld_PF_Parameter(sim, PacketFramework, NValues=10, datatype=ORTD.DATATYPE_FLOAT, ParameterName="A vectorial parameter"); + [sim, PacketFramework, par2]=ld_PF_Parameter(sim, PacketFramework, NValues=2, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Test"); + + // printf these parameters + [sim] = ld_printf(sim, ev, Input, "Oscillator input ", 1); + [sim] = ld_printf(sim, ev, par2, "Test ", 2); + [sim] = ld_printf(sim, ev, AParVector, "A vectorial parameter", 10); + + // The system to control + T_a = 0.1; [sim, x,v] = damped_oscillator(sim, Input, T_a); + + // Stream the data of the oscillator + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X"); + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V"); + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); + + // Wait until a number of time steps has passed, then notify that + // the experiment has finished by setting "finished" to 1. + [sim, finished] = ld_steps2(sim, ev, activation_simsteps=NSamples, values=[0,1] ); + + [sim, out] = ld_const(sim, 0, 11); + outlist=list(out); + + // chose the next state to enter when the calibration experiment has finished + userdata.State = "constant"; + + case "constant" // design a constant signal to send to PaPi + in1 = inlist(1); + [sim] = ld_printf(sim, 0, in1, "Case constant active: ", 1); + + // Stream a constant + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=in1, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Const"); + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim, PacketFramework); + + // Wait until a number of time steps has passed, then notify that + // the experiment (control system in this case) has finished. + [sim, finished] = ld_steps2(sim, ev, activation_simsteps=NSamples, values=[0,1] ); + + [sim, out] = ld_const(sim, 0, 22); + outlist=list(out); + + // chose the next state to enter when the demo experiment has finished + userdata.State = "finished"; + + case "finished" // experiment finished - nothing to do + in1 = inlist(1); + [sim] = ld_printf(sim, ev, in1, "Case finished active" , 1); + [sim, finished] = ld_const(sim, ev, 0); // never finish + [sim, out] = ld_const(sim, 0, 33); + outlist=list(out); + + end + end // CalledOnline == %t + + // When RTmain.sce is executed, this part will be run. It may be used to define an initial experiment in advance to + // the execution of the whole control system. + if CalledOnline == %f then + SchematicInfo = "Off-line compiled"; + + // default output (dummy) + [sim, out] = ld_const(sim, 0, 0); + outlist=list(out); + [sim, finished] = ld_steps2(sim, 0, activation_simsteps=10, values=[0,1] ); + end + + endfunction + + + + + function [sim, outlist, HoldState, userdata] = whileComputing_example(sim, ev, inlist, CalibrationReturnVal, computation_finished, par); + + [sim, HoldState] = ld_const(sim, 0, 0); + + [sim] = ld_printf(sim, 0, HoldState, "calculating ... " , 1); + + // While the computation is running this is called regularly + [sim, out] = ld_const(sim, ev, 0); + outlist=list(out); + endfunction + + + function [sim, ToScilab, userdata] = PreScilabRun(sim, ev, par) + userdata = par.userdata; + + // get the stored sensor data + [sim, ToScilab] = ld_const(sim, ev, 0); + endfunction + + + // Start the experiment + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; + ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; + + insizes=[1]; outsizes=[1]; + intypes=[ORTD.DATATYPE_FLOAT]; outtypes=[ORTD.DATATYPE_FLOAT]; + + + CallbackFns.experiment = ExperimentCntrl; + CallbackFns.whileComputing = whileComputing_example; + CallbackFns.PreScilabRun = PreScilabRun; + + // Please note ident_str must be unique. + userdata = []; + [sim, finished, outlist, userdata] = ld_AutoOnlineExch_dev(sim, 0, inlist=list(Signal), ... + insizes, outsizes, intypes, outtypes, ... + ThreadPrioStruct, CallbackFns, ident_str="AutoConfigDemo", userdata); + + PacketFramework = userdata(1); +// [sim] = ld_printf(sim, 0, finished, "State ", 1); + +endfunction + + +// The main real-time thread +function [sim, outlist, userdata] = Thread_MainRT(sim, inlist, userdata) + // This will run in a thread + [sim, Tpause] = ld_const(sim, ev, 1/5); // The sampling time that is constant at 20 Hz in this example + [sim, out] = ld_ClockSync(sim, ev, in=Tpause); // synchronise this simulation + + // + // Add you own control system here + // + + // some dummy input to the state machine + [sim,Signal] = ld_const(sim, 0, 1.234); + + [sim, outlist] = AutoConfigExample(sim, Signal); + + [sim] = ld_printf(sim, 0, outlist(1) , "output ", 1); + + + outlist = list(); +endfunction + +// This is the main top level schematic +function [sim, outlist] = schematic_fn(sim, inlist) + +// +// Create a thread that runs the control system +// + + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; // or ORTD.ORTD_RT_REALTIMETASK + ThreadPrioStruct.prio2=0; // for ORTD.ORTD_RT_REALTIMETASK: 1-99 as described in man sched_setscheduler + // for ORTD.ORTD_RT_NORMALTASK this is the nice-value (higher value means less priority) + ThreadPrioStruct.cpu = -1; // The CPU on which the thread will run; -1 dynamically assigns to a CPU, + // counting of the CPUs starts at 0 + + [sim, StartThread] = ld_initimpuls(sim, ev); // triggers your computation only once + [sim, outlist, computation_finished] = ld_async_simulation(sim, ev, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = Thread_MainRT, ... + TriggerSignal=StartThread, name="MainRealtimeThread", ... + ThreadPrioStruct, userdata=list() ); + + + // output of schematic (empty) + outlist = list(); +endfunction + + + + + + + + + + +// +// Set-up (no detailed understanding necessary) +// + +thispath = get_absolute_file_path(ProgramName+'.sce'); +cd(thispath); +z = poly(0,'z'); + +exec('webinterface/PacketFramework.sce'); + +// defile ev +ev = [0]; // main event + +// set-up schematic by calling the user defined function "schematic_fn" +insizes = []; outsizes=[]; +[sim_container_irpar, sim]=libdyn_setup_schematic(schematic_fn, insizes, outsizes); + +// pack the simulation into a irpar container +parlist = new_irparam_set(); +parlist = new_irparam_container(parlist, sim_container_irpar, 901); // pack simulations into irpar container with id = 901 +par = combine_irparam(parlist); // complete irparam set +save_irparam(par, ProgramName+'.ipar', ProgramName+'.rpar'); // Save the schematic to disk + +// clear +par.ipar = []; par.rpar = []; + diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh b/data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh new file mode 100755 index 00000000..c0a83aea --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +#ortd --baserate=10 --rtmode 1 -s SelectCasePaPiConfig -i 901 -l 0 +ortdrun -s SelectCasePaPiConfig diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce new file mode 100644 index 00000000..4aac7ffe --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce @@ -0,0 +1,1108 @@ + + +// Interfacing functions are placed in this place + +function [sim, out] = ld_udp_main_receiver(sim, events, udpport, identstr, socket_fname, vecsize) // PARSEDOCU_BLOCK +// udp main receiver - block +// +// This is a simulation-synchronising Block +// +// EXPERIMENTAL FIXME: REMOVE +// + + datatype = ORTD.DATATYPE_FLOAT; + + btype = 39001; + [sim,blk] = libdyn_new_block(sim, events, btype, ipar=[ udpport, vecsize, datatype, length(socket_fname), ascii(socket_fname), length(identstr), ascii(identstr) ], rpar=[ ], ... + insizes=[], outsizes=[vecsize], ... + intypes=[], outtypes=[ORTD.DATATYPE_FLOAT] ); + + //[sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); + [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction + + + + +function [sim] = ld_UDPSocket_shObj(sim, events, ObjectIdentifyer, Visibility, hostname, UDPPort) // PARSEDOCU_BLOCK +// +// Set-up an UDP-Socket +// +// hostname - Network interface to bind socket to??? +// UDPPort - UDP port to bind. If -1 then no UDP server is set-up +// +// EXPERIMENTAL +// + + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + + + + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); + + parlist = new_irparam_elemet_ivec(parlist, UDPPort, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 11); // id = 11; A string parameter + + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + +// Set-up the block parameters. There are no I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 0; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + [sim] = libdyn_CreateSharedObjBlk(sim, btype, ObjectIdentifyer, Visibility, Uipar, Urpar); +endfunction + +function [sim] = ld_UDPSocket_Send(sim, events, ObjectIdentifyer, in, insize, intype) // PARSEDOCU_BLOCK +// +// UDP - Send block +// +// in *, ORTD.DATATYPE_BINARY - input +// +// EXPERIMENTAL, About to be removed +// + + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + + + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); + + parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + +// Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 1; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + insizes=[insize]; // Input port sizes + outsizes=[]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[intype]; // datatype for each input port + outtypes=[]; // datatype for each output port + + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); + + // connect the inputs + [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 + +// // connect the ouputs +// [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction + + +function [sim, out, SrcAddr] = ld_UDPSocket_Recv(sim, events, ObjectIdentifyer, outsize) // PARSEDOCU_BLOCK +// +// UDP - receiver block +// +// out *, ORTD.DATATYPE_BINARY - output +// SrcAddr - information about where the package comes from (not implemented) +// +// This is a simulation-synchronising Block. Everytime an UDP-Packet is received, +// the simulation that contains this blocks goes on for one step. +// +// EXPERIMENTAL +// + + printf("Synchronising simulation to UDP-Receiver\n"); + + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + + // + outtype = ORTD.DATATYPE_BINARY; + + // IPv4 + AddrSize = 4+2; // IPnumber + port + + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); + + parlist = new_irparam_elemet_ivec(parlist, outsize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, outtype, 11); // id = 11 + + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + +// Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 2; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + insizes=[]; // Input port sizes + outsizes=[outsize, AddrSize]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[]; // datatype for each input port + outtypes=[outtype, ORTD.DATATYPE_BINARY]; // datatype for each output port + + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); + +// // connect the inputs +// [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 + + // connect the ouputs + [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port + [sim,SrcAddr] = libdyn_new_oport_hint(sim, blk, 1); // 1th port +endfunction + +function [sim] = ld_UDPSocket_SendTo(sim, events, SendSize, ObjectIdentifyer, hostname, UDPPort, in, insize) // PARSEDOCU_BLOCK +// +// UDP - Send block +// +// in *, ORTD.DATATYPE_BINARY - input +// SendSize *. ORTD.DATATYPE_INT32 - Number of bytes to send +// +// EXPERIMENTAL +// + + + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + + // only send binary data + intype=ORTD.DATATYPE_BINARY; + + + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); + + parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + + parlist = new_irparam_elemet_ivec(parlist, UDPPort, 12); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 13); // id = 11; A string parameter + + + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + +// Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 3; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + insizes=[insize, 1]; // Input port sizes + outsizes=[]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[intype, ORTD.DATATYPE_INT32 ]; // datatype for each input port + outtypes=[]; // datatype for each output port + + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); + + // connect the inputs + [sim,blk] = libdyn_conn_equation(sim, blk, list(in, SendSize) ); // connect in1 to port 0 and in2 to port 1 + +// // connect the ouputs +// [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction + + + + + + +function [sim] = ld_UDPSocket_Reply(sim, events, SendSize, ObjectIdentifyer, DestAddr, in, insize) // PARSEDOCU_BLOCK +// +// UDP - Send block +// +// in *, ORTD.DATATYPE_BINARY - input +// SendSize *. ORTD.DATATYPE_INT32 - Number of bytes to send +// DestAddr - dynamic representation for the destination address +// +// EXPERIMENTAL not implemented by now +// + + + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + + // only send binary data + intype=ORTD.DATATYPE_BINARY; + + // IPv4 + AddrSize=4+2; + + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); + + parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + +// parlist = new_irparam_elemet_ivec(parlist, UDPPort, 12); // id = 10 +// parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 13); // id = 11; A string parameter + + + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + +// Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 4; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + insizes=[insize, 1, AddrSize]; // Input port sizes + outsizes=[]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[intype, ORTD.DATATYPE_INT32, ORTD.DATATYPE_BINARY ]; // datatype for each input port + outtypes=[]; // datatype for each output port + + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); + + // connect the inputs + [sim,blk] = libdyn_conn_equation(sim, blk, list(in, SendSize, DestAddr) ); // connect in1 to port 0 and in2 to port 1 + +// // connect the ouputs +// [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction + + + + + + + + + + + + +function [sim, out, NBytes] = ld_ConcateData(sim, events, inlist, insizes, intypes) // PARSEDOCU_BLOCK +// +// Concate Data - block +// +// concatenates the binary representation of all inputs +// +// The output is of type ORTD.DATATYPE_BINARY +// +// EXPERIMENTAL +// + + + // pack all parameters into a structure "parlist" +// parlist = new_irparam_set(); +// +// parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 +// parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 +// +// p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + +// Set-up the block parameters and I/O ports + Uipar = [ ]; + Urpar = [ ]; + btype = 39001 + 10; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + // count the number of bytes + NBytes = 0; + for i = 1:length(inlist) + NBytes = NBytes + insizes(i) * libdyn_datatype_len( intypes(i) ); + end + + + +// insizes=[insizes]; // Input port sizes + outsizes=[ NBytes ]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) +// intypes=[intypes]; // datatype for each input port + outtypes=[ ORTD.DATATYPE_BINARY ]; // datatype for each output port + +// disp(outsizes); +// disp(outtypes); + + blocktype = 2; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed); + + // connect the inputs +// for i = 1:length(inlist) + [sim,blk] = libdyn_conn_equation(sim, blk, inlist ); // connect in1 to port 0 and in2 to port 1 +// end + +// // connect the ouputs + [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction + + +function [sim, outlist] = ld_DisassembleData(sim, events, in, outsizes, outtypes) // PARSEDOCU_BLOCK +// +// disasseble Data - block +// +// disassemble the binary representation of the input, which is of type ORTD.DATATYPE_BINARY +// +// EXPERIMENTAL +// + + + // pack all parameters into a structure "parlist" +// parlist = new_irparam_set(); +// +// parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 +// parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 +// +// p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + +// Set-up the block parameters and I/O ports + Uipar = [ ]; + Urpar = [ ]; + btype = 39001 + 11; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + // count the number of bytes + NBytes = 0; + for i = 1:length(outsizes) + NBytes = NBytes + outsizes(i)*libdyn_datatype_len( outtypes(i) ); + end + + + +// insizes=[insizes]; // Input port sizes + insizes=[ NBytes ]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) +// intypes=[intypes]; // datatype for each input port + intypes=[ ORTD.DATATYPE_BINARY ]; // datatype for each output port + +// disp(outsizes); +// disp(outtypes); + + blocktype = 2; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed); + + // connect the inputs +// for i = 1:length(inlist) + [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 +// end + +// // connect the ouputs + outlist = list(); + for i = 1:length(outtypes) + [sim,outlist($+1)] = libdyn_new_oport_hint(sim, blk, i-1); // 0th port + end +endfunction + + + + + + + + + + +// +// +// A packet based communication interface from ORTD using UDP datagrams to e.g. +// nodejs. +// webappUDP.js is the counterpart that provides a web-interface +// +// Current Rev: 8 +// +// Revisions: +// +// 27.3.14 - possibility to reservate sources +// 3.4.14 - small re-arrangements +// 4.4.14 - Bugfixes +// 7.4.14 - Bugfix +// 12.6.14 - Bugfix +// 2.11.14 - Added group finalising packet +// + + +function [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName) + SourceID = PacketFramework.SourceID_counter; + + Source.SourceName = SourceName; + Source.SourceID = SourceID; + Source.NValues_send = NValues_send; + Source.datatype = datatype; + + // Add new source to the list + PacketFramework.Sources($+1) = Source; + + // inc counter + PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; +endfunction + +function [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName) + ParameterID = PacketFramework.Parameterid_counter; + + Parameter.ParameterName = ParameterName; + Parameter.ParameterID = ParameterID; + Parameter.NValues = NValues; + Parameter.datatype = datatype; + Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; + + // Add new source to the list + PacketFramework.Parameters($+1) = Parameter; + + // inc counters + PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; + PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; + + // return values + ParameterID = Parameter.ParameterID; + MemoryOfs = Parameter.MemoryOfs; +endfunction + +function [sim, PacketFramework, Parameter] = ld_PF_Parameter(sim, PacketFramework, NValues, datatype, ParameterName) // PARSEDOCU_BLOCK +// +// Define a parameter +// +// NValues - amount of data sets +// datatype - only ORTD.DATATYPE_FLOAT for now +// ParameterName - a unique string decribing the parameter +// +// +// + + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); + + // read data from global memory + [sim, readI] = ld_const(sim, 0, MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + datatype, NValues); +endfunction + + + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, SourceID); + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); + + printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + +endfunction + + + + +function [sim, PacketFramework] = ld_SendPacket(sim, PacketFramework, Signal, NValues_send, datatype, SourceName) // PARSEDOCU_BLOCK // PARSEDOCU_BLOCK +// +// Stream data - block +// +// Signal - the signal to stream +// NValues_send - the vector length of Signal +// datatype - only ORTD.DATATYPE_FLOAT by now +// SourceName - a unique string identifier descring the stream +// +// +// + + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); +endfunction + + + + +function [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName, Configuration) // PARSEDOCU_BLOCK +// +// Initialise an instance of the Packet Framework +// +// InstanceName - a unique string identifier for the instance +// Configuration must include the following properties: +// +// Configuration.UnderlyingProtocoll = "UDP" +// Configuration.DestHost +// Configuration.DestPort +// Configuration.LocalSocketHost +// Configuration.LocalSocketPort +// +// +// Example: +// +// +// Configuration.UnderlyingProtocoll = "UDP"; +// Configuration.DestHost = "127.0.0.1"; +// Configuration.DestPort = 20000; +// Configuration.LocalSocketHost = "127.0.0.1"; +// Configuration.LocalSocketPort = 20001; +// [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); +// +// +// +// Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations +// +// + + // initialise structure for sources + PacketFramework.InstanceName = InstanceName; + PacketFramework.Configuration = Configuration; + + PacketFramework.Configuration.debugmode = %F; + +// disp(Configuration.UnderlyingProtocoll) + + if Configuration.UnderlyingProtocoll == 'UDP' + null; + else + error("PacketFramework: Only UDP supported up to now"); + end + + // possible packet sizes for UDP + PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; + + // sources + PacketFramework.SourceID_counter = 0; + PacketFramework.Sources = list(); + + // parameters + PacketFramework.Parameterid_counter = 0; + PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory + PacketFramework.Parameters = list(); + + PacketFramework.SenderID = 1295793; + + // Open an UDP-Port in server mode + [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... + hostname=PacketFramework.Configuration.LocalSocketHost, ... + UDPPort=PacketFramework.Configuration.LocalSocketPort); +endfunction + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Group ID + [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources + [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); + +// printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + + + // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); +endfunction + + +// Send the newConfigAvailable Signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_SendNewConfigAvailableUDP(sim, PacketFramework) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -2); // -2 means a new config is available + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Group ID + [sim, GroupID_] = ld_const(sim, 0, 0); // 0 (not used) + [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); + +// printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + + + // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); +endfunction + +// Send the configuration via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_SendConfigUDP(sim, PacketFramework) + InstanceName = PacketFramework.InstanceName; + + str_PF_Export = ld_PF_Export_str(PacketFramework); + strLength = length(str_PF_Export); + maxPacketLength = 1400; + maxPacketStrLength = maxPacketLength - 4*4; + nPackets = ceil(strLength/maxPacketStrLength); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -4); // -4 means configItem + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // nPackets + [sim, nPackets_] = ld_const(sim, 0, nPackets); // the number of config packets for the whole configuration + [sim, nPackets_int32] = ld_ceilInt32(sim, 0, nPackets_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + for i=1:nPackets + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_const(sim, ev, i); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + partBegin = (i-1)*maxPacketStrLength+1; + partEnd = i*maxPacketStrLength; + if (partEnd > strLength) + partEnd = strLength; + end + strPart_PF_Export = part(str_PF_Export, partBegin:partEnd); + sendSize = length(strPart_PF_Export); + [sim, partPF_Export_bin] = ld_const_bin(sim, ev, in=ascii(strPart_PF_Export)); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, nPackets_int32, partPF_Export_bin), insizes=[1,1,1,1,sendSize], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_BINARY ] ); + + // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + [sim] = ld_printf(sim, ev, SenderID, "SenderID", 1); + [sim] = ld_printf(sim, ev, SourceID, "Sent config packet number " + string(i) + " ", 1); + [sim] = ld_printf(sim, ev, Counter, "Packet Count ", 1); + [sim] = ld_printf(sim, ev, nPackets_, "Number of Packet ", 1); + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + end + +endfunction + +// select case function for the different received PaPi Commands (SenderID) +function [sim, outlist, userdata] = SelectCasePaPiCmd(sim, inlist, Ncase, casename, userdata) + // This function is called multiple times -- once to define each case + // At runtime, all cases will become different nested simulations of + // which only one is active a a time. + + printf("Defining case %s (#%d) ...\n", casename, Ncase ); + + // define names for the first event in the simulation + events = 0; + + DisAsm_ = list(); + DisAsm_(4) = inlist(4); + [sim, DisAsm_(1)] = ld_Int32ToFloat(sim, 0, inlist(1) ); + [sim, DisAsm_(2)] = ld_Int32ToFloat(sim, 0, inlist(2) ); + [sim, DisAsm_(3)] = ld_Int32ToFloat(sim, 0, inlist(3) ); + PacketFramework = userdata(1); + ParameterMemory = PacketFramework.ParameterMemory; + TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + InstanceName = PacketFramework.InstanceName; + // print out some state information + //[sim] = ld_printf(sim, events, in=DisAsm_(3), str="case "+string(casename)+" with Ncase = "+string(Ncase)+" : SourceID", insize=1); + + // sample data for the output + [sim, dummy] = ld_const(sim, 0, 9999); + + // The signals "active_state" is used to indicate state switching: A value > 0 means the + // the state enumed by "active_state" shall be activated in the next time step. + // A value less or equal to zero causes the statemachine to stay in its currently active + // state + select Ncase + case 1 + [sim, memofs] = ld_ArrayInt32(sim, 0, array=ParameterMemory.MemoryOfs, in=inlist(3) ); + [sim, Nelements] = ld_ArrayInt32(sim, 0, array=ParameterMemory.Sizes, in=inlist(3) ); + + [sim, memofs_] = ld_Int32ToFloat(sim, 0, memofs ); + [sim, Nelements_] = ld_Int32ToFloat(sim, 0, Nelements ); + + if PacketFramework.Configuration.debugmode then + // print the contents of the packet + [sim] = ld_printf(sim, 0, DisAsm_(1), "DisAsm(1) (SenderID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(2), "DisAsm(2) (Packet Counter) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(3), "DisAsm(3) (SourceID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(4), "DisAsm(4) (Signal) = ", TotalElemetsPerPacket); + + [sim] = ld_printf(sim, 0, memofs_ , "memofs = ", 1); + [sim] = ld_printf(sim, 0, memofs_ , "Nelements = ", 1); + end + + // Store the input data into a shared memory + [sim] = ld_WriteMemory2(sim, 0, data=inlist(4), index=memofs, ElementsToWrite=Nelements, ... + ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); + + case 2 + [sim] = ld_printf(sim, 0, DisAsm_(3), "Give Config (SourceID) = ", 1); + [sim] = ld_PF_SendConfigUDP(sim, PacketFramework); + + case 3 + [sim] = ld_printf(sim, events, in=DisAsm_(3), str="case "+string(casename)+" : Wrong SourceID", insize=1); + end + + // the user defined output signals of this nested simulation + outlist = list(dummy); + userdata(1) = PacketFramework; +endfunction + +function [sim, outlist, userdata] = SelectCaseSendNewConfigAvailable(sim, inlist, Ncase, casename, userdata) + // This function is called multiple times -- once to define each case + // At runtime, all cases will become different nested simulations of + // which only one is active a a time. + + printf("Defining case %s (#%d) ...\n", casename, Ncase ); + + // define names for the first event in the simulation + events = 0; + + // pause; + + PacketFramework = userdata(1); + + // print out some state information + // [sim] = ld_printf(sim, events, in=in1, str="case"+string(casename)+": in1", insize=1); + + // sample data for the output + [sim, dummy] = ld_const(sim, 0, 999); + + // The signals "active_state" is used to indicate state switching: A value > 0 means the + // the state enumed by "active_state" shall be activated in the next time step. + // A value less or equal to zero causes the statemachine to stay in its currently active + // state + + select Ncase + case 1 // Finished + //[sim] = ld_printf(sim, events, dummy, "case "+string(casename)+" : Config already sent ", 1); + + case 2 // Send + //[sim] = ld_printf(sim, events, dummy, "case "+string(casename)+" : Config is sent to PaPi ", 1); + [sim] = ld_PF_SendNewConfigAvailableUDP(sim, PacketFramework); + + end + + // the user defined output signals of this nested simulation + outlist = list(dummy); + userdata(1) = PacketFramework; +endfunction + + +function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOCU_BLOCK +// +// Finalise the instance. +// +// + + // The main real-time thread + function [sim,PacketFramework] = ld_PF_InitUDP(sim, PacketFramework) + + function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) + // This will run in a thread. Each time a UDP-packet is received + // one simulation step is performed. Herein, the packet is parsed + // and the contained parameters are stored into a memory. + + PacketFramework = userdata(1); + + TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + InstanceName = PacketFramework.InstanceName; + PacketSize = PacketFramework.PacketSize; + // Sync the simulation to incomming UDP-packets + [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); + + // disassemble packet's structure + [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... + outsizes=[1,1,1,TotalElemetsPerPacket], ... + outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); + + [sim, sourceID] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); + // [sim] = ld_printf(sim, 0, sourceID, "DisAsm(3) (SourceID) = ", 1); + [sim, selectSignal_checkGtZero] = ld_cond_overwrite(sim, ev, sourceID, sourceID, 1); + [sim, selectSignal_notCheckGtZero] = ld_not(sim, ev, sourceID); + [sim, selectSignal_checkMinThreeInt32] = ld_CompareEqInt32(sim, ev, DisAsm(3), -3); + [sim, selectSignal_checkMinThree] = ld_Int32ToFloat(sim, ev, selectSignal_checkMinThreeInt32); + [sim, selectSignal_checked] = ld_cond_overwrite(sim, ev, selectSignal_checkGtZero, selectSignal_checkMinThree, 2); + [sim, selectSignal_notCheckMinThree] = ld_not(sim, ev, selectSignal_checkMinThree); + [sim, selectSignal_undefined] = ld_and(sim, ev, list(selectSignal_notCheckGtZero, selectSignal_notCheckMinThree)); + [sim, selectSignal_checkedSecure] = ld_cond_overwrite(sim, ev, selectSignal_checked, selectSignal_undefined, 3); + [sim, selectSignal_checkedSecureInt32] = ld_roundInt32(sim, ev, selectSignal_checkedSecure); + + // set-up the states for each PaPi Command represented by nested simulations + [sim, outlist, userdataSelectCasePaPiCmd] = ld_CaseSwitchNest(sim, ev, ... + inlist=DisAsm, .. + insizes=[1,1,1,TotalElemetsPerPacket], outsizes=[1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ], outtypes=[ORTD.DATATYPE_FLOAT], ... + CaseSwitch_fn=SelectCasePaPiCmd, SimnestName="SwitchSelectPaPiCmd", DirectFeedthrough=%t, SelectSignal=selectSignal_checkedSecureInt32, list("Param", "GiveConfig", "Undefined"), list(PacketFramework) ); + + PacketFramework = userdataSelectCasePaPiCmd(1); + + // output of schematic + outlist = list(); + userdata(1) = PacketFramework; + endfunction + + + + // start the node.js service from the subfolder webinterface + //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); + + TotalMemorySize = sum(PacketFramework.ParameterMemory.Sizes); + InstanceName = PacketFramework.InstanceName; +// // Open an UDP-Port in server mode +// [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... +// hostname=PacketFramework.Configuration.LocalSocketHost, ... +// UDPPort=PacketFramework.Configuration.LocalSocketPort); + + // initialise a global memory for storing the input data for the computation + [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... + datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... + initial_data=[zeros(TotalMemorySize,1)], ... + visibility='global', useMutex=1); + + // Create thread for the receiver + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; + [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step + [sim, outlist, computation_finished, userdata] = ld_async_simulation(sim, 0, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = UDPReceiverThread, ... + TriggerSignal=startcalc, name=InstanceName+"Thread1", ... + ThreadPrioStruct, userdata=list(PacketFramework) ); + + PacketFramework = userdata(1); + + endfunction + + + // calc memory + MemoryOfs = []; + Sizes = []; + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + + Sizes = [Sizes; P.NValues]; + MemoryOfs = [MemoryOfs; P.MemoryOfs]; + end + + PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; + PacketFramework.ParameterMemory.Sizes = Sizes; + + // udp + [sim] = ld_PF_InitUDP(sim, PacketFramework); + + // Send to group update notifications for each group (currently only one possible) + [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); + + + [sim, initSelect] = ld_initimpuls(sim, ev); + [sim, selectNewConfig] = ld_add_ofs(sim, ev, initSelect, 1); + //[sim] = ld_printf(sim, ev, selectNewConfig, ORTD.termcode.red + "Select New Config Signal" + ORTD.termcode.reset, 1); + [sim, selectNewConfigInt32] = ld_roundInt32(sim, ev, selectNewConfig); + // set-up the states to send a Signal to PaPi that a new config is available only in the first time step represented by nested simulations + [sim, outlist, userdata] = ld_CaseSwitchNest(sim, ev, ... + inlist=list(), .. + insizes=[], outsizes=[1], ... + intypes=[], outtypes=[ORTD.DATATYPE_FLOAT], ... + CaseSwitch_fn=SelectCaseSendNewConfigAvailable, SimnestName="SelectCaseSendNewConfigAvailable", DirectFeedthrough=%t, SelectSignal=selectNewConfigInt32, list("Finished", "Send"), list(PacketFramework) ); + + PacketFramework = userdata(1); + +endfunction + + +function str=ld_PF_Export_str(PacketFramework) + str=' {""SourcesConfig"" : {'+char(10); + + for i=1:length(PacketFramework.Sources) + + + SourceID = PacketFramework.Sources(i).SourceID; + SourceName = PacketFramework.Sources(i).SourceName; + disp(SourceID ); + disp( SourceName ); + + + line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"" } ", ... + string(PacketFramework.Sources(i).SourceID), ... + string(PacketFramework.Sources(i).SourceName), ... + string(PacketFramework.Sources(i).NValues_send), ... + string(PacketFramework.Sources(i).datatype) ); + + + if i==length(PacketFramework.Sources) + // finalise the last entry without "," + printf('%s' , line); + str=str+line + char(10); + else + printf('%s,' , line); + str=str+line+',' + char(10); + end + + + end + + + + str=str+'} , ' + char(10) + ' ""ParametersConfig"" : {' + char(10); + + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + + printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); + + line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } ", ... + string(PacketFramework.Parameters(i).ParameterID), ... + string(PacketFramework.Parameters(i).ParameterName), ... + string(PacketFramework.Parameters(i).NValues), ... + string(PacketFramework.Parameters(i).datatype) ); + + + if i==length(PacketFramework.Parameters) + // finalise the last entry without "," + printf('%s' , line); + str=str+line + char(10); + else + printf('%s,' , line); + str=str+line+',' + char(10); + end + + + end + str=str+'}'+char(10)+'}'; +endfunction + +function ld_PF_Export_js(PacketFramework, fname) // PARSEDOCU_BLOCK +// +// Export configuration of the defined protocoll (Sources, Parameters) +// into JSON-format. This is to be used by software that shall communicate +// to the real-time system. +// +// fname - The file name +// +// + str=ld_PF_Export_str(PacketFramework); + + fd = mopen(fname,'wt'); + mfprintf(fd,'%s', str); + mclose(fd); + +endfunction + +// +// Added 27.3.14 +// + +function [sim, PacketFramework, SourceID]=ld_SendPacketReserve(sim, PacketFramework, NValues_send, datatype, SourceName) + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); +endfunction + +function [sim, PacketFramework]=ld_SendPacket2(sim, PacketFramework, Signal, SourceName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Sources) + S = PacketFramework.Sources(i); + if S.SourceName == SourceName + index = i; + printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); + break; + end + end + + if index == -1 + printf("SourceName = %s\n", SourceName); + error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); + end + + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); +endfunction + + + +function [sim, PacketFramework, ParameterID]=ld_PF_ParameterReserve(sim, PacketFramework, NValues, datatype, ParameterName) + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); +endfunction + + +function [sim, PacketFramework, Parameter]=ld_PF_Parameter2(sim, PacketFramework, ParameterName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + if P.ParameterName == ParameterName + index = i; + printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); + break; + end + end + + if index == -1 + printf("ParameterName = %s\n", ParameterName); + error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); + end + + // read data from global memory + [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + P.datatype, P.NValues); +endfunction + + + + diff --git a/data_sources/ORTD/DataSourceExample_Groups/PF.sci b/data_sources/ORTD/DataSourceExample_Groups/PF.sci index c9b7cd1f..cff07675 100644 --- a/data_sources/ORTD/DataSourceExample_Groups/PF.sci +++ b/data_sources/ORTD/DataSourceExample_Groups/PF.sci @@ -8,7 +8,7 @@ // nodejs. // webappUDP.js is the counterpart that provides a web-interface // -// Current Rev: 9 +// Current Rev: 10 // // Revisions: // @@ -18,7 +18,8 @@ // 7.4.14 - Bugfix // 12.6.14 - Bugfix // 2.11.14 - Added group finalising packet -// 12.14 - Added Support for Groups +// 12.1.14 - Added Support for Groups +// 27.1.14 - Added the possibility to request the protocoll configuration via network // @@ -303,6 +304,10 @@ function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOC // Sync the simulation to incomming UDP-packets [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); + + + + // disassemble packet's structure [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... outsizes=[1,1,1,TotalElemetsPerPacket], ... From 6cecaab2c7712d39367b21e7542dbbbbb29f1d1a Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Wed, 4 Mar 2015 16:41:54 +0100 Subject: [PATCH 150/260] ortd and dynamik cfg. new event for block deletion --- papi/event/data/DeleteBlock.py | 37 ++++ papi/event/data/__init__.py | 3 +- papi/plugin/base_classes/base_plugin.py | 4 + papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 178 ++++++------------ .../visual/OrtdController/OrtdController.py | 140 +++++++------- 5 files changed, 176 insertions(+), 186 deletions(-) create mode 100644 papi/event/data/DeleteBlock.py diff --git a/papi/event/data/DeleteBlock.py b/papi/event/data/DeleteBlock.py new file mode 100644 index 00000000..18cea183 --- /dev/null +++ b/papi/event/data/DeleteBlock.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'Stefan' + +from papi.event.data.DataBase import DataBase + + +class DeleteBlock(DataBase): + def __init__(self, oID, destID, Blockname ,opt=None): + super().__init__(oID,destID, 'delete_block', opt) + self.blockname = Blockname \ No newline at end of file diff --git a/papi/event/data/__init__.py b/papi/event/data/__init__.py index 9deaa469..58a5bd91 100644 --- a/papi/event/data/__init__.py +++ b/papi/event/data/__init__.py @@ -4,4 +4,5 @@ from .NewData import NewData from .NewBlock import NewBlock from .NewParameter import NewParameter -from .EditDPlugin import EditDPlugin \ No newline at end of file +from .EditDPlugin import EditDPlugin +from .DeleteBlock import DeleteBlock \ No newline at end of file diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py index e0a53471..251a9143 100644 --- a/papi/plugin/base_classes/base_plugin.py +++ b/papi/plugin/base_classes/base_plugin.py @@ -152,6 +152,10 @@ def send_new_parameter_list(self, parameters): event = Event.data.NewParameter(self.__id__, 0, opt) self._Core_event_queue__.put(event) + def send_delete_block(self, blockname): + event = Event.data.DeleteBlock(self.__id__, 0,blockname) + self._Core_event_queue__.put(event) + def update_plugin_meta(self, dplug): self.dplugin_info = dplug self.__subscription_for_demux = self.dplugin_info.get_subscribtions() diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index a0a1b67a..d9bc7210 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -110,73 +110,16 @@ def start_init(self, config=None): self.sock_parameter = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock_parameter.setblocking(1) - # Load protocol config. - #path = config['Cfg_Path']['value'] - #f = open(path, 'r') - #self.ProtocolConfig = json.load(f) - #self.Sources = self.ProtocolConfig['SourcesConfig'] - #self.Parameters = self.ProtocolConfig['ParametersConfig'] - - - # For each group:: loop through all sources (=signals) in the group and register the signals - # Register signals - - - # if self.separate == 1: - # - # self.blocks = {} - # - # # sort hash keys for usage in right order! - # keys = list(self.Sources.keys()) - # #keys.sort() - # for key in keys: - # Source = self.Sources[key] - # block = DBlock('SourceGroup' + str(key)) - # block.add_signal(DSignal(Source['SourceName'])) - # self.blocks[int(key)] = block - # - # - # self.send_new_block_list(list(self.blocks.values())) - # - # else: - # self.block1 = DBlock('SourceGroup0') - # - # keys = list(self.Sources.keys()) - # #keys.sort() - # for key in keys: - # Source = self.Sources[key] - # sig_name = Source['SourceName'] - # self.block1.add_signal(DSignal(sig_name)) - - - - - - # self.ControlBlock = DBlock('ControllerSignals') - # self.ControlBlock.add_signal(DSignal('ControlSignalReset')) - # self.ControlBlock.add_signal(DSignal('ControlSignalCreate')) - # self.ControlBlock.add_signal(DSignal('ControlSignalSub')) - # self.ControlBlock.add_signal(DSignal('ControllerSignalParameter')) - # self.ControlBlock.add_signal(DSignal('ControllerSignalClose')) - # - # #self.block1 = DBlock(None, 1, 2, 'SourceGroup0', names) - # self.send_new_block_list([self.block1, self.ControlBlock]) - - # Register parameters - #self.Parameter_List = [] - # - # for Pid in self.Parameters: - # Para = self.Parameters[Pid] - # para_name = Para['ParameterName'] - # val_count = Para['NValues'] - # opt_object = OptionalObject(Pid, val_count) - # Parameter = DParameter(para_name, default=0, OptionalObject=opt_object) - # self.Parameter_List.append(Parameter) - # - # self.ControlParameter = DParameter('triggerConfiguration',default=0) - # self.Parameter_List.append(self.ControlParameter) - - #self.send_new_parameter_list(self.Parameter_List) + + + self.ControlBlock = DBlock('ControllerSignals') + self.ControlBlock.add_signal(DSignal('ControlSignalReset')) + self.ControlBlock.add_signal(DSignal('ControlSignalCreate')) + self.ControlBlock.add_signal(DSignal('ControlSignalSub')) + self.ControlBlock.add_signal(DSignal('ControllerSignalParameter')) + self.ControlBlock.add_signal(DSignal('ControllerSignalClose')) + self.send_new_block_list([self.ControlBlock]) + self.t = 0 @@ -191,8 +134,6 @@ def start_init(self, config=None): self.thread = threading.Thread(target=self.thread_execute) self.thread.start() - #self.process_received_package(None) - self.blocks = {} self.Sources = {} @@ -202,6 +143,9 @@ def start_init(self, config=None): self.block_id = 0 + self.send_new_block_list([DBlock('Test1')]) + self.send_delete_block('Test1') + return True def pause(self): @@ -243,26 +187,20 @@ def thread_execute(self): def process_received_package(self, rev): SenderId, Counter, SourceId = struct.unpack_from(' 0: # data stream finished self.process_finished_action(SourceId, rev) self.signal_values = {} - - if SourceId >= 0: + if SourceId >= 0 and len(self.blocks) > 0: # got data stream self.process_data_stream(SourceId, rev) - if SourceId == -2: # new config in ORTD available # send trigger to get new config - print('newcfg') - Counter = 1 - data = struct.pack(' 0: + Data = self.event_list.pop(0) + ############################ + # Reset Signal # + ############################ + if 'ControlSignalReset' in Data: + val = Data['ControlSignalReset'] + if val == 1: + while len(self.plugin_started_list): + pl_uname = self.plugin_started_list.pop() + self.control_api.do_delete_plugin_uname(pl_uname) + time.sleep(0.5) + + ############################ + # Create Plugins # + ############################ + if 'ControlSignalCreate' in Data: + cfg = Data['ControlSignalCreate'] + if cfg is not None: + for pl_uname in cfg: + if pl_uname not in self.plugin_started_list: + pl_cfg = cfg[pl_uname] + self.control_api.do_create_plugin(pl_cfg['identifier']['value'],pl_uname, pl_cfg['config']) + self.plugin_started_list.append(pl_uname) + time.sleep(0.5) + + ############################ + # Create Subs # + ############################ + if 'ControlSignalSub' in Data: + cfg = Data['ControlSignalSub'] + if cfg is not None: + for pl_uname in cfg: + pl_cfg = cfg[pl_uname] + sig = [] + if 'signals' in pl_cfg: + sig = pl_cfg['signals'] + self.control_api.do_subscribe_uname(pl_uname,self.ortd_uname, pl_cfg['block'], signals=sig, sub_alias= None) + + ############################ + # Set parameter links # + ############################ + if 'ControllerSignalParameter' in Data: + cfg = Data['ControllerSignalParameter'] + if cfg is not None: + for pl_uname in cfg: + pl_cfg = cfg[pl_uname] + para = None + if 'parameter' in pl_cfg: + para = pl_cfg['parameter'] + self.control_api.do_subscribe_uname(self.ortd_uname,pl_uname, pl_cfg['block'], signals=[], sub_alias= para) + + ############################ + # Close plugin # + ############################ + if 'ControllerSignalClose' in Data: + cfg = Data['ControllerSignalClose'] + if cfg is not None: + for pl_uname in cfg: + if pl_uname in self.plugin_started_list: + self.control_api.do_delete_plugin_uname(pl_uname) + self.plugin_started_list.remove(pl_uname) + + self.thread_alive = False def set_parameter(self, name, value): # attetion: value is a string and need to be processed ! From 646cb95e71c40142dc188a43df90a6f07bf95a18 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Wed, 4 Mar 2015 17:57:49 +0100 Subject: [PATCH 151/260] ortd and dynamik cfg. new event for block deletion --- papi/core.py | 25 ++++++++++++++++- papi/data/DCore.py | 23 ++++++++++++--- papi/event/data/DeleteParameter.py | 37 +++++++++++++++++++++++++ papi/event/data/__init__.py | 3 +- papi/plugin/base_classes/base_plugin.py | 6 +++- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 9 +++--- 6 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 papi/event/data/DeleteParameter.py diff --git a/papi/core.py b/papi/core.py index f13acea8..0c9703d6 100644 --- a/papi/core.py +++ b/papi/core.py @@ -82,6 +82,8 @@ def __init__(self, gui_start_function, use_gui=True): 'new_block': self.__process_new_block__, 'new_parameter': self.__process_new_parameter__, 'edit_dplugin': self.__process_edit_dplugin, + 'delete_block': self.__process_delete_block__, + 'delete_parameter': self.__delete_parameter__ } self.__process_instr_event_l__ = {'create_plugin': self.__process_create_plugin__, @@ -836,7 +838,6 @@ def __process_start_plugin__(self, event): self.gui_event_queue.put(event) self.update_meta_data_to_gui(id) - def __process_plugin_stopped__(self, event): """ Process plugin_stopped event. @@ -1055,6 +1056,28 @@ def __process_new_block__(self, event): self.log.printText(1, 'new_block, plugin with id ' + str(pl_id) + ' not found') return -1 + def __process_delete_block__(self, event): + """ + Processes delete_block event. + Will try to delete a Block of a plugin + + :param event: event to process + :type event: PapiEventBase + """ + pl_id = event.get_originID() + self.core_data.rm_all_subscribers_of_a_dblock(pl_id, event.blockname) + + plugin = self.core_data.get_dplugin_by_id(pl_id) + dblock = plugin.get_dblock_by_name(event.blockname) + plugin.rm_dblock(dblock) + + self.update_meta_data_to_gui_for_all() + + def __delete_parameter__(self, event): + print('Delete') + + + def __process_new_parameter__(self, event): """ Processes new parameter event. Adding a new parameter to DPluign in DCore and updating GUI information diff --git a/papi/data/DCore.py b/papi/data/DCore.py index cb3c0dca..a6e3710d 100644 --- a/papi/data/DCore.py +++ b/papi/data/DCore.py @@ -299,10 +299,25 @@ def rm_all_subscribers(self, dplugin_id): subscriber.unsubscribe(dblock) dblock.rm_subscriber(subscriber) - if len(dplugin.get_dblocks()) == 0: - return True - else: - return False + # if len(dplugin.get_dblocks()) == 0: + # return True + # else: + # return False + + + def rm_all_subscribers_of_a_dblock(self, dplugin_id, dblock_name): + dplugin = self.get_dplugin_by_id(dplugin_id) + if dplugin is not None: + dblock = dplugin.get_dblock_by_name(dblock_name) + if dblock is not None: + dplugin_ids = dblock.get_subscribers() + for dplugin_id in dplugin_ids: + + subscriber = self.get_dplugin_by_id(dplugin_id) + + subscriber.unsubscribe(dblock) + dblock.rm_subscriber(subscriber) + def subscribe_signals(self, subscriber_id, target_id, dblock_name, signals): """ diff --git a/papi/event/data/DeleteParameter.py b/papi/event/data/DeleteParameter.py new file mode 100644 index 00000000..b69a21e1 --- /dev/null +++ b/papi/event/data/DeleteParameter.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.data.DataBase import DataBase + + +class DeleteParameter(DataBase): + def __init__(self, oID, destID, parameterName , opt=None): + super().__init__(oID,destID, 'delete_parameter', opt) + self.parameterName = parameterName \ No newline at end of file diff --git a/papi/event/data/__init__.py b/papi/event/data/__init__.py index 58a5bd91..9c501f1d 100644 --- a/papi/event/data/__init__.py +++ b/papi/event/data/__init__.py @@ -5,4 +5,5 @@ from .NewBlock import NewBlock from .NewParameter import NewParameter from .EditDPlugin import EditDPlugin -from .DeleteBlock import DeleteBlock \ No newline at end of file +from .DeleteBlock import DeleteBlock +from .DeleteParameter import DeleteParameter \ No newline at end of file diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py index 251a9143..e1160d9b 100644 --- a/papi/plugin/base_classes/base_plugin.py +++ b/papi/plugin/base_classes/base_plugin.py @@ -153,7 +153,11 @@ def send_new_parameter_list(self, parameters): self._Core_event_queue__.put(event) def send_delete_block(self, blockname): - event = Event.data.DeleteBlock(self.__id__, 0,blockname) + event = Event.data.DeleteBlock(self.__id__, 0, blockname) + self._Core_event_queue__.put(event) + + def send_delete_parameter(self, parameterName): + event = Event.data.DeleteParameter(self.__id__, 0, parameterName) self._Core_event_queue__.put(event) def update_plugin_meta(self, dplug): diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index d9bc7210..34b475bd 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -143,8 +143,8 @@ def start_init(self, config=None): self.block_id = 0 - self.send_new_block_list([DBlock('Test1')]) - self.send_delete_block('Test1') + self.send_new_parameter_list([DParameter('Test1')]) + self.send_delete_parameter('Test1') return True @@ -281,8 +281,9 @@ def update_parameter_list(self, ORTDParameter): toDeleteDict = self.parameters self.parameters = newList + for par in toDeleteDict: + self.send_delete_parameter(par) - print('ToDo: Delete parameter:', toDeleteDict) def update_block_list(self,ORTDSources): self.block_id = self.block_id +1 @@ -300,7 +301,7 @@ def update_block_list(self,ORTDSources): # Remove BLOCKS if 'SourceGroup'+str(self.block_id-1) in self.blocks: - self.send_delete_block(self.blocks.pop('SourceGroup'+str(self.block_id-1))) + self.send_delete_block(self.blocks.pop('SourceGroup'+str(self.block_id-1)).name) def process_data_stream(self, SourceId, rev): # Received a data packet From 88bfe48076414272860edc390c12e2bf827f835c Mon Sep 17 00:00:00 2001 From: christianausb Date: Thu, 5 Mar 2015 10:30:30 +0100 Subject: [PATCH 152/260] JSON export of PaPiConfig --- .../SelectCasePaPiConfig.ipar | 2228 ++++++++++++----- .../SelectCasePaPiConfig.sce | 16 +- .../webinterface/PacketFramework.sce | 1500 +++++------ 3 files changed, 2420 insertions(+), 1324 deletions(-) diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar index d76470ea..f680df5f 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar @@ -4,7 +4,7 @@ 10 0 0 - 13884 + 14886 26 1 3 @@ -18,11 +18,11 @@ 100 6 0 - 13846 + 14848 26 100 101 - 13852 + 14854 26 12 0 @@ -34,7 +34,7 @@ 0 15011 203 - 13840 + 14842 26 1 0 @@ -80,11 +80,11 @@ 4 12 0 - 13777 + 14779 0 21 1 - 13789 + 14791 0 1 26 @@ -100,7 +100,7 @@ 1 1 1 - 13776 + 14778 1 7 10 @@ -143,7 +143,7 @@ 10 27 0 - 13705 + 14707 26 0 0 @@ -196,17 +196,17 @@ 100 84 2 - 13540 + 14542 24 211 100 - 13624 + 14626 26 15 0 100 101 - 13639 + 14641 26 28 0 @@ -296,7 +296,7 @@ 0 15002 207 - 13534 + 14536 24 1 0 @@ -348,11 +348,11 @@ 10 376 11 - 13040 + 14042 11 902 10 - 13416 + 14418 22 62 2 @@ -744,95 +744,95 @@ 100 6 0 - 12622 + 13624 4 208 100 - 12628 + 13630 4 6 0 210 100 - 12634 + 13636 4 7 0 212 100 - 12641 + 13643 4 8 0 215 100 - 12649 + 13651 4 6 0 217 100 - 12655 + 13657 4 28 0 218 100 - 12683 + 13685 4 31 0 219 100 - 12714 + 13716 4 6 1 221 100 - 12720 + 13722 5 24 0 222 100 - 12744 + 13746 5 6 1 224 100 - 12750 + 13752 6 6 1 226 100 - 12756 + 13758 7 6 1 228 100 - 12762 + 13764 8 6 1 230 100 - 12768 + 13770 9 6 1 232 100 - 12774 + 13776 10 6 1 100 101 - 12780 + 13782 11 156 0 @@ -844,7 +844,7 @@ 0 15011 205 - 12616 + 13618 4 1 0 @@ -890,11 +890,11 @@ 4 14 0 - 12551 + 13553 0 21 1 - 12565 + 13567 0 1 4 @@ -912,7 +912,7 @@ 1 1 1 - 12550 + 13552 1 7 10 @@ -955,7 +955,7 @@ 10 22 0 - 12484 + 13486 4 0 1 @@ -991,53 +991,53 @@ 100 6 1 - 12132 + 13134 0 205 100 - 12138 + 13140 1 44 0 206 100 - 12182 + 13184 1 6 1 208 100 - 12188 + 13190 2 6 1 210 100 - 12194 + 13196 3 8 0 212 100 - 12202 + 13204 3 8 0 214 100 - 12210 + 13212 3 6 1 216 100 - 12216 + 13218 4 130 0 100 101 - 12346 + 13348 4 76 0 @@ -1049,7 +1049,7 @@ 0 22000 203 - 12126 + 13128 0 1 0 @@ -1064,7 +1064,7 @@ 0 0 0 - 11899 + 12901 105 94 12 @@ -10384,11 +10384,6 @@ 32 32 32 - 91 - 115 - 105 - 109 - 44 80 97 99 @@ -10404,167 +10399,62 @@ 111 114 107 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 83 - 101 - 110 - 100 + 46 80 97 - 99 - 107 - 101 - 116 - 40 - 115 - 105 - 109 - 44 - 32 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 + 73 + 67 111 - 114 - 107 - 44 - 32 - 83 + 110 + 102 105 103 - 110 - 97 - 108 - 61 - 118 - 44 - 32 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 95 - 115 - 101 - 110 - 100 - 61 - 49 - 44 - 32 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 61 - 79 - 82 - 84 - 68 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 84 - 44 - 32 - 83 111 - 117 + 67 114 - 99 101 - 78 97 - 109 + 116 101 - 61 - 39 - 86 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 102 - 105 - 110 - 97 + 46 + 112 108 - 105 - 115 - 101 - 32 + 111 116 - 104 + 95 + 88 + 46 + 105 + 100 101 - 32 - 99 - 111 - 109 - 109 - 117 110 - 105 - 99 - 97 116 105 - 111 - 110 - 32 + 102 105 - 110 - 116 101 114 - 102 + 46 + 118 97 - 99 + 108 + 117 101 + 32 + 61 + 32 + 39 + 112 + 108 + 111 + 116 + 95 + 120 + 39 + 59 10 32 32 @@ -10572,11 +10462,6 @@ 32 32 32 - 91 - 115 - 105 - 109 - 44 80 97 99 @@ -10592,29 +10477,72 @@ 111 114 107 - 93 - 32 - 61 - 32 - 108 - 100 - 95 + 46 80 - 70 - 95 - 70 - 105 + 97 + 80 + 73 + 67 + 111 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 97 + 116 + 101 + 46 + 112 108 + 111 + 116 + 95 + 88 + 46 + 99 + 111 + 110 + 102 105 + 103 + 46 115 + 105 + 122 + 101 + 46 + 118 + 97 + 108 + 117 101 + 32 + 61 + 32 + 39 40 - 115 - 105 - 109 + 51 + 48 + 48 44 + 51 + 48 + 48 + 41 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 32 80 97 @@ -10631,8 +10559,877 @@ 111 114 107 - 41 - 59 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 99 + 111 + 110 + 102 + 105 + 103 + 46 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 + 46 + 118 + 97 + 108 + 117 + 101 + 32 + 61 + 32 + 39 + 40 + 51 + 48 + 48 + 44 + 48 + 41 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 99 + 111 + 110 + 102 + 105 + 103 + 46 + 110 + 97 + 109 + 101 + 46 + 118 + 97 + 108 + 117 + 101 + 32 + 61 + 32 + 39 + 80 + 108 + 111 + 116 + 32 + 88 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 83 + 117 + 98 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 98 + 108 + 111 + 99 + 107 + 32 + 61 + 32 + 39 + 83 + 111 + 117 + 114 + 99 + 101 + 71 + 114 + 111 + 117 + 112 + 48 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 83 + 117 + 98 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 115 + 105 + 103 + 110 + 97 + 108 + 115 + 32 + 61 + 32 + 39 + 88 + 39 + 59 + 47 + 47 + 32 + 110 + 111 + 116 + 32 + 111 + 107 + 97 + 121 + 32 + 115 + 111 + 32 + 102 + 97 + 114 + 44 + 32 + 110 + 101 + 101 + 100 + 32 + 97 + 110 + 32 + 97 + 114 + 114 + 97 + 121 + 32 + 111 + 102 + 32 + 115 + 116 + 114 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 118 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 86 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 109 + 109 + 117 + 110 + 105 + 99 + 97 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 70 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 87 + 97 + 105 + 116 + 32 + 117 + 110 + 116 + 105 + 108 + 32 + 97 + 32 + 110 + 117 + 109 + 98 + 101 + 114 + 32 + 111 + 102 + 32 + 116 + 105 + 109 + 101 + 32 + 115 + 116 + 101 + 112 + 115 + 32 + 104 + 97 + 115 + 32 + 112 + 97 + 115 + 115 + 101 + 100 + 44 + 32 + 116 + 104 + 101 + 110 + 32 + 110 + 111 + 116 + 105 + 102 + 121 + 32 + 116 + 104 + 97 + 116 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 98 + 121 + 32 + 115 + 101 + 116 + 116 + 105 + 110 + 103 + 32 + 34 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 34 + 32 + 116 + 111 + 32 + 49 + 46 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 116 + 101 + 112 + 115 + 50 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 105 + 111 + 110 + 95 + 115 + 105 + 109 + 115 + 116 + 101 + 112 + 115 + 61 + 78 + 83 + 97 + 109 + 112 + 108 + 101 + 115 + 44 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 61 + 91 + 48 + 44 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 49 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 10 32 32 @@ -10646,81 +11443,56 @@ 47 47 32 - 87 - 97 - 105 + 99 + 104 + 111 + 115 + 101 + 32 116 + 104 + 101 32 - 117 110 + 101 + 120 116 - 105 - 108 32 + 115 + 116 97 - 32 - 110 - 117 - 109 - 98 + 116 101 - 114 32 + 116 111 - 102 32 - 116 - 105 - 109 101 - 32 - 115 + 110 116 101 - 112 - 115 + 114 32 + 119 104 - 97 - 115 - 32 - 112 - 97 - 115 - 115 101 - 100 - 44 + 110 32 116 104 101 - 110 32 - 110 - 111 - 116 + 99 + 97 + 108 105 - 102 - 121 - 32 - 116 - 104 + 98 + 114 97 116 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 116 - 104 - 101 + 105 + 111 + 110 32 101 120 @@ -10745,151 +11517,159 @@ 104 101 100 + 10 + 32 + 32 + 32 + 32 32 - 98 - 121 32 + 117 115 101 + 114 + 100 + 97 116 + 97 + 46 + 83 116 - 105 - 110 - 103 + 97 + 116 + 101 32 - 34 - 102 - 105 + 61 + 32 + 39 + 99 + 111 110 - 105 115 - 104 - 101 - 100 - 34 - 32 116 - 111 - 32 - 49 - 46 + 97 + 110 + 116 + 39 + 59 10 32 32 + 10 32 32 32 32 - 91 + 99 + 97 115 - 105 - 109 - 44 - 102 - 105 + 101 + 32 + 39 + 99 + 111 110 - 105 115 - 104 - 101 - 100 - 93 + 116 + 97 + 110 + 116 + 39 + 59 32 - 61 + 47 + 47 32 - 108 100 - 95 - 115 - 116 101 - 112 - 115 - 50 - 40 115 105 - 109 - 44 - 32 - 101 - 118 - 44 + 103 + 110 32 97 + 32 99 + 111 + 110 + 115 116 - 105 - 118 97 + 110 116 + 32 + 115 105 - 111 + 103 110 - 95 + 97 + 108 + 32 + 116 + 111 + 32 115 + 101 + 110 + 100 + 32 + 116 + 111 + 32 + 80 + 97 + 80 105 - 109 - 115 + 32 116 + 104 101 - 112 - 115 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 49 + 32 61 - 78 - 83 - 97 - 109 - 112 - 108 - 101 - 115 - 44 32 - 118 - 97 + 105 + 110 108 - 117 - 101 + 105 115 - 61 - 91 - 48 - 44 + 116 + 40 49 - 93 41 59 10 32 32 - 10 - 32 - 32 32 32 32 32 - 91 115 105 109 - 44 - 111 - 117 - 116 - 93 32 61 32 108 100 95 - 99 - 111 + 112 + 114 + 105 110 - 115 116 + 102 40 115 105 @@ -10899,35 +11679,38 @@ 48 44 32 + 105 + 110 49 - 49 - 41 - 59 - 10 - 32 - 32 - 32 - 32 + 44 32 + 39 + 67 + 97 + 115 + 101 32 + 99 111 - 117 - 116 - 108 - 105 + 110 115 116 - 32 - 61 - 32 - 108 - 105 - 115 + 97 + 110 116 - 40 - 111 - 117 + 32 + 97 + 99 116 + 105 + 118 + 101 + 58 + 32 + 39 + 44 + 32 + 49 41 59 10 @@ -10943,80 +11726,168 @@ 47 47 32 - 99 - 104 - 111 - 115 - 101 - 32 + 83 116 - 104 + 114 101 + 97 + 109 32 - 110 - 101 - 120 - 116 + 97 32 + 99 + 111 + 110 115 116 97 + 110 116 - 101 + 10 + 32 + 32 32 - 116 - 111 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 101 - 110 116 + 70 + 114 + 97 + 109 101 + 119 + 111 114 + 107 + 93 32 - 119 - 104 + 61 + 32 + 108 + 100 + 95 + 83 101 110 - 32 - 116 - 104 + 100 + 80 + 97 + 99 + 107 101 + 116 + 40 + 115 + 105 + 109 + 44 32 + 80 + 97 99 + 107 + 101 + 116 + 70 + 114 97 - 108 - 105 - 98 + 109 + 101 + 119 + 111 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 97 - 116 + 108 + 61 105 - 111 110 + 49 + 44 32 + 78 + 86 + 97 + 108 + 117 101 - 120 - 112 - 101 - 114 - 105 - 109 + 115 + 95 + 115 101 110 - 116 + 100 + 61 + 49 + 44 32 - 104 + 100 97 - 115 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 32 - 102 - 105 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 67 + 111 110 - 105 115 - 104 - 101 - 100 + 116 + 39 + 41 + 59 10 32 32 @@ -11024,129 +11895,242 @@ 32 32 32 - 117 - 115 + 80 + 97 + 99 + 107 101 + 116 + 70 114 - 100 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 116 - 97 + 95 + 88 46 - 83 + 105 + 100 + 101 + 110 116 + 105 + 102 + 105 + 101 + 114 + 46 + 118 97 - 116 + 108 + 117 101 32 61 32 39 - 99 + 112 + 108 111 - 110 - 115 - 116 - 97 - 110 116 + 95 + 88 39 59 10 32 32 - 10 32 32 32 32 + 80 + 97 99 + 107 + 101 + 116 + 70 + 114 97 - 115 + 109 101 - 32 - 39 - 99 + 119 111 - 110 - 115 - 116 + 114 + 107 + 46 + 80 97 + 80 + 73 + 67 + 111 110 - 116 - 39 - 59 - 32 - 47 - 47 - 32 - 100 - 101 - 115 + 102 105 103 - 110 - 32 + 46 + 84 + 111 + 67 + 114 + 101 97 - 32 - 99 + 116 + 101 + 46 + 112 + 108 111 - 110 - 115 116 - 97 + 95 + 88 + 46 + 99 + 111 110 - 116 - 32 - 115 + 102 105 103 - 110 + 46 + 115 + 105 + 122 + 101 + 46 + 118 97 108 + 117 + 101 + 32 + 61 + 32 + 39 + 40 + 51 + 48 + 48 + 44 + 51 + 48 + 48 + 41 + 39 + 59 + 10 32 - 116 - 111 32 - 115 - 101 - 110 - 100 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 116 + 70 + 114 + 97 + 109 + 101 + 119 111 - 32 + 114 + 107 + 46 80 97 80 + 73 + 67 + 111 + 110 + 102 105 - 32 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 116 - 104 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 99 + 111 110 - 10 - 32 - 32 - 32 - 32 - 32 - 32 + 102 + 105 + 103 + 46 + 112 + 111 + 115 + 105 + 116 105 + 111 110 - 49 + 46 + 118 + 97 + 108 + 117 + 101 32 61 32 - 105 - 110 - 108 - 105 - 115 - 116 + 39 40 - 49 + 51 + 48 + 48 + 44 + 48 41 + 39 59 10 32 @@ -11155,94 +12139,82 @@ 32 32 32 - 115 - 105 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 109 - 32 - 61 - 32 - 108 - 100 - 95 - 112 + 101 + 119 + 111 114 - 105 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 110 - 116 102 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 105 - 110 - 49 - 44 - 32 - 39 + 103 + 46 + 84 + 111 67 + 114 + 101 97 - 115 + 116 101 - 32 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 99 111 110 - 115 - 116 - 97 + 102 + 105 + 103 + 46 110 - 116 - 32 97 - 99 - 116 - 105 + 109 + 101 + 46 118 + 97 + 108 + 117 101 - 58 - 32 - 39 - 44 - 32 - 49 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 32 + 61 32 - 32 - 47 - 47 - 32 - 83 + 39 + 80 + 108 + 111 116 - 114 - 101 - 97 - 109 - 32 - 97 32 - 99 + 67 111 110 115 116 - 97 - 110 - 116 + 39 + 59 10 32 32 @@ -11250,11 +12222,6 @@ 32 32 32 - 91 - 115 - 105 - 109 - 44 80 97 99 @@ -11270,28 +12237,60 @@ 111 114 107 - 93 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 83 + 117 + 98 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 98 + 108 + 111 + 99 + 107 32 61 32 - 108 - 100 - 95 + 39 83 - 101 - 110 - 100 - 80 - 97 + 111 + 117 + 114 99 - 107 101 - 116 - 40 - 115 - 105 - 109 - 44 + 71 + 114 + 111 + 117 + 112 + 48 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 32 80 97 @@ -11308,86 +12307,89 @@ 111 114 107 - 44 - 32 - 83 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 105 103 - 110 - 97 + 46 + 84 + 111 + 83 + 117 + 98 + 46 + 112 108 - 61 + 111 + 116 + 95 + 88 + 46 + 115 105 + 103 110 - 49 - 44 - 32 - 78 - 86 97 108 - 117 - 101 115 - 95 - 115 - 101 - 110 - 100 + 32 61 - 49 - 44 32 - 100 - 97 + 39 + 67 + 111 + 110 + 115 116 - 97 + 39 + 59 + 47 + 47 + 32 + 110 + 111 116 + 32 + 111 + 107 + 97 121 - 112 - 101 - 61 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 44 32 - 83 + 115 111 - 117 + 32 + 102 + 97 114 - 99 + 44 + 32 + 110 101 - 78 - 97 - 109 101 - 61 - 39 - 67 - 111 + 100 + 32 + 97 110 + 32 + 97 + 114 + 114 + 97 + 121 + 32 + 111 + 102 + 32 115 116 - 39 - 41 - 59 + 114 10 32 32 diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce index 5353f956..0e476639 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce @@ -106,7 +106,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) // default output (dummy) outlist=list(zero); - + // // Here a state-machine is implemented that may be used to implement some automation // logic that is executed during runtime using the embedded Scilab interpreter. @@ -136,6 +136,14 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) // Stream the data of the oscillator [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X"); + PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'plot_x'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot X'; + PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup0'; + PacketFramework.PaPIConfig.ToSub.plot_X.signals = 'X'; // not okay so far, need an array of str + + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V"); // finalise the communication interface @@ -157,6 +165,12 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) // Stream a constant [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=in1, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Const"); + PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'plot_X'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot Const'; + PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup0'; + PacketFramework.PaPIConfig.ToSub.plot_X.signals = 'Const'; // not okay so far, need an array of str // finalise the communication interface [sim,PacketFramework] = ld_PF_Finalise(sim, PacketFramework); diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce index 4aac7ffe..0e611392 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce @@ -3,210 +3,210 @@ // Interfacing functions are placed in this place function [sim, out] = ld_udp_main_receiver(sim, events, udpport, identstr, socket_fname, vecsize) // PARSEDOCU_BLOCK -// udp main receiver - block -// -// This is a simulation-synchronising Block -// -// EXPERIMENTAL FIXME: REMOVE -// - - datatype = ORTD.DATATYPE_FLOAT; - - btype = 39001; - [sim,blk] = libdyn_new_block(sim, events, btype, ipar=[ udpport, vecsize, datatype, length(socket_fname), ascii(socket_fname), length(identstr), ascii(identstr) ], rpar=[ ], ... - insizes=[], outsizes=[vecsize], ... - intypes=[], outtypes=[ORTD.DATATYPE_FLOAT] ); - - //[sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); - [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port + // udp main receiver - block + // + // This is a simulation-synchronising Block + // + // EXPERIMENTAL FIXME: REMOVE + // + + datatype = ORTD.DATATYPE_FLOAT; + + btype = 39001; + [sim,blk] = libdyn_new_block(sim, events, btype, ipar=[ udpport, vecsize, datatype, length(socket_fname), ascii(socket_fname), length(identstr), ascii(identstr) ], rpar=[ ], ... + insizes=[], outsizes=[vecsize], ... + intypes=[], outtypes=[ORTD.DATATYPE_FLOAT] ); + + //[sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); + [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port endfunction function [sim] = ld_UDPSocket_shObj(sim, events, ObjectIdentifyer, Visibility, hostname, UDPPort) // PARSEDOCU_BLOCK -// -// Set-up an UDP-Socket -// -// hostname - Network interface to bind socket to??? -// UDPPort - UDP port to bind. If -1 then no UDP server is set-up -// -// EXPERIMENTAL -// + // + // Set-up an UDP-Socket + // + // hostname - Network interface to bind socket to??? + // UDPPort - UDP port to bind. If -1 then no UDP server is set-up + // + // EXPERIMENTAL + // - // add a postfix that identifies the type of the shared object - ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; - // pack all parameters into a structure "parlist" - parlist = new_irparam_set(); + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); - parlist = new_irparam_elemet_ivec(parlist, UDPPort, 10); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 11); // id = 11; A string parameter + parlist = new_irparam_elemet_ivec(parlist, UDPPort, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 11); // id = 11; A string parameter - p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively -// Set-up the block parameters. There are no I/O ports - Uipar = [ p.ipar ]; - Urpar = [ p.rpar ]; - btype = 39001 + 0; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + // Set-up the block parameters. There are no I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 0; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - [sim] = libdyn_CreateSharedObjBlk(sim, btype, ObjectIdentifyer, Visibility, Uipar, Urpar); + [sim] = libdyn_CreateSharedObjBlk(sim, btype, ObjectIdentifyer, Visibility, Uipar, Urpar); endfunction function [sim] = ld_UDPSocket_Send(sim, events, ObjectIdentifyer, in, insize, intype) // PARSEDOCU_BLOCK -// -// UDP - Send block -// -// in *, ORTD.DATATYPE_BINARY - input -// -// EXPERIMENTAL, About to be removed -// + // + // UDP - Send block + // + // in *, ORTD.DATATYPE_BINARY - input + // + // EXPERIMENTAL, About to be removed + // - // add a postfix that identifies the type of the shared object - ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; - // pack all parameters into a structure "parlist" - parlist = new_irparam_set(); + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); - parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 - p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively -// Set-up the block parameters and I/O ports - Uipar = [ p.ipar ]; - Urpar = [ p.rpar ]; - btype = 39001 + 1; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + // Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 1; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - insizes=[insize]; // Input port sizes - outsizes=[]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) - intypes=[intype]; // datatype for each input port - outtypes=[]; // datatype for each output port + insizes=[insize]; // Input port sizes + outsizes=[]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[intype]; // datatype for each input port + outtypes=[]; // datatype for each output port - blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); - - // connect the inputs - [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); -// // connect the ouputs -// [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port + // connect the inputs + [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 + + // // connect the ouputs + // [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port endfunction function [sim, out, SrcAddr] = ld_UDPSocket_Recv(sim, events, ObjectIdentifyer, outsize) // PARSEDOCU_BLOCK -// -// UDP - receiver block -// -// out *, ORTD.DATATYPE_BINARY - output -// SrcAddr - information about where the package comes from (not implemented) -// -// This is a simulation-synchronising Block. Everytime an UDP-Packet is received, -// the simulation that contains this blocks goes on for one step. -// -// EXPERIMENTAL -// + // + // UDP - receiver block + // + // out *, ORTD.DATATYPE_BINARY - output + // SrcAddr - information about where the package comes from (not implemented) + // + // This is a simulation-synchronising Block. Everytime an UDP-Packet is received, + // the simulation that contains this blocks goes on for one step. + // + // EXPERIMENTAL + // + + printf("Synchronising simulation to UDP-Receiver\n"); - printf("Synchronising simulation to UDP-Receiver\n"); + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; - // add a postfix that identifies the type of the shared object - ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + // + outtype = ORTD.DATATYPE_BINARY; - // - outtype = ORTD.DATATYPE_BINARY; + // IPv4 + AddrSize = 4+2; // IPnumber + port - // IPv4 - AddrSize = 4+2; // IPnumber + port + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); - // pack all parameters into a structure "parlist" - parlist = new_irparam_set(); + parlist = new_irparam_elemet_ivec(parlist, outsize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, outtype, 11); // id = 11 - parlist = new_irparam_elemet_ivec(parlist, outsize, 10); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, outtype, 11); // id = 11 + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + // Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 2; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function -// Set-up the block parameters and I/O ports - Uipar = [ p.ipar ]; - Urpar = [ p.rpar ]; - btype = 39001 + 2; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + insizes=[]; // Input port sizes + outsizes=[outsize, AddrSize]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[]; // datatype for each input port + outtypes=[outtype, ORTD.DATATYPE_BINARY]; // datatype for each output port - insizes=[]; // Input port sizes - outsizes=[outsize, AddrSize]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) - intypes=[]; // datatype for each input port - outtypes=[outtype, ORTD.DATATYPE_BINARY]; // datatype for each output port + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) - blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); - -// // connect the inputs -// [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 + // // connect the inputs + // [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 - // connect the ouputs - [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port - [sim,SrcAddr] = libdyn_new_oport_hint(sim, blk, 1); // 1th port + // connect the ouputs + [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port + [sim,SrcAddr] = libdyn_new_oport_hint(sim, blk, 1); // 1th port endfunction function [sim] = ld_UDPSocket_SendTo(sim, events, SendSize, ObjectIdentifyer, hostname, UDPPort, in, insize) // PARSEDOCU_BLOCK -// -// UDP - Send block -// -// in *, ORTD.DATATYPE_BINARY - input -// SendSize *. ORTD.DATATYPE_INT32 - Number of bytes to send -// -// EXPERIMENTAL -// + // + // UDP - Send block + // + // in *, ORTD.DATATYPE_BINARY - input + // SendSize *. ORTD.DATATYPE_INT32 - Number of bytes to send + // + // EXPERIMENTAL + // - // add a postfix that identifies the type of the shared object - ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; - // only send binary data - intype=ORTD.DATATYPE_BINARY; + // only send binary data + intype=ORTD.DATATYPE_BINARY; - // pack all parameters into a structure "parlist" - parlist = new_irparam_set(); + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); - parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 - - parlist = new_irparam_elemet_ivec(parlist, UDPPort, 12); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 13); // id = 11; A string parameter - + parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 - p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + parlist = new_irparam_elemet_ivec(parlist, UDPPort, 12); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 13); // id = 11; A string parameter -// Set-up the block parameters and I/O ports - Uipar = [ p.ipar ]; - Urpar = [ p.rpar ]; - btype = 39001 + 3; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - insizes=[insize, 1]; // Input port sizes - outsizes=[]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) - intypes=[intype, ORTD.DATATYPE_INT32 ]; // datatype for each input port - outtypes=[]; // datatype for each output port + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + // Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 3; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); - - // connect the inputs - [sim,blk] = libdyn_conn_equation(sim, blk, list(in, SendSize) ); // connect in1 to port 0 and in2 to port 1 + insizes=[insize, 1]; // Input port sizes + outsizes=[]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[intype, ORTD.DATATYPE_INT32 ]; // datatype for each input port + outtypes=[]; // datatype for each output port -// // connect the ouputs -// [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); + + // connect the inputs + [sim,blk] = libdyn_conn_equation(sim, blk, list(in, SendSize) ); // connect in1 to port 0 and in2 to port 1 + + // // connect the ouputs + // [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port endfunction @@ -215,63 +215,60 @@ endfunction function [sim] = ld_UDPSocket_Reply(sim, events, SendSize, ObjectIdentifyer, DestAddr, in, insize) // PARSEDOCU_BLOCK -// -// UDP - Send block -// -// in *, ORTD.DATATYPE_BINARY - input -// SendSize *. ORTD.DATATYPE_INT32 - Number of bytes to send -// DestAddr - dynamic representation for the destination address -// -// EXPERIMENTAL not implemented by now -// + // + // UDP - Send block + // + // in *, ORTD.DATATYPE_BINARY - input + // SendSize *. ORTD.DATATYPE_INT32 - Number of bytes to send + // DestAddr - dynamic representation for the destination address + // + // EXPERIMENTAL not implemented by now + // - // add a postfix that identifies the type of the shared object - ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; - // only send binary data - intype=ORTD.DATATYPE_BINARY; + // only send binary data + intype=ORTD.DATATYPE_BINARY; - // IPv4 - AddrSize=4+2; + // IPv4 + AddrSize=4+2; - // pack all parameters into a structure "parlist" - parlist = new_irparam_set(); + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); - parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 - -// parlist = new_irparam_elemet_ivec(parlist, UDPPort, 12); // id = 10 -// parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 13); // id = 11; A string parameter - + parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 - p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + // parlist = new_irparam_elemet_ivec(parlist, UDPPort, 12); // id = 10 + // parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 13); // id = 11; A string parameter -// Set-up the block parameters and I/O ports - Uipar = [ p.ipar ]; - Urpar = [ p.rpar ]; - btype = 39001 + 4; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - insizes=[insize, 1, AddrSize]; // Input port sizes - outsizes=[]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) - intypes=[intype, ORTD.DATATYPE_INT32, ORTD.DATATYPE_BINARY ]; // datatype for each input port - outtypes=[]; // datatype for each output port + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + // Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 4; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); - - // connect the inputs - [sim,blk] = libdyn_conn_equation(sim, blk, list(in, SendSize, DestAddr) ); // connect in1 to port 0 and in2 to port 1 + insizes=[insize, 1, AddrSize]; // Input port sizes + outsizes=[]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[intype, ORTD.DATATYPE_INT32, ORTD.DATATYPE_BINARY ]; // datatype for each input port + outtypes=[]; // datatype for each output port -// // connect the ouputs -// [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port -endfunction + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); + // connect the inputs + [sim,blk] = libdyn_conn_equation(sim, blk, list(in, SendSize, DestAddr) ); // connect in1 to port 0 and in2 to port 1 + // // connect the ouputs + // [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction @@ -281,118 +278,121 @@ endfunction -function [sim, out, NBytes] = ld_ConcateData(sim, events, inlist, insizes, intypes) // PARSEDOCU_BLOCK -// -// Concate Data - block -// -// concatenates the binary representation of all inputs -// -// The output is of type ORTD.DATATYPE_BINARY -// -// EXPERIMENTAL -// - // pack all parameters into a structure "parlist" -// parlist = new_irparam_set(); -// -// parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 -// parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 -// -// p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively -// Set-up the block parameters and I/O ports - Uipar = [ ]; - Urpar = [ ]; - btype = 39001 + 10; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function +function [sim, out, NBytes] = ld_ConcateData(sim, events, inlist, insizes, intypes) // PARSEDOCU_BLOCK + // + // Concate Data - block + // + // concatenates the binary representation of all inputs + // + // The output is of type ORTD.DATATYPE_BINARY + // + // EXPERIMENTAL + // + + + // pack all parameters into a structure "parlist" + // parlist = new_irparam_set(); + // + // parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + // parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + // + // p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + + // Set-up the block parameters and I/O ports + Uipar = [ ]; + Urpar = [ ]; + btype = 39001 + 10; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + // count the number of bytes + NBytes = 0; + for i = 1:length(inlist) + NBytes = NBytes + insizes(i) * libdyn_datatype_len( intypes(i) ); + end - // count the number of bytes - NBytes = 0; - for i = 1:length(inlist) - NBytes = NBytes + insizes(i) * libdyn_datatype_len( intypes(i) ); - end + // insizes=[insizes]; // Input port sizes + outsizes=[ NBytes ]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + // intypes=[intypes]; // datatype for each input port + outtypes=[ ORTD.DATATYPE_BINARY ]; // datatype for each output port -// insizes=[insizes]; // Input port sizes - outsizes=[ NBytes ]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) -// intypes=[intypes]; // datatype for each input port - outtypes=[ ORTD.DATATYPE_BINARY ]; // datatype for each output port + // disp(outsizes); + // disp(outtypes); -// disp(outsizes); -// disp(outtypes); + blocktype = 2; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) - blocktype = 2; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed); - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed); - - // connect the inputs -// for i = 1:length(inlist) + // connect the inputs + // for i = 1:length(inlist) [sim,blk] = libdyn_conn_equation(sim, blk, inlist ); // connect in1 to port 0 and in2 to port 1 -// end + // end -// // connect the ouputs - [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port + // // connect the ouputs + [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port endfunction function [sim, outlist] = ld_DisassembleData(sim, events, in, outsizes, outtypes) // PARSEDOCU_BLOCK -// -// disasseble Data - block -// -// disassemble the binary representation of the input, which is of type ORTD.DATATYPE_BINARY -// -// EXPERIMENTAL -// - - - // pack all parameters into a structure "parlist" -// parlist = new_irparam_set(); -// -// parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 -// parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 -// -// p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - -// Set-up the block parameters and I/O ports - Uipar = [ ]; - Urpar = [ ]; - btype = 39001 + 11; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + // + // disasseble Data - block + // + // disassemble the binary representation of the input, which is of type ORTD.DATATYPE_BINARY + // + // EXPERIMENTAL + // + + + // pack all parameters into a structure "parlist" + // parlist = new_irparam_set(); + // + // parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + // parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + // + // p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + + // Set-up the block parameters and I/O ports + Uipar = [ ]; + Urpar = [ ]; + btype = 39001 + 11; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + // count the number of bytes + NBytes = 0; + for i = 1:length(outsizes) + NBytes = NBytes + outsizes(i)*libdyn_datatype_len( outtypes(i) ); + end - // count the number of bytes - NBytes = 0; - for i = 1:length(outsizes) - NBytes = NBytes + outsizes(i)*libdyn_datatype_len( outtypes(i) ); - end + // insizes=[insizes]; // Input port sizes + insizes=[ NBytes ]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + // intypes=[intypes]; // datatype for each input port + intypes=[ ORTD.DATATYPE_BINARY ]; // datatype for each output port -// insizes=[insizes]; // Input port sizes - insizes=[ NBytes ]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) -// intypes=[intypes]; // datatype for each input port - intypes=[ ORTD.DATATYPE_BINARY ]; // datatype for each output port + // disp(outsizes); + // disp(outtypes); -// disp(outsizes); -// disp(outtypes); + blocktype = 2; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) - blocktype = 2; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed); - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed); - - // connect the inputs -// for i = 1:length(inlist) + // connect the inputs + // for i = 1:length(inlist) [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 -// end + // end -// // connect the ouputs - outlist = list(); - for i = 1:length(outtypes) - [sim,outlist($+1)] = libdyn_new_oport_hint(sim, blk, i-1); // 0th port - end + // // connect the ouputs + outlist = list(); + for i = 1:length(outtypes) + [sim,outlist($+1)] = libdyn_new_oport_hint(sim, blk, i-1); // 0th port + end endfunction @@ -424,58 +424,58 @@ endfunction function [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName) - SourceID = PacketFramework.SourceID_counter; - - Source.SourceName = SourceName; - Source.SourceID = SourceID; - Source.NValues_send = NValues_send; - Source.datatype = datatype; - - // Add new source to the list - PacketFramework.Sources($+1) = Source; - - // inc counter - PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; + SourceID = PacketFramework.SourceID_counter; + + Source.SourceName = SourceName; + Source.SourceID = SourceID; + Source.NValues_send = NValues_send; + Source.datatype = datatype; + + // Add new source to the list + PacketFramework.Sources($+1) = Source; + + // inc counter + PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; endfunction function [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName) - ParameterID = PacketFramework.Parameterid_counter; - - Parameter.ParameterName = ParameterName; - Parameter.ParameterID = ParameterID; - Parameter.NValues = NValues; - Parameter.datatype = datatype; - Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; - - // Add new source to the list - PacketFramework.Parameters($+1) = Parameter; - - // inc counters - PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; - PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; - - // return values - ParameterID = Parameter.ParameterID; - MemoryOfs = Parameter.MemoryOfs; + ParameterID = PacketFramework.Parameterid_counter; + + Parameter.ParameterName = ParameterName; + Parameter.ParameterID = ParameterID; + Parameter.NValues = NValues; + Parameter.datatype = datatype; + Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; + + // Add new source to the list + PacketFramework.Parameters($+1) = Parameter; + + // inc counters + PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; + PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; + + // return values + ParameterID = Parameter.ParameterID; + MemoryOfs = Parameter.MemoryOfs; endfunction function [sim, PacketFramework, Parameter] = ld_PF_Parameter(sim, PacketFramework, NValues, datatype, ParameterName) // PARSEDOCU_BLOCK -// -// Define a parameter -// -// NValues - amount of data sets -// datatype - only ORTD.DATATYPE_FLOAT for now -// ParameterName - a unique string decribing the parameter -// -// -// + // + // Define a parameter + // + // NValues - amount of data sets + // datatype - only ORTD.DATATYPE_FLOAT for now + // ParameterName - a unique string decribing the parameter + // + // + // [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); - + // read data from global memory [sim, readI] = ld_const(sim, 0, MemoryOfs); // start at index 1 [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... - datatype, NValues); + datatype, NValues); endfunction @@ -483,34 +483,34 @@ endfunction // Send a signal via UDP, a simple protocoll is defined, internal function function [sim] = ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID) - InstanceName = PacketFramework.InstanceName; - [sim,one] = ld_const(sim, 0, 1); + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - // Source ID - [sim, SourceID] = ld_const(sim, 0, SourceID); - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + // Source ID + [sim, SourceID] = ld_const(sim, 0, SourceID); + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); - printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); endfunction @@ -518,192 +518,192 @@ endfunction function [sim, PacketFramework] = ld_SendPacket(sim, PacketFramework, Signal, NValues_send, datatype, SourceName) // PARSEDOCU_BLOCK // PARSEDOCU_BLOCK -// -// Stream data - block -// -// Signal - the signal to stream -// NValues_send - the vector length of Signal -// datatype - only ORTD.DATATYPE_FLOAT by now -// SourceName - a unique string identifier descring the stream -// -// -// - - [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); - [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); + // + // Stream data - block + // + // Signal - the signal to stream + // NValues_send - the vector length of Signal + // datatype - only ORTD.DATATYPE_FLOAT by now + // SourceName - a unique string identifier descring the stream + // + // + // + + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); endfunction function [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName, Configuration) // PARSEDOCU_BLOCK -// -// Initialise an instance of the Packet Framework -// -// InstanceName - a unique string identifier for the instance -// Configuration must include the following properties: -// -// Configuration.UnderlyingProtocoll = "UDP" -// Configuration.DestHost -// Configuration.DestPort -// Configuration.LocalSocketHost -// Configuration.LocalSocketPort -// -// -// Example: -// -// -// Configuration.UnderlyingProtocoll = "UDP"; -// Configuration.DestHost = "127.0.0.1"; -// Configuration.DestPort = 20000; -// Configuration.LocalSocketHost = "127.0.0.1"; -// Configuration.LocalSocketPort = 20001; -// [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); -// -// -// -// Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations -// -// + // + // Initialise an instance of the Packet Framework + // + // InstanceName - a unique string identifier for the instance + // Configuration must include the following properties: + // + // Configuration.UnderlyingProtocoll = "UDP" + // Configuration.DestHost + // Configuration.DestPort + // Configuration.LocalSocketHost + // Configuration.LocalSocketPort + // + // + // Example: + // + // + // Configuration.UnderlyingProtocoll = "UDP"; + // Configuration.DestHost = "127.0.0.1"; + // Configuration.DestPort = 20000; + // Configuration.LocalSocketHost = "127.0.0.1"; + // Configuration.LocalSocketPort = 20001; + // [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); + // + // + // + // Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations + // + // + + // initialise structure for sources + PacketFramework.InstanceName = InstanceName; + PacketFramework.Configuration = Configuration; + + PacketFramework.Configuration.debugmode = %F; + + // disp(Configuration.UnderlyingProtocoll) + + if Configuration.UnderlyingProtocoll == 'UDP' + null; + else + error("PacketFramework: Only UDP supported up to now"); + end + + // possible packet sizes for UDP + PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; + + // sources + PacketFramework.SourceID_counter = 0; + PacketFramework.Sources = list(); + + // parameters + PacketFramework.Parameterid_counter = 0; + PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory + PacketFramework.Parameters = list(); + + PacketFramework.SenderID = 1295793; - // initialise structure for sources - PacketFramework.InstanceName = InstanceName; - PacketFramework.Configuration = Configuration; - - PacketFramework.Configuration.debugmode = %F; - -// disp(Configuration.UnderlyingProtocoll) - - if Configuration.UnderlyingProtocoll == 'UDP' - null; - else - error("PacketFramework: Only UDP supported up to now"); - end - - // possible packet sizes for UDP - PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes - PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; - - // sources - PacketFramework.SourceID_counter = 0; - PacketFramework.Sources = list(); - - // parameters - PacketFramework.Parameterid_counter = 0; - PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory - PacketFramework.Parameters = list(); - - PacketFramework.SenderID = 1295793; - - // Open an UDP-Port in server mode - [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... - hostname=PacketFramework.Configuration.LocalSocketHost, ... - UDPPort=PacketFramework.Configuration.LocalSocketPort); + // Open an UDP-Port in server mode + [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... + hostname=PacketFramework.Configuration.LocalSocketHost, ... + UDPPort=PacketFramework.Configuration.LocalSocketPort); endfunction // Send a signal via UDP, a simple protocoll is defined, internal function function [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID) - InstanceName = PacketFramework.InstanceName; - [sim,one] = ld_const(sim, 0, 1); + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - // Source ID - [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + // Source ID + [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - // Group ID - [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources - [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); + // Group ID + [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources + [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); -// printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); - // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); + // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); endfunction // Send the newConfigAvailable Signal via UDP, a simple protocoll is defined, internal function function [sim] = ld_PF_SendNewConfigAvailableUDP(sim, PacketFramework) - InstanceName = PacketFramework.InstanceName; - [sim,one] = ld_const(sim, 0, 1); + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - // Source ID - [sim, SourceID] = ld_const(sim, 0, -2); // -2 means a new config is available - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + // Source ID + [sim, SourceID] = ld_const(sim, 0, -2); // -2 means a new config is available + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - // Group ID - [sim, GroupID_] = ld_const(sim, 0, 0); // 0 (not used) - [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); + // Group ID + [sim, GroupID_] = ld_const(sim, 0, 0); // 0 (not used) + [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); -// printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); - // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); + // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); endfunction // Send the configuration via UDP, a simple protocoll is defined, internal function function [sim] = ld_PF_SendConfigUDP(sim, PacketFramework) - InstanceName = PacketFramework.InstanceName; - - str_PF_Export = ld_PF_Export_str(PacketFramework); - strLength = length(str_PF_Export); - maxPacketLength = 1400; - maxPacketStrLength = maxPacketLength - 4*4; - nPackets = ceil(strLength/maxPacketStrLength); - - // Source ID - [sim, SourceID] = ld_const(sim, 0, -4); // -4 means configItem - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - - // nPackets - [sim, nPackets_] = ld_const(sim, 0, nPackets); // the number of config packets for the whole configuration - [sim, nPackets_int32] = ld_ceilInt32(sim, 0, nPackets_); - - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - - for i=1:nPackets + InstanceName = PacketFramework.InstanceName; + + str_PF_Export = ld_PF_Export_str(PacketFramework); + strLength = length(str_PF_Export); + maxPacketLength = 1400; + maxPacketStrLength = maxPacketLength - 4*4; + nPackets = ceil(strLength/maxPacketStrLength); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -4); // -4 means configItem + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // nPackets + [sim, nPackets_] = ld_const(sim, 0, nPackets); // the number of config packets for the whole configuration + [sim, nPackets_int32] = ld_ceilInt32(sim, 0, nPackets_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + for i=1:nPackets // Packet counter, so the order of the network packages can be determined [sim, Counter] = ld_const(sim, ev, i); [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); @@ -715,12 +715,12 @@ function [sim] = ld_PF_SendConfigUDP(sim, PacketFramework) strPart_PF_Export = part(str_PF_Export, partBegin:partEnd); sendSize = length(strPart_PF_Export); [sim, partPF_Export_bin] = ld_const_bin(sim, ev, in=ascii(strPart_PF_Export)); - + // make a binary structure [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, nPackets_int32, partPF_Export_bin), insizes=[1,1,1,1,sendSize], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_BINARY ] ); - + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, nPackets_int32, partPF_Export_bin), insizes=[1,1,1,1,sendSize], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_BINARY ] ); + // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); [sim] = ld_printf(sim, ev, SenderID, "SenderID", 1); [sim] = ld_printf(sim, ev, SourceID, "Sent config packet number " + string(i) + " ", 1); @@ -729,11 +729,11 @@ function [sim] = ld_PF_SendConfigUDP(sim, PacketFramework) // send to the network [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); - end - + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + end + endfunction // select case function for the different received PaPi Commands (SenderID) @@ -787,7 +787,7 @@ function [sim, outlist, userdata] = SelectCasePaPiCmd(sim, inlist, Ncase, casena // Store the input data into a shared memory [sim] = ld_WriteMemory2(sim, 0, data=inlist(4), index=memofs, ElementsToWrite=Nelements, ... - ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); + ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); case 2 [sim] = ld_printf(sim, 0, DisAsm_(3), "Give Config (SourceID) = ", 1); @@ -834,7 +834,7 @@ function [sim, outlist, userdata] = SelectCaseSendNewConfigAvailable(sim, inlist case 2 // Send //[sim] = ld_printf(sim, events, dummy, "case "+string(casename)+" : Config is sent to PaPi ", 1); [sim] = ld_PF_SendNewConfigAvailableUDP(sim, PacketFramework); - + end // the user defined output signals of this nested simulation @@ -844,204 +844,284 @@ endfunction function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOCU_BLOCK -// -// Finalise the instance. -// -// - - // The main real-time thread - function [sim,PacketFramework] = ld_PF_InitUDP(sim, PacketFramework) - - function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) - // This will run in a thread. Each time a UDP-packet is received - // one simulation step is performed. Herein, the packet is parsed - // and the contained parameters are stored into a memory. - - PacketFramework = userdata(1); - - TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes - InstanceName = PacketFramework.InstanceName; - PacketSize = PacketFramework.PacketSize; - // Sync the simulation to incomming UDP-packets - [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); - - // disassemble packet's structure - [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... - outsizes=[1,1,1,TotalElemetsPerPacket], ... - outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); - - [sim, sourceID] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); - // [sim] = ld_printf(sim, 0, sourceID, "DisAsm(3) (SourceID) = ", 1); - [sim, selectSignal_checkGtZero] = ld_cond_overwrite(sim, ev, sourceID, sourceID, 1); - [sim, selectSignal_notCheckGtZero] = ld_not(sim, ev, sourceID); - [sim, selectSignal_checkMinThreeInt32] = ld_CompareEqInt32(sim, ev, DisAsm(3), -3); - [sim, selectSignal_checkMinThree] = ld_Int32ToFloat(sim, ev, selectSignal_checkMinThreeInt32); - [sim, selectSignal_checked] = ld_cond_overwrite(sim, ev, selectSignal_checkGtZero, selectSignal_checkMinThree, 2); - [sim, selectSignal_notCheckMinThree] = ld_not(sim, ev, selectSignal_checkMinThree); - [sim, selectSignal_undefined] = ld_and(sim, ev, list(selectSignal_notCheckGtZero, selectSignal_notCheckMinThree)); - [sim, selectSignal_checkedSecure] = ld_cond_overwrite(sim, ev, selectSignal_checked, selectSignal_undefined, 3); - [sim, selectSignal_checkedSecureInt32] = ld_roundInt32(sim, ev, selectSignal_checkedSecure); - - // set-up the states for each PaPi Command represented by nested simulations - [sim, outlist, userdataSelectCasePaPiCmd] = ld_CaseSwitchNest(sim, ev, ... + // + // Finalise the instance. + // + // + + // The main real-time thread + function [sim,PacketFramework] = ld_PF_InitUDP(sim, PacketFramework) + + function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) + // This will run in a thread. Each time a UDP-packet is received + // one simulation step is performed. Herein, the packet is parsed + // and the contained parameters are stored into a memory. + + PacketFramework = userdata(1); + + TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + InstanceName = PacketFramework.InstanceName; + PacketSize = PacketFramework.PacketSize; + // Sync the simulation to incomming UDP-packets + [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); + + // disassemble packet's structure + [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... + outsizes=[1,1,1,TotalElemetsPerPacket], ... + outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); + + [sim, sourceID] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); + // [sim] = ld_printf(sim, 0, sourceID, "DisAsm(3) (SourceID) = ", 1); + [sim, selectSignal_checkGtZero] = ld_cond_overwrite(sim, ev, sourceID, sourceID, 1); + [sim, selectSignal_notCheckGtZero] = ld_not(sim, ev, sourceID); + [sim, selectSignal_checkMinThreeInt32] = ld_CompareEqInt32(sim, ev, DisAsm(3), -3); + [sim, selectSignal_checkMinThree] = ld_Int32ToFloat(sim, ev, selectSignal_checkMinThreeInt32); + [sim, selectSignal_checked] = ld_cond_overwrite(sim, ev, selectSignal_checkGtZero, selectSignal_checkMinThree, 2); + [sim, selectSignal_notCheckMinThree] = ld_not(sim, ev, selectSignal_checkMinThree); + [sim, selectSignal_undefined] = ld_and(sim, ev, list(selectSignal_notCheckGtZero, selectSignal_notCheckMinThree)); + [sim, selectSignal_checkedSecure] = ld_cond_overwrite(sim, ev, selectSignal_checked, selectSignal_undefined, 3); + [sim, selectSignal_checkedSecureInt32] = ld_roundInt32(sim, ev, selectSignal_checkedSecure); + + // set-up the states for each PaPi Command represented by nested simulations + [sim, outlist, userdataSelectCasePaPiCmd] = ld_CaseSwitchNest(sim, ev, ... inlist=DisAsm, .. insizes=[1,1,1,TotalElemetsPerPacket], outsizes=[1], ... intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ], outtypes=[ORTD.DATATYPE_FLOAT], ... CaseSwitch_fn=SelectCasePaPiCmd, SimnestName="SwitchSelectPaPiCmd", DirectFeedthrough=%t, SelectSignal=selectSignal_checkedSecureInt32, list("Param", "GiveConfig", "Undefined"), list(PacketFramework) ); - - PacketFramework = userdataSelectCasePaPiCmd(1); - - // output of schematic - outlist = list(); - userdata(1) = PacketFramework; - endfunction - - - - // start the node.js service from the subfolder webinterface - //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); - + + PacketFramework = userdataSelectCasePaPiCmd(1); + + // output of schematic + outlist = list(); + userdata(1) = PacketFramework; + endfunction + + + + // start the node.js service from the subfolder webinterface + //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); + TotalMemorySize = sum(PacketFramework.ParameterMemory.Sizes); InstanceName = PacketFramework.InstanceName; -// // Open an UDP-Port in server mode -// [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... -// hostname=PacketFramework.Configuration.LocalSocketHost, ... -// UDPPort=PacketFramework.Configuration.LocalSocketPort); - - // initialise a global memory for storing the input data for the computation - [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... - datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... - initial_data=[zeros(TotalMemorySize,1)], ... - visibility='global', useMutex=1); - - // Create thread for the receiver - ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; - [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step - [sim, outlist, computation_finished, userdata] = ld_async_simulation(sim, 0, ... - inlist=list(), ... - insizes=[], outsizes=[], ... - intypes=[], outtypes=[], ... - nested_fn = UDPReceiverThread, ... - TriggerSignal=startcalc, name=InstanceName+"Thread1", ... - ThreadPrioStruct, userdata=list(PacketFramework) ); + // // Open an UDP-Port in server mode + // [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... + // hostname=PacketFramework.Configuration.LocalSocketHost, ... + // UDPPort=PacketFramework.Configuration.LocalSocketPort); + + // initialise a global memory for storing the input data for the computation + [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... + datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... + initial_data=[zeros(TotalMemorySize,1)], ... + visibility='global', useMutex=1); + + // Create thread for the receiver + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; + [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step + [sim, outlist, computation_finished, userdata] = ld_async_simulation(sim, 0, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = UDPReceiverThread, ... + TriggerSignal=startcalc, name=InstanceName+"Thread1", ... + ThreadPrioStruct, userdata=list(PacketFramework) ); - PacketFramework = userdata(1); + PacketFramework = userdata(1); endfunction - // calc memory - MemoryOfs = []; - Sizes = []; - // go through all parameters and create memories for all - for i=1:length(PacketFramework.Parameters) - P = PacketFramework.Parameters(i); - - Sizes = [Sizes; P.NValues]; - MemoryOfs = [MemoryOfs; P.MemoryOfs]; - end - - PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; - PacketFramework.ParameterMemory.Sizes = Sizes; - - // udp - [sim] = ld_PF_InitUDP(sim, PacketFramework); - - // Send to group update notifications for each group (currently only one possible) - [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); - - - [sim, initSelect] = ld_initimpuls(sim, ev); - [sim, selectNewConfig] = ld_add_ofs(sim, ev, initSelect, 1); - //[sim] = ld_printf(sim, ev, selectNewConfig, ORTD.termcode.red + "Select New Config Signal" + ORTD.termcode.reset, 1); - [sim, selectNewConfigInt32] = ld_roundInt32(sim, ev, selectNewConfig); - // set-up the states to send a Signal to PaPi that a new config is available only in the first time step represented by nested simulations - [sim, outlist, userdata] = ld_CaseSwitchNest(sim, ev, ... - inlist=list(), .. - insizes=[], outsizes=[1], ... - intypes=[], outtypes=[ORTD.DATATYPE_FLOAT], ... - CaseSwitch_fn=SelectCaseSendNewConfigAvailable, SimnestName="SelectCaseSendNewConfigAvailable", DirectFeedthrough=%t, SelectSignal=selectNewConfigInt32, list("Finished", "Send"), list(PacketFramework) ); - - PacketFramework = userdata(1); - + // calc memory + MemoryOfs = []; + Sizes = []; + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + + Sizes = [Sizes; P.NValues]; + MemoryOfs = [MemoryOfs; P.MemoryOfs]; + end + + PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; + PacketFramework.ParameterMemory.Sizes = Sizes; + + // udp + [sim] = ld_PF_InitUDP(sim, PacketFramework); + + // Send to group update notifications for each group (currently only one possible) + [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); + + + [sim, initSelect] = ld_initimpuls(sim, ev); + [sim, selectNewConfig] = ld_add_ofs(sim, ev, initSelect, 1); + //[sim] = ld_printf(sim, ev, selectNewConfig, ORTD.termcode.red + "Select New Config Signal" + ORTD.termcode.reset, 1); + [sim, selectNewConfigInt32] = ld_roundInt32(sim, ev, selectNewConfig); + // set-up the states to send a Signal to PaPi that a new config is available only in the first time step represented by nested simulations + [sim, outlist, userdata] = ld_CaseSwitchNest(sim, ev, ... + inlist=list(), .. + insizes=[], outsizes=[1], ... + intypes=[], outtypes=[ORTD.DATATYPE_FLOAT], ... + CaseSwitch_fn=SelectCaseSendNewConfigAvailable, SimnestName="SelectCaseSendNewConfigAvailable", DirectFeedthrough=%t, SelectSignal=selectNewConfigInt32, list("Finished", "Send"), list(PacketFramework) ); + + PacketFramework = userdata(1); + endfunction function str=ld_PF_Export_str(PacketFramework) - str=' {""SourcesConfig"" : {'+char(10); - - for i=1:length(PacketFramework.Sources) - - - SourceID = PacketFramework.Sources(i).SourceID; - SourceName = PacketFramework.Sources(i).SourceName; - disp(SourceID ); - disp( SourceName ); - - - line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"" } ", ... - string(PacketFramework.Sources(i).SourceID), ... - string(PacketFramework.Sources(i).SourceName), ... - string(PacketFramework.Sources(i).NValues_send), ... - string(PacketFramework.Sources(i).datatype) ); - - - if i==length(PacketFramework.Sources) - // finalise the last entry without "," - printf('%s' , line); - str=str+line + char(10); - else - printf('%s,' , line); - str=str+line+',' + char(10); - end - - - end - - - - str=str+'} , ' + char(10) + ' ""ParametersConfig"" : {' + char(10); - - // go through all parameters and create memories for all - for i=1:length(PacketFramework.Parameters) - - printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); - - line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } ", ... - string(PacketFramework.Parameters(i).ParameterID), ... - string(PacketFramework.Parameters(i).ParameterName), ... - string(PacketFramework.Parameters(i).NValues), ... - string(PacketFramework.Parameters(i).datatype) ); - - - if i==length(PacketFramework.Parameters) - // finalise the last entry without "," - printf('%s' , line); - str=str+line + char(10); - else - printf('%s,' , line); - str=str+line+',' + char(10); - end - - - end - str=str+'}'+char(10)+'}'; +// Added possibility to add GUI-configurations on 5.3.15 + + function jsonstr = struct2json(a) + // + // Rev 1 as of 4.3.15: Initial version + // + // + // Example usage: + // + // clear a; + // a.Field1 = 2; + // a.Field2 = "jkh"; + // a.Field3 = 1.2; + // a.F3.name = "Joe"; + // a.F3.age = 32; + // //a.F3.data = [1,2]; + // jsonstr = struct2json(a); + // disp(jsonstr); + // + // Warning: For strings, make sure you escape the special characters that are used by the JSON-format! + // + + function valstr=val2str(val) + select typeof(val) + + case "string" + valstr = '''' + string(val) + ''''; + + case "constant" + if length(val) == 1 then + valstr = string(val); + else + valstr = '''' + "** Matrix not supported **" + ''''; + end + + case "st" + valstr = struct2json(val); + + else + valstr = '''' + "**Datatype " + typeof(val) + " not supported **" + ''''; + end + endfunction + + if isstruct(a) then + F = fieldnames(a); + str = "{ "; + N = length(length(F)); + + for i = 1:(N-1) + f = F(i); + val = eval('a.'+f); + valstr = val2str(val); + str = str + '''' + f + '''' + ' : ' + valstr + ' , '; + end + + f = F(N); + val = eval('a.'+f); + valstr = val2str(val); + str = str + '''' + f + '''' + ' : ' + valstr + ' } '; + + else + error("Not a structure") + end + + jsonstr = str; + endfunction + + // check if there is a GUI to be set-up in Papi + if isfield(PacketFramework, 'PaPIConfig') then + PaPIConfigstr = struct2json(PacketFramework.PaPIConfig) + else + PaPIConfigstr = '{}'; + end + + + + str=' {""SourcesConfig"" : {'+char(10); + + for i=1:length(PacketFramework.Sources) + + + SourceID = PacketFramework.Sources(i).SourceID; + SourceName = PacketFramework.Sources(i).SourceName; + disp(SourceID ); + disp( SourceName ); + + + line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"" } ", ... + string(PacketFramework.Sources(i).SourceID), ... + string(PacketFramework.Sources(i).SourceName), ... + string(PacketFramework.Sources(i).NValues_send), ... + string(PacketFramework.Sources(i).datatype) ); + + + if i==length(PacketFramework.Sources) + // finalise the last entry without "," + printf('%s' , line); + str=str+line + char(10); + else + printf('%s,' , line); + str=str+line+',' + char(10); + end + + + end + + + + str=str+'} , ' + char(10) + ' ""ParametersConfig"" : {' + char(10); + + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + + printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); + + line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } ", ... + string(PacketFramework.Parameters(i).ParameterID), ... + string(PacketFramework.Parameters(i).ParameterName), ... + string(PacketFramework.Parameters(i).NValues), ... + string(PacketFramework.Parameters(i).datatype) ); + + + if i==length(PacketFramework.Parameters) + // finalise the last entry without "," + printf('%s' , line); + str=str+line + char(10); + else + printf('%s,' , line); + str=str+line+',' + char(10); + end + + + end + str=str+'}, '+char(10)+ 'PaPIConfig : ' + PaPIConfigstr + char(10) + '}'; // + + // print the configuration to be send to Papi + disp(str); + endfunction function ld_PF_Export_js(PacketFramework, fname) // PARSEDOCU_BLOCK -// -// Export configuration of the defined protocoll (Sources, Parameters) -// into JSON-format. This is to be used by software that shall communicate -// to the real-time system. -// -// fname - The file name -// -// - str=ld_PF_Export_str(PacketFramework); - - fd = mopen(fname,'wt'); - mfprintf(fd,'%s', str); - mclose(fd); + // + // Export configuration of the defined protocoll (Sources, Parameters) + // into JSON-format. This is to be used by software that shall communicate + // to the real-time system. + // + // fname - The file name + // + // + str=ld_PF_Export_str(PacketFramework); + + fd = mopen(fname,'wt'); + mfprintf(fd,'%s', str); + mclose(fd); endfunction @@ -1050,27 +1130,27 @@ endfunction // function [sim, PacketFramework, SourceID]=ld_SendPacketReserve(sim, PacketFramework, NValues_send, datatype, SourceName) - [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); endfunction function [sim, PacketFramework]=ld_SendPacket2(sim, PacketFramework, Signal, SourceName) - // find Sourcename - index = -1; - for i=1:length(PacketFramework.Sources) - S = PacketFramework.Sources(i); - if S.SourceName == SourceName - index = i; - printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); - break; + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Sources) + S = PacketFramework.Sources(i); + if S.SourceName == SourceName + index = i; + printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); + break; + end end - end - if index == -1 - printf("SourceName = %s\n", SourceName); - error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); - end + if index == -1 + printf("SourceName = %s\n", SourceName); + error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); + end - [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); endfunction @@ -1081,26 +1161,26 @@ endfunction function [sim, PacketFramework, Parameter]=ld_PF_Parameter2(sim, PacketFramework, ParameterName) - // find Sourcename - index = -1; - for i=1:length(PacketFramework.Parameters) - P = PacketFramework.Parameters(i); - if P.ParameterName == ParameterName - index = i; - printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); - break; + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + if P.ParameterName == ParameterName + index = i; + printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); + break; + end end - end - - if index == -1 - printf("ParameterName = %s\n", ParameterName); - error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); - end - - // read data from global memory - [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 - [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... - P.datatype, P.NValues); + + if index == -1 + printf("ParameterName = %s\n", ParameterName); + error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); + end + + // read data from global memory + [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + P.datatype, P.NValues); endfunction From caae025fa6309373291008d501b114016fbffd86 Mon Sep 17 00:00:00 2001 From: christianausb Date: Thu, 5 Mar 2015 10:34:04 +0100 Subject: [PATCH 153/260] small fix --- .../webinterface/PacketFramework.sce | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce index 0e611392..2aeba89c 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce @@ -1101,7 +1101,7 @@ function str=ld_PF_Export_str(PacketFramework) end - str=str+'}, '+char(10)+ 'PaPIConfig : ' + PaPIConfigstr + char(10) + '}'; // + str=str+'}, '+char(10)+ '''' + 'PaPIConfig' + '''' + ' : ' + PaPIConfigstr + char(10) + '}'; // // print the configuration to be send to Papi disp(str); From cebd20b63f8e8d030a617e4a7fbd7b55bbc4d115 Mon Sep 17 00:00:00 2001 From: christianausb Date: Thu, 5 Mar 2015 10:54:27 +0100 Subject: [PATCH 154/260] JSON export of list() of strings --- .../webinterface/PacketFramework.sce | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce index 2aeba89c..d4c88761 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce @@ -968,11 +968,12 @@ endfunction function str=ld_PF_Export_str(PacketFramework) -// Added possibility to add GUI-configurations on 5.3.15 + // Added possibility to add GUI-configurations on 5.3.15 function jsonstr = struct2json(a) // // Rev 1 as of 4.3.15: Initial version + // Rev 2 as of 4.3.15: added arrays of strings that are defined by e.g. list('str1', 'str2') // // // Example usage: @@ -987,14 +988,26 @@ function str=ld_PF_Export_str(PacketFramework) // jsonstr = struct2json(a); // disp(jsonstr); // - // Warning: For strings, make sure you escape the special characters that are used by the JSON-format! + // Warning: For strings make sure you escape the special characters that are used by the JSON-format! // function valstr=val2str(val) select typeof(val) case "string" - valstr = '''' + string(val) + ''''; + if length(length(val)) == 1 then + valstr = '''' + string(val) + ''''; + end + + case "list" // convert to array of strings + valstr = '['; + + N = length(val); + for i=1:(N-1) + valstr = valstr + '''' + string( val(i) ) + '''' + ','; + end + valstr = valstr + '''' + string( val(N) ) + '''' + ']'; + case "constant" if length(val) == 1 then @@ -1035,6 +1048,7 @@ function str=ld_PF_Export_str(PacketFramework) jsonstr = str; endfunction + // check if there is a GUI to be set-up in Papi if isfield(PacketFramework, 'PaPIConfig') then PaPIConfigstr = struct2json(PacketFramework.PaPIConfig) From 5644559f5c8722758d9cd81f451d5d52d1012cb9 Mon Sep 17 00:00:00 2001 From: christianausb Date: Thu, 5 Mar 2015 10:54:55 +0100 Subject: [PATCH 155/260] updated example --- .../DataSourceAutoConfigExample/SelectCasePaPiConfig.sce | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce index 0e476639..ed6e5b03 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce @@ -136,12 +136,12 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) // Stream the data of the oscillator [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X"); - PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'plot_x'; + PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'plot'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot X'; PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup0'; - PacketFramework.PaPIConfig.ToSub.plot_X.signals = 'X'; // not okay so far, need an array of str + PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('X'); // not okay so far, need an array of str [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V"); @@ -165,12 +165,12 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) // Stream a constant [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=in1, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Const"); - PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'plot_X'; + PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'plot'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot Const'; PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup0'; - PacketFramework.PaPIConfig.ToSub.plot_X.signals = 'Const'; // not okay so far, need an array of str + PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('Const'); // not okay so far, need an array of str // finalise the communication interface [sim,PacketFramework] = ld_PF_Finalise(sim, PacketFramework); From 867d9475c7981f654f83f43516e4fa0e525e5256 Mon Sep 17 00:00:00 2001 From: christianausb Date: Thu, 5 Mar 2015 10:56:52 +0100 Subject: [PATCH 156/260] .. --- .../ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce index ed6e5b03..0cc3f0a9 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce @@ -141,7 +141,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot X'; PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup0'; - PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('X'); // not okay so far, need an array of str + PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('X'); [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V"); @@ -170,7 +170,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot Const'; PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup0'; - PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('Const'); // not okay so far, need an array of str + PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('Const'); // finalise the communication interface [sim,PacketFramework] = ld_PF_Finalise(sim, PacketFramework); From 127faf94280fb4c1431dc2830433d0262f618db3 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Thu, 5 Mar 2015 11:55:15 +0100 Subject: [PATCH 157/260] Added first version of a progress bar. Plugin Trigger was added to test the progress bar. --- papi/plugin/dpp/trigger/Trigger.py | 108 +++++++++ papi/plugin/dpp/trigger/Trigger.yapsy-plugin | 9 + papi/plugin/dpp/trigger/box.png | Bin 0 -> 177 bytes papi/plugin/visual/ProgressBar/ProgressBar.py | 208 ++++++++++++++++++ .../ProgressBar/ProgressBar.yapsy-plugin | 9 + 5 files changed, 334 insertions(+) create mode 100644 papi/plugin/dpp/trigger/Trigger.py create mode 100644 papi/plugin/dpp/trigger/Trigger.yapsy-plugin create mode 100644 papi/plugin/dpp/trigger/box.png create mode 100644 papi/plugin/visual/ProgressBar/ProgressBar.py create mode 100644 papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin diff --git a/papi/plugin/dpp/trigger/Trigger.py b/papi/plugin/dpp/trigger/Trigger.py new file mode 100644 index 00000000..c24fb007 --- /dev/null +++ b/papi/plugin/dpp/trigger/Trigger.py @@ -0,0 +1,108 @@ +#!/usr/bin/python3 +#-*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +from papi.data.DPlugin import DBlock +from papi.data.DSignal import DSignal + +from papi.data.DParameter import DParameter +from papi.plugin.base_classes.iop_base import iop_base + +import time +import math +import numpy + + +class Trigger(iop_base): + def __init__(self): + self.initialized = False + + def start_init(self, config=None): + + + + self.block1 = DBlock('Progress') + signal = DSignal('percent') + self.block1.add_signal(signal) + + self.block2 = DBlock('Trigger') + signal = DSignal('trigger') + self.block2.add_signal(signal) + + self.block3 = DBlock('ResetTrigger') + signal = DSignal('reset') + self.block3.add_signal(signal) + + blockList = [self.block1, self.block2, self.block3] + self.send_new_block_list(blockList) + + self.para3 = DParameter('Choose', default=0, Regex='\d+') + para_l = [self.para3] + + self.send_new_parameter_list(para_l) + + self.initialized = True + + return True + + def pause(self): + pass + + def resume(self): + pass + + def execute(self, Data=None, block_name = None): + pass + + def set_parameter(self, name, value): + if not self.initialized: + return + value = int(value) + if name == self.para3.name: + if value == 0: + print(value) + self.send_new_data('Progress', [0], {'percent': [20]}) + + if value == 1: + print(value) + self.send_new_data('Trigger', [0], {'trigger': [0]}) + + if value == 2: + print(value) + self.send_new_data('ResetTrigger', [0], {'reset': [0]}) + + + def get_plugin_configuration(self): + config = { + } + return config + + def quit(self): + print('Trigger: will quit') + + def plugin_meta_updated(self): + pass diff --git a/papi/plugin/dpp/trigger/Trigger.yapsy-plugin b/papi/plugin/dpp/trigger/Trigger.yapsy-plugin new file mode 100644 index 00000000..5b59d417 --- /dev/null +++ b/papi/plugin/dpp/trigger/Trigger.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = Trigger +Module = Trigger + +[Documentation] +Author = S.K. +Version = 0.1 +Website = www +Description = Used to trigger specific data vectors by parameter events. \ No newline at end of file diff --git a/papi/plugin/dpp/trigger/box.png b/papi/plugin/dpp/trigger/box.png new file mode 100644 index 0000000000000000000000000000000000000000..b61529159774d34551c73f69c7bdd50d76aa415e GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1SJ1Ryj={WI14-?iy0WWg+Z8+Vb&Z8pdfpR zr>`sfJsxp>837rm_&Y!$$r9Iy66gHf+|;}h2Ir#G#FEq$h4Rdj3#4hjMsEKCQd@w2RDG%B?4 Ras_H*@O1TaS?83{1ORc}EEE6$ literal 0 HcmV?d00001 diff --git a/papi/plugin/visual/ProgressBar/ProgressBar.py b/papi/plugin/visual/ProgressBar/ProgressBar.py new file mode 100644 index 00000000..013398b5 --- /dev/null +++ b/papi/plugin/visual/ProgressBar/ProgressBar.py @@ -0,0 +1,208 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: + Date: Thu, 5 Mar 2015 13:53:56 +0100 Subject: [PATCH 158/260] " instead of ' --- .gitignore | 4 ++++ .../SelectCasePaPiConfig.sce | 2 ++ .../webinterface/PacketFramework.sce | 16 ++++++++-------- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 15528654..984f529e 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,7 @@ coverage.xml # Sphinx documentation docs/_build/ + +data_sources/ORTD/DataSourceAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar + +data_sources/ORTD/DataSourceAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce index 0cc3f0a9..6695c8ec 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce @@ -148,6 +148,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) // finalise the communication interface [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); + ld_PF_Export_js(PacketFramework, fname="ProtocollConfig.json"); // Wait until a number of time steps has passed, then notify that // the experiment has finished by setting "finished" to 1. @@ -174,6 +175,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) // finalise the communication interface [sim,PacketFramework] = ld_PF_Finalise(sim, PacketFramework); + ld_PF_Export_js(PacketFramework, fname="ProtocollConfig.json"); // Wait until a number of time steps has passed, then notify that // the experiment (control system in this case) has finished. diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce index d4c88761..a08a9981 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce @@ -996,7 +996,7 @@ function str=ld_PF_Export_str(PacketFramework) case "string" if length(length(val)) == 1 then - valstr = '''' + string(val) + ''''; + valstr = """" + string(val) + """"; end case "list" // convert to array of strings @@ -1004,23 +1004,23 @@ function str=ld_PF_Export_str(PacketFramework) N = length(val); for i=1:(N-1) - valstr = valstr + '''' + string( val(i) ) + '''' + ','; + valstr = valstr + """" + string( val(i) ) + """" + ','; end - valstr = valstr + '''' + string( val(N) ) + '''' + ']'; + valstr = valstr + """" + string( val(N) ) + """" + ']'; case "constant" if length(val) == 1 then valstr = string(val); else - valstr = '''' + "** Matrix not supported **" + ''''; + valstr = """" + "** Matrix not supported **" + """"; end case "st" valstr = struct2json(val); else - valstr = '''' + "**Datatype " + typeof(val) + " not supported **" + ''''; + valstr = """" + "**Datatype " + typeof(val) + " not supported **" + """"; end endfunction @@ -1033,13 +1033,13 @@ function str=ld_PF_Export_str(PacketFramework) f = F(i); val = eval('a.'+f); valstr = val2str(val); - str = str + '''' + f + '''' + ' : ' + valstr + ' , '; + str = str + """" + f + """" + ' : ' + valstr + ' , '; end f = F(N); val = eval('a.'+f); valstr = val2str(val); - str = str + '''' + f + '''' + ' : ' + valstr + ' } '; + str = str + """" + f + """" + ' : ' + valstr + ' } '; else error("Not a structure") @@ -1115,7 +1115,7 @@ function str=ld_PF_Export_str(PacketFramework) end - str=str+'}, '+char(10)+ '''' + 'PaPIConfig' + '''' + ' : ' + PaPIConfigstr + char(10) + '}'; // + str=str+'}, '+char(10)+ """" + 'PaPIConfig' + """" + ' : ' + PaPIConfigstr + char(10) + '}'; // // print the configuration to be send to Papi disp(str); From e0d7075e00899ba0814f18b6cb6aa04f61fa8d78 Mon Sep 17 00:00:00 2001 From: christianausb Date: Thu, 5 Mar 2015 14:06:07 +0100 Subject: [PATCH 159/260] .. --- .../ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce index 6695c8ec..41c64449 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce @@ -136,7 +136,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) // Stream the data of the oscillator [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X"); - PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'plot'; + PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'Plot'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot X'; @@ -166,7 +166,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) // Stream a constant [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=in1, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Const"); - PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'plot'; + PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'Plot'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot Const'; From 845c3f3bd4208f61b001ec9ec0324d5e854350dd Mon Sep 17 00:00:00 2001 From: christianausb Date: Thu, 5 Mar 2015 14:15:34 +0100 Subject: [PATCH 160/260] .. --- .../ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce index 41c64449..fc44f6f8 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce @@ -140,7 +140,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot X'; - PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup0'; + PacketFramework.PaPIConfig.ToSub.plot_X.block = 'ControllerSignals'; PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('X'); @@ -170,7 +170,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot Const'; - PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup0'; + PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup2'; PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('Const'); // finalise the communication interface From c180b1b760878e240fbe4560726be9e7b5dd241c Mon Sep 17 00:00:00 2001 From: christianausb Date: Thu, 5 Mar 2015 14:20:50 +0100 Subject: [PATCH 161/260] commit of the data source example to branch dynamikORTDConfig --- .../ProtocollConfig.json | 11 + .../SelectCasePaPiConfig.ipar | 14967 ++++++++++++++++ .../SelectCasePaPiConfig.rpar | 26 + .../SelectCasePaPiConfig.sce | 342 + .../run_SelectCasePaPiConfig.sh | 4 + .../webinterface/PacketFramework.sce | 1202 ++ 6 files changed, 16552 insertions(+) create mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/ProtocollConfig.json create mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar create mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar create mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce create mode 100755 data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh create mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/ProtocollConfig.json b/data_sources/ORTD/DataSourceAutoConfigExample/ProtocollConfig.json new file mode 100644 index 00000000..bb8d1b0d --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/ProtocollConfig.json @@ -0,0 +1,11 @@ + {"SourcesConfig" : { + "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257" } , + "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257" } +} , + "ParametersConfig" : { + "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , + "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , + "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } +}, +"PaPIConfig" : { "ToCreate" : { "plot_X" : { "identifier" : { "value" : "plot" } , "config" : { "size" : { "value" : "(300,300)" } , "position" : { "value" : "(300,0)" } , "name" : { "value" : "Plot X" } } } } , "ToSub" : { "plot_X" : { "block" : "SourceGroup0" , "signals" : ["X"] } } } +} \ No newline at end of file diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar new file mode 100644 index 00000000..70974d3a --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar @@ -0,0 +1,14967 @@ + 1 + 1 + 901 + 10 + 0 + 0 + 14959 + 26 + 1 + 3 + 201 + 100 + 0 + 0 + 6 + 0 + 203 + 100 + 6 + 0 + 14921 + 26 + 100 + 101 + 14927 + 26 + 12 + 0 + 60023 + 201 + 0 + 0 + 1 + 0 + 15011 + 203 + 14915 + 26 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 14852 + 0 + 21 + 1 + 14864 + 0 + 1 + 26 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 14851 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 21 + 4 + 4 + 0 + 19 + 0 + 22 + 4 + 23 + 0 + 4 + 0 + 900 + 10 + 27 + 0 + 14780 + 26 + 0 + 0 + 0 + 0 + 18 + 77 + 97 + 105 + 110 + 82 + 101 + 97 + 108 + 116 + 105 + 109 + 101 + 84 + 104 + 114 + 101 + 97 + 100 + 3 + 2 + 0 + -1 + 1 + 6 + 201 + 100 + 0 + 0 + 6 + 1 + 203 + 100 + 6 + 1 + 72 + 0 + 205 + 100 + 78 + 1 + 6 + 1 + 207 + 100 + 84 + 2 + 14615 + 24 + 211 + 100 + 14699 + 26 + 15 + 0 + 100 + 101 + 14714 + 26 + 28 + 0 + 40 + 201 + 0 + 1 + 1 + 0 + 15102 + 203 + 66 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 3 + 0 + 21 + 1 + 15 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 2 + 1 + 0 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 15002 + 207 + 14609 + 24 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 4 + 0 + 12 + 4 + 7 + 0 + 3 + 0 + 13 + 4 + 10 + 0 + 4 + 0 + 20 + 4 + 14 + 0 + 4 + 0 + 21 + 1 + 18 + 0 + 1 + 1 + 900 + 10 + 19 + 1 + 357 + 10 + 901 + 10 + 376 + 11 + 14115 + 11 + 902 + 10 + 14491 + 22 + 62 + 2 + 2 + 1 + 1 + 3 + 1 + 1 + 1 + 2 + 257 + 257 + 3 + 257 + 257 + 257 + 3 + 3 + 1 + 1 + 1 + 1 + 6 + 203 + 100 + 0 + 0 + 6 + 1 + 205 + 100 + 6 + 1 + 6 + 1 + 207 + 100 + 12 + 2 + 227 + 6 + 210 + 100 + 239 + 8 + 6 + 1 + 212 + 100 + 245 + 9 + 6 + 1 + 100 + 101 + 251 + 10 + 68 + 0 + 40 + 203 + 0 + 1 + 1 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 15001 + 207 + 221 + 6 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 3 + 0 + 12 + 4 + 5 + 0 + 2 + 0 + 13 + 4 + 7 + 0 + 3 + 0 + 20 + 4 + 10 + 0 + 4 + 0 + 21 + 4 + 14 + 0 + 37 + 0 + 900 + 10 + 51 + 0 + 54 + 2 + 901 + 10 + 105 + 2 + 66 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 257 + 2 + 257 + 257 + 3 + 2 + 1 + 0 + 36 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 1 + 3 + 202 + 100 + 0 + 0 + 6 + 1 + 204 + 100 + 6 + 1 + 8 + 1 + 100 + 101 + 14 + 2 + 20 + 0 + 40 + 202 + 0 + 1 + 1 + 0 + 60010 + 204 + 2 + 1 + 1 + 0 + 1 + 0 + 0 + 2 + 8 + 4 + 0 + 204 + 204 + 0 + 1 + 0 + 0 + 0 + 0 + 202 + 202 + 0 + 1 + 0 + 0 + 1 + 1 + 4 + 202 + 100 + 0 + 0 + 6 + 1 + 204 + 100 + 6 + 1 + 6 + 1 + 206 + 100 + 12 + 2 + 8 + 2 + 100 + 101 + 20 + 4 + 20 + 0 + 40 + 202 + 0 + 1 + 1 + 0 + 40 + 204 + 0 + 1 + 1 + 0 + 60026 + 206 + 2 + 2 + 1 + 0 + 2 + 10 + 0 + 2 + 8 + 4 + 0 + 204 + 204 + 0 + 1 + 0 + 0 + 0 + 0 + 206 + 206 + 0 + 1 + 0 + 0 + 1 + 40 + 210 + 0 + 1 + 1 + 0 + 60020 + 212 + 0 + 1 + 1 + 0 + 0 + 8 + 8 + 4 + 1 + 0 + 0 + 0 + 0 + 207 + 207 + 0 + 0 + 205 + 205 + 0 + 0 + 207 + 207 + 1 + 0 + 203 + 203 + 0 + 0 + 207 + 207 + 2 + 0 + 207 + 207 + 1 + 0 + 212 + 212 + 0 + 0 + 210 + 210 + 0 + 0 + 212 + 212 + 1 + 0 + 207 + 207 + 0 + 1 + 0 + 0 + 0 + 0 + 212 + 212 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 2 + 1 + 17 + 203 + 100 + 0 + 0 + 6 + 0 + 205 + 100 + 6 + 0 + 13697 + 4 + 208 + 100 + 13703 + 4 + 6 + 0 + 210 + 100 + 13709 + 4 + 7 + 0 + 212 + 100 + 13716 + 4 + 8 + 0 + 215 + 100 + 13724 + 4 + 6 + 0 + 217 + 100 + 13730 + 4 + 28 + 0 + 218 + 100 + 13758 + 4 + 31 + 0 + 219 + 100 + 13789 + 4 + 6 + 1 + 221 + 100 + 13795 + 5 + 24 + 0 + 222 + 100 + 13819 + 5 + 6 + 1 + 224 + 100 + 13825 + 6 + 6 + 1 + 226 + 100 + 13831 + 7 + 6 + 1 + 228 + 100 + 13837 + 8 + 6 + 1 + 230 + 100 + 13843 + 9 + 6 + 1 + 232 + 100 + 13849 + 10 + 6 + 1 + 100 + 101 + 13855 + 11 + 156 + 0 + 60023 + 203 + 0 + 0 + 1 + 0 + 15011 + 205 + 13691 + 4 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 3 + 0 + 12 + 4 + 5 + 0 + 2 + 0 + 13 + 4 + 7 + 0 + 3 + 0 + 14 + 4 + 10 + 0 + 2 + 0 + 15 + 4 + 12 + 0 + 2 + 0 + 20 + 4 + 14 + 0 + 13626 + 0 + 21 + 1 + 13640 + 0 + 1 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 257 + 2 + 2 + 257 + 1 + 1 + 1 + 1 + 13625 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 21 + 4 + 6 + 0 + 12 + 0 + 22 + 4 + 18 + 0 + 4 + 0 + 900 + 10 + 22 + 0 + 13559 + 4 + 0 + 1 + 1 + 0 + 1 + 2 + 11 + 67 + 111 + 109 + 112 + 32 + 84 + 104 + 114 + 101 + 97 + 100 + 3 + 2 + 0 + -1 + 1 + 10 + 201 + 100 + 0 + 0 + 6 + 1 + 203 + 100 + 6 + 1 + 13207 + 0 + 205 + 100 + 13213 + 1 + 44 + 0 + 206 + 100 + 13257 + 1 + 6 + 1 + 208 + 100 + 13263 + 2 + 6 + 1 + 210 + 100 + 13269 + 3 + 8 + 0 + 212 + 100 + 13277 + 3 + 8 + 0 + 214 + 100 + 13285 + 3 + 6 + 1 + 216 + 100 + 13291 + 4 + 130 + 0 + 100 + 101 + 13421 + 4 + 76 + 0 + 40 + 201 + 0 + 1 + 1 + 0 + 22000 + 203 + 13201 + 0 + 1 + 0 + 1 + 20 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 12974 + 105 + 94 + 12 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 102 + 108 + 97 + 103 + 41 + 10 + 32 + 32 + 47 + 47 + 32 + 84 + 104 + 105 + 115 + 32 + 115 + 99 + 105 + 108 + 97 + 98 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 105 + 115 + 32 + 99 + 97 + 108 + 108 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 45 + 116 + 105 + 109 + 101 + 10 + 32 + 32 + 47 + 47 + 32 + 78 + 79 + 84 + 69 + 58 + 32 + 80 + 108 + 101 + 97 + 115 + 101 + 32 + 110 + 111 + 116 + 101 + 32 + 116 + 104 + 97 + 116 + 32 + 116 + 104 + 101 + 32 + 118 + 97 + 114 + 105 + 97 + 98 + 108 + 101 + 115 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 111 + 117 + 116 + 115 + 105 + 100 + 101 + 32 + 116 + 104 + 105 + 115 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 97 + 114 + 101 + 32 + 116 + 121 + 112 + 105 + 99 + 97 + 108 + 108 + 121 + 32 + 110 + 111 + 116 + 32 + 97 + 118 + 97 + 105 + 108 + 97 + 98 + 108 + 101 + 32 + 97 + 116 + 32 + 114 + 117 + 110 + 45 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 84 + 104 + 105 + 115 + 32 + 97 + 108 + 115 + 111 + 32 + 104 + 111 + 108 + 100 + 115 + 32 + 116 + 114 + 117 + 101 + 32 + 102 + 111 + 114 + 32 + 115 + 101 + 108 + 102 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 115 + 33 + 10 + 32 + 32 + 10 + 32 + 32 + 115 + 101 + 108 + 101 + 99 + 116 + 32 + 102 + 108 + 97 + 103 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 49 + 59 + 32 + 47 + 47 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 112 + 108 + 105 + 116 + 32 + 116 + 104 + 101 + 32 + 115 + 101 + 110 + 115 + 111 + 114 + 32 + 100 + 97 + 116 + 97 + 10 + 32 + 32 + 32 + 32 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 112 + 116 + 114 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 43 + 32 + 49 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 103 + 101 + 116 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 10 + 32 + 32 + 32 + 32 + 116 + 114 + 121 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 59 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 116 + 99 + 104 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 105 + 115 + 73 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 32 + 61 + 32 + 37 + 102 + 59 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 115 + 104 + 111 + 117 + 108 + 100 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 32 + 116 + 104 + 101 + 32 + 115 + 101 + 110 + 115 + 111 + 114 + 32 + 100 + 97 + 116 + 97 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 73 + 110 + 102 + 111 + 32 + 61 + 32 + 39 + 84 + 104 + 105 + 115 + 32 + 115 + 99 + 104 + 101 + 109 + 116 + 105 + 99 + 32 + 119 + 97 + 115 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 32 + 105 + 110 + 32 + 105 + 116 + 101 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 35 + 39 + 32 + 43 + 32 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 73 + 110 + 112 + 117 + 116 + 68 + 97 + 116 + 97 + 32 + 61 + 32 + 100 + 97 + 116 + 97 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 32 + 116 + 111 + 32 + 116 + 104 + 105 + 115 + 32 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 97 + 108 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 58 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 100 + 105 + 115 + 112 + 40 + 99 + 102 + 112 + 97 + 114 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 50 + 93 + 59 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 49 + 93 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 93 + 59 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 93 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 61 + 32 + 34 + 65 + 117 + 116 + 111 + 67 + 97 + 108 + 105 + 98 + 68 + 101 + 109 + 111 + 34 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 97 + 112 + 112 + 101 + 110 + 100 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 32 + 116 + 111 + 32 + 116 + 104 + 101 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 115 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 95 + 95 + 32 + 61 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 59 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 95 + 95 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 49 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 95 + 95 + 32 + 61 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 59 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 95 + 95 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 34 + 68 + 101 + 102 + 105 + 110 + 105 + 110 + 103 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 58 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 32 + 117 + 115 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 102 + 111 + 108 + 108 + 111 + 119 + 105 + 110 + 103 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 58 + 92 + 110 + 34 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 100 + 105 + 115 + 112 + 40 + 102 + 117 + 110 + 50 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 41 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 95 + 95 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 105 + 115 + 92 + 110 + 39 + 41 + 59 + 100 + 105 + 115 + 112 + 40 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 114 + 101 + 97 + 116 + 101 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 105 + 114 + 45 + 112 + 97 + 114 + 32 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 46 + 91 + 105 + 44 + 114 + 93 + 112 + 97 + 114 + 32 + 102 + 105 + 108 + 101 + 115 + 10 + 32 + 32 + 32 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 32 + 37 + 116 + 59 + 47 + 47 + 32 + 84 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 105 + 115 + 32 + 99 + 97 + 108 + 108 + 101 + 100 + 32 + 111 + 110 + 108 + 105 + 110 + 101 + 32 + 98 + 101 + 99 + 97 + 117 + 115 + 101 + 32 + 119 + 101 + 32 + 97 + 114 + 101 + 32 + 105 + 110 + 32 + 101 + 109 + 98 + 101 + 100 + 100 + 101 + 100 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 104 + 101 + 114 + 101 + 10 + 32 + 32 + 32 + 32 + 78 + 32 + 61 + 32 + 50 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 91 + 112 + 97 + 114 + 44 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 105 + 109 + 110 + 101 + 115 + 116 + 50 + 95 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 109 + 101 + 110 + 116 + 40 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 95 + 95 + 44 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 95 + 95 + 44 + 32 + 110 + 101 + 115 + 116 + 101 + 100 + 95 + 102 + 110 + 61 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 61 + 108 + 105 + 115 + 116 + 40 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 44 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 44 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 41 + 44 + 32 + 78 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 53 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 95 + 95 + 32 + 78 + 101 + 119 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 105 + 115 + 92 + 110 + 39 + 41 + 59 + 100 + 105 + 115 + 112 + 40 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 97 + 118 + 101 + 32 + 118 + 101 + 99 + 116 + 111 + 114 + 115 + 32 + 116 + 111 + 32 + 97 + 32 + 102 + 105 + 108 + 101 + 10 + 32 + 32 + 32 + 32 + 115 + 97 + 118 + 101 + 95 + 105 + 114 + 112 + 97 + 114 + 97 + 109 + 40 + 112 + 97 + 114 + 44 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 43 + 32 + 39 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 105 + 112 + 97 + 114 + 39 + 44 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 43 + 32 + 39 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 114 + 112 + 97 + 114 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 84 + 101 + 108 + 108 + 32 + 116 + 104 + 97 + 116 + 32 + 101 + 118 + 101 + 114 + 121 + 116 + 104 + 105 + 110 + 103 + 32 + 119 + 101 + 110 + 116 + 32 + 102 + 105 + 110 + 101 + 46 + 10 + 32 + 32 + 32 + 32 + 99 + 111 + 109 + 112 + 114 + 101 + 97 + 100 + 121 + 32 + 61 + 32 + 49 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 82 + 101 + 116 + 117 + 114 + 110 + 86 + 97 + 108 + 32 + 61 + 32 + 48 + 59 + 47 + 47 + 32 + 114 + 117 + 110 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 97 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 112 + 97 + 99 + 107 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 32 + 61 + 32 + 122 + 101 + 114 + 111 + 115 + 40 + 50 + 48 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 40 + 49 + 41 + 32 + 61 + 32 + 99 + 111 + 109 + 112 + 114 + 101 + 97 + 100 + 121 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 40 + 50 + 41 + 32 + 61 + 32 + 67 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 82 + 101 + 116 + 117 + 114 + 110 + 86 + 97 + 108 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 108 + 101 + 97 + 114 + 10 + 32 + 32 + 32 + 32 + 112 + 97 + 114 + 46 + 105 + 112 + 97 + 114 + 32 + 61 + 32 + 91 + 93 + 59 + 10 + 32 + 32 + 32 + 32 + 112 + 97 + 114 + 46 + 114 + 112 + 97 + 114 + 32 + 61 + 32 + 91 + 93 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 112 + 116 + 114 + 40 + 49 + 41 + 32 + 61 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 52 + 59 + 32 + 47 + 47 + 32 + 105 + 110 + 105 + 116 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 48 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 53 + 59 + 32 + 47 + 47 + 32 + 116 + 101 + 114 + 109 + 105 + 110 + 97 + 116 + 101 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 34 + 116 + 101 + 114 + 109 + 105 + 110 + 97 + 116 + 101 + 92 + 110 + 34 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 49 + 48 + 59 + 32 + 47 + 47 + 32 + 99 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 101 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 91 + 115 + 105 + 109 + 44 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 61 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 44 + 32 + 112 + 97 + 114 + 41 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 84 + 104 + 101 + 32 + 110 + 101 + 115 + 116 + 101 + 100 + 32 + 115 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 115 + 32 + 116 + 119 + 111 + 32 + 115 + 117 + 98 + 45 + 115 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 115 + 58 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 49 + 41 + 32 + 65 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 44 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 99 + 111 + 109 + 109 + 111 + 110 + 108 + 121 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 115 + 32 + 110 + 111 + 116 + 104 + 105 + 110 + 103 + 32 + 97 + 110 + 100 + 32 + 105 + 115 + 32 + 115 + 119 + 105 + 116 + 99 + 104 + 101 + 100 + 32 + 116 + 111 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 109 + 101 + 110 + 116 + 32 + 105 + 115 + 32 + 105 + 110 + 32 + 112 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 32 + 40 + 119 + 104 + 105 + 99 + 104 + 32 + 109 + 97 + 121 + 32 + 116 + 97 + 107 + 101 + 32 + 115 + 111 + 109 + 101 + 32 + 116 + 105 + 109 + 101 + 41 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 50 + 41 + 32 + 84 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 44 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 97 + 99 + 116 + 117 + 97 + 108 + 108 + 121 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 115 + 32 + 116 + 104 + 101 + 32 + 97 + 108 + 103 + 111 + 114 + 105 + 116 + 104 + 109 + 32 + 116 + 111 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 72 + 101 + 114 + 101 + 44 + 32 + 116 + 104 + 101 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 32 + 115 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 115 + 32 + 97 + 114 + 101 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 44 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 99 + 97 + 110 + 32 + 116 + 104 + 101 + 110 + 32 + 98 + 101 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 101 + 118 + 32 + 61 + 32 + 48 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 32 + 61 + 32 + 112 + 97 + 114 + 40 + 49 + 41 + 59 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 110 + 117 + 109 + 98 + 101 + 114 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 115 + 116 + 101 + 100 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 115 + 32 + 40 + 111 + 110 + 101 + 32 + 111 + 102 + 32 + 116 + 119 + 111 + 41 + 32 + 34 + 49 + 34 + 32 + 109 + 101 + 97 + 110 + 115 + 32 + 116 + 104 + 101 + 10 + 32 + 32 + 47 + 47 + 32 + 100 + 117 + 109 + 109 + 121 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 105 + 115 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 101 + 100 + 32 + 119 + 104 + 105 + 108 + 101 + 32 + 116 + 104 + 101 + 32 + 50 + 110 + 100 + 32 + 34 + 50 + 34 + 32 + 105 + 115 + 32 + 101 + 120 + 99 + 104 + 97 + 110 + 103 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 10 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 112 + 97 + 114 + 40 + 50 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 32 + 78 + 61 + 37 + 100 + 92 + 110 + 39 + 44 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 49 + 41 + 59 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 50 + 41 + 59 + 10 + 32 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 51 + 41 + 59 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 52 + 41 + 59 + 10 + 32 + 32 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 53 + 41 + 59 + 10 + 32 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 54 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 122 + 101 + 114 + 111 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 48 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 102 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 32 + 61 + 61 + 32 + 49 + 32 + 116 + 104 + 101 + 110 + 32 + 32 + 32 + 47 + 47 + 32 + 105 + 115 + 32 + 116 + 104 + 105 + 115 + 32 + 116 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 50 + 41 + 32 + 63 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 68 + 101 + 102 + 105 + 110 + 101 + 32 + 100 + 117 + 109 + 109 + 121 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 115 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 78 + 79 + 84 + 69 + 58 + 32 + 111 + 110 + 108 + 121 + 32 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 32 + 105 + 115 + 32 + 115 + 117 + 112 + 112 + 111 + 114 + 116 + 101 + 100 + 32 + 98 + 121 + 32 + 116 + 104 + 105 + 115 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 102 + 111 + 114 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 32 + 61 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 44 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 100 + 117 + 109 + 109 + 121 + 79 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 118 + 101 + 99 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 122 + 101 + 114 + 111 + 115 + 40 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 44 + 32 + 49 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 100 + 117 + 109 + 109 + 121 + 79 + 117 + 116 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 122 + 101 + 114 + 111 + 59 + 47 + 47 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 102 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 32 + 61 + 61 + 32 + 50 + 32 + 116 + 104 + 101 + 110 + 32 + 32 + 32 + 47 + 47 + 32 + 105 + 115 + 32 + 116 + 104 + 105 + 115 + 32 + 116 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 50 + 41 + 32 + 63 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 58 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 114 + 117 + 110 + 32 + 116 + 104 + 101 + 32 + 99 + 97 + 108 + 108 + 98 + 97 + 99 + 107 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 102 + 111 + 114 + 32 + 100 + 101 + 102 + 105 + 110 + 105 + 116 + 105 + 111 + 110 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 100 + 105 + 115 + 112 + 40 + 102 + 117 + 110 + 50 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 44 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 44 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 32 + 61 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 44 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 58 + 32 + 100 + 111 + 110 + 101 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 116 + 111 + 114 + 101 + 32 + 116 + 104 + 101 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 111 + 102 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 53 + 41 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 97 + 100 + 100 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 59 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 91 + 115 + 105 + 109 + 44 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 44 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 61 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 44 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 41 + 10 + 32 + 32 + 10 + 32 + 32 + 47 + 47 + 32 + 68 + 101 + 102 + 105 + 110 + 101 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 46 + 32 + 84 + 104 + 101 + 121 + 32 + 109 + 117 + 115 + 116 + 32 + 98 + 101 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 111 + 110 + 99 + 101 + 32 + 97 + 103 + 97 + 105 + 110 + 32 + 97 + 116 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 108 + 97 + 99 + 101 + 44 + 32 + 98 + 101 + 99 + 97 + 117 + 115 + 101 + 32 + 116 + 104 + 105 + 115 + 32 + 119 + 105 + 108 + 108 + 32 + 97 + 108 + 115 + 111 + 32 + 98 + 101 + 32 + 99 + 97 + 108 + 108 + 101 + 100 + 32 + 97 + 116 + 10 + 32 + 32 + 47 + 47 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 78 + 83 + 97 + 109 + 112 + 108 + 101 + 115 + 32 + 61 + 32 + 53 + 48 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 102 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 61 + 32 + 37 + 116 + 32 + 116 + 104 + 101 + 110 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 84 + 104 + 101 + 32 + 99 + 111 + 110 + 116 + 101 + 110 + 116 + 115 + 32 + 111 + 102 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 97 + 114 + 116 + 32 + 119 + 105 + 108 + 108 + 32 + 98 + 101 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 111 + 110 + 45 + 108 + 105 + 110 + 101 + 44 + 32 + 119 + 104 + 105 + 108 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 105 + 115 + 32 + 114 + 117 + 110 + 110 + 105 + 110 + 103 + 46 + 32 + 84 + 104 + 101 + 32 + 97 + 105 + 109 + 32 + 105 + 115 + 32 + 116 + 111 + 32 + 103 + 101 + 110 + 101 + 114 + 97 + 116 + 101 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 102 + 111 + 114 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 46 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 80 + 108 + 101 + 97 + 115 + 101 + 32 + 110 + 111 + 116 + 101 + 58 + 32 + 83 + 105 + 110 + 99 + 101 + 32 + 116 + 104 + 105 + 115 + 32 + 99 + 111 + 100 + 101 + 32 + 105 + 115 + 32 + 111 + 110 + 108 + 121 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 100 + 32 + 111 + 110 + 45 + 108 + 105 + 110 + 101 + 44 + 32 + 109 + 111 + 115 + 116 + 32 + 112 + 111 + 116 + 101 + 110 + 116 + 105 + 97 + 108 + 32 + 101 + 114 + 114 + 111 + 114 + 115 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 111 + 99 + 99 + 117 + 114 + 105 + 110 + 103 + 32 + 105 + 110 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 97 + 114 + 116 + 32 + 98 + 101 + 99 + 111 + 109 + 101 + 32 + 111 + 110 + 108 + 121 + 32 + 118 + 105 + 115 + 105 + 98 + 108 + 101 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 101 + 120 + 101 + 99 + 40 + 39 + 119 + 101 + 98 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 47 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 115 + 99 + 101 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 102 + 117 + 110 + 99 + 112 + 114 + 111 + 116 + 40 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 122 + 32 + 61 + 32 + 112 + 111 + 108 + 121 + 40 + 48 + 44 + 32 + 39 + 122 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 65 + 110 + 100 + 32 + 101 + 120 + 97 + 109 + 112 + 108 + 101 + 45 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 116 + 104 + 97 + 116 + 32 + 105 + 115 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 101 + 100 + 32 + 118 + 105 + 97 + 32 + 85 + 68 + 80 + 32 + 97 + 110 + 100 + 32 + 111 + 110 + 101 + 32 + 115 + 116 + 101 + 112 + 32 + 102 + 117 + 114 + 116 + 104 + 101 + 114 + 32 + 119 + 105 + 116 + 104 + 32 + 116 + 104 + 101 + 32 + 87 + 101 + 98 + 45 + 103 + 117 + 105 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 117 + 112 + 101 + 114 + 98 + 108 + 111 + 99 + 107 + 58 + 32 + 65 + 32 + 109 + 111 + 114 + 101 + 32 + 99 + 111 + 109 + 112 + 108 + 101 + 120 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 119 + 105 + 116 + 104 + 32 + 100 + 97 + 109 + 112 + 105 + 110 + 103 + 10 + 32 + 32 + 32 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 91 + 115 + 105 + 109 + 44 + 32 + 120 + 44 + 118 + 93 + 32 + 61 + 32 + 100 + 97 + 109 + 112 + 101 + 100 + 95 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 117 + 44 + 32 + 84 + 95 + 97 + 41 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 114 + 101 + 97 + 116 + 101 + 32 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 115 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 110 + 101 + 119 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 40 + 115 + 105 + 109 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 110 + 101 + 119 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 40 + 115 + 105 + 109 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 117 + 115 + 101 + 32 + 116 + 104 + 105 + 115 + 32 + 97 + 115 + 32 + 97 + 32 + 110 + 111 + 114 + 109 + 97 + 108 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 97 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 97 + 100 + 100 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 117 + 44 + 32 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 44 + 32 + 91 + 49 + 44 + 32 + 45 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 97 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 97 + 100 + 100 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 97 + 44 + 32 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 44 + 32 + 91 + 49 + 44 + 32 + 45 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 118 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 122 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 97 + 44 + 32 + 49 + 47 + 40 + 122 + 45 + 49 + 41 + 32 + 42 + 32 + 84 + 95 + 97 + 32 + 41 + 59 + 32 + 47 + 47 + 32 + 73 + 110 + 116 + 101 + 103 + 114 + 97 + 116 + 111 + 114 + 32 + 97 + 112 + 112 + 114 + 111 + 120 + 105 + 109 + 97 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 32 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 118 + 95 + 103 + 97 + 105 + 110 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 103 + 97 + 105 + 110 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 118 + 44 + 32 + 48 + 46 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 108 + 111 + 115 + 101 + 32 + 108 + 111 + 111 + 112 + 32 + 118 + 95 + 103 + 97 + 105 + 110 + 32 + 61 + 32 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 99 + 108 + 111 + 115 + 101 + 95 + 108 + 111 + 111 + 112 + 40 + 115 + 105 + 109 + 44 + 32 + 118 + 95 + 103 + 97 + 105 + 110 + 44 + 32 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 120 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 122 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 118 + 44 + 32 + 49 + 47 + 40 + 122 + 45 + 49 + 41 + 32 + 42 + 32 + 84 + 95 + 97 + 32 + 41 + 59 + 32 + 47 + 47 + 32 + 73 + 110 + 116 + 101 + 103 + 114 + 97 + 116 + 111 + 114 + 32 + 97 + 112 + 112 + 114 + 111 + 120 + 105 + 109 + 97 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 32 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 120 + 95 + 103 + 97 + 105 + 110 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 103 + 97 + 105 + 110 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 120 + 44 + 32 + 48 + 46 + 54 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 108 + 111 + 115 + 101 + 32 + 108 + 111 + 111 + 112 + 32 + 120 + 95 + 103 + 97 + 105 + 110 + 32 + 61 + 32 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 99 + 108 + 111 + 115 + 101 + 95 + 108 + 111 + 111 + 112 + 40 + 115 + 105 + 109 + 44 + 32 + 120 + 95 + 103 + 97 + 105 + 110 + 44 + 32 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 105 + 102 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 105 + 115 + 73 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 32 + 61 + 61 + 32 + 37 + 102 + 32 + 116 + 104 + 101 + 110 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 116 + 97 + 116 + 101 + 32 + 118 + 97 + 114 + 105 + 97 + 98 + 108 + 101 + 115 + 32 + 99 + 97 + 110 + 32 + 98 + 101 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 32 + 97 + 116 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 108 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 48 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 105 + 115 + 73 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 32 + 61 + 32 + 37 + 116 + 59 + 47 + 47 + 32 + 112 + 114 + 101 + 118 + 101 + 110 + 116 + 32 + 102 + 114 + 111 + 109 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 118 + 97 + 114 + 105 + 97 + 98 + 108 + 101 + 115 + 32 + 111 + 110 + 99 + 101 + 32 + 97 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 69 + 120 + 97 + 109 + 112 + 108 + 101 + 32 + 102 + 111 + 114 + 32 + 97 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 117 + 112 + 100 + 97 + 116 + 101 + 58 + 32 + 105 + 110 + 99 + 114 + 101 + 97 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 43 + 32 + 49 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 66 + 117 + 105 + 108 + 100 + 32 + 97 + 110 + 32 + 105 + 110 + 102 + 111 + 45 + 115 + 116 + 114 + 105 + 110 + 103 + 10 + 32 + 32 + 32 + 32 + 83 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 73 + 110 + 102 + 111 + 32 + 61 + 32 + 39 + 79 + 110 + 45 + 108 + 105 + 110 + 101 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 105 + 110 + 32 + 105 + 116 + 101 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 35 + 39 + 32 + 43 + 32 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 68 + 101 + 102 + 105 + 110 + 101 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 101 + 114 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 100 + 101 + 112 + 101 + 110 + 100 + 105 + 110 + 103 + 32 + 111 + 110 + 32 + 116 + 104 + 101 + 32 + 99 + 117 + 114 + 114 + 101 + 110 + 116 + 108 + 121 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 32 + 115 + 116 + 97 + 116 + 101 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 105 + 110 + 105 + 116 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 80 + 97 + 80 + 105 + 32 + 85 + 68 + 80 + 32 + 83 + 111 + 99 + 107 + 101 + 116 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 85 + 110 + 100 + 101 + 114 + 108 + 121 + 105 + 110 + 103 + 80 + 114 + 111 + 116 + 111 + 99 + 111 + 108 + 108 + 32 + 61 + 32 + 39 + 85 + 68 + 80 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 68 + 101 + 115 + 116 + 72 + 111 + 115 + 116 + 32 + 61 + 32 + 39 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 68 + 101 + 115 + 116 + 80 + 111 + 114 + 116 + 32 + 61 + 32 + 50 + 48 + 48 + 48 + 48 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 76 + 111 + 99 + 97 + 108 + 83 + 111 + 99 + 107 + 101 + 116 + 72 + 111 + 115 + 116 + 32 + 61 + 32 + 39 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 76 + 111 + 99 + 97 + 108 + 83 + 111 + 99 + 107 + 101 + 116 + 80 + 111 + 114 + 116 + 32 + 61 + 32 + 50 + 48 + 48 + 48 + 49 + 59 + 10 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 100 + 101 + 98 + 117 + 103 + 109 + 111 + 100 + 101 + 32 + 61 + 32 + 37 + 116 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 73 + 110 + 105 + 116 + 73 + 110 + 115 + 116 + 97 + 110 + 99 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 73 + 110 + 115 + 116 + 97 + 110 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 84 + 114 + 111 + 99 + 107 + 101 + 110 + 111 + 102 + 101 + 110 + 82 + 101 + 109 + 111 + 116 + 101 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 39 + 44 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 122 + 101 + 114 + 111 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 110 + 101 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 100 + 101 + 102 + 97 + 117 + 108 + 116 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 40 + 100 + 117 + 109 + 109 + 121 + 41 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 122 + 101 + 114 + 111 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 72 + 101 + 114 + 101 + 32 + 97 + 32 + 115 + 116 + 97 + 116 + 101 + 45 + 109 + 97 + 99 + 104 + 105 + 110 + 101 + 32 + 105 + 115 + 32 + 105 + 109 + 112 + 108 + 101 + 109 + 101 + 110 + 116 + 101 + 100 + 32 + 116 + 104 + 97 + 116 + 32 + 109 + 97 + 121 + 32 + 98 + 101 + 32 + 117 + 115 + 101 + 100 + 32 + 116 + 111 + 32 + 105 + 109 + 112 + 108 + 101 + 109 + 101 + 110 + 116 + 32 + 115 + 111 + 109 + 101 + 32 + 97 + 117 + 116 + 111 + 109 + 97 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 108 + 111 + 103 + 105 + 99 + 32 + 116 + 104 + 97 + 116 + 32 + 105 + 115 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 32 + 117 + 115 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 101 + 109 + 98 + 101 + 100 + 100 + 101 + 100 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 105 + 110 + 116 + 101 + 114 + 112 + 114 + 101 + 116 + 101 + 114 + 46 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 73 + 110 + 32 + 116 + 104 + 105 + 115 + 32 + 101 + 120 + 97 + 109 + 112 + 108 + 101 + 44 + 32 + 97 + 32 + 99 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 114 + 117 + 110 + 32 + 115 + 117 + 99 + 99 + 101 + 101 + 100 + 101 + 100 + 32 + 98 + 121 + 32 + 116 + 104 + 101 + 32 + 100 + 101 + 115 + 105 + 103 + 110 + 47 + 99 + 111 + 109 + 112 + 105 + 108 + 97 + 116 + 105 + 111 + 110 + 47 + 101 + 120 + 101 + 99 + 117 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 111 + 102 + 32 + 97 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 45 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 105 + 115 + 32 + 105 + 109 + 112 + 108 + 101 + 109 + 101 + 110 + 116 + 101 + 100 + 46 + 32 + 84 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 115 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 105 + 110 + 32 + 101 + 97 + 99 + 104 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 97 + 114 + 101 + 32 + 108 + 111 + 97 + 100 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 97 + 116 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 115 + 101 + 108 + 101 + 99 + 116 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 39 + 59 + 32 + 47 + 47 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 32 + 97 + 110 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 116 + 111 + 32 + 112 + 101 + 114 + 102 + 111 + 114 + 109 + 32 + 97 + 110 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 49 + 32 + 61 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 105 + 110 + 49 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 58 + 32 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 65 + 100 + 100 + 32 + 97 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 32 + 102 + 111 + 114 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 73 + 110 + 112 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 111 + 109 + 101 + 32 + 109 + 111 + 114 + 101 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 65 + 80 + 97 + 114 + 86 + 101 + 99 + 116 + 111 + 114 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 49 + 48 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 65 + 32 + 118 + 101 + 99 + 116 + 111 + 114 + 105 + 97 + 108 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 112 + 97 + 114 + 50 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 50 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 84 + 101 + 115 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 32 + 116 + 104 + 101 + 115 + 101 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 73 + 110 + 112 + 117 + 116 + 44 + 32 + 39 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 32 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 112 + 97 + 114 + 50 + 44 + 32 + 39 + 84 + 101 + 115 + 116 + 32 + 39 + 44 + 32 + 50 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 65 + 80 + 97 + 114 + 86 + 101 + 99 + 116 + 111 + 114 + 44 + 32 + 39 + 65 + 32 + 118 + 101 + 99 + 116 + 111 + 114 + 105 + 97 + 108 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 39 + 44 + 32 + 49 + 48 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 84 + 104 + 101 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 116 + 111 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 84 + 95 + 97 + 32 + 61 + 32 + 48 + 46 + 49 + 59 + 91 + 115 + 105 + 109 + 44 + 120 + 44 + 118 + 93 + 32 + 61 + 32 + 100 + 97 + 109 + 112 + 101 + 100 + 95 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 73 + 110 + 112 + 117 + 116 + 44 + 32 + 84 + 95 + 97 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 116 + 114 + 101 + 97 + 109 + 32 + 116 + 104 + 101 + 32 + 100 + 97 + 116 + 97 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 120 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 88 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 105 + 100 + 101 + 110 + 116 + 105 + 102 + 105 + 101 + 114 + 46 + 118 + 97 + 108 + 117 + 101 + 32 + 61 + 32 + 39 + 80 + 108 + 111 + 116 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 99 + 111 + 110 + 102 + 105 + 103 + 46 + 115 + 105 + 122 + 101 + 46 + 118 + 97 + 108 + 117 + 101 + 32 + 61 + 32 + 39 + 40 + 51 + 48 + 48 + 44 + 51 + 48 + 48 + 41 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 99 + 111 + 110 + 102 + 105 + 103 + 46 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 + 46 + 118 + 97 + 108 + 117 + 101 + 32 + 61 + 32 + 39 + 40 + 51 + 48 + 48 + 44 + 48 + 41 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 99 + 111 + 110 + 102 + 105 + 103 + 46 + 110 + 97 + 109 + 101 + 46 + 118 + 97 + 108 + 117 + 101 + 32 + 61 + 32 + 39 + 80 + 108 + 111 + 116 + 32 + 88 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 83 + 117 + 98 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 98 + 108 + 111 + 99 + 107 + 32 + 61 + 32 + 39 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 101 + 114 + 83 + 105 + 103 + 110 + 97 + 108 + 115 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 83 + 117 + 98 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 115 + 105 + 103 + 110 + 97 + 108 + 115 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 39 + 88 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 118 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 86 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 109 + 109 + 117 + 110 + 105 + 99 + 97 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 70 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 69 + 120 + 112 + 111 + 114 + 116 + 95 + 106 + 115 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 102 + 110 + 97 + 109 + 101 + 61 + 39 + 80 + 114 + 111 + 116 + 111 + 99 + 111 + 108 + 108 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 106 + 115 + 111 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 87 + 97 + 105 + 116 + 32 + 117 + 110 + 116 + 105 + 108 + 32 + 97 + 32 + 110 + 117 + 109 + 98 + 101 + 114 + 32 + 111 + 102 + 32 + 116 + 105 + 109 + 101 + 32 + 115 + 116 + 101 + 112 + 115 + 32 + 104 + 97 + 115 + 32 + 112 + 97 + 115 + 115 + 101 + 100 + 44 + 32 + 116 + 104 + 101 + 110 + 32 + 110 + 111 + 116 + 105 + 102 + 121 + 32 + 116 + 104 + 97 + 116 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 98 + 121 + 32 + 115 + 101 + 116 + 116 + 105 + 110 + 103 + 32 + 34 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 34 + 32 + 116 + 111 + 32 + 49 + 46 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 116 + 101 + 112 + 115 + 50 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 105 + 111 + 110 + 95 + 115 + 105 + 109 + 115 + 116 + 101 + 112 + 115 + 61 + 78 + 83 + 97 + 109 + 112 + 108 + 101 + 115 + 44 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 61 + 91 + 48 + 44 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 49 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 104 + 111 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 116 + 111 + 32 + 101 + 110 + 116 + 101 + 114 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 99 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 39 + 59 + 32 + 47 + 47 + 32 + 100 + 101 + 115 + 105 + 103 + 110 + 32 + 97 + 32 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 32 + 116 + 111 + 32 + 115 + 101 + 110 + 100 + 32 + 116 + 111 + 32 + 80 + 97 + 80 + 105 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 49 + 32 + 61 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 105 + 110 + 49 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 58 + 32 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 116 + 114 + 101 + 97 + 109 + 32 + 97 + 32 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 105 + 110 + 49 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 67 + 111 + 110 + 115 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 105 + 100 + 101 + 110 + 116 + 105 + 102 + 105 + 101 + 114 + 46 + 118 + 97 + 108 + 117 + 101 + 32 + 61 + 32 + 39 + 80 + 108 + 111 + 116 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 99 + 111 + 110 + 102 + 105 + 103 + 46 + 115 + 105 + 122 + 101 + 46 + 118 + 97 + 108 + 117 + 101 + 32 + 61 + 32 + 39 + 40 + 51 + 48 + 48 + 44 + 51 + 48 + 48 + 41 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 99 + 111 + 110 + 102 + 105 + 103 + 46 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 + 46 + 118 + 97 + 108 + 117 + 101 + 32 + 61 + 32 + 39 + 40 + 51 + 48 + 48 + 44 + 48 + 41 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 99 + 111 + 110 + 102 + 105 + 103 + 46 + 110 + 97 + 109 + 101 + 46 + 118 + 97 + 108 + 117 + 101 + 32 + 61 + 32 + 39 + 80 + 108 + 111 + 116 + 32 + 67 + 111 + 110 + 115 + 116 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 83 + 117 + 98 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 98 + 108 + 111 + 99 + 107 + 32 + 61 + 32 + 39 + 83 + 111 + 117 + 114 + 99 + 101 + 71 + 114 + 111 + 117 + 112 + 50 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 84 + 111 + 83 + 117 + 98 + 46 + 112 + 108 + 111 + 116 + 95 + 88 + 46 + 115 + 105 + 103 + 110 + 97 + 108 + 115 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 39 + 67 + 111 + 110 + 115 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 109 + 109 + 117 + 110 + 105 + 99 + 97 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 70 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 69 + 120 + 112 + 111 + 114 + 116 + 95 + 106 + 115 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 102 + 110 + 97 + 109 + 101 + 61 + 39 + 80 + 114 + 111 + 116 + 111 + 99 + 111 + 108 + 108 + 67 + 111 + 110 + 102 + 105 + 103 + 46 + 106 + 115 + 111 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 87 + 97 + 105 + 116 + 32 + 117 + 110 + 116 + 105 + 108 + 32 + 97 + 32 + 110 + 117 + 109 + 98 + 101 + 114 + 32 + 111 + 102 + 32 + 116 + 105 + 109 + 101 + 32 + 115 + 116 + 101 + 112 + 115 + 32 + 104 + 97 + 115 + 32 + 112 + 97 + 115 + 115 + 101 + 100 + 44 + 32 + 116 + 104 + 101 + 110 + 32 + 110 + 111 + 116 + 105 + 102 + 121 + 32 + 116 + 104 + 97 + 116 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 40 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 105 + 110 + 32 + 116 + 104 + 105 + 115 + 32 + 99 + 97 + 115 + 101 + 41 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 46 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 116 + 101 + 112 + 115 + 50 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 105 + 111 + 110 + 95 + 115 + 105 + 109 + 115 + 116 + 101 + 112 + 115 + 61 + 78 + 83 + 97 + 109 + 112 + 108 + 101 + 115 + 44 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 61 + 91 + 48 + 44 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 50 + 50 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 104 + 111 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 116 + 111 + 32 + 101 + 110 + 116 + 101 + 114 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 100 + 101 + 109 + 111 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 39 + 59 + 32 + 47 + 47 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 45 + 32 + 110 + 111 + 116 + 104 + 105 + 110 + 103 + 32 + 116 + 111 + 32 + 100 + 111 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 49 + 32 + 61 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 105 + 110 + 49 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 48 + 41 + 59 + 47 + 47 + 32 + 110 + 101 + 118 + 101 + 114 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 51 + 51 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 47 + 47 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 61 + 32 + 37 + 116 + 10 + 32 + 32 + 10 + 32 + 32 + 47 + 47 + 32 + 87 + 104 + 101 + 110 + 32 + 82 + 84 + 109 + 97 + 105 + 110 + 46 + 115 + 99 + 101 + 32 + 105 + 115 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 100 + 44 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 97 + 114 + 116 + 32 + 119 + 105 + 108 + 108 + 32 + 98 + 101 + 32 + 114 + 117 + 110 + 46 + 32 + 73 + 116 + 32 + 109 + 97 + 121 + 32 + 98 + 101 + 32 + 117 + 115 + 101 + 100 + 32 + 116 + 111 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 32 + 97 + 110 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 105 + 110 + 32 + 97 + 100 + 118 + 97 + 110 + 99 + 101 + 32 + 116 + 111 + 10 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 105 + 111 + 110 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 119 + 104 + 111 + 108 + 101 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 46 + 10 + 32 + 32 + 105 + 102 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 61 + 32 + 37 + 102 + 32 + 116 + 104 + 101 + 110 + 32 + 10 + 32 + 32 + 32 + 32 + 83 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 73 + 110 + 102 + 111 + 32 + 61 + 32 + 39 + 79 + 102 + 102 + 45 + 108 + 105 + 110 + 101 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 100 + 101 + 102 + 97 + 117 + 108 + 116 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 40 + 100 + 117 + 109 + 109 + 121 + 41 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 116 + 101 + 112 + 115 + 50 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 105 + 111 + 110 + 95 + 115 + 105 + 109 + 115 + 116 + 101 + 112 + 115 + 61 + 49 + 48 + 44 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 61 + 91 + 48 + 44 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 118 + 101 + 99 + 115 + 105 + 122 + 101 + 61 + 49 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 118 + 101 + 99 + 115 + 105 + 122 + 101 + 61 + 50 + 48 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 49 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 50 + 53 + 55 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 49 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 50 + 53 + 55 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 61 + 39 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 39 + 32 + 10 + 59 + 10 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 52 + 41 + 59 + 10 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 115 + 99 + 105 + 108 + 97 + 98 + 32 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 97 + 108 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 92 + 110 + 39 + 41 + 59 + 32 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 112 + 116 + 114 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 112 + 116 + 114 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 112 + 116 + 114 + 40 + 49 + 41 + 32 + 61 + 32 + 115 + 99 + 105 + 108 + 97 + 98 + 95 + 105 + 110 + 116 + 101 + 114 + 102 + 46 + 105 + 110 + 118 + 101 + 99 + 49 + 59 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 49 + 41 + 59 + 32 + 115 + 99 + 105 + 108 + 97 + 98 + 95 + 105 + 110 + 116 + 101 + 114 + 102 + 46 + 111 + 117 + 116 + 118 + 101 + 99 + 49 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 112 + 116 + 114 + 40 + 49 + 41 + 59 + 32 + 10 + 10 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 53 + 41 + 59 + 10 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 115 + 99 + 105 + 108 + 97 + 98 + 32 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 97 + 108 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 100 + 101 + 115 + 116 + 114 + 117 + 99 + 116 + 101 + 100 + 92 + 110 + 39 + 41 + 59 + 32 + 10 + 99 + 108 + 101 + 97 + 114 + 32 + 98 + 108 + 111 + 99 + 107 + 59 + 10 + 66 + 85 + 73 + 76 + 68 + 73 + 78 + 95 + 80 + 65 + 84 + 72 + 0 + 170 + 205 + 38 + 0 + 1 + 0 + 20 + 36 + 84 + 104 + 101 + 32 + 102 + 114 + 111 + 109 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 114 + 101 + 116 + 117 + 114 + 110 + 101 + 100 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 32 + 97 + 114 + 101 + 32 + 40 + 206 + 0 + 1 + 1 + 0 + 40 + 208 + 0 + 1 + 1 + 0 + 60009 + 210 + 2 + 0 + 1 + 0 + 20 + 257 + 60009 + 212 + 2 + 0 + 1 + 0 + 20 + 257 + 40 + 214 + 0 + 1 + 1 + 0 + 15003 + 216 + 124 + 0 + 1 + 0 + 0 + 0 + 0 + 41 + 41 + 36 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 105 + 112 + 97 + 114 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 114 + 112 + 97 + 114 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 0 + 9 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 203 + 203 + 0 + 0 + 210 + 210 + 0 + 0 + 206 + 206 + 0 + 0 + 210 + 210 + 1 + 0 + 203 + 203 + 0 + 0 + 212 + 212 + 0 + 0 + 208 + 208 + 0 + 0 + 212 + 212 + 1 + 0 + 210 + 210 + 0 + 0 + 216 + 216 + 0 + 0 + 214 + 214 + 0 + 0 + 216 + 216 + 1 + 0 + 212 + 212 + 0 + 1 + 0 + 0 + 0 + 4 + 60032 + 208 + 0 + 0 + 1 + 0 + 60006 + 210 + 1 + 0 + 1 + 0 + 2 + 60002 + 212 + 2 + 0 + 1 + 0 + 2 + 0 + 60014 + 215 + 0 + 0 + 1 + 0 + 170 + 217 + 22 + 0 + 1 + 0 + 1 + 20 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 95 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 170 + 218 + 25 + 0 + 1 + 0 + 1 + 23 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 95 + 78 + 111 + 116 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 40 + 219 + 0 + 1 + 1 + 0 + 170 + 221 + 18 + 0 + 1 + 0 + 1 + 16 + 99 + 97 + 108 + 99 + 117 + 108 + 97 + 116 + 105 + 110 + 103 + 32 + 46 + 46 + 46 + 32 + 40 + 222 + 0 + 1 + 1 + 0 + 40 + 224 + 0 + 1 + 1 + 0 + 60020 + 226 + 0 + 1 + 1 + 0 + 60020 + 228 + 0 + 1 + 1 + 0 + 60020 + 230 + 0 + 1 + 1 + 0 + 60020 + 232 + 0 + 1 + 1 + 0 + 0 + 19 + 8 + 4 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 205 + 205 + 0 + 0 + 208 + 208 + 0 + 0 + 208 + 208 + 0 + 0 + 210 + 210 + 0 + 0 + 210 + 210 + 0 + 0 + 212 + 212 + 0 + 0 + 205 + 205 + 1 + 0 + 215 + 215 + 0 + 0 + 205 + 205 + 1 + 0 + 217 + 217 + 0 + 0 + 215 + 215 + 0 + 0 + 218 + 218 + 0 + 0 + 219 + 219 + 0 + 0 + 221 + 221 + 0 + 0 + 212 + 212 + 0 + 0 + 226 + 226 + 0 + 0 + 224 + 224 + 0 + 0 + 226 + 226 + 1 + 0 + 212 + 212 + 1 + 0 + 228 + 228 + 0 + 0 + 226 + 226 + 0 + 0 + 228 + 228 + 1 + 0 + 215 + 215 + 0 + 0 + 230 + 230 + 0 + 0 + 228 + 228 + 0 + 0 + 230 + 230 + 1 + 0 + 219 + 219 + 0 + 0 + 232 + 232 + 0 + 0 + 230 + 230 + 0 + 0 + 232 + 232 + 1 + 0 + 222 + 222 + 0 + 1 + 0 + 0 + 0 + 0 + 232 + 232 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 2 + 1 + 3 + 203 + 100 + 0 + 0 + 8 + 1 + 205 + 100 + 8 + 1 + 6 + 1 + 100 + 101 + 14 + 2 + 28 + 0 + 60010 + 203 + 2 + 1 + 1 + 0 + 1 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 0 + 3 + 8 + 4 + 0 + 203 + 203 + 0 + 1 + 0 + 0 + 0 + 0 + 205 + 205 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 2 + 170 + 211 + 9 + 0 + 1 + 0 + 1 + 7 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 0 + 3 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 207 + 207 + 0 + 0 + 207 + 207 + 0 + 0 + 211 + 211 + 0 + 26 + 0 + 1 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar new file mode 100644 index 00000000..0bd666ad --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar @@ -0,0 +1,26 @@ +0.2000000000000000111022302 +1.2339999999999999857891453 +1.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +0.0000000000000000000000000 +2.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +2.0000000000000000000000000 +2.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +3.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce new file mode 100644 index 00000000..fc44f6f8 --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce @@ -0,0 +1,342 @@ +// +// Copyright (C) 2011, 2012, 2013, 2014, 2015 Christian Klauer +// +// This file is part of OpenRTDynamics, the Real-Time Dynamics Framework +// +// OpenRTDynamics is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// OpenRTDynamics is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with OpenRTDynamics. If not, see . +// +funcprot(0); +// The name of the program +ProgramName = 'SelectCasePaPiConfig'; // must be the filename without .sce + + +function [sim, outlist] = AutoConfigExample(sim, Signal) + + function [sim, finished, outlist, userdata] = ExperimentCntrl(sim, ev, inlist, userdata, CalledOnline) + + // Define parameters. They must be defined once again at this place, because this will also be called at + // runtime. + NSamples=50; + + if CalledOnline == %t then + // The contents of this part will be compiled on-line, while the control + // system is running. The aim is to generate a new compiled schematic for + // the experiment. + // Please note: Since this code is only executed on-line, most potential errors + // occuring in this part become only visible during runtime. + + printf("Compiling a new control system\n"); + exec('webinterface/PacketFramework.sce'); + funcprot(0); + z = poly(0,'z'); + // And example-system that is controlled via UDP and one step further with the Web-gui + // Superblock: A more complex oscillator with damping + function [sim, x,v] = damped_oscillator(sim, u, T_a) + // create feedback signals + [sim,x_feedback] = libdyn_new_feedback(sim); + + [sim,v_feedback] = libdyn_new_feedback(sim); + + // use this as a normal signal + [sim,a] = ld_add(sim, ev, list(u, x_feedback), [1, -1]); + [sim,a] = ld_add(sim, ev, list(a, v_feedback), [1, -1]); + + [sim,v] = ld_ztf(sim, ev, a, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,v_gain] = ld_gain(sim, ev, v, 0.1); + + // close loop v_gain = v_feedback + [sim] = libdyn_close_loop(sim, v_gain, v_feedback); + + + [sim,x] = ld_ztf(sim, ev, v, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,x_gain] = ld_gain(sim, ev, x, 0.6); + + // close loop x_gain = x_feedback + [sim] = libdyn_close_loop(sim, x_gain, x_feedback); + endfunction + + if userdata.isInitialised == %f then + // + // State variables can be initialise at this place + // + userdata.Acounter = 0; + userdata.State = "oscillator"; + + userdata.isInitialised = %t; // prevent from initialising the variables once again + end + + // + // Example for a state update: increase the counter + // + userdata.Acounter = userdata.Acounter + 1; + + // Build an info-string + SchematicInfo = "On-line compiled in iteration #" + string(userdata.Acounter); + + // + // Define a new experiment controller schematic depending on the currently active state + // + + // initalise the PaPi UDP Socket + Configuration.UnderlyingProtocoll = "UDP"; + Configuration.DestHost = "127.0.0.1"; + Configuration.DestPort = 20000; + Configuration.LocalSocketHost = "127.0.0.1"; + Configuration.LocalSocketPort = 20001; + PacketFramework.Configuration.debugmode = %t; + [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="TrockenofenRemoteControl", Configuration); + + [sim, zero] = ld_const(sim, ev, 0); + [sim, one] = ld_const(sim, 0, 1); + + // default output (dummy) + outlist=list(zero); + + // + // Here a state-machine is implemented that may be used to implement some automation + // logic that is executed during runtime using the embedded Scilab interpreter. + // In this example, a calibration run succeeded by the design/compilation/execution + // of a control-system is implemented. The schematics defined in each state are loaded + // at runtime. + // + select userdata.State + case "oscillator" // define an experiment to perform an oscillator experiment + in1 = inlist(1); + [sim] = ld_printf(sim, 0, in1, "Case oscillator active: ", 1); + + // Add a parameter for controlling the oscillator + [sim, PacketFramework, Input]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Oscillator input"); + + // some more parameters + [sim, PacketFramework, AParVector]=ld_PF_Parameter(sim, PacketFramework, NValues=10, datatype=ORTD.DATATYPE_FLOAT, ParameterName="A vectorial parameter"); + [sim, PacketFramework, par2]=ld_PF_Parameter(sim, PacketFramework, NValues=2, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Test"); + + // printf these parameters + [sim] = ld_printf(sim, ev, Input, "Oscillator input ", 1); + [sim] = ld_printf(sim, ev, par2, "Test ", 2); + [sim] = ld_printf(sim, ev, AParVector, "A vectorial parameter", 10); + + // The system to control + T_a = 0.1; [sim, x,v] = damped_oscillator(sim, Input, T_a); + + // Stream the data of the oscillator + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X"); + PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'Plot'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot X'; + PacketFramework.PaPIConfig.ToSub.plot_X.block = 'ControllerSignals'; + PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('X'); + + + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V"); + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); + ld_PF_Export_js(PacketFramework, fname="ProtocollConfig.json"); + + // Wait until a number of time steps has passed, then notify that + // the experiment has finished by setting "finished" to 1. + [sim, finished] = ld_steps2(sim, ev, activation_simsteps=NSamples, values=[0,1] ); + + [sim, out] = ld_const(sim, 0, 11); + outlist=list(out); + + // chose the next state to enter when the calibration experiment has finished + userdata.State = "constant"; + + case "constant" // design a constant signal to send to PaPi + in1 = inlist(1); + [sim] = ld_printf(sim, 0, in1, "Case constant active: ", 1); + + // Stream a constant + [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=in1, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Const"); + PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'Plot'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot Const'; + PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup2'; + PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('Const'); + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim, PacketFramework); + ld_PF_Export_js(PacketFramework, fname="ProtocollConfig.json"); + + // Wait until a number of time steps has passed, then notify that + // the experiment (control system in this case) has finished. + [sim, finished] = ld_steps2(sim, ev, activation_simsteps=NSamples, values=[0,1] ); + + [sim, out] = ld_const(sim, 0, 22); + outlist=list(out); + + // chose the next state to enter when the demo experiment has finished + userdata.State = "finished"; + + case "finished" // experiment finished - nothing to do + in1 = inlist(1); + [sim] = ld_printf(sim, ev, in1, "Case finished active" , 1); + [sim, finished] = ld_const(sim, ev, 0); // never finish + [sim, out] = ld_const(sim, 0, 33); + outlist=list(out); + + end + end // CalledOnline == %t + + // When RTmain.sce is executed, this part will be run. It may be used to define an initial experiment in advance to + // the execution of the whole control system. + if CalledOnline == %f then + SchematicInfo = "Off-line compiled"; + + // default output (dummy) + [sim, out] = ld_const(sim, 0, 0); + outlist=list(out); + [sim, finished] = ld_steps2(sim, 0, activation_simsteps=10, values=[0,1] ); + end + + endfunction + + + + + function [sim, outlist, HoldState, userdata] = whileComputing_example(sim, ev, inlist, CalibrationReturnVal, computation_finished, par); + + [sim, HoldState] = ld_const(sim, 0, 0); + + [sim] = ld_printf(sim, 0, HoldState, "calculating ... " , 1); + + // While the computation is running this is called regularly + [sim, out] = ld_const(sim, ev, 0); + outlist=list(out); + endfunction + + + function [sim, ToScilab, userdata] = PreScilabRun(sim, ev, par) + userdata = par.userdata; + + // get the stored sensor data + [sim, ToScilab] = ld_const(sim, ev, 0); + endfunction + + + // Start the experiment + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; + ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; + + insizes=[1]; outsizes=[1]; + intypes=[ORTD.DATATYPE_FLOAT]; outtypes=[ORTD.DATATYPE_FLOAT]; + + + CallbackFns.experiment = ExperimentCntrl; + CallbackFns.whileComputing = whileComputing_example; + CallbackFns.PreScilabRun = PreScilabRun; + + // Please note ident_str must be unique. + userdata = []; + [sim, finished, outlist, userdata] = ld_AutoOnlineExch_dev(sim, 0, inlist=list(Signal), ... + insizes, outsizes, intypes, outtypes, ... + ThreadPrioStruct, CallbackFns, ident_str="AutoConfigDemo", userdata); + + PacketFramework = userdata(1); +// [sim] = ld_printf(sim, 0, finished, "State ", 1); + +endfunction + + +// The main real-time thread +function [sim, outlist, userdata] = Thread_MainRT(sim, inlist, userdata) + // This will run in a thread + [sim, Tpause] = ld_const(sim, ev, 1/5); // The sampling time that is constant at 20 Hz in this example + [sim, out] = ld_ClockSync(sim, ev, in=Tpause); // synchronise this simulation + + // + // Add you own control system here + // + + // some dummy input to the state machine + [sim,Signal] = ld_const(sim, 0, 1.234); + + [sim, outlist] = AutoConfigExample(sim, Signal); + + [sim] = ld_printf(sim, 0, outlist(1) , "output ", 1); + + + outlist = list(); +endfunction + +// This is the main top level schematic +function [sim, outlist] = schematic_fn(sim, inlist) + +// +// Create a thread that runs the control system +// + + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; // or ORTD.ORTD_RT_REALTIMETASK + ThreadPrioStruct.prio2=0; // for ORTD.ORTD_RT_REALTIMETASK: 1-99 as described in man sched_setscheduler + // for ORTD.ORTD_RT_NORMALTASK this is the nice-value (higher value means less priority) + ThreadPrioStruct.cpu = -1; // The CPU on which the thread will run; -1 dynamically assigns to a CPU, + // counting of the CPUs starts at 0 + + [sim, StartThread] = ld_initimpuls(sim, ev); // triggers your computation only once + [sim, outlist, computation_finished] = ld_async_simulation(sim, ev, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = Thread_MainRT, ... + TriggerSignal=StartThread, name="MainRealtimeThread", ... + ThreadPrioStruct, userdata=list() ); + + + // output of schematic (empty) + outlist = list(); +endfunction + + + + + + + + + + +// +// Set-up (no detailed understanding necessary) +// + +thispath = get_absolute_file_path(ProgramName+'.sce'); +cd(thispath); +z = poly(0,'z'); + +exec('webinterface/PacketFramework.sce'); + +// defile ev +ev = [0]; // main event + +// set-up schematic by calling the user defined function "schematic_fn" +insizes = []; outsizes=[]; +[sim_container_irpar, sim]=libdyn_setup_schematic(schematic_fn, insizes, outsizes); + +// pack the simulation into a irpar container +parlist = new_irparam_set(); +parlist = new_irparam_container(parlist, sim_container_irpar, 901); // pack simulations into irpar container with id = 901 +par = combine_irparam(parlist); // complete irparam set +save_irparam(par, ProgramName+'.ipar', ProgramName+'.rpar'); // Save the schematic to disk + +// clear +par.ipar = []; par.rpar = []; + diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh b/data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh new file mode 100755 index 00000000..c0a83aea --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +#ortd --baserate=10 --rtmode 1 -s SelectCasePaPiConfig -i 901 -l 0 +ortdrun -s SelectCasePaPiConfig diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce new file mode 100644 index 00000000..a08a9981 --- /dev/null +++ b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce @@ -0,0 +1,1202 @@ + + +// Interfacing functions are placed in this place + +function [sim, out] = ld_udp_main_receiver(sim, events, udpport, identstr, socket_fname, vecsize) // PARSEDOCU_BLOCK + // udp main receiver - block + // + // This is a simulation-synchronising Block + // + // EXPERIMENTAL FIXME: REMOVE + // + + datatype = ORTD.DATATYPE_FLOAT; + + btype = 39001; + [sim,blk] = libdyn_new_block(sim, events, btype, ipar=[ udpport, vecsize, datatype, length(socket_fname), ascii(socket_fname), length(identstr), ascii(identstr) ], rpar=[ ], ... + insizes=[], outsizes=[vecsize], ... + intypes=[], outtypes=[ORTD.DATATYPE_FLOAT] ); + + //[sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); + [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction + + + + +function [sim] = ld_UDPSocket_shObj(sim, events, ObjectIdentifyer, Visibility, hostname, UDPPort) // PARSEDOCU_BLOCK + // + // Set-up an UDP-Socket + // + // hostname - Network interface to bind socket to??? + // UDPPort - UDP port to bind. If -1 then no UDP server is set-up + // + // EXPERIMENTAL + // + + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + + + + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); + + parlist = new_irparam_elemet_ivec(parlist, UDPPort, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 11); // id = 11; A string parameter + + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + + // Set-up the block parameters. There are no I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 0; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + [sim] = libdyn_CreateSharedObjBlk(sim, btype, ObjectIdentifyer, Visibility, Uipar, Urpar); +endfunction + +function [sim] = ld_UDPSocket_Send(sim, events, ObjectIdentifyer, in, insize, intype) // PARSEDOCU_BLOCK + // + // UDP - Send block + // + // in *, ORTD.DATATYPE_BINARY - input + // + // EXPERIMENTAL, About to be removed + // + + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + + + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); + + parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + + // Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 1; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + insizes=[insize]; // Input port sizes + outsizes=[]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[intype]; // datatype for each input port + outtypes=[]; // datatype for each output port + + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); + + // connect the inputs + [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 + + // // connect the ouputs + // [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction + + +function [sim, out, SrcAddr] = ld_UDPSocket_Recv(sim, events, ObjectIdentifyer, outsize) // PARSEDOCU_BLOCK + // + // UDP - receiver block + // + // out *, ORTD.DATATYPE_BINARY - output + // SrcAddr - information about where the package comes from (not implemented) + // + // This is a simulation-synchronising Block. Everytime an UDP-Packet is received, + // the simulation that contains this blocks goes on for one step. + // + // EXPERIMENTAL + // + + printf("Synchronising simulation to UDP-Receiver\n"); + + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + + // + outtype = ORTD.DATATYPE_BINARY; + + // IPv4 + AddrSize = 4+2; // IPnumber + port + + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); + + parlist = new_irparam_elemet_ivec(parlist, outsize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, outtype, 11); // id = 11 + + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + + // Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 2; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + insizes=[]; // Input port sizes + outsizes=[outsize, AddrSize]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[]; // datatype for each input port + outtypes=[outtype, ORTD.DATATYPE_BINARY]; // datatype for each output port + + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); + + // // connect the inputs + // [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 + + // connect the ouputs + [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port + [sim,SrcAddr] = libdyn_new_oport_hint(sim, blk, 1); // 1th port +endfunction + +function [sim] = ld_UDPSocket_SendTo(sim, events, SendSize, ObjectIdentifyer, hostname, UDPPort, in, insize) // PARSEDOCU_BLOCK + // + // UDP - Send block + // + // in *, ORTD.DATATYPE_BINARY - input + // SendSize *. ORTD.DATATYPE_INT32 - Number of bytes to send + // + // EXPERIMENTAL + // + + + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + + // only send binary data + intype=ORTD.DATATYPE_BINARY; + + + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); + + parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + + parlist = new_irparam_elemet_ivec(parlist, UDPPort, 12); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 13); // id = 11; A string parameter + + + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + + // Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 3; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + insizes=[insize, 1]; // Input port sizes + outsizes=[]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[intype, ORTD.DATATYPE_INT32 ]; // datatype for each input port + outtypes=[]; // datatype for each output port + + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); + + // connect the inputs + [sim,blk] = libdyn_conn_equation(sim, blk, list(in, SendSize) ); // connect in1 to port 0 and in2 to port 1 + + // // connect the ouputs + // [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction + + + + + + +function [sim] = ld_UDPSocket_Reply(sim, events, SendSize, ObjectIdentifyer, DestAddr, in, insize) // PARSEDOCU_BLOCK + // + // UDP - Send block + // + // in *, ORTD.DATATYPE_BINARY - input + // SendSize *. ORTD.DATATYPE_INT32 - Number of bytes to send + // DestAddr - dynamic representation for the destination address + // + // EXPERIMENTAL not implemented by now + // + + + // add a postfix that identifies the type of the shared object + ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; + + // only send binary data + intype=ORTD.DATATYPE_BINARY; + + // IPv4 + AddrSize=4+2; + + // pack all parameters into a structure "parlist" + parlist = new_irparam_set(); + + parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + + // parlist = new_irparam_elemet_ivec(parlist, UDPPort, 12); // id = 10 + // parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 13); // id = 11; A string parameter + + + p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + + // Set-up the block parameters and I/O ports + Uipar = [ p.ipar ]; + Urpar = [ p.rpar ]; + btype = 39001 + 4; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + insizes=[insize, 1, AddrSize]; // Input port sizes + outsizes=[]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + intypes=[intype, ORTD.DATATYPE_INT32, ORTD.DATATYPE_BINARY ]; // datatype for each input port + outtypes=[]; // datatype for each output port + + blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); + + // connect the inputs + [sim,blk] = libdyn_conn_equation(sim, blk, list(in, SendSize, DestAddr) ); // connect in1 to port 0 and in2 to port 1 + + // // connect the ouputs + // [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction + + + + + + + + + + + + +function [sim, out, NBytes] = ld_ConcateData(sim, events, inlist, insizes, intypes) // PARSEDOCU_BLOCK + // + // Concate Data - block + // + // concatenates the binary representation of all inputs + // + // The output is of type ORTD.DATATYPE_BINARY + // + // EXPERIMENTAL + // + + + // pack all parameters into a structure "parlist" + // parlist = new_irparam_set(); + // + // parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + // parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + // + // p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + + // Set-up the block parameters and I/O ports + Uipar = [ ]; + Urpar = [ ]; + btype = 39001 + 10; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + // count the number of bytes + NBytes = 0; + for i = 1:length(inlist) + NBytes = NBytes + insizes(i) * libdyn_datatype_len( intypes(i) ); + end + + + + // insizes=[insizes]; // Input port sizes + outsizes=[ NBytes ]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + // intypes=[intypes]; // datatype for each input port + outtypes=[ ORTD.DATATYPE_BINARY ]; // datatype for each output port + + // disp(outsizes); + // disp(outtypes); + + blocktype = 2; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed); + + // connect the inputs + // for i = 1:length(inlist) + [sim,blk] = libdyn_conn_equation(sim, blk, inlist ); // connect in1 to port 0 and in2 to port 1 + // end + + // // connect the ouputs + [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port +endfunction + + +function [sim, outlist] = ld_DisassembleData(sim, events, in, outsizes, outtypes) // PARSEDOCU_BLOCK + // + // disasseble Data - block + // + // disassemble the binary representation of the input, which is of type ORTD.DATATYPE_BINARY + // + // EXPERIMENTAL + // + + + // pack all parameters into a structure "parlist" + // parlist = new_irparam_set(); + // + // parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 + // parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 + // + // p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively + + // Set-up the block parameters and I/O ports + Uipar = [ ]; + Urpar = [ ]; + btype = 39001 + 11; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function + + // count the number of bytes + NBytes = 0; + for i = 1:length(outsizes) + NBytes = NBytes + outsizes(i)*libdyn_datatype_len( outtypes(i) ); + end + + + + // insizes=[insizes]; // Input port sizes + insizes=[ NBytes ]; // Output port sizes + dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) + // intypes=[intypes]; // datatype for each input port + intypes=[ ORTD.DATATYPE_BINARY ]; // datatype for each output port + + // disp(outsizes); + // disp(outtypes); + + blocktype = 2; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) + + // Create the block + [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed); + + // connect the inputs + // for i = 1:length(inlist) + [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 + // end + + // // connect the ouputs + outlist = list(); + for i = 1:length(outtypes) + [sim,outlist($+1)] = libdyn_new_oport_hint(sim, blk, i-1); // 0th port + end +endfunction + + + + + + + + + + +// +// +// A packet based communication interface from ORTD using UDP datagrams to e.g. +// nodejs. +// webappUDP.js is the counterpart that provides a web-interface +// +// Current Rev: 8 +// +// Revisions: +// +// 27.3.14 - possibility to reservate sources +// 3.4.14 - small re-arrangements +// 4.4.14 - Bugfixes +// 7.4.14 - Bugfix +// 12.6.14 - Bugfix +// 2.11.14 - Added group finalising packet +// + + +function [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName) + SourceID = PacketFramework.SourceID_counter; + + Source.SourceName = SourceName; + Source.SourceID = SourceID; + Source.NValues_send = NValues_send; + Source.datatype = datatype; + + // Add new source to the list + PacketFramework.Sources($+1) = Source; + + // inc counter + PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; +endfunction + +function [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName) + ParameterID = PacketFramework.Parameterid_counter; + + Parameter.ParameterName = ParameterName; + Parameter.ParameterID = ParameterID; + Parameter.NValues = NValues; + Parameter.datatype = datatype; + Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; + + // Add new source to the list + PacketFramework.Parameters($+1) = Parameter; + + // inc counters + PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; + PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; + + // return values + ParameterID = Parameter.ParameterID; + MemoryOfs = Parameter.MemoryOfs; +endfunction + +function [sim, PacketFramework, Parameter] = ld_PF_Parameter(sim, PacketFramework, NValues, datatype, ParameterName) // PARSEDOCU_BLOCK + // + // Define a parameter + // + // NValues - amount of data sets + // datatype - only ORTD.DATATYPE_FLOAT for now + // ParameterName - a unique string decribing the parameter + // + // + // + + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); + + // read data from global memory + [sim, readI] = ld_const(sim, 0, MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + datatype, NValues); +endfunction + + + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, SourceID); + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); + + printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + +endfunction + + + + +function [sim, PacketFramework] = ld_SendPacket(sim, PacketFramework, Signal, NValues_send, datatype, SourceName) // PARSEDOCU_BLOCK // PARSEDOCU_BLOCK + // + // Stream data - block + // + // Signal - the signal to stream + // NValues_send - the vector length of Signal + // datatype - only ORTD.DATATYPE_FLOAT by now + // SourceName - a unique string identifier descring the stream + // + // + // + + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); +endfunction + + + + +function [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName, Configuration) // PARSEDOCU_BLOCK + // + // Initialise an instance of the Packet Framework + // + // InstanceName - a unique string identifier for the instance + // Configuration must include the following properties: + // + // Configuration.UnderlyingProtocoll = "UDP" + // Configuration.DestHost + // Configuration.DestPort + // Configuration.LocalSocketHost + // Configuration.LocalSocketPort + // + // + // Example: + // + // + // Configuration.UnderlyingProtocoll = "UDP"; + // Configuration.DestHost = "127.0.0.1"; + // Configuration.DestPort = 20000; + // Configuration.LocalSocketHost = "127.0.0.1"; + // Configuration.LocalSocketPort = 20001; + // [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); + // + // + // + // Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations + // + // + + // initialise structure for sources + PacketFramework.InstanceName = InstanceName; + PacketFramework.Configuration = Configuration; + + PacketFramework.Configuration.debugmode = %F; + + // disp(Configuration.UnderlyingProtocoll) + + if Configuration.UnderlyingProtocoll == 'UDP' + null; + else + error("PacketFramework: Only UDP supported up to now"); + end + + // possible packet sizes for UDP + PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; + + // sources + PacketFramework.SourceID_counter = 0; + PacketFramework.Sources = list(); + + // parameters + PacketFramework.Parameterid_counter = 0; + PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory + PacketFramework.Parameters = list(); + + PacketFramework.SenderID = 1295793; + + // Open an UDP-Port in server mode + [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... + hostname=PacketFramework.Configuration.LocalSocketHost, ... + UDPPort=PacketFramework.Configuration.LocalSocketPort); +endfunction + + +// Send a signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Group ID + [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources + [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); + + // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + + + // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); +endfunction + + +// Send the newConfigAvailable Signal via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_SendNewConfigAvailableUDP(sim, PacketFramework) + InstanceName = PacketFramework.InstanceName; + [sim,one] = ld_const(sim, 0, 1); + + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -2); // -2 means a new config is available + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // Group ID + [sim, GroupID_] = ld_const(sim, 0, 0); // 0 (not used) + [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); + + // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + + + // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); +endfunction + +// Send the configuration via UDP, a simple protocoll is defined, internal function +function [sim] = ld_PF_SendConfigUDP(sim, PacketFramework) + InstanceName = PacketFramework.InstanceName; + + str_PF_Export = ld_PF_Export_str(PacketFramework); + strLength = length(str_PF_Export); + maxPacketLength = 1400; + maxPacketStrLength = maxPacketLength - 4*4; + nPackets = ceil(strLength/maxPacketStrLength); + + // Source ID + [sim, SourceID] = ld_const(sim, 0, -4); // -4 means configItem + [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); + + // nPackets + [sim, nPackets_] = ld_const(sim, 0, nPackets); // the number of config packets for the whole configuration + [sim, nPackets_int32] = ld_ceilInt32(sim, 0, nPackets_); + + // Sender ID + [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number + [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); + + for i=1:nPackets + // Packet counter, so the order of the network packages can be determined + [sim, Counter] = ld_const(sim, ev, i); + [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); + partBegin = (i-1)*maxPacketStrLength+1; + partEnd = i*maxPacketStrLength; + if (partEnd > strLength) + partEnd = strLength; + end + strPart_PF_Export = part(str_PF_Export, partBegin:partEnd); + sendSize = length(strPart_PF_Export); + [sim, partPF_Export_bin] = ld_const_bin(sim, ev, in=ascii(strPart_PF_Export)); + + // make a binary structure + [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... + inlist=list(SenderID_int32, Counter_int32, SourceID_int32, nPackets_int32, partPF_Export_bin), insizes=[1,1,1,1,sendSize], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_BINARY ] ); + + // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); + [sim] = ld_printf(sim, ev, SenderID, "SenderID", 1); + [sim] = ld_printf(sim, ev, SourceID, "Sent config packet number " + string(i) + " ", 1); + [sim] = ld_printf(sim, ev, Counter, "Packet Count ", 1); + [sim] = ld_printf(sim, ev, nPackets_, "Number of Packet ", 1); + // send to the network + [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to + [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... + hostname=PacketFramework.Configuration.DestHost, ... + UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... + insize=NBytes); + end + +endfunction + +// select case function for the different received PaPi Commands (SenderID) +function [sim, outlist, userdata] = SelectCasePaPiCmd(sim, inlist, Ncase, casename, userdata) + // This function is called multiple times -- once to define each case + // At runtime, all cases will become different nested simulations of + // which only one is active a a time. + + printf("Defining case %s (#%d) ...\n", casename, Ncase ); + + // define names for the first event in the simulation + events = 0; + + DisAsm_ = list(); + DisAsm_(4) = inlist(4); + [sim, DisAsm_(1)] = ld_Int32ToFloat(sim, 0, inlist(1) ); + [sim, DisAsm_(2)] = ld_Int32ToFloat(sim, 0, inlist(2) ); + [sim, DisAsm_(3)] = ld_Int32ToFloat(sim, 0, inlist(3) ); + PacketFramework = userdata(1); + ParameterMemory = PacketFramework.ParameterMemory; + TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + InstanceName = PacketFramework.InstanceName; + // print out some state information + //[sim] = ld_printf(sim, events, in=DisAsm_(3), str="case "+string(casename)+" with Ncase = "+string(Ncase)+" : SourceID", insize=1); + + // sample data for the output + [sim, dummy] = ld_const(sim, 0, 9999); + + // The signals "active_state" is used to indicate state switching: A value > 0 means the + // the state enumed by "active_state" shall be activated in the next time step. + // A value less or equal to zero causes the statemachine to stay in its currently active + // state + select Ncase + case 1 + [sim, memofs] = ld_ArrayInt32(sim, 0, array=ParameterMemory.MemoryOfs, in=inlist(3) ); + [sim, Nelements] = ld_ArrayInt32(sim, 0, array=ParameterMemory.Sizes, in=inlist(3) ); + + [sim, memofs_] = ld_Int32ToFloat(sim, 0, memofs ); + [sim, Nelements_] = ld_Int32ToFloat(sim, 0, Nelements ); + + if PacketFramework.Configuration.debugmode then + // print the contents of the packet + [sim] = ld_printf(sim, 0, DisAsm_(1), "DisAsm(1) (SenderID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(2), "DisAsm(2) (Packet Counter) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(3), "DisAsm(3) (SourceID) = ", 1); + [sim] = ld_printf(sim, 0, DisAsm_(4), "DisAsm(4) (Signal) = ", TotalElemetsPerPacket); + + [sim] = ld_printf(sim, 0, memofs_ , "memofs = ", 1); + [sim] = ld_printf(sim, 0, memofs_ , "Nelements = ", 1); + end + + // Store the input data into a shared memory + [sim] = ld_WriteMemory2(sim, 0, data=inlist(4), index=memofs, ElementsToWrite=Nelements, ... + ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); + + case 2 + [sim] = ld_printf(sim, 0, DisAsm_(3), "Give Config (SourceID) = ", 1); + [sim] = ld_PF_SendConfigUDP(sim, PacketFramework); + + case 3 + [sim] = ld_printf(sim, events, in=DisAsm_(3), str="case "+string(casename)+" : Wrong SourceID", insize=1); + end + + // the user defined output signals of this nested simulation + outlist = list(dummy); + userdata(1) = PacketFramework; +endfunction + +function [sim, outlist, userdata] = SelectCaseSendNewConfigAvailable(sim, inlist, Ncase, casename, userdata) + // This function is called multiple times -- once to define each case + // At runtime, all cases will become different nested simulations of + // which only one is active a a time. + + printf("Defining case %s (#%d) ...\n", casename, Ncase ); + + // define names for the first event in the simulation + events = 0; + + // pause; + + PacketFramework = userdata(1); + + // print out some state information + // [sim] = ld_printf(sim, events, in=in1, str="case"+string(casename)+": in1", insize=1); + + // sample data for the output + [sim, dummy] = ld_const(sim, 0, 999); + + // The signals "active_state" is used to indicate state switching: A value > 0 means the + // the state enumed by "active_state" shall be activated in the next time step. + // A value less or equal to zero causes the statemachine to stay in its currently active + // state + + select Ncase + case 1 // Finished + //[sim] = ld_printf(sim, events, dummy, "case "+string(casename)+" : Config already sent ", 1); + + case 2 // Send + //[sim] = ld_printf(sim, events, dummy, "case "+string(casename)+" : Config is sent to PaPi ", 1); + [sim] = ld_PF_SendNewConfigAvailableUDP(sim, PacketFramework); + + end + + // the user defined output signals of this nested simulation + outlist = list(dummy); + userdata(1) = PacketFramework; +endfunction + + +function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOCU_BLOCK + // + // Finalise the instance. + // + // + + // The main real-time thread + function [sim,PacketFramework] = ld_PF_InitUDP(sim, PacketFramework) + + function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) + // This will run in a thread. Each time a UDP-packet is received + // one simulation step is performed. Herein, the packet is parsed + // and the contained parameters are stored into a memory. + + PacketFramework = userdata(1); + + TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes + InstanceName = PacketFramework.InstanceName; + PacketSize = PacketFramework.PacketSize; + // Sync the simulation to incomming UDP-packets + [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); + + // disassemble packet's structure + [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... + outsizes=[1,1,1,TotalElemetsPerPacket], ... + outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); + + [sim, sourceID] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); + // [sim] = ld_printf(sim, 0, sourceID, "DisAsm(3) (SourceID) = ", 1); + [sim, selectSignal_checkGtZero] = ld_cond_overwrite(sim, ev, sourceID, sourceID, 1); + [sim, selectSignal_notCheckGtZero] = ld_not(sim, ev, sourceID); + [sim, selectSignal_checkMinThreeInt32] = ld_CompareEqInt32(sim, ev, DisAsm(3), -3); + [sim, selectSignal_checkMinThree] = ld_Int32ToFloat(sim, ev, selectSignal_checkMinThreeInt32); + [sim, selectSignal_checked] = ld_cond_overwrite(sim, ev, selectSignal_checkGtZero, selectSignal_checkMinThree, 2); + [sim, selectSignal_notCheckMinThree] = ld_not(sim, ev, selectSignal_checkMinThree); + [sim, selectSignal_undefined] = ld_and(sim, ev, list(selectSignal_notCheckGtZero, selectSignal_notCheckMinThree)); + [sim, selectSignal_checkedSecure] = ld_cond_overwrite(sim, ev, selectSignal_checked, selectSignal_undefined, 3); + [sim, selectSignal_checkedSecureInt32] = ld_roundInt32(sim, ev, selectSignal_checkedSecure); + + // set-up the states for each PaPi Command represented by nested simulations + [sim, outlist, userdataSelectCasePaPiCmd] = ld_CaseSwitchNest(sim, ev, ... + inlist=DisAsm, .. + insizes=[1,1,1,TotalElemetsPerPacket], outsizes=[1], ... + intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ], outtypes=[ORTD.DATATYPE_FLOAT], ... + CaseSwitch_fn=SelectCasePaPiCmd, SimnestName="SwitchSelectPaPiCmd", DirectFeedthrough=%t, SelectSignal=selectSignal_checkedSecureInt32, list("Param", "GiveConfig", "Undefined"), list(PacketFramework) ); + + PacketFramework = userdataSelectCasePaPiCmd(1); + + // output of schematic + outlist = list(); + userdata(1) = PacketFramework; + endfunction + + + + // start the node.js service from the subfolder webinterface + //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); + + TotalMemorySize = sum(PacketFramework.ParameterMemory.Sizes); + InstanceName = PacketFramework.InstanceName; + // // Open an UDP-Port in server mode + // [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... + // hostname=PacketFramework.Configuration.LocalSocketHost, ... + // UDPPort=PacketFramework.Configuration.LocalSocketPort); + + // initialise a global memory for storing the input data for the computation + [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... + datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... + initial_data=[zeros(TotalMemorySize,1)], ... + visibility='global', useMutex=1); + + // Create thread for the receiver + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; + [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step + [sim, outlist, computation_finished, userdata] = ld_async_simulation(sim, 0, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = UDPReceiverThread, ... + TriggerSignal=startcalc, name=InstanceName+"Thread1", ... + ThreadPrioStruct, userdata=list(PacketFramework) ); + + PacketFramework = userdata(1); + + endfunction + + + // calc memory + MemoryOfs = []; + Sizes = []; + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + + Sizes = [Sizes; P.NValues]; + MemoryOfs = [MemoryOfs; P.MemoryOfs]; + end + + PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; + PacketFramework.ParameterMemory.Sizes = Sizes; + + // udp + [sim] = ld_PF_InitUDP(sim, PacketFramework); + + // Send to group update notifications for each group (currently only one possible) + [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); + + + [sim, initSelect] = ld_initimpuls(sim, ev); + [sim, selectNewConfig] = ld_add_ofs(sim, ev, initSelect, 1); + //[sim] = ld_printf(sim, ev, selectNewConfig, ORTD.termcode.red + "Select New Config Signal" + ORTD.termcode.reset, 1); + [sim, selectNewConfigInt32] = ld_roundInt32(sim, ev, selectNewConfig); + // set-up the states to send a Signal to PaPi that a new config is available only in the first time step represented by nested simulations + [sim, outlist, userdata] = ld_CaseSwitchNest(sim, ev, ... + inlist=list(), .. + insizes=[], outsizes=[1], ... + intypes=[], outtypes=[ORTD.DATATYPE_FLOAT], ... + CaseSwitch_fn=SelectCaseSendNewConfigAvailable, SimnestName="SelectCaseSendNewConfigAvailable", DirectFeedthrough=%t, SelectSignal=selectNewConfigInt32, list("Finished", "Send"), list(PacketFramework) ); + + PacketFramework = userdata(1); + +endfunction + + +function str=ld_PF_Export_str(PacketFramework) + // Added possibility to add GUI-configurations on 5.3.15 + + function jsonstr = struct2json(a) + // + // Rev 1 as of 4.3.15: Initial version + // Rev 2 as of 4.3.15: added arrays of strings that are defined by e.g. list('str1', 'str2') + // + // + // Example usage: + // + // clear a; + // a.Field1 = 2; + // a.Field2 = "jkh"; + // a.Field3 = 1.2; + // a.F3.name = "Joe"; + // a.F3.age = 32; + // //a.F3.data = [1,2]; + // jsonstr = struct2json(a); + // disp(jsonstr); + // + // Warning: For strings make sure you escape the special characters that are used by the JSON-format! + // + + function valstr=val2str(val) + select typeof(val) + + case "string" + if length(length(val)) == 1 then + valstr = """" + string(val) + """"; + end + + case "list" // convert to array of strings + valstr = '['; + + N = length(val); + for i=1:(N-1) + valstr = valstr + """" + string( val(i) ) + """" + ','; + end + valstr = valstr + """" + string( val(N) ) + """" + ']'; + + + case "constant" + if length(val) == 1 then + valstr = string(val); + else + valstr = """" + "** Matrix not supported **" + """"; + end + + case "st" + valstr = struct2json(val); + + else + valstr = """" + "**Datatype " + typeof(val) + " not supported **" + """"; + end + endfunction + + if isstruct(a) then + F = fieldnames(a); + str = "{ "; + N = length(length(F)); + + for i = 1:(N-1) + f = F(i); + val = eval('a.'+f); + valstr = val2str(val); + str = str + """" + f + """" + ' : ' + valstr + ' , '; + end + + f = F(N); + val = eval('a.'+f); + valstr = val2str(val); + str = str + """" + f + """" + ' : ' + valstr + ' } '; + + else + error("Not a structure") + end + + jsonstr = str; + endfunction + + + // check if there is a GUI to be set-up in Papi + if isfield(PacketFramework, 'PaPIConfig') then + PaPIConfigstr = struct2json(PacketFramework.PaPIConfig) + else + PaPIConfigstr = '{}'; + end + + + + str=' {""SourcesConfig"" : {'+char(10); + + for i=1:length(PacketFramework.Sources) + + + SourceID = PacketFramework.Sources(i).SourceID; + SourceName = PacketFramework.Sources(i).SourceName; + disp(SourceID ); + disp( SourceName ); + + + line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"" } ", ... + string(PacketFramework.Sources(i).SourceID), ... + string(PacketFramework.Sources(i).SourceName), ... + string(PacketFramework.Sources(i).NValues_send), ... + string(PacketFramework.Sources(i).datatype) ); + + + if i==length(PacketFramework.Sources) + // finalise the last entry without "," + printf('%s' , line); + str=str+line + char(10); + else + printf('%s,' , line); + str=str+line+',' + char(10); + end + + + end + + + + str=str+'} , ' + char(10) + ' ""ParametersConfig"" : {' + char(10); + + // go through all parameters and create memories for all + for i=1:length(PacketFramework.Parameters) + + printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); + + line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } ", ... + string(PacketFramework.Parameters(i).ParameterID), ... + string(PacketFramework.Parameters(i).ParameterName), ... + string(PacketFramework.Parameters(i).NValues), ... + string(PacketFramework.Parameters(i).datatype) ); + + + if i==length(PacketFramework.Parameters) + // finalise the last entry without "," + printf('%s' , line); + str=str+line + char(10); + else + printf('%s,' , line); + str=str+line+',' + char(10); + end + + + end + str=str+'}, '+char(10)+ """" + 'PaPIConfig' + """" + ' : ' + PaPIConfigstr + char(10) + '}'; // + + // print the configuration to be send to Papi + disp(str); + +endfunction + +function ld_PF_Export_js(PacketFramework, fname) // PARSEDOCU_BLOCK + // + // Export configuration of the defined protocoll (Sources, Parameters) + // into JSON-format. This is to be used by software that shall communicate + // to the real-time system. + // + // fname - The file name + // + // + str=ld_PF_Export_str(PacketFramework); + + fd = mopen(fname,'wt'); + mfprintf(fd,'%s', str); + mclose(fd); + +endfunction + +// +// Added 27.3.14 +// + +function [sim, PacketFramework, SourceID]=ld_SendPacketReserve(sim, PacketFramework, NValues_send, datatype, SourceName) + [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); +endfunction + +function [sim, PacketFramework]=ld_SendPacket2(sim, PacketFramework, Signal, SourceName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Sources) + S = PacketFramework.Sources(i); + if S.SourceName == SourceName + index = i; + printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); + break; + end + end + + if index == -1 + printf("SourceName = %s\n", SourceName); + error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); + end + + [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); +endfunction + + + +function [sim, PacketFramework, ParameterID]=ld_PF_ParameterReserve(sim, PacketFramework, NValues, datatype, ParameterName) + [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); +endfunction + + +function [sim, PacketFramework, Parameter]=ld_PF_Parameter2(sim, PacketFramework, ParameterName) + // find Sourcename + index = -1; + for i=1:length(PacketFramework.Parameters) + P = PacketFramework.Parameters(i); + if P.ParameterName == ParameterName + index = i; + printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); + break; + end + end + + if index == -1 + printf("ParameterName = %s\n", ParameterName); + error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); + end + + // read data from global memory + [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 + [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... + P.datatype, P.NValues); +endfunction + + + + From a22047eeaded083fcf2c33dcf78b75740ba48c2e Mon Sep 17 00:00:00 2001 From: christianausb Date: Thu, 5 Mar 2015 14:40:07 +0100 Subject: [PATCH 162/260] nice example --- .gitignore | 8 + .../SelectCasePaPiConfig.ipar | 240 +++++++++++++----- .../SelectCasePaPiConfig.rpar | 2 +- .../SelectCasePaPiConfig.sce | 15 +- 4 files changed, 189 insertions(+), 76 deletions(-) diff --git a/.gitignore b/.gitignore index 15528654..4e691971 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,11 @@ coverage.xml # Sphinx documentation docs/_build/ + +data_sources/ORTD/DataSourceAutoConfigExample/ProtocollConfig.json + +data_sources/ORTD/DataSourceAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar + +data_sources/ORTD/DataSourceAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar + +data_sources/ORTD/DataSourceAutoConfigExample/ProtocollConfig.json diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar index 70974d3a..d269e69e 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar @@ -4,7 +4,7 @@ 10 0 0 - 14959 + 15063 26 1 3 @@ -18,11 +18,11 @@ 100 6 0 - 14921 + 15025 26 100 101 - 14927 + 15031 26 12 0 @@ -34,7 +34,7 @@ 0 15011 203 - 14915 + 15019 26 1 0 @@ -80,11 +80,11 @@ 4 12 0 - 14852 + 14956 0 21 1 - 14864 + 14968 0 1 26 @@ -100,7 +100,7 @@ 1 1 1 - 14851 + 14955 1 7 10 @@ -143,7 +143,7 @@ 10 27 0 - 14780 + 14884 26 0 0 @@ -196,17 +196,17 @@ 100 84 2 - 14615 + 14719 24 211 100 - 14699 + 14803 26 15 0 100 101 - 14714 + 14818 26 28 0 @@ -296,7 +296,7 @@ 0 15002 207 - 14609 + 14713 24 1 0 @@ -348,11 +348,11 @@ 10 376 11 - 14115 + 14219 11 902 10 - 14491 + 14595 22 62 2 @@ -744,95 +744,95 @@ 100 6 0 - 13697 + 13801 4 208 100 - 13703 + 13807 4 6 0 210 100 - 13709 + 13813 4 7 0 212 100 - 13716 + 13820 4 8 0 215 100 - 13724 + 13828 4 6 0 217 100 - 13730 + 13834 4 28 0 218 100 - 13758 + 13862 4 31 0 219 100 - 13789 + 13893 4 6 1 221 100 - 13795 + 13899 5 24 0 222 100 - 13819 + 13923 5 6 1 224 100 - 13825 + 13929 6 6 1 226 100 - 13831 + 13935 7 6 1 228 100 - 13837 + 13941 8 6 1 230 100 - 13843 + 13947 9 6 1 232 100 - 13849 + 13953 10 6 1 100 101 - 13855 + 13959 11 156 0 @@ -844,7 +844,7 @@ 0 15011 205 - 13691 + 13795 4 1 0 @@ -890,11 +890,11 @@ 4 14 0 - 13626 + 13730 0 21 1 - 13640 + 13744 0 1 4 @@ -912,7 +912,7 @@ 1 1 1 - 13625 + 13729 1 7 10 @@ -955,7 +955,7 @@ 10 22 0 - 13559 + 13663 4 0 1 @@ -991,53 +991,53 @@ 100 6 1 - 13207 + 13311 0 205 100 - 13213 + 13317 1 44 0 206 100 - 13257 + 13361 1 6 1 208 100 - 13263 + 13367 2 6 1 210 100 - 13269 + 13373 3 8 0 212 100 - 13277 + 13381 3 8 0 214 100 - 13285 + 13389 3 6 1 216 100 - 13291 + 13395 4 130 0 100 101 - 13421 + 13525 4 76 0 @@ -1049,7 +1049,7 @@ 0 22000 203 - 13201 + 13305 0 1 0 @@ -1064,7 +1064,7 @@ 0 0 0 - 12974 + 13078 105 94 12 @@ -5860,7 +5860,8 @@ 32 61 32 - 53 + 51 + 48 48 59 10 @@ -10128,6 +10129,58 @@ 114 111 108 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 73 + 110 + 112 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 97 + 100 + 100 + 95 + 111 + 102 + 115 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 73 + 110 + 112 + 117 + 116 + 44 + 32 + 48 + 46 + 50 + 41 + 59 10 32 32 @@ -10529,7 +10582,7 @@ 48 48 44 - 51 + 53 48 48 41 @@ -10611,7 +10664,7 @@ 32 39 40 - 51 + 49 48 48 44 @@ -10754,24 +10807,46 @@ 61 32 39 - 67 + 83 111 - 110 - 116 + 117 114 - 111 - 108 - 108 + 99 101 + 71 + 114 + 111 + 117 + 112 + 39 + 32 + 43 + 32 + 115 + 116 114 - 83 105 - 103 110 - 97 - 108 + 103 + 40 + 117 115 - 39 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 41 59 10 32 @@ -12319,8 +12394,35 @@ 111 117 112 - 50 39 + 32 + 43 + 32 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 41 59 10 32 @@ -12985,14 +13087,16 @@ 61 32 39 - 102 - 105 - 110 - 105 + 111 115 - 104 - 101 - 100 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 39 59 10 diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar index 0bd666ad..38f8a318 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar @@ -1,4 +1,4 @@ -0.2000000000000000111022302 +0.0500000000000000027755576 1.2339999999999999857891453 1.0000000000000000000000000 0.0000000000000000000000000 diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce index fc44f6f8..8de58ae2 100644 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce +++ b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce @@ -27,7 +27,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) // Define parameters. They must be defined once again at this place, because this will also be called at // runtime. - NSamples=50; + NSamples=300; if CalledOnline == %t then // The contents of this part will be compiled on-line, while the control @@ -132,15 +132,16 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) [sim] = ld_printf(sim, ev, AParVector, "A vectorial parameter", 10); // The system to control + [sim, Input] = ld_add_ofs(sim, 0, Input, 0.2); T_a = 0.1; [sim, x,v] = damped_oscillator(sim, Input, T_a); // Stream the data of the oscillator [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X"); PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'Plot'; - PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; - PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,500)'; + PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(100,0)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot X'; - PacketFramework.PaPIConfig.ToSub.plot_X.block = 'ControllerSignals'; + PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup'+string(userdata.Acounter); PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('X'); @@ -170,7 +171,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot Const'; - PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup2'; + PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup'+string(userdata.Acounter) ; PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('Const'); // finalise the communication interface @@ -185,7 +186,7 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) outlist=list(out); // chose the next state to enter when the demo experiment has finished - userdata.State = "finished"; + userdata.State = "oscillator"; case "finished" // experiment finished - nothing to do in1 = inlist(1); @@ -260,7 +261,7 @@ endfunction // The main real-time thread function [sim, outlist, userdata] = Thread_MainRT(sim, inlist, userdata) // This will run in a thread - [sim, Tpause] = ld_const(sim, ev, 1/5); // The sampling time that is constant at 20 Hz in this example + [sim, Tpause] = ld_const(sim, ev, 1/20); // The sampling time that is constant at 20 Hz in this example [sim, out] = ld_ClockSync(sim, ev, in=Tpause); // synchronise this simulation // From 21352b5f96e9e6eb77051a9118aa0d40ee48a8c1 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Thu, 5 Mar 2015 19:27:50 +0100 Subject: [PATCH 163/260] Added new configuration type 'color' which enables a new color picker. Added helper module which can be filled with useful functions by the developer. Major changes in the progress bar --- papi/__init__.py | 2 +- papi/gui/qt_new/create_plugin_dialog.py | 11 +++- papi/gui/qt_new/custom.py | 54 +++++++++++++++++- papi/helper.py | 43 +++++++++++++++ papi/plugin/visual/ProgressBar/ProgressBar.py | 55 +++++++++++++------ 5 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 papi/helper.py diff --git a/papi/__init__.py b/papi/__init__.py index e87a6f2b..b3744bd4 100644 --- a/papi/__init__.py +++ b/papi/__init__.py @@ -1 +1 @@ -__author__ = 'Knuth' \ No newline at end of file +__author__ = 'knuths' diff --git a/papi/gui/qt_new/create_plugin_dialog.py b/papi/gui/qt_new/create_plugin_dialog.py index 0c67e632..2ed4bedf 100644 --- a/papi/gui/qt_new/create_plugin_dialog.py +++ b/papi/gui/qt_new/create_plugin_dialog.py @@ -34,7 +34,7 @@ from PySide.QtCore import * from papi.ui.gui.qt_new.create_dialog import Ui_CreatePluginDialog -from papi.gui.qt_new.custom import FileLineEdit +from papi.gui.qt_new.custom import FileLineEdit, ColorLineEdit from papi.constants import GUI_DEFAULT_TAB @@ -74,6 +74,9 @@ def accept(self): if isinstance(self.configuration_inputs[attr], QLineEdit): config[attr]['value'] = self.configuration_inputs[attr].text() + if isinstance(self.configuration_inputs[attr], ColorLineEdit): + config[attr]['value'] = self.configuration_inputs[attr].text() + if isinstance(self.configuration_inputs[attr], QComboBox): config[attr]['value'] = self.configuration_inputs[attr].currentText() @@ -194,6 +197,12 @@ def showEvent(self, *args, **kwargs): editable_field.setReadOnly(True) editable_field.setText(value) + if parameter_type == 'color': + editable_field = ColorLineEdit() + editable_field.set_default_color(startup_config[attr]['value']) + # + #editable_field.setText(value) + else: editable_field = QLineEdit() diff --git a/papi/gui/qt_new/custom.py b/papi/gui/qt_new/custom.py index 7a9cf1d4..22357833 100644 --- a/papi/gui/qt_new/custom.py +++ b/papi/gui/qt_new/custom.py @@ -28,8 +28,9 @@ __author__ = 'knuths' -from PySide.QtGui import QLineEdit, QFileDialog -import os +from PySide.QtGui import QLineEdit, QFileDialog, QColorDialog, QPushButton, QColor +import os, re +import papi.helper as ph class FileLineEdit(QLineEdit): def __init__(self): @@ -56,4 +57,51 @@ def mousePressEvent(self, event): if len(fileNames): if fileNames[0] != '': - self.setText(fileNames[0]) \ No newline at end of file + self.setText(fileNames[0]) + + +class ColorLineEdit(QPushButton): + def __init__(self): + super(ColorLineEdit, self).__init__() + self.clicked.connect(self.open_color_picker) + self.color_picker = QColorDialog() + + def set_default_color(self, color_string): + + #self.setStyleSheet("filter: invert(100%)") + self.setText(color_string) + + + color_inverse = ph.get_color_by_string(color_string, inverse=True) + color_string_inverse = self.__get_string_by_color(color_inverse) + + self.setStyleSheet("\ + QPushButton { \ + border : 1px outset black; \ + background-color: rgb" + color_string + " ;color: rgb" + color_string_inverse + "; \ + } \ + QPushButton:checked{\ + background-color: rgb" + color_string + "; \ + border-style: outset; \ + } \ + QPushButton:hover{ \ + background-color: rgb " + color_string + "; \ + border-style: solid; \ + } \ + "); + + def open_color_picker(self): + color = self.color_picker.getColor() + + if color.isValid(): + + color_string = self.__get_string_by_color(color) + self.set_default_color(color_string) + + pass + + def __get_string_by_color(self, color): + + color_string = '(' + str(color.red()) + ',' + str(color.green()) + ',' + str(color.blue()) + ')' + + return color_string diff --git a/papi/helper.py b/papi/helper.py new file mode 100644 index 00000000..103bbf48 --- /dev/null +++ b/papi/helper.py @@ -0,0 +1,43 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: + Date: Thu, 5 Mar 2015 20:02:08 +0100 Subject: [PATCH 164/260] Added Example configuration for the ProgressBar; Finalized configuration for the progressbar and minor changes in the trigger plugin due to better testing of the progress bar --- cfg_collection/progress_bar_example.xml | 725 ++++++++++++++++++ papi/plugin/dpp/trigger/Trigger.py | 5 +- papi/plugin/visual/ProgressBar/ProgressBar.py | 57 +- 3 files changed, 771 insertions(+), 16 deletions(-) create mode 100644 cfg_collection/progress_bar_example.xml diff --git a/cfg_collection/progress_bar_example.xml b/cfg_collection/progress_bar_example.xml new file mode 100644 index 00000000..cfd8d849 --- /dev/null +++ b/cfg_collection/progress_bar_example.xml @@ -0,0 +1,725 @@ + + + + + 771 + + + 853 + + + + + default + + + + + Button + + + Clack + 0 + Text for state 2 + + + 0 + 0 + ([-]{0,1}\d+\.\d+) + + + ButtonX2 + + + Click + 0 + Text for state 1 + + + (159,54) + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + + + PaPI-Tab + Used for tabs + + + Button2 + 0 + + + (150,55) + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + + + 1 + 0 + ([-]{0,1}\d+\.\d+) + + + + + + + Parameter + + + + + + + ProgressBar + + + 0 + 1 + Min Range + \d+ + Set minimum range for the progress bar. + + + 1 + 1 + Show + + + 1 + 0 + Show current/max + bool + A label next to the bar shows the current and max value + + + ProgressBarShowAll + + + Button3 + Used for window title + + + reset + 0 + Reset Value + Name of the scalar which is used to reset the progress bar. + + + (314,0) + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + + + PaPI-Tab + Used for tabs + + + percent + 0 + Progress Value + Name of the scalar which is used for the progress bar. + + + (156,53) + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + + + trigger + 0 + Trigger Value + Name of the scalar which is used to increment progress bar by one. + + + 0 + 0 + Show percent + bool + Show progress in percent over the progress bar + + + 153 + 1 + Show current/max + \d+ + Set maximum range for the progress bar. + + + + 0 + + + + + ButtonX3 + + trigger + + + + Trigger + + + trigger + + + + percent + + + + reset + + + + + + Button + + + Clack + 0 + Text for state 2 + + + 0 + 0 + ([-]{0,1}\d+\.\d+) + + + ButtonX3 + + + Click + 0 + Text for state 1 + + + (313,52) + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + + + PaPI-Tab + Used for tabs + + + Button3 + 0 + + + (150,55) + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + + + 1 + 0 + ([-]{0,1}\d+\.\d+) + + + + + + + Parameter + + + + + + + Trigger + + + Trigger + + + + 0 + + + + + trigger + + + + + percent + + + + + reset + + + + + + ButtonX6 + + Choose + + + + ButtonReset + + Choose + + + + ButtonX5 + + Choose + + + + + + Button + + + ResetAll + 0 + Text for state 2 + + + 2 + 0 + ([-]{0,1}\d+\.\d+) + + + ButtonReset + + + ResetAll + 0 + Text for state 1 + + + (309,176) + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + + + PaPI-Tab + Used for tabs + + + ResetAll + 0 + + + (150,55) + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + + + 2 + 0 + ([-]{0,1}\d+\.\d+) + + + + + + + Parameter + + + + + + + Button + + + TriggerAll + 0 + Text for state 2 + + + 1 + 0 + ([-]{0,1}\d+\.\d+) + + + ButtonX5 + + + TriggerAll + 0 + Text for state 1 + + + (154,176) + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + + + PaPI-Tab + Used for tabs + + + TriggerAll + 0 + + + (150,55) + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + + + 1 + 0 + ([-]{0,1}\d+\.\d+) + + + + + + + Parameter + + + + + + + Button + + + To 20 + 0 + Text for state 2 + + + 0 + 0 + ([-]{0,1}\d+\.\d+) + + + ButtonX6 + + + To 20 + 0 + Text for state 1 + + + (2,174) + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + + + PaPI-Tab + Used for tabs + + + SetAll + 0 + + + (150,55) + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + + + 0 + 0 + ([-]{0,1}\d+\.\d+) + + + + + + + Parameter + + + + + + + ProgressBar + + + 0 + 1 + Min Range + \d+ + Set minimum range for the progress bar. + + + 1 + 1 + Show + + + 1 + 0 + Show current/max + bool + A label next to the bar shows the current and max value + + + ProgressBar + + + Button1 + Used for window title + + + reset + 0 + Reset Value + Name of the scalar which is used to reset the progress bar. + + + (0,0) + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + + + PaPI-Tab + Used for tabs + + + percent + 0 + Progress Value + Name of the scalar which is used for the progress bar. + + + (150,50) + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + + + trigger + 0 + Trigger Value + Name of the scalar which is used to increment progress bar by one. + + + 1 + 0 + Show percent + bool + Show progress in percent over the progress bar + + + 100 + 1 + Show current/max + \d+ + Set maximum range for the progress bar. + + + + 0 + + + + + Button + + trigger + + + + Trigger + + + trigger + + + + percent + + + + reset + + + + + + Button + + + Clack + 0 + Text for state 2 + + + 0 + 0 + ([-]{0,1}\d+\.\d+) + + + Button + + + Click + 0 + Text for state 1 + + + (0,52) + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + + + PaPI-Tab + Used for tabs + + + Button1 + 0 + + + (150,55) + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + + + 1 + 0 + ([-]{0,1}\d+\.\d+) + + + + + + + Parameter + + + + + + + ProgressBar + + + 0 + 1 + Min Range + \d+ + Set minimum range for the progress bar. + + + 1 + 1 + Show + + + 0 + 0 + Show current/max + bool + A label next to the bar shows the current and max value + + + ProgressBarShowCurrentMax + + + Button2 + Used for window title + + + reset + 0 + Reset Value + Name of the scalar which is used to reset the progress bar. + + + (158,0) + 1 + \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + + + PaPI-Tab + Used for tabs + + + percent + 0 + Progress Value + Name of the scalar which is used for the progress bar. + + + (150,53) + 1 + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + + + trigger + 0 + Trigger Value + Name of the scalar which is used to increment progress bar by one. + + + 0 + 0 + Show percent + bool + Show progress in percent over the progress bar + + + 100 + 1 + Show current/max + \d+ + Set maximum range for the progress bar. + + + + 0 + + + + + ButtonX2 + + trigger + + + + Trigger + + + trigger + + + + percent + + + + reset + + + + + diff --git a/papi/plugin/dpp/trigger/Trigger.py b/papi/plugin/dpp/trigger/Trigger.py index c24fb007..d77e13eb 100644 --- a/papi/plugin/dpp/trigger/Trigger.py +++ b/papi/plugin/dpp/trigger/Trigger.py @@ -81,18 +81,15 @@ def execute(self, Data=None, block_name = None): def set_parameter(self, name, value): if not self.initialized: return - value = int(value) + value = int(float(value)) if name == self.para3.name: if value == 0: - print(value) self.send_new_data('Progress', [0], {'percent': [20]}) if value == 1: - print(value) self.send_new_data('Trigger', [0], {'trigger': [0]}) if value == 2: - print(value) self.send_new_data('ResetTrigger', [0], {'reset': [0]}) diff --git a/papi/plugin/visual/ProgressBar/ProgressBar.py b/papi/plugin/visual/ProgressBar/ProgressBar.py index 0f20a25b..c01c2cb9 100644 --- a/papi/plugin/visual/ProgressBar/ProgressBar.py +++ b/papi/plugin/visual/ProgressBar/ProgressBar.py @@ -57,6 +57,8 @@ def initiate_layer_0(self, config=None): self.show_percent = self.config['show_percent']['value'] == '1' self.show_current_max = self.config['show_current_max']['value'] == '1' + self.min_range = int(self.config['min_rage']['value']) + self.max_range = int(self.config['max_range']['value']) # -------------------------------- # Create Widget # -------------------------------- @@ -65,6 +67,7 @@ def initiate_layer_0(self, config=None): self.central_widget = QtGui.QWidget() self.horizontal_layoyt = QtGui.QHBoxLayout() + self.horizontal_layoyt.setContentsMargins(0,0,0,0) self.central_widget.setLayout(self.horizontal_layoyt) @@ -76,7 +79,7 @@ def initiate_layer_0(self, config=None): self.progressbar = QtGui.QProgressBar() - self.progressbar.setRange(0, 157) + self.progressbar.setRange(self.min_range, self.max_range) self.progressbar.setTextVisible(True) self.progressbar.setValue(0) @@ -85,12 +88,20 @@ def initiate_layer_0(self, config=None): # This call is important, because the background structure needs to know the used widget! # In the background the qmidiwindow will becreated and the widget will be added + if self.show_current_max: - self.horizontal_layoyt.addWidget(self.progressbar) - self.horizontal_layoyt.addWidget(self.label) + self.horizontal_layoyt.addWidget(self.progressbar) + self.horizontal_layoyt.addWidget(self.label) + self.refresh_label() - self.set_widget_for_internal_usage(self.central_widget) + if self.show_current_max: + self.set_widget_for_internal_usage(self.central_widget) + else: + self.set_widget_for_internal_usage(self.progressbar) + + if not self.show_percent: + self.progressbar.setTextVisible(False) # --------------------------- # Create Parameters # --------------------------- @@ -103,10 +114,6 @@ def initiate_layer_0(self, config=None): # build parameter list to send to Core self.send_new_parameter_list(para_list) - # --------------------------- - # Create Legend - # --------------------------- - return True def show_context_menu(self, pos): @@ -138,7 +145,7 @@ def execute(self, Data=None, block_name = None): # Data could have multiple types stored in it e.a. Data['d1'] = int, Data['d2'] = [] if self.reset_trigger_value in Data: - self.progressbar.reset() + self.reset() if self.trigger_value in Data: old_value = self.progressbar.value() + 1 @@ -149,14 +156,26 @@ def execute(self, Data=None, block_name = None): self.progressbar.setValue(new_value) + self.refresh_label() + def set_parameter(self, name, value): - # attetion: value is a string and need to be processed ! + # attention: value is a string and need to be processed ! # if name == 'irgendeinParameter': # do that .... with value if name == self.para_trigger.name: - self.para_trigger.value += 1 - self.progressbar.setValue(self.para_trigger.value) + new_value = self.progressbar.value() + 1 + self.progressbar.setValue(new_value) + + self.refresh_label() + + def refresh_label(self): + if self.show_current_max: + self.label.setText(str(self.progressbar.value()) + ' / ' + str(self.max_range)) + + def reset(self): + self.progressbar.setValue(0) + self.refresh_label() def quit(self): # do something before plugin will close, e.a. close connections ... @@ -191,6 +210,20 @@ def get_plugin_configuration(self): 'tooltip' : 'A label next to the bar shows the current and max value', 'type' : 'bool', 'advanced' : '0' + }, + "min_rage": { + 'value': '0', + 'display_text' : 'Min Range', + 'regex': '\d+', + 'tooltip' : 'Set minimum range for the progress bar.', + 'advanced' : '1' + }, + "max_range": { + 'value': '100', + 'display_text' : 'Show current/max', + 'regex': '\d+', + 'tooltip' : 'Set maximum range for the progress bar.', + 'advanced' : '1' }, "trigger_value": { 'value': 'trigger', From cd4190d411982beefbc3f7cf59b27b1f8581f160 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Thu, 5 Mar 2015 20:37:08 +0100 Subject: [PATCH 165/260] Minor changes in the tooltips for the configuration. --- papi/plugin/visual/ProgressBar/ProgressBar.py | 8 ++++---- papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/papi/plugin/visual/ProgressBar/ProgressBar.py b/papi/plugin/visual/ProgressBar/ProgressBar.py index c01c2cb9..9bc3d58d 100644 --- a/papi/plugin/visual/ProgressBar/ProgressBar.py +++ b/papi/plugin/visual/ProgressBar/ProgressBar.py @@ -194,7 +194,7 @@ def get_plugin_configuration(self): "progress_value": { 'value': 'percent', 'display_text' : 'Progress Value', - 'tooltip' : 'Name of the scalar which is used for the progress bar.', + 'tooltip' : 'Name of the signal whose first value is used to set the current value for the progress bar.', 'advanced' : '0' }, "show_percent": { @@ -220,7 +220,7 @@ def get_plugin_configuration(self): }, "max_range": { 'value': '100', - 'display_text' : 'Show current/max', + 'display_text' : 'Max Range', 'regex': '\d+', 'tooltip' : 'Set maximum range for the progress bar.', 'advanced' : '1' @@ -228,13 +228,13 @@ def get_plugin_configuration(self): "trigger_value": { 'value': 'trigger', 'display_text' : 'Trigger Value', - 'tooltip' : 'Name of the scalar which is used to increment progress bar by one.', + 'tooltip' : 'Name of the signal which triggers the progress bar to increment by one.', 'advanced' : '0' }, "reset_trigger_value": { 'value': 'reset', 'display_text' : 'Reset Value', - 'tooltip' : 'Name of the scalar which is used to reset the progress bar.', + 'tooltip' : 'Name of the signal which triggers the progress bar to reset.', 'advanced' : '0' }, 'name': { diff --git a/papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin b/papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin index f0063b57..1b698480 100644 --- a/papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin +++ b/papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin @@ -6,4 +6,4 @@ Module = ProgressBar Author = S. Knuth Version = 0.1 Website = -Description = Simple progress bar which is used to display a value between 0 and 100. A simple increment by one is possible by using the 'trigger'-Parameter. \ No newline at end of file +Description = Simple progress bar which is used to display a value between 0 and 100. A simple increment by one is possible by using the 'trigger'-Parameter. It is possible to subscribe signals to controll the progress bar. By sending \ No newline at end of file From da95447150d0d6f0ebe32495fb8146551dead110 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Thu, 5 Mar 2015 20:44:05 +0100 Subject: [PATCH 166/260] changed base class of trigger --- papi/last_active_papi.xml | 725 +++++++++++++++++++++++++++++ papi/plugin/dpp/trigger/Trigger.py | 4 +- 2 files changed, 727 insertions(+), 2 deletions(-) create mode 100644 papi/last_active_papi.xml diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml new file mode 100644 index 00000000..04d6eb32 --- /dev/null +++ b/papi/last_active_papi.xml @@ -0,0 +1,725 @@ + + + + + 853 + + + 771 + + + + + default + + + + + Button + + + ButtonX2 + + + 0 + Button2 + + + Determine position: (x,y) + 1 + (159,54) + \(([0-9]+),([0-9]+)\) + + + 0 + Clack + Text for state 2 + + + Used for tabs + PaPI-Tab + + + Determine size: (height,width) + 1 + (150,55) + \(([0-9]+),([0-9]+)\) + + + 0 + 0 + ([-]{0,1}\d+\.\d+) + + + 0 + 1 + ([-]{0,1}\d+\.\d+) + + + 0 + Click + Text for state 1 + + + + + + + Parameter + + + + + + + ProgressBar + + + ProgressBarShowAll + + + Used for window title + Button3 + + + Set maximum range for the progress bar. + 1 + 153 + Show current/max + \d+ + + + Name of the scalar which is used for the progress bar. + 0 + percent + Progress Value + + + Name of the scalar which is used to increment progress bar by one. + 0 + trigger + Trigger Value + + + Show progress in percent over the progress bar + 0 + 0 + Show percent + bool + + + Show + 1 + 1 + + + Used for tabs + PaPI-Tab + + + Determine position: (x,y) + 1 + (314,0) + \(([0-9]+),([0-9]+)\) + + + Name of the scalar which is used to reset the progress bar. + 0 + reset + Reset Value + + + A label next to the bar shows the current and max value + 0 + 1 + Show current/max + bool + + + Set minimum range for the progress bar. + 1 + 0 + Min Range + \d+ + + + Determine size: (height,width) + 1 + (156,53) + \(([0-9]+),([0-9]+)\) + + + + 0 + + + + + ButtonX3 + + trigger + + + + Trigger + + + trigger + + + + reset + + + + percent + + + + + + Button + + + ButtonX3 + + + 0 + Button3 + + + Determine position: (x,y) + 1 + (313,52) + \(([0-9]+),([0-9]+)\) + + + 0 + Clack + Text for state 2 + + + Used for tabs + PaPI-Tab + + + Determine size: (height,width) + 1 + (150,55) + \(([0-9]+),([0-9]+)\) + + + 0 + 0 + ([-]{0,1}\d+\.\d+) + + + 0 + 1 + ([-]{0,1}\d+\.\d+) + + + 0 + Click + Text for state 1 + + + + + + + Parameter + + + + + + + Trigger + + + Trigger + + + + 0 + + + + + trigger + + + + + reset + + + + + percent + + + + + + ButtonX6 + + Choose + + + + ButtonReset + + Choose + + + + ButtonX5 + + Choose + + + + + + Button + + + ButtonReset + + + 0 + ResetAll + + + Determine position: (x,y) + 1 + (309,176) + \(([0-9]+),([0-9]+)\) + + + 0 + ResetAll + Text for state 2 + + + Used for tabs + PaPI-Tab + + + Determine size: (height,width) + 1 + (150,55) + \(([0-9]+),([0-9]+)\) + + + 0 + 2 + ([-]{0,1}\d+\.\d+) + + + 0 + 2 + ([-]{0,1}\d+\.\d+) + + + 0 + ResetAll + Text for state 1 + + + + + + + Parameter + + + + + + + Button + + + ButtonX5 + + + 0 + TriggerAll + + + Determine position: (x,y) + 1 + (154,176) + \(([0-9]+),([0-9]+)\) + + + 0 + TriggerAll + Text for state 2 + + + Used for tabs + PaPI-Tab + + + Determine size: (height,width) + 1 + (150,55) + \(([0-9]+),([0-9]+)\) + + + 0 + 1 + ([-]{0,1}\d+\.\d+) + + + 0 + 1 + ([-]{0,1}\d+\.\d+) + + + 0 + TriggerAll + Text for state 1 + + + + + + + Parameter + + + + + + + Button + + + ButtonX6 + + + 0 + SetAll + + + Determine position: (x,y) + 1 + (2,174) + \(([0-9]+),([0-9]+)\) + + + 0 + To 20 + Text for state 2 + + + Used for tabs + PaPI-Tab + + + Determine size: (height,width) + 1 + (150,55) + \(([0-9]+),([0-9]+)\) + + + 0 + 0 + ([-]{0,1}\d+\.\d+) + + + 0 + 0 + ([-]{0,1}\d+\.\d+) + + + 0 + To 20 + Text for state 1 + + + + + + + Parameter + + + + + + + ProgressBar + + + ProgressBar + + + Used for window title + Button1 + + + Set maximum range for the progress bar. + 1 + 100 + Show current/max + \d+ + + + Name of the scalar which is used for the progress bar. + 0 + percent + Progress Value + + + Name of the scalar which is used to increment progress bar by one. + 0 + trigger + Trigger Value + + + Show progress in percent over the progress bar + 0 + 1 + Show percent + bool + + + Show + 1 + 1 + + + Used for tabs + PaPI-Tab + + + Determine position: (x,y) + 1 + (0,0) + \(([0-9]+),([0-9]+)\) + + + Name of the scalar which is used to reset the progress bar. + 0 + reset + Reset Value + + + A label next to the bar shows the current and max value + 0 + 1 + Show current/max + bool + + + Set minimum range for the progress bar. + 1 + 0 + Min Range + \d+ + + + Determine size: (height,width) + 1 + (150,50) + \(([0-9]+),([0-9]+)\) + + + + 0 + + + + + Button + + trigger + + + + Trigger + + + trigger + + + + reset + + + + percent + + + + + + Button + + + Button + + + 0 + Button1 + + + Determine position: (x,y) + 1 + (0,52) + \(([0-9]+),([0-9]+)\) + + + 0 + Clack + Text for state 2 + + + Used for tabs + PaPI-Tab + + + Determine size: (height,width) + 1 + (150,55) + \(([0-9]+),([0-9]+)\) + + + 0 + 0 + ([-]{0,1}\d+\.\d+) + + + 0 + 1 + ([-]{0,1}\d+\.\d+) + + + 0 + Click + Text for state 1 + + + + + + + Parameter + + + + + + + ProgressBar + + + ProgressBarShowCurrentMax + + + Used for window title + Button2 + + + Set maximum range for the progress bar. + 1 + 100 + Show current/max + \d+ + + + Name of the scalar which is used for the progress bar. + 0 + percent + Progress Value + + + Name of the scalar which is used to increment progress bar by one. + 0 + trigger + Trigger Value + + + Show progress in percent over the progress bar + 0 + 0 + Show percent + bool + + + Show + 1 + 1 + + + Used for tabs + PaPI-Tab + + + Determine position: (x,y) + 1 + (158,0) + \(([0-9]+),([0-9]+)\) + + + Name of the scalar which is used to reset the progress bar. + 0 + reset + Reset Value + + + A label next to the bar shows the current and max value + 0 + 0 + Show current/max + bool + + + Set minimum range for the progress bar. + 1 + 0 + Min Range + \d+ + + + Determine size: (height,width) + 1 + (150,53) + \(([0-9]+),([0-9]+)\) + + + + 0 + + + + + ButtonX2 + + trigger + + + + Trigger + + + trigger + + + + reset + + + + percent + + + + + diff --git a/papi/plugin/dpp/trigger/Trigger.py b/papi/plugin/dpp/trigger/Trigger.py index d77e13eb..cbbf372b 100644 --- a/papi/plugin/dpp/trigger/Trigger.py +++ b/papi/plugin/dpp/trigger/Trigger.py @@ -30,14 +30,14 @@ from papi.data.DSignal import DSignal from papi.data.DParameter import DParameter -from papi.plugin.base_classes.iop_base import iop_base +from papi.plugin.base_classes.dpp_base import dpp_base import time import math import numpy -class Trigger(iop_base): +class Trigger(dpp_base): def __init__(self): self.initialized = False From 7a94cf8887dd44861eea6f25bcef8c3808879f89 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Thu, 5 Mar 2015 20:46:27 +0100 Subject: [PATCH 167/260] Trigger plugin: Changed event trigger mode to True --- papi/last_active_papi.xml | 618 ++++++++++++++--------------- papi/plugin/dpp/trigger/Trigger.py | 2 + 2 files changed, 311 insertions(+), 309 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 04d6eb32..ddb63afa 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,64 +1,64 @@ - + - - - 853 - - - 771 - - default + + + 771 + + + 853 + + Button - - ButtonX2 + + ([-]{0,1}\d+\.\d+) + 0 + 1 + + + ([-]{0,1}\d+\.\d+) + 0 + 0 0 Button2 - - Determine position: (x,y) - 1 - (159,54) - \(([0-9]+),([0-9]+)\) - - + 0 - Clack - Text for state 2 + Click + Text for state 1 - - Used for tabs - PaPI-Tab + + ButtonX2 - Determine size: (height,width) + \(([0-9]+),([0-9]+)\) 1 (150,55) - \(([0-9]+),([0-9]+)\) + Determine size: (height,width) - - 0 - 0 - ([-]{0,1}\d+\.\d+) + + Used for tabs + PaPI-Tab - - 0 - 1 - ([-]{0,1}\d+\.\d+) + + \(([0-9]+),([0-9]+)\) + 1 + (159,54) + Determine position: (x,y) - + 0 - Click - Text for state 1 + Clack + Text for state 2 @@ -74,19 +74,30 @@ ProgressBar - - ProgressBarShowAll + + Show + 1 + 1 Used for window title Button3 + + ProgressBarShowAll + - Set maximum range for the progress bar. + \d+ 1 153 + Set maximum range for the progress bar. Show current/max - \d+ + + + \(([0-9]+),([0-9]+)\) + 1 + (314,0) + Determine position: (x,y) Name of the scalar which is used for the progress bar. @@ -94,33 +105,32 @@ percent Progress Value - - Name of the scalar which is used to increment progress bar by one. + + \d+ + 1 + 0 + Set minimum range for the progress bar. + Min Range + + + A label next to the bar shows the current and max value 0 - trigger - Trigger Value + 1 + bool + Show current/max + + + \(([0-9]+),([0-9]+)\) + 1 + (156,53) + Determine size: (height,width) Show progress in percent over the progress bar 0 0 - Show percent bool - - - Show - 1 - 1 - - - Used for tabs - PaPI-Tab - - - Determine position: (x,y) - 1 - (314,0) - \(([0-9]+),([0-9]+)\) + Show percent Name of the scalar which is used to reset the progress bar. @@ -128,25 +138,15 @@ reset Reset Value - - A label next to the bar shows the current and max value - 0 - 1 - Show current/max - bool - - - Set minimum range for the progress bar. - 1 - 0 - Min Range - \d+ + + Used for tabs + PaPI-Tab - - Determine size: (height,width) - 1 - (156,53) - \(([0-9]+),([0-9]+)\) + + Name of the scalar which is used to increment progress bar by one. + 0 + trigger + Trigger Value @@ -162,6 +162,10 @@ Trigger + + + percent + trigger @@ -170,58 +174,54 @@ reset - - - percent - Button - - ButtonX3 + + ([-]{0,1}\d+\.\d+) + 0 + 1 + + + ([-]{0,1}\d+\.\d+) + 0 + 0 0 Button3 - - Determine position: (x,y) - 1 - (313,52) - \(([0-9]+),([0-9]+)\) - - + 0 - Clack - Text for state 2 + Click + Text for state 1 - - Used for tabs - PaPI-Tab + + ButtonX3 - Determine size: (height,width) + \(([0-9]+),([0-9]+)\) 1 (150,55) - \(([0-9]+),([0-9]+)\) + Determine size: (height,width) - - 0 - 0 - ([-]{0,1}\d+\.\d+) + + Used for tabs + PaPI-Tab - - 0 - 1 - ([-]{0,1}\d+\.\d+) + + \(([0-9]+),([0-9]+)\) + 1 + (313,52) + Determine position: (x,y) - + 0 - Click - Text for state 1 + Clack + Text for state 2 @@ -242,7 +242,7 @@ - 0 + 1.0 @@ -250,16 +250,16 @@ trigger - - - reset - - percent + + + reset + + @@ -285,48 +285,48 @@ Button - - ButtonReset + + ([-]{0,1}\d+\.\d+) + 0 + 2 + + + ([-]{0,1}\d+\.\d+) + 0 + 2 0 ResetAll - - Determine position: (x,y) - 1 - (309,176) - \(([0-9]+),([0-9]+)\) - - + 0 ResetAll - Text for state 2 + Text for state 1 - - Used for tabs - PaPI-Tab + + ButtonReset - Determine size: (height,width) + \(([0-9]+),([0-9]+)\) 1 (150,55) - \(([0-9]+),([0-9]+)\) + Determine size: (height,width) - - 0 - 2 - ([-]{0,1}\d+\.\d+) + + Used for tabs + PaPI-Tab - - 0 - 2 - ([-]{0,1}\d+\.\d+) + + \(([0-9]+),([0-9]+)\) + 1 + (309,176) + Determine position: (x,y) - + 0 ResetAll - Text for state 1 + Text for state 2 @@ -342,48 +342,48 @@ Button - - ButtonX5 + + ([-]{0,1}\d+\.\d+) + 0 + 1 + + + ([-]{0,1}\d+\.\d+) + 0 + 1 0 TriggerAll - - Determine position: (x,y) - 1 - (154,176) - \(([0-9]+),([0-9]+)\) - - + 0 TriggerAll - Text for state 2 + Text for state 1 - - Used for tabs - PaPI-Tab + + ButtonX5 - Determine size: (height,width) + \(([0-9]+),([0-9]+)\) 1 (150,55) - \(([0-9]+),([0-9]+)\) + Determine size: (height,width) - - 0 - 1 - ([-]{0,1}\d+\.\d+) + + Used for tabs + PaPI-Tab - - 0 - 1 - ([-]{0,1}\d+\.\d+) + + \(([0-9]+),([0-9]+)\) + 1 + (154,176) + Determine position: (x,y) - + 0 TriggerAll - Text for state 1 + Text for state 2 @@ -399,48 +399,48 @@ Button - - ButtonX6 + + ([-]{0,1}\d+\.\d+) + 0 + 0 + + + ([-]{0,1}\d+\.\d+) + 0 + 0 0 SetAll - - Determine position: (x,y) - 1 - (2,174) - \(([0-9]+),([0-9]+)\) - - + 0 To 20 - Text for state 2 + Text for state 1 - - Used for tabs - PaPI-Tab + + ButtonX6 - Determine size: (height,width) + \(([0-9]+),([0-9]+)\) 1 (150,55) - \(([0-9]+),([0-9]+)\) + Determine size: (height,width) - - 0 - 0 - ([-]{0,1}\d+\.\d+) + + Used for tabs + PaPI-Tab - - 0 - 0 - ([-]{0,1}\d+\.\d+) + + \(([0-9]+),([0-9]+)\) + 1 + (2,174) + Determine position: (x,y) - + 0 To 20 - Text for state 1 + Text for state 2 @@ -456,19 +456,30 @@ ProgressBar - - ProgressBar + + Show + 1 + 1 Used for window title Button1 + + ProgressBar + - Set maximum range for the progress bar. + \d+ 1 100 + Set maximum range for the progress bar. Show current/max - \d+ + + + \(([0-9]+),([0-9]+)\) + 1 + (0,0) + Determine position: (x,y) Name of the scalar which is used for the progress bar. @@ -476,33 +487,32 @@ percent Progress Value - - Name of the scalar which is used to increment progress bar by one. - 0 - trigger - Trigger Value + + \d+ + 1 + 0 + Set minimum range for the progress bar. + Min Range - - Show progress in percent over the progress bar + + A label next to the bar shows the current and max value 0 1 - Show percent bool + Show current/max - - Show + + \(([0-9]+),([0-9]+)\) 1 - 1 - - - Used for tabs - PaPI-Tab + (150,50) + Determine size: (height,width) - - Determine position: (x,y) - 1 - (0,0) - \(([0-9]+),([0-9]+)\) + + Show progress in percent over the progress bar + 0 + 1 + bool + Show percent Name of the scalar which is used to reset the progress bar. @@ -510,25 +520,15 @@ reset Reset Value - - A label next to the bar shows the current and max value - 0 - 1 - Show current/max - bool - - - Set minimum range for the progress bar. - 1 - 0 - Min Range - \d+ + + Used for tabs + PaPI-Tab - - Determine size: (height,width) - 1 - (150,50) - \(([0-9]+),([0-9]+)\) + + Name of the scalar which is used to increment progress bar by one. + 0 + trigger + Trigger Value @@ -544,6 +544,10 @@ Trigger + + + percent + trigger @@ -552,58 +556,54 @@ reset - - - percent - Button - - Button + + ([-]{0,1}\d+\.\d+) + 0 + 1 + + + ([-]{0,1}\d+\.\d+) + 0 + 0 0 Button1 - - Determine position: (x,y) - 1 - (0,52) - \(([0-9]+),([0-9]+)\) - - + 0 - Clack - Text for state 2 + Click + Text for state 1 - - Used for tabs - PaPI-Tab + + Button - Determine size: (height,width) + \(([0-9]+),([0-9]+)\) 1 (150,55) - \(([0-9]+),([0-9]+)\) + Determine size: (height,width) - - 0 - 0 - ([-]{0,1}\d+\.\d+) + + Used for tabs + PaPI-Tab - - 0 - 1 - ([-]{0,1}\d+\.\d+) + + \(([0-9]+),([0-9]+)\) + 1 + (0,52) + Determine position: (x,y) - + 0 - Click - Text for state 1 + Clack + Text for state 2 @@ -619,19 +619,30 @@ ProgressBar - - ProgressBarShowCurrentMax + + Show + 1 + 1 Used for window title Button2 + + ProgressBarShowCurrentMax + - Set maximum range for the progress bar. + \d+ 1 100 + Set maximum range for the progress bar. Show current/max - \d+ + + + \(([0-9]+),([0-9]+)\) + 1 + (158,0) + Determine position: (x,y) Name of the scalar which is used for the progress bar. @@ -639,33 +650,32 @@ percent Progress Value - - Name of the scalar which is used to increment progress bar by one. - 0 - trigger - Trigger Value + + \d+ + 1 + 0 + Set minimum range for the progress bar. + Min Range - - Show progress in percent over the progress bar + + A label next to the bar shows the current and max value 0 0 - Show percent bool + Show current/max - - Show + + \(([0-9]+),([0-9]+)\) 1 - 1 - - - Used for tabs - PaPI-Tab + (150,53) + Determine size: (height,width) - - Determine position: (x,y) - 1 - (158,0) - \(([0-9]+),([0-9]+)\) + + Show progress in percent over the progress bar + 0 + 0 + bool + Show percent Name of the scalar which is used to reset the progress bar. @@ -673,25 +683,15 @@ reset Reset Value - - A label next to the bar shows the current and max value - 0 - 0 - Show current/max - bool - - - Set minimum range for the progress bar. - 1 - 0 - Min Range - \d+ + + Used for tabs + PaPI-Tab - - Determine size: (height,width) - 1 - (150,53) - \(([0-9]+),([0-9]+)\) + + Name of the scalar which is used to increment progress bar by one. + 0 + trigger + Trigger Value @@ -707,6 +707,10 @@ Trigger + + + percent + trigger @@ -715,10 +719,6 @@ reset - - - percent - diff --git a/papi/plugin/dpp/trigger/Trigger.py b/papi/plugin/dpp/trigger/Trigger.py index cbbf372b..e9f09499 100644 --- a/papi/plugin/dpp/trigger/Trigger.py +++ b/papi/plugin/dpp/trigger/Trigger.py @@ -65,6 +65,8 @@ def start_init(self, config=None): self.send_new_parameter_list(para_l) + self.set_event_trigger_mode(True) + self.initialized = True return True From 51040be9edbde6842e63e70ee3104c61723f31ed Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Fri, 6 Mar 2015 15:10:06 +0100 Subject: [PATCH 168/260] ortd and dynamik cfg. new event for parameter deletion --- papi/core.py | 24 ++++++++++++-- papi/data/DCore.py | 6 +--- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 51 +++++++++++++++-------------- 3 files changed, 49 insertions(+), 32 deletions(-) diff --git a/papi/core.py b/papi/core.py index 0c9703d6..2140c161 100644 --- a/papi/core.py +++ b/papi/core.py @@ -31,7 +31,7 @@ import os from multiprocessing import Process, Queue from threading import Timer - +import copy from papi.yapsy.PluginManager import PluginManager from papi.data.DCore import DCore from papi.data.DPlugin import DPlugin, DBlock @@ -1064,6 +1064,7 @@ def __process_delete_block__(self, event): :param event: event to process :type event: PapiEventBase """ + print('Block Delete: ', event.blockname) pl_id = event.get_originID() self.core_data.rm_all_subscribers_of_a_dblock(pl_id, event.blockname) @@ -1074,9 +1075,26 @@ def __process_delete_block__(self, event): self.update_meta_data_to_gui_for_all() def __delete_parameter__(self, event): - print('Delete') - + pl_id = event.get_originID() + print('ToDelete:', event.parameterName) + dplugin = self.core_data.get_dplugin_by_id(pl_id) + if dplugin is not None: + # get connections of this dplugin + subscriptions = copy.deepcopy(dplugin.get_subscribtions()) + # iterate over all source plugins by id in subscription dict + for source_id in subscriptions: + # iterate over all blocks + for blockName in subscriptions[source_id]: + dSubObject = subscriptions[source_id][blockName] + # search for parameter to delete in subscription + if dSubObject.alias == event.parameterName: + self.core_data.unsubscribe(pl_id, source_id, blockName) + + paraObject = dplugin.get_parameters()[event.parameterName] + + dplugin.rm_parameter(paraObject) + self.update_meta_data_to_gui_for_all() def __process_new_parameter__(self, event): """ diff --git a/papi/data/DCore.py b/papi/data/DCore.py index a6e3710d..389f7035 100644 --- a/papi/data/DCore.py +++ b/papi/data/DCore.py @@ -46,7 +46,7 @@ def __init__(self): self.__DPlugins = {} self.__newid = 0 - self.log = ConsoleLog(2, "DCore") + self.log = ConsoleLog(2, "DCore: ") def create_id(self): """ @@ -396,8 +396,4 @@ def unsubscribe_signals(self, subscriber_id, target_id, dblock_name, signals): return True - def subscribe_dparameter(self, subscriber_id, target_id): - pass - def unsubscribe_dparameter(self, subscriber_id, target_id): - pass diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 34b475bd..6148fc14 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -143,9 +143,18 @@ def start_init(self, config=None): self.block_id = 0 - self.send_new_parameter_list([DParameter('Test1')]) - self.send_delete_parameter('Test1') - + # db = DBlock('SourceGroup0') + # db.add_signal(DSignal('TestSig1')) + # + # self.send_new_block_list([db]) + # + # self.send_delete_block('SourceGroup0') + # + # db = DBlock('SourceGroup0') + # db.add_signal(DSignal('TestSig2')) + # + # self.send_new_block_list([db]) + # self.blocks['SourceGroup0'] = db return True def pause(self): @@ -286,10 +295,13 @@ def update_parameter_list(self, ORTDParameter): def update_block_list(self,ORTDSources): - self.block_id = self.block_id +1 - newBlock = DBlock('SourceGroup'+str(self.block_id)) - self.blocks['SourceGroup'+str(self.block_id)] = newBlock - + #self.block_id = self.block_id +1 + #newBlock = DBlock('SourceGroup'+str(self.block_id)) + #self.blocks['SourceGroup'+str(self.block_id)] = newBlock + if 'SourceGroup0' in self.blocks: + self.send_delete_block('SourceGroup0') + newBlock = DBlock('SourceGroup0') + self.blocks['SourceGroup0'] = newBlock self.Sources = ORTDSources keys = list(self.Sources.keys()) for key in keys: @@ -300,8 +312,8 @@ def update_block_list(self,ORTDSources): self.send_new_block_list([newBlock]) # Remove BLOCKS - if 'SourceGroup'+str(self.block_id-1) in self.blocks: - self.send_delete_block(self.blocks.pop('SourceGroup'+str(self.block_id-1)).name) + #if 'SourceGroup'+str(self.block_id-1) in self.blocks: + #self.send_delete_block(self.blocks.pop('SourceGroup'+str(self.block_id-1)).name) def process_data_stream(self, SourceId, rev): # Received a data packet @@ -331,22 +343,13 @@ def process_finished_action(self, SourceId, rev): keys = list(self.signal_values.keys()) keys.sort() # REMARK: Die liste keys nur einmal sortieren; bei initialisierung - if self.separate == 1: - for key in keys: - # signals_to_send.append(signal_values[key]) - Source = self.Sources[str(key)] - NValues = int(Source['NValues_send']) - n = len(self.signal_values[key]) - t = np.linspace(self.t, self.t + 1 - 1 / NValues, NValues) - # flush data to papi - sig_name = self.Sources[str(key)]['SourceName'] - self.send_new_data(self.blocks[key].name, t, {sig_name:self.signal_values[key]}) - else: - signals_to_send = {} - for key in keys: - sig_name = self.Sources[str(key)]['SourceName'] - signals_to_send[sig_name] = self.signal_values[key] + signals_to_send = {} + for key in keys: + sig_name = self.Sources[str(key)]['SourceName'] + signals_to_send[sig_name] = self.signal_values[key] + + if len( list(self.blocks.keys()) ) >0: block = list(self.blocks.keys())[0] if len(self.blocks[block].signals) == len(signals_to_send): self.send_new_data(block, [self.t], signals_to_send ) From ec3045bd8610fab9aa59b03436940ca6edfc9ce7 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Fri, 6 Mar 2015 15:33:16 +0100 Subject: [PATCH 169/260] + added sinus and plot to the progress bar + added parameter for the progressbar to enable live changes + modified regex constant --- cfg_collection/progress_bar_example.xml | 895 ++++++++++++------ papi/constants.py | 3 +- papi/plugin/visual/ProgressBar/ProgressBar.py | 139 ++- 3 files changed, 699 insertions(+), 338 deletions(-) diff --git a/cfg_collection/progress_bar_example.xml b/cfg_collection/progress_bar_example.xml index cfd8d849..076eec49 100644 --- a/cfg_collection/progress_bar_example.xml +++ b/cfg_collection/progress_bar_example.xml @@ -1,5 +1,10 @@ - + + + + default + + 771 @@ -8,43 +13,19 @@ 853 - - - default - - Button - - Clack - 0 - Text for state 2 - - - 0 - 0 - ([-]{0,1}\d+\.\d+) - - - ButtonX2 + + PaPI-Tab + Used for tabs Click 0 Text for state 1 - - (159,54) - 1 - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - - - PaPI-Tab - Used for tabs - Button2 0 @@ -52,14 +33,33 @@ (150,55) 1 - \(([0-9]+),([0-9]+)\) Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 0 + 0 + ([-]{0,1}\d+\.\d+) 1 0 ([-]{0,1}\d+\.\d+) + + Clack + 0 + Text for state 2 + + + ButtonX2 + + + (159,54) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + @@ -78,75 +78,75 @@ 0 1 Min Range - \d+ Set minimum range for the progress bar. + \d+ - - 1 + + percent + 0 + Progress Value + Name of the scalar which is used for the progress bar. + + + (156,53) 1 - Show + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) - - 1 + + 0 0 - Show current/max + Show percent + Show progress in percent over the progress bar bool - A label next to the bar shows the current and max value - - - ProgressBarShowAll - - Button3 - Used for window title + + trigger + 0 + Trigger Value + Name of the scalar which is used to increment progress bar by one. - - reset + + 1 0 - Reset Value - Name of the scalar which is used to reset the progress bar. + Show current/max + A label next to the bar shows the current and max value + bool (314,0) 1 - \(([0-9]+),([0-9]+)\) Determine position: (x,y) + \(([0-9]+),([0-9]+)\) PaPI-Tab Used for tabs - - percent - 0 - Progress Value - Name of the scalar which is used for the progress bar. - - - (156,53) + + 1 1 - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) + Show - - trigger - 0 - Trigger Value - Name of the scalar which is used to increment progress bar by one. + + Button3 + Used for window title - - 0 + + reset 0 - Show percent - bool - Show progress in percent over the progress bar + Reset Value + Name of the scalar which is used to reset the progress bar. + + + ProgressBarShowAll 153 1 Show current/max - \d+ Set maximum range for the progress bar. + \d+ @@ -162,14 +162,14 @@ Trigger - - - trigger - percent + + + trigger + reset @@ -180,34 +180,15 @@ Button - - Clack - 0 - Text for state 2 - - - 0 - 0 - ([-]{0,1}\d+\.\d+) - - - ButtonX3 + + PaPI-Tab + Used for tabs Click 0 Text for state 1 - - (313,52) - 1 - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - - - PaPI-Tab - Used for tabs - Button3 0 @@ -215,14 +196,33 @@ (150,55) 1 - \(([0-9]+),([0-9]+)\) Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 0 + 0 + ([-]{0,1}\d+\.\d+) 1 0 ([-]{0,1}\d+\.\d+) + + Clack + 0 + Text for state 2 + + + ButtonX3 + + + (312,57) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + @@ -242,19 +242,19 @@ - 0 + 2.0 - - - trigger - - percent + + + trigger + + reset @@ -263,19 +263,19 @@ - ButtonX6 + ButtonReset Choose - ButtonReset + ButtonX5 Choose - ButtonX5 + ButtonX6 Choose @@ -285,34 +285,15 @@ Button - - ResetAll - 0 - Text for state 2 - - - 2 - 0 - ([-]{0,1}\d+\.\d+) - - - ButtonReset + + PaPI-Tab + Used for tabs ResetAll 0 Text for state 1 - - (309,176) - 1 - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - - - PaPI-Tab - Used for tabs - ResetAll 0 @@ -320,14 +301,33 @@ (150,55) 1 - \(([0-9]+),([0-9]+)\) Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 2 + 0 + ([-]{0,1}\d+\.\d+) 2 0 ([-]{0,1}\d+\.\d+) + + ResetAll + 0 + Text for state 2 + + + ButtonReset + + + (309,176) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + @@ -342,34 +342,15 @@ Button - - TriggerAll - 0 - Text for state 2 - - - 1 - 0 - ([-]{0,1}\d+\.\d+) - - - ButtonX5 - - + + PaPI-Tab + Used for tabs + + TriggerAll 0 Text for state 1 - - (154,176) - 1 - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - - - PaPI-Tab - Used for tabs - TriggerAll 0 @@ -377,14 +358,33 @@ (150,55) 1 - \(([0-9]+),([0-9]+)\) Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 1 + 0 + ([-]{0,1}\d+\.\d+) 1 0 ([-]{0,1}\d+\.\d+) + + TriggerAll + 0 + Text for state 2 + + + ButtonX5 + + + (154,176) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + @@ -399,34 +399,15 @@ Button - - To 20 - 0 - Text for state 2 - - - 0 - 0 - ([-]{0,1}\d+\.\d+) - - - ButtonX6 + + PaPI-Tab + Used for tabs To 20 0 Text for state 1 - - (2,174) - 1 - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - - - PaPI-Tab - Used for tabs - SetAll 0 @@ -434,14 +415,33 @@ (150,55) 1 - \(([0-9]+),([0-9]+)\) Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 0 + 0 + ([-]{0,1}\d+\.\d+) 0 0 ([-]{0,1}\d+\.\d+) + + To 20 + 0 + Text for state 2 + + + ButtonX6 + + + (2,174) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + @@ -460,75 +460,75 @@ 0 1 Min Range - \d+ Set minimum range for the progress bar. + \d+ - - 1 + + percent + 0 + Progress Value + Name of the scalar which is used for the progress bar. + + + (150,50) 1 - Show + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) - + 1 0 - Show current/max + Show percent + Show progress in percent over the progress bar bool - A label next to the bar shows the current and max value - - - ProgressBar - - Button1 - Used for window title + + trigger + 0 + Trigger Value + Name of the scalar which is used to increment progress bar by one. - - reset + + 1 0 - Reset Value - Name of the scalar which is used to reset the progress bar. + Show current/max + A label next to the bar shows the current and max value + bool (0,0) 1 - \(([0-9]+),([0-9]+)\) Determine position: (x,y) + \(([0-9]+),([0-9]+)\) PaPI-Tab Used for tabs - - percent - 0 - Progress Value - Name of the scalar which is used for the progress bar. - - - (150,50) + + 1 1 - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) + Show - - trigger - 0 - Trigger Value - Name of the scalar which is used to increment progress bar by one. + + Button1 + Used for window title - - 1 + + reset 0 - Show percent - bool - Show progress in percent over the progress bar + Reset Value + Name of the scalar which is used to reset the progress bar. + + + ProgressBar 100 1 Show current/max - \d+ Set maximum range for the progress bar. + \d+ @@ -536,60 +536,41 @@ - - Button - - trigger - - Trigger - - - trigger - percent + + + trigger + reset + + Button + + trigger + + Button - - Clack - 0 - Text for state 2 - - - 0 - 0 - ([-]{0,1}\d+\.\d+) - - - Button + + PaPI-Tab + Used for tabs Click 0 Text for state 1 - - (0,52) - 1 - \(([0-9]+),([0-9]+)\) - Determine position: (x,y) - - - PaPI-Tab - Used for tabs - Button1 0 @@ -597,14 +578,33 @@ (150,55) 1 - \(([0-9]+),([0-9]+)\) Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 0 + 0 + ([-]{0,1}\d+\.\d+) 1 0 ([-]{0,1}\d+\.\d+) + + Clack + 0 + Text for state 2 + + + Button + + + (0,52) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + @@ -623,23 +623,55 @@ 0 1 Min Range - \d+ Set minimum range for the progress bar. + \d+ - - 1 + + percent + 0 + Progress Value + Name of the scalar which is used for the progress bar. + + + (150,53) 1 - Show + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) - + 0 0 - Show current/max + Show percent + Show progress in percent over the progress bar bool - A label next to the bar shows the current and max value - - ProgressBarShowCurrentMax + + trigger + 0 + Trigger Value + Name of the scalar which is used to increment progress bar by one. + + + 0 + 0 + Show current/max + A label next to the bar shows the current and max value + bool + + + (158,0) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + + + PaPI-Tab + Used for tabs + + + 1 + 1 + Show Button2 @@ -651,47 +683,350 @@ Reset Value Name of the scalar which is used to reset the progress bar. + + ProgressBarShowCurrentMax + + + 100 + 1 + Show current/max + Set maximum range for the progress bar. + \d+ + + + + 0 + + + + + Trigger + + + percent + + + + trigger + + + + reset + + + + ButtonX2 + + trigger + + + + + + ProgressBar + + + -1 + 1 + Min Range + Set minimum range for the progress bar. + ([-]{0,1}\d+(.\d+)?) + + + f3_1 + 0 + Progress Value + Name of the signal whose first value is used to set the current value for the progress bar. + + + (204,53) + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 1 + 0 + Show percent + Show progress in percent over the progress bar + bool + + + trigger + 0 + Trigger Value + Name of the signal which triggers the progress bar to increment by one. + + + 1 + 0 + Show current/max + A label next to the bar shows the current and max value + bool + - (158,0) + (8,641) 1 + Determine position: (x,y) \(([0-9]+),([0-9]+)\) + + + PaPI-Tab + Used for tabs + + + 1 + 1 + Show + + + ProgressBar + Used for window title + + + reset + 0 + Reset Value + Name of the signal which triggers the progress bar to reset. + + + ProgressBarSinus + + + 1 + 1 + Max Range + Set maximum range for the progress bar. + ([-]{0,1}\d+(.\d+)?) + + + + 0 + + + + + Sinus + + + f3_1 + + + + + + Plot + + + [0 1 2 3 4] + 1 + Color + ^\[(\s*\d\s*)+\] + + + PlotSinus + Used for window title + + + [-0.998401550109 0.999506560366] + 1 + y: range + (\d+\.\d+) + + + [0 0 0 0 0] + 1 + Style + ^\[(\s*\d\s*)+\] + + + (7,338) + 1 Determine position: (x,y) + \(([0-9]+),([0-9]+)\) PaPI-Tab Used for tabs + + 0 + bool + Grid-X + ^(1|0)$ + + + 0 + bool + Grid-Y + ^(1|0)$ + + + (300,300) + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + Plot + + + 10 + (\d+) + + + 100 + 1 + Buffersize + ^(\d+)$ + + + 0 + bool + Rolling Plot + ^(1|0)$ + + + + [0 1 2 3 4] + 0 + 0 + 0 + [-0.998401550109 0.999506560366] + 100 + [0 0 0 0 0] + 10 + + + + + Sinus + + + f3_1 + + + + + + Sinus + + + 3 + [0-9]+ + + + Sinus + + + 1 + \d+.{0,1}\d* + + + + 0.6 + + + + + f1_f1DNAME + + + + + f3_1 + + + f3_2 + + + f3_scalar + + + + + f2_1 + + + + + + + ProgressBar + + + -2 + 1 + Min Range + Set minimum range for the progress bar. + ([-]{0,1}\d+(.\d+)?) + - percent + f3_1 0 Progress Value - Name of the scalar which is used for the progress bar. + Name of the signal whose first value is used to set the current value for the progress bar. - (150,53) + (185,53) 1 - \(([0-9]+),([0-9]+)\) Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 1 + 0 + Show percent + Show progress in percent over the progress bar + bool trigger 0 Trigger Value - Name of the scalar which is used to increment progress bar by one. + Name of the signal which triggers the progress bar to increment by one. - - 0 + + 1 0 - Show percent + Show current/max + A label next to the bar shows the current and max value bool - Show progress in percent over the progress bar + + + (218,641) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + + + PaPI-Tab + Used for tabs + + + 1 + 1 + Show + + + ProgressBar + Used for window title + + + reset + 0 + Reset Value + Name of the signal which triggers the progress bar to reset. + + + ProgressBarX2 - 100 + 4 1 - Show current/max - \d+ + Max Range Set maximum range for the progress bar. + ([-]{0,1}\d+(.\d+)?) @@ -700,24 +1035,10 @@ - ButtonX2 - - trigger - - - - Trigger - + Sinus + - trigger - - - - percent - - - - reset + f3_1 diff --git a/papi/constants.py b/papi/constants.py index 86bda83b..474b04fd 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -130,4 +130,5 @@ REGEX_SINGLE_INT = '[0-9]+' REGEX_BOOL_BIN = '^(1|0)$' REGEX_SINGLE_UNSIGNED_FLOAT_FORCED = '(\d+\.\d+)' -REGEX_SIGNED_FLOAT = r'([-]{0,1}\d+\.\d+)' \ No newline at end of file +REGEX_SIGNED_FLOAT = r'([-]{0,1}\d+\.\d+)' +REGEX_SIGNED_FLOAT_OR_INT = r'([-]{0,1}\d+(.\d+)?)' \ No newline at end of file diff --git a/papi/plugin/visual/ProgressBar/ProgressBar.py b/papi/plugin/visual/ProgressBar/ProgressBar.py index 9bc3d58d..f981ec4d 100644 --- a/papi/plugin/visual/ProgressBar/ProgressBar.py +++ b/papi/plugin/visual/ProgressBar/ProgressBar.py @@ -26,12 +26,12 @@ Date: Fri, 6 Mar 2015 16:27:23 +0100 Subject: [PATCH 170/260] removed wrong configuration attribute --- papi/plugin/visual/ProgressBar/ProgressBar.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/papi/plugin/visual/ProgressBar/ProgressBar.py b/papi/plugin/visual/ProgressBar/ProgressBar.py index f981ec4d..fc2042f8 100644 --- a/papi/plugin/visual/ProgressBar/ProgressBar.py +++ b/papi/plugin/visual/ProgressBar/ProgressBar.py @@ -280,11 +280,7 @@ def get_plugin_configuration(self): 'value': 'ProgressBar', 'tooltip': 'Used for window title' }, - 'color': { - 'value': '(212, 123, 123)', - 'type': 'color', - 'advanced' : '1' - },'size': { + 'size': { 'value': "(150,50)", 'regex': '\(([0-9]+),([0-9]+)\)', 'advanced': '1', From dbb054068533af715799bdc9f742c4299a342c0d Mon Sep 17 00:00:00 2001 From: SvKn Date: Fri, 6 Mar 2015 16:28:10 +0100 Subject: [PATCH 171/260] Update CHANGENLOG.md --- CHANGENLOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 57ffbcc4..52ae9ed2 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -12,6 +12,7 @@ v.1.XX * [improvement]: improved core performance while processing new data (see #20) * [improvement]: huge changes in the plotting plugin with performance benefits * [feature]: modified PaPI gui to have tabs. User can organise visual plugins in tabs, remove tabs, rename tabs. + * [plugin]: added a new visual plugin: ProgressBar v.1.0 --- From e90607663048993267716adec691c7caf7de3c8a Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 16 Mar 2015 16:20:03 +0100 Subject: [PATCH 172/260] Added new configuration item for LCDDisplay plugin: initial value --- papi/last_active_papi.xml | 714 ++------------------ papi/plugin/visual/LCDDisplay/LCDDisplay.py | 71 +- 2 files changed, 104 insertions(+), 681 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index ddb63afa..53d612aa 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,4 +1,4 @@ - + @@ -6,720 +6,142 @@ - - 771 - 853 + + 771 + - - Button + + LCDDisplay - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - 0 - Button2 - - - 0 - Click - Text for state 1 + LCD + Used for window title - - ButtonX2 + + 0.3 + Used as initial value for the LCD-Display + ([-]{0,1}\d+(.\d+)?) \(([0-9]+),([0-9]+)\) + (150,75) 1 - (150,55) Determine size: (height,width) - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (159,54) - Determine position: (x,y) - - - 0 - Clack - Text for state 2 - - - - - - - Parameter - - - - - - - ProgressBar - - - Show - 1 - 1 - - - Used for window title - Button3 - - - ProgressBarShowAll - - - \d+ - 1 - 153 - Set maximum range for the progress bar. - Show current/max - - - \(([0-9]+),([0-9]+)\) + + 0 + Used to offset displayed value 1 - (314,0) - Determine position: (x,y) + -?\d+(\.?\d+)? - - Name of the scalar which is used for the progress bar. - 0 - percent - Progress Value - - - \d+ + + 3 + Number of digits 1 - 0 - Set minimum range for the progress bar. - Min Range + [3-9] - - A label next to the bar shows the current and max value - 0 + 1 - bool - Show current/max - - - \(([0-9]+),([0-9]+)\) + Used to scale displayed value 1 - (156,53) - Determine size: (height,width) - - - Show progress in percent over the progress bar - 0 - 0 - bool - Show percent - - - Name of the scalar which is used to reset the progress bar. - 0 - reset - Reset Value - - - Used for tabs - PaPI-Tab - - - Name of the scalar which is used to increment progress bar by one. - 0 - trigger - Trigger Value - - - - 0 - - - - - ButtonX3 - - trigger - - - - Trigger - - - percent - - - - trigger - - - - reset - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - 0 - Button3 - - - 0 - Click - Text for state 1 + -?[1-9]+[0-9]*(\.?[0-9]+)? - ButtonX3 - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) + LCDDisplay - Used for tabs PaPI-Tab + Used for tabs \(([0-9]+),([0-9]+)\) + (0,0) 1 - (313,52) Determine position: (x,y) - - 0 - Clack - Text for state 2 - - - - - - - Parameter - - - - - - - Trigger - - - Trigger + + 1000 + 1 + Minimal time between updates (in ms) + [0-9]+ - 1.0 + 0.0 + 3 + 1.0 + 1000 - - - - trigger - - - - - percent - - - - - reset - - - - - - ButtonX6 - - Choose - - - - ButtonReset - - Choose - - - - ButtonX5 - - Choose - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 2 - - - ([-]{0,1}\d+\.\d+) - 0 - 2 - - - 0 - ResetAll - - - 0 - ResetAll - Text for state 1 - - - ButtonReset - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (309,176) - Determine position: (x,y) - - - 0 - ResetAll - Text for state 2 - - - - - - - Parameter - - - + - - Button + + LCDDisplay - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - 0 - TriggerAll + LCD + Used for window title - - 0 - TriggerAll - Text for state 1 - - - ButtonX5 + + -1.3 + Used as initial value for the LCD-Display + ([-]{0,1}\d+(.\d+)?) \(([0-9]+),([0-9]+)\) + (150,75) 1 - (150,55) Determine size: (height,width) - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (154,176) - Determine position: (x,y) - - - 0 - TriggerAll - Text for state 2 - - - - - - - Parameter - - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 + 0 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - 0 - SetAll - - - 0 - To 20 - Text for state 1 - - - ButtonX6 - - - \(([0-9]+),([0-9]+)\) + Used to offset displayed value 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab + -?\d+(\.?\d+)? - - \(([0-9]+),([0-9]+)\) + + 3 + Number of digits 1 - (2,174) - Determine position: (x,y) + [3-9] - - 0 - To 20 - Text for state 2 - - - - - - - Parameter - - - - - - - ProgressBar - - - Show - 1 - 1 - - - Used for window title - Button1 - - - ProgressBar - - - \d+ - 1 - 100 - Set maximum range for the progress bar. - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (0,0) - Determine position: (x,y) - - - Name of the scalar which is used for the progress bar. - 0 - percent - Progress Value - - - \d+ - 1 - 0 - Set minimum range for the progress bar. - Min Range - - - A label next to the bar shows the current and max value - 0 + 1 - bool - Show current/max - - - \(([0-9]+),([0-9]+)\) + Used to scale displayed value 1 - (150,50) - Determine size: (height,width) - - - Show progress in percent over the progress bar - 0 - 1 - bool - Show percent - - - Name of the scalar which is used to reset the progress bar. - 0 - reset - Reset Value - - - Used for tabs - PaPI-Tab - - - Name of the scalar which is used to increment progress bar by one. - 0 - trigger - Trigger Value - - - - 0 - - - - - Button - - trigger - - - - Trigger - - - percent - - - - trigger - - - - reset - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - 0 - Button1 - - - 0 - Click - Text for state 1 + -?[1-9]+[0-9]*(\.?[0-9]+)? - Button - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) + LCDDisplayX2 - Used for tabs PaPI-Tab + Used for tabs \(([0-9]+),([0-9]+)\) + (23,62) 1 - (0,52) - Determine position: (x,y) - - - 0 - Clack - Text for state 2 - - - - - - - Parameter - - - - - - - ProgressBar - - - Show - 1 - 1 - - - Used for window title - Button2 - - - ProgressBarShowCurrentMax - - - \d+ - 1 - 100 - Set maximum range for the progress bar. - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (158,0) Determine position: (x,y) - - Name of the scalar which is used for the progress bar. - 0 - percent - Progress Value - - - \d+ - 1 - 0 - Set minimum range for the progress bar. - Min Range - - - A label next to the bar shows the current and max value - 0 - 0 - bool - Show current/max - - - \(([0-9]+),([0-9]+)\) + + 1000 1 - (150,53) - Determine size: (height,width) - - - Show progress in percent over the progress bar - 0 - 0 - bool - Show percent - - - Name of the scalar which is used to reset the progress bar. - 0 - reset - Reset Value - - - Used for tabs - PaPI-Tab - - - Name of the scalar which is used to increment progress bar by one. - 0 - trigger - Trigger Value + Minimal time between updates (in ms) + [0-9]+ - 0 + 0.0 + 3 + 1.0 + 1000 - - - ButtonX2 - - trigger - - - - Trigger - - - percent - - - - trigger - - - - reset - - - + diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.py b/papi/plugin/visual/LCDDisplay/LCDDisplay.py index b6841fd7..b81d76ce 100644 --- a/papi/plugin/visual/LCDDisplay/LCDDisplay.py +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.py @@ -28,14 +28,11 @@ __author__ = 'Stefan' -from PySide.QtGui import QMdiSubWindow -import papi.pyqtgraph as pq from papi.plugin.base_classes.vip_base import vip_base from papi.data.DParameter import DParameter +import papi.constants as pc -import collections -import re import time from papi.pyqtgraph.Qt import QtGui, QtCore @@ -54,6 +51,7 @@ def initiate_layer_0(self, config=None): self.value_scale = float(self.config['value_scale']['value']) self.value_offset = float(self.config['value_offset']['value']) self.digit_count = int(self.config['digit_count']['value']) + self.init_value = float(self.config['value_init']['value']) # -------------------------------- # Create Widget @@ -61,7 +59,7 @@ def initiate_layer_0(self, config=None): # Create Widget needed for this plugin self.LcdWidget = QtGui.QLCDNumber() self.LcdWidget.setSmallDecimalPoint(True) - self.LcdWidget.display(0) + self.LcdWidget.display(self.init_value) self.LcdWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.LcdWidget.customContextMenuRequested.connect(self.show_context_menu) @@ -179,36 +177,39 @@ def get_plugin_configuration(self): # http://utilitymill.com/utility/Regex_For_Range config = { "updateFrequency": { - 'value': '1000', - 'regex': '[0-9]+', - 'display_text' : 'Minimal time between updates (in ms)', - 'advanced' : '1' - }, 'size': { - 'value': "(150,75)", - 'regex': '\(([0-9]+),([0-9]+)\)', - 'advanced': '1', - 'tooltip': 'Determine size: (height,width)' - }, 'name': { - 'value': 'LCD', - 'tooltip': 'Used for window title' - }, 'value_scale': { - 'value': '1', - 'tooltip': 'Used to scale displayed value', - 'regex': '-?[1-9]+[0-9]*(\.?[0-9]+)?', - 'advanced': '1' - }, 'value_offset': { - 'value': '0', - 'tooltip': 'Used to offset displayed value', - 'regex': '-?\d+(\.?\d+)?', - 'advanced': '1' - }, 'digit_count': { - 'value': '3', - 'tooltip': 'Number of digits', - 'regex': '[3-9]', - 'advanced': '1' - }, - - } + 'value': '1000', + 'regex': '[0-9]+', + 'display_text' : 'Minimal time between updates (in ms)', + 'advanced' : '1' + }, 'size': { + 'value': "(150,75)", + 'regex': '\(([0-9]+),([0-9]+)\)', + 'advanced': '1', + 'tooltip': 'Determine size: (height,width)' + }, 'name': { + 'value': 'LCD', + 'tooltip': 'Used for window title' + }, 'value_init': { + 'value': 0, + 'regex' : pc.REGEX_SIGNED_FLOAT_OR_INT, + 'tooltip': 'Used as initial value for the LCD-Display' + }, 'value_scale': { + 'value': '1', + 'tooltip': 'Used to scale displayed value', + 'regex': '-?[1-9]+[0-9]*(\.?[0-9]+)?', + 'advanced': '1' + }, 'value_offset': { + 'value': '0', + 'tooltip': 'Used to offset displayed value', + 'regex': '-?\d+(\.?\d+)?', + 'advanced': '1' + }, 'digit_count': { + 'value': '3', + 'tooltip': 'Number of digits', + 'regex': '[3-9]', + 'advanced': '1' + } + } return config def plugin_meta_updated(self): From 45125da4e5c5e6e325d7b42f6b95c3bf43687b65 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 16 Mar 2015 16:30:53 +0100 Subject: [PATCH 173/260] Added ability to change slider positon by using plus/minus key. --- papi/last_active_papi.xml | 137 ++++++------------------------- papi/plugin/pcp/slider/Slider.py | 11 +++ 2 files changed, 38 insertions(+), 110 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 53d612aa..926a1659 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,4 +1,4 @@ - + @@ -6,142 +6,59 @@ - - 853 - 771 + + 853 + - - LCDDisplay + + Slider - - LCD - Used for window title - - - 0.3 - Used as initial value for the LCD-Display - ([-]{0,1}\d+(.\d+)?) - - - \(([0-9]+),([0-9]+)\) - (150,75) - 1 - Determine size: (height,width) - - - 0 - Used to offset displayed value - 1 - -?\d+(\.?\d+)? - - - 3 - Number of digits - 1 - [3-9] - - - 1 - Used to scale displayed value - 1 - -?[1-9]+[0-9]*(\.?[0-9]+)? - - - LCDDisplay - - PaPI-Tab Used for tabs + PaPI-Tab + + + 0.0 \(([0-9]+),([0-9]+)\) - (0,0) - 1 Determine position: (x,y) - - - 1000 + (0,0) 1 - Minimal time between updates (in ms) - [0-9]+ - - - 0.0 - 3 - 1.0 - 1000 - - - - - - LCDDisplay - - LCD Used for window title - - - -1.3 - Used as initial value for the LCD-Display - ([-]{0,1}\d+(.\d+)?) + PaPI Slider \(([0-9]+),([0-9]+)\) - (150,75) - 1 Determine size: (height,width) - - - 0 - Used to offset displayed value + (150,75) 1 - -?\d+(\.?\d+)? - - 3 - Number of digits - 1 - [3-9] + + 1.0 - - 1 - Used to scale displayed value - 1 - -?[1-9]+[0-9]*(\.?[0-9]+)? + + [0-9]+ + 11 - LCDDisplayX2 - - - PaPI-Tab - Used for tabs - - - \(([0-9]+),([0-9]+)\) - (23,62) - 1 - Determine position: (x,y) - - - 1000 - 1 - Minimal time between updates (in ms) - [0-9]+ + Slider - - 0.0 - 3 - 1.0 - 1000 - - + + + + + SliderParameter1 + + + diff --git a/papi/plugin/pcp/slider/Slider.py b/papi/plugin/pcp/slider/Slider.py index 6e882776..1a8b4bf2 100644 --- a/papi/plugin/pcp/slider/Slider.py +++ b/papi/plugin/pcp/slider/Slider.py @@ -31,6 +31,7 @@ from papi.plugin.base_classes.pcp_base import pcp_base from PySide.QtGui import QSlider, QHBoxLayout, QWidget, QLineEdit from PySide import QtCore +from PySide.QtCore import Qt from papi.data.DPlugin import DBlock from papi.data.DSignal import DSignal @@ -79,6 +80,8 @@ def create_widget(self): self.slider.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.slider.customContextMenuRequested.connect(self.show_context_menu) + self.central_widget.keyPressEvent = self.key_event + return self.central_widget def show_context_menu(self, pos): @@ -122,5 +125,13 @@ def get_plugin_configuration(self): } } return config + def key_event(self, event): + + if event.key() == Qt.Key_Plus: + self.slider.setValue(self.slider.value() + 1) + + if event.key() == Qt.Key_Minus: + self.slider.setValue(self.slider.value() - 1) + def quit(self): pass \ No newline at end of file From 36343968b9c39ecd57ff3017f7a6d08bac7318b4 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 16 Mar 2015 16:38:43 +0100 Subject: [PATCH 174/260] refined some gui items created a gui_management object and added it to gui now yapsy plugin manager is not collecting at every creation. Now there is a reload option --- papi/gui/gui_api.py | 4 +- papi/gui/gui_event_processing.py | 8 +-- papi/gui/gui_management.py | 64 ++++++++++++++++++++ papi/gui/qt_new/create_plugin_menu.py | 11 ++-- papi/gui/qt_new/main.py | 86 ++++++++++----------------- papi/ui/gui/qt_new/create.py | 2 +- papi/ui/gui/qt_new/create_dialog.py | 2 +- papi/ui/gui/qt_new/main.py | 11 +++- papi/ui/gui/qt_new/overview.py | 2 +- ui/gui/qt_new/main.ui | 11 +++- 10 files changed, 127 insertions(+), 74 deletions(-) create mode 100644 papi/gui/gui_management.py diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index c8419f9c..a7ab8506 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -52,12 +52,12 @@ class Gui_api(QtCore.QObject): error_occured = QtCore.Signal(str, str, str) - def __init__(self, gui_data, core_queue, gui_id, get_gui_config_function = None, set_gui_config_function = None, LOG_IDENT=GUI_PROCESS_CONSOLE_IDENTIFIER): + def __init__(self, gui_data, core_queue, gui_id, get_gui_config_function = None, set_gui_config_function = None): super(Gui_api, self).__init__() self.gui_id = gui_id self.gui_data = gui_data self.core_queue = core_queue - self.log = ConsoleLog(GUI_PROCESS_CONSOLE_LOG_LEVEL, LOG_IDENT) + self.log = ConsoleLog(GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_PROCESS_CONSOLE_IDENTIFIER) self.get_gui_config_function = get_gui_config_function self.set_gui_config_function = set_gui_config_function diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 58cd839b..51669df5 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -59,7 +59,7 @@ class GuiEventProcessing(QtCore.QObject): dgui_changed = QtCore.Signal() plugin_died = QtCore.Signal(DPlugin, Exception, str) - def __init__(self, gui_data, core_queue, gui_id, gui_queue, TabManager): + def __init__(self, gui_data, core_queue, gui_id, gui_queue, TabManager, plugin_manager): """ Init for eventProcessing @@ -78,8 +78,8 @@ def __init__(self, gui_data, core_queue, gui_id, gui_queue, TabManager): self.core_queue = core_queue self.gui_id = gui_id self.log = ConsoleLog(GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_PROCESS_CONSOLE_IDENTIFIER) - self.plugin_manager = PluginManager() - self.plugin_manager.setPluginPlaces(PLUGIN_ROOT_FOLDER_LIST) + self.plugin_manager = plugin_manager + self.gui_queue = gui_queue self.TabManger = TabManager # switch case for event processing @@ -268,8 +268,6 @@ def process_create_plugin(self, event): self.log.printText(2, 'create_plugin, Try to create plugin with Name ' + plugin_identifier + " and UName " + uname) - # collect plugin in folder for yapsy manager - self.plugin_manager.collectPlugins() # get the plugin object from yapsy manager plugin_orginal = self.plugin_manager.getPluginByName(plugin_identifier) diff --git a/papi/gui/gui_management.py b/papi/gui/gui_management.py new file mode 100644 index 00000000..7c043536 --- /dev/null +++ b/papi/gui/gui_management.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: + - + + @@ -74,13 +75,14 @@ + + View - @@ -168,6 +170,11 @@ About PySide + + + Reload DB + + From 4ff8ad813aac00ea203f323ae0481bab672a6a6a Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Mon, 16 Mar 2015 16:44:02 +0100 Subject: [PATCH 175/260] Added initial value for the slider's configuration --- papi/last_active_papi.xml | 129 ++++++++++++++++++++++++++++--- papi/plugin/pcp/slider/Slider.py | 28 ++++--- 2 files changed, 138 insertions(+), 19 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 926a1659..bcf3f484 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,4 +1,4 @@ - + @@ -14,41 +14,150 @@ - + Slider + + 1.0 + - Used for tabs PaPI-Tab + Used for tabs + + + PaPI Slider + Used for window title + + + 11 + [0-9]+ + + + (150,75) + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + 1 0.0 - \(([0-9]+),([0-9]+)\) + (239,57) Determine position: (x,y) - (0,0) + \(([0-9]+),([0-9]+)\) 1 + + 0.81 + Used as initial value for the Slider + ([-]{0,1}\d+(.\d+)?) + + + SliderX3 + + + + + + + SliderParameter1 + + + + + + + Slider + + + 1.0 + + + PaPI-Tab + Used for tabs + - Used for window title PaPI Slider + Used for window title + + + 11 + [0-9]+ - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) (150,75) + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + 1 + + + 0.0 + + + (0,0) + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) 1 + + 0.57 + Used as initial value for the Slider + ([-]{0,1}\d+(.\d+)?) + + + Slider + + + + + + + SliderParameter1 + + + + + + + Slider + 1.0 + + PaPI-Tab + Used for tabs + + + PaPI Slider + Used for window title + - [0-9]+ 11 + [0-9]+ + + + (150,75) + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + 1 + + + 0.0 + + + (57,173) + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + 1 + + + 0.79 + Used as initial value for the Slider + ([-]{0,1}\d+(.\d+)?) - Slider + SliderX2 diff --git a/papi/plugin/pcp/slider/Slider.py b/papi/plugin/pcp/slider/Slider.py index 1a8b4bf2..f1433eca 100644 --- a/papi/plugin/pcp/slider/Slider.py +++ b/papi/plugin/pcp/slider/Slider.py @@ -29,13 +29,14 @@ __author__ = 'knuths' from papi.plugin.base_classes.pcp_base import pcp_base -from PySide.QtGui import QSlider, QHBoxLayout, QWidget, QLineEdit +from PySide.QtGui import QSlider, QHBoxLayout, QWidget, QLabel from PySide import QtCore from PySide.QtCore import Qt from papi.data.DPlugin import DBlock from papi.data.DSignal import DSignal -from papi.constants import REGEX_SINGLE_INT +import papi.constants as pc + class Slider(pcp_base): @@ -55,9 +56,13 @@ def create_widget(self): self.slider.sliderPressed.connect(self.clicked) self.slider.valueChanged.connect(self.value_changed) + + self.value_max = float(self.config['upper_bound']['value']) self.value_min = float(self.config['lower_bound']['value']) self.tick_count = float(self.config['step_count']['value']) + self.init_value = float(self.config['value_init']['value']) + self.tick_width = (self.value_max-self.value_min)/(self.tick_count-1) @@ -66,12 +71,12 @@ def create_widget(self): self.slider.setOrientation(QtCore.Qt.Horizontal) - self.text_field = QLineEdit() - self.text_field.setReadOnly(True) - self.text_field.setFixedWidth(50) - self.text_field.setText('0') - + self.text_field = QLabel() + self.text_field.setMinimumWidth(25) + self.text_field.setText(str(self.init_value)) + init_value = (self.init_value - self.value_min)/self.tick_width + self.slider.setValue(init_value) self.layout = QHBoxLayout(self.central_widget) self.layout.addWidget(self.slider) @@ -111,7 +116,7 @@ def get_plugin_configuration(self): }, 'step_count': { 'value': '11', - 'regex': REGEX_SINGLE_INT, + 'regex': pc.REGEX_SINGLE_INT, }, 'size': { 'value': "(150,75)", @@ -119,10 +124,15 @@ def get_plugin_configuration(self): 'advanced': '1', 'tooltip': 'Determine size: (height,width)' }, + 'value_init': { + 'value': 0, + 'regex' : pc.REGEX_SIGNED_FLOAT_OR_INT, + 'tooltip': 'Used as initial value for the Slider' + }, 'name': { 'value': 'PaPI Slider', 'tooltip': 'Used for window title' - } } + }} return config def key_event(self, event): From d3a140ac4c1d4945d5c1de9d77dbeea5ed4e5d17 Mon Sep 17 00:00:00 2001 From: SvKn Date: Mon, 16 Mar 2015 16:48:18 +0100 Subject: [PATCH 176/260] Update CHANGENLOG.md --- CHANGENLOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 52ae9ed2..c9febe9f 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -8,11 +8,13 @@ v.1.XX * [fix]: clean up of DParameter (#18) * [fix]: renamed some variables of ownProcess_base to be private * [fix]: fixed memory leak of gui event processing timer loop (#25) - * [improvement]: changed the demux function to in imporve performance + * [fix]: fixed memory leak due to recall of the plugin manager collection function + * [improvement]: changed the demux function to in improve performance * [improvement]: improved core performance while processing new data (see #20) * [improvement]: huge changes in the plotting plugin with performance benefits * [feature]: modified PaPI gui to have tabs. User can organise visual plugins in tabs, remove tabs, rename tabs. * [plugin]: added a new visual plugin: ProgressBar + * [plugin]: Minor changes in LCDDisplay and Slider v.1.0 --- From 8c436b7a66b8c02dde9e9bdbbd5da4709547f854 Mon Sep 17 00:00:00 2001 From: SvKn Date: Mon, 16 Mar 2015 16:49:21 +0100 Subject: [PATCH 177/260] Update LCDDisplay.yapsy-plugin --- papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin b/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin index a0c47853..5c721e48 100644 --- a/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin @@ -4,6 +4,6 @@ Module = LCDDisplay [Documentation] Author = S. Ruppin -Version = 0.1 +Version = 1.0 Website = -Description = LCD Display for displaying floating point numbers up to 5 digits with sign. Refresh rate is user defined. \ No newline at end of file +Description = LCD Display for displaying floating point numbers up to 5 digits with sign. Refresh rate is user defined. From 3a90d475abe9e86ec9a218156a4f6c46aadc2d7d Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Mon, 16 Mar 2015 16:50:31 +0100 Subject: [PATCH 178/260] refined some gui items created a gui_management object and added it to gui now yapsy plugin manager is not collecting at every creation. Now there is a reload option --- papi/last_active_papi.xml | 719 ++-------------------------- papi/ui/gui/qt_new/create.py | 2 +- papi/ui/gui/qt_new/create_dialog.py | 2 +- papi/ui/gui/qt_new/main.py | 2 +- papi/ui/gui/qt_new/overview.py | 2 +- 5 files changed, 54 insertions(+), 673 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index ddb63afa..4bf4f530 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,725 +1,106 @@ - + - + default - + - - 771 - 853 + + 771 + - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - 0 - Button2 - - - 0 - Click - Text for state 1 - - - ButtonX2 - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (159,54) - Determine position: (x,y) - - - 0 - Clack - Text for state 2 - - - - - - - Parameter - - - - - - - ProgressBar + + ORTD_UDP - - Show + + 20001 1 - 1 - - Used for window title - Button3 - - - ProgressBarShowAll - - - \d+ - 1 - 153 - Set maximum range for the progress bar. - Show current/max - - - \(([0-9]+),([0-9]+)\) + + 20000 1 - (314,0) - Determine position: (x,y) - - Name of the scalar which is used for the progress bar. + + /home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json + file 0 - percent - Progress Value - - \d+ + + 127.0.0.1 1 - 0 - Set minimum range for the progress bar. - Min Range - - - A label next to the bar shows the current and max value - 0 - 1 - bool - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (156,53) - Determine size: (height,width) - - - Show progress in percent over the progress bar - 0 - 0 - bool - Show percent - - - Name of the scalar which is used to reset the progress bar. - 0 - reset - Reset Value - - - Used for tabs - PaPI-Tab - - - Name of the scalar which is used to increment progress bar by one. - 0 - trigger - Trigger Value - 0 + 0 + 0 + 0 + 1 - - - - ButtonX3 - - trigger - - - - Trigger - - - percent - - - - trigger - - - - reset - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - 0 - Button3 - - - 0 - Click - Text for state 1 - - - ButtonX3 - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (313,52) - Determine position: (x,y) - - - 0 - Clack - Text for state 2 - - - - - - Parameter + + + ControlSignalReset - - - - - - Trigger - - - Trigger - - - - 1.0 - - - - - trigger + + ControlSignalCreate - - - - percent + + ControlSignalSub - - - - reset + + ControllerSignalParameter - - - - - ButtonX6 - - Choose - - - - ButtonReset - - Choose - - - - ButtonX5 - - Choose - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 2 - - - ([-]{0,1}\d+\.\d+) - 0 - 2 - - - 0 - ResetAll - - - 0 - ResetAll - Text for state 1 - - - ButtonReset - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (309,176) - Determine position: (x,y) - - - 0 - ResetAll - Text for state 2 - - - - - - - Parameter + + ControllerSignalClose - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - 0 - TriggerAll - - - 0 - TriggerAll - Text for state 1 - - - ButtonX5 - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (154,176) - Determine position: (x,y) - - - 0 - TriggerAll - Text for state 2 - - - - - - - Parameter + + + V - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - 0 - SetAll - - - 0 - To 20 - Text for state 1 - - - ButtonX6 - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (2,174) - Determine position: (x,y) - - - 0 - To 20 - Text for state 2 - - - - - - - Parameter + + X - - - - ProgressBar - - - Show - 1 - 1 - - - Used for window title - Button1 - - - ProgressBar - - - \d+ - 1 - 100 - Set maximum range for the progress bar. - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (0,0) - Determine position: (x,y) - - - Name of the scalar which is used for the progress bar. - 0 - percent - Progress Value - - - \d+ - 1 - 0 - Set minimum range for the progress bar. - Min Range - - - A label next to the bar shows the current and max value - 0 - 1 - bool - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (150,50) - Determine size: (height,width) - - - Show progress in percent over the progress bar - 0 - 1 - bool - Show percent - - - Name of the scalar which is used to reset the progress bar. - 0 - reset - Reset Value - - - Used for tabs - PaPI-Tab - - - Name of the scalar which is used to increment progress bar by one. - 0 - trigger - Trigger Value - - - - 0 - - - Button + Butt1 - trigger - - - - Trigger - - - percent - - - - trigger - - - - reset + Oscillator input - + Button - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - 0 - Button1 - - 0 - Click + Go to state 2 Text for state 1 - - - Button - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab + 0 - \(([0-9]+),([0-9]+)\) - 1 - (0,52) - Determine position: (x,y) + (600,0) - 0 - Clack + Go to state 1 Text for state 2 - - - - - - - Parameter - - - - - - - ProgressBar - - - Show - 1 - 1 - - - Used for window title - Button2 - - - ProgressBarShowCurrentMax - - - \d+ - 1 - 100 - Set maximum range for the progress bar. - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (158,0) - Determine position: (x,y) - - - Name of the scalar which is used for the progress bar. - 0 - percent - Progress Value - - - \d+ - 1 - 0 - Set minimum range for the progress bar. - Min Range - - - A label next to the bar shows the current and max value - 0 - 0 - bool - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (150,53) - Determine size: (height,width) - - - Show progress in percent over the progress bar 0 - 0 - bool - Show percent - - - Name of the scalar which is used to reset the progress bar. - 0 - reset - Reset Value + Tab Used for tabs - PaPI-Tab - - Name of the scalar which is used to increment progress bar by one. + + + ([-]{0,1}\d+\.\d+) 0 - trigger - Trigger Value - - - 0 - - - - - ButtonX2 - - trigger - - - - Trigger - - - percent - - - - trigger - - - - reset - - - - - + + \ No newline at end of file diff --git a/papi/ui/gui/qt_new/create.py b/papi/ui/gui/qt_new/create.py index c6ebfc17..162686d5 100644 --- a/papi/ui/gui/qt_new/create.py +++ b/papi/ui/gui/qt_new/create.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/create.ui' # -# Created: Mon Mar 16 16:37:08 2015 +# Created: Mon Mar 16 16:42:19 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/create_dialog.py b/papi/ui/gui/qt_new/create_dialog.py index 380c958f..d9439453 100644 --- a/papi/ui/gui/qt_new/create_dialog.py +++ b/papi/ui/gui/qt_new/create_dialog.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/create_dialog.ui' # -# Created: Mon Mar 16 16:37:08 2015 +# Created: Mon Mar 16 16:42:19 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/main.py b/papi/ui/gui/qt_new/main.py index d2986a0c..850ce96f 100644 --- a/papi/ui/gui/qt_new/main.py +++ b/papi/ui/gui/qt_new/main.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/main.ui' # -# Created: Mon Mar 16 16:37:08 2015 +# Created: Mon Mar 16 16:42:20 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/overview.py b/papi/ui/gui/qt_new/overview.py index 0a6fc1aa..837dc1ec 100644 --- a/papi/ui/gui/qt_new/overview.py +++ b/papi/ui/gui/qt_new/overview.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/overview.ui' # -# Created: Mon Mar 16 16:37:08 2015 +# Created: Mon Mar 16 16:42:19 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! From 2024c101f50931d5ce1980771704557bc2d5c8cb Mon Sep 17 00:00:00 2001 From: SvKn Date: Mon, 16 Mar 2015 16:51:45 +0100 Subject: [PATCH 179/260] Update ProgressBar.yapsy-plugin --- papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin b/papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin index 1b698480..91cb040d 100644 --- a/papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin +++ b/papi/plugin/visual/ProgressBar/ProgressBar.yapsy-plugin @@ -4,6 +4,6 @@ Module = ProgressBar [Documentation] Author = S. Knuth -Version = 0.1 +Version = 1.0 Website = -Description = Simple progress bar which is used to display a value between 0 and 100. A simple increment by one is possible by using the 'trigger'-Parameter. It is possible to subscribe signals to controll the progress bar. By sending \ No newline at end of file +Description = Simple progress bar which is used to display a value between a user defined minimum and maximum value. A simple increment by one is possible by using the 'trigger'-Parameter. It is possible to subscribe signals to controll the progress bar. From 4d14c56c9e9701369aa6e798c62bec07d7167328 Mon Sep 17 00:00:00 2001 From: SvKn Date: Mon, 16 Mar 2015 16:52:20 +0100 Subject: [PATCH 180/260] Update CHANGENLOG.md --- CHANGENLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index c9febe9f..eca57886 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -15,7 +15,7 @@ v.1.XX * [feature]: modified PaPI gui to have tabs. User can organise visual plugins in tabs, remove tabs, rename tabs. * [plugin]: added a new visual plugin: ProgressBar * [plugin]: Minor changes in LCDDisplay and Slider - + v.1.0 --- From 4c963f9fd4ff3aaa8e3c7d4ec6159f4160773442 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Thu, 19 Mar 2015 19:31:37 +0100 Subject: [PATCH 181/260] deleted ortd example --- .../ProtocollConfig.json | 11 - .../SelectCasePaPiConfig.ipar | 15071 ---------------- .../SelectCasePaPiConfig.rpar | 26 - .../SelectCasePaPiConfig.sce | 343 - .../run_SelectCasePaPiConfig.sh | 4 - .../webinterface/PacketFramework.sce | 1202 -- 6 files changed, 16657 deletions(-) delete mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/ProtocollConfig.json delete mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar delete mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar delete mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce delete mode 100755 data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh delete mode 100644 data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/ProtocollConfig.json b/data_sources/ORTD/DataSourceAutoConfigExample/ProtocollConfig.json deleted file mode 100644 index bb8d1b0d..00000000 --- a/data_sources/ORTD/DataSourceAutoConfigExample/ProtocollConfig.json +++ /dev/null @@ -1,11 +0,0 @@ - {"SourcesConfig" : { - "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257" } , - "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257" } -} , - "ParametersConfig" : { - "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , - "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , - "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } -}, -"PaPIConfig" : { "ToCreate" : { "plot_X" : { "identifier" : { "value" : "plot" } , "config" : { "size" : { "value" : "(300,300)" } , "position" : { "value" : "(300,0)" } , "name" : { "value" : "Plot X" } } } } , "ToSub" : { "plot_X" : { "block" : "SourceGroup0" , "signals" : ["X"] } } } -} \ No newline at end of file diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar deleted file mode 100644 index d269e69e..00000000 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.ipar +++ /dev/null @@ -1,15071 +0,0 @@ - 1 - 1 - 901 - 10 - 0 - 0 - 15063 - 26 - 1 - 3 - 201 - 100 - 0 - 0 - 6 - 0 - 203 - 100 - 6 - 0 - 15025 - 26 - 100 - 101 - 15031 - 26 - 12 - 0 - 60023 - 201 - 0 - 0 - 1 - 0 - 15011 - 203 - 15019 - 26 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 - 2 - 0 - 14 - 4 - 8 - 0 - 2 - 0 - 15 - 4 - 10 - 0 - 2 - 0 - 20 - 4 - 12 - 0 - 14956 - 0 - 21 - 1 - 14968 - 0 - 1 - 26 - 1 - 1 - 1 - 1 - 1 - 257 - 1 - 257 - 1 - 1 - 1 - 1 - 14955 - 1 - 7 - 10 - 4 - 0 - 0 - 1 - 0 - 11 - 4 - 1 - 0 - 1 - 0 - 12 - 4 - 2 - 0 - 1 - 0 - 13 - 4 - 3 - 0 - 1 - 0 - 21 - 4 - 4 - 0 - 19 - 0 - 22 - 4 - 23 - 0 - 4 - 0 - 900 - 10 - 27 - 0 - 14884 - 26 - 0 - 0 - 0 - 0 - 18 - 77 - 97 - 105 - 110 - 82 - 101 - 97 - 108 - 116 - 105 - 109 - 101 - 84 - 104 - 114 - 101 - 97 - 100 - 3 - 2 - 0 - -1 - 1 - 6 - 201 - 100 - 0 - 0 - 6 - 1 - 203 - 100 - 6 - 1 - 72 - 0 - 205 - 100 - 78 - 1 - 6 - 1 - 207 - 100 - 84 - 2 - 14719 - 24 - 211 - 100 - 14803 - 26 - 15 - 0 - 100 - 101 - 14818 - 26 - 28 - 0 - 40 - 201 - 0 - 1 - 1 - 0 - 15102 - 203 - 66 - 0 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 - 2 - 0 - 14 - 4 - 8 - 0 - 2 - 0 - 15 - 4 - 10 - 0 - 2 - 0 - 20 - 4 - 12 - 0 - 3 - 0 - 21 - 1 - 15 - 0 - 1 - 0 - 1 - 1 - 1 - 1 - 1 - 257 - 1 - 257 - 1 - 1 - 1 - 1 - 2 - 1 - 0 - 0 - 40 - 205 - 0 - 1 - 1 - 0 - 15002 - 207 - 14713 - 24 - 1 - 0 - 1 - 9 - 10 - 4 - 0 - 0 - 3 - 0 - 11 - 4 - 3 - 0 - 4 - 0 - 12 - 4 - 7 - 0 - 3 - 0 - 13 - 4 - 10 - 0 - 4 - 0 - 20 - 4 - 14 - 0 - 4 - 0 - 21 - 1 - 18 - 0 - 1 - 1 - 900 - 10 - 19 - 1 - 357 - 10 - 901 - 10 - 376 - 11 - 14219 - 11 - 902 - 10 - 14595 - 22 - 62 - 2 - 2 - 1 - 1 - 3 - 1 - 1 - 1 - 2 - 257 - 257 - 3 - 257 - 257 - 257 - 3 - 3 - 1 - 1 - 1 - 1 - 6 - 203 - 100 - 0 - 0 - 6 - 1 - 205 - 100 - 6 - 1 - 6 - 1 - 207 - 100 - 12 - 2 - 227 - 6 - 210 - 100 - 239 - 8 - 6 - 1 - 212 - 100 - 245 - 9 - 6 - 1 - 100 - 101 - 251 - 10 - 68 - 0 - 40 - 203 - 0 - 1 - 1 - 0 - 40 - 205 - 0 - 1 - 1 - 0 - 15001 - 207 - 221 - 6 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 3 - 0 - 12 - 4 - 5 - 0 - 2 - 0 - 13 - 4 - 7 - 0 - 3 - 0 - 20 - 4 - 10 - 0 - 4 - 0 - 21 - 4 - 14 - 0 - 37 - 0 - 900 - 10 - 51 - 0 - 54 - 2 - 901 - 10 - 105 - 2 - 66 - 4 - 1 - 1 - 2 - 1 - 1 - 1 - 257 - 2 - 257 - 257 - 3 - 2 - 1 - 0 - 36 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 68 - 101 - 109 - 111 - 95 - 82 - 101 - 112 - 108 - 97 - 99 - 101 - 97 - 98 - 108 - 101 - 83 - 105 - 109 - 117 - 108 - 97 - 116 - 105 - 111 - 110 - 1 - 3 - 202 - 100 - 0 - 0 - 6 - 1 - 204 - 100 - 6 - 1 - 8 - 1 - 100 - 101 - 14 - 2 - 20 - 0 - 40 - 202 - 0 - 1 - 1 - 0 - 60010 - 204 - 2 - 1 - 1 - 0 - 1 - 0 - 0 - 2 - 8 - 4 - 0 - 204 - 204 - 0 - 1 - 0 - 0 - 0 - 0 - 202 - 202 - 0 - 1 - 0 - 0 - 1 - 1 - 4 - 202 - 100 - 0 - 0 - 6 - 1 - 204 - 100 - 6 - 1 - 6 - 1 - 206 - 100 - 12 - 2 - 8 - 2 - 100 - 101 - 20 - 4 - 20 - 0 - 40 - 202 - 0 - 1 - 1 - 0 - 40 - 204 - 0 - 1 - 1 - 0 - 60026 - 206 - 2 - 2 - 1 - 0 - 2 - 10 - 0 - 2 - 8 - 4 - 0 - 204 - 204 - 0 - 1 - 0 - 0 - 0 - 0 - 206 - 206 - 0 - 1 - 0 - 0 - 1 - 40 - 210 - 0 - 1 - 1 - 0 - 60020 - 212 - 0 - 1 - 1 - 0 - 0 - 8 - 8 - 4 - 1 - 0 - 0 - 0 - 0 - 207 - 207 - 0 - 0 - 205 - 205 - 0 - 0 - 207 - 207 - 1 - 0 - 203 - 203 - 0 - 0 - 207 - 207 - 2 - 0 - 207 - 207 - 1 - 0 - 212 - 212 - 0 - 0 - 210 - 210 - 0 - 0 - 212 - 212 - 1 - 0 - 207 - 207 - 0 - 1 - 0 - 0 - 0 - 0 - 212 - 212 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 1 - 17 - 203 - 100 - 0 - 0 - 6 - 0 - 205 - 100 - 6 - 0 - 13801 - 4 - 208 - 100 - 13807 - 4 - 6 - 0 - 210 - 100 - 13813 - 4 - 7 - 0 - 212 - 100 - 13820 - 4 - 8 - 0 - 215 - 100 - 13828 - 4 - 6 - 0 - 217 - 100 - 13834 - 4 - 28 - 0 - 218 - 100 - 13862 - 4 - 31 - 0 - 219 - 100 - 13893 - 4 - 6 - 1 - 221 - 100 - 13899 - 5 - 24 - 0 - 222 - 100 - 13923 - 5 - 6 - 1 - 224 - 100 - 13929 - 6 - 6 - 1 - 226 - 100 - 13935 - 7 - 6 - 1 - 228 - 100 - 13941 - 8 - 6 - 1 - 230 - 100 - 13947 - 9 - 6 - 1 - 232 - 100 - 13953 - 10 - 6 - 1 - 100 - 101 - 13959 - 11 - 156 - 0 - 60023 - 203 - 0 - 0 - 1 - 0 - 15011 - 205 - 13795 - 4 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 3 - 0 - 12 - 4 - 5 - 0 - 2 - 0 - 13 - 4 - 7 - 0 - 3 - 0 - 14 - 4 - 10 - 0 - 2 - 0 - 15 - 4 - 12 - 0 - 2 - 0 - 20 - 4 - 14 - 0 - 13730 - 0 - 21 - 1 - 13744 - 0 - 1 - 4 - 1 - 1 - 2 - 1 - 1 - 1 - 257 - 2 - 2 - 257 - 1 - 1 - 1 - 1 - 13729 - 1 - 7 - 10 - 4 - 0 - 0 - 1 - 0 - 11 - 4 - 1 - 0 - 2 - 0 - 12 - 4 - 3 - 0 - 1 - 0 - 13 - 4 - 4 - 0 - 2 - 0 - 21 - 4 - 6 - 0 - 12 - 0 - 22 - 4 - 18 - 0 - 4 - 0 - 900 - 10 - 22 - 0 - 13663 - 4 - 0 - 1 - 1 - 0 - 1 - 2 - 11 - 67 - 111 - 109 - 112 - 32 - 84 - 104 - 114 - 101 - 97 - 100 - 3 - 2 - 0 - -1 - 1 - 10 - 201 - 100 - 0 - 0 - 6 - 1 - 203 - 100 - 6 - 1 - 13311 - 0 - 205 - 100 - 13317 - 1 - 44 - 0 - 206 - 100 - 13361 - 1 - 6 - 1 - 208 - 100 - 13367 - 2 - 6 - 1 - 210 - 100 - 13373 - 3 - 8 - 0 - 212 - 100 - 13381 - 3 - 8 - 0 - 214 - 100 - 13389 - 3 - 6 - 1 - 216 - 100 - 13395 - 4 - 130 - 0 - 100 - 101 - 13525 - 4 - 76 - 0 - 40 - 201 - 0 - 1 - 1 - 0 - 22000 - 203 - 13305 - 0 - 1 - 0 - 1 - 20 - 1 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 13078 - 105 - 94 - 12 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 32 - 98 - 108 - 111 - 99 - 107 - 61 - 99 - 111 - 109 - 112 - 95 - 102 - 110 - 40 - 98 - 108 - 111 - 99 - 107 - 44 - 32 - 102 - 108 - 97 - 103 - 41 - 10 - 32 - 32 - 47 - 47 - 32 - 84 - 104 - 105 - 115 - 32 - 115 - 99 - 105 - 108 - 97 - 98 - 32 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 32 - 105 - 115 - 32 - 99 - 97 - 108 - 108 - 101 - 100 - 32 - 100 - 117 - 114 - 105 - 110 - 103 - 32 - 114 - 117 - 110 - 45 - 116 - 105 - 109 - 101 - 10 - 32 - 32 - 47 - 47 - 32 - 78 - 79 - 84 - 69 - 58 - 32 - 80 - 108 - 101 - 97 - 115 - 101 - 32 - 110 - 111 - 116 - 101 - 32 - 116 - 104 - 97 - 116 - 32 - 116 - 104 - 101 - 32 - 118 - 97 - 114 - 105 - 97 - 98 - 108 - 101 - 115 - 32 - 100 - 101 - 102 - 105 - 110 - 101 - 100 - 32 - 111 - 117 - 116 - 115 - 105 - 100 - 101 - 32 - 116 - 104 - 105 - 115 - 10 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 32 - 97 - 114 - 101 - 32 - 116 - 121 - 112 - 105 - 99 - 97 - 108 - 108 - 121 - 32 - 110 - 111 - 116 - 32 - 97 - 118 - 97 - 105 - 108 - 97 - 98 - 108 - 101 - 32 - 97 - 116 - 32 - 114 - 117 - 110 - 45 - 116 - 105 - 109 - 101 - 46 - 10 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 84 - 104 - 105 - 115 - 32 - 97 - 108 - 115 - 111 - 32 - 104 - 111 - 108 - 100 - 115 - 32 - 116 - 114 - 117 - 101 - 32 - 102 - 111 - 114 - 32 - 115 - 101 - 108 - 102 - 32 - 100 - 101 - 102 - 105 - 110 - 101 - 100 - 32 - 83 - 99 - 105 - 108 - 97 - 98 - 32 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 115 - 33 - 10 - 32 - 32 - 10 - 32 - 32 - 115 - 101 - 108 - 101 - 99 - 116 - 32 - 102 - 108 - 97 - 103 - 10 - 32 - 32 - 99 - 97 - 115 - 101 - 32 - 49 - 59 - 32 - 47 - 47 - 32 - 111 - 117 - 116 - 112 - 117 - 116 - 32 - 116 - 104 - 101 - 110 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 115 - 112 - 108 - 105 - 116 - 32 - 116 - 104 - 101 - 32 - 115 - 101 - 110 - 115 - 111 - 114 - 32 - 100 - 97 - 116 - 97 - 10 - 32 - 32 - 32 - 32 - 100 - 97 - 116 - 97 - 32 - 61 - 32 - 98 - 108 - 111 - 99 - 107 - 46 - 105 - 110 - 112 - 116 - 114 - 40 - 49 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 98 - 108 - 111 - 99 - 107 - 46 - 115 - 116 - 97 - 116 - 101 - 115 - 46 - 69 - 120 - 101 - 99 - 67 - 111 - 117 - 110 - 116 - 101 - 114 - 32 - 61 - 32 - 98 - 108 - 111 - 99 - 107 - 46 - 115 - 116 - 97 - 116 - 101 - 115 - 46 - 69 - 120 - 101 - 99 - 67 - 111 - 117 - 110 - 116 - 101 - 114 - 32 - 43 - 32 - 49 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 103 - 101 - 116 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 10 - 32 - 32 - 32 - 32 - 116 - 114 - 121 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 32 - 61 - 32 - 98 - 108 - 111 - 99 - 107 - 46 - 115 - 116 - 97 - 116 - 101 - 115 - 46 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 59 - 10 - 32 - 32 - 32 - 32 - 99 - 97 - 116 - 99 - 104 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 105 - 115 - 73 - 110 - 105 - 116 - 105 - 97 - 108 - 105 - 115 - 101 - 100 - 32 - 61 - 32 - 37 - 102 - 59 - 10 - 32 - 32 - 32 - 32 - 101 - 110 - 100 - 44 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 32 - 115 - 104 - 111 - 117 - 108 - 100 - 32 - 99 - 111 - 110 - 116 - 97 - 105 - 110 - 32 - 116 - 104 - 101 - 32 - 115 - 101 - 110 - 115 - 111 - 114 - 32 - 100 - 97 - 116 - 97 - 10 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 83 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 73 - 110 - 102 - 111 - 32 - 61 - 32 - 39 - 84 - 104 - 105 - 115 - 32 - 115 - 99 - 104 - 101 - 109 - 116 - 105 - 99 - 32 - 119 - 97 - 115 - 32 - 99 - 111 - 109 - 112 - 105 - 108 - 101 - 100 - 32 - 100 - 117 - 114 - 105 - 110 - 103 - 32 - 114 - 117 - 110 - 116 - 105 - 109 - 101 - 32 - 105 - 110 - 32 - 105 - 116 - 101 - 114 - 97 - 116 - 105 - 111 - 110 - 32 - 35 - 39 - 32 - 43 - 32 - 115 - 116 - 114 - 105 - 110 - 103 - 40 - 98 - 108 - 111 - 99 - 107 - 46 - 115 - 116 - 97 - 116 - 101 - 115 - 46 - 69 - 120 - 101 - 99 - 67 - 111 - 117 - 110 - 116 - 101 - 114 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 73 - 110 - 112 - 117 - 116 - 68 - 97 - 116 - 97 - 32 - 61 - 32 - 100 - 97 - 116 - 97 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 39 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 115 - 32 - 116 - 111 - 32 - 116 - 104 - 105 - 115 - 32 - 99 - 111 - 109 - 112 - 117 - 116 - 97 - 116 - 105 - 111 - 110 - 97 - 108 - 32 - 83 - 99 - 105 - 108 - 97 - 98 - 32 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 58 - 92 - 110 - 39 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 100 - 105 - 115 - 112 - 40 - 99 - 102 - 112 - 97 - 114 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 105 - 110 - 115 - 105 - 122 - 101 - 115 - 32 - 61 - 32 - 99 - 102 - 112 - 97 - 114 - 46 - 105 - 110 - 115 - 105 - 122 - 101 - 115 - 59 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 32 - 61 - 32 - 99 - 102 - 112 - 97 - 114 - 46 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 59 - 10 - 32 - 32 - 32 - 32 - 105 - 110 - 116 - 121 - 112 - 101 - 115 - 32 - 61 - 32 - 99 - 102 - 112 - 97 - 114 - 46 - 105 - 110 - 116 - 121 - 112 - 101 - 115 - 59 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 116 - 121 - 112 - 101 - 115 - 32 - 61 - 32 - 99 - 102 - 112 - 97 - 114 - 46 - 111 - 117 - 116 - 116 - 121 - 112 - 101 - 115 - 59 - 10 - 32 - 32 - 32 - 32 - 105 - 100 - 101 - 110 - 116 - 95 - 115 - 116 - 114 - 32 - 61 - 32 - 99 - 102 - 112 - 97 - 114 - 46 - 105 - 100 - 101 - 110 - 116 - 95 - 115 - 116 - 114 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 32 - 32 - 105 - 110 - 115 - 105 - 122 - 101 - 115 - 61 - 91 - 50 - 93 - 59 - 32 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 61 - 91 - 49 - 93 - 59 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 32 - 32 - 105 - 110 - 116 - 121 - 112 - 101 - 115 - 61 - 91 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 93 - 59 - 32 - 111 - 117 - 116 - 116 - 121 - 112 - 101 - 115 - 61 - 91 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 93 - 59 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 105 - 100 - 101 - 110 - 116 - 95 - 115 - 116 - 114 - 32 - 61 - 32 - 34 - 65 - 117 - 116 - 111 - 67 - 97 - 108 - 105 - 98 - 68 - 101 - 109 - 111 - 34 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 97 - 112 - 112 - 101 - 110 - 100 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 32 - 115 - 105 - 103 - 110 - 97 - 108 - 32 - 116 - 111 - 32 - 116 - 104 - 101 - 32 - 111 - 117 - 116 - 112 - 117 - 116 - 115 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 95 - 95 - 32 - 61 - 32 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 59 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 95 - 95 - 40 - 36 - 32 - 43 - 32 - 49 - 41 - 32 - 61 - 32 - 49 - 59 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 116 - 121 - 112 - 101 - 115 - 95 - 95 - 32 - 61 - 32 - 111 - 117 - 116 - 116 - 121 - 112 - 101 - 115 - 59 - 111 - 117 - 116 - 116 - 121 - 112 - 101 - 115 - 95 - 95 - 40 - 36 - 32 - 43 - 32 - 49 - 41 - 32 - 61 - 32 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 34 - 68 - 101 - 102 - 105 - 110 - 105 - 110 - 103 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 58 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 95 - 117 - 115 - 101 - 114 - 32 - 117 - 115 - 105 - 110 - 103 - 32 - 116 - 104 - 101 - 32 - 102 - 111 - 108 - 108 - 111 - 119 - 105 - 110 - 103 - 32 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 58 - 92 - 110 - 34 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 100 - 105 - 115 - 112 - 40 - 102 - 117 - 110 - 50 - 115 - 116 - 114 - 105 - 110 - 103 - 40 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 95 - 117 - 115 - 101 - 114 - 41 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 39 - 95 - 95 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 32 - 105 - 115 - 92 - 110 - 39 - 41 - 59 - 100 - 105 - 115 - 112 - 40 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 99 - 114 - 101 - 97 - 116 - 101 - 32 - 97 - 32 - 110 - 101 - 119 - 32 - 105 - 114 - 45 - 112 - 97 - 114 - 32 - 69 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 46 - 91 - 105 - 44 - 114 - 93 - 112 - 97 - 114 - 32 - 102 - 105 - 108 - 101 - 115 - 10 - 32 - 32 - 32 - 32 - 67 - 97 - 108 - 108 - 101 - 100 - 79 - 110 - 108 - 105 - 110 - 101 - 32 - 61 - 32 - 37 - 116 - 59 - 47 - 47 - 32 - 84 - 104 - 101 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 32 - 105 - 115 - 32 - 99 - 97 - 108 - 108 - 101 - 100 - 32 - 111 - 110 - 108 - 105 - 110 - 101 - 32 - 98 - 101 - 99 - 97 - 117 - 115 - 101 - 32 - 119 - 101 - 32 - 97 - 114 - 101 - 32 - 105 - 110 - 32 - 101 - 109 - 98 - 101 - 100 - 100 - 101 - 100 - 32 - 83 - 99 - 105 - 108 - 97 - 98 - 32 - 104 - 101 - 114 - 101 - 10 - 32 - 32 - 32 - 32 - 78 - 32 - 61 - 32 - 50 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 10 - 32 - 32 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 91 - 112 - 97 - 114 - 44 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 115 - 105 - 109 - 110 - 101 - 115 - 116 - 50 - 95 - 114 - 101 - 112 - 108 - 97 - 99 - 101 - 109 - 101 - 110 - 116 - 40 - 105 - 110 - 115 - 105 - 122 - 101 - 115 - 44 - 32 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 95 - 95 - 44 - 32 - 105 - 110 - 116 - 121 - 112 - 101 - 115 - 44 - 32 - 111 - 117 - 116 - 116 - 121 - 112 - 101 - 115 - 95 - 95 - 44 - 32 - 110 - 101 - 115 - 116 - 101 - 100 - 95 - 102 - 110 - 61 - 69 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 82 - 101 - 112 - 108 - 97 - 99 - 101 - 97 - 98 - 108 - 101 - 44 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 61 - 108 - 105 - 115 - 116 - 40 - 105 - 110 - 115 - 105 - 122 - 101 - 115 - 44 - 32 - 105 - 110 - 116 - 121 - 112 - 101 - 115 - 44 - 32 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 44 - 32 - 111 - 117 - 116 - 116 - 121 - 112 - 101 - 115 - 44 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 44 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 95 - 117 - 115 - 101 - 114 - 41 - 44 - 32 - 78 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 98 - 108 - 111 - 99 - 107 - 46 - 115 - 116 - 97 - 116 - 101 - 115 - 46 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 32 - 61 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 40 - 53 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 32 - 32 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 39 - 95 - 95 - 32 - 78 - 101 - 119 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 32 - 105 - 115 - 92 - 110 - 39 - 41 - 59 - 100 - 105 - 115 - 112 - 40 - 98 - 108 - 111 - 99 - 107 - 46 - 115 - 116 - 97 - 116 - 101 - 115 - 46 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 115 - 97 - 118 - 101 - 32 - 118 - 101 - 99 - 116 - 111 - 114 - 115 - 32 - 116 - 111 - 32 - 97 - 32 - 102 - 105 - 108 - 101 - 10 - 32 - 32 - 32 - 32 - 115 - 97 - 118 - 101 - 95 - 105 - 114 - 112 - 97 - 114 - 97 - 109 - 40 - 112 - 97 - 114 - 44 - 32 - 105 - 100 - 101 - 110 - 116 - 95 - 115 - 116 - 114 - 32 - 43 - 32 - 39 - 95 - 82 - 101 - 112 - 108 - 97 - 99 - 101 - 97 - 98 - 108 - 101 - 83 - 105 - 109 - 117 - 108 - 97 - 116 - 105 - 111 - 110 - 46 - 105 - 112 - 97 - 114 - 39 - 44 - 32 - 105 - 100 - 101 - 110 - 116 - 95 - 115 - 116 - 114 - 32 - 43 - 32 - 39 - 95 - 82 - 101 - 112 - 108 - 97 - 99 - 101 - 97 - 98 - 108 - 101 - 83 - 105 - 109 - 117 - 108 - 97 - 116 - 105 - 111 - 110 - 46 - 114 - 112 - 97 - 114 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 84 - 101 - 108 - 108 - 32 - 116 - 104 - 97 - 116 - 32 - 101 - 118 - 101 - 114 - 121 - 116 - 104 - 105 - 110 - 103 - 32 - 119 - 101 - 110 - 116 - 32 - 102 - 105 - 110 - 101 - 46 - 10 - 32 - 32 - 32 - 32 - 99 - 111 - 109 - 112 - 114 - 101 - 97 - 100 - 121 - 32 - 61 - 32 - 49 - 59 - 10 - 32 - 32 - 32 - 32 - 67 - 97 - 108 - 105 - 98 - 114 - 97 - 116 - 105 - 111 - 110 - 82 - 101 - 116 - 117 - 114 - 110 - 86 - 97 - 108 - 32 - 61 - 32 - 48 - 59 - 47 - 47 - 32 - 114 - 117 - 110 - 32 - 116 - 104 - 101 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 97 - 103 - 97 - 105 - 110 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 112 - 97 - 99 - 107 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 118 - 101 - 99 - 32 - 61 - 32 - 122 - 101 - 114 - 111 - 115 - 40 - 50 - 48 - 44 - 32 - 49 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 118 - 101 - 99 - 40 - 49 - 41 - 32 - 61 - 32 - 99 - 111 - 109 - 112 - 114 - 101 - 97 - 100 - 121 - 59 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 118 - 101 - 99 - 40 - 50 - 41 - 32 - 61 - 32 - 67 - 97 - 108 - 105 - 98 - 114 - 97 - 116 - 105 - 111 - 110 - 82 - 101 - 116 - 117 - 114 - 110 - 86 - 97 - 108 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 99 - 108 - 101 - 97 - 114 - 10 - 32 - 32 - 32 - 32 - 112 - 97 - 114 - 46 - 105 - 112 - 97 - 114 - 32 - 61 - 32 - 91 - 93 - 59 - 10 - 32 - 32 - 32 - 32 - 112 - 97 - 114 - 46 - 114 - 112 - 97 - 114 - 32 - 61 - 32 - 91 - 93 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 98 - 108 - 111 - 99 - 107 - 46 - 111 - 117 - 116 - 112 - 116 - 114 - 40 - 49 - 41 - 32 - 61 - 32 - 111 - 117 - 116 - 118 - 101 - 99 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 99 - 97 - 115 - 101 - 32 - 52 - 59 - 32 - 47 - 47 - 32 - 105 - 110 - 105 - 116 - 32 - 116 - 104 - 101 - 110 - 10 - 32 - 32 - 32 - 32 - 98 - 108 - 111 - 99 - 107 - 46 - 115 - 116 - 97 - 116 - 101 - 115 - 46 - 69 - 120 - 101 - 99 - 67 - 111 - 117 - 110 - 116 - 101 - 114 - 32 - 61 - 32 - 48 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 99 - 97 - 115 - 101 - 32 - 53 - 59 - 32 - 47 - 47 - 32 - 116 - 101 - 114 - 109 - 105 - 110 - 97 - 116 - 101 - 32 - 116 - 104 - 101 - 110 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 32 - 32 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 34 - 116 - 101 - 114 - 109 - 105 - 110 - 97 - 116 - 101 - 92 - 110 - 34 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 99 - 97 - 115 - 101 - 32 - 49 - 48 - 59 - 32 - 47 - 47 - 32 - 99 - 111 - 110 - 102 - 105 - 103 - 117 - 114 - 101 - 32 - 116 - 104 - 101 - 110 - 10 - 32 - 32 - 10 - 32 - 32 - 101 - 110 - 100 - 44 - 10 - 101 - 110 - 100 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 10 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 32 - 91 - 115 - 105 - 109 - 44 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 44 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 93 - 61 - 69 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 82 - 101 - 112 - 108 - 97 - 99 - 101 - 97 - 98 - 108 - 101 - 40 - 115 - 105 - 109 - 44 - 32 - 105 - 110 - 108 - 105 - 115 - 116 - 44 - 32 - 112 - 97 - 114 - 41 - 10 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 84 - 104 - 101 - 32 - 110 - 101 - 115 - 116 - 101 - 100 - 32 - 115 - 105 - 109 - 117 - 108 - 97 - 116 - 105 - 111 - 110 - 32 - 99 - 111 - 110 - 116 - 97 - 105 - 110 - 115 - 32 - 116 - 119 - 111 - 32 - 115 - 117 - 98 - 45 - 115 - 105 - 109 - 117 - 108 - 97 - 116 - 105 - 111 - 110 - 115 - 58 - 10 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 49 - 41 - 32 - 65 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 44 - 32 - 119 - 104 - 105 - 99 - 104 - 32 - 99 - 111 - 109 - 109 - 111 - 110 - 108 - 121 - 32 - 99 - 111 - 110 - 116 - 97 - 105 - 110 - 115 - 32 - 110 - 111 - 116 - 104 - 105 - 110 - 103 - 32 - 97 - 110 - 100 - 32 - 105 - 115 - 32 - 115 - 119 - 105 - 116 - 99 - 104 - 101 - 100 - 32 - 116 - 111 - 10 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 119 - 104 - 101 - 110 - 32 - 116 - 104 - 101 - 32 - 114 - 101 - 112 - 108 - 97 - 99 - 101 - 109 - 101 - 110 - 116 - 32 - 105 - 115 - 32 - 105 - 110 - 32 - 112 - 114 - 111 - 103 - 114 - 101 - 115 - 115 - 32 - 40 - 119 - 104 - 105 - 99 - 104 - 32 - 109 - 97 - 121 - 32 - 116 - 97 - 107 - 101 - 32 - 115 - 111 - 109 - 101 - 32 - 116 - 105 - 109 - 101 - 41 - 10 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 50 - 41 - 32 - 84 - 104 - 101 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 44 - 32 - 119 - 104 - 105 - 99 - 104 - 32 - 97 - 99 - 116 - 117 - 97 - 108 - 108 - 121 - 32 - 99 - 111 - 110 - 116 - 97 - 105 - 110 - 115 - 32 - 116 - 104 - 101 - 32 - 97 - 108 - 103 - 111 - 114 - 105 - 116 - 104 - 109 - 32 - 116 - 111 - 32 - 101 - 120 - 101 - 99 - 117 - 116 - 101 - 10 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 72 - 101 - 114 - 101 - 44 - 32 - 116 - 104 - 101 - 32 - 105 - 110 - 105 - 116 - 105 - 97 - 108 - 32 - 115 - 105 - 109 - 117 - 108 - 97 - 116 - 105 - 111 - 110 - 115 - 32 - 97 - 114 - 101 - 32 - 100 - 101 - 102 - 105 - 110 - 101 - 100 - 44 - 32 - 119 - 104 - 105 - 99 - 104 - 32 - 99 - 97 - 110 - 32 - 116 - 104 - 101 - 110 - 32 - 98 - 101 - 10 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 114 - 101 - 112 - 108 - 97 - 99 - 101 - 100 - 32 - 100 - 117 - 114 - 105 - 110 - 103 - 32 - 114 - 117 - 110 - 116 - 105 - 109 - 101 - 10 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 10 - 32 - 32 - 10 - 32 - 32 - 101 - 118 - 32 - 61 - 32 - 48 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 99 - 110 - 116 - 114 - 108 - 78 - 32 - 61 - 32 - 112 - 97 - 114 - 40 - 49 - 41 - 59 - 47 - 47 - 32 - 116 - 104 - 101 - 32 - 110 - 117 - 109 - 98 - 101 - 114 - 32 - 111 - 102 - 32 - 116 - 104 - 101 - 32 - 110 - 101 - 115 - 116 - 101 - 100 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 115 - 32 - 40 - 111 - 110 - 101 - 32 - 111 - 102 - 32 - 116 - 119 - 111 - 41 - 32 - 34 - 49 - 34 - 32 - 109 - 101 - 97 - 110 - 115 - 32 - 116 - 104 - 101 - 10 - 32 - 32 - 47 - 47 - 32 - 100 - 117 - 109 - 109 - 121 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 32 - 119 - 104 - 105 - 99 - 104 - 32 - 105 - 115 - 32 - 97 - 99 - 116 - 105 - 118 - 97 - 116 - 101 - 100 - 32 - 119 - 104 - 105 - 108 - 101 - 32 - 116 - 104 - 101 - 32 - 50 - 110 - 100 - 32 - 34 - 50 - 34 - 32 - 105 - 115 - 32 - 101 - 120 - 99 - 104 - 97 - 110 - 103 - 101 - 100 - 32 - 100 - 117 - 114 - 105 - 110 - 103 - 32 - 114 - 117 - 110 - 116 - 105 - 109 - 101 - 10 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 32 - 61 - 32 - 112 - 97 - 114 - 40 - 50 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 39 - 67 - 111 - 109 - 112 - 105 - 108 - 105 - 110 - 103 - 32 - 114 - 101 - 112 - 108 - 97 - 99 - 101 - 97 - 98 - 108 - 101 - 32 - 78 - 61 - 37 - 100 - 92 - 110 - 39 - 44 - 32 - 99 - 110 - 116 - 114 - 108 - 78 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 105 - 110 - 115 - 105 - 122 - 101 - 115 - 32 - 61 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 40 - 49 - 41 - 59 - 105 - 110 - 116 - 121 - 112 - 101 - 115 - 32 - 61 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 40 - 50 - 41 - 59 - 10 - 32 - 32 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 32 - 61 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 40 - 51 - 41 - 59 - 111 - 117 - 116 - 116 - 121 - 112 - 101 - 115 - 32 - 61 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 40 - 52 - 41 - 59 - 10 - 32 - 32 - 117 - 115 - 101 - 114 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 32 - 61 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 40 - 53 - 41 - 59 - 10 - 32 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 95 - 117 - 115 - 101 - 114 - 32 - 61 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 40 - 54 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 122 - 101 - 114 - 111 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 99 - 111 - 110 - 115 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 48 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 105 - 102 - 32 - 99 - 110 - 116 - 114 - 108 - 78 - 32 - 61 - 61 - 32 - 49 - 32 - 116 - 104 - 101 - 110 - 32 - 32 - 32 - 47 - 47 - 32 - 105 - 115 - 32 - 116 - 104 - 105 - 115 - 32 - 116 - 104 - 101 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 32 - 50 - 41 - 32 - 63 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 68 - 101 - 102 - 105 - 110 - 101 - 32 - 100 - 117 - 109 - 109 - 121 - 32 - 111 - 117 - 116 - 112 - 117 - 116 - 115 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 78 - 79 - 84 - 69 - 58 - 32 - 111 - 110 - 108 - 121 - 32 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 32 - 105 - 115 - 32 - 115 - 117 - 112 - 112 - 111 - 114 - 116 - 101 - 100 - 32 - 98 - 121 - 32 - 116 - 104 - 105 - 115 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 32 - 61 - 32 - 108 - 105 - 115 - 116 - 40 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 102 - 111 - 114 - 32 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 32 - 61 - 32 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 44 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 100 - 117 - 109 - 109 - 121 - 79 - 117 - 116 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 99 - 111 - 110 - 115 - 116 - 118 - 101 - 99 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 122 - 101 - 114 - 111 - 115 - 40 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 44 - 32 - 49 - 41 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 40 - 36 - 32 - 43 - 32 - 49 - 41 - 32 - 61 - 32 - 100 - 117 - 109 - 109 - 121 - 79 - 117 - 116 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 101 - 110 - 100 - 44 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 40 - 36 - 32 - 43 - 32 - 49 - 41 - 32 - 61 - 32 - 122 - 101 - 114 - 111 - 59 - 47 - 47 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 32 - 111 - 117 - 116 - 112 - 117 - 116 - 10 - 32 - 32 - 101 - 110 - 100 - 44 - 10 - 32 - 32 - 10 - 32 - 32 - 105 - 102 - 32 - 99 - 110 - 116 - 114 - 108 - 78 - 32 - 61 - 61 - 32 - 50 - 32 - 116 - 104 - 101 - 110 - 32 - 32 - 32 - 47 - 47 - 32 - 105 - 115 - 32 - 116 - 104 - 105 - 115 - 32 - 116 - 104 - 101 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 32 - 50 - 41 - 32 - 63 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 39 - 67 - 111 - 109 - 112 - 105 - 108 - 105 - 110 - 103 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 58 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 95 - 117 - 115 - 101 - 114 - 92 - 110 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 114 - 117 - 110 - 32 - 116 - 104 - 101 - 32 - 99 - 97 - 108 - 108 - 98 - 97 - 99 - 107 - 32 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 32 - 102 - 111 - 114 - 32 - 100 - 101 - 102 - 105 - 110 - 105 - 116 - 105 - 111 - 110 - 32 - 111 - 102 - 32 - 116 - 104 - 101 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 108 - 101 - 114 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 100 - 105 - 115 - 112 - 40 - 102 - 117 - 110 - 50 - 115 - 116 - 114 - 105 - 110 - 103 - 40 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 95 - 117 - 115 - 101 - 114 - 41 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 44 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 44 - 117 - 115 - 101 - 114 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 93 - 32 - 61 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 95 - 117 - 115 - 101 - 114 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 105 - 110 - 108 - 105 - 115 - 116 - 44 - 32 - 117 - 115 - 101 - 114 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 44 - 32 - 67 - 97 - 108 - 108 - 101 - 100 - 79 - 110 - 108 - 105 - 110 - 101 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 39 - 67 - 111 - 109 - 112 - 105 - 108 - 105 - 110 - 103 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 58 - 32 - 100 - 111 - 110 - 101 - 92 - 110 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 115 - 116 - 111 - 114 - 101 - 32 - 116 - 104 - 101 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 32 - 111 - 102 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 95 - 117 - 115 - 101 - 114 - 10 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 40 - 53 - 41 - 32 - 61 - 32 - 117 - 115 - 101 - 114 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 97 - 100 - 100 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 32 - 111 - 117 - 116 - 112 - 117 - 116 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 40 - 36 - 32 - 43 - 32 - 49 - 41 - 32 - 61 - 32 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 59 - 10 - 32 - 32 - 101 - 110 - 100 - 44 - 10 - 32 - 32 - 10 - 101 - 110 - 100 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 10 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 32 - 91 - 115 - 105 - 109 - 44 - 32 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 44 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 44 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 93 - 61 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 95 - 117 - 115 - 101 - 114 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 105 - 110 - 108 - 105 - 115 - 116 - 44 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 44 - 32 - 67 - 97 - 108 - 108 - 101 - 100 - 79 - 110 - 108 - 105 - 110 - 101 - 41 - 10 - 32 - 32 - 10 - 32 - 32 - 47 - 47 - 32 - 68 - 101 - 102 - 105 - 110 - 101 - 32 - 112 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 115 - 46 - 32 - 84 - 104 - 101 - 121 - 32 - 109 - 117 - 115 - 116 - 32 - 98 - 101 - 32 - 100 - 101 - 102 - 105 - 110 - 101 - 100 - 32 - 111 - 110 - 99 - 101 - 32 - 97 - 103 - 97 - 105 - 110 - 32 - 97 - 116 - 32 - 116 - 104 - 105 - 115 - 32 - 112 - 108 - 97 - 99 - 101 - 44 - 32 - 98 - 101 - 99 - 97 - 117 - 115 - 101 - 32 - 116 - 104 - 105 - 115 - 32 - 119 - 105 - 108 - 108 - 32 - 97 - 108 - 115 - 111 - 32 - 98 - 101 - 32 - 99 - 97 - 108 - 108 - 101 - 100 - 32 - 97 - 116 - 10 - 32 - 32 - 47 - 47 - 32 - 114 - 117 - 110 - 116 - 105 - 109 - 101 - 46 - 10 - 32 - 32 - 78 - 83 - 97 - 109 - 112 - 108 - 101 - 115 - 32 - 61 - 32 - 51 - 48 - 48 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 105 - 102 - 32 - 67 - 97 - 108 - 108 - 101 - 100 - 79 - 110 - 108 - 105 - 110 - 101 - 32 - 61 - 61 - 32 - 37 - 116 - 32 - 116 - 104 - 101 - 110 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 84 - 104 - 101 - 32 - 99 - 111 - 110 - 116 - 101 - 110 - 116 - 115 - 32 - 111 - 102 - 32 - 116 - 104 - 105 - 115 - 32 - 112 - 97 - 114 - 116 - 32 - 119 - 105 - 108 - 108 - 32 - 98 - 101 - 32 - 99 - 111 - 109 - 112 - 105 - 108 - 101 - 100 - 32 - 111 - 110 - 45 - 108 - 105 - 110 - 101 - 44 - 32 - 119 - 104 - 105 - 108 - 101 - 32 - 116 - 104 - 101 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 115 - 121 - 115 - 116 - 101 - 109 - 32 - 105 - 115 - 32 - 114 - 117 - 110 - 110 - 105 - 110 - 103 - 46 - 32 - 84 - 104 - 101 - 32 - 97 - 105 - 109 - 32 - 105 - 115 - 32 - 116 - 111 - 32 - 103 - 101 - 110 - 101 - 114 - 97 - 116 - 101 - 32 - 97 - 32 - 110 - 101 - 119 - 32 - 99 - 111 - 109 - 112 - 105 - 108 - 101 - 100 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 32 - 102 - 111 - 114 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 116 - 104 - 101 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 46 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 80 - 108 - 101 - 97 - 115 - 101 - 32 - 110 - 111 - 116 - 101 - 58 - 32 - 83 - 105 - 110 - 99 - 101 - 32 - 116 - 104 - 105 - 115 - 32 - 99 - 111 - 100 - 101 - 32 - 105 - 115 - 32 - 111 - 110 - 108 - 121 - 32 - 101 - 120 - 101 - 99 - 117 - 116 - 101 - 100 - 32 - 111 - 110 - 45 - 108 - 105 - 110 - 101 - 44 - 32 - 109 - 111 - 115 - 116 - 32 - 112 - 111 - 116 - 101 - 110 - 116 - 105 - 97 - 108 - 32 - 101 - 114 - 114 - 111 - 114 - 115 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 111 - 99 - 99 - 117 - 114 - 105 - 110 - 103 - 32 - 105 - 110 - 32 - 116 - 104 - 105 - 115 - 32 - 112 - 97 - 114 - 116 - 32 - 98 - 101 - 99 - 111 - 109 - 101 - 32 - 111 - 110 - 108 - 121 - 32 - 118 - 105 - 115 - 105 - 98 - 108 - 101 - 32 - 100 - 117 - 114 - 105 - 110 - 103 - 32 - 114 - 117 - 110 - 116 - 105 - 109 - 101 - 46 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 39 - 67 - 111 - 109 - 112 - 105 - 108 - 105 - 110 - 103 - 32 - 97 - 32 - 110 - 101 - 119 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 32 - 115 - 121 - 115 - 116 - 101 - 109 - 92 - 110 - 39 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 101 - 120 - 101 - 99 - 40 - 39 - 119 - 101 - 98 - 105 - 110 - 116 - 101 - 114 - 102 - 97 - 99 - 101 - 47 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 115 - 99 - 101 - 39 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 102 - 117 - 110 - 99 - 112 - 114 - 111 - 116 - 40 - 48 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 122 - 32 - 61 - 32 - 112 - 111 - 108 - 121 - 40 - 48 - 44 - 32 - 39 - 122 - 39 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 65 - 110 - 100 - 32 - 101 - 120 - 97 - 109 - 112 - 108 - 101 - 45 - 115 - 121 - 115 - 116 - 101 - 109 - 32 - 116 - 104 - 97 - 116 - 32 - 105 - 115 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 108 - 101 - 100 - 32 - 118 - 105 - 97 - 32 - 85 - 68 - 80 - 32 - 97 - 110 - 100 - 32 - 111 - 110 - 101 - 32 - 115 - 116 - 101 - 112 - 32 - 102 - 117 - 114 - 116 - 104 - 101 - 114 - 32 - 119 - 105 - 116 - 104 - 32 - 116 - 104 - 101 - 32 - 87 - 101 - 98 - 45 - 103 - 117 - 105 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 83 - 117 - 112 - 101 - 114 - 98 - 108 - 111 - 99 - 107 - 58 - 32 - 65 - 32 - 109 - 111 - 114 - 101 - 32 - 99 - 111 - 109 - 112 - 108 - 101 - 120 - 32 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 119 - 105 - 116 - 104 - 32 - 100 - 97 - 109 - 112 - 105 - 110 - 103 - 10 - 32 - 32 - 32 - 32 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 32 - 91 - 115 - 105 - 109 - 44 - 32 - 120 - 44 - 118 - 93 - 32 - 61 - 32 - 100 - 97 - 109 - 112 - 101 - 100 - 95 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 40 - 115 - 105 - 109 - 44 - 32 - 117 - 44 - 32 - 84 - 95 - 97 - 41 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 99 - 114 - 101 - 97 - 116 - 101 - 32 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 32 - 115 - 105 - 103 - 110 - 97 - 108 - 115 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 120 - 95 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 93 - 32 - 61 - 32 - 108 - 105 - 98 - 100 - 121 - 110 - 95 - 110 - 101 - 119 - 95 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 40 - 115 - 105 - 109 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 118 - 95 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 93 - 32 - 61 - 32 - 108 - 105 - 98 - 100 - 121 - 110 - 95 - 110 - 101 - 119 - 95 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 40 - 115 - 105 - 109 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 117 - 115 - 101 - 32 - 116 - 104 - 105 - 115 - 32 - 97 - 115 - 32 - 97 - 32 - 110 - 111 - 114 - 109 - 97 - 108 - 32 - 115 - 105 - 103 - 110 - 97 - 108 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 97 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 97 - 100 - 100 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 108 - 105 - 115 - 116 - 40 - 117 - 44 - 32 - 120 - 95 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 41 - 44 - 32 - 91 - 49 - 44 - 32 - 45 - 49 - 93 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 97 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 97 - 100 - 100 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 108 - 105 - 115 - 116 - 40 - 97 - 44 - 32 - 118 - 95 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 41 - 44 - 32 - 91 - 49 - 44 - 32 - 45 - 49 - 93 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 118 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 122 - 116 - 102 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 97 - 44 - 32 - 49 - 47 - 40 - 122 - 45 - 49 - 41 - 32 - 42 - 32 - 84 - 95 - 97 - 32 - 41 - 59 - 32 - 47 - 47 - 32 - 73 - 110 - 116 - 101 - 103 - 114 - 97 - 116 - 111 - 114 - 32 - 97 - 112 - 112 - 114 - 111 - 120 - 105 - 109 - 97 - 116 - 105 - 111 - 110 - 10 - 32 - 32 - 32 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 32 - 103 - 97 - 105 - 110 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 118 - 95 - 103 - 97 - 105 - 110 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 103 - 97 - 105 - 110 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 118 - 44 - 32 - 48 - 46 - 49 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 99 - 108 - 111 - 115 - 101 - 32 - 108 - 111 - 111 - 112 - 32 - 118 - 95 - 103 - 97 - 105 - 110 - 32 - 61 - 32 - 118 - 95 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 93 - 32 - 61 - 32 - 108 - 105 - 98 - 100 - 121 - 110 - 95 - 99 - 108 - 111 - 115 - 101 - 95 - 108 - 111 - 111 - 112 - 40 - 115 - 105 - 109 - 44 - 32 - 118 - 95 - 103 - 97 - 105 - 110 - 44 - 32 - 118 - 95 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 120 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 122 - 116 - 102 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 118 - 44 - 32 - 49 - 47 - 40 - 122 - 45 - 49 - 41 - 32 - 42 - 32 - 84 - 95 - 97 - 32 - 41 - 59 - 32 - 47 - 47 - 32 - 73 - 110 - 116 - 101 - 103 - 114 - 97 - 116 - 111 - 114 - 32 - 97 - 112 - 112 - 114 - 111 - 120 - 105 - 109 - 97 - 116 - 105 - 111 - 110 - 10 - 32 - 32 - 32 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 32 - 103 - 97 - 105 - 110 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 120 - 95 - 103 - 97 - 105 - 110 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 103 - 97 - 105 - 110 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 120 - 44 - 32 - 48 - 46 - 54 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 99 - 108 - 111 - 115 - 101 - 32 - 108 - 111 - 111 - 112 - 32 - 120 - 95 - 103 - 97 - 105 - 110 - 32 - 61 - 32 - 120 - 95 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 93 - 32 - 61 - 32 - 108 - 105 - 98 - 100 - 121 - 110 - 95 - 99 - 108 - 111 - 115 - 101 - 95 - 108 - 111 - 111 - 112 - 40 - 115 - 105 - 109 - 44 - 32 - 120 - 95 - 103 - 97 - 105 - 110 - 44 - 32 - 120 - 95 - 102 - 101 - 101 - 100 - 98 - 97 - 99 - 107 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 101 - 110 - 100 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 105 - 102 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 105 - 115 - 73 - 110 - 105 - 116 - 105 - 97 - 108 - 105 - 115 - 101 - 100 - 32 - 61 - 61 - 32 - 37 - 102 - 32 - 116 - 104 - 101 - 110 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 83 - 116 - 97 - 116 - 101 - 32 - 118 - 97 - 114 - 105 - 97 - 98 - 108 - 101 - 115 - 32 - 99 - 97 - 110 - 32 - 98 - 101 - 32 - 105 - 110 - 105 - 116 - 105 - 97 - 108 - 105 - 115 - 101 - 32 - 97 - 116 - 32 - 116 - 104 - 105 - 115 - 32 - 112 - 108 - 97 - 99 - 101 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 65 - 99 - 111 - 117 - 110 - 116 - 101 - 114 - 32 - 61 - 32 - 48 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 83 - 116 - 97 - 116 - 101 - 32 - 61 - 32 - 39 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 39 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 105 - 115 - 73 - 110 - 105 - 116 - 105 - 97 - 108 - 105 - 115 - 101 - 100 - 32 - 61 - 32 - 37 - 116 - 59 - 47 - 47 - 32 - 112 - 114 - 101 - 118 - 101 - 110 - 116 - 32 - 102 - 114 - 111 - 109 - 32 - 105 - 110 - 105 - 116 - 105 - 97 - 108 - 105 - 115 - 105 - 110 - 103 - 32 - 116 - 104 - 101 - 32 - 118 - 97 - 114 - 105 - 97 - 98 - 108 - 101 - 115 - 32 - 111 - 110 - 99 - 101 - 32 - 97 - 103 - 97 - 105 - 110 - 10 - 32 - 32 - 32 - 32 - 101 - 110 - 100 - 44 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 69 - 120 - 97 - 109 - 112 - 108 - 101 - 32 - 102 - 111 - 114 - 32 - 97 - 32 - 115 - 116 - 97 - 116 - 101 - 32 - 117 - 112 - 100 - 97 - 116 - 101 - 58 - 32 - 105 - 110 - 99 - 114 - 101 - 97 - 115 - 101 - 32 - 116 - 104 - 101 - 32 - 99 - 111 - 117 - 110 - 116 - 101 - 114 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 65 - 99 - 111 - 117 - 110 - 116 - 101 - 114 - 32 - 61 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 65 - 99 - 111 - 117 - 110 - 116 - 101 - 114 - 32 - 43 - 32 - 49 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 66 - 117 - 105 - 108 - 100 - 32 - 97 - 110 - 32 - 105 - 110 - 102 - 111 - 45 - 115 - 116 - 114 - 105 - 110 - 103 - 10 - 32 - 32 - 32 - 32 - 83 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 73 - 110 - 102 - 111 - 32 - 61 - 32 - 39 - 79 - 110 - 45 - 108 - 105 - 110 - 101 - 32 - 99 - 111 - 109 - 112 - 105 - 108 - 101 - 100 - 32 - 105 - 110 - 32 - 105 - 116 - 101 - 114 - 97 - 116 - 105 - 111 - 110 - 32 - 35 - 39 - 32 - 43 - 32 - 115 - 116 - 114 - 105 - 110 - 103 - 40 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 65 - 99 - 111 - 117 - 110 - 116 - 101 - 114 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 68 - 101 - 102 - 105 - 110 - 101 - 32 - 97 - 32 - 110 - 101 - 119 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 108 - 101 - 114 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 32 - 100 - 101 - 112 - 101 - 110 - 100 - 105 - 110 - 103 - 32 - 111 - 110 - 32 - 116 - 104 - 101 - 32 - 99 - 117 - 114 - 114 - 101 - 110 - 116 - 108 - 121 - 32 - 97 - 99 - 116 - 105 - 118 - 101 - 32 - 115 - 116 - 97 - 116 - 101 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 105 - 110 - 105 - 116 - 97 - 108 - 105 - 115 - 101 - 32 - 116 - 104 - 101 - 32 - 80 - 97 - 80 - 105 - 32 - 85 - 68 - 80 - 32 - 83 - 111 - 99 - 107 - 101 - 116 - 10 - 32 - 32 - 32 - 32 - 67 - 111 - 110 - 102 - 105 - 103 - 117 - 114 - 97 - 116 - 105 - 111 - 110 - 46 - 85 - 110 - 100 - 101 - 114 - 108 - 121 - 105 - 110 - 103 - 80 - 114 - 111 - 116 - 111 - 99 - 111 - 108 - 108 - 32 - 61 - 32 - 39 - 85 - 68 - 80 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 67 - 111 - 110 - 102 - 105 - 103 - 117 - 114 - 97 - 116 - 105 - 111 - 110 - 46 - 68 - 101 - 115 - 116 - 72 - 111 - 115 - 116 - 32 - 61 - 32 - 39 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 67 - 111 - 110 - 102 - 105 - 103 - 117 - 114 - 97 - 116 - 105 - 111 - 110 - 46 - 68 - 101 - 115 - 116 - 80 - 111 - 114 - 116 - 32 - 61 - 32 - 50 - 48 - 48 - 48 - 48 - 59 - 10 - 32 - 32 - 32 - 32 - 67 - 111 - 110 - 102 - 105 - 103 - 117 - 114 - 97 - 116 - 105 - 111 - 110 - 46 - 76 - 111 - 99 - 97 - 108 - 83 - 111 - 99 - 107 - 101 - 116 - 72 - 111 - 115 - 116 - 32 - 61 - 32 - 39 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 67 - 111 - 110 - 102 - 105 - 103 - 117 - 114 - 97 - 116 - 105 - 111 - 110 - 46 - 76 - 111 - 99 - 97 - 108 - 83 - 111 - 99 - 107 - 101 - 116 - 80 - 111 - 114 - 116 - 32 - 61 - 32 - 50 - 48 - 48 - 48 - 49 - 59 - 10 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 67 - 111 - 110 - 102 - 105 - 103 - 117 - 114 - 97 - 116 - 105 - 111 - 110 - 46 - 100 - 101 - 98 - 117 - 103 - 109 - 111 - 100 - 101 - 32 - 61 - 32 - 37 - 116 - 59 - 10 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 80 - 70 - 95 - 73 - 110 - 105 - 116 - 73 - 110 - 115 - 116 - 97 - 110 - 99 - 101 - 40 - 115 - 105 - 109 - 44 - 32 - 73 - 110 - 115 - 116 - 97 - 110 - 99 - 101 - 78 - 97 - 109 - 101 - 61 - 39 - 84 - 114 - 111 - 99 - 107 - 101 - 110 - 111 - 102 - 101 - 110 - 82 - 101 - 109 - 111 - 116 - 101 - 67 - 111 - 110 - 116 - 114 - 111 - 108 - 39 - 44 - 32 - 67 - 111 - 110 - 102 - 105 - 103 - 117 - 114 - 97 - 116 - 105 - 111 - 110 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 122 - 101 - 114 - 111 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 99 - 111 - 110 - 115 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 48 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 111 - 110 - 101 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 99 - 111 - 110 - 115 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 49 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 100 - 101 - 102 - 97 - 117 - 108 - 116 - 32 - 111 - 117 - 116 - 112 - 117 - 116 - 32 - 40 - 100 - 117 - 109 - 109 - 121 - 41 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 32 - 61 - 32 - 108 - 105 - 115 - 116 - 40 - 122 - 101 - 114 - 111 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 72 - 101 - 114 - 101 - 32 - 97 - 32 - 115 - 116 - 97 - 116 - 101 - 45 - 109 - 97 - 99 - 104 - 105 - 110 - 101 - 32 - 105 - 115 - 32 - 105 - 109 - 112 - 108 - 101 - 109 - 101 - 110 - 116 - 101 - 100 - 32 - 116 - 104 - 97 - 116 - 32 - 109 - 97 - 121 - 32 - 98 - 101 - 32 - 117 - 115 - 101 - 100 - 32 - 116 - 111 - 32 - 105 - 109 - 112 - 108 - 101 - 109 - 101 - 110 - 116 - 32 - 115 - 111 - 109 - 101 - 32 - 97 - 117 - 116 - 111 - 109 - 97 - 116 - 105 - 111 - 110 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 108 - 111 - 103 - 105 - 99 - 32 - 116 - 104 - 97 - 116 - 32 - 105 - 115 - 32 - 101 - 120 - 101 - 99 - 117 - 116 - 101 - 100 - 32 - 100 - 117 - 114 - 105 - 110 - 103 - 32 - 114 - 117 - 110 - 116 - 105 - 109 - 101 - 32 - 117 - 115 - 105 - 110 - 103 - 32 - 116 - 104 - 101 - 32 - 101 - 109 - 98 - 101 - 100 - 100 - 101 - 100 - 32 - 83 - 99 - 105 - 108 - 97 - 98 - 32 - 105 - 110 - 116 - 101 - 114 - 112 - 114 - 101 - 116 - 101 - 114 - 46 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 73 - 110 - 32 - 116 - 104 - 105 - 115 - 32 - 101 - 120 - 97 - 109 - 112 - 108 - 101 - 44 - 32 - 97 - 32 - 99 - 97 - 108 - 105 - 98 - 114 - 97 - 116 - 105 - 111 - 110 - 32 - 114 - 117 - 110 - 32 - 115 - 117 - 99 - 99 - 101 - 101 - 100 - 101 - 100 - 32 - 98 - 121 - 32 - 116 - 104 - 101 - 32 - 100 - 101 - 115 - 105 - 103 - 110 - 47 - 99 - 111 - 109 - 112 - 105 - 108 - 97 - 116 - 105 - 111 - 110 - 47 - 101 - 120 - 101 - 99 - 117 - 116 - 105 - 111 - 110 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 111 - 102 - 32 - 97 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 45 - 115 - 121 - 115 - 116 - 101 - 109 - 32 - 105 - 115 - 32 - 105 - 109 - 112 - 108 - 101 - 109 - 101 - 110 - 116 - 101 - 100 - 46 - 32 - 84 - 104 - 101 - 32 - 115 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 115 - 32 - 100 - 101 - 102 - 105 - 110 - 101 - 100 - 32 - 105 - 110 - 32 - 101 - 97 - 99 - 104 - 32 - 115 - 116 - 97 - 116 - 101 - 32 - 97 - 114 - 101 - 32 - 108 - 111 - 97 - 100 - 101 - 100 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 97 - 116 - 32 - 114 - 117 - 110 - 116 - 105 - 109 - 101 - 46 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 10 - 32 - 32 - 32 - 32 - 115 - 101 - 108 - 101 - 99 - 116 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 83 - 116 - 97 - 116 - 101 - 10 - 32 - 32 - 32 - 32 - 99 - 97 - 115 - 101 - 32 - 39 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 39 - 59 - 32 - 47 - 47 - 32 - 100 - 101 - 102 - 105 - 110 - 101 - 32 - 97 - 110 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 116 - 111 - 32 - 112 - 101 - 114 - 102 - 111 - 114 - 109 - 32 - 97 - 110 - 32 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 116 - 104 - 101 - 110 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 105 - 110 - 49 - 32 - 61 - 32 - 105 - 110 - 108 - 105 - 115 - 116 - 40 - 49 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 115 - 105 - 109 - 32 - 61 - 32 - 108 - 100 - 95 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 105 - 110 - 49 - 44 - 32 - 39 - 67 - 97 - 115 - 101 - 32 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 97 - 99 - 116 - 105 - 118 - 101 - 58 - 32 - 39 - 44 - 32 - 49 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 65 - 100 - 100 - 32 - 97 - 32 - 112 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 32 - 102 - 111 - 114 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 108 - 105 - 110 - 103 - 32 - 116 - 104 - 101 - 32 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 73 - 110 - 112 - 117 - 116 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 80 - 70 - 95 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 40 - 115 - 105 - 109 - 44 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 32 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 61 - 49 - 44 - 32 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 61 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 44 - 32 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 78 - 97 - 109 - 101 - 61 - 39 - 79 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 105 - 110 - 112 - 117 - 116 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 115 - 111 - 109 - 101 - 32 - 109 - 111 - 114 - 101 - 32 - 112 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 115 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 65 - 80 - 97 - 114 - 86 - 101 - 99 - 116 - 111 - 114 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 80 - 70 - 95 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 40 - 115 - 105 - 109 - 44 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 32 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 61 - 49 - 48 - 44 - 32 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 61 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 44 - 32 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 78 - 97 - 109 - 101 - 61 - 39 - 65 - 32 - 118 - 101 - 99 - 116 - 111 - 114 - 105 - 97 - 108 - 32 - 112 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 39 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 112 - 97 - 114 - 50 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 80 - 70 - 95 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 40 - 115 - 105 - 109 - 44 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 32 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 61 - 50 - 44 - 32 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 61 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 44 - 32 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 78 - 97 - 109 - 101 - 61 - 39 - 84 - 101 - 115 - 116 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 112 - 114 - 105 - 110 - 116 - 102 - 32 - 116 - 104 - 101 - 115 - 101 - 32 - 112 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 115 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 115 - 105 - 109 - 32 - 61 - 32 - 108 - 100 - 95 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 73 - 110 - 112 - 117 - 116 - 44 - 32 - 39 - 79 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 105 - 110 - 112 - 117 - 116 - 32 - 39 - 44 - 32 - 49 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 115 - 105 - 109 - 32 - 61 - 32 - 108 - 100 - 95 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 112 - 97 - 114 - 50 - 44 - 32 - 39 - 84 - 101 - 115 - 116 - 32 - 39 - 44 - 32 - 50 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 115 - 105 - 109 - 32 - 61 - 32 - 108 - 100 - 95 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 65 - 80 - 97 - 114 - 86 - 101 - 99 - 116 - 111 - 114 - 44 - 32 - 39 - 65 - 32 - 118 - 101 - 99 - 116 - 111 - 114 - 105 - 97 - 108 - 32 - 112 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 39 - 44 - 32 - 49 - 48 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 84 - 104 - 101 - 32 - 115 - 121 - 115 - 116 - 101 - 109 - 32 - 116 - 111 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 73 - 110 - 112 - 117 - 116 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 97 - 100 - 100 - 95 - 111 - 102 - 115 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 73 - 110 - 112 - 117 - 116 - 44 - 32 - 48 - 46 - 50 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 84 - 95 - 97 - 32 - 61 - 32 - 48 - 46 - 49 - 59 - 91 - 115 - 105 - 109 - 44 - 120 - 44 - 118 - 93 - 32 - 61 - 32 - 100 - 97 - 109 - 112 - 101 - 100 - 95 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 40 - 115 - 105 - 109 - 44 - 32 - 73 - 110 - 112 - 117 - 116 - 44 - 32 - 84 - 95 - 97 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 83 - 116 - 114 - 101 - 97 - 109 - 32 - 116 - 104 - 101 - 32 - 100 - 97 - 116 - 97 - 32 - 111 - 102 - 32 - 116 - 104 - 101 - 32 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 83 - 101 - 110 - 100 - 80 - 97 - 99 - 107 - 101 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 32 - 83 - 105 - 103 - 110 - 97 - 108 - 61 - 120 - 44 - 32 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 95 - 115 - 101 - 110 - 100 - 61 - 49 - 44 - 32 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 61 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 44 - 32 - 83 - 111 - 117 - 114 - 99 - 101 - 78 - 97 - 109 - 101 - 61 - 39 - 88 - 39 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 105 - 100 - 101 - 110 - 116 - 105 - 102 - 105 - 101 - 114 - 46 - 118 - 97 - 108 - 117 - 101 - 32 - 61 - 32 - 39 - 80 - 108 - 111 - 116 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 99 - 111 - 110 - 102 - 105 - 103 - 46 - 115 - 105 - 122 - 101 - 46 - 118 - 97 - 108 - 117 - 101 - 32 - 61 - 32 - 39 - 40 - 51 - 48 - 48 - 44 - 53 - 48 - 48 - 41 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 99 - 111 - 110 - 102 - 105 - 103 - 46 - 112 - 111 - 115 - 105 - 116 - 105 - 111 - 110 - 46 - 118 - 97 - 108 - 117 - 101 - 32 - 61 - 32 - 39 - 40 - 49 - 48 - 48 - 44 - 48 - 41 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 99 - 111 - 110 - 102 - 105 - 103 - 46 - 110 - 97 - 109 - 101 - 46 - 118 - 97 - 108 - 117 - 101 - 32 - 61 - 32 - 39 - 80 - 108 - 111 - 116 - 32 - 88 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 83 - 117 - 98 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 98 - 108 - 111 - 99 - 107 - 32 - 61 - 32 - 39 - 83 - 111 - 117 - 114 - 99 - 101 - 71 - 114 - 111 - 117 - 112 - 39 - 32 - 43 - 32 - 115 - 116 - 114 - 105 - 110 - 103 - 40 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 65 - 99 - 111 - 117 - 110 - 116 - 101 - 114 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 83 - 117 - 98 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 115 - 105 - 103 - 110 - 97 - 108 - 115 - 32 - 61 - 32 - 108 - 105 - 115 - 116 - 40 - 39 - 88 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 83 - 101 - 110 - 100 - 80 - 97 - 99 - 107 - 101 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 32 - 83 - 105 - 103 - 110 - 97 - 108 - 61 - 118 - 44 - 32 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 95 - 115 - 101 - 110 - 100 - 61 - 49 - 44 - 32 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 61 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 44 - 32 - 83 - 111 - 117 - 114 - 99 - 101 - 78 - 97 - 109 - 101 - 61 - 39 - 86 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 102 - 105 - 110 - 97 - 108 - 105 - 115 - 101 - 32 - 116 - 104 - 101 - 32 - 99 - 111 - 109 - 109 - 117 - 110 - 105 - 99 - 97 - 116 - 105 - 111 - 110 - 32 - 105 - 110 - 116 - 101 - 114 - 102 - 97 - 99 - 101 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 80 - 70 - 95 - 70 - 105 - 110 - 97 - 108 - 105 - 115 - 101 - 40 - 115 - 105 - 109 - 44 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 108 - 100 - 95 - 80 - 70 - 95 - 69 - 120 - 112 - 111 - 114 - 116 - 95 - 106 - 115 - 40 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 32 - 102 - 110 - 97 - 109 - 101 - 61 - 39 - 80 - 114 - 111 - 116 - 111 - 99 - 111 - 108 - 108 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 106 - 115 - 111 - 110 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 87 - 97 - 105 - 116 - 32 - 117 - 110 - 116 - 105 - 108 - 32 - 97 - 32 - 110 - 117 - 109 - 98 - 101 - 114 - 32 - 111 - 102 - 32 - 116 - 105 - 109 - 101 - 32 - 115 - 116 - 101 - 112 - 115 - 32 - 104 - 97 - 115 - 32 - 112 - 97 - 115 - 115 - 101 - 100 - 44 - 32 - 116 - 104 - 101 - 110 - 32 - 110 - 111 - 116 - 105 - 102 - 121 - 32 - 116 - 104 - 97 - 116 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 116 - 104 - 101 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 104 - 97 - 115 - 32 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 32 - 98 - 121 - 32 - 115 - 101 - 116 - 116 - 105 - 110 - 103 - 32 - 34 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 34 - 32 - 116 - 111 - 32 - 49 - 46 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 115 - 116 - 101 - 112 - 115 - 50 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 97 - 99 - 116 - 105 - 118 - 97 - 116 - 105 - 111 - 110 - 95 - 115 - 105 - 109 - 115 - 116 - 101 - 112 - 115 - 61 - 78 - 83 - 97 - 109 - 112 - 108 - 101 - 115 - 44 - 32 - 118 - 97 - 108 - 117 - 101 - 115 - 61 - 91 - 48 - 44 - 49 - 93 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 111 - 117 - 116 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 99 - 111 - 110 - 115 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 49 - 49 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 32 - 61 - 32 - 108 - 105 - 115 - 116 - 40 - 111 - 117 - 116 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 99 - 104 - 111 - 115 - 101 - 32 - 116 - 104 - 101 - 32 - 110 - 101 - 120 - 116 - 32 - 115 - 116 - 97 - 116 - 101 - 32 - 116 - 111 - 32 - 101 - 110 - 116 - 101 - 114 - 32 - 119 - 104 - 101 - 110 - 32 - 116 - 104 - 101 - 32 - 99 - 97 - 108 - 105 - 98 - 114 - 97 - 116 - 105 - 111 - 110 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 104 - 97 - 115 - 32 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 83 - 116 - 97 - 116 - 101 - 32 - 61 - 32 - 39 - 99 - 111 - 110 - 115 - 116 - 97 - 110 - 116 - 39 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 99 - 97 - 115 - 101 - 32 - 39 - 99 - 111 - 110 - 115 - 116 - 97 - 110 - 116 - 39 - 59 - 32 - 47 - 47 - 32 - 100 - 101 - 115 - 105 - 103 - 110 - 32 - 97 - 32 - 99 - 111 - 110 - 115 - 116 - 97 - 110 - 116 - 32 - 115 - 105 - 103 - 110 - 97 - 108 - 32 - 116 - 111 - 32 - 115 - 101 - 110 - 100 - 32 - 116 - 111 - 32 - 80 - 97 - 80 - 105 - 32 - 116 - 104 - 101 - 110 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 105 - 110 - 49 - 32 - 61 - 32 - 105 - 110 - 108 - 105 - 115 - 116 - 40 - 49 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 115 - 105 - 109 - 32 - 61 - 32 - 108 - 100 - 95 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 105 - 110 - 49 - 44 - 32 - 39 - 67 - 97 - 115 - 101 - 32 - 99 - 111 - 110 - 115 - 116 - 97 - 110 - 116 - 32 - 97 - 99 - 116 - 105 - 118 - 101 - 58 - 32 - 39 - 44 - 32 - 49 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 83 - 116 - 114 - 101 - 97 - 109 - 32 - 97 - 32 - 99 - 111 - 110 - 115 - 116 - 97 - 110 - 116 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 83 - 101 - 110 - 100 - 80 - 97 - 99 - 107 - 101 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 32 - 83 - 105 - 103 - 110 - 97 - 108 - 61 - 105 - 110 - 49 - 44 - 32 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 95 - 115 - 101 - 110 - 100 - 61 - 49 - 44 - 32 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 61 - 79 - 82 - 84 - 68 - 46 - 68 - 65 - 84 - 65 - 84 - 89 - 80 - 69 - 95 - 70 - 76 - 79 - 65 - 84 - 44 - 32 - 83 - 111 - 117 - 114 - 99 - 101 - 78 - 97 - 109 - 101 - 61 - 39 - 67 - 111 - 110 - 115 - 116 - 39 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 105 - 100 - 101 - 110 - 116 - 105 - 102 - 105 - 101 - 114 - 46 - 118 - 97 - 108 - 117 - 101 - 32 - 61 - 32 - 39 - 80 - 108 - 111 - 116 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 99 - 111 - 110 - 102 - 105 - 103 - 46 - 115 - 105 - 122 - 101 - 46 - 118 - 97 - 108 - 117 - 101 - 32 - 61 - 32 - 39 - 40 - 51 - 48 - 48 - 44 - 51 - 48 - 48 - 41 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 99 - 111 - 110 - 102 - 105 - 103 - 46 - 112 - 111 - 115 - 105 - 116 - 105 - 111 - 110 - 46 - 118 - 97 - 108 - 117 - 101 - 32 - 61 - 32 - 39 - 40 - 51 - 48 - 48 - 44 - 48 - 41 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 99 - 111 - 110 - 102 - 105 - 103 - 46 - 110 - 97 - 109 - 101 - 46 - 118 - 97 - 108 - 117 - 101 - 32 - 61 - 32 - 39 - 80 - 108 - 111 - 116 - 32 - 67 - 111 - 110 - 115 - 116 - 39 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 83 - 117 - 98 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 98 - 108 - 111 - 99 - 107 - 32 - 61 - 32 - 39 - 83 - 111 - 117 - 114 - 99 - 101 - 71 - 114 - 111 - 117 - 112 - 39 - 32 - 43 - 32 - 115 - 116 - 114 - 105 - 110 - 103 - 40 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 65 - 99 - 111 - 117 - 110 - 116 - 101 - 114 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 46 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 84 - 111 - 83 - 117 - 98 - 46 - 112 - 108 - 111 - 116 - 95 - 88 - 46 - 115 - 105 - 103 - 110 - 97 - 108 - 115 - 32 - 61 - 32 - 108 - 105 - 115 - 116 - 40 - 39 - 67 - 111 - 110 - 115 - 116 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 102 - 105 - 110 - 97 - 108 - 105 - 115 - 101 - 32 - 116 - 104 - 101 - 32 - 99 - 111 - 109 - 109 - 117 - 110 - 105 - 99 - 97 - 116 - 105 - 111 - 110 - 32 - 105 - 110 - 116 - 101 - 114 - 102 - 97 - 99 - 101 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 80 - 70 - 95 - 70 - 105 - 110 - 97 - 108 - 105 - 115 - 101 - 40 - 115 - 105 - 109 - 44 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 108 - 100 - 95 - 80 - 70 - 95 - 69 - 120 - 112 - 111 - 114 - 116 - 95 - 106 - 115 - 40 - 80 - 97 - 99 - 107 - 101 - 116 - 70 - 114 - 97 - 109 - 101 - 119 - 111 - 114 - 107 - 44 - 32 - 102 - 110 - 97 - 109 - 101 - 61 - 39 - 80 - 114 - 111 - 116 - 111 - 99 - 111 - 108 - 108 - 67 - 111 - 110 - 102 - 105 - 103 - 46 - 106 - 115 - 111 - 110 - 39 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 87 - 97 - 105 - 116 - 32 - 117 - 110 - 116 - 105 - 108 - 32 - 97 - 32 - 110 - 117 - 109 - 98 - 101 - 114 - 32 - 111 - 102 - 32 - 116 - 105 - 109 - 101 - 32 - 115 - 116 - 101 - 112 - 115 - 32 - 104 - 97 - 115 - 32 - 112 - 97 - 115 - 115 - 101 - 100 - 44 - 32 - 116 - 104 - 101 - 110 - 32 - 110 - 111 - 116 - 105 - 102 - 121 - 32 - 116 - 104 - 97 - 116 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 116 - 104 - 101 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 40 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 32 - 115 - 121 - 115 - 116 - 101 - 109 - 32 - 105 - 110 - 32 - 116 - 104 - 105 - 115 - 32 - 99 - 97 - 115 - 101 - 41 - 32 - 104 - 97 - 115 - 32 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 46 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 115 - 116 - 101 - 112 - 115 - 50 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 97 - 99 - 116 - 105 - 118 - 97 - 116 - 105 - 111 - 110 - 95 - 115 - 105 - 109 - 115 - 116 - 101 - 112 - 115 - 61 - 78 - 83 - 97 - 109 - 112 - 108 - 101 - 115 - 44 - 32 - 118 - 97 - 108 - 117 - 101 - 115 - 61 - 91 - 48 - 44 - 49 - 93 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 111 - 117 - 116 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 99 - 111 - 110 - 115 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 50 - 50 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 32 - 61 - 32 - 108 - 105 - 115 - 116 - 40 - 111 - 117 - 116 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 99 - 104 - 111 - 115 - 101 - 32 - 116 - 104 - 101 - 32 - 110 - 101 - 120 - 116 - 32 - 115 - 116 - 97 - 116 - 101 - 32 - 116 - 111 - 32 - 101 - 110 - 116 - 101 - 114 - 32 - 119 - 104 - 101 - 110 - 32 - 116 - 104 - 101 - 32 - 100 - 101 - 109 - 111 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 104 - 97 - 115 - 32 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 117 - 115 - 101 - 114 - 100 - 97 - 116 - 97 - 46 - 83 - 116 - 97 - 116 - 101 - 32 - 61 - 32 - 39 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 39 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 99 - 97 - 115 - 101 - 32 - 39 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 39 - 59 - 32 - 47 - 47 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 32 - 45 - 32 - 110 - 111 - 116 - 104 - 105 - 110 - 103 - 32 - 116 - 111 - 32 - 100 - 111 - 32 - 116 - 104 - 101 - 110 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 105 - 110 - 49 - 32 - 61 - 32 - 105 - 110 - 108 - 105 - 115 - 116 - 40 - 49 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 115 - 105 - 109 - 32 - 61 - 32 - 108 - 100 - 95 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 105 - 110 - 49 - 44 - 32 - 39 - 67 - 97 - 115 - 101 - 32 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 32 - 97 - 99 - 116 - 105 - 118 - 101 - 39 - 44 - 32 - 49 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 99 - 111 - 110 - 115 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 101 - 118 - 44 - 32 - 48 - 41 - 59 - 47 - 47 - 32 - 110 - 101 - 118 - 101 - 114 - 32 - 102 - 105 - 110 - 105 - 115 - 104 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 111 - 117 - 116 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 99 - 111 - 110 - 115 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 51 - 51 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 32 - 61 - 32 - 108 - 105 - 115 - 116 - 40 - 111 - 117 - 116 - 41 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 101 - 110 - 100 - 44 - 10 - 32 - 32 - 101 - 110 - 100 - 44 - 47 - 47 - 32 - 67 - 97 - 108 - 108 - 101 - 100 - 79 - 110 - 108 - 105 - 110 - 101 - 32 - 61 - 61 - 32 - 37 - 116 - 10 - 32 - 32 - 10 - 32 - 32 - 47 - 47 - 32 - 87 - 104 - 101 - 110 - 32 - 82 - 84 - 109 - 97 - 105 - 110 - 46 - 115 - 99 - 101 - 32 - 105 - 115 - 32 - 101 - 120 - 101 - 99 - 117 - 116 - 101 - 100 - 44 - 32 - 116 - 104 - 105 - 115 - 32 - 112 - 97 - 114 - 116 - 32 - 119 - 105 - 108 - 108 - 32 - 98 - 101 - 32 - 114 - 117 - 110 - 46 - 32 - 73 - 116 - 32 - 109 - 97 - 121 - 32 - 98 - 101 - 32 - 117 - 115 - 101 - 100 - 32 - 116 - 111 - 32 - 100 - 101 - 102 - 105 - 110 - 101 - 32 - 97 - 110 - 32 - 105 - 110 - 105 - 116 - 105 - 97 - 108 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 105 - 110 - 32 - 97 - 100 - 118 - 97 - 110 - 99 - 101 - 32 - 116 - 111 - 10 - 32 - 32 - 47 - 47 - 32 - 116 - 104 - 101 - 32 - 101 - 120 - 101 - 99 - 117 - 116 - 105 - 111 - 110 - 32 - 111 - 102 - 32 - 116 - 104 - 101 - 32 - 119 - 104 - 111 - 108 - 101 - 32 - 99 - 111 - 110 - 116 - 114 - 111 - 108 - 32 - 115 - 121 - 115 - 116 - 101 - 109 - 46 - 10 - 32 - 32 - 105 - 102 - 32 - 67 - 97 - 108 - 108 - 101 - 100 - 79 - 110 - 108 - 105 - 110 - 101 - 32 - 61 - 61 - 32 - 37 - 102 - 32 - 116 - 104 - 101 - 110 - 32 - 10 - 32 - 32 - 32 - 32 - 83 - 99 - 104 - 101 - 109 - 97 - 116 - 105 - 99 - 73 - 110 - 102 - 111 - 32 - 61 - 32 - 39 - 79 - 102 - 102 - 45 - 108 - 105 - 110 - 101 - 32 - 99 - 111 - 109 - 112 - 105 - 108 - 101 - 100 - 39 - 59 - 10 - 32 - 32 - 10 - 32 - 32 - 32 - 32 - 47 - 47 - 32 - 100 - 101 - 102 - 97 - 117 - 108 - 116 - 32 - 111 - 117 - 116 - 112 - 117 - 116 - 32 - 40 - 100 - 117 - 109 - 109 - 121 - 41 - 10 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 111 - 117 - 116 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 99 - 111 - 110 - 115 - 116 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 48 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 111 - 117 - 116 - 108 - 105 - 115 - 116 - 32 - 61 - 32 - 108 - 105 - 115 - 116 - 40 - 111 - 117 - 116 - 41 - 59 - 10 - 32 - 32 - 32 - 32 - 91 - 115 - 105 - 109 - 44 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 93 - 32 - 61 - 32 - 108 - 100 - 95 - 115 - 116 - 101 - 112 - 115 - 50 - 40 - 115 - 105 - 109 - 44 - 32 - 48 - 44 - 32 - 97 - 99 - 116 - 105 - 118 - 97 - 116 - 105 - 111 - 110 - 95 - 115 - 105 - 109 - 115 - 116 - 101 - 112 - 115 - 61 - 49 - 48 - 44 - 32 - 118 - 97 - 108 - 117 - 101 - 115 - 61 - 91 - 48 - 44 - 49 - 93 - 41 - 59 - 10 - 32 - 32 - 101 - 110 - 100 - 44 - 10 - 32 - 32 - 10 - 101 - 110 - 100 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 10 - 10 - 98 - 108 - 111 - 99 - 107 - 46 - 105 - 110 - 118 - 101 - 99 - 115 - 105 - 122 - 101 - 61 - 49 - 59 - 10 - 98 - 108 - 111 - 99 - 107 - 46 - 111 - 117 - 116 - 118 - 101 - 99 - 115 - 105 - 122 - 101 - 61 - 50 - 48 - 59 - 10 - 99 - 102 - 112 - 97 - 114 - 46 - 105 - 110 - 115 - 105 - 122 - 101 - 115 - 61 - 91 - 49 - 93 - 59 - 10 - 99 - 102 - 112 - 97 - 114 - 46 - 105 - 110 - 116 - 121 - 112 - 101 - 115 - 61 - 91 - 50 - 53 - 55 - 93 - 59 - 10 - 99 - 102 - 112 - 97 - 114 - 46 - 111 - 117 - 116 - 115 - 105 - 122 - 101 - 115 - 61 - 91 - 49 - 93 - 59 - 10 - 99 - 102 - 112 - 97 - 114 - 46 - 111 - 117 - 116 - 116 - 121 - 112 - 101 - 115 - 61 - 91 - 50 - 53 - 55 - 93 - 59 - 10 - 99 - 102 - 112 - 97 - 114 - 46 - 105 - 100 - 101 - 110 - 116 - 95 - 115 - 116 - 114 - 61 - 39 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 68 - 101 - 109 - 111 - 39 - 32 - 10 - 59 - 10 - 59 - 10 - 98 - 108 - 111 - 99 - 107 - 61 - 99 - 111 - 109 - 112 - 95 - 102 - 110 - 40 - 98 - 108 - 111 - 99 - 107 - 44 - 32 - 52 - 41 - 59 - 10 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 39 - 115 - 99 - 105 - 108 - 97 - 98 - 32 - 99 - 111 - 109 - 112 - 117 - 116 - 97 - 116 - 105 - 111 - 110 - 97 - 108 - 32 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 32 - 105 - 110 - 105 - 116 - 105 - 97 - 108 - 105 - 115 - 101 - 100 - 92 - 110 - 39 - 41 - 59 - 32 - 10 - 98 - 108 - 111 - 99 - 107 - 46 - 105 - 110 - 112 - 116 - 114 - 32 - 61 - 32 - 108 - 105 - 115 - 116 - 40 - 49 - 41 - 59 - 10 - 98 - 108 - 111 - 99 - 107 - 46 - 111 - 117 - 116 - 112 - 116 - 114 - 32 - 61 - 32 - 108 - 105 - 115 - 116 - 40 - 49 - 41 - 59 - 10 - 98 - 108 - 111 - 99 - 107 - 46 - 105 - 110 - 112 - 116 - 114 - 40 - 49 - 41 - 32 - 61 - 32 - 115 - 99 - 105 - 108 - 97 - 98 - 95 - 105 - 110 - 116 - 101 - 114 - 102 - 46 - 105 - 110 - 118 - 101 - 99 - 49 - 59 - 98 - 108 - 111 - 99 - 107 - 61 - 99 - 111 - 109 - 112 - 95 - 102 - 110 - 40 - 98 - 108 - 111 - 99 - 107 - 44 - 32 - 49 - 41 - 59 - 32 - 115 - 99 - 105 - 108 - 97 - 98 - 95 - 105 - 110 - 116 - 101 - 114 - 102 - 46 - 111 - 117 - 116 - 118 - 101 - 99 - 49 - 32 - 61 - 32 - 98 - 108 - 111 - 99 - 107 - 46 - 111 - 117 - 116 - 112 - 116 - 114 - 40 - 49 - 41 - 59 - 32 - 10 - 10 - 98 - 108 - 111 - 99 - 107 - 61 - 99 - 111 - 109 - 112 - 95 - 102 - 110 - 40 - 98 - 108 - 111 - 99 - 107 - 44 - 32 - 53 - 41 - 59 - 10 - 112 - 114 - 105 - 110 - 116 - 102 - 40 - 39 - 115 - 99 - 105 - 108 - 97 - 98 - 32 - 99 - 111 - 109 - 112 - 117 - 116 - 97 - 116 - 105 - 111 - 110 - 97 - 108 - 32 - 102 - 117 - 110 - 99 - 116 - 105 - 111 - 110 - 32 - 100 - 101 - 115 - 116 - 114 - 117 - 99 - 116 - 101 - 100 - 92 - 110 - 39 - 41 - 59 - 32 - 10 - 99 - 108 - 101 - 97 - 114 - 32 - 98 - 108 - 111 - 99 - 107 - 59 - 10 - 66 - 85 - 73 - 76 - 68 - 73 - 78 - 95 - 80 - 65 - 84 - 72 - 0 - 170 - 205 - 38 - 0 - 1 - 0 - 20 - 36 - 84 - 104 - 101 - 32 - 102 - 114 - 111 - 109 - 32 - 83 - 99 - 105 - 108 - 97 - 98 - 32 - 114 - 101 - 116 - 117 - 114 - 110 - 101 - 100 - 32 - 118 - 97 - 108 - 117 - 101 - 115 - 32 - 97 - 114 - 101 - 32 - 40 - 206 - 0 - 1 - 1 - 0 - 40 - 208 - 0 - 1 - 1 - 0 - 60009 - 210 - 2 - 0 - 1 - 0 - 20 - 257 - 60009 - 212 - 2 - 0 - 1 - 0 - 20 - 257 - 40 - 214 - 0 - 1 - 1 - 0 - 15003 - 216 - 124 - 0 - 1 - 0 - 0 - 0 - 0 - 41 - 41 - 36 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 68 - 101 - 109 - 111 - 95 - 82 - 101 - 112 - 108 - 97 - 99 - 101 - 97 - 98 - 108 - 101 - 83 - 105 - 109 - 117 - 108 - 97 - 116 - 105 - 111 - 110 - 46 - 105 - 112 - 97 - 114 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 68 - 101 - 109 - 111 - 95 - 82 - 101 - 112 - 108 - 97 - 99 - 101 - 97 - 98 - 108 - 101 - 83 - 105 - 109 - 117 - 108 - 97 - 116 - 105 - 111 - 110 - 46 - 114 - 112 - 97 - 114 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 68 - 101 - 109 - 111 - 95 - 82 - 101 - 112 - 108 - 97 - 99 - 101 - 97 - 98 - 108 - 101 - 83 - 105 - 109 - 117 - 108 - 97 - 116 - 105 - 111 - 110 - 0 - 9 - 8 - 4 - 0 - 201 - 201 - 0 - 0 - 203 - 203 - 0 - 0 - 203 - 203 - 0 - 0 - 205 - 205 - 0 - 0 - 203 - 203 - 0 - 0 - 210 - 210 - 0 - 0 - 206 - 206 - 0 - 0 - 210 - 210 - 1 - 0 - 203 - 203 - 0 - 0 - 212 - 212 - 0 - 0 - 208 - 208 - 0 - 0 - 212 - 212 - 1 - 0 - 210 - 210 - 0 - 0 - 216 - 216 - 0 - 0 - 214 - 214 - 0 - 0 - 216 - 216 - 1 - 0 - 212 - 212 - 0 - 1 - 0 - 0 - 0 - 4 - 60032 - 208 - 0 - 0 - 1 - 0 - 60006 - 210 - 1 - 0 - 1 - 0 - 2 - 60002 - 212 - 2 - 0 - 1 - 0 - 2 - 0 - 60014 - 215 - 0 - 0 - 1 - 0 - 170 - 217 - 22 - 0 - 1 - 0 - 1 - 20 - 99 - 111 - 109 - 112 - 117 - 116 - 97 - 116 - 105 - 111 - 110 - 95 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 170 - 218 - 25 - 0 - 1 - 0 - 1 - 23 - 99 - 111 - 109 - 112 - 117 - 116 - 97 - 116 - 105 - 111 - 110 - 95 - 78 - 111 - 116 - 102 - 105 - 110 - 105 - 115 - 104 - 101 - 100 - 40 - 219 - 0 - 1 - 1 - 0 - 170 - 221 - 18 - 0 - 1 - 0 - 1 - 16 - 99 - 97 - 108 - 99 - 117 - 108 - 97 - 116 - 105 - 110 - 103 - 32 - 46 - 46 - 46 - 32 - 40 - 222 - 0 - 1 - 1 - 0 - 40 - 224 - 0 - 1 - 1 - 0 - 60020 - 226 - 0 - 1 - 1 - 0 - 60020 - 228 - 0 - 1 - 1 - 0 - 60020 - 230 - 0 - 1 - 1 - 0 - 60020 - 232 - 0 - 1 - 1 - 0 - 0 - 19 - 8 - 4 - 0 - 203 - 203 - 0 - 0 - 205 - 205 - 0 - 0 - 205 - 205 - 0 - 0 - 208 - 208 - 0 - 0 - 208 - 208 - 0 - 0 - 210 - 210 - 0 - 0 - 210 - 210 - 0 - 0 - 212 - 212 - 0 - 0 - 205 - 205 - 1 - 0 - 215 - 215 - 0 - 0 - 205 - 205 - 1 - 0 - 217 - 217 - 0 - 0 - 215 - 215 - 0 - 0 - 218 - 218 - 0 - 0 - 219 - 219 - 0 - 0 - 221 - 221 - 0 - 0 - 212 - 212 - 0 - 0 - 226 - 226 - 0 - 0 - 224 - 224 - 0 - 0 - 226 - 226 - 1 - 0 - 212 - 212 - 1 - 0 - 228 - 228 - 0 - 0 - 226 - 226 - 0 - 0 - 228 - 228 - 1 - 0 - 215 - 215 - 0 - 0 - 230 - 230 - 0 - 0 - 228 - 228 - 0 - 0 - 230 - 230 - 1 - 0 - 219 - 219 - 0 - 0 - 232 - 232 - 0 - 0 - 230 - 230 - 0 - 0 - 232 - 232 - 1 - 0 - 222 - 222 - 0 - 1 - 0 - 0 - 0 - 0 - 232 - 232 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 1 - 3 - 203 - 100 - 0 - 0 - 8 - 1 - 205 - 100 - 8 - 1 - 6 - 1 - 100 - 101 - 14 - 2 - 28 - 0 - 60010 - 203 - 2 - 1 - 1 - 0 - 1 - 0 - 40 - 205 - 0 - 1 - 1 - 0 - 0 - 3 - 8 - 4 - 0 - 203 - 203 - 0 - 1 - 0 - 0 - 0 - 0 - 205 - 205 - 0 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 1 - 1 - 0 - 0 - 2 - 170 - 211 - 9 - 0 - 1 - 0 - 1 - 7 - 111 - 117 - 116 - 112 - 117 - 116 - 32 - 0 - 3 - 8 - 4 - 0 - 201 - 201 - 0 - 0 - 203 - 203 - 0 - 0 - 205 - 205 - 0 - 0 - 207 - 207 - 0 - 0 - 207 - 207 - 0 - 0 - 211 - 211 - 0 - 26 - 0 - 1 - 8 - 4 - 0 - 201 - 201 - 0 - 0 - 203 - 203 - 0 diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar deleted file mode 100644 index 38f8a318..00000000 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.rpar +++ /dev/null @@ -1,26 +0,0 @@ -0.0500000000000000027755576 -1.2339999999999999857891453 -1.0000000000000000000000000 -0.0000000000000000000000000 -1.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -1.0000000000000000000000000 -0.0000000000000000000000000 -2.0000000000000000000000000 -0.0000000000000000000000000 -1.0000000000000000000000000 -2.0000000000000000000000000 -2.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -1.0000000000000000000000000 -3.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce b/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce deleted file mode 100644 index 8de58ae2..00000000 --- a/data_sources/ORTD/DataSourceAutoConfigExample/SelectCasePaPiConfig.sce +++ /dev/null @@ -1,343 +0,0 @@ -// -// Copyright (C) 2011, 2012, 2013, 2014, 2015 Christian Klauer -// -// This file is part of OpenRTDynamics, the Real-Time Dynamics Framework -// -// OpenRTDynamics is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// OpenRTDynamics is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with OpenRTDynamics. If not, see . -// -funcprot(0); -// The name of the program -ProgramName = 'SelectCasePaPiConfig'; // must be the filename without .sce - - -function [sim, outlist] = AutoConfigExample(sim, Signal) - - function [sim, finished, outlist, userdata] = ExperimentCntrl(sim, ev, inlist, userdata, CalledOnline) - - // Define parameters. They must be defined once again at this place, because this will also be called at - // runtime. - NSamples=300; - - if CalledOnline == %t then - // The contents of this part will be compiled on-line, while the control - // system is running. The aim is to generate a new compiled schematic for - // the experiment. - // Please note: Since this code is only executed on-line, most potential errors - // occuring in this part become only visible during runtime. - - printf("Compiling a new control system\n"); - exec('webinterface/PacketFramework.sce'); - funcprot(0); - z = poly(0,'z'); - // And example-system that is controlled via UDP and one step further with the Web-gui - // Superblock: A more complex oscillator with damping - function [sim, x,v] = damped_oscillator(sim, u, T_a) - // create feedback signals - [sim,x_feedback] = libdyn_new_feedback(sim); - - [sim,v_feedback] = libdyn_new_feedback(sim); - - // use this as a normal signal - [sim,a] = ld_add(sim, ev, list(u, x_feedback), [1, -1]); - [sim,a] = ld_add(sim, ev, list(a, v_feedback), [1, -1]); - - [sim,v] = ld_ztf(sim, ev, a, 1/(z-1) * T_a ); // Integrator approximation - - // feedback gain - [sim,v_gain] = ld_gain(sim, ev, v, 0.1); - - // close loop v_gain = v_feedback - [sim] = libdyn_close_loop(sim, v_gain, v_feedback); - - - [sim,x] = ld_ztf(sim, ev, v, 1/(z-1) * T_a ); // Integrator approximation - - // feedback gain - [sim,x_gain] = ld_gain(sim, ev, x, 0.6); - - // close loop x_gain = x_feedback - [sim] = libdyn_close_loop(sim, x_gain, x_feedback); - endfunction - - if userdata.isInitialised == %f then - // - // State variables can be initialise at this place - // - userdata.Acounter = 0; - userdata.State = "oscillator"; - - userdata.isInitialised = %t; // prevent from initialising the variables once again - end - - // - // Example for a state update: increase the counter - // - userdata.Acounter = userdata.Acounter + 1; - - // Build an info-string - SchematicInfo = "On-line compiled in iteration #" + string(userdata.Acounter); - - // - // Define a new experiment controller schematic depending on the currently active state - // - - // initalise the PaPi UDP Socket - Configuration.UnderlyingProtocoll = "UDP"; - Configuration.DestHost = "127.0.0.1"; - Configuration.DestPort = 20000; - Configuration.LocalSocketHost = "127.0.0.1"; - Configuration.LocalSocketPort = 20001; - PacketFramework.Configuration.debugmode = %t; - [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="TrockenofenRemoteControl", Configuration); - - [sim, zero] = ld_const(sim, ev, 0); - [sim, one] = ld_const(sim, 0, 1); - - // default output (dummy) - outlist=list(zero); - - // - // Here a state-machine is implemented that may be used to implement some automation - // logic that is executed during runtime using the embedded Scilab interpreter. - // In this example, a calibration run succeeded by the design/compilation/execution - // of a control-system is implemented. The schematics defined in each state are loaded - // at runtime. - // - select userdata.State - case "oscillator" // define an experiment to perform an oscillator experiment - in1 = inlist(1); - [sim] = ld_printf(sim, 0, in1, "Case oscillator active: ", 1); - - // Add a parameter for controlling the oscillator - [sim, PacketFramework, Input]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Oscillator input"); - - // some more parameters - [sim, PacketFramework, AParVector]=ld_PF_Parameter(sim, PacketFramework, NValues=10, datatype=ORTD.DATATYPE_FLOAT, ParameterName="A vectorial parameter"); - [sim, PacketFramework, par2]=ld_PF_Parameter(sim, PacketFramework, NValues=2, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Test"); - - // printf these parameters - [sim] = ld_printf(sim, ev, Input, "Oscillator input ", 1); - [sim] = ld_printf(sim, ev, par2, "Test ", 2); - [sim] = ld_printf(sim, ev, AParVector, "A vectorial parameter", 10); - - // The system to control - [sim, Input] = ld_add_ofs(sim, 0, Input, 0.2); - T_a = 0.1; [sim, x,v] = damped_oscillator(sim, Input, T_a); - - // Stream the data of the oscillator - [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X"); - PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'Plot'; - PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,500)'; - PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(100,0)'; - PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot X'; - PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup'+string(userdata.Acounter); - PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('X'); - - - [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V"); - - // finalise the communication interface - [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); - ld_PF_Export_js(PacketFramework, fname="ProtocollConfig.json"); - - // Wait until a number of time steps has passed, then notify that - // the experiment has finished by setting "finished" to 1. - [sim, finished] = ld_steps2(sim, ev, activation_simsteps=NSamples, values=[0,1] ); - - [sim, out] = ld_const(sim, 0, 11); - outlist=list(out); - - // chose the next state to enter when the calibration experiment has finished - userdata.State = "constant"; - - case "constant" // design a constant signal to send to PaPi - in1 = inlist(1); - [sim] = ld_printf(sim, 0, in1, "Case constant active: ", 1); - - // Stream a constant - [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=in1, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Const"); - PacketFramework.PaPIConfig.ToCreate.plot_X.identifier.value = 'Plot'; - PacketFramework.PaPIConfig.ToCreate.plot_X.config.size.value = '(300,300)'; - PacketFramework.PaPIConfig.ToCreate.plot_X.config.position.value = '(300,0)'; - PacketFramework.PaPIConfig.ToCreate.plot_X.config.name.value = 'Plot Const'; - PacketFramework.PaPIConfig.ToSub.plot_X.block = 'SourceGroup'+string(userdata.Acounter) ; - PacketFramework.PaPIConfig.ToSub.plot_X.signals = list('Const'); - - // finalise the communication interface - [sim,PacketFramework] = ld_PF_Finalise(sim, PacketFramework); - ld_PF_Export_js(PacketFramework, fname="ProtocollConfig.json"); - - // Wait until a number of time steps has passed, then notify that - // the experiment (control system in this case) has finished. - [sim, finished] = ld_steps2(sim, ev, activation_simsteps=NSamples, values=[0,1] ); - - [sim, out] = ld_const(sim, 0, 22); - outlist=list(out); - - // chose the next state to enter when the demo experiment has finished - userdata.State = "oscillator"; - - case "finished" // experiment finished - nothing to do - in1 = inlist(1); - [sim] = ld_printf(sim, ev, in1, "Case finished active" , 1); - [sim, finished] = ld_const(sim, ev, 0); // never finish - [sim, out] = ld_const(sim, 0, 33); - outlist=list(out); - - end - end // CalledOnline == %t - - // When RTmain.sce is executed, this part will be run. It may be used to define an initial experiment in advance to - // the execution of the whole control system. - if CalledOnline == %f then - SchematicInfo = "Off-line compiled"; - - // default output (dummy) - [sim, out] = ld_const(sim, 0, 0); - outlist=list(out); - [sim, finished] = ld_steps2(sim, 0, activation_simsteps=10, values=[0,1] ); - end - - endfunction - - - - - function [sim, outlist, HoldState, userdata] = whileComputing_example(sim, ev, inlist, CalibrationReturnVal, computation_finished, par); - - [sim, HoldState] = ld_const(sim, 0, 0); - - [sim] = ld_printf(sim, 0, HoldState, "calculating ... " , 1); - - // While the computation is running this is called regularly - [sim, out] = ld_const(sim, ev, 0); - outlist=list(out); - endfunction - - - function [sim, ToScilab, userdata] = PreScilabRun(sim, ev, par) - userdata = par.userdata; - - // get the stored sensor data - [sim, ToScilab] = ld_const(sim, ev, 0); - endfunction - - - // Start the experiment - ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; - ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; - - insizes=[1]; outsizes=[1]; - intypes=[ORTD.DATATYPE_FLOAT]; outtypes=[ORTD.DATATYPE_FLOAT]; - - - CallbackFns.experiment = ExperimentCntrl; - CallbackFns.whileComputing = whileComputing_example; - CallbackFns.PreScilabRun = PreScilabRun; - - // Please note ident_str must be unique. - userdata = []; - [sim, finished, outlist, userdata] = ld_AutoOnlineExch_dev(sim, 0, inlist=list(Signal), ... - insizes, outsizes, intypes, outtypes, ... - ThreadPrioStruct, CallbackFns, ident_str="AutoConfigDemo", userdata); - - PacketFramework = userdata(1); -// [sim] = ld_printf(sim, 0, finished, "State ", 1); - -endfunction - - -// The main real-time thread -function [sim, outlist, userdata] = Thread_MainRT(sim, inlist, userdata) - // This will run in a thread - [sim, Tpause] = ld_const(sim, ev, 1/20); // The sampling time that is constant at 20 Hz in this example - [sim, out] = ld_ClockSync(sim, ev, in=Tpause); // synchronise this simulation - - // - // Add you own control system here - // - - // some dummy input to the state machine - [sim,Signal] = ld_const(sim, 0, 1.234); - - [sim, outlist] = AutoConfigExample(sim, Signal); - - [sim] = ld_printf(sim, 0, outlist(1) , "output ", 1); - - - outlist = list(); -endfunction - -// This is the main top level schematic -function [sim, outlist] = schematic_fn(sim, inlist) - -// -// Create a thread that runs the control system -// - - ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; // or ORTD.ORTD_RT_REALTIMETASK - ThreadPrioStruct.prio2=0; // for ORTD.ORTD_RT_REALTIMETASK: 1-99 as described in man sched_setscheduler - // for ORTD.ORTD_RT_NORMALTASK this is the nice-value (higher value means less priority) - ThreadPrioStruct.cpu = -1; // The CPU on which the thread will run; -1 dynamically assigns to a CPU, - // counting of the CPUs starts at 0 - - [sim, StartThread] = ld_initimpuls(sim, ev); // triggers your computation only once - [sim, outlist, computation_finished] = ld_async_simulation(sim, ev, ... - inlist=list(), ... - insizes=[], outsizes=[], ... - intypes=[], outtypes=[], ... - nested_fn = Thread_MainRT, ... - TriggerSignal=StartThread, name="MainRealtimeThread", ... - ThreadPrioStruct, userdata=list() ); - - - // output of schematic (empty) - outlist = list(); -endfunction - - - - - - - - - - -// -// Set-up (no detailed understanding necessary) -// - -thispath = get_absolute_file_path(ProgramName+'.sce'); -cd(thispath); -z = poly(0,'z'); - -exec('webinterface/PacketFramework.sce'); - -// defile ev -ev = [0]; // main event - -// set-up schematic by calling the user defined function "schematic_fn" -insizes = []; outsizes=[]; -[sim_container_irpar, sim]=libdyn_setup_schematic(schematic_fn, insizes, outsizes); - -// pack the simulation into a irpar container -parlist = new_irparam_set(); -parlist = new_irparam_container(parlist, sim_container_irpar, 901); // pack simulations into irpar container with id = 901 -par = combine_irparam(parlist); // complete irparam set -save_irparam(par, ProgramName+'.ipar', ProgramName+'.rpar'); // Save the schematic to disk - -// clear -par.ipar = []; par.rpar = []; - diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh b/data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh deleted file mode 100755 index c0a83aea..00000000 --- a/data_sources/ORTD/DataSourceAutoConfigExample/run_SelectCasePaPiConfig.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -#ortd --baserate=10 --rtmode 1 -s SelectCasePaPiConfig -i 901 -l 0 -ortdrun -s SelectCasePaPiConfig diff --git a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce deleted file mode 100644 index a08a9981..00000000 --- a/data_sources/ORTD/DataSourceAutoConfigExample/webinterface/PacketFramework.sce +++ /dev/null @@ -1,1202 +0,0 @@ - - -// Interfacing functions are placed in this place - -function [sim, out] = ld_udp_main_receiver(sim, events, udpport, identstr, socket_fname, vecsize) // PARSEDOCU_BLOCK - // udp main receiver - block - // - // This is a simulation-synchronising Block - // - // EXPERIMENTAL FIXME: REMOVE - // - - datatype = ORTD.DATATYPE_FLOAT; - - btype = 39001; - [sim,blk] = libdyn_new_block(sim, events, btype, ipar=[ udpport, vecsize, datatype, length(socket_fname), ascii(socket_fname), length(identstr), ascii(identstr) ], rpar=[ ], ... - insizes=[], outsizes=[vecsize], ... - intypes=[], outtypes=[ORTD.DATATYPE_FLOAT] ); - - //[sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); - [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port -endfunction - - - - -function [sim] = ld_UDPSocket_shObj(sim, events, ObjectIdentifyer, Visibility, hostname, UDPPort) // PARSEDOCU_BLOCK - // - // Set-up an UDP-Socket - // - // hostname - Network interface to bind socket to??? - // UDPPort - UDP port to bind. If -1 then no UDP server is set-up - // - // EXPERIMENTAL - // - - // add a postfix that identifies the type of the shared object - ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; - - - - // pack all parameters into a structure "parlist" - parlist = new_irparam_set(); - - parlist = new_irparam_elemet_ivec(parlist, UDPPort, 10); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 11); // id = 11; A string parameter - - p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - - // Set-up the block parameters. There are no I/O ports - Uipar = [ p.ipar ]; - Urpar = [ p.rpar ]; - btype = 39001 + 0; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - - [sim] = libdyn_CreateSharedObjBlk(sim, btype, ObjectIdentifyer, Visibility, Uipar, Urpar); -endfunction - -function [sim] = ld_UDPSocket_Send(sim, events, ObjectIdentifyer, in, insize, intype) // PARSEDOCU_BLOCK - // - // UDP - Send block - // - // in *, ORTD.DATATYPE_BINARY - input - // - // EXPERIMENTAL, About to be removed - // - - // add a postfix that identifies the type of the shared object - ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; - - - // pack all parameters into a structure "parlist" - parlist = new_irparam_set(); - - parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 - - p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - - // Set-up the block parameters and I/O ports - Uipar = [ p.ipar ]; - Urpar = [ p.rpar ]; - btype = 39001 + 1; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - - insizes=[insize]; // Input port sizes - outsizes=[]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) - intypes=[intype]; // datatype for each input port - outtypes=[]; // datatype for each output port - - blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) - - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); - - // connect the inputs - [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 - - // // connect the ouputs - // [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port -endfunction - - -function [sim, out, SrcAddr] = ld_UDPSocket_Recv(sim, events, ObjectIdentifyer, outsize) // PARSEDOCU_BLOCK - // - // UDP - receiver block - // - // out *, ORTD.DATATYPE_BINARY - output - // SrcAddr - information about where the package comes from (not implemented) - // - // This is a simulation-synchronising Block. Everytime an UDP-Packet is received, - // the simulation that contains this blocks goes on for one step. - // - // EXPERIMENTAL - // - - printf("Synchronising simulation to UDP-Receiver\n"); - - // add a postfix that identifies the type of the shared object - ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; - - // - outtype = ORTD.DATATYPE_BINARY; - - // IPv4 - AddrSize = 4+2; // IPnumber + port - - // pack all parameters into a structure "parlist" - parlist = new_irparam_set(); - - parlist = new_irparam_elemet_ivec(parlist, outsize, 10); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, outtype, 11); // id = 11 - - p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - - // Set-up the block parameters and I/O ports - Uipar = [ p.ipar ]; - Urpar = [ p.rpar ]; - btype = 39001 + 2; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - - insizes=[]; // Input port sizes - outsizes=[outsize, AddrSize]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) - intypes=[]; // datatype for each input port - outtypes=[outtype, ORTD.DATATYPE_BINARY]; // datatype for each output port - - blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) - - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); - - // // connect the inputs - // [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 - - // connect the ouputs - [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port - [sim,SrcAddr] = libdyn_new_oport_hint(sim, blk, 1); // 1th port -endfunction - -function [sim] = ld_UDPSocket_SendTo(sim, events, SendSize, ObjectIdentifyer, hostname, UDPPort, in, insize) // PARSEDOCU_BLOCK - // - // UDP - Send block - // - // in *, ORTD.DATATYPE_BINARY - input - // SendSize *. ORTD.DATATYPE_INT32 - Number of bytes to send - // - // EXPERIMENTAL - // - - - // add a postfix that identifies the type of the shared object - ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; - - // only send binary data - intype=ORTD.DATATYPE_BINARY; - - - // pack all parameters into a structure "parlist" - parlist = new_irparam_set(); - - parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 - - parlist = new_irparam_elemet_ivec(parlist, UDPPort, 12); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 13); // id = 11; A string parameter - - - p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - - // Set-up the block parameters and I/O ports - Uipar = [ p.ipar ]; - Urpar = [ p.rpar ]; - btype = 39001 + 3; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - - insizes=[insize, 1]; // Input port sizes - outsizes=[]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) - intypes=[intype, ORTD.DATATYPE_INT32 ]; // datatype for each input port - outtypes=[]; // datatype for each output port - - blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) - - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); - - // connect the inputs - [sim,blk] = libdyn_conn_equation(sim, blk, list(in, SendSize) ); // connect in1 to port 0 and in2 to port 1 - - // // connect the ouputs - // [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port -endfunction - - - - - - -function [sim] = ld_UDPSocket_Reply(sim, events, SendSize, ObjectIdentifyer, DestAddr, in, insize) // PARSEDOCU_BLOCK - // - // UDP - Send block - // - // in *, ORTD.DATATYPE_BINARY - input - // SendSize *. ORTD.DATATYPE_INT32 - Number of bytes to send - // DestAddr - dynamic representation for the destination address - // - // EXPERIMENTAL not implemented by now - // - - - // add a postfix that identifies the type of the shared object - ObjectIdentifyer = ObjectIdentifyer + ".UDPSocket_ShObj"; - - // only send binary data - intype=ORTD.DATATYPE_BINARY; - - // IPv4 - AddrSize=4+2; - - // pack all parameters into a structure "parlist" - parlist = new_irparam_set(); - - parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 - parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 - - // parlist = new_irparam_elemet_ivec(parlist, UDPPort, 12); // id = 10 - // parlist = new_irparam_elemet_ivec(parlist, ascii(hostname), 13); // id = 11; A string parameter - - - p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - - // Set-up the block parameters and I/O ports - Uipar = [ p.ipar ]; - Urpar = [ p.rpar ]; - btype = 39001 + 4; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - - insizes=[insize, 1, AddrSize]; // Input port sizes - outsizes=[]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) - intypes=[intype, ORTD.DATATYPE_INT32, ORTD.DATATYPE_BINARY ]; // datatype for each input port - outtypes=[]; // datatype for each output port - - blocktype = 1; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) - - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed, ObjectIdentifyer); - - // connect the inputs - [sim,blk] = libdyn_conn_equation(sim, blk, list(in, SendSize, DestAddr) ); // connect in1 to port 0 and in2 to port 1 - - // // connect the ouputs - // [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port -endfunction - - - - - - - - - - - - -function [sim, out, NBytes] = ld_ConcateData(sim, events, inlist, insizes, intypes) // PARSEDOCU_BLOCK - // - // Concate Data - block - // - // concatenates the binary representation of all inputs - // - // The output is of type ORTD.DATATYPE_BINARY - // - // EXPERIMENTAL - // - - - // pack all parameters into a structure "parlist" - // parlist = new_irparam_set(); - // - // parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 - // parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 - // - // p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - - // Set-up the block parameters and I/O ports - Uipar = [ ]; - Urpar = [ ]; - btype = 39001 + 10; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - - // count the number of bytes - NBytes = 0; - for i = 1:length(inlist) - NBytes = NBytes + insizes(i) * libdyn_datatype_len( intypes(i) ); - end - - - - // insizes=[insizes]; // Input port sizes - outsizes=[ NBytes ]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) - // intypes=[intypes]; // datatype for each input port - outtypes=[ ORTD.DATATYPE_BINARY ]; // datatype for each output port - - // disp(outsizes); - // disp(outtypes); - - blocktype = 2; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) - - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed); - - // connect the inputs - // for i = 1:length(inlist) - [sim,blk] = libdyn_conn_equation(sim, blk, inlist ); // connect in1 to port 0 and in2 to port 1 - // end - - // // connect the ouputs - [sim,out] = libdyn_new_oport_hint(sim, blk, 0); // 0th port -endfunction - - -function [sim, outlist] = ld_DisassembleData(sim, events, in, outsizes, outtypes) // PARSEDOCU_BLOCK - // - // disasseble Data - block - // - // disassemble the binary representation of the input, which is of type ORTD.DATATYPE_BINARY - // - // EXPERIMENTAL - // - - - // pack all parameters into a structure "parlist" - // parlist = new_irparam_set(); - // - // parlist = new_irparam_elemet_ivec(parlist, insize, 10); // id = 10 - // parlist = new_irparam_elemet_ivec(parlist, intype, 11); // id = 11 - // - // p = combine_irparam(parlist); // convert to two vectors of integers and floating point values respectively - - // Set-up the block parameters and I/O ports - Uipar = [ ]; - Urpar = [ ]; - btype = 39001 + 11; // Reference to the block's type (computational function). Use the same id you are giving via the "libdyn_compfnlist_add" C-function - - // count the number of bytes - NBytes = 0; - for i = 1:length(outsizes) - NBytes = NBytes + outsizes(i)*libdyn_datatype_len( outtypes(i) ); - end - - - - // insizes=[insizes]; // Input port sizes - insizes=[ NBytes ]; // Output port sizes - dfeed=[1]; // for each output 0 (no df) or 1 (a direct feedthrough to one of the inputs) - // intypes=[intypes]; // datatype for each input port - intypes=[ ORTD.DATATYPE_BINARY ]; // datatype for each output port - - // disp(outsizes); - // disp(outtypes); - - blocktype = 2; // 1-BLOCKTYPE_DYNAMIC (if block uses states), 2-BLOCKTYPE_STATIC (if there is only a static relationship between in- and output) - - // Create the block - [sim, blk] = libdyn_CreateBlockAutoConfig(sim, events, btype, blocktype, Uipar, Urpar, insizes, outsizes, intypes, outtypes, dfeed); - - // connect the inputs - // for i = 1:length(inlist) - [sim,blk] = libdyn_conn_equation(sim, blk, list(in) ); // connect in1 to port 0 and in2 to port 1 - // end - - // // connect the ouputs - outlist = list(); - for i = 1:length(outtypes) - [sim,outlist($+1)] = libdyn_new_oport_hint(sim, blk, i-1); // 0th port - end -endfunction - - - - - - - - - - -// -// -// A packet based communication interface from ORTD using UDP datagrams to e.g. -// nodejs. -// webappUDP.js is the counterpart that provides a web-interface -// -// Current Rev: 8 -// -// Revisions: -// -// 27.3.14 - possibility to reservate sources -// 3.4.14 - small re-arrangements -// 4.4.14 - Bugfixes -// 7.4.14 - Bugfix -// 12.6.14 - Bugfix -// 2.11.14 - Added group finalising packet -// - - -function [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName) - SourceID = PacketFramework.SourceID_counter; - - Source.SourceName = SourceName; - Source.SourceID = SourceID; - Source.NValues_send = NValues_send; - Source.datatype = datatype; - - // Add new source to the list - PacketFramework.Sources($+1) = Source; - - // inc counter - PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; -endfunction - -function [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName) - ParameterID = PacketFramework.Parameterid_counter; - - Parameter.ParameterName = ParameterName; - Parameter.ParameterID = ParameterID; - Parameter.NValues = NValues; - Parameter.datatype = datatype; - Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; - - // Add new source to the list - PacketFramework.Parameters($+1) = Parameter; - - // inc counters - PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; - PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; - - // return values - ParameterID = Parameter.ParameterID; - MemoryOfs = Parameter.MemoryOfs; -endfunction - -function [sim, PacketFramework, Parameter] = ld_PF_Parameter(sim, PacketFramework, NValues, datatype, ParameterName) // PARSEDOCU_BLOCK - // - // Define a parameter - // - // NValues - amount of data sets - // datatype - only ORTD.DATATYPE_FLOAT for now - // ParameterName - a unique string decribing the parameter - // - // - // - - [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); - - // read data from global memory - [sim, readI] = ld_const(sim, 0, MemoryOfs); // start at index 1 - [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... - datatype, NValues); -endfunction - - - - -// Send a signal via UDP, a simple protocoll is defined, internal function -function [sim] = ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID) - InstanceName = PacketFramework.InstanceName; - [sim,one] = ld_const(sim, 0, 1); - - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - - // Source ID - [sim, SourceID] = ld_const(sim, 0, SourceID); - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); - - printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); - -endfunction - - - - -function [sim, PacketFramework] = ld_SendPacket(sim, PacketFramework, Signal, NValues_send, datatype, SourceName) // PARSEDOCU_BLOCK // PARSEDOCU_BLOCK - // - // Stream data - block - // - // Signal - the signal to stream - // NValues_send - the vector length of Signal - // datatype - only ORTD.DATATYPE_FLOAT by now - // SourceName - a unique string identifier descring the stream - // - // - // - - [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); - [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); -endfunction - - - - -function [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName, Configuration) // PARSEDOCU_BLOCK - // - // Initialise an instance of the Packet Framework - // - // InstanceName - a unique string identifier for the instance - // Configuration must include the following properties: - // - // Configuration.UnderlyingProtocoll = "UDP" - // Configuration.DestHost - // Configuration.DestPort - // Configuration.LocalSocketHost - // Configuration.LocalSocketPort - // - // - // Example: - // - // - // Configuration.UnderlyingProtocoll = "UDP"; - // Configuration.DestHost = "127.0.0.1"; - // Configuration.DestPort = 20000; - // Configuration.LocalSocketHost = "127.0.0.1"; - // Configuration.LocalSocketPort = 20001; - // [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); - // - // - // - // Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations - // - // - - // initialise structure for sources - PacketFramework.InstanceName = InstanceName; - PacketFramework.Configuration = Configuration; - - PacketFramework.Configuration.debugmode = %F; - - // disp(Configuration.UnderlyingProtocoll) - - if Configuration.UnderlyingProtocoll == 'UDP' - null; - else - error("PacketFramework: Only UDP supported up to now"); - end - - // possible packet sizes for UDP - PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes - PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; - - // sources - PacketFramework.SourceID_counter = 0; - PacketFramework.Sources = list(); - - // parameters - PacketFramework.Parameterid_counter = 0; - PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory - PacketFramework.Parameters = list(); - - PacketFramework.SenderID = 1295793; - - // Open an UDP-Port in server mode - [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... - hostname=PacketFramework.Configuration.LocalSocketHost, ... - UDPPort=PacketFramework.Configuration.LocalSocketPort); -endfunction - - -// Send a signal via UDP, a simple protocoll is defined, internal function -function [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID) - InstanceName = PacketFramework.InstanceName; - [sim,one] = ld_const(sim, 0, 1); - - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - - // Source ID - [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - - // Group ID - [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources - [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); - - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); - - // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); - - - // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); -endfunction - - -// Send the newConfigAvailable Signal via UDP, a simple protocoll is defined, internal function -function [sim] = ld_PF_SendNewConfigAvailableUDP(sim, PacketFramework) - InstanceName = PacketFramework.InstanceName; - [sim,one] = ld_const(sim, 0, 1); - - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - - // Source ID - [sim, SourceID] = ld_const(sim, 0, -2); // -2 means a new config is available - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - - // Group ID - [sim, GroupID_] = ld_const(sim, 0, 0); // 0 (not used) - [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); - - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); - - // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); - - - // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); -endfunction - -// Send the configuration via UDP, a simple protocoll is defined, internal function -function [sim] = ld_PF_SendConfigUDP(sim, PacketFramework) - InstanceName = PacketFramework.InstanceName; - - str_PF_Export = ld_PF_Export_str(PacketFramework); - strLength = length(str_PF_Export); - maxPacketLength = 1400; - maxPacketStrLength = maxPacketLength - 4*4; - nPackets = ceil(strLength/maxPacketStrLength); - - // Source ID - [sim, SourceID] = ld_const(sim, 0, -4); // -4 means configItem - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - - // nPackets - [sim, nPackets_] = ld_const(sim, 0, nPackets); // the number of config packets for the whole configuration - [sim, nPackets_int32] = ld_ceilInt32(sim, 0, nPackets_); - - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - - for i=1:nPackets - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_const(sim, ev, i); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - partBegin = (i-1)*maxPacketStrLength+1; - partEnd = i*maxPacketStrLength; - if (partEnd > strLength) - partEnd = strLength; - end - strPart_PF_Export = part(str_PF_Export, partBegin:partEnd); - sendSize = length(strPart_PF_Export); - [sim, partPF_Export_bin] = ld_const_bin(sim, ev, in=ascii(strPart_PF_Export)); - - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, nPackets_int32, partPF_Export_bin), insizes=[1,1,1,1,sendSize], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_BINARY ] ); - - // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - [sim] = ld_printf(sim, ev, SenderID, "SenderID", 1); - [sim] = ld_printf(sim, ev, SourceID, "Sent config packet number " + string(i) + " ", 1); - [sim] = ld_printf(sim, ev, Counter, "Packet Count ", 1); - [sim] = ld_printf(sim, ev, nPackets_, "Number of Packet ", 1); - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); - end - -endfunction - -// select case function for the different received PaPi Commands (SenderID) -function [sim, outlist, userdata] = SelectCasePaPiCmd(sim, inlist, Ncase, casename, userdata) - // This function is called multiple times -- once to define each case - // At runtime, all cases will become different nested simulations of - // which only one is active a a time. - - printf("Defining case %s (#%d) ...\n", casename, Ncase ); - - // define names for the first event in the simulation - events = 0; - - DisAsm_ = list(); - DisAsm_(4) = inlist(4); - [sim, DisAsm_(1)] = ld_Int32ToFloat(sim, 0, inlist(1) ); - [sim, DisAsm_(2)] = ld_Int32ToFloat(sim, 0, inlist(2) ); - [sim, DisAsm_(3)] = ld_Int32ToFloat(sim, 0, inlist(3) ); - PacketFramework = userdata(1); - ParameterMemory = PacketFramework.ParameterMemory; - TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes - InstanceName = PacketFramework.InstanceName; - // print out some state information - //[sim] = ld_printf(sim, events, in=DisAsm_(3), str="case "+string(casename)+" with Ncase = "+string(Ncase)+" : SourceID", insize=1); - - // sample data for the output - [sim, dummy] = ld_const(sim, 0, 9999); - - // The signals "active_state" is used to indicate state switching: A value > 0 means the - // the state enumed by "active_state" shall be activated in the next time step. - // A value less or equal to zero causes the statemachine to stay in its currently active - // state - select Ncase - case 1 - [sim, memofs] = ld_ArrayInt32(sim, 0, array=ParameterMemory.MemoryOfs, in=inlist(3) ); - [sim, Nelements] = ld_ArrayInt32(sim, 0, array=ParameterMemory.Sizes, in=inlist(3) ); - - [sim, memofs_] = ld_Int32ToFloat(sim, 0, memofs ); - [sim, Nelements_] = ld_Int32ToFloat(sim, 0, Nelements ); - - if PacketFramework.Configuration.debugmode then - // print the contents of the packet - [sim] = ld_printf(sim, 0, DisAsm_(1), "DisAsm(1) (SenderID) = ", 1); - [sim] = ld_printf(sim, 0, DisAsm_(2), "DisAsm(2) (Packet Counter) = ", 1); - [sim] = ld_printf(sim, 0, DisAsm_(3), "DisAsm(3) (SourceID) = ", 1); - [sim] = ld_printf(sim, 0, DisAsm_(4), "DisAsm(4) (Signal) = ", TotalElemetsPerPacket); - - [sim] = ld_printf(sim, 0, memofs_ , "memofs = ", 1); - [sim] = ld_printf(sim, 0, memofs_ , "Nelements = ", 1); - end - - // Store the input data into a shared memory - [sim] = ld_WriteMemory2(sim, 0, data=inlist(4), index=memofs, ElementsToWrite=Nelements, ... - ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); - - case 2 - [sim] = ld_printf(sim, 0, DisAsm_(3), "Give Config (SourceID) = ", 1); - [sim] = ld_PF_SendConfigUDP(sim, PacketFramework); - - case 3 - [sim] = ld_printf(sim, events, in=DisAsm_(3), str="case "+string(casename)+" : Wrong SourceID", insize=1); - end - - // the user defined output signals of this nested simulation - outlist = list(dummy); - userdata(1) = PacketFramework; -endfunction - -function [sim, outlist, userdata] = SelectCaseSendNewConfigAvailable(sim, inlist, Ncase, casename, userdata) - // This function is called multiple times -- once to define each case - // At runtime, all cases will become different nested simulations of - // which only one is active a a time. - - printf("Defining case %s (#%d) ...\n", casename, Ncase ); - - // define names for the first event in the simulation - events = 0; - - // pause; - - PacketFramework = userdata(1); - - // print out some state information - // [sim] = ld_printf(sim, events, in=in1, str="case"+string(casename)+": in1", insize=1); - - // sample data for the output - [sim, dummy] = ld_const(sim, 0, 999); - - // The signals "active_state" is used to indicate state switching: A value > 0 means the - // the state enumed by "active_state" shall be activated in the next time step. - // A value less or equal to zero causes the statemachine to stay in its currently active - // state - - select Ncase - case 1 // Finished - //[sim] = ld_printf(sim, events, dummy, "case "+string(casename)+" : Config already sent ", 1); - - case 2 // Send - //[sim] = ld_printf(sim, events, dummy, "case "+string(casename)+" : Config is sent to PaPi ", 1); - [sim] = ld_PF_SendNewConfigAvailableUDP(sim, PacketFramework); - - end - - // the user defined output signals of this nested simulation - outlist = list(dummy); - userdata(1) = PacketFramework; -endfunction - - -function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOCU_BLOCK - // - // Finalise the instance. - // - // - - // The main real-time thread - function [sim,PacketFramework] = ld_PF_InitUDP(sim, PacketFramework) - - function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) - // This will run in a thread. Each time a UDP-packet is received - // one simulation step is performed. Herein, the packet is parsed - // and the contained parameters are stored into a memory. - - PacketFramework = userdata(1); - - TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes - InstanceName = PacketFramework.InstanceName; - PacketSize = PacketFramework.PacketSize; - // Sync the simulation to incomming UDP-packets - [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); - - // disassemble packet's structure - [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... - outsizes=[1,1,1,TotalElemetsPerPacket], ... - outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); - - [sim, sourceID] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); - // [sim] = ld_printf(sim, 0, sourceID, "DisAsm(3) (SourceID) = ", 1); - [sim, selectSignal_checkGtZero] = ld_cond_overwrite(sim, ev, sourceID, sourceID, 1); - [sim, selectSignal_notCheckGtZero] = ld_not(sim, ev, sourceID); - [sim, selectSignal_checkMinThreeInt32] = ld_CompareEqInt32(sim, ev, DisAsm(3), -3); - [sim, selectSignal_checkMinThree] = ld_Int32ToFloat(sim, ev, selectSignal_checkMinThreeInt32); - [sim, selectSignal_checked] = ld_cond_overwrite(sim, ev, selectSignal_checkGtZero, selectSignal_checkMinThree, 2); - [sim, selectSignal_notCheckMinThree] = ld_not(sim, ev, selectSignal_checkMinThree); - [sim, selectSignal_undefined] = ld_and(sim, ev, list(selectSignal_notCheckGtZero, selectSignal_notCheckMinThree)); - [sim, selectSignal_checkedSecure] = ld_cond_overwrite(sim, ev, selectSignal_checked, selectSignal_undefined, 3); - [sim, selectSignal_checkedSecureInt32] = ld_roundInt32(sim, ev, selectSignal_checkedSecure); - - // set-up the states for each PaPi Command represented by nested simulations - [sim, outlist, userdataSelectCasePaPiCmd] = ld_CaseSwitchNest(sim, ev, ... - inlist=DisAsm, .. - insizes=[1,1,1,TotalElemetsPerPacket], outsizes=[1], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ], outtypes=[ORTD.DATATYPE_FLOAT], ... - CaseSwitch_fn=SelectCasePaPiCmd, SimnestName="SwitchSelectPaPiCmd", DirectFeedthrough=%t, SelectSignal=selectSignal_checkedSecureInt32, list("Param", "GiveConfig", "Undefined"), list(PacketFramework) ); - - PacketFramework = userdataSelectCasePaPiCmd(1); - - // output of schematic - outlist = list(); - userdata(1) = PacketFramework; - endfunction - - - - // start the node.js service from the subfolder webinterface - //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); - - TotalMemorySize = sum(PacketFramework.ParameterMemory.Sizes); - InstanceName = PacketFramework.InstanceName; - // // Open an UDP-Port in server mode - // [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... - // hostname=PacketFramework.Configuration.LocalSocketHost, ... - // UDPPort=PacketFramework.Configuration.LocalSocketPort); - - // initialise a global memory for storing the input data for the computation - [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... - datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... - initial_data=[zeros(TotalMemorySize,1)], ... - visibility='global', useMutex=1); - - // Create thread for the receiver - ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; - [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step - [sim, outlist, computation_finished, userdata] = ld_async_simulation(sim, 0, ... - inlist=list(), ... - insizes=[], outsizes=[], ... - intypes=[], outtypes=[], ... - nested_fn = UDPReceiverThread, ... - TriggerSignal=startcalc, name=InstanceName+"Thread1", ... - ThreadPrioStruct, userdata=list(PacketFramework) ); - - PacketFramework = userdata(1); - - endfunction - - - // calc memory - MemoryOfs = []; - Sizes = []; - // go through all parameters and create memories for all - for i=1:length(PacketFramework.Parameters) - P = PacketFramework.Parameters(i); - - Sizes = [Sizes; P.NValues]; - MemoryOfs = [MemoryOfs; P.MemoryOfs]; - end - - PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; - PacketFramework.ParameterMemory.Sizes = Sizes; - - // udp - [sim] = ld_PF_InitUDP(sim, PacketFramework); - - // Send to group update notifications for each group (currently only one possible) - [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); - - - [sim, initSelect] = ld_initimpuls(sim, ev); - [sim, selectNewConfig] = ld_add_ofs(sim, ev, initSelect, 1); - //[sim] = ld_printf(sim, ev, selectNewConfig, ORTD.termcode.red + "Select New Config Signal" + ORTD.termcode.reset, 1); - [sim, selectNewConfigInt32] = ld_roundInt32(sim, ev, selectNewConfig); - // set-up the states to send a Signal to PaPi that a new config is available only in the first time step represented by nested simulations - [sim, outlist, userdata] = ld_CaseSwitchNest(sim, ev, ... - inlist=list(), .. - insizes=[], outsizes=[1], ... - intypes=[], outtypes=[ORTD.DATATYPE_FLOAT], ... - CaseSwitch_fn=SelectCaseSendNewConfigAvailable, SimnestName="SelectCaseSendNewConfigAvailable", DirectFeedthrough=%t, SelectSignal=selectNewConfigInt32, list("Finished", "Send"), list(PacketFramework) ); - - PacketFramework = userdata(1); - -endfunction - - -function str=ld_PF_Export_str(PacketFramework) - // Added possibility to add GUI-configurations on 5.3.15 - - function jsonstr = struct2json(a) - // - // Rev 1 as of 4.3.15: Initial version - // Rev 2 as of 4.3.15: added arrays of strings that are defined by e.g. list('str1', 'str2') - // - // - // Example usage: - // - // clear a; - // a.Field1 = 2; - // a.Field2 = "jkh"; - // a.Field3 = 1.2; - // a.F3.name = "Joe"; - // a.F3.age = 32; - // //a.F3.data = [1,2]; - // jsonstr = struct2json(a); - // disp(jsonstr); - // - // Warning: For strings make sure you escape the special characters that are used by the JSON-format! - // - - function valstr=val2str(val) - select typeof(val) - - case "string" - if length(length(val)) == 1 then - valstr = """" + string(val) + """"; - end - - case "list" // convert to array of strings - valstr = '['; - - N = length(val); - for i=1:(N-1) - valstr = valstr + """" + string( val(i) ) + """" + ','; - end - valstr = valstr + """" + string( val(N) ) + """" + ']'; - - - case "constant" - if length(val) == 1 then - valstr = string(val); - else - valstr = """" + "** Matrix not supported **" + """"; - end - - case "st" - valstr = struct2json(val); - - else - valstr = """" + "**Datatype " + typeof(val) + " not supported **" + """"; - end - endfunction - - if isstruct(a) then - F = fieldnames(a); - str = "{ "; - N = length(length(F)); - - for i = 1:(N-1) - f = F(i); - val = eval('a.'+f); - valstr = val2str(val); - str = str + """" + f + """" + ' : ' + valstr + ' , '; - end - - f = F(N); - val = eval('a.'+f); - valstr = val2str(val); - str = str + """" + f + """" + ' : ' + valstr + ' } '; - - else - error("Not a structure") - end - - jsonstr = str; - endfunction - - - // check if there is a GUI to be set-up in Papi - if isfield(PacketFramework, 'PaPIConfig') then - PaPIConfigstr = struct2json(PacketFramework.PaPIConfig) - else - PaPIConfigstr = '{}'; - end - - - - str=' {""SourcesConfig"" : {'+char(10); - - for i=1:length(PacketFramework.Sources) - - - SourceID = PacketFramework.Sources(i).SourceID; - SourceName = PacketFramework.Sources(i).SourceName; - disp(SourceID ); - disp( SourceName ); - - - line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"" } ", ... - string(PacketFramework.Sources(i).SourceID), ... - string(PacketFramework.Sources(i).SourceName), ... - string(PacketFramework.Sources(i).NValues_send), ... - string(PacketFramework.Sources(i).datatype) ); - - - if i==length(PacketFramework.Sources) - // finalise the last entry without "," - printf('%s' , line); - str=str+line + char(10); - else - printf('%s,' , line); - str=str+line+',' + char(10); - end - - - end - - - - str=str+'} , ' + char(10) + ' ""ParametersConfig"" : {' + char(10); - - // go through all parameters and create memories for all - for i=1:length(PacketFramework.Parameters) - - printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); - - line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } ", ... - string(PacketFramework.Parameters(i).ParameterID), ... - string(PacketFramework.Parameters(i).ParameterName), ... - string(PacketFramework.Parameters(i).NValues), ... - string(PacketFramework.Parameters(i).datatype) ); - - - if i==length(PacketFramework.Parameters) - // finalise the last entry without "," - printf('%s' , line); - str=str+line + char(10); - else - printf('%s,' , line); - str=str+line+',' + char(10); - end - - - end - str=str+'}, '+char(10)+ """" + 'PaPIConfig' + """" + ' : ' + PaPIConfigstr + char(10) + '}'; // - - // print the configuration to be send to Papi - disp(str); - -endfunction - -function ld_PF_Export_js(PacketFramework, fname) // PARSEDOCU_BLOCK - // - // Export configuration of the defined protocoll (Sources, Parameters) - // into JSON-format. This is to be used by software that shall communicate - // to the real-time system. - // - // fname - The file name - // - // - str=ld_PF_Export_str(PacketFramework); - - fd = mopen(fname,'wt'); - mfprintf(fd,'%s', str); - mclose(fd); - -endfunction - -// -// Added 27.3.14 -// - -function [sim, PacketFramework, SourceID]=ld_SendPacketReserve(sim, PacketFramework, NValues_send, datatype, SourceName) - [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); -endfunction - -function [sim, PacketFramework]=ld_SendPacket2(sim, PacketFramework, Signal, SourceName) - // find Sourcename - index = -1; - for i=1:length(PacketFramework.Sources) - S = PacketFramework.Sources(i); - if S.SourceName == SourceName - index = i; - printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); - break; - end - end - - if index == -1 - printf("SourceName = %s\n", SourceName); - error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); - end - - [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); -endfunction - - - -function [sim, PacketFramework, ParameterID]=ld_PF_ParameterReserve(sim, PacketFramework, NValues, datatype, ParameterName) - [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); -endfunction - - -function [sim, PacketFramework, Parameter]=ld_PF_Parameter2(sim, PacketFramework, ParameterName) - // find Sourcename - index = -1; - for i=1:length(PacketFramework.Parameters) - P = PacketFramework.Parameters(i); - if P.ParameterName == ParameterName - index = i; - printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); - break; - end - end - - if index == -1 - printf("ParameterName = %s\n", ParameterName); - error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); - end - - // read data from global memory - [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 - [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... - P.datatype, P.NValues); -endfunction - - - - From 7d9f43e9645f5157840ae0543858f729ee593f8f Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Thu, 19 Mar 2015 19:33:21 +0100 Subject: [PATCH 182/260] added new ortd example --- ...AutoConfigDemo_ReplaceableSimulation.ipar} | 6538 +++--- .../AutoConfigDemo_ReplaceableSimulation.rpar | 36 + .../SwitchingPaPiConfig.ipar | 18251 ++++++++++++++++ .../SwitchingPaPiConfig.rpar | 27 + .../SwitchingPaPiConfig.sce | 407 + .../run_switchingPaPiConfig.sh | 4 + .../ORTD/DataSourceExample_Groups/PF.sci | 595 - .../ProtocollConfig.json | 10 - .../ORTD/DataSourceExample_Groups/README | 54 - .../ORTD/DataSourceExample_Groups/UDPio.rpar | 70 - .../ORTD/DataSourceExample_Groups/UDPio.sce | 261 - .../DataSourceExample_Groups/run_UDPio.sh | 4 - .../webinterface/PacketFramework.sce | 487 - .../webinterface/html/mainAuto.html | 155 - .../webinterface/html/main_Plot.html | 365 - .../webinterface/install_nodejs.sh | 11 - .../webinterface/webappUDP.js | 304 - papi/last_active_papi.xml | 716 +- 18 files changed, 22083 insertions(+), 6212 deletions(-) rename data_sources/ORTD/{DataSourceExample_Groups/UDPio.ipar => DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar} (73%) create mode 100644 data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar create mode 100644 data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.ipar create mode 100644 data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.rpar create mode 100644 data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.sce create mode 100755 data_sources/ORTD/DataSourceChangingAutoConfigExample/run_switchingPaPiConfig.sh delete mode 100644 data_sources/ORTD/DataSourceExample_Groups/PF.sci delete mode 100644 data_sources/ORTD/DataSourceExample_Groups/ProtocollConfig.json delete mode 100644 data_sources/ORTD/DataSourceExample_Groups/README delete mode 100644 data_sources/ORTD/DataSourceExample_Groups/UDPio.rpar delete mode 100644 data_sources/ORTD/DataSourceExample_Groups/UDPio.sce delete mode 100755 data_sources/ORTD/DataSourceExample_Groups/run_UDPio.sh delete mode 100644 data_sources/ORTD/DataSourceExample_Groups/webinterface/PacketFramework.sce delete mode 100644 data_sources/ORTD/DataSourceExample_Groups/webinterface/html/mainAuto.html delete mode 100644 data_sources/ORTD/DataSourceExample_Groups/webinterface/html/main_Plot.html delete mode 100644 data_sources/ORTD/DataSourceExample_Groups/webinterface/install_nodejs.sh delete mode 100644 data_sources/ORTD/DataSourceExample_Groups/webinterface/webappUDP.js diff --git a/data_sources/ORTD/DataSourceExample_Groups/UDPio.ipar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar similarity index 73% rename from data_sources/ORTD/DataSourceExample_Groups/UDPio.ipar rename to data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar index 99921c10..abbf4fd8 100644 --- a/data_sources/ORTD/DataSourceExample_Groups/UDPio.ipar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar @@ -1,45 +1,5 @@ 1 - 1 - 901 - 10 - 0 - 0 - 5255 - 70 - 1 - 3 - 201 - 100 - 0 - 0 - 6 - 0 - 203 - 100 - 6 - 0 - 5217 - 70 - 100 - 101 - 5223 - 70 - 12 - 0 - 60023 - 201 - 0 - 0 - 1 - 0 - 15011 - 203 - 5211 - 70 - 1 - 0 - 1 - 8 + 7 10 4 0 @@ -50,659 +10,459 @@ 4 2 0 - 2 + 3 0 12 4 - 4 + 5 0 2 0 13 4 - 6 - 0 - 2 - 0 - 14 - 4 - 8 + 7 0 - 2 + 3 0 - 15 + 21 4 10 0 - 2 + 17 0 - 20 + 22 4 - 12 + 27 0 - 5148 + 27 0 - 21 - 1 - 5160 + 100 + 10 + 54 0 - 1 - 70 + 5245 + 36 1 1 + 2 1 1 1 257 - 1 + 2 257 - 1 - 1 - 1 - 1 - 5147 - 1 - 7 - 10 - 4 - 0 - 0 - 1 - 0 - 11 - 4 - 1 - 0 - 1 - 0 + 257 + 16 + -1 + -1 + -1 + 1426768201 + -1 + -1 + 2015 + 3 12 - 4 - 2 - 0 - 1 - 0 + 78 + 5 + 19 13 - 4 - 3 - 0 + 30 1 - 0 - 21 - 4 - 4 - 0 - 19 - 0 - 22 - 4 - 23 - 0 - 4 - 0 - 900 - 10 - 27 - 0 - 5076 - 70 - 0 - 0 - 0 - 0 - 18 - 77 - 97 - 105 - 110 + 925 + 26 + 79 82 + 84 + 68 + 45 + 114 101 - 97 + 112 108 - 116 - 105 + 97 + 99 + 101 109 101 - 84 + 110 + 116 + 45 + 115 + 99 104 - 114 101 + 109 97 - 100 - 3 - 2 - 0 - -1 + 116 + 105 + 99 1 - 67 - 201 + 33 + 202 100 0 0 6 1 - 203 + 204 100 6 1 - 72 + 149 0 205 100 - 78 + 155 1 6 1 207 100 - 84 + 161 2 - 43 - 0 + 6 + 1 209 100 - 127 - 2 + 167 + 3 6 1 211 100 - 133 - 3 - 43 + 173 + 4 + 49 0 213 100 - 176 - 3 + 222 + 4 6 1 215 100 - 182 - 4 - 43 + 228 + 5 + 49 0 217 100 - 225 - 4 - 25 - 0 + 277 + 5 + 49 + 2 218 100 - 250 - 4 - 13 - 0 - 219 + 326 + 7 + 6 + 1 + 220 100 - 263 - 4 - 29 - 0 + 332 + 8 + 3336 + 13 222 100 - 292 - 4 + 3668 + 21 6 - 2 + 1 224 100 - 298 - 6 - 6 - 2 + 3674 + 22 + 8 + 0 226 100 - 304 - 8 - 8 - 3 + 3682 + 22 + 6 + 0 228 100 - 312 - 11 + 3688 + 22 6 1 230 100 - 318 - 12 - 8 - 3 + 3694 + 23 + 6 + 0 232 100 - 326 - 15 + 3700 + 23 6 1 234 100 - 332 - 16 + 3706 + 24 6 - 1 + 0 236 100 - 338 - 17 - 8 - 0 + 3712 + 24 + 6 + 1 238 100 - 346 - 17 + 3718 + 25 6 0 240 100 - 352 - 17 - 6 - 1 - 242 - 100 - 358 - 18 - 6 - 0 - 244 - 100 - 364 - 18 - 6 - 1 - 246 - 100 - 370 - 19 - 6 - 0 - 248 - 100 - 376 - 19 - 76 - 0 - 250 - 100 - 452 - 19 - 9 - 0 - 252 - 100 - 461 - 19 - 155 - 0 - 253 - 100 - 616 - 19 - 6 - 1 - 255 - 100 - 622 - 20 - 8 - 0 - 257 - 100 - 630 - 20 - 6 - 0 - 259 - 100 - 636 - 20 - 6 - 1 - 261 - 100 - 642 - 21 - 6 - 0 - 263 - 100 - 648 - 21 - 6 - 1 - 265 - 100 - 654 - 22 - 6 - 0 - 267 - 100 - 660 - 22 + 3724 + 25 76 0 - 269 + 242 100 - 736 - 22 + 3800 + 25 9 0 - 271 - 100 - 745 - 22 - 155 - 0 - 272 - 100 - 900 - 22 - 6 - 1 - 274 - 100 - 906 - 23 - 8 - 0 - 276 - 100 - 914 - 23 - 6 - 0 - 278 - 100 - 920 - 23 - 6 - 1 - 280 + 244 100 - 926 - 24 - 6 + 3809 + 25 + 161 0 - 282 - 100 - 932 - 24 - 6 - 1 - 284 + 245 100 - 938 + 3970 25 6 0 - 286 + 247 100 - 944 + 3976 25 6 1 - 288 - 100 - 950 - 26 - 6 - 0 - 290 - 100 - 956 - 26 - 76 - 0 - 292 - 100 - 1032 - 26 - 9 - 0 - 294 - 100 - 1041 - 26 - 155 - 0 - 295 + 248 100 - 1196 + 3982 26 - 1437 - 26 - 298 - 100 - 2633 - 52 - 143 - 0 - 299 - 100 - 2776 - 52 - 43 - 13 - 300 - 100 - 2819 - 65 - 6 - 1 - 302 - 100 - 2825 - 66 - 1051 - 0 - 304 - 100 - 3876 - 66 6 - 1 - 306 - 100 - 3882 - 67 - 8 - 0 - 308 + 2 + 250 100 - 3890 - 67 + 3988 + 28 6 0 - 310 - 100 - 3896 - 67 - 6 - 1 - 312 + 252 100 - 3902 - 68 + 3994 + 28 + 688 6 - 0 - 314 + 254 100 - 3908 - 68 + 4682 + 34 6 - 1 - 316 + 2 + 256 100 - 3914 - 69 - 6 + 4688 + 36 + 37 0 - 318 - 100 - 3920 - 69 - 6 - 1 - 320 + 257 100 - 3926 - 70 + 4725 + 36 6 0 - 322 - 100 - 3932 - 70 - 76 - 0 - 324 - 100 - 4008 - 70 - 9 - 0 - 326 + 259 100 - 4017 - 70 - 155 + 4731 + 36 + 78 0 100 101 - 4172 - 70 - 500 + 4809 + 36 + 236 0 40 - 201 + 202 0 1 1 0 - 15102 - 203 - 66 + 39001 + 204 + 143 0 1 0 1 - 8 + 10 10 4 0 0 - 2 + 1 0 11 4 - 2 + 1 0 - 2 + 1 0 12 4 - 4 - 0 2 0 + 1 + 0 13 4 - 6 + 3 0 - 2 + 1 0 14 4 - 8 + 4 0 2 0 15 4 - 10 + 6 0 2 0 20 4 - 12 + 8 0 - 3 + 27 0 21 1 - 15 + 35 0 1 0 - 1 - 1 - 1 - 1 - 1 - 257 - 1 - 257 - 1 - 1 - 1 - 1 - 2 - 1 + 30 + 4 + 36 0 + 43 0 - 40 - 205 + 31 + 4 + 79 0 - 1 - 1 + 2 0 - 15007 - 207 - 37 0 - 1 0 0 - 257 + 0 + 1 1 + 1 + 2 + 26 + 1 + 2 + 10 + 4 0 0 + 2 0 + 11 + 4 + 2 0 + 10 0 + 1 + 20001 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 0 - 0 - 26 - 82 - 101 - 109 - 111 + 42 + 83 + 119 + 105 116 - 101 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 67 111 110 - 116 - 114 + 102 + 105 + 103 + 97 + 83 111 - 108 - 77 + 99 + 107 101 - 109 - 111 - 114 - 121 + 116 46 - 109 - 101 - 109 + 85 + 68 + 80 + 83 111 - 114 - 121 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 1 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 40 + 207 + 0 + 1 + 1 + 0 40 209 0 @@ -711,13 +471,13 @@ 0 15007 211 - 37 + 43 0 1 0 0 257 - 10 + 1 0 0 0 @@ -725,20 +485,26 @@ 0 0 0 - 26 - 82 - 101 - 109 - 111 + 32 + 83 + 119 + 105 116 - 101 - 67 - 111 + 99 + 104 + 105 110 + 103 + 65 + 117 116 - 114 111 - 108 + 67 + 111 + 110 + 102 + 105 + 103 77 101 109 @@ -760,13 +526,13 @@ 0 15007 215 - 37 + 43 0 1 0 0 257 - 2 + 1 0 0 0 @@ -774,20 +540,26 @@ 0 0 0 - 26 - 82 - 101 - 109 - 111 + 32 + 83 + 119 + 105 116 - 101 - 67 - 111 + 99 + 104 + 105 110 + 103 + 65 + 117 116 - 114 111 - 108 + 67 + 111 + 110 + 102 + 105 + 103 77 101 109 @@ -801,245 +573,310 @@ 111 114 121 - 170 + 15005 217 - 19 - 0 + 43 + 2 1 0 - 1 - 17 - 79 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 105 - 110 - 112 - 117 - 116 - 32 - 170 - 218 - 7 0 + 257 + 2 1 0 - 2 - 5 - 84 - 101 - 115 - 116 - 32 - 170 - 219 - 23 0 - 1 0 - 10 - 21 - 65 + 0 + 0 + 0 32 - 118 - 101 + 83 + 119 + 105 + 116 99 + 104 + 105 + 110 + 103 + 65 + 117 116 111 - 114 + 67 + 111 + 110 + 102 105 - 97 - 108 - 32 - 112 - 97 + 103 + 77 + 101 + 109 + 111 114 - 97 + 121 + 46 109 101 - 116 - 101 + 109 + 111 114 - 12 - 222 + 121 + 40 + 218 0 - 2 1 - 0 - 12 - 224 - 0 - 2 1 0 - 30 - 226 - 2 - 3 + 15011 + 220 + 3330 + 13 1 0 - 0 1 - 20 - 228 + 8 + 10 + 4 0 - 1 - 1 0 - 30 - 230 2 - 3 - 1 0 + 11 + 4 + 2 0 - 1 - 20 - 232 + 2 0 - 1 - 1 + 12 + 4 + 4 0 - 40 - 234 + 2 0 - 1 - 1 + 13 + 4 + 6 0 - 60005 - 236 2 0 - 1 + 14 + 4 + 8 0 + 2 0 - 100000 - 60031 - 238 + 15 + 4 + 10 0 + 2 0 - 1 + 20 + 4 + 12 0 - 40 - 240 + 3267 0 + 21 1 - 1 - 0 - 60031 - 242 - 0 + 3279 0 1 - 0 - 40 - 244 - 0 + 13 1 1 - 0 - 60031 - 246 - 0 - 0 1 - 0 - 39011 - 248 - 70 - 0 1 - 0 1 - 8 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 3266 + 1 + 7 10 4 0 0 - 5 + 1 0 11 4 - 5 + 1 0 - 2 + 1 0 12 4 - 7 + 2 0 - 5 + 1 0 13 4 - 12 + 3 0 - 2 + 1 0 - 14 + 21 + 4 4 - 14 0 - 2 + 27 0 - 15 + 22 4 - 16 - 0 - 2 + 31 0 - 20 4 - 18 0 - 1 + 900 + 10 + 35 0 - 21 - 1 - 19 + 3187 + 13 0 - 1 0 - 4 - 1 - 1 - 1 - 1 - 1 - 20 - 4 - 2 - 2 + 0 + 0 + 26 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 84 + 104 + 114 + 101 + 97 + 100 + 49 + 3 2 - 257 + 0 + -1 1 + 16 + 201 + 100 + 0 + 0 + 137 + 0 + 204 + 100 + 137 + 0 + 76 + 0 + 209 + 100 + 213 + 0 + 6 + 0 + 211 + 100 + 219 + 0 6 1 + 212 + 100 + 225 1 - 1 + 6 2 + 214 + 100 + 231 + 3 + 6 + 1 + 216 + 100 + 237 + 4 + 6 0 + 218 + 100 + 243 + 4 + 7 0 - 60035 + 220 + 100 250 - 3 + 4 + 6 0 + 222 + 100 + 256 + 4 + 6 1 + 224 + 100 + 262 + 5 + 6 + 0 + 226 + 100 + 268 + 5 + 6 0 + 228 + 100 + 274 + 5 + 6 1 + 230 + 100 + 280 + 6 + 6 0 - 20 - 39004 - 252 - 149 + 232 + 100 + 286 + 6 + 2623 + 7 + 100 + 101 + 2909 + 13 + 180 + 0 + 39003 + 201 + 131 0 1 0 @@ -1049,25 +886,25 @@ 4 0 0 - 3 + 1 0 11 4 - 3 - 0 1 0 + 3 + 0 12 4 4 0 - 3 + 1 0 13 4 - 7 + 5 0 - 1 + 3 0 14 4 @@ -1085,35 +922,35 @@ 4 12 0 - 43 + 19 0 21 1 - 55 + 31 0 1 0 30 4 - 56 + 32 0 - 37 + 43 0 - 2 - 20 - 1 0 2 + 1396 6 - 2 0 + 2 + 6 + 6 1 1 1 - 1 - 42 - 1 - 4 + 1 + 18 + 1 + 2 10 4 0 @@ -1126,49 +963,31 @@ 0 2 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 - 10 - 0 1 - 20 + 1396 1 6 - 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 0 - 36 - 82 - 101 - 109 - 111 + 42 + 83 + 119 + 105 116 - 101 - 67 - 111 + 99 + 104 + 105 110 + 103 + 65 + 117 116 - 114 111 - 108 + 67 + 111 + 110 + 102 + 105 + 103 97 83 111 @@ -1192,52 +1011,8 @@ 79 98 106 - 40 - 253 - 0 - 1 - 1 - 0 - 60005 - 255 - 2 - 0 - 1 - 0 - 0 - 100000 - 60031 - 257 - 0 - 0 - 1 - 0 - 40 - 259 - 0 - 1 - 1 - 0 - 60031 - 261 - 0 - 0 - 1 - 0 - 40 - 263 - 0 - 1 - 1 - 0 - 60031 - 265 - 0 - 0 - 1 - 0 - 39011 - 267 + 39012 + 204 70 0 1 @@ -1248,25 +1023,25 @@ 4 0 0 - 5 + 2 0 11 4 - 5 - 0 2 0 + 5 + 0 12 4 7 0 - 5 + 2 0 13 4 - 12 + 9 0 - 2 + 5 0 14 4 @@ -1292,249 +1067,358 @@ 0 1 0 - 4 1 + 1396 + 4 1 1 1 + 173 1 - 20 + 6 4 2 2 2 257 1 - 6 1 1 + 2 + 0 + 0 + 60032 + 209 + 0 + 0 + 1 + 0 + 40 + 211 + 0 + 1 1 + 0 + 12 + 212 + 0 2 + 1 0 + 60020 + 214 + 0 + 1 + 1 + 0 + 60014 + 216 0 - 60035 - 269 - 3 0 1 0 + 60043 + 218 1 0 - 20 - 39004 - 271 - 149 + 1 + 0 + -3 + 60032 + 220 + 0 0 1 0 + 60020 + 222 + 0 1 - 9 + 1 + 0 + 60014 + 224 + 0 + 0 + 1 + 0 + 60022 + 226 + 0 + 0 + 1 + 0 + 60020 + 228 + 0 + 1 + 1 + 0 + 60034 + 230 + 0 + 0 + 1 + 0 + 15013 + 232 + 2617 + 7 + 1 + 0 + 1 + 8 10 4 0 0 - 3 + 6 0 11 4 - 3 + 6 0 - 1 + 2 0 12 4 - 4 + 8 0 - 3 + 6 0 13 4 - 7 + 14 0 - 1 + 2 0 14 4 - 8 + 16 0 2 0 15 4 - 10 + 18 0 2 0 20 4 - 12 + 20 0 - 43 + 2546 0 21 1 - 55 + 2566 0 1 - 0 - 30 - 4 - 56 - 0 - 37 - 0 - 2 - 20 + 7 + 5 1 - 0 + 1 + 1 + 173 + 1 + 1 + 1 + 5 2 - 6 2 - 0 + 2 + 257 + 2 + 1 + 257 1 1 1 1 - 42 + 2545 + 1 + 9 1 4 + 0 + 0 + 3 + 0 10 4 + 3 0 - 0 - 2 + 5 0 11 4 - 2 + 8 0 2 0 12 4 - 4 + 10 0 - 2 + 5 0 13 4 - 6 + 15 0 - 10 + 2 0 + 21 + 4 + 17 + 0 + 20 + 0 + 900 + 10 + 37 + 0 + 401 + 1 + 901 + 10 + 438 1 - 20 - 1 + 1906 + 5 + 902 + 10 + 2344 6 + 145 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 - 0 - 36 - 82 - 101 - 109 - 111 + 2 + 3 + 1 + 4 + 1 + 1 + 1 + 173 + 1 + 1 + 4 + 2 + 2 + 2 + 257 + 1 + 257 + 19 + 83 + 119 + 105 116 + 99 + 104 + 83 101 - 67 - 111 - 110 - 116 - 114 - 111 108 - 97 - 83 - 111 - 99 - 107 101 - 116 - 46 - 85 - 68 - 80 - 83 - 111 99 - 107 - 101 116 - 95 - 83 - 104 - 79 - 98 - 106 - 40 - 272 - 0 - 1 + 80 + 97 + 80 + 105 + 67 + 109 + 100 1 + 10 + 205 + 100 0 - 60005 - 274 - 2 0 - 1 + 6 0 + 207 + 100 + 6 0 - 100000 - 60031 - 276 + 6 0 + 209 + 100 + 12 0 - 1 + 6 0 - 40 - 278 + 211 + 100 + 18 0 + 6 1 + 213 + 100 + 24 1 + 81 0 - 60031 - 280 - 0 + 215 + 100 + 105 + 1 + 81 0 + 217 + 100 + 186 1 + 6 0 - 40 - 282 + 219 + 100 + 192 + 1 + 6 0 + 221 + 100 + 198 1 + 49 + 0 + 100 + 101 + 247 1 + 92 0 - 60031 - 284 + 60032 + 205 0 0 1 0 - 40 - 286 + 60032 + 207 + 0 0 - 1 1 0 - 60031 - 288 + 60032 + 209 0 0 1 0 - 39011 - 290 - 70 + 40 + 211 + 0 + 1 + 1 + 0 + 60304 + 213 + 75 0 1 0 @@ -1544,110 +1428,106 @@ 4 0 0 - 5 + 2 0 11 4 - 5 + 2 0 2 0 12 4 - 7 + 4 0 - 5 + 2 0 13 4 - 12 + 6 0 2 0 14 4 - 14 + 8 0 2 0 15 4 - 16 + 10 0 2 0 20 4 - 18 + 12 0 - 1 + 12 0 21 1 - 19 + 24 0 1 0 - 4 1 1 1 1 1 - 16 - 4 - 2 - 2 - 2 2 1 - 6 + 2 1 1 1 2 + 11 + 1 + 1 + 10 + 4 0 0 - 60035 - 292 3 0 + 2 1 + 2 0 - 1 - 0 - 16 - 39004 - 294 - 149 + 60304 + 215 + 75 0 1 0 1 - 9 + 8 10 4 0 0 - 3 + 2 0 11 4 - 3 + 2 0 - 1 + 2 0 12 4 4 0 - 3 + 2 0 13 4 - 7 + 6 0 - 1 + 2 0 14 4 @@ -1665,425 +1545,440 @@ 4 12 0 - 43 + 12 0 21 1 - 55 + 24 0 1 0 - 30 - 4 - 56 - 0 - 37 - 0 - 2 - 16 1 - 0 + 1 + 1 + 1 + 1 2 - 6 + 1 2 - 0 1 1 1 + 2 + 11 1 - 42 1 - 4 10 4 0 0 - 2 + 3 0 - 11 - 4 2 + 1 + 1 0 - 2 + 60032 + 217 0 - 12 - 4 - 4 0 - 2 + 1 0 - 13 - 4 - 6 + 60032 + 219 0 - 10 0 1 - 16 - 1 - 6 + 0 + 15008 + 221 + 43 + 0 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 0 - 36 - 82 - 101 - 109 - 111 + 0 + 257 + 173 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 32 + 83 + 119 + 105 116 - 101 - 67 - 111 + 99 + 104 + 105 110 + 103 + 65 + 117 116 - 114 111 - 108 - 97 - 83 + 67 111 - 99 - 107 + 110 + 102 + 105 + 103 + 77 101 - 116 - 46 - 85 - 68 - 80 - 83 + 109 111 - 99 - 107 + 114 + 121 + 46 + 109 101 - 116 - 95 - 83 - 104 - 79 - 98 - 106 - 15002 - 295 - 1431 - 26 + 109 + 111 + 114 + 121 + 0 + 11 + 8 + 4 1 + 0 + 0 + 0 + 0 + 205 + 205 0 1 - 9 - 10 - 4 0 0 - 4 + 1 0 - 11 - 4 - 4 + 207 + 207 0 - 3 + 1 0 - 12 - 4 - 7 0 - 4 + 2 0 - 13 - 4 - 11 + 209 + 209 0 - 3 + 1 0 - 20 - 4 - 14 0 - 4 + 2 + 0 + 213 + 213 0 - 21 1 - 18 + 0 + 0 + 2 + 0 + 215 + 215 + 0 + 0 + 213 + 213 + 0 + 0 + 217 + 217 + 0 + 0 + 215 + 215 + 0 + 0 + 219 + 219 0 1 - 4 - 900 - 10 - 19 - 4 - 1056 - 12 - 901 - 10 - 1075 - 16 - 150 - 5 - 902 - 10 - 1225 - 21 - 150 - 5 + 0 + 0 3 + 0 + 221 + 221 + 0 + 0 + 213 + 213 + 0 + 0 + 221 + 221 1 - 1 - 4 + 0 + 215 + 215 + 0 + 0 + 221 + 221 2 + 0 + 211 + 211 + 0 1 - 4 - 3 - 257 - 257 - 257 - 2 - 257 - 257 - 3 - 3 - 4 - 3 - 4 + 0 + 0 + 0 1 - 29 - 204 + 22 + 205 100 0 0 - 8 + 6 + 0 + 207 + 100 + 6 + 0 + 6 0 209 100 - 8 + 12 0 6 - 1 + 0 211 100 - 14 - 1 - 8 + 18 0 + 6 + 1 213 100 - 22 + 24 1 - 6 + 39 0 - 215 + 214 100 - 28 + 63 1 6 1 - 217 + 216 100 - 34 + 69 2 6 0 - 219 + 218 100 - 40 + 75 2 6 1 - 221 + 220 100 - 46 + 81 3 6 0 - 223 + 222 100 - 52 - 3 - 76 - 0 - 225 - 100 - 128 - 3 - 9 - 0 - 227 - 100 - 137 - 3 - 155 - 0 - 228 - 100 - 292 + 87 3 6 1 - 230 - 100 - 298 - 4 - 8 - 0 - 232 + 224 100 - 306 + 93 4 6 0 - 234 + 226 100 - 312 + 99 4 6 1 - 236 + 228 100 - 318 + 105 5 6 0 - 238 + 230 100 - 324 + 111 5 - 6 - 1 - 240 - 100 - 330 - 6 - 6 + 1151 0 - 242 - 100 - 336 - 6 - 6 - 1 - 244 + 232 100 - 342 - 7 - 6 + 1262 + 5 + 78 0 - 246 + 234 100 - 348 - 7 - 76 + 1340 + 5 + 16 0 - 248 + 235 100 - 424 - 7 - 9 + 1356 + 5 + 36 0 - 250 + 236 100 - 433 - 7 - 155 + 1392 + 5 + 21 0 - 251 + 237 100 - 588 - 7 - 12 + 1413 + 5 + 25 0 - 252 - 100 - 600 - 7 - 8 - 2 - 254 + 238 100 - 608 + 1438 + 5 9 - 6 - 1 - 255 - 100 - 614 - 10 - 6 - 2 - 257 + 0 + 240 100 - 620 - 12 - 8 + 1447 + 5 + 161 0 100 101 - 628 - 12 - 252 + 1608 + 5 + 164 + 0 + 60032 + 205 0 - 60002 - 204 - 2 0 1 0 - 4 + 60032 + 207 0 - 40 - 209 0 1 + 0 + 60032 + 209 + 0 + 0 1 0 - 60005 + 40 211 - 2 0 1 + 1 + 0 + 170 + 213 + 33 0 + 1 + 0 + 1 + 31 + 71 + 105 + 118 + 101 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 32 + 40 + 83 + 111 + 117 + 114 + 99 + 101 + 73 + 68 + 41 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 61 + 32 + 40 + 214 + 0 + 1 + 1 0 - 100000 60031 - 213 + 216 0 0 1 0 40 - 215 + 218 0 1 1 0 60031 - 217 + 220 0 0 1 0 40 - 219 + 222 0 1 1 0 60031 - 221 + 224 0 0 1 0 - 39011 - 223 - 70 + 40 + 226 + 0 + 1 + 1 + 0 + 60031 + 228 + 0 + 0 + 1 + 0 + 60305 + 230 + 1145 0 1 0 @@ -2093,1201 +1988,1407 @@ 4 0 0 - 5 + 1 0 11 4 - 5 + 1 0 2 0 12 4 - 7 + 3 0 - 5 + 1 0 13 4 - 12 + 4 0 2 0 14 4 - 14 + 6 0 2 0 15 4 - 16 + 8 0 2 0 20 4 - 18 + 10 0 - 1 + 1084 0 21 1 - 19 + 1094 0 1 0 - 4 - 1 - 1 - 1 - 1 + 0 1 - 20 - 4 - 2 - 2 - 2 - 257 + 1066 + 0 1 6 1 1 1 2 - 0 - 0 - 60035 - 225 - 3 - 0 - 1 - 0 - 1 - 0 - 20 - 39004 - 227 - 149 - 0 - 1 - 0 + 1083 1 - 9 + 2 10 4 0 0 - 3 - 0 - 11 - 4 - 3 - 0 - 1 - 0 - 12 - 4 - 4 - 0 - 3 - 0 - 13 - 4 - 7 - 0 - 1 - 0 - 14 - 4 - 8 - 0 2 0 - 15 + 11 4 - 10 - 0 2 0 - 20 - 4 - 12 - 0 - 43 + 1067 0 - 21 1 - 55 - 0 - 1 - 0 - 30 - 4 - 56 - 0 - 37 - 0 - 2 - 20 - 1 - 0 - 2 - 6 - 2 - 0 - 1 - 1 - 1 - 1 - 42 - 1 - 4 + 1066 + 1066 + 32 + 123 + 34 + 83 + 111 + 117 + 114 + 99 + 101 + 115 + 67 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 + 125 + 32 + 44 + 32 10 - 0 - 1 - 20 - 1 - 6 - 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 - 0 - 36 - 82 - 101 + 32 + 34 + 80 + 97 + 114 + 97 109 - 111 + 101 116 101 + 114 + 115 67 111 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 10 + 32 + 34 + 48 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 80 + 97 + 114 + 97 + 109 + 101 116 + 101 114 - 111 - 108 + 78 97 - 83 - 111 + 109 + 101 + 34 + 32 + 58 + 32 + 34 + 79 + 115 99 - 107 + 105 + 34 + 32 + 44 + 32 + 34 + 78 + 86 + 97 + 108 + 117 101 + 115 + 34 + 32 + 58 + 32 + 34 + 49 + 34 + 44 + 32 + 34 + 100 + 97 116 - 46 - 85 - 68 + 97 + 116 + 121 + 112 + 101 + 34 + 32 + 58 + 32 + 34 + 50 + 53 + 55 + 34 + 32 + 32 + 125 + 32 + 44 + 10 + 32 + 34 + 49 + 34 + 32 + 58 + 32 + 123 + 32 + 34 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 34 83 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 80 + 114 111 - 99 - 107 + 103 + 114 + 101 + 115 + 115 + 34 + 32 + 44 + 32 + 34 + 78 + 86 + 97 + 108 + 117 101 + 115 + 34 + 32 + 58 + 32 + 34 + 49 + 34 + 44 + 32 + 34 + 100 + 97 116 - 95 - 83 - 104 - 79 - 98 - 106 - 40 - 228 - 0 - 1 - 1 - 0 - 60005 - 230 - 2 - 0 - 1 - 0 - 0 - 100000 - 60031 - 232 - 0 - 0 - 1 - 0 - 40 - 234 - 0 - 1 - 1 - 0 - 60031 - 236 - 0 - 0 - 1 - 0 - 40 - 238 - 0 - 1 - 1 - 0 - 60031 - 240 - 0 - 0 - 1 - 0 - 40 - 242 - 0 - 1 - 1 - 0 - 60031 - 244 - 0 - 0 - 1 - 0 - 39011 - 246 - 70 - 0 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 5 - 0 - 11 - 4 - 5 - 0 - 2 - 0 - 12 - 4 - 7 - 0 - 5 - 0 - 13 - 4 - 12 - 0 - 2 - 0 - 14 - 4 - 14 - 0 - 2 - 0 - 15 - 4 - 16 - 0 - 2 - 0 - 20 - 4 - 18 - 0 - 1 - 0 - 21 - 1 - 19 - 0 - 1 - 0 - 4 - 1 - 1 - 1 - 1 - 1 - 16 - 4 - 2 - 2 - 2 - 2 - 1 - 6 - 1 - 1 - 1 - 2 - 0 - 0 - 60035 - 248 - 3 - 0 - 1 - 0 - 1 - 0 - 16 - 39004 - 250 - 149 - 0 - 1 - 0 - 1 - 9 - 10 - 4 - 0 - 0 - 3 - 0 - 11 - 4 - 3 - 0 - 1 - 0 - 12 - 4 - 4 - 0 - 3 - 0 - 13 - 4 - 7 - 0 - 1 - 0 - 14 - 4 - 8 - 0 - 2 - 0 - 15 - 4 - 10 - 0 - 2 - 0 - 20 - 4 - 12 - 0 - 43 - 0 - 21 - 1 + 97 + 116 + 121 + 112 + 101 + 34 + 32 + 58 + 32 + 34 + 50 + 53 55 - 0 - 1 - 0 - 30 - 4 - 56 - 0 - 37 - 0 - 2 - 16 - 1 - 0 - 2 - 6 - 2 - 0 - 1 - 1 - 1 - 1 - 42 - 1 - 4 + 34 + 32 + 32 + 125 + 32 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 + 125 + 44 + 32 10 - 0 - 1 - 16 - 1 - 6 - 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 - 0 - 36 - 82 - 101 - 109 + 34 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 84 111 + 67 + 114 + 101 + 97 116 101 - 67 - 111 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 80 + 108 + 117 + 103 + 105 + 110 + 49 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 105 + 100 + 101 110 116 + 105 + 102 + 105 + 101 114 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 66 + 117 + 116 + 116 + 111 + 110 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 99 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 110 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 79 + 115 + 99 + 105 + 108 108 97 - 83 + 116 111 - 99 - 107 + 114 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 105 + 122 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 101 + 34 + 32 + 58 + 32 + 34 + 40 + 50 + 53 + 48 + 44 + 49 + 48 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 112 + 111 + 115 + 105 116 - 46 - 85 - 68 - 80 - 83 + 105 111 - 99 - 107 + 110 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 101 + 34 + 32 + 58 + 32 + 34 + 40 + 50 + 53 + 48 + 44 + 49 + 48 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 116 - 95 - 83 - 104 - 79 + 97 98 - 106 - 170 - 251 - 6 - 0 - 1 - 0 - 1 - 4 - 45 - 45 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 80 + 97 + 80 + 73 45 + 84 + 97 + 98 + 34 32 - 60019 - 252 - 2 - 2 - 1 - 0 - 2 - 10 - 40 - 254 - 0 - 1 - 1 - 0 - 12 - 255 - 0 - 2 - 1 - 0 - 60003 - 257 - 2 - 0 - 1 - 0 - 4 - 0 - 0 - 31 - 8 - 4 - 1 - 0 - 0 - 2 - 0 - 204 - 204 - 0 - 0 - 209 - 209 - 0 - 0 - 211 - 211 - 0 - 0 - 211 - 211 - 0 - 0 - 213 - 213 - 0 - 0 - 215 - 215 - 0 - 0 - 217 - 217 - 0 - 0 - 219 - 219 - 0 - 0 - 221 - 221 - 0 - 0 - 221 - 221 - 0 - 0 - 223 - 223 - 0 - 0 - 213 - 213 - 0 - 0 - 223 - 223 - 1 - 0 - 217 - 217 - 0 - 0 - 223 - 223 - 2 - 0 - 204 - 204 - 0 - 0 - 223 - 223 - 3 - 0 - 223 - 223 - 0 - 0 - 227 - 227 - 0 - 0 - 225 - 225 - 0 - 0 - 227 - 227 - 1 - 0 - 228 - 228 - 0 - 0 - 230 - 230 - 0 - 0 - 230 - 230 - 0 - 0 - 232 - 232 - 0 - 0 - 234 - 234 - 0 - 0 - 236 - 236 - 0 - 0 - 238 - 238 - 0 - 0 - 240 - 240 - 0 - 0 - 242 - 242 - 0 - 0 - 244 - 244 - 0 - 0 - 244 - 244 - 0 - 0 - 246 - 246 - 0 - 0 - 232 - 232 - 0 - 0 - 246 - 246 - 1 - 0 - 236 - 236 - 0 - 0 - 246 - 246 - 2 - 0 - 240 - 240 - 0 - 0 - 246 - 246 - 3 - 0 - 246 - 246 - 0 - 0 - 250 - 250 - 0 - 0 - 248 - 248 - 0 - 0 - 250 - 250 - 1 - 0 - 204 - 204 - 0 - 0 - 251 - 251 - 0 - 0 - 204 - 204 - 0 - 0 - 255 - 255 - 0 - 0 - 254 - 254 - 0 - 0 - 255 - 255 - 1 - 0 - 255 - 255 - 0 - 0 - 257 - 257 - 0 - 0 - 204 - 204 - 1 - 0 - 257 - 257 - 1 - 0 - 204 - 204 - 2 - 0 - 257 - 257 - 2 - 0 - 204 - 204 - 3 - 0 - 257 - 257 - 3 - 0 - 252 - 252 - 0 - 1 - 0 - 0 - 0 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 71 + 111 + 32 + 116 + 111 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 76 + 101 + 97 + 118 + 105 + 110 + 103 + 32 + 116 + 111 + 34 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 80 + 108 + 117 + 103 + 105 + 110 + 50 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 105 + 100 + 101 + 110 + 116 + 105 + 102 + 105 + 101 + 114 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 66 + 117 + 116 + 116 + 111 + 110 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 99 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 110 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 83 + 108 + 105 + 100 + 101 + 114 + 32 + 76 + 67 + 68 + 32 + 80 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 66 + 97 + 114 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 105 + 122 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 50 + 53 + 48 + 44 + 49 + 48 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 50 + 53 + 48 + 44 + 50 + 53 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 116 + 97 + 98 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 71 + 111 + 32 + 116 + 111 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 76 + 101 + 97 + 118 + 105 + 110 + 103 + 32 + 116 + 111 + 34 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 84 + 111 + 67 + 111 + 110 + 116 + 114 + 111 + 108 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 80 + 108 + 117 + 103 + 105 + 110 + 49 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 98 + 108 + 111 + 99 + 107 + 34 + 32 + 58 + 32 + 34 + 67 + 108 + 105 + 99 + 107 + 95 + 69 + 118 + 101 + 110 + 116 + 34 + 32 + 44 + 32 + 34 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 34 + 32 + 58 + 32 + 34 + 79 + 115 + 99 + 105 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 80 + 108 + 117 + 103 + 105 + 110 + 50 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 98 + 108 + 111 + 99 + 107 + 34 + 32 + 58 + 32 + 34 + 67 + 108 + 105 + 99 + 107 + 95 + 69 + 118 + 101 + 110 + 116 + 34 + 32 + 44 + 32 + 34 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 34 + 32 + 58 + 32 + 34 + 83 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 80 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 34 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 125 + 32 + 10 + 125 0 - 257 - 257 + 39011 + 232 + 72 0 1 0 - 0 - 1 1 - 6 - 204 - 100 - 0 - 0 8 + 10 + 4 0 - 209 - 100 - 8 0 - 8 - 2 - 211 - 100 - 16 - 2 - 6 - 1 - 212 - 100 - 22 - 3 6 - 2 - 214 - 100 - 28 - 5 - 8 - 0 - 100 - 101 - 36 - 5 - 76 - 0 - 60002 - 204 - 2 - 0 - 1 0 + 11 4 - 0 - 60019 - 209 - 2 - 2 - 1 + 6 0 2 - 10 - 40 - 211 - 0 - 1 - 1 0 12 - 212 + 4 + 8 0 - 2 - 1 + 6 0 - 60003 - 214 - 2 + 13 + 4 + 14 0 - 1 + 2 0 + 14 4 + 16 0 + 2 0 - 9 - 8 + 15 4 - 1 - 0 + 18 0 2 0 - 204 - 204 - 0 + 20 + 4 + 20 0 - 204 - 204 1 0 - 212 - 212 - 0 - 0 - 211 - 211 - 0 - 0 - 212 - 212 + 21 1 + 21 0 - 204 - 204 - 0 - 0 - 214 - 214 - 0 - 0 - 212 - 212 - 0 - 0 - 214 - 214 1 0 - 204 - 204 - 2 - 0 - 214 - 214 - 2 - 0 - 204 - 204 - 3 - 0 - 214 - 214 - 3 - 0 - 209 - 209 - 0 + 5 1 - 0 - 0 - 0 - 0 - 214 - 214 - 0 1 - 0 - 0 1 1 - 6 - 204 - 100 - 0 - 0 - 8 - 0 - 209 - 100 - 8 - 0 - 8 - 2 - 211 - 100 - 16 - 2 - 6 + 1066 1 - 212 - 100 - 22 - 3 - 6 - 2 - 214 - 100 - 28 - 5 - 8 - 0 - 100 - 101 - 36 + 1082 5 - 76 - 0 - 60002 - 204 2 - 0 - 1 - 0 - 4 - 0 - 60019 - 209 2 2 - 1 - 0 2 - 10 - 40 - 211 - 0 - 1 + 6 1 - 0 - 12 - 212 - 0 - 2 + 6 1 - 0 - 60003 - 214 - 2 - 0 1 - 0 - 4 - 0 - 0 - 9 - 8 - 4 1 - 0 - 0 - 2 - 0 - 204 - 204 - 0 - 0 - 204 - 204 2 0 - 212 - 212 - 0 - 0 - 211 - 211 0 + 170 + 234 + 10 0 - 212 - 212 1 0 - 204 - 204 - 0 - 0 - 214 - 214 - 0 + 1 + 8 + 83 + 101 + 110 + 100 + 101 + 114 + 73 + 68 + 170 + 235 + 30 0 - 204 - 204 1 0 - 214 - 214 1 + 28 + 83 + 101 + 110 + 116 + 32 + 99 + 111 + 110 + 102 + 105 + 103 + 32 + 112 + 97 + 99 + 107 + 101 + 116 + 32 + 110 + 117 + 109 + 98 + 101 + 114 + 32 + 49 + 32 + 170 + 236 + 15 0 - 212 - 212 - 0 + 1 0 - 214 - 214 - 2 + 1 + 13 + 80 + 97 + 99 + 107 + 101 + 116 + 32 + 67 + 111 + 117 + 110 + 116 + 32 + 170 + 237 + 19 0 - 204 - 204 - 3 + 1 0 - 214 - 214 + 1 + 17 + 78 + 117 + 109 + 98 + 101 + 114 + 32 + 111 + 102 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 32 + 60035 + 238 3 0 - 209 - 209 - 0 1 - 0 - 0 - 0 - 0 - 214 - 214 0 1 0 - 0 - 1 - 39001 - 298 - 137 + 1082 + 39004 + 240 + 155 0 1 0 1 - 10 + 9 10 4 0 0 - 1 + 3 0 11 4 - 1 + 3 0 1 0 12 4 - 2 + 4 0 - 1 + 3 0 13 4 - 3 + 7 0 1 0 14 4 - 4 + 8 0 2 0 15 4 - 6 + 10 0 2 0 20 4 - 8 + 12 0 - 27 + 43 0 21 1 - 35 + 55 0 1 0 30 4 - 36 - 0 - 37 + 56 0 - 31 - 4 - 73 + 43 0 2 + 1082 + 1 0 - 0 - 0 - 0 + 2 + 6 + 2 0 1 1 1 - 2 - 26 1 - 2 + 42 + 1 + 4 10 4 0 @@ -3298,10 +3399,26 @@ 4 2 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 10 0 1 - 20001 + 1082 + 1 + 6 + 1 + 20000 9 49 50 @@ -3313,20 +3430,26 @@ 46 49 0 - 36 - 82 - 101 - 109 - 111 + 42 + 83 + 119 + 105 116 - 101 - 67 - 111 + 99 + 104 + 105 110 + 103 + 65 + 117 116 - 114 111 - 108 + 67 + 111 + 110 + 102 + 105 + 103 97 83 111 @@ -3350,613 +3473,556 @@ 79 98 106 + 0 + 20 + 8 + 4 1 0 - 15005 - 299 - 37 - 13 + 0 + 0 + 0 + 205 + 205 + 0 1 0 0 - 257 - 13 1 0 + 207 + 207 0 + 1 0 0 + 2 0 + 209 + 209 0 - 26 - 82 - 101 - 109 - 111 - 116 - 101 - 67 - 111 - 110 - 116 - 114 - 111 - 108 - 77 - 101 - 109 - 111 - 114 - 121 - 46 - 109 - 101 - 109 - 111 - 114 - 121 - 40 - 300 0 - 1 - 1 + 209 + 209 + 0 + 0 + 213 + 213 + 0 + 0 + 214 + 214 + 0 + 0 + 216 + 216 + 0 + 0 + 218 + 218 + 0 0 - 15011 - 302 - 1045 + 220 + 220 + 0 + 0 + 222 + 222 + 0 + 0 + 224 + 224 + 0 + 0 + 226 + 226 + 0 + 0 + 228 + 228 + 0 + 0 + 224 + 224 + 0 + 0 + 232 + 232 + 0 + 0 + 228 + 228 0 - 1 0 + 232 + 232 1 - 8 - 10 - 4 0 + 216 + 216 0 - 2 0 - 11 - 4 + 232 + 232 2 0 - 2 + 220 + 220 0 - 12 - 4 - 4 0 - 2 + 232 + 232 + 3 0 - 13 - 4 - 6 + 230 + 230 0 - 2 0 - 14 + 232 + 232 4 - 8 0 - 2 + 222 + 222 0 - 15 - 4 - 10 0 - 2 + 234 + 234 0 - 20 - 4 - 12 0 - 982 + 214 + 214 0 - 21 - 1 - 994 0 - 1 + 235 + 235 0 - 1 - 1 - 1 - 1 - 1 - 257 - 1 - 257 - 1 - 1 - 1 - 1 - 981 - 1 - 7 - 10 - 4 0 + 226 + 226 0 - 1 0 - 11 - 4 - 1 + 236 + 236 0 - 1 0 - 12 - 4 - 2 + 218 + 218 0 - 1 0 - 13 - 4 - 3 + 237 + 237 0 - 1 0 - 21 - 4 - 4 + 232 + 232 0 - 21 0 - 22 - 4 - 25 + 240 + 240 0 - 4 0 - 900 - 10 - 29 + 238 + 238 0 - 908 0 + 240 + 240 + 1 0 + 211 + 211 0 + 1 0 0 - 20 - 82 - 101 - 109 - 111 - 116 - 101 - 67 - 111 - 110 - 116 - 114 - 111 - 108 - 84 - 104 - 114 - 101 - 97 - 100 - 49 - 3 - 2 0 - -1 1 - 17 - 201 + 6 + 205 100 0 0 - 131 + 6 0 - 204 + 207 100 - 131 + 6 0 - 76 + 6 0 209 100 - 207 + 12 0 6 0 211 100 - 213 + 18 0 6 - 0 + 1 213 100 - 219 - 0 - 6 + 24 + 1 + 39 0 - 215 100 - 225 + 101 + 63 + 1 + 44 0 - 82 + 60032 + 205 0 - 217 - 100 - 307 0 - 82 + 1 0 - 219 - 100 - 389 + 60032 + 207 0 - 6 0 - 221 - 100 - 395 + 1 0 - 6 + 60032 + 209 0 - 223 - 100 - 401 0 - 37 + 1 0 - 224 + 40 + 211 + 0 + 1 + 1 + 0 + 170 + 213 + 33 + 0 + 1 + 0 + 1 + 31 + 99 + 97 + 115 + 101 + 32 + 85 + 110 100 - 438 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 58 + 32 + 87 + 114 + 111 + 110 + 103 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 73 + 68 + 0 + 5 + 8 + 4 + 1 0 - 37 0 - 225 - 100 - 475 0 - 37 0 - 226 - 100 - 512 + 205 + 205 0 - 37 + 1 0 - 227 - 100 - 549 0 - 36 + 1 0 - 228 - 100 - 585 + 207 + 207 0 - 36 + 1 0 - 229 - 100 - 621 0 - 43 + 2 0 - 100 - 101 - 664 + 209 + 209 0 - 140 0 - 39003 - 201 - 125 + 209 + 209 0 - 1 0 - 1 - 9 - 10 - 4 + 213 + 213 0 0 - 1 + 211 + 211 0 - 11 - 4 1 0 - 3 0 - 12 - 4 - 4 0 - 1 + 7 0 - 13 + 22 + 8 4 - 5 0 - 3 + 201 + 201 0 - 14 - 4 - 8 0 - 2 + 204 + 204 0 - 15 - 4 - 10 0 + 204 + 204 2 0 - 20 - 4 - 12 + 209 + 209 0 - 19 0 - 21 - 1 - 31 + 209 + 209 0 - 1 0 - 30 - 4 - 32 + 212 + 212 0 - 37 0 + 211 + 211 0 - 2 - 1396 - 6 0 - 2 - 6 - 6 - 1 - 1 - 1 - 1 - 18 + 212 + 212 1 - 2 - 10 - 4 0 + 212 + 212 0 - 2 0 - 11 - 4 - 2 + 214 + 214 0 - 2 0 - 1 - 1396 - 1 - 6 + 209 + 209 0 - 36 - 82 - 101 - 109 - 111 - 116 - 101 - 67 - 111 - 110 - 116 - 114 - 111 - 108 - 97 - 83 - 111 - 99 - 107 - 101 - 116 - 46 - 85 - 68 - 80 - 83 - 111 - 99 - 107 - 101 - 116 - 95 - 83 - 104 - 79 - 98 - 106 - 39012 - 204 - 70 0 + 214 + 214 1 0 - 1 - 8 - 10 - 4 + 212 + 212 0 0 - 2 + 216 + 216 0 - 11 - 4 + 0 + 204 + 204 2 0 - 5 + 218 + 218 0 - 12 - 4 - 7 0 - 2 + 218 + 218 0 - 13 - 4 - 9 0 - 5 + 220 + 220 0 - 14 - 4 - 14 0 - 2 + 220 + 220 0 - 15 - 4 - 16 0 - 2 + 222 + 222 0 - 20 - 4 - 18 0 - 1 + 214 + 214 0 - 21 - 1 - 19 0 + 222 + 222 1 0 + 220 + 220 + 0 + 0 + 224 + 224 + 0 + 0 + 216 + 216 + 0 + 0 + 226 + 226 + 0 + 0 + 224 + 224 + 0 + 0 + 226 + 226 1 - 1396 - 4 - 1 - 1 - 1 - 173 - 1 - 6 - 4 - 2 - 2 - 2 - 257 - 1 - 1 - 1 - 2 + 0 + 226 + 226 0 0 - 60032 - 209 + 228 + 228 + 0 + 0 + 222 + 222 0 0 + 228 + 228 1 0 - 60032 - 211 + 228 + 228 + 0 0 + 230 + 230 0 - 1 0 - 60032 - 213 + 204 + 204 0 0 - 1 + 232 + 232 0 - 60304 - 215 - 76 0 + 204 + 204 1 0 + 232 + 232 1 - 8 - 10 - 4 0 - 0 - 2 - 0 - 11 - 4 + 204 + 204 2 0 + 232 + 232 2 0 - 12 - 4 - 4 + 204 + 204 + 3 0 - 2 + 232 + 232 + 3 0 - 13 - 4 - 6 + 230 + 230 0 - 2 0 - 14 + 232 + 232 4 - 8 + 13 + 40 + 222 0 + 1 + 1 + 0 + 60005 + 224 2 0 - 15 - 4 - 10 + 1 0 - 2 0 - 20 - 4 - 12 + 100000 + 60031 + 226 0 - 13 0 - 21 1 - 25 0 - 1 + 40 + 228 0 1 1 + 0 + 60031 + 230 + 0 + 0 1 + 0 + 40 + 232 + 0 1 1 - 2 - 1 - 2 - 1 - 1 + 0 + 60031 + 234 + 0 + 0 1 - 2 - 12 + 0 + 40 + 236 + 0 1 1 - 10 - 4 0 + 60031 + 238 0 - 4 0 - 3 1 - 2 - 12 0 - 60304 - 217 - 76 + 39011 + 240 + 70 0 1 0 @@ -3966,500 +4032,557 @@ 4 0 0 - 2 + 5 0 11 4 - 2 + 5 0 2 0 12 4 - 4 + 7 0 - 2 + 5 0 13 4 - 6 + 12 0 2 0 14 4 - 8 + 14 0 2 0 15 4 - 10 + 16 0 2 0 20 4 - 12 + 18 0 - 13 + 1 0 21 1 - 25 + 19 0 1 0 + 4 1 1 1 1 1 + 16 + 4 + 2 + 2 2 - 1 2 1 + 6 1 - 1 - 2 - 12 1 1 - 10 - 4 - 0 + 2 0 - 4 0 + 60035 + 242 3 + 0 1 - 10 - 2 0 - 60032 - 219 + 1 0 + 16 + 39004 + 244 + 155 0 1 0 - 60032 - 221 + 1 + 9 + 10 + 4 0 0 - 1 + 3 0 - 170 - 223 - 31 + 11 + 4 + 3 0 1 0 - 1 - 29 - 68 - 105 - 115 - 65 - 115 - 109 - 40 - 49 - 41 - 32 - 40 - 83 - 101 - 110 - 100 - 101 - 114 - 73 - 68 - 41 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 61 - 32 - 170 - 224 - 31 + 12 + 4 + 4 0 - 1 + 3 + 0 + 13 + 4 + 7 0 1 - 29 - 68 - 105 - 115 - 65 - 115 - 109 - 40 - 50 - 41 - 32 - 40 - 80 - 97 - 99 - 107 - 101 - 116 - 32 - 67 - 111 - 117 - 110 - 116 - 101 - 114 - 41 - 32 - 61 - 32 - 170 - 225 - 31 0 - 1 + 14 + 4 + 8 + 0 + 2 0 - 1 - 29 - 68 - 105 - 115 - 65 - 115 - 109 - 40 - 51 - 41 - 32 - 40 - 83 - 111 - 117 - 114 - 99 - 101 - 73 - 68 - 41 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 61 - 32 - 170 - 226 - 31 + 15 + 4 + 10 0 - 1 + 2 0 - 173 - 29 - 68 - 105 - 115 - 65 - 115 - 109 - 40 - 52 - 41 - 32 - 40 - 83 - 105 - 103 - 110 - 97 - 108 - 41 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 61 - 32 - 170 - 227 - 30 + 20 + 4 + 12 + 0 + 43 0 + 21 1 + 55 0 1 - 28 - 109 - 101 - 109 - 111 - 102 - 115 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 61 - 32 - 170 - 228 + 0 30 + 4 + 56 0 - 1 + 43 0 + 2 + 16 1 - 28 - 78 - 101 - 108 - 101 - 109 - 101 - 110 - 116 - 115 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 61 - 32 - 15008 - 229 - 37 0 + 2 + 6 + 2 + 0 + 1 + 1 1 + 1 + 42 + 1 + 4 + 10 + 4 0 0 - 257 - 173 + 2 0 + 11 + 4 + 2 0 + 2 0 + 12 + 4 + 4 0 + 2 0 + 13 + 4 + 6 0 + 10 0 - 26 - 82 - 101 - 109 - 111 + 1 + 16 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 + 83 + 119 + 105 116 - 101 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 67 111 110 - 116 - 114 + 102 + 105 + 103 + 97 + 83 111 - 108 - 77 + 99 + 107 101 - 109 - 111 - 114 - 121 + 116 46 - 109 - 101 - 109 + 85 + 68 + 80 + 83 111 - 114 - 121 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 60023 + 245 + 0 + 0 + 1 + 0 + 40 + 247 + 0 + 1 + 1 0 - 17 - 8 - 4 + 12 + 248 0 - 201 - 201 + 2 + 1 0 + 60034 + 250 0 - 204 - 204 0 + 1 0 - 204 - 204 + 15013 + 252 + 682 + 6 + 1 0 + 1 + 8 + 10 + 4 0 - 209 - 209 0 + 2 0 - 204 - 204 - 1 + 11 + 4 + 2 0 - 211 - 211 + 2 0 + 12 + 4 + 4 0 - 204 - 204 2 0 - 213 - 213 + 13 + 4 + 6 0 + 2 + 0 + 14 + 4 + 8 0 - 204 - 204 2 0 - 215 - 215 + 15 + 4 + 10 0 + 2 0 - 204 - 204 + 20 + 4 + 12 + 0 + 619 + 0 + 21 + 1 + 631 + 0 + 1 + 6 + 1 + 1 + 1 + 1 + 1 2 + 1 + 257 + 1 + 1 + 1 + 1 + 618 + 1 + 8 + 1 + 4 0 - 217 - 217 0 + 3 0 - 215 - 215 + 10 + 4 + 3 0 + 1 0 - 219 - 219 + 11 + 4 + 4 0 + 2 0 - 217 - 217 + 12 + 4 + 6 0 + 1 0 - 221 - 221 + 13 + 4 + 7 0 + 2 0 - 209 - 209 + 21 + 4 + 9 0 + 33 0 - 223 - 223 + 900 + 10 + 42 0 + 32 + 1 + 901 + 10 + 74 + 1 + 494 + 5 + 2 + 2 + 1 0 - 211 - 211 + 1 + 1 0 + 1 + 257 + 32 + 83 + 101 + 108 + 101 + 99 + 116 + 67 + 97 + 115 + 101 + 83 + 101 + 110 + 100 + 78 + 101 + 119 + 67 + 111 + 110 + 102 + 105 + 103 + 65 + 118 + 97 + 105 + 108 + 97 + 98 + 108 + 101 + 1 + 2 + 201 + 100 0 - 224 - 224 0 + 6 + 1 + 100 + 101 + 6 + 1 + 12 0 - 213 - 213 + 40 + 201 0 + 1 + 1 0 - 225 - 225 0 + 1 + 8 + 4 0 - 204 - 204 - 3 + 201 + 201 0 - 226 - 226 + 1 0 0 - 219 - 219 0 + 1 + 14 + 201 + 100 0 - 227 - 227 0 + 6 + 1 + 203 + 100 + 6 + 1 + 6 + 1 + 205 + 100 + 12 + 2 + 8 0 - 219 - 219 + 207 + 100 + 20 + 2 + 6 0 + 209 + 100 + 26 + 2 + 6 + 1 + 211 + 100 + 32 + 3 + 6 0 - 228 - 228 + 213 + 100 + 38 + 3 + 6 + 1 + 215 + 100 + 44 + 4 + 6 0 + 217 + 100 + 50 + 4 + 6 + 1 + 219 + 100 + 56 + 5 + 6 0 - 204 - 204 - 3 + 221 + 100 + 62 + 5 + 76 0 - 229 - 229 + 223 + 100 + 138 + 5 + 9 0 + 225 + 100 + 147 + 5 + 161 0 - 215 - 215 + 100 + 101 + 308 + 5 + 100 0 + 40 + 201 0 - 229 - 229 1 - 0 - 217 - 217 - 0 - 0 - 229 - 229 - 2 + 1 0 40 - 304 + 203 0 1 1 0 60005 - 306 + 205 2 0 1 @@ -4467,49 +4590,49 @@ 0 100000 60031 - 308 + 207 0 0 1 0 40 - 310 + 209 0 1 1 0 60031 - 312 + 211 0 0 1 0 40 - 314 + 213 0 1 1 0 60031 - 316 + 215 0 0 1 0 40 - 318 + 217 0 1 1 0 60031 - 320 + 219 0 0 1 0 39011 - 322 + 221 70 0 1 @@ -4585,7 +4708,7 @@ 0 0 60035 - 324 + 223 3 0 1 @@ -4594,8 +4717,8 @@ 0 16 39004 - 326 - 149 + 225 + 155 0 1 0 @@ -4653,7 +4776,7 @@ 4 56 0 - 37 + 43 0 2 16 @@ -4711,20 +4834,26 @@ 46 49 0 - 36 - 82 - 101 - 109 - 111 + 42 + 83 + 119 + 105 116 - 101 - 67 - 111 + 99 + 104 + 105 110 + 103 + 65 + 117 116 - 114 111 - 108 + 67 + 111 + 110 + 102 + 105 + 103 97 83 111 @@ -4749,19 +4878,19 @@ 98 106 0 - 62 + 12 8 4 0 - 201 - 201 - 0 - 0 203 203 0 0 205 + 205 + 0 + 0 + 205 205 0 0 @@ -4785,479 +4914,430 @@ 215 0 0 - 207 - 207 - 0 - 0 217 217 0 0 - 215 - 215 - 0 - 0 - 218 - 218 - 0 - 0 - 211 - 211 - 0 - 0 219 219 0 0 - 207 - 207 - 0 - 0 - 222 - 222 - 0 - 0 - 222 - 222 - 0 - 0 - 224 - 224 - 0 - 0 - 224 - 224 - 0 - 0 - 226 - 226 - 0 - 0 - 226 - 226 - 0 - 0 - 228 - 228 - 0 - 0 - 228 - 228 - 0 - 0 - 224 - 224 - 1 - 0 - 226 - 226 - 0 - 0 - 230 - 230 - 0 - 0 - 230 - 230 - 0 - 0 - 232 - 232 - 0 - 0 - 232 - 232 - 0 - 0 - 222 - 222 - 1 - 0 - 234 - 234 - 0 - 0 - 236 - 236 - 0 - 0 - 236 - 236 - 0 - 0 - 238 - 238 - 0 - 0 - 240 - 240 - 0 - 0 - 242 - 242 - 0 - 0 - 244 - 244 - 0 - 0 - 246 - 246 - 0 - 0 - 246 - 246 + 219 + 219 0 0 - 248 - 248 + 221 + 221 0 0 - 238 - 238 + 207 + 207 0 0 - 248 - 248 + 221 + 221 1 0 - 242 - 242 + 211 + 211 0 0 - 248 - 248 + 221 + 221 2 0 - 230 - 230 + 215 + 215 0 0 - 248 - 248 + 221 + 221 3 0 - 248 - 248 + 221 + 221 0 0 - 252 - 252 + 225 + 225 0 0 - 250 - 250 + 223 + 223 0 0 - 252 - 252 + 225 + 225 1 0 - 253 - 253 - 0 - 0 - 255 - 255 - 0 - 0 - 255 - 255 + 201 + 201 0 + 1 0 - 257 - 257 0 0 - 259 - 259 + 6 + 12 + 254 0 + 2 + 1 0 - 261 - 261 + 15006 + 256 + 31 0 + 1 0 - 263 - 263 0 + 257 + 1 0 - 265 - 265 0 0 - 265 - 265 0 0 - 267 - 267 0 0 - 257 + 20 + 78 + 101 + 120 + 116 + 83 + 116 + 97 + 116 + 101 + 68 + 97 + 116 + 97 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 60022 257 0 0 - 267 - 267 1 0 - 261 - 261 + 170 + 259 + 72 0 + 1 0 - 267 - 267 - 2 + 1 + 70 + 67 + 97 + 115 + 101 + 32 + 99 + 104 + 111 + 111 + 115 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 46 + 32 + 78 + 101 + 120 + 116 + 32 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 105 + 115 + 32 + 40 + 48 + 32 + 109 + 101 + 97 + 110 + 115 + 32 + 110 + 111 + 32 + 99 + 104 + 97 + 110 + 103 + 101 + 41 + 58 0 - 226 - 226 + 29 + 8 + 4 0 0 - 267 - 267 - 3 0 - 267 - 267 0 + 2 + 204 + 204 0 - 271 - 271 0 + 209 + 209 0 - 269 - 269 0 + 211 + 211 0 - 271 - 271 - 1 0 - 272 - 272 + 213 + 213 0 0 - 274 - 274 + 215 + 215 0 0 - 274 - 274 0 0 - 276 - 276 0 + 2 + 217 + 217 0 - 278 - 278 0 + 218 + 218 0 - 280 - 280 0 + 220 + 220 0 - 282 - 282 0 + 222 + 222 0 - 284 - 284 0 + 224 + 224 0 - 286 - 286 0 + 224 + 224 0 - 288 - 288 0 + 226 + 226 0 - 288 - 288 0 + 228 + 228 0 - 290 - 290 0 + 230 + 230 0 - 276 - 276 0 + 232 + 232 0 - 290 - 290 - 1 0 - 280 - 280 + 234 + 234 0 0 - 290 - 290 - 2 + 236 + 236 0 - 284 - 284 0 + 238 + 238 0 - 290 - 290 - 3 0 - 290 - 290 + 238 + 238 0 0 - 294 - 294 + 240 + 240 0 0 - 292 - 292 + 226 + 226 0 0 - 294 - 294 + 240 + 240 1 0 230 230 0 0 - 295 - 295 - 0 - 0 - 226 - 226 + 240 + 240 + 2 0 + 234 + 234 0 - 295 - 295 - 1 0 + 240 + 240 + 3 0 + 240 + 240 0 0 - 2 - 298 - 298 + 244 + 244 0 0 + 242 + 242 0 0 + 244 + 244 + 1 0 - 2 - 299 - 299 + 245 + 245 0 0 - 300 - 300 + 248 + 248 0 0 - 302 - 302 + 247 + 247 0 0 - 304 - 304 + 248 + 248 + 1 0 + 248 + 248 0 - 306 - 306 0 + 250 + 250 0 - 306 - 306 0 + 250 + 250 0 - 308 - 308 0 + 252 + 252 0 - 310 - 310 0 + 211 + 211 0 - 312 - 312 0 + 254 + 254 0 - 314 - 314 0 + 215 + 215 0 - 316 - 316 0 + 254 + 254 + 1 0 - 318 - 318 + 254 + 254 0 0 - 320 - 320 + 256 + 256 0 0 - 320 - 320 + 207 + 207 0 0 - 322 - 322 + 256 + 256 + 1 0 + 254 + 254 0 - 308 - 308 0 + 257 + 257 0 - 322 - 322 - 1 0 - 312 - 312 + 207 + 207 0 0 - 322 - 322 - 2 + 257 + 257 + 1 0 - 316 - 316 + 254 + 254 0 0 - 322 - 322 - 3 + 259 + 259 0 - 322 - 322 0 + 254 + 254 0 - 326 - 326 + 1 0 0 - 324 - 324 0 0 - 326 - 326 - 1 - 70 + 257 + 257 0 1 - 8 - 4 - 0 - 201 - 201 0 0 - 203 - 203 - 0 + 1 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar new file mode 100644 index 00000000..bb646cca --- /dev/null +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar @@ -0,0 +1,36 @@ +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +2.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +2.0000000000000000000000000 +3.0000000000000000000000000 +9999.0000000000000000000000000 +9999.0000000000000000000000000 +-4.0000000000000000000000000 +1.0000000000000000000000000 +1295793.0000000000000000000000000 +1.0000000000000000000000000 +9999.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +0.0000000000000000000000000 +1295793.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +999.0000000000000000000000000 +999.0000000000000000000000000 +1.0000000000000000000000000 +-2.0000000000000000000000000 +0.0000000000000000000000000 +1295793.0000000000000000000000000 +1.0000000000000000000000000 +2.0000000000000000000000000 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.ipar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.ipar new file mode 100644 index 00000000..be6505f8 --- /dev/null +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.ipar @@ -0,0 +1,18251 @@ + 1 + 1 + 901 + 10 + 0 + 0 + 18243 + 27 + 1 + 3 + 201 + 100 + 0 + 0 + 6 + 0 + 203 + 100 + 6 + 0 + 18205 + 27 + 100 + 101 + 18211 + 27 + 12 + 0 + 60023 + 201 + 0 + 0 + 1 + 0 + 15011 + 203 + 18199 + 27 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 18136 + 0 + 21 + 1 + 18148 + 0 + 1 + 27 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 18135 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 1 + 0 + 12 + 4 + 2 + 0 + 1 + 0 + 13 + 4 + 3 + 0 + 1 + 0 + 21 + 4 + 4 + 0 + 19 + 0 + 22 + 4 + 23 + 0 + 4 + 0 + 900 + 10 + 27 + 0 + 18064 + 27 + 0 + 0 + 0 + 0 + 18 + 77 + 97 + 105 + 110 + 82 + 101 + 97 + 108 + 116 + 105 + 109 + 101 + 84 + 104 + 114 + 101 + 97 + 100 + 3 + 2 + 0 + -1 + 1 + 7 + 201 + 100 + 0 + 0 + 6 + 1 + 203 + 100 + 6 + 1 + 72 + 0 + 205 + 100 + 78 + 1 + 6 + 1 + 207 + 100 + 84 + 2 + 37 + 1 + 208 + 100 + 121 + 3 + 17848 + 24 + 212 + 100 + 17969 + 27 + 15 + 0 + 100 + 101 + 17984 + 27 + 36 + 0 + 40 + 201 + 0 + 1 + 1 + 0 + 15102 + 203 + 66 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 3 + 0 + 21 + 1 + 15 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 257 + 1 + 257 + 1 + 1 + 1 + 1 + 2 + 1 + 0 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 15005 + 207 + 31 + 1 + 1 + 0 + 0 + 257 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 20 + 78 + 101 + 120 + 116 + 83 + 116 + 97 + 116 + 101 + 68 + 97 + 116 + 97 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 15002 + 208 + 17842 + 24 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 4 + 0 + 12 + 4 + 7 + 0 + 3 + 0 + 13 + 4 + 10 + 0 + 4 + 0 + 20 + 4 + 14 + 0 + 4 + 0 + 21 + 1 + 18 + 0 + 1 + 1 + 900 + 10 + 19 + 1 + 357 + 10 + 901 + 10 + 376 + 11 + 17348 + 11 + 902 + 10 + 17724 + 22 + 62 + 2 + 2 + 1 + 1 + 3 + 1 + 1 + 1 + 2 + 257 + 257 + 3 + 257 + 257 + 257 + 3 + 3 + 1 + 1 + 1 + 1 + 6 + 203 + 100 + 0 + 0 + 6 + 1 + 205 + 100 + 6 + 1 + 6 + 1 + 207 + 100 + 12 + 2 + 227 + 6 + 210 + 100 + 239 + 8 + 6 + 1 + 212 + 100 + 245 + 9 + 6 + 1 + 100 + 101 + 251 + 10 + 68 + 0 + 40 + 203 + 0 + 1 + 1 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 15001 + 207 + 221 + 6 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 3 + 0 + 12 + 4 + 5 + 0 + 2 + 0 + 13 + 4 + 7 + 0 + 3 + 0 + 20 + 4 + 10 + 0 + 4 + 0 + 21 + 4 + 14 + 0 + 37 + 0 + 900 + 10 + 51 + 0 + 54 + 2 + 901 + 10 + 105 + 2 + 66 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 257 + 2 + 257 + 257 + 3 + 2 + 1 + 0 + 36 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 1 + 3 + 202 + 100 + 0 + 0 + 6 + 1 + 204 + 100 + 6 + 1 + 8 + 1 + 100 + 101 + 14 + 2 + 20 + 0 + 40 + 202 + 0 + 1 + 1 + 0 + 60010 + 204 + 2 + 1 + 1 + 0 + 1 + 0 + 0 + 2 + 8 + 4 + 0 + 204 + 204 + 0 + 1 + 0 + 0 + 0 + 0 + 202 + 202 + 0 + 1 + 0 + 0 + 1 + 1 + 4 + 202 + 100 + 0 + 0 + 6 + 1 + 204 + 100 + 6 + 1 + 6 + 1 + 206 + 100 + 12 + 2 + 8 + 2 + 100 + 101 + 20 + 4 + 20 + 0 + 40 + 202 + 0 + 1 + 1 + 0 + 40 + 204 + 0 + 1 + 1 + 0 + 60026 + 206 + 2 + 2 + 1 + 0 + 2 + 10 + 0 + 2 + 8 + 4 + 0 + 204 + 204 + 0 + 1 + 0 + 0 + 0 + 0 + 206 + 206 + 0 + 1 + 0 + 0 + 1 + 40 + 210 + 0 + 1 + 1 + 0 + 60020 + 212 + 0 + 1 + 1 + 0 + 0 + 8 + 8 + 4 + 1 + 0 + 0 + 0 + 0 + 207 + 207 + 0 + 0 + 205 + 205 + 0 + 0 + 207 + 207 + 1 + 0 + 203 + 203 + 0 + 0 + 207 + 207 + 2 + 0 + 207 + 207 + 1 + 0 + 212 + 212 + 0 + 0 + 210 + 210 + 0 + 0 + 212 + 212 + 1 + 0 + 207 + 207 + 0 + 1 + 0 + 0 + 0 + 0 + 212 + 212 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 2 + 1 + 15 + 203 + 100 + 0 + 0 + 6 + 0 + 205 + 100 + 6 + 0 + 17017 + 4 + 208 + 100 + 17023 + 4 + 6 + 0 + 210 + 100 + 17029 + 4 + 7 + 0 + 212 + 100 + 17036 + 4 + 8 + 0 + 215 + 100 + 17044 + 4 + 6 + 0 + 217 + 100 + 17050 + 4 + 6 + 1 + 219 + 100 + 17056 + 5 + 24 + 0 + 220 + 100 + 17080 + 5 + 6 + 1 + 222 + 100 + 17086 + 6 + 6 + 1 + 224 + 100 + 17092 + 7 + 6 + 1 + 226 + 100 + 17098 + 8 + 6 + 1 + 228 + 100 + 17104 + 9 + 6 + 1 + 230 + 100 + 17110 + 10 + 6 + 1 + 100 + 101 + 17116 + 11 + 140 + 0 + 60023 + 203 + 0 + 0 + 1 + 0 + 15011 + 205 + 17011 + 4 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 3 + 0 + 12 + 4 + 5 + 0 + 2 + 0 + 13 + 4 + 7 + 0 + 3 + 0 + 14 + 4 + 10 + 0 + 2 + 0 + 15 + 4 + 12 + 0 + 2 + 0 + 20 + 4 + 14 + 0 + 16946 + 0 + 21 + 1 + 16960 + 0 + 1 + 4 + 1 + 1 + 2 + 1 + 1 + 1 + 257 + 2 + 2 + 257 + 1 + 1 + 1 + 1 + 16945 + 1 + 7 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 21 + 4 + 6 + 0 + 12 + 0 + 22 + 4 + 18 + 0 + 4 + 0 + 900 + 10 + 22 + 0 + 16879 + 4 + 0 + 1 + 1 + 0 + 1 + 2 + 11 + 67 + 111 + 109 + 112 + 32 + 84 + 104 + 114 + 101 + 97 + 100 + 3 + 2 + 0 + -1 + 1 + 11 + 201 + 100 + 0 + 0 + 6 + 1 + 203 + 100 + 6 + 1 + 37 + 0 + 205 + 100 + 43 + 1 + 16476 + 0 + 207 + 100 + 16519 + 1 + 44 + 0 + 208 + 100 + 16563 + 1 + 6 + 1 + 210 + 100 + 16569 + 2 + 6 + 1 + 212 + 100 + 16575 + 3 + 8 + 0 + 214 + 100 + 16583 + 3 + 8 + 0 + 216 + 100 + 16591 + 3 + 6 + 1 + 218 + 100 + 16597 + 4 + 130 + 0 + 100 + 101 + 16727 + 4 + 84 + 0 + 40 + 201 + 0 + 1 + 1 + 0 + 15007 + 203 + 31 + 0 + 1 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 20 + 78 + 101 + 120 + 116 + 83 + 116 + 97 + 116 + 101 + 68 + 97 + 116 + 97 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 22000 + 205 + 16470 + 0 + 1 + 0 + 1 + 20 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 16243 + 105 + 94 + 12 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 102 + 108 + 97 + 103 + 41 + 10 + 32 + 32 + 47 + 47 + 32 + 84 + 104 + 105 + 115 + 32 + 115 + 99 + 105 + 108 + 97 + 98 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 105 + 115 + 32 + 99 + 97 + 108 + 108 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 45 + 116 + 105 + 109 + 101 + 10 + 32 + 32 + 47 + 47 + 32 + 78 + 79 + 84 + 69 + 58 + 32 + 80 + 108 + 101 + 97 + 115 + 101 + 32 + 110 + 111 + 116 + 101 + 32 + 116 + 104 + 97 + 116 + 32 + 116 + 104 + 101 + 32 + 118 + 97 + 114 + 105 + 97 + 98 + 108 + 101 + 115 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 111 + 117 + 116 + 115 + 105 + 100 + 101 + 32 + 116 + 104 + 105 + 115 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 97 + 114 + 101 + 32 + 116 + 121 + 112 + 105 + 99 + 97 + 108 + 108 + 121 + 32 + 110 + 111 + 116 + 32 + 97 + 118 + 97 + 105 + 108 + 97 + 98 + 108 + 101 + 32 + 97 + 116 + 32 + 114 + 117 + 110 + 45 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 84 + 104 + 105 + 115 + 32 + 97 + 108 + 115 + 111 + 32 + 104 + 111 + 108 + 100 + 115 + 32 + 116 + 114 + 117 + 101 + 32 + 102 + 111 + 114 + 32 + 115 + 101 + 108 + 102 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 115 + 33 + 10 + 32 + 32 + 10 + 32 + 32 + 115 + 101 + 108 + 101 + 99 + 116 + 32 + 102 + 108 + 97 + 103 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 49 + 59 + 32 + 47 + 47 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 116 + 105 + 99 + 40 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 112 + 108 + 105 + 116 + 32 + 116 + 104 + 101 + 32 + 115 + 101 + 110 + 115 + 111 + 114 + 32 + 100 + 97 + 116 + 97 + 10 + 32 + 32 + 32 + 32 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 112 + 116 + 114 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 43 + 32 + 49 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 103 + 101 + 116 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 10 + 32 + 32 + 32 + 32 + 116 + 114 + 121 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 59 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 116 + 99 + 104 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 105 + 115 + 73 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 32 + 61 + 32 + 37 + 102 + 59 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 115 + 104 + 111 + 117 + 108 + 100 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 32 + 116 + 104 + 101 + 32 + 115 + 101 + 110 + 115 + 111 + 114 + 32 + 100 + 97 + 116 + 97 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 73 + 110 + 102 + 111 + 32 + 61 + 32 + 39 + 84 + 104 + 105 + 115 + 32 + 115 + 99 + 104 + 101 + 109 + 116 + 105 + 99 + 32 + 119 + 97 + 115 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 32 + 105 + 110 + 32 + 105 + 116 + 101 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 35 + 39 + 32 + 43 + 32 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 73 + 110 + 112 + 117 + 116 + 68 + 97 + 116 + 97 + 32 + 61 + 32 + 100 + 97 + 116 + 97 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 32 + 116 + 111 + 32 + 116 + 104 + 105 + 115 + 32 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 97 + 108 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 58 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 100 + 105 + 115 + 112 + 40 + 99 + 102 + 112 + 97 + 114 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 59 + 10 + 32 + 32 + 32 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 61 + 32 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 50 + 93 + 59 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 49 + 93 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 93 + 59 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 93 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 61 + 32 + 34 + 65 + 117 + 116 + 111 + 67 + 97 + 108 + 105 + 98 + 68 + 101 + 109 + 111 + 34 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 97 + 112 + 112 + 101 + 110 + 100 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 32 + 116 + 111 + 32 + 116 + 104 + 101 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 115 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 95 + 95 + 32 + 61 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 59 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 95 + 95 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 49 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 95 + 95 + 32 + 61 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 59 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 95 + 95 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 34 + 68 + 101 + 102 + 105 + 110 + 105 + 110 + 103 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 58 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 32 + 117 + 115 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 102 + 111 + 108 + 108 + 111 + 119 + 105 + 110 + 103 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 58 + 92 + 110 + 34 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 100 + 105 + 115 + 112 + 40 + 102 + 117 + 110 + 50 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 41 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 95 + 95 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 105 + 115 + 92 + 110 + 39 + 41 + 59 + 100 + 105 + 115 + 112 + 40 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 114 + 101 + 97 + 116 + 101 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 105 + 114 + 45 + 112 + 97 + 114 + 32 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 46 + 91 + 105 + 44 + 114 + 93 + 112 + 97 + 114 + 32 + 102 + 105 + 108 + 101 + 115 + 10 + 32 + 32 + 32 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 32 + 37 + 116 + 59 + 47 + 47 + 32 + 84 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 105 + 115 + 32 + 99 + 97 + 108 + 108 + 101 + 100 + 32 + 111 + 110 + 108 + 105 + 110 + 101 + 32 + 98 + 101 + 99 + 97 + 117 + 115 + 101 + 32 + 119 + 101 + 32 + 97 + 114 + 101 + 32 + 105 + 110 + 32 + 101 + 109 + 98 + 101 + 100 + 100 + 101 + 100 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 104 + 101 + 114 + 101 + 10 + 32 + 32 + 32 + 32 + 78 + 32 + 61 + 32 + 50 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 91 + 112 + 97 + 114 + 44 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 105 + 109 + 110 + 101 + 115 + 116 + 50 + 95 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 109 + 101 + 110 + 116 + 40 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 95 + 95 + 44 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 95 + 95 + 44 + 32 + 110 + 101 + 115 + 116 + 101 + 100 + 95 + 102 + 110 + 61 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 61 + 108 + 105 + 115 + 116 + 40 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 44 + 32 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 44 + 32 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 44 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 41 + 44 + 32 + 78 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 53 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 95 + 95 + 32 + 78 + 101 + 119 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 105 + 115 + 92 + 110 + 39 + 41 + 59 + 100 + 105 + 115 + 112 + 40 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 97 + 118 + 101 + 32 + 118 + 101 + 99 + 116 + 111 + 114 + 115 + 32 + 116 + 111 + 32 + 97 + 32 + 102 + 105 + 108 + 101 + 10 + 32 + 32 + 32 + 32 + 115 + 97 + 118 + 101 + 95 + 105 + 114 + 112 + 97 + 114 + 97 + 109 + 40 + 112 + 97 + 114 + 44 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 43 + 32 + 39 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 105 + 112 + 97 + 114 + 39 + 44 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 32 + 43 + 32 + 39 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 114 + 112 + 97 + 114 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 84 + 101 + 108 + 108 + 32 + 116 + 104 + 97 + 116 + 32 + 101 + 118 + 101 + 114 + 121 + 116 + 104 + 105 + 110 + 103 + 32 + 119 + 101 + 110 + 116 + 32 + 102 + 105 + 110 + 101 + 46 + 10 + 32 + 32 + 32 + 32 + 99 + 111 + 109 + 112 + 114 + 101 + 97 + 100 + 121 + 32 + 61 + 32 + 49 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 82 + 101 + 116 + 117 + 114 + 110 + 86 + 97 + 108 + 32 + 61 + 32 + 48 + 59 + 47 + 47 + 32 + 114 + 117 + 110 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 97 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 112 + 97 + 99 + 107 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 32 + 61 + 32 + 122 + 101 + 114 + 111 + 115 + 40 + 50 + 48 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 40 + 49 + 41 + 32 + 61 + 32 + 99 + 111 + 109 + 112 + 114 + 101 + 97 + 100 + 121 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 40 + 50 + 41 + 32 + 61 + 32 + 67 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 82 + 101 + 116 + 117 + 114 + 110 + 86 + 97 + 108 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 108 + 101 + 97 + 114 + 10 + 32 + 32 + 32 + 32 + 112 + 97 + 114 + 46 + 105 + 112 + 97 + 114 + 32 + 61 + 32 + 91 + 93 + 59 + 10 + 32 + 32 + 32 + 32 + 112 + 97 + 114 + 46 + 114 + 112 + 97 + 114 + 32 + 61 + 32 + 91 + 93 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 112 + 116 + 114 + 40 + 49 + 41 + 32 + 61 + 32 + 111 + 117 + 116 + 118 + 101 + 99 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 69 + 108 + 97 + 112 + 115 + 101 + 100 + 84 + 105 + 109 + 101 + 32 + 61 + 32 + 116 + 111 + 99 + 40 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 84 + 105 + 109 + 101 + 32 + 116 + 111 + 32 + 114 + 117 + 110 + 32 + 116 + 104 + 101 + 32 + 101 + 109 + 98 + 101 + 100 + 100 + 101 + 100 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 67 + 111 + 100 + 101 + 32 + 37 + 102 + 32 + 115 + 101 + 99 + 46 + 92 + 110 + 39 + 44 + 32 + 69 + 108 + 97 + 112 + 115 + 101 + 100 + 84 + 105 + 109 + 101 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 52 + 59 + 32 + 47 + 47 + 32 + 105 + 110 + 105 + 116 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 34 + 83 + 101 + 116 + 116 + 105 + 110 + 103 + 32 + 102 + 117 + 110 + 99 + 112 + 114 + 111 + 99 + 40 + 48 + 41 + 44 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 104 + 97 + 115 + 32 + 98 + 101 + 101 + 110 + 32 + 111 + 102 + 32 + 118 + 97 + 108 + 117 + 101 + 32 + 37 + 100 + 32 + 98 + 101 + 102 + 111 + 114 + 101 + 92 + 110 + 34 + 44 + 32 + 102 + 117 + 110 + 99 + 112 + 114 + 111 + 116 + 40 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 102 + 117 + 110 + 99 + 112 + 114 + 111 + 99 + 40 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 115 + 116 + 97 + 116 + 101 + 115 + 46 + 69 + 120 + 101 + 99 + 67 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 48 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 53 + 59 + 32 + 47 + 47 + 32 + 116 + 101 + 114 + 109 + 105 + 110 + 97 + 116 + 101 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 34 + 116 + 101 + 114 + 109 + 105 + 110 + 97 + 116 + 101 + 92 + 110 + 34 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 49 + 48 + 59 + 32 + 47 + 47 + 32 + 99 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 101 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 91 + 115 + 105 + 109 + 44 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 61 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 44 + 32 + 112 + 97 + 114 + 41 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 84 + 104 + 101 + 32 + 110 + 101 + 115 + 116 + 101 + 100 + 32 + 115 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 115 + 32 + 116 + 119 + 111 + 32 + 115 + 117 + 98 + 45 + 115 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 115 + 58 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 49 + 41 + 32 + 65 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 44 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 99 + 111 + 109 + 109 + 111 + 110 + 108 + 121 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 115 + 32 + 110 + 111 + 116 + 104 + 105 + 110 + 103 + 32 + 97 + 110 + 100 + 32 + 105 + 115 + 32 + 115 + 119 + 105 + 116 + 99 + 104 + 101 + 100 + 32 + 116 + 111 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 109 + 101 + 110 + 116 + 32 + 105 + 115 + 32 + 105 + 110 + 32 + 112 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 32 + 40 + 119 + 104 + 105 + 99 + 104 + 32 + 109 + 97 + 121 + 32 + 116 + 97 + 107 + 101 + 32 + 115 + 111 + 109 + 101 + 32 + 116 + 105 + 109 + 101 + 41 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 50 + 41 + 32 + 84 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 44 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 97 + 99 + 116 + 117 + 97 + 108 + 108 + 121 + 32 + 99 + 111 + 110 + 116 + 97 + 105 + 110 + 115 + 32 + 116 + 104 + 101 + 32 + 97 + 108 + 103 + 111 + 114 + 105 + 116 + 104 + 109 + 32 + 116 + 111 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 72 + 101 + 114 + 101 + 44 + 32 + 116 + 104 + 101 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 32 + 115 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 115 + 32 + 97 + 114 + 101 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 44 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 99 + 97 + 110 + 32 + 116 + 104 + 101 + 110 + 32 + 98 + 101 + 10 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 10 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 101 + 118 + 32 + 61 + 32 + 48 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 32 + 61 + 32 + 112 + 97 + 114 + 40 + 49 + 41 + 59 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 110 + 117 + 109 + 98 + 101 + 114 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 115 + 116 + 101 + 100 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 115 + 32 + 40 + 111 + 110 + 101 + 32 + 111 + 102 + 32 + 116 + 119 + 111 + 41 + 32 + 34 + 49 + 34 + 32 + 109 + 101 + 97 + 110 + 115 + 32 + 116 + 104 + 101 + 10 + 32 + 32 + 47 + 47 + 32 + 100 + 117 + 109 + 109 + 121 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 119 + 104 + 105 + 99 + 104 + 32 + 105 + 115 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 101 + 100 + 32 + 119 + 104 + 105 + 108 + 101 + 32 + 116 + 104 + 101 + 32 + 50 + 110 + 100 + 32 + 34 + 50 + 34 + 32 + 105 + 115 + 32 + 101 + 120 + 99 + 104 + 97 + 110 + 103 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 10 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 112 + 97 + 114 + 40 + 50 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 114 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 32 + 78 + 61 + 37 + 100 + 92 + 110 + 39 + 44 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 49 + 41 + 59 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 50 + 41 + 59 + 10 + 32 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 51 + 41 + 59 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 52 + 41 + 59 + 10 + 32 + 32 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 53 + 41 + 59 + 10 + 32 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 54 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 122 + 101 + 114 + 111 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 48 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 102 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 32 + 61 + 61 + 32 + 49 + 32 + 116 + 104 + 101 + 110 + 32 + 32 + 32 + 47 + 47 + 32 + 105 + 115 + 32 + 116 + 104 + 105 + 115 + 32 + 116 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 50 + 41 + 32 + 63 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 68 + 101 + 102 + 105 + 110 + 101 + 32 + 100 + 117 + 109 + 109 + 121 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 115 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 78 + 79 + 84 + 69 + 58 + 32 + 111 + 110 + 108 + 121 + 32 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 32 + 105 + 115 + 32 + 115 + 117 + 112 + 112 + 111 + 114 + 116 + 101 + 100 + 32 + 98 + 121 + 32 + 116 + 104 + 105 + 115 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 102 + 111 + 114 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 32 + 61 + 32 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 44 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 100 + 117 + 109 + 109 + 121 + 79 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 118 + 101 + 99 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 122 + 101 + 114 + 111 + 115 + 40 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 44 + 32 + 49 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 100 + 117 + 109 + 109 + 121 + 79 + 117 + 116 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 122 + 101 + 114 + 111 + 59 + 47 + 47 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 102 + 32 + 99 + 110 + 116 + 114 + 108 + 78 + 32 + 61 + 61 + 32 + 50 + 32 + 116 + 104 + 101 + 110 + 32 + 32 + 32 + 47 + 47 + 32 + 105 + 115 + 32 + 116 + 104 + 105 + 115 + 32 + 116 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 50 + 41 + 32 + 63 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 58 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 114 + 117 + 110 + 32 + 116 + 104 + 101 + 32 + 99 + 97 + 108 + 108 + 98 + 97 + 99 + 107 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 102 + 111 + 114 + 32 + 100 + 101 + 102 + 105 + 110 + 105 + 116 + 105 + 111 + 110 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 100 + 105 + 115 + 112 + 40 + 102 + 117 + 110 + 50 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 44 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 44 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 32 + 61 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 44 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 58 + 32 + 100 + 111 + 110 + 101 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 116 + 111 + 114 + 101 + 32 + 116 + 104 + 101 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 32 + 111 + 102 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 40 + 53 + 41 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 97 + 100 + 100 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 40 + 36 + 32 + 43 + 32 + 49 + 41 + 32 + 61 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 59 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 91 + 115 + 105 + 109 + 44 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 44 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 93 + 61 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 95 + 117 + 115 + 101 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 44 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 41 + 10 + 32 + 32 + 10 + 32 + 32 + 47 + 47 + 32 + 68 + 101 + 102 + 105 + 110 + 101 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 46 + 32 + 84 + 104 + 101 + 121 + 32 + 109 + 117 + 115 + 116 + 32 + 98 + 101 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 111 + 110 + 99 + 101 + 32 + 97 + 103 + 97 + 105 + 110 + 32 + 97 + 116 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 108 + 97 + 99 + 101 + 44 + 32 + 98 + 101 + 99 + 97 + 117 + 115 + 101 + 32 + 116 + 104 + 105 + 115 + 32 + 119 + 105 + 108 + 108 + 32 + 97 + 108 + 115 + 111 + 32 + 98 + 101 + 32 + 99 + 97 + 108 + 108 + 101 + 100 + 32 + 97 + 116 + 10 + 32 + 32 + 47 + 47 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 78 + 83 + 97 + 109 + 112 + 108 + 101 + 115 + 32 + 61 + 32 + 51 + 48 + 48 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 105 + 102 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 61 + 32 + 37 + 116 + 32 + 116 + 104 + 101 + 110 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 84 + 104 + 101 + 32 + 99 + 111 + 110 + 116 + 101 + 110 + 116 + 115 + 32 + 111 + 102 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 97 + 114 + 116 + 32 + 119 + 105 + 108 + 108 + 32 + 98 + 101 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 111 + 110 + 45 + 108 + 105 + 110 + 101 + 44 + 32 + 119 + 104 + 105 + 108 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 105 + 115 + 32 + 114 + 117 + 110 + 110 + 105 + 110 + 103 + 46 + 32 + 84 + 104 + 101 + 32 + 97 + 105 + 109 + 32 + 105 + 115 + 32 + 116 + 111 + 32 + 103 + 101 + 110 + 101 + 114 + 97 + 116 + 101 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 102 + 111 + 114 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 46 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 80 + 108 + 101 + 97 + 115 + 101 + 32 + 110 + 111 + 116 + 101 + 58 + 32 + 83 + 105 + 110 + 99 + 101 + 32 + 116 + 104 + 105 + 115 + 32 + 99 + 111 + 100 + 101 + 32 + 105 + 115 + 32 + 111 + 110 + 108 + 121 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 100 + 32 + 111 + 110 + 45 + 108 + 105 + 110 + 101 + 44 + 32 + 109 + 111 + 115 + 116 + 32 + 112 + 111 + 116 + 101 + 110 + 116 + 105 + 97 + 108 + 32 + 101 + 114 + 114 + 111 + 114 + 115 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 111 + 99 + 99 + 117 + 114 + 105 + 110 + 103 + 32 + 105 + 110 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 97 + 114 + 116 + 32 + 98 + 101 + 99 + 111 + 109 + 101 + 32 + 111 + 110 + 108 + 121 + 32 + 118 + 105 + 115 + 105 + 98 + 108 + 101 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 67 + 111 + 109 + 112 + 105 + 108 + 105 + 110 + 103 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 92 + 110 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 102 + 117 + 110 + 99 + 112 + 114 + 111 + 116 + 40 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 122 + 32 + 61 + 32 + 112 + 111 + 108 + 121 + 40 + 48 + 44 + 32 + 39 + 122 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 65 + 110 + 100 + 32 + 101 + 120 + 97 + 109 + 112 + 108 + 101 + 45 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 116 + 104 + 97 + 116 + 32 + 105 + 115 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 101 + 100 + 32 + 118 + 105 + 97 + 32 + 85 + 68 + 80 + 32 + 97 + 110 + 100 + 32 + 111 + 110 + 101 + 32 + 115 + 116 + 101 + 112 + 32 + 102 + 117 + 114 + 116 + 104 + 101 + 114 + 32 + 119 + 105 + 116 + 104 + 32 + 116 + 104 + 101 + 32 + 87 + 101 + 98 + 45 + 103 + 117 + 105 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 117 + 112 + 101 + 114 + 98 + 108 + 111 + 99 + 107 + 58 + 32 + 65 + 32 + 109 + 111 + 114 + 101 + 32 + 99 + 111 + 109 + 112 + 108 + 101 + 120 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 119 + 105 + 116 + 104 + 32 + 100 + 97 + 109 + 112 + 105 + 110 + 103 + 10 + 32 + 32 + 32 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 91 + 115 + 105 + 109 + 44 + 32 + 120 + 44 + 118 + 93 + 32 + 61 + 32 + 100 + 97 + 109 + 112 + 101 + 100 + 95 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 117 + 44 + 32 + 84 + 95 + 97 + 41 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 114 + 101 + 97 + 116 + 101 + 32 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 115 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 110 + 101 + 119 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 40 + 115 + 105 + 109 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 110 + 101 + 119 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 40 + 115 + 105 + 109 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 117 + 115 + 101 + 32 + 116 + 104 + 105 + 115 + 32 + 97 + 115 + 32 + 97 + 32 + 110 + 111 + 114 + 109 + 97 + 108 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 97 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 97 + 100 + 100 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 117 + 44 + 32 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 44 + 32 + 91 + 49 + 44 + 32 + 45 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 97 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 97 + 100 + 100 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 97 + 44 + 32 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 44 + 32 + 91 + 49 + 44 + 32 + 45 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 118 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 122 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 97 + 44 + 32 + 49 + 47 + 40 + 122 + 45 + 49 + 41 + 32 + 42 + 32 + 84 + 95 + 97 + 32 + 41 + 59 + 32 + 47 + 47 + 32 + 73 + 110 + 116 + 101 + 103 + 114 + 97 + 116 + 111 + 114 + 32 + 97 + 112 + 112 + 114 + 111 + 120 + 105 + 109 + 97 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 32 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 118 + 95 + 103 + 97 + 105 + 110 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 103 + 97 + 105 + 110 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 118 + 44 + 32 + 48 + 46 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 108 + 111 + 115 + 101 + 32 + 108 + 111 + 111 + 112 + 32 + 118 + 95 + 103 + 97 + 105 + 110 + 32 + 61 + 32 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 99 + 108 + 111 + 115 + 101 + 95 + 108 + 111 + 111 + 112 + 40 + 115 + 105 + 109 + 44 + 32 + 118 + 95 + 103 + 97 + 105 + 110 + 44 + 32 + 118 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 120 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 122 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 118 + 44 + 32 + 49 + 47 + 40 + 122 + 45 + 49 + 41 + 32 + 42 + 32 + 84 + 95 + 97 + 32 + 41 + 59 + 32 + 47 + 47 + 32 + 73 + 110 + 116 + 101 + 103 + 114 + 97 + 116 + 111 + 114 + 32 + 97 + 112 + 112 + 114 + 111 + 120 + 105 + 109 + 97 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 32 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 120 + 95 + 103 + 97 + 105 + 110 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 103 + 97 + 105 + 110 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 120 + 44 + 32 + 48 + 46 + 54 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 108 + 111 + 115 + 101 + 32 + 108 + 111 + 111 + 112 + 32 + 120 + 95 + 103 + 97 + 105 + 110 + 32 + 61 + 32 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 93 + 32 + 61 + 32 + 108 + 105 + 98 + 100 + 121 + 110 + 95 + 99 + 108 + 111 + 115 + 101 + 95 + 108 + 111 + 111 + 112 + 40 + 115 + 105 + 109 + 44 + 32 + 120 + 95 + 103 + 97 + 105 + 110 + 44 + 32 + 120 + 95 + 102 + 101 + 101 + 100 + 98 + 97 + 99 + 107 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 105 + 102 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 105 + 115 + 73 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 32 + 61 + 61 + 32 + 37 + 102 + 32 + 116 + 104 + 101 + 110 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 116 + 97 + 116 + 101 + 32 + 118 + 97 + 114 + 105 + 97 + 98 + 108 + 101 + 115 + 32 + 99 + 97 + 110 + 32 + 98 + 101 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 32 + 97 + 116 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 108 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 48 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 99 + 104 + 111 + 111 + 115 + 101 + 69 + 120 + 112 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 105 + 115 + 73 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 32 + 61 + 32 + 37 + 116 + 59 + 47 + 47 + 32 + 112 + 114 + 101 + 118 + 101 + 110 + 116 + 32 + 102 + 114 + 111 + 109 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 118 + 97 + 114 + 105 + 97 + 98 + 108 + 101 + 115 + 32 + 111 + 110 + 99 + 101 + 32 + 97 + 103 + 97 + 105 + 110 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 69 + 120 + 97 + 109 + 112 + 108 + 101 + 32 + 102 + 111 + 114 + 32 + 97 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 117 + 112 + 100 + 97 + 116 + 101 + 58 + 32 + 105 + 110 + 99 + 114 + 101 + 97 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 61 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 32 + 43 + 32 + 49 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 66 + 117 + 105 + 108 + 100 + 32 + 97 + 110 + 32 + 105 + 110 + 102 + 111 + 45 + 115 + 116 + 114 + 105 + 110 + 103 + 10 + 32 + 32 + 32 + 32 + 83 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 73 + 110 + 102 + 111 + 32 + 61 + 32 + 39 + 79 + 110 + 45 + 108 + 105 + 110 + 101 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 32 + 105 + 110 + 32 + 105 + 116 + 101 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 35 + 39 + 32 + 43 + 32 + 115 + 116 + 114 + 105 + 110 + 103 + 40 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 68 + 101 + 102 + 105 + 110 + 101 + 32 + 97 + 32 + 110 + 101 + 119 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 101 + 114 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 32 + 100 + 101 + 112 + 101 + 110 + 100 + 105 + 110 + 103 + 32 + 111 + 110 + 32 + 116 + 104 + 101 + 32 + 99 + 117 + 114 + 114 + 101 + 110 + 116 + 108 + 121 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 32 + 115 + 116 + 97 + 116 + 101 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 105 + 110 + 105 + 116 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 80 + 97 + 80 + 105 + 32 + 85 + 68 + 80 + 32 + 83 + 111 + 99 + 107 + 101 + 116 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 85 + 110 + 100 + 101 + 114 + 108 + 121 + 105 + 110 + 103 + 80 + 114 + 111 + 116 + 111 + 99 + 111 + 108 + 108 + 32 + 61 + 32 + 39 + 85 + 68 + 80 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 68 + 101 + 115 + 116 + 72 + 111 + 115 + 116 + 32 + 61 + 32 + 39 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 68 + 101 + 115 + 116 + 80 + 111 + 114 + 116 + 32 + 61 + 32 + 50 + 48 + 48 + 48 + 48 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 76 + 111 + 99 + 97 + 108 + 83 + 111 + 99 + 107 + 101 + 116 + 72 + 111 + 115 + 116 + 32 + 61 + 32 + 39 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 76 + 111 + 99 + 97 + 108 + 83 + 111 + 99 + 107 + 101 + 116 + 80 + 111 + 114 + 116 + 32 + 61 + 32 + 50 + 48 + 48 + 48 + 49 + 59 + 10 + 32 + 32 + 32 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 46 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 46 + 100 + 101 + 98 + 117 + 103 + 109 + 111 + 100 + 101 + 32 + 61 + 32 + 37 + 116 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 73 + 110 + 105 + 116 + 73 + 110 + 115 + 116 + 97 + 110 + 99 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 73 + 110 + 115 + 116 + 97 + 110 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 39 + 44 + 32 + 67 + 111 + 110 + 102 + 105 + 103 + 117 + 114 + 97 + 116 + 105 + 111 + 110 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 122 + 101 + 114 + 111 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 110 + 101 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 100 + 101 + 102 + 97 + 117 + 108 + 116 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 40 + 100 + 117 + 109 + 109 + 121 + 41 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 122 + 101 + 114 + 111 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 72 + 101 + 114 + 101 + 32 + 97 + 32 + 115 + 116 + 97 + 116 + 101 + 45 + 109 + 97 + 99 + 104 + 105 + 110 + 101 + 32 + 105 + 115 + 32 + 105 + 109 + 112 + 108 + 101 + 109 + 101 + 110 + 116 + 101 + 100 + 32 + 116 + 104 + 97 + 116 + 32 + 109 + 97 + 121 + 32 + 98 + 101 + 32 + 117 + 115 + 101 + 100 + 32 + 116 + 111 + 32 + 105 + 109 + 112 + 108 + 101 + 109 + 101 + 110 + 116 + 32 + 115 + 111 + 109 + 101 + 32 + 97 + 117 + 116 + 111 + 109 + 97 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 108 + 111 + 103 + 105 + 99 + 32 + 116 + 104 + 97 + 116 + 32 + 105 + 115 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 100 + 32 + 100 + 117 + 114 + 105 + 110 + 103 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 32 + 117 + 115 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 101 + 109 + 98 + 101 + 100 + 100 + 101 + 100 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 105 + 110 + 116 + 101 + 114 + 112 + 114 + 101 + 116 + 101 + 114 + 46 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 73 + 110 + 32 + 116 + 104 + 105 + 115 + 32 + 101 + 120 + 97 + 109 + 112 + 108 + 101 + 44 + 32 + 97 + 32 + 99 + 97 + 108 + 105 + 98 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 114 + 117 + 110 + 32 + 115 + 117 + 99 + 99 + 101 + 101 + 100 + 101 + 100 + 32 + 98 + 121 + 32 + 116 + 104 + 101 + 32 + 100 + 101 + 115 + 105 + 103 + 110 + 47 + 99 + 111 + 109 + 112 + 105 + 108 + 97 + 116 + 105 + 111 + 110 + 47 + 101 + 120 + 101 + 99 + 117 + 116 + 105 + 111 + 110 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 111 + 102 + 32 + 97 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 45 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 105 + 115 + 32 + 105 + 109 + 112 + 108 + 101 + 109 + 101 + 110 + 116 + 101 + 100 + 46 + 32 + 84 + 104 + 101 + 32 + 115 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 115 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 100 + 32 + 105 + 110 + 32 + 101 + 97 + 99 + 104 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 97 + 114 + 101 + 32 + 108 + 111 + 97 + 100 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 97 + 116 + 32 + 114 + 117 + 110 + 116 + 105 + 109 + 101 + 46 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 10 + 32 + 32 + 32 + 32 + 115 + 101 + 108 + 101 + 99 + 116 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 99 + 104 + 111 + 111 + 115 + 101 + 69 + 120 + 112 + 39 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 65 + 100 + 100 + 32 + 115 + 111 + 109 + 101 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 32 + 116 + 111 + 32 + 99 + 104 + 111 + 111 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 66 + 117 + 116 + 116 + 111 + 110 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 97 + 100 + 100 + 112 + 108 + 117 + 103 + 105 + 110 + 65 + 100 + 118 + 97 + 110 + 99 + 101 + 100 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 39 + 66 + 117 + 116 + 116 + 111 + 110 + 39 + 44 + 32 + 39 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 39 + 44 + 32 + 39 + 40 + 50 + 53 + 48 + 44 + 49 + 48 + 48 + 41 + 39 + 44 + 32 + 39 + 40 + 50 + 53 + 48 + 44 + 49 + 48 + 48 + 41 + 39 + 44 + 32 + 39 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 39 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 91 + 39 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 + 39 + 44 + 39 + 71 + 111 + 32 + 116 + 111 + 39 + 93 + 44 + 32 + 91 + 39 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 39 + 44 + 39 + 76 + 101 + 97 + 118 + 105 + 110 + 103 + 32 + 116 + 111 + 39 + 93 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 71 + 111 + 84 + 111 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 73 + 110 + 99 + 108 + 67 + 111 + 110 + 116 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 79 + 115 + 99 + 105 + 39 + 44 + 32 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 66 + 117 + 116 + 116 + 111 + 110 + 44 + 32 + 39 + 67 + 108 + 105 + 99 + 107 + 95 + 69 + 118 + 101 + 110 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 83 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 112 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 98 + 97 + 114 + 66 + 117 + 116 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 97 + 100 + 100 + 112 + 108 + 117 + 103 + 105 + 110 + 65 + 100 + 118 + 97 + 110 + 99 + 101 + 100 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 39 + 66 + 117 + 116 + 116 + 111 + 110 + 39 + 44 + 32 + 39 + 83 + 108 + 105 + 100 + 101 + 114 + 32 + 76 + 67 + 68 + 32 + 80 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 66 + 97 + 114 + 39 + 44 + 32 + 39 + 40 + 50 + 53 + 48 + 44 + 49 + 48 + 48 + 41 + 39 + 44 + 32 + 39 + 40 + 50 + 53 + 48 + 44 + 50 + 53 + 48 + 41 + 39 + 44 + 32 + 39 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 39 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 91 + 39 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 + 39 + 44 + 39 + 71 + 111 + 32 + 116 + 111 + 39 + 93 + 44 + 32 + 91 + 39 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 39 + 44 + 39 + 76 + 101 + 97 + 118 + 105 + 110 + 103 + 32 + 116 + 111 + 39 + 93 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 71 + 111 + 84 + 111 + 83 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 80 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 73 + 110 + 99 + 108 + 67 + 111 + 110 + 116 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 83 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 80 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 39 + 44 + 32 + 83 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 112 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 98 + 97 + 114 + 66 + 117 + 116 + 116 + 44 + 32 + 39 + 67 + 108 + 105 + 99 + 107 + 95 + 69 + 118 + 101 + 110 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 109 + 109 + 117 + 110 + 105 + 99 + 97 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 70 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 32 + 105 + 102 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 105 + 115 + 32 + 99 + 104 + 111 + 115 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 101 + 120 + 112 + 83 + 116 + 97 + 116 + 101 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 97 + 100 + 100 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 71 + 111 + 84 + 111 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 44 + 32 + 71 + 111 + 84 + 111 + 83 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 80 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 41 + 44 + 32 + 91 + 49 + 44 + 50 + 93 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 119 + 114 + 105 + 116 + 101 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 111 + 110 + 32 + 116 + 104 + 101 + 32 + 102 + 105 + 114 + 115 + 116 + 32 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 103 + 108 + 111 + 98 + 97 + 108 + 32 + 109 + 101 + 109 + 111 + 114 + 121 + 10 + 32 + 32 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 119 + 114 + 105 + 116 + 101 + 95 + 103 + 108 + 111 + 98 + 97 + 108 + 95 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 100 + 97 + 116 + 97 + 61 + 101 + 120 + 112 + 83 + 116 + 97 + 116 + 101 + 44 + 32 + 105 + 110 + 100 + 101 + 120 + 61 + 111 + 110 + 101 + 44 + 32 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 61 + 39 + 78 + 101 + 120 + 116 + 83 + 116 + 97 + 116 + 101 + 68 + 97 + 116 + 97 + 39 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 69 + 108 + 101 + 109 + 101 + 110 + 116 + 115 + 84 + 111 + 87 + 114 + 105 + 116 + 101 + 61 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 97 + 110 + 100 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 101 + 120 + 112 + 83 + 116 + 97 + 116 + 101 + 44 + 32 + 111 + 110 + 101 + 41 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 101 + 120 + 112 + 83 + 116 + 97 + 116 + 101 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 101 + 120 + 112 + 83 + 116 + 97 + 116 + 101 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 99 + 104 + 111 + 111 + 115 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 46 + 32 + 78 + 101 + 120 + 116 + 32 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 105 + 115 + 32 + 40 + 48 + 32 + 109 + 101 + 97 + 110 + 115 + 32 + 110 + 111 + 32 + 99 + 104 + 97 + 110 + 103 + 101 + 41 + 58 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 104 + 111 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 116 + 111 + 32 + 101 + 110 + 116 + 101 + 114 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 100 + 101 + 109 + 111 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 99 + 104 + 111 + 111 + 115 + 101 + 100 + 69 + 120 + 112 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 99 + 104 + 111 + 111 + 115 + 101 + 100 + 69 + 120 + 112 + 39 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 71 + 111 + 116 + 32 + 116 + 104 + 101 + 32 + 102 + 111 + 108 + 108 + 111 + 119 + 105 + 110 + 103 + 32 + 99 + 104 + 111 + 115 + 101 + 110 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 102 + 111 + 114 + 32 + 105 + 116 + 101 + 114 + 97 + 116 + 105 + 111 + 110 + 32 + 37 + 100 + 58 + 92 + 110 + 39 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 65 + 99 + 111 + 117 + 110 + 116 + 101 + 114 + 41 + 59 + 100 + 105 + 115 + 112 + 40 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 73 + 110 + 112 + 117 + 116 + 68 + 97 + 116 + 97 + 40 + 49 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 110 + 101 + 120 + 116 + 69 + 120 + 112 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 73 + 110 + 112 + 117 + 116 + 68 + 97 + 116 + 97 + 40 + 49 + 41 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 109 + 109 + 117 + 110 + 105 + 99 + 97 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 70 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 61 + 32 + 111 + 110 + 101 + 59 + 47 + 47 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 32 + 100 + 105 + 114 + 101 + 99 + 116 + 108 + 121 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 104 + 111 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 116 + 111 + 32 + 101 + 110 + 116 + 101 + 114 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 100 + 101 + 109 + 111 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 101 + 108 + 101 + 99 + 116 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 73 + 110 + 112 + 117 + 116 + 68 + 97 + 116 + 97 + 40 + 49 + 41 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 49 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 50 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 115 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 112 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 98 + 97 + 114 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 101 + 108 + 115 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 39 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 110 + 101 + 120 + 116 + 69 + 120 + 112 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 110 + 101 + 120 + 116 + 69 + 120 + 112 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 99 + 104 + 111 + 111 + 115 + 101 + 100 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 46 + 32 + 78 + 101 + 120 + 116 + 32 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 105 + 115 + 32 + 39 + 32 + 43 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 39 + 59 + 32 + 47 + 47 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 32 + 97 + 110 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 116 + 111 + 32 + 112 + 101 + 114 + 102 + 111 + 114 + 109 + 32 + 97 + 110 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 49 + 32 + 61 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 105 + 110 + 49 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 58 + 32 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 65 + 100 + 100 + 32 + 97 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 32 + 102 + 111 + 114 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 108 + 105 + 110 + 103 + 32 + 116 + 104 + 101 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 68 + 105 + 115 + 116 + 117 + 114 + 98 + 97 + 110 + 99 + 101 + 66 + 117 + 116 + 116 + 111 + 110 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 97 + 100 + 100 + 112 + 108 + 117 + 103 + 105 + 110 + 65 + 100 + 118 + 97 + 110 + 99 + 101 + 100 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 39 + 66 + 117 + 116 + 116 + 111 + 110 + 39 + 44 + 32 + 39 + 68 + 105 + 115 + 116 + 117 + 114 + 98 + 97 + 110 + 99 + 101 + 39 + 44 + 32 + 39 + 40 + 49 + 53 + 48 + 44 + 53 + 48 + 41 + 39 + 44 + 32 + 39 + 40 + 54 + 48 + 48 + 44 + 49 + 48 + 48 + 41 + 39 + 44 + 32 + 39 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 39 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 91 + 39 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 + 39 + 44 + 39 + 68 + 105 + 115 + 116 + 117 + 114 + 98 + 39 + 93 + 44 + 32 + 91 + 39 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 39 + 44 + 39 + 68 + 105 + 115 + 116 + 117 + 114 + 98 + 105 + 110 + 103 + 39 + 93 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 73 + 110 + 112 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 73 + 110 + 99 + 108 + 67 + 111 + 110 + 116 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 39 + 44 + 32 + 68 + 105 + 115 + 116 + 117 + 114 + 98 + 97 + 110 + 99 + 101 + 66 + 117 + 116 + 116 + 111 + 110 + 44 + 32 + 39 + 67 + 108 + 105 + 99 + 107 + 95 + 69 + 118 + 101 + 110 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 65 + 100 + 100 + 32 + 97 + 32 + 98 + 117 + 116 + 116 + 111 + 110 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 32 + 116 + 111 + 32 + 103 + 111 + 32 + 116 + 111 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 115 + 101 + 108 + 101 + 99 + 116 + 105 + 111 + 110 + 32 + 100 + 105 + 97 + 108 + 111 + 103 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 79 + 107 + 66 + 117 + 116 + 116 + 111 + 110 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 97 + 100 + 100 + 112 + 108 + 117 + 103 + 105 + 110 + 65 + 100 + 118 + 97 + 110 + 99 + 101 + 100 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 39 + 66 + 117 + 116 + 116 + 111 + 110 + 39 + 44 + 32 + 39 + 76 + 101 + 97 + 118 + 101 + 39 + 44 + 32 + 39 + 40 + 49 + 53 + 48 + 44 + 53 + 48 + 41 + 39 + 44 + 32 + 39 + 40 + 54 + 48 + 48 + 44 + 51 + 50 + 53 + 41 + 39 + 44 + 32 + 39 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 39 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 91 + 39 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 + 39 + 44 + 39 + 79 + 107 + 39 + 93 + 44 + 32 + 91 + 39 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 39 + 44 + 39 + 76 + 101 + 97 + 118 + 105 + 110 + 103 + 39 + 93 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 71 + 111 + 84 + 111 + 67 + 104 + 111 + 111 + 115 + 101 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 73 + 110 + 99 + 108 + 67 + 111 + 110 + 116 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 111 + 107 + 39 + 44 + 32 + 79 + 107 + 66 + 117 + 116 + 116 + 111 + 110 + 44 + 32 + 39 + 67 + 108 + 105 + 99 + 107 + 95 + 69 + 118 + 101 + 110 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 112 + 114 + 105 + 110 + 116 + 102 + 32 + 116 + 104 + 101 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 73 + 110 + 112 + 117 + 116 + 44 + 32 + 39 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 32 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 84 + 104 + 101 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 32 + 116 + 111 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 73 + 110 + 112 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 97 + 100 + 100 + 95 + 111 + 102 + 115 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 73 + 110 + 112 + 117 + 116 + 44 + 32 + 48 + 46 + 50 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 84 + 95 + 97 + 32 + 61 + 32 + 48 + 46 + 49 + 59 + 91 + 115 + 105 + 109 + 44 + 120 + 44 + 118 + 93 + 32 + 61 + 32 + 100 + 97 + 109 + 112 + 101 + 100 + 95 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 73 + 110 + 112 + 117 + 116 + 44 + 32 + 84 + 95 + 97 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 83 + 116 + 114 + 101 + 97 + 109 + 32 + 116 + 104 + 101 + 32 + 100 + 97 + 116 + 97 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 80 + 108 + 111 + 116 + 88 + 89 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 97 + 100 + 100 + 112 + 108 + 117 + 103 + 105 + 110 + 65 + 100 + 118 + 97 + 110 + 99 + 101 + 100 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 39 + 80 + 108 + 111 + 116 + 39 + 44 + 32 + 39 + 80 + 108 + 111 + 116 + 32 + 88 + 86 + 39 + 44 + 32 + 39 + 40 + 53 + 48 + 48 + 44 + 53 + 48 + 48 + 41 + 39 + 44 + 32 + 39 + 40 + 48 + 44 + 48 + 41 + 39 + 44 + 32 + 39 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 39 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 91 + 39 + 121 + 82 + 97 + 110 + 103 + 101 + 39 + 44 + 39 + 91 + 45 + 49 + 48 + 46 + 48 + 32 + 49 + 48 + 46 + 48 + 93 + 39 + 93 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 73 + 110 + 99 + 108 + 83 + 117 + 98 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 120 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 88 + 39 + 44 + 32 + 80 + 108 + 111 + 116 + 88 + 89 + 44 + 32 + 39 + 83 + 111 + 117 + 114 + 99 + 101 + 71 + 114 + 111 + 117 + 112 + 48 + 39 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 73 + 110 + 99 + 108 + 83 + 117 + 98 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 118 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 86 + 39 + 44 + 32 + 80 + 108 + 111 + 116 + 88 + 89 + 44 + 32 + 39 + 83 + 111 + 117 + 114 + 99 + 101 + 71 + 114 + 111 + 117 + 112 + 48 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 109 + 109 + 117 + 110 + 105 + 99 + 97 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 70 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 120 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 61 + 32 + 71 + 111 + 84 + 111 + 67 + 104 + 111 + 111 + 115 + 101 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 104 + 111 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 116 + 111 + 32 + 101 + 110 + 116 + 101 + 114 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 100 + 101 + 109 + 111 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 99 + 104 + 111 + 111 + 115 + 101 + 69 + 120 + 112 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 115 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 112 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 98 + 97 + 114 + 39 + 59 + 32 + 47 + 47 + 32 + 100 + 101 + 115 + 105 + 103 + 110 + 32 + 97 + 32 + 99 + 111 + 110 + 115 + 116 + 97 + 110 + 116 + 32 + 115 + 105 + 103 + 110 + 97 + 108 + 32 + 116 + 111 + 32 + 115 + 101 + 110 + 100 + 32 + 116 + 111 + 32 + 80 + 97 + 80 + 105 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 49 + 32 + 61 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 105 + 110 + 49 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 115 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 112 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 98 + 97 + 114 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 58 + 32 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 65 + 100 + 100 + 32 + 97 + 32 + 98 + 117 + 116 + 116 + 111 + 110 + 32 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 32 + 116 + 111 + 32 + 103 + 111 + 32 + 116 + 111 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 115 + 101 + 108 + 101 + 99 + 116 + 105 + 111 + 110 + 32 + 100 + 105 + 97 + 108 + 111 + 103 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 79 + 107 + 66 + 117 + 116 + 116 + 111 + 110 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 97 + 100 + 100 + 112 + 108 + 117 + 103 + 105 + 110 + 65 + 100 + 118 + 97 + 110 + 99 + 101 + 100 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 39 + 66 + 117 + 116 + 116 + 111 + 110 + 39 + 44 + 32 + 39 + 76 + 101 + 97 + 118 + 101 + 39 + 44 + 32 + 39 + 40 + 49 + 53 + 48 + 44 + 53 + 48 + 41 + 39 + 44 + 32 + 39 + 40 + 54 + 48 + 48 + 44 + 50 + 50 + 53 + 41 + 39 + 44 + 32 + 39 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 39 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 91 + 39 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 + 39 + 44 + 39 + 79 + 107 + 39 + 93 + 44 + 32 + 91 + 39 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 39 + 44 + 39 + 76 + 101 + 97 + 118 + 105 + 110 + 103 + 39 + 93 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 71 + 111 + 84 + 111 + 67 + 104 + 111 + 111 + 115 + 101 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 73 + 110 + 99 + 108 + 67 + 111 + 110 + 116 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 111 + 107 + 39 + 44 + 32 + 79 + 107 + 66 + 117 + 116 + 116 + 111 + 110 + 44 + 32 + 39 + 67 + 108 + 105 + 99 + 107 + 95 + 69 + 118 + 101 + 110 + 116 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 65 + 100 + 100 + 32 + 97 + 32 + 115 + 108 + 105 + 100 + 101 + 114 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 83 + 108 + 105 + 100 + 101 + 114 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 97 + 100 + 100 + 112 + 108 + 117 + 103 + 105 + 110 + 65 + 100 + 118 + 97 + 110 + 99 + 101 + 100 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 39 + 83 + 108 + 105 + 100 + 101 + 114 + 39 + 44 + 32 + 39 + 83 + 108 + 105 + 100 + 101 + 114 + 32 + 69 + 120 + 97 + 109 + 112 + 108 + 101 + 39 + 44 + 32 + 39 + 40 + 53 + 48 + 48 + 44 + 55 + 53 + 41 + 39 + 44 + 32 + 39 + 40 + 53 + 48 + 44 + 53 + 48 + 41 + 39 + 44 + 32 + 39 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 39 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 91 + 39 + 115 + 116 + 101 + 112 + 95 + 99 + 111 + 117 + 110 + 116 + 39 + 44 + 39 + 49 + 48 + 49 + 39 + 93 + 44 + 32 + 91 + 39 + 108 + 111 + 119 + 101 + 114 + 95 + 98 + 111 + 117 + 110 + 100 + 39 + 44 + 39 + 48 + 46 + 48 + 39 + 93 + 44 + 32 + 91 + 39 + 117 + 112 + 112 + 101 + 114 + 95 + 98 + 111 + 117 + 110 + 100 + 39 + 44 + 39 + 49 + 46 + 48 + 39 + 93 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 115 + 108 + 105 + 100 + 101 + 114 + 86 + 97 + 108 + 117 + 101 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 73 + 110 + 99 + 108 + 67 + 111 + 110 + 116 + 114 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 61 + 39 + 115 + 108 + 105 + 100 + 101 + 114 + 86 + 97 + 108 + 39 + 44 + 32 + 83 + 108 + 105 + 100 + 101 + 114 + 44 + 32 + 39 + 83 + 108 + 105 + 100 + 101 + 114 + 66 + 108 + 111 + 99 + 107 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 104 + 111 + 119 + 32 + 116 + 104 + 101 + 32 + 115 + 108 + 105 + 100 + 101 + 114 + 32 + 118 + 97 + 108 + 117 + 101 + 32 + 111 + 110 + 32 + 97 + 32 + 112 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 32 + 98 + 97 + 114 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 112 + 101 + 114 + 99 + 83 + 108 + 105 + 100 + 101 + 114 + 86 + 97 + 108 + 117 + 101 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 103 + 97 + 105 + 110 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 115 + 108 + 105 + 100 + 101 + 114 + 86 + 97 + 108 + 117 + 101 + 44 + 32 + 49 + 48 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 80 + 98 + 97 + 114 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 97 + 100 + 100 + 112 + 108 + 117 + 103 + 105 + 110 + 65 + 100 + 118 + 97 + 110 + 99 + 101 + 100 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 39 + 80 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 66 + 97 + 114 + 39 + 44 + 32 + 39 + 80 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 66 + 97 + 114 + 32 + 69 + 120 + 97 + 109 + 112 + 108 + 101 + 39 + 44 + 32 + 39 + 40 + 53 + 48 + 48 + 44 + 55 + 53 + 41 + 39 + 44 + 32 + 39 + 40 + 53 + 48 + 44 + 50 + 53 + 48 + 41 + 39 + 44 + 32 + 39 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 39 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 73 + 110 + 99 + 108 + 83 + 117 + 98 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 112 + 101 + 114 + 99 + 83 + 108 + 105 + 100 + 101 + 114 + 86 + 97 + 108 + 117 + 101 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 112 + 101 + 114 + 99 + 101 + 110 + 116 + 39 + 44 + 32 + 80 + 98 + 97 + 114 + 44 + 32 + 39 + 83 + 111 + 117 + 114 + 99 + 101 + 71 + 114 + 111 + 117 + 112 + 48 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 115 + 104 + 111 + 119 + 32 + 116 + 104 + 101 + 32 + 115 + 108 + 105 + 100 + 101 + 114 + 32 + 118 + 97 + 108 + 117 + 101 + 32 + 111 + 110 + 32 + 97 + 32 + 108 + 99 + 100 + 32 + 100 + 105 + 115 + 112 + 108 + 97 + 121 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 76 + 67 + 68 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 97 + 100 + 100 + 112 + 108 + 117 + 103 + 105 + 110 + 65 + 100 + 118 + 97 + 110 + 99 + 101 + 100 + 40 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 39 + 76 + 67 + 68 + 68 + 105 + 115 + 112 + 108 + 97 + 121 + 39 + 44 + 32 + 39 + 76 + 67 + 68 + 32 + 69 + 120 + 97 + 109 + 112 + 108 + 101 + 39 + 44 + 32 + 39 + 40 + 50 + 49 + 48 + 44 + 49 + 50 + 48 + 41 + 39 + 44 + 32 + 39 + 40 + 49 + 57 + 53 + 44 + 52 + 53 + 48 + 41 + 39 + 44 + 32 + 39 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 39 + 44 + 32 + 108 + 105 + 115 + 116 + 40 + 91 + 39 + 117 + 112 + 100 + 97 + 116 + 101 + 70 + 114 + 101 + 113 + 117 + 101 + 110 + 99 + 121 + 39 + 44 + 39 + 53 + 48 + 39 + 93 + 41 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 83 + 101 + 110 + 100 + 80 + 97 + 99 + 107 + 101 + 116 + 73 + 110 + 99 + 108 + 83 + 117 + 98 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 44 + 32 + 83 + 105 + 103 + 110 + 97 + 108 + 61 + 115 + 108 + 105 + 100 + 101 + 114 + 86 + 97 + 108 + 117 + 101 + 44 + 32 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 61 + 49 + 44 + 32 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 61 + 79 + 82 + 84 + 68 + 46 + 68 + 65 + 84 + 65 + 84 + 89 + 80 + 69 + 95 + 70 + 76 + 79 + 65 + 84 + 44 + 32 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 61 + 39 + 76 + 67 + 68 + 86 + 97 + 108 + 39 + 44 + 32 + 76 + 67 + 68 + 44 + 32 + 39 + 83 + 111 + 117 + 114 + 99 + 101 + 71 + 114 + 111 + 117 + 112 + 48 + 39 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 102 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 99 + 111 + 109 + 109 + 117 + 110 + 105 + 99 + 97 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 116 + 101 + 114 + 102 + 97 + 99 + 101 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 80 + 70 + 95 + 70 + 105 + 110 + 97 + 108 + 105 + 115 + 101 + 40 + 115 + 105 + 109 + 44 + 32 + 80 + 97 + 99 + 107 + 101 + 116 + 70 + 114 + 97 + 109 + 101 + 119 + 111 + 114 + 107 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 115 + 108 + 105 + 100 + 101 + 114 + 86 + 97 + 108 + 117 + 101 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 61 + 32 + 71 + 111 + 84 + 111 + 67 + 104 + 111 + 111 + 115 + 101 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 99 + 104 + 111 + 115 + 101 + 32 + 116 + 104 + 101 + 32 + 110 + 101 + 120 + 116 + 32 + 115 + 116 + 97 + 116 + 101 + 32 + 116 + 111 + 32 + 101 + 110 + 116 + 101 + 114 + 32 + 119 + 104 + 101 + 110 + 32 + 116 + 104 + 101 + 32 + 100 + 101 + 109 + 111 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 104 + 97 + 115 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 117 + 115 + 101 + 114 + 100 + 97 + 116 + 97 + 46 + 83 + 116 + 97 + 116 + 101 + 32 + 61 + 32 + 39 + 99 + 104 + 111 + 111 + 115 + 101 + 69 + 120 + 112 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 99 + 97 + 115 + 101 + 32 + 39 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 39 + 59 + 32 + 47 + 47 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 45 + 32 + 110 + 111 + 116 + 104 + 105 + 110 + 103 + 32 + 116 + 111 + 32 + 100 + 111 + 32 + 116 + 104 + 101 + 110 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 105 + 110 + 49 + 32 + 61 + 32 + 105 + 110 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 115 + 105 + 109 + 32 + 61 + 32 + 108 + 100 + 95 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 105 + 110 + 49 + 44 + 32 + 39 + 67 + 97 + 115 + 101 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 39 + 44 + 32 + 49 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 101 + 118 + 44 + 32 + 48 + 41 + 59 + 47 + 47 + 32 + 110 + 101 + 118 + 101 + 114 + 32 + 102 + 105 + 110 + 105 + 115 + 104 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 53 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 47 + 47 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 61 + 32 + 37 + 116 + 10 + 32 + 32 + 10 + 32 + 32 + 47 + 47 + 32 + 87 + 104 + 101 + 110 + 32 + 82 + 84 + 109 + 97 + 105 + 110 + 46 + 115 + 99 + 101 + 32 + 105 + 115 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 101 + 100 + 44 + 32 + 116 + 104 + 105 + 115 + 32 + 112 + 97 + 114 + 116 + 32 + 119 + 105 + 108 + 108 + 32 + 98 + 101 + 32 + 114 + 117 + 110 + 46 + 32 + 73 + 116 + 32 + 109 + 97 + 121 + 32 + 98 + 101 + 32 + 117 + 115 + 101 + 100 + 32 + 116 + 111 + 32 + 100 + 101 + 102 + 105 + 110 + 101 + 32 + 97 + 110 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 105 + 110 + 32 + 97 + 100 + 118 + 97 + 110 + 99 + 101 + 32 + 116 + 111 + 10 + 32 + 32 + 47 + 47 + 32 + 116 + 104 + 101 + 32 + 101 + 120 + 101 + 99 + 117 + 116 + 105 + 111 + 110 + 32 + 111 + 102 + 32 + 116 + 104 + 101 + 32 + 119 + 104 + 111 + 108 + 101 + 32 + 99 + 111 + 110 + 116 + 114 + 111 + 108 + 32 + 115 + 121 + 115 + 116 + 101 + 109 + 46 + 10 + 32 + 32 + 105 + 102 + 32 + 67 + 97 + 108 + 108 + 101 + 100 + 79 + 110 + 108 + 105 + 110 + 101 + 32 + 61 + 61 + 32 + 37 + 102 + 32 + 116 + 104 + 101 + 110 + 32 + 10 + 32 + 32 + 32 + 32 + 83 + 99 + 104 + 101 + 109 + 97 + 116 + 105 + 99 + 73 + 110 + 102 + 111 + 32 + 61 + 32 + 39 + 79 + 102 + 102 + 45 + 108 + 105 + 110 + 101 + 32 + 99 + 111 + 109 + 112 + 105 + 108 + 101 + 100 + 39 + 59 + 10 + 32 + 32 + 10 + 32 + 32 + 32 + 32 + 47 + 47 + 32 + 100 + 101 + 102 + 97 + 117 + 108 + 116 + 32 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 40 + 100 + 117 + 109 + 109 + 121 + 41 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 111 + 117 + 116 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 99 + 111 + 110 + 115 + 116 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 48 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 111 + 117 + 116 + 108 + 105 + 115 + 116 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 111 + 117 + 116 + 41 + 59 + 10 + 32 + 32 + 32 + 32 + 91 + 115 + 105 + 109 + 44 + 102 + 105 + 110 + 105 + 115 + 104 + 101 + 100 + 93 + 32 + 61 + 32 + 108 + 100 + 95 + 115 + 116 + 101 + 112 + 115 + 50 + 40 + 115 + 105 + 109 + 44 + 32 + 48 + 44 + 32 + 97 + 99 + 116 + 105 + 118 + 97 + 116 + 105 + 111 + 110 + 95 + 115 + 105 + 109 + 115 + 116 + 101 + 112 + 115 + 61 + 49 + 48 + 44 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 61 + 91 + 48 + 44 + 49 + 93 + 41 + 59 + 10 + 32 + 32 + 101 + 110 + 100 + 44 + 10 + 32 + 32 + 10 + 101 + 110 + 100 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 10 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 118 + 101 + 99 + 115 + 105 + 122 + 101 + 61 + 49 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 118 + 101 + 99 + 115 + 105 + 122 + 101 + 61 + 50 + 48 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 49 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 110 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 50 + 53 + 55 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 115 + 105 + 122 + 101 + 115 + 61 + 91 + 49 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 111 + 117 + 116 + 116 + 121 + 112 + 101 + 115 + 61 + 91 + 50 + 53 + 55 + 93 + 59 + 10 + 99 + 102 + 112 + 97 + 114 + 46 + 105 + 100 + 101 + 110 + 116 + 95 + 115 + 116 + 114 + 61 + 39 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 39 + 32 + 10 + 59 + 10 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 52 + 41 + 59 + 10 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 115 + 99 + 105 + 108 + 97 + 98 + 32 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 97 + 108 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 105 + 110 + 105 + 116 + 105 + 97 + 108 + 105 + 115 + 101 + 100 + 92 + 110 + 39 + 41 + 59 + 32 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 112 + 116 + 114 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 112 + 116 + 114 + 32 + 61 + 32 + 108 + 105 + 115 + 116 + 40 + 49 + 41 + 59 + 10 + 98 + 108 + 111 + 99 + 107 + 46 + 105 + 110 + 112 + 116 + 114 + 40 + 49 + 41 + 32 + 61 + 32 + 115 + 99 + 105 + 108 + 97 + 98 + 95 + 105 + 110 + 116 + 101 + 114 + 102 + 46 + 105 + 110 + 118 + 101 + 99 + 49 + 59 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 49 + 41 + 59 + 32 + 115 + 99 + 105 + 108 + 97 + 98 + 95 + 105 + 110 + 116 + 101 + 114 + 102 + 46 + 111 + 117 + 116 + 118 + 101 + 99 + 49 + 32 + 61 + 32 + 98 + 108 + 111 + 99 + 107 + 46 + 111 + 117 + 116 + 112 + 116 + 114 + 40 + 49 + 41 + 59 + 32 + 10 + 10 + 98 + 108 + 111 + 99 + 107 + 61 + 99 + 111 + 109 + 112 + 95 + 102 + 110 + 40 + 98 + 108 + 111 + 99 + 107 + 44 + 32 + 53 + 41 + 59 + 10 + 112 + 114 + 105 + 110 + 116 + 102 + 40 + 39 + 115 + 99 + 105 + 108 + 97 + 98 + 32 + 99 + 111 + 109 + 112 + 117 + 116 + 97 + 116 + 105 + 111 + 110 + 97 + 108 + 32 + 102 + 117 + 110 + 99 + 116 + 105 + 111 + 110 + 32 + 100 + 101 + 115 + 116 + 114 + 117 + 99 + 116 + 101 + 100 + 92 + 110 + 39 + 41 + 59 + 32 + 10 + 99 + 108 + 101 + 97 + 114 + 32 + 98 + 108 + 111 + 99 + 107 + 59 + 10 + 66 + 85 + 73 + 76 + 68 + 73 + 78 + 95 + 80 + 65 + 84 + 72 + 0 + 170 + 207 + 38 + 0 + 1 + 0 + 20 + 36 + 84 + 104 + 101 + 32 + 102 + 114 + 111 + 109 + 32 + 83 + 99 + 105 + 108 + 97 + 98 + 32 + 114 + 101 + 116 + 117 + 114 + 110 + 101 + 100 + 32 + 118 + 97 + 108 + 117 + 101 + 115 + 32 + 97 + 114 + 101 + 32 + 40 + 208 + 0 + 1 + 1 + 0 + 40 + 210 + 0 + 1 + 1 + 0 + 60009 + 212 + 2 + 0 + 1 + 0 + 20 + 257 + 60009 + 214 + 2 + 0 + 1 + 0 + 20 + 257 + 40 + 216 + 0 + 1 + 1 + 0 + 15003 + 218 + 124 + 0 + 1 + 0 + 0 + 0 + 0 + 41 + 41 + 36 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 105 + 112 + 97 + 114 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 46 + 114 + 112 + 97 + 114 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 68 + 101 + 109 + 111 + 95 + 82 + 101 + 112 + 108 + 97 + 99 + 101 + 97 + 98 + 108 + 101 + 83 + 105 + 109 + 117 + 108 + 97 + 116 + 105 + 111 + 110 + 0 + 10 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 205 + 205 + 0 + 0 + 207 + 207 + 0 + 0 + 205 + 205 + 0 + 0 + 212 + 212 + 0 + 0 + 208 + 208 + 0 + 0 + 212 + 212 + 1 + 0 + 205 + 205 + 0 + 0 + 214 + 214 + 0 + 0 + 210 + 210 + 0 + 0 + 214 + 214 + 1 + 0 + 212 + 212 + 0 + 0 + 218 + 218 + 0 + 0 + 216 + 216 + 0 + 0 + 218 + 218 + 1 + 0 + 214 + 214 + 0 + 1 + 0 + 0 + 0 + 4 + 60032 + 208 + 0 + 0 + 1 + 0 + 60006 + 210 + 1 + 0 + 1 + 0 + 2 + 60002 + 212 + 2 + 0 + 1 + 0 + 2 + 0 + 60014 + 215 + 0 + 0 + 1 + 0 + 40 + 217 + 0 + 1 + 1 + 0 + 170 + 219 + 18 + 0 + 1 + 0 + 1 + 16 + 99 + 97 + 108 + 99 + 117 + 108 + 97 + 116 + 105 + 110 + 103 + 32 + 46 + 46 + 46 + 32 + 40 + 220 + 0 + 1 + 1 + 0 + 40 + 222 + 0 + 1 + 1 + 0 + 60020 + 224 + 0 + 1 + 1 + 0 + 60020 + 226 + 0 + 1 + 1 + 0 + 60020 + 228 + 0 + 1 + 1 + 0 + 60020 + 230 + 0 + 1 + 1 + 0 + 0 + 17 + 8 + 4 + 0 + 203 + 203 + 0 + 0 + 205 + 205 + 0 + 0 + 205 + 205 + 0 + 0 + 208 + 208 + 0 + 0 + 208 + 208 + 0 + 0 + 210 + 210 + 0 + 0 + 210 + 210 + 0 + 0 + 212 + 212 + 0 + 0 + 205 + 205 + 1 + 0 + 215 + 215 + 0 + 0 + 217 + 217 + 0 + 0 + 219 + 219 + 0 + 0 + 212 + 212 + 0 + 0 + 224 + 224 + 0 + 0 + 222 + 222 + 0 + 0 + 224 + 224 + 1 + 0 + 212 + 212 + 1 + 0 + 226 + 226 + 0 + 0 + 224 + 224 + 0 + 0 + 226 + 226 + 1 + 0 + 215 + 215 + 0 + 0 + 228 + 228 + 0 + 0 + 226 + 226 + 0 + 0 + 228 + 228 + 1 + 0 + 217 + 217 + 0 + 0 + 230 + 230 + 0 + 0 + 228 + 228 + 0 + 0 + 230 + 230 + 1 + 0 + 220 + 220 + 0 + 1 + 0 + 0 + 0 + 0 + 230 + 230 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 2 + 1 + 3 + 203 + 100 + 0 + 0 + 8 + 1 + 205 + 100 + 8 + 1 + 6 + 1 + 100 + 101 + 14 + 2 + 28 + 0 + 60010 + 203 + 2 + 1 + 1 + 0 + 1 + 0 + 40 + 205 + 0 + 1 + 1 + 0 + 0 + 3 + 8 + 4 + 0 + 203 + 203 + 0 + 1 + 0 + 0 + 0 + 0 + 205 + 205 + 0 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 1 + 1 + 0 + 0 + 2 + 170 + 212 + 9 + 0 + 1 + 0 + 1 + 7 + 111 + 117 + 116 + 112 + 117 + 116 + 32 + 0 + 4 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 + 0 + 0 + 0 + 0 + 2 + 207 + 207 + 0 + 0 + 205 + 205 + 0 + 0 + 208 + 208 + 0 + 0 + 208 + 208 + 0 + 0 + 212 + 212 + 0 + 27 + 0 + 1 + 8 + 4 + 0 + 201 + 201 + 0 + 0 + 203 + 203 + 0 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.rpar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.rpar new file mode 100644 index 00000000..5dfb8615 --- /dev/null +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.rpar @@ -0,0 +1,27 @@ +0.0500000000000000027755576 +0.4320999999999999841016063 +0.0000000000000000000000000 +1.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +0.0000000000000000000000000 +2.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +2.0000000000000000000000000 +2.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +1.0000000000000000000000000 +3.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 +0.0000000000000000000000000 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.sce b/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.sce new file mode 100644 index 00000000..16a7e925 --- /dev/null +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.sce @@ -0,0 +1,407 @@ +// +// Copyright (C) 2011, 2012, 2013, 2014, 2015 Christian Klauer +// +// This file is part of OpenRTDynamics, the Real-Time Dynamics Framework +// +// OpenRTDynamics is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// OpenRTDynamics is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with OpenRTDynamics. If not, see . +// + + + +// +// +// Example for a changing PaPI-configuration, which is given by the ORTD-program. +// The ORTD-program has several states, one to choose between two experiments, +// one which evaluates the selection and two states with the different experiments. +// In the first experiment an oscillator-system is given with a button to disturb it. +// The second experiment shows a slider and the adjusted value is sent to the ORTD and +// from there sent back to a LCD and a progress bar in PaPI. There is an additional +// finish-state, which is normally not possible to be used. +// +// Rev 1 +// + + +funcprot(0); +// The name of the program +ProgramName = 'SwitchingPaPiConfig'; // must be the filename without .sce + + +function [sim, outlist] = AutoConfigExample(sim, Signal) + + function [sim, finished, outlist, userdata] = ExperimentCntrl(sim, ev, inlist, userdata, CalledOnline) + + // Define parameters. They must be defined once again at this place, because this will also be called at + // runtime. + NSamples=300; + + if CalledOnline == %t then + // The contents of this part will be compiled on-line, while the control + // system is running. The aim is to generate a new compiled schematic for + // the experiment. + // Please note: Since this code is only executed on-line, most potential errors + // occuring in this part become only visible during runtime. + + printf("Compiling a new control system\n"); + + funcprot(0); + z = poly(0,'z'); + // And example-system that is controlled via UDP and one step further with the Web-gui + // Superblock: A more complex oscillator with damping + function [sim, x,v] = damped_oscillator(sim, u, T_a) + // create feedback signals + [sim,x_feedback] = libdyn_new_feedback(sim); + + [sim,v_feedback] = libdyn_new_feedback(sim); + + // use this as a normal signal + [sim,a] = ld_add(sim, ev, list(u, x_feedback), [1, -1]); + [sim,a] = ld_add(sim, ev, list(a, v_feedback), [1, -1]); + + [sim,v] = ld_ztf(sim, ev, a, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,v_gain] = ld_gain(sim, ev, v, 0.1); + + // close loop v_gain = v_feedback + [sim] = libdyn_close_loop(sim, v_gain, v_feedback); + + + [sim,x] = ld_ztf(sim, ev, v, 1/(z-1) * T_a ); // Integrator approximation + + // feedback gain + [sim,x_gain] = ld_gain(sim, ev, x, 0.6); + + // close loop x_gain = x_feedback + [sim] = libdyn_close_loop(sim, x_gain, x_feedback); + endfunction + + if userdata.isInitialised == %f then + // + // State variables can be initialise at this place + // + userdata.Acounter = 0; + userdata.State = "chooseExp"; + + userdata.isInitialised = %t; // prevent from initialising the variables once again + end + + // + // Example for a state update: increase the counter + // + userdata.Acounter = userdata.Acounter + 1; + + // Build an info-string + SchematicInfo = "On-line compiled in iteration #" + string(userdata.Acounter); + + // + // Define a new experiment controller schematic depending on the currently active state + // + + // initalise the PaPi UDP Socket + Configuration.UnderlyingProtocoll = "UDP"; + Configuration.DestHost = "127.0.0.1"; + Configuration.DestPort = 20000; + Configuration.LocalSocketHost = "127.0.0.1"; + Configuration.LocalSocketPort = 20001; + PacketFramework.Configuration.debugmode = %t; + [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="SwitchingAutoConfig", Configuration); + + [sim, zero] = ld_const(sim, ev, 0); + [sim, one] = ld_const(sim, 0, 1); + + // default output (dummy) + outlist=list(zero); + + // + // Here a state-machine is implemented that may be used to implement some automation + // logic that is executed during runtime using the embedded Scilab interpreter. + // In this example, a calibration run succeeded by the design/compilation/execution + // of a control-system is implemented. The schematics defined in each state are loaded + // at runtime. + // + select userdata.State + case "chooseExp" + // Add some parameters to choose the next state + [PacketFramework, OscillatorButton] = ld_PF_addpluginAdvanced(PacketFramework, "Button", "Oscillator", "(250,100)", "(250,100)", "PaPI-Tab", list(["state1_text","Go to"], ["state2_text","Leaving to"])); + [sim, PacketFramework, GoToOscillator]=ld_PF_ParameterInclControl(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Osci", OscillatorButton, 'Click_Event'); + + [PacketFramework, SliderLCDprogressbarButton] = ld_PF_addpluginAdvanced(PacketFramework, "Button", "Slider LCD ProgressBar", "(250,100)", "(250,250)", "PaPI-Tab", list(["state1_text","Go to"], ["state2_text","Leaving to"])); + [sim, PacketFramework, GoToSliderLCDProgress]=ld_PF_ParameterInclControl(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="SliderLCDProgress", SliderLCDprogressbarButton, 'Click_Event'); + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); + + // finish if next state is chosen + [sim, expState] = ld_add(sim, ev, list(GoToOscillator, GoToSliderLCDProgress), [1,2]); + // write the next state on the first position of the global memory + [sim] = ld_write_global_memory(sim, ev, data=expState, index=one, ... + ident_str="NextStateData", datatype=ORTD.DATATYPE_FLOAT, ... + ElementsToWrite=1); + + [sim, finished] = ld_and(sim, ev, list(expState, one)); + + outlist=list(expState); + [sim] = ld_printf(sim, ev, expState, "Case choose experiment active. Next Experiment is (0 means no change):", 1); + // chose the next state to enter when the demo experiment has finished + userdata.State = "choosedExp"; + + case "choosedExp" + printf("Got the following chosen experiment for iteration %d:\n", userdata.Acounter); disp(userdata.InputData(1)); + [sim, nextExp] = ld_const(sim, ev, userdata.InputData(1)); + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); + + finished = one; // finish directly + + // chose the next state to enter when the demo experiment has finished + select userdata.InputData(1) + case 1 + userdata.State = "oscillator"; + case 2 + userdata.State = "sliderLCDprogressbar"; + else + userdata.State = "finished"; + end + + outlist=list(nextExp); + [sim] = ld_printf(sim, ev, nextExp, "Case choosed experiment active. Next Experiment is "+ userdata.State, 1); + + case "oscillator" // define an experiment to perform an oscillator experiment + in1 = inlist(1); + [sim] = ld_printf(sim, 0, in1, "Case oscillator active: ", 1); + + // Add a parameter for controlling the oscillator + [PacketFramework, DisturbanceButton] = ld_PF_addpluginAdvanced(PacketFramework, "Button", "Disturbance", "(150,50)", "(600,100)", "PaPI-Tab", list(["state1_text","Disturb"], ["state2_text","Disturbing"])); + [sim, PacketFramework, Input]=ld_PF_ParameterInclControl(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Oscillator input", DisturbanceButton, 'Click_Event'); + + // Add a button parameter to go to the experiment selection dialog + [PacketFramework, OkButton] = ld_PF_addpluginAdvanced(PacketFramework, "Button", "Leave", "(150,50)", "(600,325)", "PaPI-Tab", list(["state1_text","Ok"], ["state2_text","Leaving"])); + [sim, PacketFramework, GoToChoose]=ld_PF_ParameterInclControl(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="ok", OkButton, 'Click_Event'); + + // printf the parameter + [sim] = ld_printf(sim, ev, Input, "Oscillator input ", 1); + + // The system to control + [sim, Input] = ld_add_ofs(sim, 0, Input, 0.2); + T_a = 0.1; [sim, x,v] = damped_oscillator(sim, Input, T_a); + + // Stream the data of the oscillator + [PacketFramework, PlotXY] = ld_PF_addpluginAdvanced(PacketFramework, "Plot", "Plot XV", "(500,500)", "(0,0)", "PaPI-Tab", list(["yRange","[-10.0 10.0]"])); + [sim, PacketFramework]=ld_SendPacketInclSub(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X", PlotXY, 'SourceGroup0'); + [sim, PacketFramework]=ld_SendPacketInclSub(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V", PlotXY, 'SourceGroup0'); + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); + + outlist=list(x); + + finished = GoToChoose; + + // chose the next state to enter when the demo experiment has finished + userdata.State = "chooseExp"; + + case "sliderLCDprogressbar" // design a constant signal to send to PaPi + in1 = inlist(1); + [sim] = ld_printf(sim, 0, in1, "Case sliderLCDprogressbar active: ", 1); + + // Add a button parameter to go to the experiment selection dialog + [PacketFramework, OkButton] = ld_PF_addpluginAdvanced(PacketFramework, "Button", "Leave", "(150,50)", "(600,225)", "PaPI-Tab", list(["state1_text","Ok"], ["state2_text","Leaving"])); + [sim, PacketFramework, GoToChoose]=ld_PF_ParameterInclControl(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="ok", OkButton, 'Click_Event'); + + // Add a slider + [PacketFramework, Slider] = ld_PF_addpluginAdvanced(PacketFramework, "Slider", "Slider Example", "(500,75)", "(50,50)", "PaPI-Tab", list(["step_count","101"], ["lower_bound","0.0"], ["upper_bound","1.0"])); + [sim, PacketFramework, sliderValue]=ld_PF_ParameterInclControl(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="sliderVal", Slider, 'SliderBlock'); + + // show the slider value on a progress bar + [sim, percSliderValue] = ld_gain(sim, ev, sliderValue, 100); + [PacketFramework, Pbar] = ld_PF_addpluginAdvanced(PacketFramework, "ProgressBar", "ProgressBar Example", "(500,75)", "(50,250)", "PaPI-Tab", list()); + [sim, PacketFramework]=ld_SendPacketInclSub(sim, PacketFramework, Signal=percSliderValue, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="percent", Pbar, 'SourceGroup0'); + + // show the slider value on a lcd display + [PacketFramework, LCD] = ld_PF_addpluginAdvanced(PacketFramework, "LCDDisplay", "LCD Example", "(210,120)", "(195,450)", "PaPI-Tab", list(["updateFrequency","50"])); + [sim, PacketFramework]=ld_SendPacketInclSub(sim, PacketFramework, Signal=sliderValue, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="LCDVal", LCD, 'SourceGroup0'); + + // finalise the communication interface + [sim,PacketFramework] = ld_PF_Finalise(sim, PacketFramework); + + outlist=list(sliderValue); + + finished = GoToChoose; + + // chose the next state to enter when the demo experiment has finished + userdata.State = "chooseExp"; + + case "finished" // experiment finished - nothing to do + in1 = inlist(1); + [sim] = ld_printf(sim, ev, in1, "Case finished active" , 1); + [sim, finished] = ld_const(sim, ev, 0); // never finish + [sim, out] = ld_const(sim, 0, 5); + outlist=list(out); + + end + end // CalledOnline == %t + + // When RTmain.sce is executed, this part will be run. It may be used to define an initial experiment in advance to + // the execution of the whole control system. + if CalledOnline == %f then + SchematicInfo = "Off-line compiled"; + + // default output (dummy) + [sim, out] = ld_const(sim, 0, 0); + outlist=list(out); + [sim, finished] = ld_steps2(sim, 0, activation_simsteps=10, values=[0,1] ); + end + + endfunction + + + + + function [sim, outlist, HoldState, userdata] = whileComputing_example(sim, ev, inlist, CalibrationReturnVal, computation_finished, par); + + [sim, HoldState] = ld_const(sim, 0, 0); + + [sim] = ld_printf(sim, 0, HoldState, "calculating ... " , 1); + + // While the computation is running this is called regularly + [sim, out] = ld_const(sim, ev, 0); + outlist=list(out); + endfunction + + + function [sim, ToScilab, userdata] = PreScilabRun(sim, ev, par) + userdata = par.userdata; + + // get the stored sensor data + [sim, readI] = ld_const(sim, 0, 1); // start at index 1 + [sim, ToScilab] = ld_read_global_memory(sim, 0, index=readI, ident_str="NextStateData", ... + datatype=ORTD.DATATYPE_FLOAT, ... + ElementsToRead=1); + //[sim, ToScilab] = ld_const(sim, ev, 0); + endfunction + + + // initialise a global memory for storing the next state + [sim] = ld_global_memory(sim, ev, ident_str="NextStateData", ... + datatype=ORTD.DATATYPE_FLOAT, len=1, ... + initial_data=[zeros(1,1)], ... + visibility='global', useMutex=1); + + // Start the experiment + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; + ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; + + insizes=[1]; outsizes=[1]; + intypes=[ORTD.DATATYPE_FLOAT]; outtypes=[ORTD.DATATYPE_FLOAT]; + + + CallbackFns.experiment = ExperimentCntrl; + CallbackFns.whileComputing = whileComputing_example; + CallbackFns.PreScilabRun = PreScilabRun; + + // Please note ident_str must be unique. + userdata = []; + [sim, finished, outlist, userdata] = ld_AutoOnlineExch_dev(sim, 0, inlist=list(Signal), ... + insizes, outsizes, intypes, outtypes, ... + ThreadPrioStruct, CallbackFns, ident_str="AutoConfigDemo", userdata); + + PacketFramework = userdata(1); +// [sim] = ld_printf(sim, 0, finished, "State ", 1); + +endfunction + + +// The main real-time thread +function [sim, outlist, userdata] = Thread_MainRT(sim, inlist, userdata) + // This will run in a thread + [sim, Tpause] = ld_const(sim, ev, 1/20); // The sampling time that is constant at 20 Hz in this example + [sim, out] = ld_ClockSync(sim, ev, in=Tpause); // synchronise this simulation + + // + // Add you own control system here + // + + // some dummy input to the state machine + [sim,Signal] = ld_const(sim, 0, 0.4321); + + [sim, outlist] = AutoConfigExample(sim, Signal); + + [sim] = ld_printf(sim, 0, outlist(1) , "output ", 1); + + + outlist = list(); +endfunction + +// This is the main top level schematic +function [sim, outlist] = schematic_fn(sim, inlist) + +// +// Create a thread that runs the control system +// + + ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; // or ORTD.ORTD_RT_REALTIMETASK + ThreadPrioStruct.prio2=0; // for ORTD.ORTD_RT_REALTIMETASK: 1-99 as described in man sched_setscheduler + // for ORTD.ORTD_RT_NORMALTASK this is the nice-value (higher value means less priority) + ThreadPrioStruct.cpu = -1; // The CPU on which the thread will run; -1 dynamically assigns to a CPU, + // counting of the CPUs starts at 0 + + [sim, StartThread] = ld_initimpuls(sim, ev); // triggers your computation only once + [sim, outlist, computation_finished] = ld_async_simulation(sim, ev, ... + inlist=list(), ... + insizes=[], outsizes=[], ... + intypes=[], outtypes=[], ... + nested_fn = Thread_MainRT, ... + TriggerSignal=StartThread, name="MainRealtimeThread", ... + ThreadPrioStruct, userdata=list() ); + + + // output of schematic (empty) + outlist = list(); +endfunction + + + + + + + + + + +// +// Set-up (no detailed understanding necessary) +// + +thispath = get_absolute_file_path(ProgramName+'.sce'); +cd(thispath); +z = poly(0,'z'); + +// defile ev +ev = [0]; // main event + +// set-up schematic by calling the user defined function "schematic_fn" +insizes = []; outsizes=[]; +[sim_container_irpar, sim]=libdyn_setup_schematic(schematic_fn, insizes, outsizes); + +// pack the simulation into a irpar container +parlist = new_irparam_set(); +parlist = new_irparam_container(parlist, sim_container_irpar, 901); // pack simulations into irpar container with id = 901 +par = combine_irparam(parlist); // complete irparam set +save_irparam(par, ProgramName+'.ipar', ProgramName+'.rpar'); // Save the schematic to disk + +// clear +par.ipar = []; par.rpar = []; + diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/run_switchingPaPiConfig.sh b/data_sources/ORTD/DataSourceChangingAutoConfigExample/run_switchingPaPiConfig.sh new file mode 100755 index 00000000..f830cb5f --- /dev/null +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/run_switchingPaPiConfig.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +#ortd --baserate=10 --rtmode 1 -s SwitchingPaPiConfig -i 901 -l 0 +ortdrun -s SwitchingPaPiConfig diff --git a/data_sources/ORTD/DataSourceExample_Groups/PF.sci b/data_sources/ORTD/DataSourceExample_Groups/PF.sci deleted file mode 100644 index cff07675..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/PF.sci +++ /dev/null @@ -1,595 +0,0 @@ - - - - -// -// -// A packet based communication interface from ORTD using UDP datagrams to e.g. -// nodejs. -// webappUDP.js is the counterpart that provides a web-interface -// -// Current Rev: 10 -// -// Revisions: -// -// 27.3.14 - possibility to reservate sources -// 3.4.14 - small re-arrangements -// 4.4.14 - Bugfixes -// 7.4.14 - Bugfix -// 12.6.14 - Bugfix -// 2.11.14 - Added group finalising packet -// 12.1.14 - Added Support for Groups -// 27.1.14 - Added the possibility to request the protocoll configuration via network -// - - -function [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName) - SourceID = PacketFramework.SourceID_counter; - - Source.SourceName = SourceName; - Source.SourceID = SourceID; - Source.NValues_send = NValues_send; - Source.datatype = datatype; - Source.Group = PacketFramework.ActiveGroup; - - // Add new source to the list - PacketFramework.Sources($+1) = Source; - - // inc counter - PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; -endfunction - -function [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName) - ParameterID = PacketFramework.Parameterid_counter; - - Parameter.ParameterName = ParameterName; - Parameter.ParameterID = ParameterID; - Parameter.NValues = NValues; - Parameter.datatype = datatype; - Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; - - // Add new source to the list - PacketFramework.Parameters($+1) = Parameter; - - // inc counters - PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; - PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; - - // return values - ParameterID = Parameter.ParameterID; - MemoryOfs = Parameter.MemoryOfs; -endfunction - -function [sim, PacketFramework, Parameter] = ld_PF_Parameter(sim, PacketFramework, NValues, datatype, ParameterName) // PARSEDOCU_BLOCK - // - // Define a parameter - // - // NValues - amount of data sets - // datatype - only ORTD.DATATYPE_FLOAT for now - // ParameterName - a unique string decribing the parameter - // - // - // - - [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); - - // read data from global memory - [sim, readI] = ld_const(sim, 0, MemoryOfs); // start at index 1 - [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... - datatype, NValues); -endfunction - - - - -// Send a signal via UDP, a simple protocoll is defined, internal function -function [sim] = ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID) - InstanceName = PacketFramework.InstanceName; - [sim,one] = ld_const(sim, 0, 1); - - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - - // Source ID - [sim, SourceID] = ld_const(sim, 0, SourceID); - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); - - printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); - -endfunction - - - - -function [sim, PacketFramework] = ld_SendPacket(sim, PacketFramework, Signal, NValues_send, datatype, SourceName) // PARSEDOCU_BLOCK // PARSEDOCU_BLOCK - // - // Stream data - block - // - // Signal - the signal to stream - // NValues_send - the vector length of Signal - // datatype - only ORTD.DATATYPE_FLOAT by now - // SourceName - a unique string identifier descring the stream - // - // - // - - [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); - [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); -endfunction - - - - -function [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName, Configuration) // PARSEDOCU_BLOCK - // - // Initialise an instance of the Packet Framework - // - // InstanceName - a unique string identifier for the instance - // Configuration must include the following properties: - // - // Configuration.UnderlyingProtocoll = "UDP" - // Configuration.DestHost - // Configuration.DestPort - // Configuration.LocalSocketHost - // Configuration.LocalSocketPort - // Configuration.SenderId (optional) - // Configuration.debugmode (bool, optional) - // - // - // Example: - // - // - // Configuration.UnderlyingProtocoll = "UDP"; - // Configuration.DestHost = "127.0.0.1"; - // Configuration.DestPort = 20000; - // Configuration.LocalSocketHost = "127.0.0.1"; - // Configuration.LocalSocketPort = 20001; - // Configuration.SenderId = 12; - // Configuration.debugmode = %f; - // [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); - // - // - // - // Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations - // - // - - // initialise structure for sources - PacketFramework.InstanceName = InstanceName; - PacketFramework.Configuration = Configuration; - - PacketFramework.Configuration.debugmode = %F; - try - PacketFramework.Configuration.debugmode = Configuration.debugmode; - catch - null; - end - - // disp(Configuration.UnderlyingProtocoll) - - if Configuration.UnderlyingProtocoll == 'UDP' - null; - else - error("PacketFramework: Only UDP supported up to now"); - end - - // possible packet sizes for UDP - PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes - PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; - - // sources - PacketFramework.SourceID_counter = 0; - PacketFramework.Sources = list(); - - // parameters - PacketFramework.Parameterid_counter = 0; - PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory - PacketFramework.Parameters = list(); - - - - PacketFramework.SenderID = 0; // default value - try - PacketFramework.SenderID = Configuration.SenderId; - catch - null; - end - - - // Groups - printf("Groups are enabled.\n"); - - DefaultGroup.ID = 0; - - PacketFramework.GroupIdCounter = 1; - PacketFramework.GroupList = list(); - PacketFramework.ActiveGroup = DefaultGroup; - -endfunction - - - -// added 23.12.14 -function [sim, PacketFramework, Group] = ld_PF_NewGroup(sim, PacketFramework, GroupName, opt) - - Group.ID = PacketFramework.GroupIdCounter; - Group.Name = GroupName; - - PacketFramework.GroupIdCounter = PacketFramework.GroupIdCounter + 1; - PacketFramework.GroupList( Group.ID ) = Group; - -endfunction - -// added 23.12.14 -function [sim, PacketFramework] = ld_PF_FinishGroup(sim, PacketFramework, Group) - [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, Group.ID); -endfunction - -// added 23.12.14 -function [sim, PacketFramework] = ld_PF_SetActiveGroup(sim, PacketFramework, Group) - PacketFramework.ActiveGroup = Group; -endfunction - - -// Send a signal via UDP, a simple protocoll is defined, internal function -function [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID) - InstanceName = PacketFramework.InstanceName; - [sim,one] = ld_const(sim, 0, 1); - - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - - // Source ID - [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - - // Group ID - [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources - [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); - - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); - - // printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); - - - // [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); -endfunction - -function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOCU_BLOCK - // - // Finalise the instance. - // - // - - // The main real-time thread - function [sim] = ld_PF_InitUDP(sim, InstanceName, ParameterMemory) - - function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) - // This will run in a thread. Each time a UDP-packet is received - // one simulation step is performed. Herein, the packet is parsed - // and the contained parameters are stored into a memory. - - // Sync the simulation to incomming UDP-packets - [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); - - - - - - // disassemble packet's structure - [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... - outsizes=[1,1,1,TotalElemetsPerPacket], ... - outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); - - - - DisAsm_ = list(); - DisAsm_(4) = DisAsm(4); - [sim, DisAsm_(1)] = ld_Int32ToFloat(sim, 0, DisAsm(1) ); - [sim, DisAsm_(2)] = ld_Int32ToFloat(sim, 0, DisAsm(2) ); - [sim, DisAsm_(3)] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); - - - [sim, memofs] = ld_ArrayInt32(sim, 0, array=ParameterMemory.MemoryOfs, in=DisAsm(3) ); - [sim, Nelements] = ld_ArrayInt32(sim, 0, array=ParameterMemory.Sizes, in=DisAsm(3) ); - - [sim, memofs_] = ld_Int32ToFloat(sim, 0, memofs ); - [sim, Nelements_] = ld_Int32ToFloat(sim, 0, Nelements ); - - if PacketFramework.Configuration.debugmode then - // print the contents of the packet - [sim] = ld_printf(sim, 0, DisAsm_(1), "DisAsm(1) (SenderID) = ", 1); - [sim] = ld_printf(sim, 0, DisAsm_(2), "DisAsm(2) (Packet Counter) = ", 1); - [sim] = ld_printf(sim, 0, DisAsm_(3), "DisAsm(3) (SourceID) = ", 1); - [sim] = ld_printf(sim, 0, DisAsm_(4), "DisAsm(4) (Signal) = ", TotalElemetsPerPacket); - - [sim] = ld_printf(sim, 0, memofs_ , "memofs = ", 1); - [sim] = ld_printf(sim, 0, memofs_ , "Nelements = ", 1); - end - - // Store the input data into a shared memory - [sim] = ld_WriteMemory2(sim, 0, data=DisAsm(4), index=memofs, ElementsToWrite=Nelements, ... - ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); - - - - // output of schematic - outlist = list(); - endfunction - - - - // start the node.js service from the subfolder webinterface - //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); - - TotalMemorySize = sum(PacketFramework.ParameterMemory.Sizes); - TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes - PacketSize = PacketFramework.PacketSize; - - // Open an UDP-Port in server mode - [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... - hostname=PacketFramework.Configuration.LocalSocketHost, ... - UDPPort=PacketFramework.Configuration.LocalSocketPort); - - // initialise a global memory for storing the input data for the computation - [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... - datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... - initial_data=[zeros(TotalMemorySize,1)], ... - visibility='global', useMutex=1); - - // Create thread for the receiver - ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; - [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step - [sim, outlist, computation_finished] = ld_async_simulation(sim, 0, ... - inlist=list(), ... - insizes=[], outsizes=[], ... - intypes=[], outtypes=[], ... - nested_fn = UDPReceiverThread, ... - TriggerSignal=startcalc, name=InstanceName+"Thread1", ... - ThreadPrioStruct, userdata=list() ); - - - endfunction - - - - - - // calc memory - MemoryOfs = []; - Sizes = []; - // go through all parameters and create memories for all - for i=1:length(PacketFramework.Parameters) - P = PacketFramework.Parameters(i); - - Sizes = [Sizes; P.NValues]; - MemoryOfs = [MemoryOfs; P.MemoryOfs]; - end - - PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; - PacketFramework.ParameterMemory.Sizes = Sizes; - - // udp - [sim] = ld_PF_InitUDP(sim, PacketFramework.InstanceName, PacketFramework.ParameterMemory); - - // Send to group update notifications for each group (currently only one possible) - [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); - -endfunction - -function ld_PF_Export_js(PacketFramework, fname) // PARSEDOCU_BLOCK - // - // Export configuration of the defined protocoll (Sources, Parameters) - // into JSON-format. This is to be used by software that shall communicate - // to the real-time system. - // - // fname - The file name - // - // - - - - // TODO: Export Groups - - fd = mopen(fname,'wt'); - - - - mfprintf(fd,' { \n'); - - mfprintf(fd,' ""SenderID"" : ""%s"", \n', string(PacketFramework.SenderID) ); - - - - mfprintf(fd,' ""SourcesConfig"" : {\n'); - - for i=1:length(PacketFramework.Sources) - - - SourceID = PacketFramework.Sources(i).SourceID; - SourceName = PacketFramework.Sources(i).SourceName; - disp(SourceID ); - disp( SourceName ); - - - line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"", ""GroupID"" : ""%s"" } \n", ... - string(PacketFramework.Sources(i).SourceID), ... - string(PacketFramework.Sources(i).SourceName), ... - string(PacketFramework.Sources(i).NValues_send), ... - string(PacketFramework.Sources(i).datatype), string(PacketFramework.Sources(i).Group.ID) ); - - - - if i==length(PacketFramework.Sources) - // finalise the last entry without "," - printf('%s \n' , line); - mfprintf(fd,'%s', line); - else - printf('%s, \n' , line); - mfprintf(fd,'%s,', line); - end - - - end - - - - - mfprintf(fd,'} , \n '); - - - // Section ParametersConfig - mfprintf(fd,' ""ParametersConfig"" : {\n'); - - // go through all parameters and create memories for all - for i=1:length(PacketFramework.Parameters) - - printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); - - line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } \n", ... - string(PacketFramework.Parameters(i).ParameterID), ... - string(PacketFramework.Parameters(i).ParameterName), ... - string(PacketFramework.Parameters(i).NValues), ... - string(PacketFramework.Parameters(i).datatype) ); - - - - - if i==length(PacketFramework.Parameters) - // finalise the last entry without "," - printf('%s \n' , line); - mfprintf(fd,'%s', line); - else - printf('%s, \n' , line); - mfprintf(fd,'%s,', line); - end - - - end - - mfprintf(fd,' , \n'); - - // pause; - - // Section GroupsConfig - mfprintf(fd,' ""GroupsConfig"" : {\n'); - - // go through all parameters and create memories for all - for i=1:length(PacketFramework.GroupList) - - printf("export of Group %s \n",PacketFramework.GroupList(i).Name ); - - line=sprintf(" ""%s"" : { ""Name"" : ""%s"" } \n", ... - string(PacketFramework.GroupList(i).ID) , ... - string(PacketFramework.GroupList(i).Name) ... - ); - - - if i==length(PacketFramework.GroupList) - // finalise the last entry without "," - printf('%s \n' , line); - mfprintf(fd,'%s', line); - else - printf('%s, \n' , line); - mfprintf(fd,'%s,', line); - end - - - end - - mfprintf(fd,'}\n}'); - - mclose(fd); -endfunction - -// -// Added 27.3.14 -// - -function [sim, PacketFramework, SourceID]=ld_SendPacketReserve(sim, PacketFramework, NValues_send, datatype, SourceName) - [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); -endfunction - -function [sim, PacketFramework]=ld_SendPacket2(sim, PacketFramework, Signal, SourceName) - // find Sourcename - index = -1; - for i=1:length(PacketFramework.Sources) - S = PacketFramework.Sources(i); - if S.SourceName == SourceName - index = i; - printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); - break; - end - end - - if index == -1 - printf("SourceName = %s\n", SourceName); - error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); - end - - [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); -endfunction - - - -function [sim, PacketFramework, ParameterID]=ld_PF_ParameterReserve(sim, PacketFramework, NValues, datatype, ParameterName) - [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); -endfunction - - -function [sim, PacketFramework, Parameter]=ld_PF_Parameter2(sim, PacketFramework, ParameterName) - // find Sourcename - index = -1; - for i=1:length(PacketFramework.Parameters) - P = PacketFramework.Parameters(i); - if P.ParameterName == ParameterName - index = i; - printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); - break; - end - end - - if index == -1 - printf("ParameterName = %s\n", ParameterName); - error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); - end - - // read data from global memory - [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 - [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... - P.datatype, P.NValues); -endfunction - - diff --git a/data_sources/ORTD/DataSourceExample_Groups/ProtocollConfig.json b/data_sources/ORTD/DataSourceExample_Groups/ProtocollConfig.json deleted file mode 100644 index ab7b2e64..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/ProtocollConfig.json +++ /dev/null @@ -1,10 +0,0 @@ - { - "SenderID" : "1", - "SourcesConfig" : { - "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257", "GroupID" : "1" } , "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257", "GroupID" : "1" } , "2" : { "SourceName" : "Counter" , "NValues_send" : "1", "datatype" : "257", "GroupID" : "2" } } , - "ParametersConfig" : { - "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } , - "GroupsConfig" : { - "1" : { "Name" : "G1" } , "2" : { "Name" : "G2" } } -} -} \ No newline at end of file diff --git a/data_sources/ORTD/DataSourceExample_Groups/README b/data_sources/ORTD/DataSourceExample_Groups/README deleted file mode 100644 index 10c48a96..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/README +++ /dev/null @@ -1,54 +0,0 @@ -This is a demonstration on how to set-up a web-interface to a control -system implemented using OpenRTDynamics.sf.net. As you'll notice -this is possible in a few lines of code, because the usage of -Javascript for implementing the gui-part and ORTD for the -real-time part including the UDP-communication allows an efficient -formulation of the algorithms. - -UDPio.sce is a sample ORTD-simulation and webappUDP.js the nodejs program -that connects to this simulation. Further, it provides a web-interface -accessible via http://localhost:8090/mainAuto.html . - -The template for the web-interface is stored in html/mainAuto.html. - - - -The files used in this example are: - -- UDPio.sce is a sample ORTD-simulation that simulates an osicillator - and a communication interface to node.js using UDPio -- webappUDP.js is the node.js program that connects to this simulation - via an UDP-interface and provides a web-interface on port 8090. -- Different templates for the html-page are stored in html/main*.html. - -- UDPio.ipar and UDPio.rpar are the compiled ORTD-programm files. -- PacketFramework.sce The file that implements the packet framework. - This will was also integrated into ORTD at Rev. 495 - -To make this example working: - -- The installation of node.js (nodejs.org) and its package manager - npm is required. -- Then, the "socket.io" node.js-package of node.js - is required that can be installed with "npm". To do this, call the - following commmand from the directory that contains webappUDP.js: - - $ cd webinterface - $ npm install socket.io - -- To start the set-up, two services / processes are required to run - at the same time: - -the ORTD-simulation is started by running - - $ sh run_UDPio.sh - -and the node.js part by - - $ cd webinterface - $ node webappUDP.js - -The order doesn't matter and it is also possible to start / stop each -service, which is one advantage of using stateless UDP-communication. - -- Finally, point your browser to http://localhost:8090/mainAuto.html . \ No newline at end of file diff --git a/data_sources/ORTD/DataSourceExample_Groups/UDPio.rpar b/data_sources/ORTD/DataSourceExample_Groups/UDPio.rpar deleted file mode 100644 index e922d927..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/UDPio.rpar +++ /dev/null @@ -1,70 +0,0 @@ -0.0370370370370370349810685 -1.0000000000000000000000000 -2.0000000000000000000000000 -12.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -0.1000000000000000055511151 --1.0000000000000000000000000 -1.0000000000000000000000000 -0.1000000000000000055511151 -0.1000000000000000055511151 --1.0000000000000000000000000 -1.0000000000000000000000000 -0.5999999999999999777955395 -1.0000000000000000000000000 -0.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -0.0000000000000000000000000 -2.0000000000000000000000000 -0.0000000000000000000000000 -1.0000000000000000000000000 -2.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -2.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -2.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -3.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -0.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -0.0000000000000000000000000 -1.0000000000000000000000000 diff --git a/data_sources/ORTD/DataSourceExample_Groups/UDPio.sce b/data_sources/ORTD/DataSourceExample_Groups/UDPio.sce deleted file mode 100644 index e4108ce4..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/UDPio.sce +++ /dev/null @@ -1,261 +0,0 @@ -// -// -// Example for a communication interface from ORTD using UDP datagrams to e.g. -// nodejs. -// The file webinterface/webappUDP.js is the counterpart that provides a -// web-interface to control a oscillator-system in this example. -// -// For more details, please consider the readme-file. -// -// Rev 1 -// - -// The name of the program -ProgramName = 'UDPio'; // must be the filename without .sce - - - - - - - - - - - - - -// And example-system that is controlled via UDP and one step further with the Web-gui -// Superblock: A more complex oscillator with damping -function [sim, x,v] = damped_oscillator(sim, u) - // create feedback signals - [sim,x_feedback] = libdyn_new_feedback(sim); - - [sim,v_feedback] = libdyn_new_feedback(sim); - - // use this as a normal signal - [sim,a] = ld_add(sim, ev, list(u, x_feedback), [1, -1]); - [sim,a] = ld_add(sim, ev, list(a, v_feedback), [1, -1]); - - [sim,v] = ld_ztf(sim, ev, a, 1/(z-1) * T_a ); // Integrator approximation - - // feedback gain - [sim,v_gain] = ld_gain(sim, ev, v, 0.1); - - // close loop v_gain = v_feedback - [sim] = libdyn_close_loop(sim, v_gain, v_feedback); - - - [sim,x] = ld_ztf(sim, ev, v, 1/(z-1) * T_a ); // Integrator approximation - - // feedback gain - [sim,x_gain] = ld_gain(sim, ev, x, 0.6); - - // close loop x_gain = x_feedback - [sim] = libdyn_close_loop(sim, x_gain, x_feedback); -endfunction - - - - - - -function [sim, outlist, active_state, x_global_kp1, userdata] = state_mainfn(sim, inlist, x_global, state, statename, userdata) - // This function is called multiple times -- once to define each state. - // At runtime, all states will become different nested simulations of - // which only one is active a a time. Switching - // between them represents state changing, thus each simulation - // represents a certain state. - - PacketFramework = userdata(1); - - printf("Defining state %s (#%d) ...\n", statename, state); - - // define names for the first event in the simulation - events = 0; - - - // demultiplex x_global that is a state variable shared among the different states - [sim, x_global] = ld_demux(sim, events, vecsize=4, invec=x_global); - - - // The signals "active_state" is used to indicate state switching: A value > 0 means the - // the state enumed by "active_state" shall be activated in the next time step. - // A value less or equal to zero causes the statemachine to stay in its currently active - // state - - select state - case 1 // state 1 - - [sim, PacketFramework, Group2] = ld_PF_NewGroup(sim, PacketFramework, GroupName="G2", opt=list() ); - [sim, PacketFramework]=ld_PF_SetActiveGroup(sim, PacketFramework, Group2); - - [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x_global(1), NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="Counter"); - - [sim, PacketFramework]=ld_PF_FinishGroup(sim, PacketFramework, Group2); - - [sim] = ld_printf(sim, ev, x_global(1), "--- ", 1); - - // wait 10 simulation steps and then switch to state 2 - [sim, active_state] = ld_steps(sim, events, activation_simsteps=[10], values=[-1,2]); // -1 means stay within this state. 2 means go to state # 2 - [sim, x_global(1)] = ld_add_ofs(sim, events, x_global(1), 1); // increase counter 1 by 1 - case 2 // state 2 - // wait 10 simulation steps and then switch to state 3 - [sim, active_state] = ld_steps(sim, events, activation_simsteps=[10], values=[-1,3]); - [sim, x_global(2)] = ld_add_ofs(sim, events, x_global(2), 1); // increase counter 2 by 1 - case 3 // state 3 - // wait 10 simulation steps and then switch to state 1 - [sim, active_state] = ld_steps(sim, events, activation_simsteps=[10], values=[-1,1]); - [sim, x_global(3)] = ld_add_ofs(sim, events, x_global(3), 1); // increase counter 3 by 1 - end - - // multiplex the new global states - [sim, x_global_kp1] = ld_mux(sim, events, vecsize=4, inlist=x_global); - - userdata(1) = PacketFramework; - - // the user defined output signals of this nested simulation - outlist = list(); -endfunction - - -// The main real-time thread -function [sim, outlist, userdata] = Thread_MainRT(sim, inlist, userdata) - // This will run in a thread - [sim, Tpause] = ld_const(sim, ev, 1/27); // The sampling time that is constant at 20 Hz in this example - [sim, out] = ld_ClockSync(sim, ev, in=Tpause); // synchronise this simulation - - // - // Add you own control system here - // - - - Configuration.UnderlyingProtocoll = "UDP"; - Configuration.DestHost = "127.0.0.1"; - Configuration.DestPort = 20000; - Configuration.LocalSocketHost = "127.0.0.1"; - Configuration.LocalSocketPort = 20001; - Configuration.debugmode = %t; - Configuration.SenderId = 1; - - [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="RemoteControl", Configuration); - - - // Add a parameter for controlling the oscillator - [sim, PacketFramework, Input]=ld_PF_Parameter(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Oscillator input"); - - // some some more parameters - [sim, PacketFramework, AParVector]=ld_PF_Parameter(sim, PacketFramework, NValues=10, datatype=ORTD.DATATYPE_FLOAT, ParameterName="A vectorial parameter"); - [sim, PacketFramework, par2]=ld_PF_Parameter(sim, PacketFramework, NValues=2, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Test"); - - // printf these parameters - [sim] = ld_printf(sim, ev, Input, "Oscillator input ", 1); - [sim] = ld_printf(sim, ev, par2, "Test ", 2); - [sim] = ld_printf(sim, ev, AParVector, "A vectorial parameter", 10); - - - - - - // The system to control - T_a = 0.1; [sim, x,v] = damped_oscillator(sim, Input); - - - // Stream the data of the oscillator - [sim, PacketFramework, Group1] = ld_PF_NewGroup(sim, PacketFramework, GroupName="G1", opt=list() ); - [sim, PacketFramework]=ld_PF_SetActiveGroup(sim, PacketFramework, Group1); - - [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=x, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="X") - [sim, PacketFramework]=ld_SendPacket(sim, PacketFramework, Signal=v, NValues_send=1, datatype=ORTD.DATATYPE_FLOAT, SourceName="V") - - [sim, PacketFramework]=ld_PF_FinishGroup(sim, PacketFramework, Group1); - - - - - // set-up three states represented by three nested simulations - [sim, outlist, x_global, active_state,userdata] = ld_statemachine(sim, 0, ... - inlist=list( x, v ), .. - insizes=[1,1], outsizes=[], ... - intypes=[ORTD.DATATYPE_FLOAT,ORTD.DATATYPE_FLOAT ], outtypes=[], ... - nested_fn=state_mainfn, Nstates=3, state_names_list=list("state1", "state2", "state3"), ... - inittial_state=3, x0_global=[1,0,2,0], userdata=list( PacketFramework ) ); - - PacketFramework = userdata(1); - - - - // finalise the communication interface - [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework); - ld_PF_Export_js(PacketFramework, fname="ProtocollConfig.json"); - - - outlist = list(); -endfunction - - - - -// This is the main top level schematic -function [sim, outlist] = schematic_fn(sim, inlist) - -// -// Create a thread that runs the control system -// - - ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK; // or ORTD.ORTD_RT_NORMALTASK - ThreadPrioStruct.prio2=0; // for ORTD.ORTD_RT_REALTIMETASK: 1-99 as described in man sched_setscheduler - // for ORTD.ORTD_RT_NORMALTASK this is the nice-value (higher value means less priority) - ThreadPrioStruct.cpu = -1; // The CPU on which the thread will run; -1 dynamically assigns to a CPU, - // counting of the CPUs starts at 0 - - [sim, StartThread] = ld_initimpuls(sim, ev); // triggers your computation only once - [sim, outlist, computation_finished] = ld_async_simulation(sim, ev, ... - inlist=list(), ... - insizes=[], outsizes=[], ... - intypes=[], outtypes=[], ... - nested_fn = Thread_MainRT, ... - TriggerSignal=StartThread, name="MainRealtimeThread", ... - ThreadPrioStruct, userdata=list() ); - - // output of schematic (empty) - outlist = list(); -endfunction - - - - - -// -// Set-up (no detailed understanding necessary) -// - -thispath = get_absolute_file_path(ProgramName+'.sce'); -cd(thispath); -z = poly(0,'z'); - - -// The following code is integrated into ORTD since rev 494. -// Thus the following line is commented. - -//exec('webinterface/PacketFramework.sce'); -exec('PF.sci'); - - -// defile ev -ev = [0]; // main event - -// set-up schematic by calling the user defined function "schematic_fn" -insizes = []; outsizes=[]; -[sim_container_irpar, sim]=libdyn_setup_schematic(schematic_fn, insizes, outsizes); - -// pack the simulation into a irpar container -parlist = new_irparam_set(); -parlist = new_irparam_container(parlist, sim_container_irpar, 901); // pack simulations into irpar container with id = 901 -par = combine_irparam(parlist); // complete irparam set -save_irparam(par, ProgramName+'.ipar', ProgramName+'.rpar'); // Save the schematic to disk - -// clear -par.ipar = []; par.rpar = []; - - diff --git a/data_sources/ORTD/DataSourceExample_Groups/run_UDPio.sh b/data_sources/ORTD/DataSourceExample_Groups/run_UDPio.sh deleted file mode 100755 index 28def1c1..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/run_UDPio.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -#ortd --baserate=10 --rtmode 1 -s UDPio -i 901 -l 0 -ortdrun -s UDPio diff --git a/data_sources/ORTD/DataSourceExample_Groups/webinterface/PacketFramework.sce b/data_sources/ORTD/DataSourceExample_Groups/webinterface/PacketFramework.sce deleted file mode 100644 index cb51db73..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/webinterface/PacketFramework.sce +++ /dev/null @@ -1,487 +0,0 @@ - - -// -// -// A packet based communication interface from ORTD using UDP datagrams to e.g. -// nodejs. -// webappUDP.js is the counterpart that provides a web-interface -// -// Current Rev: 7 -// -// Revisions: -// -// 27.3.14 - possibility to reservate sources -// 3.4.14 - small re-arrangements -// 4.4.14 - Bugfixes -// 7.4.14 - Bugfix -// 12.6.14 - Bugfix -// 2.11.14 - Added group finalising packet -// - - -function [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName) - SourceID = PacketFramework.SourceID_counter; - - Source.SourceName = SourceName; - Source.SourceID = SourceID; - Source.NValues_send = NValues_send; - Source.datatype = datatype; - - // Add new source to the list - PacketFramework.Sources($+1) = Source; - - // inc counter - PacketFramework.SourceID_counter = PacketFramework.SourceID_counter + 1; -endfunction - -function [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName) - ParameterID = PacketFramework.Parameterid_counter; - - Parameter.ParameterName = ParameterName; - Parameter.ParameterID = ParameterID; - Parameter.NValues = NValues; - Parameter.datatype = datatype; - Parameter.MemoryOfs = PacketFramework.ParameterMemOfs_counter; - - // Add new source to the list - PacketFramework.Parameters($+1) = Parameter; - - // inc counters - PacketFramework.Parameterid_counter = PacketFramework.Parameterid_counter + 1; - PacketFramework.ParameterMemOfs_counter = PacketFramework.ParameterMemOfs_counter + NValues; - - // return values - ParameterID = Parameter.ParameterID; - MemoryOfs = Parameter.MemoryOfs; -endfunction - -function [sim, PacketFramework, Parameter] = ld_PF_Parameter(sim, PacketFramework, NValues, datatype, ParameterName) // PARSEDOCU_BLOCK -// -// Define a parameter -// -// NValues - amount of data sets -// datatype - only ORTD.DATATYPE_FLOAT for now -// ParameterName - a unique string decribing the parameter -// -// -// - - [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); - - // read data from global memory - [sim, readI] = ld_const(sim, 0, MemoryOfs); // start at index 1 - [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... - datatype, NValues); -endfunction - - - - -// Send a signal via UDP, a simple protocoll is defined, internal function -function [sim] = ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID) - InstanceName = PacketFramework.InstanceName; - [sim,one] = ld_const(sim, 0, 1); - - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - - // Source ID - [sim, SourceID] = ld_const(sim, 0, SourceID); - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, Signal ), insizes=[1,1,1,NValues_send], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, datatype ] ); - - printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); - -endfunction - - - - -function [sim, PacketFramework] = ld_SendPacket(sim, PacketFramework, Signal, NValues_send, datatype, SourceName) // PARSEDOCU_BLOCK // PARSEDOCU_BLOCK -// -// Stream data - block -// -// Signal - the signal to stream -// NValues_send - the vector length of Signal -// datatype - only ORTD.DATATYPE_FLOAT by now -// SourceName - a unique string identifier descring the stream -// -// -// - - [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); - [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, NValues_send, datatype, SourceID); -endfunction - - - - -function [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName, Configuration) // PARSEDOCU_BLOCK -// -// Initialise an instance of the Packet Framework -// -// InstanceName - a unique string identifier for the instance -// Configuration must include the following properties: -// -// Configuration.UnderlyingProtocoll = "UDP" -// Configuration.DestHost -// Configuration.DestPort -// Configuration.LocalSocketHost -// Configuration.LocalSocketPort -// -// -// Example: -// -// -// Configuration.UnderlyingProtocoll = "UDP"; -// Configuration.DestHost = "127.0.0.1"; -// Configuration.DestPort = 20000; -// Configuration.LocalSocketHost = "127.0.0.1"; -// Configuration.LocalSocketPort = 20001; -// [sim, PacketFramework] = ld_PF_InitInstance(sim, InstanceName="UDPCommunication", Configuration); -// -// -// -// Also consider the file webappUDP.js as the counterpart that communicates to ORTD-simulations -// -// - - // initialise structure for sources - PacketFramework.InstanceName = InstanceName; - PacketFramework.Configuration = Configuration; - - PacketFramework.Configuration.debugmode = %F; - -// disp(Configuration.UnderlyingProtocoll) - - if Configuration.UnderlyingProtocoll == 'UDP' - null; - else - error("PacketFramework: Only UDP supported up to now"); - end - - // possible packet sizes for UDP - PacketFramework.TotalElemetsPerPacket = floor((1400-3*4)/8); // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes - PacketFramework.PacketSize = PacketFramework.TotalElemetsPerPacket*8 + 3*4; - - // sources - PacketFramework.SourceID_counter = 0; - PacketFramework.Sources = list(); - - // parameters - PacketFramework.Parameterid_counter = 0; - PacketFramework.ParameterMemOfs_counter = 1; // start at the first index in the memory - PacketFramework.Parameters = list(); - - PacketFramework.SenderID = 1295793; -endfunction - - -// Send a signal via UDP, a simple protocoll is defined, internal function -function [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID) - InstanceName = PacketFramework.InstanceName; - [sim,one] = ld_const(sim, 0, 1); - - // Packet counter, so the order of the network packages can be determined - [sim, Counter] = ld_modcounter(sim, 0, in=one, initial_count=0, mod=100000); - [sim, Counter_int32] = ld_ceilInt32(sim, 0, Counter); - - // Source ID - [sim, SourceID] = ld_const(sim, 0, -1); // -1 means finish a group of sources - [sim, SourceID_int32] = ld_ceilInt32(sim, 0, SourceID); - - // Group ID - [sim, GroupID_] = ld_const(sim, 0, GroupID); // -1 means finish a group of sources - [sim, GroupID_int32] = ld_ceilInt32(sim, 0, GroupID_); - - // Sender ID - [sim, SenderID] = ld_const(sim, 0, PacketFramework.SenderID); // random number - [sim, SenderID_int32] = ld_ceilInt32(sim, 0, SenderID); - - // make a binary structure - [sim, Data, NBytes] = ld_ConcateData(sim, 0, ... - inlist=list(SenderID_int32, Counter_int32, SourceID_int32, GroupID_int32 ), insizes=[1,1,1,1], ... - intypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32 ] ); - -// printf("The size of the UDP-packets will be %d bytes.\n", NBytes); - - // send to the network - [sim, NBytes__] = ld_constvecInt32(sim, 0, vec=NBytes); // the number of bytes that are actually send is dynamic, but must be smaller or equal to - [sim] = ld_UDPSocket_SendTo(sim, 0, SendSize=NBytes__, ObjectIdentifyer=InstanceName+"aSocket", ... - hostname=PacketFramework.Configuration.DestHost, ... - UDPPort=PacketFramework.Configuration.DestPort, in=Data, ... - insize=NBytes); - - - [sim] = ld_printf(sim, ev, GroupID_, "Sent finish packet ", 1); -endfunction - -function [sim,PacketFramework] = ld_PF_Finalise(sim,PacketFramework) // PARSEDOCU_BLOCK -// -// Finalise the instance. -// -// - - // The main real-time thread - function [sim] = ld_PF_InitUDP(sim, InstanceName, ParameterMemory) - - function [sim, outlist, userdata] = UDPReceiverThread(sim, inlist, userdata) - // This will run in a thread. Each time a UDP-packet is received - // one simulation step is performed. Herein, the packet is parsed - // and the contained parameters are stored into a memory. - - // Sync the simulation to incomming UDP-packets - [sim, Data, SrcAddr] = ld_UDPSocket_Recv(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", outsize=PacketSize ); - - // disassemble packet's structure - [sim, DisAsm] = ld_DisassembleData(sim, 0, in=Data, ... - outsizes=[1,1,1,TotalElemetsPerPacket], ... - outtypes=[ ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_INT32, ORTD.DATATYPE_FLOAT ] ); - - - - DisAsm_ = list(); - DisAsm_(4) = DisAsm(4); - [sim, DisAsm_(1)] = ld_Int32ToFloat(sim, 0, DisAsm(1) ); - [sim, DisAsm_(2)] = ld_Int32ToFloat(sim, 0, DisAsm(2) ); - [sim, DisAsm_(3)] = ld_Int32ToFloat(sim, 0, DisAsm(3) ); - - - [sim, memofs] = ld_ArrayInt32(sim, 0, array=ParameterMemory.MemoryOfs, in=DisAsm(3) ); - [sim, Nelements] = ld_ArrayInt32(sim, 0, array=ParameterMemory.Sizes, in=DisAsm(3) ); - - [sim, memofs_] = ld_Int32ToFloat(sim, 0, memofs ); - [sim, Nelements_] = ld_Int32ToFloat(sim, 0, Nelements ); - - if PacketFramework.Configuration.debugmode then - // print the contents of the packet - [sim] = ld_printf(sim, 0, DisAsm_(1), "DisAsm(1) (SenderID) = ", 1); - [sim] = ld_printf(sim, 0, DisAsm_(2), "DisAsm(2) (Packet Counter) = ", 1); - [sim] = ld_printf(sim, 0, DisAsm_(3), "DisAsm(3) (SourceID) = ", 1); - [sim] = ld_printf(sim, 0, DisAsm_(4), "DisAsm(4) (Signal) = ", TotalElemetsPerPacket); - - [sim] = ld_printf(sim, 0, memofs_ , "memofs = ", 1); - [sim] = ld_printf(sim, 0, memofs_ , "Nelements = ", 1); - end - - // Store the input data into a shared memory - [sim] = ld_WriteMemory2(sim, 0, data=DisAsm(4), index=memofs, ElementsToWrite=Nelements, ... - ident_str=InstanceName+"Memory", datatype=ORTD.DATATYPE_FLOAT, MaxElements=TotalElemetsPerPacket ); - - - - // output of schematic - outlist = list(); - endfunction - - - - // start the node.js service from the subfolder webinterface - //[sim, out] = ld_startproc2(sim, 0, exepath="./webappUDP.sh", chpwd="webinterface", prio=0, whentorun=0); - - TotalMemorySize = sum(PacketFramework.ParameterMemory.Sizes); - TotalElemetsPerPacket = PacketFramework.TotalElemetsPerPacket; // number of doubles values that fit into one UDP-packet with maximal size of 1400 bytes - PacketSize = PacketFramework.PacketSize; - - // Open an UDP-Port in server mode - [sim] = ld_UDPSocket_shObj(sim, 0, ObjectIdentifyer=InstanceName+"aSocket", Visibility=0, ... - hostname=PacketFramework.Configuration.LocalSocketHost, ... - UDPPort=PacketFramework.Configuration.LocalSocketPort); - - // initialise a global memory for storing the input data for the computation - [sim] = ld_global_memory(sim, 0, ident_str=InstanceName+"Memory", ... - datatype=ORTD.DATATYPE_FLOAT, len=TotalMemorySize, ... - initial_data=[zeros(TotalMemorySize,1)], ... - visibility='global', useMutex=1); - - // Create thread for the receiver - ThreadPrioStruct.prio1=ORTD.ORTD_RT_NORMALTASK, ThreadPrioStruct.prio2=0, ThreadPrioStruct.cpu = -1; - [sim, startcalc] = ld_const(sim, 0, 1); // triggers your computation during each time step - [sim, outlist, computation_finished] = ld_async_simulation(sim, 0, ... - inlist=list(), ... - insizes=[], outsizes=[], ... - intypes=[], outtypes=[], ... - nested_fn = UDPReceiverThread, ... - TriggerSignal=startcalc, name=InstanceName+"Thread1", ... - ThreadPrioStruct, userdata=list() ); - - - endfunction - - - - - - // calc memory - MemoryOfs = []; - Sizes = []; - // go through all parameters and create memories for all - for i=1:length(PacketFramework.Parameters) - P = PacketFramework.Parameters(i); - - Sizes = [Sizes; P.NValues]; - MemoryOfs = [MemoryOfs; P.MemoryOfs]; - end - - PacketFramework.ParameterMemory.MemoryOfs = MemoryOfs; - PacketFramework.ParameterMemory.Sizes = Sizes; - - // udp - [sim] = ld_PF_InitUDP(sim, PacketFramework.InstanceName, PacketFramework.ParameterMemory); - - // Send to group update notifications for each group (currently only one possible) - [sim] = ld_PF_SendGroupFinshUDP(sim, PacketFramework, GroupID=0); - -endfunction - -function ld_PF_Export_js(PacketFramework, fname) // PARSEDOCU_BLOCK -// -// Export configuration of the defined protocoll (Sources, Parameters) -// into JSON-format. This is to be used by software that shall communicate -// to the real-time system. -// -// fname - The file name -// -// - - - fd = mopen(fname,'wt'); - - mfprintf(fd,' {""SourcesConfig"" : {\n'); - - for i=1:length(PacketFramework.Sources) - - - SourceID = PacketFramework.Sources(i).SourceID; - SourceName = PacketFramework.Sources(i).SourceName; - disp(SourceID ); - disp( SourceName ); - - - line=sprintf(" ""%s"" : { ""SourceName"" : ""%s"" , ""NValues_send"" : ""%s"", ""datatype"" : ""%s"" } \n", ... - string(PacketFramework.Sources(i).SourceID), ... - string(PacketFramework.Sources(i).SourceName), ... - string(PacketFramework.Sources(i).NValues_send), ... - string(PacketFramework.Sources(i).datatype) ); - - - if i==length(PacketFramework.Sources) - // finalise the last entry without "," - printf('%s \n' , line); - mfprintf(fd,'%s', line); - else - printf('%s, \n' , line); - mfprintf(fd,'%s,', line); - end - - - end - - - - - mfprintf(fd,'} , \n ""ParametersConfig"" : {\n'); - - // go through all parameters and create memories for all - for i=1:length(PacketFramework.Parameters) - - printf("export of parameter %s \n",PacketFramework.Parameters(i).ParameterName ); - - line=sprintf(" ""%s"" : { ""ParameterName"" : ""%s"" , ""NValues"" : ""%s"", ""datatype"" : ""%s"" } \n", ... - string(PacketFramework.Parameters(i).ParameterID), ... - string(PacketFramework.Parameters(i).ParameterName), ... - string(PacketFramework.Parameters(i).NValues), ... - string(PacketFramework.Parameters(i).datatype) ); - - - if i==length(PacketFramework.Parameters) - // finalise the last entry without "," - printf('%s \n' , line); - mfprintf(fd,'%s', line); - else - printf('%s, \n' , line); - mfprintf(fd,'%s,', line); - end - - - end - - mfprintf(fd,'}\n}'); - - mclose(fd); -endfunction - -// -// Added 27.3.14 -// - -function [sim, PacketFramework, SourceID]=ld_SendPacketReserve(sim, PacketFramework, NValues_send, datatype, SourceName) - [PacketFramework,SourceID] = ld_PF_addsource(PacketFramework, NValues_send, datatype, SourceName); -endfunction - -function [sim, PacketFramework]=ld_SendPacket2(sim, PacketFramework, Signal, SourceName) - // find Sourcename - index = -1; - for i=1:length(PacketFramework.Sources) - S = PacketFramework.Sources(i); - if S.SourceName == SourceName - index = i; - printf(" %s found at index %d Nvalues %d\n", SourceName, index, S.NValues_send); - break; - end - end - - if index == -1 - printf("SourceName = %s\n", SourceName); - error("SourceName not found! This source must be reservated using ld_SendPacketReserve"); - end - - [sim]=ld_PF_ISendUDP(sim, PacketFramework, Signal, S.NValues_send, S.datatype, S.SourceID); -endfunction - - - -function [sim, PacketFramework, ParameterID]=ld_PF_ParameterReserve(sim, PacketFramework, NValues, datatype, ParameterName) - [PacketFramework,ParameterID,MemoryOfs] = ld_PF_addparameter(PacketFramework, NValues, datatype, ParameterName); -endfunction - - -function [sim, PacketFramework, Parameter]=ld_PF_Parameter2(sim, PacketFramework, ParameterName) - // find Sourcename - index = -1; - for i=1:length(PacketFramework.Parameters) - P = PacketFramework.Parameters(i); - if P.ParameterName == ParameterName - index = i; - printf(" %s found at index %d Nvalues %d\n", ParameterName, index, P.NValues); - break; - end - end - - if index == -1 - printf("ParameterName = %s\n", ParameterName); - error("ParameterName not found! This source must be reservated using ld_PF_ParameterReserve"); - end - - // read data from global memory - [sim, readI] = ld_const(sim, 0, P.MemoryOfs); // start at index 1 - [sim, Parameter] = ld_read_global_memory(sim, 0, index=readI, ident_str=PacketFramework.InstanceName+"Memory", ... - P.datatype, P.NValues); -endfunction - diff --git a/data_sources/ORTD/DataSourceExample_Groups/webinterface/html/mainAuto.html b/data_sources/ORTD/DataSourceExample_Groups/webinterface/html/mainAuto.html deleted file mode 100644 index 491f5a19..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/webinterface/html/mainAuto.html +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - Generic GUI-interface to an ORTD-simulation - -
- This is a generic interface to ORTD. All parameters and datasources are automatically shown. Feel free to copy and adapt it to your needs to build you own specialised web-interface. - -

- - Parameters:
0
- - Displays:
0
- -

- ProtocollConfiguration:
0
- - - \ No newline at end of file diff --git a/data_sources/ORTD/DataSourceExample_Groups/webinterface/html/main_Plot.html b/data_sources/ORTD/DataSourceExample_Groups/webinterface/html/main_Plot.html deleted file mode 100644 index 47a8fa23..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/webinterface/html/main_Plot.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - - -
- - - - - - - - - - - GUI-interface to ORTD-simulation - -

- - - Parameters:
0
- - Displays:
0
- - - - - ProtocollConfiguration:
0
-

- - - - - - - - \ No newline at end of file diff --git a/data_sources/ORTD/DataSourceExample_Groups/webinterface/install_nodejs.sh b/data_sources/ORTD/DataSourceExample_Groups/webinterface/install_nodejs.sh deleted file mode 100644 index 7cdf2b59..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/webinterface/install_nodejs.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -echo 'export PATH=$HOME/local/bin:$PATH' >> ~/.bashrc -. ~/.bashrc -mkdir ~/local -mkdir ~/node-latest-install -cd ~/node-latest-install -curl http://nodejs.org/dist/node-latest.tar.gz | tar xz --strip-components=1 -./configure --prefix=~/local -make install # ok, fine, this step probably takes more than 30 seconds... -curl https://npmjs.org/install.sh | sh diff --git a/data_sources/ORTD/DataSourceExample_Groups/webinterface/webappUDP.js b/data_sources/ORTD/DataSourceExample_Groups/webinterface/webappUDP.js deleted file mode 100644 index 30baf361..00000000 --- a/data_sources/ORTD/DataSourceExample_Groups/webinterface/webappUDP.js +++ /dev/null @@ -1,304 +0,0 @@ -/* - - node.js interface to ORTD using UDP datagrams and the packet framework. - A web-interface is provided at e.g. http://localhost:8091/mainAuto.html, - however currently not passwort protected. - The web-interface(s) are defined in html/* - - You can adapt the html files to your need and also created new ones - to e.g. set-up an individual interface to your application - - Rev 2 - -*/ - -// http-server config -var HTTPPORT = 8091; - -// UDP config -var PORT = 20000; -var HOST = '127.0.0.1'; -var ORTD_HOST = '127.0.0.1'; // the IP and port of the ORTD simulator running UDPio.sce -var ORTD_PORT = 20001; - -var NValues = 7; // must be the same as NValues_send defined in UDPio.sce when calling UDPSend -var DataBufferSize = 20000; // Number of elementes stored in the ringbuffer - - - -// -// Includes -// - -http = require('http'); -url = require("url"), -path = require("path"), -fs = require("fs") - -var fs = require('fs'); -var dgram = require('dgram'); - - -// -// Load configuration -// - -var ProtocollConfig = require('../ProtocollConfig.json'); -console.log(ProtocollConfig); - -// -// RingBuffer -// - -var RingBuffer = new RingBuffer(DataBufferSize, NValues); -console.log("RingBuffer created"); - - - - // console.log(P.length); - -function GetParameterID(ParametersConfig, ParameterName) -{ - var P = ParametersConfig; - for (key in P) { - if (P[key].ParameterName == ParameterName) { - return key; - } - } - return -1; -} - -console.log( GetParameterID(ProtocollConfig.ParametersConfig, "Parameter2") ); - -// -// http-server -// - -// got from stackoverflow: -// http://stackoverflow.com/questions/6084360/node-js-as-a-simple-web-server -var httpserver = http.createServer(function(request, response) { - - var uri = url.parse(request.url).pathname - , filename = path.join(process.cwd(), 'html', uri); - - path.exists(filename, function(exists) { - if(!exists) { - response.writeHead(404, {"Content-Type": "text/plain"}); - response.write("404 Not Found\n"); - response.end(); - return; - } - - if (fs.statSync(filename).isDirectory()) filename += '/index.html'; - - fs.readFile(filename, "binary", function(err, file) { - if(err) { - response.writeHead(500, {"Content-Type": "text/plain"}); - response.write(err + "\n"); - response.end(); - return; - } - - response.writeHead(200); - response.write(file, "binary"); - response.end(); - }); - }); -}).listen(HTTPPORT); - -// set-up socket.io -var io = require('socket.io').listen(httpserver); -io.set('log level', 1); // reduce logging - - - - -// -// UDP interface -// -var server = dgram.createSocket('udp4'); -server.on('listening', function () { - var address = server.address(); - console.log('UDP Server listening on ' + address.address + ":" + address.port); -}); - -// Buffer for sending UDP packets -var UDPSendPacketBuffer = new Buffer(2000); // size is propably bigger than every UDP-Packet - - -server.on('message', function (message, remote) { - // received new packet from ORTD via UDP - //console.log(remote.address + ':' + remote.port); - - - var i; - - try { - // disassemble header - var SenderID = message.readInt32LE( 0 ); - var PacketCounter = message.readInt32LE( 4 ); - var SourceID = message.readInt32LE( 8 ); - - // check wheter the sender ID is correct - if (SenderID != 1295793) - throw 1; - - // get popoerties of this packet source - SourceProperties = ProtocollConfig.SourcesConfig[SourceID] -// console.log( 'SourceProperties: '); -// console.log( SourceProperties ); - - if (SourceProperties.datatype != 257) // ORTD.DATATYPE_FLOAT - throw 2; - - - - // check if the recved packet has the correct size - if ( message.length != 12+8*SourceProperties.NValues_send) - throw 2; - -// console.log('Disasm data '+ SourceProperties.NValues_send); - - // disassemble data-values - var ValuesBuffer = message.slice(12, 12+8*SourceProperties.NValues_send); - var Values = new Array(SourceProperties.NValues_send); - - for (i=0; i= this.DataBufferSize) { - this.WriteIndex = 0; - } - } - -// this.ReturnBuffer=ReturnBuffer; -// function ReturnBuffer() { -// return DataBuffer; -// } -} - diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index ddb63afa..b5208ec4 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,10 +1,5 @@ - + - - - default - - 771 @@ -13,713 +8,100 @@ 853 + + + default + + - - Button + + OrtdController - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - 0 - Button2 - - - 0 - Click - Text for state 1 - - - ButtonX2 - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab + ORTDController - \(([0-9]+),([0-9]+)\) - 1 - (159,54) - Determine position: (x,y) - - - 0 - Clack - Text for state 2 - - - - - - - Parameter - - - - - - - ProgressBar - - - Show - 1 - 1 - - - Used for window title - Button3 - - - ProgressBarShowAll - - - \d+ + (0,0) 1 - 153 - Set maximum range for the progress bar. - Show current/max - - \(([0-9]+),([0-9]+)\) - 1 - (314,0) Determine position: (x,y) - - Name of the scalar which is used for the progress bar. - 0 - percent - Progress Value - - - \d+ - 1 - 0 - Set minimum range for the progress bar. - Min Range - - - A label next to the bar shows the current and max value - 0 - 1 - bool - Show current/max + + OrtdController - \(([0-9]+),([0-9]+)\) + (378,313) 1 - (156,53) + \(([0-9]+),([0-9]+)\) Determine size: (height,width) - - Show progress in percent over the progress bar - 0 - 0 - bool - Show percent - - - Name of the scalar which is used to reset the progress bar. - 0 - reset - Reset Value - - Used for tabs PaPI-Tab + Used for tabs - - Name of the scalar which is used to increment progress bar by one. + + ORTDPlugin1 0 - trigger - Trigger Value + Uname to use for ortd plugin instance - - 0 - + - ButtonX3 - - trigger - - - - Trigger - - - percent - - - - trigger - - + ORTDPlugin1 + - reset + ControlSignalReset + ControlSignalCreate + ControlSignalSub + ControllerSignalParameter + ControllerSignalClose - - Button + + ORTD_UDP - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - 0 - Button3 - - - 0 - Click - Text for state 1 - - - ButtonX3 - - - \(([0-9]+),([0-9]+)\) + + 20000 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - \(([0-9]+),([0-9]+)\) + + 127.0.0.1 1 - (313,52) - Determine position: (x,y) - - - 0 - Clack - Text for state 2 - - - - - - - Parameter - - - - - - - Trigger - - - Trigger - - - - 1.0 - - - - - trigger - - - - - percent - - - - - reset - - - - - - ButtonX6 - - Choose - - - - ButtonReset - - Choose - - - - ButtonX5 - - Choose - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 2 - - ([-]{0,1}\d+\.\d+) + + /home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json 0 - 2 + file - - 0 - ResetAll - - - 0 - ResetAll - Text for state 1 - - - ButtonReset - - - \(([0-9]+),([0-9]+)\) + + 20001 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (309,176) - Determine position: (x,y) - - - 0 - ResetAll - Text for state 2 - - - Parameter + + + ControlSignalReset - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - 0 - TriggerAll - - - 0 - TriggerAll - Text for state 1 - - - ButtonX5 - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (154,176) - Determine position: (x,y) - - - 0 - TriggerAll - Text for state 2 - - - - - - - Parameter + + ControlSignalCreate - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - 0 - SetAll - - - 0 - To 20 - Text for state 1 - - - ButtonX6 - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (2,174) - Determine position: (x,y) - - - 0 - To 20 - Text for state 2 - - - - - - - Parameter + + ControlSignalSub - - - - - - ProgressBar - - - Show - 1 - 1 - - - Used for window title - Button1 - - - ProgressBar - - - \d+ - 1 - 100 - Set maximum range for the progress bar. - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (0,0) - Determine position: (x,y) - - - Name of the scalar which is used for the progress bar. - 0 - percent - Progress Value - - - \d+ - 1 - 0 - Set minimum range for the progress bar. - Min Range - - - A label next to the bar shows the current and max value - 0 - 1 - bool - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (150,50) - Determine size: (height,width) - - - Show progress in percent over the progress bar - 0 - 1 - bool - Show percent - - - Name of the scalar which is used to reset the progress bar. - 0 - reset - Reset Value - - - Used for tabs - PaPI-Tab - - - Name of the scalar which is used to increment progress bar by one. - 0 - trigger - Trigger Value - - - - 0 - - - - - Button - - trigger - - - - Trigger - - - percent - - - - trigger - - - - reset - - - - - - Button - - - ([-]{0,1}\d+\.\d+) - 0 - 1 - - - ([-]{0,1}\d+\.\d+) - 0 - 0 - - - 0 - Button1 - - - 0 - Click - Text for state 1 - - - Button - - - \(([0-9]+),([0-9]+)\) - 1 - (150,55) - Determine size: (height,width) - - - Used for tabs - PaPI-Tab - - - \(([0-9]+),([0-9]+)\) - 1 - (0,52) - Determine position: (x,y) - - - 0 - Clack - Text for state 2 - - - - - - - Parameter + + ControllerSignalParameter + + + ControllerSignalClose - - ProgressBar - - - Show - 1 - 1 - - - Used for window title - Button2 - - - ProgressBarShowCurrentMax - - - \d+ - 1 - 100 - Set maximum range for the progress bar. - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (158,0) - Determine position: (x,y) - - - Name of the scalar which is used for the progress bar. - 0 - percent - Progress Value - - - \d+ - 1 - 0 - Set minimum range for the progress bar. - Min Range - - - A label next to the bar shows the current and max value - 0 - 0 - bool - Show current/max - - - \(([0-9]+),([0-9]+)\) - 1 - (150,53) - Determine size: (height,width) - - - Show progress in percent over the progress bar - 0 - 0 - bool - Show percent - - - Name of the scalar which is used to reset the progress bar. - 0 - reset - Reset Value - - - Used for tabs - PaPI-Tab - - - Name of the scalar which is used to increment progress bar by one. - 0 - trigger - Trigger Value - - - - 0 - - - - - ButtonX2 - - trigger - - - - Trigger - - - percent - - - - trigger - - - - reset - - - - From 15efa8d4841063cbfbba52968efc97bc404a20ee Mon Sep 17 00:00:00 2001 From: christianausb Date: Sat, 21 Mar 2015 17:16:08 +0100 Subject: [PATCH 183/260] Tempoarily extended maximal packet size + debug printf's --- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 6148fc14..7f166a11 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -174,7 +174,7 @@ def thread_execute(self): signal_values = {} while goOn: try: - rev = self.sock_recv.recv(1600) + rev = self.sock_recv.recv(20000) # not feasible for network connection other than loopback except socket.timeout: # print('timeout') newData = False @@ -223,12 +223,21 @@ def process_received_package(self, rev): unp = unp + str(struct.unpack_from(' Date: Mon, 23 Mar 2015 10:53:20 +0100 Subject: [PATCH 184/260] extended wait in ORTDController to allow more plugins to be displayed --- papi/plugin/visual/OrtdController/OrtdController.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index 8e4f47c7..5ea4b3d2 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -111,7 +111,7 @@ def execute_cfg(self): while len(self.plugin_started_list): pl_uname = self.plugin_started_list.pop() self.control_api.do_delete_plugin_uname(pl_uname) - time.sleep(0.5) + time.sleep(1) ############################ # Create Plugins # @@ -124,7 +124,7 @@ def execute_cfg(self): pl_cfg = cfg[pl_uname] self.control_api.do_create_plugin(pl_cfg['identifier']['value'],pl_uname, pl_cfg['config']) self.plugin_started_list.append(pl_uname) - time.sleep(0.5) + time.sleep(1) ############################ # Create Subs # From d7f4a3fc91fade4f8a613397b9fe2c03512b4008 Mon Sep 17 00:00:00 2001 From: christianausb Date: Mon, 23 Mar 2015 16:54:03 +0100 Subject: [PATCH 185/260] .. --- papi/plugin/visual/OrtdController/OrtdController.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index 5ea4b3d2..df3c33cf 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -111,7 +111,7 @@ def execute_cfg(self): while len(self.plugin_started_list): pl_uname = self.plugin_started_list.pop() self.control_api.do_delete_plugin_uname(pl_uname) - time.sleep(1) + time.sleep(0.5) ############################ # Create Plugins # @@ -124,7 +124,7 @@ def execute_cfg(self): pl_cfg = cfg[pl_uname] self.control_api.do_create_plugin(pl_cfg['identifier']['value'],pl_uname, pl_cfg['config']) self.plugin_started_list.append(pl_uname) - time.sleep(1) + time.sleep(1.5) ############################ # Create Subs # From 364068a3b41b224e81439fb9d5dec99c0e4efd09 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 24 Mar 2015 10:50:48 +0100 Subject: [PATCH 186/260] refined some gui items created a gui_management object and added it to gui now yapsy plugin manager is not collecting at every creation. Now there is a reload option --- papi/last_active_papi.xml | 203 ++++++++++------------------ papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 18 +-- 2 files changed, 77 insertions(+), 144 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index bcf3f484..a12e23eb 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,170 +1,109 @@ - + - - - default - - - - 771 - 853 + + 771 + + + + default + + - - Slider + + OrtdController - - 1.0 - - - PaPI-Tab - Used for tabs - - - PaPI Slider - Used for window title - - - 11 - [0-9]+ - - (150,75) - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) 1 - - - 0.0 - - - (239,57) - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - 1 - - - 0.81 - Used as initial value for the Slider - ([-]{0,1}\d+(.\d+)?) - - - SliderX3 - - - - - - - SliderParameter1 - - - - - - - Slider - - - 1.0 - - - PaPI-Tab - Used for tabs - - - PaPI Slider - Used for window title - - - 11 - [0-9]+ - - - (150,75) + (300,300) Determine size: (height,width) \(([0-9]+),([0-9]+)\) - 1 - - 0.0 + + ORTDController + 1 (0,0) Determine position: (x,y) \(([0-9]+),([0-9]+)\) - 1 - - 0.57 - Used as initial value for the Slider - ([-]{0,1}\d+(.\d+)?) + + Tab + Used for tabs - Slider + OrtdController + + + 0 + ORTDPlugin1 + Uname to use for ortd plugin instance - - - - SliderParameter1 - - - - + + + + ORTDPlugin1 + + + ControlSignalReset + ControlSignalCreate + ControlSignalSub + ControllerSignalParameter + ControllerSignalClose + + + - - Slider + + ORTD_UDP - - 1.0 - - - PaPI-Tab - Used for tabs - - - PaPI Slider - Used for window title - - - 11 - [0-9]+ - - - (150,75) - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) + 1 + 20000 - - 0.0 - - - (57,173) - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) + 1 + 20001 - - 0.79 - Used as initial value for the Slider - ([-]{0,1}\d+(.\d+)?) + + 1 + 127.0.0.1 - - SliderX2 + + 0 + /home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json + file - - - SliderParameter1 + + + ControlSignalReset + + + ControlSignalCreate + + + ControlSignalSub + + + ControllerSignalParameter + + + ControllerSignalClose + + + + + Const diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 6148fc14..09d8ec56 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -143,18 +143,10 @@ def start_init(self, config=None): self.block_id = 0 - # db = DBlock('SourceGroup0') - # db.add_signal(DSignal('TestSig1')) - # - # self.send_new_block_list([db]) - # - # self.send_delete_block('SourceGroup0') - # - # db = DBlock('SourceGroup0') - # db.add_signal(DSignal('TestSig2')) - # - # self.send_new_block_list([db]) - # self.blocks['SourceGroup0'] = db + Counter = 1 + data = struct.pack(' Date: Tue, 24 Mar 2015 11:52:21 +0100 Subject: [PATCH 187/260] Added initial value for the slider's configuration --- papi/gui/qt_new/create_record_config.py | 123 ++++++++++++ papi/gui/qt_new/item.py | 253 ++++++++++++++++++++++++ papi/last_active_papi.xml | 6 + papi/ui/gui/qt_new/CreateRecording.py | 90 +++++++++ papi/ui/gui/qt_new/create.py | 4 + papi/ui/gui/qt_new/create_dialog.py | 4 + papi/ui/gui/qt_new/main.py | 4 + papi/ui/gui/qt_new/overview.py | 4 + ui/gui/qt_new/CreateRecording.ui | 111 +++++++++++ 9 files changed, 599 insertions(+) create mode 100644 papi/gui/qt_new/create_record_config.py create mode 100644 papi/ui/gui/qt_new/CreateRecording.py create mode 100644 ui/gui/qt_new/CreateRecording.ui diff --git a/papi/gui/qt_new/create_record_config.py b/papi/gui/qt_new/create_record_config.py new file mode 100644 index 00000000..681d9201 --- /dev/null +++ b/papi/gui/qt_new/create_record_config.py @@ -0,0 +1,123 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Sven Knuth +""" + +from PySide.QtCore import * +from PySide.QtGui import * + +from papi.ui.gui.qt_new.CreateRecording import Ui_CreateRecording +from PySide.QtGui import QMainWindow + +from papi.gui.qt_new.item import CustomFieldModel, CustomFieldItem, StructTreeModel, StructTreeNode, StructRootNode + +class CreateRecordingConfig(QMainWindow, Ui_CreateRecording): + def __init__(self, gui_api): + super(CreateRecordingConfig, self).__init__() + self.setupUi(self) + self.gui_api = gui_api + + # ----------------------------------------- + # Initiate first tab: Fields + # ----------------------------------------- + + self.addFieldButton.clicked.connect(self.add_button_triggered) + self.previewButton.clicked.connect(self.preview_button_triggered) + + self.field_model = CustomFieldModel() + self.field_model.dataChanged.connect(self.custom_field_edited) + self.field_model.setHorizontalHeaderLabels(['Field', 'Type', 'Remove']) + + self.customFieldTable.setModel(self.field_model) + + self.struct_model = StructTreeModel() + self.structureView.setModel(self.struct_model) + self.struct_model.setHorizontalHeaderLabels(['Name', 'Size']) + self.root_struct = StructRootNode('Data') + + self.struct_model.appendRow(self.root_struct) + + # ----------------------------------------- + # Initiate second tab: Subscription + # ----------------------------------------- + + self.previewButton_sub.clicked.connect(self.preview_sub_button_triggered) + + self.struct_model_sub = StructTreeModel() + self.struct_model_sub.setHorizontalHeaderLabels(['Name', 'Size']) + self.structureView_sub.setModel(self.struct_model_sub) + self.root_struct_sub = StructRootNode('Data') + + self.struct_model_sub.appendRow(self.root_struct_sub) + + + def showEvent(self, *args, **kwargs): + + pass + + def add_button_triggered(self): + custom_field_item = CustomFieldItem(CustomField()) + + self.field_model.appendRow(custom_field_item) + + def preview_button_triggered(self): + + self.struct_model.clear() + self.struct_model.setHorizontalHeaderLabels(['Name', 'Size']) + + self.root_struct = StructRootNode('Data') + self.struct_model.appendRow(self.root_struct) + + for i in range(self.field_model.rowCount()): + field = self.field_model.item(i).object + + self.root_struct.appendRow(field) + + self.structureView.expandAll() + + def preview_sub_button_triggered(self): + + self.struct_model_sub.clear() + self.struct_model_sub.setHorizontalHeaderLabels(['Name', 'Size']) + + self.root_struct_sub = StructRootNode('Data') + self.struct_model_sub.appendRow(self.root_struct_sub) + + for i in range(self.field_model.rowCount()): + field = self.field_model.item(i).object + + self.root_struct_sub.appendRow(field) + + self.structureView_sub.expandAll() + + def custom_field_edited(self, index, none): + print('Edited !!') + + +class CustomField(): + def __init__(self): + self.desc = '1::2::3' + self.size = '3x1' \ No newline at end of file diff --git a/papi/gui/qt_new/item.py b/papi/gui/qt_new/item.py index 34534ffa..d4666f38 100644 --- a/papi/gui/qt_new/item.py +++ b/papi/gui/qt_new/item.py @@ -425,3 +425,256 @@ def setData(self, index, value, role): return True return False +<<<<<<< Updated upstream +======= + + +class CustomFieldModel(QStandardItemModel): + def __init__(self, parent=None): + super(CustomFieldModel, self).__init__(parent) + + def data(self, index, role): + """ + For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum' + :param index: + :param role: + :return: + """ + + if not index.isValid(): + return None + + row = index.row() + col = index.column() + + if role == Qt.ToolTipRole: + return super(CustomFieldModel, self).data(index, Qt.ToolTipRole) + + if role == Qt.DisplayRole: + + if col == 0: + field = super(CustomFieldModel, self).data(index, Qt.UserRole) + return field.desc + if col == 1: + index_sibling = index.sibling(row, col-1) + field = super(CustomFieldModel, self).data(index_sibling, Qt.UserRole) + return field.size + if col == 2: + return None + + if role == Qt.DecorationRole: + pass + + if role == Qt.UserRole: + pass + + if role == Qt.EditRole: + if col == 0: + field = super(CustomFieldModel, self).data(index, Qt.UserRole) + return field.desc + if col == 1: + index_sibling = index.sibling(row, col-1) + field = super(CustomFieldModel, self).data(index_sibling, Qt.UserRole) + return field.size + if col == 2: + return None + + return None + + def setData(self, index, value, role): + """ + This function is called when a content in a row is edited by the user. + + :param index: Current selected index. + :param value: New value from user + :param role: + :return: + """ + + if not index.isValid(): + return None + + row = index.row() + col = index.column() + + if role == Qt.EditRole: + + if col == 0: + + field = super(CustomFieldModel, self).data(index, Qt.UserRole) + field.desc = value + self.dataChanged.emit(index, None) + + return True + + if col == 1: + index_sibling = index.sibling(row, col-1) + field = super(CustomFieldModel, self).data(index_sibling, Qt.UserRole) + field.size = value + self.dataChanged.emit(index, None) + + return False + + +class StructTreeModel(PaPITreeModel): + """ + This model is used to handle Plugin objects in TreeView created by the yapsy plugin manager. + """ + def __init__(self, parent=None): + super(StructTreeModel, self).__init__(parent) + + def data(self, index, role): + """ + For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum' + :param index: + :param role: + :return: + """ + + if not index.isValid(): + return None + + row = index.row() + col = index.column() + + if role == Qt.ToolTipRole: + return super(StructTreeModel, self).data(index, Qt.ToolTipRole) + + if role == Qt.DisplayRole: + + if col == 0: + item = super(StructTreeModel, self).data(index, Qt.UserRole) + if item is None: + return super(StructTreeModel, self).data(index, Qt.DisplayRole) + return item.desc + + return 'Quark' + if col == 1: + index_sibling = index.sibling(row, col-1) + item = super(StructTreeModel, self).data(index_sibling, Qt.UserRole) + return item.size + if col == 2: + return None + + if role == Qt.DecorationRole: + pass + + if role == Qt.UserRole: + pass + + if role == Qt.EditRole: + if col == 0: + item = super(StructTreeModel, self).data(index, Qt.UserRole) + return item.field + if col == 1: + index_sibling = index.sibling(row, col-1) + item = super(StructTreeModel, self).data(index_sibling, Qt.UserRole) + return item.size + if col == 2: + return None + + return None + +class StructRootNode(QStandardItem): + """ + This model is used to handle Plugin objects in TreeView created by the yapsy plugin manager. + """ + def __init__(self, name, parent=None): + super(StructRootNode, self).__init__(name) + self.nodes = {} + self.size = '' + self.field = 'Data' + + def appendRow(self, field): + + elements = str.split(field.desc, "::") + + # Create new sub node for this element + + if elements[0] not in self.nodes: + +# sub_elements = str.split(field.desc, "::", 1) + #field.desc = sub_elements[1] + struct_node = StructTreeNode(field, 0) + self.nodes[elements[0]] = struct_node + + super(StructRootNode, self).appendRow(struct_node) + + # There is already a subnode for this element + + if elements[0] in self.nodes: + self.nodes[elements[0]].appendRow(field) + + +class StructTreeNode(QStandardItem): + """ + This model is used to handle Plugin objects in TreeView created by the yapsy plugin manager. + """ + def __init__(self, field, level, parent=None): + + elements = str.split(field.desc, "::") + + self.nodes = {} + self.level = level + self.time_identifier = 'time' + self.ptime_identifier = 'ptime' + self.size = '' + self.field = '' + self.object = field + + super(StructTreeNode, self).__init__(elements[self.level]) + + self.appendRow(field) + + def appendRow(self, field): + + elements = str.split(field.desc, "::") + + # Node is responsible for the first element + + if len(elements) > self.level + 1: + + if self.time_identifier not in self.nodes: + struct_node = QStandardItem(self.time_identifier) + self.nodes[self.time_identifier] = struct_node + super(StructTreeNode, self).appendRow(struct_node) + + if self.ptime_identifier not in self.nodes: + struct_node = QStandardItem(self.ptime_identifier) + self.nodes[self.ptime_identifier] = struct_node + super(StructTreeNode, self).appendRow(struct_node) + + if elements[self.level + 1] not in self.nodes: + struct_node = StructTreeNode(field, self.level + 1) + self.nodes[elements[self.level + 1]] = struct_node + super(StructTreeNode, self).appendRow(struct_node) + + if elements[self.level + 1] in self.nodes: + self.nodes[elements[self.level + 1]].appendRow(field) + else: + self.size = '3x1' + self.field = elements[self.level] + + def data(self, role): + """ + For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum' + :param role: + :return: + """ + if role == Qt.ToolTipRole: + return None + + if role == Qt.DisplayRole: + return self.field + + if role == Qt.DecorationRole: + return None + + if role == Qt.UserRole: + return self.object + + if role == Qt.EditRole: + return None + + return None +>>>>>>> Stashed changes diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index bcf3f484..367f54ce 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,3 +1,4 @@ +<<<<<<< Updated upstream @@ -5,6 +6,11 @@ default +======= + + + +>>>>>>> Stashed changes 771 diff --git a/papi/ui/gui/qt_new/CreateRecording.py b/papi/ui/gui/qt_new/CreateRecording.py new file mode 100644 index 00000000..ec1939d8 --- /dev/null +++ b/papi/ui/gui/qt_new/CreateRecording.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/gui/qt_new/CreateRecording.ui' +# +# Created: Tue Mar 24 11:15:40 2015 +# by: pyside-uic 0.2.15 running on PySide 1.2.1 +# +# WARNING! All changes made in this file will be lost! + +from PySide import QtCore, QtGui + +class Ui_CreateRecording(object): + def setupUi(self, CreateRecording): + CreateRecording.setObjectName("CreateRecording") + CreateRecording.resize(800, 600) + self.centralwidget = QtGui.QWidget(CreateRecording) + self.centralwidget.setObjectName("centralwidget") + self.horizontalLayout_2 = QtGui.QHBoxLayout(self.centralwidget) + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.tabWidget = QtGui.QTabWidget(self.centralwidget) + self.tabWidget.setObjectName("tabWidget") + self.Fields = QtGui.QWidget() + self.Fields.setObjectName("Fields") + self.horizontalLayout = QtGui.QHBoxLayout(self.Fields) + self.horizontalLayout.setObjectName("horizontalLayout") + self.verticalLayout = QtGui.QVBoxLayout() + self.verticalLayout.setObjectName("verticalLayout") + self.customFieldTable = QtGui.QTableView(self.Fields) + self.customFieldTable.setObjectName("customFieldTable") + self.verticalLayout.addWidget(self.customFieldTable) + self.addFieldButton = QtGui.QPushButton(self.Fields) + self.addFieldButton.setObjectName("addFieldButton") + self.verticalLayout.addWidget(self.addFieldButton) + self.horizontalLayout.addLayout(self.verticalLayout) + self.verticalLayout_2 = QtGui.QVBoxLayout() + self.verticalLayout_2.setObjectName("verticalLayout_2") + self.structureView = QtGui.QTreeView(self.Fields) + self.structureView.setObjectName("structureView") + self.verticalLayout_2.addWidget(self.structureView) + self.previewButton = QtGui.QPushButton(self.Fields) + self.previewButton.setObjectName("previewButton") + self.verticalLayout_2.addWidget(self.previewButton) + self.horizontalLayout.addLayout(self.verticalLayout_2) + self.tabWidget.addTab(self.Fields, "") + self.Subscription = QtGui.QWidget() + self.Subscription.setObjectName("Subscription") + self.horizontalLayout_3 = QtGui.QHBoxLayout(self.Subscription) + self.horizontalLayout_3.setObjectName("horizontalLayout_3") + self.verticalLayout_3 = QtGui.QVBoxLayout() + self.verticalLayout_3.setObjectName("verticalLayout_3") + self.structureView_sub = QtGui.QTreeView(self.Subscription) + self.structureView_sub.setObjectName("structureView_sub") + self.verticalLayout_3.addWidget(self.structureView_sub) + self.previewButton_sub = QtGui.QPushButton(self.Subscription) + self.previewButton_sub.setObjectName("previewButton_sub") + self.verticalLayout_3.addWidget(self.previewButton_sub) + self.horizontalLayout_3.addLayout(self.verticalLayout_3) + self.verticalLayout_4 = QtGui.QVBoxLayout() + self.verticalLayout_4.setObjectName("verticalLayout_4") + self.subView = QtGui.QTableView(self.Subscription) + self.subView.setObjectName("subView") + self.verticalLayout_4.addWidget(self.subView) + self.pushButton_2 = QtGui.QPushButton(self.Subscription) + self.pushButton_2.setObjectName("pushButton_2") + self.verticalLayout_4.addWidget(self.pushButton_2) + self.horizontalLayout_3.addLayout(self.verticalLayout_4) + self.tabWidget.addTab(self.Subscription, "") + self.horizontalLayout_2.addWidget(self.tabWidget) + CreateRecording.setCentralWidget(self.centralwidget) + self.menubar = QtGui.QMenuBar(CreateRecording) + self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 25)) + self.menubar.setObjectName("menubar") + CreateRecording.setMenuBar(self.menubar) + self.statusbar = QtGui.QStatusBar(CreateRecording) + self.statusbar.setObjectName("statusbar") + CreateRecording.setStatusBar(self.statusbar) + + self.retranslateUi(CreateRecording) + self.tabWidget.setCurrentIndex(0) + QtCore.QMetaObject.connectSlotsByName(CreateRecording) + + def retranslateUi(self, CreateRecording): + CreateRecording.setWindowTitle(QtGui.QApplication.translate("CreateRecording", "MainWindow", None, QtGui.QApplication.UnicodeUTF8)) + self.addFieldButton.setText(QtGui.QApplication.translate("CreateRecording", "AddField", None, QtGui.QApplication.UnicodeUTF8)) + self.previewButton.setText(QtGui.QApplication.translate("CreateRecording", "Preview", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.Fields), QtGui.QApplication.translate("CreateRecording", "Fields", None, QtGui.QApplication.UnicodeUTF8)) + self.previewButton_sub.setText(QtGui.QApplication.translate("CreateRecording", "PreviewButton", None, QtGui.QApplication.UnicodeUTF8)) + self.pushButton_2.setText(QtGui.QApplication.translate("CreateRecording", "SendConfig", None, QtGui.QApplication.UnicodeUTF8)) + self.tabWidget.setTabText(self.tabWidget.indexOf(self.Subscription), QtGui.QApplication.translate("CreateRecording", "Subscription", None, QtGui.QApplication.UnicodeUTF8)) + diff --git a/papi/ui/gui/qt_new/create.py b/papi/ui/gui/qt_new/create.py index c6ebfc17..1f892669 100644 --- a/papi/ui/gui/qt_new/create.py +++ b/papi/ui/gui/qt_new/create.py @@ -2,7 +2,11 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/create.ui' # +<<<<<<< Updated upstream # Created: Mon Mar 16 16:37:08 2015 +======= +# Created: Tue Mar 24 11:15:40 2015 +>>>>>>> Stashed changes # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/create_dialog.py b/papi/ui/gui/qt_new/create_dialog.py index 380c958f..ce00c165 100644 --- a/papi/ui/gui/qt_new/create_dialog.py +++ b/papi/ui/gui/qt_new/create_dialog.py @@ -2,7 +2,11 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/create_dialog.ui' # +<<<<<<< Updated upstream # Created: Mon Mar 16 16:37:08 2015 +======= +# Created: Tue Mar 24 11:15:40 2015 +>>>>>>> Stashed changes # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/main.py b/papi/ui/gui/qt_new/main.py index d2986a0c..bf31f2d4 100644 --- a/papi/ui/gui/qt_new/main.py +++ b/papi/ui/gui/qt_new/main.py @@ -2,7 +2,11 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/main.ui' # +<<<<<<< Updated upstream # Created: Mon Mar 16 16:37:08 2015 +======= +# Created: Tue Mar 24 11:15:40 2015 +>>>>>>> Stashed changes # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/overview.py b/papi/ui/gui/qt_new/overview.py index 0a6fc1aa..8e8ae0cf 100644 --- a/papi/ui/gui/qt_new/overview.py +++ b/papi/ui/gui/qt_new/overview.py @@ -2,7 +2,11 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/overview.ui' # +<<<<<<< Updated upstream # Created: Mon Mar 16 16:37:08 2015 +======= +# Created: Tue Mar 24 11:15:40 2015 +>>>>>>> Stashed changes # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/ui/gui/qt_new/CreateRecording.ui b/ui/gui/qt_new/CreateRecording.ui new file mode 100644 index 00000000..10478eb3 --- /dev/null +++ b/ui/gui/qt_new/CreateRecording.ui @@ -0,0 +1,111 @@ + + + CreateRecording + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + 0 + + + + Fields + + + + + + + + + + + AddField + + + + + + + + + + + + + + Preview + + + + + + + + + + Subscription + + + + + + + + + + + PreviewButton + + + + + + + + + + + + + + SendConfig + + + + + + + + + + + + + + + 0 + 0 + 800 + 25 + + + + + + + + From 578aedd0405b152c934ce9d0ae8d53541eaeeec4 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 24 Mar 2015 11:58:23 +0100 Subject: [PATCH 188/260] Added initial value for the slider's configuration --- papi/gui/qt_new/item.py | 6 +- papi/last_active_papi.xml | 176 +------------------------- papi/ui/gui/qt_new/CreateRecording.py | 2 +- papi/ui/gui/qt_new/create.py | 6 +- papi/ui/gui/qt_new/create_dialog.py | 6 +- papi/ui/gui/qt_new/main.py | 6 +- papi/ui/gui/qt_new/overview.py | 6 +- 7 files changed, 11 insertions(+), 197 deletions(-) diff --git a/papi/gui/qt_new/item.py b/papi/gui/qt_new/item.py index d4666f38..16873ffa 100644 --- a/papi/gui/qt_new/item.py +++ b/papi/gui/qt_new/item.py @@ -425,9 +425,6 @@ def setData(self, index, value, role): return True return False -<<<<<<< Updated upstream -======= - class CustomFieldModel(QStandardItemModel): def __init__(self, parent=None): @@ -676,5 +673,4 @@ def data(self, role): if role == Qt.EditRole: return None - return None ->>>>>>> Stashed changes + return None \ No newline at end of file diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 367f54ce..58c55fe1 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,179 +1,13 @@ -<<<<<<< Updated upstream - + - - - default - - -======= - - - ->>>>>>> Stashed changes - - 771 - 853 + + 771 + + - - Slider - - - 1.0 - - - PaPI-Tab - Used for tabs - - - PaPI Slider - Used for window title - - - 11 - [0-9]+ - - - (150,75) - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - 1 - - - 0.0 - - - (239,57) - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - 1 - - - 0.81 - Used as initial value for the Slider - ([-]{0,1}\d+(.\d+)?) - - - SliderX3 - - - - - - - SliderParameter1 - - - - - - - Slider - - - 1.0 - - - PaPI-Tab - Used for tabs - - - PaPI Slider - Used for window title - - - 11 - [0-9]+ - - - (150,75) - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - 1 - - - 0.0 - - - (0,0) - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - 1 - - - 0.57 - Used as initial value for the Slider - ([-]{0,1}\d+(.\d+)?) - - - Slider - - - - - - - SliderParameter1 - - - - - - - Slider - - - 1.0 - - - PaPI-Tab - Used for tabs - - - PaPI Slider - Used for window title - - - 11 - [0-9]+ - - - (150,75) - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - 1 - - - 0.0 - - - (57,173) - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - 1 - - - 0.79 - Used as initial value for the Slider - ([-]{0,1}\d+(.\d+)?) - - - SliderX2 - - - - - - - SliderParameter1 - - - - - diff --git a/papi/ui/gui/qt_new/CreateRecording.py b/papi/ui/gui/qt_new/CreateRecording.py index ec1939d8..5f913cdf 100644 --- a/papi/ui/gui/qt_new/CreateRecording.py +++ b/papi/ui/gui/qt_new/CreateRecording.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/CreateRecording.ui' # -# Created: Tue Mar 24 11:15:40 2015 +# Created: Tue Mar 24 11:57:40 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/create.py b/papi/ui/gui/qt_new/create.py index 1f892669..ad1bbdd2 100644 --- a/papi/ui/gui/qt_new/create.py +++ b/papi/ui/gui/qt_new/create.py @@ -2,11 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/create.ui' # -<<<<<<< Updated upstream -# Created: Mon Mar 16 16:37:08 2015 -======= -# Created: Tue Mar 24 11:15:40 2015 ->>>>>>> Stashed changes +# Created: Tue Mar 24 11:57:40 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/create_dialog.py b/papi/ui/gui/qt_new/create_dialog.py index ce00c165..a35e8e56 100644 --- a/papi/ui/gui/qt_new/create_dialog.py +++ b/papi/ui/gui/qt_new/create_dialog.py @@ -2,11 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/create_dialog.ui' # -<<<<<<< Updated upstream -# Created: Mon Mar 16 16:37:08 2015 -======= -# Created: Tue Mar 24 11:15:40 2015 ->>>>>>> Stashed changes +# Created: Tue Mar 24 11:57:40 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/main.py b/papi/ui/gui/qt_new/main.py index bf31f2d4..ccb92784 100644 --- a/papi/ui/gui/qt_new/main.py +++ b/papi/ui/gui/qt_new/main.py @@ -2,11 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/main.ui' # -<<<<<<< Updated upstream -# Created: Mon Mar 16 16:37:08 2015 -======= -# Created: Tue Mar 24 11:15:40 2015 ->>>>>>> Stashed changes +# Created: Tue Mar 24 11:57:40 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! diff --git a/papi/ui/gui/qt_new/overview.py b/papi/ui/gui/qt_new/overview.py index 8e8ae0cf..9da26dad 100644 --- a/papi/ui/gui/qt_new/overview.py +++ b/papi/ui/gui/qt_new/overview.py @@ -2,11 +2,7 @@ # Form implementation generated from reading ui file 'ui/gui/qt_new/overview.ui' # -<<<<<<< Updated upstream -# Created: Mon Mar 16 16:37:08 2015 -======= -# Created: Tue Mar 24 11:15:40 2015 ->>>>>>> Stashed changes +# Created: Tue Mar 24 11:57:40 2015 # by: pyside-uic 0.2.15 running on PySide 1.2.1 # # WARNING! All changes made in this file will be lost! From 77c81521416031965dab1ca409290d7ec19e080c Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 24 Mar 2015 12:18:22 +0100 Subject: [PATCH 189/260] refined some gui items created a gui_management object and added it to gui now yapsy plugin manager is not collecting at every creation. Now there is a reload option --- .../AutoConfigDemo_ReplaceableSimulation.ipar | 272 ++++-------------- papi/last_active_papi.xml | 68 ++--- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 8 +- 3 files changed, 97 insertions(+), 251 deletions(-) diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar index abbf4fd8..edd1d918 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar @@ -40,7 +40,7 @@ 10 54 0 - 5245 + 5091 36 1 1 @@ -56,19 +56,19 @@ -1 -1 -1 - 1426768201 + 1427195026 -1 -1 2015 3 - 12 - 78 - 5 - 19 13 - 30 - 1 - 925 + 83 + 3 + 24 + 12 + 3 + 46 + 853 26 79 82 @@ -162,137 +162,137 @@ 100 332 8 - 3336 + 3182 13 222 100 - 3668 + 3514 21 6 1 224 100 - 3674 + 3520 22 8 0 226 100 - 3682 + 3528 22 6 0 228 100 - 3688 + 3534 22 6 1 230 100 - 3694 + 3540 23 6 0 232 100 - 3700 + 3546 23 6 1 234 100 - 3706 + 3552 24 6 0 236 100 - 3712 + 3558 24 6 1 238 100 - 3718 + 3564 25 6 0 240 100 - 3724 + 3570 25 76 0 242 100 - 3800 + 3646 25 9 0 244 100 - 3809 + 3655 25 161 0 245 100 - 3970 + 3816 25 6 0 247 100 - 3976 + 3822 25 6 1 248 100 - 3982 + 3828 26 6 2 250 100 - 3988 + 3834 28 6 0 252 100 - 3994 + 3840 28 688 6 254 100 - 4682 + 4528 34 6 2 256 100 - 4688 + 4534 36 37 0 257 100 - 4725 + 4571 36 6 0 259 100 - 4731 + 4577 36 78 0 100 101 - 4809 + 4655 36 236 0 @@ -630,7 +630,7 @@ 0 15011 220 - 3330 + 3176 13 1 0 @@ -676,11 +676,11 @@ 4 12 0 - 3267 + 3113 0 21 1 - 3279 + 3125 0 1 13 @@ -696,7 +696,7 @@ 1 1 1 - 3266 + 3112 1 7 10 @@ -739,7 +739,7 @@ 10 35 0 - 3187 + 3033 13 0 0 @@ -866,11 +866,11 @@ 100 286 6 - 2623 + 2469 7 100 101 - 2909 + 2755 13 180 0 @@ -1162,7 +1162,7 @@ 0 15013 232 - 2617 + 2463 7 1 0 @@ -1208,11 +1208,11 @@ 4 20 0 - 2546 + 2392 0 21 1 - 2566 + 2412 0 1 7 @@ -1236,7 +1236,7 @@ 1 1 1 - 2545 + 2391 1 9 1 @@ -1285,11 +1285,11 @@ 10 438 1 - 1906 + 1752 5 902 10 - 2344 + 2190 6 145 1 @@ -1732,7 +1732,7 @@ 0 0 1 - 22 + 18 205 100 0 @@ -1827,43 +1827,19 @@ 100 1340 5 - 16 - 0 - 235 - 100 - 1356 - 5 - 36 - 0 - 236 - 100 - 1392 - 5 - 21 - 0 - 237 - 100 - 1413 - 5 - 25 - 0 - 238 - 100 - 1438 - 5 9 0 - 240 + 236 100 - 1447 + 1349 5 161 0 100 101 - 1608 + 1510 5 - 164 + 132 0 60032 205 @@ -3205,106 +3181,8 @@ 2 0 0 - 170 - 234 - 10 - 0 - 1 - 0 - 1 - 8 - 83 - 101 - 110 - 100 - 101 - 114 - 73 - 68 - 170 - 235 - 30 - 0 - 1 - 0 - 1 - 28 - 83 - 101 - 110 - 116 - 32 - 99 - 111 - 110 - 102 - 105 - 103 - 32 - 112 - 97 - 99 - 107 - 101 - 116 - 32 - 110 - 117 - 109 - 98 - 101 - 114 - 32 - 49 - 32 - 170 - 236 - 15 - 0 - 1 - 0 - 1 - 13 - 80 - 97 - 99 - 107 - 101 - 116 - 32 - 67 - 111 - 117 - 110 - 116 - 32 - 170 - 237 - 19 - 0 - 1 - 0 - 1 - 17 - 78 - 117 - 109 - 98 - 101 - 114 - 32 - 111 - 102 - 32 - 80 - 97 - 99 - 107 - 101 - 116 - 32 60035 - 238 + 234 3 0 1 @@ -3313,7 +3191,7 @@ 0 1082 39004 - 240 + 236 155 0 1 @@ -3474,7 +3352,7 @@ 98 106 0 - 20 + 16 8 4 1 @@ -3582,52 +3460,20 @@ 232 4 0 - 222 - 222 - 0 - 0 - 234 - 234 - 0 - 0 - 214 - 214 - 0 - 0 - 235 - 235 - 0 - 0 - 226 - 226 - 0 - 0 - 236 - 236 - 0 - 0 - 218 - 218 - 0 - 0 - 237 - 237 - 0 - 0 232 232 0 0 - 240 - 240 + 236 + 236 0 0 - 238 - 238 + 234 + 234 0 0 - 240 - 240 + 236 + 236 1 0 211 diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index b5208ec4..7cc81a2d 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,18 +1,18 @@ - + + + + default + + - - 771 - 853 + + 771 + - - - default - - OrtdController @@ -20,29 +20,29 @@ ORTDController + + Used for tabs + Tab + - (0,0) - 1 \(([0-9]+),([0-9]+)\) Determine position: (x,y) + (0,0) + 1 + + + ORTDPlugin1 + 0 + Uname to use for ortd plugin instance OrtdController - (378,313) - 1 \(([0-9]+),([0-9]+)\) Determine size: (height,width) - - - PaPI-Tab - Used for tabs - - - ORTDPlugin1 - 0 - Uname to use for ortd plugin instance + (300,300) + 1 @@ -64,25 +64,28 @@ ORTD_UDP - - 20000 - 1 - - - 127.0.0.1 - 1 - + file /home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json 0 - file + 1 20001 + + 1 + 20000 + + + 1 + 127.0.0.1 - + + 0 + 0 + @@ -101,6 +104,7 @@ ControllerSignalClose + diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 2f2e3feb..ce6df365 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -240,12 +240,6 @@ def process_received_package(self, rev): ORTDSources, ORTDParameters, plToCreate, \ plToClose, subscriptions, paraConnections = self.extract_config_elements(cfg) - # print(ORTDSources) - # print(ORTDParameters) - # print(plToCreate) - # print(plToClose) - # print(subscriptions) - # print(paraConnections) self.update_block_list(ORTDSources) self.update_parameter_list(ORTDParameters) @@ -254,6 +248,8 @@ def process_received_package(self, rev): def process_papi_configuration(self, toCreate, toClose, subs, paraConnections): + print(toCreate) + self.send_new_data('ControllerSignals', [1], {'ControlSignalReset': 1, 'ControlSignalCreate':None, 'ControlSignalSub':None, From ebcced1c7f269c7f74a4a4fa59634b24ad58c7b9 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 24 Mar 2015 15:46:35 +0100 Subject: [PATCH 190/260] added a possibility for delaying core events to be processed after init of a plugin was completed. Now no more sleeps needed before sub after pl creation --- cfg_collection/sinus_add_example.xml | 275 +- .../AutoConfigDemo_ReplaceableSimulation.ipar | 3628 ++++++++++++----- .../AutoConfigDemo_ReplaceableSimulation.rpar | 9 +- papi/core.py | 241 +- papi/event/data/EditDPluginByUname.py | 42 + papi/event/data/__init__.py | 1 + papi/event/instruction/SubsribeByUname.py | 40 + papi/event/instruction/__init__.py | 1 + papi/gui/gui_api.py | 31 +- papi/last_active_papi.xml | 324 +- papi/plugin/base_classes/pcp_base.py | 3 +- papi/plugin/dpp/add/Add.py | 2 +- papi/plugin/pcp/button/Button.py | 2 + papi/plugin/pcp/slider/Slider.py | 2 + .../visual/OrtdController/OrtdController.py | 2 +- 15 files changed, 3282 insertions(+), 1321 deletions(-) create mode 100644 papi/event/data/EditDPluginByUname.py create mode 100644 papi/event/instruction/SubsribeByUname.py diff --git a/cfg_collection/sinus_add_example.xml b/cfg_collection/sinus_add_example.xml index 7e8f3616..d09e15d1 100644 --- a/cfg_collection/sinus_add_example.xml +++ b/cfg_collection/sinus_add_example.xml @@ -1,36 +1,44 @@ - - - + + + + + 771 + + + 853 + + + + + default + + + Sinus - - SinusPlugin + + \d+.{0,1}\d* + 2 - 5 [0-9]+ + 5 - - 2 - \d+.{0,1}\d* + + SinusPlugin - 1.0 + 0.6 - - - f2_1 - - f3_1 - f3_2 + f3_2312323wd f3_scalar @@ -41,110 +49,116 @@ f1_f1DNAME + + + f2_1 + + Plot - - (0,0) - \(([0-9]+),([0-9]+)\) - 1 - Determine position: (x,y) - - - 1 - (\d+) + + Used for window title + Sinus - - Plot + + (\d+\.\d+) + [0.0 1.0] + 1 + y: range - Style + ^\[(\s*\d\s*)+\] [0 0 0 0 0] + 1 + Style + + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] 1 + Color - - bool + + (\d+) 1 - ^(1|0)$ - y: auto range - 1 - - Color - [0 1 2 3 4] - ^\[(\s*\d\s*)+\] + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + (0,0) - + bool - 0 ^(1|0)$ - Grid-X + 0 + Grid-Y - + bool - 1 ^(1|0)$ - x: auto range - 1 + 0 + Grid-X - - Sinus - Used for window title + + Plot - x: range - [0.0 1.0] (\d+\.\d+) + [0.0 1.0] 1 + x: range - - (300,300) - \(([0-9]+),([0-9]+)\) + + ^([1-9][0-9]{0,3}|10000)$ + 2000 1 - Determine size: (height,width) + Buffersize + + + bool + ^(1|0)$ + 1 + 1 + y: auto range bool - 0 ^(1|0)$ + 0 Rolling Plot - - y: range - [0.0 1.0] - (\d+\.\d+) - 1 + + Used for tabs + Tab - + bool - 0 ^(1|0)$ - Grid-Y + 1 + 1 + x: auto range - - Buffersize - 2000 - ^([1-9][0-9]{0,3}|10000)$ + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + (300,300) - [0,1] 0 - 1 - 0 + [0,1] [0 0 0 0 0] - 1 - 2000 [0 1 2 3 4] - [0,1] + 1 + 2000 0 - 1 + 0 @@ -161,104 +175,105 @@ Plot - - (450,0) - \(([0-9]+),([0-9]+)\) - 1 - Determine position: (x,y) - - - 1 - (\d+) + + Used for window title + Add - - PlotX2 + + (\d+\.\d+) + [0.0 1.0] + 1 + y: range - Style + ^\[(\s*\d\s*)+\] [0 0 0 0 0] + 1 + Style + + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] 1 + Color - - bool + + (\d+) 1 - ^(1|0)$ - y: auto range - 1 - - Color - [0 1 2 3 4] - ^\[(\s*\d\s*)+\] + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + (450,0) - + bool - 0 ^(1|0)$ - Grid-X + 0 + Grid-Y - + bool - 1 ^(1|0)$ - x: auto range - 1 + 0 + Grid-X - - Add - Used for window title + + PlotX2 - x: range - [0.0 1.0] (\d+\.\d+) + [0.0 1.0] + 1 + x: range + + + ^([1-9][0-9]{0,3}|10000)$ + 2000 1 + Buffersize - - (300,300) - \(([0-9]+),([0-9]+)\) + + bool + ^(1|0)$ + 1 1 - Determine size: (height,width) + y: auto range bool - 0 ^(1|0)$ + 0 Rolling Plot - - y: range - [0.0 1.0] - (\d+\.\d+) - 1 + + Used for tabs + Tab - + bool - 0 ^(1|0)$ - Grid-Y + 1 + 1 + x: auto range - - Buffersize - 2000 - ^([1-9][0-9]{0,3}|10000)$ + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + (300,300) - [0,1] 0 - 1 - 0 + [0,1] [0 0 0 0 0] - 1 - 2000 [0 1 2 3 4] - [0,1] + 1 + 2000 0 - 1 + 0 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar index edd1d918..41f7dd74 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar @@ -40,8 +40,8 @@ 10 54 0 - 5091 - 36 + 6639 + 41 1 1 2 @@ -56,7 +56,7 @@ -1 -1 -1 - 1427195026 + 1427206727 -1 -1 2015 @@ -65,10 +65,10 @@ 83 3 24 - 12 - 3 - 46 - 853 + 15 + 18 + 47 + 341 26 79 82 @@ -97,7 +97,7 @@ 105 99 1 - 33 + 51 202 100 0 @@ -126,175 +126,283 @@ 100 167 3 + 42 + 0 + 210 + 100 + 209 + 3 6 1 - 211 + 212 100 - 173 + 215 4 49 0 - 213 + 214 100 - 222 + 264 4 6 1 - 215 + 216 100 - 228 + 270 5 49 0 - 217 - 100 - 277 - 5 - 49 - 2 218 100 - 326 - 7 + 319 + 5 6 1 220 100 - 332 - 8 - 3182 - 13 + 325 + 6 + 6 + 1 222 100 - 3514 - 21 + 331 + 7 + 8 + 0 + 224 + 100 + 339 + 7 + 6 + 0 + 226 + 100 + 345 + 7 6 1 - 224 + 228 100 - 3520 - 22 + 351 8 + 6 0 - 226 + 230 100 - 3528 - 22 + 357 + 8 + 6 + 1 + 232 + 100 + 363 + 9 6 0 - 228 + 234 100 - 3534 - 22 + 369 + 9 + 76 + 0 + 236 + 100 + 445 + 9 + 9 + 0 + 238 + 100 + 454 + 9 + 161 + 0 + 239 + 100 + 615 + 9 6 1 - 230 + 241 100 - 3540 - 23 + 621 + 10 + 8 + 0 + 243 + 100 + 629 + 10 6 0 - 232 + 245 100 - 3546 - 23 + 635 + 10 6 1 - 234 + 247 100 - 3552 - 24 + 641 + 11 6 0 - 236 + 249 100 - 3558 - 24 + 647 + 11 6 1 - 238 + 251 100 - 3564 - 25 + 653 + 12 6 0 - 240 + 253 100 - 3570 - 25 + 659 + 12 76 0 - 242 + 255 100 - 3646 - 25 + 735 + 12 9 0 - 244 + 257 100 - 3655 - 25 + 744 + 12 161 0 - 245 + 258 + 100 + 905 + 12 + 49 + 2 + 259 + 100 + 954 + 14 + 6 + 1 + 261 100 - 3816 - 25 + 960 + 15 + 4001 + 13 + 263 + 100 + 4961 + 28 + 6 + 1 + 265 + 100 + 4967 + 29 + 8 + 0 + 267 + 100 + 4975 + 29 6 0 - 247 + 269 100 - 3822 - 25 + 4981 + 29 6 1 - 248 + 271 100 - 3828 - 26 + 4987 + 30 6 - 2 - 250 + 0 + 273 100 - 3834 - 28 + 4993 + 30 + 6 + 1 + 275 + 100 + 4999 + 31 6 0 - 252 + 277 100 - 3840 - 28 - 688 + 5005 + 31 6 - 254 + 1 + 279 100 - 4528 - 34 + 5011 + 32 6 - 2 - 256 + 0 + 281 100 - 4534 - 36 - 37 + 5017 + 32 + 76 0 - 257 + 283 100 - 4571 - 36 + 5093 + 32 + 9 + 0 + 285 + 100 + 5102 + 32 + 161 + 0 + 286 + 100 + 5263 + 32 6 0 - 259 + 288 100 - 4577 - 36 - 78 + 5269 + 32 + 6 + 1 + 289 + 100 + 5275 + 33 + 6 + 2 + 291 + 100 + 5281 + 35 + 6 0 + 293 + 100 + 5287 + 35 + 688 + 6 100 101 - 4655 - 36 - 236 + 5975 + 41 + 356 0 40 202 @@ -463,14 +571,56 @@ 1 1 0 - 40 + 170 209 + 36 + 0 + 1 + 0 + 1 + 34 + 67 + 97 + 115 + 101 + 32 + 115 + 108 + 105 + 100 + 101 + 114 + 76 + 67 + 68 + 112 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 98 + 97 + 114 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 58 + 32 + 40 + 210 0 1 1 0 15007 - 211 + 212 43 0 1 @@ -519,13 +669,13 @@ 114 121 40 - 213 + 214 0 1 1 0 15007 - 215 + 216 43 0 1 @@ -573,65 +723,60 @@ 111 114 121 - 15005 - 217 - 43 - 2 + 20 + 218 + 0 + 1 1 0 + 40 + 220 0 - 257 + 1 + 1 + 0 + 60005 + 222 2 + 0 1 0 0 + 100000 + 60031 + 224 0 0 + 1 0 + 40 + 226 + 0 + 1 + 1 + 0 + 60031 + 228 + 0 + 0 + 1 0 - 32 - 83 - 119 - 105 - 116 - 99 - 104 - 105 - 110 - 103 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 77 - 101 - 109 - 111 - 114 - 121 - 46 - 109 - 101 - 109 - 111 - 114 - 121 40 - 218 + 230 0 1 1 0 - 15011 - 220 - 3176 - 13 + 60031 + 232 + 0 + 0 + 1 + 0 + 39011 + 234 + 70 + 0 1 0 1 @@ -640,112 +785,198 @@ 4 0 0 - 2 + 5 0 11 4 - 2 + 5 0 2 0 12 4 - 4 + 7 0 - 2 + 5 0 13 4 - 6 + 12 0 2 0 14 4 - 8 + 14 0 2 0 15 4 - 10 + 16 0 2 0 20 4 - 12 + 18 0 - 3113 + 1 0 21 1 - 3125 + 19 0 1 - 13 + 0 + 4 1 1 1 1 1 + 20 + 4 + 2 + 2 + 2 257 1 - 257 + 6 1 1 1 + 2 + 0 + 0 + 60035 + 236 + 3 + 0 1 - 3112 + 0 1 - 7 + 0 + 20 + 39004 + 238 + 155 + 0 + 1 + 0 + 1 + 9 10 4 0 0 - 1 + 3 0 11 4 - 1 + 3 0 1 0 12 4 - 2 + 4 0 - 1 + 3 0 13 4 - 3 + 7 0 1 0 - 21 + 14 4 + 8 + 0 + 2 + 0 + 15 4 + 10 0 - 27 + 2 0 - 22 + 20 4 - 31 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 0 + 30 4 + 56 0 - 900 + 43 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 10 - 35 + 4 0 - 3033 - 13 0 + 2 + 0 + 11 + 4 + 2 0 + 2 0 + 12 + 4 + 4 0 - 26 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 83 119 105 @@ -765,118 +996,161 @@ 102 105 103 - 84 - 104 - 114 - 101 97 - 100 - 49 - 3 - 2 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 239 0 - -1 1 - 16 - 201 - 100 + 1 0 + 60005 + 241 + 2 0 - 137 + 1 0 - 204 - 100 - 137 0 - 76 + 100000 + 60031 + 243 0 - 209 - 100 - 213 0 - 6 + 1 0 - 211 - 100 - 219 + 40 + 245 0 - 6 1 - 212 - 100 - 225 1 - 6 - 2 - 214 - 100 - 231 - 3 - 6 + 0 + 60031 + 247 + 0 + 0 1 - 216 - 100 - 237 + 0 + 40 + 249 + 0 + 1 + 1 + 0 + 60031 + 251 + 0 + 0 + 1 + 0 + 39011 + 253 + 70 + 0 + 1 + 0 + 1 + 8 + 10 4 - 6 0 - 218 - 100 - 243 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 4 7 0 - 220 - 100 - 250 + 5 + 0 + 13 4 - 6 + 12 0 - 222 - 100 - 256 + 2 + 0 + 14 4 - 6 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 1 - 224 - 100 - 262 - 5 - 6 0 - 226 - 100 - 268 - 5 - 6 + 21 + 1 + 19 0 - 228 - 100 - 274 - 5 - 6 1 - 230 - 100 - 280 - 6 - 6 0 - 232 - 100 - 286 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 6 - 2469 - 7 - 100 - 101 - 2755 - 13 - 180 + 1 + 1 + 1 + 2 0 - 39003 - 201 - 131 + 0 + 60035 + 255 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 257 + 155 0 1 0 @@ -886,25 +1160,25 @@ 4 0 0 - 1 + 3 0 11 4 - 1 - 0 3 0 + 1 + 0 12 4 4 0 - 1 + 3 0 13 4 - 5 + 7 0 - 3 + 1 0 14 4 @@ -922,35 +1196,35 @@ 4 12 0 - 19 + 43 0 21 1 - 31 + 55 0 1 0 30 4 - 32 + 56 0 43 - 0 0 2 - 1396 - 6 + 20 + 1 0 2 6 - 6 + 2 + 0 1 1 1 1 - 18 + 42 1 - 2 + 4 10 4 0 @@ -963,10 +1237,34 @@ 0 2 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 1 - 1396 + 20 1 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 0 42 83 @@ -1011,10 +1309,65 @@ 79 98 106 - 39012 - 204 - 70 + 15005 + 258 + 43 + 2 + 1 + 0 + 0 + 257 + 2 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 32 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 259 0 + 1 + 1 + 0 + 15011 + 261 + 3995 + 13 1 0 1 @@ -1029,465 +1382,848 @@ 4 2 0 - 5 + 2 0 12 4 - 7 + 4 0 2 0 13 4 - 9 + 6 0 - 5 + 2 0 14 4 - 14 + 8 0 2 0 15 4 - 16 + 10 0 2 0 20 4 - 18 + 12 0 - 1 + 3932 0 21 1 - 19 + 3944 0 1 - 0 + 13 1 - 1396 - 4 1 1 1 - 173 1 - 6 - 4 - 2 - 2 - 2 + 257 + 1 257 1 1 1 - 2 - 0 - 0 - 60032 - 209 + 1 + 3931 + 1 + 7 + 10 + 4 0 0 1 0 - 40 - 211 - 0 + 11 + 4 1 + 0 1 0 12 - 212 - 0 + 4 2 + 0 1 0 - 60020 - 214 + 13 + 4 + 3 0 1 - 1 0 - 60014 - 216 + 21 + 4 + 4 0 + 27 0 - 1 + 22 + 4 + 31 0 - 60043 - 218 - 1 + 4 0 - 1 + 900 + 10 + 35 0 - -3 - 60032 - 220 + 3852 + 13 0 0 - 1 0 - 60020 - 222 0 + 26 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 84 + 104 + 114 + 101 + 97 + 100 + 49 + 3 + 2 + 0 + -1 1 - 1 + 16 + 201 + 100 0 - 60014 - 224 0 + 137 0 - 1 + 204 + 100 + 137 0 - 60022 - 226 + 76 0 + 209 + 100 + 213 0 - 1 + 6 0 - 60020 - 228 + 211 + 100 + 219 0 + 6 + 1 + 212 + 100 + 225 1 + 6 + 2 + 214 + 100 + 231 + 3 + 6 1 + 216 + 100 + 237 + 4 + 6 0 - 60034 - 230 + 218 + 100 + 243 + 4 + 7 + 0 + 220 + 100 + 250 + 4 + 6 + 0 + 222 + 100 + 256 + 4 + 6 + 1 + 224 + 100 + 262 + 5 + 6 0 + 226 + 100 + 268 + 5 + 6 0 + 228 + 100 + 274 + 5 + 6 1 + 230 + 100 + 280 + 6 + 6 0 - 15013 232 - 2463 + 100 + 286 + 6 + 3288 7 + 100 + 101 + 3574 + 13 + 180 + 0 + 39003 + 201 + 131 + 0 1 0 1 - 8 + 9 10 4 0 0 - 6 + 1 0 11 4 - 6 + 1 0 - 2 + 3 0 12 4 - 8 + 4 0 - 6 + 1 0 13 4 - 14 + 5 0 - 2 + 3 0 14 4 - 16 + 8 0 2 0 15 4 - 18 + 10 0 2 0 20 4 - 20 + 12 0 - 2392 + 19 0 21 1 - 2412 + 31 0 1 - 7 - 5 - 1 + 0 + 30 + 4 + 32 + 0 + 43 + 0 + 0 + 2 + 1396 + 6 + 0 + 2 + 6 + 6 1 1 - 173 1 1 + 18 1 - 5 2 + 10 + 4 + 0 + 0 2 + 0 + 11 + 4 2 - 257 + 0 2 + 0 1 - 257 - 1 - 1 - 1 + 1396 1 - 2391 + 6 + 0 + 42 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 39012 + 204 + 70 + 0 1 - 9 + 0 1 + 8 + 10 4 0 0 - 3 + 2 0 - 10 + 11 4 - 3 + 2 0 5 0 - 11 + 12 4 - 8 + 7 0 2 0 - 12 + 13 4 - 10 + 9 0 5 0 - 13 + 14 4 - 15 + 14 0 2 0 - 21 + 15 4 - 17 + 16 0 - 20 + 2 0 - 900 - 10 - 37 + 20 + 4 + 18 0 - 401 1 - 901 - 10 - 438 + 0 + 21 1 - 1752 - 5 - 902 - 10 - 2190 - 6 - 145 + 19 + 0 1 - 2 - 3 + 0 1 + 1396 4 1 1 1 173 1 - 1 + 6 4 2 2 2 257 1 - 257 - 19 - 83 - 119 - 105 - 116 - 99 - 104 - 83 - 101 - 108 - 101 - 99 - 116 - 80 - 97 - 80 - 105 - 67 - 109 - 100 1 - 10 - 205 - 100 - 0 - 0 - 6 - 0 - 207 - 100 - 6 + 1 + 2 0 - 6 0 + 60032 209 - 100 - 12 0 - 6 0 + 1 + 0 + 40 211 - 100 - 18 0 - 6 1 - 213 - 100 - 24 1 - 81 0 - 215 - 100 - 105 + 12 + 212 + 0 + 2 1 - 81 0 - 217 - 100 - 186 + 60020 + 214 + 0 + 1 1 - 6 0 - 219 - 100 - 192 + 60014 + 216 + 0 + 0 1 - 6 0 - 221 - 100 - 198 + 60043 + 218 1 - 49 0 - 100 - 101 - 247 1 - 92 0 + -3 60032 - 205 + 220 0 0 1 0 - 60032 - 207 + 60020 + 222 + 0 + 1 + 1 + 0 + 60014 + 224 0 0 1 0 - 60032 - 209 + 60022 + 226 0 0 1 0 - 40 - 211 + 60020 + 228 0 1 1 0 - 60304 - 213 - 75 + 60034 + 230 + 0 0 1 0 + 15013 + 232 + 3282 + 7 + 1 + 0 1 8 10 4 0 0 - 2 + 6 0 11 4 - 2 + 6 0 2 0 12 4 - 4 + 8 0 - 2 + 6 0 13 4 - 6 + 14 0 2 0 14 4 - 8 + 16 0 2 0 15 4 - 10 + 18 0 2 0 20 4 - 12 + 20 0 - 12 + 3211 0 21 1 - 24 + 3231 0 1 - 0 + 7 + 5 1 1 1 + 173 1 1 - 2 1 + 5 + 2 + 2 + 2 + 257 2 1 + 257 1 1 - 2 - 11 1 1 - 10 + 3210 + 1 + 9 + 1 + 4 + 0 + 0 + 3 + 0 + 10 + 4 + 3 + 0 + 5 + 0 + 11 + 4 + 8 + 0 + 2 + 0 + 12 + 4 + 10 + 0 + 5 + 0 + 13 + 4 + 15 + 0 + 2 + 0 + 21 + 4 + 17 + 0 + 20 + 0 + 900 + 10 + 37 + 0 + 401 + 1 + 901 + 10 + 438 + 1 + 2571 + 5 + 902 + 10 + 3009 + 6 + 145 + 1 + 2 + 3 + 1 + 4 + 1 + 1 + 1 + 173 + 1 + 1 + 4 + 2 + 2 + 2 + 257 + 1 + 257 + 19 + 83 + 119 + 105 + 116 + 99 + 104 + 83 + 101 + 108 + 101 + 99 + 116 + 80 + 97 + 80 + 105 + 67 + 109 + 100 + 1 + 10 + 205 + 100 + 0 + 0 + 6 + 0 + 207 + 100 + 6 + 0 + 6 + 0 + 209 + 100 + 12 + 0 + 6 + 0 + 211 + 100 + 18 + 0 + 6 + 1 + 213 + 100 + 24 + 1 + 81 + 0 + 215 + 100 + 105 + 1 + 81 + 0 + 217 + 100 + 186 + 1 + 6 + 0 + 219 + 100 + 192 + 1 + 6 + 0 + 221 + 100 + 198 + 1 + 49 + 0 + 100 + 101 + 247 + 1 + 92 + 0 + 60032 + 205 + 0 + 0 + 1 + 0 + 60032 + 207 + 0 + 0 + 1 + 0 + 60032 + 209 + 0 + 0 + 1 + 0 + 40 + 211 + 0 + 1 + 1 + 0 + 60304 + 213 + 75 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 2 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 12 + 0 + 21 + 1 + 24 + 0 + 1 + 0 + 1 + 1 + 1 + 1 + 1 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 11 + 1 + 1 + 10 4 0 0 @@ -1815,29 +2551,29 @@ 100 111 5 - 1151 + 1970 0 232 100 - 1262 + 2081 5 78 0 234 100 - 1340 + 2159 5 9 0 236 100 - 1349 + 2168 5 161 0 100 101 - 1510 + 2329 5 132 0 @@ -1954,7 +2690,7 @@ 0 60305 230 - 1145 + 1964 0 1 0 @@ -2000,17 +2736,17 @@ 4 10 0 - 1084 + 1903 0 21 1 - 1094 + 1913 0 1 0 0 1 - 1066 + 1885 0 1 6 @@ -2018,7 +2754,7 @@ 1 1 2 - 1083 + 1902 1 2 10 @@ -2031,11 +2767,11 @@ 4 2 0 - 1067 + 1886 0 1 - 1066 - 1066 + 1885 + 1885 32 123 34 @@ -2058,24 +2794,750 @@ 32 123 10 + 32 + 34 + 48 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 34 + 112 + 101 + 114 + 99 + 101 + 110 + 116 + 34 + 32 + 44 + 32 + 34 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 34 + 32 + 58 + 32 + 34 + 49 + 34 + 44 + 32 + 34 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 34 + 32 + 58 + 32 + 34 + 50 + 53 + 55 + 34 + 32 + 32 + 125 + 32 + 44 + 10 + 32 + 34 + 49 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 83 + 111 + 117 + 114 + 99 + 101 + 78 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 34 + 76 + 67 + 68 + 86 + 97 + 108 + 34 + 32 + 44 + 32 + 34 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 95 + 115 + 101 + 110 + 100 + 34 + 32 + 58 + 32 + 34 + 49 + 34 + 44 + 32 + 34 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 34 + 32 + 58 + 32 + 34 + 50 + 53 + 55 + 34 + 32 + 32 + 125 + 32 + 10 + 125 + 32 + 44 + 32 + 10 + 32 + 34 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 115 + 67 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 10 + 32 + 34 + 48 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 34 + 111 + 107 + 34 + 32 + 44 + 32 + 34 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 34 + 32 + 58 + 32 + 34 + 49 + 34 + 44 + 32 + 34 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 34 + 32 + 58 + 32 + 34 + 50 + 53 + 55 + 34 + 32 + 32 + 125 + 32 + 44 + 10 + 32 + 34 + 49 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 34 + 115 + 108 + 105 + 100 + 101 + 114 + 86 + 97 + 108 + 34 + 32 + 44 + 32 + 34 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 34 + 32 + 58 + 32 + 34 + 49 + 34 + 44 + 32 + 34 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 34 + 32 + 58 + 32 + 34 + 50 + 53 + 55 + 34 + 32 + 32 + 125 + 32 + 10 + 125 + 44 + 32 + 10 + 34 + 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 80 + 108 + 117 + 103 + 105 + 110 + 49 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 105 + 100 + 101 + 110 + 116 + 105 + 102 + 105 + 101 + 114 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 66 + 117 + 116 + 116 + 111 + 110 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 99 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 110 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 76 + 101 + 97 + 118 + 101 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 105 + 122 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 49 + 53 + 48 + 44 + 53 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 54 + 48 + 48 + 44 + 50 + 50 + 53 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 116 + 97 + 98 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 79 + 107 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 76 + 101 + 97 + 118 + 105 + 110 + 103 + 34 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 80 + 108 + 117 + 103 + 105 + 110 + 50 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 105 + 100 + 101 + 110 + 116 + 105 + 102 + 105 + 101 + 114 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 83 + 108 + 105 + 100 + 101 + 114 + 34 + 32 125 32 - 44 32 - 10 + 44 32 34 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 115 - 67 + 99 111 110 102 @@ -2086,10 +3548,12 @@ 58 32 123 - 10 32 34 - 48 + 110 + 97 + 109 + 101 34 32 58 @@ -2097,76 +3561,118 @@ 123 32 34 - 80 - 97 - 114 + 118 97 - 109 + 108 + 117 101 - 116 + 34 + 32 + 58 + 32 + 34 + 83 + 108 + 105 + 100 101 114 - 78 + 32 + 69 + 120 97 109 + 112 + 108 101 34 32 - 58 + 125 + 32 + 32 + 44 32 34 - 79 115 - 99 105 + 122 + 101 34 32 - 44 + 58 + 32 + 123 32 34 - 78 - 86 + 118 97 108 117 101 - 115 34 32 58 32 34 - 49 + 40 + 53 + 48 + 48 + 44 + 55 + 53 + 41 34 + 32 + 125 + 32 + 32 44 32 34 - 100 - 97 + 112 + 111 + 115 + 105 116 + 105 + 111 + 110 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 97 - 116 - 121 - 112 + 108 + 117 101 34 32 58 32 34 - 50 + 40 53 - 55 + 48 + 44 + 53 + 48 + 41 34 32 - 32 125 + 32 32 44 - 10 32 34 - 49 + 116 + 97 + 98 34 32 58 @@ -2174,100 +3680,120 @@ 123 32 34 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 78 + 118 97 - 109 + 108 + 117 101 34 32 58 32 34 - 83 - 108 - 105 - 100 - 101 - 114 - 76 - 67 - 68 80 - 114 - 111 - 103 - 114 - 101 - 115 - 115 + 97 + 80 + 73 + 45 + 84 + 97 + 98 34 32 + 125 + 32 + 32 44 32 34 - 78 - 86 + 115 + 116 + 101 + 112 + 95 + 99 + 111 + 117 + 110 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 97 108 117 101 - 115 34 32 58 32 34 49 + 48 + 49 34 + 32 + 125 + 32 + 32 44 32 34 + 108 + 111 + 119 + 101 + 114 + 95 + 98 + 111 + 117 + 110 100 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 97 - 116 - 97 - 116 - 121 - 112 + 108 + 117 101 34 32 58 32 34 - 50 - 53 - 55 + 48 + 46 + 48 34 32 - 32 125 32 - 10 - 125 + 32 44 32 - 10 34 - 80 - 97 - 80 - 73 - 67 + 117 + 112 + 112 + 101 + 114 + 95 + 98 111 + 117 110 - 102 - 105 - 103 + 100 34 32 58 @@ -2275,19 +3801,31 @@ 123 32 34 - 84 - 111 - 67 - 114 - 101 + 118 97 - 116 + 108 + 117 101 34 32 - 58 + 58 + 32 + 34 + 49 + 46 + 48 + 34 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 125 + 32 32 - 123 + 44 32 34 80 @@ -2296,7 +3834,7 @@ 103 105 110 - 49 + 51 34 32 58 @@ -2331,12 +3869,17 @@ 58 32 34 - 66 - 117 - 116 - 116 + 80 + 114 111 - 110 + 103 + 114 + 101 + 115 + 115 + 66 + 97 + 114 34 32 125 @@ -2379,16 +3922,25 @@ 58 32 34 - 79 + 80 + 114 + 111 + 103 + 114 + 101 115 - 99 - 105 - 108 - 108 + 115 + 66 97 - 116 - 111 114 + 32 + 69 + 120 + 97 + 109 + 112 + 108 + 101 34 32 125 @@ -2419,13 +3971,12 @@ 32 34 40 - 50 53 48 - 44 - 49 - 48 48 + 44 + 55 + 53 41 34 32 @@ -2461,12 +4012,11 @@ 32 34 40 - 50 53 48 44 - 49 - 48 + 50 + 53 48 41 34 @@ -2507,93 +4057,6 @@ 98 34 32 - 125 - 32 - 32 - 44 - 32 - 34 - 115 - 116 - 97 - 116 - 101 - 49 - 95 - 116 - 101 - 120 - 116 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 71 - 111 - 32 - 116 - 111 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 115 - 116 - 97 - 116 - 101 - 50 - 95 - 116 - 101 - 120 - 116 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 76 - 101 - 97 - 118 - 105 - 110 - 103 - 32 - 116 - 111 - 34 - 32 125 32 32 @@ -2612,7 +4075,7 @@ 103 105 110 - 50 + 52 34 32 58 @@ -2647,12 +4110,16 @@ 58 32 34 - 66 - 117 - 116 - 116 - 111 - 110 + 76 + 67 + 68 + 68 + 105 + 115 + 112 + 108 + 97 + 121 34 32 125 @@ -2695,28 +4162,17 @@ 58 32 34 - 83 - 108 - 105 - 100 - 101 - 114 - 32 76 67 68 32 - 80 - 114 - 111 - 103 - 114 - 101 - 115 - 115 - 66 + 69 + 120 97 - 114 + 109 + 112 + 108 + 101 34 32 125 @@ -2748,11 +4204,11 @@ 34 40 50 - 53 + 49 48 44 49 - 48 + 50 48 41 34 @@ -2789,11 +4245,11 @@ 32 34 40 - 50 + 49 + 57 53 - 48 44 - 50 + 52 53 48 41 @@ -2841,58 +4297,21 @@ 44 32 34 - 115 - 116 + 117 + 112 + 100 97 116 101 - 49 - 95 - 116 + 70 + 114 101 - 120 - 116 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 + 113 117 101 - 34 - 32 - 58 - 32 - 34 - 71 - 111 - 32 - 116 - 111 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 115 - 116 - 97 - 116 - 101 - 50 - 95 - 116 - 101 - 120 - 116 + 110 + 99 + 121 34 32 58 @@ -2904,22 +4323,14 @@ 97 108 117 - 101 - 34 - 32 - 58 - 32 - 34 - 76 - 101 - 97 - 118 - 105 - 110 - 103 + 101 + 34 32 - 116 - 111 + 58 + 32 + 34 + 53 + 48 34 32 125 @@ -3007,10 +4418,8 @@ 58 32 34 - 79 - 115 - 99 - 105 + 111 + 107 34 32 125 @@ -3043,17 +4452,17 @@ 58 32 34 - 67 + 83 108 105 + 100 + 101 + 114 + 66 + 108 + 111 99 107 - 95 - 69 - 118 - 101 - 110 - 116 34 32 44 @@ -3073,25 +4482,171 @@ 58 32 34 - 83 + 115 108 105 100 101 114 - 76 - 67 - 68 + 86 + 97 + 108 + 34 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 84 + 111 + 83 + 117 + 98 + 34 + 32 + 58 + 32 + 123 + 32 + 34 80 + 108 + 117 + 103 + 105 + 110 + 51 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 115 + 105 + 103 + 110 + 97 + 108 + 115 + 34 + 32 + 58 + 32 + 91 + 34 + 112 + 101 114 + 99 + 101 + 110 + 116 + 34 + 93 + 32 + 44 + 32 + 34 + 98 + 108 111 - 103 + 99 + 107 + 34 + 32 + 58 + 32 + 34 + 83 + 111 + 117 114 + 99 101 + 71 + 114 + 111 + 117 + 112 + 48 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 80 + 108 + 117 + 103 + 105 + 110 + 52 + 34 + 32 + 58 + 32 + 123 + 32 + 34 115 + 105 + 103 + 110 + 97 + 108 115 34 32 + 58 + 32 + 91 + 34 + 76 + 67 + 68 + 86 + 97 + 108 + 34 + 93 + 32 + 44 + 32 + 34 + 98 + 108 + 111 + 99 + 107 + 34 + 32 + 58 + 32 + 34 + 83 + 111 + 117 + 114 + 99 + 101 + 71 + 114 + 111 + 117 + 112 + 48 + 34 + 32 125 32 32 @@ -3164,9 +4719,9 @@ 1 1 1 - 1066 + 1885 1 - 1082 + 1901 5 2 2 @@ -3189,7 +4744,7 @@ 0 1 0 - 1082 + 1901 39004 236 155 @@ -3253,7 +4808,7 @@ 43 0 2 - 1082 + 1901 1 0 2 @@ -3292,7 +4847,7 @@ 10 0 1 - 1082 + 1901 1 6 1 @@ -3811,13 +5366,13 @@ 4 13 40 - 222 + 263 0 1 1 0 60005 - 224 + 265 2 0 1 @@ -3825,49 +5380,49 @@ 0 100000 60031 - 226 + 267 0 0 1 0 40 - 228 + 269 0 1 1 0 60031 - 230 + 271 0 0 1 0 40 - 232 + 273 0 1 1 0 60031 - 234 + 275 0 0 1 0 40 - 236 + 277 0 1 1 0 60031 - 238 + 279 0 0 1 0 39011 - 240 + 281 70 0 1 @@ -3943,7 +5498,7 @@ 0 0 60035 - 242 + 283 3 0 1 @@ -3952,7 +5507,7 @@ 0 16 39004 - 244 + 285 155 0 1 @@ -4113,31 +5668,31 @@ 98 106 60023 - 245 + 286 0 0 1 0 40 - 247 + 288 0 1 1 0 12 - 248 + 289 0 2 1 0 60034 - 250 + 291 0 0 1 0 15013 - 252 + 293 682 6 1 @@ -4808,151 +6363,24 @@ 225 0 0 - 223 - 223 - 0 - 0 - 225 - 225 - 1 - 0 - 201 - 201 - 0 - 1 - 0 - 0 - 0 - 6 - 12 - 254 - 0 - 2 - 1 - 0 - 15006 - 256 - 31 - 0 - 1 - 0 - 0 - 257 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 20 - 78 - 101 - 120 - 116 - 83 - 116 - 97 - 116 - 101 - 68 - 97 - 116 - 97 - 46 - 109 - 101 - 109 - 111 - 114 - 121 - 60022 - 257 - 0 - 0 - 1 + 223 + 223 0 - 170 - 259 - 72 0 + 225 + 225 1 0 + 201 + 201 + 0 1 - 70 - 67 - 97 - 115 - 101 - 32 - 99 - 104 - 111 - 111 - 115 - 101 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 97 - 99 - 116 - 105 - 118 - 101 - 46 - 32 - 78 - 101 - 120 - 116 - 32 - 69 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 105 - 115 - 32 - 40 - 48 - 32 - 109 - 101 - 97 - 110 - 115 - 32 - 110 - 111 - 32 - 99 - 104 - 97 - 110 - 103 - 101 - 41 - 58 0 - 29 + 0 + 0 + 6 + 0 + 44 8 4 0 @@ -4962,30 +6390,34 @@ 2 204 204 + 0 + 1 + 0 + 0 0 0 209 209 0 0 - 211 - 211 + 210 + 210 0 0 - 213 - 213 - 0 + 212 + 212 0 - 215 - 215 0 + 214 + 214 0 0 + 216 + 216 0 0 - 2 - 217 - 217 + 216 + 216 0 0 218 @@ -5000,8 +6432,8 @@ 222 0 0 - 224 - 224 + 222 + 222 0 0 224 @@ -5021,6 +6453,10 @@ 0 0 232 + 232 + 0 + 0 + 232 232 0 0 @@ -5028,160 +6464,272 @@ 234 0 0 - 236 - 236 + 224 + 224 + 0 + 0 + 234 + 234 + 1 + 0 + 228 + 228 + 0 + 0 + 234 + 234 + 2 + 0 + 218 + 218 + 0 + 0 + 234 + 234 + 3 + 0 + 234 + 234 0 0 238 238 0 0 + 236 + 236 + 0 + 0 238 238 + 1 0 + 239 + 239 0 - 240 - 240 0 + 241 + 241 0 - 226 - 226 + 0 + 241 + 241 + 0 + 0 + 243 + 243 + 0 + 0 + 245 + 245 + 0 + 0 + 247 + 247 + 0 + 0 + 249 + 249 + 0 + 0 + 251 + 251 + 0 + 0 + 251 + 251 + 0 + 0 + 253 + 253 + 0 + 0 + 243 + 243 0 0 - 240 - 240 + 253 + 253 1 0 - 230 - 230 + 247 + 247 0 0 - 240 - 240 + 253 + 253 2 0 - 234 - 234 + 216 + 216 0 0 - 240 - 240 + 253 + 253 3 0 - 240 - 240 + 253 + 253 0 0 - 244 - 244 + 257 + 257 0 0 - 242 - 242 + 255 + 255 0 0 - 244 - 244 + 257 + 257 1 0 - 245 - 245 0 0 - 248 - 248 0 + 2 + 258 + 258 0 - 247 - 247 0 + 259 + 259 0 - 248 - 248 - 1 0 - 248 - 248 + 261 + 261 0 0 - 250 - 250 + 263 + 263 0 0 - 250 - 250 + 265 + 265 0 0 - 252 - 252 + 265 + 265 0 0 - 211 - 211 + 267 + 267 0 0 - 254 - 254 + 269 + 269 0 0 - 215 - 215 + 271 + 271 + 0 + 0 + 273 + 273 0 0 - 254 - 254 + 275 + 275 + 0 + 0 + 277 + 277 + 0 + 0 + 279 + 279 + 0 + 0 + 279 + 279 + 0 + 0 + 281 + 281 + 0 + 0 + 267 + 267 + 0 + 0 + 281 + 281 1 0 - 254 - 254 + 271 + 271 0 0 - 256 - 256 + 281 + 281 + 2 0 + 275 + 275 0 - 207 - 207 0 + 281 + 281 + 3 + 0 + 281 + 281 0 - 256 - 256 + 0 + 285 + 285 + 0 + 0 + 283 + 283 + 0 + 0 + 285 + 285 1 0 - 254 - 254 + 286 + 286 0 0 - 257 - 257 + 289 + 289 0 0 - 207 - 207 + 288 + 288 0 0 - 257 - 257 + 289 + 289 1 0 - 254 - 254 + 289 + 289 0 0 - 259 - 259 + 291 + 291 + 0 + 0 + 291 + 291 0 0 - 254 - 254 + 293 + 293 + 0 + 0 + 216 + 216 0 1 0 0 0 0 - 257 - 257 + 212 + 212 0 1 0 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar index bb646cca..41d9d5e8 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar @@ -3,6 +3,13 @@ 1.0000000000000000000000000 1.0000000000000000000000000 2.0000000000000000000000000 +100.0000000000000000000000000 +1.0000000000000000000000000 +0.0000000000000000000000000 +1295793.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1295793.0000000000000000000000000 0.0000000000000000000000000 0.0000000000000000000000000 1.0000000000000000000000000 @@ -32,5 +39,3 @@ -2.0000000000000000000000000 0.0000000000000000000000000 1295793.0000000000000000000000000 -1.0000000000000000000000000 -2.0000000000000000000000000 diff --git a/papi/core.py b/papi/core.py index 2140c161..944464a7 100644 --- a/papi/core.py +++ b/papi/core.py @@ -78,23 +78,25 @@ def __init__(self, gui_start_function, use_gui=True): 'plugin_stopped': self.__process_plugin_stopped__ } - self.__process_data_event_l__ = {'new_data': self.__process_new_data__, - 'new_block': self.__process_new_block__, - 'new_parameter': self.__process_new_parameter__, - 'edit_dplugin': self.__process_edit_dplugin, - 'delete_block': self.__process_delete_block__, - 'delete_parameter': self.__delete_parameter__ + self.__process_data_event_l__ = {'new_data': self.__process_new_data__, + 'new_block': self.__process_new_block__, + 'new_parameter': self.__process_new_parameter__, + 'edit_dplugin': self.__process_edit_dplugin, + 'edit_dplugin_by_uname': self.__process_edit_dplugin_by_uname__, + 'delete_block': self.__process_delete_block__, + 'delete_parameter': self.__delete_parameter__ } - self.__process_instr_event_l__ = {'create_plugin': self.__process_create_plugin__, - 'stop_plugin': self.__process_stop_plugin__, - 'close_program': self.__process_close_programm__, - 'subscribe': self.__process_subscribe__, - 'unsubscribe': self.__process_unsubsribe__, - 'set_parameter': self.__process_set_parameter__, - 'pause_plugin': self.__process_pause_plugin__, - 'resume_plugin': self.__process_resume_plugin__, - 'start_plugin': self.__process_start_plugin__ + self.__process_instr_event_l__ = {'create_plugin': self.__process_create_plugin__, + 'stop_plugin': self.__process_stop_plugin__, + 'close_program': self.__process_close_programm__, + 'subscribe': self.__process_subscribe__, + 'subscribe_by_uname': self.__process_subscribe_by_uname__, + 'unsubscribe': self.__process_unsubsribe__, + 'set_parameter': self.__process_set_parameter__, + 'pause_plugin': self.__process_pause_plugin__, + 'resume_plugin': self.__process_resume_plugin__, + 'start_plugin': self.__process_start_plugin__ } # creating the main core data object DCore and core queue @@ -123,6 +125,9 @@ def __init__(self, gui_start_function, use_gui=True): self.alive_count = 0 self.gui_alive_count = 0 + # + self.core_delayed_operation_queue = [] + def run(self): """ Main operation function of core. @@ -339,6 +344,64 @@ def handle_parameter_change(self, plugin, parameter_name, value): else: return -1 + def new_subscription(self, subscriber_id, source_id, block_name, signals, sub_alias, orginal_event=None): + """ + Gets information of a new and wanted subscription. + This methond will try to create the wanted subscription. + + :param subscriber_id: Id of the plugin that want to get data + :param source_id: Id of the plugin that will be the data source + :param block_name: name of source block of source plugin + :param signals: signals to subscribe + :param sub_alias: optional alias for parameter + :return: + """ + already_sub = False + # test if already subscribed + source_pl = self.core_data.get_dplugin_by_id(source_id) + if source_pl is not None: + + if source_pl.state != PLUGIN_STATE_START_SUCCESFUL: + self.core_delayed_operation_queue.append(orginal_event) + self.log.printText(2, 'subscribe, event was placed in delayed queue because plugin with id: ' + + str(source_id) + ' is still starting.') + return 0 + + blocks = source_pl.get_dblocks() + if block_name in blocks: + b = blocks[block_name] + subs = b.get_subscribers() + if subscriber_id in subs: + already_sub = True + + if already_sub is False: + dsubscription = self.core_data.subscribe(subscriber_id, source_id, block_name) + if dsubscription is None: + # subscribtion failed + self.log.printText(1, 'subscribe, something failed in subsription process with subscriber id: ' + str( + subscriber_id) + '..target id:' + str(source_id) + '..and block ' + str(block_name)) + return -1 + else: + # subscribtion correct + dsubscription.alias = sub_alias + + if signals != []: + if self.core_data.subscribe_signals(subscriber_id, source_id, block_name, signals) is None: + # subscribtion failed + self.log.printText(1, 'subscribe, something failed in subsription process with subscriber id: ' + str( + subscriber_id) + '..target id:' + str(source_id) + '..and block ' + str(block_name) + + '. A Problem with signals') + return -1 + else: + pass + + self.log.printText(1, 'subscribe, subscribtion correct: ' + str(subscriber_id) + '->(' + str(source_id) + ',' + str( + block_name) + ')') + self.update_meta_data_to_gui(subscriber_id) + self.update_meta_data_to_gui(source_id) + return ERROR.NO_ERROR + + # ------- Event processing initial stage --------- def __process_event__(self, event): @@ -396,6 +459,10 @@ def __process_start_successfull__(self, event): # plugin exists and sent successfull event, so change it state dplug.state = PLUGIN_STATE_START_SUCCESFUL self.update_meta_data_to_gui(dplug.id) + + # process delayed_operation_queue + while len(self.core_delayed_operation_queue) != 0: + self.core_event_queue.put(self.core_delayed_operation_queue.pop(0)) return 1 else: # plugin does not exist @@ -645,25 +712,45 @@ def __process_edit_dplugin(self, event): pl = self.core_data.get_dplugin_by_id(dID) - # DBlock of DPlugin should be edited - if isinstance(eObject, DBlock): + if pl is not None: + if pl.state != PLUGIN_STATE_START_SUCCESFUL: + self.core_delayed_operation_queue.append(event) + self.log.printText(2, 'edit_dplugin, event was placed in delayed queue because plugin with name: ' + + pl.uname + ' is still starting') + + return 0 + + # DBlock of DPlugin should be edited + if isinstance(eObject, DBlock): + + dblock = pl.get_dblock_by_name(eObject.name) + + if "edit" in cRequest: + cObject = cRequest["edit"] + + # Signal should be modified in DBlock + if isinstance(cObject, DSignal): + dsignal = dblock.get_signal_by_uname(cObject.uname) - dblock = pl.get_dblock_by_name(eObject.name) + dsignal.dname = cObject.dname - if "edit" in cRequest: - cObject = cRequest["edit"] + self.log.printText(3, + 'edit_dplugin, Edited Dblock ' + dblock.name + ' of DPlugin ' + pl.uname + + " : DSignal " + dsignal.uname + " to dname -> " + dsignal.dname) - # Signal should be modified in DBlock - if isinstance(cObject, DSignal): - dsignal = dblock.get_signal_by_uname(cObject.uname) + self.update_meta_data_to_gui(pl.id, True) - dsignal.dname = cObject.dname + def __process_edit_dplugin_by_uname__(self, event): - self.log.printText(3, - 'edit_dplugin, Edited Dblock ' + dblock.name + ' of DPlugin ' + pl.uname + - " : DSignal " + dsignal.uname + " to dname -> " + dsignal.dname) + dplugin = self.core_data.get_dplugin_by_uname(event.plugin_uname) + if dplugin is not None: + pl_id = dplugin.id + + idEvent = Event.data.EditDPlugin(self.gui_id, pl_id, event.editedObject, event.changeRequest) - self.update_meta_data_to_gui(pl.id, True) + self.__process_edit_dplugin(idEvent) + else: + self.log.printText(1, " Do edit plugin with uname " + event.plugin_uname + ' failed') # ------- Event processing second stage: instr events --------- def __process_create_plugin__(self, event): @@ -923,43 +1010,71 @@ def __process_subscribe__(self, event): """ # get event origin id and optional parameters opt = event.get_optional_parameter() - oID = event.get_originID() - - already_sub = False - # test if already subscribed - source_pl = self.core_data.get_dplugin_by_id(opt.source_ID) - if source_pl is not None: - blocks = source_pl.get_dblocks() - if opt.block_name in blocks: - b = blocks[opt.block_name] - subs = b.get_subscribers() - if oID in subs: - already_sub = True - - if already_sub is False: - dsubscription = self.core_data.subscribe(oID, opt.source_ID, opt.block_name) - if dsubscription is None: - # subscribtion failed - self.log.printText(1, 'subscribe, something failed in subsription process with subscriber id: ' + str( - oID) + '..target id:' + str(opt.source_ID) + '..and block ' + str(opt.block_name)) - return -1 - else: - # subscribtion correct - dsubscription.alias = opt.subscription_alias + self.new_subscription(event.get_originID(), opt.source_ID, opt.block_name, opt.signals, opt.subscription_alias, + orginal_event=event) + + # already_sub = False + # # test if already subscribed + # source_pl = self.core_data.get_dplugin_by_id(opt.source_ID) + # if source_pl is not None: + # blocks = source_pl.get_dblocks() + # if opt.block_name in blocks: + # b = blocks[opt.block_name] + # subs = b.get_subscribers() + # if oID in subs: + # already_sub = True + # + # if already_sub is False: + # dsubscription = self.core_data.subscribe(oID, opt.source_ID, opt.block_name) + # if dsubscription is None: + # # subscribtion failed + # self.log.printText(1, 'subscribe, something failed in subsription process with subscriber id: ' + str( + # oID) + '..target id:' + str(opt.source_ID) + '..and block ' + str(opt.block_name)) + # return -1 + # else: + # # subscribtion correct + # dsubscription.alias = opt.subscription_alias + # + # if opt.signals != []: + # if self.core_data.subscribe_signals(oID, opt.source_ID, opt.block_name, opt.signals) is None: + # # subscribtion failed + # self.log.printText(1, 'subscribe, something failed in subsription process with subscriber id: ' + str( + # oID) + '..target id:' + str(opt.source_ID) + '..and block ' + str(opt.block_name)) + # return -1 + # else: + # pass + # + # self.log.printText(1, 'subscribe, subscribtion correct: ' + str(oID) + '->(' + str(opt.source_ID) + ',' + str( + # opt.block_name) + ')') + # self.update_meta_data_to_gui(oID) + # self.update_meta_data_to_gui(opt.source_ID) + + def __process_subscribe_by_uname__(self, event): + """ + Process subscribe_event. + Will set a new route in DCore for this two plugins to route new data events. Update of meta will be send to GUI. - if opt.signals != []: - if self.core_data.subscribe_signals(oID, opt.source_ID, opt.block_name, opt.signals) is None: - # subscribtion failed - self.log.printText(1, 'subscribe, something failed in subsription process with subscriber id: ' + str( - oID) + '..target id:' + str(opt.source_ID) + '..and block ' + str(opt.block_name)) - return -1 - else: - pass + :param event: event to process + :type event: PapiEventBase + :type dplugin_sub: DPlugin + :type dplugin_source: DPlugin + """ + subscriber_id = self.core_data.get_dplugin_by_uname(event.subscriber_uname).id + #self.do_get_plugin_id_from_uname(event.subscriber_uname) + if subscriber_id is None: + # plugin with uname does not exist + self.log.printText(1, 'do_subscribe, sub uname worng') + return -1 - self.log.printText(1, 'subscribe, subscribtion correct: ' + str(oID) + '->(' + str(opt.source_ID) + ',' + str( - opt.block_name) + ')') - self.update_meta_data_to_gui(oID) - self.update_meta_data_to_gui(opt.source_ID) + source_id = self.core_data.get_dplugin_by_uname(event.source_uname).id + #self.do_get_plugin_id_from_uname(event.source_uname) + if source_id is None: + # plugin with uname does not exist + self.log.printText(1, 'do_subscribe, target uname wrong') + return -1 + #print(subscriber_id, source_id, event.block_name, event.signals, event.sub_alias) + self.new_subscription(subscriber_id, source_id, event.block_name, event.signals, event.sub_alias, + orginal_event=event) def __process_unsubsribe__(self, event): """ diff --git a/papi/event/data/EditDPluginByUname.py b/papi/event/data/EditDPluginByUname.py new file mode 100644 index 00000000..6b3dd98e --- /dev/null +++ b/papi/event/data/EditDPluginByUname.py @@ -0,0 +1,42 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Sven Knuth +""" + +__author__ = 'Sven Knuth' + +from papi.event.data.DataBase import DataBase + + +class EditDPluginByUname(DataBase): + def __init__(self, oID, plugin_uname, eObject, changeRequest): + super().__init__(oID, None, 'edit_dplugin_by_uname', None) + #Object of DPlugin, which should be changed + self.editedObject = eObject + #Kind of action. e.g. {'edit': DSignal} + self.changeRequest = changeRequest + + self.plugin_uname = plugin_uname diff --git a/papi/event/data/__init__.py b/papi/event/data/__init__.py index 9c501f1d..fbbb20c2 100644 --- a/papi/event/data/__init__.py +++ b/papi/event/data/__init__.py @@ -5,5 +5,6 @@ from .NewBlock import NewBlock from .NewParameter import NewParameter from .EditDPlugin import EditDPlugin +from .EditDPluginByUname import EditDPluginByUname from .DeleteBlock import DeleteBlock from .DeleteParameter import DeleteParameter \ No newline at end of file diff --git a/papi/event/instruction/SubsribeByUname.py b/papi/event/instruction/SubsribeByUname.py new file mode 100644 index 00000000..19d773d4 --- /dev/null +++ b/papi/event/instruction/SubsribeByUname.py @@ -0,0 +1,40 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class SubscribeByUname(InstructionBase): + def __init__(self, oID, destID, subscriber_uname, source_uname, block_name, signals=None, sub_alias=None): + super().__init__(oID, destID, 'subscribe_by_uname', None) + self.source_uname = source_uname + self.subscriber_uname = subscriber_uname + self.block_name = block_name + self.signals = signals + self.sub_alias = sub_alias diff --git a/papi/event/instruction/__init__.py b/papi/event/instruction/__init__.py index 1ef00e1e..7da24f85 100644 --- a/papi/event/instruction/__init__.py +++ b/papi/event/instruction/__init__.py @@ -7,6 +7,7 @@ from .ResumePlugin import ResumePlugin from .startPlugin import StartPlugin from .Subscribe import Subscribe +from .SubsribeByUname import SubscribeByUname from .Unsubscribe import Unsubscribe from .SetParameter import SetParameter from .CloseProgram import CloseProgram diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index a7ab8506..6bea0791 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -149,12 +149,9 @@ def do_edit_plugin_uname(self, uname, eObject, changeRequest): :param changeRequest: :return: """ - pl_id = self.do_get_plugin_id_from_uname(uname) + event = Event.data.EditDPluginByUname(self.gui_id, uname, eObject, changeRequest) - if pl_id is not None: - self.do_edit_plugin(pl_id, eObject, changeRequest) - else: - self.log.printText(1, " Do edit plugin with uname " + uname + ' failed') + self.core_queue.put(event) def do_stopReset_pluign(self, id): """ @@ -246,20 +243,9 @@ def do_subscribe_uname(self, subscriber_uname, source_uname, block_name, signals :type block_name: basestring :return: """ - subscriber_id = self.do_get_plugin_id_from_uname(subscriber_uname) - if subscriber_id is None: - # plugin with uname does not exist - self.log.printText(1, 'do_subscribe, sub uname worng') - return -1 - - source_id = self.do_get_plugin_id_from_uname(source_uname) - if source_id is None: - # plugin with uname does not exist - self.log.printText(1, 'do_subscribe, target uname wrong') - return -1 - - # call do_subscribe with ids to subscribe - self.do_subscribe(subscriber_id, source_id, block_name, signals, sub_alias) + event = Event.instruction.SubscribeByUname(self.gui_id, 0, subscriber_uname, source_uname, block_name, + signals=signals, sub_alias= sub_alias) + self.core_queue.put(event) def do_unsubscribe(self, subscriber_id, source_id, block_name, signal_index=None): """ @@ -562,9 +548,10 @@ def do_load_xml(self, path): # 0: ident, 1: uname, 2: config self.do_create_plugin(pl[0], pl[1], pl[2]) - QtCore.QTimer.singleShot(CONFIG_LOADER_SUBSCRIBE_DELAY, \ - lambda: self.config_loader_subs(plugins_to_start, subs_to_make, \ - parameters_to_change, signals_to_change)) + # QtCore.QTimer.singleShot(CONFIG_LOADER_SUBSCRIBE_DELAY, \ + # lambda: self.config_loader_subs(plugins_to_start, subs_to_make, \ + # parameters_to_change, signals_to_change)) + self.config_loader_subs(plugins_to_start, subs_to_make, parameters_to_change, signals_to_change) def change_uname_to_uniqe(self, uname): """ diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 7cc81a2d..74d00802 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,4 +1,4 @@ - + @@ -6,106 +6,310 @@ - - 853 - 771 + + 853 + - - OrtdController + + Sinus - - ORTDController + + 5 + [0-9]+ - - Used for tabs - Tab + + SinusPlugin - + + 2 + \d+.{0,1}\d* + + + + 0.6 + + + + + f1_f1DNAME + + + + + f2_1 + + + + + f3_1 + + + f3_2312323wd + + + f3_scalar + + + + + + + Plot + + + 1 + Determine size: (height,width) \(([0-9]+),([0-9]+)\) + (300,300) + + + ^(1|0)$ + bool + 0 + Grid-X + + + ^(1|0)$ + bool + 0 + Grid-Y + + + 1 + ^([1-9][0-9]{0,3}|10000)$ + 2000 + Buffersize + + + 1 + ^(1|0)$ + bool + 1 + x: auto range + + + Sinus + Used for window title + + + 1 Determine position: (x,y) + \(([0-9]+),([0-9]+)\) (0,0) + + + 1 + ^(1|0)$ + bool + 1 + y: auto range + + 1 + (\d+\.\d+) + [0.0 1.0] + x: range + + + 1 + (\d+\.\d+) + [0.0 1.0] + y: range + + + Tab + Used for tabs - - ORTDPlugin1 - 0 - Uname to use for ortd plugin instance + + ^(1|0)$ + bool + 0 + Rolling Plot - OrtdController + Plot - - \(([0-9]+),([0-9]+)\) - Determine size: (height,width) - (300,300) + + 1 + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] + Color + + 1 + ^\[(\s*\d\s*)+\] + [0 0 0 0 0] + Style + + + 1 + (\d+) - + + [0,1] + 2000 + 0 + 0 + [0 1 2 3 4] + 1 + [0 0 0 0 0] + 0 + - ORTDPlugin1 - + SinusPlugin + - ControlSignalReset - ControlSignalCreate - ControlSignalSub - ControllerSignalParameter - ControllerSignalClose + f3_1 + f3_2 - - ORTD_UDP + + Plot - - file - /home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json - 0 + + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + (300,300) + + + ^(1|0)$ + bool + 0 + Grid-X + + + ^(1|0)$ + bool + 0 + Grid-Y + + + 1 + ^([1-9][0-9]{0,3}|10000)$ + 2000 + Buffersize + + + 1 + ^(1|0)$ + bool + 1 + x: auto range + + + Add + Used for window title + + + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + (450,0) - + 1 - 20001 + ^(1|0)$ + bool + 1 + y: auto range - + 1 - 20000 + (\d+\.\d+) + [0.0 1.0] + x: range - + 1 - 127.0.0.1 + (\d+\.\d+) + [0.0 1.0] + y: range + + + Tab + Used for tabs + + + ^(1|0)$ + bool + 0 + Rolling Plot + + + PlotX2 + + + 1 + ^\[(\s*\d\s*)+\] + [0 1 2 3 4] + Color + + + 1 + ^\[(\s*\d\s*)+\] + [0 0 0 0 0] + Style + + + 1 + (\d+) - 0 - 0 + [0,1] + 2000 + 0 + 0 + [0 1 2 3 4] + 1 + [0 0 0 0 0] + 0 + + + + Add + + + Sum + + + + + + Add + + + Add + + + - - - ControlSignalReset - - - ControlSignalCreate - - - ControlSignalSub - - - ControllerSignalParameter - - - ControllerSignalClose + + + Sum - - + + + SinusPlugin + + + f3_1 + f3_2 + + + diff --git a/papi/plugin/base_classes/pcp_base.py b/papi/plugin/base_classes/pcp_base.py index 046f7614..67759dc6 100644 --- a/papi/plugin/base_classes/pcp_base.py +++ b/papi/plugin/base_classes/pcp_base.py @@ -35,8 +35,7 @@ class pcp_base(base_visual): def initiate_layer_1(self, config): - - self.initiate_layer_0(config) + return self.initiate_layer_0(config) def initiate_layer_0(self, config): raise NotImplementedError("Please Implement this method") diff --git a/papi/plugin/dpp/add/Add.py b/papi/plugin/dpp/add/Add.py index 6fd6d838..541fd759 100644 --- a/papi/plugin/dpp/add/Add.py +++ b/papi/plugin/dpp/add/Add.py @@ -44,7 +44,7 @@ class Add(dpp_base): def start_init(self, config=None): self.t = 0 - print(['ADD: process id: ',os.getpid()] ) + #print(['ADD: process id: ',os.getpid()] ) self.approx_max = 300 self.fac= 1 self.amax = 20 diff --git a/papi/plugin/pcp/button/Button.py b/papi/plugin/pcp/button/Button.py index 4865e284..3967f6aa 100644 --- a/papi/plugin/pcp/button/Button.py +++ b/papi/plugin/pcp/button/Button.py @@ -68,6 +68,8 @@ def initiate_layer_0(self, config): self.button.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.button.customContextMenuRequested.connect(self.show_context_menu) + return True + def show_context_menu(self, pos): gloPos = self.button.mapToGlobal(pos) diff --git a/papi/plugin/pcp/slider/Slider.py b/papi/plugin/pcp/slider/Slider.py index f1433eca..7a6fea51 100644 --- a/papi/plugin/pcp/slider/Slider.py +++ b/papi/plugin/pcp/slider/Slider.py @@ -49,6 +49,8 @@ def initiate_layer_0(self, config): self.send_new_block_list([block]) self.set_widget_for_internal_usage(self.create_widget()) + return True + def create_widget(self): self.central_widget = QWidget() diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index df3c33cf..66381f7a 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -124,7 +124,7 @@ def execute_cfg(self): pl_cfg = cfg[pl_uname] self.control_api.do_create_plugin(pl_cfg['identifier']['value'],pl_uname, pl_cfg['config']) self.plugin_started_list.append(pl_uname) - time.sleep(1.5) + ############################ # Create Subs # From 31f9683104f989fc63bb7b055880e0b7f39aea6c Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 24 Mar 2015 16:26:44 +0100 Subject: [PATCH 191/260] added a possibility for delaying core events to be processed after init of a plugin was completed. Now no more sleeps needed before sub after pl creation --- .../AutoConfigDemo_ReplaceableSimulation.ipar | 1553 ++++++++--------- .../AutoConfigDemo_ReplaceableSimulation.rpar | 16 +- papi/core.py | 68 +- papi/event/instruction/StopPluginByUname.py | 38 + papi/event/instruction/__init__.py | 1 + papi/gui/gui_api.py | 9 +- papi/last_active_papi.xml | 318 +--- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 28 +- .../visual/OrtdController/OrtdController.py | 3 +- 9 files changed, 826 insertions(+), 1208 deletions(-) create mode 100644 papi/event/instruction/StopPluginByUname.py diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar index 41f7dd74..ecd845ad 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar @@ -40,8 +40,8 @@ 10 54 0 - 6639 - 41 + 6478 + 55 1 1 2 @@ -56,7 +56,7 @@ -1 -1 -1 - 1427206727 + 1427210792 -1 -1 2015 @@ -65,10 +65,10 @@ 83 3 24 - 15 - 18 - 47 - 341 + 16 + 26 + 32 + 891 26 79 82 @@ -97,7 +97,7 @@ 105 99 1 - 51 + 59 202 100 0 @@ -126,283 +126,331 @@ 100 167 3 - 42 + 32 0 210 100 - 209 + 199 3 6 1 212 100 - 215 + 205 4 49 0 214 100 - 264 + 254 4 6 1 216 100 - 270 + 260 5 49 0 218 100 - 319 + 309 + 5 + 25 + 0 + 219 + 100 + 334 5 6 1 220 100 - 325 + 340 + 6 + 6 + 2 + 224 + 100 + 346 + 8 + 6 + 2 + 226 + 100 + 352 + 10 + 6 + 2 + 228 + 100 + 358 + 12 + 8 + 3 + 230 + 100 + 366 + 15 + 6 + 1 + 232 + 100 + 372 + 16 + 8 + 3 + 234 + 100 + 380 + 19 6 + 1 + 236 + 100 + 386 + 20 6 1 - 222 + 238 100 - 331 - 7 + 392 + 21 8 0 - 224 + 240 100 - 339 - 7 + 400 + 21 6 0 - 226 + 242 100 - 345 - 7 + 406 + 21 6 1 - 228 + 244 100 - 351 - 8 + 412 + 22 6 0 - 230 + 246 100 - 357 - 8 + 418 + 22 6 1 - 232 + 248 100 - 363 - 9 + 424 + 23 6 0 - 234 + 250 100 - 369 - 9 + 430 + 23 76 0 - 236 + 252 100 - 445 - 9 + 506 + 23 9 0 - 238 + 254 100 - 454 - 9 + 515 + 23 161 0 - 239 + 255 100 - 615 - 9 + 676 + 23 6 1 - 241 + 257 100 - 621 - 10 + 682 + 24 8 0 - 243 + 259 100 - 629 - 10 + 690 + 24 6 0 - 245 + 261 100 - 635 - 10 + 696 + 24 6 1 - 247 + 263 100 - 641 - 11 + 702 + 25 6 0 - 249 + 265 100 - 647 - 11 + 708 + 25 6 1 - 251 + 267 100 - 653 - 12 + 714 + 26 6 0 - 253 + 269 100 - 659 - 12 + 720 + 26 76 0 - 255 + 271 100 - 735 - 12 + 796 + 26 9 0 - 257 + 273 100 - 744 - 12 + 805 + 26 161 0 - 258 + 274 100 - 905 - 12 + 966 + 26 49 2 - 259 + 275 100 - 954 - 14 + 1015 + 28 6 1 - 261 + 277 100 - 960 - 15 - 4001 + 1021 + 29 + 3651 13 - 263 + 279 100 - 4961 - 28 + 4672 + 42 6 1 - 265 + 281 100 - 4967 - 29 + 4678 + 43 8 0 - 267 + 283 100 - 4975 - 29 + 4686 + 43 6 0 - 269 + 285 100 - 4981 - 29 + 4692 + 43 6 1 - 271 + 287 100 - 4987 - 30 + 4698 + 44 6 0 - 273 + 289 100 - 4993 - 30 + 4704 + 44 6 1 - 275 + 291 100 - 4999 - 31 + 4710 + 45 6 0 - 277 + 293 100 - 5005 - 31 + 4716 + 45 6 1 - 279 + 295 100 - 5011 - 32 + 4722 + 46 6 0 - 281 + 297 100 - 5017 - 32 + 4728 + 46 76 0 - 283 + 299 100 - 5093 - 32 + 4804 + 46 9 0 - 285 + 301 100 - 5102 - 32 + 4813 + 46 161 0 - 286 + 302 100 - 5263 - 32 + 4974 + 46 6 0 - 288 + 304 100 - 5269 - 32 + 4980 + 46 6 1 - 289 + 305 100 - 5275 - 33 + 4986 + 47 6 2 - 291 + 307 100 - 5281 - 35 + 4992 + 49 6 0 - 293 + 309 100 - 5287 - 35 + 4998 + 49 688 6 100 101 - 5975 - 41 - 356 + 5686 + 55 + 436 0 40 202 @@ -573,36 +621,26 @@ 0 170 209 - 36 + 26 0 1 0 1 - 34 + 24 67 97 115 101 32 - 115 - 108 - 105 - 100 - 101 - 114 - 76 - 67 - 68 - 112 - 114 111 - 103 - 114 - 101 115 - 115 - 98 + 99 + 105 + 108 + 108 97 + 116 + 111 114 32 97 @@ -723,58 +761,129 @@ 111 114 121 - 20 + 170 218 + 19 0 1 - 1 0 + 1 + 17 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 32 40 - 220 + 219 0 1 1 0 - 60005 - 222 - 2 + 12 + 220 0 + 2 1 0 - 0 - 100000 - 60031 + 12 224 0 - 0 + 2 1 0 - 40 + 12 226 0 - 1 + 2 1 0 - 60031 + 30 228 + 2 + 3 + 1 0 0 1 - 0 - 40 + 20 230 0 1 1 0 - 60031 + 30 232 + 2 + 3 + 1 + 0 + 0 + 1 + 20 + 234 + 0 + 1 + 1 + 0 + 40 + 236 + 0 + 1 + 1 + 0 + 60005 + 238 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 240 + 0 + 0 + 1 + 0 + 40 + 242 + 0 + 1 + 1 + 0 + 60031 + 244 + 0 + 0 + 1 + 0 + 40 + 246 + 0 + 1 + 1 + 0 + 60031 + 248 0 0 1 0 39011 - 234 + 250 70 0 1 @@ -850,7 +959,7 @@ 0 0 60035 - 236 + 252 3 0 1 @@ -859,7 +968,7 @@ 0 20 39004 - 238 + 254 155 0 1 @@ -1020,13 +1129,13 @@ 98 106 40 - 239 + 255 0 1 1 0 60005 - 241 + 257 2 0 1 @@ -1034,37 +1143,37 @@ 0 100000 60031 - 243 + 259 0 0 1 0 40 - 245 + 261 0 1 1 0 60031 - 247 + 263 0 0 1 0 40 - 249 + 265 0 1 1 0 60031 - 251 + 267 0 0 1 0 39011 - 253 + 269 70 0 1 @@ -1140,7 +1249,7 @@ 0 0 60035 - 255 + 271 3 0 1 @@ -1149,7 +1258,7 @@ 0 20 39004 - 257 + 273 155 0 1 @@ -1310,7 +1419,7 @@ 98 106 15005 - 258 + 274 43 2 1 @@ -1359,14 +1468,14 @@ 114 121 40 - 259 + 275 0 1 1 0 15011 - 261 - 3995 + 277 + 3645 13 1 0 @@ -1412,11 +1521,11 @@ 4 12 0 - 3932 + 3582 0 21 1 - 3944 + 3594 0 1 13 @@ -1432,7 +1541,7 @@ 1 1 1 - 3931 + 3581 1 7 10 @@ -1475,7 +1584,7 @@ 10 35 0 - 3852 + 3502 13 0 0 @@ -1602,11 +1711,11 @@ 100 286 6 - 3288 + 2938 7 100 101 - 3574 + 3224 13 180 0 @@ -1898,7 +2007,7 @@ 0 15013 232 - 3282 + 2932 7 1 0 @@ -1944,11 +2053,11 @@ 4 20 0 - 3211 + 2861 0 21 1 - 3231 + 2881 0 1 7 @@ -1972,7 +2081,7 @@ 1 1 1 - 3210 + 2860 1 9 1 @@ -2021,11 +2130,11 @@ 10 438 1 - 2571 + 2221 5 902 10 - 3009 + 2659 6 145 1 @@ -2551,29 +2660,29 @@ 100 111 5 - 1970 + 1620 0 232 100 - 2081 + 1731 5 78 0 234 100 - 2159 + 1809 5 9 0 236 100 - 2168 + 1818 5 161 0 100 101 - 2329 + 1979 5 132 0 @@ -2690,7 +2799,7 @@ 0 60305 230 - 1964 + 1614 0 1 0 @@ -2736,17 +2845,17 @@ 4 10 0 - 1903 + 1553 0 21 1 - 1913 + 1563 0 1 0 0 1 - 1885 + 1535 0 1 6 @@ -2754,7 +2863,7 @@ 1 1 2 - 1902 + 1552 1 2 10 @@ -2767,11 +2876,11 @@ 4 2 0 - 1886 + 1536 0 1 - 1885 - 1885 + 1535 + 1535 32 123 34 @@ -2819,13 +2928,7 @@ 58 32 34 - 112 - 101 - 114 - 99 - 101 - 110 - 116 + 88 34 32 44 @@ -2901,12 +3004,7 @@ 58 32 34 - 76 - 67 - 68 86 - 97 - 108 34 32 44 @@ -3013,8 +3111,22 @@ 58 32 34 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 111 - 107 + 114 + 32 + 105 + 110 + 112 + 117 + 116 34 32 44 @@ -3088,15 +3200,8 @@ 58 32 34 - 115 - 108 - 105 - 100 - 101 - 114 - 86 - 97 - 108 + 111 + 107 34 32 44 @@ -3267,10 +3372,16 @@ 58 32 34 - 76 - 101 + 68 + 105 + 115 + 116 + 117 + 114 + 98 97 - 118 + 110 + 99 101 34 32 @@ -3347,9 +3458,9 @@ 48 48 44 - 50 - 50 - 53 + 49 + 48 + 48 41 34 32 @@ -3423,8 +3534,13 @@ 58 32 34 - 79 - 107 + 68 + 105 + 115 + 116 + 117 + 114 + 98 34 32 125 @@ -3461,10 +3577,13 @@ 58 32 34 - 76 - 101 - 97 - 118 + 68 + 105 + 115 + 116 + 117 + 114 + 98 105 110 103 @@ -3523,12 +3642,12 @@ 58 32 34 - 83 - 108 - 105 - 100 - 101 - 114 + 66 + 117 + 116 + 116 + 111 + 110 34 32 125 @@ -3571,19 +3690,10 @@ 58 32 34 - 83 - 108 - 105 - 100 + 76 101 - 114 - 32 - 69 - 120 97 - 109 - 112 - 108 + 118 101 34 32 @@ -3615,12 +3725,12 @@ 32 34 40 + 49 53 48 - 48 44 - 55 53 + 48 41 34 32 @@ -3656,291 +3766,14 @@ 32 34 40 - 53 - 48 - 44 - 53 - 48 - 41 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 116 - 97 - 98 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 80 - 97 - 80 - 73 - 45 - 84 - 97 - 98 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 115 - 116 - 101 - 112 - 95 - 99 - 111 - 117 - 110 - 116 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 49 - 48 - 49 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 108 - 111 - 119 - 101 - 114 - 95 - 98 - 111 - 117 - 110 - 100 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 + 54 48 - 46 48 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 117 - 112 - 112 - 101 - 114 - 95 - 98 - 111 - 117 - 110 - 100 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 49 - 46 - 48 - 34 - 32 - 125 - 32 - 32 - 125 - 32 - 32 - 125 - 32 - 32 44 - 32 - 34 - 80 - 108 - 117 - 103 - 105 - 110 - 51 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 105 - 100 - 101 - 110 - 116 - 105 - 102 - 105 - 101 - 114 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 80 - 114 - 111 - 103 - 114 - 101 - 115 - 115 - 66 - 97 - 114 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 99 - 111 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 110 - 97 - 109 - 101 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 80 - 114 - 111 - 103 - 114 - 101 - 115 - 115 - 66 - 97 - 114 - 32 - 69 - 120 - 97 - 109 - 112 - 108 - 101 + 51 + 50 + 53 + 41 34 32 125 @@ -3949,10 +3782,9 @@ 44 32 34 - 115 - 105 - 122 - 101 + 116 + 97 + 98 34 32 58 @@ -3970,14 +3802,14 @@ 58 32 34 - 40 - 53 - 48 - 48 - 44 - 55 - 53 - 41 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 34 32 125 @@ -3986,14 +3818,17 @@ 44 32 34 - 112 - 111 115 - 105 116 - 105 - 111 - 110 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 34 32 58 @@ -4011,14 +3846,8 @@ 58 32 34 - 40 - 53 - 48 - 44 - 50 - 53 - 48 - 41 + 79 + 107 34 32 125 @@ -4027,9 +3856,17 @@ 44 32 34 + 115 116 97 - 98 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 34 32 58 @@ -4047,14 +3884,13 @@ 58 32 34 - 80 - 97 - 80 - 73 - 45 - 84 + 76 + 101 97 - 98 + 118 + 105 + 110 + 103 34 32 125 @@ -4075,7 +3911,7 @@ 103 105 110 - 52 + 51 34 32 58 @@ -4110,16 +3946,10 @@ 58 32 34 - 76 - 67 - 68 - 68 - 105 - 115 - 112 + 80 108 - 97 - 121 + 111 + 116 34 32 125 @@ -4162,17 +3992,13 @@ 58 32 34 - 76 - 67 - 68 - 32 - 69 - 120 - 97 - 109 - 112 + 80 108 - 101 + 111 + 116 + 32 + 88 + 86 34 32 125 @@ -4203,12 +4029,12 @@ 32 34 40 - 50 - 49 + 53 + 48 48 44 - 49 - 50 + 53 + 48 48 41 34 @@ -4245,12 +4071,8 @@ 32 34 40 - 49 - 57 - 53 + 48 44 - 52 - 53 48 41 34 @@ -4297,21 +4119,12 @@ 44 32 34 - 117 - 112 - 100 + 121 + 82 97 - 116 - 101 - 70 - 114 - 101 - 113 - 117 - 101 110 - 99 - 121 + 103 + 101 34 32 58 @@ -4329,8 +4142,18 @@ 58 32 34 - 53 + 91 + 45 + 49 + 48 + 46 + 48 + 32 + 49 48 + 46 + 48 + 93 34 32 125 @@ -4418,8 +4241,22 @@ 58 32 34 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 111 - 107 + 114 + 32 + 105 + 110 + 112 + 117 + 116 34 32 125 @@ -4452,17 +4289,17 @@ 58 32 34 - 83 + 67 108 105 - 100 - 101 - 114 - 66 - 108 - 111 99 107 + 95 + 69 + 118 + 101 + 110 + 116 34 32 44 @@ -4482,15 +4319,8 @@ 58 32 34 - 115 - 108 - 105 - 100 - 101 - 114 - 86 - 97 - 108 + 111 + 107 34 32 125 @@ -4541,82 +4371,11 @@ 32 91 34 - 112 - 101 - 114 - 99 - 101 - 110 - 116 - 34 - 93 - 32 - 44 - 32 - 34 - 98 - 108 - 111 - 99 - 107 - 34 - 32 - 58 - 32 - 34 - 83 - 111 - 117 - 114 - 99 - 101 - 71 - 114 - 111 - 117 - 112 - 48 + 88 34 - 32 - 125 - 32 - 32 44 - 32 34 - 80 - 108 - 117 - 103 - 105 - 110 - 52 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 115 - 105 - 103 - 110 - 97 - 108 - 115 - 34 - 32 - 58 - 32 - 91 - 34 - 76 - 67 - 68 86 - 97 - 108 34 93 32 @@ -4719,9 +4478,9 @@ 1 1 1 - 1885 + 1535 1 - 1901 + 1551 5 2 2 @@ -4744,7 +4503,7 @@ 0 1 0 - 1901 + 1551 39004 236 155 @@ -4808,7 +4567,7 @@ 43 0 2 - 1901 + 1551 1 0 2 @@ -4847,7 +4606,7 @@ 10 0 1 - 1901 + 1551 1 6 1 @@ -5366,13 +5125,13 @@ 4 13 40 - 263 + 279 0 1 1 0 60005 - 265 + 281 2 0 1 @@ -5380,49 +5139,49 @@ 0 100000 60031 - 267 + 283 0 0 1 0 40 - 269 + 285 0 1 1 0 60031 - 271 + 287 0 0 1 0 40 - 273 + 289 0 1 1 0 60031 - 275 + 291 0 0 1 0 40 - 277 + 293 0 1 1 0 60031 - 279 + 295 0 0 1 0 39011 - 281 + 297 70 0 1 @@ -5498,7 +5257,7 @@ 0 0 60035 - 283 + 299 3 0 1 @@ -5507,7 +5266,7 @@ 0 16 39004 - 285 + 301 155 0 1 @@ -5668,31 +5427,31 @@ 98 106 60023 - 286 + 302 0 0 1 0 40 - 288 + 304 0 1 1 0 12 - 289 + 305 0 2 1 0 60034 - 291 + 307 0 0 1 0 15013 - 293 + 309 682 6 1 @@ -6380,7 +6139,7 @@ 0 6 0 - 44 + 54 8 4 0 @@ -6416,85 +6175,93 @@ 216 0 0 - 216 - 216 + 212 + 212 0 0 218 218 0 0 + 212 + 212 + 0 + 0 220 220 0 0 - 222 - 222 + 219 + 219 0 0 - 222 - 222 + 220 + 220 + 1 + 0 + 220 + 220 0 0 224 224 0 0 - 226 - 226 + 224 + 224 0 0 - 228 - 228 + 226 + 226 0 0 - 230 - 230 + 226 + 226 0 0 - 232 - 232 + 228 + 228 0 0 - 232 - 232 + 228 + 228 0 0 - 234 - 234 + 230 + 230 0 0 - 224 - 224 + 230 + 230 0 0 - 234 - 234 + 226 + 226 1 0 228 228 0 0 - 234 - 234 - 2 + 232 + 232 0 - 218 - 218 + 0 + 232 + 232 0 0 234 234 - 3 + 0 0 234 234 0 0 - 238 - 238 - 0 + 224 + 224 + 1 0 236 236 @@ -6502,94 +6269,90 @@ 0 238 238 - 1 - 0 - 239 - 239 0 0 - 241 - 241 - 0 - 0 - 241 - 241 + 238 + 238 0 0 - 243 - 243 + 240 + 240 0 0 - 245 - 245 + 242 + 242 0 0 - 247 - 247 + 244 + 244 0 0 - 249 - 249 + 246 + 246 0 0 - 251 - 251 + 248 + 248 0 0 - 251 - 251 + 248 + 248 0 0 - 253 - 253 + 250 + 250 0 0 - 243 - 243 + 240 + 240 0 0 - 253 - 253 + 250 + 250 1 0 - 247 - 247 + 244 + 244 0 0 - 253 - 253 + 250 + 250 2 0 - 216 - 216 + 232 + 232 0 0 - 253 - 253 + 250 + 250 3 0 - 253 - 253 + 250 + 250 0 0 - 257 - 257 + 254 + 254 0 0 + 252 + 252 + 0 + 0 + 254 + 254 + 1 + 0 255 255 0 0 257 257 - 1 - 0 0 0 - 0 - 2 - 258 - 258 + 257 + 257 0 0 259 @@ -6608,8 +6371,8 @@ 265 0 0 - 265 - 265 + 267 + 267 0 0 267 @@ -6620,12 +6383,52 @@ 269 0 0 + 259 + 259 + 0 + 0 + 269 + 269 + 1 + 0 + 263 + 263 + 0 + 0 + 269 + 269 + 2 + 0 + 228 + 228 + 0 + 0 + 269 + 269 + 3 + 0 + 269 + 269 + 0 + 0 + 273 + 273 + 0 + 0 271 271 0 0 273 273 + 1 + 0 + 0 + 0 + 0 + 2 + 274 + 274 0 0 275 @@ -6640,96 +6443,132 @@ 279 0 0 - 279 - 279 + 281 + 281 0 0 281 281 0 0 - 267 - 267 + 283 + 283 0 0 - 281 - 281 + 285 + 285 + 0 + 0 + 287 + 287 + 0 + 0 + 289 + 289 + 0 + 0 + 291 + 291 + 0 + 0 + 293 + 293 + 0 + 0 + 295 + 295 + 0 + 0 + 295 + 295 + 0 + 0 + 297 + 297 + 0 + 0 + 283 + 283 + 0 + 0 + 297 + 297 1 0 - 271 - 271 + 287 + 287 0 0 - 281 - 281 + 297 + 297 2 0 - 275 - 275 + 291 + 291 0 0 - 281 - 281 + 297 + 297 3 0 - 281 - 281 + 297 + 297 0 0 - 285 - 285 + 301 + 301 0 0 - 283 - 283 + 299 + 299 0 0 - 285 - 285 + 301 + 301 1 0 - 286 - 286 + 302 + 302 0 0 - 289 - 289 + 305 + 305 0 0 - 288 - 288 + 304 + 304 0 0 - 289 - 289 + 305 + 305 1 0 - 289 - 289 + 305 + 305 0 0 - 291 - 291 + 307 + 307 0 0 - 291 - 291 + 307 + 307 0 0 - 293 - 293 + 309 + 309 0 0 - 216 - 216 + 232 + 232 0 1 0 0 0 0 - 212 - 212 + 216 + 216 0 1 0 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar index 41d9d5e8..2e1ef963 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar @@ -3,7 +3,21 @@ 1.0000000000000000000000000 1.0000000000000000000000000 2.0000000000000000000000000 -100.0000000000000000000000000 +0.2000000000000000111022302 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +0.1000000000000000055511151 +-1.0000000000000000000000000 +1.0000000000000000000000000 +0.1000000000000000055511151 +0.1000000000000000055511151 +-1.0000000000000000000000000 +1.0000000000000000000000000 +0.5999999999999999777955395 1.0000000000000000000000000 0.0000000000000000000000000 1295793.0000000000000000000000000 diff --git a/papi/core.py b/papi/core.py index 944464a7..56711ba1 100644 --- a/papi/core.py +++ b/papi/core.py @@ -78,25 +78,26 @@ def __init__(self, gui_start_function, use_gui=True): 'plugin_stopped': self.__process_plugin_stopped__ } - self.__process_data_event_l__ = {'new_data': self.__process_new_data__, - 'new_block': self.__process_new_block__, - 'new_parameter': self.__process_new_parameter__, - 'edit_dplugin': self.__process_edit_dplugin, - 'edit_dplugin_by_uname': self.__process_edit_dplugin_by_uname__, - 'delete_block': self.__process_delete_block__, - 'delete_parameter': self.__delete_parameter__ + self.__process_data_event_l__ = {'new_data': self.__process_new_data__, + 'new_block': self.__process_new_block__, + 'new_parameter': self.__process_new_parameter__, + 'edit_dplugin': self.__process_edit_dplugin, + 'edit_dplugin_by_uname': self.__process_edit_dplugin_by_uname__, + 'delete_block': self.__process_delete_block__, + 'delete_parameter': self.__delete_parameter__ } - self.__process_instr_event_l__ = {'create_plugin': self.__process_create_plugin__, - 'stop_plugin': self.__process_stop_plugin__, - 'close_program': self.__process_close_programm__, - 'subscribe': self.__process_subscribe__, - 'subscribe_by_uname': self.__process_subscribe_by_uname__, - 'unsubscribe': self.__process_unsubsribe__, - 'set_parameter': self.__process_set_parameter__, - 'pause_plugin': self.__process_pause_plugin__, - 'resume_plugin': self.__process_resume_plugin__, - 'start_plugin': self.__process_start_plugin__ + self.__process_instr_event_l__ = {'create_plugin': self.__process_create_plugin__, + 'stop_plugin': self.__process_stop_plugin__, + 'stop_plugin_by_uname': self.__process_stop_plugin_by_uname__, + 'close_program': self.__process_close_programm__, + 'subscribe': self.__process_subscribe__, + 'subscribe_by_uname': self.__process_subscribe_by_uname__, + 'unsubscribe': self.__process_unsubsribe__, + 'set_parameter': self.__process_set_parameter__, + 'pause_plugin': self.__process_pause_plugin__, + 'resume_plugin': self.__process_resume_plugin__, + 'start_plugin': self.__process_start_plugin__ } # creating the main core data object DCore and core queue @@ -908,6 +909,21 @@ def __process_stop_plugin__(self, event): self.log.printText(1, 'stop_plugin, plugin with id ' + str(id) + ' not found') return ERROR.UNKNOWN_ERROR + def __process_stop_plugin_by_uname__(self, event): + """ + + :param event: + :return: + """ + dplugin = self.core_data.get_dplugin_by_uname(event.plugin_uname) + + if dplugin is not None: + id = dplugin.id + + idEvent = Event.instruction.StopPlugin(self.gui_id, id, None) + self.__process_stop_plugin__(idEvent) + + def __process_start_plugin__(self, event): # get destination id id = event.get_destinatioID() @@ -1059,20 +1075,22 @@ def __process_subscribe_by_uname__(self, event): :type dplugin_sub: DPlugin :type dplugin_source: DPlugin """ - subscriber_id = self.core_data.get_dplugin_by_uname(event.subscriber_uname).id - #self.do_get_plugin_id_from_uname(event.subscriber_uname) - if subscriber_id is None: + pl = self.core_data.get_dplugin_by_uname(event.subscriber_uname) + if pl is not None: + subscriber_id = pl.id + else: # plugin with uname does not exist self.log.printText(1, 'do_subscribe, sub uname worng') return -1 - source_id = self.core_data.get_dplugin_by_uname(event.source_uname).id - #self.do_get_plugin_id_from_uname(event.source_uname) - if source_id is None: + pl = self.core_data.get_dplugin_by_uname(event.source_uname) + if pl is not None: + source_id = pl.id + else: # plugin with uname does not exist self.log.printText(1, 'do_subscribe, target uname wrong') return -1 - #print(subscriber_id, source_id, event.block_name, event.signals, event.sub_alias) + self.new_subscription(subscriber_id, source_id, event.block_name, event.signals, event.sub_alias, orginal_event=event) @@ -1179,7 +1197,6 @@ def __process_delete_block__(self, event): :param event: event to process :type event: PapiEventBase """ - print('Block Delete: ', event.blockname) pl_id = event.get_originID() self.core_data.rm_all_subscribers_of_a_dblock(pl_id, event.blockname) @@ -1191,7 +1208,6 @@ def __process_delete_block__(self, event): def __delete_parameter__(self, event): pl_id = event.get_originID() - print('ToDelete:', event.parameterName) dplugin = self.core_data.get_dplugin_by_id(pl_id) if dplugin is not None: diff --git a/papi/event/instruction/StopPluginByUname.py b/papi/event/instruction/StopPluginByUname.py new file mode 100644 index 00000000..0ed3b000 --- /dev/null +++ b/papi/event/instruction/StopPluginByUname.py @@ -0,0 +1,38 @@ +#!/usr/bin/python3 +#-*- coding: latin-1 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Stefan Ruppin +""" + +__author__ = 'stefan' + +from papi.event.instruction.InstructionBase import InstructionBase + +class StopPluginByUname(InstructionBase): + def __init__(self, oID, plugin_uname, delete = True): + self.delete = delete + super().__init__(oID, None, 'stop_plugin_by_uname', None) + + self.plugin_uname = plugin_uname \ No newline at end of file diff --git a/papi/event/instruction/__init__.py b/papi/event/instruction/__init__.py index 7da24f85..81a4ed25 100644 --- a/papi/event/instruction/__init__.py +++ b/papi/event/instruction/__init__.py @@ -3,6 +3,7 @@ from .CreatePlugin import CreatePlugin from .StopPlugin import StopPlugin +from .StopPluginByUname import StopPluginByUname from .PausePlugin import PausePlugin from .ResumePlugin import ResumePlugin from .startPlugin import StartPlugin diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 6bea0791..f6134fa3 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -89,6 +89,7 @@ def do_create_plugin(self, plugin_identifier, uname, config={}, autostart=True): for pluginID in allPlugins: plugin = allPlugins[pluginID] if plugin.uname == uname: + print('UNAME GLEICH') return False # create event object and sent it to core @@ -115,12 +116,8 @@ def do_delete_plugin_uname(self, uname): :type uname: basestring :return: """ - pl_id = self.do_get_plugin_id_from_uname(uname) - - if pl_id is not None: - self.do_delete_plugin(pl_id) - else: - self.log.printText(1, " Do delete plugin with uname " + uname + ' failed') + event =Event.instruction.StopPluginByUname(self.gui_id, uname) + self.core_queue.put(event) def do_edit_plugin(self, pl_id, eObject, changeRequest): """ diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 74d00802..f7a96f6c 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,10 +1,5 @@ - + - - - default - - 771 @@ -13,303 +8,22 @@ 853 + + + default + + + default + + - - Sinus - - - 5 - [0-9]+ - - - SinusPlugin - - - 2 - \d+.{0,1}\d* - - - - 0.6 - - - - - f1_f1DNAME - - - - - f2_1 - - - - - f3_1 - - - f3_2312323wd - - - f3_scalar - - - - - - - Plot - - - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - (300,300) - - - ^(1|0)$ - bool - 0 - Grid-X - - - ^(1|0)$ - bool - 0 - Grid-Y - - - 1 - ^([1-9][0-9]{0,3}|10000)$ - 2000 - Buffersize - - - 1 - ^(1|0)$ - bool - 1 - x: auto range - - - Sinus - Used for window title - - - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - (0,0) - - - 1 - ^(1|0)$ - bool - 1 - y: auto range - - - 1 - (\d+\.\d+) - [0.0 1.0] - x: range - - - 1 - (\d+\.\d+) - [0.0 1.0] - y: range - - - Tab - Used for tabs - - - ^(1|0)$ - bool - 0 - Rolling Plot - - - Plot - - - 1 - ^\[(\s*\d\s*)+\] - [0 1 2 3 4] - Color - - - 1 - ^\[(\s*\d\s*)+\] - [0 0 0 0 0] - Style - - - 1 - (\d+) - - - - [0,1] - 2000 - 0 - 0 - [0 1 2 3 4] - 1 - [0 0 0 0 0] - 0 - - - - - SinusPlugin - - - f3_1 - f3_2 - - - - - - Plot + + Button - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - (300,300) - - - ^(1|0)$ - bool - 0 - Grid-X - - - ^(1|0)$ - bool - 0 - Grid-Y - - - 1 - ^([1-9][0-9]{0,3}|10000)$ - 2000 - Buffersize - - - 1 - ^(1|0)$ - bool - 1 - x: auto range - - - Add - Used for window title - - - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - (450,0) - - - 1 - ^(1|0)$ - bool - 1 - y: auto range - - - 1 - (\d+\.\d+) - [0.0 1.0] - x: range - - - 1 - (\d+\.\d+) - [0.0 1.0] - y: range - - - Tab - Used for tabs - - - ^(1|0)$ - bool - 0 - Rolling Plot - - - PlotX2 - - - 1 - ^\[(\s*\d\s*)+\] - [0 1 2 3 4] - Color - - - 1 - ^\[(\s*\d\s*)+\] - [0 0 0 0 0] - Style - - - 1 - (\d+) - - - - [0,1] - 2000 - 0 - 0 - [0 1 2 3 4] - 1 - [0 0 0 0 0] - 0 - - - - - Add - - - Sum - - - - - - Add - - - Add + (150,50) - - - - - - Sum - - - - - - SinusPlugin - - - f3_1 - f3_2 - - - - - + + ([-]{0,1}\d+\.\d+) + 0 + \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index ce6df365..48b2c0bf 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -60,7 +60,7 @@ import struct import json - +import time import pickle class OptionalObject(object): @@ -143,10 +143,6 @@ def start_init(self, config=None): self.block_id = 0 - Counter = 1 - data = struct.pack(' Date: Tue, 24 Mar 2015 17:22:04 +0100 Subject: [PATCH 192/260] new HTMLViewer Plugin --- .../AutoConfigDemo_ReplaceableSimulation.ipar | 2479 ++++------------- .../AutoConfigDemo_ReplaceableSimulation.rpar | 23 +- papi/last_active_papi.xml | 83 +- papi/plugin/visual/HTMLViewer/HTMLViewer.py | 185 ++ .../visual/HTMLViewer/HTMLViewer.yapsy-plugin | 9 + 5 files changed, 811 insertions(+), 1968 deletions(-) create mode 100644 papi/plugin/visual/HTMLViewer/HTMLViewer.py create mode 100644 papi/plugin/visual/HTMLViewer/HTMLViewer.yapsy-plugin diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar index ecd845ad..3b2ea845 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar @@ -40,8 +40,8 @@ 10 54 0 - 6478 - 55 + 5091 + 36 1 1 2 @@ -56,7 +56,7 @@ -1 -1 -1 - 1427210792 + 1427211152 -1 -1 2015 @@ -66,9 +66,9 @@ 3 24 16 - 26 32 - 891 + 32 + 229 26 79 82 @@ -97,7 +97,7 @@ 105 99 1 - 59 + 33 202 100 0 @@ -126,331 +126,175 @@ 100 167 3 - 32 - 0 - 210 - 100 - 199 - 3 6 1 - 212 + 211 100 - 205 + 173 4 49 0 - 214 + 213 100 - 254 + 222 4 6 1 - 216 + 215 100 - 260 + 228 5 49 0 - 218 - 100 - 309 - 5 - 25 - 0 - 219 + 217 100 - 334 + 277 5 - 6 - 1 - 220 - 100 - 340 - 6 - 6 - 2 - 224 - 100 - 346 - 8 - 6 - 2 - 226 - 100 - 352 - 10 - 6 + 49 2 - 228 - 100 - 358 - 12 - 8 - 3 - 230 + 218 100 - 366 - 15 + 326 + 7 6 1 - 232 + 220 100 - 372 - 16 + 332 8 - 3 - 234 - 100 - 380 - 19 - 6 - 1 - 236 + 3182 + 13 + 222 100 - 386 - 20 + 3514 + 21 6 1 - 238 + 224 100 - 392 - 21 + 3520 + 22 8 0 - 240 - 100 - 400 - 21 - 6 - 0 - 242 - 100 - 406 - 21 - 6 - 1 - 244 + 226 100 - 412 + 3528 22 6 0 - 246 + 228 100 - 418 + 3534 22 6 1 - 248 + 230 100 - 424 + 3540 23 6 0 - 250 - 100 - 430 - 23 - 76 - 0 - 252 - 100 - 506 - 23 - 9 - 0 - 254 - 100 - 515 - 23 - 161 - 0 - 255 + 232 100 - 676 + 3546 23 6 1 - 257 - 100 - 682 - 24 - 8 - 0 - 259 + 234 100 - 690 + 3552 24 6 0 - 261 + 236 100 - 696 + 3558 24 6 1 - 263 + 238 100 - 702 + 3564 25 6 0 - 265 + 240 100 - 708 + 3570 25 - 6 - 1 - 267 - 100 - 714 - 26 - 6 - 0 - 269 - 100 - 720 - 26 76 0 - 271 + 242 100 - 796 - 26 + 3646 + 25 9 0 - 273 + 244 100 - 805 - 26 + 3655 + 25 161 0 - 274 - 100 - 966 - 26 - 49 - 2 - 275 - 100 - 1015 - 28 - 6 - 1 - 277 - 100 - 1021 - 29 - 3651 - 13 - 279 - 100 - 4672 - 42 - 6 - 1 - 281 - 100 - 4678 - 43 - 8 - 0 - 283 + 245 100 - 4686 - 43 + 3816 + 25 6 0 - 285 + 247 100 - 4692 - 43 + 3822 + 25 6 1 - 287 - 100 - 4698 - 44 - 6 - 0 - 289 + 248 100 - 4704 - 44 + 3828 + 26 6 - 1 - 291 + 2 + 250 100 - 4710 - 45 + 3834 + 28 6 0 - 293 + 252 100 - 4716 - 45 + 3840 + 28 + 688 6 - 1 - 295 + 254 100 - 4722 - 46 + 4528 + 34 6 - 0 - 297 - 100 - 4728 - 46 - 76 - 0 - 299 - 100 - 4804 - 46 - 9 - 0 - 301 + 2 + 256 100 - 4813 - 46 - 161 + 4534 + 36 + 37 0 - 302 + 257 100 - 4974 - 46 + 4571 + 36 6 0 - 304 - 100 - 4980 - 46 - 6 - 1 - 305 - 100 - 4986 - 47 - 6 - 2 - 307 + 259 100 - 4992 - 49 - 6 + 4577 + 36 + 78 0 - 309 - 100 - 4998 - 49 - 688 - 6 100 101 - 5686 - 55 - 436 + 4655 + 36 + 236 0 40 202 @@ -619,46 +463,14 @@ 1 1 0 - 170 - 209 - 26 - 0 - 1 - 0 - 1 - 24 - 67 - 97 - 115 - 101 - 32 - 111 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 97 - 99 - 116 - 105 - 118 - 101 - 58 - 32 40 - 210 + 209 0 1 1 0 15007 - 212 + 211 43 0 1 @@ -707,13 +519,13 @@ 114 121 40 - 214 + 213 0 1 1 0 15007 - 216 + 215 43 0 1 @@ -761,665 +573,8 @@ 111 114 121 - 170 - 218 - 19 - 0 - 1 - 0 - 1 - 17 - 79 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 105 - 110 - 112 - 117 - 116 - 32 - 40 - 219 - 0 - 1 - 1 - 0 - 12 - 220 - 0 - 2 - 1 - 0 - 12 - 224 - 0 - 2 - 1 - 0 - 12 - 226 - 0 - 2 - 1 - 0 - 30 - 228 - 2 - 3 - 1 - 0 - 0 - 1 - 20 - 230 - 0 - 1 - 1 - 0 - 30 - 232 - 2 - 3 - 1 - 0 - 0 - 1 - 20 - 234 - 0 - 1 - 1 - 0 - 40 - 236 - 0 - 1 - 1 - 0 - 60005 - 238 - 2 - 0 - 1 - 0 - 0 - 100000 - 60031 - 240 - 0 - 0 - 1 - 0 - 40 - 242 - 0 - 1 - 1 - 0 - 60031 - 244 - 0 - 0 - 1 - 0 - 40 - 246 - 0 - 1 - 1 - 0 - 60031 - 248 - 0 - 0 - 1 - 0 - 39011 - 250 - 70 - 0 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 5 - 0 - 11 - 4 - 5 - 0 - 2 - 0 - 12 - 4 - 7 - 0 - 5 - 0 - 13 - 4 - 12 - 0 - 2 - 0 - 14 - 4 - 14 - 0 - 2 - 0 - 15 - 4 - 16 - 0 - 2 - 0 - 20 - 4 - 18 - 0 - 1 - 0 - 21 - 1 - 19 - 0 - 1 - 0 - 4 - 1 - 1 - 1 - 1 - 1 - 20 - 4 - 2 - 2 - 2 - 257 - 1 - 6 - 1 - 1 - 1 - 2 - 0 - 0 - 60035 - 252 - 3 - 0 - 1 - 0 - 1 - 0 - 20 - 39004 - 254 - 155 - 0 - 1 - 0 - 1 - 9 - 10 - 4 - 0 - 0 - 3 - 0 - 11 - 4 - 3 - 0 - 1 - 0 - 12 - 4 - 4 - 0 - 3 - 0 - 13 - 4 - 7 - 0 - 1 - 0 - 14 - 4 - 8 - 0 - 2 - 0 - 15 - 4 - 10 - 0 - 2 - 0 - 20 - 4 - 12 - 0 - 43 - 0 - 21 - 1 - 55 - 0 - 1 - 0 - 30 - 4 - 56 - 0 - 43 - 0 - 2 - 20 - 1 - 0 - 2 - 6 - 2 - 0 - 1 - 1 - 1 - 1 - 42 - 1 - 4 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 - 10 - 0 - 1 - 20 - 1 - 6 - 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 - 0 - 42 - 83 - 119 - 105 - 116 - 99 - 104 - 105 - 110 - 103 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 97 - 83 - 111 - 99 - 107 - 101 - 116 - 46 - 85 - 68 - 80 - 83 - 111 - 99 - 107 - 101 - 116 - 95 - 83 - 104 - 79 - 98 - 106 - 40 - 255 - 0 - 1 - 1 - 0 - 60005 - 257 - 2 - 0 - 1 - 0 - 0 - 100000 - 60031 - 259 - 0 - 0 - 1 - 0 - 40 - 261 - 0 - 1 - 1 - 0 - 60031 - 263 - 0 - 0 - 1 - 0 - 40 - 265 - 0 - 1 - 1 - 0 - 60031 - 267 - 0 - 0 - 1 - 0 - 39011 - 269 - 70 - 0 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 5 - 0 - 11 - 4 - 5 - 0 - 2 - 0 - 12 - 4 - 7 - 0 - 5 - 0 - 13 - 4 - 12 - 0 - 2 - 0 - 14 - 4 - 14 - 0 - 2 - 0 - 15 - 4 - 16 - 0 - 2 - 0 - 20 - 4 - 18 - 0 - 1 - 0 - 21 - 1 - 19 - 0 - 1 - 0 - 4 - 1 - 1 - 1 - 1 - 1 - 20 - 4 - 2 - 2 - 2 - 257 - 1 - 6 - 1 - 1 - 1 - 2 - 0 - 0 - 60035 - 271 - 3 - 0 - 1 - 0 - 1 - 0 - 20 - 39004 - 273 - 155 - 0 - 1 - 0 - 1 - 9 - 10 - 4 - 0 - 0 - 3 - 0 - 11 - 4 - 3 - 0 - 1 - 0 - 12 - 4 - 4 - 0 - 3 - 0 - 13 - 4 - 7 - 0 - 1 - 0 - 14 - 4 - 8 - 0 - 2 - 0 - 15 - 4 - 10 - 0 - 2 - 0 - 20 - 4 - 12 - 0 - 43 - 0 - 21 - 1 - 55 - 0 - 1 - 0 - 30 - 4 - 56 - 0 - 43 - 0 - 2 - 20 - 1 - 0 - 2 - 6 - 2 - 0 - 1 - 1 - 1 - 1 - 42 - 1 - 4 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 - 10 - 0 - 1 - 20 - 1 - 6 - 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 - 0 - 42 - 83 - 119 - 105 - 116 - 99 - 104 - 105 - 110 - 103 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 97 - 83 - 111 - 99 - 107 - 101 - 116 - 46 - 85 - 68 - 80 - 83 - 111 - 99 - 107 - 101 - 116 - 95 - 83 - 104 - 79 - 98 - 106 15005 - 274 + 217 43 2 1 @@ -1468,14 +623,14 @@ 114 121 40 - 275 + 218 0 1 1 0 15011 - 277 - 3645 + 220 + 3176 13 1 0 @@ -1521,11 +676,11 @@ 4 12 0 - 3582 + 3113 0 21 1 - 3594 + 3125 0 1 13 @@ -1541,7 +696,7 @@ 1 1 1 - 3581 + 3112 1 7 10 @@ -1584,7 +739,7 @@ 10 35 0 - 3502 + 3033 13 0 0 @@ -1711,11 +866,11 @@ 100 286 6 - 2938 + 2469 7 100 101 - 3224 + 2755 13 180 0 @@ -2007,7 +1162,7 @@ 0 15013 232 - 2932 + 2463 7 1 0 @@ -2053,11 +1208,11 @@ 4 20 0 - 2861 + 2392 0 21 1 - 2881 + 2412 0 1 7 @@ -2081,7 +1236,7 @@ 1 1 1 - 2860 + 2391 1 9 1 @@ -2130,11 +1285,11 @@ 10 438 1 - 2221 + 1752 5 902 10 - 2659 + 2190 6 145 1 @@ -2660,29 +1815,29 @@ 100 111 5 - 1620 + 1151 0 232 100 - 1731 + 1262 5 78 0 234 100 - 1809 + 1340 5 9 0 236 100 - 1818 + 1349 5 161 0 100 101 - 1979 + 1510 5 132 0 @@ -2799,7 +1954,7 @@ 0 60305 230 - 1614 + 1145 0 1 0 @@ -2845,17 +2000,17 @@ 4 10 0 - 1553 + 1084 0 21 1 - 1563 + 1094 0 1 0 0 1 - 1535 + 1066 0 1 6 @@ -2863,7 +2018,7 @@ 1 1 2 - 1552 + 1083 1 2 10 @@ -2876,118 +2031,13 @@ 4 2 0 - 1536 + 1067 0 1 - 1535 - 1535 - 32 - 123 - 34 - 83 - 111 - 117 - 114 - 99 - 101 - 115 - 67 - 111 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 10 - 32 - 34 - 48 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 83 - 111 - 117 - 114 - 99 - 101 - 78 - 97 - 109 - 101 - 34 - 32 - 58 - 32 - 34 - 88 - 34 - 32 - 44 - 32 - 34 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 95 - 115 - 101 - 110 - 100 - 34 - 32 - 58 - 32 - 34 - 49 - 34 - 44 - 32 - 34 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 34 - 32 - 58 - 32 - 34 - 50 - 53 - 55 - 34 - 32 - 32 - 125 - 32 - 44 - 10 - 32 - 34 - 49 - 34 - 32 - 58 + 1066 + 1066 32 123 - 32 34 83 111 @@ -2995,81 +2045,6 @@ 114 99 101 - 78 - 97 - 109 - 101 - 34 - 32 - 58 - 32 - 34 - 86 - 34 - 32 - 44 - 32 - 34 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 95 - 115 - 101 - 110 - 100 - 34 - 32 - 58 - 32 - 34 - 49 - 34 - 44 - 32 - 34 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 34 - 32 - 58 - 32 - 34 - 50 - 53 - 55 - 34 - 32 - 32 - 125 - 32 - 10 - 125 - 32 - 44 - 32 - 10 - 32 - 34 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 115 67 111 @@ -3082,99 +2057,39 @@ 58 32 123 - 10 - 32 - 34 - 48 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 78 - 97 - 109 - 101 - 34 - 32 - 58 - 32 - 34 - 79 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 105 - 110 - 112 - 117 - 116 - 34 - 32 - 44 - 32 - 34 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 34 - 32 - 58 + 10 + 125 32 - 34 - 49 - 34 44 32 + 10 + 32 34 - 100 + 80 97 - 116 + 114 97 + 109 + 101 116 - 121 - 112 101 + 114 + 115 + 67 + 111 + 110 + 102 + 105 + 103 34 32 58 32 - 34 - 50 - 53 - 55 - 34 - 32 - 32 - 125 - 32 - 44 + 123 10 32 34 - 49 + 48 34 32 58 @@ -3200,8 +2115,10 @@ 58 32 34 - 111 - 107 + 79 + 115 + 99 + 105 34 32 44 @@ -3245,50 +2162,10 @@ 32 125 32 - 10 - 125 44 - 32 10 - 34 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 34 - 32 - 58 - 32 - 123 32 34 - 80 - 108 - 117 - 103 - 105 - 110 49 34 32 @@ -3297,61 +2174,16 @@ 123 32 34 - 105 - 100 - 101 - 110 - 116 - 105 - 102 - 105 - 101 + 80 + 97 114 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 97 - 108 - 117 + 109 101 - 34 - 32 - 58 - 32 - 34 - 66 - 117 - 116 116 - 111 - 110 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 99 - 111 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 110 + 101 + 114 + 78 97 109 101 @@ -3359,207 +2191,83 @@ 32 58 32 - 123 - 32 34 - 118 - 97 + 83 108 - 117 + 105 + 100 101 - 34 - 32 - 58 - 32 - 34 + 114 + 76 + 67 68 - 105 - 115 - 116 - 117 + 80 114 - 98 - 97 - 110 - 99 - 101 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 115 - 105 - 122 - 101 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 40 - 49 - 53 - 48 - 44 - 53 - 48 - 41 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 112 - 111 - 115 - 105 - 116 - 105 111 - 110 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 40 - 54 - 48 - 48 - 44 - 49 - 48 - 48 - 41 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 116 - 97 - 98 + 103 + 114 + 101 + 115 + 115 34 32 - 58 - 32 - 123 + 44 32 34 - 118 + 78 + 86 97 108 117 101 + 115 34 32 58 32 34 - 80 - 97 - 80 - 73 - 45 - 84 - 97 - 98 + 49 34 - 32 - 125 - 32 - 32 44 32 34 - 115 - 116 + 100 97 116 - 101 - 49 - 95 + 97 116 + 121 + 112 101 - 120 - 116 34 32 58 32 - 123 - 32 34 - 118 - 97 - 108 - 117 - 101 + 50 + 53 + 55 34 32 - 58 - 32 - 34 - 68 - 105 - 115 - 116 - 117 - 114 - 98 - 34 32 125 32 - 32 + 10 + 125 44 32 + 10 34 - 115 - 116 + 80 97 - 116 - 101 - 50 - 95 - 116 - 101 - 120 - 116 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 34 32 58 @@ -3567,38 +2275,19 @@ 123 32 34 - 118 + 84 + 111 + 67 + 114 + 101 97 - 108 - 117 + 116 101 34 32 58 32 - 34 - 68 - 105 - 115 - 116 - 117 - 114 - 98 - 105 - 110 - 103 - 34 - 32 - 125 - 32 - 32 - 125 - 32 - 32 - 125 - 32 - 32 - 44 + 123 32 34 80 @@ -3607,7 +2296,7 @@ 103 105 110 - 50 + 49 34 32 58 @@ -3690,11 +2379,16 @@ 58 32 34 - 76 - 101 + 79 + 115 + 99 + 105 + 108 + 108 97 - 118 - 101 + 116 + 111 + 114 34 32 125 @@ -3725,11 +2419,12 @@ 32 34 40 - 49 + 50 53 48 44 - 53 + 49 + 48 48 41 34 @@ -3766,13 +2461,13 @@ 32 34 40 - 54 - 48 - 48 - 44 - 51 50 53 + 48 + 44 + 49 + 48 + 48 41 34 32 @@ -3846,8 +2541,11 @@ 58 32 34 - 79 - 107 + 71 + 111 + 32 + 116 + 111 34 32 125 @@ -3891,6 +2589,9 @@ 105 110 103 + 32 + 116 + 111 34 32 125 @@ -3911,7 +2612,7 @@ 103 105 110 - 51 + 50 34 32 58 @@ -3946,10 +2647,12 @@ 58 32 34 - 80 - 108 - 111 + 66 + 117 + 116 116 + 111 + 110 34 32 125 @@ -3992,13 +2695,28 @@ 58 32 34 - 80 + 83 108 - 111 - 116 + 105 + 100 + 101 + 114 32 - 88 - 86 + 76 + 67 + 68 + 32 + 80 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 66 + 97 + 114 34 32 125 @@ -4029,11 +2747,11 @@ 32 34 40 + 50 53 48 - 48 44 - 53 + 49 48 48 41 @@ -4071,8 +2789,12 @@ 32 34 40 + 50 + 53 48 44 + 50 + 53 48 41 34 @@ -4119,12 +2841,17 @@ 44 32 34 - 121 - 82 + 115 + 116 97 - 110 - 103 + 116 + 101 + 49 + 95 + 116 101 + 120 + 116 34 32 58 @@ -4142,18 +2869,57 @@ 58 32 34 - 91 - 45 - 49 - 48 - 46 - 48 + 71 + 111 + 32 + 116 + 111 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 76 + 101 + 97 + 118 + 105 + 110 + 103 32 - 49 - 48 - 46 - 48 - 93 + 116 + 111 34 32 125 @@ -4245,18 +3011,6 @@ 115 99 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 105 - 110 - 112 - 117 - 116 34 32 125 @@ -4319,91 +3073,23 @@ 58 32 34 - 111 - 107 - 34 - 32 - 125 - 32 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 84 - 111 83 - 117 - 98 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 80 108 - 117 - 103 105 - 110 - 51 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 115 - 105 - 103 - 110 - 97 - 108 - 115 - 34 - 32 - 58 - 32 - 91 - 34 - 88 - 34 - 44 - 34 - 86 - 34 - 93 - 32 - 44 - 32 - 34 - 98 - 108 - 111 - 99 - 107 - 34 - 32 - 58 - 32 - 34 - 83 - 111 - 117 - 114 - 99 + 100 101 - 71 + 114 + 76 + 67 + 68 + 80 114 111 - 117 - 112 - 48 + 103 + 114 + 101 + 115 + 115 34 32 125 @@ -4478,9 +3164,9 @@ 1 1 1 - 1535 + 1066 1 - 1551 + 1082 5 2 2 @@ -4503,7 +3189,7 @@ 0 1 0 - 1551 + 1082 39004 236 155 @@ -4567,7 +3253,7 @@ 43 0 2 - 1551 + 1082 1 0 2 @@ -4606,7 +3292,7 @@ 10 0 1 - 1551 + 1082 1 6 1 @@ -5125,13 +3811,13 @@ 4 13 40 - 279 + 222 0 1 1 0 60005 - 281 + 224 2 0 1 @@ -5139,49 +3825,49 @@ 0 100000 60031 - 283 + 226 0 0 1 0 40 - 285 + 228 0 1 1 0 60031 - 287 + 230 0 0 1 0 40 - 289 + 232 0 1 1 0 60031 - 291 + 234 0 0 1 0 40 - 293 + 236 0 1 1 0 60031 - 295 + 238 0 0 1 0 39011 - 297 + 240 70 0 1 @@ -5257,7 +3943,7 @@ 0 0 60035 - 299 + 242 3 0 1 @@ -5266,7 +3952,7 @@ 0 16 39004 - 301 + 244 155 0 1 @@ -5427,31 +4113,31 @@ 98 106 60023 - 302 + 245 0 0 1 0 40 - 304 + 247 0 1 1 0 12 - 305 + 248 0 2 1 0 60034 - 307 + 250 0 0 1 0 15013 - 309 + 252 682 6 1 @@ -6138,69 +4824,180 @@ 0 0 6 + 12 + 254 0 - 54 - 8 - 4 + 2 + 1 0 + 15006 + 256 + 31 0 + 1 0 0 - 2 - 204 - 204 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 20 + 78 + 101 + 120 + 116 + 83 + 116 + 97 + 116 + 101 + 68 + 97 + 116 + 97 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 60022 + 257 + 0 + 0 + 1 + 0 + 170 + 259 + 72 0 1 0 + 1 + 70 + 67 + 97 + 115 + 101 + 32 + 99 + 104 + 111 + 111 + 115 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 46 + 32 + 78 + 101 + 120 + 116 + 32 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 105 + 115 + 32 + 40 + 48 + 32 + 109 + 101 + 97 + 110 + 115 + 32 + 110 + 111 + 32 + 99 + 104 + 97 + 110 + 103 + 101 + 41 + 58 + 0 + 29 + 8 + 4 + 0 + 0 0 + 0 + 2 + 204 + 204 0 0 209 209 0 0 - 210 - 210 + 211 + 211 0 0 - 212 - 212 + 213 + 213 0 0 - 214 - 214 + 215 + 215 0 0 - 216 - 216 0 0 - 212 - 212 + 0 + 2 + 217 + 217 0 0 218 218 0 0 - 212 - 212 - 0 - 0 220 220 0 0 - 219 - 219 - 0 - 0 - 220 - 220 - 1 - 0 - 220 - 220 + 222 + 222 0 0 224 @@ -6212,10 +5009,6 @@ 0 0 226 - 226 - 0 - 0 - 226 226 0 0 @@ -6223,46 +5016,18 @@ 228 0 0 - 228 - 228 - 0 - 0 - 230 - 230 - 0 - 0 230 230 0 0 - 226 - 226 - 1 - 0 - 228 - 228 - 0 - 0 232 232 0 0 - 232 - 232 - 0 - 0 - 234 - 234 - 0 - 0 234 234 0 0 - 224 - 224 - 1 - 0 236 236 0 @@ -6279,296 +5044,144 @@ 240 0 0 - 242 - 242 - 0 - 0 - 244 - 244 - 0 - 0 - 246 - 246 - 0 - 0 - 248 - 248 - 0 - 0 - 248 - 248 - 0 - 0 - 250 - 250 + 226 + 226 0 0 240 240 - 0 - 0 - 250 - 250 1 0 - 244 - 244 + 230 + 230 0 0 - 250 - 250 + 240 + 240 2 0 - 232 - 232 + 234 + 234 0 0 - 250 - 250 + 240 + 240 3 0 - 250 - 250 - 0 - 0 - 254 - 254 - 0 - 0 - 252 - 252 - 0 - 0 - 254 - 254 - 1 - 0 - 255 - 255 - 0 - 0 - 257 - 257 - 0 - 0 - 257 - 257 - 0 - 0 - 259 - 259 - 0 - 0 - 261 - 261 - 0 - 0 - 263 - 263 - 0 - 0 - 265 - 265 - 0 - 0 - 267 - 267 - 0 - 0 - 267 - 267 + 240 + 240 0 0 - 269 - 269 + 244 + 244 0 0 - 259 - 259 + 242 + 242 0 0 - 269 - 269 + 244 + 244 1 0 - 263 - 263 - 0 - 0 - 269 - 269 - 2 - 0 - 228 - 228 - 0 - 0 - 269 - 269 - 3 - 0 - 269 - 269 + 245 + 245 0 0 - 273 - 273 + 248 + 248 0 0 - 271 - 271 + 247 + 247 0 0 - 273 - 273 + 248 + 248 1 0 + 248 + 248 0 0 - 0 - 2 - 274 - 274 - 0 - 0 - 275 - 275 - 0 - 0 - 277 - 277 - 0 - 0 - 279 - 279 - 0 - 0 - 281 - 281 - 0 - 0 - 281 - 281 - 0 - 0 - 283 - 283 - 0 - 0 - 285 - 285 - 0 - 0 - 287 - 287 - 0 - 0 - 289 - 289 - 0 - 0 - 291 - 291 + 250 + 250 0 0 - 293 - 293 + 250 + 250 0 0 - 295 - 295 + 252 + 252 0 0 - 295 - 295 + 211 + 211 0 0 - 297 - 297 + 254 + 254 0 0 - 283 - 283 + 215 + 215 0 0 - 297 - 297 + 254 + 254 1 0 - 287 - 287 - 0 - 0 - 297 - 297 - 2 - 0 - 291 - 291 - 0 - 0 - 297 - 297 - 3 - 0 - 297 - 297 + 254 + 254 0 0 - 301 - 301 + 256 + 256 0 0 - 299 - 299 + 207 + 207 0 0 - 301 - 301 + 256 + 256 1 0 - 302 - 302 + 254 + 254 0 0 - 305 - 305 + 257 + 257 0 0 - 304 - 304 + 207 + 207 0 0 - 305 - 305 + 257 + 257 1 0 - 305 - 305 - 0 - 0 - 307 - 307 - 0 - 0 - 307 - 307 + 254 + 254 0 0 - 309 - 309 + 259 + 259 0 0 - 232 - 232 + 254 + 254 0 1 0 0 0 0 - 216 - 216 + 257 + 257 0 1 0 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar index 2e1ef963..bb646cca 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar @@ -3,27 +3,6 @@ 1.0000000000000000000000000 1.0000000000000000000000000 2.0000000000000000000000000 -0.2000000000000000111022302 -1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -0.1000000000000000055511151 --1.0000000000000000000000000 -1.0000000000000000000000000 -0.1000000000000000055511151 -0.1000000000000000055511151 --1.0000000000000000000000000 -1.0000000000000000000000000 -0.5999999999999999777955395 -1.0000000000000000000000000 -0.0000000000000000000000000 -1295793.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -1295793.0000000000000000000000000 0.0000000000000000000000000 0.0000000000000000000000000 1.0000000000000000000000000 @@ -53,3 +32,5 @@ -2.0000000000000000000000000 0.0000000000000000000000000 1295793.0000000000000000000000000 +1.0000000000000000000000000 +2.0000000000000000000000000 diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index f7a96f6c..fa8a81a4 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,29 +1,84 @@ - + - - 771 - 853 + + 771 + default - - default - - - Button + + HTMLViewer + + HTMLViewer + - (150,50) + (319,144) + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + 1 + + + VisualPlugin + Used for window title + + + <!DOCTYPE html> +<html> + +<head> +<style> +table, th, td { + border: 1px solid black; +} +</style> +</head> + +<body> + +<table style="width:100%"> + <tr> + <td>Jill</td> + <td>Smith</td> + <td>50</td> + </tr> + <tr> + <td>Eve</td> + <td>Jackson</td> + <td>94</td> + </tr> + <tr> + <td>John</td> + <td>Doe</td> + <td>80</td> + </tr> +</table> + +</body> +</html> + + + + PaPI-Tab + Used for tabs + + + (0,0) + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + 1 - - ([-]{0,1}\d+\.\d+) - 0 - \ No newline at end of file + + + + + + diff --git a/papi/plugin/visual/HTMLViewer/HTMLViewer.py b/papi/plugin/visual/HTMLViewer/HTMLViewer.py new file mode 100644 index 00000000..9e332e8e --- /dev/null +++ b/papi/plugin/visual/HTMLViewer/HTMLViewer.py @@ -0,0 +1,185 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +your wefwefwef here

]]>' + 'value': """ + + + + + + + + + + + + + + + + + + + + + + + + +
JillSmith50
EveJackson94
JohnDoe80
+ + + +""" + }} + + return config + + def plugin_meta_updated(self): + """ + Whenever the meta information is updated this function is called (if implemented). + + :return: + """ + + #dplugin_info = self.dplugin_info + pass diff --git a/papi/plugin/visual/HTMLViewer/HTMLViewer.yapsy-plugin b/papi/plugin/visual/HTMLViewer/HTMLViewer.yapsy-plugin new file mode 100644 index 00000000..3a47d441 --- /dev/null +++ b/papi/plugin/visual/HTMLViewer/HTMLViewer.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = HTMLViewer +Module = HTMLViewer + +[Documentation] +Author = S. Ruppin +Version = 1.0 +Website = +Description = HTMLViewer for displaying HTML From 01c1841d35af1696bd8b6aee0e11768ef35ed4d8 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 24 Mar 2015 17:49:54 +0100 Subject: [PATCH 193/260] new HTMLViewer Plugin --- papi/last_active_papi.xml | 81 ++------------------- papi/plugin/visual/HTMLViewer/HTMLViewer.py | 60 ++------------- 2 files changed, 10 insertions(+), 131 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index fa8a81a4..beead429 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,84 +1,13 @@ - + + - - 853 - 771 + + 853 + - - - default - - - - HTMLViewer - - - HTMLViewer - - - (319,144) - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - 1 - - - VisualPlugin - Used for window title - - - <!DOCTYPE html> -<html> - -<head> -<style> -table, th, td { - border: 1px solid black; -} -</style> -</head> - -<body> - -<table style="width:100%"> - <tr> - <td>Jill</td> - <td>Smith</td> - <td>50</td> - </tr> - <tr> - <td>Eve</td> - <td>Jackson</td> - <td>94</td> - </tr> - <tr> - <td>John</td> - <td>Doe</td> - <td>80</td> - </tr> -</table> - -</body> -</html> - - - - PaPI-Tab - Used for tabs - - - (0,0) - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - 1 - - - - - - diff --git a/papi/plugin/visual/HTMLViewer/HTMLViewer.py b/papi/plugin/visual/HTMLViewer/HTMLViewer.py index 9e332e8e..d6d209fa 100644 --- a/papi/plugin/visual/HTMLViewer/HTMLViewer.py +++ b/papi/plugin/visual/HTMLViewer/HTMLViewer.py @@ -45,42 +45,23 @@ class HTMLViewer(vip_base): def initiate_layer_0(self, config=None): -# self.config = config - # --------------------------- # Read configuration # --------------------------- - # Note: this cfg items have to exist! - # self.show_grid_x = int(self.config['x-grid']['value']) == '1' - # self.show_grid_y = int(self.config['y-grid']['value']) == '1' - # - # int_re = re.compile(r'(\d+)') - # - # self.colors_selected = int_re.findall(self.config['color']['value']); - # self.types_selected = int_re.findall(self.config['style']['value']); - - - + content = config['content']['value'] # -------------------------------- # Create Widget # -------------------------------- - # Create Widget needed for this plugin - self.WebView = QWebView() # This call is important, because the background structure needs to know the used widget! # In the background the qmidiwindow will becreated and the widget will be added self.set_widget_for_internal_usage( self.WebView ) - - content = config['content']['value'] - - self.WebView.setHtml(content) self.WebView.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.WebView.customContextMenuRequested.connect(self.show_context_menu) - return True def show_context_menu(self, pos): @@ -135,41 +116,10 @@ def get_plugin_configuration(self): config = { "content": { - #'value': 'your wefwefwef here

]]>' - 'value': """ - - - - - - - - - - - - - - - - - - - - - - - - -
JillSmith50
EveJackson94
JohnDoe80
- - - -""" + 'value': """

Insert your html code here

""", + 'display_text' : 'HTML Content', + 'advanced' : '0', + 'tooltip' : 'Plain html code to be displayed' }} return config From 546d2cf4d0acc055553752d53a5cabc1d5796998 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 24 Mar 2015 17:58:09 +0100 Subject: [PATCH 194/260] new HTMLViewer Plugin --- papi/last_active_papi.xml | 57 ++++++++++++++++++++- papi/plugin/visual/HTMLViewer/HTMLViewer.py | 6 +-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index beead429..162e52bc 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,6 +1,10 @@ - + - + + + default + + 771 @@ -10,4 +14,53 @@ + + HTMLViewer + + + Used for tabs + PaPI-Tab + + + Used for window title + VisualPlugin + + + 0 + Plain html code to be displayed + HTML Content + <!DOCTYPE html> +<html> +<body> + +<p id="demo"></p> + +<script> +document.getElementById("demo").innerHTML = "Hello JavaScript!"; +</script> + +</body> +</html> + + + + 1 + Determine position: (x,y) + (0,0) + \(([0-9]+),([0-9]+)\) + + + HTMLViewer + + + 1 + Determine size: (height,width) + (300,300) + \(([0-9]+),([0-9]+)\) + + + + + + diff --git a/papi/plugin/visual/HTMLViewer/HTMLViewer.py b/papi/plugin/visual/HTMLViewer/HTMLViewer.py index d6d209fa..febe5f00 100644 --- a/papi/plugin/visual/HTMLViewer/HTMLViewer.py +++ b/papi/plugin/visual/HTMLViewer/HTMLViewer.py @@ -34,12 +34,10 @@ from papi.plugin.base_classes.vip_base import vip_base from papi.data.DParameter import DParameter -import collections -import re -from papi.pyqtgraph.Qt import QtGui, QtCore +from papi.pyqtgraph.Qt import QtCore + -#RENAME TO PLUGIN NAME class HTMLViewer(vip_base): From 4963d512b18a3552a870496a5fc3164aa08db518 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 24 Mar 2015 18:02:02 +0100 Subject: [PATCH 195/260] Update CHANGENLOG.md --- CHANGENLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index eca57886..e9b49445 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -15,7 +15,7 @@ v.1.XX * [feature]: modified PaPI gui to have tabs. User can organise visual plugins in tabs, remove tabs, rename tabs. * [plugin]: added a new visual plugin: ProgressBar * [plugin]: Minor changes in LCDDisplay and Slider - + * [improvement]: shifted some control logic from gui to core to increase speed when changing cfgs incl. subscriptions v.1.0 --- From aaf5ae23285b3b8df7714402f83ed35d9aabcce2 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 24 Mar 2015 18:02:13 +0100 Subject: [PATCH 196/260] Update CHANGENLOG.md --- CHANGENLOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index e9b49445..fe1a8027 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -16,6 +16,7 @@ v.1.XX * [plugin]: added a new visual plugin: ProgressBar * [plugin]: Minor changes in LCDDisplay and Slider * [improvement]: shifted some control logic from gui to core to increase speed when changing cfgs incl. subscriptions + * v.1.0 --- From 2a18e630eba79134bd4792878925ff95db50e72b Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 24 Mar 2015 18:02:19 +0100 Subject: [PATCH 197/260] Update CHANGENLOG.md --- CHANGENLOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index fe1a8027..0993df8f 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -16,7 +16,7 @@ v.1.XX * [plugin]: added a new visual plugin: ProgressBar * [plugin]: Minor changes in LCDDisplay and Slider * [improvement]: shifted some control logic from gui to core to increase speed when changing cfgs incl. subscriptions - * + v.1.0 --- From b44a39f2990daea94f7a3857e78e603708cf7168 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Fri, 27 Mar 2015 17:35:12 +0100 Subject: [PATCH 198/260] Tab position will be saved to xml and loaded correctly --- papi/gui/qt_new/PapiTabManger.py | 21 ++++++++-- papi/gui/qt_new/main.py | 27 ++++++++++++- papi/last_active_papi.xml | 68 ++++++++------------------------ 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index b2a16c08..cd17db5c 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -122,10 +122,12 @@ def moveFromTo(self, start, dest, subWindow, posX=0, posY=0): return False def set_background_for_tab_with_name(self, name, bg): - pixmap = QtGui.QPixmap(bg) - widgetArea = self.tab_dict_uname[name] - widgetArea.setBackground(pixmap) - widgetArea.background = bg + if bg is not None: + if name in self.tab_dict_uname: + pixmap = QtGui.QPixmap(bg) + widgetArea = self.tab_dict_uname[name] + widgetArea.setBackground(pixmap) + widgetArea.background = bg def set_all_tabs_to_close_when_empty(self, state): for tabName in self.tab_dict_uname: @@ -175,6 +177,17 @@ def close_all_empty_tabs(self): for tab in tabs_to_close: self.closeTab_by_name(tab) + def getTabPosition_by_name(self, tabName): + if tabName in self.tab_dict_uname: + tabObj = self.tab_dict_uname[tabName] + ind = self.tabWidget.indexOf(tabObj) + return ind + + def setTabPosition_by_name(self, tabName): + if tabName in self.tab_dict_uname: + tabObj = self.tab_dict_uname[tabName] + #self.tabWidget. + def show_context_menu(self, pos): self.cmenu = self.create_context_menu() gloPos = self.tabWidget.mapToGlobal(pos) diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index b92a77ac..51b744b5 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -181,6 +181,7 @@ def get_gui_config(self): tabOb = tab_dict[tab] tabs[tab]= {} tabs[tab]['background'] = tabOb.background + tabs[tab]['position'] = str(self.TabManager.getTabPosition_by_name(tab)) size = {} size['x'] = {} @@ -199,16 +200,38 @@ def set_gui_config(self, cfg): # Cfgs for Tabs # ################# if 'tabs' in cfg: + tabList = {} for tab in cfg['tabs']: + # Tab Name name = tab - tabOb = self.TabManager.add_tab(name) - + # Tab details tabDetails = cfg['tabs'][tab] + + # check for background if 'background' in tabDetails: bg = tabDetails['background'] if bg != 'default': self.TabManager.set_background_for_tab_with_name(name,bg) + else: + bg = None + + # check for position + if 'position' in tabDetails: + pos = int(tabDetails['position']) + else: + pos = max(list(tabList.keys()))+1 + + tabList[pos] = [name, bg] + + # sort tabs acoriding to positions + keys = list(tabList.keys()) + keys.sort() + for position in keys: + name = tabList[position][0] + bg = tabList[position][1] + tabOb = self.TabManager.add_tab(name) + self.TabManager.set_background_for_tab_with_name(name,bg) ################# # windows size: # diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 162e52bc..1e552602 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,9 +1,22 @@ - + - + + 0 default - + + + 3 + default + + + 1 + default + + + 2 + default + @@ -14,53 +27,4 @@ - - HTMLViewer - - - Used for tabs - PaPI-Tab - - - Used for window title - VisualPlugin - - - 0 - Plain html code to be displayed - HTML Content - <!DOCTYPE html> -<html> -<body> - -<p id="demo"></p> - -<script> -document.getElementById("demo").innerHTML = "Hello JavaScript!"; -</script> - -</body> -</html> - - - - 1 - Determine position: (x,y) - (0,0) - \(([0-9]+),([0-9]+)\) - - - HTMLViewer - - - 1 - Determine size: (height,width) - (300,300) - \(([0-9]+),([0-9]+)\) - - - - - - From 89a0b9d1bead747fe3c568212935d4ab77563980 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Fri, 27 Mar 2015 17:46:37 +0100 Subject: [PATCH 199/260] Active Tab will be remembered after loading xml cfg --- papi/gui/qt_new/PapiTabManger.py | 6 ++++++ papi/gui/qt_new/main.py | 9 +++++++++ papi/last_active_papi.xml | 31 ++++++++++++++++++++----------- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index cd17db5c..271efee9 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -108,6 +108,12 @@ def get_default_tab(self, NotThisIndex): else: return self.tabWidget.widget(0) + def get_currently_active_tab(self): + return self.tabWidget.currentIndex() + + def set_tab_active_by_index(self, index): + self.tabWidget.setCurrentIndex(index) + def moveFromTo(self, start, dest, subWindow, posX=0, posY=0): if start in self.tab_dict_uname and dest in self.tab_dict_uname: startTab = self.tab_dict_uname[start] diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index 51b744b5..d3707b8c 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -175,6 +175,11 @@ def __init__(self, core_queue, gui_queue,gui_id, gui_data = None, parent=None): def get_gui_config(self): + + actTab = {} + actTab['active'] = {} + actTab['active']['value'] = str(self.TabManager.get_currently_active_tab()) + tabs = {} tab_dict = self.TabManager.get_tabs_by_uname() for tab in tab_dict: @@ -190,6 +195,7 @@ def get_gui_config(self): size['y']['value'] = str(self.size().height()) cfg = {} + cfg['activeTab'] = actTab cfg['tabs'] = tabs cfg['size'] = size @@ -233,6 +239,9 @@ def set_gui_config(self, cfg): tabOb = self.TabManager.add_tab(name) self.TabManager.set_background_for_tab_with_name(name,bg) + if 'activeTab' in cfg: + self.TabManager.set_tab_active_by_index(int( cfg['activeTab']['active']['value'] )) + ################# # windows size: # ################# diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 1e552602..ecb3b855 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,23 +1,32 @@ - + - + + default + 1 + + + default 0 + + default - - 3 + + default - - - 1 - default - - 2 + + default - + 4 + + + + 3 + + 771 From 35e6870c4a88b6882cfa7dd90c5679e6d49db7bb Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Fri, 27 Mar 2015 18:04:37 +0100 Subject: [PATCH 200/260] Added new plugin: Radiobutton --- cfg_collection/radiobutton_example.xml | 509 ++++++++++++++++++ papi/plugin/pcp/radiobutton/Radiobutton.py | 139 +++++ .../pcp/radiobutton/Radiobutton.yapsy-plugin | 11 + 3 files changed, 659 insertions(+) create mode 100644 cfg_collection/radiobutton_example.xml create mode 100644 papi/plugin/pcp/radiobutton/Radiobutton.py create mode 100644 papi/plugin/pcp/radiobutton/Radiobutton.yapsy-plugin diff --git a/cfg_collection/radiobutton_example.xml b/cfg_collection/radiobutton_example.xml new file mode 100644 index 00000000..bdd6870d --- /dev/null +++ b/cfg_collection/radiobutton_example.xml @@ -0,0 +1,509 @@ + + + + + 849 + + + 787 + + + + + default + + + + + Plot + + + (300,300) + Determine size: (height,width) + 1 + \(([0-9]+),([0-9]+)\) + + + Grid-X + 0 + bool + ^(1|0)$ + + + 5 + (\d+) + + + Used for tabs + PaPI-Tab + + + Grid-Y + 0 + bool + ^(1|0)$ + + + (299,0) + Determine position: (x,y) + 1 + \(([0-9]+),([0-9]+)\) + + + 400 + Buffersize + 1 + ^(\d+)$ + + + Rolling Plot + 1 + bool + ^(1|0)$ + + + [0 1 2 3 4] + Color + 1 + ^\[(\s*\d\s*)+\] + + + Plot + + + Used for window title + VisualPlugin + + + [-1.0 0.999980260856] + y: range + 1 + (\d+\.\d+) + + + [0 0 0 0 0] + Style + 1 + ^\[(\s*\d\s*)+\] + + + + 0 + 5 + [0 1 2 3 4] + 0 + 1 + [0,1] + [0 0 0 0 0] + 400 + + + + + RBSetDS + + downsampling_rate + + + + Sinus + + + f3_1 + + + + RBXGrid + + x-grid + + + + RBYGrid + + y-grid + + + + RBSetBuffersize + + buffersize + + + + + + Radiobutton + + + (300,300) + Determine size: (height,width) + 1 + \(([0-9]+),([0-9]+)\) + + + This text is seen by the user. Must be separated by commas. + 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 + + + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + + Used for tabs + PaPI-Tab + + + Used for window title + Set Downsampling Rate + + + (0,0) + Determine position: (x,y) + 1 + \(([0-9]+),([0-9]+)\) + + + 1 + Preselected Option + 1 + Preselect an option by its index. + [0-9]+ + + + RBSetDS + + + + + + + + + + Sinus + + + 3 + [0-9]+ + + + Sinus + + + 1 + \d+.{0,1}\d* + + + + 0.6 + + + + + f3_1 + + + f3_2 + + + f3_scalar + + + + + f1_f1DNAME + + + + + f2_1 + + + + + + + Radiobutton + + + (219,96) + Determine size: (height,width) + 1 + \(([0-9]+),([0-9]+)\) + + + Displayed Option + Off, On + This text is seen by the user. Must be separated by commas. + + + Value per Option + 0, 1 + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + Used for tabs + PaPI-Tab + + + Plugin Name + Enable/Disable X-Grid + Used for window title + + + (1,301) + Determine position: (x,y) + 1 + \(([0-9]+),([0-9]+)\) + + + 0 + Preselected Option + 1 + Preselect an option by its index. + [0-9]+ + + + RBXGrid + + + + + + + + + + Radiobutton + + + (218,96) + Determine size: (height,width) + 1 + \(([0-9]+),([0-9]+)\) + + + Displayed Option + Off, On + This text is seen by the user. Must be separated by commas. + + + Value per Option + 0, 1 + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + Used for tabs + PaPI-Tab + + + Plugin Name + Enable/Disable Y-Grid + Used for window title + + + (1,398) + Determine position: (x,y) + 1 + \(([0-9]+),([0-9]+)\) + + + 0 + Preselected Option + 1 + Preselect an option by its index. + [0-9]+ + + + RBYGrid + + + + + + + + + + Radiobutton + + + (167,292) + Determine size: (height,width) + 1 + \(([0-9]+),([0-9]+)\) + + + Displayed Option + 100, 200, 400, 800, 1200, 1600, 2000, 2500, 3000 + This text is seen by the user. Must be separated by commas. + + + Value per Option + + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + Used for tabs + PaPI-Tab + + + Plugin Name + Set Buffersize + Used for window title + + + (598,2) + Determine position: (x,y) + 1 + \(([0-9]+),([0-9]+)\) + + + 2 + Preselected Option + 1 + Preselect an option by its index. + [0-9]+ + + + RBSetBuffersize + + + + + + + + + + LCDDisplay + + + (150,75) + Determine size: (height,width) + 1 + \(([0-9]+),([0-9]+)\) + + + Used as initial value for the LCD-Display + 0 + ([-]{0,1}\d+(.\d+)?) + + + 1 + Used to scale displayed value + 1 + -?[1-9]+[0-9]*(\.?[0-9]+)? + + + Used for tabs + PaPI-Tab + + + Used for window title + Sinus Value + + + 3 + Number of digits + 1 + [3-9] + + + (564,364) + Determine position: (x,y) + 1 + \(([0-9]+),([0-9]+)\) + + + 0 + Used to offset displayed value + 1 + -?\d+(\.?\d+)? + + + 100 + Minimal time between updates (in ms) + 1 + [0-9]+ + + + LCDSinus + + + + 0.0 + 1.0 + 3 + 100 + + + + + RBRefreshRate + + update_interval + + + + Sinus + + + f3_1 + + + + + + Radiobutton + + + (221,124) + Determine size: (height,width) + 1 + \(([0-9]+),([0-9]+)\) + + + Displayed Option + Low, Mid, High + This text is seen by the user. Must be separated by commas. + + + Value per Option + 1000, 500, 100 + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + Used for tabs + PaPI-Tab + + + Plugin Name + Set LCD refresh rate + Used for window title + + + (342,363) + Determine position: (x,y) + 1 + \(([0-9]+),([0-9]+)\) + + + 2 + Preselected Option + 1 + Preselect an option by its index. + [0-9]+ + + + RBRefreshRate + + + + + + + + + diff --git a/papi/plugin/pcp/radiobutton/Radiobutton.py b/papi/plugin/pcp/radiobutton/Radiobutton.py new file mode 100644 index 00000000..a8c2a12f --- /dev/null +++ b/papi/plugin/pcp/radiobutton/Radiobutton.py @@ -0,0 +1,139 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors +Sven Knuth +""" + +__author__ = 'knuths' + +from papi.plugin.base_classes.pcp_base import pcp_base +from PySide.QtGui import QSlider, QVBoxLayout, QWidget, QLabel, QRadioButton +from PySide import QtCore + +from papi.data.DPlugin import DBlock +import papi.constants as pc + +class Radiobutton(pcp_base): + def initiate_layer_0(self, config): + + self.block = DBlock('Choice') + + self.send_new_block_list([self.block]) + self.set_widget_for_internal_usage(self.create_widget()) + + return True + + def create_widget(self): + self.central_widget = QWidget() + + self.option_texts = [] + self.option_values = [] + self.pre_selected_index = None + + + if isinstance(self.config['selected_index']['value'], str): + if self.config['selected_index']['value'] != '': + self.pre_selected_index = int(self.config['selected_index']['value']) + + if isinstance(self.config['option_values']['value'], str): + self.option_values = str.split(self.config['option_values']['value'], ',') + + for i in range(len(self.option_values)): + self.option_values[i] = self.option_values[i].lstrip().rstrip() + + + if isinstance(self.config['option_texts']['value'], str): + self.option_texts = str.split(self.config['option_texts']['value'], ',') + + for i in range(len(self.option_texts)): + self.option_texts[i] = self.option_texts[i].lstrip().rstrip() + + + self.buttons = [] + + self.layout = QVBoxLayout(self.central_widget) + + for i in range(len(self.option_texts)): + button = QRadioButton(self.option_texts[i]) + + button.released.connect(self.button_released) + + if i == self.pre_selected_index: + button.setChecked(True) + + self.buttons.append(button) + self.layout.addWidget(button) + + self.central_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.central_widget.customContextMenuRequested.connect(self.show_context_menu) + + return self.central_widget + + def show_context_menu(self, pos): + gloPos = self.central_widget.mapToGlobal(pos) + self.cmenu = self.create_control_context_menu() + self.cmenu.exec_(gloPos) + + def button_released(self): + for i in range(len(self.buttons)): + if self.buttons[i].isChecked(): + self.config['selected_index']['value'] = str(i) + if len(self.option_values) == len(self.option_texts): + self.send_parameter_change(self.option_values[i], self.block.name) + else: + self.send_parameter_change(self.option_texts[i], self.block.name) + + def plugin_meta_updated(self): + pass + + def get_plugin_configuration(self): + config = { + 'option_texts': { + 'display_text' : 'Displayed Option', + 'value': 'Option Text 1, Option Text 2, Option Text 3', + 'tooltip': 'This text is seen by the user. Must be separated by commas.' + }, + 'option_values': { + 'display_text' : 'Value per Option', + 'value': '', + 'tooltip': 'It is possible to set a value for every option. ' + 'The corresponding value is send instead of the displayed text. ' + }, + 'selected_index': { + 'display_text' : 'Preselected Option', + 'value' : '', + 'regex' : pc.REGEX_SINGLE_INT, + 'tooltip': 'Preselect an option by its index.', + 'advanced' : '1' + }, + 'name': { + 'display_text' : 'Plugin Name', + 'value': 'RadioButton Label', + 'tooltip': 'Used for window title' + }} + return config + + def quit(self): + pass \ No newline at end of file diff --git a/papi/plugin/pcp/radiobutton/Radiobutton.yapsy-plugin b/papi/plugin/pcp/radiobutton/Radiobutton.yapsy-plugin new file mode 100644 index 00000000..c2200e42 --- /dev/null +++ b/papi/plugin/pcp/radiobutton/Radiobutton.yapsy-plugin @@ -0,0 +1,11 @@ +[Core] +Name = Radiobutton +Module = Radiobutton + +[Documentation] +Author = S.K. +Version = 0.1 +Website = www +Description = Enables a user to select between distinct options. + It is possible to define an 'option_texts' and an 'option_values'. + If the length between both settings differs the selected 'option_texts' will be send. \ No newline at end of file From 3e931b85fe8b93736c00e810b8dc40760e3e0421 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Fri, 27 Mar 2015 18:32:40 +0100 Subject: [PATCH 201/260] set active tab api for plugins added --- papi/gui/gui_api.py | 6 ++- papi/gui/gui_event_processing.py | 2 +- papi/gui/plugin_api.py | 10 +++-- papi/gui/qt_new/PapiTabManger.py | 6 +++ papi/gui/qt_new/main.py | 3 +- papi/last_active_papi.xml | 75 ++++++++++++++++++++++---------- 6 files changed, 72 insertions(+), 30 deletions(-) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index f6134fa3..5028f856 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -52,7 +52,7 @@ class Gui_api(QtCore.QObject): error_occured = QtCore.Signal(str, str, str) - def __init__(self, gui_data, core_queue, gui_id, get_gui_config_function = None, set_gui_config_function = None): + def __init__(self, gui_data, core_queue, gui_id, get_gui_config_function = None, set_gui_config_function = None, TabManager = None): super(Gui_api, self).__init__() self.gui_id = gui_id self.gui_data = gui_data @@ -60,6 +60,7 @@ def __init__(self, gui_data, core_queue, gui_id, get_gui_config_function = None, self.log = ConsoleLog(GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_PROCESS_CONSOLE_IDENTIFIER) self.get_gui_config_function = get_gui_config_function self.set_gui_config_function = set_gui_config_function + self.tabManager = TabManager def do_create_plugin(self, plugin_identifier, uname, config={}, autostart=True): """ @@ -438,6 +439,9 @@ def do_close_program(self): event = Event.instruction.CloseProgram(self.gui_id, 0, opt) self.core_queue.put(event) + def do_set_tab_active_by_name(self, tabName): + self.tabManager.set_tab_active_by_name(tabName) + def do_load_xml(self, path): """ Function to load a xml config to papi and apply the configuration. diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 087bc1a9..14e38c14 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -304,7 +304,7 @@ def process_create_plugin(self, event): dplugin.plugin_identifier = plugin_identifier dplugin.startup_config = config # call the init function of plugin and set queues and id - api = Plugin_api(self.gui_data, self.core_queue, self.gui_id, uname + ' API:') + api = Plugin_api(self.gui_data, self.core_queue, self.gui_id, uname + ' API:', tabManager=self.TabManger) # call the plugin developers init function with config diff --git a/papi/gui/plugin_api.py b/papi/gui/plugin_api.py index eaa68f4c..4029f47d 100644 --- a/papi/gui/plugin_api.py +++ b/papi/gui/plugin_api.py @@ -49,9 +49,9 @@ class Plugin_api(QtCore.QObject): resize_gui = QtCore.Signal(int, int) - def __init__(self, gui_data, core_queue, gui_id, PLUGIN_API_IDENTIFIER): + def __init__(self, gui_data, core_queue, gui_id, PLUGIN_API_IDENTIFIER, tabManager = None): super(Plugin_api, self).__init__() - self.__default_api = Gui_api(gui_data, core_queue, gui_id, PLUGIN_API_IDENTIFIER) + self.__default_api = Gui_api(gui_data, core_queue, gui_id, PLUGIN_API_IDENTIFIER, TabManager=tabManager) def do_create_plugin(self, plugin_identifier, uname, config={}, autostart = True): """ @@ -145,4 +145,8 @@ def do_pause_plugin_by_uname(self, plugin_uname): :param plugin_uname: uname of plugin to pause :type plugin_uname: basestring """ - self.__default_api.do_pause_plugin_by_uname(plugin_uname) \ No newline at end of file + self.__default_api.do_pause_plugin_by_uname(plugin_uname) + + + def do_set_tab_active_by_name(self, tabName): + self.__default_api.do_set_tab_active_by_name(tabName) \ No newline at end of file diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index 271efee9..9c2b1d7f 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -114,6 +114,12 @@ def get_currently_active_tab(self): def set_tab_active_by_index(self, index): self.tabWidget.setCurrentIndex(index) + def set_tab_active_by_name(self, tabName): + if tabName in self.tab_dict_uname: + tabObj = self.tab_dict_uname[tabName] + ind = self.tabWidget.indexOf(tabObj) + self.set_tab_active_by_index(ind) + def moveFromTo(self, start, dest, subWindow, posX=0, posY=0): if start in self.tab_dict_uname and dest in self.tab_dict_uname: startTab = self.tab_dict_uname[start] diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index d3707b8c..a096bd01 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -239,7 +239,8 @@ def set_gui_config(self, cfg): tabOb = self.TabManager.add_tab(name) self.TabManager.set_background_for_tab_with_name(name,bg) - if 'activeTab' in cfg: + if 'activeTab' in cfg: + if 'value' in cfg['activeTab']['active']: self.TabManager.set_tab_active_by_index(int( cfg['activeTab']['active']['value'] )) ################# diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index ecb3b855..b831a193 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,30 +1,8 @@ - + - - - default - 1 - - - default - 0 - - - default - 3 - - - default - 2 - - - default - 4 - - - 3 + 1 @@ -35,5 +13,54 @@ 853 + + + default + 0 + + + default + 2 + + + default + 1 + + + + OrtdController + + + Uname to use for ortd plugin instance + ORTDPlugin1 + 0 + + + \(([0-9]+),([0-9]+)\) + (300,300) + 1 + Determine size: (height,width) + + + ORTDController + + + TabXX + Used for tabs + + + \(([0-9]+),([0-9]+)\) + (0,0) + 1 + Determine position: (x,y) + + + OrtdController + + + + + + From 9d10ce9d75a9d9ec918802aac10e6b3e5f451b46 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Fri, 27 Mar 2015 18:35:09 +0100 Subject: [PATCH 202/260] set active tab api for plugins added --- papi/gui/qt_new/main.py | 5 +- papi/last_active_papi.xml | 509 +++++++++++++++++++++++++++++++++++--- 2 files changed, 483 insertions(+), 31 deletions(-) diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index a096bd01..5525bfc6 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -226,7 +226,10 @@ def set_gui_config(self, cfg): if 'position' in tabDetails: pos = int(tabDetails['position']) else: - pos = max(list(tabList.keys()))+1 + if len(list(tabList.keys())) > 1: + pos = max(list(tabList.keys()))+1 + else: + pos = 0 tabList[pos] = [name, bg] diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index b831a193..56d753d7 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,66 +1,515 @@ - + + + + 0 + default + + - 1 + 0 - - 771 - - 853 + 849 + + 787 + - - - default - 0 - - - default - 2 - - - default - 1 - - - - OrtdController + + Plot - - Uname to use for ortd plugin instance - ORTDPlugin1 - 0 + + 5 + (\d+) + + + [-1.0 0.999980260856] + 1 + y: range + (\d+\.\d+) + (300,300) + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + Plot + + + 0 + Grid-Y + ^(1|0)$ + bool + + + (299,0) + 1 + Determine position: (x,y) \(([0-9]+),([0-9]+)\) + + + 1 + Rolling Plot + ^(1|0)$ + bool + + + [0 0 0 0 0] + 1 + Style + ^\[(\s*\d\s*)+\] + + + PaPI-Tab + Used for tabs + + + VisualPlugin + Used for window title + + + 0 + Grid-X + ^(1|0)$ + bool + + + 400 + 1 + Buffersize + ^(\d+)$ + + + [0 1 2 3 4] + 1 + Color + ^\[(\s*\d\s*)+\] + + + + 5 + [0 0 0 0 0] + [0,1] + 0 + 1 + [0 1 2 3 4] + 400 + 0 + + + + + RBSetDS + + downsampling_rate + + + + Sinus + + + f3_1 + + + + RBXGrid + + x-grid + + + + RBYGrid + + y-grid + + + + RBSetBuffersize + + buffersize + + + + + + Radiobutton + + + PaPI-Tab + Used for tabs + + (300,300) 1 Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 1 + 1 + Preselect an option by its index. + Preselected Option + [0-9]+ - ORTDController + Set Downsampling Rate + Used for window title + + + 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 + This text is seen by the user. Must be separated by commas. + + + RBSetDS + + + (0,0) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + + + + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + + + + + + + + Sinus + + + 1 + \d+.{0,1}\d* + + + 3 + [0-9]+ + + + Sinus + + + 0.6 + + + + + f3_1 + + + f3_2 + + + f3_scalar + + + + + f2_1 + + + + + f1_f1DNAME + + + + + + + Radiobutton + - TabXX + PaPI-Tab Used for tabs + + (219,96) + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 0 + 1 + Preselect an option by its index. + Preselected Option + [0-9]+ + + + Enable/Disable X-Grid + Used for window title + Plugin Name + + + Off, On + This text is seen by the user. Must be separated by commas. + Displayed Option + + + RBXGrid + + (1,301) + 1 + Determine position: (x,y) \(([0-9]+),([0-9]+)\) - (0,0) + + + 0, 1 + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + Value per Option + + + + + + + + + + Radiobutton + + + PaPI-Tab + Used for tabs + + + (218,96) + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 0 + 1 + Preselect an option by its index. + Preselected Option + [0-9]+ + + + Enable/Disable Y-Grid + Used for window title + Plugin Name + + + Off, On + This text is seen by the user. Must be separated by commas. + Displayed Option + + + RBYGrid + + + (1,398) 1 Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + + + 0, 1 + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + Value per Option + + + + + + + + + + Radiobutton + + + PaPI-Tab + Used for tabs + + + (167,292) + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 2 + 1 + Preselect an option by its index. + Preselected Option + [0-9]+ + + + Set Buffersize + Used for window title + Plugin Name + + + 100, 200, 400, 800, 1200, 1600, 2000, 2500, 3000 + This text is seen by the user. Must be separated by commas. + Displayed Option - OrtdController + RBSetBuffersize + + + (598,2) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + + + + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + Value per Option + + + + + + + LCDDisplay + + + PaPI-Tab + Used for tabs + + + (150,75) + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 0 + Used as initial value for the LCD-Display + ([-]{0,1}\d+(.\d+)?) + + + 0 + 1 + Used to offset displayed value + -?\d+(\.?\d+)? + + + Sinus Value + Used for window title + + + 3 + 1 + Number of digits + [3-9] + + + 100 + 1 + Minimal time between updates (in ms) + [0-9]+ + + + (564,364) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + + + LCDSinus + + + 1 + 1 + Used to scale displayed value + -?[1-9]+[0-9]*(\.?[0-9]+)? + + + + 0.0 + 100 + 3 + 1.0 + + + + RBRefreshRate + + update_interval + + + + Sinus + + + f3_1 + + + + + + Radiobutton + + + PaPI-Tab + Used for tabs + + + (221,124) + 1 + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + + + 2 + 1 + Preselect an option by its index. + Preselected Option + [0-9]+ + + + Set LCD refresh rate + Used for window title + Plugin Name + + + Low, Mid, High + This text is seen by the user. Must be separated by commas. + Displayed Option + + + RBRefreshRate + + + (342,363) + 1 + Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + + + 1000, 500, 100 + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + Value per Option + + + + + + From 841af71ba576b0de1620c6508401748ac57004d5 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Fri, 27 Mar 2015 18:55:43 +0100 Subject: [PATCH 203/260] set active tab api for plugins added --- papi/last_active_papi.xml | 493 ++---------------------- papi/plugin/base_classes/base_plugin.py | 3 + 2 files changed, 29 insertions(+), 467 deletions(-) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 56d753d7..46fa8fcc 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,509 +1,68 @@ - + - - - 0 - default - - - - - 0 - - - 849 + 853 - 787 + 771 - - - Plot - - - 5 - (\d+) - - - [-1.0 0.999980260856] - 1 - y: range - (\d+\.\d+) - - - (300,300) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - Plot - - - 0 - Grid-Y - ^(1|0)$ - bool - - - (299,0) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - 1 - Rolling Plot - ^(1|0)$ - bool - - - [0 0 0 0 0] - 1 - Style - ^\[(\s*\d\s*)+\] - - - PaPI-Tab - Used for tabs - - - VisualPlugin - Used for window title - - + + 0 - Grid-X - ^(1|0)$ - bool - - - 400 - 1 - Buffersize - ^(\d+)$ - - - [0 1 2 3 4] - 1 - Color - ^\[(\s*\d\s*)+\] - - - - 5 - [0 0 0 0 0] - [0,1] - 0 - 1 - [0 1 2 3 4] - 400 - 0 - - - - - RBSetDS - - downsampling_rate - - - - Sinus - - - f3_1 - - - - RBXGrid - - x-grid - - - - RBYGrid - - y-grid - - - - RBSetBuffersize - - buffersize - - - - - + + + + + default + 0 + + + + Radiobutton - - PaPI-Tab - Used for tabs - - - (300,300) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 1 - 1 - Preselect an option by its index. - Preselected Option - [0-9]+ - - - Set Downsampling Rate - Used for window title - - 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 + Displayed Option + O, P, T This text is seen by the user. Must be separated by commas. - - RBSetDS - - - (0,0) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - + Value per Option + 1,2,3 It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - - - - - - - - - Sinus - - - 1 - \d+.{0,1}\d* - - - 3 - [0-9]+ - - - Sinus - - - - 0.6 - - - - - f3_1 - - - f3_2 - - - f3_scalar - - - - - f2_1 - - - - - f1_f1DNAME - - - - - - - Radiobutton - - - PaPI-Tab - Used for tabs - - (219,96) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 0 - 1 - Preselect an option by its index. - Preselected Option - [0-9]+ - - - Enable/Disable X-Grid - Used for window title - Plugin Name - - - Off, On - This text is seen by the user. Must be separated by commas. - Displayed Option - - - RBXGrid - - - (1,301) + (84,138) 1 - Determine position: (x,y) \(([0-9]+),([0-9]+)\) - - - 0, 1 - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - Value per Option - - - - - - - - - - Radiobutton - - - PaPI-Tab - Used for tabs - - - (218,96) - 1 Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - 0 - 1 - Preselect an option by its index. Preselected Option - [0-9]+ - - - Enable/Disable Y-Grid - Used for window title - Plugin Name - - - Off, On - This text is seen by the user. Must be separated by commas. - Displayed Option - - - RBYGrid - - - (1,398) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - 0, 1 - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - Value per Option - - - - - - - - - - Radiobutton - - - PaPI-Tab - Used for tabs - - - (167,292) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 2 - 1 + Preselect an option by its index. - Preselected Option [0-9]+ - - - Set Buffersize - Used for window title - Plugin Name - - - 100, 200, 400, 800, 1200, 1600, 2000, 2500, 3000 - This text is seen by the user. Must be separated by commas. - Displayed Option - - - RBSetBuffersize - - - (598,2) 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - Value per Option - - - - - - - - - - LCDDisplay - PaPI-Tab Used for tabs - - (150,75) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 0 - Used as initial value for the LCD-Display - ([-]{0,1}\d+(.\d+)?) - - - 0 - 1 - Used to offset displayed value - -?\d+(\.?\d+)? - - - Sinus Value - Used for window title - - - 3 - 1 - Number of digits - [3-9] - - - 100 - 1 - Minimal time between updates (in ms) - [0-9]+ - - (564,364) + (0,0) 1 - Determine position: (x,y) \(([0-9]+),([0-9]+)\) + Determine position: (x,y) - LCDSinus - - - 1 - 1 - Used to scale displayed value - -?[1-9]+[0-9]*(\.?[0-9]+)? - - - - 0.0 - 100 - 3 - 1.0 - - - - - RBRefreshRate - - update_interval - - - - Sinus - - - f3_1 - - - - - - Radiobutton - - - PaPI-Tab - Used for tabs - - - (221,124) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 2 - 1 - Preselect an option by its index. - Preselected Option - [0-9]+ + Radiobutton - Set LCD refresh rate - Used for window title Plugin Name - - - Low, Mid, High - This text is seen by the user. Must be separated by commas. - Displayed Option - - - RBRefreshRate - - - (342,363) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - 1000, 500, 100 - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - Value per Option + RadioButton Label + Used for window title diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py index e1160d9b..d54f7a0d 100644 --- a/papi/plugin/base_classes/base_plugin.py +++ b/papi/plugin/base_classes/base_plugin.py @@ -73,6 +73,9 @@ def resume(self): def quit(self): raise NotImplementedError("Please Implement this method") + def set_parameter(self, parameter_name, parameter_value): + raise NotImplementedError("Please Implement this method") + def set_parameter_internal(self, name, value): self.set_parameter(name, value) From 7f96af0b26bc47c1efb9316c238f380b64bc90d1 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Fri, 27 Mar 2015 18:57:59 +0100 Subject: [PATCH 204/260] Bugfix: overview menu -> right click on dparameter value --- cfg_collection/radiobutton_example.xml | 439 ++++++++++-------- papi/gui/qt_new/overview_menu.py | 5 + papi/last_active_papi.xml | 515 --------------------- papi/plugin/pcp/radiobutton/Radiobutton.py | 12 + 4 files changed, 268 insertions(+), 703 deletions(-) delete mode 100644 papi/last_active_papi.xml diff --git a/cfg_collection/radiobutton_example.xml b/cfg_collection/radiobutton_example.xml index bdd6870d..470eb339 100644 --- a/cfg_collection/radiobutton_example.xml +++ b/cfg_collection/radiobutton_example.xml @@ -1,15 +1,21 @@ - + + + + 0 + + - - 849 - 787 + + 849 + + 0 default @@ -17,85 +23,85 @@ Plot - - (300,300) - Determine size: (height,width) - 1 - \(([0-9]+),([0-9]+)\) - - - Grid-X - 0 - bool - ^(1|0)$ - - - 5 - (\d+) - - Used for tabs PaPI-Tab + Used for tabs + bool Grid-Y 0 + ^(1|0)$ + + bool + Grid-X + 0 ^(1|0)$ - (299,0) - Determine position: (x,y) - 1 + (301,0) \(([0-9]+),([0-9]+)\) - - - 400 - Buffersize + Determine position: (x,y) 1 - ^(\d+)$ + bool Rolling Plot 1 - bool ^(1|0)$ - [0 1 2 3 4] Color + [0 1 2 3 4] 1 ^\[(\s*\d\s*)+\] - - Plot - - - Used for window title - VisualPlugin + + (298,300) + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 - [-1.0 0.999980260856] y: range + [-1.0 0.999980260856] 1 (\d+\.\d+) - [0 0 0 0 0] Style + [0 0 0 0 0] 1 ^\[(\s*\d\s*)+\] + + VisualPlugin + Used for window title + + + Buffersize + 1200 + 1 + ^(\d+)$ + + + 10 + (\d+) + + + Plot + - 0 - 5 [0 1 2 3 4] 0 + 0 + [0 0 0 0 0] + 1200 + 10 1 [0,1] - [0 0 0 0 0] - 400 @@ -130,48 +136,54 @@ buffersize + + RBRolling + + rolling + + Radiobutton + + PaPI-Tab + Used for tabs + - (300,300) + (301,348) + \(([0-9]+),([0-9]+)\) Determine size: (height,width) 1 - \(([0-9]+),([0-9]+)\) - - - This text is seen by the user. Must be separated by commas. - 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 - - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - - - - Used for tabs - PaPI-Tab + + RBSetDS - Used for window title Set Downsampling Rate + Used for window title (0,0) + \(([0-9]+),([0-9]+)\) Determine position: (x,y) 1 - \(([0-9]+),([0-9]+)\) + + + + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 + This text is seen by the user. Must be separated by commas. - 1 Preselected Option - 1 - Preselect an option by its index. + 2 [0-9]+ - - - RBSetDS + Preselect an option by its index. + 1 @@ -183,6 +195,10 @@ Sinus + + 1 + \d+.{0,1}\d* + 3 [0-9]+ @@ -190,10 +206,6 @@ Sinus - - 1 - \d+.{0,1}\d* - 0.6 @@ -210,41 +222,34 @@ f3_scalar - - - f1_f1DNAME - - f2_1 + + + f1_f1DNAME + +
Radiobutton + + PaPI-Tab + Used for tabs + (219,96) + \(([0-9]+),([0-9]+)\) Determine size: (height,width) 1 - \(([0-9]+),([0-9]+)\) - - - Displayed Option - Off, On - This text is seen by the user. Must be separated by commas. - - Value per Option - 0, 1 - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - - - Used for tabs - PaPI-Tab + + RBXGrid Plugin Name @@ -252,20 +257,27 @@ Used for window title - (1,301) + (2,349) + \(([0-9]+),([0-9]+)\) Determine position: (x,y) 1 - \(([0-9]+),([0-9]+)\) + + + Value per Option + 0, 1 + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + Displayed Option + Off, On + This text is seen by the user. Must be separated by commas. - 0 Preselected Option - 1 - Preselect an option by its index. + 0 [0-9]+ - - - RBXGrid + Preselect an option by its index. + 1 @@ -277,25 +289,18 @@ Radiobutton + + PaPI-Tab + Used for tabs + (218,96) + \(([0-9]+),([0-9]+)\) Determine size: (height,width) 1 - \(([0-9]+),([0-9]+)\) - - - Displayed Option - Off, On - This text is seen by the user. Must be separated by commas. - - - Value per Option - 0, 1 - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - - Used for tabs - PaPI-Tab + + RBYGrid Plugin Name @@ -303,20 +308,27 @@ Used for window title - (1,398) + (0,446) + \(([0-9]+),([0-9]+)\) Determine position: (x,y) 1 - \(([0-9]+),([0-9]+)\) + + + Value per Option + 0, 1 + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + Displayed Option + Off, On + This text is seen by the user. Must be separated by commas. - 0 Preselected Option - 1 - Preselect an option by its index. + 0 [0-9]+ - - - RBYGrid + Preselect an option by its index. + 1 @@ -328,25 +340,18 @@ Radiobutton + + PaPI-Tab + Used for tabs + - (167,292) + (167,300) + \(([0-9]+),([0-9]+)\) Determine size: (height,width) 1 - \(([0-9]+),([0-9]+)\) - - - Displayed Option - 100, 200, 400, 800, 1200, 1600, 2000, 2500, 3000 - This text is seen by the user. Must be separated by commas. - - Value per Option - - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - - - Used for tabs - PaPI-Tab + + RBSetBuffersize Plugin Name @@ -354,20 +359,27 @@ Used for window title - (598,2) + (598,0) + \(([0-9]+),([0-9]+)\) Determine position: (x,y) 1 - \(([0-9]+),([0-9]+)\) + + + Value per Option + + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + Displayed Option + 100, 200, 400, 800, 1200, 1600, 2000, 2500, 3000 + This text is seen by the user. Must be separated by commas. - 2 Preselected Option - 1 - Preselect an option by its index. + 4 [0-9]+ - - - RBSetBuffersize + Preselect an option by its index. + 1 @@ -379,64 +391,64 @@ LCDDisplay - - (150,75) - Determine size: (height,width) + + Minimal time between updates (in ms) + 1000 1 - \(([0-9]+),([0-9]+)\) + [0-9]+ - - Used as initial value for the LCD-Display + 0 - ([-]{0,1}\d+(.\d+)?) - - - 1 - Used to scale displayed value + -?\d+(\.?\d+)? + Used to offset displayed value 1 - -?[1-9]+[0-9]*(\.?[0-9]+)? - Used for tabs PaPI-Tab + Used for tabs + + + (150,75) + \(([0-9]+),([0-9]+)\) + Determine size: (height,width) + 1 + + + LCDSinus - Used for window title Sinus Value - - - 3 - Number of digits - 1 - [3-9] + Used for window title - (564,364) + (558,323) + \(([0-9]+),([0-9]+)\) Determine position: (x,y) 1 - \(([0-9]+),([0-9]+)\) - - 0 - Used to offset displayed value + + 1 + -?[1-9]+[0-9]*(\.?[0-9]+)? + Used to scale displayed value 1 - -?\d+(\.?\d+)? - - 100 - Minimal time between updates (in ms) - 1 - [0-9]+ + + 0 + Used as initial value for the LCD-Display + ([-]{0,1}\d+(.\d+)?) - - LCDSinus + + 3 + [3-9] + Number of digits + 1 + 3 0.0 1.0 - 3 - 100 + 1000 @@ -458,46 +470,97 @@ Radiobutton + + PaPI-Tab + Used for tabs + (221,124) + \(([0-9]+),([0-9]+)\) Determine size: (height,width) 1 + + + RBRefreshRate + + + Plugin Name + Set LCD refresh rate + Used for window title + + + (337,322) \(([0-9]+),([0-9]+)\) + Determine position: (x,y) + 1 + + + Value per Option + 1000, 500, 100 + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. Displayed Option Low, Mid, High This text is seen by the user. Must be separated by commas. - - Value per Option - 1000, 500, 100 - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + Preselected Option + 0 + [0-9]+ + Preselect an option by its index. + 1 + + + + + + + + + Radiobutton + - Used for tabs PaPI-Tab + Used for tabs + + + Determine size: (height,width) + \(([0-9]+),([0-9]+)\) + 1 + (218,96) + + + RBRolling Plugin Name - Set LCD refresh rate + Enable/Disable Rolling Used for window title - (342,363) Determine position: (x,y) - 1 \(([0-9]+),([0-9]+)\) + 1 + (0,543) + + + Value per Option + 0, 1 + It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + + Displayed Option + Off, On + This text is seen by the user. Must be separated by commas. - 2 Preselected Option 1 - Preselect an option by its index. [0-9]+ - - - RBRefreshRate + Preselect an option by its index. + 1 diff --git a/papi/gui/qt_new/overview_menu.py b/papi/gui/qt_new/overview_menu.py index 36fda587..c3b678bb 100644 --- a/papi/gui/qt_new/overview_menu.py +++ b/papi/gui/qt_new/overview_menu.py @@ -507,6 +507,11 @@ def open_context_menu_parameter_tree(self, position): if self.parameterTree.isIndexHidden(index): return + index_sibling = index.sibling(index.row(), index.column()-1) + + if index_sibling.isValid(): + index = index_sibling + dparameter = self.parameterTree.model().data(index, Qt.UserRole) dplugin = self.pluginTree.model().data(self.pluginTree.currentIndex(), Qt.UserRole) diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml deleted file mode 100644 index 56d753d7..00000000 --- a/papi/last_active_papi.xml +++ /dev/null @@ -1,515 +0,0 @@ - - - - - 0 - default - - - - - 0 - - - - - 849 - - - 787 - - - - - Plot - - - 5 - (\d+) - - - [-1.0 0.999980260856] - 1 - y: range - (\d+\.\d+) - - - (300,300) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - Plot - - - 0 - Grid-Y - ^(1|0)$ - bool - - - (299,0) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - 1 - Rolling Plot - ^(1|0)$ - bool - - - [0 0 0 0 0] - 1 - Style - ^\[(\s*\d\s*)+\] - - - PaPI-Tab - Used for tabs - - - VisualPlugin - Used for window title - - - 0 - Grid-X - ^(1|0)$ - bool - - - 400 - 1 - Buffersize - ^(\d+)$ - - - [0 1 2 3 4] - 1 - Color - ^\[(\s*\d\s*)+\] - - - - 5 - [0 0 0 0 0] - [0,1] - 0 - 1 - [0 1 2 3 4] - 400 - 0 - - - - - RBSetDS - - downsampling_rate - - - - Sinus - - - f3_1 - - - - RBXGrid - - x-grid - - - - RBYGrid - - y-grid - - - - RBSetBuffersize - - buffersize - - - - - - Radiobutton - - - PaPI-Tab - Used for tabs - - - (300,300) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 1 - 1 - Preselect an option by its index. - Preselected Option - [0-9]+ - - - Set Downsampling Rate - Used for window title - - - 1, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50 - This text is seen by the user. Must be separated by commas. - - - RBSetDS - - - (0,0) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - - - - - - - - - - Sinus - - - 1 - \d+.{0,1}\d* - - - 3 - [0-9]+ - - - Sinus - - - - 0.6 - - - - - f3_1 - - - f3_2 - - - f3_scalar - - - - - f2_1 - - - - - f1_f1DNAME - - - - - - - Radiobutton - - - PaPI-Tab - Used for tabs - - - (219,96) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 0 - 1 - Preselect an option by its index. - Preselected Option - [0-9]+ - - - Enable/Disable X-Grid - Used for window title - Plugin Name - - - Off, On - This text is seen by the user. Must be separated by commas. - Displayed Option - - - RBXGrid - - - (1,301) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - 0, 1 - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - Value per Option - - - - - - - - - - Radiobutton - - - PaPI-Tab - Used for tabs - - - (218,96) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 0 - 1 - Preselect an option by its index. - Preselected Option - [0-9]+ - - - Enable/Disable Y-Grid - Used for window title - Plugin Name - - - Off, On - This text is seen by the user. Must be separated by commas. - Displayed Option - - - RBYGrid - - - (1,398) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - 0, 1 - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - Value per Option - - - - - - - - - - Radiobutton - - - PaPI-Tab - Used for tabs - - - (167,292) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 2 - 1 - Preselect an option by its index. - Preselected Option - [0-9]+ - - - Set Buffersize - Used for window title - Plugin Name - - - 100, 200, 400, 800, 1200, 1600, 2000, 2500, 3000 - This text is seen by the user. Must be separated by commas. - Displayed Option - - - RBSetBuffersize - - - (598,2) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - Value per Option - - - - - - - - - - LCDDisplay - - - PaPI-Tab - Used for tabs - - - (150,75) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 0 - Used as initial value for the LCD-Display - ([-]{0,1}\d+(.\d+)?) - - - 0 - 1 - Used to offset displayed value - -?\d+(\.?\d+)? - - - Sinus Value - Used for window title - - - 3 - 1 - Number of digits - [3-9] - - - 100 - 1 - Minimal time between updates (in ms) - [0-9]+ - - - (564,364) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - LCDSinus - - - 1 - 1 - Used to scale displayed value - -?[1-9]+[0-9]*(\.?[0-9]+)? - - - - 0.0 - 100 - 3 - 1.0 - - - - - RBRefreshRate - - update_interval - - - - Sinus - - - f3_1 - - - - - - Radiobutton - - - PaPI-Tab - Used for tabs - - - (221,124) - 1 - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - - - 2 - 1 - Preselect an option by its index. - Preselected Option - [0-9]+ - - - Set LCD refresh rate - Used for window title - Plugin Name - - - Low, Mid, High - This text is seen by the user. Must be separated by commas. - Displayed Option - - - RBRefreshRate - - - (342,363) - 1 - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - - - 1000, 500, 100 - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. - Value per Option - - - - - - - - - diff --git a/papi/plugin/pcp/radiobutton/Radiobutton.py b/papi/plugin/pcp/radiobutton/Radiobutton.py index a8c2a12f..aad78d1e 100644 --- a/papi/plugin/pcp/radiobutton/Radiobutton.py +++ b/papi/plugin/pcp/radiobutton/Radiobutton.py @@ -33,6 +33,8 @@ from PySide import QtCore from papi.data.DPlugin import DBlock +from papi.data.DParameter import DParameter + import papi.constants as pc class Radiobutton(pcp_base): @@ -43,6 +45,16 @@ def initiate_layer_0(self, config): self.send_new_block_list([self.block]) self.set_widget_for_internal_usage(self.create_widget()) + para_list = [] + + self.para_texts = DParameter('texts') + self.para_values = DParameter('values') + + para_list.append(self.para_texts) + para_list.append(self.para_values) + + self.send_parameter_change(para_list) + return True def create_widget(self): From e33bcd2dbf1f808f4328e6ca7aab7727fc9e4970 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Fri, 27 Mar 2015 19:05:20 +0100 Subject: [PATCH 205/260] set support for ortd to set tabs active --- .../AutoConfigDemo_ReplaceableSimulation.ipar | 14 +- papi/last_active_papi.xml | 178 +++++++++++++----- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 10 +- .../visual/OrtdController/OrtdController.py | 12 +- 4 files changed, 157 insertions(+), 57 deletions(-) diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar index 3b2ea845..2508fd4e 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar @@ -56,19 +56,19 @@ -1 -1 -1 - 1427211152 + 1427479437 -1 -1 2015 3 13 - 83 + 86 + 6 + 27 + 19 3 - 24 - 16 - 32 - 32 - 229 + 57 + 626 26 79 82 diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml index 46fa8fcc..f6fb91a6 100644 --- a/papi/last_active_papi.xml +++ b/papi/last_active_papi.xml @@ -1,74 +1,164 @@ - + - - - 853 - - - 771 - - 0 - + + 1 default + + 0 + default + + + 853 + + + 771 + + - - Radiobutton + + OrtdController - - Displayed Option - O, P, T - This text is seen by the user. Must be separated by commas. + + Used for tabs + PaPI-Tab - - Value per Option - 1,2,3 - It is possible to set a value for every option. The corresponding value is send instead of the displayed text. + + Uname to use for ortd plugin instance + 0 + ORTDPlugin1 - (84,138) - 1 - \(([0-9]+),([0-9]+)\) Determine size: (height,width) - - - Preselected Option - - Preselect an option by its index. - [0-9]+ + \(([0-9]+),([0-9]+)\) 1 + (300,300) - - PaPI-Tab - Used for tabs + + ORTDController - (0,0) - 1 - \(([0-9]+),([0-9]+)\) Determine position: (x,y) + \(([0-9]+),([0-9]+)\) + 1 + (0,0) - Radiobutton - - - Plugin Name - RadioButton Label - Used for window title + OrtdController + + + + ORTDPlugin1 + + + ControlSignalReset + ControlSignalCreate + ControlSignalSub + ControllerSignalParameter + ControllerSignalClose + ActiveTab + + + + + + ORTD_UDP + + + 1 + 127.0.0.1 + + + 1 + 20001 + + + file + 0 + /home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json + + + 1 + 20000 + + + + 0 + 0 + - + + + ControlSignalReset + + + ControlSignalCreate + + + ControlSignalSub + + + ControllerSignalParameter + + + ControllerSignalClose + + + ActiveTab + + + - + + + Plugin2 + + SliderLCDProgress + + + + Plugin1 + + Osci + + + - + + Button + + + PaPI-Tab + + + ([-]{0,1}\d+\.\d+) + 0 + + + + Go to + + + Slider LCD ProgressBar + + + (250,250) + + + Leaving to + + + ([-]{0,1}\d+\.\d+) + 0 + \ No newline at end of file diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 48b2c0bf..e6d8344f 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -118,6 +118,7 @@ def start_init(self, config=None): self.ControlBlock.add_signal(DSignal('ControlSignalSub')) self.ControlBlock.add_signal(DSignal('ControllerSignalParameter')) self.ControlBlock.add_signal(DSignal('ControllerSignalClose')) + self.ControlBlock.add_signal(DSignal('ActiveTab')) self.send_new_block_list([self.ControlBlock]) @@ -247,20 +248,19 @@ def process_received_package(self, rev): self.process_papi_configuration(plToCreate, plToClose, subscriptions, paraConnections) def process_papi_configuration(self, toCreate, toClose, subs, paraConnections): - - print(toCreate) - self.send_new_data('ControllerSignals', [1], {'ControlSignalReset': 1, 'ControlSignalCreate':None, 'ControlSignalSub':None, 'ControllerSignalParameter':None, - 'ControllerSignalClose':None}) + 'ControllerSignalClose':None, + 'ActiveTab': None }) self.send_new_data('ControllerSignals', [1], {'ControlSignalReset':0, 'ControlSignalCreate':toCreate, 'ControlSignalSub':subs, 'ControllerSignalParameter':paraConnections, - 'ControllerSignalClose':toClose}) + 'ControllerSignalClose':toClose, + 'ActiveTab': 'PaPI-Tab'}) def parse_json_stream(self,stream): decoder = json.JSONDecoder() diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index a0f342fc..967d8efa 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -163,6 +163,15 @@ def execute_cfg(self): self.control_api.do_delete_plugin_uname(pl_uname) self.plugin_started_list.remove(pl_uname) + + # wait before set active tab + time.sleep(1.0) + if 'ActiveTab' in Data: + cfg = Data['ActiveTab'] + if cfg is not None: + tabName = cfg + self.control_api.do_set_tab_active_by_name(tabName) + self.thread_alive = False def set_parameter(self, name, value): @@ -318,7 +327,8 @@ def subscribe_control_signal(self): 'ControlSignalCreate', 'ControlSignalSub', 'ControllerSignalParameter', - 'ControllerSignalClose']) + 'ControllerSignalClose', + 'ActiveTab']) self.api.do_set_parameter_uname(self.ortd_uname, 'triggerConfiguration', '1') From 6898b96721abe32dfe1832bbc619f812fdd528d3 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Fri, 27 Mar 2015 19:06:07 +0100 Subject: [PATCH 206/260] deleted last cfg in git --- papi/last_active_papi.xml | 164 -------------------------------------- 1 file changed, 164 deletions(-) delete mode 100644 papi/last_active_papi.xml diff --git a/papi/last_active_papi.xml b/papi/last_active_papi.xml deleted file mode 100644 index f6fb91a6..00000000 --- a/papi/last_active_papi.xml +++ /dev/null @@ -1,164 +0,0 @@ - - - - - 0 - - - - - 1 - default - - - 0 - default - - - - - 853 - - - 771 - - - - - OrtdController - - - Used for tabs - PaPI-Tab - - - Uname to use for ortd plugin instance - 0 - ORTDPlugin1 - - - Determine size: (height,width) - \(([0-9]+),([0-9]+)\) - 1 - (300,300) - - - ORTDController - - - Determine position: (x,y) - \(([0-9]+),([0-9]+)\) - 1 - (0,0) - - - OrtdController - - - - - - - ORTDPlugin1 - - - ControlSignalReset - ControlSignalCreate - ControlSignalSub - ControllerSignalParameter - ControllerSignalClose - ActiveTab - - - - - - ORTD_UDP - - - 1 - 127.0.0.1 - - - 1 - 20001 - - - file - 0 - /home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample/ProtocollConfigForController.json - - - 1 - 20000 - - - - 0 - 0 - - - - - ControlSignalReset - - - ControlSignalCreate - - - ControlSignalSub - - - ControllerSignalParameter - - - ControllerSignalClose - - - ActiveTab - - - - - - - Plugin2 - - SliderLCDProgress - - - - Plugin1 - - Osci - - - - - - Button - - - PaPI-Tab - - - ([-]{0,1}\d+\.\d+) - 0 - - - - Go to - - - Slider LCD ProgressBar - - - (250,250) - - - Leaving to - - - ([-]{0,1}\d+\.\d+) - 0 - \ No newline at end of file From 5882458fb193ccc3e25831d777ff85f507e270dc Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Fri, 27 Mar 2015 19:22:15 +0100 Subject: [PATCH 207/260] deleted last cfg in git --- papi/gui/gui_api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/papi/gui/gui_api.py b/papi/gui/gui_api.py index 5028f856..f4786fc1 100644 --- a/papi/gui/gui_api.py +++ b/papi/gui/gui_api.py @@ -442,6 +442,10 @@ def do_close_program(self): def do_set_tab_active_by_name(self, tabName): self.tabManager.set_tab_active_by_name(tabName) + def do_open_new_tabs_with_names_in_order(self, tabNames = None): + for name in tabNames: + self.tabManager.add_tab(name) + def do_load_xml(self, path): """ Function to load a xml config to papi and apply the configuration. From 4faba0163046f0c9e8dc3d47fddba38e27fe91e4 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Fri, 27 Mar 2015 19:30:33 +0100 Subject: [PATCH 208/260] Merge branch 'development' of https://github.com/TUB-Control/PaPI into development Conflicts: papi/last_active_papi.xml Plugin Radiobutton: Added parameters --- papi/plugin/pcp/radiobutton/Radiobutton.py | 53 ++++++++++++++++------ 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/papi/plugin/pcp/radiobutton/Radiobutton.py b/papi/plugin/pcp/radiobutton/Radiobutton.py index aad78d1e..c2283f7c 100644 --- a/papi/plugin/pcp/radiobutton/Radiobutton.py +++ b/papi/plugin/pcp/radiobutton/Radiobutton.py @@ -43,49 +43,63 @@ def initiate_layer_0(self, config): self.block = DBlock('Choice') self.send_new_block_list([self.block]) - self.set_widget_for_internal_usage(self.create_widget()) + para_list = [] - self.para_texts = DParameter('texts') - self.para_values = DParameter('values') + self.para_texts = DParameter('texts', default=self.config['option_texts']['value']) + self.para_values = DParameter('values', default=self.config['option_values']['value']) para_list.append(self.para_texts) para_list.append(self.para_values) - self.send_parameter_change(para_list) - - return True + self.send_new_parameter_list(para_list) - def create_widget(self): self.central_widget = QWidget() self.option_texts = [] self.option_values = [] self.pre_selected_index = None - if isinstance(self.config['selected_index']['value'], str): if self.config['selected_index']['value'] != '': self.pre_selected_index = int(self.config['selected_index']['value']) - if isinstance(self.config['option_values']['value'], str): - self.option_values = str.split(self.config['option_values']['value'], ',') + self.set_widget_for_internal_usage(self.central_widget) + self.layout = QVBoxLayout(self.central_widget) + + self.buttons = [] + + self.set_option_texts(self.config['option_texts']['value']) + self.set_option_values(self.config['option_values']['value']) + + self.update_widget() + + return True + + def set_option_values(self, values): + + if isinstance(values, str): + self.option_values = str.split(values, ',') for i in range(len(self.option_values)): self.option_values[i] = self.option_values[i].lstrip().rstrip() + def set_option_texts(self, texts): - if isinstance(self.config['option_texts']['value'], str): - self.option_texts = str.split(self.config['option_texts']['value'], ',') + if isinstance(texts, str): + self.option_texts = str.split(texts, ',') for i in range(len(self.option_texts)): self.option_texts[i] = self.option_texts[i].lstrip().rstrip() + def update_widget(self): - self.buttons = [] + for button in self.buttons: + self.layout.removeWidget(button) + button.deleteLater() - self.layout = QVBoxLayout(self.central_widget) + self.buttons = [] for i in range(len(self.option_texts)): button = QRadioButton(self.option_texts[i]) @@ -117,6 +131,17 @@ def button_released(self): else: self.send_parameter_change(self.option_texts[i], self.block.name) + def set_parameter(self, parameter_name, parameter_value): + + + if parameter_name == self.para_texts.name: + self.set_option_texts(parameter_value) + self.update_widget() + + if parameter_name == self.para_values.name: + self.set_option_values(parameter_value) + + def plugin_meta_updated(self): pass From 709355ef7b364d6508591264af3f6e5fc8aa13b6 Mon Sep 17 00:00:00 2001 From: SvKn Date: Fri, 27 Mar 2015 19:34:40 +0100 Subject: [PATCH 209/260] Update CHANGENLOG.md --- CHANGENLOG.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 0993df8f..61e50117 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -3,19 +3,20 @@ Changelog v.1.XX --- - * [fix]: core will check plugin state before routing, so data to paused plugins will not be routed (#19) - * [fix]: oboslete parameter in send_parameter_change was removed (#21) - * [fix]: clean up of DParameter (#18) - * [fix]: renamed some variables of ownProcess_base to be private - * [fix]: fixed memory leak of gui event processing timer loop (#25) - * [fix]: fixed memory leak due to recall of the plugin manager collection function - * [improvement]: changed the demux function to in improve performance - * [improvement]: improved core performance while processing new data (see #20) - * [improvement]: huge changes in the plotting plugin with performance benefits - * [feature]: modified PaPI gui to have tabs. User can organise visual plugins in tabs, remove tabs, rename tabs. - * [plugin]: added a new visual plugin: ProgressBar + * [fix]: Core will check plugin state before routing, so data to paused plugins will not be routed (#19) + * [fix]: Oboslete parameter in send_parameter_change was removed (#21) + * [fix]: Clean up of DParameter (#18) + * [fix]: Renamed some variables of ownProcess_base to be private + * [fix]: Fixed memory leak of gui event processing timer loop (#25) + * [fix]: Fixed memory leak due to recall of the plugin manager collection function + * [improvement]: Changed the demux function to in improve performance + * [improvement]: Improved core performance while processing new data (see #20) + * [improvement]: Huge changes in the plotting plugin with performance benefits + * [feature]: Modified PaPI gui to have tabs. User can organise visual plugins in tabs, remove tabs, rename tabs. + * [plugin]: Added a new visual plugin: ProgressBar * [plugin]: Minor changes in LCDDisplay and Slider - * [improvement]: shifted some control logic from gui to core to increase speed when changing cfgs incl. subscriptions + * [plugin]: Added a new pcp plugin: Radiobutton + * [improvement]: Shifted some control logic from gui to core to increase speed when changing cfgs incl. subscriptions v.1.0 --- From 4b2a7b23fbbbff6264f4df1bd070a348be5b2b20 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Fri, 27 Mar 2015 19:38:11 +0100 Subject: [PATCH 210/260] deleted last cfg in git --- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index e6d8344f..4001fc1d 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -240,14 +240,14 @@ def process_received_package(self, rev): cfg = d ORTDSources, ORTDParameters, plToCreate, \ - plToClose, subscriptions, paraConnections = self.extract_config_elements(cfg) + plToClose, subscriptions, paraConnections, activeTab = self.extract_config_elements(cfg) self.update_block_list(ORTDSources) self.update_parameter_list(ORTDParameters) - self.process_papi_configuration(plToCreate, plToClose, subscriptions, paraConnections) + self.process_papi_configuration(plToCreate, plToClose, subscriptions, paraConnections, activeTab) - def process_papi_configuration(self, toCreate, toClose, subs, paraConnections): + def process_papi_configuration(self, toCreate, toClose, subs, paraConnections, activeTab): self.send_new_data('ControllerSignals', [1], {'ControlSignalReset': 1, 'ControlSignalCreate':None, 'ControlSignalSub':None, @@ -260,7 +260,7 @@ def process_papi_configuration(self, toCreate, toClose, subs, paraConnections): 'ControlSignalSub':subs, 'ControllerSignalParameter':paraConnections, 'ControllerSignalClose':toClose, - 'ActiveTab': 'PaPI-Tab'}) + 'ActiveTab': activeTab}) def parse_json_stream(self,stream): decoder = json.JSONDecoder() @@ -467,6 +467,7 @@ def extract_config_elements(self, configuration): plToClose = {} ORTDSources = {} ORTDParameters = {} + activeTab = 'PaPI-Tab' if 'PaPIConfig' in configuration: if 'ToCreate' in configuration['PaPIConfig']: @@ -481,12 +482,15 @@ def extract_config_elements(self, configuration): if 'ToClose' in configuration['PaPIConfig']: plToClose = configuration['PaPIConfig']['ToClose'] + if 'ActiveTab' in configuration['PaPIConfig']: + activeTab = configuration['PaPIConfig']['tab'] + if 'SourcesConfig' in configuration: ORTDSources = configuration['SourcesConfig'] if 'ParametersConfig' in configuration: ORTDParameters = configuration['ParametersConfig'] - return ORTDSources, ORTDParameters, plToCreate, plToClose, subscriptions, paraConnections + return ORTDSources, ORTDParameters, plToCreate, plToClose, subscriptions, paraConnections, activeTab From 431ec5c7b779778d845ea6f7f372b4acc35f8bc6 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Fri, 27 Mar 2015 20:20:15 +0100 Subject: [PATCH 211/260] Minor changes: Run Mode --- papi/gui/qt_new/main.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index 5525bfc6..b774c7d4 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -453,6 +453,7 @@ def toggle_run_mode(self): self.loadButton.show() self.saveButton.show() self.menubar.setHidden(False) + self.disable_lock() elif not self.in_run_mode: self.in_run_mode = True @@ -460,6 +461,35 @@ def toggle_run_mode(self): self.loadButton.hide() self.saveButton.hide() self.menubar.hide() + self.enable_lock() + + def enable_lock(self): + for tab_name in self.TabManager.get_tabs_by_uname(): + area = self.TabManager.get_tabs_by_uname()[tab_name] + + windowsList = area.subWindowList() + + for window in windowsList: + + #window.setAttribute(Qt.WA_NoBackground) + + #window.setAttribute(Qt.WA_NoSystemBackground) + #window.setAttribute(Qt.WA_TranslucentBackground) + + window.setMouseTracking(False) + window.setWindowFlags( Qt.WindowTitleHint | Qt.FramelessWindowHint ) + + def disable_lock(self): + for tab_name in self.TabManager.get_tabs_by_uname(): + area = self.TabManager.get_tabs_by_uname()[tab_name] + + windowsList = area.subWindowList() + + for window in windowsList: + + window.setMouseTracking(True) + window.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowMinMaxButtonsHint | Qt.WindowTitleHint ) + def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: From c7af9c3938c8b432692e61e67756d8730bc73177 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 31 Mar 2015 10:35:05 +0200 Subject: [PATCH 212/260] tabs for runmode --- papi/gui/qt_new/PapiTabManger.py | 11 +++++++---- papi/gui/qt_new/main.py | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/papi/gui/qt_new/PapiTabManger.py b/papi/gui/qt_new/PapiTabManger.py index 9c2b1d7f..2dbf4dcc 100644 --- a/papi/gui/qt_new/PapiTabManger.py +++ b/papi/gui/qt_new/PapiTabManger.py @@ -195,10 +195,13 @@ def getTabPosition_by_name(self, tabName): ind = self.tabWidget.indexOf(tabObj) return ind - def setTabPosition_by_name(self, tabName): - if tabName in self.tab_dict_uname: - tabObj = self.tab_dict_uname[tabName] - #self.tabWidget. + def setTabs_movable_closable(self, move, close): + if isinstance(move, bool): + self.tabWidget.setMovable(move) + + if isinstance(close,bool): + self.tabWidget.setTabsClosable(close) + def show_context_menu(self, pos): self.cmenu = self.create_context_menu() diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index b774c7d4..015d3263 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -454,6 +454,7 @@ def toggle_run_mode(self): self.saveButton.show() self.menubar.setHidden(False) self.disable_lock() + self.TabManager.setTabs_movable_closable(True, True) elif not self.in_run_mode: self.in_run_mode = True @@ -462,6 +463,7 @@ def toggle_run_mode(self): self.saveButton.hide() self.menubar.hide() self.enable_lock() + self.TabManager.setTabs_movable_closable(False, False) def enable_lock(self): for tab_name in self.TabManager.get_tabs_by_uname(): From 68ae1bffb420ada833a6f28810ef9cebb2ab6fbd Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 31 Mar 2015 11:08:20 +0200 Subject: [PATCH 213/260] added plugin_uname to execute function. ITS NOT COMPATIBLE TO OLD PLUGNS, so make sure to adapt your plugins!! --- papi/core.py | 4 ++-- papi/event/data/NewData.py | 6 +++++- papi/gui/gui_event_processing.py | 3 ++- papi/plugin/base_classes/base_plugin.py | 4 ++-- papi/plugin/base_classes/ownProcess_base.py | 2 +- papi/plugin/dpp/add/Add.py | 2 +- papi/plugin/dpp/toHDD/ToHDD_CSV.py | 2 +- papi/plugin/dpp/trigger/Trigger.py | 2 +- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 2 +- papi/plugin/io/cpu_load/CPU_Load.py | 2 +- papi/plugin/io/fourier_rect/Fourier_Rect.py | 2 +- papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py | 2 +- papi/plugin/io/sinus/Sinus.py | 2 +- papi/plugin/templates/IOP_DPP_template.py | 2 +- papi/plugin/templates/visual_template.py | 2 +- papi/plugin/visual/HTMLViewer/HTMLViewer.py | 2 +- papi/plugin/visual/LCDDisplay/LCDDisplay.py | 2 +- papi/plugin/visual/OrtdController/OrtdController.py | 2 +- papi/plugin/visual/Plot/Plot.py | 2 +- papi/plugin/visual/ProgressBar/ProgressBar.py | 2 +- papi/plugin/visual/WizardExample/WizardExample.py | 2 +- 21 files changed, 28 insertions(+), 23 deletions(-) diff --git a/papi/core.py b/papi/core.py index 56711ba1..f9ed280e 100644 --- a/papi/core.py +++ b/papi/core.py @@ -592,7 +592,7 @@ def __process_new_data__(self, event): else: # Plugin is not running in GUI, so just 1:1 relation for event and destinations opt.parameter_alias = pl.get_subscribtions()[oID][opt.block_name].alias - new_event = Event.data.NewData(oID, [pl.id], opt) + new_event = Event.data.NewData(oID, [pl.id], opt, source_plugin_uname= dplug.uname) pl.queue.put(new_event) # this event will be a new parameter value for a plugin @@ -612,7 +612,7 @@ def __process_new_data__(self, event): # send new_data event to GUI with id_list of destinations opt = event.get_optional_parameter() opt.parameter_alias = pl.get_subscribtions()[oID][opt.block_name].alias - new_event = Event.data.NewData(oID, id_list, opt) + new_event = Event.data.NewData(oID, id_list, opt, source_plugin_uname= dplug.uname) self.gui_event_queue.put(new_event) # process new_data seemed correct return 1 diff --git a/papi/event/data/NewData.py b/papi/event/data/NewData.py index f4da4252..fe011c57 100644 --- a/papi/event/data/NewData.py +++ b/papi/event/data/NewData.py @@ -32,5 +32,9 @@ class NewData(DataBase): - def __init__(self, oID, destID, opt): + def __init__(self, oID, destID, opt, source_plugin_uname): super().__init__(oID,destID, 'new_data', opt) + + #self.block_name = + self.source_plugin_uname = source_plugin_uname + diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 14e38c14..30312daf 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -165,7 +165,8 @@ def process_new_data_event(self, event): if opt.is_parameter is False: dplugin.plugin.execute( Data=dplugin.plugin.demux(opt.data_source_id, opt.block_name, opt.data), - block_name=opt.block_name) + block_name=opt.block_name, + plugin_uname = event.source_plugin_uname) else: dplugin.plugin.set_parameter_internal(opt.parameter_alias, opt.data) except Exception as E: diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py index d54f7a0d..5cdc768b 100644 --- a/papi/plugin/base_classes/base_plugin.py +++ b/papi/plugin/base_classes/base_plugin.py @@ -46,7 +46,7 @@ def papi_init(self, ): def get_type(self): raise NotImplementedError("Please Implement this method") - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): raise NotImplementedError("Please Implement this method") def get_configuration_base(self): @@ -116,7 +116,7 @@ def send_new_data(self, block_name, time_line, data): opt.data_source_id = self.__id__ opt.block_name = block_name - event = Event.data.NewData(self.__id__, 0, opt) + event = Event.data.NewData(self.__id__, 0, opt, None) self._Core_event_queue__.put(event) diff --git a/papi/plugin/base_classes/ownProcess_base.py b/papi/plugin/base_classes/ownProcess_base.py index a1903114..de57504e 100644 --- a/papi/plugin/base_classes/ownProcess_base.py +++ b/papi/plugin/base_classes/ownProcess_base.py @@ -102,7 +102,7 @@ def work_process(self, CoreQueue, pluginQueue, id, defaultEventTriggered=False, opt = event.get_optional_parameter() if opt.is_parameter is False: data = self.demux(opt.data_source_id, opt.block_name, opt.data) - self.execute(Data=data, block_name = opt.block_name) + self.execute(Data=data, block_name = opt.block_name, plugin_uname= event.source_plugin_uname) if opt.is_parameter is True: self.set_parameter_internal(opt.parameter_alias, opt.data) if op == 'set_parameter' and self.__plugin_stopped is False: diff --git a/papi/plugin/dpp/add/Add.py b/papi/plugin/dpp/add/Add.py index 541fd759..2bb3437c 100644 --- a/papi/plugin/dpp/add/Add.py +++ b/papi/plugin/dpp/add/Add.py @@ -70,7 +70,7 @@ def pause(self): def resume(self): pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): #self.approx = round(self.approx_max*self.para1.value) # self.vec[1] = 0 # self.vec[0] = Data[0] diff --git a/papi/plugin/dpp/toHDD/ToHDD_CSV.py b/papi/plugin/dpp/toHDD/ToHDD_CSV.py index 288fa111..dfb9fa9c 100644 --- a/papi/plugin/dpp/toHDD/ToHDD_CSV.py +++ b/papi/plugin/dpp/toHDD/ToHDD_CSV.py @@ -63,7 +63,7 @@ def resume(self): print('toHDD resume') pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): t = Data['t'] if block_name not in self.known_blocks.keys(): diff --git a/papi/plugin/dpp/trigger/Trigger.py b/papi/plugin/dpp/trigger/Trigger.py index e9f09499..e9b13b31 100644 --- a/papi/plugin/dpp/trigger/Trigger.py +++ b/papi/plugin/dpp/trigger/Trigger.py @@ -77,7 +77,7 @@ def pause(self): def resume(self): pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): pass def set_parameter(self, name, value): diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 4001fc1d..9cd2bce7 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -422,7 +422,7 @@ def thread_executeBackUP(self): # Thread ended self.sock_recv.close() - def execute(self, Data=None, block_name=None): + def execute(self, Data=None, block_name = None, plugin_uname = None): raise Exception('Should not be called!') def set_parameter(self, name, value): diff --git a/papi/plugin/io/cpu_load/CPU_Load.py b/papi/plugin/io/cpu_load/CPU_Load.py index ec5fa0f1..84d10d05 100644 --- a/papi/plugin/io/cpu_load/CPU_Load.py +++ b/papi/plugin/io/cpu_load/CPU_Load.py @@ -41,7 +41,7 @@ def pause(self): def resume(self): pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): vec = numpy.zeros((2,1)) vec[0,0] = self.t diff --git a/papi/plugin/io/fourier_rect/Fourier_Rect.py b/papi/plugin/io/fourier_rect/Fourier_Rect.py index 7e26db3f..3abd76ec 100644 --- a/papi/plugin/io/fourier_rect/Fourier_Rect.py +++ b/papi/plugin/io/fourier_rect/Fourier_Rect.py @@ -80,7 +80,7 @@ def pause(self): def resume(self): pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): # amount of elements per vector: self.amax # amount of vectors : self.max_approx+1 vec = numpy.zeros( (self.max_approx, (self.amax) )) diff --git a/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py b/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py index 9324261a..f76fc574 100644 --- a/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py +++ b/papi/plugin/io/fourier_rect_mod/Fourier_Rect_MOD.py @@ -117,7 +117,7 @@ def thread_execute(self,host,port): time.sleep(0.001*self.amax ) - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): print("EXECUTE FUNC") pass diff --git a/papi/plugin/io/sinus/Sinus.py b/papi/plugin/io/sinus/Sinus.py index 87d4206e..f63f6e71 100644 --- a/papi/plugin/io/sinus/Sinus.py +++ b/papi/plugin/io/sinus/Sinus.py @@ -85,7 +85,7 @@ def resume(self): print('Sinus resume') pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): vec = numpy.zeros( (2,self.amax)) vec2 = numpy.zeros((2,self.amax)) vec3 = numpy.zeros((3,self.amax)) diff --git a/papi/plugin/templates/IOP_DPP_template.py b/papi/plugin/templates/IOP_DPP_template.py index 37a344ab..b9b17682 100644 --- a/papi/plugin/templates/IOP_DPP_template.py +++ b/papi/plugin/templates/IOP_DPP_template.py @@ -88,7 +88,7 @@ def resume(self): # e.a. reopen communication ports, files etc. pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): # Do main work here! # If this plugin is an IOP plugin, then there will be no Data parameter because it wont get data # If this plugin is a DPP, then it will get Data with data diff --git a/papi/plugin/templates/visual_template.py b/papi/plugin/templates/visual_template.py index 43fe0289..5d3bd5a4 100644 --- a/papi/plugin/templates/visual_template.py +++ b/papi/plugin/templates/visual_template.py @@ -101,7 +101,7 @@ def resume(self): # e.a. reopen communication ports, files etc. pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): # Do main work here! # If this plugin is an IOP plugin, then there will be no Data parameter because it wont get data # If this plugin is a DPP, then it will get Data with data diff --git a/papi/plugin/visual/HTMLViewer/HTMLViewer.py b/papi/plugin/visual/HTMLViewer/HTMLViewer.py index febe5f00..dae00808 100644 --- a/papi/plugin/visual/HTMLViewer/HTMLViewer.py +++ b/papi/plugin/visual/HTMLViewer/HTMLViewer.py @@ -79,7 +79,7 @@ def resume(self): # e.a. reopen communication ports, files etc. pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): # Do main work here! # If this plugin is an IOP plugin, then there will be no Data parameter because it wont get data # If this plugin is a DPP, then it will get Data with data diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.py b/papi/plugin/visual/LCDDisplay/LCDDisplay.py index b81d76ce..48110dc1 100644 --- a/papi/plugin/visual/LCDDisplay/LCDDisplay.py +++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.py @@ -111,7 +111,7 @@ def resume(self): # e.a. reopen communication ports, files etc. self.LcdWidget.display('...') - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): # Do main work here! # If this plugin is an IOP plugin, then there will be no Data parameter because it wont get data # If this plugin is a DPP, then it will get Data with data diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py index 967d8efa..9c0ddab6 100644 --- a/papi/plugin/visual/OrtdController/OrtdController.py +++ b/papi/plugin/visual/OrtdController/OrtdController.py @@ -80,7 +80,7 @@ def resume(self): # e.a. reopen communication ports, files etc. pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): # Do main work here! # If this plugin is an IOP plugin, then there will be no Data parameter because it wont get data # If this plugin is a DPP, then it will get Data with data diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py index e64c6faa..cc3fbb84 100644 --- a/papi/plugin/visual/Plot/Plot.py +++ b/papi/plugin/visual/Plot/Plot.py @@ -313,7 +313,7 @@ def resume(self): """ self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=False, y=True) - def execute(self, Data=None, block_name=None): + def execute(self, Data=None, block_name = None, plugin_uname = None): """ Function execute diff --git a/papi/plugin/visual/ProgressBar/ProgressBar.py b/papi/plugin/visual/ProgressBar/ProgressBar.py index fc2042f8..b7690c27 100644 --- a/papi/plugin/visual/ProgressBar/ProgressBar.py +++ b/papi/plugin/visual/ProgressBar/ProgressBar.py @@ -129,7 +129,7 @@ def resume(self): # e.a. reopen communication ports, files etc. pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): # Do main work here! # If this plugin is an IOP plugin, then there will be no Data parameter because it wont get data # If this plugin is a DPP, then it will get Data with data diff --git a/papi/plugin/visual/WizardExample/WizardExample.py b/papi/plugin/visual/WizardExample/WizardExample.py index a91904b1..ea99272c 100644 --- a/papi/plugin/visual/WizardExample/WizardExample.py +++ b/papi/plugin/visual/WizardExample/WizardExample.py @@ -120,7 +120,7 @@ def resume(self): # e.a. reopen communication ports, files etc. pass - def execute(self, Data=None, block_name = None): + def execute(self, Data=None, block_name = None, plugin_uname = None): # Do main work here! # If this plugin is an IOP plugin, then there will be no Data parameter because it wont get data # If this plugin is a DPP, then it will get Data with data From 5d50de12c798d49726a4d80ca8a8ab53a505e3d4 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 31 Mar 2015 12:43:48 +0200 Subject: [PATCH 214/260] added plugin_uname to execute function. ITS NOT COMPATIBLE TO OLD PLUGNS, so make sure to adapt your plugins!! --- .../AutoConfigDemo_ReplaceableSimulation.ipar | 8183 +++++++++++++---- .../AutoConfigDemo_ReplaceableSimulation.rpar | 33 +- .../SwitchingPaPiConfig.ipar | 277 +- .../SwitchingPaPiConfig.sce | 4 +- papi/plugin/base_classes/base_plugin.py | 2 +- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 44 +- 6 files changed, 6792 insertions(+), 1751 deletions(-) diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar index 2508fd4e..d8ca20e3 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar @@ -40,8 +40,8 @@ 10 54 0 - 5091 - 36 + 10090 + 63 1 1 2 @@ -56,19 +56,19 @@ -1 -1 -1 - 1427479437 + 1427798592 -1 -1 2015 3 - 13 - 86 - 6 - 27 - 19 + 14 + 90 3 - 57 - 626 + 31 + 12 + 43 + 12 + 600 26 79 82 @@ -97,7 +97,7 @@ 105 99 1 - 33 + 59 202 100 0 @@ -126,175 +126,331 @@ 100 167 3 + 32 + 0 + 210 + 100 + 199 + 3 6 1 - 211 + 212 100 - 173 + 205 4 49 0 - 213 + 214 100 - 222 + 254 4 6 1 - 215 + 216 100 - 228 + 260 5 49 0 - 217 + 218 100 - 277 + 309 5 - 49 - 2 - 218 + 25 + 0 + 219 100 - 326 - 7 + 334 + 5 6 1 220 100 - 332 + 340 + 6 + 6 + 2 + 224 + 100 + 346 8 - 3182 - 13 - 222 + 6 + 2 + 226 100 - 3514 - 21 + 352 + 10 + 6 + 2 + 228 + 100 + 358 + 12 + 8 + 3 + 230 + 100 + 366 + 15 6 1 - 224 + 232 100 - 3520 - 22 + 372 + 16 + 8 + 3 + 234 + 100 + 380 + 19 + 6 + 1 + 236 + 100 + 386 + 20 + 6 + 1 + 238 + 100 + 392 + 21 8 0 - 226 + 240 + 100 + 400 + 21 + 6 + 0 + 242 100 - 3528 + 406 + 21 + 6 + 1 + 244 + 100 + 412 22 6 0 - 228 + 246 100 - 3534 + 418 22 6 1 - 230 + 248 100 - 3540 + 424 23 6 0 - 232 + 250 + 100 + 430 + 23 + 76 + 0 + 252 + 100 + 506 + 23 + 9 + 0 + 254 + 100 + 515 + 23 + 161 + 0 + 255 100 - 3546 + 676 23 6 1 - 234 + 257 + 100 + 682 + 24 + 8 + 0 + 259 100 - 3552 + 690 24 6 0 - 236 + 261 100 - 3558 + 696 24 6 1 - 238 + 263 100 - 3564 + 702 25 6 0 - 240 + 265 100 - 3570 + 708 25 + 6 + 1 + 267 + 100 + 714 + 26 + 6 + 0 + 269 + 100 + 720 + 26 76 0 - 242 + 271 100 - 3646 - 25 + 796 + 26 9 0 - 244 + 273 100 - 3655 - 25 + 805 + 26 161 0 - 245 + 274 100 - 3816 - 25 + 966 + 26 + 49 + 2 + 275 + 100 + 1015 + 28 + 6 + 1 + 277 + 100 + 1021 + 29 + 7263 + 21 + 279 + 100 + 8284 + 50 6 + 1 + 281 + 100 + 8290 + 51 + 8 0 - 247 + 283 100 - 3822 - 25 + 8298 + 51 + 6 + 0 + 285 + 100 + 8304 + 51 6 1 - 248 + 287 100 - 3828 - 26 + 8310 + 52 6 - 2 - 250 + 0 + 289 100 - 3834 - 28 + 8316 + 52 + 6 + 1 + 291 + 100 + 8322 + 53 6 0 - 252 + 293 100 - 3840 - 28 - 688 + 8328 + 53 6 - 254 + 1 + 295 100 - 4528 - 34 + 8334 + 54 6 - 2 - 256 + 0 + 297 100 - 4534 - 36 - 37 + 8340 + 54 + 76 0 - 257 + 299 100 - 4571 - 36 + 8416 + 54 + 9 + 0 + 301 + 100 + 8425 + 54 + 161 + 0 + 302 + 100 + 8586 + 54 6 0 - 259 + 304 100 - 4577 - 36 - 78 + 8592 + 54 + 6 + 1 + 305 + 100 + 8598 + 55 + 6 + 2 + 307 + 100 + 8604 + 57 + 6 0 + 309 + 100 + 8610 + 57 + 688 + 6 100 101 - 4655 - 36 - 236 + 9298 + 63 + 436 0 40 202 @@ -463,14 +619,46 @@ 1 1 0 - 40 + 170 209 + 26 + 0 + 1 + 0 + 1 + 24 + 67 + 97 + 115 + 101 + 32 + 111 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 58 + 32 + 40 + 210 0 1 1 0 15007 - 211 + 212 43 0 1 @@ -519,13 +707,13 @@ 114 121 40 - 213 + 214 0 1 1 0 15007 - 215 + 216 43 0 1 @@ -573,65 +761,131 @@ 111 114 121 - 15005 - 217 - 43 + 170 + 218 + 19 + 0 + 1 + 0 + 1 + 17 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 32 + 40 + 219 + 0 + 1 + 1 + 0 + 12 + 220 + 0 2 1 0 + 12 + 224 0 - 257 2 1 0 + 12 + 226 0 + 2 + 1 0 + 30 + 228 + 2 + 3 + 1 0 0 + 1 + 20 + 230 + 0 + 1 + 1 + 0 + 30 + 232 + 2 + 3 + 1 + 0 + 0 + 1 + 20 + 234 + 0 + 1 + 1 0 - 32 - 83 - 119 - 105 - 116 - 99 - 104 - 105 - 110 - 103 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 77 - 101 - 109 - 111 - 114 - 121 - 46 - 109 - 101 - 109 - 111 - 114 - 121 40 - 218 + 236 0 1 1 0 - 15011 - 220 - 3176 - 13 + 60005 + 238 + 2 + 0 + 1 + 0 + 0 + 100000 + 60031 + 240 + 0 + 0 + 1 + 0 + 40 + 242 + 0 + 1 + 1 + 0 + 60031 + 244 + 0 + 0 + 1 + 0 + 40 + 246 + 0 + 1 + 1 + 0 + 60031 + 248 + 0 + 0 + 1 + 0 + 39011 + 250 + 70 + 0 1 0 1 @@ -640,112 +894,198 @@ 4 0 0 - 2 + 5 0 11 4 - 2 + 5 0 2 0 12 4 - 4 + 7 0 - 2 + 5 0 13 4 - 6 + 12 0 2 0 14 4 - 8 + 14 0 2 0 15 4 - 10 + 16 0 2 0 20 4 - 12 + 18 0 - 3113 + 1 0 21 1 - 3125 + 19 0 1 - 13 + 0 + 4 1 1 1 1 1 + 20 + 4 + 2 + 2 + 2 257 1 - 257 + 6 1 1 1 + 2 + 0 + 0 + 60035 + 252 + 3 + 0 1 - 3112 + 0 1 - 7 + 0 + 20 + 39004 + 254 + 155 + 0 + 1 + 0 + 1 + 9 10 4 0 0 - 1 + 3 0 11 4 - 1 + 3 0 1 0 12 4 - 2 + 4 0 - 1 + 3 0 13 4 - 3 + 7 0 1 0 - 21 + 14 4 + 8 + 0 + 2 + 0 + 15 4 + 10 0 - 27 + 2 0 - 22 + 20 4 - 31 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 0 + 30 4 + 56 0 - 900 + 43 + 0 + 2 + 20 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 10 - 35 + 4 0 - 3033 - 13 0 + 2 0 + 11 + 4 + 2 0 + 2 0 - 26 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 20 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 83 119 105 @@ -765,118 +1105,161 @@ 102 105 103 - 84 - 104 - 114 - 101 97 - 100 - 49 - 3 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 255 + 0 + 1 + 1 + 0 + 60005 + 257 2 0 - -1 1 - 16 - 201 - 100 0 0 - 137 + 100000 + 60031 + 259 0 - 204 - 100 - 137 0 - 76 + 1 0 - 209 - 100 - 213 + 40 + 261 0 - 6 + 1 + 1 + 0 + 60031 + 263 0 - 211 - 100 - 219 0 - 6 1 - 212 - 100 - 225 + 0 + 40 + 265 + 0 1 - 6 - 2 - 214 - 100 - 231 - 3 - 6 1 - 216 - 100 - 237 + 0 + 60031 + 267 + 0 + 0 + 1 + 0 + 39011 + 269 + 70 + 0 + 1 + 0 + 1 + 8 + 10 4 - 6 0 - 218 - 100 - 243 + 0 + 5 + 0 + 11 + 4 + 5 + 0 + 2 + 0 + 12 4 7 0 - 220 - 100 - 250 + 5 + 0 + 13 4 - 6 + 12 0 - 222 - 100 - 256 + 2 + 0 + 14 4 - 6 + 14 + 0 + 2 + 0 + 15 + 4 + 16 + 0 + 2 + 0 + 20 + 4 + 18 + 0 1 - 224 - 100 - 262 - 5 - 6 0 - 226 - 100 - 268 - 5 - 6 + 21 + 1 + 19 0 - 228 - 100 - 274 - 5 - 6 1 - 230 - 100 - 280 - 6 - 6 0 - 232 - 100 - 286 + 4 + 1 + 1 + 1 + 1 + 1 + 20 + 4 + 2 + 2 + 2 + 257 + 1 6 - 2469 - 7 - 100 - 101 - 2755 - 13 - 180 + 1 + 1 + 1 + 2 0 - 39003 - 201 - 131 + 0 + 60035 + 271 + 3 + 0 + 1 + 0 + 1 + 0 + 20 + 39004 + 273 + 155 0 1 0 @@ -886,25 +1269,25 @@ 4 0 0 - 1 + 3 0 11 4 - 1 - 0 3 0 + 1 + 0 12 4 4 0 - 1 + 3 0 13 4 - 5 + 7 0 - 3 + 1 0 14 4 @@ -922,35 +1305,35 @@ 4 12 0 - 19 + 43 0 21 1 - 31 + 55 0 1 0 30 4 - 32 + 56 0 43 - 0 0 2 - 1396 - 6 + 20 + 1 0 2 6 - 6 + 2 + 0 1 1 1 1 - 18 + 42 1 - 2 + 4 10 4 0 @@ -963,10 +1346,34 @@ 0 2 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 1 - 1396 + 20 1 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 0 42 83 @@ -1011,10 +1418,65 @@ 79 98 106 - 39012 - 204 - 70 + 15005 + 274 + 43 + 2 + 1 + 0 + 0 + 257 + 2 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 32 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 77 + 101 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 40 + 275 + 0 + 1 + 1 0 + 15011 + 277 + 7257 + 21 1 0 1 @@ -1029,324 +1491,707 @@ 4 2 0 - 5 + 2 0 12 4 - 7 + 4 0 2 0 13 4 - 9 + 6 0 - 5 + 2 0 14 4 - 14 + 8 0 2 0 15 4 - 16 + 10 0 2 0 20 4 - 18 + 12 0 - 1 + 7194 0 21 1 - 19 + 7206 0 1 - 0 + 21 1 - 1396 - 4 1 1 1 - 173 1 - 6 - 4 - 2 - 2 - 2 257 1 + 257 1 1 - 2 - 0 - 0 - 60032 - 209 + 1 + 1 + 7193 + 1 + 7 + 10 + 4 0 0 1 0 - 40 - 211 - 0 + 11 + 4 1 + 0 1 0 12 - 212 - 0 + 4 2 - 1 - 0 - 60020 - 214 0 1 - 1 - 0 - 60014 - 216 0 + 13 + 4 + 3 0 1 0 - 60043 - 218 - 1 + 21 + 4 + 4 0 - 1 + 27 0 - -3 - 60032 - 220 + 22 + 4 + 31 + 0 + 4 0 + 900 + 10 + 35 0 - 1 + 7114 + 21 0 - 60020 - 222 0 - 1 - 1 0 - 60014 - 224 0 + 26 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 84 + 104 + 114 + 101 + 97 + 100 + 49 + 3 + 2 0 + -1 1 + 16 + 201 + 100 0 - 60022 - 226 0 + 137 0 - 1 + 204 + 100 + 137 0 - 60020 - 228 + 76 0 - 1 - 1 + 209 + 100 + 213 0 - 60034 - 230 + 6 0 + 211 + 100 + 219 0 + 6 1 - 0 - 15013 - 232 - 2463 - 7 + 212 + 100 + 225 1 - 0 + 6 + 2 + 214 + 100 + 231 + 3 + 6 1 - 8 - 10 + 216 + 100 + 237 4 - 0 - 0 6 0 - 11 + 218 + 100 + 243 4 - 6 + 7 0 - 2 + 220 + 100 + 250 + 4 + 6 0 - 12 + 222 + 100 + 256 4 - 8 + 6 + 1 + 224 + 100 + 262 + 5 + 6 + 0 + 226 + 100 + 268 + 5 + 6 + 0 + 228 + 100 + 274 + 5 + 6 + 1 + 230 + 100 + 280 + 6 + 6 0 + 232 + 100 + 286 6 + 6550 + 15 + 100 + 101 + 6836 + 21 + 180 + 0 + 39003 + 201 + 131 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 3 + 0 + 12 + 4 + 4 + 0 + 1 0 13 4 - 14 + 5 0 - 2 + 3 0 14 4 - 16 + 8 0 2 0 15 4 - 18 + 10 0 2 0 20 4 - 20 + 12 0 - 2392 + 19 0 21 1 - 2412 + 31 0 1 - 7 - 5 - 1 + 0 + 30 + 4 + 32 + 0 + 43 + 0 + 0 + 2 + 1396 + 6 + 0 + 2 + 6 + 6 1 1 - 173 1 1 + 18 1 - 5 2 + 10 + 4 + 0 + 0 2 + 0 + 11 + 4 2 - 257 + 0 2 + 0 1 - 257 - 1 - 1 - 1 + 1396 1 - 2391 + 6 + 0 + 42 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 39012 + 204 + 70 + 0 1 - 9 + 0 1 + 8 + 10 4 0 0 - 3 + 2 0 - 10 + 11 4 - 3 + 2 0 5 0 - 11 + 12 4 - 8 + 7 0 2 0 - 12 + 13 4 - 10 + 9 0 5 0 - 13 + 14 4 - 15 + 14 0 2 0 - 21 + 15 4 - 17 + 16 0 - 20 + 2 0 - 900 - 10 - 37 + 20 + 4 + 18 0 - 401 1 - 901 - 10 - 438 + 0 + 21 1 - 1752 - 5 - 902 - 10 - 2190 - 6 - 145 + 19 + 0 1 - 2 - 3 + 0 1 + 1396 4 1 1 1 173 1 - 1 + 6 4 2 2 2 257 1 - 257 - 19 - 83 - 119 - 105 - 116 - 99 - 104 - 83 - 101 - 108 - 101 - 99 - 116 - 80 - 97 - 80 - 105 - 67 - 109 - 100 1 - 10 - 205 - 100 + 1 + 2 0 0 - 6 + 60032 + 209 0 - 207 - 100 - 6 0 - 6 + 1 0 - 209 - 100 - 12 + 40 + 211 + 0 + 1 + 1 + 0 + 12 + 212 + 0 + 2 + 1 + 0 + 60020 + 214 + 0 + 1 + 1 + 0 + 60014 + 216 + 0 + 0 + 1 + 0 + 60043 + 218 + 1 + 0 + 1 + 0 + -3 + 60032 + 220 + 0 + 0 + 1 + 0 + 60020 + 222 + 0 + 1 + 1 + 0 + 60014 + 224 + 0 + 0 + 1 + 0 + 60022 + 226 + 0 + 0 + 1 + 0 + 60020 + 228 + 0 + 1 + 1 + 0 + 60034 + 230 + 0 + 0 + 1 + 0 + 15013 + 232 + 6544 + 15 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 6 + 0 + 11 + 4 + 6 + 0 + 2 + 0 + 12 + 4 + 8 + 0 + 6 + 0 + 13 + 4 + 14 + 0 + 2 + 0 + 14 + 4 + 16 + 0 + 2 + 0 + 15 + 4 + 18 + 0 + 2 + 0 + 20 + 4 + 20 + 0 + 6473 + 0 + 21 + 1 + 6493 + 0 + 1 + 15 + 5 + 1 + 1 + 1 + 173 + 1 + 1 + 1 + 5 + 2 + 2 + 2 + 257 + 2 + 1 + 257 + 1 + 1 + 1 + 1 + 6472 + 1 + 9 + 1 + 4 + 0 + 0 + 3 + 0 + 10 + 4 + 3 + 0 + 5 + 0 + 11 + 4 + 8 + 0 + 2 + 0 + 12 + 4 + 10 + 0 + 5 + 0 + 13 + 4 + 15 + 0 + 2 + 0 + 21 + 4 + 17 + 0 + 20 + 0 + 900 + 10 + 37 + 0 + 401 + 1 + 901 + 10 + 438 + 1 + 5833 + 13 + 902 + 10 + 6271 + 14 + 145 + 1 + 2 + 3 + 1 + 4 + 1 + 1 + 1 + 173 + 1 + 1 + 4 + 2 + 2 + 2 + 257 + 1 + 257 + 19 + 83 + 119 + 105 + 116 + 99 + 104 + 83 + 101 + 108 + 101 + 99 + 116 + 80 + 97 + 80 + 105 + 67 + 109 + 100 + 1 + 10 + 205 + 100 + 0 + 0 + 6 + 0 + 207 + 100 + 6 + 0 + 6 + 0 + 209 + 100 + 12 0 6 0 @@ -1732,7 +2577,7 @@ 0 0 1 - 18 + 66 205 100 0 @@ -1815,34 +2660,322 @@ 100 111 5 - 1151 + 269 0 232 100 - 1262 + 380 5 78 0 234 100 - 1340 + 458 5 9 0 236 100 - 1349 + 467 5 161 0 + 237 100 - 101 - 1510 + 628 5 - 132 - 0 - 60032 - 205 + 6 + 1 + 239 + 100 + 634 + 6 + 6 + 0 + 241 + 100 + 640 + 6 + 269 + 0 + 243 + 100 + 909 + 6 + 78 + 0 + 245 + 100 + 987 + 6 + 9 + 0 + 247 + 100 + 996 + 6 + 161 + 0 + 248 + 100 + 1157 + 6 + 6 + 1 + 250 + 100 + 1163 + 7 + 6 + 0 + 252 + 100 + 1169 + 7 + 269 + 0 + 254 + 100 + 1438 + 7 + 78 + 0 + 256 + 100 + 1516 + 7 + 9 + 0 + 258 + 100 + 1525 + 7 + 161 + 0 + 259 + 100 + 1686 + 7 + 6 + 1 + 261 + 100 + 1692 + 8 + 6 + 0 + 263 + 100 + 1698 + 8 + 269 + 0 + 265 + 100 + 1967 + 8 + 78 + 0 + 267 + 100 + 2045 + 8 + 9 + 0 + 269 + 100 + 2054 + 8 + 161 + 0 + 270 + 100 + 2215 + 8 + 6 + 1 + 272 + 100 + 2221 + 9 + 6 + 0 + 274 + 100 + 2227 + 9 + 269 + 0 + 276 + 100 + 2496 + 9 + 78 + 0 + 278 + 100 + 2574 + 9 + 9 + 0 + 280 + 100 + 2583 + 9 + 161 + 0 + 281 + 100 + 2744 + 9 + 6 + 1 + 283 + 100 + 2750 + 10 + 6 + 0 + 285 + 100 + 2756 + 10 + 269 + 0 + 287 + 100 + 3025 + 10 + 78 + 0 + 289 + 100 + 3103 + 10 + 9 + 0 + 291 + 100 + 3112 + 10 + 161 + 0 + 292 + 100 + 3273 + 10 + 6 + 1 + 294 + 100 + 3279 + 11 + 6 + 0 + 296 + 100 + 3285 + 11 + 269 + 0 + 298 + 100 + 3554 + 11 + 78 + 0 + 300 + 100 + 3632 + 11 + 9 + 0 + 302 + 100 + 3641 + 11 + 161 + 0 + 303 + 100 + 3802 + 11 + 6 + 1 + 305 + 100 + 3808 + 12 + 6 + 0 + 307 + 100 + 3814 + 12 + 269 + 0 + 309 + 100 + 4083 + 12 + 78 + 0 + 311 + 100 + 4161 + 12 + 9 + 0 + 313 + 100 + 4170 + 12 + 161 + 0 + 314 + 100 + 4331 + 12 + 6 + 1 + 316 + 100 + 4337 + 13 + 6 + 0 + 318 + 100 + 4343 + 13 + 200 + 0 + 320 + 100 + 4543 + 13 + 78 + 0 + 322 + 100 + 4621 + 13 + 9 + 0 + 324 + 100 + 4630 + 13 + 161 + 0 + 100 + 101 + 4791 + 13 + 644 + 0 + 60032 + 205 0 0 1 @@ -1954,7 +3087,7 @@ 0 60305 230 - 1145 + 263 0 1 0 @@ -2000,17 +3133,17 @@ 4 10 0 - 1084 + 202 0 21 1 - 1094 + 212 0 1 0 0 1 - 1066 + 184 0 1 6 @@ -2018,7 +3151,7 @@ 1 1 2 - 1083 + 201 1 2 10 @@ -2031,11 +3164,11 @@ 4 2 0 - 1067 + 185 0 1 - 1066 - 1066 + 184 + 184 32 123 34 @@ -2057,36 +3190,6 @@ 58 32 123 - 10 - 125 - 32 - 44 - 32 - 10 - 32 - 34 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 115 - 67 - 111 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 10 32 34 48 @@ -2097,15 +3200,12 @@ 123 32 34 - 80 - 97 + 83 + 111 + 117 114 - 97 - 109 - 101 - 116 + 99 101 - 114 78 97 109 @@ -2115,10 +3215,7 @@ 58 32 34 - 79 - 115 - 99 - 105 + 88 34 32 44 @@ -2131,6 +3228,11 @@ 117 101 115 + 95 + 115 + 101 + 110 + 100 34 32 58 @@ -2163,7 +3265,6 @@ 125 32 44 - 10 32 34 49 @@ -2174,15 +3275,12 @@ 123 32 34 - 80 - 97 + 83 + 111 + 117 114 - 97 - 109 - 101 - 116 + 99 101 - 114 78 97 109 @@ -2192,23 +3290,7 @@ 58 32 34 - 83 - 108 - 105 - 100 - 101 - 114 - 76 - 67 - 68 - 80 - 114 - 111 - 103 - 114 - 101 - 115 - 115 + 86 34 32 44 @@ -2221,6 +3303,11 @@ 117 101 115 + 95 + 115 + 101 + 110 + 100 34 32 58 @@ -2252,278 +3339,3548 @@ 32 125 32 - 10 - 125 - 44 - 32 - 10 - 34 - 80 - 97 - 80 - 73 - 67 - 111 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 80 - 108 - 117 - 103 - 105 - 110 - 49 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 105 - 100 - 101 - 110 - 116 - 105 - 102 - 105 - 101 - 114 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 66 - 117 - 116 - 116 - 111 - 110 - 34 - 32 125 - 32 32 44 32 - 34 - 99 - 111 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 110 - 97 - 109 - 101 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 32 34 - 79 - 115 - 99 - 105 - 108 - 108 + 80 97 - 116 - 111 114 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 115 - 105 - 122 - 101 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 97 - 108 - 117 + 109 101 - 34 - 32 - 58 - 32 - 34 - 40 - 50 - 53 - 48 - 44 - 49 - 48 - 48 - 41 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 112 - 111 - 115 - 105 116 - 105 - 111 - 110 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 101 - 34 - 32 - 58 - 32 - 34 - 40 - 50 - 53 - 48 - 44 - 49 - 48 - 48 - 41 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 116 - 97 - 98 - 34 - 32 - 58 - 32 - 123 - 32 + 0 + 39011 + 232 + 72 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 6 + 0 + 11 + 4 + 6 + 0 + 2 + 0 + 12 + 4 + 8 + 0 + 6 + 0 + 13 + 4 + 14 + 0 + 2 + 0 + 14 + 4 + 16 + 0 + 2 + 0 + 15 + 4 + 18 + 0 + 2 + 0 + 20 + 4 + 20 + 0 + 1 + 0 + 21 + 1 + 21 + 0 + 1 + 0 + 5 + 1 + 1 + 1 + 1 + 184 + 1 + 200 + 5 + 2 + 2 + 2 + 2 + 6 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 234 + 3 + 0 + 1 + 0 + 1 + 0 + 200 + 39004 + 236 + 155 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 43 + 0 + 2 + 200 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 200 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 237 + 0 + 1 + 1 + 0 + 60031 + 239 + 0 + 0 + 1 + 0 + 60305 + 241 + 263 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 14 + 4 + 6 + 0 + 2 + 0 + 15 + 4 + 8 + 0 + 2 + 0 + 20 + 4 + 10 + 0 + 202 + 0 + 21 + 1 + 212 + 0 + 1 + 0 + 0 + 1 + 184 + 0 + 1 + 6 + 1 + 1 + 1 + 2 + 201 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 185 + 0 + 1 + 184 + 184 + 114 + 115 + 67 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 48 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 34 + 79 + 115 + 99 + 105 + 108 + 108 + 97 + 116 + 111 + 114 + 32 + 105 + 110 + 112 + 117 + 116 + 34 + 32 + 44 + 32 + 34 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 34 + 32 + 58 + 32 + 34 + 49 + 34 + 44 + 32 + 34 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 34 + 32 + 58 + 32 + 34 + 50 + 53 + 55 + 34 + 32 + 32 + 125 + 32 + 44 + 32 + 34 + 49 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 80 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 34 + 111 + 107 + 34 + 32 + 44 + 32 + 34 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 34 + 32 + 58 + 32 + 34 + 49 + 34 + 44 + 32 + 34 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 34 + 32 + 58 + 32 + 34 + 50 + 53 + 55 + 34 + 32 + 32 + 125 + 32 + 125 + 44 + 32 + 34 + 80 + 97 + 80 + 73 + 67 + 111 + 0 + 39011 + 243 + 72 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 6 + 0 + 11 + 4 + 6 + 0 + 2 + 0 + 12 + 4 + 8 + 0 + 6 + 0 + 13 + 4 + 14 + 0 + 2 + 0 + 14 + 4 + 16 + 0 + 2 + 0 + 15 + 4 + 18 + 0 + 2 + 0 + 20 + 4 + 20 + 0 + 1 + 0 + 21 + 1 + 21 + 0 + 1 + 0 + 5 + 1 + 1 + 1 + 1 + 184 + 1 + 200 + 5 + 2 + 2 + 2 + 2 + 6 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 245 + 3 + 0 + 1 + 0 + 1 + 0 + 200 + 39004 + 247 + 155 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 43 + 0 + 2 + 200 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 200 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 248 + 0 + 1 + 1 + 0 + 60031 + 250 + 0 + 0 + 1 + 0 + 60305 + 252 + 263 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 14 + 4 + 6 + 0 + 2 + 0 + 15 + 4 + 8 + 0 + 2 + 0 + 20 + 4 + 10 + 0 + 202 + 0 + 21 + 1 + 212 + 0 + 1 + 0 + 0 + 1 + 184 + 0 + 1 + 6 + 1 + 1 + 1 + 2 + 201 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 185 + 0 + 1 + 184 + 184 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 80 + 108 + 117 + 103 + 105 + 110 + 49 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 105 + 100 + 101 + 110 + 116 + 105 + 102 + 105 + 101 + 114 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 66 + 117 + 116 + 116 + 111 + 110 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 99 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 110 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 68 + 105 + 115 + 116 + 117 + 114 + 98 + 97 + 110 + 99 + 101 + 103 + 114 + 103 + 114 + 103 + 114 + 114 + 103 + 114 + 103 + 114 + 103 + 114 + 103 + 114 + 103 + 103 + 101 + 103 + 101 + 114 + 103 + 103 + 101 + 114 + 101 + 103 + 101 + 103 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 105 + 122 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 0 + 39011 + 254 + 72 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 6 + 0 + 11 + 4 + 6 + 0 + 2 + 0 + 12 + 4 + 8 + 0 + 6 + 0 + 13 + 4 + 14 + 0 + 2 + 0 + 14 + 4 + 16 + 0 + 2 + 0 + 15 + 4 + 18 + 0 + 2 + 0 + 20 + 4 + 20 + 0 + 1 + 0 + 21 + 1 + 21 + 0 + 1 + 0 + 5 + 1 + 1 + 1 + 1 + 184 + 1 + 200 + 5 + 2 + 2 + 2 + 2 + 6 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 256 + 3 + 0 + 1 + 0 + 1 + 0 + 200 + 39004 + 258 + 155 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 43 + 0 + 2 + 200 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 200 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 259 + 0 + 1 + 1 + 0 + 60031 + 261 + 0 + 0 + 1 + 0 + 60305 + 263 + 263 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 14 + 4 + 6 + 0 + 2 + 0 + 15 + 4 + 8 + 0 + 2 + 0 + 20 + 4 + 10 + 0 + 202 + 0 + 21 + 1 + 212 + 0 + 1 + 0 + 0 + 1 + 184 + 0 + 1 + 6 + 1 + 1 + 1 + 2 + 201 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 185 + 0 + 1 + 184 + 184 + 40 + 49 + 53 + 48 + 44 + 53 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 54 + 48 + 48 + 44 + 49 + 48 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 116 + 97 + 98 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 68 + 105 + 115 + 116 + 117 + 114 + 98 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 68 + 105 + 115 + 116 + 117 + 114 + 98 + 105 + 110 + 103 + 34 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 125 + 0 + 39011 + 265 + 72 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 6 + 0 + 11 + 4 + 6 + 0 + 2 + 0 + 12 + 4 + 8 + 0 + 6 + 0 + 13 + 4 + 14 + 0 + 2 + 0 + 14 + 4 + 16 + 0 + 2 + 0 + 15 + 4 + 18 + 0 + 2 + 0 + 20 + 4 + 20 + 0 + 1 + 0 + 21 + 1 + 21 + 0 + 1 + 0 + 5 + 1 + 1 + 1 + 1 + 184 + 1 + 200 + 5 + 2 + 2 + 2 + 2 + 6 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 267 + 3 + 0 + 1 + 0 + 1 + 0 + 200 + 39004 + 269 + 155 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 43 + 0 + 2 + 200 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 200 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 270 + 0 + 1 + 1 + 0 + 60031 + 272 + 0 + 0 + 1 + 0 + 60305 + 274 + 263 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 14 + 4 + 6 + 0 + 2 + 0 + 15 + 4 + 8 + 0 + 2 + 0 + 20 + 4 + 10 + 0 + 202 + 0 + 21 + 1 + 212 + 0 + 1 + 0 + 0 + 1 + 184 + 0 + 1 + 6 + 1 + 1 + 1 + 2 + 201 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 185 + 0 + 1 + 184 + 184 + 32 + 32 + 44 + 32 + 34 + 80 + 108 + 117 + 103 + 105 + 110 + 50 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 105 + 100 + 101 + 110 + 116 + 105 + 102 + 105 + 101 + 114 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 66 + 117 + 116 + 116 + 111 + 110 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 99 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 110 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 76 + 101 + 97 + 118 + 101 + 101 + 114 + 103 + 101 + 114 + 103 + 101 + 114 + 103 + 101 + 114 + 103 + 101 + 114 + 103 + 103 + 101 + 103 + 114 + 103 + 103 + 114 + 101 + 103 + 101 + 103 + 114 + 103 + 101 + 101 + 103 + 103 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 105 + 122 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 49 + 53 + 48 + 44 + 53 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 + 0 + 39011 + 276 + 72 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 6 + 0 + 11 + 4 + 6 + 0 + 2 + 0 + 12 + 4 + 8 + 0 + 6 + 0 + 13 + 4 + 14 + 0 + 2 + 0 + 14 + 4 + 16 + 0 + 2 + 0 + 15 + 4 + 18 + 0 + 2 + 0 + 20 + 4 + 20 + 0 + 1 + 0 + 21 + 1 + 21 + 0 + 1 + 0 + 5 + 1 + 1 + 1 + 1 + 184 + 1 + 200 + 5 + 2 + 2 + 2 + 2 + 6 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 278 + 3 + 0 + 1 + 0 + 1 + 0 + 200 + 39004 + 280 + 155 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 43 + 0 + 2 + 200 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 200 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 281 + 0 + 1 + 1 + 0 + 60031 + 283 + 0 + 0 + 1 + 0 + 60305 + 285 + 263 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 14 + 4 + 6 + 0 + 2 + 0 + 15 + 4 + 8 + 0 + 2 + 0 + 20 + 4 + 10 + 0 + 202 + 0 + 21 + 1 + 212 + 0 + 1 + 0 + 0 + 1 + 184 + 0 + 1 + 6 + 1 + 1 + 1 + 2 + 201 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 185 + 0 + 1 + 184 + 184 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 54 + 48 + 48 + 44 + 51 + 50 + 53 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 116 + 97 + 98 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 79 + 107 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 116 + 97 + 116 + 101 + 50 + 95 + 116 + 101 + 120 + 116 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 76 + 101 + 97 + 118 + 105 + 110 + 103 + 34 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 80 + 108 + 117 + 103 + 105 + 110 + 51 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 105 + 100 + 101 + 110 + 116 + 105 + 102 + 105 + 101 + 114 + 34 + 32 + 58 + 0 + 39011 + 287 + 72 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 6 + 0 + 11 + 4 + 6 + 0 + 2 + 0 + 12 + 4 + 8 + 0 + 6 + 0 + 13 + 4 + 14 + 0 + 2 + 0 + 14 + 4 + 16 + 0 + 2 + 0 + 15 + 4 + 18 + 0 + 2 + 0 + 20 + 4 + 20 + 0 + 1 + 0 + 21 + 1 + 21 + 0 + 1 + 0 + 5 + 1 + 1 + 1 + 1 + 184 + 1 + 200 + 5 + 2 + 2 + 2 + 2 + 6 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 289 + 3 + 0 + 1 + 0 + 1 + 0 + 200 + 39004 + 291 + 155 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 43 + 0 + 2 + 200 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 200 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 292 + 0 + 1 + 1 + 0 + 60031 + 294 + 0 + 0 + 1 + 0 + 60305 + 296 + 263 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 14 + 4 + 6 + 0 + 2 + 0 + 15 + 4 + 8 + 0 + 2 + 0 + 20 + 4 + 10 + 0 + 202 + 0 + 21 + 1 + 212 + 0 + 1 + 0 + 0 + 1 + 184 + 0 + 1 + 6 + 1 + 1 + 1 + 2 + 201 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 185 + 0 + 1 + 184 + 184 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 80 + 108 + 111 + 116 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 99 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 110 + 97 + 109 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 80 + 108 + 111 + 116 + 32 + 88 + 86 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 115 + 105 + 122 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 53 + 48 + 48 + 44 + 53 + 48 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 48 + 44 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 116 + 97 + 98 + 34 + 32 + 58 + 32 + 123 + 32 34 118 97 108 117 - 101 - 34 - 32 - 58 - 32 - 34 - 80 + 101 + 34 + 32 + 58 + 32 + 34 + 80 + 97 + 80 + 73 + 45 + 84 + 97 + 98 + 34 + 32 + 125 + 32 + 32 + 0 + 39011 + 298 + 72 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 6 + 0 + 11 + 4 + 6 + 0 + 2 + 0 + 12 + 4 + 8 + 0 + 6 + 0 + 13 + 4 + 14 + 0 + 2 + 0 + 14 + 4 + 16 + 0 + 2 + 0 + 15 + 4 + 18 + 0 + 2 + 0 + 20 + 4 + 20 + 0 + 1 + 0 + 21 + 1 + 21 + 0 + 1 + 0 + 5 + 1 + 1 + 1 + 1 + 184 + 1 + 200 + 5 + 2 + 2 + 2 + 2 + 6 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 300 + 3 + 0 + 1 + 0 + 1 + 0 + 200 + 39004 + 302 + 155 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 43 + 0 + 2 + 200 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 200 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 + 68 80 - 73 - 45 - 84 - 97 + 83 + 111 + 99 + 107 + 101 + 116 + 95 + 83 + 104 + 79 98 - 34 - 32 - 125 - 32 - 32 + 106 + 40 + 303 + 0 + 1 + 1 + 0 + 60031 + 305 + 0 + 0 + 1 + 0 + 60305 + 307 + 263 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 14 + 4 + 6 + 0 + 2 + 0 + 15 + 4 + 8 + 0 + 2 + 0 + 20 + 4 + 10 + 0 + 202 + 0 + 21 + 1 + 212 + 0 + 1 + 0 + 0 + 1 + 184 + 0 + 1 + 6 + 1 + 1 + 1 + 2 + 201 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 185 + 0 + 1 + 184 + 184 44 32 34 - 115 - 116 + 121 + 82 97 - 116 - 101 - 49 - 95 - 116 + 110 + 103 101 - 120 - 116 34 32 58 @@ -2541,59 +6898,23 @@ 58 32 34 - 71 - 111 + 91 + 45 + 49 + 48 + 46 + 48 32 - 116 - 111 + 49 + 48 + 46 + 48 + 93 34 32 125 32 32 - 44 - 32 - 34 - 115 - 116 - 97 - 116 - 101 - 50 - 95 - 116 - 101 - 120 - 116 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 76 - 101 - 97 - 118 - 105 - 110 - 103 - 32 - 116 - 111 - 34 - 32 125 32 32 @@ -2606,30 +6927,15 @@ 44 32 34 - 80 - 108 - 117 - 103 - 105 - 110 - 50 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 105 - 100 - 101 + 84 + 111 + 67 + 111 110 116 - 105 - 102 - 105 - 101 114 + 111 + 108 34 32 58 @@ -2637,47 +6943,13 @@ 123 32 34 - 118 - 97 + 80 108 117 - 101 - 34 - 32 - 58 - 32 - 34 - 66 - 117 - 116 - 116 - 111 - 110 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 99 - 111 - 110 - 102 - 105 103 - 34 - 32 - 58 - 32 - 123 - 32 - 34 + 105 110 - 97 - 109 - 101 + 49 34 32 58 @@ -2685,118 +6957,62 @@ 123 32 34 - 118 - 97 + 98 108 - 117 - 101 + 111 + 99 + 107 34 32 58 32 34 - 83 + 67 108 105 - 100 - 101 - 114 - 32 - 76 - 67 - 68 - 32 - 80 - 114 - 111 - 103 - 114 + 99 + 107 + 95 + 69 + 118 101 - 115 - 115 - 66 - 97 - 114 + 110 + 116 34 32 - 125 - 32 - 32 44 32 34 - 115 - 105 - 122 - 101 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 + 112 97 - 108 - 117 + 114 + 97 + 109 + 101 + 116 101 + 114 34 32 58 32 34 - 40 - 50 - 53 - 48 - 44 - 49 - 48 - 48 - 41 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 112 - 111 + 79 115 + 99 105 + 108 + 108 + 97 116 - 105 111 - 110 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 40 - 50 - 53 - 48 - 44 - 50 - 53 - 48 - 41 + 114 + 32 + 105 + 110 + 112 + 117 + 116 34 32 125 @@ -2805,9 +7021,13 @@ 44 32 34 - 116 - 97 - 98 + 80 + 108 + 117 + 103 + 105 + 110 + 50 34 32 58 @@ -2815,122 +7035,398 @@ 123 32 34 - 118 - 97 + 98 108 - 117 - 101 + 111 + 99 + 107 34 32 58 32 34 - 80 - 97 - 80 - 73 - 45 - 84 - 97 - 98 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 115 + 67 + 108 + 105 + 99 + 107 + 95 + 69 + 118 + 101 + 110 + 116 + 0 + 39011 + 309 + 72 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 6 + 0 + 11 + 4 + 6 + 0 + 2 + 0 + 12 + 4 + 8 + 0 + 6 + 0 + 13 + 4 + 14 + 0 + 2 + 0 + 14 + 4 + 16 + 0 + 2 + 0 + 15 + 4 + 18 + 0 + 2 + 0 + 20 + 4 + 20 + 0 + 1 + 0 + 21 + 1 + 21 + 0 + 1 + 0 + 5 + 1 + 1 + 1 + 1 + 184 + 1 + 200 + 5 + 2 + 2 + 2 + 2 + 6 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 311 + 3 + 0 + 1 + 0 + 1 + 0 + 200 + 39004 + 313 + 155 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 43 + 0 + 2 + 200 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 200 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 + 83 + 119 + 105 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 116 + 111 + 67 + 111 + 110 + 102 + 105 + 103 97 - 116 + 83 + 111 + 99 + 107 101 - 49 - 95 116 + 46 + 85 + 68 + 80 + 83 + 111 + 99 + 107 101 - 120 116 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 71 - 111 - 32 + 95 + 83 + 104 + 79 + 98 + 106 + 40 + 314 + 0 + 1 + 1 + 0 + 60031 + 316 + 0 + 0 + 1 + 0 + 60305 + 318 + 194 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 1 + 0 + 11 + 4 + 1 + 0 + 2 + 0 + 12 + 4 + 3 + 0 + 1 + 0 + 13 + 4 + 4 + 0 + 2 + 0 + 14 + 4 + 6 + 0 + 2 + 0 + 15 + 4 + 8 + 0 + 2 + 0 + 20 + 4 + 10 + 0 + 133 + 0 + 21 + 1 + 143 + 0 + 1 + 0 + 0 + 1 + 115 + 0 + 1 + 6 + 1 + 1 + 1 + 2 + 132 + 1 + 2 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 116 - 111 + 0 + 1 + 115 + 115 34 32 - 125 - 32 - 32 44 32 34 - 115 - 116 + 112 97 - 116 - 101 - 50 - 95 - 116 + 114 + 97 + 109 101 - 120 116 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 101 + 114 34 32 58 32 34 - 76 - 101 - 97 - 118 - 105 - 110 - 103 - 32 - 116 111 + 107 34 32 125 32 32 - 125 - 32 - 32 - 125 - 32 - 32 125 32 32 @@ -2939,13 +7435,9 @@ 34 84 111 - 67 - 111 - 110 - 116 - 114 - 111 - 108 + 83 + 117 + 98 34 32 58 @@ -2959,7 +7451,7 @@ 103 105 110 - 49 + 51 34 32 58 @@ -2967,70 +7459,28 @@ 123 32 34 - 98 - 108 - 111 - 99 - 107 - 34 - 32 - 58 - 32 - 34 - 67 - 108 - 105 - 99 - 107 - 95 - 69 - 118 - 101 - 110 - 116 - 34 - 32 - 44 - 32 - 34 - 112 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 34 - 32 - 58 - 32 - 34 - 79 115 - 99 - 105 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 80 - 108 - 117 - 103 105 + 103 110 - 50 + 97 + 108 + 115 34 32 58 32 - 123 + 91 + 34 + 88 + 34 + 44 + 34 + 86 + 34 + 93 + 32 + 44 32 34 98 @@ -3043,437 +7493,913 @@ 58 32 34 - 67 - 108 - 105 + 83 + 111 + 117 + 114 99 - 107 - 95 - 69 - 118 101 - 110 - 116 + 71 + 114 + 111 + 117 + 112 + 48 34 32 - 44 + 125 32 - 34 - 112 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 34 32 - 58 + 125 32 - 34 + 32 + 125 + 32 + 125 + 0 + 39011 + 320 + 72 + 0 + 1 + 0 + 1 + 8 + 10 + 4 + 0 + 0 + 6 + 0 + 11 + 4 + 6 + 0 + 2 + 0 + 12 + 4 + 8 + 0 + 6 + 0 + 13 + 4 + 14 + 0 + 2 + 0 + 14 + 4 + 16 + 0 + 2 + 0 + 15 + 4 + 18 + 0 + 2 + 0 + 20 + 4 + 20 + 0 + 1 + 0 + 21 + 1 + 21 + 0 + 1 + 0 + 5 + 1 + 1 + 1 + 1 + 115 + 1 + 131 + 5 + 2 + 2 + 2 + 2 + 6 + 1 + 6 + 1 + 1 + 1 + 2 + 0 + 0 + 60035 + 322 + 3 + 0 + 1 + 0 + 1 + 0 + 131 + 39004 + 324 + 155 + 0 + 1 + 0 + 1 + 9 + 10 + 4 + 0 + 0 + 3 + 0 + 11 + 4 + 3 + 0 + 1 + 0 + 12 + 4 + 4 + 0 + 3 + 0 + 13 + 4 + 7 + 0 + 1 + 0 + 14 + 4 + 8 + 0 + 2 + 0 + 15 + 4 + 10 + 0 + 2 + 0 + 20 + 4 + 12 + 0 + 43 + 0 + 21 + 1 + 55 + 0 + 1 + 0 + 30 + 4 + 56 + 0 + 43 + 0 + 2 + 131 + 1 + 0 + 2 + 6 + 2 + 0 + 1 + 1 + 1 + 1 + 42 + 1 + 4 + 10 + 4 + 0 + 0 + 2 + 0 + 11 + 4 + 2 + 0 + 2 + 0 + 12 + 4 + 4 + 0 + 2 + 0 + 13 + 4 + 6 + 0 + 10 + 0 + 1 + 131 + 1 + 6 + 1 + 20000 + 9 + 49 + 50 + 55 + 46 + 48 + 46 + 48 + 46 + 49 + 0 + 42 83 - 108 + 119 105 - 100 - 101 - 114 - 76 + 116 + 99 + 104 + 105 + 110 + 103 + 65 + 117 + 116 + 111 67 + 111 + 110 + 102 + 105 + 103 + 97 + 83 + 111 + 99 + 107 + 101 + 116 + 46 + 85 68 80 - 114 + 83 111 - 103 - 114 + 99 + 107 101 - 115 - 115 - 34 - 32 - 125 - 32 - 32 - 125 - 32 - 32 - 125 - 32 - 10 - 125 + 116 + 95 + 83 + 104 + 79 + 98 + 106 + 0 + 80 + 8 + 4 + 1 + 0 + 0 + 0 + 0 + 205 + 205 + 0 + 1 + 0 + 0 + 1 + 0 + 207 + 207 + 0 + 1 + 0 + 0 + 2 + 0 + 209 + 209 + 0 + 0 + 209 + 209 + 0 + 0 + 213 + 213 + 0 + 0 + 214 + 214 + 0 + 0 + 216 + 216 + 0 + 0 + 218 + 218 + 0 + 0 + 220 + 220 + 0 + 0 + 222 + 222 + 0 + 0 + 224 + 224 + 0 + 0 + 226 + 226 + 0 + 0 + 228 + 228 + 0 + 0 + 224 + 224 + 0 + 0 + 232 + 232 + 0 + 0 + 228 + 228 + 0 + 0 + 232 + 232 + 1 + 0 + 216 + 216 + 0 + 0 + 232 + 232 + 2 + 0 + 220 + 220 + 0 + 0 + 232 + 232 + 3 + 0 + 230 + 230 + 0 + 0 + 232 + 232 + 4 + 0 + 232 + 232 + 0 + 0 + 236 + 236 + 0 + 0 + 234 + 234 + 0 + 0 + 236 + 236 + 1 + 0 + 237 + 237 + 0 + 0 + 239 + 239 + 0 + 0 + 224 + 224 + 0 + 0 + 243 + 243 + 0 + 0 + 239 + 239 + 0 + 0 + 243 + 243 + 1 + 0 + 216 + 216 + 0 + 0 + 243 + 243 + 2 + 0 + 220 + 220 + 0 + 0 + 243 + 243 + 3 + 0 + 241 + 241 + 0 + 0 + 243 + 243 + 4 + 0 + 243 + 243 + 0 + 0 + 247 + 247 + 0 + 0 + 245 + 245 + 0 + 0 + 247 + 247 + 1 + 0 + 248 + 248 + 0 + 0 + 250 + 250 + 0 + 0 + 224 + 224 + 0 + 0 + 254 + 254 0 - 39011 - 232 - 72 0 - 1 + 250 + 250 0 - 1 - 8 - 10 - 4 0 + 254 + 254 + 1 0 - 6 + 216 + 216 0 - 11 - 4 - 6 0 + 254 + 254 2 0 - 12 - 4 - 8 + 220 + 220 0 - 6 0 - 13 - 4 - 14 + 254 + 254 + 3 0 - 2 + 252 + 252 0 - 14 + 0 + 254 + 254 4 - 16 0 - 2 + 254 + 254 0 - 15 - 4 - 18 0 - 2 + 258 + 258 0 - 20 - 4 - 20 0 - 1 + 256 + 256 0 - 21 - 1 - 21 0 + 258 + 258 1 0 - 5 - 1 - 1 - 1 - 1 - 1066 - 1 - 1082 - 5 - 2 - 2 - 2 - 2 - 6 - 1 - 6 - 1 - 1 - 1 - 2 + 259 + 259 0 0 - 60035 - 234 - 3 + 261 + 261 0 - 1 0 - 1 + 224 + 224 0 - 1082 - 39004 - 236 - 155 0 - 1 + 265 + 265 0 + 0 + 261 + 261 + 0 + 0 + 265 + 265 1 - 9 - 10 - 4 0 + 216 + 216 0 - 3 0 - 11 - 4 - 3 + 265 + 265 + 2 0 - 1 + 220 + 220 0 - 12 - 4 - 4 0 + 265 + 265 3 0 - 13 - 4 - 7 + 263 + 263 0 - 1 0 - 14 + 265 + 265 4 - 8 0 - 2 + 265 + 265 0 - 15 - 4 - 10 0 - 2 + 269 + 269 0 - 20 - 4 - 12 0 - 43 + 267 + 267 0 - 21 - 1 - 55 0 + 269 + 269 1 0 - 30 - 4 - 56 + 270 + 270 0 - 43 0 - 2 - 1082 - 1 + 272 + 272 0 - 2 - 6 - 2 0 + 224 + 224 + 0 + 0 + 276 + 276 + 0 + 0 + 272 + 272 + 0 + 0 + 276 + 276 1 - 1 - 1 - 1 - 42 - 1 - 4 - 10 - 4 0 + 216 + 216 0 - 2 0 - 11 - 4 + 276 + 276 2 0 - 2 + 220 + 220 0 - 12 - 4 - 4 0 - 2 + 276 + 276 + 3 0 - 13 + 274 + 274 + 0 + 0 + 276 + 276 4 - 6 0 - 10 + 276 + 276 0 + 0 + 280 + 280 + 0 + 0 + 278 + 278 + 0 + 0 + 280 + 280 1 - 1082 - 1 - 6 + 0 + 281 + 281 + 0 + 0 + 283 + 283 + 0 + 0 + 224 + 224 + 0 + 0 + 287 + 287 + 0 + 0 + 283 + 283 + 0 + 0 + 287 + 287 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 0 - 42 - 83 - 119 - 105 - 116 - 99 - 104 - 105 - 110 - 103 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 97 - 83 - 111 - 99 - 107 - 101 - 116 - 46 - 85 - 68 - 80 - 83 - 111 - 99 - 107 - 101 - 116 - 95 - 83 - 104 - 79 - 98 - 106 + 216 + 216 + 0 + 0 + 287 + 287 + 2 + 0 + 220 + 220 0 - 16 - 8 + 0 + 287 + 287 + 3 + 0 + 285 + 285 + 0 + 0 + 287 + 287 4 - 1 0 + 287 + 287 0 0 + 291 + 291 0 - 205 - 205 0 - 1 + 289 + 289 0 0 + 291 + 291 1 0 - 207 - 207 + 292 + 292 + 0 + 0 + 294 + 294 + 0 0 + 224 + 224 + 0 + 0 + 298 + 298 + 0 + 0 + 294 + 294 + 0 + 0 + 298 + 298 1 0 + 216 + 216 0 + 0 + 298 + 298 2 0 - 209 - 209 + 220 + 220 0 0 - 209 - 209 + 298 + 298 + 3 0 + 296 + 296 0 - 213 - 213 0 + 298 + 298 + 4 + 0 + 298 + 298 + 0 + 0 + 302 + 302 + 0 + 0 + 300 + 300 + 0 + 0 + 302 + 302 + 1 + 0 + 303 + 303 0 - 214 - 214 0 + 305 + 305 + 0 + 0 + 224 + 224 + 0 + 0 + 309 + 309 + 0 + 0 + 305 + 305 + 0 + 0 + 309 + 309 + 1 0 216 216 0 0 - 218 - 218 - 0 + 309 + 309 + 2 0 220 220 0 0 - 222 - 222 + 309 + 309 + 3 0 + 307 + 307 0 - 224 - 224 0 + 309 + 309 + 4 0 - 226 - 226 + 309 + 309 0 0 - 228 - 228 + 313 + 313 + 0 + 0 + 311 + 311 + 0 + 0 + 313 + 313 + 1 + 0 + 314 + 314 + 0 + 0 + 316 + 316 0 0 224 224 0 0 - 232 - 232 + 320 + 320 0 0 - 228 - 228 + 316 + 316 0 0 - 232 - 232 + 320 + 320 1 0 216 216 0 0 - 232 - 232 + 320 + 320 2 0 220 220 0 0 - 232 - 232 + 320 + 320 3 0 - 230 - 230 + 318 + 318 0 0 - 232 - 232 + 320 + 320 4 0 - 232 - 232 + 320 + 320 0 0 - 236 - 236 + 324 + 324 0 0 - 234 - 234 + 322 + 322 0 0 - 236 - 236 + 324 + 324 1 0 211 @@ -3628,7 +8554,7 @@ 0 0 0 - 7 + 15 0 22 8 @@ -3809,15 +8735,15 @@ 232 232 4 - 13 + 21 40 - 222 + 279 0 1 1 0 60005 - 224 + 281 2 0 1 @@ -3825,49 +8751,49 @@ 0 100000 60031 - 226 + 283 0 0 1 0 40 - 228 + 285 0 1 1 0 60031 - 230 + 287 0 0 1 0 40 - 232 + 289 0 1 1 0 60031 - 234 + 291 0 0 1 0 40 - 236 + 293 0 1 1 0 60031 - 238 + 295 0 0 1 0 39011 - 240 + 297 70 0 1 @@ -3943,7 +8869,7 @@ 0 0 60035 - 242 + 299 3 0 1 @@ -3952,7 +8878,7 @@ 0 16 39004 - 244 + 301 155 0 1 @@ -4113,31 +9039,31 @@ 98 106 60023 - 245 + 302 0 0 1 0 40 - 247 + 304 0 1 1 0 12 - 248 + 305 0 2 1 0 60034 - 250 + 307 0 0 1 0 15013 - 252 + 309 682 6 1 @@ -4824,135 +9750,8 @@ 0 0 6 - 12 - 254 - 0 - 2 - 1 - 0 - 15006 - 256 - 31 - 0 - 1 - 0 - 0 - 257 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 0 - 20 - 78 - 101 - 120 - 116 - 83 - 116 - 97 - 116 - 101 - 68 - 97 - 116 - 97 - 46 - 109 - 101 - 109 - 111 - 114 - 121 - 60022 - 257 - 0 - 0 - 1 - 0 - 170 - 259 - 72 - 0 - 1 - 0 - 1 - 70 - 67 - 97 - 115 - 101 - 32 - 99 - 104 - 111 - 111 - 115 - 101 - 32 - 101 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 97 - 99 - 116 - 105 - 118 - 101 - 46 - 32 - 78 - 101 - 120 - 116 - 32 - 69 - 120 - 112 - 101 - 114 - 105 - 109 - 101 - 110 - 116 - 32 - 105 - 115 - 32 - 40 - 48 - 32 - 109 - 101 - 97 - 110 - 115 - 32 - 110 - 111 - 32 - 99 - 104 - 97 - 110 - 103 - 101 - 41 - 58 0 - 29 + 54 8 4 0 @@ -4962,42 +9761,58 @@ 2 204 204 + 0 + 1 + 0 + 0 0 0 209 209 0 0 - 211 - 211 + 210 + 210 0 0 - 213 - 213 - 0 + 212 + 212 0 - 215 - 215 0 + 214 + 214 0 0 + 216 + 216 0 0 - 2 - 217 - 217 + 212 + 212 0 0 218 218 0 0 + 212 + 212 + 0 + 0 220 220 0 0 - 222 - 222 + 219 + 219 + 0 + 0 + 220 + 220 + 1 + 0 + 220 + 220 0 0 224 @@ -5012,6 +9827,14 @@ 226 0 0 + 226 + 226 + 0 + 0 + 228 + 228 + 0 + 0 228 228 0 @@ -5020,6 +9843,22 @@ 230 0 0 + 230 + 230 + 0 + 0 + 226 + 226 + 1 + 0 + 228 + 228 + 0 + 0 + 232 + 232 + 0 + 0 232 232 0 @@ -5028,6 +9867,14 @@ 234 0 0 + 234 + 234 + 0 + 0 + 224 + 224 + 1 + 0 236 236 0 @@ -5044,144 +9891,296 @@ 240 0 0 - 226 - 226 + 242 + 242 + 0 + 0 + 244 + 244 + 0 + 0 + 246 + 246 + 0 + 0 + 248 + 248 + 0 + 0 + 248 + 248 + 0 + 0 + 250 + 250 0 0 240 240 + 0 + 0 + 250 + 250 1 0 - 230 - 230 + 244 + 244 0 0 - 240 - 240 + 250 + 250 2 0 - 234 - 234 + 232 + 232 0 0 - 240 - 240 + 250 + 250 3 0 - 240 - 240 + 250 + 250 0 0 - 244 - 244 + 254 + 254 0 0 - 242 - 242 + 252 + 252 0 0 - 244 - 244 + 254 + 254 1 0 - 245 - 245 + 255 + 255 0 0 - 248 - 248 + 257 + 257 0 0 - 247 - 247 + 257 + 257 0 0 - 248 - 248 + 259 + 259 + 0 + 0 + 261 + 261 + 0 + 0 + 263 + 263 + 0 + 0 + 265 + 265 + 0 + 0 + 267 + 267 + 0 + 0 + 267 + 267 + 0 + 0 + 269 + 269 + 0 + 0 + 259 + 259 + 0 + 0 + 269 + 269 1 0 - 248 - 248 + 263 + 263 0 0 - 250 - 250 + 269 + 269 + 2 0 + 228 + 228 0 - 250 - 250 0 + 269 + 269 + 3 0 - 252 - 252 + 269 + 269 0 0 - 211 - 211 + 273 + 273 0 0 - 254 - 254 + 271 + 271 0 0 - 215 - 215 + 273 + 273 + 1 0 0 - 254 - 254 + 0 + 0 + 2 + 274 + 274 + 0 + 0 + 275 + 275 + 0 + 0 + 277 + 277 + 0 + 0 + 279 + 279 + 0 + 0 + 281 + 281 + 0 + 0 + 281 + 281 + 0 + 0 + 283 + 283 + 0 + 0 + 285 + 285 + 0 + 0 + 287 + 287 + 0 + 0 + 289 + 289 + 0 + 0 + 291 + 291 + 0 + 0 + 293 + 293 + 0 + 0 + 295 + 295 + 0 + 0 + 295 + 295 + 0 + 0 + 297 + 297 + 0 + 0 + 283 + 283 + 0 + 0 + 297 + 297 1 0 - 254 - 254 + 287 + 287 0 0 - 256 - 256 + 297 + 297 + 2 0 + 291 + 291 0 - 207 - 207 0 + 297 + 297 + 3 + 0 + 297 + 297 0 - 256 - 256 + 0 + 301 + 301 + 0 + 0 + 299 + 299 + 0 + 0 + 301 + 301 1 0 - 254 - 254 + 302 + 302 0 0 - 257 - 257 + 305 + 305 0 0 - 207 - 207 + 304 + 304 0 0 - 257 - 257 + 305 + 305 1 0 - 254 - 254 + 305 + 305 0 0 - 259 - 259 + 307 + 307 0 0 - 254 - 254 + 307 + 307 + 0 + 0 + 309 + 309 + 0 + 0 + 232 + 232 0 1 0 0 0 0 - 257 - 257 + 216 + 216 0 1 0 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar index bb646cca..2db80e99 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar @@ -3,6 +3,27 @@ 1.0000000000000000000000000 1.0000000000000000000000000 2.0000000000000000000000000 +0.2000000000000000111022302 +1.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +1.0000000000000000000000000 +-1.0000000000000000000000000 +0.1000000000000000055511151 +-1.0000000000000000000000000 +1.0000000000000000000000000 +0.1000000000000000055511151 +0.1000000000000000055511151 +-1.0000000000000000000000000 +1.0000000000000000000000000 +0.5999999999999999777955395 +1.0000000000000000000000000 +0.0000000000000000000000000 +1295793.0000000000000000000000000 +1.0000000000000000000000000 +1.0000000000000000000000000 +1295793.0000000000000000000000000 0.0000000000000000000000000 0.0000000000000000000000000 1.0000000000000000000000000 @@ -15,9 +36,17 @@ 9999.0000000000000000000000000 9999.0000000000000000000000000 -4.0000000000000000000000000 -1.0000000000000000000000000 +9.0000000000000000000000000 1295793.0000000000000000000000000 1.0000000000000000000000000 +2.0000000000000000000000000 +3.0000000000000000000000000 +4.0000000000000000000000000 +5.0000000000000000000000000 +6.0000000000000000000000000 +7.0000000000000000000000000 +8.0000000000000000000000000 +9.0000000000000000000000000 9999.0000000000000000000000000 1.0000000000000000000000000 -1.0000000000000000000000000 @@ -32,5 +61,3 @@ -2.0000000000000000000000000 0.0000000000000000000000000 1295793.0000000000000000000000000 -1.0000000000000000000000000 -2.0000000000000000000000000 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.ipar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.ipar index be6505f8..5a08eb5b 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.ipar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.ipar @@ -4,7 +4,7 @@ 10 0 0 - 18243 + 18246 27 1 3 @@ -18,11 +18,11 @@ 100 6 0 - 18205 + 18208 27 100 101 - 18211 + 18214 27 12 0 @@ -34,7 +34,7 @@ 0 15011 203 - 18199 + 18202 27 1 0 @@ -80,11 +80,11 @@ 4 12 0 - 18136 + 18139 0 21 1 - 18148 + 18151 0 1 27 @@ -100,7 +100,7 @@ 1 1 1 - 18135 + 18138 1 7 10 @@ -143,7 +143,7 @@ 10 27 0 - 18064 + 18067 27 0 0 @@ -202,17 +202,17 @@ 100 121 3 - 17848 + 17851 24 212 100 - 17969 + 17972 27 15 0 100 101 - 17984 + 17987 27 36 0 @@ -339,7 +339,7 @@ 121 15002 208 - 17842 + 17845 24 1 0 @@ -391,11 +391,11 @@ 10 376 11 - 17348 + 17351 11 902 10 - 17724 + 17727 22 62 2 @@ -787,83 +787,83 @@ 100 6 0 - 17017 + 17020 4 208 100 - 17023 + 17026 4 6 0 210 100 - 17029 + 17032 4 7 0 212 100 - 17036 + 17039 4 8 0 215 100 - 17044 + 17047 4 6 0 217 100 - 17050 + 17053 4 6 1 219 100 - 17056 + 17059 5 24 0 220 100 - 17080 + 17083 5 6 1 222 100 - 17086 + 17089 6 6 1 224 100 - 17092 + 17095 7 6 1 226 100 - 17098 + 17101 8 6 1 228 100 - 17104 + 17107 9 6 1 230 100 - 17110 + 17113 10 6 1 100 101 - 17116 + 17119 11 140 0 @@ -875,7 +875,7 @@ 0 15011 205 - 17011 + 17014 4 1 0 @@ -921,11 +921,11 @@ 4 14 0 - 16946 + 16949 0 21 1 - 16960 + 16963 0 1 4 @@ -943,7 +943,7 @@ 1 1 1 - 16945 + 16948 1 7 10 @@ -986,7 +986,7 @@ 10 22 0 - 16879 + 16882 4 0 1 @@ -1011,7 +1011,7 @@ 0 -1 1 - 11 + 10 201 100 0 @@ -1028,55 +1028,49 @@ 100 43 1 - 16476 + 16537 0 207 100 - 16519 - 1 - 44 - 0 - 208 - 100 - 16563 + 16580 1 6 1 - 210 + 209 100 - 16569 + 16586 2 6 1 - 212 + 211 100 - 16575 + 16592 3 8 0 - 214 + 213 100 - 16583 + 16600 3 8 0 - 216 + 215 100 - 16591 + 16608 3 6 1 - 218 + 217 100 - 16597 + 16614 4 130 0 100 101 - 16727 + 16744 4 - 84 + 76 0 40 201 @@ -1123,7 +1117,7 @@ 121 22000 205 - 16470 + 16531 0 1 0 @@ -1138,7 +1132,7 @@ 0 0 0 - 16243 + 16304 105 94 12 @@ -12308,6 +12302,35 @@ 110 99 101 + 103 + 114 + 103 + 114 + 103 + 114 + 114 + 103 + 114 + 103 + 114 + 103 + 114 + 103 + 114 + 103 + 103 + 101 + 103 + 101 + 114 + 103 + 103 + 101 + 114 + 101 + 103 + 101 + 103 39 44 32 @@ -12773,6 +12796,38 @@ 97 118 101 + 101 + 114 + 103 + 101 + 114 + 103 + 101 + 114 + 103 + 101 + 114 + 103 + 101 + 114 + 103 + 103 + 101 + 103 + 114 + 103 + 103 + 114 + 101 + 103 + 101 + 103 + 114 + 103 + 101 + 101 + 103 + 103 39 44 32 @@ -17597,64 +17652,20 @@ 84 72 0 - 170 - 207 - 38 - 0 - 1 - 0 - 20 - 36 - 84 - 104 - 101 - 32 - 102 - 114 - 111 - 109 - 32 - 83 - 99 - 105 - 108 - 97 - 98 - 32 - 114 - 101 - 116 - 117 - 114 - 110 - 101 - 100 - 32 - 118 - 97 - 108 - 117 - 101 - 115 - 32 - 97 - 114 - 101 - 32 40 - 208 + 207 0 1 1 0 40 - 210 + 209 0 1 1 0 60009 - 212 + 211 2 0 1 @@ -17662,7 +17673,7 @@ 20 257 60009 - 214 + 213 2 0 1 @@ -17670,13 +17681,13 @@ 20 257 40 - 216 + 215 0 1 1 0 15003 - 218 + 217 124 0 1 @@ -17806,7 +17817,7 @@ 111 110 0 - 10 + 9 8 4 0 @@ -17830,60 +17841,52 @@ 205 0 0 - 207 - 207 + 211 + 211 0 0 - 205 - 205 - 0 - 0 - 212 - 212 - 0 - 0 - 208 - 208 + 207 + 207 0 0 - 212 - 212 + 211 + 211 1 0 205 205 0 0 - 214 - 214 + 213 + 213 0 0 - 210 - 210 + 209 + 209 0 0 - 214 - 214 + 213 + 213 1 0 - 212 - 212 + 211 + 211 0 0 - 218 - 218 + 217 + 217 0 0 - 216 - 216 + 215 + 215 0 0 - 218 - 218 + 217 + 217 1 0 - 214 - 214 + 213 + 213 0 1 0 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.sce b/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.sce index 16a7e925..6b075604 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.sce +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/SwitchingPaPiConfig.sce @@ -184,11 +184,11 @@ function [sim, outlist] = AutoConfigExample(sim, Signal) [sim] = ld_printf(sim, 0, in1, "Case oscillator active: ", 1); // Add a parameter for controlling the oscillator - [PacketFramework, DisturbanceButton] = ld_PF_addpluginAdvanced(PacketFramework, "Button", "Disturbance", "(150,50)", "(600,100)", "PaPI-Tab", list(["state1_text","Disturb"], ["state2_text","Disturbing"])); + [PacketFramework, DisturbanceButton] = ld_PF_addpluginAdvanced(PacketFramework, "Button", "Disturbancegrgrgrrgrgrgrgrggegerggeregeg", "(150,50)", "(600,100)", "PaPI-Tab", list(["state1_text","Disturb"], ["state2_text","Disturbing"])); [sim, PacketFramework, Input]=ld_PF_ParameterInclControl(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="Oscillator input", DisturbanceButton, 'Click_Event'); // Add a button parameter to go to the experiment selection dialog - [PacketFramework, OkButton] = ld_PF_addpluginAdvanced(PacketFramework, "Button", "Leave", "(150,50)", "(600,325)", "PaPI-Tab", list(["state1_text","Ok"], ["state2_text","Leaving"])); + [PacketFramework, OkButton] = ld_PF_addpluginAdvanced(PacketFramework, "Button", "Leaveergergergergerggegrggregegrgeegg", "(150,50)", "(600,325)", "PaPI-Tab", list(["state1_text","Ok"], ["state2_text","Leaving"])); [sim, PacketFramework, GoToChoose]=ld_PF_ParameterInclControl(sim, PacketFramework, NValues=1, datatype=ORTD.DATATYPE_FLOAT, ParameterName="ok", OkButton, 'Click_Event'); // printf the parameter diff --git a/papi/plugin/base_classes/base_plugin.py b/papi/plugin/base_classes/base_plugin.py index 5cdc768b..d20c7503 100644 --- a/papi/plugin/base_classes/base_plugin.py +++ b/papi/plugin/base_classes/base_plugin.py @@ -91,7 +91,7 @@ def send_parameter_change(self, data, block_name): opt.data_source_id = self.__id__ opt.is_parameter = True opt.block_name = block_name - event = Event.data.NewData(self.__id__, 0, opt) + event = Event.data.NewData(self.__id__, 0, opt, None) self._Core_event_queue__.put(event) def create_new_block(self, name, signalNames, types, frequency): diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 9cd2bce7..0226f01b 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -144,6 +144,8 @@ def start_init(self, config=None): self.block_id = 0 + self.json_config_file = '' + return True def pause(self): @@ -206,13 +208,14 @@ def process_received_package(self, rev): Counter = 1 data = struct.pack(' Date: Tue, 31 Mar 2015 15:18:17 +0200 Subject: [PATCH 215/260] Merge branch 'development' of https://github.com/TUB-Control/PaPI into development Conflicts: papi/last_active_papi.xml papi/ui/gui/qt_new/create.py papi/ui/gui/qt_new/create_dialog.py papi/ui/gui/qt_new/main.py papi/ui/gui/qt_new/overview.py Added first version of a new plugin: StaticPlot Improved RunMode -> Widgets are no more movable and resizable. --- papi/gui/qt_new/item.py | 21 +- papi/gui/qt_new/main.py | 62 +- papi/plugin/base_classes/base_visual.py | 14 +- papi/plugin/visual/StaticPlot/StaticPlot.py | 632 ++++++++++++++++++ .../visual/StaticPlot/StaticPlot.yapsy-plugin | 9 + 5 files changed, 707 insertions(+), 31 deletions(-) create mode 100644 papi/plugin/visual/StaticPlot/StaticPlot.py create mode 100644 papi/plugin/visual/StaticPlot/StaticPlot.yapsy-plugin diff --git a/papi/gui/qt_new/item.py b/papi/gui/qt_new/item.py index 16873ffa..a656ed82 100644 --- a/papi/gui/qt_new/item.py +++ b/papi/gui/qt_new/item.py @@ -34,6 +34,8 @@ from papi.data.DPlugin import * from papi.data.DSignal import DSignal + + # ------------------------------------ # Item Object # ------------------------------------ @@ -673,4 +675,21 @@ def data(self, role): if role == Qt.EditRole: return None - return None \ No newline at end of file + return None + +# ------------------------------------ +# Custom GUI elements +# ------------------------------------ + + +class PaPIMDISubWindow(QMdiSubWindow): + def __init__(self): + super(PaPIMDISubWindow, self).__init__() + self.movable = True + + def mouseMoveEvent(self, event): + if self.movable: + super(PaPIMDISubWindow, self).mouseMoveEvent(event) + + def set_movable(self, flag): + self.movable = flag \ No newline at end of file diff --git a/papi/gui/qt_new/main.py b/papi/gui/qt_new/main.py index 015d3263..3ccbaf95 100644 --- a/papi/gui/qt_new/main.py +++ b/papi/gui/qt_new/main.py @@ -31,9 +31,7 @@ import sys import os import traceback -import cProfile import re -import threading from PySide.QtGui import QMainWindow, QApplication, QFileDialog, QDesktopServices from PySide.QtGui import QIcon @@ -46,17 +44,17 @@ from papi.data.DGui import DGui from papi.ConsoleLog import ConsoleLog +from papi.gui.qt_new.item import PaPIMDISubWindow + from papi.constants import GUI_PAPI_WINDOW_TITLE, GUI_WOKRING_INTERVAL, GUI_PROCESS_CONSOLE_IDENTIFIER, \ GUI_PROCESS_CONSOLE_LOG_LEVEL, GUI_START_CONSOLE_MESSAGE, GUI_WAIT_TILL_RELOAD, GUI_DEFAULT_HEIGHT, GUI_DEFAULT_WIDTH, \ PLUGIN_STATE_PAUSE, PLUGIN_STATE_STOPPED, PAPI_ABOUT_TEXT, PAPI_ABOUT_TITLE, PAPI_DEFAULT_BG_PATH, PAPI_LAST_CFG_PATH - from papi.constants import CONFIG_DEFAULT_FILE, PLUGIN_VIP_IDENTIFIER, PLUGIN_PCP_IDENTIFIER, CONFIG_DEFAULT_DIRECTORY from papi.gui.qt_new.create_plugin_menu import CreatePluginMenu from papi.gui.qt_new.overview_menu import OverviewPluginMenu - from papi.gui.qt_new.PapiTabManger import PapiTabManger from papi.gui.gui_management import GuiManagement @@ -376,7 +374,14 @@ def add_dplugin(self, dplugin): :return: """ if dplugin.type == PLUGIN_VIP_IDENTIFIER or dplugin.type == PLUGIN_PCP_IDENTIFIER: + + sub_window_ori = dplugin.plugin.get_sub_window() + + dplugin.plugin.set_window_for_internal_usage(PaPIMDISubWindow()) + dplugin.plugin.set_widget_for_internal_usage(sub_window_ori.widget()) + sub_window = dplugin.plugin.get_sub_window() + config = dplugin.startup_config tab_name = config['tab']['value'] if tab_name in self.TabManager.get_tabs_by_uname(): @@ -385,9 +390,10 @@ def add_dplugin(self, dplugin): self.log.printText(1,'add dplugin: no tab with tab_id of dplugin') area = self.TabManager.add_tab(tab_name) - area.addSubWindow(sub_window) + sub_window.show() + size_re = re.compile(r'([0-9]+)') pos = config['position']['value'] @@ -453,45 +459,43 @@ def toggle_run_mode(self): self.loadButton.show() self.saveButton.show() self.menubar.setHidden(False) - self.disable_lock() - self.TabManager.setTabs_movable_closable(True, True) + self.toogle_lock() elif not self.in_run_mode: self.in_run_mode = True - self.loadButton.hide() self.saveButton.hide() self.menubar.hide() - self.enable_lock() - self.TabManager.setTabs_movable_closable(False, False) - - def enable_lock(self): - for tab_name in self.TabManager.get_tabs_by_uname(): - area = self.TabManager.get_tabs_by_uname()[tab_name] + self.toogle_lock() - windowsList = area.subWindowList() + def toogle_lock(self): - for window in windowsList: - - #window.setAttribute(Qt.WA_NoBackground) + if self.in_run_mode: + for tab_name in self.TabManager.get_tabs_by_uname(): + area = self.TabManager.get_tabs_by_uname()[tab_name] - #window.setAttribute(Qt.WA_NoSystemBackground) - #window.setAttribute(Qt.WA_TranslucentBackground) + windowsList = area.subWindowList() - window.setMouseTracking(False) - window.setWindowFlags( Qt.WindowTitleHint | Qt.FramelessWindowHint ) + for window in windowsList: - def disable_lock(self): - for tab_name in self.TabManager.get_tabs_by_uname(): - area = self.TabManager.get_tabs_by_uname()[tab_name] + #window.setAttribute(Qt.WA_NoBackground) - windowsList = area.subWindowList() + #window.setAttribute(Qt.WA_NoSystemBackground) + #window.setAttribute(Qt.WA_TranslucentBackground) + window.set_movable(False) + window.setMouseTracking(False) + window.setWindowFlags(~Qt.WindowMinMaxButtonsHint & (Qt.CustomizeWindowHint | Qt.WindowTitleHint)) - for window in windowsList: + if not self.in_run_mode: + for tab_name in self.TabManager.get_tabs_by_uname(): + area = self.TabManager.get_tabs_by_uname()[tab_name] - window.setMouseTracking(True) - window.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowMinMaxButtonsHint | Qt.WindowTitleHint ) + windowsList = area.subWindowList() + for window in windowsList: + window.set_movable(True) + window.setMouseTracking(True) + window.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowMinMaxButtonsHint | Qt.WindowTitleHint ) def keyPressEvent(self, event): if event.key() == Qt.Key_Escape: diff --git a/papi/plugin/base_classes/base_visual.py b/papi/plugin/base_classes/base_visual.py index 43ad7721..70177f89 100644 --- a/papi/plugin/base_classes/base_visual.py +++ b/papi/plugin/base_classes/base_visual.py @@ -43,6 +43,7 @@ def init_plugin(self, CoreQueue, pluginQueue, id, control_api, dpluginInfo = Non self.control_api = control_api self.dplugin_info = dpluginInfo self.TabManager = TabManger + self.movable = True def start_init(self, config=None): self.config = config @@ -93,9 +94,13 @@ def get_configuration_base(self): def set_window_for_internal_usage(self, subwindow): self._subWindow = subwindow self.original_resize_function = self._subWindow.resizeEvent - self._subWindow.resizeEvent = self.window_resize self.original_move_function = self._subWindow.moveEvent + self.original_mouse_move_function = self._subWindow.mouseMoveEvent + + self._subWindow.resizeEvent = self.window_resize self._subWindow.moveEvent = self.window_move + + self._subWindow.setWindowTitle(self.window_name) self._subWindow.resize(int(self.window_size[0]), int(self.window_size[1])) @@ -120,9 +125,16 @@ def window_resize(self, event): self.config['size']['value'] = '(' + str(w) + ',' + str(h) + ')' self.original_resize_function(event) + def window_mouse_move(self, event): + if self.movable: + self.original_mouse_move_function(event) + def get_sub_window(self): return self._subWindow + def get_widget(self): + return self.widget + def create_control_context_menu(self): ctrlMenu = QtGui.QMenu("Control") diff --git a/papi/plugin/visual/StaticPlot/StaticPlot.py b/papi/plugin/visual/StaticPlot/StaticPlot.py new file mode 100644 index 00000000..7591254e --- /dev/null +++ b/papi/plugin/visual/StaticPlot/StaticPlot.py @@ -0,0 +1,632 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: + ma: + ma = ma_buf + else: + ma = ma_buf + + if mi is not None: + if mi_buf < mi: + mi = mi_buf + else: + mi = mi_buf + + self.yRange_maxEdit.setText(ma) + self.yRange_minEdit.setText(mi) + self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + str(float(mi)) + ' ' + str(float(ma)) + ']') + + def contextMenu_xGrid_toogle(self): + if self.xGrid_Checkbox.isChecked(): + self.control_api.do_set_parameter(self.__id__, 'x-grid', '1') + else: + self.control_api.do_set_parameter(self.__id__, 'x-grid', '0') + + def contextMenu_yGrid_toogle(self): + if self.yGrid_Checkbox.isChecked(): + self.control_api.do_set_parameter(self.__id__, 'y-grid', '1') + else: + self.control_api.do_set_parameter(self.__id__, 'y-grid', '0') + + def contextMenu_yRange_toogle(self): + mi = self.yRange_minEdit.text() + ma = self.yRange_maxEdit.text() + if float(mi) < float(ma): + self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + float(mi) + ' ' + float(ma) + ']') + + + def quit(self): + """ + Function quit plugin + + :return: + """ + print('StaticPlot: will quit') + + def get_plugin_configuration(self): + """ + Function get plugin configuration + + :return {}: + """ + config = { + 'json_data' : { + 'value' : '{"v1": [5, 6, 7, 8, 9 ], "v2": [ 0, 1, 2, 3, 4 ], ' + '"v3": [ -5, -6, -7, -8, -9 ], "v4": [ 0, -1, -2, -3, -4 ], ' + '"t": [ 0, 1, 2, 3, 4 ]}', + 'tooltip' : 'Used as data source. Format: { "time" : [...], "y1" : [...], "y2" : [...]}' + }, + 'time_identifier' : { + 'value' : 't', + 'tooltip' : 'Used to specify the identifier for the X-Axis e.g. time in json_data' + }, + 'x-grid': { + 'value': "0", + 'regex': pc.REGEX_BOOL_BIN, + 'type': 'bool', + 'display_text': 'Grid-X' + }, 'y-grid': { + 'value': "0", + 'regex': pc.REGEX_BOOL_BIN, + 'type': 'bool', + 'display_text': 'Grid-Y' + }, 'color': { + 'value': "[0 1 2 3 4]", + 'regex': '^\[(\s*\d\s*)+\]', + 'advanced': '1', + 'display_text': 'Color' + }, 'style': { + 'value': "[0 0 0 0 0]", + 'regex': '^\[(\s*\d\s*)+\]', + 'advanced': '1', + 'display_text': 'Style' + }, 'yRange': { + 'value': '[0.0 1.0]', + 'regex': '(\d+\.\d+)', + 'advanced': '1', + 'display_text': 'y: range' + } + } + # http://www.regexr.com/ + return config + + def clear(self): + """ + + :return: + """ + self.__plotWidget__.clear() + +class GraphicItem(pg.QtGui.QGraphicsPathItem): + """ + Represents a single object which is drawn by a plot. + """ + def __init__(self, x, y, counter, pen=pg.mkPen('r')): + """ + + :param x: + :param y: + :param pen: + :return: + """ + + x = np.array(x[:])[np.newaxis, :] + + connect = np.ones(x.shape, dtype=bool) + connect[:,-1] = 0 # don't draw the segment between each trace + self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten()) + pg.QtGui.QGraphicsPathItem.__init__(self, self.path) + self.setCacheMode(pg.QtGui.QGraphicsItem.NoCache) + self.setPen(pen) + self.not_drawn = True + self.counter = counter + self.y = y + self.last_x = x[0][-1] + self.last_y = y[-1] + + def shape(self): # override because QGraphicsPathItem.shape is too expensive. + return pg.QtGui.QGraphicsItem.shape(self) + + def length(self): + return self.counter + + def boundingRect(self): + return self.path.boundingRect() + + diff --git a/papi/plugin/visual/StaticPlot/StaticPlot.yapsy-plugin b/papi/plugin/visual/StaticPlot/StaticPlot.yapsy-plugin new file mode 100644 index 00000000..09c6db00 --- /dev/null +++ b/papi/plugin/visual/StaticPlot/StaticPlot.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = StaticPlot +Module = StaticPlot + +[Documentation] +Author = S.K. +Version = 0.1 +Website = +Description = Used to plot static data provided by the configuration. From f4b5ac84f73b605a2bb56ba3e0fd19f83cd1c695 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 31 Mar 2015 15:19:48 +0200 Subject: [PATCH 216/260] StaticPlot: changed configuration time_identifier -> xaxis_identifier --- papi/plugin/visual/StaticPlot/StaticPlot.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/papi/plugin/visual/StaticPlot/StaticPlot.py b/papi/plugin/visual/StaticPlot/StaticPlot.py index 7591254e..017bda0a 100644 --- a/papi/plugin/visual/StaticPlot/StaticPlot.py +++ b/papi/plugin/visual/StaticPlot/StaticPlot.py @@ -258,13 +258,13 @@ def set_parameter(self, name, value): def read_json_data(self): json_str = self.config['json_data']['value'] - time_id = self.config['time_identifier']['value'] + axis_id = self.config['axis_identifier']['value'] data = json.loads(json_str) - self.plot_data(data, time_id) + self.plot_data(data, axis_id) - def plot_data(self, data, time_identifier): + def plot_data(self, data, axis_id): """ Function update_plot_single_timestamp @@ -274,7 +274,7 @@ def plot_data(self, data, time_identifier): self.__plotWidget__.clear() - tdata = data[time_identifier] + tdata = data[axis_id] x_min = min(tdata) x_max = max(tdata) @@ -283,7 +283,7 @@ def plot_data(self, data, time_identifier): y_max = None for signal_name in data: - if signal_name != time_identifier: + if signal_name != axis_id: signal_data = data[signal_name] print(signal_data) @@ -552,7 +552,7 @@ def get_plugin_configuration(self): '"t": [ 0, 1, 2, 3, 4 ]}', 'tooltip' : 'Used as data source. Format: { "time" : [...], "y1" : [...], "y2" : [...]}' }, - 'time_identifier' : { + 'axis_identifier' : { 'value' : 't', 'tooltip' : 'Used to specify the identifier for the X-Axis e.g. time in json_data' }, From 4f20bd1e80b46de8da36af25ac49807ead4a9f42 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 31 Mar 2015 15:29:12 +0200 Subject: [PATCH 217/260] added timeouts to ORTD Plugin and possibility to receive more than 1 cfg package --- .../AutoConfigDemo_ReplaceableSimulation.ipar | 5315 ++++------------- .../AutoConfigDemo_ReplaceableSimulation.rpar | 28 +- papi/plugin/io/ORTD_UDP/ORTD_UDP.py | 177 +- 3 files changed, 1343 insertions(+), 4177 deletions(-) diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar index d8ca20e3..afac5c56 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.ipar @@ -40,8 +40,8 @@ 10 54 0 - 10090 - 63 + 7309 + 41 1 1 2 @@ -56,7 +56,7 @@ -1 -1 -1 - 1427798592 + 1427808470 -1 -1 2015 @@ -65,10 +65,10 @@ 90 3 31 - 12 - 43 - 12 - 600 + 15 + 27 + 50 + 517 26 79 82 @@ -97,7 +97,7 @@ 105 99 1 - 59 + 33 202 100 0 @@ -126,331 +126,175 @@ 100 167 3 - 32 - 0 - 210 - 100 - 199 - 3 6 1 - 212 + 211 100 - 205 + 173 4 49 0 - 214 + 213 100 - 254 + 222 4 6 1 - 216 + 215 100 - 260 + 228 5 49 0 - 218 - 100 - 309 - 5 - 25 - 0 - 219 + 217 100 - 334 + 277 5 - 6 - 1 - 220 - 100 - 340 - 6 - 6 - 2 - 224 - 100 - 346 - 8 - 6 - 2 - 226 - 100 - 352 - 10 - 6 + 49 2 - 228 - 100 - 358 - 12 - 8 - 3 - 230 + 218 100 - 366 - 15 + 326 + 7 6 1 - 232 + 220 100 - 372 - 16 + 332 8 - 3 - 234 - 100 - 380 - 19 - 6 - 1 - 236 + 5400 + 18 + 222 100 - 386 - 20 + 5732 + 26 6 1 - 238 + 224 100 - 392 - 21 + 5738 + 27 8 0 - 240 - 100 - 400 - 21 - 6 - 0 - 242 - 100 - 406 - 21 - 6 - 1 - 244 - 100 - 412 - 22 - 6 - 0 - 246 - 100 - 418 - 22 - 6 - 1 - 248 + 226 100 - 424 - 23 + 5746 + 27 6 0 - 250 - 100 - 430 - 23 - 76 - 0 - 252 - 100 - 506 - 23 - 9 - 0 - 254 - 100 - 515 - 23 - 161 - 0 - 255 + 228 100 - 676 - 23 + 5752 + 27 6 1 - 257 - 100 - 682 - 24 - 8 - 0 - 259 + 230 100 - 690 - 24 + 5758 + 28 6 0 - 261 + 232 100 - 696 - 24 + 5764 + 28 6 1 - 263 + 234 100 - 702 - 25 + 5770 + 29 6 0 - 265 + 236 100 - 708 - 25 + 5776 + 29 6 1 - 267 + 238 100 - 714 - 26 + 5782 + 30 6 0 - 269 + 240 100 - 720 - 26 + 5788 + 30 76 0 - 271 + 242 100 - 796 - 26 + 5864 + 30 9 0 - 273 + 244 100 - 805 - 26 + 5873 + 30 161 0 - 274 - 100 - 966 - 26 - 49 - 2 - 275 - 100 - 1015 - 28 - 6 - 1 - 277 - 100 - 1021 - 29 - 7263 - 21 - 279 - 100 - 8284 - 50 - 6 - 1 - 281 - 100 - 8290 - 51 - 8 - 0 - 283 + 245 100 - 8298 - 51 + 6034 + 30 6 0 - 285 + 247 100 - 8304 - 51 + 6040 + 30 6 1 - 287 - 100 - 8310 - 52 - 6 - 0 - 289 + 248 100 - 8316 - 52 + 6046 + 31 6 - 1 - 291 + 2 + 250 100 - 8322 - 53 + 6052 + 33 6 0 - 293 + 252 100 - 8328 - 53 + 6058 + 33 + 688 6 - 1 - 295 + 254 100 - 8334 - 54 + 6746 + 39 6 - 0 - 297 - 100 - 8340 - 54 - 76 - 0 - 299 - 100 - 8416 - 54 - 9 - 0 - 301 + 2 + 256 100 - 8425 - 54 - 161 + 6752 + 41 + 37 0 - 302 + 257 100 - 8586 - 54 + 6789 + 41 6 0 - 304 - 100 - 8592 - 54 - 6 - 1 - 305 - 100 - 8598 - 55 - 6 - 2 - 307 + 259 100 - 8604 - 57 - 6 + 6795 + 41 + 78 0 - 309 - 100 - 8610 - 57 - 688 - 6 100 101 - 9298 - 63 - 436 + 6873 + 41 + 236 0 40 202 @@ -619,46 +463,69 @@ 1 1 0 - 170 + 40 209 - 26 0 1 + 1 + 0 + 15007 + 211 + 43 0 1 - 24 - 67 - 97 - 115 - 101 + 0 + 0 + 257 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 32 - 111 - 115 - 99 + 83 + 119 105 - 108 - 108 - 97 116 - 111 - 114 - 32 - 97 99 + 104 + 105 + 110 + 103 + 65 + 117 116 + 111 + 67 + 111 + 110 + 102 105 - 118 + 103 + 77 101 - 58 - 32 + 109 + 111 + 114 + 121 + 46 + 109 + 101 + 109 + 111 + 114 + 121 40 - 210 + 213 0 1 1 0 15007 - 212 + 215 43 0 1 @@ -706,20 +573,15 @@ 111 114 121 - 40 - 214 - 0 - 1 - 1 - 0 - 15007 - 216 + 15005 + 217 43 - 0 + 2 1 0 0 257 + 2 1 0 0 @@ -727,7 +589,6 @@ 0 0 0 - 0 32 83 119 @@ -761,131 +622,16 @@ 111 114 121 - 170 - 218 - 19 - 0 - 1 - 0 - 1 - 17 - 79 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 105 - 110 - 112 - 117 - 116 - 32 40 - 219 + 218 0 1 1 0 - 12 + 15011 220 - 0 - 2 - 1 - 0 - 12 - 224 - 0 - 2 - 1 - 0 - 12 - 226 - 0 - 2 - 1 - 0 - 30 - 228 - 2 - 3 - 1 - 0 - 0 - 1 - 20 - 230 - 0 - 1 - 1 - 0 - 30 - 232 - 2 - 3 - 1 - 0 - 0 - 1 - 20 - 234 - 0 - 1 - 1 - 0 - 40 - 236 - 0 - 1 - 1 - 0 - 60005 - 238 - 2 - 0 - 1 - 0 - 0 - 100000 - 60031 - 240 - 0 - 0 - 1 - 0 - 40 - 242 - 0 - 1 - 1 - 0 - 60031 - 244 - 0 - 0 - 1 - 0 - 40 - 246 - 0 - 1 - 1 - 0 - 60031 - 248 - 0 - 0 - 1 - 0 - 39011 - 250 - 70 - 0 + 5394 + 18 1 0 1 @@ -894,198 +640,112 @@ 4 0 0 - 5 + 2 0 11 4 - 5 + 2 0 2 0 12 4 - 7 + 4 0 - 5 + 2 0 13 4 - 12 + 6 0 2 0 14 4 - 14 + 8 0 2 0 15 4 - 16 + 10 0 2 0 20 4 - 18 + 12 0 - 1 + 5331 0 21 1 - 19 + 5343 0 1 - 0 - 4 + 18 1 1 1 1 1 - 20 - 4 - 2 - 2 - 2 257 1 - 6 - 1 - 1 + 257 1 - 2 - 0 - 0 - 60035 - 252 - 3 - 0 1 - 0 1 - 0 - 20 - 39004 - 254 - 155 - 0 1 - 0 + 5330 1 - 9 + 7 10 4 0 0 - 3 + 1 0 11 4 - 3 + 1 0 1 0 12 4 - 4 + 2 0 - 3 + 1 0 13 4 - 7 + 3 0 1 0 - 14 + 21 + 4 4 - 8 0 - 2 + 27 0 - 15 + 22 4 - 10 - 0 - 2 + 31 0 - 20 4 - 12 0 - 43 + 900 + 10 + 35 0 - 21 - 1 - 55 + 5251 + 18 0 - 1 0 - 30 - 4 - 56 - 0 - 43 - 0 - 2 - 20 - 1 - 0 - 2 - 6 - 2 - 0 - 1 - 1 - 1 - 1 - 42 - 1 - 4 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 - 10 0 - 1 - 20 - 1 - 6 - 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 0 - 42 + 26 83 119 105 @@ -1105,275 +765,208 @@ 102 105 103 - 97 - 83 - 111 - 99 - 107 - 101 - 116 - 46 - 85 - 68 - 80 - 83 - 111 - 99 - 107 - 101 - 116 - 95 - 83 + 84 104 - 79 - 98 - 106 - 40 - 255 + 114 + 101 + 97 + 100 + 49 + 3 + 2 0 + -1 1 - 1 + 16 + 201 + 100 0 - 60005 - 257 - 2 0 - 1 + 137 0 + 204 + 100 + 137 0 - 100000 - 60031 - 259 + 76 0 + 209 + 100 + 213 0 - 1 + 6 0 - 40 - 261 + 211 + 100 + 219 0 + 6 1 + 212 + 100 + 225 + 1 + 6 + 2 + 214 + 100 + 231 + 3 + 6 1 + 216 + 100 + 237 + 4 + 6 0 - 60031 - 263 + 218 + 100 + 243 + 4 + 7 0 + 220 + 100 + 250 + 4 + 6 0 + 222 + 100 + 256 + 4 + 6 1 + 224 + 100 + 262 + 5 + 6 0 - 40 - 265 + 226 + 100 + 268 + 5 + 6 0 + 228 + 100 + 274 + 5 + 6 1 - 1 - 0 - 60031 - 267 - 0 + 230 + 100 + 280 + 6 + 6 0 - 1 + 232 + 100 + 286 + 6 + 4687 + 12 + 100 + 101 + 4973 + 18 + 180 0 - 39011 - 269 - 70 + 39003 + 201 + 131 0 1 0 1 - 8 + 9 10 4 0 0 - 5 + 1 0 11 4 - 5 + 1 0 - 2 + 3 0 12 4 - 7 + 4 0 - 5 + 1 0 13 4 - 12 + 5 0 - 2 + 3 0 14 4 - 14 + 8 0 2 0 15 4 - 16 + 10 0 2 0 20 4 - 18 + 12 0 - 1 + 19 0 21 1 - 19 + 31 0 1 0 + 30 4 - 1 - 1 - 1 - 1 - 1 - 20 - 4 - 2 + 32 + 0 + 43 + 0 + 0 2 + 1396 + 6 + 0 2 - 257 - 1 6 + 6 + 1 + 1 1 1 + 18 1 2 + 10 + 4 0 0 - 60035 - 271 - 3 + 2 0 - 1 + 11 + 4 + 2 0 - 1 + 2 0 - 20 - 39004 - 273 - 155 - 0 - 1 - 0 - 1 - 9 - 10 - 4 - 0 - 0 - 3 - 0 - 11 - 4 - 3 - 0 - 1 - 0 - 12 - 4 - 4 - 0 - 3 - 0 - 13 - 4 - 7 - 0 - 1 - 0 - 14 - 4 - 8 - 0 - 2 - 0 - 15 - 4 - 10 - 0 - 2 - 0 - 20 - 4 - 12 - 0 - 43 - 0 - 21 - 1 - 55 - 0 - 1 - 0 - 30 - 4 - 56 - 0 - 43 - 0 - 2 - 20 1 - 0 - 2 - 6 - 2 - 0 - 1 - 1 - 1 - 1 - 42 - 1 - 4 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 - 10 - 0 - 1 - 20 + 1396 1 6 - 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 0 42 83 @@ -1418,65 +1011,10 @@ 79 98 106 - 15005 - 274 - 43 - 2 - 1 - 0 - 0 - 257 - 2 - 1 - 0 - 0 - 0 - 0 - 0 - 0 - 32 - 83 - 119 - 105 - 116 - 99 - 104 - 105 - 110 - 103 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 77 - 101 - 109 - 111 - 114 - 121 - 46 - 109 - 101 - 109 - 111 - 114 - 121 - 40 - 275 - 0 - 1 - 1 + 39012 + 204 + 70 0 - 15011 - 277 - 7257 - 21 1 0 1 @@ -1491,626 +1029,243 @@ 4 2 0 - 2 + 5 0 12 4 - 4 + 7 0 2 0 13 4 - 6 + 9 0 - 2 + 5 0 14 4 - 8 + 14 0 2 0 15 4 - 10 + 16 0 2 0 20 4 - 12 + 18 0 - 7194 + 1 0 21 1 - 7206 + 19 0 1 - 21 - 1 + 0 1 + 1396 + 4 1 1 1 - 257 + 173 1 + 6 + 4 + 2 + 2 + 2 257 1 1 1 - 1 - 7193 - 1 - 7 - 10 - 4 + 2 0 0 - 1 + 60032 + 209 + 0 0 - 11 - 4 1 0 + 40 + 211 + 0 + 1 1 0 12 - 4 - 2 + 212 0 + 2 1 0 - 13 - 4 - 3 + 60020 + 214 0 1 + 1 0 - 21 - 4 - 4 + 60014 + 216 0 - 27 0 - 22 - 4 - 31 + 1 0 - 4 + 60043 + 218 + 1 0 - 900 - 10 - 35 + 1 0 - 7114 - 21 + -3 + 60032 + 220 0 0 + 1 0 + 60020 + 222 0 - 26 - 83 - 119 - 105 - 116 - 99 - 104 - 105 - 110 - 103 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 84 - 104 - 114 - 101 - 97 - 100 - 49 - 3 - 2 - 0 - -1 1 - 16 - 201 - 100 + 1 0 + 60014 + 224 0 - 137 0 - 204 - 100 - 137 + 1 0 - 76 + 60022 + 226 0 - 209 - 100 - 213 0 - 6 + 1 0 - 211 - 100 - 219 + 60020 + 228 0 - 6 - 1 - 212 - 100 - 225 1 - 6 - 2 - 214 - 100 - 231 - 3 - 6 1 - 216 - 100 - 237 - 4 - 6 - 0 - 218 - 100 - 243 - 4 - 7 - 0 - 220 - 100 - 250 - 4 - 6 0 - 222 - 100 - 256 - 4 - 6 - 1 - 224 - 100 - 262 - 5 - 6 + 60034 + 230 0 - 226 - 100 - 268 - 5 - 6 0 - 228 - 100 - 274 - 5 - 6 1 - 230 - 100 - 280 - 6 - 6 0 + 15013 232 - 100 - 286 - 6 - 6550 - 15 - 100 - 101 - 6836 - 21 - 180 - 0 - 39003 - 201 - 131 - 0 + 4681 + 12 1 0 1 - 9 + 8 10 4 0 0 - 1 + 6 0 11 4 - 1 + 6 0 - 3 + 2 0 12 4 - 4 + 8 0 - 1 + 6 0 13 4 - 5 + 14 0 - 3 + 2 0 14 4 - 8 + 16 0 2 0 15 4 - 10 + 18 0 2 0 20 4 - 12 + 20 0 - 19 + 4610 0 21 1 - 31 + 4630 0 1 - 0 - 30 - 4 - 32 - 0 - 43 - 0 - 0 - 2 - 1396 - 6 - 0 - 2 - 6 - 6 + 12 + 5 1 1 1 + 173 1 - 18 1 + 1 + 5 2 - 10 - 4 - 0 - 0 2 - 0 - 11 - 4 2 - 0 + 257 2 - 0 1 - 1396 + 257 1 - 6 - 0 - 42 - 83 - 119 - 105 - 116 - 99 - 104 - 105 - 110 - 103 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 97 - 83 - 111 - 99 - 107 - 101 - 116 - 46 - 85 - 68 - 80 - 83 - 111 - 99 - 107 - 101 - 116 - 95 - 83 - 104 - 79 - 98 - 106 - 39012 - 204 - 70 - 0 1 - 0 1 - 8 - 10 + 1 + 4609 + 1 + 9 + 1 4 0 0 - 2 + 3 0 - 11 + 10 4 - 2 + 3 0 5 0 - 12 + 11 4 - 7 + 8 0 2 0 - 13 + 12 4 - 9 + 10 0 5 0 - 14 + 13 4 - 14 - 0 - 2 - 0 - 15 - 4 - 16 - 0 - 2 - 0 - 20 - 4 - 18 - 0 - 1 - 0 - 21 - 1 - 19 - 0 - 1 - 0 - 1 - 1396 - 4 - 1 - 1 - 1 - 173 - 1 - 6 - 4 - 2 - 2 - 2 - 257 - 1 - 1 - 1 - 2 - 0 - 0 - 60032 - 209 - 0 - 0 - 1 - 0 - 40 - 211 - 0 - 1 - 1 - 0 - 12 - 212 - 0 - 2 - 1 - 0 - 60020 - 214 - 0 - 1 - 1 - 0 - 60014 - 216 - 0 - 0 - 1 - 0 - 60043 - 218 - 1 - 0 - 1 - 0 - -3 - 60032 - 220 - 0 - 0 - 1 - 0 - 60020 - 222 - 0 - 1 - 1 - 0 - 60014 - 224 - 0 - 0 - 1 - 0 - 60022 - 226 - 0 - 0 - 1 - 0 - 60020 - 228 - 0 - 1 - 1 - 0 - 60034 - 230 - 0 - 0 - 1 - 0 - 15013 - 232 - 6544 - 15 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 6 - 0 - 11 - 4 - 6 - 0 - 2 - 0 - 12 - 4 - 8 - 0 - 6 - 0 - 13 - 4 - 14 - 0 - 2 - 0 - 14 - 4 - 16 - 0 - 2 - 0 - 15 - 4 - 18 - 0 - 2 - 0 - 20 - 4 - 20 - 0 - 6473 - 0 - 21 - 1 - 6493 - 0 - 1 - 15 - 5 - 1 - 1 - 1 - 173 - 1 - 1 - 1 - 5 - 2 - 2 - 2 - 257 - 2 - 1 - 257 - 1 - 1 - 1 - 1 - 6472 - 1 - 9 - 1 - 4 - 0 - 0 - 3 - 0 - 10 - 4 - 3 - 0 - 5 - 0 - 11 - 4 - 8 - 0 - 2 - 0 - 12 - 4 - 10 - 0 - 5 - 0 - 13 - 4 - 15 + 15 0 2 0 @@ -2130,12 +1285,12 @@ 10 438 1 - 5833 - 13 + 3970 + 10 902 10 - 6271 - 14 + 4408 + 11 145 1 2 @@ -2577,7 +1732,7 @@ 0 0 1 - 66 + 48 205 100 0 @@ -2840,1840 +1995,145 @@ 100 2756 10 - 269 + 224 0 287 100 - 3025 + 2980 10 78 0 289 100 - 3103 + 3058 10 9 - 0 - 291 - 100 - 3112 - 10 - 161 - 0 - 292 - 100 - 3273 - 10 - 6 - 1 - 294 - 100 - 3279 - 11 - 6 - 0 - 296 - 100 - 3285 - 11 - 269 - 0 - 298 - 100 - 3554 - 11 - 78 - 0 - 300 - 100 - 3632 - 11 - 9 - 0 - 302 - 100 - 3641 - 11 - 161 - 0 - 303 - 100 - 3802 - 11 - 6 - 1 - 305 - 100 - 3808 - 12 - 6 - 0 - 307 - 100 - 3814 - 12 - 269 - 0 - 309 - 100 - 4083 - 12 - 78 - 0 - 311 - 100 - 4161 - 12 - 9 - 0 - 313 - 100 - 4170 - 12 - 161 - 0 - 314 - 100 - 4331 - 12 - 6 - 1 - 316 - 100 - 4337 - 13 - 6 - 0 - 318 - 100 - 4343 - 13 - 200 - 0 - 320 - 100 - 4543 - 13 - 78 - 0 - 322 - 100 - 4621 - 13 - 9 - 0 - 324 - 100 - 4630 - 13 - 161 - 0 - 100 - 101 - 4791 - 13 - 644 - 0 - 60032 - 205 - 0 - 0 - 1 - 0 - 60032 - 207 - 0 - 0 - 1 - 0 - 60032 - 209 - 0 - 0 - 1 - 0 - 40 - 211 - 0 - 1 - 1 - 0 - 170 - 213 - 33 - 0 - 1 - 0 - 1 - 31 - 71 - 105 - 118 - 101 - 32 - 67 - 111 - 110 - 102 - 105 - 103 - 32 - 40 - 83 - 111 - 117 - 114 - 99 - 101 - 73 - 68 - 41 - 32 - 32 - 32 - 32 - 32 - 32 - 32 - 61 - 32 - 40 - 214 - 0 - 1 - 1 - 0 - 60031 - 216 - 0 - 0 - 1 - 0 - 40 - 218 - 0 - 1 - 1 - 0 - 60031 - 220 - 0 - 0 - 1 - 0 - 40 - 222 - 0 - 1 - 1 - 0 - 60031 - 224 - 0 - 0 - 1 - 0 - 40 - 226 - 0 - 1 - 1 - 0 - 60031 - 228 - 0 - 0 - 1 - 0 - 60305 - 230 - 263 - 0 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 1 - 0 - 11 - 4 - 1 - 0 - 2 - 0 - 12 - 4 - 3 - 0 - 1 - 0 - 13 - 4 - 4 - 0 - 2 - 0 - 14 - 4 - 6 - 0 - 2 - 0 - 15 - 4 - 8 - 0 - 2 - 0 - 20 - 4 - 10 - 0 - 202 - 0 - 21 - 1 - 212 - 0 - 1 - 0 - 0 - 1 - 184 - 0 - 1 - 6 - 1 - 1 - 1 - 2 - 201 - 1 - 2 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 185 - 0 - 1 - 184 - 184 - 32 - 123 - 34 - 83 - 111 - 117 - 114 - 99 - 101 - 115 - 67 - 111 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 48 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 83 - 111 - 117 - 114 - 99 - 101 - 78 - 97 - 109 - 101 - 34 - 32 - 58 - 32 - 34 - 88 - 34 - 32 - 44 - 32 - 34 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 95 - 115 - 101 - 110 - 100 - 34 - 32 - 58 - 32 - 34 - 49 - 34 - 44 - 32 - 34 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 34 - 32 - 58 - 32 - 34 - 50 - 53 - 55 - 34 - 32 - 32 - 125 - 32 - 44 - 32 - 34 - 49 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 83 - 111 - 117 - 114 - 99 - 101 - 78 - 97 - 109 - 101 - 34 - 32 - 58 - 32 - 34 - 86 - 34 - 32 - 44 - 32 - 34 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 95 - 115 - 101 - 110 - 100 - 34 - 32 - 58 - 32 - 34 - 49 - 34 - 44 - 32 - 34 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 34 - 32 - 58 - 32 - 34 - 50 - 53 - 55 - 34 - 32 - 32 - 125 - 32 - 125 - 32 - 44 - 32 - 32 - 34 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 0 - 39011 - 232 - 72 - 0 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 6 - 0 - 11 - 4 - 6 - 0 - 2 - 0 - 12 - 4 - 8 - 0 - 6 - 0 - 13 - 4 - 14 - 0 - 2 - 0 - 14 - 4 - 16 - 0 - 2 - 0 - 15 - 4 - 18 - 0 - 2 - 0 - 20 - 4 - 20 - 0 - 1 - 0 - 21 - 1 - 21 - 0 - 1 - 0 - 5 - 1 - 1 - 1 - 1 - 184 - 1 - 200 - 5 - 2 - 2 - 2 - 2 - 6 - 1 - 6 - 1 - 1 - 1 - 2 - 0 - 0 - 60035 - 234 - 3 - 0 - 1 - 0 - 1 - 0 - 200 - 39004 - 236 - 155 - 0 - 1 - 0 - 1 - 9 - 10 - 4 - 0 - 0 - 3 - 0 - 11 - 4 - 3 - 0 - 1 - 0 - 12 - 4 - 4 - 0 - 3 - 0 - 13 - 4 - 7 - 0 - 1 - 0 - 14 - 4 - 8 - 0 - 2 - 0 - 15 - 4 - 10 - 0 - 2 - 0 - 20 - 4 - 12 - 0 - 43 - 0 - 21 - 1 - 55 - 0 - 1 - 0 - 30 - 4 - 56 - 0 - 43 - 0 - 2 - 200 - 1 - 0 - 2 - 6 - 2 - 0 - 1 - 1 - 1 - 1 - 42 - 1 - 4 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 - 10 - 0 - 1 - 200 - 1 - 6 - 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 - 0 - 42 - 83 - 119 - 105 - 116 - 99 - 104 - 105 - 110 - 103 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 97 - 83 - 111 - 99 - 107 - 101 - 116 - 46 - 85 - 68 - 80 - 83 - 111 - 99 - 107 - 101 - 116 - 95 - 83 - 104 - 79 - 98 - 106 - 40 - 237 - 0 - 1 - 1 - 0 - 60031 - 239 - 0 - 0 - 1 - 0 - 60305 - 241 - 263 - 0 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 1 - 0 - 11 - 4 - 1 - 0 - 2 - 0 - 12 - 4 - 3 - 0 - 1 - 0 - 13 - 4 - 4 - 0 - 2 - 0 - 14 - 4 - 6 - 0 - 2 - 0 - 15 - 4 - 8 - 0 - 2 - 0 - 20 - 4 - 10 - 0 - 202 - 0 - 21 - 1 - 212 - 0 - 1 - 0 - 0 - 1 - 184 - 0 - 1 - 6 - 1 - 1 - 1 - 2 - 201 - 1 - 2 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 185 - 0 - 1 - 184 - 184 - 114 - 115 - 67 - 111 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 48 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 78 - 97 - 109 - 101 - 34 - 32 - 58 - 32 - 34 - 79 - 115 - 99 - 105 - 108 - 108 - 97 - 116 - 111 - 114 - 32 - 105 - 110 - 112 - 117 - 116 - 34 - 32 - 44 - 32 - 34 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 34 - 32 - 58 - 32 - 34 - 49 - 34 - 44 - 32 - 34 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 34 - 32 - 58 - 32 - 34 - 50 - 53 - 55 - 34 - 32 - 32 - 125 - 32 - 44 - 32 - 34 - 49 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 80 - 97 - 114 - 97 - 109 - 101 - 116 - 101 - 114 - 78 - 97 - 109 - 101 - 34 - 32 - 58 - 32 - 34 - 111 - 107 - 34 - 32 - 44 - 32 - 34 - 78 - 86 - 97 - 108 - 117 - 101 - 115 - 34 - 32 - 58 - 32 - 34 - 49 - 34 - 44 - 32 - 34 - 100 - 97 - 116 - 97 - 116 - 121 - 112 - 101 - 34 - 32 - 58 - 32 - 34 - 50 - 53 - 55 - 34 - 32 - 32 - 125 - 32 - 125 - 44 - 32 - 34 - 80 - 97 - 80 - 73 - 67 - 111 - 0 - 39011 - 243 - 72 - 0 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 6 - 0 - 11 - 4 - 6 - 0 - 2 - 0 - 12 - 4 - 8 - 0 - 6 - 0 - 13 - 4 - 14 - 0 - 2 - 0 - 14 - 4 - 16 - 0 - 2 - 0 - 15 - 4 - 18 - 0 - 2 - 0 - 20 - 4 - 20 - 0 - 1 - 0 - 21 - 1 - 21 - 0 - 1 - 0 - 5 - 1 - 1 - 1 - 1 - 184 - 1 - 200 - 5 - 2 - 2 - 2 - 2 - 6 - 1 - 6 - 1 - 1 - 1 - 2 - 0 - 0 - 60035 - 245 - 3 - 0 - 1 - 0 - 1 - 0 - 200 - 39004 - 247 - 155 - 0 - 1 - 0 - 1 - 9 - 10 - 4 - 0 - 0 - 3 - 0 - 11 - 4 - 3 - 0 - 1 - 0 - 12 - 4 - 4 - 0 - 3 - 0 - 13 - 4 - 7 - 0 - 1 - 0 - 14 - 4 - 8 - 0 - 2 - 0 - 15 - 4 - 10 - 0 - 2 - 0 - 20 - 4 - 12 - 0 - 43 - 0 - 21 - 1 - 55 - 0 - 1 - 0 - 30 - 4 - 56 - 0 - 43 - 0 - 2 - 200 - 1 - 0 - 2 - 6 - 2 - 0 - 1 - 1 - 1 - 1 - 42 - 1 - 4 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 - 0 - 10 - 0 - 1 - 200 - 1 - 6 - 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 - 0 - 42 - 83 - 119 - 105 - 116 - 99 - 104 - 105 - 110 - 103 - 65 - 117 - 116 - 111 - 67 - 111 - 110 - 102 - 105 - 103 - 97 - 83 - 111 - 99 - 107 - 101 - 116 - 46 - 85 - 68 - 80 - 83 - 111 - 99 - 107 - 101 - 116 - 95 - 83 - 104 - 79 - 98 - 106 - 40 - 248 - 0 - 1 - 1 - 0 - 60031 - 250 - 0 - 0 - 1 - 0 - 60305 - 252 - 263 - 0 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 1 - 0 - 11 - 4 - 1 - 0 - 2 - 0 - 12 - 4 - 3 - 0 - 1 - 0 - 13 - 4 - 4 - 0 - 2 - 0 - 14 - 4 - 6 - 0 - 2 - 0 - 15 - 4 - 8 - 0 - 2 - 0 - 20 - 4 - 10 - 0 - 202 - 0 - 21 - 1 - 212 - 0 - 1 - 0 - 0 - 1 - 184 - 0 - 1 - 6 - 1 - 1 - 1 - 2 - 201 - 1 - 2 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 185 - 0 - 1 - 184 - 184 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 84 - 111 - 67 - 114 - 101 - 97 - 116 - 101 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 80 - 108 - 117 - 103 - 105 - 110 - 49 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 105 - 100 - 101 - 110 - 116 - 105 - 102 - 105 - 101 - 114 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 66 - 117 - 116 - 116 - 111 - 110 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 99 - 111 - 110 - 102 - 105 - 103 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 110 - 97 - 109 - 101 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 68 - 105 - 115 - 116 - 117 - 114 - 98 - 97 - 110 - 99 - 101 - 103 - 114 - 103 - 114 - 103 - 114 - 114 - 103 - 114 - 103 - 114 - 103 - 114 - 103 - 114 - 103 - 103 - 101 - 103 - 101 - 114 - 103 - 103 - 101 - 114 - 101 - 103 - 101 - 103 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 115 - 105 - 122 - 101 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 0 - 39011 - 254 - 72 - 0 - 1 - 0 - 1 - 8 - 10 - 4 - 0 - 0 - 6 - 0 - 11 - 4 - 6 - 0 - 2 - 0 - 12 - 4 - 8 - 0 - 6 - 0 - 13 - 4 - 14 - 0 - 2 - 0 - 14 - 4 - 16 - 0 - 2 - 0 - 15 - 4 - 18 - 0 - 2 - 0 - 20 - 4 - 20 - 0 - 1 - 0 - 21 - 1 - 21 - 0 - 1 - 0 - 5 - 1 - 1 - 1 - 1 - 184 - 1 - 200 - 5 - 2 - 2 - 2 - 2 - 6 - 1 - 6 - 1 - 1 - 1 - 2 - 0 - 0 - 60035 - 256 - 3 - 0 - 1 - 0 - 1 - 0 - 200 - 39004 - 258 - 155 - 0 - 1 - 0 - 1 - 9 - 10 - 4 - 0 - 0 - 3 - 0 - 11 - 4 - 3 - 0 - 1 - 0 - 12 - 4 - 4 - 0 - 3 - 0 - 13 - 4 - 7 - 0 - 1 - 0 - 14 - 4 - 8 - 0 - 2 - 0 - 15 - 4 + 0 + 291 + 100 + 3067 10 + 161 0 - 2 + 100 + 101 + 3228 + 10 + 452 0 - 20 - 4 - 12 + 60032 + 205 0 - 43 0 - 21 1 - 55 + 0 + 60032 + 207 + 0 0 1 0 - 30 - 4 - 56 + 60032 + 209 0 - 43 0 - 2 - 200 1 0 - 2 - 6 - 2 + 40 + 211 0 1 1 - 1 - 1 - 42 - 1 - 4 - 10 - 4 - 0 - 0 - 2 - 0 - 11 - 4 - 2 - 0 - 2 - 0 - 12 - 4 - 4 - 0 - 2 - 0 - 13 - 4 - 6 0 - 10 + 170 + 213 + 33 0 1 - 200 - 1 - 6 - 1 - 20000 - 9 - 49 - 50 - 55 - 46 - 48 - 46 - 48 - 46 - 49 0 - 42 - 83 - 119 - 105 - 116 - 99 - 104 + 1 + 31 + 71 105 - 110 - 103 - 65 - 117 - 116 - 111 + 118 + 101 + 32 67 111 110 102 105 103 - 97 + 32 + 40 83 111 + 117 + 114 99 - 107 101 - 116 - 46 - 85 + 73 68 - 80 - 83 - 111 - 99 - 107 - 101 - 116 - 95 - 83 - 104 - 79 - 98 - 106 + 41 + 32 + 32 + 32 + 32 + 32 + 32 + 32 + 61 + 32 40 - 259 + 214 0 1 1 0 60031 - 261 + 216 + 0 + 0 + 1 + 0 + 40 + 218 + 0 + 1 + 1 + 0 + 60031 + 220 + 0 + 0 + 1 + 0 + 40 + 222 + 0 + 1 + 1 + 0 + 60031 + 224 + 0 + 0 + 1 + 0 + 40 + 226 + 0 + 1 + 1 + 0 + 60031 + 228 0 0 1 0 60305 - 263 + 230 263 0 1 @@ -4756,154 +2216,133 @@ 1 184 184 - 40 - 49 - 53 - 48 - 44 - 53 - 48 - 41 - 34 - 32 - 125 - 32 - 32 - 44 32 + 123 34 - 112 + 83 111 + 117 + 114 + 99 + 101 115 - 105 - 116 - 105 + 67 111 110 + 102 + 105 + 103 34 32 58 32 123 + 125 + 32 + 44 + 32 32 34 - 118 + 80 97 - 108 - 117 + 114 + 97 + 109 + 101 + 116 101 + 114 + 115 + 67 + 111 + 110 + 102 + 105 + 103 34 32 58 32 + 123 + 32 34 - 40 - 54 - 48 - 48 - 44 - 49 - 48 48 - 41 34 32 - 125 - 32 + 58 32 - 44 + 123 32 34 + 80 + 97 + 114 + 97 + 109 + 101 116 + 101 + 114 + 78 97 - 98 + 109 + 101 34 32 58 32 - 123 + 34 + 79 + 115 + 99 + 105 + 34 + 32 + 44 32 34 - 118 + 78 + 86 97 108 117 101 + 115 34 32 58 32 34 - 80 - 97 - 80 - 73 - 45 - 84 - 97 - 98 + 49 34 - 32 - 125 - 32 - 32 44 32 34 - 115 - 116 + 100 97 116 - 101 - 49 - 95 + 97 116 + 121 + 112 101 - 120 - 116 34 32 58 32 - 123 - 32 34 - 118 - 97 - 108 - 117 - 101 + 50 + 53 + 55 34 32 - 58 - 32 - 34 - 68 - 105 - 115 - 116 - 117 - 114 - 98 - 34 32 125 - 32 32 44 32 34 - 115 - 116 - 97 - 116 - 101 - 50 - 95 - 116 - 101 - 120 - 116 + 49 34 32 58 @@ -4911,38 +2350,59 @@ 123 32 34 - 118 + 80 97 - 108 - 117 + 114 + 97 + 109 + 101 + 116 + 101 + 114 + 78 + 97 + 109 101 34 32 58 32 34 - 68 + 83 + 108 105 - 115 - 116 - 117 + 100 + 101 114 - 98 - 105 - 110 + 76 + 67 + 68 + 80 + 114 + 111 103 + 114 + 101 + 115 + 115 34 32 - 125 - 32 - 32 - 125 + 44 32 + 34 + 78 + 86 + 97 + 108 + 117 + 101 + 115 + 34 32 - 125 + 58 0 39011 - 265 + 232 72 0 1 @@ -5020,7 +2480,7 @@ 0 0 60035 - 267 + 234 3 0 1 @@ -5029,7 +2489,7 @@ 0 200 39004 - 269 + 236 155 0 1 @@ -5190,19 +2650,19 @@ 98 106 40 - 270 + 237 0 1 1 0 60031 - 272 + 239 0 0 1 0 60305 - 274 + 241 263 0 1 @@ -5286,17 +2746,76 @@ 184 184 32 + 34 + 49 + 34 + 44 + 32 + 34 + 100 + 97 + 116 + 97 + 116 + 121 + 112 + 101 + 34 + 32 + 58 + 32 + 34 + 50 + 53 + 55 + 34 + 32 + 32 + 125 32 + 125 44 32 34 80 + 97 + 80 + 73 + 67 + 111 + 110 + 102 + 105 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 84 + 111 + 67 + 114 + 101 + 97 + 116 + 101 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 80 108 117 103 105 110 - 50 + 49 34 32 58 @@ -5379,43 +2898,16 @@ 58 32 34 - 76 - 101 + 79 + 115 + 99 + 105 + 108 + 108 97 - 118 - 101 - 101 - 114 - 103 - 101 - 114 - 103 - 101 - 114 - 103 - 101 - 114 - 103 - 101 - 114 - 103 - 103 - 101 - 103 - 114 - 103 - 103 - 114 - 101 - 103 - 101 - 103 + 116 + 111 114 - 103 - 101 - 101 - 103 - 103 34 32 125 @@ -5427,51 +2919,19 @@ 115 105 122 - 101 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 - 32 - 58 - 32 - 34 - 40 - 49 - 53 - 48 - 44 - 53 - 48 - 41 + 101 34 32 - 125 - 32 + 58 32 - 44 + 123 32 34 - 112 - 111 - 115 - 105 - 116 - 105 - 111 - 110 + 118 + 97 0 39011 - 276 + 243 72 0 1 @@ -5549,7 +3009,7 @@ 0 0 60035 - 278 + 245 3 0 1 @@ -5558,7 +3018,7 @@ 0 200 39004 - 280 + 247 155 0 1 @@ -5719,19 +3179,19 @@ 98 106 40 - 281 + 248 0 1 1 0 60031 - 283 + 250 0 0 1 0 60305 - 285 + 252 263 0 1 @@ -5814,6 +3274,39 @@ 1 184 184 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 50 + 53 + 48 + 44 + 49 + 48 + 48 + 41 + 34 + 32 + 125 + 32 + 32 + 44 + 32 + 34 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 34 32 58 @@ -5832,13 +3325,13 @@ 32 34 40 - 54 - 48 - 48 - 44 - 51 50 53 + 48 + 44 + 49 + 48 + 48 41 34 32 @@ -5912,8 +3405,11 @@ 58 32 34 - 79 - 107 + 71 + 111 + 32 + 116 + 111 34 32 125 @@ -5957,50 +3453,14 @@ 105 110 103 - 34 - 32 - 125 - 32 - 32 - 125 - 32 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 80 - 108 - 117 - 103 - 105 - 110 - 51 - 34 - 32 - 58 32 - 123 - 32 - 34 - 105 - 100 - 101 - 110 116 - 105 - 102 - 105 - 101 - 114 + 111 34 32 - 58 0 39011 - 287 + 254 72 0 1 @@ -6078,7 +3538,7 @@ 0 0 60035 - 289 + 256 3 0 1 @@ -6087,7 +3547,7 @@ 0 200 39004 - 291 + 258 155 0 1 @@ -6248,19 +3708,19 @@ 98 106 40 - 292 + 259 0 1 1 0 60031 - 294 + 261 0 0 1 0 60305 - 296 + 263 263 0 1 @@ -6343,25 +3803,11 @@ 1 184 184 + 125 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 - 34 32 - 58 + 125 32 - 34 - 80 - 108 - 111 - 116 - 34 32 125 32 @@ -6369,12 +3815,13 @@ 44 32 34 - 99 - 111 - 110 - 102 - 105 + 80 + 108 + 117 103 + 105 + 110 + 50 34 32 58 @@ -6382,10 +3829,16 @@ 123 32 34 + 105 + 100 + 101 110 - 97 - 109 + 116 + 105 + 102 + 105 101 + 114 34 32 58 @@ -6403,13 +3856,12 @@ 58 32 34 - 80 - 108 - 111 + 66 + 117 116 - 32 - 88 - 86 + 116 + 111 + 110 34 32 125 @@ -6418,9 +3870,22 @@ 44 32 34 - 115 + 99 + 111 + 110 + 102 105 - 122 + 103 + 34 + 32 + 58 + 32 + 123 + 32 + 34 + 110 + 97 + 109 101 34 32 @@ -6439,15 +3904,28 @@ 58 32 34 - 40 - 53 - 48 - 48 - 44 - 53 - 48 - 48 - 41 + 83 + 108 + 105 + 100 + 101 + 114 + 32 + 76 + 67 + 68 + 32 + 80 + 114 + 111 + 103 + 114 + 101 + 115 + 115 + 66 + 97 + 114 34 32 125 @@ -6456,14 +3934,10 @@ 44 32 34 - 112 - 111 115 105 - 116 - 105 - 111 - 110 + 122 + 101 34 32 58 @@ -6482,54 +3956,40 @@ 32 34 40 + 50 + 53 48 - 44 - 48 - 41 - 34 - 32 - 125 - 32 - 32 - 44 - 32 - 34 - 116 - 97 - 98 - 34 - 32 - 58 - 32 - 123 - 32 - 34 - 118 - 97 - 108 - 117 - 101 + 44 + 49 + 48 + 48 + 41 34 32 - 58 + 125 + 32 + 32 + 44 32 34 - 80 - 97 - 80 - 73 - 45 - 84 - 97 - 98 + 112 + 111 + 115 + 105 + 116 + 105 + 111 + 110 34 32 - 125 + 58 32 + 123 32 + 34 0 39011 - 298 + 265 72 0 1 @@ -6607,7 +4067,7 @@ 0 0 60035 - 300 + 267 3 0 1 @@ -6616,7 +4076,7 @@ 0 200 39004 - 302 + 269 155 0 1 @@ -6777,19 +4237,19 @@ 98 106 40 - 303 + 270 0 1 1 0 60031 - 305 + 272 0 0 1 0 60305 - 307 + 274 263 0 1 @@ -6872,15 +4332,36 @@ 1 184 184 + 118 + 97 + 108 + 117 + 101 + 34 + 32 + 58 + 32 + 34 + 40 + 50 + 53 + 48 + 44 + 50 + 53 + 48 + 41 + 34 + 32 + 125 + 32 + 32 44 32 34 - 121 - 82 + 116 97 - 110 - 103 - 101 + 98 34 32 58 @@ -6898,44 +4379,33 @@ 58 32 34 - 91 + 80 + 97 + 80 + 73 45 - 49 - 48 - 46 - 48 - 32 - 49 - 48 - 46 - 48 - 93 + 84 + 97 + 98 34 32 - 125 - 32 - 32 - 125 - 32 - 32 - 125 - 32 - 32 125 32 32 44 32 34 - 84 - 111 - 67 - 111 - 110 + 115 + 116 + 97 + 116 + 101 + 49 + 95 + 116 + 101 + 120 116 - 114 - 111 - 108 34 32 58 @@ -6943,122 +4413,112 @@ 123 32 34 - 80 + 118 + 97 108 117 - 103 - 105 - 110 - 49 + 101 34 32 58 32 - 123 - 32 34 - 98 - 108 + 71 + 111 + 32 + 116 111 - 99 - 107 34 32 - 58 + 125 + 32 + 32 + 44 32 34 - 67 - 108 - 105 - 99 - 107 + 115 + 116 + 97 + 116 + 101 + 50 95 - 69 - 118 + 116 101 - 110 + 120 116 34 32 - 44 + 58 + 32 + 123 32 34 - 112 - 97 - 114 + 118 97 - 109 - 101 - 116 + 108 + 117 101 - 114 34 32 58 32 34 - 79 - 115 - 99 - 105 - 108 - 108 + 76 + 101 97 - 116 - 111 - 114 - 32 + 118 105 110 - 112 - 117 + 103 + 32 116 + 111 34 32 125 32 32 - 44 + 125 32 - 34 - 80 - 108 - 117 - 103 - 105 - 110 - 50 - 34 32 - 58 + 125 32 - 123 + 32 + 125 + 32 + 32 + 44 32 34 - 98 - 108 + 84 111 - 99 - 107 + 67 + 111 + 110 + 116 + 114 + 111 + 108 34 32 58 32 + 123 + 32 34 - 67 + 80 108 + 117 + 103 105 - 99 - 107 - 95 - 69 - 118 - 101 110 - 116 + 49 + 34 + 32 0 39011 - 309 + 276 72 0 1 @@ -7136,7 +4596,7 @@ 0 0 60035 - 311 + 278 3 0 1 @@ -7145,7 +4605,7 @@ 0 200 39004 - 313 + 280 155 0 1 @@ -7306,20 +4766,20 @@ 98 106 40 - 314 + 281 0 1 1 0 60031 - 316 + 283 0 0 1 0 60305 - 318 - 194 + 285 + 218 0 1 0 @@ -7365,17 +4825,17 @@ 4 10 0 - 133 + 157 0 21 1 - 143 + 167 0 1 0 0 1 - 115 + 139 0 1 6 @@ -7383,7 +4843,7 @@ 1 1 2 - 132 + 156 1 2 10 @@ -7396,11 +4856,37 @@ 4 2 0 + 140 + 0 + 1 + 139 + 139 + 58 + 32 + 123 + 32 + 34 + 98 + 108 + 111 + 99 + 107 + 34 + 32 + 58 + 32 + 34 + 67 + 108 + 105 + 99 + 107 + 95 + 69 + 118 + 101 + 110 116 - 0 - 1 - 115 - 115 34 32 44 @@ -7420,29 +4906,16 @@ 58 32 34 - 111 - 107 + 79 + 115 + 99 + 105 34 32 - 125 - 32 - 32 125 32 32 44 - 32 - 34 - 84 - 111 - 83 - 117 - 98 - 34 - 32 - 58 - 32 - 123 32 34 80 @@ -7451,7 +4924,7 @@ 103 105 110 - 51 + 50 34 32 58 @@ -7459,52 +4932,63 @@ 123 32 34 - 115 - 105 - 103 - 110 - 97 + 98 108 - 115 + 111 + 99 + 107 34 32 58 32 - 91 - 34 - 88 - 34 - 44 34 - 86 + 67 + 108 + 105 + 99 + 107 + 95 + 69 + 118 + 101 + 110 + 116 34 - 93 32 44 32 34 - 98 - 108 - 111 - 99 - 107 + 112 + 97 + 114 + 97 + 109 + 101 + 116 + 101 + 114 34 32 58 32 34 83 - 111 - 117 - 114 - 99 + 108 + 105 + 100 101 - 71 + 114 + 76 + 67 + 68 + 80 114 111 - 117 - 112 - 48 + 103 + 114 + 101 + 115 + 115 34 32 125 @@ -7518,7 +5002,7 @@ 125 0 39011 - 320 + 287 72 0 1 @@ -7578,9 +5062,9 @@ 1 1 1 - 115 + 139 1 - 131 + 155 5 2 2 @@ -7596,16 +5080,16 @@ 0 0 60035 - 322 + 289 3 0 1 0 1 0 - 131 + 155 39004 - 324 + 291 155 0 1 @@ -7667,7 +5151,7 @@ 43 0 2 - 131 + 155 1 0 2 @@ -7706,7 +5190,7 @@ 10 0 1 - 131 + 155 1 6 1 @@ -7766,7 +5250,7 @@ 98 106 0 - 80 + 56 8 4 1 @@ -8135,271 +5619,79 @@ 0 0 280 - 280 - 0 - 0 - 278 - 278 - 0 - 0 - 280 - 280 - 1 - 0 - 281 - 281 - 0 - 0 - 283 - 283 - 0 - 0 - 224 - 224 - 0 - 0 - 287 - 287 - 0 - 0 - 283 - 283 - 0 - 0 - 287 - 287 - 1 - 0 - 216 - 216 - 0 - 0 - 287 - 287 - 2 - 0 - 220 - 220 - 0 - 0 - 287 - 287 - 3 - 0 - 285 - 285 - 0 - 0 - 287 - 287 - 4 - 0 - 287 - 287 - 0 - 0 - 291 - 291 - 0 - 0 - 289 - 289 - 0 - 0 - 291 - 291 - 1 - 0 - 292 - 292 - 0 - 0 - 294 - 294 - 0 - 0 - 224 - 224 - 0 - 0 - 298 - 298 - 0 - 0 - 294 - 294 - 0 - 0 - 298 - 298 - 1 - 0 - 216 - 216 - 0 - 0 - 298 - 298 - 2 - 0 - 220 - 220 - 0 - 0 - 298 - 298 - 3 - 0 - 296 - 296 - 0 - 0 - 298 - 298 - 4 - 0 - 298 - 298 - 0 - 0 - 302 - 302 - 0 - 0 - 300 - 300 - 0 - 0 - 302 - 302 - 1 - 0 - 303 - 303 - 0 - 0 - 305 - 305 - 0 - 0 - 224 - 224 - 0 - 0 - 309 - 309 - 0 - 0 - 305 - 305 - 0 - 0 - 309 - 309 - 1 - 0 - 216 - 216 - 0 - 0 - 309 - 309 - 2 - 0 - 220 - 220 - 0 - 0 - 309 - 309 - 3 - 0 - 307 - 307 - 0 - 0 - 309 - 309 - 4 - 0 - 309 - 309 - 0 - 0 - 313 - 313 + 280 0 0 - 311 - 311 + 278 + 278 0 0 - 313 - 313 + 280 + 280 1 0 - 314 - 314 + 281 + 281 0 0 - 316 - 316 + 283 + 283 0 0 224 224 0 0 - 320 - 320 + 287 + 287 0 0 - 316 - 316 + 283 + 283 0 0 - 320 - 320 + 287 + 287 1 0 216 216 0 0 - 320 - 320 + 287 + 287 2 0 220 220 0 0 - 320 - 320 + 287 + 287 3 0 - 318 - 318 + 285 + 285 0 0 - 320 - 320 + 287 + 287 4 0 - 320 - 320 + 287 + 287 0 0 - 324 - 324 + 291 + 291 0 0 - 322 - 322 + 289 + 289 0 0 - 324 - 324 + 291 + 291 1 0 211 @@ -8554,7 +5846,7 @@ 0 0 0 - 15 + 12 0 22 8 @@ -8735,15 +6027,15 @@ 232 232 4 - 21 + 18 40 - 279 + 222 0 1 1 0 60005 - 281 + 224 2 0 1 @@ -8751,49 +6043,49 @@ 0 100000 60031 - 283 + 226 0 0 1 0 40 - 285 + 228 0 1 1 0 60031 - 287 + 230 0 0 1 0 40 - 289 + 232 0 1 1 0 60031 - 291 + 234 0 0 1 0 40 - 293 + 236 0 1 1 0 60031 - 295 + 238 0 0 1 0 39011 - 297 + 240 70 0 1 @@ -8869,7 +6161,7 @@ 0 0 60035 - 299 + 242 3 0 1 @@ -8878,7 +6170,7 @@ 0 16 39004 - 301 + 244 155 0 1 @@ -9039,31 +6331,31 @@ 98 106 60023 - 302 + 245 0 0 1 0 40 - 304 + 247 0 1 1 0 12 - 305 + 248 0 2 1 0 60034 - 307 + 250 0 0 1 0 15013 - 309 + 252 682 6 1 @@ -9750,437 +7042,364 @@ 0 0 6 - 0 - 54 - 8 - 4 - 0 - 0 - 0 + 12 + 254 0 2 - 204 - 204 - 0 1 0 + 15006 + 256 + 31 0 - 0 - 0 - 209 - 209 - 0 - 0 - 210 - 210 - 0 - 0 - 212 - 212 - 0 - 0 - 214 - 214 - 0 - 0 - 216 - 216 - 0 - 0 - 212 - 212 - 0 - 0 - 218 - 218 - 0 - 0 - 212 - 212 - 0 - 0 - 220 - 220 - 0 - 0 - 219 - 219 - 0 - 0 - 220 - 220 1 0 - 220 - 220 - 0 - 0 - 224 - 224 - 0 - 0 - 224 - 224 - 0 - 0 - 226 - 226 - 0 - 0 - 226 - 226 - 0 - 0 - 228 - 228 - 0 - 0 - 228 - 228 - 0 - 0 - 230 - 230 - 0 - 0 - 230 - 230 - 0 0 - 226 - 226 + 257 1 0 - 228 - 228 - 0 - 0 - 232 - 232 0 0 - 232 - 232 0 0 - 234 - 234 0 0 - 234 - 234 + 20 + 78 + 101 + 120 + 116 + 83 + 116 + 97 + 116 + 101 + 68 + 97 + 116 + 97 + 46 + 109 + 101 + 109 + 111 + 114 + 121 + 60022 + 257 0 0 - 224 - 224 1 0 - 236 - 236 - 0 - 0 - 238 - 238 - 0 - 0 - 238 - 238 - 0 + 170 + 259 + 72 0 - 240 - 240 + 1 0 + 1 + 70 + 67 + 97 + 115 + 101 + 32 + 99 + 104 + 111 + 111 + 115 + 101 + 32 + 101 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 97 + 99 + 116 + 105 + 118 + 101 + 46 + 32 + 78 + 101 + 120 + 116 + 32 + 69 + 120 + 112 + 101 + 114 + 105 + 109 + 101 + 110 + 116 + 32 + 105 + 115 + 32 + 40 + 48 + 32 + 109 + 101 + 97 + 110 + 115 + 32 + 110 + 111 + 32 + 99 + 104 + 97 + 110 + 103 + 101 + 41 + 58 0 - 242 - 242 + 29 + 8 + 4 0 0 - 244 - 244 0 0 - 246 - 246 + 2 + 204 + 204 0 0 - 248 - 248 + 209 + 209 0 0 - 248 - 248 + 211 + 211 0 0 - 250 - 250 + 213 + 213 0 0 - 240 - 240 + 215 + 215 0 0 - 250 - 250 - 1 0 - 244 - 244 0 0 - 250 - 250 2 - 0 - 232 - 232 + 217 + 217 0 0 - 250 - 250 - 3 + 218 + 218 0 - 250 - 250 0 + 220 + 220 0 - 254 - 254 0 + 222 + 222 0 - 252 - 252 0 + 224 + 224 0 - 254 - 254 - 1 0 - 255 - 255 + 224 + 224 0 0 - 257 - 257 + 226 + 226 0 0 - 257 - 257 + 228 + 228 0 0 - 259 - 259 + 230 + 230 0 0 - 261 - 261 + 232 + 232 0 0 - 263 - 263 + 234 + 234 0 0 - 265 - 265 + 236 + 236 0 0 - 267 - 267 + 238 + 238 0 0 - 267 - 267 + 238 + 238 0 0 - 269 - 269 + 240 + 240 0 0 - 259 - 259 + 226 + 226 0 0 - 269 - 269 + 240 + 240 1 0 - 263 - 263 + 230 + 230 0 0 - 269 - 269 + 240 + 240 2 0 - 228 - 228 + 234 + 234 0 0 - 269 - 269 + 240 + 240 3 0 - 269 - 269 + 240 + 240 0 0 - 273 - 273 + 244 + 244 0 0 - 271 - 271 + 242 + 242 0 0 - 273 - 273 + 244 + 244 1 0 + 245 + 245 0 0 + 248 + 248 0 - 2 - 274 - 274 - 0 - 0 - 275 - 275 - 0 - 0 - 277 - 277 - 0 - 0 - 279 - 279 - 0 - 0 - 281 - 281 - 0 - 0 - 281 - 281 - 0 - 0 - 283 - 283 - 0 - 0 - 285 - 285 0 + 247 + 247 0 - 287 - 287 0 + 248 + 248 + 1 0 - 289 - 289 + 248 + 248 0 0 - 291 - 291 + 250 + 250 0 0 - 293 - 293 + 250 + 250 0 0 - 295 - 295 + 252 + 252 0 0 - 295 - 295 + 211 + 211 0 0 - 297 - 297 + 254 + 254 0 0 - 283 - 283 + 215 + 215 0 0 - 297 - 297 + 254 + 254 1 0 - 287 - 287 - 0 - 0 - 297 - 297 - 2 - 0 - 291 - 291 - 0 - 0 - 297 - 297 - 3 - 0 - 297 - 297 + 254 + 254 0 0 - 301 - 301 + 256 + 256 0 0 - 299 - 299 + 207 + 207 0 0 - 301 - 301 + 256 + 256 1 0 - 302 - 302 + 254 + 254 0 0 - 305 - 305 + 257 + 257 0 0 - 304 - 304 + 207 + 207 0 0 - 305 - 305 + 257 + 257 1 0 - 305 - 305 - 0 - 0 - 307 - 307 - 0 - 0 - 307 - 307 + 254 + 254 0 0 - 309 - 309 + 259 + 259 0 0 - 232 - 232 + 254 + 254 0 1 0 0 0 0 - 216 - 216 + 257 + 257 0 1 0 diff --git a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar index 2db80e99..867165fc 100644 --- a/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar +++ b/data_sources/ORTD/DataSourceChangingAutoConfigExample/AutoConfigDemo_ReplaceableSimulation.rpar @@ -3,27 +3,6 @@ 1.0000000000000000000000000 1.0000000000000000000000000 2.0000000000000000000000000 -0.2000000000000000111022302 -1.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -1.0000000000000000000000000 --1.0000000000000000000000000 -0.1000000000000000055511151 --1.0000000000000000000000000 -1.0000000000000000000000000 -0.1000000000000000055511151 -0.1000000000000000055511151 --1.0000000000000000000000000 -1.0000000000000000000000000 -0.5999999999999999777955395 -1.0000000000000000000000000 -0.0000000000000000000000000 -1295793.0000000000000000000000000 -1.0000000000000000000000000 -1.0000000000000000000000000 -1295793.0000000000000000000000000 0.0000000000000000000000000 0.0000000000000000000000000 1.0000000000000000000000000 @@ -36,7 +15,7 @@ 9999.0000000000000000000000000 9999.0000000000000000000000000 -4.0000000000000000000000000 -9.0000000000000000000000000 +6.0000000000000000000000000 1295793.0000000000000000000000000 1.0000000000000000000000000 2.0000000000000000000000000 @@ -44,9 +23,6 @@ 4.0000000000000000000000000 5.0000000000000000000000000 6.0000000000000000000000000 -7.0000000000000000000000000 -8.0000000000000000000000000 -9.0000000000000000000000000 9999.0000000000000000000000000 1.0000000000000000000000000 -1.0000000000000000000000000 @@ -61,3 +37,5 @@ -2.0000000000000000000000000 0.0000000000000000000000000 1295793.0000000000000000000000000 +1.0000000000000000000000000 +2.0000000000000000000000000 diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py index 0226f01b..d6468765 100644 --- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py +++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py @@ -63,6 +63,8 @@ import time import pickle +from threading import Timer + class OptionalObject(object): def __init__(self, ORTD_par_id, nvalues): self.ORTD_par_id = ORTD_par_id @@ -144,7 +146,11 @@ def start_init(self, config=None): self.block_id = 0 - self.json_config_file = '' + self.config_complete = False + self.config_buffer = {} + + self.timer = Timer(3,self.callback_timeout_timer) + self.timer_active = False return True @@ -161,8 +167,7 @@ def resume(self): def thread_execute(self): time.sleep(1) - data = struct.pack(' Date: Tue, 31 Mar 2015 16:04:16 +0200 Subject: [PATCH 218/260] StaticPlot: finalized context menu --- papi/plugin/visual/StaticPlot/StaticPlot.py | 57 +++------------------ 1 file changed, 6 insertions(+), 51 deletions(-) diff --git a/papi/plugin/visual/StaticPlot/StaticPlot.py b/papi/plugin/visual/StaticPlot/StaticPlot.py index 017bda0a..eaeab1af 100644 --- a/papi/plugin/visual/StaticPlot/StaticPlot.py +++ b/papi/plugin/visual/StaticPlot/StaticPlot.py @@ -176,6 +176,7 @@ def initiate_layer_0(self, config=None): self.__parameters__['yRange'] = \ DParameter('yRange', '[0,1]', Regex='^\[(\d+\.\d+)\s+(\d+\.\d+)\]$') + self.send_new_parameter_list(list(self.__parameters__.values())) # --------------------------- # Create Legend @@ -228,6 +229,7 @@ def set_parameter(self, name, value): :param value: :return: """ + print(value) if name == 'x-grid': self.config['x-grid']['value'] = value self.__plotWidget__.showGrid(x=value == '1') @@ -353,10 +355,8 @@ def use_range_for_x(self, value): """ reg = re.compile(r'(\d+\.\d+)') range = reg.findall(value) - print(value) + if len(range) == 2: - #self.xRange_minEdit.setText(range[0]) - #self.xRange_maxEdit.setText(range[1]) self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]),float(range[1])) def use_range_for_y(self, value): @@ -369,8 +369,6 @@ def use_range_for_y(self, value): range = reg.findall(value) if len(range) == 2: - self.yRange_minEdit.setText(range[0]) - self.yRange_maxEdit.setText(range[1]) self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]), float(range[1])) def setup_context_menu(self): @@ -380,26 +378,8 @@ def setup_context_menu(self): """ self.custMenu = QtGui.QMenu("Options") - self.axesMenu = QtGui.QMenu('Y-Axis') self.gridMenu = QtGui.QMenu('Grid') - ##### Y-Range Actions - self.yRange_Widget = QtGui.QWidget() - self.yRange_Layout = QtGui.QVBoxLayout(self.yRange_Widget) - self.yRange_Layout.setContentsMargins(2, 2, 2, 2) - self.yRange_Layout.setSpacing(1) - - self.yAutoRangeButton = QtGui.QPushButton() - self.yAutoRangeButton.clicked.connect(self.contextMenu_yAutoRangeButton_clicked) - self.yAutoRangeButton.setText('Set y range') - self.yRange_Layout.addWidget(self.yAutoRangeButton) - - ##### Y Line Edits - # Layout - self.yRange_EditWidget = QtGui.QWidget() - self.yRange_EditLayout = QtGui.QHBoxLayout(self.yRange_EditWidget) - self.yRange_EditLayout.setContentsMargins(2, 2, 2, 2) - self.yRange_EditLayout.setSpacing(1) # get old values; reg = re.compile(r'(\d+\.\d+)') @@ -414,33 +394,8 @@ def setup_context_menu(self): rx = QRegExp(r'([-]{0,1}\d+\.\d+)') validator = QRegExpValidator(rx, self.__plotWidget__) - # Min - self.yRange_minEdit = QtGui.QLineEdit() - self.yRange_minEdit.setFixedWidth(80) - self.yRange_minEdit.setText(y_min) - self.yRange_minEdit.editingFinished.connect(self.contextMenu_yRange_toogle) - self.yRange_minEdit.setValidator(validator) - # Max - self.yRange_maxEdit = QtGui.QLineEdit() - self.yRange_maxEdit.setFixedWidth(80) - self.yRange_maxEdit.setText(y_max) - self.yRange_maxEdit.editingFinished.connect(self.contextMenu_yRange_toogle) - self.yRange_maxEdit.setValidator(validator) - # addTo Layout - self.yRange_EditLayout.addWidget(self.yRange_minEdit) - self.yRange_EditLayout.addWidget(QtGui.QLabel('<')) - self.yRange_EditLayout.addWidget(self.yRange_maxEdit) - self.yRange_Layout.addWidget(self.yRange_EditWidget) - - # build Action - self.yRange_Action = QtGui.QWidgetAction(self.__plotWidget__) - self.yRange_Action.setDefaultWidget(self.yRange_Widget) - - - ##### Build axes menu - #self.axesMenu.addAction(self.xRange_Action) - self.axesMenu.addSeparator().setText("Y-Range") - self.axesMenu.addAction(self.yRange_Action) + + # Grid Menu: # ----------------------------------------------------------- @@ -467,7 +422,7 @@ def setup_context_menu(self): self.yGrid_Checkbox.setChecked(True) # add Menus - self.custMenu.addMenu(self.axesMenu) + self.custMenu.addMenu(self.gridMenu) self.custMenu.addSeparator().setText("Rolling Plot") From ce16b8a7a5252c2b2a7dd1107c6191f8cb936321 Mon Sep 17 00:00:00 2001 From: Sven Knuth Date: Tue, 31 Mar 2015 16:54:22 +0200 Subject: [PATCH 219/260] StaticPlot: add legend FLAG, --- papi/constants.py | 8 +- papi/gui/gui_event_processing.py | 3 +- papi/plugin/visual/StaticPlot/StaticPlot.py | 144 +++++--------------- 3 files changed, 42 insertions(+), 113 deletions(-) diff --git a/papi/constants.py b/papi/constants.py index 474b04fd..91b0c950 100644 --- a/papi/constants.py +++ b/papi/constants.py @@ -131,4 +131,10 @@ REGEX_BOOL_BIN = '^(1|0)$' REGEX_SINGLE_UNSIGNED_FLOAT_FORCED = '(\d+\.\d+)' REGEX_SIGNED_FLOAT = r'([-]{0,1}\d+\.\d+)' -REGEX_SIGNED_FLOAT_OR_INT = r'([-]{0,1}\d+(.\d+)?)' \ No newline at end of file +REGEX_SIGNED_FLOAT_OR_INT = r'([-]{0,1}\d+(.\d+)?)' + +# CONFIGURATION TYPE Collection + +CFG_TYPE_FILE = 'file' +CFG_TYPE_COLOR = 'color' +CFG_TYPE_BOOL = 'bool' \ No newline at end of file diff --git a/papi/gui/gui_event_processing.py b/papi/gui/gui_event_processing.py index 30312daf..aae2fe10 100644 --- a/papi/gui/gui_event_processing.py +++ b/papi/gui/gui_event_processing.py @@ -165,8 +165,7 @@ def process_new_data_event(self, event): if opt.is_parameter is False: dplugin.plugin.execute( Data=dplugin.plugin.demux(opt.data_source_id, opt.block_name, opt.data), - block_name=opt.block_name, - plugin_uname = event.source_plugin_uname) + block_name=opt.block_name, plugin_uname=event.source_plugin_uname) else: dplugin.plugin.set_parameter_internal(opt.parameter_alias, opt.data) except Exception as E: diff --git a/papi/plugin/visual/StaticPlot/StaticPlot.py b/papi/plugin/visual/StaticPlot/StaticPlot.py index eaeab1af..e79d906a 100644 --- a/papi/plugin/visual/StaticPlot/StaticPlot.py +++ b/papi/plugin/visual/StaticPlot/StaticPlot.py @@ -31,24 +31,18 @@ import papi.pyqtgraph as pq import numpy as np -import collections import re -import copy -import time import json from papi.plugin.base_classes.vip_base import vip_base from papi.data.DParameter import DParameter -from papi.data.DSignal import DSignal + import papi.pyqtgraph as pg import papi.constants as pc -current_milli_time = lambda: int(round(time.time() * 1000)) - from papi.pyqtgraph.Qt import QtCore, QtGui -from PySide.QtGui import QRegExpValidator -from PySide.QtCore import * + class StaticPlot(vip_base): """ @@ -67,7 +61,7 @@ class StaticPlot(vip_base): 4 : (100, 100, 100) """ - def __init__(self, debug=False): + def __init__(self): super(StaticPlot, self).__init__() """ Function init @@ -76,40 +70,14 @@ def __init__(self, debug=False): :return: """ - self.signals = {} - - self.__papi_debug__ = debug - self.__buffer_size__ = None - self.__downsampling_rate__ = 1 - self.__tbuffer__ = [] - - self.__signals_have_same_length = True - - self.__append_at__ = 1 - self.__new_added_data__ = 0 - self.__input_size__ = 0 - self.__rolling_plot__ = False self.__colors_selected__ = [] self.__styles_selected__ = [] self.__show_grid_x__ = None self.__show_grid_y__ = None self.__parameters__ = {} - self.__update_intervall__ = None - self.__last_time__ = None - self.__last_plot_time__ = None self.__plotWidget__ = None self.__legend__ = None - self.__stp_min_x = None - self.__stp_max_x = None - - self.__stp_min_y = None - self.__stp_max_y = None - self.__stp_active__ = None - - self.__downsampling_rate_start__ = None; - self.__downsampling_rate__ = None - self.styles = { 0: QtCore.Qt.SolidLine, 1: QtCore.Qt.DashDotDotLine, @@ -164,9 +132,9 @@ def initiate_layer_0(self, config=None): # --------------------------- self.__parameters__['x-grid'] = \ - DParameter('x-grid', self.config['x-grid']['value'], Regex='^(1|0){1}$') + DParameter('x-grid', '[0 1]', Regex='^(1|0){1}$') self.__parameters__['y-grid'] = \ - DParameter('y-grid', self.config['y-grid']['value'], Regex='^(1|0){1}$') + DParameter('y-grid', '[0 1]', Regex='^(1|0){1}$') self.__parameters__['color'] = \ DParameter('color', self.config['color']['value'], Regex='^\[(\s*\d\s*)+\]') @@ -181,16 +149,14 @@ def initiate_layer_0(self, config=None): # --------------------------- # Create Legend # --------------------------- - - self.__legend__ = pq.LegendItem((100, 40), offset=(40, 1)) # args are (size, offset) - self.__legend__.setParentItem(self.__plotWidget__.graphicsItem()) + if self.config['show_legend']['value']=='1': + self.__legend__ = pq.LegendItem((100, 40), offset=(40, 1)) # args are (size, offset) + self.__legend__.setParentItem(self.__plotWidget__.graphicsItem()) self.setup_context_menu() self.__plotWidget__.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.__plotWidget__.customContextMenuRequested.connect(self.showContextMenu) - self.use_range_for_y(self.config['yRange']['value']) - self.read_json_data() return True @@ -211,10 +177,12 @@ def resume(self): """ pass - def execute(self, Data=None, block_name=None): + + def execute(self, Data=None, block_name = None, plugin_uname = None): """ Function execute + :param **kwargs: :param Data: :param block_name: :return: @@ -229,7 +197,7 @@ def set_parameter(self, name, value): :param value: :return: """ - print(value) + if name == 'x-grid': self.config['x-grid']['value'] = value self.__plotWidget__.showGrid(x=value == '1') @@ -253,9 +221,10 @@ def set_parameter(self, name, value): self.__styles_selected__ = int_re.findall(self.config['style']['value']) if name == 'yRange': - self.config['yRange']['value'] = value self.use_range_for_y(value) + if name == 'xRange': + self.use_range_for_x(value) def read_json_data(self): @@ -273,8 +242,7 @@ def plot_data(self, data, axis_id): :return: """ - self.__plotWidget__.clear() - + self.clear() tdata = data[axis_id] @@ -284,10 +252,11 @@ def plot_data(self, data, axis_id): y_min = None y_max = None + counter = 0 + for signal_name in data: if signal_name != axis_id: signal_data = data[signal_name] - print(signal_data) if y_max is not None: y_max = max(max(signal_data), y_max) @@ -299,17 +268,25 @@ def plot_data(self, data, axis_id): else: y_min = min(signal_data) - graphic = GraphicItem(np.array(tdata), np.array(signal_data), 0) + pen = self.get_pen(counter) + + graphic = GraphicItem(np.array(tdata), np.array(signal_data), 0, pen=pen) self.__plotWidget__.addItem(graphic) + if self.__legend__ is not None: + data_item = pg.PlotDataItem() + data_item.setPen(pen) + + self.__legend__.addItem(data_item, signal_name) + + counter += 1 self.use_range_for_x("[{:2.2f} {:2.2f}]".format(x_min, x_max)) self.use_range_for_y("[{:2.2f} {:2.2f}]".format(y_min, y_max)) # self.__plotWidget__.getPlotItem().getViewBox().setXRange(x_min, x_max) - def plugin_meta_updated(self): """ This function is called whenever meta information are changed. @@ -381,22 +358,6 @@ def setup_context_menu(self): self.gridMenu = QtGui.QMenu('Grid') - # get old values; - reg = re.compile(r'(\d+\.\d+)') - range = reg.findall(self.config['yRange']['value']) - if len(range) == 2: - y_min = range[0] - y_max = range[1] - else: - y_min = '0.0' - y_max = '1.0' - - rx = QRegExp(r'([-]{0,1}\d+\.\d+)') - validator = QRegExpValidator(rx, self.__plotWidget__) - - - - # Grid Menu: # ----------------------------------------------------------- # Y-Grid checkbox @@ -422,51 +383,15 @@ def setup_context_menu(self): self.yGrid_Checkbox.setChecked(True) # add Menus - self.custMenu.addMenu(self.gridMenu) - self.custMenu.addSeparator().setText("Rolling Plot") self.__plotWidget__.getPlotItem().getViewBox().menu.clear() - if not self.__papi_debug__: - self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] + self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu] def showContextMenu(self): self.setup_context_menu() - - def contextMenu_yAutoRangeButton_clicked(self): - mi = None - ma = None - - if self.__stp_active__: - mi = self.__stp_min_y - ma = self.__stp_max_y - else: - for sig in self.signals: - graphics = self.signals[sig].graphics - buf = [] - for graphic in graphics: - buf.extend(graphic.y) - - ma_buf = max(buf) - mi_buf = min(buf) - if ma is not None: - if ma_buf > ma: - ma = ma_buf - else: - ma = ma_buf - - if mi is not None: - if mi_buf < mi: - mi = mi_buf - else: - mi = mi_buf - - self.yRange_maxEdit.setText(ma) - self.yRange_minEdit.setText(mi) - self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + str(float(mi)) + ' ' + str(float(ma)) + ']') - def contextMenu_xGrid_toogle(self): if self.xGrid_Checkbox.isChecked(): self.control_api.do_set_parameter(self.__id__, 'x-grid', '1') @@ -511,15 +436,19 @@ def get_plugin_configuration(self): 'value' : 't', 'tooltip' : 'Used to specify the identifier for the X-Axis e.g. time in json_data' }, + 'show_legend' : { + 'value' : pc.REGEX_BOOL_BIN, + 'type' : pc.CFG_TYPE_BOOL + }, 'x-grid': { 'value': "0", 'regex': pc.REGEX_BOOL_BIN, - 'type': 'bool', + 'type': pc.CFG_TYPE_BOOL, 'display_text': 'Grid-X' }, 'y-grid': { 'value': "0", 'regex': pc.REGEX_BOOL_BIN, - 'type': 'bool', + 'type': pc.CFG_TYPE_BOOL, 'display_text': 'Grid-Y' }, 'color': { 'value': "[0 1 2 3 4]", @@ -531,11 +460,6 @@ def get_plugin_configuration(self): 'regex': '^\[(\s*\d\s*)+\]', 'advanced': '1', 'display_text': 'Style' - }, 'yRange': { - 'value': '[0.0 1.0]', - 'regex': '(\d+\.\d+)', - 'advanced': '1', - 'display_text': 'y: range' } } # http://www.regexr.com/ From 717fc8ed21e400af8ac3fb6d18ed0623a649b134 Mon Sep 17 00:00:00 2001 From: StefanBerlin Date: Tue, 31 Mar 2015 18:06:36 +0200 Subject: [PATCH 220/260] added Plugin Human --- CHANGENLOG.md | 2 + papi/plugin/dpp/Human/Human.py | 172 + papi/plugin/dpp/Human/Human.yapsy-plugin | 9 + papi/plugin/dpp/Human/blender.js/Mensch.js | 52 + papi/plugin/dpp/Human/index.html | 322 + papi/plugin/dpp/Human/js/TrackballControls.js | 557 + papi/plugin/dpp/Human/js/dat.gui.min.js | 94 + papi/plugin/dpp/Human/js/jquery-1.9.0.js | 9555 ++++ papi/plugin/dpp/Human/js/stats.min.js | 6 + papi/plugin/dpp/Human/js/three.js | 36949 ++++++++++++++++ papi/plugin/dpp/Human/js/tween.min.js | 13 + papi/plugin/dpp/Human/viewer/Detector.js | 59 + papi/plugin/dpp/Human/viewer/JSONLoader.js | 544 + papi/plugin/dpp/Human/viewer/OBJLoader.js | 291 + .../dpp/Human/viewer/TrackballControls.js | 557 + papi/plugin/dpp/Human/viewer/three.js | 36949 ++++++++++++++++ 16 files changed, 86131 insertions(+) create mode 100644 papi/plugin/dpp/Human/Human.py create mode 100644 papi/plugin/dpp/Human/Human.yapsy-plugin create mode 100755 papi/plugin/dpp/Human/blender.js/Mensch.js create mode 100644 papi/plugin/dpp/Human/index.html create mode 100755 papi/plugin/dpp/Human/js/TrackballControls.js create mode 100755 papi/plugin/dpp/Human/js/dat.gui.min.js create mode 100755 papi/plugin/dpp/Human/js/jquery-1.9.0.js create mode 100755 papi/plugin/dpp/Human/js/stats.min.js create mode 100755 papi/plugin/dpp/Human/js/three.js create mode 100755 papi/plugin/dpp/Human/js/tween.min.js create mode 100755 papi/plugin/dpp/Human/viewer/Detector.js create mode 100755 papi/plugin/dpp/Human/viewer/JSONLoader.js create mode 100755 papi/plugin/dpp/Human/viewer/OBJLoader.js create mode 100755 papi/plugin/dpp/Human/viewer/TrackballControls.js create mode 100755 papi/plugin/dpp/Human/viewer/three.js diff --git a/CHANGENLOG.md b/CHANGENLOG.md index 61e50117..475d74fe 100644 --- a/CHANGENLOG.md +++ b/CHANGENLOG.md @@ -9,6 +9,7 @@ v.1.XX * [fix]: Renamed some variables of ownProcess_base to be private * [fix]: Fixed memory leak of gui event processing timer loop (#25) * [fix]: Fixed memory leak due to recall of the plugin manager collection function + * [fix]: Tab position and active tab will now be saved to xml * [improvement]: Changed the demux function to in improve performance * [improvement]: Improved core performance while processing new data (see #20) * [improvement]: Huge changes in the plotting plugin with performance benefits @@ -17,6 +18,7 @@ v.1.XX * [plugin]: Minor changes in LCDDisplay and Slider * [plugin]: Added a new pcp plugin: Radiobutton * [improvement]: Shifted some control logic from gui to core to increase speed when changing cfgs incl. subscriptions + * [imporvement]: ORTD can now receive its configuration via udp in multiple packages v.1.0 --- diff --git a/papi/plugin/dpp/Human/Human.py b/papi/plugin/dpp/Human/Human.py new file mode 100644 index 00000000..6cbab2b5 --- /dev/null +++ b/papi/plugin/dpp/Human/Human.py @@ -0,0 +1,172 @@ +#!/usr/bin/python3 +#-*- coding: utf-8 -*- + +""" +Copyright (C) 2014 Technische Universität Berlin, +Fakultät IV - Elektrotechnik und Informatik, +Fachgebiet Regelungssysteme, +Einsteinufer 17, D-10587 Berlin, Germany + +This file is part of PaPI. + +PaPI is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +PaPI is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with PaPI. If not, see . + +Contributors: +Mirjana Ruppel +""" + +__author__ = 'Mirjana' + +# basic import for block and parameter structure +from papi.data.DPlugin import DBlock +from papi.data.DParameter import DParameter +from papi.data.DSignal import DSignal +from papi.plugin.base_classes.dpp_base import dpp_base + +import tornado.ioloop +import tornado.web +import tornado.websocket +import tornado.template +import tornado.tcpserver +import threading +import multiprocessing + +import time + +# This class initializes a WebSocket to enable bidirectional communication between browser and server +class WSHandler(tornado.websocket.WebSocketHandler): + + # WebSockets don't use CORS headers and can bypass the usual same-origin policies + # to accept cross-origin traffic, always return True + #def check_origin(self, origin): + #return True + + # initialize the data that shall be sent + def initialize(self, QuatData): + self.QuatData = QuatData + + # this function is called when a new WebSocket is opened + def open(self): + print('Connection established') + + # this function handles incoming messages + def on_message(self, message): + # index.html sends a message to the server when it is ready to render a new frame + # the answer to this message is the current quaternion data received from the ORTD Plugin + self.write_message(str(self.QuatData()).encode('utf-8')) + + # this function is called when the WebSocket is closed + def on_close(self): + print('Connection closed') + + +class Human(dpp_base): + + def start_init(self, config=None): + + # Plugin is triggered by arrival of data + self.set_event_trigger_mode(True) + + # initialize and start a thread + self.thread_goOn = True + self.lock = threading.Lock() + self.thread = threading.Thread(target=self.thread_execute) + self.thread.start() + + # initialize a variable to store the data + self.Angle_Data = 0 + + # return init success + return True + + def pause(self): + # will be called, when plugin gets paused + # stop thread and join into main thread + self.thread_goOn = False + self.thread.join() + + def resume(self): + # will be called when plugin gets resumed + # restart thread + self.thread_goOn = True + self.lock = threading.Lock() + self.thread = threading.Thread(target=self.thread_execute) + self.thread.start() + + def execute(self, Data=None, block_name = None): + + # IMPORTANT: The identification names have to be the same as written in RTmain.sce! + + # quaternion for upper arm + self.Angle_Data_Up_w = Data['quat_upperarm_w'][0] + self.Angle_Data_Up_x = Data['quat_upperarm_x'][0] + self.Angle_Data_Up_y = Data['quat_upperarm_y'][0] + self.Angle_Data_Up_z = Data['quat_upperarm_z'][0] + + # quaternion for forearm + self.Angle_Data_Fo_w = Data['quat_forearm_w'][0] + self.Angle_Data_Fo_x = Data['quat_forearm_x'][0] + self.Angle_Data_Fo_y = Data['quat_forearm_y'][0] + self.Angle_Data_Fo_z = Data['quat_forearm_z'][0] + + # be sure, data has the right identification names + if 'quat_forearm_w' & 'quat_forearm_x' & 'quat_forearm_y' & 'quat_forearm_z' & 'quat_upperarm_w' & 'quat_upperarm_x' & 'quat_upperarm_y' & 'quat_upperarm_z' in Data: + self.Angle_Data = [self.Angle_Data_Up_w,self.Angle_Data_Up_x,self.Angle_Data_Up_y,self.Angle_Data_Up_z, self.Angle_Data_Fo_w, self.Angle_Data_Fo_x, self.Angle_Data_Fo_y, self.Angle_Data_Fo_z] + + else: + print('Wrong identification names for data!') + + + def set_parameter(self, name, value): + pass + + def quit(self): + self.thread_goOn = False + self.thread.join() + print('Human-Plugin quits') + + + def get_plugin_configuration(self): + + config = {} + return config + + def plugin_meta_updated(self): + pass + + + def thread_execute(self): + print('Start a thread') + + # start a tornado web application in the thread + # tornado.web.Application() is a collection of request handlers that create a web application + # pass a list of regexp or request_class tuples + application = tornado.web.Application([ + (r'/ws', WSHandler, {'QuatData': self.get_data} ), # pass quaternion data, so class WSHandler can process it + (r"/(.*)", tornado.web.StaticFileHandler, {"path": "./resources"}), + ]) + + print('Application created successfully') + # port has to be the same as the WebSocket port in index.html + application.listen(9999) + print('Start IOLoop') + # tornado.ioloop is an I/O event loop for non-blocking sockets + # start the main ioloop + tornado.ioloop.IOLoop.instance().start() + print('IO Loop started successfully') + + + def get_data(self): + return self.Angle_Data + diff --git a/papi/plugin/dpp/Human/Human.yapsy-plugin b/papi/plugin/dpp/Human/Human.yapsy-plugin new file mode 100644 index 00000000..d41cc2ca --- /dev/null +++ b/papi/plugin/dpp/Human/Human.yapsy-plugin @@ -0,0 +1,9 @@ +[Core] +Name = Human +Module = Human + +[Documentation] +Author = Mirjana Ruppel +Version = 0.1 +Website = openrtdynamics.sf.net +Description = openrtdynamics interface \ No newline at end of file diff --git a/papi/plugin/dpp/Human/blender.js/Mensch.js b/papi/plugin/dpp/Human/blender.js/Mensch.js new file mode 100755 index 00000000..bf243870 --- /dev/null +++ b/papi/plugin/dpp/Human/blender.js/Mensch.js @@ -0,0 +1,52 @@ + { + + "metadata": { + "formatVersion" : 3.1, + "generatedBy" : "Blender 2.7 Exporter", + "vertices" : 14813, + "faces" : 14818, + "normals" : 14769, + "colors" : 0, + "uvs" : [3308], + "materials" : 1, + "morphTargets" : 0, + "bones" : 16 + }, + + "scale" : 1000.000000, + + "vertices" : [-2158.4,3746.94,227.415,-2149.57,3736.5,245.178,-2130.13,3757.05,233.922,-2139.66,3767.82,217.215,-2180.09,3741.73,105.542,-2167.6,3760.39,98.7423,-2165.09,3748.45,81.474,-2176.25,3730.68,87.7302,-2157.13,3683.82,54.8189,-2147.8,3696.24,50.1802,-2137.02,3677.14,45.6987,-2146.29,3666.48,49.4497,-2120.62,3638.39,49.0113,-2110.78,3648.33,47.5285,-2101.66,3640.55,53.511,-2112.04,3630.5,54.0679,-2102.48,3622.56,61.5561,-2091.57,3634.03,60.9326,-2064.86,3649.02,287.299,-2059.71,3673.5,262.053,-2080.71,3684.95,279.104,-2085.07,3658.22,302.962,-2354.17,3554.66,266.3,-2354.62,3551.95,239.955,-2368.39,3532.17,247.343,-2368.37,3535.48,273.693,-2322.41,3510.08,89.4867,-2312.32,3502.77,78.5338,-2328.15,3485.61,84.8709,-2337.35,3491.59,95.8992,-2246.19,3500.91,60.9642,-2228.91,3517.57,56.783,-2217.88,3507.76,63.2226,-2235.04,3491.49,67.5844,-2225.57,3470.11,111.85,-2201.66,3484.69,104.157,-2199,3482.29,121.46,-2225.3,3468.73,130.165,-2225.93,3487.16,196.944,-2193.34,3497.26,187.089,-2192.25,3501.74,209.38,-2226.56,3492.57,219.431,-2448.57,3210.11,399.734,-2444.09,3215.69,409.672,-2453,3218.27,411.599,-2458.49,3215.31,403.499,-2110.08,3596.51,363.932,-2112.11,3568.81,379.386,-2097.45,3577.9,373.183,-2092.3,3606.01,352.135,-2111.63,3332.76,440.574,-2102.69,3336.74,445.03,-2114.71,3345.42,446.344,-2122.46,3343.93,442.552,-2043.82,3546.22,274.744,-2034.45,3570.33,280.17,-2033.45,3549.93,313.181,-2041.4,3529.19,306.149,-2224.96,3646.72,94.3193,-2230.15,3655.01,111.536,-2209.2,3677.34,106.948,-2203.9,3668.43,90.3285,-2188.89,3603.81,50.7015,-2197.81,3614.21,56.7992,-2176.48,3634.58,55.3141,-2167.83,3624.75,49.8889,-2151.55,3560.7,60.233,-2160.11,3573.29,51.0822,-2141.68,3596.44,49.8914,-2133.1,3583.87,58.5971,-2389.27,3231.81,273.14,-2382.34,3241.19,268.26,-2387.71,3243.39,280.787,-2394.76,3236.8,285.198,-2347.88,3534.18,163.036,-2341.91,3525.88,138.323,-2354.17,3502.65,144.912,-2360.78,3511.74,169.857,-2286.29,3627.86,186.661,-2307.59,3605.32,193.277,-2306.91,3606.81,218.229,-2285.51,3629.29,210.671,-2285.04,3629.67,235.806,-2306.4,3608.05,243.937,-2330.6,3554.68,364.667,-2341.77,3552.77,343.316,-2356.29,3535.63,350.958,-2345.78,3539.19,372.334,-2462.85,3283.05,512.062,-2457.17,3288.96,516.074,-2461.24,3295.4,514.654,-2465.66,3293.73,510.923,-2343.43,3273.55,152.238,-2352.62,3273.56,155.83,-2350.63,3272.94,147.106,-2342.55,3274.44,141.52,-2383.82,3515.07,337.922,-2376.16,3511.81,358.702,-2362.39,3537.41,325.699,-2375.54,3494.45,261.499,-2385.94,3508.07,264.727,-2366,3513.78,248.264,-2344.24,3450.97,283.15,-2348.25,3452.8,263.231,-2328.66,3460.5,249.428,-2325.91,3462.57,273.274,-2339.95,3463.94,353.655,-2335.21,3449.59,350.739,-2314.2,3459.07,346.466,-2325.56,3470.32,349.572,-2331.25,3432.74,185.898,-2329.67,3429.98,166.92,-2308.5,3436.34,157.288,-2313.47,3442.49,180.362,-2349.62,3463.25,251.675,-2342.21,3451.85,248.76,-2340.53,3470.22,250.807,-2388.04,3500.62,226.762,-2385.58,3503.85,248.476,-2368.11,3526.59,221.121,-2360.61,3467.21,157.821,-2372.6,3479.05,158.219,-2349.75,3483.62,146.015,-2340.74,3518.86,425.41,-2327.19,3512.22,436.632,-2306.23,3532.64,420.151,-2320.42,3538.66,408.607,-2361.15,3501.59,364.486,-2369.88,3515.46,369.598,-2352.74,3517.88,353.638,-2319.6,3452.24,378.538,-2331.77,3452.94,364.761,-2299.56,3461.37,364.686,-2297.16,3476.88,428.325,-2291.55,3467.53,418.186,-2265.24,3480.55,408.854,-2272.43,3492.21,418.799,-2332.66,3458.54,82.6738,-2341.81,3467.96,87.9862,-2319.17,3474.88,78.7479,-2303.9,3428.41,102.97,-2302.3,3434.01,90.029,-2287.77,3445.6,84.4578,-2290.49,3437.86,98.5733,-2327.66,3440.17,154.115,-2319.3,3429.7,153.132,-2320.43,3444.62,154.966,-2360.44,3480.69,128.615,-2360.82,3482.66,143.363,-2349.79,3499.57,127.264,-2448.51,3408.85,339.013,-2438.95,3434.2,331.047,-2436.37,3431.81,318.431,-2445.56,3407.83,326.055,-2422.56,3389.97,310.206,-2413.74,3414.3,299.58,-2401.56,3409.18,299.654,-2410.26,3385.07,312.286,-2402.96,3379.58,324.874,-2392.39,3401.68,314.017,-2403.32,3385.16,381.487,-2395.47,3377.06,372.897,-2386.55,3403.34,370.062,-2396.38,3411.52,376.711,-2391.37,3379.98,209.329,-2397.95,3381.66,197.921,-2388.89,3398.7,188.643,-2380.34,3395.76,202.892,-2398.78,3387.33,259.492,-2386.27,3387.49,252.266,-2375.57,3409.33,252.48,-2389.98,3411.02,258.714,-2436.36,3383.58,216.181,-2431.29,3408.86,209.747,-2426.52,3405.01,198.975,-2430.64,3381.84,205.446,-2408.31,3380.19,196.264,-2400.59,3398.39,187.633,-2452.27,3386.08,466.604,-2454.67,3385.68,452.93,-2465.77,3356.6,463.225,-2463.24,3358.49,474.684,-2452.97,3325.01,451.331,-2445.34,3346.59,444.882,-2433.3,3343.16,444.849,-2439.41,3321.37,450.293,-2414.46,3317.84,481.016,-2408.49,3339.75,473.745,-2406.69,3342.53,485.708,-2412.9,3321.21,493.049,-2420.37,3376.95,499.445,-2428.57,3379.86,498.024,-2440.49,3356.35,504.835,-2431.95,3353.94,506.58,-2368.35,3391.09,100.113,-2375.23,3394.94,103.734,-2374.31,3411.19,100.357,-2367.6,3406.02,96.576,-2341.81,3385.47,139.893,-2340.09,3396.85,139.272,-2345.53,3400.74,149.483,-2347.28,3387.94,149.257,-2354.95,3392.28,152.157,-2354.13,3405.76,151.888,-2379.83,3396.68,111.818,-2378.94,3413.85,108.376,-2477.73,3289.74,367.926,-2474.95,3318.97,362.948,-2472.39,3318.61,348.638,-2475.56,3290.38,352.532,-2443.7,3283.07,334.946,-2443.88,3306.33,333.027,-2432.57,3303.44,334.929,-2431.43,3282.76,336.943,-2410.87,3235.71,377.338,-2411.87,3237.86,390.961,-2417.35,3222.04,389.428,-2417.06,3219.64,377.935,-2445.06,3234.47,416.416,-2457.31,3237.38,415.657,-2382.92,3260.1,265.883,-2388.76,3261.59,279.296,-2421.86,3243.09,291.697,-2433.64,3240.6,287.158,-2422.95,3225.31,284.724,-2413.64,3227.02,287.539,-2449.5,3276.48,239.082,-2452.81,3305.46,236.416,-2446.32,3306.93,224.495,-2442.1,3278.21,225.76,-2414.87,3294.65,214.064,-2420.99,3318.57,211.203,-2412.73,3323.62,212.242,-2406.36,3302.39,216.28,-2329.29,3297.45,124.658,-2323.71,3306.2,125.895,-2321.88,3295.27,130.878,-2326.89,3286.33,132.984,-2333.15,3335.53,154.206,-2337.46,3348.24,150.136,-2344.23,3348.59,160.515,-2340.14,3336.36,164.755,-2355.79,3305.06,181.489,-2363.58,3303.44,178.977,-2354.49,3290.38,179.19,-2348.95,3291.74,180.105,-2379.71,3325.36,132.371,-2381.53,3350.98,125.341,-2376.34,3350.76,116.218,-2373.89,3324.82,121.517,-2099.56,3761.59,235.838,-2112.43,3774.93,222.117,-2117.85,3743.18,249.812,-2137.29,3797.39,168.037,-2141.31,3798.7,147.346,-2156.88,3782.79,157.211,-2153.41,3780.64,178.278,-2151.33,3774.16,87.5166,-2148.38,3761.05,70.0676,-2105.45,3748.55,23.8777,-2088.49,3759.49,12.0562,-2081.63,3739.77,3.79279,-2098.04,3728.97,15.8751,-2059.99,3639.78,76.8586,-2053.47,3639.06,89.1024,-2068.18,3623.85,93.7459,-2074.83,3625.93,81.5877,-2030.42,3646.12,148.768,-2028.56,3651.67,169.008,-2037.3,3627.3,180.087,-2040.41,3624.19,158.042,-2046.99,3693.27,240.568,-2063.6,3704.88,256.171,-2145.28,3439.29,367.832,-2141.37,3414.92,371.051,-2146.39,3429.27,387.685,-2145.97,3452.22,384.781,-2085.16,3471.16,308.638,-2106.49,3466.34,307.69,-2107.88,3483.51,289.871,-2080.9,3492.43,292.136,-2044.86,3520.57,351.132,-2039.06,3511.91,332.593,-2040.65,3557.97,331.793,-2116.33,3534.98,393.419,-2101.21,3542.46,391.445,-2143.62,3393.29,381.329,-2148.89,3405.8,395.13,-2086.12,3366.84,360.036,-2099.23,3370.79,353.547,-2097.55,3395.78,346.566,-2086.77,3393.27,350.488,-2067.05,3384.23,413.13,-2059.93,3377.81,398.443,-2064.75,3406.49,385.655,-2069.26,3410.9,396.426,-2128.31,3407.11,430.978,-2117.2,3406.77,435.998,-2118.74,3430.95,419.051,-2124.24,3433.36,413.145,-2116.45,3691.77,291.248,-2105.11,3672.57,302.026,-2092.3,3706.78,272.38,-2238.68,3597.61,365.341,-2254.46,3584.24,374.948,-2239.21,3572.36,386.935,-2225.67,3584.73,378.188,-2213.11,3570.08,387.19,-2224.67,3559.42,395.056,-2174.69,3507.82,371.006,-2180.84,3520.73,386.185,-2188.06,3517.17,391.394,-2184.16,3505.82,376.34,-2276.89,3530.52,417.111,-2289.51,3542.65,411.938,-2294.11,3520.98,424.85,-2252.14,3480.33,391.433,-2246.44,3490.28,403.855,-2273.86,3470.55,397.312,-2285.96,3469.89,312.076,-2282.74,3468.76,336.367,-2317.95,3459.83,322.028,-2125.4,3489.58,266.779,-2106.19,3493.09,264.884,-2127.13,3483.87,292.145,-2173.01,3497.08,136.564,-2196.06,3485.65,142.901,-2176.38,3497.42,115.511,-2069.44,3601.73,115.93,-2080.51,3606.9,95.8152,-2056.3,3622.34,114.605,-2451.84,3300.34,515.847,-2458.61,3305.05,513.527,-2411.84,3360.59,445.325,-2423.08,3339.97,451.468,-2420.84,3364,438.685,-2451.48,3381.83,445.507,-2463.41,3353.7,456.235,-2474.52,3305.27,491.094,-2476.81,3304.62,480.761,-2477.9,3291.76,486.535,-2475.5,3291.32,495.068,-505.309,4109.57,-373.916,-440.289,4111.22,-387.203,-423.98,4049.78,-361.937,-488.141,4047.28,-349.849,-408.359,3991.39,-335.548,-472.014,3988.38,-325.129,-367.664,4116.46,-386.138,-351.553,4056.26,-358.5,-337.226,3999.06,-330.119,-544.773,4047.44,-323.873,-564.346,4110.25,-348.028,-593.041,4050.1,-279.301,-615.507,4112.51,-305.917,-527.521,3988.86,-299.448,-574.473,3992.84,-254.348,-584.432,4177.14,-370.711,-521.864,4175.11,-396.126,-601.981,4247.12,-392.387,-535.664,4242.73,-416.505,-640.065,4180.51,-329.573,-670.5,4258.06,-350.224,-455.318,4175.47,-409.779,-382.227,4179.33,-410.219,-467.849,4241.7,-429.516,-394.312,4243.93,-430.505,-300.287,4204.58,415.192,-360.23,4200.35,410.564,-352.375,4149.71,404.819,-297.15,4152.46,413.478,-345.984,4090.13,407.138,-292.156,4092.19,417.665,-420.948,4198.54,395.959,-409.176,4148.9,388.81,-400.728,4089.43,391.233,-241.866,4156.7,417.975,-240.204,4210.84,414.51,-182.481,4159.85,422.041,-183.403,4217.87,413.113,-238.434,4094.49,424.176,-181.447,4095.8,428.128,-241.13,4271.67,409.594,-303.533,4263.17,416.389,-242.735,4325.54,402.475,-305.872,4322.9,411.247,-184.893,4311.71,397.752,-185.312,4338.45,391.809,-369.016,4260.95,418.089,-433.815,4261.25,403.384,-374.586,4323.89,415.202,-441.799,4325.31,400.099,-273.167,4848.62,-420.445,-339.513,4864.47,-430.288,-333.328,4907.56,-417.199,-273.521,4890.2,-410.552,-327.299,4944.95,-403.327,-273.978,4929.26,-400.132,-400.156,4875.92,-430.757,-390.344,4919.7,-416.145,-379.475,4956.23,-400.35,-225.667,4874.75,-403.129,-223.491,4834.62,-410.829,-190.984,4869.12,-400.136,-190.304,4829.65,-407.369,-228.169,4914.69,-395.235,-191.672,4909.09,-392.838,-222.403,4792.6,-418.994,-274.897,4803.52,-429.845,-222.397,4744.98,-427.636,-278.51,4753.8,-438.401,-189.598,4788.75,-415.165,-188.794,4742,-423.617,-346.791,4814.88,-440.332,-409.098,4823.87,-441.14,-354.04,4762.17,-446.558,-417.463,4767.86,-446.486,-545.717,4653.86,-443.122,-542.382,4719.65,-442.29,-483.229,4714.79,-446.356,-489.323,4654.24,-448.632,-424.458,4711.21,-450.217,-428.449,4652.05,-455.034,-537.525,4784.82,-436.496,-476.234,4775.3,-443.276,-494.06,4592.68,-453.084,-550.885,4592.07,-443.381,-493.912,4525.46,-460.099,-554.249,4528.34,-448.122,-428.42,4588.21,-461.378,-424.341,4519.19,-467.078,-604.559,4587.39,-436.704,-603.526,4652.64,-440.116,-661.819,4580.44,-430.859,-666.321,4648.76,-437.339,-609.024,4526.5,-436.559,-662.858,4522.14,-425.13,-605.635,4724.98,-436.678,-603.901,4793.72,-421.362,-672.337,4725.41,-429.079,-678.249,4801.14,-390.867,-549.298,4382.53,-447.509,-483.549,4379.3,-457.817,-476.954,4309.54,-445.871,-544.038,4311.63,-434.517,-411.772,4377.85,-459.643,-403.991,4310.03,-447.067,-608.005,4316.95,-415.493,-610.631,4387.71,-431.339,-667.895,4328.39,-389.742,-666.781,4395.64,-411.251,-611.227,4459.39,-437.328,-552.845,4456.05,-452.058,-665.206,4461.82,-420.853,-489.055,4451.72,-463.248,-418.205,4447.67,-466.957,-239.918,3909.93,-263.505,-275.363,3900.45,-271.931,-270.8,3955.92,-290.146,-233.473,3965.63,-278.967,-272.071,4010.05,-312.97,-230.967,4019.25,-299.138,-325.433,3889.56,-281.831,-328.3,3944.17,-304.191,-205.595,3972.31,-273.444,-208.935,3916.99,-258.564,-176.722,3974.98,-271.908,-175.873,3919.96,-256.719,-203.821,4025.51,-293.087,-177.52,4027.95,-291.54,-213.167,3852.8,-247.09,-247.666,3848.36,-250.919,-214.767,3784.5,-236.726,-252.053,3785.46,-240.309,-174.844,3854.94,-245.254,-173.713,3783.95,-234.806,-282.18,3842.23,-256.757,-326.891,3834.69,-263.123,-287.542,3784.25,-244.811,-329.989,3780.87,-248.989,-281.179,4655.15,269.59,-338.659,4671.95,271.114,-357.239,4622.11,314.315,-293.269,4609.56,308.151,-367.182,4565.96,345.155,-300.479,4556.74,339.531,-403.252,4687.32,263.623,-423.359,4631.35,306.302,-433.844,4572.49,334.031,-238.942,4595.62,300.374,-233.405,4637.73,268.103,-188.93,4580.67,294.126,-189.464,4619.02,268.752,-242.39,4546.51,329.626,-188.286,4535.6,319.771,-227.276,4671.24,236.095,-267.296,4690.41,228.38,-223.648,4698.71,207.179,-259.242,4717.65,193.667,-189.881,4649.75,245.52,-190.21,4674.65,224.543,-313.743,4710.09,217.724,-368.738,4732.86,194.015,-300.768,4736.8,177.442,-354.282,4758.8,152.355,-305.765,4442,384.325,-304.034,4500.23,364.402,-372.312,4507.14,368.686,-375.195,4447.11,388.085,-440.374,4512.14,355.975,-444.321,4450.74,374.706,-376.24,4386.03,403.935,-306.465,4382.72,400.122,-445.048,4388.49,389.751,-243.845,4381.2,390.829,-244.283,4437.58,374.802,-186.007,4383.31,380.061,-186.785,4434.29,363.785,-243.942,4493.15,354.468,-187.559,4486.01,343.457,-469.541,3696.88,319.243,-506.378,3708.31,265.621,-507.074,3645.83,258.932,-474.367,3627.55,305.823,-506.368,3589.57,258.291,-478.476,3565.48,295.646,-531.085,3721.45,219.952,-530.919,3667.31,219.374,-532.035,3614.66,222.954,-429.189,3614.95,352.002,-423.594,3685.17,358.105,-376.041,3605.45,378.588,-373.03,3674.7,381.869,-436.308,3548.9,343.486,-382.729,3538.39,378.354,-420.926,3755.96,364.199,-466.321,3766.55,329.328,-421.715,3826.22,368.188,-467.438,3834.37,335.651,-373.405,3746.93,388.212,-373.984,3819.35,391.629,-505.949,3773.75,277.025,-533.423,3780.11,226.219,-508.967,3839.92,287.74,-539.6,3843.82,233.364,-584.216,3726.37,-99.8622,-552.935,3722.25,-140.92,-563.582,3666.67,-138.721,-594.602,3673.2,-94.5881,-571.621,3614.31,-137.395,-601.487,3623.66,-92.6048,-521.279,3711.02,-172.44,-528.116,3656.74,-173.486,-534.795,3603.8,-173.89,-617.118,3679.35,-41.9517,-611.218,3728.16,-44.9158,-625.196,3685.93,14.9934,-619.895,3734.16,16.8649,-621.797,3632.05,-41.7078,-630.592,3637.76,13.2617,-604.514,3779.64,-52.3128,-572.879,3780.32,-107.504,-599.642,3831.8,-63.1701,-566.84,3828.96,-113.987,-617.626,3782.64,15.6622,-619.338,3833.26,9.97713,-544.257,3774.3,-142.577,-517.462,3760.38,-171.092,-542.382,3817.91,-143.452,-519.059,3802.14,-172.02,-644.057,4125.09,-129.615,-630.1,4089.02,-127.568,-635.882,4106.94,-110.594,-649.696,4144.11,-111.634,-652.619,4105.2,-62.4341,-659.903,4158.07,-87.6574,-615.813,4041.14,-127.206,-623.993,4057.17,-102.067,-650.877,4050.56,-41.4867,-669.962,4189,-110.635,-666.78,4169.37,-142.676,-691.154,4233.34,-110.003,-702.409,4227.26,-176.38,-682.617,4214.09,-92.1522,-703.342,4265.92,-86.542,-675.724,4179.29,-265.244,-650.253,4114.94,-233.126,-702.04,4239.63,-278.032,-624.06,4057.83,-193.571,-604.143,4005.15,-177.974,-380.238,5849.52,90.8858,-357.714,5871.97,92.5005,-360.171,5886.39,51.1291,-383.432,5863.8,51.4487,-361.743,5896.29,10.0479,-385.626,5873.62,12.032,-332.227,5890.18,94.1963,-333.971,5904.62,51.2113,-334.953,5914.5,8.70769,-403.304,5837.39,51.5989,-399.478,5823.38,88.9431,-419.337,5807.69,51.0117,-415.12,5794.07,86.262,-406.009,5847.04,14,-422.301,5817.09,15.2914,-394.558,5806.63,124.558,-375.996,5832.24,128.611,-388.565,5788.81,156.969,-370.664,5813.44,162.891,-409.7,5778.04,119.874,-403.128,5761.4,150.679,-354.247,5854.33,132.221,-329.543,5872.35,135.574,-349.647,5834.79,168.352,-325.741,5852.33,173.257,-607.47,3956.19,141.099,-593.856,3902.59,140.932,-571.91,3907.99,187.257,-584.034,3963,186.431,-549.832,3906.57,235.439,-562.102,3964.81,233.181,-584.227,3845.62,139.754,-561.672,3847.56,186.385,-598.446,4014.27,184.819,-626.734,4008.27,138.206,-617.163,4070.51,181.477,-651.935,4063.61,129.297,-575.134,4020.44,230.558,-589.331,4080.11,227.915,-654.315,4000.32,61.923,-635.325,3945.92,72.0432,-643.819,3994.74,-25.3333,-634.34,3939.8,-11.6322,-671.485,4056.86,47.7788,-619.419,3892.64,77.8584,-608.658,3839.11,81.2351,-625.398,3886,0.152603,-971.57,4571.49,28.7038,-991.997,4602.95,10.9577,-1037.02,4555.49,-8.4278,-1017.33,4532.28,8.39235,-1077.64,4511.79,-24.4737,-1058.96,4496.88,-8.42735,-1012.54,4633.57,-16.7249,-1056.24,4579.12,-32.8311,-1095.5,4527.5,-45.1007,-997.417,4510.92,18.3641,-951.953,4541.88,38.6145,-977.127,4492.38,22.3742,-932.734,4515.46,40.935,-1039.51,4482.81,3.0981,-1019.18,4470.32,10.0449,-904.233,4570.94,58.4763,-922.044,4607.54,45.8294,-855.105,4595.78,78.459,-870.048,4637.86,60.316,-887.231,4537.26,62.2855,-841.066,4555.79,86.747,-940.882,4645.24,24.0554,-960.533,4681.27,-7.58341,-885.665,4679.73,32.2242,-901.551,4717.63,-6.22603,-1046.63,4676.38,-103.337,-1056.48,4682.6,-156.183,-1102.91,4629.28,-150.656,-1091.28,4618.7,-104.402,-1140.68,4569.79,-145.265,-1128.59,4559.55,-105.644,-1060.84,4678.02,-208.875,-1106.01,4626.33,-199.633,-1143.39,4568.37,-189.151,-1074.59,4601.02,-65.0017,-1031.05,4658.78,-55.7977,-1112.6,4544.12,-71.6914,-979.338,4710.02,-51.0872,-995.021,4727.73,-104.013,-917.371,4747.51,-53.7254,-933.174,4768.02,-107.074,-1005.86,4733.37,-160.847,-1011.91,4728.14,-216.779,-947.636,4778.4,-164.079,-957.811,4775.17,-222.084,-1052.77,4639.14,-299.711,-1039,4607.68,-331.181,-1076.72,4561.69,-312.516,-1093.01,4589.48,-284.172,-1110.98,4518.69,-294.078,-1126.64,4537.07,-266.161,-1018.99,4571.67,-349.967,-1056.13,4532.99,-331.09,-1092,4499.39,-315.365,-1102.21,4611.31,-245.238,-1059.57,4662.78,-257.569,-1137.39,4554.87,-230.87,-1012.32,4711.42,-268.087,-1006.57,4685.12,-311.817,-959.936,4757.49,-275.525,-954.945,4728.86,-320.593,-994.835,4651.61,-345.544,-977.378,4612.63,-366.674,-944.162,4693,-355.485,-928.987,4651.69,-378.965,-971.463,4510.92,-359.4,-949.438,4487.07,-357.241,-986.262,4462.28,-349.221,-1008.97,4486.75,-348.677,-1026.05,4428.38,-340.52,-1048.81,4458.06,-341.637,-927.477,4458.8,-349.261,-963.236,4432.18,-340.65,-1002.91,4399.73,-327.507,-1032.96,4507.62,-342.154,-995.286,4537.57,-357.851,-1071.34,4480.15,-331.428,-955.737,4572.01,-374.503,-933.36,4536.36,-372.827,-910.477,4606.62,-389.319,-890.627,4562.8,-387.808,-912.864,4507,-367.314,-893.208,4477.65,-358.246,-872.074,4525.26,-380.325,-855.3,4491.54,-370.135,-882.393,4408.27,-304.238,-863.767,4399.59,-272.456,-886.848,4387.57,-268.412,-912.159,4392.42,-298.114,-921.828,4367.93,-265.805,-953.317,4372.18,-291.194,-849.117,4398.02,-239.996,-867.645,4388.41,-236.867,-897.051,4368.91,-234.595,-938.539,4406.05,-322.538,-904.316,4427.83,-331.417,-979.934,4381.52,-310.001,-873.952,4444.73,-341.244,-858.098,4418.62,-313.687,-840.815,4456.22,-354.359,-831.266,4423.74,-328.595,-845.345,4404.48,-280.616,-834.432,4399.81,-246.443,-824.589,4402.51,-295.034,-818.303,4393.73,-258.192,-827.743,4406.17,-179.078,-817.77,4412.91,-144.02,-841.622,4401.42,-144.866,-847.079,4396.29,-177.73,-879.078,4378.95,-140.927,-878.614,4375.78,-173.757,-802.065,4422.53,-99.1992,-833.899,4409.54,-105.487,-880.253,4384.74,-105.146,-855.072,4392.08,-207.221,-837.581,4401.18,-209.428,-883.695,4372.48,-204.179,-824.356,4401.94,-213.541,-814.266,4407.48,-180.74,-811.086,4394.9,-221.169,-802.092,4400.97,-184.009,-802.089,4415.32,-141.984,-784.537,4425.03,-92.5521,-788.624,4409.4,-139.167,-771.939,4420.8,-86.4878,-803.212,4439.5,-43.6443,-824.114,4447.45,-22.6912,-862.887,4437.56,-21.2055,-841.646,4428.75,-42.7628,-908.613,4417.51,-19.7756,-891.694,4405.67,-42.2678,-848.004,4455.24,-4.40834,-886.947,4446.09,-4.73794,-929.716,4428.95,-3.61682,-830.463,4419.42,-69.957,-794.896,4431.51,-66.341,-882.373,4394.2,-70.9583,-778.848,4432.95,-63.4778,-783.625,4439.71,-44.708,-768.871,4429.59,-60.6333,-772.053,4436.08,-44.1326,-797.489,4447.88,-24.6589,-816.152,4456.9,-2.70987,-780.734,4443.43,-23.9079,-792.991,4452.94,4.33692,-892.961,4476.38,25.7514,-913.298,4493.4,36.2083,-956.098,4477.13,21.3208,-934.041,4464.92,16.1592,-997.761,4459.55,12.9128,-975.355,4449.71,11.9156,-910.908,4454.91,7.47672,-871.187,4464.18,11.6759,-952.408,4439.7,6.67828,-835.449,4467.91,19.841,-853.693,4484.31,41.1776,-805.464,4466.64,36.0079,-817.129,4488.52,65.0039,-870.64,4507.83,56.4421,-828.503,4519.34,82.8532,-1329.83,4305.34,21.4588,-1373.1,4273.23,18.4613,-1355.47,4259.17,34.9164,-1311.86,4290.21,37.4456,-1336.83,4243.57,44.572,-1292.69,4274.39,46.2483,-1417.16,4239.19,8.32409,-1400.73,4227.11,25.7023,-1381.67,4212.43,38.1363,-1267.94,4321.41,37.1495,-1286.69,4336.76,20.5747,-1222.58,4353.86,34.1244,-1242.22,4368.82,16.7632,-1247.55,4305.68,46.3425,-1200.84,4338.56,44.2712,-1302.57,4350.26,-2.83673,-1345.01,4318.42,-1.12948,-1314.15,4360.14,-32.8805,-1355.5,4328.13,-29.8822,-1258.82,4382.05,-7.21379,-1271.36,4391.8,-37.3664,-1387.64,4284.77,-3.30707,-1429.02,4249.07,-10.9634,-1397.01,4293.67,-29.254,-1436,4257.51,-31.7437,-1358.49,4334.31,-97.9029,-1354.84,4332.46,-124.979,-1389.11,4304.37,-120.096,-1396.36,4303.28,-91.3174,-1426.43,4274.84,-111.454,-1434.06,4271.23,-82.032,-1352.89,4329.83,-143.53,-1382.7,4304.2,-141.429,-1418.85,4273.55,-138.267,-1399.95,4299.92,-59.103,-1359.91,4333.5,-63.4809,-1437.66,4264.98,-55.1298,-1320.48,4364.86,-67.3962,-1322.23,4364.51,-100.795,-1279.54,4396.43,-70.912,-1284.31,4396.24,-102.405,-1322.19,4361.81,-126.118,-1322.51,4359.2,-145.361,-1286.82,4394.29,-127.312,-1288.11,4393,-149.525,-1354.09,4322.01,-187.39,-1348.67,4315.17,-226.513,-1381.86,4279.29,-211.341,-1381.39,4295.49,-180.107,-1410.76,4238.75,-204.485,-1412.57,4251.41,-181.718,-1334.91,4298.63,-259.052,-1368.82,4264.03,-242.168,-1400.22,4234.35,-234.437,-1380.24,4301.87,-158.812,-1353.41,4326.47,-161.29,-1413.93,4265.83,-161.358,-1323.05,4356.98,-167.63,-1321.48,4354.53,-201.343,-1287.92,4392.48,-177.248,-1284.92,4390.62,-215.747,-1313.46,4347.04,-241.413,-1299.39,4330.59,-270.953,-1276.11,4380.83,-253.984,-1262.09,4363.09,-281.641,-1292.01,4238.44,-256.065,-1324.74,4217.85,-244.263,-1347.64,4242.13,-252.499,-1314.5,4271.07,-270.157,-1354.83,4199.94,-242.296,-1377.64,4222.72,-252.092,-1279.2,4305.68,-285.433,-1250.96,4270.95,-275.687,-1242.44,4338.66,-297.404,-1213.8,4304.58,-292.139,-1229.08,4256.67,-260.525,-1275.41,4229.62,-247.522,-1216.64,4255.71,-255.244,-1263.54,4229.19,-244.604,-1187.21,4283.17,-270.647,-1172.45,4281.7,-263.093,-1310.59,4210.35,-238.334,-1339.84,4191.32,-235.362,-1301.3,4209.41,-236.226,-1330.71,4189.96,-232.434,-1244.68,4231,-240.914,-1287.34,4208.86,-233.269,-1292.99,4209.96,-234.858,-1253.9,4229.96,-242.452,-1322.93,4182.6,-228.104,-1325.51,4188.27,-230.479,-1205.73,4257.23,-252.068,-1194.29,4259.36,-249.628,-1158.97,4284.06,-258.987,-1145.11,4286.76,-255.743,-1178.39,4255.38,-242.189,-1232.79,4226.81,-235.596,-1142.13,4223.02,-204.978,-1197.06,4190.42,-201.6,-1123.56,4283.09,-245.967,-1089.08,4258.27,-210.749,-1281.15,4198.78,-228.131,-1316.47,4164.18,-221.158,-1245.8,4156.97,-197.242,-1290.04,4123.74,-192.549,-1185.48,4179.06,-133.868,-1241.08,4147.63,-123.094,-1236.46,4144.57,-160.244,-1184.48,4178.83,-166.127,-1286.35,4115.83,-119.494,-1283.34,4110.68,-158.797,-1129.67,4215.1,-171.212,-1127.39,4216.22,-140.14,-1076.6,4251.47,-177.868,-1072.81,4252.23,-146.215,-1130.78,4221.31,-109.824,-1194.16,4183.49,-103.025,-1137.49,4228.01,-82.4593,-1202.88,4189.13,-79.3765,-1074.57,4256.56,-115.9,-1079.92,4262.38,-87.8286,-1251.98,4154.6,-93.0795,-1295.47,4125.48,-87.3077,-1260.62,4159.19,-76.1452,-1304.4,4130.62,-70.066,-1210.89,4201.99,-43.9759,-1215.07,4209.73,-23.2251,-1270.29,4170.8,-33.605,-1267.61,4165.11,-52.4549,-1318.77,4138.05,-37.0035,-1314.4,4135,-50.7701,-1223.39,4220.41,1.17561,-1275.72,4182.35,-5.35217,-1324.56,4146.41,-13.0584,-1265.03,4162.17,-64.8369,-1207.66,4195.46,-61.1507,-1310.26,4133.31,-60.1676,-1144.44,4234.87,-60.0034,-1151.1,4240.9,-40.1898,-1086.38,4268.55,-62.2638,-1093.92,4275.24,-38.7426,-1159,4247.63,-19.3263,-1170.45,4256.26,2.59484,-1103.58,4282.66,-16.3459,-1116.88,4290.98,5.08708,-1254.91,4245.07,38.9424,-1273.44,4259.1,46.7583,-1318.77,4227.72,45.7313,-1302.15,4212.48,38.2661,-1363.22,4196.17,41.5908,-1347.4,4179.6,34.6695,-1287.25,4197.7,22.245,-1237.71,4232.26,23.3271,-1334.2,4162.62,16.789,-1186.55,4266.41,23.4909,-1205.79,4277.88,39.0911,-1134.59,4300.45,24.5238,-1155.5,4311.44,38.9233,-1226.49,4290.9,47.0239,-1178.06,4324.18,45.8968,-1707.53,4041.55,58.0382,-1739.22,4021.37,70.9113,-1725.9,4001,76.5583,-1694.54,4020.8,64.0765,-1713.23,3980.36,78.4248,-1681.9,3999.91,66.5687,-1772.79,3999.7,83.5055,-1759.23,3979.72,89.0692,-1746.6,3959.49,90.2464,-1664.81,4039.55,52.2054,-1677.45,4060.58,45.6396,-1635.65,4057.48,41.1248,-1648.27,4078.99,34.1662,-1651.57,4017.76,54.5688,-1622.41,4035.09,43.4628,-1693.07,4080.99,29.1571,-1724.24,4062.14,40.7391,-1704.52,4092.17,10.9523,-1734.76,4072.87,21.7862,-1662.76,4098.61,19.8531,-1674.21,4111.06,5.4875,-1757.46,4041.68,52.6819,-1788.71,4019.88,66.1989,-1766.72,4051.86,34.9082,-1797.85,4030.5,48.4348,-1748.58,4098.21,-7.48809,-1759.21,4108.05,-38.1015,-1792.19,4086.13,-24.1584,-1782.12,4076.98,5.69493,-1826.01,4061.82,-9.01724,-1815.8,4052.54,20.0843,-1760.82,4107.55,-67.8605,-1794.33,4085.32,-53.4756,-1828.62,4060.8,-37.4839,-1769.62,4060.52,24.6116,-1737.8,4081.64,11.075,-1803.92,4037.75,37.9076,-1710.14,4101.35,-1.13818,-1718.22,4117.71,-20.022,-1684.14,4124.33,-10.1592,-1691.16,4139.81,-33.944,-1727.2,4128.08,-50.7688,-1728.19,4127.95,-80.6841,-1696.27,4146.76,-62.1037,-1696.52,4147.03,-91.9961,-1748.54,4087.84,-129.154,-1737.07,4071.14,-157.062,-1773.69,4047.58,-140.162,-1784.04,4064.81,-113.103,-1811.07,4022.79,-120.641,-1820.24,4040.1,-94.7216,-1723.69,4051.54,-180.726,-1761.27,4027.35,-163.211,-1799.77,4002.4,-142.836,-1791.27,4077.8,-83.6641,-1756.86,4100.39,-98.8172,-1826.39,4053.16,-66.5204,-1723.24,4121.37,-112.09,-1713.77,4109.54,-143.013,-1690.47,4141.16,-123.595,-1679.74,4130.27,-154.814,-1701.18,4093.7,-171.483,-1686.87,4075.06,-195.528,-1665.93,4115.5,-183.567,-1650.66,4097.96,-207.763,-1695.09,4007.97,-209.55,-1733.67,3982.26,-192.017,-1747.82,4005.35,-180.62,-1709.59,4030.31,-198.335,-1773.76,3956.73,-171.317,-1787.23,3980.12,-159.86,-1672.26,4054.84,-213.177,-1657.6,4033.44,-224.128,-1635.55,4078.77,-225.318,-1620.79,4058.27,-235.962,-1643.15,4011.28,-228.078,-1680.48,3985.03,-214.033,-1629.18,3988.74,-224.725,-1666.06,3962.04,-211.443,-1606.6,4036.81,-239.424,-1593.16,4014.71,-235.435,-1719.14,3958.73,-197.029,-1759.7,3933.02,-176.809,-1704.55,3935.43,-195.283,-1745.35,3909.77,-175.939,-1639.44,3918.54,-185.382,-1676.99,3892.59,-171.618,-1690.21,3913.04,-186.406,-1652.14,3939.51,-201.444,-1717.71,3868.02,-154.994,-1731.06,3887.78,-168.309,-1615.93,3966.25,-213.768,-1604.04,3944.91,-196.661,-1580.69,3992.32,-223.725,-1569.71,3970.77,-205.83,-1594.13,3925.88,-174.86,-1628.74,3900.23,-164.604,-1586.85,3910.28,-149.823,-1620.76,3885.68,-140.458,-1560.75,3951.18,-183.285,-1554.31,3934.68,-157.626,-1665.72,3875.12,-152.134,-1706.23,3851.46,-137.07,-1657.25,3861.67,-129.173,-1697.53,3839.08,-115.611,-1614.67,3871.01,-87.0982,-1650.63,3849.66,-77.4932,-1652.44,3853.29,-103.955,-1616.26,3875.98,-114.292,-1690.47,3829.36,-66.332,-1692.51,3831.87,-91.6931,-1582.81,3899.25,-123.004,-1581.54,3892.83,-95.3828,-1550.92,3922.41,-130.388,-1550.16,3914.6,-102.527,-1583.12,3892.31,-66.476,-1616.11,3872.24,-58.1561,-1590.47,3903.31,-35.1537,-1623.89,3885.89,-26.6237,-1552.05,3912.58,-73.6622,-1559.17,3921.19,-42.7928,-1651.8,3852.65,-48.7788,-1691.25,3833.61,-38.3105,-1660.36,3869.76,-16.6487,-1699.35,3853.77,-5.90396,-1645.72,3913.32,4.82041,-1681.76,3898.46,12.686,-1673.46,3888.33,3.63249,-1635.35,3902.33,-6.06464,-1712.87,3884.7,20.3111,-1709.72,3874.51,13.4799,-1600.96,3918.83,-13.7805,-1610.98,3929.2,-2.4875,-1570.23,3935.24,-20.4396,-1579.48,3944.42,-8.83031,-1619.22,3934.9,4.97749,-1651.23,3918.99,11.1201,-1623.43,3939.88,10.801,-1652.89,3923.46,15.4518,-1587.47,3950.68,-0.991002,-1593.43,3956.66,6.58703,-1684.31,3903.66,17.3103,-1713.64,3889.71,24.4218,-1685.14,3907.36,20.7403,-1715.18,3892.96,28.6721,-1655.97,3942.55,34.7597,-1667.17,3973.62,59.6503,-1700.56,3956.82,73.0951,-1690.41,3925,44.7222,-1735.23,3938.08,85.4216,-1725.13,3911.32,61.1763,-1686.51,3911.88,26.6128,-1653.24,3929.15,20.9068,-1719.4,3897.16,37.5896,-1624.6,3947.18,17.0069,-1627.13,3962.06,29.3693,-1596.74,3967.1,15.2742,-1600.8,3985.22,27.2005,-1637.3,3991.03,48.4065,-1609.84,4010.72,39.6019,-1845.82,3950.33,106.808,-1884.09,3924.32,117.624,-1869.7,3906.99,124.709,-1831.88,3932.06,112.848,-1858.32,3889.57,125.422,-1820.31,3913.84,113.525,-1923.01,3898.55,128.018,-1907.06,3881.69,137.347,-1895.63,3864.89,137.996,-1794.86,3956.56,101.019,-1808.38,3975.49,95.8074,-1782.67,3937.25,101.88,-1826.11,3992.97,79.6594,-1864.25,3966.61,85.5068,-1839.78,4000.32,58.7764,-1873.09,3973.17,67.9609,-1903.52,3937.94,94.4346,-1936.83,3911.14,109.301,-1908.75,3943.33,82.6759,-1939.48,3917.92,98.5227,-1875.88,3986.44,51.6807,-1873.74,3978.14,60.3514,-1843.63,4005.18,49.6294,-1848.7,4017.73,37.1477,-1860.01,4033.43,8.46612,-1890.98,4001.85,28.5529,-1863.58,4033.5,-19.843,-1898.9,4004.34,-0.821576,-1925.85,3971.73,46.2049,-1910.1,3957.89,66.9469,-1961.28,3941.34,64.9593,-1946.78,3929.55,83.8184,-1934.31,3974.25,19.2388,-1969.21,3943.94,40.4212,-1907.93,3948.89,76.132,-1940.88,3922.46,92.4485,-1894.41,3985.22,-51.2879,-1898.28,3997.25,-26.453,-1862.16,4026.04,-47.2749,-1857.14,4013.35,-73.8732,-1849.3,3996.52,-98.3576,-1887.95,3969.23,-74.2168,-1839.36,3976.64,-119.453,-1879.51,3950.27,-94.1115,-1926.62,3941.36,-49.1218,-1931.71,3956.34,-27.6915,-1964.92,3913.35,-23.9775,-1968.72,3927.39,-3.81431,-1919.69,3923.5,-67.8602,-1959.4,3896.56,-41.7472,-1934.45,3967.6,-4.57869,-1970.39,3937.92,17.8215,-1858.82,3907.2,-121.003,-1902.03,3882.64,-93.836,-1911.42,3903.64,-82.8963,-1869.71,3929.3,-109.842,-1944.53,3857.84,-66.9578,-1952.54,3877.76,-56.2021,-1828.08,3954.78,-135.882,-1815.77,3931.81,-147.238,-1802.71,3908.56,-153.115,-1847.09,3884.85,-127.192,-1789.21,3885.89,-153.106,-1834.78,3863.12,-128.003,-1891.74,3861.39,-100.283,-1935.56,3837.67,-73.6323,-1880.76,3840.76,-101.843,-1925.8,3818.13,-75.8432,-1810.07,3824.98,-113.062,-1858.16,3804.79,-89.7813,-1869.3,3821.62,-98.1197,-1822.16,3842.87,-123.03,-1905.17,3784.32,-66.3094,-1915.44,3800.11,-73.2083,-1775.55,3864.66,-146.805,-1762.66,3845.77,-135.138,-1751.43,3830.15,-119.032,-1799.38,3810.31,-98.8844,-1742.78,3818.72,-99.4135,-1790.95,3799.72,-81.2838,-1848.11,3791.05,-77.4946,-1895.69,3771.49,-55.7266,-1839.94,3781.21,-61.9253,-1887.69,3762.33,-42.059,-1782.8,3792.84,-39.0187,-1831.68,3776.01,-22.253,-1834.44,3776.08,-43.727,-1785.64,3794.06,-61.0459,-1878.67,3759.94,-3.55875,-1881.93,3758.05,-25.0402,-1737.62,3812.39,-77.21,-1735.24,3810.66,-53.3825,-1735.67,3815.6,-26.354,-1782.87,3798.47,-13.1688,-1741.5,3834.82,4.4028,-1786.95,3813.46,14.2174,-1832.74,3782.58,2.90295,-1878.01,3767.19,18.9997,-1836.05,3794.09,25.9605,-1878.68,3776.78,37.761,-1790.87,3839.07,41.9036,-1789.92,3828.69,32.0213,-1747.02,3854.07,22.6781,-1747.83,3864.94,30.0586,-1748.3,3871.07,35.5046,-1792.39,3845.54,50.1317,-1751.45,3875.14,43.083,-1794.35,3851.19,61.8525,-1836.22,3820.8,62.2678,-1837.1,3814.01,51.7054,-1876.84,3798.48,73.2011,-1878.02,3792.27,61.2761,-1836.2,3828.47,76.2892,-1876.3,3806.67,88.8766,-1837.68,3805.32,41.368,-1878.83,3785.53,50.8376,-1802.28,3876.75,97.0298,-1797.18,3860.93,78.9131,-1756.34,3881.02,58.4161,-1762.69,3896.63,81.9463,-1771.81,3917.34,96.9639,-1810.32,3895.37,109.019,-1849.33,3872.56,121.147,-1842.18,3855.44,110.334,-1887.45,3849.04,133.425,-1881.3,3833.26,123.181,-1837.76,3839.97,93.7749,-1877.51,3818.46,107.11,-656.592,4935.11,-280.589,-629.38,4959.15,-261.375,-610.603,4955.78,-302.737,-636.887,4925.11,-324.067,-581.032,4950.2,-338.944,-605.636,4913.48,-360.827,-603.582,4979.71,-242.963,-586.238,4980.73,-282.815,-558.513,4979.25,-318.52,-672.257,4879.31,-346.151,-688.822,4906.27,-299.211,-714.949,4823.54,-353.164,-738.395,4845.62,-319.085,-638.876,4861.38,-381.371,-697.744,4917.83,-248.891,-665.112,4940.21,-233.326,-696.198,4915.02,-195.903,-662.247,4937.38,-184.235,-739.833,4885.33,-264.224,-737.005,4886.24,-205.671,-636.851,4959.17,-216.872,-609.862,4975.91,-200.933,-632.637,4954.49,-171.41,-604.648,4969.25,-158.725,-627.185,4892.07,-88.4584,-661.348,4857.42,-94.7184,-626.455,4831.34,-50.1149,-594.354,4864.58,-44.8553,-579.53,4813.61,-4.02611,-551.734,4842.97,-3.07311,-697.714,4828.84,-101.74,-662.788,4807.3,-56.704,-616.808,4788.93,2.66486,-564.554,4900.97,-41.2629,-595.309,4924.01,-82.9141,-536.683,4932.07,-39.1157,-566.107,4947.29,-77.6227,-526.156,4878.58,-1.95466,-501.512,4914.65,-2.47893,-617.977,4943.1,-126.529,-649.352,4920.44,-135.161,-589.145,4959.72,-117.583,-684.401,4889.99,-142.825,-723.09,4858.55,-149.288,-451.872,4814.34,69.9199,-464.174,4795.65,76.0882,-406.816,4787.85,107.537,-398.796,4804.13,98.5388,-350.418,4775.73,131.65,-346.444,4792.8,119.381,-475.394,4779.33,92.7589,-413.677,4772.78,125.31,-386.717,4829.06,91.1024,-436.457,4842.47,65.6293,-371.126,4865.75,81.9369,-418.276,4880.49,59.7014,-338.227,4816.85,108.735,-327.11,4852.29,96.3902,-482.938,4858.79,34.0032,-502.792,4826.72,35.5884,-461.968,4896.75,30.9073,-521.378,4803.2,38.9081,-539.532,4783.77,53.3412,-385.741,5767.87,-265.485,-387.652,5739.12,-276.426,-405.185,5729.97,-254.897,-403.422,5757.57,-246.548,-418.625,5722,-230.623,-416.898,5749.25,-224.841,-388.753,5707.49,-285.096,-405.801,5699.41,-261.878,-418.794,5692.05,-236.115,-400.6,5781.31,-236.51,-383.53,5793.21,-251.82,-398.852,5800.25,-222.967,-382.04,5814.6,-234.603,-413.192,5772.66,-218.677,-408.822,5788.82,-213.387,-361.561,5805.39,-265.411,-363.82,5778.55,-281.753,-335.54,5816.43,-276.833,-337.94,5788.37,-295.132,-359.879,5828.64,-245.579,-333.657,5841.03,-255.139,-366.013,5748.51,-294.935,-367.499,5715.64,-305.294,-340.255,5757.19,-310.147,-341.885,5723.22,-321.993,-235.255,4138.54,-350.312,-232.117,4076.95,-324.17,-279.633,4066.81,-340.484,-290.421,4126.79,-368.835,-301.049,4188.67,-394.111,-238.75,4200.91,-373.746,-310.634,4250.84,-415.362,-241.993,4260.66,-393.168,-203.862,4209.98,-364.013,-203.481,4146.97,-342.029,-180.308,4213.38,-361.632,-179.349,4150.2,-340.058,-204.489,4268.07,-381.907,-181.19,4270.9,-379.096,-203.366,4083.93,-317.216,-178.395,4086.62,-315.515,-510.816,4895.39,-409.757,-561.851,4904.15,-389.084,-541.229,4943.64,-368.288,-494.4,4936.46,-390.876,-521.187,4975.67,-348.405,-477.243,4970.52,-372.192,-443.522,4928.61,-406.877,-456.348,4885.89,-423.442,-429.342,4964.11,-389.648,-467.317,4834.08,-436.394,-525.975,4844.87,-426.139,-584.362,4853.92,-408.314,-278.674,4633.08,-450.847,-272.629,4566.24,-451.256,-352.6,4579.27,-462.242,-357.757,4645.58,-457.963,-265.197,4502.18,-446.956,-345.137,4511.46,-462.871,-358.385,4706.83,-452.092,-280.913,4697.39,-445.825,-222.126,4687.86,-435.367,-219.908,4622.02,-439.657,-187.824,4685.06,-431.298,-186.718,4619.11,-435.59,-216.25,4556.35,-438.984,-212.706,4496.01,-434.023,-185.645,4553.97,-435.082,-184.685,4494.73,-430.38,-250.923,4378.36,-425.356,-207.228,4379.11,-412.071,-205.479,4323.06,-397.403,-245.721,4318.79,-409.997,-182.873,4379.85,-408.701,-182.022,4324.88,-394.259,-319.87,4313.6,-433.382,-329.098,4378.13,-448.071,-337.328,4444.29,-458.152,-257.903,4440.06,-438.228,-209.757,4437.54,-424.996,-183.771,4437.28,-421.502,-447.437,3877.2,-272.845,-499.444,3876.48,-241.75,-511.927,3930.56,-271.461,-457.965,3931.4,-299.163,-538.319,3880.3,-194.662,-555.533,3932.27,-222.946,-395.671,3935.36,-309.4,-387.186,3881.13,-284.764,-382.947,3828.77,-263.244,-440.806,3827.06,-249.402,-382.047,3778.48,-247.072,-437.267,3780.29,-232.899,-490.803,3830.04,-218.223,-526.176,3839.59,-178.451,-485.272,3788.28,-204.685,-535.489,4706.99,221.121,-594.234,4712.22,182.304,-605.132,4648.45,216.62,-547.337,4644.97,252.54,-613.302,4585.71,239.372,-556.079,4582.61,276.288,-655.32,4704.38,138.627,-663.509,4643.81,171.703,-669.874,4583.38,192.235,-486.518,4638.55,283.66,-469.488,4697.81,246.289,-496.378,4577.86,309.061,-429.291,4748.23,165.703,-492.837,4755.01,134.262,-555.18,4758.45,97.5839,-623.573,4758.79,62.3273,-570.592,4457.54,312.952,-572.044,4393.54,322.282,-510.734,4390.73,362.594,-509.916,4453.95,349.79,-567.671,4327.46,326.064,-506.298,4326.41,369.768,-504.206,4516.33,330.91,-564.242,4520.38,296.771,-620.544,4524.4,256.583,-625.954,4462.42,268.231,-673.287,4528.51,209.052,-676.321,4470.68,219.774,-628.454,4397.82,271.95,-626.087,4328.62,269.509,-679.221,4406.86,215.024,-676.196,4330.31,186.748,-281.949,3959.71,423.48,-278.298,3889.65,420.735,-228.265,3891.79,425.998,-230.618,3962.5,429.633,-178.105,3893.5,427.536,-179.292,3964.48,431.498,-275.441,3815.12,417.373,-226.017,3816.22,422.852,-176.864,3818.96,424.986,-234.017,4028.7,428.376,-286.811,4026.57,421.976,-180.374,4030.12,430.882,-339.603,4024.73,410.878,-333.27,3957.76,412.36,-392.623,4024.1,394.993,-384.464,3957.82,396.114,-328.129,3888.52,410.714,-325.308,3816.16,407.699,-377.769,3889.83,394.521,-273.958,5912.76,98.5861,-272.635,5895.03,141.698,-241.763,5900.47,143.745,-242.474,5918.07,100.338,-210.169,5902.5,144.63,-210.289,5920.04,101.162,-270.587,5874.75,180.884,-240.643,5880.23,183.142,-209.99,5882.29,184.052,-242.851,5932.08,55.124,-274.699,5926.92,53.7497,-242.966,5941.51,10.3109,-274.998,5936.5,9.38925,-210.343,5933.97,55.8478,-210.322,5943.34,10.8858,-305.276,5917.94,52.2656,-304.091,5903.62,96.3824,-305.849,5927.7,8.67082,-302.116,5885.79,138.853,-299.17,5865.54,177.512,-263.1,4767.94,145.025,-267.955,4796.42,128.303,-300.045,4805.92,119.706,-300.732,4780.04,133.684,-269.625,4830.79,113.887,-295.919,4840.42,105.76,-298.694,4758.35,151.314,-258.651,4742.33,166.374,-223.965,4725.27,181.001,-227.655,4758.92,154.031,-190.428,4691.42,209.6,-191.274,4753.93,160.387,-232.153,4793.32,134.087,-235.137,4826.54,120.844,-192.107,4812.05,129.383,-192.406,4831.45,123.993,-276.062,5801.76,-312.638,-274.678,5831.03,-291.849,-241.427,5834.83,-295.501,-241.893,5805.37,-316.877,-207.357,5836.42,-296.62,-206.787,5806.96,-318.179,-273.573,5856.96,-267.761,-241.087,5860.94,-270.841,-207.886,5862.57,-271.777,-242.3,5772.94,-334.716,-277.37,5769.48,-329.948,-242.467,5737.87,-348.771,-278.246,5734.51,-343.603,-206.181,5774.51,-336.182,-205.547,5739.45,-350.355,-310.365,5764.2,-321.789,-308.382,5796.1,-305.409,-311.759,5729.57,-334.721,-306.314,5824.94,-285.637,-304.659,5850.39,-262.53,-1481.56,4186.03,-15.344,-1498.58,4171,-18.7235,-1494.2,4159.56,-11.5149,-1474.52,4174.59,-5.79798,-1487.21,4144.98,-4.64679,-1464.24,4159.79,2.79152,-1512.95,4158.58,-16.6659,-1509.77,4146.08,-10.1174,-1504.77,4130.5,-3.48151,-1443.93,4196.4,7.94223,-1455.25,4208.15,-5.77927,-1428.41,4181.63,20.2562,-1462.76,4217.8,-19.7962,-1485.87,4195.5,-24.9651,-1466.81,4226.53,-34.5875,-1488.26,4204.34,-35.3039,-1501.39,4180.55,-25.8652,-1515.88,4168.92,-23.1072,-1503.85,4189.48,-33.8435,-1520.13,4178.32,-30.5622,-1489.61,4223.41,-65.9567,-1489.4,4213.53,-48.0577,-1467.58,4235.18,-51.6746,-1465.19,4243.73,-73.3553,-1459.56,4249.96,-101.539,-1488.49,4231.8,-92.2489,-1448.85,4242.15,-140.572,-1479.47,4229.23,-130.807,-1516.55,4218.06,-86.0171,-1511.71,4208.78,-60.6841,-1549.51,4206.71,-82.8522,-1537.63,4197.64,-57.7561,-1516.15,4219.85,-121.723,-1554.97,4210.82,-117.356,-1507.01,4198.76,-44.5363,-1527.18,4187.78,-41.1454,-1456.57,4211.82,-176.448,-1458.86,4216.88,-164.811,-1438.9,4229.03,-166.051,-1435.22,4223.61,-177.436,-1431.66,4220.77,-190.353,-1455.36,4209.5,-186.068,-1424.16,4218.97,-216.311,-1449.72,4207.27,-206.621,-1482.04,4195.93,-192.838,-1482.8,4200.12,-181.638,-1503.82,4182.8,-197.307,-1512.03,4189.13,-184.336,-1478.37,4192.37,-208.947,-1501.78,4177.56,-208.243,-1492.96,4208.59,-160.368,-1545.21,4205.53,-150.928,-1408.45,4174.78,-258.216,-1441.91,4163.15,-258.862,-1463.87,4181.5,-241.561,-1431.74,4196.27,-248.924,-1478.61,4148.1,-250.762,-1495.56,4166.74,-229.335,-1404.51,4208.58,-252.33,-1382.59,4184.49,-248.802,-1368.11,4171.98,-237.481,-1393.06,4158.57,-246.452,-1359.35,4167.86,-231.999,-1385.18,4149.86,-237.393,-1422.83,4146.13,-256.689,-1461.29,4128.59,-256.101,-1413.3,4133.39,-247.644,-1450.57,4111.9,-250.566,-1379.64,4125.1,-224.98,-1407.3,4096.09,-226.619,-1409.98,4118.82,-238.098,-1381.79,4141.27,-231.513,-1437.95,4068.51,-226.285,-1444.46,4093.29,-240.603,-1354.82,4163.68,-228.863,-1352.86,4153.23,-225.206,-1345.11,4128.47,-215.193,-1371.7,4094.99,-210.74,-1327.77,4093.3,-187.216,-1362.52,4067.53,-183.583,-1400.19,4066.37,-207.585,-1430.28,4042.12,-204.381,-1394.87,4044.86,-180.799,-1426.21,4023.77,-177.896,-1363.7,4051.09,-123.748,-1396.81,4023.81,-124.262,-1394.47,4032.11,-153.007,-1361.29,4055.62,-155.159,-1428.26,4000.05,-122.058,-1425.88,4009.99,-150.195,-1324.81,4081.23,-157.236,-1328.08,4081.51,-121.324,-1335.74,4092.02,-86.4929,-1368.92,4057.84,-90.2864,-1344.2,4100.65,-66.0848,-1375.14,4069.56,-65.4502,-1401.16,4023.25,-94.2504,-1433.25,3995.4,-93.3721,-1407.15,4030.79,-66.9443,-1441.49,3998.16,-65.4328,-1389.02,4085.05,-39.6899,-1381.63,4079.09,-50.309,-1351.12,4105.05,-55.0841,-1357.42,4107.08,-45.7023,-1364.38,4109.39,-34.8132,-1397.19,4089.49,-29.8353,-1371.94,4114.68,-19.3326,-1406.5,4095.09,-18.3666,-1424.45,4067.81,-22.611,-1417.86,4056.94,-33.0167,-1457.51,4034.91,-16.0666,-1454.73,4020,-28.9512,-1433.51,4078.06,-12.6675,-1462.83,4052.32,-5.00089,-1412.67,4043.89,-46.7022,-1449.89,4007.49,-43.7753,-1433.81,4121.13,5.01233,-1418.46,4104.9,-5.43123,-1381.04,4126.53,2.08409,-1393.39,4144.49,20.4979,-1410.02,4163.86,26.4824,-1450.29,4141.19,7.64176,-1476.45,4126.97,0.298649,-1461.83,4107.54,0.863642,-1496.61,4111.73,2.22456,-1485.08,4091.5,5.01902,-1446.22,4090.73,-4.12843,-1472.48,4071.26,2.58651,-1990.71,3847.92,153.995,-1977.26,3833.27,164.416,-1945.27,3857.11,150.019,-1962.84,3871.43,138.946,-1964.74,3816.37,166.089,-1931.52,3840.48,151.538,-1971.75,3880.21,128.048,-1997.56,3856.63,144.502,-1974.08,3887.15,120.328,-2001.99,3862.72,138.442,-2025.6,3833.94,167.505,-2017.22,3822.9,176.98,-2043.46,3816.93,189.226,-2031.18,3803.53,195.313,-2031.7,3842.46,159.331,-2005.56,3807.69,183.119,-1994.72,3791.94,182.169,-2017.9,3786.64,198.001,-1998.72,3756.4,184.226,-2007.21,3768.25,194.245,-2026.44,3746.89,214.297,-2015.46,3735.1,202.491,-2016.88,3878.81,122.782,-2006.83,3868.63,132.876,-1975.66,3894.36,112.853,-1981.53,3904.09,102.101,-1995.14,3913.83,83.7757,-2030.5,3887.45,103.437,-2003.5,3914.79,61.7972,-2037.45,3887.35,82.1469,-2061.87,3862.45,122.017,-2050.14,3857.68,139.895,-2090.77,3839.88,138.625,-2083.04,3837.41,156.158,-2068.38,3861.7,101.881,-2099.39,3833.31,100.118,-2100.88,3824.41,80.8411,-2116.4,3812.06,85.9512,-2113.77,3821.84,106.346,-2038.73,3849.84,151.421,-2073.52,3831.57,171.121,-2039.9,3871.92,41.9619,-2039.72,3881.54,61.8948,-2005.79,3909.03,40.2184,-2005.1,3899.02,19.6168,-2002.44,3885.67,0.310212,-2038.2,3859.09,23.2013,-1998.1,3869.65,-16.8235,-2034.83,3843.65,6.45658,-2071.24,3834.42,44.1514,-2072.15,3846.81,62.5836,-2098.32,3812.44,55.91,-2068.62,3819.45,27.6375,-2071.23,3856.09,82.1042,-2023.83,3807.7,-18.0946,-2058.79,3784.41,3.18304,-2064.41,3802.46,13.8698,-2029.98,3826.21,-7.42754,-2093.06,3778.8,23.5165,-1992.38,3851.63,-30.9153,-1985.44,3832.52,-41.5919,-1977.46,3813.19,-48.4845,-2016.55,3789.04,-25.1877,-1968.59,3794.57,-51.2221,-2008.3,3771.16,-28.3524,-2051.96,3766.28,-4.0881,-2044.08,3749.04,-7.61086,-1989.92,3741.07,-22.3695,-2026.24,3720.5,-2.93712,-2035.35,3733.65,-7.05045,-1999.24,3755,-27.2319,-2054.3,3695.08,5.60461,-2063.83,3706.98,1.04341,-1959.01,3777.53,-49.4328,-1949.33,3762.72,-43.6601,-1940.15,3750.74,-34.4663,-1980.86,3729.9,-14.2114,-1932.12,3742.33,-22.0994,-1972.54,3722.28,-2.81371,-2017.21,3709.99,4.21551,-2044.08,3684,12.9509,-2008.63,3702.64,14.0489,-1959.82,3720.7,28.1274,-1965.51,3719.28,11.6635,-1925.98,3738.94,-5.85969,-1921.71,3740.93,13.4314,-1919.23,3747.3,33.0004,-1955.41,3725.96,45.1061,-1917.99,3755.59,49.6688,-1952.35,3733.52,60.7606,-1987.82,3703.28,55.3119,-1993.92,3699.54,40.2748,-2010.11,3677.21,58.2682,-2017.71,3674.48,44.6296,-1982.72,3709.71,70.4705,-2003.64,3682.51,72.3581,-2020.76,3667.27,77.915,-2027.2,3664.41,64.5366,-2000.87,3699.2,26.249,-2025.45,3673.84,33.1549,-2060.96,3675.78,22.919,-2033.66,3676.02,23.4563,-2050.99,3667.97,32.3188,-1948.89,3747.87,86.1265,-1950.46,3741.28,74.0334,-1917.03,3763.46,62.4049,-1915.69,3769.92,73.3935,-1914.29,3776.27,86.2776,-1947.48,3754.48,99.9761,-1913.74,3784.74,102.449,-1947.18,3763.02,116.458,-1975.45,3732.7,114.348,-1976.49,3725.27,99.1309,-1991.26,3711.23,117.862,-1992.76,3700.54,102.328,-1975.89,3741.39,131.312,-1992.25,3722.7,134.331,-2005.15,3698.99,146.667,-2005.12,3688.97,129.007,-1978.84,3717.47,85.0163,-1997.04,3690.77,87.5851,-2034.68,3663.78,51.8706,-2042.51,3664.67,41.2818,-1951.63,3787.26,149.503,-1948.45,3774.07,133.89,-1914.7,3796.36,120.402,-1917.81,3810.49,136.125,-1923.4,3825.32,146.599,-1956.8,3801.44,160.465,-1987.02,3777.78,175.175,-1981.54,3764.37,163.806,-1993.95,3744.66,170.411,-1991.86,3732.9,152.933,-2008.82,3723.19,186.797,-2005.24,3710.36,167.556,-1977.89,3752,148.621,-2007.77,3680.16,111.576,-2013.12,3672.78,94.7495,-760.927,4683.79,92.3717,-816.16,4663.25,74.8871,-804.538,4615.87,99.2297,-752.693,4630.07,121.566,-793.114,4570.21,113.516,-741.126,4578.44,141.347,-704.377,4636.4,144.677,-705.374,4698.13,116.153,-704.223,4578.3,159.974,-700.713,4755.6,58.9482,-765.837,4735.96,48.2627,-691.456,4780.38,-15.2773,-760.513,4771.88,-18.5768,-827.034,4709.21,39.1671,-835.343,4748.61,-9.14739,-787.4,4799.99,-116.397,-855.141,4791.22,-114.874,-841.083,4775.08,-65.4396,-766.603,4787.36,-74.0188,-710.502,4795.08,-68.7411,-738.377,4810.46,-110.147,-765.559,4832.14,-155.361,-813.712,4814.63,-161.347,-784.493,4852.33,-214.457,-833.326,4826.09,-221.766,-874.448,4802.86,-165.26,-887.791,4808.34,-223,-818.709,4808.7,-312.296,-827.487,4820.71,-274.581,-787.149,4840.14,-279.003,-769.784,4822.93,-317.049,-750.961,4809.13,-347.941,-822.581,4773.78,-361.852,-742.567,4771.61,-392.961,-813.115,4728.6,-394.692,-887.17,4733.26,-361.739,-895.51,4770.22,-324.047,-874.182,4689.63,-388.192,-894.076,4798.26,-274.926,-787.716,4613.47,-417.079,-801.7,4673.01,-413.6,-739.23,4704.3,-422.999,-729.491,4634.73,-429.159,-719.424,4570.35,-421.737,-773.666,4557.44,-409.465,-714.759,4516.64,-412.615,-763.972,4510.2,-399.002,-825.099,4542.28,-395.157,-841.749,4589.05,-403.078,-811.434,4502.09,-384.423,-858.84,4640.29,-402.216,-758.846,4417.23,-369.254,-759.295,4464.76,-387.169,-714.205,4463.98,-403.937,-715.718,4406.97,-390.303,-717.975,4352.02,-365.781,-760.052,4374.93,-341.251,-722.062,4308.37,-327.15,-761.84,4346.71,-302.401,-796.378,4392.64,-316.399,-797.85,4423.3,-348.125,-795.14,4376.6,-277.463,-801.968,4462.59,-370.333,-766.086,4344.34,-195.637,-765.318,4338.56,-253.061,-731.646,4290.18,-271.457,-735.054,4285.64,-196.725,-714.238,4280.31,-112.983,-745.736,4343.41,-121.613,-724.008,4321.19,-80.3826,-745.253,4378.11,-77.4538,-772.356,4390.43,-133.003,-787.783,4382.57,-189.336,-759.949,4408.21,-80.672,-793.065,4375.44,-234.457,-752.259,4412.68,-36.3959,-749.907,4403.38,-57.5195,-735.417,4358.52,-57.4134,-736.707,4359.38,-1.24922,-714.411,4285.48,-51.2044,-713.29,4282.04,58.9321,-723.907,4351.61,89.119,-752.948,4410.61,18.4166,-717.651,4378.95,130.175,-747.458,4416.85,82.9166,-695.044,4300.37,120.869,-768.059,4434.38,-15.4276,-762.519,4429.09,-42.2659,-774.286,4443.1,27.4824,-759.971,4421.53,-58.0974,-739.887,4487.63,143.997,-780.939,4489.2,101.294,-778.251,4459.69,68.7913,-745.819,4445.59,118.041,-717.651,4429.96,161.424,-711.311,4484.67,173.724,-705.567,4532.82,170.342,-734.627,4532.56,150.774,-784.903,4527.86,115.732,-536.454,4197.2,317.892,-481.015,4197.75,364.021,-496.382,4260.96,370.997,-554.926,4258.93,324.306,-605.476,4251.88,263.753,-581.765,4197.85,264.893,-644.076,4254.56,198.762,-620.216,4195.81,210.735,-565.189,4150.32,274.263,-519.479,4151.86,322.87,-552.269,4088.95,283.745,-507.193,4091.25,332.445,-603.998,4142.07,220.923,-465.45,4149.74,361.543,-455.046,4090.7,367.688,-255.997,3221.48,-314.88,-321.437,3226.39,-318.807,-311.96,3285.81,-294.423,-255.75,3280.8,-290.279,-303.31,3338.86,-270.625,-246.748,3335.43,-265.565,-383.349,3229.06,-315.533,-372.872,3292.06,-294.664,-365.441,3357.58,-263.794,-197.116,3261.67,-289.365,-192.688,3204.8,-306.733,-164.76,3254.51,-289.233,-163.793,3199.49,-304.347,-196.983,3329.87,-262.271,-166.058,3326.67,-261.908,-188.343,3161.09,-315.262,-250.482,3165.32,-328,-182.164,3125.05,-316.855,-237.462,3115.49,-329.252,-163.112,3159.87,-311.216,-162.58,3128.08,-313.025,-321.23,3162.62,-332.397,-388.936,3159.29,-326.824,-311.094,3098.34,-333.531,-391.001,3071.76,-324.748,-462.601,3261.77,285.379,-431.216,3227.76,314.059,-440.568,3275.69,301.221,-468.349,3303.25,273.708,-443.924,3322.68,301.539,-470.861,3346.89,274.251,-398.347,3186.33,338.556,-406.822,3248.44,335.907,-404.493,3305.37,342.997,-499.877,3327.78,242.218,-499.878,3291.32,247.442,-533.375,3351.85,204.768,-533.845,3319.72,210.829,-501.458,3370.24,244.906,-536.584,3389.56,206.079,-512.889,3246.73,262.022,-466.199,3210.34,302.243,-541.023,3168.14,278.001,-486.48,3133.78,312.82,-548.193,3273.82,225.025,-579.113,3198.04,237.308,-420.587,3160.4,328.297,-380.251,3109.91,337.268,-414.282,3089.48,331.497,-360.053,3040.3,330.103,-601.931,3356.46,129.041,-565.575,3343.12,174.715,-562.857,3373.46,169.137,-595.877,3391.64,126.14,-567.945,3408.63,167.646,-599.341,3426.82,124.245,-622.546,3395.87,67.3525,-628.948,3361.77,67.594,-633.566,3396.13,14.9239,-641.319,3357.44,14.4632,-625.463,3434.64,69.5273,-636.506,3438.43,13.0136,-645.914,3302.44,71.0177,-623.716,3299.84,130.586,-654.479,3242.21,74.2177,-638.614,3234.1,132.855,-653.938,3299.61,11.9152,-661.48,3242.03,14.451,-584.829,3293.78,183.179,-613.693,3218.98,188.103,-619.795,3333.95,-98.8774,-603.74,3382.92,-93.6645,-583.245,3374.7,-133.109,-604.412,3313.09,-145.878,-554.369,3358.97,-180.845,-574.401,3291.26,-197.138,-599.146,3415.38,-93.287,-568.81,3407.48,-132.945,-536.203,3400.39,-166.623,-610.878,3245.5,-149.172,-634.067,3275.86,-102.08,-611.963,3176.09,-137.604,-629.943,3199.03,-107.254,-585.595,3229.57,-195.844,-595.07,3165.54,-184.561,-649.944,3290.71,-46.6744,-638.267,3347.14,-48.3901,-657.878,3231.6,-45.0612,-626.081,3392.79,-47.7451,-626.178,3431.36,-45.7522,-490.421,3254.62,-273.286,-479.133,3315.37,-257.655,-429.921,3302.15,-281.562,-440.1,3239.2,-300.441,-459.429,3384.04,-225.009,-416.169,3374.09,-246.006,-449.764,3172.89,-311.173,-501.781,3191.92,-281.761,-461.799,3099.77,-309.433,-515.159,3124.72,-280.764,-547.163,3212.07,-243.322,-535.413,3272.63,-238.267,-559.681,3147.19,-239.053,-522.574,3334.71,-223.016,-503.59,3392.1,-195.169,-283.492,3158.05,394.98,-223.633,3154.47,403.849,-225.031,3221.43,414.165,-283.197,3225.99,405.08,-225.622,3284.98,421.961,-283.427,3288.89,409.625,-165.806,3153.63,406.059,-166.958,3220.78,416.962,-168.046,3284.67,425.448,-347.4,3232.66,380.111,-351.565,3157.34,365.415,-343.55,3296.43,387.685,-349.037,3080.75,343.232,-294.713,3082.27,371.17,-336.886,3028.42,332.248,-296.546,3018.97,349.855,-220.811,3086.87,391.666,-164.64,3085.97,393.503,-218.859,3020.41,376.671,-163.506,3020.45,380.07,-325.596,2852.05,318.486,-362.078,2903.65,334.206,-383.236,2849.23,341.271,-344.149,2807.09,324.245,-397.053,2794.87,346.252,-357.388,2759.44,327.988,-400.475,2946.26,338.519,-425.178,2888.46,345.172,-439.784,2829.92,351.031,-308.832,2770.66,287.274,-291.8,2812.49,283.484,-286.937,2738.93,232.268,-273.298,2778.65,236.737,-322.297,2726.48,288.978,-299.961,2696.54,228.837,-279.429,2839.27,283.391,-311.137,2886.52,316.2,-269.739,2853.5,285.943,-299.473,2907.06,317.331,-262.741,2805.85,242.959,-253.056,2821.26,248.655,-342.538,2949.8,327.318,-371.405,2999.89,330.879,-331.658,2973.99,325.736,-190.234,2882.83,-158.115,-198.613,2913.24,-217.405,-178.773,2924.85,-216.746,-174.834,2893.15,-157.871,-159.665,2928.63,-216.179,-159.365,2896.57,-157.326,-205.856,2943.74,-259.248,-179.728,2961.26,-260.055,-160.132,2967.41,-260.524,-172.366,2871.48,-89.013,-185.357,2862.91,-89.5963,-171.525,2853.73,-14.4402,-183.744,2846.43,-15.3109,-159.265,2874.23,-88.4529,-159.254,2855.95,-13.815,-198.221,2847.76,-89.192,-205.493,2865.13,-156.915,-211.021,2825.17,-86.5775,-220.597,2839.91,-153.333,-195.864,2833.12,-15.488,-207.949,2812.94,-13.9277,-218.627,2893.15,-216.852,-235.805,2919.98,-258.563,-238.05,2863.67,-212.719,-263.743,2889.87,-254.146,-206.122,3034.34,-312.137,-217.344,3073.61,-322.701,-174.935,3095.3,-313.543,-172.26,3064.17,-307.412,-162.148,3101.53,-311.368,-161.687,3072.5,-306.691,-174.737,3017.62,-293.268,-206.157,2987.87,-293.621,-160.989,3027.21,-293.822,-250.884,2953.5,-291.829,-266.19,2996.49,-314.14,-289.441,2919.38,-283.646,-315.966,2958.17,-304.503,-286.775,3043.45,-327.229,-347.807,3006.1,-318.303,-192.908,2814.15,142.747,-185.431,2829.4,66.3275,-172.35,2835.69,67.5657,-176.161,2819.98,145.158,-159.271,2837.53,68.3665,-159.315,2821.77,146.361,-184.331,2819.43,209.724,-206.338,2814.76,206.13,-194.97,2834.67,266.528,-223.002,2832.97,259.751,-159.557,2821.11,211.374,-160.021,2835.3,270.266,-224.714,2806.67,201.461,-209.509,2803.52,140.28,-240.038,2794.52,196.243,-225.964,2787.52,138.71,-241.42,2829.2,253.789,-198.624,2817.53,65.7158,-212.101,2799.22,67.1421,-511.552,3015.39,316.736,-556.191,3047.05,287.307,-565.481,2982.5,293.728,-520.69,2955.36,321.705,-577.541,2910.83,301.716,-532.362,2888.64,328.721,-596.201,3072.7,248.416,-606.131,3004.09,255.872,-618.716,2928.04,264.523,-473.939,2923.16,339.333,-459.65,2980.06,335.575,-485.65,2861.38,345.129,-431.188,3037.51,332.804,-502.825,3072.03,313.9,-549.293,3106.78,282.392,-588.599,3135.54,242.23,-655.668,3104.02,145.757,-673.826,3109.13,86.7043,-687.303,3032.76,95.0759,-667.782,3029.42,154.257,-702.407,2945.75,103.292,-682.137,2946.49,162.798,-683.219,3106.65,25.7104,-698.105,3029.33,33.5544,-714.284,2941.78,43.4414,-640.47,3019.77,208.635,-629.571,3091.77,200.455,-653.869,2940.14,217.63,-621.009,3157.41,193.5,-645.974,3172.01,138.534,-662.876,3178.92,79.6713,-670.97,3177.63,19.2127,-644.597,3079.73,-106.055,-676.644,3004.88,-93.5722,-698.392,3019.22,-28.3414,-679.35,3096.28,-37.1355,-709.137,2921.88,-80.6425,-717.627,2934.91,-19.0749,-664.976,3166.05,-43.8707,-629.229,3142.12,-108.903,-616.719,3120.77,-134.818,-626.197,3056.14,-138.509,-605.333,3100.67,-174.03,-615.686,3032.64,-168.953,-643.952,2988.23,-139.779,-679.374,2909.33,-137.157,-628.941,2966.95,-169.705,-655.608,2891.58,-174.983,-548.875,2982.13,-255.886,-563.333,2913.09,-241.766,-601.907,2939.27,-211.658,-588.662,3009.29,-219.883,-585.283,2845.4,-238.39,-624.986,2866.38,-211.712,-574.347,3079.92,-230.948,-532.109,3054.33,-271.23,-485.055,3025.94,-295.695,-506.45,2954.72,-273.976,-434.956,2993.66,-305.363,-464.832,2926.77,-281.081,-523.523,2890.6,-257.058,-543.824,2828.67,-253.495,-486.293,2867.47,-264.115,-506.922,2805.44,-262.081,-250.272,2767.82,-137.117,-264.038,2722.74,-126.934,-290.809,2737.45,-179.503,-276.562,2784.83,-191.577,-328.402,2759.77,-218.37,-314.125,2808.88,-229.283,-277.102,2672.92,-117.165,-304.554,2684.35,-168.854,-340.684,2701.93,-209.878,-258.591,2826.05,-203.452,-235.696,2807.13,-146.365,-291.694,2851.19,-242.779,-223.66,2794.75,-80.7614,-235.792,2757.78,-73.3576,-219.914,2785.9,-9.61489,-230.613,2752.06,-2.90167,-247.582,2714.45,-65.4538,-259.254,2666.09,-56.6989,-242.661,2709.59,7.29957,-257.182,2661.22,20.9553,-387.754,2864.02,-270.482,-409.274,2811.38,-258.479,-448.474,2839.89,-265.101,-425.276,2895.5,-279.535,-425.486,2749.51,-254.906,-467.544,2776.97,-262.506,-392.422,2951.66,-300.569,-356.365,2912.84,-288.298,-323.832,2879.19,-269.427,-351.117,2834.91,-254.092,-368.91,2784.42,-243.498,-382.226,2724.11,-238.293,-256.781,2740.39,139.445,-265.277,2752.25,185.466,-279.059,2717.57,180.667,-272.81,2705.47,140.431,-293.255,2676.1,176.175,-289.003,2663.56,141.215,-257.803,2702.53,89.269,-241.059,2742.09,80.1117,-276.217,2657.21,98.5165,-226.167,2773.9,72.2756,-241.92,2766.46,138.702,-253.138,2777.22,190.838,-683.115,4182.71,91.4653,-672.259,4123.91,111.756,-636.241,4132.33,173.045,-654.868,4191.78,156.464,-672.547,4247.24,136.786,-694.123,4231.55,78.3842,-704.066,4219.34,-6.36485,-693.216,4164.07,21.5022,-682.588,4114.77,32.7,-293.399,5185.52,160.917,-294.591,5188.91,187.9,-310.079,5202.32,176.726,-308.066,5197.3,148.813,-324.012,5216.02,166.302,-321.128,5209.01,138.134,-296.037,5193.33,211.78,-311.627,5207.52,202.052,-325.636,5222.43,192.458,-306.368,5189.7,118.796,-293.338,5180.63,131.879,-305.753,5177.29,90.4827,-295.057,5171.32,102.937,-317.457,5198.6,108.046,-314.906,5183.09,81.0376,-277.878,5171.3,145.486,-277.183,5174.07,172.672,-261.005,5162.13,156.863,-260.461,5164.09,181.963,-281.053,5164.9,117.433,-264.069,5158.01,131.404,-278.057,5176.59,197.951,-279.565,5180.67,220.801,-261.454,5166.41,205.848,-262.821,5170.16,228.979,-228.733,5151.43,192.049,-229.341,5153.55,216.623,-245.253,5158.78,212.058,-244.402,5156.64,187.993,-230.182,5157.31,240.968,-246.384,5162.45,235.781,-244.386,5154.6,164.404,-228.536,5149.34,168.711,-246.219,5151.12,141.71,-229.266,5145.72,147.575,-213.093,5146.23,170.966,-213.26,5148.4,194.487,-197.774,5145.32,171.718,-197.903,5147.56,195.329,-213.237,5142.44,150.341,-197.629,5141.46,151.145,-213.633,5150.61,219.45,-214.146,5154.52,244.298,-198.039,5149.84,220.461,-198.203,5153.84,245.53,-261.131,5508.2,-335.416,-269.765,5547.34,-351.269,-236.232,5548.28,-355.16,-230.287,5504.61,-335.392,-202.38,5549,-356.167,-201.71,5503.4,-334.82,-273.83,5583.4,-358.958,-238.952,5586.03,-364.041,-202.979,5587.49,-365.548,-220.044,5454.01,-303.702,-244.981,5462.96,-309.165,-211.442,5416.02,-278.06,-227.792,5423.4,-283.348,-200.975,5450.94,-301.318,-200.46,5413.81,-276.286,-277.181,5474,-312.851,-293.335,5511.53,-331.697,-311.116,5482.27,-309.361,-323.238,5512.98,-322.076,-255.121,5436.26,-290.529,-291.933,5450.56,-293.455,-301.769,5545.81,-343.458,-306.412,5580.03,-349.942,-330.439,5543.76,-331.097,-335.471,5576.49,-336.716,-227.122,5259.12,-279.581,-216.666,5302.92,-263.899,-206.684,5306.26,-262.213,-211.398,5264.11,-277.545,-198.756,5307.16,-261.563,-198.014,5265.75,-276.666,-211.705,5327.22,-257.099,-204.711,5328.81,-256.112,-199.144,5329.25,-255.749,-217.142,5207.97,-299.343,-238.568,5205.8,-300.005,-220.436,5164.03,-315.64,-245.135,5164.49,-315.209,-196.987,5208.8,-298.763,-196.182,5163.97,-315.345,-262.058,5202.94,-299.173,-247.321,5251.24,-281.482,-287.312,5200.5,-294.756,-272.578,5242.38,-280.711,-270.482,5165.27,-312.596,-296.251,5166.36,-306.175,-231.335,5296.06,-266.079,-222.225,5323.63,-258.348,-253.686,5285.6,-267.278,-239.865,5316.88,-259.159,-270.197,4997.7,-377.579,-272.996,4965.27,-389.086,-320.422,4977.8,-389.707,-312.656,5006.98,-376.623,-367.752,4987.24,-385.092,-355.631,5014.34,-370.838,-304.419,5033.47,-364.254,-266.078,5026.77,-366.132,-296.265,5058.5,-352.728,-261.415,5053.62,-355.189,-343.542,5039.02,-357.718,-331.914,5062.68,-345.774,-229.148,5020.98,-365.646,-230.186,4989.3,-376.308,-193.594,5018.88,-364.919,-193.012,4986.09,-375.294,-227.427,5049.6,-355.179,-194.118,5048.22,-354.62,-229.907,4953.52,-386.406,-192.361,4948.94,-384.839,-367.876,5513.56,-287.756,-347.932,5513.27,-306.716,-339.484,5486.47,-297.311,-360.782,5488.64,-279.961,-327.334,5460.62,-286.588,-352.89,5465.66,-271.548,-377.114,5490.72,-260.218,-383.93,5514.39,-266.71,-389.958,5493.34,-239.502,-396.573,5515.87,-244.655,-370.222,5469,-253.04,-383.26,5472.53,-233.478,-390.331,5540.32,-271.776,-374.333,5540.84,-294.076,-395.903,5568.84,-274.675,-379.894,5570.84,-298.049,-402.762,5540.45,-248.342,-408.187,5567.44,-249.971,-354.481,5542,-314.211,-359.85,5573.39,-319.07,-327.183,5232.71,-260.03,-315.874,5266.82,-252.392,-283.719,5274.33,-263.788,-300.689,5235.52,-274.008,-304.467,5298.6,-247.004,-268.316,5307.19,-256.804,-312.966,5199.28,-284.703,-336.368,5199.74,-268.616,-321.813,5167.62,-294.392,-345.457,5168.96,-277.221,-355.906,5201,-247.134,-348.24,5233.28,-239.581,-371.479,5201.83,-220.972,-363.521,5234.9,-214.454,-366.147,5169.73,-255.01,-383.05,5169.47,-227.937,-341.882,5265.47,-233.131,-336.801,5295.95,-228.518,-358.461,5267.47,-209.015,-355.669,5298.33,-205.276,-440.76,5022.83,-339.299,-459.25,4998.74,-354.914,-500.38,5001.87,-330.442,-478.998,5024.18,-314.528,-535.079,5002.88,-300.414,-510.966,5023.17,-284.605,-457.492,5044.65,-300.649,-422.244,5044.71,-325.424,-436.305,5065.32,-288.769,-404.134,5066.28,-313.311,-486.682,5042.26,-270.995,-462.718,5062.23,-259.483,-383.442,5042.76,-344.663,-398.824,5019.48,-358.285,-368.555,5065.32,-332.504,-414.279,4993.85,-373.327,-430.474,5620.58,-191.086,-424.503,5624.5,-218.449,-421.274,5594.71,-222.572,-427.313,5592.39,-196.74,-417.055,5566.67,-224.962,-423.301,5566.09,-200.636,-415.728,5628.56,-245.892,-412.512,5597.16,-248.951,-431.426,5589.71,-172.655,-434.465,5616.22,-164.986,-434.405,5586.18,-151.521,-437.2,5610.5,-142.117,-427.722,5565.26,-177.988,-431.11,5563.74,-158.006,-436.9,5644.73,-155.268,-432.768,5650.18,-184.479,-438.353,5675.48,-147.029,-434.072,5680.77,-178.536,-439.633,5636.79,-129.556,-442.013,5668.44,-111.17,-426.715,5655.23,-213.412,-417.826,5660.54,-241.409,-427.885,5686.06,-208.282,-383.164,5236.57,-153.521,-374.793,5235.79,-185.421,-382.98,5201.57,-190.314,-390.584,5201.11,-155.936,-395.075,5167.96,-196.034,-402.203,5166.07,-159.842,-394.454,5201.44,-118.761,-388.969,5238.17,-119.849,-394.867,5205.62,-81.0952,-392.539,5241.56,-86.1344,-404.372,5164.75,-119.83,-399.187,5165.8,-71.7587,-386.114,5274.21,-120.827,-378.799,5271.29,-152.154,-384.773,5307.87,-123.62,-376.56,5303.93,-152.603,-391.766,5277,-89.2285,-392.858,5314.72,-91.1156,-369.753,5269.36,-181.815,-367.106,5301.05,-179.962,-547.758,5013.48,-212.816,-576.13,4997.2,-226.847,-581.421,4991.06,-186.777,-552.092,5006.09,-174.263,-576.037,4983.33,-147.021,-546.901,4998.08,-136.273,-522.623,5022.64,-163.162,-519.233,5030.41,-200.641,-493.903,5042.38,-153.226,-491.292,5049.88,-190.093,-517.682,5014.84,-126.357,-489.169,5035.24,-117.116,-507.299,5037.25,-237.14,-534.011,5019.42,-250.174,-481.001,5056.79,-226.063,-560.532,5001.27,-265.341,-511.911,4982.17,-65.9066,-485.516,4974.7,-33.5686,-461.071,4996.16,-28.912,-485.501,5001.52,-59.2382,-436.866,5020.97,-22.7228,-459.985,5024.6,-52.0065,-454.521,4967.87,-3.21588,-430.971,4992.11,-0.574619,-404.199,5019.65,4.8703,-504.796,5007.78,-91.7017,-532.857,4990.05,-100.162,-477.439,5029.34,-83.3603,-560.997,4974.54,-108.777,-538.662,4965.01,-72.0316,-510.551,4954.91,-36.9067,-477.663,4944.02,-3.58328,-375.004,4957.15,46.8503,-334.831,4949.51,62.2266,-320.044,4987.19,55.2211,-351.381,4990.78,43.2934,-309.579,5019.95,50.9226,-330.712,5022.4,42.286,-305.397,4937.45,72.4767,-299.033,4977.97,63.7993,-295.703,5012.78,57.784,-391.967,4990.89,24.6947,-417.726,4962.07,24.4571,-363.866,5021.37,27.6326,-440.324,4932.31,26.9742,-397.819,4920.79,52.8073,-352.819,4908.39,71.64,-315.186,4894.52,83.6097,-264.997,4913.13,90.435,-234.226,4909.22,99.4048,-232.607,4948.76,92.831,-262.767,4953.31,82.8617,-231.342,4983.4,89.1305,-261.634,4989.87,77.9031,-193.617,4908.97,106.238,-194.202,4945.85,100.248,-194.498,4968.04,97.5314,-282.713,4964.52,72.5806,-285.398,4923.59,81.224,-281.986,5000.99,66.4232,-290.223,4881.24,92.4142,-267.872,4871.29,100.904,-235.546,4867.08,108.977,-192.974,4867.98,114.86,-252.775,5105.6,-335.418,-256.879,5079.66,-344.966,-288.693,5083.35,-342.124,-282.036,5108.62,-332.313,-321.201,5086.7,-334.973,-311.724,5111.54,-325.074,-276.257,5135.27,-322.848,-249.125,5132.94,-325.998,-303.601,5137.69,-315.733,-222.263,5131.26,-326.711,-223.837,5103.33,-336.009,-195.591,5130.8,-326.463,-195.09,5102.64,-335.692,-225.572,5076.69,-345.303,-194.61,5075.72,-344.878,-370.908,5114.03,-293.811,-386.838,5089.31,-302.936,-415.868,5088.16,-278.832,-396.864,5113.23,-270.286,-439.541,5085.15,-249.956,-417.958,5110.84,-241.888,-380.035,5140.45,-262.537,-356.929,5140.55,-285.411,-398.835,5139.04,-234.729,-330.991,5139.53,-303.212,-341.918,5113.42,-312.17,-354.574,5088.76,-321.804,-440.362,5101.58,-172.91,-464.709,5073.79,-180.945,-466.851,5067.07,-144.201,-442.239,5096.21,-135.494,-462.5,5060.82,-108.278,-438.388,5090.93,-98.909,-420.899,5129.15,-126.633,-419.151,5132.62,-165.691,-417.342,5124.96,-87.7932,-411.841,5136.17,-202.116,-432.366,5106.72,-208.909,-455.727,5080.1,-216.771,-411.465,5081.9,-32.6526,-435.915,5051.82,-43.7346,-410.768,5049.65,-13.9659,-380.982,5080.63,-1.36729,-373.859,5050.42,13.2205,-345.91,5080.83,23.1858,-353.213,5109.56,13.4247,-385.073,5112.72,-16.8329,-335.321,5131.96,25.8371,-361.01,5139.39,2.23412,-327.739,5106.61,31.6883,-319.72,5125.96,37.5409,-407.29,5117.97,-51.857,-428.943,5085.82,-65.0092,-386.203,5149.35,-31.9976,-452.035,5055.81,-74.8397,-308.948,5076.11,43.5584,-316.348,5051.1,42.6938,-303.72,5047.45,48.7249,-301.07,5071.02,48.1737,-294.236,5041.01,54.4245,-293.244,5064.66,53.6196,-299.715,5091.51,49.4597,-306.061,5097.18,45.0577,-298.157,5109.26,53.2554,-304.784,5114.57,48.0399,-291.64,5085.39,55.606,-288.683,5103.89,61.0545,-313.796,5102.45,40.2936,-321.792,5079.62,36.8423,-311.064,5120.17,43.5131,-338.763,5051.86,32.2951,-258.356,5049.33,76.4371,-260.642,5021.83,75.5557,-230.095,5017.27,87.5661,-228.109,5046.26,89.1373,-195.307,5014.56,92.9727,-195.824,5045.65,94.1884,-225.076,5071.52,93.6591,-254.077,5073.6,80.9677,-221.347,5093.37,100.68,-248.087,5095.2,89.1247,-196.264,5071.23,98.4016,-196.647,5092.91,104.617,-277.721,5078.69,66.0416,-280.76,5056.57,62.6281,-272.423,5098.72,73.6828,-281.865,5031.33,62.9273,-389.32,5443.75,-183.905,-381.079,5439.05,-202.337,-375.833,5420.21,-196.498,-384.268,5424.88,-177.852,-371.517,5400.41,-191.023,-380.161,5404.9,-171.71,-370.882,5434.32,-220.951,-365.564,5415.47,-215.194,-361.059,5395.79,-210.278,-391.389,5429.39,-159.923,-396.182,5448.23,-166.258,-397.805,5433.7,-143.364,-402.324,5452.3,-149.985,-387.553,5409.38,-153.102,-394.23,5413.96,-135.977,-401.592,5466.43,-171.829,-395.014,5462.14,-189.57,-407.279,5484.51,-176.356,-401.044,5480.64,-194.548,-407.417,5470.2,-155.559,-412.718,5487.81,-159.804,-386.983,5457.6,-208.211,-376.833,5453.1,-227.17,-393.28,5476.53,-213.785,-277.636,5658.8,-359.327,-278.334,5697.21,-353.424,-242.214,5700.51,-358.793,-241.55,5662.03,-364.739,-204.891,5702.13,-360.427,-204.233,5663.66,-366.374,-240.484,5623.58,-366.571,-276.155,5620.49,-361.229,-203.591,5625.2,-368.174,-309.402,5616.48,-351.906,-311.282,5654.41,-349.922,-339.024,5612.08,-338.354,-341.276,5649.31,-336.308,-312.066,5692.54,-344.147,-342.228,5686.81,-330.784,-386.99,5638.51,-296.632,-366.41,5643.93,-318.267,-363.818,5607.82,-320.329,-384.112,5603.84,-298.783,-400.235,5600.25,-274.672,-403.327,5633.31,-272.229,-405.182,5666.77,-267.81,-388.535,5673.51,-291.951,-367.633,5680.32,-313.163,-274.762,5939.57,-71.6131,-305.8,5930.93,-70.7691,-305.99,5931.8,-32.2323,-274.994,5940.51,-32.2967,-335.287,5917.79,-68.8839,-335.352,5918.63,-31.2274,-242.894,5945.43,-31.9001,-242.676,5944.41,-71.78,-210.22,5947.2,-31.523,-210.046,5946.15,-71.63,-242.356,5939.06,-109.601,-274.375,5934.3,-108.871,-241.975,5929.99,-145.638,-273.908,5925.31,-144.377,-209.809,5940.78,-109.685,-209.518,5931.68,-145.937,-305.379,5925.76,-107.265,-334.876,5912.75,-104.555,-304.825,5916.97,-142.044,-334.237,5904.29,-138.531,-387.275,5876.52,-61.5596,-408.132,5849.6,-57.4182,-407.571,5850.67,-22.3812,-386.862,5877.49,-25.6302,-424.47,5819.31,-53.8239,-423.956,5820.46,-19.73,-362.549,5900.33,-28.8025,-362.719,5899.45,-65.5979,-362.378,5894.56,-100.515,-386.995,5871.82,-95.779,-361.654,5886.6,-133.73,-386.157,5864.5,-128.312,-407.836,5845.15,-90.9841,-424.011,5815.18,-86.7636,-406.827,5838.62,-122.951,-422.744,5809.56,-118.321,-442.176,5752.08,-49.8403,-440.858,5748.16,-81.6174,-443.862,5713.06,-80.0162,-445.646,5718.4,-48.6808,-444.972,5680.84,-76.9031,-446.971,5687.53,-47.1349,-438.793,5744.11,-113.008,-441.371,5708.46,-111.804,-446.455,5720.34,-18.0437,-442.496,5753.45,-17.8976,-446.028,5717.78,12.1104,-441.569,5750.62,14.1048,-448.073,5689.96,-18.4489,-448.025,5687.68,9.50246,-435.297,5787.44,-18.3477,-435.467,5786.26,-51.4262,-433.906,5784.34,15.2462,-434.629,5782.52,-83.7545,-432.991,5777.93,-115.093,-460.423,5549.34,-54.5804,-464.968,5551.95,-63.9642,-464.909,5543.89,-67.6635,-459.987,5541.24,-57.7091,-464.531,5534.85,-70.5854,-459.385,5532.45,-60.1797,-468.555,5554.61,-72.1633,-468.845,5546.51,-76.6428,-468.675,5537.14,-80.1198,-454.679,5538.51,-47.2346,-455.435,5546.52,-44.544,-449.583,5535.69,-36.6976,-450.588,5543.48,-34.4029,-453.857,5529.96,-49.3149,-448.564,5527.41,-38.4013,-456.177,5553.99,-41.5634,-460.754,5556.67,-51.2264,-456.954,5560.9,-38.6134,-461.15,5563.34,-48.2524,-451.592,5550.77,-31.7015,-452.608,5557.57,-28.7744,-464.72,5558.71,-59.9708,-467.76,5560.95,-67.1523,-464.621,5564.61,-56.8717,-466.79,5564.82,-62.9294,-451.544,5471.79,-53.4388,-456.284,5471.72,-62.8483,-454.577,5461.22,-58.2379,-450.108,5461.69,-49.474,-453.192,5451.15,-53.2595,-449.056,5451.95,-45.1581,-460.162,5471.35,-71.6078,-458.195,5460.39,-66.3641,-456.487,5449.89,-60.7163,-445.329,5461.85,-40.3276,-446.489,5471.61,-43.6552,-440.78,5461.78,-31.0549,-441.667,5471.22,-33.7743,-444.604,5452.41,-36.662,-440.357,5452.64,-28.0226,-447.894,5481.56,-46.4957,-453.173,5482.12,-56.8496,-449.352,5491.59,-48.7019,-454.808,5492.55,-59.5022,-442.835,5480.83,-36.079,-444.107,5490.49,-37.8635,-458.126,5482.48,-66.8335,-462.204,5482.59,-76.1423,-459.922,5493.35,-69.9334,-464.139,5493.95,-79.6588,-393.264,5254.45,-22.8288,-392.553,5263.15,5.49874,-399.063,5285.64,-1.27807,-397.953,5280.34,-29.1734,-403.543,5306.39,-6.09064,-401.422,5304.21,-32.2559,-392.458,5274.54,33.1005,-399.488,5294.22,25.9878,-404.656,5312.47,19.6302,-395.704,5278.29,-58.624,-393.789,5247.32,-53.5761,-398.101,5306.26,-59.547,-391.099,5216.86,-45.1083,-385.752,5228.69,-13.0394,-384.35,5190.19,-30.2086,-373.43,5205.42,0.74181,-382.715,5240.18,14.1935,-382.526,5254.01,40.9205,-368.175,5218.12,24.9341,-368.542,5233.45,49.5095,-300.963,5143.31,63.4168,-303.297,5129.3,53.6301,-295.486,5124.87,60.6409,-291.416,5139.66,73.2109,-283.788,5120.63,71.1222,-277.105,5136.11,86.7638,-286.19,5153.76,92.4544,-297.991,5157.88,79.3907,-269.886,5149.2,107.486,-306.764,5161.81,69.616,-308.403,5147.42,56.1627,-314.811,5166.5,61.716,-315.681,5152.86,49.7837,-309.804,5134.3,48.1928,-316.978,5140.38,42.6415,-235.48,5128.44,114.028,-241.368,5113.76,100.426,-217.878,5111.38,109.554,-215.411,5125.37,120.309,-196.97,5110.56,112.317,-197.235,5124.32,122.094,-213.949,5135.6,133.635,-231.459,5138.97,129.566,-197.452,5134.54,134.788,-250.607,5143.95,120.868,-257.338,5132.35,101.956,-265.197,5116.84,85.8317,-438.019,5571.33,102.355,-437.773,5565.04,87.1454,-436.188,5551.84,93.3263,-436.589,5556.39,108.72,-434.003,5538.89,98.25,-434.614,5542.08,113.794,-436.495,5558.97,73.8506,-434.751,5547.24,79.6443,-432.459,5535.58,84.2774,-435.091,5560.57,126.093,-436.307,5577.53,119.877,-430.833,5564.07,145.715,-431.625,5582.56,140.131,-433.471,5545.05,131.119,-429.752,5547.68,150.44,-437.508,5596.24,111.65,-439.253,5586.62,93.9015,-439.235,5614.79,98.9406,-440.683,5601.42,82.1449,-432.488,5603.94,133.037,-434.068,5632.68,122.166,-439.083,5578.04,79.0293,-437.984,5570.42,66.3684,-440.442,5590.38,68.298,-439.51,5581.2,56.6671,-390.003,5300.63,90.6754,-391.966,5287.76,61.7644,-382.715,5269.37,69.9608,-381.309,5283,99.2636,-370.529,5250.79,78.1213,-370.796,5265.41,107.558,-378.381,5294.26,127.455,-386.657,5312.35,119.241,-374.246,5303.57,155.444,-381.96,5322.1,147.919,-369.158,5276.66,135.273,-366.157,5285.92,162.699,-394.292,5330.85,110.62,-397.297,5317.8,81.8883,-401.357,5347.56,99.8138,-403.568,5333.66,72.3325,-389.541,5341.76,140.15,-397.806,5364.56,131.115,-399.066,5305.41,53.7221,-404.705,5321.82,45.705,-415.307,5466.64,125.996,-416.285,5467.71,108.959,-415.383,5455.27,106.286,-413.355,5453.73,123.489,-415.196,5442.44,102.244,-412.174,5440.32,119.618,-417.148,5468.67,93.376,-417.103,5456.76,90.6695,-417.665,5444.55,86.6405,-410.878,5452.36,142.274,-413.85,5465.72,144.498,-407.809,5451.42,162.631,-411.548,5465.2,164.472,-408.646,5438.43,138.739,-404.663,5437.01,159.586,-417.304,5478.68,145.371,-417.914,5479.19,127.117,-420.983,5491.43,144.853,-421.063,5491.48,126.834,-415.529,5478.56,165.051,-419.395,5491.72,164.307,-417.938,5479.81,110.263,-417.952,5480.27,94.781,-420.378,5491.62,110.201,-419.671,5491.57,94.8986,-435.192,5729.31,77.5309,-440.703,5697.27,71.6252,-435.811,5681.99,101.023,-429.676,5714.6,108.085,-428.623,5668.03,130.195,-422.359,5700.41,137.284,-443.815,5668.41,64.4503,-439.844,5652.95,91.8632,-421.189,5746.98,114.376,-426.845,5762.15,82.4342,-414.127,5731.73,144.114,-431.087,5775.25,49.1148,-439.146,5741.94,46.0597,-444.109,5709.65,41.9936,-446.574,5680.24,37.0658,-457.998,5570.88,-91.0135,-463.281,5568.43,-90.3541,-462.477,5577.36,-79.5993,-457.514,5580.45,-79.8596,-461.265,5584.08,-67.4519,-456.697,5587.85,-67.2376,-467.625,5566.19,-89.4233,-466.582,5574.6,-79.2482,-465.045,5580.77,-67.7205,-452.459,5584.65,-80.961,-452.541,5574.15,-92.4466,-448.075,5590.71,-83.8313,-447.677,5578.85,-95.6984,-452.106,5592.97,-67.8541,-448.258,5600.33,-70.0749,-452.288,5562.1,-101.975,-458.109,5559.71,-100.313,-451.632,5549.12,-109.211,-457.814,5547.49,-107.37,-446.97,5565.48,-105.431,-445.862,5551.33,-112.784,-463.668,5557.85,-99.3187,-468.196,5556.09,-97.8691,-463.635,5546.14,-106.093,-468.32,5544.81,-104.208,-445.38,5465.67,-99.9554,-442.102,5451.31,-92.5753,-449.864,5452.8,-90.7304,-452.942,5466.64,-98.0521,-455.979,5454.27,-88.5029,-458.853,5467.59,-95.6503,-439.194,5437.12,-84.4787,-447.055,5439.21,-82.6789,-453.305,5441.26,-80.6381,-455.985,5480.56,-104.231,-448.691,5480.03,-106.175,-458.692,5494.38,-108.853,-451.698,5494.23,-110.793,-461.655,5481.04,-101.694,-464.116,5494.45,-106.249,-440.619,5479.35,-108.656,-437.025,5464.53,-102.474,-432.621,5478.43,-112.81,-428.733,5463.06,-106.724,-443.97,5493.97,-113.212,-436.344,5493.53,-117.257,-433.551,5449.61,-95.1225,-430.563,5434.72,-87.0628,-425.064,5447.48,-99.4559,-422.004,5431.74,-91.46,-428.175,5379.86,-26.6258,-426.419,5378.84,-15.3814,-431.277,5385.15,-16.6199,-433.827,5385.92,-26.8419,-435.662,5391.2,-17.7475,-438.776,5391.7,-27.1373,-425.148,5380.3,-4.17885,-429.129,5386.65,-6.57652,-432.839,5392.75,-8.62618,-436.495,5389.09,-37.0352,-430.147,5383.47,-37.6605,-438.996,5394.83,-46.9914,-432.073,5389.8,-48.2336,-441.903,5394.43,-36.6148,-444.765,5399.59,-45.9981,-423.468,5376.91,-38.8819,-422.352,5372.88,-26.7395,-417.067,5368.73,-41.0884,-416.896,5364.31,-27.4342,-424.661,5383.86,-50.2594,-417.427,5376.37,-53.6011,-421.522,5371.67,-14.1468,-421.186,5373.18,-1.41563,-417.017,5363.03,-13.0303,-417.531,5364.75,1.72932,-423.054,5426.26,50.3445,-423.817,5429.13,40.8559,-424.628,5419.57,35.0693,-423.936,5415.89,43.5966,-425.369,5410.64,28.3387,-424.506,5406.2,35.7742,-424.567,5431.66,32.6155,-425.347,5422.88,27.8365,-426.281,5414.73,22.1644,-422.8,5411.85,53.7944,-421.86,5423.06,61.4263,-420.753,5407.42,66.0399,-419.819,5419.55,74.4475,-423.249,5401.32,44.847,-421.154,5395.95,55.9306,-420.684,5434.68,67.7648,-422.016,5437.04,56.0052,-419.533,5446.4,72.8306,-420.978,5447.99,60.56,-418.679,5432.04,81.274,-422.985,5439.12,45.6946,-423.906,5440.89,36.5349,-422.184,5449.31,49.5782,-423.331,5450.37,39.6314,-424.157,5509.9,65.6948,-424.046,5508.05,54.1591,-422.41,5498.77,55.4169,-421.909,5500.19,67.2696,-421.298,5489.29,55.9197,-420.277,5490.26,67.9478,-424.497,5506.34,43.2485,-423.339,5497.43,44.2237,-422.604,5488.32,44.6047,-421.903,5501.5,79.9806,-424.773,5511.75,78.0673,-422.459,5502.56,93.7484,-425.837,5513.44,91.4889,-419.686,5491.07,80.878,-427.879,5521.97,75.2549,-426.723,5519.52,63.3004,-430.805,5532.3,71.6626,-429.311,5529.13,60.164,-429.329,5524.38,88.2784,-426.046,5517.17,52.1872,-426.033,5515.07,41.6889,-428.251,5526.21,49.5417,-427.896,5523.63,39.5529,-454.128,5594.65,-39.1746,-457.618,5589.53,-40.6303,-455.165,5588.78,-26.8555,-452.296,5594.45,-24.5881,-452.278,5586.04,-13.4358,-450.046,5592.12,-10.2024,-460.441,5585.07,-42.2146,-457.442,5583.83,-29.1865,-454.023,5580.73,-16.6092,-449.55,5601.79,-22.5538,-450.721,5601.4,-38.2169,-447.643,5611.76,-20.9186,-448.15,5610.77,-38.128,-447.969,5599.84,-6.90573,-446.686,5610.07,-3.54314,-451.549,5598.5,-53.4606,-455.581,5592.52,-53.5339,-448.319,5606.99,-54.6772,-459.649,5588.05,-54.3125,-462.986,5584.15,-55.2172,-408.401,5340.16,-30.7619,-404.801,5323.92,-32.3004,-407.032,5324.22,-9.16983,-410.128,5339.41,-11.0162,-408.502,5328.47,14.1002,-411.601,5342.42,9.35256,-413.338,5352.31,-12.1447,-412.34,5353.51,-28.9571,-414.473,5354.51,5.27449,-411.549,5358.26,-44.67,-406.602,5344.88,-49.501,-411.035,5366.68,-58.7923,-405.118,5354.14,-66.1595,-401.91,5327.98,-55.4499,-399.307,5338.09,-76.0321,-404.377,5385.97,-90.7702,-405.336,5404.65,-100.235,-398.644,5398.5,-112.819,-397.737,5377.41,-103.192,-391.967,5392.67,-128.104,-390.983,5369.6,-118.964,-407.364,5423.49,-108.627,-400.744,5418.75,-121.103,-397.937,5355.92,-91.4371,-404.341,5368.49,-79.6643,-391.244,5344.5,-107.778,-410.866,5378.93,-70.8293,-411.237,5393.94,-81.2085,-417.875,5387.35,-64.5795,-418.635,5400.82,-74.3377,-412.337,5410.62,-90.3565,-414.362,5427.91,-98.6968,-419.937,5415.9,-83.1935,-431.51,5524.17,-135.827,-434.772,5540.7,-133.58,-430.895,5542.23,-146.131,-427.026,5524.36,-148.101,-427.173,5543.1,-161.809,-422.716,5523.85,-163.197,-437.534,5557.81,-128.832,-434.262,5561.09,-141.684,-422.732,5507.13,-147.812,-427.791,5507.96,-135.75,-418.094,5490.19,-145.482,-423.656,5491.81,-133.528,-417.857,5505.55,-162.44,-433.296,5508.28,-126.326,-436.456,5523.56,-126.373,-439.516,5508.3,-119.612,-442.149,5522.81,-119.741,-429.656,5492.87,-124.074,-439.121,5538.88,-124.068,-441.282,5554.39,-119.261,-444.26,5537.13,-117.508,-447.096,5640.65,-41.4421,-447.742,5642.7,-19.1923,-448.253,5664.17,-18.7789,-447.242,5661.76,-44.3915,-447.641,5640.92,3.28128,-448.299,5662.16,6.52283,-445.516,5655.1,-70.2326,-445.933,5634.81,-62.8909,-443.467,5642.99,-93.4311,-444.514,5624.89,-82.2644,-446.657,5618.93,-57.7627,-447.171,5623.75,-39.2784,-445.919,5610.82,-74.6787,-447.289,5625.32,-19.8527,-446.84,5623.66,-0.110537,-413.21,5358.34,50.1339,-413.279,5369.96,69.1948,-417.525,5378.29,55.5391,-416.761,5368.09,39.7836,-420.692,5385.46,43.9987,-419.709,5376.45,30.6905,-413.009,5383.16,85.6742,-417.777,5389.99,69.4034,-415.679,5359.96,22.8063,-412.64,5348.95,29.9614,-418.541,5369.39,16.4519,-409.093,5336.26,37.6758,-408.843,5347.02,61.3665,-407.788,5360.16,84.9783,-406.738,5375.18,105.152,-424.546,5389.96,17.1501,-424.608,5397.43,26.8952,-425.988,5402.57,20.6721,-426.686,5395.68,12.2057,-427.401,5407.38,15.5614,-428.804,5401.09,8.148,-427.666,5390.27,3.07675,-424.626,5384.13,6.72789,-430.585,5396.13,0.0459105,-421.554,5377.31,11.1393,-422.263,5383.65,23.2288,-422.948,5391.79,34.5608,-435.22,5437.07,-7.18595,-434.49,5444.64,-9.15652,-437.273,5444.33,-16.898,-437.923,5436.35,-14.233,-440.575,5443.9,-24.783,-441.056,5435.56,-21.5325,-433.928,5452.71,-11.069,-436.838,5452.73,-19.4933,-438.407,5428.77,-11.6079,-435.767,5430.04,-5.20614,-438.344,5421.59,-9.13148,-435.742,5423.55,-3.11083,-441.421,5427.59,-18.4702,-441.293,5419.98,-15.7925,-433.485,5431.23,0.556067,-432.872,5437.6,-0.444099,-431.489,5431.62,5.84567,-430.791,5437.63,6.02876,-433.527,5425.67,1.9854,-431.97,5427.45,5.446,-432.119,5444.77,-1.56452,-431.502,5452.59,-2.7456,-430.052,5444.64,5.87147,-429.442,5452.38,5.47968,-437.95,5505.89,-18.0981,-434.641,5504.5,-7.75406,-435.879,5512.7,-7.84864,-439.14,5514.34,-18.0648,-437.226,5520.5,-7.71731,-440.395,5522.4,-17.6302,-431.793,5503.64,2.41798,-433.05,5511.7,2.14115,-434.47,5519.35,1.91745,-442.973,5516.4,-28.5458,-441.84,5507.62,-28.6717,-447.52,5518.65,-39.3329,-446.437,5509.5,-39.5277,-444.126,5524.8,-27.8489,-440.726,5498.55,-28.2545,-436.839,5497.13,-17.7438,-439.627,5489.31,-27.3232,-435.827,5488.18,-17.0108,-445.303,5500.09,-39.0263,-433.543,5495.98,-7.42543,-430.737,5495.27,2.76542,-432.619,5487.25,-6.85783,-429.917,5486.66,3.20533,-444.378,5582.01,16.3416,-443.571,5589.68,22.5766,-445.907,5595.7,8.29408,-447.338,5587.88,3.55537,-443.326,5599.27,29.6323,-445.174,5605.77,13.5092,-448.947,5581.58,-0.821918,-445.389,5575.71,10.739,-450.219,5576.1,-4.9578,-446.238,5570.23,5.5795,-441.818,5568.73,21.0004,-441.377,5574.85,27.8115,-438.452,5560.96,29.7156,-438.541,5566.68,37.6207,-442.291,5563.43,14.8146,-438.59,5556.01,22.5615,-441.163,5582.13,35.4753,-441.364,5590.94,44.22,-438.886,5573.36,46.5233,-444.816,5626.38,46.4943,-444.97,5645.1,55.7428,-447.132,5655.6,31.4275,-446.565,5635.27,25.4035,-445.653,5618.81,19.3218,-444.005,5611.3,37.6984,-442.175,5601.65,54.2718,-442.697,5614.88,65.876,-442.03,5631.28,79.2759,-412.98,5520.24,-201.141,-407.105,5499.87,-198.535,-412.902,5503.01,-179.56,-418.294,5522.34,-181.115,-423.288,5542.95,-180.699,-418.451,5542.18,-201.973,-411.872,5541.21,-224.797,-405.999,5517.95,-222.559,-399.71,5496.52,-218.729,-213.322,5385.89,-261.524,-205.522,5383.27,-259.776,-204.596,5374.45,-256.578,-211.009,5375.98,-257.624,-204.087,5366.66,-254.673,-209.799,5367.62,-255.394,-221.812,5379.02,-259.288,-227.164,5391.03,-264.456,-242.55,5384.39,-261.039,-253.668,5400.13,-267.756,-218.966,5369.35,-256.386,-236.005,5372.05,-257.167,-237.352,5408.5,-273.903,-217.938,5399.86,-268.723,-270.75,5421.95,-278.581,-207.367,5395.59,-265.691,-200.183,5394.35,-264.707,-332.691,5423.38,-256.095,-293.471,5412.62,-266.47,-278.306,5393.15,-259.652,-320.544,5403.21,-250.211,-267.087,5376.82,-255.307,-309.171,5383.83,-246.164,-350.339,5410.39,-233.624,-356.925,5429.6,-239.306,-343.952,5390.61,-229.298,-363.52,5448.88,-245.927,-343.69,5444.13,-263.412,-310.972,5435.55,-275.779,-457.51,5513.15,-61.8912,-451.859,5511.36,-50.723,-452.918,5520.87,-50.4655,-458.556,5523.04,-61.5639,-463.816,5525.1,-72.2435,-462.793,5514.83,-72.6676,-468.086,5527.03,-82.1223,-467.113,5516.34,-82.687,-461.486,5504.19,-71.8877,-456.257,5502.95,-61.193,-465.786,5505.26,-81.8532,-450.671,5501.56,-50.123,-448.379,5434.04,-36.4097,-444.692,5434.85,-29.0337,-444.506,5443.42,-32.8071,-448.579,5442.72,-40.6961,-452.312,5441.63,-48.1753,-451.662,5432.95,-43.3329,-455.219,5440,-54.9707,-454.125,5431.18,-49.4443,-450.967,5425.35,-39.083,-448.155,5425.95,-32.6213,-450.254,5418.52,-36.1411,-447.686,5418.37,-29.745,-452.947,5423.9,-44.4546,-451.793,5419.1,-40.8373,-444.822,5426.67,-25.6128,-444.553,5418.88,-22.8129,-428.308,5515.98,122.23,-428.051,5517.13,139.789,-431.062,5530.67,135.772,-431.743,5528.7,118.374,-425.689,5518.4,158.966,-428.023,5532.56,154.955,-430.892,5526.62,102.597,-427.293,5514.8,106.169,-423.642,5503.21,108.769,-424.636,5503.66,125.127,-424.628,5504.15,142.904,-422.798,5504.89,162.181,-412.12,5411.89,107.379,-406.818,5408.16,127.362,-407.413,5423.73,133.934,-411.877,5426.3,114.399,-400.853,5405.23,149.743,-402.463,5421.74,155.4,-415.688,5429.16,96.8317,-416.512,5415.76,89.7524,-417.324,5402.59,80.7091,-412.56,5397.33,98.103,-406.504,5391.58,118.524,-399.478,5387.11,142.292,-455.84,5521.65,-113.752,-462.232,5521.11,-112.033,-463.174,5533.88,-110.28,-457.074,5534.79,-111.802,-467.251,5520.49,-109.586,-468.02,5532.91,-108.063,-450.507,5535.82,-113.816,-448.879,5522.19,-115.927,-446.711,5508.25,-115.68,-454.064,5508.12,-113.367,-460.756,5507.92,-111.505,-465.965,5507.63,-108.931,-435.239,5410.31,-67.3461,-433.685,5398.96,-58.0923,-441.047,5403.28,-56.501,-442.902,5413.88,-65.5964,-447.083,5407.39,-55.1061,-449.11,5417.29,-63.9259,-444.82,5426.04,-74.3126,-436.991,5423.24,-76.1055,-451.1,5428.76,-72.441,-428.426,5419.98,-78.7546,-426.894,5406.12,-69.9885,-425.721,5393.84,-60.5601,-419.56,5469.53,66.3477,-420.976,5469.59,54.5042,-421.465,5459.49,52.5002,-420.096,5458.83,63.9957,-422.444,5469.49,43.5083,-422.809,5459.93,41.9402,-418.659,5457.94,76.6458,-418.262,5469.25,79.2392,-418.539,5480.31,80.6404,-419.556,5480.03,67.6528,-420.87,5479.54,55.6301,-422.34,5478.98,44.3815,-433.828,5548.49,51.5621,-432.887,5544.1,42.0563,-430.5,5535.22,46.261,-431.625,5538.86,56.3628,-432.508,5540.28,33.3008,-430.04,5532.05,36.849,-433.136,5542.89,67.4073,-435.105,5553.45,62.0747,-436.944,5563.71,55.252,-436.079,5557.83,45.4263,-435.507,5552.71,36.6372,-435.343,5548.28,28.6307,-414.668,5458.73,-123.545,-408.403,5455.79,-135.673,-404.126,5437.75,-128.826,-410.607,5441.46,-116.52,-417.502,5444.74,-106.659,-421.362,5461.14,-113.822,-425.545,5477.17,-119.766,-419.15,5475.46,-129.337,-413.188,5473.17,-141.331,-441.565,5593.66,-111.243,-443.104,5610.61,-98.2892,-441.487,5624.24,-111.119,-439.403,5602.47,-124.44,-437.047,5581.3,-134.541,-439.752,5575.76,-121.405,-442.926,5570.26,-111.805,-444.17,5585.57,-101.814,-445.129,5599.4,-89.4004,-434.247,5470.09,-14.5119,-437.62,5470.68,-24.0735,-437.003,5461.56,-21.9111,-433.879,5461.24,-12.8754,-431.294,5460.9,-3.92731,-431.445,5469.55,-5.04637,-429.129,5460.61,4.95583,-429.108,5469.18,4.3622,-431.903,5478.38,-6.0437,-434.93,5479.12,-15.912,-429.373,5477.91,3.75791,-438.543,5479.97,-25.9036,-442.992,5537.04,-15.4958,-446.468,5540.17,-24.7076,-445.3,5532.72,-26.5533,-441.699,5529.97,-16.7834,-438.649,5527.78,-7.36426,-440.075,5534.5,-6.80274,-436.019,5526.49,1.72427,-437.631,5533.05,1.44698,-441.434,5540.59,-6.04706,-444.218,5543.58,-13.7399,-442.438,5545.87,-4.32822,-445.265,5549.56,-11.2914,-439.245,5538.94,0.969589,-440.542,5543.26,0.399441,-447.605,5547.14,-22.3587,-448.683,5553.62,-19.5531,-471.261,5560.66,-83.5931,-471.906,5551.64,-90.2681,-471.195,5549.12,-84.1901,-470.672,5557.6,-78.6471,-472.028,5541.37,-95.2243,-471.198,5539.33,-88.375,-469.559,5564.22,-72.1274,-470.024,5567.92,-75.5013,-467.746,5568.54,-64.5559,-468.113,5572.92,-66.1818,-469.066,5571.42,-77.8784,-470.266,5563.57,-87.1773,-467.27,5577.02,-67.2669,-470.924,5553.99,-94.8399,-471.094,5543.21,-100.543,-470.918,5518.77,-99.167,-469.68,5506.76,-98.3495,-468.584,5506.1,-90.7408,-469.873,5517.65,-91.5862,-467.984,5494.47,-95.8028,-466.907,5494.3,-88.3472,-470.751,5528.79,-90.8189,-471.7,5530.36,-98.1577,-470.826,5531.74,-103.964,-470.096,5519.71,-105.232,-468.874,5507.26,-104.479,-467.133,5494.5,-101.835,-463.423,5469.7,-86.1724,-460.981,5457.59,-79.793,-460.423,5459.13,-73.5964,-462.633,5470.64,-79.4398,-458.714,5445.89,-72.8428,-458.418,5448.07,-67.2745,-464.859,5482.42,-84.4685,-465.827,5482.05,-91.6262,-464.848,5481.57,-97.4305,-462.254,5468.64,-91.6328,-459.593,5455.91,-84.8098,-457.1,5443.56,-77.3285,-455.09,5424.81,-58.3473,-453.234,5416.38,-50.916,-453.78,5420.65,-48.1946,-455.351,5428.33,-54.3863,-451.034,5409.99,-43.3113,-451.91,5415.1,-41.913,-456.813,5437.64,-60.8084,-456.834,5434.81,-65.6757,-455.017,5431.77,-69.5571,-453.091,5421.04,-61.5184,-451.073,5411.86,-53.2319,-448.715,5404.73,-44.7224,-445.098,5404.37,-27.5167,-441.754,5404.34,-19.6122,-443.544,5411.43,-20.9055,-446.748,5411.19,-28.1935,-438.499,5405.75,-11.9582,-440.293,5412.71,-13.6986,-449.552,5412.18,-35.2208,-448.256,5406.12,-35.4645,-445.765,5400.15,-36.0096,-442.487,5397.86,-27.264,-439.14,5397.6,-18.6523,-435.988,5399.11,-10.3439,-433.096,5411.96,1.79692,-430.985,5416.52,7.55428,-432.567,5420.92,4.39474,-434.76,5417.58,-0.796497,-429.302,5422,12.3238,-430.853,5425.12,8.36937,-437.351,5414.8,-6.9111,-435.606,5408.33,-4.76401,-433.291,5402.13,-2.50638,-431.025,5406.49,4.72838,-429.165,5412.01,11.2325,-427.685,5418.52,16.874,-427.189,5435.59,18.5787,-426.52,5443.42,20.5137,-428.181,5444.19,13.1465,-428.888,5436.92,12.2666,-426.021,5451.72,21.8667,-427.625,5452.11,13.6104,-429.703,5430.51,10.8335,-428.077,5428.41,15.9242,-426.56,5425.85,21.524,-425.72,5433.83,25.276,-425.09,5442.32,28.2255,-424.601,5451.15,30.4669,-425.428,5469.14,23.3003,-425.432,5478.03,23.4907,-427.248,5477.82,13.5534,-427.133,5469.07,13.7552,-425.702,5486.86,23.4036,-427.635,5486.56,13.2478,-427.267,5460.45,13.7939,-425.64,5460.33,22.7777,-424.178,5460.18,32.0957,-423.896,5469.31,33.1626,-423.829,5478.44,33.717,-424.048,5487.48,33.8106,-427.187,5503.97,22.5374,-428.404,5512.14,21.7121,-430.512,5511.55,11.9477,-429.285,5503.5,12.4708,-429.939,5520,20.5929,-431.983,5519.23,11.2996,-428.32,5495.15,12.8955,-426.287,5495.52,23.0944,-424.628,5496.32,33.4928,-425.568,5504.95,32.753,-426.865,5513.35,31.5782,-428.518,5521.51,29.9568,-433.978,5534.63,17.1077,-436.503,5541.44,14.1484,-437.641,5539.41,7.56095,-435.575,5533.19,9.33008,-439.418,5547.94,9.84448,-440.008,5545.07,4.41773,-433.679,5526.46,10.5017,-431.794,5527.49,19.1572,-430.525,5529.41,27.8769,-432.914,5537.06,25.0419,-435.706,5544.46,21.1533,-438.928,5551.63,15.9146,-446.465,5559.97,-3.86548,-450.391,5565.26,-12.7886,-449.676,5559.61,-16.3396,-446.023,5554.92,-7.92681,-454.42,5569.75,-22.7905,-453.647,5563.86,-25.8058,-442.8,5550.16,-0.876084,-442.772,5554.16,3.75906,-442.607,5558.58,9.02905,-446.566,5565.03,0.674471,-450.636,5570.71,-8.97054,-454.642,5575.34,-19.7265,-462.169,5575.08,-44.841,-465.448,5575.15,-55.823,-465.121,5570.08,-55.8577,-461.78,5569.46,-46.2661,-457.818,5567.26,-36.0149,-458.42,5573.15,-33.6523,-458.411,5578.65,-31.4143,-461.84,5580.27,-43.5542,-464.828,5579.85,-55.6658,-432.324,5742.55,-173.146,-434.262,5711.92,-174.87,-438.447,5708.71,-143.8,-436.234,5742.32,-143.789,-430.766,5774.2,-145.204,-427.246,5771.55,-173.033,-420.838,5803.99,-148.268,-417.882,5798.51,-175.274,-421.732,5770.22,-197.521,-426.205,5744.56,-200.269,-413.468,5793.17,-198.008,-427.986,5716.17,-203.879,-383.504,5844.83,-187.654,-359.804,5863.74,-195.007,-359.417,5847.94,-221.918,-382.292,5831.47,-213.004,-332.938,5879.11,-201.748,-332.892,5861.83,-229.935,-400.315,5813.4,-204.102,-403.082,5823.01,-180.612,-405.247,5831.33,-153.195,-384.893,5855.67,-159.18,-360.673,5876.47,-165.418,-333.489,5893.17,-171.104,-273.112,5897.91,-210.635,-241.242,5902.28,-212.789,-241.052,5883.39,-243.142,-273.106,5879.2,-240.55,-208.797,5903.94,-213.421,-208.365,5885.02,-243.922,-303.92,5872.13,-236.147,-303.859,5890.34,-206.956,-304.24,5905.22,-175.431,-273.433,5913.23,-178.437,-241.577,5917.77,-180.16,-209.18,5919.44,-180.637,-375.745,5359.42,-160.334,-377.306,5383.21,-165.782,-368.387,5378.96,-186.247,-366.538,5355.49,-182.556,-357.598,5374.6,-206.57,-355.372,5351.51,-204.336,-366.074,5329.64,-180.335,-375.52,5333.15,-155.636,-354.659,5326.22,-203.818,-383.846,5337.5,-130.31,-383.778,5363.86,-138.571,-385.016,5387.68,-146.077,-296.068,5345.87,-243.41,-300.455,5365,-244.012,-259.984,5362.14,-253.04,-257.265,5347.5,-252.414,-232.527,5360.98,-255.213,-231.546,5349.66,-254.694,-259.579,5330.41,-253.438,-297.25,5324.55,-244.271,-233.413,5336.18,-255.7,-334.051,5323.11,-226.171,-334.662,5347.41,-225.619,-338.351,5369.7,-226.648,-209.121,5351.06,-253.853,-209.187,5359.53,-254.181,-203.813,5359.04,-253.589,-203.752,5351.06,-253.261,-203.976,5341.51,-253.878,-209.763,5341.05,-254.569,-218.485,5339.57,-255.364,-217.296,5350.8,-254.502,-217.548,5360.27,-254.893,-337.387,5171.13,32.9203,-355.378,5186.29,18.0751,-367.76,5171.77,-4.82053,-344.883,5157.85,19.665,-327.415,5147.78,34.5735,-324.568,5160.35,42.7073,-323.797,5173.27,54.3039,-335.496,5183.88,46.386,-324.888,5189.9,73.1285,-337.195,5200.29,65.8613,-350.96,5199.01,36.592,-352.355,5215.11,58.1349,-345.955,5234.06,122.577,-348.038,5244.37,149.972,-358.985,5259.98,142.724,-358.74,5248.96,115.401,-348.131,5252.92,176.666,-357.551,5269.05,169.684,-356.175,5233.79,85.9499,-341.523,5219.22,93.0153,-328.316,5207.96,99.8579,-333.392,5220.92,129.684,-336.445,5229.83,157.559,-337.622,5237.45,184.092,-242.231,3661.67,-225.986,-292.244,3669.99,-234.646,-290.792,3727.25,-237.743,-250.552,3723.12,-232.512,-338.802,3673.99,-236.83,-334.097,3727.78,-240.521,-209.811,3714.69,-225.989,-198.521,3649.52,-214.73,-172.537,3709.78,-222.289,-171.506,3644.44,-210.14,-192.122,3598.31,-211.929,-235.656,3606.35,-224.057,-191.888,3550.18,-216.975,-237.497,3552.83,-228.956,-170.705,3595.44,-208.203,-169.925,3549.43,-213.345,-293.989,3613.99,-234.954,-343.737,3619.88,-236.609,-297.827,3557.61,-238.175,-348.768,3564.24,-238.369,-483.543,3963.66,339.799,-527.585,3965.32,292.502,-516.651,3903.92,292.758,-473.664,3899.9,338.747,-426.795,3894.37,371.061,-435.149,3960.39,373.087,-445.25,4025.27,372.525,-495.33,4026.89,338.197,-539.819,4026.11,289.244,-579.322,3917.04,-119.978,-596.798,3972.47,-123.274,-581.433,3944.87,-162.112,-561.264,3895.38,-150.869,-548.393,3856.13,-145.631,-569.307,3872.81,-117.836,-599.718,3883.12,-74.4391,-605.302,3933.67,-82.5904,-613.485,3988.78,-89.7008,-577,3737.54,136.882,-552.397,3732.06,180.661,-554.795,3786.43,183.151,-578.269,3788.51,138.083,-603.31,3786.71,82.9895,-603.775,3738.52,82.8351,-610.49,3691.25,80.0243,-581.95,3690.53,136.01,-620.709,3640.89,75.1107,-592.363,3640.71,133.928,-554.668,3682.69,180.863,-561.228,3632.05,182.116,-437.858,3684.99,-220.514,-483.744,3696.79,-199.482,-482.964,3745.08,-199.897,-436.435,3733.77,-224.066,-383.966,3728.75,-237.281,-387.739,3677.93,-232.693,-392.397,3626.14,-231.665,-441.213,3634.35,-219.785,-397.161,3572.74,-232.622,-445.743,3582.68,-220.581,-487.284,3645.01,-200.22,-492.301,3593.12,-201.114,-249.979,3640.11,400.347,-246.479,3587.38,398.019,-178.793,3576.91,386.859,-180.332,3590.68,388.187,-172.756,3576.22,385.906,-172.936,3587.28,387.415,-253.655,3540.87,397.537,-182.608,3563.2,388.886,-172.55,3566.53,387.926,-212.117,3713.43,409.876,-265.926,3728.68,409.474,-175.031,3711.42,410.571,-322.069,3739.53,403.068,-315.616,3662.61,396.004,-314.935,3597.27,392.923,-321.333,3536.02,392.975,-256.111,3420.8,-239.335,-209.831,3417.22,-238.738,-199.553,3366.84,-249.018,-246.41,3379.24,-247.513,-167.736,3422.72,-238.21,-166.731,3364.5,-249.335,-304.515,3391.85,-246.456,-308.482,3431.39,-239.334,-361.429,3402.64,-240.942,-357.99,3440.41,-235.911,-304.053,3494.26,-240.693,-246.749,3491.95,-236.661,-353.817,3502.11,-238.735,-197.531,3496.85,-227.406,-169.054,3499.13,-223.801,-476.597,3454.11,287.646,-504.695,3483.19,259.39,-502.682,3423.39,253.363,-472.499,3396.28,281.091,-538.009,3505.15,224.405,-539.174,3443.77,214.981,-442.628,3371.91,310.996,-442.033,3427.3,323.003,-398.258,3360.64,354.37,-393.837,3416.04,366.427,-440.397,3487.9,334.294,-478.92,3511.14,291.156,-388.657,3475.13,376.047,-505.509,3538.05,260.022,-534.523,3561.9,226.187,-608.991,3532.73,128.685,-603.363,3586.8,130.803,-628.006,3590.58,71.7149,-631.682,3540.32,70.1771,-634.467,3589.54,12.1658,-637.43,3541.23,11.3956,-631.066,3487.04,70.1354,-606.24,3476.59,126.07,-638.783,3491.23,11.6319,-574.983,3461.7,171.917,-575.793,3520.86,177.957,-569.987,3577.85,181.596,-609.185,3525.85,-93.11,-605.626,3575.21,-92.4278,-576.691,3564.34,-137.236,-580.42,3512.18,-138.014,-540.02,3552.46,-174.475,-543.114,3491.9,-174.014,-572.707,3441.17,-134.742,-607.77,3466.83,-93.8622,-535.765,3423.25,-165.964,-630.396,3484.82,-43.916,-628.549,3535.52,-42.7315,-625.404,3584.1,-42.0694,-451.505,3459.32,-218.558,-449.686,3526.62,-221.389,-400.77,3513.12,-232.844,-403.328,3448.1,-229.446,-408.508,3409.2,-230.499,-452.171,3413.87,-215.719,-499.693,3417.16,-191.26,-500.964,3474.41,-199.526,-497.229,3539.84,-202.002,-278.466,3408.46,409.812,-336.799,3411.17,393.065,-340.63,3353.59,390.23,-282.43,3347.99,410.801,-225.461,3344.35,424.395,-221.753,3407.8,420.342,-169.037,3344.28,427.893,-170.079,3408.71,423.116,-209.762,3482.3,408.13,-268.597,3474.97,403.91,-171.278,3484.68,409.243,-330.303,3472.44,394.323,-732.414,1638.19,178.955,-748.373,1654.85,164.541,-749.055,1595.49,161.491,-732.319,1578.27,171.408,-761.269,1519.09,155.388,-737.699,1511.11,163.951,-756.945,1668.17,155.906,-762.666,1611.49,152.278,-789.096,1526.69,140.799,-708.214,1564.26,182.243,-700.308,1624.71,199.949,-674.523,1554.29,189.801,-658.652,1616.95,211.05,-714.115,1502.3,170.99,-684.917,1493.05,175.07,-681.209,1688.14,231.924,-725.444,1693.92,198.168,-667.392,1753.83,256.276,-706.12,1758.33,234.879,-643.727,1682.96,235.343,-635.566,1747.98,256.307,-747.807,1703.06,169.906,-755.872,1711.29,158.562,-741.983,1756.36,185.497,-753.742,1757.02,162.991,-771.143,1677.82,141.842,-800.997,1624.1,120.507,-776.882,1622.31,140.973,-763.053,1674.79,149.514,-843.377,1549.63,89.0153,-819.426,1537.05,117.833,-760.397,1714.34,152.622,-765.165,1715.19,147.259,-758.647,1758.42,154.407,-763.147,1756.11,148.803,-776.99,1716.16,136.032,-795.882,1677.19,118.886,-823.563,1717.89,80.4007,-837.226,1675.55,57.5191,-772.774,1753.1,140.225,-813.137,1758.54,98.1657,-836.135,1622.46,81.3287,-858.765,1562.33,56.4156,-850.913,1628.52,37.0675,-864.486,1574.03,18.0855,-820.94,1685.49,-27.6778,-836.776,1637.11,-38.0145,-847.677,1634.04,-3.99131,-836.03,1680.62,8.16433,-850.384,1581.77,-55.2694,-859.854,1580.43,-21.0938,-830.814,1717.62,18.6889,-813.071,1715.74,-21.7751,-832.395,1754.37,31.3652,-815.054,1742.43,-13.8567,-794.23,1715.47,-53.5779,-803.067,1691.24,-59.4015,-777.333,1713.74,-79.9912,-784.212,1692.7,-88.4284,-794.387,1733.05,-49.1982,-776.477,1727.96,-76.2244,-824.433,1640.64,-71.2854,-840.065,1583.05,-88.4163,-805.721,1643.45,-106.683,-823.443,1585.16,-124.788,-746.064,1682.73,-139.863,-757.504,1642.66,-160.894,-782.389,1644.52,-137.628,-765.747,1689.36,-115.367,-772.396,1588.71,-181.663,-800.15,1587.48,-157.592,-760.993,1709.88,-104.395,-742.519,1703.85,-128.554,-760.159,1723.89,-100.013,-741.734,1719.68,-124.274,-717.582,1697.21,-153.35,-721.151,1675.2,-162.289,-681.049,1691.99,-172.12,-686.942,1668.87,-178.472,-715.475,1716.23,-150.675,-676.175,1713.26,-169.868,-729.197,1639.17,-179.21,-740.445,1589.13,-198.728,-694.729,1635.38,-191.272,-703.991,1588.2,-209.074,-601.401,1610.99,186.938,-625.796,1613.12,204.066,-640.049,1548.55,186.587,-611.757,1544.63,172.511,-652.57,1484.8,170.518,-621.558,1478.14,155.694,-587.814,1539.58,150.016,-581.693,1609.62,164.38,-564.194,1533.54,117.414,-562.073,1605.98,135.43,-594.017,1472.62,131.209,-568.16,1468.17,97.6703,-572.264,1680.77,169.945,-590.241,1677.48,199.333,-562.793,1744.93,167.076,-579.906,1739.75,209.439,-559.626,1683.92,141.441,-554.253,1753.38,136.124,-614.362,1678.75,222.559,-605.543,1742.51,241.102,-514.619,1621.79,-122.294,-502.071,1601.33,-94.9597,-496.934,1549.51,-105.863,-506.646,1570.67,-133.738,-494.125,1499.78,-117.638,-501.923,1519.77,-146.045,-499.213,1587.42,-55.7143,-497.107,1535.47,-69.6335,-495.186,1483.15,-82.2563,-525.659,1596.28,-152.492,-533.23,1642.62,-138.706,-550.199,1618.38,-164.04,-553.765,1657.79,-148.584,-518.867,1540.67,-168.399,-545.221,1561.12,-184.792,-537.379,1673.58,-129.371,-520.563,1661.48,-113.497,-536.988,1695.38,-123.127,-520.905,1690.36,-106.897,-554.74,1681.68,-139.391,-553.313,1698.79,-133.863,-507.007,1648.01,-86.1175,-501.912,1638.94,-43.6203,-506.503,1684.89,-78.5952,-500.26,1682.75,-32.9672,-605.003,1666.09,-170.736,-612.158,1633.12,-185.356,-653.215,1633.36,-193.244,-644.183,1666.13,-180.781,-619.175,1582.96,-206.698,-662.153,1586.22,-211.57,-636.587,1689.09,-173.782,-599.589,1687.35,-162.593,-630.912,1709.04,-169.881,-595.641,1704.63,-157.935,-574.136,1685.3,-149.81,-576.277,1664.61,-158.427,-571.356,1701.41,-145.211,-578.364,1630.06,-174.162,-579.535,1575.54,-197.183,-521.363,1590.49,41.1987,-540.604,1597.86,92.252,-540.626,1528.44,73.1768,-520.86,1527.09,24.5015,-543.378,1466.22,55.2597,-521.733,1467.46,8.39293,-505.85,1528.73,-24.3427,-507.52,1586.28,-7.86833,-505.026,1472.73,-38.8501,-509.616,1641.17,7.35003,-525.73,1654.65,59.7115,-510.258,1693.36,23.2736,-532.446,1724.33,81.7012,-544.487,1674.5,108.439,-546.735,1749.72,114.419,-1000.7,226.277,-137.727,-1018.54,173.183,-129.643,-1038.49,178.483,-98.0825,-1017.19,233.352,-109.271,-1052.43,191.773,-66.3438,-1028.6,246.191,-80.5051,-1033.49,122.802,-125.275,-1056.58,125.936,-90.6656,-1081.35,132.886,-48.3233,-997.069,288.733,-120.578,-982.526,280.917,-147.747,-979.126,344.487,-131.457,-965.709,336.358,-158.705,-1007.17,300.811,-92.8843,-988.522,355.815,-103.438,-965.338,276.706,-174.509,-982.029,222.989,-165.958,-947.307,275.439,-200.98,-963.029,222.5,-194.59,-949.756,331.3,-184.871,-932.752,329.183,-209.652,-997.977,171.067,-160.227,-1011.33,121.853,-158.323,-978.102,171.262,-191.187,-990.713,122.62,-191.475,-927.493,226.414,-252.284,-906.714,229.76,-276.058,-919.832,178.96,-282.062,-941.673,175.591,-255.2,-930.003,131.128,-288.343,-953.009,127.577,-259.782,-881.097,233.33,-292.927,-892.048,182.62,-301.363,-900.283,134.964,-308.778,-960.215,172.898,-223.877,-945.557,223.817,-224.246,-972.26,124.634,-226.393,-930.23,276.455,-227.275,-912.564,279.036,-251.374,-916.182,329.876,-232.739,-898.982,332.452,-252.815,-892.762,282.458,-271.252,-869.277,285.999,-284.89,-880.089,335.982,-268.565,-858.437,339.536,-278.671,-736.54,232.661,-210.251,-733.728,186.329,-216.721,-737.3,188.419,-250.825,-741.498,235.806,-242.91,-751.534,190.785,-278.408,-755.472,239.158,-269.349,-732.049,141.474,-221.758,-734.296,142.873,-256.545,-748.528,144.589,-284.759,-745.306,285.003,-234.419,-738.97,280.835,-203.605,-747.139,335.977,-226.968,-739.51,331.213,-198.044,-758.995,289.307,-259.45,-760.754,340.833,-250.581,-738.197,279.474,-168.425,-737.995,232.392,-172.918,-741.196,283.587,-130.3,-743.264,237.665,-132.466,-736.816,329.098,-165.018,-738.007,332.184,-129.107,-737.476,187.114,-177.672,-737.908,142.9,-181.859,-745.2,193.37,-135.254,-747.992,149.654,-138.313,-813.628,239.011,-298.461,-816.371,189.114,-308.639,-855.679,186.191,-310.009,-848.52,236.593,-300.246,-818.212,141.997,-316.229,-860.891,138.767,-317.791,-840.565,288.937,-290.263,-810.218,290.79,-287.637,-832.963,342.189,-281.818,-806.381,343.549,-278.113,-781.831,291.075,-277.277,-781.064,240.046,-288.013,-781.406,343.227,-267.664,-779.773,190.831,-297.892,-778.628,144.118,-304.936,-757.982,270.037,-49.0362,-768.519,291.517,-10.4294,-774.564,249.466,-1.56546,-763.143,227.318,-46.1185,-781.867,205.017,5.10228,-769.579,183.688,-44.7493,-781.949,313.192,21.9204,-788.606,273.023,36.458,-797.763,226.376,61.3845,-753.555,207.695,-91.0414,-749.744,251.15,-90.4429,-758.417,164.242,-92.5819,-746.174,295.848,-90.6489,-753.491,313.406,-51.9798,-742.031,343.029,-91.5235,-748.989,358.846,-54.7428,-763.506,333.41,-16.8002,-776.581,353.01,12.3826,-758.983,376.843,-21.2399,-772.116,394.236,6.50918,-866.122,727.221,25.8555,-876.094,670.418,26.3354,-852.225,669.341,36.8199,-840.803,726.732,36.1564,-827.24,667.401,41.8502,-814.649,726.153,40.5623,-884.822,615.897,26.8984,-861.132,614.924,37.0017,-836.545,612.625,41.8077,-826.866,784.399,35.9143,-855.057,784.708,26.9148,-814.614,839.399,39.169,-845.46,841.606,31.591,-798.699,784.885,37.737,-783.606,838.427,37.8398,-880.102,785.027,11.0081,-888.799,727.18,9.82288,-900.406,784.362,-10.7137,-907.519,725.99,-11.5221,-872.428,843.086,15.7502,-894.055,842.853,-6.46594,-897.522,670.277,10.5458,-906.163,615.251,11.5859,-915.318,668.509,-10.3999,-923.701,612.69,-8.85242,-930.994,718.882,-68.248,-936.347,659.561,-66.357,-928.207,664.817,-36.386,-921.541,723.11,-37.886,-943.257,601.378,-63.7351,-935.983,607.92,-34.3294,-915.943,782.093,-37.5084,-927.078,778.672,-68.3266,-911.374,841.065,-33.9331,-924.479,838.257,-65.5124,-933.682,774.524,-102.15,-935.926,713.683,-101.593,-935.545,770.129,-138.11,-936.383,707.88,-136.903,-933.001,834.901,-100.087,-933.9,833.728,-140.003,-939.895,653.103,-99.2565,-945.77,593.498,-95.9607,-939.011,645.802,-134.03,-943.766,584.716,-129.894,-923.431,698.013,-208.673,-924.973,630.862,-203.846,-933.851,638.022,-169.619,-932.245,702.049,-173.353,-927.759,566.901,-197.549,-937.493,575.469,-164.427,-929.664,768.35,-177.988,-915.21,771.951,-214.82,-917.054,841.508,-186.608,-894.46,849.597,-218.563,-898.615,776.617,-241.449,-911.102,695.693,-239.613,-883.57,779.125,-260.202,-895.634,695.475,-264.56,-881.416,850.184,-235.179,-871.971,848.305,-248.937,-912.937,625.42,-234.528,-915.374,560.161,-227.251,-898.295,622.806,-259.486,-901.147,556.392,-251.521,-855.944,703.16,-292.575,-877.184,698.419,-281.862,-867.374,781.83,-275.725,-847.75,784.835,-287.546,-859.69,847.042,-263.567,-843.598,845.855,-279.384,-825.152,786.76,-296.121,-832.118,708.067,-297.678,-797.413,786.969,-301.803,-805.757,711.593,-297.987,-820.87,846.578,-296.755,-789.383,849.215,-308.056,-841.516,632.63,-290.989,-862.732,627.884,-286.701,-850.274,563.98,-283.337,-869.094,559.756,-278.654,-817.812,636.888,-290.418,-828.932,567.962,-283.312,-881.609,624.119,-276.538,-885.885,556.742,-268.353,-765.968,723.078,33.4168,-743.202,718.214,21.6016,-732.093,786.28,18.1125,-752.584,787.942,27.2918,-718.326,848.53,10.4456,-736.131,844.884,20.4189,-720.943,710.908,2.25179,-709.917,779.167,2.78871,-699.528,848.236,-1.25189,-773.808,786.5,33.9828,-789.55,725.354,39.5368,-757.034,840.445,30.0897,-802.555,664.735,41.3963,-778.711,660.975,34.9423,-812.514,609.292,41.2321,-789.492,604.774,34.6488,-756.118,655.846,21.8576,-735.281,649.415,1.38231,-767.937,598.917,21.4329,-748.301,591.568,0.959143,-669.975,697.262,-200.977,-689.696,627.272,-197.878,-700.596,631.506,-224.639,-680.579,702.929,-228.566,-717.069,635.672,-247.065,-697.072,707.628,-252.103,-706.906,561.228,-195.482,-717.717,564.559,-221.277,-733.759,567.989,-242.763,-661.319,775.334,-229.957,-651.565,768.166,-203.592,-641.911,840.017,-232.802,-632.968,831.947,-207.48,-675.81,780.062,-254.775,-655.415,844.79,-257.314,-646.152,759.937,-172.383,-665.016,692.048,-169.458,-646.246,754.476,-136.492,-665.791,689.04,-134.34,-627.925,822.13,-176.674,-627.273,814.305,-139.601,-684.414,624.352,-167.346,-701.419,559.363,-165.855,-684.795,624.074,-133.604,-701.347,560.329,-132.87,-747.479,711.969,-285.224,-776.944,712.598,-294.053,-764.539,785.564,-301.465,-729.657,783.818,-293.124,-750.017,851.128,-311.366,-710.361,850.955,-302.422,-698.86,782.388,-276.599,-719.995,710.334,-271.157,-678.294,848.204,-282.397,-739.065,638.442,-264.59,-764.547,639.659,-277.482,-754.943,570.152,-259.461,-779.226,570.94,-271.621,-791.475,639.181,-286.004,-804.569,570.245,-279.488,-684.28,695.884,-60.1173,-672.509,690.796,-96.9286,-652.737,753.937,-97.6375,-665.152,757.105,-59.3949,-632.879,812.999,-97.6081,-649.569,822.999,-55.6756,-685.489,766.536,-23.6461,-700.458,702.608,-26.1284,-676.323,839.481,-21.0471,-716.94,641.821,-27.0787,-701.873,634.171,-60.8253,-731.042,582.574,-27.4006,-716.942,573.396,-60.9522,-690.887,627.801,-97.2153,-706.782,565.493,-97.0057,-938.571,361.893,56.2786,-950.837,332.567,74.9262,-915.682,338.629,83.0766,-904.87,366.696,64.4112,-881.886,338.241,86.3528,-872.302,366.042,67.731,-962.553,305.59,97.0781,-926.602,312.38,105.579,-892.193,312.079,109.399,-894.796,397.586,50.0231,-926.613,394.282,41.6831,-886.091,432.302,40.3547,-915.819,430.444,31.6896,-863.841,396.562,53.7147,-856.902,430.876,44.4847,-956.416,386.496,28.6909,-970.401,351.654,43.3319,-981.331,374.076,11.0438,-997.352,336,25.5728,-943.44,425.018,18.4799,-966.307,415.731,0.713482,-984.344,320.243,61.8764,-997.198,292.015,83.796,-1013.2,301.847,43.9011,-1027.69,271.963,65.6343,-1027.89,291.035,-23.0794,-1032.06,266.766,-51.3435,-1054.94,217.862,-35.2285,-1048.2,249.033,-5.77496,-1078.17,177.622,-14.0281,-1067.93,214.059,15.547,-1034.38,277.57,20.9769,-1016.42,314.954,2.99785,-1051.18,245.741,42.4927,-998.485,356.871,-11.2621,-1008.26,337.307,-36.9019,-981.776,402.299,-21.6193,-990.319,386.589,-47.2611,-1011.02,317.811,-64.5512,-992.41,370.472,-74.9539,-819.444,348.664,58.8715,-827.237,317.453,77.0352,-806.059,296.871,61.7922,-798.863,332.667,44.5593,-836.533,288.699,101.968,-815.26,264.473,88.0754,-793.07,369.356,33.0608,-813.058,382.217,46.1988,-788.488,408.236,26.0301,-808.085,418.944,38.2493,-836.622,391.362,52.7616,-843.876,359.911,66.2402,-830.895,426.457,44.092,-852.459,331.213,84.78,-862.173,304.376,108.635,-691.316,1878.97,261.08,-652.828,1874.49,274.3,-644.57,1944.08,267.997,-683.173,1947.87,254.665,-640.965,1992.7,265.856,-675.543,1996.51,255.451,-619.944,1871.69,273.295,-611.946,1939.31,267.147,-609.257,1985.49,264.547,-718.251,1946.43,226.468,-728.009,1886.02,222.745,-740.371,1937.46,195.776,-747.299,1882.48,184.337,-707.43,1993.92,235.465,-734.672,1985.43,208.969,-733.217,1823.25,211.546,-696.166,1818.26,254.767,-750.708,1817.17,172.295,-660.582,1812.73,268.402,-628.753,1807,269.262,-762.358,1865.79,157.672,-763.17,1808.79,151.925,-757.69,1814.56,159.463,-755.995,1874.5,167.045,-752.186,1927.58,177.406,-760.747,1917.44,166.458,-752.043,1978.25,187.857,-763.628,1971.52,174.518,-776.019,1904.18,152.985,-773.876,1854.49,146.69,-820.976,1895.38,114.124,-814.031,1847.63,111.497,-782.337,1961.9,158.612,-829.365,1952.75,115.505,-773.4,1800.51,142.454,-811.548,1801.54,106.372,-832.123,1829.69,4.17101,-824.2,1781.79,-3.58352,-836.638,1796.51,43.3242,-839.875,1840.63,51.1824,-843.262,1887.87,55.4365,-835.857,1876.65,7.03296,-846.06,1942.03,57.4063,-838.186,1929.57,7.65438,-820.657,1865.78,-39.3229,-814.34,1813.56,-40.3803,-799.319,1855.14,-81.0868,-790.721,1794.3,-77.3873,-822.87,1917.74,-40.3957,-801.414,1907,-83.3439,-802.03,1762.03,-44.3449,-780.463,1748.2,-74.8836,-742.896,1779.4,-132.617,-742.203,1739.84,-124.825,-762.232,1741.9,-99.8068,-767.511,1782.23,-106.431,-773.49,1845.17,-114.872,-743.924,1837.38,-141.147,-775.126,1897.85,-117.774,-744.332,1889.92,-143.797,-710.272,1831.28,-160.092,-711.6,1780.45,-156.07,-672.513,1824.48,-170.542,-672.524,1777.8,-170.21,-709.362,1882.87,-161.524,-670.547,1876.37,-171.068,-713.937,1741.1,-151.145,-673.408,1739.94,-168.967,-562.293,1860.92,226.298,-544.351,1849.2,165.209,-537.748,1904.45,179.222,-559.502,1926.12,229.094,-535.943,1954.68,199.354,-558.151,1969.15,234.277,-539.727,1842.92,131.482,-531.286,1883.84,139.195,-522.868,1931.84,158.434,-584.885,1935.05,255.59,-590.649,1867.82,262.787,-582.457,1977.77,253.861,-598.638,1801.27,257.391,-570.644,1796.16,218.452,-553.466,1798.71,162.887,-547.278,1803.27,131.052,-495.639,1752.64,-95.0522,-517.181,1746.27,-116.551,-494.826,1797.88,-115.568,-471.028,1806.63,-88.946,-474.383,1860.92,-114.122,-451.644,1867.78,-82.3029,-538.954,1743.65,-130.708,-523.238,1792.23,-133.673,-505.736,1858.01,-136.362,-458.508,1818.7,-48.5982,-478.797,1762.34,-60.5255,-469.126,1835.24,8.74802,-479.42,1778.32,-6.56548,-442.921,1878.56,-38.6383,-456.085,1891.4,16.754,-497.254,1719.11,-70.6259,-513.422,1716.74,-100.641,-491.527,1726.06,-21.8212,-531.298,1716.06,-118.393,-548.323,1716.74,-130.814,-591.116,1754.11,-157.291,-592.712,1724.46,-155.867,-628.292,1732.81,-167.928,-628.877,1766.84,-168.739,-631.115,1814.93,-171.153,-589.658,1801.99,-162.092,-628.2,1869.93,-172.509,-584.885,1864.17,-166.631,-554.173,1793.2,-148.685,-562.241,1745.88,-143.705,-543.194,1859.43,-154.089,-567.618,1719.06,-142.733,-531.287,1827.93,99.7697,-512.565,1808.68,65.6647,-506.384,1853.41,73.6881,-522.846,1862.88,101.994,-490.094,1897.3,72.6725,-508.28,1902.98,104.335,-528.449,1871.4,118.284,-536.822,1836.6,114.636,-516.46,1912.86,127.096,-543.126,1800.26,113.957,-535.099,1786.51,95.2657,-511.502,1752.18,45.9993,-763.959,1393.38,150.76,-733.099,1387.62,158.437,-722.326,1444.53,164.816,-750.435,1450.77,157.988,-701.652,1381.12,158.356,-692.506,1436.09,165.948,-779.804,1455.6,145.555,-794.281,1399.19,135.934,-810.147,1462.38,126.251,-823.114,1406.65,114.517,-804.682,1347.45,127.48,-774.918,1341.92,143.321,-814.176,1295.8,119.054,-784.908,1291.35,135.646,-833.134,1353.29,104.624,-842.495,1299.63,94.9099,-744.066,1336.39,151.459,-712.364,1330.55,151.202,-754.578,1286.24,144.137,-723.07,1280.42,143.979,-871.115,1427.03,55.9616,-883.332,1366.09,41.3298,-860.041,1359.74,75.4421,-849.161,1416.54,87.5354,-894.264,1305.44,27.3859,-869.981,1302.9,63.7625,-837.514,1473.82,100.874,-858.323,1487.58,71.4681,-872.965,1500.59,37.6629,-886.533,1436.95,18.5469,-878.058,1510.71,-2.33746,-890.742,1444,-24.73,-899.846,1372.07,0.789046,-911.003,1307.15,-17.7389,-899.134,1372.23,-52.7065,-899.746,1305.09,-83.5277,-875.185,1441.05,-95.1251,-875.667,1354.74,-116.64,-884.55,1362.27,-95.5754,-883.545,1444.01,-64.7044,-879.038,1291.17,-131.848,-884.374,1297.36,-117.527,-872.905,1515,-41.9312,-863.538,1516.07,-74.7909,-853.422,1518.5,-106.543,-866.344,1441.46,-124.686,-836.862,1522.38,-143.516,-851.138,1449.15,-162.841,-871.218,1353.93,-134.903,-876.057,1288.79,-145.89,-863.437,1365.84,-169.808,-872.008,1294.04,-177.052,-796.025,1459.24,-227.061,-807.387,1385.98,-251.924,-839.463,1381.13,-221.097,-826.262,1455.83,-200.169,-817.388,1313.73,-274.289,-850.928,1307.96,-239.937,-812.882,1526.11,-177.646,-784.126,1528.45,-202.679,-750.754,1529.8,-220.539,-760.924,1461.46,-246.197,-712.212,1529.67,-231.295,-720.195,1461.93,-257.761,-770.58,1388.84,-272.487,-779.195,1317.1,-295.929,-727.822,1389.78,-284.949,-734.886,1318.41,-309.033,-638.603,1367.72,131.83,-608.041,1362.18,105.992,-600.933,1414.81,116.626,-629.942,1419.99,141.998,-578.396,1357.9,72.2615,-573.174,1411.24,83.0792,-660.961,1427.2,158.647,-669.932,1374.12,149.431,-680.044,1324.1,141.864,-647.585,1317.53,123.893,-690.269,1273.85,134.622,-656.867,1266.96,116.589,-615.473,1311.35,97.7398,-584.187,1306.06,63.8527,-623.552,1260.16,90.4037,-591.016,1253.89,56.587,-495.446,1399.25,-176.683,-494.267,1333.01,-192.178,-511.021,1348.13,-223.227,-512.355,1417.07,-204.324,-539.294,1362.37,-248.889,-540.055,1433.31,-226.807,-495.914,1267.29,-205.545,-512.385,1279.95,-239.647,-540.873,1292.34,-268.163,-515.28,1482.53,-185.3,-498.363,1462.63,-160.744,-542.131,1500.29,-204.891,-491.113,1443.24,-130.581,-488.855,1382.19,-143.713,-493.266,1427.05,-94.1682,-492.111,1368.23,-105.242,-488.357,1319,-156.045,-490.625,1256.06,-166.586,-492.62,1308.08,-115.135,-495.689,1247.95,-123.499,-624.913,1454.31,-255.903,-673.415,1459.65,-260.693,-668.149,1527.29,-234.236,-622.497,1522.02,-230.238,-579.178,1513.23,-220.164,-579.017,1445.63,-244.303,-579.757,1373.73,-268.858,-627.762,1382.14,-282.168,-582.209,1302.77,-290.364,-631.55,1310.85,-305.246,-678.667,1387.51,-287.854,-684.056,1316.19,-311.805,-524.698,1355.77,-14.5948,-504.742,1359.73,-61.0999,-504.557,1416.69,-50.8638,-522.977,1411.23,-4.39928,-546.519,1409.73,41.4926,-549.933,1355.47,30.9628,-554.211,1302.16,22.6827,-527.594,1300.58,-22.8312,-559.949,1248.57,15.6604,-532.364,1245.16,-29.7476,-506.381,1302.25,-69.75,-510.273,1244.63,-77.0096,-1220.36,14.0293,151.562,-1230.61,12.5177,190.698,-1226.43,34.8589,194.776,-1216.17,39.4053,155.631,-1214.99,58.1111,206.177,-1204.28,65.609,167.214,-1237.18,12.5911,232.737,-1233.43,32.3829,236.646,-1222.75,53.0537,247.651,-1202.63,45.1881,118.697,-1206.01,16.5154,114.857,-1185.84,51.3711,83.4617,-1187.97,19.8501,79.8104,-1190.76,74.4396,130.331,-1174.54,83.4911,95.096,-1200.05,-9.29288,119.977,-1216.91,-8.19751,155.8,-1187.61,-28.2379,136.138,-1206.82,-24.5261,169.366,-1178.97,-8.74191,85.4945,-1152.96,-32.9637,105.575,-1227.83,-6.78741,194.47,-1234.22,-4.46109,236.289,-1218.4,-20.9314,206.624,-1224.77,-16.9111,247.66,-858.706,56.4252,342.842,-862.715,24.2057,340.283,-855.405,24.9128,298.482,-848.031,62.0553,302.821,-851.942,29.9628,252.313,-840.419,71.2209,258.922,-875.464,-1.99588,337.675,-872.301,-5.30416,295.894,-882.592,-10.4431,241.524,-850.616,99.0267,304.851,-863.093,89.5575,343.889,-861.389,131.89,304.246,-874.98,119.285,342.898,-840.457,111.465,262.266,-849.526,147.076,262.507,-878.639,82.9194,380.302,-873.476,53.1935,379.752,-898.006,78.9783,415.01,-892.68,52.0705,414.726,-891.089,109.496,379.224,-910.504,102.757,413.982,-876.052,24.5264,377.91,-886.819,1.12114,375.104,-894.817,25.9366,413.158,-904.714,4.48062,410.34,-917.115,156.525,333.451,-934.815,141.622,371.156,-910.372,128.719,376.188,-893.47,141.291,339.341,-954.346,130.323,407.61,-929.88,119.501,411.612,-878.577,156.703,300.679,-900.984,174.304,294.564,-865.098,174.438,259.807,-886.159,194.229,254.565,-927.409,185.527,286.312,-944.467,165.938,325.462,-956.652,191.209,276.336,-974.075,170.478,315.607,-911.693,207.126,247.177,-940.689,213.805,238.042,-962.743,149.244,364.095,-982.114,136.329,401.683,-992.483,152.617,354.971,-1011.4,138.63,393.544,-819.923,60.2707,-317.481,-862.914,57.7957,-321.165,-863.009,95.3563,-321.835,-818.912,98.5645,-319.286,-903.167,54.4483,-314.817,-903.955,91.4523,-313.754,-778.299,100.596,-307.165,-780.66,62.3883,-304.972,-747.806,100.969,-286.534,-751.539,62.8087,-284.781,-787.589,31.619,-298.746,-822.694,28.5746,-310.489,-796.012,8.02085,-286.948,-828.592,3.70715,-297.866,-761.901,32.9922,-280.549,-775.45,14.8905,-275.756,-861.486,27.9202,-314.812,-898.022,26.3127,-310.493,-864.223,5.06383,-303.275,-890.837,9.78815,-302.991,-958.927,44.7255,-270.867,-949.65,13.3418,-273.293,-971.049,8.2551,-242.449,-980.168,40.6007,-238.22,-991.916,5.1881,-206.766,-1000.36,37.6934,-202.25,-929.057,-9.46378,-271.675,-949.89,-15.3623,-242.166,-970.98,-18.1611,-206.724,-979.741,79.9652,-232.021,-959.404,83.3797,-265.684,-999.045,77.4676,-196.157,-935.115,87.3343,-293.983,-934.216,49.7166,-297.225,-926.359,20.4071,-296.315,-908.257,-0.132525,-292.508,-1044.65,36.0253,-129.426,-1036.03,4.32701,-130.765,-1059.05,4.70451,-92.2663,-1068.38,36.1427,-92.9318,-1082.58,4.40117,-54.6481,-1092.93,35.3786,-57.0177,-1014.42,-16.7533,-127.583,-1036.42,-15.121,-86.5309,-1058.35,-14.5872,-46.252,-1067.05,77.5212,-90.6735,-1043,76.2997,-126.399,-1092.12,76.7951,-54.7446,-1020.23,76.2637,-161.255,-1021.92,36.355,-165.925,-1013.61,4.18328,-169.236,-992.55,-18.1955,-168.089,-1142.98,28.2383,11.8073,-1166.61,23.9052,45.6459,-1165.79,57.1177,49.4129,-1142.99,63.3499,15.5132,-1155.75,91.6552,61.0799,-1134.4,100.813,27.1681,-1117.93,70.9898,-19.2773,-1118.1,32.4031,-22.259,-1110.49,112.849,-7.7548,-1106.49,2.50364,-18.8223,-1130.78,-0.772444,16.0593,-1080.05,-16.4407,-8.07117,-1102.27,-20.2718,28.8,-1155.41,-5.2103,50.8437,-1125.76,-25.6745,65.1449,-859.509,238.335,173.098,-839.969,215.202,171.539,-827.07,237.974,126.722,-847.425,262.519,134.952,-827.434,183.785,168.332,-814.818,203.885,117.576,-872.821,278.511,138.058,-884.534,253.655,171.794,-902.822,286.479,136.686,-914.178,261.516,167.62,-897.447,229.847,208.585,-872.377,215.572,213.523,-926.666,237.267,201.604,-852.437,193.98,216.33,-838.606,164.611,216.923,-829.746,99.7165,158.74,-848.189,57.1217,148.569,-844.242,67.4295,95.5609,-823.616,111.348,105.573,-837.111,70.6735,42.9828,-815.427,115.378,52.126,-881.746,20.3949,131.928,-877.768,30.1378,80.3933,-869.911,33.4107,29.4759,-813.738,159.255,111.719,-823.427,143.613,164.698,-802.81,166.003,57.9057,-831.864,127.005,215.214,-834.835,85.0574,210.376,-850.146,42.6662,201.581,-882.827,6.50157,184.256,-790.817,92.4023,-51.1664,-776.417,76.3926,-99.619,-765.146,119.553,-95.2025,-777.893,137.595,-46.4517,-761.871,63.4745,-145.219,-752.628,105.471,-141.293,-790.655,155.931,4.88368,-804.157,107.23,-0.353396,-825.598,63.9426,-8.73835,-811.077,51.4798,-58.8379,-857.192,27.8945,-21.0004,-841.151,17.395,-70.0558,-794.918,37.5251,-106.551,-778.49,26.3227,-151.11,-823.324,5.71804,-116.713,-805.248,-3.33039,-159.989,-739.11,57.5582,-225.414,-752.831,23.3876,-227.375,-751.362,28.6302,-256.866,-738.975,60.1925,-258.056,-776.349,-2.81034,-232.269,-772.302,5.38616,-258.865,-734.069,99.202,-258.452,-733.012,97.7356,-224.098,-740.557,99.0117,-184.652,-748.089,57.9244,-187.476,-763.167,22.1134,-191.754,-788.46,-5.94438,-198.91,-899.497,515.825,25.523,-907.049,471.095,26.847,-879.386,471.846,35.8464,-873.667,515.768,34.8708,-851.884,470.063,40.2236,-847.607,513.698,39.3863,-867.92,563.611,35.7983,-892.357,564.229,26.0842,-842.888,561.36,40.4287,-914.48,562.898,11.3384,-923.067,513.532,11.364,-932.57,559.3,-8.38605,-942.347,508.544,-7.58272,-932.522,467.467,13.2246,-953.452,460.62,-5.02194,-834.412,956.015,56.5542,-827.712,1012.49,72.0879,-853.932,1014.58,54.4902,-860.04,957.669,39.1309,-878.479,1015.36,29.9762,-883.54,958.119,15.3694,-820.652,1069.09,87.8459,-847.43,1071.57,70.1575,-872.957,1072.66,45.0548,-866.072,900.517,25.6686,-839.802,898.575,42.2664,-888.59,900.697,2.72408,-809.088,894.488,50.3402,-806.317,952.765,66.9056,-775.869,889.063,47.5206,-774.907,946.725,67.7613,-799.765,1009.26,82.4959,-792.338,1065.34,97.8176,-770.044,1005.11,85.4567,-762.193,1060.47,99.771,-962.379,490.423,-58.5931,-975.114,437.623,-54.1748,-967.49,450.215,-27.8928,-955.307,500.521,-31.2948,-944.911,553.122,-33.0362,-951.847,545.003,-61.4638,-953.725,535.583,-92.5208,-963.994,479.206,-88.2991,-950.89,525.502,-125.059,-960.587,467.832,-119.234,-976.812,424.22,-82.6565,-973.063,411.379,-112.124,-922.173,955.079,-48.9591,-921.072,1012.86,-37.224,-935.241,1010.97,-77.879,-934.169,952.683,-87.7718,-927.033,1012.68,-137.604,-926.549,952.697,-142.636,-919.085,1070.57,-24.5085,-934.546,1069.32,-67.9278,-921.826,1074.42,-135.171,-933.187,894.422,-95.3631,-923.081,896.965,-58.7096,-928.082,896.408,-144.253,-907.792,899.32,-25.8392,-904.653,957.099,-14.4989,-901.412,1014.64,-1.18156,-897.527,1072.19,12.8413,-941.122,448.223,-179.99,-951.949,391.851,-169.596,-964.35,400.474,-141.365,-952.59,457.258,-150.217,-943.688,515.402,-157.931,-933.1,506.297,-189.453,-920.109,499.202,-217.936,-927.308,441.462,-207.295,-905.697,495.131,-241.697,-912.268,437.717,-230.87,-937.134,385.857,-196.037,-921.179,382.838,-219.902,-881.333,947.021,-208.731,-875.842,942.827,-217.557,-874.361,903.258,-224.732,-881.196,905.726,-214.628,-871.061,940.286,-228.459,-868.523,900.886,-235.643,-898.144,904.843,-194.387,-893.874,951.731,-192.751,-898.745,1011.06,-185.42,-888.296,1003.3,-200.682,-897.758,1073.36,-176.828,-890.571,1066.31,-190.012,-883.012,995.628,-210.471,-878.043,990.134,-224.053,-886.903,1059.53,-199.952,-883.189,1053.52,-216.529,-881.15,440.215,-263.094,-897.124,437.723,-249.457,-890.845,495.098,-259.049,-874.9,497.723,-270.558,-857.207,501.621,-276.786,-863.616,443.926,-271.816,-837.113,505.41,-278.299,-843.796,447.59,-275.662,-870.808,389.258,-268.914,-888.844,385.651,-256.951,-850.423,392.849,-275.692,-905.359,383.14,-240.409,-844.678,947.45,-289.61,-808.237,962.647,-323.022,-816.371,900.773,-305.468,-844.316,895.247,-278.914,-766.767,970.31,-334.721,-777.14,909.51,-321.935,-859.956,898.05,-251.864,-863.784,939.07,-248.867,-869.506,992.604,-256.458,-842.077,1011.82,-307.786,-872.372,1058.32,-263.352,-838.776,1071.72,-313.957,-802.965,1022.14,-332.431,-761.272,1025.88,-340.955,-800.025,1077.18,-333.656,-756.428,1080.12,-343.632,-801.113,504.306,33.0833,-781.134,496.489,20.7294,-775.885,545.917,20.7556,-796.53,552.709,33.5935,-763.636,486.263,1.21561,-757.384,537.218,0.725225,-818.977,557.791,39.9223,-823.347,509.959,39.0461,-826.895,466.086,39.978,-804.535,459.678,34.2566,-784.922,450.604,22.204,-768.173,438.628,2.96523,-711.425,929.594,33.1219,-741.277,937.782,55.6602,-745.432,885.632,35.5429,-721.678,886.617,20.3097,-703.496,891.074,5.83494,-689.319,927.132,9.67748,-686.647,895.655,-7.4352,-671.68,930.187,-10.1598,-674.887,977.853,28.7967,-704.421,989.619,60.7573,-662.285,1040.21,51.2191,-696.338,1048.16,78.5199,-654.187,970.976,-2.88787,-634.862,1030.07,14.8523,-738.158,999.444,79.9354,-729.927,1054.63,93.4019,-730.308,439.951,-193.58,-736.647,384.157,-194.828,-745.411,388.692,-222.177,-740.009,443.878,-219.798,-759.396,393.337,-244.609,-754.636,447.945,-241.402,-730.816,502.258,-219.581,-720.419,498.88,-193.925,-746.187,505.788,-240.831,-715.025,497.24,-164.41,-725.368,438.084,-163.478,-714.663,498.922,-131.586,-725.023,440.198,-130.219,-732.586,382.002,-163.525,-732.711,384.503,-129.236,-587.909,930.131,-214.523,-560.51,984.342,-220.695,-559.845,975,-178.85,-585.041,924.667,-178.572,-570.071,970.68,-134.606,-588.604,925.549,-136.01,-539.431,1037.91,-224.324,-536.936,1028.06,-181.669,-546.939,1020.21,-136.03,-607.796,876.895,-179.834,-614.053,882.684,-210.304,-606.585,873.121,-140.261,-621.689,888.985,-237.722,-596.358,940.377,-248.571,-635.556,897.429,-266.715,-614.426,952.505,-280.706,-572.016,996.555,-258.33,-553.892,1048.92,-261.989,-595.459,1008.46,-290.206,-580.137,1059.88,-293.612,-797.14,450.86,-268.713,-820.961,449.941,-274.67,-813.964,507.707,-275.66,-789.76,508.525,-268.67,-766.501,507.882,-257.127,-774.355,450.235,-257.666,-779.119,395.816,-261.153,-802.35,396.387,-271.833,-826.861,395.312,-276.671,-682.549,968.979,-324.21,-644.538,963.027,-307.006,-660.961,905.503,-293.713,-695.537,911.442,-313.766,-734.513,913.71,-324.243,-724.291,971.22,-333.502,-715.491,1026.3,-339.879,-670.51,1023.78,-330.467,-707.79,1079.39,-343.107,-660.292,1075.76,-333.178,-629.812,1017.73,-313.974,-617.677,1069.21,-317.178,-737.192,459.986,-59.0031,-729.108,448.211,-94.5303,-719.362,505.515,-96.0002,-728.478,515.263,-60.3132,-741.367,526.415,-27.1844,-748.845,473.382,-26.2248,-754.406,423.512,-24.3117,-743.792,407.775,-57.1146,-736.503,393.933,-92.9272,-629.714,934.93,-54.5219,-613.4,970.602,-57.295,-635.332,969.643,-29.0058,-652.585,934.007,-29.1804,-591.255,1015.62,-54.4418,-614.258,1020.65,-19.4106,-666.41,896.776,-24.3149,-640.745,890.37,-51.5794,-616.294,878.144,-93.842,-604.852,931.038,-91.0474,-589.593,970.401,-92.2216,-567.36,1016.1,-92.885,-668.03,2104.71,294.181,-631.489,2096.59,303.238,-620.964,2173.3,327.604,-662.06,2179.68,315.803,-609.468,2244.74,342.132,-652.398,2252.24,330.5,-594.755,2086.82,304.28,-579.698,2164.48,329.898,-567.136,2235.13,344.144,-701.5,2184.01,295.741,-704.41,2110.09,277.435,-737.46,2185.23,264.464,-739.291,2111.03,248.699,-693.928,2256.97,308.789,-730.171,2258.21,276.014,-703.129,2043,254.25,-670.437,2041.37,269.345,-735.554,2039.97,227.866,-637.931,2034.84,276.708,-605.973,2025.01,275.812,-628.518,2400.59,351.605,-672.22,2407.05,328.566,-683.752,2330.89,320.207,-641.143,2325.27,342.364,-711.76,2410.95,293.552,-722.249,2333.56,286.298,-596.946,2317.24,354.271,-583.214,2391.91,363.926,-553.362,2307.17,356.305,-538.512,2381.14,366.088,-568.254,2468.91,370.165,-614.445,2478.73,357.317,-552.051,2548.38,372.027,-598.847,2560.09,358.6,-522.663,2456.88,372.565,-505.9,2534.21,374.797,-659.005,2486.37,333.334,-699.335,2491.65,297.047,-643.961,2569.46,333.656,-684.759,2576.33,296.078,-781,2102.5,175.639,-773.636,2031.99,175.881,-759.105,2035.58,197.723,-765.805,2107.33,210.011,-765.008,2182.35,220.963,-780.625,2177.63,176.574,-759.582,2256.61,229.985,-775.786,2250.79,179.415,-789.885,2173.62,141.417,-793.739,2099.43,149.174,-800.126,2168.02,112.508,-812.482,2092.84,119.012,-784.088,2243.06,138.519,-791.218,2235.89,106.48,-791.131,2028.29,156.091,-824.858,2021.37,119.135,-762.314,2407.89,185.385,-751.145,2491.31,183.403,-759.842,2487.06,131.724,-771.925,2402.92,136.623,-768.118,2481.84,95.3889,-780.859,2396.38,97.8819,-737.94,2577.84,179.882,-747.376,2572.44,126.428,-756.206,2567.02,89.7239,-779.663,2318.71,138.965,-770.282,2326.7,183.888,-788.019,2310.34,101.619,-752.44,2332.31,238.162,-743.119,2411.4,243.62,-731.456,2493.66,245.195,-717.487,2579.66,242.905,-836.321,2061.16,2.93435,-838.258,1991.65,6.17537,-845.052,2005.5,58.9986,-837.612,2077.19,63.4006,-818.908,2156.83,70.9287,-832.059,2137.07,-0.546575,-802.627,2229.47,70.0244,-822.213,2218.78,1.48623,-817.297,2124.06,-54.588,-820.877,2048.79,-48.2257,-794.787,2112.76,-100.878,-798.692,2037.53,-93.433,-812.4,2203.33,-62.231,-789.653,2191.96,-109.637,-822.839,1979.39,-43.4298,-801.017,1968.27,-87.5157,-807.835,2379.72,-13.2709,-801.307,2464.39,-28.6593,-788.859,2454.1,-90.4509,-798.753,2368.37,-79.7884,-766.007,2441.74,-140.729,-776.004,2356.34,-130.003,-789.584,2555.57,-42.2283,-775.752,2542.87,-101.118,-752.906,2529.44,-151.229,-806.414,2284.75,-70.5083,-814.048,2297.22,-2.65667,-783.64,2273.12,-119.493,-798.483,2303,60.1626,-794.216,2386.1,52.2142,-786.611,2471.85,48.7411,-776.176,2562.2,39.0222,-738.203,2019.84,-155.849,-742.262,1950.86,-148.612,-774.042,1958.9,-122.496,-770.998,2028.01,-129.226,-766.398,2103.11,-137.626,-732.615,2094.72,-164.999,-760.646,2182.1,-147.362,-725.962,2173.4,-175.556,-693.922,2087.24,-183.164,-700.709,2012.59,-173.546,-650.805,2080.29,-192.29,-658.924,2005.88,-182.563,-686.17,2165.54,-194.279,-641.839,2158.22,-203.587,-706.024,1943.76,-166.105,-665.676,1937.18,-175.217,-710.102,2335.65,-198.91,-699.416,2418.99,-210.781,-657.327,2408.48,-230.683,-668.578,2326.42,-218.495,-610.426,2398.38,-240.201,-622.285,2317.68,-227.996,-685.904,2503.65,-222.163,-643.501,2491.26,-242.312,-596.286,2479.18,-251.719,-677.957,2245.36,-206.208,-718.704,2253.75,-187.015,-632.544,2237.49,-215.639,-754.146,2262.92,-158.097,-746.148,2345.56,-169.37,-735.904,2430.04,-180.721,-722.664,2516.37,-191.689,-529.435,2063.38,272.026,-502.724,2054.88,239.832,-472.202,2132.76,256.52,-505.105,2141.35,298.861,-452.738,2202.27,265.809,-489.064,2212.18,314.771,-475.067,2056.74,194.093,-446.294,2137.8,187.792,-431.205,2202.16,182.697,-541.208,2153.41,321.649,-559.974,2075.44,294.406,-527.378,2224.04,336.598,-576.393,2014.82,265.925,-550.487,2006.11,247.542,-529.233,1996.82,218.69,-510.919,1984.29,182.701,-456.665,2354.79,338.938,-496.343,2368.44,358.428,-512.336,2295.41,348.706,-473.306,2282.7,328.197,-435.777,2270.68,278.359,-418.534,2341.15,291.611,-415.532,2266.94,185.185,-398.531,2333.86,191.468,-401.264,2412.15,302.207,-439.441,2427.44,346,-384.124,2482.1,307.59,-422.067,2499.94,348.957,-380.78,2397.92,202.323,-361.268,2462.78,215.676,-479.629,2442.64,364.941,-462.413,2517.56,367.403,-422.58,2000.72,-72.6873,-445.417,1993.22,-109.594,-430.971,2066.61,-110.26,-408.681,2072.69,-72.4336,-416.566,2141.33,-113.006,-394.831,2146.68,-73.1237,-479.489,1989.05,-139.901,-465.455,2062.94,-143.614,-451.418,2138.94,-149.295,-399.364,2080.21,-29.479,-413.623,2008.5,-30.9205,-404.593,2097.09,32.7391,-417.572,2017.01,17.8624,-384.938,2154.15,-24.2857,-399.681,2169.54,51.3878,-428.787,1941.92,-31.952,-436.818,1931.8,-76.3065,-438.001,1950.03,16.7834,-459.87,1924,-111.326,-492.824,1920.53,-137.5,-360.825,2292.7,-78.3574,-348.894,2298.29,-22.625,-368.078,2226.46,-22.6758,-378.575,2220.34,-74.8132,-369.124,2311.69,71.1649,-387.007,2237.33,62.7878,-401.51,2216.11,-117.747,-386.019,2290.95,-124.56,-437.601,2215.3,-157.074,-423.812,2292.13,-166.557,-370.799,2364.76,-133.687,-342.701,2363.51,-84.3897,-356.305,2437.06,-142.044,-325.795,2435.12,-91.3497,-409.887,2367.81,-175.914,-395.801,2440.94,-183.802,-328.332,2367.5,-21.9693,-350.361,2379.64,74.3131,-309.363,2437.42,-23.466,-329.037,2442.55,69.7998,-566.37,1993.67,-175.934,-576.308,1925.23,-169.748,-621.564,1930.72,-176.189,-613.251,1999.29,-183.143,-603.747,2073.5,-192.545,-555.463,2067.68,-184.509,-593.538,2151.12,-203.537,-544.002,2144.97,-194.658,-508.659,2063.64,-168.754,-520.964,1989.87,-161.592,-495.878,2140.52,-177.3,-532.524,1921.55,-156.623,-520.275,2301.75,-216.769,-507.272,2379.79,-227.803,-456.5,2372.66,-206.678,-470.075,2295.92,-196.881,-493.013,2456.64,-237.94,-442.282,2447.59,-215.881,-483.156,2218.24,-186.972,-532.396,2223.23,-205.565,-583.106,2229.88,-215.27,-571.931,2309.23,-227.281,-559.493,2388.59,-239.109,-545.16,2467.41,-249.907,-443.644,2059.39,111.759,-432.049,2036.17,72.9435,-423.026,2123.85,87.6491,-432.395,2141.09,114.949,-416.858,2188.53,98.062,-423.62,2201.4,119.321,-437.135,2145.45,139.187,-453.961,2065.98,146.343,-426.609,2205.37,138.767,-494.404,1972.54,142.111,-481.36,1962.65,107.791,-461.697,1953.93,68.393,-394.836,2331.55,130.906,-396.99,2332.82,146.877,-412.552,2268.56,142.348,-410.279,2266.46,125.45,-404.763,2254.68,107.128,-388.757,2324.83,114.406,-371.458,2386.26,117.611,-378.569,2389.78,134.591,-350.449,2445.07,116.517,-358.338,2447.78,136.822,-380.831,2391.93,151.641,-360.936,2451.17,157.57,-564.041,2730.17,346.966,-516.973,2711.98,361.622,-500.293,2789.99,353.179,-547.212,2812.49,337.753,-470.806,2690.38,365.632,-454.34,2763.61,358.063,-592.622,2830.38,311.259,-609.529,2744.52,320.818,-634.001,2843.69,273.894,-650.839,2754.83,282.717,-627.069,2656.52,328.923,-581.654,2644.96,354.549,-668.133,2664.91,290.584,-534.592,2630.42,368.544,-488.304,2612.98,371.848,-709.432,2758.35,170.767,-685.383,2759.97,231.586,-669.41,2852.05,225.709,-696.772,2853.53,167.154,-713.052,2847.75,106.232,-722.483,2753.19,114.157,-722.864,2844.33,57.1308,-732.517,2751.38,70.3859,-733.76,2661.92,121.12,-723.051,2667.29,175.017,-743.57,2657.83,81.7571,-701.565,2669.05,237.409,-752.051,2741.58,-60.2967,-772.053,2648.16,-51.4871,-763.244,2656.05,24.5633,-748.521,2750.99,9.54743,-732.978,2842.72,-3.77579,-731.727,2833.13,-69.9082,-717.472,2817.53,-128.552,-738.98,2726.57,-120.008,-692.734,2800.03,-177.187,-716.321,2710.1,-170.264,-758.669,2634.36,-110.807,-735.96,2619.54,-161.062,-648.803,2675.22,-242.238,-668.828,2589.5,-232.593,-705.682,2604.55,-201.814,-685.846,2692.9,-211.205,-658.67,2780.85,-215.199,-619.861,2761.86,-244.126,-578.864,2742.81,-265.267,-606.509,2657.24,-262.945,-535.193,2721.22,-273.089,-558.986,2639.47,-269.869,-626.37,2574.51,-252.894,-578.841,2559.63,-261.226,-388.147,2640.05,341.708,-351.236,2615.52,299.888,-335.914,2675.25,292.904,-372.018,2703.62,334.417,-326.78,2592.56,224.995,-312.891,2648.7,226.077,-411.63,2733.8,352.604,-427.731,2665.64,359.365,-444.886,2592.72,364.921,-404.875,2571.51,347.021,-367.398,2550.48,306.051,-342.527,2529.67,223.034,-300.907,2562.47,-101.993,-312.568,2501.59,-96.454,-343.168,2504.62,-147.889,-330.652,2568,-153.584,-381.915,2510.61,-190.38,-368.196,2576.91,-196.324,-317.929,2627.79,-160.319,-289.291,2619.49,-108.873,-354.266,2640.49,-202.664,-270.861,2614.18,-47.3675,-282.729,2559.01,-37.7771,-273.583,2609.52,35.9058,-291,2556.13,49.6801,-295.024,2501,-29.1387,-308.993,2501.37,60.9341,-460.05,2604.95,-251.253,-477.152,2531.57,-245.339,-528.184,2545.12,-257.845,-509.37,2622.06,-264.292,-488.817,2700.03,-266.722,-442.388,2678.14,-254.794,-396.552,2657.56,-234.605,-412.038,2589.41,-229.55,-427.4,2519.81,-223.446,-319.063,2566.37,139.407,-310.713,2559.14,110.007,-293.783,2609.78,105.754,-303.923,2618.03,140.856,-306.963,2629.83,171.85,-321.411,2575.9,167.676,-338.794,2514.7,163.389,-336.665,2508.33,137.955,-328.945,2503.86,113.154,-853.266,-25.5411,-254.334,-870.829,-31.3233,-223.287,-914.199,-28.5365,-234.833,-894.635,-22.3819,-264.727,-890.442,-31.9463,-186.892,-934.81,-30.4652,-199.136,-876.625,-11.4432,-287.06,-838.984,-14.0981,-279.17,-802.859,-8.673,-268.037,-811.832,-19.0677,-242.386,-826.631,-23.7738,-210.367,-844.91,-22.8273,-172.88,-931.175,-21.7454,-102.034,-950.388,-15.9625,-56.376,-997.836,-21.6574,-73.5829,-977.142,-25.1718,-117.049,-967.562,-13.0888,-10.4325,-1017.53,-20.0356,-30.4501,-955.962,-28.7258,-159.387,-910.875,-27.9132,-146.007,-864.32,-16.2628,-130.823,-883.514,-7.28427,-85.4119,-901.148,0.906382,-37.8638,-915.874,5.10765,10.6069,-993.745,-21.6717,77.6945,-981.744,-15.6449,34.3943,-926.347,2.11703,58.7834,-933.411,-5.89553,106.56,-937.908,-16.7622,153.83,-1004.37,-29.2097,119.056,-945.875,-27.0339,194.591,-1014.1,-35.7252,156.725,-1071.2,-32.927,88.6884,-1053.31,-26.8507,50.5594,-1083.73,-38.1821,125.925,-1035.72,-22.1605,10.8894,-1035.5,167.697,290.508,-1019.45,188.449,251.844,-1051.91,179.998,236.114,-1066.86,160.185,274.274,-1084.36,166.827,217.251,-1098.36,148.469,254.922,-1005.05,210.647,215.076,-1038.48,200.998,199.958,-1071.46,186.098,181.559,-1082.46,142.79,313.598,-1052.36,149.562,330.072,-1097.85,129.044,353.244,-1069.2,135.215,369.539,-1112.64,132.298,293.97,-1126.42,119.595,333.8,-1022.36,152.78,343.747,-1004.49,171.095,304.122,-1040.4,138.334,382.901,-987.514,192.184,265.05,-972.132,214.948,227.555,-982.73,256.207,150.231,-972.859,280.251,122.184,-1007.91,266.721,108.565,-1017.61,243.615,136.183,-1039.66,246.69,90.3735,-1050.21,224.785,118.012,-1027.42,221.956,166.647,-993.137,233.119,181.199,-1060.44,205.002,148.443,-959.057,238.29,192.663,-947.582,262.271,160.57,-937,286.948,131.476,-1102.16,170.344,70.6787,-1117.29,155.362,102.147,-1090.7,182.458,126.022,-1078.51,200.003,95.3029,-1131.67,141.683,135.622,-1103,166.04,159.233,-1065.62,220.492,67.3641,-1085.81,188.87,41.4222,-1100.27,152.566,14.4299,-1120.79,136.879,46.7613,-1139.28,124.748,79.5781,-1156,113.88,113.362,-1159.16,112.834,207.96,-1184.64,90.3194,185.518,-1195.98,80.1507,224.371,-1171.31,100.308,246.784,-1204.93,72.7404,265.394,-1181.74,90.8133,287.35,-1142.9,117.914,270.842,-1129.76,132.451,231.956,-1154.98,106.642,310.994,-1116.24,148.932,194.649,-1145.79,127.065,170.9,-1171.22,101.983,148.595,-1170.66,-33.8787,222.479,-1153,-37.4471,199.575,-1133.06,-35.5209,230.339,-1146.67,-32.1901,251.87,-1110.02,-32.6464,255.692,-1119.38,-29.3309,277.937,-1124.58,-39.6619,189.632,-1111.49,-37.459,220.48,-1100.36,-34.7798,242.09,-1156,-27.8791,281.349,-1181.47,-29.425,253.863,-1162.92,-22.7918,316.926,-1188.1,-24.2481,291.458,-1127.21,-25.2072,306.344,-1134.53,-20.3566,340.133,-1202.61,-27.7933,227.688,-1191.06,-32.2057,193.286,-1209.06,-22.8978,267.209,-1171.55,-36.3208,165.422,-1138.24,-39.3264,152.501,-1031.93,-38.7307,216.788,-983.968,-35.7745,243.31,-1001.4,-34.8665,260.74,-1039.45,-36.522,241.311,-1013.91,-32.1469,278.044,-1045.94,-33.2045,264.127,-946.531,-31.9805,273.489,-971.762,-32.5938,283.015,-990.739,-31.2687,288.937,-1078.22,-37.5888,226.014,-1081.52,-40.0589,197.132,-1077.66,-34.1583,251.023,-1085.26,-40.4074,162.921,-1023.38,-38.6819,188.948,-963.344,-33.2679,222.839,-917.521,-26.6972,260.82,-925.334,-24.0886,335.372,-897.299,-17.0724,336.479,-906.232,-12.8153,371.67,-932.266,-19.6895,367.457,-922.662,-8.39385,406.3,-946.885,-14.8524,401.157,-962.895,-21.9062,362.318,-956.685,-26.1085,333.029,-996.095,-21.8716,356.104,-989.903,-25.8175,328.671,-975.61,-17.0587,395.03,-1007.06,-17.1786,388.039,-958.249,-29.6727,306.852,-928.537,-28.1353,304.112,-988.346,-29.0539,305.48,-898.284,-21.4989,299.115,-1056.86,-26.4328,311.155,-1089.13,-27.4944,297.164,-1082.13,-30.9265,273.613,-1051.39,-29.9282,286.847,-1019.76,-29.2219,298.14,-1023.54,-25.9002,321.518,-1029.84,-21.9909,348.665,-1063.42,-22.457,338.737,-1039.46,-17.3745,380.303,-1072.02,-17.8412,370.573,-1096.11,-23.4647,325.052,-1103.98,-18.7709,357.6,-803.954,1182.63,114.979,-812.679,1126.06,102.174,-783.917,1121.92,111.745,-774.691,1178.15,124.178,-753.372,1116.59,112.899,-743.776,1172.54,124.717,-764.849,1233.21,135.009,-794.642,1237.98,126.168,-733.609,1227.45,135.113,-823.323,1241.69,109.037,-831.999,1185.82,97.5023,-851.227,1244.23,84.0691,-859.261,1187.6,72.128,-840.076,1128.88,84.5186,-866.528,1130.24,59.1143,-910.236,1186.88,0.775412,-915.507,1128.51,-11.8885,-892.452,1129.98,26.2986,-886.176,1187.83,39.2391,-878.69,1245.53,51.7113,-903.185,1245.78,13.7359,-918.851,1245.69,-36.2122,-925.643,1187.54,-49.1457,-901.83,1246.08,-105.127,-907.349,1191.19,-119.39,-931.515,1128.01,-58.3376,-914.601,1132.9,-129.693,-887.111,1172.52,-164.1,-889.149,1118.29,-178.568,-894.9,1127.16,-165.704,-892.182,1183.2,-150.279,-888.51,1238.42,-133.93,-883.995,1229.74,-147.618,-881.116,1224.01,-161.311,-884.066,1164.29,-176.521,-878.173,1225.71,-188.587,-881.396,1163.91,-198.005,-886.046,1111.35,-189.083,-883.167,1108.86,-207.037,-831.148,1186.13,-303.167,-835.508,1128.05,-310.77,-870.545,1117.97,-263.23,-866.266,1177.39,-260.078,-859.772,1240.56,-253.125,-825.22,1247.56,-291.154,-786.237,1251.38,-313.044,-791.917,1190.36,-324.555,-741.18,1253.05,-326.19,-746.841,1192.41,-337.192,-796.444,1132.73,-331.185,-751.812,1135.09,-342.711,-676.658,1158.73,99.2099,-686.688,1103.2,89.3325,-652.152,1095.17,63.2041,-642.334,1151.11,73.8686,-621.056,1086.57,29.2936,-609.422,1142.76,40.406,-632.602,1206.46,82.5213,-666.631,1213.67,108.316,-599.36,1199.55,49.1787,-700.584,1220.8,126.031,-710.774,1165.93,116.215,-720.626,1110.22,105.297,-510.707,1147,-221.215,-523.384,1091.39,-224.239,-538.578,1101.93,-261.89,-526.271,1157.69,-258.444,-566.093,1112.3,-293.756,-554.038,1168.41,-289.986,-517.551,1216.74,-251.221,-501.472,1205.47,-215.104,-545.817,1228.07,-281.654,-496.666,1195.78,-174.347,-506.261,1137.96,-179.551,-502.219,1189.21,-129.989,-512.17,1131.83,-134.632,-519.451,1082.25,-182.151,-526.617,1075.31,-136.833,-643.491,1184.89,-330.973,-651.509,1127.69,-334.245,-701.424,1133.06,-343.252,-695.655,1190.37,-339.002,-689.737,1250.99,-328.628,-636.782,1245.76,-321.353,-587.185,1237.93,-305.363,-594.92,1177.46,-314.429,-605.698,1120.82,-317.889,-550.934,1131.55,-41.395,-567.975,1073.41,-46.9083,-543.594,1072.09,-91.3216,-527.525,1129.99,-87.9737,-517.217,1187.28,-83.0724,-539.722,1189,-35.9769,-567.834,1193.26,8.85943,-579.189,1135.7,1.75643,-595.223,1078.69,-7.36922,-412.25,5521.36,201.826,-420.391,5519.93,179.881,-418.332,5506.12,183.041,-411.122,5507.39,204.913,-415.559,5492.62,185.249,-409.16,5493.66,207.216,-401.065,5508.27,227.228,-401.359,5522.33,224.098,-388.054,5508.34,249.417,-387.811,5522.5,245.99,-399.886,5494.34,229.749,-387.424,5494.19,252.386,-400.957,5536.87,220.78,-412.688,5535.91,198.31,-400.049,5552.24,217.698,-412.583,5551.39,194.718,-386.891,5537.02,242.645,-385.492,5552.25,239.92,-421.805,5534.39,176.084,-422.637,5549.83,171.969,-367.75,5332.27,204.918,-375.622,5328.56,176.756,-368.67,5310.45,183.161,-361.684,5315.04,210.045,-361.527,5293.12,189.527,-355.434,5298.28,215.232,-353.193,5317.38,235.477,-358.455,5333.79,231.572,-343.102,5317.51,258.842,-347.603,5333.28,256.261,-347.821,5301.27,239.321,-338.537,5301.85,261.184,-363.584,5350.47,228.259,-373.567,5349.92,200.467,-368.557,5367.4,226.187,-378.818,5367.73,197.901,-352.129,5349.3,253.97,-356.771,5365.7,252.503,-382.426,5347.56,170.598,-388.134,5366.82,167.363,-258.961,5511.45,338.166,-285.03,5513.04,329.612,-284.224,5498.18,335.784,-258.249,5496.7,344.007,-283.097,5483.02,342.093,-257.327,5481.59,350.185,-309.689,5515.09,317.926,-308.973,5500.2,324.14,-307.818,5485.07,330.303,-231.457,5495.9,348.904,-231.955,5510.53,343.449,-204.26,5495.89,350.575,-204.48,5510.46,345.328,-230.855,5480.86,354.857,-204.035,5480.89,356.39,-232.334,5525.18,339.282,-259.439,5526.22,333.592,-232.589,5540.28,337.195,-259.661,5541.39,331.218,-204.705,5525.06,341.369,-204.946,5540.11,339.419,-285.491,5527.92,324.648,-309.962,5530.04,312.802,-285.587,5543.17,321.964,-309.793,5545.32,309.91,-387.053,5402.91,200.713,-394.472,5403.78,174.564,-391.802,5385.71,169.726,-383.188,5385.49,198.43,-373.352,5384.56,226.006,-377.97,5401.72,227.082,-361.618,5382.6,252.39,-366.505,5399.72,253.144,-382.416,5418.62,228.779,-390.786,5419.71,203.409,-386.694,5435.01,230.462,-394.579,5435.8,205.919,-371.262,5416.75,254.275,-375.723,5433.41,255.295,-397.195,5420.58,178.754,-400.271,5436.3,182.137,-249.556,5376.99,361.708,-272.469,5379.77,352.924,-270.746,5361.88,349.773,-248.365,5358.78,358.033,-269.267,5344.83,345.982,-247.355,5341.48,353.651,-294.532,5383.43,340.045,-292.157,5365.85,337.519,-290.065,5349.02,334.499,-225.343,5356.84,362.702,-226.081,5375.3,366.74,-202.009,5356.35,364.185,-202.332,5374.92,368.364,-224.706,5339.34,357.942,-201.699,5338.75,359.287,-226.887,5394.17,369.484,-250.869,5395.59,364.184,-227.727,5412.92,370.362,-252.245,5414.09,364.964,-202.657,5393.92,371.219,-202.972,5412.77,372.141,-274.345,5398.03,355.032,-297.06,5401.34,341.744,-276.285,5416.19,355.691,-299.604,5419.14,342.286,-401.932,5681.14,190.07,-413.004,5689.35,164.689,-418.331,5659.65,159.15,-405.846,5654.76,186.859,-421.618,5631.42,156.446,-408.352,5629.55,185.365,-392.078,5651.25,212.296,-389.462,5675.46,213.193,-377.151,5649.38,235.779,-375.588,5672.99,234.404,-393.908,5627.75,212.017,-378.512,5626.28,236.657,-385.326,5699.05,214.637,-396,5707.3,194.371,-378.191,5720.59,218.398,-387.03,5732.21,200.149,-373.326,5695.55,232.456,-371.013,5712.36,230.394,-405.665,5718.35,170.821,-395.457,5745.97,177.506,-245.844,5310.29,344.475,-267.039,5313.81,337.845,-265.741,5298.23,333.804,-244.935,5294.7,340.122,-263.957,5281.52,329.983,-243.649,5278,336.221,-286.825,5318.12,328.02,-285.056,5302.58,324.664,-282.775,5285.98,321.299,-223.141,5292.4,343.801,-223.719,5307.97,348.229,-200.868,5291.74,345.023,-201.142,5307.3,349.432,-222.356,5275.77,340.003,-200.579,5275.15,341.323,-224.203,5323.33,353.03,-246.583,5325.6,349.058,-201.415,5322.69,354.278,-268.123,5329.07,341.956,-288.391,5333.35,331.316,-320.787,5327.22,298.085,-335.064,5330.9,278.523,-331.32,5315.44,279.524,-317.769,5311.83,297.443,-327.435,5299.8,280.215,-314.469,5295.94,296.513,-302.374,5307.33,312.516,-304.725,5322.77,314.675,-299.598,5291.06,310.175,-306.954,5338.02,316.732,-323.786,5342.57,298.671,-309.367,5353.69,318.77,-327.023,5358.35,299.434,-338.853,5346.5,277.596,-342.875,5362.52,277.133,-334.758,5392.24,301.867,-315.459,5387.75,322.725,-318.755,5405.37,324.034,-338.785,5409.62,302.892,-321.962,5422.86,324.491,-342.604,5426.82,303.361,-356.503,5413.59,279.309,-351.937,5396.39,278.373,-360.776,5430.51,279.945,-347.315,5379.26,277.519,-330.758,5375.03,300.607,-312.266,5370.4,320.869,-353.194,5519.69,286.04,-332.467,5517.43,303.242,-332.323,5532.31,298.405,-352.589,5534.43,281.731,-331.704,5547.57,295.535,-351.502,5549.65,278.983,-370.773,5536.11,263.053,-371.699,5521.5,266.797,-369.37,5551.3,260.396,-371.984,5507.14,270.908,-353.209,5505.1,290.995,-371.462,5492.71,274.666,-352.524,5490.39,295.682,-332.087,5502.65,308.975,-331.135,5487.68,314.53,-343.365,5678.27,271.821,-324.52,5683.24,287.431,-325.078,5714.21,276.895,-343.798,5705.91,263.426,-327.323,5743.73,262.989,-346.051,5731.94,251.676,-303.825,5688.26,300.332,-304.129,5722.29,288.255,-305.991,5754.7,272.822,-359.683,5698.77,248.666,-360.305,5674.39,254.045,-361.158,5720.78,239.92,-361.186,5649.4,257.621,-343.932,5650.73,277.374,-362.39,5625.4,259.538,-345.158,5624.95,280.244,-325.14,5652.77,294.588,-304.56,5654.94,308.813,-326.429,5624.76,298.357,-305.817,5624.67,313.459,-257.482,5695.31,316.608,-232.719,5697.31,320.44,-233.143,5736.64,305.738,-257.766,5733.55,302.44,-234.009,5773.87,288,-258.948,5769.82,285.126,-207.488,5698.26,321.707,-208.086,5737.97,306.823,-208.636,5775.52,288.958,-281.561,5728.75,296.691,-281.331,5692.29,309.982,-283.071,5763.42,280.134,-281.938,5656.66,319.598,-257.781,5657.96,327.112,-282.938,5624.49,325.131,-258.355,5624.35,333.429,-232.598,5658.87,331.524,-206.891,5659.45,333.005,-232.636,5624.35,338.405,-206.344,5624.6,340.114,-357.363,5775.18,216.265,-337.543,5793.25,224.561,-343.792,5814.66,198.95,-364.194,5794.59,191.993,-315.09,5808.47,231.939,-320.643,5831.27,205.156,-381.525,5771.54,184.704,-374.006,5754.83,207.787,-366.575,5738.69,226.243,-350.944,5754.66,236.057,-331.766,5769.84,245.726,-309.925,5783.19,254.272,-264.422,5828.68,241.755,-237.224,5833.85,244.236,-239.042,5858.31,216.325,-267.677,5852.9,213.944,-209.461,5835.85,245.128,-209.757,5860.37,217.229,-295.075,5843.95,210.188,-290.547,5820.25,237.663,-286.306,5793.66,260.718,-261.34,5801.26,265.19,-235.457,5805.99,267.814,-209.091,5807.85,268.715,-297.542,5208.36,251.479,-298.256,5218.57,267.224,-311.013,5230.02,258.057,-311.741,5221.88,242.612,-321.552,5242.27,250.772,-324.027,5236.26,234.846,-299.766,5230.89,279.408,-311.4,5239.93,269.37,-318.956,5246.44,261.102,-312.036,5214.22,223.773,-296.926,5199.92,232.777,-325.466,5229.39,215.322,-280.74,5187.19,241.822,-281.898,5196.12,260.581,-264.079,5176.71,250.333,-265.459,5186,269.387,-283.372,5207.47,276.547,-285.501,5221.26,289.188,-267.19,5197.99,285.622,-269.503,5212.6,298.521,-232.194,5174.31,283.885,-233.534,5187.15,301.069,-250.538,5191.34,294.05,-248.878,5178.86,277.364,-235.213,5202.63,314.475,-252.671,5206.5,307.216,-247.543,5169.19,257.745,-231.105,5164.3,263.619,-214.738,5161.73,267.52,-215.445,5171.99,288.324,-198.415,5161.16,269.008,-198.669,5171.53,290.057,-216.301,5185.06,305.916,-217.342,5200.68,319.497,-198.955,5184.67,307.831,-199.267,5200.33,321.484,-411.224,5586.54,188.521,-422.957,5585.36,163.536,-422.956,5566.59,167.853,-412.08,5568.15,191.406,-398.823,5568.79,215.273,-397.348,5586.74,213.513,-383.808,5568.52,238.352,-381.987,5586.12,237.55,-395.687,5606.32,212.426,-410.063,5606.91,186.214,-380.173,5605.28,237.116,-422.835,5606.81,158.822,-259.327,5575.49,333.9,-284.701,5577.06,324.687,-285.297,5559.09,322.63,-259.603,5557.32,331.973,-308.242,5579,312.386,-309.183,5561.25,310.393,-232.708,5556.22,337.978,-232.73,5574.54,339.717,-205.213,5556.06,340.199,-205.522,5574.44,341.83,-232.692,5596.74,340.497,-258.891,5597.34,335.038,-205.894,5596.78,342.428,-283.887,5598.4,326.174,-307.082,5599.74,314.028,-402.601,5465.67,208.493,-408.037,5465.35,185.93,-404.005,5451.15,184.555,-398.619,5451.06,207.644,-390.808,5450.64,231.497,-394.53,5465.66,231.788,-379.723,5449.4,255.715,-383.107,5464.8,255.436,-397.632,5480.18,231.236,-406.218,5479.81,208.38,-385.724,5479.7,254.359,-412.007,5479.1,186.186,-254.967,5449.19,360.358,-280.017,5450.8,351.68,-278.199,5433.79,354.497,-253.627,5431.96,363.556,-304.267,5453.17,338.976,-302.034,5436.42,341.338,-228.568,5431,368.803,-229.386,5448.38,365.341,-203.265,5430.94,370.522,-203.539,5448.38,366.959,-230.156,5465.01,360.513,-256.217,5465.75,355.768,-203.795,5465.04,362.05,-281.672,5467.22,347.468,-306.221,5469.39,335.272,-348.817,5459.59,301.583,-367.62,5462.61,279.063,-364.518,5446.83,279.894,-345.979,5443.47,302.949,-324.889,5439.82,323.793,-327.45,5456.28,321.916,-329.56,5472.23,318.836,-351.03,5475.22,299.186,-369.971,5477.88,277.352,-348.386,5583.08,279.662,-365.802,5584.83,259.671,-367.654,5567.4,259.545,-350.044,5565.64,278.71,-330.659,5563.51,295.705,-329.334,5581.07,297.306,-327.875,5601.18,298.726,-346.7,5602.63,280.59,-363.988,5604.02,259.946,-342.073,5266.31,224.342,-336.468,5270.19,245.006,-342.364,5285.45,242.455,-349.069,5282.04,219.868,-328.843,5271.04,263.303,-333.817,5286.18,262.754,-354.14,5276.47,195.543,-345.998,5260.38,201.57,-336.611,5244.73,208.028,-333.932,5251.05,229.046,-329.784,5255.73,247.101,-323.513,5256.94,262.583,-306.606,5262.05,292.296,-292.289,5255.16,303.801,-296.093,5273.35,307.569,-310.627,5279.07,295.062,-276.151,5248.5,313.723,-279.67,5267.59,317.977,-323.221,5283.7,280.206,-318.839,5267.83,278.932,-314.454,5252.89,275.824,-302.767,5245.69,287.422,-288.616,5237.48,297.974,-272.626,5229.81,307.563,-239.588,5239.83,329.149,-219.95,5237.82,333.664,-221.246,5257.22,337.099,-241.784,5259.34,332.992,-199.932,5237.37,335.386,-200.264,5256.69,338.607,-261.413,5262.86,326.535,-258.452,5243.39,322.338,-255.424,5224.23,316.272,-237.315,5220.56,323.405,-218.604,5218.62,328.271,-199.596,5218.25,330.174,-1143.09,4440.69,-12.8508,-1194.91,4403.13,7.17689,-1175.64,4388.62,26.2854,-1125.11,4429.03,7.34609,-1153.13,4374.7,37.4733,-1103.01,4417.63,19.924,-1080.24,4457.49,-2.051,-1099.25,4467.34,-16.8803,-1059.56,4447.7,7.98646,-1116.25,4475.89,-34.2315,-1156.79,4449.09,-34.7763,-1131.67,4483.72,-52.3767,-1168.98,4453.5,-54.4344,-1209.2,4416.5,-18.6712,-1219.7,4425.62,-46.5096,-1197.72,4463.16,-101.014,-1240.77,4430.27,-102.817,-1230.41,4429.67,-74.2737,-1183.03,4457.58,-74.4056,-1147.11,4493.39,-73.8535,-1162.36,4505.14,-103.503,-1172.83,4513.63,-139.286,-1207.74,4467.82,-132.463,-1174.9,4514.76,-177.85,-1210.59,4470.49,-166.09,-1247.4,4429.83,-129.381,-1250.05,4430.21,-156.403,-1201.07,4464.15,-240.212,-1244.74,4427.88,-229.257,-1248.97,4431.04,-189.398,-1207.39,4470.59,-203.129,-1169.48,4509.48,-216.657,-1160.39,4498.28,-251.004,-1148.15,4484.31,-280.88,-1191.82,4450.83,-272.927,-1132.83,4467.18,-306.398,-1178.54,4432.3,-300.143,-1235.76,4415.7,-264.577,-1222.08,4397.07,-291.678,-1137.19,4379.49,-326.132,-1176.77,4341.62,-310.02,-1203.43,4372.94,-309.454,-1161.54,4408.56,-320.443,-1115.98,4445.68,-327.221,-1094.2,4418.31,-337.054,-1067.4,4388.04,-327.129,-1107.74,4348.11,-305.698,-1044.02,4366.88,-308.299,-1087.09,4334.66,-286.155,-1146.61,4311.64,-283.619,-1129,4306.91,-271.088,-1049.48,4333.19,-268.826,-1096.44,4311.47,-260.826,-1113.36,4308.88,-265.13,-1068.58,4333.15,-275.581,-1024.37,4357.23,-292.389,-1002.71,4351.92,-280.11,-971.468,4344.95,-261.515,-1022.56,4326.43,-255.3,-941.748,4340.6,-230.281,-992.133,4313.26,-224.505,-1071.87,4306.79,-249.283,-1040.97,4288.06,-217.984,-972.877,4314.25,-157.549,-1021.01,4284.75,-151.605,-1026.25,4282.6,-184.082,-977.079,4310.7,-189.824,-927.75,4342.06,-197.561,-924.278,4345.36,-165.732,-926.339,4349.28,-133.242,-973.857,4319.68,-126.297,-930.113,4354.91,-100.555,-977.513,4325.85,-95.8904,-1021.86,4289.83,-121.153,-1025.8,4295.93,-92.0793,-989.481,4340.27,-39.1361,-1038.51,4308.92,-38.5227,-1031.06,4302.27,-64.451,-982.397,4332.42,-66.6801,-934.192,4363.49,-69.0278,-941.867,4375.21,-40.5771,-955.101,4388.88,-17.1428,-1000.13,4351.17,-14.1941,-972.997,4403.49,-0.128746,-1015.35,4366.74,5.71457,-1049.09,4316.08,-14.4895,-1063.71,4324.73,7.31361,-1056.04,4396.99,24.0986,-1105.15,4348.3,37.1012,-1082.83,4335.71,25.3873,-1034.54,4383.26,18.2005,-993.743,4417.27,9.93523,-1015.68,4428.84,13.9275,-1037.84,4438.46,13.1727,-1079.2,4407.57,24.9071,-1129.14,4361.33,41.1494,-1566.65,4124.86,3.24272,-1533.18,4144.16,-9.40472,-1537.62,4156.34,-16.5184,-1574.74,4139.78,-6.36497,-1545.26,4166.94,-24.898,-1585.86,4150.84,-16.776,-1623.78,4118.69,8.3517,-1611.68,4100.13,20.6887,-1635.67,4131.83,-3.88089,-1601.82,4078.37,28.3799,-1561.77,4106.14,11.7035,-1592.43,4055.13,32.5158,-1557.74,4084,18.9435,-1530.04,4129.32,-2.2798,-1526.18,4111.19,4.96733,-1622.76,4174.9,-52.0871,-1573.89,4187.18,-55.5006,-1591.16,4195.62,-82.3513,-1634.74,4181.51,-79.9318,-1599.27,4198.79,-115.087,-1635.14,4182.65,-110.09,-1666.49,4164.55,-72.0372,-1661.48,4158.72,-44.7439,-1665.9,4165.22,-101.843,-1650.32,4147.08,-20.506,-1601.44,4162.74,-29.8415,-1557.56,4177.05,-36.9041,-1613.69,4169.66,-172.65,-1570.17,4185.06,-179.375,-1531.76,4169.56,-200.683,-1591.03,4156.36,-201.618,-1522.58,4162.7,-210.037,-1562.7,4143.16,-218.563,-1631.28,4136.77,-193.433,-1646.46,4150.38,-164.681,-1614.05,4120.46,-217.5,-1658.61,4160.18,-133.436,-1627.11,4178.39,-141.641,-1592.2,4195.51,-147.441,-1541.03,4111.14,-241.275,-1511.63,4131.21,-239.786,-1497.87,4109.8,-248.508,-1529.85,4089.74,-247.905,-1486.61,4090.72,-247.131,-1519.28,4068,-246.658,-1568.42,4062.46,-247.564,-1581.93,4083.34,-244.306,-1556.39,4040.4,-243.525,-1597.04,4102.75,-234.285,-1550.13,4130.04,-229.454,-1519.89,4150.93,-222.636,-1501.55,4018.75,-219.235,-1469.07,4043.75,-223.803,-1461.83,4019.95,-200.529,-1494.36,3998.07,-195.951,-1457.85,4002.43,-174.253,-1489.77,3980.58,-169.732,-1527.42,3975.21,-190.258,-1535.44,3995.15,-213.262,-1521.94,3958.06,-164.225,-1545.44,4017.07,-231.642,-1510.15,4043.9,-237.065,-1477.65,4069.38,-239.226,-1489.17,3956.76,-114.081,-1458.87,3978.05,-118.505,-1463.38,3973.74,-89.2744,-1492.68,3952.9,-84.9195,-1471.44,3976.76,-61.0487,-1499.83,3957.5,-56.0122,-1522.08,3932.62,-79.7908,-1519.49,3935.76,-108.754,-1529.01,3938.82,-49.9738,-1519.44,3944.77,-136.741,-1488.22,3966.62,-142.121,-1457.08,3988.26,-146.587,-1520.57,3975.08,-20.1795,-1489.08,3993.58,-25.5297,-1493.62,4003.7,-13.1563,-1527.89,3982.15,-9.52631,-1497.28,4020.05,-0.130832,-1533.35,3993.12,1.27625,-1557.78,3966.16,-5.74782,-1550.64,3959.18,-14.44,-1563.1,3974.43,2.98485,-1540.56,3950.89,-27.0749,-1510.35,3967.31,-33.8072,-1481.03,3984.97,-39.5332,-1546.92,4035.41,22.7587,-1512.72,4067.05,14.7204,-1520.45,4090,11.3329,-1553.01,4059.82,23.49,-1583.07,4031.97,31.808,-1575.23,4008.98,25.2547,-1568.46,3988.24,14.1514,-1539.3,4011.15,13.8171,-1503.85,4042.37,10.7438,-249.822,2906.54,329.11,-275.647,2963.74,343.035,-314.965,2975.1,328.723,-281.192,2910.6,320.07,-257.25,2859.42,290.003,-235.349,2861.61,298.235,-199.493,2863.33,310.549,-203.621,2906.78,343.324,-160.678,2863.98,317.377,-161.504,2906.8,347.668,-212.313,2960.27,362.579,-162.449,2959.67,366.331,-1240.33,18.7877,323.606,-1237.51,4.58794,326.243,-1234.3,11.0221,366.348,-1236.77,23.8478,365.756,-1227.63,17.1751,401.204,-1229.62,29.689,402.918,-1229.17,-5.788,335.446,-1227.21,1.64201,372.493,-1223.82,8.69171,397.093,-1234.58,38.0389,365.808,-1237.55,35.0644,326.125,-1227.71,52.4852,371.102,-1229.1,51.918,334.766,-1228.1,42.7304,400.1,-1224.53,52.3527,395.22,-1237.15,32.8163,281.749,-1240.44,14.8575,278.153,-1227.45,51.5487,292.065,-1237.46,-0.621484,281.552,-1228.36,-11.9183,292.217,-943.151,54.6209,480.098,-947.734,77.3245,479.435,-972.65,76.7049,503.877,-969.912,57.0055,505.965,-996.317,77.1769,521.668,-995.706,59.3833,525.333,-958.421,96.9616,478.055,-980.536,93.5804,501.341,-996.173,89.3362,515.011,-969.74,37.3131,504.278,-944.253,32.2696,479.132,-975.278,21.096,502.048,-952.051,13.8497,477.263,-994.018,41.2547,522.714,-992.192,28.4392,516.486,-918.415,28.4976,447.829,-916.655,52.7611,448.929,-927.494,8.51775,445.544,-921.945,77.5975,448.934,-934.013,99.2996,447.931,-997.501,117.952,474.769,-1022.14,121.653,470.639,-1039.56,114.201,499.013,-1016.74,111.345,501.905,-1053.07,102.88,521.323,-1031.51,100.774,522.935,-1048.12,122.431,464.198,-1063.56,114.513,493.321,-1075.47,102.914,516.334,-996.149,104.8,501.682,-975.636,110.113,476.87,-1011.32,95.6705,520.209,-952.59,114.156,446.01,-975.972,123.355,442.742,-1002.46,128.081,437.697,-1030.34,129.518,430.447,-1099.59,118.589,443.451,-1124.87,113.42,428.983,-1135.34,106.012,461.078,-1111.7,110.686,474.412,-1143.03,95.186,486.659,-1120.79,99.3082,499.054,-1149.67,105.719,411.678,-1158.35,99.141,445.079,-1164.53,89.1961,471.692,-1087.69,113.432,485.14,-1073.97,121.501,455.163,-1098.15,101.804,508.928,-1057.91,128.855,420.561,-1085.18,125.828,407.912,-1112.18,120.176,392.372,-1138.9,111.637,373.81,-1196.11,82.416,370.376,-1214.9,67.8425,350.498,-1216.1,66.075,386.229,-1200.2,78.5882,406.283,-1216.54,61.1398,413.126,-1202.77,71.5318,434.406,-1180.46,89.8049,426.357,-1173.89,95.2132,391.456,-1184.93,81.0973,454.101,-1165.35,99.9505,352.1,-1189.98,85.6703,329.64,-1211.21,69.3516,308.828,-1041.55,62.4862,542.245,-1063.32,63.1251,542.312,-1059.63,40.7514,539.091,-1037.66,40.8387,538.953,-1049.77,20.7535,525.457,-1027.36,21.5164,526.067,-1084.85,63.0732,538.504,-1081.77,40.7588,535.469,-1073.13,20.7998,521.558,-1016.22,41.718,533.883,-1019.3,61.2239,537.006,-1007.04,24.3504,522.498,-1018.35,80.1768,532.217,-1039.64,83.4766,536.644,-1061.06,84.8447,536.073,-1082.66,84.7794,531.761,-1127.66,60.54,523.08,-1148.44,57.7635,511.326,-1146.4,36.7292,508.51,-1125.35,38.8545,520.321,-1141.37,17.8529,494.322,-1119.12,19.4358,506.281,-1168.47,53.7828,496.785,-1166.74,33.6799,493.683,-1162.87,15.5566,479.094,-1103.74,40.1619,529.259,-1106.39,62.2614,532.116,-1096.31,20.3891,515.264,-1104.45,83.778,524.963,-1126.13,81.6427,515.634,-1147.36,78.1763,503.726,-1167.82,73.1822,489.191,-1204.58,42.3233,458.479,-1218.76,35.9586,433.408,-1217.29,21.5383,430.148,-1203.42,25.2876,454.53,-1215.55,8.32674,416.161,-1201.52,9.67672,438.984,-1186.16,29.6011,475.699,-1187.5,48.4502,479.388,-1183.44,12.4583,460.304,-1187.19,66.464,471.983,-1204.29,58.5904,451.788,-1217.97,50.13,428.292,-988.804,-2.42809,472.318,-1013.83,-4.21316,467.874,-993.556,-11.5573,431.473,-966.741,-9.48836,437.259,-1041.03,-4.29599,462.008,-1022.93,-11.6968,424.735,-944.162,-3.47133,441.985,-967.555,2.93826,475.22,-989.667,11.8186,502.501,-1010.04,7.50086,502.888,-1033.54,6.16178,500.46,-1058.66,6.15905,495.677,-1096.52,-5.12367,445.546,-1123.8,-6.13109,433.305,-1113.59,-13.3435,395.126,-1083.64,-12.4227,407.641,-1150.08,-7.70937,417.293,-1142.35,-14.8585,378.523,-1053.19,-11.9298,417.151,-1068.76,-4.55533,454.84,-1083.89,5.85068,489.001,-1108.93,5.13591,479.891,-1133.44,3.91706,467.812,-1157.12,2.09518,452.223,-1197,-11.4562,374.222,-1215.38,-10.5959,352.623,-1213.27,-17.3276,310.424,-1193.22,-18.5131,332.996,-1169.24,-17.1358,356.755,-1174.85,-9.98852,396.689,-1179.63,-0.430609,432.586,-1199.57,-2.31498,410.883,-1215.53,-2.21474,389.095,-2170.85,3759.71,186.924,-2165.45,3754.71,207.749,-2147.41,3775.59,198.303,-2203.56,3715.4,203.209,-2200.02,3710.65,225.271,-2182.78,3732.87,216.657,-2187.31,3737.66,195.156,-2194.9,3703.33,246.289,-2188.1,3693.91,266.099,-2168.8,3715.29,255.781,-2176.69,3725.22,236.964,-2203.17,3703.31,119.694,-2191.64,3722.41,112.731,-2186.72,3712.5,94.4814,-2197.36,3694.51,101.101,-2206.79,3715.12,159.799,-2192.82,3736.39,152.406,-2193.32,3730.62,132.236,-2206.1,3710.32,139.401,-2178.74,3757.65,144.799,-2163.42,3777.86,137.26,-2166.77,3770.2,117.602,-2180.63,3750.86,124.766,-2176.78,3658.99,61.7852,-2166.85,3671.4,58.596,-2156.1,3655.82,51.9807,-2166.15,3645.14,54.014,-2192.68,3683.86,85.7263,-2182.51,3699.94,80.1998,-2175.73,3686.33,68.1441,-2185.65,3671.98,72.5037,-2172.43,3716.23,74.3757,-2162.13,3732.37,68.547,-2156.15,3714.94,58.1879,-2165.8,3700.59,63.3255,-2140.1,3616.5,47.8976,-2130.32,3627.71,48.6578,-2121.94,3619.46,52.8877,-2131.8,3607.94,51.3312,-2157.75,3635.42,49.3879,-2147.83,3646.15,48.3644,-2139.17,3636.45,47.2848,-2148.95,3625.56,47.4372,-2138.2,3656.7,46.8293,-2128.89,3666.98,43.7016,-2120.03,3657.31,44.373,-2129.5,3647.12,46.796,-2086.83,3610.96,83.046,-2094.22,3616.06,71.402,-2082.6,3629.25,70.4771,-2108.97,3578.92,84.2849,-2115.44,3587.12,70.8239,-2104.83,3601.62,71.0739,-2097.84,3594.96,83.6036,-2122.8,3596.84,59.5006,-2112.62,3609.94,60.6308,-2069.89,3599.99,340.174,-2067.53,3624.98,313.966,-2088.45,3632.31,328.156,-2039.1,3580.92,302.665,-2039.64,3602.73,271.883,-2051.6,3613.82,295.282,-2052.15,3590.44,323.163,-2039.63,3622.75,240.742,-2036.71,3642.39,210.337,-2045.91,3658.64,238.608,-2050.47,3636.08,266.215,-2347.78,3555.21,318.134,-2351.79,3555.71,292.525,-2366.4,3537.29,299.997,-2316.43,3588.72,302.424,-2320.75,3590.88,277.252,-2336.82,3573.77,284.992,-2332.48,3572.3,310.382,-2323.13,3590.94,251.431,-2323.63,3589.16,225.426,-2339.56,3570.85,232.674,-2339.16,3573.18,258.891,-2336.87,3521.29,120.428,-2330.4,3515.95,103.712,-2344.37,3495.89,110.292,-2307.8,3563.4,108.245,-2300.08,3555.43,91.1401,-2315.61,3535.84,97.0428,-2322.87,3542.58,113.962,-2290.71,3546.7,76.7849,-2279.02,3536.63,65.9076,-2295.93,3519.81,71.8713,-2306.86,3528.47,82.8068,-2280.69,3468.7,72.3288,-2263.55,3484.71,66.2995,-2252.51,3475.93,72.9114,-2270.07,3460.62,78.5634,-2302.86,3491.44,72.3825,-2286.15,3507.97,65.9495,-2275.04,3495.87,64.4647,-2292.02,3479.57,70.8255,-2269.05,3524.58,60.2906,-2251.47,3541.11,56.0121,-2240.35,3528.9,54.842,-2257.77,3512.29,59.0557,-2275.51,3444.97,128.394,-2250.34,3457.07,119.966,-2252.47,3456.83,138.968,-2280.11,3445.76,148.283,-2270.41,3452.11,91.9629,-2250.35,3466.46,85.532,-2249.97,3460.8,101.297,-2272.7,3447.63,108.477,-2230.48,3481.46,79.3211,-2211.18,3497.55,73.9586,-2206.11,3490.24,87.9225,-2227.64,3474.83,94.2645,-2291.34,3466.14,216.349,-2258.4,3476.09,206.705,-2260.48,3481.34,229.503,-2294.64,3471.11,239.437,-2283.54,3452.02,170.969,-2253.61,3461.83,161.624,-2255.65,3468.68,184.063,-2287.45,3459.43,193.493,-2224.58,3473.26,152.243,-2194.34,3491.13,164.7,-2224.97,3479.85,174.413,-2456.79,3211.04,373.016,-2453.14,3209.19,386.696,-2462.74,3214.87,392.316,-2465.69,3216.25,380.162,-2433.89,3214.21,359.286,-2429.21,3212.09,372.554,-2441.42,3209.23,379.888,-2445.59,3211.13,365.901,-2425.6,3212.74,386.094,-2424.77,3217.62,397.992,-2434.54,3215.71,404.675,-2437.14,3210,393.479,-2107.1,3647.8,324.036,-2108.49,3622.63,345.419,-2155.1,3618.36,352.221,-2152.28,3594.99,364.584,-2130.34,3609.4,357.571,-2130.67,3633.95,340.253,-2148.4,3570.82,373.544,-2144.7,3545.38,378.57,-2127.6,3557.52,381.089,-2129.02,3584.13,370.718,-2127.79,3332.7,422.548,-2120.11,3331.58,432.252,-2129,3343.46,436.157,-2134.69,3343.95,428.324,-2106.2,3319.58,407.831,-2096.5,3317.96,417.543,-2108.14,3323.33,425.518,-2117.37,3324.67,415.521,-2087.53,3319.38,426.726,-2080.54,3324.91,433.086,-2090.96,3329.83,439.788,-2099.38,3324.74,434.494,-2081.58,3505.24,266.004,-2061.36,3524.68,270.82,-2058.27,3510.02,299.922,-2079.12,3524.19,209.535,-2061.34,3549.43,210.058,-2061.44,3537.17,240.105,-2080.39,3515.14,238.209,-2046.43,3578.3,211.409,-2038.11,3609.39,213.523,-2036.53,3590.53,246.951,-2044.7,3562.6,243.047,-2208.66,3626.22,66.4361,-2217.72,3636.88,79.0829,-2196.44,3658.13,75.8176,-2187.3,3646.9,64.0976,-2252.42,3585.37,69.4154,-2262.09,3595.57,83.3356,-2240.01,3616.29,81.1195,-2230.57,3605.88,67.8313,-2269.79,3604.91,99.9796,-2275.41,3612.53,118.532,-2252.79,3633.79,115.044,-2247.37,3625.9,97.1019,-2169.48,3582.83,47.5942,-2179.29,3593.01,47.7519,-2158.74,3614.64,47.2846,-2149.92,3605.29,47.0265,-2208.98,3538.85,52.3365,-2220.13,3550.08,51.2572,-2199.78,3571.38,49.1095,-2189.19,3560.59,49.554,-2230.86,3562.02,53.1706,-2240.64,3573.62,58.9861,-2219.32,3593.94,57.7931,-2209.9,3582.85,51.6764,-2139.01,3541.04,88.3185,-2144.79,3550.04,73.0928,-2126.32,3572.86,70.8891,-2120.65,3563.41,85.5125,-2179.64,3501.58,97.9051,-2184.68,3508.5,81.6565,-2164.47,3528.53,76.7448,-2158.91,3520.52,92.5597,-2190.61,3517.35,67.9397,-2198.24,3528.82,57.7737,-2179.07,3550.71,53.9812,-2170.9,3538.5,63.5704,-2411.43,3219.65,277.656,-2400.23,3224.5,276.353,-2404.08,3231.16,287.149,-2407.98,3216.27,251.172,-2398.43,3221.4,246.856,-2399.08,3221.58,261.852,-2409.49,3216.61,264.501,-2388.87,3229.58,242.624,-2383.07,3240.36,239.847,-2381.48,3240.01,253.931,-2388.09,3229.47,257.993,-2354.36,3547.33,213.91,-2351.99,3541.31,188.315,-2365.71,3519.68,195.327,-2324.27,3586.8,199.962,-2322.89,3582.85,174.885,-2338.23,3562.64,181.364,-2339.92,3567.46,206.818,-2319.21,3577.18,150.573,-2313.39,3569.96,127.047,-2328.15,3548.25,132.387,-2334.19,3556.06,156.543,-2242.46,3671.77,173.689,-2264.44,3649.93,180.163,-2263.76,3651.43,203.12,-2241.79,3673.39,195.586,-2236.65,3662.5,131.328,-2258.99,3641.09,135.816,-2262.95,3646.39,157.597,-2240.76,3668.09,152.126,-2281.31,3619.49,140.306,-2302.79,3597.24,145.165,-2306.28,3602.14,168.743,-2284.97,3624.56,163.075,-2278.49,3624.19,284.758,-2282.71,3628.02,260.625,-2304.1,3607.4,269.411,-2299.91,3604.58,294.303,-2234.96,3662.45,265.316,-2239.13,3668.35,242.802,-2261.08,3648.37,251.776,-2256.87,3643.54,275.122,-2241.52,3671.97,219.425,-2263.39,3650.98,227.636,-2304.12,3550.36,400.441,-2317.62,3553.65,384.464,-2333.42,3540.07,392.388,-2271.13,3573.11,383.58,-2285.24,3579.57,367.683,-2301.61,3566.84,376.314,-2287.73,3561.86,392.12,-2298.65,3583.6,348.254,-2310.29,3584.44,326.991,-2326.27,3568.86,335.284,-2314.89,3569.43,356.644,-2474.2,3279.75,494.439,-2468.74,3280.3,504.442,-2470.8,3292.29,503.934,-2460.53,3264.43,486.547,-2453.97,3263.72,497.749,-2462.84,3270.28,502.199,-2468.94,3270.33,491.045,-2447.58,3266.61,507.532,-2441.53,3273.94,513.438,-2449.99,3280.86,515.939,-2456.39,3273.41,511.072,-2325.11,3285.38,144.111,-2333.99,3278.14,147.969,-2334.43,3279.44,136.926,-2330.74,3291.68,167.92,-2338.46,3286.44,171.202,-2335.92,3281.13,159.921,-2327.13,3287.61,156.42,-2346.73,3283.01,173.344,-2354.7,3282.68,173.053,-2353.85,3277.17,164.661,-2344.92,3277.13,163.213,-2389.39,3512.29,287.749,-2387.95,3514.24,312.997,-2427.22,3463,317.597,-2425.69,3464.23,337.902,-2408.5,3490.56,325.862,-2410.11,3489.19,302.374,-2421.02,3463.21,357.451,-2413.44,3458.21,370.947,-2395.85,3487.43,365.996,-2404.26,3491.43,348.795,-2354.62,3465.37,260.293,-2360.87,3480.41,259.66,-2353.87,3490.9,249.402,-2392.32,3433.37,286.802,-2400.96,3443.5,288.315,-2377.84,3465.09,273.024,-2372.21,3451.32,272.72,-2414.2,3452.19,292.233,-2423.02,3460.12,299.896,-2404.84,3484.67,281.73,-2393.82,3475.63,275.611,-2335.85,3448.65,330.109,-2340.04,3450.37,306.666,-2321.88,3461.28,297.594,-2369.42,3417.71,345.456,-2373.24,3419.41,325.06,-2356.76,3436.47,315.392,-2352.32,3433.86,338.08,-2377.74,3420.33,304.728,-2384.26,3426.05,289.054,-2366.72,3440.97,276.014,-2361.22,3435.9,293.226,-2363.43,3497.62,358.75,-2347.52,3481.5,354.147,-2339.27,3493.53,352.333,-2401.84,3450.6,373.87,-2387.13,3440.92,370.732,-2363.72,3464.32,360.811,-2381.16,3478.02,366.892,-2377.29,3429.45,368.398,-2371.59,3419.61,362.541,-2353.71,3435.86,356.468,-2356.88,3448.88,360.63,-2337,3444.86,229.547,-2334.08,3438.98,207.241,-2318.49,3448.89,203.385,-2323.79,3455.35,226.303,-2361.93,3417.55,235.436,-2363.25,3413.52,216.382,-2348.55,3427.18,211.719,-2349.21,3431.7,233.236,-2364.93,3409.77,198,-2369.47,3410.96,182.689,-2349.97,3420.73,175.383,-2348.06,3420.92,191.923,-2374.61,3491.72,251.936,-2358.64,3477.4,251.611,-2405.6,3443.18,257.029,-2388.93,3437.29,257.416,-2370.59,3460.41,255.518,-2389.06,3469.73,255.84,-2375.82,3430.41,256.195,-2365.23,3424.92,250.497,-2354.08,3439.95,249.626,-2361.42,3449.54,254.778,-2381.44,3487.5,178.909,-2386.06,3494.45,202.763,-2419.23,3437.39,199.38,-2422.03,3441.55,217.148,-2405.42,3468.74,210.004,-2401.76,3463.26,188.765,-2422.14,3445,234.917,-2417.73,3446.09,249.877,-2402.47,3475.4,249.274,-2406.28,3473.49,231.055,-2337.82,3441.03,162.388,-2345.75,3455.89,159.425,-2335.15,3462.85,149.672,-2378.4,3414.97,179.536,-2388.74,3422.11,179.531,-2366.29,3441.33,170.485,-2357.48,3428.65,171.292,-2402.11,3427.36,179.887,-2412.66,3432.64,184.452,-2393.22,3456.32,170.719,-2381.44,3449.22,168.553,-2364.11,3519.94,388.991,-2353.08,3520.57,409.01,-2398.98,3481.28,424.067,-2390.01,3478.1,441.968,-2372.47,3500.72,425.456,-2382.51,3501.11,406.156,-2380.94,3472.18,456.15,-2372.57,3465.69,464.421,-2348.38,3491.54,452.588,-2360.95,3498.23,441.645,-2339.79,3467.55,361.527,-2347.05,3486.27,360.395,-2375.55,3442.9,393.255,-2383.5,3456.38,393.776,-2362.5,3474,373.642,-2356.58,3457.01,375.432,-2394.72,3467.68,398.435,-2400.75,3477.71,406.82,-2385.14,3496.37,388.037,-2376.95,3486.24,379.15,-2298.17,3459.57,407.57,-2307.75,3455.41,394.725,-2285.53,3465.01,382.833,-2342.7,3432.23,430.683,-2349.2,3430.58,419.335,-2329.11,3443.97,406.507,-2321.18,3446.59,418.452,-2357.34,3429.78,406.813,-2366.43,3433.06,397.173,-2349.29,3444.11,381.559,-2339.03,3441.29,392.269,-2315.95,3501.63,438.36,-2305.94,3488.72,434.531,-2282.96,3506.63,423.558,-2363.09,3460.61,465.559,-2351.84,3452.98,460.667,-2328.24,3470.82,446.82,-2337.7,3482.54,452.544,-2343.1,3443.88,452.273,-2339.23,3436.96,441.126,-2316.25,3452.8,428.899,-2320.59,3460.78,439.619,-2311.51,3439.8,83.916,-2321.95,3448.47,81.9564,-2308.84,3463.33,77.2336,-2297.89,3452.9,78.5829,-2338.68,3411.69,92.9889,-2348.29,3417.13,90.6794,-2335.08,3433.29,86.3044,-2325.11,3426.17,88.6303,-2358.12,3424,90.7563,-2366.44,3430.89,94.7646,-2354.92,3449.97,90.8751,-2345.87,3441.88,86.3856,-2312.16,3426.32,137.326,-2307.83,3426.58,118.448,-2295.37,3434.44,115.769,-2300.99,3433.67,136.671,-2332.47,3407.8,138.128,-2330.7,3406.6,122.996,-2319.4,3417.51,121.176,-2322.53,3417.47,138.356,-2329.15,3406.19,109.683,-2330.67,3408.38,98.7827,-2316.45,3421.67,95.0062,-2316.65,3417.82,106.941,-2352.55,3469.26,148.367,-2337.79,3454.82,151.357,-2365.66,3435.18,148.763,-2354.47,3427.17,151.566,-2344.74,3443.41,153.063,-2358.53,3454.08,150.026,-2345.46,3418.61,152.489,-2337.89,3411.12,149.313,-2329.03,3421.01,150.817,-2336.27,3431.32,154.478,-2350.42,3473.32,98.6127,-2356.42,3477.12,112.582,-2372.83,3434.77,104.098,-2376.3,3437.75,116.393,-2367.5,3458.08,114.497,-2362.64,3454.7,101.074,-2377.41,3440.81,129.936,-2373.79,3441.96,141.96,-2367.82,3462.81,142.573,-2370.24,3461.55,129.422,-2469.79,3349.74,355.65,-2460.46,3381.03,347.345,-2457.57,3380.25,332.738,-2466.8,3348.79,340.53,-2463.2,3348.76,387.647,-2454.26,3380.35,380.456,-2458.97,3380.86,364.544,-2468.03,3349.56,372.255,-2442.03,3408.81,371.687,-2432.39,3433.8,364.291,-2437.32,3435.2,348.23,-2447.23,3409.22,356.131,-2437.61,3334.78,326.986,-2430.52,3362.68,318.694,-2417.93,3359.07,322.184,-2425.95,3331.74,330.12,-2460.03,3344.18,332.983,-2452.27,3375.29,325.811,-2443.26,3369.04,321.593,-2449.52,3339.11,328.915,-2441.47,3403.57,319.401,-2432.24,3426.67,311.305,-2424.96,3420.99,305.195,-2433.79,3397.21,314.241,-2394.31,3376.23,358.199,-2398.53,3377.32,341.664,-2386.69,3399.06,333.964,-2383.08,3399.47,353.615,-2408.15,3324.24,369.108,-2412.69,3326.09,354.622,-2406.88,3353.68,348.33,-2402.22,3351.41,363.451,-2417.81,3328.2,340.41,-2411.01,3355.33,333.252,-2426.42,3402.04,385.135,-2415.36,3394.37,385.015,-2408.83,3419.05,378.227,-2418.1,3424.92,377.655,-2442.08,3341.24,401.979,-2428.23,3334.64,398.306,-2421.43,3363.6,392.689,-2435.34,3372.08,395.398,-2416.22,3328.76,392.146,-2408.69,3322.99,382.593,-2402.71,3350.18,377.027,-2409.89,3356.6,387,-2384.55,3383.57,240.35,-2387.64,3381.28,225.066,-2375.76,3397.57,221.32,-2373.12,3402.01,238.793,-2399.75,3342.33,247.435,-2401.41,3342.58,233.816,-2396.19,3363.84,228.804,-2393.21,3363.85,243.029,-2403.64,3343.34,219.577,-2409.2,3345.45,209.314,-2404.18,3364.8,204.242,-2398.84,3363.34,214.303,-2427.47,3386.94,261.509,-2414.14,3387.2,261.837,-2407.3,3413.29,259.476,-2420.92,3414.8,257.696,-2436.82,3337.94,270.11,-2424.76,3339.2,269.056,-2419.82,3362.73,264.876,-2432.24,3361.96,265.17,-2412.48,3340.8,265.847,-2402.35,3342.45,258.408,-2395.12,3365.01,254.039,-2406.65,3363.81,262.111,-2448.83,3334.7,229.968,-2441.94,3360.61,222.999,-2436.06,3360.92,211.213,-2442.56,3335.26,217.618,-2450.61,3335.37,257.22,-2444.61,3360.7,251.752,-2444.91,3360.6,237.722,-2451.29,3334.87,244.286,-2440.09,3386.18,245.213,-2434.15,3414.7,239.112,-2433.96,3412.3,224.162,-2439.77,3385.2,230.519,-2417.64,3342.29,207.148,-2413.59,3363.17,202.064,-2435.29,3337.27,211.531,-2429.67,3361.73,205.882,-2422.4,3362.51,203.356,-2426.6,3339.67,208.427,-2424.48,3380.78,200.413,-2420.19,3401.76,193.446,-2411.71,3399.54,190.162,-2417.16,3380.09,197.803,-2436.47,3382.44,491.653,-2444.72,3384.58,480.836,-2456.22,3358.91,488.143,-2448.33,3358.03,498.637,-2408.88,3426.39,473.765,-2416.48,3429.53,464.19,-2430.18,3408.38,472.077,-2422.25,3405.91,482.308,-2424.96,3431.41,449.495,-2427.92,3428.93,436.136,-2438.76,3409.99,443.48,-2437.52,3410.37,457.111,-2463.47,3285.23,465.869,-2459.19,3304.04,458.441,-2445.77,3300.02,456.566,-2451.97,3282.08,464.324,-2476.11,3299.34,475.056,-2473.19,3316.19,469.406,-2468.7,3310.61,464.586,-2471.87,3292.09,470.582,-2469.37,3332.98,463.82,-2456.49,3350.35,450.321,-2463.9,3329.35,458.269,-2428.36,3277.82,495.079,-2420.66,3296.18,488.31,-2419.54,3300.35,499.933,-2427.15,3282.74,505.446,-2442.38,3276.82,472.313,-2435.71,3295.8,464.424,-2427.76,3294.05,476.371,-2434.62,3275.35,483.614,-2429.33,3317.74,457.695,-2414.95,3338.93,462.096,-2421.27,3316.4,469.183,-2401.76,3367.07,488.921,-2411.13,3372.67,496.423,-2422.11,3350.41,504.058,-2412.04,3346.15,496.987,-2379.76,3407.8,473.404,-2390.54,3415.43,478.201,-2400.26,3395.23,486.877,-2390.73,3388.42,480.826,-2397.76,3420.1,479.58,-2403.2,3423.2,478.439,-2415.26,3403.14,487.76,-2408.4,3400.01,488.984,-2351.07,3384.5,100.807,-2359.69,3387.47,99.4499,-2359.54,3401.03,95.751,-2350.87,3396.77,96.6014,-2348.97,3358.79,107.641,-2359.09,3361.1,106.442,-2359.53,3374.69,102.888,-2350.26,3372.18,104.157,-2368.67,3364.11,107.327,-2375.95,3367.34,111.345,-2375.49,3381.02,107.363,-2368.53,3377.76,103.675,-2339.77,3360.9,145.822,-2340.97,3373.62,142.074,-2347.1,3375.23,151.539,-2346.14,3361.9,155.518,-2336.7,3358,121.963,-2338.41,3370.71,118.569,-2338.72,3371.51,130.123,-2337.19,3358.98,133.669,-2339.88,3382.15,115.099,-2338.84,3392.73,111.384,-2338.81,3393.99,125.09,-2340.31,3383.15,127.305,-2372.02,3399.8,149.411,-2363.72,3396.17,151.566,-2363.68,3410.72,150.114,-2371.86,3415.68,146.834,-2373.29,3370.12,157.254,-2363.93,3367.7,158.759,-2363.74,3382.34,154.561,-2372.87,3385.37,152.816,-2354.44,3365.24,158.747,-2354.93,3379.17,154.78,-2380.69,3368.14,119.822,-2379.86,3382.19,115.509,-2383.61,3370.48,142.077,-2382.43,3385.77,137.645,-2382.01,3383.73,125.86,-2382.98,3369.16,130.403,-2382.18,3400.93,133.78,-2381.39,3418.6,130.128,-2381.16,3416.16,118.747,-2382.05,3398.64,122.246,-2474.47,3236.34,376.629,-2477.75,3261.59,372.361,-2476.31,3263.17,356.59,-2473.55,3239.19,361.875,-2466,3235.42,405.719,-2469.04,3260.69,403.707,-2474.77,3260.47,388.747,-2471.41,3234.89,391.759,-2469.79,3288.65,399.144,-2468.09,3317.64,393.139,-2473.01,3318.5,378.678,-2475.28,3288.94,384.221,-2441.31,3238.34,340.892,-2442.97,3259.93,337.213,-2430.11,3261.95,338.533,-2429.41,3242.74,341.063,-2465.64,3236.74,351.892,-2468.58,3260.82,347.155,-2456.47,3258.99,341.439,-2453.78,3236.11,345.289,-2468.1,3287.41,343.414,-2465.71,3314.59,340.465,-2455.66,3310.13,335.801,-2456.42,3284.35,338.089,-2420.6,3238.04,350.145,-2414.83,3235.18,363.175,-2419.8,3219.53,366.421,-2424,3221.3,355.442,-2421.22,3278.85,346.634,-2414.73,3275.83,360.728,-2414.32,3254.08,361.719,-2420.41,3257.63,348.008,-2409.99,3275.51,375.835,-2411.04,3275.51,390.245,-2410.46,3255.48,391.049,-2409.57,3254.57,376.771,-2419.61,3234.48,402.327,-2431.67,3232.75,410.768,-2419.57,3277.76,401.284,-2432.94,3280.02,408.782,-2431.9,3254.09,411.369,-2418.88,3254.6,403.042,-2447.47,3284.39,413.141,-2460.57,3287.87,410.116,-2459.72,3261.02,414.52,-2446.55,3257.74,416.614,-2385.05,3259.45,234.219,-2382.51,3258.29,249.978,-2399.34,3300.68,227.462,-2396.59,3299.08,242.218,-2389.07,3277.94,246.151,-2391.7,3279.87,230.666,-2395.73,3299.92,257.068,-2399.75,3299.56,269.772,-2393.5,3280.04,275.013,-2388.8,3279.83,261.783,-2396.87,3254.77,286.892,-2408.18,3247.7,290.657,-2409.26,3294.98,278.273,-2422.11,3289.09,282.617,-2414.45,3266.54,288.145,-2402.57,3273.99,283.84,-2436.29,3284.96,284.263,-2447.75,3280.63,280.201,-2441.42,3258.88,285.154,-2429.5,3262.46,289.465,-2431.15,3228.57,248.834,-2441.19,3249.93,243.443,-2434.34,3251.91,229.807,-2425.38,3231.42,235.826,-2435.62,3233.4,276.628,-2445.14,3253.78,273.846,-2443.85,3250.31,259.355,-2434.08,3229.45,263.283,-2452.63,3278.07,268.733,-2455.45,3304.32,262.366,-2455.32,3304.81,249.905,-2452.41,3276.29,254.443,-2399.58,3250.76,221.216,-2407.45,3271.7,216.915,-2398.93,3281.29,219.001,-2392.74,3261.34,222.24,-2417.33,3235.28,228.61,-2427.01,3256.58,223.485,-2417.78,3262.69,219.818,-2408.21,3241.78,224.092,-2434.39,3282.36,219.558,-2439.2,3310.08,217.677,-2430.44,3313.97,213.65,-2424.55,3287.61,216.051,-2349.82,3284.91,130.081,-2338.11,3289.97,126.559,-2363.59,3323.68,115.83,-2351,3324.38,114.198,-2343.65,3305.11,119.781,-2356.93,3301.86,122.258,-2340.46,3327.1,114.625,-2332.62,3330.48,118.903,-2327.72,3317.9,122.265,-2334.48,3311.08,119.073,-2325.69,3310.33,161.317,-2328.93,3322.85,158.115,-2336.26,3324.19,168.757,-2333.45,3312.52,171.708,-2320.37,3306.72,136.589,-2324.17,3319.22,132.675,-2325.2,3320.04,145.353,-2321.63,3307.72,148.942,-2328.97,3332.05,128.995,-2333.95,3345.1,125.217,-2334.47,3346.4,137.414,-2329.84,3333.17,141.438,-2339.46,3310.03,177.159,-2346.99,3307.1,180.344,-2343.18,3294.1,178.989,-2337.35,3297.24,176.659,-2348.31,3336.96,169.579,-2358.04,3336.4,171.508,-2352.2,3320.84,177.297,-2343.55,3323.02,174.408,-2368.33,3336.34,171.312,-2376.73,3335.67,167.189,-2370.71,3318.37,174.596,-2362.34,3319.79,177.825,-2364.6,3284.06,148.09,-2373.09,3302.04,139.98,-2367.79,3301.25,128.68,-2360.4,3283.77,136.828,-2366.27,3294.63,171.104,-2374.69,3311.38,165.383,-2374.52,3305.33,153.108,-2365.99,3288.01,160.192,-2381.29,3331.3,157.445,-2383.98,3352.84,147.819,-2383.78,3351.66,136.487,-2381.64,3327.44,145.228,-2073.98,3726.02,252.986,-2086.58,3745.13,245.806,-2105.05,3726.27,262.355,-2036.67,3766.08,216.131,-2049.7,3783.27,213.002,-2068.1,3764.01,229.289,-2055.28,3745.87,234.49,-2063.04,3797.89,206.933,-2076.46,3809.2,196.953,-2094.55,3792.2,209.709,-2081.29,3779.72,221.503,-2122.38,3785.54,206.153,-2130.74,3792.82,187.641,-2086.81,3818.86,182.347,-2095.78,3824.84,165.023,-2113.36,3809.01,176.402,-2104.68,3802.39,194.43,-2103.05,3828.16,146.648,-2107.83,3828.02,127.298,-2124.72,3813.52,137.335,-2120.28,3813.02,157.411,-2147.76,3793.38,126.883,-2150.94,3784.9,106.788,-2133.71,3798.51,96.3718,-2130.86,3807.77,116.614,-2116.15,3799.84,66.3451,-2112.71,3785.01,48.7077,-2130.59,3773.06,59.3857,-2133.78,3787.09,76.8976,-2131.05,3707.27,39.3213,-2114.56,3718.12,27.7505,-2103.88,3698.67,24.0621,-2120.11,3687.96,35.6146,-2145.37,3744.38,57.0828,-2128,3756.04,46.0094,-2122.45,3737.62,35.6338,-2139.35,3726.29,46.8545,-2094.68,3796.87,38.1401,-2110.55,3767.44,34.7531,-2094.4,3658.39,41.1957,-2077.86,3667.14,32.4237,-2068.25,3659.47,40.7205,-2085.26,3650.82,48.3373,-2112.28,3677.42,34.8697,-2096.15,3687.29,24.0352,-2087.28,3677.03,26.8508,-2103.48,3667.61,36.8433,-2075.98,3645.42,56.0139,-2067.58,3641.99,65.5175,-2051.3,3652.93,59.0309,-2059.41,3655.06,49.0616,-2037.35,3652.95,83.5871,-2043.77,3652.04,70.934,-2043.25,3639.64,108.53,-2035.37,3642.07,128.583,-2046.86,3622.54,136.403,-2022.14,3660.75,120.324,-2028.6,3655.81,101.805,-2017.22,3675.18,158.083,-2018.23,3667.17,139.28,-2029.03,3665.36,195.45,-2036.38,3680.08,219.951,-2022.94,3701.58,203.059,-2017.76,3687.62,181.516,-2045.17,3725.68,234.944,-2031.53,3713.8,221.208,-2149.74,3490.92,363.35,-2149.2,3465.47,363.827,-2154.6,3479.42,378.1,-2157.28,3506.2,375.347,-2119.92,3468.43,322.547,-2120.68,3442.2,334.171,-2135.23,3450.6,349.677,-2132.63,3477.2,344.152,-2113.8,3418.84,345.542,-2111.92,3400.7,349.75,-2126.79,3406.17,358.598,-2129.18,3425.55,354.868,-2048.08,3496.13,323.246,-2065.09,3482,315.986,-2064.19,3427.37,361.763,-2075.53,3419.7,348.164,-2069.79,3451.83,330.71,-2054.33,3462.57,340.373,-2089.36,3415.86,342.423,-2100.7,3415.48,341.371,-2104.74,3438.89,324.65,-2087.54,3442.96,323.926,-2077.87,3537.05,383.303,-2058.9,3528.86,368.268,-2055.49,3565.57,348.828,-2074.33,3573.34,363.742,-2090.75,3460.73,412.495,-2073.13,3450.48,398.011,-2065.12,3490.57,384.87,-2083.64,3499.13,399.278,-2065.74,3436.46,380.291,-2063.23,3432.51,371.403,-2047.67,3473.69,351.701,-2052.71,3481.22,368.196,-2146.43,3516.07,383.658,-2131.24,3525.64,390.44,-2138.1,3451.34,394.272,-2131.27,3450.58,401.009,-2134.22,3493.1,397.905,-2147.76,3486.47,389.298,-2124.49,3453.84,408.229,-2113.19,3463.08,415.296,-2106.04,3505.11,405.615,-2120.33,3499.97,404.366,-2116.39,3374.98,358.009,-2131.56,3382.44,369.197,-2117.04,3337.57,383.495,-2129.62,3343.56,392.516,-2131.36,3360.89,381.237,-2117.35,3353.65,370.594,-2139.79,3352.85,401.822,-2144.96,3364.26,411.845,-2147.93,3384.21,403.876,-2142.73,3371.68,391.805,-2063.47,3371.24,384.616,-2074.03,3366.9,371.871,-2075.01,3392.25,360.043,-2064.74,3396.51,373.206,-2069.55,3331.68,406.912,-2079.56,3329.1,396.155,-2076.39,3345.56,384.576,-2065.59,3349.69,396.32,-2090.64,3330.44,385.575,-2102.51,3335.77,378.302,-2101.17,3350.6,365.456,-2088.09,3346.29,372.81,-2098.69,3399.54,434.459,-2081.59,3390.59,425.244,-2080.15,3414.85,413.768,-2098.64,3422.84,424.91,-2100.89,3355.72,446.897,-2085.93,3346.71,440.195,-2083.41,3366.56,434.211,-2099.72,3376.44,442.293,-2072.44,3340.99,430.368,-2064.69,3337.67,417.406,-2061.25,3355.96,408.793,-2068.77,3361.01,422.995,-2146.07,3405.82,409.244,-2137.95,3405.92,421.542,-2129.37,3434.64,407.499,-2137.5,3434.1,400.004,-2142.11,3362.79,423.756,-2135.36,3362.2,434.477,-2137.49,3382.74,429.121,-2144.99,3383.39,417.226,-2126.73,3363.44,443.094,-2116.29,3364.89,447.42,-2117.02,3384.85,443.436,-2128.21,3384.25,438.579,-2139.1,3724.03,262.949,-2128.05,3709,278.014,-2181.5,3683.41,286.599,-2174.12,3670.92,305.351,-2151.01,3690.8,292.597,-2160.22,3704.13,275.252,-2166.28,3656.43,322.147,-2158.08,3640.03,336.954,-2131.17,3657.17,320.796,-2141.14,3674.74,307.538,-2207.12,3624,345.625,-2222.92,3611,355.727,-2212.13,3597.08,369.41,-2198.59,3609.19,360.26,-2222.68,3646.42,308.328,-2242.69,3630.4,318.979,-2233.08,3621.68,338.589,-2215.15,3636.12,328.05,-2262.49,3613.97,329.36,-2282.21,3597.46,339.711,-2268.81,3592.19,358.994,-2250.94,3606.92,348.774,-2189.63,3536.34,391.726,-2201.35,3552.98,391.42,-2211.1,3544.3,398.141,-2198.27,3530.11,397.406,-2174.23,3549.6,379.721,-2183.08,3570.58,377.004,-2191.82,3561.5,383.955,-2181.16,3542.7,385.605,-2190.63,3591.03,370.559,-2201.61,3580.53,378.868,-2168.59,3491.25,326.794,-2171.32,3498.61,351.169,-2183.37,3498.11,356.236,-2184.31,3492.17,331.923,-2136.26,3487.87,316.806,-2145.14,3498.51,342.611,-2158.93,3498.11,346.488,-2152.52,3489.09,321.859,-2156.33,3511.81,362.496,-2167.34,3528.38,376.768,-2173.66,3524.44,380.904,-2165.06,3509.55,366.034,-2254.41,3501.97,412.824,-2265.18,3515.98,416.576,-2216.86,3520.74,403.096,-2228.96,3534.96,404.726,-2247.09,3525.39,410.434,-2235.93,3511.44,407.705,-2242.11,3549.94,402.684,-2256,3562.55,395.39,-2272.78,3552.66,403.734,-2259.62,3540.24,409.779,-2271.31,3470.73,356.146,-2260.67,3474.72,375.653,-2213.82,3486.78,339.619,-2209.58,3491.29,362.255,-2235.37,3483.34,368.592,-2242.73,3479.13,347.634,-2207,3497.46,381.259,-2207.31,3508.08,395.976,-2227.19,3499.44,399.48,-2229.92,3489.19,386.029,-2292.55,3472.84,263.373,-2289.43,3471.7,287.837,-2224.87,3491.44,243.714,-2222.57,3488.21,268.192,-2255.87,3479.29,277.825,-2258.55,3481.47,253.554,-2220.72,3486.47,292.234,-2219.01,3486.07,316.296,-2251.1,3477.77,326.25,-2253.39,3478.19,302.024,-2166.7,3490.88,276.384,-2145.67,3489.04,270.49,-2146.61,3486.14,295.601,-2166.64,3489.25,300.962,-2167.25,3499.21,226.976,-2143.62,3499.11,220.25,-2144.61,3494.76,245.372,-2166.82,3494.88,251.84,-2121.77,3500.09,215.136,-2101.74,3505.43,211.168,-2104.12,3499.81,238.422,-2123.69,3495.13,241.368,-2129.79,3524.68,125.367,-2150.84,3510.06,130.626,-2155,3514.7,109.989,-2134.73,3533.75,105.142,-2122.7,3510.6,167.432,-2145.03,3504.88,173.41,-2147.63,3507.05,151.836,-2125.79,3517.17,146.173,-2169.05,3500.76,180.115,-2170.75,3498.51,158.12,-2052.75,3595.1,157.947,-2060.12,3597.8,136.845,-2082.75,3541.48,159.623,-2088.48,3550.53,138.882,-2073.58,3573.23,137.546,-2066.89,3567.14,158.566,-2095.54,3560.65,118.637,-2103.69,3571.74,99.0908,-2092.01,3589.17,97.3389,-2082.12,3580.76,117.098,-2433.95,3287.25,512.659,-2443.29,3293.77,515.749,-2419.49,3324.66,503.498,-2429.94,3329.29,509.113,-2436.71,3310.27,512.992,-2426.68,3304.66,508.761,-2439.99,3333.46,510.651,-2448.54,3336.28,508.434,-2454.3,3319.21,511.226,-2446.25,3315.53,513.671,-2387.51,3398.8,429.666,-2399.53,3380.2,437.885,-2408.18,3384.23,431.107,-2395.81,3403.31,422.159,-2375.44,3398.06,451.091,-2387.31,3379.34,458.507,-2392.67,3378.77,447.958,-2380.87,3397.44,440.133,-2398.83,3360.05,466.304,-2404.64,3359.44,455.562,-2425.75,3424.04,429.751,-2436.55,3406.71,437.828,-2408.9,3410.54,419.741,-2421.32,3391.65,429.659,-2431.37,3400.8,433.687,-2419.73,3418.18,424.498,-2432.95,3368.66,437.161,-2443.93,3375.49,440.561,-2464.47,3306.18,508.642,-2469.84,3305.9,501.002,-2455.98,3337.53,502.542,-2463.04,3337.35,493.122,-2467.13,3320.59,497.172,-2461.06,3320.68,505.677,-2469.06,3336.46,480.288,-2471,3335.48,469.81,-2474.24,3319.58,475.111,-2472.27,3319.96,485.746,-2087.8,3709.54,12.1264,-2070.73,3686.2,16.4675,-2173.68,3762.03,165.09,-2189.27,3740.07,172.781,-2204.7,3717.85,180.426,-2220.85,3696.28,188.096,-2220.48,3693.81,211.394,-2217.82,3689.07,234.12,-2213.52,3681.98,255.84,-2207.67,3672.93,276.601,-2215.89,3685,126.078,-2220,3690.86,146.243,-2221.72,3694.6,167.018,-2116.29,3555.15,101.561,-2293.93,3599.52,318.501,-2297.09,3590.57,122.528,-2291.32,3583.25,103.546,-2283.33,3574.27,86.4049,-2273.54,3564.42,72.0562,-2261.45,3553.02,61.3277,-2458.73,3217.18,360.945,-2466.22,3219.83,368.973,-2449.18,3217.78,354.342,-2439.4,3220.38,349.13,-2430.24,3225.35,346.896,-2185.05,3621.37,351.193,-2179.64,3601.86,362.814,-2174.23,3580.01,370.932,-2167.68,3556.86,375.131,-2161.45,3532.36,374.453,-2132.67,3336.68,412.387,-2138.08,3345.7,419.448,-2124.25,3329.91,405.432,-2114.58,3325.44,398.966,-2103.41,3324.3,393.99,-2094.16,3319.64,401.208,-2085.37,3318.48,409.618,-2077.89,3319.26,417.304,-2072.88,3322.89,424.117,-2099.91,3511.62,184.153,-2078.39,3533.71,181.067,-2061.83,3562.05,180.19,-2047.61,3593.64,179.689,-2422.02,3219.43,276.52,-2419.78,3216.46,266.512,-2417.08,3215.44,255.902,-2413.55,3217.44,245.789,-2406.83,3220.46,239.188,-2400.16,3226.06,234.162,-2393.75,3233.39,230.021,-2389.11,3242.3,228.177,-2272.59,3618.17,308.138,-2251.08,3636.57,297.666,-2229.22,3654.51,286.966,-2476.92,3283.92,483.471,-2472.5,3276.12,479.879,-2465.65,3270.55,476.66,-2457.75,3268.48,475.114,-2450.85,3264.12,482.784,-2444.28,3263.67,492.213,-2438.51,3266.2,501.68,-2435.12,3271.3,508.696,-2319.87,3294.95,140.768,-2321.59,3295.89,151.763,-2325.56,3298.04,163.108,-2332.61,3300.73,172.773,-2425.8,3429.95,373.969,-2430.33,3415.16,251.16,-2416.33,3456.55,426.38,-2412.88,3456.82,441.115,-2403.85,3452.26,455.73,-2396.4,3446.98,466.201,-2390.87,3442.38,471.529,-2383.3,3420.28,410.184,-2395.47,3427.77,407.546,-2406.17,3437.4,411.223,-2413.52,3447.3,417.683,-2359.77,3419.04,454.794,-2361.67,3414.96,443.276,-2367.32,3414.04,431.316,-2374.54,3415.3,419.38,-2385.2,3439.01,472.678,-2377.84,3434.45,471.146,-2366.07,3426.8,466.526,-2343.03,3393.78,100.95,-2377.89,3419.81,140.503,-2459.66,3315.59,403.587,-2454.48,3346.36,398.956,-2446.39,3377.98,391.806,-2434.85,3406.63,381.317,-2412.35,3295.63,389.196,-2410.71,3296.5,374.928,-2414.95,3297.55,359.675,-2421.77,3299.97,345.004,-2447.89,3311.44,406.885,-2434.77,3306.16,404.212,-2421.37,3301.03,398.53,-2405.16,3319.53,265.416,-2400.86,3320.24,252.93,-2401.88,3320.31,237.818,-2405.2,3321.54,222.89,-2436.26,3386.14,256.466,-2440.79,3360.51,261.689,-2446.58,3335.3,267.269,-2451.43,3303.62,272.605,-2441.94,3308.61,276.171,-2429.89,3312.69,275.496,-2416.11,3316.69,272.721,-2397.03,3362.76,477.274,-2385.71,3382.57,469.286,-2373.91,3401.44,461.898,-2343.48,3382.41,105.673,-2342.11,3370.41,109.277,-2340.38,3357.25,112.621,-2337.87,3343.5,115.485,-2347.1,3344.17,110.923,-2358.1,3345.55,110.273,-2368.36,3347.86,111.629,-2378.47,3402.45,144.271,-2379.36,3387.26,148.099,-2380.35,3371.56,152.484,-2380.53,3354.04,157.489,-2373.36,3353.55,162.506,-2363.9,3352.36,164.22,-2353.4,3351.08,163.983,-2063.67,3822.94,183.339,-2096.08,3838.64,119.638,-2079.92,3697.24,12.7501,-2149.4,3511.74,361.373,-2133.48,3495.23,338.601,-2120.11,3484.92,311.812,-2202.84,3662.69,297.827,-2197.23,3650.58,317.559,-2191.32,3637.06,335.598,-2186.42,3490.92,306.375,-2190.81,3498.93,233.769,-2188.95,3494.26,258.256,-2187.6,3491.6,282.299,-2167.47,3502.33,202.038,-2142.6,3502.94,195.047,-2119.99,3505.13,188.926,-2110.76,3542.27,121.443,-2106.21,3530.69,141.983,-2102.54,3520.28,162.799,-2071.89,3720.33,-0.143319,1807.45,3812.41,242.883,1788.11,3832.67,232.535,1778.82,3821.59,249.169,1798.83,3801.68,260.578,1830.25,3807.94,121.184,1826.92,3796.77,103.344,1815.22,3814.16,96.999,1817.2,3826.18,114.285,1809.61,3749.3,70.291,1799.39,3731.62,64.8398,1789.8,3741.97,61.0151,1799.91,3761.42,65.578,1774.67,3702.7,64.2048,1766.31,3694.53,69.1952,1755.61,3704.23,68.5561,1764.52,3712.31,62.644,1756.96,3686.28,76.6096,1745.68,3697.39,75.8995,1716.73,3711.45,302.049,1736.5,3721.31,317.868,1731.45,3747.89,293.973,1710.97,3735.75,276.76,2009.15,3626.69,283.318,2023.91,3607.99,290.825,2024.25,3604.69,264.476,2009.89,3624,256.979,1980.26,3581.12,106.268,1995.75,3563.12,112.802,1986.84,3556.86,101.692,1970.5,3573.47,95.2404,1904.6,3569.43,77.1542,1893.73,3559.65,83.6885,1876.07,3575.35,79.1909,1886.82,3585.52,72.8363,1884.62,3537.96,127.882,1884.25,3536.57,146.194,1857.58,3549.26,137.282,1860.3,3551.75,120.001,1883.75,3555.01,212.973,1884.03,3560.43,235.465,1849.51,3568.47,225.144,1850.93,3564.03,202.864,2113.83,3285.43,417.53,2123.55,3290.95,421.38,2117.9,3293.73,429.444,2109.09,3290.86,427.432,1763.06,3660.45,379.039,1745.07,3669.37,367.103,1750.98,3641.44,388.194,1765.88,3632.84,394.512,1772.72,3396.89,455.725,1783.15,3408.41,457.786,1775.33,3409.65,461.517,1763.61,3400.57,460.111,1699.2,3608.02,289.343,1697.09,3590.91,320.731,1688.4,3611.37,327.698,1688.99,3631.8,294.693,1878.31,3714.46,110.324,1856.58,3735.47,106.166,1861.46,3744.54,122.826,1883.09,3722.91,127.579,1844.02,3670.39,66.4317,1822.29,3690.63,65.4522,1830.57,3700.74,70.9433,1852.54,3681.08,72.5974,1808.06,3626.07,75.6773,1788.86,3648.62,73.8945,1797.09,3661.47,65.2544,1816.27,3638.94,66.5919,2054.84,3305.18,290.474,2060.07,3310.35,302.574,2052.83,3316.7,298.107,2047.64,3314.33,285.539,2004.34,3606.04,180.011,2017.93,3584.04,186.936,2011.81,3574.73,161.942,1998.85,3597.55,155.254,1939.51,3697.62,203.144,1938.5,3699.03,227.147,1960.57,3677.27,234.874,1961.49,3675.8,209.928,1937.82,3699.39,252.277,1959.82,3678.49,260.577,1984.82,3625.92,381.499,2000.45,3610.94,389.287,2011.24,3607.73,367.993,1996.22,3624.39,360.236,2124.82,3358.76,529.976,2127.27,3369.57,528.831,2122.81,3371.05,532.583,2118.93,3364.47,533.973,2008.59,3345.4,169.213,2007.76,3346.26,158.487,2015.84,3345.02,164.139,2017.74,3345.71,172.875,2039.53,3588.09,355.176,2017.47,3609.72,342.783,2031.82,3584.59,375.895,2032.54,3567.22,278.692,2022.47,3586.23,265.381,2042.45,3581.18,281.999,2002.52,3522.74,300.104,1983.89,3533.72,290.084,1986.9,3531.75,266.261,2006.62,3524.69,280.217,1997.26,3535.54,370.572,1982.69,3541.45,366.377,1971.74,3529.83,363.183,1993.01,3521.04,367.621,1990.9,3504.09,202.757,1972.85,3513.26,197.081,1968.26,3506.95,173.97,1989.56,3501.29,183.767,2007.74,3535.19,268.67,1998.42,3541.86,267.731,2000.73,3523.55,265.699,2045.1,3573.81,244.057,2024.36,3599.11,238.254,2042.36,3576.95,265.747,2019.32,3539.52,174.904,2008.01,3555.56,163.012,2030.91,3551.75,175.395,1995.67,3590.45,442.324,1974.84,3609.57,425.36,1960.76,3603.09,436.794,1982.26,3583.36,453.441,2017.11,3573.87,381.563,2008.26,3589.87,370.648,2025.34,3588.02,386.742,1977.11,3523.18,395.297,1956.88,3531.64,381.288,1989.35,3524.28,381.615,1953.48,3547.05,444.904,1928.33,3561.57,435.184,1921.61,3549.67,425.184,1948.26,3537.52,434.723,1992.26,3529.94,99.5379,1978.27,3545.83,95.5069,2001.05,3539.66,104.913,1964.35,3498.88,119.619,1950.67,3507.88,115.116,1947.8,3515.52,100.978,1962.66,3504.43,106.664,1987.31,3511.41,170.946,1979.93,3515.62,171.739,1979.31,3500.67,169.898,2018.94,3552.99,145.696,2007.68,3571.51,144.261,2019.13,3554.97,160.447,2107.69,3484.08,356.785,2104.88,3482.96,343.799,2094.97,3506.62,336.109,2097.36,3509.1,348.744,2082.59,3464.35,327.779,2070.45,3459.04,329.762,2061.06,3482.86,317.06,2073.06,3488.37,317.08,2063.24,3453.31,342.294,2052.02,3475.06,331.352,2062.97,3458.89,398.907,2055.2,3485.02,394.074,2045.7,3476.51,387.35,2055.46,3450.55,390.258,2052.54,3453.35,226.662,2041.05,3468.75,220.138,2049.6,3471.98,205.955,2059.15,3455.25,215.305,2059.31,3460.93,276.881,2049.74,3484.31,276.031,2035.45,3482.15,269.685,2046.85,3460.68,269.557,2097.35,3458.44,233.858,2091.75,3456.51,223.077,2086.95,3479.52,216.581,2091.5,3483.54,227.38,2069.56,3454.12,213.729,2061.32,3472.05,205.036,2111.2,3461.42,484.421,2123.01,3434.22,492.574,2125.72,3432.38,481.136,2113.73,3461.12,470.752,2114.03,3400.41,469.145,2100.61,3396.33,468.001,2093.82,3417.9,462.507,2105.74,3421.72,462.633,2075.54,3391.96,498.529,2073.78,3395.28,510.554,2066.93,3416.39,503.158,2068.91,3413.67,491.208,2079.36,3451.25,517.008,2091.66,3428.62,524.254,2100.12,3431.31,522.584,2087.46,3454.44,515.65,2030.01,3463.7,117.269,2028.81,3478.6,113.721,2035.33,3483.99,117.547,2036.74,3467.78,120.937,2003.37,3457.2,156.841,2008.68,3459.86,166.247,2006.51,3472.59,166.458,2001.27,3468.53,156.205,2016.18,3464.44,169.208,2014.92,3477.89,168.928,2041.22,3469.67,129.052,2039.79,3486.8,125.606,2140.62,3365.98,385.947,2138.54,3366.56,370.524,2134.47,3394.66,366.606,2136.9,3395.11,380.941,2107.05,3358.2,352.696,2094.79,3357.48,354.597,2095.26,3378.19,352.59,2106.49,3381.45,350.775,2075.48,3309.78,394.837,2082.2,3293.92,395.484,2082.31,3296.33,406.979,2076.3,3311.96,408.467,2109.39,3309.66,434.184,2121.55,3312.98,433.531,2047.61,3333.25,283.164,2053.3,3334.93,296.622,2086.89,3317.52,309.283,2079.24,3301.2,305.064,2088.61,3299.79,302.321,2098.78,3315.42,304.836,2113.82,3351.82,256.881,2106.47,3353.31,243.501,2109.76,3382.15,242.264,2116.2,3380.9,254.237,2078.81,3368.84,231.592,2070.02,3376.29,233.74,2075.73,3397.73,229.748,2084.15,3392.95,228.776,1993.88,3368.83,141.521,1991.78,3357.63,149.828,1986.5,3366.4,147.683,1988.01,3377.39,142.712,1996.25,3407.01,171.093,2003.12,3408.07,181.695,2006.85,3420.42,177.486,2000.17,3419.85,167.053,2019.66,3377.29,198.555,2013.28,3363.75,197.12,2018.88,3362.58,196.25,2027.53,3375.93,196.106,2043.3,3398.37,149.623,2037.58,3397.65,138.717,2039.22,3423.68,133.432,2044.32,3424.04,142.602,1748.09,3825.11,250.846,1766.87,3807.31,264.964,1760.62,3838.87,237.224,1785.15,3862.14,183.337,1801.73,3845.94,193.705,1805.29,3848.21,172.665,1789.29,3863.6,162.677,1800.57,3839.41,102.931,1798.2,3826.21,85.461,1756.06,3812.3,38.9393,1749.37,3792.49,30.8817,1732.7,3802.74,18.6704,1738.85,3822.68,26.9846,1713.8,3702.08,91.5781,1729.06,3688.73,96.4246,1722.38,3686.43,108.531,1707.21,3701.15,103.771,1683.49,3707.44,163.254,1694.12,3685.85,172.609,1690.73,3688.85,194.628,1681.28,3712.92,183.478,1697.77,3755.09,255.174,1713.86,3767.24,270.905,1803.4,3504.48,383.234,1803.52,3517.43,400.186,1804.68,3494.5,403.096,1800.27,3479.99,386.426,1742.72,3534.35,323.569,1737.89,3555.47,307.031,1765.17,3547.45,304.978,1764.2,3530.25,322.787,1700.46,3582.42,365.754,1695.19,3619.65,346.366,1695.11,3573.57,347.155,1771.1,3599.16,408.582,1755.76,3606.14,406.489,1803.15,3458.45,396.723,1807.9,3471.12,410.563,1746.72,3430.12,374.986,1746.57,3456.55,365.44,1757.29,3459.42,361.602,1759.74,3434.5,368.599,1726.67,3446.86,427.927,1728.14,3473.56,411.231,1723.86,3469.02,400.438,1719.89,3440.21,413.185,1787.01,3471.75,446.249,1782.21,3497.85,428.382,1776.75,3495.27,434.245,1775.87,3471.04,451.183,1766.85,3755.89,306.395,1742.36,3770.09,287.336,1756.06,3736.32,317.087,1891.54,3665.8,381.45,1878.86,3652.49,394.197,1892.73,3640.57,403.051,1907.68,3652.96,391.183,1866.71,3637.44,403.103,1878.56,3627.16,411.061,1830.5,3573.94,386.628,1839.99,3572.25,392.036,1843.4,3583.73,407.119,1836.1,3587.05,401.853,1931.53,3600,433.526,1949,3591.03,441.4,1943.78,3612.54,428.45,1908.66,3549.02,407.662,1930.64,3539.96,413.711,1902.53,3558.77,420.038,1943.42,3539.72,328.572,1975.65,3530.72,338.775,1940.05,3538.48,352.837,1782.65,3554.1,282.022,1784.37,3548.44,307.402,1763.35,3556.97,279.977,1831.01,3563.18,152.182,1834.52,3563.64,131.155,1854.37,3552.52,158.699,1724.2,3664.37,130.727,1710.4,3684.53,129.297,1735.26,3669.9,110.698,2113.23,3375.67,533.714,2119.85,3380.62,531.408,2071.8,3434.61,462.813,2080.73,3438.32,456.243,2083.66,3414.38,469.046,2110.72,3457.16,463.293,2123.47,3429.44,474.127,2135.93,3381.38,509.092,2137.34,3367.46,513.07,2139.79,3367.99,504.524,2138.31,3380.83,498.751,148.029,4120.36,-371.368,132.739,4057.53,-347.427,68.6268,4057.91,-360.015,83.0954,4119.86,-385.162,118.373,3998.13,-322.827,54.7363,3999.03,-333.742,-3.99932,4062,-357.145,10.33,4122.7,-384.663,-16.6519,4004.36,-328.869,206.807,4122.98,-345.021,189.13,4059.56,-321.01,257.536,4126.93,-302.512,236.935,4063.8,-276.063,173.632,4000.44,-296.713,220.073,4005.96,-251.249,162.584,4186.41,-393.456,224.851,4190.5,-367.554,174.302,4254.45,-413.736,240.248,4261.02,-389.102,280.019,4195.7,-325.984,308.038,4274.22,-346.407,96.1706,4184.58,-407.628,22.9971,4186.02,-408.639,106.662,4251.18,-427.276,33.1009,4250.99,-428.837,-66.1644,4208.45,416.093,-67.5681,4156.25,414.374,-12.2158,4155.32,406.145,-6.08123,4206.19,411.945,-70.6018,4095.85,418.529,-16.6549,4095.57,408.422,44.7045,4156.39,390.58,54.7753,4206.39,397.814,38.205,4096.68,392.945,-126.403,4212.74,414.945,-122.994,4158.67,418.437,-124.419,4096.37,424.621,-64.8736,4267.07,417.315,-127.452,4273.5,410.023,-64.4567,4326.87,412.197,-127.581,4327.4,402.924,0.640586,4267.05,419.532,65.507,4269.49,405.331,4.15272,4330.14,416.68,71.3972,4333.78,402.101,-108.013,4851.35,-419.801,-109.109,4892.92,-409.91,-49.857,4912.24,-416.093,-42.1517,4869.38,-429.128,-110.023,4931.96,-399.493,-57.2257,4949.41,-402.274,6.71678,4926.26,-414.596,18.0821,4882.82,-429.125,-5.47492,4962.41,-398.891,-157.273,4835.71,-410.571,-156.483,4875.89,-402.859,-155.363,4915.89,-394.951,-104.722,4806.33,-429.182,-156.91,4793.68,-418.738,-99.4033,4756.75,-437.702,-155.277,4746.08,-427.374,-33.1622,4820.05,-439.108,28.8187,4831.1,-439.432,-24.1293,4767.62,-445.271,39.0692,4775.4,-444.705,170.986,4665.69,-440.326,114.654,4664.21,-446.277,106.548,4724.53,-444.056,165.474,4731.34,-439.529,53.938,4660.01,-453.152,47.9585,4719.01,-448.374,97.5355,4784.77,-441.038,158.425,4796.31,-433.781,178.192,4604.11,-440.537,121.456,4602.84,-450.684,183.695,4540.52,-445.243,123.582,4535.65,-457.69,56.0662,4596.21,-459.488,54.3121,4527.09,-465.211,228.778,4666.38,-436.87,231.938,4601.2,-433.441,291.644,4664.57,-433.602,289.35,4596.14,-427.149,238.41,4540.49,-433.254,292.267,4537.9,-421.404,228.471,4738.75,-433.425,224.35,4807.39,-418.131,295.062,4741.38,-425.306,298.173,4817.26,-387.058,183.556,4394.63,-444.651,180.538,4323.59,-431.69,113.651,4319.29,-443.567,118.032,4389.23,-455.47,40.7215,4317.38,-445.332,46.358,4385.42,-457.856,244.556,4401.83,-428.004,244.144,4331.02,-412.169,300.256,4411.61,-407.479,303.421,4344.43,-385.953,184.709,4468.23,-449.181,242.832,4473.49,-433.997,296.572,4477.7,-417.102,121.187,4461.79,-460.867,50.5397,4455.4,-465.129,-111.481,3912.05,-263.004,-119.64,3967.51,-278.523,-81.927,3959.03,-289.409,-75.6777,3903.75,-271.152,-123.758,4021.02,-298.719,-82.266,4013.19,-312.229,-23.9622,3949.19,-303.004,-25.1999,3894.51,-280.66,-142.718,3918.09,-258.306,-147.766,3973.27,-273.219,-151.141,4026.38,-292.882,-101.803,3850.77,-250.35,-136.458,3854.07,-246.791,-95.4254,3788.05,-239.698,-132.685,3785.85,-236.406,-67.061,3845.78,-255.918,-22.077,3839.72,-261.934,-59.8811,3788.01,-243.924,-17.3143,3786.03,-247.77,-98.9909,4658.16,270.3,-85.7069,4612.98,308.954,-22.2428,4627.64,315.622,-42.12,4676.85,272.272,-77.0049,4560.43,340.396,-10.6928,4571.85,346.545,43.5973,4639.06,308.123,21.9869,4694.34,265.282,55.8031,4580.57,335.941,-146.144,4639.17,268.46,-139.476,4597.24,300.767,-134.653,4548.31,330.055,-113.711,4692.98,228.971,-153.115,4672.48,236.393,-122.396,4719.93,194.203,-157.451,4699.77,207.489,-67.8674,4714.15,218.683,-13.4675,4738.73,195.401,-81.4005,4740.42,178.299,-28.4469,4764.18,153.626,-68.2802,4445.91,385.242,0.904761,4453.32,389.552,-3.80696,4513.22,370.124,-71.7848,4504.06,365.311,69.9751,4459.23,376.712,64.1496,4520.47,357.943,-65.749,4386.7,401.065,3.84144,4392.31,405.417,72.6394,4397.04,391.77,-129.521,4439.46,375.267,-128.224,4383.07,391.301,-131.546,4495.01,354.938,120.498,3706.62,321.545,127.714,3637.49,308.172,160.164,3656.85,261.535,157.355,3719.27,268.21,133.95,3575.59,298.035,161.322,3600.59,260.896,183.596,3679.1,222.161,181.97,3733.23,222.734,186.421,3626.52,225.756,74.6616,3693.4,360.048,82.6188,3623.4,353.998,24.2947,3681.25,383.435,29.611,3612.14,380.172,91.98,3557.62,345.546,38.5036,3545.34,379.989,114.901,3776.15,331.595,69.6098,3764.06,366.112,113.73,3843.97,337.918,68.0485,3834.3,370.098,22.2268,3753.46,389.755,20.389,3825.86,393.167,154.676,3784.66,279.602,182.322,3791.93,229.011,155.425,3850.89,290.331,186.336,3855.8,236.196,237.402,3739.93,-96.6575,249.496,3687.14,-91.2957,219.054,3679.59,-135.668,206.596,3734.78,-137.958,257.997,3637.85,-89.2524,228.806,3627.52,-134.273,184.207,3668.5,-170.708,175.574,3722.53,-169.722,192.633,3615.81,-171.053,263.902,3742.61,-41.5024,271.386,3694.02,-38.4861,271.893,3748.88,20.3433,278.798,3700.86,18.5194,277.621,3646.9,-38.1997,285.794,3652.89,16.836,224.35,3793.48,-104.394,255.56,3793.83,-48.9581,216.76,3841.9,-110.931,249.052,3845.81,-59.8598,268.035,3797.26,19.1167,268.119,3847.91,13.4386,196.217,3786.53,-139.689,170.119,3771.73,-168.41,192.91,3830.05,-140.584,170.344,3813.52,-169.331,284.279,4140.42,-125.994,289.147,4159.61,-107.972,276.559,4122,-107.035,271.505,4103.9,-124.051,298.699,4173.9,-83.9184,292.969,4120.81,-58.7459,266.253,4071.87,-98.5946,258.803,4055.58,-123.795,292.868,4066.14,-37.8056,305.629,4185.42,-138.883,307.911,4205.15,-106.821,339.589,4244.46,-172.316,327.623,4250.16,-106.029,319.587,4230.64,-88.2431,338.546,4283.12,-82.4782,291.614,4130.49,-229.452,315.196,4195.65,-261.379,339.606,4256.83,-273.969,267.012,4072.54,-190.095,248.724,4019.23,-174.647,-38.0309,5855.17,92.2206,-35.0023,5869.56,52.8077,-58.9928,5891.37,52.3038,-61.2949,5876.86,93.6566,-32.8265,5879.44,13.4081,-57.4285,5901.31,11.2349,-85.7806,5908.72,52.1794,-87.3821,5894.22,95.1512,-84.7933,5918.63,9.68339,-17.9232,5829.67,90.4314,-14.2715,5843.81,53.1163,-1.30191,5800.9,87.8761,2.73791,5814.66,52.6579,-11.5933,5853.55,15.5384,5.66778,5824.16,16.9607,-41.994,5837.75,129.914,-22.5656,5812.77,126.009,-46.9701,5818.79,164.153,-28.2192,5794.76,158.374,-6.45202,5784.69,121.447,-12.7117,5767.84,152.201,-64.4882,5859.11,133.351,-89.7991,5876.31,136.509,-68.7216,5839.42,169.448,-93.2309,5856.16,174.164,251.177,3970.37,144.448,227.176,3976.39,189.595,216.869,3921.01,190.333,239.342,3916.35,144.182,204.833,3977.48,236.172,194.475,3918.85,238.343,208.639,3860.28,189.389,231.608,3859.09,142.936,268.733,4023.05,141.698,239.9,4028.11,188.089,292.163,4079.2,132.979,256.776,4084.94,184.886,216.041,4033.5,233.644,228.281,4093.6,231.104,279.894,3961.03,75.613,297.156,4016.02,65.6342,279.764,3954.89,-8.06674,287.531,4010.12,-21.7005,312.56,4073.11,51.6171,265.71,3907.25,81.3108,256.696,3853.39,84.6103,272.511,3900.82,3.65484,595.634,4597.36,34.8166,642.818,4559.69,14.8677,661.86,4583.54,-1.80137,615.148,4629.49,17.2264,685.722,4525.68,-1.62226,704.028,4541.2,-17.5244,680.484,4607.79,-26.057,634.884,4660.77,-10.2991,721.518,4557.5,-38.0134,576.927,4567.12,44.5779,623.546,4537.68,24.6867,558.574,4540.08,46.7518,603.848,4518.48,28.5407,666.663,4510.98,9.75296,646.697,4497.82,16.5426,544.813,4631.76,51.5508,528.121,4594.59,64.0632,491.732,4660.34,65.6275,478.045,4617.79,83.6589,512.211,4560.36,67.744,465.27,4577.36,91.8423,562.565,4670.06,29.9196,581.261,4706.73,-1.56966,506.177,4702.71,37.6531,521.103,4741.12,-0.676945,668.22,4704.7,-96.6482,714.75,4648.52,-97.358,726.39,4659.48,-143.521,678.267,4711.24,-149.417,754.007,4590.63,-98.3013,766.061,4601.27,-137.828,729.968,4656.64,-192.472,683.193,4706.81,-202.072,769.16,4599.94,-181.692,652.858,4686.58,-49.2296,698.354,4630.29,-58.0863,738.272,4574.68,-64.4721,614.949,4754.31,-97.7328,599.447,4736.08,-44.9292,551.831,4792.54,-101.282,536.298,4771.51,-48.0554,626.039,4760.32,-154.482,632.694,4755.29,-210.364,566.386,4803.39,-158.173,577.114,4800.51,-216.097,677.112,4667.7,-292.964,718.846,4619.39,-277.105,703.711,4591.08,-305.571,664.635,4635.81,-324.537,754.052,4568.12,-258.825,739.22,4549.24,-286.862,684.223,4561.72,-324.302,645.975,4599.15,-343.473,721.061,4529.32,-308.293,682.801,4691.55,-250.772,727.017,4641.51,-238.104,763.928,4586.26,-223.454,629.515,4712.13,-305.436,634.056,4738.6,-261.665,576.547,4754.15,-314.619,580.239,4782.91,-269.518,619.159,4678.26,-339.248,603.163,4638.72,-360.509,567.226,4717.96,-349.59,553.607,4676.17,-373.182,600.552,4536.88,-353.269,638.756,4513.95,-342.25,616.869,4488.74,-342.969,579.31,4512.31,-351.278,679.464,4486.59,-334.896,657.682,4456.18,-333.953,594.784,4457.9,-334.573,558.233,4483.32,-343.466,635.405,4426.77,-321.117,623.469,4564.3,-351.537,661.987,4535.6,-335.543,701.172,4509.41,-324.514,561.737,4561.04,-366.995,582.937,4597.42,-368.502,518.272,4586.06,-382.312,536.675,4630.52,-383.674,542.179,4531.02,-361.639,523.432,4501.04,-352.721,500.911,4547.92,-374.969,485.179,4513.67,-364.906,514.492,4431.33,-298.79,544.716,4416.48,-292.431,519.349,4410.78,-262.927,495.915,4422.04,-267.153,586.465,4397.6,-285.189,554.937,4392.31,-260.045,499.882,4410.99,-231.533,481.072,4419.98,-234.808,529.898,4392.47,-229.029,535.969,4451.61,-325.799,570.822,4430.97,-316.65,612.905,4407.81,-303.788,489.943,4440.88,-308.429,505.141,4467.5,-335.865,463.074,4445.11,-323.546,471.746,4477.88,-349.24,477.406,4426.32,-275.456,466.387,4421.29,-241.369,456.84,4423.67,-290.036,450.56,4414.68,-253.243,458.966,4427.41,-174.059,478.608,4418.17,-172.559,472.727,4423.12,-139.739,448.504,4433.82,-139.081,510.77,4398.72,-168.337,510.874,4401.9,-135.506,464.434,4430.98,-100.423,432.141,4442.91,-94.3852,511.577,4407.72,-99.7175,469.201,4422.75,-204.331,486.965,4414.23,-201.987,516.194,4395.59,-198.718,445.467,4428.28,-175.827,455.99,4423.08,-208.547,433.54,4421.37,-179.19,443.019,4415.61,-216.277,432.736,4435.7,-137.168,414.488,4444.83,-87.8753,419.452,4429.35,-134.455,401.99,4440.19,-81.9089,432.294,4459.9,-38.8253,471.053,4450.42,-37.6426,491.824,4459.93,-15.9215,452.758,4468.53,-17.7108,521.831,4429,-36.7544,538.173,4441.4,-14.1324,515.459,4469.24,0.732124,476.234,4477.1,0.756845,558.761,4453.53,2.18895,424.423,4451.64,-61.585,460.397,4440.73,-64.922,513.118,4417.24,-65.5153,412.719,4459.46,-40.0417,408.315,4452.55,-58.8472,401.269,4455.45,-39.556,398.431,4448.87,-56.0803,426.149,4468.08,-19.8861,444.333,4477.71,2.20665,409.545,4463.08,-19.2652,421.26,4472.99,9.07306,520.232,4499.71,31.2636,561.742,4489.62,21.9934,583.343,4502.54,27.3253,539.915,4517.39,41.8766,603.566,4475.78,18.0742,625.627,4486.35,19.2449,498.984,4486.8,17.0202,539.02,4478.85,13.1322,581.005,4465.01,12.6593,480.605,4506.33,46.3821,463.079,4489.35,24.9059,443.737,4509.34,69.9219,433.027,4487.09,40.8387,496.647,4530.4,61.7751,453.948,4540.51,87.8554,962.526,4343.18,30.4,944.947,4327.47,46.2483,989.578,4297.89,44.0633,1006.85,4312.52,27.7441,926.235,4311.02,54.9032,971.383,4281.68,53.5751,1035.93,4267.34,35.2064,1052.1,4279.96,17.9554,1017.27,4252.04,47.4932,918.385,4373.17,29.1756,900.027,4357.21,45.6056,872.913,4403.74,25.0133,853.637,4388.14,42.2226,880.087,4340.8,54.6413,832.341,4372.12,52.2016,977.442,4356.76,7.92921,933.992,4387.19,5.88694,987.835,4366.81,-20.7421,945.469,4397.44,-24.0669,889.255,4417.51,1.16473,901.697,4427.68,-28.8904,1021.18,4324.54,6.08851,1063.77,4290.22,-1.24037,1030.46,4333.74,-19.7857,1070.64,4298.9,-21.9666,991.15,4373.1,-88.7382,1029.97,4343.33,-81.8535,1022.91,4344.2,-110.688,987.776,4371.13,-115.842,1068.64,4312.55,-72.2703,1061.12,4315.91,-101.751,1016.67,4343.81,-132.07,986.057,4368.44,-134.407,1053.79,4314.37,-128.622,992.325,4372.33,-54.3061,1033.41,4340.09,-49.6117,1072.23,4306.42,-45.3401,953.932,4402.09,-91.9171,951.913,4402.38,-58.5327,915.002,4432.55,-93.8267,909.983,4432.58,-62.3718,954.185,4399.39,-117.239,954.732,4396.8,-136.479,917.763,4430.69,-118.714,919.269,4429.44,-140.915,987.852,4360.68,-178.255,1015.95,4335.07,-170.756,1017.2,4318.89,-201.984,982.967,4353.67,-217.418,1048.58,4292.04,-172.118,1047.38,4279.33,-194.897,1004.92,4303.22,-232.909,970.017,4336.68,-250.061,1037.21,4274.59,-224.93,986.82,4365.1,-152.162,1014.43,4341.4,-149.472,1049.31,4306.5,-151.751,954.299,4392.11,-192.466,955.525,4394.6,-158.743,916.684,4426.97,-207.16,919.319,4428.92,-168.639,946.845,4384.36,-232.597,933.552,4367.46,-262.243,908.499,4416.89,-245.463,895.289,4398.71,-273.227,929.1,4275.11,-247.402,950.615,4308.47,-261.322,984.553,4280.63,-243.402,962.405,4255.61,-235.342,1015.17,4262.22,-242.759,993.053,4238.71,-233.138,887.159,4306.25,-267.347,914.316,4341.9,-276.877,849.038,4338.63,-284.093,876.572,4373.64,-289.139,912.737,4265.75,-238.987,865.647,4291.25,-252.354,900.866,4264.92,-236.161,853.204,4289.89,-247.171,823.005,4316.36,-262.806,808.234,4314.4,-255.367,948.459,4247.65,-229.522,978.301,4229.59,-226.32,939.195,4246.39,-227.487,969.196,4227.93,-223.464,881.929,4266.11,-232.619,891.189,4265.37,-234.085,930.856,4246.67,-226.184,925.231,4245.39,-224.639,964.045,4226.07,-221.549,961.634,4220.32,-219.193,830.695,4292.79,-241.73,842.225,4291.04,-244.08,780.691,4318.56,-248.231,794.653,4316.31,-251.367,870.14,4261.53,-227.393,814.886,4288.29,-234.414,835.368,4223.97,-193.673,779.42,4254.74,-197.483,759.203,4314.17,-238.623,725.279,4288.22,-203.672,919.342,4235.11,-219.548,955.727,4201.69,-212.296,885.148,4192.16,-188.93,930.421,4160.4,-183.889,823.64,4212.23,-126.031,822.904,4211.97,-158.297,875.934,4179.44,-152.005,880.163,4182.65,-114.82,923.894,4147.12,-150.188,926.424,4152.36,-110.864,764.406,4247.45,-132.761,766.969,4246.41,-163.814,708.714,4281.64,-139.266,712.773,4281.01,-170.888,831.935,4216.94,-95.1218,767.391,4252.65,-102.42,840.276,4222.86,-71.4073,773.662,4259.56,-75.0048,710.1,4286.02,-108.939,715.038,4292.01,-80.8274,890.597,4189.97,-84.7224,934.977,4162.31,-78.6079,898.946,4194.84,-67.7219,943.592,4167.73,-61.2978,847.575,4235.98,-35.9469,905.549,4200.99,-43.9785,907.893,4206.76,-25.1091,851.338,4243.85,-15.1651,953.291,4172.44,-41.925,957.453,4175.63,-28.1251,912.717,4218.48,3.18381,859.107,4254.79,9.29835,962.782,4184.18,-4.13658,844.7,4229.34,-53.1455,903.165,4197.97,-56.3799,949.289,4170.62,-51.3543,786.51,4272.89,-32.6322,780.207,4266.65,-52.4963,728.215,4305.32,-31.6355,721.084,4298.39,-55.214,794.025,4279.88,-11.7086,805.004,4288.87,10.3,737.455,4313.05,-9.16491,750.301,4321.8,12.37,889.505,4280.48,47.3066,937.802,4249.46,47.0029,953.846,4265.24,54.5956,907.501,4295.11,55.265,984.132,4218.09,43.7635,999.343,4235.18,50.8059,872.857,4267.1,31.5593,923.523,4234.2,30.8681,971.64,4200.69,25.7828,839.327,4311.64,47.068,820.598,4299.55,31.3197,787.962,4343.53,46.5037,767.538,4331.85,31.9429,859.53,4325.34,55.1604,810.03,4357,53.6512,1348.44,4092,69.9579,1336.09,4070.84,75.8973,1367.99,4052.08,88.6259,1380.67,4072.87,83.0802,1324.13,4049.54,78.2935,1355.99,4031.04,90.3961,1401.9,4031.91,101.399,1414.84,4052.32,95.9388,1389.94,4011.27,102.48,1317.84,4110.03,57.3227,1305.85,4088.59,63.7924,1288.16,4127.47,45.6196,1276.21,4105.55,52.4823,1293.32,4066.38,66.0552,1263.69,4082.74,54.7198,1364.59,4113.14,52.7869,1332.9,4130.94,40.9597,1374.9,4124.21,33.9153,1344.12,4142.5,22.8434,1302.1,4147.56,31.4175,1313.25,4160.38,17.14,1398.37,4093.78,64.9911,1430.21,4073.02,78.7542,1407.43,4104.27,47.2889,1439.14,4083.94,61.0605,1388.11,4149.99,4.74653,1422.22,4129.88,18.1933,1432.21,4139.36,-11.5817,1398.64,4160.18,-25.7844,1456.57,4106.57,32.8481,1466.7,4116.18,3.8258,1434.61,4138.63,-40.8812,1400.5,4159.74,-55.5299,1469.56,4115.26,-24.6195,1377.73,4133.08,23.227,1410.12,4113.02,37.014,1445.05,4091.39,50.58,1357.21,4168.48,-8.02635,1349.53,4151.86,10.796,1329.55,4189.68,-22.1619,1322.85,4173.98,1.56942,1366.09,4179.15,-38.7036,1367.31,4179.06,-68.6103,1334.64,4196.8,-50.2818,1335.12,4197.08,-80.1714,1389.35,4139.64,-116.915,1425.46,4117.8,-100.584,1415.9,4100.24,-127.721,1378.66,4122.58,-144.909,1462.32,4094.29,-81.9181,1453.93,4076.69,-107.906,1404.33,4079.61,-150.864,1366.11,4102.55,-168.674,1443.48,4055.95,-130.186,1397.02,4152.46,-86.5157,1432.03,4131.02,-71.0918,1467.81,4107.55,-53.6715,1353.99,4160.19,-131.047,1362.83,4172.32,-100.053,1319.39,4179.79,-143.116,1329.52,4191.02,-111.816,1342.15,4143.94,-159.612,1328.66,4124.84,-183.766,1306.3,4164.57,-171.974,1291.81,4146.54,-196.287,1339.2,4058.06,-197.715,1352.87,4080.87,-186.39,1391.76,4057.19,-168.374,1378.47,4033.64,-179.878,1431.81,4033.27,-147.305,1419.22,4009.45,-158.863,1301,4082.29,-212.588,1314.86,4104.15,-201.525,1263.49,4105.89,-224.712,1277.47,4126.86,-213.956,1325.39,4034.66,-202.308,1287.32,4059.66,-216.648,1311.72,4011.2,-199.829,1274.07,4036.68,-213.401,1250.03,4083.97,-228.282,1237.31,4061.44,-224.395,1364.76,4009.64,-185,1405.98,3985.28,-164.461,1350.93,3985.88,-183.365,1392.41,3961.57,-163.7,1286.35,3966.84,-173.97,1298.46,3988.22,-189.935,1337.28,3963.02,-174.598,1324.62,3942.14,-159.91,1378.79,3939.12,-156.179,1366,3918.93,-142.966,1250.18,3992.03,-185.528,1261.49,4013.75,-202.545,1215.09,4016.75,-194.968,1225.49,4038.65,-212.779,1276.09,3948.19,-153.274,1240.74,3972.68,-163.803,1268.41,3933.38,-129.189,1233.77,3956.85,-138.821,1206.6,3996.87,-172.491,1200.51,3980.16,-146.881,1313.78,3924.31,-140.513,1354.93,3902,-125.13,1305.58,3910.59,-117.617,1346.47,3889.34,-103.738,1262.39,3918.51,-75.8764,1264.03,3923.54,-103.058,1300.85,3902.05,-92.4354,1298.96,3898.36,-65.9885,1341.51,3881.96,-79.8591,1339.35,3879.38,-54.5143,1228.62,3939.23,-84.4219,1229.89,3945.69,-112.033,1196.6,3959.95,-91.8131,1197.32,3967.78,-119.669,1263.56,3919.78,-46.9241,1229.99,3938.75,-55.5036,1270.64,3933.67,-15.3337,1236.74,3949.98,-24.1263,1198.33,3957.99,-62.9345,1204.92,3966.82,-32.0116,1299.8,3901.38,-37.2663,1339.78,3883.65,-26.4881,1307.55,3918.76,-5.07252,1346.96,3904.06,5.97799,1291.3,3961.81,16.2761,1281.39,3950.49,5.31197,1319.87,3937.74,15.3078,1327.75,3948.15,24.4244,1356.49,3925.13,25.4395,1359.25,3935.42,32.2937,1256.12,3976.53,8.69547,1246.54,3965.84,-2.67392,1224.18,3990.71,2.10528,1215.33,3981.23,-9.5747,1296.58,3967.66,22.6179,1264.11,3982.5,16.2238,1298.05,3972.18,26.9618,1268.11,3987.62,22.0793,1231.9,3997.22,10.0058,1237.61,4003.4,17.6294,1330.1,3953.42,29.0678,1359.82,3940.45,36.4096,1330.78,3957.15,32.5038,1361.22,3943.75,40.6714,1300.35,3991.36,46.2906,1335.27,3974.95,56.5238,1344.15,4007.08,84.9709,1310.33,4022.78,71.2638,1370.29,3962.42,73.2498,1379.32,3989.5,97.5697,1298.17,3977.87,32.4187,1331.95,3961.71,38.3862,1365.22,3948.08,49.621,1270.93,4009.91,40.673,1268.99,3994.96,28.2932,1243.87,4032.19,38.296,1240.5,4013.94,26.3408,1279.99,4039.19,59.7852,1251.97,4057.97,50.7642,1489.27,4005.39,119.817,1475.9,3986.67,125.75,1514.43,3962.86,137.908,1528.3,3980.66,130.934,1464.93,3968.08,126.339,1503.63,3945.07,138.536,1552.5,3938.8,150.841,1567.96,3956.18,141.635,1541.64,3921.64,151.403,1451.11,4029.3,108.521,1438.19,4009.93,113.629,1426.63,3990.23,114.397,1507.32,4022.28,98.6574,1468.38,4047.37,92.5094,1516.08,4029.13,81.1803,1481.96,4055.16,71.7327,1547.45,3994.92,107.895,1581.51,3969.23,123.024,1552.59,4000.48,96.1767,1584.02,3976.09,112.266,1518.55,4042.49,64.9205,1490.47,4072.86,50.172,1485.73,4060.15,62.6154,1516.62,4034.11,73.5753,1533.32,4058.39,41.9092,1501.48,4088.92,21.5775,1541.38,4061.14,12.5971,1505.27,4089.12,-6.70297,1553.58,4015.07,80.4567,1569.03,4029.43,59.8366,1591.04,3987.95,97.6175,1605.29,4000.23,78.8706,1577.61,4032.23,32.9369,1613.33,4003.09,54.3948,1551.64,4006,89.6259,1585.31,3980.67,106.202,1537.92,4041.88,-37.9003,1499.92,4068.77,-60.7792,1504.3,4081.62,-34.1442,1541.19,4054.03,-13.0375,1532.17,4025.7,-60.8768,1492.82,4051.7,-85.3219,1524.51,4006.47,-80.8344,1483.71,4031.5,-106.492,1575.97,4014.26,-14.01,1571.55,3999.11,-35.4773,1613.73,3986.54,10.1588,1610.55,3972.38,-10.0316,1565.35,3981.04,-54.267,1605.72,3955.42,-27.8417,1578.15,4025.59,9.12207,1614.88,3997.11,31.8057,1505.47,3962.75,-107.881,1515.54,3985.19,-96.6382,1557.86,3960.91,-69.3645,1549.25,3939.62,-80.3744,1599.6,3936.4,-42.3472,1592.34,3916.23,-53.1625,1461.83,3985.92,-134.454,1473.3,4009.29,-123.005,1494.53,3940.02,-114.158,1449.6,3962.25,-140.43,1482.96,3917.89,-115.062,1436.85,3939.15,-140.523,1539.72,3918.04,-86.8986,1584.09,3895.78,-59.9041,1529.44,3897.06,-88.5417,1575,3875.93,-62.1885,1459.39,3878.96,-100.31,1470.96,3897.24,-110.186,1518.59,3877.56,-84.9054,1507.95,3860.36,-76.652,1565.22,3857.57,-59.6322,1555.42,3841.45,-52.8116,1411.5,3898.17,-122.757,1423.86,3917.48,-134.326,1449.09,3863.94,-86.214,1400.67,3882.19,-106.738,1440.87,3853.07,-68.6782,1392.25,3870.48,-87.1855,1498.26,3846.3,-64.4422,1546.28,3828.32,-42.3013,1490.3,3836.19,-48.9359,1538.48,3818.9,-28.6953,1432.63,3845.93,-26.477,1435.59,3847.25,-48.4817,1484.82,3830.88,-30.7804,1481.9,3830.72,-9.32857,1532.74,3814.43,-11.7214,1529.25,3816.2,9.73369,1384.62,3862.16,-41.2137,1387.12,3863.98,-65.0222,1432.31,3851.55,-0.628129,1384.68,3867.11,-14.1832,1435.67,3866.66,26.7871,1389.62,3886.51,16.6156,1482.55,3837.32,15.8341,1528.17,3823.42,32.2854,1485.3,3848.92,38.9154,1528.38,3833.03,51.0501,1438.54,3892.39,54.4997,1394.76,3916.83,42.3161,1394.36,3905.93,34.9309,1438.01,3881.98,44.6117,1439.78,3898.9,62.7387,1394.98,3922.97,47.7648,1441.46,3904.61,74.4736,1397.94,3927.14,55.3671,1485.49,3868.87,64.6649,1484.3,3875.62,75.2193,1527.03,3848.49,74.5574,1525.55,3854.65,86.472,1483.92,3883.28,89.2392,1524.62,3862.82,102.142,1486.44,3860.2,54.3336,1528.14,3841.77,64.1264,1448.27,3930.41,109.708,1408.16,3948.98,94.3141,1402.51,3933.17,70.737,1443.83,3914.44,91.5546,1455.59,3949.29,121.758,1416.47,3969.98,109.4,1488.74,3910.44,123.326,1495.24,3927.78,134.192,1528.47,3889.55,136.481,1534.02,3905.53,146.771,1484.97,3894.82,106.735,1525.3,3874.64,120.382,271.246,4950.43,-276.97,252.221,4939.79,-320.599,224.773,4969.57,-299.479,243.106,4973.55,-257.972,221.659,4927.14,-357.6,195.686,4963.02,-335.914,199.443,4993.7,-279.751,216.5,4993.25,-239.764,172.062,4991.32,-315.671,304.555,4922.67,-295.337,289.255,4895.18,-342.401,356.256,4863.69,-314.815,333.818,4840.86,-349.074,256.759,4876.17,-377.878,279.224,4955.8,-229.642,312.697,4934.51,-244.949,276.072,4952.87,-180.575,310.831,4931.65,-191.975,355.955,4903.42,-259.95,352.642,4904.23,-201.421,250.225,4973.81,-213.412,222.574,4989.66,-197.686,245.813,4969,-167.984,217.254,4982.82,-155.519,241.778,4906.41,-85.0689,209.533,4877.86,-41.7198,242.754,4845.69,-46.7246,277.113,4872.92,-91.0579,167.325,4854.84,-0.268415,196.082,4826.42,-1.00082,279.911,4822.87,-53.027,314.458,4845.55,-97.7924,234.101,4802.98,5.98389,208.823,4937.29,-79.7775,178.521,4913.24,-38.3645,178.828,4959.59,-74.7171,149.623,4943.4,-36.4388,140.578,4889.59,0.645906,114.761,4924.82,-0.0751466,263.36,4935.5,-131.601,231.188,4957.12,-123.217,201.754,4972.78,-114.498,299.454,4906.24,-138.988,339.209,4876.09,-145.145,67.8969,4822.92,71.9473,14.9648,4810.96,100.153,23.4478,4794.96,109.215,80.76,4804.64,78.2138,-37.145,4797.9,120.588,-32.7058,4780.97,132.889,30.6633,4780.12,127.044,92.3824,4788.71,94.9735,51.5951,4850.53,67.533,2.12756,4835.48,92.6191,32.2156,4887.93,61.4586,-14.5935,4871.64,83.3276,-46.0689,4821.68,109.874,-58.2528,4856.73,97.4389,118.647,4836.98,38.0124,97.758,4868.38,36.2682,75.5707,4905.62,33.004,137.972,4814.09,41.4798,156.644,4795.27,56.0566,-27.0565,5773.79,-264.086,-9.19291,5764.07,-245.01,-6.45465,5736.55,-253.341,-24.1122,5745.12,-275.008,4.38083,5756.21,-223.197,7.05139,5729.03,-228.962,-4.77643,5706.03,-260.314,-21.8999,5713.55,-283.666,8.251,5699.1,-234.449,-30.209,5799.05,-250.442,-12.8757,5787.72,-234.998,-32.5384,5820.37,-233.24,-15.3532,5806.58,-221.472,-0.143856,5779.48,-217.066,-5.08633,5795.49,-211.812,-49.1908,5783.75,-280.525,-52.4615,5810.49,-264.206,-75.2756,5792.71,-294.107,-78.7429,5820.67,-275.832,-55.0648,5833.68,-244.39,-81.6056,5845.19,-254.156,-45.9047,5753.8,-293.687,-43.2535,5721,-304.029,-71.8154,5761.62,-309.1,-68.9724,5727.73,-320.929,-123.011,4140.4,-349.874,-67.3436,4130.47,-367.965,-76.3666,4070.17,-339.692,-124.318,4078.73,-323.75,-121.393,4202.84,-373.288,-58.5677,4192.67,-393.165,-119.973,4262.68,-392.692,-50.8751,4255.13,-414.349,-155.109,4147.76,-341.841,-156.637,4210.76,-363.829,-157.788,4268.84,-381.725,-153.337,4084.76,-317.02,127.872,4905.93,-407.266,109.963,4946.44,-388.518,156.351,4955.16,-365.567,178.427,4916.37,-386.197,91.5453,4979.91,-369.973,135.109,4986.51,-345.845,73.8557,4894.65,-421.374,59.4977,4936.92,-404.915,44.0198,4971.93,-387.802,144.818,4855.94,-423.523,86.6295,4843.23,-434.233,202.733,4866.91,-405.244,-95.1568,4636.11,-450.131,-16.4764,4651.21,-456.631,-19.4082,4584.77,-460.943,-98.9896,4569.11,-450.579,-24.6234,4516.75,-461.621,-104.336,4504.84,-446.328,-95.0816,4700.46,-445.101,-17.9164,4712.45,-450.764,-153.612,4623.12,-439.399,-153.602,4688.99,-435.1,-155.105,4557.35,-438.745,-156.694,4496.93,-433.804,-158.481,4379.91,-411.881,-157.781,4438.39,-424.793,-114.683,4380.61,-424.824,-118.035,4320.89,-409.499,-158.493,4323.84,-397.22,-36.368,4382.96,-446.929,-43.5754,4318.16,-432.304,-109.643,4442.51,-437.65,-30.248,4449.36,-456.954,97.0723,3886.19,-270.721,106.01,3940.71,-296.963,159.753,3941.65,-268.841,148.831,3887.19,-239.222,202.898,3944.79,-219.988,187.19,3892.28,-191.832,36.819,3888.13,-283.11,43.7014,3942.61,-307.686,91.9172,3835.86,-247.324,34.1428,3835.66,-261.617,89.7953,3788.99,-230.843,34.7771,3785.36,-245.446,141.544,3840.48,-215.756,176.271,3851.19,-175.711,137.289,3798.55,-202.256,153.83,4718.37,223.81,167.474,4656.77,255.328,225.4,4662.16,219.859,212.671,4725.55,185.451,178.083,4594.73,279.152,235.46,4599.72,242.682,284.247,4659.46,175.4,274.322,4719.73,142.253,292.443,4599.27,195.989,87.9738,4707.01,248.464,106.659,4648.34,285.974,118.318,4588.01,311.459,110.294,4764.97,136.615,46.7643,4756.09,167.56,172.775,4770.47,100.423,241.392,4773.07,65.7011,196.431,4470.2,315.944,135.621,4464.6,352.308,138.425,4401.45,365.126,199.921,4406.29,325.293,136.059,4337.01,372.274,197.703,4340.1,329.05,188.136,4532.8,299.706,128.003,4526.77,333.376,251.948,4476.91,271.656,244.587,4538.69,259.958,302.392,4486.84,223.591,297.535,4544.53,212.838,256.551,4412.43,275.402,256.488,4343.19,272.952,307.434,4423.15,218.873,307.158,4346.55,190.584,-76.4747,3963.1,424.282,-127.917,3964.2,430.034,-127.906,3893.45,426.39,-77.7893,3892.96,421.517,-127.633,3817.85,423.236,-78.16,3818.37,418.146,-73.8114,4030.09,422.807,-126.695,4030.47,428.794,-25.0323,3962.85,413.562,-20.9025,4030,412.121,26.2569,3964.6,397.716,32.2319,4031.12,396.65,-27.8722,3893.48,411.885,-28.2829,3821.06,408.853,21.8219,3896.43,396.079,-146.397,5914.87,99.0836,-178.052,5919.14,100.589,-178.52,5901.52,143.991,-147.471,5897.1,142.186,-179.278,5881.24,183.382,-149.153,5876.75,181.358,-145.774,5929.04,54.2526,-177.785,5933.15,55.3778,-145.446,5938.64,9.89464,-177.632,5942.59,10.5657,-115.962,5906.72,97.1162,-114.907,5921.09,53.008,-114.316,5930.86,9.41794,-117.679,5888.84,139.573,-120.256,5868.5,178.21,-119.833,4770.33,145.587,-82.5204,4783.64,134.536,-83.9538,4809.49,120.549,-115.799,4798.96,128.891,-89.1069,4843.83,106.567,-115.149,4833.36,114.488,-123.608,4744.55,166.92,-83.9774,4761.9,152.152,-155.059,4760.17,154.28,-157.847,4726.36,181.271,-151.532,4794.68,134.383,-149.528,4827.97,121.173,-137.424,5804.05,-312.097,-171.659,5806.53,-316.603,-173.263,5835.95,-295.235,-139.935,5833.26,-291.324,-174.658,5862.04,-270.582,-142.082,5859.13,-267.248,-134.915,5771.83,-329.393,-170.042,5774.13,-334.435,-132.779,5736.92,-343.036,-168.608,5739.09,-348.483,-104.991,5799.46,-304.616,-101.829,5767.64,-320.975,-99.1916,5733.08,-333.891,-108.164,5828.21,-284.864,-110.839,5853.59,-261.774,1118.39,4228.95,-5.20302,1111.67,4217.29,4.28931,1131.87,4202.91,-1.27208,1135.93,4214.5,-8.44765,1101.81,4202.15,12.8003,1125.31,4188.11,5.54323,1147.87,4189.96,0.248581,1150.69,4202.56,-6.27646,1143.34,4174.22,6.84735,1091.3,4250.19,4.15349,1080.27,4238.08,17.7878,1065.15,4222.8,29.9823,1122.46,4238.56,-14.7914,1098.59,4260.09,-9.80575,1124.64,4247.48,-25.1124,1102.47,4268.94,-24.5661,1138.48,4224.14,-15.5685,1153.32,4212.99,-12.6962,1140.71,4233.14,-23.5285,1157.32,4222.52,-20.1189,1125.61,4266.59,-55.7562,1100.58,4286.09,-63.3475,1103.09,4277.62,-41.6478,1125.58,4256.7,-37.8581,1124.41,4274.94,-82.0574,1094.97,4292.14,-91.5756,1115.78,4272.08,-120.684,1084.83,4283.98,-130.689,1148.13,4252.7,-50.3094,1152.86,4262.13,-75.6051,1174.39,4242.42,-47.178,1186.16,4251.87,-72.1819,1152.68,4263.91,-111.314,1191.74,4256.17,-106.643,1143.64,4242.53,-34.1975,1164.13,4232.22,-30.648,1093.83,4253.93,-166.5,1072.11,4265.01,-167.656,1075.52,4270.54,-156.243,1095.85,4259.06,-154.846,1092.77,4251.57,-176.129,1068.74,4262.05,-180.6,1087.36,4249.15,-196.725,1061.51,4260.01,-206.616,1120.47,4243.1,-171.484,1119.94,4238.89,-182.689,1150.06,4233.08,-173.953,1142.17,4226.49,-186.986,1116.51,4235.21,-198.826,1140.39,4221.19,-197.937,1130.18,4251.9,-150.136,1182.42,4250.56,-140.289,1047.6,4215.33,-248.637,1070.09,4237.58,-239.166,1102.63,4223.88,-231.55,1081.42,4204.81,-249.02,1134.7,4210.16,-219.075,1118.53,4190.98,-240.632,1021.36,4224.17,-239.425,1042.5,4248.98,-242.786,1032.66,4198.62,-236.99,1007.21,4211.19,-228.217,1024.99,4189.65,-227.992,998.551,4206.79,-222.802,1062.9,4187.17,-246.994,1101.91,4170.91,-246.104,1053.72,4174.12,-238.022,1091.71,4153.88,-240.649,1020.18,4164.72,-215.619,1021.85,4180.95,-222.138,1050.81,4159.45,-228.5,1048.79,4136.64,-217.039,1086.13,4135.07,-230.733,1080.34,4110.09,-216.462,992.492,4191.95,-216.058,994.132,4202.46,-219.701,1013.13,4134.36,-201.438,985.488,4166.95,-206.103,1004.65,4106.61,-174.35,969.1,4131.22,-178.257,1042.53,4106.7,-198.057,1073.37,4083.46,-194.615,1037.71,4085.02,-171.311,1069.7,4064.97,-168.161,1005.91,4090.21,-114.506,1003.59,4094.66,-145.935,1037.51,4072.26,-143.522,1039.9,4064.04,-114.757,1069.62,4051.19,-140.462,1072.1,4041.33,-112.306,969.281,4119.44,-112.363,966.308,4119.06,-148.3,1010.64,4097.13,-81.005,976.315,4130.19,-77.4748,1016.27,4109.04,-56.1226,984.333,4139.09,-57.0024,1044.03,4063.62,-84.7131,1077.02,4036.84,-83.5809,1049.56,4071.35,-57.362,1084.94,4039.88,-55.5786,1029.44,4124.97,-30.2569,997.17,4145.95,-36.5183,991.019,4143.72,-45.9487,1022.33,4118.78,-40.9324,1037.37,4129.69,-20.3394,1003.97,4148.49,-25.5755,1046.4,4135.59,-8.79915,1011.23,4154.02,-10.0372,1059.13,4097.83,-23.3554,1065.28,4108.92,-12.8999,1097.17,4062.13,-18.9976,1099.36,4077.12,-6.09359,1073.92,4119.45,-2.88747,1104.01,4094.69,5.01101,1054.49,4084.62,-37.0791,1092.86,4049.47,-33.8574,1072.66,4162.51,14.7887,1031.37,4184.52,29.9556,1019.77,4166.17,11.4484,1057.94,4145.79,4.22785,1088.45,4183.1,17.544,1047.31,4204.43,36.0672,1101.15,4149.85,10.8605,1115.11,4169.75,10.4069,1124.88,4134.59,15.1991,1135.75,4155.19,12.4919,1086.13,4132.54,5.74885,1112.97,4113.95,12.671,1637.1,3907.81,168.145,1608.58,3930.4,152.876,1591.41,3915.5,163.814,1624.06,3892.72,178.462,1578.2,3898.43,165.227,1612.09,3875.42,180.041,1643.73,3916.75,158.704,1617.28,3939.47,142.047,1648,3922.98,152.678,1619.44,3946.48,134.344,1664.23,3883.68,191.339,1672.32,3894.99,181.928,1678.68,3864.78,209.783,1690.56,3878.58,203.791,1678.2,3903.71,173.801,1653.04,3868.09,197.389,1642.73,3851.99,196.356,1665.95,3847.46,212.37,1647.88,3816.6,198.45,1665.18,3795.86,216.848,1675.67,3808.01,228.737,1655.9,3828.72,208.533,1662.47,3939.56,137.133,1626.47,3963.66,116.174,1620.84,3953.73,126.881,1652.69,3929.04,147.15,1675.95,3948.64,117.894,1639.89,3973.84,97.954,1683.07,3948.77,96.6586,1648.39,3975.08,76.0412,1696.27,3919.53,154.507,1707.99,3924.69,136.721,1729.7,3900.35,171.03,1737.48,3903.08,153.556,1714.67,3924.15,116.637,1746.62,3896.8,115.12,1761.32,3885.81,121.461,1764.42,3876.13,101.088,1748.55,3887.96,95.8556,1685.04,3911.32,165.946,1720.26,3894.2,185.919,1686.34,3933.44,56.496,1650.84,3959.37,33.8766,1651.04,3969.4,54.4816,1685.68,3943.04,76.4255,1685.21,3920.56,37.7243,1648.77,3945.95,14.5515,1682.48,3905.02,20.9558,1645.09,3929.8,-2.61357,1719.23,3909.41,77.3717,1718.88,3896.99,58.9346,1746.58,3875.92,70.9068,1716.89,3881.94,42.4027,1717.86,3918.65,96.8834,1672.87,3868.73,-3.67582,1678.32,3887.44,7.03642,1713.35,3864.83,28.6047,1708.41,3846.61,17.8768,1742.68,3842.12,38.4777,1633.86,3892.27,-27.4751,1640.08,3911.6,-16.7472,1666.26,3849.84,-10.8231,1626.58,3872.69,-34.4272,1658.63,3831.7,-14.0497,1618.35,3853.79,-37.2314,1702.23,3828.26,10.5549,1694.96,3810.77,6.97312,1641.21,3801.02,-8.20643,1650.1,3815.25,-12.9979,1686.74,3795.1,7.46737,1678.04,3781.65,11.5112,1716.02,3769.39,15.7865,1706.85,3757.17,20.2748,1600.09,3821.31,-29.8158,1609.32,3836.44,-35.5147,1632.46,3789.55,-0.117823,1591.24,3809.04,-20.6923,1624.3,3781.66,11.2156,1583.39,3800.37,-8.38745,1669.3,3770.85,18.5945,1696.94,3745.76,27.5425,1660.89,3763.23,28.3618,1611.4,3779.66,42.0568,1572.76,3798.62,27.0615,1577.24,3796.78,7.80447,1617.27,3778.43,25.638,1606.69,3784.77,58.9999,1569.92,3804.9,46.6097,1603.26,3792.22,74.6291,1568.28,3813.15,63.2668,1646.08,3759.64,54.4725,1639.75,3763.17,69.4612,1670.65,3735.37,59.0159,1662.87,3737.85,72.5944,1634.33,3769.42,84.5786,1680.32,3725.62,78.9977,1673.68,3728.27,92.3251,1656.11,3742.93,86.6328,1653.16,3759.52,40.5014,1678.5,3734.99,47.6019,1714.01,3738.1,37.6431,1704.23,3729.97,46.9658,1686.71,3737.44,37.9674,1599.13,3806.45,99.9655,1565.32,3827.39,86.971,1566.96,3820.98,75.9941,1601.01,3799.91,87.8857,1597.4,3813.01,113.803,1563.61,3833.69,99.8429,1596.69,3821.53,130.281,1562.66,3842.13,116.009,1627.36,3784.76,113.188,1625.96,3792.16,128.395,1644.42,3760.59,116.515,1642.44,3771.22,132.035,1625.97,3800.86,145.36,1656.94,3749.43,143.291,1656.5,3759.44,160.949,1642.92,3782.72,148.51,1630.08,3777.05,99.0927,1649.13,3750.97,101.807,1695.79,3726.39,55.863,1687.91,3725.24,66.3904,1600.07,3845.9,163.357,1565.61,3868.01,149.712,1563.09,3853.78,133.967,1597.45,3832.61,147.721,1604.69,3860.24,174.356,1570.63,3883.01,160.227,1630.61,3824.01,177.895,1635.55,3837.59,189.304,1643.61,3804.71,184.599,1642.05,3792.89,167.107,1656.05,3770.8,181.836,1659.06,3783.75,201.103,1627.49,3811.52,162.684,1665.73,3733.51,109.099,1660.01,3740.71,125.882,380.908,4702.64,96.8255,374.225,4648.68,125.962,426.683,4636.2,104.032,436.924,4683.94,79.7747,364.215,4596.69,145.658,416.662,4590.18,118.234,324.728,4715.13,120.171,325.547,4653.4,148.694,327.192,4595.33,163.997,384.437,4754.95,52.7494,318.619,4772.42,62.9241,378.451,4790.69,-14.1343,309.128,4796.9,-11.3745,446.553,4730.23,44.1348,453.933,4769.9,-4.11859,405.158,4819.68,-111.745,384.459,4806.37,-69.5291,459.235,4796.55,-60.3676,473.138,4813.15,-109.693,355.769,4828.53,-105.879,328.095,4812.23,-64.6901,431.323,4835.18,-156.491,382.572,4851.09,-150.883,451.018,4847.3,-216.756,401.29,4871.9,-209.832,492.443,4825.42,-159.928,506.047,4831.35,-217.564,437.69,4829.44,-307.396,388.36,4842.05,-312.532,404.85,4859.82,-274.354,445.772,4841.73,-269.615,443.099,4794.67,-356.915,370.244,4827.64,-343.568,435.385,4749.21,-389.822,363.444,4789.87,-388.647,515.809,4793.52,-318.543,508.987,4756.31,-356.293,497.653,4712.28,-382.841,513.066,4821.49,-269.437,413.975,4633.31,-412.392,355.177,4652.63,-424.929,362.566,4722.49,-418.701,425.959,4693.28,-408.811,401.724,4576.85,-404.88,347.183,4587.96,-417.577,393.513,4529.31,-394.487,344.223,4534.12,-408.485,468.675,4610.68,-397.967,453.517,4563.39,-390.17,441.102,4522.77,-379.538,484.057,4662.46,-396.978,391.227,4436.22,-364.768,348.627,4424.54,-386.152,345.339,4481.47,-399.804,390.247,4483.74,-382.685,393.61,4393.98,-336.751,352.505,4369.69,-361.606,396.026,4365.83,-297.885,357.73,4326.19,-322.938,429.844,4443.57,-343.336,429.138,4412.87,-311.619,428.126,4396.8,-272.692,432.836,4482.98,-365.517,399.515,4363.58,-191.09,370.447,4303.89,-192.413,367.474,4308.33,-267.17,399.386,4357.79,-248.518,378.63,4361.97,-117.227,349.167,4297.86,-108.835,376.658,4396.63,-73.0778,357.327,4339.04,-76.1649,419.889,4402.51,-184.625,403.771,4409.85,-128.416,390.377,4427.21,-76.1852,425.755,4395.56,-229.703,382.198,4431.41,-31.9709,368.14,4377.62,3.06022,367.318,4376.73,-53.1122,380.32,4422.04,-53.111,346.821,4299.54,63.067,348.686,4303.04,-47.0578,382.528,4429.35,22.8456,354.9,4369.42,93.3269,376.332,4435.41,87.3,347.425,4396.54,134.33,327.497,4317.25,124.857,391.957,4448.15,-37.7629,397.109,4453.62,-10.8829,402.711,4462.53,32.0733,389.783,4440.51,-53.6128,365.953,4505.88,148.31,373.472,4464.07,122.406,405.803,4479.24,73.4097,407.262,4508.82,105.928,337.26,4501.98,177.814,345.498,4447.51,165.571,359.16,4550.62,155.041,329.956,4549.92,174.381,409.835,4547.58,120.392,170.868,4208.88,320.65,187.241,4271.18,327.201,128.3,4271.28,373.433,115.083,4207.59,366.346,216.544,4211.03,268.007,238.467,4265.81,267.045,255.462,4210.27,214.151,277.463,4269.77,202.357,155.36,4163,325.502,201.474,4162.98,277.253,145.007,4102.02,334.989,190.514,4101.22,286.642,240.949,4156.02,224.219,101.131,4159.1,363.753,92.6334,4099.74,369.825,-72.2852,3224.52,-314.164,-74.6819,3283.79,-289.572,-18.6377,3290.65,-293.279,-7.01422,3231.58,-317.581,-85.6749,3338.09,-264.937,-29.2192,3343.39,-269.556,42.0348,3298.91,-293.045,54.7484,3236.29,-313.824,32.2054,3364.15,-262.242,-135.07,3205.75,-306.508,-132.658,3262.74,-289.113,-135.253,3330.89,-262.03,-75.841,3168.21,-327.319,-137.904,3161.92,-315.066,-87.1991,3117.97,-328.666,-142.877,3125.7,-316.702,-5.00968,3167.84,-331.164,62.7233,3166.75,-325.062,-13.0091,3103.26,-332.369,67.6608,3079.33,-322.959,128.19,3271.52,287.683,132.655,3313.17,276.053,105.586,3284.7,303.352,97.7214,3236.49,316.122,133.722,3356.87,276.609,107.386,3331.78,303.689,72.4875,3256.35,337.777,66.0478,3194,340.367,68.2253,3313.18,344.841,164.765,3302.29,250.035,163.601,3338.74,244.806,198.06,3331.8,213.683,196.578,3363.9,207.616,163.759,3381.22,247.5,198.53,3401.7,208.946,133.351,3220.24,304.581,179.127,3258.15,264.721,156.065,3144.39,315.326,209.715,3180.53,280.929,213.805,3286.4,227.997,247.112,3211.69,240.531,89.2112,3168.81,330.285,50.4947,3117.02,338.948,85.2257,3097.73,333.446,32.6622,3046.78,331.635,265.532,3370.78,132.424,258.343,3405.75,129.472,225.607,3386.47,172.213,229.281,3356.24,177.815,260.659,3441.02,127.599,229.543,3421.79,170.756,292.838,3376.99,71.1893,285.316,3410.86,70.8936,305.759,3373.08,18.1573,296.73,3411.49,18.5525,286.934,3449.71,73.0861,298.287,3453.86,16.6597,289.161,3314.91,134.146,311.726,3318.25,74.753,306.204,3249.69,136.541,322.249,3258.33,78.0274,320.3,3315.69,15.7152,329.718,3258.39,18.3171,250.087,3307.56,186.435,281.365,3233.75,191.594,285.907,3348.9,-95.3447,271.588,3327.55,-142.461,248.299,3388.43,-129.866,268.204,3397.32,-90.2635,242.714,3304.75,-193.951,220.332,3371.76,-177.823,232.789,3420.71,-129.819,262.538,3429.61,-89.9261,200.697,3412.56,-163.749,302.113,3291.31,-98.429,280.306,3260.21,-145.696,300.567,3214.39,-103.624,283.592,3190.88,-134.111,255.928,3243.46,-192.561,267.424,3179.78,-181.197,303.539,3362.69,-44.7166,317.059,3306.67,-42.9026,326.926,3247.86,-41.22,289.848,3407.91,-44.1726,288.656,3446.46,-42.1839,160.586,3265.37,-270.747,111.015,3248.3,-298.291,98.6167,3310.87,-279.5,147.177,3325.71,-255.212,82.2197,3382.32,-244.062,124.963,3393.69,-222.73,174.076,3203.07,-279.125,122.946,3182.34,-308.939,189.656,3136.36,-278.015,137.374,3109.66,-307.096,204.684,3284.85,-235.38,218.467,3224.71,-240.335,233.085,3160.28,-235.961,189.685,3346.47,-220.238,168.6,3403.2,-192.547,-48.2479,3161.93,395.897,-50.8644,3229.83,405.986,-108.917,3223.35,414.617,-108.024,3156.38,404.3,-52.7462,3292.7,410.528,-110.484,3286.88,422.407,20.0397,3163.47,366.864,13.2762,3238.62,381.518,7.26392,3302.23,389.054,-34.3465,3086.57,372.186,20.2141,3086.85,344.673,-30.2584,3023.36,350.893,9.88382,3034.14,333.601,-108.517,3088.73,392.104,-108.157,3022.24,377.102,4.52886,2857.5,319.774,24.5104,2813.18,325.683,62.0517,2856.58,343.007,39.1643,2910.28,335.771,39.2865,2765.99,329.535,77.6164,2802.71,348.104,102.644,2897.18,347.231,76.0988,2954.13,340.378,119.128,2839.15,353.211,-27.6683,2816.85,284.514,-9.29497,2775.6,288.442,-44.6785,2782.43,237.629,-29.7015,2743.18,233.272,5.60759,2731.89,290.257,-15.2583,2701.24,229.947,-11.0418,2891.47,317.371,-40.9154,2843.21,284.321,-23.3851,2911.62,318.408,-51.09,2857.11,286.796,-56.1751,2809.26,243.765,-66.4085,2824.34,249.383,18.1661,2955.75,328.725,45.335,3006.77,332.505,6.50578,2979.57,327.055,-128.054,2883.86,-157.872,-143.788,2893.66,-157.75,-140.438,2925.48,-216.597,-120.221,2914.53,-217.099,-140.348,2961.91,-259.902,-113.663,2945.27,-258.888,-132.805,2863.78,-89.3913,-146.076,2871.91,-88.9105,-134.453,2847.25,-15.1187,-146.912,2854.14,-14.3442,-112.229,2866.67,-156.551,-119.451,2849.06,-88.8847,-96.3289,2841.96,-152.848,-105.934,2826.91,-86.1676,-121.899,2834.34,-15.1995,-109.167,2814.57,-13.5424,-99.56,2895.12,-216.387,-82.9526,2922.51,-257.967,-79.207,2866.29,-212.1,-54.071,2893.33,-253.328,-115.975,3035.82,-311.786,-150.839,3064.53,-307.328,-149.145,3095.72,-313.442,-105.974,3075.45,-322.267,-114.55,2989.38,-293.264,-146.937,3018.08,-293.159,-54.6766,2999.98,-313.315,-68.7285,2956.5,-291.118,-3.7393,2963.33,-303.285,-29.1309,2923.67,-282.63,-35.5513,3047.59,-326.249,26.6088,3012.28,-316.843,-125.461,2815.26,143.01,-142.41,2820.54,145.29,-146.132,2836.13,67.668,-132.841,2830.26,66.5326,-112.553,2816.31,206.495,-134.73,2820.25,209.917,-96.9179,2835.05,260.243,-125.042,2835.82,266.801,-108.499,2805.19,140.674,-93.8845,2808.83,201.972,-91.5133,2789.74,139.235,-78.1272,2797.19,196.874,-78.3394,2831.89,254.426,-119.259,2818.84,66.0254,-105.196,2800.98,67.5591,185,3026.89,319.453,196.075,2967.2,324.501,240.163,2995.8,296.87,228.798,3060.01,290.368,209.889,2900.89,331.616,254.519,2924.57,304.961,280.373,3018.72,259.33,268.241,3086.97,251.788,295.393,2943.14,268.089,134.147,2989.87,337.891,150.277,2933.47,341.768,163.975,2872.11,347.663,174.431,3083.21,316.542,103.827,3046.34,334.891,219.97,3119.48,285.393,258.617,3149.53,245.534,327.441,3120.25,149.592,341.944,3046.09,158.195,361.806,3050.08,99.1679,345.881,3125.96,90.6816,358.962,2963.68,166.858,379.709,2963.62,107.513,373.195,3047.01,37.7329,355.826,3123.8,29.7631,392.177,2960.05,47.7571,301.337,3107.14,204.086,314.542,3035.54,212.36,330.492,2956.39,221.469,315.565,3187.88,142.285,290.668,3172.46,197.056,332.687,3195.35,83.5544,341.291,3194.34,23.161,319.142,3095.64,-102.296,352.791,3113.33,-33.1097,374.297,3036.93,-24.1574,353.544,3021.89,-89.554,396.232,2953.3,-14.7303,388.657,2940,-76.3606,301.745,3157.49,-105.271,336.174,3182.58,-39.9658,301.784,3071.46,-134.89,290.15,3135.75,-131.281,292.293,3047.63,-165.411,279.74,3115.29,-170.578,321.781,3004.18,-136.012,359.766,2926.49,-133.104,307.714,2982.41,-166.051,336.895,2907.96,-171.112,227.865,2994.95,-252.856,266.453,3023.41,-216.548,281.936,2953.86,-208.21,244.484,2926.42,-238.615,307.409,2881.77,-208.075,268.63,2859.5,-235.06,208.845,3066.56,-268.34,249.9,3093.53,-227.733,186.51,2966.16,-271.273,162.946,3036.64,-293.167,145.894,2936.85,-278.699,114.017,3002.73,-303.222,205.558,2902.64,-254.214,227.865,2841.41,-250.485,169.169,2878.29,-261.558,191.817,2816.98,-259.356,-64.4172,2770.88,-136.393,-38.28,2788.77,-190.648,-22.5714,2741.88,-178.456,-49.251,2726.29,-126.097,-1.23812,2814.05,-228.062,14.5665,2765.44,-217.032,-7.16452,2689.26,-167.694,-34.6266,2676.92,-116.22,28.6846,2708.03,-208.437,-80.2106,2809.69,-145.759,-57.5082,2829.37,-202.668,-24.948,2855.59,-241.739,-79.055,2760.37,-72.7463,-92.3429,2796.92,-80.2492,-84.592,2754.47,-2.33212,-96.3497,2787.94,-9.13292,-65.9037,2717.45,-64.7451,-52.7099,2669.5,-55.8932,-71.2292,2712.42,7.96828,-55.2254,2664.56,21.7431,70.8499,2871.59,-268.693,107.381,2904.3,-277.457,132.289,2849.48,-262.836,94.0014,2819.69,-256.516,153.405,2787.22,-260.084,112.219,2758.39,-252.809,38.0061,2919.35,-286.76,72.8569,2959.34,-298.754,35.0671,2841.28,-252.586,6.456,2884.64,-268.138,54.4334,2791.4,-241.846,69.6926,2731.57,-236.531,-59.1637,2743.65,140.216,-41.9992,2709.28,141.331,-36.4675,2721.58,181.613,-51.4235,2755.78,186.3,-24.4376,2667.92,142.247,-20.8757,2680.6,177.237,-74.4707,2744.84,80.7615,-56.5018,2705.86,90.0542,-36.6739,2661.16,99.4509,-74.8717,2769.22,139.353,-90.3426,2776.14,72.8054,-64.4213,2780.33,191.574,319.689,4199.27,95.3766,290.652,4207.39,160.152,273.868,4147.36,176.595,310.621,4140.14,115.59,329.18,4248.44,82.3756,306.644,4263.4,140.605,330.945,4180.98,25.4969,340.181,4236.58,-2.2918,321.863,4131.36,36.6179,-103.448,5188.66,161.658,-89.0834,5200.91,149.668,-87.4549,5205.99,177.594,-102.579,5192.08,188.649,-76.3327,5213.06,139.089,-73.9005,5220.15,167.278,-86.2772,5211.24,202.931,-101.465,5196.54,212.539,-72.6929,5226.61,193.445,-103.121,5183.77,132.621,-90.2952,5193.27,119.639,-100.869,5174.53,103.694,-90.2797,5180.84,91.3231,-79.423,5202.53,108.974,-81.2496,5186.95,81.949,-119.368,5176.68,173.287,-118.369,5173.94,146.108,-135.823,5166.15,182.449,-135.019,5164.21,157.354,-114.767,5167.65,118.081,-131.623,5160.2,131.923,-118.774,5179.22,198.572,-117.58,5183.35,221.433,-135.094,5168.49,206.341,-134.032,5172.29,229.481,-167.194,5152.45,192.29,-151.674,5158.17,188.355,-151.082,5160.33,212.425,-166.848,5154.58,216.866,-150.258,5164.04,236.156,-166.321,5158.37,241.217,-167.14,5150.36,168.952,-151.439,5156.13,164.767,-166.126,5146.76,147.822,-149.314,5152.72,142.087,-182.577,5148.91,194.607,-182.49,5146.74,171.085,-182.059,5142.96,150.459,-182.472,5151.12,219.572,-182.282,5155.04,244.423,-142.478,5510.16,-334.953,-173.185,5505.55,-335.169,-168.532,5549.4,-354.896,-135.017,5549.56,-350.744,-166.99,5587.22,-363.761,-132.084,5585.74,-358.405,-157.33,5464.41,-308.823,-181.999,5454.64,-303.553,-173.404,5424.3,-283.136,-189.543,5416.38,-277.974,-110.431,5514.55,-330.984,-125.484,5476.51,-312.259,-80.6685,5516.98,-321.13,-91.869,5485.89,-308.506,-146.46,5438.06,-290.105,-110.118,5453.56,-292.746,-103.042,5549.09,-342.683,-99.4801,5583.45,-349.135,-74.4174,5547.99,-330.098,-70.4247,5580.86,-335.683,-168.681,5260.09,-279.353,-184.576,5264.55,-277.441,-190.798,5306.52,-262.151,-180.699,5303.51,-263.759,-193.562,5329,-256.069,-186.512,5327.64,-257.001,-155.322,5207.18,-299.68,-176.812,5208.63,-299.185,-147.277,5166.11,-314.828,-171.943,5164.83,-315.451,-148.218,5252.88,-281.096,-131.757,5205.09,-298.665,-122.689,5244.85,-280.127,-106.472,5203.49,-294.05,-121.99,5167.72,-312.017,-96.3218,5169.66,-305.396,-165.795,5297.14,-265.824,-175.87,5324.39,-258.168,-143.102,5287.42,-266.847,-158.011,5318.23,-258.839,-116.237,5000.25,-376.979,-74.116,5010.91,-375.693,-65.289,4982.02,-388.712,-112.279,4967.93,-388.459,-31.4536,5019.69,-369.573,-18.334,4993.01,-383.729,-121.402,5029.16,-365.568,-83.3189,5037.12,-363.392,-127.034,5055.84,-354.665,-92.3844,5061.87,-351.933,-44.4528,5043.96,-356.551,-56.9478,5067.22,-344.702,-155.957,4990.53,-376.019,-158.124,5022.15,-365.369,-160.87,5050.7,-354.92,-154.976,4954.76,-386.114,-36.3432,5519.04,-286.462,-42.6714,5493.9,-278.721,-63.7497,5491.03,-296.236,-56.1179,5518.09,-305.577,-49.8653,5470.66,-270.366,-75.1226,5464.78,-285.604,-20.4898,5520.39,-265.293,-26.5707,5496.51,-258.851,-8.07474,5522.28,-243.139,-13.9822,5499.55,-238.036,-32.7982,5474.57,-251.724,-20.0364,5478.53,-232.061,-30.7406,5546.51,-292.736,-14.9088,5546.52,-270.312,-26.1425,5576.68,-296.669,-10.2586,5575.21,-273.171,-2.67173,5547.06,-246.781,1.872,5574.21,-248.372,-50.4627,5547.02,-313.025,-46.095,5578.57,-317.846,-67.958,5236.99,-259.019,-94.4205,5238.93,-273.204,-112.742,5277.16,-263.121,-80.4459,5270.71,-251.474,-129.274,5309.49,-256.262,-92.9373,5302.09,-246.179,-57.6224,5204.34,-267.529,-80.871,5203.11,-283.798,-47.4558,5173.88,-276.059,-70.9075,5171.76,-293.413,-47.0913,5238.25,-238.407,-38.3054,5206.24,-245.895,-32.0684,5240.37,-213.162,-22.9728,5207.58,-219.613,-26.976,5175.33,-253.687,-10.2856,5175.62,-226.483,-54.5589,5270.21,-232.01,-60.6786,5300.51,-227.441,-38.2431,5272.76,-207.766,-42.0816,5303.5,-204.053,53.1007,5030.98,-337.373,91.0783,5033.59,-312.304,113.309,5012,-328.048,72.496,5007.52,-352.84,122.828,5033.64,-282.133,147.721,5014.15,-297.751,33.7643,5052.24,-323.645,68.8006,5053.34,-298.596,14.8581,5073.2,-311.677,46.8513,5073.3,-286.885,97.8219,5051.91,-268.715,73.1228,5071.07,-257.393,11.447,5026.26,-356.685,-4.80063,5049.01,-343.186,-20.519,5071.07,-331.147,27.8558,5001.15,-371.603,21.9328,5628.05,-189.321,19.7484,5599.77,-194.996,13.8375,5601.89,-220.875,16.0493,5631.78,-216.731,16.6374,5573.35,-198.92,10.5654,5573.73,-223.294,5.20556,5604.06,-247.322,7.35922,5635.54,-244.242,25.8612,5623.82,-163.191,23.759,5597.22,-170.88,28.6054,5618.19,-140.3,26.6886,5593.79,-149.722,20.9058,5572.67,-176.238,24.1865,5571.26,-156.23,23.1966,5657.71,-182.701,27.2788,5652.4,-153.458,23.4441,5688.32,-176.751,27.6516,5683.18,-145.212,30.0715,5644.54,-127.724,31.2624,5676.25,-109.324,17.2067,5662.56,-211.681,8.36501,5667.58,-239.746,17.3174,5693.41,-206.546,-12.9672,5242.68,-152.077,-4.3617,5207.48,-154.43,-11.7091,5207.7,-188.866,-21.0582,5241.63,-184.041,8.43773,5172.85,-158.24,1.53374,5174.51,-194.487,-7.48038,5244.47,-118.361,-0.794761,5207.94,-117.225,-4.28727,5247.97,-84.6201,-0.813842,5212.13,-79.5583,10.3366,5171.59,-118.213,4.745,5172.47,-70.1832,-18.4866,5277.24,-150.749,-11.516,5280.39,-119.366,-21.7974,5309.78,-151.219,-13.946,5313.99,-122.173,-6.20573,5283.37,-87.7246,-6.34533,5321.1,-89.608,-27.2321,5275.01,-180.479,-30.9383,5306.6,-178.651,159.359,5025.16,-210.058,163.633,5017.91,-171.471,193.54,5003.85,-183.755,188.362,5009.83,-223.865,158.414,5009.72,-133.522,188.103,4995.95,-144.04,130.197,5041.13,-198.108,133.549,5033.47,-160.602,101.547,5059.66,-187.781,104.117,5052.26,-150.894,128.581,5025.51,-123.836,99.3387,5044.95,-114.82,145.715,5030.65,-247.523,118.329,5047.58,-234.7,91.3142,5066.24,-223.831,172.938,5013.38,-262.481,123.42,4992.66,-63.4286,96.335,5011.13,-56.9688,71.8588,5004.96,-26.8334,97.0345,4984.32,-31.2964,70.015,5033.35,-49.9393,46.8007,5028.96,-20.8363,41.6891,4999.92,1.26897,66.0462,4976.47,-1.18545,13.981,5026.55,6.50138,144.361,5001.23,-97.5205,115.665,5018.02,-89.2816,87.5475,5038.67,-81.1565,150.771,4976.39,-69.3425,173.065,4986.66,-105.914,122.735,4965.36,-34.4365,89.9654,4953.39,-1.36921,-13.4616,4963.12,48.2604,-38.1536,4995.95,44.5151,-69.4464,4991.33,56.1985,-53.4793,4954.16,63.3239,-59.8466,5026.87,43.3425,-80.9537,5023.73,51.8143,-90.2096,4981.42,64.6137,-82.5764,4941.12,73.3459,-94.6392,5016.1,58.5682,29.248,4969.45,26.2005,2.55062,4997.4,26.2335,-26.5627,5026.94,28.9483,10.4942,4927.53,54.4,52.7966,4940.45,28.8976,-34.2171,4913.65,72.8827,-71.4647,4898.55,84.5603,-122.312,4915.5,90.9931,-125.776,4955.57,83.4001,-155.889,4950,93.1313,-152.997,4910.57,99.7196,-128.084,4992.07,78.4233,-158.24,4984.51,89.4163,-150.308,4868.45,109.312,-102.177,4926.62,81.9376,-106.136,4967.44,73.2683,-108.029,5003.87,67.1026,-118.119,4873.75,101.486,-96.05,4884.45,93.1716,-137.539,5107.5,-334.968,-108.419,5111.49,-331.636,-100.856,5086.45,-341.391,-132.507,5081.71,-344.481,-78.9011,5115.38,-324.166,-68.5316,5090.87,-333.987,-142.163,5134.71,-325.581,-115.148,5137.93,-322.22,-87.9556,5141.25,-314.892,-166.381,5104.27,-335.785,-168.949,5132.14,-326.503,-163.695,5077.71,-345.062,-20.0767,5119.83,-292.442,5.70758,5119.88,-268.715,25.5944,5095.45,-277.11,-3.26899,5095.64,-301.44,26.6462,5118.18,-240.153,49.1284,5093.21,-248.05,-34.989,5145.86,-284.155,-12.0715,5146.52,-261.102,6.54716,5145.74,-233.148,-48.8865,5118.26,-311.027,-60.7396,5143.99,-302.158,-35.3495,5094.03,-320.559,48.8054,5109.66,-171.002,50.5663,5104.35,-133.572,76.1946,5076.04,-142.083,74.1179,5082.69,-178.844,46.6065,5098.94,-97.0172,71.7726,5069.64,-106.194,26.5252,5139.98,-163.952,28.0825,5136.56,-124.882,24.3626,5132.26,-86.0703,40.9254,5114.53,-207.063,19.3867,5143.29,-200.434,65.2124,5088.7,-214.739,19.4809,5089.02,-30.9717,-11.187,5086.74,0.075087,19.7024,5056.76,-12.2868,44.9959,5059.76,-41.8589,-46.437,5085.78,24.3539,-17.4222,5056.3,14.6108,-8.03702,5118.94,-15.3624,-40.0101,5114.73,14.6464,-33.115,5144.8,3.51304,-58.7284,5136.53,26.9159,-65.5147,5110.94,32.7111,-74.2134,5130.02,38.4984,37.0718,5093.52,-63.1915,14.2673,5124.93,-50.2128,-7.99894,5155.6,-30.5225,61.2175,5064.28,-72.8378,-83.3805,5079.84,44.4383,-91.1232,5074.49,48.9925,-87.6995,5051.02,49.5674,-75.1525,5055.08,43.6346,-98.776,5067.87,54.377,-97.0112,5044.26,55.1938,-86.9728,5100.79,45.9123,-93.1627,5094.92,50.2652,-88.8469,5118.14,48.8821,-95.3346,5112.6,54.0465,-101.077,5088.54,56.3472,-104.688,5106.92,61.7722,-70.6077,5083.76,37.822,-79.3796,5106.33,41.2079,-82.7193,5123.94,44.4037,-52.6942,5056.58,33.4108,-133.314,5051.4,76.9267,-163.533,5047.33,89.384,-160.587,5018.41,87.8305,-130.123,5023.99,76.0673,-194.618,4968.03,97.5128,-138.414,5075.51,81.4197,-167.436,5072.47,93.8926,-145.205,5096.9,89.5355,-171.947,5094.18,100.875,-111.053,5059.37,63.2879,-114.846,5081.38,66.675,-120.865,5101.22,74.2737,-109.129,5034.18,63.605,-13.4163,5449.96,-182.439,-17.8902,5430.93,-176.423,-26.0209,5425.99,-195.134,-21.3543,5444.99,-200.933,-21.3827,5410.82,-170.311,-29.7226,5406.05,-189.689,-35.9813,5420.91,-213.909,-31.2444,5439.93,-219.626,-39.8729,5401.1,-209.025,-6.84404,5454.66,-164.739,-11.0617,5435.67,-158.44,-0.967339,5458.93,-148.419,-4.9211,5440.18,-141.832,-14.2884,5415.54,-151.646,-7.89966,5420.34,-134.47,-8.28807,5468.52,-188.062,-1.99398,5473.03,-170.271,-2.8336,5487.21,-192.995,3.1278,5491.29,-174.755,3.57585,5476.99,-153.956,8.32554,5494.76,-158.162,-16.02,5463.72,-206.764,-25.8675,5458.9,-225.802,-10.3075,5482.85,-212.292,-130.767,5661.22,-358.755,-166.897,5663.26,-364.448,-167.549,5701.75,-358.502,-131.383,5699.64,-352.851,-130.968,5622.89,-360.663,-166.679,5624.8,-366.283,-97.0688,5657.95,-349.087,-97.6803,5619.98,-351.081,-67.0295,5653.83,-335.238,-68.0352,5616.55,-337.297,-97.5889,5696.08,-343.31,-67.3594,5691.35,-329.712,-21.2949,5644.55,-295.205,-23.01,5609.8,-297.375,-43.256,5613.12,-319.078,-41.8729,5649.28,-317.002,-4.98612,5639.88,-270.675,-6.96572,5606.74,-273.138,-20.9423,5679.57,-290.517,-4.27146,5673.39,-266.246,-41.892,5685.7,-311.893,-145.151,5941.71,-71.1076,-145.257,5942.66,-31.7907,-113.992,5934.97,-31.4835,-113.852,5934.1,-70.0204,-84.2199,5922.78,-30.2479,-83.9632,5921.94,-67.9036,-177.377,5945.49,-71.5253,-177.504,5946.51,-31.6451,-145.073,5936.43,-108.366,-177.225,5940.14,-109.347,-144.966,5927.44,-143.874,-177.026,5931.06,-145.384,-113.818,5928.92,-106.518,-83.9299,5916.89,-103.576,-113.81,5920.12,-141.299,-84.0234,5908.42,-137.555,-30.7001,5882.4,-60.1689,-31.425,5883.35,-24.2438,-9.86808,5857.24,-20.83,-8.99909,5856.19,-55.8614,7.484,5827.58,-18.0472,8.30165,5826.46,-52.136,-55.9672,5904.51,-64.4015,-56.4532,5905.38,-27.6085,-30.5581,5877.71,-94.3888,-55.8743,5899.62,-99.3199,-30.9002,5870.37,-126.926,-56.076,5891.64,-132.538,-8.88566,5851.74,-89.428,8.23655,5822.31,-85.0776,-9.42987,5845.18,-121.401,7.40176,5816.66,-116.643,28.186,5759.84,-48.0058,32.7567,5726.3,-46.8149,31.3938,5720.91,-78.1626,27.2451,5755.89,-79.7916,35.0876,5695.49,-45.2547,33.5429,5688.74,-75.0366,29.3048,5716.23,-109.968,25.5603,5751.77,-111.197,28.2117,5761.22,-16.0616,33.262,5728.26,-16.1726,27.1291,5758.35,15.9329,32.6848,5725.68,13.9775,35.8852,5697.95,-16.5613,35.6939,5695.67,11.3891,20.365,5793.79,-49.6483,19.8977,5794.95,-16.5722,18.3485,5791.8,17.0101,19.9031,5790.03,-81.9816,18.6618,5785.39,-113.332,53.1513,5557.82,-52.5772,53.0074,5549.71,-55.7082,57.9173,5552.52,-65.6243,57.6809,5560.58,-61.9257,52.7154,5540.9,-58.1823,57.8601,5543.47,-68.5478,61.8344,5555.27,-74.5728,61.2424,5563.36,-70.0968,62.0006,5545.91,-78.0499,48.1808,5554.84,-42.5797,47.7108,5546.81,-45.275,43.3586,5551.63,-32.4763,42.6286,5543.81,-34.7778,47.1874,5538.23,-47.3607,41.8973,5535.51,-36.4883,53.2141,5565.16,-49.2218,48.6527,5562.32,-39.5944,53.3663,5571.84,-46.2456,49.1788,5569.26,-36.6393,44.1003,5558.95,-29.768,44.8683,5565.78,-26.834,57.1788,5567.33,-57.9352,60.1996,5569.66,-65.0931,56.8604,5573.22,-54.8377,59.0686,5573.5,-60.8782,46.8285,5480.02,-51.495,45.6964,5469.87,-47.5401,50.2463,5469.56,-56.2689,51.6413,5480.1,-60.8672,44.9327,5460.11,-43.2313,49.1557,5459.44,-51.3001,53.953,5468.85,-64.3665,55.5978,5479.86,-69.5961,52.5481,5458.29,-58.7308,41.7065,5479.67,-41.751,40.8431,5469.88,-38.4313,36.8224,5479.12,-31.9079,36.2269,5469.66,-29.1943,40.4018,5460.42,-34.7702,36.0822,5460.5,-26.1643,48.1426,5490.4,-54.8942,42.8037,5489.66,-44.5817,49.453,5500.88,-57.5354,43.9472,5499.73,-46.7778,37.6903,5488.77,-34.2047,38.6573,5498.46,-35.9805,53.1585,5490.92,-64.8393,57.3031,5491.17,-74.1159,54.6183,5501.85,-67.9264,58.8896,5502.58,-77.6188,-4.48202,5260.86,-21.3125,-0.601716,5286.91,-27.6236,0.115685,5292.23,0.27892,-5.70084,5269.54,7.00763,2.10189,5310.88,-30.6821,3.94604,5313.12,-4.50124,0.0445098,5300.82,27.5462,-6.38738,5280.92,34.6063,4.65696,5319.23,21.2267,-3.48286,5253.77,-52.0538,-2.55103,5284.78,-57.0905,-1.07203,5312.82,-57.9984,-11.2158,5234.87,-11.5785,-5.23094,5223.23,-43.6032,-22.8704,5211.21,2.10914,-11.212,5196.35,-28.7531,-14.8423,5246.25,15.6284,-15.6968,5260.07,42.3513,-28.73,5223.73,26.2581,-29.0608,5239.05,50.8336,-93.7344,5146.73,64.2251,-103.23,5142.77,73.944,-98.5769,5128.12,61.4089,-90.8631,5132.81,54.4586,-117.521,5138.75,87.3861,-110.211,5123.5,71.8002,-97.3098,5161.19,80.1734,-109.07,5156.69,93.1462,-125.333,5151.59,108.053,-86.3772,5151.09,57.0287,-88.5955,5165.42,70.467,-79.2332,5156.76,50.7059,-80.6465,5170.37,62.6294,-84.4823,5138.02,49.0717,-77.4689,5144.33,43.5756,-159.084,5129.71,114.331,-179.09,5125.97,120.45,-176.08,5112.08,109.721,-152.598,5115.21,100.758,-163.572,5140.09,129.833,-180.987,5136.14,133.761,-137.268,5134.33,102.422,-144.528,5145.7,121.281,-128.786,5119.08,86.3662,28.8102,5579.04,104.176,27.8245,5564.06,110.531,27.6948,5559.5,95.1356,28.8914,5572.74,88.9656,26.2837,5549.69,115.591,25.9003,5546.48,100.044,26.5168,5554.85,81.4435,27.9179,5566.64,75.6621,24.575,5543.13,86.06,26.7584,5585.17,121.683,26.0545,5568.18,127.892,21.7553,5590.05,141.899,21.53,5571.54,147.48,24.9087,5552.62,132.907,20.9547,5555.12,152.198,29.6053,5594.36,95.7302,27.4052,5603.91,113.463,30.6373,5609.2,83.9832,28.6179,5622.51,100.765,21.9668,5611.44,134.809,22.6823,5640.22,123.948,29.834,5585.78,80.8582,29.0866,5578.13,68.1901,30.8685,5598.17,70.1362,30.3309,5588.95,58.4996,-10.1513,5306.9,92.1569,-18.3251,5288.99,100.679,-16.2415,5275.42,71.3903,-7.53912,5294.11,63.2637,-28.3157,5271.07,108.894,-27.8705,5256.45,79.4578,-14.1046,5318.5,120.694,-21.8426,5300.15,128.845,-19.3444,5328.09,149.333,-26.5015,5309.31,156.801,-30.5407,5282.25,136.594,-34.0591,5291.4,163.994,-3.35956,5324.31,83.4248,-7.01755,5337.24,112.131,2.45936,5340.36,73.9162,-0.424325,5354.18,101.378,-12.3557,5347.98,141.622,-4.7784,5371.05,132.648,-0.962988,5311.98,55.2749,4.19397,5328.57,47.2999,9.38354,5473.65,127.653,7.87821,5460.68,125.132,9.9879,5462.3,107.946,10.4578,5474.76,110.624,7.17008,5447.24,121.253,10.2565,5449.46,103.904,11.7795,5463.84,92.3423,11.4104,5475.74,95.0476,12.7755,5451.65,88.3195,7.81324,5472.68,146.142,5.30162,5459.23,143.897,5.37384,5472.08,166.098,2.10608,5458.19,164.23,3.55826,5445.23,140.347,-0.538841,5443.68,161.163,11.566,5486.28,128.792,10.8305,5485.75,147.041,14.3087,5498.67,128.533,14.0905,5498.61,146.55,8.90645,5485.57,166.706,12.3425,5498.84,165.991,11.7004,5486.9,111.939,11.8201,5487.37,96.4573,13.7496,5498.79,111.894,13.1644,5498.72,96.5869,20.9641,5736.84,79.31,15.6983,5721.95,109.822,22.9612,5689.56,102.812,27.5755,5705,73.4518,8.62671,5707.52,138.965,16.0109,5675.37,131.929,28.0224,5660.67,93.688,31.6945,5676.26,66.305,11.4997,5769.38,84.1439,6.09785,5754.04,116.042,-0.688262,5738.56,145.726,24.7437,5749.6,47.8691,15.5666,5782.63,50.8569,30.8024,5717.49,43.846,34.2748,5688.18,38.9413,50.3009,5579.27,-89.0309,49.4147,5588.82,-77.8823,54.4746,5585.89,-77.583,55.6565,5577,-88.33,48.2547,5596.19,-65.268,52.9468,5592.57,-65.4463,58.666,5583.27,-77.1995,60.0649,5574.91,-87.3651,56.8359,5589.38,-65.6849,44.7498,5582.36,-90.507,44.2319,5592.85,-79.0237,39.7592,5586.9,-93.7972,39.673,5598.76,-81.9288,43.5026,5601.15,-65.921,39.4315,5608.38,-68.1728,50.8535,5568.11,-98.3279,44.9689,5570.31,-100.036,51.0168,5555.89,-105.386,44.7987,5557.32,-107.274,39.5701,5573.51,-103.534,38.9877,5559.33,-110.893,56.4629,5566.43,-97.29,61.0354,5564.82,-95.8049,56.8685,5554.74,-104.063,61.5801,5553.56,-102.142,41.2324,5473.71,-98.0574,48.7437,5474.93,-96.0953,46.0669,5460.99,-88.7961,38.373,5459.25,-90.7012,54.6012,5476.06,-93.6477,52.1134,5462.65,-86.5212,43.6461,5447.31,-80.765,35.8719,5444.96,-82.6257,49.8085,5449.57,-78.6757,44.1163,5488.17,-104.253,51.3738,5488.94,-102.252,46.6886,5502.46,-108.849,53.6586,5502.84,-106.855,57.0046,5489.6,-99.6711,59.057,5503.09,-104.209,32.9397,5472.29,-100.641,36.0906,5487.22,-106.797,24.7341,5470.55,-104.955,28.1605,5486.04,-111.012,38.9928,5501.94,-111.328,31.4171,5501.25,-115.433,29.9028,5457.26,-93.3148,27.3453,5442.28,-85.2768,21.5254,5454.86,-97.714,18.9243,5439.02,-89.7402,26.298,5387.36,-24.8532,31.7495,5393.6,-25.0259,29.1463,5392.75,-14.824,24.4898,5386.29,-13.6226,36.5071,5399.54,-25.2835,33.338,5398.95,-15.9181,26.8714,5394.18,-4.79794,23.0835,5387.71,-2.43054,30.3942,5400.4,-6.81934,28.2365,5391.04,-35.8727,34.3897,5396.87,-35.1985,30.0346,5397.43,-46.4312,36.7777,5402.69,-45.1357,39.6157,5402.38,-34.7366,42.3788,5407.64,-44.098,20.7103,5380.19,-25.0113,21.7876,5384.26,-37.1452,15.5457,5371.45,-25.7474,15.677,5375.87,-39.4005,22.8387,5391.25,-48.5139,15.8825,5383.52,-51.911,19.8224,5378.96,-12.4254,19.3371,5380.45,0.302598,15.5967,5370.17,-11.3429,15.9384,5371.91,3.42007,19.0485,5433.56,52.0689,20.3245,5423.23,45.3294,20.9618,5426.92,36.8072,19.7907,5436.46,42.5861,21.2757,5413.56,37.5129,22.049,5418.03,30.0839,21.627,5430.26,29.5798,20.5207,5439.01,34.3515,22.8743,5422.14,23.9163,17.8746,5430.32,63.1414,19.244,5419.14,55.5186,15.8485,5426.75,76.1469,17.2483,5414.65,67.7483,20.1093,5408.64,46.5763,18.1068,5403.2,57.644,17.6109,5444.3,57.72,16.2667,5441.89,69.4691,16.177,5455.2,62.265,14.689,5453.57,74.5242,14.2444,5439.19,82.9626,18.5914,5446.41,47.4169,19.5246,5448.21,38.2645,17.4244,5456.57,51.2929,18.6133,5457.66,41.3552,17.2698,5517.19,67.4165,15.332,5507.4,68.9751,15.9713,5506.01,57.1267,17.3105,5515.33,55.8806,14.0229,5497.43,69.6417,15.1693,5496.5,57.6221,17.0317,5504.7,45.9414,17.9029,5513.65,44.974,16.5949,5495.57,46.3177,17.7278,5519.06,79.7933,15.1837,5508.72,81.6855,18.6312,5520.77,93.2225,15.5963,5509.8,95.4571,13.3053,5498.22,82.5669,19.5364,5526.89,65.041,20.5169,5529.37,77.0039,21.8298,5536.58,61.9236,23.1281,5539.79,73.4331,21.7856,5531.83,90.0379,19.0237,5524.52,53.9232,19.1613,5522.42,43.4253,20.9499,5533.63,51.2937,20.7576,5531.04,41.3028,45.2443,5602.89,-37.2269,43.3065,5602.63,-22.655,46.3786,5597.06,-24.8992,48.9128,5597.9,-38.6546,41.0217,5600.23,-8.287,43.4787,5594.23,-11.5022,48.8354,5592.19,-27.2118,51.8934,5593.53,-40.2162,45.423,5588.98,-14.6611,41.6086,5609.52,-36.2966,40.3036,5609.88,-20.6432,38.7298,5618.81,-36.2289,38.0555,5619.78,-19.0241,38.6654,5607.88,-5.00764,37.0201,5618.05,-1.6565,46.8788,5600.81,-51.5741,42.6514,5606.66,-51.533,39.1526,5615.04,-52.7758,51.0977,5596.49,-52.3204,54.5691,5592.7,-53.1986,7.87896,5347.03,-29.1382,9.47548,5346.34,-9.37954,6.86843,5331.05,-7.55547,4.82841,5330.68,-30.7027,10.6898,5349.39,10.9997,8.0158,5335.35,15.7248,11.3608,5360.51,-27.3044,12.2667,5359.34,-10.4846,13.193,5361.57,6.94256,6.07081,5351.69,-47.8914,10.5361,5365.23,-43.0236,4.41259,5360.9,-64.5621,9.85434,5373.63,-57.1506,1.98598,5334.65,-53.8746,-0.788443,5344.67,-74.4777,2.81255,5392.69,-89.1819,-3.4439,5383.92,-101.655,-3.1587,5405.03,-111.277,3.22878,5411.4,-98.6412,-9.81352,5375.89,-117.477,-9.52011,5398.99,-126.612,-1.66357,5425.34,-119.546,4.69875,5430.29,-107.02,3.26686,5375.21,-78.0745,-2.62611,5362.44,-89.8952,-8.81108,5350.81,-106.286,9.33088,5400.88,-79.5681,9.37515,5385.87,-69.1902,16.4443,5408,-72.6407,16.0528,5394.52,-62.887,9.95173,5417.59,-88.7094,11.4691,5434.94,-97.0359,17.3164,5423.12,-81.488,25.7199,5531.72,-134.044,21.3275,5531.76,-146.353,24.5893,5549.75,-144.355,28.4169,5548.35,-131.773,17.1547,5531.11,-161.481,20.9626,5550.5,-160.061,27.2972,5568.71,-139.884,30.5749,5565.54,-127.006,22.5366,5515.4,-133.993,17.6026,5514.4,-146.094,18.9203,5499.12,-131.802,13.5082,5497.31,-143.799,12.8968,5512.67,-160.759,30.609,5531.27,-124.552,27.9554,5515.89,-124.527,36.2721,5530.7,-117.875,34.1182,5516.11,-117.765,24.8085,5500.37,-122.302,32.7491,5546.67,-122.227,34.3594,5562.24,-117.406,37.8922,5545.09,-115.628,36.716,5648.64,-39.555,36.188,5669.74,-42.5059,36.9191,5672.18,-16.8866,37.1201,5650.71,-17.3011,36.8335,5670.17,8.41507,36.9029,5648.92,5.17119,35.9135,5642.77,-61.0115,34.8845,5663.03,-68.3588,34.9733,5632.81,-80.3942,33.4166,5650.87,-91.5711,37.3314,5631.75,-37.3886,37.1212,5626.92,-55.8758,36.7832,5618.79,-72.796,37.2468,5633.32,-17.9627,36.6984,5631.64,1.77543,11.4534,5365.35,51.7902,14.7614,5375.21,41.4667,15.0652,5385.43,57.2264,10.9903,5376.96,70.8497,17.5025,5383.67,32.3958,18.0845,5392.71,45.7101,14.823,5397.13,71.0907,10.1563,5390.15,87.3246,11.3513,5355.95,31.6152,14.0811,5367.06,24.4825,16.6801,5376.57,18.1494,7.37532,5353.89,62.9899,8.16505,5343.15,39.3033,5.7029,5366.98,86.591,4.00087,5381.96,106.754,21.9973,5397.33,18.8918,23.9853,5403.12,13.9635,22.9938,5409.99,22.4233,21.7359,5404.8,28.6361,25.9548,5408.6,9.92176,24.2878,5414.83,17.3232,22.3501,5391.51,8.47128,25.2148,5397.74,4.84319,27.9615,5403.7,1.83441,19.8757,5390.95,24.9534,19.4708,5384.59,12.8595,20.2039,5399.1,36.2893,31.2991,5444.77,-5.36634,34.0794,5444.14,-12.3921,33.1873,5452.1,-15.063,30.3352,5452.31,-7.34347,37.2935,5443.45,-19.6667,36.5627,5451.78,-22.922,32.496,5460.48,-17.6626,29.5217,5460.36,-9.26132,32.0622,5437.77,-3.3814,34.7929,5436.58,-9.76229,32.2362,5431.27,-1.28551,34.9478,5429.41,-7.28545,37.8979,5435.5,-16.6006,38.0001,5427.89,-13.923,28.8832,5445.22,1.35696,29.698,5438.88,2.36265,26.7512,5445.19,7.81339,27.6488,5439.2,7.63652,29.9121,5433.32,3.793,28.2701,5435.05,7.2412,27.9026,5452.36,0.229731,27.0364,5460.16,-0.957087,25.7826,5452.16,7.64942,24.9205,5459.88,7.25189,31.8409,5513.64,-16.2656,32.7517,5522.13,-16.2243,29.4663,5520.39,-6.03357,28.4988,5512.15,-5.94762,33.7364,5530.23,-15.7808,30.5546,5528.22,-5.89275,26.5941,5519.29,3.93401,25.6015,5511.19,4.20201,27.7632,5526.98,3.72034,35.7549,5515.5,-26.8089,36.596,5524.31,-26.6753,40.3711,5517.54,-37.629,41.1499,5526.71,-37.4269,37.4652,5532.75,-25.9705,31.0174,5504.86,-15.9189,34.9372,5506.41,-26.3992,30.2958,5495.87,-15.1927,34.1367,5497.13,-25.4753,39.545,5508.09,-37.1351,27.6806,5503.6,-5.62648,24.8198,5502.79,4.54228,27.0409,5494.84,-5.06492,24.2815,5494.16,4.97688,35.4845,5589.94,18.2133,38.3483,5595.89,5.44977,36.6234,5603.66,10.1762,34.376,5597.57,24.4408,35.5171,5613.71,15.3841,33.7594,5607.14,31.4932,36.746,5583.67,12.6196,40.1986,5589.66,1.08595,37.8162,5578.22,7.46748,41.6826,5584.22,-3.0392,32.6324,5582.67,29.6604,33.3276,5576.58,22.8537,29.9911,5574.42,39.4482,30.1523,5568.7,31.5434,34.024,5571.3,16.6724,30.5088,5563.76,24.3911,32.1174,5589.94,37.3213,31.9598,5598.76,46.0662,30.045,5581.1,48.3523,34.2226,5634.29,48.3628,35.8414,5643.23,27.2851,35.6902,5663.57,33.3108,33.6856,5653,57.6098,33.9781,5619.19,39.5627,35.5201,5626.75,21.1986,32.3327,5622.73,67.7288,32.3385,5609.48,56.1226,31.0201,5639.09,81.1211,7.83932,5527.19,-199.499,12.9244,5529.46,-179.433,8.16173,5509.96,-177.918,2.61994,5506.63,-196.937,12.5892,5549.29,-200.291,17.2325,5550.22,-178.981,1.10466,5524.68,-220.971,6.22406,5548.11,-223.166,-4.50288,5503.05,-217.187,-186.798,5386.32,-261.421,-188.813,5376.35,-257.538,-195.18,5374.6,-256.541,-194.521,5383.46,-259.733,-189.764,5367.95,-255.316,-195.446,5366.81,-254.64,-173.111,5391.92,-264.246,-178.104,5379.75,-259.117,-146.897,5401.9,-267.339,-157.541,5385.79,-260.708,-180.651,5369.98,-256.236,-163.705,5373.24,-256.885,-182.589,5400.44,-268.585,-163.431,5409.72,-273.615,-130.46,5424.27,-278.034,-193.037,5395.82,-265.635,-68.7772,5427.74,-255.066,-80.2981,5407.18,-249.274,-122.106,5395.73,-259.043,-107.539,5415.69,-265.745,-91.0562,5387.43,-245.313,-132.813,5379.04,-254.784,-44.8937,5434.75,-238.089,-50.8861,5415.33,-232.456,-56.6508,5395.35,-228.178,-58.4129,5448.84,-262.299,-38.8871,5454.24,-244.661,-90.7319,5439.18,-274.92,51.4912,5521.56,-59.9059,52.2084,5531.47,-59.5717,46.5583,5529.12,-48.5174,45.8156,5519.58,-48.7819,56.8003,5523.41,-70.641,57.4807,5533.71,-70.2102,61.1465,5525.07,-80.6265,61.7615,5535.78,-80.0557,50.5705,5511.31,-59.2161,55.8394,5512.73,-69.8699,60.1798,5513.94,-79.8017,44.9476,5509.75,-48.1898,44.7784,5442.18,-34.4861,44.7258,5450.86,-38.772,40.5702,5451.43,-30.9151,41.0099,5442.87,-27.1392,48.15,5441.2,-41.3835,48.5502,5449.9,-46.2218,50.7179,5439.52,-47.4752,51.5625,5448.36,-52.994,44.7922,5434.09,-30.6985,47.6731,5433.58,-37.1382,44.5514,5426.49,-27.8251,47.1626,5426.74,-34.201,49.7411,5432.2,-42.4939,48.7187,5427.37,-38.8851,41.3824,5434.7,-23.7163,41.3493,5426.9,-20.9177,20.7773,5523.4,123.982,23.8209,5536.22,120.151,22.9389,5538.17,137.543,20.3449,5524.54,141.538,19.6903,5539.95,156.701,17.7933,5525.72,160.696,19.9269,5522.18,107.914,23.1623,5534.11,104.368,17.4919,5510.96,126.852,16.6406,5510.48,110.487,17.3286,5511.45,144.628,15.3249,5512.13,163.89,8.14995,5418.83,109.018,7.37692,5433.22,116.034,2.848,5430.51,135.535,2.8189,5414.92,128.959,-2.20061,5428.35,156.962,-3.22048,5411.8,151.294,12.5499,5422.84,91.4259,11.2286,5436.21,98.4969,9.14329,5404.29,99.7479,13.8662,5409.71,82.391,3.12117,5398.34,120.122,-3.9388,5393.64,143.834,49.9469,5530,-111.779,50.731,5543.17,-109.822,56.8454,5542.46,-108.252,56.3394,5529.67,-110.01,61.7036,5541.66,-105.997,61.3569,5529.21,-107.524,42.9884,5530.31,-114.009,44.1496,5543.99,-111.886,48.6154,5516.42,-111.406,41.2806,5516.3,-113.777,55.2956,5516.44,-109.492,60.4911,5516.32,-106.877,32.6713,5418.04,-65.5211,40.1982,5421.85,-63.7121,38.6225,5411.2,-54.63,31.4203,5406.63,-56.2782,46.2767,5425.47,-61.9936,44.5086,5415.51,-53.1887,34.0633,5431.02,-74.2681,41.7817,5434.08,-72.4147,47.9538,5437,-70.4945,24.4894,5413.57,-68.2279,25.631,5427.48,-76.9836,23.6488,5401.25,-58.8074,14.0035,5476.69,68.0388,14.911,5466.02,65.6924,16.3464,5466.72,54.2079,15.5091,5476.79,56.2067,17.7577,5467.2,43.6586,17.065,5476.75,45.2226,12.6144,5476.36,80.9198,13.4051,5465.07,78.3311,13.6432,5487.18,69.3424,12.5154,5487.42,82.3217,15.0655,5486.74,57.3304,16.6411,5486.22,46.0937,25.772,5556.08,53.3547,23.8512,5546.38,58.1394,22.925,5542.71,48.0296,25.051,5551.66,43.8424,22.6433,5539.52,38.6147,24.8662,5547.83,35.0848,26.8026,5561.08,63.8763,25.142,5550.45,69.1947,27.7617,5565.49,47.2354,28.3552,5571.39,57.0669,27.4267,5560.36,38.4428,27.4722,5555.92,30.436,10.9513,5465.75,-121.885,7.40854,5448.36,-114.889,1.14952,5444.44,-127.245,4.88164,5462.62,-134.061,17.4866,5468.39,-112.11,14.1141,5451.86,-104.976,14.9238,5482.62,-127.644,21.1845,5484.55,-118.024,9.13486,5480.14,-139.684,33.2829,5601.5,-109.391,30.9349,5610.24,-122.606,32.1948,5632.06,-109.272,34.1611,5618.49,-96.4277,32.1417,5583.55,-119.565,29.3577,5589,-132.722,36.0806,5593.5,-99.9404,35.4208,5578.16,-109.939,36.4856,5607.35,-87.5219,29.2943,5477.74,-12.7038,29.2061,5468.88,-11.0692,32.3877,5469.31,-20.0803,32.7203,5478.45,-22.239,26.4374,5477.11,-3.26045,26.5638,5468.46,-2.14145,24.0409,5476.66,6.12973,24.3399,5468.1,6.7246,29.6894,5486.79,-14.0997,26.6113,5485.95,-4.25528,24.022,5485.39,5.52639,33.3506,5487.77,-24.063,35.8314,5544.95,-13.6282,34.7826,5537.84,-14.9249,38.3668,5540.71,-24.6668,39.2741,5548.19,-22.813,32.9327,5542.31,-4.95781,31.7334,5535.55,-5.52958,30.4735,5540.78,3.27279,29.0763,5534.17,3.53833,36.8271,5551.52,-11.8636,34.0837,5548.44,-4.19235,37.6575,5557.53,-9.40774,34.8994,5553.75,-2.46643,31.8955,5546.72,2.80724,33.0538,5551.08,2.24668,40.1625,5555.19,-20.4562,41.0042,5561.71,-17.6431,63.8359,5569.5,-81.506,63.3094,5566.41,-76.5644,64.1557,5557.96,-82.1019,64.8305,5560.5,-88.1747,64.5146,5548.18,-86.2855,65.3304,5550.24,-93.1284,62.2971,5576.71,-73.4249,61.9282,5573,-70.0543,60.1492,5581.64,-64.1213,59.9142,5577.25,-62.4977,62.7733,5572.37,-85.0982,61.2421,5580.17,-75.8099,59.18,5585.71,-65.2135,63.807,5562.82,-92.7542,64.3771,5552.05,-98.4547,64.9971,5527.62,-97.0767,63.9304,5526.47,-89.5042,63.0173,5514.88,-88.6674,64.1497,5515.57,-96.2673,61.7114,5503.03,-86.2854,62.8413,5503.23,-93.7324,65.3885,5539.23,-96.0628,64.4343,5537.62,-88.7315,64.1915,5528.53,-103.148,64.5148,5540.58,-101.876,63.3757,5516.04,-102.403,62.0366,5503.23,-99.7709,59.0255,5478.32,-84.1346,58.1521,5479.24,-77.4085,56.2771,5467.66,-71.581,56.9345,5466.14,-77.7729,54.5899,5456.54,-65.2736,55.0003,5454.38,-70.8391,61.0628,5490.75,-89.5712,60.0273,5491.09,-82.4212,57.9347,5477.23,-89.6038,60.1449,5490.23,-95.3828,55.6413,5464.42,-82.8001,53.5001,5451.99,-75.337,51.9613,5433.18,-56.3695,52.0757,5436.71,-52.4071,50.7109,5428.98,-46.2269,50.3273,5424.69,-48.9519,48.9759,5423.37,-39.9594,48.2801,5418.23,-41.3637,53.4311,5443.24,-63.6855,53.2795,5446.06,-58.8188,50.1128,5429.34,-59.5556,51.7462,5440.14,-67.5806,48.3348,5420.1,-51.284,46.1473,5412.9,-42.7922,42.4097,5412.42,-25.6152,43.8391,5419.29,-26.28,40.5721,5419.43,-19.0172,39.0069,5412.28,-17.7371,37.2243,5420.59,-11.8361,35.6471,5413.58,-10.1088,45.5705,5414.27,-33.5383,46.663,5420.37,-33.2854,40.0133,5405.83,-25.3821,43.2822,5408.23,-34.1021,36.6095,5405.45,-16.7966,33.3445,5406.86,-8.5133,29.9354,5419.6,3.60289,31.433,5425.28,1.02186,29.0907,5428.54,6.19538,27.6306,5424.09,9.34306,27.2075,5432.69,10.156,25.7303,5429.51,14.0986,32.6147,5416.06,-2.93775,34.1616,5422.58,-5.072,28.0231,5414.07,6.51886,30.4883,5409.79,-0.697464,25.9314,5419.53,13.0075,24.1936,5425.98,18.6365,23.1205,5443.03,20.3351,24.8246,5444.41,14.0363,23.8704,5451.65,14.9097,22.1791,5450.82,22.2638,23.0501,5459.55,15.3682,21.3961,5459.1,23.6119,24.266,5435.88,17.6886,25.8615,5438.03,12.6105,21.6581,5441.22,27.0209,22.7911,5433.27,23.2767,20.7258,5449.68,29.9644,19.9275,5458.49,32.2007,20.2162,5476.5,25.0385,21.998,5476.48,15.507,21.8253,5485.23,15.3049,19.9258,5485.38,25.2278,21.926,5493.98,15.0013,19.9043,5494.22,25.1416,20.7235,5467.7,24.5187,22.4159,5467.88,15.5477,18.6026,5476.62,34.8885,19.1943,5467.5,33.8251,18.2298,5485.74,35.4413,18.1502,5494.78,35.5353,20.8307,5511.37,24.2848,23.0218,5510.97,14.2349,23.9864,5519.06,13.7205,21.7835,5519.58,23.468,25.2076,5526.78,13.0828,23.0675,5527.48,22.3597,20.2053,5502.9,24.8359,22.3294,5502.58,14.6532,19.1004,5512.29,34.4874,18.4404,5503.63,35.221,20.1287,5520.73,33.3217,21.5237,5528.95,31.7121,26.648,5542.23,18.9043,28.3522,5540.85,11.1396,30.2257,5547.13,9.38587,28.97,5549.12,15.964,32.4285,5552.87,6.26045,31.7022,5555.72,11.682,24.6848,5535.02,20.9376,26.6707,5534.06,12.2973,25.4424,5544.63,26.8297,23.2856,5536.9,29.647,28.0192,5552.12,22.9621,31.0439,5559.39,17.7476,38.4552,5567.98,-1.97405,38.2116,5562.91,-6.03807,41.7733,5567.73,-14.4228,42.274,5573.39,-10.867,45.6755,5572.1,-23.8582,46.2302,5578.01,-20.8377,34.897,5562.04,5.62218,35.093,5558.05,0.987917,38.3539,5573.03,2.56586,34.5445,5566.46,10.8902,42.3091,5578.84,-7.04785,46.2439,5583.61,-17.7728,53.9708,5583.6,-42.8278,53.7782,5577.97,-44.2552,57.1719,5578.71,-53.8205,57.331,5583.78,-53.7839,50.2006,5581.55,-31.6685,49.8112,5575.64,-34.0349,53.4603,5588.78,-41.5443,49.9922,5587.04,-29.4313,56.5552,5588.46,-53.6321,19.6152,5750.01,-171.384,23.3019,5749.91,-141.996,26.6236,5716.39,-141.986,22.5766,5719.46,-173.088,13.5823,5778.83,-171.313,16.7957,5781.59,-143.459,3.35124,5805.47,-173.631,5.91423,5811.04,-146.603,13.6455,5751.82,-198.553,8.30618,5777.32,-195.844,-0.707082,5799.99,-196.398,16.3903,5723.5,-202.146,-32.44,5850.63,-186.285,-33.0121,5837.24,-211.642,-56.3483,5852.95,-220.736,-56.6921,5868.74,-193.825,-83.2538,5865.95,-228.961,-83.9979,5883.22,-200.777,-12.2079,5829.47,-179.087,-14.4724,5819.77,-202.597,-31.6315,5861.51,-157.802,-10.532,5837.85,-151.655,-56.4749,5881.49,-164.232,-84.1506,5897.28,-170.132,-144.341,5900.04,-210.133,-143.496,5881.34,-240.044,-175.649,5884.47,-242.887,-176.32,5903.35,-212.536,-113.39,5893.49,-206.213,-112.501,5875.29,-235.4,-144.777,5915.35,-177.935,-113.747,5908.37,-174.688,-176.751,5918.84,-179.907,-24.3837,5365.22,-158.963,-33.2822,5360.99,-181.256,-32.1798,5384.5,-184.935,-23.5667,5389.05,-164.402,-44.1409,5356.65,-203.122,-42.661,5379.8,-205.342,-23.7783,5338.95,-154.264,-32.9101,5335.14,-179.036,-44.0221,5331.35,-202.606,-16.6713,5369.92,-137.139,-15.7986,5343.58,-128.875,-16.1626,5393.77,-144.639,-102.92,5349.06,-242.657,-141.684,5349.41,-251.963,-139.445,5364.13,-252.57,-99.162,5368.32,-243.227,-167.441,5350.72,-254.444,-166.831,5362.07,-254.956,-101.028,5327.79,-243.505,-138.799,5332.41,-252.967,-165.123,5337.31,-255.434,-64.5372,5351.87,-224.565,-64.3418,5327.56,-225.119,-61.5787,5374.27,-225.568,-189.906,5351.38,-253.778,-195.277,5351.2,-253.227,-195.477,5359.18,-253.556,-190.118,5359.84,-254.106,-188.93,5341.4,-254.488,-194.733,5341.66,-253.842,-181.723,5351.38,-254.363,-180.157,5340.21,-255.214,-181.781,5360.86,-254.753,-58.0114,5175.75,34.01,-49.9777,5162.72,20.8153,-27.3828,5177.38,-3.49288,-40.4154,5191.49,19.3036,-70.543,5164.54,43.6981,-67.2202,5152.08,35.5884,-60.4273,5188.42,47.4589,-71.8312,5177.43,55.2867,-59.4229,5204.87,66.9447,-71.4364,5194.08,74.1172,-45.3955,5204.05,37.7839,-44.7009,5220.19,59.3349,-52.225,5238.91,123.722,-39.8835,5254.23,116.644,-40.2149,5265.24,143.967,-50.6969,5249.28,151.132,-42.1582,5274.26,170.915,-51.0941,5257.82,177.825,-55.934,5223.93,94.1292,-41.7162,5238.98,87.1764,-64.4025,5225.36,130.733,-68.8152,5212.25,100.87,-61.8626,5234.37,158.63,-61.1453,5242.02,185.17,-101.267,3664,-225.436,-94.9283,3725.69,-231.905,-54.8064,3731.15,-236.823,-51.4891,3673.96,-233.707,-11.523,3733.11,-239.263,-5.07369,3679.5,-235.529,-144.639,3650.41,-214.52,-135.418,3715.91,-225.699,-106.027,3608.49,-223.551,-149.366,3599.02,-211.762,-102.382,3555.06,-228.429,-147.971,3550.91,-216.803,-47.8946,3618.05,-233.995,1.64323,3625.58,-235.262,-42.1722,3561.83,-237.178,8.52209,3570.14,-236.975,125.525,3973.72,342.175,117.765,3909.66,341.053,160.953,3915.11,295.401,169.856,3976.84,295.223,77.0074,3968.85,375.084,70.8535,3902.58,373.002,135.231,4037.3,340.656,84.9655,4034.02,374.593,180.102,4038,292.052,226.374,3930.34,-116.835,209.283,3908.1,-147.864,227.894,3958.23,-158.955,242.036,3986.31,-120.003,217.808,3885.81,-114.766,197.673,3868.44,-142.721,251.499,3947.81,-79.2485,247.522,3897.11,-71.1345,257.913,4003.17,-86.3019,227.976,3750.83,140.021,227.551,3801.82,141.226,203.808,3798.96,186.109,203.226,3744.53,183.608,255.124,3752.7,86.1852,253.067,3800.85,86.3297,234.481,3704.01,139.194,263.417,3705.68,83.433,246.548,3654.57,137.2,275.332,3655.68,78.6057,207.124,3695.27,183.834,215.342,3644.87,185.145,93.4353,3693.76,-218.442,90.4309,3742.47,-222.01,136.371,3755.31,-197.481,138.741,3707.07,-197.054,43.673,3685.05,-231.011,38.2609,3735.72,-235.634,98.4538,3643.26,-217.68,50.0297,3633.44,-229.939,104.694,3591.77,-218.434,56.5615,3580.23,-230.852,143.995,3655.43,-197.757,150.728,3603.74,-198.606,-97.7306,3642.61,400.98,-165.685,3590.8,388.219,-166.784,3577.18,386.877,-99.4075,3589.78,398.624,-162.45,3563.65,388.94,-90.6995,3543.56,398.159,-84.7488,3731.67,410.199,-138.032,3714.65,410.16,-32.8021,3667.26,397.126,-28.9578,3744.36,404.205,-31.2806,3601.95,394.013,-22.898,3540.97,394.093,-79.3401,3423.72,-238.645,-87.5992,3381.86,-246.893,-134.008,3367.93,-248.763,-125.479,3418.61,-238.409,-27.3484,3436.03,-238.238,-29.9525,3396.39,-245.385,21.807,3446.68,-234.429,26.5295,3409.04,-239.429,-91.0656,3494.52,-236.054,-33.8398,3498.73,-239.639,15.6216,3508.21,-237.294,-140.489,3497.8,-227.183,135.811,3464.22,290.034,133.674,3406.29,283.455,163.162,3434.39,255.96,163.153,3494.22,261.995,199.26,3455.96,217.861,195.995,3517.27,227.268,101.875,3436.28,325.125,104.393,3380.94,313.129,53.7403,3423.43,368.173,60.0817,3368.21,356.158,136.222,3521.29,293.556,98.1516,3496.79,336.394,46.5399,3482.31,377.747,162.151,3549.07,262.626,190.624,3573.88,229.015,266.773,3547.18,132.101,289.656,3555.53,73.7707,284.311,3605.64,75.2734,259.347,3601.04,134.167,295.829,3556.64,15.0358,291.267,3604.82,15.7766,265.897,3490.99,129.471,290.799,3502.26,73.7312,298.83,3506.71,15.289,233.601,3534.22,181.114,234.792,3475.07,175.075,225.889,3590.99,184.7,268.923,3540.35,-89.6849,240.976,3525.74,-134.81,235.522,3577.75,-134.068,263.731,3589.56,-89.0369,204.642,3504.25,-171.098,199.554,3564.67,-171.59,269.463,3481.31,-90.4405,235.586,3454.52,-131.589,199.501,3435.39,-163.096,287.563,3550.64,-39.1583,291.093,3500.03,-40.3217,282.812,3599.09,-38.527,114.508,3468.67,-216.35,66.8144,3455.86,-227.613,62.1378,3520.76,-231.039,110.491,3535.87,-219.204,116.652,3423.26,-213.5,73.2831,3417.16,-228.62,163.292,3485.38,-196.935,163.847,3428.11,-188.671,157.419,3550.64,-199.449,-61.6429,3412.03,410.669,-55.6976,3351.73,411.703,2.44101,3359.25,391.574,-3.31452,3416.68,394.348,-118.386,3409.51,420.733,-112.624,3346.21,424.82,-73.6655,3478.21,404.641,-132.731,3483.59,408.395,-11.861,3477.73,395.508,452.271,1657.75,183.576,454.212,1597.86,176.036,470.448,1615.62,166.248,467.782,1674.93,169.285,461.864,1530.91,168.63,485.224,1539.67,160.25,483.595,1632.06,157.139,475.978,1688.52,160.715,512.898,1548.19,145.877,420.464,1643.21,204.321,430.5,1583.06,186.684,379.002,1634.08,215.097,397.097,1571.98,193.981,438.529,1521.33,175.486,409.622,1511.12,179.339,443.315,1713.22,202.727,399.033,1705.98,236.137,421.59,1776.95,239.277,382.865,1771.17,260.372,361.717,1699.56,239.264,351.251,1764.27,260.157,465.584,1723.09,174.639,473.461,1731.58,163.357,457.882,1776.17,190.177,469.788,1777.22,167.764,489.959,1698.64,146.761,481.914,1695.34,154.37,497.533,1643.35,145.944,521.735,1645.94,125.667,543.048,1559.54,123.147,566.794,1572.91,94.5156,482.708,1735.79,152.126,477.929,1734.78,157.452,479.328,1776.62,153.65,474.711,1778.78,159.217,514.883,1698.83,123.999,494.581,1737.15,140.992,556.736,1698.56,62.9562,541.505,1740.42,85.7251,489.116,1773.94,145.147,529.604,1780.71,103.403,557.212,1645.47,86.7635,582.008,1586.12,62.0353,572.127,1652.02,42.6181,587.639,1598,23.7495,540.796,1707.97,-22.3664,555.758,1703.6,13.593,569.031,1657.43,1.53458,558.3,1660.15,-32.573,583.104,1604.25,-15.4656,573.861,1605.28,-49.7141,531.887,1737.95,-16.5291,549.242,1740.41,24.0718,532.926,1764.69,-8.59895,549.51,1777.19,36.7554,522.99,1713.14,-54.2292,513.314,1737.06,-48.4778,504.324,1713.98,-83.4024,496.69,1734.77,-75.0219,512.856,1754.63,-44.0993,495.334,1748.96,-71.2638,546.107,1663.27,-65.9395,563.765,1606.23,-82.9406,527.589,1665.46,-101.483,547.366,1607.79,-119.441,466.929,1702.75,-135.131,486.191,1710.03,-110.484,504.476,1665.76,-132.608,479.849,1663.09,-156.068,524.266,1609.34,-152.426,496.675,1609.66,-176.713,462.6,1723.75,-123.853,480.676,1730.38,-99.5523,461.26,1739.54,-119.581,479.346,1744.35,-95.1778,442.454,1694.41,-157.75,438.09,1716.29,-148.842,408.6,1686.95,-174.199,401.897,1709.87,-167.896,435.336,1735.23,-146.186,396.306,1730.96,-165.685,451.817,1658.67,-174.604,464.861,1609.03,-194.026,417.587,1653.75,-186.933,428.54,1606.89,-204.657,322.168,1626.23,190.54,334.821,1560.26,176.203,362.858,1565.1,190.498,346.346,1629.17,207.858,346.943,1494.13,159.471,377.602,1501.81,174.536,302.693,1624.22,167.829,311.235,1554.42,153.523,283.43,1619.94,138.728,288.081,1547.61,120.739,319.791,1487.7,134.773,294.358,1482.41,101.035,308.724,1692.32,202.84,290.878,1695.02,173.312,296.26,1754.21,212.857,279.316,1758.84,170.361,278.365,1697.75,144.71,270.744,1767,139.342,332.607,1694.39,226.252,321.544,1757.81,244.718,237.491,1634.2,-119.361,231.3,1582.85,-130.86,222.074,1561.38,-103.059,225.413,1613.34,-92.1221,228.356,1531.82,-143.197,221,1511.59,-114.849,222.428,1547.35,-66.827,222.709,1599.34,-52.8985,222.334,1495,-79.4576,255.532,1655.64,-135.629,249.603,1609.07,-149.468,275.632,1671.48,-145.349,273.489,1631.98,-160.827,244.775,1553.28,-165.421,270.566,1574.59,-181.61,242.053,1674.08,-110.523,258.584,1686.72,-126.266,241.39,1702.95,-103.924,257.425,1708.5,-120.028,275.746,1695.39,-136.152,273.712,1712.44,-130.637,228.737,1660.15,-83.2477,223.612,1650.92,-40.7904,226.957,1697,-75.7343,220.432,1694.65,-30.1562,326.739,1681.47,-167.101,365.973,1682.81,-176.84,376.179,1650.36,-189.229,335.092,1648.76,-181.662,386.811,1603.54,-207.478,343.927,1598.86,-202.942,320.563,1702.54,-159.004,357.57,1705.51,-169.904,316.01,1719.68,-154.379,351.209,1725.25,-166.05,297.983,1679.04,-155.017,295.093,1699.65,-146.419,291.746,1715.66,-141.844,301.331,1644.59,-170.731,304.481,1590.14,-193.735,243.99,1603.12,44.184,245.71,1539.75,27.4915,265.04,1541.74,76.3193,262.578,1611.12,95.3848,248.676,1480.18,11.3979,269.985,1479.65,58.4323,230.676,1598.46,-4.98901,231.036,1540.9,-21.4684,232.174,1484.9,-35.9747,246.092,1667.4,62.722,230.841,1653.4,10.2382,250.333,1737.26,84.7544,229.635,1705.58,26.1595,263.803,1687.84,111.592,263.52,1763.1,117.58,769.481,255.502,-130.822,785.502,263.115,-102.24,808.519,208.977,-90.8783,788.998,203.026,-122.593,796.263,276.319,-73.3873,821.761,222.716,-59.0337,828.274,157.055,-83.3139,805.569,153.165,-118.102,852.471,164.813,-40.7806,749.589,309.514,-140.991,763.654,317.802,-113.71,731.037,364.371,-152.086,743.966,372.935,-124.736,773.133,330.204,-85.9403,752.764,384.563,-96.6461,751.147,251.604,-159.198,732.759,304.741,-167.886,732.397,250.491,-187.977,714.986,302.883,-194.496,715.465,358.793,-178.376,698.733,356.118,-203.289,768.755,200.235,-153.336,783.709,151.489,-151.322,749.127,199.778,-184.45,763.338,151.578,-184.633,697.203,253.237,-245.947,713.075,202.911,-248.746,691.344,205.56,-275.777,676.51,255.899,-269.883,726.025,155.298,-253.233,703.138,158.091,-281.973,663.606,208.304,-295.295,650.922,258.624,-286.952,673.468,160.946,-302.64,715.124,251.235,-217.769,731.451,200.827,-217.279,745.101,152.988,-219.695,680.539,305.338,-245.16,698.091,303.339,-220.924,665.212,358.276,-246.714,682.33,356.268,-226.504,660.789,308.107,-265.193,637.308,310.873,-279.013,646.336,361.182,-262.611,624.658,364.022,-272.886,505.825,253.173,-205.405,510.931,256.484,-238.025,508.361,208.985,-245.967,504.595,206.774,-211.891,524.993,260.299,-264.354,522.724,211.823,-273.438,506.907,163.365,-251.704,504.437,161.889,-216.935,521.295,165.554,-279.806,506.612,301.399,-198.746,513.047,305.779,-229.51,505.445,351.767,-193.188,513.138,356.784,-222.053,526.782,310.535,-254.435,526.769,362.09,-245.559,506.997,252.947,-168.062,505.61,300.009,-163.574,511.774,258.385,-127.571,508.174,304.215,-125.427,502.565,349.56,-160.184,503.374,352.679,-124.265,508.01,207.677,-172.814,509.934,163.502,-176.992,515.193,214.18,-130.338,519.45,170.58,-133.369,583.347,262.075,-293.012,618.313,260.811,-294.525,627.208,210.674,-304.225,587.815,212.297,-303.162,634.043,163.448,-311.96,591.269,165.268,-310.731,578.146,313.713,-282.222,608.557,312.862,-284.611,572.495,366.314,-272.735,599.135,365.833,-276.232,550.686,262.033,-282.818,549.685,313.058,-272.083,547.463,365.167,-262.481,551.098,212.804,-292.701,551.55,166.079,-299.748,524.764,291.216,-44.0329,531.309,248.689,-41.0696,541.645,271.197,3.56828,534.285,313.026,-5.34785,539.171,205.296,-39.6446,550.359,227.012,10.2985,554.605,295.2,41.6971,546.74,335.129,27.1029,565.102,248.877,66.7003,517.477,272.072,-85.5002,522.725,228.767,-86.0634,529.031,185.498,-87.5603,518.867,334.413,-47.017,512.435,316.628,-85.7398,512.889,379.679,-49.8208,506.744,363.647,-86.6527,527.942,354.731,-11.7629,540.134,374.748,17.5184,522.022,397.993,-16.2434,534.356,415.805,11.6051,617.167,751.71,31.641,591.797,750.384,41.7443,605.103,693.402,42.5042,629.004,695.268,32.206,565.644,748.942,45.9461,580.156,690.637,47.3397,615.799,639.308,42.7624,639.522,641.064,32.844,591.265,636.198,47.3768,604.201,808.8,32.6065,575.967,807.56,41.386,592.695,865.35,37.2005,561.881,862.125,44.538,547.786,807.115,42.9889,530.933,860.13,42.9671,639.957,752.42,15.7857,629.346,809.948,16.8957,658.872,751.851,-5.41249,649.829,809.956,-4.66704,619.723,867.721,21.5703,641.518,868.205,-0.47648,650.547,695.836,16.584,660.992,641.124,17.6986,668.554,694.659,-4.22205,678.763,639.146,-2.60204,683.01,745.529,-61.9526,673.186,749.439,-31.6659,681.761,691.397,-30.1064,690.303,686.416,-60.0122,691.395,634.788,-27.9818,699.11,628.493,-57.3291,677.123,805.157,-62.0695,665.641,808.205,-31.3394,672.537,864.624,-59.2833,659.1,866.993,-27.8075,688.37,740.5,-95.2576,684.125,801.233,-95.8402,689.295,734.72,-130.562,686.412,796.907,-131.784,681.435,861.555,-93.7904,682.683,860.418,-133.697,694.319,680.083,-92.8822,702.133,620.704,-89.5331,693.948,672.762,-127.661,700.685,611.866,-123.48,677.236,724.44,-202.43,685.636,728.76,-167.042,689.325,664.82,-163.288,680.956,657.376,-197.582,694.99,602.421,-158.06,685.803,593.541,-191.255,666.626,798.067,-208.65,680.904,794.94,-171.706,643.355,874.987,-212.565,665.953,867.644,-180.434,665.231,721.719,-233.464,650.094,802.186,-235.409,649.974,720.992,-258.532,635.122,804.199,-254.278,630.428,875.145,-229.283,621.158,872.957,-243.113,669.346,651.544,-228.356,673.879,586.399,-221.052,654.993,648.45,-253.427,659.973,582.166,-245.432,610.271,727.367,-286.856,599.347,808.726,-281.902,618.966,806.369,-269.927,631.573,723.329,-275.978,593.119,869.575,-273.78,609.04,871.29,-257.839,586.338,731.485,-292.145,576.766,809.906,-290.653,559.878,734.139,-292.661,549.08,809.2,-296.551,570.516,869.55,-291.328,539.048,871.147,-302.874,619.495,652.356,-280.92,598.168,656.399,-285.373,628.039,584.474,-272.815,609.128,588.074,-277.645,574.332,659.873,-284.988,587.666,591.349,-277.787,638.406,649.215,-270.61,644.84,582.014,-262.382,517.148,744.262,38.4215,501.678,808.65,32.1841,481.325,806.314,22.8454,494.648,738.651,26.4298,483.408,865.019,25.1757,465.571,868.076,15.0634,459.516,798.475,7.35009,472.793,730.617,6.90794,446.885,867.163,3.21968,540.593,747.315,44.7249,522.885,807.908,39.0405,504.37,861.271,35.0098,531.922,682.613,40.0543,555.577,687.158,46.6937,544.554,626.799,39.8523,567.362,632.073,46.6143,509.613,676.743,26.7946,489.159,669.631,6.15825,523.307,620.235,26.4694,504.085,612.245,5.84406,423.89,715.322,-196.71,434.516,721.339,-224.216,456.849,650.615,-220.125,445.886,646.02,-193.448,451.028,726.583,-247.625,473.35,655.325,-242.422,476.143,584.27,-216.62,465.247,580.581,-190.911,492.231,588.23,-237.981,403.171,785.58,-199.479,412.888,793.069,-225.767,382.509,848.713,-203.519,391.378,857.077,-228.772,427.408,798.276,-250.472,404.908,862.296,-253.178,418.86,709.943,-165.231,397.789,777.173,-168.312,419.461,706.958,-130.107,397.783,771.713,-132.42,377.553,838.73,-172.753,376.87,830.884,-135.685,440.465,642.924,-162.958,459.594,578.531,-161.326,440.593,642.654,-129.214,459.234,579.49,-128.343,501.521,732.591,-280.352,481.399,803.813,-288.4,516.269,806.71,-296.469,531.017,734.193,-288.951,459.971,870.278,-297.857,499.667,871.76,-306.491,473.997,730.046,-266.5,450.538,801.365,-272.116,427.857,866.466,-278.083,520.906,660.882,-272.468,495.378,658.823,-259.775,537.799,592.684,-266.484,513.461,591.093,-254.514,547.901,661.294,-280.78,563.212,592.827,-274.153,437.134,714.398,-55.7432,415.99,774.955,-55.1779,403.985,771.383,-93.5158,425.825,708.93,-92.6445,398.212,840.298,-51.5887,382.189,829.757,-93.6489,452.816,721.648,-21.6301,435.726,785.048,-19.2728,424.137,857.65,-16.7547,456.76,653.299,-56.3061,471.301,661.439,-22.4439,473.827,593.055,-56.3076,487.354,602.69,-22.6483,446.273,646.576,-92.7798,464.215,584.827,-92.4382,701.395,388.969,62.6752,667.492,392.655,70.5441,679.079,364.958,89.2969,714.477,360.061,81.4217,634.939,390.927,73.6099,645.29,363.455,92.3095,690.683,339.082,111.887,726.904,333.483,103.668,656.274,337.644,115.438,688.489,420.947,47.9827,656.517,423.198,56.0739,676.586,456.735,37.9007,646.746,457.609,46.3335,625.584,421.153,59.5241,617.588,455.22,50.2359,733.646,379.787,49.9785,718.633,414.15,35.2244,761.237,365.034,32.432,744.082,402.562,17.7737,704.473,452.224,24.9076,727.772,443.7,7.32116,748.474,348.852,68.6352,762.081,321.061,90.6579,778.056,331.42,50.8877,793.358,302.028,72.7373,793.625,321.107,-15.9746,815.172,279.797,1.49305,823.167,248.869,-27.903,798.813,296.994,-44.2023,835.873,245.49,22.9727,847.547,209.415,-6.51687,781.164,344.632,10.0092,800.212,307.859,28.1326,817.883,276.599,49.783,772.578,366.709,-29.9559,761.968,385.937,-4.3955,753.106,415.373,-40.4609,743.85,430.789,-14.8886,776.205,347.318,-57.5802,755.944,399.337,-68.1344,582.754,371.814,64.3408,562.825,355.148,49.8706,571.063,319.607,67.1635,591.431,340.876,82.5686,581.123,287.527,93.5219,601.476,312.44,107.577,575.362,405.139,51.6143,555.913,391.627,38.3225,569.242,441.683,43.6215,550.105,430.336,31.2513,606.743,383.86,71.8983,598.56,415.057,58.3595,591.746,449.945,49.641,616.124,355.459,90.5082,626.532,328.954,114.442,402.607,1897.03,265.347,392.245,1965.62,258.859,353.685,1960.56,271.891,364.186,1891.28,278.267,383.007,2013.99,259.58,348.494,2009.04,269.716,321.244,1954.72,270.787,331.422,1887.39,277.006,317.052,2000.78,268.16,439.345,1905.29,227.298,427.57,1965.35,230.937,459.041,1902.39,189.042,450.213,1957.12,200.42,415.118,2012.45,239.844,442.831,2004.87,213.562,409.508,1836.51,259.08,446.71,1842.73,216.149,464.697,1837.24,177.035,374.021,1829.81,272.437,342.393,1823.03,273.05,474.85,1886.21,162.498,468.13,1894.71,171.819,471.862,1834.86,164.259,477.588,1829.27,156.765,471.466,1937.78,171.264,462.491,1947.63,182.144,472.498,1991.93,179.339,460.593,1998.27,192.587,486.82,1875.31,151.608,487.273,1925.03,157.912,527.452,1869.78,116.729,532.797,1917.73,119.404,491.638,1982.93,163.581,539.278,1975.34,120.844,488.159,1821.34,147.375,526.533,1823.63,111.591,546.964,1852.46,9.55015,553.984,1863.64,56.6192,552.266,1819.44,48.7416,540.687,1804.33,1.74025,549.124,1899.51,12.4351,555.776,1910.97,60.8934,549.699,1952.49,13.0678,556.77,1965.19,62.878,530.071,1835.76,-35.1364,534.652,1888.15,-34.0366,507.39,1815.73,-72.3241,514.004,1876.82,-75.9642,535.157,1940.16,-35.0987,514.403,1928.73,-78.2115,519.5,1783.85,-39.1903,498.641,1769.31,-69.8944,460.515,1799.26,-127.923,484.819,1802.9,-101.546,480.821,1762.43,-94.9583,461.068,1759.7,-120.131,459.695,1857.26,-136.453,488.782,1866.01,-109.949,458.389,1909.78,-139.106,488.701,1918.71,-112.845,429.385,1799.29,-151.62,426.412,1850.05,-155.659,390.529,1795.35,-166.064,388.98,1842,-166.402,423.81,1901.58,-157.104,385.307,1893.81,-166.95,432.982,1760.03,-146.672,392.653,1757.54,-164.809,274.526,1874.74,229.562,269.563,1939.81,232.327,248.925,1917.44,182.29,257.457,1862.44,168.337,266.752,1982.77,237.495,245.306,1967.58,202.402,243.459,1896.63,142.217,253.307,1856.02,134.576,233.312,1944.32,161.384,302.353,1882.56,266.27,294.43,1949.57,259.02,290.606,1992.19,257.267,285.071,1810.29,221.79,312.577,1816.32,260.945,268.252,1812.27,166.092,262.166,1816.63,134.209,213.991,1764.35,-92.2843,187.564,1817.5,-86.3771,211.845,1809.55,-112.812,235.898,1758.71,-113.613,166.121,1877.98,-79.8933,189.322,1871.87,-111.533,240.568,1804.84,-130.694,257.855,1756.81,-127.6,220.926,1870.01,-133.528,196.569,1773.49,-57.8912,174.339,1829.14,-46.1298,196.243,1789.47,-3.93009,183.956,1846.02,11.2954,156.707,1888.46,-36.2994,169.007,1901.72,19.1922,232.992,1729.07,-97.7293,216.521,1730.9,-67.8418,210.188,1737.64,-19.0841,251.019,1728.98,-115.342,268.108,1730.22,-127.629,309.85,1768.98,-153.777,347.258,1782.96,-164.932,347.79,1748.93,-164.121,312.412,1739.4,-152.337,306.849,1816.79,-158.595,347.926,1831.09,-167.335,300.062,1878.78,-163.179,343.208,1885.96,-168.72,281.157,1759.8,-140.416,271.57,1806.83,-145.464,258.453,1872.67,-150.962,287.409,1733.17,-139.398,245.613,1840.76,102.8,236.007,1875.4,104.954,220.087,1865.4,76.5217,227.804,1820.9,68.5525,220.107,1915.01,107.176,202.366,1908.73,75.3734,250.743,1849.6,117.708,241.198,1884.11,121.286,227.779,1925.15,129.999,250.826,1799.48,98.3312,258.249,1813.49,117.083,228.76,1764.4,48.8867,492.098,1414.12,155.659,476.632,1471.02,162.774,448.691,1463.86,169.384,461.387,1407.34,163.096,419.158,1454.45,170.284,430.173,1399.81,162.77,522.327,1420.93,141.07,505.921,1476.82,150.57,551.064,1429.34,119.877,536.173,1484.61,131.502,504.808,1363.05,148.313,534.495,1369.56,132.703,516.521,1312.83,140.722,545.755,1318.25,124.358,562.917,1376.34,110.069,574.119,1323.02,100.435,474.092,1356.5,156.211,442.604,1349.62,155.707,486.311,1306.72,148.977,455.014,1299.87,148.574,598.821,1451.3,61.695,576.98,1440.08,93.0981,589.823,1383.68,81.0973,613.157,1390.8,47.1668,601.725,1327.2,69.5029,626.193,1330.54,33.3164,583.917,1511.39,77.0935,563.344,1496.95,106.338,614.195,1461.72,24.4005,598.385,1524.88,43.4019,618.506,1468.91,-18.8431,603.453,1535.17,3.44115,629.78,1397.32,6.75532,643.218,1332.81,-11.6767,629.48,1397.47,-46.7442,632.549,1330.39,-77.5509,603.604,1465.46,-89.3571,611.624,1468.7,-58.8725,615.568,1387.03,-89.7242,607.103,1379.22,-110.856,617.706,1322.16,-111.668,612.689,1315.8,-126.03,589.329,1540.06,-69.124,598.469,1539.3,-36.1921,594.985,1465.59,-118.986,579.386,1542.16,-100.954,579.832,1472.77,-157.26,562.996,1545.49,-138.056,602.826,1378.27,-129.154,609.898,1313.32,-140.094,594.928,1389.92,-164.119,605.921,1318.44,-171.288,524.919,1481.05,-221.909,555.041,1478.64,-194.781,570.864,1404.41,-215.596,538.886,1408.21,-246.673,584.884,1331.67,-234.337,551.441,1336.33,-268.949,510.552,1549.82,-197.629,539.173,1548.43,-172.372,489.914,1482.11,-241.318,477.293,1550.07,-215.749,449.283,1481.24,-253.2,438.862,1548.67,-226.806,502.166,1409.86,-267.523,513.327,1338.44,-290.887,459.499,1409.38,-280.318,469.103,1338.29,-304.337,367.81,1384.33,135.756,357.348,1436.29,145.849,328.725,1430.16,120.252,337.649,1377.79,109.68,301.362,1425.68,86.4903,308.426,1372.54,75.7206,398.771,1391.77,153.599,387.982,1444.52,162.739,378.505,1334.47,127.895,410.587,1342.11,146.118,389.507,1284.23,120.671,422.523,1292.23,138.963,346.819,1327.23,101.493,315.99,1320.92,67.3638,356.641,1276.34,94.2269,324.594,1269.01,60.1584,226.1,1411.16,-173.869,242.626,1429.53,-201.379,243.716,1360.59,-220.283,227.229,1344.92,-189.364,269.95,1446.68,-223.647,271.703,1375.76,-245.726,247.458,1292.49,-236.683,231.148,1279.3,-202.709,275.743,1305.82,-264.978,226.799,1474.6,-157.915,243.241,1495.05,-182.341,269.642,1513.69,-201.725,219.819,1393.89,-140.949,219.957,1454.98,-127.807,223.234,1380.04,-102.452,222.359,1438.86,-91.3769,221.503,1330.71,-153.276,225.93,1267.89,-163.791,225.805,1319.94,-112.332,230.922,1259.94,-120.664,354.292,1470.47,-252.084,349.443,1538.06,-226.447,394.926,1544.84,-230.089,402.628,1477.41,-256.495,308.619,1460.28,-240.841,306.36,1527.85,-216.71,359.727,1398.44,-278.316,311.923,1388.45,-265.38,366.046,1327.32,-301.355,316.884,1317.61,-286.857,410.47,1405.49,-283.606,418.397,1334.4,-307.504,255.506,1368.65,-11.5516,251.876,1424.03,-1.377,233.649,1428.88,-47.9845,235.793,1371.95,-58.2115,280.381,1369.17,34.2015,275.096,1423.3,44.6973,260.286,1313.59,-19.7581,286.482,1316.04,25.9618,266.937,1258.36,-26.6299,294.04,1262.67,18.9914,239.396,1314.56,-66.841,245.244,1257.11,-74.0626,993.758,50.5841,160.198,988.701,75.807,164.231,998.805,71.5968,203.456,1003.75,49.4064,199.413,975.867,101.603,175.718,986.51,94.4571,214.764,1005.55,69.3475,245.379,1009.98,49.6912,241.502,994.114,89.6534,256.298,979.626,52.5996,123.382,975.276,81.1449,127.191,961.755,55.3412,88.1953,958.564,86.7746,91.8258,962.348,109.987,138.729,946.117,118.503,103.368,991.016,28.2547,164.412,974.478,26.6079,128.458,981.361,11.6,177.901,962.544,7.26045,144.524,953.666,26.4675,93.8127,928.314,1.39773,113.693,1001.58,30.02,203.166,1007.56,32.5497,245.033,992.525,15.5705,215.248,998.443,19.7934,256.331,629.425,80.9936,348.646,618.883,86.2732,308.543,627.513,49.3957,304.266,634.515,48.9243,346.123,611.314,95.1884,264.585,624.245,54.3343,258.071,645.416,19.7534,301.814,648.142,23.1585,343.618,656.295,14.964,247.526,632.708,114.253,349.724,620.23,123.31,310.588,643.615,144.357,348.822,629.916,156.511,310.063,609.998,135.412,267.924,617.885,171.303,268.23,644.006,78.2472,385.671,648.181,108.127,386.258,662.962,77.7542,420.793,667.395,104.822,421.116,659.754,135.1,385.273,679.109,129,420.182,647.541,49.6802,383.852,659.096,26.644,381.134,665.974,51.7048,419.246,676.595,30.5878,416.508,684.57,182.969,339.698,661.396,166.962,345.406,678.415,154.949,382.385,702.457,168.654,377.542,697.94,146.376,417.961,722.066,158,414.149,668.164,200.21,300.685,646.303,181.878,306.626,653.002,219.64,260.568,632.566,199.164,265.649,711.657,193.28,331.922,694.268,212.3,292.638,741.176,198.796,322.298,723.384,218.946,282.889,678.154,233.374,253.378,706.984,241.008,244.468,730.173,177.194,370.698,749.666,164.921,408.438,759.855,181.548,361.806,778.918,168.188,400.527,595.687,83.6428,-311.96,593.427,121.882,-313.777,637.625,120.132,-315.982,638.764,82.5885,-315.308,678.613,117.581,-307.581,679.055,80.5706,-308.645,556.279,84.4611,-299.757,552.675,122.57,-301.974,527.004,83.9179,-279.794,522.027,121.933,-281.581,599.449,52.054,-304.942,564.171,53.9365,-293.473,606.065,27.393,-292.27,573.277,30.6275,-281.607,538.311,54.4586,-275.478,552.413,36.8135,-270.577,638.274,52.6811,-308.962,674.808,52.2797,-304.359,641.674,29.926,-297.401,668.114,35.5263,-296.91,734.761,72.6878,-264.261,755.872,69.2623,-231.448,747.858,36.634,-235.744,726.544,41.015,-266.755,775.866,67.0184,-195.321,768.536,34.2524,-199.9,727.489,12.331,-235.624,706.703,17.5424,-265.294,748.382,10.2253,-200.018,733.921,111.336,-259.079,754.097,108.59,-225.259,773.192,106.727,-189.244,710.105,76.8636,-290.811,709.736,114.491,-287.567,703.213,47.3109,-289.959,685.77,26.1847,-286.29,819.621,66.8042,-122.154,843.049,67.7001,-85.4765,834.755,35.9707,-84.8796,812.056,34.8383,-123.557,867.323,67.7417,-49.3718,857.984,36.4392,-47.0791,812.743,15.4081,-79.3184,791.136,13.0557,-120.541,834.335,16.6605,-38.8697,816.617,107.001,-119.146,840.336,109.012,-83.2339,865.131,109.108,-47.1105,797.171,66.3875,-158.83,794.134,106.218,-154.178,789.959,33.9599,-162.201,769.64,10.898,-161.216,917.045,62.2486,19.8423,915.869,97.341,23.5437,938.603,91.8609,57.621,940.543,58.6939,53.8648,905.958,134.499,35.1264,927.337,126.046,69.2049,892.312,65.5942,-14.4174,890.844,104.154,-11.4422,881.932,145.743,0.0164956,905.78,32.8506,24.0029,881.671,35.3279,-11.0676,877.828,12.4193,36.5232,855.785,15.5199,-0.520572,930.272,29.2237,58.9789,901.197,7.79011,73.0509,625.547,262.853,178.891,612.969,286.63,140.648,593.5,261.428,132.263,606.793,239.087,177.182,582.452,226.953,123.026,595.328,207.274,173.882,650.061,278.991,177.78,637.798,303.451,143.951,679.462,287.827,173.836,667.53,312.405,142.811,638.843,240.522,219.417,663.466,255.617,214.674,692.478,263.998,207.92,619.605,218.284,222.072,606.748,188.474,222.56,600.488,123.329,164.319,594.393,134.759,111.104,616.535,91.5464,101.259,620.406,81.3675,154.297,586.493,138.524,57.5942,609.711,94.5602,48.626,651.391,55.3838,86.3577,655.286,45.7711,137.923,643.827,58.4021,35.3802,592.677,166.993,170.221,582.891,182.313,117.167,572.166,188.703,63.2687,605.656,108.84,215.995,601.264,150.666,220.804,622.426,66.978,207.325,656.417,31.9138,190.26,563.46,114.761,-45.8842,549.016,159.502,-41.2763,537.251,141.055,-90.1226,549.976,98.2908,-94.4456,525.564,126.573,-136.308,536.22,84.9059,-140.156,575.907,130.014,5.02941,560.764,178.242,10.1548,585.12,74.5311,-53.3922,598.83,87.4596,-3.18244,616.389,41.4589,-64.3709,631.691,52.4758,-15.1931,569.802,60.0563,-101.227,554.102,48.3235,-145.913,599.321,29.2058,-111.164,581.892,19.5709,-154.579,514.292,78.2515,-220.525,514.325,80.8842,-253.167,527.737,49.7478,-251.877,529.148,44.5527,-222.375,549.448,27.2082,-253.71,553.556,19.1461,-227.082,506.861,118.206,-219.262,508.138,119.711,-253.607,522.958,78.9091,-182.519,514.052,119.725,-179.758,539.243,43.6162,-186.674,565.503,16.4095,-193.629,657.503,541.531,31.596,631.616,540.62,40.7422,638.775,496.911,41.7679,666.517,497.075,32.9846,605.604,537.691,45.0546,611.313,494.22,45.9308,648.765,589.673,32.0953,624.287,588.247,41.6187,599.307,585.17,46.054,681.246,540.018,17.6216,671.034,589.074,17.5226,700.827,535.672,-1.17351,689.386,586.079,-2.05967,692.201,494.291,19.5618,713.488,488.141,1.47994,577.683,979.329,62.062,603.377,981.831,44.839,595.274,1038.51,60.1428,569.001,1035.55,77.5358,627.034,983.058,21.2614,619.973,1040.1,35.8209,586.772,1095.25,75.7516,559.955,1091.88,93.2309,612.445,1097.18,50.8486,585.077,922.099,47.8241,611.397,924.91,31.4315,634.075,925.837,8.66318,549.63,975.151,72.1944,554.452,917,55.6586,518.431,968.078,72.806,521.454,910.481,52.5808,541.096,1031.4,87.7259,531.702,1087.2,102.982,511.506,1026.27,90.4553,501.72,1081.34,104.701,721.843,518.229,-52.0238,714.229,528.085,-24.7828,728.039,478.208,-21.2794,736.279,465.878,-47.4996,709.538,572.432,-54.9836,702.116,580.315,-26.612,724.059,507.076,-81.7149,711.968,563.083,-86.0238,721.271,495.6,-112.674,709.721,552.918,-118.582,738.641,452.541,-75.9653,735.547,439.587,-105.459,666.247,981.304,-42.7635,678.617,979.31,-81.4812,677.689,1037.6,-71.5878,663.148,1039.01,-31.0448,671.429,979.079,-136.403,669.894,1039.04,-131.376,674.99,1095.89,-61.6499,659.158,1096.63,-18.3526,662.632,1100.58,-128.991,669.149,923.253,-52.4992,679.619,921.049,-89.0723,674.832,922.872,-138.001,648.402,982.74,-8.44123,653.535,925.098,-19.7493,643.159,1040.14,4.84301,637.268,1097.53,18.8278,702.938,475.366,-173.578,713.869,484.771,-143.717,727.428,428.404,-134.766,715.539,419.381,-163.092,693.078,533.145,-183.11,703.113,542.591,-151.508,689.568,468.157,-200.988,680.551,525.628,-211.693,674.844,463.92,-224.679,666.466,521.087,-235.565,701.136,412.904,-189.646,685.476,409.363,-213.635,626.942,971.923,-202.848,628.215,930.647,-208.74,621.544,927.955,-218.897,621.662,967.551,-211.716,615.873,925.393,-229.854,617.053,964.854,-222.655,639.196,977.042,-186.771,645.024,930.321,-188.368,631.981,1028.4,-194.752,642.049,1036.5,-179.41,632.092,1091.45,-184.073,638.939,1098.74,-170.834,627.029,1020.56,-204.581,622.351,1014.91,-218.201,628.727,1084.55,-194.041,625.342,1078.43,-210.645,643.913,465.394,-257.145,635.827,522.665,-264.665,651.759,520.567,-253.032,659.854,463.429,-243.384,626.335,468.525,-266.004,618.063,525.977,-271.032,606.435,471.533,-270.006,597.868,529.101,-272.702,653.356,411.113,-250.935,635.305,414.124,-263.04,614.866,417.042,-269.976,669.815,409.147,-234.265,590.925,971.152,-284.011,592.203,918.965,-273.311,564.299,923.568,-300.083,554.264,985.142,-317.708,524.931,931.007,-316.857,512.657,991.433,-329.731,609.978,963.402,-243.118,607.531,922.278,-246.14,586.343,1035.4,-302.214,613.989,1017.1,-250.671,581.114,1095.16,-308.418,614.738,1082.88,-257.551,547.105,1044.43,-327.165,505.378,1046.8,-336.014,542.358,1099.34,-328.42,498.768,1100.84,-338.736,559.497,526.769,38.3904,553.314,574.995,38.8585,533.006,567.526,25.861,539.884,518.299,25.882,514.959,558.224,5.68798,522.885,507.503,6.23369,581.485,533.153,44.5256,575.532,580.814,45.3616,564.38,482.279,39.596,586.472,489.421,45.4908,545.172,472.564,27.392,528.978,460.044,8.02472,455.821,948.866,37.6747,467.587,906.252,24.9484,491.242,906.05,40.3666,485.21,958.032,60.4441,433.992,945.678,14.0589,449.382,910.108,10.3317,416.417,948.152,-5.91567,432.494,914.131,-3.07,446.625,1008.62,65.2469,417.745,995.892,33.0585,436.475,1066.86,82.9384,402.917,1057.8,55.3739,397.531,988.34,1.21433,376.128,1046.77,18.7957,479.868,1019.55,84.6863,469.715,1074.44,98.081,492.624,460.142,-188.81,502.394,464.39,-214.952,509.634,409.413,-217.282,500.811,404.587,-190.001,517.047,468.941,-236.442,523.633,414.52,-239.604,480.798,518.712,-189.239,491.278,522.435,-214.814,506.689,526.473,-235.944,487.514,458.109,-158.747,475.232,516.891,-159.768,486.84,460.205,-125.492,474.558,518.555,-126.948,496.579,402.296,-158.731,496.354,404.794,-124.442,334.29,945.357,-210.926,331.323,939.797,-174.998,304.483,989.271,-175.479,305.165,998.634,-217.319,334.523,940.79,-132.41,314.5,985.285,-131.156,279.857,1041.55,-178.483,282.359,1051.48,-221.119,289.758,1034.02,-132.766,361.952,898.798,-206.497,355.653,892.802,-176.076,354.258,888.985,-136.513,342.661,955.88,-244.908,369.59,905.351,-233.856,360.57,968.602,-276.903,383.396,914.252,-262.74,316.555,1011.23,-254.864,296.742,1062.97,-258.671,339.84,1023.9,-286.558,322.857,1074.79,-290.09,559.643,473.261,-263.421,550.365,530.651,-263.443,574.636,530.633,-270.244,583.528,473.129,-269.191,536.806,471.882,-252.551,527.05,529.238,-252.082,566.673,418.989,-266.493,543.391,417.65,-255.994,591.243,418.725,-271.14,428.449,987.322,-319.876,443.247,930.243,-309.324,408.731,923.164,-289.541,390.521,980.116,-302.969,470.165,990.941,-328.844,482.208,933.798,-319.497,414.656,1041.7,-326.234,459.602,1045.7,-335.295,402.749,1093.31,-329.031,450.177,1098.51,-338.59,374.053,1034.3,-310.058,360.25,1085.36,-313.363,497.794,480.375,-54.186,487.271,535.335,-55.5712,478.759,525.296,-91.327,490.38,468.345,-89.7738,508.742,494.145,-21.3196,499.525,546.903,-22.3443,506.098,428.411,-52.2395,515.931,444.486,-19.3568,499.55,414.34,-88.106,374.665,951.512,-50.6043,397.356,951.342,-25.0851,378.934,986.389,-25.0497,357.204,986.627,-53.5091,356.114,1036.67,-15.6257,333.564,1030.89,-50.8346,387.137,907.34,-47.5702,412.363,914.587,-20.1072,350.23,946.807,-87.3221,363.433,894.319,-90.0206,333.69,985.645,-88.6202,309.966,1030.59,-89.463,371.625,2121.87,298.236,363.015,2196.6,319.802,322.062,2188.87,331.283,335.302,2112.55,307.009,350.849,2268.8,334.413,308.1,2259.89,345.711,281.092,2178.7,333.256,298.904,2101.58,307.766,266.094,2248.89,347.394,407.938,2128.46,281.774,402.447,2202.23,300.047,442.991,2130.55,253.311,438.589,2204.64,269.051,392.368,2274.91,313.026,428.805,2277.34,280.535,376.315,2058.65,273.428,409.053,2061.36,258.588,441.764,2059.41,232.458,343.986,2051.05,280.538,312.379,2040.17,279.395,321.921,2416.28,355.313,337.098,2341.42,346.179,379.669,2348.45,324.355,365.565,2424.18,332.614,418.32,2352.39,290.746,405.226,2429.39,297.909,276.834,2406.11,367.28,293.098,2331.93,357.742,232.496,2393.87,369.096,249.856,2320.43,359.438,305.233,2493.92,360.905,259.291,2482.58,373.393,286.948,2574.71,362.055,240.461,2561.46,375.118,214.105,2469.04,375.438,194.782,2545.78,377.53,349.702,2503.02,337.269,390.117,2509.64,301.297,331.921,2585.57,337.462,372.761,2593.79,300.203,485.528,2123.41,180.579,469.914,2127.73,214.831,465.682,2055.8,202.501,480.494,2052.69,180.773,482.665,2198.48,181.501,466.555,2202.69,225.766,475.392,2271.44,184.296,458.611,2276.73,234.737,498.567,2120.76,154.214,492.326,2194.79,146.418,517.752,2114.8,124.2,502.972,2189.53,117.591,484.264,2264,143.466,491.876,2257.07,111.485,498.255,2049.57,161.12,532.479,2043.78,124.429,456.696,2428.01,190.14,466.845,2423.37,141.454,452.03,2507.07,136.451,442.795,2511.02,188.06,476.292,2417.14,102.785,460.757,2502.13,100.182,436.794,2591.99,131.047,426.769,2597.07,184.425,446.085,2586.86,94.4134,467.35,2347.14,188.715,477.34,2339.47,143.868,486.259,2331.38,106.589,436.941,2430.88,248.223,448.911,2352.14,242.847,422.558,2512.72,249.697,405.776,2598.2,247.286,543.528,2083.94,8.31646,543.818,2100,68.7889,553.655,2028.6,64.4543,547.733,2014.53,11.5814,536.79,2159.66,4.7926,522.437,2178.97,76.1606,524.237,2241.01,6.73805,503.775,2251.03,75.12,528.901,2071.08,-42.961,522.888,2146.19,-49.3606,507.452,2059.1,-88.3384,501.125,2134.16,-95.8238,515.437,2225.26,-57.0518,493.448,2213.15,-104.632,533.115,2001.77,-38.1409,512.016,1989.95,-82.3942,504.67,2401.39,-8.15147,496.486,2389.76,-74.7364,483.852,2475.11,-85.4868,495.47,2485.8,-23.6012,474.539,2376.99,-125.125,461.812,2462.02,-135.94,467.905,2563.4,-96.2673,480.85,2576.55,-37.2729,445.906,2549.23,-146.553,513.519,2319.13,2.52149,506.831,2306.44,-65.386,484.836,2294.06,-114.545,490.337,2407.31,57.2246,497.283,2324.39,65.2167,479.933,2492.76,53.6813,466.597,2582.72,43.8696,448.069,2039.42,-151.222,480.368,2048.67,-124.345,485.639,1979.7,-117.583,454.346,1970.62,-143.945,440.084,2114.08,-160.425,473.357,2123.57,-132.791,430.92,2192.5,-171.044,465.077,2202.34,-142.581,410.973,2030.95,-169.21,401.802,2105.33,-178.89,369.504,2022.86,-178.552,359.01,2096.96,-188.351,391.556,2183.34,-190.075,347.565,2174.56,-199.727,418.5,1962.32,-161.719,378.464,1954.41,-171.145,409.895,2354.14,-194.542,368.853,2343.55,-214.448,354.995,2425.19,-226.734,396.557,2437.09,-206.506,322.949,2333.28,-224.309,308.528,2413.55,-236.616,338.534,2507.47,-238.482,380.347,2521.25,-218.004,291.818,2493.85,-248.255,421.104,2272.57,-182.569,380.806,2262.84,-202.078,335.753,2253.47,-211.862,445.363,2365.23,-164.722,455.996,2282.89,-153.377,432.424,2449.33,-176.164,416.428,2535.18,-187.246,234.647,2075.99,275.006,207.548,2153.11,301.64,175.277,2143.45,259.045,208.483,2066.62,242.606,189.054,2223.37,317.416,153.458,2212.27,268.174,149.754,2147.64,190.117,181.137,2067.57,196.652,132.59,2211.47,184.896,264.596,2089.05,297.622,243.055,2166.36,324.708,226.784,2236.49,339.54,257.769,2019.45,250.695,283.229,2029.01,269.278,237.058,2009.48,221.678,219.45,1996.35,185.55,151.778,2364.84,341.311,170.873,2293.34,330.709,209.3,2307.33,351.521,190.83,2379.78,361.109,114.488,2349.95,293.69,134.151,2280.09,280.582,95.5181,2342.01,193.395,114.768,2275.69,187.253,132.111,2436.88,348.229,94.8011,2420.34,304.142,112.33,2508.76,351.041,75.3202,2489.69,309.382,75.5783,2405.45,204.103,53.8323,2469.63,217.295,171.626,2453.39,367.481,151.927,2527.7,369.799,132.61,2009.89,-70.5218,116.341,2081.36,-70.3858,139.115,2076.02,-108.036,155.97,2003.15,-107.248,100.062,2154.85,-71.1933,122.273,2150.23,-110.905,173.959,2073.5,-141.12,190.396,2000.11,-137.288,157.466,2148.99,-146.92,123.076,2017.36,-28.8272,106.447,2088.57,-27.5061,126.362,2025.99,19.9841,110.631,2105.6,34.7487,89.5477,2161.98,-22.4349,103.184,2177.84,53.3492,149.144,1941.47,-74.021,140.437,1951.32,-29.7317,148.998,1959.72,19.073,172.713,1934.44,-108.859,205.966,1932.07,-134.775,61.2966,2299.67,-76.7109,81.3972,2227.94,-73.0191,70.2984,2233.7,-20.9659,48.7531,2304.86,-21.0739,88.1907,2245.18,64.6413,67.7986,2318.9,72.8691,86.8948,2298.75,-122.715,104.794,2224.47,-115.772,124.955,2301.19,-164.417,141.198,2224.86,-154.816,40.8934,2369.84,-82.8935,69.3177,2372.03,-131.97,21.687,2440.86,-89.9943,52.5104,2443.81,-140.449,108.612,2376.37,-173.892,92.1813,2449,-181.898,25.9133,2373.35,-20.5876,46.7781,2386.2,75.8621,4.65959,2442.6,-22.2411,23.4248,2448.37,71.1746,277.355,2007.6,-172.643,324.08,2014.77,-179.487,334.598,1946.51,-172.459,289.499,1939.53,-166.371,264.079,2081.21,-181.313,312.205,2088.63,-188.972,250.152,2158.08,-191.56,299.526,2165.86,-200.054,231.989,2002.31,-158.655,217.312,2075.63,-165.923,202.067,2152.04,-174.578,245.759,1934.4,-153.587,221.436,2313.99,-213.876,171.302,2306.51,-194.379,155.278,2382.76,-204.292,205.95,2391.57,-225.021,138.667,2457.18,-213.615,189.241,2467.91,-235.279,236.054,2235.92,-202.568,186.863,2229.3,-184.358,272.897,2323.18,-223.986,286.591,2244.24,-211.878,257.939,2402.09,-235.921,241.097,2480.39,-246.84,150.287,2069.2,114.075,136.323,2150.48,117.168,127.741,2132.95,89.7973,139.768,2045.61,75.1738,125.527,2210.46,121.462,119.361,2197.38,100.154,160.111,2076.12,148.738,140.727,2154.99,141.441,128.233,2214.53,140.931,191.205,1973.75,110.415,203.648,1984.06,144.834,172.149,1964.4,70.8653,92.3739,2339.59,132.806,110,2275.05,127.48,112.07,2277.23,144.394,94.3597,2340.93,148.794,86.6487,2332.68,116.26,105.018,2263.1,109.117,74.1656,2397.26,136.357,67.3069,2393.51,119.323,52.0145,2454.55,138.422,44.3778,2451.59,118.057,76.2217,2399.47,153.424,54.3366,2458.02,159.19,246.638,2743.55,350.128,227.173,2825.27,340.774,180.903,2801.24,355.836,200.084,2723.82,364.419,135.809,2773.36,360.365,154.624,2700.7,368.072,291.83,2759.4,324.334,272.173,2844.66,314.632,333.073,2771.07,286.555,313.381,2859.33,277.59,266.994,2658.97,357.859,312.202,2672.02,332.586,353.264,2681.77,294.568,220.331,2642.88,371.488,174.619,2623.92,374.434,392.389,2776.54,175.065,376.623,2871.25,171.34,348.869,2868.86,229.681,367.826,2777.36,235.694,406.045,2771.83,118.559,393.559,2866.02,110.548,416.474,2770.35,74.8675,403.862,2862.93,61.5256,408.974,2685.98,179.433,420.274,2680.97,125.621,430.521,2677.21,86.3367,386.955,2687.02,241.655,437.34,2761.22,-55.6574,432.957,2770.49,14.1558,450.687,2676.09,29.2982,460.345,2668.5,-46.6801,414.08,2852.04,-65.439,414.499,2861.67,0.699908,425.237,2745.79,-115.468,400.805,2835.99,-124.19,403.527,2728.59,-165.896,377.039,2817.69,-173.014,447.887,2654.28,-106.101,426.073,2638.72,-156.53,337.761,2691.51,-238.39,373.957,2710.39,-207.071,396.624,2622.75,-197.515,360.528,2606.5,-228.578,305.99,2777.15,-240.515,343.924,2797.4,-211.289,296.245,2672.15,-259.424,265.81,2756.75,-261.973,249.39,2652.82,-266.716,222.938,2733.74,-270.132,318.748,2590.11,-249.207,271.802,2573.68,-257.908,73.8609,2647.68,343.51,55.7001,2710.69,336.085,20.8769,2681.14,294.296,38.1076,2621.95,301.406,-0.735603,2653.86,227.295,15.0072,2598.2,226.328,112.44,2674.55,361.472,94.1512,2742.15,354.577,92.8011,2579.72,348.962,131.949,2602.25,367.171,56.3589,2557.48,307.704,32.8368,2535.86,224.498,-7.30727,2567.31,-100.848,22.6408,2573.83,-152.206,37.1971,2510.9,-146.406,6.31367,2506.86,-95.2101,60.2022,2583.98,-194.653,76.0552,2518.17,-188.594,-20.7454,2623.92,-107.826,8.00388,2633.17,-159.048,44.2307,2647.07,-201.11,-25.8617,2563.25,-36.7752,-39.4687,2618,-46.465,-18.1828,2560.64,50.7443,-37.2446,2613.42,36.8276,-11.7264,2505.68,-28.0337,1.52032,2506.49,62.1452,151.506,2615.05,-248.868,200.334,2633.77,-261.524,221.627,2557.5,-254.921,170.974,2542.27,-242.811,131.466,2687.61,-252.556,177.239,2711.03,-264.124,103.866,2597.92,-227.537,86.1783,2665.53,-232.722,121.469,2528.87,-221.305,8.82737,2571.78,140.686,-8.02126,2622.91,142.01,-17.6089,2614.34,106.831,0.949904,2564.28,111.223,10.6388,2581.39,168.972,-5.61424,2634.81,173.025,28.3461,2514.36,139.379,30.0649,2520.79,164.828,20.9709,2509.64,114.519,631.351,-1.03045,-248.544,672.673,3.49416,-258.613,692.196,-2.01487,-228.567,648.853,-6.23351,-217.36,712.58,-3.26698,-192.71,668.191,-6.21348,-180.812,616.894,9.93831,-273.492,654.487,13.835,-281.088,589.634,4.06991,-236.919,580.523,14.1665,-262.641,604.331,-0.148752,-204.785,622.275,1.39528,-167.158,707.903,5.31561,-95.6407,754.073,3.41028,-110.297,774.301,7.60002,-66.6708,726.557,11.7234,-49.8352,793.597,9.86569,-23.3859,743.268,15.1567,-3.75953,688.161,-1.51356,-139.77,733.353,-0.835405,-152.798,659.662,18.1936,-79.393,641.129,8.59225,-124.952,676.645,26.9555,-31.7098,690.845,31.6344,16.8738,769.032,7.43151,84.5701,707.987,21.2035,112.963,701.035,28.9849,65.1308,757.177,13.0642,41.1769,779.579,0.243278,126.014,712.472,10.4854,160.267,789.219,-5.95303,163.759,720.455,0.476289,201.09,828.945,4.22482,57.901,846.727,-1.26234,96.1691,859.139,-6.10587,133.504,811.524,8.33702,18.0945,802.849,198.047,297.679,834.568,191.577,281.69,819.276,210.892,243.413,786.429,218.263,258.888,866.584,180.909,262.586,852.286,198.801,224.805,805.442,231.441,207.15,771.593,239.978,222.005,839.031,217.639,189.01,819.989,180.473,337.375,850.424,174.702,321.137,836.985,166.685,376.974,865.951,161.466,360.903,881.091,165.215,301.747,894.972,152.968,341.684,771.646,200.419,311.049,789.796,182.698,350.815,807.999,168.851,390.111,754.285,220.941,271.843,738.452,243.189,234.227,748.285,284.786,156.982,783.67,273.354,143.209,773.428,296.13,115.513,737.845,308.494,128.857,817.014,255.612,125.295,805.963,277.161,97.5722,759.207,262.05,188.034,793.957,252.027,173.752,827.654,236.174,155.807,712.877,289.684,167.047,724.886,266.092,199.232,701.712,314.002,137.868,871.103,202.922,78.3754,846.298,231.782,102.811,858.817,214.645,133.626,886.469,188.443,109.962,871.394,198.637,166.934,901.031,175.242,143.55,854.378,220.901,48.9898,832.949,251.837,74.7694,891.013,170.094,54.6083,870.241,185.098,22.1158,909.633,158.576,87.5697,926.439,148.261,121.485,928.899,147.307,216.104,941.15,135.184,255.024,966.642,115.855,232.806,955.281,125.649,193.865,951.576,126.034,295.671,975.515,108.739,273.899,898.68,165.94,239.868,911.983,151.84,278.857,924.12,140.968,319.103,915.35,161.094,178.939,884.917,181.971,202.454,941.767,136.868,156.837,945.123,1.05249,230.732,920.853,1.94403,259.934,907.538,-1.83096,238.298,927.765,-3.09433,207.691,893.285,3.89785,285.788,884.213,0.277506,263.471,886.113,-4.47904,228.271,899.515,-6.24468,197.527,874.736,-2.17136,249.794,955.532,5.85646,262.199,929.813,6.55736,289.484,961.69,11.2442,299.843,936.277,11.8652,325.114,900.757,8.27401,314.254,907.649,13.3592,348.099,965.678,3.40154,201.698,976.805,8.1881,236.189,982.782,13.2893,275.758,946.538,-1.35153,173.684,913.443,-5.45348,160.503,806.677,-8.37577,223.96,813.929,-5.92317,248.54,775.691,-5.52702,267.671,758.435,-7.00818,250.107,820.124,-2.3961,271.406,787.967,-2.39848,285.073,745.821,-4.23675,289.715,720.659,-4.45543,279.992,764.698,-2.28646,295.784,856.429,-8.06355,204.691,852.824,-5.70791,233.546,851.957,-2.30092,258.549,798.346,-8.60571,196.053,860.453,-8.28416,170.511,737.899,-5.1809,229.475,691.59,-0.130948,267.096,698.73,2.72453,341.706,705.264,7.34564,373.844,678.985,13.3562,377.853,670.472,8.81104,342.594,719.451,12.6586,407.656,694.989,18.3129,412.609,730.148,1.74108,339.608,735.988,6.14185,368.944,763.372,3.12886,335.509,769.217,7.27343,362.99,748.28,11.4023,401.754,779.769,12.3214,395.008,702.31,-1.2105,310.474,732.034,-1.76641,313.445,762.104,-0.154251,312.308,671.894,4.42407,305.239,830.448,4.72613,318.516,825.284,1.05535,294.167,856.149,1.07376,281.173,862.839,4.73224,304.777,797.049,4.15742,328.619,793.562,0.71579,305.213,836.655,8.91261,346.148,803.004,8.26908,355.814,844.855,13.8058,378.05,812.215,13.1961,387.525,869.472,8.98648,332.719,876.921,13.9333,365.326,539.306,1204.8,120.219,510.136,1199.37,129.189,521.31,1143.46,116.836,549.993,1148.56,107.489,479.42,1192.74,129.488,490.949,1137.13,117.752,528.085,1259.82,131.327,498.397,1254.07,139.936,467.365,1247.28,139.797,567.366,1208.92,102.961,556.761,1264.47,114.42,594.752,1211.61,77.7994,584.76,1267.94,89.6702,577.419,1152.29,90.0475,604.009,1154.52,64.8502,646.278,1212.58,6.84658,621.9,1212.73,45.1213,630.183,1155.12,32.2376,653.571,1154.42,-5.76836,637.185,1271.21,19.7441,612.416,1270.15,57.5274,662.043,1213.75,-42.953,653.235,1271.64,-30.0803,644.186,1216.81,-113.338,636.748,1271.48,-99.1255,669.949,1154.45,-52.0911,653.439,1158.78,-123.577,624.925,1197.48,-158.202,629.533,1208.33,-144.343,634.22,1152.41,-159.74,628.865,1143.35,-172.647,619.794,1254.57,-141.752,623.913,1263.39,-128.031,622.251,1189.16,-170.645,617.213,1248.75,-155.467,619.762,1188.69,-192.15,614.428,1250.35,-182.765,626.075,1136.32,-183.185,623.42,1133.73,-201.161,569.63,1209.26,-297.703,604.68,1201.67,-254.341,610.943,1142.42,-257.452,575.964,1151.35,-305.264,561.584,1270.46,-285.745,596.05,1264.59,-247.447,530.449,1212.2,-319.397,522.668,1272.99,-307.939,485.43,1212.75,-332.385,477.684,1273.17,-321.436,536.927,1154.75,-325.984,492.333,1155.63,-337.858,412.995,1176.72,103.46,379.141,1167.97,77.8528,390.882,1112.39,67.2724,424.93,1121.55,93.6684,346.784,1158.55,44.1356,360.353,1102.77,33.1216,401.089,1231.3,112.481,367.52,1222.97,86.4223,334.785,1214.97,52.8223,446.721,1185.04,120.73,434.649,1239.55,130.459,458.492,1129.68,109.896,250.026,1159.56,-218.248,265.519,1170.76,-255.356,279.686,1115.44,-258.699,264.555,1104.39,-221.166,293.162,1182.4,-286.681,307.091,1126.72,-290.351,238.818,1217.69,-212.217,254.798,1229.49,-248.208,282.911,1241.75,-278.422,245.556,1150.37,-176.619,234.017,1207.84,-171.497,251.314,1144.44,-131.654,239.438,1201.45,-127.097,260.597,1095.13,-179.108,267.635,1088.43,-133.735,382.339,1201.82,-326.972,434.354,1209.03,-334.595,442.045,1151.94,-338.792,392.265,1144.93,-330.174,373.549,1262.44,-317.413,426.358,1269.41,-324.274,333.912,1192.8,-310.806,324.114,1252.98,-301.809,346.581,1136.54,-314.175,289.337,1145.43,-38.1175,266.357,1143.1,-84.8772,284.354,1085.76,-88.0921,308.331,1087.88,-43.4902,276.193,1202.47,-32.7944,254.126,1200.01,-80.0638,317.102,1150.49,5.25239,303.799,1207.65,12.2592,335.081,1094.05,-3.74058,3.93021,5528.23,203.449,3.24015,5514.23,206.529,10.6583,5513.2,184.714,12.2848,5527.07,181.569,1.71465,5500.44,208.818,8.31561,5499.62,186.902,-7.16004,5528.84,225.635,-7.01413,5514.78,228.765,-20.8763,5528.56,247.422,-20.193,5514.41,250.852,-7.75248,5500.81,231.278,-20.3788,5500.25,253.818,3.91562,5542.79,199.934,-8.01577,5543.36,222.313,3.32718,5558.26,196.34,-9.40699,5558.69,219.221,-22.2495,5543.04,244.067,-24.1295,5558.21,241.33,13.2505,5541.57,177.781,13.6043,5557.03,173.671,-34.3263,5337.78,206.218,-39.8603,5320.35,211.3,-32.5167,5316,184.473,-26.1166,5334.33,178.119,-45.5942,5303.39,216.441,-39.1335,5298.44,190.785,-43.8742,5338.98,232.799,-48.6225,5322.41,236.665,-54.8954,5338.11,257.402,-58.8932,5322.2,259.95,-53.4891,5306.12,240.469,-62.957,5306.4,262.259,-29.0606,5355.6,201.811,-39.2727,5355.82,229.524,-24.3809,5373.58,199.283,-34.8452,5372.91,227.488,-50.8827,5354.27,255.145,-46.7736,5370.81,253.713,-19.8961,5353.55,172.012,-14.8018,5372.98,168.819,-150.007,5513.25,338.591,-150.277,5498.49,344.428,-124.301,5500.82,336.408,-123.939,5515.7,330.241,-150.748,5483.35,350.601,-124.976,5485.63,342.71,-99.543,5503.66,324.956,-99.2708,5518.57,318.747,-100.246,5488.49,331.112,-177.009,5511.44,343.664,-177.065,5496.79,349.116,-177.217,5481.75,355.066,-149.981,5528.03,334.019,-177.08,5526.1,339.498,-150.241,5543.19,331.645,-177.307,5541.19,337.411,-123.93,5530.59,325.279,-99.4506,5533.51,313.623,-124.316,5545.83,322.593,-100.101,5548.78,310.728,-17.3339,5409.01,202.155,-20.6038,5391.47,199.844,-11.7783,5391.98,171.209,-9.74351,5410.13,176.064,-26.5782,5407.52,228.452,-30.6188,5390.22,227.343,-38.1738,5405.14,254.425,-42.4863,5387.87,253.635,-14.1784,5425.93,204.878,-22.7056,5424.56,230.182,-10.9382,5442.14,207.416,-18.9844,5441.08,231.896,-33.9904,5422.32,255.59,-30.0892,5439.12,256.643,-7.60919,5427.01,180.273,-5.0802,5442.82,183.679,-155.151,5378.54,362.076,-155.712,5360.31,358.394,-133.382,5364.15,350.309,-132.275,5382.09,353.471,-156.116,5342.99,354.007,-134.267,5347.06,346.508,-112.019,5368.82,338.221,-110.245,5386.47,340.764,-113.53,5351.93,335.188,-178.596,5376.08,366.925,-178.693,5357.61,362.884,-178.715,5340.1,358.121,-154.473,5397.18,364.559,-178.436,5394.97,369.673,-153.714,5415.71,365.349,-178.222,5413.74,370.555,-131.019,5400.4,355.591,-108.323,5404.46,342.48,-129.685,5418.61,356.263,-106.372,5422.33,343.04,-11.5643,5687.58,191.593,-6.75707,5661.35,188.416,5.77572,5666.66,160.804,-0.571676,5696.16,166.298,-3.40839,5636.24,186.945,10.0138,5638.54,158.129,-24.0204,5681.5,214.618,-20.5994,5657.38,213.745,-37.9703,5678.57,235.721,-35.6397,5655.02,237.111,-17.9923,5633.96,213.484,-33.5235,5631.98,238.002,-18.3902,5713.53,195.844,-28.9435,5704.93,216.027,-28.2219,5738.13,201.548,-36.8156,5726.23,219.729,-40.9601,5701.04,233.753,-43.8103,5717.76,231.671,-8.91191,5724.9,172.369,-20.0773,5752.17,178.97,-156.525,5311.76,344.824,-156.885,5296.16,340.466,-136.158,5300.37,334.31,-135.407,5315.99,338.359,-157.589,5279.42,336.557,-137.36,5283.61,330.477,-116.926,5305.35,325.32,-115.698,5320.95,328.688,-118.632,5288.69,321.939,-178.59,5308.72,348.405,-178.619,5293.13,343.974,-178.826,5276.49,340.173,-156.328,5327.09,349.41,-178.651,5324.08,353.208,-134.86,5331.27,342.475,-114.662,5336.22,331.993,-81.8218,5331.16,299.017,-84.3251,5315.68,298.354,-70.7616,5319.74,280.54,-67.5226,5335.32,279.566,-87.0918,5299.69,297.399,-74.1337,5303.99,281.203,-97.8582,5326.19,315.482,-99.6809,5310.67,313.307,-101.9,5294.32,310.946,-79.3368,5346.61,299.625,-96.1488,5341.5,317.554,-76.6286,5362.49,300.411,-94.2704,5357.24,319.609,-64.2429,5351.03,278.667,-60.7492,5367.17,278.233,-70.0357,5396.61,302.9,-66.5923,5414.12,303.954,-86.6349,5409.21,324.94,-89.3376,5391.49,323.607,-63.3465,5431.43,304.45,-84.0106,5426.79,325.419,-52.82,5401.33,279.54,-48.8313,5418.67,280.51,-45.125,5435.72,281.176,-73.4549,5379.28,301.611,-56.8669,5384.05,278.652,-91.9415,5374.03,321.728,-55.6931,5524.6,287.2,-56.7513,5539.32,282.885,-77.0655,5536.52,299.401,-76.468,5521.66,304.24,-58.3183,5554.49,280.126,-78.166,5551.76,296.524,-37.1089,5527.02,268.102,-38.487,5541.59,264.35,-40.3698,5556.73,261.679,-55.2358,5510.02,292.157,-36.3821,5512.68,272.217,-55.4708,5495.29,296.841,-36.4564,5498.24,275.973,-76.4053,5506.87,309.972,-76.9055,5491.88,315.521,-70.6404,5682.77,272.885,-71.055,5710.41,264.49,-90.1434,5718.09,277.811,-89.7601,5687.11,288.347,-69.5708,5736.51,252.754,-88.7653,5747.67,263.92,-111.435,5725.47,289.007,-110.71,5691.45,301.086,-110.524,5757.93,273.584,-53.4434,5679.46,255.242,-54.8284,5703.8,249.855,-54.0122,5725.85,241.118,-69.2078,5655.26,278.446,-51.7663,5654.51,258.828,-67.1542,5629.54,281.329,-49.7853,5630.56,260.757,-88.1905,5656.68,295.512,-108.942,5658.17,309.576,-86.0076,5628.73,299.295,-106.722,5627.95,314.235,-157.386,5696.97,316.998,-158.254,5735.2,302.828,-182.99,5737.47,305.934,-182.23,5698.14,320.637,-158.135,5771.49,285.519,-183.216,5774.71,288.198,-133.4,5694.73,310.558,-134.27,5731.18,297.266,-133.775,5765.88,280.716,-155.936,5659.64,327.509,-131.692,5659.14,320.184,-154.302,5626.07,333.835,-129.674,5627.02,325.729,-181.169,5659.72,331.725,-180.045,5625.22,338.61,-59.416,5780.1,217.427,-53.0408,5799.73,193.206,-74.1483,5819.11,200.002,-79.8861,5797.5,225.566,-97.8806,5834.95,206.025,-102.886,5811.97,232.767,-42.0455,5760.31,209.082,-34.9024,5777.26,186.056,-65.3089,5759.38,237.171,-49.0828,5743.93,227.481,-85.0518,5773.92,246.688,-107.388,5786.53,255.062,-154.268,5830.5,242.185,-151.598,5854.82,214.397,-180.414,5859.27,216.554,-181.64,5834.76,244.453,-127.848,5822.93,238.297,-123.891,5846.78,210.856,-156.627,5802.99,265.598,-131.389,5796.22,261.323,-182.671,5806.86,268.019,-100.767,5211.61,252.246,-86.9534,5225.6,243.489,-88.0706,5233.71,258.927,-100.513,5221.83,267.996,-75.0886,5240.37,235.817,-77.8848,5246.29,251.723,-88.0984,5243.62,270.241,-99.5063,5234.2,280.189,-80.6968,5250.37,262.031,-100.958,5203.16,233.542,-86.2586,5217.95,224.653,-73.2708,5233.55,216.306,-116.069,5198.86,261.228,-116.785,5189.89,242.462,-132.234,5188.2,269.907,-133.157,5178.88,250.843,-115.095,5210.25,277.204,-113.521,5224.1,289.859,-131.025,5200.23,286.153,-129.297,5214.92,299.068,-165.206,5175.42,284.147,-148.631,5180.51,277.755,-147.514,5193.04,294.452,-164.424,5188.29,301.338,-145.986,5208.26,307.633,-163.362,5203.82,314.755,-165.806,5165.38,263.874,-149.493,5170.81,258.127,-181.904,5172.54,288.455,-182.109,5162.27,267.647,-181.616,5185.63,306.051,-181.197,5201.28,319.638,0.856794,5593.35,190.128,2.29672,5574.99,193.022,13.4016,5573.8,169.555,12.8167,5592.55,165.236,-13.2129,5593.08,215.011,-11.1591,5575.19,216.785,-28.7317,5591.95,238.928,-26.3373,5574.43,239.747,-0.957824,5613.67,187.81,-15.5101,5612.6,213.909,-31.1735,5611.04,238.477,12.0241,5613.99,160.518,-151.722,5577.27,334.32,-150.831,5559.12,332.397,-125.138,5561.74,323.254,-126.342,5579.67,325.305,-101.241,5564.68,311.204,-102.783,5582.39,313.187,-178.318,5575.44,339.929,-177.721,5557.13,338.192,-152.888,5599.09,335.451,-179.094,5597.63,340.706,-127.872,5600.98,326.783,-104.639,5603.08,314.818,-3.92668,5472.25,210.048,-7.41757,5457.52,209.17,-1.85841,5457.79,186.123,1.69224,5472.12,187.528,-12.1748,5471.97,233.279,-15.3963,5456.84,232.961,-23.7469,5470.74,256.837,-26.6234,5455.23,257.092,-0.778154,5486.51,209.961,-9.5496,5486.58,232.75,-21.6147,5485.71,255.78,5.20458,5485.99,187.813,-152.116,5450.89,360.759,-152.912,5433.63,363.949,-128.344,5436.26,355.082,-127.067,5453.33,352.277,-104.507,5439.69,342.108,-102.81,5456.5,339.762,-177.695,5449.23,365.543,-177.965,5431.83,369,-151.378,5467.48,356.177,-177.436,5465.88,360.719,-125.921,5469.79,348.076,-101.363,5472.77,336.071,-58.2051,5464.39,302.717,-60.5201,5448.18,304.063,-41.9228,5452.16,281.153,-39.3372,5468.03,280.343,-79.6094,5460.37,322.883,-81.6401,5443.84,324.742,-56.4907,5480.09,300.335,-78.0025,5476.38,319.817,-37.4786,5483.37,278.649,-62.5413,5587.8,280.777,-60.3015,5570.43,279.84,-42.6103,5572.76,260.813,-45.0377,5590.12,260.923,-81.6537,5585.16,298.272,-79.7373,5567.66,296.684,-64.8788,5607.28,281.689,-83.7864,5605.21,299.678,-47.4857,5609.25,261.181,-57.9623,5271,225.45,-51.4555,5286.95,221.028,-58.445,5290.14,243.563,-63.8536,5274.69,246.07,-67.1695,5290.59,263.794,-71.6456,5275.28,264.306,-53.6664,5265.2,202.71,-46.014,5281.56,196.745,-65.6325,5255.48,230.092,-62.5822,5249.25,209.097,-70.0732,5260.02,248.114,-76.5009,5261.02,263.546,-93.7987,5265.56,293.126,-90.3631,5282.71,295.921,-104.798,5276.51,308.315,-107.97,5258.21,304.52,-121.103,5270.21,318.595,-123.957,5251.01,314.317,-81.6593,5271.75,279.857,-77.8141,5287.75,281.164,-97.0571,5249.08,288.224,-85.5247,5256.67,276.717,-111.012,5240.41,298.667,-126.814,5232.21,308.131,-160.332,5241.14,329.459,-158.812,5260.71,333.316,-179.3,5257.92,337.263,-179.928,5238.48,333.82,-141.543,5245.32,322.794,-139.26,5264.88,327.012,-161.923,5221.8,323.699,-143.89,5226.07,316.707,-180.597,5219.25,328.419,771.693,4472.31,-5.38224,753.951,4460.05,14.6754,805.639,4421.33,34.0133,824.568,4436.47,15.0538,732.144,4447.93,27.0819,783.518,4406.68,45.0272,727.03,4497.5,-9.75691,708.24,4487.02,4.92495,687.823,4476.54,14.8021,785.279,4481.15,-27.2014,743.88,4506.6,-26.9761,797.469,4485.97,-46.7643,759.168,4514.94,-45.0015,838.613,4450.31,-10.6837,849.024,4459.78,-38.4406,826.245,4496.58,-93.1198,811.533,4490.5,-66.6258,859.806,4464.18,-66.1209,870.369,4465.12,-94.5825,789.534,4537.36,-95.8902,774.451,4525.12,-66.3585,836.349,4501.57,-124.49,799.994,4546.2,-131.591,839.367,4504.34,-158.094,802.328,4547.41,-170.138,877.211,4464.91,-121.094,880.063,4465.38,-148.095,830.645,4497.69,-232.287,836.452,4504.34,-195.157,879.207,4466.17,-181.097,875.4,4462.88,-220.987,788.947,4530.47,-243.401,797.395,4541.95,-208.985,822.087,4484.08,-265.072,777.405,4516.1,-273.369,809.64,4465.13,-292.388,762.859,4498.48,-299.004,867.098,4450.41,-256.375,854.257,4431.35,-283.579,770.261,4410.98,-318.692,793.599,4440.84,-312.817,836.555,4406.62,-301.497,810.948,4374.44,-302.267,726.1,4448.36,-329.954,746.886,4476.44,-319.955,741.704,4378.65,-298.484,700.242,4417.22,-320.234,721.364,4364.52,-279.101,677.425,4395.3,-301.585,781.584,4343.48,-276.098,764.041,4338.16,-263.704,683.684,4361.8,-262.066,702.825,4362.39,-268.671,748.301,4339.62,-257.868,731.275,4341.65,-253.697,636.408,4378.98,-273.716,657.984,4385,-285.827,656.899,4354.16,-248.749,605.273,4370.98,-255.365,626.684,4339.98,-218.191,575.47,4365.65,-224.363,706.78,4336.16,-242.345,676.276,4316.41,-211.286,606.884,4340.33,-151.387,611.452,4336.92,-183.628,661.477,4310.46,-177.499,655.914,4312.44,-145.064,557.349,4369.82,-159.953,561.176,4366.64,-191.753,607.44,4345.78,-120.129,559.027,4373.81,-127.448,610.653,4352.07,-89.6958,562.358,4379.55,-94.734,656.357,4317.54,-114.607,659.872,4323.76,-85.5044,621.695,4366.87,-32.8518,615.089,4358.8,-60.4491,664.705,4330.27,-57.8367,671.727,4337.15,-31.852,572.967,4400.22,-34.6685,565.905,4388.25,-63.1767,631.78,4378.12,-7.82894,585.559,4414.32,-11.1334,646.327,4394.17,12.1959,602.83,4429.51,6.01774,681.877,4344.66,-7.73787,696.03,4353.78,14.1774,685.848,4425.75,30.8928,664.862,4411.31,24.829,714.636,4365.39,32.3983,736.434,4378.71,44.2842,644.547,4456.25,20.4032,623.03,4443.96,16.2414,708.641,4437.09,31.8805,666.376,4466.6,19.82,759.951,4392.52,48.5178,1205.32,4170.62,14.0547,1212.98,4185.81,4.50838,1175.41,4201.13,-5.93635,1171.32,4188.81,1.14405,1223.81,4197.23,-5.81698,1182.77,4211.98,-14.2575,1251,4147.39,31.8544,1262.57,4166.34,19.6098,1274.12,4179.87,7.46861,1200.99,4151.75,22.4795,1241.8,4125.32,39.4713,1197.63,4129.49,29.6908,1233.15,4101.78,43.5369,1168.61,4173.87,8.24617,1165.3,4155.62,15.4653,1260.17,4222.5,-40.8424,1272.14,4229.5,-68.5937,1228.14,4242.16,-71.3548,1210.95,4233.16,-44.6385,1272.74,4230.66,-98.7482,1236.39,4245.61,-104.027,1299.34,4207.6,-33.1954,1304.37,4213.59,-60.4495,1303.99,4214.25,-90.2589,1239.09,4209.64,-18.7622,1288.38,4195.59,-9.04378,1194.82,4222.49,-26.1686,1252.22,4216.98,-161.472,1230.24,4202.94,-190.613,1170.56,4214.18,-190.143,1208.27,4230.93,-168.538,1202.49,4188.81,-207.778,1161.68,4207.01,-199.567,1285.54,4198.79,-153.245,1271.05,4184.69,-182.112,1254.55,4167.82,-206.311,1265.1,4226.14,-130.36,1297.12,4208.98,-121.908,1229.69,4242.1,-136.435,1182.07,4156.1,-230.654,1171.65,4134.34,-237.368,1139.04,4153.33,-238.223,1152.01,4175.19,-229.397,1161.79,4112.26,-236.2,1128.39,4133.9,-236.931,1223.89,4129.66,-233.362,1211.09,4108.35,-236.722,1199.77,4085.9,-232.775,1190.45,4175.29,-218.765,1238.26,4149.56,-223.226,1159.48,4195.16,-212.186,1145.49,4062.45,-208.91,1138.8,4041.55,-185.68,1105.6,4062.34,-190.515,1112.24,4086.36,-213.735,1134.59,4023.91,-159.496,1102,4044.69,-164.269,1180.09,4039.98,-202.67,1172.55,4019.79,-179.727,1167.44,4002.46,-153.735,1153.39,4087.88,-226.676,1189.5,4062.22,-220.974,1120.09,4112.27,-229.094,1134.34,4000.07,-103.848,1137.75,3996.33,-74.6597,1107.81,4016.19,-79.2456,1103.39,4020.35,-108.511,1144.52,4001.15,-45.698,1115.55,4019.47,-50.9583,1165.29,3980.09,-98.282,1167.76,3977.03,-69.2993,1174.25,3983.45,-39.43,1133.28,4009.9,-131.896,1165.17,3989.09,-126.269,1101.48,4030.5,-136.607,1164.39,4019.41,-9.70697,1171.38,4026.71,1.00201,1136.46,4047.12,-2.89777,1132.35,4036.86,-15.3049,1176.39,4037.85,11.8454,1139.47,4063.58,10.1537,1194.92,4004.51,-3.731,1201.75,4011.71,5.01558,1206.73,4020.16,13.7884,1154.53,4011.3,-23.413,1185.22,3995.89,-16.4432,1124.69,4027.99,-29.3697,1188.4,4080.57,33.4277,1193.67,4105.16,34.2032,1160.23,4134.25,21.7887,1153.24,4111.07,25.1189,1217.54,4055.08,36.1478,1224.57,4078.32,42.759,1181.65,4056.07,24.43,1211.54,4034.13,24.9946,1145.21,4086.11,21.0763,-73.083,2909.45,329.799,-41.7943,2914.55,321.004,-10.237,2980.13,329.912,-49.2693,2967.47,343.918,-85.8241,2864.08,298.818,-63.7982,2862.61,290.757,-119.375,2908.17,343.653,-121.812,2864.61,310.852,-112.605,2961.91,362.968,1012.22,55.9767,332.392,1008.17,60.9116,374.512,1006.12,48.0105,375.087,1009.86,41.6915,335.009,1000.54,66.5086,411.617,998.974,53.9356,409.888,999.292,38.4013,381.177,1001.79,31.0447,344.148,995.486,45.3323,405.75,1008.89,72.1527,334.887,1005.52,75.0227,374.546,999.818,88.7168,343.46,998.122,89.2329,379.784,998.616,79.4931,408.785,994.763,88.9927,403.876,1012.82,52.0584,286.942,1008.91,69.8975,290.509,998.517,88.299,300.748,1010.33,36.4893,290.319,1001.52,24.8963,300.915,712.811,81.9606,486.557,739.276,85.2242,512.632,741.379,105.003,510.563,716.647,104.803,485.927,764.825,88.4489,532.2,764.878,106.254,528.538,748.723,122.13,508.086,726.69,124.783,484.628,764.384,118.402,521.878,714.658,59.658,485.603,739.768,65.5369,510.946,723.074,41.5056,483.797,745.854,49.5116,508.761,763.757,70.2751,529.571,762.404,57.407,523.329,686.634,79.2312,455.183,689.203,55.0394,454.1,698.954,35.37,451.888,691.101,104.229,455.226,702.454,126.317,454.314,765.081,147.052,481.644,784.315,141.081,508.93,807.048,144.688,506.216,789.62,151.565,477.706,799.263,130.999,530.076,820.753,133.816,528.632,831.067,145.793,500.711,815.603,153.2,471.468,843.172,134.59,523.818,743.471,138.495,483.575,763.954,133.859,508.547,779.276,125.233,527.194,743.636,151.746,449.449,720.545,141.78,452.536,769.988,157.343,444.61,797.86,159.701,437.578,867.338,151.063,451.123,879.461,143.56,482.178,903.342,139.67,469.03,892.878,146.732,436.853,888.725,132.485,506.892,911.185,129.1,494.671,926.685,133.564,453.212,918.057,139.857,419.743,932.984,123.825,479.873,841.545,153.125,462.634,855.286,145.51,492.718,865.942,134.231,516.589,852.975,157.826,415.472,825.515,159.95,427.908,880.259,153.07,400.143,907.39,145.42,381.792,965.56,118.104,378.808,969.501,114.41,414.746,985.956,102.43,394.818,984.975,104.162,359.079,972.077,107.438,442.889,986.354,97.509,421.719,942.769,130.159,399.712,949.24,124.966,434.664,953.784,116.407,462.443,959.648,121.16,338.024,934.389,134.617,360.29,981.561,105.554,317.381,810.404,93.0615,549.469,807.259,71.2978,546.149,829.218,71.936,546.459,832.145,94.419,549.706,797.708,51.648,533.186,820.135,51.6249,532.75,851.38,72.6746,543.009,853.698,95.0779,546.065,843.513,52.4431,529.034,788.25,91.0664,544.057,785.846,71.4699,540.912,777.334,53.8102,529.458,807.849,113.978,543.85,786.717,109.978,539.258,829.222,116.053,543.446,850.839,116.701,539.303,896.687,93.9614,530.976,895.107,72.2116,528.202,916.317,70.7843,516.555,917.637,91.874,519.385,889.634,52.5999,514.117,912.015,51.7539,502.331,936.853,68.4097,501.888,937.897,88.5583,505.001,933.699,50.1703,487.271,875.293,94.9785,539.846,873.402,72.8042,536.971,866.74,52.7985,522.921,894.513,115.003,523.516,872.709,116.421,532.675,915.938,112.241,511.774,936.664,107.927,497.399,974.664,78.3022,466.98,974.1,61.2378,463.023,988.273,57.9514,438.751,989.24,72.4122,442.02,972.834,45.5749,447.465,987.081,44.6915,424.753,957.229,83.8595,487.753,956.537,64.9763,484.057,954.506,47.7551,468.643,973.894,94.5518,460.284,956.379,101.854,480.344,988.024,86.5502,436.897,760.381,26.4505,479.141,738.838,18.6703,443.912,765.751,17.4883,438.335,785.491,25.4932,474.893,795.167,18.3192,431.826,812.716,26.309,469.239,738.945,31.1121,481.877,716.036,23.9383,448.46,781.043,37.0712,509.874,760.539,40.7142,509.328,804.589,36.509,507.63,829.729,37.3362,503.043,868.335,27.3155,453.21,855.996,19.6001,415.207,886.059,19.6703,402.925,895.726,27.2107,441.182,914.985,19.1078,386.548,922.17,26.5029,425.376,840.497,26.9662,462.287,825.473,19.0866,424.479,880.128,37.9747,487.65,855.013,37.8617,496.564,904.764,37.5675,475.762,928.606,36.5301,460.358,969.517,24.3122,382.673,966.299,17.1402,341.419,986.474,18.9892,319.004,988.034,25.7821,361.218,947.164,25.0454,404.966,942.101,17.7218,364.99,971.502,33.5287,419.351,951.341,34.7508,440.898,987.619,34.1586,397.688,1819.78,3825.59,202.489,1795.74,3840.69,213.683,1814.4,3820.41,223.273,1853.82,3782.39,219.034,1836.9,3804.09,210.852,1832.37,3799.16,232.318,1850.26,3777.51,241.069,1845.22,3770.03,262.048,1826.37,3791.31,252.578,1818.67,3781.12,271.334,1838.58,3760.39,281.804,1854.47,3770.3,135.52,1849.1,3761.32,116.884,1837.92,3778.94,110.179,1842.37,3789.01,128.466,1857.39,3782.22,175.651,1857.02,3777.41,155.249,1843.63,3797.27,147.982,1842.78,3803.02,168.147,1828.07,3823.8,160.427,1830.34,3817.08,140.411,1815.9,3835.95,133.137,1812.15,3843.49,152.766,1830.02,3725.14,77.4136,1819.91,3710.95,69.5614,1809.52,3721.29,67.4485,1819.7,3737.21,74.1453,1844.9,3750.52,101.475,1838.37,3738.41,88.1992,1828.01,3752.43,83.7605,1834.25,3766.25,95.867,1823.67,3782.21,89.9623,1817.66,3766.35,78.8628,1807.58,3780.38,73.6482,1812.9,3798,84.0514,1794.87,3681.46,63.2458,1786.83,3672.63,66.6156,1776.58,3683.82,68.0937,1784.71,3692.35,63.9282,1811.87,3700.96,64.8712,1803.42,3690.81,62.8533,1793.28,3701.38,62.6231,1801.61,3711.36,63.7691,1791.65,3721.58,62.1574,1783.27,3711.72,62.0576,1773.48,3721.59,59.5594,1782.03,3731.54,58.956,1741.54,3674.16,97.9784,1736.81,3692.31,85.3745,1748.84,3679.5,86.3917,1764.7,3642.88,99.394,1753.06,3658.53,98.6239,1759.92,3665.43,86.1482,1771.01,3651.28,85.9829,1778.14,3661.25,74.716,1767.52,3673.99,75.7651,1722.96,3662.61,354.967,1740.54,3695.52,343.091,1719.98,3687.51,328.739,1693.11,3642.54,317.223,1705.68,3652.48,337.821,1704.57,3675.83,309.933,1693.17,3664.36,286.443,1692.74,3684.37,255.3,1702.93,3698.05,280.855,1697.85,3720.45,253.21,1689.42,3703.9,224.871,2002.34,3627.03,335.101,2021.68,3609.74,317.113,2006.53,3627.66,309.524,1970.03,3659.49,319.143,1986.54,3643.61,327.229,1991.03,3645.22,301.873,1974.47,3661.79,294.006,1977.05,3661.93,268.204,1993.6,3644.71,275.791,1994.27,3642.4,249.578,1977.81,3660.18,242.203,1994.09,3592.79,137.32,2002.51,3567.65,127.243,1987.93,3587.24,120.553,1963.75,3633.92,124.906,1979.45,3613.61,130.742,1972.56,3606.64,113.768,1956.43,3625.7,107.745,1947.46,3616.67,93.3157,1964.16,3598.99,99.4606,1953.61,3589.97,88.4475,1936.2,3606.22,82.352,1940.06,3538.39,88.7916,1929.67,3529.96,94.9443,1911.65,3544.68,89.1535,1922.45,3553.81,82.6268,1961.47,3561.84,89.0164,1951.04,3549.62,87.3754,1933.58,3565.35,80.8802,1944.27,3577.82,82.4501,1926.67,3593.85,76.6558,1915.82,3581.2,75.3347,1897.89,3597.22,70.983,1908.59,3609.79,72.2382,1935.23,3514.48,144.818,1939.65,3515.43,164.742,1911.73,3525.58,155.211,1909.74,3525.75,136.193,1930.18,3521.46,108.347,1932.49,3517.06,124.879,1909.4,3529.47,117.521,1909.71,3535.14,101.758,1889.4,3549.48,95.3905,1886.67,3542.75,110.312,1864.69,3557.44,103.8,1869.63,3564.92,89.8757,1949.67,3536.16,232.891,1952.62,3541.23,256.003,1918.22,3550.33,245.802,1916.49,3545.02,222.989,1942.69,3521.8,187.454,1946.18,3529.32,210.006,1914.17,3537.53,200.327,1912.53,3530.61,177.874,1883.21,3541.07,168.265,1883.21,3547.67,190.437,1852.3,3557.94,180.484,2122.22,3286.64,390.876,2130.89,3292.13,398.096,2127.89,3290.68,410.212,2118.52,3284.68,404.522,2099.33,3289.05,376.968,2111.08,3286.35,383.674,2106.86,3284.32,397.628,2094.63,3286.78,390.199,2090.89,3287.31,403.71,2102.45,3284.95,411.186,2099.58,3290.57,422.359,2089.81,3292.15,415.601,1758.69,3711.62,339.114,1760.75,3686.51,360.511,1807.42,3683.78,367.677,1782.58,3698.56,355.517,1782.93,3674.01,372.835,1805.28,3660.33,380.021,1802.12,3636.04,388.953,1782.34,3648.71,385.975,1781.72,3622.06,396.338,1799.24,3610.49,393.953,1789.01,3397.36,437.826,1795.49,3408.84,443.654,1789.76,3408.15,451.442,1781.3,3395.99,447.469,1767.98,3383.54,422.942,1778.91,3389,430.719,1769.66,3387.35,440.643,1758.26,3381.6,432.579,1749.18,3382.72,441.691,1760.79,3388.47,449.55,1752.16,3393.27,454.777,1741.96,3388.02,447.996,1738.35,3568.31,280.904,1714.64,3572.31,314.638,1717.46,3587.07,285.559,1735.71,3587.17,224.414,1737.05,3578.17,253.099,1717.37,3599.56,254.844,1717.1,3611.82,224.796,1701.23,3640.17,226.026,1699.78,3624.42,257.653,1690.66,3652.07,261.489,1691.87,3670.97,228.072,1862.92,3693.44,82.3171,1840.9,3713.4,79.8094,1849.58,3724.93,91.599,1871.52,3704.4,95.0328,1907.98,3654.06,85.6429,1885.48,3673.83,83.8857,1894.46,3684.54,97.2458,1917.2,3664.57,99.6367,1924.46,3674.15,116.339,1901.38,3694.39,113.284,1906.4,3702.46,131.267,1929.67,3681.96,134.934,1825.34,3648.78,63.1759,1805.06,3670.58,62.4527,1813.56,3680.22,62.7784,1834.81,3659.28,63.4088,1866.23,3606.13,68.2317,1845.76,3627.2,65.2921,1855.99,3638.33,64.9289,1877.02,3617.72,67.238,1887.33,3630.01,69.2334,1865.71,3650.14,67.5732,1874.7,3661.53,73.7617,1896.67,3641.92,75.1235,1795.95,3606,103.667,1776.88,3627.75,100.715,1782.35,3637.4,86.1347,1801.55,3615.2,88.4853,1837.79,3567.9,113.575,1816.48,3586.16,108.066,1821.91,3594.35,92.2935,1842.72,3574.99,97.3652,1848.46,3584.03,83.694,1828.11,3604.52,79.1683,1835.94,3616.99,69.6416,1855.79,3595.75,73.5863,2077.34,3293.76,295.164,2069.55,3305.02,304.598,2066,3298.24,293.773,2074.21,3290.27,268.654,2075.61,3290.66,281.995,2065.06,3295.28,279.264,2064.54,3295.08,264.263,2054.75,3302.94,259.956,2053.85,3302.8,275.319,2046.93,3313.12,271.203,2048.61,3313.52,257.133,2009.99,3619.38,230.933,2022.39,3592.13,212.443,2008.02,3613.29,205.321,1978.73,3657.84,216.746,1994.95,3639.02,223.726,1993.62,3634.15,198.26,1977.67,3653.86,191.659,1974.37,3648.06,167.319,1989.99,3627.45,173.41,1984.4,3619.45,149.208,1968.98,3640.66,143.75,1894.36,3740.07,189.825,1893.47,3741.66,211.716,1916.09,3720.44,219.423,1917,3718.97,196.473,1889.19,3730.62,147.421,1892.95,3736.33,168.25,1915.8,3715.38,173.896,1912.19,3709.96,152.085,1935.17,3689.1,156.752,1938.49,3694.28,179.548,1960.48,3672.58,185.385,1957.34,3667.57,161.781,1931.07,3693.69,301.177,1953.05,3674.79,310.892,1957.34,3677.75,286.032,1935.35,3697.66,277.078,1886.46,3730.49,281.391,1908.9,3712.32,291.37,1913.14,3717.28,268.057,1890.6,3736.53,258.91,1893.05,3740.23,235.551,1915.54,3719.97,243.936,1958.23,3620.73,417.066,1987.91,3611.41,409.243,1971.73,3624.47,401.195,1924.63,3642.39,399.945,1941.53,3631.69,408.616,1955.36,3637.12,392.918,1938.65,3649.31,384.158,1952.07,3653.77,364.834,1968.7,3640.15,373.352,1980.26,3639.96,352.081,1963.84,3655,343.662,2136.39,3355.88,512.409,2132.5,3368.29,521.873,2130.83,3356.25,522.348,2123.31,3340.1,504.426,2131.49,3346.27,508.993,2125.3,3346.02,520.091,2116.69,3339.19,515.57,2110.13,3341.85,525.305,2118.68,3348.93,528.923,2111.99,3356.18,533.72,2103.79,3349.01,531.135,1989.94,3356.62,160.942,1999.51,3350.99,153.83,1999.03,3349.68,164.87,1995.18,3363.1,184.793,1991.79,3358.91,173.266,2000.77,3352.73,176.836,2003.04,3358.12,188.136,2011.4,3354.96,190.342,2009.87,3349.03,180.2,2018.79,3349.36,181.715,2019.38,3354.9,190.111,2045.58,3585.5,305.047,2043.88,3587.41,330.283,2084.78,3537.5,335.195,2066.94,3563.11,319.838,2065.11,3564.42,343.312,2083.06,3538.66,355.488,2078.26,3537.49,374.999,2060.66,3565.15,366.213,2052.24,3560.87,383.345,2070.75,3532.24,388.437,2012.6,3537.47,277.327,2011.09,3562.96,266.427,2018.35,3552.71,276.741,2051.12,3506.73,304.134,2030.54,3524.01,289.893,2035.71,3537.96,290.239,2059.41,3517.14,305.713,2072.32,3526.26,309.733,2051.32,3549.02,292.949,2061.98,3558.42,299.152,2080.82,3534.48,317.462,1993.84,3520.13,346.997,1979.72,3532.3,314.372,1998.15,3521.99,323.587,2028.29,3490.31,362.609,2010.73,3505.89,355.098,2015.26,3508.65,332.445,2032.22,3492.14,342.244,2036.84,3493.21,321.946,2019.91,3508.23,310.314,2025.37,3513.48,293.145,2043.29,3499.15,306.323,2019.57,3569.98,375.846,1995.61,3565.1,369.241,2004.23,3553.34,371.121,2059.38,3524.26,391.271,2037.87,3550.97,384.129,2020.94,3536.71,377.913,2045.03,3514.09,388.019,2035.59,3502.31,385.611,2014.61,3521.06,377.681,2011.91,3507.93,373.496,2030.26,3492.28,379.711,1995.9,3516.4,246.447,1982.38,3526.45,243.099,1977.47,3519.82,220.141,1993.35,3510.42,224.12,2021.68,3489.92,252.534,2008.51,3503.64,250.233,2008.17,3499.11,228.712,2023.27,3485.94,233.491,2025.22,3482.25,215.124,2008.04,3492.83,208.914,2010.08,3492.72,192.389,2029.84,3483.59,199.848,2031.77,3564.47,269.123,2016.28,3549.62,268.675,2064.3,3516.98,274.464,2046.9,3542.97,273.142,2028.76,3533.04,272.678,2047.83,3510.54,274.721,2034.96,3503.23,273.399,2019.95,3521.88,271.867,2012.97,3512.05,266.66,2024.61,3497.39,267.619,2039.3,3560.49,196.152,2043.5,3567.58,220.037,2078.55,3511.65,216.921,2060.35,3536.94,206.171,2063.65,3542.53,227.442,2081.08,3515.9,234.712,2080.94,3519.35,252.482,2064.19,3547.3,248.497,2060.17,3549.08,266.678,2076.38,3520.29,267.403,1997.37,3512.6,179.297,1994.08,3534.33,166.558,2004.83,3527.71,176.394,2038.66,3487.9,196.765,2017.36,3500.87,188.356,2025.75,3513.84,187.616,2048.75,3495.37,196.839,2061.94,3501.06,197.299,2040.65,3522.23,185.801,2052.17,3529.71,188.057,2072.27,3506.69,201.945,2019.28,3592.31,406.089,2008.07,3592.57,426.021,2055.12,3554.81,441.44,2038.16,3574.09,423.398,2027.98,3573.38,442.622,2046.13,3551.34,459.27,2037.15,3545.12,473.383,2016.42,3570.5,458.723,2004,3563.39,469.566,2028.93,3538.36,481.587,1996.91,3539.15,378.442,2003.56,3558.1,377.364,2033.22,3515.69,410.451,2013.93,3529.17,392.479,2019.3,3546.34,390.733,2040.72,3529.43,411.033,2051.52,3541.08,415.777,2033.29,3559.05,396.352,2041.08,3569.45,405.301,2057.15,3551.31,424.208,1955.22,3529.79,424.16,1942.6,3534.81,399.325,1965.03,3525.95,411.391,2000.45,3503.94,447.623,1978.56,3517.58,435.223,1986.66,3515.22,423.341,2007.09,3502.5,436.326,2015.34,3501.97,423.868,1996.78,3512.88,409.181,2007.02,3516.03,398.551,2024.4,3505.55,414.299,1971.36,3572.41,455.083,1938.34,3576.32,440.023,1961.81,3559.18,451.177,2019.61,3532.96,482.654,1993.62,3554.05,469.438,1984.59,3542.03,463.642,2008.67,3524.97,477.675,2000.3,3515.58,469.214,1977.34,3531.73,456.383,1973.35,3523.62,445.63,1996.74,3508.54,458.038,1971.73,3510.52,100.623,1957.72,3523.16,95.1818,1968.34,3533.95,93.9165,1981.89,3519.53,98.7453,1999.74,3483.32,109.911,1985.73,3497.34,105.444,1995.49,3504.78,103.195,2009.18,3489.07,107.676,2018.77,3496.26,107.829,2005.98,3513.73,103.354,2014.72,3522.11,107.919,2026.82,3503.42,111.91,1972.41,3497.05,154.038,1961,3504.03,153.295,1955.53,3504.62,132.349,1968.21,3497.17,135.127,1993.31,3479.21,155,1983.06,3488.55,155.15,1980.06,3488.5,137.946,1991.69,3477.96,139.855,1990.26,3477.5,126.53,1977.41,3488.71,123.69,1977.18,3492.56,111.753,1991.79,3479.75,115.642,2011.27,3541.31,165.387,1996.97,3526.39,168.264,2025.49,3507.67,165.891,2017.74,3526.33,167.095,2004.28,3515.21,170.026,2014.55,3499.3,168.607,2005.82,3490.45,169.461,1996.2,3502.85,171.376,1989.34,3492.3,167.661,1998.53,3482.71,166.227,2009.4,3545.29,115.625,2015.16,3549.29,129.633,2033.01,3507.51,121.285,2022.21,3527.09,118.176,2026.84,3530.62,131.64,2036.29,3510.6,133.605,2037.19,3513.69,147.155,2029.35,3534.19,146.584,2026.79,3535.37,159.714,2033.45,3514.72,159.15,2130.76,3425.69,373.594,2127.93,3424.65,358.45,2117.74,3455.79,350.587,2120.48,3456.66,365.227,2123.97,3424.49,405.538,2128.89,3425.46,390.189,2118.86,3456.45,382.412,2114.03,3455.77,398.272,2100.95,3483.81,389.408,2106.26,3484.39,373.908,2095.55,3510.03,365.922,2090.54,3508.47,381.926,2099.33,3409.69,344.682,2087.75,3406.26,347.726,2078.89,3433.31,339.724,2091.38,3437.33,336.332,2121.38,3419.81,350.853,2111.07,3414.4,346.703,2103.89,3444.12,339.329,2112.65,3450.66,343.616,2100.97,3478.56,337.121,2093.55,3471.96,331.9,2084.01,3495.43,322.782,2091.05,3501.35,328.945,2054.44,3449.67,375.551,2042.48,3472.53,370.876,2046.26,3472.25,351.255,2058.75,3450.91,359.049,2069.91,3398.17,386.574,2063.12,3425.13,380.868,2067.82,3427.56,365.784,2074.5,3400.18,372.124,2079.65,3402.45,357.952,2072.02,3429.34,350.738,2085.47,3476.53,402.734,2076.46,3499.13,395.187,2067.37,3492.96,395.686,2074.67,3468.5,402.529,2103,3416.28,419.707,2095.29,3446.88,413.071,2081.69,3437.95,410.252,2089.4,3409.23,415.927,2077.64,3402.96,409.674,2070.44,3430.56,404.476,2063.55,3423.92,394.447,2070.38,3396.94,400.063,2045.36,3456.7,257.629,2033.35,3474.75,255.98,2036.27,3470.41,238.529,2048.65,3454.52,242.37,2061.86,3415.99,264.837,2054.64,3437.29,260.377,2057.73,3437.37,246.177,2063.62,3416.3,251.231,2065.93,3417.13,237.01,2060.51,3436.96,231.696,2065.88,3438.6,221.676,2071.5,3419.42,226.793,2087.98,3461.49,279.121,2080.54,3489.12,275.255,2066.97,3487.16,276.926,2074.65,3461.31,279.346,2098.87,3412.82,287.801,2093.54,3436.68,282.822,2081.11,3437.04,282.43,2086.79,3413.69,286.653,2074.49,3414.88,283.348,2067.94,3437.68,279.564,2056.43,3438.5,271.402,2064.36,3416.2,275.83,2111.29,3409.98,247.756,2105.1,3410.35,235.348,2097.81,3435.78,228.905,2103.61,3435.66,240.75,2112.84,3410.71,275.02,2113.64,3410.24,262.095,2106.47,3435.74,255.483,2106.05,3435.83,269.498,2100.75,3461.14,262.924,2100.59,3460.16,248.239,2093.92,3487.07,241.824,2093.9,3489.47,256.773,2080.05,3416.55,224.691,2075.36,3437.28,219.573,2097.82,3412.11,229.211,2089.09,3414.22,226.04,2084.18,3436.91,220.933,2091.45,3436.36,223.517,2085.67,3455.24,218.008,2078.4,3454.31,215.339,2072.37,3473.57,207.652,2080.74,3476.07,211,2095.32,3457.26,509.309,2107.93,3433.27,516.401,2115.84,3434.42,505.956,2103.56,3459.66,498.562,2066.45,3500.27,491.221,2080.41,3480.25,499.874,2088.34,3482.97,489.7,2074.01,3503.66,481.7,2082.53,3505.83,467.074,2095.71,3485.21,474.79,2097.08,3484.86,461.183,2085.68,3503.45,453.747,2125.73,3361,483.769,2114.34,3357.47,482.134,2107.62,3375.19,474.326,2120.88,3379.65,476.306,2137.81,3375.51,493.068,2133.86,3368.13,488.551,2130.12,3386.54,482.527,2134.38,3392.26,487.38,2130.05,3408.92,481.759,2124.76,3405.1,476.169,2116.72,3425.86,468.158,2090.65,3352.42,512.706,2089.2,3357.3,523.063,2081.07,3374.65,517.485,2082.41,3370.52,505.873,2104.88,3351.89,490.049,2097.08,3350.16,501.29,2089.66,3368.64,493.991,2097.64,3370.65,482.106,2090.59,3392.36,475.325,2082.49,3390.76,486.75,2075.49,3413.06,479.611,2061.17,3440.75,506.327,2072.08,3420.19,514.482,2081.93,3424.77,521.611,2070.29,3446.65,513.884,2037.95,3480.73,490.635,2049.51,3461.73,498.146,2058.76,3468.85,504.267,2048.44,3488.71,495.512,2055.49,3493.63,496.948,2066.71,3473.9,506.432,2073.47,3477.23,505.255,2060.84,3496.89,495.847,2012.96,3456.55,117.829,2012.39,3468.8,113.62,2020.9,3473.35,112.838,2021.48,3459.81,116.54,2011.65,3430.78,124.65,2012.53,3444.21,121.174,2021.72,3447.02,119.978,2021.71,3433.42,123.53,2031.17,3436.75,124.489,2030.61,3450.39,120.832,2037.42,3453.88,124.578,2038.3,3440.2,128.572,2002.09,3432.58,162.757,2008.36,3433.79,172.503,2008.9,3447.15,168.53,2002.9,3445.33,159.017,1999.3,3429.59,138.876,1999.67,3430.58,150.585,2000.82,3443.16,147.049,2000.62,3442.34,135.494,2001.74,3453.83,132.034,2002.04,3454.84,144.242,2000.2,3465.63,142.014,2000.38,3464.37,128.309,2033.02,3472.53,166.592,2032.35,3488.4,164.012,2024.31,3483.17,167.229,2024.82,3468.62,168.683,2035.2,3442.9,174.449,2034.31,3458.13,170.006,2025.28,3454.8,171.679,2025.91,3440.17,175.882,2016.51,3437.41,175.795,2016.57,3451.35,171.831,2042.95,3441.17,137.08,2041.69,3455.2,132.757,2045.62,3443.6,159.353,2045.13,3442.26,147.673,2043.71,3456.79,143.127,2043.97,3458.84,154.912,2043.26,3473.99,151.041,2043.29,3471.7,139.507,2041.84,3489.19,135.998,2041.9,3491.63,147.383,2139.03,3312.51,394.625,2138.13,3315.33,379.86,2140.14,3339.38,374.601,2141.49,3337.85,390.388,2130.35,3311.31,423.628,2135.91,3310.95,409.734,2138.43,3336.64,406.752,2132.57,3336.67,421.647,2132.44,3364.63,417.093,2138.03,3365.1,402.221,2134.83,3394.57,396.652,2129.83,3393.55,411.069,2106.1,3313.42,358.629,2094.06,3317.42,358.706,2094.15,3336.64,356.18,2107.08,3335.05,354.96,2130.38,3312.62,369.819,2118.6,3311.6,363.124,2120.57,3334.56,359.291,2132.56,3336.78,365.102,2131.23,3363.35,361.358,2119.7,3359.9,355.938,2118.11,3385.64,353.641,2127.97,3390.43,358.384,2085.34,3312.43,367.72,2089.24,3295.81,373.046,2085.03,3293.9,383.992,2079.56,3309.38,380.705,2084.64,3353.24,364.209,2084.52,3332.01,365.58,2078.44,3328.25,379.242,2078.15,3350.01,378.253,2073.3,3349.53,393.322,2073.56,3328.59,394.257,2074.31,3329.52,408.542,2074.23,3349.56,407.74,2084.06,3308.84,419.894,2096.1,3307.5,428.428,2082.6,3352.08,418.844,2082.66,3328.92,420.6,2095.62,3328.84,429.029,2095.83,3354.79,426.447,2110.17,3359.63,430.918,2110.11,3332.97,434.39,2123.17,3336.68,432.386,2123.16,3363.55,427.987,2050.01,3332.67,251.518,2047.38,3331.43,267.257,2062.98,3374.35,244.867,2056.01,3353.3,248.014,2053.32,3351.28,263.479,2060.17,3372.66,259.601,2059.17,3373.47,274.444,2052.86,3353.16,279.107,2057.45,3353.53,292.374,2063.1,3373.24,287.18,2061.57,3328.37,304.282,2073.07,3321.68,308.136,2072.69,3368.97,295.754,2066.65,3347.77,301.272,2078.74,3340.72,305.67,2085.69,3363.51,300.199,2099.99,3359.85,301.957,2093.91,3337.14,307.109,2105.96,3333.95,302.891,2111.62,3355.9,297.985,2096.99,3303.33,266.496,2091.22,3305.99,253.443,2099.55,3326.76,247.492,2106.36,3325,261.181,2101.08,3308.3,294.323,2099.77,3304.3,280.967,2108.88,3325.47,277.112,2109.94,3328.98,291.613,2116.67,3353.51,286.556,2116.62,3351.72,272.264,2118.62,3380.32,267.745,2118.67,3379.84,280.208,2064.92,3324.47,238.63,2057.73,3334.82,239.599,2063.27,3354.96,236.406,2072.13,3345.65,234.387,2083.11,3309.58,246.164,2073.82,3315.77,241.574,2082.72,3336.99,237.372,2092.13,3331.19,241.112,2098.68,3357.2,237.239,2088.69,3362.12,233.654,2093.73,3388.66,231.297,2102.58,3385.07,235.392,2014.77,3356.97,147.105,2002.92,3361.64,143.491,2027.35,3396.18,132.96,2021.37,3374.14,139.335,2008.01,3376.95,136.754,2014.77,3396.46,131.226,2004.14,3398.83,131.571,1998.66,3382.62,135.975,1991.65,3389.21,139.112,1996.16,3401.95,135.787,1989.57,3381.57,178.148,1997.17,3384.02,188.6,1999.62,3395.77,185.669,1992.42,3394.19,174.97,1984.56,3377.79,153.38,1985.69,3378.83,165.742,1988.88,3391.26,162.179,1987.98,3390.42,149.495,1992.39,3403.39,145.85,1993.11,3404.54,158.299,1997.34,3417.92,154.31,1996.96,3416.6,142.11,2003.22,3381.73,194.097,2001.54,3368.88,193.582,2007.45,3365.92,195.959,2010.82,3379.04,197.34,2011.23,3408.94,186.583,2006.9,3394.84,191.376,2015.59,3392.95,194.334,2020.96,3408.69,188.588,2031.25,3408.98,188.473,2025.76,3392.24,194.942,2034.19,3391.1,191.777,2039.69,3408.58,184.413,2029.43,3356.61,165.227,2025.33,3356.18,153.93,2032.2,3373.89,145.846,2037.37,3374.86,157.188,2030.57,3367.22,188.251,2030.59,3360.59,177.342,2038.6,3378.19,170.322,2038.47,3384.24,182.595,2044.48,3404.37,174.707,2045.05,3400.53,162.494,2046.45,3424.8,153.767,2046.53,3425.98,165.103,1723.57,3788.72,267.799,1754.54,3789.99,277.409,1735.59,3808.23,260.714,1685.24,3827.52,230.648,1704.37,3807.94,249.155,1716.62,3826.5,244.051,1697.72,3845.14,227.618,1710.63,3860.19,221.652,1729.35,3842.63,236.367,1742.28,3855.54,224.674,1723.74,3871.94,211.776,1770.34,3849.81,221.336,1778.6,3857.36,202.889,1733.87,3881.94,197.25,1752.19,3866.07,209.474,1760.78,3872.97,191.513,1742.78,3888.22,179.995,1750.08,3891.77,161.677,1767.71,3877.21,172.576,1772.3,3877.86,152.535,1755.02,3891.79,142.365,1796.07,3858.49,142.265,1799.68,3850.12,122.197,1778.78,3872.32,131.864,1782.1,3863.16,111.645,1764.74,3863.91,81.4823,1782.69,3851.75,92.1736,1780.1,3837.63,74.6392,1761.92,3848.98,63.8205,1782.89,3771.88,54.5875,1772.62,3752.23,50.798,1756.14,3762.4,39.118,1766.14,3782.18,42.887,1795.84,3809.45,72.4554,1790.5,3791.17,62.1827,1773.32,3801.94,50.8291,1778.18,3820.52,61.2453,1760.45,3831.35,49.8517,1743.59,3860.24,53.111,1747.86,3721.82,56.1823,1738.92,3713.95,63.2534,1721.7,3722.04,55.503,1731.11,3730.02,47.2805,1765.15,3741.43,49.9935,1756.67,3731.34,51.8996,1740.24,3740.22,41.7799,1748.79,3750.76,39.0322,1729.77,3708.25,70.858,1721.41,3704.54,80.2963,1712.94,3717.34,63.7754,1704.83,3714.94,73.6815,1697.23,3713.81,85.5257,1690.69,3714.5,98.1282,1696.83,3701.39,123.118,1700.79,3684.41,151.021,1688.72,3703.56,143.109,1681.71,3717.07,116.277,1674.94,3721.79,134.744,1670.68,3728.07,153.668,1669.26,3736.04,172.462,1681.09,3726.61,209.921,1687.76,3741.56,234.477,1669.21,3748.49,195.897,1673.75,3762.61,217.478,1681.8,3775.11,235.692,1694.93,3787.43,249.532,1806.19,3556.23,378.78,1813.12,3571.75,390.834,1811.3,3544.9,393.569,1806.49,3530.78,379.256,1777.44,3532.77,337.749,1789.69,3541.96,359.451,1793.12,3515.45,364.999,1778.97,3506.58,349.381,1772.79,3483,360.702,1787.87,3490.22,370.146,1786.08,3470.77,373.861,1771.47,3464.81,364.897,1704.73,3558.09,337.885,1722.25,3544.53,330.758,1722.79,3489.89,376.534,1711.94,3524.75,355.063,1727.83,3514.53,345.523,1734.49,3482.6,363.025,1748.48,3479.22,357.393,1745.91,3506.25,338.878,1763.23,3502.75,339.737,1759.83,3479.21,356.429,1732.68,3599.96,398.166,1728.1,3636.12,378.575,1709.64,3627.73,363.513,1714.12,3591.15,382.979,1747.84,3524.1,427.467,1739.57,3562.25,414.192,1721.45,3553.09,399.637,1730.69,3513.28,412.846,1723.9,3499.02,395.078,1709.48,3543.34,382.859,1704.86,3535.62,366.332,1721.59,3494.99,386.166,1801.88,3581.25,399.058,1786.34,3590.32,405.721,1795.62,3516.29,409.616,1804.15,3551.71,404.712,1790.33,3557.89,413.213,1788.77,3515.3,416.299,1781.83,3518.33,423.465,1776.17,3564.3,419.564,1761.71,3568.96,420.701,1770.17,3527.19,430.443,1776.72,3439.25,373.194,1791.55,3447.21,384.499,1778.41,3401.88,398.689,1778.29,3417.96,385.789,1791.97,3425.66,396.539,1790.71,3408.28,407.808,1800.5,3417.9,417.191,1802.89,3436.82,407.195,1807.58,3449.51,419.305,1805.21,3429.48,427.252,1723.75,3433.76,399.388,1724.27,3459.06,387.984,1734.78,3455.15,374.902,1734.55,3429.78,386.727,1730.96,3394.42,421.735,1726.49,3412.29,411.111,1737.51,3408.52,399.452,1741.13,3392.17,411.057,1752.25,3393.89,400.564,1749.27,3409.65,387.777,1762.26,3414.39,380.525,1763.98,3399.61,393.383,1757.62,3463.21,449.5,1756.88,3486.5,439.949,1738.75,3477.9,428.665,1740.9,3453.7,440.153,1761.17,3419.48,461.961,1759.36,3440.15,457.345,1743.45,3429.74,449.138,1746.58,3409.99,455.143,1733.36,3403.82,445.213,1729.08,3423.71,437.809,1721.85,3418.41,423.549,1725.82,3400.25,432.191,1804.97,3471.05,424.655,1795.54,3499.03,415.344,1787.34,3499.3,422.776,1796.75,3470.88,436.89,1802.32,3427.91,439.141,1804.57,3448.6,432.631,1797,3447.7,444.468,1795.51,3427.1,449.809,1786.78,3428.05,458.358,1787.6,3448.9,453.853,1776.36,3449.13,458.622,1776.26,3429.15,462.603,1788.63,3788.88,278.269,1777.97,3773.49,293.249,1832.17,3749.68,302.255,1810.31,3769.68,290.739,1801.41,3756.05,308.013,1825.06,3736.94,320.95,1817.58,3722.2,337.686,1791.96,3739.68,322.879,1782.47,3721.78,336.061,1809.8,3705.54,352.43,1859.27,3691.13,361.486,1851.12,3676.05,376.056,1864.99,3664.39,385.313,1875.42,3678.66,371.712,1874.38,3714.06,324.308,1867.04,3703.51,343.972,1885.36,3689.68,354.653,1894.82,3698.71,335.118,1915.07,3682.94,345.654,1903.61,3675.51,364.979,1921.88,3661.38,375.34,1935.25,3667.08,356.161,1844.33,3602.94,407.46,1853.13,3597,413.208,1865.48,3611.6,414.041,1855.5,3619.96,407.244,1828.59,3615.69,395.334,1835.7,3609.02,401.273,1845.75,3628.16,399.704,1836.77,3636.95,392.684,1843.69,3657.64,386.295,1854.95,3647.5,394.691,1825.3,3557.19,342.372,1840.94,3558.63,347.623,1839.62,3564.53,371.928,1827.6,3564.63,366.766,1793.18,3552.75,332.133,1809.34,3554.5,337.312,1815.27,3563.72,361.989,1801.5,3563.67,358.004,1812.09,3577.32,377.975,1820.86,3575.35,381.581,1828.85,3590.52,396.515,1822.44,3594.25,392.33,1910.04,3570.72,429.068,1920.32,3585.08,432.901,1871.97,3588.24,419.044,1891.3,3579.58,423.804,1901.97,3593.89,426.618,1883.58,3602.86,420.766,1896.24,3618.27,418.825,1914.01,3609.15,426.058,1926.8,3621.99,420.115,1909.77,3631.33,411.639,1928.41,3540.07,372.527,1917.49,3543.7,391.949,1870.55,3554.21,355.55,1899.63,3547.52,363.791,1891.98,3551.49,384.691,1865.98,3558.58,378.151,1863.06,3564.65,397.134,1886.2,3557.15,402.084,1883.02,3567.3,415.512,1862.9,3575.28,411.852,1950.28,3542.89,279.922,1947.01,3541.64,304.361,1882.19,3559.25,259.733,1916.1,3550.39,269.837,1913.3,3548.12,294.087,1879.8,3555.94,284.193,1877.82,3554.14,308.22,1910.68,3546.94,318.266,1908.21,3546.44,342.474,1875.94,3553.67,332.269,1823.81,3556.76,291.949,1823.62,3555.13,316.526,1803.74,3551.36,311.01,1802.9,3554.23,285.891,1824.48,3565.12,242.546,1824,3560.77,267.406,1801.85,3559.92,260.765,1800.92,3564.23,235.636,1779.09,3564.49,230.352,1780.97,3559.6,256.598,1761.27,3563.62,253.499,1758.92,3569.17,226.226,1786.99,3589.34,140.644,1791.79,3598.57,120.457,1812.63,3580.21,125.465,1808.47,3575.43,146.069,1780.04,3575.04,182.654,1783.07,3581.7,161.419,1805.2,3572.31,167.254,1802.5,3570.05,188.807,1826.59,3566.73,195.7,1828.53,3564.54,173.719,1707.41,3657.18,172.614,1714.85,3660.12,151.569,1739.15,3604.58,174.53,1722.47,3629.7,173.346,1729.11,3636.01,152.378,1744.74,3613.81,153.833,1751.62,3624.16,133.643,1737.56,3643.82,131.997,1747.32,3652.56,112.314,1759.56,3635.52,114.159,2095.78,3362.04,530.323,2104.88,3368.86,533.483,2080.18,3398.95,521.048,2087.98,3379.19,526.382,2097.8,3385.14,530.672,2090.42,3403.93,526.724,2100.31,3408.42,528.355,2107.15,3390.7,531.438,2115.07,3394.64,529.039,2108.77,3411.52,526.198,2046.34,3472,446.96,2054.54,3476.78,439.517,2067.47,3458.12,448.564,2058.9,3453.81,455.275,2034.14,3470.86,468.29,2039.67,3470.42,457.375,2052.02,3452.15,465.295,2046.56,3452.54,475.801,2058.65,3433.64,483.69,2064.56,3433.23,472.994,2083.72,3498.49,447.34,2095.02,3481.53,455.504,2067.4,3484.44,437.201,2077.94,3492.44,442.042,2090.07,3475.45,451.322,2080.37,3465.97,447.218,2092.69,3443.38,454.813,2103.41,3450.56,458.299,2125.67,3381.96,526.526,2131.12,3381.86,518.941,2116.22,3413.02,520.368,2121.83,3396.33,523.548,2127.96,3396.45,515.086,2123.34,3413.09,510.997,2129.49,3412.39,498.223,2133.21,3395.99,503.707,2135.26,3395.67,493.112,2131.54,3411.47,487.776,1739.8,3772.73,27.0557,1723.48,3748.84,31.2667,1822.71,3828.01,180.677,1838.95,3806.58,188.493,1855.05,3784.88,196.261,1871.85,3763.84,204.06,1871.37,3761.37,227.355,1868.7,3756.54,250.059,1864.46,3749.31,271.746,1858.75,3740.06,292.462,1867.74,3752.42,142.006,1871.5,3758.41,162.202,1872.93,3762.2,182.989,1772.68,3619.35,116.73,1947.06,3669.54,335.043,1952.03,3660.72,139.102,1946.66,3653.21,120.076,1939.11,3643.98,102.874,1929.76,3633.82,88.4511,1918.13,3622.02,77.63,2124.05,3292.84,378.82,2131.39,3295.74,386.906,2114.54,3293.13,372.143,2104.72,3295.4,366.854,2095.42,3300.07,364.548,1837.26,3687.78,366.882,1832.41,3668.1,378.463,1827.66,3646.08,386.542,1821.85,3622.72,390.692,1816.44,3598.03,389.969,1793.83,3401.5,427.703,1798.89,3410.7,434.804,1785.69,3394.46,420.683,1776.23,3389.68,414.142,1765.14,3388.17,409.079,1755.99,3383.21,416.225,1747.18,3381.75,424.567,1739.63,3382.29,432.194,1734.44,3385.75,438.967,1757.09,3575.3,199.197,1734.89,3596.67,195.941,1717.41,3624.44,194.931,1702.15,3655.55,194.315,2087.95,3293.88,294.111,2085.88,3290.85,284.086,2083.31,3289.74,273.456,2079.79,3291.62,263.315,2073.02,3294.42,256.662,2066.21,3299.8,251.583,2059.59,3306.91,247.391,2054.68,3315.66,245.51,1925.19,3687.47,324.511,1903.17,3705.16,313.869,1880.82,3722.36,302.997,2139.07,3360.14,501.475,2134.94,3352.19,497.85,2128.31,3346.39,494.579,2120.49,3344.07,492.971,2113.67,3339.48,500.588,2107.05,3338.81,509.964,2101.13,3341.14,519.386,2097.52,3346.13,526.369,1984.42,3366.01,157.557,1986.02,3367,168.564,1989.83,3369.29,179.94,1996.71,3372.2,189.659,2084.01,3504.4,391.559,2089.98,3489.79,268.791,2073.26,3530.67,443.891,2069.7,3530.82,458.6,2060.71,3525.96,473.142,2053.35,3520.44,483.557,2047.93,3515.66,488.841,2041.58,3493.33,427.443,2053.52,3501.22,424.899,2063.86,3511.2,428.658,2070.83,3521.33,435.174,2017.75,3491.31,471.868,2019.88,3487.29,460.366,2025.64,3486.56,448.45,2032.92,3488.07,436.571,2042.37,3512.1,489.947,2035.18,3507.3,488.359,2023.7,3499.27,483.648,2004.62,3465.56,117.908,2038.28,3492.72,157.728,2121.4,3391.22,421.454,2115.24,3421.8,416.78,2106.17,3453.14,409.562,2093.77,3481.4,398.981,2074.89,3369.71,406.698,2073.33,3370.53,392.418,2077.66,3371.72,377.198,2084.5,3374.37,362.581,2109.75,3386.68,424.662,2096.83,3380.98,421.887,2083.65,3375.4,416.102,2067.88,3393.38,282.863,2063.66,3393.95,270.343,2064.79,3394.06,255.24,2068.18,3395.4,240.339,2096.84,3460.98,274.147,2102.16,3435.51,279.408,2108.74,3410.51,285.037,2114.6,3379,290.414,2104.91,3383.67,293.905,2092.74,3387.35,293.136,2078.86,3390.9,290.253,2056.68,3436.29,494.645,2044.77,3455.72,486.567,2032.41,3474.18,479.085,2005.4,3454.21,122.636,2004.4,3442.17,126.231,2003.08,3428.96,129.563,2001,3415.14,132.409,2010.24,3416.11,127.919,2021.2,3417.85,127.354,2031.36,3420.49,128.794,2039.41,3475.39,161.502,2040.77,3460.23,165.339,2042.25,3444.57,169.734,2042.96,3427.07,174.743,2035.78,3426.34,179.704,2026.34,3424.84,181.344,2015.9,3423.22,181.025,1710.61,3885.25,198.06,1742.98,3902.02,134.612,1732.32,3760.18,27.6195,1805.17,3577.03,376.798,1789.98,3560.01,353.905,1777.17,3549.26,327.013,1854.1,3729.67,313.651,1848.73,3717.38,333.34,1843.14,3703.67,351.334,1843.29,3557.45,322.093,1847.98,3565.61,249.522,1846.08,3560.88,273.994,1844.63,3558.17,298.027,1824.78,3568.24,217.61,1799.96,3568.03,210.425,1777.34,3569.47,204.128,1767.42,3606.3,136.569,1763.1,3594.57,157.075,1759.6,3584.04,177.863,1723.64,3783,14.6609], + "faces" : [43,0,1,2,3,0,0,1,2,3,0,1,2,3,43,4,5,6,7,0,4,5,6,7,4,5,6,7,43,8,9,10,11,0,8,9,10,10,8,9,10,11,43,12,13,14,15,0,11,11,12,12,12,13,14,15,43,16,15,14,17,0,13,12,12,14,16,15,14,17,43,18,19,20,21,0,15,15,16,16,18,19,20,21,43,22,23,24,25,0,17,18,18,19,22,23,24,25,43,26,27,28,29,0,20,6,6,21,26,27,28,29,43,30,31,32,33,0,9,9,10,10,30,31,32,33,43,34,35,36,37,0,11,11,12,12,34,35,36,37,43,38,39,40,41,0,22,22,23,23,38,39,40,41,43,42,43,44,45,0,24,25,26,27,42,43,44,45,43,46,47,48,49,0,28,16,16,16,46,47,48,49,43,50,51,52,53,0,15,15,16,16,50,51,52,53,43,54,55,56,57,0,29,30,30,30,54,55,56,57,43,58,59,60,61,0,31,32,33,34,58,59,60,61,43,62,63,64,65,0,10,9,10,35,62,63,64,65,43,66,67,68,69,0,36,11,12,37,66,67,68,69,43,70,71,72,73,0,38,22,23,39,70,71,72,73,43,74,75,76,77,0,40,41,42,43,74,75,76,77,43,78,79,80,81,0,44,45,18,18,78,79,80,81,43,82,81,80,83,0,46,18,18,47,82,81,80,83,43,84,85,86,87,0,48,49,26,50,84,85,86,87,43,88,89,90,91,0,51,52,2,53,88,89,90,91,43,92,93,94,95,0,54,21,6,55,92,93,94,95,43,96,97,86,98,0,27,26,26,27,96,97,86,98,43,99,100,24,101,0,56,18,18,56,99,100,24,101,43,102,103,104,105,0,57,23,23,57,102,103,104,105,43,106,107,108,109,0,58,30,30,58,106,107,108,109,43,110,111,112,113,0,14,12,12,14,110,111,112,113,43,114,115,104,116,0,39,23,23,39,114,115,104,116,43,117,118,24,119,0,59,18,18,59,117,118,24,119,43,120,121,76,122,0,60,42,42,60,120,121,76,122,43,123,124,125,126,0,53,2,2,53,123,124,125,126,43,127,128,86,129,0,25,26,26,25,127,128,86,129,43,130,131,108,132,0,61,30,30,61,130,131,108,132,43,133,134,135,136,0,62,16,16,62,133,134,135,136,43,137,138,28,139,0,55,6,6,55,137,138,28,139,43,140,141,142,143,0,35,10,10,35,140,141,142,143,43,144,145,112,146,0,63,12,12,63,144,145,112,146,43,147,148,76,149,0,64,42,42,64,147,148,76,149,43,150,151,152,153,0,19,19,18,18,150,151,152,153,43,154,155,156,157,0,39,39,23,23,154,155,156,157,43,158,157,156,159,0,57,23,23,57,158,157,156,159,43,160,161,162,163,0,58,30,30,58,160,161,162,163,43,164,165,166,167,0,14,12,12,14,164,165,166,167,43,168,169,170,171,0,39,23,23,39,168,169,170,171,43,172,173,174,175,0,43,43,42,42,172,173,174,175,43,176,177,166,165,0,63,63,12,12,176,177,166,165,43,178,179,180,181,0,50,26,26,50,178,179,180,181,43,182,183,184,185,0,58,58,30,30,182,183,184,185,43,186,187,188,189,0,15,15,16,16,186,187,188,189,43,190,191,192,193,0,52,2,2,52,190,191,192,193,43,194,195,196,197,0,55,6,6,55,194,195,196,197,43,198,199,200,201,0,11,11,12,12,198,199,200,201,43,202,201,200,203,0,63,12,12,63,202,201,200,203,43,204,205,196,195,0,21,21,6,6,204,205,196,195,43,206,207,208,209,0,19,19,18,18,206,207,208,209,43,210,211,212,213,0,39,39,23,23,210,211,212,213,43,214,215,216,217,0,65,30,30,65,214,215,216,217,43,218,219,44,43,0,25,26,26,25,218,219,44,43,43,220,221,72,71,0,22,23,23,22,220,221,72,71,43,222,223,224,225,0,56,18,18,56,222,223,224,225,43,226,227,228,229,0,43,43,42,42,226,227,228,229,43,230,231,232,233,0,63,63,12,12,230,231,232,233,43,234,235,236,237,0,9,10,10,9,234,235,236,237,43,238,239,240,241,0,11,11,12,12,238,239,240,241,43,242,243,244,245,0,60,42,42,60,242,243,244,245,43,246,247,248,249,0,21,21,6,6,246,247,248,249,43,250,251,2,252,0,52,2,2,52,250,251,2,252,43,253,254,255,256,0,66,18,18,66,253,254,255,256,43,257,258,6,5,0,5,6,6,5,257,258,6,5,43,259,260,261,262,0,67,67,9,9,259,260,261,262,43,263,264,265,266,0,22,23,23,22,263,264,265,266,43,267,268,269,270,0,65,30,30,65,267,268,269,270,43,271,272,20,19,0,15,16,16,15,271,272,20,19,43,273,274,275,276,0,15,15,16,16,273,274,275,276,43,277,278,279,280,0,30,30,30,30,277,278,279,280,43,281,282,56,283,0,61,30,30,61,281,282,56,283,43,284,285,48,47,0,16,16,16,16,284,285,48,47,43,286,287,275,274,0,15,16,16,15,286,287,275,274,43,288,289,290,291,0,30,30,30,30,288,289,290,291,43,292,293,294,295,0,61,30,30,61,292,293,294,295,43,296,297,298,299,0,16,16,16,16,296,297,298,299,43,300,301,20,302,0,68,69,16,62,300,301,20,302,43,303,304,305,306,0,70,71,2,72,303,304,305,306,43,307,306,305,308,0,73,72,2,74,307,306,305,308,43,309,310,311,312,0,75,76,77,78,309,310,311,312,43,313,314,125,315,0,79,2,2,52,313,314,125,315,43,316,317,135,318,0,80,81,16,15,316,317,135,318,43,319,320,108,321,0,65,30,30,65,319,320,108,321,43,322,323,279,324,0,82,83,30,30,322,323,279,324,43,325,326,36,327,0,84,14,12,37,325,326,36,327,43,328,329,265,330,0,85,86,23,57,328,329,265,330,43,331,332,90,89,0,52,2,2,52,331,332,90,89,43,333,334,184,335,0,61,61,30,30,333,334,184,335,43,336,337,180,179,0,25,25,26,26,336,337,180,179,43,338,339,340,341,0,50,26,26,50,338,339,340,341,43,342,343,344,345,0,87,88,89,90,342,343,344,345,43,345,344,346,347,0,90,89,91,92,345,344,346,347,43,343,348,349,344,0,88,93,94,89,343,348,349,344,43,344,349,350,346,0,89,94,95,91,344,349,350,346,43,342,345,351,352,0,87,90,96,97,342,345,351,352,43,352,351,353,354,0,97,96,98,99,352,351,353,354,43,345,347,355,351,0,90,92,100,96,345,347,355,351,43,351,355,356,353,0,96,100,101,98,351,355,356,353,43,342,352,357,358,0,87,97,102,103,342,352,357,358,43,358,357,359,360,0,103,102,104,105,358,357,359,360,43,352,354,361,357,0,97,99,106,102,352,354,361,357,43,357,361,362,359,0,102,106,107,104,357,361,362,359,43,342,358,363,343,0,87,103,108,88,342,358,363,343,43,343,363,364,348,0,88,108,109,93,343,363,364,348,43,358,360,365,363,0,103,105,110,108,358,360,365,363,43,363,365,366,364,0,108,110,111,109,363,365,366,364,43,367,368,369,370,0,112,113,114,115,367,368,369,370,43,370,369,371,372,0,115,114,116,117,370,369,371,372,43,368,373,374,369,0,113,118,119,114,368,373,374,369,43,369,374,375,371,0,114,119,120,116,369,374,375,371,43,367,370,376,377,0,112,115,121,122,367,370,376,377,43,377,376,378,379,0,122,121,123,124,377,376,378,379,43,370,372,380,376,0,115,117,125,121,370,372,380,376,43,376,380,381,378,0,121,125,126,123,376,380,381,378,43,367,377,382,383,0,112,122,127,128,367,377,382,383,43,383,382,384,385,0,128,127,129,130,383,382,384,385,43,377,379,386,382,0,122,124,131,127,377,379,386,382,43,382,386,387,384,0,127,131,132,129,382,386,387,384,43,367,383,388,368,0,112,128,133,113,367,383,388,368,43,368,388,389,373,0,113,133,134,118,368,388,389,373,43,383,385,390,388,0,128,130,135,133,383,385,390,388,43,388,390,391,389,0,133,135,136,134,388,390,391,389,43,392,393,394,395,0,137,138,139,140,392,393,394,395,43,395,394,396,397,0,140,139,141,142,395,394,396,397,43,393,398,399,394,0,138,143,144,139,393,398,399,394,43,394,399,400,396,0,139,144,145,141,394,399,400,396,43,392,395,401,402,0,137,140,146,147,392,395,401,402,43,402,401,403,404,0,147,146,148,149,402,401,403,404,43,395,397,405,401,0,140,142,150,146,395,397,405,401,43,401,405,406,403,0,146,150,151,148,401,405,406,403,43,392,402,407,408,0,137,147,152,153,392,402,407,408,43,408,407,409,410,0,153,152,9,154,408,407,409,410,43,402,404,411,407,0,147,149,155,152,402,404,411,407,43,407,411,412,409,0,152,155,156,9,407,411,412,409,43,392,408,413,393,0,137,153,157,138,392,408,413,393,43,393,413,414,398,0,138,157,158,143,393,413,414,398,43,408,410,415,413,0,153,154,159,157,408,410,415,413,43,413,415,416,414,0,157,159,160,158,413,415,416,414,43,417,418,419,420,0,161,162,163,164,417,418,419,420,43,420,419,421,422,0,164,163,165,166,420,419,421,422,43,418,423,424,419,0,162,167,168,163,418,423,424,419,43,419,424,416,421,0,163,168,169,165,419,424,416,421,43,417,420,425,426,0,161,164,170,171,417,420,425,426,43,426,425,427,428,0,171,170,172,173,426,425,427,428,43,420,422,429,425,0,164,166,174,170,420,422,429,425,43,425,429,430,427,0,170,174,175,172,425,429,430,427,43,417,426,431,432,0,161,171,176,177,417,426,431,432,43,432,431,433,434,0,177,176,178,179,432,431,433,434,43,426,428,435,431,0,171,173,180,176,426,428,435,431,43,431,435,436,433,0,176,180,181,178,431,435,436,433,43,417,432,437,418,0,161,177,182,162,417,432,437,418,43,418,437,438,423,0,162,182,183,167,418,437,438,423,43,432,434,439,437,0,177,179,184,182,432,434,439,437,43,437,439,440,438,0,182,184,185,183,437,439,440,438,43,441,442,443,444,0,186,187,188,189,441,442,443,444,43,444,443,365,360,0,189,188,110,105,444,443,365,360,43,442,445,446,443,0,187,190,191,188,442,445,446,443,43,443,446,366,365,0,188,191,111,110,443,446,366,365,43,441,444,447,448,0,186,189,192,193,441,444,447,448,43,448,447,449,450,0,193,192,194,195,448,447,449,450,43,444,360,359,447,0,189,105,104,192,444,360,359,447,43,447,359,362,449,0,192,104,107,194,447,359,362,449,43,441,448,451,452,0,186,193,196,197,441,448,451,452,43,452,451,435,428,0,197,196,180,173,452,451,435,428,43,448,450,453,451,0,193,195,198,196,448,450,453,451,43,451,453,436,435,0,196,198,181,180,451,453,436,435,43,441,452,454,442,0,186,197,199,187,441,452,454,442,43,442,454,455,445,0,187,199,200,190,442,454,455,445,43,452,428,427,454,0,197,173,172,199,452,428,427,454,43,454,427,430,455,0,199,172,175,200,454,427,430,455,43,456,457,458,459,0,201,202,203,204,456,457,458,459,43,459,458,460,461,0,204,203,205,206,459,458,460,461,43,457,462,463,458,0,202,207,208,203,457,462,463,458,43,458,463,350,460,0,203,208,95,205,458,463,350,460,43,456,459,464,465,0,201,204,209,210,456,459,464,465,43,465,464,466,467,0,210,209,211,212,465,464,466,467,43,459,461,468,464,0,204,206,213,209,459,461,468,464,43,464,468,469,466,0,209,213,214,211,464,468,469,466,43,456,465,470,471,0,201,210,215,216,456,465,470,471,43,471,470,472,473,0,216,215,217,218,471,470,472,473,43,465,467,474,470,0,210,212,219,215,465,467,474,470,43,470,474,475,472,0,215,219,220,217,470,474,475,472,43,456,471,476,457,0,201,216,221,202,456,471,476,457,43,457,476,477,462,0,202,221,222,207,457,476,477,462,43,471,473,478,476,0,216,218,223,221,471,473,478,476,43,476,478,479,477,0,221,223,14,222,476,478,479,477,43,480,481,482,483,0,224,225,226,227,480,481,482,483,43,483,482,484,485,0,227,226,228,229,483,482,484,485,43,481,486,487,482,0,225,230,231,226,481,486,487,482,43,482,487,488,484,0,226,231,232,228,482,487,488,484,43,480,483,489,490,0,224,227,233,234,480,483,489,490,43,490,489,491,492,0,234,233,235,236,490,489,491,492,43,483,485,493,489,0,227,229,237,233,483,485,493,489,43,489,493,494,491,0,233,237,238,235,489,493,494,491,43,480,490,495,496,0,224,234,239,240,480,490,495,496,43,496,495,497,498,0,240,239,241,242,496,495,497,498,43,490,492,499,495,0,234,236,243,239,490,492,499,495,43,495,499,500,497,0,239,243,244,241,495,499,500,497,43,480,496,501,481,0,224,240,245,225,480,496,501,481,43,481,501,502,486,0,225,245,246,230,481,501,502,486,43,496,498,503,501,0,240,242,247,245,496,498,503,501,43,501,503,504,502,0,245,247,248,246,501,503,504,502,43,505,506,507,508,0,249,250,251,252,505,506,507,508,43,508,507,509,510,0,252,251,253,254,508,507,509,510,43,506,485,484,507,0,250,229,228,251,506,485,484,507,43,507,484,488,509,0,251,228,232,253,507,484,488,509,43,505,508,511,512,0,249,252,255,256,505,508,511,512,43,512,511,390,385,0,256,255,135,130,512,511,390,385,43,508,510,513,511,0,252,254,257,255,508,510,513,511,43,511,513,391,390,0,255,257,136,135,511,513,391,390,43,505,512,514,515,0,249,256,258,259,505,512,514,515,43,515,514,516,517,0,259,258,260,261,515,514,516,517,43,512,385,384,514,0,256,130,129,258,512,385,384,514,43,514,384,387,516,0,258,129,132,260,514,384,387,516,43,505,515,518,506,0,249,259,262,250,505,515,518,506,43,506,518,493,485,0,250,262,237,229,506,518,493,485,43,515,517,519,518,0,259,261,263,262,515,517,519,518,43,518,519,494,493,0,262,263,238,237,518,519,494,493,43,520,521,522,523,0,264,265,266,267,520,521,522,523,43,523,522,524,525,0,267,266,268,269,523,522,524,525,43,521,526,527,522,0,265,270,271,266,521,526,527,522,43,522,527,528,524,0,266,271,272,268,522,527,528,524,43,520,523,529,530,0,264,267,273,274,520,523,529,530,43,530,529,531,532,0,274,273,275,276,530,529,531,532,43,523,525,533,529,0,267,269,277,273,523,525,533,529,43,529,533,534,531,0,273,277,278,275,529,533,534,531,43,520,530,535,536,0,264,274,279,280,520,530,535,536,43,536,535,537,538,0,280,279,223,281,536,535,537,538,43,530,532,539,535,0,274,276,282,279,530,532,539,535,43,535,539,540,537,0,279,282,14,223,535,539,540,537,43,520,536,541,521,0,264,280,283,265,520,536,541,521,43,521,541,542,526,0,265,283,284,270,521,541,542,526,43,536,538,543,541,0,280,281,285,283,536,538,543,541,43,541,543,544,542,0,283,285,5,284,541,543,544,542,43,545,546,547,548,0,286,287,288,289,545,546,547,548,43,548,547,549,550,0,289,288,290,291,548,547,549,550,43,546,551,552,547,0,287,292,293,288,546,551,552,547,43,547,552,553,549,0,288,293,294,290,547,552,553,549,43,545,548,554,555,0,286,289,295,296,545,548,554,555,43,555,554,556,557,0,296,295,297,298,555,554,556,557,43,548,550,558,554,0,289,291,299,295,548,550,558,554,43,554,558,559,556,0,295,299,300,297,554,558,559,556,43,545,555,560,561,0,286,296,301,302,545,555,560,561,43,561,560,562,563,0,302,301,303,304,561,560,562,563,43,555,557,564,560,0,296,298,305,301,555,557,564,560,43,560,564,565,562,0,301,305,306,303,560,564,565,562,43,545,561,566,546,0,286,302,307,287,545,561,566,546,43,546,566,567,551,0,287,307,308,292,546,566,567,551,43,561,563,568,566,0,302,304,309,307,561,563,568,566,43,566,568,569,567,0,307,309,310,308,566,568,569,567,43,570,571,572,573,0,311,312,313,314,570,571,572,573,43,573,572,574,575,0,314,313,315,316,573,572,574,575,43,571,576,577,572,0,312,317,318,313,571,576,577,572,43,572,577,578,574,0,313,318,319,315,572,577,578,574,43,570,573,579,580,0,311,314,320,321,570,573,579,580,43,580,579,581,582,0,321,320,322,323,580,579,581,582,43,573,575,583,579,0,314,316,324,320,573,575,583,579,43,579,583,584,581,0,320,324,325,322,579,583,584,581,43,570,580,585,586,0,311,321,326,327,570,580,585,586,43,586,585,361,354,0,327,326,328,329,586,585,361,354,43,580,582,587,585,0,321,323,330,326,580,582,587,585,43,585,587,362,361,0,326,330,331,328,585,587,362,361,43,570,586,588,571,0,311,327,332,312,570,586,588,571,43,571,588,589,576,0,312,332,333,317,571,588,589,576,43,586,354,353,588,0,327,329,334,332,586,354,353,588,43,588,353,356,589,0,332,334,335,333,588,353,356,589,43,590,591,592,593,0,336,337,338,339,590,591,592,593,43,593,592,594,595,0,339,338,340,341,593,592,594,595,43,591,596,597,592,0,337,342,343,338,591,596,597,592,43,592,597,598,594,0,338,343,344,340,592,597,598,594,43,590,593,599,600,0,336,339,345,346,590,593,599,600,43,600,599,601,602,0,346,345,347,348,600,599,601,602,43,593,595,603,599,0,339,341,349,345,593,595,603,599,43,599,603,604,601,0,345,349,350,347,599,603,604,601,43,590,600,605,606,0,336,346,351,352,590,600,605,606,43,606,605,607,608,0,352,351,353,354,606,605,607,608,43,600,602,609,605,0,346,348,355,351,600,602,609,605,43,605,609,610,607,0,351,355,356,353,605,609,610,607,43,590,606,611,591,0,336,352,357,337,590,606,611,591,43,591,611,612,596,0,337,357,358,342,591,611,612,596,43,606,608,613,611,0,352,354,359,357,606,608,613,611,43,611,613,614,612,0,357,359,360,358,611,613,614,612,43,615,616,617,618,0,361,362,363,364,615,616,617,618,43,618,617,619,620,0,364,363,244,365,618,617,619,620,43,616,621,622,617,0,362,366,367,363,616,621,622,617,43,617,622,544,619,0,363,367,368,244,617,622,544,619,43,615,618,623,624,0,361,364,369,370,615,618,623,624,43,624,623,625,626,0,370,369,371,372,624,623,625,626,43,618,620,627,623,0,364,365,373,369,618,620,627,623,43,623,627,628,625,0,369,373,374,371,623,627,628,625,43,615,624,629,630,0,361,370,375,376,615,624,629,630,43,630,629,631,632,0,376,375,377,378,630,629,631,632,43,624,626,633,629,0,370,372,379,375,624,626,633,629,43,629,633,578,631,0,375,379,319,377,629,633,578,631,43,615,630,634,616,0,361,376,380,362,615,630,634,616,43,616,634,635,621,0,362,380,381,366,616,634,635,621,43,630,632,636,634,0,376,378,382,380,630,632,636,634,43,634,636,565,635,0,380,382,306,381,634,636,565,635,43,637,638,639,640,0,383,384,385,386,637,638,639,640,43,640,639,641,642,0,386,385,385,386,640,639,641,642,43,638,643,644,639,0,384,387,388,385,638,643,644,639,43,639,644,645,641,0,385,388,388,385,639,644,645,641,43,637,640,646,647,0,383,386,62,389,637,640,646,647,43,647,646,648,649,0,389,62,16,390,647,646,648,649,43,640,642,650,646,0,386,386,62,62,640,642,650,646,43,646,650,651,648,0,62,62,16,16,646,650,651,648,43,637,647,652,653,0,383,389,391,392,637,647,652,653,43,653,652,654,655,0,392,391,393,394,653,652,654,655,43,647,649,656,652,0,389,390,395,391,647,649,656,652,43,652,656,657,654,0,391,395,396,393,652,656,657,654,43,637,653,658,638,0,383,392,397,384,637,653,658,638,43,638,658,659,643,0,384,397,398,387,638,658,659,643,43,653,655,660,658,0,392,394,399,397,653,655,660,658,43,658,660,661,659,0,397,399,400,398,658,660,661,659,43,662,663,664,665,0,401,402,66,403,662,663,664,665,43,665,664,666,667,0,403,66,66,403,665,664,666,667,43,663,668,669,664,0,402,404,18,66,663,668,669,664,43,664,669,670,666,0,66,18,18,66,664,669,670,666,43,662,665,671,672,0,401,403,405,406,662,665,671,672,43,672,671,644,643,0,406,405,388,387,672,671,644,643,43,665,667,673,671,0,403,403,405,405,665,667,673,671,43,671,673,645,644,0,405,405,388,388,671,673,645,644,43,662,672,674,675,0,401,406,407,408,662,672,674,675,43,675,674,676,677,0,408,407,409,410,675,674,676,677,43,672,643,659,674,0,406,387,398,407,672,643,659,674,43,674,659,661,676,0,407,398,400,409,674,659,661,676,43,662,675,678,663,0,401,408,411,402,662,675,678,663,43,663,678,679,668,0,402,411,412,404,663,678,679,668,43,675,677,680,678,0,408,410,413,411,675,677,680,678,43,678,680,681,679,0,411,413,414,412,678,680,681,679,43,682,683,684,685,0,415,416,417,418,682,683,684,685,43,685,684,686,687,0,418,417,417,418,685,684,686,687,43,683,688,689,684,0,416,419,420,417,683,688,689,684,43,684,689,690,686,0,417,420,420,417,684,689,690,686,43,682,685,691,692,0,415,418,421,422,682,685,691,692,43,692,691,669,668,0,422,421,18,404,692,691,669,668,43,685,687,693,691,0,418,418,421,421,685,687,693,691,43,691,693,670,669,0,421,421,18,18,691,693,670,669,43,682,692,694,695,0,415,422,423,424,682,692,694,695,43,695,694,696,697,0,424,423,425,426,695,694,696,697,43,692,668,679,694,0,422,404,412,423,692,668,679,694,43,694,679,681,696,0,423,412,414,425,694,679,681,696,43,682,695,698,683,0,415,424,427,416,682,695,698,683,43,683,698,699,688,0,416,427,428,419,683,698,699,688,43,695,697,700,698,0,424,426,429,427,695,697,700,698,43,698,700,701,699,0,427,429,430,428,698,700,701,699,43,702,703,704,705,0,431,432,9,433,702,703,704,705,43,705,704,706,707,0,433,9,9,433,705,704,706,707,43,703,708,709,704,0,432,434,10,9,703,708,709,704,43,704,709,710,706,0,9,10,10,9,704,709,710,706,43,702,705,711,712,0,431,433,435,436,702,705,711,712,43,712,711,689,688,0,436,435,420,419,712,711,689,688,43,705,707,713,711,0,433,433,435,435,705,707,713,711,43,711,713,690,689,0,435,435,420,420,711,713,690,689,43,702,712,714,715,0,431,436,437,438,702,712,714,715,43,715,714,716,717,0,438,437,439,440,715,714,716,717,43,712,688,699,714,0,436,419,428,437,712,688,699,714,43,714,699,701,716,0,437,428,430,439,714,699,701,716,43,702,715,718,703,0,431,438,441,432,702,715,718,703,43,703,718,719,708,0,432,441,442,434,703,718,719,708,43,715,717,720,718,0,438,440,443,441,715,717,720,718,43,718,720,721,719,0,441,443,444,442,718,720,721,719,43,722,723,724,725,0,445,446,447,448,722,723,724,725,43,725,724,726,727,0,448,447,447,448,725,724,726,727,43,723,728,729,724,0,446,449,450,447,723,728,729,724,43,724,729,730,726,0,447,450,450,447,724,729,730,726,43,722,725,731,732,0,445,448,35,451,722,725,731,732,43,732,731,709,708,0,451,35,10,434,732,731,709,708,43,725,727,733,731,0,448,448,35,35,725,727,733,731,43,731,733,710,709,0,35,35,10,10,731,733,710,709,43,722,732,734,735,0,445,451,452,453,722,732,734,735,43,735,734,736,737,0,453,452,454,455,735,734,736,737,43,732,708,719,734,0,451,434,442,452,732,708,719,734,43,734,719,721,736,0,452,442,444,454,734,719,721,736,43,722,735,738,723,0,445,453,456,446,722,735,738,723,43,723,738,739,728,0,446,456,457,449,723,738,739,728,43,735,737,740,738,0,453,455,458,456,735,737,740,738,43,738,740,741,739,0,456,458,459,457,738,740,741,739,43,742,743,744,745,0,460,461,22,462,742,743,744,745,43,745,744,746,747,0,462,22,22,462,745,744,746,747,43,743,748,749,744,0,461,463,23,22,743,748,749,744,43,744,749,750,746,0,22,23,23,22,744,749,750,746,43,742,745,751,752,0,460,462,464,465,742,745,751,752,43,752,751,729,728,0,465,464,450,449,752,751,729,728,43,745,747,753,751,0,462,462,464,464,745,747,753,751,43,751,753,730,729,0,464,464,450,450,751,753,730,729,43,742,752,754,755,0,460,465,466,467,742,752,754,755,43,755,754,756,757,0,467,466,468,469,755,754,756,757,43,752,728,739,754,0,465,449,457,466,752,728,739,754,43,754,739,741,756,0,466,457,459,468,754,739,741,756,43,742,755,758,743,0,460,467,470,461,742,755,758,743,43,743,758,759,748,0,461,470,471,463,743,758,759,748,43,755,757,760,758,0,467,469,472,470,755,757,760,758,43,758,760,761,759,0,470,472,473,471,758,760,761,759,43,762,763,764,765,0,474,475,476,477,762,763,764,765,43,765,764,766,767,0,477,476,476,477,765,764,766,767,43,763,768,769,764,0,475,478,479,476,763,768,769,764,43,764,769,770,766,0,476,479,479,476,764,769,770,766,43,762,765,771,772,0,474,477,57,480,762,765,771,772,43,772,771,749,748,0,480,57,23,463,772,771,749,748,43,765,767,773,771,0,477,477,57,57,765,767,773,771,43,771,773,750,749,0,57,57,23,23,771,773,750,749,43,762,772,774,775,0,474,480,481,482,762,772,774,775,43,775,774,776,777,0,482,481,483,484,775,774,776,777,43,772,748,759,774,0,480,463,471,481,772,748,759,774,43,774,759,761,776,0,481,471,473,483,774,759,761,776,43,762,775,778,763,0,474,482,485,475,762,775,778,763,43,763,778,779,768,0,475,485,486,478,763,778,779,768,43,775,777,780,778,0,482,484,487,485,775,777,780,778,43,778,780,781,779,0,485,487,488,486,778,780,781,779,43,782,783,784,785,0,489,490,15,491,782,783,784,785,43,785,784,786,787,0,491,15,15,491,785,784,786,787,43,783,649,648,784,0,490,390,16,15,783,649,648,784,43,784,648,651,786,0,15,16,16,15,784,648,651,786,43,782,785,788,789,0,489,491,492,493,782,785,788,789,43,789,788,769,768,0,493,492,479,478,789,788,769,768,43,785,787,790,788,0,491,491,492,492,785,787,790,788,43,788,790,770,769,0,492,492,479,479,788,790,770,769,43,782,789,791,792,0,489,493,494,495,782,789,791,792,43,792,791,793,794,0,495,494,496,497,792,791,793,794,43,789,768,779,791,0,493,478,486,494,789,768,779,791,43,791,779,781,793,0,494,486,488,496,791,779,781,793,43,782,792,795,783,0,489,495,498,490,782,792,795,783,43,783,795,656,649,0,490,498,395,390,783,795,656,649,43,792,794,796,795,0,495,497,499,498,792,794,796,795,43,795,796,657,656,0,498,499,396,395,795,796,657,656,43,797,798,799,800,0,386,386,62,62,797,798,799,800,43,800,799,801,802,0,62,62,16,16,800,799,801,802,43,798,803,804,799,0,386,386,62,62,798,803,804,799,43,799,804,805,801,0,62,62,16,16,799,804,805,801,43,797,800,806,807,0,386,62,62,386,797,800,806,807,43,807,806,808,809,0,386,62,62,386,807,806,808,809,43,800,802,810,806,0,62,16,16,62,800,802,810,806,43,806,810,811,808,0,62,16,16,62,806,810,811,808,43,797,807,812,813,0,386,386,385,385,797,807,812,813,43,813,812,814,815,0,385,385,388,388,813,812,814,815,43,807,809,816,812,0,386,386,385,385,807,809,816,812,43,812,816,817,814,0,385,385,388,388,812,816,817,814,43,797,813,818,798,0,386,385,385,386,797,813,818,798,43,798,818,819,803,0,386,385,385,386,798,818,819,803,43,813,815,820,818,0,385,388,388,385,813,815,820,818,43,818,820,821,819,0,385,388,388,385,818,820,821,819,43,822,823,824,825,0,403,66,66,403,822,823,824,825,43,825,824,826,827,0,403,66,66,403,825,824,826,827,43,823,828,829,824,0,66,18,18,66,823,828,829,824,43,824,829,830,826,0,66,18,18,66,824,829,830,826,43,822,825,831,832,0,403,403,405,405,822,825,831,832,43,832,831,820,815,0,405,405,388,388,832,831,820,815,43,825,827,833,831,0,403,403,405,405,825,827,833,831,43,831,833,821,820,0,405,405,388,388,831,833,821,820,43,822,832,834,835,0,403,405,405,403,822,832,834,835,43,835,834,836,837,0,403,405,405,403,835,834,836,837,43,832,815,814,834,0,405,388,388,405,832,815,814,834,43,834,814,817,836,0,405,388,388,405,834,814,817,836,43,822,835,838,823,0,403,403,66,66,822,835,838,823,43,823,838,839,828,0,66,66,18,18,823,838,839,828,43,835,837,840,838,0,403,403,66,66,835,837,840,838,43,838,840,841,839,0,66,66,18,18,838,840,841,839,43,842,843,844,845,0,418,417,417,418,842,843,844,845,43,845,844,846,847,0,418,417,417,418,845,844,846,847,43,843,848,849,844,0,417,420,420,417,843,848,849,844,43,844,849,850,846,0,417,420,420,417,844,849,850,846,43,842,845,851,852,0,418,418,421,421,842,845,851,852,43,852,851,829,828,0,421,421,18,18,852,851,829,828,43,845,847,853,851,0,418,418,421,421,845,847,853,851,43,851,853,830,829,0,421,421,18,18,851,853,830,829,43,842,852,854,855,0,418,421,421,418,842,852,854,855,43,855,854,856,857,0,418,421,421,418,855,854,856,857,43,852,828,839,854,0,421,18,18,421,852,828,839,854,43,854,839,841,856,0,421,18,18,421,854,839,841,856,43,842,855,858,843,0,418,418,417,417,842,855,858,843,43,843,858,859,848,0,417,417,420,420,843,858,859,848,43,855,857,860,858,0,418,418,417,417,855,857,860,858,43,858,860,861,859,0,417,417,420,420,858,860,861,859,43,862,863,864,865,0,433,433,435,435,862,863,864,865,43,865,864,849,848,0,435,435,420,420,865,864,849,848,43,863,866,867,864,0,433,433,435,435,863,866,867,864,43,864,867,850,849,0,435,435,420,420,864,867,850,849,43,862,865,868,869,0,433,435,435,433,862,865,868,869,43,869,868,870,871,0,433,435,435,433,869,868,870,871,43,865,848,859,868,0,435,420,420,435,865,848,859,868,43,868,859,861,870,0,435,420,420,435,868,859,861,870,43,862,869,872,873,0,433,433,9,9,862,869,872,873,43,873,872,874,875,0,9,9,10,10,873,872,874,875,43,869,871,876,872,0,433,433,9,9,869,871,876,872,43,872,876,877,874,0,9,9,10,10,872,876,877,874,43,862,873,878,863,0,433,9,9,433,862,873,878,863,43,863,878,879,866,0,433,9,9,433,863,878,879,866,43,873,875,880,878,0,9,10,10,9,873,875,880,878,43,878,880,881,879,0,9,10,10,9,878,880,881,879,43,882,883,884,885,0,448,448,35,35,882,883,884,885,43,885,884,880,875,0,35,35,10,10,885,884,880,875,43,883,886,887,884,0,448,448,35,35,883,886,887,884,43,884,887,881,880,0,35,35,10,10,884,887,881,880,43,882,885,888,889,0,448,35,35,448,882,885,888,889,43,889,888,890,891,0,448,35,35,448,889,888,890,891,43,885,875,874,888,0,35,10,10,35,885,875,874,888,43,888,874,877,890,0,35,10,10,35,888,874,877,890,43,882,889,892,893,0,448,448,447,447,882,889,892,893,43,893,892,894,895,0,447,447,450,450,893,892,894,895,43,889,891,896,892,0,448,448,447,447,889,891,896,892,43,892,896,897,894,0,447,447,450,450,892,896,897,894,43,882,893,898,883,0,448,447,447,448,882,893,898,883,43,883,898,899,886,0,448,447,447,448,883,898,899,886,43,893,895,900,898,0,447,450,450,447,893,895,900,898,43,898,900,901,899,0,447,450,450,447,898,900,901,899,43,902,903,904,905,0,462,462,464,464,902,903,904,905,43,905,904,900,895,0,464,464,450,450,905,904,900,895,43,903,906,907,904,0,462,462,464,464,903,906,907,904,43,904,907,901,900,0,464,464,450,450,904,907,901,900,43,902,905,908,909,0,462,464,464,462,902,905,908,909,43,909,908,910,911,0,462,464,464,462,909,908,910,911,43,905,895,894,908,0,464,450,450,464,905,895,894,908,43,908,894,897,910,0,464,450,450,464,908,894,897,910,43,902,909,912,913,0,462,462,22,22,902,909,912,913,43,913,912,914,915,0,22,22,23,23,913,912,914,915,43,909,911,916,912,0,462,462,22,22,909,911,916,912,43,912,916,917,914,0,22,22,23,23,912,916,917,914,43,902,913,918,903,0,462,22,22,462,902,913,918,903,43,903,918,919,906,0,462,22,22,462,903,918,919,906,43,913,915,920,918,0,22,23,23,22,913,915,920,918,43,918,920,921,919,0,22,23,23,22,918,920,921,919,43,922,923,924,925,0,477,476,476,477,922,923,924,925,43,925,924,926,927,0,477,476,476,477,925,924,926,927,43,923,928,929,924,0,476,479,479,476,923,928,929,924,43,924,929,930,926,0,476,479,479,476,924,929,930,926,43,922,925,931,932,0,477,477,57,57,922,925,931,932,43,932,931,920,915,0,57,57,23,23,932,931,920,915,43,925,927,933,931,0,477,477,57,57,925,927,933,931,43,931,933,921,920,0,57,57,23,23,931,933,921,920,43,922,932,934,935,0,477,57,57,477,922,932,934,935,43,935,934,936,937,0,477,57,57,477,935,934,936,937,43,932,915,914,934,0,57,23,23,57,932,915,914,934,43,934,914,917,936,0,57,23,23,57,934,914,917,936,43,922,935,938,923,0,477,477,476,476,922,935,938,923,43,923,938,939,928,0,476,476,479,479,923,938,939,928,43,935,937,940,938,0,477,477,476,476,935,937,940,938,43,938,940,941,939,0,476,476,479,479,938,940,941,939,43,942,943,944,945,0,491,15,15,491,942,943,944,945,43,945,944,946,947,0,491,15,15,491,945,944,946,947,43,943,802,801,944,0,15,16,16,15,943,802,801,944,43,944,801,805,946,0,15,16,16,15,944,801,805,946,43,942,945,948,949,0,491,491,492,492,942,945,948,949,43,949,948,929,928,0,492,492,479,479,949,948,929,928,43,945,947,950,948,0,491,491,492,492,945,947,950,948,43,948,950,930,929,0,492,492,479,479,948,950,930,929,43,942,949,951,952,0,491,492,492,491,942,949,951,952,43,952,951,953,954,0,491,492,492,491,952,951,953,954,43,949,928,939,951,0,492,479,479,492,949,928,939,951,43,951,939,941,953,0,492,479,479,492,951,939,941,953,43,942,952,955,943,0,491,491,15,15,942,952,955,943,43,943,955,810,802,0,15,15,16,16,943,955,810,802,43,952,954,956,955,0,491,491,15,15,952,954,956,955,43,955,956,811,810,0,15,15,16,16,955,956,811,810,43,957,958,959,960,0,386,386,62,62,957,958,959,960,43,960,959,961,962,0,62,62,16,16,960,959,961,962,43,958,963,964,959,0,386,386,62,62,958,963,964,959,43,959,964,965,961,0,62,62,16,16,959,964,965,961,43,957,960,966,967,0,386,62,62,386,957,960,966,967,43,967,966,968,969,0,386,62,62,386,967,966,968,969,43,960,962,970,966,0,62,16,16,62,960,962,970,966,43,966,970,971,968,0,62,16,16,62,966,970,971,968,43,957,967,972,973,0,386,386,385,385,957,967,972,973,43,973,972,974,975,0,385,385,388,388,973,972,974,975,43,967,969,976,972,0,386,386,385,385,967,969,976,972,43,972,976,977,974,0,385,385,388,388,972,976,977,974,43,957,973,978,958,0,386,385,385,386,957,973,978,958,43,958,978,979,963,0,386,385,385,386,958,978,979,963,43,973,975,980,978,0,385,388,388,385,973,975,980,978,43,978,980,981,979,0,385,388,388,385,978,980,981,979,43,982,983,984,985,0,403,66,66,403,982,983,984,985,43,985,984,986,987,0,403,66,66,403,985,984,986,987,43,983,988,989,984,0,66,18,18,66,983,988,989,984,43,984,989,990,986,0,66,18,18,66,984,989,990,986,43,982,985,991,992,0,403,403,405,405,982,985,991,992,43,992,991,980,975,0,405,405,388,388,992,991,980,975,43,985,987,993,991,0,403,403,405,405,985,987,993,991,43,991,993,981,980,0,405,405,388,388,991,993,981,980,43,982,992,994,995,0,403,405,405,403,982,992,994,995,43,995,994,996,997,0,403,405,405,403,995,994,996,997,43,992,975,974,994,0,405,388,388,405,992,975,974,994,43,994,974,977,996,0,405,388,388,405,994,974,977,996,43,982,995,998,983,0,403,403,66,66,982,995,998,983,43,983,998,999,988,0,66,66,18,18,983,998,999,988,43,995,997,1000,998,0,403,403,66,66,995,997,1000,998,43,998,1000,1001,999,0,66,66,18,18,998,1000,1001,999,43,1002,1003,1004,1005,0,418,417,417,418,1002,1003,1004,1005,43,1005,1004,1006,1007,0,418,417,417,418,1005,1004,1006,1007,43,1003,1008,1009,1004,0,417,420,420,417,1003,1008,1009,1004,43,1004,1009,1010,1006,0,417,420,420,417,1004,1009,1010,1006,43,1002,1005,1011,1012,0,418,418,421,421,1002,1005,1011,1012,43,1012,1011,989,988,0,421,421,18,18,1012,1011,989,988,43,1005,1007,1013,1011,0,418,418,421,421,1005,1007,1013,1011,43,1011,1013,990,989,0,421,421,18,18,1011,1013,990,989,43,1002,1012,1014,1015,0,418,421,421,418,1002,1012,1014,1015,43,1015,1014,1016,1017,0,418,421,421,418,1015,1014,1016,1017,43,1012,988,999,1014,0,421,18,18,421,1012,988,999,1014,43,1014,999,1001,1016,0,421,18,18,421,1014,999,1001,1016,43,1002,1015,1018,1003,0,418,418,417,417,1002,1015,1018,1003,43,1003,1018,1019,1008,0,417,417,420,420,1003,1018,1019,1008,43,1015,1017,1020,1018,0,418,418,417,417,1015,1017,1020,1018,43,1018,1020,1021,1019,0,417,417,420,420,1018,1020,1021,1019,43,1022,1023,1024,1025,0,433,433,435,435,1022,1023,1024,1025,43,1025,1024,1009,1008,0,435,435,420,420,1025,1024,1009,1008,43,1023,1026,1027,1024,0,433,433,435,435,1023,1026,1027,1024,43,1024,1027,1010,1009,0,435,435,420,420,1024,1027,1010,1009,43,1022,1025,1028,1029,0,433,435,435,433,1022,1025,1028,1029,43,1029,1028,1030,1031,0,433,435,435,433,1029,1028,1030,1031,43,1025,1008,1019,1028,0,435,420,420,435,1025,1008,1019,1028,43,1028,1019,1021,1030,0,435,420,420,435,1028,1019,1021,1030,43,1022,1029,1032,1033,0,433,433,9,9,1022,1029,1032,1033,43,1033,1032,1034,1035,0,9,9,10,10,1033,1032,1034,1035,43,1029,1031,1036,1032,0,433,433,9,9,1029,1031,1036,1032,43,1032,1036,1037,1034,0,9,9,10,10,1032,1036,1037,1034,43,1022,1033,1038,1023,0,433,9,9,433,1022,1033,1038,1023,43,1023,1038,1039,1026,0,433,9,9,433,1023,1038,1039,1026,43,1033,1035,1040,1038,0,9,10,10,9,1033,1035,1040,1038,43,1038,1040,1041,1039,0,9,10,10,9,1038,1040,1041,1039,43,1042,1043,1044,1045,0,448,448,35,35,1042,1043,1044,1045,43,1045,1044,1040,1035,0,35,35,10,10,1045,1044,1040,1035,43,1043,1046,1047,1044,0,448,448,35,35,1043,1046,1047,1044,43,1044,1047,1041,1040,0,35,35,10,10,1044,1047,1041,1040,43,1042,1045,1048,1049,0,448,35,35,448,1042,1045,1048,1049,43,1049,1048,1050,1051,0,448,35,35,448,1049,1048,1050,1051,43,1045,1035,1034,1048,0,35,10,10,35,1045,1035,1034,1048,43,1048,1034,1037,1050,0,35,10,10,35,1048,1034,1037,1050,43,1042,1049,1052,1053,0,448,448,447,447,1042,1049,1052,1053,43,1053,1052,1054,1055,0,447,447,450,450,1053,1052,1054,1055,43,1049,1051,1056,1052,0,448,448,447,447,1049,1051,1056,1052,43,1052,1056,1057,1054,0,447,447,450,450,1052,1056,1057,1054,43,1042,1053,1058,1043,0,448,447,447,448,1042,1053,1058,1043,43,1043,1058,1059,1046,0,448,447,447,448,1043,1058,1059,1046,43,1053,1055,1060,1058,0,447,450,450,447,1053,1055,1060,1058,43,1058,1060,1061,1059,0,447,450,450,447,1058,1060,1061,1059,43,1062,1063,1064,1065,0,462,462,464,464,1062,1063,1064,1065,43,1065,1064,1060,1055,0,464,464,450,450,1065,1064,1060,1055,43,1063,1066,1067,1064,0,462,462,464,464,1063,1066,1067,1064,43,1064,1067,1061,1060,0,464,464,450,450,1064,1067,1061,1060,43,1062,1065,1068,1069,0,462,464,464,462,1062,1065,1068,1069,43,1069,1068,1070,1071,0,462,464,464,462,1069,1068,1070,1071,43,1065,1055,1054,1068,0,464,450,450,464,1065,1055,1054,1068,43,1068,1054,1057,1070,0,464,450,450,464,1068,1054,1057,1070,43,1062,1069,1072,1073,0,462,462,22,22,1062,1069,1072,1073,43,1073,1072,1074,1075,0,22,22,23,23,1073,1072,1074,1075,43,1069,1071,1076,1072,0,462,462,22,22,1069,1071,1076,1072,43,1072,1076,1077,1074,0,22,22,23,23,1072,1076,1077,1074,43,1062,1073,1078,1063,0,462,22,22,462,1062,1073,1078,1063,43,1063,1078,1079,1066,0,462,22,22,462,1063,1078,1079,1066,43,1073,1075,1080,1078,0,22,23,23,22,1073,1075,1080,1078,43,1078,1080,1081,1079,0,22,23,23,22,1078,1080,1081,1079,43,1082,1083,1084,1085,0,477,477,57,57,1082,1083,1084,1085,43,1085,1084,1080,1075,0,57,57,23,23,1085,1084,1080,1075,43,1083,1086,1087,1084,0,477,477,57,57,1083,1086,1087,1084,43,1084,1087,1081,1080,0,57,57,23,23,1084,1087,1081,1080,43,1082,1085,1088,1089,0,477,57,57,477,1082,1085,1088,1089,43,1089,1088,1090,1091,0,477,57,57,477,1089,1088,1090,1091,43,1085,1075,1074,1088,0,57,23,23,57,1085,1075,1074,1088,43,1088,1074,1077,1090,0,57,23,23,57,1088,1074,1077,1090,43,1082,1089,1092,1093,0,477,477,476,476,1082,1089,1092,1093,43,1093,1092,1094,1095,0,476,476,479,479,1093,1092,1094,1095,43,1089,1091,1096,1092,0,477,477,476,476,1089,1091,1096,1092,43,1092,1096,1097,1094,0,476,476,479,479,1092,1096,1097,1094,43,1082,1093,1098,1083,0,477,476,476,477,1082,1093,1098,1083,43,1083,1098,1099,1086,0,477,476,476,477,1083,1098,1099,1086,43,1093,1095,1100,1098,0,476,479,479,476,1093,1095,1100,1098,43,1098,1100,1101,1099,0,476,479,479,476,1098,1100,1101,1099,43,1102,1103,1104,1105,0,491,15,15,491,1102,1103,1104,1105,43,1105,1104,1106,1107,0,491,15,15,491,1105,1104,1106,1107,43,1103,962,961,1104,0,15,16,16,15,1103,962,961,1104,43,1104,961,965,1106,0,15,16,16,15,1104,961,965,1106,43,1102,1105,1108,1109,0,491,491,492,492,1102,1105,1108,1109,43,1109,1108,1100,1095,0,492,492,479,479,1109,1108,1100,1095,43,1105,1107,1110,1108,0,491,491,492,492,1105,1107,1110,1108,43,1108,1110,1101,1100,0,492,492,479,479,1108,1110,1101,1100,43,1102,1109,1111,1112,0,491,492,492,491,1102,1109,1111,1112,43,1112,1111,1113,1114,0,491,492,492,491,1112,1111,1113,1114,43,1109,1095,1094,1111,0,492,479,479,492,1109,1095,1094,1111,43,1111,1094,1097,1113,0,492,479,479,492,1111,1094,1097,1113,43,1102,1112,1115,1103,0,491,491,15,15,1102,1112,1115,1103,43,1103,1115,970,962,0,15,15,16,16,1103,1115,970,962,43,1112,1114,1116,1115,0,491,491,15,15,1112,1114,1116,1115,43,1115,1116,971,970,0,15,15,16,16,1115,1116,971,970,43,1117,1118,1119,1120,0,386,386,62,62,1117,1118,1119,1120,43,1120,1119,1121,1122,0,62,62,16,16,1120,1119,1121,1122,43,1118,1123,1124,1119,0,386,386,62,62,1118,1123,1124,1119,43,1119,1124,1125,1121,0,62,62,16,16,1119,1124,1125,1121,43,1117,1120,1126,1127,0,386,62,62,386,1117,1120,1126,1127,43,1127,1126,964,963,0,386,62,62,386,1127,1126,964,963,43,1120,1122,1128,1126,0,62,16,16,62,1120,1122,1128,1126,43,1126,1128,965,964,0,62,16,16,62,1126,1128,965,964,43,1117,1127,1129,1130,0,386,386,385,385,1117,1127,1129,1130,43,1130,1129,1131,1132,0,385,385,388,388,1130,1129,1131,1132,43,1127,963,979,1129,0,386,386,385,385,1127,963,979,1129,43,1129,979,981,1131,0,385,385,388,388,1129,979,981,1131,43,1117,1130,1133,1118,0,386,385,385,386,1117,1130,1133,1118,43,1118,1133,1134,1123,0,386,385,385,386,1118,1133,1134,1123,43,1130,1132,1135,1133,0,385,388,388,385,1130,1132,1135,1133,43,1133,1135,1136,1134,0,385,388,388,385,1133,1135,1136,1134,43,1137,1138,1139,1140,0,403,405,405,403,1137,1138,1139,1140,43,1140,1139,993,987,0,403,405,405,403,1140,1139,993,987,43,1138,1132,1131,1139,0,405,388,388,405,1138,1132,1131,1139,43,1139,1131,981,993,0,405,388,388,405,1139,1131,981,993,43,1137,1140,1141,1142,0,403,403,66,66,1137,1140,1141,1142,43,1142,1141,1143,1144,0,66,66,18,18,1142,1141,1143,1144,43,1140,987,986,1141,0,403,403,66,66,1140,987,986,1141,43,1141,986,990,1143,0,66,66,18,18,1141,986,990,1143,43,1137,1142,1145,1146,0,403,66,66,403,1137,1142,1145,1146,43,1146,1145,1147,1148,0,403,66,66,403,1146,1145,1147,1148,43,1142,1144,1149,1145,0,66,18,18,66,1142,1144,1149,1145,43,1145,1149,1150,1147,0,66,18,18,66,1145,1149,1150,1147,43,1137,1146,1151,1138,0,403,403,405,405,1137,1146,1151,1138,43,1138,1151,1135,1132,0,405,405,388,388,1138,1151,1135,1132,43,1146,1148,1152,1151,0,403,403,405,405,1146,1148,1152,1151,43,1151,1152,1136,1135,0,405,405,388,388,1151,1152,1136,1135,43,1153,1154,1155,1156,0,418,421,421,418,1153,1154,1155,1156,43,1156,1155,1013,1007,0,418,421,421,418,1156,1155,1013,1007,43,1154,1144,1143,1155,0,421,18,18,421,1154,1144,1143,1155,43,1155,1143,990,1013,0,421,18,18,421,1155,1143,990,1013,43,1153,1156,1157,1158,0,418,418,417,417,1153,1156,1157,1158,43,1158,1157,1159,1160,0,417,417,420,420,1158,1157,1159,1160,43,1156,1007,1006,1157,0,418,418,417,417,1156,1007,1006,1157,43,1157,1006,1010,1159,0,417,417,420,420,1157,1006,1010,1159,43,1153,1158,1161,1162,0,418,417,417,418,1153,1158,1161,1162,43,1162,1161,1163,1164,0,418,417,417,418,1162,1161,1163,1164,43,1158,1160,1165,1161,0,417,420,420,417,1158,1160,1165,1161,43,1161,1165,1166,1163,0,417,420,420,417,1161,1165,1166,1163,43,1153,1162,1167,1154,0,418,418,421,421,1153,1162,1167,1154,43,1154,1167,1149,1144,0,421,421,18,18,1154,1167,1149,1144,43,1162,1164,1168,1167,0,418,418,421,421,1162,1164,1168,1167,43,1167,1168,1150,1149,0,421,421,18,18,1167,1168,1150,1149,43,1169,1170,1171,1172,0,433,433,435,435,1169,1170,1171,1172,43,1172,1171,1165,1160,0,435,435,420,420,1172,1171,1165,1160,43,1170,1173,1174,1171,0,433,433,435,435,1170,1173,1174,1171,43,1171,1174,1166,1165,0,435,435,420,420,1171,1174,1166,1165,43,1169,1172,1175,1176,0,433,435,435,433,1169,1172,1175,1176,43,1176,1175,1027,1026,0,433,435,435,433,1176,1175,1027,1026,43,1172,1160,1159,1175,0,435,420,420,435,1172,1160,1159,1175,43,1175,1159,1010,1027,0,435,420,420,435,1175,1159,1010,1027,43,1169,1176,1177,1178,0,433,433,9,9,1169,1176,1177,1178,43,1178,1177,1179,1180,0,9,9,10,10,1178,1177,1179,1180,43,1176,1026,1039,1177,0,433,433,9,9,1176,1026,1039,1177,43,1177,1039,1041,1179,0,9,9,10,10,1177,1039,1041,1179,43,1169,1178,1181,1170,0,433,9,9,433,1169,1178,1181,1170,43,1170,1181,1182,1173,0,433,9,9,433,1170,1181,1182,1173,43,1178,1180,1183,1181,0,9,10,10,9,1178,1180,1183,1181,43,1181,1183,1184,1182,0,9,10,10,9,1181,1183,1184,1182,43,1185,1186,1187,1188,0,448,448,35,35,1185,1186,1187,1188,43,1188,1187,1183,1180,0,35,35,10,10,1188,1187,1183,1180,43,1186,1189,1190,1187,0,448,448,35,35,1186,1189,1190,1187,43,1187,1190,1184,1183,0,35,35,10,10,1187,1190,1184,1183,43,1185,1188,1191,1192,0,448,35,35,448,1185,1188,1191,1192,43,1192,1191,1047,1046,0,448,35,35,448,1192,1191,1047,1046,43,1188,1180,1179,1191,0,35,10,10,35,1188,1180,1179,1191,43,1191,1179,1041,1047,0,35,10,10,35,1191,1179,1041,1047,43,1185,1192,1193,1194,0,448,448,447,447,1185,1192,1193,1194,43,1194,1193,1195,1196,0,447,447,450,450,1194,1193,1195,1196,43,1192,1046,1059,1193,0,448,448,447,447,1192,1046,1059,1193,43,1193,1059,1061,1195,0,447,447,450,450,1193,1059,1061,1195,43,1185,1194,1197,1186,0,448,447,447,448,1185,1194,1197,1186,43,1186,1197,1198,1189,0,448,447,447,448,1186,1197,1198,1189,43,1194,1196,1199,1197,0,447,450,450,447,1194,1196,1199,1197,43,1197,1199,1200,1198,0,447,450,450,447,1197,1199,1200,1198,43,1201,1202,1203,1204,0,462,462,464,464,1201,1202,1203,1204,43,1204,1203,1199,1196,0,464,464,450,450,1204,1203,1199,1196,43,1202,1205,1206,1203,0,462,462,464,464,1202,1205,1206,1203,43,1203,1206,1200,1199,0,464,464,450,450,1203,1206,1200,1199,43,1201,1204,1207,1208,0,462,464,464,462,1201,1204,1207,1208,43,1208,1207,1067,1066,0,462,464,464,462,1208,1207,1067,1066,43,1204,1196,1195,1207,0,464,450,450,464,1204,1196,1195,1207,43,1207,1195,1061,1067,0,464,450,450,464,1207,1195,1061,1067,43,1201,1208,1209,1210,0,462,462,22,22,1201,1208,1209,1210,43,1210,1209,1211,1212,0,22,22,23,23,1210,1209,1211,1212,43,1208,1066,1079,1209,0,462,462,22,22,1208,1066,1079,1209,43,1209,1079,1081,1211,0,22,22,23,23,1209,1079,1081,1211,43,1201,1210,1213,1202,0,462,22,22,462,1201,1210,1213,1202,43,1202,1213,1214,1205,0,462,22,22,462,1202,1213,1214,1205,43,1210,1212,1215,1213,0,22,23,23,22,1210,1212,1215,1213,43,1213,1215,1216,1214,0,22,23,23,22,1213,1215,1216,1214,43,1217,1218,1219,1220,0,477,57,57,477,1217,1218,1219,1220,43,1220,1219,1087,1086,0,477,57,57,477,1220,1219,1087,1086,43,1218,1212,1211,1219,0,57,23,23,57,1218,1212,1211,1219,43,1219,1211,1081,1087,0,57,23,23,57,1219,1211,1081,1087,43,1217,1220,1221,1222,0,477,477,476,476,1217,1220,1221,1222,43,1222,1221,1223,1224,0,476,476,479,479,1222,1221,1223,1224,43,1220,1086,1099,1221,0,477,477,476,476,1220,1086,1099,1221,43,1221,1099,1101,1223,0,476,476,479,479,1221,1099,1101,1223,43,1217,1222,1225,1226,0,477,476,476,477,1217,1222,1225,1226,43,1226,1225,1227,1228,0,477,476,476,477,1226,1225,1227,1228,43,1222,1224,1229,1225,0,476,479,479,476,1222,1224,1229,1225,43,1225,1229,1230,1227,0,476,479,479,476,1225,1229,1230,1227,43,1217,1226,1231,1218,0,477,477,57,57,1217,1226,1231,1218,43,1218,1231,1215,1212,0,57,57,23,23,1218,1231,1215,1212,43,1226,1228,1232,1231,0,477,477,57,57,1226,1228,1232,1231,43,1231,1232,1216,1215,0,57,57,23,23,1231,1232,1216,1215,43,1233,1234,1235,1236,0,491,492,492,491,1233,1234,1235,1236,43,1236,1235,1110,1107,0,491,492,492,491,1236,1235,1110,1107,43,1234,1224,1223,1235,0,492,479,479,492,1234,1224,1223,1235,43,1235,1223,1101,1110,0,492,479,479,492,1235,1223,1101,1110,43,1233,1236,1237,1238,0,491,491,15,15,1233,1236,1237,1238,43,1238,1237,1128,1122,0,15,15,16,16,1238,1237,1128,1122,43,1236,1107,1106,1237,0,491,491,15,15,1236,1107,1106,1237,43,1237,1106,965,1128,0,15,15,16,16,1237,1106,965,1128,43,1233,1238,1239,1240,0,491,15,15,491,1233,1238,1239,1240,43,1240,1239,1241,1242,0,491,15,15,491,1240,1239,1241,1242,43,1238,1122,1121,1239,0,15,16,16,15,1238,1122,1121,1239,43,1239,1121,1125,1241,0,15,16,16,15,1239,1121,1125,1241,43,1233,1240,1243,1234,0,491,491,492,492,1233,1240,1243,1234,43,1234,1243,1229,1224,0,492,492,479,479,1234,1243,1229,1224,43,1240,1242,1244,1243,0,491,491,492,492,1240,1242,1244,1243,43,1243,1244,1230,1229,0,492,492,479,479,1243,1244,1230,1229,43,1245,1246,1247,1248,0,500,501,502,503,1245,1246,1247,1248,43,1248,1247,1249,1250,0,503,502,504,505,1248,1247,1249,1250,43,1246,1251,1252,1247,0,501,506,507,502,1246,1251,1252,1247,43,1247,1252,1253,1249,0,502,507,508,504,1247,1252,1253,1249,43,1245,1248,1254,1255,0,500,503,509,510,1245,1248,1254,1255,43,1255,1254,1256,1257,0,510,509,511,512,1255,1254,1256,1257,43,1248,1250,1258,1254,0,503,505,513,509,1248,1250,1258,1254,43,1254,1258,440,1256,0,509,513,514,511,1254,1258,440,1256,43,1245,1255,1259,1260,0,500,510,515,516,1245,1255,1259,1260,43,1260,1259,1261,1262,0,516,515,517,518,1260,1259,1261,1262,43,1255,1257,1263,1259,0,510,512,519,515,1255,1257,1263,1259,43,1259,1263,1264,1261,0,515,519,520,517,1259,1263,1264,1261,43,1245,1260,1265,1246,0,500,516,521,501,1245,1260,1265,1246,43,1246,1265,1266,1251,0,501,521,522,506,1246,1265,1266,1251,43,1260,1262,1267,1265,0,516,518,523,521,1260,1262,1267,1265,43,1265,1267,1268,1266,0,521,523,524,522,1265,1267,1268,1266,43,1269,1270,1271,1272,0,525,526,527,528,1269,1270,1271,1272,43,1272,1271,1273,1274,0,528,527,529,530,1272,1271,1273,1274,43,1270,1275,1276,1271,0,526,531,532,527,1270,1275,1276,1271,43,1271,1276,1277,1273,0,527,532,533,529,1271,1276,1277,1273,43,1269,1272,1278,1279,0,525,528,534,535,1269,1272,1278,1279,43,1279,1278,1280,1281,0,535,534,536,537,1279,1278,1280,1281,43,1272,1274,1282,1278,0,528,530,538,534,1272,1274,1282,1278,43,1278,1282,1283,1280,0,534,538,539,536,1278,1282,1283,1280,43,1269,1279,1284,1285,0,525,535,540,541,1269,1279,1284,1285,43,1285,1284,1267,1262,0,541,540,523,518,1285,1284,1267,1262,43,1279,1281,1286,1284,0,535,537,542,540,1279,1281,1286,1284,43,1284,1286,1268,1267,0,540,542,524,523,1284,1286,1268,1267,43,1269,1285,1287,1270,0,525,541,543,526,1269,1285,1287,1270,43,1270,1287,1288,1275,0,526,543,544,531,1270,1287,1288,1275,43,1285,1262,1261,1287,0,541,518,517,543,1285,1262,1261,1287,43,1287,1261,1264,1288,0,543,517,520,544,1287,1261,1264,1288,43,1289,1290,1291,1292,0,545,546,547,548,1289,1290,1291,1292,43,1292,1291,1293,1294,0,548,547,549,550,1292,1291,1293,1294,43,1290,1295,1296,1291,0,546,551,552,547,1290,1295,1296,1291,43,1291,1296,504,1293,0,547,552,248,549,1291,1296,504,1293,43,1289,1292,1297,1298,0,545,548,553,554,1289,1292,1297,1298,43,1298,1297,1299,1300,0,554,553,555,556,1298,1297,1299,1300,43,1292,1294,1301,1297,0,548,550,557,553,1292,1294,1301,1297,43,1297,1301,1302,1299,0,553,557,558,555,1297,1301,1302,1299,43,1289,1298,1303,1304,0,545,554,559,560,1289,1298,1303,1304,43,1304,1303,1282,1274,0,560,559,538,530,1304,1303,1282,1274,43,1298,1300,1305,1303,0,554,556,561,559,1298,1300,1305,1303,43,1303,1305,1283,1282,0,559,561,539,538,1303,1305,1283,1282,43,1289,1304,1306,1290,0,545,560,562,546,1289,1304,1306,1290,43,1290,1306,1307,1295,0,546,562,563,551,1290,1306,1307,1295,43,1304,1274,1273,1306,0,560,530,529,562,1304,1274,1273,1306,43,1306,1273,1277,1307,0,562,529,533,563,1306,1273,1277,1307,43,1308,1309,1310,1311,0,564,565,566,567,1308,1309,1310,1311,43,1311,1310,1312,1313,0,567,566,568,569,1311,1310,1312,1313,43,1309,1314,1315,1310,0,565,570,571,566,1309,1314,1315,1310,43,1310,1315,1316,1312,0,566,571,572,568,1310,1315,1316,1312,43,1308,1311,1317,1318,0,564,567,573,574,1308,1311,1317,1318,43,1318,1317,1319,1320,0,574,573,575,576,1318,1317,1319,1320,43,1311,1313,1321,1317,0,567,569,577,573,1311,1313,1321,1317,43,1317,1321,1322,1319,0,573,577,578,575,1317,1321,1322,1319,43,1308,1318,1323,1324,0,564,574,579,580,1308,1318,1323,1324,43,1324,1323,1325,1326,0,580,579,581,582,1324,1323,1325,1326,43,1318,1320,1327,1323,0,574,576,583,579,1318,1320,1327,1323,43,1323,1327,1328,1325,0,579,583,584,581,1323,1327,1328,1325,43,1308,1324,1329,1309,0,564,580,585,565,1308,1324,1329,1309,43,1309,1329,1330,1314,0,565,585,586,570,1309,1329,1330,1314,43,1324,1326,1331,1329,0,580,582,587,585,1324,1326,1331,1329,43,1329,1331,1332,1330,0,585,587,588,586,1329,1331,1332,1330,43,1333,1334,1335,1336,0,589,590,591,592,1333,1334,1335,1336,43,1336,1335,349,348,0,592,591,94,93,1336,1335,349,348,43,1334,461,460,1335,0,590,206,205,591,1334,461,460,1335,43,1335,460,350,349,0,591,205,95,94,1335,460,350,349,43,1333,1336,1337,1338,0,589,592,593,594,1333,1336,1337,1338,43,1338,1337,1339,1340,0,594,593,595,596,1338,1337,1339,1340,43,1336,348,364,1337,0,592,93,109,593,1336,348,364,1337,43,1337,364,366,1339,0,593,109,111,595,1337,364,366,1339,43,1333,1338,1341,1342,0,589,594,597,598,1333,1338,1341,1342,43,1342,1341,1343,1344,0,598,597,599,600,1342,1341,1343,1344,43,1338,1340,1345,1341,0,594,596,601,597,1338,1340,1345,1341,43,1341,1345,1346,1343,0,597,601,602,599,1341,1345,1346,1343,43,1333,1342,1347,1334,0,589,598,603,590,1333,1342,1347,1334,43,1334,1347,468,461,0,590,603,213,206,1334,1347,468,461,43,1342,1344,1348,1347,0,598,600,604,603,1342,1344,1348,1347,43,1347,1348,469,468,0,603,604,214,213,1347,1348,469,468,43,1349,1350,1351,1352,0,605,606,607,608,1349,1350,1351,1352,43,1352,1351,1353,1354,0,608,607,609,610,1352,1351,1353,1354,43,1350,1250,1249,1351,0,606,505,504,607,1350,1250,1249,1351,43,1351,1249,1253,1353,0,607,504,508,609,1351,1249,1253,1353,43,1349,1352,1355,1356,0,605,608,611,612,1349,1352,1355,1356,43,1356,1355,399,398,0,612,611,144,143,1356,1355,399,398,43,1352,1354,1357,1355,0,608,610,613,611,1352,1354,1357,1355,43,1355,1357,400,399,0,611,613,145,144,1355,1357,400,399,43,1349,1356,1358,1359,0,605,612,614,615,1349,1356,1358,1359,43,1359,1358,424,423,0,615,614,616,617,1359,1358,424,423,43,1356,398,414,1358,0,612,143,158,614,1356,398,414,1358,43,1358,414,416,424,0,614,158,160,616,1358,414,416,424,43,1349,1359,1360,1350,0,605,615,618,606,1349,1359,1360,1350,43,1350,1360,1258,1250,0,606,618,513,505,1350,1360,1258,1250,43,1359,423,438,1360,0,615,617,619,618,1359,423,438,1360,43,1360,438,440,1258,0,618,619,514,513,1360,438,440,1258,43,1361,1362,1363,1364,0,620,621,622,623,1361,1362,1363,1364,43,1364,1363,429,422,0,623,622,174,166,1364,1363,429,422,43,1362,1365,1366,1363,0,621,624,625,622,1362,1365,1366,1363,43,1363,1366,430,429,0,622,625,175,174,1363,1366,430,429,43,1361,1364,1367,1368,0,620,623,626,627,1361,1364,1367,1368,43,1368,1367,415,410,0,627,626,628,629,1368,1367,415,410,43,1364,422,421,1367,0,623,166,165,626,1364,422,421,1367,43,1367,421,416,415,0,626,165,169,628,1367,421,416,415,43,1361,1368,1369,1370,0,620,627,630,631,1361,1368,1369,1370,43,1370,1369,1371,1372,0,631,630,632,633,1370,1369,1371,1372,43,1368,410,409,1369,0,627,629,62,630,1368,410,409,1369,43,1369,409,412,1371,0,630,62,634,632,1369,409,412,1371,43,1361,1370,1373,1362,0,620,631,635,621,1361,1370,1373,1362,43,1362,1373,1374,1365,0,621,635,636,624,1362,1373,1374,1365,43,1370,1372,1375,1373,0,631,633,637,635,1370,1372,1375,1373,43,1373,1375,1376,1374,0,635,637,638,636,1373,1375,1376,1374,43,1377,1378,1379,1380,0,639,640,641,642,1377,1378,1379,1380,43,1380,1379,1345,1340,0,642,641,601,596,1380,1379,1345,1340,43,1378,1381,1382,1379,0,640,643,644,641,1378,1381,1382,1379,43,1379,1382,1346,1345,0,641,644,602,601,1379,1382,1346,1345,43,1377,1380,1383,1384,0,639,642,645,646,1377,1380,1383,1384,43,1384,1383,446,445,0,646,645,191,190,1384,1383,446,445,43,1380,1340,1339,1383,0,642,596,595,645,1380,1340,1339,1383,43,1383,1339,366,446,0,645,595,111,191,1383,1339,366,446,43,1377,1384,1385,1386,0,639,646,647,648,1377,1384,1385,1386,43,1386,1385,1366,1365,0,648,647,625,624,1386,1385,1366,1365,43,1384,445,455,1385,0,646,190,200,647,1384,445,455,1385,43,1385,455,430,1366,0,647,200,175,625,1385,455,430,1366,43,1377,1386,1387,1378,0,639,648,649,640,1377,1386,1387,1378,43,1378,1387,1388,1381,0,640,649,650,643,1378,1387,1388,1381,43,1386,1365,1374,1387,0,648,624,636,649,1386,1365,1374,1387,43,1387,1374,1376,1388,0,649,636,638,650,1387,1374,1376,1388,43,1389,1390,1391,1392,0,651,652,653,654,1389,1390,1391,1392,43,1392,1391,355,347,0,654,653,100,92,1392,1391,355,347,43,1390,1393,1394,1391,0,652,655,656,653,1390,1393,1394,1391,43,1391,1394,356,355,0,653,656,101,100,1391,1394,356,355,43,1389,1392,1395,1396,0,651,654,657,658,1389,1392,1395,1396,43,1396,1395,463,462,0,658,657,208,207,1396,1395,463,462,43,1392,347,346,1395,0,654,92,91,657,1392,347,346,1395,43,1395,346,350,463,0,657,91,95,208,1395,346,350,463,43,1389,1396,1397,1398,0,651,658,659,660,1389,1396,1397,1398,43,1398,1397,1399,1400,0,660,659,661,662,1398,1397,1399,1400,43,1396,462,477,1397,0,658,207,222,659,1396,462,477,1397,43,1397,477,479,1399,0,659,222,14,661,1397,477,479,1399,43,1389,1398,1401,1390,0,651,660,663,652,1389,1398,1401,1390,43,1390,1401,1402,1393,0,652,663,664,655,1390,1401,1402,1393,43,1398,1400,1403,1401,0,660,662,309,663,1398,1400,1403,1401,43,1401,1403,569,1402,0,663,309,665,664,1401,1403,569,1402,43,1404,1405,1406,1407,0,666,667,668,669,1404,1405,1406,1407,43,1407,1406,1408,1409,0,669,668,670,671,1407,1406,1408,1409,43,1405,1410,1411,1406,0,667,672,673,668,1405,1410,1411,1406,43,1406,1411,1412,1408,0,668,673,674,670,1406,1411,1412,1408,43,1404,1407,1413,1414,0,666,669,675,676,1404,1407,1413,1414,43,1414,1413,487,486,0,676,675,231,230,1414,1413,487,486,43,1407,1409,1415,1413,0,669,671,677,675,1407,1409,1415,1413,43,1413,1415,488,487,0,675,677,232,231,1413,1415,488,487,43,1404,1414,1416,1417,0,666,676,678,679,1404,1414,1416,1417,43,1417,1416,1296,1295,0,679,678,680,681,1417,1416,1296,1295,43,1414,486,502,1416,0,676,230,246,678,1414,486,502,1416,43,1416,502,504,1296,0,678,246,248,680,1416,502,504,1296,43,1404,1417,1418,1405,0,666,679,682,667,1404,1417,1418,1405,43,1405,1418,1419,1410,0,667,682,683,672,1405,1418,1419,1410,43,1417,1295,1307,1418,0,679,681,634,682,1417,1295,1307,1418,43,1418,1307,1277,1419,0,682,634,684,683,1418,1307,1277,1419,43,1420,1421,1422,1423,0,685,686,687,688,1420,1421,1422,1423,43,1423,1422,513,510,0,688,687,257,254,1423,1422,513,510,43,1421,1424,1425,1422,0,686,689,690,687,1421,1424,1425,1422,43,1422,1425,391,513,0,687,690,136,257,1422,1425,391,513,43,1420,1423,1426,1427,0,685,688,691,692,1420,1423,1426,1427,43,1427,1426,1415,1409,0,692,691,677,671,1427,1426,1415,1409,43,1423,510,509,1426,0,688,254,253,691,1423,510,509,1426,43,1426,509,488,1415,0,691,253,232,677,1426,509,488,1415,43,1420,1427,1428,1429,0,685,692,693,694,1420,1427,1428,1429,43,1429,1428,1430,1431,0,694,693,695,696,1429,1428,1430,1431,43,1427,1409,1408,1428,0,692,671,670,693,1427,1409,1408,1428,43,1428,1408,1412,1430,0,693,670,674,695,1428,1408,1412,1430,43,1420,1429,1432,1421,0,685,694,697,686,1420,1429,1432,1421,43,1421,1432,1433,1424,0,686,697,698,689,1421,1432,1433,1424,43,1429,1431,1434,1432,0,694,696,699,697,1429,1431,1434,1432,43,1432,1434,1435,1433,0,697,699,700,698,1432,1434,1435,1433,43,1436,1437,1438,1439,0,701,702,703,704,1436,1437,1438,1439,43,1439,1438,1440,1441,0,704,703,705,706,1439,1438,1440,1441,43,1437,1442,1443,1438,0,702,707,708,703,1437,1442,1443,1438,43,1438,1443,1444,1440,0,703,708,709,705,1438,1443,1444,1440,43,1436,1439,1445,1446,0,701,704,710,711,1436,1439,1445,1446,43,1446,1445,380,372,0,711,710,125,117,1446,1445,380,372,43,1439,1441,1447,1445,0,704,706,712,710,1439,1441,1447,1445,43,1445,1447,381,380,0,710,712,126,125,1445,1447,381,380,43,1436,1446,1448,1449,0,701,711,713,714,1436,1446,1448,1449,43,1449,1448,1450,1451,0,714,713,715,716,1449,1448,1450,1451,43,1446,372,371,1448,0,711,117,116,713,1446,372,371,1448,43,1448,371,375,1450,0,713,116,120,715,1448,371,375,1450,43,1436,1449,1452,1437,0,701,714,659,702,1436,1449,1452,1437,43,1437,1452,1453,1442,0,702,659,661,707,1437,1452,1453,1442,43,1449,1451,1454,1452,0,714,716,222,659,1449,1451,1454,1452,43,1452,1454,540,1453,0,659,222,14,661,1452,1454,540,1453,43,1455,1456,1457,1458,0,717,718,719,720,1455,1456,1457,1458,43,1458,1457,1459,1460,0,720,719,721,722,1458,1457,1459,1460,43,1456,1461,1462,1457,0,718,723,724,719,1456,1461,1462,1457,43,1457,1462,1463,1459,0,719,724,725,721,1457,1462,1463,1459,43,1455,1458,1464,1465,0,717,720,726,727,1455,1458,1464,1465,43,1465,1464,1466,1467,0,727,726,728,729,1465,1464,1466,1467,43,1458,1460,1468,1464,0,720,722,730,726,1458,1460,1468,1464,43,1464,1468,1469,1466,0,726,730,731,728,1464,1468,1469,1466,43,1455,1465,1470,1471,0,717,727,732,733,1455,1465,1470,1471,43,1471,1470,597,596,0,733,732,343,342,1471,1470,597,596,43,1465,1467,1472,1470,0,727,729,734,732,1465,1467,1472,1470,43,1470,1472,598,597,0,732,734,344,343,1470,1472,598,597,43,1455,1471,1473,1456,0,717,733,735,718,1455,1471,1473,1456,43,1456,1473,1474,1461,0,718,735,736,723,1456,1473,1474,1461,43,1471,596,612,1473,0,733,342,358,735,1471,596,612,1473,43,1473,612,614,1474,0,735,358,360,736,1473,612,614,1474,43,1475,1476,1477,1478,0,737,738,739,740,1475,1476,1477,1478,43,1478,1477,1301,1294,0,740,739,557,550,1478,1477,1301,1294,43,1476,1479,1480,1477,0,738,741,742,739,1476,1479,1480,1477,43,1477,1480,1302,1301,0,739,742,558,557,1477,1480,1302,1301,43,1475,1478,1481,1482,0,737,740,743,744,1475,1478,1481,1482,43,1482,1481,503,498,0,744,743,680,745,1482,1481,503,498,43,1478,1294,1293,1481,0,740,550,549,743,1478,1294,1293,1481,43,1481,1293,504,503,0,743,549,248,680,1481,1293,504,503,43,1475,1482,1483,1484,0,737,744,746,747,1475,1482,1483,1484,43,1484,1483,1485,1486,0,747,746,748,749,1484,1483,1485,1486,43,1482,498,497,1483,0,744,745,62,746,1482,498,497,1483,43,1483,497,500,1485,0,746,62,634,748,1483,497,500,1485,43,1475,1484,1487,1476,0,737,747,750,738,1475,1484,1487,1476,43,1476,1487,1488,1479,0,738,750,751,741,1476,1487,1488,1479,43,1484,1486,1489,1487,0,747,749,752,750,1484,1486,1489,1487,43,1487,1489,1490,1488,0,750,752,753,751,1487,1489,1490,1488,43,1491,1492,1493,1494,0,754,755,756,757,1491,1492,1493,1494,43,1494,1493,1495,1496,0,757,756,758,759,1494,1493,1495,1496,43,1492,1497,1498,1493,0,755,760,761,756,1492,1497,1498,1493,43,1493,1498,1499,1495,0,756,761,762,758,1493,1498,1499,1495,43,1491,1494,1500,1501,0,754,757,763,764,1491,1494,1500,1501,43,1501,1500,1502,1503,0,764,763,765,766,1501,1500,1502,1503,43,1494,1496,1504,1500,0,757,759,767,763,1494,1496,1504,1500,43,1500,1504,1505,1502,0,763,767,768,765,1500,1504,1505,1502,43,1491,1501,1506,1507,0,754,764,769,770,1491,1501,1506,1507,43,1507,1506,1331,1326,0,770,769,587,582,1507,1506,1331,1326,43,1501,1503,1508,1506,0,764,766,771,769,1501,1503,1508,1506,43,1506,1508,1332,1331,0,769,771,588,587,1506,1508,1332,1331,43,1491,1507,1509,1492,0,754,770,772,755,1491,1507,1509,1492,43,1492,1509,1510,1497,0,755,772,773,760,1492,1509,1510,1497,43,1507,1326,1325,1509,0,770,582,581,772,1507,1326,1325,1509,43,1509,1325,1328,1510,0,772,581,584,773,1509,1325,1328,1510,43,1511,1512,1513,1514,0,386,386,62,62,1511,1512,1513,1514,43,1514,1513,1515,1516,0,62,62,16,16,1514,1513,1515,1516,43,1512,1517,1518,1513,0,386,386,62,62,1512,1517,1518,1513,43,1513,1518,1519,1515,0,62,62,16,16,1513,1518,1519,1515,43,1511,1514,1520,1521,0,386,62,62,386,1511,1514,1520,1521,43,1521,1520,804,803,0,386,62,62,386,1521,1520,804,803,43,1514,1516,1522,1520,0,62,16,16,62,1514,1516,1522,1520,43,1520,1522,805,804,0,62,16,16,62,1520,1522,805,804,43,1511,1521,1523,1524,0,386,386,385,385,1511,1521,1523,1524,43,1524,1523,1525,1526,0,385,385,388,388,1524,1523,1525,1526,43,1521,803,819,1523,0,386,386,385,385,1521,803,819,1523,43,1523,819,821,1525,0,385,385,388,388,1523,819,821,1525,43,1511,1524,1527,1512,0,386,385,385,386,1511,1524,1527,1512,43,1512,1527,1528,1517,0,386,385,385,386,1512,1527,1528,1517,43,1524,1526,1529,1527,0,385,388,388,385,1524,1526,1529,1527,43,1527,1529,1530,1528,0,385,388,388,385,1527,1529,1530,1528,43,1531,1532,1533,1534,0,403,405,405,403,1531,1532,1533,1534,43,1534,1533,833,827,0,403,405,405,403,1534,1533,833,827,43,1532,1526,1525,1533,0,405,388,388,405,1532,1526,1525,1533,43,1533,1525,821,833,0,405,388,388,405,1533,1525,821,833,43,1531,1534,1535,1536,0,403,403,66,66,1531,1534,1535,1536,43,1536,1535,1537,1538,0,66,66,18,18,1536,1535,1537,1538,43,1534,827,826,1535,0,403,403,66,66,1534,827,826,1535,43,1535,826,830,1537,0,66,66,18,18,1535,826,830,1537,43,1531,1536,1539,1540,0,403,66,66,403,1531,1536,1539,1540,43,1540,1539,1541,1542,0,403,66,66,403,1540,1539,1541,1542,43,1536,1538,1543,1539,0,66,18,18,66,1536,1538,1543,1539,43,1539,1543,1544,1541,0,66,18,18,66,1539,1543,1544,1541,43,1531,1540,1545,1532,0,403,403,405,405,1531,1540,1545,1532,43,1532,1545,1529,1526,0,405,405,388,388,1532,1545,1529,1526,43,1540,1542,1546,1545,0,403,403,405,405,1540,1542,1546,1545,43,1545,1546,1530,1529,0,405,405,388,388,1545,1546,1530,1529,43,1547,1548,1549,1550,0,418,421,421,418,1547,1548,1549,1550,43,1550,1549,853,847,0,418,421,421,418,1550,1549,853,847,43,1548,1538,1537,1549,0,421,18,18,421,1548,1538,1537,1549,43,1549,1537,830,853,0,421,18,18,421,1549,1537,830,853,43,1547,1550,1551,1552,0,418,418,417,417,1547,1550,1551,1552,43,1552,1551,1553,1554,0,417,417,420,420,1552,1551,1553,1554,43,1550,847,846,1551,0,418,418,417,417,1550,847,846,1551,43,1551,846,850,1553,0,417,417,420,420,1551,846,850,1553,43,1547,1552,1555,1556,0,418,417,417,418,1547,1552,1555,1556,43,1556,1555,1557,1558,0,418,417,417,418,1556,1555,1557,1558,43,1552,1554,1559,1555,0,417,420,420,417,1552,1554,1559,1555,43,1555,1559,1560,1557,0,417,420,420,417,1555,1559,1560,1557,43,1547,1556,1561,1548,0,418,418,421,421,1547,1556,1561,1548,43,1548,1561,1543,1538,0,421,421,18,18,1548,1561,1543,1538,43,1556,1558,1562,1561,0,418,418,421,421,1556,1558,1562,1561,43,1561,1562,1544,1543,0,421,421,18,18,1561,1562,1544,1543,43,1563,1564,1565,1566,0,433,433,435,435,1563,1564,1565,1566,43,1566,1565,1559,1554,0,435,435,420,420,1566,1565,1559,1554,43,1564,1567,1568,1565,0,433,433,435,435,1564,1567,1568,1565,43,1565,1568,1560,1559,0,435,435,420,420,1565,1568,1560,1559,43,1563,1566,1569,1570,0,433,435,435,433,1563,1566,1569,1570,43,1570,1569,867,866,0,433,435,435,433,1570,1569,867,866,43,1566,1554,1553,1569,0,435,420,420,435,1566,1554,1553,1569,43,1569,1553,850,867,0,435,420,420,435,1569,1553,850,867,43,1563,1570,1571,1572,0,433,433,9,9,1563,1570,1571,1572,43,1572,1571,1573,1574,0,9,9,10,10,1572,1571,1573,1574,43,1570,866,879,1571,0,433,433,9,9,1570,866,879,1571,43,1571,879,881,1573,0,9,9,10,10,1571,879,881,1573,43,1563,1572,1575,1564,0,433,9,9,433,1563,1572,1575,1564,43,1564,1575,1576,1567,0,433,9,9,433,1564,1575,1576,1567,43,1572,1574,1577,1575,0,9,10,10,9,1572,1574,1577,1575,43,1575,1577,1578,1576,0,9,10,10,9,1575,1577,1578,1576,43,1579,1580,1581,1582,0,448,448,35,35,1579,1580,1581,1582,43,1582,1581,1577,1574,0,35,35,10,10,1582,1581,1577,1574,43,1580,1583,1584,1581,0,448,448,35,35,1580,1583,1584,1581,43,1581,1584,1578,1577,0,35,35,10,10,1581,1584,1578,1577,43,1579,1582,1585,1586,0,448,35,35,448,1579,1582,1585,1586,43,1586,1585,887,886,0,448,35,35,448,1586,1585,887,886,43,1582,1574,1573,1585,0,35,10,10,35,1582,1574,1573,1585,43,1585,1573,881,887,0,35,10,10,35,1585,1573,881,887,43,1579,1586,1587,1588,0,448,448,447,447,1579,1586,1587,1588,43,1588,1587,1589,1590,0,447,447,450,450,1588,1587,1589,1590,43,1586,886,899,1587,0,448,448,447,447,1586,886,899,1587,43,1587,899,901,1589,0,447,447,450,450,1587,899,901,1589,43,1579,1588,1591,1580,0,448,447,447,448,1579,1588,1591,1580,43,1580,1591,1592,1583,0,448,447,447,448,1580,1591,1592,1583,43,1588,1590,1593,1591,0,447,450,450,447,1588,1590,1593,1591,43,1591,1593,1594,1592,0,447,450,450,447,1591,1593,1594,1592,43,1595,1596,1597,1598,0,462,462,464,464,1595,1596,1597,1598,43,1598,1597,1593,1590,0,464,464,450,450,1598,1597,1593,1590,43,1596,1599,1600,1597,0,462,462,464,464,1596,1599,1600,1597,43,1597,1600,1594,1593,0,464,464,450,450,1597,1600,1594,1593,43,1595,1598,1601,1602,0,462,464,464,462,1595,1598,1601,1602,43,1602,1601,907,906,0,462,464,464,462,1602,1601,907,906,43,1598,1590,1589,1601,0,464,450,450,464,1598,1590,1589,1601,43,1601,1589,901,907,0,464,450,450,464,1601,1589,901,907,43,1595,1602,1603,1604,0,462,462,22,22,1595,1602,1603,1604,43,1604,1603,1605,1606,0,22,22,23,23,1604,1603,1605,1606,43,1602,906,919,1603,0,462,462,22,22,1602,906,919,1603,43,1603,919,921,1605,0,22,22,23,23,1603,919,921,1605,43,1595,1604,1607,1596,0,462,22,22,462,1595,1604,1607,1596,43,1596,1607,1608,1599,0,462,22,22,462,1596,1607,1608,1599,43,1604,1606,1609,1607,0,22,23,23,22,1604,1606,1609,1607,43,1607,1609,1610,1608,0,22,23,23,22,1607,1609,1610,1608,43,1611,1612,1613,1614,0,477,57,57,477,1611,1612,1613,1614,43,1614,1613,933,927,0,477,57,57,477,1614,1613,933,927,43,1612,1606,1605,1613,0,57,23,23,57,1612,1606,1605,1613,43,1613,1605,921,933,0,57,23,23,57,1613,1605,921,933,43,1611,1614,1615,1616,0,477,477,476,476,1611,1614,1615,1616,43,1616,1615,1617,1618,0,476,476,479,479,1616,1615,1617,1618,43,1614,927,926,1615,0,477,477,476,476,1614,927,926,1615,43,1615,926,930,1617,0,476,476,479,479,1615,926,930,1617,43,1611,1616,1619,1620,0,477,476,476,477,1611,1616,1619,1620,43,1620,1619,1621,1622,0,477,476,476,477,1620,1619,1621,1622,43,1616,1618,1623,1619,0,476,479,479,476,1616,1618,1623,1619,43,1619,1623,1624,1621,0,476,479,479,476,1619,1623,1624,1621,43,1611,1620,1625,1612,0,477,477,57,57,1611,1620,1625,1612,43,1612,1625,1609,1606,0,57,57,23,23,1612,1625,1609,1606,43,1620,1622,1626,1625,0,477,477,57,57,1620,1622,1626,1625,43,1625,1626,1610,1609,0,57,57,23,23,1625,1626,1610,1609,43,1627,1628,1629,1630,0,491,492,492,491,1627,1628,1629,1630,43,1630,1629,950,947,0,491,492,492,491,1630,1629,950,947,43,1628,1618,1617,1629,0,492,479,479,492,1628,1618,1617,1629,43,1629,1617,930,950,0,492,479,479,492,1629,1617,930,950,43,1627,1630,1631,1632,0,491,491,15,15,1627,1630,1631,1632,43,1632,1631,1522,1516,0,15,15,16,16,1632,1631,1522,1516,43,1630,947,946,1631,0,491,491,15,15,1630,947,946,1631,43,1631,946,805,1522,0,15,15,16,16,1631,946,805,1522,43,1627,1632,1633,1634,0,491,15,15,491,1627,1632,1633,1634,43,1634,1633,1635,1636,0,491,15,15,491,1634,1633,1635,1636,43,1632,1516,1515,1633,0,15,16,16,15,1632,1516,1515,1633,43,1633,1515,1519,1635,0,15,16,16,15,1633,1515,1519,1635,43,1627,1634,1637,1628,0,491,491,492,492,1627,1634,1637,1628,43,1628,1637,1623,1618,0,492,492,479,479,1628,1637,1623,1618,43,1634,1636,1638,1637,0,491,491,492,492,1634,1636,1638,1637,43,1637,1638,1624,1623,0,492,492,479,479,1637,1638,1624,1623,43,1639,1640,1641,1642,0,386,62,62,386,1639,1640,1641,1642,43,1642,1641,1124,1123,0,386,62,62,386,1642,1641,1124,1123,43,1640,1643,1644,1641,0,62,16,16,62,1640,1643,1644,1641,43,1641,1644,1125,1124,0,62,16,16,62,1641,1644,1125,1124,43,1639,1642,1645,1646,0,386,386,385,385,1639,1642,1645,1646,43,1646,1645,1647,1648,0,385,385,388,388,1646,1645,1647,1648,43,1642,1123,1134,1645,0,386,386,385,385,1642,1123,1134,1645,43,1645,1134,1136,1647,0,385,385,388,388,1645,1134,1136,1647,43,1639,1646,1649,1650,0,386,385,385,386,1639,1646,1649,1650,43,1650,1649,1651,1652,0,386,385,385,386,1650,1649,1651,1652,43,1646,1648,1653,1649,0,385,388,388,385,1646,1648,1653,1649,43,1639,1650,1654,1640,0,386,386,62,62,1639,1650,1654,1640,43,1640,1654,1655,1643,0,62,62,16,16,1640,1654,1655,1643,43,1650,1652,1656,1654,0,386,386,62,62,1650,1652,1656,1654,43,1657,1658,1659,1660,0,15,16,16,15,1657,1658,1659,1660,43,1661,1662,1663,1664,0,403,405,405,403,1661,1662,1663,1664,43,1664,1663,1152,1148,0,403,405,405,403,1664,1663,1152,1148,43,1662,1648,1647,1663,0,405,388,388,405,1662,1648,1647,1663,43,1663,1647,1136,1152,0,405,388,388,405,1663,1647,1136,1152,43,1661,1664,1665,1666,0,403,403,66,66,1661,1664,1665,1666,43,1666,1665,1667,1668,0,66,66,18,18,1666,1665,1667,1668,43,1664,1148,1147,1665,0,403,403,66,66,1664,1148,1147,1665,43,1665,1147,1150,1667,0,66,66,18,18,1665,1147,1150,1667,43,1661,1666,1669,1670,0,403,66,66,403,1661,1666,1669,1670,43,1670,1669,1671,1672,0,403,66,66,403,1670,1669,1671,1672,43,1666,1668,1673,1669,0,66,18,18,66,1666,1668,1673,1669,43,1674,1675,1676,1677,0,421,774,774,421,1674,1675,1676,1677,43,1661,1670,1678,1662,0,403,403,405,405,1661,1670,1678,1662,43,1662,1678,1653,1648,0,405,405,388,388,1662,1678,1653,1648,43,1670,1672,1679,1678,0,403,403,405,405,1670,1672,1679,1678,43,1680,1681,1682,1683,0,418,421,421,418,1680,1681,1682,1683,43,1683,1682,1168,1164,0,418,421,421,418,1683,1682,1168,1164,43,1681,1668,1667,1682,0,421,18,18,421,1681,1668,1667,1682,43,1682,1667,1150,1168,0,421,18,18,421,1682,1667,1150,1168,43,1680,1683,1684,1685,0,418,418,417,417,1680,1683,1684,1685,43,1685,1684,1686,1687,0,417,417,420,420,1685,1684,1686,1687,43,1683,1164,1163,1684,0,418,418,417,417,1683,1164,1163,1684,43,1684,1163,1166,1686,0,417,417,420,420,1684,1163,1166,1686,43,1680,1685,1688,1689,0,418,417,417,418,1680,1685,1688,1689,43,1689,1688,1690,1675,0,418,417,417,418,1689,1688,1690,1675,43,1685,1687,1691,1688,0,417,420,420,417,1685,1687,1691,1688,43,1680,1689,1692,1681,0,418,418,421,421,1680,1689,1692,1681,43,1681,1692,1673,1668,0,421,421,18,18,1681,1692,1673,1668,43,1689,1675,1674,1692,0,418,418,421,421,1689,1675,1674,1692,43,1693,1694,1695,1696,0,433,433,435,435,1693,1694,1695,1696,43,1696,1695,1691,1687,0,435,435,420,420,1696,1695,1691,1687,43,1694,260,1697,1695,0,433,433,435,435,1694,260,1697,1695,43,1693,1696,1698,1699,0,433,435,435,433,1693,1696,1698,1699,43,1699,1698,1174,1173,0,433,435,435,433,1699,1698,1174,1173,43,1696,1687,1686,1698,0,435,420,420,435,1696,1687,1686,1698,43,1698,1686,1166,1174,0,435,420,420,435,1698,1686,1166,1174,43,1693,1699,1700,1701,0,433,433,9,9,1693,1699,1700,1701,43,1701,1700,1702,1703,0,9,9,10,10,1701,1700,1702,1703,43,1699,1173,1182,1700,0,433,433,9,9,1699,1173,1182,1700,43,1700,1182,1184,1702,0,9,9,10,10,1700,1182,1184,1702,43,1693,1701,1704,1694,0,433,9,9,433,1693,1701,1704,1694,43,1694,1704,261,260,0,433,9,9,433,1694,1704,261,260,43,1701,1703,1705,1704,0,9,10,10,9,1701,1703,1705,1704,43,1706,1707,1708,1709,0,448,448,35,35,1706,1707,1708,1709,43,1709,1708,1705,1703,0,35,35,10,10,1709,1708,1705,1703,43,1707,1710,1711,1708,0,448,448,35,35,1707,1710,1711,1708,43,1706,1709,1712,1713,0,448,35,35,448,1706,1709,1712,1713,43,1713,1712,1190,1189,0,448,35,35,448,1713,1712,1190,1189,43,1709,1703,1702,1712,0,35,10,10,35,1709,1703,1702,1712,43,1712,1702,1184,1190,0,35,10,10,35,1712,1702,1184,1190,43,1706,1713,1714,1715,0,448,448,447,447,1706,1713,1714,1715,43,1715,1714,1716,1717,0,447,447,450,450,1715,1714,1716,1717,43,1713,1189,1198,1714,0,448,448,447,447,1713,1189,1198,1714,43,1714,1198,1200,1716,0,447,447,450,450,1714,1198,1200,1716,43,1706,1715,1718,1707,0,448,447,447,448,1706,1715,1718,1707,43,1707,1718,1719,1710,0,448,447,447,448,1707,1718,1719,1710,43,1715,1717,1720,1718,0,447,450,450,447,1715,1717,1720,1718,43,1721,1722,1723,1724,0,462,464,464,462,1721,1722,1723,1724,43,1724,1723,1206,1205,0,462,464,464,462,1724,1723,1206,1205,43,1722,1717,1716,1723,0,464,450,450,464,1722,1717,1716,1723,43,1723,1716,1200,1206,0,464,450,450,464,1723,1716,1200,1206,43,1721,1724,1725,1726,0,462,462,22,22,1721,1724,1725,1726,43,1726,1725,1727,1728,0,22,22,23,23,1726,1725,1727,1728,43,1724,1205,1214,1725,0,462,462,22,22,1724,1205,1214,1725,43,1725,1214,1216,1727,0,22,22,23,23,1725,1214,1216,1727,43,1721,1726,1729,1730,0,462,22,22,462,1721,1726,1729,1730,43,1730,1729,1731,1732,0,462,22,22,462,1730,1729,1731,1732,43,1726,1728,1733,1729,0,22,23,23,22,1726,1728,1733,1729,43,1731,1734,1735,1736,0,22,23,23,22,1731,1734,1735,1736,43,1721,1730,1737,1722,0,462,462,464,464,1721,1730,1737,1722,43,1722,1737,1720,1717,0,464,464,450,450,1722,1737,1720,1717,43,1730,1732,1738,1737,0,462,462,464,464,1730,1732,1738,1737,43,1739,1719,1740,1741,0,11,11,12,12,1739,1719,1740,1741,43,1742,1743,1744,1745,0,477,57,57,477,1742,1743,1744,1745,43,1745,1744,1232,1228,0,477,57,57,477,1745,1744,1232,1228,43,1743,1728,1727,1744,0,57,23,23,57,1743,1728,1727,1744,43,1744,1727,1216,1232,0,57,23,23,57,1744,1727,1216,1232,43,1742,1745,1746,1747,0,477,477,476,476,1742,1745,1746,1747,43,1747,1746,1748,1749,0,476,476,479,479,1747,1746,1748,1749,43,1745,1228,1227,1746,0,477,477,476,476,1745,1228,1227,1746,43,1746,1227,1230,1748,0,476,476,479,479,1746,1227,1230,1748,43,1742,1747,1750,1751,0,477,476,476,477,1742,1747,1750,1751,43,1751,1750,1752,1753,0,477,476,476,477,1751,1750,1752,1753,43,1747,1749,1754,1750,0,476,479,479,476,1747,1749,1754,1750,43,1752,1755,1756,1757,0,65,30,30,65,1752,1755,1756,1757,43,1742,1751,1758,1743,0,477,477,57,57,1742,1751,1758,1743,43,1743,1758,1733,1728,0,57,57,23,23,1743,1758,1733,1728,43,1751,1753,1759,1758,0,477,477,57,57,1751,1753,1759,1758,43,1738,1732,1760,1761,0,14,775,775,14,1738,1732,1760,1761,43,1762,1763,1764,1765,0,491,492,492,491,1762,1763,1764,1765,43,1765,1764,1244,1242,0,491,492,492,491,1765,1764,1244,1242,43,1763,1749,1748,1764,0,492,479,479,492,1763,1749,1748,1764,43,1764,1748,1230,1244,0,492,479,479,492,1764,1748,1230,1244,43,1762,1765,1766,1767,0,491,491,15,15,1762,1765,1766,1767,43,1767,1766,1644,1643,0,15,15,16,16,1767,1766,1644,1643,43,1765,1242,1241,1766,0,491,491,15,15,1765,1242,1241,1766,43,1766,1241,1125,1644,0,15,15,16,16,1766,1241,1125,1644,43,1762,1767,1768,1769,0,491,15,15,491,1762,1767,1768,1769,43,1769,1768,1657,1770,0,491,15,15,491,1769,1768,1657,1770,43,1767,1643,1655,1768,0,15,16,16,15,1767,1643,1655,1768,43,1771,1770,1772,1773,0,61,776,776,61,1771,1770,1772,1773,43,1762,1769,1774,1763,0,491,491,492,492,1762,1769,1774,1763,43,1763,1774,1754,1749,0,492,492,479,479,1763,1774,1754,1749,43,1769,1770,1771,1774,0,491,491,492,492,1769,1770,1771,1774,43,1759,1753,1775,1776,0,57,777,777,57,1759,1753,1775,1776,43,1777,1778,1779,1780,0,778,779,780,781,1777,1778,1779,1780,43,1780,1779,1781,1782,0,781,780,782,783,1780,1779,1781,1782,43,1778,655,654,1779,0,779,394,393,780,1778,655,654,1779,43,1779,654,657,1781,0,780,393,396,782,1779,654,657,1781,43,1777,1780,1783,1784,0,778,781,784,785,1777,1780,1783,1784,43,1784,1783,1411,1410,0,785,784,673,672,1784,1783,1411,1410,43,1780,1782,1785,1783,0,781,783,786,784,1780,1782,1785,1783,43,1783,1785,1412,1411,0,784,786,674,673,1783,1785,1412,1411,43,1777,1784,1786,1787,0,778,785,787,788,1777,1784,1786,1787,43,1787,1786,1788,1789,0,788,787,789,790,1787,1786,1788,1789,43,1784,1410,1419,1786,0,785,672,683,787,1784,1410,1419,1786,43,1786,1419,1277,1788,0,787,683,684,789,1786,1419,1277,1788,43,1777,1787,1790,1778,0,778,788,791,779,1777,1787,1790,1778,43,1778,1790,660,655,0,779,791,399,394,1778,1790,660,655,43,1787,1789,1791,1790,0,788,790,792,791,1787,1789,1791,1790,43,1790,1791,661,660,0,791,792,400,399,1790,1791,661,660,43,1792,1793,1794,1795,0,793,794,795,796,1792,1793,1794,1795,43,1795,1794,1791,1789,0,796,795,797,798,1795,1794,1791,1789,43,1793,677,676,1794,0,794,410,409,795,1793,677,676,1794,43,1794,676,661,1791,0,795,409,400,797,1794,676,661,1791,43,1792,1795,1796,1797,0,793,796,799,800,1792,1795,1796,1797,43,1797,1796,1276,1275,0,800,799,532,531,1797,1796,1276,1275,43,1795,1789,1788,1796,0,796,798,30,799,1795,1789,1788,1796,43,1796,1788,1277,1276,0,799,30,533,532,1796,1788,1277,1276,43,1792,1797,1798,1799,0,793,800,801,802,1792,1797,1798,1799,43,1799,1798,1800,1801,0,802,801,803,804,1799,1798,1800,1801,43,1797,1275,1288,1798,0,800,531,544,801,1797,1275,1288,1798,43,1798,1288,1264,1800,0,801,544,520,803,1798,1288,1264,1800,43,1792,1799,1802,1793,0,793,802,805,794,1792,1799,1802,1793,43,1793,1802,680,677,0,794,805,413,410,1793,1802,680,677,43,1799,1801,1803,1802,0,802,804,806,805,1799,1801,1803,1802,43,1802,1803,681,680,0,805,806,414,413,1802,1803,681,680,43,1804,1805,1806,1807,0,807,808,809,810,1804,1805,1806,1807,43,1807,1806,1263,1257,0,810,809,519,512,1807,1806,1263,1257,43,1805,1801,1800,1806,0,808,804,803,809,1805,1801,1800,1806,43,1806,1800,1264,1263,0,809,803,520,519,1806,1800,1264,1263,43,1804,1807,1808,1809,0,807,810,811,812,1804,1807,1808,1809,43,1809,1808,1810,1811,0,812,811,813,814,1809,1808,1810,1811,43,1807,1257,1256,1808,0,810,512,511,811,1807,1257,1256,1808,43,1808,1256,440,1810,0,811,511,514,813,1808,1256,440,1810,43,1804,1809,1812,1813,0,807,812,815,816,1804,1809,1812,1813,43,1813,1812,700,697,0,816,815,429,426,1813,1812,700,697,43,1809,1811,1814,1812,0,812,814,817,815,1809,1811,1814,1812,43,1812,1814,701,700,0,815,817,430,429,1812,1814,701,700,43,1804,1813,1815,1805,0,807,816,818,808,1804,1813,1815,1805,43,1805,1815,1803,1801,0,808,818,806,804,1805,1815,1803,1801,43,1813,697,696,1815,0,816,426,425,818,1813,697,696,1815,43,1815,696,681,1803,0,818,425,414,806,1815,696,681,1803,43,1816,1817,1818,1819,0,819,820,821,822,1816,1817,1818,1819,43,1819,1818,439,434,0,822,821,184,179,1819,1818,439,434,43,1817,1811,1810,1818,0,820,823,824,821,1817,1811,1810,1818,43,1818,1810,440,439,0,821,824,185,184,1818,1810,440,439,43,1816,1819,1820,1821,0,819,822,825,826,1816,1819,1820,1821,43,1821,1820,1822,1823,0,826,825,827,828,1821,1820,1822,1823,43,1819,434,433,1820,0,822,179,178,825,1819,434,433,1820,43,1820,433,436,1822,0,825,178,181,827,1820,433,436,1822,43,1816,1821,1824,1825,0,819,826,829,830,1816,1821,1824,1825,43,1825,1824,720,717,0,830,829,443,440,1825,1824,720,717,43,1821,1823,1826,1824,0,826,828,831,829,1821,1823,1826,1824,43,1824,1826,721,720,0,829,831,444,443,1824,1826,721,720,43,1816,1825,1827,1817,0,819,830,832,820,1816,1825,1827,1817,43,1817,1827,1814,1811,0,820,832,833,823,1817,1827,1814,1811,43,1825,717,716,1827,0,830,440,439,832,1825,717,716,1827,43,1827,716,701,1814,0,832,439,430,833,1827,716,701,1814,43,1828,1829,1830,1831,0,834,835,836,837,1828,1829,1830,1831,43,1831,1830,453,450,0,837,836,198,195,1831,1830,453,450,43,1829,1823,1822,1830,0,835,828,827,836,1829,1823,1822,1830,43,1830,1822,436,453,0,836,827,181,198,1830,1822,436,453,43,1828,1831,1832,1833,0,834,837,838,839,1828,1831,1832,1833,43,1833,1832,1834,1835,0,839,838,840,841,1833,1832,1834,1835,43,1831,450,449,1832,0,837,195,194,838,1831,450,449,1832,43,1832,449,362,1834,0,838,194,107,840,1832,449,362,1834,43,1828,1833,1836,1837,0,834,839,842,843,1828,1833,1836,1837,43,1837,1836,740,737,0,843,842,458,455,1837,1836,740,737,43,1833,1835,1838,1836,0,839,841,844,842,1833,1835,1838,1836,43,1836,1838,741,740,0,842,844,459,458,1836,1838,741,740,43,1828,1837,1839,1829,0,834,843,845,835,1828,1837,1839,1829,43,1829,1839,1826,1823,0,835,845,831,828,1829,1839,1826,1823,43,1837,737,736,1839,0,843,455,454,845,1837,737,736,1839,43,1839,736,721,1826,0,845,454,444,831,1839,736,721,1826,43,1840,1841,1842,1843,0,846,847,848,849,1840,1841,1842,1843,43,1843,1842,587,582,0,849,848,330,323,1843,1842,587,582,43,1841,1835,1834,1842,0,847,850,851,848,1841,1835,1834,1842,43,1842,1834,362,587,0,848,851,331,330,1842,1834,362,587,43,1840,1843,1844,1845,0,846,849,852,853,1840,1843,1844,1845,43,1845,1844,1846,1847,0,853,852,854,855,1845,1844,1846,1847,43,1843,582,581,1844,0,849,323,322,852,1843,582,581,1844,43,1844,581,584,1846,0,852,322,325,854,1844,581,584,1846,43,1840,1845,1848,1849,0,846,853,856,857,1840,1845,1848,1849,43,1849,1848,760,757,0,857,856,472,469,1849,1848,760,757,43,1845,1847,1850,1848,0,853,855,858,856,1845,1847,1850,1848,43,1848,1850,761,760,0,856,858,473,472,1848,1850,761,760,43,1840,1849,1851,1841,0,846,857,859,847,1840,1849,1851,1841,43,1841,1851,1838,1835,0,847,859,860,850,1841,1851,1838,1835,43,1849,757,756,1851,0,857,469,468,859,1849,757,756,1851,43,1851,756,741,1838,0,859,468,459,860,1851,756,741,1838,43,1852,1853,1854,1855,0,861,862,863,864,1852,1853,1854,1855,43,1855,1854,1856,1857,0,864,863,865,866,1855,1854,1856,1857,43,1853,1847,1846,1854,0,862,855,854,863,1853,1847,1846,1854,43,1854,1846,584,1856,0,863,854,325,865,1854,1846,584,1856,43,1852,1855,1858,1859,0,861,864,867,868,1852,1855,1858,1859,43,1859,1858,1860,1861,0,868,867,869,870,1859,1858,1860,1861,43,1855,1857,1862,1858,0,864,866,871,867,1855,1857,1862,1858,43,1858,1862,1435,1860,0,867,871,872,869,1858,1862,1435,1860,43,1852,1859,1863,1864,0,861,868,873,874,1852,1859,1863,1864,43,1864,1863,780,777,0,874,873,487,484,1864,1863,780,777,43,1859,1861,1865,1863,0,868,870,875,873,1859,1861,1865,1863,43,1863,1865,781,780,0,873,875,488,487,1863,1865,781,780,43,1852,1864,1866,1853,0,861,874,876,862,1852,1864,1866,1853,43,1853,1866,1850,1847,0,862,876,858,855,1853,1866,1850,1847,43,1864,777,776,1866,0,874,484,483,876,1864,777,776,1866,43,1866,776,761,1850,0,876,483,473,858,1866,776,761,1850,43,1867,1868,1869,1870,0,877,878,879,880,1867,1868,1869,1870,43,1870,1869,1865,1861,0,880,879,881,882,1870,1869,1865,1861,43,1868,794,793,1869,0,878,497,496,879,1868,794,793,1869,43,1869,793,781,1865,0,879,496,488,881,1869,793,781,1865,43,1867,1870,1871,1872,0,877,880,883,884,1867,1870,1871,1872,43,1872,1871,1434,1431,0,884,883,699,696,1872,1871,1434,1431,43,1870,1861,1860,1871,0,880,882,885,883,1870,1861,1860,1871,43,1871,1860,1435,1434,0,883,885,700,699,1871,1860,1435,1434,43,1867,1872,1873,1874,0,877,884,886,887,1867,1872,1873,1874,43,1874,1873,1785,1782,0,887,886,786,783,1874,1873,1785,1782,43,1872,1431,1430,1873,0,884,696,695,886,1872,1431,1430,1873,43,1873,1430,1412,1785,0,886,695,674,786,1873,1430,1412,1785,43,1867,1874,1875,1868,0,877,887,888,878,1867,1874,1875,1868,43,1868,1875,796,794,0,878,888,499,497,1868,1875,796,794,43,1874,1782,1781,1875,0,887,783,782,888,1874,1782,1781,1875,43,1875,1781,657,796,0,888,782,396,499,1875,1781,657,796,43,1876,1877,1878,1879,0,889,890,891,892,1876,1877,1878,1879,43,1879,1878,1425,1424,0,892,891,690,689,1879,1878,1425,1424,43,1877,373,389,1878,0,890,118,134,891,1877,373,389,1878,43,1878,389,391,1425,0,891,134,136,690,1878,389,391,1425,43,1876,1879,1880,1881,0,889,892,893,894,1876,1879,1880,1881,43,1881,1880,1882,1883,0,894,893,895,896,1881,1880,1882,1883,43,1879,1424,1433,1880,0,892,689,698,893,1879,1424,1433,1880,43,1880,1433,1435,1882,0,893,698,700,895,1880,1433,1435,1882,43,1876,1881,1884,1885,0,889,894,897,898,1876,1881,1884,1885,43,1885,1884,1886,1887,0,898,897,899,900,1885,1884,1886,1887,43,1881,1883,1888,1884,0,894,896,901,897,1881,1883,1888,1884,43,1884,1888,628,1886,0,897,901,902,899,1884,1888,628,1886,43,1876,1885,1889,1877,0,889,898,903,890,1876,1885,1889,1877,43,1877,1889,374,373,0,890,903,119,118,1877,1889,374,373,43,1885,1887,1890,1889,0,898,900,904,903,1885,1887,1890,1889,43,1889,1890,375,374,0,903,904,120,119,1889,1890,375,374,43,1891,1892,1893,1894,0,905,906,906,905,1891,1892,1893,1894,43,1894,1893,1895,1896,0,905,906,906,905,1894,1893,1895,1896,43,1892,1897,1898,1893,0,906,12,12,906,1892,1897,1898,1893,43,1893,1898,1899,1895,0,906,12,12,906,1893,1898,1899,1895,43,1891,1894,1900,1901,0,905,905,9,9,1891,1894,1900,1901,43,1901,1900,1902,1903,0,9,9,156,156,1901,1900,1902,1903,43,1894,1896,1904,1900,0,905,905,9,9,1894,1896,1904,1900,43,1900,1904,1905,1902,0,9,9,156,156,1900,1904,1905,1902,43,1891,1901,1906,1907,0,905,9,9,905,1891,1901,1906,1907,43,1907,1906,1908,1909,0,905,9,9,905,1907,1906,1908,1909,43,1901,1903,1910,1906,0,9,156,156,9,1901,1903,1910,1906,43,1906,1910,1911,1908,0,9,156,156,9,1906,1910,1911,1908,43,1891,1907,1912,1892,0,905,905,906,906,1891,1907,1912,1892,43,1892,1912,1913,1897,0,906,906,12,12,1892,1912,1913,1897,43,1907,1909,1914,1912,0,905,905,906,906,1907,1909,1914,1912,43,1912,1914,1915,1913,0,906,906,12,12,1912,1914,1915,1913,43,1916,1917,1918,1919,0,10,906,906,10,1916,1917,1918,1919,43,1919,1918,1920,1921,0,10,906,906,10,1919,1918,1920,1921,43,1917,1922,1923,1918,0,906,12,12,906,1917,1922,1923,1918,43,1918,1923,1924,1920,0,906,12,12,906,1918,1923,1924,1920,43,1916,1919,1925,1926,0,10,10,67,67,1916,1919,1925,1926,43,1926,1925,1927,1928,0,67,67,6,6,1926,1925,1927,1928,43,1919,1921,1929,1925,0,10,10,67,67,1919,1921,1929,1925,43,1925,1929,1930,1927,0,67,67,6,6,1925,1929,1930,1927,43,1916,1926,1931,1932,0,10,67,67,10,1916,1926,1931,1932,43,1932,1931,1933,1934,0,10,67,67,907,1932,1931,1933,1934,43,1926,1928,1935,1931,0,67,6,6,67,1926,1928,1935,1931,43,1931,1935,1936,1933,0,67,6,6,67,1931,1935,1936,1933,43,1916,1932,1937,1917,0,10,10,906,906,1916,1932,1937,1917,43,1917,1937,1938,1922,0,906,906,12,12,1917,1937,1938,1922,43,1932,1934,1939,1937,0,10,907,908,906,1932,1934,1939,1937,43,1937,1939,1940,1938,0,906,908,12,12,1937,1939,1940,1938,43,1941,1942,1943,1944,0,909,910,910,909,1941,1942,1943,1944,43,1944,1943,1945,1946,0,909,910,910,909,1944,1943,1945,1946,43,1942,1928,1927,1943,0,910,911,911,910,1942,1928,1927,1943,43,1943,1927,1930,1945,0,910,911,911,910,1943,1927,1930,1945,43,1941,1944,1947,1948,0,909,909,912,912,1941,1944,1947,1948,43,1948,1947,1949,1950,0,912,912,913,913,1948,1947,1949,1950,43,1944,1946,1951,1947,0,909,909,912,912,1944,1946,1951,1947,43,1947,1951,1952,1949,0,912,912,913,913,1947,1951,1952,1949,43,1941,1948,1953,1954,0,909,912,912,909,1941,1948,1953,1954,43,1954,1953,1955,1956,0,909,912,912,909,1954,1953,1955,1956,43,1948,1950,1957,1953,0,912,913,913,912,1948,1950,1957,1953,43,1953,1957,1958,1955,0,912,913,913,912,1953,1957,1958,1955,43,1941,1954,1959,1942,0,909,909,910,910,1941,1954,1959,1942,43,1942,1959,1935,1928,0,910,910,911,911,1942,1959,1935,1928,43,1954,1956,1960,1959,0,909,909,910,910,1954,1956,1960,1959,43,1959,1960,1936,1935,0,910,910,911,911,1959,1960,1936,1935,43,1961,1962,1963,1964,0,914,914,915,915,1961,1962,1963,1964,43,1964,1963,1965,1966,0,915,915,916,916,1964,1963,1965,1966,43,1962,1967,1968,1963,0,914,914,915,915,1962,1967,1968,1963,43,1963,1968,1969,1965,0,915,915,916,916,1963,1968,1969,1965,43,1961,1964,1970,1971,0,914,915,915,914,1961,1964,1970,1971,43,1971,1970,1972,1973,0,914,915,915,914,1971,1970,1972,1973,43,1964,1966,1974,1970,0,915,916,916,915,1964,1966,1974,1970,43,1970,1974,1975,1972,0,915,916,916,915,1970,1974,1975,1972,43,1961,1971,1976,1977,0,914,914,917,917,1961,1971,1976,1977,43,1977,1976,1957,1950,0,917,917,913,913,1977,1976,1957,1950,43,1971,1973,1978,1976,0,914,914,917,917,1971,1973,1978,1976,43,1976,1978,1958,1957,0,917,917,913,913,1976,1978,1958,1957,43,1961,1977,1979,1962,0,914,917,917,914,1961,1977,1979,1962,43,1962,1979,1980,1967,0,914,917,917,914,1962,1979,1980,1967,43,1977,1950,1949,1979,0,917,913,913,917,1977,1950,1949,1979,43,1979,1949,1952,1980,0,917,913,913,917,1979,1949,1952,1980,43,1981,1982,1983,1984,0,918,918,919,919,1981,1982,1983,1984,43,1984,1983,1898,1897,0,919,919,12,12,1984,1983,1898,1897,43,1982,1985,1986,1983,0,918,918,919,919,1982,1985,1986,1983,43,1983,1986,1899,1898,0,919,919,12,12,1983,1986,1899,1898,43,1981,1984,1987,1988,0,918,919,919,918,1981,1984,1987,1988,43,1988,1987,1989,1990,0,918,919,920,921,1988,1987,1989,1990,43,1984,1897,1913,1987,0,919,12,12,919,1984,1897,1913,1987,43,1987,1913,1915,1989,0,919,12,12,920,1987,1913,1915,1989,43,1981,1988,1991,1992,0,918,918,922,922,1981,1988,1991,1992,43,1992,1991,1974,1966,0,922,922,916,916,1992,1991,1974,1966,43,1988,1990,1993,1991,0,918,921,922,922,1988,1990,1993,1991,43,1991,1993,1975,1974,0,922,922,916,916,1991,1993,1975,1974,43,1981,1992,1994,1982,0,918,922,922,918,1981,1992,1994,1982,43,1982,1994,1995,1985,0,918,922,922,918,1982,1994,1995,1985,43,1992,1966,1965,1994,0,922,916,916,922,1992,1966,1965,1994,43,1994,1965,1969,1995,0,922,916,916,922,1994,1965,1969,1995,43,1996,1997,1998,1999,0,923,924,924,923,1996,1997,1998,1999,43,1999,1998,2000,2001,0,923,924,924,923,1999,1998,2000,2001,43,1997,2002,2003,1998,0,924,925,925,924,1997,2002,2003,1998,43,1998,2003,2004,2000,0,924,925,925,924,1998,2003,2004,2000,43,1996,1999,2005,2006,0,923,923,919,919,1996,1999,2005,2006,43,2006,2005,1923,1922,0,919,919,12,12,2006,2005,1923,1922,43,1999,2001,2007,2005,0,923,923,919,919,1999,2001,2007,2005,43,2005,2007,1924,1923,0,919,919,12,12,2005,2007,1924,1923,43,1996,2006,2008,2009,0,923,919,919,923,1996,2006,2008,2009,43,2009,2008,2010,2011,0,923,919,920,926,2009,2008,2010,2011,43,2006,1922,1938,2008,0,919,12,12,919,2006,1922,1938,2008,43,2008,1938,1940,2010,0,919,12,12,920,2008,1938,1940,2010,43,1996,2009,2012,1997,0,923,923,924,924,1996,2009,2012,1997,43,1997,2012,2013,2002,0,924,924,925,925,1997,2012,2013,2002,43,2009,2011,2014,2012,0,923,926,924,924,2009,2011,2014,2012,43,2012,2014,2015,2013,0,924,924,925,925,2012,2014,2015,2013,43,2016,2017,2018,2019,0,12,927,928,929,2016,2017,2018,2019,43,2019,2018,2020,2021,0,929,928,928,929,2019,2018,2020,2021,43,2017,2022,2023,2018,0,927,930,931,928,2017,2022,2023,2018,43,2018,2023,2024,2020,0,928,931,931,928,2018,2023,2024,2020,43,2016,2019,2025,2026,0,12,929,12,932,2016,2019,2025,2026,43,2026,2025,2027,2028,0,932,12,12,932,2026,2025,2027,2028,43,2019,2021,2029,2025,0,929,929,12,12,2019,2021,2029,2025,43,2025,2029,2030,2027,0,12,12,12,12,2025,2029,2030,2027,43,2016,2026,2031,2032,0,12,932,933,934,2016,2026,2031,2032,43,2032,2031,2033,2034,0,934,933,328,935,2032,2031,2033,2034,43,2026,2028,2035,2031,0,932,932,933,933,2026,2028,2035,2031,43,2031,2035,2036,2033,0,933,933,328,328,2031,2035,2036,2033,43,2016,2032,2037,2017,0,12,934,12,927,2016,2032,2037,2017,43,2017,2037,2038,2022,0,927,12,936,930,2017,2037,2038,2022,43,2032,2034,2039,2037,0,934,935,937,12,2032,2034,2039,2037,43,2037,2039,1940,2038,0,12,937,12,936,2037,2039,1940,2038,43,2040,2041,2042,2043,0,938,938,924,924,2040,2041,2042,2043,43,2043,2042,2044,2045,0,924,924,925,925,2043,2042,2044,2045,43,2041,2046,2047,2042,0,938,938,924,924,2041,2046,2047,2042,43,2042,2047,2048,2044,0,924,924,925,925,2042,2047,2048,2044,43,2040,2043,2049,2050,0,938,924,924,938,2040,2043,2049,2050,43,2050,2049,2051,2052,0,938,924,924,938,2050,2049,2051,2052,43,2043,2045,2053,2049,0,924,925,925,924,2043,2045,2053,2049,43,2049,2053,2054,2051,0,924,925,925,924,2049,2053,2054,2051,43,2040,2050,2055,2056,0,938,938,939,939,2040,2050,2055,2056,43,2056,2055,2057,2058,0,939,939,328,328,2056,2055,2057,2058,43,2050,2052,2059,2055,0,938,938,939,939,2050,2052,2059,2055,43,2055,2059,2060,2057,0,939,939,328,328,2055,2059,2060,2057,43,2040,2056,2061,2041,0,938,939,939,938,2040,2056,2061,2041,43,2041,2061,2062,2046,0,938,939,939,938,2041,2061,2062,2046,43,2056,2058,2063,2061,0,939,328,328,939,2056,2058,2063,2061,43,2061,2063,2064,2062,0,939,328,328,939,2061,2063,2064,2062,43,2065,2066,2067,2068,0,940,941,924,924,2065,2066,2067,2068,43,2068,2067,2069,2070,0,924,924,925,925,2068,2067,2069,2070,43,2066,1909,1908,2067,0,941,923,924,924,2066,1909,1908,2067,43,2067,1908,1911,2069,0,924,924,925,925,2067,1908,1911,2069,43,2065,2068,2071,2072,0,940,924,924,938,2065,2068,2071,2072,43,2072,2071,2047,2046,0,938,924,924,938,2072,2071,2047,2046,43,2068,2070,2073,2071,0,924,925,925,924,2068,2070,2073,2071,43,2071,2073,2048,2047,0,924,925,925,924,2071,2073,2048,2047,43,2065,2072,2074,2075,0,940,938,939,942,2065,2072,2074,2075,43,2075,2074,2076,2077,0,942,939,328,943,2075,2074,2076,2077,43,2072,2046,2062,2074,0,938,938,939,939,2072,2046,2062,2074,43,2074,2062,2064,2076,0,939,939,328,328,2074,2062,2064,2076,43,2065,2075,2078,2066,0,940,942,944,941,2065,2075,2078,2066,43,2066,2078,1914,1909,0,941,944,919,923,2066,2078,1914,1909,43,2075,2077,2079,2078,0,942,943,945,944,2075,2077,2079,2078,43,2078,2079,1915,1914,0,944,945,12,919,2078,2079,1915,1914,43,2080,2081,2082,2083,0,938,938,924,924,2080,2081,2082,2083,43,2083,2082,2084,2085,0,924,924,925,925,2083,2082,2084,2085,43,2081,2052,2051,2082,0,938,938,924,924,2081,2052,2051,2082,43,2082,2051,2054,2084,0,924,924,925,925,2082,2051,2054,2084,43,2080,2083,2086,2087,0,938,924,924,938,2080,2083,2086,2087,43,2087,2086,2088,2089,0,938,924,924,938,2087,2086,2088,2089,43,2083,2085,2090,2086,0,924,925,925,924,2083,2085,2090,2086,43,2086,2090,2091,2088,0,924,925,925,924,2086,2090,2091,2088,43,2080,2087,2092,2093,0,938,938,939,939,2080,2087,2092,2093,43,2093,2092,2094,2095,0,939,939,328,328,2093,2092,2094,2095,43,2087,2089,2096,2092,0,938,938,939,939,2087,2089,2096,2092,43,2092,2096,2036,2094,0,939,939,328,328,2092,2096,2036,2094,43,2080,2093,2097,2081,0,938,939,939,938,2080,2093,2097,2081,43,2081,2097,2059,2052,0,938,939,939,938,2081,2097,2059,2052,43,2093,2095,2098,2097,0,939,328,328,939,2093,2095,2098,2097,43,2097,2098,2060,2059,0,939,328,328,939,2097,2098,2060,2059,43,2099,2100,2101,2102,0,946,67,67,947,2099,2100,2101,2102,43,2102,2101,2103,2104,0,947,67,67,947,2102,2101,2103,2104,43,2100,2105,2106,2101,0,67,6,6,67,2100,2105,2106,2101,43,2101,2106,2107,2103,0,67,6,6,67,2101,2106,2107,2103,43,2099,2102,2108,2109,0,946,947,948,949,2099,2102,2108,2109,43,2109,2108,2023,2022,0,949,948,931,930,2109,2108,2023,2022,43,2102,2104,2110,2108,0,947,947,948,948,2102,2104,2110,2108,43,2108,2110,2024,2023,0,948,948,931,931,2108,2110,2024,2023,43,2099,2109,2111,2112,0,946,949,950,951,2099,2109,2111,2112,43,2112,2111,1939,1934,0,951,950,908,907,2112,2111,1939,1934,43,2109,2022,2038,2111,0,949,930,936,950,2109,2022,2038,2111,43,2111,2038,1940,1939,0,950,936,12,908,2111,2038,1940,1939,43,2099,2112,2113,2100,0,946,951,67,67,2099,2112,2113,2100,43,2100,2113,2114,2105,0,67,67,6,6,2100,2113,2114,2105,43,2112,1934,1933,2113,0,951,907,67,67,2112,1934,1933,2113,43,2113,1933,1936,2114,0,67,67,6,6,2113,1933,1936,2114,43,2115,2116,2117,2118,0,909,912,912,909,2115,2116,2117,2118,43,2118,2117,2119,2120,0,909,912,912,909,2118,2117,2119,2120,43,2116,2121,2122,2117,0,912,913,913,912,2116,2121,2122,2117,43,2117,2122,2123,2119,0,912,913,913,912,2117,2122,2123,2119,43,2115,2118,2124,2125,0,909,909,910,910,2115,2118,2124,2125,43,2125,2124,2106,2105,0,910,910,911,911,2125,2124,2106,2105,43,2118,2120,2126,2124,0,909,909,910,910,2118,2120,2126,2124,43,2124,2126,2107,2106,0,910,910,911,911,2124,2126,2107,2106,43,2115,2125,2127,2128,0,909,910,910,909,2115,2125,2127,2128,43,2128,2127,1960,1956,0,909,910,910,909,2128,2127,1960,1956,43,2125,2105,2114,2127,0,910,911,911,910,2125,2105,2114,2127,43,2127,2114,1936,1960,0,910,911,911,910,2127,2114,1936,1960,43,2115,2128,2129,2116,0,909,909,912,912,2115,2128,2129,2116,43,2116,2129,2130,2121,0,912,912,913,913,2116,2129,2130,2121,43,2128,1956,1955,2129,0,909,909,912,912,2128,1956,1955,2129,43,2129,1955,1958,2130,0,912,912,913,913,2129,1955,1958,2130,43,2131,2132,2133,2134,0,914,914,917,917,2131,2132,2133,2134,43,2134,2133,2122,2121,0,917,917,913,913,2134,2133,2122,2121,43,2132,2135,2136,2133,0,914,914,917,917,2132,2135,2136,2133,43,2133,2136,2123,2122,0,917,917,913,913,2133,2136,2123,2122,43,2131,2134,2137,2138,0,914,917,917,914,2131,2134,2137,2138,43,2138,2137,1978,1973,0,914,917,917,914,2138,2137,1978,1973,43,2134,2121,2130,2137,0,917,913,913,917,2134,2121,2130,2137,43,2137,2130,1958,1978,0,917,913,913,917,2137,2130,1958,1978,43,2131,2138,2139,2140,0,914,914,915,915,2131,2138,2139,2140,43,2140,2139,2141,2142,0,915,915,916,916,2140,2139,2141,2142,43,2138,1973,1972,2139,0,914,914,915,915,2138,1973,1972,2139,43,2139,1972,1975,2141,0,915,915,916,916,2139,1972,1975,2141,43,2131,2140,2143,2132,0,914,915,915,914,2131,2140,2143,2132,43,2132,2143,2144,2135,0,914,915,915,914,2132,2143,2144,2135,43,2140,2142,2145,2143,0,915,916,916,915,2140,2142,2145,2143,43,2143,2145,2146,2144,0,915,916,916,915,2143,2145,2146,2144,43,2147,2148,2149,2150,0,952,953,922,922,2147,2148,2149,2150,43,2150,2149,2145,2142,0,922,922,916,916,2150,2149,2145,2142,43,2148,2151,2152,2149,0,953,953,922,922,2148,2151,2152,2149,43,2149,2152,2146,2145,0,922,922,916,916,2149,2152,2146,2145,43,2147,2150,2153,2154,0,952,922,922,954,2147,2150,2153,2154,43,2154,2153,1993,1990,0,954,922,922,921,2154,2153,1993,1990,43,2150,2142,2141,2153,0,922,916,916,922,2150,2142,2141,2153,43,2153,2141,1975,1993,0,922,916,916,922,2153,2141,1975,1993,43,2147,2154,2155,2156,0,952,954,944,942,2147,2154,2155,2156,43,2156,2155,2157,2158,0,942,944,945,943,2156,2155,2157,2158,43,2154,1990,1989,2155,0,954,921,920,944,2154,1990,1989,2155,43,2155,1989,1915,2157,0,944,920,12,945,2155,1989,1915,2157,43,2147,2156,2159,2148,0,952,942,939,953,2147,2156,2159,2148,43,2148,2159,2160,2151,0,953,939,939,953,2148,2159,2160,2151,43,2156,2158,2161,2159,0,942,943,328,939,2156,2158,2161,2159,43,2159,2161,2162,2160,0,939,328,328,939,2159,2161,2162,2160,43,2163,2164,2165,2166,0,932,12,12,932,2163,2164,2165,2166,43,2166,2165,2167,2168,0,932,12,12,932,2166,2165,2167,2168,43,2164,2169,2170,2165,0,12,12,12,12,2164,2169,2170,2165,43,2165,2170,2171,2167,0,12,12,12,12,2165,2170,2171,2167,43,2163,2166,2172,2173,0,932,932,933,933,2163,2166,2172,2173,43,2173,2172,2063,2058,0,933,933,328,328,2173,2172,2063,2058,43,2166,2168,2174,2172,0,932,932,933,933,2166,2168,2174,2172,43,2172,2174,2064,2063,0,933,933,328,328,2172,2174,2064,2063,43,2163,2173,2175,2176,0,932,933,933,932,2163,2173,2175,2176,43,2176,2175,2177,2178,0,932,933,933,932,2176,2175,2177,2178,43,2173,2058,2057,2175,0,933,328,328,933,2173,2058,2057,2175,43,2175,2057,2060,2177,0,933,328,328,933,2175,2057,2060,2177,43,2163,2176,2179,2164,0,932,932,12,12,2163,2176,2179,2164,43,2164,2179,2180,2169,0,12,12,12,12,2164,2179,2180,2169,43,2176,2178,2181,2179,0,932,932,12,12,2176,2178,2181,2179,43,2179,2181,2182,2180,0,12,12,12,12,2179,2181,2182,2180,43,2183,2184,2185,2186,0,955,932,933,933,2183,2184,2185,2186,43,2186,2185,2161,2158,0,933,933,328,943,2186,2185,2161,2158,43,2184,2187,2188,2185,0,932,932,933,933,2184,2187,2188,2185,43,2185,2188,2162,2161,0,933,933,328,328,2185,2188,2162,2161,43,2183,2186,2189,2190,0,955,933,933,933,2183,2186,2189,2190,43,2190,2189,2079,2077,0,933,933,945,943,2190,2189,2079,2077,43,2186,2158,2157,2189,0,933,943,945,933,2186,2158,2157,2189,43,2189,2157,1915,2079,0,933,945,12,945,2189,2157,1915,2079,43,2183,2190,2191,2192,0,955,933,933,932,2183,2190,2191,2192,43,2192,2191,2174,2168,0,932,933,933,932,2192,2191,2174,2168,43,2190,2077,2076,2191,0,933,943,328,933,2190,2077,2076,2191,43,2191,2076,2064,2174,0,933,328,328,933,2191,2076,2064,2174,43,2183,2192,2193,2184,0,955,932,12,932,2183,2192,2193,2184,43,2184,2193,2194,2187,0,932,12,12,932,2184,2193,2194,2187,43,2192,2168,2167,2193,0,932,932,12,12,2192,2168,2167,2193,43,2193,2167,2171,2194,0,12,12,12,12,2193,2167,2171,2194,43,2195,2196,2197,2198,0,932,932,12,12,2195,2196,2197,2198,43,2198,2197,2199,2200,0,12,12,12,12,2198,2197,2199,2200,43,2196,2028,2027,2197,0,932,932,12,12,2196,2028,2027,2197,43,2197,2027,2030,2199,0,12,12,12,12,2197,2027,2030,2199,43,2195,2198,2201,2202,0,932,12,12,932,2195,2198,2201,2202,43,2202,2201,2181,2178,0,932,12,12,932,2202,2201,2181,2178,43,2198,2200,2203,2201,0,12,12,12,12,2198,2200,2203,2201,43,2201,2203,2182,2181,0,12,12,12,12,2201,2203,2182,2181,43,2195,2202,2204,2205,0,932,932,933,933,2195,2202,2204,2205,43,2205,2204,2098,2095,0,933,933,328,328,2205,2204,2098,2095,43,2202,2178,2177,2204,0,932,932,933,933,2202,2178,2177,2204,43,2204,2177,2060,2098,0,933,933,328,328,2204,2177,2060,2098,43,2195,2205,2206,2196,0,932,933,933,932,2195,2205,2206,2196,43,2196,2206,2035,2028,0,932,933,933,932,2196,2206,2035,2028,43,2205,2095,2094,2206,0,933,328,328,933,2205,2095,2094,2206,43,2206,2094,2036,2035,0,933,328,328,933,2206,2094,2036,2035,43,2207,2208,2209,2210,0,956,957,958,959,2207,2208,2209,2210,43,2210,2209,1888,1883,0,959,958,960,961,2210,2209,1888,1883,43,2208,626,625,2209,0,957,372,371,958,2208,626,625,2209,43,2209,625,628,1888,0,958,371,374,960,2209,625,628,1888,43,2207,2210,2211,2212,0,956,959,962,963,2207,2210,2211,2212,43,2212,2211,1862,1857,0,963,962,871,866,2212,2211,1862,1857,43,2210,1883,1882,2211,0,959,961,964,962,2210,1883,1882,2211,43,2211,1882,1435,1862,0,962,964,872,871,2211,1882,1435,1862,43,2207,2212,2213,2214,0,956,963,965,966,2207,2212,2213,2214,43,2214,2213,583,575,0,966,965,324,316,2214,2213,583,575,43,2212,1857,1856,2213,0,963,866,865,965,2212,1857,1856,2213,43,2213,1856,584,583,0,965,865,325,324,2213,1856,584,583,43,2207,2214,2215,2208,0,956,966,967,957,2207,2214,2215,2208,43,2208,2215,633,626,0,957,967,379,372,2208,2215,633,626,43,2214,575,574,2215,0,966,316,315,967,2214,575,574,2215,43,2215,574,578,633,0,967,315,319,379,2215,574,578,633,43,2216,2217,2218,2219,0,968,969,970,971,2216,2217,2218,2219,43,2219,2218,2220,2221,0,971,970,972,973,2219,2218,2220,2221,43,2217,2222,2223,2218,0,969,974,975,970,2217,2222,2223,2218,43,2218,2223,2224,2220,0,970,975,976,972,2218,2223,2224,2220,43,2216,2219,2225,2226,0,968,971,977,978,2216,2219,2225,2226,43,2226,2225,2227,2228,0,978,977,979,980,2226,2225,2227,2228,43,2219,2221,2229,2225,0,971,973,981,977,2219,2221,2229,2225,43,2225,2229,2230,2227,0,977,981,982,979,2225,2229,2230,2227,43,2216,2226,2231,2232,0,968,978,983,984,2216,2226,2231,2232,43,2232,2231,2233,2234,0,984,983,985,986,2232,2231,2233,2234,43,2226,2228,2235,2231,0,978,980,987,983,2226,2228,2235,2231,43,2231,2235,2236,2233,0,983,987,988,985,2231,2235,2236,2233,43,2216,2232,2237,2217,0,968,984,989,969,2216,2232,2237,2217,43,2217,2237,2238,2222,0,969,989,990,974,2217,2237,2238,2222,43,2232,2234,2239,2237,0,984,986,991,989,2232,2234,2239,2237,43,2237,2239,2240,2238,0,989,991,992,990,2237,2239,2240,2238,43,2241,2242,2243,2244,0,993,994,995,996,2241,2242,2243,2244,43,2244,2243,2239,2234,0,996,995,991,986,2244,2243,2239,2234,43,2242,2245,2246,2243,0,994,997,998,995,2242,2245,2246,2243,43,2243,2246,2240,2239,0,995,998,992,991,2243,2246,2240,2239,43,2241,2244,2247,2248,0,993,996,999,1000,2241,2244,2247,2248,43,2248,2247,2249,2250,0,1000,999,1001,1002,2248,2247,2249,2250,43,2244,2234,2233,2247,0,996,986,985,999,2244,2234,2233,2247,43,2247,2233,2236,2249,0,999,985,988,1001,2247,2233,2236,2249,43,2241,2248,2251,2252,0,993,1000,1003,1003,2241,2248,2251,2252,43,2252,2251,2253,2254,0,1003,1003,1004,1004,2252,2251,2253,2254,43,2248,2250,2255,2251,0,1000,1002,1003,1003,2248,2250,2255,2251,43,2251,2255,2256,2253,0,1003,1003,1004,1004,2251,2255,2256,2253,43,2241,2252,2257,2242,0,993,1003,1003,994,2241,2252,2257,2242,43,2242,2257,2258,2245,0,994,1003,1003,997,2242,2257,2258,2245,43,2252,2254,2259,2257,0,1003,1004,1004,1003,2252,2254,2259,2257,43,2257,2259,2260,2258,0,1003,1004,1004,1003,2257,2259,2260,2258,43,2261,2262,2263,2264,0,1005,1005,1006,1006,2261,2262,2263,2264,43,2264,2263,2265,2266,0,1006,1006,1007,1007,2264,2263,2265,2266,43,2262,2267,2268,2263,0,1005,1005,1006,1006,2262,2267,2268,2263,43,2263,2268,2269,2265,0,1006,1006,1007,1007,2263,2268,2269,2265,43,2261,2264,2270,2271,0,1005,1006,1006,1005,2261,2264,2270,2271,43,2271,2270,2272,2273,0,1005,1006,1006,1005,2271,2270,2272,2273,43,2264,2266,2274,2270,0,1006,1007,1007,1006,2264,2266,2274,2270,43,2270,2274,2275,2272,0,1006,1007,1007,1006,2270,2274,2275,2272,43,2261,2271,2276,2277,0,1005,1005,1008,1008,2261,2271,2276,2277,43,2277,2276,2278,2279,0,1008,1008,1009,1009,2277,2276,2278,2279,43,2271,2273,2280,2276,0,1005,1005,1008,1008,2271,2273,2280,2276,43,2276,2280,2281,2278,0,1008,1008,1010,1009,2276,2280,2281,2278,43,2261,2277,2282,2262,0,1005,1008,1008,1005,2261,2277,2282,2262,43,2262,2282,2283,2267,0,1005,1008,1008,1005,2262,2282,2283,2267,43,2277,2279,2284,2282,0,1008,1009,1009,1008,2277,2279,2284,2282,43,2282,2284,2285,2283,0,1008,1009,1010,1008,2282,2284,2285,2283,43,2286,2287,2288,2289,0,1005,1005,1006,1006,2286,2287,2288,2289,43,2289,2288,2290,2291,0,1006,1006,1007,1007,2289,2288,2290,2291,43,2287,2292,2293,2288,0,1005,1005,1006,1006,2287,2292,2293,2288,43,2288,2293,2294,2290,0,1006,1006,1007,1007,2288,2293,2294,2290,43,2286,2289,2295,2296,0,1005,1006,1006,1005,2286,2289,2295,2296,43,2296,2295,2297,2298,0,1005,1006,1006,1005,2296,2295,2297,2298,43,2289,2291,2299,2295,0,1006,1007,1007,1006,2289,2291,2299,2295,43,2295,2299,2300,2297,0,1006,1007,1007,1006,2295,2299,2300,2297,43,2286,2296,2301,2302,0,1005,1005,1008,1008,2286,2296,2301,2302,43,2302,2301,2303,2304,0,1008,1008,1009,1009,2302,2301,2303,2304,43,2296,2298,2305,2301,0,1005,1005,1008,1008,2296,2298,2305,2301,43,2301,2305,2306,2303,0,1008,1008,1010,1009,2301,2305,2306,2303,43,2286,2302,2307,2287,0,1005,1008,1008,1005,2286,2302,2307,2287,43,2287,2307,2308,2292,0,1005,1008,1008,1005,2287,2307,2308,2292,43,2302,2304,2309,2307,0,1008,1009,1009,1008,2302,2304,2309,2307,43,2307,2309,2310,2308,0,1008,1009,1009,1008,2307,2309,2310,2308,43,2311,2312,2313,2314,0,1011,1012,1013,1014,2311,2312,2313,2314,43,2314,2313,2315,2316,0,1014,1013,1015,1016,2314,2313,2315,2316,43,2312,397,396,2313,0,1012,142,141,1013,2312,397,396,2313,43,2313,396,400,2315,0,1013,141,145,1015,2313,396,400,2315,43,2311,2314,2317,2318,0,1011,1014,1008,1005,2311,2314,2317,2318,43,2318,2317,2319,2320,0,1005,1008,1008,1005,2318,2317,2319,2320,43,2314,2316,2321,2317,0,1014,1016,1009,1008,2314,2316,2321,2317,43,2317,2321,2322,2319,0,1008,1009,1010,1008,2317,2321,2322,2319,43,2311,2318,2323,2324,0,1011,1005,1006,1017,2311,2318,2323,2324,43,2324,2323,2325,2326,0,1017,1006,1007,1018,2324,2323,2325,2326,43,2318,2320,2327,2323,0,1005,1005,1006,1006,2318,2320,2327,2323,43,2323,2327,2328,2325,0,1006,1006,1007,1007,2323,2327,2328,2325,43,2311,2324,2329,2312,0,1011,1017,1019,1012,2311,2324,2329,2312,43,2312,2329,405,397,0,1012,1019,150,142,2312,2329,405,397,43,2324,2326,2330,2329,0,1017,1018,1020,1019,2324,2326,2330,2329,43,2329,2330,406,405,0,1019,1020,151,150,2329,2330,406,405,43,2331,2332,2333,2334,0,1021,1022,1022,1023,2331,2332,2333,2334,43,2334,2333,2335,2336,0,1023,1022,1022,1024,2334,2333,2335,2336,43,2332,2279,2278,2333,0,1022,1009,1009,1022,2332,2279,2278,2333,43,2333,2278,2281,2335,0,1022,1009,1010,1022,2333,2278,2281,2335,43,2331,2334,2337,2338,0,1021,1023,1025,1026,2331,2334,2337,2338,43,2338,2337,2339,2340,0,1026,1025,1027,1028,2338,2337,2339,2340,43,2334,2336,2341,2337,0,1023,1024,1029,1025,2334,2336,2341,2337,43,2337,2341,2342,2339,0,1025,1029,1030,1027,2337,2341,2342,2339,43,2331,2338,2343,2344,0,1021,1026,1031,1032,2331,2338,2343,2344,43,2344,2343,2345,2346,0,1032,1031,1033,1034,2344,2343,2345,2346,43,2338,2340,2347,2343,0,1026,1028,1035,1031,2338,2340,2347,2343,43,2343,2347,2348,2345,0,1031,1035,1036,1033,2343,2347,2348,2345,43,2331,2344,2349,2332,0,1021,1032,1022,1022,2331,2344,2349,2332,43,2332,2349,2284,2279,0,1022,1022,1009,1009,2332,2349,2284,2279,43,2344,2346,2350,2349,0,1032,1034,1022,1022,2344,2346,2350,2349,43,2349,2350,2285,2284,0,1022,1022,1010,1009,2349,2350,2285,2284,43,2351,2352,2353,2354,0,1037,1038,1022,1022,2351,2352,2353,2354,43,2354,2353,2309,2304,0,1022,1022,1009,1009,2354,2353,2309,2304,43,2352,2355,2356,2353,0,1038,1039,1022,1022,2352,2355,2356,2353,43,2353,2356,2310,2309,0,1022,1022,1009,1009,2353,2356,2310,2309,43,2351,2354,2357,2358,0,1037,1022,1022,1040,2351,2354,2357,2358,43,2358,2357,2359,2360,0,1040,1022,1022,1041,2358,2357,2359,2360,43,2354,2304,2303,2357,0,1022,1009,1009,1022,2354,2304,2303,2357,43,2357,2303,2306,2359,0,1022,1009,1010,1022,2357,2303,2306,2359,43,2351,2358,2361,2362,0,1037,1040,1042,1043,2351,2358,2361,2362,43,2362,2361,2363,2364,0,1043,1042,1044,1045,2362,2361,2363,2364,43,2358,2360,2365,2361,0,1040,1041,1046,1042,2358,2360,2365,2361,43,2361,2365,2366,2363,0,1042,1046,1047,1044,2361,2365,2366,2363,43,2351,2362,2367,2352,0,1037,1043,1048,1038,2351,2362,2367,2352,43,2352,2367,2368,2355,0,1038,1048,1049,1039,2352,2367,2368,2355,43,2362,2364,2369,2367,0,1043,1045,1050,1048,2362,2364,2369,2367,43,2367,2369,2370,2368,0,1048,1050,1051,1049,2367,2369,2370,2368,43,2371,2372,2373,2374,0,1052,1053,1054,1055,2371,2372,2373,2374,43,2374,2373,2375,2376,0,1055,1054,1056,1057,2374,2373,2375,2376,43,2372,1354,1353,2373,0,1053,610,609,1054,2372,1354,1353,2373,43,2373,1353,1253,2375,0,1054,609,508,1056,2373,1353,1253,2375,43,2371,2374,2377,2378,0,1052,1055,1058,1059,2371,2374,2377,2378,43,2378,2377,2379,2380,0,1059,1058,1058,1059,2378,2377,2379,2380,43,2374,2376,2381,2377,0,1055,1057,1060,1058,2374,2376,2381,2377,43,2377,2381,2382,2379,0,1058,1060,1060,1058,2377,2381,2382,2379,43,2371,2378,2383,2384,0,1052,1059,1022,1061,2371,2378,2383,2384,43,2384,2383,2321,2316,0,1061,1022,1009,1016,2384,2383,2321,2316,43,2378,2380,2385,2383,0,1059,1059,1022,1022,2378,2380,2385,2383,43,2383,2385,2322,2321,0,1022,1022,1010,1009,2383,2385,2322,2321,43,2371,2384,2386,2372,0,1052,1061,1062,1053,2371,2384,2386,2372,43,2372,2386,1357,1354,0,1053,1062,613,610,2372,2386,1357,1354,43,2384,2316,2315,2386,0,1061,1016,1015,1062,2384,2316,2315,2386,43,2386,2315,400,1357,0,1062,1015,145,613,2386,2315,400,1357,43,2387,2388,2389,2390,0,1063,1064,1065,1066,2387,2388,2389,2390,43,2390,2389,2391,2392,0,1066,1065,1067,1068,2390,2389,2391,2392,43,2388,2393,2394,2389,0,1064,1069,1070,1065,2388,2393,2394,2389,43,2389,2394,2348,2391,0,1065,1070,1036,1067,2389,2394,2348,2391,43,2387,2390,2395,2396,0,1063,1066,1071,1072,2387,2390,2395,2396,43,2396,2395,2397,2398,0,1072,1071,1073,1074,2396,2395,2397,2398,43,2390,2392,2399,2395,0,1066,1068,1075,1071,2390,2392,2399,2395,43,2395,2399,2400,2397,0,1071,1075,1076,1073,2395,2399,2400,2397,43,2387,2396,2401,2402,0,1063,1072,1077,1078,2387,2396,2401,2402,43,2402,2401,2403,2404,0,1078,1077,1079,1080,2402,2401,2403,2404,43,2396,2398,2405,2401,0,1072,1074,1081,1077,2396,2398,2405,2401,43,2401,2405,2406,2403,0,1077,1081,1082,1079,2401,2405,2406,2403,43,2387,2402,2407,2388,0,1063,1078,1083,1064,2387,2402,2407,2388,43,2388,2407,2408,2393,0,1064,1083,1084,1069,2388,2407,2408,2393,43,2402,2404,2409,2407,0,1078,1080,1085,1083,2402,2404,2409,2407,43,2407,2409,1316,2408,0,1083,1085,572,1084,2407,2409,1316,2408,43,2410,2411,2412,2413,0,1086,1087,1088,1089,2410,2411,2412,2413,43,2413,2412,2414,2415,0,1089,1088,1090,1091,2413,2412,2414,2415,43,2411,2364,2363,2412,0,1087,1045,1044,1088,2411,2364,2363,2412,43,2412,2363,2366,2414,0,1088,1044,1047,1090,2412,2363,2366,2414,43,2410,2413,2416,2417,0,1086,1089,1092,1093,2410,2413,2416,2417,43,2417,2416,2418,2419,0,1093,1092,1094,1095,2417,2416,2418,2419,43,2413,2415,2420,2416,0,1089,1091,1096,1092,2413,2415,2420,2416,43,2416,2420,2421,2418,0,1092,1096,1097,1094,2416,2420,2421,2418,43,2410,2417,2422,2423,0,1086,1093,1098,1099,2410,2417,2422,2423,43,2423,2422,2424,2425,0,1099,1098,1100,1101,2423,2422,2424,2425,43,2417,2419,2426,2422,0,1093,1095,1102,1098,2417,2419,2426,2422,43,2422,2426,2427,2424,0,1098,1102,1103,1100,2422,2426,2427,2424,43,2410,2423,2428,2411,0,1086,1099,1104,1087,2410,2423,2428,2411,43,2411,2428,2369,2364,0,1087,1104,1050,1045,2411,2428,2369,2364,43,2423,2425,2429,2428,0,1099,1101,1105,1104,2423,2425,2429,2428,43,2428,2429,2370,2369,0,1104,1105,1051,1050,2428,2429,2370,2369,43,2430,2431,2432,2433,0,1106,1107,1108,1109,2430,2431,2432,2433,43,2433,2432,2434,2435,0,1109,1108,1110,1111,2433,2432,2434,2435,43,2431,1251,1266,2432,0,1107,506,522,1108,2431,1251,1266,2432,43,2432,1266,1268,2434,0,1108,522,524,1110,2432,1266,1268,2434,43,2430,2433,2436,2437,0,1106,1109,1112,1113,2430,2433,2436,2437,43,2437,2436,2438,2439,0,1113,1112,1112,1113,2437,2436,2438,2439,43,2433,2435,2440,2436,0,1109,1111,1114,1112,2433,2435,2440,2436,43,2436,2440,2441,2438,0,1112,1114,1114,1112,2436,2440,2441,2438,43,2430,2437,2442,2443,0,1106,1113,1115,1116,2430,2437,2442,2443,43,2443,2442,2381,2376,0,1116,1115,1060,1057,2443,2442,2381,2376,43,2437,2439,2444,2442,0,1113,1113,1115,1115,2437,2439,2444,2442,43,2442,2444,2382,2381,0,1115,1115,1060,1060,2442,2444,2382,2381,43,2430,2443,2445,2431,0,1106,1116,1117,1107,2430,2443,2445,2431,43,2431,2445,1252,1251,0,1107,1117,507,506,2431,2445,1252,1251,43,2443,2376,2375,2445,0,1116,1057,1056,1117,2443,2376,2375,2445,43,2445,2375,1253,1252,0,1117,1056,508,507,2445,2375,1253,1252,43,2446,2447,2448,2449,0,1118,1119,1120,1121,2446,2447,2448,2449,43,2449,2448,2450,2451,0,1121,1120,1120,1121,2449,2448,2450,2451,43,2447,2452,2453,2448,0,1119,1122,1123,1120,2447,2452,2453,2448,43,2448,2453,2454,2450,0,1120,1123,1123,1120,2448,2453,2454,2450,43,2446,2449,2455,2456,0,1118,1121,1124,1125,2446,2449,2455,2456,43,2456,2455,2440,2435,0,1125,1124,1114,1111,2456,2455,2440,2435,43,2449,2451,2457,2455,0,1121,1121,1124,1124,2449,2451,2457,2455,43,2455,2457,2441,2440,0,1124,1124,1114,1114,2455,2457,2441,2440,43,2446,2456,2458,2459,0,1118,1125,1126,1127,2446,2456,2458,2459,43,2459,2458,1286,1281,0,1127,1126,542,537,2459,2458,1286,1281,43,2456,2435,2434,2458,0,1125,1111,1110,1126,2456,2435,2434,2458,43,2458,2434,1268,1286,0,1126,1110,524,542,2458,2434,1268,1286,43,2446,2459,2460,2447,0,1118,1127,1128,1119,2446,2459,2460,2447,43,2447,2460,2461,2452,0,1119,1128,1129,1122,2447,2460,2461,2452,43,2459,1281,1280,2460,0,1127,537,536,1128,2459,1281,1280,2460,43,2460,1280,1283,2461,0,1128,536,539,1129,2460,1280,1283,2461,43,2462,2463,2464,2465,0,1130,1131,1132,1133,2462,2463,2464,2465,43,2465,2464,2466,2467,0,1133,1132,1132,1133,2465,2464,2466,2467,43,2463,2468,2469,2464,0,1131,1134,1135,1132,2463,2468,2469,2464,43,2464,2469,2470,2466,0,1132,1135,1135,1132,2464,2469,2470,2466,43,2462,2465,2471,2472,0,1130,1133,1136,1137,2462,2465,2471,2472,43,2472,2471,2453,2452,0,1137,1136,1123,1122,2472,2471,2453,2452,43,2465,2467,2473,2471,0,1133,1133,1136,1136,2465,2467,2473,2471,43,2471,2473,2454,2453,0,1136,1136,1123,1123,2471,2473,2454,2453,43,2462,2472,2474,2475,0,1130,1137,1138,1139,2462,2472,2474,2475,43,2475,2474,1305,1300,0,1139,1138,561,556,2475,2474,1305,1300,43,2472,2452,2461,2474,0,1137,1122,1129,1138,2472,2452,2461,2474,43,2474,2461,1283,1305,0,1138,1129,539,561,2474,2461,1283,1305,43,2462,2475,2476,2463,0,1130,1139,1140,1131,2462,2475,2476,2463,43,2463,2476,2477,2468,0,1131,1140,1141,1134,2463,2476,2477,2468,43,2475,1300,1299,2476,0,1139,556,555,1140,2475,1300,1299,2476,43,2476,1299,1302,2477,0,1140,555,558,1141,2476,1299,1302,2477,43,2478,2479,2480,2481,0,1142,1143,1003,1144,2478,2479,2480,2481,43,2481,2480,2482,2483,0,1144,1003,1003,1144,2481,2480,2482,2483,43,2479,2484,2485,2480,0,1143,1145,1004,1003,2479,2484,2485,2480,43,2480,2485,2486,2482,0,1003,1004,1004,1003,2480,2485,2486,2482,43,2478,2481,2487,2488,0,1142,1144,1146,1147,2478,2481,2487,2488,43,2488,2487,2469,2468,0,1147,1146,1135,1134,2488,2487,2469,2468,43,2481,2483,2489,2487,0,1144,1144,1146,1146,2481,2483,2489,2487,43,2487,2489,2470,2469,0,1146,1146,1135,1135,2487,2489,2470,2469,43,2478,2488,2490,2491,0,1142,1147,1148,1149,2478,2488,2490,2491,43,2491,2490,1480,1479,0,1149,1148,742,741,2491,2490,1480,1479,43,2488,2468,2477,2490,0,1147,1134,1141,1148,2488,2468,2477,2490,43,2490,2477,1302,1480,0,1148,1141,558,742,2490,2477,1302,1480,43,2478,2491,2492,2479,0,1142,1149,1150,1143,2478,2491,2492,2479,43,2479,2492,2493,2484,0,1143,1150,1151,1145,2479,2492,2493,2484,43,2491,1479,1488,2492,0,1149,741,751,1150,2491,1479,1488,2492,43,2492,1488,1490,2493,0,1150,751,753,1151,2492,1488,1490,2493,43,2494,2495,2496,2497,0,1005,1005,1008,1008,2494,2495,2496,2497,43,2497,2496,2498,2499,0,1008,1008,1009,1009,2497,2496,2498,2499,43,2495,2320,2319,2496,0,1005,1005,1008,1008,2495,2320,2319,2496,43,2496,2319,2322,2498,0,1008,1008,1010,1009,2496,2319,2322,2498,43,2494,2497,2500,2501,0,1005,1008,1008,1005,2494,2497,2500,2501,43,2501,2500,2305,2298,0,1005,1008,1008,1005,2501,2500,2305,2298,43,2497,2499,2502,2500,0,1008,1009,1009,1008,2497,2499,2502,2500,43,2500,2502,2306,2305,0,1008,1009,1010,1008,2500,2502,2306,2305,43,2494,2501,2503,2504,0,1005,1005,1006,1006,2494,2501,2503,2504,43,2504,2503,2505,2506,0,1006,1006,1007,1007,2504,2503,2505,2506,43,2501,2298,2297,2503,0,1005,1005,1006,1006,2501,2298,2297,2503,43,2503,2297,2300,2505,0,1006,1006,1007,1007,2503,2297,2300,2505,43,2494,2504,2507,2495,0,1005,1006,1006,1005,2494,2504,2507,2495,43,2495,2507,2327,2320,0,1005,1006,1006,1005,2495,2507,2327,2320,43,2504,2506,2508,2507,0,1006,1007,1007,1006,2504,2506,2508,2507,43,2507,2508,2328,2327,0,1006,1007,1007,1006,2507,2508,2328,2327,43,2509,2510,2511,2512,0,1152,1059,1058,1153,2509,2510,2511,2512,43,2512,2511,2513,2514,0,1153,1058,1060,1154,2512,2511,2513,2514,43,2510,2380,2379,2511,0,1059,1059,1058,1058,2510,2380,2379,2511,43,2511,2379,2382,2513,0,1058,1058,1060,1060,2511,2379,2382,2513,43,2509,2512,2515,2516,0,1152,1153,1155,1156,2509,2512,2515,2516,43,2516,2515,2365,2360,0,1156,1155,1046,1041,2516,2515,2365,2360,43,2512,2514,2517,2515,0,1153,1154,1157,1155,2512,2514,2517,2515,43,2515,2517,2366,2365,0,1155,1157,1047,1046,2515,2517,2366,2365,43,2509,2516,2518,2519,0,1152,1156,1022,1022,2509,2516,2518,2519,43,2519,2518,2502,2499,0,1022,1022,1009,1009,2519,2518,2502,2499,43,2516,2360,2359,2518,0,1156,1041,1022,1022,2516,2360,2359,2518,43,2518,2359,2306,2502,0,1022,1022,1010,1009,2518,2359,2306,2502,43,2509,2519,2520,2510,0,1152,1022,1022,1059,2509,2519,2520,2510,43,2510,2520,2385,2380,0,1059,1022,1022,1059,2510,2520,2385,2380,43,2519,2499,2498,2520,0,1022,1009,1009,1022,2519,2499,2498,2520,43,2520,2498,2322,2385,0,1022,1009,1010,1022,2520,2498,2322,2385,43,2521,2522,2523,2524,0,1158,1113,1112,1159,2521,2522,2523,2524,43,2524,2523,2525,2526,0,1159,1112,1114,1160,2524,2523,2525,2526,43,2522,2439,2438,2523,0,1113,1113,1112,1112,2522,2439,2438,2523,43,2523,2438,2441,2525,0,1112,1112,1114,1114,2523,2438,2441,2525,43,2521,2524,2527,2528,0,1158,1159,1161,1162,2521,2524,2527,2528,43,2528,2527,2420,2415,0,1162,1161,1096,1091,2528,2527,2420,2415,43,2524,2526,2529,2527,0,1159,1160,1163,1161,2524,2526,2529,2527,43,2527,2529,2421,2420,0,1161,1163,1097,1096,2527,2529,2421,2420,43,2521,2528,2530,2531,0,1158,1162,1164,1165,2521,2528,2530,2531,43,2531,2530,2517,2514,0,1165,1164,1157,1154,2531,2530,2517,2514,43,2528,2415,2414,2530,0,1162,1091,1090,1164,2528,2415,2414,2530,43,2530,2414,2366,2517,0,1164,1090,1047,1157,2530,2414,2366,2517,43,2521,2531,2532,2522,0,1158,1165,1115,1113,2521,2531,2532,2522,43,2522,2532,2444,2439,0,1113,1115,1115,1113,2522,2532,2444,2439,43,2531,2514,2513,2532,0,1165,1154,1060,1115,2531,2514,2513,2532,43,2532,2513,2382,2444,0,1115,1060,1060,1115,2532,2513,2382,2444,43,2533,2534,2535,2536,0,1166,1121,1120,1167,2533,2534,2535,2536,43,2536,2535,2537,2538,0,1167,1120,1123,1168,2536,2535,2537,2538,43,2534,2451,2450,2535,0,1121,1121,1120,1120,2534,2451,2450,2535,43,2535,2450,2454,2537,0,1120,1120,1123,1123,2535,2450,2454,2537,43,2533,2536,2539,2540,0,1166,1167,1169,1170,2533,2536,2539,2540,43,2540,2539,2541,2542,0,1170,1169,1171,1172,2540,2539,2541,2542,43,2536,2538,2543,2539,0,1167,1168,1173,1169,2536,2538,2543,2539,43,2539,2543,2544,2541,0,1169,1173,1174,1171,2539,2543,2544,2541,43,2533,2540,2545,2546,0,1166,1170,1175,1176,2533,2540,2545,2546,43,2546,2545,2529,2526,0,1176,1175,1163,1160,2546,2545,2529,2526,43,2540,2542,2547,2545,0,1170,1172,1177,1175,2540,2542,2547,2545,43,2545,2547,2421,2529,0,1175,1177,1097,1163,2545,2547,2421,2529,43,2533,2546,2548,2534,0,1166,1176,1124,1121,2533,2546,2548,2534,43,2534,2548,2457,2451,0,1121,1124,1124,1121,2534,2548,2457,2451,43,2546,2526,2525,2548,0,1176,1160,1114,1124,2546,2526,2525,2548,43,2548,2525,2441,2457,0,1124,1114,1114,1124,2548,2525,2441,2457,43,2549,2550,2551,2552,0,1178,1133,1132,1179,2549,2550,2551,2552,43,2552,2551,2553,2554,0,1179,1132,1135,1180,2552,2551,2553,2554,43,2550,2467,2466,2551,0,1133,1133,1132,1132,2550,2467,2466,2551,43,2551,2466,2470,2553,0,1132,1132,1135,1135,2551,2466,2470,2553,43,2549,2552,2555,2556,0,1178,1179,1181,1182,2549,2552,2555,2556,43,2556,2555,2557,2558,0,1182,1181,1183,1184,2556,2555,2557,2558,43,2552,2554,2559,2555,0,1179,1180,1185,1181,2552,2554,2559,2555,43,2555,2559,2560,2557,0,1181,1185,1186,1183,2555,2559,2560,2557,43,2549,2556,2561,2562,0,1178,1182,1187,1188,2549,2556,2561,2562,43,2562,2561,2543,2538,0,1188,1187,1173,1168,2562,2561,2543,2538,43,2556,2558,2563,2561,0,1182,1184,1189,1187,2556,2558,2563,2561,43,2561,2563,2544,2543,0,1187,1189,1174,1173,2561,2563,2544,2543,43,2549,2562,2564,2550,0,1178,1188,1136,1133,2549,2562,2564,2550,43,2550,2564,2473,2467,0,1133,1136,1136,1133,2550,2564,2473,2467,43,2562,2538,2537,2564,0,1188,1168,1123,1136,2562,2538,2537,2564,43,2564,2537,2454,2473,0,1136,1123,1123,1136,2564,2537,2454,2473,43,2565,2566,2567,2568,0,1190,1144,1003,1003,2565,2566,2567,2568,43,2568,2567,2569,2570,0,1003,1003,1004,1004,2568,2567,2569,2570,43,2566,2483,2482,2567,0,1144,1144,1003,1003,2566,2483,2482,2567,43,2567,2482,2486,2569,0,1003,1003,1004,1004,2567,2482,2486,2569,43,2565,2568,2571,2572,0,1190,1003,1003,1191,2565,2568,2571,2572,43,2572,2571,2573,2574,0,1191,1003,1003,1192,2572,2571,2573,2574,43,2568,2570,2575,2571,0,1003,1004,1004,1003,2568,2570,2575,2571,43,2571,2575,2576,2573,0,1003,1004,1004,1003,2571,2575,2576,2573,43,2565,2572,2577,2578,0,1190,1191,1193,1194,2565,2572,2577,2578,43,2578,2577,2559,2554,0,1194,1193,1185,1180,2578,2577,2559,2554,43,2572,2574,2579,2577,0,1191,1192,1195,1193,2572,2574,2579,2577,43,2577,2579,2560,2559,0,1193,1195,1186,1185,2577,2579,2560,2559,43,2565,2578,2580,2566,0,1190,1194,1146,1144,2565,2578,2580,2566,43,2566,2580,2489,2483,0,1144,1146,1146,1144,2566,2580,2489,2483,43,2578,2554,2553,2580,0,1194,1180,1135,1146,2578,2554,2553,2580,43,2580,2553,2470,2489,0,1146,1135,1135,1146,2580,2553,2470,2489,43,2581,2582,2583,2584,0,1196,1197,1198,1199,2581,2582,2583,2584,43,2584,2583,2585,2586,0,1199,1198,1200,1201,2584,2583,2585,2586,43,2582,2587,2588,2583,0,1197,1202,1203,1198,2582,2587,2588,2583,43,2583,2588,2589,2585,0,1198,1203,1204,1200,2583,2588,2589,2585,43,2581,2584,2590,2591,0,1196,1199,1205,1206,2581,2584,2590,2591,43,2591,2590,2592,2593,0,1206,1205,1207,1208,2591,2590,2592,2593,43,2584,2586,2594,2590,0,1199,1201,1209,1205,2584,2586,2594,2590,43,2590,2594,2595,2592,0,1205,1209,1210,1207,2590,2594,2595,2592,43,2581,2591,2596,2597,0,1196,1206,1211,1212,2581,2591,2596,2597,43,2597,2596,2598,2599,0,1212,1211,1213,1214,2597,2596,2598,2599,43,2591,2593,2600,2596,0,1206,1208,1215,1211,2591,2593,2600,2596,43,2596,2600,2601,2598,0,1211,1215,1216,1213,2596,2600,2601,2598,43,2581,2597,2602,2582,0,1196,1212,1217,1197,2581,2597,2602,2582,43,2582,2602,2603,2587,0,1197,1217,1218,1202,2582,2602,2603,2587,43,2597,2599,2604,2602,0,1212,1214,1219,1217,2597,2599,2604,2602,43,2602,2604,2342,2603,0,1217,1219,1030,1218,2602,2604,2342,2603,43,2605,2606,2607,2608,0,1220,1221,1222,1223,2605,2606,2607,2608,43,2608,2607,2609,2610,0,1223,1222,1224,1225,2608,2607,2609,2610,43,2606,1503,1502,2607,0,1221,766,765,1222,2606,1503,1502,2607,43,2607,1502,1505,2609,0,1222,765,768,1224,2607,1502,1505,2609,43,2605,2608,2611,2612,0,1220,1223,1006,1005,2605,2608,2611,2612,43,2612,2611,2268,2267,0,1005,1006,1006,1005,2612,2611,2268,2267,43,2608,2610,2613,2611,0,1223,1225,1007,1006,2608,2610,2613,2611,43,2611,2613,2269,2268,0,1006,1007,1007,1006,2611,2613,2269,2268,43,2605,2612,2614,2615,0,1220,1005,1008,1226,2605,2612,2614,2615,43,2615,2614,2616,2617,0,1226,1008,1009,1227,2615,2614,2616,2617,43,2612,2267,2283,2614,0,1005,1005,1008,1008,2612,2267,2283,2614,43,2614,2283,2285,2616,0,1008,1008,1010,1009,2614,2283,2285,2616,43,2605,2615,2618,2606,0,1220,1226,1228,1221,2605,2615,2618,2606,43,2606,2618,1508,1503,0,1221,1228,771,766,2606,2618,1508,1503,43,2615,2617,2619,2618,0,1226,1227,1229,1228,2615,2617,2619,2618,43,2618,2619,1332,1508,0,1228,1229,588,771,2618,2619,1332,1508,43,2620,2621,2622,2623,0,1230,1231,1022,1232,2620,2621,2622,2623,43,2623,2622,2350,2346,0,1232,1022,1022,1034,2623,2622,2350,2346,43,2621,2617,2616,2622,0,1231,1227,1009,1022,2621,2617,2616,2622,43,2622,2616,2285,2350,0,1022,1009,1010,1022,2622,2616,2285,2350,43,2620,2623,2624,2625,0,1230,1232,1233,1234,2620,2623,2624,2625,43,2625,2624,2394,2393,0,1234,1233,1070,1069,2625,2624,2394,2393,43,2623,2346,2345,2624,0,1232,1034,1033,1233,2623,2346,2345,2624,43,2624,2345,2348,2394,0,1233,1033,1036,1070,2624,2345,2348,2394,43,2620,2625,2626,2627,0,1230,1234,1235,1236,2620,2625,2626,2627,43,2627,2626,1315,1314,0,1236,1235,571,570,2627,2626,1315,1314,43,2625,2393,2408,2626,0,1234,1069,1084,1235,2625,2393,2408,2626,43,2626,2408,1316,1315,0,1235,1084,572,571,2626,2408,1316,1315,43,2620,2627,2628,2621,0,1230,1236,1237,1231,2620,2627,2628,2621,43,2621,2628,2619,2617,0,1231,1237,1229,1227,2621,2628,2619,2617,43,2627,1314,1330,2628,0,1236,570,586,1237,2627,1314,1330,2628,43,2628,1330,1332,2619,0,1237,586,588,1229,2628,1330,1332,2619,43,2629,2630,2631,2632,0,1238,1239,1240,1241,2629,2630,2631,2632,43,2632,2631,1472,1467,0,1241,1240,734,729,2632,2631,1472,1467,43,2630,2633,2634,2631,0,1239,1242,1243,1240,2630,2633,2634,2631,43,2631,2634,598,1472,0,1240,1243,344,734,2631,2634,598,1472,43,2629,2632,2635,2636,0,1238,1241,1244,1245,2629,2632,2635,2636,43,2636,2635,2637,2638,0,1245,1244,1246,1247,2636,2635,2637,2638,43,2632,1467,1466,2635,0,1241,729,728,1244,2632,1467,1466,2635,43,2635,1466,1469,2637,0,1244,728,731,1246,2635,1466,1469,2637,43,2629,2636,2639,2640,0,1238,1245,1248,1249,2629,2636,2639,2640,43,2640,2639,2641,2642,0,1249,1248,1250,1251,2640,2639,2641,2642,43,2636,2638,2643,2639,0,1245,1247,1252,1248,2636,2638,2643,2639,43,2639,2643,2644,2641,0,1248,1252,1253,1250,2639,2643,2644,2641,43,2629,2640,2645,2630,0,1238,1249,1254,1239,2629,2640,2645,2630,43,2630,2645,2646,2633,0,1239,1254,1255,1242,2630,2645,2646,2633,43,2640,2642,2647,2645,0,1249,1251,1256,1254,2640,2642,2647,2645,43,2645,2647,2648,2646,0,1254,1256,1257,1255,2645,2647,2648,2646,43,2649,2650,2651,2652,0,1258,1259,1260,1261,2649,2650,2651,2652,43,2652,2651,603,595,0,1261,1260,349,341,2652,2651,603,595,43,2650,2653,2654,2651,0,1259,1262,1263,1260,2650,2653,2654,2651,43,2651,2654,604,603,0,1260,1263,350,349,2651,2654,604,603,43,2649,2652,2655,2656,0,1258,1261,1264,1265,2649,2652,2655,2656,43,2656,2655,2634,2633,0,1265,1264,1243,1242,2656,2655,2634,2633,43,2652,595,594,2655,0,1261,341,340,1264,2652,595,594,2655,43,2655,594,598,2634,0,1264,340,344,1243,2655,594,598,2634,43,2649,2656,2657,2658,0,1258,1265,1266,1267,2649,2656,2657,2658,43,2658,2657,2659,2660,0,1267,1266,1268,1269,2658,2657,2659,2660,43,2656,2633,2646,2657,0,1265,1242,1255,1266,2656,2633,2646,2657,43,2657,2646,2648,2659,0,1266,1255,1257,1268,2657,2646,2648,2659,43,2649,2658,2661,2650,0,1258,1267,1270,1259,2649,2658,2661,2650,43,2650,2661,2662,2653,0,1259,1270,1271,1262,2650,2661,2662,2653,43,2658,2660,2663,2661,0,1267,1269,1272,1270,2658,2660,2663,2661,43,2661,2663,2664,2662,0,1270,1272,1273,1271,2661,2663,2664,2662,43,2665,2666,2667,2668,0,1274,1275,1276,1277,2665,2666,2667,2668,43,2668,2667,2669,2670,0,1277,1276,1278,1279,2668,2667,2669,2670,43,2666,2671,2672,2667,0,1275,1280,1281,1276,2666,2671,2672,2667,43,2667,2672,2406,2669,0,1276,1281,1082,1278,2667,2672,2406,2669,43,2665,2668,2673,2674,0,1274,1277,1282,1283,2665,2668,2673,2674,43,2674,2673,2675,2676,0,1283,1282,1284,1285,2674,2673,2675,2676,43,2668,2670,2677,2673,0,1277,1279,1286,1282,2668,2670,2677,2673,43,2673,2677,2678,2675,0,1282,1286,1287,1284,2673,2677,2678,2675,43,2665,2674,2679,2680,0,1274,1283,1288,1289,2665,2674,2679,2680,43,2680,2679,2654,2653,0,1289,1288,1263,1262,2680,2679,2654,2653,43,2674,2676,2681,2679,0,1283,1285,1290,1288,2674,2676,2681,2679,43,2679,2681,604,2654,0,1288,1290,350,1263,2679,2681,604,2654,43,2665,2680,2682,2666,0,1274,1289,1291,1275,2665,2680,2682,2666,43,2666,2682,2683,2671,0,1275,1291,1292,1280,2666,2682,2683,2671,43,2680,2653,2662,2682,0,1289,1262,1271,1291,2680,2653,2662,2682,43,2682,2662,2664,2683,0,1291,1271,1273,1292,2682,2662,2664,2683,43,2684,2685,2686,2687,0,1293,1294,1295,1296,2684,2685,2686,2687,43,2687,2686,2688,2689,0,1296,1295,1297,1298,2687,2686,2688,2689,43,2685,2690,2691,2686,0,1294,1299,1300,1295,2685,2690,2691,2686,43,2686,2691,2692,2688,0,1295,1300,1301,1297,2686,2691,2692,2688,43,2684,2687,2693,2694,0,1293,1296,1302,1303,2684,2687,2693,2694,43,2694,2693,2695,2696,0,1303,1302,1304,1305,2694,2693,2695,2696,43,2687,2689,2697,2693,0,1296,1298,1306,1302,2687,2689,2697,2693,43,2693,2697,2698,2695,0,1302,1306,1307,1304,2693,2697,2698,2695,43,2684,2694,2699,2700,0,1293,1303,1308,1309,2684,2694,2699,2700,43,2700,2699,2701,2702,0,1309,1308,1310,1311,2700,2699,2701,2702,43,2694,2696,2703,2699,0,1303,1305,1312,1308,2694,2696,2703,2699,43,2699,2703,2704,2701,0,1308,1312,1313,1310,2699,2703,2704,2701,43,2684,2700,2705,2685,0,1293,1309,1314,1294,2684,2700,2705,2685,43,2685,2705,2706,2690,0,1294,1314,1315,1299,2685,2705,2706,2690,43,2700,2702,2707,2705,0,1309,1311,1316,1314,2700,2702,2707,2705,43,2705,2707,2708,2706,0,1314,1316,1317,1315,2705,2707,2708,2706,43,2709,2710,2711,2712,0,1318,1319,1320,1321,2709,2710,2711,2712,43,2712,2711,2713,2714,0,1321,1320,1322,1323,2712,2711,2713,2714,43,2710,2715,2716,2711,0,1319,1324,1325,1320,2710,2715,2716,2711,43,2711,2716,2717,2713,0,1320,1325,1326,1322,2711,2716,2717,2713,43,2709,2712,2718,2719,0,1318,1321,1327,1328,2709,2712,2718,2719,43,2719,2718,2720,2721,0,1328,1327,1329,1330,2719,2718,2720,2721,43,2712,2714,2722,2718,0,1321,1323,1331,1327,2712,2714,2722,2718,43,2718,2722,2723,2720,0,1327,1331,1332,1329,2718,2722,2723,2720,43,2709,2719,2724,2725,0,1318,1328,1333,1334,2709,2719,2724,2725,43,2725,2724,2726,2727,0,1334,1333,1335,1336,2725,2724,2726,2727,43,2719,2721,2728,2724,0,1328,1330,1337,1333,2719,2721,2728,2724,43,2724,2728,2729,2726,0,1333,1337,1338,1335,2724,2728,2729,2726,43,2709,2725,2730,2710,0,1318,1334,1339,1319,2709,2725,2730,2710,43,2710,2730,2731,2715,0,1319,1339,1340,1324,2710,2730,2731,2715,43,2725,2727,2732,2730,0,1334,1336,1341,1339,2725,2727,2732,2730,43,2730,2732,2733,2731,0,1339,1341,1342,1340,2730,2732,2733,2731,43,2734,2735,2736,2737,0,1343,1344,1345,1346,2734,2735,2736,2737,43,2737,2736,2738,2739,0,1346,1345,1347,1348,2737,2736,2738,2739,43,2735,2740,2741,2736,0,1344,1349,1350,1345,2735,2740,2741,2736,43,2736,2741,2742,2738,0,1345,1350,1351,1347,2736,2741,2742,2738,43,2734,2737,2743,2744,0,1343,1346,1352,1353,2734,2737,2743,2744,43,2744,2743,2426,2419,0,1353,1352,1102,1095,2744,2743,2426,2419,43,2737,2739,2745,2743,0,1346,1348,1354,1352,2737,2739,2745,2743,43,2743,2745,2427,2426,0,1352,1354,1103,1102,2743,2745,2427,2426,43,2734,2744,2746,2747,0,1343,1353,1355,1356,2734,2744,2746,2747,43,2747,2746,2748,2749,0,1356,1355,1357,1358,2747,2746,2748,2749,43,2744,2419,2418,2746,0,1353,1095,1094,1355,2744,2419,2418,2746,43,2746,2418,2421,2748,0,1355,1094,1097,1357,2746,2418,2421,2748,43,2734,2747,2750,2735,0,1343,1356,1359,1344,2734,2747,2750,2735,43,2735,2750,2751,2740,0,1344,1359,1360,1349,2735,2750,2751,2740,43,2747,2749,2752,2750,0,1356,1358,1361,1359,2747,2749,2752,2750,43,2750,2752,2753,2751,0,1359,1361,1362,1360,2750,2752,2753,2751,43,2754,2755,2756,2757,0,1363,1364,1365,1366,2754,2755,2756,2757,43,2757,2756,2758,2759,0,1366,1365,1367,1368,2757,2756,2758,2759,43,2755,2558,2557,2756,0,1364,1184,1183,1365,2755,2558,2557,2756,43,2756,2557,2560,2758,0,1365,1183,1186,1367,2756,2557,2560,2758,43,2754,2757,2760,2761,0,1363,1366,1369,1370,2754,2757,2760,2761,43,2761,2760,2235,2228,0,1370,1369,987,980,2761,2760,2235,2228,43,2757,2759,2762,2760,0,1366,1368,1371,1369,2757,2759,2762,2760,43,2760,2762,2236,2235,0,1369,1371,988,987,2760,2762,2236,2235,43,2754,2761,2763,2764,0,1363,1370,1372,1373,2754,2761,2763,2764,43,2764,2763,2765,2766,0,1373,1372,1374,1375,2764,2763,2765,2766,43,2761,2228,2227,2763,0,1370,980,979,1372,2761,2228,2227,2763,43,2763,2227,2230,2765,0,1372,979,982,1374,2763,2227,2230,2765,43,2754,2764,2767,2755,0,1363,1373,1376,1364,2754,2764,2767,2755,43,2755,2767,2563,2558,0,1364,1376,1189,1184,2755,2767,2563,2558,43,2764,2766,2768,2767,0,1373,1375,1377,1376,2764,2766,2768,2767,43,2767,2768,2544,2563,0,1376,1377,1174,1189,2767,2768,2544,2563,43,2769,2770,2771,2772,0,1378,1379,1003,1003,2769,2770,2771,2772,43,2772,2771,2773,2774,0,1003,1003,1004,1004,2772,2771,2773,2774,43,2770,2574,2573,2771,0,1379,1192,1003,1003,2770,2574,2573,2771,43,2771,2573,2576,2773,0,1003,1003,1004,1004,2771,2573,2576,2773,43,2769,2772,2775,2776,0,1378,1003,1003,1380,2769,2772,2775,2776,43,2776,2775,2255,2250,0,1380,1003,1003,1002,2776,2775,2255,2250,43,2772,2774,2777,2775,0,1003,1004,1004,1003,2772,2774,2777,2775,43,2775,2777,2256,2255,0,1003,1004,1004,1003,2775,2777,2256,2255,43,2769,2776,2778,2779,0,1378,1380,1381,1382,2769,2776,2778,2779,43,2779,2778,2762,2759,0,1382,1381,1371,1368,2779,2778,2762,2759,43,2776,2250,2249,2778,0,1380,1002,1001,1381,2776,2250,2249,2778,43,2778,2249,2236,2762,0,1381,1001,988,1371,2778,2249,2236,2762,43,2769,2779,2780,2770,0,1378,1382,1383,1379,2769,2779,2780,2770,43,2770,2780,2579,2574,0,1379,1383,1195,1192,2770,2780,2579,2574,43,2779,2759,2758,2780,0,1382,1368,1367,1383,2779,2759,2758,2780,43,2780,2758,2560,2579,0,1383,1367,1186,1195,2780,2758,2560,2579,43,2781,2782,2783,2784,0,1384,1385,1386,1387,2781,2782,2783,2784,43,2784,2783,2785,2786,0,1387,1386,1388,1389,2784,2783,2785,2786,43,2782,2787,2788,2783,0,1385,1390,1391,1386,2782,2787,2788,2783,43,2783,2788,2789,2785,0,1386,1391,1392,1388,2783,2788,2789,2785,43,2781,2784,2790,2791,0,1384,1387,1393,1394,2781,2784,2790,2791,43,2791,2790,2792,2793,0,1394,1393,1395,1396,2791,2790,2792,2793,43,2784,2786,2794,2790,0,1387,1389,1397,1393,2784,2786,2794,2790,43,2790,2794,2795,2792,0,1393,1397,1398,1395,2790,2794,2795,2792,43,2781,2791,2796,2797,0,1384,1394,1399,1400,2781,2791,2796,2797,43,2797,2796,2798,2799,0,1400,1399,1401,1402,2797,2796,2798,2799,43,2791,2793,2800,2796,0,1394,1396,1403,1399,2791,2793,2800,2796,43,2796,2800,2801,2798,0,1399,1403,1404,1401,2796,2800,2801,2798,43,2781,2797,2802,2782,0,1384,1400,1405,1385,2781,2797,2802,2782,43,2782,2802,2803,2787,0,1385,1405,1406,1390,2782,2802,2803,2787,43,2797,2799,2804,2802,0,1400,1402,1407,1405,2797,2799,2804,2802,43,2802,2804,2805,2803,0,1405,1407,1408,1406,2802,2804,2805,2803,43,2806,2807,2808,2809,0,1409,1410,1411,1412,2806,2807,2808,2809,43,2809,2808,2810,2811,0,1412,1411,1413,1414,2809,2808,2810,2811,43,2807,2740,2751,2808,0,1410,1349,1360,1411,2807,2740,2751,2808,43,2808,2751,2753,2810,0,1411,1360,1362,1413,2808,2751,2753,2810,43,2806,2809,2812,2813,0,1409,1412,1415,1416,2806,2809,2812,2813,43,2813,2812,2814,2815,0,1416,1415,1417,1418,2813,2812,2814,2815,43,2809,2811,2816,2812,0,1412,1414,1419,1415,2809,2811,2816,2812,43,2812,2816,2817,2814,0,1415,1419,1420,1417,2812,2816,2817,2814,43,2806,2813,2818,2819,0,1409,1416,1421,1422,2806,2813,2818,2819,43,2819,2818,2820,2821,0,1422,1421,1423,1424,2819,2818,2820,2821,43,2813,2815,2822,2818,0,1416,1418,1425,1421,2813,2815,2822,2818,43,2818,2822,2823,2820,0,1421,1425,1426,1423,2818,2822,2823,2820,43,2806,2819,2824,2807,0,1409,1422,1427,1410,2806,2819,2824,2807,43,2807,2824,2741,2740,0,1410,1427,1350,1349,2807,2824,2741,2740,43,2819,2821,2825,2824,0,1422,1424,1428,1427,2819,2821,2825,2824,43,2824,2825,2742,2741,0,1427,1428,1351,1350,2824,2825,2742,2741,43,2826,2827,2828,2829,0,1429,1430,1431,1432,2826,2827,2828,2829,43,2829,2828,2830,2831,0,1432,1431,1433,1434,2829,2828,2830,2831,43,2827,2832,2833,2828,0,1430,1435,1436,1431,2827,2832,2833,2828,43,2828,2833,2834,2830,0,1431,1436,1437,1433,2828,2833,2834,2830,43,2826,2829,2835,2836,0,1429,1432,1438,1439,2826,2829,2835,2836,43,2836,2835,2837,2838,0,1439,1438,1440,1441,2836,2835,2837,2838,43,2829,2831,2839,2835,0,1432,1434,1442,1438,2829,2831,2839,2835,43,2835,2839,2840,2837,0,1438,1442,1443,1440,2835,2839,2840,2837,43,2826,2836,2841,2842,0,1429,1439,1444,1445,2826,2836,2841,2842,43,2842,2841,2843,2844,0,1445,1444,1446,1447,2842,2841,2843,2844,43,2836,2838,2845,2841,0,1439,1441,1448,1444,2836,2838,2845,2841,43,2841,2845,2846,2843,0,1444,1448,1449,1446,2841,2845,2846,2843,43,2826,2842,2847,2827,0,1429,1445,1450,1430,2826,2842,2847,2827,43,2827,2847,2848,2832,0,1430,1450,1451,1435,2827,2847,2848,2832,43,2842,2844,2849,2847,0,1445,1447,1452,1450,2842,2844,2849,2847,43,2847,2849,2850,2848,0,1450,1452,1453,1451,2847,2849,2850,2848,43,2851,2852,2853,2854,0,1454,1455,1456,1457,2851,2852,2853,2854,43,2854,2853,2855,2856,0,1457,1456,1458,1459,2854,2853,2855,2856,43,2852,2857,2858,2853,0,1455,1460,1461,1456,2852,2857,2858,2853,43,2853,2858,2801,2855,0,1456,1461,1404,1458,2853,2858,2801,2855,43,2851,2854,2859,2860,0,1454,1457,1462,1463,2851,2854,2859,2860,43,2860,2859,609,602,0,1463,1462,355,348,2860,2859,609,602,43,2854,2856,2861,2859,0,1457,1459,1464,1462,2854,2856,2861,2859,43,2859,2861,610,609,0,1462,1464,356,355,2859,2861,610,609,43,2851,2860,2862,2863,0,1454,1463,1465,1466,2851,2860,2862,2863,43,2863,2862,2681,2676,0,1466,1465,1290,1285,2863,2862,2681,2676,43,2860,602,601,2862,0,1463,348,347,1465,2860,602,601,2862,43,2862,601,604,2681,0,1465,347,350,1290,2862,601,604,2681,43,2851,2863,2864,2852,0,1454,1466,1467,1455,2851,2863,2864,2852,43,2852,2864,2865,2857,0,1455,1467,1468,1460,2852,2864,2865,2857,43,2863,2676,2675,2864,0,1466,1285,1284,1467,2863,2676,2675,2864,43,2864,2675,2678,2865,0,1467,1284,1287,1468,2864,2675,2678,2865,43,2866,2867,2868,2869,0,1469,1470,1471,1472,2866,2867,2868,2869,43,2869,2868,2870,2871,0,1472,1471,1473,1474,2869,2868,2870,2871,43,2867,2872,2873,2868,0,1470,1475,1476,1471,2867,2872,2873,2868,43,2868,2873,2874,2870,0,1471,1476,1477,1473,2868,2873,2874,2870,43,2866,2869,2875,2876,0,1469,1472,1478,1479,2866,2869,2875,2876,43,2876,2875,2877,2878,0,1479,1478,1480,1481,2876,2875,2877,2878,43,2869,2871,2879,2875,0,1472,1474,1482,1478,2869,2871,2879,2875,43,2875,2879,2880,2877,0,1478,1482,1483,1480,2875,2879,2880,2877,43,2866,2876,2881,2882,0,1469,1479,1484,1485,2866,2876,2881,2882,43,2882,2881,2883,2884,0,1485,1484,1486,1487,2882,2881,2883,2884,43,2876,2878,2885,2881,0,1479,1481,1488,1484,2876,2878,2885,2881,43,2881,2885,2886,2883,0,1484,1488,1489,1486,2881,2885,2886,2883,43,2866,2882,2887,2867,0,1469,1485,1490,1470,2866,2882,2887,2867,43,2867,2887,2888,2872,0,1470,1490,1490,1475,2867,2887,2888,2872,43,2882,2884,2889,2887,0,1485,1487,1490,1490,2882,2884,2889,2887,43,2887,2889,2890,2888,0,1490,1490,1490,1490,2887,2889,2890,2888,43,2891,2892,2893,2894,0,1491,1492,1490,1490,2891,2892,2893,2894,43,2894,2893,2895,2896,0,1490,1490,1490,1490,2894,2893,2895,2896,43,2892,2897,2898,2893,0,1492,1493,1490,1490,2892,2897,2898,2893,43,2893,2898,2899,2895,0,1490,1490,1490,1490,2893,2898,2899,2895,43,2891,2894,2900,2901,0,1491,1490,1490,1494,2891,2894,2900,2901,43,2901,2900,2902,2903,0,1494,1490,1490,1495,2901,2900,2902,2903,43,2894,2896,2904,2900,0,1490,1490,1490,1490,2894,2896,2904,2900,43,2900,2904,2905,2902,0,1490,1490,1490,1490,2900,2904,2905,2902,43,2891,2901,2906,2907,0,1491,1494,1496,1497,2891,2901,2906,2907,43,2907,2906,2908,2909,0,1497,1496,1498,1499,2907,2906,2908,2909,43,2901,2903,2910,2906,0,1494,1495,1500,1496,2901,2903,2910,2906,43,2906,2910,2911,2908,0,1496,1500,1501,1498,2906,2910,2911,2908,43,2891,2907,2912,2892,0,1491,1497,1502,1492,2891,2907,2912,2892,43,2892,2912,2913,2897,0,1492,1502,1503,1493,2892,2912,2913,2897,43,2907,2909,2914,2912,0,1497,1499,1504,1502,2907,2909,2914,2912,43,2912,2914,2915,2913,0,1502,1504,1505,1503,2912,2914,2915,2913,43,2916,2917,2918,2919,0,1506,1507,1508,1509,2916,2917,2918,2919,43,2919,2918,2920,2921,0,1509,1508,1510,1511,2919,2918,2920,2921,43,2917,2922,2923,2918,0,1507,1512,1513,1508,2917,2922,2923,2918,43,2918,2923,2924,2920,0,1508,1513,1514,1510,2918,2923,2924,2920,43,2916,2919,2925,2926,0,1506,1509,1515,1516,2916,2919,2925,2926,43,2926,2925,2927,2928,0,1516,1515,1517,1518,2926,2925,2927,2928,43,2919,2921,2929,2925,0,1509,1511,1519,1515,2919,2921,2929,2925,43,2925,2929,2930,2927,0,1515,1519,1520,1517,2925,2929,2930,2927,43,2916,2926,2931,2932,0,1506,1516,1521,1522,2916,2926,2931,2932,43,2932,2931,2933,2934,0,1522,1521,1523,1524,2932,2931,2933,2934,43,2926,2928,2935,2931,0,1516,1518,1525,1521,2926,2928,2935,2931,43,2931,2935,2936,2933,0,1521,1525,1526,1523,2931,2935,2936,2933,43,2916,2932,2937,2917,0,1506,1522,1527,1507,2916,2932,2937,2917,43,2917,2937,2938,2922,0,1507,1527,1528,1512,2917,2937,2938,2922,43,2932,2934,2939,2937,0,1522,1524,1529,1527,2932,2934,2939,2937,43,2937,2939,2940,2938,0,1527,1529,1530,1528,2937,2939,2940,2938,43,2941,2942,2943,2944,0,1531,1532,1533,1534,2941,2942,2943,2944,43,2944,2943,2945,2946,0,1534,1533,1535,1536,2944,2943,2945,2946,43,2942,2947,2948,2943,0,1532,1537,1538,1533,2942,2947,2948,2943,43,2943,2948,2949,2945,0,1533,1538,1539,1535,2943,2948,2949,2945,43,2941,2944,2950,2951,0,1531,1534,1540,1541,2941,2944,2950,2951,43,2951,2950,2952,2953,0,1541,1540,1542,1543,2951,2950,2952,2953,43,2944,2946,2954,2950,0,1534,1536,1544,1540,2944,2946,2954,2950,43,2950,2954,2955,2952,0,1540,1544,1545,1542,2950,2954,2955,2952,43,2941,2951,2956,2957,0,1531,1541,1546,1547,2941,2951,2956,2957,43,2957,2956,2958,2959,0,1547,1546,1548,1549,2957,2956,2958,2959,43,2951,2953,2960,2956,0,1541,1543,1550,1546,2951,2953,2960,2956,43,2956,2960,2834,2958,0,1546,1550,1437,1548,2956,2960,2834,2958,43,2941,2957,2961,2942,0,1531,1547,1551,1532,2941,2957,2961,2942,43,2942,2961,2962,2947,0,1532,1551,1551,1537,2942,2961,2962,2947,43,2957,2959,2963,2961,0,1547,1549,1552,1551,2957,2959,2963,2961,43,2961,2963,2964,2962,0,1551,1552,1552,1551,2961,2963,2964,2962,43,2965,2966,2967,2968,0,1553,1554,1555,1556,2965,2966,2967,2968,43,2968,2967,2969,2970,0,1556,1555,1557,1558,2968,2967,2969,2970,43,2966,2971,2972,2967,0,1554,1554,1555,1555,2966,2971,2972,2967,43,2967,2972,2973,2969,0,1555,1555,1557,1557,2967,2972,2973,2969,43,2965,2968,2974,2975,0,1553,1556,1559,1560,2965,2968,2974,2975,43,2975,2974,2976,2977,0,1560,1559,1561,1562,2975,2974,2976,2977,43,2968,2970,2978,2974,0,1556,1558,1563,1559,2968,2970,2978,2974,43,2974,2978,2850,2976,0,1559,1563,1453,1561,2974,2978,2850,2976,43,2965,2975,2979,2980,0,1553,1560,1564,1565,2965,2975,2979,2980,43,2980,2979,2981,2982,0,1565,1564,1566,1567,2980,2979,2981,2982,43,2975,2977,2983,2979,0,1560,1562,1568,1564,2975,2977,2983,2979,43,2979,2983,2789,2981,0,1564,1568,1392,1566,2979,2983,2789,2981,43,2965,2980,2984,2966,0,1553,1565,1569,1554,2965,2980,2984,2966,43,2966,2984,2985,2971,0,1554,1569,1569,1554,2966,2984,2985,2971,43,2980,2982,2986,2984,0,1565,1567,1570,1569,2980,2982,2986,2984,43,2984,2986,2987,2985,0,1569,1570,1570,1569,2984,2986,2987,2985,43,2988,2989,2990,2991,0,1571,1572,1573,1574,2988,2989,2990,2991,43,2991,2990,2992,2993,0,1574,1573,1575,1576,2991,2990,2992,2993,43,2989,2994,2995,2990,0,1572,1577,1578,1573,2989,2994,2995,2990,43,2990,2995,2996,2992,0,1573,1578,1579,1575,2990,2995,2996,2992,43,2988,2991,2997,2998,0,1571,1574,1580,1581,2988,2991,2997,2998,43,2998,2997,2999,3000,0,1581,1580,1582,1583,2998,2997,2999,3000,43,2991,2993,3001,2997,0,1574,1576,1584,1580,2991,2993,3001,2997,43,2997,3001,3002,2999,0,1580,1584,1585,1582,2997,3001,3002,2999,43,2988,2998,3003,3004,0,1571,1581,1586,1587,2988,2998,3003,3004,43,3004,3003,2879,2871,0,1587,1586,1482,1474,3004,3003,2879,2871,43,2998,3000,3005,3003,0,1581,1583,1588,1586,2998,3000,3005,3003,43,3003,3005,2880,2879,0,1586,1588,1483,1482,3003,3005,2880,2879,43,2988,3004,3006,2989,0,1571,1587,1589,1572,2988,3004,3006,2989,43,2989,3006,3007,2994,0,1572,1589,1590,1577,2989,3006,3007,2994,43,3004,2871,2870,3006,0,1587,1474,1473,1589,3004,2871,2870,3006,43,3006,2870,2874,3007,0,1589,1473,1477,1590,3006,2870,2874,3007,43,3008,3009,3010,3011,0,1591,1592,1593,1594,3008,3009,3010,3011,43,3011,3010,3012,3013,0,1594,1593,1595,1596,3011,3010,3012,3013,43,3009,2739,2738,3010,0,1592,1348,1347,1593,3009,2739,2738,3010,43,3010,2738,2742,3012,0,1593,1347,1351,1595,3010,2738,2742,3012,43,3008,3011,3014,3015,0,1591,1594,1597,1598,3008,3011,3014,3015,43,3015,3014,2939,2934,0,1598,1597,1529,1524,3015,3014,2939,2934,43,3011,3013,3016,3014,0,1594,1596,1599,1597,3011,3013,3016,3014,43,3014,3016,2940,2939,0,1597,1599,1530,1529,3014,3016,2940,2939,43,3008,3015,3017,3018,0,1591,1598,1600,1601,3008,3015,3017,3018,43,3018,3017,3019,3020,0,1601,1600,1602,1603,3018,3017,3019,3020,43,3015,2934,2933,3017,0,1598,1524,1523,1600,3015,2934,2933,3017,43,3017,2933,2936,3019,0,1600,1523,1526,1602,3017,2933,2936,3019,43,3008,3018,3021,3009,0,1591,1601,1604,1592,3008,3018,3021,3009,43,3009,3021,2745,2739,0,1592,1604,1354,1348,3009,3021,2745,2739,43,3018,3020,3022,3021,0,1601,1603,1605,1604,3018,3020,3022,3021,43,3021,3022,2427,2745,0,1604,1605,1103,1354,3021,3022,2427,2745,43,3023,3024,3025,3026,0,1606,1607,1608,1609,3023,3024,3025,3026,43,3026,3025,3027,3028,0,1609,1608,1610,1611,3026,3025,3027,3028,43,3024,3029,3030,3025,0,1607,1612,1613,1608,3024,3029,3030,3025,43,3025,3030,2595,3027,0,1608,1613,1210,1610,3025,3030,2595,3027,43,3023,3026,3031,3032,0,1606,1609,1614,1615,3023,3026,3031,3032,43,3032,3031,3022,3020,0,1615,1614,1605,1603,3032,3031,3022,3020,43,3026,3028,3033,3031,0,1609,1611,1616,1614,3026,3028,3033,3031,43,3031,3033,2427,3022,0,1614,1616,1103,1605,3031,3033,2427,3022,43,3023,3032,3034,3035,0,1606,1615,1617,1618,3023,3032,3034,3035,43,3035,3034,3036,3037,0,1618,1617,1619,1620,3035,3034,3036,3037,43,3032,3020,3019,3034,0,1615,1603,1602,1617,3032,3020,3019,3034,43,3034,3019,2936,3036,0,1617,1602,1526,1619,3034,3019,2936,3036,43,3023,3035,3038,3024,0,1606,1618,1621,1607,3023,3035,3038,3024,43,3024,3038,3039,3029,0,1607,1621,1622,1612,3024,3038,3039,3029,43,3035,3037,3040,3038,0,1618,1620,1623,1621,3035,3037,3040,3038,43,3038,3040,2915,3039,0,1621,1623,1505,1622,3038,3040,2915,3039,43,3041,3042,3043,3044,0,1624,1625,1626,1627,3041,3042,3043,3044,43,3044,3043,3045,3046,0,1627,1626,1628,1629,3044,3043,3045,3046,43,3042,3047,3048,3043,0,1625,1630,1631,1626,3042,3047,3048,3043,43,3043,3048,2400,3045,0,1626,1631,1076,1628,3043,3048,2400,3045,43,3041,3044,3049,3050,0,1624,1627,1632,1633,3041,3044,3049,3050,43,3050,3049,3051,3052,0,1633,1632,1634,1635,3050,3049,3051,3052,43,3044,3046,3053,3049,0,1627,1629,1636,1632,3044,3046,3053,3049,43,3049,3053,2601,3051,0,1632,1636,1216,1634,3049,3053,2601,3051,43,3041,3050,3054,3055,0,1624,1633,1637,1638,3041,3050,3054,3055,43,3055,3054,3056,3057,0,1638,1637,1639,1640,3055,3054,3056,3057,43,3050,3052,3058,3054,0,1633,1635,1641,1637,3050,3052,3058,3054,43,3054,3058,2911,3056,0,1637,1641,1501,1639,3054,3058,2911,3056,43,3041,3055,3059,3042,0,1624,1638,1642,1625,3041,3055,3059,3042,43,3042,3059,3060,3047,0,1625,1642,1643,1630,3042,3059,3060,3047,43,3055,3057,3061,3059,0,1638,1640,1644,1642,3055,3057,3061,3059,43,3059,3061,2886,3060,0,1642,1644,1489,1643,3059,3061,2886,3060,43,3062,3063,3064,3065,0,1645,1646,1647,1648,3062,3063,3064,3065,43,3065,3064,2677,2670,0,1648,1647,1286,1279,3065,3064,2677,2670,43,3063,3066,3067,3064,0,1646,1649,1650,1647,3063,3066,3067,3064,43,3064,3067,2678,2677,0,1647,1650,1287,1286,3064,3067,2678,2677,43,3062,3065,3068,3069,0,1645,1648,1651,1652,3062,3065,3068,3069,43,3069,3068,3070,3071,0,1652,1651,1653,1654,3069,3068,3070,3071,43,3065,2670,2669,3068,0,1648,1279,1278,1651,3065,2670,2669,3068,43,3068,2669,2406,3070,0,1651,1278,1082,1653,3068,2669,2406,3070,43,3062,3069,3072,3073,0,1645,1652,1655,1656,3062,3069,3072,3073,43,3073,3072,3005,3000,0,1656,1655,1588,1583,3073,3072,3005,3000,43,3069,3071,3074,3072,0,1652,1654,1657,1655,3069,3071,3074,3072,43,3072,3074,2880,3005,0,1655,1657,1483,1588,3072,3074,2880,3005,43,3062,3073,3075,3063,0,1645,1656,1658,1646,3062,3073,3075,3063,43,3063,3075,3076,3066,0,1646,1658,1659,1649,3063,3075,3076,3066,43,3073,3000,2999,3075,0,1656,1583,1582,1658,3073,3000,2999,3075,43,3075,2999,3002,3076,0,1658,1582,1585,1659,3075,2999,3002,3076,43,3077,3078,3079,3080,0,1660,1661,1662,1663,3077,3078,3079,3080,43,3080,3079,3081,3082,0,1663,1662,1664,1665,3080,3079,3081,3082,43,3078,3083,3084,3079,0,1661,1666,1667,1662,3078,3083,3084,3079,43,3079,3084,2955,3081,0,1662,1667,1545,1664,3079,3084,2955,3081,43,3077,3080,3085,3086,0,1660,1663,1668,1669,3077,3080,3085,3086,43,3086,3085,3016,3013,0,1669,1668,1599,1596,3086,3085,3016,3013,43,3080,3082,3087,3085,0,1663,1665,1670,1668,3080,3082,3087,3085,43,3085,3087,2940,3016,0,1668,1670,1530,1599,3085,3087,2940,3016,43,3077,3086,3088,3089,0,1660,1669,1671,1672,3077,3086,3088,3089,43,3089,3088,2825,2821,0,1672,1671,1428,1424,3089,3088,2825,2821,43,3086,3013,3012,3088,0,1669,1596,1595,1671,3086,3013,3012,3088,43,3088,3012,2742,2825,0,1671,1595,1351,1428,3088,3012,2742,2825,43,3077,3089,3090,3078,0,1660,1672,1673,1661,3077,3089,3090,3078,43,3078,3090,3091,3083,0,1661,1673,1674,1666,3078,3090,3091,3083,43,3089,2821,2820,3090,0,1672,1424,1423,1673,3089,2821,2820,3090,43,3090,2820,2823,3091,0,1673,1423,1426,1674,3090,2820,2823,3091,43,3092,3093,3094,3095,0,1675,1676,1677,1678,3092,3093,3094,3095,43,3095,3094,3096,3097,0,1678,1677,1679,1680,3095,3094,3096,3097,43,3093,2946,2945,3094,0,1676,1536,1535,1677,3093,2946,2945,3094,43,3094,2945,2949,3096,0,1677,1535,1539,1679,3094,2945,2949,3096,43,3092,3095,3098,3099,0,1675,1678,1681,1682,3092,3095,3098,3099,43,3099,3098,2923,2922,0,1682,1681,1513,1512,3099,3098,2923,2922,43,3095,3097,3100,3098,0,1678,1680,1683,1681,3095,3097,3100,3098,43,3098,3100,2924,2923,0,1681,1683,1514,1513,3098,3100,2924,2923,43,3092,3099,3101,3102,0,1675,1682,1684,1685,3092,3099,3101,3102,43,3102,3101,3087,3082,0,1685,1684,1670,1665,3102,3101,3087,3082,43,3099,2922,2938,3101,0,1682,1512,1528,1684,3099,2922,2938,3101,43,3101,2938,2940,3087,0,1684,1528,1530,1670,3101,2938,2940,3087,43,3092,3102,3103,3093,0,1675,1685,1686,1676,3092,3102,3103,3093,43,3093,3103,2954,2946,0,1676,1686,1544,1536,3093,3103,2954,2946,43,3102,3082,3081,3103,0,1685,1665,1664,1686,3102,3082,3081,3103,43,3103,3081,2955,2954,0,1686,1664,1545,1544,3103,3081,2955,2954,43,3104,3105,3106,3107,0,1687,1688,1689,1690,3104,3105,3106,3107,43,3107,3106,3108,3109,0,1690,1689,1691,1692,3107,3106,3108,3109,43,3105,3110,3111,3106,0,1688,1693,1694,1689,3105,3110,3111,3106,43,3106,3111,2723,3108,0,1689,1694,1332,1691,3106,3111,2723,3108,43,3104,3107,3112,3113,0,1687,1690,1695,1696,3104,3107,3112,3113,43,3113,3112,3114,3115,0,1696,1695,1697,1698,3113,3112,3114,3115,43,3107,3109,3116,3112,0,1690,1692,1699,1695,3107,3109,3116,3112,43,3112,3116,3117,3114,0,1695,1699,1700,1697,3112,3116,3117,3114,43,3104,3113,3118,3119,0,1687,1696,1701,1702,3104,3113,3118,3119,43,3119,3118,3120,3121,0,1702,1701,1703,1704,3119,3118,3120,3121,43,3113,3115,3122,3118,0,1696,1698,1705,1701,3113,3115,3122,3118,43,3118,3122,3123,3120,0,1701,1705,1706,1703,3118,3122,3123,3120,43,3104,3119,3124,3105,0,1687,1702,1707,1688,3104,3119,3124,3105,43,3105,3124,3125,3110,0,1688,1707,1708,1693,3105,3124,3125,3110,43,3119,3121,3126,3124,0,1702,1704,1709,1707,3119,3121,3126,3124,43,3124,3126,3127,3125,0,1707,1709,1710,1708,3124,3126,3127,3125,43,3128,3129,3130,3131,0,1711,1712,1713,1714,3128,3129,3130,3131,43,3131,3130,3132,3133,0,1714,1713,1715,1716,3131,3130,3132,3133,43,3129,3134,3135,3130,0,1712,1717,1718,1713,3129,3134,3135,3130,43,3130,3135,3136,3132,0,1713,1718,1719,1715,3130,3135,3136,3132,43,3128,3131,3137,3138,0,1711,1714,1720,1721,3128,3131,3137,3138,43,3138,3137,3139,3140,0,1721,1720,1722,1723,3138,3137,3139,3140,43,3131,3133,3141,3137,0,1714,1716,1724,1720,3131,3133,3141,3137,43,3137,3141,2698,3139,0,1720,1724,1307,1722,3137,3141,2698,3139,43,3128,3138,3142,3143,0,1711,1721,1725,1726,3128,3138,3142,3143,43,3143,3142,3144,3145,0,1726,1725,1727,1728,3143,3142,3144,3145,43,3138,3140,3146,3142,0,1721,1723,1729,1725,3138,3140,3146,3142,43,3142,3146,2729,3144,0,1725,1729,1338,1727,3142,3146,2729,3144,43,3128,3143,3147,3129,0,1711,1726,1730,1712,3128,3143,3147,3129,43,3129,3147,3148,3134,0,1712,1730,1731,1717,3129,3147,3148,3134,43,3143,3145,3149,3147,0,1726,1728,1732,1730,3143,3145,3149,3147,43,3147,3149,3150,3148,0,1730,1732,1733,1731,3147,3149,3150,3148,43,3151,3152,3153,3154,0,1734,1735,1736,1737,3151,3152,3153,3154,43,3154,3153,3001,2993,0,1737,1736,1584,1576,3154,3153,3001,2993,43,3152,3155,3156,3153,0,1735,1738,1739,1736,3152,3155,3156,3153,43,3153,3156,3002,3001,0,1736,1739,1585,1584,3153,3156,3002,3001,43,3151,3154,3157,3158,0,1734,1737,1740,1741,3151,3154,3157,3158,43,3158,3157,3159,3160,0,1741,1740,1742,1743,3158,3157,3159,3160,43,3154,2993,2992,3157,0,1737,1576,1575,1740,3154,2993,2992,3157,43,3157,2992,2996,3159,0,1740,1575,1579,1742,3157,2992,2996,3159,43,3151,3158,3161,3162,0,1734,1741,1744,1745,3151,3158,3161,3162,43,3162,3161,3163,3164,0,1745,1744,1746,1747,3162,3161,3163,3164,43,3158,3160,3165,3161,0,1741,1743,1748,1744,3158,3160,3165,3161,43,3161,3165,3166,3163,0,1744,1748,1749,1746,3161,3165,3166,3163,43,3151,3162,3167,3152,0,1734,1745,1750,1735,3151,3162,3167,3152,43,3152,3167,3168,3155,0,1735,1750,1751,1738,3152,3167,3168,3155,43,3162,3164,3169,3167,0,1745,1747,1752,1750,3162,3164,3169,3167,43,3167,3169,2805,3168,0,1750,1752,1408,1751,3167,3169,2805,3168,43,3170,3171,3172,3173,0,1753,1754,1755,1756,3170,3171,3172,3173,43,3173,3172,3067,3066,0,1756,1755,1650,1649,3173,3172,3067,3066,43,3171,2857,2865,3172,0,1754,1460,1468,1755,3171,2857,2865,3172,43,3172,2865,2678,3067,0,1755,1468,1287,1650,3172,2865,2678,3067,43,3170,3173,3174,3175,0,1753,1756,1757,1758,3170,3173,3174,3175,43,3175,3174,3156,3155,0,1758,1757,1739,1738,3175,3174,3156,3155,43,3173,3066,3076,3174,0,1756,1649,1659,1757,3173,3066,3076,3174,43,3174,3076,3002,3156,0,1757,1659,1585,1739,3174,3076,3002,3156,43,3170,3175,3176,3177,0,1753,1758,1759,1760,3170,3175,3176,3177,43,3177,3176,2804,2799,0,1760,1759,1407,1402,3177,3176,2804,2799,43,3175,3155,3168,3176,0,1758,1738,1751,1759,3175,3155,3168,3176,43,3176,3168,2805,2804,0,1759,1751,1408,1407,3176,3168,2805,2804,43,3170,3177,3178,3171,0,1753,1760,1761,1754,3170,3177,3178,3171,43,3171,3178,2858,2857,0,1754,1761,1461,1460,3171,3178,2858,2857,43,3177,2799,2798,3178,0,1760,1402,1401,1761,3177,2799,2798,3178,43,3178,2798,2801,2858,0,1761,1401,1404,1461,3178,2798,2801,2858,43,3179,3180,3181,3182,0,1762,1763,1764,1765,3179,3180,3181,3182,43,3182,3181,3053,3046,0,1765,1764,1636,1629,3182,3181,3053,3046,43,3180,2599,2598,3181,0,1763,1214,1213,1764,3180,2599,2598,3181,43,3181,2598,2601,3053,0,1764,1213,1216,1636,3181,2598,2601,3053,43,3179,3182,3183,3184,0,1762,1765,1766,1767,3179,3182,3183,3184,43,3184,3183,2399,2392,0,1767,1766,1075,1068,3184,3183,2399,2392,43,3182,3046,3045,3183,0,1765,1629,1628,1766,3182,3046,3045,3183,43,3183,3045,2400,2399,0,1766,1628,1076,1075,3183,3045,2400,2399,43,3179,3184,3185,3186,0,1762,1767,1768,1769,3179,3184,3185,3186,43,3186,3185,2347,2340,0,1769,1768,1035,1028,3186,3185,2347,2340,43,3184,2392,2391,3185,0,1767,1068,1067,1768,3184,2392,2391,3185,43,3185,2391,2348,2347,0,1768,1067,1036,1035,3185,2391,2348,2347,43,3179,3186,3187,3180,0,1762,1769,1770,1763,3179,3186,3187,3180,43,3180,3187,2604,2599,0,1763,1770,1219,1214,3180,3187,2604,2599,43,3186,2340,2339,3187,0,1769,1028,1027,1770,3186,2340,2339,3187,43,3187,2339,2342,2604,0,1770,1027,1030,1219,3187,2339,2342,2604,43,3188,3189,3190,3191,0,1005,1006,1006,1005,3188,3189,3190,3191,43,3191,3190,3192,3193,0,1005,1006,1006,1005,3191,3190,3192,3193,43,3188,3191,3194,3195,0,1005,1005,1008,1008,3188,3191,3194,3195,43,3195,3194,3196,3197,0,1008,1008,1009,1009,3195,3194,3196,3197,43,3191,3193,3198,3194,0,1005,1005,1008,1008,3191,3193,3198,3194,43,3194,3198,3199,3196,0,1008,1008,1009,1009,3194,3198,3199,3196,43,3188,3195,3200,3201,0,1005,1008,1008,1005,3188,3195,3200,3201,43,3201,3200,2280,2273,0,1005,1008,1008,1005,3201,3200,2280,2273,43,3195,3197,3202,3200,0,1008,1009,1009,1008,3195,3197,3202,3200,43,3200,3202,2281,2280,0,1008,1009,1010,1008,3200,3202,2281,2280,43,3188,3201,3203,3189,0,1005,1005,1006,1006,3188,3201,3203,3189,43,3201,2273,2272,3203,0,1005,1005,1006,1006,3201,2273,2272,3203,43,3203,2272,2275,3204,0,1006,1006,1007,1007,3203,2272,2275,3204,43,3205,3206,3207,3208,0,1771,1022,1022,1772,3205,3206,3207,3208,43,3208,3207,3209,3210,0,1772,1022,1022,1773,3208,3207,3209,3210,43,3206,3197,3196,3207,0,1022,1009,1009,1022,3206,3197,3196,3207,43,3207,3196,3199,3209,0,1022,1009,1009,1022,3207,3196,3199,3209,43,3205,3208,3211,3212,0,1771,1772,1774,1775,3205,3208,3211,3212,43,3212,3211,2588,2587,0,1775,1774,1203,1202,3212,3211,2588,2587,43,3208,3210,3213,3211,0,1772,1773,1776,1774,3208,3210,3213,3211,43,3211,3213,2589,2588,0,1774,1776,1204,1203,3211,3213,2589,2588,43,3205,3212,3214,3215,0,1771,1775,1777,1778,3205,3212,3214,3215,43,3215,3214,2341,2336,0,1778,1777,1029,1024,3215,3214,2341,2336,43,3212,2587,2603,3214,0,1775,1202,1218,1777,3212,2587,2603,3214,43,3214,2603,2342,2341,0,1777,1218,1030,1029,3214,2603,2342,2341,43,3205,3215,3216,3206,0,1771,1778,1022,1022,3205,3215,3216,3206,43,3206,3216,3202,3197,0,1022,1022,1009,1009,3206,3216,3202,3197,43,3215,2336,2335,3216,0,1778,1024,1022,1022,3215,2336,2335,3216,43,3216,2335,2281,3202,0,1022,1022,1010,1009,3216,2335,2281,3202,43,3217,3218,3219,3220,0,1779,1780,1781,1782,3217,3218,3219,3220,43,3220,3219,2697,2689,0,1782,1781,1306,1298,3220,3219,2697,2689,43,3218,3140,3139,3219,0,1780,1723,1722,1781,3218,3140,3139,3219,43,3219,3139,2698,2697,0,1781,1722,1307,1306,3219,3139,2698,2697,43,3217,3220,3221,3222,0,1779,1782,1783,1784,3217,3220,3221,3222,43,3222,3221,3223,3224,0,1784,1783,1785,1786,3222,3221,3223,3224,43,3220,2689,2688,3221,0,1782,1298,1297,1783,3220,2689,2688,3221,43,3221,2688,2692,3223,0,1783,1297,1301,1785,3221,2688,2692,3223,43,3217,3222,3225,3226,0,1779,1784,1787,1788,3217,3222,3225,3226,43,3226,3225,2732,2727,0,1788,1787,1341,1336,3226,3225,2732,2727,43,3222,3224,3227,3225,0,1784,1786,1789,1787,3222,3224,3227,3225,43,3225,3227,2733,2732,0,1787,1789,1342,1341,3225,3227,2733,2732,43,3217,3226,3228,3218,0,1779,1788,1790,1780,3217,3226,3228,3218,43,3218,3228,3146,3140,0,1780,1790,1729,1723,3218,3228,3146,3140,43,3226,2727,2726,3228,0,1788,1336,1335,1790,3226,2727,2726,3228,43,3228,2726,2729,3146,0,1790,1335,1338,1729,3228,2726,2729,3146,43,3229,3230,3231,3232,0,1791,1792,1793,1794,3229,3230,3231,3232,43,3232,3231,2722,2714,0,1794,1793,1331,1323,3232,3231,2722,2714,43,3230,3109,3108,3231,0,1792,1692,1691,1793,3230,3109,3108,3231,43,3231,3108,2723,2722,0,1793,1691,1332,1331,3231,3108,2723,2722,43,3229,3232,3233,3234,0,1791,1794,1795,1796,3229,3232,3233,3234,43,3234,3233,3235,3236,0,1796,1795,1797,1798,3234,3233,3235,3236,43,3232,2714,2713,3233,0,1794,1323,1322,1795,3232,2714,2713,3233,43,3233,2713,2717,3235,0,1795,1322,1326,1797,3233,2713,2717,3235,43,3229,3234,3237,3238,0,1791,1796,1799,1800,3229,3234,3237,3238,43,3238,3237,3239,3240,0,1800,1799,1801,1802,3238,3237,3239,3240,43,3234,3236,3241,3237,0,1796,1798,1803,1799,3234,3236,3241,3237,43,3237,3241,3242,3239,0,1799,1803,1804,1801,3237,3241,3242,3239,43,3229,3238,3243,3230,0,1791,1800,1805,1792,3229,3238,3243,3230,43,3230,3243,3116,3109,0,1792,1805,1699,1692,3230,3243,3116,3109,43,3238,3240,3244,3243,0,1800,1802,1806,1805,3238,3240,3244,3243,43,3243,3244,3117,3116,0,1805,1806,1700,1699,3243,3244,3117,3116,43,3245,3246,3247,3248,0,1807,1808,1809,1810,3245,3246,3247,3248,43,3248,3247,2794,2786,0,1810,1809,1397,1389,3248,3247,2794,2786,43,3246,3249,3250,3247,0,1808,1811,1812,1809,3246,3249,3250,3247,43,3247,3250,2795,2794,0,1809,1812,1398,1397,3247,3250,2795,2794,43,3245,3248,3251,3252,0,1807,1810,1813,1814,3245,3248,3251,3252,43,3252,3251,2983,2977,0,1814,1813,1568,1562,3252,3251,2983,2977,43,3248,2786,2785,3251,0,1810,1389,1388,1813,3248,2786,2785,3251,43,3251,2785,2789,2983,0,1813,1388,1392,1568,3251,2785,2789,2983,43,3245,3252,3253,3254,0,1807,1814,1815,1816,3245,3252,3253,3254,43,3254,3253,2849,2844,0,1816,1815,1452,1447,3254,3253,2849,2844,43,3252,2977,2976,3253,0,1814,1562,1561,1815,3252,2977,2976,3253,43,3253,2976,2850,2849,0,1815,1561,1453,1452,3253,2976,2850,2849,43,3245,3254,3255,3246,0,1807,1816,1817,1808,3245,3254,3255,3246,43,3246,3255,3256,3249,0,1808,1817,1818,1811,3246,3255,3256,3249,43,3254,2844,2843,3255,0,1816,1447,1446,1817,3254,2844,2843,3255,43,3255,2843,2846,3256,0,1817,1446,1449,1818,3255,2843,2846,3256,43,3257,3258,3259,3260,0,1819,1820,1821,1822,3257,3258,3259,3260,43,3260,3259,2839,2831,0,1822,1821,1442,1434,3260,3259,2839,2831,43,3258,3261,3262,3259,0,1820,1823,1824,1821,3258,3261,3262,3259,43,3259,3262,2840,2839,0,1821,1824,1443,1442,3259,3262,2840,2839,43,3257,3260,3263,3264,0,1819,1822,1825,1826,3257,3260,3263,3264,43,3264,3263,2960,2953,0,1826,1825,1550,1543,3264,3263,2960,2953,43,3260,2831,2830,3263,0,1822,1434,1433,1825,3260,2831,2830,3263,43,3263,2830,2834,2960,0,1825,1433,1437,1550,3263,2830,2834,2960,43,3257,3264,3265,3266,0,1819,1826,1827,1828,3257,3264,3265,3266,43,3266,3265,3084,3083,0,1828,1827,1667,1666,3266,3265,3084,3083,43,3264,2953,2952,3265,0,1826,1543,1542,1827,3264,2953,2952,3265,43,3265,2952,2955,3084,0,1827,1542,1545,1667,3265,2952,2955,3084,43,3257,3266,3267,3258,0,1819,1828,1829,1820,3257,3266,3267,3258,43,3258,3267,3268,3261,0,1820,1829,1830,1823,3258,3267,3268,3261,43,3266,3083,3091,3267,0,1828,1666,1674,1829,3266,3083,3091,3267,43,3267,3091,2823,3268,0,1829,1674,1426,1830,3267,3091,2823,3268,43,3269,3270,3271,3272,0,1831,1490,1490,1832,3269,3270,3271,3272,43,3272,3271,2889,2884,0,1832,1490,1490,1487,3272,3271,2889,2884,43,3270,3273,3274,3271,0,1490,1490,1490,1490,3270,3273,3274,3271,43,3271,3274,2890,2889,0,1490,1490,1490,1490,3271,3274,2890,2889,43,3269,3272,3275,3276,0,1831,1832,1833,1834,3269,3272,3275,3276,43,3276,3275,3061,3057,0,1834,1833,1644,1640,3276,3275,3061,3057,43,3272,2884,2883,3275,0,1832,1487,1486,1833,3272,2884,2883,3275,43,3275,2883,2886,3061,0,1833,1486,1489,1644,3275,2883,2886,3061,43,3269,3276,3277,3278,0,1831,1834,1835,1836,3269,3276,3277,3278,43,3278,3277,2910,2903,0,1836,1835,1500,1495,3278,3277,2910,2903,43,3276,3057,3056,3277,0,1834,1640,1639,1835,3276,3057,3056,3277,43,3277,3056,2911,2910,0,1835,1639,1501,1500,3277,3056,2911,2910,43,3269,3278,3279,3270,0,1831,1836,1490,1490,3269,3278,3279,3270,43,3270,3279,3280,3273,0,1490,1490,1490,1490,3270,3279,3280,3273,43,3278,2903,2902,3279,0,1836,1495,1490,1490,3278,2903,2902,3279,43,3279,2902,2905,3280,0,1490,1490,1490,1490,3279,2902,2905,3280,43,3281,3282,3283,3284,0,1837,1838,1839,1840,3281,3282,3283,3284,43,3284,3283,3285,3286,0,1840,1839,1841,1842,3284,3283,3285,3286,43,3282,2928,2927,3283,0,1838,1518,1517,1839,3282,2928,2927,3283,43,3283,2927,2930,3285,0,1839,1517,1520,1841,3283,2927,2930,3285,43,3281,3284,3287,3288,0,1837,1840,1490,1843,3281,3284,3287,3288,43,3288,3287,2898,2897,0,1843,1490,1490,1493,3288,3287,2898,2897,43,3284,3286,3289,3287,0,1840,1842,1490,1490,3284,3286,3289,3287,43,3287,3289,2899,2898,0,1490,1490,1490,1490,3287,3289,2899,2898,43,3281,3288,3290,3291,0,1837,1843,1844,1845,3281,3288,3290,3291,43,3291,3290,3040,3037,0,1845,1844,1623,1620,3291,3290,3040,3037,43,3288,2897,2913,3290,0,1843,1493,1503,1844,3288,2897,2913,3290,43,3290,2913,2915,3040,0,1844,1503,1505,1623,3290,2913,2915,3040,43,3281,3291,3292,3282,0,1837,1845,1846,1838,3281,3291,3292,3282,43,3282,3292,2935,2928,0,1838,1846,1525,1518,3282,3292,2935,2928,43,3291,3037,3036,3292,0,1845,1620,1619,1846,3291,3037,3036,3292,43,3292,3036,2936,2935,0,1846,1619,1526,1525,3292,3036,2936,2935,43,3293,3294,3295,3296,0,1847,1848,1849,1850,3293,3294,3295,3296,43,3296,3295,2963,2959,0,1850,1849,1552,1549,3296,3295,2963,2959,43,3294,3297,3298,3295,0,1848,1848,1849,1849,3294,3297,3298,3295,43,3295,3298,2964,2963,0,1849,1849,1552,1552,3295,3298,2964,2963,43,3293,3296,3299,3300,0,1847,1850,1851,1852,3293,3296,3299,3300,43,3300,3299,2833,2832,0,1852,1851,1436,1435,3300,3299,2833,2832,43,3296,2959,2958,3299,0,1850,1549,1548,1851,3296,2959,2958,3299,43,3299,2958,2834,2833,0,1851,1548,1437,1436,3299,2958,2834,2833,43,3293,3300,3301,3302,0,1847,1852,1853,1854,3293,3300,3301,3302,43,3302,3301,2978,2970,0,1854,1853,1563,1558,3302,3301,2978,2970,43,3300,2832,2848,3301,0,1852,1435,1451,1853,3300,2832,2848,3301,43,3301,2848,2850,2978,0,1853,1451,1453,1563,3301,2848,2850,2978,43,3293,3302,3303,3294,0,1847,1854,1855,1848,3293,3302,3303,3294,43,3294,3303,3304,3297,0,1848,1855,1855,1848,3294,3303,3304,3297,43,3302,2970,2969,3303,0,1854,1558,1557,1855,3302,2970,2969,3303,43,3303,2969,2973,3304,0,1855,1557,1557,1855,3303,2969,2973,3304,43,3305,3306,3307,3308,0,1856,1857,1858,1859,3305,3306,3307,3308,43,3308,3307,2986,2982,0,1859,1858,1570,1567,3308,3307,2986,2982,43,3306,3309,3310,3307,0,1857,1860,1858,1858,3306,3309,3310,3307,43,3307,3310,2987,2986,0,1858,1858,1570,1570,3307,3310,2987,2986,43,3305,3308,3311,3312,0,1856,1859,1861,1862,3305,3308,3311,3312,43,3312,3311,2788,2787,0,1862,1861,1391,1390,3312,3311,2788,2787,43,3308,2982,2981,3311,0,1859,1567,1566,1861,3308,2982,2981,3311,43,3311,2981,2789,2788,0,1861,1566,1392,1391,3311,2981,2789,2788,43,3305,3312,3313,3314,0,1856,1862,1863,1864,3305,3312,3313,3314,43,3314,3313,3169,3164,0,1864,1863,1752,1747,3314,3313,3169,3164,43,3312,2787,2803,3313,0,1862,1390,1406,1863,3312,2787,2803,3313,43,3313,2803,2805,3169,0,1863,1406,1408,1752,3313,2803,2805,3169,43,3305,3314,3315,3306,0,1856,1864,1865,1857,3305,3314,3315,3306,43,3306,3315,3316,3309,0,1857,1865,1866,1860,3306,3315,3316,3309,43,3314,3164,3163,3315,0,1864,1747,1746,1865,3314,3164,3163,3315,43,3315,3163,3166,3316,0,1865,1746,1749,1866,3315,3163,3166,3316,43,3317,3318,3319,3320,0,1867,1868,1869,1870,3317,3318,3319,3320,43,3320,3319,3030,3029,0,1870,1869,1613,1612,3320,3319,3030,3029,43,3318,2593,2592,3319,0,1868,1208,1207,1869,3318,2593,2592,3319,43,3319,2592,2595,3030,0,1869,1207,1210,1613,3319,2592,2595,3030,43,3317,3320,3321,3322,0,1867,1870,1871,1872,3317,3320,3321,3322,43,3322,3321,2914,2909,0,1872,1871,1504,1499,3322,3321,2914,2909,43,3320,3029,3039,3321,0,1870,1612,1622,1871,3320,3029,3039,3321,43,3321,3039,2915,2914,0,1871,1622,1505,1504,3321,3039,2915,2914,43,3317,3322,3323,3324,0,1867,1872,1873,1874,3317,3322,3323,3324,43,3324,3323,3058,3052,0,1874,1873,1641,1635,3324,3323,3058,3052,43,3322,2909,2908,3323,0,1872,1499,1498,1873,3322,2909,2908,3323,43,3323,2908,2911,3058,0,1873,1498,1501,1641,3323,2908,2911,3058,43,3317,3324,3325,3318,0,1867,1874,1875,1868,3317,3324,3325,3318,43,3318,3325,2600,2593,0,1868,1875,1215,1208,3318,3325,2600,2593,43,3324,3052,3051,3325,0,1874,1635,1634,1875,3324,3052,3051,3325,43,3325,3051,2601,2600,0,1875,1634,1216,1215,3325,3051,2601,2600,43,3326,3327,3328,3329,0,1876,1877,1878,1879,3326,3327,3328,3329,43,3329,3328,2405,2398,0,1879,1878,1081,1074,3329,3328,2405,2398,43,3327,3071,3070,3328,0,1877,1654,1653,1878,3327,3071,3070,3328,43,3328,3070,2406,2405,0,1878,1653,1082,1081,3328,3070,2406,2405,43,3326,3329,3330,3331,0,1876,1879,1880,1881,3326,3329,3330,3331,43,3331,3330,3048,3047,0,1881,1880,1631,1630,3331,3330,3048,3047,43,3329,2398,2397,3330,0,1879,1074,1073,1880,3329,2398,2397,3330,43,3330,2397,2400,3048,0,1880,1073,1076,1631,3330,2397,2400,3048,43,3326,3331,3332,3333,0,1876,1881,1882,1883,3326,3331,3332,3333,43,3333,3332,2885,2878,0,1883,1882,1488,1481,3333,3332,2885,2878,43,3331,3047,3060,3332,0,1881,1630,1643,1882,3331,3047,3060,3332,43,3332,3060,2886,2885,0,1882,1643,1489,1488,3332,3060,2886,2885,43,3326,3333,3334,3327,0,1876,1883,1884,1877,3326,3333,3334,3327,43,3327,3334,3074,3071,0,1877,1884,1657,1654,3327,3334,3074,3071,43,3333,2878,2877,3334,0,1883,1481,1480,1884,3333,2878,2877,3334,43,3334,2877,2880,3074,0,1884,1480,1483,1657,3334,2877,2880,3074,43,3335,3336,3337,3338,0,1885,1886,1887,1888,3335,3336,3337,3338,43,3338,3337,3111,3110,0,1888,1887,1694,1693,3338,3337,3111,3110,43,3336,2721,2720,3337,0,1886,1330,1329,1887,3336,2721,2720,3337,43,3337,2720,2723,3111,0,1887,1329,1332,1694,3337,2720,2723,3111,43,3335,3338,3339,3340,0,1885,1888,1889,1890,3335,3338,3339,3340,43,3340,3339,3341,3342,0,1890,1889,1891,1892,3340,3339,3341,3342,43,3338,3110,3125,3339,0,1888,1693,1708,1889,3338,3110,3125,3339,43,3339,3125,3127,3341,0,1889,1708,1710,1891,3339,3125,3127,3341,43,3335,3340,3343,3344,0,1885,1890,1893,1894,3335,3340,3343,3344,43,3344,3343,3149,3145,0,1894,1893,1732,1728,3344,3343,3149,3145,43,3340,3342,3345,3343,0,1890,1892,1895,1893,3340,3342,3345,3343,43,3343,3345,3150,3149,0,1893,1895,1733,1732,3343,3345,3150,3149,43,3335,3344,3346,3336,0,1885,1894,1896,1886,3335,3344,3346,3336,43,3336,3346,2728,2721,0,1886,1896,1337,1330,3336,3346,2728,2721,43,3344,3145,3144,3346,0,1894,1728,1727,1896,3344,3145,3144,3346,43,3346,3144,2729,2728,0,1896,1727,1338,1337,3346,3144,2729,2728,43,3347,3348,3349,3350,0,1897,1898,1899,1900,3347,3348,3349,3350,43,3350,3349,3141,3133,0,1900,1899,1724,1716,3350,3349,3141,3133,43,3348,2696,2695,3349,0,1898,1305,1304,1899,3348,2696,2695,3349,43,3349,2695,2698,3141,0,1899,1304,1307,1724,3349,2695,2698,3141,43,3347,3350,3351,3352,0,1897,1900,1901,1902,3347,3350,3351,3352,43,3352,3351,3353,3354,0,1902,1901,1903,1904,3352,3351,3353,3354,43,3350,3133,3132,3351,0,1900,1716,1715,1901,3350,3133,3132,3351,43,3351,3132,3136,3353,0,1901,1715,1719,1903,3351,3132,3136,3353,43,3347,3352,3355,3356,0,1897,1902,1905,1906,3347,3352,3355,3356,43,3356,3355,3357,3358,0,1906,1905,1907,1908,3356,3355,3357,3358,43,3352,3354,3359,3355,0,1902,1904,1909,1905,3352,3354,3359,3355,43,3355,3359,3360,3357,0,1905,1909,1910,1907,3355,3359,3360,3357,43,3347,3356,3361,3348,0,1897,1906,1911,1898,3347,3356,3361,3348,43,3348,3361,2703,2696,0,1898,1911,1312,1305,3348,3361,2703,2696,43,3356,3358,3362,3361,0,1906,1908,1912,1911,3356,3358,3362,3361,43,3361,3362,2704,2703,0,1911,1912,1313,1312,3361,3362,2704,2703,43,3363,3364,3365,3366,0,1913,1914,1915,1916,3363,3364,3365,3366,43,3366,3365,2691,2690,0,1916,1915,1300,1299,3366,3365,2691,2690,43,3364,3367,3368,3365,0,1914,1917,1918,1915,3364,3367,3368,3365,43,3365,3368,2692,2691,0,1915,1918,1301,1300,3365,3368,2692,2691,43,3363,3366,3369,3370,0,1913,1916,1919,1920,3363,3366,3369,3370,43,3370,3369,3371,3372,0,1920,1919,1921,1922,3370,3369,3371,3372,43,3366,2690,2706,3369,0,1916,1299,1315,1919,3366,2690,2706,3369,43,3369,2706,2708,3371,0,1919,1315,1317,1921,3369,2706,2708,3371,43,3363,3370,3373,3374,0,1913,1920,1923,1924,3363,3370,3373,3374,43,3374,3373,2873,2872,0,1924,1923,1476,1475,3374,3373,2873,2872,43,3370,3372,3375,3373,0,1920,1922,1925,1923,3370,3372,3375,3373,43,3373,3375,2874,2873,0,1923,1925,1477,1476,3373,3375,2874,2873,43,3363,3374,3376,3364,0,1913,1924,1490,1914,3363,3374,3376,3364,43,3364,3376,3377,3367,0,1914,1490,1490,1917,3364,3376,3377,3367,43,3374,2872,2888,3376,0,1924,1475,1490,1490,3374,2872,2888,3376,43,3376,2888,2890,3377,0,1490,1490,1490,1490,3376,2888,2890,3377,43,3378,3379,3380,3381,0,1926,1927,1928,1929,3378,3379,3380,3381,43,3381,3380,3227,3224,0,1929,1928,1789,1786,3381,3380,3227,3224,43,3379,3382,3383,3380,0,1927,1930,1931,1928,3379,3382,3383,3380,43,3380,3383,2733,3227,0,1928,1931,1342,1789,3380,3383,2733,3227,43,3378,3381,3384,3385,0,1926,1929,1932,1933,3378,3381,3384,3385,43,3385,3384,3368,3367,0,1933,1932,1918,1917,3385,3384,3368,3367,43,3381,3224,3223,3384,0,1929,1786,1785,1932,3381,3224,3223,3384,43,3384,3223,2692,3368,0,1932,1785,1301,1918,3384,3223,2692,3368,43,3378,3385,3386,3387,0,1926,1933,1490,1490,3378,3385,3386,3387,43,3387,3386,3274,3273,0,1490,1490,1490,1490,3387,3386,3274,3273,43,3385,3367,3377,3386,0,1933,1917,1490,1490,3385,3367,3377,3386,43,3386,3377,2890,3274,0,1490,1490,1490,1490,3386,3377,2890,3274,43,3378,3387,3388,3379,0,1926,1490,1490,1927,3378,3387,3388,3379,43,3379,3388,3389,3382,0,1927,1490,1490,1930,3379,3388,3389,3382,43,3387,3273,3280,3388,0,1490,1490,1490,1490,3387,3273,3280,3388,43,3388,3280,2905,3389,0,1490,1490,1490,1490,3388,3280,2905,3389,43,3390,3391,3392,3393,0,1934,1935,1936,1937,3390,3391,3392,3393,43,3393,3392,2716,2715,0,1937,1936,1325,1324,3393,3392,2716,2715,43,3391,3394,3395,3392,0,1935,1938,1939,1936,3391,3394,3395,3392,43,3392,3395,2717,2716,0,1936,1939,1326,1325,3392,3395,2717,2716,43,3390,3393,3396,3397,0,1934,1937,1940,1941,3390,3393,3396,3397,43,3397,3396,3383,3382,0,1941,1940,1931,1930,3397,3396,3383,3382,43,3393,2715,2731,3396,0,1937,1324,1340,1940,3393,2715,2731,3396,43,3396,2731,2733,3383,0,1940,1340,1342,1931,3396,2731,2733,3383,43,3390,3397,3398,3399,0,1934,1941,1490,1490,3390,3397,3398,3399,43,3399,3398,2904,2896,0,1490,1490,1490,1490,3399,3398,2904,2896,43,3397,3382,3389,3398,0,1941,1930,1490,1490,3397,3382,3389,3398,43,3398,3389,2905,2904,0,1490,1490,1490,1490,3398,3389,2905,2904,43,3390,3399,3400,3391,0,1934,1490,1490,1935,3390,3399,3400,3391,43,3391,3400,3401,3394,0,1935,1490,1490,1938,3391,3400,3401,3394,43,3399,2896,2895,3400,0,1490,1490,1490,1490,3399,2896,2895,3400,43,3400,2895,2899,3401,0,1490,1490,1490,1490,3400,2895,2899,3401,43,3402,3403,3404,3405,0,1942,1943,1944,1945,3402,3403,3404,3405,43,3405,3404,3241,3236,0,1945,1944,1803,1798,3405,3404,3241,3236,43,3403,3406,3407,3404,0,1943,1946,1947,1944,3403,3406,3407,3404,43,3404,3407,3242,3241,0,1944,1947,1804,1803,3404,3407,3242,3241,43,3402,3405,3408,3409,0,1942,1945,1948,1949,3402,3405,3408,3409,43,3409,3408,3395,3394,0,1949,1948,1939,1938,3409,3408,3395,3394,43,3405,3236,3235,3408,0,1945,1798,1797,1948,3405,3236,3235,3408,43,3408,3235,2717,3395,0,1948,1797,1326,1939,3408,3235,2717,3395,43,3402,3409,3410,3411,0,1942,1949,1490,1950,3402,3409,3410,3411,43,3411,3410,3289,3286,0,1950,1490,1490,1842,3411,3410,3289,3286,43,3409,3394,3401,3410,0,1949,1938,1490,1490,3409,3394,3401,3410,43,3410,3401,2899,3289,0,1490,1490,1490,1490,3410,3401,2899,3289,43,3402,3411,3412,3403,0,1942,1950,1951,1943,3402,3411,3412,3403,43,3403,3412,3413,3406,0,1943,1951,1952,1946,3403,3412,3413,3406,43,3411,3286,3285,3412,0,1950,1842,1841,1951,3411,3286,3285,3412,43,3412,3285,2930,3413,0,1951,1841,1520,1952,3412,3285,2930,3413,43,3414,3415,3416,3417,0,1953,1954,1955,1956,3414,3415,3416,3417,43,3417,3416,3244,3240,0,1956,1955,1806,1802,3417,3416,3244,3240,43,3415,3418,3419,3416,0,1954,1957,1958,1955,3415,3418,3419,3416,43,3416,3419,3117,3244,0,1955,1958,1700,1806,3416,3419,3117,3244,43,3414,3417,3420,3421,0,1953,1956,1959,1960,3414,3417,3420,3421,43,3421,3420,3407,3406,0,1960,1959,1947,1946,3421,3420,3407,3406,43,3417,3240,3239,3420,0,1956,1802,1801,1959,3417,3240,3239,3420,43,3420,3239,3242,3407,0,1959,1801,1804,1947,3420,3239,3242,3407,43,3414,3421,3422,3423,0,1953,1960,1961,1962,3414,3421,3422,3423,43,3423,3422,2929,2921,0,1962,1961,1519,1511,3423,3422,2929,2921,43,3421,3406,3413,3422,0,1960,1946,1952,1961,3421,3406,3413,3422,43,3422,3413,2930,2929,0,1961,1952,1520,1519,3422,3413,2930,2929,43,3414,3423,3424,3415,0,1953,1962,1963,1954,3414,3423,3424,3415,43,3415,3424,3425,3418,0,1954,1963,1964,1957,3415,3424,3425,3418,43,3423,2921,2920,3424,0,1962,1511,1510,1963,3423,2921,2920,3424,43,3424,2920,2924,3425,0,1963,1510,1514,1964,3424,2920,2924,3425,43,3426,3427,3428,3429,0,1965,1966,1967,1968,3426,3427,3428,3429,43,3429,3428,3122,3115,0,1968,1967,1705,1698,3429,3428,3122,3115,43,3427,3430,3431,3428,0,1966,1969,1970,1967,3427,3430,3431,3428,43,3428,3431,3123,3122,0,1967,1970,1706,1705,3428,3431,3123,3122,43,3426,3429,3432,3433,0,1965,1968,1971,1972,3426,3429,3432,3433,43,3433,3432,3419,3418,0,1972,1971,1958,1957,3433,3432,3419,3418,43,3429,3115,3114,3432,0,1968,1698,1697,1971,3429,3115,3114,3432,43,3432,3114,3117,3419,0,1971,1697,1700,1958,3432,3114,3117,3419,43,3426,3433,3434,3435,0,1965,1972,1973,1974,3426,3433,3434,3435,43,3435,3434,3100,3097,0,1974,1973,1683,1680,3435,3434,3100,3097,43,3433,3418,3425,3434,0,1972,1957,1964,1973,3433,3418,3425,3434,43,3434,3425,2924,3100,0,1973,1964,1514,1683,3434,3425,2924,3100,43,3426,3435,3436,3427,0,1965,1974,1975,1966,3426,3435,3436,3427,43,3427,3436,3437,3430,0,1966,1975,1976,1969,3427,3436,3437,3430,43,3435,3097,3096,3436,0,1974,1680,1679,1975,3435,3097,3096,3436,43,3436,3096,2949,3437,0,1975,1679,1539,1976,3436,3096,2949,3437,43,3438,3439,3440,3441,0,1977,1978,1979,1980,3438,3439,3440,3441,43,3441,3440,3126,3121,0,1980,1979,1709,1704,3441,3440,3126,3121,43,3439,3442,3443,3440,0,1978,1981,1982,1979,3439,3442,3443,3440,43,3440,3443,3127,3126,0,1979,1982,1710,1709,3440,3443,3127,3126,43,3438,3441,3444,3445,0,1977,1980,1983,1984,3438,3441,3444,3445,43,3445,3444,3431,3430,0,1984,1983,1970,1969,3445,3444,3431,3430,43,3441,3121,3120,3444,0,1980,1704,1703,1983,3441,3121,3120,3444,43,3444,3120,3123,3431,0,1983,1703,1706,1970,3444,3120,3123,3431,43,3438,3445,3446,3447,0,1977,1984,1985,1986,3438,3445,3446,3447,43,3447,3446,2948,2947,0,1986,1985,1538,1537,3447,3446,2948,2947,43,3445,3430,3437,3446,0,1984,1969,1976,1985,3445,3430,3437,3446,43,3446,3437,2949,2948,0,1985,1976,1539,1538,3446,3437,2949,2948,43,3438,3447,3448,3439,0,1977,1986,1551,1978,3438,3447,3448,3439,43,3439,3448,3449,3442,0,1978,1551,1552,1981,3439,3448,3449,3442,43,3447,2947,2962,3448,0,1986,1537,1551,1551,3447,2947,2962,3448,43,3448,2962,2964,3449,0,1551,1551,1552,1552,3448,2962,2964,3449,43,3450,3451,3452,3453,0,1987,1988,1989,1990,3450,3451,3452,3453,43,3453,3452,3345,3342,0,1990,1989,1895,1892,3453,3452,3345,3342,43,3451,3454,3455,3452,0,1988,1991,1992,1989,3451,3454,3455,3452,43,3452,3455,3150,3345,0,1989,1992,1733,1895,3452,3455,3150,3345,43,3450,3453,3456,3457,0,1987,1990,1993,1994,3450,3453,3456,3457,43,3457,3456,3443,3442,0,1994,1993,1982,1981,3457,3456,3443,3442,43,3453,3342,3341,3456,0,1990,1892,1891,1993,3453,3342,3341,3456,43,3456,3341,3127,3443,0,1993,1891,1710,1982,3456,3341,3127,3443,43,3450,3457,3458,3459,0,1987,1994,1849,1848,3450,3457,3458,3459,43,3459,3458,3298,3297,0,1848,1849,1849,1848,3459,3458,3298,3297,43,3457,3442,3449,3458,0,1994,1981,1552,1849,3457,3442,3449,3458,43,3458,3449,2964,3298,0,1849,1552,1552,1849,3458,3449,2964,3298,43,3450,3459,3460,3451,0,1987,1848,1855,1988,3450,3459,3460,3451,43,3451,3460,3461,3454,0,1988,1855,1557,1991,3451,3460,3461,3454,43,3459,3297,3304,3460,0,1848,1848,1855,1855,3459,3297,3304,3460,43,3460,3304,2973,3461,0,1855,1855,1557,1557,3460,3304,2973,3461,43,3462,3463,3464,3465,0,1995,1996,1997,1998,3462,3463,3464,3465,43,3465,3464,3135,3134,0,1998,1997,1718,1717,3465,3464,3135,3134,43,3463,3466,3467,3464,0,1996,1999,2000,1997,3463,3466,3467,3464,43,3464,3467,3136,3135,0,1997,2000,1719,1718,3464,3467,3136,3135,43,3462,3465,3468,3469,0,1995,1998,2001,2002,3462,3465,3468,3469,43,3469,3468,3455,3454,0,2002,2001,1992,1991,3469,3468,3455,3454,43,3465,3134,3148,3468,0,1998,1717,1731,2001,3465,3134,3148,3468,43,3468,3148,3150,3455,0,2001,1731,1733,1992,3468,3148,3150,3455,43,3462,3469,3470,3471,0,1995,2002,1555,1554,3462,3469,3470,3471,43,3471,3470,2972,2971,0,1554,1555,1555,1554,3471,3470,2972,2971,43,3469,3454,3461,3470,0,2002,1991,1557,1555,3469,3454,3461,3470,43,3470,3461,2973,2972,0,1555,1557,1557,1555,3470,3461,2973,2972,43,3462,3471,3472,3463,0,1995,1554,1569,1996,3462,3471,3472,3463,43,3463,3472,3473,3466,0,1996,1569,1570,1999,3463,3472,3473,3466,43,3471,2971,2985,3472,0,1554,1554,1569,1569,3471,2971,2985,3472,43,3472,2985,2987,3473,0,1569,1569,1570,1570,3472,2985,2987,3473,43,3474,3475,3476,3477,0,2003,2004,2005,2006,3474,3475,3476,3477,43,3477,3476,3359,3354,0,2006,2005,1909,1904,3477,3476,3359,3354,43,3475,3478,3479,3476,0,2004,2007,2008,2005,3475,3478,3479,3476,43,3476,3479,3360,3359,0,2005,2008,1910,1909,3476,3479,3360,3359,43,3474,3477,3480,3481,0,2003,2006,2009,2010,3474,3477,3480,3481,43,3481,3480,3467,3466,0,2010,2009,2000,1999,3481,3480,3467,3466,43,3477,3354,3353,3480,0,2006,1904,1903,2009,3477,3354,3353,3480,43,3480,3353,3136,3467,0,2009,1903,1719,2000,3480,3353,3136,3467,43,3474,3481,3482,3483,0,2003,2010,1858,2011,3474,3481,3482,3483,43,3483,3482,3310,3309,0,2011,1858,1858,1860,3483,3482,3310,3309,43,3481,3466,3473,3482,0,2010,1999,1570,1858,3481,3466,3473,3482,43,3482,3473,2987,3310,0,1858,1570,1570,1858,3482,3473,2987,3310,43,3474,3483,3484,3475,0,2003,2011,2012,2004,3474,3483,3484,3475,43,3475,3484,3485,3478,0,2004,2012,2013,2007,3475,3484,3485,3478,43,3483,3309,3316,3484,0,2011,1860,1866,2012,3483,3309,3316,3484,43,3484,3316,3166,3485,0,2012,1866,1749,2013,3484,3316,3166,3485,43,3486,3487,3488,3489,0,2014,2015,2016,2017,3486,3487,3488,3489,43,3489,3488,3362,3358,0,2017,2016,1912,1908,3489,3488,3362,3358,43,3487,3490,3491,3488,0,2015,2018,2019,2016,3487,3490,3491,3488,43,3488,3491,2704,3362,0,2016,2019,1313,1912,3488,3491,2704,3362,43,3486,3489,3492,3493,0,2014,2017,2020,2021,3486,3489,3492,3493,43,3493,3492,3479,3478,0,2021,2020,2008,2007,3493,3492,3479,3478,43,3489,3358,3357,3492,0,2017,1908,1907,2020,3489,3358,3357,3492,43,3492,3357,3360,3479,0,2020,1907,1910,2008,3492,3357,3360,3479,43,3486,3493,3494,3495,0,2014,2021,2022,2023,3486,3493,3494,3495,43,3495,3494,3165,3160,0,2023,2022,1748,1743,3495,3494,3165,3160,43,3493,3478,3485,3494,0,2021,2007,2013,2022,3493,3478,3485,3494,43,3494,3485,3166,3165,0,2022,2013,1749,1748,3494,3485,3166,3165,43,3486,3495,3496,3487,0,2014,2023,2024,2015,3486,3495,3496,3487,43,3487,3496,3497,3490,0,2015,2024,2025,2018,3487,3496,3497,3490,43,3495,3160,3159,3496,0,2023,1743,1742,2024,3495,3160,3159,3496,43,3496,3159,2996,3497,0,2024,1742,1579,2025,3496,3159,2996,3497,43,3498,3499,3500,3501,0,2026,2027,2028,2029,3498,3499,3500,3501,43,3501,3500,2707,2702,0,2029,2028,1316,1311,3501,3500,2707,2702,43,3499,3372,3371,3500,0,2027,1922,1921,2028,3499,3372,3371,3500,43,3500,3371,2708,2707,0,2028,1921,1317,1316,3500,3371,2708,2707,43,3498,3501,3502,3503,0,2026,2029,2030,2031,3498,3501,3502,3503,43,3503,3502,3491,3490,0,2031,2030,2019,2018,3503,3502,3491,3490,43,3501,2702,2701,3502,0,2029,1311,1310,2030,3501,2702,2701,3502,43,3502,2701,2704,3491,0,2030,1310,1313,2019,3502,2701,2704,3491,43,3498,3503,3504,3505,0,2026,2031,2032,2033,3498,3503,3504,3505,43,3505,3504,2995,2994,0,2033,2032,1578,1577,3505,3504,2995,2994,43,3503,3490,3497,3504,0,2031,2018,2025,2032,3503,3490,3497,3504,43,3504,3497,2996,2995,0,2032,2025,1579,1578,3504,3497,2996,2995,43,3498,3505,3506,3499,0,2026,2033,2034,2027,3498,3505,3506,3499,43,3499,3506,3375,3372,0,2027,2034,1925,1922,3499,3506,3375,3372,43,3505,2994,3007,3506,0,2033,1577,1590,2034,3505,2994,3007,3506,43,3506,3007,2874,3375,0,2034,1590,1477,1925,3506,3007,2874,3375,43,3507,3508,3509,3510,0,2035,2036,2037,2038,3507,3508,3509,3510,43,3510,3509,2672,2671,0,2038,2037,1281,1280,3510,3509,2672,2671,43,3508,2404,2403,3509,0,2036,1080,1079,2037,3508,2404,2403,3509,43,3509,2403,2406,2672,0,2037,1079,1082,1281,3509,2403,2406,2672,43,3507,3510,3511,3512,0,2035,2038,2039,2040,3507,3510,3511,3512,43,3512,3511,3513,3514,0,2040,2039,2041,2042,3512,3511,3513,3514,43,3510,2671,2683,3511,0,2038,1280,1292,2039,3510,2671,2683,3511,43,3511,2683,2664,3513,0,2039,1292,1273,2041,3511,2683,2664,3513,43,3507,3512,3515,3516,0,2035,2040,2043,2044,3507,3512,3515,3516,43,3516,3515,1321,1313,0,2044,2043,577,569,3516,3515,1321,1313,43,3512,3514,3517,3515,0,2040,2042,2045,2043,3512,3514,3517,3515,43,3515,3517,1322,1321,0,2043,2045,578,577,3515,3517,1322,1321,43,3507,3516,3518,3508,0,2035,2044,2046,2036,3507,3516,3518,3508,43,3508,3518,2409,2404,0,2036,2046,1085,1080,3508,3518,2409,2404,43,3516,1313,1312,3518,0,2044,569,568,2046,3516,1313,1312,3518,43,3518,1312,1316,2409,0,2046,568,572,1085,3518,1312,1316,2409,43,3519,3520,3521,3522,0,2047,2048,2049,2050,3519,3520,3521,3522,43,3522,3521,1327,1320,0,2050,2049,583,576,3522,3521,1327,1320,43,3520,3523,3524,3521,0,2048,2051,2052,2049,3520,3523,3524,3521,43,3521,3524,1328,1327,0,2049,2052,584,583,3521,3524,1328,1327,43,3519,3522,3525,3526,0,2047,2050,2053,2054,3519,3522,3525,3526,43,3526,3525,3517,3514,0,2054,2053,2045,2042,3526,3525,3517,3514,43,3522,1320,1319,3525,0,2050,576,575,2053,3522,1320,1319,3525,43,3525,1319,1322,3517,0,2053,575,578,2045,3525,1319,1322,3517,43,3519,3526,3527,3528,0,2047,2054,2055,2056,3519,3526,3527,3528,43,3528,3527,2663,2660,0,2056,2055,1272,1269,3528,3527,2663,2660,43,3526,3514,3513,3527,0,2054,2042,2041,2055,3526,3514,3513,3527,43,3527,3513,2664,2663,0,2055,2041,1273,1272,3527,3513,2664,2663,43,3519,3528,3529,3520,0,2047,2056,2057,2048,3519,3528,3529,3520,43,3520,3529,3530,3523,0,2048,2057,2058,2051,3520,3529,3530,3523,43,3528,2660,2659,3529,0,2056,1269,1268,2057,3528,2660,2659,3529,43,3529,2659,2648,3530,0,2057,1268,1257,2058,3529,2659,2648,3530,43,3531,3532,3533,3534,0,2059,2060,2061,2062,3531,3532,3533,3534,43,3534,3533,1498,1497,0,2062,2061,761,760,3534,3533,1498,1497,43,3532,3535,3536,3533,0,2060,2063,2064,2061,3532,3535,3536,3533,43,3533,3536,1499,1498,0,2061,2064,762,761,3533,3536,1499,1498,43,3531,3534,3537,3538,0,2059,2062,2065,2066,3531,3534,3537,3538,43,3538,3537,3524,3523,0,2066,2065,2052,2051,3538,3537,3524,3523,43,3534,1497,1510,3537,0,2062,760,773,2065,3534,1497,1510,3537,43,3537,1510,1328,3524,0,2065,773,584,2052,3537,1510,1328,3524,43,3531,3538,3539,3540,0,2059,2066,2067,2068,3531,3538,3539,3540,43,3540,3539,2647,2642,0,2068,2067,1256,1251,3540,3539,2647,2642,43,3538,3523,3530,3539,0,2066,2051,2058,2067,3538,3523,3530,3539,43,3539,3530,2648,2647,0,2067,2058,1257,1256,3539,3530,2648,2647,43,3531,3540,3541,3532,0,2059,2068,2069,2060,3531,3540,3541,3532,43,3532,3541,3542,3535,0,2060,2069,2070,2063,3532,3541,3542,3535,43,3540,2642,2641,3541,0,2068,1251,1250,2069,3540,2642,2641,3541,43,3541,2641,2644,3542,0,2069,1250,1253,2070,3541,2641,2644,3542,43,3543,3544,3545,3546,0,2071,2072,2073,2074,3543,3544,3545,3546,43,3546,3545,3547,3548,0,2074,2073,2075,2076,3546,3545,3547,3548,43,3544,2586,2585,3545,0,2072,1201,1200,2073,3544,2586,2585,3545,43,3545,2585,2589,3547,0,2073,1200,1204,2075,3545,2585,2589,3547,43,3543,3546,3549,3550,0,2071,2074,2077,2078,3543,3546,3549,3550,43,3550,3549,2429,2425,0,2078,2077,1105,1101,3550,3549,2429,2425,43,3546,3548,3551,3549,0,2074,2076,2079,2077,3546,3548,3551,3549,43,3549,3551,2370,2429,0,2077,2079,1051,1105,3549,3551,2370,2429,43,3543,3550,3552,3553,0,2071,2078,2080,2081,3543,3550,3552,3553,43,3553,3552,3033,3028,0,2081,2080,1616,1611,3553,3552,3033,3028,43,3550,2425,2424,3552,0,2078,1101,1100,2080,3550,2425,2424,3552,43,3552,2424,2427,3033,0,2080,1100,1103,1616,3552,2424,2427,3033,43,3543,3553,3554,3544,0,2071,2081,2082,2072,3543,3553,3554,3544,43,3544,3554,2594,2586,0,2072,2082,1209,1201,3544,3554,2594,2586,43,3553,3028,3027,3554,0,2081,1611,1610,2082,3553,3028,3027,3554,43,3554,3027,2595,2594,0,2082,1610,1210,1209,3554,3027,2595,2594,43,3555,3556,3557,3558,0,2083,2084,1022,1022,3555,3556,3557,3558,43,3558,3557,3559,3560,0,1022,1022,1010,1009,3558,3557,3559,3560,43,3556,3210,3209,3557,0,2084,1773,1022,1022,3556,3210,3209,3557,43,3557,3209,3199,3559,0,1022,1022,1009,1010,3557,3209,3199,3559,43,3555,3558,3561,3562,0,2083,1022,1022,2085,3555,3558,3561,3562,43,3562,3561,2356,2355,0,2085,1022,1022,1039,3562,3561,2356,2355,43,3558,3560,3563,3561,0,1022,1009,1010,1022,3558,3560,3563,3561,43,3561,3563,2310,2356,0,1022,1010,1009,1022,3561,3563,2310,2356,43,3555,3562,3564,3565,0,2083,2085,2086,2087,3555,3562,3564,3565,43,3565,3564,3551,3548,0,2087,2086,2079,2076,3565,3564,3551,3548,43,3562,2355,2368,3564,0,2085,1039,1049,2086,3562,2355,2368,3564,43,3564,2368,2370,3551,0,2086,1049,1051,2079,3564,2368,2370,3551,43,3555,3565,3566,3556,0,2083,2087,2088,2084,3555,3565,3566,3556,43,3556,3566,3213,3210,0,2084,2088,1776,1773,3556,3566,3213,3210,43,3565,3548,3547,3566,0,2087,2076,2075,2088,3565,3548,3547,3566,43,3566,3547,2589,3213,0,2088,2075,1204,1776,3566,3547,2589,3213,43,3567,3568,3569,3570,0,1005,1005,1006,1006,3567,3568,3569,3570,43,3568,3193,3192,3569,0,1005,1005,1006,1006,3568,3193,3192,3569,43,3567,3570,3571,3572,0,1005,1006,1006,1005,3567,3570,3571,3572,43,3572,3571,2293,2292,0,1005,1006,1006,1005,3572,3571,2293,2292,43,3567,3572,3573,3574,0,1005,1005,1008,1008,3567,3572,3573,3574,43,3574,3573,3563,3560,0,1008,1008,1010,1009,3574,3573,3563,3560,43,3572,2292,2308,3573,0,1005,1005,1008,1008,3572,2292,2308,3573,43,3573,2308,2310,3563,0,1008,1008,1009,1010,3573,2308,2310,3563,43,3567,3574,3575,3568,0,1005,1008,1008,1005,3567,3574,3575,3568,43,3568,3575,3198,3193,0,1005,1008,1008,1005,3568,3575,3198,3193,43,3574,3560,3559,3575,0,1008,1009,1010,1008,3574,3560,3559,3575,43,3575,3559,3199,3198,0,1008,1010,1009,1008,3575,3559,3199,3198,43,3576,3577,3578,3579,0,2089,2090,2091,2092,3576,3577,3578,3579,43,3579,3578,2547,2542,0,2092,2091,1177,1172,3579,3578,2547,2542,43,3577,2749,2748,3578,0,2090,1358,1357,2091,3577,2749,2748,3578,43,3578,2748,2421,2547,0,2091,1357,1097,1177,3578,2748,2421,2547,43,3576,3579,3580,3581,0,2089,2092,2093,2094,3576,3579,3580,3581,43,3581,3580,2768,2766,0,2094,2093,1377,1375,3581,3580,2768,2766,43,3579,2542,2541,3580,0,2092,1172,1171,2093,3579,2542,2541,3580,43,3580,2541,2544,2768,0,2093,1171,1174,1377,3580,2541,2544,2768,43,3576,3581,3582,3583,0,2089,2094,2095,2096,3576,3581,3582,3583,43,3583,3582,3584,3585,0,2096,2095,2097,2098,3583,3582,3584,3585,43,3581,2766,2765,3582,0,2094,1375,1374,2095,3581,2766,2765,3582,43,3582,2765,2230,3584,0,2095,1374,982,2097,3582,2765,2230,3584,43,3576,3583,3586,3577,0,2089,2096,2099,2090,3576,3583,3586,3577,43,3577,3586,2752,2749,0,2090,2099,1361,1358,3577,3586,2752,2749,43,3583,3585,3587,3586,0,2096,2098,2100,2099,3583,3585,3587,3586,43,3586,3587,2753,2752,0,2099,2100,1362,1361,3586,3587,2753,2752,43,3588,3589,3590,3591,0,2101,2102,2103,2104,3588,3589,3590,3591,43,3591,3590,2816,2811,0,2104,2103,1419,1414,3591,3590,2816,2811,43,3589,3592,3593,3590,0,2102,2105,2106,2103,3589,3592,3593,3590,43,3590,3593,2817,2816,0,2103,2106,1420,1419,3590,3593,2817,2816,43,3588,3591,3594,3595,0,2101,2104,2107,2108,3588,3591,3594,3595,43,3595,3594,3587,3585,0,2108,2107,2100,2098,3595,3594,3587,3585,43,3591,2811,2810,3594,0,2104,1414,1413,2107,3591,2811,2810,3594,43,3594,2810,2753,3587,0,2107,1413,1362,2100,3594,2810,2753,3587,43,3588,3595,3596,3597,0,2101,2108,2109,2110,3588,3595,3596,3597,43,3597,3596,2229,2221,0,2110,2109,981,973,3597,3596,2229,2221,43,3595,3585,3584,3596,0,2108,2098,2097,2109,3595,3585,3584,3596,43,3596,3584,2230,2229,0,2109,2097,982,981,3596,3584,2230,2229,43,3588,3597,3598,3589,0,2101,2110,2111,2102,3588,3597,3598,3589,43,3589,3598,3599,3592,0,2102,2111,2112,2105,3589,3598,3599,3592,43,3597,2221,2220,3598,0,2110,973,972,2111,3597,2221,2220,3598,43,3598,2220,2224,3599,0,2111,972,976,2112,3598,2220,2224,3599,43,3600,3601,3602,3603,0,2113,274,279,2114,3600,3601,3602,3603,43,3603,3602,478,473,0,2114,279,223,218,3603,3602,478,473,43,3601,3604,3605,3602,0,274,276,282,279,3601,3604,3605,3602,43,3602,3605,479,478,0,279,282,14,223,3602,3605,479,478,43,3600,3603,3606,3607,0,2113,2114,2115,2116,3600,3603,3606,3607,43,3607,3606,3608,3609,0,2116,2115,2117,2118,3607,3606,3608,3609,43,3603,473,472,3606,0,2114,218,217,2115,3603,473,472,3606,43,3606,472,475,3608,0,2115,217,220,2117,3606,472,475,3608,43,3600,3607,3610,3611,0,2113,2116,2119,2120,3600,3607,3610,3611,43,3611,3610,3612,3613,0,2120,2119,2121,2122,3611,3610,3612,3613,43,3607,3609,3614,3610,0,2116,2118,2123,2119,3607,3609,3614,3610,43,3610,3614,3615,3612,0,2119,2123,2124,2121,3610,3614,3615,3612,43,3600,3611,3616,3601,0,2113,2120,273,274,3600,3611,3616,3601,43,3601,3616,3617,3604,0,274,273,275,276,3601,3616,3617,3604,43,3611,3613,3618,3616,0,2120,2122,277,273,3611,3613,3618,3616,43,3616,3618,3619,3617,0,273,277,278,275,3616,3618,3619,3617,43,3620,3621,3622,3623,0,2125,2126,2127,2128,3620,3621,3622,3623,43,3623,3622,543,538,0,2128,2127,285,281,3623,3622,543,538,43,3621,620,619,3622,0,2126,2129,2130,2127,3621,620,619,3622,43,3622,619,544,543,0,2127,2130,5,285,3622,619,544,543,43,3620,3623,3624,3625,0,2125,2128,221,2131,3620,3623,3624,3625,43,3625,3624,1454,1451,0,2131,221,222,716,3625,3624,1454,1451,43,3623,538,537,3624,0,2128,281,223,221,3623,538,537,3624,43,3624,537,540,1454,0,221,223,14,222,3624,537,540,1454,43,3620,3625,3626,3627,0,2125,2131,2132,2133,3620,3625,3626,3627,43,3627,3626,1890,1887,0,2133,2132,904,900,3627,3626,1890,1887,43,3625,1451,1450,3626,0,2131,716,715,2132,3625,1451,1450,3626,43,3626,1450,375,1890,0,2132,715,120,904,3626,1450,375,1890,43,3620,3627,3628,3621,0,2125,2133,2134,2126,3620,3627,3628,3621,43,3621,3628,627,620,0,2126,2134,2135,2129,3621,3628,627,620,43,3627,1887,1886,3628,0,2133,900,899,2134,3627,1887,1886,3628,43,3628,1886,628,627,0,2134,899,902,2135,3628,1886,628,627,43,3629,3630,3631,3632,0,2136,2137,2138,2139,3629,3630,3631,3632,43,3632,3631,1394,1393,0,2139,2138,2140,2141,3632,3631,1394,1393,43,3630,576,589,3631,0,2137,317,333,2138,3630,576,589,3631,43,3631,589,356,1394,0,2138,333,335,2140,3631,589,356,1394,43,3629,3632,3633,3634,0,2136,2139,709,2142,3629,3632,3633,3634,43,3634,3633,568,563,0,2142,709,309,304,3634,3633,568,563,43,3632,1393,1402,3633,0,2139,2141,925,709,3632,1393,1402,3633,43,3633,1402,569,568,0,709,925,310,309,3633,1402,569,568,43,3629,3634,3635,3636,0,2136,2142,2143,2144,3629,3634,3635,3636,43,3636,3635,636,632,0,2144,2143,382,378,3636,3635,636,632,43,3634,563,562,3635,0,2142,304,303,2143,3634,563,562,3635,43,3635,562,565,636,0,2143,303,306,382,3635,562,565,636,43,3629,3636,3637,3630,0,2136,2144,2145,2137,3629,3636,3637,3630,43,3630,3637,577,576,0,2137,2145,318,317,3630,3637,577,576,43,3636,632,631,3637,0,2144,378,377,2145,3636,632,631,3637,43,3637,631,578,577,0,2145,377,319,318,3637,631,578,577,43,3638,3639,3640,3641,0,2146,2147,2148,2149,3638,3639,3640,3641,43,3641,3640,622,621,0,2149,2148,367,366,3641,3640,622,621,43,3639,526,542,3640,0,2147,2150,2151,2148,3639,526,542,3640,43,3640,542,544,622,0,2148,2151,368,367,3640,542,544,622,43,3638,3641,3642,3643,0,2146,2149,2152,2153,3638,3641,3642,3643,43,3643,3642,564,557,0,2153,2152,305,298,3643,3642,564,557,43,3641,621,635,3642,0,2149,366,381,2152,3641,621,635,3642,43,3642,635,565,564,0,2152,381,306,305,3642,635,565,564,43,3638,3643,3644,3645,0,2146,2153,2154,2155,3638,3643,3644,3645,43,3645,3644,3646,3647,0,2155,2154,2156,2157,3645,3644,3646,3647,43,3643,557,556,3644,0,2153,298,297,2154,3643,557,556,3644,43,3644,556,559,3646,0,2154,297,300,2156,3644,556,559,3646,43,3638,3645,3648,3639,0,2146,2155,2158,2147,3638,3645,3648,3639,43,3639,3648,527,526,0,2147,2158,2159,2150,3639,3648,527,526,43,3645,3647,3649,3648,0,2155,2157,2160,2158,3645,3647,3649,3648,43,3648,3649,528,527,0,2158,2160,2161,2159,3648,3649,528,527,43,3650,3651,3652,3653,0,2162,2163,2164,2165,3650,3651,3652,3653,43,3653,3652,1403,1400,0,2165,2164,309,662,3653,3652,1403,1400,43,3651,551,567,3652,0,2163,2166,2167,2164,3651,551,567,3652,43,3652,567,569,1403,0,2164,2167,665,309,3652,567,569,1403,43,3650,3653,3654,3655,0,2162,2165,2168,2169,3650,3653,3654,3655,43,3655,3654,3605,3604,0,2169,2168,282,276,3655,3654,3605,3604,43,3653,1400,1399,3654,0,2165,662,661,2168,3653,1400,1399,3654,43,3654,1399,479,3605,0,2168,661,14,282,3654,1399,479,3605,43,3650,3655,3656,3657,0,2162,2169,2170,2171,3650,3655,3656,3657,43,3657,3656,3658,3659,0,2171,2170,2172,2173,3657,3656,3658,3659,43,3655,3604,3617,3656,0,2169,276,275,2170,3655,3604,3617,3656,43,3656,3617,3619,3658,0,2170,275,278,2172,3656,3617,3619,3658,43,3650,3657,3660,3651,0,2162,2171,2174,2163,3650,3657,3660,3651,43,3651,3660,552,551,0,2163,2174,2175,2166,3651,3660,552,551,43,3657,3659,3661,3660,0,2171,2173,2176,2174,3657,3659,3661,3660,43,3660,3661,553,552,0,2174,2176,294,2175,3660,3661,553,552,43,3662,3663,3664,3665,0,2177,2178,2179,2180,3662,3663,3664,3665,43,3665,3664,3666,3667,0,2180,2179,2181,2182,3665,3664,3666,3667,43,3663,3668,3669,3664,0,2178,2183,2184,2179,3663,3668,3669,3664,43,3664,3669,3670,3666,0,2179,2184,2185,2181,3664,3669,3670,3666,43,3662,3665,3671,3672,0,2177,2180,2186,2187,3662,3665,3671,3672,43,3672,3671,1443,1442,0,2187,2186,708,707,3672,3671,1443,1442,43,3665,3667,3673,3671,0,2180,2182,2188,2186,3665,3667,3673,3671,43,3671,3673,1444,1443,0,2186,2188,709,708,3671,3673,1444,1443,43,3662,3672,3674,3675,0,2177,2187,2168,2169,3662,3672,3674,3675,43,3675,3674,539,532,0,2169,2168,282,276,3675,3674,539,532,43,3672,1442,1453,3674,0,2187,707,661,2168,3672,1442,1453,3674,43,3674,1453,540,539,0,2168,661,14,282,3674,1453,540,539,43,3662,3675,3676,3663,0,2177,2169,2170,2178,3662,3675,3676,3663,43,3663,3676,3677,3668,0,2178,2170,2172,2183,3663,3676,3677,3668,43,3675,532,531,3676,0,2169,276,275,2170,3675,532,531,3676,43,3676,531,534,3677,0,2170,275,278,2172,3676,531,534,3677,43,3678,3679,3680,3681,0,2189,2190,9,905,3678,3679,3680,3681,43,3681,3680,1904,1896,0,905,9,9,905,3681,3680,1904,1896,43,3679,3682,3683,3680,0,2190,2191,156,9,3679,3682,3683,3680,43,3680,3683,1905,1904,0,9,156,156,9,3680,3683,1905,1904,43,3678,3681,3684,3685,0,2189,905,906,2192,3678,3681,3684,3685,43,3685,3684,3686,3687,0,2192,906,12,2193,3685,3684,3686,3687,43,3681,1896,1895,3684,0,905,905,906,906,3681,1896,1895,3684,43,3684,1895,1899,3686,0,906,906,12,12,3684,1895,1899,3686,43,3678,3685,3688,3689,0,2189,2192,2194,2195,3678,3685,3688,3689,43,3689,3688,3618,3613,0,2195,2194,277,2122,3689,3688,3618,3613,43,3685,3687,3690,3688,0,2192,2193,2196,2194,3685,3687,3690,3688,43,3688,3690,3619,3618,0,2194,2196,278,277,3688,3690,3619,3618,43,3678,3689,3691,3679,0,2189,2195,2197,2190,3678,3689,3691,3679,43,3679,3691,3692,3682,0,2190,2197,2198,2191,3679,3691,3692,3682,43,3689,3613,3612,3691,0,2195,2122,2121,2197,3689,3613,3612,3691,43,3691,3612,3615,3692,0,2197,2121,2124,2198,3691,3612,3615,3692,43,3693,3694,3695,3696,0,2199,2200,67,10,3693,3694,3695,3696,43,3696,3695,1929,1921,0,10,67,67,10,3696,3695,1929,1921,43,3694,3697,3698,3695,0,2200,2201,6,67,3694,3697,3698,3695,43,3695,3698,1930,1929,0,67,6,6,67,3695,3698,1930,1929,43,3693,3696,3699,3700,0,2199,10,906,2192,3693,3696,3699,3700,43,3700,3699,3701,3702,0,2192,906,12,2193,3700,3699,3701,3702,43,3696,1921,1920,3699,0,10,10,906,906,3696,1921,1920,3699,43,3699,1920,1924,3701,0,906,906,12,12,3699,1920,1924,3701,43,3693,3700,3703,3704,0,2199,2192,2194,2202,3693,3700,3703,3704,43,3704,3703,533,525,0,2202,2194,277,269,3704,3703,533,525,43,3700,3702,3705,3703,0,2192,2193,2196,2194,3700,3702,3705,3703,43,3703,3705,534,533,0,2194,2196,278,277,3703,3705,534,533,43,3693,3704,3706,3694,0,2199,2202,2203,2200,3693,3704,3706,3694,43,3694,3706,3707,3697,0,2200,2203,2204,2201,3694,3706,3707,3697,43,3704,525,524,3706,0,2202,269,268,2203,3704,525,524,3706,43,3706,524,528,3707,0,2203,268,272,2204,3706,524,528,3707,43,3708,3709,3710,3711,0,2205,2206,2207,2208,3708,3709,3710,3711,43,3711,3710,3712,3713,0,2208,2207,2209,2210,3711,3710,3712,3713,43,3709,3647,3646,3710,0,2206,2157,2156,2207,3709,3647,3646,3710,43,3710,3646,559,3712,0,2207,2156,300,2209,3710,3646,559,3712,43,3708,3711,3714,3715,0,2205,2208,912,909,3708,3711,3714,3715,43,3715,3714,1951,1946,0,909,912,912,909,3715,3714,1951,1946,43,3711,3713,3716,3714,0,2208,2210,913,912,3711,3713,3716,3714,43,3714,3716,1952,1951,0,912,913,913,912,3714,3716,1952,1951,43,3708,3715,3717,3718,0,2205,909,910,2211,3708,3715,3717,3718,43,3718,3717,3698,3697,0,2211,910,911,2212,3718,3717,3698,3697,43,3715,1946,1945,3717,0,909,909,910,910,3715,1946,1945,3717,43,3717,1945,1930,3698,0,910,910,911,911,3717,1945,1930,3698,43,3708,3718,3719,3709,0,2205,2211,2213,2206,3708,3718,3719,3709,43,3709,3719,3649,3647,0,2206,2213,2160,2157,3709,3719,3649,3647,43,3718,3697,3707,3719,0,2211,2212,2214,2213,3718,3697,3707,3719,43,3719,3707,528,3649,0,2213,2214,2161,2160,3719,3707,528,3649,43,3720,3721,3722,3723,0,2215,2216,2217,2218,3720,3721,3722,3723,43,3723,3722,3724,3725,0,2218,2217,2219,2220,3723,3722,3724,3725,43,3721,550,549,3722,0,2216,291,290,2217,3721,550,549,3722,43,3722,549,553,3724,0,2217,290,294,2219,3722,549,553,3724,43,3720,3723,3726,3727,0,2215,2218,915,914,3720,3723,3726,3727,43,3727,3726,1968,1967,0,914,915,915,914,3727,3726,1968,1967,43,3723,3725,3728,3726,0,2218,2220,916,915,3723,3725,3728,3726,43,3726,3728,1969,1968,0,915,916,916,915,3726,3728,1969,1968,43,3720,3727,3729,3730,0,2215,914,917,2221,3720,3727,3729,3730,43,3730,3729,3716,3713,0,2221,917,913,2210,3730,3729,3716,3713,43,3727,1967,1980,3729,0,914,914,917,917,3727,1967,1980,3729,43,3729,1980,1952,3716,0,917,917,913,913,3729,1980,1952,3716,43,3720,3730,3731,3721,0,2215,2221,2222,2216,3720,3730,3731,3721,43,3721,3731,558,550,0,2216,2222,299,291,3721,3731,558,550,43,3730,3713,3712,3731,0,2221,2210,2209,2222,3730,3713,3712,3731,43,3731,3712,559,558,0,2222,2209,300,299,3731,3712,559,558,43,3732,3733,3734,3735,0,2223,2224,2225,2226,3732,3733,3734,3735,43,3735,3734,3690,3687,0,2226,2225,2196,2193,3735,3734,3690,3687,43,3733,3659,3658,3734,0,2224,2173,2172,2225,3733,3659,3658,3734,43,3734,3658,3619,3690,0,2225,2172,278,2196,3734,3658,3619,3690,43,3732,3735,3736,3737,0,2223,2226,919,918,3732,3735,3736,3737,43,3737,3736,1986,1985,0,918,919,919,918,3737,3736,1986,1985,43,3735,3687,3686,3736,0,2226,2193,12,919,3735,3687,3686,3736,43,3736,3686,1899,1986,0,919,12,12,919,3736,3686,1899,1986,43,3732,3737,3738,3739,0,2223,918,922,2227,3732,3737,3738,3739,43,3739,3738,3728,3725,0,2227,922,916,2220,3739,3738,3728,3725,43,3737,1985,1995,3738,0,918,918,922,922,3737,1985,1995,3738,43,3738,1995,1969,3728,0,922,922,916,916,3738,1995,1969,3728,43,3732,3739,3740,3733,0,2223,2227,2228,2224,3732,3739,3740,3733,43,3733,3740,3661,3659,0,2224,2228,2176,2173,3733,3740,3661,3659,43,3739,3725,3724,3740,0,2227,2220,2219,2228,3739,3725,3724,3740,43,3740,3724,553,3661,0,2228,2219,294,2176,3740,3724,553,3661,43,3741,3742,3743,3744,0,2229,2226,919,923,3741,3742,3743,3744,43,3744,3743,2007,2001,0,923,919,919,923,3744,3743,2007,2001,43,3742,3702,3701,3743,0,2226,2193,12,919,3742,3702,3701,3743,43,3743,3701,1924,2007,0,919,12,12,919,3743,3701,1924,2007,43,3741,3744,3745,3746,0,2229,923,924,2230,3741,3744,3745,3746,43,3746,3745,3747,3748,0,2230,924,925,2231,3746,3745,3747,3748,43,3744,2001,2000,3745,0,923,923,924,924,3744,2001,2000,3745,43,3745,2000,2004,3747,0,924,924,925,925,3745,2000,2004,3747,43,3741,3746,3749,3750,0,2229,2230,2232,2233,3741,3746,3749,3750,43,3750,3749,3669,3668,0,2233,2232,2184,2183,3750,3749,3669,3668,43,3746,3748,3751,3749,0,2230,2231,2234,2232,3746,3748,3751,3749,43,3749,3751,3670,3669,0,2232,2234,2185,2184,3749,3751,3670,3669,43,3741,3750,3752,3742,0,2229,2233,2225,2226,3741,3750,3752,3742,43,3742,3752,3705,3702,0,2226,2225,2196,2193,3742,3752,3705,3702,43,3750,3668,3677,3752,0,2233,2183,2172,2225,3750,3668,3677,3752,43,3752,3677,534,3705,0,2225,2172,278,2196,3752,3677,534,3705,43,3753,3754,3755,3756,0,947,67,67,947,3753,3754,3755,3756,43,3756,3755,3757,3758,0,947,67,67,947,3756,3755,3757,3758,43,3754,3759,3760,3755,0,67,6,6,67,3754,3759,3760,3755,43,3755,3760,3761,3757,0,67,6,6,67,3755,3760,3761,3757,43,3753,3756,3762,3763,0,947,947,948,948,3753,3756,3762,3763,43,3763,3762,3764,3765,0,948,948,931,931,3763,3762,3764,3765,43,3756,3758,3766,3762,0,947,947,948,948,3756,3758,3766,3762,43,3762,3766,3767,3764,0,948,948,931,931,3762,3766,3767,3764,43,3753,3763,3768,3769,0,947,948,948,947,3753,3763,3768,3769,43,3769,3768,3770,3771,0,947,948,948,947,3769,3768,3770,3771,43,3763,3765,3772,3768,0,948,931,931,948,3763,3765,3772,3768,43,3768,3772,3773,3770,0,948,931,931,948,3768,3772,3773,3770,43,3753,3769,3774,3754,0,947,947,67,67,3753,3769,3774,3754,43,3754,3774,3775,3759,0,67,67,6,6,3754,3774,3775,3759,43,3769,3771,3776,3774,0,947,947,67,67,3769,3771,3776,3774,43,3774,3776,3777,3775,0,67,67,6,6,3774,3776,3777,3775,43,3778,3779,3780,3781,0,909,909,910,910,3778,3779,3780,3781,43,3781,3780,3760,3759,0,910,910,911,911,3781,3780,3760,3759,43,3779,3782,3783,3780,0,909,909,910,910,3779,3782,3783,3780,43,3780,3783,3761,3760,0,910,910,911,911,3780,3783,3761,3760,43,3778,3781,3784,3785,0,909,910,910,909,3778,3781,3784,3785,43,3785,3784,3786,3787,0,909,910,910,909,3785,3784,3786,3787,43,3781,3759,3775,3784,0,910,911,911,910,3781,3759,3775,3784,43,3784,3775,3777,3786,0,910,911,911,910,3784,3775,3777,3786,43,3778,3785,3788,3789,0,909,909,912,912,3778,3785,3788,3789,43,3789,3788,3790,3791,0,912,912,913,913,3789,3788,3790,3791,43,3785,3787,3792,3788,0,909,909,912,912,3785,3787,3792,3788,43,3788,3792,3793,3790,0,912,912,913,913,3788,3792,3793,3790,43,3778,3789,3794,3779,0,909,912,912,909,3778,3789,3794,3779,43,3779,3794,3795,3782,0,909,912,912,909,3779,3794,3795,3782,43,3789,3791,3796,3794,0,912,913,913,912,3789,3791,3796,3794,43,3794,3796,3797,3795,0,912,913,913,912,3794,3796,3797,3795,43,3798,3799,3800,3801,0,914,914,917,917,3798,3799,3800,3801,43,3801,3800,3796,3791,0,917,917,913,913,3801,3800,3796,3791,43,3799,3802,3803,3800,0,914,914,917,917,3799,3802,3803,3800,43,3800,3803,3797,3796,0,917,917,913,913,3800,3803,3797,3796,43,3798,3801,3804,3805,0,914,917,917,914,3798,3801,3804,3805,43,3805,3804,3806,3807,0,914,917,917,914,3805,3804,3806,3807,43,3801,3791,3790,3804,0,917,913,913,917,3801,3791,3790,3804,43,3804,3790,3793,3806,0,917,913,913,917,3804,3790,3793,3806,43,3798,3805,3808,3809,0,914,914,915,915,3798,3805,3808,3809,43,3809,3808,3810,3811,0,915,915,916,916,3809,3808,3810,3811,43,3805,3807,3812,3808,0,914,914,915,915,3805,3807,3812,3808,43,3808,3812,3813,3810,0,915,915,916,916,3808,3812,3813,3810,43,3798,3809,3814,3799,0,914,915,915,914,3798,3809,3814,3799,43,3799,3814,3815,3802,0,914,915,915,914,3799,3814,3815,3802,43,3809,3811,3816,3814,0,915,916,916,915,3809,3811,3816,3814,43,3814,3816,3817,3815,0,915,916,916,915,3814,3816,3817,3815,43,3818,3819,3820,3821,0,953,953,922,922,3818,3819,3820,3821,43,3821,3820,3816,3811,0,922,922,916,916,3821,3820,3816,3811,43,3819,3822,3823,3820,0,953,953,922,922,3819,3822,3823,3820,43,3820,3823,3817,3816,0,922,922,916,916,3820,3823,3817,3816,43,3818,3821,3824,3825,0,953,922,922,953,3818,3821,3824,3825,43,3825,3824,3826,3827,0,953,922,922,953,3825,3824,3826,3827,43,3821,3811,3810,3824,0,922,916,916,922,3821,3811,3810,3824,43,3824,3810,3813,3826,0,922,916,916,922,3824,3810,3813,3826,43,3818,3825,3828,3829,0,953,953,939,939,3818,3825,3828,3829,43,3829,3828,3830,3831,0,939,939,328,328,3829,3828,3830,3831,43,3825,3827,3832,3828,0,953,953,939,939,3825,3827,3832,3828,43,3828,3832,3833,3830,0,939,939,328,328,3828,3832,3833,3830,43,3818,3829,3834,3819,0,953,939,939,953,3818,3829,3834,3819,43,3819,3834,3835,3822,0,953,939,939,953,3819,3834,3835,3822,43,3829,3831,3836,3834,0,939,328,328,939,3829,3831,3836,3834,43,3834,3836,3837,3835,0,939,328,328,939,3834,3836,3837,3835,43,3838,3839,3840,3841,0,929,928,928,929,3838,3839,3840,3841,43,3841,3840,3842,3843,0,929,928,928,929,3841,3840,3842,3843,43,3839,3765,3764,3840,0,928,931,931,928,3839,3765,3764,3840,43,3840,3764,3767,3842,0,928,931,931,928,3840,3764,3767,3842,43,3838,3841,3844,3845,0,929,929,12,12,3838,3841,3844,3845,43,3845,3844,3846,3847,0,12,12,12,12,3845,3844,3846,3847,43,3841,3843,3848,3844,0,929,929,12,12,3841,3843,3848,3844,43,3844,3848,3849,3846,0,12,12,12,12,3844,3848,3849,3846,43,3838,3845,3850,3851,0,929,12,12,929,3838,3845,3850,3851,43,3851,3850,3852,3853,0,929,12,12,929,3851,3850,3852,3853,43,3845,3847,3854,3850,0,12,12,12,12,3845,3847,3854,3850,43,3850,3854,3855,3852,0,12,12,12,12,3850,3854,3855,3852,43,3838,3851,3856,3839,0,929,929,928,928,3838,3851,3856,3839,43,3839,3856,3772,3765,0,928,928,931,931,3839,3856,3772,3765,43,3851,3853,3857,3856,0,929,929,928,928,3851,3853,3857,3856,43,3856,3857,3773,3772,0,928,928,931,931,3856,3857,3773,3772,43,3858,3859,3860,3861,0,12,12,12,12,3858,3859,3860,3861,43,3861,3860,3862,3863,0,12,12,12,12,3861,3860,3862,3863,43,3859,3864,3865,3860,0,12,12,12,12,3859,3864,3865,3860,43,3860,3865,3866,3862,0,12,12,12,12,3860,3865,3866,3862,43,3858,3861,3867,3868,0,12,12,12,12,3858,3861,3867,3868,43,3868,3867,3869,3870,0,12,12,12,12,3868,3867,3869,3870,43,3861,3863,3871,3867,0,12,12,12,12,3861,3863,3871,3867,43,3867,3871,3872,3869,0,12,12,12,12,3867,3871,3872,3869,43,3858,3868,3873,3874,0,12,12,12,12,3858,3868,3873,3874,43,3874,3873,3875,3876,0,12,12,12,12,3874,3873,3875,3876,43,3868,3870,3877,3873,0,12,12,12,12,3868,3870,3877,3873,43,3873,3877,3878,3875,0,12,12,12,12,3873,3877,3878,3875,43,3858,3874,3879,3859,0,12,12,12,12,3858,3874,3879,3859,43,3859,3879,3880,3864,0,12,12,12,12,3859,3879,3880,3864,43,3874,3876,3881,3879,0,12,12,12,12,3874,3876,3881,3879,43,3879,3881,3882,3880,0,12,12,12,12,3879,3881,3882,3880,43,3883,3884,3885,3886,0,932,932,933,933,3883,3884,3885,3886,43,3886,3885,3836,3831,0,933,933,328,328,3886,3885,3836,3831,43,3884,3887,3888,3885,0,932,932,933,933,3884,3887,3888,3885,43,3885,3888,3837,3836,0,933,933,328,328,3885,3888,3837,3836,43,3883,3886,3889,3890,0,932,933,933,932,3883,3886,3889,3890,43,3890,3889,3891,3892,0,932,933,933,932,3890,3889,3891,3892,43,3886,3831,3830,3889,0,933,328,328,933,3886,3831,3830,3889,43,3889,3830,3833,3891,0,933,328,328,933,3889,3830,3833,3891,43,3883,3890,3893,3894,0,932,932,12,12,3883,3890,3893,3894,43,3894,3893,3877,3870,0,12,12,12,12,3894,3893,3877,3870,43,3890,3892,3895,3893,0,932,932,12,12,3890,3892,3895,3893,43,3893,3895,3878,3877,0,12,12,12,12,3893,3895,3878,3877,43,3883,3894,3896,3884,0,932,12,12,932,3883,3894,3896,3884,43,3884,3896,3897,3887,0,932,12,12,932,3884,3896,3897,3887,43,3894,3870,3869,3896,0,12,12,12,12,3894,3870,3869,3896,43,3896,3869,3872,3897,0,12,12,12,12,3896,3869,3872,3897,43,3898,3899,3900,3901,0,12,12,12,12,3898,3899,3900,3901,43,3901,3900,3902,3903,0,12,12,12,12,3901,3900,3902,3903,43,3899,3847,3846,3900,0,12,12,12,12,3899,3847,3846,3900,43,3900,3846,3849,3902,0,12,12,12,12,3900,3846,3849,3902,43,3898,3901,3904,3905,0,12,12,12,12,3898,3901,3904,3905,43,3905,3904,3865,3864,0,12,12,12,12,3905,3904,3865,3864,43,3901,3903,3906,3904,0,12,12,12,12,3901,3903,3906,3904,43,3904,3906,3866,3865,0,12,12,12,12,3904,3906,3866,3865,43,3898,3905,3907,3908,0,12,12,12,12,3898,3905,3907,3908,43,3908,3907,3909,3910,0,12,12,12,12,3908,3907,3909,3910,43,3905,3864,3880,3907,0,12,12,12,12,3905,3864,3880,3907,43,3907,3880,3882,3909,0,12,12,12,12,3907,3880,3882,3909,43,3898,3908,3911,3899,0,12,12,12,12,3898,3908,3911,3899,43,3899,3911,3854,3847,0,12,12,12,12,3899,3911,3854,3847,43,3908,3910,3912,3911,0,12,12,12,12,3908,3910,3912,3911,43,3911,3912,3855,3854,0,12,12,12,12,3911,3912,3855,3854,43,3913,3914,3915,3916,0,2235,2236,2237,2238,3913,3914,3915,3916,43,3916,3915,3917,3918,0,2238,2237,2239,2240,3916,3915,3917,3918,43,3914,3919,3920,3915,0,2236,2241,2242,2237,3914,3919,3920,3915,43,3915,3920,3921,3917,0,2237,2242,2243,2239,3915,3920,3921,3917,43,3913,3916,3922,3923,0,2235,2238,917,914,3913,3916,3922,3923,43,3923,3922,3924,3925,0,914,917,917,914,3923,3922,3924,3925,43,3916,3918,3926,3922,0,2238,2240,913,917,3916,3918,3926,3922,43,3922,3926,3927,3924,0,917,913,913,917,3922,3926,3927,3924,43,3913,3923,3928,3929,0,2235,914,915,915,3913,3923,3928,3929,43,3929,3928,3930,3931,0,915,915,916,916,3929,3928,3930,3931,43,3923,3925,3932,3928,0,914,914,915,915,3923,3925,3932,3928,43,3928,3932,3933,3930,0,915,915,916,916,3928,3932,3933,3930,43,3913,3929,3934,3914,0,2235,915,915,2236,3913,3929,3934,3914,43,3914,3934,3935,3919,0,2236,915,915,2241,3914,3934,3935,3919,43,3929,3931,3936,3934,0,915,916,916,915,3929,3931,3936,3934,43,3934,3936,3937,3935,0,915,916,916,915,3934,3936,3937,3935,43,3938,3939,3940,3941,0,953,939,939,953,3938,3939,3940,3941,43,3941,3940,3942,3943,0,953,939,939,953,3941,3940,3942,3943,43,3939,3944,3945,3940,0,939,328,328,939,3939,3944,3945,3940,43,3940,3945,3946,3942,0,939,328,328,939,3940,3945,3946,3942,43,3938,3941,3947,3948,0,953,953,922,922,3938,3941,3947,3948,43,3948,3947,3936,3931,0,922,922,916,916,3948,3947,3936,3931,43,3941,3943,3949,3947,0,953,953,922,922,3941,3943,3949,3947,43,3947,3949,3937,3936,0,922,922,916,916,3947,3949,3937,3936,43,3938,3948,3950,3951,0,953,922,922,953,3938,3948,3950,3951,43,3951,3950,3952,3953,0,953,922,922,953,3951,3950,3952,3953,43,3948,3931,3930,3950,0,922,916,916,922,3948,3931,3930,3950,43,3950,3930,3933,3952,0,922,916,916,922,3950,3930,3933,3952,43,3938,3951,3954,3939,0,953,953,939,939,3938,3951,3954,3939,43,3939,3954,3955,3944,0,939,939,328,328,3939,3954,3955,3944,43,3951,3953,3956,3954,0,953,953,939,939,3951,3953,3956,3954,43,3954,3956,3957,3955,0,939,939,328,328,3954,3956,3957,3955,43,3958,3959,3960,3961,0,12,12,12,12,3958,3959,3960,3961,43,3961,3960,3962,3963,0,12,12,12,12,3961,3960,3962,3963,43,3959,3964,3965,3960,0,12,12,12,12,3959,3964,3965,3960,43,3960,3965,3966,3962,0,12,12,12,12,3960,3965,3966,3962,43,3958,3961,3967,3968,0,12,12,12,12,3958,3961,3967,3968,43,3968,3967,3969,3970,0,12,12,12,12,3968,3967,3969,3970,43,3961,3963,3971,3967,0,12,12,12,12,3961,3963,3971,3967,43,3967,3971,3972,3969,0,12,12,12,12,3967,3971,3972,3969,43,3958,3968,3973,3974,0,12,12,12,12,3958,3968,3973,3974,43,3974,3973,3975,3976,0,12,12,12,12,3974,3973,3975,3976,43,3968,3970,3977,3973,0,12,12,12,12,3968,3970,3977,3973,43,3973,3977,3978,3975,0,12,12,12,12,3973,3977,3978,3975,43,3958,3974,3979,3959,0,12,12,12,12,3958,3974,3979,3959,43,3959,3979,3980,3964,0,12,12,12,12,3959,3979,3980,3964,43,3974,3976,3981,3979,0,12,12,12,12,3974,3976,3981,3979,43,3979,3981,3982,3980,0,12,12,12,12,3979,3981,3982,3980,43,3983,3984,3985,3986,0,932,932,933,933,3983,3984,3985,3986,43,3986,3985,3945,3944,0,933,933,328,328,3986,3985,3945,3944,43,3984,3987,3988,3985,0,932,932,933,933,3984,3987,3988,3985,43,3985,3988,3946,3945,0,933,933,328,328,3985,3988,3946,3945,43,3983,3986,3989,3990,0,932,933,933,932,3983,3986,3989,3990,43,3990,3989,3991,3992,0,932,933,933,932,3990,3989,3991,3992,43,3986,3944,3955,3989,0,933,328,328,933,3986,3944,3955,3989,43,3989,3955,3957,3991,0,933,328,328,933,3989,3955,3957,3991,43,3983,3990,3993,3994,0,932,932,12,12,3983,3990,3993,3994,43,3994,3993,3971,3963,0,12,12,12,12,3994,3993,3971,3963,43,3990,3992,3995,3993,0,932,932,12,12,3990,3992,3995,3993,43,3993,3995,3972,3971,0,12,12,12,12,3993,3995,3972,3971,43,3983,3994,3996,3984,0,932,12,12,932,3983,3994,3996,3984,43,3984,3996,3997,3987,0,932,12,12,932,3984,3996,3997,3987,43,3994,3963,3962,3996,0,12,12,12,12,3994,3963,3962,3996,43,3996,3962,3966,3997,0,12,12,12,12,3996,3962,3966,3997,43,3998,3999,4000,4001,0,12,12,12,12,3998,3999,4000,4001,43,4001,4000,4002,4003,0,12,12,12,12,4001,4000,4002,4003,43,3999,4004,4005,4000,0,12,12,12,12,3999,4004,4005,4000,43,4000,4005,4006,4002,0,12,12,12,12,4000,4005,4006,4002,43,3998,4001,4007,4008,0,12,12,12,12,3998,4001,4007,4008,43,4008,4007,3981,3976,0,12,12,12,12,4008,4007,3981,3976,43,4001,4003,4009,4007,0,12,12,12,12,4001,4003,4009,4007,43,4007,4009,3982,3981,0,12,12,12,12,4007,4009,3982,3981,43,3998,4008,4010,4011,0,12,12,12,12,3998,4008,4010,4011,43,4011,4010,4012,4013,0,12,12,12,12,4011,4010,4012,4013,43,4008,3976,3975,4010,0,12,12,12,12,4008,3976,3975,4010,43,4010,3975,3978,4012,0,12,12,12,12,4010,3975,3978,4012,43,3998,4011,4014,3999,0,12,12,12,12,3998,4011,4014,3999,43,3999,4014,4015,4004,0,12,12,12,12,3999,4014,4015,4004,43,4011,4013,4016,4014,0,12,12,12,12,4011,4013,4016,4014,43,4014,4016,4017,4015,0,12,12,12,12,4014,4016,4017,4015,43,4018,4019,4020,4021,0,947,947,948,948,4018,4019,4020,4021,43,4021,4020,4022,4023,0,948,948,931,931,4021,4020,4022,4023,43,4019,4024,4025,4020,0,947,947,948,948,4019,4024,4025,4020,43,4020,4025,4026,4022,0,948,948,931,931,4020,4025,4026,4022,43,4018,4021,4027,4028,0,947,948,948,947,4018,4021,4027,4028,43,4028,4027,4029,4030,0,947,948,948,947,4028,4027,4029,4030,43,4021,4023,4031,4027,0,948,931,931,948,4021,4023,4031,4027,43,4027,4031,4032,4029,0,948,931,931,948,4027,4031,4032,4029,43,4018,4028,4033,4034,0,947,947,67,67,4018,4028,4033,4034,43,4034,4033,4035,4036,0,67,67,6,6,4034,4033,4035,4036,43,4028,4030,4037,4033,0,947,947,67,67,4028,4030,4037,4033,43,4033,4037,4038,4035,0,67,67,6,6,4033,4037,4038,4035,43,4018,4034,4039,4019,0,947,67,67,947,4018,4034,4039,4019,43,4019,4039,4040,4024,0,947,67,67,947,4019,4039,4040,4024,43,4034,4036,4041,4039,0,67,6,6,67,4034,4036,4041,4039,43,4039,4041,4042,4040,0,67,6,6,67,4039,4041,4042,4040,43,4043,4044,4045,4046,0,909,909,910,910,4043,4044,4045,4046,43,4046,4045,4041,4036,0,910,910,911,911,4046,4045,4041,4036,43,4044,4047,4048,4045,0,909,909,910,910,4044,4047,4048,4045,43,4045,4048,4042,4041,0,910,910,911,911,4045,4048,4042,4041,43,4043,4046,4049,4050,0,909,910,910,909,4043,4046,4049,4050,43,4050,4049,4051,4052,0,909,910,910,909,4050,4049,4051,4052,43,4046,4036,4035,4049,0,910,911,911,910,4046,4036,4035,4049,43,4049,4035,4038,4051,0,910,911,911,910,4049,4035,4038,4051,43,4043,4050,4053,4054,0,909,909,912,912,4043,4050,4053,4054,43,4054,4053,4055,4056,0,912,912,913,913,4054,4053,4055,4056,43,4050,4052,4057,4053,0,909,909,912,912,4050,4052,4057,4053,43,4053,4057,4058,4055,0,912,912,913,913,4053,4057,4058,4055,43,4043,4054,4059,4044,0,909,912,912,909,4043,4054,4059,4044,43,4044,4059,4060,4047,0,909,912,912,909,4044,4059,4060,4047,43,4054,4056,4061,4059,0,912,913,913,912,4054,4056,4061,4059,43,4059,4061,4062,4060,0,912,913,913,912,4059,4061,4062,4060,43,4063,4064,4065,4066,0,914,914,917,917,4063,4064,4065,4066,43,4066,4065,4061,4056,0,917,917,913,913,4066,4065,4061,4056,43,4064,4067,4068,4065,0,914,914,917,917,4064,4067,4068,4065,43,4065,4068,4062,4061,0,917,917,913,913,4065,4068,4062,4061,43,4063,4066,4069,4070,0,914,917,917,914,4063,4066,4069,4070,43,4070,4069,4071,4072,0,914,917,917,914,4070,4069,4071,4072,43,4066,4056,4055,4069,0,917,913,913,917,4066,4056,4055,4069,43,4069,4055,4058,4071,0,917,913,913,917,4069,4055,4058,4071,43,4063,4070,4073,4074,0,914,914,915,915,4063,4070,4073,4074,43,4074,4073,4075,4076,0,915,915,916,916,4074,4073,4075,4076,43,4070,4072,4077,4073,0,914,914,915,915,4070,4072,4077,4073,43,4073,4077,4078,4075,0,915,915,916,916,4073,4077,4078,4075,43,4063,4074,4079,4064,0,914,915,915,914,4063,4074,4079,4064,43,4064,4079,4080,4067,0,914,915,915,914,4064,4079,4080,4067,43,4074,4076,4081,4079,0,915,916,916,915,4074,4076,4081,4079,43,4079,4081,4082,4080,0,915,916,916,915,4079,4081,4082,4080,43,4083,4084,4085,4086,0,953,922,922,953,4083,4084,4085,4086,43,4086,4085,4087,4088,0,953,922,922,953,4086,4085,4087,4088,43,4084,4076,4075,4085,0,922,916,916,922,4084,4076,4075,4085,43,4085,4075,4078,4087,0,922,916,916,922,4085,4075,4078,4087,43,4083,4086,4089,4090,0,953,953,939,939,4083,4086,4089,4090,43,4090,4089,4091,4092,0,939,939,328,328,4090,4089,4091,4092,43,4086,4088,4093,4089,0,953,953,939,939,4086,4088,4093,4089,43,4089,4093,4094,4091,0,939,939,328,328,4089,4093,4094,4091,43,4083,4090,4095,4096,0,953,939,939,953,4083,4090,4095,4096,43,4096,4095,4097,4098,0,953,939,939,953,4096,4095,4097,4098,43,4090,4092,4099,4095,0,939,328,328,939,4090,4092,4099,4095,43,4095,4099,4100,4097,0,939,328,328,939,4095,4099,4100,4097,43,4083,4096,4101,4084,0,953,953,922,922,4083,4096,4101,4084,43,4084,4101,4081,4076,0,922,922,916,916,4084,4101,4081,4076,43,4096,4098,4102,4101,0,953,953,922,922,4096,4098,4102,4101,43,4101,4102,4082,4081,0,922,922,916,916,4101,4102,4082,4081,43,4103,4104,4105,4106,0,929,12,12,929,4103,4104,4105,4106,43,4106,4105,4107,4108,0,929,12,12,929,4106,4105,4107,4108,43,4104,4109,4110,4105,0,12,12,12,12,4104,4109,4110,4105,43,4105,4110,4111,4107,0,12,12,12,12,4105,4110,4111,4107,43,4103,4106,4112,4113,0,929,929,928,928,4103,4106,4112,4113,43,4113,4112,4031,4023,0,928,928,931,931,4113,4112,4031,4023,43,4106,4108,4114,4112,0,929,929,928,928,4106,4108,4114,4112,43,4112,4114,4032,4031,0,928,928,931,931,4112,4114,4032,4031,43,4103,4113,4115,4116,0,929,928,928,929,4103,4113,4115,4116,43,4116,4115,4117,4118,0,929,928,928,929,4116,4115,4117,4118,43,4113,4023,4022,4115,0,928,931,931,928,4113,4023,4022,4115,43,4115,4022,4026,4117,0,928,931,931,928,4115,4022,4026,4117,43,4103,4116,4119,4104,0,929,929,12,12,4103,4116,4119,4104,43,4104,4119,4120,4109,0,12,12,12,12,4104,4119,4120,4109,43,4116,4118,4121,4119,0,929,929,12,12,4116,4118,4121,4119,43,4119,4121,4122,4120,0,12,12,12,12,4119,4121,4122,4120,43,4123,4124,4125,4126,0,12,12,12,12,4123,4124,4125,4126,43,4126,4125,4127,4128,0,12,12,12,12,4126,4125,4127,4128,43,4124,4129,4130,4125,0,12,12,12,12,4124,4129,4130,4125,43,4125,4130,4131,4127,0,12,12,12,12,4125,4130,4131,4127,43,4123,4126,4132,4133,0,12,12,12,12,4123,4126,4132,4133,43,4133,4132,4134,4135,0,12,12,12,12,4133,4132,4134,4135,43,4126,4128,4136,4132,0,12,12,12,12,4126,4128,4136,4132,43,4132,4136,4137,4134,0,12,12,12,12,4132,4136,4137,4134,43,4123,4133,4138,4139,0,12,12,12,12,4123,4133,4138,4139,43,4139,4138,4140,4141,0,12,12,12,12,4139,4138,4140,4141,43,4133,4135,4142,4138,0,12,12,12,12,4133,4135,4142,4138,43,4138,4142,4143,4140,0,12,12,12,12,4138,4142,4143,4140,43,4123,4139,4144,4124,0,12,12,12,12,4123,4139,4144,4124,43,4124,4144,4145,4129,0,12,12,12,12,4124,4144,4145,4129,43,4139,4141,4146,4144,0,12,12,12,12,4139,4141,4146,4144,43,4144,4146,4147,4145,0,12,12,12,12,4144,4146,4147,4145,43,4148,4149,4150,4151,0,932,933,933,932,4148,4149,4150,4151,43,4151,4150,4152,4153,0,932,933,933,932,4151,4150,4152,4153,43,4149,4092,4091,4150,0,933,328,328,933,4149,4092,4091,4150,43,4150,4091,4094,4152,0,933,328,328,933,4150,4091,4094,4152,43,4148,4151,4154,4155,0,932,932,12,12,4148,4151,4154,4155,43,4155,4154,4136,4128,0,12,12,12,12,4155,4154,4136,4128,43,4151,4153,4156,4154,0,932,932,12,12,4151,4153,4156,4154,43,4154,4156,4137,4136,0,12,12,12,12,4154,4156,4137,4136,43,4148,4155,4157,4158,0,932,12,12,932,4148,4155,4157,4158,43,4158,4157,4159,4160,0,932,12,12,932,4158,4157,4159,4160,43,4155,4128,4127,4157,0,12,12,12,12,4155,4128,4127,4157,43,4157,4127,4131,4159,0,12,12,12,12,4157,4127,4131,4159,43,4148,4158,4161,4149,0,932,932,933,933,4148,4158,4161,4149,43,4149,4161,4099,4092,0,933,933,328,328,4149,4161,4099,4092,43,4158,4160,4162,4161,0,932,932,933,933,4158,4160,4162,4161,43,4161,4162,4100,4099,0,933,933,328,328,4161,4162,4100,4099,43,4163,4164,4165,4166,0,12,12,12,12,4163,4164,4165,4166,43,4166,4165,4167,4168,0,12,12,12,12,4166,4165,4167,4168,43,4164,4141,4140,4165,0,12,12,12,12,4164,4141,4140,4165,43,4165,4140,4143,4167,0,12,12,12,12,4165,4140,4143,4167,43,4163,4166,4169,4170,0,12,12,12,12,4163,4166,4169,4170,43,4170,4169,4110,4109,0,12,12,12,12,4170,4169,4110,4109,43,4166,4168,4171,4169,0,12,12,12,12,4166,4168,4171,4169,43,4169,4171,4111,4110,0,12,12,12,12,4169,4171,4111,4110,43,4163,4170,4172,4173,0,12,12,12,12,4163,4170,4172,4173,43,4173,4172,4174,4175,0,12,12,12,12,4173,4172,4174,4175,43,4170,4109,4120,4172,0,12,12,12,12,4170,4109,4120,4172,43,4172,4120,4122,4174,0,12,12,12,12,4172,4120,4122,4174,43,4163,4173,4176,4164,0,12,12,12,12,4163,4173,4176,4164,43,4164,4176,4146,4141,0,12,12,12,12,4164,4176,4146,4141,43,4173,4175,4177,4176,0,12,12,12,12,4173,4175,4177,4176,43,4176,4177,4147,4146,0,12,12,12,12,4176,4177,4147,4146,43,4178,4179,4180,4181,0,2244,2245,2246,2247,4178,4179,4180,4181,43,4181,4180,4182,4183,0,2247,2246,2248,2249,4181,4180,4182,4183,43,4179,4184,4185,4180,0,2245,2250,2251,2246,4179,4184,4185,4180,43,4180,4185,4186,4182,0,2246,2251,2252,2248,4180,4185,4186,4182,43,4178,4181,4187,4188,0,2244,2247,948,947,4178,4181,4187,4188,43,4188,4187,4189,4190,0,947,948,948,947,4188,4187,4189,4190,43,4181,4183,4191,4187,0,2247,2249,931,948,4181,4183,4191,4187,43,4187,4191,4192,4189,0,948,931,931,948,4187,4191,4192,4189,43,4178,4188,4193,4194,0,2244,947,67,2253,4178,4188,4193,4194,43,4194,4193,4195,4196,0,2253,67,6,2254,4194,4193,4195,4196,43,4188,4190,4197,4193,0,947,947,67,67,4188,4190,4197,4193,43,4193,4197,4198,4195,0,67,67,6,6,4193,4197,4198,4195,43,4178,4194,4199,4179,0,2244,2253,2255,2245,4178,4194,4199,4179,43,4179,4199,4200,4184,0,2245,2255,2256,2250,4179,4199,4200,4184,43,4194,4196,4201,4199,0,2253,2254,2257,2255,4194,4196,4201,4199,43,4199,4201,4202,4200,0,2255,2257,2258,2256,4199,4201,4202,4200,43,4203,4204,4205,4206,0,2259,2260,2261,2262,4203,4204,4205,4206,43,4206,4205,4207,4208,0,2262,2261,2263,2264,4206,4205,4207,4208,43,4204,3918,3917,4205,0,2260,2240,2239,2261,4204,3918,3917,4205,43,4205,3917,3921,4207,0,2261,2239,2243,2263,4205,3917,3921,4207,43,4203,4206,4209,4210,0,2259,2262,2265,2266,4203,4206,4209,4210,43,4210,4209,4201,4196,0,2266,2265,2267,2268,4210,4209,4201,4196,43,4206,4208,4211,4209,0,2262,2264,2269,2265,4206,4208,4211,4209,43,4209,4211,4202,4201,0,2265,2269,2270,2267,4209,4211,4202,4201,43,4203,4210,4212,4213,0,2259,2266,910,909,4203,4210,4212,4213,43,4213,4212,4214,4215,0,909,910,910,909,4213,4212,4214,4215,43,4210,4196,4195,4212,0,2266,2268,911,910,4210,4196,4195,4212,43,4212,4195,4198,4214,0,910,911,911,910,4212,4195,4198,4214,43,4203,4213,4216,4204,0,2259,909,912,2260,4203,4213,4216,4204,43,4204,4216,3926,3918,0,2260,912,913,2240,4204,4216,3926,3918,43,4213,4215,4217,4216,0,909,909,912,912,4213,4215,4217,4216,43,4216,4217,3927,3926,0,912,912,913,913,4216,4217,3927,3926,43,4218,4219,4220,4221,0,2271,2272,12,12,4218,4219,4220,4221,43,4221,4220,4005,4004,0,12,12,12,12,4221,4220,4005,4004,43,4219,4222,4223,4220,0,2272,2273,12,12,4219,4222,4223,4220,43,4220,4223,4006,4005,0,12,12,12,12,4220,4223,4006,4005,43,4218,4221,4224,4225,0,2271,12,12,929,4218,4221,4224,4225,43,4225,4224,4226,4227,0,929,12,12,929,4225,4224,4226,4227,43,4221,4004,4015,4224,0,12,12,12,12,4221,4004,4015,4224,43,4224,4015,4017,4226,0,12,12,12,12,4224,4015,4017,4226,43,4218,4225,4228,4229,0,2271,929,928,2274,4218,4225,4228,4229,43,4229,4228,4191,4183,0,2274,928,931,2249,4229,4228,4191,4183,43,4225,4227,4230,4228,0,929,929,928,928,4225,4227,4230,4228,43,4228,4230,4192,4191,0,928,928,931,931,4228,4230,4192,4191,43,4218,4229,4231,4219,0,2271,2274,2275,2272,4218,4229,4231,4219,43,4219,4231,4232,4222,0,2272,2275,2276,2273,4219,4231,4232,4222,43,4229,4183,4182,4231,0,2274,2249,2248,2275,4229,4183,4182,4231,43,4231,4182,4186,4232,0,2275,2248,2252,2276,4231,4182,4186,4232,43,4233,4234,4235,4236,0,947,948,948,947,4233,4234,4235,4236,43,4236,4235,4237,4238,0,947,948,948,947,4236,4235,4237,4238,43,4234,4239,4240,4235,0,948,931,931,948,4234,4239,4240,4235,43,4235,4240,4241,4237,0,948,931,931,948,4235,4240,4241,4237,43,4233,4236,4242,4243,0,947,947,67,67,4233,4236,4242,4243,43,4243,4242,4244,4245,0,67,67,6,6,4243,4242,4244,4245,43,4236,4238,4246,4242,0,947,947,67,67,4236,4238,4246,4242,43,4242,4246,4247,4244,0,67,67,6,6,4242,4246,4247,4244,43,4233,4243,4248,4249,0,947,67,67,947,4233,4243,4248,4249,43,4249,4248,3776,3771,0,947,67,67,947,4249,4248,3776,3771,43,4243,4245,4250,4248,0,67,6,6,67,4243,4245,4250,4248,43,4248,4250,3777,3776,0,67,6,6,67,4248,4250,3777,3776,43,4233,4249,4251,4234,0,947,947,948,948,4233,4249,4251,4234,43,4234,4251,4252,4239,0,948,948,931,931,4234,4251,4252,4239,43,4249,3771,3770,4251,0,947,947,948,948,4249,3771,3770,4251,43,4251,3770,3773,4252,0,948,948,931,931,4251,3770,3773,4252,43,4253,4254,4255,4256,0,909,909,910,910,4253,4254,4255,4256,43,4256,4255,4250,4245,0,910,910,911,911,4256,4255,4250,4245,43,4254,3787,3786,4255,0,909,909,910,910,4254,3787,3786,4255,43,4255,3786,3777,4250,0,910,910,911,911,4255,3786,3777,4250,43,4253,4256,4257,4258,0,909,910,910,909,4253,4256,4257,4258,43,4258,4257,4259,4260,0,909,910,910,909,4258,4257,4259,4260,43,4256,4245,4244,4257,0,910,911,911,910,4256,4245,4244,4257,43,4257,4244,4247,4259,0,910,911,911,910,4257,4244,4247,4259,43,4253,4258,4261,4262,0,909,909,912,912,4253,4258,4261,4262,43,4262,4261,4263,4264,0,912,912,913,913,4262,4261,4263,4264,43,4258,4260,4265,4261,0,909,909,912,912,4258,4260,4265,4261,43,4261,4265,4266,4263,0,912,912,913,913,4261,4265,4266,4263,43,4253,4262,4267,4254,0,909,912,912,909,4253,4262,4267,4254,43,4254,4267,3792,3787,0,909,912,912,909,4254,4267,3792,3787,43,4262,4264,4268,4267,0,912,913,913,912,4262,4264,4268,4267,43,4267,4268,3793,3792,0,912,913,913,912,4267,4268,3793,3792,43,4269,4270,4271,4272,0,914,914,917,917,4269,4270,4271,4272,43,4272,4271,4268,4264,0,917,917,913,913,4272,4271,4268,4264,43,4270,3807,3806,4271,0,914,914,917,917,4270,3807,3806,4271,43,4271,3806,3793,4268,0,917,917,913,913,4271,3806,3793,4268,43,4269,4272,4273,4274,0,914,917,917,914,4269,4272,4273,4274,43,4274,4273,4275,4276,0,914,917,917,914,4274,4273,4275,4276,43,4272,4264,4263,4273,0,917,913,913,917,4272,4264,4263,4273,43,4273,4263,4266,4275,0,917,913,913,917,4273,4263,4266,4275,43,4269,4274,4277,4278,0,914,914,915,915,4269,4274,4277,4278,43,4278,4277,4279,4280,0,915,915,916,916,4278,4277,4279,4280,43,4274,4276,4281,4277,0,914,914,915,915,4274,4276,4281,4277,43,4277,4281,4282,4279,0,915,915,916,916,4277,4281,4282,4279,43,4269,4278,4283,4270,0,914,915,915,914,4269,4278,4283,4270,43,4270,4283,3812,3807,0,914,915,915,914,4270,4283,3812,3807,43,4278,4280,4284,4283,0,915,916,916,915,4278,4280,4284,4283,43,4283,4284,3813,3812,0,915,916,916,915,4283,4284,3813,3812,43,4285,4286,4287,4288,0,953,953,922,922,4285,4286,4287,4288,43,4288,4287,4284,4280,0,922,922,916,916,4288,4287,4284,4280,43,4286,3827,3826,4287,0,953,953,922,922,4286,3827,3826,4287,43,4287,3826,3813,4284,0,922,922,916,916,4287,3826,3813,4284,43,4285,4288,4289,4290,0,953,922,922,953,4285,4288,4289,4290,43,4290,4289,4291,4292,0,953,922,922,953,4290,4289,4291,4292,43,4288,4280,4279,4289,0,922,916,916,922,4288,4280,4279,4289,43,4289,4279,4282,4291,0,922,916,916,922,4289,4279,4282,4291,43,4285,4290,4293,4294,0,953,953,939,939,4285,4290,4293,4294,43,4294,4293,4295,4296,0,939,939,328,328,4294,4293,4295,4296,43,4290,4292,4297,4293,0,953,953,939,939,4290,4292,4297,4293,43,4293,4297,4298,4295,0,939,939,328,328,4293,4297,4298,4295,43,4285,4294,4299,4286,0,953,939,939,953,4285,4294,4299,4286,43,4286,4299,3832,3827,0,953,939,939,953,4286,4299,3832,3827,43,4294,4296,4300,4299,0,939,328,328,939,4294,4296,4300,4299,43,4299,4300,3833,3832,0,939,328,328,939,4299,4300,3833,3832,43,4301,4302,4303,4304,0,929,12,12,929,4301,4302,4303,4304,43,4304,4303,4305,4306,0,929,12,12,929,4304,4303,4305,4306,43,4302,4307,4308,4303,0,12,12,12,12,4302,4307,4308,4303,43,4303,4308,4309,4305,0,12,12,12,12,4303,4308,4309,4305,43,4301,4304,4310,4311,0,929,929,928,928,4301,4304,4310,4311,43,4311,4310,4240,4239,0,928,928,931,931,4311,4310,4240,4239,43,4304,4306,4312,4310,0,929,929,928,928,4304,4306,4312,4310,43,4310,4312,4241,4240,0,928,928,931,931,4310,4312,4241,4240,43,4301,4311,4313,4314,0,929,928,928,929,4301,4311,4313,4314,43,4314,4313,3857,3853,0,929,928,928,929,4314,4313,3857,3853,43,4311,4239,4252,4313,0,928,931,931,928,4311,4239,4252,4313,43,4313,4252,3773,3857,0,928,931,931,928,4313,4252,3773,3857,43,4301,4314,4315,4302,0,929,929,12,12,4301,4314,4315,4302,43,4302,4315,4316,4307,0,12,12,12,12,4302,4315,4316,4307,43,4314,3853,3852,4315,0,929,929,12,12,4314,3853,3852,4315,43,4315,3852,3855,4316,0,12,12,12,12,4315,3852,3855,4316,43,4317,4318,4319,4320,0,12,12,12,12,4317,4318,4319,4320,43,4320,4319,4321,4322,0,12,12,12,12,4320,4319,4321,4322,43,4318,4323,4324,4319,0,12,12,12,12,4318,4323,4324,4319,43,4319,4324,4325,4321,0,12,12,12,12,4319,4324,4325,4321,43,4317,4320,4326,4327,0,12,12,12,12,4317,4320,4326,4327,43,4327,4326,4328,4329,0,12,12,12,12,4327,4326,4328,4329,43,4320,4322,4330,4326,0,12,12,12,12,4320,4322,4330,4326,43,4326,4330,4331,4328,0,12,12,12,12,4326,4330,4331,4328,43,4317,4327,4332,4333,0,12,12,12,12,4317,4327,4332,4333,43,4333,4332,3881,3876,0,12,12,12,12,4333,4332,3881,3876,43,4327,4329,4334,4332,0,12,12,12,12,4327,4329,4334,4332,43,4332,4334,3882,3881,0,12,12,12,12,4332,4334,3882,3881,43,4317,4333,4335,4318,0,12,12,12,12,4317,4333,4335,4318,43,4318,4335,4336,4323,0,12,12,12,12,4318,4335,4336,4323,43,4333,3876,3875,4335,0,12,12,12,12,4333,3876,3875,4335,43,4335,3875,3878,4336,0,12,12,12,12,4335,3875,3878,4336,43,4337,4338,4339,4340,0,932,932,933,933,4337,4338,4339,4340,43,4340,4339,4300,4296,0,933,933,328,328,4340,4339,4300,4296,43,4338,3892,3891,4339,0,932,932,933,933,4338,3892,3891,4339,43,4339,3891,3833,4300,0,933,933,328,328,4339,3891,3833,4300,43,4337,4340,4341,4342,0,932,933,933,932,4337,4340,4341,4342,43,4342,4341,4343,4344,0,932,933,933,932,4342,4341,4343,4344,43,4340,4296,4295,4341,0,933,328,328,933,4340,4296,4295,4341,43,4341,4295,4298,4343,0,933,328,328,933,4341,4295,4298,4343,43,4337,4342,4345,4346,0,932,932,12,12,4337,4342,4345,4346,43,4346,4345,4324,4323,0,12,12,12,12,4346,4345,4324,4323,43,4342,4344,4347,4345,0,932,932,12,12,4342,4344,4347,4345,43,4345,4347,4325,4324,0,12,12,12,12,4345,4347,4325,4324,43,4337,4346,4348,4338,0,932,12,12,932,4337,4346,4348,4338,43,4338,4348,3895,3892,0,932,12,12,932,4338,4348,3895,3892,43,4346,4323,4336,4348,0,12,12,12,12,4346,4323,4336,4348,43,4348,4336,3878,3895,0,12,12,12,12,4348,4336,3878,3895,43,4349,4350,4351,4352,0,12,12,12,12,4349,4350,4351,4352,43,4352,4351,4353,4354,0,12,12,12,12,4352,4351,4353,4354,43,4350,4329,4328,4351,0,12,12,12,12,4350,4329,4328,4351,43,4351,4328,4331,4353,0,12,12,12,12,4351,4328,4331,4353,43,4349,4352,4355,4356,0,12,12,12,12,4349,4352,4355,4356,43,4356,4355,4308,4307,0,12,12,12,12,4356,4355,4308,4307,43,4352,4354,4357,4355,0,12,12,12,12,4352,4354,4357,4355,43,4355,4357,4309,4308,0,12,12,12,12,4355,4357,4309,4308,43,4349,4356,4358,4359,0,12,12,12,12,4349,4356,4358,4359,43,4359,4358,3912,3910,0,12,12,12,12,4359,4358,3912,3910,43,4356,4307,4316,4358,0,12,12,12,12,4356,4307,4316,4358,43,4358,4316,3855,3912,0,12,12,12,12,4358,4316,3855,3912,43,4349,4359,4360,4350,0,12,12,12,12,4349,4359,4360,4350,43,4350,4360,4334,4329,0,12,12,12,12,4350,4360,4334,4329,43,4359,3910,3909,4360,0,12,12,12,12,4359,3910,3909,4360,43,4360,3909,3882,4334,0,12,12,12,12,4360,3909,3882,4334,43,4361,4362,4363,4364,0,2277,948,948,947,4361,4362,4363,4364,43,4364,4363,3766,3758,0,947,948,948,947,4364,4363,3766,3758,43,4362,4365,4366,4363,0,948,931,931,948,4362,4365,4366,4363,43,4363,4366,3767,3766,0,948,931,931,948,4363,4366,3767,3766,43,4361,4364,4367,4368,0,2277,947,67,2278,4361,4364,4367,4368,43,4368,4367,4369,4370,0,2278,67,6,2279,4368,4367,4369,4370,43,4364,3758,3757,4367,0,947,947,67,67,4364,3758,3757,4367,43,4367,3757,3761,4369,0,67,67,6,6,4367,3757,3761,4369,43,4361,4368,4371,4372,0,2277,2278,2280,2281,4361,4368,4371,4372,43,4372,4371,4373,4374,0,2281,2280,2282,2283,4372,4371,4373,4374,43,4368,4370,4375,4371,0,2278,2279,2284,2280,4368,4370,4375,4371,43,4371,4375,4376,4373,0,2280,2284,2285,2282,4371,4375,4376,4373,43,4361,4372,4377,4362,0,2277,2281,948,948,4361,4372,4377,4362,43,4362,4377,4378,4365,0,948,948,931,931,4362,4377,4378,4365,43,4372,4374,4379,4377,0,2281,2283,948,948,4372,4374,4379,4377,43,4377,4379,4380,4378,0,948,948,931,931,4377,4379,4380,4378,43,4381,4382,4383,4384,0,2205,2206,910,910,4381,4382,4383,4384,43,4384,4383,4375,4370,0,910,910,911,911,4384,4383,4375,4370,43,4382,4385,4386,4383,0,2206,2286,910,910,4382,4385,4386,4383,43,4383,4386,4376,4375,0,910,910,911,911,4383,4386,4376,4375,43,4381,4384,4387,4388,0,2205,910,910,909,4381,4384,4387,4388,43,4388,4387,3783,3782,0,909,910,910,909,4388,4387,3783,3782,43,4384,4370,4369,4387,0,910,911,911,910,4384,4370,4369,4387,43,4387,4369,3761,3783,0,910,911,911,910,4387,4369,3761,3783,43,4381,4388,4389,4390,0,2205,909,912,2207,4381,4388,4389,4390,43,4390,4389,4391,4392,0,2207,912,913,2287,4390,4389,4391,4392,43,4388,3782,3795,4389,0,909,909,912,912,4388,3782,3795,4389,43,4389,3795,3797,4391,0,912,912,913,913,4389,3795,3797,4391,43,4381,4390,4393,4382,0,2205,2207,2288,2206,4381,4390,4393,4382,43,4382,4393,4394,4385,0,2206,2288,2289,2286,4382,4393,4394,4385,43,4390,4392,4395,4393,0,2207,2287,2290,2288,4390,4392,4395,4393,43,4393,4395,4396,4394,0,2288,2290,382,2289,4393,4395,4396,4394,43,4397,4398,4399,4400,0,2291,2292,2293,2222,4397,4398,4399,4400,43,4400,4399,4395,4392,0,2222,2293,2290,2287,4400,4399,4395,4392,43,4398,4401,4402,4399,0,2292,2294,2295,2293,4398,4401,4402,4399,43,4399,4402,4396,4395,0,2293,2295,382,2290,4399,4402,4396,4395,43,4397,4400,4403,4404,0,2291,2222,917,914,4397,4400,4403,4404,43,4404,4403,3803,3802,0,914,917,917,914,4404,4403,3803,3802,43,4400,4392,4391,4403,0,2222,2287,913,917,4400,4392,4391,4403,43,4403,4391,3797,3803,0,917,913,913,917,4403,4391,3797,3803,43,4397,4404,4405,4406,0,2291,914,915,915,4397,4404,4405,4406,43,4406,4405,4407,4408,0,915,915,916,916,4406,4405,4407,4408,43,4404,3802,3815,4405,0,914,914,915,915,4404,3802,3815,4405,43,4405,3815,3817,4407,0,915,915,916,916,4405,3815,3817,4407,43,4397,4406,4409,4398,0,2291,915,915,2292,4397,4406,4409,4398,43,4398,4409,4410,4401,0,2292,915,915,2294,4398,4409,4410,4401,43,4406,4408,4411,4409,0,915,916,916,915,4406,4408,4411,4409,43,4409,4411,4412,4410,0,915,916,916,915,4409,4411,4412,4410,43,4413,4414,4415,4416,0,953,953,922,922,4413,4414,4415,4416,43,4416,4415,4411,4408,0,922,922,916,916,4416,4415,4411,4408,43,4414,4417,4418,4415,0,953,953,922,922,4414,4417,4418,4415,43,4415,4418,4412,4411,0,922,922,916,916,4415,4418,4412,4411,43,4413,4416,4419,4420,0,953,922,922,953,4413,4416,4419,4420,43,4420,4419,3823,3822,0,953,922,922,953,4420,4419,3823,3822,43,4416,4408,4407,4419,0,922,916,916,922,4416,4408,4407,4419,43,4419,4407,3817,3823,0,922,916,916,922,4419,4407,3817,3823,43,4413,4420,4421,4422,0,953,953,939,939,4413,4420,4421,4422,43,4422,4421,4423,4424,0,939,939,328,328,4422,4421,4423,4424,43,4420,3822,3835,4421,0,953,953,939,939,4420,3822,3835,4421,43,4421,3835,3837,4423,0,939,939,328,328,4421,3835,3837,4423,43,4413,4422,4425,4414,0,953,939,939,953,4413,4422,4425,4414,43,4414,4425,4426,4417,0,953,939,939,953,4414,4425,4426,4417,43,4422,4424,4427,4425,0,939,328,328,939,4422,4424,4427,4425,43,4425,4427,4428,4426,0,939,328,328,939,4425,4427,4428,4426,43,4429,4430,4431,4432,0,929,12,12,929,4429,4430,4431,4432,43,4432,4431,3848,3843,0,929,12,12,929,4432,4431,3848,3843,43,4430,4433,4434,4431,0,12,12,12,12,4430,4433,4434,4431,43,4431,4434,3849,3848,0,12,12,12,12,4431,4434,3849,3848,43,4429,4432,4435,4436,0,929,929,928,928,4429,4432,4435,4436,43,4436,4435,4366,4365,0,928,928,931,931,4436,4435,4366,4365,43,4432,3843,3842,4435,0,929,929,928,928,4432,3843,3842,4435,43,4435,3842,3767,4366,0,928,928,931,931,4435,3842,3767,4366,43,4429,4436,4437,4438,0,929,928,928,929,4429,4436,4437,4438,43,4438,4437,4439,4440,0,929,928,928,929,4438,4437,4439,4440,43,4436,4365,4378,4437,0,928,931,931,928,4436,4365,4378,4437,43,4437,4378,4380,4439,0,928,931,931,928,4437,4378,4380,4439,43,4429,4438,4441,4430,0,929,929,12,12,4429,4438,4441,4430,43,4430,4441,4442,4433,0,12,12,12,12,4430,4441,4442,4433,43,4438,4440,4443,4441,0,929,929,12,12,4438,4440,4443,4441,43,4441,4443,4444,4442,0,12,12,12,12,4441,4443,4444,4442,43,4445,4446,4447,4448,0,12,12,12,12,4445,4446,4447,4448,43,4448,4447,4449,4450,0,12,12,12,12,4448,4447,4449,4450,43,4446,4451,4452,4447,0,12,12,12,12,4446,4451,4452,4447,43,4447,4452,4453,4449,0,12,12,12,12,4447,4452,4453,4449,43,4445,4448,4454,4455,0,12,12,12,12,4445,4448,4454,4455,43,4455,4454,3871,3863,0,12,12,12,12,4455,4454,3871,3863,43,4448,4450,4456,4454,0,12,12,12,12,4448,4450,4456,4454,43,4454,4456,3872,3871,0,12,12,12,12,4454,4456,3872,3871,43,4445,4455,4457,4458,0,12,12,12,12,4445,4455,4457,4458,43,4458,4457,4459,4460,0,12,12,12,12,4458,4457,4459,4460,43,4455,3863,3862,4457,0,12,12,12,12,4455,3863,3862,4457,43,4457,3862,3866,4459,0,12,12,12,12,4457,3862,3866,4459,43,4445,4458,4461,4446,0,12,12,12,12,4445,4458,4461,4446,43,4446,4461,4462,4451,0,12,12,12,12,4446,4461,4462,4451,43,4458,4460,4463,4461,0,12,12,12,12,4458,4460,4463,4461,43,4461,4463,4464,4462,0,12,12,12,12,4461,4463,4464,4462,43,4465,4466,4467,4468,0,932,933,933,932,4465,4466,4467,4468,43,4468,4467,3888,3887,0,932,933,933,932,4468,4467,3888,3887,43,4466,4424,4423,4467,0,933,328,328,933,4466,4424,4423,4467,43,4467,4423,3837,3888,0,933,328,328,933,4467,4423,3837,3888,43,4465,4468,4469,4470,0,932,932,12,12,4465,4468,4469,4470,43,4470,4469,4456,4450,0,12,12,12,12,4470,4469,4456,4450,43,4468,3887,3897,4469,0,932,932,12,12,4468,3887,3897,4469,43,4469,3897,3872,4456,0,12,12,12,12,4469,3897,3872,4456,43,4465,4470,4471,4472,0,932,12,12,932,4465,4470,4471,4472,43,4472,4471,4473,4474,0,932,12,12,932,4472,4471,4473,4474,43,4470,4450,4449,4471,0,12,12,12,12,4470,4450,4449,4471,43,4471,4449,4453,4473,0,12,12,12,12,4471,4449,4453,4473,43,4465,4472,4475,4466,0,932,932,933,933,4465,4472,4475,4466,43,4466,4475,4427,4424,0,933,933,328,328,4466,4475,4427,4424,43,4472,4474,4476,4475,0,932,932,933,933,4472,4474,4476,4475,43,4475,4476,4428,4427,0,933,933,328,328,4475,4476,4428,4427,43,4477,4478,4479,4480,0,12,12,12,12,4477,4478,4479,4480,43,4480,4479,3906,3903,0,12,12,12,12,4480,4479,3906,3903,43,4478,4460,4459,4479,0,12,12,12,12,4478,4460,4459,4479,43,4479,4459,3866,3906,0,12,12,12,12,4479,4459,3866,3906,43,4477,4480,4481,4482,0,12,12,12,12,4477,4480,4481,4482,43,4482,4481,4434,4433,0,12,12,12,12,4482,4481,4434,4433,43,4480,3903,3902,4481,0,12,12,12,12,4480,3903,3902,4481,43,4481,3902,3849,4434,0,12,12,12,12,4481,3902,3849,4434,43,4477,4482,4483,4484,0,12,12,12,12,4477,4482,4483,4484,43,4484,4483,4485,4486,0,12,12,12,12,4484,4483,4485,4486,43,4482,4433,4442,4483,0,12,12,12,12,4482,4433,4442,4483,43,4483,4442,4444,4485,0,12,12,12,12,4483,4442,4444,4485,43,4477,4484,4487,4478,0,12,12,12,12,4477,4484,4487,4478,43,4478,4487,4463,4460,0,12,12,12,12,4478,4487,4463,4460,43,4484,4486,4488,4487,0,12,12,12,12,4484,4486,4488,4487,43,4487,4488,4464,4463,0,12,12,12,12,4487,4488,4464,4463,43,4489,4490,4491,4492,0,2296,2297,2298,2299,4489,4490,4491,4492,43,4492,4491,4493,4494,0,2299,2298,2300,2301,4492,4491,4493,4494,43,4490,4495,4496,4491,0,2297,2302,2303,2298,4490,4495,4496,4491,43,4491,4496,4497,4493,0,2298,2303,2304,2300,4491,4496,4497,4493,43,4489,4492,4498,4499,0,2296,2299,2305,2306,4489,4492,4498,4499,43,4499,4498,4500,4501,0,2306,2305,2307,2308,4499,4498,4500,4501,43,4492,4494,4502,4498,0,2299,2301,2309,2305,4492,4494,4502,4498,43,4498,4502,4503,4500,0,2305,2309,2310,2307,4498,4502,4503,4500,43,4489,4499,4504,4505,0,2296,2306,2311,2312,4489,4499,4504,4505,43,4505,4504,4506,4507,0,2312,2311,2313,2314,4505,4504,4506,4507,43,4499,4501,4508,4504,0,2306,2308,2315,2311,4499,4501,4508,4504,43,4504,4508,4509,4506,0,2311,2315,2316,2313,4504,4508,4509,4506,43,4489,4505,4510,4490,0,2296,2312,2317,2297,4489,4505,4510,4490,43,4490,4510,4511,4495,0,2297,2317,2318,2302,4490,4510,4511,4495,43,4505,4507,4512,4510,0,2312,2314,2319,2317,4505,4507,4512,4510,43,4510,4512,4513,4511,0,2317,2319,2320,2318,4510,4512,4513,4511,43,4514,4515,4516,4517,0,2321,2322,2323,2324,4514,4515,4516,4517,43,4517,4516,4518,4519,0,2324,2323,2325,2326,4517,4516,4518,4519,43,4515,4520,4521,4516,0,2322,2327,2328,2323,4515,4520,4521,4516,43,4516,4521,4522,4518,0,2323,2328,2329,2325,4516,4521,4522,4518,43,4514,4517,4523,4524,0,2321,2324,12,12,4514,4517,4523,4524,43,4524,4523,4525,4526,0,12,12,12,12,4524,4523,4525,4526,43,4517,4519,4527,4523,0,2324,2326,12,12,4517,4519,4527,4523,43,4523,4527,4528,4525,0,12,12,12,12,4523,4527,4528,4525,43,4514,4524,4529,4530,0,2321,12,12,2330,4514,4524,4529,4530,43,4530,4529,4531,4532,0,2330,12,12,2331,4530,4529,4531,4532,43,4524,4526,4533,4529,0,12,12,12,12,4524,4526,4533,4529,43,4529,4533,4534,4531,0,12,12,12,12,4529,4533,4534,4531,43,4514,4530,4535,4515,0,2321,2330,2332,2322,4514,4530,4535,4515,43,4515,4535,4536,4520,0,2322,2332,2333,2327,4515,4535,4536,4520,43,4530,4532,4537,4535,0,2330,2331,2334,2332,4530,4532,4537,4535,43,4535,4537,4538,4536,0,2332,2334,2335,2333,4535,4537,4538,4536,43,4539,4540,4541,4542,0,2336,2337,12,12,4539,4540,4541,4542,43,4542,4541,4533,4526,0,12,12,12,12,4542,4541,4533,4526,43,4540,4543,4544,4541,0,2337,2338,12,12,4540,4543,4544,4541,43,4541,4544,4534,4533,0,12,12,12,12,4541,4544,4534,4533,43,4539,4542,4545,4546,0,2336,12,12,2339,4539,4542,4545,4546,43,4546,4545,4547,4548,0,2339,12,12,2340,4546,4545,4547,4548,43,4542,4526,4525,4545,0,12,12,12,12,4542,4526,4525,4545,43,4545,4525,4528,4547,0,12,12,12,12,4545,4525,4528,4547,43,4539,4546,4549,4550,0,2336,2339,2341,2342,4539,4546,4549,4550,43,4550,4549,4551,4552,0,2342,2341,2343,2344,4550,4549,4551,4552,43,4546,4548,4553,4549,0,2339,2340,2345,2341,4546,4548,4553,4549,43,4549,4553,4554,4551,0,2341,2345,2346,2343,4549,4553,4554,4551,43,4539,4550,4555,4540,0,2336,2342,2347,2337,4539,4550,4555,4540,43,4540,4555,4556,4543,0,2337,2347,2348,2338,4540,4555,4556,4543,43,4550,4552,4557,4555,0,2342,2344,2349,2347,4550,4552,4557,4555,43,4555,4557,4558,4556,0,2347,2349,2350,2348,4555,4557,4558,4556,43,4559,4560,4561,4562,0,2351,2352,933,932,4559,4560,4561,4562,43,4562,4561,3988,3987,0,932,933,933,932,4562,4561,3988,3987,43,4560,4563,4564,4561,0,2352,2353,328,933,4560,4563,4564,4561,43,4561,4564,3946,3988,0,933,328,328,933,4561,4564,3946,3988,43,4559,4562,4565,4566,0,2351,932,12,2354,4559,4562,4565,4566,43,4566,4565,4567,4568,0,2354,12,12,2355,4566,4565,4567,4568,43,4562,3987,3997,4565,0,932,932,12,12,4562,3987,3997,4565,43,4565,3997,3966,4567,0,12,12,12,12,4565,3997,3966,4567,43,4559,4566,4569,4570,0,2351,2354,2356,2357,4559,4566,4569,4570,43,4570,4569,4571,4572,0,2357,2356,2358,2359,4570,4569,4571,4572,43,4566,4568,4573,4569,0,2354,2355,2360,2356,4566,4568,4573,4569,43,4569,4573,4574,4571,0,2356,2360,2361,2358,4569,4573,4574,4571,43,4559,4570,4575,4560,0,2351,2357,2362,2352,4559,4570,4575,4560,43,4560,4575,4576,4563,0,2352,2362,2363,2353,4560,4575,4576,4563,43,4570,4572,4577,4575,0,2357,2359,2364,2362,4570,4572,4577,4575,43,4575,4577,4578,4576,0,2362,2364,2365,2363,4575,4577,4578,4576,43,4579,4580,4581,4582,0,2366,2367,2368,2369,4579,4580,4581,4582,43,4582,4581,4583,4584,0,2369,2368,2370,2371,4582,4581,4583,4584,43,4580,4585,4586,4581,0,2367,2372,2373,2368,4580,4585,4586,4581,43,4581,4586,4587,4583,0,2368,2373,2374,2370,4581,4586,4587,4583,43,4579,4582,4588,4589,0,2366,2369,922,953,4579,4582,4588,4589,43,4589,4588,3949,3943,0,953,922,922,953,4589,4588,3949,3943,43,4582,4584,4590,4588,0,2369,2371,916,922,4582,4584,4590,4588,43,4588,4590,3937,3949,0,922,916,916,922,4588,4590,3937,3949,43,4579,4589,4591,4592,0,2366,953,939,2375,4579,4589,4591,4592,43,4592,4591,4564,4563,0,2375,939,328,2353,4592,4591,4564,4563,43,4589,3943,3942,4591,0,953,953,939,939,4589,3943,3942,4591,43,4591,3942,3946,4564,0,939,939,328,328,4591,3942,3946,4564,43,4579,4592,4593,4580,0,2366,2375,2376,2367,4579,4592,4593,4580,43,4580,4593,4594,4585,0,2367,2376,2377,2372,4580,4593,4594,4585,43,4592,4563,4576,4593,0,2375,2353,2363,2376,4592,4563,4576,4593,43,4593,4576,4578,4594,0,2376,2363,2365,2377,4593,4576,4578,4594,43,4595,4596,4597,4598,0,2378,2379,2380,2381,4595,4596,4597,4598,43,4598,4597,4599,4600,0,2381,2380,2382,2383,4598,4597,4599,4600,43,4596,4601,4602,4597,0,2379,2384,2385,2380,4596,4601,4602,4597,43,4597,4602,4603,4599,0,2380,2385,2386,2382,4597,4602,4603,4599,43,4595,4598,4604,4605,0,2378,2381,2387,2388,4595,4598,4604,4605,43,4605,4604,3920,3919,0,2388,2387,2242,2241,4605,4604,3920,3919,43,4598,4600,4606,4604,0,2381,2383,2389,2387,4598,4600,4606,4604,43,4604,4606,3921,3920,0,2387,2389,2243,2242,4604,4606,3921,3920,43,4595,4605,4607,4608,0,2378,2388,915,2390,4595,4605,4607,4608,43,4608,4607,4590,4584,0,2390,915,916,2371,4608,4607,4590,4584,43,4605,3919,3935,4607,0,2388,2241,915,915,4605,3919,3935,4607,43,4607,3935,3937,4590,0,915,915,916,916,4607,3935,3937,4590,43,4595,4608,4609,4596,0,2378,2390,2391,2379,4595,4608,4609,4596,43,4596,4609,4610,4601,0,2379,2391,2392,2384,4596,4609,4610,4601,43,4608,4584,4583,4609,0,2390,2371,2370,2391,4608,4584,4583,4609,43,4609,4583,4587,4610,0,2391,2370,2374,2392,4609,4583,4587,4610,43,4611,4612,4613,4614,0,2393,2394,2395,2396,4611,4612,4613,4614,43,4614,4613,4615,4616,0,2396,2395,2397,2398,4614,4613,4615,4616,43,4612,4501,4500,4613,0,2394,2308,2307,2395,4612,4501,4500,4613,43,4613,4500,4503,4615,0,2395,2307,2310,2397,4613,4500,4503,4615,43,4611,4614,4617,4618,0,2393,2396,2399,2400,4611,4614,4617,4618,43,4618,4617,4606,4600,0,2400,2399,2389,2383,4618,4617,4606,4600,43,4614,4616,4619,4617,0,2396,2398,2401,2399,4614,4616,4619,4617,43,4617,4619,3921,4606,0,2399,2401,2243,2389,4617,4619,3921,4606,43,4611,4618,4620,4621,0,2393,2400,2402,2403,4611,4618,4620,4621,43,4621,4620,4622,4623,0,2403,2402,2404,2405,4621,4620,4622,4623,43,4618,4600,4599,4620,0,2400,2383,2382,2402,4618,4600,4599,4620,43,4620,4599,4603,4622,0,2402,2382,2386,2404,4620,4599,4603,4622,43,4611,4621,4624,4612,0,2393,2403,2406,2394,4611,4621,4624,4612,43,4612,4624,4508,4501,0,2394,2406,2315,2308,4612,4624,4508,4501,43,4621,4623,4625,4624,0,2403,2405,2407,2406,4621,4623,4625,4624,43,4624,4625,4509,4508,0,2406,2407,2316,2315,4624,4625,4509,4508,43,4626,4627,4628,4629,0,2408,12,12,2409,4626,4627,4628,4629,43,4629,4628,4223,4222,0,2409,12,12,2273,4629,4628,4223,4222,43,4627,4630,4631,4628,0,12,12,12,12,4627,4630,4631,4628,43,4628,4631,4006,4223,0,12,12,12,12,4628,4631,4006,4223,43,4626,4629,4632,4633,0,2408,2409,2410,2411,4626,4629,4632,4633,43,4633,4632,4634,4635,0,2411,2410,2412,2413,4633,4632,4634,4635,43,4629,4222,4232,4632,0,2409,2273,2276,2410,4629,4222,4232,4632,43,4632,4232,4186,4634,0,2410,2276,2252,2412,4632,4232,4186,4634,43,4626,4633,4636,4637,0,2408,2411,2414,2415,4626,4633,4636,4637,43,4637,4636,4553,4548,0,2415,2414,2345,2340,4637,4636,4553,4548,43,4633,4635,4638,4636,0,2411,2413,2416,2414,4633,4635,4638,4636,43,4636,4638,4554,4553,0,2414,2416,2346,2345,4636,4638,4554,4553,43,4626,4637,4639,4627,0,2408,2415,12,12,4626,4637,4639,4627,43,4627,4639,4640,4630,0,12,12,12,12,4627,4639,4640,4630,43,4637,4548,4547,4639,0,2415,2340,12,12,4637,4548,4547,4639,43,4639,4547,4528,4640,0,12,12,12,12,4639,4547,4528,4640,43,4641,4642,4643,4644,0,2417,2418,2419,2420,4641,4642,4643,4644,43,4644,4643,4645,4646,0,2420,2419,2421,2422,4644,4643,4645,4646,43,4642,4647,4648,4643,0,2418,2423,2424,2419,4642,4647,4648,4643,43,4643,4648,4649,4645,0,2419,2424,2425,2421,4643,4648,4649,4645,43,4641,4644,4650,4651,0,2417,2420,12,12,4641,4644,4650,4651,43,4651,4650,4631,4630,0,12,12,12,12,4651,4650,4631,4630,43,4644,4646,4652,4650,0,2420,2422,12,12,4644,4646,4652,4650,43,4650,4652,4006,4631,0,12,12,12,12,4650,4652,4006,4631,43,4641,4651,4653,4654,0,2417,12,12,2426,4641,4651,4653,4654,43,4654,4653,4527,4519,0,2426,12,12,2326,4654,4653,4527,4519,43,4651,4630,4640,4653,0,12,12,12,12,4651,4630,4640,4653,43,4653,4640,4528,4527,0,12,12,12,12,4653,4640,4528,4527,43,4641,4654,4655,4642,0,2417,2426,2427,2418,4641,4654,4655,4642,43,4642,4655,4656,4647,0,2418,2427,2428,2423,4642,4655,4656,4647,43,4654,4519,4518,4655,0,2426,2326,2325,2427,4654,4519,4518,4655,43,4655,4518,4522,4656,0,2427,2325,2329,2428,4655,4518,4522,4656,43,4657,4658,4659,4660,0,2429,2430,12,12,4657,4658,4659,4660,43,4660,4659,4009,4003,0,12,12,12,12,4660,4659,4009,4003,43,4658,4661,4662,4659,0,2430,2431,12,12,4658,4661,4662,4659,43,4659,4662,3982,4009,0,12,12,12,12,4659,4662,3982,4009,43,4657,4660,4663,4664,0,2429,12,12,2432,4657,4660,4663,4664,43,4664,4663,4652,4646,0,2432,12,12,2422,4664,4663,4652,4646,43,4660,4003,4002,4663,0,12,12,12,12,4660,4003,4002,4663,43,4663,4002,4006,4652,0,12,12,12,12,4663,4002,4006,4652,43,4657,4664,4665,4666,0,2429,2432,2433,2434,4657,4664,4665,4666,43,4666,4665,4667,4668,0,2434,2433,2435,2436,4666,4665,4667,4668,43,4664,4646,4645,4665,0,2432,2422,2421,2433,4664,4646,4645,4665,43,4665,4645,4649,4667,0,2433,2421,2425,2435,4665,4645,4649,4667,43,4657,4666,4669,4658,0,2429,2434,2437,2430,4657,4666,4669,4658,43,4658,4669,4670,4661,0,2430,2437,2438,2431,4658,4669,4670,4661,43,4666,4668,4671,4669,0,2434,2436,2439,2437,4666,4668,4671,4669,43,4669,4671,4672,4670,0,2437,2439,2440,2438,4669,4671,4672,4670,43,4673,4674,4675,4676,0,2441,2442,2356,2354,4673,4674,4675,4676,43,4676,4675,4573,4568,0,2354,2356,2360,2355,4676,4675,4573,4568,43,4674,4677,4678,4675,0,2442,2443,2358,2356,4674,4677,4678,4675,43,4675,4678,4574,4573,0,2356,2358,2361,2360,4675,4678,4574,4573,43,4673,4676,4679,4680,0,2441,2354,12,12,4673,4676,4679,4680,43,4680,4679,3965,3964,0,12,12,12,12,4680,4679,3965,3964,43,4676,4568,4567,4679,0,2354,2355,12,12,4676,4568,4567,4679,43,4679,4567,3966,3965,0,12,12,12,12,4679,4567,3966,3965,43,4673,4680,4681,4682,0,2441,12,12,2444,4673,4680,4681,4682,43,4682,4681,4662,4661,0,2444,12,12,2431,4682,4681,4662,4661,43,4680,3964,3980,4681,0,12,12,12,12,4680,3964,3980,4681,43,4681,3980,3982,4662,0,12,12,12,12,4681,3980,3982,4662,43,4673,4682,4683,4674,0,2441,2444,2445,2442,4673,4682,4683,4674,43,4674,4683,4684,4677,0,2442,2445,2446,2443,4674,4683,4684,4677,43,4682,4661,4670,4683,0,2444,2431,2438,2445,4682,4661,4670,4683,43,4683,4670,4672,4684,0,2445,2438,2440,2446,4683,4670,4672,4684,43,4685,4686,4687,4688,0,947,947,948,948,4685,4686,4687,4688,43,4688,4687,4689,4690,0,948,948,931,931,4688,4687,4689,4690,43,4686,4190,4189,4687,0,947,947,948,948,4686,4190,4189,4687,43,4687,4189,4192,4689,0,948,948,931,931,4687,4189,4192,4689,43,4685,4688,4691,4692,0,947,948,948,947,4685,4688,4691,4692,43,4692,4691,4025,4024,0,947,948,948,947,4692,4691,4025,4024,43,4688,4690,4693,4691,0,948,931,931,948,4688,4690,4693,4691,43,4691,4693,4026,4025,0,948,931,931,948,4691,4693,4026,4025,43,4685,4692,4694,4695,0,947,947,67,67,4685,4692,4694,4695,43,4695,4694,4696,4697,0,67,67,6,6,4695,4694,4696,4697,43,4692,4024,4040,4694,0,947,947,67,67,4692,4024,4040,4694,43,4694,4040,4042,4696,0,67,67,6,6,4694,4040,4042,4696,43,4685,4695,4698,4686,0,947,67,67,947,4685,4695,4698,4686,43,4686,4698,4197,4190,0,947,67,67,947,4686,4698,4197,4190,43,4695,4697,4699,4698,0,67,6,6,67,4695,4697,4699,4698,43,4698,4699,4198,4197,0,67,6,6,67,4698,4699,4198,4197,43,4700,4701,4702,4703,0,2277,2281,2280,2278,4700,4701,4702,4703,43,4703,4702,4704,4705,0,2278,2280,2284,2279,4703,4702,4704,4705,43,4701,4706,4707,4702,0,2281,2283,2282,2280,4701,4706,4707,4702,43,4702,4707,4708,4704,0,2280,2282,2285,2284,4702,4707,4708,4704,43,4700,4703,4709,4710,0,2277,2278,67,947,4700,4703,4709,4710,43,4710,4709,4037,4030,0,947,67,67,947,4710,4709,4037,4030,43,4703,4705,4711,4709,0,2278,2279,6,67,4703,4705,4711,4709,43,4709,4711,4038,4037,0,67,6,6,67,4709,4711,4038,4037,43,4700,4710,4712,4713,0,2277,947,948,948,4700,4710,4712,4713,43,4713,4712,4714,4715,0,948,948,931,931,4713,4712,4714,4715,43,4710,4030,4029,4712,0,947,947,948,948,4710,4030,4029,4712,43,4712,4029,4032,4714,0,948,948,931,931,4712,4029,4032,4714,43,4700,4713,4716,4701,0,2277,948,948,2281,4700,4713,4716,4701,43,4701,4716,4717,4706,0,2281,948,948,2283,4701,4716,4717,4706,43,4713,4715,4718,4716,0,948,931,931,948,4713,4715,4718,4716,43,4716,4718,4719,4717,0,948,931,931,948,4716,4718,4719,4717,43,4720,4721,4722,4723,0,909,909,910,910,4720,4721,4722,4723,43,4723,4722,4699,4697,0,910,910,911,911,4723,4722,4699,4697,43,4721,4215,4214,4722,0,909,909,910,910,4721,4215,4214,4722,43,4722,4214,4198,4699,0,910,910,911,911,4722,4214,4198,4699,43,4720,4723,4724,4725,0,909,910,910,909,4720,4723,4724,4725,43,4725,4724,4048,4047,0,909,910,910,909,4725,4724,4048,4047,43,4723,4697,4696,4724,0,910,911,911,910,4723,4697,4696,4724,43,4724,4696,4042,4048,0,910,911,911,910,4724,4696,4042,4048,43,4720,4725,4726,4727,0,909,909,912,912,4720,4725,4726,4727,43,4727,4726,4728,4729,0,912,912,913,913,4727,4726,4728,4729,43,4725,4047,4060,4726,0,909,909,912,912,4725,4047,4060,4726,43,4726,4060,4062,4728,0,912,912,913,913,4726,4060,4062,4728,43,4720,4727,4730,4721,0,909,912,912,909,4720,4727,4730,4721,43,4721,4730,4217,4215,0,909,912,912,909,4721,4730,4217,4215,43,4727,4729,4731,4730,0,912,913,913,912,4727,4729,4731,4730,43,4730,4731,3927,4217,0,912,913,913,912,4730,4731,3927,4217,43,4732,4733,4734,4735,0,2205,2206,2288,2207,4732,4733,4734,4735,43,4735,4734,4736,4737,0,2207,2288,2290,2287,4735,4734,4736,4737,43,4733,4738,4739,4734,0,2206,2286,2289,2288,4733,4738,4739,4734,43,4734,4739,4740,4736,0,2288,2289,382,2290,4734,4739,4740,4736,43,4732,4735,4741,4742,0,2205,2207,912,909,4732,4735,4741,4742,43,4742,4741,4057,4052,0,909,912,912,909,4742,4741,4057,4052,43,4735,4737,4743,4741,0,2207,2287,913,912,4735,4737,4743,4741,43,4741,4743,4058,4057,0,912,913,913,912,4741,4743,4058,4057,43,4732,4742,4744,4745,0,2205,909,910,910,4732,4742,4744,4745,43,4745,4744,4711,4705,0,910,910,911,911,4745,4744,4711,4705,43,4742,4052,4051,4744,0,909,909,910,910,4742,4052,4051,4744,43,4744,4051,4038,4711,0,910,910,911,911,4744,4051,4038,4711,43,4732,4745,4746,4733,0,2205,910,910,2206,4732,4745,4746,4733,43,4733,4746,4747,4738,0,2206,910,910,2286,4733,4746,4747,4738,43,4745,4705,4704,4746,0,910,911,911,910,4745,4705,4704,4746,43,4746,4704,4708,4747,0,910,911,911,910,4746,4704,4708,4747,43,4748,4749,4750,4751,0,914,914,917,917,4748,4749,4750,4751,43,4751,4750,4731,4729,0,917,917,913,913,4751,4750,4731,4729,43,4749,3925,3924,4750,0,914,914,917,917,4749,3925,3924,4750,43,4750,3924,3927,4731,0,917,917,913,913,4750,3924,3927,4731,43,4748,4751,4752,4753,0,914,917,917,914,4748,4751,4752,4753,43,4753,4752,4068,4067,0,914,917,917,914,4753,4752,4068,4067,43,4751,4729,4728,4752,0,917,913,913,917,4751,4729,4728,4752,43,4752,4728,4062,4068,0,917,913,913,917,4752,4728,4062,4068,43,4748,4753,4754,4755,0,914,914,915,915,4748,4753,4754,4755,43,4755,4754,4756,4757,0,915,915,916,916,4755,4754,4756,4757,43,4753,4067,4080,4754,0,914,914,915,915,4753,4067,4080,4754,43,4754,4080,4082,4756,0,915,915,916,916,4754,4080,4082,4756,43,4748,4755,4758,4749,0,914,915,915,914,4748,4755,4758,4749,43,4749,4758,3932,3925,0,914,915,915,914,4749,4758,3932,3925,43,4755,4757,4759,4758,0,915,916,916,915,4755,4757,4759,4758,43,4758,4759,3933,3932,0,915,916,916,915,4758,4759,3933,3932,43,4760,4761,4762,4763,0,2291,915,915,914,4760,4761,4762,4763,43,4763,4762,4077,4072,0,914,915,915,914,4763,4762,4077,4072,43,4761,4764,4765,4762,0,915,916,916,915,4761,4764,4765,4762,43,4762,4765,4078,4077,0,915,916,916,915,4762,4765,4078,4077,43,4760,4763,4766,4767,0,2291,914,917,2222,4760,4763,4766,4767,43,4767,4766,4743,4737,0,2222,917,913,2287,4767,4766,4743,4737,43,4763,4072,4071,4766,0,914,914,917,917,4763,4072,4071,4766,43,4766,4071,4058,4743,0,917,917,913,913,4766,4071,4058,4743,43,4760,4767,4768,4769,0,2291,2222,2293,2292,4760,4767,4768,4769,43,4769,4768,4770,4771,0,2292,2293,2295,2294,4769,4768,4770,4771,43,4767,4737,4736,4768,0,2222,2287,2290,2293,4767,4737,4736,4768,43,4768,4736,4740,4770,0,2293,2290,382,2295,4768,4736,4740,4770,43,4760,4769,4772,4761,0,2291,2292,915,915,4760,4769,4772,4761,43,4761,4772,4773,4764,0,915,915,916,916,4761,4772,4773,4764,43,4769,4771,4774,4772,0,2292,2294,915,915,4769,4771,4774,4772,43,4772,4774,4775,4773,0,915,915,916,916,4772,4774,4775,4773,43,4776,4777,4778,4779,0,953,922,922,953,4776,4777,4778,4779,43,4779,4778,4102,4098,0,953,922,922,953,4779,4778,4102,4098,43,4777,4757,4756,4778,0,922,916,916,922,4777,4757,4756,4778,43,4778,4756,4082,4102,0,922,916,916,922,4778,4756,4082,4102,43,4776,4779,4780,4781,0,953,953,939,939,4776,4779,4780,4781,43,4781,4780,4782,4783,0,939,939,328,328,4781,4780,4782,4783,43,4779,4098,4097,4780,0,953,953,939,939,4779,4098,4097,4780,43,4780,4097,4100,4782,0,939,939,328,328,4780,4097,4100,4782,43,4776,4781,4784,4785,0,953,939,939,953,4776,4781,4784,4785,43,4785,4784,3956,3953,0,953,939,939,953,4785,4784,3956,3953,43,4781,4783,4786,4784,0,939,328,328,939,4781,4783,4786,4784,43,4784,4786,3957,3956,0,939,328,328,939,4784,4786,3957,3956,43,4776,4785,4787,4777,0,953,953,922,922,4776,4785,4787,4777,43,4777,4787,4759,4757,0,922,922,916,916,4777,4787,4759,4757,43,4785,3953,3952,4787,0,953,953,922,922,4785,3953,3952,4787,43,4787,3952,3933,4759,0,922,922,916,916,4787,3952,3933,4759,43,4788,4789,4790,4791,0,953,939,939,953,4788,4789,4790,4791,43,4791,4790,4093,4088,0,953,939,939,953,4791,4790,4093,4088,43,4789,4792,4793,4790,0,939,328,328,939,4789,4792,4793,4790,43,4790,4793,4094,4093,0,939,328,328,939,4790,4793,4094,4093,43,4788,4791,4794,4795,0,953,953,922,922,4788,4791,4794,4795,43,4795,4794,4765,4764,0,922,922,916,916,4795,4794,4765,4764,43,4791,4088,4087,4794,0,953,953,922,922,4791,4088,4087,4794,43,4794,4087,4078,4765,0,922,922,916,916,4794,4087,4078,4765,43,4788,4795,4796,4797,0,953,922,922,953,4788,4795,4796,4797,43,4797,4796,4798,4799,0,953,922,922,953,4797,4796,4798,4799,43,4795,4764,4773,4796,0,922,916,916,922,4795,4764,4773,4796,43,4796,4773,4775,4798,0,922,916,916,922,4796,4773,4775,4798,43,4788,4797,4800,4789,0,953,953,939,939,4788,4797,4800,4789,43,4789,4800,4801,4792,0,939,939,328,328,4789,4800,4801,4792,43,4797,4799,4802,4800,0,953,953,939,939,4797,4799,4802,4800,43,4800,4802,4803,4801,0,939,939,328,328,4800,4802,4803,4801,43,4804,4805,4806,4807,0,929,12,12,929,4804,4805,4806,4807,43,4807,4806,4121,4118,0,929,12,12,929,4807,4806,4121,4118,43,4805,4808,4809,4806,0,12,12,12,12,4805,4808,4809,4806,43,4806,4809,4122,4121,0,12,12,12,12,4806,4809,4122,4121,43,4804,4807,4810,4811,0,929,929,928,928,4804,4807,4810,4811,43,4811,4810,4693,4690,0,928,928,931,931,4811,4810,4693,4690,43,4807,4118,4117,4810,0,929,929,928,928,4807,4118,4117,4810,43,4810,4117,4026,4693,0,928,928,931,931,4810,4117,4026,4693,43,4804,4811,4812,4813,0,929,928,928,929,4804,4811,4812,4813,43,4813,4812,4230,4227,0,929,928,928,929,4813,4812,4230,4227,43,4811,4690,4689,4812,0,928,931,931,928,4811,4690,4689,4812,43,4812,4689,4192,4230,0,928,931,931,928,4812,4689,4192,4230,43,4804,4813,4814,4805,0,929,929,12,12,4804,4813,4814,4805,43,4805,4814,4815,4808,0,12,12,12,12,4805,4814,4815,4808,43,4813,4227,4226,4814,0,929,929,12,12,4813,4227,4226,4814,43,4814,4226,4017,4815,0,12,12,12,12,4814,4226,4017,4815,43,4816,4817,4818,4819,0,929,928,928,929,4816,4817,4818,4819,43,4819,4818,4114,4108,0,929,928,928,929,4819,4818,4114,4108,43,4817,4715,4714,4818,0,928,931,931,928,4817,4715,4714,4818,43,4818,4714,4032,4114,0,928,931,931,928,4818,4714,4032,4114,43,4816,4819,4820,4821,0,929,929,12,12,4816,4819,4820,4821,43,4821,4820,4822,4823,0,12,12,12,12,4821,4820,4822,4823,43,4819,4108,4107,4820,0,929,929,12,12,4819,4108,4107,4820,43,4820,4107,4111,4822,0,12,12,12,12,4820,4107,4111,4822,43,4816,4821,4824,4825,0,929,12,12,929,4816,4821,4824,4825,43,4825,4824,4826,4827,0,929,12,12,929,4825,4824,4826,4827,43,4821,4823,4828,4824,0,12,12,12,12,4821,4823,4828,4824,43,4824,4828,4829,4826,0,12,12,12,12,4824,4828,4829,4826,43,4816,4825,4830,4817,0,929,929,928,928,4816,4825,4830,4817,43,4817,4830,4718,4715,0,928,928,931,931,4817,4830,4718,4715,43,4825,4827,4831,4830,0,929,929,928,928,4825,4827,4831,4830,43,4830,4831,4719,4718,0,928,928,931,931,4830,4831,4719,4718,43,4832,4833,4834,4835,0,12,12,12,12,4832,4833,4834,4835,43,4835,4834,4836,4837,0,12,12,12,12,4835,4834,4836,4837,43,4833,3970,3969,4834,0,12,12,12,12,4833,3970,3969,4834,43,4834,3969,3972,4836,0,12,12,12,12,4834,3969,3972,4836,43,4832,4835,4838,4839,0,12,12,12,12,4832,4835,4838,4839,43,4839,4838,4130,4129,0,12,12,12,12,4839,4838,4130,4129,43,4835,4837,4840,4838,0,12,12,12,12,4835,4837,4840,4838,43,4838,4840,4131,4130,0,12,12,12,12,4838,4840,4131,4130,43,4832,4839,4841,4842,0,12,12,12,12,4832,4839,4841,4842,43,4842,4841,4843,4844,0,12,12,12,12,4842,4841,4843,4844,43,4839,4129,4145,4841,0,12,12,12,12,4839,4129,4145,4841,43,4841,4145,4147,4843,0,12,12,12,12,4841,4145,4147,4843,43,4832,4842,4845,4833,0,12,12,12,12,4832,4842,4845,4833,43,4833,4845,3977,3970,0,12,12,12,12,4833,4845,3977,3970,43,4842,4844,4846,4845,0,12,12,12,12,4842,4844,4846,4845,43,4845,4846,3978,3977,0,12,12,12,12,4845,4846,3978,3977,43,4847,4848,4849,4850,0,12,12,12,12,4847,4848,4849,4850,43,4850,4849,4851,4852,0,12,12,12,12,4850,4849,4851,4852,43,4848,4853,4854,4849,0,12,12,12,12,4848,4853,4854,4849,43,4849,4854,4855,4851,0,12,12,12,12,4849,4854,4855,4851,43,4847,4850,4856,4857,0,12,12,12,12,4847,4850,4856,4857,43,4857,4856,4142,4135,0,12,12,12,12,4857,4856,4142,4135,43,4850,4852,4858,4856,0,12,12,12,12,4850,4852,4858,4856,43,4856,4858,4143,4142,0,12,12,12,12,4856,4858,4143,4142,43,4847,4857,4859,4860,0,12,12,12,12,4847,4857,4859,4860,43,4860,4859,4861,4862,0,12,12,12,12,4860,4859,4861,4862,43,4857,4135,4134,4859,0,12,12,12,12,4857,4135,4134,4859,43,4859,4134,4137,4861,0,12,12,12,12,4859,4134,4137,4861,43,4847,4860,4863,4848,0,12,12,12,12,4847,4860,4863,4848,43,4848,4863,4864,4853,0,12,12,12,12,4848,4863,4864,4853,43,4860,4862,4865,4863,0,12,12,12,12,4860,4862,4865,4863,43,4863,4865,4866,4864,0,12,12,12,12,4863,4865,4866,4864,43,4867,4868,4869,4870,0,932,933,933,932,4867,4868,4869,4870,43,4870,4869,4162,4160,0,932,933,933,932,4870,4869,4162,4160,43,4868,4783,4782,4869,0,933,328,328,933,4868,4783,4782,4869,43,4869,4782,4100,4162,0,933,328,328,933,4869,4782,4100,4162,43,4867,4870,4871,4872,0,932,932,12,12,4867,4870,4871,4872,43,4872,4871,4840,4837,0,12,12,12,12,4872,4871,4840,4837,43,4870,4160,4159,4871,0,932,932,12,12,4870,4160,4159,4871,43,4871,4159,4131,4840,0,12,12,12,12,4871,4159,4131,4840,43,4867,4872,4873,4874,0,932,12,12,932,4867,4872,4873,4874,43,4874,4873,3995,3992,0,932,12,12,932,4874,4873,3995,3992,43,4872,4837,4836,4873,0,12,12,12,12,4872,4837,4836,4873,43,4873,4836,3972,3995,0,12,12,12,12,4873,4836,3972,3995,43,4867,4874,4875,4868,0,932,932,933,933,4867,4874,4875,4868,43,4868,4875,4786,4783,0,933,933,328,328,4868,4875,4786,4783,43,4874,3992,3991,4875,0,932,932,933,933,4874,3992,3991,4875,43,4875,3991,3957,4786,0,933,933,328,328,4875,3991,3957,4786,43,4876,4877,4878,4879,0,932,12,12,932,4876,4877,4878,4879,43,4879,4878,4156,4153,0,932,12,12,932,4879,4878,4156,4153,43,4877,4862,4861,4878,0,12,12,12,12,4877,4862,4861,4878,43,4878,4861,4137,4156,0,12,12,12,12,4878,4861,4137,4156,43,4876,4879,4880,4881,0,932,932,933,933,4876,4879,4880,4881,43,4881,4880,4793,4792,0,933,933,328,328,4881,4880,4793,4792,43,4879,4153,4152,4880,0,932,932,933,933,4879,4153,4152,4880,43,4880,4152,4094,4793,0,933,933,328,328,4880,4152,4094,4793,43,4876,4881,4882,4883,0,932,933,933,932,4876,4881,4882,4883,43,4883,4882,4884,4885,0,932,933,933,932,4883,4882,4884,4885,43,4881,4792,4801,4882,0,933,328,328,933,4881,4792,4801,4882,43,4882,4801,4803,4884,0,933,328,328,933,4882,4801,4803,4884,43,4876,4883,4886,4877,0,932,932,12,12,4876,4883,4886,4877,43,4877,4886,4865,4862,0,12,12,12,12,4877,4886,4865,4862,43,4883,4885,4887,4886,0,932,932,12,12,4883,4885,4887,4886,43,4886,4887,4866,4865,0,12,12,12,12,4886,4887,4866,4865,43,4888,4889,4890,4891,0,12,12,12,12,4888,4889,4890,4891,43,4891,4890,4177,4175,0,12,12,12,12,4891,4890,4177,4175,43,4889,4844,4843,4890,0,12,12,12,12,4889,4844,4843,4890,43,4890,4843,4147,4177,0,12,12,12,12,4890,4843,4147,4177,43,4888,4891,4892,4893,0,12,12,12,12,4888,4891,4892,4893,43,4893,4892,4809,4808,0,12,12,12,12,4893,4892,4809,4808,43,4891,4175,4174,4892,0,12,12,12,12,4891,4175,4174,4892,43,4892,4174,4122,4809,0,12,12,12,12,4892,4174,4122,4809,43,4888,4893,4894,4895,0,12,12,12,12,4888,4893,4894,4895,43,4895,4894,4016,4013,0,12,12,12,12,4895,4894,4016,4013,43,4893,4808,4815,4894,0,12,12,12,12,4893,4808,4815,4894,43,4894,4815,4017,4016,0,12,12,12,12,4894,4815,4017,4016,43,4888,4895,4896,4889,0,12,12,12,12,4888,4895,4896,4889,43,4889,4896,4846,4844,0,12,12,12,12,4889,4896,4846,4844,43,4895,4013,4012,4896,0,12,12,12,12,4895,4013,4012,4896,43,4896,4012,3978,4846,0,12,12,12,12,4896,4012,3978,4846,43,4897,4898,4899,4900,0,12,12,12,12,4897,4898,4899,4900,43,4900,4899,4828,4823,0,12,12,12,12,4900,4899,4828,4823,43,4898,4901,4902,4899,0,12,12,12,12,4898,4901,4902,4899,43,4899,4902,4829,4828,0,12,12,12,12,4899,4902,4829,4828,43,4897,4900,4903,4904,0,12,12,12,12,4897,4900,4903,4904,43,4904,4903,4171,4168,0,12,12,12,12,4904,4903,4171,4168,43,4900,4823,4822,4903,0,12,12,12,12,4900,4823,4822,4903,43,4903,4822,4111,4171,0,12,12,12,12,4903,4822,4111,4171,43,4897,4904,4905,4906,0,12,12,12,12,4897,4904,4905,4906,43,4906,4905,4858,4852,0,12,12,12,12,4906,4905,4858,4852,43,4904,4168,4167,4905,0,12,12,12,12,4904,4168,4167,4905,43,4905,4167,4143,4858,0,12,12,12,12,4905,4167,4143,4858,43,4897,4906,4907,4898,0,12,12,12,12,4897,4906,4907,4898,43,4898,4907,4908,4901,0,12,12,12,12,4898,4907,4908,4901,43,4906,4852,4851,4907,0,12,12,12,12,4906,4852,4851,4907,43,4907,4851,4855,4908,0,12,12,12,12,4907,4851,4855,4908,43,4909,4910,4911,4912,0,947,948,948,947,4909,4910,4911,4912,43,4912,4911,4913,4914,0,947,948,948,947,4912,4911,4913,4914,43,4910,4915,4916,4911,0,948,931,931,948,4910,4915,4916,4911,43,4911,4916,4917,4913,0,948,931,931,948,4911,4916,4917,4913,43,4909,4912,4918,4919,0,947,947,67,67,4909,4912,4918,4919,43,4919,4918,4920,4921,0,67,67,6,6,4919,4918,4920,4921,43,4912,4914,4922,4918,0,947,947,67,67,4912,4914,4922,4918,43,4918,4922,4923,4920,0,67,67,6,6,4918,4922,4923,4920,43,4909,4919,4924,4925,0,947,67,67,947,4909,4919,4924,4925,43,4925,4924,4246,4238,0,947,67,67,947,4925,4924,4246,4238,43,4919,4921,4926,4924,0,67,6,6,67,4919,4921,4926,4924,43,4924,4926,4247,4246,0,67,6,6,67,4924,4926,4247,4246,43,4909,4925,4927,4910,0,947,947,948,948,4909,4925,4927,4910,43,4910,4927,4928,4915,0,948,948,931,931,4910,4927,4928,4915,43,4925,4238,4237,4927,0,947,947,948,948,4925,4238,4237,4927,43,4927,4237,4241,4928,0,948,948,931,931,4927,4237,4241,4928,43,4929,4930,4931,4932,0,947,67,67,947,4929,4930,4931,4932,43,4932,4931,4922,4914,0,947,67,67,947,4932,4931,4922,4914,43,4930,4933,4934,4931,0,67,6,6,67,4930,4933,4934,4931,43,4931,4934,4923,4922,0,67,6,6,67,4931,4934,4923,4922,43,4929,4932,4935,4936,0,947,947,948,948,4929,4932,4935,4936,43,4936,4935,4937,4938,0,948,948,931,931,4936,4935,4937,4938,43,4932,4914,4913,4935,0,947,947,948,948,4932,4914,4913,4935,43,4935,4913,4917,4937,0,948,948,931,931,4935,4913,4917,4937,43,4929,4936,4939,4940,0,947,948,948,947,4929,4936,4939,4940,43,4940,4939,4941,4942,0,947,948,948,947,4940,4939,4941,4942,43,4936,4938,4943,4939,0,948,931,931,948,4936,4938,4943,4939,43,4939,4943,4944,4941,0,948,931,931,948,4939,4943,4944,4941,43,4929,4940,4945,4930,0,947,947,67,67,4929,4940,4945,4930,43,4930,4945,4946,4933,0,67,67,6,6,4930,4945,4946,4933,43,4940,4942,4947,4945,0,947,947,67,67,4940,4942,4947,4945,43,4945,4947,4948,4946,0,67,67,6,6,4945,4947,4948,4946,43,4949,4950,4951,4952,0,909,909,910,910,4949,4950,4951,4952,43,4952,4951,4926,4921,0,910,910,911,911,4952,4951,4926,4921,43,4950,4260,4259,4951,0,909,909,910,910,4950,4260,4259,4951,43,4951,4259,4247,4926,0,910,910,911,911,4951,4259,4247,4926,43,4949,4952,4953,4954,0,909,910,910,909,4949,4952,4953,4954,43,4954,4953,4955,4956,0,909,910,910,909,4954,4953,4955,4956,43,4952,4921,4920,4953,0,910,911,911,910,4952,4921,4920,4953,43,4953,4920,4923,4955,0,910,911,911,910,4953,4920,4923,4955,43,4949,4954,4957,4958,0,909,909,912,912,4949,4954,4957,4958,43,4958,4957,4959,4960,0,912,912,913,913,4958,4957,4959,4960,43,4954,4956,4961,4957,0,909,909,912,912,4954,4956,4961,4957,43,4957,4961,4962,4959,0,912,912,913,913,4957,4961,4962,4959,43,4949,4958,4963,4950,0,909,912,912,909,4949,4958,4963,4950,43,4950,4963,4265,4260,0,909,912,912,909,4950,4963,4265,4260,43,4958,4960,4964,4963,0,912,913,913,912,4958,4960,4964,4963,43,4963,4964,4266,4265,0,912,913,913,912,4963,4964,4266,4265,43,4965,4966,4967,4968,0,909,909,912,912,4965,4966,4967,4968,43,4968,4967,4969,4970,0,912,912,913,913,4968,4967,4969,4970,43,4966,4971,4972,4967,0,909,909,912,912,4966,4971,4972,4967,43,4967,4972,4973,4969,0,912,912,913,913,4967,4972,4973,4969,43,4965,4968,4974,4975,0,909,912,912,909,4965,4968,4974,4975,43,4975,4974,4961,4956,0,909,912,912,909,4975,4974,4961,4956,43,4968,4970,4976,4974,0,912,913,913,912,4968,4970,4976,4974,43,4974,4976,4962,4961,0,912,913,913,912,4974,4976,4962,4961,43,4965,4975,4977,4978,0,909,909,910,910,4965,4975,4977,4978,43,4978,4977,4934,4933,0,910,910,911,911,4978,4977,4934,4933,43,4975,4956,4955,4977,0,909,909,910,910,4975,4956,4955,4977,43,4977,4955,4923,4934,0,910,910,911,911,4977,4955,4923,4934,43,4965,4978,4979,4966,0,909,910,910,909,4965,4978,4979,4966,43,4966,4979,4980,4971,0,909,910,910,909,4966,4979,4980,4971,43,4978,4933,4946,4979,0,910,911,911,910,4978,4933,4946,4979,43,4979,4946,4948,4980,0,910,911,911,910,4979,4946,4948,4980,43,4981,4982,4983,4984,0,914,914,917,917,4981,4982,4983,4984,43,4984,4983,4964,4960,0,917,917,913,913,4984,4983,4964,4960,43,4982,4276,4275,4983,0,914,914,917,917,4982,4276,4275,4983,43,4983,4275,4266,4964,0,917,917,913,913,4983,4275,4266,4964,43,4981,4984,4985,4986,0,914,917,917,914,4981,4984,4985,4986,43,4986,4985,4987,4988,0,914,917,917,914,4986,4985,4987,4988,43,4984,4960,4959,4985,0,917,913,913,917,4984,4960,4959,4985,43,4985,4959,4962,4987,0,917,913,913,917,4985,4959,4962,4987,43,4981,4986,4989,4990,0,914,914,915,915,4981,4986,4989,4990,43,4990,4989,4991,4992,0,915,915,916,916,4990,4989,4991,4992,43,4986,4988,4993,4989,0,914,914,915,915,4986,4988,4993,4989,43,4989,4993,4994,4991,0,915,915,916,916,4989,4993,4994,4991,43,4981,4990,4995,4982,0,914,915,915,914,4981,4990,4995,4982,43,4982,4995,4281,4276,0,914,915,915,914,4982,4995,4281,4276,43,4990,4992,4996,4995,0,915,916,916,915,4990,4992,4996,4995,43,4995,4996,4282,4281,0,915,916,916,915,4995,4996,4282,4281,43,4997,4998,4999,5000,0,914,914,915,915,4997,4998,4999,5000,43,5000,4999,5001,5002,0,915,915,916,916,5000,4999,5001,5002,43,4998,5003,5004,4999,0,914,914,915,915,4998,5003,5004,4999,43,4999,5004,5005,5001,0,915,915,916,916,4999,5004,5005,5001,43,4997,5000,5006,5007,0,914,915,915,914,4997,5000,5006,5007,43,5007,5006,4993,4988,0,914,915,915,914,5007,5006,4993,4988,43,5000,5002,5008,5006,0,915,916,916,915,5000,5002,5008,5006,43,5006,5008,4994,4993,0,915,916,916,915,5006,5008,4994,4993,43,4997,5007,5009,5010,0,914,914,917,917,4997,5007,5009,5010,43,5010,5009,4976,4970,0,917,917,913,913,5010,5009,4976,4970,43,5007,4988,4987,5009,0,914,914,917,917,5007,4988,4987,5009,43,5009,4987,4962,4976,0,917,917,913,913,5009,4987,4962,4976,43,4997,5010,5011,4998,0,914,917,917,914,4997,5010,5011,4998,43,4998,5011,5012,5003,0,914,917,917,914,4998,5011,5012,5003,43,5010,4970,4969,5011,0,917,913,913,917,5010,4970,4969,5011,43,5011,4969,4973,5012,0,917,913,913,917,5011,4969,4973,5012,43,5013,5014,5015,5016,0,953,953,922,922,5013,5014,5015,5016,43,5016,5015,4996,4992,0,922,922,916,916,5016,5015,4996,4992,43,5014,4292,4291,5015,0,953,953,922,922,5014,4292,4291,5015,43,5015,4291,4282,4996,0,922,922,916,916,5015,4291,4282,4996,43,5013,5016,5017,5018,0,953,922,922,953,5013,5016,5017,5018,43,5018,5017,5019,5020,0,953,922,922,953,5018,5017,5019,5020,43,5016,4992,4991,5017,0,922,916,916,922,5016,4992,4991,5017,43,5017,4991,4994,5019,0,922,916,916,922,5017,4991,4994,5019,43,5013,5018,5021,5022,0,953,953,939,939,5013,5018,5021,5022,43,5022,5021,5023,5024,0,939,939,328,328,5022,5021,5023,5024,43,5018,5020,5025,5021,0,953,953,939,939,5018,5020,5025,5021,43,5021,5025,5026,5023,0,939,939,328,328,5021,5025,5026,5023,43,5013,5022,5027,5014,0,953,939,939,953,5013,5022,5027,5014,43,5014,5027,4297,4292,0,953,939,939,953,5014,5027,4297,4292,43,5022,5024,5028,5027,0,939,328,328,939,5022,5024,5028,5027,43,5027,5028,4298,4297,0,939,328,328,939,5027,5028,4298,4297,43,5029,5030,5031,5032,0,953,953,939,939,5029,5030,5031,5032,43,5032,5031,5033,5034,0,939,939,328,328,5032,5031,5033,5034,43,5030,5035,5036,5031,0,953,953,939,939,5030,5035,5036,5031,43,5031,5036,5037,5033,0,939,939,328,328,5031,5036,5037,5033,43,5029,5032,5038,5039,0,953,939,939,953,5029,5032,5038,5039,43,5039,5038,5025,5020,0,953,939,939,953,5039,5038,5025,5020,43,5032,5034,5040,5038,0,939,328,328,939,5032,5034,5040,5038,43,5038,5040,5026,5025,0,939,328,328,939,5038,5040,5026,5025,43,5029,5039,5041,5042,0,953,953,922,922,5029,5039,5041,5042,43,5042,5041,5008,5002,0,922,922,916,916,5042,5041,5008,5002,43,5039,5020,5019,5041,0,953,953,922,922,5039,5020,5019,5041,43,5041,5019,4994,5008,0,922,922,916,916,5041,5019,4994,5008,43,5029,5042,5043,5030,0,953,922,922,953,5029,5042,5043,5030,43,5030,5043,5044,5035,0,953,922,922,953,5030,5043,5044,5035,43,5042,5002,5001,5043,0,922,916,916,922,5042,5002,5001,5043,43,5043,5001,5005,5044,0,922,916,916,922,5043,5001,5005,5044,43,5045,5046,5047,5048,0,929,12,12,929,5045,5046,5047,5048,43,5048,5047,5049,5050,0,929,12,12,929,5048,5047,5049,5050,43,5046,5051,5052,5047,0,12,12,12,12,5046,5051,5052,5047,43,5047,5052,5053,5049,0,12,12,12,12,5047,5052,5053,5049,43,5045,5048,5054,5055,0,929,929,928,928,5045,5048,5054,5055,43,5055,5054,4916,4915,0,928,928,931,931,5055,5054,4916,4915,43,5048,5050,5056,5054,0,929,929,928,928,5048,5050,5056,5054,43,5054,5056,4917,4916,0,928,928,931,931,5054,5056,4917,4916,43,5045,5055,5057,5058,0,929,928,928,929,5045,5055,5057,5058,43,5058,5057,4312,4306,0,929,928,928,929,5058,5057,4312,4306,43,5055,4915,4928,5057,0,928,931,931,928,5055,4915,4928,5057,43,5057,4928,4241,4312,0,928,931,931,928,5057,4928,4241,4312,43,5045,5058,5059,5046,0,929,929,12,12,5045,5058,5059,5046,43,5046,5059,5060,5051,0,12,12,12,12,5046,5059,5060,5051,43,5058,4306,4305,5059,0,929,929,12,12,5058,4306,4305,5059,43,5059,4305,4309,5060,0,12,12,12,12,5059,4305,4309,5060,43,5061,5062,5063,5064,0,929,928,928,929,5061,5062,5063,5064,43,5064,5063,5056,5050,0,929,928,928,929,5064,5063,5056,5050,43,5062,4938,4937,5063,0,928,931,931,928,5062,4938,4937,5063,43,5063,4937,4917,5056,0,928,931,931,928,5063,4937,4917,5056,43,5061,5064,5065,5066,0,929,929,12,12,5061,5064,5065,5066,43,5066,5065,5067,5068,0,12,12,12,12,5066,5065,5067,5068,43,5064,5050,5049,5065,0,929,929,12,12,5064,5050,5049,5065,43,5065,5049,5053,5067,0,12,12,12,12,5065,5049,5053,5067,43,5061,5066,5069,5070,0,929,12,12,929,5061,5066,5069,5070,43,5070,5069,5071,5072,0,929,12,12,929,5070,5069,5071,5072,43,5066,5068,5073,5069,0,12,12,12,12,5066,5068,5073,5069,43,5069,5073,5074,5071,0,12,12,12,12,5069,5073,5074,5071,43,5061,5070,5075,5062,0,929,929,928,928,5061,5070,5075,5062,43,5062,5075,4943,4938,0,928,928,931,931,5062,5075,4943,4938,43,5070,5072,5076,5075,0,929,929,928,928,5070,5072,5076,5075,43,5075,5076,4944,4943,0,928,928,931,931,5075,5076,4944,4943,43,5077,5078,5079,5080,0,12,12,12,12,5077,5078,5079,5080,43,5080,5079,5081,5082,0,12,12,12,12,5080,5079,5081,5082,43,5078,5083,5084,5079,0,12,12,12,12,5078,5083,5084,5079,43,5079,5084,5085,5081,0,12,12,12,12,5079,5084,5085,5081,43,5077,5080,5086,5087,0,12,12,12,12,5077,5080,5086,5087,43,5087,5086,5088,5089,0,12,12,12,12,5087,5086,5088,5089,43,5080,5082,5090,5086,0,12,12,12,12,5080,5082,5090,5086,43,5086,5090,5091,5088,0,12,12,12,12,5086,5090,5091,5088,43,5077,5087,5092,5093,0,12,12,12,12,5077,5087,5092,5093,43,5093,5092,4330,4322,0,12,12,12,12,5093,5092,4330,4322,43,5087,5089,5094,5092,0,12,12,12,12,5087,5089,5094,5092,43,5092,5094,4331,4330,0,12,12,12,12,5092,5094,4331,4330,43,5077,5093,5095,5078,0,12,12,12,12,5077,5093,5095,5078,43,5078,5095,5096,5083,0,12,12,12,12,5078,5095,5096,5083,43,5093,4322,4321,5095,0,12,12,12,12,5093,4322,4321,5095,43,5095,4321,4325,5096,0,12,12,12,12,5095,4321,4325,5096,43,5097,5098,5099,5100,0,12,12,12,12,5097,5098,5099,5100,43,5100,5099,5090,5082,0,12,12,12,12,5100,5099,5090,5082,43,5098,5101,5102,5099,0,12,12,12,12,5098,5101,5102,5099,43,5099,5102,5091,5090,0,12,12,12,12,5099,5102,5091,5090,43,5097,5100,5103,5104,0,12,12,12,12,5097,5100,5103,5104,43,5104,5103,5105,5106,0,12,12,12,12,5104,5103,5105,5106,43,5100,5082,5081,5103,0,12,12,12,12,5100,5082,5081,5103,43,5103,5081,5085,5105,0,12,12,12,12,5103,5081,5085,5105,43,5097,5104,5107,5108,0,12,12,12,12,5097,5104,5107,5108,43,5108,5107,5109,5110,0,12,12,12,12,5108,5107,5109,5110,43,5104,5106,5111,5107,0,12,12,12,12,5104,5106,5111,5107,43,5107,5111,5112,5109,0,12,12,12,12,5107,5111,5112,5109,43,5097,5108,5113,5098,0,12,12,12,12,5097,5108,5113,5098,43,5098,5113,5114,5101,0,12,12,12,12,5098,5113,5114,5101,43,5108,5110,5115,5113,0,12,12,12,12,5108,5110,5115,5113,43,5113,5115,5116,5114,0,12,12,12,12,5113,5115,5116,5114,43,5117,5118,5119,5120,0,932,932,933,933,5117,5118,5119,5120,43,5120,5119,5028,5024,0,933,933,328,328,5120,5119,5028,5024,43,5118,4344,4343,5119,0,932,932,933,933,5118,4344,4343,5119,43,5119,4343,4298,5028,0,933,933,328,328,5119,4343,4298,5028,43,5117,5120,5121,5122,0,932,933,933,932,5117,5120,5121,5122,43,5122,5121,5123,5124,0,932,933,933,932,5122,5121,5123,5124,43,5120,5024,5023,5121,0,933,328,328,933,5120,5024,5023,5121,43,5121,5023,5026,5123,0,933,328,328,933,5121,5023,5026,5123,43,5117,5122,5125,5126,0,932,932,12,12,5117,5122,5125,5126,43,5126,5125,5084,5083,0,12,12,12,12,5126,5125,5084,5083,43,5122,5124,5127,5125,0,932,932,12,12,5122,5124,5127,5125,43,5125,5127,5085,5084,0,12,12,12,12,5125,5127,5085,5084,43,5117,5126,5128,5118,0,932,12,12,932,5117,5126,5128,5118,43,5118,5128,4347,4344,0,932,12,12,932,5118,5128,4347,4344,43,5126,5083,5096,5128,0,12,12,12,12,5126,5083,5096,5128,43,5128,5096,4325,4347,0,12,12,12,12,5128,5096,4325,4347,43,5129,5130,5131,5132,0,932,932,12,12,5129,5130,5131,5132,43,5132,5131,5111,5106,0,12,12,12,12,5132,5131,5111,5106,43,5130,5133,5134,5131,0,932,932,12,12,5130,5133,5134,5131,43,5131,5134,5112,5111,0,12,12,12,12,5131,5134,5112,5111,43,5129,5132,5135,5136,0,932,12,12,932,5129,5132,5135,5136,43,5136,5135,5127,5124,0,932,12,12,932,5136,5135,5127,5124,43,5132,5106,5105,5135,0,12,12,12,12,5132,5106,5105,5135,43,5135,5105,5085,5127,0,12,12,12,12,5135,5105,5085,5127,43,5129,5136,5137,5138,0,932,932,933,933,5129,5136,5137,5138,43,5138,5137,5040,5034,0,933,933,328,328,5138,5137,5040,5034,43,5136,5124,5123,5137,0,932,932,933,933,5136,5124,5123,5137,43,5137,5123,5026,5040,0,933,933,328,328,5137,5123,5026,5040,43,5129,5138,5139,5130,0,932,933,933,932,5129,5138,5139,5130,43,5130,5139,5140,5133,0,932,933,933,932,5130,5139,5140,5133,43,5138,5034,5033,5139,0,933,328,328,933,5138,5034,5033,5139,43,5139,5033,5037,5140,0,933,328,328,933,5139,5033,5037,5140,43,5141,5142,5143,5144,0,12,12,12,12,5141,5142,5143,5144,43,5144,5143,5145,5146,0,12,12,12,12,5144,5143,5145,5146,43,5142,5089,5088,5143,0,12,12,12,12,5142,5089,5088,5143,43,5143,5088,5091,5145,0,12,12,12,12,5143,5088,5091,5145,43,5141,5144,5147,5148,0,12,12,12,12,5141,5144,5147,5148,43,5148,5147,5052,5051,0,12,12,12,12,5148,5147,5052,5051,43,5144,5146,5149,5147,0,12,12,12,12,5144,5146,5149,5147,43,5147,5149,5053,5052,0,12,12,12,12,5147,5149,5053,5052,43,5141,5148,5150,5151,0,12,12,12,12,5141,5148,5150,5151,43,5151,5150,4357,4354,0,12,12,12,12,5151,5150,4357,4354,43,5148,5051,5060,5150,0,12,12,12,12,5148,5051,5060,5150,43,5150,5060,4309,4357,0,12,12,12,12,5150,5060,4309,4357,43,5141,5151,5152,5142,0,12,12,12,12,5141,5151,5152,5142,43,5142,5152,5094,5089,0,12,12,12,12,5142,5152,5094,5089,43,5151,4354,4353,5152,0,12,12,12,12,5151,4354,4353,5152,43,5152,4353,4331,5094,0,12,12,12,12,5152,4353,4331,5094,43,5153,5154,5155,5156,0,12,12,12,12,5153,5154,5155,5156,43,5156,5155,5149,5146,0,12,12,12,12,5156,5155,5149,5146,43,5154,5068,5067,5155,0,12,12,12,12,5154,5068,5067,5155,43,5155,5067,5053,5149,0,12,12,12,12,5155,5067,5053,5149,43,5153,5156,5157,5158,0,12,12,12,12,5153,5156,5157,5158,43,5158,5157,5102,5101,0,12,12,12,12,5158,5157,5102,5101,43,5156,5146,5145,5157,0,12,12,12,12,5156,5146,5145,5157,43,5157,5145,5091,5102,0,12,12,12,12,5157,5145,5091,5102,43,5153,5158,5159,5160,0,12,12,12,12,5153,5158,5159,5160,43,5160,5159,5161,5162,0,12,12,12,12,5160,5159,5161,5162,43,5158,5101,5114,5159,0,12,12,12,12,5158,5101,5114,5159,43,5159,5114,5116,5161,0,12,12,12,12,5159,5114,5116,5161,43,5153,5160,5163,5154,0,12,12,12,12,5153,5160,5163,5154,43,5154,5163,5073,5068,0,12,12,12,12,5154,5163,5073,5068,43,5160,5162,5164,5163,0,12,12,12,12,5160,5162,5164,5163,43,5163,5164,5074,5073,0,12,12,12,12,5163,5164,5074,5073,43,5165,5166,5167,5168,0,947,948,948,947,5165,5166,5167,5168,43,5168,5167,2110,2104,0,947,948,948,947,5168,5167,2110,2104,43,5166,5169,5170,5167,0,948,931,931,948,5166,5169,5170,5167,43,5167,5170,2024,2110,0,948,931,931,948,5167,5170,2024,2110,43,5165,5168,5171,5172,0,947,947,67,67,5165,5168,5171,5172,43,5172,5171,5173,5174,0,67,67,6,6,5172,5171,5173,5174,43,5168,2104,2103,5171,0,947,947,67,67,5168,2104,2103,5171,43,5171,2103,2107,5173,0,67,67,6,6,5171,2103,2107,5173,43,5165,5172,5175,5176,0,947,67,67,947,5165,5172,5175,5176,43,5176,5175,4947,4942,0,947,67,67,947,5176,5175,4947,4942,43,5172,5174,5177,5175,0,67,6,6,67,5172,5174,5177,5175,43,5175,5177,4948,4947,0,67,6,6,67,5175,5177,4948,4947,43,5165,5176,5178,5166,0,947,947,948,948,5165,5176,5178,5166,43,5166,5178,5179,5169,0,948,948,931,931,5166,5178,5179,5169,43,5176,4942,4941,5178,0,947,947,948,948,5176,4942,4941,5178,43,5178,4941,4944,5179,0,948,948,931,931,5178,4941,4944,5179,43,5180,5181,5182,5183,0,909,910,910,909,5180,5181,5182,5183,43,5183,5182,2126,2120,0,909,910,910,909,5183,5182,2126,2120,43,5181,5174,5173,5182,0,910,911,911,910,5181,5174,5173,5182,43,5182,5173,2107,2126,0,910,911,911,910,5182,5173,2107,2126,43,5180,5183,5184,5185,0,909,909,912,912,5180,5183,5184,5185,43,5185,5184,5186,5187,0,912,912,913,913,5185,5184,5186,5187,43,5183,2120,2119,5184,0,909,909,912,912,5183,2120,2119,5184,43,5184,2119,2123,5186,0,912,912,913,913,5184,2119,2123,5186,43,5180,5185,5188,5189,0,909,912,912,909,5180,5185,5188,5189,43,5189,5188,4972,4971,0,909,912,912,909,5189,5188,4972,4971,43,5185,5187,5190,5188,0,912,913,913,912,5185,5187,5190,5188,43,5188,5190,4973,4972,0,912,913,913,912,5188,5190,4973,4972,43,5180,5189,5191,5181,0,909,909,910,910,5180,5189,5191,5181,43,5181,5191,5177,5174,0,910,910,911,911,5181,5191,5177,5174,43,5189,4971,4980,5191,0,909,909,910,910,5189,4971,4980,5191,43,5191,4980,4948,5177,0,910,910,911,911,5191,4980,4948,5177,43,5192,5193,5194,5195,0,914,914,917,917,5192,5193,5194,5195,43,5195,5194,5190,5187,0,917,917,913,913,5195,5194,5190,5187,43,5193,5003,5012,5194,0,914,914,917,917,5193,5003,5012,5194,43,5194,5012,4973,5190,0,917,917,913,913,5194,5012,4973,5190,43,5192,5195,5196,5197,0,914,917,917,914,5192,5195,5196,5197,43,5197,5196,2136,2135,0,914,917,917,914,5197,5196,2136,2135,43,5195,5187,5186,5196,0,917,913,913,917,5195,5187,5186,5196,43,5196,5186,2123,2136,0,917,913,913,917,5196,5186,2123,2136,43,5192,5197,5198,5199,0,914,914,915,915,5192,5197,5198,5199,43,5199,5198,5200,5201,0,915,915,916,916,5199,5198,5200,5201,43,5197,2135,2144,5198,0,914,914,915,915,5197,2135,2144,5198,43,5198,2144,2146,5200,0,915,915,916,916,5198,2144,2146,5200,43,5192,5199,5202,5193,0,914,915,915,914,5192,5199,5202,5193,43,5193,5202,5004,5003,0,914,915,915,914,5193,5202,5004,5003,43,5199,5201,5203,5202,0,915,916,916,915,5199,5201,5203,5202,43,5202,5203,5005,5004,0,915,916,916,915,5202,5203,5005,5004,43,5204,5205,5206,5207,0,953,953,922,922,5204,5205,5206,5207,43,5207,5206,5203,5201,0,922,922,916,916,5207,5206,5203,5201,43,5205,5035,5044,5206,0,953,953,922,922,5205,5035,5044,5206,43,5206,5044,5005,5203,0,922,922,916,916,5206,5044,5005,5203,43,5204,5207,5208,5209,0,953,922,922,953,5204,5207,5208,5209,43,5209,5208,2152,2151,0,953,922,922,953,5209,5208,2152,2151,43,5207,5201,5200,5208,0,922,916,916,922,5207,5201,5200,5208,43,5208,5200,2146,2152,0,922,916,916,922,5208,5200,2146,2152,43,5204,5209,5210,5211,0,953,953,939,939,5204,5209,5210,5211,43,5211,5210,5212,5213,0,939,939,328,328,5211,5210,5212,5213,43,5209,2151,2160,5210,0,953,953,939,939,5209,2151,2160,5210,43,5210,2160,2162,5212,0,939,939,328,328,5210,2160,2162,5212,43,5204,5211,5214,5205,0,953,939,939,953,5204,5211,5214,5205,43,5205,5214,5036,5035,0,953,939,939,953,5205,5214,5036,5035,43,5211,5213,5215,5214,0,939,328,328,939,5211,5213,5215,5214,43,5214,5215,5037,5036,0,939,328,328,939,5214,5215,5037,5036,43,5216,5217,5218,5219,0,929,12,12,929,5216,5217,5218,5219,43,5219,5218,2029,2021,0,929,12,12,929,5219,5218,2029,2021,43,5217,5220,5221,5218,0,12,12,12,12,5217,5220,5221,5218,43,5218,5221,2030,2029,0,12,12,12,12,5218,5221,2030,2029,43,5216,5219,5222,5223,0,929,929,928,928,5216,5219,5222,5223,43,5223,5222,5170,5169,0,928,928,931,931,5223,5222,5170,5169,43,5219,2021,2020,5222,0,929,929,928,928,5219,2021,2020,5222,43,5222,2020,2024,5170,0,928,928,931,931,5222,2020,2024,5170,43,5216,5223,5224,5225,0,929,928,928,929,5216,5223,5224,5225,43,5225,5224,5076,5072,0,929,928,928,929,5225,5224,5076,5072,43,5223,5169,5179,5224,0,928,931,931,928,5223,5169,5179,5224,43,5224,5179,4944,5076,0,928,931,931,928,5224,5179,4944,5076,43,5216,5225,5226,5217,0,929,929,12,12,5216,5225,5226,5217,43,5217,5226,5227,5220,0,12,12,12,12,5217,5226,5227,5220,43,5225,5072,5071,5226,0,929,929,12,12,5225,5072,5071,5226,43,5226,5071,5074,5227,0,12,12,12,12,5226,5071,5074,5227,43,5228,5229,5230,5231,0,12,12,12,12,5228,5229,5230,5231,43,5231,5230,5232,5233,0,12,12,12,12,5231,5230,5232,5233,43,5229,5110,5109,5230,0,12,12,12,12,5229,5110,5109,5230,43,5230,5109,5112,5232,0,12,12,12,12,5230,5109,5112,5232,43,5228,5231,5234,5235,0,12,12,12,12,5228,5231,5234,5235,43,5235,5234,2170,2169,0,12,12,12,12,5235,5234,2170,2169,43,5231,5233,5236,5234,0,12,12,12,12,5231,5233,5236,5234,43,5234,5236,2171,2170,0,12,12,12,12,5234,5236,2171,2170,43,5228,5235,5237,5238,0,12,12,12,12,5228,5235,5237,5238,43,5238,5237,5239,5240,0,12,12,12,12,5238,5237,5239,5240,43,5235,2169,2180,5237,0,12,12,12,12,5235,2169,2180,5237,43,5237,2180,2182,5239,0,12,12,12,12,5237,2180,2182,5239,43,5228,5238,5241,5229,0,12,12,12,12,5228,5238,5241,5229,43,5229,5241,5115,5110,0,12,12,12,12,5229,5241,5115,5110,43,5238,5240,5242,5241,0,12,12,12,12,5238,5240,5242,5241,43,5241,5242,5116,5115,0,12,12,12,12,5241,5242,5116,5115,43,5243,5244,5245,5246,0,932,932,933,933,5243,5244,5245,5246,43,5246,5245,5215,5213,0,933,933,328,328,5246,5245,5215,5213,43,5244,5133,5140,5245,0,932,932,933,933,5244,5133,5140,5245,43,5245,5140,5037,5215,0,933,933,328,328,5245,5140,5037,5215,43,5243,5246,5247,5248,0,932,933,933,932,5243,5246,5247,5248,43,5248,5247,2188,2187,0,932,933,933,932,5248,5247,2188,2187,43,5246,5213,5212,5247,0,933,328,328,933,5246,5213,5212,5247,43,5247,5212,2162,2188,0,933,328,328,933,5247,5212,2162,2188,43,5243,5248,5249,5250,0,932,932,12,12,5243,5248,5249,5250,43,5250,5249,5236,5233,0,12,12,12,12,5250,5249,5236,5233,43,5248,2187,2194,5249,0,932,932,12,12,5248,2187,2194,5249,43,5249,2194,2171,5236,0,12,12,12,12,5249,2194,2171,5236,43,5243,5250,5251,5244,0,932,12,12,932,5243,5250,5251,5244,43,5244,5251,5134,5133,0,932,12,12,932,5244,5251,5134,5133,43,5250,5233,5232,5251,0,12,12,12,12,5250,5233,5232,5251,43,5251,5232,5112,5134,0,12,12,12,12,5251,5232,5112,5134,43,5252,5253,5254,5255,0,12,12,12,12,5252,5253,5254,5255,43,5255,5254,2203,2200,0,12,12,12,12,5255,5254,2203,2200,43,5253,5240,5239,5254,0,12,12,12,12,5253,5240,5239,5254,43,5254,5239,2182,2203,0,12,12,12,12,5254,5239,2182,2203,43,5252,5255,5256,5257,0,12,12,12,12,5252,5255,5256,5257,43,5257,5256,5221,5220,0,12,12,12,12,5257,5256,5221,5220,43,5255,2200,2199,5256,0,12,12,12,12,5255,2200,2199,5256,43,5256,2199,2030,5221,0,12,12,12,12,5256,2199,2030,5221,43,5252,5257,5258,5259,0,12,12,12,12,5252,5257,5258,5259,43,5259,5258,5164,5162,0,12,12,12,12,5259,5258,5164,5162,43,5257,5220,5227,5258,0,12,12,12,12,5257,5220,5227,5258,43,5258,5227,5074,5164,0,12,12,12,12,5258,5227,5074,5164,43,5252,5259,5260,5253,0,12,12,12,12,5252,5259,5260,5253,43,5253,5260,5242,5240,0,12,12,12,12,5253,5260,5242,5240,43,5259,5162,5161,5260,0,12,12,12,12,5259,5162,5161,5260,43,5260,5161,5116,5242,0,12,12,12,12,5260,5161,5116,5242,43,5261,5262,5263,5264,0,2447,2448,2449,2450,5261,5262,5263,5264,43,5264,5263,4586,4585,0,2450,2449,2373,2372,5264,5263,4586,4585,43,5262,5265,5266,5263,0,2448,2451,2452,2449,5262,5265,5266,5263,43,5263,5266,4587,4586,0,2449,2452,2374,2373,5263,5266,4587,4586,43,5261,5264,5267,5268,0,2447,2450,2453,2454,5261,5264,5267,5268,43,5268,5267,4577,4572,0,2454,2453,2364,2359,5268,5267,4577,4572,43,5264,4585,4594,5267,0,2450,2372,2377,2453,5264,4585,4594,5267,43,5267,4594,4578,4577,0,2453,2377,2365,2364,5267,4594,4578,4577,43,5261,5268,5269,5270,0,2447,2454,2455,2456,5261,5268,5269,5270,43,5270,5269,4678,4677,0,2456,2455,2358,2443,5270,5269,4678,4677,43,5268,4572,4571,5269,0,2454,2359,2358,2455,5268,4572,4571,5269,43,5269,4571,4574,4678,0,2455,2358,2361,2358,5269,4571,4574,4678,43,5261,5270,5271,5262,0,2447,2456,2457,2448,5261,5270,5271,5262,43,5262,5271,5272,5265,0,2448,2457,2458,2451,5262,5271,5272,5265,43,5270,4677,4684,5271,0,2456,2443,2446,2457,5270,4677,4684,5271,43,5271,4684,4672,5272,0,2457,2446,2440,2458,5271,4684,4672,5272,43,5273,5274,5275,5276,0,2459,2460,2461,2462,5273,5274,5275,5276,43,5276,5275,4602,4601,0,2462,2461,2385,2384,5276,5275,4602,4601,43,5274,5277,5278,5275,0,2460,2463,2464,2461,5274,5277,5278,5275,43,5275,5278,4603,4602,0,2461,2464,2386,2385,5275,5278,4603,4602,43,5273,5276,5279,5280,0,2459,2462,2465,2466,5273,5276,5279,5280,43,5280,5279,5266,5265,0,2466,2465,2452,2451,5280,5279,5266,5265,43,5276,4601,4610,5279,0,2462,2384,2392,2465,5276,4601,4610,5279,43,5279,4610,4587,5266,0,2465,2392,2374,2452,5279,4610,4587,5266,43,5273,5280,5281,5282,0,2459,2466,2467,2468,5273,5280,5281,5282,43,5282,5281,4671,4668,0,2468,2467,2439,2436,5282,5281,4671,4668,43,5280,5265,5272,5281,0,2466,2451,2458,2467,5280,5265,5272,5281,43,5281,5272,4672,4671,0,2467,2458,2440,2439,5281,5272,4672,4671,43,5273,5282,5283,5274,0,2459,2468,2469,2460,5273,5282,5283,5274,43,5274,5283,5284,5277,0,2460,2469,2470,2463,5274,5283,5284,5277,43,5282,4668,4667,5283,0,2468,2436,2435,2469,5282,4668,4667,5283,43,5283,4667,4649,5284,0,2469,2435,2425,2470,5283,4667,4649,5284,43,5285,5286,5287,5288,0,2471,2472,2473,2474,5285,5286,5287,5288,43,5288,5287,4648,4647,0,2474,2473,2424,2423,5288,5287,4648,4647,43,5286,5277,5284,5287,0,2472,2463,2470,2473,5286,5277,5284,5287,43,5287,5284,4649,4648,0,2473,2470,2425,2424,5287,5284,4649,4648,43,5285,5288,5289,5290,0,2471,2474,2475,2476,5285,5288,5289,5290,43,5290,5289,5291,5292,0,2476,2475,2477,2478,5290,5289,5291,5292,43,5288,4647,4656,5289,0,2474,2423,2428,2475,5288,4647,4656,5289,43,5289,4656,4522,5291,0,2475,2428,2329,2477,5289,4656,4522,5291,43,5285,5290,5293,5294,0,2471,2476,2479,2480,5285,5290,5293,5294,43,5294,5293,4625,4623,0,2480,2479,2407,2405,5294,5293,4625,4623,43,5290,5292,5295,5293,0,2476,2478,2481,2479,5290,5292,5295,5293,43,5293,5295,4509,4625,0,2479,2481,2316,2407,5293,5295,4509,4625,43,5285,5294,5296,5286,0,2471,2480,2482,2472,5285,5294,5296,5286,43,5286,5296,5278,5277,0,2472,2482,2464,2463,5286,5296,5278,5277,43,5294,4623,4622,5296,0,2480,2405,2404,2482,5294,4623,4622,5296,43,5296,4622,4603,5278,0,2482,2404,2386,2464,5296,4622,4603,5278,43,5297,5298,5299,5300,0,2483,2484,2485,2486,5297,5298,5299,5300,43,5300,5299,5301,5302,0,2486,2485,2487,2488,5300,5299,5301,5302,43,5298,5303,5304,5299,0,2484,2489,2490,2485,5298,5303,5304,5299,43,5299,5304,5305,5301,0,2485,2490,2491,2487,5299,5304,5305,5301,43,5297,5300,5306,5307,0,2483,2486,2492,2493,5297,5300,5306,5307,43,5307,5306,5308,5309,0,2493,2492,2494,2495,5307,5306,5308,5309,43,5300,5302,5310,5306,0,2486,2488,2496,2492,5300,5302,5310,5306,43,5306,5310,5311,5308,0,2492,2496,2497,2494,5306,5310,5311,5308,43,5297,5307,5312,5313,0,2483,2493,2498,2499,5297,5307,5312,5313,43,5313,5312,4557,4552,0,2499,2498,2349,2344,5313,5312,4557,4552,43,5307,5309,5314,5312,0,2493,2495,2500,2498,5307,5309,5314,5312,43,5312,5314,4558,4557,0,2498,2500,2350,2349,5312,5314,4558,4557,43,5297,5313,5315,5298,0,2483,2499,2501,2484,5297,5313,5315,5298,43,5298,5315,5316,5303,0,2484,2501,2502,2489,5298,5315,5316,5303,43,5313,4552,4551,5315,0,2499,2344,2343,2501,5313,4552,4551,5315,43,5315,4551,4554,5316,0,2501,2343,2346,2502,5315,4551,4554,5316,43,5317,5318,5319,5320,0,2503,2504,2505,2506,5317,5318,5319,5320,43,5320,5319,5321,5322,0,2506,2505,2507,2508,5320,5319,5321,5322,43,5318,4184,4200,5319,0,2504,2250,2256,2505,5318,4184,4200,5319,43,5319,4200,4202,5321,0,2505,2256,2258,2507,5319,4200,4202,5321,43,5317,5320,5323,5324,0,2503,2506,2509,2510,5317,5320,5323,5324,43,5324,5323,5304,5303,0,2510,2509,2511,2512,5324,5323,5304,5303,43,5320,5322,5325,5323,0,2506,2508,2513,2509,5320,5322,5325,5323,43,5323,5325,5305,5304,0,2509,2513,2514,2511,5323,5325,5305,5304,43,5317,5324,5326,5327,0,2503,2510,2515,2516,5317,5324,5326,5327,43,5327,5326,4638,4635,0,2516,2515,2416,2413,5327,5326,4638,4635,43,5324,5303,5316,5326,0,2510,2512,2517,2515,5324,5303,5316,5326,43,5326,5316,4554,4638,0,2515,2517,2346,2416,5326,5316,4554,4638,43,5317,5327,5328,5318,0,2503,2516,2518,2504,5317,5327,5328,5318,43,5318,5328,4185,4184,0,2504,2518,2251,2250,5318,5328,4185,4184,43,5327,4635,4634,5328,0,2516,2413,2412,2518,5327,4635,4634,5328,43,5328,4634,4186,4185,0,2518,2412,2252,2251,5328,4634,4186,4185,43,5329,5330,5331,5332,0,2519,2520,2521,2522,5329,5330,5331,5332,43,5332,5331,5325,5322,0,2522,2521,2523,2524,5332,5331,5325,5322,43,5330,5333,5334,5331,0,2520,2525,2526,2521,5330,5333,5334,5331,43,5331,5334,5305,5325,0,2521,2526,2527,2523,5331,5334,5305,5325,43,5329,5332,5335,5336,0,2519,2522,2528,2529,5329,5332,5335,5336,43,5336,5335,4211,4208,0,2529,2528,2269,2264,5336,5335,4211,4208,43,5332,5322,5321,5335,0,2522,2524,2530,2528,5332,5322,5321,5335,43,5335,5321,4202,4211,0,2528,2530,2270,2269,5335,5321,4202,4211,43,5329,5336,5337,5338,0,2519,2529,2531,2532,5329,5336,5337,5338,43,5338,5337,4619,4616,0,2532,2531,2401,2398,5338,5337,4619,4616,43,5336,4208,4207,5337,0,2529,2264,2263,2531,5336,4208,4207,5337,43,5337,4207,3921,4619,0,2531,2263,2243,2401,5337,4207,3921,4619,43,5329,5338,5339,5330,0,2519,2532,2533,2520,5329,5338,5339,5330,43,5330,5339,5340,5333,0,2520,2533,2534,2525,5330,5339,5340,5333,43,5338,4616,4615,5339,0,2532,2398,2397,2533,5338,4616,4615,5339,43,5339,4615,4503,5340,0,2533,2397,2310,2534,5339,4615,4503,5340,43,5341,5342,5343,5344,0,2535,2536,2537,2538,5341,5342,5343,5344,43,5344,5343,5345,5346,0,2538,2537,2539,2540,5344,5343,5345,5346,43,5342,4494,4493,5343,0,2536,2301,2300,2537,5342,4494,4493,5343,43,5343,4493,4497,5345,0,2537,2300,2304,2539,5343,4493,4497,5345,43,5341,5344,5347,5348,0,2535,2538,2541,2542,5341,5344,5347,5348,43,5348,5347,5310,5302,0,2542,2541,2543,2544,5348,5347,5310,5302,43,5344,5346,5349,5347,0,2538,2540,2545,2541,5344,5346,5349,5347,43,5347,5349,5311,5310,0,2541,2545,2546,2543,5347,5349,5311,5310,43,5341,5348,5350,5351,0,2535,2542,2547,2548,5341,5348,5350,5351,43,5351,5350,5334,5333,0,2548,2547,2549,2550,5351,5350,5334,5333,43,5348,5302,5301,5350,0,2542,2544,2551,2547,5348,5302,5301,5350,43,5350,5301,5305,5334,0,2547,2551,2552,2549,5350,5301,5305,5334,43,5341,5351,5352,5342,0,2535,2548,2553,2536,5341,5351,5352,5342,43,5342,5352,4502,4494,0,2536,2553,2309,2301,5342,5352,4502,4494,43,5351,5333,5340,5352,0,2548,2550,2554,2553,5351,5333,5340,5352,43,5352,5340,4503,4502,0,2553,2554,2310,2309,5352,5340,4503,4502,43,5353,5354,5355,5356,0,2555,2556,2557,2558,5353,5354,5355,5356,43,5356,5355,5357,5358,0,2558,2557,2559,2560,5356,5355,5357,5357,43,5354,5359,5360,5355,0,2556,2561,2562,2557,5354,5358,5359,5355,43,5355,5360,5361,5357,0,2557,2562,2563,2559,5355,5359,5357,5357,43,5353,5356,5362,5363,0,2555,2558,2564,2565,5353,5356,5360,5361,43,5363,5362,5364,5365,0,2565,2564,2566,2567,5361,5360,5362,5363,43,5356,5358,5366,5362,0,2558,2560,2568,2564,5356,5357,5357,5360,43,5362,5366,5367,5364,0,2564,2568,2569,2566,5360,5357,5364,5362,43,5353,5363,5368,5369,0,2555,2565,2570,2571,5353,5361,5365,5366,43,5369,5368,4512,4507,0,2571,2570,2319,2314,5366,5365,4512,4507,43,5363,5365,5370,5368,0,2565,2567,2572,2570,5361,5363,5367,5365,43,5368,5370,4513,4512,0,2570,2572,2320,2319,5365,5367,4513,4512,43,5353,5369,5371,5354,0,2555,2571,2573,2556,5353,5366,5368,5354,43,5354,5371,5372,5359,0,2556,2573,2574,2561,5354,5368,5369,5358,43,5369,4507,4506,5371,0,2571,2314,2313,2573,5366,4507,4506,5368,43,5371,4506,4509,5372,0,2573,2313,2316,2574,5368,4506,4509,5369,43,5373,5374,5375,5376,0,2575,2576,2577,2578,5370,5371,5372,5373,43,5376,5375,5377,5378,0,2578,2577,2579,2580,5373,5372,5374,5357,43,5374,5379,5380,5375,0,2576,2581,2582,2577,5371,5375,5376,5372,43,5375,5380,5381,5377,0,2577,2582,2583,2579,5372,5376,5377,5374,43,5373,5376,5382,5383,0,2575,2578,2584,2585,5370,5373,5378,5379,43,5383,5382,5360,5359,0,2585,2584,2562,2561,5379,5378,5359,5358,43,5376,5378,5384,5382,0,2578,2580,2586,2584,5373,5357,5357,5378,43,5382,5384,5361,5360,0,2584,2586,2563,2562,5378,5357,5357,5359,43,5373,5383,5385,5386,0,2575,2585,2587,2588,5370,5379,5380,5381,43,5386,5385,5295,5292,0,2588,2587,2481,2478,5381,5380,5295,5292,43,5383,5359,5372,5385,0,2585,2561,2574,2587,5379,5358,5369,5380,43,5385,5372,4509,5295,0,2587,2574,2316,2481,5380,5369,4509,5295,43,5373,5386,5387,5374,0,2575,2588,2589,2576,5370,5381,5382,5371,43,5374,5387,5388,5379,0,2576,2589,2590,2581,5371,5382,5383,5375,43,5386,5292,5291,5387,0,2588,2478,2477,2589,5381,5292,5291,5382,43,5387,5291,4522,5388,0,2589,2477,2329,2590,5382,5291,4522,5383,43,5389,5390,5391,5392,0,2591,2592,2593,2594,5384,5385,5386,5387,43,5392,5391,5393,5394,0,2594,2593,2595,2596,5387,5386,5388,5389,43,5390,4520,4536,5391,0,2592,2327,2333,2593,5385,4520,4536,5386,43,5391,4536,4538,5393,0,2593,2333,2335,2595,5386,4536,4538,5388,43,5389,5392,5395,5396,0,2591,2594,2597,2598,5384,5387,5390,5391,43,5396,5395,5397,5398,0,2598,2597,2599,2600,5391,5390,5357,5357,43,5392,5394,5399,5395,0,2594,2596,2601,2597,5387,5389,5392,5390,43,5395,5399,5400,5397,0,2597,2601,2602,2599,5390,5392,5357,5357,43,5389,5396,5401,5402,0,2591,2598,2603,2604,5384,5391,5393,5394,43,5402,5401,5380,5379,0,2604,2603,2582,2581,5394,5393,5376,5375,43,5396,5398,5403,5401,0,2598,2600,2605,2603,5391,5357,5357,5393,43,5401,5403,5381,5380,0,2603,2605,2583,2582,5393,5357,5377,5376,43,5389,5402,5404,5390,0,2591,2604,2606,2592,5384,5394,5395,5385,43,5390,5404,4521,4520,0,2592,2606,2328,2327,5385,5395,4521,4520,43,5402,5379,5388,5404,0,2604,2581,2590,2606,5394,5375,5383,5395,43,5404,5388,4522,4521,0,2606,2590,2329,2328,5395,5383,4522,4521,43,5405,5406,5407,5408,0,2607,2608,2609,2610,5374,5357,5357,5357,43,5408,5407,5384,5378,0,2610,2609,2586,2580,5357,5357,5357,5357,43,5406,5358,5357,5407,0,2608,2560,2559,2609,5357,5357,5357,5357,43,5407,5357,5361,5384,0,2609,2559,2563,2586,5357,5357,5357,5357,43,5405,5408,5409,5410,0,2607,2610,2611,2612,5374,5357,5377,5374,43,5410,5409,5403,5398,0,2612,2611,2605,2600,5374,5377,5357,5357,43,5408,5378,5377,5409,0,2610,2580,2579,2611,5357,5357,5374,5377,43,5409,5377,5381,5403,0,2611,2579,2583,2605,5377,5374,5377,5357,43,5405,5410,5411,5412,0,2607,2612,2613,2614,5374,5374,5357,5364,43,5412,5411,5413,5414,0,2614,2613,2615,2616,5364,5357,5357,5364,43,5410,5398,5397,5411,0,2612,2600,2599,2613,5374,5357,5357,5357,43,5411,5397,5400,5413,0,2613,2599,2602,2615,5357,5357,5357,5357,43,5405,5412,5415,5406,0,2607,2614,2617,2608,5374,5364,5364,5357,43,5406,5415,5366,5358,0,2608,2617,2568,2560,5357,5364,5357,5357,43,5412,5414,5416,5415,0,2614,2616,2618,2617,5364,5364,5364,5364,43,5415,5416,5367,5366,0,2617,2618,2569,2568,5364,5364,5364,5357,43,5417,5418,5419,5420,0,2619,2620,948,948,5396,5397,5398,5399,43,5420,5419,5421,5422,0,948,948,931,931,5399,5398,5400,5401,43,5418,4706,4717,5419,0,2620,2283,948,948,5397,4706,4717,5398,43,5419,4717,4719,5421,0,948,948,931,931,5398,4717,4719,5400,43,5417,5420,5423,5424,0,2619,948,948,2620,5396,5399,5402,5403,43,5424,5423,4379,4374,0,2620,948,948,2283,5403,5402,4379,4374,43,5420,5422,5425,5423,0,948,931,931,948,5399,5401,5404,5402,43,5423,5425,4380,4379,0,948,931,931,948,5402,5404,4380,4379,43,5417,5424,5426,5427,0,2619,2620,2621,2622,5396,5403,5405,5406,43,5427,5426,5428,5429,0,2622,2621,2623,2624,5406,5405,5407,5408,43,5424,4374,4373,5426,0,2620,2283,2282,2621,5403,4374,4373,5405,43,5426,4373,4376,5428,0,2621,2282,2285,2623,5405,4373,4376,5407,43,5417,5427,5430,5418,0,2619,2622,2621,2620,5396,5406,5409,5397,43,5418,5430,4707,4706,0,2620,2621,2282,2283,5397,5409,4707,4706,43,5427,5429,5431,5430,0,2622,2624,2623,2621,5406,5408,5410,5409,43,5430,5431,4708,4707,0,2621,2623,2285,2282,5409,5410,4708,4707,43,5432,5433,5434,5435,0,2625,2626,2627,2628,5411,5412,5413,5414,43,5435,5434,5431,5429,0,2628,2627,2623,2624,5414,5413,5410,5408,43,5433,4738,4747,5434,0,2626,2629,2630,2627,5412,4738,4747,5413,43,5434,4747,4708,5431,0,2627,2630,2285,2623,5413,4747,4708,5410,43,5432,5435,5436,5437,0,2625,2628,2627,2626,5411,5414,5415,5416,43,5437,5436,4386,4385,0,2626,2627,2630,2629,5416,5415,4386,4385,43,5435,5429,5428,5436,0,2628,2624,2623,2627,5414,5408,5407,5415,43,5436,5428,4376,4386,0,2627,2623,2285,2630,5415,5407,4376,4386,43,5432,5437,5438,5439,0,2625,2626,2631,2632,5411,5416,5417,5418,43,5439,5438,5440,5441,0,2632,2631,2633,2634,5418,5417,5419,5420,43,5437,4385,4394,5438,0,2626,2629,2635,2631,5416,4385,4394,5417,43,5438,4394,4396,5440,0,2631,2635,382,2633,5417,4394,4396,5419,43,5432,5439,5442,5433,0,2625,2632,2631,2626,5411,5418,5421,5412,43,5433,5442,4739,4738,0,2626,2631,2635,2629,5412,5421,4739,4738,43,5439,5441,5443,5442,0,2632,2634,2633,2631,5418,5420,5422,5421,43,5442,5443,4740,4739,0,2631,2633,382,2635,5421,5422,4740,4739,43,5444,5445,5446,5447,0,2636,2637,2638,2639,5423,5424,5425,5426,43,5447,5446,5443,5441,0,2639,2638,2633,2634,5426,5425,5422,5420,43,5445,4771,4770,5446,0,2637,2294,2295,2638,5424,4771,4770,5425,43,5446,4770,4740,5443,0,2638,2295,382,2633,5425,4770,4740,5422,43,5444,5447,5448,5449,0,2636,2639,2638,2637,5423,5426,5427,5428,43,5449,5448,4402,4401,0,2637,2638,2295,2294,5428,5427,4402,4401,43,5447,5441,5440,5448,0,2639,2634,2633,2638,5426,5420,5419,5427,43,5448,5440,4396,4402,0,2638,2633,382,2295,5427,5419,4396,4402,43,5444,5449,5450,5451,0,2636,2637,915,915,5423,5428,5429,5430,43,5451,5450,5452,5453,0,915,915,916,916,5430,5429,5431,5432,43,5449,4401,4410,5450,0,2637,2294,915,915,5428,4401,4410,5429,43,5450,4410,4412,5452,0,915,915,916,916,5429,4410,4412,5431,43,5444,5451,5454,5445,0,2636,915,915,2637,5423,5430,5433,5424,43,5445,5454,4774,4771,0,2637,915,915,2294,5424,5433,4774,4771,43,5451,5453,5455,5454,0,915,916,916,915,5430,5432,5434,5433,43,5454,5455,4775,4774,0,915,916,916,915,5433,5434,4775,4774,43,5456,5457,5458,5459,0,953,953,922,922,5435,5436,5437,5438,43,5459,5458,5455,5453,0,922,922,916,916,5438,5437,5434,5432,43,5457,4799,4798,5458,0,953,953,922,922,5436,4799,4798,5437,43,5458,4798,4775,5455,0,922,922,916,916,5437,4798,4775,5434,43,5456,5459,5460,5461,0,953,922,922,953,5435,5438,5439,5440,43,5461,5460,4418,4417,0,953,922,922,953,5440,5439,4418,4417,43,5459,5453,5452,5460,0,922,916,916,922,5438,5432,5431,5439,43,5460,5452,4412,4418,0,922,916,916,922,5439,5431,4412,4418,43,5456,5461,5462,5463,0,953,953,939,939,5435,5440,5441,5442,43,5463,5462,5464,5465,0,939,939,328,328,5442,5441,5443,5444,43,5461,4417,4426,5462,0,953,953,939,939,5440,4417,4426,5441,43,5462,4426,4428,5464,0,939,939,328,328,5441,4426,4428,5443,43,5456,5463,5466,5457,0,953,939,939,953,5435,5442,5445,5436,43,5457,5466,4802,4799,0,953,939,939,953,5436,5445,4802,4799,43,5463,5465,5467,5466,0,939,328,328,939,5442,5444,5446,5445,43,5466,5467,4803,4802,0,939,328,328,939,5445,5446,4803,4802,43,5468,5469,5470,5471,0,929,929,12,12,5447,5448,5449,5450,43,5471,5470,5472,5473,0,12,12,12,12,5450,5449,5451,5452,43,5469,4827,4826,5470,0,929,929,12,12,5448,4827,4826,5449,43,5470,4826,4829,5472,0,12,12,12,12,5449,4826,4829,5451,43,5468,5471,5474,5475,0,929,12,12,929,5447,5450,5453,5454,43,5475,5474,4443,4440,0,929,12,12,929,5454,5453,4443,4440,43,5471,5473,5476,5474,0,12,12,12,12,5450,5452,5455,5453,43,5474,5476,4444,4443,0,12,12,12,12,5453,5455,4444,4443,43,5468,5475,5477,5478,0,929,929,928,928,5447,5454,5456,5457,43,5478,5477,5425,5422,0,928,928,931,931,5457,5456,5404,5401,43,5475,4440,4439,5477,0,929,929,928,928,5454,4440,4439,5456,43,5477,4439,4380,5425,0,928,928,931,931,5456,4439,4380,5404,43,5468,5478,5479,5469,0,929,928,928,929,5447,5457,5458,5448,43,5469,5479,4831,4827,0,929,928,928,929,5448,5458,4831,4827,43,5478,5422,5421,5479,0,928,931,931,928,5457,5401,5400,5458,43,5479,5421,4719,4831,0,928,931,931,928,5458,5400,4719,4831,43,5480,5481,5482,5483,0,12,12,12,12,5459,5460,5461,5462,43,5483,5482,5484,5485,0,12,12,12,12,5462,5461,5463,5464,43,5481,4853,4864,5482,0,12,12,12,12,5460,4853,4864,5461,43,5482,4864,4866,5484,0,12,12,12,12,5461,4864,4866,5463,43,5480,5483,5486,5487,0,12,12,12,12,5459,5462,5465,5466,43,5487,5486,4452,4451,0,12,12,12,12,5466,5465,4452,4451,43,5483,5485,5488,5486,0,12,12,12,12,5462,5464,5467,5465,43,5486,5488,4453,4452,0,12,12,12,12,5465,5467,4453,4452,43,5480,5487,5489,5490,0,12,12,12,12,5459,5466,5468,5469,43,5490,5489,5491,5492,0,12,12,12,12,5469,5468,5470,5471,43,5487,4451,4462,5489,0,12,12,12,12,5466,4451,4462,5468,43,5489,4462,4464,5491,0,12,12,12,12,5468,4462,4464,5470,43,5480,5490,5493,5481,0,12,12,12,12,5459,5469,5472,5460,43,5481,5493,4854,4853,0,12,12,12,12,5460,5472,4854,4853,43,5490,5492,5494,5493,0,12,12,12,12,5469,5471,5473,5472,43,5493,5494,4855,4854,0,12,12,12,12,5472,5473,4855,4854,43,5495,5496,5497,5498,0,932,932,933,933,5474,5475,5476,5477,43,5498,5497,5467,5465,0,933,933,328,328,5477,5476,5446,5444,43,5496,4885,4884,5497,0,932,932,933,933,5475,4885,4884,5476,43,5497,4884,4803,5467,0,933,933,328,328,5476,4884,4803,5446,43,5495,5498,5499,5500,0,932,933,933,932,5474,5477,5478,5479,43,5500,5499,4476,4474,0,932,933,933,932,5479,5478,4476,4474,43,5498,5465,5464,5499,0,933,328,328,933,5477,5444,5443,5478,43,5499,5464,4428,4476,0,933,328,328,933,5478,5443,4428,4476,43,5495,5500,5501,5502,0,932,932,12,12,5474,5479,5480,5481,43,5502,5501,5488,5485,0,12,12,12,12,5481,5480,5467,5464,43,5500,4474,4473,5501,0,932,932,12,12,5479,4474,4473,5480,43,5501,4473,4453,5488,0,12,12,12,12,5480,4473,4453,5467,43,5495,5502,5503,5496,0,932,12,12,932,5474,5481,5482,5475,43,5496,5503,4887,4885,0,932,12,12,932,5475,5482,4887,4885,43,5502,5485,5484,5503,0,12,12,12,12,5481,5464,5463,5482,43,5503,5484,4866,4887,0,12,12,12,12,5482,5463,4866,4887,43,5504,5505,5506,5507,0,12,12,12,12,5483,5484,5485,5486,43,5507,5506,5494,5492,0,12,12,12,12,5486,5485,5473,5471,43,5505,4901,4908,5506,0,12,12,12,12,5484,4901,4908,5485,43,5506,4908,4855,5494,0,12,12,12,12,5485,4908,4855,5473,43,5504,5507,5508,5509,0,12,12,12,12,5483,5486,5487,5488,43,5509,5508,4488,4486,0,12,12,12,12,5488,5487,4488,4486,43,5507,5492,5491,5508,0,12,12,12,12,5486,5471,5470,5487,43,5508,5491,4464,4488,0,12,12,12,12,5487,5470,4464,4488,43,5504,5509,5510,5511,0,12,12,12,12,5483,5488,5489,5490,43,5511,5510,5476,5473,0,12,12,12,12,5490,5489,5455,5452,43,5509,4486,4485,5510,0,12,12,12,12,5488,4486,4485,5489,43,5510,4485,4444,5476,0,12,12,12,12,5489,4485,4444,5455,43,5504,5511,5512,5505,0,12,12,12,12,5483,5490,5491,5484,43,5505,5512,4902,4901,0,12,12,12,12,5484,5491,4902,4901,43,5511,5473,5472,5512,0,12,12,12,12,5490,5452,5451,5491,43,5512,5472,4829,4902,0,12,12,12,12,5491,5451,4829,4902,43,5513,5514,5515,5516,0,2640,2641,2642,2643,5492,5493,5494,5495,43,5516,5515,5517,5518,0,2643,2642,2644,2645,5495,5494,5496,5497,43,5514,3249,3256,5515,0,2641,1811,1818,2642,5493,3249,3256,5494,43,5515,3256,2846,5517,0,2642,1818,1449,2644,5494,3256,2846,5496,43,5513,5516,5519,5520,0,2640,2643,2646,2647,5492,5495,5498,5499,43,5520,5519,5521,5522,0,2647,2646,2648,2649,5499,5498,5500,5501,43,5516,5518,5523,5519,0,2643,2645,2650,2646,5495,5497,5502,5498,43,5519,5523,5524,5521,0,2646,2650,2651,2648,5498,5502,5503,5500,43,5513,5520,5525,5526,0,2640,2647,2652,2653,5492,5499,5504,5505,43,5526,5525,5527,5528,0,2653,2652,2654,2655,5505,5504,5506,5507,43,5520,5522,5529,5525,0,2647,2649,2656,2652,5499,5501,5508,5504,43,5525,5529,5530,5527,0,2652,2656,2657,2654,5504,5508,5509,5506,43,5513,5526,5531,5514,0,2640,2653,2658,2641,5492,5505,5510,5493,43,5514,5531,3250,3249,0,2641,2658,1812,1811,5493,5510,3250,3249,43,5526,5528,5532,5531,0,2653,2655,2659,2658,5505,5507,5511,5510,43,5531,5532,2795,3250,0,2658,2659,1398,1812,5510,5511,2795,3250,43,5533,5534,5535,5536,0,2660,2661,2662,2663,5512,5513,5514,5515,43,5536,5535,5537,5538,0,2663,2662,2664,2665,5515,5514,5516,5517,43,5534,2815,2814,5535,0,2661,1418,1417,2662,5513,2815,2814,5514,43,5535,2814,2817,5537,0,2662,1417,1420,2664,5514,2814,2817,5516,43,5533,5536,5539,5540,0,2660,2663,2666,2667,5512,5515,5518,5519,43,5540,5539,5541,5542,0,2667,2666,2668,2669,5519,5518,5520,5521,43,5536,5538,5543,5539,0,2663,2665,2670,2666,5515,5517,5522,5518,43,5539,5543,5544,5541,0,2666,2670,2671,2668,5518,5522,5523,5520,43,5533,5540,5545,5546,0,2660,2667,2672,2673,5512,5519,5524,5525,43,5546,5545,5547,5548,0,2673,2672,2674,2675,5525,5524,5526,5527,43,5540,5542,5549,5545,0,2667,2669,2676,2672,5519,5521,5528,5524,43,5545,5549,5550,5547,0,2672,2676,2677,2674,5524,5528,5529,5526,43,5533,5546,5551,5534,0,2660,2673,2678,2661,5512,5525,5530,5513,43,5534,5551,2822,2815,0,2661,2678,1425,1418,5513,5530,2822,2815,43,5546,5548,5552,5551,0,2673,2675,2679,2678,5525,5527,5531,5530,43,5551,5552,2823,2822,0,2678,2679,1426,1425,5530,5531,2823,2822,43,5553,5554,5555,5556,0,2680,2681,2682,2683,5532,5533,5534,5535,43,5556,5555,5557,5558,0,2683,2682,2684,2685,5535,5534,5536,5537,43,5554,5559,5560,5555,0,2681,2686,2687,2682,5533,5538,5539,5534,43,5555,5560,5561,5557,0,2682,2687,2688,2684,5534,5539,5540,5536,43,5553,5556,5562,5563,0,2680,2683,1003,1003,5532,5535,5541,5542,43,5563,5562,5564,5565,0,1003,1003,1004,1004,5542,5541,5543,5544,43,5556,5558,5566,5562,0,2683,2685,1003,1003,5535,5537,5545,5541,43,5562,5566,5567,5564,0,1003,1003,1004,1004,5541,5545,5546,5543,43,5553,5563,5568,5569,0,2680,1003,1003,2689,5532,5542,5547,5548,43,5569,5568,5570,5571,0,2689,1003,1003,2690,5548,5547,5549,5550,43,5563,5565,5572,5568,0,1003,1004,1004,1003,5542,5544,5551,5547,43,5568,5572,5573,5570,0,1003,1004,1004,1003,5547,5551,5552,5549,43,5553,5569,5574,5554,0,2680,2689,2691,2681,5532,5548,5553,5533,43,5554,5574,5575,5559,0,2681,2691,2692,2686,5533,5553,5554,5538,43,5569,5571,5576,5574,0,2689,2690,2693,2691,5548,5550,5555,5553,43,5574,5576,5577,5575,0,2691,2693,2694,2692,5553,5555,5556,5554,43,5578,5579,5580,5581,0,2695,2696,2697,2698,5557,5558,5559,5560,43,5581,5580,5552,5548,0,2698,2697,2679,2675,5560,5559,5531,5527,43,5579,3261,3268,5580,0,2696,1823,1830,2697,5558,3261,3268,5559,43,5580,3268,2823,5552,0,2697,1830,1426,2679,5559,3268,2823,5531,43,5578,5581,5582,5583,0,2695,2698,2699,2700,5557,5560,5561,5562,43,5583,5582,5584,5585,0,2700,2699,2701,2702,5562,5561,5563,5564,43,5581,5548,5547,5582,0,2698,2675,2674,2699,5560,5527,5526,5561,43,5582,5547,5550,5584,0,2699,2674,2677,2701,5561,5526,5529,5563,43,5578,5583,5586,5587,0,2695,2700,2703,2704,5557,5562,5565,5566,43,5587,5586,5588,5589,0,2704,2703,2705,2706,5566,5565,5567,5568,43,5583,5585,5590,5586,0,2700,2702,2707,2703,5562,5564,5569,5565,43,5586,5590,5591,5588,0,2703,2707,2708,2705,5565,5569,5570,5567,43,5578,5587,5592,5579,0,2695,2704,2709,2696,5557,5566,5571,5558,43,5579,5592,3262,3261,0,2696,2709,1824,1823,5558,5571,3262,3261,43,5587,5589,5593,5592,0,2704,2706,2710,2709,5566,5568,5572,5571,43,5592,5593,2840,3262,0,2709,2710,1443,1824,5571,5572,2840,3262,43,5594,5595,5596,5597,0,2711,2712,2713,2714,5573,5574,5575,5576,43,5597,5596,5598,5599,0,2714,2713,2715,2711,5576,5575,5577,5578,43,5595,5600,5601,5596,0,2712,2716,2717,2713,5574,5579,5580,5575,43,5596,5601,5602,5598,0,2713,2717,2718,2715,5575,5580,5581,5577,43,5594,5597,5603,5604,0,2711,2714,1003,1003,5573,5576,5582,5583,43,5604,5603,5605,5606,0,1003,1003,1004,1004,5583,5582,5584,5585,43,5597,5599,5607,5603,0,2714,2711,1003,1003,5576,5578,5586,5582,43,5603,5607,5608,5605,0,1003,1003,1004,1004,5582,5586,5587,5584,43,5594,5604,5609,5610,0,2711,1003,1003,2719,5573,5583,5588,5589,43,5610,5609,5611,5612,0,2719,1003,1003,2720,5589,5588,5590,5591,43,5604,5606,5613,5609,0,1003,1004,1004,1003,5583,5585,5592,5588,43,5609,5613,5614,5611,0,1003,1004,1004,1003,5588,5592,5593,5590,43,5594,5610,5615,5595,0,2711,2719,2721,2712,5573,5589,5594,5574,43,5595,5615,5616,5600,0,2712,2721,2722,2716,5574,5594,5595,5579,43,5610,5612,5617,5615,0,2719,2720,2723,2721,5589,5591,5596,5594,43,5615,5617,5618,5616,0,2721,2723,2724,2722,5594,5596,5597,5595,43,5619,5620,5621,5622,0,2725,2726,2727,2728,5598,5599,5600,5601,43,5622,5621,5623,5624,0,2728,2727,2729,2730,5601,5600,5602,5603,43,5620,2856,2855,5621,0,2726,1459,1458,2727,5599,2856,2855,5600,43,5621,2855,2801,5623,0,2727,1458,1404,2729,5600,2855,2801,5602,43,5619,5622,5625,5626,0,2725,2728,2731,2732,5598,5601,5604,5605,43,5626,5625,5627,5628,0,2732,2731,2733,2734,5605,5604,5606,5607,43,5622,5624,5629,5625,0,2728,2730,2735,2731,5601,5603,5608,5604,43,5625,5629,5630,5627,0,2731,2735,2736,2733,5604,5608,5609,5606,43,5619,5626,5631,5632,0,2725,2732,2737,2738,5598,5605,5610,5611,43,5632,5631,5633,5634,0,2738,2737,2739,2740,5611,5610,5612,5613,43,5626,5628,5635,5631,0,2732,2734,2741,2737,5605,5607,5614,5610,43,5631,5635,5636,5633,0,2737,2741,2742,2739,5610,5614,5615,5612,43,5619,5632,5637,5620,0,2725,2738,2743,2726,5598,5611,5616,5599,43,5620,5637,2861,2856,0,2726,2743,1464,1459,5599,5616,2861,2856,43,5632,5634,5638,5637,0,2738,2740,2744,2743,5611,5613,5617,5616,43,5637,5638,610,2861,0,2743,2744,356,1464,5616,5617,610,2861,43,5639,5640,5641,5642,0,2745,2746,2747,2685,5618,5619,5620,5621,43,5642,5641,5643,5644,0,2685,2747,2748,2749,5621,5620,5622,5623,43,5640,5645,5646,5641,0,2746,2750,2751,2747,5619,5624,5625,5620,43,5641,5646,5647,5643,0,2747,2751,2752,2748,5620,5625,5626,5622,43,5639,5642,5648,5649,0,2745,2685,1003,1003,5618,5621,5627,5628,43,5649,5648,5650,5651,0,1003,1003,1004,1004,5628,5627,5629,5630,43,5642,5644,5652,5648,0,2685,2749,1003,1003,5621,5623,5631,5627,43,5648,5652,5653,5650,0,1003,1003,1004,1004,5627,5631,5632,5629,43,5639,5649,5654,5655,0,2745,1003,1003,2753,5618,5628,5633,5634,43,5655,5654,5607,5599,0,2753,1003,1003,2711,5634,5633,5586,5578,43,5649,5651,5656,5654,0,1003,1004,1004,1003,5628,5630,5635,5633,43,5654,5656,5608,5607,0,1003,1004,1004,1003,5633,5635,5587,5586,43,5639,5655,5657,5640,0,2745,2753,2754,2746,5618,5634,5636,5619,43,5640,5657,5658,5645,0,2746,2754,2755,2750,5619,5636,5637,5624,43,5655,5599,5598,5657,0,2753,2711,2715,2754,5634,5578,5577,5636,43,5657,5598,5602,5658,0,2754,2715,2718,2755,5636,5577,5581,5637,43,5659,5660,5661,5662,0,2756,2757,2758,2759,5638,5639,5640,5641,43,5662,5661,5663,5664,0,2759,2758,2760,2761,5641,5640,5642,5643,43,5660,5542,5541,5661,0,2757,2669,2668,2758,5639,5521,5520,5640,43,5661,5541,5544,5663,0,2758,2668,2671,2760,5640,5520,5523,5642,43,5659,5662,5665,5666,0,2756,2759,2762,2763,5638,5641,5644,5645,43,5666,5665,5646,5645,0,2763,2762,2751,2750,5645,5644,5625,5624,43,5662,5664,5667,5665,0,2759,2761,2764,2762,5641,5643,5646,5644,43,5665,5667,5647,5646,0,2762,2764,2752,2751,5644,5646,5626,5625,43,5659,5666,5668,5669,0,2756,2763,2765,2766,5638,5645,5647,5648,43,5669,5668,5670,5671,0,2766,2765,2767,2768,5648,5647,5649,5650,43,5666,5645,5658,5668,0,2763,2750,2755,2765,5645,5624,5637,5647,43,5668,5658,5602,5670,0,2765,2755,2718,2767,5647,5637,5581,5649,43,5659,5669,5672,5660,0,2756,2766,2769,2757,5638,5648,5651,5639,43,5660,5672,5549,5542,0,2757,2769,2676,2669,5639,5651,5528,5521,43,5669,5671,5673,5672,0,2766,2768,2770,2769,5648,5650,5652,5651,43,5672,5673,5550,5549,0,2769,2770,2677,2676,5651,5652,5529,5528,43,5674,5675,5676,5677,0,2771,2772,2773,2774,5653,5654,5655,5656,43,5677,5676,5678,5679,0,2774,2773,2775,2776,5656,5655,5657,5658,43,5675,5600,5616,5676,0,2772,2716,2722,2773,5654,5579,5595,5655,43,5676,5616,5618,5678,0,2773,2722,2724,2775,5655,5595,5597,5657,43,5674,5677,5680,5681,0,2771,2774,2777,2778,5653,5656,5659,5660,43,5681,5680,5590,5585,0,2778,2777,2707,2702,5660,5659,5569,5564,43,5677,5679,5682,5680,0,2774,2776,2779,2777,5656,5658,5661,5659,43,5680,5682,5591,5590,0,2777,2779,2708,2707,5659,5661,5570,5569,43,5674,5681,5683,5684,0,2771,2778,2780,2781,5653,5660,5662,5663,43,5684,5683,5673,5671,0,2781,2780,2770,2768,5663,5662,5652,5650,43,5681,5585,5584,5683,0,2778,2702,2701,2780,5660,5564,5563,5662,43,5683,5584,5550,5673,0,2780,2701,2677,2770,5662,5563,5529,5652,43,5674,5684,5685,5675,0,2771,2781,2782,2772,5653,5663,5664,5654,43,5675,5685,5601,5600,0,2772,2782,2717,2716,5654,5664,5580,5579,43,5684,5671,5670,5685,0,2781,2768,2767,2782,5663,5650,5649,5664,43,5685,5670,5602,5601,0,2782,2767,2718,2717,5664,5649,5581,5580,43,5686,5687,5688,5689,0,2783,2784,2785,2786,5665,5666,5667,5668,43,5689,5688,5690,5691,0,2786,2785,2787,2788,5668,5667,5669,5670,43,5687,5559,5575,5688,0,2784,2686,2692,2785,5666,5538,5554,5667,43,5688,5575,5577,5690,0,2785,2692,2694,2787,5667,5554,5556,5669,43,5686,5689,5692,5693,0,2783,2786,2789,2790,5665,5668,5671,5672,43,5693,5692,5529,5522,0,2790,2789,2656,2649,5672,5671,5508,5501,43,5689,5691,5694,5692,0,2786,2788,2791,2789,5668,5670,5673,5671,43,5692,5694,5530,5529,0,2789,2791,2657,2656,5671,5673,5509,5508,43,5686,5693,5695,5696,0,2783,2790,2792,2793,5665,5672,5674,5675,43,5696,5695,5697,5698,0,2793,2792,2794,2795,5675,5674,5676,5677,43,5693,5522,5521,5695,0,2790,2649,2648,2792,5672,5501,5500,5674,43,5695,5521,5524,5697,0,2792,2648,2651,2794,5674,5500,5503,5676,43,5686,5696,5699,5687,0,2783,2793,2796,2784,5665,5675,5678,5666,43,5687,5699,5560,5559,0,2784,2796,2687,2686,5666,5678,5539,5538,43,5696,5698,5700,5699,0,2793,2795,2797,2796,5675,5677,5679,5678,43,5699,5700,5561,5560,0,2796,2797,2688,2687,5678,5679,5540,5539,43,5701,5702,5703,5704,0,2798,2799,2800,2801,5680,5681,5682,5683,43,5704,5703,5705,5706,0,2801,2800,2802,2803,5683,5682,5684,5685,43,5702,5707,5708,5703,0,2799,2804,2805,2800,5681,5686,5687,5682,43,5703,5708,5709,5705,0,2800,2805,2806,2802,5682,5687,5688,5684,43,5701,5704,5710,5711,0,2798,2801,2807,2808,5680,5683,5689,5690,43,5711,5710,5635,5628,0,2808,2807,2741,2734,5690,5689,5614,5607,43,5704,5706,5712,5710,0,2801,2803,2809,2807,5683,5685,5691,5689,43,5710,5712,5636,5635,0,2807,2809,2742,2741,5689,5691,5615,5614,43,5701,5711,5713,5714,0,2798,2808,2810,2811,5680,5690,5692,5693,43,5714,5713,5715,5716,0,2811,2810,2812,2813,5693,5692,5694,5695,43,5711,5628,5627,5713,0,2808,2734,2733,2810,5690,5607,5606,5692,43,5713,5627,5630,5715,0,2810,2733,2736,2812,5692,5606,5609,5694,43,5701,5714,5717,5702,0,2798,2811,2814,2799,5680,5693,5696,5681,43,5702,5717,5718,5707,0,2799,2814,2815,2804,5681,5696,5697,5686,43,5714,5716,5719,5717,0,2811,2813,2816,2814,5693,5695,5698,5696,43,5717,5719,5720,5718,0,2814,2816,2817,2815,5696,5698,5699,5697,43,5721,5722,5723,5724,0,2818,2819,2820,2821,5700,5701,5702,5703,43,5724,5723,5725,5726,0,2821,2820,2822,2823,5703,5702,5704,5705,43,5722,5727,5728,5723,0,2819,2824,2825,2820,5701,5706,5707,5702,43,5723,5728,5729,5725,0,2820,2825,2826,2822,5702,5707,5708,5704,43,5721,5724,5730,5731,0,2818,2821,2827,2828,5700,5703,5709,5710,43,5731,5730,5708,5707,0,2828,2827,2805,2804,5710,5709,5687,5686,43,5724,5726,5732,5730,0,2821,2823,2829,2827,5703,5705,5711,5709,43,5730,5732,5709,5708,0,2827,2829,2806,2805,5709,5711,5688,5687,43,5721,5731,5733,5734,0,2818,2828,2830,2831,5700,5710,5712,5713,43,5734,5733,5735,5736,0,2831,2830,2832,2833,5713,5712,5714,5715,43,5731,5707,5718,5733,0,2828,2804,2815,2830,5710,5686,5697,5712,43,5733,5718,5720,5735,0,2830,2815,2817,2832,5712,5697,5699,5714,43,5721,5734,5737,5722,0,2818,2831,1003,2819,5700,5713,5716,5701,43,5722,5737,5738,5727,0,2819,1003,1004,2824,5701,5716,5717,5706,43,5734,5736,5739,5737,0,2831,2833,1003,1003,5713,5715,5718,5716,43,5737,5739,5740,5738,0,1003,1003,1004,1004,5716,5718,5719,5717,43,5741,5742,5743,5744,0,2834,2835,2836,2837,5720,5721,5722,5723,43,5744,5743,613,608,0,2837,2836,359,354,5723,5722,613,608,43,5742,5745,5746,5743,0,2835,2838,2839,2836,5721,5724,5725,5722,43,5743,5746,614,613,0,2836,2839,360,359,5722,5725,614,613,43,5741,5744,5747,5748,0,2834,2837,2840,2841,5720,5723,5726,5727,43,5748,5747,5638,5634,0,2841,2840,2744,2740,5727,5726,5617,5613,43,5744,608,607,5747,0,2837,354,353,2840,5723,608,607,5726,43,5747,607,610,5638,0,2840,353,356,2744,5726,607,610,5617,43,5741,5748,5749,5750,0,2834,2841,2842,2843,5720,5727,5728,5729,43,5750,5749,5712,5706,0,2843,2842,2809,2803,5729,5728,5691,5685,43,5748,5634,5633,5749,0,2841,2740,2739,2842,5727,5613,5612,5728,43,5749,5633,5636,5712,0,2842,2739,2742,2809,5728,5612,5615,5691,43,5741,5750,5751,5742,0,2834,2843,2844,2835,5720,5729,5730,5721,43,5742,5751,5752,5745,0,2835,2844,2845,2838,5721,5730,5731,5724,43,5750,5706,5705,5751,0,2843,2803,2802,2844,5729,5685,5684,5730,43,5751,5705,5709,5752,0,2844,2802,2806,2845,5730,5684,5688,5731,43,5753,5754,5755,5756,0,2846,2847,2848,2849,5732,5733,5734,5735,43,5756,5755,1462,1461,0,2849,2848,724,723,5735,5734,1462,1461,43,5754,5757,5758,5755,0,2847,2850,2851,2848,5733,5736,5737,5734,43,5755,5758,1463,1462,0,2848,2851,725,724,5734,5737,1463,1462,43,5753,5756,5759,5760,0,2846,2849,2852,2853,5732,5735,5738,5739,43,5760,5759,5746,5745,0,2853,2852,2839,2838,5739,5738,5725,5724,43,5756,1461,1474,5759,0,2849,723,736,2852,5735,1461,1474,5738,43,5759,1474,614,5746,0,2852,736,360,2839,5738,1474,614,5725,43,5753,5760,5761,5762,0,2846,2853,2854,2855,5732,5739,5740,5741,43,5762,5761,5732,5726,0,2855,2854,2829,2823,5741,5740,5711,5705,43,5760,5745,5752,5761,0,2853,2838,2845,2854,5739,5724,5731,5740,43,5761,5752,5709,5732,0,2854,2845,2806,2829,5740,5731,5688,5711,43,5753,5762,5763,5754,0,2846,2855,2856,2847,5732,5741,5742,5733,43,5754,5763,5764,5757,0,2847,2856,2857,2850,5733,5742,5743,5736,43,5762,5726,5725,5763,0,2855,2823,2822,2856,5741,5705,5704,5742,43,5763,5725,5729,5764,0,2856,2822,2826,2857,5742,5704,5708,5743,43,5765,5766,5767,5768,0,2858,2859,2860,2861,5744,5745,5746,5747,43,5768,5767,5769,5770,0,2861,2860,2862,2863,5747,5746,5748,5749,43,5766,5771,5772,5767,0,2859,2864,2865,2860,5745,5750,5751,5746,43,5767,5772,5773,5769,0,2860,2865,2866,2862,5746,5751,5752,5748,43,5765,5768,5774,5775,0,2858,2861,2867,2868,5744,5747,5753,5754,43,5775,5774,2223,2222,0,2868,2867,975,974,5754,5753,2223,2222,43,5768,5770,5776,5774,0,2861,2863,2869,2867,5747,5749,5755,5753,43,5774,5776,2224,2223,0,2867,2869,976,975,5753,5755,2224,2223,43,5765,5775,5777,5778,0,2858,2868,2870,2871,5744,5754,5756,5757,43,5778,5777,5779,5780,0,2871,2870,2872,2873,5757,5756,5758,5759,43,5775,2222,2238,5777,0,2868,974,990,2870,5754,2222,2238,5756,43,5777,2238,2240,5779,0,2870,990,992,2872,5756,2238,2240,5758,43,5765,5778,5781,5766,0,2858,2871,2874,2859,5744,5757,5760,5745,43,5766,5781,5782,5771,0,2859,2874,2875,2864,5745,5760,5761,5750,43,5778,5780,5783,5781,0,2871,2873,2876,2874,5757,5759,5762,5760,43,5781,5783,5784,5782,0,2874,2876,2877,2875,5760,5762,5763,5761,43,5785,5786,5787,5788,0,2878,2879,2880,2881,5764,5765,5766,5767,43,5788,5787,5783,5780,0,2881,2880,2876,2873,5767,5766,5762,5759,43,5786,5789,5790,5787,0,2879,2882,2883,2880,5765,5768,5769,5766,43,5787,5790,5784,5783,0,2880,2883,2877,2876,5766,5769,5763,5762,43,5785,5788,5791,5792,0,2878,2881,2884,2885,5764,5767,5770,5771,43,5792,5791,2246,2245,0,2885,2884,998,997,5771,5770,2246,2245,43,5788,5780,5779,5791,0,2881,2873,2872,2884,5767,5759,5758,5770,43,5791,5779,2240,2246,0,2884,2872,992,998,5770,5758,2240,2246,43,5785,5792,5793,5794,0,2878,2885,1003,1003,5764,5771,5772,5773,43,5794,5793,5795,5796,0,1003,1003,1004,1004,5773,5772,5774,5775,43,5792,2245,2258,5793,0,2885,997,1003,1003,5771,2245,2258,5772,43,5793,2258,2260,5795,0,1003,1003,1004,1004,5772,2258,2260,5774,43,5785,5794,5797,5786,0,2878,1003,1003,2879,5764,5773,5776,5765,43,5786,5797,5798,5789,0,2879,1003,1003,2882,5765,5776,5777,5768,43,5794,5796,5799,5797,0,1003,1004,1004,1003,5773,5775,5778,5776,43,5797,5799,5800,5798,0,1003,1004,1004,1003,5776,5778,5779,5777,43,5801,5802,5803,5804,0,2886,2887,2888,2889,5780,5781,5782,5783,43,5804,5803,5532,5528,0,2889,2888,2659,2655,5783,5782,5511,5507,43,5802,2793,2792,5803,0,2887,1396,1395,2888,5781,2793,2792,5782,43,5803,2792,2795,5532,0,2888,1395,1398,2659,5782,2792,2795,5511,43,5801,5804,5805,5806,0,2886,2889,2890,2891,5780,5783,5784,5785,43,5806,5805,5807,5808,0,2891,2890,2892,2893,5785,5784,5786,5787,43,5804,5528,5527,5805,0,2889,2655,2654,2890,5783,5507,5506,5784,43,5805,5527,5530,5807,0,2890,2654,2657,2892,5784,5506,5509,5786,43,5801,5806,5809,5810,0,2886,2891,2894,2895,5780,5785,5788,5789,43,5810,5809,5629,5624,0,2895,2894,2735,2730,5789,5788,5608,5603,43,5806,5808,5811,5809,0,2891,2893,2896,2894,5785,5787,5790,5788,43,5809,5811,5630,5629,0,2894,2896,2736,2735,5788,5790,5609,5608,43,5801,5810,5812,5802,0,2886,2895,2897,2887,5780,5789,5791,5781,43,5802,5812,2800,2793,0,2887,2897,1403,1396,5781,5791,2800,2793,43,5810,5624,5623,5812,0,2895,2730,2729,2897,5789,5603,5602,5791,43,5812,5623,2801,2800,0,2897,2729,1404,1403,5791,5602,2801,2800,43,5813,5814,5815,5816,0,2898,2899,2900,2901,5792,5793,5794,5795,43,5816,5815,5576,5571,0,2901,2900,2693,2690,5795,5794,5555,5550,43,5814,5817,5818,5815,0,2899,2902,2903,2900,5793,5796,5797,5794,43,5815,5818,5577,5576,0,2900,2903,2694,2693,5794,5797,5556,5555,43,5813,5816,5819,5820,0,2898,2901,1003,1003,5792,5795,5798,5799,43,5820,5819,5821,5822,0,1003,1003,1004,1004,5799,5798,5800,5801,43,5816,5571,5570,5819,0,2901,2690,1003,1003,5795,5550,5549,5798,43,5819,5570,5573,5821,0,1003,1003,1004,1004,5798,5549,5552,5800,43,5813,5820,5823,5824,0,2898,1003,1003,2904,5792,5799,5802,5803,43,5824,5823,5739,5736,0,2904,1003,1003,2833,5803,5802,5718,5715,43,5820,5822,5825,5823,0,1003,1004,1004,1003,5799,5801,5804,5802,43,5823,5825,5740,5739,0,1003,1004,1004,1003,5802,5804,5719,5718,43,5813,5824,5826,5814,0,2898,2904,2905,2899,5792,5803,5805,5793,43,5814,5826,5827,5817,0,2899,2905,2906,2902,5793,5805,5806,5796,43,5824,5736,5735,5826,0,2904,2833,2832,2905,5803,5715,5714,5805,43,5826,5735,5720,5827,0,2905,2832,2817,2906,5805,5714,5699,5806,43,5828,5829,5830,5831,0,2907,2908,2909,2910,5807,5808,5809,5810,43,5831,5830,5593,5589,0,2910,2909,2710,2706,5810,5809,5572,5568,43,5829,2838,2837,5830,0,2908,1441,1440,2909,5808,2838,2837,5809,43,5830,2837,2840,5593,0,2909,1440,1443,2710,5809,2837,2840,5572,43,5828,5831,5832,5833,0,2907,2910,2911,2912,5807,5810,5811,5812,43,5833,5832,5834,5835,0,2912,2911,2913,2914,5812,5811,5813,5814,43,5831,5589,5588,5832,0,2910,2706,2705,2911,5810,5568,5567,5811,43,5832,5588,5591,5834,0,2911,2705,2708,2913,5811,5567,5570,5813,43,5828,5833,5836,5837,0,2907,2912,2915,2916,5807,5812,5815,5816,43,5837,5836,5523,5518,0,2916,2915,2650,2645,5816,5815,5502,5497,43,5833,5835,5838,5836,0,2912,2914,2917,2915,5812,5814,5817,5815,43,5836,5838,5524,5523,0,2915,2917,2651,2650,5815,5817,5503,5502,43,5828,5837,5839,5829,0,2907,2916,2918,2908,5807,5816,5818,5808,43,5829,5839,2845,2838,0,2908,2918,1448,1441,5808,5818,2845,2838,43,5837,5518,5517,5839,0,2916,2645,2644,2918,5816,5497,5496,5818,43,5839,5517,2846,2845,0,2918,2644,1449,1448,5818,5496,2846,2845,43,5840,5841,5842,5843,0,2919,2920,2921,2922,5819,5820,5821,5822,43,5843,5842,5617,5612,0,2922,2921,2723,2720,5822,5821,5596,5591,43,5841,5844,5845,5842,0,2920,2923,2924,2921,5820,5823,5824,5821,43,5842,5845,5618,5617,0,2921,2924,2724,2723,5821,5824,5597,5596,43,5840,5843,5846,5847,0,2919,2922,1003,1003,5819,5822,5825,5826,43,5847,5846,5848,5849,0,1003,1003,1004,1004,5826,5825,5827,5828,43,5843,5612,5611,5846,0,2922,2720,1003,1003,5822,5591,5590,5825,43,5846,5611,5614,5848,0,1003,1003,1004,1004,5825,5590,5593,5827,43,5840,5847,5850,5851,0,2919,1003,1003,2925,5819,5826,5829,5830,43,5851,5850,5566,5558,0,2925,1003,1003,2685,5830,5829,5545,5537,43,5847,5849,5852,5850,0,1003,1004,1004,1003,5826,5828,5831,5829,43,5850,5852,5567,5566,0,1003,1004,1004,1003,5829,5831,5546,5545,43,5840,5851,5853,5841,0,2919,2925,2926,2920,5819,5830,5832,5820,43,5841,5853,5854,5844,0,2920,2926,2927,2923,5820,5832,5833,5823,43,5851,5558,5557,5853,0,2925,2685,2684,2926,5830,5537,5536,5832,43,5853,5557,5561,5854,0,2926,2684,2688,2927,5832,5536,5540,5833,43,5855,5856,5857,5858,0,2928,2929,2930,2931,5834,5835,5836,5837,43,5858,5857,5682,5679,0,2931,2930,2779,2776,5837,5836,5661,5658,43,5856,5835,5834,5857,0,2929,2914,2913,2930,5835,5814,5813,5836,43,5857,5834,5591,5682,0,2930,2913,2708,2779,5836,5813,5570,5661,43,5855,5858,5859,5860,0,2928,2931,2932,2933,5834,5837,5838,5839,43,5860,5859,5845,5844,0,2933,2932,2924,2923,5839,5838,5824,5823,43,5858,5679,5678,5859,0,2931,2776,2775,2932,5837,5658,5657,5838,43,5859,5678,5618,5845,0,2932,2775,2724,2924,5838,5657,5597,5824,43,5855,5860,5861,5862,0,2928,2933,2934,2935,5834,5839,5840,5841,43,5862,5861,5700,5698,0,2935,2934,2797,2795,5841,5840,5679,5677,43,5860,5844,5854,5861,0,2933,2923,2927,2934,5839,5823,5833,5840,43,5861,5854,5561,5700,0,2934,2927,2688,2797,5840,5833,5540,5679,43,5855,5862,5863,5856,0,2928,2935,2936,2929,5834,5841,5842,5835,43,5856,5863,5838,5835,0,2929,2936,2917,2914,5835,5842,5817,5814,43,5862,5698,5697,5863,0,2935,2795,2794,2936,5841,5677,5676,5842,43,5863,5697,5524,5838,0,2936,2794,2651,2917,5842,5676,5503,5817,43,5864,5865,5866,5867,0,2937,2938,2939,2940,5843,5844,5845,5846,43,5867,5866,5694,5691,0,2940,2939,2791,2788,5846,5845,5673,5670,43,5865,5808,5807,5866,0,2938,2893,2892,2939,5844,5787,5786,5845,43,5866,5807,5530,5694,0,2939,2892,2657,2791,5845,5786,5509,5673,43,5864,5867,5868,5869,0,2937,2940,2941,2942,5843,5846,5847,5848,43,5869,5868,5818,5817,0,2942,2941,2903,2902,5848,5847,5797,5796,43,5867,5691,5690,5868,0,2940,2788,2787,2941,5846,5670,5669,5847,43,5868,5690,5577,5818,0,2941,2787,2694,2903,5847,5669,5556,5797,43,5864,5869,5870,5871,0,2937,2942,2943,2944,5843,5848,5849,5850,43,5871,5870,5719,5716,0,2944,2943,2816,2813,5850,5849,5698,5695,43,5869,5817,5827,5870,0,2942,2902,2906,2943,5848,5796,5806,5849,43,5870,5827,5720,5719,0,2943,2906,2817,2816,5849,5806,5699,5698,43,5864,5871,5872,5865,0,2937,2944,2945,2938,5843,5850,5851,5844,43,5865,5872,5811,5808,0,2938,2945,2896,2893,5844,5851,5790,5787,43,5871,5716,5715,5872,0,2944,2813,2812,2945,5850,5695,5694,5851,43,5872,5715,5630,5811,0,2945,2812,2736,2896,5851,5694,5609,5790,43,5873,5874,5875,5876,0,2946,2947,2948,2949,5852,5853,5854,5855,43,5876,5875,5543,5538,0,2949,2948,2670,2665,5855,5854,5522,5517,43,5874,5877,5878,5875,0,2947,2950,2951,2948,5853,5856,5857,5854,43,5875,5878,5544,5543,0,2948,2951,2671,2670,5854,5857,5523,5522,43,5873,5876,5879,5880,0,2946,2949,2952,2953,5852,5855,5858,5859,43,5880,5879,3593,3592,0,2953,2952,2106,2105,5859,5858,3593,3592,43,5876,5538,5537,5879,0,2949,2665,2664,2952,5855,5517,5516,5858,43,5879,5537,2817,3593,0,2952,2664,1420,2106,5858,5516,2817,3593,43,5873,5880,5881,5882,0,2946,2953,2954,2955,5852,5859,5860,5861,43,5882,5881,5776,5770,0,2955,2954,2869,2863,5861,5860,5755,5749,43,5880,3592,3599,5881,0,2953,2105,2112,2954,5859,3592,3599,5860,43,5881,3599,2224,5776,0,2954,2112,976,2869,5860,3599,2224,5755,43,5873,5882,5883,5874,0,2946,2955,2956,2947,5852,5861,5862,5853,43,5874,5883,5884,5877,0,2947,2956,2957,2950,5853,5862,5863,5856,43,5882,5770,5769,5883,0,2955,2863,2862,2956,5861,5749,5748,5862,43,5883,5769,5773,5884,0,2956,2862,2866,2957,5862,5748,5752,5863,43,5885,5886,5887,5888,0,2958,2959,2960,2961,5864,5865,5866,5867,43,5888,5887,5667,5664,0,2961,2960,2764,2761,5867,5866,5646,5643,43,5886,5889,5890,5887,0,2959,2962,2963,2960,5865,5868,5869,5866,43,5887,5890,5647,5667,0,2960,2963,2752,2764,5866,5869,5626,5646,43,5885,5888,5891,5892,0,2958,2961,2964,2965,5864,5867,5870,5871,43,5892,5891,5878,5877,0,2965,2964,2951,2950,5871,5870,5857,5856,43,5888,5664,5663,5891,0,2961,2761,2760,2964,5867,5643,5642,5870,43,5891,5663,5544,5878,0,2964,2760,2671,2951,5870,5642,5523,5857,43,5885,5892,5893,5894,0,2958,2965,2966,2967,5864,5871,5872,5873,43,5894,5893,5772,5771,0,2967,2966,2865,2864,5873,5872,5751,5750,43,5892,5877,5884,5893,0,2965,2950,2957,2966,5871,5856,5863,5872,43,5893,5884,5773,5772,0,2966,2957,2866,2865,5872,5863,5752,5751,43,5885,5894,5895,5886,0,2958,2967,2968,2959,5864,5873,5874,5865,43,5886,5895,5896,5889,0,2959,2968,2969,2962,5865,5874,5875,5868,43,5894,5771,5782,5895,0,2967,2864,2875,2968,5873,5750,5761,5874,43,5895,5782,5784,5896,0,2968,2875,2877,2969,5874,5761,5763,5875,43,5897,5898,5899,5900,0,2970,1003,1003,2971,5876,5877,5878,5879,43,5900,5899,5652,5644,0,2971,1003,1003,2749,5879,5878,5631,5623,43,5898,5901,5902,5899,0,1003,1004,1004,1003,5877,5880,5881,5878,43,5899,5902,5653,5652,0,1003,1004,1004,1003,5878,5881,5632,5631,43,5897,5900,5903,5904,0,2970,2971,2972,2973,5876,5879,5882,5883,43,5904,5903,5890,5889,0,2973,2972,2963,2962,5883,5882,5869,5868,43,5900,5644,5643,5903,0,2971,2749,2748,2972,5879,5623,5622,5882,43,5903,5643,5647,5890,0,2972,2748,2752,2963,5882,5622,5626,5869,43,5897,5904,5905,5906,0,2970,2973,2974,2975,5876,5883,5884,5885,43,5906,5905,5790,5789,0,2975,2974,2883,2882,5885,5884,5769,5768,43,5904,5889,5896,5905,0,2973,2962,2969,2974,5883,5868,5875,5884,43,5905,5896,5784,5790,0,2974,2969,2877,2883,5884,5875,5763,5769,43,5897,5906,5907,5898,0,2970,2975,1003,1003,5876,5885,5886,5877,43,5898,5907,5908,5901,0,1003,1003,1004,1004,5877,5886,5887,5880,43,5906,5789,5798,5907,0,2975,2882,1003,1003,5885,5768,5777,5886,43,5907,5798,5800,5908,0,1003,1003,1004,1004,5886,5777,5779,5887,43,5909,5910,5911,5912,0,386,386,62,62,5888,5889,5890,5891,43,5912,5911,5913,5914,0,62,62,16,16,5891,5890,5892,5893,43,5910,809,808,5911,0,386,386,62,62,5889,809,808,5890,43,5911,808,811,5913,0,62,62,16,16,5890,808,811,5892,43,5909,5912,5915,5916,0,386,62,62,386,5888,5891,5894,5895,43,5916,5915,650,642,0,386,62,62,386,5895,5894,650,642,43,5912,5914,5917,5915,0,62,16,16,62,5891,5893,5896,5894,43,5915,5917,651,650,0,62,16,16,62,5894,5896,651,650,43,5909,5916,5918,5919,0,386,386,385,385,5888,5895,5897,5898,43,5919,5918,5920,5921,0,385,385,388,388,5898,5897,5899,5900,43,5916,642,641,5918,0,386,386,385,385,5895,642,641,5897,43,5918,641,645,5920,0,385,385,388,388,5897,641,645,5899,43,5909,5919,5922,5910,0,386,385,385,386,5888,5898,5901,5889,43,5910,5922,816,809,0,386,385,385,386,5889,5901,816,809,43,5919,5921,5923,5922,0,385,388,388,385,5898,5900,5902,5901,43,5922,5923,817,816,0,385,388,388,385,5901,5902,817,816,43,5924,5925,5926,5927,0,403,403,405,405,5903,5904,5905,5906,43,5927,5926,5923,5921,0,405,405,388,388,5906,5905,5902,5900,43,5925,837,836,5926,0,403,403,405,405,5904,837,836,5905,43,5926,836,817,5923,0,405,405,388,388,5905,836,817,5902,43,5924,5927,5928,5929,0,403,405,405,403,5903,5906,5907,5908,43,5929,5928,673,667,0,403,405,405,403,5908,5907,673,667,43,5927,5921,5920,5928,0,405,388,388,405,5906,5900,5899,5907,43,5928,5920,645,673,0,405,388,388,405,5907,5899,645,673,43,5924,5929,5930,5931,0,403,403,66,66,5903,5908,5909,5910,43,5931,5930,5932,5933,0,66,66,18,18,5910,5909,5911,5912,43,5929,667,666,5930,0,403,403,66,66,5908,667,666,5909,43,5930,666,670,5932,0,66,66,18,18,5909,666,670,5911,43,5924,5931,5934,5925,0,403,66,66,403,5903,5910,5913,5904,43,5925,5934,840,837,0,403,66,66,403,5904,5913,840,837,43,5931,5933,5935,5934,0,66,18,18,66,5910,5912,5914,5913,43,5934,5935,841,840,0,66,18,18,66,5913,5914,841,840,43,5936,5937,5938,5939,0,418,418,421,421,5915,5916,5917,5918,43,5939,5938,5935,5933,0,421,421,18,18,5918,5917,5914,5912,43,5937,857,856,5938,0,418,418,421,421,5916,857,856,5917,43,5938,856,841,5935,0,421,421,18,18,5917,856,841,5914,43,5936,5939,5940,5941,0,418,421,421,418,5915,5918,5919,5920,43,5941,5940,693,687,0,418,421,421,418,5920,5919,693,687,43,5939,5933,5932,5940,0,421,18,18,421,5918,5912,5911,5919,43,5940,5932,670,693,0,421,18,18,421,5919,5911,670,693,43,5936,5941,5942,5943,0,418,418,417,417,5915,5920,5921,5922,43,5943,5942,5944,5945,0,417,417,420,420,5922,5921,5923,5924,43,5941,687,686,5942,0,418,418,417,417,5920,687,686,5921,43,5942,686,690,5944,0,417,417,420,420,5921,686,690,5923,43,5936,5943,5946,5937,0,418,417,417,418,5915,5922,5925,5916,43,5937,5946,860,857,0,418,417,417,418,5916,5925,860,857,43,5943,5945,5947,5946,0,417,420,420,417,5922,5924,5926,5925,43,5946,5947,861,860,0,417,420,420,417,5925,5926,861,860,43,5948,5949,5950,5951,0,433,433,435,435,5927,5928,5929,5930,43,5951,5950,5947,5945,0,435,435,420,420,5930,5929,5926,5924,43,5949,871,870,5950,0,433,433,435,435,5928,871,870,5929,43,5950,870,861,5947,0,435,435,420,420,5929,870,861,5926,43,5948,5951,5952,5953,0,433,435,435,433,5927,5930,5931,5932,43,5953,5952,713,707,0,433,435,435,433,5932,5931,713,707,43,5951,5945,5944,5952,0,435,420,420,435,5930,5924,5923,5931,43,5952,5944,690,713,0,435,420,420,435,5931,5923,690,713,43,5948,5953,5954,5955,0,433,433,9,9,5927,5932,5933,5934,43,5955,5954,5956,5957,0,9,9,10,10,5934,5933,5935,5936,43,5953,707,706,5954,0,433,433,9,9,5932,707,706,5933,43,5954,706,710,5956,0,9,9,10,10,5933,706,710,5935,43,5948,5955,5958,5949,0,433,9,9,433,5927,5934,5937,5928,43,5949,5958,876,871,0,433,9,9,433,5928,5937,876,871,43,5955,5957,5959,5958,0,9,10,10,9,5934,5936,5938,5937,43,5958,5959,877,876,0,9,10,10,9,5937,5938,877,876,43,5960,5961,5962,5963,0,448,448,35,35,5939,5940,5941,5942,43,5963,5962,5959,5957,0,35,35,10,10,5942,5941,5938,5936,43,5961,891,890,5962,0,448,448,35,35,5940,891,890,5941,43,5962,890,877,5959,0,35,35,10,10,5941,890,877,5938,43,5960,5963,5964,5965,0,448,35,35,448,5939,5942,5943,5944,43,5965,5964,733,727,0,448,35,35,448,5944,5943,733,727,43,5963,5957,5956,5964,0,35,10,10,35,5942,5936,5935,5943,43,5964,5956,710,733,0,35,10,10,35,5943,5935,710,733,43,5960,5965,5966,5967,0,448,448,447,447,5939,5944,5945,5946,43,5967,5966,5968,5969,0,447,447,450,450,5946,5945,5947,5948,43,5965,727,726,5966,0,448,448,447,447,5944,727,726,5945,43,5966,726,730,5968,0,447,447,450,450,5945,726,730,5947,43,5960,5967,5970,5961,0,448,447,447,448,5939,5946,5949,5940,43,5961,5970,896,891,0,448,447,447,448,5940,5949,896,891,43,5967,5969,5971,5970,0,447,450,450,447,5946,5948,5950,5949,43,5970,5971,897,896,0,447,450,450,447,5949,5950,897,896,43,5972,5973,5974,5975,0,462,462,464,464,5951,5952,5953,5954,43,5975,5974,5971,5969,0,464,464,450,450,5954,5953,5950,5948,43,5973,911,910,5974,0,462,462,464,464,5952,911,910,5953,43,5974,910,897,5971,0,464,464,450,450,5953,910,897,5950,43,5972,5975,5976,5977,0,462,464,464,462,5951,5954,5955,5956,43,5977,5976,753,747,0,462,464,464,462,5956,5955,753,747,43,5975,5969,5968,5976,0,464,450,450,464,5954,5948,5947,5955,43,5976,5968,730,753,0,464,450,450,464,5955,5947,730,753,43,5972,5977,5978,5979,0,462,462,22,22,5951,5956,5957,5958,43,5979,5978,5980,5981,0,22,22,23,23,5958,5957,5959,5960,43,5977,747,746,5978,0,462,462,22,22,5956,747,746,5957,43,5978,746,750,5980,0,22,22,23,23,5957,746,750,5959,43,5972,5979,5982,5973,0,462,22,22,462,5951,5958,5961,5952,43,5973,5982,916,911,0,462,22,22,462,5952,5961,916,911,43,5979,5981,5983,5982,0,22,23,23,22,5958,5960,5962,5961,43,5982,5983,917,916,0,22,23,23,22,5961,5962,917,916,43,5984,5985,5986,5987,0,477,477,57,57,5963,5964,5965,5966,43,5987,5986,5983,5981,0,57,57,23,23,5966,5965,5962,5960,43,5985,937,936,5986,0,477,477,57,57,5964,937,936,5965,43,5986,936,917,5983,0,57,57,23,23,5965,936,917,5962,43,5984,5987,5988,5989,0,477,57,57,477,5963,5966,5967,5968,43,5989,5988,773,767,0,477,57,57,477,5968,5967,773,767,43,5987,5981,5980,5988,0,57,23,23,57,5966,5960,5959,5967,43,5988,5980,750,773,0,57,23,23,57,5967,5959,750,773,43,5984,5989,5990,5991,0,477,477,476,476,5963,5968,5969,5970,43,5991,5990,5992,5993,0,476,476,479,479,5970,5969,5971,5972,43,5989,767,766,5990,0,477,477,476,476,5968,767,766,5969,43,5990,766,770,5992,0,476,476,479,479,5969,766,770,5971,43,5984,5991,5994,5985,0,477,476,476,477,5963,5970,5973,5964,43,5985,5994,940,937,0,477,476,476,477,5964,5973,940,937,43,5991,5993,5995,5994,0,476,479,479,476,5970,5972,5974,5973,43,5994,5995,941,940,0,476,479,479,476,5973,5974,941,940,43,5996,5997,5998,5999,0,491,491,492,492,5975,5976,5977,5978,43,5999,5998,5995,5993,0,492,492,479,479,5978,5977,5974,5972,43,5997,954,953,5998,0,491,491,492,492,5976,954,953,5977,43,5998,953,941,5995,0,492,492,479,479,5977,953,941,5974,43,5996,5999,6000,6001,0,491,492,492,491,5975,5978,5979,5980,43,6001,6000,790,787,0,491,492,492,491,5980,5979,790,787,43,5999,5993,5992,6000,0,492,479,479,492,5978,5972,5971,5979,43,6000,5992,770,790,0,492,479,479,492,5979,5971,770,790,43,5996,6001,6002,6003,0,491,491,15,15,5975,5980,5981,5982,43,6003,6002,5917,5914,0,15,15,16,16,5982,5981,5896,5893,43,6001,787,786,6002,0,491,491,15,15,5980,787,786,5981,43,6002,786,651,5917,0,15,15,16,16,5981,786,651,5896,43,5996,6003,6004,5997,0,491,15,15,491,5975,5982,5983,5976,43,5997,6004,956,954,0,491,15,15,491,5976,5983,956,954,43,6003,5914,5913,6004,0,15,16,16,15,5982,5893,5892,5983,43,6004,5913,811,956,0,15,16,16,15,5983,5892,811,956,43,6005,6006,6007,6008,0,386,386,385,385,5984,5985,5986,5987,43,6008,6007,6009,6010,0,385,385,388,388,5987,5986,5988,5989,43,6006,1517,1528,6007,0,386,386,385,385,5985,1517,1528,5986,43,6007,1528,1530,6009,0,385,385,388,388,5986,1528,1530,5988,43,6005,6008,6011,6012,0,386,385,385,386,5984,5987,5990,5991,43,6012,6011,976,969,0,386,385,385,386,5991,5990,976,969,43,6008,6010,6013,6011,0,385,388,388,385,5987,5989,5992,5990,43,6011,6013,977,976,0,385,388,388,385,5990,5992,977,976,43,6005,6012,6014,6015,0,386,386,62,62,5984,5991,5993,5994,43,6015,6014,6016,6017,0,62,62,16,16,5994,5993,5995,5996,43,6012,969,968,6014,0,386,386,62,62,5991,969,968,5993,43,6014,968,971,6016,0,62,62,16,16,5993,968,971,5995,43,6005,6015,6018,6006,0,386,62,62,386,5984,5994,5997,5985,43,6006,6018,1518,1517,0,386,62,62,386,5985,5997,1518,1517,43,6015,6017,6019,6018,0,62,16,16,62,5994,5996,5998,5997,43,6018,6019,1519,1518,0,62,16,16,62,5997,5998,1519,1518,43,6020,6021,6022,6023,0,403,403,66,66,5999,6000,6001,6002,43,6023,6022,6024,6025,0,66,66,18,18,6002,6001,6003,6004,43,6021,1542,1541,6022,0,403,403,66,66,6000,1542,1541,6001,43,6022,1541,1544,6024,0,66,66,18,18,6001,1541,1544,6003,43,6020,6023,6026,6027,0,403,66,66,403,5999,6002,6005,6006,43,6027,6026,1000,997,0,403,66,66,403,6006,6005,1000,997,43,6023,6025,6028,6026,0,66,18,18,66,6002,6004,6007,6005,43,6026,6028,1001,1000,0,66,18,18,66,6005,6007,1001,1000,43,6020,6027,6029,6030,0,403,403,405,405,5999,6006,6008,6009,43,6030,6029,6013,6010,0,405,405,388,388,6009,6008,5992,5989,43,6027,997,996,6029,0,403,403,405,405,6006,997,996,6008,43,6029,996,977,6013,0,405,405,388,388,6008,996,977,5992,43,6020,6030,6031,6021,0,403,405,405,403,5999,6009,6010,6000,43,6021,6031,1546,1542,0,403,405,405,403,6000,6010,1546,1542,43,6030,6010,6009,6031,0,405,388,388,405,6009,5989,5988,6010,43,6031,6009,1530,1546,0,405,388,388,405,6010,5988,1530,1546,43,6032,6033,6034,6035,0,418,418,417,417,6011,6012,6013,6014,43,6035,6034,6036,6037,0,417,417,420,420,6014,6013,6015,6016,43,6033,1558,1557,6034,0,418,418,417,417,6012,1558,1557,6013,43,6034,1557,1560,6036,0,417,417,420,420,6013,1557,1560,6015,43,6032,6035,6038,6039,0,418,417,417,418,6011,6014,6017,6018,43,6039,6038,1020,1017,0,418,417,417,418,6018,6017,1020,1017,43,6035,6037,6040,6038,0,417,420,420,417,6014,6016,6019,6017,43,6038,6040,1021,1020,0,417,420,420,417,6017,6019,1021,1020,43,6032,6039,6041,6042,0,418,418,421,421,6011,6018,6020,6021,43,6042,6041,6028,6025,0,421,421,18,18,6021,6020,6007,6004,43,6039,1017,1016,6041,0,418,418,421,421,6018,1017,1016,6020,43,6041,1016,1001,6028,0,421,421,18,18,6020,1016,1001,6007,43,6032,6042,6043,6033,0,418,421,421,418,6011,6021,6022,6012,43,6033,6043,1562,1558,0,418,421,421,418,6012,6022,1562,1558,43,6042,6025,6024,6043,0,421,18,18,421,6021,6004,6003,6022,43,6043,6024,1544,1562,0,421,18,18,421,6022,6003,1544,1562,43,6044,6045,6046,6047,0,433,433,9,9,6023,6024,6025,6026,43,6047,6046,6048,6049,0,9,9,10,10,6026,6025,6027,6028,43,6045,1567,1576,6046,0,433,433,9,9,6024,1567,1576,6025,43,6046,1576,1578,6048,0,9,9,10,10,6025,1576,1578,6027,43,6044,6047,6050,6051,0,433,9,9,433,6023,6026,6029,6030,43,6051,6050,1036,1031,0,433,9,9,433,6030,6029,1036,1031,43,6047,6049,6052,6050,0,9,10,10,9,6026,6028,6031,6029,43,6050,6052,1037,1036,0,9,10,10,9,6029,6031,1037,1036,43,6044,6051,6053,6054,0,433,433,435,435,6023,6030,6032,6033,43,6054,6053,6040,6037,0,435,435,420,420,6033,6032,6019,6016,43,6051,1031,1030,6053,0,433,433,435,435,6030,1031,1030,6032,43,6053,1030,1021,6040,0,435,435,420,420,6032,1030,1021,6019,43,6044,6054,6055,6045,0,433,435,435,433,6023,6033,6034,6024,43,6045,6055,1568,1567,0,433,435,435,433,6024,6034,1568,1567,43,6054,6037,6036,6055,0,435,420,420,435,6033,6016,6015,6034,43,6055,6036,1560,1568,0,435,420,420,435,6034,6015,1560,1568,43,6056,6057,6058,6059,0,448,448,447,447,6035,6036,6037,6038,43,6059,6058,6060,6061,0,447,447,450,450,6038,6037,6039,6040,43,6057,1583,1592,6058,0,448,448,447,447,6036,1583,1592,6037,43,6058,1592,1594,6060,0,447,447,450,450,6037,1592,1594,6039,43,6056,6059,6062,6063,0,448,447,447,448,6035,6038,6041,6042,43,6063,6062,1056,1051,0,448,447,447,448,6042,6041,1056,1051,43,6059,6061,6064,6062,0,447,450,450,447,6038,6040,6043,6041,43,6062,6064,1057,1056,0,447,450,450,447,6041,6043,1057,1056,43,6056,6063,6065,6066,0,448,448,35,35,6035,6042,6044,6045,43,6066,6065,6052,6049,0,35,35,10,10,6045,6044,6031,6028,43,6063,1051,1050,6065,0,448,448,35,35,6042,1051,1050,6044,43,6065,1050,1037,6052,0,35,35,10,10,6044,1050,1037,6031,43,6056,6066,6067,6057,0,448,35,35,448,6035,6045,6046,6036,43,6057,6067,1584,1583,0,448,35,35,448,6036,6046,1584,1583,43,6066,6049,6048,6067,0,35,10,10,35,6045,6028,6027,6046,43,6067,6048,1578,1584,0,35,10,10,35,6046,6027,1578,1584,43,6068,6069,6070,6071,0,462,462,22,22,6047,6048,6049,6050,43,6071,6070,6072,6073,0,22,22,23,23,6050,6049,6051,6052,43,6069,1599,1608,6070,0,462,462,22,22,6048,1599,1608,6049,43,6070,1608,1610,6072,0,22,22,23,23,6049,1608,1610,6051,43,6068,6071,6074,6075,0,462,22,22,462,6047,6050,6053,6054,43,6075,6074,1076,1071,0,462,22,22,462,6054,6053,1076,1071,43,6071,6073,6076,6074,0,22,23,23,22,6050,6052,6055,6053,43,6074,6076,1077,1076,0,22,23,23,22,6053,6055,1077,1076,43,6068,6075,6077,6078,0,462,462,464,464,6047,6054,6056,6057,43,6078,6077,6064,6061,0,464,464,450,450,6057,6056,6043,6040,43,6075,1071,1070,6077,0,462,462,464,464,6054,1071,1070,6056,43,6077,1070,1057,6064,0,464,464,450,450,6056,1070,1057,6043,43,6068,6078,6079,6069,0,462,464,464,462,6047,6057,6058,6048,43,6069,6079,1600,1599,0,462,464,464,462,6048,6058,1600,1599,43,6078,6061,6060,6079,0,464,450,450,464,6057,6040,6039,6058,43,6079,6060,1594,1600,0,464,450,450,464,6058,6039,1594,1600,43,6080,6081,6082,6083,0,477,477,476,476,6059,6060,6061,6062,43,6083,6082,6084,6085,0,476,476,479,479,6062,6061,6063,6064,43,6081,1622,1621,6082,0,477,477,476,476,6060,1622,1621,6061,43,6082,1621,1624,6084,0,476,476,479,479,6061,1621,1624,6063,43,6080,6083,6086,6087,0,477,476,476,477,6059,6062,6065,6066,43,6087,6086,1096,1091,0,477,476,476,477,6066,6065,1096,1091,43,6083,6085,6088,6086,0,476,479,479,476,6062,6064,6067,6065,43,6086,6088,1097,1096,0,476,479,479,476,6065,6067,1097,1096,43,6080,6087,6089,6090,0,477,477,57,57,6059,6066,6068,6069,43,6090,6089,6076,6073,0,57,57,23,23,6069,6068,6055,6052,43,6087,1091,1090,6089,0,477,477,57,57,6066,1091,1090,6068,43,6089,1090,1077,6076,0,57,57,23,23,6068,1090,1077,6055,43,6080,6090,6091,6081,0,477,57,57,477,6059,6069,6070,6060,43,6081,6091,1626,1622,0,477,57,57,477,6060,6070,1626,1622,43,6090,6073,6072,6091,0,57,23,23,57,6069,6052,6051,6070,43,6091,6072,1610,1626,0,57,23,23,57,6070,6051,1610,1626,43,6092,6093,6094,6095,0,491,491,15,15,6071,6072,6073,6074,43,6095,6094,6019,6017,0,15,15,16,16,6074,6073,5998,5996,43,6093,1636,1635,6094,0,491,491,15,15,6072,1636,1635,6073,43,6094,1635,1519,6019,0,15,15,16,16,6073,1635,1519,5998,43,6092,6095,6096,6097,0,491,15,15,491,6071,6074,6075,6076,43,6097,6096,1116,1114,0,491,15,15,491,6076,6075,1116,1114,43,6095,6017,6016,6096,0,15,16,16,15,6074,5996,5995,6075,43,6096,6016,971,1116,0,15,16,16,15,6075,5995,971,1116,43,6092,6097,6098,6099,0,491,491,492,492,6071,6076,6077,6078,43,6099,6098,6088,6085,0,492,492,479,479,6078,6077,6067,6064,43,6097,1114,1113,6098,0,491,491,492,492,6076,1114,1113,6077,43,6098,1113,1097,6088,0,492,492,479,479,6077,1113,1097,6067,43,6092,6099,6100,6093,0,491,492,492,491,6071,6078,6079,6072,43,6093,6100,1638,1636,0,491,492,492,491,6072,6079,1638,1636,43,6099,6085,6084,6100,0,492,479,479,492,6078,6064,6063,6079,43,6100,6084,1624,1638,0,492,479,479,492,6079,6063,1624,1638,43,6101,6102,6103,6104,0,940,941,944,942,6080,6081,6082,6083,43,6104,6103,2039,2034,0,942,944,937,935,6083,6082,2039,2034,43,6102,2011,2010,6103,0,941,926,920,944,6081,2011,2010,6082,43,6103,2010,1940,2039,0,944,920,12,937,6082,2010,1940,2039,43,6101,6104,6105,6106,0,940,942,939,938,6080,6083,6084,6085,43,6106,6105,2096,2089,0,938,939,939,938,6085,6084,2096,2089,43,6104,2034,2033,6105,0,942,935,328,939,6083,2034,2033,6084,43,6105,2033,2036,2096,0,939,328,328,939,6084,2033,2036,2096,43,6101,6106,6107,6108,0,940,938,924,924,6080,6085,6086,6087,43,6108,6107,6109,6110,0,924,924,925,925,6087,6086,6088,6089,43,6106,2089,2088,6107,0,938,938,924,924,6085,2089,2088,6086,43,6107,2088,2091,6109,0,924,924,925,925,6086,2088,2091,6088,43,6101,6108,6111,6102,0,940,924,924,941,6080,6087,6090,6081,43,6102,6111,2014,2011,0,941,924,924,926,6081,6090,2014,2011,43,6108,6110,6112,6111,0,924,925,925,924,6087,6089,6091,6090,43,6111,6112,2015,2014,0,924,925,925,924,6090,6091,2015,2014,43,6113,6114,6115,6116,0,2976,2977,2978,2979,6092,6093,6094,6095,43,6116,6115,6117,6118,0,2979,2978,2980,2981,6095,6094,6096,6097,43,6114,6119,6120,6115,0,2977,2982,2980,2978,6093,6098,6099,6094,43,6115,6120,6121,6117,0,2978,2980,2983,2980,6094,6099,6100,6096,43,6113,6116,6122,6123,0,2976,2979,2978,2984,6092,6095,6101,6102,43,6123,6122,6124,6125,0,2984,2978,2980,2985,6102,6101,6103,6104,43,6116,6118,6126,6122,0,2979,2981,2980,2978,6095,6097,6105,6101,43,6122,6126,6127,6124,0,2978,2980,2983,2980,6101,6105,6106,6103,43,6113,6123,6128,6129,0,2976,2984,2986,2987,6092,6102,6107,6108,43,6129,6128,4496,4495,0,2987,2986,2303,2302,6108,6107,4496,4495,43,6123,6125,6130,6128,0,2984,2985,2988,2986,6102,6104,6109,6107,43,6128,6130,4497,4496,0,2986,2988,2304,2303,6107,6109,4497,4496,43,6113,6129,6131,6114,0,2976,2987,2989,2977,6092,6108,6110,6093,43,6114,6131,6132,6119,0,2977,2989,2990,2982,6093,6110,6111,6098,43,6129,4495,4511,6131,0,2987,2302,2318,2989,6108,4495,4511,6110,43,6131,4511,4513,6132,0,2989,2318,2320,2990,6110,4511,4513,6111,43,6133,6134,6135,6136,0,2991,12,12,12,6112,6113,6114,6115,43,6136,6135,6137,6138,0,12,12,12,12,6115,6114,6116,6117,43,6134,6139,6140,6135,0,12,12,12,12,6113,6118,6119,6114,43,6135,6140,6141,6137,0,12,12,12,12,6114,6119,6120,6116,43,6133,6136,6142,6143,0,2991,12,12,2992,6112,6115,6121,6122,43,6143,6142,6144,6145,0,2992,12,12,2993,6122,6121,6123,6124,43,6136,6138,6146,6142,0,12,12,12,12,6115,6117,6125,6121,43,6142,6146,6147,6144,0,12,12,12,12,6121,6125,6126,6123,43,6133,6143,6148,6149,0,2991,2992,2994,2992,6112,6122,6127,6128,43,6149,6148,4537,4532,0,2992,2994,2334,2331,6128,6127,4537,4532,43,6143,6145,6150,6148,0,2992,2993,2995,2994,6122,6124,6129,6127,43,6148,6150,4538,4537,0,2994,2995,2335,2334,6127,6129,4538,4537,43,6133,6149,6151,6134,0,2991,2992,12,12,6112,6128,6130,6113,43,6134,6151,6152,6139,0,12,12,12,12,6113,6130,6131,6118,43,6149,4532,4531,6151,0,2992,2331,12,12,6128,4532,4531,6130,43,6151,4531,4534,6152,0,12,12,12,12,6130,4531,4534,6131,43,6153,6154,6155,6156,0,2996,2997,2998,2999,6132,6133,6134,6135,43,6156,6155,6157,6158,0,2999,2998,2998,2999,6135,6134,6136,6137,43,6154,6159,6160,6155,0,2997,3000,3001,2998,6133,6138,6139,6134,43,6155,6160,6161,6157,0,2998,3001,3001,2998,6134,6139,6140,6136,43,6153,6156,6162,6163,0,2996,2999,12,12,6132,6135,6141,6142,43,6163,6162,6140,6139,0,12,12,12,12,6142,6141,6119,6118,43,6156,6158,6164,6162,0,2999,2999,12,12,6135,6137,6143,6141,43,6162,6164,6141,6140,0,12,12,12,12,6141,6143,6120,6119,43,6153,6163,6165,6166,0,2996,12,12,3002,6132,6142,6144,6145,43,6166,6165,4544,4543,0,3002,12,12,2338,6145,6144,4544,4543,43,6163,6139,6152,6165,0,12,12,12,12,6142,6118,6131,6144,43,6165,6152,4534,4544,0,12,12,12,12,6144,6131,4534,4544,43,6153,6166,6167,6154,0,2996,3002,3003,2997,6132,6145,6146,6133,43,6154,6167,6168,6159,0,2997,3003,3004,3000,6133,6146,6147,6138,43,6166,4543,4556,6167,0,3002,2338,2348,3003,6145,4543,4556,6146,43,6167,4556,4558,6168,0,3003,2348,2350,3004,6146,4556,4558,6147,43,6169,6170,6171,6172,0,3005,3006,3007,3008,6148,6149,6150,6151,43,6172,6171,6173,6174,0,3008,3007,3007,3008,6151,6150,6152,6153,43,6170,6175,6176,6171,0,3006,3009,3010,3007,6149,6154,6155,6150,43,6171,6176,6177,6173,0,3007,3010,3010,3007,6150,6155,6156,6152,43,6169,6172,6178,6179,0,3005,3008,3011,3012,6148,6151,6157,6158,43,6179,6178,6160,6159,0,3012,3011,3001,3000,6158,6157,6139,6138,43,6172,6174,6180,6178,0,3008,3008,3011,3011,6151,6153,6159,6157,43,6178,6180,6161,6160,0,3011,3011,3001,3001,6157,6159,6140,6139,43,6169,6179,6181,6182,0,3005,3012,3013,3014,6148,6158,6160,6161,43,6182,6181,5314,5309,0,3014,3013,2500,2495,6161,6160,5314,5309,43,6179,6159,6168,6181,0,3012,3000,3004,3013,6158,6138,6147,6160,43,6181,6168,4558,5314,0,3013,3004,2350,2500,6160,6147,4558,5314,43,6169,6182,6183,6170,0,3005,3014,3015,3006,6148,6161,6162,6149,43,6170,6183,6184,6175,0,3006,3015,3016,3009,6149,6162,6163,6154,43,6182,5309,5308,6183,0,3014,2495,2494,3015,6161,5309,5308,6162,43,6183,5308,5311,6184,0,3015,2494,2497,3016,6162,5308,5311,6163,43,6185,6186,6187,6188,0,3017,3018,3019,3020,6164,6165,6166,6167,43,6188,6187,6189,6190,0,3020,3019,3021,3022,6167,6166,6168,6169,43,6186,6125,6124,6187,0,3018,2985,2980,3019,6165,6104,6103,6166,43,6187,6124,6127,6189,0,3019,2980,2983,3021,6166,6103,6106,6168,43,6185,6188,6191,6192,0,3017,3020,3023,3024,6164,6167,6170,6171,43,6192,6191,6176,6175,0,3024,3023,3025,3026,6171,6170,6155,6154,43,6188,6190,6193,6191,0,3020,3022,3023,3023,6167,6169,6172,6170,43,6191,6193,6177,6176,0,3023,3023,3025,3025,6170,6172,6156,6155,43,6185,6192,6194,6195,0,3017,3024,3027,3028,6164,6171,6173,6174,43,6195,6194,5349,5346,0,3028,3027,2545,2540,6174,6173,5349,5346,43,6192,6175,6184,6194,0,3024,3026,3029,3027,6171,6154,6163,6173,43,6194,6184,5311,5349,0,3027,3029,2546,2545,6173,6163,5311,5349,43,6185,6195,6196,6186,0,3017,3028,3030,3018,6164,6174,6175,6165,43,6186,6196,6130,6125,0,3018,3030,2988,2985,6165,6175,6109,6104,43,6195,5346,5345,6196,0,3028,2540,2539,3030,6174,5346,5345,6175,43,6196,5345,4497,6130,0,3030,2539,2304,2988,6175,5345,4497,6109,43,6197,6198,6199,6200,0,2999,2998,2998,2999,6176,6177,6178,6179,43,6200,6199,6201,6202,0,2999,2998,2998,2999,6179,6178,6180,6181,43,6198,6203,6204,6199,0,2998,3001,3001,2998,6177,6182,6183,6178,43,6199,6204,6205,6201,0,2998,3001,3001,2998,6178,6183,6184,6180,43,6197,6200,6206,6207,0,2999,2999,12,12,6176,6179,6185,6186,43,6207,6206,6146,6138,0,12,12,12,12,6186,6185,6125,6117,43,6200,6202,6208,6206,0,2999,2999,12,12,6179,6181,6187,6185,43,6206,6208,6147,6146,0,12,12,12,12,6185,6187,6126,6125,43,6197,6207,6209,6210,0,2999,12,12,2999,6176,6186,6188,6189,43,6210,6209,6164,6158,0,2999,12,12,2999,6189,6188,6143,6137,43,6207,6138,6137,6209,0,12,12,12,12,6186,6117,6116,6188,43,6209,6137,6141,6164,0,12,12,12,12,6188,6116,6120,6143,43,6197,6210,6211,6198,0,2999,2999,2998,2998,6176,6189,6190,6177,43,6198,6211,6212,6203,0,2998,2998,3001,3001,6177,6190,6191,6182,43,6210,6158,6157,6211,0,2999,2999,2998,2998,6189,6137,6136,6190,43,6211,6157,6161,6212,0,2998,2998,3001,3001,6190,6136,6140,6191,43,6213,6214,6215,6216,0,3008,3007,3007,3008,6192,6193,6194,6195,43,6216,6215,6217,6218,0,3008,3007,3007,3008,6195,6194,6196,6197,43,6214,6219,6220,6215,0,3007,3010,3010,3007,6193,6198,6199,6194,43,6215,6220,6221,6217,0,3007,3010,3010,3007,6194,6199,6200,6196,43,6213,6216,6222,6223,0,3008,3008,3011,3011,6192,6195,6201,6202,43,6223,6222,6204,6203,0,3011,3011,3001,3001,6202,6201,6183,6182,43,6216,6218,6224,6222,0,3008,3008,3011,3011,6195,6197,6203,6201,43,6222,6224,6205,6204,0,3011,3011,3001,3001,6201,6203,6184,6183,43,6213,6223,6225,6226,0,3008,3011,3011,3008,6192,6202,6204,6205,43,6226,6225,6180,6174,0,3008,3011,3011,3008,6205,6204,6159,6153,43,6223,6203,6212,6225,0,3011,3001,3001,3011,6202,6182,6191,6204,43,6225,6212,6161,6180,0,3011,3001,3001,3011,6204,6191,6140,6159,43,6213,6226,6227,6214,0,3008,3008,3007,3007,6192,6205,6206,6193,43,6214,6227,6228,6219,0,3007,3007,3010,3010,6193,6206,6207,6198,43,6226,6174,6173,6227,0,3008,3008,3007,3007,6205,6153,6152,6206,43,6227,6173,6177,6228,0,3007,3007,3010,3010,6206,6152,6156,6207,43,6229,6230,6231,6232,0,3031,3032,3019,3020,6208,6209,6210,6211,43,6232,6231,6233,6234,0,3020,3019,3021,3022,6211,6210,6212,6213,43,6230,6118,6117,6231,0,3032,2981,2980,3019,6209,6097,6096,6210,43,6231,6117,6121,6233,0,3019,2980,2983,3021,6210,6096,6100,6212,43,6229,6232,6235,6236,0,3031,3020,3023,3023,6208,6211,6214,6215,43,6236,6235,6220,6219,0,3023,3023,3025,3025,6215,6214,6199,6198,43,6232,6234,6237,6235,0,3020,3022,3023,3023,6211,6213,6216,6214,43,6235,6237,6221,6220,0,3023,3023,3025,3025,6214,6216,6200,6199,43,6229,6236,6238,6239,0,3031,3023,3023,3020,6208,6215,6217,6218,43,6239,6238,6193,6190,0,3020,3023,3023,3022,6218,6217,6172,6169,43,6236,6219,6228,6238,0,3023,3025,3025,3023,6215,6198,6207,6217,43,6238,6228,6177,6193,0,3023,3025,3025,3023,6217,6207,6156,6172,43,6229,6239,6240,6230,0,3031,3020,3019,3032,6208,6218,6219,6209,43,6230,6240,6126,6118,0,3032,3019,2980,2981,6209,6219,6105,6097,43,6239,6190,6189,6240,0,3020,3022,3021,3019,6218,6169,6168,6219,43,6240,6189,6127,6126,0,3019,3021,2983,2980,6219,6168,6106,6105,43,6241,6242,6243,6244,0,3033,2998,2998,3034,6220,6221,6222,6223,43,6244,6243,5399,5394,0,3034,2998,2998,3035,6223,6222,5392,5389,43,6242,6245,6246,6243,0,2998,3001,3001,2998,6221,6224,6225,6222,43,6243,6246,5400,5399,0,2998,3001,3001,2998,6222,6225,5357,5392,43,6241,6244,6247,6248,0,3033,3034,2994,2992,6220,6223,6226,6227,43,6248,6247,6150,6145,0,2992,2994,2995,2993,6227,6226,6129,6124,43,6244,5394,5393,6247,0,3034,3035,3036,2994,6223,5389,5388,6226,43,6247,5393,4538,6150,0,2994,3036,2335,2995,6226,5388,4538,6129,43,6241,6248,6249,6250,0,3033,2992,12,2999,6220,6227,6228,6229,43,6250,6249,6208,6202,0,2999,12,12,2999,6229,6228,6187,6181,43,6248,6145,6144,6249,0,2992,2993,12,12,6227,6124,6123,6228,43,6249,6144,6147,6208,0,12,12,12,12,6228,6123,6126,6187,43,6241,6250,6251,6242,0,3033,2999,2998,2998,6220,6229,6230,6221,43,6242,6251,6252,6245,0,2998,2998,3001,3001,6221,6230,6231,6224,43,6250,6202,6201,6251,0,2999,2999,2998,2998,6229,6181,6180,6230,43,6251,6201,6205,6252,0,2998,2998,3001,3001,6230,6180,6184,6231,43,6253,6254,6255,6256,0,3008,3007,3007,3008,6232,6233,6234,6235,43,6256,6255,5416,5414,0,3008,3007,3007,3008,6235,6234,5364,5364,43,6254,6257,6258,6255,0,3007,3010,3010,3007,6233,6236,6237,6234,43,6255,6258,5367,5416,0,3007,3010,3010,3007,6234,6237,5364,5364,43,6253,6256,6259,6260,0,3008,3008,3011,3011,6232,6235,6238,6239,43,6260,6259,6246,6245,0,3011,3011,3001,3001,6239,6238,6225,6224,43,6256,5414,5413,6259,0,3008,3008,3011,3011,6235,5364,5357,6238,43,6259,5413,5400,6246,0,3011,3011,3001,3001,6238,5357,5357,6225,43,6253,6260,6261,6262,0,3008,3011,3011,3008,6232,6239,6240,6241,43,6262,6261,6224,6218,0,3008,3011,3011,3008,6241,6240,6203,6197,43,6260,6245,6252,6261,0,3011,3001,3001,3011,6239,6224,6231,6240,43,6261,6252,6205,6224,0,3011,3001,3001,3011,6240,6231,6184,6203,43,6253,6262,6263,6254,0,3008,3008,3007,3007,6232,6241,6242,6233,43,6254,6263,6264,6257,0,3007,3007,3010,3010,6233,6242,6243,6236,43,6262,6218,6217,6263,0,3008,3008,3007,3007,6241,6197,6196,6242,43,6263,6217,6221,6264,0,3007,3007,3010,3010,6242,6196,6200,6243,43,6265,6266,6267,6268,0,3037,3038,3039,3040,6244,6245,6246,6247,43,6268,6267,5370,5365,0,3040,3039,3041,3042,6247,6246,5367,5363,43,6266,6119,6132,6267,0,3038,2982,2990,3039,6245,6098,6111,6246,43,6267,6132,4513,5370,0,3039,2990,2320,3041,6246,6111,4513,5367,43,6265,6268,6269,6270,0,3037,3040,3023,3023,6244,6247,6248,6249,43,6270,6269,6258,6257,0,3023,3023,3025,3025,6249,6248,6237,6236,43,6268,5365,5364,6269,0,3040,3042,3023,3023,6247,5363,5362,6248,43,6269,5364,5367,6258,0,3023,3023,3025,3025,6248,5362,5364,6237,43,6265,6270,6271,6272,0,3037,3023,3023,3020,6244,6249,6250,6251,43,6272,6271,6237,6234,0,3020,3023,3023,3022,6251,6250,6216,6213,43,6270,6257,6264,6271,0,3023,3025,3025,3023,6249,6236,6243,6250,43,6271,6264,6221,6237,0,3023,3025,3025,3023,6250,6243,6200,6216,43,6265,6272,6273,6266,0,3037,3020,3019,3038,6244,6251,6252,6245,43,6266,6273,6120,6119,0,3038,3019,2980,2982,6245,6252,6099,6098,43,6272,6234,6233,6273,0,3020,3022,3021,3019,6251,6213,6212,6252,43,6273,6233,6121,6120,0,3019,3021,2983,2980,6252,6212,6100,6099,43,6274,6275,6276,256,0,3043,3044,3045,66,6253,6254,6255,256,43,6277,6278,6279,6280,0,3046,3047,3048,3049,6256,6257,6258,6259,43,6281,6282,6283,6284,0,3050,3051,3052,3053,6260,6261,6262,6263,43,6285,6286,6287,6288,0,3054,3055,3056,3057,6264,6265,6266,6267,43,6289,6290,6291,6292,0,3058,3059,3060,3061,6268,6269,6270,6271,43,6293,6294,6295,6296,0,3062,421,774,3063,6272,6273,6274,6275,43,6297,6298,6299,6300,0,3064,3065,10,10,6276,6277,6278,6279,43,6301,6302,6303,6304,0,3066,3067,3068,3069,6280,6281,6282,6283,43,6305,6306,6307,6308,0,3070,55,67,3071,6284,6285,6286,6287,43,6309,6310,6311,6312,0,11,11,12,12,6288,6289,6290,6291,43,6313,6314,6315,6316,0,35,35,906,906,6292,6293,6294,6295,43,6317,6318,6319,6320,0,35,35,906,906,6296,6297,6298,6299,43,6321,6322,6323,266,0,3072,3073,775,22,6300,6301,6302,266,43,6324,6325,6326,6327,0,3074,3075,3076,3077,6303,6304,6305,6306,43,6328,6312,6311,6329,0,3078,12,12,3079,6307,6291,6290,6308,43,6330,6331,6332,49,0,15,15,16,16,6309,6310,6311,49,43,6333,6334,6335,6336,0,61,61,776,776,6312,6313,6314,6315,43,6337,6338,6339,6340,0,61,61,776,776,6316,6317,6318,6319,43,6341,6342,6343,98,0,3080,3081,3082,27,6320,6321,6322,98,43,6344,6345,6346,6347,0,3083,3084,3085,3086,6323,6324,6325,6326,43,6348,6349,6350,6351,0,3087,18,18,3088,6327,6328,6329,6330,43,6352,6353,6354,149,0,3089,3090,3091,64,6331,6332,6333,149,43,6355,6356,6357,6358,0,3092,3093,3094,3095,6334,6335,6336,6337,43,6359,6360,6361,6362,0,3096,6,6,3097,6338,6339,6340,6341,43,6363,6364,6365,6366,0,9,9,10,10,6342,6343,6344,6345,43,6367,6368,6369,6370,0,55,55,67,67,6346,6347,6348,6349,43,6371,6372,6373,6374,0,55,55,67,67,6350,6351,6352,6353,43,6375,6376,6377,6378,0,11,11,12,12,6354,6355,6356,6357,43,6379,6380,6381,6382,0,35,35,906,906,6358,6359,6360,6361,43,6383,6384,6385,6386,0,35,35,906,906,6362,6363,6364,6365,43,6387,6388,6389,6390,0,22,22,23,23,6366,6367,6368,6369,43,6391,6392,6393,6394,0,14,14,775,775,6370,6371,6372,6373,43,6395,326,6396,6397,0,14,14,775,775,6374,326,6375,6376,43,6398,6399,6400,6401,0,3098,3099,3082,19,6377,6378,6379,6380,43,6402,6403,6404,6405,0,3100,3101,3102,3103,6381,6382,6383,6384,43,6406,6407,6408,6409,0,3104,58,3105,3106,6385,6386,6387,6388,43,6410,6411,6332,21,0,3107,3108,16,16,6389,6390,6311,21,43,6412,6413,6414,6415,0,3109,3110,69,3110,6391,6392,6393,6394,43,6416,6417,6418,6419,0,3107,16,16,3108,6395,6396,6397,6398,43,6420,6421,6422,6423,0,15,15,16,16,6399,6400,6401,6402,43,6424,6425,6426,6427,0,61,61,776,776,6403,6404,6405,6406,43,6428,6429,6430,6431,0,61,61,776,776,6407,6408,6409,6410,43,6432,6433,6434,280,0,3111,3112,30,30,6411,6412,6413,280,43,6435,6436,6437,6438,0,3113,3114,83,3114,6414,6415,6416,6417,43,6439,6440,6441,6442,0,3111,30,30,3112,6418,6419,6420,6421,43,6443,6444,6445,6446,0,3115,3116,3117,3118,6422,6423,6424,6425,43,6447,6448,6449,6450,0,3119,3120,3121,3122,6426,6427,6428,6429,43,6451,6452,6453,6454,0,3123,3124,3125,3126,6430,6431,6432,6433,43,6455,6456,6457,6458,0,906,35,906,11,6434,6435,6436,6437,43,6459,6460,6461,6462,0,10,9,10,35,6438,6439,6440,6441,43,6463,6464,6465,6466,0,67,55,67,9,6442,6443,6444,6445,43,6467,6468,6469,6470,0,3127,3128,3129,3130,6446,6447,6448,6449,43,6471,6472,6473,6474,0,36,3131,3132,3128,6450,6451,6452,6453,43,6475,6476,6477,6478,0,3133,35,906,3131,6454,6455,6456,6457,43,6479,6480,6481,225,0,3134,3135,3136,56,6458,6459,6460,225,43,6482,6483,6484,6485,0,3137,3138,3139,3140,6461,6462,6463,6464,43,6486,6487,6488,6489,0,3141,14,775,3142,6465,6466,6467,6468,43,6490,6491,6492,119,0,3143,3144,3145,59,6469,6470,6471,119,43,6493,6494,6495,6496,0,3146,3147,3148,3149,6472,6473,6474,6475,43,6497,6498,6499,6500,0,3150,3151,3152,3153,6476,6477,6478,6479,43,6501,6502,6503,6504,0,3154,3155,18,18,6480,6481,6482,6483,43,6505,6506,6507,6508,0,3156,3157,3158,3159,6484,6485,6486,6487,43,6509,6510,6511,6512,0,3160,3161,3162,3163,6488,6489,6490,6491,43,6513,6514,6515,6516,0,3164,3165,3166,3167,6492,6493,6494,6495,43,6517,6518,6519,6520,0,3168,3169,3170,3171,6496,6497,6498,6499,43,6521,6504,6503,6522,0,3172,18,18,3173,6500,6483,6482,6501,43,6523,6524,6525,126,0,3174,3175,3176,53,6502,6503,6504,126,43,6526,6527,6528,6529,0,3177,3178,3179,3180,6505,6506,6507,6508,43,6530,6531,6532,6533,0,3181,3182,3183,3184,6509,6510,6511,6512,43,6534,6535,6536,341,0,3185,3186,3176,50,6513,6514,6515,341,43,6537,6538,6539,6540,0,3187,3188,3189,3190,6516,6517,6518,6519,43,6541,6542,6543,6544,0,3191,62,3192,3193,6520,6521,6522,6523,43,6545,6546,6547,237,0,3194,3195,67,9,6524,6525,6526,237,43,6548,6549,6550,6551,0,3196,3197,3198,3199,6527,6528,6529,6530,43,6552,6553,6554,6555,0,3200,64,3091,3201,6531,6532,6533,6534,43,6556,6557,6343,25,0,19,3082,3082,19,6535,6536,6322,25,43,6558,6559,6560,6561,0,19,3082,3082,19,6537,6538,6539,6540,43,6562,6563,6564,6565,0,27,26,26,27,6541,6542,6543,6544,43,6566,6567,6568,116,0,39,3136,3136,39,6545,6546,6547,116,43,6569,6570,6571,6572,0,39,3136,3136,39,6548,6549,6550,6551,43,6573,6574,6575,6576,0,56,18,18,56,6552,6553,6554,6555,43,6577,6578,6579,321,0,65,777,777,65,6556,6557,6558,321,43,6580,6581,6582,6583,0,65,777,777,65,6559,6560,6561,6562,43,6584,6585,6586,6587,0,57,23,23,57,6563,6564,6565,6566,43,6588,6589,6590,129,0,25,3105,3105,25,6567,6568,6569,129,43,6591,6592,6593,6594,0,25,3105,3105,25,6570,6571,6572,6573,43,6595,6596,6597,6598,0,58,30,30,58,6574,6575,6576,6577,43,6599,6600,6601,6602,0,22,775,775,22,6578,6579,6580,6581,43,6603,6604,6605,6606,0,22,775,775,22,6582,6583,6584,6585,43,6607,6608,6609,6610,0,14,12,12,14,6586,6587,6588,6589,43,6611,6612,6568,101,0,56,3136,3136,56,6590,6591,6547,101,43,6613,6614,6615,6616,0,56,3136,3136,56,6592,6593,6594,6595,43,6617,6618,6619,6620,0,39,23,23,39,6596,6597,6598,6599,43,6621,6622,6492,77,0,43,3145,3145,43,6600,6601,6471,77,43,6623,6624,6625,6626,0,43,3145,3145,43,6602,6603,6604,6605,43,6627,6628,6629,6630,0,59,18,18,59,6606,6607,6608,6609,43,6631,6632,6633,146,0,63,3202,3202,63,6610,6611,6612,146,43,6634,6635,6636,6637,0,63,3202,3202,63,6613,6614,6615,6616,43,6638,6639,6640,6641,0,60,42,42,60,6617,6618,6619,6620,43,6642,6643,6525,87,0,50,3176,3176,50,6621,6622,6504,87,43,6644,6645,6646,6647,0,50,3176,3176,50,6623,6624,6625,6626,43,6648,6649,6650,6651,0,53,2,2,53,6627,6628,6629,6630,43,6652,6653,6590,109,0,58,3105,3105,58,6631,6632,6569,109,43,6654,6655,6656,6657,0,58,3105,3105,58,6633,6634,6635,6636,43,6658,6659,6660,6661,0,25,26,26,25,6637,6638,6639,6640,43,6662,6663,6664,318,0,15,776,776,15,6641,6642,6643,318,43,6665,6666,6667,6668,0,15,776,776,15,6644,6645,6646,6647,43,6669,6670,6671,6672,0,61,30,30,61,6648,6649,6650,6651,43,6673,6674,6675,315,0,52,3192,3192,52,6652,6653,6654,315,43,6676,6677,6678,6679,0,52,3192,3192,52,6655,6656,6657,6658,43,6680,6681,6682,6683,0,62,16,16,62,6659,6660,6661,6662,43,6684,6685,6686,6687,0,9,67,67,9,6663,6664,6665,6666,43,6688,6689,6690,6691,0,9,67,67,9,6667,6668,6669,6670,43,6692,6693,6694,6695,0,55,6,6,55,6671,6672,6673,6674,43,6696,6697,6698,6699,0,11,906,906,11,6675,6676,6677,6678,43,6700,6701,6702,6703,0,11,906,906,11,6679,6680,6681,6682,43,6704,6705,6706,6707,0,35,10,10,35,6683,6684,6685,6686,43,6708,6709,6633,122,0,60,3202,3202,60,6687,6688,6612,122,43,6710,6711,6712,6713,0,60,3202,3202,60,6689,6690,6691,6692,43,6714,6715,6716,6717,0,63,12,12,63,6693,6694,6695,6696,43,6718,6719,6354,29,0,21,3091,3091,21,6697,6698,6333,29,43,6720,6721,6722,6723,0,21,3091,3091,21,6699,6700,6701,6702,43,6724,6725,6726,6727,0,64,42,42,64,6703,6704,6705,6706,43,6728,6729,6730,6731,0,19,19,18,18,6707,6708,6709,6710,43,6732,6733,6734,6735,0,27,27,3082,3082,6711,6712,6713,6714,43,6736,6737,6738,6739,0,27,27,3082,3082,6715,6716,6717,6718,43,6740,6741,6742,6743,0,39,39,23,23,6719,6720,6721,6722,43,6744,6745,6746,6747,0,56,56,3136,3136,6723,6724,6725,6726,43,6748,6749,6750,6751,0,56,56,3136,3136,6727,6728,6729,6730,43,6752,6753,6754,6755,0,65,777,777,65,6731,6732,6733,6734,43,6756,6757,6758,6759,0,65,777,777,65,6735,6736,6737,6738,43,6760,6743,6742,6761,0,57,23,23,57,6739,6722,6721,6740,43,6762,6763,6764,6765,0,25,3105,3105,25,6741,6742,6743,6744,43,6766,6767,6768,6769,0,25,3105,3105,25,6745,6746,6747,6748,43,6770,6771,6772,6773,0,58,30,30,58,6749,6750,6751,6752,43,6774,6775,6776,6777,0,22,775,775,22,6753,6754,6755,6756,43,6778,6779,6780,6781,0,22,775,775,22,6757,6758,6759,6760,43,6782,6783,6784,6785,0,14,12,12,14,6761,6762,6763,6764,43,6786,6787,6788,6789,0,56,3136,3136,56,6765,6766,6767,6768,43,6790,6791,6792,6793,0,56,3136,3136,56,6769,6770,6771,6772,43,6794,6795,6796,6797,0,39,23,23,39,6773,6774,6775,6776,43,6798,6799,6800,6801,0,43,43,42,42,6777,6778,6779,6780,43,6802,6803,6804,6805,0,59,59,3145,3145,6781,6782,6783,6784,43,6806,6807,6808,6809,0,59,59,3145,3145,6785,6786,6787,6788,43,6810,6811,6784,6783,0,63,63,12,12,6789,6790,6763,6762,43,6812,6813,6814,6815,0,60,60,3202,3202,6791,6792,6793,6794,43,6816,6817,6818,6819,0,60,60,3202,3202,6795,6796,6797,6798,43,6820,6821,6822,6823,0,53,3176,3176,53,6799,6800,6801,6802,43,6824,6825,6826,6827,0,53,3176,3176,53,6803,6804,6805,6806,43,6828,6829,6830,6831,0,50,26,26,50,6807,6808,6809,6810,43,6832,6833,6834,6835,0,58,58,30,30,6811,6812,6813,6814,43,6836,6837,6838,6839,0,25,25,3105,3105,6815,6816,6817,6818,43,6840,337,6841,6842,0,25,25,3105,3105,6819,337,6820,6821,43,6843,6844,6845,6846,0,15,15,16,16,6822,6823,6824,6825,43,6847,6848,6849,6850,0,61,61,776,776,6826,6827,6828,6829,43,6851,334,6852,6853,0,61,61,776,776,6830,334,6831,6832,43,6854,6855,6856,6857,0,62,3192,3192,62,6833,6834,6835,6836,43,6858,6859,6860,6861,0,62,3192,3192,62,6837,6838,6839,6840,43,6862,6863,6864,6865,0,52,2,2,52,6841,6842,6843,6844,43,6866,6867,6868,6869,0,9,67,67,9,6845,6846,6847,6848,43,6870,6871,6872,6873,0,9,67,67,9,6849,6850,6851,6852,43,6874,6875,6876,6877,0,55,6,6,55,6853,6854,6855,6856,43,6878,6879,6880,6881,0,11,11,12,12,6857,6858,6859,6860,43,6882,6883,6884,6885,0,35,35,906,906,6861,6862,6863,6864,43,6886,6887,6888,6889,0,35,35,906,906,6865,6866,6867,6868,43,6890,6891,6892,6893,0,60,3202,3202,60,6869,6870,6871,6872,43,6894,6895,6896,6897,0,60,3202,3202,60,6873,6874,6875,6876,43,6898,6881,6880,6899,0,63,12,12,63,6877,6860,6859,6878,43,6900,6901,6876,6875,0,21,21,6,6,6879,6880,6855,6854,43,6902,6903,6904,6905,0,64,64,3091,3091,6881,6882,6883,6884,43,6906,6907,6908,6909,0,64,64,3091,3091,6885,6886,6887,6888,43,6910,6911,6912,6913,0,19,19,18,18,6889,6890,6891,6892,43,6914,6915,6916,6917,0,27,27,3082,3082,6893,6894,6895,6896,43,6918,6919,6920,6921,0,27,27,3082,3082,6897,6898,6899,6900,43,6922,6923,6924,6925,0,39,39,23,23,6901,6902,6903,6904,43,6926,6927,6928,6929,0,56,56,3136,3136,6905,6906,6907,6908,43,6930,6931,6932,6933,0,56,56,3136,3136,6909,6910,6911,6912,43,6934,6935,6936,6937,0,57,777,777,57,6913,6914,6915,6916,43,6938,6939,6940,6941,0,57,777,777,57,6917,6918,6919,6920,43,6942,6943,6944,6945,0,65,30,30,65,6921,6922,6923,6924,43,6946,6947,6408,6407,0,58,3105,3105,58,6925,6926,6387,6386,43,6948,6949,6950,6951,0,58,3105,3105,58,6927,6928,6929,6930,43,6952,6953,6954,6955,0,25,26,26,25,6931,6932,6933,6934,43,6956,6957,6488,6487,0,14,775,775,14,6935,6936,6467,6466,43,6958,6959,6960,6961,0,14,775,775,14,6937,6938,6939,6940,43,6962,6963,6964,6965,0,22,23,23,22,6941,6942,6943,6944,43,6966,6967,6481,73,0,39,3136,3136,39,6945,6946,6460,73,43,6968,6969,6970,6971,0,39,3136,3136,39,6947,6948,6949,6950,43,6972,6973,6974,6975,0,56,18,18,56,6951,6952,6953,6954,43,6976,6977,6978,6979,0,43,43,42,42,6955,6956,6957,6958,43,6980,6981,6982,6983,0,59,59,3145,3145,6959,6960,6961,6962,43,6984,6985,6986,6987,0,59,59,3145,3145,6963,6964,6965,6966,43,6988,6989,6990,6991,0,63,63,12,12,6967,6968,6969,6970,43,6992,6993,6994,6995,0,60,60,3202,3202,6971,6972,6973,6974,43,6996,6997,6998,6999,0,60,60,3202,3202,6975,6976,6977,6978,43,7000,7001,6547,95,0,55,67,67,55,6979,6980,6526,95,43,7002,7003,7004,7005,0,55,67,67,55,6981,6982,6983,6984,43,7006,7007,7008,7009,0,9,10,10,9,6985,6986,6987,6988,43,7010,7011,7012,7013,0,11,11,12,12,6989,6990,6991,6992,43,7014,7015,7016,7017,0,35,35,906,906,6993,6994,6995,6996,43,7018,7019,7020,7021,0,35,35,906,906,6997,6998,6999,7000,43,7022,7023,7024,7025,0,63,3202,3202,63,7001,7002,7003,7004,43,7026,7027,7028,7029,0,63,3202,3202,63,7005,7006,7007,7008,43,7030,7031,7032,7033,0,60,42,42,60,7009,7010,7011,7012,43,7034,7035,7036,7037,0,21,21,6,6,7013,7014,7015,7016,43,7038,7039,7040,7041,0,64,64,3091,3091,7017,7018,7019,7020,43,7042,7043,7044,7045,0,64,64,3091,3091,7021,7022,7023,7024,43,7046,7047,7048,302,0,62,3192,3192,62,7025,7026,7027,302,43,7049,7050,7051,7052,0,62,3192,3192,62,7028,7029,7030,7031,43,7053,7054,7055,7056,0,52,2,2,52,7032,7033,7034,7035,43,7057,7058,6276,3,0,3,3045,3045,3,7036,7037,6255,3,43,7059,7060,7061,7062,0,3,3045,3045,3,7038,7039,7040,7041,43,7063,7064,7065,7066,0,66,18,18,66,7042,7043,7044,7045,43,7067,7068,6295,6294,0,421,774,774,421,7046,7047,6274,6273,43,1677,1676,7069,7070,0,421,774,774,421,1677,1676,7048,7049,43,7071,7072,7073,7074,0,5,6,6,5,7050,7051,7052,7053,43,7075,7076,7077,7078,0,9,9,10,10,7054,7055,7056,7057,43,7079,7080,7081,7082,0,55,55,67,67,7058,7059,7060,7061,43,7072,7083,1697,7084,0,6,6,55,55,7051,7062,1697,7063,43,7085,7086,7087,7088,0,11,11,12,12,7064,7065,7066,7067,43,7089,7090,7091,7092,0,35,35,906,906,7068,7069,7070,7071,43,7093,7094,6323,17,0,14,775,775,14,7072,7073,6302,17,43,1761,1760,7095,7096,0,14,775,775,14,1761,1760,7074,7075,43,1736,1735,7097,7098,0,22,23,23,22,1736,1735,7076,7077,43,7099,7100,7101,330,0,57,777,777,57,7078,7079,7080,330,43,1776,1775,7102,7103,0,57,777,777,57,1776,1775,7081,7082,43,1757,1756,7104,7105,0,65,30,30,65,1757,1756,7083,7084,43,7106,7107,6339,6338,0,61,776,776,61,7085,7086,6318,6317,43,1773,1772,7108,7109,0,61,776,776,61,1773,1772,7087,7088,43,1660,1659,7110,7111,0,15,16,16,15,1660,1659,7089,7090,43,7112,7113,7114,7115,0,15,15,16,16,7091,7092,7093,7094,43,7116,7117,7118,7119,0,61,61,776,776,7095,7096,7097,7098,43,7120,7121,7122,7123,0,61,61,776,776,7099,7100,7101,7102,43,7124,7125,6434,57,0,30,30,30,30,7103,7104,6413,57,43,7126,7127,7128,7129,0,30,30,30,30,7105,7106,7107,7108,43,7130,7131,7132,7133,0,30,30,30,30,7109,7110,7111,7112,43,7134,7135,7136,7137,0,15,776,776,15,7113,7114,7115,7116,43,7138,7139,7140,7141,0,15,776,776,15,7117,7118,7119,7120,43,7142,7143,7144,7145,0,61,30,30,61,7121,7122,7123,7124,43,7146,7147,6418,6417,0,16,16,16,16,7125,7126,6397,6396,43,7148,7149,7150,7151,0,16,16,16,16,7127,7128,7129,7130,43,7152,7153,7154,7155,0,16,16,16,16,7131,7132,7133,7134,43,7156,7157,7122,7121,0,61,776,776,61,7135,7136,7101,7100,43,7158,7159,7160,7161,0,61,776,776,61,7137,7138,7139,7140,43,7162,7163,7164,7165,0,15,16,16,15,7141,7142,7143,7144,43,7166,7167,7168,7169,0,30,30,30,30,7145,7146,7147,7148,43,7170,7171,7172,7173,0,30,30,30,30,7149,7150,7151,7152,43,7174,7175,7176,7177,0,30,30,30,30,7153,7154,7155,7156,43,7178,7179,7180,7181,0,15,776,776,15,7157,7158,7159,7160,43,7182,7183,7184,7185,0,15,776,776,15,7161,7162,7163,7164,43,7186,7187,7188,7189,0,61,30,30,61,7165,7166,7167,7168,43,7190,7191,7192,7193,0,16,16,16,16,7169,7170,7171,7172,43,7194,7195,7196,7197,0,16,16,16,16,7173,7174,7175,7176,43,7198,7199,7200,7201,0,16,16,16,16,7177,7178,7179,7180,43,7202,7203,7048,252,0,3203,3204,3192,52,7181,7182,7027,252,43,7204,7205,7206,7207,0,3205,3206,3207,3208,7183,7184,7185,7186,43,7208,7209,7210,7211,0,3209,3210,3211,3212,7187,7188,7189,7190,43,7212,7213,7214,7215,0,3213,3214,3215,3216,7191,7192,7193,7194,43,7216,7217,7218,7219,0,3217,3218,3219,3220,7195,7196,7197,7198,43,7220,7221,7222,7223,0,3221,3222,3223,3224,7199,7200,7201,7202,43,7224,7225,7226,7227,0,3225,3226,3227,3228,7203,7204,7205,7206,43,7228,7229,7230,7231,0,3229,3230,3231,3232,7207,7208,7209,7210,43,7232,7215,7214,7233,0,3233,3216,3215,3234,7211,7194,7193,7212,43,7234,7235,7236,7237,0,3235,3236,3237,3238,7213,7214,7215,7216,43,7238,7239,7240,7241,0,3239,3240,3241,3242,7217,7218,7219,7220,43,7242,7243,7244,7245,0,80,81,3243,3244,7221,7222,7223,7224,43,7246,7247,6675,136,0,3245,3246,3192,62,7225,7226,6654,136,43,7248,7249,7250,7251,0,3247,3248,3249,3250,7227,7228,7229,7230,43,7252,7253,7254,7255,0,3251,2,2,3252,7231,7232,7233,7234,43,7256,7257,6664,132,0,3239,3240,776,61,7235,7236,6643,132,43,7258,7259,7260,7261,0,3235,3236,3241,3242,7237,7238,7239,7240,43,7262,7263,7264,7265,0,75,76,3243,3244,7241,7242,7243,7244,43,7266,7267,6579,105,0,57,777,777,57,7245,7246,6558,105,43,7268,7269,7270,7271,0,57,777,777,57,7247,7248,7249,7250,43,7272,7273,7274,7275,0,65,30,30,65,7251,7252,7253,7254,43,7276,7277,7278,7279,0,3253,3254,30,30,7255,7256,7257,7258,43,7280,7281,7282,7283,0,3255,3256,3257,3258,7259,7260,7261,7262,43,7284,7285,7286,7287,0,3259,3260,3261,3262,7263,7264,7265,7266,43,7288,7289,7290,7291,0,3263,3264,3129,3130,7267,7268,7269,7270,43,7292,7293,7294,7295,0,3265,3266,3267,3268,7271,7272,7273,7274,43,7296,39,6396,7297,0,3269,22,775,3270,7275,39,6375,7276,43,7298,7299,7101,270,0,3271,3272,777,65,7277,7278,7080,270,43,7300,7301,7302,7303,0,3273,3274,3275,3276,7279,7280,7281,7282,43,7304,7305,7306,7307,0,3277,3278,3279,3280,7283,7284,7285,7286,43,7308,7309,6543,6542,0,62,3192,3192,62,7287,7288,6522,6521,43,7310,7311,7312,7313,0,62,3192,3192,62,7289,7290,7291,7292,43,7314,7315,7316,7317,0,52,2,2,52,7293,7294,7295,7296,43,7318,7319,7320,7321,0,61,61,30,30,7297,7298,7299,7300,43,7322,7323,7324,7325,0,15,15,776,776,7301,7302,7303,7304,43,7326,187,6852,7327,0,15,15,776,776,7305,187,6831,7306,43,7328,7329,6830,6829,0,25,25,26,26,7307,7308,6809,6808,43,7330,7331,7332,7333,0,58,58,3105,3105,7309,7310,7311,7312,43,7334,183,6841,7335,0,58,58,3105,3105,7313,183,6820,7314,43,7336,7337,6536,91,0,53,3176,3176,53,7315,7316,6515,91,43,7338,7339,7340,7341,0,53,3176,3176,53,7317,7318,7319,7320,43,7342,7343,7344,7345,0,50,26,26,50,7321,7322,7323,7324,43,6275,0,3,6276,0,3044,0,3,3045,6254,0,3,6255,43,0,6275,6279,6284,0,0,3044,3048,3053,0,6254,6258,6263,43,1,0,6284,6283,0,1,0,3053,3052,1,0,6263,6262,43,6286,4,7,6287,0,3055,4,7,3056,6265,4,7,6266,43,6291,6296,4,6286,0,3060,3063,4,3055,6270,6275,4,6265,43,6296,6295,5,4,0,3063,774,5,4,6275,6274,5,4,43,6298,8,11,6299,0,3065,8,10,10,6277,8,11,6278,43,6303,6308,8,6298,0,3068,3071,8,3065,6282,6287,8,6277,43,6308,6307,9,8,0,3071,67,9,8,6287,6286,9,8,43,6310,12,15,6311,0,11,11,12,12,6289,12,15,6290,43,6315,6320,12,6310,0,906,906,11,11,6294,6299,12,6289,43,6320,6319,13,12,0,906,906,11,11,6299,6298,13,12,43,6322,16,17,6323,0,3073,13,14,775,6301,16,17,6302,43,6326,6329,16,6322,0,3076,3079,13,3073,6305,6308,16,6301,43,6329,6311,15,16,0,3079,12,12,13,6308,6290,15,16,43,6331,18,21,6332,0,15,15,16,16,6310,18,21,6311,43,6335,6340,18,6331,0,776,776,15,15,6314,6319,18,6310,43,6340,6339,19,18,0,776,776,15,15,6319,6318,19,18,43,6342,22,25,6343,0,3081,17,19,3082,6321,22,25,6322,43,6346,6351,22,6342,0,3085,3088,17,3081,6325,6330,22,6321,43,6351,6350,23,22,0,3088,18,18,17,6330,6329,23,22,43,6353,26,29,6354,0,3090,20,21,3091,6332,26,29,6333,43,6357,6362,26,6353,0,3094,3097,20,3090,6336,6341,26,6332,43,6362,6361,27,26,0,3097,6,6,20,6341,6340,27,26,43,6364,30,33,6365,0,9,9,10,10,6343,30,33,6344,43,6369,6374,30,6364,0,67,67,9,9,6348,6353,30,6343,43,6374,6373,31,30,0,67,67,9,9,6353,6352,31,30,43,6376,34,37,6377,0,11,11,12,12,6355,34,37,6356,43,6381,6386,34,6376,0,906,906,11,11,6360,6365,34,6355,43,6386,6385,35,34,0,906,906,11,11,6365,6364,35,34,43,6388,38,41,6389,0,22,22,23,23,6367,38,41,6368,43,6393,6397,38,6388,0,775,775,22,22,6372,6376,38,6367,43,6397,6396,39,38,0,775,775,22,22,6376,6375,39,38,43,6399,42,45,6400,0,3099,24,27,3082,6378,42,45,6379,43,6404,6409,42,6399,0,3102,3106,24,3099,6383,6388,42,6378,43,6409,6408,43,42,0,3106,3105,25,24,6388,6387,43,42,43,6411,46,49,6332,0,3108,28,16,16,6390,46,49,6311,43,6414,6419,46,6411,0,69,3108,28,3108,6393,6398,46,6390,43,6419,6418,47,46,0,3108,16,16,28,6398,6397,47,46,43,6421,50,53,6422,0,15,15,16,16,6400,50,53,6401,43,6426,6431,50,6421,0,776,776,15,15,6405,6410,50,6400,43,6431,6430,51,50,0,776,776,15,15,6410,6409,51,50,43,6433,54,57,6434,0,3112,29,30,30,6412,54,57,6413,43,6437,6442,54,6433,0,83,3112,29,3112,6416,6421,54,6412,43,6442,6441,55,54,0,3112,30,30,29,6421,6420,55,54,43,6444,58,61,6445,0,3116,31,34,3117,6423,58,61,6424,43,6449,6454,58,6444,0,3121,3126,31,3116,6428,6433,58,6423,43,6454,6453,59,58,0,3126,3125,32,31,6433,6432,59,58,43,6456,62,65,6457,0,35,10,35,906,6435,62,65,6436,43,6461,6466,62,6456,0,10,9,10,35,6440,6445,62,6435,43,6466,6465,63,62,0,9,67,9,10,6445,6444,63,62,43,6468,66,69,6469,0,3128,36,37,3129,6447,66,69,6448,43,6473,6478,66,6468,0,3132,3131,36,3128,6452,6457,66,6447,43,6478,6477,67,66,0,3131,906,11,36,6457,6456,67,66,43,6480,70,73,6481,0,3135,38,39,3136,6459,70,73,6460,43,6484,6489,70,6480,0,3139,3142,38,3135,6463,6468,70,6459,43,6489,6488,71,70,0,3142,775,22,38,6468,6467,71,70,43,6491,74,77,6492,0,3144,40,43,3145,6470,74,77,6471,43,6495,6500,74,6491,0,3148,3153,40,3144,6474,6479,74,6470,43,6500,6499,75,74,0,3153,3152,41,40,6479,6478,75,74,43,6502,78,81,6503,0,3155,44,18,18,6481,78,81,6482,43,6507,6512,78,6502,0,3158,3163,44,3155,6486,6491,78,6481,43,6512,6511,79,78,0,3163,3162,45,44,6491,6490,79,78,43,6514,82,83,6515,0,3165,46,47,3166,6493,82,83,6494,43,6519,6522,82,6514,0,3170,3173,46,3165,6498,6501,82,6493,43,6522,6503,81,82,0,3173,18,18,46,6501,6482,81,82,43,6524,84,87,6525,0,3175,48,50,3176,6503,84,87,6504,43,6528,6533,84,6524,0,3179,3184,48,3175,6507,6512,84,6503,43,6533,6532,85,84,0,3184,3183,49,48,6512,6511,85,84,43,6535,88,91,6536,0,3186,51,53,3176,6514,88,91,6515,43,6539,6544,88,6535,0,3189,3193,51,3186,6518,6523,88,6514,43,6544,6543,89,88,0,3193,3192,52,51,6523,6522,89,88,43,6546,92,95,6547,0,3195,54,55,67,6525,92,95,6526,43,6550,6555,92,6546,0,3198,3201,54,3195,6529,6534,92,6525,43,6555,6554,93,92,0,3201,3091,21,54,6534,6533,93,92,43,6557,96,98,6343,0,3082,27,27,3082,6536,96,98,6322,43,6560,6565,96,6557,0,3082,27,27,3082,6539,6544,96,6536,43,6565,6564,97,96,0,27,26,26,27,6544,6543,97,96,43,6567,99,101,6568,0,3136,56,56,3136,6546,99,101,6547,43,6571,6576,99,6567,0,3136,56,56,3136,6550,6555,99,6546,43,6576,6575,100,99,0,56,18,18,56,6555,6554,100,99,43,6578,102,105,6579,0,777,57,57,777,6557,102,105,6558,43,6582,6587,102,6578,0,777,57,57,777,6561,6566,102,6557,43,6587,6586,103,102,0,57,23,23,57,6566,6565,103,102,43,6589,106,109,6590,0,3105,58,58,3105,6568,106,109,6569,43,6593,6598,106,6589,0,3105,58,58,3105,6572,6577,106,6568,43,6598,6597,107,106,0,58,30,30,58,6577,6576,107,106,43,6600,110,113,6601,0,775,14,14,775,6579,110,113,6580,43,6605,6610,110,6600,0,775,14,14,775,6584,6589,110,6579,43,6610,6609,111,110,0,14,12,12,14,6589,6588,111,110,43,6612,114,116,6568,0,3136,39,39,3136,6591,114,116,6547,43,6615,6620,114,6612,0,3136,39,39,3136,6594,6599,114,6591,43,6620,6619,115,114,0,39,23,23,39,6599,6598,115,114,43,6622,117,119,6492,0,3145,59,59,3145,6601,117,119,6471,43,6625,6630,117,6622,0,3145,59,59,3145,6604,6609,117,6601,43,6630,6629,118,117,0,59,18,18,59,6609,6608,118,117,43,6632,120,122,6633,0,3202,60,60,3202,6611,120,122,6612,43,6636,6641,120,6632,0,3202,60,60,3202,6615,6620,120,6611,43,6641,6640,121,120,0,60,42,42,60,6620,6619,121,120,43,6643,123,126,6525,0,3176,53,53,3176,6622,123,126,6504,43,6646,6651,123,6643,0,3176,53,53,3176,6625,6630,123,6622,43,6651,6650,124,123,0,53,2,2,53,6630,6629,124,123,43,6653,127,129,6590,0,3105,25,25,3105,6632,127,129,6569,43,6656,6661,127,6653,0,3105,25,25,3105,6635,6640,127,6632,43,6661,6660,128,127,0,25,26,26,25,6640,6639,128,127,43,6663,130,132,6664,0,776,61,61,776,6642,130,132,6643,43,6667,6672,130,6663,0,776,61,61,776,6646,6651,130,6642,43,6672,6671,131,130,0,61,30,30,61,6651,6650,131,130,43,6674,133,136,6675,0,3192,62,62,3192,6653,133,136,6654,43,6678,6683,133,6674,0,3192,62,62,3192,6657,6662,133,6653,43,6683,6682,134,133,0,62,16,16,62,6662,6661,134,133,43,6685,137,139,6686,0,67,55,55,67,6664,137,139,6665,43,6690,6695,137,6685,0,67,55,55,67,6669,6674,137,6664,43,6695,6694,138,137,0,55,6,6,55,6674,6673,138,137,43,6697,140,143,6698,0,906,35,35,906,6676,140,143,6677,43,6702,6707,140,6697,0,906,35,35,906,6681,6686,140,6676,43,6707,6706,141,140,0,35,10,10,35,6686,6685,141,140,43,6709,144,146,6633,0,3202,63,63,3202,6688,144,146,6612,43,6712,6717,144,6709,0,3202,63,63,3202,6691,6696,144,6688,43,6717,6716,145,144,0,63,12,12,63,6696,6695,145,144,43,6719,147,149,6354,0,3091,64,64,3091,6698,147,149,6333,43,6722,6727,147,6719,0,3091,64,64,3091,6701,6706,147,6698,43,6727,6726,148,147,0,64,42,42,64,6706,6705,148,147,43,6729,150,153,6730,0,19,19,18,18,6708,150,153,6709,43,6734,6739,150,6729,0,3082,3082,19,19,6713,6718,150,6708,43,6739,6738,151,150,0,3082,3082,19,19,6718,6717,151,150,43,6741,154,157,6742,0,39,39,23,23,6720,154,157,6721,43,6746,6751,154,6741,0,3136,3136,39,39,6725,6730,154,6720,43,6751,6750,155,154,0,3136,3136,39,39,6730,6729,155,154,43,6753,158,159,6754,0,777,57,57,777,6732,158,159,6733,43,6758,6761,158,6753,0,777,57,57,777,6737,6740,158,6732,43,6761,6742,157,158,0,57,23,23,57,6740,6721,157,158,43,6763,160,163,6764,0,3105,58,58,3105,6742,160,163,6743,43,6768,6773,160,6763,0,3105,58,58,3105,6747,6752,160,6742,43,6773,6772,161,160,0,58,30,30,58,6752,6751,161,160,43,6775,164,167,6776,0,775,14,14,775,6754,164,167,6755,43,6780,6785,164,6775,0,775,14,14,775,6759,6764,164,6754,43,6785,6784,165,164,0,14,12,12,14,6764,6763,165,164,43,6787,168,171,6788,0,3136,39,39,3136,6766,168,171,6767,43,6792,6797,168,6787,0,3136,39,39,3136,6771,6776,168,6766,43,6797,6796,169,168,0,39,23,23,39,6776,6775,169,168,43,6799,172,175,6800,0,43,43,42,42,6778,172,175,6779,43,6804,6809,172,6799,0,3145,3145,43,43,6783,6788,172,6778,43,6809,6808,173,172,0,3145,3145,43,43,6788,6787,173,172,43,6811,176,165,6784,0,63,63,12,12,6790,176,165,6763,43,6814,6819,176,6811,0,3202,3202,63,63,6793,6798,176,6790,43,6819,6818,177,176,0,3202,3202,63,63,6798,6797,177,176,43,6821,178,181,6822,0,3176,50,50,3176,6800,178,181,6801,43,6826,6831,178,6821,0,3176,50,50,3176,6805,6810,178,6800,43,6831,6830,179,178,0,50,26,26,50,6810,6809,179,178,43,6833,182,185,6834,0,58,58,30,30,6812,182,185,6813,43,6838,6842,182,6833,0,3105,3105,58,58,6817,6821,182,6812,43,6842,6841,183,182,0,3105,3105,58,58,6821,6820,183,182,43,6844,186,189,6845,0,15,15,16,16,6823,186,189,6824,43,6849,6853,186,6844,0,776,776,15,15,6828,6832,186,6823,43,6853,6852,187,186,0,776,776,15,15,6832,6831,187,186,43,6855,190,193,6856,0,3192,52,52,3192,6834,190,193,6835,43,6860,6865,190,6855,0,3192,52,52,3192,6839,6844,190,6834,43,6865,6864,191,190,0,52,2,2,52,6844,6843,191,190,43,6867,194,197,6868,0,67,55,55,67,6846,194,197,6847,43,6872,6877,194,6867,0,67,55,55,67,6851,6856,194,6846,43,6877,6876,195,194,0,55,6,6,55,6856,6855,195,194,43,6879,198,201,6880,0,11,11,12,12,6858,198,201,6859,43,6884,6889,198,6879,0,906,906,11,11,6863,6868,198,6858,43,6889,6888,199,198,0,906,906,11,11,6868,6867,199,198,43,6891,202,203,6892,0,3202,63,63,3202,6870,202,203,6871,43,6896,6899,202,6891,0,3202,63,63,3202,6875,6878,202,6870,43,6899,6880,201,202,0,63,12,12,63,6878,6859,201,202,43,6901,204,195,6876,0,21,21,6,6,6880,204,195,6855,43,6904,6909,204,6901,0,3091,3091,21,21,6883,6888,204,6880,43,6909,6908,205,204,0,3091,3091,21,21,6888,6887,205,204,43,6911,206,209,6912,0,19,19,18,18,6890,206,209,6891,43,6916,6921,206,6911,0,3082,3082,19,19,6895,6900,206,6890,43,6921,6920,207,206,0,3082,3082,19,19,6900,6899,207,206,43,6923,210,213,6924,0,39,39,23,23,6902,210,213,6903,43,6928,6933,210,6923,0,3136,3136,39,39,6907,6912,210,6902,43,6933,6932,211,210,0,3136,3136,39,39,6912,6911,211,210,43,6935,214,217,6936,0,777,65,65,777,6914,214,217,6915,43,6940,6945,214,6935,0,777,65,65,777,6919,6924,214,6914,43,6945,6944,215,214,0,65,30,30,65,6924,6923,215,214,43,6947,218,43,6408,0,3105,25,25,3105,6926,218,43,6387,43,6950,6955,218,6947,0,3105,25,25,3105,6929,6934,218,6926,43,6955,6954,219,218,0,25,26,26,25,6934,6933,219,218,43,6957,220,71,6488,0,775,22,22,775,6936,220,71,6467,43,6960,6965,220,6957,0,775,22,22,775,6939,6944,220,6936,43,6965,6964,221,220,0,22,23,23,22,6944,6943,221,220,43,6967,222,225,6481,0,3136,56,56,3136,6946,222,225,6460,43,6970,6975,222,6967,0,3136,56,56,3136,6949,6954,222,6946,43,6975,6974,223,222,0,56,18,18,56,6954,6953,223,222,43,6977,226,229,6978,0,43,43,42,42,6956,226,229,6957,43,6982,6987,226,6977,0,3145,3145,43,43,6961,6966,226,6956,43,6987,6986,227,226,0,3145,3145,43,43,6966,6965,227,226,43,6989,230,233,6990,0,63,63,12,12,6968,230,233,6969,43,6994,6999,230,6989,0,3202,3202,63,63,6973,6978,230,6968,43,6999,6998,231,230,0,3202,3202,63,63,6978,6977,231,230,43,7001,234,237,6547,0,67,9,9,67,6980,234,237,6526,43,7004,7009,234,7001,0,67,9,9,67,6983,6988,234,6980,43,7009,7008,235,234,0,9,10,10,9,6988,6987,235,234,43,7011,238,241,7012,0,11,11,12,12,6990,238,241,6991,43,7016,7021,238,7011,0,906,906,11,11,6995,7000,238,6990,43,7021,7020,239,238,0,906,906,11,11,7000,6999,239,238,43,7023,242,245,7024,0,3202,60,60,3202,7002,242,245,7003,43,7028,7033,242,7023,0,3202,60,60,3202,7007,7012,242,7002,43,7033,7032,243,242,0,60,42,42,60,7012,7011,243,242,43,7035,246,249,7036,0,21,21,6,6,7014,246,249,7015,43,7040,7045,246,7035,0,3091,3091,21,21,7019,7024,246,7014,43,7045,7044,247,246,0,3091,3091,21,21,7024,7023,247,246,43,7047,250,252,7048,0,3192,52,52,3192,7026,250,252,7027,43,7051,7056,250,7047,0,3192,52,52,3192,7030,7035,250,7026,43,7056,7055,251,250,0,52,2,2,52,7035,7034,251,250,43,7058,253,256,6276,0,3045,66,66,3045,7037,253,256,6255,43,7061,7066,253,7058,0,3045,66,66,3045,7040,7045,253,7037,43,7066,7065,254,253,0,66,18,18,66,7045,7044,254,253,43,7068,257,5,6295,0,774,5,5,774,7047,257,5,6274,43,7069,7074,257,7068,0,774,5,5,774,7048,7053,257,7047,43,7074,7073,258,257,0,5,6,6,5,7053,7052,258,257,43,7076,262,7346,7077,0,9,9,10,10,7055,262,7325,7056,43,7081,259,262,7076,0,67,67,9,9,7060,259,262,7055,43,1690,7083,7072,7071,0,5,6,6,5,1690,7062,7051,7050,43,7086,1739,1741,7087,0,11,11,12,12,7065,1739,1741,7066,43,7091,7347,1739,7086,0,906,906,11,11,7070,7326,1739,7065,43,7094,263,266,6323,0,775,22,22,775,7073,263,266,6302,43,7095,7098,263,7094,0,775,22,22,775,7074,7077,263,7073,43,7098,7097,264,263,0,22,23,23,22,7077,7076,264,263,43,7100,267,270,7101,0,777,65,65,777,7079,267,270,7080,43,7102,7105,267,7100,0,777,65,65,777,7081,7084,267,7079,43,7105,7104,268,267,0,65,30,30,65,7084,7083,268,267,43,7107,271,19,6339,0,776,15,15,776,7086,271,19,6318,43,7108,7111,271,7107,0,776,15,15,776,7087,7090,271,7086,43,7111,7110,272,271,0,15,16,16,15,7090,7089,272,271,43,7113,273,276,7114,0,15,15,16,16,7092,273,276,7093,43,7118,7123,273,7113,0,776,776,15,15,7097,7102,273,7092,43,7123,7122,274,273,0,776,776,15,15,7102,7101,274,273,43,7125,277,280,6434,0,30,30,30,30,7104,277,280,6413,43,7128,7133,277,7125,0,30,30,30,30,7107,7112,277,7104,43,7133,7132,278,277,0,30,30,30,30,7112,7111,278,277,43,7135,281,283,7136,0,776,61,61,776,7114,281,283,7115,43,7140,7145,281,7135,0,776,61,61,776,7119,7124,281,7114,43,7145,7144,282,281,0,61,30,30,61,7124,7123,282,281,43,7147,284,47,6418,0,16,16,16,16,7126,284,47,6397,43,7150,7155,284,7147,0,16,16,16,16,7129,7134,284,7126,43,7155,7154,285,284,0,16,16,16,16,7134,7133,285,284,43,7157,286,274,7122,0,776,15,15,776,7136,286,274,7101,43,7160,7165,286,7157,0,776,15,15,776,7139,7144,286,7136,43,7165,7164,287,286,0,15,16,16,15,7144,7143,287,286,43,7167,288,291,7168,0,30,30,30,30,7146,288,291,7147,43,7172,7177,288,7167,0,30,30,30,30,7151,7156,288,7146,43,7177,7176,289,288,0,30,30,30,30,7156,7155,289,288,43,7179,292,295,7180,0,776,61,61,776,7158,292,295,7159,43,7184,7189,292,7179,0,776,61,61,776,7163,7168,292,7158,43,7189,7188,293,292,0,61,30,30,61,7168,7167,293,292,43,7191,296,299,7192,0,16,16,16,16,7170,296,299,7171,43,7196,7201,296,7191,0,16,16,16,16,7175,7180,296,7170,43,7201,7200,297,296,0,16,16,16,16,7180,7179,297,296,43,7203,300,302,7048,0,3204,68,62,3192,7182,300,302,7027,43,7206,7211,300,7203,0,3207,3212,68,3204,7185,7190,300,7182,43,7211,7210,301,300,0,3212,3211,69,68,7190,7189,301,300,43,7213,303,306,7214,0,3214,70,72,3215,7192,303,306,7193,43,7218,7223,303,7213,0,3219,3224,70,3214,7197,7202,303,7192,43,7223,7222,304,303,0,3224,3223,71,70,7202,7201,304,303,43,7225,307,308,7226,0,3226,73,74,3227,7204,307,308,7205,43,7230,7233,307,7225,0,3231,3234,73,3226,7209,7212,307,7204,43,7233,7214,306,307,0,3234,3215,72,73,7212,7193,306,307,43,7235,309,312,7236,0,3236,75,78,3237,7214,309,312,7215,43,7240,7245,309,7235,0,3241,3244,75,3236,7219,7224,309,7214,43,7245,7244,310,309,0,3244,3243,76,75,7224,7223,310,309,43,7247,313,315,6675,0,3246,79,52,3192,7226,313,315,6654,43,7250,7255,313,7247,0,3249,3252,79,3246,7229,7234,313,7226,43,7255,7254,314,313,0,3252,2,2,79,7234,7233,314,313,43,7257,316,318,6664,0,3240,80,15,776,7236,316,318,6643,43,7260,7265,316,7257,0,3241,3244,80,3240,7239,7244,316,7236,43,7265,7264,317,316,0,3244,3243,81,80,7244,7243,317,316,43,7267,319,321,6579,0,777,65,65,777,7246,319,321,6558,43,7270,7275,319,7267,0,777,65,65,777,7249,7254,319,7246,43,7275,7274,320,319,0,65,30,30,65,7254,7253,320,319,43,7277,322,324,7278,0,3254,82,30,30,7256,322,324,7257,43,7282,7287,322,7277,0,3257,3262,82,3254,7261,7266,322,7256,43,7287,7286,323,322,0,3262,3261,83,82,7266,7265,323,322,43,7289,325,327,7290,0,3264,84,37,3129,7268,325,327,7269,43,7294,7297,325,7289,0,3267,3270,84,3264,7273,7276,325,7268,43,7297,6396,326,325,0,3270,775,14,84,7276,6375,326,325,43,7299,328,330,7101,0,3272,85,57,777,7278,328,330,7080,43,7302,7307,328,7299,0,3275,3280,85,3272,7281,7286,328,7278,43,7307,7306,329,328,0,3280,3279,86,85,7286,7285,329,328,43,7309,331,89,6543,0,3192,52,52,3192,7288,331,89,6522,43,7312,7317,331,7309,0,3192,52,52,3192,7291,7296,331,7288,43,7317,7316,332,331,0,52,2,2,52,7296,7295,332,331,43,7319,333,335,7320,0,61,61,30,30,7298,333,335,7299,43,7324,7327,333,7319,0,776,776,61,61,7303,7306,333,7298,43,7327,6852,334,333,0,776,776,61,61,7306,6831,334,333,43,7329,336,179,6830,0,25,25,26,26,7308,336,179,6809,43,7332,7335,336,7329,0,3105,3105,25,25,7311,7314,336,7308,43,7335,6841,337,336,0,3105,3105,25,25,7314,6820,337,336,43,7337,338,341,6536,0,3176,50,50,3176,7316,338,341,6515,43,7340,7345,338,7337,0,3176,50,50,3176,7319,7324,338,7316,43,7345,7344,339,338,0,50,26,26,50,7324,7323,339,338,43,7348,6274,256,255,0,18,3043,66,18,7327,6253,256,255,43,7349,6280,6274,7348,0,18,3049,3043,18,7328,6259,6253,7327,43,6280,6279,6275,6274,0,3049,3048,3044,3043,6259,6258,6254,6253,43,7350,6277,6280,7349,0,18,3046,3049,18,7329,6256,6259,7328,43,7351,7352,6277,7350,0,18,3281,3046,18,7330,7331,6256,7329,43,7352,7353,6278,6277,0,3281,3282,3047,3046,7331,7332,6257,6256,43,6278,6281,6284,6279,0,3047,3050,3053,3048,6257,6260,6263,6258,43,7353,7354,6281,6278,0,3282,3283,3050,3047,7332,7333,6260,6257,43,7354,7355,6282,6281,0,3283,3284,3051,3050,7333,7334,6261,6260,43,7356,6285,6288,60,0,3285,3054,3057,33,7335,6264,6267,60,43,7357,6292,6285,7356,0,3286,3061,3054,3285,7336,6271,6264,7335,43,6292,6291,6286,6285,0,3061,3060,3055,3054,6271,6270,6265,6264,43,7358,6289,6292,7357,0,3287,3058,3061,3286,7337,6268,6271,7336,43,7351,7350,6289,7358,0,18,18,3058,3287,7330,7329,6268,7337,43,7350,7349,6290,6289,0,18,18,3059,3058,7329,7328,6269,6268,43,6290,6293,6296,6291,0,3059,3062,3063,3060,6269,6272,6275,6270,43,7349,7348,6293,6290,0,18,18,3062,3059,7328,7327,6272,6269,43,7348,255,6294,6293,0,18,18,421,3062,7327,255,6273,6272,43,6446,6297,6300,64,0,3118,3064,10,10,6425,6276,6279,64,43,6445,6304,6297,6446,0,3117,3069,3064,3118,6424,6283,6276,6425,43,6304,6303,6298,6297,0,3069,3068,3065,3064,6283,6282,6277,6276,43,61,6301,6304,6445,0,34,3066,3069,3117,61,6280,6283,6424,43,60,6288,6301,61,0,33,3057,3066,34,60,6267,6280,61,43,6288,6287,6302,6301,0,3057,3056,3067,3066,6267,6266,6281,6280,43,6302,6305,6308,6303,0,3067,3070,3071,3068,6281,6284,6287,6282,43,6287,7,6305,6302,0,3056,7,3070,3067,6266,7,6284,6281,43,7,6,6306,6305,0,7,6,55,3070,7,6,6285,6284,43,6458,6309,6312,68,0,11,11,12,12,6437,6288,6291,68,43,6457,6316,6309,6458,0,906,906,11,11,6436,6295,6288,6437,43,6316,6315,6310,6309,0,906,906,11,11,6295,6294,6289,6288,43,65,6313,6316,6457,0,35,35,906,906,65,6292,6295,6436,43,64,6300,6313,65,0,10,10,35,35,64,6279,6292,65,43,6300,6299,6314,6313,0,10,10,35,35,6279,6278,6293,6292,43,6314,6317,6320,6315,0,35,35,906,906,6293,6296,6299,6294,43,6299,11,6317,6314,0,10,10,35,35,6278,11,6296,6293,43,11,10,6318,6317,0,10,10,35,35,11,10,6297,6296,43,329,6321,266,265,0,86,3072,22,23,329,6300,266,265,43,7306,6327,6321,329,0,3279,3077,3072,86,7285,6306,6300,329,43,6327,6326,6322,6321,0,3077,3076,3073,3072,6306,6305,6301,6300,43,7305,6324,6327,7306,0,3278,3074,3077,3279,7284,6303,6306,7285,43,7359,6470,6324,7305,0,3288,3130,3074,3278,7338,6449,6303,7284,43,6470,6469,6325,6324,0,3130,3129,3075,3074,6449,6448,6304,6303,43,6325,6328,6329,6326,0,3075,3078,3079,3076,6304,6307,6308,6305,43,6469,69,6328,6325,0,3129,37,3078,3075,6448,69,6307,6304,43,69,68,6312,6328,0,37,12,12,3078,69,68,6291,6307,43,7137,6330,49,48,0,15,15,16,16,7116,6309,49,48,43,7136,6336,6330,7137,0,776,776,15,15,7115,6315,6309,7116,43,6336,6335,6331,6330,0,776,776,15,15,6315,6314,6310,6309,43,283,6333,6336,7136,0,61,61,776,776,283,6312,6315,7115,43,56,55,6333,283,0,30,30,61,61,56,55,6312,283,43,55,6441,6334,6333,0,30,30,61,61,55,6420,6313,6312,43,6334,6337,6340,6335,0,61,61,776,776,6313,6316,6319,6314,43,6441,6440,6337,6334,0,30,30,61,61,6420,6419,6316,6313,43,6440,269,6338,6337,0,30,30,61,61,6419,269,6317,6316,43,85,6341,98,86,0,49,3080,27,26,85,6320,98,86,43,6532,6347,6341,85,0,3183,3086,3080,49,6511,6326,6320,85,43,6347,6346,6342,6341,0,3086,3085,3081,3080,6326,6325,6321,6320,43,6531,6344,6347,6532,0,3182,3083,3086,3183,6510,6323,6326,6511,43,7360,6516,6344,6531,0,3289,3167,3083,3182,7339,6495,6323,6510,43,6516,6515,6345,6344,0,3167,3166,3084,3083,6495,6494,6324,6323,43,6345,6348,6351,6346,0,3084,3087,3088,3085,6324,6327,6330,6325,43,6515,83,6348,6345,0,3166,47,3087,3084,6494,83,6327,6324,43,83,80,6349,6348,0,47,18,18,3087,83,80,6328,6327,43,75,6352,149,76,0,41,3089,64,42,75,6331,149,76,43,6499,6358,6352,75,0,3152,3095,3089,41,6478,6337,6331,75,43,6358,6357,6353,6352,0,3095,3094,3090,3089,6337,6336,6332,6331,43,6498,6355,6358,6499,0,3151,3092,3095,3152,6477,6334,6337,6478,43,7361,7362,6355,6498,0,3290,3291,3092,3151,7340,7341,6334,6477,43,7362,7363,6356,6355,0,3291,3292,3093,3092,7341,7342,6335,6334,43,6356,6359,6362,6357,0,3093,3096,3097,3094,6335,6338,6341,6336,43,7363,7364,6359,6356,0,3292,3293,3096,3093,7342,7343,6338,6335,43,7364,7365,6360,6359,0,3293,6,6,3096,7343,7344,6339,6338,43,6687,6363,6366,142,0,9,9,10,10,6666,6342,6345,142,43,6686,6370,6363,6687,0,67,67,9,9,6665,6349,6342,6666,43,6370,6369,6364,6363,0,67,67,9,9,6349,6348,6343,6342,43,139,6367,6370,6686,0,55,55,67,67,139,6346,6349,6665,43,28,27,6367,139,0,6,6,55,55,28,27,6346,139,43,27,6361,6368,6367,0,6,6,55,55,27,6340,6347,6346,43,6368,6371,6374,6369,0,55,55,67,67,6347,6350,6353,6348,43,6361,6360,6371,6368,0,6,6,55,55,6340,6339,6350,6347,43,6360,7365,6372,6371,0,6,6,55,55,6339,7344,6351,6350,43,6699,6375,6378,112,0,11,11,12,12,6678,6354,6357,112,43,6698,6382,6375,6699,0,906,906,11,11,6677,6361,6354,6678,43,6382,6381,6376,6375,0,906,906,11,11,6361,6360,6355,6354,43,143,6379,6382,6698,0,35,35,906,906,143,6358,6361,6677,43,142,6366,6379,143,0,10,10,35,35,142,6345,6358,143,43,6366,6365,6380,6379,0,10,10,35,35,6345,6344,6359,6358,43,6380,6383,6386,6381,0,35,35,906,906,6359,6362,6365,6360,43,6365,33,6383,6380,0,10,10,35,35,6344,33,6362,6359,43,33,32,6384,6383,0,10,10,35,35,33,32,6363,6362,43,6602,6387,6390,104,0,22,22,23,23,6581,6366,6369,104,43,6601,6394,6387,6602,0,775,775,22,22,6580,6373,6366,6581,43,6394,6393,6388,6387,0,775,775,22,22,6373,6372,6367,6366,43,113,6391,6394,6601,0,14,14,775,775,113,6370,6373,6580,43,112,6378,6391,113,0,12,12,14,14,112,6357,6370,113,43,6378,6377,6392,6391,0,12,12,14,14,6357,6356,6371,6370,43,6392,6395,6397,6393,0,14,14,775,775,6371,6374,6376,6372,43,6377,37,6395,6392,0,12,12,14,14,6356,37,6374,6371,43,37,36,326,6395,0,12,12,14,14,37,36,326,6374,43,7366,6398,6401,7367,0,56,3098,19,18,7345,6377,6380,7346,43,7368,6405,6398,7366,0,3136,3103,3098,56,7347,6384,6377,7345,43,6405,6404,6399,6398,0,3103,3102,3099,3098,6384,6383,6378,6377,43,7369,6402,6405,7368,0,39,3100,3103,3136,7348,6381,6384,7347,43,7370,6937,6402,7369,0,23,57,3100,39,7349,6916,6381,7348,43,6937,6936,6403,6402,0,57,777,3101,3100,6916,6915,6382,6381,43,6403,6406,6409,6404,0,3101,3104,3106,3102,6382,6385,6388,6383,43,6936,217,6406,6403,0,777,65,3104,3101,6915,217,6385,6382,43,217,216,6407,6406,0,65,30,58,3104,217,216,6386,6385,43,301,6410,21,20,0,69,3107,16,16,301,6389,21,20,43,7210,6415,6410,301,0,3211,3110,3107,69,7189,6394,6389,301,43,6415,6414,6411,6410,0,3110,69,3108,3107,6394,6393,6390,6389,43,7209,6412,6415,7210,0,3210,3109,3110,3211,7188,6391,6394,7189,43,7371,7372,6412,7209,0,3294,3210,3109,3210,7350,7351,6391,7188,43,7372,7373,6413,6412,0,3210,3211,3110,3109,7351,7352,6392,6391,43,6413,6416,6419,6414,0,3110,3107,3108,69,6392,6395,6398,6393,43,7373,7374,6416,6413,0,3211,69,3107,3110,7352,7353,6395,6392,43,7374,7375,6417,6416,0,69,16,16,3107,7353,7354,6396,6395,43,7376,6420,6423,7377,0,15,15,16,16,7355,6399,6402,7356,43,7378,6427,6420,7376,0,776,776,15,15,7357,6406,6399,7355,43,6427,6426,6421,6420,0,776,776,15,15,6406,6405,6400,6399,43,7379,6424,6427,7378,0,61,61,776,776,7358,6403,6406,7357,43,7380,7381,6424,7379,0,30,30,61,61,7359,7360,6403,7358,43,7381,7382,6425,6424,0,30,30,61,61,7360,7361,6404,6403,43,6425,6428,6431,6426,0,61,61,776,776,6404,6407,6410,6405,43,7382,7383,6428,6425,0,30,30,61,61,7361,7362,6407,6404,43,7383,7384,6429,6428,0,30,30,61,61,7362,7363,6408,6407,43,323,6432,280,279,0,83,3111,30,30,323,6411,280,279,43,7286,6438,6432,323,0,3261,3114,3111,83,7265,6417,6411,323,43,6438,6437,6433,6432,0,3114,83,3112,3111,6417,6416,6412,6411,43,7285,6435,6438,7286,0,3260,3113,3114,3261,7264,6414,6417,7265,43,7385,7386,6435,7285,0,3295,3260,3113,3260,7364,7365,6414,7264,43,7386,7387,6436,6435,0,3260,3261,3114,3113,7365,7366,6415,6414,43,6436,6439,6442,6437,0,3114,3111,3112,83,6415,6418,6421,6416,43,7387,7388,6439,6436,0,3261,83,3111,3114,7366,7367,6418,6415,43,7388,269,6440,6439,0,83,30,30,3111,7367,269,6419,6418,43,63,6443,6446,64,0,9,3115,3118,10,63,6422,6425,64,43,6465,6450,6443,63,0,67,3122,3115,9,6444,6429,6422,63,43,6450,6449,6444,6443,0,3122,3121,3116,3115,6429,6428,6423,6422,43,6464,6447,6450,6465,0,55,3119,3122,67,6443,6426,6429,6444,43,7365,7364,6447,6464,0,6,3293,3119,55,7344,7343,6426,6443,43,7364,7363,6448,6447,0,3293,3292,3120,3119,7343,7342,6427,6426,43,6448,6451,6454,6449,0,3120,3123,3126,3121,6427,6430,6433,6428,43,7363,7362,6451,6448,0,3292,3291,3123,3120,7342,7341,6430,6427,43,7362,7361,6452,6451,0,3291,3290,3124,3123,7341,7340,6431,6430,43,67,6455,6458,68,0,11,906,11,12,67,6434,6437,68,43,6477,6462,6455,67,0,906,35,906,11,6456,6441,6434,67,43,6462,6461,6456,6455,0,35,10,35,906,6441,6440,6435,6434,43,6476,6459,6462,6477,0,35,10,35,906,6455,6438,6441,6456,43,32,31,6459,6476,0,10,9,10,35,32,31,6438,6455,43,31,6373,6460,6459,0,9,67,9,10,31,6352,6439,6438,43,6460,6463,6466,6461,0,9,67,9,10,6439,6442,6445,6440,43,6373,6372,6463,6460,0,67,55,67,9,6352,6351,6442,6439,43,6372,7365,6464,6463,0,55,6,55,67,6351,7344,6443,6442,43,7291,6467,6470,7359,0,3130,3127,3130,3288,7270,6446,6449,7338,43,7290,6474,6467,7291,0,3129,3128,3127,3130,7269,6453,6446,7270,43,6474,6473,6468,6467,0,3128,3132,3128,3127,6453,6452,6447,6446,43,327,6471,6474,7290,0,37,36,3128,3129,327,6450,6453,7269,43,36,35,6471,327,0,12,11,36,37,36,35,6450,327,43,35,6385,6472,6471,0,11,906,3131,36,35,6364,6451,6450,43,6472,6475,6478,6473,0,3131,3133,3131,3132,6451,6454,6457,6452,43,6385,6384,6475,6472,0,906,35,3133,3131,6364,6363,6454,6451,43,6384,32,6476,6475,0,35,10,35,3133,6363,32,6455,6454,43,7389,6479,225,224,0,59,3134,56,18,7368,6458,225,224,43,7390,6485,6479,7389,0,3145,3140,3134,59,7369,6464,6458,7368,43,6485,6484,6480,6479,0,3140,3139,3135,3134,6464,6463,6459,6458,43,7391,6482,6485,7390,0,43,3137,3140,3145,7370,6461,6464,7369,43,7392,7393,6482,7391,0,42,60,3137,43,7371,7372,6461,7370,43,7393,7394,6483,6482,0,60,3202,3138,3137,7372,7373,6462,6461,43,6483,6486,6489,6484,0,3138,3141,3142,3139,6462,6465,6468,6463,43,7394,7395,6486,6483,0,3202,63,3141,3138,7373,7374,6465,6462,43,7395,7396,6487,6486,0,63,12,14,3141,7374,7375,6466,6465,43,23,6490,119,24,0,18,3143,59,18,23,6469,119,24,43,6350,6496,6490,23,0,18,3149,3143,18,6329,6475,6469,23,43,6496,6495,6491,6490,0,3149,3148,3144,3143,6475,6474,6470,6469,43,6349,6493,6496,6350,0,18,3146,3149,18,6328,6472,6475,6329,43,80,79,6493,6349,0,18,45,3146,18,80,79,6472,6328,43,79,6511,6494,6493,0,45,3162,3147,3146,79,6490,6473,6472,43,6494,6497,6500,6495,0,3147,3150,3153,3148,6473,6476,6479,6474,43,6511,6510,6497,6494,0,3162,3161,3150,3147,6490,6489,6476,6473,43,6510,7361,6498,6497,0,3161,3290,3151,3150,6489,7340,6477,6476,43,7358,6501,6504,7351,0,3287,3154,18,18,7337,6480,6483,7330,43,7357,6508,6501,7358,0,3286,3159,3154,3287,7336,6487,6480,7337,43,6508,6507,6502,6501,0,3159,3158,3155,3154,6487,6486,6481,6480,43,7356,6505,6508,7357,0,3285,3156,3159,3286,7335,6484,6487,7336,43,60,59,6505,7356,0,33,32,3156,3285,60,59,6484,7335,43,59,6453,6506,6505,0,32,3125,3157,3156,59,6432,6485,6484,43,6506,6509,6512,6507,0,3157,3160,3163,3158,6485,6488,6491,6486,43,6453,6452,6509,6506,0,3125,3124,3160,3157,6432,6431,6488,6485,43,6452,7361,6510,6509,0,3124,3290,3161,3160,6431,7340,6489,6488,43,7397,6513,6516,7360,0,3296,3164,3167,3289,7376,6492,6495,7339,43,7398,6520,6513,7397,0,3297,3171,3164,3296,7377,6499,6492,7376,43,6520,6519,6514,6513,0,3171,3170,3165,3164,6499,6498,6493,6492,43,7399,6517,6520,7398,0,3298,3168,3171,3297,7378,6496,6499,7377,43,7355,7354,6517,7399,0,3284,3283,3168,3298,7334,7333,6496,7378,43,7354,7353,6518,6517,0,3283,3282,3169,3168,7333,7332,6497,6496,43,6518,6521,6522,6519,0,3169,3172,3173,3170,6497,6500,6501,6498,43,7353,7352,6521,6518,0,3282,3281,3172,3169,7332,7331,6500,6497,43,7352,7351,6504,6521,0,3281,18,18,3172,7331,7330,6483,6500,43,314,6523,126,125,0,2,3174,53,2,314,6502,126,125,43,7254,6529,6523,314,0,2,3180,3174,2,7233,6508,6502,314,43,6529,6528,6524,6523,0,3180,3179,3175,3174,6508,6507,6503,6502,43,7253,6526,6529,7254,0,2,3177,3180,2,7232,6505,6508,7233,43,305,304,6526,7253,0,2,71,3177,2,305,304,6505,7232,43,304,7222,6527,6526,0,71,3223,3178,3177,304,7201,6506,6505,43,6527,6530,6533,6528,0,3178,3181,3184,3179,6506,6509,6512,6507,43,7222,7221,6530,6527,0,3223,3222,3181,3178,7201,7200,6509,6506,43,7221,7360,6531,6530,0,3222,3289,3182,3181,7200,7339,6510,6509,43,7400,6534,341,340,0,25,3185,50,26,7379,6513,341,340,43,7401,6540,6534,7400,0,3105,3190,3185,25,7380,6519,6513,7379,43,6540,6539,6535,6534,0,3190,3189,3186,3185,6519,6518,6514,6513,43,7402,6537,6540,7401,0,58,3187,3190,3105,7381,6516,6519,7380,43,7403,7404,6537,7402,0,30,61,3187,58,7382,7383,6516,7381,43,7404,7405,6538,6537,0,61,776,3188,3187,7383,7384,6517,6516,43,6538,6541,6544,6539,0,3188,3191,3193,3189,6517,6520,6523,6518,43,7405,7406,6541,6538,0,776,15,3191,3188,7384,7385,6520,6517,43,7406,7407,6542,6541,0,15,16,62,3191,7385,7386,6521,6520,43,7408,6545,237,236,0,35,3194,9,10,7387,6524,237,236,43,7409,6551,6545,7408,0,906,3199,3194,35,7388,6530,6524,7387,43,6551,6550,6546,6545,0,3199,3198,3195,3194,6530,6529,6525,6524,43,7410,6548,6551,7409,0,11,3196,3199,906,7389,6527,6530,7388,43,7411,7025,6548,7410,0,12,63,3196,11,7390,7004,6527,7389,43,7025,7024,6549,6548,0,63,3202,3197,3196,7004,7003,6528,6527,43,6549,6552,6555,6550,0,3197,3200,3201,3198,6528,6531,6534,6529,43,7024,245,6552,6549,0,3202,60,3200,3197,7003,245,6531,6528,43,245,244,6553,6552,0,60,42,64,3200,245,244,6532,6531,43,100,6556,25,24,0,18,19,19,18,100,6535,25,24,43,6575,6561,6556,100,0,18,19,19,18,6554,6540,6535,100,43,6561,6560,6557,6556,0,19,3082,3082,19,6540,6539,6536,6535,43,6574,6558,6561,6575,0,18,19,19,18,6553,6537,6540,6554,43,152,151,6558,6574,0,18,19,19,18,152,151,6537,6553,43,151,6738,6559,6558,0,19,3082,3082,19,151,6717,6538,6537,43,6559,6562,6565,6560,0,3082,27,27,3082,6538,6541,6544,6539,43,6738,6737,6562,6559,0,3082,27,27,3082,6717,6716,6541,6538,43,6737,7412,6563,6562,0,27,26,26,27,6716,7391,6542,6541,43,103,6566,116,104,0,23,39,39,23,103,6545,116,104,43,6586,6572,6566,103,0,23,39,39,23,6565,6551,6545,103,43,6572,6571,6567,6566,0,39,3136,3136,39,6551,6550,6546,6545,43,6585,6569,6572,6586,0,23,39,39,23,6564,6548,6551,6565,43,156,155,6569,6585,0,23,39,39,23,156,155,6548,6564,43,155,6750,6570,6569,0,39,3136,3136,39,155,6729,6549,6548,43,6570,6573,6576,6571,0,3136,56,56,3136,6549,6552,6555,6550,43,6750,6749,6573,6570,0,3136,56,56,3136,6729,6728,6552,6549,43,6749,152,6574,6573,0,56,18,18,56,6728,152,6553,6552,43,107,6577,321,108,0,30,65,65,30,107,6556,321,108,43,6597,6583,6577,107,0,30,65,65,30,6576,6562,6556,107,43,6583,6582,6578,6577,0,65,777,777,65,6562,6561,6557,6556,43,6596,6580,6583,6597,0,30,65,65,30,6575,6559,6562,6576,43,162,6755,6580,6596,0,30,65,65,30,162,6734,6559,6575,43,6755,6754,6581,6580,0,65,777,777,65,6734,6733,6560,6559,43,6581,6584,6587,6582,0,777,57,57,777,6560,6563,6566,6561,43,6754,159,6584,6581,0,777,57,57,777,6733,159,6563,6560,43,159,156,6585,6584,0,57,23,23,57,159,156,6564,6563,43,97,6588,129,86,0,26,25,25,26,97,6567,129,86,43,6564,6594,6588,97,0,26,25,25,26,6543,6573,6567,97,43,6594,6593,6589,6588,0,25,3105,3105,25,6573,6572,6568,6567,43,6563,6591,6594,6564,0,26,25,25,26,6542,6570,6573,6543,43,7412,6765,6591,6563,0,26,25,25,26,7391,6744,6570,6542,43,6765,6764,6592,6591,0,25,3105,3105,25,6744,6743,6571,6570,43,6592,6595,6598,6593,0,3105,58,58,3105,6571,6574,6577,6572,43,6764,163,6595,6592,0,3105,58,58,3105,6743,163,6574,6571,43,163,162,6596,6595,0,58,30,30,58,163,162,6575,6574,43,115,6599,6602,104,0,23,22,22,23,115,6578,6581,104,43,6619,6606,6599,115,0,23,22,22,23,6598,6585,6578,115,43,6606,6605,6600,6599,0,22,775,775,22,6585,6584,6579,6578,43,6618,6603,6606,6619,0,23,22,22,23,6597,6582,6585,6598,43,170,6777,6603,6618,0,23,22,22,23,170,6756,6582,6597,43,6777,6776,6604,6603,0,22,775,775,22,6756,6755,6583,6582,43,6604,6607,6610,6605,0,775,14,14,775,6583,6586,6589,6584,43,6776,167,6607,6604,0,775,14,14,775,6755,167,6586,6583,43,167,166,6608,6607,0,14,12,12,14,167,166,6587,6586,43,118,6611,101,24,0,18,56,56,18,118,6590,101,24,43,6629,6616,6611,118,0,18,56,56,18,6608,6595,6590,118,43,6616,6615,6612,6611,0,56,3136,3136,56,6595,6594,6591,6590,43,6628,6613,6616,6629,0,18,56,56,18,6607,6592,6595,6608,43,7413,6789,6613,6628,0,18,56,56,18,7392,6768,6592,6607,43,6789,6788,6614,6613,0,56,3136,3136,56,6768,6767,6593,6592,43,6614,6617,6620,6615,0,3136,39,39,3136,6593,6596,6599,6594,43,6788,171,6617,6614,0,3136,39,39,3136,6767,171,6596,6593,43,171,170,6618,6617,0,39,23,23,39,171,170,6597,6596,43,121,6621,77,76,0,42,43,43,42,121,6600,77,76,43,6640,6626,6621,121,0,42,43,43,42,6619,6605,6600,121,43,6626,6625,6622,6621,0,43,3145,3145,43,6605,6604,6601,6600,43,6639,6623,6626,6640,0,42,43,43,42,6618,6602,6605,6619,43,174,173,6623,6639,0,42,43,43,42,174,173,6602,6618,43,173,6808,6624,6623,0,43,3145,3145,43,173,6787,6603,6602,43,6624,6627,6630,6625,0,3145,59,59,3145,6603,6606,6609,6604,43,6808,6807,6627,6624,0,3145,59,59,3145,6787,6786,6606,6603,43,6807,7413,6628,6627,0,59,18,18,59,6786,7392,6607,6606,43,111,6631,146,112,0,12,63,63,12,111,6610,146,112,43,6609,6637,6631,111,0,12,63,63,12,6588,6616,6610,111,43,6637,6636,6632,6631,0,63,3202,3202,63,6616,6615,6611,6610,43,6608,6634,6637,6609,0,12,63,63,12,6587,6613,6616,6588,43,166,177,6634,6608,0,12,63,63,12,166,177,6613,6587,43,177,6818,6635,6634,0,63,3202,3202,63,177,6797,6614,6613,43,6635,6638,6641,6636,0,3202,60,60,3202,6614,6617,6620,6615,43,6818,6817,6638,6635,0,3202,60,60,3202,6797,6796,6617,6614,43,6817,174,6639,6638,0,60,42,42,60,6796,174,6618,6617,43,128,6642,87,86,0,26,50,50,26,128,6621,87,86,43,6660,6647,6642,128,0,26,50,50,26,6639,6626,6621,128,43,6647,6646,6643,6642,0,50,3176,3176,50,6626,6625,6622,6621,43,6659,6644,6647,6660,0,26,50,50,26,6638,6623,6626,6639,43,7414,7415,6644,6659,0,26,50,50,26,7393,7394,6623,6638,43,7415,7416,6645,6644,0,50,3176,3176,50,7394,7395,6624,6623,43,6645,6648,6651,6646,0,3176,53,53,3176,6624,6627,6630,6625,43,7416,7417,6648,6645,0,3176,53,53,3176,7395,7396,6627,6624,43,7417,7418,6649,6648,0,53,2,2,53,7396,7397,6628,6627,43,131,6652,109,108,0,30,58,58,30,131,6631,109,108,43,6671,6657,6652,131,0,30,58,58,30,6650,6636,6631,131,43,6657,6656,6653,6652,0,58,3105,3105,58,6636,6635,6632,6631,43,6670,6654,6657,6671,0,30,58,58,30,6649,6633,6636,6650,43,7419,7420,6654,6670,0,30,58,58,30,7398,7399,6633,6649,43,7420,7421,6655,6654,0,58,3105,3105,58,7399,7400,6634,6633,43,6655,6658,6661,6656,0,3105,25,25,3105,6634,6637,6640,6635,43,7421,7422,6658,6655,0,3105,25,25,3105,7400,7401,6637,6634,43,7422,7414,6659,6658,0,25,26,26,25,7401,7393,6638,6637,43,134,6662,318,135,0,16,15,15,16,134,6641,318,135,43,6682,6668,6662,134,0,16,15,15,16,6661,6647,6641,134,43,6668,6667,6663,6662,0,15,776,776,15,6647,6646,6642,6641,43,6681,6665,6668,6682,0,16,15,15,16,6660,6644,6647,6661,43,7423,7424,6665,6681,0,16,15,15,16,7402,7403,6644,6660,43,7424,7425,6666,6665,0,15,776,776,15,7403,7404,6645,6644,43,6666,6669,6672,6667,0,776,61,61,776,6645,6648,6651,6646,43,7425,7426,6669,6666,0,776,61,61,776,7404,7405,6648,6645,43,7426,7419,6670,6669,0,61,30,30,61,7405,7398,6649,6648,43,124,6673,315,125,0,2,52,52,2,124,6652,315,125,43,6650,6679,6673,124,0,2,52,52,2,6629,6658,6652,124,43,6679,6678,6674,6673,0,52,3192,3192,52,6658,6657,6653,6652,43,6649,6676,6679,6650,0,2,52,52,2,6628,6655,6658,6629,43,7418,7427,6676,6649,0,2,52,52,2,7397,7406,6655,6628,43,7427,7428,6677,6676,0,52,3192,3192,52,7406,7407,6656,6655,43,6677,6680,6683,6678,0,3192,62,62,3192,6656,6659,6662,6657,43,7428,7429,6680,6677,0,3192,62,62,3192,7407,7408,6659,6656,43,7429,7423,6681,6680,0,62,16,16,62,7408,7402,6660,6659,43,141,6684,6687,142,0,10,9,9,10,141,6663,6666,142,43,6706,6691,6684,141,0,10,9,9,10,6685,6670,6663,141,43,6691,6690,6685,6684,0,9,67,67,9,6670,6669,6664,6663,43,6705,6688,6691,6706,0,10,9,9,10,6684,6667,6670,6685,43,7430,6869,6688,6705,0,10,9,9,10,7409,6848,6667,6684,43,6869,6868,6689,6688,0,9,67,67,9,6848,6847,6668,6667,43,6689,6692,6695,6690,0,67,55,55,67,6668,6671,6674,6669,43,6868,197,6692,6689,0,67,55,55,67,6847,197,6671,6668,43,197,196,6693,6692,0,55,6,6,55,197,196,6672,6671,43,145,6696,6699,112,0,12,11,11,12,145,6675,6678,112,43,6716,6703,6696,145,0,12,11,11,12,6695,6682,6675,145,43,6703,6702,6697,6696,0,11,906,906,11,6682,6681,6676,6675,43,6715,6700,6703,6716,0,12,11,11,12,6694,6679,6682,6695,43,200,199,6700,6715,0,12,11,11,12,200,199,6679,6694,43,199,6888,6701,6700,0,11,906,906,11,199,6867,6680,6679,43,6701,6704,6707,6702,0,906,35,35,906,6680,6683,6686,6681,43,6888,6887,6704,6701,0,906,35,35,906,6867,6866,6683,6680,43,6887,7430,6705,6704,0,35,10,10,35,6866,7409,6684,6683,43,148,6708,122,76,0,42,60,60,42,148,6687,122,76,43,6726,6713,6708,148,0,42,60,60,42,6705,6692,6687,148,43,6713,6712,6709,6708,0,60,3202,3202,60,6692,6691,6688,6687,43,6725,6710,6713,6726,0,42,60,60,42,6704,6689,6692,6705,43,7431,6893,6710,6725,0,42,60,60,42,7410,6872,6689,6704,43,6893,6892,6711,6710,0,60,3202,3202,60,6872,6871,6690,6689,43,6711,6714,6717,6712,0,3202,63,63,3202,6690,6693,6696,6691,43,6892,203,6714,6711,0,3202,63,63,3202,6871,203,6693,6690,43,203,200,6715,6714,0,63,12,12,63,203,200,6694,6693,43,138,6718,29,28,0,6,21,21,6,138,6697,29,28,43,6694,6723,6718,138,0,6,21,21,6,6673,6702,6697,138,43,6723,6722,6719,6718,0,21,3091,3091,21,6702,6701,6698,6697,43,6693,6720,6723,6694,0,6,21,21,6,6672,6699,6702,6673,43,196,205,6720,6693,0,6,21,21,6,196,205,6699,6672,43,205,6908,6721,6720,0,21,3091,3091,21,205,6887,6700,6699,43,6721,6724,6727,6722,0,3091,64,64,3091,6700,6703,6706,6701,43,6908,6907,6724,6721,0,3091,64,64,3091,6887,6886,6703,6700,43,6907,7431,6725,6724,0,64,42,42,64,6886,7410,6704,6703,43,207,6728,6731,208,0,19,19,18,18,207,6707,6710,208,43,6920,6735,6728,207,0,3082,3082,19,19,6899,6714,6707,207,43,6735,6734,6729,6728,0,3082,3082,19,19,6714,6713,6708,6707,43,6919,6732,6735,6920,0,27,27,3082,3082,6898,6711,6714,6899,43,7432,7433,6732,6919,0,26,26,27,27,7411,7412,6711,6898,43,7433,7434,6733,6732,0,26,26,27,27,7412,7413,6712,6711,43,6733,6736,6739,6734,0,27,27,3082,3082,6712,6715,6718,6713,43,7434,7435,6736,6733,0,26,26,27,27,7413,7414,6715,6712,43,7435,7412,6737,6736,0,26,26,27,27,7414,7391,6716,6715,43,211,6740,6743,212,0,39,39,23,23,211,6719,6722,212,43,6932,6747,6740,211,0,3136,3136,39,39,6911,6726,6719,211,43,6747,6746,6741,6740,0,3136,3136,39,39,6726,6725,6720,6719,43,6931,6744,6747,6932,0,56,56,3136,3136,6910,6723,6726,6911,43,208,6731,6744,6931,0,18,18,56,56,208,6710,6723,6910,43,6731,6730,6745,6744,0,18,18,56,56,6710,6709,6724,6723,43,6745,6748,6751,6746,0,56,56,3136,3136,6724,6727,6730,6725,43,6730,153,6748,6745,0,18,18,56,56,6709,153,6727,6724,43,153,152,6749,6748,0,18,18,56,56,153,152,6728,6727,43,161,6752,6755,162,0,30,65,65,30,161,6731,6734,162,43,6772,6759,6752,161,0,30,65,65,30,6751,6738,6731,161,43,6759,6758,6753,6752,0,65,777,777,65,6738,6737,6732,6731,43,6771,6756,6759,6772,0,30,65,65,30,6750,6735,6738,6751,43,7436,7437,6756,6771,0,30,65,65,30,7415,7416,6735,6750,43,7437,7438,6757,6756,0,65,777,777,65,7416,7417,6736,6735,43,6757,6760,6761,6758,0,777,57,57,777,6736,6739,6740,6737,43,7438,7439,6760,6757,0,777,57,57,777,7417,7418,6739,6736,43,7439,212,6743,6760,0,57,23,23,57,7418,212,6722,6739,43,7435,6762,6765,7412,0,26,25,25,26,7414,6741,6744,7391,43,7434,6769,6762,7435,0,26,25,25,26,7413,6748,6741,7414,43,6769,6768,6763,6762,0,25,3105,3105,25,6748,6747,6742,6741,43,7433,6766,6769,7434,0,26,25,25,26,7412,6745,6748,7413,43,7432,7440,6766,7433,0,26,25,25,26,7411,7419,6745,7412,43,7440,7441,6767,6766,0,25,3105,3105,25,7419,7420,6746,6745,43,6767,6770,6773,6768,0,3105,58,58,3105,6746,6749,6752,6747,43,7441,7442,6770,6767,0,3105,58,58,3105,7420,7421,6749,6746,43,7442,7436,6771,6770,0,58,30,30,58,7421,7415,6750,6749,43,169,6774,6777,170,0,23,22,22,23,169,6753,6756,170,43,6796,6781,6774,169,0,23,22,22,23,6775,6760,6753,169,43,6781,6780,6775,6774,0,22,775,775,22,6760,6759,6754,6753,43,6795,6778,6781,6796,0,23,22,22,23,6774,6757,6760,6775,43,7443,7444,6778,6795,0,23,22,22,23,7422,7423,6757,6774,43,7444,7445,6779,6778,0,22,775,775,22,7423,7424,6758,6757,43,6779,6782,6785,6780,0,775,14,14,775,6758,6761,6764,6759,43,7445,7446,6782,6779,0,775,14,14,775,7424,7425,6761,6758,43,7446,232,6783,6782,0,14,12,12,14,7425,232,6762,6761,43,7447,6786,6789,7413,0,18,56,56,18,7426,6765,6768,7392,43,7448,6793,6786,7447,0,18,56,56,18,7427,6772,6765,7426,43,6793,6792,6787,6786,0,56,3136,3136,56,6772,6771,6766,6765,43,7449,6790,6793,7448,0,18,56,56,18,7428,6769,6772,7427,43,7450,7451,6790,7449,0,18,56,56,18,7429,7430,6769,7428,43,7451,7452,6791,6790,0,56,3136,3136,56,7430,7431,6770,6769,43,6791,6794,6797,6792,0,3136,39,39,3136,6770,6773,6776,6771,43,7452,7453,6794,6791,0,3136,39,39,3136,7431,7432,6773,6770,43,7453,7443,6795,6794,0,39,23,23,39,7432,7422,6774,6773,43,227,6798,6801,228,0,43,43,42,42,227,6777,6780,228,43,6986,6805,6798,227,0,3145,3145,43,43,6965,6784,6777,227,43,6805,6804,6799,6798,0,3145,3145,43,43,6784,6783,6778,6777,43,6985,6802,6805,6986,0,59,59,3145,3145,6964,6781,6784,6965,43,7450,7449,6802,6985,0,18,18,59,59,7429,7428,6781,6964,43,7449,7448,6803,6802,0,18,18,59,59,7428,7427,6782,6781,43,6803,6806,6809,6804,0,59,59,3145,3145,6782,6785,6788,6783,43,7448,7447,6806,6803,0,18,18,59,59,7427,7426,6785,6782,43,7447,7413,6807,6806,0,18,18,59,59,7426,7392,6786,6785,43,231,6810,6783,232,0,63,63,12,12,231,6789,6762,232,43,6998,6815,6810,231,0,3202,3202,63,63,6977,6794,6789,231,43,6815,6814,6811,6810,0,3202,3202,63,63,6794,6793,6790,6789,43,6997,6812,6815,6998,0,60,60,3202,3202,6976,6791,6794,6977,43,228,6801,6812,6997,0,42,42,60,60,228,6780,6791,6976,43,6801,6800,6813,6812,0,42,42,60,60,6780,6779,6792,6791,43,6813,6816,6819,6814,0,60,60,3202,3202,6792,6795,6798,6793,43,6800,175,6816,6813,0,42,42,60,60,6779,175,6795,6792,43,175,174,6817,6816,0,42,42,60,60,175,174,6796,6795,43,191,6820,6823,192,0,2,53,53,2,191,6799,6802,192,43,6864,6827,6820,191,0,2,53,53,2,6843,6806,6799,191,43,6827,6826,6821,6820,0,53,3176,3176,53,6806,6805,6800,6799,43,6863,6824,6827,6864,0,2,53,53,2,6842,6803,6806,6843,43,7418,7417,6824,6863,0,2,53,53,2,7397,7396,6803,6842,43,7417,7416,6825,6824,0,53,3176,3176,53,7396,7395,6804,6803,43,6825,6828,6831,6826,0,3176,50,50,3176,6804,6807,6810,6805,43,7416,7415,6828,6825,0,3176,50,50,3176,7395,7394,6807,6804,43,7415,7414,6829,6828,0,50,26,26,50,7394,7393,6808,6807,43,7402,6832,6835,7403,0,58,58,30,30,7381,6811,6814,7382,43,7401,6839,6832,7402,0,3105,3105,58,58,7380,6818,6811,7381,43,6839,6838,6833,6832,0,3105,3105,58,58,6818,6817,6812,6811,43,7400,6836,6839,7401,0,25,25,3105,3105,7379,6815,6818,7380,43,340,339,6836,7400,0,26,26,25,25,340,339,6815,7379,43,339,7344,6837,6836,0,26,26,25,25,339,7323,6816,6815,43,6837,6840,6842,6838,0,25,25,3105,3105,6816,6819,6821,6817,43,7344,7343,6840,6837,0,26,26,25,25,7323,7322,6819,6816,43,7343,180,337,6840,0,26,26,25,25,7322,180,337,6819,43,7406,6843,6846,7407,0,15,15,16,16,7385,6822,6825,7386,43,7405,6850,6843,7406,0,776,776,15,15,7384,6829,6822,7385,43,6850,6849,6844,6843,0,776,776,15,15,6829,6828,6823,6822,43,7404,6847,6850,7405,0,61,61,776,776,7383,6826,6829,7384,43,7403,6835,6847,7404,0,30,30,61,61,7382,6814,6826,7383,43,6835,6834,6848,6847,0,30,30,61,61,6814,6813,6827,6826,43,6848,6851,6853,6849,0,61,61,776,776,6827,6830,6832,6828,43,6834,185,6851,6848,0,30,30,61,61,6813,185,6830,6827,43,185,184,334,6851,0,30,30,61,61,185,184,334,6830,43,7454,6854,6857,188,0,16,62,62,16,7433,6833,6836,188,43,7455,6861,6854,7454,0,16,62,62,16,7434,6840,6833,7433,43,6861,6860,6855,6854,0,62,3192,3192,62,6840,6839,6834,6833,43,7456,6858,6861,7455,0,16,62,62,16,7435,6837,6840,7434,43,7423,7429,6858,7456,0,16,62,62,16,7402,7408,6837,7435,43,7429,7428,6859,6858,0,62,3192,3192,62,7408,7407,6838,6837,43,6859,6862,6865,6860,0,3192,52,52,3192,6838,6841,6844,6839,43,7428,7427,6862,6859,0,3192,52,52,3192,7407,7406,6841,6838,43,7427,7418,6863,6862,0,52,2,2,52,7406,7397,6842,6841,43,7457,6866,6869,7430,0,10,9,9,10,7436,6845,6848,7409,43,7458,6873,6866,7457,0,10,9,9,10,7437,6852,6845,7436,43,6873,6872,6867,6866,0,9,67,67,9,6852,6851,6846,6845,43,7459,6870,6873,7458,0,10,9,9,10,7438,6849,6852,7437,43,7460,7461,6870,7459,0,10,9,9,10,7439,7440,6849,7438,43,7461,7462,6871,6870,0,9,67,67,9,7440,7441,6850,6849,43,6871,6874,6877,6872,0,67,55,55,67,6850,6853,6856,6851,43,7462,7463,6874,6871,0,67,55,55,67,7441,7442,6853,6850,43,7463,248,6875,6874,0,55,6,6,55,7442,248,6854,6853,43,239,6878,6881,240,0,11,11,12,12,239,6857,6860,240,43,7020,6885,6878,239,0,906,906,11,11,6999,6864,6857,239,43,6885,6884,6879,6878,0,906,906,11,11,6864,6863,6858,6857,43,7019,6882,6885,7020,0,35,35,906,906,6998,6861,6864,6999,43,7460,7459,6882,7019,0,10,10,35,35,7439,7438,6861,6998,43,7459,7458,6883,6882,0,10,10,35,35,7438,7437,6862,6861,43,6883,6886,6889,6884,0,35,35,906,906,6862,6865,6868,6863,43,7458,7457,6886,6883,0,10,10,35,35,7437,7436,6865,6862,43,7457,7430,6887,6886,0,10,10,35,35,7436,7409,6866,6865,43,7464,6890,6893,7431,0,42,60,60,42,7443,6869,6872,7410,43,7465,6897,6890,7464,0,42,60,60,42,7444,6876,6869,7443,43,6897,6896,6891,6890,0,60,3202,3202,60,6876,6875,6870,6869,43,7466,6894,6897,7465,0,42,60,60,42,7445,6873,6876,7444,43,7467,7468,6894,7466,0,42,60,60,42,7446,7447,6873,7445,43,7468,7469,6895,6894,0,60,3202,3202,60,7447,7448,6874,6873,43,6895,6898,6899,6896,0,3202,63,63,3202,6874,6877,6878,6875,43,7469,7470,6898,6895,0,3202,63,63,3202,7448,7449,6877,6874,43,7470,240,6881,6898,0,63,12,12,63,7449,240,6860,6877,43,247,6900,6875,248,0,21,21,6,6,247,6879,6854,248,43,7044,6905,6900,247,0,3091,3091,21,21,7023,6884,6879,247,43,6905,6904,6901,6900,0,3091,3091,21,21,6884,6883,6880,6879,43,7043,6902,6905,7044,0,64,64,3091,3091,7022,6881,6884,7023,43,7467,7466,6902,7043,0,42,42,64,64,7446,7445,6881,7022,43,7466,7465,6903,6902,0,42,42,64,64,7445,7444,6882,6881,43,6903,6906,6909,6904,0,64,64,3091,3091,6882,6885,6888,6883,43,7465,7464,6906,6903,0,42,42,64,64,7444,7443,6885,6882,43,7464,7431,6907,6906,0,42,42,64,64,7443,7410,6886,6885,43,6401,6910,6913,7367,0,19,19,18,18,6380,6889,6892,7346,43,6400,6917,6910,6401,0,3082,3082,19,19,6379,6896,6889,6380,43,6917,6916,6911,6910,0,3082,3082,19,19,6896,6895,6890,6889,43,45,6914,6917,6400,0,27,27,3082,3082,45,6893,6896,6379,43,44,219,6914,45,0,26,26,27,27,44,219,6893,45,43,219,6954,6915,6914,0,26,26,27,27,219,6933,6894,6893,43,6915,6918,6921,6916,0,27,27,3082,3082,6894,6897,6900,6895,43,6954,6953,6918,6915,0,26,26,27,27,6933,6932,6897,6894,43,6953,7432,6919,6918,0,26,26,27,27,6932,7411,6898,6897,43,7369,6922,6925,7370,0,39,39,23,23,7348,6901,6904,7349,43,7368,6929,6922,7369,0,3136,3136,39,39,7347,6908,6901,7348,43,6929,6928,6923,6922,0,3136,3136,39,39,6908,6907,6902,6901,43,7366,6926,6929,7368,0,56,56,3136,3136,7345,6905,6908,7347,43,7367,6913,6926,7366,0,18,18,56,56,7346,6892,6905,7345,43,6913,6912,6927,6926,0,18,18,56,56,6892,6891,6906,6905,43,6927,6930,6933,6928,0,56,56,3136,3136,6906,6909,6912,6907,43,6912,209,6930,6927,0,18,18,56,56,6891,209,6909,6906,43,209,208,6931,6930,0,18,18,56,56,209,208,6910,6909,43,6925,6934,6937,7370,0,23,57,57,23,6904,6913,6916,7349,43,6924,6941,6934,6925,0,23,57,57,23,6903,6920,6913,6904,43,6941,6940,6935,6934,0,57,777,777,57,6920,6919,6914,6913,43,213,6938,6941,6924,0,23,57,57,23,213,6917,6920,6903,43,212,7439,6938,213,0,23,57,57,23,212,7418,6917,213,43,7439,7438,6939,6938,0,57,777,777,57,7418,7417,6918,6917,43,6939,6942,6945,6940,0,777,65,65,777,6918,6921,6924,6919,43,7438,7437,6942,6939,0,777,65,65,777,7417,7416,6921,6918,43,7437,7436,6943,6942,0,65,30,30,65,7416,7415,6922,6921,43,215,6946,6407,216,0,30,58,58,30,215,6925,6386,216,43,6944,6951,6946,215,0,30,58,58,30,6923,6930,6925,215,43,6951,6950,6947,6946,0,58,3105,3105,58,6930,6929,6926,6925,43,6943,6948,6951,6944,0,30,58,58,30,6922,6927,6930,6923,43,7436,7442,6948,6943,0,30,58,58,30,7415,7421,6927,6922,43,7442,7441,6949,6948,0,58,3105,3105,58,7421,7420,6928,6927,43,6949,6952,6955,6950,0,3105,25,25,3105,6928,6931,6934,6929,43,7441,7440,6952,6949,0,3105,25,25,3105,7420,7419,6931,6928,43,7440,7432,6953,6952,0,25,26,26,25,7419,7411,6932,6931,43,6991,6956,6487,7396,0,12,14,14,12,6970,6935,6466,7375,43,6990,6961,6956,6991,0,12,14,14,12,6969,6940,6935,6970,43,6961,6960,6957,6956,0,14,775,775,14,6940,6939,6936,6935,43,233,6958,6961,6990,0,12,14,14,12,233,6937,6940,6969,43,232,7446,6958,233,0,12,14,14,12,232,7425,6937,233,43,7446,7445,6959,6958,0,14,775,775,14,7425,7424,6938,6937,43,6959,6962,6965,6960,0,775,22,22,775,6938,6941,6944,6939,43,7445,7444,6962,6959,0,775,22,22,775,7424,7423,6941,6938,43,7444,7443,6963,6962,0,22,23,23,22,7423,7422,6942,6941,43,221,6966,73,72,0,23,39,39,23,221,6945,73,72,43,6964,6971,6966,221,0,23,39,39,23,6943,6950,6945,221,43,6971,6970,6967,6966,0,39,3136,3136,39,6950,6949,6946,6945,43,6963,6968,6971,6964,0,23,39,39,23,6942,6947,6950,6943,43,7443,7453,6968,6963,0,23,39,39,23,7422,7432,6947,6942,43,7453,7452,6969,6968,0,39,3136,3136,39,7432,7431,6948,6947,43,6969,6972,6975,6970,0,3136,56,56,3136,6948,6951,6954,6949,43,7452,7451,6972,6969,0,3136,56,56,3136,7431,7430,6951,6948,43,7451,7450,6973,6972,0,56,18,18,56,7430,7429,6952,6951,43,7391,6976,6979,7392,0,43,43,42,42,7370,6955,6958,7371,43,7390,6983,6976,7391,0,3145,3145,43,43,7369,6962,6955,7370,43,6983,6982,6977,6976,0,3145,3145,43,43,6962,6961,6956,6955,43,7389,6980,6983,7390,0,59,59,3145,3145,7368,6959,6962,7369,43,224,223,6980,7389,0,18,18,59,59,224,223,6959,7368,43,223,6974,6981,6980,0,18,18,59,59,223,6953,6960,6959,43,6981,6984,6987,6982,0,59,59,3145,3145,6960,6963,6966,6961,43,6974,6973,6984,6981,0,18,18,59,59,6953,6952,6963,6960,43,6973,7450,6985,6984,0,18,18,59,59,6952,7429,6964,6963,43,7395,6988,6991,7396,0,63,63,12,12,7374,6967,6970,7375,43,7394,6995,6988,7395,0,3202,3202,63,63,7373,6974,6967,7374,43,6995,6994,6989,6988,0,3202,3202,63,63,6974,6973,6968,6967,43,7393,6992,6995,7394,0,60,60,3202,3202,7372,6971,6974,7373,43,7392,6979,6992,7393,0,42,42,60,60,7371,6958,6971,7372,43,6979,6978,6993,6992,0,42,42,60,60,6958,6957,6972,6971,43,6993,6996,6999,6994,0,60,60,3202,3202,6972,6975,6978,6973,43,6978,229,6996,6993,0,42,42,60,60,6957,229,6975,6972,43,229,228,6997,6996,0,42,42,60,60,229,228,6976,6975,43,7037,7000,95,94,0,6,55,55,6,7016,6979,95,94,43,7036,7005,7000,7037,0,6,55,55,6,7015,6984,6979,7016,43,7005,7004,7001,7000,0,55,67,67,55,6984,6983,6980,6979,43,249,7002,7005,7036,0,6,55,55,6,249,6981,6984,7015,43,248,7463,7002,249,0,6,55,55,6,248,7442,6981,249,43,7463,7462,7003,7002,0,55,67,67,55,7442,7441,6982,6981,43,7003,7006,7009,7004,0,67,9,9,67,6982,6985,6988,6983,43,7462,7461,7006,7003,0,67,9,9,67,7441,7440,6985,6982,43,7461,7460,7007,7006,0,9,10,10,9,7440,7439,6986,6985,43,7410,7010,7013,7411,0,11,11,12,12,7389,6989,6992,7390,43,7409,7017,7010,7410,0,906,906,11,11,7388,6996,6989,7389,43,7017,7016,7011,7010,0,906,906,11,11,6996,6995,6990,6989,43,7408,7014,7017,7409,0,35,35,906,906,7387,6993,6996,7388,43,236,235,7014,7408,0,10,10,35,35,236,235,6993,7387,43,235,7008,7015,7014,0,10,10,35,35,235,6987,6994,6993,43,7015,7018,7021,7016,0,35,35,906,906,6994,6997,7000,6995,43,7008,7007,7018,7015,0,10,10,35,35,6987,6986,6997,6994,43,7007,7460,7019,7018,0,10,10,35,35,6986,7439,6998,6997,43,7013,7022,7025,7411,0,12,63,63,12,6992,7001,7004,7390,43,7012,7029,7022,7013,0,12,63,63,12,6991,7008,7001,6992,43,7029,7028,7023,7022,0,63,3202,3202,63,7008,7007,7002,7001,43,241,7026,7029,7012,0,12,63,63,12,241,7005,7008,6991,43,240,7470,7026,241,0,12,63,63,12,240,7449,7005,241,43,7470,7469,7027,7026,0,63,3202,3202,63,7449,7448,7006,7005,43,7027,7030,7033,7028,0,3202,60,60,3202,7006,7009,7012,7007,43,7469,7468,7030,7027,0,3202,60,60,3202,7448,7447,7009,7006,43,7468,7467,7031,7030,0,60,42,42,60,7447,7446,7010,7009,43,93,7034,7037,94,0,21,21,6,6,93,7013,7016,94,43,6554,7041,7034,93,0,3091,3091,21,21,6533,7020,7013,93,43,7041,7040,7035,7034,0,3091,3091,21,21,7020,7019,7014,7013,43,6553,7038,7041,6554,0,64,64,3091,3091,6532,7017,7020,6533,43,244,243,7038,6553,0,42,42,64,64,244,243,7017,6532,43,243,7032,7039,7038,0,42,42,64,64,243,7011,7018,7017,43,7039,7042,7045,7040,0,64,64,3091,3091,7018,7021,7024,7019,43,7032,7031,7042,7039,0,42,42,64,64,7011,7010,7021,7018,43,7031,7467,7043,7042,0,42,42,64,64,7010,7446,7022,7021,43,272,7046,302,20,0,16,62,62,16,272,7025,302,20,43,7110,7052,7046,272,0,16,62,62,16,7089,7031,7025,272,43,7052,7051,7047,7046,0,62,3192,3192,62,7031,7030,7026,7025,43,1659,7049,7052,7110,0,16,62,62,16,1659,7028,7031,7089,43,1768,1655,1658,1657,0,15,16,16,15,1768,1655,1658,1657,43,1656,1652,7050,7049,0,62,3192,3192,62,1656,1652,7029,7028,43,7050,7053,7056,7051,0,3192,52,52,3192,7029,7032,7035,7030,43,1652,1651,7053,7050,0,3192,52,52,3192,1652,1651,7032,7029,43,1654,1656,1658,1655,0,62,62,16,16,1654,1656,1658,1655,43,251,7057,3,2,0,2,3,3,2,251,7036,3,2,43,7055,7062,7057,251,0,2,3,3,2,7034,7041,7036,251,43,7062,7061,7058,7057,0,3,3045,3045,3,7041,7040,7037,7036,43,7054,7059,7062,7055,0,2,3,3,2,7033,7038,7041,7034,43,1678,1679,7471,1653,0,405,405,388,388,1678,1679,7450,1653,43,1679,1672,7060,7059,0,3,3045,3045,3,1679,1672,7039,7038,43,7060,7063,7066,7061,0,3045,66,66,3045,7039,7042,7045,7040,43,1672,1671,7063,7060,0,3045,66,66,3045,1672,1671,7042,7039,43,1649,1653,7471,1651,0,385,388,388,385,1649,1653,7450,1651,43,254,7067,6294,255,0,18,421,421,18,254,7046,6273,255,43,7065,7070,7067,254,0,18,421,421,18,7044,7049,7046,254,43,7070,7069,7068,7067,0,421,774,774,421,7049,7048,7047,7046,43,7064,1677,7070,7065,0,18,421,421,18,7043,1677,7049,7044,43,1676,7071,7074,7069,0,774,5,5,774,1676,7050,7053,7048,43,1671,7472,7064,7063,0,66,18,18,66,1671,7451,7043,7042,43,9,7075,7078,10,0,9,9,10,10,9,7054,7057,10,43,6307,7082,7075,9,0,67,67,9,9,6286,7061,7054,9,43,7082,7081,7076,7075,0,67,67,9,9,7061,7060,7055,7054,43,6306,7079,7082,6307,0,55,55,67,67,6285,7058,7061,6286,43,6,258,7079,6306,0,6,6,55,55,6,258,7058,6285,43,258,7073,7080,7079,0,6,6,55,55,258,7052,7059,7058,43,7080,7084,259,7081,0,55,55,67,67,7059,7063,259,7060,43,7073,7072,7084,7080,0,6,6,55,55,7052,7051,7063,7059,43,1675,1690,7071,1676,0,774,5,5,774,1675,1690,7050,1676,43,13,7085,7088,14,0,11,11,12,12,13,7064,7067,14,43,6319,7092,7085,13,0,906,906,11,11,6298,7071,7064,13,43,7092,7091,7086,7085,0,906,906,11,11,7071,7070,7065,7064,43,6318,7089,7092,6319,0,35,35,906,906,6297,7068,7071,6298,43,10,7078,7089,6318,0,10,10,35,35,10,7057,7068,6297,43,7078,7077,7090,7089,0,10,10,35,35,7057,7056,7069,7068,43,7090,7473,7347,7091,0,35,35,906,906,7069,7452,7326,7070,43,7077,7346,7473,7090,0,10,10,35,35,7056,7325,7452,7069,43,7088,7093,17,14,0,12,14,14,12,7067,7072,17,14,43,7087,7096,7093,7088,0,12,14,14,12,7066,7075,7072,7067,43,7096,7095,7094,7093,0,14,775,775,14,7075,7074,7073,7072,43,1741,1761,7096,7087,0,12,14,14,12,1741,1761,7075,7066,43,1737,1738,1740,1720,0,464,464,450,450,1737,1738,1740,1720,43,1740,1738,1761,1741,0,12,14,14,12,1740,1738,1761,1741,43,1760,1736,7098,7095,0,775,22,22,775,1760,1736,7077,7074,43,1718,1720,1740,1719,0,447,450,450,447,1718,1720,1740,1719,43,1732,1731,1736,1760,0,775,22,22,775,1732,1731,1736,1760,43,264,7099,330,265,0,23,57,57,23,264,7078,330,265,43,7097,7103,7099,264,0,23,57,57,23,7076,7082,7078,264,43,7103,7102,7100,7099,0,57,777,777,57,7082,7081,7079,7078,43,1735,1776,7103,7097,0,23,57,57,23,1735,1776,7082,7076,43,1758,1759,1734,1733,0,57,57,23,23,1758,1759,1734,1733,43,1734,1759,1776,1735,0,23,57,57,23,1734,1759,1776,1735,43,1775,1757,7105,7102,0,777,65,65,777,1775,1757,7084,7081,43,1729,1733,1734,1731,0,22,23,23,22,1729,1733,1734,1731,43,1753,1752,1757,1775,0,777,65,65,777,1753,1752,1757,1775,43,268,7106,6338,269,0,30,61,61,30,268,7085,6317,269,43,7104,7109,7106,268,0,30,61,61,30,7083,7088,7085,268,43,7109,7108,7107,7106,0,61,776,776,61,7088,7087,7086,7085,43,1756,1773,7109,7104,0,30,61,61,30,1756,1773,7088,7083,43,1774,1771,1755,1754,0,492,492,479,479,1774,1771,1755,1754,43,1755,1771,1773,1756,0,30,61,61,30,1755,1771,1773,1756,43,1772,1660,7111,7108,0,776,15,15,776,1772,1660,7090,7087,43,1750,1754,1755,1752,0,476,479,479,476,1750,1754,1755,1752,43,1770,1657,1660,1772,0,776,15,15,776,1770,1657,1660,1772,43,7474,7112,7115,7375,0,15,15,16,16,7453,7091,7094,7354,43,7475,7119,7112,7474,0,776,776,15,15,7454,7098,7091,7453,43,7119,7118,7113,7112,0,776,776,15,15,7098,7097,7092,7091,43,7476,7116,7119,7475,0,61,61,776,776,7455,7095,7098,7454,43,279,278,7116,7476,0,30,30,61,61,279,278,7095,7455,43,278,7132,7117,7116,0,30,30,61,61,278,7111,7096,7095,43,7117,7120,7123,7118,0,61,61,776,776,7096,7099,7102,7097,43,7132,7131,7120,7117,0,30,30,61,61,7111,7110,7099,7096,43,7131,290,7121,7120,0,30,30,61,61,7110,290,7100,7099,43,282,7124,57,56,0,30,30,30,30,282,7103,57,56,43,7144,7129,7124,282,0,30,30,30,30,7123,7108,7103,282,43,7129,7128,7125,7124,0,30,30,30,30,7108,7107,7104,7103,43,7143,7126,7129,7144,0,30,30,30,30,7122,7105,7108,7123,43,294,7169,7126,7143,0,30,30,30,30,294,7148,7105,7122,43,7169,7168,7127,7126,0,30,30,30,30,7148,7147,7106,7105,43,7127,7130,7133,7128,0,30,30,30,30,7106,7109,7112,7107,43,7168,291,7130,7127,0,30,30,30,30,7147,291,7109,7106,43,291,290,7131,7130,0,30,30,30,30,291,290,7110,7109,43,285,7134,7137,48,0,16,15,15,16,285,7113,7116,48,43,7154,7141,7134,285,0,16,15,15,16,7133,7120,7113,285,43,7141,7140,7135,7134,0,15,776,776,15,7120,7119,7114,7113,43,7153,7138,7141,7154,0,16,15,15,16,7132,7117,7120,7133,43,298,7181,7138,7153,0,16,15,15,16,298,7160,7117,7132,43,7181,7180,7139,7138,0,15,776,776,15,7160,7159,7118,7117,43,7139,7142,7145,7140,0,776,61,61,776,7118,7121,7124,7119,43,7180,295,7142,7139,0,776,61,61,776,7159,295,7121,7118,43,295,294,7143,7142,0,61,30,30,61,295,294,7122,7121,43,7115,7146,6417,7375,0,16,16,16,16,7094,7125,6396,7354,43,7114,7151,7146,7115,0,16,16,16,16,7093,7130,7125,7094,43,7151,7150,7147,7146,0,16,16,16,16,7130,7129,7126,7125,43,276,7148,7151,7114,0,16,16,16,16,276,7127,7130,7093,43,275,7193,7148,276,0,16,16,16,16,275,7172,7127,276,43,7193,7192,7149,7148,0,16,16,16,16,7172,7171,7128,7127,43,7149,7152,7155,7150,0,16,16,16,16,7128,7131,7134,7129,43,7192,299,7152,7149,0,16,16,16,16,7171,299,7131,7128,43,299,298,7153,7152,0,16,16,16,16,299,298,7132,7131,43,289,7156,7121,290,0,30,61,61,30,289,7135,7100,290,43,7176,7161,7156,289,0,30,61,61,30,7155,7140,7135,289,43,7161,7160,7157,7156,0,61,776,776,61,7140,7139,7136,7135,43,7175,7158,7161,7176,0,30,61,61,30,7154,7137,7140,7155,43,7380,7379,7158,7175,0,30,61,61,30,7359,7358,7137,7154,43,7379,7378,7159,7158,0,61,776,776,61,7358,7357,7138,7137,43,7159,7162,7165,7160,0,776,15,15,776,7138,7141,7144,7139,43,7378,7376,7162,7159,0,776,15,15,776,7357,7355,7141,7138,43,7376,7377,7163,7162,0,15,16,16,15,7355,7356,7142,7141,43,293,7166,7169,294,0,30,30,30,30,293,7145,7148,294,43,7188,7173,7166,293,0,30,30,30,30,7167,7152,7145,293,43,7173,7172,7167,7166,0,30,30,30,30,7152,7151,7146,7145,43,7187,7170,7173,7188,0,30,30,30,30,7166,7149,7152,7167,43,7384,7383,7170,7187,0,30,30,30,30,7363,7362,7149,7166,43,7383,7382,7171,7170,0,30,30,30,30,7362,7361,7150,7149,43,7171,7174,7177,7172,0,30,30,30,30,7150,7153,7156,7151,43,7382,7381,7174,7171,0,30,30,30,30,7361,7360,7153,7150,43,7381,7380,7175,7174,0,30,30,30,30,7360,7359,7154,7153,43,297,7178,7181,298,0,16,15,15,16,297,7157,7160,298,43,7200,7185,7178,297,0,16,15,15,16,7179,7164,7157,297,43,7185,7184,7179,7178,0,15,776,776,15,7164,7163,7158,7157,43,7199,7182,7185,7200,0,16,15,15,16,7178,7161,7164,7179,43,52,51,7182,7199,0,16,15,15,16,52,51,7161,7178,43,51,6430,7183,7182,0,15,776,776,15,51,6409,7162,7161,43,7183,7186,7189,7184,0,776,61,61,776,7162,7165,7168,7163,43,6430,6429,7186,7183,0,776,61,61,776,6409,6408,7165,7162,43,6429,7384,7187,7186,0,61,30,30,61,6408,7363,7166,7165,43,287,7190,7193,275,0,16,16,16,16,287,7169,7172,275,43,7164,7197,7190,287,0,16,16,16,16,7143,7176,7169,287,43,7197,7196,7191,7190,0,16,16,16,16,7176,7175,7170,7169,43,7163,7194,7197,7164,0,16,16,16,16,7142,7173,7176,7143,43,7377,6423,7194,7163,0,16,16,16,16,7356,6402,7173,7142,43,6423,6422,7195,7194,0,16,16,16,16,6402,6401,7174,7173,43,7195,7198,7201,7196,0,16,16,16,16,7174,7177,7180,7175,43,6422,53,7198,7195,0,16,16,16,16,6401,53,7177,7174,43,53,52,7199,7198,0,16,16,16,16,53,52,7178,7177,43,1,7202,252,2,0,1,3203,52,2,1,7181,252,2,43,6283,7207,7202,1,0,3052,3208,3203,1,6262,7186,7181,1,43,7207,7206,7203,7202,0,3208,3207,3204,3203,7186,7185,7182,7181,43,6282,7204,7207,6283,0,3051,3205,3208,3052,6261,7183,7186,6262,43,7355,7477,7204,6282,0,3284,3299,3205,3051,7334,7456,7183,6261,43,7477,7478,7205,7204,0,3299,3300,3206,3205,7456,7457,7184,7183,43,7205,7208,7211,7206,0,3206,3209,3212,3207,7184,7187,7190,7185,43,7478,7479,7208,7205,0,3300,3301,3209,3206,7457,7458,7187,7184,43,7479,7371,7209,7208,0,3301,3294,3210,3209,7458,7350,7188,7187,43,7479,7212,7215,7371,0,3301,3213,3216,3294,7458,7191,7194,7350,43,7478,7219,7212,7479,0,3300,3220,3213,3301,7457,7198,7191,7458,43,7219,7218,7213,7212,0,3220,3219,3214,3213,7198,7197,7192,7191,43,7477,7216,7219,7478,0,3299,3217,3220,3300,7456,7195,7198,7457,43,7355,7399,7216,7477,0,3284,3298,3217,3299,7334,7378,7195,7456,43,7399,7398,7217,7216,0,3298,3297,3218,3217,7378,7377,7196,7195,43,7217,7220,7223,7218,0,3218,3221,3224,3219,7196,7199,7202,7197,43,7398,7397,7220,7217,0,3297,3296,3221,3218,7377,7376,7199,7196,43,7397,7360,7221,7220,0,3296,3289,3222,3221,7376,7339,7200,7199,43,310,7224,7227,311,0,76,3225,3228,77,310,7203,7206,311,43,7244,7231,7224,310,0,3243,3232,3225,76,7223,7210,7203,310,43,7231,7230,7225,7224,0,3232,3231,3226,3225,7210,7209,7204,7203,43,7243,7228,7231,7244,0,81,3229,3232,3243,7222,7207,7210,7223,43,7375,7374,7228,7243,0,16,69,3229,81,7354,7353,7207,7222,43,7374,7373,7229,7228,0,69,3211,3230,3229,7353,7352,7208,7207,43,7229,7232,7233,7230,0,3230,3233,3234,3231,7208,7211,7212,7209,43,7373,7372,7232,7229,0,3211,3210,3233,3230,7352,7351,7211,7208,43,7372,7371,7215,7232,0,3210,3294,3216,3233,7351,7350,7194,7211,43,7279,7234,7237,7480,0,30,3235,3238,30,7258,7213,7216,7459,43,7278,7241,7234,7279,0,30,3242,3235,30,7257,7220,7213,7258,43,7241,7240,7235,7234,0,3242,3241,3236,3235,7220,7219,7214,7213,43,324,7238,7241,7278,0,30,3239,3242,30,324,7217,7220,7257,43,279,7476,7238,324,0,30,61,3239,30,279,7455,7217,324,43,7476,7475,7239,7238,0,61,776,3240,3239,7455,7454,7218,7217,43,7239,7242,7245,7240,0,3240,80,3244,3241,7218,7221,7224,7219,43,7475,7474,7242,7239,0,776,15,80,3240,7454,7453,7221,7218,43,7474,7375,7243,7242,0,15,16,81,80,7453,7354,7222,7221,43,317,7246,136,135,0,81,3245,62,16,317,7225,136,135,43,7264,7251,7246,317,0,3243,3250,3245,81,7243,7230,7225,317,43,7251,7250,7247,7246,0,3250,3249,3246,3245,7230,7229,7226,7225,43,7263,7248,7251,7264,0,76,3247,3250,3243,7242,7227,7230,7243,43,311,7227,7248,7263,0,77,3228,3247,76,311,7206,7227,7242,43,7227,7226,7249,7248,0,3228,3227,3248,3247,7206,7205,7228,7227,43,7249,7252,7255,7250,0,3248,3251,3252,3249,7228,7231,7234,7229,43,7226,308,7252,7249,0,3227,74,3251,3248,7205,308,7231,7228,43,308,305,7253,7252,0,74,2,2,3251,308,305,7232,7231,43,320,7256,132,108,0,30,3239,61,30,320,7235,132,108,43,7274,7261,7256,320,0,30,3242,3239,30,7253,7240,7235,320,43,7261,7260,7257,7256,0,3242,3241,3240,3239,7240,7239,7236,7235,43,7273,7258,7261,7274,0,30,3235,3242,30,7252,7237,7240,7253,43,7480,7237,7258,7273,0,30,3238,3235,30,7459,7216,7237,7252,43,7237,7236,7259,7258,0,3238,3237,3236,3235,7216,7215,7238,7237,43,7259,7262,7265,7260,0,3236,75,3244,3241,7238,7241,7244,7239,43,7236,312,7262,7259,0,3237,78,75,3236,7215,312,7241,7238,43,312,311,7263,7262,0,78,77,76,75,312,311,7242,7241,43,6390,7266,105,104,0,23,57,57,23,6369,7245,105,104,43,6389,7271,7266,6390,0,23,57,57,23,6368,7250,7245,6369,43,7271,7270,7267,7266,0,57,777,777,57,7250,7249,7246,7245,43,41,7268,7271,6389,0,23,57,57,23,41,7247,7250,6368,43,40,7481,7268,41,0,23,57,57,23,40,7460,7247,41,43,7481,7482,7269,7268,0,57,777,777,57,7460,7461,7248,7247,43,7269,7272,7275,7270,0,777,65,65,777,7248,7251,7254,7249,43,7482,7483,7272,7269,0,777,65,65,777,7461,7462,7251,7248,43,7483,7480,7273,7272,0,65,30,30,65,7462,7459,7252,7251,43,7483,7276,7279,7480,0,65,3253,30,30,7462,7255,7258,7459,43,7482,7283,7276,7483,0,777,3258,3253,65,7461,7262,7255,7462,43,7283,7282,7277,7276,0,3258,3257,3254,3253,7262,7261,7256,7255,43,7481,7280,7283,7482,0,57,3255,3258,777,7460,7259,7262,7461,43,40,7484,7280,7481,0,23,3302,3255,57,40,7463,7259,7460,43,7484,7485,7281,7280,0,3302,3303,3256,3255,7463,7464,7260,7259,43,7281,7284,7287,7282,0,3256,3259,3262,3257,7260,7263,7266,7261,43,7485,7486,7284,7281,0,3303,3304,3259,3256,7464,7465,7263,7260,43,7486,7385,7285,7284,0,3304,3295,3260,3259,7465,7364,7264,7263,43,7487,7288,7291,7359,0,3305,3263,3130,3288,7466,7267,7270,7338,43,7488,7295,7288,7487,0,3306,3268,3263,3305,7467,7274,7267,7466,43,7295,7294,7289,7288,0,3268,3267,3264,3263,7274,7273,7268,7267,43,7489,7292,7295,7488,0,3307,3265,3268,3306,7468,7271,7274,7467,43,7385,7486,7292,7489,0,3295,3304,3265,3307,7364,7465,7271,7468,43,7486,7485,7293,7292,0,3304,3303,3266,3265,7465,7464,7272,7271,43,7293,7296,7297,7294,0,3266,3269,3270,3267,7272,7275,7276,7273,43,7485,7484,7296,7293,0,3303,3302,3269,3266,7464,7463,7275,7272,43,7484,40,39,7296,0,3302,23,22,3269,7463,40,39,7275,43,7388,7298,270,269,0,83,3271,65,30,7367,7277,270,269,43,7387,7303,7298,7388,0,3261,3276,3271,83,7366,7282,7277,7367,43,7303,7302,7299,7298,0,3276,3275,3272,3271,7282,7281,7278,7277,43,7386,7300,7303,7387,0,3260,3273,3276,3261,7365,7279,7282,7366,43,7385,7489,7300,7386,0,3295,3307,3273,3260,7364,7468,7279,7365,43,7489,7488,7301,7300,0,3307,3306,3274,3273,7468,7467,7280,7279,43,7301,7304,7307,7302,0,3274,3277,3280,3275,7280,7283,7286,7281,43,7488,7487,7304,7301,0,3306,3305,3277,3274,7467,7466,7283,7280,43,7487,7359,7305,7304,0,3305,3288,3278,3277,7466,7338,7284,7283,43,6846,7308,6542,7407,0,16,62,62,16,6825,7287,6521,7386,43,6845,7313,7308,6846,0,16,62,62,16,6824,7292,7287,6825,43,7313,7312,7309,7308,0,62,3192,3192,62,7292,7291,7288,7287,43,189,7310,7313,6845,0,16,62,62,16,189,7289,7292,6824,43,188,6857,7310,189,0,16,62,62,16,188,6836,7289,189,43,6857,6856,7311,7310,0,62,3192,3192,62,6836,6835,7290,7289,43,7311,7314,7317,7312,0,3192,52,52,3192,7290,7293,7296,7291,43,6856,193,7314,7311,0,3192,52,52,3192,6835,193,7293,7290,43,193,192,7315,7314,0,52,2,2,52,193,192,7294,7293,43,7426,7318,7321,7419,0,61,61,30,30,7405,7297,7300,7398,43,7425,7325,7318,7426,0,776,776,61,61,7404,7304,7297,7405,43,7325,7324,7319,7318,0,776,776,61,61,7304,7303,7298,7297,43,7424,7322,7325,7425,0,15,15,776,776,7403,7301,7304,7404,43,7423,7456,7322,7424,0,16,16,15,15,7402,7435,7301,7403,43,7456,7455,7323,7322,0,16,16,15,15,7435,7434,7302,7301,43,7323,7326,7327,7324,0,15,15,776,776,7302,7305,7306,7303,43,7455,7454,7326,7323,0,16,16,15,15,7434,7433,7305,7302,43,7454,188,187,7326,0,16,16,15,15,7433,188,187,7305,43,7422,7328,6829,7414,0,25,25,26,26,7401,7307,6808,7393,43,7421,7333,7328,7422,0,3105,3105,25,25,7400,7312,7307,7401,43,7333,7332,7329,7328,0,3105,3105,25,25,7312,7311,7308,7307,43,7420,7330,7333,7421,0,58,58,3105,3105,7399,7309,7312,7400,43,7419,7321,7330,7420,0,30,30,58,58,7398,7300,7309,7399,43,7321,7320,7331,7330,0,30,30,58,58,7300,7299,7310,7309,43,7331,7334,7335,7332,0,58,58,3105,3105,7310,7313,7314,7311,43,7320,335,7334,7331,0,30,30,58,58,7299,335,7313,7310,43,335,184,183,7334,0,30,30,58,58,335,184,183,7313,43,332,7336,91,90,0,2,53,53,2,332,7315,91,90,43,7316,7341,7336,332,0,2,53,53,2,7295,7320,7315,332,43,7341,7340,7337,7336,0,53,3176,3176,53,7320,7319,7316,7315,43,7315,7338,7341,7316,0,2,53,53,2,7294,7317,7320,7295,43,192,6823,7338,7315,0,2,53,53,2,192,6802,7317,7294,43,6823,6822,7339,7338,0,53,3176,3176,53,6802,6801,7318,7317,43,7339,7342,7345,7340,0,3176,50,50,3176,7318,7321,7324,7319,43,6822,181,7342,7339,0,3176,50,50,3176,6801,181,7321,7318,43,181,180,7343,7342,0,50,26,26,50,181,180,7322,7321,43,7472,1674,1677,7064,0,18,421,421,18,7451,1674,1677,7043,43,7471,1679,7059,7054,0,2,3,3,2,7450,1679,7038,7033,43,1669,1673,7472,1671,0,66,18,18,66,1669,1673,7451,1671,43,1692,1674,7472,1673,0,421,421,18,18,1692,1674,7451,1673,43,1658,1656,7049,1659,0,16,62,62,16,1658,1656,7028,1659,43,1651,7471,7054,7053,0,52,2,2,52,1651,7450,7033,7032,43,7347,1710,1719,1739,0,906,906,11,11,7326,1710,1719,1739,43,7346,7490,1711,7473,0,10,10,35,35,7325,7469,1711,7452,43,7473,1711,1710,7347,0,35,35,906,906,7452,1711,1710,7326,43,1704,1705,7490,261,0,9,10,10,9,1704,1705,7469,261,43,1708,1711,7490,1705,0,35,35,10,10,1708,1711,7469,1705,43,262,261,7490,7346,0,9,9,10,10,262,261,7469,7325,43,7084,1697,260,259,0,55,55,67,67,7063,1697,260,259,43,1688,1691,7083,1690,0,417,420,420,417,1688,1691,7062,1690,43,1695,1697,7083,1691,0,435,435,420,420,1695,1697,7062,1691,43,7491,7492,7493,7494,0,0,3,2,1,7470,7471,7472,7473,43,7495,7496,7497,7498,0,4,7,6,5,7474,7475,7476,7477,43,7499,7500,7501,7502,0,8,10,10,9,7478,7479,7480,7481,43,7503,7504,7505,7506,0,11,12,12,11,7482,7483,7484,7485,43,7507,7508,7505,7504,0,13,14,12,12,7486,7487,7484,7483,43,7509,7510,7511,7512,0,15,16,16,15,7488,7489,7490,7491,43,7513,7514,7515,7516,0,17,19,18,18,7492,7493,7494,7495,43,7517,7518,7519,7520,0,20,21,6,6,7496,7497,7498,7499,43,7521,7522,7523,7524,0,9,10,10,9,7500,7501,7502,7503,43,7525,7526,7527,7528,0,11,12,12,11,7504,7505,7506,7507,43,7529,7530,7531,7532,0,22,23,23,22,7508,7509,7510,7511,43,7533,7534,7535,7536,0,24,27,26,25,7512,7513,7514,7515,43,7537,7538,7539,7540,0,28,16,16,16,7516,7517,7518,7519,43,7541,7542,7543,7544,0,15,16,16,15,7520,7521,7522,7523,43,7545,7546,7547,7548,0,29,30,30,30,7524,7525,7526,7527,43,7549,7550,7551,7552,0,31,34,33,32,7528,7529,7530,7531,43,7553,7554,7555,7556,0,10,35,10,9,7532,7533,7534,7535,43,7557,7558,7559,7560,0,36,37,12,11,7536,7537,7538,7539,43,7561,7562,7563,7564,0,38,39,23,22,7540,7541,7542,7543,43,7565,7566,7567,7568,0,40,43,42,41,7544,7545,7546,7547,43,7569,7570,7571,7572,0,44,18,18,45,7548,7549,7550,7551,43,7573,7574,7571,7570,0,46,47,18,18,7552,7553,7550,7549,43,7575,7576,7577,7578,0,48,50,26,49,7554,7555,7556,7557,43,7579,7580,7581,7582,0,51,53,2,52,7558,7559,7560,7561,43,7583,7584,7585,7586,0,54,55,6,21,7562,7563,7564,7565,43,7587,7588,7577,7589,0,27,27,26,26,7566,7567,7556,7568,43,7590,7591,7515,7592,0,56,56,18,18,7569,7570,7494,7571,43,7593,7594,7595,7596,0,57,57,23,23,7572,7573,7574,7575,43,7597,7598,7599,7600,0,58,58,30,30,7576,7577,7578,7579,43,7601,7602,7603,7604,0,14,14,12,12,7580,7581,7582,7583,43,7605,7606,7595,7607,0,39,39,23,23,7584,7585,7574,7586,43,7608,7609,7515,7610,0,59,59,18,18,7587,7588,7494,7589,43,7611,7612,7567,7613,0,60,60,42,42,7590,7591,7546,7592,43,7614,7615,7616,7617,0,53,53,2,2,7593,7594,7595,7596,43,7618,7619,7577,7620,0,25,25,26,26,7597,7598,7556,7599,43,7621,7622,7599,7623,0,61,61,30,30,7600,7601,7578,7602,43,7624,7625,7626,7627,0,62,62,16,16,7603,7604,7605,7606,43,7628,7629,7519,7630,0,55,55,6,6,7607,7608,7498,7609,43,7631,7632,7633,7634,0,35,35,10,10,7610,7611,7612,7613,43,7635,7636,7603,7637,0,63,63,12,12,7614,7615,7582,7616,43,7638,7639,7567,7640,0,64,64,42,42,7617,7618,7546,7619,43,7641,7642,7643,7644,0,19,18,18,19,7620,7621,7622,7623,43,7645,7646,7647,7648,0,39,23,23,39,7624,7625,7626,7627,43,7649,7650,7647,7646,0,57,57,23,23,7628,7629,7626,7625,43,7651,7652,7653,7654,0,58,58,30,30,7630,7631,7632,7633,43,7655,7656,7657,7658,0,14,14,12,12,7634,7635,7636,7637,43,7659,7660,7661,7662,0,39,39,23,23,7638,7639,7640,7641,43,7663,7664,7665,7666,0,43,42,42,43,7642,7643,7644,7645,43,7667,7658,7657,7668,0,63,12,12,63,7646,7637,7636,7647,43,7669,7670,7671,7672,0,50,50,26,26,7648,7649,7650,7651,43,7673,7674,7675,7676,0,58,30,30,58,7652,7653,7654,7655,43,7677,7678,7679,7680,0,15,16,16,15,7656,7657,7658,7659,43,7681,7682,7683,7684,0,52,52,2,2,7660,7661,7662,7663,43,7685,7686,7687,7688,0,55,55,6,6,7664,7665,7666,7667,43,7689,7690,7691,7692,0,11,12,12,11,7668,7669,7670,7671,43,7693,7694,7691,7690,0,63,63,12,12,7672,7673,7670,7669,43,7695,7688,7687,7696,0,21,6,6,21,7674,7667,7666,7675,43,7697,7698,7699,7700,0,19,18,18,19,7676,7677,7678,7679,43,7701,7702,7703,7704,0,39,23,23,39,7680,7681,7682,7683,43,7705,7706,7707,7708,0,65,65,30,30,7684,7685,7686,7687,43,7709,7536,7535,7710,0,25,25,26,26,7688,7515,7514,7689,43,7711,7564,7563,7712,0,22,22,23,23,7690,7543,7542,7691,43,7713,7714,7715,7716,0,56,56,18,18,7692,7693,7694,7695,43,7717,7718,7719,7720,0,43,42,42,43,7696,7697,7698,7699,43,7721,7722,7723,7724,0,63,12,12,63,7700,7701,7702,7703,43,7725,7726,7727,7728,0,9,9,10,10,7704,7705,7706,7707,43,7729,7730,7731,7732,0,11,12,12,11,7708,7709,7710,7711,43,7733,7734,7735,7736,0,60,60,42,42,7712,7713,7714,7715,43,7737,7738,7739,7740,0,21,6,6,21,7716,7717,7718,7719,43,7741,7742,7493,7743,0,52,52,2,2,7720,7721,7472,7722,43,7744,7745,7746,7747,0,66,66,18,18,7723,7724,7725,7726,43,7748,7498,7497,7749,0,5,5,6,6,7727,7477,7476,7728,43,7750,7751,7752,7753,0,67,9,9,67,7729,7730,7731,7732,43,7754,7755,7756,7757,0,22,22,23,23,7733,7734,7735,7736,43,7758,7759,7760,7761,0,65,65,30,30,7737,7738,7739,7740,43,7762,7512,7511,7763,0,15,15,16,16,7741,7491,7490,7742,43,7764,7765,7766,7767,0,15,16,16,15,7743,7744,7745,7746,43,7768,7769,7770,7771,0,30,30,30,30,7747,7748,7749,7750,43,7772,7773,7547,7774,0,61,61,30,30,7751,7752,7526,7753,43,7775,7540,7539,7776,0,16,16,16,16,7754,7519,7518,7755,43,7777,7767,7766,7778,0,15,15,16,16,7756,7746,7745,7757,43,7779,7780,7781,7782,0,30,30,30,30,7758,7759,7760,7761,43,7783,7784,7785,7786,0,61,61,30,30,7762,7763,7764,7765,43,7787,7788,7789,7790,0,16,16,16,16,7766,7767,7768,7769,43,7791,7792,7511,7793,0,68,62,16,69,7770,7771,7490,7772,43,7794,7795,7796,7797,0,70,72,2,71,7773,7774,7775,7776,43,7798,7799,7796,7795,0,73,74,2,72,7777,7778,7775,7774,43,7800,7801,7802,7803,0,75,78,77,76,7779,7780,7781,7782,43,7804,7805,7616,7806,0,79,52,2,2,7783,7784,7595,7785,43,7807,7808,7626,7809,0,80,15,16,81,7786,7787,7605,7788,43,7810,7811,7599,7812,0,65,65,30,30,7789,7790,7578,7791,43,7813,7814,7770,7815,0,82,30,30,83,7792,7793,7749,7794,43,7816,7817,7527,7818,0,84,37,12,14,7795,7796,7506,7797,43,7819,7820,7756,7821,0,85,57,23,86,7798,7799,7735,7800,43,7822,7582,7581,7823,0,52,52,2,2,7801,7561,7560,7802,43,7824,7825,7675,7826,0,61,30,30,61,7803,7804,7654,7805,43,7827,7672,7671,7828,0,25,26,26,25,7806,7651,7650,7807,43,7829,7830,7831,7832,0,50,50,26,26,7808,7809,7810,7811,43,7833,7834,7835,7836,0,87,90,89,88,7812,7813,7814,7815,43,7834,7837,7838,7835,0,90,92,91,89,7813,7816,7817,7814,43,7836,7835,7839,7840,0,88,89,94,93,7815,7814,7818,7819,43,7835,7838,7841,7839,0,89,91,95,94,7814,7817,7820,7818,43,7833,7842,7843,7834,0,87,97,96,90,7812,7821,7822,7813,43,7842,7844,7845,7843,0,97,99,98,96,7821,7823,7824,7822,43,7834,7843,7846,7837,0,90,96,100,92,7813,7822,7825,7816,43,7843,7845,7847,7846,0,96,98,101,100,7822,7824,7826,7825,43,7833,7848,7849,7842,0,87,103,102,97,7812,7827,7828,7821,43,7848,7850,7851,7849,0,103,105,104,102,7827,7829,7830,7828,43,7842,7849,7852,7844,0,97,102,106,99,7821,7828,7831,7823,43,7849,7851,7853,7852,0,102,104,107,106,7828,7830,7832,7831,43,7833,7836,7854,7848,0,87,88,108,103,7812,7815,7833,7827,43,7836,7840,7855,7854,0,88,93,109,108,7815,7819,7834,7833,43,7848,7854,7856,7850,0,103,108,110,105,7827,7833,7835,7829,43,7854,7855,7857,7856,0,108,109,111,110,7833,7834,7836,7835,43,7858,7859,7860,7861,0,112,115,114,113,7837,7838,7839,7840,43,7859,7862,7863,7860,0,115,117,116,114,7838,7841,7842,7839,43,7861,7860,7864,7865,0,113,114,119,118,7840,7839,7843,7844,43,7860,7863,7866,7864,0,114,116,120,119,7839,7842,7845,7843,43,7858,7867,7868,7859,0,112,122,121,115,7837,7846,7847,7838,43,7867,379,378,7868,0,122,124,123,121,7846,379,378,7847,43,7859,7868,7869,7862,0,115,121,125,117,7838,7847,7848,7841,43,7868,378,381,7869,0,121,123,126,125,7847,378,381,7848,43,7858,7870,7871,7867,0,112,128,127,122,7837,7849,7850,7846,43,7870,7872,7873,7871,0,128,130,129,127,7849,7851,7852,7850,43,7871,7873,387,386,0,127,129,132,131,7850,7852,387,386,43,7858,7861,7874,7870,0,112,113,133,128,7837,7840,7853,7849,43,7861,7865,7875,7874,0,113,118,134,133,7840,7844,7854,7853,43,7870,7874,7876,7872,0,128,133,135,130,7849,7853,7855,7851,43,7874,7875,7877,7876,0,133,134,136,135,7853,7854,7856,7855,43,7878,7879,7880,7881,0,137,140,139,138,7857,7858,7859,7860,43,7879,7882,7883,7880,0,140,142,141,139,7858,7861,7862,7859,43,7881,7880,7884,7885,0,138,139,144,143,7860,7859,7863,7864,43,7880,7883,7886,7884,0,139,141,145,144,7859,7862,7865,7863,43,7878,7887,7888,7879,0,137,147,146,140,7857,7866,7867,7858,43,7879,7888,7889,7882,0,140,146,150,142,7858,7867,7868,7861,43,7888,403,406,7889,0,146,148,151,150,7867,403,406,7868,43,7878,7890,7891,7887,0,137,153,152,147,7857,7869,7870,7866,43,7890,7892,7893,7891,0,153,154,9,152,7869,7871,7872,7870,43,7878,7881,7894,7890,0,137,138,157,153,7857,7860,7873,7869,43,7881,7885,7895,7894,0,138,143,158,157,7860,7864,7874,7873,43,7890,7894,7896,7892,0,153,157,159,154,7869,7873,7875,7871,43,7894,7895,7897,7896,0,157,158,160,159,7873,7874,7876,7875,43,7898,7899,7900,7901,0,161,164,163,162,7877,7878,7879,7880,43,7899,7902,7903,7900,0,164,166,165,163,7878,7881,7882,7879,43,7901,7900,7904,7905,0,162,163,168,167,7880,7879,7883,7884,43,7900,7903,7897,7904,0,163,165,169,168,7879,7882,7876,7883,43,7898,7906,7907,7899,0,161,171,170,164,7877,7885,7886,7878,43,7906,7908,7909,7907,0,171,173,172,170,7885,7887,7888,7886,43,7899,7907,7910,7902,0,164,170,174,166,7878,7886,7889,7881,43,7907,7909,7911,7910,0,170,172,175,174,7886,7888,7890,7889,43,7898,7912,7913,7906,0,161,177,176,171,7877,7891,7892,7885,43,7912,7914,7915,7913,0,177,179,178,176,7891,7893,7894,7892,43,7906,7913,7916,7908,0,171,176,180,173,7885,7892,7895,7887,43,7913,7915,7917,7916,0,176,178,181,180,7892,7894,7896,7895,43,7898,7901,7918,7912,0,161,162,182,177,7877,7880,7897,7891,43,7901,7905,7919,7918,0,162,167,183,182,7880,7884,7898,7897,43,7912,7918,7920,7914,0,177,182,184,179,7891,7897,7899,7893,43,7918,7919,7921,7920,0,182,183,185,184,7897,7898,7900,7899,43,7922,7923,7924,7925,0,186,189,188,187,7901,7902,7903,7904,43,7923,7850,7856,7924,0,189,105,110,188,7902,7829,7835,7903,43,7925,7924,7926,7927,0,187,188,191,190,7904,7903,7905,7906,43,7924,7856,7857,7926,0,188,110,111,191,7903,7835,7836,7905,43,7922,7928,7929,7923,0,186,193,192,189,7901,7907,7908,7902,43,7928,7930,7931,7929,0,193,195,194,192,7907,7909,7910,7908,43,7923,7929,7851,7850,0,189,192,104,105,7902,7908,7830,7829,43,7929,7931,7853,7851,0,192,194,107,104,7908,7910,7832,7830,43,7922,7932,7933,7928,0,186,197,196,193,7901,7911,7912,7907,43,7932,7908,7916,7933,0,197,173,180,196,7911,7887,7895,7912,43,7928,7933,7934,7930,0,193,196,198,195,7907,7912,7913,7909,43,7933,7916,7917,7934,0,196,180,181,198,7912,7895,7896,7913,43,7922,7925,7935,7932,0,186,187,199,197,7901,7904,7914,7911,43,7925,7927,7936,7935,0,187,190,200,199,7904,7906,7915,7914,43,7932,7935,7909,7908,0,197,199,172,173,7911,7914,7888,7887,43,7935,7936,7911,7909,0,199,200,175,172,7914,7915,7890,7888,43,7937,7938,7939,7940,0,201,204,203,202,7916,7917,7918,7919,43,7938,7941,7942,7939,0,204,206,205,203,7917,7920,7921,7918,43,7940,7939,7943,7944,0,202,203,208,207,7919,7918,7922,7923,43,7939,7942,7841,7943,0,203,205,95,208,7918,7921,7820,7922,43,7937,7945,7946,7938,0,201,210,209,204,7916,7924,7925,7917,43,7945,467,466,7946,0,210,212,211,209,7924,467,466,7925,43,7938,7946,7947,7941,0,204,209,213,206,7917,7925,7926,7920,43,7946,466,469,7947,0,209,211,214,213,7925,466,469,7926,43,7937,7948,7949,7945,0,201,216,215,210,7916,7927,7928,7924,43,7948,7950,7951,7949,0,216,218,217,215,7927,7929,7930,7928,43,7945,7949,474,467,0,210,215,219,212,7924,7928,474,467,43,7949,7951,475,474,0,215,217,220,219,7928,7930,475,474,43,7937,7940,7952,7948,0,201,202,221,216,7916,7919,7931,7927,43,7940,7944,7953,7952,0,202,207,222,221,7919,7923,7932,7931,43,7948,7952,7954,7950,0,216,221,223,218,7927,7931,7933,7929,43,7952,7953,7955,7954,0,221,222,14,223,7931,7932,7934,7933,43,7956,7957,7958,7959,0,224,227,226,225,7935,7936,7937,7938,43,7957,7960,7961,7958,0,227,229,228,226,7936,7939,7940,7937,43,7959,7958,7962,7963,0,225,226,231,230,7938,7937,7941,7942,43,7958,7961,7964,7962,0,226,228,232,231,7937,7940,7943,7941,43,7956,7965,7966,7957,0,224,234,233,227,7935,7944,7945,7936,43,7965,492,491,7966,0,234,236,235,233,7944,492,491,7945,43,7957,7966,7967,7960,0,227,233,237,229,7936,7945,7946,7939,43,7966,491,494,7967,0,233,235,238,237,7945,491,494,7946,43,7956,7968,7969,7965,0,224,240,239,234,7935,7947,7948,7944,43,7968,7970,7971,7969,0,240,242,241,239,7947,7949,7950,7948,43,7969,7971,500,499,0,239,241,244,243,7948,7950,500,499,43,7956,7959,7972,7968,0,224,225,245,240,7935,7938,7951,7947,43,7959,7963,7973,7972,0,225,230,246,245,7938,7942,7952,7951,43,7968,7972,7974,7970,0,240,245,247,242,7947,7951,7953,7949,43,7972,7973,7975,7974,0,245,246,248,247,7951,7952,7954,7953,43,7976,7977,7978,7979,0,249,252,251,250,7955,7956,7957,7958,43,7977,7980,7981,7978,0,252,254,253,251,7956,7959,7960,7957,43,7979,7978,7961,7960,0,250,251,228,229,7958,7957,7940,7939,43,7978,7981,7964,7961,0,251,253,232,228,7957,7960,7943,7940,43,7976,7982,7983,7977,0,249,256,255,252,7955,7961,7962,7956,43,7982,7872,7876,7983,0,256,130,135,255,7961,7851,7855,7962,43,7977,7983,7984,7980,0,252,255,257,254,7956,7962,7963,7959,43,7983,7876,7877,7984,0,255,135,136,257,7962,7855,7856,7963,43,7976,7985,7986,7982,0,249,259,258,256,7955,7964,7965,7961,43,7985,517,516,7986,0,259,261,260,258,7964,517,516,7965,43,7982,7986,7873,7872,0,256,258,129,130,7961,7965,7852,7851,43,7986,516,387,7873,0,258,260,132,129,7965,516,387,7852,43,7976,7979,7987,7985,0,249,250,262,259,7955,7958,7966,7964,43,7979,7960,7967,7987,0,250,229,237,262,7958,7939,7946,7966,43,7985,7987,519,517,0,259,262,263,261,7964,7966,519,517,43,7987,7967,494,519,0,262,237,238,263,7966,7946,494,519,43,7988,7989,7990,7991,0,264,267,266,265,7967,7968,7969,7970,43,7989,7992,7993,7990,0,267,269,268,266,7968,7971,7972,7969,43,7991,7990,7994,7995,0,265,266,271,270,7970,7969,7973,7974,43,7990,7993,7996,7994,0,266,268,272,271,7969,7972,7975,7973,43,7988,7997,7998,7989,0,264,274,273,267,7967,7976,7977,7968,43,7997,7999,8000,7998,0,274,276,275,273,7976,7978,7979,7977,43,7989,7998,8001,7992,0,267,273,277,269,7968,7977,7980,7971,43,7998,8000,8002,8001,0,273,275,278,277,7977,7979,7981,7980,43,7988,8003,8004,7997,0,264,280,279,274,7967,7982,7983,7976,43,8003,8005,8006,8004,0,280,281,223,279,7982,7984,7985,7983,43,7997,8004,8007,7999,0,274,279,282,276,7976,7983,7986,7978,43,8004,8006,8008,8007,0,279,223,14,282,7983,7985,7987,7986,43,7988,7991,8009,8003,0,264,265,283,280,7967,7970,7988,7982,43,7991,7995,8010,8009,0,265,270,284,283,7970,7974,7989,7988,43,8003,8009,8011,8005,0,280,283,285,281,7982,7988,7990,7984,43,8009,8010,8012,8011,0,283,284,5,285,7988,7989,7991,7990,43,8013,8014,8015,8016,0,286,289,288,287,7992,7993,7994,7995,43,8014,8017,8018,8015,0,289,291,290,288,7993,7996,7997,7994,43,8016,8015,8019,8020,0,287,288,293,292,7995,7994,7998,7999,43,8015,8018,8021,8019,0,288,290,294,293,7994,7997,8000,7998,43,8013,8022,8023,8014,0,286,296,295,289,7992,8001,8002,7993,43,8022,8024,8025,8023,0,296,298,297,295,8001,8003,8004,8002,43,8014,8023,8026,8017,0,289,295,299,291,7993,8002,8005,7996,43,8023,8025,8027,8026,0,295,297,300,299,8002,8004,8006,8005,43,8013,8028,8029,8022,0,286,302,301,296,7992,8007,8008,8001,43,8028,8030,8031,8029,0,302,304,303,301,8007,8009,8010,8008,43,8022,8029,8032,8024,0,296,301,305,298,8001,8008,8011,8003,43,8029,8031,8033,8032,0,301,303,306,305,8008,8010,8012,8011,43,8013,8016,8034,8028,0,286,287,307,302,7992,7995,8013,8007,43,8016,8020,8035,8034,0,287,292,308,307,7995,7999,8014,8013,43,8028,8034,8036,8030,0,302,307,309,304,8007,8013,8015,8009,43,8034,8035,8037,8036,0,307,308,310,309,8013,8014,8016,8015,43,8038,8039,8040,8041,0,311,314,313,312,8017,8018,8019,8020,43,8039,8042,8043,8040,0,314,316,315,313,8018,8021,8022,8019,43,8041,8040,8044,8045,0,312,313,318,317,8020,8019,8023,8024,43,8040,8043,8046,8044,0,313,315,319,318,8019,8022,8025,8023,43,8038,8047,8048,8039,0,311,321,320,314,8017,8026,8027,8018,43,8047,8049,8050,8048,0,321,323,322,320,8026,8028,8029,8027,43,8039,8048,8051,8042,0,314,320,324,316,8018,8027,8030,8021,43,8048,8050,8052,8051,0,320,322,325,324,8027,8029,8031,8030,43,8038,8053,8054,8047,0,311,327,326,321,8017,8032,8033,8026,43,8053,7844,7852,8054,0,327,329,328,326,8032,7823,7831,8033,43,8047,8054,8055,8049,0,321,326,330,323,8026,8033,8034,8028,43,8054,7852,7853,8055,0,326,328,331,330,8033,7831,7832,8034,43,8038,8041,8056,8053,0,311,312,332,327,8017,8020,8035,8032,43,8041,8045,8057,8056,0,312,317,333,332,8020,8024,8036,8035,43,8053,8056,7845,7844,0,327,332,334,329,8032,8035,7824,7823,43,8056,8057,7847,7845,0,332,333,335,334,8035,8036,7826,7824,43,8058,8059,8060,8061,0,336,339,338,337,8037,8038,8039,8040,43,8059,8062,8063,8060,0,339,341,340,338,8038,8041,8042,8039,43,8061,8060,8064,8065,0,337,338,343,342,8040,8039,8043,8044,43,8060,8063,8066,8064,0,338,340,344,343,8039,8042,8045,8043,43,8058,8067,8068,8059,0,336,346,345,339,8037,8046,8047,8038,43,8067,8069,8070,8068,0,346,348,347,345,8046,8048,8049,8047,43,8059,8068,8071,8062,0,339,345,349,341,8038,8047,8050,8041,43,8068,8070,8072,8071,0,345,347,350,349,8047,8049,8051,8050,43,8058,8073,8074,8067,0,336,352,351,346,8037,8052,8053,8046,43,8073,8075,8076,8074,0,352,354,353,351,8052,8054,8055,8053,43,8067,8074,8077,8069,0,346,351,355,348,8046,8053,8056,8048,43,8074,8076,8078,8077,0,351,353,356,355,8053,8055,8057,8056,43,8058,8061,8079,8073,0,336,337,357,352,8037,8040,8058,8052,43,8061,8065,8080,8079,0,337,342,358,357,8040,8044,8059,8058,43,8073,8079,8081,8075,0,352,357,359,354,8052,8058,8060,8054,43,8079,8080,8082,8081,0,357,358,360,359,8058,8059,8061,8060,43,8083,8084,8085,8086,0,361,364,363,362,8062,8063,8064,8065,43,8084,8087,8088,8085,0,364,365,244,363,8063,8066,8067,8064,43,8086,8085,8089,8090,0,362,363,367,366,8065,8064,8068,8069,43,8085,8088,8012,8089,0,363,244,368,367,8064,8067,7991,8068,43,8083,8091,8092,8084,0,361,370,369,364,8062,8070,8071,8063,43,8091,8093,8094,8092,0,370,372,371,369,8070,8072,8073,8071,43,8084,8092,8095,8087,0,364,369,373,365,8063,8071,8074,8066,43,8092,8094,8096,8095,0,369,371,374,373,8071,8073,8075,8074,43,8083,8097,8098,8091,0,361,376,375,370,8062,8076,8077,8070,43,8097,8099,8100,8098,0,376,378,377,375,8076,8078,8079,8077,43,8091,8098,8101,8093,0,370,375,379,372,8070,8077,8080,8072,43,8098,8100,8046,8101,0,375,377,319,379,8077,8079,8025,8080,43,8083,8086,8102,8097,0,361,362,380,376,8062,8065,8081,8076,43,8086,8090,8103,8102,0,362,366,381,380,8065,8069,8082,8081,43,8097,8102,8104,8099,0,376,380,382,378,8076,8081,8083,8078,43,8102,8103,8033,8104,0,380,381,306,382,8081,8082,8012,8083,43,8105,8106,8107,8108,0,383,386,385,384,8084,8085,8086,8087,43,8106,8109,8110,8107,0,386,386,385,385,8085,8088,8089,8086,43,8108,8107,8111,8112,0,384,385,388,387,8087,8086,8090,8091,43,8107,8110,8113,8111,0,385,385,388,388,8086,8089,8092,8090,43,8105,8114,8115,8106,0,383,389,62,386,8084,8093,8094,8085,43,8114,8116,8117,8115,0,389,390,16,62,8093,8095,8096,8094,43,8106,8115,8118,8109,0,386,62,62,386,8085,8094,8097,8088,43,8115,8117,8119,8118,0,62,16,16,62,8094,8096,8098,8097,43,8105,8120,8121,8114,0,383,392,391,389,8084,8099,8100,8093,43,8120,8122,8123,8121,0,392,394,393,391,8099,8101,8102,8100,43,8114,8121,8124,8116,0,389,391,395,390,8093,8100,8103,8095,43,8121,8123,8125,8124,0,391,393,396,395,8100,8102,8104,8103,43,8105,8108,8126,8120,0,383,384,397,392,8084,8087,8105,8099,43,8108,8112,8127,8126,0,384,387,398,397,8087,8091,8106,8105,43,8120,8126,8128,8122,0,392,397,399,394,8099,8105,8107,8101,43,8126,8127,8129,8128,0,397,398,400,399,8105,8106,8108,8107,43,8130,8131,8132,8133,0,401,403,66,402,8109,8110,8111,8112,43,8131,8134,8135,8132,0,403,403,66,66,8110,8113,8114,8111,43,8133,8132,8136,8137,0,402,66,18,404,8112,8111,8115,8116,43,8132,8135,8138,8136,0,66,66,18,18,8111,8114,8117,8115,43,8130,8139,8140,8131,0,401,406,405,403,8109,8118,8119,8110,43,8139,8112,8111,8140,0,406,387,388,405,8118,8091,8090,8119,43,8131,8140,8141,8134,0,403,405,405,403,8110,8119,8120,8113,43,8140,8111,8113,8141,0,405,388,388,405,8119,8090,8092,8120,43,8130,8142,8143,8139,0,401,408,407,406,8109,8121,8122,8118,43,8142,8144,8145,8143,0,408,410,409,407,8121,8123,8124,8122,43,8139,8143,8127,8112,0,406,407,398,387,8118,8122,8106,8091,43,8143,8145,8129,8127,0,407,409,400,398,8122,8124,8108,8106,43,8130,8133,8146,8142,0,401,402,411,408,8109,8112,8125,8121,43,8133,8137,8147,8146,0,402,404,412,411,8112,8116,8126,8125,43,8142,8146,8148,8144,0,408,411,413,410,8121,8125,8127,8123,43,8146,8147,8149,8148,0,411,412,414,413,8125,8126,8128,8127,43,8150,8151,8152,8153,0,415,418,417,416,8129,8130,8131,8132,43,8151,8154,8155,8152,0,418,418,417,417,8130,8133,8134,8131,43,8153,8152,8156,8157,0,416,417,420,419,8132,8131,8135,8136,43,8152,8155,8158,8156,0,417,417,420,420,8131,8134,8137,8135,43,8150,8159,8160,8151,0,415,422,421,418,8129,8138,8139,8130,43,8159,8137,8136,8160,0,422,404,18,421,8138,8116,8115,8139,43,8151,8160,8161,8154,0,418,421,421,418,8130,8139,8140,8133,43,8160,8136,8138,8161,0,421,18,18,421,8139,8115,8117,8140,43,8150,8162,8163,8159,0,415,424,423,422,8129,8141,8142,8138,43,8162,8164,8165,8163,0,424,426,425,423,8141,8143,8144,8142,43,8159,8163,8147,8137,0,422,423,412,404,8138,8142,8126,8116,43,8163,8165,8149,8147,0,423,425,414,412,8142,8144,8128,8126,43,8150,8153,8166,8162,0,415,416,427,424,8129,8132,8145,8141,43,8153,8157,8167,8166,0,416,419,428,427,8132,8136,8146,8145,43,8162,8166,8168,8164,0,424,427,429,426,8141,8145,8147,8143,43,8166,8167,8169,8168,0,427,428,430,429,8145,8146,8148,8147,43,8170,8171,8172,8173,0,431,433,9,432,8149,8150,8151,8152,43,8171,8174,8175,8172,0,433,433,9,9,8150,8153,8154,8151,43,8173,8172,8176,8177,0,432,9,10,434,8152,8151,8155,8156,43,8172,8175,8178,8176,0,9,9,10,10,8151,8154,8157,8155,43,8170,8179,8180,8171,0,431,436,435,433,8149,8158,8159,8150,43,8179,8157,8156,8180,0,436,419,420,435,8158,8136,8135,8159,43,8171,8180,8181,8174,0,433,435,435,433,8150,8159,8160,8153,43,8180,8156,8158,8181,0,435,420,420,435,8159,8135,8137,8160,43,8170,8182,8183,8179,0,431,438,437,436,8149,8161,8162,8158,43,8182,8184,8185,8183,0,438,440,439,437,8161,8163,8164,8162,43,8179,8183,8167,8157,0,436,437,428,419,8158,8162,8146,8136,43,8183,8185,8169,8167,0,437,439,430,428,8162,8164,8148,8146,43,8170,8173,8186,8182,0,431,432,441,438,8149,8152,8165,8161,43,8173,8177,8187,8186,0,432,434,442,441,8152,8156,8166,8165,43,8182,8186,8188,8184,0,438,441,443,440,8161,8165,8167,8163,43,8186,8187,8189,8188,0,441,442,444,443,8165,8166,8168,8167,43,8190,8191,8192,8193,0,445,448,447,446,8169,8170,8171,8172,43,8191,8194,8195,8192,0,448,448,447,447,8170,8173,8174,8171,43,8193,8192,8196,8197,0,446,447,450,449,8172,8171,8175,8176,43,8192,8195,8198,8196,0,447,447,450,450,8171,8174,8177,8175,43,8190,8199,8200,8191,0,445,451,35,448,8169,8178,8179,8170,43,8199,8177,8176,8200,0,451,434,10,35,8178,8156,8155,8179,43,8191,8200,8201,8194,0,448,35,35,448,8170,8179,8180,8173,43,8200,8176,8178,8201,0,35,10,10,35,8179,8155,8157,8180,43,8190,8202,8203,8199,0,445,453,452,451,8169,8181,8182,8178,43,8202,8204,8205,8203,0,453,455,454,452,8181,8183,8184,8182,43,8199,8203,8187,8177,0,451,452,442,434,8178,8182,8166,8156,43,8203,8205,8189,8187,0,452,454,444,442,8182,8184,8168,8166,43,8190,8193,8206,8202,0,445,446,456,453,8169,8172,8185,8181,43,8193,8197,8207,8206,0,446,449,457,456,8172,8176,8186,8185,43,8202,8206,8208,8204,0,453,456,458,455,8181,8185,8187,8183,43,8206,8207,8209,8208,0,456,457,459,458,8185,8186,8188,8187,43,8210,8211,8212,8213,0,460,462,22,461,8189,8190,8191,8192,43,8211,8214,8215,8212,0,462,462,22,22,8190,8193,8194,8191,43,8213,8212,8216,8217,0,461,22,23,463,8192,8191,8195,8196,43,8212,8215,8218,8216,0,22,22,23,23,8191,8194,8197,8195,43,8210,8219,8220,8211,0,460,465,464,462,8189,8198,8199,8190,43,8219,8197,8196,8220,0,465,449,450,464,8198,8176,8175,8199,43,8211,8220,8221,8214,0,462,464,464,462,8190,8199,8200,8193,43,8220,8196,8198,8221,0,464,450,450,464,8199,8175,8177,8200,43,8210,8222,8223,8219,0,460,467,466,465,8189,8201,8202,8198,43,8222,8224,8225,8223,0,467,469,468,466,8201,8203,8204,8202,43,8219,8223,8207,8197,0,465,466,457,449,8198,8202,8186,8176,43,8223,8225,8209,8207,0,466,468,459,457,8202,8204,8188,8186,43,8210,8213,8226,8222,0,460,461,470,467,8189,8192,8205,8201,43,8213,8217,8227,8226,0,461,463,471,470,8192,8196,8206,8205,43,8222,8226,8228,8224,0,467,470,472,469,8201,8205,8207,8203,43,8226,8227,8229,8228,0,470,471,473,472,8205,8206,8208,8207,43,8230,8231,8232,8233,0,474,477,476,475,8209,8210,8211,8212,43,8231,8234,8235,8232,0,477,477,476,476,8210,8213,8214,8211,43,8233,8232,8236,8237,0,475,476,479,478,8212,8211,8215,8216,43,8232,8235,8238,8236,0,476,476,479,479,8211,8214,8217,8215,43,8230,8239,8240,8231,0,474,480,57,477,8209,8218,8219,8210,43,8239,8217,8216,8240,0,480,463,23,57,8218,8196,8195,8219,43,8231,8240,8241,8234,0,477,57,57,477,8210,8219,8220,8213,43,8240,8216,8218,8241,0,57,23,23,57,8219,8195,8197,8220,43,8230,8242,8243,8239,0,474,482,481,480,8209,8221,8222,8218,43,8242,8244,8245,8243,0,482,484,483,481,8221,8223,8224,8222,43,8239,8243,8227,8217,0,480,481,471,463,8218,8222,8206,8196,43,8243,8245,8229,8227,0,481,483,473,471,8222,8224,8208,8206,43,8230,8233,8246,8242,0,474,475,485,482,8209,8212,8225,8221,43,8233,8237,8247,8246,0,475,478,486,485,8212,8216,8226,8225,43,8242,8246,8248,8244,0,482,485,487,484,8221,8225,8227,8223,43,8246,8247,8249,8248,0,485,486,488,487,8225,8226,8228,8227,43,8250,8251,8252,8253,0,489,491,15,490,8229,8230,8231,8232,43,8251,8254,8255,8252,0,491,491,15,15,8230,8233,8234,8231,43,8253,8252,8117,8116,0,490,15,16,390,8232,8231,8096,8095,43,8252,8255,8119,8117,0,15,15,16,16,8231,8234,8098,8096,43,8250,8256,8257,8251,0,489,493,492,491,8229,8235,8236,8230,43,8256,8237,8236,8257,0,493,478,479,492,8235,8216,8215,8236,43,8251,8257,8258,8254,0,491,492,492,491,8230,8236,8237,8233,43,8257,8236,8238,8258,0,492,479,479,492,8236,8215,8217,8237,43,8250,8259,8260,8256,0,489,495,494,493,8229,8238,8239,8235,43,8259,8261,8262,8260,0,495,497,496,494,8238,8240,8241,8239,43,8256,8260,8247,8237,0,493,494,486,478,8235,8239,8226,8216,43,8260,8262,8249,8247,0,494,496,488,486,8239,8241,8228,8226,43,8250,8253,8263,8259,0,489,490,498,495,8229,8232,8242,8238,43,8253,8116,8124,8263,0,490,390,395,498,8232,8095,8103,8242,43,8259,8263,8264,8261,0,495,498,499,497,8238,8242,8243,8240,43,8263,8124,8125,8264,0,498,395,396,499,8242,8103,8104,8243,43,8265,8266,8267,8268,0,386,62,62,386,8244,8245,8246,8247,43,8266,8269,8270,8267,0,62,16,16,62,8245,8248,8249,8246,43,8268,8267,8271,8272,0,386,62,62,386,8247,8246,8250,8251,43,8267,8270,8273,8271,0,62,16,16,62,8246,8249,8252,8250,43,8265,8274,8275,8266,0,386,386,62,62,8244,8253,8254,8245,43,8274,8276,8277,8275,0,386,386,62,62,8253,8255,8256,8254,43,8266,8275,8278,8269,0,62,62,16,16,8245,8254,8257,8248,43,8275,8277,8279,8278,0,62,62,16,16,8254,8256,8258,8257,43,8265,8280,8281,8274,0,386,385,385,386,8244,8259,8260,8253,43,8280,8282,8283,8281,0,385,388,388,385,8259,8261,8262,8260,43,8274,8281,8284,8276,0,386,385,385,386,8253,8260,8263,8255,43,8281,8283,8285,8284,0,385,388,388,385,8260,8262,8264,8263,43,8265,8268,8286,8280,0,386,386,385,385,8244,8247,8265,8259,43,8268,8272,8287,8286,0,386,386,385,385,8247,8251,8266,8265,43,8280,8286,8288,8282,0,385,385,388,388,8259,8265,8267,8261,43,8286,8287,8289,8288,0,385,385,388,388,8265,8266,8268,8267,43,8290,8291,8292,8293,0,403,403,66,66,8269,8270,8271,8272,43,8291,8294,8295,8292,0,403,403,66,66,8270,8273,8274,8271,43,8293,8292,8296,8297,0,66,66,18,18,8272,8271,8275,8276,43,8292,8295,8298,8296,0,66,66,18,18,8271,8274,8277,8275,43,8290,8299,8300,8291,0,403,405,405,403,8269,8278,8279,8270,43,8299,8282,8288,8300,0,405,388,388,405,8278,8261,8267,8279,43,8291,8300,8301,8294,0,403,405,405,403,8270,8279,8280,8273,43,8300,8288,8289,8301,0,405,388,388,405,8279,8267,8268,8280,43,8290,8302,8303,8299,0,403,403,405,405,8269,8281,8282,8278,43,8302,8304,8305,8303,0,403,403,405,405,8281,8283,8284,8282,43,8299,8303,8283,8282,0,405,405,388,388,8278,8282,8262,8261,43,8303,8305,8285,8283,0,405,405,388,388,8282,8284,8264,8262,43,8290,8293,8306,8302,0,403,66,66,403,8269,8272,8285,8281,43,8293,8297,8307,8306,0,66,18,18,66,8272,8276,8286,8285,43,8302,8306,8308,8304,0,403,66,66,403,8281,8285,8287,8283,43,8306,8307,8309,8308,0,66,18,18,66,8285,8286,8288,8287,43,8310,8311,8312,8313,0,418,418,417,417,8289,8290,8291,8292,43,8311,8314,8315,8312,0,418,418,417,417,8290,8293,8294,8291,43,8313,8312,8316,8317,0,417,417,420,420,8292,8291,8295,8296,43,8312,8315,8318,8316,0,417,417,420,420,8291,8294,8297,8295,43,8310,8319,8320,8311,0,418,421,421,418,8289,8298,8299,8290,43,8319,8297,8296,8320,0,421,18,18,421,8298,8276,8275,8299,43,8311,8320,8321,8314,0,418,421,421,418,8290,8299,8300,8293,43,8320,8296,8298,8321,0,421,18,18,421,8299,8275,8277,8300,43,8310,8322,8323,8319,0,418,418,421,421,8289,8301,8302,8298,43,8322,8324,8325,8323,0,418,418,421,421,8301,8303,8304,8302,43,8319,8323,8307,8297,0,421,421,18,18,8298,8302,8286,8276,43,8323,8325,8309,8307,0,421,421,18,18,8302,8304,8288,8286,43,8310,8313,8326,8322,0,418,417,417,418,8289,8292,8305,8301,43,8313,8317,8327,8326,0,417,420,420,417,8292,8296,8306,8305,43,8322,8326,8328,8324,0,418,417,417,418,8301,8305,8307,8303,43,8326,8327,8329,8328,0,417,420,420,417,8305,8306,8308,8307,43,8330,8331,8332,8333,0,433,435,435,433,8309,8310,8311,8312,43,8331,8317,8316,8332,0,435,420,420,435,8310,8296,8295,8311,43,8333,8332,8334,8335,0,433,435,435,433,8312,8311,8313,8314,43,8332,8316,8318,8334,0,435,420,420,435,8311,8295,8297,8313,43,8330,8336,8337,8331,0,433,433,435,435,8309,8315,8316,8310,43,8336,8338,8339,8337,0,433,433,435,435,8315,8317,8318,8316,43,8331,8337,8327,8317,0,435,435,420,420,8310,8316,8306,8296,43,8337,8339,8329,8327,0,435,435,420,420,8316,8318,8308,8306,43,8330,8340,8341,8336,0,433,9,9,433,8309,8319,8320,8315,43,8340,8342,8343,8341,0,9,10,10,9,8319,8321,8322,8320,43,8336,8341,8344,8338,0,433,9,9,433,8315,8320,8323,8317,43,8341,8343,8345,8344,0,9,10,10,9,8320,8322,8324,8323,43,8330,8333,8346,8340,0,433,433,9,9,8309,8312,8325,8319,43,8333,8335,8347,8346,0,433,433,9,9,8312,8314,8326,8325,43,8340,8346,8348,8342,0,9,9,10,10,8319,8325,8327,8321,43,8346,8347,8349,8348,0,9,9,10,10,8325,8326,8328,8327,43,8350,8351,8352,8353,0,448,35,35,448,8329,8330,8331,8332,43,8351,8342,8348,8352,0,35,10,10,35,8330,8321,8327,8331,43,8353,8352,8354,8355,0,448,35,35,448,8332,8331,8333,8334,43,8352,8348,8349,8354,0,35,10,10,35,8331,8327,8328,8333,43,8350,8356,8357,8351,0,448,448,35,35,8329,8335,8336,8330,43,8356,8358,8359,8357,0,448,448,35,35,8335,8337,8338,8336,43,8351,8357,8343,8342,0,35,35,10,10,8330,8336,8322,8321,43,8357,8359,8345,8343,0,35,35,10,10,8336,8338,8324,8322,43,8350,8360,8361,8356,0,448,447,447,448,8329,8339,8340,8335,43,8360,8362,8363,8361,0,447,450,450,447,8339,8341,8342,8340,43,8356,8361,8364,8358,0,448,447,447,448,8335,8340,8343,8337,43,8361,8363,8365,8364,0,447,450,450,447,8340,8342,8344,8343,43,8350,8353,8366,8360,0,448,448,447,447,8329,8332,8345,8339,43,8353,8355,8367,8366,0,448,448,447,447,8332,8334,8346,8345,43,8360,8366,8368,8362,0,447,447,450,450,8339,8345,8347,8341,43,8366,8367,8369,8368,0,447,447,450,450,8345,8346,8348,8347,43,8370,8371,8372,8373,0,462,464,464,462,8349,8350,8351,8352,43,8371,8362,8368,8372,0,464,450,450,464,8350,8341,8347,8351,43,8373,8372,8374,8375,0,462,464,464,462,8352,8351,8353,8354,43,8372,8368,8369,8374,0,464,450,450,464,8351,8347,8348,8353,43,8370,8376,8377,8371,0,462,462,464,464,8349,8355,8356,8350,43,8376,8378,8379,8377,0,462,462,464,464,8355,8357,8358,8356,43,8371,8377,8363,8362,0,464,464,450,450,8350,8356,8342,8341,43,8377,8379,8365,8363,0,464,464,450,450,8356,8358,8344,8342,43,8370,8380,8381,8376,0,462,22,22,462,8349,8359,8360,8355,43,8380,8382,8383,8381,0,22,23,23,22,8359,8361,8362,8360,43,8376,8381,8384,8378,0,462,22,22,462,8355,8360,8363,8357,43,8381,8383,8385,8384,0,22,23,23,22,8360,8362,8364,8363,43,8370,8373,8386,8380,0,462,462,22,22,8349,8352,8365,8359,43,8373,8375,8387,8386,0,462,462,22,22,8352,8354,8366,8365,43,8380,8386,8388,8382,0,22,22,23,23,8359,8365,8367,8361,43,8386,8387,8389,8388,0,22,22,23,23,8365,8366,8368,8367,43,8390,8391,8392,8393,0,477,477,476,476,8369,8370,8371,8372,43,8391,8394,8395,8392,0,477,477,476,476,8370,8373,8374,8371,43,8393,8392,8396,8397,0,476,476,479,479,8372,8371,8375,8376,43,8392,8395,8398,8396,0,476,476,479,479,8371,8374,8377,8375,43,8390,8399,8400,8391,0,477,57,57,477,8369,8378,8379,8370,43,8399,8382,8388,8400,0,57,23,23,57,8378,8361,8367,8379,43,8391,8400,8401,8394,0,477,57,57,477,8370,8379,8380,8373,43,8400,8388,8389,8401,0,57,23,23,57,8379,8367,8368,8380,43,8390,8402,8403,8399,0,477,477,57,57,8369,8381,8382,8378,43,8402,8404,8405,8403,0,477,477,57,57,8381,8383,8384,8382,43,8399,8403,8383,8382,0,57,57,23,23,8378,8382,8362,8361,43,8403,8405,8385,8383,0,57,57,23,23,8382,8384,8364,8362,43,8390,8393,8406,8402,0,477,476,476,477,8369,8372,8385,8381,43,8393,8397,8407,8406,0,476,479,479,476,8372,8376,8386,8385,43,8402,8406,8408,8404,0,477,476,476,477,8381,8385,8387,8383,43,8406,8407,8409,8408,0,476,479,479,476,8385,8386,8388,8387,43,8410,8411,8412,8413,0,491,491,15,15,8389,8390,8391,8392,43,8411,8414,8415,8412,0,491,491,15,15,8390,8393,8394,8391,43,8413,8412,8270,8269,0,15,15,16,16,8392,8391,8249,8248,43,8412,8415,8273,8270,0,15,15,16,16,8391,8394,8252,8249,43,8410,8416,8417,8411,0,491,492,492,491,8389,8395,8396,8390,43,8416,8397,8396,8417,0,492,479,479,492,8395,8376,8375,8396,43,8411,8417,8418,8414,0,491,492,492,491,8390,8396,8397,8393,43,8417,8396,8398,8418,0,492,479,479,492,8396,8375,8377,8397,43,8410,8419,8420,8416,0,491,491,492,492,8389,8398,8399,8395,43,8419,8421,8422,8420,0,491,491,492,492,8398,8400,8401,8399,43,8416,8420,8407,8397,0,492,492,479,479,8395,8399,8386,8376,43,8420,8422,8409,8407,0,492,492,479,479,8399,8401,8388,8386,43,8410,8413,8423,8419,0,491,15,15,491,8389,8392,8402,8398,43,8413,8269,8278,8423,0,15,16,16,15,8392,8248,8257,8402,43,8419,8423,8424,8421,0,491,15,15,491,8398,8402,8403,8400,43,8423,8278,8279,8424,0,15,16,16,15,8402,8257,8258,8403,43,8425,8426,8427,8428,0,386,62,62,386,8404,8405,8406,8407,43,8426,8429,8430,8427,0,62,16,16,62,8405,8408,8409,8406,43,8428,8427,8431,8432,0,386,62,62,386,8407,8406,8410,8411,43,8427,8430,8433,8431,0,62,16,16,62,8406,8409,8412,8410,43,8425,8434,8435,8426,0,386,386,62,62,8404,8413,8414,8405,43,8434,8436,8437,8435,0,386,386,62,62,8413,8415,8416,8414,43,8426,8435,8438,8429,0,62,62,16,16,8405,8414,8417,8408,43,8435,8437,8439,8438,0,62,62,16,16,8414,8416,8418,8417,43,8425,8440,8441,8434,0,386,385,385,386,8404,8419,8420,8413,43,8440,8442,8443,8441,0,385,388,388,385,8419,8421,8422,8420,43,8434,8441,8444,8436,0,386,385,385,386,8413,8420,8423,8415,43,8441,8443,8445,8444,0,385,388,388,385,8420,8422,8424,8423,43,8425,8428,8446,8440,0,386,386,385,385,8404,8407,8425,8419,43,8428,8432,8447,8446,0,386,386,385,385,8407,8411,8426,8425,43,8440,8446,8448,8442,0,385,385,388,388,8419,8425,8427,8421,43,8446,8447,8449,8448,0,385,385,388,388,8425,8426,8428,8427,43,8450,8451,8452,8453,0,403,403,66,66,8429,8430,8431,8432,43,8451,8454,8455,8452,0,403,403,66,66,8430,8433,8434,8431,43,8453,8452,8456,8457,0,66,66,18,18,8432,8431,8435,8436,43,8452,8455,8458,8456,0,66,66,18,18,8431,8434,8437,8435,43,8450,8459,8460,8451,0,403,405,405,403,8429,8438,8439,8430,43,8459,8442,8448,8460,0,405,388,388,405,8438,8421,8427,8439,43,8451,8460,8461,8454,0,403,405,405,403,8430,8439,8440,8433,43,8460,8448,8449,8461,0,405,388,388,405,8439,8427,8428,8440,43,8450,8462,8463,8459,0,403,403,405,405,8429,8441,8442,8438,43,8462,8464,8465,8463,0,403,403,405,405,8441,8443,8444,8442,43,8459,8463,8443,8442,0,405,405,388,388,8438,8442,8422,8421,43,8463,8465,8445,8443,0,405,405,388,388,8442,8444,8424,8422,43,8450,8453,8466,8462,0,403,66,66,403,8429,8432,8445,8441,43,8453,8457,8467,8466,0,66,18,18,66,8432,8436,8446,8445,43,8462,8466,8468,8464,0,403,66,66,403,8441,8445,8447,8443,43,8466,8467,8469,8468,0,66,18,18,66,8445,8446,8448,8447,43,8470,8471,8472,8473,0,418,418,417,417,8449,8450,8451,8452,43,8471,8474,8475,8472,0,418,418,417,417,8450,8453,8454,8451,43,8473,8472,8476,8477,0,417,417,420,420,8452,8451,8455,8456,43,8472,8475,8478,8476,0,417,417,420,420,8451,8454,8457,8455,43,8470,8479,8480,8471,0,418,421,421,418,8449,8458,8459,8450,43,8479,8457,8456,8480,0,421,18,18,421,8458,8436,8435,8459,43,8471,8480,8481,8474,0,418,421,421,418,8450,8459,8460,8453,43,8480,8456,8458,8481,0,421,18,18,421,8459,8435,8437,8460,43,8470,8482,8483,8479,0,418,418,421,421,8449,8461,8462,8458,43,8482,8484,8485,8483,0,418,418,421,421,8461,8463,8464,8462,43,8479,8483,8467,8457,0,421,421,18,18,8458,8462,8446,8436,43,8483,8485,8469,8467,0,421,421,18,18,8462,8464,8448,8446,43,8470,8473,8486,8482,0,418,417,417,418,8449,8452,8465,8461,43,8473,8477,8487,8486,0,417,420,420,417,8452,8456,8466,8465,43,8482,8486,8488,8484,0,418,417,417,418,8461,8465,8467,8463,43,8486,8487,8489,8488,0,417,420,420,417,8465,8466,8468,8467,43,8490,8491,8492,8493,0,433,435,435,433,8469,8470,8471,8472,43,8491,8477,8476,8492,0,435,420,420,435,8470,8456,8455,8471,43,8493,8492,8494,8495,0,433,435,435,433,8472,8471,8473,8474,43,8492,8476,8478,8494,0,435,420,420,435,8471,8455,8457,8473,43,8490,8496,8497,8491,0,433,433,435,435,8469,8475,8476,8470,43,8496,8498,8499,8497,0,433,433,435,435,8475,8477,8478,8476,43,8491,8497,8487,8477,0,435,435,420,420,8470,8476,8466,8456,43,8497,8499,8489,8487,0,435,435,420,420,8476,8478,8468,8466,43,8490,8500,8501,8496,0,433,9,9,433,8469,8479,8480,8475,43,8500,8502,8503,8501,0,9,10,10,9,8479,8481,8482,8480,43,8496,8501,8504,8498,0,433,9,9,433,8475,8480,8483,8477,43,8501,8503,8505,8504,0,9,10,10,9,8480,8482,8484,8483,43,8490,8493,8506,8500,0,433,433,9,9,8469,8472,8485,8479,43,8493,8495,8507,8506,0,433,433,9,9,8472,8474,8486,8485,43,8500,8506,8508,8502,0,9,9,10,10,8479,8485,8487,8481,43,8506,8507,8509,8508,0,9,9,10,10,8485,8486,8488,8487,43,8510,8511,8512,8513,0,448,35,35,448,8489,8490,8491,8492,43,8511,8502,8508,8512,0,35,10,10,35,8490,8481,8487,8491,43,8513,8512,8514,8515,0,448,35,35,448,8492,8491,8493,8494,43,8512,8508,8509,8514,0,35,10,10,35,8491,8487,8488,8493,43,8510,8516,8517,8511,0,448,448,35,35,8489,8495,8496,8490,43,8516,8518,8519,8517,0,448,448,35,35,8495,8497,8498,8496,43,8511,8517,8503,8502,0,35,35,10,10,8490,8496,8482,8481,43,8517,8519,8505,8503,0,35,35,10,10,8496,8498,8484,8482,43,8510,8520,8521,8516,0,448,447,447,448,8489,8499,8500,8495,43,8520,8522,8523,8521,0,447,450,450,447,8499,8501,8502,8500,43,8516,8521,8524,8518,0,448,447,447,448,8495,8500,8503,8497,43,8521,8523,8525,8524,0,447,450,450,447,8500,8502,8504,8503,43,8510,8513,8526,8520,0,448,448,447,447,8489,8492,8505,8499,43,8513,8515,8527,8526,0,448,448,447,447,8492,8494,8506,8505,43,8520,8526,8528,8522,0,447,447,450,450,8499,8505,8507,8501,43,8526,8527,8529,8528,0,447,447,450,450,8505,8506,8508,8507,43,8530,8531,8532,8533,0,462,464,464,462,8509,8510,8511,8512,43,8531,8522,8528,8532,0,464,450,450,464,8510,8501,8507,8511,43,8533,8532,8534,8535,0,462,464,464,462,8512,8511,8513,8514,43,8532,8528,8529,8534,0,464,450,450,464,8511,8507,8508,8513,43,8530,8536,8537,8531,0,462,462,464,464,8509,8515,8516,8510,43,8536,8538,8539,8537,0,462,462,464,464,8515,8517,8518,8516,43,8531,8537,8523,8522,0,464,464,450,450,8510,8516,8502,8501,43,8537,8539,8525,8523,0,464,464,450,450,8516,8518,8504,8502,43,8530,8540,8541,8536,0,462,22,22,462,8509,8519,8520,8515,43,8540,8542,8543,8541,0,22,23,23,22,8519,8521,8522,8520,43,8536,8541,8544,8538,0,462,22,22,462,8515,8520,8523,8517,43,8541,8543,8545,8544,0,22,23,23,22,8520,8522,8524,8523,43,8530,8533,8546,8540,0,462,462,22,22,8509,8512,8525,8519,43,8533,8535,8547,8546,0,462,462,22,22,8512,8514,8526,8525,43,8540,8546,8548,8542,0,22,22,23,23,8519,8525,8527,8521,43,8546,8547,8549,8548,0,22,22,23,23,8525,8526,8528,8527,43,8550,8551,8552,8553,0,477,57,57,477,8529,8530,8531,8532,43,8551,8542,8548,8552,0,57,23,23,57,8530,8521,8527,8531,43,8553,8552,8554,8555,0,477,57,57,477,8532,8531,8533,8534,43,8552,8548,8549,8554,0,57,23,23,57,8531,8527,8528,8533,43,8550,8556,8557,8551,0,477,477,57,57,8529,8535,8536,8530,43,8556,8558,8559,8557,0,477,477,57,57,8535,8537,8538,8536,43,8551,8557,8543,8542,0,57,57,23,23,8530,8536,8522,8521,43,8557,8559,8545,8543,0,57,57,23,23,8536,8538,8524,8522,43,8550,8560,8561,8556,0,477,476,476,477,8529,8539,8540,8535,43,8560,8562,8563,8561,0,476,479,479,476,8539,8541,8542,8540,43,8556,8561,8564,8558,0,477,476,476,477,8535,8540,8543,8537,43,8561,8563,8565,8564,0,476,479,479,476,8540,8542,8544,8543,43,8550,8553,8566,8560,0,477,477,476,476,8529,8532,8545,8539,43,8553,8555,8567,8566,0,477,477,476,476,8532,8534,8546,8545,43,8560,8566,8568,8562,0,476,476,479,479,8539,8545,8547,8541,43,8566,8567,8569,8568,0,476,476,479,479,8545,8546,8548,8547,43,8570,8571,8572,8573,0,491,491,15,15,8549,8550,8551,8552,43,8571,8574,8575,8572,0,491,491,15,15,8550,8553,8554,8551,43,8573,8572,8430,8429,0,15,15,16,16,8552,8551,8409,8408,43,8572,8575,8433,8430,0,15,15,16,16,8551,8554,8412,8409,43,8570,8576,8577,8571,0,491,492,492,491,8549,8555,8556,8550,43,8576,8562,8568,8577,0,492,479,479,492,8555,8541,8547,8556,43,8571,8577,8578,8574,0,491,492,492,491,8550,8556,8557,8553,43,8577,8568,8569,8578,0,492,479,479,492,8556,8547,8548,8557,43,8570,8579,8580,8576,0,491,491,492,492,8549,8558,8559,8555,43,8579,8581,8582,8580,0,491,491,492,492,8558,8560,8561,8559,43,8576,8580,8563,8562,0,492,492,479,479,8555,8559,8542,8541,43,8580,8582,8565,8563,0,492,492,479,479,8559,8561,8544,8542,43,8570,8573,8583,8579,0,491,15,15,491,8549,8552,8562,8558,43,8573,8429,8438,8583,0,15,16,16,15,8552,8408,8417,8562,43,8579,8583,8584,8581,0,491,15,15,491,8558,8562,8563,8560,43,8583,8438,8439,8584,0,15,16,16,15,8562,8417,8418,8563,43,8585,8586,8587,8588,0,386,62,62,386,8564,8565,8566,8567,43,8586,8589,8590,8587,0,62,16,16,62,8565,8568,8569,8566,43,8588,8587,8591,8592,0,386,62,62,386,8567,8566,8570,8571,43,8587,8590,8593,8591,0,62,16,16,62,8566,8569,8572,8570,43,8585,8594,8595,8586,0,386,386,62,62,8564,8573,8574,8565,43,8594,8432,8431,8595,0,386,386,62,62,8573,8411,8410,8574,43,8586,8595,8596,8589,0,62,62,16,16,8565,8574,8575,8568,43,8595,8431,8433,8596,0,62,62,16,16,8574,8410,8412,8575,43,8585,8597,8598,8594,0,386,385,385,386,8564,8576,8577,8573,43,8597,8599,8600,8598,0,385,388,388,385,8576,8578,8579,8577,43,8594,8598,8447,8432,0,386,385,385,386,8573,8577,8426,8411,43,8598,8600,8449,8447,0,385,388,388,385,8577,8579,8428,8426,43,8585,8588,8601,8597,0,386,386,385,385,8564,8567,8580,8576,43,8588,8592,8602,8601,0,386,386,385,385,8567,8571,8581,8580,43,8597,8601,8603,8599,0,385,385,388,388,8576,8580,8582,8578,43,8601,8602,8604,8603,0,385,385,388,388,8580,8581,8583,8582,43,8605,8606,8607,8608,0,403,403,405,405,8584,8585,8586,8587,43,8606,8454,8461,8607,0,403,403,405,405,8585,8433,8440,8586,43,8608,8607,8600,8599,0,405,405,388,388,8587,8586,8579,8578,43,8607,8461,8449,8600,0,405,405,388,388,8586,8440,8428,8579,43,8605,8609,8610,8606,0,403,66,66,403,8584,8588,8589,8585,43,8609,8611,8612,8610,0,66,18,18,66,8588,8590,8591,8589,43,8606,8610,8455,8454,0,403,66,66,403,8585,8589,8434,8433,43,8610,8612,8458,8455,0,66,18,18,66,8589,8591,8437,8434,43,8605,8613,8614,8609,0,403,403,66,66,8584,8592,8593,8588,43,8613,8615,8616,8614,0,403,403,66,66,8592,8594,8595,8593,43,8609,8614,8617,8611,0,66,66,18,18,8588,8593,8596,8590,43,8614,8616,8618,8617,0,66,66,18,18,8593,8595,8597,8596,43,8605,8608,8619,8613,0,403,405,405,403,8584,8587,8598,8592,43,8608,8599,8603,8619,0,405,388,388,405,8587,8578,8582,8598,43,8613,8619,8620,8615,0,403,405,405,403,8592,8598,8599,8594,43,8619,8603,8604,8620,0,405,388,388,405,8598,8582,8583,8599,43,8621,8622,8623,8624,0,418,418,421,421,8600,8601,8602,8603,43,8622,8474,8481,8623,0,418,418,421,421,8601,8453,8460,8602,43,8624,8623,8612,8611,0,421,421,18,18,8603,8602,8591,8590,43,8623,8481,8458,8612,0,421,421,18,18,8602,8460,8437,8591,43,8621,8625,8626,8622,0,418,417,417,418,8600,8604,8605,8601,43,8625,8627,8628,8626,0,417,420,420,417,8604,8606,8607,8605,43,8622,8626,8475,8474,0,418,417,417,418,8601,8605,8454,8453,43,8626,8628,8478,8475,0,417,420,420,417,8605,8607,8457,8454,43,8621,8629,8630,8625,0,418,418,417,417,8600,8608,8609,8604,43,8629,8631,8632,8630,0,418,418,417,417,8608,8610,8611,8609,43,8625,8630,8633,8627,0,417,417,420,420,8604,8609,8612,8606,43,8630,8632,8634,8633,0,417,417,420,420,8609,8611,8613,8612,43,8621,8624,8635,8629,0,418,421,421,418,8600,8603,8614,8608,43,8624,8611,8617,8635,0,421,18,18,421,8603,8590,8596,8614,43,8629,8635,8636,8631,0,418,421,421,418,8608,8614,8615,8610,43,8635,8617,8618,8636,0,421,18,18,421,8614,8596,8597,8615,43,8637,8638,8639,8640,0,433,435,435,433,8616,8617,8618,8619,43,8638,8627,8633,8639,0,435,420,420,435,8617,8606,8612,8618,43,8640,8639,8641,8642,0,433,435,435,433,8619,8618,8620,8621,43,8639,8633,8634,8641,0,435,420,420,435,8618,8612,8613,8620,43,8637,8643,8644,8638,0,433,433,435,435,8616,8622,8623,8617,43,8643,8495,8494,8644,0,433,433,435,435,8622,8474,8473,8623,43,8638,8644,8628,8627,0,435,435,420,420,8617,8623,8607,8606,43,8644,8494,8478,8628,0,435,435,420,420,8623,8473,8457,8607,43,8637,8645,8646,8643,0,433,9,9,433,8616,8624,8625,8622,43,8645,8647,8648,8646,0,9,10,10,9,8624,8626,8627,8625,43,8643,8646,8507,8495,0,433,9,9,433,8622,8625,8486,8474,43,8646,8648,8509,8507,0,9,10,10,9,8625,8627,8488,8486,43,8637,8640,8649,8645,0,433,433,9,9,8616,8619,8628,8624,43,8640,8642,8650,8649,0,433,433,9,9,8619,8621,8629,8628,43,8645,8649,8651,8647,0,9,9,10,10,8624,8628,8630,8626,43,8649,8650,8652,8651,0,9,9,10,10,8628,8629,8631,8630,43,8653,8654,8655,8656,0,448,35,35,448,8632,8633,8634,8635,43,8654,8647,8651,8655,0,35,10,10,35,8633,8626,8630,8634,43,8656,8655,8657,8658,0,448,35,35,448,8635,8634,8636,8637,43,8655,8651,8652,8657,0,35,10,10,35,8634,8630,8631,8636,43,8653,8659,8660,8654,0,448,448,35,35,8632,8638,8639,8633,43,8659,8515,8514,8660,0,448,448,35,35,8638,8494,8493,8639,43,8654,8660,8648,8647,0,35,35,10,10,8633,8639,8627,8626,43,8660,8514,8509,8648,0,35,35,10,10,8639,8493,8488,8627,43,8653,8661,8662,8659,0,448,447,447,448,8632,8640,8641,8638,43,8661,8663,8664,8662,0,447,450,450,447,8640,8642,8643,8641,43,8659,8662,8527,8515,0,448,447,447,448,8638,8641,8506,8494,43,8662,8664,8529,8527,0,447,450,450,447,8641,8643,8508,8506,43,8653,8656,8665,8661,0,448,448,447,447,8632,8635,8644,8640,43,8656,8658,8666,8665,0,448,448,447,447,8635,8637,8645,8644,43,8661,8665,8667,8663,0,447,447,450,450,8640,8644,8646,8642,43,8665,8666,8668,8667,0,447,447,450,450,8644,8645,8647,8646,43,8669,8670,8671,8672,0,462,464,464,462,8648,8649,8650,8651,43,8670,8663,8667,8671,0,464,450,450,464,8649,8642,8646,8650,43,8672,8671,8673,8674,0,462,464,464,462,8651,8650,8652,8653,43,8671,8667,8668,8673,0,464,450,450,464,8650,8646,8647,8652,43,8669,8675,8676,8670,0,462,462,464,464,8648,8654,8655,8649,43,8675,8535,8534,8676,0,462,462,464,464,8654,8514,8513,8655,43,8670,8676,8664,8663,0,464,464,450,450,8649,8655,8643,8642,43,8676,8534,8529,8664,0,464,464,450,450,8655,8513,8508,8643,43,8669,8677,8678,8675,0,462,22,22,462,8648,8656,8657,8654,43,8677,8679,8680,8678,0,22,23,23,22,8656,8658,8659,8657,43,8675,8678,8547,8535,0,462,22,22,462,8654,8657,8526,8514,43,8678,8680,8549,8547,0,22,23,23,22,8657,8659,8528,8526,43,8669,8672,8681,8677,0,462,462,22,22,8648,8651,8660,8656,43,8672,8674,8682,8681,0,462,462,22,22,8651,8653,8661,8660,43,8677,8681,8683,8679,0,22,22,23,23,8656,8660,8662,8658,43,8681,8682,8684,8683,0,22,22,23,23,8660,8661,8663,8662,43,8685,8686,8687,8688,0,477,477,57,57,8664,8665,8666,8667,43,8686,8555,8554,8687,0,477,477,57,57,8665,8534,8533,8666,43,8688,8687,8680,8679,0,57,57,23,23,8667,8666,8659,8658,43,8687,8554,8549,8680,0,57,57,23,23,8666,8533,8528,8659,43,8685,8689,8690,8686,0,477,476,476,477,8664,8668,8669,8665,43,8689,8691,8692,8690,0,476,479,479,476,8668,8670,8671,8669,43,8686,8690,8567,8555,0,477,476,476,477,8665,8669,8546,8534,43,8690,8692,8569,8567,0,476,479,479,476,8669,8671,8548,8546,43,8685,8693,8694,8689,0,477,477,476,476,8664,8672,8673,8668,43,8693,8695,8696,8694,0,477,477,476,476,8672,8674,8675,8673,43,8689,8694,8697,8691,0,476,476,479,479,8668,8673,8676,8670,43,8694,8696,8698,8697,0,476,476,479,479,8673,8675,8677,8676,43,8685,8688,8699,8693,0,477,57,57,477,8664,8667,8678,8672,43,8688,8679,8683,8699,0,57,23,23,57,8667,8658,8662,8678,43,8693,8699,8700,8695,0,477,57,57,477,8672,8678,8679,8674,43,8699,8683,8684,8700,0,57,23,23,57,8678,8662,8663,8679,43,8701,8702,8703,8704,0,491,491,492,492,8680,8681,8682,8683,43,8702,8574,8578,8703,0,491,491,492,492,8681,8553,8557,8682,43,8704,8703,8692,8691,0,492,492,479,479,8683,8682,8671,8670,43,8703,8578,8569,8692,0,492,492,479,479,8682,8557,8548,8671,43,8701,8705,8706,8702,0,491,15,15,491,8680,8684,8685,8681,43,8705,8589,8596,8706,0,15,16,16,15,8684,8568,8575,8685,43,8702,8706,8575,8574,0,491,15,15,491,8681,8685,8554,8553,43,8706,8596,8433,8575,0,15,16,16,15,8685,8575,8412,8554,43,8701,8707,8708,8705,0,491,491,15,15,8680,8686,8687,8684,43,8707,8709,8710,8708,0,491,491,15,15,8686,8688,8689,8687,43,8705,8708,8590,8589,0,15,15,16,16,8684,8687,8569,8568,43,8708,8710,8593,8590,0,15,15,16,16,8687,8689,8572,8569,43,8701,8704,8711,8707,0,491,492,492,491,8680,8683,8690,8686,43,8704,8691,8697,8711,0,492,479,479,492,8683,8670,8676,8690,43,8707,8711,8712,8709,0,491,492,492,491,8686,8690,8691,8688,43,8711,8697,8698,8712,0,492,479,479,492,8690,8676,8677,8691,43,8713,8714,8715,8716,0,500,503,502,501,8692,8693,8694,8695,43,8714,8717,8718,8715,0,503,505,504,502,8693,8696,8697,8694,43,8716,8715,8719,8720,0,501,502,507,506,8695,8694,8698,8699,43,8715,8718,8721,8719,0,502,504,508,507,8694,8697,8700,8698,43,8713,8722,8723,8714,0,500,510,509,503,8692,8701,8702,8693,43,8722,8724,8725,8723,0,510,512,511,509,8701,8703,8704,8702,43,8714,8723,8726,8717,0,503,509,513,505,8693,8702,8705,8696,43,8723,8725,7921,8726,0,509,511,514,513,8702,8704,7900,8705,43,8713,8727,8728,8722,0,500,516,515,510,8692,8706,8707,8701,43,8727,8729,8730,8728,0,516,518,517,515,8706,8708,8709,8707,43,8722,8728,8731,8724,0,510,515,519,512,8701,8707,8710,8703,43,8728,8730,8732,8731,0,515,517,520,519,8707,8709,8711,8710,43,8713,8716,8733,8727,0,500,501,521,516,8692,8695,8712,8706,43,8716,8720,8734,8733,0,501,506,522,521,8695,8699,8713,8712,43,8727,8733,8735,8729,0,516,521,523,518,8706,8712,8714,8708,43,8733,8734,8736,8735,0,521,522,524,523,8712,8713,8715,8714,43,8737,8738,8739,8740,0,525,528,527,526,8716,8717,8718,8719,43,8738,8741,8742,8739,0,528,530,529,527,8717,8720,8721,8718,43,8740,8739,8743,8744,0,526,527,532,531,8719,8718,8722,8723,43,8739,8742,8745,8743,0,527,529,533,532,8718,8721,8724,8722,43,8737,8746,8747,8738,0,525,535,534,528,8716,8725,8726,8717,43,8746,8748,8749,8747,0,535,537,536,534,8725,8727,8728,8726,43,8738,8747,8750,8741,0,528,534,538,530,8717,8726,8729,8720,43,8747,8749,8751,8750,0,534,536,539,538,8726,8728,8730,8729,43,8737,8752,8753,8746,0,525,541,540,535,8716,8731,8732,8725,43,8752,8729,8735,8753,0,541,518,523,540,8731,8708,8714,8732,43,8746,8753,8754,8748,0,535,540,542,537,8725,8732,8733,8727,43,8753,8735,8736,8754,0,540,523,524,542,8732,8714,8715,8733,43,8737,8740,8755,8752,0,525,526,543,541,8716,8719,8734,8731,43,8740,8744,8756,8755,0,526,531,544,543,8719,8723,8735,8734,43,8752,8755,8730,8729,0,541,543,517,518,8731,8734,8709,8708,43,8755,8756,8732,8730,0,543,544,520,517,8734,8735,8711,8709,43,8757,8758,8759,8760,0,545,548,547,546,8736,8737,8738,8739,43,8758,8761,8762,8759,0,548,550,549,547,8737,8740,8741,8738,43,8760,8759,8763,8764,0,546,547,552,551,8739,8738,8742,8743,43,8759,8762,7975,8763,0,547,549,248,552,8738,8741,7954,8742,43,8757,8765,8766,8758,0,545,554,553,548,8736,8744,8745,8737,43,8765,8767,8768,8766,0,554,556,555,553,8744,8746,8747,8745,43,8758,8766,8769,8761,0,548,553,557,550,8737,8745,8748,8740,43,8766,8768,8770,8769,0,553,555,558,557,8745,8747,8749,8748,43,8757,8771,8772,8765,0,545,560,559,554,8736,8750,8751,8744,43,8771,8741,8750,8772,0,560,530,538,559,8750,8720,8729,8751,43,8765,8772,8773,8767,0,554,559,561,556,8744,8751,8752,8746,43,8772,8750,8751,8773,0,559,538,539,561,8751,8729,8730,8752,43,8757,8760,8774,8771,0,545,546,562,560,8736,8739,8753,8750,43,8760,8764,8775,8774,0,546,551,563,562,8739,8743,8754,8753,43,8771,8774,8742,8741,0,560,562,529,530,8750,8753,8721,8720,43,8774,8775,8745,8742,0,562,563,533,529,8753,8754,8724,8721,43,8776,8777,8778,8779,0,564,567,566,565,8755,8756,8757,8758,43,8777,8780,8781,8778,0,567,569,568,566,8756,8759,8760,8757,43,8779,8778,8782,8783,0,565,566,571,570,8758,8757,8761,8762,43,8778,8781,8784,8782,0,566,568,572,571,8757,8760,8763,8761,43,8776,8785,8786,8777,0,564,574,573,567,8755,8764,8765,8756,43,8785,8787,8788,8786,0,574,576,575,573,8764,8766,8767,8765,43,8777,8786,8789,8780,0,567,573,577,569,8756,8765,8768,8759,43,8786,8788,8790,8789,0,573,575,578,577,8765,8767,8769,8768,43,8776,8791,8792,8785,0,564,580,579,574,8755,8770,8771,8764,43,8791,8793,8794,8792,0,580,582,581,579,8770,8772,8773,8771,43,8785,8792,8795,8787,0,574,579,583,576,8764,8771,8774,8766,43,8792,8794,8796,8795,0,579,581,584,583,8771,8773,8775,8774,43,8776,8779,8797,8791,0,564,565,585,580,8755,8758,8776,8770,43,8779,8783,8798,8797,0,565,570,586,585,8758,8762,8777,8776,43,8791,8797,8799,8793,0,580,585,587,582,8770,8776,8778,8772,43,8797,8798,8800,8799,0,585,586,588,587,8776,8777,8779,8778,43,8801,8802,8803,8804,0,589,592,591,590,8780,8781,8782,8783,43,8802,7840,7839,8803,0,592,93,94,591,8781,7819,7818,8782,43,8804,8803,7942,7941,0,590,591,205,206,8783,8782,7921,7920,43,8803,7839,7841,7942,0,591,94,95,205,8782,7818,7820,7921,43,8801,8805,8806,8802,0,589,594,593,592,8780,8784,8785,8781,43,8805,8807,8808,8806,0,594,596,595,593,8784,8786,8787,8785,43,8802,8806,7855,7840,0,592,593,109,93,8781,8785,7834,7819,43,8806,8808,7857,7855,0,593,595,111,109,8785,8787,7836,7834,43,8801,8809,8810,8805,0,589,598,597,594,8780,8788,8789,8784,43,8809,1344,1343,8810,0,598,600,599,597,8788,1344,1343,8789,43,8805,8810,8811,8807,0,594,597,601,596,8784,8789,8790,8786,43,8810,1343,1346,8811,0,597,599,602,601,8789,1343,1346,8790,43,8801,8804,8812,8809,0,589,590,603,598,8780,8783,8791,8788,43,8804,7941,7947,8812,0,590,206,213,603,8783,7920,7926,8791,43,8809,8812,1348,1344,0,598,603,604,600,8788,8791,1348,1344,43,8812,7947,469,1348,0,603,213,214,604,8791,7926,469,1348,43,8813,8814,8815,8816,0,605,608,607,606,8792,8793,8794,8795,43,8814,8817,8818,8815,0,608,610,609,607,8793,8796,8797,8794,43,8816,8815,8718,8717,0,606,607,504,505,8795,8794,8697,8696,43,8815,8818,8721,8718,0,607,609,508,504,8794,8797,8700,8697,43,8813,8819,8820,8814,0,605,612,611,608,8792,8798,8799,8793,43,8819,7885,7884,8820,0,612,143,144,611,8798,7864,7863,8799,43,8814,8820,8821,8817,0,608,611,613,610,8793,8799,8800,8796,43,8820,7884,7886,8821,0,611,144,145,613,8799,7863,7865,8800,43,8813,8822,8823,8819,0,605,615,614,612,8792,8801,8802,8798,43,8822,7905,7904,8823,0,615,617,616,614,8801,7884,7883,8802,43,8819,8823,7895,7885,0,612,614,158,143,8798,8802,7874,7864,43,8823,7904,7897,7895,0,614,616,160,158,8802,7883,7876,7874,43,8813,8816,8824,8822,0,605,606,618,615,8792,8795,8803,8801,43,8816,8717,8726,8824,0,606,505,513,618,8795,8696,8705,8803,43,8822,8824,7919,7905,0,615,618,619,617,8801,8803,7898,7884,43,8824,8726,7921,7919,0,618,513,514,619,8803,8705,7900,7898,43,8825,8826,8827,8828,0,620,623,622,621,8804,8805,8806,8807,43,8826,7902,7910,8827,0,623,166,174,622,8805,7881,7889,8806,43,8828,8827,8829,8830,0,621,622,625,624,8807,8806,8808,8809,43,8827,7910,7911,8829,0,622,174,175,625,8806,7889,7890,8808,43,8825,8831,8832,8826,0,620,627,626,623,8804,8810,8811,8805,43,8831,7892,7896,8832,0,627,629,628,626,8810,7871,7875,8811,43,8826,8832,7903,7902,0,623,626,165,166,8805,8811,7882,7881,43,8832,7896,7897,7903,0,626,628,169,165,8811,7875,7876,7882,43,8825,8833,8834,8831,0,620,631,630,627,8804,8812,8813,8810,43,8833,1372,1371,8834,0,631,633,632,630,8812,1372,1371,8813,43,8831,8834,7893,7892,0,627,630,62,629,8810,8813,7872,7871,43,8834,1371,412,7893,0,630,632,634,62,8813,1371,412,7872,43,8825,8828,8835,8833,0,620,621,635,631,8804,8807,8814,8812,43,8828,8830,8836,8835,0,621,624,636,635,8807,8809,8815,8814,43,8837,1381,1388,8838,0,640,643,650,649,8816,1381,1388,8817,43,8839,8840,8841,8837,0,639,642,641,640,8818,8819,8820,8816,43,8840,8807,8811,8841,0,642,596,601,641,8819,8786,8790,8820,43,8837,8841,1382,1381,0,640,641,644,643,8816,8820,1382,1381,43,8841,8811,1346,1382,0,641,601,602,644,8820,8790,1346,1382,43,8839,8842,8843,8840,0,639,646,645,642,8818,8821,8822,8819,43,8842,7927,7926,8843,0,646,190,191,645,8821,7906,7905,8822,43,8840,8843,8808,8807,0,642,645,595,596,8819,8822,8787,8786,43,8843,7926,7857,8808,0,645,191,111,595,8822,7905,7836,8787,43,8839,8844,8845,8842,0,639,648,647,646,8818,8823,8824,8821,43,8844,8830,8829,8845,0,648,624,625,647,8823,8809,8808,8824,43,8842,8845,7936,7927,0,646,647,200,190,8821,8824,7915,7906,43,8845,8829,7911,7936,0,647,625,175,200,8824,8808,7890,7915,43,8839,8837,8838,8844,0,639,640,649,648,8818,8816,8817,8823,43,8844,8838,8836,8830,0,648,649,636,624,8823,8817,8815,8809,43,8838,1388,1376,8836,0,649,650,638,636,8817,1388,1376,8815,43,8846,8847,8848,8849,0,651,654,653,652,8825,8826,8827,8828,43,8847,7837,7846,8848,0,654,92,100,653,8826,7816,7825,8827,43,8849,8848,8850,8851,0,652,653,656,655,8828,8827,8829,8830,43,8848,7846,7847,8850,0,653,100,101,656,8827,7825,7826,8829,43,8846,8852,8853,8847,0,651,658,657,654,8825,8831,8832,8826,43,8852,7944,7943,8853,0,658,207,208,657,8831,7923,7922,8832,43,8847,8853,7838,7837,0,654,657,91,92,8826,8832,7817,7816,43,8853,7943,7841,7838,0,657,208,95,91,8832,7922,7820,7817,43,8846,8854,8855,8852,0,651,660,659,658,8825,8833,8834,8831,43,8854,8856,8857,8855,0,660,662,661,659,8833,8835,8836,8834,43,8852,8855,7953,7944,0,658,659,222,207,8831,8834,7932,7923,43,8855,8857,7955,7953,0,659,661,14,222,8834,8836,7934,7932,43,8846,8849,8858,8854,0,651,652,663,660,8825,8828,8837,8833,43,8849,8851,8859,8858,0,652,655,664,663,8828,8830,8838,8837,43,8854,8858,8860,8856,0,660,663,309,662,8833,8837,8839,8835,43,8858,8859,8037,8860,0,663,664,665,309,8837,8838,8016,8839,43,8861,8862,8863,8864,0,666,669,668,667,8840,8841,8842,8843,43,8862,8865,8866,8863,0,669,671,670,668,8841,8844,8845,8842,43,8864,8863,8867,8868,0,667,668,673,672,8843,8842,8846,8847,43,8863,8866,8869,8867,0,668,670,674,673,8842,8845,8848,8846,43,8861,8870,8871,8862,0,666,676,675,669,8840,8849,8850,8841,43,8870,7963,7962,8871,0,676,230,231,675,8849,7942,7941,8850,43,8862,8871,8872,8865,0,669,675,677,671,8841,8850,8851,8844,43,8871,7962,7964,8872,0,675,231,232,677,8850,7941,7943,8851,43,8861,8873,8874,8870,0,666,679,678,676,8840,8852,8853,8849,43,8873,8764,8763,8874,0,679,681,680,678,8852,8743,8742,8853,43,8870,8874,7973,7963,0,676,678,246,230,8849,8853,7952,7942,43,8874,8763,7975,7973,0,678,680,248,246,8853,8742,7954,7952,43,8861,8864,8875,8873,0,666,667,682,679,8840,8843,8854,8852,43,8864,8868,8876,8875,0,667,672,683,682,8843,8847,8855,8854,43,8873,8875,8775,8764,0,679,682,634,681,8852,8854,8754,8743,43,8875,8876,8745,8775,0,682,683,684,634,8854,8855,8724,8754,43,8877,8878,8879,8880,0,685,688,687,686,8856,8857,8858,8859,43,8878,7980,7984,8879,0,688,254,257,687,8857,7959,7963,8858,43,8880,8879,8881,8882,0,686,687,690,689,8859,8858,8860,8861,43,8879,7984,7877,8881,0,687,257,136,690,8858,7963,7856,8860,43,8877,8883,8884,8878,0,685,692,691,688,8856,8862,8863,8857,43,8883,8865,8872,8884,0,692,671,677,691,8862,8844,8851,8863,43,8878,8884,7981,7980,0,688,691,253,254,8857,8863,7960,7959,43,8884,8872,7964,7981,0,691,677,232,253,8863,8851,7943,7960,43,8877,8885,8886,8883,0,685,694,693,692,8856,8864,8865,8862,43,8885,8887,8888,8886,0,694,696,695,693,8864,8866,8867,8865,43,8883,8886,8866,8865,0,692,693,670,671,8862,8865,8845,8844,43,8886,8888,8869,8866,0,693,695,674,670,8865,8867,8848,8845,43,8877,8880,8889,8885,0,685,686,697,694,8856,8859,8868,8864,43,8880,8882,8890,8889,0,686,689,698,697,8859,8861,8869,8868,43,8885,8889,8891,8887,0,694,697,699,696,8864,8868,8870,8866,43,8889,8890,8892,8891,0,697,698,700,699,8868,8869,8871,8870,43,8893,8894,8895,8896,0,701,704,703,702,8872,8873,8874,8875,43,8894,1441,1440,8895,0,704,706,705,703,8873,1441,1440,8874,43,8896,8895,8897,8898,0,702,703,708,707,8875,8874,8876,8877,43,8895,1440,1444,8897,0,703,705,709,708,8874,1440,1444,8876,43,8893,8899,8900,8894,0,701,711,710,704,8872,8878,8879,8873,43,8899,7862,7869,8900,0,711,117,125,710,8878,7841,7848,8879,43,8894,8900,1447,1441,0,704,710,712,706,8873,8879,1447,1441,43,8900,7869,381,1447,0,710,125,126,712,8879,7848,381,1447,43,8893,8901,8902,8899,0,701,714,713,711,8872,8880,8881,8878,43,8901,8903,8904,8902,0,714,716,715,713,8880,8882,8883,8881,43,8899,8902,7863,7862,0,711,713,116,117,8878,8881,7842,7841,43,8902,8904,7866,7863,0,713,715,120,116,8881,8883,7845,7842,43,8893,8896,8905,8901,0,701,702,659,714,8872,8875,8884,8880,43,8896,8898,8906,8905,0,702,707,661,659,8875,8877,8885,8884,43,8901,8905,8907,8903,0,714,659,222,716,8880,8884,8886,8882,43,8905,8906,8008,8907,0,659,661,14,222,8884,8885,7987,8886,43,8908,8909,8910,8911,0,717,720,719,718,8887,8888,8889,8890,43,8909,1460,1459,8910,0,720,722,721,719,8888,1460,1459,8889,43,8911,8910,8912,8913,0,718,719,724,723,8890,8889,8891,8892,43,8910,1459,1463,8912,0,719,721,725,724,8889,1459,1463,8891,43,8908,8914,8915,8909,0,717,727,726,720,8887,8893,8894,8888,43,8914,8916,8917,8915,0,727,729,728,726,8893,8895,8896,8894,43,8909,8915,1468,1460,0,720,726,730,722,8888,8894,1468,1460,43,8915,8917,1469,1468,0,726,728,731,730,8894,8896,1469,1468,43,8908,8918,8919,8914,0,717,733,732,727,8887,8897,8898,8893,43,8918,8065,8064,8919,0,733,342,343,732,8897,8044,8043,8898,43,8914,8919,8920,8916,0,727,732,734,729,8893,8898,8899,8895,43,8919,8064,8066,8920,0,732,343,344,734,8898,8043,8045,8899,43,8908,8911,8921,8918,0,717,718,735,733,8887,8890,8900,8897,43,8911,8913,8922,8921,0,718,723,736,735,8890,8892,8901,8900,43,8918,8921,8080,8065,0,733,735,358,342,8897,8900,8059,8044,43,8921,8922,8082,8080,0,735,736,360,358,8900,8901,8061,8059,43,8923,8924,8925,8926,0,737,740,739,738,8902,8903,8904,8905,43,8924,8761,8769,8925,0,740,550,557,739,8903,8740,8748,8904,43,8926,8925,8927,8928,0,738,739,742,741,8905,8904,8906,8907,43,8925,8769,8770,8927,0,739,557,558,742,8904,8748,8749,8906,43,8923,8929,8930,8924,0,737,744,743,740,8902,8908,8909,8903,43,8929,7970,7974,8930,0,744,745,680,743,8908,7949,7953,8909,43,8924,8930,8762,8761,0,740,743,549,550,8903,8909,8741,8740,43,8930,7974,7975,8762,0,743,680,248,549,8909,7953,7954,8741,43,8923,8931,8932,8929,0,737,747,746,744,8902,8910,8911,8908,43,8929,8932,7971,7970,0,744,746,62,745,8908,8911,7950,7949,43,7867,7871,386,379,0,122,127,131,124,7846,7850,386,379,43,8923,8926,8933,8931,0,737,738,750,747,8902,8905,8912,8910,43,8926,8928,8934,8933,0,738,741,751,750,8905,8907,8913,8912,43,8933,8934,1490,1489,0,750,751,753,752,8912,8913,1490,1489,43,8935,8936,8937,8938,0,754,757,756,755,8914,8915,8916,8917,43,8936,1496,1495,8937,0,757,759,758,756,8915,1496,1495,8916,43,8938,8937,8939,8940,0,755,756,761,760,8917,8916,8918,8919,43,8937,1495,1499,8939,0,756,758,762,761,8916,1495,1499,8918,43,8935,8941,8942,8936,0,754,764,763,757,8914,8920,8921,8915,43,8941,8943,8944,8942,0,764,766,765,763,8920,8922,8923,8921,43,8936,8942,1504,1496,0,757,763,767,759,8915,8921,1504,1496,43,8942,8944,1505,1504,0,763,765,768,767,8921,8923,1505,1504,43,8935,8945,8946,8941,0,754,770,769,764,8914,8924,8925,8920,43,8945,8793,8799,8946,0,770,582,587,769,8924,8772,8778,8925,43,8941,8946,8947,8943,0,764,769,771,766,8920,8925,8926,8922,43,8946,8799,8800,8947,0,769,587,588,771,8925,8778,8779,8926,43,8935,8938,8948,8945,0,754,755,772,770,8914,8917,8927,8924,43,8938,8940,8949,8948,0,755,760,773,772,8917,8919,8928,8927,43,8945,8948,8794,8793,0,770,772,581,582,8924,8927,8773,8772,43,8948,8949,8796,8794,0,772,773,584,581,8927,8928,8775,8773,43,8950,8951,8952,8953,0,386,62,62,386,8929,8930,8931,8932,43,8951,8954,8955,8952,0,62,16,16,62,8930,8933,8934,8931,43,8953,8952,8956,8957,0,386,62,62,386,8932,8931,8935,8936,43,8952,8955,8958,8956,0,62,16,16,62,8931,8934,8937,8935,43,8950,8959,8960,8951,0,386,386,62,62,8929,8938,8939,8930,43,8959,8272,8271,8960,0,386,386,62,62,8938,8251,8250,8939,43,8951,8960,8961,8954,0,62,62,16,16,8930,8939,8940,8933,43,8960,8271,8273,8961,0,62,62,16,16,8939,8250,8252,8940,43,8950,8962,8963,8959,0,386,385,385,386,8929,8941,8942,8938,43,8962,8964,8965,8963,0,385,388,388,385,8941,8943,8944,8942,43,8959,8963,8287,8272,0,386,385,385,386,8938,8942,8266,8251,43,8963,8965,8289,8287,0,385,388,388,385,8942,8944,8268,8266,43,8950,8953,8966,8962,0,386,386,385,385,8929,8932,8945,8941,43,8953,8957,8967,8966,0,386,386,385,385,8932,8936,8946,8945,43,8962,8966,8968,8964,0,385,385,388,388,8941,8945,8947,8943,43,8966,8967,8969,8968,0,385,385,388,388,8945,8946,8948,8947,43,8970,8971,8972,8973,0,403,403,405,405,8949,8950,8951,8952,43,8971,8294,8301,8972,0,403,403,405,405,8950,8273,8280,8951,43,8973,8972,8965,8964,0,405,405,388,388,8952,8951,8944,8943,43,8972,8301,8289,8965,0,405,405,388,388,8951,8280,8268,8944,43,8970,8974,8975,8971,0,403,66,66,403,8949,8953,8954,8950,43,8974,8976,8977,8975,0,66,18,18,66,8953,8955,8956,8954,43,8971,8975,8295,8294,0,403,66,66,403,8950,8954,8274,8273,43,8975,8977,8298,8295,0,66,18,18,66,8954,8956,8277,8274,43,8970,8978,8979,8974,0,403,403,66,66,8949,8957,8958,8953,43,8978,8980,8981,8979,0,403,403,66,66,8957,8959,8960,8958,43,8974,8979,8982,8976,0,66,66,18,18,8953,8958,8961,8955,43,8979,8981,8983,8982,0,66,66,18,18,8958,8960,8962,8961,43,8970,8973,8984,8978,0,403,405,405,403,8949,8952,8963,8957,43,8973,8964,8968,8984,0,405,388,388,405,8952,8943,8947,8963,43,8978,8984,8985,8980,0,403,405,405,403,8957,8963,8964,8959,43,8984,8968,8969,8985,0,405,388,388,405,8963,8947,8948,8964,43,8986,8987,8988,8989,0,418,418,421,421,8965,8966,8967,8968,43,8987,8314,8321,8988,0,418,418,421,421,8966,8293,8300,8967,43,8989,8988,8977,8976,0,421,421,18,18,8968,8967,8956,8955,43,8988,8321,8298,8977,0,421,421,18,18,8967,8300,8277,8956,43,8986,8990,8991,8987,0,418,417,417,418,8965,8969,8970,8966,43,8990,8992,8993,8991,0,417,420,420,417,8969,8971,8972,8970,43,8987,8991,8315,8314,0,418,417,417,418,8966,8970,8294,8293,43,8991,8993,8318,8315,0,417,420,420,417,8970,8972,8297,8294,43,8986,8994,8995,8990,0,418,418,417,417,8965,8973,8974,8969,43,8994,8996,8997,8995,0,418,418,417,417,8973,8975,8976,8974,43,8990,8995,8998,8992,0,417,417,420,420,8969,8974,8977,8971,43,8995,8997,8999,8998,0,417,417,420,420,8974,8976,8978,8977,43,8986,8989,9000,8994,0,418,421,421,418,8965,8968,8979,8973,43,8989,8976,8982,9000,0,421,18,18,421,8968,8955,8961,8979,43,8994,9000,9001,8996,0,418,421,421,418,8973,8979,8980,8975,43,9000,8982,8983,9001,0,421,18,18,421,8979,8961,8962,8980,43,9002,9003,9004,9005,0,433,435,435,433,8981,8982,8983,8984,43,9003,8992,8998,9004,0,435,420,420,435,8982,8971,8977,8983,43,9005,9004,9006,9007,0,433,435,435,433,8984,8983,8985,8986,43,9004,8998,8999,9006,0,435,420,420,435,8983,8977,8978,8985,43,9002,9008,9009,9003,0,433,433,435,435,8981,8987,8988,8982,43,9008,8335,8334,9009,0,433,433,435,435,8987,8314,8313,8988,43,9003,9009,8993,8992,0,435,435,420,420,8982,8988,8972,8971,43,9009,8334,8318,8993,0,435,435,420,420,8988,8313,8297,8972,43,9002,9010,9011,9008,0,433,9,9,433,8981,8989,8990,8987,43,9010,9012,9013,9011,0,9,10,10,9,8989,8991,8992,8990,43,9008,9011,8347,8335,0,433,9,9,433,8987,8990,8326,8314,43,9011,9013,8349,8347,0,9,10,10,9,8990,8992,8328,8326,43,9002,9005,9014,9010,0,433,433,9,9,8981,8984,8993,8989,43,9005,9007,9015,9014,0,433,433,9,9,8984,8986,8994,8993,43,9010,9014,9016,9012,0,9,9,10,10,8989,8993,8995,8991,43,9014,9015,9017,9016,0,9,9,10,10,8993,8994,8996,8995,43,9018,9019,9020,9021,0,448,35,35,448,8997,8998,8999,9000,43,9019,9012,9016,9020,0,35,10,10,35,8998,8991,8995,8999,43,9021,9020,9022,9023,0,448,35,35,448,9000,8999,9001,9002,43,9020,9016,9017,9022,0,35,10,10,35,8999,8995,8996,9001,43,9018,9024,9025,9019,0,448,448,35,35,8997,9003,9004,8998,43,9024,8355,8354,9025,0,448,448,35,35,9003,8334,8333,9004,43,9019,9025,9013,9012,0,35,35,10,10,8998,9004,8992,8991,43,9025,8354,8349,9013,0,35,35,10,10,9004,8333,8328,8992,43,9018,9026,9027,9024,0,448,447,447,448,8997,9005,9006,9003,43,9026,9028,9029,9027,0,447,450,450,447,9005,9007,9008,9006,43,9024,9027,8367,8355,0,448,447,447,448,9003,9006,8346,8334,43,9027,9029,8369,8367,0,447,450,450,447,9006,9008,8348,8346,43,9018,9021,9030,9026,0,448,448,447,447,8997,9000,9009,9005,43,9021,9023,9031,9030,0,448,448,447,447,9000,9002,9010,9009,43,9026,9030,9032,9028,0,447,447,450,450,9005,9009,9011,9007,43,9030,9031,9033,9032,0,447,447,450,450,9009,9010,9012,9011,43,9034,9035,9036,9037,0,462,464,464,462,9013,9014,9015,9016,43,9035,9028,9032,9036,0,464,450,450,464,9014,9007,9011,9015,43,9037,9036,9038,9039,0,462,464,464,462,9016,9015,9017,9018,43,9036,9032,9033,9038,0,464,450,450,464,9015,9011,9012,9017,43,9034,9040,9041,9035,0,462,462,464,464,9013,9019,9020,9014,43,9040,8375,8374,9041,0,462,462,464,464,9019,8354,8353,9020,43,9035,9041,9029,9028,0,464,464,450,450,9014,9020,9008,9007,43,9041,8374,8369,9029,0,464,464,450,450,9020,8353,8348,9008,43,9034,9042,9043,9040,0,462,22,22,462,9013,9021,9022,9019,43,9042,9044,9045,9043,0,22,23,23,22,9021,9023,9024,9022,43,9040,9043,8387,8375,0,462,22,22,462,9019,9022,8366,8354,43,9043,9045,8389,8387,0,22,23,23,22,9022,9024,8368,8366,43,9034,9037,9046,9042,0,462,462,22,22,9013,9016,9025,9021,43,9037,9039,9047,9046,0,462,462,22,22,9016,9018,9026,9025,43,9042,9046,9048,9044,0,22,22,23,23,9021,9025,9027,9023,43,9046,9047,9049,9048,0,22,22,23,23,9025,9026,9028,9027,43,9050,9051,9052,9053,0,477,477,57,57,9029,9030,9031,9032,43,9051,8394,8401,9052,0,477,477,57,57,9030,8373,8380,9031,43,9053,9052,9045,9044,0,57,57,23,23,9032,9031,9024,9023,43,9052,8401,8389,9045,0,57,57,23,23,9031,8380,8368,9024,43,9050,9054,9055,9051,0,477,476,476,477,9029,9033,9034,9030,43,9054,9056,9057,9055,0,476,479,479,476,9033,9035,9036,9034,43,9051,9055,8395,8394,0,477,476,476,477,9030,9034,8374,8373,43,9055,9057,8398,8395,0,476,479,479,476,9034,9036,8377,8374,43,9050,9058,9059,9054,0,477,477,476,476,9029,9037,9038,9033,43,9058,9060,9061,9059,0,477,477,476,476,9037,9039,9040,9038,43,9054,9059,9062,9056,0,476,476,479,479,9033,9038,9041,9035,43,9059,9061,9063,9062,0,476,476,479,479,9038,9040,9042,9041,43,9050,9053,9064,9058,0,477,57,57,477,9029,9032,9043,9037,43,9053,9044,9048,9064,0,57,23,23,57,9032,9023,9027,9043,43,9058,9064,9065,9060,0,477,57,57,477,9037,9043,9044,9039,43,9064,9048,9049,9065,0,57,23,23,57,9043,9027,9028,9044,43,9066,9067,9068,9069,0,491,491,492,492,9045,9046,9047,9048,43,9067,8414,8418,9068,0,491,491,492,492,9046,8393,8397,9047,43,9069,9068,9057,9056,0,492,492,479,479,9048,9047,9036,9035,43,9068,8418,8398,9057,0,492,492,479,479,9047,8397,8377,9036,43,9066,9070,9071,9067,0,491,15,15,491,9045,9049,9050,9046,43,9070,8954,8961,9071,0,15,16,16,15,9049,8933,8940,9050,43,9067,9071,8415,8414,0,491,15,15,491,9046,9050,8394,8393,43,9071,8961,8273,8415,0,15,16,16,15,9050,8940,8252,8394,43,9066,9072,9073,9070,0,491,491,15,15,9045,9051,9052,9049,43,9072,9074,9075,9073,0,491,491,15,15,9051,9053,9054,9052,43,9070,9073,8955,8954,0,15,15,16,16,9049,9052,8934,8933,43,9073,9075,8958,8955,0,15,15,16,16,9052,9054,8937,8934,43,9066,9069,9076,9072,0,491,492,492,491,9045,9048,9055,9051,43,9069,9056,9062,9076,0,492,479,479,492,9048,9035,9041,9055,43,9072,9076,9077,9074,0,491,492,492,491,9051,9055,9056,9053,43,9076,9062,9063,9077,0,492,479,479,492,9055,9041,9042,9056,43,9078,9079,9080,9081,0,386,386,62,62,9057,9058,9059,9060,43,9079,8592,8591,9080,0,386,386,62,62,9058,8571,8570,9059,43,9081,9080,9082,9083,0,62,62,16,16,9060,9059,9061,9062,43,9080,8591,8593,9082,0,62,62,16,16,9059,8570,8572,9061,43,9078,9084,9085,9079,0,386,385,385,386,9057,9063,9064,9058,43,9084,9086,9087,9085,0,385,388,388,385,9063,9065,9066,9064,43,9079,9085,8602,8592,0,386,385,385,386,9058,9064,8581,8571,43,9085,9087,8604,8602,0,385,388,388,385,9064,9066,8583,8581,43,9078,9088,9089,9084,0,386,386,385,385,9057,9067,9068,9063,43,9088,9090,9091,9089,0,386,386,385,385,9067,9069,9070,9068,43,9084,9089,9092,9086,0,385,385,388,388,9063,9068,9071,9065,43,9078,9081,9093,9088,0,386,62,62,386,9057,9060,9072,9067,43,9081,9083,9094,9093,0,62,16,16,62,9060,9062,9073,9072,43,9088,9093,9095,9090,0,386,62,62,386,9067,9072,9074,9069,43,9096,9097,9098,9099,0,15,15,16,16,9075,9076,9077,9078,43,9100,9101,9102,9103,0,403,403,405,405,9079,9080,9081,9082,43,9101,8615,8620,9102,0,403,403,405,405,9080,8594,8599,9081,43,9103,9102,9087,9086,0,405,405,388,388,9082,9081,9066,9065,43,9102,8620,8604,9087,0,405,405,388,388,9081,8599,8583,9066,43,9100,9104,9105,9101,0,403,66,66,403,9079,9083,9084,9080,43,9104,9106,9107,9105,0,66,18,18,66,9083,9085,9086,9084,43,9101,9105,8616,8615,0,403,66,66,403,9080,9084,8595,8594,43,9105,9107,8618,8616,0,66,18,18,66,9084,9086,8597,8595,43,9100,9108,9109,9104,0,403,403,66,66,9079,9087,9088,9083,43,9108,9110,9111,9109,0,403,403,66,66,9087,9089,9090,9088,43,9104,9109,9112,9106,0,66,66,18,18,9083,9088,9091,9085,43,9113,9114,9115,9116,0,421,421,774,774,9092,9093,9094,9095,43,9100,9103,9117,9108,0,403,405,405,403,9079,9082,9096,9087,43,9103,9086,9092,9117,0,405,388,388,405,9082,9065,9071,9096,43,9108,9117,9118,9110,0,403,405,405,403,9087,9096,9097,9089,43,9119,9120,9121,9122,0,418,418,421,421,9098,9099,9100,9101,43,9120,8631,8636,9121,0,418,418,421,421,9099,8610,8615,9100,43,9122,9121,9107,9106,0,421,421,18,18,9101,9100,9086,9085,43,9121,8636,8618,9107,0,421,421,18,18,9100,8615,8597,9086,43,9119,9123,9124,9120,0,418,417,417,418,9098,9102,9103,9099,43,9123,9125,9126,9124,0,417,420,420,417,9102,9104,9105,9103,43,9120,9124,8632,8631,0,418,417,417,418,9099,9103,8611,8610,43,9124,9126,8634,8632,0,417,420,420,417,9103,9105,8613,8611,43,9119,9127,9128,9123,0,418,418,417,417,9098,9106,9107,9102,43,9127,9116,9129,9128,0,418,418,417,417,9106,9095,9108,9107,43,9123,9128,9130,9125,0,417,417,420,420,9102,9107,9109,9104,43,9119,9122,9131,9127,0,418,421,421,418,9098,9101,9110,9106,43,9122,9106,9112,9131,0,421,18,18,421,9101,9085,9091,9110,43,9127,9131,9113,9116,0,418,421,421,418,9106,9110,9092,9095,43,9132,9133,9134,9135,0,433,435,435,433,9111,9112,9113,9114,43,9133,9125,9130,9134,0,435,420,420,435,9112,9104,9109,9113,43,9135,9134,9136,7753,0,433,435,435,433,9114,9113,9115,7732,43,9132,9137,9138,9133,0,433,433,435,435,9111,9116,9117,9112,43,9137,8642,8641,9138,0,433,433,435,435,9116,8621,8620,9117,43,9133,9138,9126,9125,0,435,435,420,420,9112,9117,9105,9104,43,9138,8641,8634,9126,0,435,435,420,420,9117,8620,8613,9105,43,9132,9139,9140,9137,0,433,9,9,433,9111,9118,9119,9116,43,9139,9141,9142,9140,0,9,10,10,9,9118,9120,9121,9119,43,9137,9140,8650,8642,0,433,9,9,433,9116,9119,8629,8621,43,9140,9142,8652,8650,0,9,10,10,9,9119,9121,8631,8629,43,9132,9135,9143,9139,0,433,433,9,9,9111,9114,9122,9118,43,9135,7753,7752,9143,0,433,433,9,9,9114,7732,7731,9122,43,9139,9143,9144,9141,0,9,9,10,10,9118,9122,9123,9120,43,9145,9146,9147,9148,0,448,35,35,448,9124,9125,9126,9127,43,9146,9141,9144,9147,0,35,10,10,35,9125,9120,9123,9126,43,9148,9147,9149,9150,0,448,35,35,448,9127,9126,9128,9129,43,9145,9151,9152,9146,0,448,448,35,35,9124,9130,9131,9125,43,9151,8658,8657,9152,0,448,448,35,35,9130,8637,8636,9131,43,9146,9152,9142,9141,0,35,35,10,10,9125,9131,9121,9120,43,9152,8657,8652,9142,0,35,35,10,10,9131,8636,8631,9121,43,9145,9153,9154,9151,0,448,447,447,448,9124,9132,9133,9130,43,9153,9155,9156,9154,0,447,450,450,447,9132,9134,9135,9133,43,9151,9154,8666,8658,0,448,447,447,448,9130,9133,8645,8637,43,9154,9156,8668,8666,0,447,450,450,447,9133,9135,8647,8645,43,9145,9148,9157,9153,0,448,448,447,447,9124,9127,9136,9132,43,9148,9150,9158,9157,0,448,448,447,447,9127,9129,9137,9136,43,9153,9157,9159,9155,0,447,447,450,450,9132,9136,9138,9134,43,9160,9161,9162,9163,0,462,462,464,464,9139,9140,9141,9142,43,9161,8674,8673,9162,0,462,462,464,464,9140,8653,8652,9141,43,9163,9162,9156,9155,0,464,464,450,450,9142,9141,9135,9134,43,9162,8673,8668,9156,0,464,464,450,450,9141,8652,8647,9135,43,9160,9164,9165,9161,0,462,22,22,462,9139,9143,9144,9140,43,9164,9166,9167,9165,0,22,23,23,22,9143,9145,9146,9144,43,9161,9165,8682,8674,0,462,22,22,462,9140,9144,8661,8653,43,9165,9167,8684,8682,0,22,23,23,22,9144,9146,8663,8661,43,9160,9168,9169,9164,0,462,462,22,22,9139,9147,9148,9143,43,9168,9170,9171,9169,0,462,462,22,22,9147,9149,9150,9148,43,9164,9169,9172,9166,0,22,22,23,23,9143,9148,9151,9145,43,9171,9173,9174,9175,0,22,22,23,23,9150,9152,9153,9154,43,9160,9163,9176,9168,0,462,464,464,462,9139,9142,9155,9147,43,9163,9155,9159,9176,0,464,450,450,464,9142,9134,9138,9155,43,9168,9176,9177,9170,0,462,464,464,462,9147,9155,9156,9149,43,9178,9179,9180,9158,0,11,12,12,11,9157,9158,9159,9137,43,9181,9182,9183,9184,0,477,477,57,57,9160,9161,9162,9163,43,9182,8695,8700,9183,0,477,477,57,57,9161,8674,8679,9162,43,9184,9183,9167,9166,0,57,57,23,23,9163,9162,9146,9145,43,9183,8700,8684,9167,0,57,57,23,23,9162,8679,8663,9146,43,9181,9185,9186,9182,0,477,476,476,477,9160,9164,9165,9161,43,9185,9187,9188,9186,0,476,479,479,476,9164,9166,9167,9165,43,9182,9186,8696,8695,0,477,476,476,477,9161,9165,8675,8674,43,9186,9188,8698,8696,0,476,479,479,476,9165,9167,8677,8675,43,9181,9189,9190,9185,0,477,477,476,476,9160,9168,9169,9164,43,9189,9191,9192,9190,0,477,477,476,476,9168,9170,9171,9169,43,9185,9190,9193,9187,0,476,476,479,479,9164,9169,9172,9166,43,9192,9194,9195,9196,0,65,65,30,30,9171,9173,9174,9175,43,9181,9184,9197,9189,0,477,57,57,477,9160,9163,9176,9168,43,9184,9166,9172,9197,0,57,23,23,57,9163,9145,9151,9176,43,9189,9197,9198,9191,0,477,57,57,477,9168,9176,9177,9170,43,9177,9199,9200,9170,0,14,14,775,775,9156,9178,9179,9149,43,9201,9202,9203,9204,0,491,491,492,492,9180,9181,9182,9183,43,9202,8709,8712,9203,0,491,491,492,492,9181,8688,8691,9182,43,9204,9203,9188,9187,0,492,492,479,479,9183,9182,9167,9166,43,9203,8712,8698,9188,0,492,492,479,479,9182,8691,8677,9167,43,9201,9205,9206,9202,0,491,15,15,491,9180,9184,9185,9181,43,9205,9083,9082,9206,0,15,16,16,15,9184,9062,9061,9185,43,9202,9206,8710,8709,0,491,15,15,491,9181,9185,8689,8688,43,9206,9082,8593,8710,0,15,16,16,15,9185,9061,8572,8689,43,9201,9207,9208,9205,0,491,491,15,15,9180,9186,9187,9184,43,9207,9209,9096,9208,0,491,491,15,15,9186,9188,9075,9187,43,9205,9208,9094,9083,0,15,15,16,16,9184,9187,9073,9062,43,9210,9211,9212,9209,0,61,61,776,776,9189,9190,9191,9188,43,9201,9204,9213,9207,0,491,492,492,491,9180,9183,9192,9186,43,9204,9187,9193,9213,0,492,479,479,492,9183,9166,9172,9192,43,9207,9213,9210,9209,0,491,492,492,491,9186,9192,9189,9188,43,9198,9214,9215,9191,0,57,57,777,777,9177,9193,9194,9170,43,9216,9217,9218,9219,0,778,781,780,779,9195,9196,9197,9198,43,9217,9220,9221,9218,0,781,783,782,780,9196,9199,9200,9197,43,9219,9218,8123,8122,0,779,780,393,394,9198,9197,8102,8101,43,9218,9221,8125,8123,0,780,782,396,393,9197,9200,8104,8102,43,9216,9222,9223,9217,0,778,785,784,781,9195,9201,9202,9196,43,9222,8868,8867,9223,0,785,672,673,784,9201,8847,8846,9202,43,9217,9223,9224,9220,0,781,784,786,783,9196,9202,9203,9199,43,9223,8867,8869,9224,0,784,673,674,786,9202,8846,8848,9203,43,9216,9225,9226,9222,0,778,788,787,785,9195,9204,9205,9201,43,9225,9227,9228,9226,0,788,790,789,787,9204,9206,9207,9205,43,9222,9226,8876,8868,0,785,787,683,672,9201,9205,8855,8847,43,9226,9228,8745,8876,0,787,789,684,683,9205,9207,8724,8855,43,9216,9219,9229,9225,0,778,779,791,788,9195,9198,9208,9204,43,9219,8122,8128,9229,0,779,394,399,791,9198,8101,8107,9208,43,9225,9229,9230,9227,0,788,791,792,790,9204,9208,9209,9206,43,9229,8128,8129,9230,0,791,399,400,792,9208,8107,8108,9209,43,9231,9232,9233,9234,0,793,796,795,794,9210,9211,9212,9213,43,9232,9227,9230,9233,0,796,798,797,795,9211,9206,9209,9212,43,9234,9233,8145,8144,0,794,795,409,410,9213,9212,8124,8123,43,9233,9230,8129,8145,0,795,797,400,409,9212,9209,8108,8124,43,9231,9235,9236,9232,0,793,800,799,796,9210,9214,9215,9211,43,9235,8744,8743,9236,0,800,531,532,799,9214,8723,8722,9215,43,9232,9236,9228,9227,0,796,799,30,798,9211,9215,9207,9206,43,9236,8743,8745,9228,0,799,532,533,30,9215,8722,8724,9207,43,9231,9237,9238,9235,0,793,802,801,800,9210,9216,9217,9214,43,9237,9239,9240,9238,0,802,804,803,801,9216,9218,9219,9217,43,9235,9238,8756,8744,0,800,801,544,531,9214,9217,8735,8723,43,9238,9240,8732,8756,0,801,803,520,544,9217,9219,8711,8735,43,9231,9234,9241,9237,0,793,794,805,802,9210,9213,9220,9216,43,9234,8144,8148,9241,0,794,410,413,805,9213,8123,8127,9220,43,9237,9241,9242,9239,0,802,805,806,804,9216,9220,9221,9218,43,9241,8148,8149,9242,0,805,413,414,806,9220,8127,8128,9221,43,9243,9244,9245,9246,0,807,810,809,808,9222,9223,9224,9225,43,9244,8724,8731,9245,0,810,512,519,809,9223,8703,8710,9224,43,9246,9245,9240,9239,0,808,809,803,804,9225,9224,9219,9218,43,9245,8731,8732,9240,0,809,519,520,803,9224,8710,8711,9219,43,9243,9247,9248,9244,0,807,812,811,810,9222,9226,9227,9223,43,9247,9249,9250,9248,0,812,814,813,811,9226,9228,9229,9227,43,9244,9248,8725,8724,0,810,811,511,512,9223,9227,8704,8703,43,9248,9250,7921,8725,0,811,813,514,511,9227,9229,7900,8704,43,9243,9251,9252,9247,0,807,816,815,812,9222,9230,9231,9226,43,9251,8164,8168,9252,0,816,426,429,815,9230,8143,8147,9231,43,9247,9252,9253,9249,0,812,815,817,814,9226,9231,9232,9228,43,9252,8168,8169,9253,0,815,429,430,817,9231,8147,8148,9232,43,9243,9246,9254,9251,0,807,808,818,816,9222,9225,9233,9230,43,9246,9239,9242,9254,0,808,804,806,818,9225,9218,9221,9233,43,9251,9254,8165,8164,0,816,818,425,426,9230,9233,8144,8143,43,9254,9242,8149,8165,0,818,806,414,425,9233,9221,8128,8144,43,9255,9256,9257,9258,0,819,822,821,820,9234,9235,9236,9237,43,9256,7914,7920,9257,0,822,179,184,821,9235,7893,7899,9236,43,9258,9257,9250,9249,0,820,821,824,823,9237,9236,9229,9228,43,9257,7920,7921,9250,0,821,184,185,824,9236,7899,7900,9229,43,9255,9259,9260,9256,0,819,826,825,822,9234,9238,9239,9235,43,9259,9261,9262,9260,0,826,828,827,825,9238,9240,9241,9239,43,9256,9260,7915,7914,0,822,825,178,179,9235,9239,7894,7893,43,9260,9262,7917,7915,0,825,827,181,178,9239,9241,7896,7894,43,9255,9263,9264,9259,0,819,830,829,826,9234,9242,9243,9238,43,9263,8184,8188,9264,0,830,440,443,829,9242,8163,8167,9243,43,9259,9264,9265,9261,0,826,829,831,828,9238,9243,9244,9240,43,9264,8188,8189,9265,0,829,443,444,831,9243,8167,8168,9244,43,9255,9258,9266,9263,0,819,820,832,830,9234,9237,9245,9242,43,9258,9249,9253,9266,0,820,823,833,832,9237,9228,9232,9245,43,9263,9266,8185,8184,0,830,832,439,440,9242,9245,8164,8163,43,9266,9253,8169,8185,0,832,833,430,439,9245,9232,8148,8164,43,9267,9268,9269,9270,0,834,837,836,835,9246,9247,9248,9249,43,9268,7930,7934,9269,0,837,195,198,836,9247,7909,7913,9248,43,9270,9269,9262,9261,0,835,836,827,828,9249,9248,9241,9240,43,9269,7934,7917,9262,0,836,198,181,827,9248,7913,7896,9241,43,9267,9271,9272,9268,0,834,839,838,837,9246,9250,9251,9247,43,9271,9273,9274,9272,0,839,841,840,838,9250,9252,9253,9251,43,9268,9272,7931,7930,0,837,838,194,195,9247,9251,7910,7909,43,9272,9274,7853,7931,0,838,840,107,194,9251,9253,7832,7910,43,9267,9275,9276,9271,0,834,843,842,839,9246,9254,9255,9250,43,9275,8204,8208,9276,0,843,455,458,842,9254,8183,8187,9255,43,9271,9276,9277,9273,0,839,842,844,841,9250,9255,9256,9252,43,9276,8208,8209,9277,0,842,458,459,844,9255,8187,8188,9256,43,9267,9270,9278,9275,0,834,835,845,843,9246,9249,9257,9254,43,9270,9261,9265,9278,0,835,828,831,845,9249,9240,9244,9257,43,9275,9278,8205,8204,0,843,845,454,455,9254,9257,8184,8183,43,9278,9265,8189,8205,0,845,831,444,454,9257,9244,8168,8184,43,9279,9280,9281,9282,0,846,849,848,847,9258,9259,9260,9261,43,9280,8049,8055,9281,0,849,323,330,848,9259,8028,8034,9260,43,9282,9281,9274,9273,0,847,848,851,850,9261,9260,9253,9252,43,9281,8055,7853,9274,0,848,330,331,851,9260,8034,7832,9253,43,9279,9283,9284,9280,0,846,853,852,849,9258,9262,9263,9259,43,9283,9285,9286,9284,0,853,855,854,852,9262,9264,9265,9263,43,9280,9284,8050,8049,0,849,852,322,323,9259,9263,8029,8028,43,9284,9286,8052,8050,0,852,854,325,322,9263,9265,8031,8029,43,9279,9287,9288,9283,0,846,857,856,853,9258,9266,9267,9262,43,9287,8224,8228,9288,0,857,469,472,856,9266,8203,8207,9267,43,9283,9288,9289,9285,0,853,856,858,855,9262,9267,9268,9264,43,9288,8228,8229,9289,0,856,472,473,858,9267,8207,8208,9268,43,9279,9282,9290,9287,0,846,847,859,857,9258,9261,9269,9266,43,9282,9273,9277,9290,0,847,850,860,859,9261,9252,9256,9269,43,9287,9290,8225,8224,0,857,859,468,469,9266,9269,8204,8203,43,9290,9277,8209,8225,0,859,860,459,468,9269,9256,8188,8204,43,9291,9292,9293,9294,0,861,864,863,862,9270,9271,9272,9273,43,9292,9295,9296,9293,0,864,866,865,863,9271,9274,9275,9272,43,9294,9293,9286,9285,0,862,863,854,855,9273,9272,9265,9264,43,9293,9296,8052,9286,0,863,865,325,854,9272,9275,8031,9265,43,9291,9297,9298,9292,0,861,868,867,864,9270,9276,9277,9271,43,9297,9299,9300,9298,0,868,870,869,867,9276,9278,9279,9277,43,9292,9298,9301,9295,0,864,867,871,866,9271,9277,9280,9274,43,9298,9300,8892,9301,0,867,869,872,871,9277,9279,8871,9280,43,9291,9302,9303,9297,0,861,874,873,868,9270,9281,9282,9276,43,9302,8244,8248,9303,0,874,484,487,873,9281,8223,8227,9282,43,9297,9303,9304,9299,0,868,873,875,870,9276,9282,9283,9278,43,9303,8248,8249,9304,0,873,487,488,875,9282,8227,8228,9283,43,9291,9294,9305,9302,0,861,862,876,874,9270,9273,9284,9281,43,9294,9285,9289,9305,0,862,855,858,876,9273,9264,9268,9284,43,9302,9305,8245,8244,0,874,876,483,484,9281,9284,8224,8223,43,9305,9289,8229,8245,0,876,858,473,483,9284,9268,8208,8224,43,9306,9307,9308,9309,0,877,880,879,878,9285,9286,9287,9288,43,9307,9299,9304,9308,0,880,882,881,879,9286,9278,9283,9287,43,9309,9308,8262,8261,0,878,879,496,497,9288,9287,8241,8240,43,9308,9304,8249,8262,0,879,881,488,496,9287,9283,8228,8241,43,9306,9310,9311,9307,0,877,884,883,880,9285,9289,9290,9286,43,9310,8887,8891,9311,0,884,696,699,883,9289,8866,8870,9290,43,9307,9311,9300,9299,0,880,883,885,882,9286,9290,9279,9278,43,9311,8891,8892,9300,0,883,699,700,885,9290,8870,8871,9279,43,9306,9312,9313,9310,0,877,887,886,884,9285,9291,9292,9289,43,9312,9220,9224,9313,0,887,783,786,886,9291,9199,9203,9292,43,9310,9313,8888,8887,0,884,886,695,696,9289,9292,8867,8866,43,9313,9224,8869,8888,0,886,786,674,695,9292,9203,8848,8867,43,9306,9309,9314,9312,0,877,878,888,887,9285,9288,9293,9291,43,9309,8261,8264,9314,0,878,497,499,888,9288,8240,8243,9293,43,9312,9314,9221,9220,0,887,888,782,783,9291,9293,9200,9199,43,9314,8264,8125,9221,0,888,499,396,782,9293,8243,8104,9200,43,9315,9316,9317,9318,0,889,892,891,890,9294,9295,9296,9297,43,9316,8882,8881,9317,0,892,689,690,891,9295,8861,8860,9296,43,9318,9317,7875,7865,0,890,891,134,118,9297,9296,7854,7844,43,9317,8881,7877,7875,0,891,690,136,134,9296,8860,7856,7854,43,9315,9319,9320,9316,0,889,894,893,892,9294,9298,9299,9295,43,9319,9321,9322,9320,0,894,896,895,893,9298,9300,9301,9299,43,9316,9320,8890,8882,0,892,893,698,689,9295,9299,8869,8861,43,9320,9322,8892,8890,0,893,895,700,698,9299,9301,8871,8869,43,9315,9323,9324,9319,0,889,898,897,894,9294,9302,9303,9298,43,9323,9325,9326,9324,0,898,900,899,897,9302,9304,9305,9303,43,9319,9324,9327,9321,0,894,897,901,896,9298,9303,9306,9300,43,9324,9326,8096,9327,0,897,899,902,901,9303,9305,8075,9306,43,9315,9318,9328,9323,0,889,890,903,898,9294,9297,9307,9302,43,9318,7865,7864,9328,0,890,118,119,903,9297,7844,7843,9307,43,9323,9328,9329,9325,0,898,903,904,900,9302,9307,9308,9304,43,9328,7864,7866,9329,0,903,119,120,904,9307,7843,7845,9308,43,9330,9331,9332,9333,0,905,905,906,906,9309,9310,9311,9312,43,9331,9334,9335,9332,0,905,905,906,906,9310,9313,9314,9311,43,9333,9332,9336,9337,0,906,906,12,12,9312,9311,9315,9316,43,9332,9335,9338,9336,0,906,906,12,12,9311,9314,9317,9315,43,9330,9339,9340,9331,0,905,9,9,905,9309,9318,9319,9310,43,9339,1903,1902,9340,0,9,156,156,9,9318,1903,1902,9319,43,9331,9340,9341,9334,0,905,9,9,905,9310,9319,9320,9313,43,9340,1902,1905,9341,0,9,156,156,9,9319,1902,1905,9320,43,9330,9342,9343,9339,0,905,905,9,9,9309,9321,9322,9318,43,9342,9344,9345,9343,0,905,905,9,9,9321,9323,9324,9322,43,9339,9343,1910,1903,0,9,9,156,156,9318,9322,1910,1903,43,9343,9345,1911,1910,0,9,9,156,156,9322,9324,1911,1910,43,9330,9333,9346,9342,0,905,906,906,905,9309,9312,9325,9321,43,9333,9337,9347,9346,0,906,12,12,906,9312,9316,9326,9325,43,9342,9346,9348,9344,0,905,906,906,905,9321,9325,9327,9323,43,9346,9347,9349,9348,0,906,12,12,906,9325,9326,9328,9327,43,9350,9351,9352,9353,0,10,10,906,906,9329,9330,9331,9332,43,9351,9354,9355,9352,0,10,10,906,906,9330,9333,9334,9331,43,9353,9352,9356,9357,0,906,906,12,12,9332,9331,9335,9336,43,9352,9355,9358,9356,0,906,906,12,12,9331,9334,9337,9335,43,9350,9359,9360,9351,0,10,67,67,10,9329,9338,9339,9330,43,9359,9361,9362,9360,0,67,6,6,67,9338,9340,9341,9339,43,9351,9360,9363,9354,0,10,67,67,10,9330,9339,9342,9333,43,9360,9362,9364,9363,0,67,6,6,67,9339,9341,9343,9342,43,9350,9365,9366,9359,0,10,10,67,67,9329,9344,9345,9338,43,9365,9367,9368,9366,0,10,907,67,67,9344,9346,9347,9345,43,9359,9366,9369,9361,0,67,67,6,6,9338,9345,9348,9340,43,9366,9368,9370,9369,0,67,67,6,6,9345,9347,9349,9348,43,9350,9353,9371,9365,0,10,906,906,10,9329,9332,9350,9344,43,9353,9357,9372,9371,0,906,12,12,906,9332,9336,9351,9350,43,9365,9371,9373,9367,0,10,906,908,907,9344,9350,9352,9346,43,9371,9372,9374,9373,0,906,12,12,908,9350,9351,9353,9352,43,9375,9376,9377,9378,0,909,909,910,910,9354,9355,9356,9357,43,9376,9379,9380,9377,0,909,909,910,910,9355,9358,9359,9356,43,9378,9377,9362,9361,0,910,910,911,911,9357,9356,9341,9340,43,9377,9380,9364,9362,0,910,910,911,911,9356,9359,9343,9341,43,9375,9381,9382,9376,0,909,912,912,909,9354,9360,9361,9355,43,9381,9383,9384,9382,0,912,913,913,912,9360,9362,9363,9361,43,9376,9382,9385,9379,0,909,912,912,909,9355,9361,9364,9358,43,9382,9384,9386,9385,0,912,913,913,912,9361,9363,9365,9364,43,9375,9387,9388,9381,0,909,909,912,912,9354,9366,9367,9360,43,9387,9389,9390,9388,0,909,909,912,912,9366,9368,9369,9367,43,9381,9388,9391,9383,0,912,912,913,913,9360,9367,9370,9362,43,9388,9390,9392,9391,0,912,912,913,913,9367,9369,9371,9370,43,9375,9378,9393,9387,0,909,910,910,909,9354,9357,9372,9366,43,9378,9361,9369,9393,0,910,911,911,910,9357,9340,9348,9372,43,9387,9393,9394,9389,0,909,910,910,909,9366,9372,9373,9368,43,9393,9369,9370,9394,0,910,911,911,910,9372,9348,9349,9373,43,9395,9396,9397,9398,0,914,915,915,914,9374,9375,9376,9377,43,9396,9399,9400,9397,0,915,916,916,915,9375,9378,9379,9376,43,9398,9397,9401,9402,0,914,915,915,914,9377,9376,9380,9381,43,9397,9400,9403,9401,0,915,916,916,915,9376,9379,9382,9380,43,9395,9404,9405,9396,0,914,914,915,915,9374,9383,9384,9375,43,9404,9406,9407,9405,0,914,914,915,915,9383,9385,9386,9384,43,9396,9405,9408,9399,0,915,915,916,916,9375,9384,9387,9378,43,9405,9407,9409,9408,0,915,915,916,916,9384,9386,9388,9387,43,9395,9410,9411,9404,0,914,917,917,914,9374,9389,9390,9383,43,9410,9383,9391,9411,0,917,913,913,917,9389,9362,9370,9390,43,9404,9411,9412,9406,0,914,917,917,914,9383,9390,9391,9385,43,9411,9391,9392,9412,0,917,913,913,917,9390,9370,9371,9391,43,9395,9398,9413,9410,0,914,914,917,917,9374,9377,9392,9389,43,9398,9402,9414,9413,0,914,914,917,917,9377,9381,9393,9392,43,9410,9413,9384,9383,0,917,917,913,913,9389,9392,9363,9362,43,9413,9414,9386,9384,0,917,917,913,913,9392,9393,9365,9363,43,9415,9416,9417,9418,0,918,919,919,918,9394,9395,9396,9397,43,9416,9337,9336,9417,0,919,12,12,919,9395,9316,9315,9396,43,9418,9417,9419,9420,0,918,919,919,918,9397,9396,9398,9399,43,9417,9336,9338,9419,0,919,12,12,919,9396,9315,9317,9398,43,9415,9421,9422,9416,0,918,918,919,919,9394,9400,9401,9395,43,9421,9423,9424,9422,0,918,921,920,919,9400,9402,9403,9401,43,9416,9422,9347,9337,0,919,919,12,12,9395,9401,9326,9316,43,9422,9424,9349,9347,0,919,920,12,12,9401,9403,9328,9326,43,9415,9425,9426,9421,0,918,922,922,918,9394,9404,9405,9400,43,9425,9399,9408,9426,0,922,916,916,922,9404,9378,9387,9405,43,9421,9426,9427,9423,0,918,922,922,921,9400,9405,9406,9402,43,9426,9408,9409,9427,0,922,916,916,922,9405,9387,9388,9406,43,9415,9418,9428,9425,0,918,918,922,922,9394,9397,9407,9404,43,9418,9420,9429,9428,0,918,918,922,922,9397,9399,9408,9407,43,9425,9428,9400,9399,0,922,922,916,916,9404,9407,9379,9378,43,9428,9429,9403,9400,0,922,922,916,916,9407,9408,9382,9379,43,9430,9431,9432,9433,0,923,923,924,924,9409,9410,9411,9412,43,9431,9434,9435,9432,0,923,923,924,924,9410,9413,9414,9411,43,9433,9432,2003,2002,0,924,924,925,925,9412,9411,2003,2002,43,9432,9435,2004,2003,0,924,924,925,925,9411,9414,2004,2003,43,9430,9436,9437,9431,0,923,919,919,923,9409,9415,9416,9410,43,9436,9357,9356,9437,0,919,12,12,919,9415,9336,9335,9416,43,9431,9437,9438,9434,0,923,919,919,923,9410,9416,9417,9413,43,9437,9356,9358,9438,0,919,12,12,919,9416,9335,9337,9417,43,9430,9439,9440,9436,0,923,923,919,919,9409,9418,9419,9415,43,9439,9441,9442,9440,0,923,926,920,919,9418,9420,9421,9419,43,9436,9440,9372,9357,0,919,919,12,12,9415,9419,9351,9336,43,9440,9442,9374,9372,0,919,920,12,12,9419,9421,9353,9351,43,9430,9433,9443,9439,0,923,924,924,923,9409,9412,9422,9418,43,9433,2002,2013,9443,0,924,925,925,924,9412,2002,2013,9422,43,9439,9443,9444,9441,0,923,924,924,926,9418,9422,9423,9420,43,9443,2013,2015,9444,0,924,925,925,924,9422,2013,2015,9423,43,9445,9446,9447,9448,0,12,929,928,927,9424,9425,9426,9427,43,9446,9449,9450,9447,0,929,929,928,928,9425,9428,9429,9426,43,9448,9447,9451,9452,0,927,928,931,930,9427,9426,9430,9431,43,9447,9450,9453,9451,0,928,928,931,931,9426,9429,9432,9430,43,9445,9454,9455,9446,0,12,932,12,929,9424,9433,9434,9425,43,9454,9456,9457,9455,0,932,932,12,12,9433,9435,9436,9434,43,9446,9455,9458,9449,0,929,12,12,929,9425,9434,9437,9428,43,9455,9457,9459,9458,0,12,12,12,12,9434,9436,9438,9437,43,9445,9460,9461,9454,0,12,934,933,932,9424,9439,9440,9433,43,9460,9462,9463,9461,0,934,935,328,933,9439,9441,9442,9440,43,9454,9461,9464,9456,0,932,933,933,932,9433,9440,9443,9435,43,9461,9463,9465,9464,0,933,328,328,933,9440,9442,9444,9443,43,9445,9448,9466,9460,0,12,927,12,934,9424,9427,9445,9439,43,9448,9452,9467,9466,0,927,930,936,12,9427,9431,9446,9445,43,9460,9466,9468,9462,0,934,12,937,935,9439,9445,9447,9441,43,9466,9467,9374,9468,0,12,936,12,937,9445,9446,9353,9447,43,9469,9470,9471,9472,0,938,924,924,938,9448,9449,9450,9451,43,9470,2045,2044,9471,0,924,925,925,924,9449,2045,2044,9450,43,9472,9471,9473,9474,0,938,924,924,938,9451,9450,9452,9453,43,9471,2044,2048,9473,0,924,925,925,924,9450,2044,2048,9452,43,9469,9475,9476,9470,0,938,938,924,924,9448,9454,9455,9449,43,9475,9477,9478,9476,0,938,938,924,924,9454,9456,9457,9455,43,9470,9476,2053,2045,0,924,924,925,925,9449,9455,2053,2045,43,9476,9478,2054,2053,0,924,924,925,925,9455,9457,2054,2053,43,9469,9479,9480,9475,0,938,939,939,938,9448,9458,9459,9454,43,9479,9481,9482,9480,0,939,328,328,939,9458,9460,9461,9459,43,9475,9480,9483,9477,0,938,939,939,938,9454,9459,9462,9456,43,9480,9482,9484,9483,0,939,328,328,939,9459,9461,9463,9462,43,9469,9472,9485,9479,0,938,938,939,939,9448,9451,9464,9458,43,9472,9474,9486,9485,0,938,938,939,939,9451,9453,9465,9464,43,9479,9485,9487,9481,0,939,939,328,328,9458,9464,9466,9460,43,9485,9486,9488,9487,0,939,939,328,328,9464,9465,9467,9466,43,9489,9490,9491,9492,0,940,924,924,941,9468,9469,9470,9471,43,9490,2070,2069,9491,0,924,925,925,924,9469,2070,2069,9470,43,9492,9491,9345,9344,0,941,924,924,923,9471,9470,9324,9323,43,9491,2069,1911,9345,0,924,925,925,924,9470,2069,1911,9324,43,9489,9493,9494,9490,0,940,938,924,924,9468,9472,9473,9469,43,9493,9474,9473,9494,0,938,938,924,924,9472,9453,9452,9473,43,9490,9494,2073,2070,0,924,924,925,925,9469,9473,2073,2070,43,9494,9473,2048,2073,0,924,924,925,925,9473,9452,2048,2073,43,9489,9495,9496,9493,0,940,942,939,938,9468,9474,9475,9472,43,9495,9497,9498,9496,0,942,943,328,939,9474,9476,9477,9475,43,9493,9496,9486,9474,0,938,939,939,938,9472,9475,9465,9453,43,9496,9498,9488,9486,0,939,328,328,939,9475,9477,9467,9465,43,9489,9492,9499,9495,0,940,941,944,942,9468,9471,9478,9474,43,9492,9344,9348,9499,0,941,923,919,944,9471,9323,9327,9478,43,9495,9499,9500,9497,0,942,944,945,943,9474,9478,9479,9476,43,9499,9348,9349,9500,0,944,919,12,945,9478,9327,9328,9479,43,9501,9502,9503,9504,0,938,924,924,938,9480,9481,9482,9483,43,9502,2085,2084,9503,0,924,925,925,924,9481,2085,2084,9482,43,9504,9503,9478,9477,0,938,924,924,938,9483,9482,9457,9456,43,9503,2084,2054,9478,0,924,925,925,924,9482,2084,2054,9457,43,9501,9505,9506,9502,0,938,938,924,924,9480,9484,9485,9481,43,9505,9507,9508,9506,0,938,938,924,924,9484,9486,9487,9485,43,9502,9506,2090,2085,0,924,924,925,925,9481,9485,2090,2085,43,9506,9508,2091,2090,0,924,924,925,925,9485,9487,2091,2090,43,9501,9509,9510,9505,0,938,939,939,938,9480,9488,9489,9484,43,9509,9511,9512,9510,0,939,328,328,939,9488,9490,9491,9489,43,9505,9510,9513,9507,0,938,939,939,938,9484,9489,9492,9486,43,9510,9512,9465,9513,0,939,328,328,939,9489,9491,9444,9492,43,9501,9504,9514,9509,0,938,938,939,939,9480,9483,9493,9488,43,9504,9477,9483,9514,0,938,938,939,939,9483,9456,9462,9493,43,9509,9514,9515,9511,0,939,939,328,328,9488,9493,9494,9490,43,9514,9483,9484,9515,0,939,939,328,328,9493,9462,9463,9494,43,9516,9517,9518,9519,0,946,947,67,67,9495,9496,9497,9498,43,9517,9520,9521,9518,0,947,947,67,67,9496,9499,9500,9497,43,9519,9518,9522,9523,0,67,67,6,6,9498,9497,9501,9502,43,9518,9521,9524,9522,0,67,67,6,6,9497,9500,9503,9501,43,9516,9525,9526,9517,0,946,949,948,947,9495,9504,9505,9496,43,9525,9452,9451,9526,0,949,930,931,948,9504,9431,9430,9505,43,9517,9526,9527,9520,0,947,948,948,947,9496,9505,9506,9499,43,9526,9451,9453,9527,0,948,931,931,948,9505,9430,9432,9506,43,9516,9528,9529,9525,0,946,951,950,949,9495,9507,9508,9504,43,9528,9367,9373,9529,0,951,907,908,950,9507,9346,9352,9508,43,9525,9529,9467,9452,0,949,950,936,930,9504,9508,9446,9431,43,9529,9373,9374,9467,0,950,908,12,936,9508,9352,9353,9446,43,9516,9519,9530,9528,0,946,67,67,951,9495,9498,9509,9507,43,9519,9523,9531,9530,0,67,6,6,67,9498,9502,9510,9509,43,9528,9530,9368,9367,0,951,67,67,907,9507,9509,9347,9346,43,9530,9531,9370,9368,0,67,6,6,67,9509,9510,9349,9347,43,9532,9533,9534,9535,0,909,909,912,912,9511,9512,9513,9514,43,9533,9536,9537,9534,0,909,909,912,912,9512,9515,9516,9513,43,9535,9534,9538,9539,0,912,912,913,913,9514,9513,9517,9518,43,9534,9537,9540,9538,0,912,912,913,913,9513,9516,9519,9517,43,9532,9541,9542,9533,0,909,910,910,909,9511,9520,9521,9512,43,9541,9523,9522,9542,0,910,911,911,910,9520,9502,9501,9521,43,9533,9542,9543,9536,0,909,910,910,909,9512,9521,9522,9515,43,9542,9522,9524,9543,0,910,911,911,910,9521,9501,9503,9522,43,9532,9544,9545,9541,0,909,909,910,910,9511,9523,9524,9520,43,9544,9389,9394,9545,0,909,909,910,910,9523,9368,9373,9524,43,9541,9545,9531,9523,0,910,910,911,911,9520,9524,9510,9502,43,9545,9394,9370,9531,0,910,910,911,911,9524,9373,9349,9510,43,9532,9535,9546,9544,0,909,912,912,909,9511,9514,9525,9523,43,9535,9539,9547,9546,0,912,913,913,912,9514,9518,9526,9525,43,9544,9546,9390,9389,0,909,912,912,909,9523,9525,9369,9368,43,9546,9547,9392,9390,0,912,913,913,912,9525,9526,9371,9369,43,9548,9549,9550,9551,0,914,917,917,914,9527,9528,9529,9530,43,9549,9539,9538,9550,0,917,913,913,917,9528,9518,9517,9529,43,9551,9550,9552,9553,0,914,917,917,914,9530,9529,9531,9532,43,9550,9538,9540,9552,0,917,913,913,917,9529,9517,9519,9531,43,9548,9554,9555,9549,0,914,914,917,917,9527,9533,9534,9528,43,9554,9406,9412,9555,0,914,914,917,917,9533,9385,9391,9534,43,9549,9555,9547,9539,0,917,917,913,913,9528,9534,9526,9518,43,9555,9412,9392,9547,0,917,917,913,913,9534,9391,9371,9526,43,9548,9556,9557,9554,0,914,915,915,914,9527,9535,9536,9533,43,9556,9558,9559,9557,0,915,916,916,915,9535,9537,9538,9536,43,9554,9557,9407,9406,0,914,915,915,914,9533,9536,9386,9385,43,9557,9559,9409,9407,0,915,916,916,915,9536,9538,9388,9386,43,9548,9551,9560,9556,0,914,914,915,915,9527,9530,9539,9535,43,9551,9553,9561,9560,0,914,914,915,915,9530,9532,9540,9539,43,9556,9560,9562,9558,0,915,915,916,916,9535,9539,9541,9537,43,9560,9561,9563,9562,0,915,915,916,916,9539,9540,9542,9541,43,9564,9565,9566,9567,0,952,922,922,953,9543,9544,9545,9546,43,9565,9558,9562,9566,0,922,916,916,922,9544,9537,9541,9545,43,9567,9566,9568,9569,0,953,922,922,953,9546,9545,9547,9548,43,9566,9562,9563,9568,0,922,916,916,922,9545,9541,9542,9547,43,9564,9570,9571,9565,0,952,954,922,922,9543,9549,9550,9544,43,9570,9423,9427,9571,0,954,921,922,922,9549,9402,9406,9550,43,9565,9571,9559,9558,0,922,922,916,916,9544,9550,9538,9537,43,9571,9427,9409,9559,0,922,922,916,916,9550,9406,9388,9538,43,9564,9572,9573,9570,0,952,942,944,954,9543,9551,9552,9549,43,9572,9574,9575,9573,0,942,943,945,944,9551,9553,9554,9552,43,9570,9573,9424,9423,0,954,944,920,921,9549,9552,9403,9402,43,9573,9575,9349,9424,0,944,945,12,920,9552,9554,9328,9403,43,9564,9567,9576,9572,0,952,953,939,942,9543,9546,9555,9551,43,9567,9569,9577,9576,0,953,953,939,939,9546,9548,9556,9555,43,9572,9576,9578,9574,0,942,939,328,943,9551,9555,9557,9553,43,9576,9577,9579,9578,0,939,939,328,328,9555,9556,9558,9557,43,9580,9581,9582,9583,0,932,932,12,12,9559,9560,9561,9562,43,9581,9584,9585,9582,0,932,932,12,12,9560,9563,9564,9561,43,9583,9582,9586,9587,0,12,12,12,12,9562,9561,9565,9566,43,9582,9585,9588,9586,0,12,12,12,12,9561,9564,9567,9565,43,9580,9589,9590,9581,0,932,933,933,932,9559,9568,9569,9560,43,9589,9481,9487,9590,0,933,328,328,933,9568,9460,9466,9569,43,9581,9590,9591,9584,0,932,933,933,932,9560,9569,9570,9563,43,9590,9487,9488,9591,0,933,328,328,933,9569,9466,9467,9570,43,9580,9592,9593,9589,0,932,932,933,933,9559,9571,9572,9568,43,9592,9594,9595,9593,0,932,932,933,933,9571,9573,9574,9572,43,9589,9593,9482,9481,0,933,933,328,328,9568,9572,9461,9460,43,9593,9595,9484,9482,0,933,933,328,328,9572,9574,9463,9461,43,9580,9583,9596,9592,0,932,12,12,932,9559,9562,9575,9571,43,9583,9587,9597,9596,0,12,12,12,12,9562,9566,9576,9575,43,9592,9596,9598,9594,0,932,12,12,932,9571,9575,9577,9573,43,9596,9597,9599,9598,0,12,12,12,12,9575,9576,9578,9577,43,9600,9601,9602,9603,0,955,933,933,932,9579,9580,9581,9582,43,9601,9574,9578,9602,0,933,943,328,933,9580,9553,9557,9581,43,9603,9602,9604,9605,0,932,933,933,932,9582,9581,9583,9584,43,9602,9578,9579,9604,0,933,328,328,933,9581,9557,9558,9583,43,9600,9606,9607,9601,0,955,933,933,933,9579,9585,9586,9580,43,9606,9497,9500,9607,0,933,943,945,933,9585,9476,9479,9586,43,9601,9607,9575,9574,0,933,933,945,943,9580,9586,9554,9553,43,9607,9500,9349,9575,0,933,945,12,945,9586,9479,9328,9554,43,9600,9608,9609,9606,0,955,932,933,933,9579,9587,9588,9585,43,9608,9584,9591,9609,0,932,932,933,933,9587,9563,9570,9588,43,9606,9609,9498,9497,0,933,933,328,943,9585,9588,9477,9476,43,9609,9591,9488,9498,0,933,933,328,328,9588,9570,9467,9477,43,9600,9603,9610,9608,0,955,932,12,932,9579,9582,9589,9587,43,9603,9605,9611,9610,0,932,932,12,12,9582,9584,9590,9589,43,9608,9610,9585,9584,0,932,12,12,932,9587,9589,9564,9563,43,9610,9611,9588,9585,0,12,12,12,12,9589,9590,9567,9564,43,9612,9613,9614,9615,0,932,12,12,932,9591,9592,9593,9594,43,9613,9616,9617,9614,0,12,12,12,12,9592,9595,9596,9593,43,9615,9614,9457,9456,0,932,12,12,932,9594,9593,9436,9435,43,9614,9617,9459,9457,0,12,12,12,12,9593,9596,9438,9436,43,9612,9618,9619,9613,0,932,932,12,12,9591,9597,9598,9592,43,9618,9594,9598,9619,0,932,932,12,12,9597,9573,9577,9598,43,9613,9619,9620,9616,0,12,12,12,12,9592,9598,9599,9595,43,9619,9598,9599,9620,0,12,12,12,12,9598,9577,9578,9599,43,9612,9621,9622,9618,0,932,933,933,932,9591,9600,9601,9597,43,9621,9511,9515,9622,0,933,328,328,933,9600,9490,9494,9601,43,9618,9622,9595,9594,0,932,933,933,932,9597,9601,9574,9573,43,9622,9515,9484,9595,0,933,328,328,933,9601,9494,9463,9574,43,9612,9615,9623,9621,0,932,932,933,933,9591,9594,9602,9600,43,9615,9456,9464,9623,0,932,932,933,933,9594,9435,9443,9602,43,9621,9623,9512,9511,0,933,933,328,328,9600,9602,9491,9490,43,9623,9464,9465,9512,0,933,933,328,328,9602,9443,9444,9491,43,9624,9625,9626,9627,0,956,959,958,957,9603,9604,9605,9606,43,9625,9321,9327,9626,0,959,961,960,958,9604,9300,9306,9605,43,9627,9626,8094,8093,0,957,958,371,372,9606,9605,8073,8072,43,9626,9327,8096,8094,0,958,960,374,371,9605,9306,8075,8073,43,9624,9628,9629,9625,0,956,963,962,959,9603,9607,9608,9604,43,9628,9295,9301,9629,0,963,866,871,962,9607,9274,9280,9608,43,9625,9629,9322,9321,0,959,962,964,961,9604,9608,9301,9300,43,9629,9301,8892,9322,0,962,871,872,964,9608,9280,8871,9301,43,9624,9630,9631,9628,0,956,966,965,963,9603,9609,9610,9607,43,9630,8042,8051,9631,0,966,316,324,965,9609,8021,8030,9610,43,9628,9631,9296,9295,0,963,965,865,866,9607,9610,9275,9274,43,9631,8051,8052,9296,0,965,324,325,865,9610,8030,8031,9275,43,9624,9627,9632,9630,0,956,957,967,966,9603,9606,9611,9609,43,9627,8093,8101,9632,0,957,372,379,967,9606,8072,8080,9611,43,9630,9632,8043,8042,0,966,967,315,316,9609,9611,8022,8021,43,9632,8101,8046,8043,0,967,379,319,315,9611,8080,8025,8022,43,9633,9634,9635,9636,0,968,971,970,969,9612,9613,9614,9615,43,9634,9637,9638,9635,0,971,973,972,970,9613,9616,9617,9614,43,9636,9635,9639,9640,0,969,970,975,974,9615,9614,9618,9619,43,9635,9638,9641,9639,0,970,972,976,975,9614,9617,9620,9618,43,9633,9642,9643,9634,0,968,978,977,971,9612,9621,9622,9613,43,9642,9644,9645,9643,0,978,980,979,977,9621,9623,9624,9622,43,9634,9643,9646,9637,0,971,977,981,973,9613,9622,9625,9616,43,9643,9645,9647,9646,0,977,979,982,981,9622,9624,9626,9625,43,9633,9648,9649,9642,0,968,984,983,978,9612,9627,9628,9621,43,9648,9650,9651,9649,0,984,986,985,983,9627,9629,9630,9628,43,9642,9649,9652,9644,0,978,983,987,980,9621,9628,9631,9623,43,9649,9651,9653,9652,0,983,985,988,987,9628,9630,9632,9631,43,9633,9636,9654,9648,0,968,969,989,984,9612,9615,9633,9627,43,9636,9640,9655,9654,0,969,974,990,989,9615,9619,9634,9633,43,9648,9654,9656,9650,0,984,989,991,986,9627,9633,9635,9629,43,9654,9655,9657,9656,0,989,990,992,991,9633,9634,9636,9635,43,9658,9659,9660,9661,0,993,996,995,994,9637,9638,9639,9640,43,9659,9650,9656,9660,0,996,986,991,995,9638,9629,9635,9639,43,9661,9660,9662,9663,0,994,995,998,997,9640,9639,9641,9642,43,9660,9656,9657,9662,0,995,991,992,998,9639,9635,9636,9641,43,9658,9664,9665,9659,0,993,1000,999,996,9637,9643,9644,9638,43,9664,9666,9667,9665,0,1000,1002,1001,999,9643,9645,9646,9644,43,9659,9665,9651,9650,0,996,999,985,986,9638,9644,9630,9629,43,9665,9667,9653,9651,0,999,1001,988,985,9644,9646,9632,9630,43,9658,9668,9669,9664,0,993,1003,1003,1000,9637,9647,9648,9643,43,9668,2254,2253,9669,0,1003,1004,1004,1003,9647,2254,2253,9648,43,9664,9669,9670,9666,0,1000,1003,1003,1002,9643,9648,9649,9645,43,9669,2253,2256,9670,0,1003,1004,1004,1003,9648,2253,2256,9649,43,9658,9661,9671,9668,0,993,994,1003,1003,9637,9640,9650,9647,43,9661,9663,9672,9671,0,994,997,1003,1003,9640,9642,9651,9650,43,9668,9671,2259,2254,0,1003,1003,1004,1004,9647,9650,2259,2254,43,9671,9672,2260,2259,0,1003,1003,1004,1004,9650,9651,2260,2259,43,9673,9674,9675,9676,0,1005,1006,1006,1005,9652,9653,9654,9655,43,9674,2266,2265,9675,0,1006,1007,1007,1006,9653,2266,2265,9654,43,9676,9675,9677,9678,0,1005,1006,1006,1005,9655,9654,9656,9657,43,9675,2265,2269,9677,0,1006,1007,1007,1006,9654,2265,2269,9656,43,9673,9679,9680,9674,0,1005,1005,1006,1006,9652,9658,9659,9653,43,9679,9681,9682,9680,0,1005,1005,1006,1006,9658,9660,9661,9659,43,9674,9680,2274,2266,0,1006,1006,1007,1007,9653,9659,2274,2266,43,9680,9682,2275,2274,0,1006,1006,1007,1007,9659,9661,2275,2274,43,9673,9683,9684,9679,0,1005,1008,1008,1005,9652,9662,9663,9658,43,9683,9685,9686,9684,0,1008,1009,1009,1008,9662,9664,9665,9663,43,9679,9684,9687,9681,0,1005,1008,1008,1005,9658,9663,9666,9660,43,9684,9686,9688,9687,0,1008,1009,1010,1008,9663,9665,9667,9666,43,9673,9676,9689,9683,0,1005,1005,1008,1008,9652,9655,9668,9662,43,9676,9678,9690,9689,0,1005,1005,1008,1008,9655,9657,9669,9668,43,9683,9689,9691,9685,0,1008,1008,1009,1009,9662,9668,9670,9664,43,9689,9690,9692,9691,0,1008,1008,1010,1009,9668,9669,9671,9670,43,9693,9694,9695,9696,0,1005,1006,1006,1005,9672,9673,9674,9675,43,9694,2291,2290,9695,0,1006,1007,1007,1006,9673,2291,2290,9674,43,9696,9695,9697,9698,0,1005,1006,1006,1005,9675,9674,9676,9677,43,9695,2290,2294,9697,0,1006,1007,1007,1006,9674,2290,2294,9676,43,9693,9699,9700,9694,0,1005,1005,1006,1006,9672,9678,9679,9673,43,9699,9701,9702,9700,0,1005,1005,1006,1006,9678,9680,9681,9679,43,9694,9700,2299,2291,0,1006,1006,1007,1007,9673,9679,2299,2291,43,9700,9702,2300,2299,0,1006,1006,1007,1007,9679,9681,2300,2299,43,9693,9703,9704,9699,0,1005,1008,1008,1005,9672,9682,9683,9678,43,9703,9705,9706,9704,0,1008,1009,1009,1008,9682,9684,9685,9683,43,9699,9704,9707,9701,0,1005,1008,1008,1005,9678,9683,9686,9680,43,9704,9706,9708,9707,0,1008,1009,1010,1008,9683,9685,9687,9686,43,9693,9696,9709,9703,0,1005,1005,1008,1008,9672,9675,9688,9682,43,9696,9698,9710,9709,0,1005,1005,1008,1008,9675,9677,9689,9688,43,9703,9709,9711,9705,0,1008,1008,1009,1009,9682,9688,9690,9684,43,9709,9710,9712,9711,0,1008,1008,1009,1009,9688,9689,9691,9690,43,9713,9714,9715,9716,0,1011,1014,1013,1012,9692,9693,9694,9695,43,9714,9717,9718,9715,0,1014,1016,1015,1013,9693,9696,9697,9694,43,9716,9715,7883,7882,0,1012,1013,141,142,9695,9694,7862,7861,43,9715,9718,7886,7883,0,1013,1015,145,141,9694,9697,7865,7862,43,9713,9719,9720,9714,0,1011,1005,1008,1014,9692,9698,9699,9693,43,9719,9721,9722,9720,0,1005,1005,1008,1008,9698,9700,9701,9699,43,9714,9720,9723,9717,0,1014,1008,1009,1016,9693,9699,9702,9696,43,9720,9722,9724,9723,0,1008,1008,1010,1009,9699,9701,9703,9702,43,9713,9725,9726,9719,0,1011,1017,1006,1005,9692,9704,9705,9698,43,9725,2326,2325,9726,0,1017,1018,1007,1006,9704,2326,2325,9705,43,9719,9726,9727,9721,0,1005,1006,1006,1005,9698,9705,9706,9700,43,9726,2325,2328,9727,0,1006,1007,1007,1006,9705,2325,2328,9706,43,9713,9716,9728,9725,0,1011,1012,1019,1017,9692,9695,9707,9704,43,9716,7882,7889,9728,0,1012,142,150,1019,9695,7861,7868,9707,43,9725,9728,2330,2326,0,1017,1019,1020,1018,9704,9707,2330,2326,43,9728,7889,406,2330,0,1019,150,151,1020,9707,7868,406,2330,43,9729,9730,9731,9732,0,1021,1023,1022,1022,9708,9709,9710,9711,43,9730,9733,9734,9731,0,1023,1024,1022,1022,9709,9712,9713,9710,43,9732,9731,9686,9685,0,1022,1022,1009,1009,9711,9710,9665,9664,43,9731,9734,9688,9686,0,1022,1022,1010,1009,9710,9713,9667,9665,43,9729,9735,9736,9730,0,1021,1026,1025,1023,9708,9714,9715,9709,43,9735,9737,9738,9736,0,1026,1028,1027,1025,9714,9716,9717,9715,43,9730,9736,9739,9733,0,1023,1025,1029,1024,9709,9715,9718,9712,43,9736,9738,9740,9739,0,1025,1027,1030,1029,9715,9717,9719,9718,43,9729,9741,9742,9735,0,1021,1032,1031,1026,9708,9720,9721,9714,43,9741,9743,9744,9742,0,1032,1034,1033,1031,9720,9722,9723,9721,43,9735,9742,9745,9737,0,1026,1031,1035,1028,9714,9721,9724,9716,43,9742,9744,9746,9745,0,1031,1033,1036,1035,9721,9723,9725,9724,43,9729,9732,9747,9741,0,1021,1022,1022,1032,9708,9711,9726,9720,43,9732,9685,9691,9747,0,1022,1009,1009,1022,9711,9664,9670,9726,43,9741,9747,9748,9743,0,1032,1022,1022,1034,9720,9726,9727,9722,43,9747,9691,9692,9748,0,1022,1009,1010,1022,9726,9670,9671,9727,43,9749,9750,9751,9752,0,1037,1022,1022,1038,9728,9729,9730,9731,43,9750,9705,9711,9751,0,1022,1009,1009,1022,9729,9684,9690,9730,43,9752,9751,9753,9754,0,1038,1022,1022,1039,9731,9730,9732,9733,43,9751,9711,9712,9753,0,1022,1009,1009,1022,9730,9690,9691,9732,43,9749,9755,9756,9750,0,1037,1040,1022,1022,9728,9734,9735,9729,43,9755,9757,9758,9756,0,1040,1041,1022,1022,9734,9736,9737,9735,43,9750,9756,9706,9705,0,1022,1022,1009,1009,9729,9735,9685,9684,43,9756,9758,9708,9706,0,1022,1022,1010,1009,9735,9737,9687,9685,43,9749,9759,9760,9755,0,1037,1043,1042,1040,9728,9738,9739,9734,43,9759,9761,9762,9760,0,1043,1045,1044,1042,9738,9740,9741,9739,43,9755,9760,9763,9757,0,1040,1042,1046,1041,9734,9739,9742,9736,43,9760,9762,9764,9763,0,1042,1044,1047,1046,9739,9741,9743,9742,43,9749,9752,9765,9759,0,1037,1038,1048,1043,9728,9731,9744,9738,43,9752,9754,9766,9765,0,1038,1039,1049,1048,9731,9733,9745,9744,43,9759,9765,9767,9761,0,1043,1048,1050,1045,9738,9744,9746,9740,43,9765,9766,9768,9767,0,1048,1049,1051,1050,9744,9745,9747,9746,43,9769,9770,9771,9772,0,1052,1055,1054,1053,9748,9749,9750,9751,43,9770,9773,9774,9771,0,1055,1057,1056,1054,9749,9752,9753,9750,43,9772,9771,8818,8817,0,1053,1054,609,610,9751,9750,8797,8796,43,9771,9774,8721,8818,0,1054,1056,508,609,9750,9753,8700,8797,43,9769,9775,9776,9770,0,1052,1059,1058,1055,9748,9754,9755,9749,43,9775,9777,9778,9776,0,1059,1059,1058,1058,9754,9756,9757,9755,43,9770,9776,9779,9773,0,1055,1058,1060,1057,9749,9755,9758,9752,43,9776,9778,9780,9779,0,1058,1058,1060,1060,9755,9757,9759,9758,43,9769,9781,9782,9775,0,1052,1061,1022,1059,9748,9760,9761,9754,43,9781,9717,9723,9782,0,1061,1016,1009,1022,9760,9696,9702,9761,43,9775,9782,9783,9777,0,1059,1022,1022,1059,9754,9761,9762,9756,43,9782,9723,9724,9783,0,1022,1009,1010,1022,9761,9702,9703,9762,43,9769,9772,9784,9781,0,1052,1053,1062,1061,9748,9751,9763,9760,43,9772,8817,8821,9784,0,1053,610,613,1062,9751,8796,8800,9763,43,9781,9784,9718,9717,0,1061,1062,1015,1016,9760,9763,9697,9696,43,9784,8821,7886,9718,0,1062,613,145,1015,9763,8800,7865,9697,43,9785,9786,9787,9788,0,1063,1066,1065,1064,9764,9765,9766,9767,43,9786,9789,9790,9787,0,1066,1068,1067,1065,9765,9768,9769,9766,43,9788,9787,9791,9792,0,1064,1065,1070,1069,9767,9766,9770,9771,43,9787,9790,9746,9791,0,1065,1067,1036,1070,9766,9769,9725,9770,43,9785,9793,9794,9786,0,1063,1072,1071,1066,9764,9772,9773,9765,43,9793,9795,9796,9794,0,1072,1074,1073,1071,9772,9774,9775,9773,43,9786,9794,9797,9789,0,1066,1071,1075,1068,9765,9773,9776,9768,43,9794,9796,9798,9797,0,1071,1073,1076,1075,9773,9775,9777,9776,43,9785,9799,9800,9793,0,1063,1078,1077,1072,9764,9778,9779,9772,43,9799,9801,9802,9800,0,1078,1080,1079,1077,9778,9780,9781,9779,43,9793,9800,9803,9795,0,1072,1077,1081,1074,9772,9779,9782,9774,43,9800,9802,9804,9803,0,1077,1079,1082,1081,9779,9781,9783,9782,43,9785,9788,9805,9799,0,1063,1064,1083,1078,9764,9767,9784,9778,43,9788,9792,9806,9805,0,1064,1069,1084,1083,9767,9771,9785,9784,43,9799,9805,9807,9801,0,1078,1083,1085,1080,9778,9784,9786,9780,43,9805,9806,8784,9807,0,1083,1084,572,1085,9784,9785,8763,9786,43,9808,9809,9810,9811,0,1086,1089,1088,1087,9787,9788,9789,9790,43,9809,9812,9813,9810,0,1089,1091,1090,1088,9788,9791,9792,9789,43,9811,9810,9762,9761,0,1087,1088,1044,1045,9790,9789,9741,9740,43,9810,9813,9764,9762,0,1088,1090,1047,1044,9789,9792,9743,9741,43,9808,9814,9815,9809,0,1086,1093,1092,1089,9787,9793,9794,9788,43,9814,9816,9817,9815,0,1093,1095,1094,1092,9793,9795,9796,9794,43,9809,9815,9818,9812,0,1089,1092,1096,1091,9788,9794,9797,9791,43,9815,9817,9819,9818,0,1092,1094,1097,1096,9794,9796,9798,9797,43,9808,9820,9821,9814,0,1086,1099,1098,1093,9787,9799,9800,9793,43,9820,9822,9823,9821,0,1099,1101,1100,1098,9799,9801,9802,9800,43,9814,9821,9824,9816,0,1093,1098,1102,1095,9793,9800,9803,9795,43,9821,9823,9825,9824,0,1098,1100,1103,1102,9800,9802,9804,9803,43,9808,9811,9826,9820,0,1086,1087,1104,1099,9787,9790,9805,9799,43,9811,9761,9767,9826,0,1087,1045,1050,1104,9790,9740,9746,9805,43,9820,9826,9827,9822,0,1099,1104,1105,1101,9799,9805,9806,9801,43,9826,9767,9768,9827,0,1104,1050,1051,1105,9805,9746,9747,9806,43,9828,9829,9830,9831,0,1106,1109,1108,1107,9807,9808,9809,9810,43,9829,9832,9833,9830,0,1109,1111,1110,1108,9808,9811,9812,9809,43,9831,9830,8734,8720,0,1107,1108,522,506,9810,9809,8713,8699,43,9830,9833,8736,8734,0,1108,1110,524,522,9809,9812,8715,8713,43,9828,9834,9835,9829,0,1106,1113,1112,1109,9807,9813,9814,9808,43,9834,9836,9837,9835,0,1113,1113,1112,1112,9813,9815,9816,9814,43,9829,9835,9838,9832,0,1109,1112,1114,1111,9808,9814,9817,9811,43,9835,9837,9839,9838,0,1112,1112,1114,1114,9814,9816,9818,9817,43,9828,9840,9841,9834,0,1106,1116,1115,1113,9807,9819,9820,9813,43,9840,9773,9779,9841,0,1116,1057,1060,1115,9819,9752,9758,9820,43,9834,9841,9842,9836,0,1113,1115,1115,1113,9813,9820,9821,9815,43,9841,9779,9780,9842,0,1115,1060,1060,1115,9820,9758,9759,9821,43,9828,9831,9843,9840,0,1106,1107,1117,1116,9807,9810,9822,9819,43,9831,8720,8719,9843,0,1107,506,507,1117,9810,8699,8698,9822,43,9840,9843,9774,9773,0,1116,1117,1056,1057,9819,9822,9753,9752,43,9843,8719,8721,9774,0,1117,507,508,1056,9822,8698,8700,9753,43,9844,9845,9846,9847,0,1118,1121,1120,1119,9823,9824,9825,9826,43,9845,9848,9849,9846,0,1121,1121,1120,1120,9824,9827,9828,9825,43,9847,9846,9850,9851,0,1119,1120,1123,1122,9826,9825,9829,9830,43,9846,9849,9852,9850,0,1120,1120,1123,1123,9825,9828,9831,9829,43,9844,9853,9854,9845,0,1118,1125,1124,1121,9823,9832,9833,9824,43,9853,9832,9838,9854,0,1125,1111,1114,1124,9832,9811,9817,9833,43,9845,9854,9855,9848,0,1121,1124,1124,1121,9824,9833,9834,9827,43,9854,9838,9839,9855,0,1124,1114,1114,1124,9833,9817,9818,9834,43,9844,9856,9857,9853,0,1118,1127,1126,1125,9823,9835,9836,9832,43,9856,8748,8754,9857,0,1127,537,542,1126,9835,8727,8733,9836,43,9853,9857,9833,9832,0,1125,1126,1110,1111,9832,9836,9812,9811,43,9857,8754,8736,9833,0,1126,542,524,1110,9836,8733,8715,9812,43,9844,9847,9858,9856,0,1118,1119,1128,1127,9823,9826,9837,9835,43,9847,9851,9859,9858,0,1119,1122,1129,1128,9826,9830,9838,9837,43,9856,9858,8749,8748,0,1127,1128,536,537,9835,9837,8728,8727,43,9858,9859,8751,8749,0,1128,1129,539,536,9837,9838,8730,8728,43,9860,9861,9862,9863,0,1130,1133,1132,1131,9839,9840,9841,9842,43,9861,9864,9865,9862,0,1133,1133,1132,1132,9840,9843,9844,9841,43,9863,9862,9866,9867,0,1131,1132,1135,1134,9842,9841,9845,9846,43,9862,9865,9868,9866,0,1132,1132,1135,1135,9841,9844,9847,9845,43,9860,9869,9870,9861,0,1130,1137,1136,1133,9839,9848,9849,9840,43,9869,9851,9850,9870,0,1137,1122,1123,1136,9848,9830,9829,9849,43,9861,9870,9871,9864,0,1133,1136,1136,1133,9840,9849,9850,9843,43,9870,9850,9852,9871,0,1136,1123,1123,1136,9849,9829,9831,9850,43,9860,9872,9873,9869,0,1130,1139,1138,1137,9839,9851,9852,9848,43,9872,8767,8773,9873,0,1139,556,561,1138,9851,8746,8752,9852,43,9869,9873,9859,9851,0,1137,1138,1129,1122,9848,9852,9838,9830,43,9873,8773,8751,9859,0,1138,561,539,1129,9852,8752,8730,9838,43,9860,9863,9874,9872,0,1130,1131,1140,1139,9839,9842,9853,9851,43,9863,9867,9875,9874,0,1131,1134,1141,1140,9842,9846,9854,9853,43,9872,9874,8768,8767,0,1139,1140,555,556,9851,9853,8747,8746,43,9874,9875,8770,8768,0,1140,1141,558,555,9853,9854,8749,8747,43,9876,9877,9878,9879,0,1142,1144,1003,1143,9855,9856,9857,9858,43,9877,9880,9881,9878,0,1144,1144,1003,1003,9856,9859,9860,9857,43,9879,9878,2485,2484,0,1143,1003,1004,1145,9858,9857,2485,2484,43,9882,2493,1490,8934,0,1150,1151,753,751,9861,2493,1490,8913,43,9876,9883,9884,9877,0,1142,1147,1146,1144,9855,9862,9863,9856,43,9883,9867,9866,9884,0,1147,1134,1135,1146,9862,9846,9845,9863,43,9877,9884,9885,9880,0,1144,1146,1146,1144,9856,9863,9864,9859,43,9884,9866,9868,9885,0,1146,1135,1135,1146,9863,9845,9847,9864,43,9876,9886,9887,9883,0,1142,1149,1148,1147,9855,9865,9866,9862,43,9886,8928,8927,9887,0,1149,741,742,1148,9865,8907,8906,9866,43,9883,9887,9875,9867,0,1147,1148,1141,1134,9862,9866,9854,9846,43,9887,8927,8770,9875,0,1148,742,558,1141,9866,8906,8749,9854,43,9876,9879,9882,9886,0,1142,1143,1150,1149,9855,9858,9861,9865,43,8931,8933,1489,1486,0,747,750,752,749,8910,8912,1489,1486,43,9886,9882,8934,8928,0,1149,1150,751,741,9865,9861,8913,8907,43,9888,9889,9890,9891,0,1005,1008,1008,1005,9867,9868,9869,9870,43,9889,9892,9893,9890,0,1008,1009,1009,1008,9868,9871,9872,9869,43,9891,9890,9722,9721,0,1005,1008,1008,1005,9870,9869,9701,9700,43,9890,9893,9724,9722,0,1008,1009,1010,1008,9869,9872,9703,9701,43,9888,9894,9895,9889,0,1005,1005,1008,1008,9867,9873,9874,9868,43,9894,9701,9707,9895,0,1005,1005,1008,1008,9873,9680,9686,9874,43,9889,9895,9896,9892,0,1008,1008,1009,1009,9868,9874,9875,9871,43,9895,9707,9708,9896,0,1008,1008,1010,1009,9874,9686,9687,9875,43,9888,9897,9898,9894,0,1005,1006,1006,1005,9867,9876,9877,9873,43,9897,2506,2505,9898,0,1006,1007,1007,1006,9876,2506,2505,9877,43,9894,9898,9702,9701,0,1005,1006,1006,1005,9873,9877,9681,9680,43,9898,2505,2300,9702,0,1006,1007,1007,1006,9877,2505,2300,9681,43,9888,9891,9899,9897,0,1005,1005,1006,1006,9867,9870,9878,9876,43,9891,9721,9727,9899,0,1005,1005,1006,1006,9870,9700,9706,9878,43,9897,9899,2508,2506,0,1006,1006,1007,1007,9876,9878,2508,2506,43,9899,9727,2328,2508,0,1006,1006,1007,1007,9878,9706,2328,2508,43,9900,9901,9902,9903,0,1152,1153,1058,1059,9879,9880,9881,9882,43,9901,9904,9905,9902,0,1153,1154,1060,1058,9880,9883,9884,9881,43,9903,9902,9778,9777,0,1059,1058,1058,1059,9882,9881,9757,9756,43,9902,9905,9780,9778,0,1058,1060,1060,1058,9881,9884,9759,9757,43,9900,9906,9907,9901,0,1152,1156,1155,1153,9879,9885,9886,9880,43,9906,9757,9763,9907,0,1156,1041,1046,1155,9885,9736,9742,9886,43,9901,9907,9908,9904,0,1153,1155,1157,1154,9880,9886,9887,9883,43,9907,9763,9764,9908,0,1155,1046,1047,1157,9886,9742,9743,9887,43,9900,9909,9910,9906,0,1152,1022,1022,1156,9879,9888,9889,9885,43,9909,9892,9896,9910,0,1022,1009,1009,1022,9888,9871,9875,9889,43,9906,9910,9758,9757,0,1156,1022,1022,1041,9885,9889,9737,9736,43,9910,9896,9708,9758,0,1022,1009,1010,1022,9889,9875,9687,9737,43,9900,9903,9911,9909,0,1152,1059,1022,1022,9879,9882,9890,9888,43,9903,9777,9783,9911,0,1059,1059,1022,1022,9882,9756,9762,9890,43,9909,9911,9893,9892,0,1022,1022,1009,1009,9888,9890,9872,9871,43,9911,9783,9724,9893,0,1022,1022,1010,1009,9890,9762,9703,9872,43,9912,9913,9914,9915,0,1158,1159,1112,1113,9891,9892,9893,9894,43,9913,9916,9917,9914,0,1159,1160,1114,1112,9892,9895,9896,9893,43,9915,9914,9837,9836,0,1113,1112,1112,1113,9894,9893,9816,9815,43,9914,9917,9839,9837,0,1112,1114,1114,1112,9893,9896,9818,9816,43,9912,9918,9919,9913,0,1158,1162,1161,1159,9891,9897,9898,9892,43,9918,9812,9818,9919,0,1162,1091,1096,1161,9897,9791,9797,9898,43,9913,9919,9920,9916,0,1159,1161,1163,1160,9892,9898,9899,9895,43,9919,9818,9819,9920,0,1161,1096,1097,1163,9898,9797,9798,9899,43,9912,9921,9922,9918,0,1158,1165,1164,1162,9891,9900,9901,9897,43,9921,9904,9908,9922,0,1165,1154,1157,1164,9900,9883,9887,9901,43,9918,9922,9813,9812,0,1162,1164,1090,1091,9897,9901,9792,9791,43,9922,9908,9764,9813,0,1164,1157,1047,1090,9901,9887,9743,9792,43,9912,9915,9923,9921,0,1158,1113,1115,1165,9891,9894,9902,9900,43,9915,9836,9842,9923,0,1113,1113,1115,1115,9894,9815,9821,9902,43,9921,9923,9905,9904,0,1165,1115,1060,1154,9900,9902,9884,9883,43,9923,9842,9780,9905,0,1115,1115,1060,1060,9902,9821,9759,9884,43,9924,9925,9926,9927,0,1166,1167,1120,1121,9903,9904,9905,9906,43,9925,9928,9929,9926,0,1167,1168,1123,1120,9904,9907,9908,9905,43,9927,9926,9849,9848,0,1121,1120,1120,1121,9906,9905,9828,9827,43,9926,9929,9852,9849,0,1120,1123,1123,1120,9905,9908,9831,9828,43,9924,9930,9931,9925,0,1166,1170,1169,1167,9903,9909,9910,9904,43,9930,9932,9933,9931,0,1170,1172,1171,1169,9909,9911,9912,9910,43,9925,9931,9934,9928,0,1167,1169,1173,1168,9904,9910,9913,9907,43,9931,9933,9935,9934,0,1169,1171,1174,1173,9910,9912,9914,9913,43,9924,9936,9937,9930,0,1166,1176,1175,1170,9903,9915,9916,9909,43,9936,9916,9920,9937,0,1176,1160,1163,1175,9915,9895,9899,9916,43,9930,9937,9938,9932,0,1170,1175,1177,1172,9909,9916,9917,9911,43,9937,9920,9819,9938,0,1175,1163,1097,1177,9916,9899,9798,9917,43,9924,9927,9939,9936,0,1166,1121,1124,1176,9903,9906,9918,9915,43,9927,9848,9855,9939,0,1121,1121,1124,1124,9906,9827,9834,9918,43,9936,9939,9917,9916,0,1176,1124,1114,1160,9915,9918,9896,9895,43,9939,9855,9839,9917,0,1124,1124,1114,1114,9918,9834,9818,9896,43,9940,9941,9942,9943,0,1178,1179,1132,1133,9919,9920,9921,9922,43,9941,9944,9945,9942,0,1179,1180,1135,1132,9920,9923,9924,9921,43,9943,9942,9865,9864,0,1133,1132,1132,1133,9922,9921,9844,9843,43,9942,9945,9868,9865,0,1132,1135,1135,1132,9921,9924,9847,9844,43,9940,9946,9947,9941,0,1178,1182,1181,1179,9919,9925,9926,9920,43,9946,9948,9949,9947,0,1182,1184,1183,1181,9925,9927,9928,9926,43,9941,9947,9950,9944,0,1179,1181,1185,1180,9920,9926,9929,9923,43,9947,9949,9951,9950,0,1181,1183,1186,1185,9926,9928,9930,9929,43,9940,9952,9953,9946,0,1178,1188,1187,1182,9919,9931,9932,9925,43,9952,9928,9934,9953,0,1188,1168,1173,1187,9931,9907,9913,9932,43,9946,9953,9954,9948,0,1182,1187,1189,1184,9925,9932,9933,9927,43,9953,9934,9935,9954,0,1187,1173,1174,1189,9932,9913,9914,9933,43,9940,9943,9955,9952,0,1178,1133,1136,1188,9919,9922,9934,9931,43,9943,9864,9871,9955,0,1133,1133,1136,1136,9922,9843,9850,9934,43,9952,9955,9929,9928,0,1188,1136,1123,1168,9931,9934,9908,9907,43,9955,9871,9852,9929,0,1136,1136,1123,1123,9934,9850,9831,9908,43,9956,9957,9958,9959,0,1190,1003,1003,1144,9935,9936,9937,9938,43,9957,2570,2569,9958,0,1003,1004,1004,1003,9936,2570,2569,9937,43,9959,9958,9881,9880,0,1144,1003,1003,1144,9938,9937,9860,9859,43,9958,2569,9960,9881,0,1003,1004,1004,1003,9937,2569,9939,9860,43,9956,9961,9962,9957,0,1190,1191,1003,1003,9935,9940,9941,9936,43,9961,9963,9964,9962,0,1191,1192,1003,1003,9940,9942,9943,9941,43,9957,9962,2575,2570,0,1003,1003,1004,1004,9936,9941,2575,2570,43,9962,9964,2576,2575,0,1003,1003,1004,1004,9941,9943,2576,2575,43,9956,9965,9966,9961,0,1190,1194,1193,1191,9935,9944,9945,9940,43,9965,9944,9950,9966,0,1194,1180,1185,1193,9944,9923,9929,9945,43,9961,9966,9967,9963,0,1191,1193,1195,1192,9940,9945,9946,9942,43,9966,9950,9951,9967,0,1193,1185,1186,1195,9945,9929,9930,9946,43,9956,9959,9968,9965,0,1190,1144,1146,1194,9935,9938,9947,9944,43,9959,9880,9885,9968,0,1144,1144,1146,1146,9938,9859,9864,9947,43,9965,9968,9945,9944,0,1194,1146,1135,1180,9944,9947,9924,9923,43,9968,9885,9868,9945,0,1146,1146,1135,1135,9947,9864,9847,9924,43,9969,9970,9971,9972,0,1196,1199,1198,1197,9948,9949,9950,9951,43,9970,9973,9974,9971,0,1199,1201,1200,1198,9949,9952,9953,9950,43,9972,9971,9975,9976,0,1197,1198,1203,1202,9951,9950,9954,9955,43,9971,9974,9977,9975,0,1198,1200,1204,1203,9950,9953,9956,9954,43,9969,9978,9979,9970,0,1196,1206,1205,1199,9948,9957,9958,9949,43,9978,9980,9981,9979,0,1206,1208,1207,1205,9957,9959,9960,9958,43,9970,9979,9982,9973,0,1199,1205,1209,1201,9949,9958,9961,9952,43,9979,9981,9983,9982,0,1205,1207,1210,1209,9958,9960,9962,9961,43,9969,9984,9985,9978,0,1196,1212,1211,1206,9948,9963,9964,9957,43,9984,9986,9987,9985,0,1212,1214,1213,1211,9963,9965,9966,9964,43,9978,9985,9988,9980,0,1206,1211,1215,1208,9957,9964,9967,9959,43,9985,9987,9989,9988,0,1211,1213,1216,1215,9964,9966,9968,9967,43,9969,9972,9990,9984,0,1196,1197,1217,1212,9948,9951,9969,9963,43,9972,9976,9991,9990,0,1197,1202,1218,1217,9951,9955,9970,9969,43,9984,9990,9992,9986,0,1212,1217,1219,1214,9963,9969,9971,9965,43,9990,9991,9740,9992,0,1217,1218,1030,1219,9969,9970,9719,9971,43,9993,9994,9995,9996,0,1220,1223,1222,1221,9972,9973,9974,9975,43,9994,2610,2609,9995,0,1223,1225,1224,1222,9973,2610,2609,9974,43,9996,9995,8944,8943,0,1221,1222,765,766,9975,9974,8923,8922,43,9995,2609,1505,8944,0,1222,1224,768,765,9974,2609,1505,8923,43,9993,9997,9998,9994,0,1220,1005,1006,1223,9972,9976,9977,9973,43,9997,9678,9677,9998,0,1005,1005,1006,1006,9976,9657,9656,9977,43,9994,9998,2613,2610,0,1223,1006,1007,1225,9973,9977,2613,2610,43,9998,9677,2269,2613,0,1006,1006,1007,1007,9977,9656,2269,2613,43,9993,9999,10000,9997,0,1220,1226,1008,1005,9972,9978,9979,9976,43,9999,10001,10002,10000,0,1226,1227,1009,1008,9978,9980,9981,9979,43,9997,10000,9690,9678,0,1005,1008,1008,1005,9976,9979,9669,9657,43,10000,10002,9692,9690,0,1008,1009,1010,1008,9979,9981,9671,9669,43,9993,9996,10003,9999,0,1220,1221,1228,1226,9972,9975,9982,9978,43,9996,8943,8947,10003,0,1221,766,771,1228,9975,8922,8926,9982,43,9999,10003,10004,10001,0,1226,1228,1229,1227,9978,9982,9983,9980,43,10003,8947,8800,10004,0,1228,771,588,1229,9982,8926,8779,9983,43,10005,10006,10007,10008,0,1230,1232,1022,1231,9984,9985,9986,9987,43,10006,9743,9748,10007,0,1232,1034,1022,1022,9985,9722,9727,9986,43,10008,10007,10002,10001,0,1231,1022,1009,1227,9987,9986,9981,9980,43,10007,9748,9692,10002,0,1022,1022,1010,1009,9986,9727,9671,9981,43,10005,10009,10010,10006,0,1230,1234,1233,1232,9984,9988,9989,9985,43,10009,9792,9791,10010,0,1234,1069,1070,1233,9988,9771,9770,9989,43,10006,10010,9744,9743,0,1232,1233,1033,1034,9985,9989,9723,9722,43,10010,9791,9746,9744,0,1233,1070,1036,1033,9989,9770,9725,9723,43,10005,10011,10012,10009,0,1230,1236,1235,1234,9984,9990,9991,9988,43,10011,8783,8782,10012,0,1236,570,571,1235,9990,8762,8761,9991,43,10009,10012,9806,9792,0,1234,1235,1084,1069,9988,9991,9785,9771,43,10012,8782,8784,9806,0,1235,571,572,1084,9991,8761,8763,9785,43,10005,10008,10013,10011,0,1230,1231,1237,1236,9984,9987,9992,9990,43,10008,10001,10004,10013,0,1231,1227,1229,1237,9987,9980,9983,9992,43,10011,10013,8798,8783,0,1236,1237,586,570,9990,9992,8777,8762,43,10013,10004,8800,8798,0,1237,1229,588,586,9992,9983,8779,8777,43,10014,10015,10016,10017,0,1238,1241,1240,1239,9993,9994,9995,9996,43,10015,8916,8920,10016,0,1241,729,734,1240,9994,8895,8899,9995,43,10017,10016,10018,10019,0,1239,1240,1243,1242,9996,9995,9997,9998,43,10016,8920,8066,10018,0,1240,734,344,1243,9995,8899,8045,9997,43,10014,10020,10021,10015,0,1238,1245,1244,1241,9993,9999,10000,9994,43,10020,2638,2637,10021,0,1245,1247,1246,1244,9999,2638,2637,10000,43,10015,10021,8917,8916,0,1241,1244,728,729,9994,10000,8896,8895,43,10021,2637,1469,8917,0,1244,1246,731,728,10000,2637,1469,8896,43,10014,10022,10023,10020,0,1238,1249,1248,1245,9993,10001,10002,9999,43,10022,10024,10025,10023,0,1249,1251,1250,1248,10001,10003,10004,10002,43,10020,10023,2643,2638,0,1245,1248,1252,1247,9999,10002,2643,2638,43,10023,10025,2644,2643,0,1248,1250,1253,1252,10002,10004,2644,2643,43,10014,10017,10026,10022,0,1238,1239,1254,1249,9993,9996,10005,10001,43,10017,10019,10027,10026,0,1239,1242,1255,1254,9996,9998,10006,10005,43,10022,10026,10028,10024,0,1249,1254,1256,1251,10001,10005,10007,10003,43,10026,10027,10029,10028,0,1254,1255,1257,1256,10005,10006,10008,10007,43,10030,10031,10032,10033,0,1258,1261,1260,1259,10009,10010,10011,10012,43,10031,8062,8071,10032,0,1261,341,349,1260,10010,8041,8050,10011,43,10033,10032,10034,10035,0,1259,1260,1263,1262,10012,10011,10013,10014,43,10032,8071,8072,10034,0,1260,349,350,1263,10011,8050,8051,10013,43,10030,10036,10037,10031,0,1258,1265,1264,1261,10009,10015,10016,10010,43,10036,10019,10018,10037,0,1265,1242,1243,1264,10015,9998,9997,10016,43,10031,10037,8063,8062,0,1261,1264,340,341,10010,10016,8042,8041,43,10037,10018,8066,8063,0,1264,1243,344,340,10016,9997,8045,8042,43,10030,10038,10039,10036,0,1258,1267,1266,1265,10009,10017,10018,10015,43,10038,10040,10041,10039,0,1267,1269,1268,1266,10017,10019,10020,10018,43,10036,10039,10027,10019,0,1265,1266,1255,1242,10015,10018,10006,9998,43,10039,10041,10029,10027,0,1266,1268,1257,1255,10018,10020,10008,10006,43,10030,10033,10042,10038,0,1258,1259,1270,1267,10009,10012,10021,10017,43,10033,10035,10043,10042,0,1259,1262,1271,1270,10012,10014,10022,10021,43,10038,10042,10044,10040,0,1267,1270,1272,1269,10017,10021,10023,10019,43,10042,10043,10045,10044,0,1270,1271,1273,1272,10021,10022,10024,10023,43,10046,10047,10048,10049,0,1274,1277,1276,1275,10025,10026,10027,10028,43,10047,10050,10051,10048,0,1277,1279,1278,1276,10026,10029,10030,10027,43,10049,10048,10052,10053,0,1275,1276,1281,1280,10028,10027,10031,10032,43,10048,10051,9804,10052,0,1276,1278,1082,1281,10027,10030,9783,10031,43,10046,10054,10055,10047,0,1274,1283,1282,1277,10025,10033,10034,10026,43,10054,10056,10057,10055,0,1283,1285,1284,1282,10033,10035,10036,10034,43,10047,10055,10058,10050,0,1277,1282,1286,1279,10026,10034,10037,10029,43,10055,10057,10059,10058,0,1282,1284,1287,1286,10034,10036,10038,10037,43,10046,10060,10061,10054,0,1274,1289,1288,1283,10025,10039,10040,10033,43,10060,10035,10034,10061,0,1289,1262,1263,1288,10039,10014,10013,10040,43,10054,10061,10062,10056,0,1283,1288,1290,1285,10033,10040,10041,10035,43,10061,10034,8072,10062,0,1288,1263,350,1290,10040,10013,8051,10041,43,10046,10049,10063,10060,0,1274,1275,1291,1289,10025,10028,10042,10039,43,10049,10053,10064,10063,0,1275,1280,1292,1291,10028,10032,10043,10042,43,10060,10063,10043,10035,0,1289,1291,1271,1262,10039,10042,10022,10014,43,10063,10064,10045,10043,0,1291,1292,1273,1271,10042,10043,10024,10022,43,10065,10066,10067,10068,0,1293,1296,1295,1294,10044,10045,10046,10047,43,10066,10069,10070,10067,0,1296,1298,1297,1295,10045,10048,10049,10046,43,10068,10067,10071,10072,0,1294,1295,1300,1299,10047,10046,10050,10051,43,10067,10070,10073,10071,0,1295,1297,1301,1300,10046,10049,10052,10050,43,10065,10074,10075,10066,0,1293,1303,1302,1296,10044,10053,10054,10045,43,10074,10076,10077,10075,0,1303,1305,1304,1302,10053,10055,10056,10054,43,10066,10075,10078,10069,0,1296,1302,1306,1298,10045,10054,10057,10048,43,10075,10077,10079,10078,0,1302,1304,1307,1306,10054,10056,10058,10057,43,10065,10080,10081,10074,0,1293,1309,1308,1303,10044,10059,10060,10053,43,10080,10082,10083,10081,0,1309,1311,1310,1308,10059,10061,10062,10060,43,10074,10081,10084,10076,0,1303,1308,1312,1305,10053,10060,10063,10055,43,10081,10083,10085,10084,0,1308,1310,1313,1312,10060,10062,10064,10063,43,10065,10068,10086,10080,0,1293,1294,1314,1309,10044,10047,10065,10059,43,10068,10072,10087,10086,0,1294,1299,1315,1314,10047,10051,10066,10065,43,10080,10086,10088,10082,0,1309,1314,1316,1311,10059,10065,10067,10061,43,10086,10087,10089,10088,0,1314,1315,1317,1316,10065,10066,10068,10067,43,10090,10091,10092,10093,0,1318,1321,1320,1319,10069,10070,10071,10072,43,10091,10094,10095,10092,0,1321,1323,1322,1320,10070,10073,10074,10071,43,10093,10092,10096,10097,0,1319,1320,1325,1324,10072,10071,10075,10076,43,10092,10095,10098,10096,0,1320,1322,1326,1325,10071,10074,10077,10075,43,10090,10099,10100,10091,0,1318,1328,1327,1321,10069,10078,10079,10070,43,10099,10101,10102,10100,0,1328,1330,1329,1327,10078,10080,10081,10079,43,10091,10100,10103,10094,0,1321,1327,1331,1323,10070,10079,10082,10073,43,10100,10102,10104,10103,0,1327,1329,1332,1331,10079,10081,10083,10082,43,10090,10105,10106,10099,0,1318,1334,1333,1328,10069,10084,10085,10078,43,10105,10107,10108,10106,0,1334,1336,1335,1333,10084,10086,10087,10085,43,10099,10106,10109,10101,0,1328,1333,1337,1330,10078,10085,10088,10080,43,10106,10108,10110,10109,0,1333,1335,1338,1337,10085,10087,10089,10088,43,10090,10093,10111,10105,0,1318,1319,1339,1334,10069,10072,10090,10084,43,10093,10097,10112,10111,0,1319,1324,1340,1339,10072,10076,10091,10090,43,10105,10111,10113,10107,0,1334,1339,1341,1336,10084,10090,10092,10086,43,10111,10112,10114,10113,0,1339,1340,1342,1341,10090,10091,10093,10092,43,10115,10116,10117,10118,0,1343,1346,1345,1344,10094,10095,10096,10097,43,10116,10119,10120,10117,0,1346,1348,1347,1345,10095,10098,10099,10096,43,10118,10117,10121,10122,0,1344,1345,1350,1349,10097,10096,10100,10101,43,10117,10120,10123,10121,0,1345,1347,1351,1350,10096,10099,10102,10100,43,10115,10124,10125,10116,0,1343,1353,1352,1346,10094,10103,10104,10095,43,10124,9816,9824,10125,0,1353,1095,1102,1352,10103,9795,9803,10104,43,10116,10125,10126,10119,0,1346,1352,1354,1348,10095,10104,10105,10098,43,10125,9824,9825,10126,0,1352,1102,1103,1354,10104,9803,9804,10105,43,10115,10127,10128,10124,0,1343,1356,1355,1353,10094,10106,10107,10103,43,10127,10129,10130,10128,0,1356,1358,1357,1355,10106,10108,10109,10107,43,10124,10128,9817,9816,0,1353,1355,1094,1095,10103,10107,9796,9795,43,10128,10130,9819,9817,0,1355,1357,1097,1094,10107,10109,9798,9796,43,10115,10118,10131,10127,0,1343,1344,1359,1356,10094,10097,10110,10106,43,10118,10122,10132,10131,0,1344,1349,1360,1359,10097,10101,10111,10110,43,10127,10131,10133,10129,0,1356,1359,1361,1358,10106,10110,10112,10108,43,10131,10132,10134,10133,0,1359,1360,1362,1361,10110,10111,10113,10112,43,10135,10136,10137,10138,0,1363,1366,1365,1364,10114,10115,10116,10117,43,10136,10139,10140,10137,0,1366,1368,1367,1365,10115,10118,10119,10116,43,10138,10137,9949,9948,0,1364,1365,1183,1184,10117,10116,9928,9927,43,10137,10140,9951,9949,0,1365,1367,1186,1183,10116,10119,9930,9928,43,10135,10141,10142,10136,0,1363,1370,1369,1366,10114,10120,10121,10115,43,10141,9644,9652,10142,0,1370,980,987,1369,10120,9623,9631,10121,43,10136,10142,10143,10139,0,1366,1369,1371,1368,10115,10121,10122,10118,43,10142,9652,9653,10143,0,1369,987,988,1371,10121,9631,9632,10122,43,10135,10144,10145,10141,0,1363,1373,1372,1370,10114,10123,10124,10120,43,10144,10146,10147,10145,0,1373,1375,1374,1372,10123,10125,10126,10124,43,10141,10145,9645,9644,0,1370,1372,979,980,10120,10124,9624,9623,43,10145,10147,9647,9645,0,1372,1374,982,979,10124,10126,9626,9624,43,10135,10138,10148,10144,0,1363,1364,1376,1373,10114,10117,10127,10123,43,10138,9948,9954,10148,0,1364,1184,1189,1376,10117,9927,9933,10127,43,10144,10148,10149,10146,0,1373,1376,1377,1375,10123,10127,10128,10125,43,10148,9954,9935,10149,0,1376,1189,1174,1377,10127,9933,9914,10128,43,10150,10151,10152,10153,0,1378,1003,1003,1379,10129,10130,10131,10132,43,10151,2774,2773,10152,0,1003,1004,1004,1003,10130,2774,2773,10131,43,10153,10152,9964,9963,0,1379,1003,1003,1192,10132,10131,9943,9942,43,10152,2773,2576,9964,0,1003,1004,1004,1003,10131,2773,2576,9943,43,10150,10154,10155,10151,0,1378,1380,1003,1003,10129,10133,10134,10130,43,10154,9666,9670,10155,0,1380,1002,1003,1003,10133,9645,9649,10134,43,10151,10155,2777,2774,0,1003,1003,1004,1004,10130,10134,2777,2774,43,10155,9670,2256,2777,0,1003,1003,1004,1004,10134,9649,2256,2777,43,10150,10156,10157,10154,0,1378,1382,1381,1380,10129,10135,10136,10133,43,10156,10139,10143,10157,0,1382,1368,1371,1381,10135,10118,10122,10136,43,10154,10157,9667,9666,0,1380,1381,1001,1002,10133,10136,9646,9645,43,10157,10143,9653,9667,0,1381,1371,988,1001,10136,10122,9632,9646,43,10150,10153,10158,10156,0,1378,1379,1383,1382,10129,10132,10137,10135,43,10153,9963,9967,10158,0,1379,1192,1195,1383,10132,9942,9946,10137,43,10156,10158,10140,10139,0,1382,1383,1367,1368,10135,10137,10119,10118,43,10158,9967,9951,10140,0,1383,1195,1186,1367,10137,9946,9930,10119,43,10159,10160,10161,10162,0,1384,1387,1386,1385,10138,10139,10140,10141,43,10160,10163,10164,10161,0,1387,1389,1388,1386,10139,10142,10143,10140,43,10162,10161,10165,10166,0,1385,1386,1391,1390,10141,10140,10144,10145,43,10161,10164,10167,10165,0,1386,1388,1392,1391,10140,10143,10146,10144,43,10159,10168,10169,10160,0,1384,1394,1393,1387,10138,10147,10148,10139,43,10168,10170,10171,10169,0,1394,1396,1395,1393,10147,10149,10150,10148,43,10160,10169,10172,10163,0,1387,1393,1397,1389,10139,10148,10151,10142,43,10169,10171,10173,10172,0,1393,1395,1398,1397,10148,10150,10152,10151,43,10159,10174,10175,10168,0,1384,1400,1399,1394,10138,10153,10154,10147,43,10174,10176,10177,10175,0,1400,1402,1401,1399,10153,10155,10156,10154,43,10168,10175,10178,10170,0,1394,1399,1403,1396,10147,10154,10157,10149,43,10175,10177,10179,10178,0,1399,1401,1404,1403,10154,10156,10158,10157,43,10159,10162,10180,10174,0,1384,1385,1405,1400,10138,10141,10159,10153,43,10162,10166,10181,10180,0,1385,1390,1406,1405,10141,10145,10160,10159,43,10174,10180,10182,10176,0,1400,1405,1407,1402,10153,10159,10161,10155,43,10180,10181,10183,10182,0,1405,1406,1408,1407,10159,10160,10162,10161,43,10184,10185,10186,10187,0,1409,1412,1411,1410,10163,10164,10165,10166,43,10185,10188,10189,10186,0,1412,1414,1413,1411,10164,10167,10168,10165,43,10187,10186,10132,10122,0,1410,1411,1360,1349,10166,10165,10111,10101,43,10186,10189,10134,10132,0,1411,1413,1362,1360,10165,10168,10113,10111,43,10184,10190,10191,10185,0,1409,1416,1415,1412,10163,10169,10170,10164,43,10190,10192,10193,10191,0,1416,1418,1417,1415,10169,10171,10172,10170,43,10185,10191,10194,10188,0,1412,1415,1419,1414,10164,10170,10173,10167,43,10191,10193,10195,10194,0,1415,1417,1420,1419,10170,10172,10174,10173,43,10184,10196,10197,10190,0,1409,1422,1421,1416,10163,10175,10176,10169,43,10196,10198,10199,10197,0,1422,1424,1423,1421,10175,10177,10178,10176,43,10190,10197,10200,10192,0,1416,1421,1425,1418,10169,10176,10179,10171,43,10197,10199,10201,10200,0,1421,1423,1426,1425,10176,10178,10180,10179,43,10184,10187,10202,10196,0,1409,1410,1427,1422,10163,10166,10181,10175,43,10187,10122,10121,10202,0,1410,1349,1350,1427,10166,10101,10100,10181,43,10196,10202,10203,10198,0,1422,1427,1428,1424,10175,10181,10182,10177,43,10202,10121,10123,10203,0,1427,1350,1351,1428,10181,10100,10102,10182,43,10204,10205,10206,10207,0,1429,1432,1431,1430,10183,10184,10185,10186,43,10205,10208,10209,10206,0,1432,1434,1433,1431,10184,10187,10188,10185,43,10207,10206,10210,10211,0,1430,1431,1436,1435,10186,10185,10189,10190,43,10206,10209,10212,10210,0,1431,1433,1437,1436,10185,10188,10191,10189,43,10204,10213,10214,10205,0,1429,1439,1438,1432,10183,10192,10193,10184,43,10213,10215,10216,10214,0,1439,1441,1440,1438,10192,10194,10195,10193,43,10205,10214,10217,10208,0,1432,1438,1442,1434,10184,10193,10196,10187,43,10214,10216,10218,10217,0,1438,1440,1443,1442,10193,10195,10197,10196,43,10204,10219,10220,10213,0,1429,1445,1444,1439,10183,10198,10199,10192,43,10219,10221,10222,10220,0,1445,1447,1446,1444,10198,10200,10201,10199,43,10213,10220,10223,10215,0,1439,1444,1448,1441,10192,10199,10202,10194,43,10220,10222,10224,10223,0,1444,1446,1449,1448,10199,10201,10203,10202,43,10204,10207,10225,10219,0,1429,1430,1450,1445,10183,10186,10204,10198,43,10207,10211,10226,10225,0,1430,1435,1451,1450,10186,10190,10205,10204,43,10219,10225,10227,10221,0,1445,1450,1452,1447,10198,10204,10206,10200,43,10225,10226,10228,10227,0,1450,1451,1453,1452,10204,10205,10207,10206,43,10229,10230,10231,10232,0,1454,1457,1456,1455,10208,10209,10210,10211,43,10230,10233,10234,10231,0,1457,1459,1458,1456,10209,10212,10213,10210,43,10232,10231,10235,10236,0,1455,1456,1461,1460,10211,10210,10214,10215,43,10231,10234,10179,10235,0,1456,1458,1404,1461,10210,10213,10158,10214,43,10229,10237,10238,10230,0,1454,1463,1462,1457,10208,10216,10217,10209,43,10237,8069,8077,10238,0,1463,348,355,1462,10216,8048,8056,10217,43,10230,10238,10239,10233,0,1457,1462,1464,1459,10209,10217,10218,10212,43,10238,8077,8078,10239,0,1462,355,356,1464,10217,8056,8057,10218,43,10229,10240,10241,10237,0,1454,1466,1465,1463,10208,10219,10220,10216,43,10240,10056,10062,10241,0,1466,1285,1290,1465,10219,10035,10041,10220,43,10237,10241,8070,8069,0,1463,1465,347,348,10216,10220,8049,8048,43,10241,10062,8072,8070,0,1465,1290,350,347,10220,10041,8051,8049,43,10229,10232,10242,10240,0,1454,1455,1467,1466,10208,10211,10221,10219,43,10232,10236,10243,10242,0,1455,1460,1468,1467,10211,10215,10222,10221,43,10240,10242,10057,10056,0,1466,1467,1284,1285,10219,10221,10036,10035,43,10242,10243,10059,10057,0,1467,1468,1287,1284,10221,10222,10038,10036,43,10244,10245,10246,10247,0,1469,1472,1471,1470,10223,10224,10225,10226,43,10245,10248,10249,10246,0,1472,1474,1473,1471,10224,10227,10228,10225,43,10247,10246,10250,10251,0,1470,1471,1476,1475,10226,10225,10229,10230,43,10246,10249,10252,10250,0,1471,1473,1477,1476,10225,10228,10231,10229,43,10244,10253,10254,10245,0,1469,1479,1478,1472,10223,10232,10233,10224,43,10253,10255,10256,10254,0,1479,1481,1480,1478,10232,10234,10235,10233,43,10245,10254,10257,10248,0,1472,1478,1482,1474,10224,10233,10236,10227,43,10254,10256,10258,10257,0,1478,1480,1483,1482,10233,10235,10237,10236,43,10244,10259,10260,10253,0,1469,1485,1484,1479,10223,10238,10239,10232,43,10259,10261,10262,10260,0,1485,1487,1486,1484,10238,10240,10241,10239,43,10253,10260,10263,10255,0,1479,1484,1488,1481,10232,10239,10242,10234,43,10260,10262,10264,10263,0,1484,1486,1489,1488,10239,10241,10243,10242,43,10244,10247,10265,10259,0,1469,1470,1490,1485,10223,10226,10244,10238,43,10247,10251,10266,10265,0,1470,1475,1490,1490,10226,10230,10245,10244,43,10259,10265,10267,10261,0,1485,1490,1490,1487,10238,10244,10246,10240,43,10265,10266,10268,10267,0,1490,1490,1490,1490,10244,10245,10247,10246,43,10269,10270,10271,10272,0,1491,1490,1490,1492,10248,10249,10250,10251,43,10270,10273,10274,10271,0,1490,1490,1490,1490,10249,10252,10253,10250,43,10272,10271,10275,10276,0,1492,1490,1490,1493,10251,10250,10254,10255,43,10271,10274,10277,10275,0,1490,1490,1490,1490,10250,10253,10256,10254,43,10269,10278,10279,10270,0,1491,1494,1490,1490,10248,10257,10258,10249,43,10278,10280,10281,10279,0,1494,1495,1490,1490,10257,10259,10260,10258,43,10270,10279,10282,10273,0,1490,1490,1490,1490,10249,10258,10261,10252,43,10279,10281,10283,10282,0,1490,1490,1490,1490,10258,10260,10262,10261,43,10269,10284,10285,10278,0,1491,1497,1496,1494,10248,10263,10264,10257,43,10284,10286,10287,10285,0,1497,1499,1498,1496,10263,10265,10266,10264,43,10278,10285,10288,10280,0,1494,1496,1500,1495,10257,10264,10267,10259,43,10285,10287,10289,10288,0,1496,1498,1501,1500,10264,10266,10268,10267,43,10269,10272,10290,10284,0,1491,1492,1502,1497,10248,10251,10269,10263,43,10272,10276,10291,10290,0,1492,1493,1503,1502,10251,10255,10270,10269,43,10284,10290,10292,10286,0,1497,1502,1504,1499,10263,10269,10271,10265,43,10290,10291,10293,10292,0,1502,1503,1505,1504,10269,10270,10272,10271,43,10294,10295,10296,10297,0,1506,1509,1508,1507,10273,10274,10275,10276,43,10295,10298,10299,10296,0,1509,1511,1510,1508,10274,10277,10278,10275,43,10297,10296,10300,10301,0,1507,1508,1513,1512,10276,10275,10279,10280,43,10296,10299,10302,10300,0,1508,1510,1514,1513,10275,10278,10281,10279,43,10294,10303,10304,10295,0,1506,1516,1515,1509,10273,10282,10283,10274,43,10303,10305,10306,10304,0,1516,1518,1517,1515,10282,10284,10285,10283,43,10295,10304,10307,10298,0,1509,1515,1519,1511,10274,10283,10286,10277,43,10304,10306,10308,10307,0,1515,1517,1520,1519,10283,10285,10287,10286,43,10294,10309,10310,10303,0,1506,1522,1521,1516,10273,10288,10289,10282,43,10309,10311,10312,10310,0,1522,1524,1523,1521,10288,10290,10291,10289,43,10303,10310,10313,10305,0,1516,1521,1525,1518,10282,10289,10292,10284,43,10310,10312,10314,10313,0,1521,1523,1526,1525,10289,10291,10293,10292,43,10294,10297,10315,10309,0,1506,1507,1527,1522,10273,10276,10294,10288,43,10297,10301,10316,10315,0,1507,1512,1528,1527,10276,10280,10295,10294,43,10309,10315,10317,10311,0,1522,1527,1529,1524,10288,10294,10296,10290,43,10315,10316,10318,10317,0,1527,1528,1530,1529,10294,10295,10297,10296,43,10319,10320,10321,10322,0,1531,1534,1533,1532,10298,10299,10300,10301,43,10320,10323,10324,10321,0,1534,1536,1535,1533,10299,10302,10303,10300,43,10322,10321,10325,10326,0,1532,1533,1538,1537,10301,10300,10304,10305,43,10321,10324,10327,10325,0,1533,1535,1539,1538,10300,10303,10306,10304,43,10319,10328,10329,10320,0,1531,1541,1540,1534,10298,10307,10308,10299,43,10328,10330,10331,10329,0,1541,1543,1542,1540,10307,10309,10310,10308,43,10320,10329,10332,10323,0,1534,1540,1544,1536,10299,10308,10311,10302,43,10329,10331,10333,10332,0,1540,1542,1545,1544,10308,10310,10312,10311,43,10319,10334,10335,10328,0,1531,1547,1546,1541,10298,10313,10314,10307,43,10334,10336,10337,10335,0,1547,1549,1548,1546,10313,10315,10316,10314,43,10328,10335,10338,10330,0,1541,1546,1550,1543,10307,10314,10317,10309,43,10335,10337,10212,10338,0,1546,1548,1437,1550,10314,10316,10191,10317,43,10319,10322,10339,10334,0,1531,1532,1551,1547,10298,10301,10318,10313,43,10322,10326,10340,10339,0,1532,1537,1551,1551,10301,10305,10319,10318,43,10334,10339,10341,10336,0,1547,1551,1552,1549,10313,10318,10320,10315,43,10339,10340,10342,10341,0,1551,1551,1552,1552,10318,10319,10321,10320,43,10343,10344,10345,10346,0,1553,1556,1555,1554,10322,10323,10324,10325,43,10344,10347,10348,10345,0,1556,1558,1557,1555,10323,10326,10327,10324,43,10346,10345,10349,10350,0,1554,1555,1555,1554,10325,10324,10328,10329,43,10345,10348,10351,10349,0,1555,1557,1557,1555,10324,10327,10330,10328,43,10343,10352,10353,10344,0,1553,1560,1559,1556,10322,10331,10332,10323,43,10352,10354,10355,10353,0,1560,1562,1561,1559,10331,10333,10334,10332,43,10344,10353,10356,10347,0,1556,1559,1563,1558,10323,10332,10335,10326,43,10353,10355,10228,10356,0,1559,1561,1453,1563,10332,10334,10207,10335,43,10343,10357,10358,10352,0,1553,1565,1564,1560,10322,10336,10337,10331,43,10357,10359,10360,10358,0,1565,1567,1566,1564,10336,10338,10339,10337,43,10352,10358,10361,10354,0,1560,1564,1568,1562,10331,10337,10340,10333,43,10358,10360,10167,10361,0,1564,1566,1392,1568,10337,10339,10146,10340,43,10343,10346,10362,10357,0,1553,1554,1569,1565,10322,10325,10341,10336,43,10346,10350,10363,10362,0,1554,1554,1569,1569,10325,10329,10342,10341,43,10357,10362,10364,10359,0,1565,1569,1570,1567,10336,10341,10343,10338,43,10362,10363,10365,10364,0,1569,1569,1570,1570,10341,10342,10344,10343,43,10366,10367,10368,10369,0,1571,1574,1573,1572,10345,10346,10347,10348,43,10367,10370,10371,10368,0,1574,1576,1575,1573,10346,10349,10350,10347,43,10369,10368,10372,10373,0,1572,1573,1578,1577,10348,10347,10351,10352,43,10368,10371,10374,10372,0,1573,1575,1579,1578,10347,10350,10353,10351,43,10366,10375,10376,10367,0,1571,1581,1580,1574,10345,10354,10355,10346,43,10375,10377,10378,10376,0,1581,1583,1582,1580,10354,10356,10357,10355,43,10367,10376,10379,10370,0,1574,1580,1584,1576,10346,10355,10358,10349,43,10376,10378,10380,10379,0,1580,1582,1585,1584,10355,10357,10359,10358,43,10366,10381,10382,10375,0,1571,1587,1586,1581,10345,10360,10361,10354,43,10381,10248,10257,10382,0,1587,1474,1482,1586,10360,10227,10236,10361,43,10375,10382,10383,10377,0,1581,1586,1588,1583,10354,10361,10362,10356,43,10382,10257,10258,10383,0,1586,1482,1483,1588,10361,10236,10237,10362,43,10366,10369,10384,10381,0,1571,1572,1589,1587,10345,10348,10363,10360,43,10369,10373,10385,10384,0,1572,1577,1590,1589,10348,10352,10364,10363,43,10381,10384,10249,10248,0,1587,1589,1473,1474,10360,10363,10228,10227,43,10384,10385,10252,10249,0,1589,1590,1477,1473,10363,10364,10231,10228,43,10386,10387,10388,10389,0,1591,1594,1593,1592,10365,10366,10367,10368,43,10387,10390,10391,10388,0,1594,1596,1595,1593,10366,10369,10370,10367,43,10389,10388,10120,10119,0,1592,1593,1347,1348,10368,10367,10099,10098,43,10388,10391,10123,10120,0,1593,1595,1351,1347,10367,10370,10102,10099,43,10386,10392,10393,10387,0,1591,1598,1597,1594,10365,10371,10372,10366,43,10392,10311,10317,10393,0,1598,1524,1529,1597,10371,10290,10296,10372,43,10387,10393,10394,10390,0,1594,1597,1599,1596,10366,10372,10373,10369,43,10393,10317,10318,10394,0,1597,1529,1530,1599,10372,10296,10297,10373,43,10386,10395,10396,10392,0,1591,1601,1600,1598,10365,10374,10375,10371,43,10395,10397,10398,10396,0,1601,1603,1602,1600,10374,10376,10377,10375,43,10392,10396,10312,10311,0,1598,1600,1523,1524,10371,10375,10291,10290,43,10396,10398,10314,10312,0,1600,1602,1526,1523,10375,10377,10293,10291,43,10386,10389,10399,10395,0,1591,1592,1604,1601,10365,10368,10378,10374,43,10389,10119,10126,10399,0,1592,1348,1354,1604,10368,10098,10105,10378,43,10395,10399,10400,10397,0,1601,1604,1605,1603,10374,10378,10379,10376,43,10399,10126,9825,10400,0,1604,1354,1103,1605,10378,10105,9804,10379,43,10401,10402,10403,10404,0,1606,1609,1608,1607,10380,10381,10382,10383,43,10402,10405,10406,10403,0,1609,1611,1610,1608,10381,10384,10385,10382,43,10404,10403,10407,10408,0,1607,1608,1613,1612,10383,10382,10386,10387,43,10403,10406,9983,10407,0,1608,1610,1210,1613,10382,10385,9962,10386,43,10401,10409,10410,10402,0,1606,1615,1614,1609,10380,10388,10389,10381,43,10409,10397,10400,10410,0,1615,1603,1605,1614,10388,10376,10379,10389,43,10402,10410,10411,10405,0,1609,1614,1616,1611,10381,10389,10390,10384,43,10410,10400,9825,10411,0,1614,1605,1103,1616,10389,10379,9804,10390,43,10401,10412,10413,10409,0,1606,1618,1617,1615,10380,10391,10392,10388,43,10412,10414,10415,10413,0,1618,1620,1619,1617,10391,10393,10394,10392,43,10409,10413,10398,10397,0,1615,1617,1602,1603,10388,10392,10377,10376,43,10413,10415,10314,10398,0,1617,1619,1526,1602,10392,10394,10293,10377,43,10401,10404,10416,10412,0,1606,1607,1621,1618,10380,10383,10395,10391,43,10404,10408,10417,10416,0,1607,1612,1622,1621,10383,10387,10396,10395,43,10412,10416,10418,10414,0,1618,1621,1623,1620,10391,10395,10397,10393,43,10416,10417,10293,10418,0,1621,1622,1505,1623,10395,10396,10272,10397,43,10419,10420,10421,10422,0,1624,1627,1626,1625,10398,10399,10400,10401,43,10420,10423,10424,10421,0,1627,1629,1628,1626,10399,10402,10403,10400,43,10422,10421,10425,10426,0,1625,1626,1631,1630,10401,10400,10404,10405,43,10421,10424,9798,10425,0,1626,1628,1076,1631,10400,10403,9777,10404,43,10419,10427,10428,10420,0,1624,1633,1632,1627,10398,10406,10407,10399,43,10427,10429,10430,10428,0,1633,1635,1634,1632,10406,10408,10409,10407,43,10420,10428,10431,10423,0,1627,1632,1636,1629,10399,10407,10410,10402,43,10428,10430,9989,10431,0,1632,1634,1216,1636,10407,10409,9968,10410,43,10419,10432,10433,10427,0,1624,1638,1637,1633,10398,10411,10412,10406,43,10432,10434,10435,10433,0,1638,1640,1639,1637,10411,10413,10414,10412,43,10427,10433,10436,10429,0,1633,1637,1641,1635,10406,10412,10415,10408,43,10433,10435,10289,10436,0,1637,1639,1501,1641,10412,10414,10268,10415,43,10419,10422,10437,10432,0,1624,1625,1642,1638,10398,10401,10416,10411,43,10422,10426,10438,10437,0,1625,1630,1643,1642,10401,10405,10417,10416,43,10432,10437,10439,10434,0,1638,1642,1644,1640,10411,10416,10418,10413,43,10437,10438,10264,10439,0,1642,1643,1489,1644,10416,10417,10243,10418,43,10440,10441,10442,10443,0,1645,1648,1647,1646,10419,10420,10421,10422,43,10441,10050,10058,10442,0,1648,1279,1286,1647,10420,10029,10037,10421,43,10443,10442,10444,10445,0,1646,1647,1650,1649,10422,10421,10423,10424,43,10442,10058,10059,10444,0,1647,1286,1287,1650,10421,10037,10038,10423,43,10440,10446,10447,10441,0,1645,1652,1651,1648,10419,10425,10426,10420,43,10446,10448,10449,10447,0,1652,1654,1653,1651,10425,10427,10428,10426,43,10441,10447,10051,10050,0,1648,1651,1278,1279,10420,10426,10030,10029,43,10447,10449,9804,10051,0,1651,1653,1082,1278,10426,10428,9783,10030,43,10440,10450,10451,10446,0,1645,1656,1655,1652,10419,10429,10430,10425,43,10450,10377,10383,10451,0,1656,1583,1588,1655,10429,10356,10362,10430,43,10446,10451,10452,10448,0,1652,1655,1657,1654,10425,10430,10431,10427,43,10451,10383,10258,10452,0,1655,1588,1483,1657,10430,10362,10237,10431,43,10440,10443,10453,10450,0,1645,1646,1658,1656,10419,10422,10432,10429,43,10443,10445,10454,10453,0,1646,1649,1659,1658,10422,10424,10433,10432,43,10450,10453,10378,10377,0,1656,1658,1582,1583,10429,10432,10357,10356,43,10453,10454,10380,10378,0,1658,1659,1585,1582,10432,10433,10359,10357,43,10455,10456,10457,10458,0,1660,1663,1662,1661,10434,10435,10436,10437,43,10456,10459,10460,10457,0,1663,1665,1664,1662,10435,10438,10439,10436,43,10458,10457,10461,10462,0,1661,1662,1667,1666,10437,10436,10440,10441,43,10457,10460,10333,10461,0,1662,1664,1545,1667,10436,10439,10312,10440,43,10455,10463,10464,10456,0,1660,1669,1668,1663,10434,10442,10443,10435,43,10463,10390,10394,10464,0,1669,1596,1599,1668,10442,10369,10373,10443,43,10456,10464,10465,10459,0,1663,1668,1670,1665,10435,10443,10444,10438,43,10464,10394,10318,10465,0,1668,1599,1530,1670,10443,10373,10297,10444,43,10455,10466,10467,10463,0,1660,1672,1671,1669,10434,10445,10446,10442,43,10466,10198,10203,10467,0,1672,1424,1428,1671,10445,10177,10182,10446,43,10463,10467,10391,10390,0,1669,1671,1595,1596,10442,10446,10370,10369,43,10467,10203,10123,10391,0,1671,1428,1351,1595,10446,10182,10102,10370,43,10455,10458,10468,10466,0,1660,1661,1673,1672,10434,10437,10447,10445,43,10458,10462,10469,10468,0,1661,1666,1674,1673,10437,10441,10448,10447,43,10466,10468,10199,10198,0,1672,1673,1423,1424,10445,10447,10178,10177,43,10468,10469,10201,10199,0,1673,1674,1426,1423,10447,10448,10180,10178,43,10470,10471,10472,10473,0,1675,1678,1677,1676,10449,10450,10451,10452,43,10471,10474,10475,10472,0,1678,1680,1679,1677,10450,10453,10454,10451,43,10473,10472,10324,10323,0,1676,1677,1535,1536,10452,10451,10303,10302,43,10472,10475,10327,10324,0,1677,1679,1539,1535,10451,10454,10306,10303,43,10470,10476,10477,10471,0,1675,1682,1681,1678,10449,10455,10456,10450,43,10476,10301,10300,10477,0,1682,1512,1513,1681,10455,10280,10279,10456,43,10471,10477,10478,10474,0,1678,1681,1683,1680,10450,10456,10457,10453,43,10477,10300,10302,10478,0,1681,1513,1514,1683,10456,10279,10281,10457,43,10470,10479,10480,10476,0,1675,1685,1684,1682,10449,10458,10459,10455,43,10479,10459,10465,10480,0,1685,1665,1670,1684,10458,10438,10444,10459,43,10476,10480,10316,10301,0,1682,1684,1528,1512,10455,10459,10295,10280,43,10480,10465,10318,10316,0,1684,1670,1530,1528,10459,10444,10297,10295,43,10470,10473,10481,10479,0,1675,1676,1686,1685,10449,10452,10460,10458,43,10473,10323,10332,10481,0,1676,1536,1544,1686,10452,10302,10311,10460,43,10479,10481,10460,10459,0,1685,1686,1664,1665,10458,10460,10439,10438,43,10481,10332,10333,10460,0,1686,1544,1545,1664,10460,10311,10312,10439,43,10482,10483,10484,10485,0,1687,1690,1689,1688,10461,10462,10463,10464,43,10483,10486,10487,10484,0,1690,1692,1691,1689,10462,10465,10466,10463,43,10485,10484,10488,10489,0,1688,1689,1694,1693,10464,10463,10467,10468,43,10484,10487,10104,10488,0,1689,1691,1332,1694,10463,10466,10083,10467,43,10482,10490,10491,10483,0,1687,1696,1695,1690,10461,10469,10470,10462,43,10490,10492,10493,10491,0,1696,1698,1697,1695,10469,10471,10472,10470,43,10483,10491,10494,10486,0,1690,1695,1699,1692,10462,10470,10473,10465,43,10491,10493,10495,10494,0,1695,1697,1700,1699,10470,10472,10474,10473,43,10482,10496,10497,10490,0,1687,1702,1701,1696,10461,10475,10476,10469,43,10496,10498,10499,10497,0,1702,1704,1703,1701,10475,10477,10478,10476,43,10490,10497,10500,10492,0,1696,1701,1705,1698,10469,10476,10479,10471,43,10497,10499,10501,10500,0,1701,1703,1706,1705,10476,10478,10480,10479,43,10482,10485,10502,10496,0,1687,1688,1707,1702,10461,10464,10481,10475,43,10485,10489,10503,10502,0,1688,1693,1708,1707,10464,10468,10482,10481,43,10496,10502,10504,10498,0,1702,1707,1709,1704,10475,10481,10483,10477,43,10502,10503,10505,10504,0,1707,1708,1710,1709,10481,10482,10484,10483,43,10506,10507,10508,10509,0,1711,1714,1713,1712,10485,10486,10487,10488,43,10507,10510,10511,10508,0,1714,1716,1715,1713,10486,10489,10490,10487,43,10509,10508,10512,10513,0,1712,1713,1718,1717,10488,10487,10491,10492,43,10508,10511,10514,10512,0,1713,1715,1719,1718,10487,10490,10493,10491,43,10506,10515,10516,10507,0,1711,1721,1720,1714,10485,10494,10495,10486,43,10515,10517,10518,10516,0,1721,1723,1722,1720,10494,10496,10497,10495,43,10507,10516,10519,10510,0,1714,1720,1724,1716,10486,10495,10498,10489,43,10516,10518,10079,10519,0,1720,1722,1307,1724,10495,10497,10058,10498,43,10506,10520,10521,10515,0,1711,1726,1725,1721,10485,10499,10500,10494,43,10520,10522,10523,10521,0,1726,1728,1727,1725,10499,10501,10502,10500,43,10515,10521,10524,10517,0,1721,1725,1729,1723,10494,10500,10503,10496,43,10521,10523,10110,10524,0,1725,1727,1338,1729,10500,10502,10089,10503,43,10506,10509,10525,10520,0,1711,1712,1730,1726,10485,10488,10504,10499,43,10509,10513,10526,10525,0,1712,1717,1731,1730,10488,10492,10505,10504,43,10520,10525,10527,10522,0,1726,1730,1732,1728,10499,10504,10506,10501,43,10525,10526,10528,10527,0,1730,1731,1733,1732,10504,10505,10507,10506,43,10529,10530,10531,10532,0,1734,1737,1736,1735,10508,10509,10510,10511,43,10530,10370,10379,10531,0,1737,1576,1584,1736,10509,10349,10358,10510,43,10532,10531,10533,10534,0,1735,1736,1739,1738,10511,10510,10512,10513,43,10531,10379,10380,10533,0,1736,1584,1585,1739,10510,10358,10359,10512,43,10529,10535,10536,10530,0,1734,1741,1740,1737,10508,10514,10515,10509,43,10535,10537,10538,10536,0,1741,1743,1742,1740,10514,10516,10517,10515,43,10530,10536,10371,10370,0,1737,1740,1575,1576,10509,10515,10350,10349,43,10536,10538,10374,10371,0,1740,1742,1579,1575,10515,10517,10353,10350,43,10529,10539,10540,10535,0,1734,1745,1744,1741,10508,10518,10519,10514,43,10539,10541,10542,10540,0,1745,1747,1746,1744,10518,10520,10521,10519,43,10535,10540,10543,10537,0,1741,1744,1748,1743,10514,10519,10522,10516,43,10540,10542,10544,10543,0,1744,1746,1749,1748,10519,10521,10523,10522,43,10529,10532,10545,10539,0,1734,1735,1750,1745,10508,10511,10524,10518,43,10532,10534,10546,10545,0,1735,1738,1751,1750,10511,10513,10525,10524,43,10539,10545,10547,10541,0,1745,1750,1752,1747,10518,10524,10526,10520,43,10545,10546,10183,10547,0,1750,1751,1408,1752,10524,10525,10162,10526,43,10548,10549,10550,10551,0,1753,1756,1755,1754,10527,10528,10529,10530,43,10549,10445,10444,10550,0,1756,1649,1650,1755,10528,10424,10423,10529,43,10551,10550,10243,10236,0,1754,1755,1468,1460,10530,10529,10222,10215,43,10550,10444,10059,10243,0,1755,1650,1287,1468,10529,10423,10038,10222,43,10548,10552,10553,10549,0,1753,1758,1757,1756,10527,10531,10532,10528,43,10552,10534,10533,10553,0,1758,1738,1739,1757,10531,10513,10512,10532,43,10549,10553,10454,10445,0,1756,1757,1659,1649,10528,10532,10433,10424,43,10553,10533,10380,10454,0,1757,1739,1585,1659,10532,10512,10359,10433,43,10548,10554,10555,10552,0,1753,1760,1759,1758,10527,10533,10534,10531,43,10554,10176,10182,10555,0,1760,1402,1407,1759,10533,10155,10161,10534,43,10552,10555,10546,10534,0,1758,1759,1751,1738,10531,10534,10525,10513,43,10555,10182,10183,10546,0,1759,1407,1408,1751,10534,10161,10162,10525,43,10548,10551,10556,10554,0,1753,1754,1761,1760,10527,10530,10535,10533,43,10551,10236,10235,10556,0,1754,1460,1461,1761,10530,10215,10214,10535,43,10554,10556,10177,10176,0,1760,1761,1401,1402,10533,10535,10156,10155,43,10556,10235,10179,10177,0,1761,1461,1404,1401,10535,10214,10158,10156,43,10557,10558,10559,10560,0,1762,1765,1764,1763,10536,10537,10538,10539,43,10558,10423,10431,10559,0,1765,1629,1636,1764,10537,10402,10410,10538,43,10560,10559,9987,9986,0,1763,1764,1213,1214,10539,10538,9966,9965,43,10559,10431,9989,9987,0,1764,1636,1216,1213,10538,10410,9968,9966,43,10557,10561,10562,10558,0,1762,1767,1766,1765,10536,10540,10541,10537,43,10561,9789,9797,10562,0,1767,1068,1075,1766,10540,9768,9776,10541,43,10558,10562,10424,10423,0,1765,1766,1628,1629,10537,10541,10403,10402,43,10562,9797,9798,10424,0,1766,1075,1076,1628,10541,9776,9777,10403,43,10557,10563,10564,10561,0,1762,1769,1768,1767,10536,10542,10543,10540,43,10563,9737,9745,10564,0,1769,1028,1035,1768,10542,9716,9724,10543,43,10561,10564,9790,9789,0,1767,1768,1067,1068,10540,10543,9769,9768,43,10564,9745,9746,9790,0,1768,1035,1036,1067,10543,9724,9725,9769,43,10557,10560,10565,10563,0,1762,1763,1770,1769,10536,10539,10544,10542,43,10560,9986,9992,10565,0,1763,1214,1219,1770,10539,9965,9971,10544,43,10563,10565,9738,9737,0,1769,1770,1027,1028,10542,10544,9717,9716,43,10565,9992,9740,9738,0,1770,1219,1030,1027,10544,9971,9719,9717,43,10566,10567,10568,10569,0,1005,1005,1006,1006,10545,10546,10547,10548,43,10567,10570,10571,10568,0,1005,1005,1006,1006,10546,10549,10550,10547,43,10566,10572,10573,10567,0,1005,1008,1008,1005,10545,10551,10552,10546,43,10572,10574,10575,10573,0,1008,1009,1009,1008,10551,10553,10554,10552,43,10567,10573,10576,10570,0,1005,1008,1008,1005,10546,10552,10555,10549,43,10573,10575,10577,10576,0,1008,1009,1009,1008,10552,10554,10556,10555,43,10566,10578,10579,10572,0,1005,1005,1008,1008,10545,10557,10558,10551,43,10578,9681,9687,10579,0,1005,1005,1008,1008,10557,9660,9666,10558,43,10572,10579,10580,10574,0,1008,1008,1009,1009,10551,10558,10559,10553,43,10579,9687,9688,10580,0,1008,1008,1010,1009,10558,9666,9667,10559,43,10566,10569,10581,10578,0,1005,1006,1006,1005,10545,10548,10560,10557,43,10578,10581,9682,9681,0,1005,1006,1006,1005,10557,10560,9661,9660,43,10581,3204,2275,9682,0,1006,1007,1007,1006,10560,3204,2275,9661,43,10582,10583,10584,10585,0,1771,1772,1022,1022,10561,10562,10563,10564,43,10583,10586,10587,10584,0,1772,1773,1022,1022,10562,10565,10566,10563,43,10585,10584,10575,10574,0,1022,1022,1009,1009,10564,10563,10554,10553,43,10584,10587,10577,10575,0,1022,1022,1009,1009,10563,10566,10556,10554,43,10582,10588,10589,10583,0,1771,1775,1774,1772,10561,10567,10568,10562,43,10588,9976,9975,10589,0,1775,1202,1203,1774,10567,9955,9954,10568,43,10583,10589,10590,10586,0,1772,1774,1776,1773,10562,10568,10569,10565,43,10589,9975,9977,10590,0,1774,1203,1204,1776,10568,9954,9956,10569,43,10582,10591,10592,10588,0,1771,1778,1777,1775,10561,10570,10571,10567,43,10591,9733,9739,10592,0,1778,1024,1029,1777,10570,9712,9718,10571,43,10588,10592,9991,9976,0,1775,1777,1218,1202,10567,10571,9970,9955,43,10592,9739,9740,9991,0,1777,1029,1030,1218,10571,9718,9719,9970,43,10582,10585,10593,10591,0,1771,1022,1022,1778,10561,10564,10572,10570,43,10585,10574,10580,10593,0,1022,1009,1009,1022,10564,10553,10559,10572,43,10591,10593,9734,9733,0,1778,1022,1022,1024,10570,10572,9713,9712,43,10593,10580,9688,9734,0,1022,1009,1010,1022,10572,10559,9667,9713,43,10594,10595,10596,10597,0,1779,1782,1781,1780,10573,10574,10575,10576,43,10595,10069,10078,10596,0,1782,1298,1306,1781,10574,10048,10057,10575,43,10597,10596,10518,10517,0,1780,1781,1722,1723,10576,10575,10497,10496,43,10596,10078,10079,10518,0,1781,1306,1307,1722,10575,10057,10058,10497,43,10594,10598,10599,10595,0,1779,1784,1783,1782,10573,10577,10578,10574,43,10598,10600,10601,10599,0,1784,1786,1785,1783,10577,10579,10580,10578,43,10595,10599,10070,10069,0,1782,1783,1297,1298,10574,10578,10049,10048,43,10599,10601,10073,10070,0,1783,1785,1301,1297,10578,10580,10052,10049,43,10594,10602,10603,10598,0,1779,1788,1787,1784,10573,10581,10582,10577,43,10602,10107,10113,10603,0,1788,1336,1341,1787,10581,10086,10092,10582,43,10598,10603,10604,10600,0,1784,1787,1789,1786,10577,10582,10583,10579,43,10603,10113,10114,10604,0,1787,1341,1342,1789,10582,10092,10093,10583,43,10594,10597,10605,10602,0,1779,1780,1790,1788,10573,10576,10584,10581,43,10597,10517,10524,10605,0,1780,1723,1729,1790,10576,10496,10503,10584,43,10602,10605,10108,10107,0,1788,1790,1335,1336,10581,10584,10087,10086,43,10605,10524,10110,10108,0,1790,1729,1338,1335,10584,10503,10089,10087,43,10606,10607,10608,10609,0,1791,1794,1793,1792,10585,10586,10587,10588,43,10607,10094,10103,10608,0,1794,1323,1331,1793,10586,10073,10082,10587,43,10609,10608,10487,10486,0,1792,1793,1691,1692,10588,10587,10466,10465,43,10608,10103,10104,10487,0,1793,1331,1332,1691,10587,10082,10083,10466,43,10606,10610,10611,10607,0,1791,1796,1795,1794,10585,10589,10590,10586,43,10610,10612,10613,10611,0,1796,1798,1797,1795,10589,10591,10592,10590,43,10607,10611,10095,10094,0,1794,1795,1322,1323,10586,10590,10074,10073,43,10611,10613,10098,10095,0,1795,1797,1326,1322,10590,10592,10077,10074,43,10606,10614,10615,10610,0,1791,1800,1799,1796,10585,10593,10594,10589,43,10614,10616,10617,10615,0,1800,1802,1801,1799,10593,10595,10596,10594,43,10610,10615,10618,10612,0,1796,1799,1803,1798,10589,10594,10597,10591,43,10615,10617,10619,10618,0,1799,1801,1804,1803,10594,10596,10598,10597,43,10606,10609,10620,10614,0,1791,1792,1805,1800,10585,10588,10599,10593,43,10609,10486,10494,10620,0,1792,1692,1699,1805,10588,10465,10473,10599,43,10614,10620,10621,10616,0,1800,1805,1806,1802,10593,10599,10600,10595,43,10620,10494,10495,10621,0,1805,1699,1700,1806,10599,10473,10474,10600,43,10622,10623,10624,10625,0,1807,1810,1809,1808,10601,10602,10603,10604,43,10623,10163,10172,10624,0,1810,1389,1397,1809,10602,10142,10151,10603,43,10625,10624,10626,10627,0,1808,1809,1812,1811,10604,10603,10605,10606,43,10624,10172,10173,10626,0,1809,1397,1398,1812,10603,10151,10152,10605,43,10622,10628,10629,10623,0,1807,1814,1813,1810,10601,10607,10608,10602,43,10628,10354,10361,10629,0,1814,1562,1568,1813,10607,10333,10340,10608,43,10623,10629,10164,10163,0,1810,1813,1388,1389,10602,10608,10143,10142,43,10629,10361,10167,10164,0,1813,1568,1392,1388,10608,10340,10146,10143,43,10622,10630,10631,10628,0,1807,1816,1815,1814,10601,10609,10610,10607,43,10630,10221,10227,10631,0,1816,1447,1452,1815,10609,10200,10206,10610,43,10628,10631,10355,10354,0,1814,1815,1561,1562,10607,10610,10334,10333,43,10631,10227,10228,10355,0,1815,1452,1453,1561,10610,10206,10207,10334,43,10622,10625,10632,10630,0,1807,1808,1817,1816,10601,10604,10611,10609,43,10625,10627,10633,10632,0,1808,1811,1818,1817,10604,10606,10612,10611,43,10630,10632,10222,10221,0,1816,1817,1446,1447,10609,10611,10201,10200,43,10632,10633,10224,10222,0,1817,1818,1449,1446,10611,10612,10203,10201,43,10634,10635,10636,10637,0,1819,1822,1821,1820,10613,10614,10615,10616,43,10635,10208,10217,10636,0,1822,1434,1442,1821,10614,10187,10196,10615,43,10637,10636,10638,10639,0,1820,1821,1824,1823,10616,10615,10617,10618,43,10636,10217,10218,10638,0,1821,1442,1443,1824,10615,10196,10197,10617,43,10634,10640,10641,10635,0,1819,1826,1825,1822,10613,10619,10620,10614,43,10640,10330,10338,10641,0,1826,1543,1550,1825,10619,10309,10317,10620,43,10635,10641,10209,10208,0,1822,1825,1433,1434,10614,10620,10188,10187,43,10641,10338,10212,10209,0,1825,1550,1437,1433,10620,10317,10191,10188,43,10634,10642,10643,10640,0,1819,1828,1827,1826,10613,10621,10622,10619,43,10642,10462,10461,10643,0,1828,1666,1667,1827,10621,10441,10440,10622,43,10640,10643,10331,10330,0,1826,1827,1542,1543,10619,10622,10310,10309,43,10643,10461,10333,10331,0,1827,1667,1545,1542,10622,10440,10312,10310,43,10634,10637,10644,10642,0,1819,1820,1829,1828,10613,10616,10623,10621,43,10637,10639,10645,10644,0,1820,1823,1830,1829,10616,10618,10624,10623,43,10642,10644,10469,10462,0,1828,1829,1674,1666,10621,10623,10448,10441,43,10644,10645,10201,10469,0,1829,1830,1426,1674,10623,10624,10180,10448,43,10646,10647,10648,10649,0,1831,1832,1490,1490,10625,10626,10627,10628,43,10647,10261,10267,10648,0,1832,1487,1490,1490,10626,10240,10246,10627,43,10649,10648,10650,10651,0,1490,1490,1490,1490,10628,10627,10629,10630,43,10648,10267,10268,10650,0,1490,1490,1490,1490,10627,10246,10247,10629,43,10646,10652,10653,10647,0,1831,1834,1833,1832,10625,10631,10632,10626,43,10652,10434,10439,10653,0,1834,1640,1644,1833,10631,10413,10418,10632,43,10647,10653,10262,10261,0,1832,1833,1486,1487,10626,10632,10241,10240,43,10653,10439,10264,10262,0,1833,1644,1489,1486,10632,10418,10243,10241,43,10646,10654,10655,10652,0,1831,1836,1835,1834,10625,10633,10634,10631,43,10654,10280,10288,10655,0,1836,1495,1500,1835,10633,10259,10267,10634,43,10652,10655,10435,10434,0,1834,1835,1639,1640,10631,10634,10414,10413,43,10655,10288,10289,10435,0,1835,1500,1501,1639,10634,10267,10268,10414,43,10646,10649,10656,10654,0,1831,1490,1490,1836,10625,10628,10635,10633,43,10649,10651,10657,10656,0,1490,1490,1490,1490,10628,10630,10636,10635,43,10654,10656,10281,10280,0,1836,1490,1490,1495,10633,10635,10260,10259,43,10656,10657,10283,10281,0,1490,1490,1490,1490,10635,10636,10262,10260,43,10658,10659,10660,10661,0,1837,1840,1839,1838,10637,10638,10639,10640,43,10659,10662,10663,10660,0,1840,1842,1841,1839,10638,10641,10642,10639,43,10661,10660,10306,10305,0,1838,1839,1517,1518,10640,10639,10285,10284,43,10660,10663,10308,10306,0,1839,1841,1520,1517,10639,10642,10287,10285,43,10658,10664,10665,10659,0,1837,1843,1490,1840,10637,10643,10644,10638,43,10664,10276,10275,10665,0,1843,1493,1490,1490,10643,10255,10254,10644,43,10659,10665,10666,10662,0,1840,1490,1490,1842,10638,10644,10645,10641,43,10665,10275,10277,10666,0,1490,1490,1490,1490,10644,10254,10256,10645,43,10658,10667,10668,10664,0,1837,1845,1844,1843,10637,10646,10647,10643,43,10667,10414,10418,10668,0,1845,1620,1623,1844,10646,10393,10397,10647,43,10664,10668,10291,10276,0,1843,1844,1503,1493,10643,10647,10270,10255,43,10668,10418,10293,10291,0,1844,1623,1505,1503,10647,10397,10272,10270,43,10658,10661,10669,10667,0,1837,1838,1846,1845,10637,10640,10648,10646,43,10661,10305,10313,10669,0,1838,1518,1525,1846,10640,10284,10292,10648,43,10667,10669,10415,10414,0,1845,1846,1619,1620,10646,10648,10394,10393,43,10669,10313,10314,10415,0,1846,1525,1526,1619,10648,10292,10293,10394,43,10670,10671,10672,10673,0,1847,1850,1849,1848,10649,10650,10651,10652,43,10671,10336,10341,10672,0,1850,1549,1552,1849,10650,10315,10320,10651,43,10673,10672,10674,10675,0,1848,1849,1849,1848,10652,10651,10653,10654,43,10672,10341,10342,10674,0,1849,1552,1552,1849,10651,10320,10321,10653,43,10670,10676,10677,10671,0,1847,1852,1851,1850,10649,10655,10656,10650,43,10676,10211,10210,10677,0,1852,1435,1436,1851,10655,10190,10189,10656,43,10671,10677,10337,10336,0,1850,1851,1548,1549,10650,10656,10316,10315,43,10677,10210,10212,10337,0,1851,1436,1437,1548,10656,10189,10191,10316,43,10670,10678,10679,10676,0,1847,1854,1853,1852,10649,10657,10658,10655,43,10678,10347,10356,10679,0,1854,1558,1563,1853,10657,10326,10335,10658,43,10676,10679,10226,10211,0,1852,1853,1451,1435,10655,10658,10205,10190,43,10679,10356,10228,10226,0,1853,1563,1453,1451,10658,10335,10207,10205,43,10670,10673,10680,10678,0,1847,1848,1855,1854,10649,10652,10659,10657,43,10673,10675,10681,10680,0,1848,1848,1855,1855,10652,10654,10660,10659,43,10678,10680,10348,10347,0,1854,1855,1557,1558,10657,10659,10327,10326,43,10680,10681,10351,10348,0,1855,1855,1557,1557,10659,10660,10330,10327,43,10682,10683,10684,10685,0,1856,1859,1858,1857,10661,10662,10663,10664,43,10683,10359,10364,10684,0,1859,1567,1570,1858,10662,10338,10343,10663,43,10685,10684,10686,10687,0,1857,1858,1858,1860,10664,10663,10665,10666,43,10684,10364,10365,10686,0,1858,1570,1570,1858,10663,10343,10344,10665,43,10682,10688,10689,10683,0,1856,1862,1861,1859,10661,10667,10668,10662,43,10688,10166,10165,10689,0,1862,1390,1391,1861,10667,10145,10144,10668,43,10683,10689,10360,10359,0,1859,1861,1566,1567,10662,10668,10339,10338,43,10689,10165,10167,10360,0,1861,1391,1392,1566,10668,10144,10146,10339,43,10682,10690,10691,10688,0,1856,1864,1863,1862,10661,10669,10670,10667,43,10690,10541,10547,10691,0,1864,1747,1752,1863,10669,10520,10526,10670,43,10688,10691,10181,10166,0,1862,1863,1406,1390,10667,10670,10160,10145,43,10691,10547,10183,10181,0,1863,1752,1408,1406,10670,10526,10162,10160,43,10682,10685,10692,10690,0,1856,1857,1865,1864,10661,10664,10671,10669,43,10685,10687,10693,10692,0,1857,1860,1866,1865,10664,10666,10672,10671,43,10690,10692,10542,10541,0,1864,1865,1746,1747,10669,10671,10521,10520,43,10692,10693,10544,10542,0,1865,1866,1749,1746,10671,10672,10523,10521,43,10694,10695,10696,10697,0,1867,1870,1869,1868,10673,10674,10675,10676,43,10695,10408,10407,10696,0,1870,1612,1613,1869,10674,10387,10386,10675,43,10697,10696,9981,9980,0,1868,1869,1207,1208,10676,10675,9960,9959,43,10696,10407,9983,9981,0,1869,1613,1210,1207,10675,10386,9962,9960,43,10694,10698,10699,10695,0,1867,1872,1871,1870,10673,10677,10678,10674,43,10698,10286,10292,10699,0,1872,1499,1504,1871,10677,10265,10271,10678,43,10695,10699,10417,10408,0,1870,1871,1622,1612,10674,10678,10396,10387,43,10699,10292,10293,10417,0,1871,1504,1505,1622,10678,10271,10272,10396,43,10694,10700,10701,10698,0,1867,1874,1873,1872,10673,10679,10680,10677,43,10700,10429,10436,10701,0,1874,1635,1641,1873,10679,10408,10415,10680,43,10698,10701,10287,10286,0,1872,1873,1498,1499,10677,10680,10266,10265,43,10701,10436,10289,10287,0,1873,1641,1501,1498,10680,10415,10268,10266,43,10694,10697,10702,10700,0,1867,1868,1875,1874,10673,10676,10681,10679,43,10697,9980,9988,10702,0,1868,1208,1215,1875,10676,9959,9967,10681,43,10700,10702,10430,10429,0,1874,1875,1634,1635,10679,10681,10409,10408,43,10702,9988,9989,10430,0,1875,1215,1216,1634,10681,9967,9968,10409,43,10703,10704,10705,10706,0,1876,1879,1878,1877,10682,10683,10684,10685,43,10704,9795,9803,10705,0,1879,1074,1081,1878,10683,9774,9782,10684,43,10706,10705,10449,10448,0,1877,1878,1653,1654,10685,10684,10428,10427,43,10705,9803,9804,10449,0,1878,1081,1082,1653,10684,9782,9783,10428,43,10703,10707,10708,10704,0,1876,1881,1880,1879,10682,10686,10687,10683,43,10707,10426,10425,10708,0,1881,1630,1631,1880,10686,10405,10404,10687,43,10704,10708,9796,9795,0,1879,1880,1073,1074,10683,10687,9775,9774,43,10708,10425,9798,9796,0,1880,1631,1076,1073,10687,10404,9777,9775,43,10703,10709,10710,10707,0,1876,1883,1882,1881,10682,10688,10689,10686,43,10709,10255,10263,10710,0,1883,1481,1488,1882,10688,10234,10242,10689,43,10707,10710,10438,10426,0,1881,1882,1643,1630,10686,10689,10417,10405,43,10710,10263,10264,10438,0,1882,1488,1489,1643,10689,10242,10243,10417,43,10703,10706,10711,10709,0,1876,1877,1884,1883,10682,10685,10690,10688,43,10706,10448,10452,10711,0,1877,1654,1657,1884,10685,10427,10431,10690,43,10709,10711,10256,10255,0,1883,1884,1480,1481,10688,10690,10235,10234,43,10711,10452,10258,10256,0,1884,1657,1483,1480,10690,10431,10237,10235,43,10712,10713,10714,10715,0,1885,1888,1887,1886,10691,10692,10693,10694,43,10713,10489,10488,10714,0,1888,1693,1694,1887,10692,10468,10467,10693,43,10715,10714,10102,10101,0,1886,1887,1329,1330,10694,10693,10081,10080,43,10714,10488,10104,10102,0,1887,1694,1332,1329,10693,10467,10083,10081,43,10712,10716,10717,10713,0,1885,1890,1889,1888,10691,10695,10696,10692,43,10716,10718,10719,10717,0,1890,1892,1891,1889,10695,10697,10698,10696,43,10713,10717,10503,10489,0,1888,1889,1708,1693,10692,10696,10482,10468,43,10717,10719,10505,10503,0,1889,1891,1710,1708,10696,10698,10484,10482,43,10712,10720,10721,10716,0,1885,1894,1893,1890,10691,10699,10700,10695,43,10720,10522,10527,10721,0,1894,1728,1732,1893,10699,10501,10506,10700,43,10716,10721,10722,10718,0,1890,1893,1895,1892,10695,10700,10701,10697,43,10721,10527,10528,10722,0,1893,1732,1733,1895,10700,10506,10507,10701,43,10712,10715,10723,10720,0,1885,1886,1896,1894,10691,10694,10702,10699,43,10715,10101,10109,10723,0,1886,1330,1337,1896,10694,10080,10088,10702,43,10720,10723,10523,10522,0,1894,1896,1727,1728,10699,10702,10502,10501,43,10723,10109,10110,10523,0,1896,1337,1338,1727,10702,10088,10089,10502,43,10724,10725,10726,10727,0,1897,1900,1899,1898,10703,10704,10705,10706,43,10725,10510,10519,10726,0,1900,1716,1724,1899,10704,10489,10498,10705,43,10727,10726,10077,10076,0,1898,1899,1304,1305,10706,10705,10056,10055,43,10726,10519,10079,10077,0,1899,1724,1307,1304,10705,10498,10058,10056,43,10724,10728,10729,10725,0,1897,1902,1901,1900,10703,10707,10708,10704,43,10728,10730,10731,10729,0,1902,1904,1903,1901,10707,10709,10710,10708,43,10725,10729,10511,10510,0,1900,1901,1715,1716,10704,10708,10490,10489,43,10729,10731,10514,10511,0,1901,1903,1719,1715,10708,10710,10493,10490,43,10724,10732,10733,10728,0,1897,1906,1905,1902,10703,10711,10712,10707,43,10732,10734,10735,10733,0,1906,1908,1907,1905,10711,10713,10714,10712,43,10728,10733,10736,10730,0,1902,1905,1909,1904,10707,10712,10715,10709,43,10733,10735,10737,10736,0,1905,1907,1910,1909,10712,10714,10716,10715,43,10724,10727,10738,10732,0,1897,1898,1911,1906,10703,10706,10717,10711,43,10727,10076,10084,10738,0,1898,1305,1312,1911,10706,10055,10063,10717,43,10732,10738,10739,10734,0,1906,1911,1912,1908,10711,10717,10718,10713,43,10738,10084,10085,10739,0,1911,1312,1313,1912,10717,10063,10064,10718,43,10740,10741,10742,10743,0,1913,1916,1915,1914,10719,10720,10721,10722,43,10741,10072,10071,10742,0,1916,1299,1300,1915,10720,10051,10050,10721,43,10743,10742,10744,10745,0,1914,1915,1918,1917,10722,10721,10723,10724,43,10742,10071,10073,10744,0,1915,1300,1301,1918,10721,10050,10052,10723,43,10740,10746,10747,10741,0,1913,1920,1919,1916,10719,10725,10726,10720,43,10746,10748,10749,10747,0,1920,1922,1921,1919,10725,10727,10728,10726,43,10741,10747,10087,10072,0,1916,1919,1315,1299,10720,10726,10066,10051,43,10747,10749,10089,10087,0,1919,1921,1317,1315,10726,10728,10068,10066,43,10740,10750,10751,10746,0,1913,1924,1923,1920,10719,10729,10730,10725,43,10750,10251,10250,10751,0,1924,1475,1476,1923,10729,10230,10229,10730,43,10746,10751,10752,10748,0,1920,1923,1925,1922,10725,10730,10731,10727,43,10751,10250,10252,10752,0,1923,1476,1477,1925,10730,10229,10231,10731,43,10740,10743,10753,10750,0,1913,1914,1490,1924,10719,10722,10732,10729,43,10743,10745,10754,10753,0,1914,1917,1490,1490,10722,10724,10733,10732,43,10750,10753,10266,10251,0,1924,1490,1490,1475,10729,10732,10245,10230,43,10753,10754,10268,10266,0,1490,1490,1490,1490,10732,10733,10247,10245,43,10755,10756,10757,10758,0,1926,1929,1928,1927,10734,10735,10736,10737,43,10756,10600,10604,10757,0,1929,1786,1789,1928,10735,10579,10583,10736,43,10758,10757,10759,10760,0,1927,1928,1931,1930,10737,10736,10738,10739,43,10757,10604,10114,10759,0,1928,1789,1342,1931,10736,10583,10093,10738,43,10755,10761,10762,10756,0,1926,1933,1932,1929,10734,10740,10741,10735,43,10761,10745,10744,10762,0,1933,1917,1918,1932,10740,10724,10723,10741,43,10756,10762,10601,10600,0,1929,1932,1785,1786,10735,10741,10580,10579,43,10762,10744,10073,10601,0,1932,1918,1301,1785,10741,10723,10052,10580,43,10755,10763,10764,10761,0,1926,1490,1490,1933,10734,10742,10743,10740,43,10763,10651,10650,10764,0,1490,1490,1490,1490,10742,10630,10629,10743,43,10761,10764,10754,10745,0,1933,1490,1490,1917,10740,10743,10733,10724,43,10764,10650,10268,10754,0,1490,1490,1490,1490,10743,10629,10247,10733,43,10755,10758,10765,10763,0,1926,1927,1490,1490,10734,10737,10744,10742,43,10758,10760,10766,10765,0,1927,1930,1490,1490,10737,10739,10745,10744,43,10763,10765,10657,10651,0,1490,1490,1490,1490,10742,10744,10636,10630,43,10765,10766,10283,10657,0,1490,1490,1490,1490,10744,10745,10262,10636,43,10767,10768,10769,10770,0,1934,1937,1936,1935,10746,10747,10748,10749,43,10768,10097,10096,10769,0,1937,1324,1325,1936,10747,10076,10075,10748,43,10770,10769,10771,10772,0,1935,1936,1939,1938,10749,10748,10750,10751,43,10769,10096,10098,10771,0,1936,1325,1326,1939,10748,10075,10077,10750,43,10767,10773,10774,10768,0,1934,1941,1940,1937,10746,10752,10753,10747,43,10773,10760,10759,10774,0,1941,1930,1931,1940,10752,10739,10738,10753,43,10768,10774,10112,10097,0,1937,1940,1340,1324,10747,10753,10091,10076,43,10774,10759,10114,10112,0,1940,1931,1342,1340,10753,10738,10093,10091,43,10767,10775,10776,10773,0,1934,1490,1490,1941,10746,10754,10755,10752,43,10775,10273,10282,10776,0,1490,1490,1490,1490,10754,10252,10261,10755,43,10773,10776,10766,10760,0,1941,1490,1490,1930,10752,10755,10745,10739,43,10776,10282,10283,10766,0,1490,1490,1490,1490,10755,10261,10262,10745,43,10767,10770,10777,10775,0,1934,1935,1490,1490,10746,10749,10756,10754,43,10770,10772,10778,10777,0,1935,1938,1490,1490,10749,10751,10757,10756,43,10775,10777,10274,10273,0,1490,1490,1490,1490,10754,10756,10253,10252,43,10777,10778,10277,10274,0,1490,1490,1490,1490,10756,10757,10256,10253,43,10779,10780,10781,10782,0,1942,1945,1944,1943,10758,10759,10760,10761,43,10780,10612,10618,10781,0,1945,1798,1803,1944,10759,10591,10597,10760,43,10782,10781,10783,10784,0,1943,1944,1947,1946,10761,10760,10762,10763,43,10781,10618,10619,10783,0,1944,1803,1804,1947,10760,10597,10598,10762,43,10779,10785,10786,10780,0,1942,1949,1948,1945,10758,10764,10765,10759,43,10785,10772,10771,10786,0,1949,1938,1939,1948,10764,10751,10750,10765,43,10780,10786,10613,10612,0,1945,1948,1797,1798,10759,10765,10592,10591,43,10786,10771,10098,10613,0,1948,1939,1326,1797,10765,10750,10077,10592,43,10779,10787,10788,10785,0,1942,1950,1490,1949,10758,10766,10767,10764,43,10787,10662,10666,10788,0,1950,1842,1490,1490,10766,10641,10645,10767,43,10785,10788,10778,10772,0,1949,1490,1490,1938,10764,10767,10757,10751,43,10788,10666,10277,10778,0,1490,1490,1490,1490,10767,10645,10256,10757,43,10779,10782,10789,10787,0,1942,1943,1951,1950,10758,10761,10768,10766,43,10782,10784,10790,10789,0,1943,1946,1952,1951,10761,10763,10769,10768,43,10787,10789,10663,10662,0,1950,1951,1841,1842,10766,10768,10642,10641,43,10789,10790,10308,10663,0,1951,1952,1520,1841,10768,10769,10287,10642,43,10791,10792,10793,10794,0,1953,1956,1955,1954,10770,10771,10772,10773,43,10792,10616,10621,10793,0,1956,1802,1806,1955,10771,10595,10600,10772,43,10794,10793,10795,10796,0,1954,1955,1958,1957,10773,10772,10774,10775,43,10793,10621,10495,10795,0,1955,1806,1700,1958,10772,10600,10474,10774,43,10791,10797,10798,10792,0,1953,1960,1959,1956,10770,10776,10777,10771,43,10797,10784,10783,10798,0,1960,1946,1947,1959,10776,10763,10762,10777,43,10792,10798,10617,10616,0,1956,1959,1801,1802,10771,10777,10596,10595,43,10798,10783,10619,10617,0,1959,1947,1804,1801,10777,10762,10598,10596,43,10791,10799,10800,10797,0,1953,1962,1961,1960,10770,10778,10779,10776,43,10799,10298,10307,10800,0,1962,1511,1519,1961,10778,10277,10286,10779,43,10797,10800,10790,10784,0,1960,1961,1952,1946,10776,10779,10769,10763,43,10800,10307,10308,10790,0,1961,1519,1520,1952,10779,10286,10287,10769,43,10791,10794,10801,10799,0,1953,1954,1963,1962,10770,10773,10780,10778,43,10794,10796,10802,10801,0,1954,1957,1964,1963,10773,10775,10781,10780,43,10799,10801,10299,10298,0,1962,1963,1510,1511,10778,10780,10278,10277,43,10801,10802,10302,10299,0,1963,1964,1514,1510,10780,10781,10281,10278,43,10803,10804,10805,10806,0,1965,1968,1967,1966,10782,10783,10784,10785,43,10804,10492,10500,10805,0,1968,1698,1705,1967,10783,10471,10479,10784,43,10806,10805,10807,10808,0,1966,1967,1970,1969,10785,10784,10786,10787,43,10805,10500,10501,10807,0,1967,1705,1706,1970,10784,10479,10480,10786,43,10803,10809,10810,10804,0,1965,1972,1971,1968,10782,10788,10789,10783,43,10809,10796,10795,10810,0,1972,1957,1958,1971,10788,10775,10774,10789,43,10804,10810,10493,10492,0,1968,1971,1697,1698,10783,10789,10472,10471,43,10810,10795,10495,10493,0,1971,1958,1700,1697,10789,10774,10474,10472,43,10803,10811,10812,10809,0,1965,1974,1973,1972,10782,10790,10791,10788,43,10811,10474,10478,10812,0,1974,1680,1683,1973,10790,10453,10457,10791,43,10809,10812,10802,10796,0,1972,1973,1964,1957,10788,10791,10781,10775,43,10812,10478,10302,10802,0,1973,1683,1514,1964,10791,10457,10281,10781,43,10803,10806,10813,10811,0,1965,1966,1975,1974,10782,10785,10792,10790,43,10806,10808,10814,10813,0,1966,1969,1976,1975,10785,10787,10793,10792,43,10811,10813,10475,10474,0,1974,1975,1679,1680,10790,10792,10454,10453,43,10813,10814,10327,10475,0,1975,1976,1539,1679,10792,10793,10306,10454,43,10815,10816,10817,10818,0,1977,1980,1979,1978,10794,10795,10796,10797,43,10816,10498,10504,10817,0,1980,1704,1709,1979,10795,10477,10483,10796,43,10818,10817,10819,10820,0,1978,1979,1982,1981,10797,10796,10798,10799,43,10817,10504,10505,10819,0,1979,1709,1710,1982,10796,10483,10484,10798,43,10815,10821,10822,10816,0,1977,1984,1983,1980,10794,10800,10801,10795,43,10821,10808,10807,10822,0,1984,1969,1970,1983,10800,10787,10786,10801,43,10816,10822,10499,10498,0,1980,1983,1703,1704,10795,10801,10478,10477,43,10822,10807,10501,10499,0,1983,1970,1706,1703,10801,10786,10480,10478,43,10815,10823,10824,10821,0,1977,1986,1985,1984,10794,10802,10803,10800,43,10823,10326,10325,10824,0,1986,1537,1538,1985,10802,10305,10304,10803,43,10821,10824,10814,10808,0,1984,1985,1976,1969,10800,10803,10793,10787,43,10824,10325,10327,10814,0,1985,1538,1539,1976,10803,10304,10306,10793,43,10815,10818,10825,10823,0,1977,1978,1551,1986,10794,10797,10804,10802,43,10818,10820,10826,10825,0,1978,1981,1552,1551,10797,10799,10805,10804,43,10823,10825,10340,10326,0,1986,1551,1551,1537,10802,10804,10319,10305,43,10825,10826,10342,10340,0,1551,1552,1552,1551,10804,10805,10321,10319,43,10827,10828,10829,10830,0,1987,1990,1989,1988,10806,10807,10808,10809,43,10828,10718,10722,10829,0,1990,1892,1895,1989,10807,10697,10701,10808,43,10830,10829,10831,10832,0,1988,1989,1992,1991,10809,10808,10810,10811,43,10829,10722,10528,10831,0,1989,1895,1733,1992,10808,10701,10507,10810,43,10827,10833,10834,10828,0,1987,1994,1993,1990,10806,10812,10813,10807,43,10833,10820,10819,10834,0,1994,1981,1982,1993,10812,10799,10798,10813,43,10828,10834,10719,10718,0,1990,1993,1891,1892,10807,10813,10698,10697,43,10834,10819,10505,10719,0,1993,1982,1710,1891,10813,10798,10484,10698,43,10827,10835,10836,10833,0,1987,1848,1849,1994,10806,10814,10815,10812,43,10835,10675,10674,10836,0,1848,1848,1849,1849,10814,10654,10653,10815,43,10833,10836,10826,10820,0,1994,1849,1552,1981,10812,10815,10805,10799,43,10836,10674,10342,10826,0,1849,1849,1552,1552,10815,10653,10321,10805,43,10827,10830,10837,10835,0,1987,1988,1855,1848,10806,10809,10816,10814,43,10830,10832,10838,10837,0,1988,1991,1557,1855,10809,10811,10817,10816,43,10835,10837,10681,10675,0,1848,1855,1855,1848,10814,10816,10660,10654,43,10837,10838,10351,10681,0,1855,1557,1557,1855,10816,10817,10330,10660,43,10839,10840,10841,10842,0,1995,1998,1997,1996,10818,10819,10820,10821,43,10840,10513,10512,10841,0,1998,1717,1718,1997,10819,10492,10491,10820,43,10842,10841,10843,10844,0,1996,1997,2000,1999,10821,10820,10822,10823,43,10841,10512,10514,10843,0,1997,1718,1719,2000,10820,10491,10493,10822,43,10839,10845,10846,10840,0,1995,2002,2001,1998,10818,10824,10825,10819,43,10845,10832,10831,10846,0,2002,1991,1992,2001,10824,10811,10810,10825,43,10840,10846,10526,10513,0,1998,2001,1731,1717,10819,10825,10505,10492,43,10846,10831,10528,10526,0,2001,1992,1733,1731,10825,10810,10507,10505,43,10839,10847,10848,10845,0,1995,1554,1555,2002,10818,10826,10827,10824,43,10847,10350,10349,10848,0,1554,1554,1555,1555,10826,10329,10328,10827,43,10845,10848,10838,10832,0,2002,1555,1557,1991,10824,10827,10817,10811,43,10848,10349,10351,10838,0,1555,1555,1557,1557,10827,10328,10330,10817,43,10839,10842,10849,10847,0,1995,1996,1569,1554,10818,10821,10828,10826,43,10842,10844,10850,10849,0,1996,1999,1570,1569,10821,10823,10829,10828,43,10847,10849,10363,10350,0,1554,1569,1569,1554,10826,10828,10342,10329,43,10849,10850,10365,10363,0,1569,1570,1570,1569,10828,10829,10344,10342,43,10851,10852,10853,10854,0,2003,2006,2005,2004,10830,10831,10832,10833,43,10852,10730,10736,10853,0,2006,1904,1909,2005,10831,10709,10715,10832,43,10854,10853,10855,10856,0,2004,2005,2008,2007,10833,10832,10834,10835,43,10853,10736,10737,10855,0,2005,1909,1910,2008,10832,10715,10716,10834,43,10851,10857,10858,10852,0,2003,2010,2009,2006,10830,10836,10837,10831,43,10857,10844,10843,10858,0,2010,1999,2000,2009,10836,10823,10822,10837,43,10852,10858,10731,10730,0,2006,2009,1903,1904,10831,10837,10710,10709,43,10858,10843,10514,10731,0,2009,2000,1719,1903,10837,10822,10493,10710,43,10851,10859,10860,10857,0,2003,2011,1858,2010,10830,10838,10839,10836,43,10859,10687,10686,10860,0,2011,1860,1858,1858,10838,10666,10665,10839,43,10857,10860,10850,10844,0,2010,1858,1570,1999,10836,10839,10829,10823,43,10860,10686,10365,10850,0,1858,1858,1570,1570,10839,10665,10344,10829,43,10851,10854,10861,10859,0,2003,2004,2012,2011,10830,10833,10840,10838,43,10854,10856,10862,10861,0,2004,2007,2013,2012,10833,10835,10841,10840,43,10859,10861,10693,10687,0,2011,2012,1866,1860,10838,10840,10672,10666,43,10861,10862,10544,10693,0,2012,2013,1749,1866,10840,10841,10523,10672,43,10863,10864,10865,10866,0,2014,2017,2016,2015,10842,10843,10844,10845,43,10864,10734,10739,10865,0,2017,1908,1912,2016,10843,10713,10718,10844,43,10866,10865,10867,10868,0,2015,2016,2019,2018,10845,10844,10846,10847,43,10865,10739,10085,10867,0,2016,1912,1313,2019,10844,10718,10064,10846,43,10863,10869,10870,10864,0,2014,2021,2020,2017,10842,10848,10849,10843,43,10869,10856,10855,10870,0,2021,2007,2008,2020,10848,10835,10834,10849,43,10864,10870,10735,10734,0,2017,2020,1907,1908,10843,10849,10714,10713,43,10870,10855,10737,10735,0,2020,2008,1910,1907,10849,10834,10716,10714,43,10863,10871,10872,10869,0,2014,2023,2022,2021,10842,10850,10851,10848,43,10871,10537,10543,10872,0,2023,1743,1748,2022,10850,10516,10522,10851,43,10869,10872,10862,10856,0,2021,2022,2013,2007,10848,10851,10841,10835,43,10872,10543,10544,10862,0,2022,1748,1749,2013,10851,10522,10523,10841,43,10863,10866,10873,10871,0,2014,2015,2024,2023,10842,10845,10852,10850,43,10866,10868,10874,10873,0,2015,2018,2025,2024,10845,10847,10853,10852,43,10871,10873,10538,10537,0,2023,2024,1742,1743,10850,10852,10517,10516,43,10873,10874,10374,10538,0,2024,2025,1579,1742,10852,10853,10353,10517,43,10875,10876,10877,10878,0,2026,2029,2028,2027,10854,10855,10856,10857,43,10876,10082,10088,10877,0,2029,1311,1316,2028,10855,10061,10067,10856,43,10878,10877,10749,10748,0,2027,2028,1921,1922,10857,10856,10728,10727,43,10877,10088,10089,10749,0,2028,1316,1317,1921,10856,10067,10068,10728,43,10875,10879,10880,10876,0,2026,2031,2030,2029,10854,10858,10859,10855,43,10879,10868,10867,10880,0,2031,2018,2019,2030,10858,10847,10846,10859,43,10876,10880,10083,10082,0,2029,2030,1310,1311,10855,10859,10062,10061,43,10880,10867,10085,10083,0,2030,2019,1313,1310,10859,10846,10064,10062,43,10875,10881,10882,10879,0,2026,2033,2032,2031,10854,10860,10861,10858,43,10881,10373,10372,10882,0,2033,1577,1578,2032,10860,10352,10351,10861,43,10879,10882,10874,10868,0,2031,2032,2025,2018,10858,10861,10853,10847,43,10882,10372,10374,10874,0,2032,1578,1579,2025,10861,10351,10353,10853,43,10875,10878,10883,10881,0,2026,2027,2034,2033,10854,10857,10862,10860,43,10878,10748,10752,10883,0,2027,1922,1925,2034,10857,10727,10731,10862,43,10881,10883,10385,10373,0,2033,2034,1590,1577,10860,10862,10364,10352,43,10883,10752,10252,10385,0,2034,1925,1477,1590,10862,10731,10231,10364,43,10884,10885,10886,10887,0,2035,2038,2037,2036,10863,10864,10865,10866,43,10885,10053,10052,10886,0,2038,1280,1281,2037,10864,10032,10031,10865,43,10887,10886,9802,9801,0,2036,2037,1079,1080,10866,10865,9781,9780,43,10886,10052,9804,9802,0,2037,1281,1082,1079,10865,10031,9783,9781,43,10884,10888,10889,10885,0,2035,2040,2039,2038,10863,10867,10868,10864,43,10888,10890,10891,10889,0,2040,2042,2041,2039,10867,10869,10870,10868,43,10885,10889,10064,10053,0,2038,2039,1292,1280,10864,10868,10043,10032,43,10889,10891,10045,10064,0,2039,2041,1273,1292,10868,10870,10024,10043,43,10884,10892,10893,10888,0,2035,2044,2043,2040,10863,10871,10872,10867,43,10892,8780,8789,10893,0,2044,569,577,2043,10871,8759,8768,10872,43,10888,10893,10894,10890,0,2040,2043,2045,2042,10867,10872,10873,10869,43,10893,8789,8790,10894,0,2043,577,578,2045,10872,8768,8769,10873,43,10884,10887,10895,10892,0,2035,2036,2046,2044,10863,10866,10874,10871,43,10887,9801,9807,10895,0,2036,1080,1085,2046,10866,9780,9786,10874,43,10892,10895,8781,8780,0,2044,2046,568,569,10871,10874,8760,8759,43,10895,9807,8784,8781,0,2046,1085,572,568,10874,9786,8763,8760,43,10896,10897,10898,10899,0,2047,2050,2049,2048,10875,10876,10877,10878,43,10897,8787,8795,10898,0,2050,576,583,2049,10876,8766,8774,10877,43,10899,10898,10900,10901,0,2048,2049,2052,2051,10878,10877,10879,10880,43,10898,8795,8796,10900,0,2049,583,584,2052,10877,8774,8775,10879,43,10896,10902,10903,10897,0,2047,2054,2053,2050,10875,10881,10882,10876,43,10902,10890,10894,10903,0,2054,2042,2045,2053,10881,10869,10873,10882,43,10897,10903,8788,8787,0,2050,2053,575,576,10876,10882,8767,8766,43,10903,10894,8790,8788,0,2053,2045,578,575,10882,10873,8769,8767,43,10896,10904,10905,10902,0,2047,2056,2055,2054,10875,10883,10884,10881,43,10904,10040,10044,10905,0,2056,1269,1272,2055,10883,10019,10023,10884,43,10902,10905,10891,10890,0,2054,2055,2041,2042,10881,10884,10870,10869,43,10905,10044,10045,10891,0,2055,1272,1273,2041,10884,10023,10024,10870,43,10896,10899,10906,10904,0,2047,2048,2057,2056,10875,10878,10885,10883,43,10899,10901,10907,10906,0,2048,2051,2058,2057,10878,10880,10886,10885,43,10904,10906,10041,10040,0,2056,2057,1268,1269,10883,10885,10020,10019,43,10906,10907,10029,10041,0,2057,2058,1257,1268,10885,10886,10008,10020,43,10908,10909,10910,10911,0,2059,2062,2061,2060,10887,10888,10889,10890,43,10909,8940,8939,10910,0,2062,760,761,2061,10888,8919,8918,10889,43,10911,10910,3536,3535,0,2060,2061,2064,2063,10890,10889,3536,3535,43,10910,8939,1499,3536,0,2061,761,762,2064,10889,8918,1499,3536,43,10908,10912,10913,10909,0,2059,2066,2065,2062,10887,10891,10892,10888,43,10912,10901,10900,10913,0,2066,2051,2052,2065,10891,10880,10879,10892,43,10909,10913,8949,8940,0,2062,2065,773,760,10888,10892,8928,8919,43,10913,10900,8796,8949,0,2065,2052,584,773,10892,10879,8775,8928,43,10908,10914,10915,10912,0,2059,2068,2067,2066,10887,10893,10894,10891,43,10914,10024,10028,10915,0,2068,1251,1256,2067,10893,10003,10007,10894,43,10912,10915,10907,10901,0,2066,2067,2058,2051,10891,10894,10886,10880,43,10915,10028,10029,10907,0,2067,1256,1257,2058,10894,10007,10008,10886,43,10908,10911,10916,10914,0,2059,2060,2069,2068,10887,10890,10895,10893,43,10911,3535,3542,10916,0,2060,2063,2070,2069,10890,3535,3542,10895,43,10914,10916,10025,10024,0,2068,2069,1250,1251,10893,10895,10004,10003,43,10916,3542,2644,10025,0,2069,2070,1253,1250,10895,3542,2644,10004,43,10917,10918,10919,10920,0,2071,2074,2073,2072,10896,10897,10898,10899,43,10918,10921,10922,10919,0,2074,2076,2075,2073,10897,10900,10901,10898,43,10920,10919,9974,9973,0,2072,2073,1200,1201,10899,10898,9953,9952,43,10919,10922,9977,9974,0,2073,2075,1204,1200,10898,10901,9956,9953,43,10917,10923,10924,10918,0,2071,2078,2077,2074,10896,10902,10903,10897,43,10923,9822,9827,10924,0,2078,1101,1105,2077,10902,9801,9806,10903,43,10918,10924,10925,10921,0,2074,2077,2079,2076,10897,10903,10904,10900,43,10924,9827,9768,10925,0,2077,1105,1051,2079,10903,9806,9747,10904,43,10917,10926,10927,10923,0,2071,2081,2080,2078,10896,10905,10906,10902,43,10926,10405,10411,10927,0,2081,1611,1616,2080,10905,10384,10390,10906,43,10923,10927,9823,9822,0,2078,2080,1100,1101,10902,10906,9802,9801,43,10927,10411,9825,9823,0,2080,1616,1103,1100,10906,10390,9804,9802,43,10917,10920,10928,10926,0,2071,2072,2082,2081,10896,10899,10907,10905,43,10920,9973,9982,10928,0,2072,1201,1209,2082,10899,9952,9961,10907,43,10926,10928,10406,10405,0,2081,2082,1610,1611,10905,10907,10385,10384,43,10928,9982,9983,10406,0,2082,1209,1210,1610,10907,9961,9962,10385,43,10929,10930,10931,10932,0,2083,1022,1022,2084,10908,10909,10910,10911,43,10930,10933,10934,10931,0,1022,1009,1010,1022,10909,10912,10913,10910,43,10932,10931,10587,10586,0,2084,1022,1022,1773,10911,10910,10566,10565,43,10931,10934,10577,10587,0,1022,1010,1009,1022,10910,10913,10556,10566,43,10929,10935,10936,10930,0,2083,2085,1022,1022,10908,10914,10915,10909,43,10935,9754,9753,10936,0,2085,1039,1022,1022,10914,9733,9732,10915,43,10930,10936,10937,10933,0,1022,1022,1010,1009,10909,10915,10916,10912,43,10936,9753,9712,10937,0,1022,1022,1009,1010,10915,9732,9691,10916,43,10929,10938,10939,10935,0,2083,2087,2086,2085,10908,10917,10918,10914,43,10938,10921,10925,10939,0,2087,2076,2079,2086,10917,10900,10904,10918,43,10935,10939,9766,9754,0,2085,2086,1049,1039,10914,10918,9745,9733,43,10939,10925,9768,9766,0,2086,2079,1051,1049,10918,10904,9747,9745,43,10929,10932,10940,10938,0,2083,2084,2088,2087,10908,10911,10919,10917,43,10932,10586,10590,10940,0,2084,1773,1776,2088,10911,10565,10569,10919,43,10938,10940,10922,10921,0,2087,2088,2075,2076,10917,10919,10901,10900,43,10940,10590,9977,10922,0,2088,1776,1204,2075,10919,10569,9956,10901,43,10941,10942,10943,10944,0,1005,1006,1006,1005,10920,10921,10922,10923,43,10944,10943,10571,10570,0,1005,1006,1006,1005,10923,10922,10550,10549,43,10941,10945,10946,10942,0,1005,1005,1006,1006,10920,10924,10925,10921,43,10945,9698,9697,10946,0,1005,1005,1006,1006,10924,9677,9676,10925,43,10941,10947,10948,10945,0,1005,1008,1008,1005,10920,10926,10927,10924,43,10947,10933,10937,10948,0,1008,1009,1010,1008,10926,10912,10916,10927,43,10945,10948,9710,9698,0,1005,1008,1008,1005,10924,10927,9689,9677,43,10948,10937,9712,9710,0,1008,1010,1009,1008,10927,10916,9691,9689,43,10941,10944,10949,10947,0,1005,1005,1008,1008,10920,10923,10928,10926,43,10944,10570,10576,10949,0,1005,1005,1008,1008,10923,10549,10555,10928,43,10947,10949,10934,10933,0,1008,1008,1010,1009,10926,10928,10913,10912,43,10949,10576,10577,10934,0,1008,1008,1009,1010,10928,10555,10556,10913,43,10950,10951,10952,10953,0,2089,2092,2091,2090,10929,10930,10931,10932,43,10951,9932,9938,10952,0,2092,1172,1177,2091,10930,9911,9917,10931,43,10953,10952,10130,10129,0,2090,2091,1357,1358,10932,10931,10109,10108,43,10952,9938,9819,10130,0,2091,1177,1097,1357,10931,9917,9798,10109,43,10950,10954,10955,10951,0,2089,2094,2093,2092,10929,10933,10934,10930,43,10954,10146,10149,10955,0,2094,1375,1377,2093,10933,10125,10128,10934,43,10951,10955,9933,9932,0,2092,2093,1171,1172,10930,10934,9912,9911,43,10955,10149,9935,9933,0,2093,1377,1174,1171,10934,10128,9914,9912,43,10950,10956,10957,10954,0,2089,2096,2095,2094,10929,10935,10936,10933,43,10956,10958,10959,10957,0,2096,2098,2097,2095,10935,10937,10938,10936,43,10954,10957,10147,10146,0,2094,2095,1374,1375,10933,10936,10126,10125,43,10957,10959,9647,10147,0,2095,2097,982,1374,10936,10938,9626,10126,43,10950,10953,10960,10956,0,2089,2090,2099,2096,10929,10932,10939,10935,43,10953,10129,10133,10960,0,2090,1358,1361,2099,10932,10108,10112,10939,43,10956,10960,10961,10958,0,2096,2099,2100,2098,10935,10939,10940,10937,43,10960,10133,10134,10961,0,2099,1361,1362,2100,10939,10112,10113,10940,43,10962,10963,10964,10965,0,2101,2104,2103,2102,10941,10942,10943,10944,43,10963,10188,10194,10964,0,2104,1414,1419,2103,10942,10167,10173,10943,43,10965,10964,10966,10967,0,2102,2103,2106,2105,10944,10943,10945,10946,43,10964,10194,10195,10966,0,2103,1419,1420,2106,10943,10173,10174,10945,43,10962,10968,10969,10963,0,2101,2108,2107,2104,10941,10947,10948,10942,43,10968,10958,10961,10969,0,2108,2098,2100,2107,10947,10937,10940,10948,43,10963,10969,10189,10188,0,2104,2107,1413,1414,10942,10948,10168,10167,43,10969,10961,10134,10189,0,2107,2100,1362,1413,10948,10940,10113,10168,43,10962,10970,10971,10968,0,2101,2110,2109,2108,10941,10949,10950,10947,43,10970,9637,9646,10971,0,2110,973,981,2109,10949,9616,9625,10950,43,10968,10971,10959,10958,0,2108,2109,2097,2098,10947,10950,10938,10937,43,10971,9646,9647,10959,0,2109,981,982,2097,10950,9625,9626,10938,43,10962,10965,10972,10970,0,2101,2102,2111,2110,10941,10944,10951,10949,43,10965,10967,10973,10972,0,2102,2105,2112,2111,10944,10946,10952,10951,43,10970,10972,9638,9637,0,2110,2111,972,973,10949,10951,9617,9616,43,10972,10973,9641,9638,0,2111,2112,976,972,10951,10952,9620,9617,43,10974,10975,10976,10977,0,2113,2114,279,274,10953,10954,10955,10956,43,10975,7950,7954,10976,0,2114,218,223,279,10954,7929,7933,10955,43,10977,10976,10978,10979,0,274,279,282,276,10956,10955,10957,10958,43,10976,7954,7955,10978,0,279,223,14,282,10955,7933,7934,10957,43,10974,10980,10981,10975,0,2113,2116,2115,2114,10953,10959,10960,10954,43,10980,3609,3608,10981,0,2116,2118,2117,2115,10959,3609,3608,10960,43,10975,10981,7951,7950,0,2114,2115,217,218,10954,10960,7930,7929,43,10981,3608,475,7951,0,2115,2117,220,217,10960,3608,475,7930,43,10974,10982,10983,10980,0,2113,2120,2119,2116,10953,10961,10962,10959,43,10982,10984,10985,10983,0,2120,2122,2121,2119,10961,10963,10964,10962,43,10980,10983,3614,3609,0,2116,2119,2123,2118,10959,10962,3614,3609,43,10983,10985,3615,3614,0,2119,2121,2124,2123,10962,10964,3615,3614,43,10974,10977,10986,10982,0,2113,274,273,2120,10953,10956,10965,10961,43,10977,10979,10987,10986,0,274,276,275,273,10956,10958,10966,10965,43,10982,10986,10988,10984,0,2120,273,277,2122,10961,10965,10967,10963,43,10986,10987,10989,10988,0,273,275,278,277,10965,10966,10968,10967,43,10990,10991,10992,10993,0,2125,2128,2127,2126,10969,10970,10971,10972,43,10991,8005,8011,10992,0,2128,281,285,2127,10970,7984,7990,10971,43,10993,10992,8088,8087,0,2126,2127,2130,2129,10972,10971,8067,8066,43,10992,8011,8012,8088,0,2127,285,5,2130,10971,7990,7991,8067,43,10990,10994,10995,10991,0,2125,2131,221,2128,10969,10973,10974,10970,43,10994,8903,8907,10995,0,2131,716,222,221,10973,8882,8886,10974,43,10991,10995,8006,8005,0,2128,221,223,281,10970,10974,7985,7984,43,10995,8907,8008,8006,0,221,222,14,223,10974,8886,7987,7985,43,10990,10996,10997,10994,0,2125,2133,2132,2131,10969,10975,10976,10973,43,10996,9325,9329,10997,0,2133,900,904,2132,10975,9304,9308,10976,43,10994,10997,8904,8903,0,2131,2132,715,716,10973,10976,8883,8882,43,10997,9329,7866,8904,0,2132,904,120,715,10976,9308,7845,8883,43,10990,10993,10998,10996,0,2125,2126,2134,2133,10969,10972,10977,10975,43,10993,8087,8095,10998,0,2126,2129,2135,2134,10972,8066,8074,10977,43,10996,10998,9326,9325,0,2133,2134,899,900,10975,10977,9305,9304,43,10998,8095,8096,9326,0,2134,2135,902,899,10977,8074,8075,9305,43,10999,11000,11001,11002,0,2136,2139,2138,2137,10978,10979,10980,10981,43,11000,8851,8850,11001,0,2139,2141,2140,2138,10979,8830,8829,10980,43,11002,11001,8057,8045,0,2137,2138,333,317,10981,10980,8036,8024,43,11001,8850,7847,8057,0,2138,2140,335,333,10980,8829,7826,8036,43,10999,11003,11004,11000,0,2136,2142,709,2139,10978,10982,10983,10979,43,11003,8030,8036,11004,0,2142,304,309,709,10982,8009,8015,10983,43,11000,11004,8859,8851,0,2139,709,925,2141,10979,10983,8838,8830,43,11004,8036,8037,8859,0,709,309,310,925,10983,8015,8016,8838,43,10999,11005,11006,11003,0,2136,2144,2143,2142,10978,10984,10985,10982,43,11005,8099,8104,11006,0,2144,378,382,2143,10984,8078,8083,10985,43,11003,11006,8031,8030,0,2142,2143,303,304,10982,10985,8010,8009,43,11006,8104,8033,8031,0,2143,382,306,303,10985,8083,8012,8010,43,10999,11002,11007,11005,0,2136,2137,2145,2144,10978,10981,10986,10984,43,11002,8045,8044,11007,0,2137,317,318,2145,10981,8024,8023,10986,43,11005,11007,8100,8099,0,2144,2145,377,378,10984,10986,8079,8078,43,11007,8044,8046,8100,0,2145,318,319,377,10986,8023,8025,8079,43,11008,11009,11010,11011,0,2146,2149,2148,2147,10987,10988,10989,10990,43,11009,8090,8089,11010,0,2149,366,367,2148,10988,8069,8068,10989,43,11011,11010,8010,7995,0,2147,2148,2151,2150,10990,10989,7989,7974,43,11010,8089,8012,8010,0,2148,367,368,2151,10989,8068,7991,7989,43,11008,11012,11013,11009,0,2146,2153,2152,2149,10987,10991,10992,10988,43,11012,8024,8032,11013,0,2153,298,305,2152,10991,8003,8011,10992,43,11009,11013,8103,8090,0,2149,2152,381,366,10988,10992,8082,8069,43,11013,8032,8033,8103,0,2152,305,306,381,10992,8011,8012,8082,43,11008,11014,11015,11012,0,2146,2155,2154,2153,10987,10993,10994,10991,43,11014,11016,11017,11015,0,2155,2157,2156,2154,10993,10995,10996,10994,43,11012,11015,8025,8024,0,2153,2154,297,298,10991,10994,8004,8003,43,11015,11017,8027,8025,0,2154,2156,300,297,10994,10996,8006,8004,43,11008,11011,11018,11014,0,2146,2147,2158,2155,10987,10990,10997,10993,43,11011,7995,7994,11018,0,2147,2150,2159,2158,10990,7974,7973,10997,43,11014,11018,11019,11016,0,2155,2158,2160,2157,10993,10997,10998,10995,43,11018,7994,7996,11019,0,2158,2159,2161,2160,10997,7973,7975,10998,43,11020,11021,11022,11023,0,2162,2165,2164,2163,10999,11000,11001,11002,43,11021,8856,8860,11022,0,2165,662,309,2164,11000,8835,8839,11001,43,11023,11022,8035,8020,0,2163,2164,2167,2166,11002,11001,8014,7999,43,11022,8860,8037,8035,0,2164,309,665,2167,11001,8839,8016,8014,43,11020,11024,11025,11021,0,2162,2169,2168,2165,10999,11003,11004,11000,43,11024,10979,10978,11025,0,2169,276,282,2168,11003,10958,10957,11004,43,11021,11025,8857,8856,0,2165,2168,661,662,11000,11004,8836,8835,43,11025,10978,7955,8857,0,2168,282,14,661,11004,10957,7934,8836,43,11020,11026,11027,11024,0,2162,2171,2170,2169,10999,11005,11006,11003,43,11026,11028,11029,11027,0,2171,2173,2172,2170,11005,11007,11008,11006,43,11024,11027,10987,10979,0,2169,2170,275,276,11003,11006,10966,10958,43,11027,11029,10989,10987,0,2170,2172,278,275,11006,11008,10968,10966,43,11020,11023,11030,11026,0,2162,2163,2174,2171,10999,11002,11009,11005,43,11023,8020,8019,11030,0,2163,2166,2175,2174,11002,7999,7998,11009,43,11026,11030,11031,11028,0,2171,2174,2176,2173,11005,11009,11010,11007,43,11030,8019,8021,11031,0,2174,2175,294,2176,11009,7998,8000,11010,43,11032,11033,11034,11035,0,2177,2180,2179,2178,11011,11012,11013,11014,43,11033,3667,3666,11034,0,2180,2182,2181,2179,11012,3667,3666,11013,43,11035,11034,11036,11037,0,2178,2179,2184,2183,11014,11013,11015,11016,43,11034,3666,3670,11036,0,2179,2181,2185,2184,11013,3666,3670,11015,43,11032,11038,11039,11033,0,2177,2187,2186,2180,11011,11017,11018,11012,43,11038,8898,8897,11039,0,2187,707,708,2186,11017,8877,8876,11018,43,11033,11039,3673,3667,0,2180,2186,2188,2182,11012,11018,3673,3667,43,11039,8897,1444,3673,0,2186,708,709,2188,11018,8876,1444,3673,43,11032,11040,11041,11038,0,2177,2169,2168,2187,11011,11019,11020,11017,43,11040,7999,8007,11041,0,2169,276,282,2168,11019,7978,7986,11020,43,11038,11041,8906,8898,0,2187,2168,661,707,11017,11020,8885,8877,43,11041,8007,8008,8906,0,2168,282,14,661,11020,7986,7987,8885,43,11032,11035,11042,11040,0,2177,2178,2170,2169,11011,11014,11021,11019,43,11035,11037,11043,11042,0,2178,2183,2172,2170,11014,11016,11022,11021,43,11040,11042,8000,7999,0,2169,2170,275,276,11019,11021,7979,7978,43,11042,11043,8002,8000,0,2170,2172,278,275,11021,11022,7981,7979,43,11044,11045,11046,11047,0,2189,905,9,2190,11023,11024,11025,11026,43,11045,9334,9341,11046,0,905,905,9,9,11024,9313,9320,11025,43,11047,11046,3683,3682,0,2190,9,156,2191,11026,11025,3683,3682,43,11046,9341,1905,3683,0,9,9,156,156,11025,9320,1905,3683,43,11044,11048,11049,11045,0,2189,2192,906,905,11023,11027,11028,11024,43,11048,11050,11051,11049,0,2192,2193,12,906,11027,11029,11030,11028,43,11045,11049,9335,9334,0,905,906,906,905,11024,11028,9314,9313,43,11049,11051,9338,9335,0,906,12,12,906,11028,11030,9317,9314,43,11044,11052,11053,11048,0,2189,2195,2194,2192,11023,11031,11032,11027,43,11052,10984,10988,11053,0,2195,2122,277,2194,11031,10963,10967,11032,43,11048,11053,11054,11050,0,2192,2194,2196,2193,11027,11032,11033,11029,43,11053,10988,10989,11054,0,2194,277,278,2196,11032,10967,10968,11033,43,11044,11047,11055,11052,0,2189,2190,2197,2195,11023,11026,11034,11031,43,11047,3682,3692,11055,0,2190,2191,2198,2197,11026,3682,3692,11034,43,11052,11055,10985,10984,0,2195,2197,2121,2122,11031,11034,10964,10963,43,11055,3692,3615,10985,0,2197,2198,2124,2121,11034,3692,3615,10964,43,11056,11057,11058,11059,0,2199,10,67,2200,11035,11036,11037,11038,43,11057,9354,9363,11058,0,10,10,67,67,11036,9333,9342,11037,43,11059,11058,11060,11061,0,2200,67,6,2201,11038,11037,11039,11040,43,11058,9363,9364,11060,0,67,67,6,6,11037,9342,9343,11039,43,11056,11062,11063,11057,0,2199,2192,906,10,11035,11041,11042,11036,43,11062,11064,11065,11063,0,2192,2193,12,906,11041,11043,11044,11042,43,11057,11063,9355,9354,0,10,906,906,10,11036,11042,9334,9333,43,11063,11065,9358,9355,0,906,12,12,906,11042,11044,9337,9334,43,11056,11066,11067,11062,0,2199,2202,2194,2192,11035,11045,11046,11041,43,11066,7992,8001,11067,0,2202,269,277,2194,11045,7971,7980,11046,43,11062,11067,11068,11064,0,2192,2194,2196,2193,11041,11046,11047,11043,43,11067,8001,8002,11068,0,2194,277,278,2196,11046,7980,7981,11047,43,11056,11059,11069,11066,0,2199,2200,2203,2202,11035,11038,11048,11045,43,11059,11061,11070,11069,0,2200,2201,2204,2203,11038,11040,11049,11048,43,11066,11069,7993,7992,0,2202,2203,268,269,11045,11048,7972,7971,43,11069,11070,7996,7993,0,2203,2204,272,268,11048,11049,7975,7972,43,11071,11072,11073,11074,0,2205,2208,2207,2206,11050,11051,11052,11053,43,11072,11075,11076,11073,0,2208,2210,2209,2207,11051,11054,11055,11052,43,11074,11073,11017,11016,0,2206,2207,2156,2157,11053,11052,10996,10995,43,11073,11076,8027,11017,0,2207,2209,300,2156,11052,11055,8006,10996,43,11071,11077,11078,11072,0,2205,909,912,2208,11050,11056,11057,11051,43,11077,9379,9385,11078,0,909,909,912,912,11056,9358,9364,11057,43,11072,11078,11079,11075,0,2208,912,913,2210,11051,11057,11058,11054,43,11078,9385,9386,11079,0,912,912,913,913,11057,9364,9365,11058,43,11071,11080,11081,11077,0,2205,2211,910,909,11050,11059,11060,11056,43,11080,11061,11060,11081,0,2211,2212,911,910,11059,11040,11039,11060,43,11077,11081,9380,9379,0,909,910,910,909,11056,11060,9359,9358,43,11081,11060,9364,9380,0,910,911,911,910,11060,11039,9343,9359,43,11071,11074,11082,11080,0,2205,2206,2213,2211,11050,11053,11061,11059,43,11074,11016,11019,11082,0,2206,2157,2160,2213,11053,10995,10998,11061,43,11080,11082,11070,11061,0,2211,2213,2214,2212,11059,11061,11049,11040,43,11082,11019,7996,11070,0,2213,2160,2161,2214,11061,10998,7975,11049,43,11083,11084,11085,11086,0,2215,2218,2217,2216,11062,11063,11064,11065,43,11084,11087,11088,11085,0,2218,2220,2219,2217,11063,11066,11067,11064,43,11086,11085,8018,8017,0,2216,2217,290,291,11065,11064,7997,7996,43,11085,11088,8021,8018,0,2217,2219,294,290,11064,11067,8000,7997,43,11083,11089,11090,11084,0,2215,914,915,2218,11062,11068,11069,11063,43,11089,9402,9401,11090,0,914,914,915,915,11068,9381,9380,11069,43,11084,11090,11091,11087,0,2218,915,916,2220,11063,11069,11070,11066,43,11090,9401,9403,11091,0,915,915,916,916,11069,9380,9382,11070,43,11083,11092,11093,11089,0,2215,2221,917,914,11062,11071,11072,11068,43,11092,11075,11079,11093,0,2221,2210,913,917,11071,11054,11058,11072,43,11089,11093,9414,9402,0,914,917,917,914,11068,11072,9393,9381,43,11093,11079,9386,9414,0,917,913,913,917,11072,11058,9365,9393,43,11083,11086,11094,11092,0,2215,2216,2222,2221,11062,11065,11073,11071,43,11086,8017,8026,11094,0,2216,291,299,2222,11065,7996,8005,11073,43,11092,11094,11076,11075,0,2221,2222,2209,2210,11071,11073,11055,11054,43,11094,8026,8027,11076,0,2222,299,300,2209,11073,8005,8006,11055,43,11095,11096,11097,11098,0,2223,2226,2225,2224,11074,11075,11076,11077,43,11096,11050,11054,11097,0,2226,2193,2196,2225,11075,11029,11033,11076,43,11098,11097,11029,11028,0,2224,2225,2172,2173,11077,11076,11008,11007,43,11097,11054,10989,11029,0,2225,2196,278,2172,11076,11033,10968,11008,43,11095,11099,11100,11096,0,2223,918,919,2226,11074,11078,11079,11075,43,11099,9420,9419,11100,0,918,918,919,919,11078,9399,9398,11079,43,11096,11100,11051,11050,0,2226,919,12,2193,11075,11079,11030,11029,43,11100,9419,9338,11051,0,919,919,12,12,11079,9398,9317,11030,43,11095,11101,11102,11099,0,2223,2227,922,918,11074,11080,11081,11078,43,11101,11087,11091,11102,0,2227,2220,916,922,11080,11066,11070,11081,43,11099,11102,9429,9420,0,918,922,922,918,11078,11081,9408,9399,43,11102,11091,9403,9429,0,922,916,916,922,11081,11070,9382,9408,43,11095,11098,11103,11101,0,2223,2224,2228,2227,11074,11077,11082,11080,43,11098,11028,11031,11103,0,2224,2173,2176,2228,11077,11007,11010,11082,43,11101,11103,11088,11087,0,2227,2228,2219,2220,11080,11082,11067,11066,43,11103,11031,8021,11088,0,2228,2176,294,2219,11082,11010,8000,11067,43,11104,11105,11106,11107,0,2229,923,919,2226,11083,11084,11085,11086,43,11105,9434,9438,11106,0,923,923,919,919,11084,9413,9417,11085,43,11107,11106,11065,11064,0,2226,919,12,2193,11086,11085,11044,11043,43,11106,9438,9358,11065,0,919,919,12,12,11085,9417,9337,11044,43,11104,11108,11109,11105,0,2229,2230,924,923,11083,11087,11088,11084,43,11108,3748,3747,11109,0,2230,2231,925,924,11087,3748,3747,11088,43,11105,11109,9435,9434,0,923,924,924,923,11084,11088,9414,9413,43,11109,3747,2004,9435,0,924,925,925,924,11088,3747,2004,9414,43,11104,11110,11111,11108,0,2229,2233,2232,2230,11083,11089,11090,11087,43,11110,11037,11036,11111,0,2233,2183,2184,2232,11089,11016,11015,11090,43,11108,11111,3751,3748,0,2230,2232,2234,2231,11087,11090,3751,3748,43,11111,11036,3670,3751,0,2232,2184,2185,2234,11090,11015,3670,3751,43,11104,11107,11112,11110,0,2229,2226,2225,2233,11083,11086,11091,11089,43,11107,11064,11068,11112,0,2226,2193,2196,2225,11086,11043,11047,11091,43,11110,11112,11043,11037,0,2233,2225,2172,2183,11089,11091,11022,11016,43,11112,11068,8002,11043,0,2225,2196,278,2172,11091,11047,7981,11022,43,11113,11114,11115,11116,0,947,947,67,67,11092,11093,11094,11095,43,11114,11117,11118,11115,0,947,947,67,67,11093,11096,11097,11094,43,11116,11115,11119,11120,0,67,67,6,6,11095,11094,11098,11099,43,11115,11118,11121,11119,0,67,67,6,6,11094,11097,11100,11098,43,11113,11122,11123,11114,0,947,948,948,947,11092,11101,11102,11093,43,11122,11124,11125,11123,0,948,931,931,948,11101,11103,11104,11102,43,11114,11123,11126,11117,0,947,948,948,947,11093,11102,11105,11096,43,11123,11125,11127,11126,0,948,931,931,948,11102,11104,11106,11105,43,11113,11128,11129,11122,0,947,947,948,948,11092,11107,11108,11101,43,11128,11130,11131,11129,0,947,947,948,948,11107,11109,11110,11108,43,11122,11129,11132,11124,0,948,948,931,931,11101,11108,11111,11103,43,11129,11131,11133,11132,0,948,948,931,931,11108,11110,11112,11111,43,11113,11116,11134,11128,0,947,67,67,947,11092,11095,11113,11107,43,11116,11120,11135,11134,0,67,6,6,67,11095,11099,11114,11113,43,11128,11134,11136,11130,0,947,67,67,947,11107,11113,11115,11109,43,11134,11135,11137,11136,0,67,6,6,67,11113,11114,11116,11115,43,11138,11139,11140,11141,0,909,910,910,909,11117,11118,11119,11120,43,11139,11120,11119,11140,0,910,911,911,910,11118,11099,11098,11119,43,11141,11140,11142,11143,0,909,910,910,909,11120,11119,11121,11122,43,11140,11119,11121,11142,0,910,911,911,910,11119,11098,11100,11121,43,11138,11144,11145,11139,0,909,909,910,910,11117,11123,11124,11118,43,11144,11146,11147,11145,0,909,909,910,910,11123,11125,11126,11124,43,11139,11145,11135,11120,0,910,910,911,911,11118,11124,11114,11099,43,11145,11147,11137,11135,0,910,910,911,911,11124,11126,11116,11114,43,11138,11148,11149,11144,0,909,912,912,909,11117,11127,11128,11123,43,11148,11150,11151,11149,0,912,913,913,912,11127,11129,11130,11128,43,11144,11149,11152,11146,0,909,912,912,909,11123,11128,11131,11125,43,11149,11151,11153,11152,0,912,913,913,912,11128,11130,11132,11131,43,11138,11141,11154,11148,0,909,909,912,912,11117,11120,11133,11127,43,11141,11143,11155,11154,0,909,909,912,912,11120,11122,11134,11133,43,11148,11154,11156,11150,0,912,912,913,913,11127,11133,11135,11129,43,11154,11155,11157,11156,0,912,912,913,913,11133,11134,11136,11135,43,11158,11159,11160,11161,0,914,917,917,914,11137,11138,11139,11140,43,11159,11150,11156,11160,0,917,913,913,917,11138,11129,11135,11139,43,11161,11160,11162,11163,0,914,917,917,914,11140,11139,11141,11142,43,11160,11156,11157,11162,0,917,913,913,917,11139,11135,11136,11141,43,11158,11164,11165,11159,0,914,914,917,917,11137,11143,11144,11138,43,11164,11166,11167,11165,0,914,914,917,917,11143,11145,11146,11144,43,11159,11165,11151,11150,0,917,917,913,913,11138,11144,11130,11129,43,11165,11167,11153,11151,0,917,917,913,913,11144,11146,11132,11130,43,11158,11168,11169,11164,0,914,915,915,914,11137,11147,11148,11143,43,11168,11170,11171,11169,0,915,916,916,915,11147,11149,11150,11148,43,11164,11169,11172,11166,0,914,915,915,914,11143,11148,11151,11145,43,11169,11171,11173,11172,0,915,916,916,915,11148,11150,11152,11151,43,11158,11161,11174,11168,0,914,914,915,915,11137,11140,11153,11147,43,11161,11163,11175,11174,0,914,914,915,915,11140,11142,11154,11153,43,11168,11174,11176,11170,0,915,915,916,916,11147,11153,11155,11149,43,11174,11175,11177,11176,0,915,915,916,916,11153,11154,11156,11155,43,11178,11179,11180,11181,0,953,922,922,953,11157,11158,11159,11160,43,11179,11170,11176,11180,0,922,916,916,922,11158,11149,11155,11159,43,11181,11180,11182,11183,0,953,922,922,953,11160,11159,11161,11162,43,11180,11176,11177,11182,0,922,916,916,922,11159,11155,11156,11161,43,11178,11184,11185,11179,0,953,953,922,922,11157,11163,11164,11158,43,11184,11186,11187,11185,0,953,953,922,922,11163,11165,11166,11164,43,11179,11185,11171,11170,0,922,922,916,916,11158,11164,11150,11149,43,11185,11187,11173,11171,0,922,922,916,916,11164,11166,11152,11150,43,11178,11188,11189,11184,0,953,939,939,953,11157,11167,11168,11163,43,11188,11190,11191,11189,0,939,328,328,939,11167,11169,11170,11168,43,11184,11189,11192,11186,0,953,939,939,953,11163,11168,11171,11165,43,11189,11191,11193,11192,0,939,328,328,939,11168,11170,11172,11171,43,11178,11181,11194,11188,0,953,953,939,939,11157,11160,11173,11167,43,11181,11183,11195,11194,0,953,953,939,939,11160,11162,11174,11173,43,11188,11194,11196,11190,0,939,939,328,328,11167,11173,11175,11169,43,11194,11195,11197,11196,0,939,939,328,328,11173,11174,11176,11175,43,11198,11199,11200,11201,0,929,929,928,928,11177,11178,11179,11180,43,11199,11202,11203,11200,0,929,929,928,928,11178,11181,11182,11179,43,11201,11200,11125,11124,0,928,928,931,931,11180,11179,11104,11103,43,11200,11203,11127,11125,0,928,928,931,931,11179,11182,11106,11104,43,11198,11204,11205,11199,0,929,12,12,929,11177,11183,11184,11178,43,11204,11206,11207,11205,0,12,12,12,12,11183,11185,11186,11184,43,11199,11205,11208,11202,0,929,12,12,929,11178,11184,11187,11181,43,11205,11207,11209,11208,0,12,12,12,12,11184,11186,11188,11187,43,11198,11210,11211,11204,0,929,929,12,12,11177,11189,11190,11183,43,11210,11212,11213,11211,0,929,929,12,12,11189,11191,11192,11190,43,11204,11211,11214,11206,0,12,12,12,12,11183,11190,11193,11185,43,11211,11213,11215,11214,0,12,12,12,12,11190,11192,11194,11193,43,11198,11201,11216,11210,0,929,928,928,929,11177,11180,11195,11189,43,11201,11124,11132,11216,0,928,931,931,928,11180,11103,11111,11195,43,11210,11216,11217,11212,0,929,928,928,929,11189,11195,11196,11191,43,11216,11132,11133,11217,0,928,931,931,928,11195,11111,11112,11196,43,11218,11219,11220,11221,0,12,12,12,12,11197,11198,11199,11200,43,11219,11222,11223,11220,0,12,12,12,12,11198,11201,11202,11199,43,11221,11220,11224,11225,0,12,12,12,12,11200,11199,11203,11204,43,11220,11223,11226,11224,0,12,12,12,12,11199,11202,11205,11203,43,11218,11227,11228,11219,0,12,12,12,12,11197,11206,11207,11198,43,11227,11229,11230,11228,0,12,12,12,12,11206,11208,11209,11207,43,11219,11228,11231,11222,0,12,12,12,12,11198,11207,11210,11201,43,11228,11230,11232,11231,0,12,12,12,12,11207,11209,11211,11210,43,11218,11233,11234,11227,0,12,12,12,12,11197,11212,11213,11206,43,11233,11235,11236,11234,0,12,12,12,12,11212,11214,11215,11213,43,11227,11234,11237,11229,0,12,12,12,12,11206,11213,11216,11208,43,11234,11236,11238,11237,0,12,12,12,12,11213,11215,11217,11216,43,11218,11221,11239,11233,0,12,12,12,12,11197,11200,11218,11212,43,11221,11225,11240,11239,0,12,12,12,12,11200,11204,11219,11218,43,11233,11239,11241,11235,0,12,12,12,12,11212,11218,11220,11214,43,11239,11240,11242,11241,0,12,12,12,12,11218,11219,11221,11220,43,11243,11244,11245,11246,0,932,933,933,932,11222,11223,11224,11225,43,11244,11190,11196,11245,0,933,328,328,933,11223,11169,11175,11224,43,11246,11245,11247,11248,0,932,933,933,932,11225,11224,11226,11227,43,11245,11196,11197,11247,0,933,328,328,933,11224,11175,11176,11226,43,11243,11249,11250,11244,0,932,932,933,933,11222,11228,11229,11223,43,11249,11251,11252,11250,0,932,932,933,933,11228,11230,11231,11229,43,11244,11250,11191,11190,0,933,933,328,328,11223,11229,11170,11169,43,11250,11252,11193,11191,0,933,933,328,328,11229,11231,11172,11170,43,11243,11253,11254,11249,0,932,12,12,932,11222,11232,11233,11228,43,11253,11229,11237,11254,0,12,12,12,12,11232,11208,11216,11233,43,11249,11254,11255,11251,0,932,12,12,932,11228,11233,11234,11230,43,11254,11237,11238,11255,0,12,12,12,12,11233,11216,11217,11234,43,11243,11246,11256,11253,0,932,932,12,12,11222,11225,11235,11232,43,11246,11248,11257,11256,0,932,932,12,12,11225,11227,11236,11235,43,11253,11256,11230,11229,0,12,12,12,12,11232,11235,11209,11208,43,11256,11257,11232,11230,0,12,12,12,12,11235,11236,11211,11209,43,11258,11259,11260,11261,0,12,12,12,12,11237,11238,11239,11240,43,11259,11262,11263,11260,0,12,12,12,12,11238,11241,11242,11239,43,11261,11260,11207,11206,0,12,12,12,12,11240,11239,11186,11185,43,11260,11263,11209,11207,0,12,12,12,12,11239,11242,11188,11186,43,11258,11264,11265,11259,0,12,12,12,12,11237,11243,11244,11238,43,11264,11225,11224,11265,0,12,12,12,12,11243,11204,11203,11244,43,11259,11265,11266,11262,0,12,12,12,12,11238,11244,11245,11241,43,11265,11224,11226,11266,0,12,12,12,12,11244,11203,11205,11245,43,11258,11267,11268,11264,0,12,12,12,12,11237,11246,11247,11243,43,11267,11269,11270,11268,0,12,12,12,12,11246,11248,11249,11247,43,11264,11268,11240,11225,0,12,12,12,12,11243,11247,11219,11204,43,11268,11270,11242,11240,0,12,12,12,12,11247,11249,11221,11219,43,11258,11261,11271,11267,0,12,12,12,12,11237,11240,11250,11246,43,11261,11206,11214,11271,0,12,12,12,12,11240,11185,11193,11250,43,11267,11271,11272,11269,0,12,12,12,12,11246,11250,11251,11248,43,11271,11214,11215,11272,0,12,12,12,12,11250,11193,11194,11251,43,11273,11274,11275,11276,0,2235,2238,2237,2236,11252,11253,11254,11255,43,11274,11277,11278,11275,0,2238,2240,2239,2237,11253,11256,11257,11254,43,11276,11275,11279,11280,0,2236,2237,2242,2241,11255,11254,11258,11259,43,11275,11278,11281,11279,0,2237,2239,2243,2242,11254,11257,11260,11258,43,11273,11282,11283,11274,0,2235,914,917,2238,11252,11261,11262,11253,43,11282,11284,11285,11283,0,914,914,917,917,11261,11263,11264,11262,43,11274,11283,11286,11277,0,2238,917,913,2240,11253,11262,11265,11256,43,11283,11285,11287,11286,0,917,917,913,913,11262,11264,11266,11265,43,11273,11288,11289,11282,0,2235,915,915,914,11252,11267,11268,11261,43,11288,11290,11291,11289,0,915,916,916,915,11267,11269,11270,11268,43,11282,11289,11292,11284,0,914,915,915,914,11261,11268,11271,11263,43,11289,11291,11293,11292,0,915,916,916,915,11268,11270,11272,11271,43,11273,11276,11294,11288,0,2235,2236,915,915,11252,11255,11273,11267,43,11276,11280,11295,11294,0,2236,2241,915,915,11255,11259,11274,11273,43,11288,11294,11296,11290,0,915,915,916,916,11267,11273,11275,11269,43,11294,11295,11297,11296,0,915,915,916,916,11273,11274,11276,11275,43,11298,11299,11300,11301,0,953,953,939,939,11277,11278,11279,11280,43,11299,11302,11303,11300,0,953,953,939,939,11278,11281,11282,11279,43,11301,11300,11304,11305,0,939,939,328,328,11280,11279,11283,11284,43,11300,11303,11306,11304,0,939,939,328,328,11279,11282,11285,11283,43,11298,11307,11308,11299,0,953,922,922,953,11277,11286,11287,11278,43,11307,11290,11296,11308,0,922,916,916,922,11286,11269,11275,11287,43,11299,11308,11309,11302,0,953,922,922,953,11278,11287,11288,11281,43,11308,11296,11297,11309,0,922,916,916,922,11287,11275,11276,11288,43,11298,11310,11311,11307,0,953,953,922,922,11277,11289,11290,11286,43,11310,11312,11313,11311,0,953,953,922,922,11289,11291,11292,11290,43,11307,11311,11291,11290,0,922,922,916,916,11286,11290,11270,11269,43,11311,11313,11293,11291,0,922,922,916,916,11290,11292,11272,11270,43,11298,11301,11314,11310,0,953,939,939,953,11277,11280,11293,11289,43,11301,11305,11315,11314,0,939,328,328,939,11280,11284,11294,11293,43,11310,11314,11316,11312,0,953,939,939,953,11289,11293,11295,11291,43,11314,11315,11317,11316,0,939,328,328,939,11293,11294,11296,11295,43,11318,11319,11320,11321,0,12,12,12,12,11297,11298,11299,11300,43,11319,11322,11323,11320,0,12,12,12,12,11298,11301,11302,11299,43,11321,11320,11324,11325,0,12,12,12,12,11300,11299,11303,11304,43,11320,11323,11326,11324,0,12,12,12,12,11299,11302,11305,11303,43,11318,11327,11328,11319,0,12,12,12,12,11297,11306,11307,11298,43,11327,11329,11330,11328,0,12,12,12,12,11306,11308,11309,11307,43,11319,11328,11331,11322,0,12,12,12,12,11298,11307,11310,11301,43,11328,11330,11332,11331,0,12,12,12,12,11307,11309,11311,11310,43,11318,11333,11334,11327,0,12,12,12,12,11297,11312,11313,11306,43,11333,11335,11336,11334,0,12,12,12,12,11312,11314,11315,11313,43,11327,11334,11337,11329,0,12,12,12,12,11306,11313,11316,11308,43,11334,11336,11338,11337,0,12,12,12,12,11313,11315,11317,11316,43,11318,11321,11339,11333,0,12,12,12,12,11297,11300,11318,11312,43,11321,11325,11340,11339,0,12,12,12,12,11300,11304,11319,11318,43,11333,11339,11341,11335,0,12,12,12,12,11312,11318,11320,11314,43,11339,11340,11342,11341,0,12,12,12,12,11318,11319,11321,11320,43,11343,11344,11345,11346,0,932,933,933,932,11322,11323,11324,11325,43,11344,11305,11304,11345,0,933,328,328,933,11323,11284,11283,11324,43,11346,11345,11347,11348,0,932,933,933,932,11325,11324,11326,11327,43,11345,11304,11306,11347,0,933,328,328,933,11324,11283,11285,11326,43,11343,11349,11350,11344,0,932,932,933,933,11322,11328,11329,11323,43,11349,11351,11352,11350,0,932,932,933,933,11328,11330,11331,11329,43,11344,11350,11315,11305,0,933,933,328,328,11323,11329,11294,11284,43,11350,11352,11317,11315,0,933,933,328,328,11329,11331,11296,11294,43,11343,11353,11354,11349,0,932,12,12,932,11322,11332,11333,11328,43,11353,11322,11331,11354,0,12,12,12,12,11332,11301,11310,11333,43,11349,11354,11355,11351,0,932,12,12,932,11328,11333,11334,11330,43,11354,11331,11332,11355,0,12,12,12,12,11333,11310,11311,11334,43,11343,11346,11356,11353,0,932,932,12,12,11322,11325,11335,11332,43,11346,11348,11357,11356,0,932,932,12,12,11325,11327,11336,11335,43,11353,11356,11323,11322,0,12,12,12,12,11332,11335,11302,11301,43,11356,11357,11326,11323,0,12,12,12,12,11335,11336,11305,11302,43,11358,11359,11360,11361,0,12,12,12,12,11337,11338,11339,11340,43,11359,11362,11363,11360,0,12,12,12,12,11338,11341,11342,11339,43,11361,11360,11364,11365,0,12,12,12,12,11340,11339,11343,11344,43,11360,11363,11366,11364,0,12,12,12,12,11339,11342,11345,11343,43,11358,11367,11368,11359,0,12,12,12,12,11337,11346,11347,11338,43,11367,11335,11341,11368,0,12,12,12,12,11346,11314,11320,11347,43,11359,11368,11369,11362,0,12,12,12,12,11338,11347,11348,11341,43,11368,11341,11342,11369,0,12,12,12,12,11347,11320,11321,11348,43,11358,11370,11371,11367,0,12,12,12,12,11337,11349,11350,11346,43,11370,11372,11373,11371,0,12,12,12,12,11349,11351,11352,11350,43,11367,11371,11336,11335,0,12,12,12,12,11346,11350,11315,11314,43,11371,11373,11338,11336,0,12,12,12,12,11350,11352,11317,11315,43,11358,11361,11374,11370,0,12,12,12,12,11337,11340,11353,11349,43,11361,11365,11375,11374,0,12,12,12,12,11340,11344,11354,11353,43,11370,11374,11376,11372,0,12,12,12,12,11349,11353,11355,11351,43,11374,11375,11377,11376,0,12,12,12,12,11353,11354,11356,11355,43,11378,11379,11380,11381,0,947,948,948,947,11357,11358,11359,11360,43,11379,11382,11383,11380,0,948,931,931,948,11358,11361,11362,11359,43,11381,11380,11384,11385,0,947,948,948,947,11360,11359,11363,11364,43,11380,11383,11386,11384,0,948,931,931,948,11359,11362,11365,11363,43,11378,11387,11388,11379,0,947,947,948,948,11357,11366,11367,11358,43,11387,11389,11390,11388,0,947,947,948,948,11366,11368,11369,11367,43,11379,11388,11391,11382,0,948,948,931,931,11358,11367,11370,11361,43,11388,11390,11392,11391,0,948,948,931,931,11367,11369,11371,11370,43,11378,11393,11394,11387,0,947,67,67,947,11357,11372,11373,11366,43,11393,11395,11396,11394,0,67,6,6,67,11372,11374,11375,11373,43,11387,11394,11397,11389,0,947,67,67,947,11366,11373,11376,11368,43,11394,11396,11398,11397,0,67,6,6,67,11373,11375,11377,11376,43,11378,11381,11399,11393,0,947,947,67,67,11357,11360,11378,11372,43,11381,11385,11400,11399,0,947,947,67,67,11360,11364,11379,11378,43,11393,11399,11401,11395,0,67,67,6,6,11372,11378,11380,11374,43,11399,11400,11402,11401,0,67,67,6,6,11378,11379,11381,11380,43,11403,11404,11405,11406,0,909,910,910,909,11382,11383,11384,11385,43,11404,11395,11401,11405,0,910,911,911,910,11383,11374,11380,11384,43,11406,11405,11407,11408,0,909,910,910,909,11385,11384,11386,11387,43,11405,11401,11402,11407,0,910,911,911,910,11384,11380,11381,11386,43,11403,11409,11410,11404,0,909,909,910,910,11382,11388,11389,11383,43,11409,11411,11412,11410,0,909,909,910,910,11388,11390,11391,11389,43,11404,11410,11396,11395,0,910,910,911,911,11383,11389,11375,11374,43,11410,11412,11398,11396,0,910,910,911,911,11389,11391,11377,11375,43,11403,11413,11414,11409,0,909,912,912,909,11382,11392,11393,11388,43,11413,11415,11416,11414,0,912,913,913,912,11392,11394,11395,11393,43,11409,11414,11417,11411,0,909,912,912,909,11388,11393,11396,11390,43,11414,11416,11418,11417,0,912,913,913,912,11393,11395,11397,11396,43,11403,11406,11419,11413,0,909,909,912,912,11382,11385,11398,11392,43,11406,11408,11420,11419,0,909,909,912,912,11385,11387,11399,11398,43,11413,11419,11421,11415,0,912,912,913,913,11392,11398,11400,11394,43,11419,11420,11422,11421,0,912,912,913,913,11398,11399,11401,11400,43,11423,11424,11425,11426,0,914,917,917,914,11402,11403,11404,11405,43,11424,11415,11421,11425,0,917,913,913,917,11403,11394,11400,11404,43,11426,11425,11427,11428,0,914,917,917,914,11405,11404,11406,11407,43,11425,11421,11422,11427,0,917,913,913,917,11404,11400,11401,11406,43,11423,11429,11430,11424,0,914,914,917,917,11402,11408,11409,11403,43,11429,11431,11432,11430,0,914,914,917,917,11408,11410,11411,11409,43,11424,11430,11416,11415,0,917,917,913,913,11403,11409,11395,11394,43,11430,11432,11418,11416,0,917,917,913,913,11409,11411,11397,11395,43,11423,11433,11434,11429,0,914,915,915,914,11402,11412,11413,11408,43,11433,11435,11436,11434,0,915,916,916,915,11412,11414,11415,11413,43,11429,11434,11437,11431,0,914,915,915,914,11408,11413,11416,11410,43,11434,11436,11438,11437,0,915,916,916,915,11413,11415,11417,11416,43,11423,11426,11439,11433,0,914,914,915,915,11402,11405,11418,11412,43,11426,11428,11440,11439,0,914,914,915,915,11405,11407,11419,11418,43,11433,11439,11441,11435,0,915,915,916,916,11412,11418,11420,11414,43,11439,11440,11442,11441,0,915,915,916,916,11418,11419,11421,11420,43,11443,11444,11445,11446,0,953,953,922,922,11422,11423,11424,11425,43,11444,11447,11448,11445,0,953,953,922,922,11423,11426,11427,11424,43,11446,11445,11436,11435,0,922,922,916,916,11425,11424,11415,11414,43,11445,11448,11438,11436,0,922,922,916,916,11424,11427,11417,11415,43,11443,11449,11450,11444,0,953,939,939,953,11422,11428,11429,11423,43,11449,11451,11452,11450,0,939,328,328,939,11428,11430,11431,11429,43,11444,11450,11453,11447,0,953,939,939,953,11423,11429,11432,11426,43,11450,11452,11454,11453,0,939,328,328,939,11429,11431,11433,11432,43,11443,11455,11456,11449,0,953,953,939,939,11422,11434,11435,11428,43,11455,11457,11458,11456,0,953,953,939,939,11434,11436,11437,11435,43,11449,11456,11459,11451,0,939,939,328,328,11428,11435,11438,11430,43,11456,11458,11460,11459,0,939,939,328,328,11435,11437,11439,11438,43,11443,11446,11461,11455,0,953,922,922,953,11422,11425,11440,11434,43,11446,11435,11441,11461,0,922,916,916,922,11425,11414,11420,11440,43,11455,11461,11462,11457,0,953,922,922,953,11434,11440,11441,11436,43,11461,11441,11442,11462,0,922,916,916,922,11440,11420,11421,11441,43,11463,11464,11465,11466,0,929,929,12,12,11442,11443,11444,11445,43,11464,11467,11468,11465,0,929,929,12,12,11443,11446,11447,11444,43,11466,11465,11469,11470,0,12,12,12,12,11445,11444,11448,11449,43,11465,11468,11471,11469,0,12,12,12,12,11444,11447,11450,11448,43,11463,11472,11473,11464,0,929,928,928,929,11442,11451,11452,11443,43,11472,11382,11391,11473,0,928,931,931,928,11451,11361,11370,11452,43,11464,11473,11474,11467,0,929,928,928,929,11443,11452,11453,11446,43,11473,11391,11392,11474,0,928,931,931,928,11452,11370,11371,11453,43,11463,11475,11476,11472,0,929,929,928,928,11442,11454,11455,11451,43,11475,11477,11478,11476,0,929,929,928,928,11454,11456,11457,11455,43,11472,11476,11383,11382,0,928,928,931,931,11451,11455,11362,11361,43,11476,11478,11386,11383,0,928,928,931,931,11455,11457,11365,11362,43,11463,11466,11479,11475,0,929,12,12,929,11442,11445,11458,11454,43,11466,11470,11480,11479,0,12,12,12,12,11445,11449,11459,11458,43,11475,11479,11481,11477,0,929,12,12,929,11454,11458,11460,11456,43,11479,11480,11482,11481,0,12,12,12,12,11458,11459,11461,11460,43,11483,11484,11485,11486,0,12,12,12,12,11462,11463,11464,11465,43,11484,11487,11488,11485,0,12,12,12,12,11463,11466,11467,11464,43,11486,11485,11489,11490,0,12,12,12,12,11465,11464,11468,11469,43,11485,11488,11491,11489,0,12,12,12,12,11464,11467,11470,11468,43,11483,11492,11493,11484,0,12,12,12,12,11462,11471,11472,11463,43,11492,11494,11495,11493,0,12,12,12,12,11471,11473,11474,11472,43,11484,11493,11496,11487,0,12,12,12,12,11463,11472,11475,11466,43,11493,11495,11497,11496,0,12,12,12,12,11472,11474,11476,11475,43,11483,11498,11499,11492,0,12,12,12,12,11462,11477,11478,11471,43,11498,11500,11501,11499,0,12,12,12,12,11477,11479,11480,11478,43,11492,11499,11502,11494,0,12,12,12,12,11471,11478,11481,11473,43,11499,11501,11503,11502,0,12,12,12,12,11478,11480,11482,11481,43,11483,11486,11504,11498,0,12,12,12,12,11462,11465,11483,11477,43,11486,11490,11505,11504,0,12,12,12,12,11465,11469,11484,11483,43,11498,11504,11506,11500,0,12,12,12,12,11477,11483,11485,11479,43,11504,11505,11507,11506,0,12,12,12,12,11483,11484,11486,11485,43,11508,11509,11510,11511,0,932,932,933,933,11487,11488,11489,11490,43,11509,11512,11513,11510,0,932,932,933,933,11488,11491,11492,11489,43,11511,11510,11452,11451,0,933,933,328,328,11490,11489,11431,11430,43,11510,11513,11454,11452,0,933,933,328,328,11489,11492,11433,11431,43,11508,11514,11515,11509,0,932,12,12,932,11487,11493,11494,11488,43,11514,11487,11496,11515,0,12,12,12,12,11493,11466,11475,11494,43,11509,11515,11516,11512,0,932,12,12,932,11488,11494,11495,11491,43,11515,11496,11497,11516,0,12,12,12,12,11494,11475,11476,11495,43,11508,11517,11518,11514,0,932,932,12,12,11487,11496,11497,11493,43,11517,11519,11520,11518,0,932,932,12,12,11496,11498,11499,11497,43,11514,11518,11488,11487,0,12,12,12,12,11493,11497,11467,11466,43,11518,11520,11491,11488,0,12,12,12,12,11497,11499,11470,11467,43,11508,11511,11521,11517,0,932,933,933,932,11487,11490,11500,11496,43,11511,11451,11459,11521,0,933,328,328,933,11490,11430,11438,11500,43,11517,11521,11522,11519,0,932,933,933,932,11496,11500,11501,11498,43,11521,11459,11460,11522,0,933,328,328,933,11500,11438,11439,11501,43,11523,11524,11525,11526,0,12,12,12,12,11502,11503,11504,11505,43,11524,11527,11528,11525,0,12,12,12,12,11503,11506,11507,11504,43,11526,11525,11501,11500,0,12,12,12,12,11505,11504,11480,11479,43,11525,11528,11503,11501,0,12,12,12,12,11504,11507,11482,11480,43,11523,11529,11530,11524,0,12,12,12,12,11502,11508,11509,11503,43,11529,11470,11469,11530,0,12,12,12,12,11508,11449,11448,11509,43,11524,11530,11531,11527,0,12,12,12,12,11503,11509,11510,11506,43,11530,11469,11471,11531,0,12,12,12,12,11509,11448,11450,11510,43,11523,11532,11533,11529,0,12,12,12,12,11502,11511,11512,11508,43,11532,11534,11535,11533,0,12,12,12,12,11511,11513,11514,11512,43,11529,11533,11480,11470,0,12,12,12,12,11508,11512,11459,11449,43,11533,11535,11482,11480,0,12,12,12,12,11512,11514,11461,11459,43,11523,11526,11536,11532,0,12,12,12,12,11502,11505,11515,11511,43,11526,11500,11506,11536,0,12,12,12,12,11505,11479,11485,11515,43,11532,11536,11537,11534,0,12,12,12,12,11511,11515,11516,11513,43,11536,11506,11507,11537,0,12,12,12,12,11515,11485,11486,11516,43,11538,11539,11540,11541,0,2244,2247,2246,2245,11517,11518,11519,11520,43,11539,11542,11543,11540,0,2247,2249,2248,2246,11518,11521,11522,11519,43,11541,11540,11544,11545,0,2245,2246,2251,2250,11520,11519,11523,11524,43,11540,11543,11546,11544,0,2246,2248,2252,2251,11519,11522,11525,11523,43,11538,11547,11548,11539,0,2244,947,948,2247,11517,11526,11527,11518,43,11547,11549,11550,11548,0,947,947,948,948,11526,11528,11529,11527,43,11539,11548,11551,11542,0,2247,948,931,2249,11518,11527,11530,11521,43,11548,11550,11552,11551,0,948,948,931,931,11527,11529,11531,11530,43,11538,11553,11554,11547,0,2244,2253,67,947,11517,11532,11533,11526,43,11553,11555,11556,11554,0,2253,2254,6,67,11532,11534,11535,11533,43,11547,11554,11557,11549,0,947,67,67,947,11526,11533,11536,11528,43,11554,11556,11558,11557,0,67,6,6,67,11533,11535,11537,11536,43,11538,11541,11559,11553,0,2244,2245,2255,2253,11517,11520,11538,11532,43,11541,11545,11560,11559,0,2245,2250,2256,2255,11520,11524,11539,11538,43,11553,11559,11561,11555,0,2253,2255,2257,2254,11532,11538,11540,11534,43,11559,11560,11562,11561,0,2255,2256,2258,2257,11538,11539,11541,11540,43,11563,11564,11565,11566,0,2259,2262,2261,2260,11542,11543,11544,11545,43,11564,11567,11568,11565,0,2262,2264,2263,2261,11543,11546,11547,11544,43,11566,11565,11278,11277,0,2260,2261,2239,2240,11545,11544,11257,11256,43,11565,11568,11281,11278,0,2261,2263,2243,2239,11544,11547,11260,11257,43,11563,11569,11570,11564,0,2259,2266,2265,2262,11542,11548,11549,11543,43,11569,11555,11561,11570,0,2266,2268,2267,2265,11548,11534,11540,11549,43,11564,11570,11571,11567,0,2262,2265,2269,2264,11543,11549,11550,11546,43,11570,11561,11562,11571,0,2265,2267,2270,2269,11549,11540,11541,11550,43,11563,11572,11573,11569,0,2259,909,910,2266,11542,11551,11552,11548,43,11572,11574,11575,11573,0,909,909,910,910,11551,11553,11554,11552,43,11569,11573,11556,11555,0,2266,910,911,2268,11548,11552,11535,11534,43,11573,11575,11558,11556,0,910,910,911,911,11552,11554,11537,11535,43,11563,11566,11576,11572,0,2259,2260,912,909,11542,11545,11555,11551,43,11566,11277,11286,11576,0,2260,2240,913,912,11545,11256,11265,11555,43,11572,11576,11577,11574,0,909,912,912,909,11551,11555,11556,11553,43,11576,11286,11287,11577,0,912,913,913,912,11555,11265,11266,11556,43,11578,11579,11580,11581,0,2271,12,12,2272,11557,11558,11559,11560,43,11579,11365,11364,11580,0,12,12,12,12,11558,11344,11343,11559,43,11581,11580,11582,11583,0,2272,12,12,2273,11560,11559,11561,11562,43,11580,11364,11366,11582,0,12,12,12,12,11559,11343,11345,11561,43,11578,11584,11585,11579,0,2271,929,12,12,11557,11563,11564,11558,43,11584,11586,11587,11585,0,929,929,12,12,11563,11565,11566,11564,43,11579,11585,11375,11365,0,12,12,12,12,11558,11564,11354,11344,43,11585,11587,11377,11375,0,12,12,12,12,11564,11566,11356,11354,43,11578,11588,11589,11584,0,2271,2274,928,929,11557,11567,11568,11563,43,11588,11542,11551,11589,0,2274,2249,931,928,11567,11521,11530,11568,43,11584,11589,11590,11586,0,929,928,928,929,11563,11568,11569,11565,43,11589,11551,11552,11590,0,928,931,931,928,11568,11530,11531,11569,43,11578,11581,11591,11588,0,2271,2272,2275,2274,11557,11560,11570,11567,43,11581,11583,11592,11591,0,2272,2273,2276,2275,11560,11562,11571,11570,43,11588,11591,11543,11542,0,2274,2275,2248,2249,11567,11570,11522,11521,43,11591,11592,11546,11543,0,2275,2276,2252,2248,11570,11571,11525,11522,43,11593,11594,11595,11596,0,947,947,948,948,11572,11573,11574,11575,43,11594,11597,11598,11595,0,947,947,948,948,11573,11576,11577,11574,43,11596,11595,11599,11600,0,948,948,931,931,11575,11574,11578,11579,43,11595,11598,11601,11599,0,948,948,931,931,11574,11577,11580,11578,43,11593,11602,11603,11594,0,947,67,67,947,11572,11581,11582,11573,43,11602,11604,11605,11603,0,67,6,6,67,11581,11583,11584,11582,43,11594,11603,11606,11597,0,947,67,67,947,11573,11582,11585,11576,43,11603,11605,11607,11606,0,67,6,6,67,11582,11584,11586,11585,43,11593,11608,11609,11602,0,947,947,67,67,11572,11587,11588,11581,43,11608,11130,11136,11609,0,947,947,67,67,11587,11109,11115,11588,43,11602,11609,11610,11604,0,67,67,6,6,11581,11588,11589,11583,43,11609,11136,11137,11610,0,67,67,6,6,11588,11115,11116,11589,43,11593,11596,11611,11608,0,947,948,948,947,11572,11575,11590,11587,43,11596,11600,11612,11611,0,948,931,931,948,11575,11579,11591,11590,43,11608,11611,11131,11130,0,947,948,948,947,11587,11590,11110,11109,43,11611,11612,11133,11131,0,948,931,931,948,11590,11591,11112,11110,43,11613,11614,11615,11616,0,909,910,910,909,11592,11593,11594,11595,43,11614,11604,11610,11615,0,910,911,911,910,11593,11583,11589,11594,43,11616,11615,11147,11146,0,909,910,910,909,11595,11594,11126,11125,43,11615,11610,11137,11147,0,910,911,911,910,11594,11589,11116,11126,43,11613,11617,11618,11614,0,909,909,910,910,11592,11596,11597,11593,43,11617,11619,11620,11618,0,909,909,910,910,11596,11598,11599,11597,43,11614,11618,11605,11604,0,910,910,911,911,11593,11597,11584,11583,43,11618,11620,11607,11605,0,910,910,911,911,11597,11599,11586,11584,43,11613,11621,11622,11617,0,909,912,912,909,11592,11600,11601,11596,43,11621,11623,11624,11622,0,912,913,913,912,11600,11602,11603,11601,43,11617,11622,11625,11619,0,909,912,912,909,11596,11601,11604,11598,43,11622,11624,11626,11625,0,912,913,913,912,11601,11603,11605,11604,43,11613,11616,11627,11621,0,909,909,912,912,11592,11595,11606,11600,43,11616,11146,11152,11627,0,909,909,912,912,11595,11125,11131,11606,43,11621,11627,11628,11623,0,912,912,913,913,11600,11606,11607,11602,43,11627,11152,11153,11628,0,912,912,913,913,11606,11131,11132,11607,43,11629,11630,11631,11632,0,914,917,917,914,11608,11609,11610,11611,43,11630,11623,11628,11631,0,917,913,913,917,11609,11602,11607,11610,43,11632,11631,11167,11166,0,914,917,917,914,11611,11610,11146,11145,43,11631,11628,11153,11167,0,917,913,913,917,11610,11607,11132,11146,43,11629,11633,11634,11630,0,914,914,917,917,11608,11612,11613,11609,43,11633,11635,11636,11634,0,914,914,917,917,11612,11614,11615,11613,43,11630,11634,11624,11623,0,917,917,913,913,11609,11613,11603,11602,43,11634,11636,11626,11624,0,917,917,913,913,11613,11615,11605,11603,43,11629,11637,11638,11633,0,914,915,915,914,11608,11616,11617,11612,43,11637,11639,11640,11638,0,915,916,916,915,11616,11618,11619,11617,43,11633,11638,11641,11635,0,914,915,915,914,11612,11617,11620,11614,43,11638,11640,11642,11641,0,915,916,916,915,11617,11619,11621,11620,43,11629,11632,11643,11637,0,914,914,915,915,11608,11611,11622,11616,43,11632,11166,11172,11643,0,914,914,915,915,11611,11145,11151,11622,43,11637,11643,11644,11639,0,915,915,916,916,11616,11622,11623,11618,43,11643,11172,11173,11644,0,915,915,916,916,11622,11151,11152,11623,43,11645,11646,11647,11648,0,953,922,922,953,11624,11625,11626,11627,43,11646,11639,11644,11647,0,922,916,916,922,11625,11618,11623,11626,43,11648,11647,11187,11186,0,953,922,922,953,11627,11626,11166,11165,43,11647,11644,11173,11187,0,922,916,916,922,11626,11623,11152,11166,43,11645,11649,11650,11646,0,953,953,922,922,11624,11628,11629,11625,43,11649,11651,11652,11650,0,953,953,922,922,11628,11630,11631,11629,43,11646,11650,11640,11639,0,922,922,916,916,11625,11629,11619,11618,43,11650,11652,11642,11640,0,922,922,916,916,11629,11631,11621,11619,43,11645,11653,11654,11649,0,953,939,939,953,11624,11632,11633,11628,43,11653,11655,11656,11654,0,939,328,328,939,11632,11634,11635,11633,43,11649,11654,11657,11651,0,953,939,939,953,11628,11633,11636,11630,43,11654,11656,11658,11657,0,939,328,328,939,11633,11635,11637,11636,43,11645,11648,11659,11653,0,953,953,939,939,11624,11627,11638,11632,43,11648,11186,11192,11659,0,953,953,939,939,11627,11165,11171,11638,43,11653,11659,11660,11655,0,939,939,328,328,11632,11638,11639,11634,43,11659,11192,11193,11660,0,939,939,328,328,11638,11171,11172,11639,43,11661,11662,11663,11664,0,929,929,12,12,11640,11641,11642,11643,43,11662,11665,11666,11663,0,929,929,12,12,11641,11644,11645,11642,43,11664,11663,11667,11668,0,12,12,12,12,11643,11642,11646,11647,43,11663,11666,11669,11667,0,12,12,12,12,11642,11645,11648,11646,43,11661,11670,11671,11662,0,929,928,928,929,11640,11649,11650,11641,43,11670,11600,11599,11671,0,928,931,931,928,11649,11579,11578,11650,43,11662,11671,11672,11665,0,929,928,928,929,11641,11650,11651,11644,43,11671,11599,11601,11672,0,928,931,931,928,11650,11578,11580,11651,43,11661,11673,11674,11670,0,929,929,928,928,11640,11652,11653,11649,43,11673,11212,11217,11674,0,929,929,928,928,11652,11191,11196,11653,43,11670,11674,11612,11600,0,928,928,931,931,11649,11653,11591,11579,43,11674,11217,11133,11612,0,928,928,931,931,11653,11196,11112,11591,43,11661,11664,11675,11673,0,929,12,12,929,11640,11643,11654,11652,43,11664,11668,11676,11675,0,12,12,12,12,11643,11647,11655,11654,43,11673,11675,11213,11212,0,929,12,12,929,11652,11654,11192,11191,43,11675,11676,11215,11213,0,12,12,12,12,11654,11655,11194,11192,43,11677,11678,11679,11680,0,12,12,12,12,11656,11657,11658,11659,43,11678,11681,11682,11679,0,12,12,12,12,11657,11660,11661,11658,43,11680,11679,11683,11684,0,12,12,12,12,11659,11658,11662,11663,43,11679,11682,11685,11683,0,12,12,12,12,11658,11661,11664,11662,43,11677,11686,11687,11678,0,12,12,12,12,11656,11665,11666,11657,43,11686,11688,11689,11687,0,12,12,12,12,11665,11667,11668,11666,43,11678,11687,11690,11681,0,12,12,12,12,11657,11666,11669,11660,43,11687,11689,11691,11690,0,12,12,12,12,11666,11668,11670,11669,43,11677,11692,11693,11686,0,12,12,12,12,11656,11671,11672,11665,43,11692,11235,11241,11693,0,12,12,12,12,11671,11214,11220,11672,43,11686,11693,11694,11688,0,12,12,12,12,11665,11672,11673,11667,43,11693,11241,11242,11694,0,12,12,12,12,11672,11220,11221,11673,43,11677,11680,11695,11692,0,12,12,12,12,11656,11659,11674,11671,43,11680,11684,11696,11695,0,12,12,12,12,11659,11663,11675,11674,43,11692,11695,11236,11235,0,12,12,12,12,11671,11674,11215,11214,43,11695,11696,11238,11236,0,12,12,12,12,11674,11675,11217,11215,43,11697,11698,11699,11700,0,932,933,933,932,11676,11677,11678,11679,43,11698,11655,11660,11699,0,933,328,328,933,11677,11634,11639,11678,43,11700,11699,11252,11251,0,932,933,933,932,11679,11678,11231,11230,43,11699,11660,11193,11252,0,933,328,328,933,11678,11639,11172,11231,43,11697,11701,11702,11698,0,932,932,933,933,11676,11680,11681,11677,43,11701,11703,11704,11702,0,932,932,933,933,11680,11682,11683,11681,43,11698,11702,11656,11655,0,933,933,328,328,11677,11681,11635,11634,43,11702,11704,11658,11656,0,933,933,328,328,11681,11683,11637,11635,43,11697,11705,11706,11701,0,932,12,12,932,11676,11684,11685,11680,43,11705,11684,11683,11706,0,12,12,12,12,11684,11663,11662,11685,43,11701,11706,11707,11703,0,932,12,12,932,11680,11685,11686,11682,43,11706,11683,11685,11707,0,12,12,12,12,11685,11662,11664,11686,43,11697,11700,11708,11705,0,932,932,12,12,11676,11679,11687,11684,43,11700,11251,11255,11708,0,932,932,12,12,11679,11230,11234,11687,43,11705,11708,11696,11684,0,12,12,12,12,11684,11687,11675,11663,43,11708,11255,11238,11696,0,12,12,12,12,11687,11234,11217,11675,43,11709,11710,11711,11712,0,12,12,12,12,11688,11689,11690,11691,43,11710,11713,11714,11711,0,12,12,12,12,11689,11692,11693,11690,43,11712,11711,11689,11688,0,12,12,12,12,11691,11690,11668,11667,43,11711,11714,11691,11689,0,12,12,12,12,11690,11693,11670,11668,43,11709,11715,11716,11710,0,12,12,12,12,11688,11694,11695,11689,43,11715,11668,11667,11716,0,12,12,12,12,11694,11647,11646,11695,43,11710,11716,11717,11713,0,12,12,12,12,11689,11695,11696,11692,43,11716,11667,11669,11717,0,12,12,12,12,11695,11646,11648,11696,43,11709,11718,11719,11715,0,12,12,12,12,11688,11697,11698,11694,43,11718,11269,11272,11719,0,12,12,12,12,11697,11248,11251,11698,43,11715,11719,11676,11668,0,12,12,12,12,11694,11698,11655,11647,43,11719,11272,11215,11676,0,12,12,12,12,11698,11251,11194,11655,43,11709,11712,11720,11718,0,12,12,12,12,11688,11691,11699,11697,43,11712,11688,11694,11720,0,12,12,12,12,11691,11667,11673,11699,43,11718,11720,11270,11269,0,12,12,12,12,11697,11699,11249,11248,43,11720,11694,11242,11270,0,12,12,12,12,11699,11673,11221,11249,43,11721,11722,11723,11724,0,2277,947,948,948,11700,11701,11702,11703,43,11722,11117,11126,11723,0,947,947,948,948,11701,11096,11105,11702,43,11724,11723,11725,11726,0,948,948,931,931,11703,11702,11704,11705,43,11723,11126,11127,11725,0,948,948,931,931,11702,11105,11106,11704,43,11721,11727,11728,11722,0,2277,2278,67,947,11700,11706,11707,11701,43,11727,11729,11730,11728,0,2278,2279,6,67,11706,11708,11709,11707,43,11722,11728,11118,11117,0,947,67,67,947,11701,11707,11097,11096,43,11728,11730,11121,11118,0,67,6,6,67,11707,11709,11100,11097,43,11721,11731,11732,11727,0,2277,2281,2280,2278,11700,11710,11711,11706,43,11731,11733,11734,11732,0,2281,2283,2282,2280,11710,11712,11713,11711,43,11727,11732,11735,11729,0,2278,2280,2284,2279,11706,11711,11714,11708,43,11732,11734,11736,11735,0,2280,2282,2285,2284,11711,11713,11715,11714,43,11721,11724,11737,11731,0,2277,948,948,2281,11700,11703,11716,11710,43,11724,11726,11738,11737,0,948,931,931,948,11703,11705,11717,11716,43,11731,11737,11739,11733,0,2281,948,948,2283,11710,11716,11718,11712,43,11737,11738,11740,11739,0,948,931,931,948,11716,11717,11719,11718,43,11741,11742,11743,11744,0,2205,910,910,2206,11720,11721,11722,11723,43,11742,11729,11735,11743,0,910,911,911,910,11721,11708,11714,11722,43,11744,11743,11745,11746,0,2206,910,910,2286,11723,11722,11724,11725,43,11743,11735,11736,11745,0,910,911,911,910,11722,11714,11715,11724,43,11741,11747,11748,11742,0,2205,909,910,910,11720,11726,11727,11721,43,11747,11143,11142,11748,0,909,909,910,910,11726,11122,11121,11727,43,11742,11748,11730,11729,0,910,910,911,911,11721,11727,11709,11708,43,11748,11142,11121,11730,0,910,910,911,911,11727,11121,11100,11709,43,11741,11749,11750,11747,0,2205,2207,912,909,11720,11728,11729,11726,43,11749,11751,11752,11750,0,2207,2287,913,912,11728,11730,11731,11729,43,11747,11750,11155,11143,0,909,912,912,909,11726,11729,11134,11122,43,11750,11752,11157,11155,0,912,913,913,912,11729,11731,11136,11134,43,11741,11744,11753,11749,0,2205,2206,2288,2207,11720,11723,11732,11728,43,11744,11746,11754,11753,0,2206,2286,2289,2288,11723,11725,11733,11732,43,11749,11753,11755,11751,0,2207,2288,2290,2287,11728,11732,11734,11730,43,11753,11754,11756,11755,0,2288,2289,382,2290,11732,11733,11735,11734,43,11757,11758,11759,11760,0,2291,2222,2293,2292,11736,11737,11738,11739,43,11758,11751,11755,11759,0,2222,2287,2290,2293,11737,11730,11734,11738,43,11760,11759,11761,11762,0,2292,2293,2295,2294,11739,11738,11740,11741,43,11759,11755,11756,11761,0,2293,2290,382,2295,11738,11734,11735,11740,43,11757,11763,11764,11758,0,2291,914,917,2222,11736,11742,11743,11737,43,11763,11163,11162,11764,0,914,914,917,917,11742,11142,11141,11743,43,11758,11764,11752,11751,0,2222,917,913,2287,11737,11743,11731,11730,43,11764,11162,11157,11752,0,917,917,913,913,11743,11141,11136,11731,43,11757,11765,11766,11763,0,2291,915,915,914,11736,11744,11745,11742,43,11765,11767,11768,11766,0,915,916,916,915,11744,11746,11747,11745,43,11763,11766,11175,11163,0,914,915,915,914,11742,11745,11154,11142,43,11766,11768,11177,11175,0,915,916,916,915,11745,11747,11156,11154,43,11757,11760,11769,11765,0,2291,2292,915,915,11736,11739,11748,11744,43,11760,11762,11770,11769,0,2292,2294,915,915,11739,11741,11749,11748,43,11765,11769,11771,11767,0,915,915,916,916,11744,11748,11750,11746,43,11769,11770,11772,11771,0,915,915,916,916,11748,11749,11751,11750,43,11773,11774,11775,11776,0,953,922,922,953,11752,11753,11754,11755,43,11774,11767,11771,11775,0,922,916,916,922,11753,11746,11750,11754,43,11776,11775,11777,11778,0,953,922,922,953,11755,11754,11756,11757,43,11775,11771,11772,11777,0,922,916,916,922,11754,11750,11751,11756,43,11773,11779,11780,11774,0,953,953,922,922,11752,11758,11759,11753,43,11779,11183,11182,11780,0,953,953,922,922,11758,11162,11161,11759,43,11774,11780,11768,11767,0,922,922,916,916,11753,11759,11747,11746,43,11780,11182,11177,11768,0,922,922,916,916,11759,11161,11156,11747,43,11773,11781,11782,11779,0,953,939,939,953,11752,11760,11761,11758,43,11781,11783,11784,11782,0,939,328,328,939,11760,11762,11763,11761,43,11779,11782,11195,11183,0,953,939,939,953,11758,11761,11174,11162,43,11782,11784,11197,11195,0,939,328,328,939,11761,11763,11176,11174,43,11773,11776,11785,11781,0,953,953,939,939,11752,11755,11764,11760,43,11776,11778,11786,11785,0,953,953,939,939,11755,11757,11765,11764,43,11781,11785,11787,11783,0,939,939,328,328,11760,11764,11766,11762,43,11785,11786,11788,11787,0,939,939,328,328,11764,11765,11767,11766,43,11789,11790,11791,11792,0,929,929,12,12,11768,11769,11770,11771,43,11790,11202,11208,11791,0,929,929,12,12,11769,11181,11187,11770,43,11792,11791,11793,11794,0,12,12,12,12,11771,11770,11772,11773,43,11791,11208,11209,11793,0,12,12,12,12,11770,11187,11188,11772,43,11789,11795,11796,11790,0,929,928,928,929,11768,11774,11775,11769,43,11795,11726,11725,11796,0,928,931,931,928,11774,11705,11704,11775,43,11790,11796,11203,11202,0,929,928,928,929,11769,11775,11182,11181,43,11796,11725,11127,11203,0,928,931,931,928,11775,11704,11106,11182,43,11789,11797,11798,11795,0,929,929,928,928,11768,11776,11777,11774,43,11797,11799,11800,11798,0,929,929,928,928,11776,11778,11779,11777,43,11795,11798,11738,11726,0,928,928,931,931,11774,11777,11717,11705,43,11798,11800,11740,11738,0,928,928,931,931,11777,11779,11719,11717,43,11789,11792,11801,11797,0,929,12,12,929,11768,11771,11780,11776,43,11792,11794,11802,11801,0,12,12,12,12,11771,11773,11781,11780,43,11797,11801,11803,11799,0,929,12,12,929,11776,11780,11782,11778,43,11801,11802,11804,11803,0,12,12,12,12,11780,11781,11783,11782,43,11805,11806,11807,11808,0,12,12,12,12,11784,11785,11786,11787,43,11806,11809,11810,11807,0,12,12,12,12,11785,11788,11789,11786,43,11808,11807,11811,11812,0,12,12,12,12,11787,11786,11790,11791,43,11807,11810,11813,11811,0,12,12,12,12,11786,11789,11792,11790,43,11805,11814,11815,11806,0,12,12,12,12,11784,11793,11794,11785,43,11814,11222,11231,11815,0,12,12,12,12,11793,11201,11210,11794,43,11806,11815,11816,11809,0,12,12,12,12,11785,11794,11795,11788,43,11815,11231,11232,11816,0,12,12,12,12,11794,11210,11211,11795,43,11805,11817,11818,11814,0,12,12,12,12,11784,11796,11797,11793,43,11817,11819,11820,11818,0,12,12,12,12,11796,11798,11799,11797,43,11814,11818,11223,11222,0,12,12,12,12,11793,11797,11202,11201,43,11818,11820,11226,11223,0,12,12,12,12,11797,11799,11205,11202,43,11805,11808,11821,11817,0,12,12,12,12,11784,11787,11800,11796,43,11808,11812,11822,11821,0,12,12,12,12,11787,11791,11801,11800,43,11817,11821,11823,11819,0,12,12,12,12,11796,11800,11802,11798,43,11821,11822,11824,11823,0,12,12,12,12,11800,11801,11803,11802,43,11825,11826,11827,11828,0,932,932,933,933,11804,11805,11806,11807,43,11826,11248,11247,11827,0,932,932,933,933,11805,11227,11226,11806,43,11828,11827,11784,11783,0,933,933,328,328,11807,11806,11763,11762,43,11827,11247,11197,11784,0,933,933,328,328,11806,11226,11176,11763,43,11825,11829,11830,11826,0,932,12,12,932,11804,11808,11809,11805,43,11829,11809,11816,11830,0,12,12,12,12,11808,11788,11795,11809,43,11826,11830,11257,11248,0,932,12,12,932,11805,11809,11236,11227,43,11830,11816,11232,11257,0,12,12,12,12,11809,11795,11211,11236,43,11825,11831,11832,11829,0,932,932,12,12,11804,11810,11811,11808,43,11831,11833,11834,11832,0,932,932,12,12,11810,11812,11813,11811,43,11829,11832,11810,11809,0,12,12,12,12,11808,11811,11789,11788,43,11832,11834,11813,11810,0,12,12,12,12,11811,11813,11792,11789,43,11825,11828,11835,11831,0,932,933,933,932,11804,11807,11814,11810,43,11828,11783,11787,11835,0,933,328,328,933,11807,11762,11766,11814,43,11831,11835,11836,11833,0,932,933,933,932,11810,11814,11815,11812,43,11835,11787,11788,11836,0,933,328,328,933,11814,11766,11767,11815,43,11837,11838,11839,11840,0,12,12,12,12,11816,11817,11818,11819,43,11838,11262,11266,11839,0,12,12,12,12,11817,11241,11245,11818,43,11840,11839,11820,11819,0,12,12,12,12,11819,11818,11799,11798,43,11839,11266,11226,11820,0,12,12,12,12,11818,11245,11205,11799,43,11837,11841,11842,11838,0,12,12,12,12,11816,11820,11821,11817,43,11841,11794,11793,11842,0,12,12,12,12,11820,11773,11772,11821,43,11838,11842,11263,11262,0,12,12,12,12,11817,11821,11242,11241,43,11842,11793,11209,11263,0,12,12,12,12,11821,11772,11188,11242,43,11837,11843,11844,11841,0,12,12,12,12,11816,11822,11823,11820,43,11843,11845,11846,11844,0,12,12,12,12,11822,11824,11825,11823,43,11841,11844,11802,11794,0,12,12,12,12,11820,11823,11781,11773,43,11844,11846,11804,11802,0,12,12,12,12,11823,11825,11783,11781,43,11837,11840,11847,11843,0,12,12,12,12,11816,11819,11826,11822,43,11840,11819,11823,11847,0,12,12,12,12,11819,11798,11802,11826,43,11843,11847,11848,11845,0,12,12,12,12,11822,11826,11827,11824,43,11847,11823,11824,11848,0,12,12,12,12,11826,11802,11803,11827,43,11849,11850,11851,11852,0,2296,2299,2298,2297,11828,11829,11830,11831,43,11850,11853,11854,11851,0,2299,2301,2300,2298,11829,11832,11833,11830,43,11852,11851,11855,11856,0,2297,2298,2303,2302,11831,11830,11834,11835,43,11851,11854,11857,11855,0,2298,2300,2304,2303,11830,11833,11836,11834,43,11849,11858,11859,11850,0,2296,2306,2305,2299,11828,11837,11838,11829,43,11858,11860,11861,11859,0,2306,2308,2307,2305,11837,11839,11840,11838,43,11850,11859,11862,11853,0,2299,2305,2309,2301,11829,11838,11841,11832,43,11859,11861,11863,11862,0,2305,2307,2310,2309,11838,11840,11842,11841,43,11849,11864,11865,11858,0,2296,2312,2311,2306,11828,11843,11844,11837,43,11864,11866,11867,11865,0,2312,2314,2313,2311,11843,11845,11846,11844,43,11858,11865,11868,11860,0,2306,2311,2315,2308,11837,11844,11847,11839,43,11865,11867,11869,11868,0,2311,2313,2316,2315,11844,11846,11848,11847,43,11849,11852,11870,11864,0,2296,2297,2317,2312,11828,11831,11849,11843,43,11852,11856,11871,11870,0,2297,2302,2318,2317,11831,11835,11850,11849,43,11864,11870,11872,11866,0,2312,2317,2319,2314,11843,11849,11851,11845,43,11870,11871,11873,11872,0,2317,2318,2320,2319,11849,11850,11852,11851,43,11874,11875,11876,11877,0,2321,2324,2323,2322,11853,11854,11855,11856,43,11875,11878,11879,11876,0,2324,2326,2325,2323,11854,11857,11858,11855,43,11877,11876,11880,11881,0,2322,2323,2328,2327,11856,11855,11859,11860,43,11876,11879,11882,11880,0,2323,2325,2329,2328,11855,11858,11861,11859,43,11874,11883,11884,11875,0,2321,12,12,2324,11853,11862,11863,11854,43,11883,11885,11886,11884,0,12,12,12,12,11862,11864,11865,11863,43,11875,11884,11887,11878,0,2324,12,12,2326,11854,11863,11866,11857,43,11884,11886,11888,11887,0,12,12,12,12,11863,11865,11867,11866,43,11874,11889,11890,11883,0,2321,2330,12,12,11853,11868,11869,11862,43,11889,11891,11892,11890,0,2330,2331,12,12,11868,11870,11871,11869,43,11883,11890,11893,11885,0,12,12,12,12,11862,11869,11872,11864,43,11890,11892,11894,11893,0,12,12,12,12,11869,11871,11873,11872,43,11874,11877,11895,11889,0,2321,2322,2332,2330,11853,11856,11874,11868,43,11877,11881,11896,11895,0,2322,2327,2333,2332,11856,11860,11875,11874,43,11889,11895,11897,11891,0,2330,2332,2334,2331,11868,11874,11876,11870,43,11895,11896,11898,11897,0,2332,2333,2335,2334,11874,11875,11877,11876,43,11899,11900,11901,11902,0,2336,12,12,2337,11878,11879,11880,11881,43,11900,11885,11893,11901,0,12,12,12,12,11879,11864,11872,11880,43,11902,11901,11903,11904,0,2337,12,12,2338,11881,11880,11882,11883,43,11901,11893,11894,11903,0,12,12,12,12,11880,11872,11873,11882,43,11899,11905,11906,11900,0,2336,2339,12,12,11878,11884,11885,11879,43,11905,11907,11908,11906,0,2339,2340,12,12,11884,11886,11887,11885,43,11900,11906,11886,11885,0,12,12,12,12,11879,11885,11865,11864,43,11906,11908,11888,11886,0,12,12,12,12,11885,11887,11867,11865,43,11899,11909,11910,11905,0,2336,2342,2341,2339,11878,11888,11889,11884,43,11909,11911,11912,11910,0,2342,2344,2343,2341,11888,11890,11891,11889,43,11905,11910,11913,11907,0,2339,2341,2345,2340,11884,11889,11892,11886,43,11910,11912,11914,11913,0,2341,2343,2346,2345,11889,11891,11893,11892,43,11899,11902,11915,11909,0,2336,2337,2347,2342,11878,11881,11894,11888,43,11902,11904,11916,11915,0,2337,2338,2348,2347,11881,11883,11895,11894,43,11909,11915,11917,11911,0,2342,2347,2349,2344,11888,11894,11896,11890,43,11915,11916,11918,11917,0,2347,2348,2350,2349,11894,11895,11897,11896,43,11919,11920,11921,11922,0,2351,932,933,2352,11898,11899,11900,11901,43,11920,11348,11347,11921,0,932,932,933,933,11899,11327,11326,11900,43,11922,11921,11923,11924,0,2352,933,328,2353,11901,11900,11902,11903,43,11921,11347,11306,11923,0,933,933,328,328,11900,11326,11285,11902,43,11919,11925,11926,11920,0,2351,2354,12,932,11898,11904,11905,11899,43,11925,11927,11928,11926,0,2354,2355,12,12,11904,11906,11907,11905,43,11920,11926,11357,11348,0,932,12,12,932,11899,11905,11336,11327,43,11926,11928,11326,11357,0,12,12,12,12,11905,11907,11305,11336,43,11919,11929,11930,11925,0,2351,2357,2356,2354,11898,11908,11909,11904,43,11929,11931,11932,11930,0,2357,2359,2358,2356,11908,11910,11911,11909,43,11925,11930,11933,11927,0,2354,2356,2360,2355,11904,11909,11912,11906,43,11930,11932,11934,11933,0,2356,2358,2361,2360,11909,11911,11913,11912,43,11919,11922,11935,11929,0,2351,2352,2362,2357,11898,11901,11914,11908,43,11922,11924,11936,11935,0,2352,2353,2363,2362,11901,11903,11915,11914,43,11929,11935,11937,11931,0,2357,2362,2364,2359,11908,11914,11916,11910,43,11935,11936,11938,11937,0,2362,2363,2365,2364,11914,11915,11917,11916,43,11939,11940,11941,11942,0,2366,2369,2368,2367,11918,11919,11920,11921,43,11940,11943,11944,11941,0,2369,2371,2370,2368,11919,11922,11923,11920,43,11942,11941,11945,11946,0,2367,2368,2373,2372,11921,11920,11924,11925,43,11941,11944,11947,11945,0,2368,2370,2374,2373,11920,11923,11926,11924,43,11939,11948,11949,11940,0,2366,953,922,2369,11918,11927,11928,11919,43,11948,11302,11309,11949,0,953,953,922,922,11927,11281,11288,11928,43,11940,11949,11950,11943,0,2369,922,916,2371,11919,11928,11929,11922,43,11949,11309,11297,11950,0,922,922,916,916,11928,11288,11276,11929,43,11939,11951,11952,11948,0,2366,2375,939,953,11918,11930,11931,11927,43,11951,11924,11923,11952,0,2375,2353,328,939,11930,11903,11902,11931,43,11948,11952,11303,11302,0,953,939,939,953,11927,11931,11282,11281,43,11952,11923,11306,11303,0,939,328,328,939,11931,11902,11285,11282,43,11939,11942,11953,11951,0,2366,2367,2376,2375,11918,11921,11932,11930,43,11942,11946,11954,11953,0,2367,2372,2377,2376,11921,11925,11933,11932,43,11951,11953,11936,11924,0,2375,2376,2363,2353,11930,11932,11915,11903,43,11953,11954,11938,11936,0,2376,2377,2365,2363,11932,11933,11917,11915,43,11955,11956,11957,11958,0,2378,2381,2380,2379,11934,11935,11936,11937,43,11956,11959,11960,11957,0,2381,2383,2382,2380,11935,11938,11939,11936,43,11958,11957,11961,11962,0,2379,2380,2385,2384,11937,11936,11940,11941,43,11957,11960,11963,11961,0,2380,2382,2386,2385,11936,11939,11942,11940,43,11955,11964,11965,11956,0,2378,2388,2387,2381,11934,11943,11944,11935,43,11964,11280,11279,11965,0,2388,2241,2242,2387,11943,11259,11258,11944,43,11956,11965,11966,11959,0,2381,2387,2389,2383,11935,11944,11945,11938,43,11965,11279,11281,11966,0,2387,2242,2243,2389,11944,11258,11260,11945,43,11955,11967,11968,11964,0,2378,2390,915,2388,11934,11946,11947,11943,43,11967,11943,11950,11968,0,2390,2371,916,915,11946,11922,11929,11947,43,11964,11968,11295,11280,0,2388,915,915,2241,11943,11947,11274,11259,43,11968,11950,11297,11295,0,915,916,916,915,11947,11929,11276,11274,43,11955,11958,11969,11967,0,2378,2379,2391,2390,11934,11937,11948,11946,43,11958,11962,11970,11969,0,2379,2384,2392,2391,11937,11941,11949,11948,43,11967,11969,11944,11943,0,2390,2391,2370,2371,11946,11948,11923,11922,43,11969,11970,11947,11944,0,2391,2392,2374,2370,11948,11949,11926,11923,43,11971,11972,11973,11974,0,2393,2396,2395,2394,11950,11951,11952,11953,43,11972,11975,11976,11973,0,2396,2398,2397,2395,11951,11954,11955,11952,43,11974,11973,11861,11860,0,2394,2395,2307,2308,11953,11952,11840,11839,43,11973,11976,11863,11861,0,2395,2397,2310,2307,11952,11955,11842,11840,43,11971,11977,11978,11972,0,2393,2400,2399,2396,11950,11956,11957,11951,43,11977,11959,11966,11978,0,2400,2383,2389,2399,11956,11938,11945,11957,43,11972,11978,11979,11975,0,2396,2399,2401,2398,11951,11957,11958,11954,43,11978,11966,11281,11979,0,2399,2389,2243,2401,11957,11945,11260,11958,43,11971,11980,11981,11977,0,2393,2403,2402,2400,11950,11959,11960,11956,43,11980,11982,11983,11981,0,2403,2405,2404,2402,11959,11961,11962,11960,43,11977,11981,11960,11959,0,2400,2402,2382,2383,11956,11960,11939,11938,43,11981,11983,11963,11960,0,2402,2404,2386,2382,11960,11962,11942,11939,43,11971,11974,11984,11980,0,2393,2394,2406,2403,11950,11953,11963,11959,43,11974,11860,11868,11984,0,2394,2308,2315,2406,11953,11839,11847,11963,43,11980,11984,11985,11982,0,2403,2406,2407,2405,11959,11963,11964,11961,43,11984,11868,11869,11985,0,2406,2315,2316,2407,11963,11847,11848,11964,43,11986,11987,11988,11989,0,2408,2409,12,12,11965,11966,11967,11968,43,11987,11583,11582,11988,0,2409,2273,12,12,11966,11562,11561,11967,43,11989,11988,11990,11991,0,12,12,12,12,11968,11967,11969,11970,43,11988,11582,11366,11990,0,12,12,12,12,11967,11561,11345,11969,43,11986,11992,11993,11987,0,2408,2411,2410,2409,11965,11971,11972,11966,43,11992,11994,11995,11993,0,2411,2413,2412,2410,11971,11973,11974,11972,43,11987,11993,11592,11583,0,2409,2410,2276,2273,11966,11972,11571,11562,43,11993,11995,11546,11592,0,2410,2412,2252,2276,11972,11974,11525,11571,43,11986,11996,11997,11992,0,2408,2415,2414,2411,11965,11975,11976,11971,43,11996,11907,11913,11997,0,2415,2340,2345,2414,11975,11886,11892,11976,43,11992,11997,11998,11994,0,2411,2414,2416,2413,11971,11976,11977,11973,43,11997,11913,11914,11998,0,2414,2345,2346,2416,11976,11892,11893,11977,43,11986,11989,11999,11996,0,2408,12,12,2415,11965,11968,11978,11975,43,11989,11991,12000,11999,0,12,12,12,12,11968,11970,11979,11978,43,11996,11999,11908,11907,0,2415,12,12,2340,11975,11978,11887,11886,43,11999,12000,11888,11908,0,12,12,12,12,11978,11979,11867,11887,43,12001,12002,12003,12004,0,2417,2420,2419,2418,11980,11981,11982,11983,43,12002,12005,12006,12003,0,2420,2422,2421,2419,11981,11984,11985,11982,43,12004,12003,12007,12008,0,2418,2419,2424,2423,11983,11982,11986,11987,43,12003,12006,12009,12007,0,2419,2421,2425,2424,11982,11985,11988,11986,43,12001,12010,12011,12002,0,2417,12,12,2420,11980,11989,11990,11981,43,12010,11991,11990,12011,0,12,12,12,12,11989,11970,11969,11990,43,12002,12011,12012,12005,0,2420,12,12,2422,11981,11990,11991,11984,43,12011,11990,11366,12012,0,12,12,12,12,11990,11969,11345,11991,43,12001,12013,12014,12010,0,2417,2426,12,12,11980,11992,11993,11989,43,12013,11878,11887,12014,0,2426,2326,12,12,11992,11857,11866,11993,43,12010,12014,12000,11991,0,12,12,12,12,11989,11993,11979,11970,43,12014,11887,11888,12000,0,12,12,12,12,11993,11866,11867,11979,43,12001,12004,12015,12013,0,2417,2418,2427,2426,11980,11983,11994,11992,43,12004,12008,12016,12015,0,2418,2423,2428,2427,11983,11987,11995,11994,43,12013,12015,11879,11878,0,2426,2427,2325,2326,11992,11994,11858,11857,43,12015,12016,11882,11879,0,2427,2428,2329,2325,11994,11995,11861,11858,43,12017,12018,12019,12020,0,2429,12,12,2430,11996,11997,11998,11999,43,12018,11362,11369,12019,0,12,12,12,12,11997,11341,11348,11998,43,12020,12019,12021,12022,0,2430,12,12,2431,11999,11998,12000,12001,43,12019,11369,11342,12021,0,12,12,12,12,11998,11348,11321,12000,43,12017,12023,12024,12018,0,2429,2432,12,12,11996,12002,12003,11997,43,12023,12005,12012,12024,0,2432,2422,12,12,12002,11984,11991,12003,43,12018,12024,11363,11362,0,12,12,12,12,11997,12003,11342,11341,43,12024,12012,11366,11363,0,12,12,12,12,12003,11991,11345,11342,43,12017,12025,12026,12023,0,2429,2434,2433,2432,11996,12004,12005,12002,43,12025,12027,12028,12026,0,2434,2436,2435,2433,12004,12006,12007,12005,43,12023,12026,12006,12005,0,2432,2433,2421,2422,12002,12005,11985,11984,43,12026,12028,12009,12006,0,2433,2435,2425,2421,12005,12007,11988,11985,43,12017,12020,12029,12025,0,2429,2430,2437,2434,11996,11999,12008,12004,43,12020,12022,12030,12029,0,2430,2431,2438,2437,11999,12001,12009,12008,43,12025,12029,12031,12027,0,2434,2437,2439,2436,12004,12008,12010,12006,43,12029,12030,12032,12031,0,2437,2438,2440,2439,12008,12009,12011,12010,43,12033,12034,12035,12036,0,2441,2354,2356,2442,12012,12013,12014,12015,43,12034,11927,11933,12035,0,2354,2355,2360,2356,12013,11906,11912,12014,43,12036,12035,12037,12038,0,2442,2356,2358,2443,12015,12014,12016,12017,43,12035,11933,11934,12037,0,2356,2360,2361,2358,12014,11912,11913,12016,43,12033,12039,12040,12034,0,2441,12,12,2354,12012,12018,12019,12013,43,12039,11325,11324,12040,0,12,12,12,12,12018,11304,11303,12019,43,12034,12040,11928,11927,0,2354,12,12,2355,12013,12019,11907,11906,43,12040,11324,11326,11928,0,12,12,12,12,12019,11303,11305,11907,43,12033,12041,12042,12039,0,2441,2444,12,12,12012,12020,12021,12018,43,12041,12022,12021,12042,0,2444,2431,12,12,12020,12001,12000,12021,43,12039,12042,11340,11325,0,12,12,12,12,12018,12021,11319,11304,43,12042,12021,11342,11340,0,12,12,12,12,12021,12000,11321,11319,43,12033,12036,12043,12041,0,2441,2442,2445,2444,12012,12015,12022,12020,43,12036,12038,12044,12043,0,2442,2443,2446,2445,12015,12017,12023,12022,43,12041,12043,12030,12022,0,2444,2445,2438,2431,12020,12022,12009,12001,43,12043,12044,12032,12030,0,2445,2446,2440,2438,12022,12023,12011,12009,43,12045,12046,12047,12048,0,947,948,948,947,12024,12025,12026,12027,43,12046,12049,12050,12047,0,948,931,931,948,12025,12028,12029,12026,43,12048,12047,11550,11549,0,947,948,948,947,12027,12026,11529,11528,43,12047,12050,11552,11550,0,948,931,931,948,12026,12029,11531,11529,43,12045,12051,12052,12046,0,947,947,948,948,12024,12030,12031,12025,43,12051,11385,11384,12052,0,947,947,948,948,12030,11364,11363,12031,43,12046,12052,12053,12049,0,948,948,931,931,12025,12031,12032,12028,43,12052,11384,11386,12053,0,948,948,931,931,12031,11363,11365,12032,43,12045,12054,12055,12051,0,947,67,67,947,12024,12033,12034,12030,43,12054,12056,12057,12055,0,67,6,6,67,12033,12035,12036,12034,43,12051,12055,11400,11385,0,947,67,67,947,12030,12034,11379,11364,43,12055,12057,11402,11400,0,67,6,6,67,12034,12036,11381,11379,43,12045,12048,12058,12054,0,947,947,67,67,12024,12027,12037,12033,43,12048,11549,11557,12058,0,947,947,67,67,12027,11528,11536,12037,43,12054,12058,12059,12056,0,67,67,6,6,12033,12037,12038,12035,43,12058,11557,11558,12059,0,67,67,6,6,12037,11536,11537,12038,43,12060,12061,12062,12063,0,2277,2278,2280,2281,12039,12040,12041,12042,43,12061,12064,12065,12062,0,2278,2279,2284,2280,12040,12043,12044,12041,43,12063,12062,12066,12067,0,2281,2280,2282,2283,12042,12041,12045,12046,43,12062,12065,12068,12066,0,2280,2284,2285,2282,12041,12044,12047,12045,43,12060,12069,12070,12061,0,2277,947,67,2278,12039,12048,12049,12040,43,12069,11389,11397,12070,0,947,947,67,67,12048,11368,11376,12049,43,12061,12070,12071,12064,0,2278,67,6,2279,12040,12049,12050,12043,43,12070,11397,11398,12071,0,67,67,6,6,12049,11376,11377,12050,43,12060,12072,12073,12069,0,2277,948,948,947,12039,12051,12052,12048,43,12072,12074,12075,12073,0,948,931,931,948,12051,12053,12054,12052,43,12069,12073,11390,11389,0,947,948,948,947,12048,12052,11369,11368,43,12073,12075,11392,11390,0,948,931,931,948,12052,12054,11371,11369,43,12060,12063,12076,12072,0,2277,2281,948,948,12039,12042,12055,12051,43,12063,12067,12077,12076,0,2281,2283,948,948,12042,12046,12056,12055,43,12072,12076,12078,12074,0,948,948,931,931,12051,12055,12057,12053,43,12076,12077,12079,12078,0,948,948,931,931,12055,12056,12058,12057,43,12080,12081,12082,12083,0,909,910,910,909,12059,12060,12061,12062,43,12081,12056,12059,12082,0,910,911,911,910,12060,12035,12038,12061,43,12083,12082,11575,11574,0,909,910,910,909,12062,12061,11554,11553,43,12082,12059,11558,11575,0,910,911,911,910,12061,12038,11537,11554,43,12080,12084,12085,12081,0,909,909,910,910,12059,12063,12064,12060,43,12084,11408,11407,12085,0,909,909,910,910,12063,11387,11386,12064,43,12081,12085,12057,12056,0,910,910,911,911,12060,12064,12036,12035,43,12085,11407,11402,12057,0,910,910,911,911,12064,11386,11381,12036,43,12080,12086,12087,12084,0,909,912,912,909,12059,12065,12066,12063,43,12086,12088,12089,12087,0,912,913,913,912,12065,12067,12068,12066,43,12084,12087,11420,11408,0,909,912,912,909,12063,12066,11399,11387,43,12087,12089,11422,11420,0,912,913,913,912,12066,12068,11401,11399,43,12080,12083,12090,12086,0,909,909,912,912,12059,12062,12069,12065,43,12083,11574,11577,12090,0,909,909,912,912,12062,11553,11556,12069,43,12086,12090,12091,12088,0,912,912,913,913,12065,12069,12070,12067,43,12090,11577,11287,12091,0,912,912,913,913,12069,11556,11266,12070,43,12092,12093,12094,12095,0,2205,2207,2288,2206,12071,12072,12073,12074,43,12093,12096,12097,12094,0,2207,2287,2290,2288,12072,12075,12076,12073,43,12095,12094,12098,12099,0,2206,2288,2289,2286,12074,12073,12077,12078,43,12094,12097,12100,12098,0,2288,2290,382,2289,12073,12076,12079,12077,43,12092,12101,12102,12093,0,2205,909,912,2207,12071,12080,12081,12072,43,12101,11411,11417,12102,0,909,909,912,912,12080,11390,11396,12081,43,12093,12102,12103,12096,0,2207,912,913,2287,12072,12081,12082,12075,43,12102,11417,11418,12103,0,912,912,913,913,12081,11396,11397,12082,43,12092,12104,12105,12101,0,2205,910,910,909,12071,12083,12084,12080,43,12104,12064,12071,12105,0,910,911,911,910,12083,12043,12050,12084,43,12101,12105,11412,11411,0,909,910,910,909,12080,12084,11391,11390,43,12105,12071,11398,11412,0,910,911,911,910,12084,12050,11377,11391,43,12092,12095,12106,12104,0,2205,2206,910,910,12071,12074,12085,12083,43,12095,12099,12107,12106,0,2206,2286,910,910,12074,12078,12086,12085,43,12104,12106,12065,12064,0,910,910,911,911,12083,12085,12044,12043,43,12106,12107,12068,12065,0,910,910,911,911,12085,12086,12047,12044,43,12108,12109,12110,12111,0,914,917,917,914,12087,12088,12089,12090,43,12109,12088,12091,12110,0,917,913,913,917,12088,12067,12070,12089,43,12111,12110,11285,11284,0,914,917,917,914,12090,12089,11264,11263,43,12110,12091,11287,11285,0,917,913,913,917,12089,12070,11266,11264,43,12108,12112,12113,12109,0,914,914,917,917,12087,12091,12092,12088,43,12112,11428,11427,12113,0,914,914,917,917,12091,11407,11406,12092,43,12109,12113,12089,12088,0,917,917,913,913,12088,12092,12068,12067,43,12113,11427,11422,12089,0,917,917,913,913,12092,11406,11401,12068,43,12108,12114,12115,12112,0,914,915,915,914,12087,12093,12094,12091,43,12114,12116,12117,12115,0,915,916,916,915,12093,12095,12096,12094,43,12112,12115,11440,11428,0,914,915,915,914,12091,12094,11419,11407,43,12115,12117,11442,11440,0,915,916,916,915,12094,12096,11421,11419,43,12108,12111,12118,12114,0,914,914,915,915,12087,12090,12097,12093,43,12111,11284,11292,12118,0,914,914,915,915,12090,11263,11271,12097,43,12114,12118,12119,12116,0,915,915,916,916,12093,12097,12098,12095,43,12118,11292,11293,12119,0,915,915,916,916,12097,11271,11272,12098,43,12120,12121,12122,12123,0,2291,914,915,915,12099,12100,12101,12102,43,12121,11431,11437,12122,0,914,914,915,915,12100,11410,11416,12101,43,12123,12122,12124,12125,0,915,915,916,916,12102,12101,12103,12104,43,12122,11437,11438,12124,0,915,915,916,916,12101,11416,11417,12103,43,12120,12126,12127,12121,0,2291,2222,917,914,12099,12105,12106,12100,43,12126,12096,12103,12127,0,2222,2287,913,917,12105,12075,12082,12106,43,12121,12127,11432,11431,0,914,917,917,914,12100,12106,11411,11410,43,12127,12103,11418,11432,0,917,913,913,917,12106,12082,11397,11411,43,12120,12128,12129,12126,0,2291,2292,2293,2222,12099,12107,12108,12105,43,12128,12130,12131,12129,0,2292,2294,2295,2293,12107,12109,12110,12108,43,12126,12129,12097,12096,0,2222,2293,2290,2287,12105,12108,12076,12075,43,12129,12131,12100,12097,0,2293,2295,382,2290,12108,12110,12079,12076,43,12120,12123,12132,12128,0,2291,915,915,2292,12099,12102,12111,12107,43,12123,12125,12133,12132,0,915,916,916,915,12102,12104,12112,12111,43,12128,12132,12134,12130,0,2292,915,915,2294,12107,12111,12113,12109,43,12132,12133,12135,12134,0,915,916,916,915,12111,12112,12114,12113,43,12136,12137,12138,12139,0,953,953,922,922,12115,12116,12117,12118,43,12137,11457,11462,12138,0,953,953,922,922,12116,11436,11441,12117,43,12139,12138,12117,12116,0,922,922,916,916,12118,12117,12096,12095,43,12138,11462,11442,12117,0,922,922,916,916,12117,11441,11421,12096,43,12136,12140,12141,12137,0,953,939,939,953,12115,12119,12120,12116,43,12140,12142,12143,12141,0,939,328,328,939,12119,12121,12122,12120,43,12137,12141,11458,11457,0,953,939,939,953,12116,12120,11437,11436,43,12141,12143,11460,11458,0,939,328,328,939,12120,12122,11439,11437,43,12136,12144,12145,12140,0,953,953,939,939,12115,12123,12124,12119,43,12144,11312,11316,12145,0,953,953,939,939,12123,11291,11295,12124,43,12140,12145,12146,12142,0,939,939,328,328,12119,12124,12125,12121,43,12145,11316,11317,12146,0,939,939,328,328,12124,11295,11296,12125,43,12136,12139,12147,12144,0,953,922,922,953,12115,12118,12126,12123,43,12139,12116,12119,12147,0,922,916,916,922,12118,12095,12098,12126,43,12144,12147,11313,11312,0,953,922,922,953,12123,12126,11292,11291,43,12147,12119,11293,11313,0,922,916,916,922,12126,12098,11272,11292,43,12148,12149,12150,12151,0,953,953,939,939,12127,12128,12129,12130,43,12149,11447,11453,12150,0,953,953,939,939,12128,11426,11432,12129,43,12151,12150,12152,12153,0,939,939,328,328,12130,12129,12131,12132,43,12150,11453,11454,12152,0,939,939,328,328,12129,11432,11433,12131,43,12148,12154,12155,12149,0,953,922,922,953,12127,12133,12134,12128,43,12154,12125,12124,12155,0,922,916,916,922,12133,12104,12103,12134,43,12149,12155,11448,11447,0,953,922,922,953,12128,12134,11427,11426,43,12155,12124,11438,11448,0,922,916,916,922,12134,12103,11417,11427,43,12148,12156,12157,12154,0,953,953,922,922,12127,12135,12136,12133,43,12156,12158,12159,12157,0,953,953,922,922,12135,12137,12138,12136,43,12154,12157,12133,12125,0,922,922,916,916,12133,12136,12112,12104,43,12157,12159,12135,12133,0,922,922,916,916,12136,12138,12114,12112,43,12148,12151,12160,12156,0,953,939,939,953,12127,12130,12139,12135,43,12151,12153,12161,12160,0,939,328,328,939,12130,12132,12140,12139,43,12156,12160,12162,12158,0,953,939,939,953,12135,12139,12141,12137,43,12160,12161,12163,12162,0,939,328,328,939,12139,12140,12142,12141,43,12164,12165,12166,12167,0,929,929,12,12,12143,12144,12145,12146,43,12165,11477,11481,12166,0,929,929,12,12,12144,11456,11460,12145,43,12167,12166,12168,12169,0,12,12,12,12,12146,12145,12147,12148,43,12166,11481,11482,12168,0,12,12,12,12,12145,11460,11461,12147,43,12164,12170,12171,12165,0,929,928,928,929,12143,12149,12150,12144,43,12170,12049,12053,12171,0,928,931,931,928,12149,12028,12032,12150,43,12165,12171,11478,11477,0,929,928,928,929,12144,12150,11457,11456,43,12171,12053,11386,11478,0,928,931,931,928,12150,12032,11365,11457,43,12164,12172,12173,12170,0,929,929,928,928,12143,12151,12152,12149,43,12172,11586,11590,12173,0,929,929,928,928,12151,11565,11569,12152,43,12170,12173,12050,12049,0,928,928,931,931,12149,12152,12029,12028,43,12173,11590,11552,12050,0,928,928,931,931,12152,11569,11531,12029,43,12164,12167,12174,12172,0,929,12,12,929,12143,12146,12153,12151,43,12167,12169,12175,12174,0,12,12,12,12,12146,12148,12154,12153,43,12172,12174,11587,11586,0,929,12,12,929,12151,12153,11566,11565,43,12174,12175,11377,11587,0,12,12,12,12,12153,12154,11356,11566,43,12176,12177,12178,12179,0,929,929,928,928,12155,12156,12157,12158,43,12177,11467,11474,12178,0,929,929,928,928,12156,11446,11453,12157,43,12179,12178,12075,12074,0,928,928,931,931,12158,12157,12054,12053,43,12178,11474,11392,12075,0,928,928,931,931,12157,11453,11371,12054,43,12176,12180,12181,12177,0,929,12,12,929,12155,12159,12160,12156,43,12180,12182,12183,12181,0,12,12,12,12,12159,12161,12162,12160,43,12177,12181,11468,11467,0,929,12,12,929,12156,12160,11447,11446,43,12181,12183,11471,11468,0,12,12,12,12,12160,12162,11450,11447,43,12176,12184,12185,12180,0,929,929,12,12,12155,12163,12164,12159,43,12184,12186,12187,12185,0,929,929,12,12,12163,12165,12166,12164,43,12180,12185,12188,12182,0,12,12,12,12,12159,12164,12167,12161,43,12185,12187,12189,12188,0,12,12,12,12,12164,12166,12168,12167,43,12176,12179,12190,12184,0,929,928,928,929,12155,12158,12169,12163,43,12179,12074,12078,12190,0,928,931,931,928,12158,12053,12057,12169,43,12184,12190,12191,12186,0,929,928,928,929,12163,12169,12170,12165,43,12190,12078,12079,12191,0,928,931,931,928,12169,12057,12058,12170,43,12192,12193,12194,12195,0,12,12,12,12,12171,12172,12173,12174,43,12193,12196,12197,12194,0,12,12,12,12,12172,12175,12176,12173,43,12195,12194,11330,11329,0,12,12,12,12,12174,12173,11309,11308,43,12194,12197,11332,11330,0,12,12,12,12,12173,12176,11311,11309,43,12192,12198,12199,12193,0,12,12,12,12,12171,12177,12178,12172,43,12198,11490,11489,12199,0,12,12,12,12,12177,11469,11468,12178,43,12193,12199,12200,12196,0,12,12,12,12,12172,12178,12179,12175,43,12199,11489,11491,12200,0,12,12,12,12,12178,11468,11470,12179,43,12192,12201,12202,12198,0,12,12,12,12,12171,12180,12181,12177,43,12201,12203,12204,12202,0,12,12,12,12,12180,12182,12183,12181,43,12198,12202,11505,11490,0,12,12,12,12,12177,12181,11484,11469,43,12202,12204,11507,11505,0,12,12,12,12,12181,12183,11486,11484,43,12192,12195,12205,12201,0,12,12,12,12,12171,12174,12184,12180,43,12195,11329,11337,12205,0,12,12,12,12,12174,11308,11316,12184,43,12201,12205,12206,12203,0,12,12,12,12,12180,12184,12185,12182,43,12205,11337,11338,12206,0,12,12,12,12,12184,11316,11317,12185,43,12207,12208,12209,12210,0,12,12,12,12,12186,12187,12188,12189,43,12208,12211,12212,12209,0,12,12,12,12,12187,12190,12191,12188,43,12210,12209,12213,12214,0,12,12,12,12,12189,12188,12192,12193,43,12209,12212,12215,12213,0,12,12,12,12,12188,12191,12194,12192,43,12207,12216,12217,12208,0,12,12,12,12,12186,12195,12196,12187,43,12216,11494,11502,12217,0,12,12,12,12,12195,11473,11481,12196,43,12208,12217,12218,12211,0,12,12,12,12,12187,12196,12197,12190,43,12217,11502,11503,12218,0,12,12,12,12,12196,11481,11482,12197,43,12207,12219,12220,12216,0,12,12,12,12,12186,12198,12199,12195,43,12219,12221,12222,12220,0,12,12,12,12,12198,12200,12201,12199,43,12216,12220,11495,11494,0,12,12,12,12,12195,12199,11474,11473,43,12220,12222,11497,11495,0,12,12,12,12,12199,12201,11476,11474,43,12207,12210,12223,12219,0,12,12,12,12,12186,12189,12202,12198,43,12210,12214,12224,12223,0,12,12,12,12,12189,12193,12203,12202,43,12219,12223,12225,12221,0,12,12,12,12,12198,12202,12204,12200,43,12223,12224,12226,12225,0,12,12,12,12,12202,12203,12205,12204,43,12227,12228,12229,12230,0,932,932,933,933,12206,12207,12208,12209,43,12228,11519,11522,12229,0,932,932,933,933,12207,11498,11501,12208,43,12230,12229,12143,12142,0,933,933,328,328,12209,12208,12122,12121,43,12229,11522,11460,12143,0,933,933,328,328,12208,11501,11439,12122,43,12227,12231,12232,12228,0,932,12,12,932,12206,12210,12211,12207,43,12231,12196,12200,12232,0,12,12,12,12,12210,12175,12179,12211,43,12228,12232,11520,11519,0,932,12,12,932,12207,12211,11499,11498,43,12232,12200,11491,11520,0,12,12,12,12,12211,12179,11470,11499,43,12227,12233,12234,12231,0,932,932,12,12,12206,12212,12213,12210,43,12233,11351,11355,12234,0,932,932,12,12,12212,11330,11334,12213,43,12231,12234,12197,12196,0,12,12,12,12,12210,12213,12176,12175,43,12234,11355,11332,12197,0,12,12,12,12,12213,11334,11311,12176,43,12227,12230,12235,12233,0,932,933,933,932,12206,12209,12214,12212,43,12230,12142,12146,12235,0,933,328,328,933,12209,12121,12125,12214,43,12233,12235,11352,11351,0,932,933,933,932,12212,12214,11331,11330,43,12235,12146,11317,11352,0,933,328,328,933,12214,12125,11296,11331,43,12236,12237,12238,12239,0,932,932,12,12,12215,12216,12217,12218,43,12237,11512,11516,12238,0,932,932,12,12,12216,11491,11495,12217,43,12239,12238,12222,12221,0,12,12,12,12,12218,12217,12201,12200,43,12238,11516,11497,12222,0,12,12,12,12,12217,11495,11476,12201,43,12236,12240,12241,12237,0,932,933,933,932,12215,12219,12220,12216,43,12240,12153,12152,12241,0,933,328,328,933,12219,12132,12131,12220,43,12237,12241,11513,11512,0,932,933,933,932,12216,12220,11492,11491,43,12241,12152,11454,11513,0,933,328,328,933,12220,12131,11433,11492,43,12236,12242,12243,12240,0,932,932,933,933,12215,12221,12222,12219,43,12242,12244,12245,12243,0,932,932,933,933,12221,12223,12224,12222,43,12240,12243,12161,12153,0,933,933,328,328,12219,12222,12140,12132,43,12243,12245,12163,12161,0,933,933,328,328,12222,12224,12142,12140,43,12236,12239,12246,12242,0,932,12,12,932,12215,12218,12225,12221,43,12239,12221,12225,12246,0,12,12,12,12,12218,12200,12204,12225,43,12242,12246,12247,12244,0,932,12,12,932,12221,12225,12226,12223,43,12246,12225,12226,12247,0,12,12,12,12,12225,12204,12205,12226,43,12248,12249,12250,12251,0,12,12,12,12,12227,12228,12229,12230,43,12249,11534,11537,12250,0,12,12,12,12,12228,11513,11516,12229,43,12251,12250,12204,12203,0,12,12,12,12,12230,12229,12183,12182,43,12250,11537,11507,12204,0,12,12,12,12,12229,11516,11486,12183,43,12248,12252,12253,12249,0,12,12,12,12,12227,12231,12232,12228,43,12252,12169,12168,12253,0,12,12,12,12,12231,12148,12147,12232,43,12249,12253,11535,11534,0,12,12,12,12,12228,12232,11514,11513,43,12253,12168,11482,11535,0,12,12,12,12,12232,12147,11461,11514,43,12248,12254,12255,12252,0,12,12,12,12,12227,12233,12234,12231,43,12254,11372,11376,12255,0,12,12,12,12,12233,11351,11355,12234,43,12252,12255,12175,12169,0,12,12,12,12,12231,12234,12154,12148,43,12255,11376,11377,12175,0,12,12,12,12,12234,11355,11356,12154,43,12248,12251,12256,12254,0,12,12,12,12,12227,12230,12235,12233,43,12251,12203,12206,12256,0,12,12,12,12,12230,12182,12185,12235,43,12254,12256,11373,11372,0,12,12,12,12,12233,12235,11352,11351,43,12256,12206,11338,11373,0,12,12,12,12,12235,12185,11317,11352,43,12257,12258,12259,12260,0,12,12,12,12,12236,12237,12238,12239,43,12258,12182,12188,12259,0,12,12,12,12,12237,12161,12167,12238,43,12260,12259,12261,12262,0,12,12,12,12,12239,12238,12240,12241,43,12259,12188,12189,12261,0,12,12,12,12,12238,12167,12168,12240,43,12257,12263,12264,12258,0,12,12,12,12,12236,12242,12243,12237,43,12263,11527,11531,12264,0,12,12,12,12,12242,11506,11510,12243,43,12258,12264,12183,12182,0,12,12,12,12,12237,12243,12162,12161,43,12264,11531,11471,12183,0,12,12,12,12,12243,11510,11450,12162,43,12257,12265,12266,12263,0,12,12,12,12,12236,12244,12245,12242,43,12265,12211,12218,12266,0,12,12,12,12,12244,12190,12197,12245,43,12263,12266,11528,11527,0,12,12,12,12,12242,12245,11507,11506,43,12266,12218,11503,11528,0,12,12,12,12,12245,12197,11482,11507,43,12257,12260,12267,12265,0,12,12,12,12,12236,12239,12246,12244,43,12260,12262,12268,12267,0,12,12,12,12,12239,12241,12247,12246,43,12265,12267,12212,12211,0,12,12,12,12,12244,12246,12191,12190,43,12267,12268,12215,12212,0,12,12,12,12,12246,12247,12194,12191,43,12269,12270,12271,12272,0,947,947,948,948,12248,12249,12250,12251,43,12270,12273,12274,12271,0,947,947,948,948,12249,12252,12253,12250,43,12272,12271,12275,12276,0,948,948,931,931,12251,12250,12254,12255,43,12271,12274,12277,12275,0,948,948,931,931,12250,12253,12256,12254,43,12269,12278,12279,12270,0,947,67,67,947,12248,12257,12258,12249,43,12278,12280,12281,12279,0,67,6,6,67,12257,12259,12260,12258,43,12270,12279,12282,12273,0,947,67,67,947,12249,12258,12261,12252,43,12279,12281,12283,12282,0,67,6,6,67,12258,12260,12262,12261,43,12269,12284,12285,12278,0,947,947,67,67,12248,12263,12264,12257,43,12284,11597,11606,12285,0,947,947,67,67,12263,11576,11585,12264,43,12278,12285,12286,12280,0,67,67,6,6,12257,12264,12265,12259,43,12285,11606,11607,12286,0,67,67,6,6,12264,11585,11586,12265,43,12269,12272,12287,12284,0,947,948,948,947,12248,12251,12266,12263,43,12272,12276,12288,12287,0,948,931,931,948,12251,12255,12267,12266,43,12284,12287,11598,11597,0,947,948,948,947,12263,12266,11577,11576,43,12287,12288,11601,11598,0,948,931,931,948,12266,12267,11580,11577,43,12289,12290,12291,12292,0,947,947,67,67,12268,12269,12270,12271,43,12290,12273,12282,12291,0,947,947,67,67,12269,12252,12261,12270,43,12292,12291,12293,12294,0,67,67,6,6,12271,12270,12272,12273,43,12291,12282,12283,12293,0,67,67,6,6,12270,12261,12262,12272,43,12289,12295,12296,12290,0,947,948,948,947,12268,12274,12275,12269,43,12295,12297,12298,12296,0,948,931,931,948,12274,12276,12277,12275,43,12290,12296,12274,12273,0,947,948,948,947,12269,12275,12253,12252,43,12296,12298,12277,12274,0,948,931,931,948,12275,12277,12256,12253,43,12289,12299,12300,12295,0,947,947,948,948,12268,12278,12279,12274,43,12299,12301,12302,12300,0,947,947,948,948,12278,12280,12281,12279,43,12295,12300,12303,12297,0,948,948,931,931,12274,12279,12282,12276,43,12300,12302,12304,12303,0,948,948,931,931,12279,12281,12283,12282,43,12289,12292,12305,12299,0,947,67,67,947,12268,12271,12284,12278,43,12292,12294,12306,12305,0,67,6,6,67,12271,12273,12285,12284,43,12299,12305,12307,12301,0,947,67,67,947,12278,12284,12286,12280,43,12305,12306,12308,12307,0,67,6,6,67,12284,12285,12287,12286,43,12309,12310,12311,12312,0,909,910,910,909,12288,12289,12290,12291,43,12310,12280,12286,12311,0,910,911,911,910,12289,12259,12265,12290,43,12312,12311,11620,11619,0,909,910,910,909,12291,12290,11599,11598,43,12311,12286,11607,11620,0,910,911,911,910,12290,12265,11586,11599,43,12309,12313,12314,12310,0,909,909,910,910,12288,12292,12293,12289,43,12313,12315,12316,12314,0,909,909,910,910,12292,12294,12295,12293,43,12310,12314,12281,12280,0,910,910,911,911,12289,12293,12260,12259,43,12314,12316,12283,12281,0,910,910,911,911,12293,12295,12262,12260,43,12309,12317,12318,12313,0,909,912,912,909,12288,12296,12297,12292,43,12317,12319,12320,12318,0,912,913,913,912,12296,12298,12299,12297,43,12313,12318,12321,12315,0,909,912,912,909,12292,12297,12300,12294,43,12318,12320,12322,12321,0,912,913,913,912,12297,12299,12301,12300,43,12309,12312,12323,12317,0,909,909,912,912,12288,12291,12302,12296,43,12312,11619,11625,12323,0,909,909,912,912,12291,11598,11604,12302,43,12317,12323,12324,12319,0,912,912,913,913,12296,12302,12303,12298,43,12323,11625,11626,12324,0,912,912,913,913,12302,11604,11605,12303,43,12325,12326,12327,12328,0,909,912,912,909,12304,12305,12306,12307,43,12326,12329,12330,12327,0,912,913,913,912,12305,12308,12309,12306,43,12328,12327,12331,12332,0,909,912,912,909,12307,12306,12310,12311,43,12327,12330,12333,12331,0,912,913,913,912,12306,12309,12312,12310,43,12325,12334,12335,12326,0,909,909,912,912,12304,12313,12314,12305,43,12334,12315,12321,12335,0,909,909,912,912,12313,12294,12300,12314,43,12326,12335,12336,12329,0,912,912,913,913,12305,12314,12315,12308,43,12335,12321,12322,12336,0,912,912,913,913,12314,12300,12301,12315,43,12325,12337,12338,12334,0,909,910,910,909,12304,12316,12317,12313,43,12337,12294,12293,12338,0,910,911,911,910,12316,12273,12272,12317,43,12334,12338,12316,12315,0,909,910,910,909,12313,12317,12295,12294,43,12338,12293,12283,12316,0,910,911,911,910,12317,12272,12262,12295,43,12325,12328,12339,12337,0,909,909,910,910,12304,12307,12318,12316,43,12328,12332,12340,12339,0,909,909,910,910,12307,12311,12319,12318,43,12337,12339,12306,12294,0,910,910,911,911,12316,12318,12285,12273,43,12339,12340,12308,12306,0,910,910,911,911,12318,12319,12287,12285,43,12341,12342,12343,12344,0,914,917,917,914,12320,12321,12322,12323,43,12342,12319,12324,12343,0,917,913,913,917,12321,12298,12303,12322,43,12344,12343,11636,11635,0,914,917,917,914,12323,12322,11615,11614,43,12343,12324,11626,11636,0,917,913,913,917,12322,12303,11605,11615,43,12341,12345,12346,12342,0,914,914,917,917,12320,12324,12325,12321,43,12345,12347,12348,12346,0,914,914,917,917,12324,12326,12327,12325,43,12342,12346,12320,12319,0,917,917,913,913,12321,12325,12299,12298,43,12346,12348,12322,12320,0,917,917,913,913,12325,12327,12301,12299,43,12341,12349,12350,12345,0,914,915,915,914,12320,12328,12329,12324,43,12349,12351,12352,12350,0,915,916,916,915,12328,12330,12331,12329,43,12345,12350,12353,12347,0,914,915,915,914,12324,12329,12332,12326,43,12350,12352,12354,12353,0,915,916,916,915,12329,12331,12333,12332,43,12341,12344,12355,12349,0,914,914,915,915,12320,12323,12334,12328,43,12344,11635,11641,12355,0,914,914,915,915,12323,11614,11620,12334,43,12349,12355,12356,12351,0,915,915,916,916,12328,12334,12335,12330,43,12355,11641,11642,12356,0,915,915,916,916,12334,11620,11621,12335,43,12357,12358,12359,12360,0,914,915,915,914,12336,12337,12338,12339,43,12358,12361,12362,12359,0,915,916,916,915,12337,12340,12341,12338,43,12360,12359,12363,12364,0,914,915,915,914,12339,12338,12342,12343,43,12359,12362,12365,12363,0,915,916,916,915,12338,12341,12344,12342,43,12357,12366,12367,12358,0,914,914,915,915,12336,12345,12346,12337,43,12366,12347,12353,12367,0,914,914,915,915,12345,12326,12332,12346,43,12358,12367,12368,12361,0,915,915,916,916,12337,12346,12347,12340,43,12367,12353,12354,12368,0,915,915,916,916,12346,12332,12333,12347,43,12357,12369,12370,12366,0,914,917,917,914,12336,12348,12349,12345,43,12369,12329,12336,12370,0,917,913,913,917,12348,12308,12315,12349,43,12366,12370,12348,12347,0,914,917,917,914,12345,12349,12327,12326,43,12370,12336,12322,12348,0,917,913,913,917,12349,12315,12301,12327,43,12357,12360,12371,12369,0,914,914,917,917,12336,12339,12350,12348,43,12360,12364,12372,12371,0,914,914,917,917,12339,12343,12351,12350,43,12369,12371,12330,12329,0,917,917,913,913,12348,12350,12309,12308,43,12371,12372,12333,12330,0,917,917,913,913,12350,12351,12312,12309,43,12373,12374,12375,12376,0,953,922,922,953,12352,12353,12354,12355,43,12374,12351,12356,12375,0,922,916,916,922,12353,12330,12335,12354,43,12376,12375,11652,11651,0,953,922,922,953,12355,12354,11631,11630,43,12375,12356,11642,11652,0,922,916,916,922,12354,12335,11621,11631,43,12373,12377,12378,12374,0,953,953,922,922,12352,12356,12357,12353,43,12377,12379,12380,12378,0,953,953,922,922,12356,12358,12359,12357,43,12374,12378,12352,12351,0,922,922,916,916,12353,12357,12331,12330,43,12378,12380,12354,12352,0,922,922,916,916,12357,12359,12333,12331,43,12373,12381,12382,12377,0,953,939,939,953,12352,12360,12361,12356,43,12381,12383,12384,12382,0,939,328,328,939,12360,12362,12363,12361,43,12377,12382,12385,12379,0,953,939,939,953,12356,12361,12364,12358,43,12382,12384,12386,12385,0,939,328,328,939,12361,12363,12365,12364,43,12373,12376,12387,12381,0,953,953,939,939,12352,12355,12366,12360,43,12376,11651,11657,12387,0,953,953,939,939,12355,11630,11636,12366,43,12381,12387,12388,12383,0,939,939,328,328,12360,12366,12367,12362,43,12387,11657,11658,12388,0,939,939,328,328,12366,11636,11637,12367,43,12389,12390,12391,12392,0,953,939,939,953,12368,12369,12370,12371,43,12390,12393,12394,12391,0,939,328,328,939,12369,12372,12373,12370,43,12392,12391,12395,12396,0,953,939,939,953,12371,12370,12374,12375,43,12391,12394,12397,12395,0,939,328,328,939,12370,12373,12376,12374,43,12389,12398,12399,12390,0,953,953,939,939,12368,12377,12378,12369,43,12398,12379,12385,12399,0,953,953,939,939,12377,12358,12364,12378,43,12390,12399,12400,12393,0,939,939,328,328,12369,12378,12379,12372,43,12399,12385,12386,12400,0,939,939,328,328,12378,12364,12365,12379,43,12389,12401,12402,12398,0,953,922,922,953,12368,12380,12381,12377,43,12401,12361,12368,12402,0,922,916,916,922,12380,12340,12347,12381,43,12398,12402,12380,12379,0,953,922,922,953,12377,12381,12359,12358,43,12402,12368,12354,12380,0,922,916,916,922,12381,12347,12333,12359,43,12389,12392,12403,12401,0,953,953,922,922,12368,12371,12382,12380,43,12392,12396,12404,12403,0,953,953,922,922,12371,12375,12383,12382,43,12401,12403,12362,12361,0,922,922,916,916,12380,12382,12341,12340,43,12403,12404,12365,12362,0,922,922,916,916,12382,12383,12344,12341,43,12405,12406,12407,12408,0,929,929,12,12,12384,12385,12386,12387,43,12406,12409,12410,12407,0,929,929,12,12,12385,12388,12389,12386,43,12408,12407,12411,12412,0,12,12,12,12,12387,12386,12390,12391,43,12407,12410,12413,12411,0,12,12,12,12,12386,12389,12392,12390,43,12405,12414,12415,12406,0,929,928,928,929,12384,12393,12394,12385,43,12414,12276,12275,12415,0,928,931,931,928,12393,12255,12254,12394,43,12406,12415,12416,12409,0,929,928,928,929,12385,12394,12395,12388,43,12415,12275,12277,12416,0,928,931,931,928,12394,12254,12256,12395,43,12405,12417,12418,12414,0,929,929,928,928,12384,12396,12397,12393,43,12417,11665,11672,12418,0,929,929,928,928,12396,11644,11651,12397,43,12414,12418,12288,12276,0,928,928,931,931,12393,12397,12267,12255,43,12418,11672,11601,12288,0,928,928,931,931,12397,11651,11580,12267,43,12405,12408,12419,12417,0,929,12,12,929,12384,12387,12398,12396,43,12408,12412,12420,12419,0,12,12,12,12,12387,12391,12399,12398,43,12417,12419,11666,11665,0,929,12,12,929,12396,12398,11645,11644,43,12419,12420,11669,11666,0,12,12,12,12,12398,12399,11648,11645,43,12421,12422,12423,12424,0,929,929,928,928,12400,12401,12402,12403,43,12422,12409,12416,12423,0,929,929,928,928,12401,12388,12395,12402,43,12424,12423,12298,12297,0,928,928,931,931,12403,12402,12277,12276,43,12423,12416,12277,12298,0,928,928,931,931,12402,12395,12256,12277,43,12421,12425,12426,12422,0,929,12,12,929,12400,12404,12405,12401,43,12425,12427,12428,12426,0,12,12,12,12,12404,12406,12407,12405,43,12422,12426,12410,12409,0,929,12,12,929,12401,12405,12389,12388,43,12426,12428,12413,12410,0,12,12,12,12,12405,12407,12392,12389,43,12421,12429,12430,12425,0,929,929,12,12,12400,12408,12409,12404,43,12429,12431,12432,12430,0,929,929,12,12,12408,12410,12411,12409,43,12425,12430,12433,12427,0,12,12,12,12,12404,12409,12412,12406,43,12430,12432,12434,12433,0,12,12,12,12,12409,12411,12413,12412,43,12421,12424,12435,12429,0,929,928,928,929,12400,12403,12414,12408,43,12424,12297,12303,12435,0,928,931,931,928,12403,12276,12282,12414,43,12429,12435,12436,12431,0,929,928,928,929,12408,12414,12415,12410,43,12435,12303,12304,12436,0,928,931,931,928,12414,12282,12283,12415,43,12437,12438,12439,12440,0,12,12,12,12,12416,12417,12418,12419,43,12438,12441,12442,12439,0,12,12,12,12,12417,12420,12421,12418,43,12440,12439,12443,12444,0,12,12,12,12,12419,12418,12422,12423,43,12439,12442,12445,12443,0,12,12,12,12,12418,12421,12424,12422,43,12437,12446,12447,12438,0,12,12,12,12,12416,12425,12426,12417,43,12446,12448,12449,12447,0,12,12,12,12,12425,12427,12428,12426,43,12438,12447,12450,12441,0,12,12,12,12,12417,12426,12429,12420,43,12447,12449,12451,12450,0,12,12,12,12,12426,12428,12430,12429,43,12437,12452,12453,12446,0,12,12,12,12,12416,12431,12432,12425,43,12452,11681,11690,12453,0,12,12,12,12,12431,11660,11669,12432,43,12446,12453,12454,12448,0,12,12,12,12,12425,12432,12433,12427,43,12453,11690,11691,12454,0,12,12,12,12,12432,11669,11670,12433,43,12437,12440,12455,12452,0,12,12,12,12,12416,12419,12434,12431,43,12440,12444,12456,12455,0,12,12,12,12,12419,12423,12435,12434,43,12452,12455,11682,11681,0,12,12,12,12,12431,12434,11661,11660,43,12455,12456,11685,11682,0,12,12,12,12,12434,12435,11664,11661,43,12457,12458,12459,12460,0,12,12,12,12,12436,12437,12438,12439,43,12458,12441,12450,12459,0,12,12,12,12,12437,12420,12429,12438,43,12460,12459,12461,12462,0,12,12,12,12,12439,12438,12440,12441,43,12459,12450,12451,12461,0,12,12,12,12,12438,12429,12430,12440,43,12457,12463,12464,12458,0,12,12,12,12,12436,12442,12443,12437,43,12463,12465,12466,12464,0,12,12,12,12,12442,12444,12445,12443,43,12458,12464,12442,12441,0,12,12,12,12,12437,12443,12421,12420,43,12464,12466,12445,12442,0,12,12,12,12,12443,12445,12424,12421,43,12457,12467,12468,12463,0,12,12,12,12,12436,12446,12447,12442,43,12467,12469,12470,12468,0,12,12,12,12,12446,12448,12449,12447,43,12463,12468,12471,12465,0,12,12,12,12,12442,12447,12450,12444,43,12468,12470,12472,12471,0,12,12,12,12,12447,12449,12451,12450,43,12457,12460,12473,12467,0,12,12,12,12,12436,12439,12452,12446,43,12460,12462,12474,12473,0,12,12,12,12,12439,12441,12453,12452,43,12467,12473,12475,12469,0,12,12,12,12,12446,12452,12454,12448,43,12473,12474,12476,12475,0,12,12,12,12,12452,12453,12455,12454,43,12477,12478,12479,12480,0,932,933,933,932,12456,12457,12458,12459,43,12478,12383,12388,12479,0,933,328,328,933,12457,12362,12367,12458,43,12480,12479,11704,11703,0,932,933,933,932,12459,12458,11683,11682,43,12479,12388,11658,11704,0,933,328,328,933,12458,12367,11637,11683,43,12477,12481,12482,12478,0,932,932,933,933,12456,12460,12461,12457,43,12481,12483,12484,12482,0,932,932,933,933,12460,12462,12463,12461,43,12478,12482,12384,12383,0,933,933,328,328,12457,12461,12363,12362,43,12482,12484,12386,12384,0,933,933,328,328,12461,12463,12365,12363,43,12477,12485,12486,12481,0,932,12,12,932,12456,12464,12465,12460,43,12485,12444,12443,12486,0,12,12,12,12,12464,12423,12422,12465,43,12481,12486,12487,12483,0,932,12,12,932,12460,12465,12466,12462,43,12486,12443,12445,12487,0,12,12,12,12,12465,12422,12424,12466,43,12477,12480,12488,12485,0,932,932,12,12,12456,12459,12467,12464,43,12480,11703,11707,12488,0,932,932,12,12,12459,11682,11686,12467,43,12485,12488,12456,12444,0,12,12,12,12,12464,12467,12435,12423,43,12488,11707,11685,12456,0,12,12,12,12,12467,11686,11664,12435,43,12489,12490,12491,12492,0,932,12,12,932,12468,12469,12470,12471,43,12490,12465,12471,12491,0,12,12,12,12,12469,12444,12450,12470,43,12492,12491,12493,12494,0,932,12,12,932,12471,12470,12472,12473,43,12491,12471,12472,12493,0,12,12,12,12,12470,12450,12451,12472,43,12489,12495,12496,12490,0,932,932,12,12,12468,12474,12475,12469,43,12495,12483,12487,12496,0,932,932,12,12,12474,12462,12466,12475,43,12490,12496,12466,12465,0,12,12,12,12,12469,12475,12445,12444,43,12496,12487,12445,12466,0,12,12,12,12,12475,12466,12424,12445,43,12489,12497,12498,12495,0,932,933,933,932,12468,12476,12477,12474,43,12497,12393,12400,12498,0,933,328,328,933,12476,12372,12379,12477,43,12495,12498,12484,12483,0,932,933,933,932,12474,12477,12463,12462,43,12498,12400,12386,12484,0,933,328,328,933,12477,12379,12365,12463,43,12489,12492,12499,12497,0,932,932,933,933,12468,12471,12478,12476,43,12492,12494,12500,12499,0,932,932,933,933,12471,12473,12479,12478,43,12497,12499,12394,12393,0,933,933,328,328,12476,12478,12373,12372,43,12499,12500,12397,12394,0,933,933,328,328,12478,12479,12376,12373,43,12501,12502,12503,12504,0,12,12,12,12,12480,12481,12482,12483,43,12502,12505,12506,12503,0,12,12,12,12,12481,12484,12485,12482,43,12504,12503,12449,12448,0,12,12,12,12,12483,12482,12428,12427,43,12503,12506,12451,12449,0,12,12,12,12,12482,12485,12430,12428,43,12501,12507,12508,12502,0,12,12,12,12,12480,12486,12487,12481,43,12507,12412,12411,12508,0,12,12,12,12,12486,12391,12390,12487,43,12502,12508,12509,12505,0,12,12,12,12,12481,12487,12488,12484,43,12508,12411,12413,12509,0,12,12,12,12,12487,12390,12392,12488,43,12501,12510,12511,12507,0,12,12,12,12,12480,12489,12490,12486,43,12510,11713,11717,12511,0,12,12,12,12,12489,11692,11696,12490,43,12507,12511,12420,12412,0,12,12,12,12,12486,12490,12399,12391,43,12511,11717,11669,12420,0,12,12,12,12,12490,11696,11648,12399,43,12501,12504,12512,12510,0,12,12,12,12,12480,12483,12491,12489,43,12504,12448,12454,12512,0,12,12,12,12,12483,12427,12433,12491,43,12510,12512,11714,11713,0,12,12,12,12,12489,12491,11693,11692,43,12512,12454,11691,11714,0,12,12,12,12,12491,12433,11670,11693,43,12513,12514,12515,12516,0,12,12,12,12,12492,12493,12494,12495,43,12514,12505,12509,12515,0,12,12,12,12,12493,12484,12488,12494,43,12516,12515,12428,12427,0,12,12,12,12,12495,12494,12407,12406,43,12515,12509,12413,12428,0,12,12,12,12,12494,12488,12392,12407,43,12513,12517,12518,12514,0,12,12,12,12,12492,12496,12497,12493,43,12517,12462,12461,12518,0,12,12,12,12,12496,12441,12440,12497,43,12514,12518,12506,12505,0,12,12,12,12,12493,12497,12485,12484,43,12518,12461,12451,12506,0,12,12,12,12,12497,12440,12430,12485,43,12513,12519,12520,12517,0,12,12,12,12,12492,12498,12499,12496,43,12519,12521,12522,12520,0,12,12,12,12,12498,12500,12501,12499,43,12517,12520,12474,12462,0,12,12,12,12,12496,12499,12453,12441,43,12520,12522,12476,12474,0,12,12,12,12,12499,12501,12455,12453,43,12513,12516,12523,12519,0,12,12,12,12,12492,12495,12502,12498,43,12516,12427,12433,12523,0,12,12,12,12,12495,12406,12412,12502,43,12519,12523,12524,12521,0,12,12,12,12,12498,12502,12503,12500,43,12523,12433,12434,12524,0,12,12,12,12,12502,12412,12413,12503,43,12525,12526,12527,12528,0,947,947,948,948,12504,12505,12506,12507,43,12526,9520,9527,12527,0,947,947,948,948,12505,9499,9506,12506,43,12528,12527,12529,12530,0,948,948,931,931,12507,12506,12508,12509,43,12527,9527,9453,12529,0,948,948,931,931,12506,9506,9432,12508,43,12525,12531,12532,12526,0,947,67,67,947,12504,12510,12511,12505,43,12531,12533,12534,12532,0,67,6,6,67,12510,12512,12513,12511,43,12526,12532,9521,9520,0,947,67,67,947,12505,12511,9500,9499,43,12532,12534,9524,9521,0,67,6,6,67,12511,12513,9503,9500,43,12525,12535,12536,12531,0,947,947,67,67,12504,12514,12515,12510,43,12535,12301,12307,12536,0,947,947,67,67,12514,12280,12286,12515,43,12531,12536,12537,12533,0,67,67,6,6,12510,12515,12516,12512,43,12536,12307,12308,12537,0,67,67,6,6,12515,12286,12287,12516,43,12525,12528,12538,12535,0,947,948,948,947,12504,12507,12517,12514,43,12528,12530,12539,12538,0,948,931,931,948,12507,12509,12518,12517,43,12535,12538,12302,12301,0,947,948,948,947,12514,12517,12281,12280,43,12538,12539,12304,12302,0,948,931,931,948,12517,12518,12283,12281,43,12540,12541,12542,12543,0,909,909,910,910,12519,12520,12521,12522,43,12541,9536,9543,12542,0,909,909,910,910,12520,9515,9522,12521,43,12543,12542,12534,12533,0,910,910,911,911,12522,12521,12513,12512,43,12542,9543,9524,12534,0,910,910,911,911,12521,9522,9503,12513,43,12540,12544,12545,12541,0,909,912,912,909,12519,12523,12524,12520,43,12544,12546,12547,12545,0,912,913,913,912,12523,12525,12526,12524,43,12541,12545,9537,9536,0,909,912,912,909,12520,12524,9516,9515,43,12545,12547,9540,9537,0,912,913,913,912,12524,12526,9519,9516,43,12540,12548,12549,12544,0,909,909,912,912,12519,12527,12528,12523,43,12548,12332,12331,12549,0,909,909,912,912,12527,12311,12310,12528,43,12544,12549,12550,12546,0,912,912,913,913,12523,12528,12529,12525,43,12549,12331,12333,12550,0,912,912,913,913,12528,12310,12312,12529,43,12540,12543,12551,12548,0,909,910,910,909,12519,12522,12530,12527,43,12543,12533,12537,12551,0,910,911,911,910,12522,12512,12516,12530,43,12548,12551,12340,12332,0,909,910,910,909,12527,12530,12319,12311,43,12551,12537,12308,12340,0,910,911,911,910,12530,12516,12287,12319,43,12552,12553,12554,12555,0,914,917,917,914,12531,12532,12533,12534,43,12553,12546,12550,12554,0,917,913,913,917,12532,12525,12529,12533,43,12555,12554,12372,12364,0,914,917,917,914,12534,12533,12351,12343,43,12554,12550,12333,12372,0,917,913,913,917,12533,12529,12312,12351,43,12552,12556,12557,12553,0,914,914,917,917,12531,12535,12536,12532,43,12556,9553,9552,12557,0,914,914,917,917,12535,9532,9531,12536,43,12553,12557,12547,12546,0,917,917,913,913,12532,12536,12526,12525,43,12557,9552,9540,12547,0,917,917,913,913,12536,9531,9519,12526,43,12552,12558,12559,12556,0,914,915,915,914,12531,12537,12538,12535,43,12558,12560,12561,12559,0,915,916,916,915,12537,12539,12540,12538,43,12556,12559,9561,9553,0,914,915,915,914,12535,12538,9540,9532,43,12559,12561,9563,9561,0,915,916,916,915,12538,12540,9542,9540,43,12552,12555,12562,12558,0,914,914,915,915,12531,12534,12541,12537,43,12555,12364,12363,12562,0,914,914,915,915,12534,12343,12342,12541,43,12558,12562,12563,12560,0,915,915,916,916,12537,12541,12542,12539,43,12562,12363,12365,12563,0,915,915,916,916,12541,12342,12344,12542,43,12564,12565,12566,12567,0,953,922,922,953,12543,12544,12545,12546,43,12565,12560,12563,12566,0,922,916,916,922,12544,12539,12542,12545,43,12567,12566,12404,12396,0,953,922,922,953,12546,12545,12383,12375,43,12566,12563,12365,12404,0,922,916,916,922,12545,12542,12344,12383,43,12564,12568,12569,12565,0,953,953,922,922,12543,12547,12548,12544,43,12568,9569,9568,12569,0,953,953,922,922,12547,9548,9547,12548,43,12565,12569,12561,12560,0,922,922,916,916,12544,12548,12540,12539,43,12569,9568,9563,12561,0,922,922,916,916,12548,9547,9542,12540,43,12564,12570,12571,12568,0,953,939,939,953,12543,12549,12550,12547,43,12570,12572,12573,12571,0,939,328,328,939,12549,12551,12552,12550,43,12568,12571,9577,9569,0,953,939,939,953,12547,12550,9556,9548,43,12571,12573,9579,9577,0,939,328,328,939,12550,12552,9558,9556,43,12564,12567,12574,12570,0,953,953,939,939,12543,12546,12553,12549,43,12567,12396,12395,12574,0,953,953,939,939,12546,12375,12374,12553,43,12570,12574,12575,12572,0,939,939,328,328,12549,12553,12554,12551,43,12574,12395,12397,12575,0,939,939,328,328,12553,12374,12376,12554,43,12576,12577,12578,12579,0,929,929,12,12,12555,12556,12557,12558,43,12577,9449,9458,12578,0,929,929,12,12,12556,9428,9437,12557,43,12579,12578,12580,12581,0,12,12,12,12,12558,12557,12559,12560,43,12578,9458,9459,12580,0,12,12,12,12,12557,9437,9438,12559,43,12576,12582,12583,12577,0,929,928,928,929,12555,12561,12562,12556,43,12582,12530,12529,12583,0,928,931,931,928,12561,12509,12508,12562,43,12577,12583,9450,9449,0,929,928,928,929,12556,12562,9429,9428,43,12583,12529,9453,9450,0,928,931,931,928,12562,12508,9432,9429,43,12576,12584,12585,12582,0,929,929,928,928,12555,12563,12564,12561,43,12584,12431,12436,12585,0,929,929,928,928,12563,12410,12415,12564,43,12582,12585,12539,12530,0,928,928,931,931,12561,12564,12518,12509,43,12585,12436,12304,12539,0,928,928,931,931,12564,12415,12283,12518,43,12576,12579,12586,12584,0,929,12,12,929,12555,12558,12565,12563,43,12579,12581,12587,12586,0,12,12,12,12,12558,12560,12566,12565,43,12584,12586,12432,12431,0,929,12,12,929,12563,12565,12411,12410,43,12586,12587,12434,12432,0,12,12,12,12,12565,12566,12413,12411,43,12588,12589,12590,12591,0,12,12,12,12,12567,12568,12569,12570,43,12589,12592,12593,12590,0,12,12,12,12,12568,12571,12572,12569,43,12591,12590,12470,12469,0,12,12,12,12,12570,12569,12449,12448,43,12590,12593,12472,12470,0,12,12,12,12,12569,12572,12451,12449,43,12588,12594,12595,12589,0,12,12,12,12,12567,12573,12574,12568,43,12594,9587,9586,12595,0,12,12,12,12,12573,9566,9565,12574,43,12589,12595,12596,12592,0,12,12,12,12,12568,12574,12575,12571,43,12595,9586,9588,12596,0,12,12,12,12,12574,9565,9567,12575,43,12588,12597,12598,12594,0,12,12,12,12,12567,12576,12577,12573,43,12597,12599,12600,12598,0,12,12,12,12,12576,12578,12579,12577,43,12594,12598,9597,9587,0,12,12,12,12,12573,12577,9576,9566,43,12598,12600,9599,9597,0,12,12,12,12,12577,12579,9578,9576,43,12588,12591,12601,12597,0,12,12,12,12,12567,12570,12580,12576,43,12591,12469,12475,12601,0,12,12,12,12,12570,12448,12454,12580,43,12597,12601,12602,12599,0,12,12,12,12,12576,12580,12581,12578,43,12601,12475,12476,12602,0,12,12,12,12,12580,12454,12455,12581,43,12603,12604,12605,12606,0,932,933,933,932,12582,12583,12584,12585,43,12604,12572,12575,12605,0,933,328,328,933,12583,12551,12554,12584,43,12606,12605,12500,12494,0,932,933,933,932,12585,12584,12479,12473,43,12605,12575,12397,12500,0,933,328,328,933,12584,12554,12376,12479,43,12603,12607,12608,12604,0,932,932,933,933,12582,12586,12587,12583,43,12607,9605,9604,12608,0,932,932,933,933,12586,9584,9583,12587,43,12604,12608,12573,12572,0,933,933,328,328,12583,12587,12552,12551,43,12608,9604,9579,12573,0,933,933,328,328,12587,9583,9558,12552,43,12603,12609,12610,12607,0,932,12,12,932,12582,12588,12589,12586,43,12609,12592,12596,12610,0,12,12,12,12,12588,12571,12575,12589,43,12607,12610,9611,9605,0,932,12,12,932,12586,12589,9590,9584,43,12610,12596,9588,9611,0,12,12,12,12,12589,12575,9567,9590,43,12603,12606,12611,12609,0,932,932,12,12,12582,12585,12590,12588,43,12606,12494,12493,12611,0,932,932,12,12,12585,12473,12472,12590,43,12609,12611,12593,12592,0,12,12,12,12,12588,12590,12572,12571,43,12611,12493,12472,12593,0,12,12,12,12,12590,12472,12451,12572,43,12612,12613,12614,12615,0,12,12,12,12,12591,12592,12593,12594,43,12613,9616,9620,12614,0,12,12,12,12,12592,9595,9599,12593,43,12615,12614,12600,12599,0,12,12,12,12,12594,12593,12579,12578,43,12614,9620,9599,12600,0,12,12,12,12,12593,9599,9578,12579,43,12612,12616,12617,12613,0,12,12,12,12,12591,12595,12596,12592,43,12616,12581,12580,12617,0,12,12,12,12,12595,12560,12559,12596,43,12613,12617,9617,9616,0,12,12,12,12,12592,12596,9596,9595,43,12617,12580,9459,9617,0,12,12,12,12,12596,12559,9438,9596,43,12612,12618,12619,12616,0,12,12,12,12,12591,12597,12598,12595,43,12618,12521,12524,12619,0,12,12,12,12,12597,12500,12503,12598,43,12616,12619,12587,12581,0,12,12,12,12,12595,12598,12566,12560,43,12619,12524,12434,12587,0,12,12,12,12,12598,12503,12413,12566,43,12612,12615,12620,12618,0,12,12,12,12,12591,12594,12599,12597,43,12615,12599,12602,12620,0,12,12,12,12,12594,12578,12581,12599,43,12618,12620,12522,12521,0,12,12,12,12,12597,12599,12501,12500,43,12620,12602,12476,12522,0,12,12,12,12,12599,12581,12455,12501,43,12621,12622,12623,12624,0,2447,2450,2449,2448,12600,12601,12602,12603,43,12622,11946,11945,12623,0,2450,2372,2373,2449,12601,11925,11924,12602,43,12624,12623,12625,12626,0,2448,2449,2452,2451,12603,12602,12604,12605,43,12623,11945,11947,12625,0,2449,2373,2374,2452,12602,11924,11926,12604,43,12621,12627,12628,12622,0,2447,2454,2453,2450,12600,12606,12607,12601,43,12627,11931,11937,12628,0,2454,2359,2364,2453,12606,11910,11916,12607,43,12622,12628,11954,11946,0,2450,2453,2377,2372,12601,12607,11933,11925,43,12628,11937,11938,11954,0,2453,2364,2365,2377,12607,11916,11917,11933,43,12621,12629,12630,12627,0,2447,2456,2455,2454,12600,12608,12609,12606,43,12629,12038,12037,12630,0,2456,2443,2358,2455,12608,12017,12016,12609,43,12627,12630,11932,11931,0,2454,2455,2358,2359,12606,12609,11911,11910,43,12630,12037,11934,11932,0,2455,2358,2361,2358,12609,12016,11913,11911,43,12621,12624,12631,12629,0,2447,2448,2457,2456,12600,12603,12610,12608,43,12624,12626,12632,12631,0,2448,2451,2458,2457,12603,12605,12611,12610,43,12629,12631,12044,12038,0,2456,2457,2446,2443,12608,12610,12023,12017,43,12631,12632,12032,12044,0,2457,2458,2440,2446,12610,12611,12011,12023,43,12633,12634,12635,12636,0,2459,2462,2461,2460,12612,12613,12614,12615,43,12634,11962,11961,12635,0,2462,2384,2385,2461,12613,11941,11940,12614,43,12636,12635,12637,12638,0,2460,2461,2464,2463,12615,12614,12616,12617,43,12635,11961,11963,12637,0,2461,2385,2386,2464,12614,11940,11942,12616,43,12633,12639,12640,12634,0,2459,2466,2465,2462,12612,12618,12619,12613,43,12639,12626,12625,12640,0,2466,2451,2452,2465,12618,12605,12604,12619,43,12634,12640,11970,11962,0,2462,2465,2392,2384,12613,12619,11949,11941,43,12640,12625,11947,11970,0,2465,2452,2374,2392,12619,12604,11926,11949,43,12633,12641,12642,12639,0,2459,2468,2467,2466,12612,12620,12621,12618,43,12641,12027,12031,12642,0,2468,2436,2439,2467,12620,12006,12010,12621,43,12639,12642,12632,12626,0,2466,2467,2458,2451,12618,12621,12611,12605,43,12642,12031,12032,12632,0,2467,2439,2440,2458,12621,12010,12011,12611,43,12633,12636,12643,12641,0,2459,2460,2469,2468,12612,12615,12622,12620,43,12636,12638,12644,12643,0,2460,2463,2470,2469,12615,12617,12623,12622,43,12641,12643,12028,12027,0,2468,2469,2435,2436,12620,12622,12007,12006,43,12643,12644,12009,12028,0,2469,2470,2425,2435,12622,12623,11988,12007,43,12645,12646,12647,12648,0,2471,2474,2473,2472,12624,12625,12626,12627,43,12646,12008,12007,12647,0,2474,2423,2424,2473,12625,11987,11986,12626,43,12648,12647,12644,12638,0,2472,2473,2470,2463,12627,12626,12623,12617,43,12647,12007,12009,12644,0,2473,2424,2425,2470,12626,11986,11988,12623,43,12645,12649,12650,12646,0,2471,2476,2475,2474,12624,12628,12629,12625,43,12649,12651,12652,12650,0,2476,2478,2477,2475,12628,12630,12631,12629,43,12646,12650,12016,12008,0,2474,2475,2428,2423,12625,12629,11995,11987,43,12650,12652,11882,12016,0,2475,2477,2329,2428,12629,12631,11861,11995,43,12645,12653,12654,12649,0,2471,2480,2479,2476,12624,12632,12633,12628,43,12653,11982,11985,12654,0,2480,2405,2407,2479,12632,11961,11964,12633,43,12649,12654,12655,12651,0,2476,2479,2481,2478,12628,12633,12634,12630,43,12654,11985,11869,12655,0,2479,2407,2316,2481,12633,11964,11848,12634,43,12645,12648,12656,12653,0,2471,2472,2482,2480,12624,12627,12635,12632,43,12648,12638,12637,12656,0,2472,2463,2464,2482,12627,12617,12616,12635,43,12653,12656,11983,11982,0,2480,2482,2404,2405,12632,12635,11962,11961,43,12656,12637,11963,11983,0,2482,2464,2386,2404,12635,12616,11942,11962,43,12657,12658,12659,12660,0,2483,2486,2485,2484,12636,12637,12638,12639,43,12658,12661,12662,12659,0,2486,2488,2487,2485,12637,12640,12641,12638,43,12660,12659,12663,12664,0,2484,2485,2490,2489,12639,12638,12642,12643,43,12659,12662,12665,12663,0,2485,2487,2491,2490,12638,12641,12644,12642,43,12657,12666,12667,12658,0,2483,2493,2492,2486,12636,12645,12646,12637,43,12666,12668,12669,12667,0,2493,2495,2494,2492,12645,12647,12648,12646,43,12658,12667,12670,12661,0,2486,2492,2496,2488,12637,12646,12649,12640,43,12667,12669,12671,12670,0,2492,2494,2497,2496,12646,12648,12650,12649,43,12657,12672,12673,12666,0,2483,2499,2498,2493,12636,12651,12652,12645,43,12672,11911,11917,12673,0,2499,2344,2349,2498,12651,11890,11896,12652,43,12666,12673,12674,12668,0,2493,2498,2500,2495,12645,12652,12653,12647,43,12673,11917,11918,12674,0,2498,2349,2350,2500,12652,11896,11897,12653,43,12657,12660,12675,12672,0,2483,2484,2501,2499,12636,12639,12654,12651,43,12660,12664,12676,12675,0,2484,2489,2502,2501,12639,12643,12655,12654,43,12672,12675,11912,11911,0,2499,2501,2343,2344,12651,12654,11891,11890,43,12675,12676,11914,11912,0,2501,2502,2346,2343,12654,12655,11893,11891,43,12677,12678,12679,12680,0,2503,2506,2505,2504,12656,12657,12658,12659,43,12678,12681,12682,12679,0,2506,2508,2507,2505,12657,12660,12661,12658,43,12680,12679,11560,11545,0,2504,2505,2256,2250,12659,12658,11539,11524,43,12679,12682,11562,11560,0,2505,2507,2258,2256,12658,12661,11541,11539,43,12677,12683,12684,12678,0,2503,2510,2509,2506,12656,12662,12663,12657,43,12683,12664,12663,12684,0,2510,2512,2511,2509,12662,12643,12642,12663,43,12678,12684,12685,12681,0,2506,2509,2513,2508,12657,12663,12664,12660,43,12684,12663,12665,12685,0,2509,2511,2514,2513,12663,12642,12644,12664,43,12677,12686,12687,12683,0,2503,2516,2515,2510,12656,12665,12666,12662,43,12686,11994,11998,12687,0,2516,2413,2416,2515,12665,11973,11977,12666,43,12683,12687,12676,12664,0,2510,2515,2517,2512,12662,12666,12655,12643,43,12687,11998,11914,12676,0,2515,2416,2346,2517,12666,11977,11893,12655,43,12677,12680,12688,12686,0,2503,2504,2518,2516,12656,12659,12667,12665,43,12680,11545,11544,12688,0,2504,2250,2251,2518,12659,11524,11523,12667,43,12686,12688,11995,11994,0,2516,2518,2412,2413,12665,12667,11974,11973,43,12688,11544,11546,11995,0,2518,2251,2252,2412,12667,11523,11525,11974,43,12689,12690,12691,12692,0,2519,2522,2521,2520,12668,12669,12670,12671,43,12690,12681,12685,12691,0,2522,2524,2523,2521,12669,12660,12664,12670,43,12692,12691,12693,12694,0,2520,2521,2526,2525,12671,12670,12672,12673,43,12691,12685,12665,12693,0,2521,2523,2527,2526,12670,12664,12644,12672,43,12689,12695,12696,12690,0,2519,2529,2528,2522,12668,12674,12675,12669,43,12695,11567,11571,12696,0,2529,2264,2269,2528,12674,11546,11550,12675,43,12690,12696,12682,12681,0,2522,2528,2530,2524,12669,12675,12661,12660,43,12696,11571,11562,12682,0,2528,2269,2270,2530,12675,11550,11541,12661,43,12689,12697,12698,12695,0,2519,2532,2531,2529,12668,12676,12677,12674,43,12697,11975,11979,12698,0,2532,2398,2401,2531,12676,11954,11958,12677,43,12695,12698,11568,11567,0,2529,2531,2263,2264,12674,12677,11547,11546,43,12698,11979,11281,11568,0,2531,2401,2243,2263,12677,11958,11260,11547,43,12689,12692,12699,12697,0,2519,2520,2533,2532,12668,12671,12678,12676,43,12692,12694,12700,12699,0,2520,2525,2534,2533,12671,12673,12679,12678,43,12697,12699,11976,11975,0,2532,2533,2397,2398,12676,12678,11955,11954,43,12699,12700,11863,11976,0,2533,2534,2310,2397,12678,12679,11842,11955,43,12701,12702,12703,12704,0,2535,2538,2537,2536,12680,12681,12682,12683,43,12702,12705,12706,12703,0,2538,2540,2539,2537,12681,12684,12685,12682,43,12704,12703,11854,11853,0,2536,2537,2300,2301,12683,12682,11833,11832,43,12703,12706,11857,11854,0,2537,2539,2304,2300,12682,12685,11836,11833,43,12701,12707,12708,12702,0,2535,2542,2541,2538,12680,12686,12687,12681,43,12707,12661,12670,12708,0,2542,2544,2543,2541,12686,12640,12649,12687,43,12702,12708,12709,12705,0,2538,2541,2545,2540,12681,12687,12688,12684,43,12708,12670,12671,12709,0,2541,2543,2546,2545,12687,12649,12650,12688,43,12701,12710,12711,12707,0,2535,2548,2547,2542,12680,12689,12690,12686,43,12710,12694,12693,12711,0,2548,2550,2549,2547,12689,12673,12672,12690,43,12707,12711,12662,12661,0,2542,2547,2551,2544,12686,12690,12641,12640,43,12711,12693,12665,12662,0,2547,2549,2552,2551,12690,12672,12644,12641,43,12701,12704,12712,12710,0,2535,2536,2553,2548,12680,12683,12691,12689,43,12704,11853,11862,12712,0,2536,2301,2309,2553,12683,11832,11841,12691,43,12710,12712,12700,12694,0,2548,2553,2554,2550,12689,12691,12679,12673,43,12712,11862,11863,12700,0,2553,2309,2310,2554,12691,11841,11842,12679,43,12713,12714,12715,12716,0,2555,2558,2557,2556,12692,12693,12694,12695,43,12714,12717,12718,12715,0,2558,2560,2559,2557,12693,12696,12696,12694,43,12716,12715,12719,12720,0,2556,2557,2562,2561,12695,12694,12697,12698,43,12715,12718,12721,12719,0,2557,2559,2563,2562,12694,12696,12696,12697,43,12713,12722,12723,12714,0,2555,2565,2564,2558,12692,12699,12700,12693,43,12722,12724,12725,12723,0,2565,2567,2566,2564,12699,12701,12702,12700,43,12714,12723,12726,12717,0,2558,2564,2568,2560,12693,12700,12703,12696,43,12723,12725,12727,12726,0,2564,2566,2569,2568,12700,12702,12703,12703,43,12713,12728,12729,12722,0,2555,2571,2570,2565,12692,12704,12705,12699,43,12728,11866,11872,12729,0,2571,2314,2319,2570,12704,11845,11851,12705,43,12722,12729,12730,12724,0,2565,2570,2572,2567,12699,12705,12706,12701,43,12729,11872,11873,12730,0,2570,2319,2320,2572,12705,11851,11852,12706,43,12713,12716,12731,12728,0,2555,2556,2573,2571,12692,12695,12707,12704,43,12716,12720,12732,12731,0,2556,2561,2574,2573,12695,12698,12708,12707,43,12728,12731,11867,11866,0,2571,2573,2313,2314,12704,12707,11846,11845,43,12731,12732,11869,11867,0,2573,2574,2316,2313,12707,12708,11848,11846,43,12733,12734,12735,12736,0,2575,2578,2577,2576,12709,12710,12711,12712,43,12734,12737,12738,12735,0,2578,2580,2579,2577,12710,12696,12703,12711,43,12736,12735,12739,12740,0,2576,2577,2582,2581,12712,12711,12713,12714,43,12735,12738,12741,12739,0,2577,2579,2583,2582,12711,12703,12703,12713,43,12733,12742,12743,12734,0,2575,2585,2584,2578,12709,12715,12716,12710,43,12742,12720,12719,12743,0,2585,2561,2562,2584,12715,12698,12697,12716,43,12734,12743,12744,12737,0,2578,2584,2586,2580,12710,12716,12696,12696,43,12743,12719,12721,12744,0,2584,2562,2563,2586,12716,12697,12696,12696,43,12733,12745,12746,12742,0,2575,2588,2587,2585,12709,12717,12718,12715,43,12745,12651,12655,12746,0,2588,2478,2481,2587,12717,12630,12634,12718,43,12742,12746,12732,12720,0,2585,2587,2574,2561,12715,12718,12708,12698,43,12746,12655,11869,12732,0,2587,2481,2316,2574,12718,12634,11848,12708,43,12733,12736,12747,12745,0,2575,2576,2589,2588,12709,12712,12719,12717,43,12736,12740,12748,12747,0,2576,2581,2590,2589,12712,12714,12720,12719,43,12745,12747,12652,12651,0,2588,2589,2477,2478,12717,12719,12631,12630,43,12747,12748,11882,12652,0,2589,2590,2329,2477,12719,12720,11861,12631,43,12749,12750,12751,12752,0,2591,2594,2593,2592,12721,12722,12723,12724,43,12750,12753,12754,12751,0,2594,2596,2595,2593,12722,12725,12726,12723,43,12752,12751,11896,11881,0,2592,2593,2333,2327,12724,12723,11875,11860,43,12751,12754,11898,11896,0,2593,2595,2335,2333,12723,12726,11877,11875,43,12749,12755,12756,12750,0,2591,2598,2597,2594,12721,12727,12728,12722,43,12755,12757,12758,12756,0,2598,2600,2599,2597,12727,12696,12696,12728,43,12750,12756,12759,12753,0,2594,2597,2601,2596,12722,12728,12729,12725,43,12756,12758,12760,12759,0,2597,2599,2602,2601,12728,12696,12703,12729,43,12749,12761,12762,12755,0,2591,2604,2603,2598,12721,12730,12731,12727,43,12761,12740,12739,12762,0,2604,2581,2582,2603,12730,12714,12713,12731,43,12755,12762,12763,12757,0,2598,2603,2605,2600,12727,12731,12703,12696,43,12762,12739,12741,12763,0,2603,2582,2583,2605,12731,12713,12703,12703,43,12749,12752,12764,12761,0,2591,2592,2606,2604,12721,12724,12732,12730,43,12752,11881,11880,12764,0,2592,2327,2328,2606,12724,11860,11859,12732,43,12761,12764,12748,12740,0,2604,2606,2590,2581,12730,12732,12720,12714,43,12764,11880,11882,12748,0,2606,2328,2329,2590,12732,11859,11861,12720,43,12765,12766,12767,12768,0,2607,2610,2609,2608,12703,12703,12696,12696,43,12766,12737,12744,12767,0,2610,2580,2586,2609,12703,12696,12696,12696,43,12768,12767,12718,12717,0,2608,2609,2559,2560,12696,12696,12696,12696,43,12767,12744,12721,12718,0,2609,2586,2563,2559,12696,12696,12696,12696,43,12765,12769,12770,12766,0,2607,2612,2611,2610,12703,12703,12703,12703,43,12769,12757,12763,12770,0,2612,2600,2605,2611,12703,12696,12703,12703,43,12766,12770,12738,12737,0,2610,2611,2579,2580,12703,12703,12703,12696,43,12770,12763,12741,12738,0,2611,2605,2583,2579,12703,12703,12703,12703,43,12765,12771,12772,12769,0,2607,2614,2613,2612,12703,12703,12703,12703,43,12771,12773,12774,12772,0,2614,2616,2615,2613,12703,12703,12703,12703,43,12769,12772,12758,12757,0,2612,2613,2599,2600,12703,12703,12696,12696,43,12772,12774,12760,12758,0,2613,2615,2602,2599,12703,12703,12703,12696,43,12765,12768,12775,12771,0,2607,2608,2617,2614,12703,12696,12703,12703,43,12768,12717,12726,12775,0,2608,2560,2568,2617,12696,12696,12703,12703,43,12771,12775,12776,12773,0,2614,2617,2618,2616,12703,12703,12703,12703,43,12775,12726,12727,12776,0,2617,2568,2569,2618,12703,12703,12703,12703,43,12777,12778,12779,12780,0,2619,948,948,2620,12733,12734,12735,12736,43,12778,12781,12782,12779,0,948,931,931,948,12734,12737,12738,12735,43,12780,12779,12077,12067,0,2620,948,948,2283,12736,12735,12056,12046,43,12779,12782,12079,12077,0,948,931,931,948,12735,12738,12058,12056,43,12777,12783,12784,12778,0,2619,2620,948,948,12733,12739,12740,12734,43,12783,11733,11739,12784,0,2620,2283,948,948,12739,11712,11718,12740,43,12778,12784,12785,12781,0,948,948,931,931,12734,12740,12741,12737,43,12784,11739,11740,12785,0,948,948,931,931,12740,11718,11719,12741,43,12777,12786,12787,12783,0,2619,2622,2621,2620,12733,12742,12743,12739,43,12786,12788,12789,12787,0,2622,2624,2623,2621,12742,12744,12745,12743,43,12783,12787,11734,11733,0,2620,2621,2282,2283,12739,12743,11713,11712,43,12787,12789,11736,11734,0,2621,2623,2285,2282,12743,12745,11715,11713,43,12777,12780,12790,12786,0,2619,2620,2621,2622,12733,12736,12746,12742,43,12780,12067,12066,12790,0,2620,2283,2282,2621,12736,12046,12045,12746,43,12786,12790,12791,12788,0,2622,2621,2623,2624,12742,12746,12747,12744,43,12790,12066,12068,12791,0,2621,2282,2285,2623,12746,12045,12047,12747,43,12792,12793,12794,12795,0,2625,2628,2627,2626,12748,12749,12750,12751,43,12793,12788,12791,12794,0,2628,2624,2623,2627,12749,12744,12747,12750,43,12795,12794,12107,12099,0,2626,2627,2630,2629,12751,12750,12086,12078,43,12794,12791,12068,12107,0,2627,2623,2285,2630,12750,12747,12047,12086,43,12792,12796,12797,12793,0,2625,2626,2627,2628,12748,12752,12753,12749,43,12796,11746,11745,12797,0,2626,2629,2630,2627,12752,11725,11724,12753,43,12793,12797,12789,12788,0,2628,2627,2623,2624,12749,12753,12745,12744,43,12797,11745,11736,12789,0,2627,2630,2285,2623,12753,11724,11715,12745,43,12792,12798,12799,12796,0,2625,2632,2631,2626,12748,12754,12755,12752,43,12798,12800,12801,12799,0,2632,2634,2633,2631,12754,12756,12757,12755,43,12796,12799,11754,11746,0,2626,2631,2635,2629,12752,12755,11733,11725,43,12799,12801,11756,11754,0,2631,2633,382,2635,12755,12757,11735,11733,43,12792,12795,12802,12798,0,2625,2626,2631,2632,12748,12751,12758,12754,43,12795,12099,12098,12802,0,2626,2629,2635,2631,12751,12078,12077,12758,43,12798,12802,12803,12800,0,2632,2631,2633,2634,12754,12758,12759,12756,43,12802,12098,12100,12803,0,2631,2635,382,2633,12758,12077,12079,12759,43,12804,12805,12806,12807,0,2636,2639,2638,2637,12760,12761,12762,12763,43,12805,12800,12803,12806,0,2639,2634,2633,2638,12761,12756,12759,12762,43,12807,12806,12131,12130,0,2637,2638,2295,2294,12763,12762,12110,12109,43,12806,12803,12100,12131,0,2638,2633,382,2295,12762,12759,12079,12110,43,12804,12808,12809,12805,0,2636,2637,2638,2639,12760,12764,12765,12761,43,12808,11762,11761,12809,0,2637,2294,2295,2638,12764,11741,11740,12765,43,12805,12809,12801,12800,0,2639,2638,2633,2634,12761,12765,12757,12756,43,12809,11761,11756,12801,0,2638,2295,382,2633,12765,11740,11735,12757,43,12804,12810,12811,12808,0,2636,915,915,2637,12760,12766,12767,12764,43,12810,12812,12813,12811,0,915,916,916,915,12766,12768,12769,12767,43,12808,12811,11770,11762,0,2637,915,915,2294,12764,12767,11749,11741,43,12811,12813,11772,11770,0,915,916,916,915,12767,12769,11751,11749,43,12804,12807,12814,12810,0,2636,2637,915,915,12760,12763,12770,12766,43,12807,12130,12134,12814,0,2637,2294,915,915,12763,12109,12113,12770,43,12810,12814,12815,12812,0,915,915,916,916,12766,12770,12771,12768,43,12814,12134,12135,12815,0,915,915,916,916,12770,12113,12114,12771,43,12816,12817,12818,12819,0,953,922,922,953,12772,12773,12774,12775,43,12817,12812,12815,12818,0,922,916,916,922,12773,12768,12771,12774,43,12819,12818,12159,12158,0,953,922,922,953,12775,12774,12138,12137,43,12818,12815,12135,12159,0,922,916,916,922,12774,12771,12114,12138,43,12816,12820,12821,12817,0,953,953,922,922,12772,12776,12777,12773,43,12820,11778,11777,12821,0,953,953,922,922,12776,11757,11756,12777,43,12817,12821,12813,12812,0,922,922,916,916,12773,12777,12769,12768,43,12821,11777,11772,12813,0,922,922,916,916,12777,11756,11751,12769,43,12816,12822,12823,12820,0,953,939,939,953,12772,12778,12779,12776,43,12822,12824,12825,12823,0,939,328,328,939,12778,12780,12781,12779,43,12820,12823,11786,11778,0,953,939,939,953,12776,12779,11765,11757,43,12823,12825,11788,11786,0,939,328,328,939,12779,12781,11767,11765,43,12816,12819,12826,12822,0,953,953,939,939,12772,12775,12782,12778,43,12819,12158,12162,12826,0,953,953,939,939,12775,12137,12141,12782,43,12822,12826,12827,12824,0,939,939,328,328,12778,12782,12783,12780,43,12826,12162,12163,12827,0,939,939,328,328,12782,12141,12142,12783,43,12828,12829,12830,12831,0,929,12,12,929,12784,12785,12786,12787,43,12829,12832,12833,12830,0,12,12,12,12,12785,12788,12789,12786,43,12831,12830,12187,12186,0,929,12,12,929,12787,12786,12166,12165,43,12830,12833,12189,12187,0,12,12,12,12,12786,12789,12168,12166,43,12828,12834,12835,12829,0,929,929,12,12,12784,12790,12791,12785,43,12834,11799,11803,12835,0,929,929,12,12,12790,11778,11782,12791,43,12829,12835,12836,12832,0,12,12,12,12,12785,12791,12792,12788,43,12835,11803,11804,12836,0,12,12,12,12,12791,11782,11783,12792,43,12828,12837,12838,12834,0,929,928,928,929,12784,12793,12794,12790,43,12837,12781,12785,12838,0,928,931,931,928,12793,12737,12741,12794,43,12834,12838,11800,11799,0,929,928,928,929,12790,12794,11779,11778,43,12838,12785,11740,11800,0,928,931,931,928,12794,12741,11719,11779,43,12828,12831,12839,12837,0,929,929,928,928,12784,12787,12795,12793,43,12831,12186,12191,12839,0,929,929,928,928,12787,12165,12170,12795,43,12837,12839,12782,12781,0,928,928,931,931,12793,12795,12738,12737,43,12839,12191,12079,12782,0,928,928,931,931,12795,12170,12058,12738,43,12840,12841,12842,12843,0,12,12,12,12,12796,12797,12798,12799,43,12841,12844,12845,12842,0,12,12,12,12,12797,12800,12801,12798,43,12843,12842,12224,12214,0,12,12,12,12,12799,12798,12203,12193,43,12842,12845,12226,12224,0,12,12,12,12,12798,12801,12205,12203,43,12840,12846,12847,12841,0,12,12,12,12,12796,12802,12803,12797,43,12846,11812,11811,12847,0,12,12,12,12,12802,11791,11790,12803,43,12841,12847,12848,12844,0,12,12,12,12,12797,12803,12804,12800,43,12847,11811,11813,12848,0,12,12,12,12,12803,11790,11792,12804,43,12840,12849,12850,12846,0,12,12,12,12,12796,12805,12806,12802,43,12849,12851,12852,12850,0,12,12,12,12,12805,12807,12808,12806,43,12846,12850,11822,11812,0,12,12,12,12,12802,12806,11801,11791,43,12850,12852,11824,11822,0,12,12,12,12,12806,12808,11803,11801,43,12840,12843,12853,12849,0,12,12,12,12,12796,12799,12809,12805,43,12843,12214,12213,12853,0,12,12,12,12,12799,12193,12192,12809,43,12849,12853,12854,12851,0,12,12,12,12,12805,12809,12810,12807,43,12853,12213,12215,12854,0,12,12,12,12,12809,12192,12194,12810,43,12855,12856,12857,12858,0,932,933,933,932,12811,12812,12813,12814,43,12856,12824,12827,12857,0,933,328,328,933,12812,12780,12783,12813,43,12858,12857,12245,12244,0,932,933,933,932,12814,12813,12224,12223,43,12857,12827,12163,12245,0,933,328,328,933,12813,12783,12142,12224,43,12855,12859,12860,12856,0,932,932,933,933,12811,12815,12816,12812,43,12859,11833,11836,12860,0,932,932,933,933,12815,11812,11815,12816,43,12856,12860,12825,12824,0,933,933,328,328,12812,12816,12781,12780,43,12860,11836,11788,12825,0,933,933,328,328,12816,11815,11767,12781,43,12855,12861,12862,12859,0,932,12,12,932,12811,12817,12818,12815,43,12861,12844,12848,12862,0,12,12,12,12,12817,12800,12804,12818,43,12859,12862,11834,11833,0,932,12,12,932,12815,12818,11813,11812,43,12862,12848,11813,11834,0,12,12,12,12,12818,12804,11792,11813,43,12855,12858,12863,12861,0,932,932,12,12,12811,12814,12819,12817,43,12858,12244,12247,12863,0,932,932,12,12,12814,12223,12226,12819,43,12861,12863,12845,12844,0,12,12,12,12,12817,12819,12801,12800,43,12863,12247,12226,12845,0,12,12,12,12,12819,12226,12205,12801,43,12864,12865,12866,12867,0,12,12,12,12,12820,12821,12822,12823,43,12865,12851,12854,12866,0,12,12,12,12,12821,12807,12810,12822,43,12867,12866,12268,12262,0,12,12,12,12,12823,12822,12247,12241,43,12866,12854,12215,12268,0,12,12,12,12,12822,12810,12194,12247,43,12864,12868,12869,12865,0,12,12,12,12,12820,12824,12825,12821,43,12868,11845,11848,12869,0,12,12,12,12,12824,11824,11827,12825,43,12865,12869,12852,12851,0,12,12,12,12,12821,12825,12808,12807,43,12869,11848,11824,12852,0,12,12,12,12,12825,11827,11803,12808,43,12864,12870,12871,12868,0,12,12,12,12,12820,12826,12827,12824,43,12870,12832,12836,12871,0,12,12,12,12,12826,12788,12792,12827,43,12868,12871,11846,11845,0,12,12,12,12,12824,12827,11825,11824,43,12871,12836,11804,11846,0,12,12,12,12,12827,12792,11783,11825,43,12864,12867,12872,12870,0,12,12,12,12,12820,12823,12828,12826,43,12867,12262,12261,12872,0,12,12,12,12,12823,12241,12240,12828,43,12870,12872,12833,12832,0,12,12,12,12,12826,12828,12789,12788,43,12872,12261,12189,12833,0,12,12,12,12,12828,12240,12168,12789,43,12873,12874,12875,12876,0,2640,2643,2642,2641,12829,12830,12831,12832,43,12874,12877,12878,12875,0,2643,2645,2644,2642,12830,12833,12834,12831,43,12876,12875,10633,10627,0,2641,2642,1818,1811,12832,12831,10612,10606,43,12875,12878,10224,10633,0,2642,2644,1449,1818,12831,12834,10203,10612,43,12873,12879,12880,12874,0,2640,2647,2646,2643,12829,12835,12836,12830,43,12879,12881,12882,12880,0,2647,2649,2648,2646,12835,12837,12838,12836,43,12874,12880,12883,12877,0,2643,2646,2650,2645,12830,12836,12839,12833,43,12880,12882,12884,12883,0,2646,2648,2651,2650,12836,12838,12840,12839,43,12873,12885,12886,12879,0,2640,2653,2652,2647,12829,12841,12842,12835,43,12885,12887,12888,12886,0,2653,2655,2654,2652,12841,12843,12844,12842,43,12879,12886,12889,12881,0,2647,2652,2656,2649,12835,12842,12845,12837,43,12886,12888,12890,12889,0,2652,2654,2657,2656,12842,12844,12846,12845,43,12873,12876,12891,12885,0,2640,2641,2658,2653,12829,12832,12847,12841,43,12876,10627,10626,12891,0,2641,1811,1812,2658,12832,10606,10605,12847,43,12885,12891,12892,12887,0,2653,2658,2659,2655,12841,12847,12848,12843,43,12891,10626,10173,12892,0,2658,1812,1398,2659,12847,10605,10152,12848,43,12893,12894,12895,12896,0,2660,2663,2662,2661,12849,12850,12851,12852,43,12894,12897,12898,12895,0,2663,2665,2664,2662,12850,12853,12854,12851,43,12896,12895,10193,10192,0,2661,2662,1417,1418,12852,12851,10172,10171,43,12895,12898,10195,10193,0,2662,2664,1420,1417,12851,12854,10174,10172,43,12893,12899,12900,12894,0,2660,2667,2666,2663,12849,12855,12856,12850,43,12899,12901,12902,12900,0,2667,2669,2668,2666,12855,12857,12858,12856,43,12894,12900,12903,12897,0,2663,2666,2670,2665,12850,12856,12859,12853,43,12900,12902,12904,12903,0,2666,2668,2671,2670,12856,12858,12860,12859,43,12893,12905,12906,12899,0,2660,2673,2672,2667,12849,12861,12862,12855,43,12905,12907,12908,12906,0,2673,2675,2674,2672,12861,12863,12864,12862,43,12899,12906,12909,12901,0,2667,2672,2676,2669,12855,12862,12865,12857,43,12906,12908,12910,12909,0,2672,2674,2677,2676,12862,12864,12866,12865,43,12893,12896,12911,12905,0,2660,2661,2678,2673,12849,12852,12867,12861,43,12896,10192,10200,12911,0,2661,1418,1425,2678,12852,10171,10179,12867,43,12905,12911,12912,12907,0,2673,2678,2679,2675,12861,12867,12868,12863,43,12911,10200,10201,12912,0,2678,1425,1426,2679,12867,10179,10180,12868,43,12913,12914,12915,12916,0,2680,2683,2682,2681,12869,12870,12871,12872,43,12914,12917,12918,12915,0,2683,2685,2684,2682,12870,12873,12874,12871,43,12916,12915,12919,12920,0,2681,2682,2687,2686,12872,12871,12875,12876,43,12915,12918,12921,12919,0,2682,2684,2688,2687,12871,12874,12877,12875,43,12913,12922,12923,12914,0,2680,1003,1003,2683,12869,12878,12879,12870,43,12922,5565,5564,12923,0,1003,1004,1004,1003,12878,5544,5543,12879,43,12914,12923,12924,12917,0,2683,1003,1003,2685,12870,12879,12880,12873,43,12923,5564,5567,12924,0,1003,1004,1004,1003,12879,5543,5546,12880,43,12913,12925,12926,12922,0,2680,2689,1003,1003,12869,12881,12882,12878,43,12925,12927,12928,12926,0,2689,2690,1003,1003,12881,12883,12884,12882,43,12922,12926,5572,5565,0,1003,1003,1004,1004,12878,12882,5551,5544,43,12926,12928,5573,5572,0,1003,1003,1004,1004,12882,12884,5552,5551,43,12913,12916,12929,12925,0,2680,2681,2691,2689,12869,12872,12885,12881,43,12916,12920,12930,12929,0,2681,2686,2692,2691,12872,12876,12886,12885,43,12925,12929,12931,12927,0,2689,2691,2693,2690,12881,12885,12887,12883,43,12929,12930,12932,12931,0,2691,2692,2694,2693,12885,12886,12888,12887,43,12933,12934,12935,12936,0,2695,2698,2697,2696,12889,12890,12891,12892,43,12934,12907,12912,12935,0,2698,2675,2679,2697,12890,12863,12868,12891,43,12936,12935,10645,10639,0,2696,2697,1830,1823,12892,12891,10624,10618,43,12935,12912,10201,10645,0,2697,2679,1426,1830,12891,12868,10180,10624,43,12933,12937,12938,12934,0,2695,2700,2699,2698,12889,12893,12894,12890,43,12937,12939,12940,12938,0,2700,2702,2701,2699,12893,12895,12896,12894,43,12934,12938,12908,12907,0,2698,2699,2674,2675,12890,12894,12864,12863,43,12938,12940,12910,12908,0,2699,2701,2677,2674,12894,12896,12866,12864,43,12933,12941,12942,12937,0,2695,2704,2703,2700,12889,12897,12898,12893,43,12941,12943,12944,12942,0,2704,2706,2705,2703,12897,12899,12900,12898,43,12937,12942,12945,12939,0,2700,2703,2707,2702,12893,12898,12901,12895,43,12942,12944,12946,12945,0,2703,2705,2708,2707,12898,12900,12902,12901,43,12933,12936,12947,12941,0,2695,2696,2709,2704,12889,12892,12903,12897,43,12936,10639,10638,12947,0,2696,1823,1824,2709,12892,10618,10617,12903,43,12941,12947,12948,12943,0,2704,2709,2710,2706,12897,12903,12904,12899,43,12947,10638,10218,12948,0,2709,1824,1443,2710,12903,10617,10197,12904,43,12949,12950,12951,12952,0,2711,2714,2713,2712,12905,12906,12907,12908,43,12950,12953,12954,12951,0,2714,2711,2715,2713,12906,12909,12910,12907,43,12952,12951,12955,12956,0,2712,2713,2717,2716,12908,12907,12911,12912,43,12951,12954,12957,12955,0,2713,2715,2718,2717,12907,12910,12913,12911,43,12949,12958,12959,12950,0,2711,1003,1003,2714,12905,12914,12915,12906,43,12958,5606,5605,12959,0,1003,1004,1004,1003,12914,5585,5584,12915,43,12950,12959,12960,12953,0,2714,1003,1003,2711,12906,12915,12916,12909,43,12959,5605,5608,12960,0,1003,1004,1004,1003,12915,5584,5587,12916,43,12949,12961,12962,12958,0,2711,2719,1003,1003,12905,12917,12918,12914,43,12961,12963,12964,12962,0,2719,2720,1003,1003,12917,12919,12920,12918,43,12958,12962,5613,5606,0,1003,1003,1004,1004,12914,12918,5592,5585,43,12962,12964,5614,5613,0,1003,1003,1004,1004,12918,12920,5593,5592,43,12949,12952,12965,12961,0,2711,2712,2721,2719,12905,12908,12921,12917,43,12952,12956,12966,12965,0,2712,2716,2722,2721,12908,12912,12922,12921,43,12961,12965,12967,12963,0,2719,2721,2723,2720,12917,12921,12923,12919,43,12965,12966,12968,12967,0,2721,2722,2724,2723,12921,12922,12924,12923,43,12969,12970,12971,12972,0,2725,2728,2727,2726,12925,12926,12927,12928,43,12970,12973,12974,12971,0,2728,2730,2729,2727,12926,12929,12930,12927,43,12972,12971,10234,10233,0,2726,2727,1458,1459,12928,12927,10213,10212,43,12971,12974,10179,10234,0,2727,2729,1404,1458,12927,12930,10158,10213,43,12969,12975,12976,12970,0,2725,2732,2731,2728,12925,12931,12932,12926,43,12975,12977,12978,12976,0,2732,2734,2733,2731,12931,12933,12934,12932,43,12970,12976,12979,12973,0,2728,2731,2735,2730,12926,12932,12935,12929,43,12976,12978,12980,12979,0,2731,2733,2736,2735,12932,12934,12936,12935,43,12969,12981,12982,12975,0,2725,2738,2737,2732,12925,12937,12938,12931,43,12981,12983,12984,12982,0,2738,2740,2739,2737,12937,12939,12940,12938,43,12975,12982,12985,12977,0,2732,2737,2741,2734,12931,12938,12941,12933,43,12982,12984,12986,12985,0,2737,2739,2742,2741,12938,12940,12942,12941,43,12969,12972,12987,12981,0,2725,2726,2743,2738,12925,12928,12943,12937,43,12972,10233,10239,12987,0,2726,1459,1464,2743,12928,10212,10218,12943,43,12981,12987,12988,12983,0,2738,2743,2744,2740,12937,12943,12944,12939,43,12987,10239,8078,12988,0,2743,1464,356,2744,12943,10218,8057,12944,43,12989,12990,12991,12992,0,2745,2685,2747,2746,12945,12946,12947,12948,43,12990,12993,12994,12991,0,2685,2749,2748,2747,12946,12949,12950,12947,43,12992,12991,12995,12996,0,2746,2747,2751,2750,12948,12947,12951,12952,43,12991,12994,12997,12995,0,2747,2748,2752,2751,12947,12950,12953,12951,43,12989,12998,12999,12990,0,2745,1003,1003,2685,12945,12954,12955,12946,43,12998,5651,5650,12999,0,1003,1004,1004,1003,12954,5630,5629,12955,43,12990,12999,13000,12993,0,2685,1003,1003,2749,12946,12955,12956,12949,43,12999,5650,5653,13000,0,1003,1004,1004,1003,12955,5629,5632,12956,43,12989,13001,13002,12998,0,2745,2753,1003,1003,12945,12957,12958,12954,43,13001,12953,12960,13002,0,2753,2711,1003,1003,12957,12909,12916,12958,43,12998,13002,5656,5651,0,1003,1003,1004,1004,12954,12958,5635,5630,43,13002,12960,5608,5656,0,1003,1003,1004,1004,12958,12916,5587,5635,43,12989,12992,13003,13001,0,2745,2746,2754,2753,12945,12948,12959,12957,43,12992,12996,13004,13003,0,2746,2750,2755,2754,12948,12952,12960,12959,43,13001,13003,12954,12953,0,2753,2754,2715,2711,12957,12959,12910,12909,43,13003,13004,12957,12954,0,2754,2755,2718,2715,12959,12960,12913,12910,43,13005,13006,13007,13008,0,2756,2759,2758,2757,12961,12962,12963,12964,43,13006,13009,13010,13007,0,2759,2761,2760,2758,12962,12965,12966,12963,43,13008,13007,12902,12901,0,2757,2758,2668,2669,12964,12963,12858,12857,43,13007,13010,12904,12902,0,2758,2760,2671,2668,12963,12966,12860,12858,43,13005,13011,13012,13006,0,2756,2763,2762,2759,12961,12967,12968,12962,43,13011,12996,12995,13012,0,2763,2750,2751,2762,12967,12952,12951,12968,43,13006,13012,13013,13009,0,2759,2762,2764,2761,12962,12968,12969,12965,43,13012,12995,12997,13013,0,2762,2751,2752,2764,12968,12951,12953,12969,43,13005,13014,13015,13011,0,2756,2766,2765,2763,12961,12970,12971,12967,43,13014,13016,13017,13015,0,2766,2768,2767,2765,12970,12972,12973,12971,43,13011,13015,13004,12996,0,2763,2765,2755,2750,12967,12971,12960,12952,43,13015,13017,12957,13004,0,2765,2767,2718,2755,12971,12973,12913,12960,43,13005,13008,13018,13014,0,2756,2757,2769,2766,12961,12964,12974,12970,43,13008,12901,12909,13018,0,2757,2669,2676,2769,12964,12857,12865,12974,43,13014,13018,13019,13016,0,2766,2769,2770,2768,12970,12974,12975,12972,43,13018,12909,12910,13019,0,2769,2676,2677,2770,12974,12865,12866,12975,43,13020,13021,13022,13023,0,2771,2774,2773,2772,12976,12977,12978,12979,43,13021,13024,13025,13022,0,2774,2776,2775,2773,12977,12980,12981,12978,43,13023,13022,12966,12956,0,2772,2773,2722,2716,12979,12978,12922,12912,43,13022,13025,12968,12966,0,2773,2775,2724,2722,12978,12981,12924,12922,43,13020,13026,13027,13021,0,2771,2778,2777,2774,12976,12982,12983,12977,43,13026,12939,12945,13027,0,2778,2702,2707,2777,12982,12895,12901,12983,43,13021,13027,13028,13024,0,2774,2777,2779,2776,12977,12983,12984,12980,43,13027,12945,12946,13028,0,2777,2707,2708,2779,12983,12901,12902,12984,43,13020,13029,13030,13026,0,2771,2781,2780,2778,12976,12985,12986,12982,43,13029,13016,13019,13030,0,2781,2768,2770,2780,12985,12972,12975,12986,43,13026,13030,12940,12939,0,2778,2780,2701,2702,12982,12986,12896,12895,43,13030,13019,12910,12940,0,2780,2770,2677,2701,12986,12975,12866,12896,43,13020,13023,13031,13029,0,2771,2772,2782,2781,12976,12979,12987,12985,43,13023,12956,12955,13031,0,2772,2716,2717,2782,12979,12912,12911,12987,43,13029,13031,13017,13016,0,2781,2782,2767,2768,12985,12987,12973,12972,43,13031,12955,12957,13017,0,2782,2717,2718,2767,12987,12911,12913,12973,43,13032,13033,13034,13035,0,2783,2786,2785,2784,12988,12989,12990,12991,43,13033,13036,13037,13034,0,2786,2788,2787,2785,12989,12992,12993,12990,43,13035,13034,12930,12920,0,2784,2785,2692,2686,12991,12990,12886,12876,43,13034,13037,12932,12930,0,2785,2787,2694,2692,12990,12993,12888,12886,43,13032,13038,13039,13033,0,2783,2790,2789,2786,12988,12994,12995,12989,43,13038,12881,12889,13039,0,2790,2649,2656,2789,12994,12837,12845,12995,43,13033,13039,13040,13036,0,2786,2789,2791,2788,12989,12995,12996,12992,43,13039,12889,12890,13040,0,2789,2656,2657,2791,12995,12845,12846,12996,43,13032,13041,13042,13038,0,2783,2793,2792,2790,12988,12997,12998,12994,43,13041,13043,13044,13042,0,2793,2795,2794,2792,12997,12999,13000,12998,43,13038,13042,12882,12881,0,2790,2792,2648,2649,12994,12998,12838,12837,43,13042,13044,12884,12882,0,2792,2794,2651,2648,12998,13000,12840,12838,43,13032,13035,13045,13041,0,2783,2784,2796,2793,12988,12991,13001,12997,43,13035,12920,12919,13045,0,2784,2686,2687,2796,12991,12876,12875,13001,43,13041,13045,13046,13043,0,2793,2796,2797,2795,12997,13001,13002,12999,43,13045,12919,12921,13046,0,2796,2687,2688,2797,13001,12875,12877,13002,43,13047,13048,13049,13050,0,2798,2801,2800,2799,13003,13004,13005,13006,43,13048,13051,13052,13049,0,2801,2803,2802,2800,13004,13007,13008,13005,43,13050,13049,13053,13054,0,2799,2800,2805,2804,13006,13005,13009,13010,43,13049,13052,13055,13053,0,2800,2802,2806,2805,13005,13008,13011,13009,43,13047,13056,13057,13048,0,2798,2808,2807,2801,13003,13012,13013,13004,43,13056,12977,12985,13057,0,2808,2734,2741,2807,13012,12933,12941,13013,43,13048,13057,13058,13051,0,2801,2807,2809,2803,13004,13013,13014,13007,43,13057,12985,12986,13058,0,2807,2741,2742,2809,13013,12941,12942,13014,43,13047,13059,13060,13056,0,2798,2811,2810,2808,13003,13015,13016,13012,43,13059,13061,13062,13060,0,2811,2813,2812,2810,13015,13017,13018,13016,43,13056,13060,12978,12977,0,2808,2810,2733,2734,13012,13016,12934,12933,43,13060,13062,12980,12978,0,2810,2812,2736,2733,13016,13018,12936,12934,43,13047,13050,13063,13059,0,2798,2799,2814,2811,13003,13006,13019,13015,43,13050,13054,13064,13063,0,2799,2804,2815,2814,13006,13010,13020,13019,43,13059,13063,13065,13061,0,2811,2814,2816,2813,13015,13019,13021,13017,43,13063,13064,13066,13065,0,2814,2815,2817,2816,13019,13020,13022,13021,43,13067,13068,13069,13070,0,2818,2821,2820,2819,13023,13024,13025,13026,43,13068,13071,13072,13069,0,2821,2823,2822,2820,13024,13027,13028,13025,43,13070,13069,5728,5727,0,2819,2820,2825,2824,13026,13025,5707,5706,43,13069,13072,5729,5728,0,2820,2822,2826,2825,13025,13028,5708,5707,43,13067,13073,13074,13068,0,2818,2828,2827,2821,13023,13029,13030,13024,43,13073,13054,13053,13074,0,2828,2804,2805,2827,13029,13010,13009,13030,43,13068,13074,13075,13071,0,2821,2827,2829,2823,13024,13030,13031,13027,43,13074,13053,13055,13075,0,2827,2805,2806,2829,13030,13009,13011,13031,43,13067,13076,13077,13073,0,2818,2831,2830,2828,13023,13032,13033,13029,43,13076,13078,13079,13077,0,2831,2833,2832,2830,13032,13034,13035,13033,43,13073,13077,13064,13054,0,2828,2830,2815,2804,13029,13033,13020,13010,43,13077,13079,13066,13064,0,2830,2832,2817,2815,13033,13035,13022,13020,43,13067,13070,13080,13076,0,2818,2819,1003,2831,13023,13026,13036,13032,43,13070,5727,5738,13080,0,2819,2824,1004,1003,13026,5706,5717,13036,43,13076,13080,13081,13078,0,2831,1003,1003,2833,13032,13036,13037,13034,43,13080,5738,5740,13081,0,1003,1004,1004,1003,13036,5717,5719,13037,43,13082,13083,13084,13085,0,2834,2837,2836,2835,13038,13039,13040,13041,43,13083,8075,8081,13084,0,2837,354,359,2836,13039,8054,8060,13040,43,13085,13084,13086,13087,0,2835,2836,2839,2838,13041,13040,13042,13043,43,13084,8081,8082,13086,0,2836,359,360,2839,13040,8060,8061,13042,43,13082,13088,13089,13083,0,2834,2841,2840,2837,13038,13044,13045,13039,43,13088,12983,12988,13089,0,2841,2740,2744,2840,13044,12939,12944,13045,43,13083,13089,8076,8075,0,2837,2840,353,354,13039,13045,8055,8054,43,13089,12988,8078,8076,0,2840,2744,356,353,13045,12944,8057,8055,43,13082,13090,13091,13088,0,2834,2843,2842,2841,13038,13046,13047,13044,43,13090,13051,13058,13091,0,2843,2803,2809,2842,13046,13007,13014,13047,43,13088,13091,12984,12983,0,2841,2842,2739,2740,13044,13047,12940,12939,43,13091,13058,12986,12984,0,2842,2809,2742,2739,13047,13014,12942,12940,43,13082,13085,13092,13090,0,2834,2835,2844,2843,13038,13041,13048,13046,43,13085,13087,13093,13092,0,2835,2838,2845,2844,13041,13043,13049,13048,43,13090,13092,13052,13051,0,2843,2844,2802,2803,13046,13048,13008,13007,43,13092,13093,13055,13052,0,2844,2845,2806,2802,13048,13049,13011,13008,43,13094,13095,13096,13097,0,2846,2849,2848,2847,13050,13051,13052,13053,43,13095,8913,8912,13096,0,2849,723,724,2848,13051,8892,8891,13052,43,13097,13096,5758,5757,0,2847,2848,2851,2850,13053,13052,5737,5736,43,13096,8912,1463,5758,0,2848,724,725,2851,13052,8891,1463,5737,43,13094,13098,13099,13095,0,2846,2853,2852,2849,13050,13054,13055,13051,43,13098,13087,13086,13099,0,2853,2838,2839,2852,13054,13043,13042,13055,43,13095,13099,8922,8913,0,2849,2852,736,723,13051,13055,8901,8892,43,13099,13086,8082,8922,0,2852,2839,360,736,13055,13042,8061,8901,43,13094,13100,13101,13098,0,2846,2855,2854,2853,13050,13056,13057,13054,43,13100,13071,13075,13101,0,2855,2823,2829,2854,13056,13027,13031,13057,43,13098,13101,13093,13087,0,2853,2854,2845,2838,13054,13057,13049,13043,43,13101,13075,13055,13093,0,2854,2829,2806,2845,13057,13031,13011,13049,43,13094,13097,13102,13100,0,2846,2847,2856,2855,13050,13053,13058,13056,43,13097,5757,5764,13102,0,2847,2850,2857,2856,13053,5736,5743,13058,43,13100,13102,13072,13071,0,2855,2856,2822,2823,13056,13058,13028,13027,43,13102,5764,5729,13072,0,2856,2857,2826,2822,13058,5743,5708,13028,43,13103,13104,13105,13106,0,2858,2861,2860,2859,13059,13060,13061,13062,43,13104,13107,13108,13105,0,2861,2863,2862,2860,13060,13063,13064,13061,43,13106,13105,13109,13110,0,2859,2860,2865,2864,13062,13061,13065,13066,43,13105,13108,13111,13109,0,2860,2862,2866,2865,13061,13064,13067,13065,43,13103,13112,13113,13104,0,2858,2868,2867,2861,13059,13068,13069,13060,43,13112,9640,9639,13113,0,2868,974,975,2867,13068,9619,9618,13069,43,13104,13113,13114,13107,0,2861,2867,2869,2863,13060,13069,13070,13063,43,13113,9639,9641,13114,0,2867,975,976,2869,13069,9618,9620,13070,43,13103,13115,13116,13112,0,2858,2871,2870,2868,13059,13071,13072,13068,43,13115,13117,13118,13116,0,2871,2873,2872,2870,13071,13073,13074,13072,43,13112,13116,9655,9640,0,2868,2870,990,974,13068,13072,9634,9619,43,13116,13118,9657,9655,0,2870,2872,992,990,13072,13074,9636,9634,43,13103,13106,13119,13115,0,2858,2859,2874,2871,13059,13062,13075,13071,43,13106,13110,13120,13119,0,2859,2864,2875,2874,13062,13066,13076,13075,43,13115,13119,13121,13117,0,2871,2874,2876,2873,13071,13075,13077,13073,43,13119,13120,13122,13121,0,2874,2875,2877,2876,13075,13076,13078,13077,43,13123,13124,13125,13126,0,2878,2881,2880,2879,13079,13080,13081,13082,43,13124,13117,13121,13125,0,2881,2873,2876,2880,13080,13073,13077,13081,43,13126,13125,13127,13128,0,2879,2880,2883,2882,13082,13081,13083,13084,43,13125,13121,13122,13127,0,2880,2876,2877,2883,13081,13077,13078,13083,43,13123,13129,13130,13124,0,2878,2885,2884,2881,13079,13085,13086,13080,43,13129,9663,9662,13130,0,2885,997,998,2884,13085,9642,9641,13086,43,13124,13130,13118,13117,0,2881,2884,2872,2873,13080,13086,13074,13073,43,13130,9662,9657,13118,0,2884,998,992,2872,13086,9641,9636,13074,43,13123,13131,13132,13129,0,2878,1003,1003,2885,13079,13087,13088,13085,43,13131,5796,5795,13132,0,1003,1004,1004,1003,13087,5775,5774,13088,43,13129,13132,9672,9663,0,2885,1003,1003,997,13085,13088,9651,9642,43,13132,5795,2260,9672,0,1003,1004,1004,1003,13088,5774,2260,9651,43,13123,13126,13133,13131,0,2878,2879,1003,1003,13079,13082,13089,13087,43,13126,13128,13134,13133,0,2879,2882,1003,1003,13082,13084,13090,13089,43,13131,13133,5799,5796,0,1003,1003,1004,1004,13087,13089,5778,5775,43,13133,13134,5800,5799,0,1003,1003,1004,1004,13089,13090,5779,5778,43,13135,13136,13137,13138,0,2886,2889,2888,2887,13091,13092,13093,13094,43,13136,12887,12892,13137,0,2889,2655,2659,2888,13092,12843,12848,13093,43,13138,13137,10171,10170,0,2887,2888,1395,1396,13094,13093,10150,10149,43,13137,12892,10173,10171,0,2888,2659,1398,1395,13093,12848,10152,10150,43,13135,13139,13140,13136,0,2886,2891,2890,2889,13091,13095,13096,13092,43,13139,13141,13142,13140,0,2891,2893,2892,2890,13095,13097,13098,13096,43,13136,13140,12888,12887,0,2889,2890,2654,2655,13092,13096,12844,12843,43,13140,13142,12890,12888,0,2890,2892,2657,2654,13096,13098,12846,12844,43,13135,13143,13144,13139,0,2886,2895,2894,2891,13091,13099,13100,13095,43,13143,12973,12979,13144,0,2895,2730,2735,2894,13099,12929,12935,13100,43,13139,13144,13145,13141,0,2891,2894,2896,2893,13095,13100,13101,13097,43,13144,12979,12980,13145,0,2894,2735,2736,2896,13100,12935,12936,13101,43,13135,13138,13146,13143,0,2886,2887,2897,2895,13091,13094,13102,13099,43,13138,10170,10178,13146,0,2887,1396,1403,2897,13094,10149,10157,13102,43,13143,13146,12974,12973,0,2895,2897,2729,2730,13099,13102,12930,12929,43,13146,10178,10179,12974,0,2897,1403,1404,2729,13102,10157,10158,12930,43,13147,13148,13149,13150,0,2898,2901,2900,2899,13103,13104,13105,13106,43,13148,12927,12931,13149,0,2901,2690,2693,2900,13104,12883,12887,13105,43,13150,13149,13151,13152,0,2899,2900,2903,2902,13106,13105,13107,13108,43,13149,12931,12932,13151,0,2900,2693,2694,2903,13105,12887,12888,13107,43,13147,13153,13154,13148,0,2898,1003,1003,2901,13103,13109,13110,13104,43,13153,5822,5821,13154,0,1003,1004,1004,1003,13109,5801,5800,13110,43,13148,13154,12928,12927,0,2901,1003,1003,2690,13104,13110,12884,12883,43,13154,5821,5573,12928,0,1003,1004,1004,1003,13110,5800,5552,12884,43,13147,13155,13156,13153,0,2898,2904,1003,1003,13103,13111,13112,13109,43,13155,13078,13081,13156,0,2904,2833,1003,1003,13111,13034,13037,13112,43,13153,13156,5825,5822,0,1003,1003,1004,1004,13109,13112,5804,5801,43,13156,13081,5740,5825,0,1003,1003,1004,1004,13112,13037,5719,5804,43,13147,13150,13157,13155,0,2898,2899,2905,2904,13103,13106,13113,13111,43,13150,13152,13158,13157,0,2899,2902,2906,2905,13106,13108,13114,13113,43,13155,13157,13079,13078,0,2904,2905,2832,2833,13111,13113,13035,13034,43,13157,13158,13066,13079,0,2905,2906,2817,2832,13113,13114,13022,13035,43,13159,13160,13161,13162,0,2907,2910,2909,2908,13115,13116,13117,13118,43,13160,12943,12948,13161,0,2910,2706,2710,2909,13116,12899,12904,13117,43,13162,13161,10216,10215,0,2908,2909,1440,1441,13118,13117,10195,10194,43,13161,12948,10218,10216,0,2909,2710,1443,1440,13117,12904,10197,10195,43,13159,13163,13164,13160,0,2907,2912,2911,2910,13115,13119,13120,13116,43,13163,13165,13166,13164,0,2912,2914,2913,2911,13119,13121,13122,13120,43,13160,13164,12944,12943,0,2910,2911,2705,2706,13116,13120,12900,12899,43,13164,13166,12946,12944,0,2911,2913,2708,2705,13120,13122,12902,12900,43,13159,13167,13168,13163,0,2907,2916,2915,2912,13115,13123,13124,13119,43,13167,12877,12883,13168,0,2916,2645,2650,2915,13123,12833,12839,13124,43,13163,13168,13169,13165,0,2912,2915,2917,2914,13119,13124,13125,13121,43,13168,12883,12884,13169,0,2915,2650,2651,2917,13124,12839,12840,13125,43,13159,13162,13170,13167,0,2907,2908,2918,2916,13115,13118,13126,13123,43,13162,10215,10223,13170,0,2908,1441,1448,2918,13118,10194,10202,13126,43,13167,13170,12878,12877,0,2916,2918,2644,2645,13123,13126,12834,12833,43,13170,10223,10224,12878,0,2918,1448,1449,2644,13126,10202,10203,12834,43,13171,13172,13173,13174,0,2919,2922,2921,2920,13127,13128,13129,13130,43,13172,12963,12967,13173,0,2922,2720,2723,2921,13128,12919,12923,13129,43,13174,13173,13175,13176,0,2920,2921,2924,2923,13130,13129,13131,13132,43,13173,12967,12968,13175,0,2921,2723,2724,2924,13129,12923,12924,13131,43,13171,13177,13178,13172,0,2919,1003,1003,2922,13127,13133,13134,13128,43,13177,5849,5848,13178,0,1003,1004,1004,1003,13133,5828,5827,13134,43,13172,13178,12964,12963,0,2922,1003,1003,2720,13128,13134,12920,12919,43,13178,5848,5614,12964,0,1003,1004,1004,1003,13134,5827,5593,12920,43,13171,13179,13180,13177,0,2919,2925,1003,1003,13127,13135,13136,13133,43,13179,12917,12924,13180,0,2925,2685,1003,1003,13135,12873,12880,13136,43,13177,13180,5852,5849,0,1003,1003,1004,1004,13133,13136,5831,5828,43,13180,12924,5567,5852,0,1003,1003,1004,1004,13136,12880,5546,5831,43,13171,13174,13181,13179,0,2919,2920,2926,2925,13127,13130,13137,13135,43,13174,13176,13182,13181,0,2920,2923,2927,2926,13130,13132,13138,13137,43,13179,13181,12918,12917,0,2925,2926,2684,2685,13135,13137,12874,12873,43,13181,13182,12921,12918,0,2926,2927,2688,2684,13137,13138,12877,12874,43,13183,13184,13185,13186,0,2928,2931,2930,2929,13139,13140,13141,13142,43,13184,13024,13028,13185,0,2931,2776,2779,2930,13140,12980,12984,13141,43,13186,13185,13166,13165,0,2929,2930,2913,2914,13142,13141,13122,13121,43,13185,13028,12946,13166,0,2930,2779,2708,2913,13141,12984,12902,13122,43,13183,13187,13188,13184,0,2928,2933,2932,2931,13139,13143,13144,13140,43,13187,13176,13175,13188,0,2933,2923,2924,2932,13143,13132,13131,13144,43,13184,13188,13025,13024,0,2931,2932,2775,2776,13140,13144,12981,12980,43,13188,13175,12968,13025,0,2932,2924,2724,2775,13144,13131,12924,12981,43,13183,13189,13190,13187,0,2928,2935,2934,2933,13139,13145,13146,13143,43,13189,13043,13046,13190,0,2935,2795,2797,2934,13145,12999,13002,13146,43,13187,13190,13182,13176,0,2933,2934,2927,2923,13143,13146,13138,13132,43,13190,13046,12921,13182,0,2934,2797,2688,2927,13146,13002,12877,13138,43,13183,13186,13191,13189,0,2928,2929,2936,2935,13139,13142,13147,13145,43,13186,13165,13169,13191,0,2929,2914,2917,2936,13142,13121,13125,13147,43,13189,13191,13044,13043,0,2935,2936,2794,2795,13145,13147,13000,12999,43,13191,13169,12884,13044,0,2936,2917,2651,2794,13147,13125,12840,13000,43,13192,13193,13194,13195,0,2937,2940,2939,2938,13148,13149,13150,13151,43,13193,13036,13040,13194,0,2940,2788,2791,2939,13149,12992,12996,13150,43,13195,13194,13142,13141,0,2938,2939,2892,2893,13151,13150,13098,13097,43,13194,13040,12890,13142,0,2939,2791,2657,2892,13150,12996,12846,13098,43,13192,13196,13197,13193,0,2937,2942,2941,2940,13148,13152,13153,13149,43,13196,13152,13151,13197,0,2942,2902,2903,2941,13152,13108,13107,13153,43,13193,13197,13037,13036,0,2940,2941,2787,2788,13149,13153,12993,12992,43,13197,13151,12932,13037,0,2941,2903,2694,2787,13153,13107,12888,12993,43,13192,13198,13199,13196,0,2937,2944,2943,2942,13148,13154,13155,13152,43,13198,13061,13065,13199,0,2944,2813,2816,2943,13154,13017,13021,13155,43,13196,13199,13158,13152,0,2942,2943,2906,2902,13152,13155,13114,13108,43,13199,13065,13066,13158,0,2943,2816,2817,2906,13155,13021,13022,13114,43,13192,13195,13200,13198,0,2937,2938,2945,2944,13148,13151,13156,13154,43,13195,13141,13145,13200,0,2938,2893,2896,2945,13151,13097,13101,13156,43,13198,13200,13062,13061,0,2944,2945,2812,2813,13154,13156,13018,13017,43,13200,13145,12980,13062,0,2945,2896,2736,2812,13156,13101,12936,13018,43,13201,13202,13203,13204,0,2946,2949,2948,2947,13157,13158,13159,13160,43,13202,12897,12903,13203,0,2949,2665,2670,2948,13158,12853,12859,13159,43,13204,13203,13205,13206,0,2947,2948,2951,2950,13160,13159,13161,13162,43,13203,12903,12904,13205,0,2948,2670,2671,2951,13159,12859,12860,13161,43,13201,13207,13208,13202,0,2946,2953,2952,2949,13157,13163,13164,13158,43,13207,10967,10966,13208,0,2953,2105,2106,2952,13163,10946,10945,13164,43,13202,13208,12898,12897,0,2949,2952,2664,2665,13158,13164,12854,12853,43,13208,10966,10195,12898,0,2952,2106,1420,2664,13164,10945,10174,12854,43,13201,13209,13210,13207,0,2946,2955,2954,2953,13157,13165,13166,13163,43,13209,13107,13114,13210,0,2955,2863,2869,2954,13165,13063,13070,13166,43,13207,13210,10973,10967,0,2953,2954,2112,2105,13163,13166,10952,10946,43,13210,13114,9641,10973,0,2954,2869,976,2112,13166,13070,9620,10952,43,13201,13204,13211,13209,0,2946,2947,2956,2955,13157,13160,13167,13165,43,13204,13206,13212,13211,0,2947,2950,2957,2956,13160,13162,13168,13167,43,13209,13211,13108,13107,0,2955,2956,2862,2863,13165,13167,13064,13063,43,13211,13212,13111,13108,0,2956,2957,2866,2862,13167,13168,13067,13064,43,13213,13214,13215,13216,0,2958,2961,2960,2959,13169,13170,13171,13172,43,13214,13009,13013,13215,0,2961,2761,2764,2960,13170,12965,12969,13171,43,13216,13215,13217,13218,0,2959,2960,2963,2962,13172,13171,13173,13174,43,13215,13013,12997,13217,0,2960,2764,2752,2963,13171,12969,12953,13173,43,13213,13219,13220,13214,0,2958,2965,2964,2961,13169,13175,13176,13170,43,13219,13206,13205,13220,0,2965,2950,2951,2964,13175,13162,13161,13176,43,13214,13220,13010,13009,0,2961,2964,2760,2761,13170,13176,12966,12965,43,13220,13205,12904,13010,0,2964,2951,2671,2760,13176,13161,12860,12966,43,13213,13221,13222,13219,0,2958,2967,2966,2965,13169,13177,13178,13175,43,13221,13110,13109,13222,0,2967,2864,2865,2966,13177,13066,13065,13178,43,13219,13222,13212,13206,0,2965,2966,2957,2950,13175,13178,13168,13162,43,13222,13109,13111,13212,0,2966,2865,2866,2957,13178,13065,13067,13168,43,13213,13216,13223,13221,0,2958,2959,2968,2967,13169,13172,13179,13177,43,13216,13218,13224,13223,0,2959,2962,2969,2968,13172,13174,13180,13179,43,13221,13223,13120,13110,0,2967,2968,2875,2864,13177,13179,13076,13066,43,13223,13224,13122,13120,0,2968,2969,2877,2875,13179,13180,13078,13076,43,13225,13226,13227,13228,0,2970,2971,1003,1003,13181,13182,13183,13184,43,13226,12993,13000,13227,0,2971,2749,1003,1003,13182,12949,12956,13183,43,13228,13227,5902,5901,0,1003,1003,1004,1004,13184,13183,5881,5880,43,13227,13000,5653,5902,0,1003,1003,1004,1004,13183,12956,5632,5881,43,13225,13229,13230,13226,0,2970,2973,2972,2971,13181,13185,13186,13182,43,13229,13218,13217,13230,0,2973,2962,2963,2972,13185,13174,13173,13186,43,13226,13230,12994,12993,0,2971,2972,2748,2749,13182,13186,12950,12949,43,13230,13217,12997,12994,0,2972,2963,2752,2748,13186,13173,12953,12950,43,13225,13231,13232,13229,0,2970,2975,2974,2973,13181,13187,13188,13185,43,13231,13128,13127,13232,0,2975,2882,2883,2974,13187,13084,13083,13188,43,13229,13232,13224,13218,0,2973,2974,2969,2962,13185,13188,13180,13174,43,13232,13127,13122,13224,0,2974,2883,2877,2969,13188,13083,13078,13180,43,13225,13228,13233,13231,0,2970,1003,1003,2975,13181,13184,13189,13187,43,13228,5901,5908,13233,0,1003,1004,1004,1003,13184,5880,5887,13189,43,13231,13233,13134,13128,0,2975,1003,1003,2882,13187,13189,13090,13084,43,13233,5908,5800,13134,0,1003,1004,1004,1003,13189,5887,5779,13090,43,13234,13235,13236,13237,0,386,62,62,386,13190,13191,13192,13193,43,13235,13238,13239,13236,0,62,16,16,62,13191,13194,13195,13192,43,13237,13236,8277,8276,0,386,62,62,386,13193,13192,8256,8255,43,13236,13239,8279,8277,0,62,16,16,62,13192,13195,8258,8256,43,13234,13240,13241,13235,0,386,386,62,62,13190,13196,13197,13191,43,13240,8109,8118,13241,0,386,386,62,62,13196,8088,8097,13197,43,13235,13241,13242,13238,0,62,62,16,16,13191,13197,13198,13194,43,13241,8118,8119,13242,0,62,62,16,16,13197,8097,8098,13198,43,13234,13243,13244,13240,0,386,385,385,386,13190,13199,13200,13196,43,13243,13245,13246,13244,0,385,388,388,385,13199,13201,13202,13200,43,13240,13244,8110,8109,0,386,385,385,386,13196,13200,8089,8088,43,13244,13246,8113,8110,0,385,388,388,385,13200,13202,8092,8089,43,13234,13237,13247,13243,0,386,386,385,385,13190,13193,13203,13199,43,13237,8276,8284,13247,0,386,386,385,385,13193,8255,8263,13203,43,13243,13247,13248,13245,0,385,385,388,388,13199,13203,13204,13201,43,13247,8284,8285,13248,0,385,385,388,388,13203,8263,8264,13204,43,13249,13250,13251,13252,0,403,405,405,403,13205,13206,13207,13208,43,13250,13245,13248,13251,0,405,388,388,405,13206,13201,13204,13207,43,13252,13251,8305,8304,0,403,405,405,403,13208,13207,8284,8283,43,13251,13248,8285,8305,0,405,388,388,405,13207,13204,8264,8284,43,13249,13253,13254,13250,0,403,403,405,405,13205,13209,13210,13206,43,13253,8134,8141,13254,0,403,403,405,405,13209,8113,8120,13210,43,13250,13254,13246,13245,0,405,405,388,388,13206,13210,13202,13201,43,13254,8141,8113,13246,0,405,405,388,388,13210,8120,8092,13202,43,13249,13255,13256,13253,0,403,66,66,403,13205,13211,13212,13209,43,13255,13257,13258,13256,0,66,18,18,66,13211,13213,13214,13212,43,13253,13256,8135,8134,0,403,66,66,403,13209,13212,8114,8113,43,13256,13258,8138,8135,0,66,18,18,66,13212,13214,8117,8114,43,13249,13252,13259,13255,0,403,403,66,66,13205,13208,13215,13211,43,13252,8304,8308,13259,0,403,403,66,66,13208,8283,8287,13215,43,13255,13259,13260,13257,0,66,66,18,18,13211,13215,13216,13213,43,13259,8308,8309,13260,0,66,66,18,18,13215,8287,8288,13216,43,13261,13262,13263,13264,0,418,421,421,418,13217,13218,13219,13220,43,13262,13257,13260,13263,0,421,18,18,421,13218,13213,13216,13219,43,13264,13263,8325,8324,0,418,421,421,418,13220,13219,8304,8303,43,13263,13260,8309,8325,0,421,18,18,421,13219,13216,8288,8304,43,13261,13265,13266,13262,0,418,418,421,421,13217,13221,13222,13218,43,13265,8154,8161,13266,0,418,418,421,421,13221,8133,8140,13222,43,13262,13266,13258,13257,0,421,421,18,18,13218,13222,13214,13213,43,13266,8161,8138,13258,0,421,421,18,18,13222,8140,8117,13214,43,13261,13267,13268,13265,0,418,417,417,418,13217,13223,13224,13221,43,13267,13269,13270,13268,0,417,420,420,417,13223,13225,13226,13224,43,13265,13268,8155,8154,0,418,417,417,418,13221,13224,8134,8133,43,13268,13270,8158,8155,0,417,420,420,417,13224,13226,8137,8134,43,13261,13264,13271,13267,0,418,418,417,417,13217,13220,13227,13223,43,13264,8324,8328,13271,0,418,418,417,417,13220,8303,8307,13227,43,13267,13271,13272,13269,0,417,417,420,420,13223,13227,13228,13225,43,13271,8328,8329,13272,0,417,417,420,420,13227,8307,8308,13228,43,13273,13274,13275,13276,0,433,435,435,433,13229,13230,13231,13232,43,13274,13269,13272,13275,0,435,420,420,435,13230,13225,13228,13231,43,13276,13275,8339,8338,0,433,435,435,433,13232,13231,8318,8317,43,13275,13272,8329,8339,0,435,420,420,435,13231,13228,8308,8318,43,13273,13277,13278,13274,0,433,433,435,435,13229,13233,13234,13230,43,13277,8174,8181,13278,0,433,433,435,435,13233,8153,8160,13234,43,13274,13278,13270,13269,0,435,435,420,420,13230,13234,13226,13225,43,13278,8181,8158,13270,0,435,435,420,420,13234,8160,8137,13226,43,13273,13279,13280,13277,0,433,9,9,433,13229,13235,13236,13233,43,13279,13281,13282,13280,0,9,10,10,9,13235,13237,13238,13236,43,13277,13280,8175,8174,0,433,9,9,433,13233,13236,8154,8153,43,13280,13282,8178,8175,0,9,10,10,9,13236,13238,8157,8154,43,13273,13276,13283,13279,0,433,433,9,9,13229,13232,13239,13235,43,13276,8338,8344,13283,0,433,433,9,9,13232,8317,8323,13239,43,13279,13283,13284,13281,0,9,9,10,10,13235,13239,13240,13237,43,13283,8344,8345,13284,0,9,9,10,10,13239,8323,8324,13240,43,13285,13286,13287,13288,0,448,35,35,448,13241,13242,13243,13244,43,13286,13281,13284,13287,0,35,10,10,35,13242,13237,13240,13243,43,13288,13287,8359,8358,0,448,35,35,448,13244,13243,8338,8337,43,13287,13284,8345,8359,0,35,10,10,35,13243,13240,8324,8338,43,13285,13289,13290,13286,0,448,448,35,35,13241,13245,13246,13242,43,13289,8194,8201,13290,0,448,448,35,35,13245,8173,8180,13246,43,13286,13290,13282,13281,0,35,35,10,10,13242,13246,13238,13237,43,13290,8201,8178,13282,0,35,35,10,10,13246,8180,8157,13238,43,13285,13291,13292,13289,0,448,447,447,448,13241,13247,13248,13245,43,13291,13293,13294,13292,0,447,450,450,447,13247,13249,13250,13248,43,13289,13292,8195,8194,0,448,447,447,448,13245,13248,8174,8173,43,13292,13294,8198,8195,0,447,450,450,447,13248,13250,8177,8174,43,13285,13288,13295,13291,0,448,448,447,447,13241,13244,13251,13247,43,13288,8358,8364,13295,0,448,448,447,447,13244,8337,8343,13251,43,13291,13295,13296,13293,0,447,447,450,450,13247,13251,13252,13249,43,13295,8364,8365,13296,0,447,447,450,450,13251,8343,8344,13252,43,13297,13298,13299,13300,0,462,464,464,462,13253,13254,13255,13256,43,13298,13293,13296,13299,0,464,450,450,464,13254,13249,13252,13255,43,13300,13299,8379,8378,0,462,464,464,462,13256,13255,8358,8357,43,13299,13296,8365,8379,0,464,450,450,464,13255,13252,8344,8358,43,13297,13301,13302,13298,0,462,462,464,464,13253,13257,13258,13254,43,13301,8214,8221,13302,0,462,462,464,464,13257,8193,8200,13258,43,13298,13302,13294,13293,0,464,464,450,450,13254,13258,13250,13249,43,13302,8221,8198,13294,0,464,464,450,450,13258,8200,8177,13250,43,13297,13303,13304,13301,0,462,22,22,462,13253,13259,13260,13257,43,13303,13305,13306,13304,0,22,23,23,22,13259,13261,13262,13260,43,13301,13304,8215,8214,0,462,22,22,462,13257,13260,8194,8193,43,13304,13306,8218,8215,0,22,23,23,22,13260,13262,8197,8194,43,13297,13300,13307,13303,0,462,462,22,22,13253,13256,13263,13259,43,13300,8378,8384,13307,0,462,462,22,22,13256,8357,8363,13263,43,13303,13307,13308,13305,0,22,22,23,23,13259,13263,13264,13261,43,13307,8384,8385,13308,0,22,22,23,23,13263,8363,8364,13264,43,13309,13310,13311,13312,0,477,57,57,477,13265,13266,13267,13268,43,13310,13305,13308,13311,0,57,23,23,57,13266,13261,13264,13267,43,13312,13311,8405,8404,0,477,57,57,477,13268,13267,8384,8383,43,13311,13308,8385,8405,0,57,23,23,57,13267,13264,8364,8384,43,13309,13313,13314,13310,0,477,477,57,57,13265,13269,13270,13266,43,13313,8234,8241,13314,0,477,477,57,57,13269,8213,8220,13270,43,13310,13314,13306,13305,0,57,57,23,23,13266,13270,13262,13261,43,13314,8241,8218,13306,0,57,57,23,23,13270,8220,8197,13262,43,13309,13315,13316,13313,0,477,476,476,477,13265,13271,13272,13269,43,13315,13317,13318,13316,0,476,479,479,476,13271,13273,13274,13272,43,13313,13316,8235,8234,0,477,476,476,477,13269,13272,8214,8213,43,13316,13318,8238,8235,0,476,479,479,476,13272,13274,8217,8214,43,13309,13312,13319,13315,0,477,477,476,476,13265,13268,13275,13271,43,13312,8404,8408,13319,0,477,477,476,476,13268,8383,8387,13275,43,13315,13319,13320,13317,0,476,476,479,479,13271,13275,13276,13273,43,13319,8408,8409,13320,0,476,476,479,479,13275,8387,8388,13276,43,13321,13322,13323,13324,0,491,492,492,491,13277,13278,13279,13280,43,13322,13317,13320,13323,0,492,479,479,492,13278,13273,13276,13279,43,13324,13323,8422,8421,0,491,492,492,491,13280,13279,8401,8400,43,13323,13320,8409,8422,0,492,479,479,492,13279,13276,8388,8401,43,13321,13325,13326,13322,0,491,491,492,492,13277,13281,13282,13278,43,13325,8254,8258,13326,0,491,491,492,492,13281,8233,8237,13282,43,13322,13326,13318,13317,0,492,492,479,479,13278,13282,13274,13273,43,13326,8258,8238,13318,0,492,492,479,479,13282,8237,8217,13274,43,13321,13327,13328,13325,0,491,15,15,491,13277,13283,13284,13281,43,13327,13238,13242,13328,0,15,16,16,15,13283,13194,13198,13284,43,13325,13328,8255,8254,0,491,15,15,491,13281,13284,8234,8233,43,13328,13242,8119,8255,0,15,16,16,15,13284,13198,8098,8234,43,13321,13324,13329,13327,0,491,491,15,15,13277,13280,13285,13283,43,13324,8421,8424,13329,0,491,491,15,15,13280,8400,8403,13285,43,13327,13329,13239,13238,0,15,15,16,16,13283,13285,13195,13194,43,13329,8424,8279,13239,0,15,15,16,16,13285,8403,8258,13195,43,13330,13331,13332,13333,0,386,385,385,386,13286,13287,13288,13289,43,13331,13334,13335,13332,0,385,388,388,385,13287,13290,13291,13288,43,13333,13332,8967,8957,0,386,385,385,386,13289,13288,8946,8936,43,13332,13335,8969,8967,0,385,388,388,385,13288,13291,8948,8946,43,13330,13336,13337,13331,0,386,386,385,385,13286,13292,13293,13287,43,13336,8436,8444,13337,0,386,386,385,385,13292,8415,8423,13293,43,13331,13337,13338,13334,0,385,385,388,388,13287,13293,13294,13290,43,13337,8444,8445,13338,0,385,385,388,388,13293,8423,8424,13294,43,13330,13339,13340,13336,0,386,62,62,386,13286,13295,13296,13292,43,13339,13341,13342,13340,0,62,16,16,62,13295,13297,13298,13296,43,13336,13340,8437,8436,0,386,62,62,386,13292,13296,8416,8415,43,13340,13342,8439,8437,0,62,16,16,62,13296,13298,8418,8416,43,13330,13333,13343,13339,0,386,386,62,62,13286,13289,13299,13295,43,13333,8957,8956,13343,0,386,386,62,62,13289,8936,8935,13299,43,13339,13343,13344,13341,0,62,62,16,16,13295,13299,13300,13297,43,13343,8956,8958,13344,0,62,62,16,16,13299,8935,8937,13300,43,13345,13346,13347,13348,0,403,66,66,403,13301,13302,13303,13304,43,13346,13349,13350,13347,0,66,18,18,66,13302,13305,13306,13303,43,13348,13347,8981,8980,0,403,66,66,403,13304,13303,8960,8959,43,13347,13350,8983,8981,0,66,18,18,66,13303,13306,8962,8960,43,13345,13351,13352,13346,0,403,403,66,66,13301,13307,13308,13302,43,13351,8464,8468,13352,0,403,403,66,66,13307,8443,8447,13308,43,13346,13352,13353,13349,0,66,66,18,18,13302,13308,13309,13305,43,13352,8468,8469,13353,0,66,66,18,18,13308,8447,8448,13309,43,13345,13354,13355,13351,0,403,405,405,403,13301,13310,13311,13307,43,13354,13334,13338,13355,0,405,388,388,405,13310,13290,13294,13311,43,13351,13355,8465,8464,0,403,405,405,403,13307,13311,8444,8443,43,13355,13338,8445,8465,0,405,388,388,405,13311,13294,8424,8444,43,13345,13348,13356,13354,0,403,403,405,405,13301,13304,13312,13310,43,13348,8980,8985,13356,0,403,403,405,405,13304,8959,8964,13312,43,13354,13356,13335,13334,0,405,405,388,388,13310,13312,13291,13290,43,13356,8985,8969,13335,0,405,405,388,388,13312,8964,8948,13291,43,13357,13358,13359,13360,0,418,417,417,418,13313,13314,13315,13316,43,13358,13361,13362,13359,0,417,420,420,417,13314,13317,13318,13315,43,13360,13359,8997,8996,0,418,417,417,418,13316,13315,8976,8975,43,13359,13362,8999,8997,0,417,420,420,417,13315,13318,8978,8976,43,13357,13363,13364,13358,0,418,418,417,417,13313,13319,13320,13314,43,13363,8484,8488,13364,0,418,418,417,417,13319,8463,8467,13320,43,13358,13364,13365,13361,0,417,417,420,420,13314,13320,13321,13317,43,13364,8488,8489,13365,0,417,417,420,420,13320,8467,8468,13321,43,13357,13366,13367,13363,0,418,421,421,418,13313,13322,13323,13319,43,13366,13349,13353,13367,0,421,18,18,421,13322,13305,13309,13323,43,13363,13367,8485,8484,0,418,421,421,418,13319,13323,8464,8463,43,13367,13353,8469,8485,0,421,18,18,421,13323,13309,8448,8464,43,13357,13360,13368,13366,0,418,418,421,421,13313,13316,13324,13322,43,13360,8996,9001,13368,0,418,418,421,421,13316,8975,8980,13324,43,13366,13368,13350,13349,0,421,421,18,18,13322,13324,13306,13305,43,13368,9001,8983,13350,0,421,421,18,18,13324,8980,8962,13306,43,13369,13370,13371,13372,0,433,9,9,433,13325,13326,13327,13328,43,13370,13373,13374,13371,0,9,10,10,9,13326,13329,13330,13327,43,13372,13371,9015,9007,0,433,9,9,433,13328,13327,8994,8986,43,13371,13374,9017,9015,0,9,10,10,9,13327,13330,8996,8994,43,13369,13375,13376,13370,0,433,433,9,9,13325,13331,13332,13326,43,13375,8498,8504,13376,0,433,433,9,9,13331,8477,8483,13332,43,13370,13376,13377,13373,0,9,9,10,10,13326,13332,13333,13329,43,13376,8504,8505,13377,0,9,9,10,10,13332,8483,8484,13333,43,13369,13378,13379,13375,0,433,435,435,433,13325,13334,13335,13331,43,13378,13361,13365,13379,0,435,420,420,435,13334,13317,13321,13335,43,13375,13379,8499,8498,0,433,435,435,433,13331,13335,8478,8477,43,13379,13365,8489,8499,0,435,420,420,435,13335,13321,8468,8478,43,13369,13372,13380,13378,0,433,433,435,435,13325,13328,13336,13334,43,13372,9007,9006,13380,0,433,433,435,435,13328,8986,8985,13336,43,13378,13380,13362,13361,0,435,435,420,420,13334,13336,13318,13317,43,13380,9006,8999,13362,0,435,435,420,420,13336,8985,8978,13318,43,13381,13382,13383,13384,0,448,447,447,448,13337,13338,13339,13340,43,13382,13385,13386,13383,0,447,450,450,447,13338,13341,13342,13339,43,13384,13383,9031,9023,0,448,447,447,448,13340,13339,9010,9002,43,13383,13386,9033,9031,0,447,450,450,447,13339,13342,9012,9010,43,13381,13387,13388,13382,0,448,448,447,447,13337,13343,13344,13338,43,13387,8518,8524,13388,0,448,448,447,447,13343,8497,8503,13344,43,13382,13388,13389,13385,0,447,447,450,450,13338,13344,13345,13341,43,13388,8524,8525,13389,0,447,447,450,450,13344,8503,8504,13345,43,13381,13390,13391,13387,0,448,35,35,448,13337,13346,13347,13343,43,13390,13373,13377,13391,0,35,10,10,35,13346,13329,13333,13347,43,13387,13391,8519,8518,0,448,35,35,448,13343,13347,8498,8497,43,13391,13377,8505,8519,0,35,10,10,35,13347,13333,8484,8498,43,13381,13384,13392,13390,0,448,448,35,35,13337,13340,13348,13346,43,13384,9023,9022,13392,0,448,448,35,35,13340,9002,9001,13348,43,13390,13392,13374,13373,0,35,35,10,10,13346,13348,13330,13329,43,13392,9022,9017,13374,0,35,35,10,10,13348,9001,8996,13330,43,13393,13394,13395,13396,0,462,22,22,462,13349,13350,13351,13352,43,13394,13397,13398,13395,0,22,23,23,22,13350,13353,13354,13351,43,13396,13395,9047,9039,0,462,22,22,462,13352,13351,9026,9018,43,13395,13398,9049,9047,0,22,23,23,22,13351,13354,9028,9026,43,13393,13399,13400,13394,0,462,462,22,22,13349,13355,13356,13350,43,13399,8538,8544,13400,0,462,462,22,22,13355,8517,8523,13356,43,13394,13400,13401,13397,0,22,22,23,23,13350,13356,13357,13353,43,13400,8544,8545,13401,0,22,22,23,23,13356,8523,8524,13357,43,13393,13402,13403,13399,0,462,464,464,462,13349,13358,13359,13355,43,13402,13385,13389,13403,0,464,450,450,464,13358,13341,13345,13359,43,13399,13403,8539,8538,0,462,464,464,462,13355,13359,8518,8517,43,13403,13389,8525,8539,0,464,450,450,464,13359,13345,8504,8518,43,13393,13396,13404,13402,0,462,462,464,464,13349,13352,13360,13358,43,13396,9039,9038,13404,0,462,462,464,464,13352,9018,9017,13360,43,13402,13404,13386,13385,0,464,464,450,450,13358,13360,13342,13341,43,13404,9038,9033,13386,0,464,464,450,450,13360,9017,9012,13342,43,13405,13406,13407,13408,0,477,476,476,477,13361,13362,13363,13364,43,13406,13409,13410,13407,0,476,479,479,476,13362,13365,13366,13363,43,13408,13407,9061,9060,0,477,476,476,477,13364,13363,9040,9039,43,13407,13410,9063,9061,0,476,479,479,476,13363,13366,9042,9040,43,13405,13411,13412,13406,0,477,477,476,476,13361,13367,13368,13362,43,13411,8558,8564,13412,0,477,477,476,476,13367,8537,8543,13368,43,13406,13412,13413,13409,0,476,476,479,479,13362,13368,13369,13365,43,13412,8564,8565,13413,0,476,476,479,479,13368,8543,8544,13369,43,13405,13414,13415,13411,0,477,57,57,477,13361,13370,13371,13367,43,13414,13397,13401,13415,0,57,23,23,57,13370,13353,13357,13371,43,13411,13415,8559,8558,0,477,57,57,477,13367,13371,8538,8537,43,13415,13401,8545,8559,0,57,23,23,57,13371,13357,8524,8538,43,13405,13408,13416,13414,0,477,477,57,57,13361,13364,13372,13370,43,13408,9060,9065,13416,0,477,477,57,57,13364,9039,9044,13372,43,13414,13416,13398,13397,0,57,57,23,23,13370,13372,13354,13353,43,13416,9065,9049,13398,0,57,57,23,23,13372,9044,9028,13354,43,13417,13418,13419,13420,0,491,15,15,491,13373,13374,13375,13376,43,13418,13341,13344,13419,0,15,16,16,15,13374,13297,13300,13375,43,13420,13419,9075,9074,0,491,15,15,491,13376,13375,9054,9053,43,13419,13344,8958,9075,0,15,16,16,15,13375,13300,8937,9054,43,13417,13421,13422,13418,0,491,491,15,15,13373,13377,13378,13374,43,13421,8581,8584,13422,0,491,491,15,15,13377,8560,8563,13378,43,13418,13422,13342,13341,0,15,15,16,16,13374,13378,13298,13297,43,13422,8584,8439,13342,0,15,15,16,16,13378,8563,8418,13298,43,13417,13423,13424,13421,0,491,492,492,491,13373,13379,13380,13377,43,13423,13409,13413,13424,0,492,479,479,492,13379,13365,13369,13380,43,13421,13424,8582,8581,0,491,492,492,491,13377,13380,8561,8560,43,13424,13413,8565,8582,0,492,479,479,492,13380,13369,8544,8561,43,13417,13420,13425,13423,0,491,491,492,492,13373,13376,13381,13379,43,13420,9074,9077,13425,0,491,491,492,492,13376,9053,9056,13381,43,13423,13425,13410,13409,0,492,492,479,479,13379,13381,13366,13365,43,13425,9077,9063,13410,0,492,492,479,479,13381,9056,9042,13366,43,13426,13427,13428,13429,0,940,942,944,941,13382,13383,13384,13385,43,13427,9462,9468,13428,0,942,935,937,944,13383,9441,9447,13384,43,13429,13428,9442,9441,0,941,944,920,926,13385,13384,9421,9420,43,13428,9468,9374,9442,0,944,937,12,920,13384,9447,9353,9421,43,13426,13430,13431,13427,0,940,938,939,942,13382,13386,13387,13383,43,13430,9507,9513,13431,0,938,938,939,939,13386,9486,9492,13387,43,13427,13431,9463,9462,0,942,939,328,935,13383,13387,9442,9441,43,13431,9513,9465,9463,0,939,939,328,328,13387,9492,9444,9442,43,13426,13432,13433,13430,0,940,924,924,938,13382,13388,13389,13386,43,13432,6110,6109,13433,0,924,925,925,924,13388,6089,6088,13389,43,13430,13433,9508,9507,0,938,924,924,938,13386,13389,9487,9486,43,13433,6109,2091,9508,0,924,925,925,924,13389,6088,2091,9487,43,13426,13429,13434,13432,0,940,941,924,924,13382,13385,13390,13388,43,13429,9441,9444,13434,0,941,926,924,924,13385,9420,9423,13390,43,13432,13434,6112,6110,0,924,924,925,925,13388,13390,6091,6089,43,13434,9444,2015,6112,0,924,924,925,925,13390,9423,2015,6091,43,13435,13436,13437,13438,0,2976,2979,2978,2977,13391,13392,13393,13394,43,13436,13439,13440,13437,0,2979,2981,2980,2978,13392,13395,13396,13393,43,13438,13437,13441,13442,0,2977,2978,2980,2982,13394,13393,13397,13398,43,13437,13440,13443,13441,0,2978,2980,2983,2980,13393,13396,13399,13397,43,13435,13444,13445,13436,0,2976,2984,2978,2979,13391,13400,13401,13392,43,13444,13446,13447,13445,0,2984,2985,2980,2978,13400,13402,13403,13401,43,13436,13445,13448,13439,0,2979,2978,2980,2981,13392,13401,13404,13395,43,13445,13447,13449,13448,0,2978,2980,2983,2980,13401,13403,13405,13404,43,13435,13450,13451,13444,0,2976,2987,2986,2984,13391,13406,13407,13400,43,13450,11856,11855,13451,0,2987,2302,2303,2986,13406,11835,11834,13407,43,13444,13451,13452,13446,0,2984,2986,2988,2985,13400,13407,13408,13402,43,13451,11855,11857,13452,0,2986,2303,2304,2988,13407,11834,11836,13408,43,13435,13438,13453,13450,0,2976,2977,2989,2987,13391,13394,13409,13406,43,13438,13442,13454,13453,0,2977,2982,2990,2989,13394,13398,13410,13409,43,13450,13453,11871,11856,0,2987,2989,2318,2302,13406,13409,11850,11835,43,13453,13454,11873,11871,0,2989,2990,2320,2318,13409,13410,11852,11850,43,13455,13456,13457,13458,0,2991,12,12,12,13411,13412,13413,13414,43,13456,13459,13460,13457,0,12,12,12,12,13412,13415,13416,13413,43,13458,13457,13461,13462,0,12,12,12,12,13414,13413,13417,13418,43,13457,13460,13463,13461,0,12,12,12,12,13413,13416,13419,13417,43,13455,13464,13465,13456,0,2991,2992,12,12,13411,13420,13421,13412,43,13464,13466,13467,13465,0,2992,2993,12,12,13420,13422,13423,13421,43,13456,13465,13468,13459,0,12,12,12,12,13412,13421,13424,13415,43,13465,13467,13469,13468,0,12,12,12,12,13421,13423,13425,13424,43,13455,13470,13471,13464,0,2991,2992,2994,2992,13411,13426,13427,13420,43,13470,11891,11897,13471,0,2992,2331,2334,2994,13426,11870,11876,13427,43,13464,13471,13472,13466,0,2992,2994,2995,2993,13420,13427,13428,13422,43,13471,11897,11898,13472,0,2994,2334,2335,2995,13427,11876,11877,13428,43,13455,13458,13473,13470,0,2991,12,12,2992,13411,13414,13429,13426,43,13458,13462,13474,13473,0,12,12,12,12,13414,13418,13430,13429,43,13470,13473,11892,11891,0,2992,12,12,2331,13426,13429,11871,11870,43,13473,13474,11894,11892,0,12,12,12,12,13429,13430,11873,11871,43,13475,13476,13477,13478,0,2996,2999,2998,2997,13431,13432,13433,13434,43,13476,13479,13480,13477,0,2999,2999,2998,2998,13432,13435,13436,13433,43,13478,13477,13481,13482,0,2997,2998,3001,3000,13434,13433,13437,13438,43,13477,13480,13483,13481,0,2998,2998,3001,3001,13433,13436,13439,13437,43,13475,13484,13485,13476,0,2996,12,12,2999,13431,13440,13441,13432,43,13484,13462,13461,13485,0,12,12,12,12,13440,13418,13417,13441,43,13476,13485,13486,13479,0,2999,12,12,2999,13432,13441,13442,13435,43,13485,13461,13463,13486,0,12,12,12,12,13441,13417,13419,13442,43,13475,13487,13488,13484,0,2996,3002,12,12,13431,13443,13444,13440,43,13487,11904,11903,13488,0,3002,2338,12,12,13443,11883,11882,13444,43,13484,13488,13474,13462,0,12,12,12,12,13440,13444,13430,13418,43,13488,11903,11894,13474,0,12,12,12,12,13444,11882,11873,13430,43,13475,13478,13489,13487,0,2996,2997,3003,3002,13431,13434,13445,13443,43,13478,13482,13490,13489,0,2997,3000,3004,3003,13434,13438,13446,13445,43,13487,13489,11916,11904,0,3002,3003,2348,2338,13443,13445,11895,11883,43,13489,13490,11918,11916,0,3003,3004,2350,2348,13445,13446,11897,11895,43,13491,13492,13493,13494,0,3005,3008,3007,3006,13447,13448,13449,13450,43,13492,13495,13496,13493,0,3008,3008,3007,3007,13448,13451,13452,13449,43,13494,13493,13497,13498,0,3006,3007,3010,3009,13450,13449,13453,13454,43,13493,13496,13499,13497,0,3007,3007,3010,3010,13449,13452,13455,13453,43,13491,13500,13501,13492,0,3005,3012,3011,3008,13447,13456,13457,13448,43,13500,13482,13481,13501,0,3012,3000,3001,3011,13456,13438,13437,13457,43,13492,13501,13502,13495,0,3008,3011,3011,3008,13448,13457,13458,13451,43,13501,13481,13483,13502,0,3011,3001,3001,3011,13457,13437,13439,13458,43,13491,13503,13504,13500,0,3005,3014,3013,3012,13447,13459,13460,13456,43,13503,12668,12674,13504,0,3014,2495,2500,3013,13459,12647,12653,13460,43,13500,13504,13490,13482,0,3012,3013,3004,3000,13456,13460,13446,13438,43,13504,12674,11918,13490,0,3013,2500,2350,3004,13460,12653,11897,13446,43,13491,13494,13505,13503,0,3005,3006,3015,3014,13447,13450,13461,13459,43,13494,13498,13506,13505,0,3006,3009,3016,3015,13450,13454,13462,13461,43,13503,13505,12669,12668,0,3014,3015,2494,2495,13459,13461,12648,12647,43,13505,13506,12671,12669,0,3015,3016,2497,2494,13461,13462,12650,12648,43,13507,13508,13509,13510,0,3017,3020,3019,3018,13463,13464,13465,13466,43,13508,13511,13512,13509,0,3020,3022,3021,3019,13464,13467,13468,13465,43,13510,13509,13447,13446,0,3018,3019,2980,2985,13466,13465,13403,13402,43,13509,13512,13449,13447,0,3019,3021,2983,2980,13465,13468,13405,13403,43,13507,13513,13514,13508,0,3017,3024,3023,3020,13463,13469,13470,13464,43,13513,13498,13497,13514,0,3024,3026,3025,3023,13469,13454,13453,13470,43,13508,13514,13515,13511,0,3020,3023,3023,3022,13464,13470,13471,13467,43,13514,13497,13499,13515,0,3023,3025,3025,3023,13470,13453,13455,13471,43,13507,13516,13517,13513,0,3017,3028,3027,3024,13463,13472,13473,13469,43,13516,12705,12709,13517,0,3028,2540,2545,3027,13472,12684,12688,13473,43,13513,13517,13506,13498,0,3024,3027,3029,3026,13469,13473,13462,13454,43,13517,12709,12671,13506,0,3027,2545,2546,3029,13473,12688,12650,13462,43,13507,13510,13518,13516,0,3017,3018,3030,3028,13463,13466,13474,13472,43,13510,13446,13452,13518,0,3018,2985,2988,3030,13466,13402,13408,13474,43,13516,13518,12706,12705,0,3028,3030,2539,2540,13472,13474,12685,12684,43,13518,13452,11857,12706,0,3030,2988,2304,2539,13474,13408,11836,12685,43,13519,13520,13521,13522,0,2999,2999,2998,2998,13475,13476,13477,13478,43,13520,13523,13524,13521,0,2999,2999,2998,2998,13476,13479,13480,13477,43,13522,13521,13525,13526,0,2998,2998,3001,3001,13478,13477,13481,13482,43,13521,13524,13527,13525,0,2998,2998,3001,3001,13477,13480,13483,13481,43,13519,13528,13529,13520,0,2999,12,12,2999,13475,13484,13485,13476,43,13528,13459,13468,13529,0,12,12,12,12,13484,13415,13424,13485,43,13520,13529,13530,13523,0,2999,12,12,2999,13476,13485,13486,13479,43,13529,13468,13469,13530,0,12,12,12,12,13485,13424,13425,13486,43,13519,13531,13532,13528,0,2999,2999,12,12,13475,13487,13488,13484,43,13531,13479,13486,13532,0,2999,2999,12,12,13487,13435,13442,13488,43,13528,13532,13460,13459,0,12,12,12,12,13484,13488,13416,13415,43,13532,13486,13463,13460,0,12,12,12,12,13488,13442,13419,13416,43,13519,13522,13533,13531,0,2999,2998,2998,2999,13475,13478,13489,13487,43,13522,13526,13534,13533,0,2998,3001,3001,2998,13478,13482,13490,13489,43,13531,13533,13480,13479,0,2999,2998,2998,2999,13487,13489,13436,13435,43,13533,13534,13483,13480,0,2998,3001,3001,2998,13489,13490,13439,13436,43,13535,13536,13537,13538,0,3008,3008,3007,3007,13491,13492,13493,13494,43,13536,13539,13540,13537,0,3008,3008,3007,3007,13492,13495,13496,13493,43,13538,13537,13541,13542,0,3007,3007,3010,3010,13494,13493,13497,13498,43,13537,13540,13543,13541,0,3007,3007,3010,3010,13493,13496,13499,13497,43,13535,13544,13545,13536,0,3008,3011,3011,3008,13491,13500,13501,13492,43,13544,13526,13525,13545,0,3011,3001,3001,3011,13500,13482,13481,13501,43,13536,13545,13546,13539,0,3008,3011,3011,3008,13492,13501,13502,13495,43,13545,13525,13527,13546,0,3011,3001,3001,3011,13501,13481,13483,13502,43,13535,13547,13548,13544,0,3008,3008,3011,3011,13491,13503,13504,13500,43,13547,13495,13502,13548,0,3008,3008,3011,3011,13503,13451,13458,13504,43,13544,13548,13534,13526,0,3011,3011,3001,3001,13500,13504,13490,13482,43,13548,13502,13483,13534,0,3011,3011,3001,3001,13504,13458,13439,13490,43,13535,13538,13549,13547,0,3008,3007,3007,3008,13491,13494,13505,13503,43,13538,13542,13550,13549,0,3007,3010,3010,3007,13494,13498,13506,13505,43,13547,13549,13496,13495,0,3008,3007,3007,3008,13503,13505,13452,13451,43,13549,13550,13499,13496,0,3007,3010,3010,3007,13505,13506,13455,13452,43,13551,13552,13553,13554,0,3031,3020,3019,3032,13507,13508,13509,13510,43,13552,13555,13556,13553,0,3020,3022,3021,3019,13508,13511,13512,13509,43,13554,13553,13440,13439,0,3032,3019,2980,2981,13510,13509,13396,13395,43,13553,13556,13443,13440,0,3019,3021,2983,2980,13509,13512,13399,13396,43,13551,13557,13558,13552,0,3031,3023,3023,3020,13507,13513,13514,13508,43,13557,13542,13541,13558,0,3023,3025,3025,3023,13513,13498,13497,13514,43,13552,13558,13559,13555,0,3020,3023,3023,3022,13508,13514,13515,13511,43,13558,13541,13543,13559,0,3023,3025,3025,3023,13514,13497,13499,13515,43,13551,13560,13561,13557,0,3031,3020,3023,3023,13507,13516,13517,13513,43,13560,13511,13515,13561,0,3020,3022,3023,3023,13516,13467,13471,13517,43,13557,13561,13550,13542,0,3023,3023,3025,3025,13513,13517,13506,13498,43,13561,13515,13499,13550,0,3023,3023,3025,3025,13517,13471,13455,13506,43,13551,13554,13562,13560,0,3031,3032,3019,3020,13507,13510,13518,13516,43,13554,13439,13448,13562,0,3032,2981,2980,3019,13510,13395,13404,13518,43,13560,13562,13512,13511,0,3020,3019,3021,3022,13516,13518,13468,13467,43,13562,13448,13449,13512,0,3019,2980,2983,3021,13518,13404,13405,13468,43,13563,13564,13565,13566,0,3033,3034,2998,2998,13519,13520,13521,13522,43,13564,12753,12759,13565,0,3034,3035,2998,2998,13520,12725,12729,13521,43,13566,13565,13567,13568,0,2998,2998,3001,3001,13522,13521,13523,13524,43,13565,12759,12760,13567,0,2998,2998,3001,3001,13521,12729,12703,13523,43,13563,13569,13570,13564,0,3033,2992,2994,3034,13519,13525,13526,13520,43,13569,13466,13472,13570,0,2992,2993,2995,2994,13525,13422,13428,13526,43,13564,13570,12754,12753,0,3034,2994,3036,3035,13520,13526,12726,12725,43,13570,13472,11898,12754,0,2994,2995,2335,3036,13526,13428,11877,12726,43,13563,13571,13572,13569,0,3033,2999,12,2992,13519,13527,13528,13525,43,13571,13523,13530,13572,0,2999,2999,12,12,13527,13479,13486,13528,43,13569,13572,13467,13466,0,2992,12,12,2993,13525,13528,13423,13422,43,13572,13530,13469,13467,0,12,12,12,12,13528,13486,13425,13423,43,13563,13566,13573,13571,0,3033,2998,2998,2999,13519,13522,13529,13527,43,13566,13568,13574,13573,0,2998,3001,3001,2998,13522,13524,13530,13529,43,13571,13573,13524,13523,0,2999,2998,2998,2999,13527,13529,13480,13479,43,13573,13574,13527,13524,0,2998,3001,3001,2998,13529,13530,13483,13480,43,13575,13576,13577,13578,0,3008,3008,3007,3007,13531,13532,13533,13534,43,13576,12773,12776,13577,0,3008,3008,3007,3007,13532,12703,12703,13533,43,13578,13577,13579,13580,0,3007,3007,3010,3010,13534,13533,13535,13536,43,13577,12776,12727,13579,0,3007,3007,3010,3010,13533,12703,12703,13535,43,13575,13581,13582,13576,0,3008,3011,3011,3008,13531,13537,13538,13532,43,13581,13568,13567,13582,0,3011,3001,3001,3011,13537,13524,13523,13538,43,13576,13582,12774,12773,0,3008,3011,3011,3008,13532,13538,12703,12703,43,13582,13567,12760,12774,0,3011,3001,3001,3011,13538,13523,12703,12703,43,13575,13583,13584,13581,0,3008,3008,3011,3011,13531,13539,13540,13537,43,13583,13539,13546,13584,0,3008,3008,3011,3011,13539,13495,13502,13540,43,13581,13584,13574,13568,0,3011,3011,3001,3001,13537,13540,13530,13524,43,13584,13546,13527,13574,0,3011,3011,3001,3001,13540,13502,13483,13530,43,13575,13578,13585,13583,0,3008,3007,3007,3008,13531,13534,13541,13539,43,13578,13580,13586,13585,0,3007,3010,3010,3007,13534,13536,13542,13541,43,13583,13585,13540,13539,0,3008,3007,3007,3008,13539,13541,13496,13495,43,13585,13586,13543,13540,0,3007,3010,3010,3007,13541,13542,13499,13496,43,13587,13588,13589,13590,0,3037,3040,3039,3038,13543,13544,13545,13546,43,13588,12724,12730,13589,0,3040,3042,3041,3039,13544,12701,12706,13545,43,13590,13589,13454,13442,0,3038,3039,2990,2982,13546,13545,13410,13398,43,13589,12730,11873,13454,0,3039,3041,2320,2990,13545,12706,11852,13410,43,13587,13591,13592,13588,0,3037,3023,3023,3040,13543,13547,13548,13544,43,13591,13580,13579,13592,0,3023,3025,3025,3023,13547,13536,13535,13548,43,13588,13592,12725,12724,0,3040,3023,3023,3042,13544,13548,12702,12701,43,13592,13579,12727,12725,0,3023,3025,3025,3023,13548,13535,12703,12702,43,13587,13593,13594,13591,0,3037,3020,3023,3023,13543,13549,13550,13547,43,13593,13555,13559,13594,0,3020,3022,3023,3023,13549,13511,13515,13550,43,13591,13594,13586,13580,0,3023,3023,3025,3025,13547,13550,13542,13536,43,13594,13559,13543,13586,0,3023,3023,3025,3025,13550,13515,13499,13542,43,13587,13590,13595,13593,0,3037,3038,3019,3020,13543,13546,13551,13549,43,13590,13442,13441,13595,0,3038,2982,2980,3019,13546,13398,13397,13551,43,13593,13595,13556,13555,0,3020,3019,3021,3022,13549,13551,13512,13511,43,13595,13441,13443,13556,0,3019,2980,2983,3021,13551,13397,13399,13512,43,13596,7745,13597,13598,0,3043,66,3045,3044,13552,7724,13553,13554,43,13599,13600,13601,13602,0,3046,3049,3048,3047,13555,13556,13557,13558,43,13603,13604,13605,13606,0,3050,3053,3052,3051,13559,13560,13561,13562,43,13607,13608,13609,13610,0,3054,3057,3056,3055,13563,13564,13565,13566,43,13611,13612,13613,13614,0,3058,3061,3060,3059,13567,13568,13569,13570,43,13615,13616,13617,13618,0,3062,3063,774,421,13571,13572,13573,13574,43,13619,13620,13621,13622,0,3064,10,10,3065,13575,13576,13577,13578,43,13623,13624,13625,13626,0,3066,3069,3068,3067,13579,13580,13581,13582,43,13627,13628,13629,13630,0,3070,3071,67,55,13583,13584,13585,13586,43,13631,13632,13633,13634,0,11,12,12,11,13587,13588,13589,13590,43,13635,13636,13637,13638,0,35,906,906,35,13591,13592,13593,13594,43,13639,13640,13641,13642,0,35,906,906,35,13595,13596,13597,13598,43,13643,7755,13644,13645,0,3072,22,775,3073,13599,7734,13600,13601,43,13646,13647,13648,13649,0,3074,3077,3076,3075,13602,13603,13604,13605,43,13650,13651,13633,13632,0,3078,3079,12,12,13606,13607,13589,13588,43,13652,7538,13653,13654,0,15,16,16,15,13608,7517,13609,13610,43,13655,13656,13657,13658,0,61,776,776,61,13611,13612,13613,13614,43,13659,13660,13661,13662,0,61,776,776,61,13615,13616,13617,13618,43,13663,7588,13664,13665,0,3080,27,3082,3081,13619,7567,13620,13621,43,13666,13667,13668,13669,0,3083,3086,3085,3084,13622,13623,13624,13625,43,13670,13671,13672,13673,0,3087,3088,18,18,13626,13627,13628,13629,43,13674,7639,13675,13676,0,3089,64,3091,3090,13630,7618,13631,13632,43,13677,13678,13679,13680,0,3092,3095,3094,3093,13633,13634,13635,13636,43,13681,13682,13683,13684,0,3096,3097,6,6,13637,13638,13639,13640,43,13685,13686,13687,13688,0,9,10,10,9,13641,13642,13643,13644,43,13689,13690,13691,13692,0,55,67,67,55,13645,13646,13647,13648,43,13693,13694,13695,13696,0,55,67,67,55,13649,13650,13651,13652,43,13697,13698,13699,13700,0,11,12,12,11,13653,13654,13655,13656,43,13701,13702,13703,13704,0,35,906,906,35,13657,13658,13659,13660,43,13705,13706,13707,13708,0,35,906,906,35,13661,13662,13663,13664,43,13709,13710,13711,13712,0,22,23,23,22,13665,13666,13667,13668,43,13713,13714,13715,13716,0,14,775,775,14,13669,13670,13671,13672,43,13717,13718,13719,7818,0,14,775,775,14,13673,13674,13675,7797,43,13720,13721,13722,13723,0,3098,19,3082,3099,13676,13677,13678,13679,43,13724,13725,13726,13727,0,3100,3103,3102,3101,13680,13681,13682,13683,43,13728,13729,13730,13731,0,3104,3106,3105,58,13684,13685,13686,13687,43,13732,7510,13653,13733,0,3107,16,16,3108,13688,7489,13609,13689,43,13734,13735,13736,13737,0,3109,3110,69,3110,13690,13691,13692,13693,43,13738,13739,13740,13741,0,3107,3108,16,16,13694,13695,13696,13697,43,13742,13743,13744,13745,0,15,16,16,15,13698,13699,13700,13701,43,13746,13747,13748,13749,0,61,776,776,61,13702,13703,13704,13705,43,13750,13751,13752,13753,0,61,776,776,61,13706,13707,13708,13709,43,13754,7769,13755,13756,0,3111,30,30,3112,13710,7748,13711,13712,43,13757,13758,13759,13760,0,3113,3114,83,3114,13713,13714,13715,13716,43,13761,13762,13763,13764,0,3111,3112,30,30,13717,13718,13719,13720,43,13765,13766,13767,13768,0,3115,3118,3117,3116,13721,13722,13723,13724,43,13769,13770,13771,13772,0,3119,3122,3121,3120,13725,13726,13727,13728,43,13773,13774,13775,13776,0,3123,3126,3125,3124,13729,13730,13731,13732,43,13777,13778,13779,13780,0,906,11,906,35,13733,13734,13735,13736,43,13781,13782,13783,13784,0,10,35,10,9,13737,13738,13739,13740,43,13785,13786,13787,13788,0,67,9,67,55,13741,13742,13743,13744,43,13789,13790,13791,13792,0,3127,3130,3129,3128,13745,13746,13747,13748,43,13793,13794,13795,13796,0,36,3128,3132,3131,13749,13750,13751,13752,43,13797,13798,13799,13800,0,3133,3131,906,35,13753,13754,13755,13756,43,13801,7714,13802,13803,0,3134,56,3136,3135,13757,7693,13758,13759,43,13804,13805,13806,13807,0,3137,3140,3139,3138,13760,13761,13762,13763,43,13808,13809,13810,13811,0,3141,3142,775,14,13764,13765,13766,13767,43,13812,7609,13813,13814,0,3143,59,3145,3144,13768,7588,13769,13770,43,13815,13816,13817,13818,0,3146,3149,3148,3147,13771,13772,13773,13774,43,13819,13820,13821,13822,0,3150,3153,3152,3151,13775,13776,13777,13778,43,13823,13824,13825,13826,0,3154,18,18,3155,13779,13780,13781,13782,43,13827,13828,13829,13830,0,3156,3159,3158,3157,13783,13784,13785,13786,43,13831,13832,13833,13834,0,3160,3163,3162,3161,13787,13788,13789,13790,43,13835,13836,13837,13838,0,3164,3167,3166,3165,13791,13792,13793,13794,43,13839,13840,13841,13842,0,3168,3171,3170,3169,13795,13796,13797,13798,43,13843,13844,13825,13824,0,3172,3173,18,18,13799,13800,13781,13780,43,13845,7615,13846,13847,0,3174,53,3176,3175,13801,7594,13802,13803,43,13848,13849,13850,13851,0,3177,3180,3179,3178,13804,13805,13806,13807,43,13852,13853,13854,13855,0,3181,3184,3183,3182,13808,13809,13810,13811,43,13856,7830,13857,13858,0,3185,50,3176,3186,13812,7809,13813,13814,43,13859,13860,13861,13862,0,3187,3190,3189,3188,13815,13816,13817,13818,43,13863,13864,13865,13866,0,3191,3193,3192,62,13819,13820,13821,13822,43,13867,7726,13868,13869,0,3194,9,67,3195,13823,7705,13824,13825,43,13870,13871,13872,13873,0,3196,3199,3198,3197,13826,13827,13828,13829,43,13874,13875,13876,13877,0,3200,3201,3091,64,13830,13831,13832,13833,43,13878,7514,13664,13879,0,19,19,3082,3082,13834,7493,13620,13835,43,13880,13881,13882,13883,0,19,19,3082,3082,13836,13837,13838,13839,43,13884,13885,13886,13887,0,27,27,26,26,13840,13841,13842,13843,43,13888,7606,13889,13890,0,39,39,3136,3136,13844,7585,13845,13846,43,13891,13892,13893,13894,0,39,39,3136,3136,13847,13848,13849,13850,43,13895,13896,13897,13898,0,56,56,18,18,13851,13852,13853,13854,43,13899,7811,13900,13901,0,65,65,777,777,13855,7790,13856,13857,43,13902,13903,13904,13905,0,65,65,777,777,13858,13859,13860,13861,43,13906,13907,13908,13909,0,57,57,23,23,13862,13863,13864,13865,43,13910,7619,13911,13912,0,25,25,3105,3105,13866,7598,13867,13868,43,13913,13914,13915,13916,0,25,25,3105,3105,13869,13870,13871,13872,43,13917,13918,13919,13920,0,58,58,30,30,13873,13874,13875,13876,43,13921,13922,13923,13924,0,22,22,775,775,13877,13878,13879,13880,43,13925,13926,13927,13928,0,22,22,775,775,13881,13882,13883,13884,43,13929,13930,13931,13932,0,14,14,12,12,13885,13886,13887,13888,43,13933,7591,13889,13934,0,56,56,3136,3136,13889,7570,13845,13890,43,13935,13936,13937,13938,0,56,56,3136,3136,13891,13892,13893,13894,43,13939,13940,13941,13942,0,39,39,23,23,13895,13896,13897,13898,43,13943,7566,13813,13944,0,43,43,3145,3145,13899,7545,13769,13900,43,13945,13946,13947,13948,0,43,43,3145,3145,13901,13902,13903,13904,43,13949,13950,13951,13952,0,59,59,18,18,13905,13906,13907,13908,43,13953,7636,13954,13955,0,63,63,3202,3202,13909,7615,13910,13911,43,13956,13957,13958,13959,0,63,63,3202,3202,13912,13913,13914,13915,43,13960,13961,13962,13963,0,60,60,42,42,13916,13917,13918,13919,43,13964,7576,13846,13965,0,50,50,3176,3176,13920,7555,13802,13921,43,13966,13967,13968,13969,0,50,50,3176,3176,13922,13923,13924,13925,43,13970,13971,13972,13973,0,53,53,2,2,13926,13927,13928,13929,43,13974,7598,13911,13975,0,58,58,3105,3105,13930,7577,13867,13931,43,13976,13977,13978,13979,0,58,58,3105,3105,13932,13933,13934,13935,43,13980,13981,13982,13983,0,25,25,26,26,13936,13937,13938,13939,43,13984,7808,13985,13986,0,15,15,776,776,13940,7787,13941,13942,43,13987,13988,13989,13990,0,15,15,776,776,13943,13944,13945,13946,43,13991,13992,13993,13994,0,61,61,30,30,13947,13948,13949,13950,43,13995,7805,13996,13997,0,52,52,3192,3192,13951,7784,13952,13953,43,13998,13999,14000,14001,0,52,52,3192,3192,13954,13955,13956,13957,43,14002,14003,14004,14005,0,62,62,16,16,13958,13959,13960,13961,43,14006,14007,14008,14009,0,9,9,67,67,13962,13963,13964,13965,43,14010,14011,14012,14013,0,9,9,67,67,13966,13967,13968,13969,43,14014,14015,14016,14017,0,55,55,6,6,13970,13971,13972,13973,43,14018,14019,14020,14021,0,11,11,906,906,13974,13975,13976,13977,43,14022,14023,14024,14025,0,11,11,906,906,13978,13979,13980,13981,43,14026,14027,14028,14029,0,35,35,10,10,13982,13983,13984,13985,43,14030,7612,13954,14031,0,60,60,3202,3202,13986,7591,13910,13987,43,14032,14033,14034,14035,0,60,60,3202,3202,13988,13989,13990,13991,43,14036,14037,14038,14039,0,63,63,12,12,13992,13993,13994,13995,43,14040,7518,13675,14041,0,21,21,3091,3091,13996,7497,13631,13997,43,14042,14043,14044,14045,0,21,21,3091,3091,13998,13999,14000,14001,43,14046,14047,14048,14049,0,64,64,42,42,14002,14003,14004,14005,43,14050,14051,14052,14053,0,19,18,18,19,14006,14007,14008,14009,43,14054,14055,14056,14057,0,27,3082,3082,27,14010,14011,14012,14013,43,14058,14059,14060,14061,0,27,3082,3082,27,14014,14015,14016,14017,43,14062,14063,14064,14065,0,39,23,23,39,14018,14019,14020,14021,43,14066,14067,14068,14069,0,56,3136,3136,56,14022,14023,14024,14025,43,14070,14071,14072,14073,0,56,3136,3136,56,14026,14027,14028,14029,43,14074,14075,14076,14077,0,65,65,777,777,14030,14031,14032,14033,43,14078,14079,14080,14081,0,65,65,777,777,14034,14035,14036,14037,43,14082,14083,14064,14063,0,57,57,23,23,14038,14039,14020,14019,43,14084,14085,14086,14087,0,25,25,3105,3105,14040,14041,14042,14043,43,14088,14089,14090,14091,0,25,25,3105,3105,14044,14045,14046,14047,43,14092,14093,14094,14095,0,58,58,30,30,14048,14049,14050,14051,43,14096,14097,14098,14099,0,22,22,775,775,14052,14053,14054,14055,43,14100,14101,14102,14103,0,22,22,775,775,14056,14057,14058,14059,43,14104,14105,14106,14107,0,14,14,12,12,14060,14061,14062,14063,43,14108,14109,14110,14111,0,56,56,3136,3136,14064,14065,14066,14067,43,14112,14113,14114,14115,0,56,56,3136,3136,14068,14069,14070,14071,43,14116,14117,14118,14119,0,39,39,23,23,14072,14073,14074,14075,43,14120,14121,14122,14123,0,43,42,42,43,14076,14077,14078,14079,43,14124,14125,14126,14127,0,59,3145,3145,59,14080,14081,14082,14083,43,14128,14129,14130,14131,0,59,3145,3145,59,14084,14085,14086,14087,43,14132,14107,14106,14133,0,63,12,12,63,14088,14063,14062,14089,43,14134,14135,14136,14137,0,60,3202,3202,60,14090,14091,14092,14093,43,14138,14139,14140,14141,0,60,3202,3202,60,14094,14095,14096,14097,43,14142,14143,14144,14145,0,53,53,3176,3176,14098,14099,14100,14101,43,14146,14147,14148,14149,0,53,53,3176,3176,14102,14103,14104,14105,43,14150,14151,14152,14153,0,50,50,26,26,14106,14107,14108,14109,43,14154,14155,14156,14157,0,58,30,30,58,14110,14111,14112,14113,43,14158,14159,14160,14161,0,25,3105,3105,25,14114,14115,14116,14117,43,14162,14163,14164,7828,0,25,3105,3105,25,14118,14119,14120,7807,43,14165,14166,14167,14168,0,15,16,16,15,14121,14122,14123,14124,43,14169,14170,14171,14172,0,61,776,776,61,14125,14126,14127,14128,43,14173,14174,14175,7826,0,61,776,776,61,14129,14130,14131,7805,43,14176,14177,14178,14179,0,62,62,3192,3192,14132,14133,14134,14135,43,14180,14181,14182,14183,0,62,62,3192,3192,14136,14137,14138,14139,43,14184,14185,14186,14187,0,52,52,2,2,14140,14141,14142,14143,43,14188,14189,14190,14191,0,9,9,67,67,14144,14145,14146,14147,43,14192,14193,14194,14195,0,9,9,67,67,14148,14149,14150,14151,43,14196,14197,14198,14199,0,55,55,6,6,14152,14153,14154,14155,43,14200,14201,14202,14203,0,11,12,12,11,14156,14157,14158,14159,43,14204,14205,14206,14207,0,35,906,906,35,14160,14161,14162,14163,43,14208,14209,14210,14211,0,35,906,906,35,14164,14165,14166,14167,43,14212,14213,14214,14215,0,60,60,3202,3202,14168,14169,14170,14171,43,14216,14217,14218,14219,0,60,60,3202,3202,14172,14173,14174,14175,43,14220,14221,14202,14201,0,63,63,12,12,14176,14177,14158,14157,43,14222,14199,14198,14223,0,21,6,6,21,14178,14155,14154,14179,43,14224,14225,14226,14227,0,64,3091,3091,64,14180,14181,14182,14183,43,14228,14229,14230,14231,0,64,3091,3091,64,14184,14185,14186,14187,43,14232,14233,14234,14235,0,19,18,18,19,14188,14189,14190,14191,43,14236,14237,14238,14239,0,27,3082,3082,27,14192,14193,14194,14195,43,14240,14241,14242,14243,0,27,3082,3082,27,14196,14197,14198,14199,43,14244,14245,14246,14247,0,39,23,23,39,14200,14201,14202,14203,43,14248,14249,14250,14251,0,56,3136,3136,56,14204,14205,14206,14207,43,14252,14253,14254,14255,0,56,3136,3136,56,14208,14209,14210,14211,43,14256,14257,14258,14259,0,57,57,777,777,14212,14213,14214,14215,43,14260,14261,14262,14263,0,57,57,777,777,14216,14217,14218,14219,43,14264,14265,14266,14267,0,65,65,30,30,14220,14221,14222,14223,43,14268,13731,13730,14269,0,58,58,3105,3105,14224,13687,13686,14225,43,14270,14271,14272,14273,0,58,58,3105,3105,14226,14227,14228,14229,43,14274,14275,14276,14277,0,25,25,26,26,14230,14231,14232,14233,43,14278,13811,13810,14279,0,14,14,775,775,14234,13767,13766,14235,43,14280,14281,14282,14283,0,14,14,775,775,14236,14237,14238,14239,43,14284,14285,14286,14287,0,22,22,23,23,14240,14241,14242,14243,43,14288,7562,13802,14289,0,39,39,3136,3136,14244,7541,13758,14245,43,14290,14291,14292,14293,0,39,39,3136,3136,14246,14247,14248,14249,43,14294,14295,14296,14297,0,56,56,18,18,14250,14251,14252,14253,43,14298,14299,14300,14301,0,43,42,42,43,14254,14255,14256,14257,43,14302,14303,14304,14305,0,59,3145,3145,59,14258,14259,14260,14261,43,14306,14307,14308,14309,0,59,3145,3145,59,14262,14263,14264,14265,43,14310,14311,14312,14313,0,63,12,12,63,14266,14267,14268,14269,43,14314,14315,14316,14317,0,60,3202,3202,60,14270,14271,14272,14273,43,14318,14319,14320,14321,0,60,3202,3202,60,14274,14275,14276,14277,43,14322,7584,13868,14323,0,55,55,67,67,14278,7563,13824,14279,43,14324,14325,14326,14327,0,55,55,67,67,14280,14281,14282,14283,43,14328,14329,14330,14331,0,9,9,10,10,14284,14285,14286,14287,43,14332,14333,14334,14335,0,11,12,12,11,14288,14289,14290,14291,43,14336,14337,14338,14339,0,35,906,906,35,14292,14293,14294,14295,43,14340,14341,14342,14343,0,35,906,906,35,14296,14297,14298,14299,43,14344,14345,14346,14347,0,63,63,3202,3202,14300,14301,14302,14303,43,14348,14349,14350,14351,0,63,63,3202,3202,14304,14305,14306,14307,43,14352,14353,14354,14355,0,60,60,42,42,14308,14309,14310,14311,43,14356,14357,14358,14359,0,21,6,6,21,14312,14313,14314,14315,43,14360,14361,14362,14363,0,64,3091,3091,64,14316,14317,14318,14319,43,14364,14365,14366,14367,0,64,3091,3091,64,14320,14321,14322,14323,43,14368,7792,14369,14370,0,62,62,3192,3192,14324,7771,14325,14326,43,14371,14372,14373,14374,0,62,62,3192,3192,14327,14328,14329,14330,43,14375,14376,14377,14378,0,52,52,2,2,14331,14332,14333,14334,43,14379,7492,13597,14380,0,3,3,3045,3045,14335,7471,13553,14336,43,14381,14382,14383,14384,0,3,3,3045,3045,14337,14338,14339,14340,43,14385,14386,14387,14388,0,66,66,18,18,14341,14342,14343,14344,43,14389,13618,13617,14390,0,421,421,774,774,14345,13574,13573,14346,43,9114,14391,14392,9115,0,421,421,774,774,9093,14347,14348,9094,43,14393,14394,14395,14396,0,5,5,6,6,14349,14350,14351,14352,43,14397,14398,14399,14400,0,9,10,10,9,14353,14354,14355,14356,43,14401,14402,14403,14404,0,55,67,67,55,14357,14358,14359,14360,43,14396,14405,9136,14406,0,6,55,55,6,14352,14361,9115,14362,43,14407,14408,14409,14410,0,11,12,12,11,14363,14364,14365,14366,43,14411,14412,14413,14414,0,35,906,906,35,14367,14368,14369,14370,43,14415,7508,13644,14416,0,14,14,775,775,14371,7487,13600,14372,43,9199,14417,14418,9200,0,14,14,775,775,9178,14373,14374,9179,43,9173,14419,14420,9174,0,22,22,23,23,9152,14375,14376,9153,43,14421,7820,14422,14423,0,57,57,777,777,14377,7799,14378,14379,43,9214,14424,14425,9215,0,57,57,777,777,9193,14380,14381,9194,43,9194,14426,14427,9195,0,65,65,30,30,9173,14382,14383,9174,43,14428,13662,13661,14429,0,61,61,776,776,14384,13618,13617,14385,43,9211,14430,14431,9212,0,61,61,776,776,9190,14386,14387,9191,43,9097,14432,14433,9098,0,15,15,16,16,9076,14388,14389,9077,43,14434,14435,14436,14437,0,15,16,16,15,14390,14391,14392,14393,43,14438,14439,14440,14441,0,61,776,776,61,14394,14395,14396,14397,43,14442,14443,14444,14445,0,61,776,776,61,14398,14399,14400,14401,43,14446,7546,13755,14447,0,30,30,30,30,14402,7525,13711,14403,43,14448,14449,14450,14451,0,30,30,30,30,14404,14405,14406,14407,43,14452,14453,14454,14455,0,30,30,30,30,14408,14409,14410,14411,43,14456,14457,14458,14459,0,15,15,776,776,14412,14413,14414,14415,43,14460,14461,14462,14463,0,15,15,776,776,14416,14417,14418,14419,43,14464,14465,14466,14467,0,61,61,30,30,14420,14421,14422,14423,43,14468,13741,13740,14469,0,16,16,16,16,14424,13697,13696,14425,43,14470,14471,14472,14473,0,16,16,16,16,14426,14427,14428,14429,43,14474,14475,14476,14477,0,16,16,16,16,14430,14431,14432,14433,43,14478,14445,14444,14479,0,61,61,776,776,14434,14401,14400,14435,43,14480,14481,14482,14483,0,61,61,776,776,14436,14437,14438,14439,43,14484,14485,14486,14487,0,15,15,16,16,14440,14441,14442,14443,43,14488,14489,14490,14491,0,30,30,30,30,14444,14445,14446,14447,43,14492,14493,14494,14495,0,30,30,30,30,14448,14449,14450,14451,43,14496,14497,14498,14499,0,30,30,30,30,14452,14453,14454,14455,43,14500,14501,14502,14503,0,15,15,776,776,14456,14457,14458,14459,43,14504,14505,14506,14507,0,15,15,776,776,14460,14461,14462,14463,43,14508,14509,14510,14511,0,61,61,30,30,14464,14465,14466,14467,43,14512,14513,14514,14515,0,16,16,16,16,14468,14469,14470,14471,43,14516,14517,14518,14519,0,16,16,16,16,14472,14473,14474,14475,43,14520,14521,14522,14523,0,16,16,16,16,14476,14477,14478,14479,43,14524,7742,14369,14525,0,3203,52,3192,3204,14480,7721,14325,14481,43,14526,14527,14528,14529,0,3205,3208,3207,3206,14482,14483,14484,14485,43,14530,14531,14532,14533,0,3209,3212,3211,3210,14486,14487,14488,14489,43,14534,14535,14536,14537,0,3213,3216,3215,3214,14490,14491,14492,14493,43,14538,14539,14540,14541,0,3217,3220,3219,3218,14494,14495,14496,14497,43,14542,14543,14544,14545,0,3221,3224,3223,3222,14498,14499,14500,14501,43,14546,14547,14548,14549,0,3225,3228,3227,3226,14502,14503,14504,14505,43,14550,14551,14552,14553,0,3229,3232,3231,3230,14506,14507,14508,14509,43,14554,14555,14536,14535,0,3233,3234,3215,3216,14510,14511,14492,14491,43,14556,14557,14558,14559,0,3235,3238,3237,3236,14512,14513,14514,14515,43,14560,14561,14562,14563,0,3239,3242,3241,3240,14516,14517,14518,14519,43,14564,14565,14566,14567,0,80,3244,3243,81,14520,14521,14522,14523,43,14568,7625,13996,14569,0,3245,62,3192,3246,14524,7604,13952,14525,43,14570,14571,14572,14573,0,3247,3250,3249,3248,14526,14527,14528,14529,43,14574,14575,14576,14577,0,3251,3252,2,2,14530,14531,14532,14533,43,14578,7622,13985,14579,0,3239,61,776,3240,14534,7601,13941,14535,43,14580,14581,14582,14583,0,3235,3242,3241,3236,14536,14537,14538,14539,43,14584,14585,14586,14587,0,75,3244,3243,76,14540,14541,14542,14543,43,14588,7594,13900,14589,0,57,57,777,777,14544,7573,13856,14545,43,14590,14591,14592,14593,0,57,57,777,777,14546,14547,14548,14549,43,14594,14595,14596,14597,0,65,65,30,30,14550,14551,14552,14553,43,14598,14599,14600,14601,0,3253,30,30,3254,14554,14555,14556,14557,43,14602,14603,14604,14605,0,3255,3258,3257,3256,14558,14559,14560,14561,43,14606,14607,14608,14609,0,3259,3262,3261,3260,14562,14563,14564,14565,43,14610,14611,14612,14613,0,3263,3130,3129,3264,14566,14567,14568,14569,43,14614,14615,14616,14617,0,3265,3268,3267,3266,14570,14571,14572,14573,43,14618,14619,13719,7532,0,3269,3270,775,22,14574,14575,13675,7511,43,14620,7759,14422,14621,0,3271,65,777,3272,14576,7738,14378,14577,43,14622,14623,14624,14625,0,3273,3276,3275,3274,14578,14579,14580,14581,43,14626,14627,14628,14629,0,3277,3280,3279,3278,14582,14583,14584,14585,43,14630,13866,13865,14631,0,62,62,3192,3192,14586,13822,13821,14587,43,14632,14633,14634,14635,0,62,62,3192,3192,14588,14589,14590,14591,43,14636,14637,14638,14639,0,52,52,2,2,14592,14593,14594,14595,43,14640,14641,14642,14643,0,61,30,30,61,14596,14597,14598,14599,43,14644,14645,14646,14647,0,15,776,776,15,14600,14601,14602,14603,43,14648,14649,14175,7680,0,15,776,776,15,14604,14605,14131,7659,43,14650,14153,14152,14651,0,25,26,26,25,14606,14109,14108,14607,43,14652,14653,14654,14655,0,58,3105,3105,58,14608,14609,14610,14611,43,14656,14657,14164,7676,0,58,3105,3105,58,14612,14613,14120,7655,43,14658,7580,13857,14659,0,53,53,3176,3176,14614,7559,13813,14615,43,14660,14661,14662,14663,0,53,53,3176,3176,14616,14617,14618,14619,43,14664,14665,14666,14667,0,50,50,26,26,14620,14621,14622,14623,43,13598,13597,7492,7491,0,3044,3045,3,0,13554,13553,7471,7470,43,13601,13598,7491,13604,0,3048,3044,0,3053,13557,13554,7470,13560,43,13604,7491,7494,13605,0,3053,0,1,3052,13560,7470,7473,13561,43,13610,13609,7496,7495,0,3055,3056,7,4,13566,13565,7475,7474,43,13613,13610,7495,13616,0,3060,3055,4,3063,13569,13566,7474,13572,43,13616,7495,7498,13617,0,3063,4,5,774,13572,7474,7477,13573,43,13622,13621,7500,7499,0,3065,10,10,8,13578,13577,7479,7478,43,13625,13622,7499,13628,0,3068,3065,8,3071,13581,13578,7478,13584,43,13628,7499,7502,13629,0,3071,8,9,67,13584,7478,7481,13585,43,13634,13633,7504,7503,0,11,12,12,11,13590,13589,7483,7482,43,13637,13634,7503,13640,0,906,11,11,906,13593,13590,7482,13596,43,13640,7503,7506,13641,0,906,11,11,906,13596,7482,7485,13597,43,13645,13644,7508,7507,0,3073,775,14,13,13601,13600,7487,7486,43,13648,13645,7507,13651,0,3076,3073,13,3079,13604,13601,7486,13607,43,13651,7507,7504,13633,0,3079,13,12,12,13607,7486,7483,13589,43,13654,13653,7510,7509,0,15,16,16,15,13610,13609,7489,7488,43,13657,13654,7509,13660,0,776,15,15,776,13613,13610,7488,13616,43,13660,7509,7512,13661,0,776,15,15,776,13616,7488,7491,13617,43,13665,13664,7514,7513,0,3081,3082,19,17,13621,13620,7493,7492,43,13668,13665,7513,13671,0,3085,3081,17,3088,13624,13621,7492,13627,43,13671,7513,7516,13672,0,3088,17,18,18,13627,7492,7495,13628,43,13676,13675,7518,7517,0,3090,3091,21,20,13632,13631,7497,7496,43,13679,13676,7517,13682,0,3094,3090,20,3097,13635,13632,7496,13638,43,13682,7517,7520,13683,0,3097,20,6,6,13638,7496,7499,13639,43,13688,13687,7522,7521,0,9,10,10,9,13644,13643,7501,7500,43,13691,13688,7521,13694,0,67,9,9,67,13647,13644,7500,13650,43,13694,7521,7524,13695,0,67,9,9,67,13650,7500,7503,13651,43,13700,13699,7526,7525,0,11,12,12,11,13656,13655,7505,7504,43,13703,13700,7525,13706,0,906,11,11,906,13659,13656,7504,13662,43,13706,7525,7528,13707,0,906,11,11,906,13662,7504,7507,13663,43,13712,13711,7530,7529,0,22,23,23,22,13668,13667,7509,7508,43,13715,13712,7529,13718,0,775,22,22,775,13671,13668,7508,13674,43,13718,7529,7532,13719,0,775,22,22,775,13674,7508,7511,13675,43,13723,13722,7534,7533,0,3099,3082,27,24,13679,13678,7513,7512,43,13726,13723,7533,13729,0,3102,3099,24,3106,13682,13679,7512,13685,43,13729,7533,7536,13730,0,3106,24,25,3105,13685,7512,7515,13686,43,13733,13653,7538,7537,0,3108,16,16,28,13689,13609,7517,7516,43,13736,13733,7537,13739,0,69,3108,28,3108,13692,13689,7516,13695,43,13739,7537,7540,13740,0,3108,28,16,16,13695,7516,7519,13696,43,13745,13744,7542,7541,0,15,16,16,15,13701,13700,7521,7520,43,13748,13745,7541,13751,0,776,15,15,776,13704,13701,7520,13707,43,13751,7541,7544,13752,0,776,15,15,776,13707,7520,7523,13708,43,13756,13755,7546,7545,0,3112,30,30,29,13712,13711,7525,7524,43,13759,13756,7545,13762,0,83,3112,29,3112,13715,13712,7524,13718,43,13762,7545,7548,13763,0,3112,29,30,30,13718,7524,7527,13719,43,13768,13767,7550,7549,0,3116,3117,34,31,13724,13723,7529,7528,43,13771,13768,7549,13774,0,3121,3116,31,3126,13727,13724,7528,13730,43,13774,7549,7552,13775,0,3126,31,32,3125,13730,7528,7531,13731,43,13780,13779,7554,7553,0,35,906,35,10,13736,13735,7533,7532,43,13783,13780,7553,13786,0,10,35,10,9,13739,13736,7532,13742,43,13786,7553,7556,13787,0,9,10,9,67,13742,7532,7535,13743,43,13792,13791,7558,7557,0,3128,3129,37,36,13748,13747,7537,7536,43,13795,13792,7557,13798,0,3132,3128,36,3131,13751,13748,7536,13754,43,13798,7557,7560,13799,0,3131,36,11,906,13754,7536,7539,13755,43,13803,13802,7562,7561,0,3135,3136,39,38,13759,13758,7541,7540,43,13806,13803,7561,13809,0,3139,3135,38,3142,13762,13759,7540,13765,43,13809,7561,7564,13810,0,3142,38,22,775,13765,7540,7543,13766,43,13814,13813,7566,7565,0,3144,3145,43,40,13770,13769,7545,7544,43,13817,13814,7565,13820,0,3148,3144,40,3153,13773,13770,7544,13776,43,13820,7565,7568,13821,0,3153,40,41,3152,13776,7544,7547,13777,43,13826,13825,7570,7569,0,3155,18,18,44,13782,13781,7549,7548,43,13829,13826,7569,13832,0,3158,3155,44,3163,13785,13782,7548,13788,43,13832,7569,7572,13833,0,3163,44,45,3162,13788,7548,7551,13789,43,13838,13837,7574,7573,0,3165,3166,47,46,13794,13793,7553,7552,43,13841,13838,7573,13844,0,3170,3165,46,3173,13797,13794,7552,13800,43,13844,7573,7570,13825,0,3173,46,18,18,13800,7552,7549,13781,43,13847,13846,7576,7575,0,3175,3176,50,48,13803,13802,7555,7554,43,13850,13847,7575,13853,0,3179,3175,48,3184,13806,13803,7554,13809,43,13853,7575,7578,13854,0,3184,48,49,3183,13809,7554,7557,13810,43,13858,13857,7580,7579,0,3186,3176,53,51,13814,13813,7559,7558,43,13861,13858,7579,13864,0,3189,3186,51,3193,13817,13814,7558,13820,43,13864,7579,7582,13865,0,3193,51,52,3192,13820,7558,7561,13821,43,13869,13868,7584,7583,0,3195,67,55,54,13825,13824,7563,7562,43,13872,13869,7583,13875,0,3198,3195,54,3201,13828,13825,7562,13831,43,13875,7583,7586,13876,0,3201,54,21,3091,13831,7562,7565,13832,43,13879,13664,7588,7587,0,3082,3082,27,27,13835,13620,7567,7566,43,13882,13879,7587,13885,0,3082,3082,27,27,13838,13835,7566,13841,43,13885,7587,7589,13886,0,27,27,26,26,13841,7566,7568,13842,43,13890,13889,7591,7590,0,3136,3136,56,56,13846,13845,7570,7569,43,13893,13890,7590,13896,0,3136,3136,56,56,13849,13846,7569,13852,43,13896,7590,7592,13897,0,56,56,18,18,13852,7569,7571,13853,43,13901,13900,7594,7593,0,777,777,57,57,13857,13856,7573,7572,43,13904,13901,7593,13907,0,777,777,57,57,13860,13857,7572,13863,43,13907,7593,7596,13908,0,57,57,23,23,13863,7572,7575,13864,43,13912,13911,7598,7597,0,3105,3105,58,58,13868,13867,7577,7576,43,13915,13912,7597,13918,0,3105,3105,58,58,13871,13868,7576,13874,43,13918,7597,7600,13919,0,58,58,30,30,13874,7576,7579,13875,43,13924,13923,7602,7601,0,775,775,14,14,13880,13879,7581,7580,43,13927,13924,7601,13930,0,775,775,14,14,13883,13880,7580,13886,43,13930,7601,7604,13931,0,14,14,12,12,13886,7580,7583,13887,43,13934,13889,7606,7605,0,3136,3136,39,39,13890,13845,7585,7584,43,13937,13934,7605,13940,0,3136,3136,39,39,13893,13890,7584,13896,43,13940,7605,7607,13941,0,39,39,23,23,13896,7584,7586,13897,43,13944,13813,7609,7608,0,3145,3145,59,59,13900,13769,7588,7587,43,13947,13944,7608,13950,0,3145,3145,59,59,13903,13900,7587,13906,43,13950,7608,7610,13951,0,59,59,18,18,13906,7587,7589,13907,43,13955,13954,7612,7611,0,3202,3202,60,60,13911,13910,7591,7590,43,13958,13955,7611,13961,0,3202,3202,60,60,13914,13911,7590,13917,43,13961,7611,7613,13962,0,60,60,42,42,13917,7590,7592,13918,43,13965,13846,7615,7614,0,3176,3176,53,53,13921,13802,7594,7593,43,13968,13965,7614,13971,0,3176,3176,53,53,13924,13921,7593,13927,43,13971,7614,7617,13972,0,53,53,2,2,13927,7593,7596,13928,43,13975,13911,7619,7618,0,3105,3105,25,25,13931,13867,7598,7597,43,13978,13975,7618,13981,0,3105,3105,25,25,13934,13931,7597,13937,43,13981,7618,7620,13982,0,25,25,26,26,13937,7597,7599,13938,43,13986,13985,7622,7621,0,776,776,61,61,13942,13941,7601,7600,43,13989,13986,7621,13992,0,776,776,61,61,13945,13942,7600,13948,43,13992,7621,7623,13993,0,61,61,30,30,13948,7600,7602,13949,43,13997,13996,7625,7624,0,3192,3192,62,62,13953,13952,7604,7603,43,14000,13997,7624,14003,0,3192,3192,62,62,13956,13953,7603,13959,43,14003,7624,7627,14004,0,62,62,16,16,13959,7603,7606,13960,43,14009,14008,7629,7628,0,67,67,55,55,13965,13964,7608,7607,43,14012,14009,7628,14015,0,67,67,55,55,13968,13965,7607,13971,43,14015,7628,7630,14016,0,55,55,6,6,13971,7607,7609,13972,43,14021,14020,7632,7631,0,906,906,35,35,13977,13976,7611,7610,43,14024,14021,7631,14027,0,906,906,35,35,13980,13977,7610,13983,43,14027,7631,7634,14028,0,35,35,10,10,13983,7610,7613,13984,43,14031,13954,7636,7635,0,3202,3202,63,63,13987,13910,7615,7614,43,14034,14031,7635,14037,0,3202,3202,63,63,13990,13987,7614,13993,43,14037,7635,7637,14038,0,63,63,12,12,13993,7614,7616,13994,43,14041,13675,7639,7638,0,3091,3091,64,64,13997,13631,7618,7617,43,14044,14041,7638,14047,0,3091,3091,64,64,14000,13997,7617,14003,43,14047,7638,7640,14048,0,64,64,42,42,14003,7617,7619,14004,43,14053,14052,7642,7641,0,19,18,18,19,14009,14008,7621,7620,43,14056,14053,7641,14059,0,3082,19,19,3082,14012,14009,7620,14015,43,14059,7641,7644,14060,0,3082,19,19,3082,14015,7620,7623,14016,43,14065,14064,7646,7645,0,39,23,23,39,14021,14020,7625,7624,43,14068,14065,7645,14071,0,3136,39,39,3136,14024,14021,7624,14027,43,14071,7645,7648,14072,0,3136,39,39,3136,14027,7624,7627,14028,43,14077,14076,7650,7649,0,777,777,57,57,14033,14032,7629,7628,43,14080,14077,7649,14083,0,777,777,57,57,14036,14033,7628,14039,43,14083,7649,7646,14064,0,57,57,23,23,14039,7628,7625,14020,43,14087,14086,7652,7651,0,3105,3105,58,58,14043,14042,7631,7630,43,14090,14087,7651,14093,0,3105,3105,58,58,14046,14043,7630,14049,43,14093,7651,7654,14094,0,58,58,30,30,14049,7630,7633,14050,43,14099,14098,7656,7655,0,775,775,14,14,14055,14054,7635,7634,43,14102,14099,7655,14105,0,775,775,14,14,14058,14055,7634,14061,43,14105,7655,7658,14106,0,14,14,12,12,14061,7634,7637,14062,43,14111,14110,7660,7659,0,3136,3136,39,39,14067,14066,7639,7638,43,14114,14111,7659,14117,0,3136,3136,39,39,14070,14067,7638,14073,43,14117,7659,7662,14118,0,39,39,23,23,14073,7638,7641,14074,43,14123,14122,7664,7663,0,43,42,42,43,14079,14078,7643,7642,43,14126,14123,7663,14129,0,3145,43,43,3145,14082,14079,7642,14085,43,14129,7663,7666,14130,0,3145,43,43,3145,14085,7642,7645,14086,43,14133,14106,7658,7667,0,63,12,12,63,14089,14062,7637,7646,43,14136,14133,7667,14139,0,3202,63,63,3202,14092,14089,7646,14095,43,14139,7667,7668,14140,0,3202,63,63,3202,14095,7646,7647,14096,43,14145,14144,7670,7669,0,3176,3176,50,50,14101,14100,7649,7648,43,14148,14145,7669,14151,0,3176,3176,50,50,14104,14101,7648,14107,43,14151,7669,7672,14152,0,50,50,26,26,14107,7648,7651,14108,43,14157,14156,7674,7673,0,58,30,30,58,14113,14112,7653,7652,43,14160,14157,7673,14163,0,3105,58,58,3105,14116,14113,7652,14119,43,14163,7673,7676,14164,0,3105,58,58,3105,14119,7652,7655,14120,43,14168,14167,7678,7677,0,15,16,16,15,14124,14123,7657,7656,43,14171,14168,7677,14174,0,776,15,15,776,14127,14124,7656,14130,43,14174,7677,7680,14175,0,776,15,15,776,14130,7656,7659,14131,43,14179,14178,7682,7681,0,3192,3192,52,52,14135,14134,7661,7660,43,14182,14179,7681,14185,0,3192,3192,52,52,14138,14135,7660,14141,43,14185,7681,7684,14186,0,52,52,2,2,14141,7660,7663,14142,43,14191,14190,7686,7685,0,67,67,55,55,14147,14146,7665,7664,43,14194,14191,7685,14197,0,67,67,55,55,14150,14147,7664,14153,43,14197,7685,7688,14198,0,55,55,6,6,14153,7664,7667,14154,43,14203,14202,7690,7689,0,11,12,12,11,14159,14158,7669,7668,43,14206,14203,7689,14209,0,906,11,11,906,14162,14159,7668,14165,43,14209,7689,7692,14210,0,906,11,11,906,14165,7668,7671,14166,43,14215,14214,7694,7693,0,3202,3202,63,63,14171,14170,7673,7672,43,14218,14215,7693,14221,0,3202,3202,63,63,14174,14171,7672,14177,43,14221,7693,7690,14202,0,63,63,12,12,14177,7672,7669,14158,43,14223,14198,7688,7695,0,21,6,6,21,14179,14154,7667,7674,43,14226,14223,7695,14229,0,3091,21,21,3091,14182,14179,7674,14185,43,14229,7695,7696,14230,0,3091,21,21,3091,14185,7674,7675,14186,43,14235,14234,7698,7697,0,19,18,18,19,14191,14190,7677,7676,43,14238,14235,7697,14241,0,3082,19,19,3082,14194,14191,7676,14197,43,14241,7697,7700,14242,0,3082,19,19,3082,14197,7676,7679,14198,43,14247,14246,7702,7701,0,39,23,23,39,14203,14202,7681,7680,43,14250,14247,7701,14253,0,3136,39,39,3136,14206,14203,7680,14209,43,14253,7701,7704,14254,0,3136,39,39,3136,14209,7680,7683,14210,43,14259,14258,7706,7705,0,777,777,65,65,14215,14214,7685,7684,43,14262,14259,7705,14265,0,777,777,65,65,14218,14215,7684,14221,43,14265,7705,7708,14266,0,65,65,30,30,14221,7684,7687,14222,43,14269,13730,7536,7709,0,3105,3105,25,25,14225,13686,7515,7688,43,14272,14269,7709,14275,0,3105,3105,25,25,14228,14225,7688,14231,43,14275,7709,7710,14276,0,25,25,26,26,14231,7688,7689,14232,43,14279,13810,7564,7711,0,775,775,22,22,14235,13766,7543,7690,43,14282,14279,7711,14285,0,775,775,22,22,14238,14235,7690,14241,43,14285,7711,7712,14286,0,22,22,23,23,14241,7690,7691,14242,43,14289,13802,7714,7713,0,3136,3136,56,56,14245,13758,7693,7692,43,14292,14289,7713,14295,0,3136,3136,56,56,14248,14245,7692,14251,43,14295,7713,7716,14296,0,56,56,18,18,14251,7692,7695,14252,43,14301,14300,7718,7717,0,43,42,42,43,14257,14256,7697,7696,43,14304,14301,7717,14307,0,3145,43,43,3145,14260,14257,7696,14263,43,14307,7717,7720,14308,0,3145,43,43,3145,14263,7696,7699,14264,43,14313,14312,7722,7721,0,63,12,12,63,14269,14268,7701,7700,43,14316,14313,7721,14319,0,3202,63,63,3202,14272,14269,7700,14275,43,14319,7721,7724,14320,0,3202,63,63,3202,14275,7700,7703,14276,43,14323,13868,7726,7725,0,67,67,9,9,14279,13824,7705,7704,43,14326,14323,7725,14329,0,67,67,9,9,14282,14279,7704,14285,43,14329,7725,7728,14330,0,9,9,10,10,14285,7704,7707,14286,43,14335,14334,7730,7729,0,11,12,12,11,14291,14290,7709,7708,43,14338,14335,7729,14341,0,906,11,11,906,14294,14291,7708,14297,43,14341,7729,7732,14342,0,906,11,11,906,14297,7708,7711,14298,43,14347,14346,7734,7733,0,3202,3202,60,60,14303,14302,7713,7712,43,14350,14347,7733,14353,0,3202,3202,60,60,14306,14303,7712,14309,43,14353,7733,7736,14354,0,60,60,42,42,14309,7712,7715,14310,43,14359,14358,7738,7737,0,21,6,6,21,14315,14314,7717,7716,43,14362,14359,7737,14365,0,3091,21,21,3091,14318,14315,7716,14321,43,14365,7737,7740,14366,0,3091,21,21,3091,14321,7716,7719,14322,43,14370,14369,7742,7741,0,3192,3192,52,52,14326,14325,7721,7720,43,14373,14370,7741,14376,0,3192,3192,52,52,14329,14326,7720,14332,43,14376,7741,7743,14377,0,52,52,2,2,14332,7720,7722,14333,43,14380,13597,7745,7744,0,3045,3045,66,66,14336,13553,7724,7723,43,14383,14380,7744,14386,0,3045,3045,66,66,14339,14336,7723,14342,43,14386,7744,7747,14387,0,66,66,18,18,14342,7723,7726,14343,43,14390,13617,7498,7748,0,774,774,5,5,14346,13573,7477,7727,43,14392,14390,7748,14394,0,774,774,5,5,14348,14346,7727,14350,43,14394,7748,7749,14395,0,5,5,6,6,14350,7727,7728,14351,43,14400,14399,14668,7751,0,9,10,10,9,14356,14355,14624,7730,43,14403,14400,7751,7750,0,67,9,9,67,14359,14356,7730,7729,43,9129,14393,14396,14406,0,5,5,6,6,9108,14349,14352,14362,43,14410,14409,9179,9178,0,11,12,12,11,14366,14365,9158,9157,43,14413,14410,9178,14669,0,906,11,11,906,14369,14366,9157,14625,43,14416,13644,7755,7754,0,775,775,22,22,14372,13600,7734,7733,43,14418,14416,7754,14419,0,775,775,22,22,14374,14372,7733,14375,43,14419,7754,7757,14420,0,22,22,23,23,14375,7733,7736,14376,43,14423,14422,7759,7758,0,777,777,65,65,14379,14378,7738,7737,43,14425,14423,7758,14426,0,777,777,65,65,14381,14379,7737,14382,43,14426,7758,7761,14427,0,65,65,30,30,14382,7737,7740,14383,43,14429,13661,7512,7762,0,776,776,15,15,14385,13617,7491,7741,43,14431,14429,7762,14432,0,776,776,15,15,14387,14385,7741,14388,43,14432,7762,7763,14433,0,15,15,16,16,14388,7741,7742,14389,43,14437,14436,7765,7764,0,15,16,16,15,14393,14392,7744,7743,43,14440,14437,7764,14443,0,776,15,15,776,14396,14393,7743,14399,43,14443,7764,7767,14444,0,776,15,15,776,14399,7743,7746,14400,43,14447,13755,7769,7768,0,30,30,30,30,14403,13711,7748,7747,43,14450,14447,7768,14453,0,30,30,30,30,14406,14403,7747,14409,43,14453,7768,7771,14454,0,30,30,30,30,14409,7747,7750,14410,43,14459,14458,7773,7772,0,776,776,61,61,14415,14414,7752,7751,43,14462,14459,7772,14465,0,776,776,61,61,14418,14415,7751,14421,43,14465,7772,7774,14466,0,61,61,30,30,14421,7751,7753,14422,43,14469,13740,7540,7775,0,16,16,16,16,14425,13696,7519,7754,43,14472,14469,7775,14475,0,16,16,16,16,14428,14425,7754,14431,43,14475,7775,7776,14476,0,16,16,16,16,14431,7754,7755,14432,43,14479,14444,7767,7777,0,776,776,15,15,14435,14400,7746,7756,43,14482,14479,7777,14485,0,776,776,15,15,14438,14435,7756,14441,43,14485,7777,7778,14486,0,15,15,16,16,14441,7756,7757,14442,43,14491,14490,7780,7779,0,30,30,30,30,14447,14446,7759,7758,43,14494,14491,7779,14497,0,30,30,30,30,14450,14447,7758,14453,43,14497,7779,7782,14498,0,30,30,30,30,14453,7758,7761,14454,43,14503,14502,7784,7783,0,776,776,61,61,14459,14458,7763,7762,43,14506,14503,7783,14509,0,776,776,61,61,14462,14459,7762,14465,43,14509,7783,7786,14510,0,61,61,30,30,14465,7762,7765,14466,43,14515,14514,7788,7787,0,16,16,16,16,14471,14470,7767,7766,43,14518,14515,7787,14521,0,16,16,16,16,14474,14471,7766,14477,43,14521,7787,7790,14522,0,16,16,16,16,14477,7766,7769,14478,43,14525,14369,7792,7791,0,3204,3192,62,68,14481,14325,7771,7770,43,14528,14525,7791,14531,0,3207,3204,68,3212,14484,14481,7770,14487,43,14531,7791,7793,14532,0,3212,68,69,3211,14487,7770,7772,14488,43,14537,14536,7795,7794,0,3214,3215,72,70,14493,14492,7774,7773,43,14540,14537,7794,14543,0,3219,3214,70,3224,14496,14493,7773,14499,43,14543,7794,7797,14544,0,3224,70,71,3223,14499,7773,7776,14500,43,14549,14548,7799,7798,0,3226,3227,74,73,14505,14504,7778,7777,43,14552,14549,7798,14555,0,3231,3226,73,3234,14508,14505,7777,14511,43,14555,7798,7795,14536,0,3234,73,72,3215,14511,7777,7774,14492,43,14559,14558,7801,7800,0,3236,3237,78,75,14515,14514,7780,7779,43,14562,14559,7800,14565,0,3241,3236,75,3244,14518,14515,7779,14521,43,14565,7800,7803,14566,0,3244,75,76,3243,14521,7779,7782,14522,43,14569,13996,7805,7804,0,3246,3192,52,79,14525,13952,7784,7783,43,14572,14569,7804,14575,0,3249,3246,79,3252,14528,14525,7783,14531,43,14575,7804,7806,14576,0,3252,79,2,2,14531,7783,7785,14532,43,14579,13985,7808,7807,0,3240,776,15,80,14535,13941,7787,7786,43,14582,14579,7807,14585,0,3241,3240,80,3244,14538,14535,7786,14541,43,14585,7807,7809,14586,0,3244,80,81,3243,14541,7786,7788,14542,43,14589,13900,7811,7810,0,777,777,65,65,14545,13856,7790,7789,43,14592,14589,7810,14595,0,777,777,65,65,14548,14545,7789,14551,43,14595,7810,7812,14596,0,65,65,30,30,14551,7789,7791,14552,43,14601,14600,7814,7813,0,3254,30,30,82,14557,14556,7793,7792,43,14604,14601,7813,14607,0,3257,3254,82,3262,14560,14557,7792,14563,43,14607,7813,7815,14608,0,3262,82,83,3261,14563,7792,7794,14564,43,14613,14612,7817,7816,0,3264,3129,37,84,14569,14568,7796,7795,43,14616,14613,7816,14619,0,3267,3264,84,3270,14572,14569,7795,14575,43,14619,7816,7818,13719,0,3270,84,14,775,14575,7795,7797,13675,43,14621,14422,7820,7819,0,3272,777,57,85,14577,14378,7799,7798,43,14624,14621,7819,14627,0,3275,3272,85,3280,14580,14577,7798,14583,43,14627,7819,7821,14628,0,3280,85,86,3279,14583,7798,7800,14584,43,14631,13865,7582,7822,0,3192,3192,52,52,14587,13821,7561,7801,43,14634,14631,7822,14637,0,3192,3192,52,52,14590,14587,7801,14593,43,14637,7822,7823,14638,0,52,52,2,2,14593,7801,7802,14594,43,14643,14642,7825,7824,0,61,30,30,61,14599,14598,7804,7803,43,14646,14643,7824,14649,0,776,61,61,776,14602,14599,7803,14605,43,14649,7824,7826,14175,0,776,61,61,776,14605,7803,7805,14131,43,14651,14152,7672,7827,0,25,26,26,25,14607,14108,7651,7806,43,14654,14651,7827,14657,0,3105,25,25,3105,14610,14607,7806,14613,43,14657,7827,7828,14164,0,3105,25,25,3105,14613,7806,7807,14120,43,14659,13857,7830,7829,0,3176,3176,50,50,14615,13813,7809,7808,43,14662,14659,7829,14665,0,3176,3176,50,50,14618,14615,7808,14621,43,14665,7829,7832,14666,0,50,50,26,26,14621,7808,7811,14622,43,14670,7746,7745,13596,0,18,18,66,3043,14626,7725,7724,13552,43,14671,14670,13596,13600,0,18,18,3043,3049,14627,14626,13552,13556,43,13600,13596,13598,13601,0,3049,3043,3044,3048,13556,13552,13554,13557,43,14672,14671,13600,13599,0,18,18,3049,3046,14628,14627,13556,13555,43,14673,14672,13599,14674,0,18,18,3046,3281,14629,14628,13555,14630,43,14674,13599,13602,14675,0,3281,3046,3047,3282,14630,13555,13558,14631,43,13602,13601,13604,13603,0,3047,3048,3053,3050,13558,13557,13560,13559,43,14675,13602,13603,14676,0,3282,3047,3050,3283,14631,13558,13559,14632,43,14676,13603,13606,14677,0,3283,3050,3051,3284,14632,13559,13562,14633,43,14678,7551,13608,13607,0,3285,33,3057,3054,14634,7530,13564,13563,43,14679,14678,13607,13612,0,3286,3285,3054,3061,14635,14634,13563,13568,43,13612,13607,13610,13613,0,3061,3054,3055,3060,13568,13563,13566,13569,43,14680,14679,13612,13611,0,3287,3286,3061,3058,14636,14635,13568,13567,43,14673,14680,13611,14672,0,18,3287,3058,18,14629,14636,13567,14628,43,14672,13611,13614,14671,0,18,3058,3059,18,14628,13567,13570,14627,43,13614,13613,13616,13615,0,3059,3060,3063,3062,13570,13569,13572,13571,43,14671,13614,13615,14670,0,18,3059,3062,18,14627,13570,13571,14626,43,14670,13615,13618,7746,0,18,3062,421,18,14626,13571,13574,7725,43,13766,7555,13620,13619,0,3118,10,10,3064,13722,7534,13576,13575,43,13767,13766,13619,13624,0,3117,3118,3064,3069,13723,13722,13575,13580,43,13624,13619,13622,13625,0,3069,3064,3065,3068,13580,13575,13578,13581,43,7550,13767,13624,13623,0,34,3117,3069,3066,7529,13723,13580,13579,43,7551,7550,13623,13608,0,33,34,3066,3057,7530,7529,13579,13564,43,13608,13623,13626,13609,0,3057,3066,3067,3056,13564,13579,13582,13565,43,13626,13625,13628,13627,0,3067,3068,3071,3070,13582,13581,13584,13583,43,13609,13626,13627,7496,0,3056,3067,3070,7,13565,13582,13583,7475,43,7496,13627,13630,7497,0,7,3070,55,6,7475,13583,13586,7476,43,13778,7559,13632,13631,0,11,12,12,11,13734,7538,13588,13587,43,13779,13778,13631,13636,0,906,11,11,906,13735,13734,13587,13592,43,13636,13631,13634,13637,0,906,11,11,906,13592,13587,13590,13593,43,7554,13779,13636,13635,0,35,906,906,35,7533,13735,13592,13591,43,7555,7554,13635,13620,0,10,35,35,10,7534,7533,13591,13576,43,13620,13635,13638,13621,0,10,35,35,10,13576,13591,13594,13577,43,13638,13637,13640,13639,0,35,906,906,35,13594,13593,13596,13595,43,13621,13638,13639,7500,0,10,35,35,10,13577,13594,13595,7479,43,7500,13639,13642,7501,0,10,35,35,10,7479,13595,13598,7480,43,7821,7756,7755,13643,0,86,23,22,3072,7800,7735,7734,13599,43,14628,7821,13643,13647,0,3279,86,3072,3077,14584,7800,13599,13603,43,13647,13643,13645,13648,0,3077,3072,3073,3076,13603,13599,13601,13604,43,14629,14628,13647,13646,0,3278,3279,3077,3074,14585,14584,13603,13602,43,14681,14629,13646,13790,0,3288,3278,3074,3130,14637,14585,13602,13746,43,13790,13646,13649,13791,0,3130,3074,3075,3129,13746,13602,13605,13747,43,13649,13648,13651,13650,0,3075,3076,3079,3078,13605,13604,13607,13606,43,13791,13649,13650,7558,0,3129,3075,3078,37,13747,13605,13606,7537,43,7558,13650,13632,7559,0,37,3078,12,12,7537,13606,13588,7538,43,14457,7539,7538,13652,0,15,16,16,15,14413,7518,7517,13608,43,14458,14457,13652,13656,0,776,15,15,776,14414,14413,13608,13612,43,13656,13652,13654,13657,0,776,15,15,776,13612,13608,13610,13613,43,7773,14458,13656,13655,0,61,776,776,61,7752,14414,13612,13611,43,7547,7773,13655,7548,0,30,61,61,30,7526,7752,13611,7527,43,7548,13655,13658,13763,0,30,61,61,30,7527,13611,13614,13719,43,13658,13657,13660,13659,0,61,776,776,61,13614,13613,13616,13615,43,13763,13658,13659,13764,0,30,61,61,30,13719,13614,13615,13720,43,13764,13659,13662,7760,0,30,61,61,30,13720,13615,13618,7739,43,7578,7577,7588,13663,0,49,26,27,3080,7557,7556,7567,13619,43,13854,7578,13663,13667,0,3183,49,3080,3086,13810,7557,13619,13623,43,13667,13663,13665,13668,0,3086,3080,3081,3085,13623,13619,13621,13624,43,13855,13854,13667,13666,0,3182,3183,3086,3083,13811,13810,13623,13622,43,14682,13855,13666,13836,0,3289,3182,3083,3167,14638,13811,13622,13792,43,13836,13666,13669,13837,0,3167,3083,3084,3166,13792,13622,13625,13793,43,13669,13668,13671,13670,0,3084,3085,3088,3087,13625,13624,13627,13626,43,13837,13669,13670,7574,0,3166,3084,3087,47,13793,13625,13626,7553,43,7574,13670,13673,7571,0,47,3087,18,18,7553,13626,13629,7550,43,7568,7567,7639,13674,0,41,42,64,3089,7547,7546,7618,13630,43,13821,7568,13674,13678,0,3152,41,3089,3095,13777,7547,13630,13634,43,13678,13674,13676,13679,0,3095,3089,3090,3094,13634,13630,13632,13635,43,13822,13821,13678,13677,0,3151,3152,3095,3092,13778,13777,13634,13633,43,14683,13822,13677,14684,0,3290,3151,3092,3291,14639,13778,13633,14640,43,14684,13677,13680,14685,0,3291,3092,3093,3292,14640,13633,13636,14641,43,13680,13679,13682,13681,0,3093,3094,3097,3096,13636,13635,13638,13637,43,14685,13680,13681,14686,0,3292,3093,3096,3293,14641,13636,13637,14642,43,14686,13681,13684,14687,0,3293,3096,6,6,14642,13637,13640,14643,43,14007,7633,13686,13685,0,9,10,10,9,13963,7612,13642,13641,43,14008,14007,13685,13690,0,67,9,9,67,13964,13963,13641,13646,43,13690,13685,13688,13691,0,67,9,9,67,13646,13641,13644,13647,43,7629,14008,13690,13689,0,55,67,67,55,7608,13964,13646,13645,43,7519,7629,13689,7520,0,6,55,55,6,7498,7608,13645,7499,43,7520,13689,13692,13683,0,6,55,55,6,7499,13645,13648,13639,43,13692,13691,13694,13693,0,55,67,67,55,13648,13647,13650,13649,43,13683,13692,13693,13684,0,6,55,55,6,13639,13648,13649,13640,43,13684,13693,13696,14687,0,6,55,55,6,13640,13649,13652,14643,43,14019,7603,13698,13697,0,11,12,12,11,13975,7582,13654,13653,43,14020,14019,13697,13702,0,906,11,11,906,13976,13975,13653,13658,43,13702,13697,13700,13703,0,906,11,11,906,13658,13653,13656,13659,43,7632,14020,13702,13701,0,35,906,906,35,7611,13976,13658,13657,43,7633,7632,13701,13686,0,10,35,35,10,7612,7611,13657,13642,43,13686,13701,13704,13687,0,10,35,35,10,13642,13657,13660,13643,43,13704,13703,13706,13705,0,35,906,906,35,13660,13659,13662,13661,43,13687,13704,13705,7522,0,10,35,35,10,13643,13660,13661,7501,43,7522,13705,13708,7523,0,10,35,35,10,7501,13661,13664,7502,43,13922,7595,13710,13709,0,22,23,23,22,13878,7574,13666,13665,43,13923,13922,13709,13714,0,775,22,22,775,13879,13878,13665,13670,43,13714,13709,13712,13715,0,775,22,22,775,13670,13665,13668,13671,43,7602,13923,13714,13713,0,14,775,775,14,7581,13879,13670,13669,43,7603,7602,13713,13698,0,12,14,14,12,7582,7581,13669,13654,43,13698,13713,13716,13699,0,12,14,14,12,13654,13669,13672,13655,43,13716,13715,13718,13717,0,14,775,775,14,13672,13671,13674,13673,43,13699,13716,13717,7526,0,12,14,14,12,13655,13672,13673,7505,43,7526,13717,7818,7527,0,12,14,14,12,7505,13673,7797,7506,43,14688,14689,13721,13720,0,56,18,19,3098,14644,14645,13677,13676,43,14690,14688,13720,13725,0,3136,56,3098,3103,14646,14644,13676,13681,43,13725,13720,13723,13726,0,3103,3098,3099,3102,13681,13676,13679,13682,43,14691,14690,13725,13724,0,39,3136,3103,3100,14647,14646,13681,13680,43,14692,14691,13724,14257,0,23,39,3100,57,14648,14647,13680,14213,43,14257,13724,13727,14258,0,57,3100,3101,777,14213,13680,13683,14214,43,13727,13726,13729,13728,0,3101,3102,3106,3104,13683,13682,13685,13684,43,14258,13727,13728,7706,0,777,3101,3104,65,14214,13683,13684,7685,43,7706,13728,13731,7707,0,65,3104,58,30,7685,13684,13687,7686,43,7793,7511,7510,13732,0,69,16,16,3107,7772,7490,7489,13688,43,14532,7793,13732,13735,0,3211,69,3107,3110,14488,7772,13688,13691,43,13735,13732,13733,13736,0,3110,3107,3108,69,13691,13688,13689,13692,43,14533,14532,13735,13734,0,3210,3211,3110,3109,14489,14488,13691,13690,43,14693,14533,13734,14694,0,3294,3210,3109,3210,14649,14489,13690,14650,43,14694,13734,13737,14695,0,3210,3109,3110,3211,14650,13690,13693,14651,43,13737,13736,13739,13738,0,3110,69,3108,3107,13693,13692,13695,13694,43,14695,13737,13738,14696,0,3211,3110,3107,69,14651,13693,13694,14652,43,14696,13738,13741,14697,0,69,3107,16,16,14652,13694,13697,14653,43,14698,14699,13743,13742,0,15,16,16,15,14654,14655,13699,13698,43,14700,14698,13742,13747,0,776,15,15,776,14656,14654,13698,13703,43,13747,13742,13745,13748,0,776,15,15,776,13703,13698,13701,13704,43,14701,14700,13747,13746,0,61,776,776,61,14657,14656,13703,13702,43,14702,14701,13746,14703,0,30,61,61,30,14658,14657,13702,14659,43,14703,13746,13749,14704,0,30,61,61,30,14659,13702,13705,14660,43,13749,13748,13751,13750,0,61,776,776,61,13705,13704,13707,13706,43,14704,13749,13750,14705,0,30,61,61,30,14660,13705,13706,14661,43,14705,13750,13753,14706,0,30,61,61,30,14661,13706,13709,14662,43,7815,7770,7769,13754,0,83,30,30,3111,7794,7749,7748,13710,43,14608,7815,13754,13758,0,3261,83,3111,3114,14564,7794,13710,13714,43,13758,13754,13756,13759,0,3114,3111,3112,83,13714,13710,13712,13715,43,14609,14608,13758,13757,0,3260,3261,3114,3113,14565,14564,13714,13713,43,14707,14609,13757,14708,0,3295,3260,3113,3260,14663,14565,13713,14664,43,14708,13757,13760,14709,0,3260,3113,3114,3261,14664,13713,13716,14665,43,13760,13759,13762,13761,0,3114,83,3112,3111,13716,13715,13718,13717,43,14709,13760,13761,14710,0,3261,3114,3111,83,14665,13716,13717,14666,43,14710,13761,13764,7760,0,83,3111,30,30,14666,13717,13720,7739,43,7556,7555,13766,13765,0,9,10,3118,3115,7535,7534,13722,13721,43,13787,7556,13765,13770,0,67,9,3115,3122,13743,7535,13721,13726,43,13770,13765,13768,13771,0,3122,3115,3116,3121,13726,13721,13724,13727,43,13788,13787,13770,13769,0,55,67,3122,3119,13744,13743,13726,13725,43,14687,13788,13769,14686,0,6,55,3119,3293,14643,13744,13725,14642,43,14686,13769,13772,14685,0,3293,3119,3120,3292,14642,13725,13728,14641,43,13772,13771,13774,13773,0,3120,3121,3126,3123,13728,13727,13730,13729,43,14685,13772,13773,14684,0,3292,3120,3123,3291,14641,13728,13729,14640,43,14684,13773,13776,14683,0,3291,3123,3124,3290,14640,13729,13732,14639,43,7560,7559,13778,13777,0,11,12,11,906,7539,7538,13734,13733,43,13799,7560,13777,13782,0,906,11,906,35,13755,7539,13733,13738,43,13782,13777,13780,13783,0,35,906,35,10,13738,13733,13736,13739,43,13800,13799,13782,13781,0,35,906,35,10,13756,13755,13738,13737,43,7523,13800,13781,7524,0,10,35,10,9,7502,13756,13737,7503,43,7524,13781,13784,13695,0,9,10,9,67,7503,13737,13740,13651,43,13784,13783,13786,13785,0,9,10,9,67,13740,13739,13742,13741,43,13695,13784,13785,13696,0,67,9,67,55,13651,13740,13741,13652,43,13696,13785,13788,14687,0,55,67,55,6,13652,13741,13744,14643,43,14611,14681,13790,13789,0,3130,3288,3130,3127,14567,14637,13746,13745,43,14612,14611,13789,13794,0,3129,3130,3127,3128,14568,14567,13745,13750,43,13794,13789,13792,13795,0,3128,3127,3128,3132,13750,13745,13748,13751,43,7817,14612,13794,13793,0,37,3129,3128,36,7796,14568,13750,13749,43,7527,7817,13793,7528,0,12,37,36,11,7506,7796,13749,7507,43,7528,13793,13796,13707,0,11,36,3131,906,7507,13749,13752,13663,43,13796,13795,13798,13797,0,3131,3132,3131,3133,13752,13751,13754,13753,43,13707,13796,13797,13708,0,906,3131,3133,35,13663,13752,13753,13664,43,13708,13797,13800,7523,0,35,3133,35,10,13664,13753,13756,7502,43,14711,7715,7714,13801,0,59,18,56,3134,14667,7694,7693,13757,43,14712,14711,13801,13805,0,3145,59,3134,3140,14668,14667,13757,13761,43,13805,13801,13803,13806,0,3140,3134,3135,3139,13761,13757,13759,13762,43,14713,14712,13805,13804,0,43,3145,3140,3137,14669,14668,13761,13760,43,14714,14713,13804,14715,0,42,43,3137,60,14670,14669,13760,14671,43,14715,13804,13807,14716,0,60,3137,3138,3202,14671,13760,13763,14672,43,13807,13806,13809,13808,0,3138,3139,3142,3141,13763,13762,13765,13764,43,14716,13807,13808,14717,0,3202,3138,3141,63,14672,13763,13764,14673,43,14717,13808,13811,14718,0,63,3141,14,12,14673,13764,13767,14674,43,7516,7515,7609,13812,0,18,18,59,3143,7495,7494,7588,13768,43,13672,7516,13812,13816,0,18,18,3143,3149,13628,7495,13768,13772,43,13816,13812,13814,13817,0,3149,3143,3144,3148,13772,13768,13770,13773,43,13673,13672,13816,13815,0,18,18,3149,3146,13629,13628,13772,13771,43,7571,13673,13815,7572,0,18,18,3146,45,7550,13629,13771,7551,43,7572,13815,13818,13833,0,45,3146,3147,3162,7551,13771,13774,13789,43,13818,13817,13820,13819,0,3147,3148,3153,3150,13774,13773,13776,13775,43,13833,13818,13819,13834,0,3162,3147,3150,3161,13789,13774,13775,13790,43,13834,13819,13822,14683,0,3161,3150,3151,3290,13790,13775,13778,14639,43,14680,14673,13824,13823,0,3287,18,18,3154,14636,14629,13780,13779,43,14679,14680,13823,13828,0,3286,3287,3154,3159,14635,14636,13779,13784,43,13828,13823,13826,13829,0,3159,3154,3155,3158,13784,13779,13782,13785,43,14678,14679,13828,13827,0,3285,3286,3159,3156,14634,14635,13784,13783,43,7551,14678,13827,7552,0,33,3285,3156,32,7530,14634,13783,7531,43,7552,13827,13830,13775,0,32,3156,3157,3125,7531,13783,13786,13731,43,13830,13829,13832,13831,0,3157,3158,3163,3160,13786,13785,13788,13787,43,13775,13830,13831,13776,0,3125,3157,3160,3124,13731,13786,13787,13732,43,13776,13831,13834,14683,0,3124,3160,3161,3290,13732,13787,13790,14639,43,14719,14682,13836,13835,0,3296,3289,3167,3164,14675,14638,13792,13791,43,14720,14719,13835,13840,0,3297,3296,3164,3171,14676,14675,13791,13796,43,13840,13835,13838,13841,0,3171,3164,3165,3170,13796,13791,13794,13797,43,14721,14720,13840,13839,0,3298,3297,3171,3168,14677,14676,13796,13795,43,14677,14721,13839,14676,0,3284,3298,3168,3283,14633,14677,13795,14632,43,14676,13839,13842,14675,0,3283,3168,3169,3282,14632,13795,13798,14631,43,13842,13841,13844,13843,0,3169,3170,3173,3172,13798,13797,13800,13799,43,14675,13842,13843,14674,0,3282,3169,3172,3281,14631,13798,13799,14630,43,14674,13843,13824,14673,0,3281,3172,18,18,14630,13799,13780,14629,43,7806,7616,7615,13845,0,2,2,53,3174,7785,7595,7594,13801,43,14576,7806,13845,13849,0,2,2,3174,3180,14532,7785,13801,13805,43,13849,13845,13847,13850,0,3180,3174,3175,3179,13805,13801,13803,13806,43,14577,14576,13849,13848,0,2,2,3180,3177,14533,14532,13805,13804,43,7796,14577,13848,7797,0,2,2,3177,71,7775,14533,13804,7776,43,7797,13848,13851,14544,0,71,3177,3178,3223,7776,13804,13807,14500,43,13851,13850,13853,13852,0,3178,3179,3184,3181,13807,13806,13809,13808,43,14544,13851,13852,14545,0,3223,3178,3181,3222,14500,13807,13808,14501,43,14545,13852,13855,14682,0,3222,3181,3182,3289,14501,13808,13811,14638,43,14722,7831,7830,13856,0,25,26,50,3185,14678,7810,7809,13812,43,14723,14722,13856,13860,0,3105,25,3185,3190,14679,14678,13812,13816,43,13860,13856,13858,13861,0,3190,3185,3186,3189,13816,13812,13814,13817,43,14724,14723,13860,13859,0,58,3105,3190,3187,14680,14679,13816,13815,43,14725,14724,13859,14726,0,30,58,3187,61,14681,14680,13815,14682,43,14726,13859,13862,14727,0,61,3187,3188,776,14682,13815,13818,14683,43,13862,13861,13864,13863,0,3188,3189,3193,3191,13818,13817,13820,13819,43,14727,13862,13863,14728,0,776,3188,3191,15,14683,13818,13819,14684,43,14728,13863,13866,14729,0,15,3191,62,16,14684,13819,13822,14685,43,14730,7727,7726,13867,0,35,10,9,3194,14686,7706,7705,13823,43,14731,14730,13867,13871,0,906,35,3194,3199,14687,14686,13823,13827,43,13871,13867,13869,13872,0,3199,3194,3195,3198,13827,13823,13825,13828,43,14732,14731,13871,13870,0,11,906,3199,3196,14688,14687,13827,13826,43,14733,14732,13870,14345,0,12,11,3196,63,14689,14688,13826,14301,43,14345,13870,13873,14346,0,63,3196,3197,3202,14301,13826,13829,14302,43,13873,13872,13875,13874,0,3197,3198,3201,3200,13829,13828,13831,13830,43,14346,13873,13874,7734,0,3202,3197,3200,60,14302,13829,13830,7713,43,7734,13874,13877,7735,0,60,3200,64,42,7713,13830,13833,7714,43,7592,7515,7514,13878,0,18,18,19,19,7571,7494,7493,13834,43,13897,7592,13878,13881,0,18,18,19,19,13853,7571,13834,13837,43,13881,13878,13879,13882,0,19,19,3082,3082,13837,13834,13835,13838,43,13898,13897,13881,13880,0,18,18,19,19,13854,13853,13837,13836,43,7643,13898,13880,7644,0,18,18,19,19,7622,13854,13836,7623,43,7644,13880,13883,14060,0,19,19,3082,3082,7623,13836,13839,14016,43,13883,13882,13885,13884,0,3082,3082,27,27,13839,13838,13841,13840,43,14060,13883,13884,14061,0,3082,3082,27,27,14016,13839,13840,14017,43,14061,13884,13887,14734,0,27,27,26,26,14017,13840,13843,14690,43,7596,7595,7606,13888,0,23,23,39,39,7575,7574,7585,13844,43,13908,7596,13888,13892,0,23,23,39,39,13864,7575,13844,13848,43,13892,13888,13890,13893,0,39,39,3136,3136,13848,13844,13846,13849,43,13909,13908,13892,13891,0,23,23,39,39,13865,13864,13848,13847,43,7647,13909,13891,7648,0,23,23,39,39,7626,13865,13847,7627,43,7648,13891,13894,14072,0,39,39,3136,3136,7627,13847,13850,14028,43,13894,13893,13896,13895,0,3136,3136,56,56,13850,13849,13852,13851,43,14072,13894,13895,14073,0,3136,3136,56,56,14028,13850,13851,14029,43,14073,13895,13898,7643,0,56,56,18,18,14029,13851,13854,7622,43,7600,7599,7811,13899,0,30,30,65,65,7579,7578,7790,13855,43,13919,7600,13899,13903,0,30,30,65,65,13875,7579,13855,13859,43,13903,13899,13901,13904,0,65,65,777,777,13859,13855,13857,13860,43,13920,13919,13903,13902,0,30,30,65,65,13876,13875,13859,13858,43,7653,13920,13902,14075,0,30,30,65,65,7632,13876,13858,14031,43,14075,13902,13905,14076,0,65,65,777,777,14031,13858,13861,14032,43,13905,13904,13907,13906,0,777,777,57,57,13861,13860,13863,13862,43,14076,13905,13906,7650,0,777,777,57,57,14032,13861,13862,7629,43,7650,13906,13909,7647,0,57,57,23,23,7629,13862,13865,7626,43,7589,7577,7619,13910,0,26,26,25,25,7568,7556,7598,13866,43,13886,7589,13910,13914,0,26,26,25,25,13842,7568,13866,13870,43,13914,13910,13912,13915,0,25,25,3105,3105,13870,13866,13868,13871,43,13887,13886,13914,13913,0,26,26,25,25,13843,13842,13870,13869,43,14734,13887,13913,14085,0,26,26,25,25,14690,13843,13869,14041,43,14085,13913,13916,14086,0,25,25,3105,3105,14041,13869,13872,14042,43,13916,13915,13918,13917,0,3105,3105,58,58,13872,13871,13874,13873,43,14086,13916,13917,7652,0,3105,3105,58,58,14042,13872,13873,7631,43,7652,13917,13920,7653,0,58,58,30,30,7631,13873,13876,7632,43,7607,7595,13922,13921,0,23,23,22,22,7586,7574,13878,13877,43,13941,7607,13921,13926,0,23,23,22,22,13897,7586,13877,13882,43,13926,13921,13924,13927,0,22,22,775,775,13882,13877,13880,13883,43,13942,13941,13926,13925,0,23,23,22,22,13898,13897,13882,13881,43,7661,13942,13925,14097,0,23,23,22,22,7640,13898,13881,14053,43,14097,13925,13928,14098,0,22,22,775,775,14053,13881,13884,14054,43,13928,13927,13930,13929,0,775,775,14,14,13884,13883,13886,13885,43,14098,13928,13929,7656,0,775,775,14,14,14054,13884,13885,7635,43,7656,13929,13932,7657,0,14,14,12,12,7635,13885,13888,7636,43,7610,7515,7591,13933,0,18,18,56,56,7589,7494,7570,13889,43,13951,7610,13933,13936,0,18,18,56,56,13907,7589,13889,13892,43,13936,13933,13934,13937,0,56,56,3136,3136,13892,13889,13890,13893,43,13952,13951,13936,13935,0,18,18,56,56,13908,13907,13892,13891,43,14735,13952,13935,14109,0,18,18,56,56,14691,13908,13891,14065,43,14109,13935,13938,14110,0,56,56,3136,3136,14065,13891,13894,14066,43,13938,13937,13940,13939,0,3136,3136,39,39,13894,13893,13896,13895,43,14110,13938,13939,7660,0,3136,3136,39,39,14066,13894,13895,7639,43,7660,13939,13942,7661,0,39,39,23,23,7639,13895,13898,7640,43,7613,7567,7566,13943,0,42,42,43,43,7592,7546,7545,13899,43,13962,7613,13943,13946,0,42,42,43,43,13918,7592,13899,13902,43,13946,13943,13944,13947,0,43,43,3145,3145,13902,13899,13900,13903,43,13963,13962,13946,13945,0,42,42,43,43,13919,13918,13902,13901,43,7665,13963,13945,7666,0,42,42,43,43,7644,13919,13901,7645,43,7666,13945,13948,14130,0,43,43,3145,3145,7645,13901,13904,14086,43,13948,13947,13950,13949,0,3145,3145,59,59,13904,13903,13906,13905,43,14130,13948,13949,14131,0,3145,3145,59,59,14086,13904,13905,14087,43,14131,13949,13952,14735,0,59,59,18,18,14087,13905,13908,14691,43,7604,7603,7636,13953,0,12,12,63,63,7583,7582,7615,13909,43,13931,7604,13953,13957,0,12,12,63,63,13887,7583,13909,13913,43,13957,13953,13955,13958,0,63,63,3202,3202,13913,13909,13911,13914,43,13932,13931,13957,13956,0,12,12,63,63,13888,13887,13913,13912,43,7657,13932,13956,7668,0,12,12,63,63,7636,13888,13912,7647,43,7668,13956,13959,14140,0,63,63,3202,3202,7647,13912,13915,14096,43,13959,13958,13961,13960,0,3202,3202,60,60,13915,13914,13917,13916,43,14140,13959,13960,14141,0,3202,3202,60,60,14096,13915,13916,14097,43,14141,13960,13963,7665,0,60,60,42,42,14097,13916,13919,7644,43,7620,7577,7576,13964,0,26,26,50,50,7599,7556,7555,13920,43,13982,7620,13964,13967,0,26,26,50,50,13938,7599,13920,13923,43,13967,13964,13965,13968,0,50,50,3176,3176,13923,13920,13921,13924,43,13983,13982,13967,13966,0,26,26,50,50,13939,13938,13923,13922,43,14736,13983,13966,14737,0,26,26,50,50,14692,13939,13922,14693,43,14737,13966,13969,14738,0,50,50,3176,3176,14693,13922,13925,14694,43,13969,13968,13971,13970,0,3176,3176,53,53,13925,13924,13927,13926,43,14738,13969,13970,14739,0,3176,3176,53,53,14694,13925,13926,14695,43,14739,13970,13973,14740,0,53,53,2,2,14695,13926,13929,14696,43,7623,7599,7598,13974,0,30,30,58,58,7602,7578,7577,13930,43,13993,7623,13974,13977,0,30,30,58,58,13949,7602,13930,13933,43,13977,13974,13975,13978,0,58,58,3105,3105,13933,13930,13931,13934,43,13994,13993,13977,13976,0,30,30,58,58,13950,13949,13933,13932,43,14741,13994,13976,14742,0,30,30,58,58,14697,13950,13932,14698,43,14742,13976,13979,14743,0,58,58,3105,3105,14698,13932,13935,14699,43,13979,13978,13981,13980,0,3105,3105,25,25,13935,13934,13937,13936,43,14743,13979,13980,14744,0,3105,3105,25,25,14699,13935,13936,14700,43,14744,13980,13983,14736,0,25,25,26,26,14700,13936,13939,14692,43,7627,7626,7808,13984,0,16,16,15,15,7606,7605,7787,13940,43,14004,7627,13984,13988,0,16,16,15,15,13960,7606,13940,13944,43,13988,13984,13986,13989,0,15,15,776,776,13944,13940,13942,13945,43,14005,14004,13988,13987,0,16,16,15,15,13961,13960,13944,13943,43,14745,14005,13987,14746,0,16,16,15,15,14701,13961,13943,14702,43,14746,13987,13990,14747,0,15,15,776,776,14702,13943,13946,14703,43,13990,13989,13992,13991,0,776,776,61,61,13946,13945,13948,13947,43,14747,13990,13991,14748,0,776,776,61,61,14703,13946,13947,14704,43,14748,13991,13994,14741,0,61,61,30,30,14704,13947,13950,14697,43,7617,7616,7805,13995,0,2,2,52,52,7596,7595,7784,13951,43,13972,7617,13995,13999,0,2,2,52,52,13928,7596,13951,13955,43,13999,13995,13997,14000,0,52,52,3192,3192,13955,13951,13953,13956,43,13973,13972,13999,13998,0,2,2,52,52,13929,13928,13955,13954,43,14740,13973,13998,14749,0,2,2,52,52,14696,13929,13954,14705,43,14749,13998,14001,14750,0,52,52,3192,3192,14705,13954,13957,14706,43,14001,14000,14003,14002,0,3192,3192,62,62,13957,13956,13959,13958,43,14750,14001,14002,14751,0,3192,3192,62,62,14706,13957,13958,14707,43,14751,14002,14005,14745,0,62,62,16,16,14707,13958,13961,14701,43,7634,7633,14007,14006,0,10,10,9,9,7613,7612,13963,13962,43,14028,7634,14006,14011,0,10,10,9,9,13984,7613,13962,13967,43,14011,14006,14009,14012,0,9,9,67,67,13967,13962,13965,13968,43,14029,14028,14011,14010,0,10,10,9,9,13985,13984,13967,13966,43,14752,14029,14010,14189,0,10,10,9,9,14708,13985,13966,14145,43,14189,14010,14013,14190,0,9,9,67,67,14145,13966,13969,14146,43,14013,14012,14015,14014,0,67,67,55,55,13969,13968,13971,13970,43,14190,14013,14014,7686,0,67,67,55,55,14146,13969,13970,7665,43,7686,14014,14017,7687,0,55,55,6,6,7665,13970,13973,7666,43,7637,7603,14019,14018,0,12,12,11,11,7616,7582,13975,13974,43,14038,7637,14018,14023,0,12,12,11,11,13994,7616,13974,13979,43,14023,14018,14021,14024,0,11,11,906,906,13979,13974,13977,13980,43,14039,14038,14023,14022,0,12,12,11,11,13995,13994,13979,13978,43,7691,14039,14022,7692,0,12,12,11,11,7670,13995,13978,7671,43,7692,14022,14025,14210,0,11,11,906,906,7671,13978,13981,14166,43,14025,14024,14027,14026,0,906,906,35,35,13981,13980,13983,13982,43,14210,14025,14026,14211,0,906,906,35,35,14166,13981,13982,14167,43,14211,14026,14029,14752,0,35,35,10,10,14167,13982,13985,14708,43,7640,7567,7612,14030,0,42,42,60,60,7619,7546,7591,13986,43,14048,7640,14030,14033,0,42,42,60,60,14004,7619,13986,13989,43,14033,14030,14031,14034,0,60,60,3202,3202,13989,13986,13987,13990,43,14049,14048,14033,14032,0,42,42,60,60,14005,14004,13989,13988,43,14753,14049,14032,14213,0,42,42,60,60,14709,14005,13988,14169,43,14213,14032,14035,14214,0,60,60,3202,3202,14169,13988,13991,14170,43,14035,14034,14037,14036,0,3202,3202,63,63,13991,13990,13993,13992,43,14214,14035,14036,7694,0,3202,3202,63,63,14170,13991,13992,7673,43,7694,14036,14039,7691,0,63,63,12,12,7673,13992,13995,7670,43,7630,7519,7518,14040,0,6,6,21,21,7609,7498,7497,13996,43,14016,7630,14040,14043,0,6,6,21,21,13972,7609,13996,13999,43,14043,14040,14041,14044,0,21,21,3091,3091,13999,13996,13997,14000,43,14017,14016,14043,14042,0,6,6,21,21,13973,13972,13999,13998,43,7687,14017,14042,7696,0,6,6,21,21,7666,13973,13998,7675,43,7696,14042,14045,14230,0,21,21,3091,3091,7675,13998,14001,14186,43,14045,14044,14047,14046,0,3091,3091,64,64,14001,14000,14003,14002,43,14230,14045,14046,14231,0,3091,3091,64,64,14186,14001,14002,14187,43,14231,14046,14049,14753,0,64,64,42,42,14187,14002,14005,14709,43,7700,7699,14051,14050,0,19,18,18,19,7679,7678,14007,14006,43,14242,7700,14050,14055,0,3082,19,19,3082,14198,7679,14006,14011,43,14055,14050,14053,14056,0,3082,19,19,3082,14011,14006,14009,14012,43,14243,14242,14055,14054,0,27,3082,3082,27,14199,14198,14011,14010,43,14754,14243,14054,14755,0,26,27,27,26,14710,14199,14010,14711,43,14755,14054,14057,14756,0,26,27,27,26,14711,14010,14013,14712,43,14057,14056,14059,14058,0,27,3082,3082,27,14013,14012,14015,14014,43,14756,14057,14058,14757,0,26,27,27,26,14712,14013,14014,14713,43,14757,14058,14061,14734,0,26,27,27,26,14713,14014,14017,14690,43,7704,7703,14063,14062,0,39,23,23,39,7683,7682,14019,14018,43,14254,7704,14062,14067,0,3136,39,39,3136,14210,7683,14018,14023,43,14067,14062,14065,14068,0,3136,39,39,3136,14023,14018,14021,14024,43,14255,14254,14067,14066,0,56,3136,3136,56,14211,14210,14023,14022,43,7699,14255,14066,14051,0,18,56,56,18,7678,14211,14022,14007,43,14051,14066,14069,14052,0,18,56,56,18,14007,14022,14025,14008,43,14069,14068,14071,14070,0,56,3136,3136,56,14025,14024,14027,14026,43,14052,14069,14070,7642,0,18,56,56,18,14008,14025,14026,7621,43,7642,14070,14073,7643,0,18,56,56,18,7621,14026,14029,7622,43,7654,7653,14075,14074,0,30,30,65,65,7633,7632,14031,14030,43,14094,7654,14074,14079,0,30,30,65,65,14050,7633,14030,14035,43,14079,14074,14077,14080,0,65,65,777,777,14035,14030,14033,14036,43,14095,14094,14079,14078,0,30,30,65,65,14051,14050,14035,14034,43,14758,14095,14078,14759,0,30,30,65,65,14714,14051,14034,14715,43,14759,14078,14081,14760,0,65,65,777,777,14715,14034,14037,14716,43,14081,14080,14083,14082,0,777,777,57,57,14037,14036,14039,14038,43,14760,14081,14082,14761,0,777,777,57,57,14716,14037,14038,14717,43,14761,14082,14063,7703,0,57,57,23,23,14717,14038,14019,7682,43,14757,14734,14085,14084,0,26,26,25,25,14713,14690,14041,14040,43,14756,14757,14084,14089,0,26,26,25,25,14712,14713,14040,14045,43,14089,14084,14087,14090,0,25,25,3105,3105,14045,14040,14043,14046,43,14755,14756,14089,14088,0,26,26,25,25,14711,14712,14045,14044,43,14754,14755,14088,14762,0,26,26,25,25,14710,14711,14044,14718,43,14762,14088,14091,14763,0,25,25,3105,3105,14718,14044,14047,14719,43,14091,14090,14093,14092,0,3105,3105,58,58,14047,14046,14049,14048,43,14763,14091,14092,14764,0,3105,3105,58,58,14719,14047,14048,14720,43,14764,14092,14095,14758,0,58,58,30,30,14720,14048,14051,14714,43,7662,7661,14097,14096,0,23,23,22,22,7641,7640,14053,14052,43,14118,7662,14096,14101,0,23,23,22,22,14074,7641,14052,14057,43,14101,14096,14099,14102,0,22,22,775,775,14057,14052,14055,14058,43,14119,14118,14101,14100,0,23,23,22,22,14075,14074,14057,14056,43,14765,14119,14100,14766,0,23,23,22,22,14721,14075,14056,14722,43,14766,14100,14103,14767,0,22,22,775,775,14722,14056,14059,14723,43,14103,14102,14105,14104,0,775,775,14,14,14059,14058,14061,14060,43,14767,14103,14104,14768,0,775,775,14,14,14723,14059,14060,14724,43,14768,14104,14107,7723,0,14,14,12,12,14724,14060,14063,7702,43,14769,14735,14109,14108,0,18,18,56,56,14725,14691,14065,14064,43,14770,14769,14108,14113,0,18,18,56,56,14726,14725,14064,14069,43,14113,14108,14111,14114,0,56,56,3136,3136,14069,14064,14067,14070,43,14771,14770,14113,14112,0,18,18,56,56,14727,14726,14069,14068,43,14772,14771,14112,14773,0,18,18,56,56,14728,14727,14068,14729,43,14773,14112,14115,14774,0,56,56,3136,3136,14729,14068,14071,14730,43,14115,14114,14117,14116,0,3136,3136,39,39,14071,14070,14073,14072,43,14774,14115,14116,14775,0,3136,3136,39,39,14730,14071,14072,14731,43,14775,14116,14119,14765,0,39,39,23,23,14731,14072,14075,14721,43,7720,7719,14121,14120,0,43,42,42,43,7699,7698,14077,14076,43,14308,7720,14120,14125,0,3145,43,43,3145,14264,7699,14076,14081,43,14125,14120,14123,14126,0,3145,43,43,3145,14081,14076,14079,14082,43,14309,14308,14125,14124,0,59,3145,3145,59,14265,14264,14081,14080,43,14772,14309,14124,14771,0,18,59,59,18,14728,14265,14080,14727,43,14771,14124,14127,14770,0,18,59,59,18,14727,14080,14083,14726,43,14127,14126,14129,14128,0,59,3145,3145,59,14083,14082,14085,14084,43,14770,14127,14128,14769,0,18,59,59,18,14726,14083,14084,14725,43,14769,14128,14131,14735,0,18,59,59,18,14725,14084,14087,14691,43,7724,7723,14107,14132,0,63,12,12,63,7703,7702,14063,14088,43,14320,7724,14132,14135,0,3202,63,63,3202,14276,7703,14088,14091,43,14135,14132,14133,14136,0,3202,63,63,3202,14091,14088,14089,14092,43,14321,14320,14135,14134,0,60,3202,3202,60,14277,14276,14091,14090,43,7719,14321,14134,14121,0,42,60,60,42,7698,14277,14090,14077,43,14121,14134,14137,14122,0,42,60,60,42,14077,14090,14093,14078,43,14137,14136,14139,14138,0,60,3202,3202,60,14093,14092,14095,14094,43,14122,14137,14138,7664,0,42,60,60,42,14078,14093,14094,7643,43,7664,14138,14141,7665,0,42,60,60,42,7643,14094,14097,7644,43,7684,7683,14143,14142,0,2,2,53,53,7663,7662,14099,14098,43,14186,7684,14142,14147,0,2,2,53,53,14142,7663,14098,14103,43,14147,14142,14145,14148,0,53,53,3176,3176,14103,14098,14101,14104,43,14187,14186,14147,14146,0,2,2,53,53,14143,14142,14103,14102,43,14740,14187,14146,14739,0,2,2,53,53,14696,14143,14102,14695,43,14739,14146,14149,14738,0,53,53,3176,3176,14695,14102,14105,14694,43,14149,14148,14151,14150,0,3176,3176,50,50,14105,14104,14107,14106,43,14738,14149,14150,14737,0,3176,3176,50,50,14694,14105,14106,14693,43,14737,14150,14153,14736,0,50,50,26,26,14693,14106,14109,14692,43,14724,14725,14155,14154,0,58,30,30,58,14680,14681,14111,14110,43,14723,14724,14154,14159,0,3105,58,58,3105,14679,14680,14110,14115,43,14159,14154,14157,14160,0,3105,58,58,3105,14115,14110,14113,14116,43,14722,14723,14159,14158,0,25,3105,3105,25,14678,14679,14115,14114,43,7831,14722,14158,7832,0,26,25,25,26,7810,14678,14114,7811,43,7832,14158,14161,14666,0,26,25,25,26,7811,14114,14117,14622,43,14161,14160,14163,14162,0,25,3105,3105,25,14117,14116,14119,14118,43,14666,14161,14162,14667,0,26,25,25,26,14622,14117,14118,14623,43,14667,14162,7828,7671,0,26,25,25,26,14623,14118,7807,7650,43,14728,14729,14166,14165,0,15,16,16,15,14684,14685,14122,14121,43,14727,14728,14165,14170,0,776,15,15,776,14683,14684,14121,14126,43,14170,14165,14168,14171,0,776,15,15,776,14126,14121,14124,14127,43,14726,14727,14170,14169,0,61,776,776,61,14682,14683,14126,14125,43,14725,14726,14169,14155,0,30,61,61,30,14681,14682,14125,14111,43,14155,14169,14172,14156,0,30,61,61,30,14111,14125,14128,14112,43,14172,14171,14174,14173,0,61,776,776,61,14128,14127,14130,14129,43,14156,14172,14173,7674,0,30,61,61,30,14112,14128,14129,7653,43,7674,14173,7826,7675,0,30,61,61,30,7653,14129,7805,7654,43,14776,7679,14177,14176,0,16,16,62,62,14732,7658,14133,14132,43,14777,14776,14176,14181,0,16,16,62,62,14733,14732,14132,14137,43,14181,14176,14179,14182,0,62,62,3192,3192,14137,14132,14135,14138,43,14778,14777,14181,14180,0,16,16,62,62,14734,14733,14137,14136,43,14745,14778,14180,14751,0,16,16,62,62,14701,14734,14136,14707,43,14751,14180,14183,14750,0,62,62,3192,3192,14707,14136,14139,14706,43,14183,14182,14185,14184,0,3192,3192,52,52,14139,14138,14141,14140,43,14750,14183,14184,14749,0,3192,3192,52,52,14706,14139,14140,14705,43,14749,14184,14187,14740,0,52,52,2,2,14705,14140,14143,14696,43,14779,14752,14189,14188,0,10,10,9,9,14735,14708,14145,14144,43,14780,14779,14188,14193,0,10,10,9,9,14736,14735,14144,14149,43,14193,14188,14191,14194,0,9,9,67,67,14149,14144,14147,14150,43,14781,14780,14193,14192,0,10,10,9,9,14737,14736,14149,14148,43,14782,14781,14192,14783,0,10,10,9,9,14738,14737,14148,14739,43,14783,14192,14195,14784,0,9,9,67,67,14739,14148,14151,14740,43,14195,14194,14197,14196,0,67,67,55,55,14151,14150,14153,14152,43,14784,14195,14196,14785,0,67,67,55,55,14740,14151,14152,14741,43,14785,14196,14199,7739,0,55,55,6,6,14741,14152,14155,7718,43,7732,7731,14201,14200,0,11,12,12,11,7711,7710,14157,14156,43,14342,7732,14200,14205,0,906,11,11,906,14298,7711,14156,14161,43,14205,14200,14203,14206,0,906,11,11,906,14161,14156,14159,14162,43,14343,14342,14205,14204,0,35,906,906,35,14299,14298,14161,14160,43,14782,14343,14204,14781,0,10,35,35,10,14738,14299,14160,14737,43,14781,14204,14207,14780,0,10,35,35,10,14737,14160,14163,14736,43,14207,14206,14209,14208,0,35,906,906,35,14163,14162,14165,14164,43,14780,14207,14208,14779,0,10,35,35,10,14736,14163,14164,14735,43,14779,14208,14211,14752,0,10,35,35,10,14735,14164,14167,14708,43,14786,14753,14213,14212,0,42,42,60,60,14742,14709,14169,14168,43,14787,14786,14212,14217,0,42,42,60,60,14743,14742,14168,14173,43,14217,14212,14215,14218,0,60,60,3202,3202,14173,14168,14171,14174,43,14788,14787,14217,14216,0,42,42,60,60,14744,14743,14173,14172,43,14789,14788,14216,14790,0,42,42,60,60,14745,14744,14172,14746,43,14790,14216,14219,14791,0,60,60,3202,3202,14746,14172,14175,14747,43,14219,14218,14221,14220,0,3202,3202,63,63,14175,14174,14177,14176,43,14791,14219,14220,14792,0,3202,3202,63,63,14747,14175,14176,14748,43,14792,14220,14201,7731,0,63,63,12,12,14748,14176,14157,7710,43,7740,7739,14199,14222,0,21,6,6,21,7719,7718,14155,14178,43,14366,7740,14222,14225,0,3091,21,21,3091,14322,7719,14178,14181,43,14225,14222,14223,14226,0,3091,21,21,3091,14181,14178,14179,14182,43,14367,14366,14225,14224,0,64,3091,3091,64,14323,14322,14181,14180,43,14789,14367,14224,14788,0,42,64,64,42,14745,14323,14180,14744,43,14788,14224,14227,14787,0,42,64,64,42,14744,14180,14183,14743,43,14227,14226,14229,14228,0,64,3091,3091,64,14183,14182,14185,14184,43,14787,14227,14228,14786,0,42,64,64,42,14743,14183,14184,14742,43,14786,14228,14231,14753,0,42,64,64,42,14742,14184,14187,14709,43,13721,14689,14233,14232,0,19,18,18,19,13677,14645,14189,14188,43,13722,13721,14232,14237,0,3082,19,19,3082,13678,13677,14188,14193,43,14237,14232,14235,14238,0,3082,19,19,3082,14193,14188,14191,14194,43,7534,13722,14237,14236,0,27,3082,3082,27,7513,13678,14193,14192,43,7535,7534,14236,7710,0,26,27,27,26,7514,7513,14192,7689,43,7710,14236,14239,14276,0,26,27,27,26,7689,14192,14195,14232,43,14239,14238,14241,14240,0,27,3082,3082,27,14195,14194,14197,14196,43,14276,14239,14240,14277,0,26,27,27,26,14232,14195,14196,14233,43,14277,14240,14243,14754,0,26,27,27,26,14233,14196,14199,14710,43,14691,14692,14245,14244,0,39,23,23,39,14647,14648,14201,14200,43,14690,14691,14244,14249,0,3136,39,39,3136,14646,14647,14200,14205,43,14249,14244,14247,14250,0,3136,39,39,3136,14205,14200,14203,14206,43,14688,14690,14249,14248,0,56,3136,3136,56,14644,14646,14205,14204,43,14689,14688,14248,14233,0,18,56,56,18,14645,14644,14204,14189,43,14233,14248,14251,14234,0,18,56,56,18,14189,14204,14207,14190,43,14251,14250,14253,14252,0,56,3136,3136,56,14207,14206,14209,14208,43,14234,14251,14252,7698,0,18,56,56,18,14190,14207,14208,7677,43,7698,14252,14255,7699,0,18,56,56,18,7677,14208,14211,7678,43,14245,14692,14257,14256,0,23,23,57,57,14201,14648,14213,14212,43,14246,14245,14256,14261,0,23,23,57,57,14202,14201,14212,14217,43,14261,14256,14259,14262,0,57,57,777,777,14217,14212,14215,14218,43,7702,14246,14261,14260,0,23,23,57,57,7681,14202,14217,14216,43,7703,7702,14260,14761,0,23,23,57,57,7682,7681,14216,14717,43,14761,14260,14263,14760,0,57,57,777,777,14717,14216,14219,14716,43,14263,14262,14265,14264,0,777,777,65,65,14219,14218,14221,14220,43,14760,14263,14264,14759,0,777,777,65,65,14716,14219,14220,14715,43,14759,14264,14267,14758,0,65,65,30,30,14715,14220,14223,14714,43,7708,7707,13731,14268,0,30,30,58,58,7687,7686,13687,14224,43,14266,7708,14268,14271,0,30,30,58,58,14222,7687,14224,14227,43,14271,14268,14269,14272,0,58,58,3105,3105,14227,14224,14225,14228,43,14267,14266,14271,14270,0,30,30,58,58,14223,14222,14227,14226,43,14758,14267,14270,14764,0,30,30,58,58,14714,14223,14226,14720,43,14764,14270,14273,14763,0,58,58,3105,3105,14720,14226,14229,14719,43,14273,14272,14275,14274,0,3105,3105,25,25,14229,14228,14231,14230,43,14763,14273,14274,14762,0,3105,3105,25,25,14719,14229,14230,14718,43,14762,14274,14277,14754,0,25,25,26,26,14718,14230,14233,14710,43,14311,14718,13811,14278,0,12,12,14,14,14267,14674,13767,14234,43,14312,14311,14278,14281,0,12,12,14,14,14268,14267,14234,14237,43,14281,14278,14279,14282,0,14,14,775,775,14237,14234,14235,14238,43,7722,14312,14281,14280,0,12,12,14,14,7701,14268,14237,14236,43,7723,7722,14280,14768,0,12,12,14,14,7702,7701,14236,14724,43,14768,14280,14283,14767,0,14,14,775,775,14724,14236,14239,14723,43,14283,14282,14285,14284,0,775,775,22,22,14239,14238,14241,14240,43,14767,14283,14284,14766,0,775,775,22,22,14723,14239,14240,14722,43,14766,14284,14287,14765,0,22,22,23,23,14722,14240,14243,14721,43,7712,7563,7562,14288,0,23,23,39,39,7691,7542,7541,14244,43,14286,7712,14288,14291,0,23,23,39,39,14242,7691,14244,14247,43,14291,14288,14289,14292,0,39,39,3136,3136,14247,14244,14245,14248,43,14287,14286,14291,14290,0,23,23,39,39,14243,14242,14247,14246,43,14765,14287,14290,14775,0,23,23,39,39,14721,14243,14246,14731,43,14775,14290,14293,14774,0,39,39,3136,3136,14731,14246,14249,14730,43,14293,14292,14295,14294,0,3136,3136,56,56,14249,14248,14251,14250,43,14774,14293,14294,14773,0,3136,3136,56,56,14730,14249,14250,14729,43,14773,14294,14297,14772,0,56,56,18,18,14729,14250,14253,14728,43,14713,14714,14299,14298,0,43,42,42,43,14669,14670,14255,14254,43,14712,14713,14298,14303,0,3145,43,43,3145,14668,14669,14254,14259,43,14303,14298,14301,14304,0,3145,43,43,3145,14259,14254,14257,14260,43,14711,14712,14303,14302,0,59,3145,3145,59,14667,14668,14259,14258,43,7715,14711,14302,7716,0,18,59,59,18,7694,14667,14258,7695,43,7716,14302,14305,14296,0,18,59,59,18,7695,14258,14261,14252,43,14305,14304,14307,14306,0,59,3145,3145,59,14261,14260,14263,14262,43,14296,14305,14306,14297,0,18,59,59,18,14252,14261,14262,14253,43,14297,14306,14309,14772,0,18,59,59,18,14253,14262,14265,14728,43,14717,14718,14311,14310,0,63,12,12,63,14673,14674,14267,14266,43,14716,14717,14310,14315,0,3202,63,63,3202,14672,14673,14266,14271,43,14315,14310,14313,14316,0,3202,63,63,3202,14271,14266,14269,14272,43,14715,14716,14315,14314,0,60,3202,3202,60,14671,14672,14271,14270,43,14714,14715,14314,14299,0,42,60,60,42,14670,14671,14270,14255,43,14299,14314,14317,14300,0,42,60,60,42,14255,14270,14273,14256,43,14317,14316,14319,14318,0,60,3202,3202,60,14273,14272,14275,14274,43,14300,14317,14318,7718,0,42,60,60,42,14256,14273,14274,7697,43,7718,14318,14321,7719,0,42,60,60,42,7697,14274,14277,7698,43,14357,7585,7584,14322,0,6,6,55,55,14313,7564,7563,14278,43,14358,14357,14322,14325,0,6,6,55,55,14314,14313,14278,14281,43,14325,14322,14323,14326,0,55,55,67,67,14281,14278,14279,14282,43,7738,14358,14325,14324,0,6,6,55,55,7717,14314,14281,14280,43,7739,7738,14324,14785,0,6,6,55,55,7718,7717,14280,14741,43,14785,14324,14327,14784,0,55,55,67,67,14741,14280,14283,14740,43,14327,14326,14329,14328,0,67,67,9,9,14283,14282,14285,14284,43,14784,14327,14328,14783,0,67,67,9,9,14740,14283,14284,14739,43,14783,14328,14331,14782,0,9,9,10,10,14739,14284,14287,14738,43,14732,14733,14333,14332,0,11,12,12,11,14688,14689,14289,14288,43,14731,14732,14332,14337,0,906,11,11,906,14687,14688,14288,14293,43,14337,14332,14335,14338,0,906,11,11,906,14293,14288,14291,14294,43,14730,14731,14337,14336,0,35,906,906,35,14686,14687,14293,14292,43,7727,14730,14336,7728,0,10,35,35,10,7706,14686,14292,7707,43,7728,14336,14339,14330,0,10,35,35,10,7707,14292,14295,14286,43,14339,14338,14341,14340,0,35,906,906,35,14295,14294,14297,14296,43,14330,14339,14340,14331,0,10,35,35,10,14286,14295,14296,14287,43,14331,14340,14343,14782,0,10,35,35,10,14287,14296,14299,14738,43,14333,14733,14345,14344,0,12,12,63,63,14289,14689,14301,14300,43,14334,14333,14344,14349,0,12,12,63,63,14290,14289,14300,14305,43,14349,14344,14347,14350,0,63,63,3202,3202,14305,14300,14303,14306,43,7730,14334,14349,14348,0,12,12,63,63,7709,14290,14305,14304,43,7731,7730,14348,14792,0,12,12,63,63,7710,7709,14304,14748,43,14792,14348,14351,14791,0,63,63,3202,3202,14748,14304,14307,14747,43,14351,14350,14353,14352,0,3202,3202,60,60,14307,14306,14309,14308,43,14791,14351,14352,14790,0,3202,3202,60,60,14747,14307,14308,14746,43,14790,14352,14355,14789,0,60,60,42,42,14746,14308,14311,14745,43,7586,7585,14357,14356,0,21,6,6,21,7565,7564,14313,14312,43,13876,7586,14356,14361,0,3091,21,21,3091,13832,7565,14312,14317,43,14361,14356,14359,14362,0,3091,21,21,3091,14317,14312,14315,14318,43,13877,13876,14361,14360,0,64,3091,3091,64,13833,13832,14317,14316,43,7735,13877,14360,7736,0,42,64,64,42,7714,13833,14316,7715,43,7736,14360,14363,14354,0,42,64,64,42,7715,14316,14319,14310,43,14363,14362,14365,14364,0,64,3091,3091,64,14319,14318,14321,14320,43,14354,14363,14364,14355,0,42,64,64,42,14310,14319,14320,14311,43,14355,14364,14367,14789,0,42,64,64,42,14311,14320,14323,14745,43,7763,7511,7792,14368,0,16,16,62,62,7742,7490,7771,14324,43,14433,7763,14368,14372,0,16,16,62,62,14389,7742,14324,14328,43,14372,14368,14370,14373,0,62,62,3192,3192,14328,14324,14326,14329,43,9098,14433,14372,14371,0,16,16,62,62,9077,14389,14328,14327,43,9208,9096,9099,9094,0,15,15,16,16,9187,9075,9078,9073,43,9095,14371,14374,9090,0,62,62,3192,3192,9074,14327,14330,9069,43,14374,14373,14376,14375,0,3192,3192,52,52,14330,14329,14332,14331,43,9090,14374,14375,9091,0,3192,3192,52,52,9069,14330,14331,9070,43,9093,9094,9099,9095,0,62,16,16,62,9072,9073,9078,9074,43,7743,7493,7492,14379,0,2,2,3,3,7722,7472,7471,14335,43,14377,7743,14379,14382,0,2,2,3,3,14333,7722,14335,14338,43,14382,14379,14380,14383,0,3,3,3045,3045,14338,14335,14336,14339,43,14378,14377,14382,14381,0,2,2,3,3,14334,14333,14338,14337,43,9117,9092,14793,9118,0,405,388,388,405,9096,9071,14749,9097,43,9118,14381,14384,9110,0,3,3,3045,3045,9097,14337,14340,9089,43,14384,14383,14386,14385,0,3045,3045,66,66,14340,14339,14342,14341,43,9110,14384,14385,9111,0,3045,3045,66,66,9089,14340,14341,9090,43,9089,9091,14793,9092,0,385,385,388,388,9068,9070,14749,9071,43,7747,7746,13618,14389,0,18,18,421,421,7726,7725,13574,14345,43,14387,7747,14389,14391,0,18,18,421,421,14343,7726,14345,14347,43,14391,14389,14390,14392,0,421,421,774,774,14347,14345,14346,14348,43,14388,14387,14391,9114,0,18,18,421,421,14344,14343,14347,9093,43,9115,14392,14394,14393,0,774,774,5,5,9094,14348,14350,14349,43,9111,14385,14388,14794,0,66,66,18,18,9090,14341,14344,14750,43,7502,7501,14398,14397,0,9,10,10,9,7481,7480,14354,14353,43,13629,7502,14397,14402,0,67,9,9,67,13585,7481,14353,14358,43,14402,14397,14400,14403,0,67,9,9,67,14358,14353,14356,14359,43,13630,13629,14402,14401,0,55,67,67,55,13586,13585,14358,14357,43,7497,13630,14401,7749,0,6,55,55,6,7476,13586,14357,7728,43,7749,14401,14404,14395,0,6,55,55,6,7728,14357,14360,14351,43,14404,14403,7750,14405,0,55,67,67,55,14360,14359,7729,14361,43,14395,14404,14405,14396,0,6,55,55,6,14351,14360,14361,14352,43,9116,9115,14393,9129,0,774,774,5,5,9095,9094,14349,9108,43,7506,7505,14408,14407,0,11,12,12,11,7485,7484,14364,14363,43,13641,7506,14407,14412,0,906,11,11,906,13597,7485,14363,14368,43,14412,14407,14410,14413,0,906,11,11,906,14368,14363,14366,14369,43,13642,13641,14412,14411,0,35,906,906,35,13598,13597,14368,14367,43,7501,13642,14411,14398,0,10,35,35,10,7480,13598,14367,14354,43,14398,14411,14414,14399,0,10,35,35,10,14354,14367,14370,14355,43,14414,14413,14669,14795,0,35,906,906,35,14370,14369,14625,14751,43,14399,14414,14795,14668,0,10,35,35,10,14355,14370,14751,14624,43,14408,7505,7508,14415,0,12,12,14,14,14364,7484,7487,14371,43,14409,14408,14415,14417,0,12,12,14,14,14365,14364,14371,14373,43,14417,14415,14416,14418,0,14,14,775,775,14373,14371,14372,14374,43,9179,14409,14417,9199,0,12,12,14,14,9158,14365,14373,9178,43,9176,9159,9180,9177,0,464,450,450,464,9155,9138,9159,9156,43,9180,9179,9199,9177,0,12,12,14,14,9159,9158,9178,9156,43,9200,14418,14419,9173,0,775,775,22,22,9179,14374,14375,9152,43,9157,9158,9180,9159,0,447,447,450,450,9136,9137,9159,9138,43,9170,9200,9173,9171,0,775,775,22,22,9149,9179,9152,9150,43,7757,7756,7820,14421,0,23,23,57,57,7736,7735,7799,14377,43,14420,7757,14421,14424,0,23,23,57,57,14376,7736,14377,14380,43,14424,14421,14423,14425,0,57,57,777,777,14380,14377,14379,14381,43,9174,14420,14424,9214,0,23,23,57,57,9153,14376,14380,9193,43,9197,9172,9175,9198,0,57,23,23,57,9176,9151,9154,9177,43,9175,9174,9214,9198,0,23,23,57,57,9154,9153,9193,9177,43,9215,14425,14426,9194,0,777,777,65,65,9194,14381,14382,9173,43,9169,9171,9175,9172,0,22,22,23,23,9148,9150,9154,9151,43,9191,9215,9194,9192,0,777,777,65,65,9170,9194,9173,9171,43,7761,7760,13662,14428,0,30,30,61,61,7740,7739,13618,14384,43,14427,7761,14428,14430,0,30,30,61,61,14383,7740,14384,14386,43,14430,14428,14429,14431,0,61,61,776,776,14386,14384,14385,14387,43,9195,14427,14430,9211,0,30,30,61,61,9174,14383,14386,9190,43,9213,9193,9196,9210,0,492,479,479,492,9192,9172,9175,9189,43,9196,9195,9211,9210,0,30,30,61,61,9175,9174,9190,9189,43,9212,14431,14432,9097,0,776,776,15,15,9191,14387,14388,9076,43,9190,9192,9196,9193,0,476,476,479,479,9169,9171,9175,9172,43,9209,9212,9097,9096,0,776,776,15,15,9188,9191,9076,9075,43,14796,14697,14435,14434,0,15,16,16,15,14752,14653,14391,14390,43,14797,14796,14434,14439,0,776,15,15,776,14753,14752,14390,14395,43,14439,14434,14437,14440,0,776,15,15,776,14395,14390,14393,14396,43,14798,14797,14439,14438,0,61,776,776,61,14754,14753,14395,14394,43,7770,14798,14438,7771,0,30,61,61,30,7749,14754,14394,7750,43,7771,14438,14441,14454,0,30,61,61,30,7750,14394,14397,14410,43,14441,14440,14443,14442,0,61,776,776,61,14397,14396,14399,14398,43,14454,14441,14442,14455,0,30,61,61,30,14410,14397,14398,14411,43,14455,14442,14445,7781,0,30,61,61,30,14411,14398,14401,7760,43,7774,7547,7546,14446,0,30,30,30,30,7753,7526,7525,14402,43,14466,7774,14446,14449,0,30,30,30,30,14422,7753,14402,14405,43,14449,14446,14447,14450,0,30,30,30,30,14405,14402,14403,14406,43,14467,14466,14449,14448,0,30,30,30,30,14423,14422,14405,14404,43,7785,14467,14448,14489,0,30,30,30,30,7764,14423,14404,14445,43,14489,14448,14451,14490,0,30,30,30,30,14445,14404,14407,14446,43,14451,14450,14453,14452,0,30,30,30,30,14407,14406,14409,14408,43,14490,14451,14452,7780,0,30,30,30,30,14446,14407,14408,7759,43,7780,14452,14455,7781,0,30,30,30,30,7759,14408,14411,7760,43,7776,7539,14457,14456,0,16,16,15,15,7755,7518,14413,14412,43,14476,7776,14456,14461,0,16,16,15,15,14432,7755,14412,14417,43,14461,14456,14459,14462,0,15,15,776,776,14417,14412,14415,14418,43,14477,14476,14461,14460,0,16,16,15,15,14433,14432,14417,14416,43,7789,14477,14460,14501,0,16,16,15,15,7768,14433,14416,14457,43,14501,14460,14463,14502,0,15,15,776,776,14457,14416,14419,14458,43,14463,14462,14465,14464,0,776,776,61,61,14419,14418,14421,14420,43,14502,14463,14464,7784,0,776,776,61,61,14458,14419,14420,7763,43,7784,14464,14467,7785,0,61,61,30,30,7763,14420,14423,7764,43,14435,14697,13741,14468,0,16,16,16,16,14391,14653,13697,14424,43,14436,14435,14468,14471,0,16,16,16,16,14392,14391,14424,14427,43,14471,14468,14469,14472,0,16,16,16,16,14427,14424,14425,14428,43,7765,14436,14471,14470,0,16,16,16,16,7744,14392,14427,14426,43,7766,7765,14470,14513,0,16,16,16,16,7745,7744,14426,14469,43,14513,14470,14473,14514,0,16,16,16,16,14469,14426,14429,14470,43,14473,14472,14475,14474,0,16,16,16,16,14429,14428,14431,14430,43,14514,14473,14474,7788,0,16,16,16,16,14470,14429,14430,7767,43,7788,14474,14477,7789,0,16,16,16,16,7767,14430,14433,7768,43,7782,7781,14445,14478,0,30,30,61,61,7761,7760,14401,14434,43,14498,7782,14478,14481,0,30,30,61,61,14454,7761,14434,14437,43,14481,14478,14479,14482,0,61,61,776,776,14437,14434,14435,14438,43,14499,14498,14481,14480,0,30,30,61,61,14455,14454,14437,14436,43,14702,14499,14480,14701,0,30,30,61,61,14658,14455,14436,14657,43,14701,14480,14483,14700,0,61,61,776,776,14657,14436,14439,14656,43,14483,14482,14485,14484,0,776,776,15,15,14439,14438,14441,14440,43,14700,14483,14484,14698,0,776,776,15,15,14656,14439,14440,14654,43,14698,14484,14487,14699,0,15,15,16,16,14654,14440,14443,14655,43,7786,7785,14489,14488,0,30,30,30,30,7765,7764,14445,14444,43,14510,7786,14488,14493,0,30,30,30,30,14466,7765,14444,14449,43,14493,14488,14491,14494,0,30,30,30,30,14449,14444,14447,14450,43,14511,14510,14493,14492,0,30,30,30,30,14467,14466,14449,14448,43,14706,14511,14492,14705,0,30,30,30,30,14662,14467,14448,14661,43,14705,14492,14495,14704,0,30,30,30,30,14661,14448,14451,14660,43,14495,14494,14497,14496,0,30,30,30,30,14451,14450,14453,14452,43,14704,14495,14496,14703,0,30,30,30,30,14660,14451,14452,14659,43,14703,14496,14499,14702,0,30,30,30,30,14659,14452,14455,14658,43,7790,7789,14501,14500,0,16,16,15,15,7769,7768,14457,14456,43,14522,7790,14500,14505,0,16,16,15,15,14478,7769,14456,14461,43,14505,14500,14503,14506,0,15,15,776,776,14461,14456,14459,14462,43,14523,14522,14505,14504,0,16,16,15,15,14479,14478,14461,14460,43,7543,14523,14504,7544,0,16,16,15,15,7522,14479,14460,7523,43,7544,14504,14507,13752,0,15,15,776,776,7523,14460,14463,13708,43,14507,14506,14509,14508,0,776,776,61,61,14463,14462,14465,14464,43,13752,14507,14508,13753,0,776,776,61,61,13708,14463,14464,13709,43,13753,14508,14511,14706,0,61,61,30,30,13709,14464,14467,14662,43,7778,7766,14513,14512,0,16,16,16,16,7757,7745,14469,14468,43,14486,7778,14512,14517,0,16,16,16,16,14442,7757,14468,14473,43,14517,14512,14515,14518,0,16,16,16,16,14473,14468,14471,14474,43,14487,14486,14517,14516,0,16,16,16,16,14443,14442,14473,14472,43,14699,14487,14516,13743,0,16,16,16,16,14655,14443,14472,13699,43,13743,14516,14519,13744,0,16,16,16,16,13699,14472,14475,13700,43,14519,14518,14521,14520,0,16,16,16,16,14475,14474,14477,14476,43,13744,14519,14520,7542,0,16,16,16,16,13700,14475,14476,7521,43,7542,14520,14523,7543,0,16,16,16,16,7521,14476,14479,7522,43,7494,7493,7742,14524,0,1,2,52,3203,7473,7472,7721,14480,43,13605,7494,14524,14527,0,3052,1,3203,3208,13561,7473,14480,14483,43,14527,14524,14525,14528,0,3208,3203,3204,3207,14483,14480,14481,14484,43,13606,13605,14527,14526,0,3051,3052,3208,3205,13562,13561,14483,14482,43,14677,13606,14526,14799,0,3284,3051,3205,3299,14633,13562,14482,14755,43,14799,14526,14529,14800,0,3299,3205,3206,3300,14755,14482,14485,14756,43,14529,14528,14531,14530,0,3206,3207,3212,3209,14485,14484,14487,14486,43,14800,14529,14530,14801,0,3300,3206,3209,3301,14756,14485,14486,14757,43,14801,14530,14533,14693,0,3301,3209,3210,3294,14757,14486,14489,14649,43,14801,14693,14535,14534,0,3301,3294,3216,3213,14757,14649,14491,14490,43,14800,14801,14534,14539,0,3300,3301,3213,3220,14756,14757,14490,14495,43,14539,14534,14537,14540,0,3220,3213,3214,3219,14495,14490,14493,14496,43,14799,14800,14539,14538,0,3299,3300,3220,3217,14755,14756,14495,14494,43,14677,14799,14538,14721,0,3284,3299,3217,3298,14633,14755,14494,14677,43,14721,14538,14541,14720,0,3298,3217,3218,3297,14677,14494,14497,14676,43,14541,14540,14543,14542,0,3218,3219,3224,3221,14497,14496,14499,14498,43,14720,14541,14542,14719,0,3297,3218,3221,3296,14676,14497,14498,14675,43,14719,14542,14545,14682,0,3296,3221,3222,3289,14675,14498,14501,14638,43,7803,7802,14547,14546,0,76,77,3228,3225,7782,7781,14503,14502,43,14566,7803,14546,14551,0,3243,76,3225,3232,14522,7782,14502,14507,43,14551,14546,14549,14552,0,3232,3225,3226,3231,14507,14502,14505,14508,43,14567,14566,14551,14550,0,81,3243,3232,3229,14523,14522,14507,14506,43,14697,14567,14550,14696,0,16,81,3229,69,14653,14523,14506,14652,43,14696,14550,14553,14695,0,69,3229,3230,3211,14652,14506,14509,14651,43,14553,14552,14555,14554,0,3230,3231,3234,3233,14509,14508,14511,14510,43,14695,14553,14554,14694,0,3211,3230,3233,3210,14651,14509,14510,14650,43,14694,14554,14535,14693,0,3210,3233,3216,3294,14650,14510,14491,14649,43,14599,14802,14557,14556,0,30,30,3238,3235,14555,14758,14513,14512,43,14600,14599,14556,14561,0,30,30,3235,3242,14556,14555,14512,14517,43,14561,14556,14559,14562,0,3242,3235,3236,3241,14517,14512,14515,14518,43,7814,14600,14561,14560,0,30,30,3242,3239,7793,14556,14517,14516,43,7770,7814,14560,14798,0,30,30,3239,61,7749,7793,14516,14754,43,14798,14560,14563,14797,0,61,3239,3240,776,14754,14516,14519,14753,43,14563,14562,14565,14564,0,3240,3241,3244,80,14519,14518,14521,14520,43,14797,14563,14564,14796,0,776,3240,80,15,14753,14519,14520,14752,43,14796,14564,14567,14697,0,15,80,81,16,14752,14520,14523,14653,43,7809,7626,7625,14568,0,81,16,62,3245,7788,7605,7604,14524,43,14586,7809,14568,14571,0,3243,81,3245,3250,14542,7788,14524,14527,43,14571,14568,14569,14572,0,3250,3245,3246,3249,14527,14524,14525,14528,43,14587,14586,14571,14570,0,76,3243,3250,3247,14543,14542,14527,14526,43,7802,14587,14570,14547,0,77,76,3247,3228,7781,14543,14526,14503,43,14547,14570,14573,14548,0,3228,3247,3248,3227,14503,14526,14529,14504,43,14573,14572,14575,14574,0,3248,3249,3252,3251,14529,14528,14531,14530,43,14548,14573,14574,7799,0,3227,3248,3251,74,14504,14529,14530,7778,43,7799,14574,14577,7796,0,74,3251,2,2,7778,14530,14533,7775,43,7812,7599,7622,14578,0,30,30,61,3239,7791,7578,7601,14534,43,14596,7812,14578,14581,0,30,30,3239,3242,14552,7791,14534,14537,43,14581,14578,14579,14582,0,3242,3239,3240,3241,14537,14534,14535,14538,43,14597,14596,14581,14580,0,30,30,3242,3235,14553,14552,14537,14536,43,14802,14597,14580,14557,0,30,30,3235,3238,14758,14553,14536,14513,43,14557,14580,14583,14558,0,3238,3235,3236,3237,14513,14536,14539,14514,43,14583,14582,14585,14584,0,3236,3241,3244,75,14539,14538,14541,14540,43,14558,14583,14584,7801,0,3237,3236,75,78,14514,14539,14540,7780,43,7801,14584,14587,7802,0,78,75,76,77,7780,14540,14543,7781,43,13710,7595,7594,14588,0,23,23,57,57,13666,7574,7573,14544,43,13711,13710,14588,14591,0,23,23,57,57,13667,13666,14544,14547,43,14591,14588,14589,14592,0,57,57,777,777,14547,14544,14545,14548,43,7530,13711,14591,14590,0,23,23,57,57,7509,13667,14547,14546,43,7531,7530,14590,14803,0,23,23,57,57,7510,7509,14546,14759,43,14803,14590,14593,14804,0,57,57,777,777,14759,14546,14549,14760,43,14593,14592,14595,14594,0,777,777,65,65,14549,14548,14551,14550,43,14804,14593,14594,14805,0,777,777,65,65,14760,14549,14550,14761,43,14805,14594,14597,14802,0,65,65,30,30,14761,14550,14553,14758,43,14805,14802,14599,14598,0,65,30,30,3253,14761,14758,14555,14554,43,14804,14805,14598,14603,0,777,65,3253,3258,14760,14761,14554,14559,43,14603,14598,14601,14604,0,3258,3253,3254,3257,14559,14554,14557,14560,43,14803,14804,14603,14602,0,57,777,3258,3255,14759,14760,14559,14558,43,7531,14803,14602,14806,0,23,57,3255,3302,7510,14759,14558,14762,43,14806,14602,14605,14807,0,3302,3255,3256,3303,14762,14558,14561,14763,43,14605,14604,14607,14606,0,3256,3257,3262,3259,14561,14560,14563,14562,43,14807,14605,14606,14808,0,3303,3256,3259,3304,14763,14561,14562,14764,43,14808,14606,14609,14707,0,3304,3259,3260,3295,14764,14562,14565,14663,43,14809,14681,14611,14610,0,3305,3288,3130,3263,14765,14637,14567,14566,43,14810,14809,14610,14615,0,3306,3305,3263,3268,14766,14765,14566,14571,43,14615,14610,14613,14616,0,3268,3263,3264,3267,14571,14566,14569,14572,43,14811,14810,14615,14614,0,3307,3306,3268,3265,14767,14766,14571,14570,43,14707,14811,14614,14808,0,3295,3307,3265,3304,14663,14767,14570,14764,43,14808,14614,14617,14807,0,3304,3265,3266,3303,14764,14570,14573,14763,43,14617,14616,14619,14618,0,3266,3267,3270,3269,14573,14572,14575,14574,43,14807,14617,14618,14806,0,3303,3266,3269,3302,14763,14573,14574,14762,43,14806,14618,7532,7531,0,3302,3269,22,23,14762,14574,7511,7510,43,14710,7760,7759,14620,0,83,30,65,3271,14666,7739,7738,14576,43,14709,14710,14620,14623,0,3261,83,3271,3276,14665,14666,14576,14579,43,14623,14620,14621,14624,0,3276,3271,3272,3275,14579,14576,14577,14580,43,14708,14709,14623,14622,0,3260,3261,3276,3273,14664,14665,14579,14578,43,14707,14708,14622,14811,0,3295,3260,3273,3307,14663,14664,14578,14767,43,14811,14622,14625,14810,0,3307,3273,3274,3306,14767,14578,14581,14766,43,14625,14624,14627,14626,0,3274,3275,3280,3277,14581,14580,14583,14582,43,14810,14625,14626,14809,0,3306,3274,3277,3305,14766,14581,14582,14765,43,14809,14626,14629,14681,0,3305,3277,3278,3288,14765,14582,14585,14637,43,14166,14729,13866,14630,0,16,16,62,62,14122,14685,13822,14586,43,14167,14166,14630,14633,0,16,16,62,62,14123,14122,14586,14589,43,14633,14630,14631,14634,0,62,62,3192,3192,14589,14586,14587,14590,43,7678,14167,14633,14632,0,16,16,62,62,7657,14123,14589,14588,43,7679,7678,14632,14177,0,16,16,62,62,7658,7657,14588,14133,43,14177,14632,14635,14178,0,62,62,3192,3192,14133,14588,14591,14134,43,14635,14634,14637,14636,0,3192,3192,52,52,14591,14590,14593,14592,43,14178,14635,14636,7682,0,3192,3192,52,52,14134,14591,14592,7661,43,7682,14636,14639,7683,0,52,52,2,2,7661,14592,14595,7662,43,14748,14741,14641,14640,0,61,30,30,61,14704,14697,14597,14596,43,14747,14748,14640,14645,0,776,61,61,776,14703,14704,14596,14601,43,14645,14640,14643,14646,0,776,61,61,776,14601,14596,14599,14602,43,14746,14747,14645,14644,0,15,776,776,15,14702,14703,14601,14600,43,14745,14746,14644,14778,0,16,15,15,16,14701,14702,14600,14734,43,14778,14644,14647,14777,0,16,15,15,16,14734,14600,14603,14733,43,14647,14646,14649,14648,0,15,776,776,15,14603,14602,14605,14604,43,14777,14647,14648,14776,0,16,15,15,16,14733,14603,14604,14732,43,14776,14648,7680,7679,0,16,15,15,16,14732,14604,7659,7658,43,14744,14736,14153,14650,0,25,26,26,25,14700,14692,14109,14606,43,14743,14744,14650,14653,0,3105,25,25,3105,14699,14700,14606,14609,43,14653,14650,14651,14654,0,3105,25,25,3105,14609,14606,14607,14610,43,14742,14743,14653,14652,0,58,3105,3105,58,14698,14699,14609,14608,43,14741,14742,14652,14641,0,30,58,58,30,14697,14698,14608,14597,43,14641,14652,14655,14642,0,30,58,58,30,14597,14608,14611,14598,43,14655,14654,14657,14656,0,58,3105,3105,58,14611,14610,14613,14612,43,14642,14655,14656,7825,0,30,58,58,30,14598,14611,14612,7804,43,7825,14656,7676,7675,0,30,58,58,30,7804,14612,7655,7654,43,7823,7581,7580,14658,0,2,2,53,53,7802,7560,7559,14614,43,14638,7823,14658,14661,0,2,2,53,53,14594,7802,14614,14617,43,14661,14658,14659,14662,0,53,53,3176,3176,14617,14614,14615,14618,43,14639,14638,14661,14660,0,2,2,53,53,14595,14594,14617,14616,43,7683,14639,14660,14143,0,2,2,53,53,7662,14595,14616,14099,43,14143,14660,14663,14144,0,53,53,3176,3176,14099,14616,14619,14100,43,14663,14662,14665,14664,0,3176,3176,50,50,14619,14618,14621,14620,43,14144,14663,14664,7670,0,3176,3176,50,50,14100,14619,14620,7649,43,7670,14664,14667,7671,0,50,50,26,26,7649,14620,14623,7650,43,14794,14388,9114,9113,0,18,18,421,421,14750,14344,9093,9092,43,14793,14378,14381,9118,0,2,2,3,3,14749,14334,14337,9097,43,9109,9111,14794,9112,0,66,66,18,18,9088,9090,14750,9091,43,9131,9112,14794,9113,0,421,18,18,421,9110,9091,14750,9092,43,9099,9098,14371,9095,0,16,16,62,62,9078,9077,14327,9074,43,9091,14375,14378,14793,0,52,52,2,2,9070,14331,14334,14749,43,14669,9178,9158,9150,0,906,11,11,906,14625,9157,9137,9129,43,14668,14795,9149,14812,0,10,35,35,10,14624,14751,9128,14768,43,14795,14669,9150,9149,0,35,906,906,35,14751,14625,9129,9128,43,9143,7752,14812,9144,0,9,9,10,10,9122,7731,14768,9123,43,9147,9144,14812,9149,0,35,10,10,35,9126,9123,14768,9128,43,7751,14668,14812,7752,0,9,10,10,9,7730,14624,14768,7731,43,14405,7750,7753,9136,0,55,67,67,55,14361,7729,7732,9115,43,9128,9129,14406,9130,0,417,417,420,420,9107,9108,14362,9109,43,9134,9130,14406,9136,0,435,420,420,435,9113,9109,14362,9115,43,9879,2484,2493,9882,0,1143,1145,1151,1150,9858,2484,2493,9861,43,9878,9881,9960,2485,0,1003,1003,1004,1004,9857,9860,9939,2485,43,7965,7969,499,492,0,234,239,243,236,7944,7948,499,492,43,8931,1486,1485,8932,0,747,749,748,746,8910,1486,1485,8911,43,8932,1485,500,7971,0,746,748,634,62,8911,1485,500,7950,43,7887,404,403,7888,0,147,149,148,146,7866,404,403,7867,43,7891,7893,412,411,0,152,9,156,155,7870,7872,412,411,43,7887,7891,411,404,0,147,152,155,149,7866,7870,411,404,43,8833,8835,1375,1372,0,631,635,637,633,8812,8814,1375,1372,43,8835,8836,1376,1375,0,635,636,638,637,8814,8815,1376,1375,42,3204,10581,10569,0,1007,1006,1006,3204,10560,10548,42,3189,3203,3204,0,1006,1006,1007,3189,3203,3204,42,3189,3204,10569,0,1006,1007,1006,3189,3204,10548,42,3189,10569,10568,0,1006,1006,1006,3189,10548,10547,42,3190,3189,10568,0,1006,1006,1006,3190,3189,10547,42,3190,10568,10571,0,1006,1006,1006,3190,10547,10550,42,3192,3190,10571,0,1006,1006,1006,3192,3190,10550,42,3192,10571,10943,0,1006,1006,1006,3192,10550,10922,42,3569,3192,10943,0,1006,1006,1006,3569,3192,10922,42,3569,10943,10942,0,1006,1006,1006,3569,10922,10921,42,3570,3569,10942,0,1006,1006,1006,3570,3569,10921,42,10946,3570,10942,0,1006,1006,1006,10925,3570,10921,42,10946,3571,3570,0,1006,1006,1006,10925,3571,3570,42,10946,9697,3571,0,1006,1006,1006,10925,9676,3571,42,2294,2293,3571,0,1007,1006,1006,2294,2293,3571,42,9697,2294,3571,0,1006,1007,1006,9676,2294,3571], + "uvs" : [[0,0.856987,0,0.975983,0,1,0,0.875,0,0.163358,0,0.125,0,0,0,0.051144,0.1875,0.012786,0.1875,0,0.25,0,0.4375,0,0.5,0,0.5,0.119582,0.5,0.125,0.3125,1,0.25,1,0,0.573969,0,0.5,0,0.575211,0,0.038761,0,0.036591,0.5,0.375,0.5,0.5,0.125,0.762974,0.125,0.850631,0,0.800842,0,0.725631,0.2396,1,0.5,0.982993,0.5,1,0.046875,0.149028,0,0.198704,0,0.204575,0.0625,0.153431,0.3125,0,0.453125,0.077497,0.5,0.103329,0.375,0.384148,0.375,0.5,0,0.241283,0,0.155044,0,0.146362,0,0.234772,0,0.421741,0,0.420273,0,0.577928,0,0.570244,0,0.846906,0,0.795875,0,0.850631,0.078125,0.962658,0.0625,1,0,0.950211,0.078125,0.027443,0.0625,0,0.125,0.5,0.5,0.625,0.375,0.950211,0,0.411591,0.125,0.109772,0.4375,1,0.1875,1,0.375,0.036591,0,0.109772,0.5,0.875,0,0.625,0.125,0,0.156301,0.993996,0.208401,1,0.015676,0.952928,0,0.945244,0.020901,1,0.07268,1,0.055172,1,0.296012,1,0.228016,1,0.220688,1,0.290516,1,0.060668,1,0.307004,1,0.242672,1,0.5,0.917729,0.5,0.931973,0.5,0.21674,0.5,0.59174,0.5,0.478329,0.71666,0.343124,0.605164,0.357117,0.612793,0.308105,0.729858,0.300171,0.618896,0.263428,0.739929,0.25946,0.490082,0.367035,0.493896,0.314209,0.496948,0.266479,0.845703,0.291016,0.824951,0.324951,0.960938,0.28125,0.931641,0.304688,0.860352,0.254883,0.980469,0.25,0.797852,0.352539,0.701233,0.38678,0.767578,0.384766,0.685333,0.43338,0.890625,0.3125,0.835938,0.296875,0.596924,0.409424,0.485962,0.423462,0.588989,0.463989,0.481995,0.481995,0.734863,0.347168,0.626953,0.348633,0.626221,0.302002,0.73468,0.301086,0.62561,0.260376,0.734528,0.259918,0.504883,0.351562,0.503052,0.303833,0.501526,0.261292,0.8125,0.300781,0.8125,0.34668,0.84375,0.300781,0.84375,0.34668,0.8125,0.259766,0.84375,0.259766,0.8125,0.398438,0.735046,0.399109,0.8125,0.457031,0.735199,0.457855,0.84375,0.398438,0.84375,0.457031,0.627686,0.401123,0.506714,0.405151,0.628296,0.460327,0.50824,0.465271,0.231402,0.112853,0.320467,0.113342,0.305414,0.160873,0.219005,0.160178,0.293835,0.197435,0.209468,0.196582,0.416077,0.113902,0.400423,0.161668,0.388382,0.19841,0.15562,0.159681,0.165039,0.112503,0.129683,0.159483,0.137532,0.112363,0.148374,0.195972,0.123645,0.195729,0.175907,0.058066,0.245707,0.058246,0.260966,0,0.146589,0.057994,0.15625,0,0.337836,0.058499,0.434139,0.058788,0.356362,0,0.453406,0,0.61052,0.76741,0.596502,0.826837,0.54046,0.875555,0.548874,0.814408,0.45773,0.906528,0.461937,0.844704,0.58212,0.88663,0.531812,0.936938,0.453406,0.968469,0.556816,0.753731,0.623807,0.708712,0.564053,0.69376,0.636,0.651109,0.465908,0.783115,0.469526,0.72188,0.656601,0.64418,0.63681,0.699128,0.644917,0.556255,0.617678,0.604978,0.67498,0.590645,0.670603,0.509084,0.616077,0.755017,0.594873,0.811377,0.589403,0.654737,0.56061,0.705015,0.657731,0.539809,0.576242,0.576712,0.582275,0.519775,0.670715,0.485168,0.475621,0.600856,0.478638,0.541138,0.737305,0.432617,0.710711,0.487043,0.760986,0.366455,0.722199,0.417571,0.691476,0.538992,0.646734,0.594965,0.694218,0.463985,0.570348,0.634731,0.472674,0.661115,0.265594,0.189423,0.374878,0.189331,0.374512,0.226074,0.265503,0.22644,0.373779,0.268311,0.26532,0.269226,0.499695,0.189148,0.498779,0.225342,0.1875,0.226562,0.1875,0.189453,0.15625,0.226562,0.15625,0.189453,0.1875,0.269531,0.15625,0.269531,0.1875,0.15625,0.265625,0.15625,0.1875,0.125,0.265625,0.125,0.15625,0.15625,0.15625,0.125,0.375,0.15625,0.5,0.15625,0.375,0.125,0.734604,0.874496,0.625916,0.875916,0.627686,0.811279,0.735046,0.809265,0.628296,0.74353,0.735199,0.741058,0.503265,0.878754,0.506714,0.815308,0.50824,0.748474,0.8125,0.808594,0.8125,0.874023,0.84375,0.808594,0.84375,0.874023,0.8125,0.740234,0.84375,0.740234,0.8125,0.9375,0.733643,0.937744,0.8125,1,0.732422,1,0.84375,0.9375,0.84375,1,0.62207,0.938477,0.496582,0.939941,0.617188,1,0.480469,1,0.735321,0.595673,0.735291,0.668884,0.628662,0.671631,0.628784,0.598511,0.509155,0.677124,0.50946,0.604187,0.628662,0.5271,0.735291,0.524353,0.509155,0.532593,0.8125,0.523438,0.8125,0.594727,0.84375,0.523438,0.84375,0.594727,0.8125,0.667969,0.84375,0.667969,0.25,0.064453,0.125,0.064453,0.125,0.039062,0.25,0.039062,0.125,0.019531,0.25,0.019531,0,0.064453,0,0.039062,0,0.019531,0.375,0.039062,0.375,0.064453,0.5,0.039062,0.5,0.064453,0.375,0.019531,0.5,0.019531,0.375,0.09375,0.25,0.09375,0.25,0.125,0.5,0.09375,0.125,0.09375,0,0.09375,0.125,0.125,0.935547,0.250244,0.935547,0.125977,0.960938,0.128906,0.960938,0.250977,0.976562,0.134766,0.979492,0.252441,0.935547,0.001953,0.960938,0.007812,0.980469,0.019531,0.960938,0.375,0.935547,0.375,0.960938,0.5,0.935547,0.5,0.980469,0.375,0.980469,0.5,0.90625,0.375,0.90625,0.25,0.875,0.375,0.875,0.25,0.90625,0.5,0.875,0.5,0.90625,0.125,0.90625,0,0.875,0.125,0.875,0,0.62719,0.249558,0.67984,0.249715,0.680298,0.37386,0.627899,0.373233,0.681213,0.49715,0.629318,0.495583,0.730545,0.249858,0.730774,0.37443,0.731232,0.498575,0.579346,0.37272,0.57843,0.24943,0.540405,0.372492,0.539398,0.249373,0.581177,0.4943,0.542419,0.49373,0.578125,0.125,0.626953,0.125,0.578125,0,0.626953,0,0.539062,0.125,0.539062,0,0.679688,0.125,0.730469,0.125,0.679688,0,0.730469,0,0.549394,0.73066,0.462636,0.732615,0.464914,0.710171,0.552297,0.708114,0.467245,0.681199,0.555049,0.679157,0.370195,0.732428,0.371872,0.709882,0.373738,0.68086,0.628322,0.702682,0.624842,0.725645,0.687291,0.692847,0.683354,0.716652,0.631409,0.673645,0.690582,0.663573,0.619712,0.744571,0.545384,0.748942,0.611675,0.761497,0.539309,0.765103,0.677279,0.736827,0.667571,0.755214,0.459766,0.750713,0.368331,0.75066,0.455657,0.766645,0.365902,0.766738,0.810799,0.74953,0.84375,0.75,0.84375,0.875,0.810913,0.874316,0.811035,0.999088,0.875,0.75,0.875,0.875,0.875,1,0.774902,0.872264,0.774445,0.748119,0.734131,0.86816,0.732986,0.745298,0.775391,0.996352,0.735352,0.99088,0.774048,0.62386,0.810699,0.624715,0.773743,0.49943,0.810623,0.499858,0.731995,0.62215,0.84375,0.625,0.875,0.625,0.84375,0.5,0.128403,0.991669,0.081299,0.971924,0.078125,0.96875,0.128906,0.992188,0.046204,0.927551,0.039062,0.921875,0.185242,0.997681,0.246918,0.996765,0.178467,0.990723,0.126892,0.990112,0.164917,0.976807,0.119476,0.98407,0.237671,0.987061,0.219177,0.967651,0.09082,0.981445,0.067627,0.94458,0.092285,0.98877,0.117188,1,0.015125,0.743164,0.011672,0.625,0.003906,0.742188,0.011601,0.5,0.015625,0.84375,0.025391,0.847656,0.054688,0.859375,0.048781,0.746094,0.119141,0.871094,0.117558,0.749023,0.046687,0.625,0.046404,0.5,0.116717,0.625,0.116011,0.5,0.016119,0.258395,0.030309,0.15858,0.015625,0.15625,0.003906,0.257812,0.055234,0.082948,0.039062,0.078125,0,0.375,0.011483,0.375,0.045934,0.375,0.052758,0.260142,0.114834,0.375,0.122016,0.26033,0.074359,0.165569,0.103747,0.097419,0.146389,0.166322,0.219312,0.124719,0.139654,0.017478,0.196786,0.009757,0.128906,0.007812,0.258671,0.008907,0.078125,0.03125,0.092076,0.038895,0.133929,0.06183,0.171898,0.046475,0.201564,0.119418,0.232368,0.107399,0.224644,0.039027,0.284686,0.035627,0.280361,0.097568,0.336714,0.089067,0.378894,0.014172,0.43042,0.033936,0.421875,0.03125,0.371094,0.007812,0.469177,0.077576,0.460938,0.078125,0.32051,0.008104,0.344538,0.032415,0.402297,0.03325,0.392596,0.081037,0.447636,0.074336,0.456055,0.041992,0.493896,0.075928,0.501465,0.069824,0.558594,0.058594,0.497322,0.257554,0.500031,0.374943,0.496094,0.257812,0.500076,0.499857,0.484375,0.15625,0.489258,0.155273,0.503906,0.152344,0.501007,0.256779,0.515625,0.146484,0.503983,0.255229,0.500122,0.374772,0.500305,0.49943,0.500305,0.37443,0.500763,0.498575,0.495979,0.741717,0.483276,0.843066,0.484375,0.84375,0.496094,0.742188,0.458527,0.920463,0.460938,0.921875,0.500153,0.624715,0.50061,0.62386,0.495636,0.740307,0.501526,0.62215,0.496902,0.738462,0.47998,0.841014,0.451294,0.916226,0.481201,0.840817,0.436523,0.912755,0.366745,0.987648,0.308533,0.995728,0.371094,0.992188,0.421875,0.96875,0.418213,0.965608,0.407227,0.956184,0.353699,0.974031,0.378418,0.92744,0.325897,0.944323,0.296631,0.98291,0.272827,0.957275,0.727524,0.314992,0.745093,0.338339,0.725289,0.269383,0.707822,0.232988,0.690807,0.216718,0.676827,0.170145,0.750172,0.35664,0.730071,0.295096,0.693764,0.248216,0.666899,0.185511,0.687007,0.288875,0.595245,0.144811,0.613959,0.266826,0.641982,0.106021,0.56061,0,0.697636,0.395057,0.737265,0.407091,0.701989,0.50331,0.740347,0.505296,0.624419,0.385028,0.62942,0.501655,0.753928,0.419125,0.758245,0.429153,0.755503,0.507283,0.758466,0.508938,0.732871,0.702358,0.698237,0.72158,0.683631,0.827175,0.715178,0.789803,0.661724,0.908479,0.684804,0.857503,0.62886,0.737939,0.618161,0.859685,0.59375,1,0.723764,0.762589,0.74421,0.685676,0.721352,0.744143,0.743953,0.672833,0.6884,0.82243,0.683278,0.799829,0.753527,0.598354,0.740072,0.605621,0.755013,0.592298,0.703273,0.612888,0.631757,0.618944,0.579205,0.911901,0.593331,0.954152,0.527879,0.95756,0.503478,0.917772,0.442616,0.957745,0.415551,0.918131,0.607422,1,0.554688,1,0.483893,0.883289,0.565009,0.876847,0.469579,0.856764,0.55315,0.850633,0.394439,0.883799,0.379325,0.85739,0.63433,0.858538,0.639563,0.895007,0.625797,0.83349,0.634785,0.944298,0.625,1,0.562047,0.311786,0.561163,0.284081,0.638573,0.294663,0.637847,0.321426,0.698833,0.313007,0.696219,0.337937,0.560006,0.26232,0.637265,0.27283,0.697461,0.291863,0.632534,0.349425,0.56192,0.342702,0.62955,0.378054,0.562416,0.374867,0.685103,0.362243,0.669144,0.380438,0.474954,0.337987,0.473561,0.306457,0.379849,0.334977,0.378761,0.303826,0.476405,0.371228,0.380999,0.368133,0.472333,0.278908,0.471378,0.257608,0.37781,0.276789,0.377075,0.255971,0.264633,0.375961,0.265015,0.319702,0.372559,0.317871,0.371033,0.372986,0.369385,0.431885,0.264221,0.436096,0.367798,0.492798,0.263824,0.498199,0.1875,0.4375,0.1875,0.376953,0.15625,0.4375,0.15625,0.376953,0.1875,0.5,0.15625,0.5,0.1875,0.320312,0.15625,0.320312,0.575456,0.120008,0.633205,0.135527,0.63794,0.1848,0.569826,0.168444,0.637074,0.219698,0.564369,0.204952,0.489607,0.162462,0.502077,0.114462,0.480016,0.199386,0.516465,0.059077,0.579248,0.062318,0.531812,0,0.58212,0,0.616926,0.071461,0.594873,0,0.261819,0.87197,0.262216,0.809562,0.361363,0.800746,0.359775,0.862882,0.262578,0.747188,0.36281,0.738752,0.358092,0.925111,0.261398,0.934403,0.356362,0.987388,0.260966,0.996847,0.1875,0.9375,0.1875,0.875,0.15625,0.9375,0.15625,0.875,0.15625,1,0.1875,0.8125,0.1875,0.75,0.15625,0.8125,0.15625,0.75,0.263187,0.622586,0.1875,0.625,0.1875,0.5625,0.263489,0.560364,0.15625,0.625,0.15625,0.5625,0.366455,0.553955,0.365248,0.615342,0.36407,0.676946,0.262892,0.684862,0.1875,0.6875,0.15625,0.6875,0.748993,0.188446,0.873535,0.187988,0.869141,0.220703,0.745972,0.222534,0.998047,0.1875,0.992188,0.21875,0.622559,0.224121,0.62439,0.188843,0.625,0.15625,0.75,0.15625,0.625,0.125,0.75,0.125,0.875,0.15625,1,0.15625,1,0.125,0.274185,0.890656,0.182983,0.900818,0.194824,0.847412,0.281555,0.832397,0.204834,0.789429,0.287262,0.769836,0.118469,0.91452,0.136963,0.868042,0.153442,0.817078,0.388428,0.822021,0.383484,0.883484,0.391479,0.756714,0.375977,0.942383,0.265381,0.946045,0.367188,1,0.255859,1,0.169922,0.951172,0.098633,0.958008,0.078125,1,0.292114,0.629898,0.28949,0.557312,0.393311,0.541748,0.393921,0.613647,0.283783,0.486969,0.391479,0.473511,0.393311,0.686279,0.291077,0.701538,0.212402,0.725342,0.213989,0.655945,0.167236,0.760132,0.173096,0.696686,0.206055,0.582031,0.190918,0.507568,0.165771,0.626221,0.121094,0.539062,0.73439,0.188492,0.734375,0.15625,0.8125,0.15625,0.8125,0.188477,0.84375,0.15625,0.84375,0.188477,0.734375,0.125,0.8125,0.125,0.84375,0.125,0.8125,0.222656,0.734436,0.222717,0.84375,0.222656,0.625244,0.2229,0.625061,0.188538,0.50061,0.223267,0.500153,0.188629,0.196728,0.729093,0.196049,0.748,0.138448,0.746588,0.138877,0.72737,0.115373,0.745918,0.115731,0.726558,0.195375,0.764877,0.138074,0.763839,0.115062,0.76334,0.139415,0.704219,0.197514,0.706129,0.140116,0.675171,0.198507,0.677077,0.116179,0.703324,0.116763,0.674279,0.278869,0.708275,0.277697,0.731017,0.28027,0.679229,0.276551,0.749557,0.27523,0.765996,0.228913,0.918806,0.21481,0.884757,0.300102,0.88431,0.318591,0.918491,0.204149,0.858565,0.28663,0.858016,0.341727,0.957931,0.245638,0.958093,0.263672,1,0.17477,0.95821,0.162835,0.919031,0.145642,0.958256,0.135696,0.919121,0.152492,0.885076,0.144536,0.858957,0.127077,0.885204,0.120447,0.859114,0.201153,0.301534,0.201713,0.33274,0.142372,0.332694,0.14198,0.301312,0.118644,0.332815,0.118317,0.301332,0.202315,0.36608,0.142796,0.366164,0.118997,0.366352,0.141644,0.274112,0.200671,0.274519,0.141386,0.253188,0.200302,0.253752,0.118036,0.274019,0.117822,0.252997,0.283325,0.275368,0.284019,0.302279,0.28279,0.254683,0.284821,0.333365,0.285676,0.366576,0,0.25,0.5,0.25,0.375,1,0.5,0.75,0.086441,0.951675,0.101257,0.970093,0.142334,0.953613,0.122375,0.92511,0.188354,0.935303,0.158356,0.89566,0.116699,0.895264,0.089233,0.932312,0.142334,0.85437,0.060547,0.966797,0.05249,0.976318,0.03125,1,0.019531,1,0.071289,0.985352,0.046875,1,0.374169,0.749009,0.23414,0.75,0.234375,0.875,0.373044,0.874328,0.234375,1,0.371094,1,0.507802,0.872313,0.51329,0.746035,0.514148,0.622578,0.373642,0.624394,0.511632,0.500662,0.37153,0.500166,0.233434,0.625,0.232021,0.5,0.364008,0.25298,0.3679,0.376003,0.506995,0.379011,0.498795,0.255686,0.485586,0.128747,0.361111,0.132173,0.469873,0,0.358711,0.012472,0.245009,0.149944,0.232091,0.256236,0.263174,0.049888,0.229668,0.375,0.438062,0.343582,0.397884,0.372736,0.506702,0.520642,0.54306,0.480378,0.358711,0.402255,0.469873,0.561377,0.578476,0.441055,0.480253,0.315158,0.61248,0.403145,0.522719,0.287828,0.373222,0.195137,0.327794,0.212845,0.423428,0.178135,0.29286,0.231024,0.263174,0.249438,0.604616,0.236693,0.563726,0.261957,0.644601,0.367117,0.679217,0.330549,0.720703,0.291016,0.646729,0.211182,0.765625,0.25,0.689453,0.185547,0.569336,0.131836,0.521249,0.14672,0.617188,0.117188,0.472691,0.162074,0.504875,0.250291,0.506836,0.12793,0.515625,0.125,0.51593,0.24943,0.509766,0.005859,0.515625,0,0.516846,0.37272,0.504852,0.373233,0.518677,0.4943,0.506271,0.495583,0.50061,0.37386,0.503082,0.252645,0.501526,0.49715,0.511719,0.136719,0.523438,0.023438,0.510857,0.734446,0.508636,0.616165,0.521729,0.613601,0.525696,0.731191,0.545776,0.612461,0.55014,0.72931,0.530273,0.847641,0.511841,0.849891,0.535156,0.963522,0.512207,0.963917,0.555176,0.844906,0.560547,0.959874,0.493652,0.845696,0.501617,0.736689,0.482422,0.950511,0.503052,0.6193,0.224197,0.825707,0.277283,0.891512,0.321289,0.86634,0.253174,0.786165,0.365234,0.840102,0.280762,0.745182,0.191406,0.700195,0.182983,0.757019,0.195312,0.640625,0.165527,0.80835,0.192444,0.862366,0.233154,0.914551,0.266968,0.363159,0.384766,0.356445,0.388428,0.411865,0.275574,0.422058,0.170898,0.436523,0.151855,0.371582,0.070312,0.453125,0.038086,0.380859,0.139648,0.31543,0.260071,0.310852,0.132324,0.26709,0.255035,0.264801,0.019531,0.320312,0.009766,0.269531,0.381104,0.306885,0.378052,0.262817,0.265625,0,0.375,0,0.249023,0,0.371094,0,1,0.75,1,0.875,1,1,1,0.625,1,0.5,0.992188,0.257812,0.96875,0.15625,0.921875,0.078125,1,0.375,0.742188,0.007812,0.625,0,0.628906,0,0.743164,0.007812,0.84375,0.03125,0.734375,0,0.8125,0,0.84375,0,0.735352,0,0.476562,0,0.46875,0,0.492188,0,0.428711,0,0.421875,0,0.507812,0,0.53125,0,0.523438,0,0.571289,0,0.449219,0,0.550781,0,0.742188,0,0.65625,0,0.741211,0,0.738281,0,0.652344,0,0.573242,0,0.640625,0,0.558594,0,0.243164,0,0.242188,0,0.34375,0,0.347656,0,0.359375,0,0.246094,0,0.749023,0.007812,0.75,0.007812,0.746094,0.007812,0.513672,0,0.634758,0.735423,0.684723,0.740596,0.687012,0.861321,0.638306,0.853797,0.689453,0.981761,0.64209,0.971729,0.592773,0.847641,0.588196,0.731191,0.597656,0.963522,0.584229,0.613601,0.631683,0.616166,0.682739,0.6193,0.543617,0.80064,0.540865,0.804798,0.615142,0.793106,0.618993,0.786792,0.672574,0.780986,0.67743,0.772081,0.535783,0.809815,0.608105,0.800437,0.663841,0.790824,0.620543,0.782554,0.544669,0.798194,0.620679,0.781447,0.544652,0.798312,0.679533,0.765182,0.68001,0.761363,0.457701,0.811426,0.457068,0.812747,0.36543,0.821574,0.365114,0.822235,0.457653,0.811803,0.365406,0.821762,0.455364,0.815204,0.4522,0.818236,0.364262,0.823463,0.362679,0.824979,0.193578,0.831499,0.193493,0.831622,0.273179,0.828716,0.27352,0.828225,0.193334,0.831773,0.272546,0.829323,0.273646,0.82796,0.193609,0.831433,0.273637,0.828036,0.193607,0.831451,0.136579,0.832838,0.113816,0.833024,0.199932,0.232986,0.141129,0.232264,0.117607,0.231975,0.282256,0.233997,0.376341,0.235153,0.37634,0.235153,0.200886,0.229346,0.203747,0.218424,0.286888,0.219372,0.283414,0.230341,0.381157,0.220456,0.377545,0.231479,0.141853,0.228635,0.118211,0.22835,0.144027,0.217747,0.120022,0.217476,0.558826,0.240239,0.470424,0.236309,0.558823,0.240202,0.558823,0.240195,0.635856,0.24957,0.635866,0.249716,0.69584,0.267149,0.695866,0.267514,0.635855,0.249542,0.695836,0.26708,0.635882,0.249941,0.55883,0.240295,0.635901,0.250205,0.558834,0.240361,0.695905,0.268076,0.695951,0.268737,0.558889,0.241139,0.558879,0.241004,0.55887,0.240872,0.558898,0.241263,0.558904,0.241361,0.636154,0.253811,0.636119,0.253317,0.696583,0.277753,0.696497,0.276518,0.636181,0.254204,0.696652,0.278733,0.636081,0.252778,0.636044,0.252248,0.696403,0.275169,0.69631,0.273844,0.559457,0.237808,0.561094,0.226856,0.636555,0.240637,0.636295,0.251106,0.695538,0.267115,0.696425,0.276565,0.636208,0.254596,0.558911,0.241459,0.69672,0.279714,0.471383,0.232617,0.474261,0.22154,0.753907,0.341603,0.73362,0.303296,0.733406,0.300175,0.753672,0.337835,0.733313,0.298853,0.753529,0.335787,0.696176,0.272107,0.695997,0.269398,0.762323,0.377414,0.762543,0.382169,0.764885,0.413948,0.765056,0.41927,0.762138,0.374772,0.764681,0.411041,0.762833,0.391148,0.754324,0.348809,0.763236,0.403535,0.755017,0.360443,0.76516,0.429426,0.765012,0.450695,0.734078,0.310019,0.69662,0.278912,0.734902,0.322143,0.75492,0.362508,0.734406,0.314415,0.734578,0.316884,0.755087,0.367867,0.734716,0.318846,0.755208,0.372333,0.763056,0.427362,0.763114,0.416293,0.763512,0.486229,0.763997,0.469515,0.762964,0.436987,0.762344,0.517176,0.763193,0.409163,0.754742,0.357892,0.763219,0.40307,0.754553,0.353586,0.764447,0.459776,0.765401,0.456221,0.734217,0.311717,0.734032,0.309067,0.754743,0.373111,0.753219,0.367621,0.760834,0.43517,0.762129,0.438179,0.760243,0.509931,0.761132,0.510427,0.762561,0.439181,0.755251,0.374942,0.761428,0.510593,0.734853,0.320808,0.734375,0.318236,0.73294,0.310523,0.743722,0.661274,0.719182,0.727542,0.718941,0.725697,0.743696,0.659989,0.678668,0.779488,0.678156,0.777228,0.756499,0.586242,0.756351,0.586847,0.755905,0.588664,0.743799,0.665127,0.719905,0.733076,0.680205,0.786268,0.542477,0.827041,0.456696,0.832891,0.455265,0.830239,0.541291,0.824419,0.365723,0.833621,0.364212,0.83098,0.617264,0.808442,0.618117,0.810947,0.620677,0.818461,0.546035,0.834905,0.460991,0.840849,0.370257,0.841544,0.194554,0.834992,0.137375,0.83545,0.193488,0.832373,0.114479,0.835633,0.273159,0.831723,0.274506,0.834352,0.278548,0.84224,0.197752,0.84285,0.139762,0.843286,0.116468,0.84346,0.558911,0.241449,0.636206,0.254557,0.696714,0.279616,0.636197,0.254439,0.558909,0.24142,0.696693,0.279322,0.75525,0.374774,0.762617,0.439334,0.761512,0.511109,0.762784,0.43979,0.755249,0.37427,0.761763,0.512658,0.734798,0.320023,0.734839,0.320612,0.743639,0.660546,0.718921,0.72578,0.678205,0.776818,0.718861,0.726029,0.743469,0.662218,0.718743,0.726526,0.742948,0.666736,0.67835,0.775587,0.678641,0.773127,0.756547,0.588947,0.756511,0.586918,0.755899,0.597711,0.541361,0.823878,0.455314,0.829863,0.364236,0.830792,0.45546,0.828734,0.54157,0.822253,0.455752,0.826476,0.541988,0.819005,0.364309,0.830228,0.364456,0.829099,0.61756,0.8061,0.617338,0.807856,0.618004,0.802588,0.19349,0.832354,0.193497,0.832298,0.193512,0.832185,0.273198,0.831422,0.273169,0.831648,0.273256,0.83097,0.753379,0.333589,0.733216,0.297435,0.733331,0.299074,0.753557,0.336128,0.73346,0.300909,0.753757,0.338974,0.695902,0.268028,0.69596,0.268847,0.696024,0.269765,0.762174,0.375212,0.761944,0.371935,0.764722,0.411525,0.764469,0.40792,0.762432,0.378884,0.765005,0.415564,0.76177,0.369448,0.753243,0.331661,0.761678,0.368144,0.753172,0.330651,0.764277,0.405184,0.764175,0.403751,0.733129,0.296191,0.695859,0.267406,0.733083,0.29554,0.199969,0.235063,0.20008,0.241293,0.141232,0.240633,0.141154,0.234356,0.117693,0.240384,0.117629,0.234077,0.28231,0.236066,0.376414,0.237235,0.28247,0.242272,0.376634,0.24348,0.558959,0.242663,0.470519,0.238439,0.558839,0.240427,0.635919,0.250469,0.636067,0.25289,0.636472,0.259625,0.559309,0.249237,0.470806,0.244829,0.201188,0.597643,0.284047,0.599062,0.282101,0.641778,0.199807,0.639909,0.378759,0.600173,0.376173,0.643201,0.141034,0.638259,0.14201,0.596402,0.117528,0.637488,0.118342,0.595827,0.142884,0.552521,0.202424,0.553295,0.143495,0.509533,0.203292,0.509884,0.11907,0.552172,0.11958,0.509401,0.28579,0.554226,0.381081,0.55504,0.287017,0.510411,0.38272,0.511068,0.56238,0.598934,0.639519,0.594433,0.635359,0.636497,0.558609,0.641645,0.699032,0.585911,0.694722,0.626991,0.470278,0.643518,0.473499,0.600488,0.476394,0.555467,0.565766,0.554379,0.478449,0.511812,0.568171,0.511336,0.643234,0.550648,0.702834,0.543145,0.64585,0.508335,0.705452,0.501504,0.753461,0.556355,0.756628,0.516164,0.762994,0.502124,0.760251,0.540878,0.763695,0.493679,0.761316,0.529223,0.758529,0.47518,0.764415,0.461176,0.756906,0.576955,0.749679,0.594435,0.753446,0.610175,0.745873,0.629158,0.758388,0.562388,0.755275,0.593581,0.730889,0.612041,0.735034,0.572292,0.726828,0.647851,0.738604,0.530744,0.740943,0.489538,0.764842,0.546944,0.767135,0.529232,0.767197,0.528753,0.764849,0.546883,0.767186,0.528834,0.764838,0.546965,0.768933,0.51535,0.769058,0.514377,0.769053,0.514417,0.762274,0.566768,0.762273,0.566776,0.759729,0.586411,0.759699,0.586644,0.762275,0.566759,0.759761,0.586163,0.762273,0.566783,0.764797,0.547293,0.762272,0.566788,0.764768,0.547518,0.759674,0.586845,0.759654,0.587,0.766928,0.530832,0.768544,0.518349,0.766791,0.531892,0.768017,0.522427,0.764439,0.550014,0.766918,0.530885,0.766815,0.531679,0.764277,0.551259,0.766705,0.532521,0.764106,0.552582,0.768919,0.515442,0.768867,0.515839,0.768813,0.516261,0.761591,0.571984,0.761805,0.570338,0.759094,0.591258,0.759339,0.589369,0.761364,0.57374,0.75883,0.59329,0.761991,0.568914,0.764581,0.548924,0.762134,0.567821,0.764693,0.548067,0.759548,0.587766,0.759701,0.58659,0.767009,0.530186,0.768965,0.515093,0.767082,0.52963,0.769001,0.514815,0.759657,0.556562,0.756042,0.592528,0.757495,0.577625,0.760792,0.542884,0.758745,0.567052,0.761721,0.534994,0.752318,0.624655,0.754021,0.609089,0.75559,0.596685,0.763378,0.503975,0.762577,0.515914,0.764116,0.499732,0.761481,0.535328,0.758014,0.576644,0.758953,0.567719,0.755332,0.605178,0.754162,0.612161,0.750389,0.643188,0.751633,0.636924,0.748141,0.664491,0.54348,0.807407,0.542684,0.813591,0.456239,0.822714,0.4568,0.818387,0.364699,0.827218,0.364979,0.825054,0.457312,0.814436,0.544196,0.801848,0.365235,0.823079,0.620288,0.78453,0.619574,0.79017,0.679985,0.761696,0.679633,0.764733,0.618744,0.796733,0.679127,0.769026,0.193564,0.831781,0.193536,0.831997,0.19359,0.831583,0.273568,0.828562,0.273466,0.829353,0.273354,0.830218,0.745947,0.681528,0.749098,0.66824,0.749017,0.669047,0.745677,0.683341,0.749127,0.668195,0.745644,0.683562,0.750333,0.658798,0.750368,0.658619,0.7506,0.65683,0.738898,0.701192,0.739464,0.698681,0.727231,0.722295,0.727975,0.719622,0.738728,0.702384,0.726959,0.724118,0.740824,0.693532,0.74669,0.676533,0.743249,0.682578,0.748113,0.666301,0.72966,0.715249,0.733499,0.707502,0.749559,0.664113,0.75069,0.655755,0.750591,0.655007,0.751636,0.647878,0.744714,0.68066,0.749076,0.653788,0.747161,0.671033,0.742813,0.696385,0.745145,0.689416,0.741012,0.712204,0.735682,0.719929,0.73763,0.706001,0.724102,0.742354,0.725928,0.730652,0.734109,0.733358,0.722802,0.753383,0.740206,0.692026,0.74676,0.665579,0.743428,0.677432,0.748941,0.651437,0.728675,0.718657,0.733583,0.705573,0.750904,0.63817,0.752659,0.624667,0.745805,0.682159,0.749637,0.66424,0.74968,0.663911,0.745936,0.681137,0.749719,0.663617,0.746078,0.680031,0.751394,0.650673,0.751357,0.650961,0.751303,0.651388,0.738729,0.701735,0.738528,0.703355,0.726663,0.724801,0.726432,0.726753,0.738955,0.699926,0.726927,0.722596,0.738376,0.704601,0.745697,0.683016,0.738298,0.705283,0.745621,0.683623,0.726269,0.728202,0.726206,0.728896,0.749585,0.664638,0.751395,0.650661,0.74952,0.665141,0.751343,0.651064,0.738384,0.685979,0.74628,0.668307,0.739855,0.694837,0.731533,0.710429,0.728559,0.721176,0.720206,0.733913,0.748621,0.651507,0.743156,0.678723,0.712613,0.724936,0.719305,0.702762,0.701813,0.745639,0.723505,0.677579,0.742636,0.659281,0.750357,0.640356,0.75234,0.623207,0.770699,0.500225,0.770756,0.50127,0.770262,0.505079,0.770242,0.503864,0.769276,0.512697,0.769324,0.511076,0.770746,0.501348,0.770222,0.50539,0.769175,0.513476,0.770085,0.500968,0.770503,0.497279,0.769714,0.495613,0.770099,0.491496,0.769229,0.508083,0.768898,0.503186,0.770631,0.495873,0.770848,0.498968,0.770612,0.495609,0.770843,0.498902,0.770196,0.489684,0.77015,0.489023,0.77092,0.5,0.770838,0.498831,0.770844,0.498913,0.77085,0.499005,0.770834,0.498769,0.770832,0.498737,0.770575,0.495077,0.770593,0.495325,0.770058,0.487692,0.770102,0.488314,0.770566,0.494946,0.770035,0.487366,0.770616,0.495653,0.770642,0.49602,0.770159,0.489133,0.770224,0.490051,0.764683,0.547439,0.762093,0.567553,0.761531,0.57248,0.764254,0.551455,0.760994,0.576624,0.763838,0.554666,0.75958,0.587086,0.758963,0.592306,0.758373,0.596864,0.766807,0.531748,0.767067,0.528899,0.768864,0.515874,0.768969,0.51409,0.766539,0.53382,0.768729,0.51691,0.767204,0.525325,0.76497,0.543098,0.767102,0.521079,0.764961,0.538914,0.768961,0.511224,0.768759,0.506942,0.762494,0.56272,0.760013,0.582368,0.762543,0.558855,0.760053,0.579314,0.752565,0.641675,0.752689,0.640726,0.752826,0.639683,0.752759,0.64017,0.753247,0.636448,0.753285,0.636084,0.752675,0.64083,0.752773,0.640097,0.753113,0.637484,0.75243,0.642626,0.752161,0.64477,0.751697,0.648119,0.751335,0.651105,0.753077,0.637479,0.752473,0.64172,0.752144,0.644911,0.752591,0.641459,0.752253,0.644051,0.752728,0.640383,0.75125,0.651816,0.75274,0.640308,0.752886,0.63916,0.752575,0.64157,0.752746,0.640257,0.753003,0.638259,0.752825,0.639635,0.753167,0.636987,0.752984,0.638395,0.75229,0.643764,0.752065,0.645511,0.75122,0.652021,0.751044,0.653393,0.752437,0.642618,0.751787,0.64766,0.752266,0.643969,0.751484,0.650011,0.751925,0.646601,0.750831,0.655042,0.752425,0.642739,0.752073,0.645464,0.765788,0.538678,0.765506,0.541804,0.763076,0.560562,0.763455,0.556843,0.760519,0.5803,0.760938,0.576433,0.765194,0.544219,0.762674,0.563677,0.760076,0.583728,0.763627,0.553164,0.765885,0.535095,0.763407,0.55017,0.765641,0.53131,0.761134,0.573055,0.760907,0.571099,0.76779,0.519763,0.767792,0.523052,0.767506,0.515367,0.767631,0.525394,0.76743,0.526952,0.763724,0.532976,0.762747,0.532272,0.760014,0.560411,0.761164,0.557179,0.757118,0.587638,0.758465,0.581816,0.762053,0.556834,0.764504,0.535365,0.75949,0.579085,0.766646,0.516212,0.765923,0.510422,0.76828,0.500911,0.767553,0.491885,0.765022,0.503404,0.766611,0.47862,0.768709,0.47231,0.768676,0.467937,0.767252,0.447611,0.76739,0.453226,0.765288,0.419603,0.765509,0.425692,0.768476,0.465092,0.766994,0.443939,0.767288,0.462728,0.768408,0.479737,0.765605,0.435882,0.769209,0.490995,0.769609,0.48512,0.769728,0.498092,0.77018,0.493209,0.769656,0.481937,0.769527,0.480102,0.770288,0.490969,0.767984,0.458114,0.768106,0.459857,0.766516,0.437185,0.766359,0.434936,0.764478,0.408133,0.764306,0.40566,0.768249,0.461905,0.7667,0.439828,0.766258,0.433476,0.767906,0.456982,0.76624,0.4332,0.767892,0.456769,0.764195,0.404054,0.769159,0.47487,0.76921,0.4756,0.77004,0.487435,0.770065,0.4878,0.76915,0.474732,0.769288,0.476724,0.76938,0.478046,0.770104,0.488362,0.763828,0.525018,0.761403,0.549254,0.759941,0.553519,0.76257,0.524751,0.758731,0.57492,0.757088,0.582056,0.764728,0.495879,0.765849,0.503097,0.766222,0.471456,0.767322,0.485482,0.766817,0.509702,0.764904,0.527578,0.768238,0.495856,0.762612,0.548502,0.760055,0.571494,0.753303,0.627814,0.750794,0.647302,0.752807,0.637054,0.754744,0.620261,0.753802,0.630771,0.755577,0.616141,0.748684,0.662468,0.751324,0.649894,0.757013,0.600583,0.755853,0.605673,0.757696,0.598699,0.754355,0.613768,0.751246,0.638527,0.747312,0.661642,0.743609,0.679909,0.755637,0.617764,0.754251,0.628557,0.754045,0.630291,0.755246,0.621013,0.753777,0.632363,0.75483,0.624225,0.756877,0.608418,0.75742,0.603882,0.75634,0.612562,0.757763,0.60022,0.755811,0.615724,0.754227,0.628328,0.754909,0.623578,0.755129,0.621867,0.756592,0.610573,0.756345,0.612489,0.758568,0.595323,0.758322,0.597229,0.755371,0.619988,0.756855,0.60853,0.756132,0.61415,0.754733,0.62495,0.755968,0.615428,0.754597,0.626012,0.758109,0.598881,0.757946,0.600152,0.75384,0.631849,0.753929,0.631147,0.753299,0.636024,0.7533,0.636001,0.753768,0.632416,0.753395,0.635291,0.754098,0.629825,0.754307,0.628194,0.753419,0.635067,0.753597,0.633677,0.756003,0.615143,0.754664,0.625468,0.75446,0.627047,0.755869,0.616186,0.754229,0.62884,0.755709,0.617424,0.753705,0.632862,0.753443,0.634893,0.753151,0.637152,0.757651,0.602434,0.757718,0.601913,0.759792,0.585914,0.759805,0.585807,0.757563,0.603125,0.75774,0.601735,0.756086,0.614489,0.757695,0.602068,0.756094,0.61442,0.759781,0.585985,0.754811,0.624317,0.753907,0.631288,0.754876,0.623808,0.754021,0.630398,0.755975,0.615033,0.756012,0.613798,0.758521,0.593853,0.758379,0.596335,0.75565,0.61471,0.758241,0.593251,0.75801,0.599655,0.755713,0.617377,0.757608,0.60277,0.755401,0.619791,0.753789,0.632218,0.75392,0.631012,0.7524,0.642931,0.75241,0.642756,0.753587,0.633776,0.752299,0.64371,0.75383,0.631134,0.753369,0.633558,0.752199,0.6441,0.753183,0.626281,0.751037,0.63734,0.754258,0.610236,0.755972,0.601128,0.757346,0.595572,0.754713,0.618804,0.752386,0.63926,0.75042,0.648778,0.74701,0.662651,0.753264,0.331996,0.753186,0.330865,0.761696,0.36842,0.761797,0.36988,0.761953,0.372129,0.753386,0.333739,0.733221,0.297532,0.733143,0.296407,0.733092,0.295678,0.55883,0.24029,0.558835,0.240372,0.558842,0.240464,0.635904,0.250249,0.635881,0.249921,0.63593,0.250616,0.635864,0.249673,0.558825,0.240228,0.764806,0.547206,0.762261,0.566849,0.762275,0.566749,0.764827,0.547047,0.767176,0.528914,0.767159,0.529042,0.769048,0.514457,0.769039,0.514521,0.767129,0.529264,0.764765,0.54752,0.769025,0.514632,0.762219,0.56717,0.763752,0.555319,0.760924,0.577143,0.761137,0.575496,0.763934,0.553906,0.766596,0.533363,0.766405,0.534843,0.768758,0.516682,0.768544,0.518336,0.766049,0.537597,0.763546,0.556912,0.765802,0.539503,0.763395,0.558085,0.768011,0.522455,0.767324,0.527757,0.76074,0.578571,0.760599,0.579669,0.745589,0.683924,0.738414,0.704584,0.738559,0.703578,0.745611,0.683784,0.726448,0.727514,0.726686,0.725941,0.749237,0.667344,0.749342,0.666528,0.749438,0.665783,0.745588,0.683898,0.738319,0.705215,0.726277,0.728583,0.746571,0.676552,0.739797,0.695215,0.739182,0.698118,0.746221,0.678926,0.727939,0.717465,0.727191,0.720392,0.749759,0.663323,0.749944,0.661774,0.750418,0.657716,0.747334,0.671642,0.741188,0.690123,0.729653,0.71309,0.770835,0.49878,0.770838,0.498836,0.770594,0.495345,0.770578,0.49512,0.770568,0.494974,0.770832,0.498743,0.770673,0.50066,0.770107,0.505167,0.770097,0.50635,0.770714,0.501587,0.770044,0.506764,0.770701,0.501691,0.770857,0.499097,0.770667,0.496387,0.770515,0.498128,0.770006,0.502616,0.752975,0.638462,0.75315,0.637105,0.753034,0.638012,0.752866,0.639307,0.752363,0.643192,0.752448,0.642532,0.752481,0.642274,0.753024,0.638081,0.753205,0.636683,0.751428,0.650429,0.75154,0.649573,0.751719,0.64819,0.751584,0.649233,0.75153,0.649651,0.751179,0.652362,0.751067,0.653182,0.751343,0.650939,0.751641,0.648749,0.751708,0.648268,0.751668,0.64858,0.768099,0.459706,0.766507,0.436991,0.766737,0.440268,0.768277,0.462246,0.769399,0.478266,0.769284,0.476627,0.769196,0.475384,0.767963,0.457779,0.766332,0.434503,0.768391,0.46753,0.768102,0.474167,0.76686,0.456034,0.766972,0.447182,0.766885,0.44247,0.768392,0.463953,0.769473,0.479367,0.769414,0.481941,0.769053,0.487019,0.755832,0.616424,0.757363,0.604615,0.75712,0.606487,0.755614,0.618108,0.754518,0.626562,0.754701,0.625145,0.753776,0.632287,0.753926,0.631125,0.75483,0.624156,0.756001,0.61513,0.754018,0.630419,0.757562,0.603085,0.755413,0.61971,0.75739,0.604463,0.757474,0.603815,0.755549,0.618662,0.753996,0.630634,0.753837,0.631863,0.752858,0.639412,0.752688,0.640724,0.753824,0.631961,0.755327,0.620376,0.753807,0.632091,0.755259,0.620902,0.752763,0.640141,0.753017,0.638182,0.757317,0.605024,0.757262,0.605455,0.770556,0.502812,0.770734,0.501438,0.770175,0.505751,0.770013,0.507003,0.770733,0.501442,0.770173,0.505767,0.769523,0.510794,0.770021,0.506944,0.768549,0.518313,0.768953,0.515192,0.770198,0.505577,0.770739,0.501394,0.769115,0.513943,0.770732,0.501452,0.77073,0.501463,0.770162,0.505853,0.770168,0.505808,0.770728,0.501482,0.770152,0.505926,0.770171,0.505783,0.770733,0.501446,0.77072,0.501544,0.770715,0.501584,0.770099,0.506336,0.77012,0.506177,0.770709,0.501626,0.770077,0.506504,0.770138,0.506037,0.770724,0.501509,0.770474,0.50344,0.769797,0.508671,0.769195,0.513316,0.769829,0.508418,0.76845,0.519064,0.767962,0.522834,0.770055,0.506673,0.770704,0.501668,0.770693,0.501753,0.770012,0.507013,0.768649,0.517531,0.763453,0.557647,0.760526,0.580243,0.760514,0.580328,0.763376,0.558237,0.757858,0.60084,0.757848,0.600914,0.76594,0.53844,0.766215,0.536321,0.766378,0.535063,0.763589,0.556593,0.760672,0.57911,0.758019,0.599599,0.754477,0.626955,0.75357,0.633953,0.75365,0.633337,0.754497,0.626794,0.753044,0.638017,0.753185,0.636924,0.755869,0.616195,0.755874,0.616164,0.756018,0.615048,0.754581,0.626152,0.753616,0.633606,0.753032,0.638105,0.752727,0.640426,0.752808,0.639784,0.753011,0.638212,0.752919,0.638945,0.752957,0.638612,0.75317,0.636967,0.752959,0.638654,0.752784,0.640006,0.75274,0.640346,0.752667,0.640892,0.753228,0.636507,0.753287,0.636057,0.75353,0.634178,0.753461,0.634713,0.753252,0.636328,0.753509,0.634351,0.75333,0.635722,0.753108,0.637439,0.752841,0.639517,0.752527,0.641955,0.752832,0.639601,0.753129,0.637299,0.75218,0.644633,0.752504,0.64214,0.753365,0.635471,0.753093,0.637562,0.751638,0.648814,0.751749,0.647957,0.752097,0.645277,0.75199,0.646099,0.752311,0.643624,0.752607,0.64134,0.752175,0.644679,0.751833,0.647313,0.751524,0.649698,0.751644,0.648767,0.752239,0.644178,0.755142,0.621795,0.757252,0.605532,0.757228,0.605714,0.755177,0.621529,0.75968,0.586797,0.759642,0.587093,0.753635,0.633414,0.753469,0.634698,0.753467,0.634711,0.755214,0.62124,0.757367,0.60464,0.759811,0.585784,0.764895,0.546536,0.767177,0.528914,0.766939,0.530751,0.764808,0.54721,0.762272,0.566791,0.762312,0.566481,0.762433,0.565547,0.765007,0.545668,0.76731,0.527887,0.751503,0.396916,0.756079,0.377491,0.763795,0.418512,0.758513,0.434724,0.741396,0.450815,0.735285,0.415681,0.706207,0.463799,0.70144,0.430239,0.717937,0.385243,0.732422,0.363877,0.687491,0.401033,0.736213,0.34147,0.56806,0.43858,0.47883,0.437354,0.477806,0.40391,0.565164,0.406321,0.382975,0.435275,0.382131,0.401188,0.635813,0.40671,0.643981,0.43704,0.646712,0.47069,0.568999,0.47316,0.479153,0.472881,0.383262,0.471524,0.203391,0.433953,0.14356,0.434041,0.143228,0.399628,0.202925,0.399494,0.119633,0.434182,0.119357,0.399822,0.286532,0.399865,0.287179,0.434253,0.287411,0.47076,0.203564,0.470427,0.143685,0.470358,0.119737,0.47039,0.754156,0.345076,0.753956,0.341819,0.733589,0.302745,0.733723,0.304662,0.696089,0.270683,0.696156,0.271642,0.733869,0.306743,0.754357,0.349156,0.696229,0.272682,0.763115,0.395114,0.762924,0.387531,0.762689,0.382555,0.558855,0.240651,0.558848,0.240556,0.558862,0.240755,0.636012,0.251783,0.635983,0.251367,0.635956,0.250983,0.740348,0.687593,0.751083,0.644215,0.753858,0.618589,0.74184,0.675836,0.718544,0.727354,0.718211,0.729153,0.717688,0.732561,0.738676,0.700082,0.716919,0.738216,0.736909,0.713415,0.748231,0.667217,0.745482,0.68837,0.731875,0.742585,0.725685,0.757695,0.732659,0.745831,0.739262,0.727565,0.715097,0.77267,0.721638,0.76336,0.743015,0.708448,0.735129,0.727704,0.715849,0.746757,0.713161,0.757385,0.70754,0.769298,0.697668,0.781696,0.265625,0.064453,0.265625,0.09375,0.1875,0.09375,0.1875,0.064453,0.15625,0.09375,0.15625,0.064453,0.1875,0.039062,0.265625,0.039062,0.1875,0.019531,0.265625,0.019531,0.15625,0.039062,0.15625,0.019531,0.250504,0.18898,0.125732,0.189209,0.125,0.15625,0.25,0.15625,0.000977,0.189453,0,0.15625,0.375305,0.188782,0.376221,0.223877,0.252014,0.22467,0.12793,0.225586,0.003906,0.226562,0.810555,0.249986,0.773468,0.249943,0.773438,0.125,0.810547,0.125,0.773438,0,0.810547,0,0.84375,0.25,0.84375,0.375,0.810577,0.374943,0.77356,0.374772,0.935547,0.75,0.935547,0.875,0.90625,0.875,0.90625,0.75,0.935547,1,0.90625,1,0.90625,0.625,0.935547,0.625,0.960938,0.625,0.960938,0.75,0.980469,0.625,0.980469,0.75,0.960938,0.875,0.960938,1,0.980469,0.875,0.980469,1,0.749756,0.064453,0.874023,0.064453,0.875,0.09375,0.75,0.09375,0.998047,0.064453,1,0.09375,0.625,0.09375,0.625,0.064453,0.625,0.039062,0.749023,0.039062,0.625,0.019531,0.747559,0.020508,0.871094,0.039062,0.992188,0.039062,0.865234,0.023438,0.734375,0.064453,0.734375,0.039062,0.8125,0.039062,0.8125,0.064453,0.84375,0.039062,0.84375,0.064453,0.734375,0.019531,0.8125,0.019531,0.84375,0.019531,0.8125,0.09375,0.734375,0.09375,0.84375,0.09375,0.265625,0.001953,0.1875,0.001953,0.15625,0.001953,0.375,0.001953,0.5,0.001953,0.375,0.007812,0.265625,0.007812,0.5,0.007812,0.1875,0.007812,0.15625,0.007812,0.25,0.001953,0.125,0.001953,0,0.001953,0.25,0.007812,0.125,0.007812,0,0.007812,0.998047,0.75,0.992188,0.75,0.992188,0.625,0.998047,0.625,0.992188,0.5,0.998047,0.5,0.998047,0.875,0.998047,1,0.992188,0.875,0.992188,1,0.991211,0.25708,0.988281,0.254883,0.976562,0.144531,0.970703,0.15332,0.938477,0.061523,0.926025,0.073975,0.998047,0.375,0.992188,0.375,0.74292,0.008789,0.745117,0.011719,0.625,0.007812,0.625,0.001953,0.84668,0.029297,0.855469,0.023438,0.734375,0.001953,0.8125,0.001953,0.84375,0.001953,0.8125,0.007812,0.734375,0.007812,0.84375,0.007812,0.992188,0.257769,0.992188,0.25764,1,0.37431,1,0.374827,1,0.497887,1,0.499472,0.992188,0.256577,1,0.370059,1,0.495445,0.242794,0,0.244613,0,0.34522,0,0.344117,0,0.42261,0,0.422059,0,0.24825,0,0.347424,0,0.423712,0,0.125882,0,0.001176,0,0.128527,0,0.133818,0,0.004703,0,0.011758,0,1,0.7478,1,0.623749,1,0.619997,1,0.741201,1,0.61601,1,0.728883,1,0.862061,1,0.871765,1,0.982748,1,0.995687,1,0.842652,1,0.956869,0.492206,0,0.492261,0,0.492371,0,0.468823,0,0.469044,0,0.469485,0,0.244141,0.000977,0.132812,0.003906,0.015625,0.007812,0.15625,0.015625,0.25,0.003906,0.183594,0.027344,0.256836,0.006836,0.0625,0.03125,0.15625,0.078125,0.984375,0.75,0.988281,0.5,0.96875,0.625,0.9375,0.625,0.953125,0.5,0.990234,0.257812,0.984375,0.257812,0.96875,0.375,0.978516,0.255859,0.945312,0.367188,0.997648,0.504841,0.99809,0.507339,1,0.524395,1,0.525621,1,0.560987,1,0.562303,0.998292,0.510004,1,0.524797,1,0.561993,1,0.530267,0.996203,0.50377,1,0.533283,0.994485,0.502805,1,0.568668,1,0.591554,0.984812,0.484812,0.990593,0.493743,0.967288,0.467288,0.977796,0.482521,0.977938,0.477938,0.939456,0.439456,0.992361,0.504961,0.993168,0.515217,0.980903,0.499803,0.98031,0.543308,0.504058,0.004058,0.51623,0.01623,0.520544,0.020544,0.505136,0.005136,0.525703,0.025703,0.506426,0.006426,0.53952,0.03952,0.547138,0.047138,0.570566,0.070566,0.503879,0.003879,0.503564,0.003564,0.515517,0.015517,0.538793,0.038793,0.514257,0.014257,0.54151,0.04151,0.49337,0,0.494183,0,0.49417,0,0.4909,0,0.489074,0,0.463601,0,0.47348,0,0.422277,0,0.437018,0,0.456294,0,0.376024,0,0.476732,0,0.476681,0,0.44183,0,0.441703,0,0.511502,0,0.535373,0,0.58138,0,0.502821,0,0.502279,0,0.511285,0,0.522569,0,0.526693,0,0.544596,0,0.509115,0,0.519676,0,0.547743,0,0.591146,0,0.573568,0,0.603009,0,0.746758,0.00769,0.737033,0.007324,0.822266,0.029297,0.838379,0.030762,0.895508,0.073242,0.915283,0.076904,0.714925,0.006592,0.790039,0.026367,0.855957,0.065918,0.656467,0,0.657118,0,0.647786,0,0.984497,0.247027,0.961426,0.234375,0.96875,0.322266,0.992188,0.342113,0.96875,0.395508,0.992188,0.421591,0.915283,0.210938,0.921875,0.290039,0.921875,0.355957,1,0.358685,0.992188,0.253734,1,0.447105,0.961426,0.153809,0.939453,0.146484,0.895508,0.131836,0.992446,0.491076,0.993222,0.500628,1,0.529623,1,0.517467,1,0.562745,1,0.553437,1,0.494997,0.992188,0.467987,1,0.537885,0.96875,0.439453,0.969784,0.46246,0.921875,0.395508,0.92351,0.416918,0.972888,0.472888,0.928415,0.428415,0.492074,0,0.492555,0,0.47022,0,0.468295,0,0.425549,0,0.423617,0,0.460756,0,0.490189,0,0.413411,0,0.507669,0.007547,0.530676,0.030188,0.53125,0.029297,0.507812,0.007324,0.53125,0.026367,0.507812,0.006592,0.577347,0.076126,0.578125,0.073242,0.578125,0.065918,0.507238,0.007238,0.528954,0.028954,0.575014,0.075014,0.50769,0.003906,0.507324,0.002441,0.506592,0.001221,0.507812,0.005371,0.53125,0.021484,0.530762,0.015625,0.578125,0.053711,0.576904,0.039062,0.529297,0.009766,0.526367,0.004883,0.573242,0.024414,0.565918,0.012207,0.504001,0.000122,0.516005,0.000488,0.539632,0.001221,0.505371,0.000488,0.521484,0.001953,0.553711,0.004883,0.62576,0.003906,0.671875,0.015625,0.736328,0.021484,0.674533,0.005371,0.710938,0.039062,0.790039,0.053711,0.618056,0,0.581163,0,0.550347,0,0.578505,0.002441,0.607422,0.009766,0.631836,0.024414,0.746094,0.125,0.75,0.171875,0.84375,0.236328,0.838379,0.171875,0.75,0.210938,0.84375,0.290039,0.822266,0.107422,0.734375,0.078125,0.646484,0.048828,0.653809,0.078125,0.65625,0.107422,0.65625,0.131836,0.750615,0.246709,0.75,0.234375,0.65625,0.146484,0.656009,0.153568,0.655286,0.155286,0.752459,0.252459,0.661338,0.161338,0.756407,0.256407,0.84922,0.34922,0.845118,0.339747,0.850964,0.350964,0.84375,0.322266,0.307757,0,0.305899,0,0.222498,0,0.221581,0,0.129997,0,0.128774,0,0.303253,0,0.223476,0,0.131302,0,0.220785,0,0.30804,0,0.220174,0,0.30762,0,0.127714,0,0.126898,0,0.383661,0,0.380674,0,0.383406,0,0.371101,0,0.358235,0,0.261474,0,0.254313,0,0.142637,0,0.154101,0,0.023516,0,0.038801,0,0.167328,0,0.268408,0,0.181438,0,0.275227,0,0.056437,0,0.07525,0,0.349865,0,0.352995,0,0.344222,0,0.351099,0,1,0.684178,1,0.653893,1,0.71973,1,0.768252,1,0.792974,1,0.857669,1,0.621138,1,0.667974,1,0.723965,1,0.810304,1,0.709525,1,0.913739,1,0.614057,1,0.61079,1,0.602866,1,0.592613,1,0.697377,1,0.621107,1,0.621973,1,0.701256,1,0.623985,1,0.704576,1,0.792735,1,0.786457,1,0.890314,1,0.881943,1,0.797565,1,0.896753,1,0.779213,1,0.693709,1,0.771487,1,0.690146,1,0.872284,1,0.861982,1,0.623338,1,0.627117,0.933516,0.434304,0.920328,0.420328,0.895108,0.395108,0.905331,0.405331,0.867123,0.367123,0.87421,0.37421,0.890437,0.390437,0.872059,0.372059,0.857122,0.357122,0.90788,0.40788,0.936867,0.440017,0.906593,0.406593,0.935299,0.441598,0.876642,0.376642,0.876748,0.376748,0.961807,0.474406,0.958221,0.461371,0.960619,0.485819,0.94509,0.44509,0.91118,0.41118,0.771941,0.271941,0.707293,0.207293,0.730717,0.230717,0.780944,0.280944,0.748695,0.248695,0.789635,0.289635,0.655824,0.155824,0.691461,0.191461,0.718433,0.218433,0.831031,0.331031,0.836982,0.336982,0.829394,0.329394,0.845256,0.345256,0.763331,0.263331,0.68142,0.18142,0.616571,0.116571,0.623017,0.123017,0.57693,0.07693,0.577585,0.077585,0.62616,0.12616,0.58302,0.08302,0.634524,0.134524,0.678782,0.178782,0.672342,0.172342,0.729716,0.229716,0.720887,0.220887,0.689414,0.189414,0.741079,0.241079,0.674285,0.174285,0.630319,0.130319,0.717125,0.217125,0.585833,0.085833,0.804329,0.304329,0.840697,0.340697,0.834064,0.334064,0.797309,0.297309,0.758231,0.258231,0.764637,0.264637,0.773227,0.273227,0.811058,0.311058,0.782909,0.282909,0.817641,0.317641,0.844949,0.344949,0.848009,0.348009,0.263672,0.007812,0.261719,0.007812,0.203125,0.03125,0.210938,0.03125,0.210938,0.078125,0.222656,0.078125,0.505859,0.246094,0.503906,0.25,0.328125,0.15625,0.335938,0.15625,0.5,0.257812,0.3125,0.15625,0.6875,0.34375,0.6875,0.328125,0.84375,0.390625,0.84375,0.367188,0.6875,0.375,0.976562,0.246094,0.976562,0.25,0.9375,0.34375,0.9375,0.328125,0.683091,0.775281,0.70832,0.751718,0.708108,0.752849,0.682867,0.776223,0.707997,0.753236,0.682729,0.776564,0.64961,0.796913,0.649822,0.796285,0.60739,0.813127,0.607575,0.812813,0.649458,0.797146,0.607241,0.813244,0.650075,0.795394,0.683371,0.773939,0.650349,0.794371,0.683681,0.772396,0.607785,0.812367,0.608006,0.811855,0.708599,0.750091,0.708913,0.748217,0.682302,0.77869,0.70771,0.755454,0.706408,0.764344,0.681626,0.78471,0.70546,0.772783,0.681052,0.790595,0.648779,0.80226,0.649029,0.798644,0.606891,0.815802,0.606885,0.813996,0.648547,0.805854,0.606916,0.817598,0.649261,0.795464,0.683089,0.773141,0.649437,0.793174,0.683646,0.769251,0.606892,0.812406,0.606908,0.811262,0.709598,0.746617,0.71089,0.740677,0.191987,0.832385,0.267155,0.831772,0.267142,0.831772,0.191983,0.832385,0.26713,0.831772,0.19198,0.832385,0.349203,0.831102,0.34917,0.831102,0.34914,0.831102,0.19199,0.832385,0.191994,0.832385,0.267169,0.831772,0.349238,0.831102,0.267183,0.831771,0.349273,0.831102,0.683361,0.768919,0.709311,0.743003,0.710402,0.740477,0.683627,0.768213,0.649522,0.792233,0.649538,0.792303,0.606928,0.810791,0.606952,0.810825,0.649509,0.793048,0.683175,0.770261,0.649457,0.79413,0.683028,0.771922,0.606978,0.811196,0.607006,0.811736,0.708795,0.745241,0.708568,0.747336,0.191967,0.832386,0.267075,0.831772,0.267073,0.831772,0.191966,0.832386,0.267076,0.831772,0.349003,0.831103,0.348996,0.831104,0.349005,0.831104,0.191968,0.832386,0.191971,0.832385,0.267082,0.831772,0.349019,0.831103,0.267091,0.831772,0.349042,0.831102,0.677975,0.780023,0.70253,0.757711,0.709865,0.748556,0.68377,0.774422,0.711314,0.742179,0.684553,0.770246,0.650266,0.796218,0.646017,0.799044,0.608057,0.812776,0.605393,0.813635,0.650718,0.793759,0.608363,0.811546,0.634466,0.801213,0.662888,0.785506,0.610043,0.803454,0.633364,0.790332,0.598225,0.813055,0.588549,0.811104,0.685287,0.766405,0.652737,0.77365,0.191974,0.832386,0.267107,0.831772,0.267128,0.831772,0.267152,0.831772,0.191986,0.832385,0.349081,0.831103,0.349135,0.831103,0.349194,0.831102,0.19197,0.832386,0.267088,0.831772,0.349035,0.831104,0.493915,0.828422,0.554861,0.823424,0.554986,0.824145,0.494061,0.828602,0.555127,0.824862,0.494219,0.82878,0.425112,0.830483,0.425002,0.830485,0.425229,0.830482,0.424912,0.830485,0.493795,0.828265,0.424852,0.830486,0.493717,0.828151,0.554763,0.822789,0.554702,0.822332,0.493714,0.828106,0.424846,0.830485,0.42488,0.830484,0.493761,0.828142,0.424925,0.830483,0.493823,0.828194,0.554761,0.822303,0.554713,0.822156,0.554822,0.822519,0.554689,0.822143,0.493694,0.828103,0.424833,0.830485,0.49431,0.828301,0.425247,0.830482,0.425316,0.830482,0.494422,0.828256,0.425387,0.830481,0.494538,0.828204,0.555573,0.82277,0.555412,0.822948,0.55574,0.822564,0.555264,0.823074,0.494204,0.828333,0.555136,0.823121,0.494108,0.828344,0.425182,0.830482,0.425121,0.830482,0.494855,0.827477,0.425917,0.829482,0.427464,0.826489,0.495815,0.82471,0.43068,0.820501,0.499624,0.818969,0.349561,0.830095,0.350405,0.827078,0.352153,0.821042,0.552372,0.82056,0.554843,0.822654,0.554471,0.815543,0.555847,0.82293,0.494646,0.828293,0.556044,0.822438,0.494765,0.82817,0.425464,0.830479,0.349311,0.831101,0.425531,0.830479,0.349345,0.8311,0.192049,0.831331,0.136603,0.83176,0.136673,0.828526,0.192214,0.828166,0.136813,0.822057,0.19255,0.821837,0.113836,0.831935,0.113894,0.828669,0.114011,0.822136,0.267723,0.82766,0.267321,0.830743,0.268553,0.821493,0.267199,0.831771,0.191998,0.832385,0.267212,0.831771,0.192001,0.832385,0.51945,0.796373,0.442811,0.797551,0.449661,0.782592,0.530212,0.781289,0.358755,0.797908,0.36253,0.782823,0.599475,0.77846,0.58426,0.79396,0.56718,0.806499,0.508378,0.809228,0.43614,0.810522,0.355116,0.810983,0.193828,0.797577,0.13735,0.797261,0.1377,0.78109,0.194608,0.781754,0.114458,0.797094,0.11475,0.780762,0.273532,0.782438,0.271695,0.797854,0.269956,0.811215,0.193115,0.811289,0.137046,0.811277,0.114205,0.811249,0.517972,0.819496,0.507712,0.823418,0.566474,0.819552,0.582791,0.814321,0.609177,0.81597,0.631755,0.809198,0.499498,0.826348,0.554233,0.823209,0.588053,0.820019,0.596995,0.807729,0.52774,0.814838,0.650106,0.800522,0.447185,0.821283,0.441264,0.82415,0.360172,0.826503,0.357211,0.827936,0.435377,0.826641,0.430469,0.82856,0.354268,0.829181,0.351814,0.830141,0.192788,0.832069,0.192493,0.832193,0.269181,0.831003,0.270359,0.830505,0.192248,0.832289,0.2682,0.831387,0.271543,0.829932,0.193084,0.831926,0.6843,0.769628,0.709802,0.744274,0.709228,0.746342,0.68399,0.770853,0.650622,0.793347,0.650824,0.792672,0.608227,0.811343,0.608396,0.811004,0.65088,0.792693,0.684609,0.769037,0.608459,0.811014,0.710895,0.741819,0.192,0.832385,0.267209,0.831771,0.267197,0.831771,0.191997,0.832385,0.349336,0.831101,0.349308,0.831101,0.192002,0.832385,0.267215,0.831771,0.349351,0.831101,0.682764,0.775044,0.708147,0.751278,0.70834,0.749431,0.682882,0.773583,0.649406,0.795212,0.649376,0.796164,0.607034,0.812277,0.607075,0.812753,0.649387,0.796852,0.682704,0.776104,0.60714,0.813097,0.708022,0.752629,0.191975,0.832385,0.267109,0.831772,0.2671,0.831772,0.191973,0.832385,0.349088,0.831102,0.349064,0.831102,0.191978,0.832385,0.267119,0.831772,0.349112,0.831102,0.493952,0.828295,0.554952,0.822924,0.554884,0.822734,0.493886,0.828248,0.424969,0.830482,0.425016,0.830482,0.425066,0.830482,0.494025,0.82833,0.555034,0.823062,0.494746,0.828117,0.556038,0.822222,0.555907,0.822358,0.494653,0.828152,0.425457,0.830481,0.425513,0.83048,0.425543,0.830479,0.494791,0.828117,0.556096,0.822225,0.67543,0.800363,0.644535,0.811566,0.648369,0.808969,0.680568,0.795737,0.604683,0.820189,0.606963,0.819154,0.704632,0.780265,0.698648,0.787145,0.682231,0.793779,0.660897,0.804697,0.633334,0.813604,0.597727,0.820423,0.494824,0.828777,0.425932,0.830288,0.425342,0.830481,0.494374,0.828934,0.349546,0.831005,0.34925,0.831101,0.555273,0.825483,0.554593,0.825669,0.55226,0.825079,0.495867,0.828025,0.427481,0.829711,0.35032,0.830717,0.192021,0.832376,0.191991,0.832385,0.267174,0.831771,0.267292,0.831733,0.267602,0.831617,0.192098,0.832347,0.999754,0.518293,0.999015,0.529815,1,0.547599,1,0.536399,1,0.580848,1,0.571924,0.998523,0.565984,1,0.598931,1,0.530759,1,0.567361,1,0.525039,0.999015,0.511575,1,0.562596,0.996062,0.521261,0.994093,0.557092,0.500519,0.000519,0.502075,0.002076,0.503113,0.003113,0.508302,0.008302,0.512453,0.012453,0.494159,0,0.476635,0,0.47663,0,0.494158,0,0.441588,0,0.441575,0,0.494163,0,0.476651,0,0.441626,0,0.307241,0,0.219623,0,0.219562,0,0.307199,0,0.126165,0,0.126083,0,0.383151,0,0.383176,0,0.383253,0,0.307367,0,0.219807,0,0.126409,0,1,0.708176,1,0.628246,1,0.635797,1,0.710346,1,0.640697,1,0.711571,1,0.802394,1,0.801911,1,0.903192,1,0.902548,1,0.800462,1,0.706568,1,0.900616,1,0.625193,1,0.709121,1,0.630897,0.494676,0.000519,0.496233,0.002076,0.498309,0.004151,0.516604,0.016604,0.999754,0.708262,0.999015,0.627463,0.996062,0.622059,0.999015,0.706911,0.992124,0.618121,0.998031,0.705927,0,0.618996,0,0.737991,0,0.75,0,0.606987,0,0.713974,0,0.725983,0,0.612991,0,0.820961,0,0.927948,0,0.951966,0,0.838974,0,0.240073,0,0.201716,0,0.102288,0,0.153431,0,0.413358,0,0.400572,0,0.301144,0,0.326716,0,0.387786,0,0.275572,0.1875,0.038358,0.1875,0.025572,0.0625,0.115073,0.0625,0.076716,0.125,0.051144,0.125,0.076716,0.0625,0.038358,0.125,0.025572,0.5,0.358747,0.5,0.239164,0.5,0.32624,0.5,0.217493,0.5,0.228329,0.5,0.342493,0.5,0.108747,0.5,0.114164,0,0.721906,0,0.647938,0,0.650421,0,0.714456,0,0.642971,0,0.645454,0,0.718181,0,0.571485,0,0.572727,0,0.116283,0,0.077522,0,0.073181,0,0.129307,0,0.086204,0,0.081863,0,0.122795,0,0.043102,0,0.040932,0.125,0.587658,0.125,0.675316,0.375,0.612553,0.375,0.725105,0.25,0.700211,0.25,0.600105,0.375,0.837658,0.25,0.900421,0.25,0.800316,0.218801,1,0.2292,1,0.156402,1,0.187601,1,0.5,0.948979,0.5,0.965986,0.5,0.846938,0.5,0.897959,0.140625,0.049676,0.09375,0.099352,0.125,0.102288,0.1875,0.051144,0.046875,0.046741,0.03125,0.093481,0.0625,0.096417,0.09375,0.048208,0.015625,0.140222,0,0.186962,0,0.192833,0.03125,0.144625,0.484375,0.23249,0.46875,0.154993,0.5,0.206657,0.5,0.309986,0.40625,0.051664,0.4375,0.103329,0.359375,0.025832,0.125,0.402443,0.25,0.393295,0.25,0.5,0.125,0.207329,0.25,0.179886,0.25,0.286591,0.125,0.304886,0.375,0.152443,0.375,0.268295,0,0.413761,0,0.327522,0,0.323181,0,0.418102,0,0.336204,0,0.331863,0,0.415932,0,0.254307,0,0.172409,0,0.163727,0,0.247795,0,0.424676,0,0.423208,0,0.274028,0,0.269625,0,0.346417,0,0.349352,0,0.265222,0,0.260818,0,0.340546,0,0.343481,0,0.733786,0,0.655857,0,0.640487,0,0.710731,0,0.779894,0,0.686596,0,0.671227,0,0.75684,0,0.593298,0,0.585613,0,0.948969,0,0.897938,0,0.900421,0,0.946485,0,0.892971,0,0.895454,0,0.947727,0,0.839456,0,0.785942,0,0.790908,0,0.843181,0.109375,0.887974,0.09375,0.925316,0.328125,0.962658,0.28125,0.975105,0.1875,0.950211,0.21875,0.925316,0.234375,0.987553,0.125,1,0.15625,0.975105,0.234375,0.009148,0.15625,0.018295,0.328125,0.027443,0.21875,0.054886,0.1875,0.036591,0.28125,0.018295,0.109375,0.082329,0.09375,0.054886,0.25,0.073181,0.0521,0.981987,0.1042,0.987991,0.031301,0.945961,0.062601,0.963974,0.083401,0.975983,0.0417,0.963974,0.093902,0.981987,0.125202,1,0.166801,1,0.125101,0.987991,0.047027,0.968298,0.031351,0.960613,0.041802,1,0.062702,1,0.015676,0.904894,0.01045,0.88184,0.020901,0.921227,0.031351,0.936596,0.005225,0.858786,0,0.835731,0,0.890487,0.01045,0.905857,0.176237,1,0.124458,1,0.110344,1,0.165516,1,0.19768,1,0.152687,1,0.138573,1,0.186958,1,0.107695,1,0.090187,1,0.432004,1,0.364008,1,0.360344,1,0.430172,1,0.435668,1,0.371336,1,0.367672,1,0.433836,1,0.235344,1,0.301508,1,0.182004,1,0.121336,1,0.171012,1,0.114008,1,0.117672,1,0.176508,1,0.057004,1,0.058836,1,0.5,0.889243,0.5,0.903486,0.5,0.667729,0.5,0.710459,0.5,0.806973,0.5,0.778486,0.5,0.753188,0.5,0.795918,0.5,0.863945,0.5,0.835459,0.5,0.400219,0.5,0.308479,0.5,0.580685,0.5,0.512123,0.5,0.410301,0.5,0.490452,0.5,0.443562,0.5,0.330151,0.5,0.818562,0.5,0.705151,0.5,0.705685,0.5,0.615452,0.5,0.660301,0.5,0.762123,0.5,0.525219,0.5,0.434986,0.5,0.456657,0.5,0.558479,0,0.600983,0,0.701966,0,0.802948,0,0.903931,0,0.278431,0,0.352288,0,0.426144,0.5,0.413315,0,0.780975,0,0.181091,0,0.135818,0,0.090545,0,0.045273,0.083603,1,0.5,0.72789,0,0.811714,0,0.842453,0,0.873192,0.020901,0.927948,0.041802,0.951966,0.062702,0.975983,0.5,0.556973,0.5,0.613945,0.5,0.670918,0.5,0.491959,0.5,0.570603,0.5,0.649246]], + "normals" : [-0.492355,0.682913,0.539598,-0.359905,0.682852,0.635731,-0.269631,0.683401,0.678396,-0.418958,0.710135,0.565813,-0.847499,0.419019,-0.325785,-0.788903,0.504807,-0.350352,-0.761162,0.40025,-0.510269,-0.816157,0.332316,-0.472671,-0.487259,0.083834,-0.869198,-0.524644,0.030244,-0.850765,-0.405438,-0.071322,-0.911313,-0.349162,0.030427,-0.936552,0.180944,-0.226386,-0.95706,0.07242,-0.41258,-0.908017,0.224799,-0.49498,-0.83929,0.337291,-0.311136,-0.888455,0.460341,-0.394482,-0.795251,0.351573,-0.564959,-0.746452,0.765007,0.413343,0.49382,0.800836,0.238929,0.54912,0.540819,0.447859,0.711966,0.522477,0.546159,0.654744,-0.791375,0.611255,-0.005676,-0.815302,0.573992,-0.076174,-0.85815,0.506302,-0.08475,-0.794763,0.605029,-0.04706,-0.680166,0.324534,-0.657247,-0.537736,0.185156,-0.822504,-0.543352,0.225257,-0.808679,-0.699881,0.373119,-0.609027,0.051729,-0.332408,-0.941679,0.069063,-0.290872,-0.954253,0.254311,-0.484085,-0.837214,0.258827,-0.541673,-0.799707,0.441786,-0.879086,-0.178838,0.48027,-0.82815,-0.288858,0.485519,-0.871639,-0.067049,0.444105,-0.894833,0.044923,0.369121,-0.894009,0.253853,0.270974,-0.943449,0.190863,0.160314,-0.986572,0.030488,0.301706,-0.950377,0.075411,-0.093356,-0.947539,0.305612,0.124027,-0.652333,0.747673,-0.32902,-0.517075,0.790124,-0.593036,-0.664479,0.454634,0.112522,0.522935,0.844874,-0.035554,0.432691,0.900815,0.274911,0.481246,0.832331,0.381512,0.555284,0.738945,-0.305887,-0.662954,0.683279,0.047334,-0.372784,0.926695,-0.204627,-0.222999,0.953063,-0.503983,-0.354747,0.787469,0.867092,-0.423688,-0.26191,0.997406,-0.047761,-0.053438,0.998657,-0.037263,0.035798,0.857204,-0.393933,-0.331614,-0.624866,0.551714,-0.552355,-0.66921,0.581805,-0.462142,-0.744774,0.474899,-0.468764,-0.692892,0.448683,-0.564409,-0.230537,0.188482,-0.954619,-0.34135,0.299387,-0.890957,-0.348369,0.255257,-0.901914,-0.223731,0.175878,-0.958617,0.437422,-0.451033,-0.777947,0.263527,-0.295328,-0.918302,0.308725,-0.229896,-0.92291,0.479446,-0.385937,-0.78811,0.717765,-0.6068,0.34138,0.944639,-0.220954,0.24247,0.792291,-0.112186,0.599689,0.565447,-0.318827,0.760643,-0.852657,0.42085,-0.309488,-0.849605,0.402966,-0.34022,-0.875942,0.340617,-0.341594,-0.824763,0.44557,-0.348125,-0.726524,0.682607,-0.078555,-0.744743,0.662648,-0.078829,-0.728782,0.684378,-0.021271,-0.717063,0.696921,-0.00824,-0.692801,0.718802,0.057405,-0.705374,0.708029,0.033235,-0.574145,0.755303,0.315958,-0.655049,0.714621,0.245338,-0.710074,0.658956,0.248054,-0.620991,0.741874,0.252876,-0.57857,-0.275735,0.767571,-0.266854,-0.079958,0.960387,-0.459883,0.096316,0.882717,-0.714255,0.038667,0.698782,0.293008,-0.944182,0.150334,-0.317606,-0.937162,0.144322,-0.26426,-0.938475,-0.222266,0.122349,-0.918424,-0.376141,-0.682821,0.697958,0.215827,-0.293069,0.532426,0.794092,-0.701041,0.690939,0.176244,-0.498001,-0.2566,-0.828303,-0.790307,0.216193,-0.57329,-0.916288,-0.17423,0.360607,0.49147,-0.857204,-0.153661,0.035646,-0.786615,-0.616352,0.334758,-0.919797,0.204657,0.368877,-0.927793,-0.055452,0.290902,0.001923,0.956725,0.564501,-0.559648,0.606708,0.248604,-0.9682,-0.027528,-0.581744,-0.691244,0.428632,0.540819,-0.811182,0.222388,0.192816,-0.882321,-0.429304,0.312571,-0.901975,0.2978,0.439161,-0.845943,0.302469,0.159246,-0.010651,0.987152,0.460921,-0.436659,0.772546,-0.656178,-0.686361,0.313486,-0.825831,0.554979,-0.099612,-0.622059,0.46437,0.63036,-0.830103,0.537461,-0.14835,-0.2978,-0.344127,-0.890408,-0.63155,0.102817,-0.768456,-0.922147,-0.318583,0.219398,-0.324442,0.770928,0.548051,0.017792,0.596606,0.802332,-0.024018,0.633534,0.773309,-0.312113,0.780877,0.541093,-0.598956,-0.199316,-0.775567,-0.855373,0.333598,-0.396222,-0.879421,-0.179235,0.440962,0.313974,-0.933042,-0.175512,-0.146764,-0.806757,-0.572344,0.303873,-0.948241,-0.091952,0.526078,-0.267983,0.807092,0.559801,-0.645222,0.519852,0.503098,-0.628071,0.593585,0.440596,-0.219062,0.87054,-0.291025,0.0094,-0.956664,-0.527482,0.245643,-0.813257,-0.341868,0.0141,-0.939604,0.577471,-0.807947,-0.116977,0.3773,-0.71279,-0.591205,0.32609,-0.686544,-0.6498,0.515244,-0.832575,-0.203162,-0.069887,0.005768,0.997528,0.263436,-0.529008,0.806665,-0.624622,-0.74694,0.22779,-0.880886,0.429792,-0.198126,-0.772912,0.330882,0.541368,-0.855525,0.415113,-0.309397,-0.931883,0.353893,-0.079348,-0.938047,0.334941,-0.088565,-0.886868,0.176946,-0.42671,-0.876888,0.229713,-0.422163,-0.218848,-0.27723,-0.935514,-0.318552,-0.273995,-0.907437,0.161016,-0.558489,-0.813685,0.331828,-0.472793,-0.816279,0.75277,-0.482925,-0.44731,0.650105,-0.618671,-0.441115,0.531175,0.025758,0.846858,0.874172,-0.202399,0.441389,0.75689,-0.271554,0.59444,0.404309,0.031892,0.91406,0.774712,-0.545061,-0.320475,0.449263,-0.537065,-0.71392,0.314249,-0.640034,-0.701132,0.672475,-0.694143,-0.256691,0.319163,-0.047487,0.946501,0.724326,-0.302133,0.619709,0.638234,-0.327525,0.696646,0.241371,-0.055239,0.968841,-0.932188,0.109989,-0.344768,-0.922941,0.182867,-0.338664,-0.783532,0.026246,-0.620746,-0.783471,-0.002686,-0.621357,0.005219,-0.377758,-0.925871,-0.065371,-0.37257,-0.925687,-0.816797,0.505264,0.27839,-0.899655,0.3961,-0.183569,-0.955992,0.267281,-0.120823,-0.881558,0.375713,0.285714,-0.353984,-0.179022,-0.917936,-0.283181,-0.184576,-0.94113,0.164251,-0.340709,-0.925687,0.167821,-0.315744,-0.933866,0.910062,-0.339457,-0.237739,0.88052,-0.411176,-0.235725,0.93173,-0.279611,0.231635,0.940184,-0.206397,0.270913,0.160649,0.276345,0.947508,-0.244728,0.441725,0.863094,-0.322611,0.351238,0.878933,0.082278,0.213385,0.973479,-0.375927,-0.217719,-0.900693,-0.731651,-0.13184,-0.668783,-0.713523,-0.014588,-0.700461,-0.38435,-0.154393,-0.910154,0.946593,0.00528,0.322306,0.876125,-0.325602,0.35548,0.522904,-0.20249,0.827967,0.659139,0.049379,0.750359,0.17008,0.10126,0.980193,0.036256,-0.020905,0.999115,-0.937101,-0.05179,-0.345134,-0.917447,0.077212,-0.39021,-0.998627,0.051088,0.00943,-0.991638,0.127415,-0.019501,-0.910459,0.049715,-0.410596,-0.918424,0.004334,-0.39555,-0.060976,-0.092074,-0.993866,-0.062868,-0.139531,-0.988189,0.413831,-0.157384,-0.896603,0.432966,-0.058016,-0.899533,0.973357,-0.213843,-0.082522,0.92758,-0.190649,0.321299,0.829585,-0.486587,0.273782,0.804987,-0.591784,-0.041841,0.159764,-0.155248,0.974853,-0.437056,-0.144475,0.887722,0.964019,0.188665,0.187231,0.764763,0.220344,0.605426,-0.111026,-0.113407,0.987304,-0.594775,-0.319102,0.737815,-0.368542,-0.622883,0.690023,0.071657,-0.550768,0.831568,-0.911863,-0.236274,-0.335612,-0.944456,-0.039399,-0.326212,-0.778619,-0.087954,-0.621265,-0.717093,-0.230934,-0.657582,0.064882,-0.101169,-0.992737,0.036348,-0.139286,-0.989563,0.549211,-0.11362,-0.827906,0.592761,0.059389,-0.803156,0.35783,-0.370525,-0.857112,0.786584,-0.043825,-0.615864,0.822108,-0.297494,-0.485397,0.583483,-0.634663,-0.506668,0.855892,0.39613,0.332377,0.874294,0.34376,0.342601,0.624195,0.40199,0.669851,0.637989,0.431806,0.637532,-0.090396,0.032685,0.995361,-0.55504,-0.169866,0.814264,-0.309336,-0.437361,0.844386,0.10715,-0.351787,0.929899,-0.928281,-0.248909,-0.276193,-0.937407,-0.111606,-0.329783,-0.728111,-0.190924,-0.658284,-0.703146,-0.291086,-0.648701,-0.009033,0.621174,0.783593,-0.197363,0.689444,0.69689,-0.086337,0.647359,0.757256,-0.581744,0.76925,0.264138,-0.652669,0.751732,0.094302,-0.707846,0.696005,0.120334,-0.643452,0.716544,0.269234,-0.71334,0.565294,-0.414197,-0.691397,0.44615,-0.568194,-0.631519,0.167943,-0.75692,-0.555528,0.218238,-0.802301,-0.477828,0.052736,-0.876858,-0.580401,-0.015687,-0.814173,0.510483,-0.764855,-0.392926,0.599628,-0.731925,-0.323557,0.653249,-0.618976,-0.435987,0.583697,-0.641377,-0.497879,0.880459,-0.47261,-0.037111,0.929319,-0.356975,0.09418,0.961058,-0.274667,-0.030274,0.900449,-0.406598,-0.154424,0.803217,0.03708,0.59447,0.60921,0.278085,0.742607,-0.91467,-0.196783,-0.352977,-0.860378,-0.135075,-0.491379,-0.987793,0.078677,0.134312,-0.940977,-0.100558,0.323099,0.368328,-0.548845,-0.750389,-0.31196,-0.561663,-0.766259,0.036012,-0.897885,-0.438704,0.497635,-0.687979,-0.528184,0.887814,0.079196,0.453291,0.985992,-0.165075,0.023499,0.889431,0.234809,0.392071,-0.205237,0.350291,0.913846,0.178686,0.380932,0.907163,-0.881069,-0.167577,-0.442244,-0.997925,0.063936,-0.003327,0.467727,-0.402905,-0.786676,-0.009217,-0.390271,-0.920652,-0.017182,-0.278573,-0.960234,0.424329,-0.290475,-0.857631,0.827631,0.241279,0.506699,0.993805,0.066775,0.088656,0.988189,0.091281,0.122959,0.898312,0.183416,0.399213,-0.543382,0.390637,0.743034,-0.128178,0.424268,0.89639,-0.278085,0.392499,0.876675,-0.629566,0.344371,0.696432,0.176397,0.601215,0.779351,0.288308,0.590045,0.754112,0.321024,0.51973,0.791681,-0.181982,0.702841,0.687643,-0.166875,0.718711,0.674947,-0.008606,0.614429,0.788903,-0.020325,0.60332,0.797204,0.165014,0.478866,0.862209,0.144536,0.458571,0.876797,0.441786,-0.691977,0.570879,0.591968,-0.247597,0.766961,0.507492,-0.352519,0.786218,0.437117,-0.748772,0.498245,0.197485,0.34843,0.916288,-0.014527,0.62392,0.781335,0.249855,0.328196,0.910947,0.432752,-0.861141,0.26664,0.454329,-0.59621,0.661855,0.453749,-0.871639,0.185217,0.250282,-0.964751,-0.081179,0.266396,-0.961394,-0.068758,0.371288,-0.923734,-0.093844,0.030793,-0.973083,-0.228309,0.277322,-0.918973,-0.28019,-0.047609,-0.99649,-0.068758,0.461409,-0.885922,-0.046999,0.446394,-0.887936,0.110721,0.530564,-0.82461,-0.196173,0.74163,-0.483352,-0.465102,0.695151,-0.514695,-0.501785,0.729545,-0.57036,-0.377361,-0.055239,0.116977,0.991577,-0.409528,0.226783,0.883633,0.522294,-0.531022,-0.667226,0.563494,-0.431471,-0.704428,0.176733,-0.408094,-0.895627,-0.734214,0.14594,-0.663015,-0.808069,0.069491,-0.584948,-0.92642,0.208258,0.313578,-0.992157,0.124912,0.000214,-0.996643,-0.000519,0.081668,-0.934843,0.005585,0.354991,-0.273232,-0.396496,-0.8764,-0.066836,-0.373882,-0.925047,-0.037385,-0.407788,-0.91229,-0.265053,-0.428663,-0.863674,-0.01062,-0.421888,-0.906552,-0.257057,-0.449721,-0.855342,0.144841,-0.351909,-0.924741,0.177374,-0.383435,-0.906339,0.20127,-0.389874,-0.898587,-0.496445,-0.454726,-0.739372,-0.479537,-0.430799,-0.764458,-0.739921,-0.471664,-0.479568,-0.702384,-0.473251,-0.531632,-0.511979,-0.471084,-0.718253,-0.766839,-0.450514,-0.457106,-0.461928,-0.391034,-0.796045,-0.273812,-0.353923,-0.894253,-0.432386,-0.353923,-0.82931,-0.261788,-0.306955,-0.914975,-0.680471,-0.435469,-0.589312,-0.64864,-0.429518,-0.628285,-0.086947,-0.328379,-0.940519,0.115665,-0.307138,-0.944578,-0.09479,-0.278573,-0.955687,0.093081,-0.260445,-0.960967,-0.027131,-0.027589,0.999237,-0.14948,-0.122135,0.98117,-0.201514,-0.052095,0.978088,-0.106784,0.011658,0.994201,-0.233314,0.026948,0.972015,-0.15067,0.053011,0.987152,-0.343547,-0.185614,0.920591,-0.346446,-0.094058,0.933317,-0.34254,0.008118,0.939451,-0.060793,0.072878,0.995483,0.012543,0.073153,0.997223,-0.005554,0.103061,0.994629,-0.005768,0.120853,0.992645,-0.089908,0.069308,0.99353,-0.005097,0.073336,0.997284,0.083132,0.107761,0.990692,0.046815,0.032563,0.998352,0.128788,0.171361,0.976745,0.087619,0.137394,0.986602,-0.006256,0.151433,0.988433,-0.00708,0.198859,0.97998,-0.107028,-0.036744,0.993561,-0.339457,-0.090854,0.936216,-0.083438,0.107852,0.990631,-0.321055,0.06357,0.944914,0.20835,0.206122,-0.956053,0.116001,0.228523,-0.966582,0.112888,0.298624,-0.947661,0.195471,0.242317,-0.950285,0.086825,0.346904,-0.933836,0.161596,0.273537,-0.948149,-0.012116,0.268715,-0.963134,-0.021851,0.366405,-0.930174,-0.042848,0.431562,-0.901059,0.164769,0.203742,-0.965026,0.180303,0.194067,-0.964263,0.000702,0.187689,-0.982208,0.000702,0.188208,-0.982116,0.137089,0.220679,-0.965636,0.000519,0.198614,-0.980071,0.180517,0.179632,-0.967009,0.194647,0.165227,-0.966826,0.170568,0.149815,-0.973876,0.166601,0.130955,-0.977264,0.000793,0.182897,-0.983123,0.001282,0.153966,-0.988067,0.094607,0.150884,-0.984008,-0.013398,0.158971,-0.987182,0.063112,0.105411,-0.992401,-0.02414,0.090976,-0.995544,-0.093112,0.023621,-0.995361,-0.092471,0.075045,-0.992859,-0.074618,0.059786,-0.995392,-0.101352,0.058351,-0.993133,-0.043519,0.075503,-0.996185,-0.060396,0.081332,-0.994842,-0.138096,0.16187,-0.977081,-0.074221,0.104038,-0.991791,-0.132481,0.079409,-0.987976,-0.140263,0.040376,-0.989288,-0.136662,0.060091,-0.988769,-0.188604,0.053499,-0.98059,-0.053468,0.078005,-0.995514,-0.019654,0.0318,-0.999298,-0.116855,-0.021607,-0.992889,-0.075716,0.00351,-0.997101,-0.126041,-0.090274,-0.987884,-0.099277,-0.003784,-0.995025,-0.197424,0.004242,-0.980285,-0.212134,-0.075106,-0.974334,-0.131687,0.152593,-0.979461,-0.239662,0.274789,-0.93115,-0.163427,0.243263,-0.956084,-0.330241,0.450209,-0.829585,-0.218146,-0.137883,-0.966094,-0.10004,-0.134404,-0.985839,-0.095126,-0.217261,-0.971465,-0.237709,-0.239723,-0.941252,0.051393,-0.141942,-0.988525,0.073641,-0.20838,-0.975249,-0.372906,-0.282632,-0.883755,-0.314035,-0.159581,-0.935881,-0.499222,-0.338542,-0.797571,-0.394787,-0.204169,-0.895779,-0.263863,-0.041047,-0.963652,-0.208319,-0.019898,-0.977844,-0.306314,-0.097629,-0.946898,-0.1171,-0.029237,-0.992676,0.020447,-0.055177,-0.99826,0.241615,-0.256478,-0.93585,0.263619,-0.294717,-0.918485,0.312357,-0.338481,-0.8876,0.294717,-0.317911,-0.90112,0.328684,-0.364544,-0.871212,0.322367,-0.357677,-0.8764,0.168432,-0.341563,-0.924619,0.201392,-0.373638,-0.905423,0.166143,-0.310068,-0.936064,0.13596,-0.230628,-0.963469,0.008789,-0.308847,-0.951048,0.007477,-0.223212,-0.974731,0.186254,-0.362346,-0.913236,0.009644,-0.365246,-0.930845,0.098392,-0.172338,-0.980102,0.172948,-0.202216,-0.963927,0.083346,-0.149327,-0.98526,0.127171,-0.153874,-0.979858,0.006531,-0.163182,-0.986572,0.006378,-0.153294,-0.988159,0.192846,-0.243355,-0.95056,0.109622,-0.290506,-0.95056,0.129673,-0.175878,-0.975799,0.04767,-0.208838,-0.976775,0.218909,0.65096,0.726829,0.129307,0.696158,0.706107,0.072115,0.54387,0.836024,0.208472,0.534684,0.818903,0.01297,0.421461,0.906735,0.177648,0.431318,0.884518,-0.016846,0.728813,0.6845,-0.128544,0.53148,0.837245,-0.204779,0.40434,0.891354,0.245033,0.524338,0.815455,0.235542,0.610126,0.756462,-0.012024,0.528153,0.849025,-0.01297,0.597064,0.802057,0.23188,0.442671,0.866146,-0.011139,0.456313,0.889737,0.197455,0.677694,0.708304,0.193579,0.730125,0.655293,0.134312,0.704398,0.696951,0.126804,0.746239,0.653462,-0.013855,0.653462,0.756828,-0.014344,0.684408,0.728904,0.138401,0.784539,0.604389,0.037294,0.833369,0.551408,0.086489,0.784265,0.614338,-0.003449,0.81048,0.585742,0.121403,0.287942,0.949889,0.145177,0.352672,0.924406,-0.025788,0.341502,0.939512,-0.04886,0.279153,0.958983,-0.237159,0.331126,0.913266,-0.25779,0.262825,0.929746,-0.066103,0.209723,0.975494,0.105502,0.222144,0.969268,-0.287606,0.176977,0.941221,0.155919,0.241249,0.957823,0.181341,0.304758,0.934996,-0.008087,0.258431,0.965972,-0.009125,0.319712,0.947447,0.207434,0.370342,0.905423,-0.010163,0.385968,0.922422,-0.745933,-0.058412,0.663411,-0.851497,-0.055605,0.521348,-0.836085,-0.019532,0.548235,-0.771325,-0.040498,0.635121,-0.799493,0.004761,0.600635,-0.775323,-0.03122,0.630757,-0.87817,-0.039338,0.476669,-0.846217,0.024964,0.532212,-0.800378,0.059633,0.596454,-0.597583,-0.030641,0.801202,-0.558336,-0.054628,0.827784,-0.354961,-0.01474,0.934751,-0.352306,-0.061983,0.933805,-0.653706,-0.028779,0.756157,-0.402936,0.003265,0.91522,-0.536699,-0.06241,0.841426,-0.7116,-0.07593,0.698447,-0.519822,-0.067232,0.851588,-0.679037,-0.094577,0.727958,-0.369854,-0.069613,0.92645,-0.377422,-0.056948,0.924253,-0.840114,-0.094882,0.534013,-0.887082,-0.101505,0.450301,-0.816706,-0.125095,0.56328,-0.883358,-0.140782,0.446974,-0.842311,0.099704,-0.529618,-0.739433,0.09714,-0.666158,-0.747948,0.101566,-0.655904,-0.861415,0.106265,-0.496628,-0.759819,0.085116,-0.64452,-0.874508,0.088168,-0.476913,-0.635609,0.071413,-0.76867,-0.614826,0.082034,-0.784356,-0.613392,0.07419,-0.786279,-0.953459,0.107852,-0.281533,-0.948424,0.095981,-0.302011,-0.992462,0.120487,0.021699,-0.995911,0.083529,0.034242,-0.956755,0.090732,-0.276284,-0.993927,0.109592,-0.006317,-0.936521,0.039766,-0.348277,-0.822108,0.046999,-0.567339,-0.913755,-0.057924,-0.40202,-0.818201,-0.059267,-0.571825,-0.999908,0.010529,0.00708,-0.995514,-0.080355,-0.049623,-0.752525,0.041444,-0.657216,-0.688192,0.005737,-0.725455,-0.800317,-0.088626,-0.592944,-0.758049,-0.140202,-0.636921,-0.924863,-0.370434,0.085849,-0.948454,-0.316813,0.003479,-0.941282,-0.302103,-0.150578,-0.927702,-0.369945,-0.049959,-0.922636,-0.240425,-0.301492,-0.916837,-0.333384,-0.219642,-0.95352,-0.282968,-0.103488,-0.930021,-0.240211,-0.278115,-0.929044,-0.217963,-0.298837,-0.909726,-0.413587,0.035981,-0.902066,-0.417646,0.108524,-0.903043,-0.414686,0.111911,-0.88583,-0.458174,0.073153,-0.923338,-0.367107,-0.112552,-0.934599,-0.355541,-0.008087,-0.860317,-0.456465,-0.226814,-0.883755,-0.443861,-0.148137,-0.836268,-0.485,-0.255715,-0.913266,-0.395123,-0.098972,-0.918638,-0.365123,-0.150761,-0.729606,0.605762,0.31727,-0.622974,0.710868,0.326395,-0.615711,0.74749,0.249184,-0.726676,0.642109,0.244148,-0.615375,0.773736,0.150304,-0.729911,0.667226,0.148198,-0.496445,0.801691,0.332804,-0.486221,0.836604,0.252205,-0.48207,0.862941,0.151402,-0.819483,0.523026,0.234199,-0.817103,0.490371,0.303079,-0.894467,0.392193,0.214637,-0.886807,0.367687,0.279885,-0.826411,0.544664,0.142735,-0.904355,0.406598,0.129612,-0.815851,0.452345,0.360179,-0.733879,0.563097,0.379833,-0.810053,0.412122,0.417035,-0.732536,0.515793,0.444197,-0.880062,0.337718,0.333781,-0.870418,0.305643,0.385876,-0.631794,0.667013,0.394848,-0.508133,0.759331,0.406446,-0.635151,0.615131,0.467055,-0.515366,0.706198,0.485427,-0.869228,-0.237587,0.433546,-0.887631,-0.186804,0.420942,-0.886135,-0.175512,0.428877,-0.865688,-0.209845,0.454451,-0.87756,-0.160802,0.451674,-0.867367,-0.174871,0.465896,-0.897763,-0.132206,0.420148,-0.893185,-0.140721,0.427076,-0.836116,-0.230354,0.497787,-0.858821,-0.26603,0.437696,-0.821741,-0.19541,0.535295,-0.885617,-0.224097,0.40672,-0.847133,-0.180151,0.499863,-0.824396,-0.152959,0.544939,-0.953703,-0.273812,0.12421,-0.947111,-0.249977,0.201147,-0.938749,-0.238411,-0.248726,-0.956969,-0.226691,-0.181005,-0.971892,-0.233985,0.024903,-0.950713,-0.188574,0.246101,-0.953398,-0.113346,0.27955,-0.978607,-0.170324,-0.115207,-0.448714,0.128086,0.884426,-0.521256,0.266091,0.810816,-0.564135,0.226966,0.793817,-0.46971,0.116886,0.875027,-0.549058,0.31315,0.774865,-0.453566,0.256935,0.853359,-0.600085,0.400922,0.69216,-0.644917,0.330241,0.689169,-0.622608,0.362377,0.693564,-0.361858,-0.016144,0.932066,-0.374493,-0.03647,0.926481,-0.238868,-0.176794,0.954802,-0.28312,-0.245491,0.927122,-0.333934,0.173772,0.92642,-0.202155,0.059816,0.977508,-0.395215,0.034822,0.917905,-0.414441,0.220099,0.883053,-0.426405,0.139775,0.893643,-0.401349,0.328379,0.855007,-0.361614,-0.202216,0.910092,-0.452284,-0.091098,0.887173,-0.436537,0.383648,0.813746,-0.477737,0.5421,0.691275,-0.378613,0.508225,0.773522,-0.359691,0.680441,0.638417,-0.703726,0.621876,0.343486,-0.727866,0.675069,0.120182,-0.790033,0.591845,0.159825,-0.750755,0.523911,0.402295,-0.830897,0.521043,0.195166,-0.758476,0.4785,0.442366,-0.741966,0.661153,-0.111118,-0.807367,0.578875,-0.114109,-0.854976,0.507981,-0.104556,-0.704978,0.429121,0.564623,-0.663869,0.523087,0.53444,-0.688345,0.419141,0.591998,-0.533677,0.667837,0.518784,-0.59447,0.736106,0.323527,-0.354564,0.801843,0.480911,-0.388562,0.859066,0.333171,-0.650899,0.749657,0.11951,-0.685171,0.721122,-0.102451,-0.465224,0.870571,0.1601,-0.543352,0.836146,-0.074526,-0.692862,0.462539,-0.553117,-0.595965,0.271371,-0.75573,-0.64095,0.225227,-0.733757,-0.75161,0.37492,-0.54268,-0.64272,0.329753,-0.691458,-0.756767,0.395734,-0.520218,-0.459059,0.068545,-0.885739,-0.493484,0.103793,-0.863491,-0.500778,0.275277,-0.820612,-0.799097,0.497604,-0.337291,-0.736167,0.589831,-0.331828,-0.82458,0.455763,-0.335093,-0.67626,0.653706,-0.339518,-0.621906,0.539598,-0.567461,-0.568957,0.747765,-0.342174,-0.53328,0.61449,-0.581347,-0.528947,0.360973,-0.768029,-0.408795,0.134617,-0.902615,-0.456404,0.4391,-0.773827,-0.359386,0.220496,-0.906735,-0.195349,-0.174078,-0.965148,-0.095065,-0.259835,-0.960936,-0.066164,-0.22013,-0.973205,-0.198737,-0.079104,-0.976836,-0.009705,-0.278024,-0.960509,-0.167821,-0.010346,-0.985748,0.002533,-0.389141,-0.92114,0.054964,-0.408155,-0.911222,0.089541,-0.514145,-0.852992,-0.341929,0.022675,-0.939421,-0.315989,-0.082034,-0.945189,-0.342906,0.185095,-0.920927,-0.286081,-0.075381,-0.955229,-0.192328,-0.216559,-0.957121,-0.264321,-0.011414,-0.964354,-0.198401,-0.194189,-0.960662,-0.132267,-0.307199,-0.942381,-0.08771,-0.412641,-0.906644,-0.171972,-0.308115,-0.935667,-0.176489,-0.401196,-0.898801,0.226203,-0.801111,-0.554064,0.271645,-0.914365,-0.30018,0.4073,-0.835627,-0.36848,0.300638,-0.760491,-0.575518,0.380474,-0.810419,-0.445418,0.249428,-0.761437,-0.598285,0.283029,-0.9541,-0.097842,0.476058,-0.859432,-0.186193,0.497635,-0.830927,-0.248787,0.176244,-0.61092,-0.771783,0.122013,-0.597522,-0.792474,0.160253,-0.676473,-0.718802,-0.0412,-0.599933,-0.798975,-0.018738,-0.825556,-0.563982,-0.213477,-0.541307,-0.813257,-0.293374,-0.728233,-0.619343,-0.054567,-0.96176,-0.268288,-0.10126,-0.994751,-0.012726,-0.397626,-0.858486,-0.323801,-0.472427,-0.880886,-0.028138,0.284585,-0.954436,0.089633,0.269723,-0.955748,0.117161,0.467971,-0.880001,0.080935,0.49678,-0.867428,0.026795,0.527818,-0.841182,0.117313,0.550798,-0.833583,0.041414,0.209571,-0.960753,0.181616,0.413343,-0.896969,0.156743,0.493912,-0.8464,0.199103,0.501602,-0.863308,-0.055513,0.284585,-0.95822,0.028108,0.549974,-0.831721,-0.075564,-0.129124,-0.980529,0.147923,-0.143712,-0.965514,0.216926,-0.516831,-0.837733,0.176214,-0.563066,-0.778161,0.278146,-0.159673,-0.956969,0.242195,-0.204138,-0.935759,0.287484,-0.601581,-0.728538,0.327555,-0.6086,-0.715659,0.342601,0.128544,-0.901486,0.413221,0.126499,-0.861995,0.490799,0.259041,-0.729789,0.632649,0.297769,-0.810755,0.503952,0.345378,-0.619953,0.70452,0.401532,-0.738609,0.541459,0.095309,-0.829981,0.549547,0.200903,-0.661428,0.722556,0.269204,-0.488174,0.830164,0.34315,-0.882656,0.321085,0.139439,-0.941374,0.307108,0.450758,-0.8211,0.350078,-0.204443,-0.929502,0.306955,-0.154149,-0.944029,0.291574,-0.554064,-0.783502,0.281228,-0.482559,-0.858547,0.173132,-0.112857,-0.946043,0.30369,-0.102329,-0.930937,0.350444,-0.428694,-0.894009,0.130131,-0.401776,-0.896329,0.187445,-0.057924,-0.664968,0.744591,-0.171667,-0.475387,0.86285,-0.108737,-0.342814,0.933073,0.014283,-0.484054,0.874905,-0.070406,-0.075838,0.994598,0.055239,-0.217231,0.974548,0.119083,-0.587329,0.800501,0.034059,-0.776818,0.628773,0.170446,-0.353862,0.919614,-0.130314,-0.877316,0.461837,-0.204108,-0.735618,0.645863,-0.408765,-0.839778,0.35728,-0.446486,-0.664724,0.598956,-0.295877,-0.485855,0.822413,-0.466109,-0.38023,0.798822,-0.409986,0.533677,0.739616,-0.476882,0.493057,0.727622,-0.354656,0.344279,0.869289,-0.286203,0.371166,0.883328,-0.181982,0.134648,0.974029,-0.122349,0.147557,0.981445,-0.555437,0.437178,0.707327,-0.460952,0.294107,0.837245,-0.307382,0.095553,0.946745,-0.255898,0.402295,0.878994,-0.372814,0.5656,0.735557,-0.234901,0.457259,0.857723,-0.342784,0.612445,0.712302,-0.102542,0.17542,0.979125,-0.091433,0.240577,0.966277,-0.472152,0.688345,0.550645,-0.509476,0.659719,0.552416,-0.552324,0.76397,0.333537,-0.581774,0.743706,0.329173,-0.432264,0.723899,0.537645,-0.508835,0.78753,0.347606,-0.565844,0.612964,0.551408,-0.617573,0.556841,0.555406,-0.622852,0.70397,0.341258,-0.655568,0.655019,0.375652,-0.641835,0.764336,-0.061434,-0.654653,0.742576,-0.141362,-0.642323,0.748924,-0.162755,-0.64684,0.760888,-0.050722,-0.649709,0.741966,-0.165258,-0.651295,0.75866,0.015717,-0.674184,0.725822,-0.136418,-0.652943,0.727134,-0.21189,-0.65981,0.687063,-0.304209,-0.64687,0.752281,0.124851,-0.62328,0.774621,0.107059,-0.665334,0.7228,0.186682,-0.607562,0.784204,0.12598,-0.642994,0.765526,-0.02121,-0.572954,0.799951,0.178198,-0.62682,0.776666,0.062136,-0.671194,0.736991,-0.079287,-0.696127,0.714316,-0.071657,-0.668661,0.743492,0.009095,-0.696036,0.717887,-0.011078,-0.720664,0.669851,-0.178564,-0.723014,0.563738,-0.399274,-0.750023,0.539659,-0.382305,-0.737205,0.626667,-0.252571,-0.734703,0.58269,-0.347331,-0.748955,0.555712,-0.36082,-0.594867,0.275124,-0.755242,-0.596789,0.338145,-0.727622,-0.600391,0.519639,-0.607837,-0.687735,0.692831,-0.216742,-0.69509,0.709555,-0.115482,-0.698813,0.611316,-0.371349,-0.713034,0.697409,-0.071749,-0.720573,0.670553,-0.176366,-0.709738,0.701987,-0.058412,-0.709281,0.672048,-0.212622,-0.690756,0.577624,-0.434919,-0.557512,0.329356,-0.762017,-0.661,0.576006,-0.48088,-0.536332,0.363353,-0.761773,0.010804,-0.503952,-0.863643,0.07651,-0.411878,-0.907987,-0.198828,-0.119602,-0.972686,-0.273782,-0.188025,-0.943205,0.214362,-0.272225,-0.938017,-0.138035,0.107822,-0.984527,-0.278787,-0.097201,-0.955412,0.029572,-0.478439,-0.87759,-0.304025,-0.019105,-0.952452,0.018586,-0.430799,-0.902249,0.186438,-0.632282,-0.751915,0.132023,-0.611774,-0.7799,0.25544,-0.685232,-0.682028,0.196539,-0.663106,-0.722251,0.220771,-0.631336,-0.74337,0.290628,-0.698935,-0.653432,0.146428,-0.497299,-0.855098,0.251503,-0.380444,-0.88992,0.168157,-0.531754,-0.830012,0.223914,-0.403241,-0.887265,0.186804,-0.58327,-0.79049,0.141881,-0.480636,-0.865352,0.167913,-0.535234,-0.827815,0.20835,-0.651448,-0.729514,0.163335,-0.325999,-0.93112,0.183569,-0.379131,-0.906919,0.270241,-0.6816,-0.679952,0.242683,-0.623157,-0.743461,0.299051,-0.700003,-0.648457,0.26017,-0.641835,-0.721335,0.242592,-0.580767,-0.777032,0.200018,-0.538255,-0.81869,0.386212,-0.711844,-0.586566,0.364086,-0.683462,-0.632679,0.258126,-0.615802,-0.744377,0.389752,-0.738578,-0.550035,0.175573,-0.436323,-0.882473,0.221351,-0.378613,-0.898679,0.384411,-0.64742,-0.65804,0.424665,-0.627766,-0.652303,0.528611,-0.834712,0.154149,0.539567,-0.808893,0.233467,0.548357,-0.823939,-0.142857,0.519303,-0.83462,-0.183569,0.585559,-0.769524,0.254769,0.582202,-0.801874,-0.13419,0.525163,-0.828364,-0.194891,0.547533,-0.831294,0.095584,0.515183,-0.834986,-0.193213,0.543626,-0.836756,0.065218,0.525163,-0.810022,0.260811,0.494583,-0.807855,0.320505,0.49855,-0.794519,0.346629,0.468215,-0.79635,0.382824,0.53444,-0.816523,0.218238,0.513932,-0.8034,0.300638,0.504959,-0.770196,0.389538,0.557085,-0.710166,0.430403,0.488601,-0.759301,0.429731,0.535874,-0.689291,0.487503,0.478805,-0.792383,0.377941,0.478042,-0.765496,0.430647,0.493301,-0.782495,0.379894,0.497482,-0.798151,0.339702,0.50322,-0.771325,0.389569,0.525132,-0.762596,0.377636,0.451979,-0.70333,0.548631,0.464705,-0.729881,0.501267,0.458174,-0.746574,0.482315,0.49086,-0.781884,0.384259,0.467299,-0.797113,0.382397,0.531022,-0.722617,0.442457,0.481338,-0.788537,0.382672,0.473952,-0.780053,0.408429,0.491287,-0.796838,0.351604,0.469985,-0.782342,0.408643,0.462386,-0.750328,0.472396,0.433576,-0.688803,0.58095,0.445723,-0.746269,0.494339,0.410749,-0.673818,0.614154,0.26307,-0.399762,0.878018,0.078524,-0.134709,0.987762,0.039064,-0.142247,0.989044,0.245888,-0.411451,0.877621,-0.077517,-0.179052,0.980773,0.16715,-0.45671,0.873745,0.389477,-0.611713,0.688528,0.387951,-0.590472,0.707663,0.35374,-0.653005,0.669607,0.374371,-0.578997,0.724265,0.260414,-0.387799,0.884152,0.352489,-0.5421,0.76278,0.24546,-0.325541,0.913083,0.086795,-0.113407,0.989746,0.084048,-0.040407,0.995636,0.03589,0.492477,0.869564,0.025178,0.489242,0.871761,0.178533,0.285836,0.941496,0.184332,0.300485,0.935789,0.29313,0.099002,0.950926,0.30137,0.110111,0.947111,0.000153,0.477493,0.878597,0.163091,0.265511,0.950194,0.276925,0.078494,0.957671,0.179388,0.303568,0.935728,0.03534,0.485855,0.873318,0.167638,0.303262,0.938017,0.03647,0.475448,0.878964,0.292032,0.111942,0.949828,0.263894,0.124424,0.956481,-0.161412,0.666768,0.727531,-0.168462,0.695364,0.698599,-0.257241,0.70806,0.657582,-0.235511,0.744438,0.624714,-0.123295,0.623524,0.771996,-0.225013,0.663259,0.713736,-0.182928,0.698691,0.69161,-0.220313,0.686087,0.69335,-0.242378,0.746422,0.619739,-0.309946,0.734153,0.604053,-0.277078,0.780786,0.559984,-0.46849,0.858547,0.208289,-0.491562,0.844844,0.211066,-0.294015,0.773888,0.5609,-0.527512,0.814356,0.241829,-0.343028,0.7322,0.588366,-0.572192,0.811182,-0.120487,-0.598071,0.792962,-0.116031,-0.628986,0.770531,-0.102969,-0.192663,0.70632,0.681143,-0.19126,0.708182,0.679617,-0.267434,0.684286,0.678365,-0.251595,0.689993,0.67864,-0.333567,0.778466,0.531663,-0.284066,0.694662,0.660848,-0.383404,0.804895,0.452864,-0.472823,0.859706,0.193152,-0.552507,0.824427,-0.122379,-0.478286,0.863491,0.159948,-0.540574,0.832148,-0.123417,-0.605884,0.609607,-0.511093,-0.590533,0.497208,-0.635609,-0.617054,0.469955,-0.631153,-0.632801,0.584704,-0.507553,-0.64391,0.446394,-0.621357,-0.661214,0.560686,-0.498398,-0.558458,0.370128,-0.742332,-0.585681,0.342998,-0.734336,-0.61214,0.322123,-0.722129,-0.631611,0.69451,-0.344493,-0.604633,0.716117,-0.348674,-0.661153,0.671163,-0.335215,-0.581988,0.734245,-0.349498,-0.5815,0.632923,-0.511093,-0.564989,0.747673,-0.348888,-0.560533,0.652547,-0.509812,-0.564623,0.525101,-0.636738,-0.530076,0.40025,-0.74752,-0.53853,0.550707,-0.637684,-0.494736,0.430525,-0.754845,-0.412275,0.047151,-0.909818,-0.451674,0.033235,-0.891537,-0.532853,0.197455,-0.82281,-0.501877,0.220099,-0.836421,-0.488845,0.026093,-0.871975,-0.562181,0.182318,-0.806635,-0.468551,0.247444,-0.848048,-0.370525,0.065798,-0.926481,-0.42143,0.280618,-0.862331,-0.314982,0.096469,-0.944151,-0.232856,-0.135319,-0.963042,-0.284921,-0.142827,-0.947844,-0.066042,-0.334269,-0.940153,-0.125248,-0.335032,-0.933836,-0.174383,-0.115635,-0.977844,-0.012452,-0.326212,-0.945189,-0.336711,-0.147374,-0.929991,-0.3867,-0.147435,-0.910306,-0.189062,-0.335582,-0.922819,-0.253731,-0.333995,-0.907743,0.17655,-0.615101,-0.768395,0.10715,-0.628651,-0.770257,-0.033448,-0.500534,-0.865047,0.036531,-0.496139,-0.867458,0.0271,-0.642964,-0.765404,-0.108982,-0.504562,-0.856441,0.095676,-0.494247,-0.864009,0.229591,-0.60683,-0.760918,0.138585,-0.49321,-0.85876,0.260628,-0.606433,-0.751152,0.340434,-0.691763,-0.636799,0.296609,-0.708182,-0.640675,0.432295,-0.760979,-0.483718,0.398846,-0.784173,-0.475326,0.359722,-0.687429,-0.630848,0.441359,-0.752098,-0.489395,0.232826,-0.732353,-0.639851,0.154088,-0.758202,-0.633503,0.344035,-0.816584,-0.463454,0.271706,-0.851192,-0.448958,0.515122,-0.854793,-0.062716,0.475051,-0.879574,-0.024415,0.425245,-0.867611,-0.257607,0.471938,-0.834864,-0.283273,0.420484,-0.907224,0.008362,0.361156,-0.902829,-0.233253,0.498337,-0.810816,-0.306925,0.538987,-0.836329,-0.100101,0.502213,-0.800592,-0.326762,0.544572,-0.828486,-0.130314,0.553178,-0.807001,0.206641,0.52739,-0.80932,0.258492,0.507767,-0.67806,0.531388,0.476821,-0.661061,0.579302,0.563402,-0.809992,0.162664,0.524247,-0.697409,0.488601,0.493088,-0.812586,0.310678,0.45613,-0.819636,0.346568,0.448073,-0.636341,0.627888,0.441176,-0.619526,0.64922,0.37965,-0.470107,0.796747,0.38728,-0.469314,0.793542,0.393628,-0.484725,0.78106,0.40727,-0.512284,0.756096,0.440291,-0.478835,0.759484,0.421705,-0.473312,0.77337,0.435102,-0.536607,0.722953,0.392743,-0.476363,0.786615,0.45439,-0.566454,0.68746,0.406232,-0.502762,0.762993,0.397687,-0.508255,0.763848,0.393658,-0.522477,0.75631,0.417524,-0.542741,0.728721,0.420759,-0.579791,0.697684,0.396558,-0.511704,0.762139,0.401532,-0.522446,0.75219,0.412702,-0.541917,0.732078,0.471603,-0.561418,0.679952,0.44026,-0.61391,0.655141,0.500656,-0.637013,0.586108,0.478103,-0.517258,0.709799,0.44087,-0.239051,0.865108,0.446059,-0.249031,0.859645,0.497757,-0.566301,0.65688,0.440931,-0.241066,0.864528,0.534074,-0.549089,0.642811,0.470382,-0.649831,0.597003,0.453078,-0.597247,0.661763,0.526536,-0.663198,0.531877,0.43907,-0.532762,0.723411,0.455123,-0.444655,0.771416,0.405316,-0.476424,0.780175,0.403638,-0.359233,0.841426,0.414167,-0.200903,0.887722,0.359874,-0.133824,0.923338,-0.110233,0.503647,0.856807,-0.141057,0.553117,0.821039,0.106601,0.28663,0.952086,0.1236,0.259255,0.957854,0.272958,0.054811,0.960448,0.26429,0.055116,0.962859,-0.156926,0.587542,0.793786,0.084841,0.331309,0.939695,0.290292,0.062349,0.954894,0.142644,0.252937,0.956877,-0.050081,0.471786,0.880245,0.266182,0.06122,0.961943,-0.293069,0.673971,0.678091,-0.353587,0.694479,0.626606,-0.407453,0.723045,0.557787,-0.419324,0.736717,0.530442,-0.341777,0.723777,0.599384,-0.292123,0.718192,0.631519,-0.359142,0.748527,0.55739,-0.285562,0.740287,0.6086,-0.307505,0.665548,0.680044,-0.348064,0.69274,0.631581,-0.35432,0.665975,0.656423,-0.356883,0.672323,0.648518,-0.537858,0.775903,0.329569,-0.473312,0.764794,0.437025,-0.654805,0.752892,-0.065615,-0.65566,0.754967,-0.009888,-0.423902,0.776696,0.465865,-0.247414,0.688345,0.681845,-0.405927,0.791192,0.457411,-0.214209,0.711081,0.669637,-0.641438,0.766594,0.028657,-0.629536,0.775353,0.049867,-0.291665,0.71334,0.637226,-0.218421,0.713706,0.665487,-0.700766,0.540513,-0.465529,-0.70043,0.645985,-0.303415,-0.685903,0.653005,-0.321055,-0.685293,0.544328,-0.483779,-0.666921,0.432752,-0.606555,-0.682791,0.4326,-0.588702,-0.634999,0.312845,-0.70629,-0.651631,0.317423,-0.688894,-0.692709,0.443617,-0.56859,-0.709128,0.547258,-0.444533,-0.697012,0.463088,-0.547441,-0.710898,0.562548,-0.42204,-0.663015,0.332926,-0.670461,-0.669271,0.35612,-0.652058,-0.70687,0.648885,-0.281503,-0.705008,0.661733,-0.255013,-0.544694,0.048036,-0.837245,-0.56212,0.074923,-0.823634,-0.620197,0.211097,-0.755455,-0.606555,0.190191,-0.771935,-0.57329,0.10715,-0.812281,-0.62859,0.238441,-0.740257,-0.587542,0.17951,-0.788995,-0.520768,0.030671,-0.853114,-0.429518,-0.136937,-0.892575,-0.460921,-0.114109,-0.880062,-0.309702,-0.32255,-0.894436,-0.349651,-0.29841,-0.888058,-0.483535,-0.082156,-0.871426,-0.498062,-0.045381,-0.865932,-0.377697,-0.264901,-0.887204,-0.394818,-0.226417,-0.890408,-0.091678,-0.635914,-0.766259,-0.121464,-0.614643,-0.779351,-0.252205,-0.449721,-0.856807,-0.221259,-0.479171,-0.849361,-0.134465,-0.586596,-0.798608,-0.269082,-0.414655,-0.869259,-0.175268,-0.498672,-0.848842,-0.04416,-0.646748,-0.761406,0.083224,-0.772881,-0.629017,0.038789,-0.77221,-0.634144,0.20658,-0.874447,-0.438887,0.169652,-0.882046,-0.439467,0.014405,-0.760582,-0.649068,0.0094,-0.741478,-0.670888,0.155461,-0.880398,-0.447981,0.166662,-0.873653,-0.457045,0.353069,-0.933714,0.05887,0.358257,-0.928098,0.101138,0.2772,-0.94293,-0.184423,0.27781,-0.938597,-0.204566,0.40788,-0.903928,0.128544,0.312113,-0.936552,-0.15949,0.305002,-0.927488,-0.216041,0.374432,-0.926664,0.032563,0.427625,-0.830653,0.356517,0.40669,-0.842891,0.352214,0.446638,-0.644917,0.620106,0.434523,-0.702506,0.563585,0.406384,-0.842952,0.352489,0.458693,-0.823817,0.332957,0.43263,-0.743004,0.510605,0.486068,-0.74929,0.449721,0.498581,-0.612537,0.6133,0.457198,-0.612293,0.644978,0.459395,-0.521592,0.718925,0.492569,-0.521043,0.697012,0.529862,-0.597613,0.601672,0.538133,-0.671468,0.509415,0.555315,-0.662984,0.502029,0.553484,-0.692892,0.46205,0.521317,-0.736351,0.431288,0.490768,-0.707755,0.508103,0.541093,-0.754723,0.370922,0.527146,-0.745628,0.407575,0.534043,-0.716269,0.44911,0.549303,-0.715873,0.430982,0.455733,-0.692007,0.559801,0.506882,-0.723502,0.468581,0.504196,-0.461043,0.730186,0.547533,-0.628498,0.552385,0.562487,-0.644276,0.518113,0.532823,-0.493149,0.687643,0.41966,-0.218818,0.880886,0.400037,-0.201727,0.893979,0.404492,-0.20069,0.892239,0.495041,-0.449995,0.743217,0.433485,-0.207434,0.876919,0.517441,-0.449904,0.727866,0.530839,-0.624928,0.572405,0.547868,-0.620594,0.56093,-0.52559,0.791406,-0.312052,-0.542009,0.814508,-0.206702,-0.500351,0.762322,-0.410443,-0.484176,0.701193,-0.523301,-0.425275,0.68865,-0.587237,-0.411023,0.603046,-0.683645,-0.531785,0.839625,-0.110508,-0.501175,0.808344,-0.308786,-0.437513,0.750755,-0.494858,-0.503189,0.580218,-0.6404,-0.538224,0.715537,-0.445296,-0.437757,0.599078,-0.67037,-0.506394,0.650472,-0.566057,-0.41792,0.467696,-0.778832,-0.577227,0.805536,-0.133671,-0.559893,0.82696,-0.051088,-0.609973,0.752403,0.248512,-0.59917,0.760704,0.24958,-0.600269,0.763756,-0.237281,-0.609973,0.758599,0.22895,-0.559679,0.828455,0.019471,-0.539628,0.83694,0.091067,-0.574389,0.774712,0.26429,-0.541154,0.790246,0.287393,-0.589099,0.481582,0.648823,-0.555895,0.508164,0.657796,-0.508072,0.533128,0.676443,-0.555223,0.42024,0.717704,-0.443068,0.582018,0.681814,-0.511399,0.40257,0.759178,-0.470077,0.622608,0.625538,-0.391583,0.726829,0.564196,-0.283792,0.819636,0.497635,-0.558946,0.428297,0.710013,-0.578906,0.529008,0.620441,-0.540513,0.495834,0.679647,-0.548692,0.598498,0.583667,-0.524613,0.368053,0.767632,-0.514939,0.411725,0.751854,-0.58269,0.659047,0.475478,-0.610034,0.612171,0.503067,-0.545854,0.703055,0.455733,-0.596057,0.598437,0.535325,-0.53737,0.644795,0.543535,-0.395001,0.437971,0.80752,-0.32667,0.645619,0.690207,-0.221931,0.675527,0.703116,-0.287576,0.479904,0.828822,-0.094913,0.691763,0.715812,-0.168706,0.518601,0.838191,-0.219031,0.826197,0.519028,-0.123997,0.8258,0.550127,-0.288797,0.371838,0.882199,-0.399213,0.346446,0.848842,-0.263558,0.333476,0.905148,-0.376385,0.334758,0.863826,-0.184179,0.402173,0.896817,-0.178411,0.343303,0.922086,-0.47618,0.342174,0.809992,-0.462844,0.408307,0.786767,-0.464064,0.35786,0.810266,-0.387829,0.610706,0.690359,-0.267434,0.813471,0.516434,-0.720634,0.333628,-0.607715,-0.746361,0.239784,-0.620808,-0.844234,0.170934,-0.50795,-0.822291,0.258065,-0.507126,-0.917966,0.109195,-0.381298,-0.901944,0.190435,-0.387585,-0.7669,0.158177,-0.621937,-0.860134,0.103763,-0.499374,-0.926725,0.05594,-0.371502,-0.798303,0.357463,-0.484664,-0.696799,0.435163,-0.570147,-0.783349,0.446699,-0.432142,-0.684225,0.53029,-0.500595,-0.876705,0.290445,-0.383374,-0.848933,0.373852,-0.373486,-0.580981,0.505753,-0.637654,-0.601917,0.403943,-0.688803,-0.453993,0.561571,-0.691702,-0.468764,0.460189,-0.75396,-0.571032,0.600482,-0.559709,-0.447188,0.65508,-0.608966,-0.625996,0.304575,-0.717856,-0.645924,0.211188,-0.733573,-0.48619,0.357067,-0.79754,-0.500534,0.255623,-0.827082,0.339274,-0.329905,-0.880917,0.335093,-0.358776,-0.871181,0.322459,-0.361797,-0.874691,0.304941,-0.332591,-0.892392,0.283242,-0.29133,-0.913694,0.335521,-0.290658,-0.896054,0.260445,-0.250923,-0.932279,0.324931,-0.25544,-0.91055,0.231635,-0.300912,-0.925077,0.220954,-0.338511,-0.91464,0.008789,-0.308725,-0.951079,0.009339,-0.344279,-0.93881,0.234321,-0.270302,-0.933805,0.008362,-0.281106,-0.959624,0.204474,-0.367229,-0.907346,0.009735,-0.371899,-0.92819,-0.211005,0.418867,-0.883175,-0.311686,0.510514,-0.801355,-0.332835,0.605792,-0.722617,-0.239448,0.521714,-0.818812,-0.353191,0.675649,-0.647053,-0.264687,0.594867,-0.758965,-0.142094,0.440657,-0.886319,-0.116855,0.333537,-0.935453,-0.166631,0.514145,-0.841334,-0.092685,0.203375,-0.97467,-0.181158,0.280313,-0.942625,-0.29487,0.372692,-0.879818,0.152898,0.056887,-0.986572,0.177099,-0.000214,-0.984191,0.072695,0.042726,-0.996429,0.041169,0.077242,-0.996155,0.209845,-0.064913,-0.975555,0.120853,-0.0159,-0.992523,0.04001,0.092502,-0.994903,0.147923,0.100558,-0.983856,0.162145,0.100864,-0.981567,0.161931,0.037477,-0.986084,0.002258,0.097842,-0.995178,0.003418,0.027497,-0.999603,0.167547,-0.030976,-0.985351,0.177496,-0.100345,-0.978973,0.004608,-0.044404,-0.998993,0.005768,-0.115909,-0.993225,0.279,-0.185278,-0.942228,0.212622,-0.216926,-0.952727,0.227851,-0.247108,-0.941801,0.306742,-0.224219,-0.924986,0.00763,-0.233894,-0.972228,0.008057,-0.26133,-0.965209,0.234901,-0.209632,-0.949126,0.204108,-0.157659,-0.966155,0.166814,-0.089969,-0.981842,0.244881,-0.130406,-0.960723,0.193426,-0.166356,-0.966887,0.006867,-0.183691,-0.98294,-0.300974,-0.451949,-0.839717,-0.578661,-0.468978,-0.667196,-0.534562,-0.489181,-0.689138,-0.264931,-0.463881,-0.845332,-0.789422,-0.429243,-0.438765,-0.774438,-0.460005,-0.434278,-0.004456,-0.419843,-0.907559,-0.030488,-0.39906,-0.91641,-0.081637,-0.342845,-0.93582,-0.353038,-0.381115,-0.854427,-0.130924,-0.239845,-0.961913,-0.380688,-0.244911,-0.891659,-0.62392,-0.370586,-0.687979,-0.796655,-0.313456,-0.51677,-0.620045,-0.204779,-0.757347,-0.274972,0.716453,0.641102,-0.387097,0.712943,0.584643,-0.525101,0.409131,0.746208,-0.406995,0.441023,0.79989,-0.559496,0.318308,0.765221,-0.462325,0.351054,0.814234,-0.390179,0.664235,0.637593,-0.569536,0.369671,0.734123,-0.643727,0.268227,0.716666,-0.287851,0.488113,0.823908,-0.14951,0.728965,0.667989,-0.362621,0.379894,0.850978,-0.070406,0.8652,0.496384,-0.157659,0.883145,0.441786,-0.227454,0.879971,0.416974,-0.205573,0.872738,0.442732,-0.556932,0.1901,0.808496,-0.605914,0.063814,0.792932,-0.464003,0.123203,0.877194,-0.423322,0.234596,0.875057,-0.644642,-0.076022,0.760674,-0.505478,-0.000824,0.862819,-0.392743,0.31843,0.862728,-0.504654,0.289529,0.813288,-0.597156,0.248146,0.762749,-0.65212,0.132603,0.746391,-0.69161,0.205176,0.692496,-0.739464,0.081362,0.668233,-0.719687,-0.015992,0.694082,-0.767602,-0.162511,0.619922,-0.805109,-0.101901,0.584277,-0.851405,-0.246101,0.463118,-0.160192,-0.019074,0.986877,-0.152867,-0.047548,0.987091,-0.070467,-0.047334,0.996368,-0.075625,-0.019349,0.996918,-0.003113,-0.046113,0.998901,-0.003571,-0.019745,0.999786,-0.13358,-0.075686,0.988128,-0.061922,-0.084048,0.994507,-0.002472,-0.083407,0.99649,-0.085971,0.032868,0.995727,-0.162938,0.028718,0.986206,-0.004425,0.032411,0.999451,-0.246864,0.01413,0.968932,-0.253334,-0.025452,0.96704,-0.344646,-0.007447,0.938688,-0.358074,-0.04471,0.932615,-0.254036,-0.049135,0.965941,-0.242317,-0.062624,0.968139,-0.37196,-0.059816,0.926298,-0.235786,0.911557,0.336772,-0.246529,0.875149,0.416272,-0.127201,0.900113,0.416608,-0.12067,0.934111,0.335856,-0.016602,0.908841,0.416791,-0.016846,0.941832,0.335612,-0.255104,0.825831,0.502853,-0.132511,0.852992,0.504776,-0.016205,0.862667,0.505448,-0.113926,0.961241,0.250984,-0.225227,0.940977,0.252632,-0.107974,0.982879,0.149205,-0.216865,0.964476,0.150761,-0.016938,0.967956,0.250496,-0.016877,0.988708,0.148778,-0.350627,0.901517,0.253548,-0.362224,0.869259,0.336375,-0.34315,0.926908,0.151891,-0.374584,0.829768,0.413648,-0.38377,0.778008,0.49736,-0.063997,0.563921,0.823328,-0.113926,0.434553,0.893368,-0.130955,0.423841,0.896176,-0.090762,0.547838,0.831629,-0.155522,0.342387,0.926572,-0.158635,0.350017,0.923185,-0.006653,0.69863,0.715415,0.023865,0.687735,0.725547,0.017884,0.664266,0.747276,-0.067415,0.558458,0.826777,-0.014252,0.674703,0.737907,-0.012543,0.561113,0.827601,-0.102023,0.429701,0.897153,-0.1236,0.315989,0.940642,-0.009888,0.385479,0.922636,-0.008545,0.295328,0.955351,-0.215705,0.52031,-0.826258,-0.210211,0.622272,-0.754021,-0.10538,0.635029,-0.765252,-0.107364,0.532884,-0.83932,-0.007538,0.640065,-0.768273,-0.005585,0.537919,-0.842952,-0.206763,0.716544,-0.666158,-0.103854,0.729484,-0.676015,-0.009461,0.734489,-0.678518,-0.108921,0.425184,-0.898495,-0.221229,0.413495,-0.883206,-0.108951,0.313669,-0.943236,-0.224281,0.303812,-0.925932,-0.003571,0.430006,-0.902799,-0.001526,0.317911,-0.948088,-0.345714,0.392407,-0.852321,-0.335124,0.497787,-0.799921,-0.353435,0.285928,-0.890652,-0.325663,0.599445,-0.731132,-0.320872,0.693319,-0.645192,-0.541124,0.39555,0.742088,-0.382763,0.431104,0.817042,-0.34669,0.333293,0.876736,-0.495956,0.26487,0.82693,-0.265267,0.212012,0.94055,-0.413343,0.090487,0.906034,-0.148717,0.467177,0.871548,-0.110782,0.397229,0.910977,-0.045442,0.318064,0.946959,-0.5262,0.251747,0.812189,-0.585589,0.397961,0.706168,-0.421674,0.052461,0.905209,-0.625111,0.51738,0.584399,-0.562914,0.503159,0.65566,-0.646321,0.618641,0.446638,-0.563311,0.602496,0.565386,-0.392987,0.517228,0.760247,-0.172948,0.538194,0.824854,-0.389691,0.606433,0.693075,-0.196051,0.622669,0.7575,-0.506455,0.799188,0.323649,-0.541948,0.700888,0.463698,-0.641072,0.705191,0.302774,-0.612079,0.776818,0.147954,-0.595141,0.795953,-0.11066,-0.462691,0.885006,0.051637,-0.583819,0.703177,-0.405774,-0.408124,0.849849,-0.333415,-0.333293,0.919553,0.208075,-0.359844,0.809564,0.463759,-0.258187,0.924741,0.27955,-0.247108,0.82458,0.508896,-0.266549,0.950285,-0.160741,-0.239479,0.969695,-0.047761,-0.376965,0.703421,0.602527,-0.222968,0.720939,0.656117,-0.388836,0.836573,-0.385876,-0.385815,0.784875,-0.484817,-0.588885,0.649648,-0.480728,-0.603961,0.692862,-0.393872,-0.603626,0.741539,-0.292825,-0.419233,0.867367,-0.268105,-0.569048,0.727897,-0.382519,-0.442549,0.840846,-0.311533,-0.388531,0.850154,-0.355266,-0.289956,0.852596,-0.434706,-0.337901,0.780694,-0.52562,-0.201941,0.800623,-0.564074,-0.455977,0.804224,-0.381115,-0.453688,0.727531,-0.514634,-0.238533,0.857082,-0.456557,-0.182287,0.910733,-0.370556,0.127384,0.017945,-0.991668,-0.150578,0.155797,-0.976226,-0.420911,0.601764,-0.678701,-0.314524,0.578875,-0.752281,-0.34669,0.245705,-0.905209,-0.490493,0.57683,-0.653188,-0.22602,0.382244,-0.895962,0.273721,-0.146886,-0.950499,0.38609,-0.326212,-0.862819,0.408582,-0.281503,-0.868191,0.343638,-0.360424,-0.867153,0.44911,-0.353404,-0.820582,0.205725,-0.220527,-0.953429,-0.059816,-0.118534,-0.991119,0.382885,-0.360332,-0.850612,0.171331,-0.328043,-0.928983,0.356609,-0.331767,-0.873318,0.371715,-0.397107,-0.839106,0.4008,-0.376873,-0.835017,0.406323,-0.338542,-0.848659,0.331523,-0.467544,-0.819422,0.276528,-0.391797,-0.877499,0.280709,-0.325175,-0.903012,0.25425,-0.30195,-0.91876,0.299875,-0.395154,-0.868252,0.380413,-0.458907,-0.802881,0.462874,-0.649525,-0.603168,0.485,-0.685781,-0.542619,0.409711,-0.547319,-0.729759,0.400525,-0.620655,-0.674001,0.48323,-0.720511,-0.497299,0.466811,-0.742821,-0.479843,0.640065,-0.763665,0.084201,0.624195,-0.78045,-0.035188,0.551714,-0.790613,-0.265481,0.573687,-0.792535,-0.206671,0.58504,-0.802728,-0.115329,0.525864,-0.791986,-0.31016,0.584429,-0.795831,-0.1583,0.619434,-0.760277,0.195471,0.601398,-0.681478,0.416944,0.656941,-0.674337,0.337138,0.568316,-0.628529,0.530961,0.628773,-0.5862,0.510849,0.663442,-0.715964,0.217261,0.618824,-0.774132,0.133183,0.648396,-0.620899,0.440504,0.604236,-0.694784,0.390027,0.553941,-0.532395,0.640065,0.589038,-0.534165,0.606342,0.551317,-0.636067,0.539811,0.533738,-0.674306,0.510269,0.494186,-0.709708,0.502029,0.508713,-0.554582,0.658467,0.42259,-0.721458,0.548509,0.417615,-0.567156,0.70983,0.523759,-0.412793,0.74514,0.565813,-0.454604,0.687857,0.50795,-0.437666,0.741874,0.529221,-0.520005,0.670431,0.432081,-0.353984,0.829432,0.44264,-0.296762,0.846126,0.603504,-0.524522,0.600482,0.55797,-0.593799,0.579669,-0.020173,-0.369335,0.929044,0.236915,-0.518601,0.821528,0.277322,-0.66805,0.690451,0.039583,-0.495254,0.867824,-0.226417,-0.218146,0.949248,-0.260414,-0.136113,0.95584,-0.126682,0.068789,0.989532,0.064974,-0.08945,0.993866,0.053255,0.223182,0.973296,0.18421,0.083529,0.979308,0.268624,-0.242378,0.932218,0.32667,-0.1077,0.938963,-0.023011,0.631275,0.775201,0.156835,0.423536,0.892178,0.076113,0.395825,0.915128,-0.138768,0.61507,0.776147,0.382214,0.09003,0.919645,0.317637,0.0824,0.944609,-0.206824,0.689749,0.693838,-0.076388,0.691916,0.717887,-0.188574,0.704856,0.683798,-0.063265,0.695242,0.715934,0.058718,0.706442,0.705313,0.160772,0.600146,0.783532,0.121677,0.663289,0.738395,0.269753,0.514817,0.813715,0.010376,0.734764,0.678213,0.320353,0.388653,0.863887,0.516892,0.09827,0.850368,0.451033,0.320139,0.833094,0.777184,-0.139164,0.613666,0.649464,0.080813,0.756066,0.655171,0.123081,0.745354,0.796808,-0.09711,0.596332,-0.179449,0.760033,0.624561,-0.068422,0.70101,0.709799,-0.141514,0.699576,0.700369,-0.187719,0.735252,0.651234,-0.397961,0.815455,0.420209,-0.421613,0.829554,0.3661,-0.614215,0.787622,0.048799,-0.616382,0.786859,0.029939,-0.461806,0.834254,0.301157,-0.235115,0.810755,0.536058,-0.530015,0.809839,0.251411,-0.369915,0.814295,0.447249,-0.624836,0.78045,0.020417,-0.675283,0.717551,-0.170446,-0.679434,0.657552,-0.32548,-0.677633,0.662435,-0.319315,-0.670461,0.726737,-0.149205,-0.065889,0.759087,0.647603,-0.191748,0.790399,0.581805,-0.7069,0.59975,-0.374889,-0.691488,0.689505,-0.215339,-0.696951,0.67861,-0.231727,-0.709098,0.5815,-0.398755,-0.698264,0.485641,-0.525864,-0.699149,0.506943,-0.504135,-0.672872,0.380932,-0.634114,-0.676321,0.402997,-0.616535,-0.689413,0.538133,-0.484848,-0.701254,0.622486,-0.347392,-0.673025,0.559648,-0.483505,-0.658223,0.442885,-0.608692,-0.688772,0.697867,-0.196234,-0.58916,0.16126,-0.791711,-0.539903,0.223609,-0.811457,-0.609729,0.343211,-0.714438,-0.639821,0.28779,-0.712546,-0.609973,0.358989,-0.706412,-0.634205,0.265419,-0.726127,-0.581195,0.137333,-0.802057,-0.508225,-0.011841,-0.86111,-0.518601,0.014283,-0.854854,-0.405896,-0.191748,-0.893551,-0.417432,-0.164708,-0.893643,-0.452223,0.082858,-0.888028,-0.330851,-0.091891,-0.939177,-0.143712,-0.539293,-0.829737,-0.033784,-0.461043,-0.886715,-0.182195,-0.285928,-0.940733,-0.288034,-0.35786,-0.888211,-0.063967,-0.492233,-0.868068,-0.208228,-0.337016,-0.918149,-0.278146,-0.382733,-0.880947,-0.138432,-0.560198,-0.816675,0.015442,-0.72396,-0.689627,0.01999,-0.70748,-0.706442,0.192633,-0.864406,-0.46437,0.210852,-0.845119,-0.491195,0.131718,-0.614002,-0.778222,0.089419,-0.629749,-0.771599,0.324931,-0.735343,-0.594684,0.503555,-0.863887,0.009003,0.38612,-0.895352,-0.2219,0.359325,-0.918668,-0.163915,0.467299,-0.878842,0.096133,0.524796,-0.806391,0.2725,0.57329,-0.800439,0.174932,0.554399,-0.743828,0.373241,0.613697,-0.73925,0.277261,0.680715,-0.732536,-0.001587,0.608325,-0.77752,-0.159215,0.659871,-0.740562,-0.126835,0.568651,-0.78103,-0.258003,0.727226,-0.677908,0.107425,0.730918,-0.681906,-0.027223,0.643971,-0.759056,-0.095279,0.54561,-0.818873,-0.178045,0.496292,-0.788903,-0.362285,0.446089,-0.792718,-0.415387,-0.065188,-0.692587,-0.718345,0.272866,-0.748344,-0.604541,0.108005,-0.807215,-0.580248,0.642201,-0.707785,0.294259,0.635304,-0.707755,0.308908,0.57152,-0.724143,0.385937,0.580706,-0.740074,0.339152,0.58565,-0.737693,0.335795,0.643269,-0.697592,0.315439,0.589801,-0.69451,0.412,0.643757,-0.654439,0.396527,0.731162,-0.606891,0.311533,0.752312,-0.61333,0.240425,0.833857,-0.486465,0.260781,0.827113,-0.538865,0.15952,0.718558,-0.586322,0.373974,0.830409,-0.470138,0.298959,0.896969,-0.385907,0.215583,0.873562,-0.45378,0.175909,0.752403,-0.632405,0.184118,0.78985,-0.610584,0.057344,0.4279,-0.856471,-0.288644,0.283029,-0.862423,-0.41963,0.609577,-0.437849,0.660787,0.637318,-0.575121,0.512833,0.586352,-0.606372,0.537095,0.558428,-0.450056,0.696799,0.478256,-0.214545,0.851588,0.535539,-0.219764,0.815394,0.659474,-0.172308,0.731681,0.721183,-0.372906,0.583789,0.831782,-0.30726,0.462294,0.840236,-0.425703,0.335734,0.865596,-0.249245,0.434217,0.896207,-0.339122,0.285958,0.727714,-0.51207,0.456282,0.818293,-0.569445,0.078036,0.735984,-0.676595,-0.022248,-0.40965,0.485397,0.772332,-0.40492,0.418714,0.812799,-0.442274,0.238105,0.864681,-0.444411,0.292215,0.846797,-0.492813,0.047334,0.868801,-0.481155,0.153233,0.863124,-0.501724,0.321909,0.802881,-0.371776,0.570788,0.732078,-0.57564,0.215949,0.788629,-0.227821,0.828578,0.511399,-0.326609,0.737541,0.591021,-0.193091,0.91113,0.364055,-0.204566,0.901852,0.380474,-0.354686,0.625019,0.695334,-0.276376,0.806574,0.522507,-0.199347,0.891079,0.407697,-0.196173,0.921323,0.335612,-0.20072,0.896664,0.394543,-0.163244,0.918973,0.358898,-0.260231,0.857967,0.442854,-0.332804,0.780663,0.528947,-0.429426,0.761345,0.485702,-0.283303,0.893613,0.348033,-0.536485,0.828852,0.158605,-0.3614,0.930509,0.058992,-0.2678,0.936552,0.226081,-0.355449,0.934629,-0.010651,-0.328959,0.805658,-0.492599,-0.366588,0.885495,-0.285409,-0.484329,0.812281,-0.324961,-0.392193,0.745659,-0.538652,-0.336161,0.688925,-0.64214,-0.314097,0.658925,-0.683462,-0.252541,0.529496,-0.809839,-0.284188,0.415204,-0.864162,-0.386364,0.533433,-0.752403,-0.414747,0.723716,-0.5515,-0.324381,0.303385,-0.895932,-0.404004,0.862941,-0.303385,-0.188788,-0.082797,-0.978515,-0.23307,0.145329,-0.961516,-0.190588,0.225532,-0.955382,-0.152379,-0.03354,-0.987732,-0.163335,-0.153264,-0.974578,-0.18836,-0.211524,-0.959044,-0.234199,-0.165136,-0.958037,-0.243446,-0.255837,-0.935545,-0.192114,-0.267464,-0.944212,-0.200781,-0.139836,-0.969573,-0.228248,-0.340739,-0.912015,-0.252174,0.064974,-0.965484,-0.481582,-0.392193,-0.783715,-0.344646,-0.296701,-0.890591,-0.336955,-0.184179,-0.923307,-0.455,-0.276101,-0.846583,-0.588031,-0.403638,-0.700888,-0.628834,-0.512131,-0.584979,-0.723777,-0.498459,-0.477096,-0.745445,-0.586077,-0.317454,-0.585284,-0.67391,-0.45085,-0.444044,-0.554552,-0.703726,-0.679067,-0.717826,-0.153539,-0.312387,-0.425398,-0.849361,-0.852412,-0.5038,0.139775,-0.814478,-0.576708,-0.063082,-0.828974,-0.525529,-0.19126,-0.876156,-0.477371,0.06653,-0.891781,-0.409284,0.192755,-0.871395,-0.415693,0.260445,-0.930998,-0.349803,0.104038,-0.897,-0.372539,0.237861,-0.807825,-0.498306,0.314737,-0.773949,-0.589831,0.230293,-0.81106,-0.487686,0.322947,-0.730461,-0.677938,0.0824,-0.899503,-0.436171,0.024201,-0.910367,-0.387768,0.144169,-0.949553,-0.310099,0.046632,-0.95465,-0.29432,0.044801,-0.955443,-0.288675,-0.061312,-0.956298,-0.237739,0.17008,-0.929929,-0.284738,0.232643,-0.893765,-0.442671,0.072054,-0.885372,-0.286294,0.366192,-0.881161,-0.398846,0.253822,-0.899594,-0.26606,0.346263,-0.711783,-0.701956,0.023774,-0.743431,-0.663564,0.083316,-0.708457,-0.689444,0.15067,-0.790124,-0.570452,0.224128,-0.722312,-0.153539,0.674276,-0.652058,-0.42375,0.628651,-0.702597,-0.604541,0.375286,-0.824641,-0.33198,0.4579,-0.841121,-0.169744,0.513474,-0.760643,0.019318,0.648854,-0.664205,0.146916,0.732963,-0.579913,0.026429,0.814234,-0.568224,-0.176946,0.803613,-0.674825,-0.221686,0.703848,-0.532212,-0.211066,0.819849,-0.530229,-0.144658,0.835383,-0.665029,-0.209235,0.71688,-0.773339,-0.259957,0.578204,-0.768273,-0.221625,0.600513,-0.843928,-0.247536,0.475906,-0.822016,-0.18656,0.53798,-0.768731,-0.118198,0.628529,-0.664113,-0.105075,0.740165,-0.777123,-0.092288,0.622486,-0.652455,-0.037812,0.756859,-0.811487,-0.132481,0.569109,-0.512375,-0.101932,0.852657,-0.488296,-0.006287,0.872616,0.126896,0.293435,-0.947508,0.019684,0.281961,-0.959197,0.058382,0.382855,-0.921934,0.118961,0.373577,-0.919919,0.063082,0.408795,-0.910428,0.099521,0.382855,-0.918424,-0.117191,0.255745,-0.959593,-0.068972,0.382244,-0.921476,-0.060732,0.437178,-0.897305,0.115635,0.342357,-0.932401,0.150487,0.258065,-0.954314,-0.001495,0.315897,-0.948759,0,0.229652,-0.973266,0.074801,0.356761,-0.931181,-0.002014,0.345164,-0.938536,0.178167,0.139439,-0.974059,0.126255,0.140355,-0.981994,0.201636,0.010193,-0.9794,0.141057,-0.006714,-0.989959,0.001801,0.124485,-0.992218,0.003784,0.005829,-0.999969,-0.014557,0.106143,-0.994232,-0.154241,0.08005,-0.984771,-0.009095,-0.052156,-0.998596,-0.153264,-0.088656,-0.984191,-0.556322,0.207251,0.804682,-0.545396,0.093234,0.832942,-0.686117,0.015656,0.727287,-0.695212,0.024751,0.718345,-0.733818,-0.07297,0.675375,-0.734733,-0.086612,0.672781,-0.516007,-0.060701,0.854396,-0.647023,-0.04648,0.76101,-0.66277,-0.06534,0.745933,-0.712607,0.024812,0.701102,-0.588549,0.251289,0.768395,-0.740226,0.035096,0.671438,-0.630482,0.260414,0.731162,-0.745445,-0.099216,0.659108,-0.778375,-0.110141,0.61803,-0.494339,0.343852,0.798334,-0.406415,0.238685,0.881924,-0.541032,0.226478,0.8099,-0.355846,0.148534,0.922636,-0.571551,0.367687,0.733573,-0.668264,0.255043,0.698813,-0.335002,0.059084,0.940336,-0.326548,-0.125462,0.936796,-0.146519,0.022462,0.988952,-0.118168,-0.09888,0.988037,-0.812952,0.252968,0.524491,-0.692068,0.25837,0.673971,-0.772179,0.04294,0.6339,-0.853175,0.043947,0.5197,-0.809076,-0.11127,0.577044,-0.871517,-0.107913,0.478286,-0.948576,0.052278,0.312143,-0.928831,0.229682,0.290689,-0.996124,0.076785,0.042451,-0.974364,0.220923,0.04239,-0.951445,-0.087893,0.2949,-0.998047,-0.06122,0.011963,-0.943999,0.221839,0.244148,-0.842341,0.279366,0.460829,-0.964812,0.156407,0.211219,-0.898068,0.192969,0.395215,-0.983123,0.181341,0.022767,-0.990722,0.134983,0.013947,-0.690451,0.33607,0.640522,-0.792901,0.233741,0.5627,-0.867763,0.262215,-0.422101,-0.840571,0.239051,-0.486068,-0.760002,0.342326,-0.552446,-0.823023,0.253395,-0.508286,-0.638295,0.410535,-0.651143,-0.738426,0.239967,-0.630177,-0.849666,0.017609,-0.526994,-0.753502,0.125126,-0.645405,-0.634907,0.254799,-0.729331,-0.883847,0.053377,-0.464705,-0.908597,0.105533,-0.404035,-0.903226,0.020569,-0.428663,-0.89465,0.011963,-0.446547,-0.82223,0.086459,-0.562517,-0.878842,0.057588,-0.473586,-0.96292,0.151097,-0.223365,-0.938536,0.237342,-0.250557,-0.964782,0.083773,-0.249275,-0.942473,0.139012,-0.303934,-0.952605,-0.042116,-0.301187,-0.467116,0.251411,-0.847682,-0.398144,0.39143,-0.829585,-0.244697,0.386212,-0.88934,-0.296091,0.252419,-0.921171,-0.363781,0.420301,-0.831233,-0.219031,0.444502,-0.868557,-0.333323,0.109226,-0.93643,-0.5244,0.139805,-0.8399,-0.349254,-0.0141,-0.936918,-0.561144,0.052919,-0.825983,-0.682272,0.129002,-0.719596,-0.608234,0.243416,-0.755455,-0.741447,0.076937,-0.666555,-0.523911,0.404767,-0.749413,-0.502365,0.365154,-0.783746,-0.285257,-0.190252,0.93936,-0.113346,-0.179662,0.977142,-0.111576,-0.133213,0.984771,-0.273415,-0.118412,0.954558,-0.131626,-0.073946,0.988525,-0.27897,-0.05591,0.958647,-0.001038,-0.168554,0.985687,-0.001495,-0.141667,0.989898,-0.002441,-0.083987,0.99646,-0.483596,-0.096316,0.869961,-0.467147,-0.177007,0.866268,-0.469649,-0.054842,0.88113,-0.388134,-0.235847,0.890896,-0.338389,-0.252754,0.9064,-0.310465,-0.210364,0.927,-0.367809,-0.256539,0.893796,-0.141942,-0.220038,0.965087,-0.00058,-0.195868,0.980621,-0.178625,-0.236579,0.955046,-0.000183,-0.219031,0.975707,0.473067,-0.113132,0.873714,0.254097,0.013459,0.967071,0.262154,0.016816,0.964873,0.508713,-0.091128,0.856075,0.281808,0.022858,0.959166,0.547777,-0.074282,0.833308,0.089267,0.07065,0.993469,0.054781,0.081088,0.995178,0.055422,0.087344,0.994629,0.737358,-0.223975,0.637257,0.68337,-0.270669,0.677999,0.865932,-0.316202,0.387494,0.814356,-0.373882,0.443861,0.782311,-0.193365,0.59209,0.898892,-0.276711,0.339702,0.55211,-0.368358,0.747978,0.361492,-0.162572,0.918058,0.255776,-0.530961,0.807825,0.074923,-0.249825,0.965361,0.690207,-0.493942,0.528764,0.443434,-0.671072,0.594134,0.187689,-0.011933,0.982147,0.094119,0.027772,0.995148,-0.006653,-0.090518,0.995849,0.607288,-0.711295,-0.353862,0.524277,-0.676962,-0.516526,0.33253,-0.768944,-0.545976,0.363842,-0.84872,-0.38374,0.015748,-0.818812,-0.573809,0.016663,-0.916776,-0.39903,0.444838,-0.603076,-0.662099,0.32371,-0.618488,-0.715995,0.013306,-0.621265,-0.783441,0.376385,-0.8905,-0.255531,0.641499,-0.734855,-0.220099,0.347911,-0.91406,-0.20835,0.618946,-0.767663,-0.166082,0.016938,-0.962066,-0.272195,0.016938,-0.974029,-0.225776,0.791375,-0.58095,-0.1901,0.742851,-0.580279,-0.33372,0.867,-0.46675,-0.174413,0.802454,-0.493667,-0.335093,0.784936,-0.607898,-0.119449,0.872951,-0.481277,-0.079226,0.618915,-0.583483,-0.525803,0.461623,-0.563097,-0.685385,0.64272,-0.522813,-0.559954,0.429518,-0.522233,-0.736717,0.26957,-0.266945,-0.92523,0.194952,-0.127995,-0.972411,0.231117,-0.107944,-0.966887,0.259896,-0.228156,-0.938292,0.005554,-0.102634,-0.99469,0.007202,-0.205878,-0.978545,0.295938,-0.404096,-0.865474,0.354717,-0.44734,-0.820978,0.009583,-0.361614,-0.932249,0.288491,-0.467696,-0.835475,0.14243,-0.308359,-0.940519,0.224555,-0.462874,-0.857479,0.057619,-0.343944,-0.937193,0.049593,-0.172308,-0.983764,-0.050783,-0.219825,-0.974212,0.431196,-0.902097,-0.016144,0.548906,-0.824732,-0.13599,0.293222,-0.937559,-0.186956,0.2154,-0.973479,-0.076754,0.016938,-0.978545,-0.205329,0.016785,-0.995178,-0.096408,0.102084,-0.982818,0.153691,0.234504,-0.944853,0.228584,-0.030732,-0.907529,0.418806,0.000855,-0.880551,0.473891,0.015839,-0.991577,0.128361,0.01352,-0.915342,0.402387,0.42027,-0.848811,0.32075,0.619007,-0.782189,0.070559,0.623829,-0.691671,0.363842,0.752312,-0.640278,0.154942,0.152654,-0.815119,0.558763,0.728202,-0.682028,-0.067171,0.833827,-0.551988,0.00531,-0.360179,0.132023,0.92346,-0.558763,0.146702,0.816218,-0.539445,0.169897,0.824671,-0.359081,0.147618,0.921537,-0.530076,0.187872,0.826868,-0.346751,0.165716,0.923185,-0.710196,0.164312,0.68453,-0.695639,0.184942,0.694143,-0.692343,0.197028,0.694113,-0.149113,0.119297,0.981567,-0.129337,0.106235,0.98587,-0.147313,0.132267,0.980193,-0.085849,0.070193,0.993805,-0.35905,0.114536,0.926237,-0.569964,0.139134,0.809778,-0.722373,0.153905,0.674123,-0.906857,0.180334,0.380871,-0.957244,0.183416,0.22364,-0.951994,0.193152,0.237403,-0.900082,0.193091,0.390545,-0.957793,0.158757,0.239509,-0.907926,0.176916,0.379864,-0.981597,0.188757,0.028443,-0.977722,0.199866,0.063906,-0.980255,0.161962,0.113285,-0.817621,0.191717,0.542863,-0.82754,0.174902,0.533433,-0.820338,0.193091,0.538255,-0.835566,0.158483,0.525986,-0.916227,0.157262,0.36848,-0.964782,0.159246,0.209296,-0.987884,0.154546,0.012024,-0.829951,0.190741,-0.524155,-0.846828,0.247993,-0.470443,-0.954588,0.227241,-0.192572,-0.939634,0.197607,-0.279305,-0.90997,0.245216,-0.3343,-0.977233,0.19834,-0.07532,-0.9447,0.124699,-0.303171,-0.861049,0.090091,-0.500412,-0.88702,0.110508,-0.448256,-0.84518,0.178259,-0.50383,-0.89346,0.100986,-0.437574,-0.85815,0.145451,-0.492325,-0.790155,0.246132,-0.561296,-0.796655,0.285653,-0.532609,-0.777917,0.225166,-0.586596,-0.715415,0.284433,-0.638142,-0.563311,-0.047334,-0.824854,-0.484512,0.024659,-0.874416,-0.677389,0.136937,-0.722739,-0.761376,0.062929,-0.645222,-0.410352,0.168126,-0.896268,-0.583941,0.230323,-0.778405,-0.769951,0.064089,-0.634816,-0.577654,-0.008515,-0.816218,-0.375225,-0.101382,-0.921354,-0.374248,-0.145665,-0.915799,-0.208411,-0.186956,-0.95999,-0.226386,-0.212653,-0.95053,-0.310739,-0.054201,-0.948943,-0.237861,0.113926,-0.964568,-0.170629,-0.098636,-0.980377,-0.062014,0.05884,-0.996338,0.848018,-0.371929,-0.377483,0.862697,-0.319498,-0.391919,0.71044,-0.324534,-0.624409,0.661275,-0.40492,-0.631428,0.506912,-0.281075,-0.814844,0.423841,-0.391247,-0.816858,0.872707,-0.281259,-0.39903,0.746818,-0.277627,-0.604236,0.578112,-0.228278,-0.783349,0.635639,-0.481826,-0.603107,0.828272,-0.43376,-0.354656,0.392712,-0.482009,-0.783197,0.907376,-0.384045,-0.17069,0.929502,-0.323771,-0.17655,0.921964,-0.384747,-0.044008,0.947905,-0.318369,-0.009308,0.939299,-0.291147,-0.181341,0.947172,-0.27195,-0.169897,0.956572,-0.28959,0.032655,0.957457,-0.275185,0.086673,0.061068,-0.311075,-0.948393,0.145817,-0.182775,-0.972259,-0.017487,-0.138524,-0.990173,-0.090274,-0.261605,-0.960936,0.234718,-0.084506,-0.968352,0.100528,-0.015412,-0.994812,-0.089267,-0.272713,-0.957915,0.033662,-0.359691,-0.932463,0.195349,-0.439436,-0.876736,0.232368,-0.357646,-0.904447,0.320292,-0.228217,-0.919401,0.397717,-0.154729,-0.904324,0.878201,-0.409558,0.246956,0.859798,-0.414045,0.298776,0.897427,-0.35432,0.262764,0.89819,-0.352519,0.262581,0.922208,-0.311075,0.229591,0.911679,-0.312845,0.266335,0.922666,-0.319956,0.215125,0.918882,-0.363506,0.153203,0.919309,-0.291757,0.263955,0.89285,-0.443037,0.080538,0.834956,-0.506394,0.215369,0.779199,-0.525895,0.340922,-0.948271,-0.127171,0.290811,-0.928037,-0.145054,0.343059,-0.837519,-0.128758,0.530961,-0.871242,-0.12772,0.473891,-0.888241,-0.208716,0.409162,-0.949644,-0.182287,0.254769,-0.964263,-0.259285,-0.053865,-0.974944,-0.219794,-0.033448,-0.978607,-0.201331,-0.042177,-0.641346,-0.761223,0.095676,-0.655232,-0.750816,0.083102,-0.708975,-0.699545,0.089236,-0.694388,-0.711173,0.109592,-0.75277,-0.650197,0.102634,-0.728355,-0.673971,0.123325,-0.682394,-0.716788,0.143193,-0.738182,-0.659719,0.140629,-0.786828,-0.59978,0.145299,-0.707602,-0.676046,0.205481,-0.655812,-0.729759,0.193152,-0.734611,-0.582476,0.347911,-0.697256,-0.631153,0.339732,-0.725303,-0.651173,0.223273,-0.733818,-0.571673,0.366924,-0.58153,-0.795984,0.167821,-0.574084,-0.814814,0.080294,-0.496628,-0.856746,0.13889,-0.489883,-0.868709,0.072726,-0.62035,-0.711325,0.330363,-0.528703,-0.793359,0.301675,-0.588,-0.80459,0.082766,-0.614673,-0.774194,0.150792,-0.501358,-0.860805,0.087374,-0.52617,-0.834223,0.16483,-0.268197,-0.95996,0.080508,-0.269143,-0.956969,0.108219,-0.394024,-0.913999,0.096408,-0.387494,-0.918851,0.074374,-0.281777,-0.936979,0.206397,-0.413709,-0.891385,0.185034,-0.396588,-0.90933,0.125736,-0.278817,-0.951842,0.127293,-0.42732,-0.861354,0.274606,-0.305612,-0.913205,0.269478,-0.140355,-0.981017,0.133671,-0.131718,-0.987396,0.087466,0.015809,-0.990387,0.137303,0.016053,-0.995697,0.091006,-0.157537,-0.947447,0.278359,0.01474,-0.958434,0.284829,-0.129551,-0.984405,0.118961,-0.135289,-0.965392,0.222877,0.01587,-0.992157,0.123753,0.015137,-0.973144,0.229621,-0.099216,-0.455336,-0.884732,-0.149846,-0.308359,-0.93936,-0.056642,-0.321909,-0.945067,-0.027009,-0.472701,-0.880795,0.009064,-0.326762,-0.945036,0.011322,-0.479568,-0.877407,-0.18778,-0.147801,-0.971007,-0.081362,-0.149876,-0.985321,0.006317,-0.15009,-0.988647,-0.001556,-0.543687,-0.839259,-0.054872,-0.536393,-0.842158,0.026643,-0.532823,-0.84579,-0.010804,-0.538865,-0.842311,0.012299,-0.547624,-0.836604,0.012055,-0.531358,-0.847041,-0.155217,-0.528398,-0.834651,-0.218879,-0.433668,-0.87405,-0.309885,-0.511399,-0.801508,-0.387585,-0.40965,-0.82577,-0.101444,-0.550188,-0.828852,-0.236824,-0.557939,-0.795343,-0.280282,-0.294107,-0.913724,-0.32017,-0.148503,-0.935636,-0.445662,-0.284433,-0.848781,-0.480331,-0.154668,-0.863338,-0.01822,0.350017,-0.936552,0.025147,0.2978,-0.954283,0.047792,0.294107,-0.954558,0.01825,0.348399,-0.937132,-0.001129,0.295267,-0.955382,-0.002106,0.350108,-0.936705,0.067415,0.212073,-0.974914,0.06122,0.136876,-0.988678,0.003021,0.051119,-0.998688,0.004242,0.354045,-0.935209,-0.039674,0.356792,-0.933317,0.002472,0.333781,-0.942625,-0.045259,0.339366,-0.939543,-0.002167,0.354076,-0.935179,-0.001801,0.33253,-0.943083,-0.133366,0.365734,-0.92111,-0.104404,0.361309,-0.926572,-0.270638,0.376019,-0.886196,-0.230689,0.376232,-0.897336,-0.144719,0.351848,-0.924772,-0.286843,0.36906,-0.883999,-0.05533,0.313211,-0.948057,-0.001679,0.226173,-0.974059,-0.173376,0.335459,-0.925932,-0.10886,0.248421,-0.962493,0.081729,0.344035,-0.935362,0.120548,0.308451,-0.943541,0.05002,0.38432,-0.921812,0.011048,0.413831,-0.910276,-0.071963,0.475692,-0.876644,-0.106632,0.503922,-0.857112,-0.026734,0.428358,-0.903195,0.0477,0.367779,-0.928678,-0.062227,0.421491,-0.90466,0.01822,0.371105,-0.928373,-0.144963,0.511368,-0.847011,-0.184606,0.492965,-0.850215,0.055208,0.333537,-0.941099,0.077609,0.299936,-0.950774,-0.001617,0.322062,-0.946684,-0.000916,0.28312,-0.959075,0.037629,0.344584,-0.937986,-0.001862,0.336589,-0.941618,0.105625,0.255379,-0.961028,-3.1e-05,0.2331,-0.972442,-0.698172,-0.354137,-0.622181,-0.561876,-0.381634,-0.733909,-0.495895,-0.474258,-0.727409,-0.656514,-0.427351,-0.62157,-0.414686,-0.536882,-0.734672,-0.602741,-0.482833,-0.635212,-0.767724,-0.389416,-0.508835,-0.79342,-0.331706,-0.5103,-0.839442,-0.362926,-0.404462,-0.860469,-0.310709,-0.403699,-0.742515,-0.428327,-0.514939,-0.824305,-0.394482,-0.406018,-0.819819,-0.256111,-0.512101,-0.730125,-0.267586,-0.628681,-0.84402,-0.165532,-0.510056,-0.756249,-0.166082,-0.632832,-0.883785,-0.241859,-0.400494,-0.905148,-0.161473,-0.3932,-0.606647,-0.276498,-0.745323,-0.63567,-0.162053,-0.754753,-0.557421,0.350169,-0.752739,-0.488113,0.321207,-0.811487,-0.315531,0.345592,-0.883724,-0.383404,0.376782,-0.843196,-0.412488,0.246345,-0.87701,-0.244423,0.262978,-0.933317,-0.431043,0.3773,-0.819636,-0.59331,0.367748,-0.715995,-0.443098,0.387097,-0.808557,-0.587878,0.406415,-0.699393,-0.737785,0.352886,-0.575396,-0.731284,0.302896,-0.611072,-0.845637,0.335521,-0.415082,-0.859859,0.253792,-0.442946,-0.711875,0.425855,-0.558428,-0.809961,0.435438,-0.392804,-0.694845,0.257363,-0.671499,-0.644704,0.186163,-0.741386,-0.856197,0.183843,-0.482772,-0.842769,0.109622,-0.526933,-0.31727,0.682241,-0.65865,-0.288369,0.649953,-0.703116,-0.370617,0.730003,-0.574175,-0.395154,0.759819,-0.516221,-0.443007,0.799005,-0.406568,-0.460158,0.820826,-0.338298,-0.435224,0.757805,-0.486099,-0.357097,0.683828,-0.63625,-0.493851,0.719108,-0.488845,-0.408979,0.649586,-0.640858,-0.499069,0.812037,-0.302408,-0.563433,0.768853,-0.302316,-0.26249,0.600116,-0.755577,-0.223731,0.596362,-0.770867,-0.307627,0.572161,-0.760216,-0.192511,0.565661,-0.801813,-0.980285,-0.051943,-0.190558,-0.962767,-0.045442,-0.266427,-0.955657,-0.092044,-0.279672,-0.973968,-0.092593,-0.206824,-0.943205,-0.155431,-0.293558,-0.963378,-0.148503,-0.223151,-0.927396,-0.03235,-0.372631,-0.919828,-0.088015,-0.382305,-0.982177,-0.088412,-0.165746,-0.987823,-0.052553,-0.146245,-0.984039,-0.073366,-0.162023,-0.989807,-0.043092,-0.135655,-0.972747,-0.138585,-0.185766,-0.974517,-0.118961,-0.19013,-0.991211,-0.025483,-0.129582,-0.983947,-0.021027,-0.177099,-0.993042,-0.001679,-0.117679,-0.985504,0.00412,-0.169408,-0.993225,-0.021943,-0.113834,-0.995422,0.00058,-0.095553,-0.966308,-0.00882,-0.25721,-0.929716,0.013703,-0.367992,-0.966582,0.022156,-0.255257,-0.963103,0.166631,-0.21131,-0.927702,0.213385,-0.306253,-0.91232,0.308725,-0.26896,-0.952208,0.263588,-0.154271,-0.875454,0.424055,-0.231758,-0.915006,0.390515,-0.100986,-0.98001,0.190954,-0.055483,-0.984802,0.106021,-0.137516,-0.994324,0.087436,0.060335,-0.997528,0.030824,-0.062685,-0.943876,0.329691,0.019166,-0.951262,0.205756,0.229621,-0.978423,0.042634,-0.202033,-0.960814,0.092593,-0.261177,-0.967986,-0.011841,-0.250679,-0.953764,0.028932,-0.299142,-0.989044,-0.013367,-0.146916,-0.973754,-0.074526,-0.214911,-0.930235,0.133824,-0.341655,-0.926237,0.0618,-0.371807,-0.515061,0.856838,0.022217,-0.509507,0.85992,-0.030183,-0.515946,0.844508,0.143468,-0.523789,0.834315,0.17185,-0.523209,0.795831,0.304758,-0.537919,0.783715,0.310495,-0.568957,0.802606,0.179144,-0.555895,0.830042,0.044649,-0.648671,0.742332,0.167608,-0.631031,0.774773,0.038697,-0.58684,0.750053,0.304941,-0.666768,0.686911,0.28901,-0.538438,0.834834,-0.114414,-0.500229,0.853114,-0.148045,-0.607776,0.785668,-0.115207,-0.490982,0.84402,-0.215644,-0.559771,0.627003,0.541734,-0.552599,0.543626,0.631703,-0.595386,0.491989,0.635151,-0.613025,0.580981,0.535356,-0.627216,0.423902,0.65334,-0.674947,0.513443,0.529862,-0.519639,0.467483,0.715079,-0.536607,0.418592,0.732658,-0.535691,0.342845,0.771661,-0.605426,0.672506,0.425611,-0.552995,0.710257,0.43553,-0.680837,0.606983,0.409864,-0.531419,0.72216,0.442732,-0.535539,0.63683,0.554613,-0.530137,0.548418,0.646657,-0.507157,0.465102,0.725547,-0.345408,0.342357,0.873745,-0.256569,0.292093,0.921323,-0.29899,0.245155,0.922208,-0.354411,0.292093,0.888272,-0.358501,0.182928,0.915403,-0.373333,0.215522,0.90231,-0.2331,0.262001,0.93646,-0.309854,0.214087,0.926359,-0.398633,0.158483,0.903287,-0.444349,0.353435,0.823176,-0.446791,0.402203,0.799097,-0.440168,0.269601,0.856471,-0.450636,0.396649,0.799738,-0.353343,0.348857,0.867977,-0.244453,0.317942,0.916044,-0.187506,0.30372,0.934111,-0.267647,0.224647,0.936949,-0.202612,0.199652,0.958678,-0.23075,0.146519,0.961882,-0.325083,0.167791,0.930662,-0.250038,0.095279,0.96353,-0.37904,0.110416,0.91876,-0.006958,0.192999,0.98117,-0.006226,0.144444,0.989471,-0.175146,0.099673,0.979461,-0.345012,0.190771,0.918973,-0.266427,0.244819,0.932218,-0.426344,0.135044,0.894406,-0.202033,0.297037,0.933226,-0.208716,0.280251,0.936949,-0.166356,0.252052,0.953276,-0.007721,0.240577,0.97058,-0.026429,0.340129,-0.93997,-0.006592,0.358287,-0.933561,-0.093814,0.398785,-0.912198,-0.120121,0.371624,-0.92056,-0.221625,0.45732,-0.861202,-0.253639,0.417829,-0.872372,-0.139134,0.351421,-0.92581,-0.040376,0.3296,-0.943236,-0.278359,0.383618,-0.88052,0.005676,0.318735,-0.947813,0.013153,0.323985,-0.945952,-0.001495,0.315714,-0.948851,-0.001556,0.319346,-0.94763,0.023865,0.337474,-0.941008,-0.00177,0.331431,-0.943449,-0.511551,0.530717,-0.67571,-0.463027,0.592608,-0.659047,-0.557695,0.655965,-0.50856,-0.615192,0.585772,-0.527604,-0.635823,0.701224,-0.322459,-0.700674,0.624378,-0.345195,-0.667165,0.508805,-0.544023,-0.555071,0.466445,-0.68865,-0.758995,0.536515,-0.368816,-0.425001,0.422681,-0.800409,-0.391644,0.472549,-0.789453,-0.352214,0.524277,-0.775262,-0.789361,0.613575,-0.019684,-0.715659,0.698294,0.01355,-0.736869,0.661184,0.14069,-0.81283,0.572893,0.105197,-0.752647,0.60329,0.26368,-0.823817,0.514267,0.23835,-0.880123,0.470077,0.066347,-0.855617,0.514389,-0.057192,-0.883938,0.408124,0.228187,-0.820002,0.537645,-0.196173,-0.756828,0.632527,-0.164617,-0.686789,0.713828,-0.136937,-0.733024,0.361888,0.575884,-0.718375,0.441328,0.537675,-0.632679,0.348643,0.691427,-0.6245,0.250343,0.739799,-0.525956,0.249947,0.812922,-0.526444,0.143376,0.838008,-0.624073,0.120029,0.772088,-0.733329,0.239387,0.636311,-0.643574,-0.029115,0.764824,-0.733512,0.049501,0.677816,-0.550798,0.033021,0.833979,-0.614246,-0.083926,0.784631,-0.83401,0.336436,0.437269,-0.803125,0.444899,0.396252,-0.85641,0.138462,0.49733,-0.753471,0.52678,0.393384,-0.474471,0.047334,0.878964,-0.407147,0.131596,0.903806,-0.43437,0.1095,0.89404,-0.520829,0.030274,0.853114,-0.485183,0.091525,0.869564,-0.562944,0.014985,0.82635,-0.606861,-0.054201,0.792932,-0.566912,-0.039277,0.82281,-0.6751,-0.149144,0.722465,-0.657399,-0.134831,0.741356,-0.62328,-0.068941,0.778924,-0.658467,-0.168004,0.733573,-0.537156,-0.01529,0.843318,-0.469344,0.078646,0.879482,-0.628895,-0.116855,0.768639,-0.443892,0.173376,0.879116,-0.461623,-0.035371,0.886349,-0.426344,0.045442,0.903378,-0.265328,0.023255,0.963866,-0.278451,-0.079134,0.957152,-0.004242,0.023408,0.999695,-0.002197,-0.098788,0.995086,-0.285348,-0.191839,0.938994,-0.478408,-0.13477,0.867702,-0.275399,-0.320719,0.906247,-0.472488,-0.261849,0.841517,-0.000244,-0.218635,0.975799,0.001953,-0.344493,0.938749,-0.585009,-0.091372,0.805841,-0.552904,-0.003723,0.833216,-0.594073,-0.204566,0.777947,-0.499008,0.071047,0.863643,-0.893704,-0.347118,-0.284188,-0.868099,-0.363598,-0.33784,-0.877255,-0.321329,-0.356548,-0.901395,-0.308206,-0.304086,-0.889737,-0.255776,-0.378033,-0.912687,-0.247353,-0.325266,-0.814631,-0.388714,-0.430372,-0.815851,-0.348491,-0.461379,-0.817591,-0.28074,-0.502701,-0.912259,-0.298807,-0.280129,-0.905393,-0.334422,-0.261452,-0.908505,-0.30018,-0.290597,-0.901517,-0.332102,-0.277413,-0.922605,-0.242134,-0.300211,-0.919431,-0.246803,-0.30604,-0.905118,-0.346171,-0.246803,-0.892697,-0.361309,-0.269295,-0.912809,-0.332774,-0.236641,-0.899777,-0.350261,-0.260109,-0.901822,-0.339518,-0.267251,-0.910947,-0.320719,-0.259438,-0.86578,-0.380139,-0.325327,-0.816523,-0.403363,-0.412976,-0.872127,-0.371105,-0.318827,-0.217353,0.09241,-0.971679,-0.222968,0.196356,-0.954833,-0.106571,0.203833,-0.973174,-0.101901,0.097934,-0.989929,0.000366,0.207282,-0.978271,0.002197,0.100742,-0.994903,-0.094668,-0.012024,-0.995422,-0.20719,-0.015748,-0.978149,0.004059,-0.009888,-0.999939,-0.340434,-0.023194,-0.93997,-0.350841,0.082125,-0.932798,-0.498642,-0.036378,-0.866024,-0.506943,0.064089,-0.859554,-0.355205,0.182592,-0.916745,-0.507614,0.158696,-0.846828,-0.779962,0.012055,-0.62569,-0.659352,0.039003,-0.750786,-0.652791,-0.052889,-0.755669,-0.773247,-0.068239,-0.63039,-0.860347,-0.08005,-0.503311,-0.867702,-0.012513,-0.496902,-0.867763,0.046358,-0.494766,-0.777947,0.08478,-0.622547,-0.657094,0.12421,-0.743461,-0.209754,0.974273,-0.082247,-0.340281,0.936918,-0.079745,-0.340434,0.939573,0.035493,-0.211859,0.976684,0.034272,-0.485916,0.870815,-0.074282,-0.483474,0.874569,0.036653,-0.10361,0.994049,0.033204,-0.10126,0.991363,-0.083071,-0.016602,0.999298,0.033052,-0.016114,0.996399,-0.082949,-0.100681,0.976104,-0.192419,-0.209449,0.958953,-0.191046,-0.101169,0.951994,-0.288827,-0.20951,0.935087,-0.285806,-0.015442,0.981201,-0.192236,-0.014649,0.957213,-0.288949,-0.34022,0.921812,-0.185644,-0.485885,0.856594,-0.173528,-0.338389,0.899594,-0.275979,-0.481521,0.838221,-0.255867,-0.740867,0.669454,-0.053529,-0.838832,0.542772,-0.041536,-0.834498,0.549272,0.043001,-0.736686,0.674917,0.041536,-0.917631,0.396069,-0.032075,-0.913114,0.405683,0.039918,-0.620228,0.783441,0.038881,-0.624195,0.778527,-0.065035,-0.623737,0.766167,-0.154515,-0.740135,0.659444,-0.131626,-0.617542,0.753349,-0.225929,-0.733726,0.652089,-0.19071,-0.838557,0.534074,-0.107273,-0.918546,0.386059,-0.084719,-0.833796,0.53029,-0.153478,-0.916471,0.382061,-0.118503,-0.988403,0.147069,-0.037538,-0.989074,0.12946,-0.070101,-0.995666,0.054476,-0.075137,-0.996307,0.071413,-0.047487,-0.996857,0.013733,-0.077761,-0.998291,0.023316,-0.053041,-0.989319,0.117649,-0.08594,-0.995178,0.041902,-0.088382,-0.996185,0.087069,-0.002991,-0.986328,0.16422,0.013642,-0.99353,0.097598,0.057833,-0.981017,0.175085,0.083071,-0.999359,0.031678,-0.015107,-0.998657,0.036744,0.036348,-0.962767,0.268593,0.02942,-0.966582,0.254494,-0.030396,-0.955077,0.275826,0.108066,-0.968017,0.240547,-0.071169,-0.967925,0.232765,-0.094394,-0.900601,-0.206153,0.38255,-0.91345,-0.179632,0.365123,-0.911069,-0.169744,0.375652,-0.897671,-0.187841,0.398541,-0.910489,-0.147465,0.386303,-0.897,-0.158361,0.41261,-0.942412,-0.13361,0.306497,-0.942106,-0.134159,0.30726,-0.941465,-0.124363,0.313211,-0.898495,-0.19837,0.391552,-0.900662,-0.223365,0.372662,-0.912168,-0.202765,0.35609,-0.912564,-0.231269,0.337199,-0.898099,-0.164586,0.407758,-0.912503,-0.168249,0.372814,-0.905454,-0.239357,0.350414,-0.907071,-0.212317,0.363445,-0.913053,-0.247078,0.324412,-0.917508,-0.207617,0.339183,-0.914182,-0.25367,0.316019,-0.917051,-0.270333,0.293039,-0.918271,-0.177435,0.353893,-0.941404,-0.128086,0.31193,-0.927763,-0.163701,0.335276,-0.939451,-0.128025,0.317759,-0.890652,0.030122,0.453627,-0.904569,0.020325,0.425764,-0.90347,0.060915,0.424207,-0.887753,0.07767,0.453688,-0.904386,0.106632,0.413099,-0.885525,0.133702,0.444868,-0.935636,-0.015259,0.35258,-0.937437,0.014374,0.347819,-0.941588,0.047639,0.333293,-0.890103,0.07651,0.449232,-0.893155,0.025056,0.448988,-0.909207,0.064241,0.411298,-0.910794,0.012146,0.41261,-0.885891,0.140263,0.442152,-0.904569,0.131596,0.405469,-0.895413,-0.014985,0.44496,-0.893521,-0.009095,0.448897,-0.896908,-0.04413,0.439985,-0.895627,-0.039796,0.442976,-0.91113,-0.026673,0.411237,-0.911252,-0.053682,0.408277,-0.906491,-0.01471,0.421888,-0.935423,-0.040773,0.351115,-0.908292,-0.043672,0.415998,-0.936216,-0.06122,0.345958,-0.979308,-0.183905,0.084109,-0.952269,-0.280648,0.119877,-0.97351,-0.225654,0.036378,-0.987335,-0.156774,-0.023316,-0.979034,-0.202551,-0.020142,-0.983398,-0.159032,-0.08713,-0.933653,-0.32551,0.149358,-0.959227,-0.265999,0.095309,-0.970977,-0.233589,0.050966,-0.993164,-0.078982,-0.08591,-0.997497,-0.067537,0.01944,-0.982452,-0.114628,-0.146977,-0.983032,-0.05887,0.17365,-0.94406,-0.232246,0.234046,-0.931211,-0.065554,0.35847,-0.872616,-0.288766,0.393841,-0.903928,-0.361827,0.227943,-0.885617,-0.413221,0.211829,-0.833033,-0.43968,0.335704,-0.820948,-0.501755,0.272469,-0.740196,-0.360393,0.567583,-0.719352,-0.242225,0.65099,-0.708762,-0.261483,0.655171,-0.707511,-0.398602,0.583544,-0.663839,-0.298532,0.68569,-0.634754,-0.467788,0.614978,-0.673055,-0.560198,0.482864,-0.732078,-0.491287,0.471847,-0.581622,-0.650899,0.487838,-0.748924,-0.462996,0.474044,-0.746269,-0.346202,0.568468,-0.745628,-0.463088,0.479141,-0.739952,-0.343455,0.578326,-0.707389,-0.229194,0.668599,-0.691061,-0.212348,0.690878,-0.395337,-0.627918,0.67037,-0.442305,-0.429121,0.78753,-0.248817,-0.48381,0.839045,-0.215155,-0.677908,0.702933,0.004883,-0.501267,0.865261,0.008606,-0.692099,0.721732,-0.183386,-0.848659,0.496078,-0.346599,-0.806055,0.479659,0.012238,-0.861446,0.507645,-0.475845,-0.737205,0.479629,-0.530839,-0.550951,0.64391,-0.575457,-0.359233,0.734672,-0.996277,-0.058107,0.063417,-0.993591,-0.112674,-0.005188,-0.988769,-0.148473,-0.016602,-0.994049,-0.093783,0.055239,-0.976959,-0.211798,-0.025269,-0.986694,-0.156865,0.042177,-0.987152,-0.156468,-0.031739,-0.980682,-0.189459,-0.048311,-0.967956,-0.24546,-0.052797,-0.987304,-0.032868,0.155187,-0.986755,-0.002106,0.162023,-0.962676,0.027558,0.269143,-0.959319,0.049226,0.277871,-0.986358,-0.088137,0.138951,-0.967772,-0.01355,0.25135,-0.986908,0.00882,0.160894,-0.996582,-0.045045,0.069185,-0.988342,0.001862,0.152226,-0.995788,-0.051363,0.075747,-0.959014,0.056093,0.277718,-0.962157,0.054933,0.266793,-0.995056,-0.098636,0.010285,-0.990173,-0.139592,-0.004578,-0.994385,-0.100711,0.031587,-0.990844,-0.131199,0.031587,-0.924589,-0.313669,0.216071,-0.925932,-0.329417,0.184606,-0.886898,-0.408216,0.216102,-0.897458,-0.378124,0.227088,-0.832392,-0.498917,0.241157,-0.858394,-0.460097,0.226691,-0.910062,-0.340831,0.235755,-0.926359,-0.29252,0.237159,-0.919767,-0.305002,0.246895,-0.928373,-0.271889,0.253273,-0.886624,-0.405408,0.22248,-0.907468,-0.350169,0.232063,-0.937315,-0.261025,0.230781,-0.941282,-0.272927,0.198645,-0.945463,-0.241707,0.218268,-0.951994,-0.250099,0.176427,-0.934904,-0.248482,0.253365,-0.94351,-0.219642,0.247963,-0.948485,-0.277871,0.152043,-0.961303,-0.248268,0.119236,-0.981048,-0.184606,0.058748,-0.991913,-0.116642,0.049745,-0.991485,-0.076479,0.105136,-0.983062,-0.145665,0.111026,-0.986663,-0.049501,0.154912,-0.980773,-0.108798,0.16184,-0.996368,-0.056734,0.063326,-0.994079,-0.018281,0.107028,-0.989654,0.000732,0.143254,-0.969726,-0.209082,0.125919,-0.966277,-0.242012,0.087771,-0.955718,-0.253029,0.150121,-0.952391,-0.273934,0.133732,-0.971587,-0.166417,0.168187,-0.960845,-0.213446,0.176519,-0.963073,-0.262276,0.060396,-0.974273,-0.224799,0.014527,-0.962859,-0.265175,0.050386,-0.964995,-0.26194,-0.011658,-0.953612,-0.271523,0.129765,-0.960448,-0.240791,0.139775,-0.985137,-0.171575,-0.001373,-0.992798,-0.117985,0.01941,-0.97055,-0.237953,-0.037172,-0.97943,-0.200995,-0.016266,-0.96118,0.168126,0.218635,-0.978057,0.099063,0.183142,-0.965453,0.09592,0.242195,-0.948485,0.155339,0.27604,-0.945891,0.089419,0.311869,-0.932279,0.137608,0.334483,-0.989105,0.038057,0.142155,-0.980071,0.042848,0.193884,-0.923307,0.234169,0.304361,-0.933927,0.255287,0.25013,-0.910428,0.209418,0.35667,-0.944548,0.270211,0.186468,-0.972106,0.175359,0.155614,-0.987243,0.100497,0.123356,-0.995056,0.037782,0.091617,-0.458632,0.626362,-0.630268,-0.429334,0.650929,-0.626057,-0.511307,0.727134,-0.458052,-0.563311,0.683981,-0.463424,-0.627857,0.737571,-0.248451,-0.680502,0.682089,-0.267617,-0.645009,0.564562,-0.514939,-0.685873,0.635212,-0.355022,-0.762352,0.629505,-0.150029,-0.743156,0.535203,-0.401502,-0.647908,0.512894,-0.563128,-0.909177,0.307474,-0.28074,-0.857082,0.310953,-0.410688,-0.827418,0.510544,-0.233802,-0.946806,0.276437,-0.164586,-0.555071,0.443678,-0.703574,-0.377544,0.523453,-0.763817,-0.479843,0.327006,-0.814112,-0.323649,0.379589,-0.866665,-0.793908,0.278848,-0.540269,-0.730247,0.202246,-0.652547,-0.382611,0.53209,-0.755272,-0.636769,0.447707,-0.627705,-0.361217,0.376598,-0.853023,-0.644032,0.299875,-0.703757,-0.294839,-0.462081,-0.83636,-0.316996,-0.502823,-0.804132,-0.347148,-0.51204,-0.785668,-0.335185,-0.46852,-0.817377,-0.577258,-0.490249,-0.652974,-0.583972,-0.451949,-0.674306,-0.351451,-0.518876,-0.779229,-0.37141,-0.533219,-0.760063,-0.581286,-0.507462,-0.636036,-0.333506,-0.398267,-0.854457,-0.282998,-0.39259,-0.875057,-0.338908,-0.292276,-0.894253,-0.278603,-0.28544,-0.91699,-0.599841,-0.388714,-0.699301,-0.621509,-0.293039,-0.726493,-0.38377,-0.394482,-0.834895,-0.396161,-0.45613,-0.796838,-0.580126,-0.392529,-0.713675,-0.586444,-0.437574,-0.68157,-0.381756,-0.292917,-0.876583,-0.588214,-0.307535,-0.747917,-0.420454,-0.486251,-0.765984,-0.457228,-0.488327,-0.743248,-0.606494,-0.449629,-0.65569,-0.637898,-0.431654,-0.637745,-0.754204,-0.656392,-0.016511,-0.817255,-0.565081,0.112827,-0.804224,-0.561632,0.19425,-0.74752,-0.661977,0.054079,-0.829798,-0.484207,0.277352,-0.800989,-0.579089,0.151708,-0.876461,-0.450301,0.170324,-0.86053,-0.439436,0.257576,-0.863369,-0.378582,0.333506,-0.685232,-0.713797,-0.144597,-0.687613,-0.697958,-0.199927,-0.611774,-0.700491,-0.367412,-0.613361,-0.677206,-0.406354,-0.768242,-0.63918,-0.034242,-0.723472,-0.642323,-0.252907,-0.757103,-0.616352,-0.216468,-0.811823,-0.580401,-0.063509,-0.852962,-0.479354,-0.206488,-0.888852,-0.448775,-0.092196,-0.695975,-0.599017,-0.395917,-0.811548,-0.470565,-0.346233,-0.862697,-0.504013,0.041475,-0.906522,-0.41258,0.089206,-0.919279,-0.393262,-0.014191,-0.941374,-0.336131,0.027985,-0.99411,0.027314,0.10474,-0.994659,0.030946,0.0983,-0.994598,0.013611,0.102786,-0.994903,-0.002411,0.100681,-0.991791,-0.028871,0.124332,-0.992981,-0.058321,0.102847,-0.991119,0.019623,0.131352,-0.988311,0.007263,0.152196,-0.981048,-0.02649,0.1919,-0.991119,-0.035646,0.127873,-0.990844,0.0065,0.134678,-0.982269,-0.078494,0.17011,-0.983978,-0.02945,0.175787,-0.988189,-0.101657,0.114536,-0.978332,-0.145878,0.146825,-0.990356,0.028504,0.135411,-0.992889,0.040284,0.111911,-0.991211,0.031953,0.128147,-0.992218,0.041719,0.117069,-0.985992,-0.002136,0.166723,-0.993805,0.035951,0.104953,-0.991791,0.022828,0.125584,-0.992706,0.035981,0.115055,-0.991455,0.024506,0.127873,-0.970794,-0.239631,0.009705,-0.980804,-0.189245,0.046358,-0.986908,-0.141636,0.076846,-0.980773,-0.19071,0.040803,-0.991363,-0.082186,0.101962,-0.9906,-0.117801,0.06943,-0.985229,-0.144383,0.091739,-0.988586,-0.102268,0.110538,-0.990448,-0.057039,0.125309,-0.971343,-0.237617,0.002289,-0.959319,-0.281472,-0.021088,-0.961089,-0.273476,-0.038331,-0.951048,-0.305155,-0.048433,-0.986847,-0.158788,0.029389,-0.956603,-0.288675,-0.039186,-0.964873,-0.262093,-0.017243,-0.964721,-0.258248,-0.051057,-0.966338,-0.254982,-0.033692,-0.954009,-0.295236,-0.051363,-0.9747,-0.222633,0.019196,-0.980377,-0.181982,0.075503,-0.971282,-0.23777,0.004547,-0.974792,-0.211921,0.069369,-0.869839,0.489486,0.061373,-0.848994,0.515275,0.116764,-0.913205,0.345866,0.215339,-0.925596,0.34489,0.155919,-0.946806,0.179449,0.267006,-0.957579,0.196997,0.210181,-0.911954,0.366161,0.185003,-0.945433,0.198798,0.258065,-0.955687,0.052675,0.28956,-0.965606,0.241951,0.095035,-0.936735,0.349376,0.020997,-0.994324,0.096744,0.044008,-0.986419,0.164068,-0.004303,-0.981048,0.132847,0.140873,-0.996521,0.029878,0.077609,-0.891537,0.444349,-0.08768,-0.787194,0.611377,-0.080782,-0.971282,0.22663,-0.07239,-0.75045,0.659688,-0.039888,-0.84817,0.52739,0.049287,-0.96408,-0.236885,-0.120029,-0.97528,-0.188177,-0.115726,-0.977386,-0.205054,-0.051576,-0.971221,-0.230293,-0.060762,-0.974975,-0.221534,0.017487,-0.973174,-0.230018,-0.002045,-0.955962,-0.289315,-0.04883,-0.94116,-0.319407,-0.110233,-0.96408,-0.265572,-0.001953,-0.921232,-0.338603,-0.191412,-0.95294,-0.240944,-0.183843,-0.896542,-0.334239,-0.290597,-0.937864,-0.233589,-0.256569,-0.969115,-0.173498,-0.175207,-0.958281,-0.158574,-0.237648,-0.898129,-0.234474,-0.37199,-0.880856,-0.258736,-0.396344,-0.914243,-0.215705,-0.342906,-0.926664,-0.180334,-0.329783,-0.929746,-0.190039,-0.315317,-0.940031,-0.143742,-0.309305,-0.863796,-0.302866,-0.4026,-0.901761,-0.266396,-0.340342,-0.942198,-0.158666,-0.294992,-0.917905,-0.2266,-0.325632,-0.954161,-0.104678,-0.280313,-0.866848,-0.318766,-0.383282,-0.838343,-0.312723,-0.446455,-0.764,-0.439406,-0.472396,-0.718375,-0.416211,-0.55736,-0.812372,-0.324229,-0.484664,-0.787011,-0.359203,-0.501511,-0.676199,-0.412091,-0.610614,-0.901151,-0.167913,-0.399609,-0.927274,-0.092868,-0.362651,-0.952391,-0.141911,-0.269814,-0.932432,-0.204108,-0.298074,-0.960601,-0.175085,-0.215705,-0.943846,-0.2313,-0.235817,-0.950499,-0.029206,-0.309305,-0.968963,-0.084689,-0.232124,-0.911435,-0.263741,-0.315683,-0.874783,-0.243812,-0.418653,-0.892758,-0.31373,-0.323313,-0.851802,-0.310739,-0.421674,-0.926328,-0.281655,-0.250099,-0.789361,-0.220069,-0.573077,-0.821253,-0.11829,-0.558123,-0.608051,-0.188635,-0.771142,-0.637196,-0.052797,-0.768883,-0.763726,-0.310862,-0.565722,-0.856685,-0.018738,-0.515458,-0.893521,0.062349,-0.444624,-0.677053,0.084811,-0.730979,-0.999207,-0.006287,-0.039033,-0.999664,-0.023499,-0.01062,-0.999786,-0.006439,-0.01822,-0.998718,-0.003418,-0.050081,-0.998932,-0.040651,0.021424,-0.999664,-0.010956,0.022889,-0.997253,-0.001831,-0.073794,-0.997772,0.009522,-0.065889,-0.995605,-0.003204,-0.093509,-0.995239,0.022492,-0.09476,-0.995575,0.070711,-0.061525,-0.999084,0.035554,-0.022706,-0.988678,0.100589,-0.11127,-0.999939,-0.001221,0.008942,-0.998627,-0.037019,0.036805,-0.963317,-0.241401,0.117161,-0.958159,-0.230323,0.169836,-0.96408,-0.225806,0.139775,-0.964507,-0.24781,0.090823,-0.971282,-0.209113,0.113437,-0.963775,-0.253822,0.081729,-0.958373,-0.193457,0.209876,-0.967071,-0.17716,0.182623,-0.96585,-0.255531,0.042421,-0.969787,-0.236854,0.058046,-0.954924,-0.291787,0.054048,-0.9682,-0.234687,0.086398,-0.959227,-0.240577,0.148289,-0.952269,-0.232673,0.197455,-0.951506,-0.204443,0.229743,-0.962706,-0.226539,0.147832,-0.983642,-0.135258,0.11887,-0.980865,-0.100253,0.166692,-0.955565,-0.196966,0.219245,-0.965026,-0.087924,0.246864,-0.937712,-0.172918,0.301248,-0.913907,-0.314035,0.257057,-0.926786,-0.333811,0.172033,-0.901822,-0.27311,0.334758,-0.93997,-0.326273,0.099857,-0.963347,-0.248634,0.100375,-0.978881,-0.176397,0.10303,-0.93112,0.166234,0.324595,-0.937956,0.152776,0.311258,-0.920927,0.171331,0.350017,-0.917539,0.183782,0.35258,-0.898984,0.185705,0.396619,-0.899747,0.194983,0.390362,-0.946837,0.102634,0.304849,-0.928556,0.116733,0.352306,-0.91995,0.148015,0.362987,-0.928587,0.136326,0.345134,-0.92111,0.0553,0.385327,-0.925016,0.054445,0.375927,-0.907224,0.15418,0.391278,-0.913633,0.054811,0.402753,-0.933653,0.117679,0.338206,-0.941191,0.139439,0.307657,-0.936949,0.083743,0.339183,-0.948637,0.100467,0.299875,-0.926267,0.054231,0.372875,-0.927854,0.053529,0.36903,-0.950896,0.127903,0.281777,-0.960143,0.0871,0.26545,-0.960295,0.095157,0.262185,-0.969359,0.068056,0.236,-0.944212,-0.119785,0.306711,-0.95407,-0.124912,0.272225,-0.951537,-0.152959,0.266732,-0.943388,-0.147404,0.297037,-0.948576,-0.181463,0.259316,-0.941923,-0.176885,0.285379,-0.960906,-0.123447,0.24781,-0.956145,-0.152715,0.249825,-0.951048,-0.182043,0.249641,-0.930601,-0.139866,0.338176,-0.930387,-0.111454,0.349162,-0.912625,-0.133305,0.386364,-0.912259,-0.103,0.396374,-0.930113,-0.171819,0.324534,-0.930082,-0.085971,0.357097,-0.945006,-0.093356,0.313425,-0.930357,-0.063051,0.361156,-0.94641,-0.06766,0.315775,-0.91174,-0.076632,0.403516,-0.956542,-0.09653,0.275063,-0.965331,-0.093356,0.243721,-0.959319,-0.067171,0.274117,-0.969359,-0.061617,0.237648,-0.976775,-0.049654,0.20838,-0.987915,-0.052004,0.14594,-0.986969,0.032044,0.157628,-0.972076,0.061586,0.226325,-0.992981,-0.079745,0.087313,-0.995178,-0.030519,0.092929,-0.959746,0.033753,0.278726,-0.962889,-0.081393,0.25721,-0.9541,-0.06296,0.292703,-0.950346,-0.147862,0.273751,-0.962798,-0.164556,0.214209,-0.976989,-0.131932,0.167425,-0.963775,-0.215247,0.157353,-0.976959,-0.182287,0.110843,-0.948271,-0.206885,0.24073,-0.949919,-0.241951,0.197699,-0.986816,-0.11478,0.113804,-0.99118,-0.114719,0.06595,-0.986145,-0.151952,0.066439,-0.995697,-0.063295,0.067629,-0.994812,-0.017609,0.10007,-0.997772,-0.0159,0.064608,-0.997223,-0.055208,0.049409,-0.996155,-0.067965,0.054781,-0.994201,-0.090396,0.058107,-0.993469,-0.102176,0.050295,-0.995056,-0.062319,0.07712,-0.991241,-0.011689,0.131443,-0.93054,-0.270699,-0.246498,-0.91345,-0.318125,-0.2537,-0.926542,-0.299142,-0.227973,-0.942869,-0.252358,-0.217353,-0.959044,-0.197058,-0.203406,-0.948088,-0.212378,-0.236579,-0.925199,-0.226691,-0.304239,-0.905087,-0.289834,-0.311075,-0.886196,-0.339457,-0.315195,0.060884,-0.398602,-0.915098,0.044252,-0.267129,-0.962615,0.048463,-0.218604,-0.974578,0.07474,-0.30018,-0.950926,0.051698,-0.169164,-0.984222,0.083529,-0.194708,-0.977264,0.025605,-0.331645,-0.943022,-0.001587,-0.433149,-0.901303,-0.0712,-0.374584,-0.924436,-0.1171,-0.477035,-0.871029,0.041414,-0.215766,-0.975555,-0.042207,-0.247566,-0.967925,-0.046205,-0.511399,-0.858089,0.033753,-0.483718,-0.874538,-0.174902,-0.5421,-0.821894,0.043489,-0.419172,-0.906857,0.009278,-0.340648,-0.940123,-0.472945,-0.49968,-0.725669,-0.272347,-0.508927,-0.816584,-0.216803,-0.418867,-0.881771,-0.411725,-0.436933,-0.799707,-0.174383,-0.288736,-0.941374,-0.361339,-0.32255,-0.874844,-0.653005,-0.403974,-0.640584,-0.685324,-0.443709,-0.57741,-0.621021,-0.320353,-0.715293,-0.715506,-0.448012,-0.535997,-0.539323,-0.511185,-0.669149,-0.337107,-0.549181,-0.764641,-0.897061,-0.09476,0.431593,-0.898129,-0.097201,0.428816,-0.89819,-0.128727,0.42027,-0.897061,-0.125065,0.423811,-0.910306,-0.120182,0.396008,-0.910031,-0.094302,0.403638,-0.940367,-0.1095,0.32197,-0.938963,-0.094302,0.33076,-0.909421,-0.069124,0.410016,-0.896725,-0.066713,0.437483,-0.937498,-0.078249,0.339,-0.897763,-0.069308,0.434919,-0.89407,0.174932,0.412275,-0.887967,0.19425,0.416791,-0.883236,0.189367,0.428968,-0.886471,0.17481,0.428449,-0.908811,0.137852,0.393719,-0.91757,0.132817,0.374676,-0.947905,0.067751,0.311228,-0.954405,0.057497,0.292856,-0.927732,0.091006,0.36195,-0.906888,0.129063,0.401074,-0.937223,0.003479,0.348674,-0.919034,0.028138,0.393078,-0.957366,0.023072,0.287912,-0.955046,-0.017945,0.295846,-0.89938,0.149876,0.410627,-0.910092,0.047182,0.411664,-0.963408,-0.267953,-0.000183,-0.974212,-0.209143,0.084323,-0.981567,-0.153264,0.113987,-0.974212,-0.224342,0.022675,-0.972442,-0.130192,0.193304,-0.971862,-0.069033,0.225166,-0.961028,-0.274117,-0.035707,-0.952269,-0.301431,-0.047884,-0.95584,-0.289346,-0.051393,-0.959624,-0.280862,-0.015046,-0.966979,-0.247627,0.059969,-0.968322,-0.189581,0.162481,-0.968444,-0.105991,0.22544,-0.962615,-0.139744,0.232032,-0.968841,-0.137364,0.205969,-0.975097,-0.091403,0.20191,-0.956816,-0.172887,0.233589,-0.961608,-0.18009,0.207007,-0.98056,-0.044984,0.190863,-0.975433,-0.069582,0.208991,-0.971221,-0.116703,0.207495,-0.962584,-0.144017,0.229438,-0.955992,-0.166265,0.241676,-0.950072,-0.187078,0.249672,-0.282205,0.024537,-0.959014,-0.350169,0.01355,-0.936552,-0.353557,0.195746,-0.91467,-0.294382,0.205267,-0.933348,-0.651418,-0.024506,-0.758293,-0.651936,0.135746,-0.745994,-0.430006,0.172613,-0.886135,-0.402203,0.004456,-0.915525,-0.387921,-0.155248,-0.908505,-0.278481,-0.142735,-0.949767,-0.345561,-0.151952,-0.925993,-0.640614,-0.168889,-0.749046,-0.461562,-0.567186,-0.682058,-0.533708,-0.619129,-0.576037,-0.532731,-0.64449,-0.548418,-0.464644,-0.590808,-0.659536,-0.670186,-0.601093,-0.435286,-0.626514,-0.555925,-0.54622,-0.410535,-0.552965,-0.725028,-0.399915,-0.533158,-0.745506,-0.597308,-0.523148,-0.607868,-0.505631,-0.488235,-0.711295,-0.563372,-0.509964,-0.650014,-0.627857,-0.550676,-0.550035,-0.994201,0.002472,0.107364,-0.992309,0.008301,0.123356,-0.992065,0.028596,0.122196,-0.992767,0.030427,0.11594,-0.990905,0.00763,0.134312,-0.990997,0.021455,0.131993,-0.993591,0.017212,0.111515,-0.996033,-0.017182,0.087252,-0.995422,-0.075289,0.058626,-0.99472,-0.045808,0.091769,-0.992706,-0.027528,0.117222,-0.990966,-0.017884,0.132847,-0.974548,-0.224128,0.001404,-0.968078,-0.246406,0.045473,-0.969848,-0.243294,0.012329,-0.971099,-0.236854,-0.029054,-0.961577,-0.251961,0.108829,-0.968596,-0.235359,0.079928,-0.974792,-0.21778,-0.048006,-0.98114,-0.191778,-0.023988,-0.984863,-0.172552,0.016083,-0.976623,-0.208686,0.050874,-0.966033,-0.239418,0.096927,-0.954955,-0.255623,0.15067,-0.83694,-0.365032,-0.407697,-0.880551,-0.342448,-0.327555,-0.888699,-0.31605,-0.332102,-0.847072,-0.346995,-0.402539,-0.764153,-0.393811,-0.510819,-0.750206,-0.399548,-0.526811,-0.749443,-0.373455,-0.546617,-0.837947,-0.353984,-0.415326,-0.881344,-0.342418,-0.325419,-0.981994,0.029664,-0.186438,-0.990509,0.030946,-0.133854,-0.993042,-0.007996,-0.117344,-0.988372,-0.019105,-0.150731,-0.980804,-0.042573,-0.190252,-0.968902,0.012146,-0.247139,-0.928343,0.111087,-0.354686,-0.956633,0.127628,-0.261727,-0.97647,0.121738,-0.177953,-0.951079,-0.002869,0.308847,-0.933164,0.000885,0.359386,-0.933042,0.050874,0.356059,-0.951384,0.043092,0.30488,-0.964721,0.03824,0.260414,-0.96466,-0.001709,0.263375,-0.973846,0.033235,0.224738,-0.974395,0.00116,0.224769,-0.96234,-0.036012,0.269356,-0.948759,-0.038911,0.313517,-0.972594,-0.029786,0.230445,-0.931669,-0.036348,0.361461,-0.93704,-0.230781,0.262032,-0.927366,-0.23191,0.293558,-0.928861,-0.203803,0.309305,-0.939695,-0.205664,0.273232,-0.94525,-0.208045,0.251381,-0.941984,-0.229896,0.244514,-0.946074,-0.208899,0.247475,-0.942045,-0.229865,0.244301,-0.938688,-0.24781,0.239631,-0.934049,-0.252663,0.252297,-0.935179,-0.263161,0.236946,-0.930967,-0.271523,0.244026,-0.938963,-0.245582,0.240822,-0.936857,-0.256478,0.237617,-0.925993,-0.255959,0.277474,-0.924802,-0.276009,0.261788,-0.993988,0.109165,0.006653,-0.998077,0.056673,-0.023896,-0.981231,-0.066439,0.180883,-0.980102,-0.047365,0.192694,-0.999481,0.004364,-0.0318,-0.980132,-0.079165,0.181677,-0.974303,-0.033662,0.222571,-0.987274,0.141301,0.072726,-0.962096,-0.050295,0.267922,-0.97824,0.11951,0.169439,-0.908292,0.393933,-0.140721,-0.904019,0.341075,-0.257637,-0.928465,0.370861,0.019593,-0.912137,0.249428,-0.325236,-0.922452,0.143254,-0.35847,-0.996155,-0.085208,-0.019135,-0.99237,-0.122562,-0.0112,-0.973235,-0.095096,0.209143,-0.975524,-0.092135,0.1995,-0.987396,-0.157903,-0.006897,-0.971526,-0.097079,0.216041,-0.977996,-0.086825,0.18952,-0.998688,-0.043092,-0.027528,-0.928495,0.035005,-0.36961,-0.927091,-0.06592,-0.368908,-0.918332,-0.156926,-0.363292,-0.903073,-0.239174,-0.356639,-0.979156,-0.202582,-0.013672,-0.977447,-0.209479,-0.026215,-0.975677,-0.072726,0.206702,-0.972472,-0.086428,0.216346,-0.977142,-0.207312,-0.046541,-0.980743,-0.054415,0.187536,-0.971068,-0.094974,0.21897,-0.982482,-0.186071,-0.007294,-0.885037,-0.305307,-0.351329,-0.869259,-0.349406,-0.349681,-0.857479,-0.3755,-0.351665,-0.850429,-0.385266,-0.358135,-0.968505,-0.239937,-0.066134,-0.959319,-0.28132,-0.02295,-0.981658,-0.102451,0.160619,-0.986633,-0.065767,0.14893,-0.945189,-0.314585,0.08713,-0.965941,-0.137028,0.219367,-0.985473,-0.047273,0.163091,-0.974853,-0.212531,-0.066683,-0.846065,-0.396496,-0.356273,-0.845271,-0.425977,-0.322489,-0.852382,-0.465987,-0.23719,-0.864437,-0.496414,-0.079226,-0.904019,-0.27076,0.33079,-0.892636,-0.225501,0.39024,-0.907956,-0.0889,0.409436,-0.920011,-0.113834,0.37495,-0.892666,-0.185003,0.410901,-0.907376,-0.070864,0.414228,-0.941252,-0.137394,0.308481,-0.923704,-0.308786,0.226691,-0.868343,-0.483993,0.108249,-0.866237,-0.429426,0.255287,-0.866878,-0.357952,0.346934,-0.877132,-0.286599,0.385296,-0.909879,-0.106967,0.40083,-0.923765,-0.061495,0.377941,-0.919706,-0.017151,0.392224,-0.915067,-0.040468,0.401257,-0.941099,-0.017823,0.337565,-0.929777,0.014069,0.367779,-0.911618,-0.057192,0.407025,-0.899869,-0.14771,0.410352,-0.895779,-0.217689,0.387494,-0.917844,-0.148808,0.367931,-0.940703,-0.084231,0.328562,-0.960295,-0.032441,0.277047,-0.969878,0.025452,0.242134,-0.978118,0.029908,0.205817,-0.968963,0.05884,0.239967,-0.957823,0.057497,0.281533,-0.983062,0.028047,0.181097,-0.976623,0.046297,0.209876,-0.944273,0.042085,0.326395,-0.95761,0.011383,0.287729,-0.973815,-0.001404,0.227271,-0.98178,0.012024,0.189581,-0.986023,0.016968,0.165624,-0.988037,0.019807,0.152776,-0.986145,0.004669,0.165746,-0.985565,-0.017182,0.168279,-0.980193,-0.022828,0.196692,-0.981292,0.003327,0.192389,-0.983734,-0.045839,0.17365,-0.977386,-0.053041,0.204627,-0.980407,0.026429,0.195105,-0.985443,0.020112,0.168767,-0.988891,0.017762,0.147404,-0.989227,0.005982,0.146245,-0.989044,-0.014893,0.146733,-0.988128,-0.045503,0.146611,-0.976135,-0.11124,0.186438,-0.970397,-0.144963,0.19306,-0.96176,-0.147496,0.230659,-0.967895,-0.116367,0.222755,-0.963042,-0.179754,0.200446,-0.954833,-0.179357,0.236763,-0.973144,-0.085116,0.213752,-0.980529,-0.07831,0.179968,-0.986114,-0.081545,0.144505,-0.98291,-0.117954,0.14127,-0.978088,-0.154363,0.139531,-0.971374,-0.189673,0.142827,-0.94644,-0.237739,0.21836,-0.939665,-0.254524,0.228431,-0.937712,-0.248878,0.242286,-0.942015,-0.232032,0.242378,-0.935392,-0.262215,0.237098,-0.935148,-0.260506,0.239967,-0.947844,-0.20893,0.240608,-0.954619,-0.212256,0.208777,-0.962951,-0.221412,0.153844,-0.953764,-0.24546,0.173223,-0.945494,-0.258065,0.198431,-0.939848,-0.257241,0.224616,-0.932401,-0.26014,0.250801,-0.933287,-0.246895,0.260689,-0.926237,-0.278268,0.254189,-0.929655,-0.277474,0.242256,-0.937193,-0.215094,0.274545,-0.923765,-0.264595,0.276803,-0.932859,-0.269997,0.238411,-0.933287,-0.263649,0.243751,-0.937895,-0.24366,0.246895,-0.939512,-0.217902,0.264138,-0.944548,-0.17658,0.276742,-0.952788,-0.111179,0.28251,-0.955443,-0.068697,0.287027,-0.967009,0.034791,0.252297,-0.946654,-0.106204,0.304209,-0.933988,-0.171209,0.313578,-0.925687,-0.228126,0.301645,-0.944884,-0.155766,0.287942,-0.96054,-0.0141,0.277749,-0.961882,0.115177,0.247963,-0.951018,0.260384,0.166448,-0.977813,0.122135,-0.170171,-0.984893,0.044404,-0.167272,-0.993103,0.037996,-0.110813,-0.987152,0.115177,-0.110508,-0.964538,0.233345,-0.123142,-0.953124,0.239784,-0.184393,-0.90997,0.384899,-0.154118,-0.896359,0.386517,-0.217017,-0.926847,0.254189,-0.276254,-0.953001,0.144444,-0.266243,-0.873745,0.381817,-0.301218,-0.963439,0.066164,-0.259621,-0.704367,0.633259,-0.320627,-0.589496,0.715812,-0.374218,-0.575701,0.672109,-0.465621,-0.689016,0.598071,-0.409284,-0.460585,0.781762,-0.42027,-0.450606,0.72982,-0.514054,-0.790887,0.501785,-0.3502,-0.808283,0.525498,-0.265511,-0.82345,0.531358,-0.198798,-0.721061,0.647114,-0.247475,-0.605213,0.739799,-0.293893,-0.472274,0.815851,-0.333598,-0.207251,0.857753,-0.470351,-0.102481,0.872616,-0.477493,-0.10303,0.810022,-0.577258,-0.206153,0.796319,-0.56859,-0.012604,0.877773,-0.478835,-0.01117,0.815088,-0.579211,-0.322214,0.771233,-0.548936,-0.327494,0.829341,-0.452681,-0.33375,0.870754,-0.361064,-0.208716,0.903043,-0.375408,-0.101901,0.919095,-0.380596,-0.013764,0.924314,-0.381329,-0.934202,-0.104984,-0.340892,-0.923765,-0.175939,-0.340098,-0.901517,-0.176672,-0.394971,-0.911283,-0.09415,-0.40083,-0.818445,-0.190405,-0.5421,-0.820917,-0.086795,-0.564379,-0.919462,-0.012818,-0.392895,-0.944578,-0.035585,-0.326304,-0.828883,0.018494,-0.559069,-0.955321,-0.064608,-0.288369,-0.942747,-0.120121,-0.311106,-0.932585,-0.178564,-0.313578,-0.323099,-0.021455,-0.946104,-0.328257,-0.175787,-0.928068,-0.151616,-0.140934,-0.978332,-0.154485,0.004486,-0.987976,-0.030335,-0.114078,-0.992981,-0.035615,0.013794,-0.999268,-0.186102,0.142186,-0.972167,-0.352702,0.125278,-0.927305,-0.060457,0.13596,-0.988861,-0.602344,0.075533,-0.794641,-0.584979,-0.061403,-0.808679,-0.59447,-0.20011,-0.778771,0.095126,0.012543,-0.995361,0.090976,-0.090518,-0.991699,0.055635,-0.120731,-0.991119,0.058901,-0.072085,-0.995636,0.058016,-0.022248,-0.998047,0.088961,0.115299,-0.989319,0.032563,0.123478,-0.991791,0.047609,0.014161,-0.998749,0.048891,-0.098544,-0.993927,-0.744926,-0.343699,0.571734,-0.791894,-0.330851,0.51323,-0.829127,-0.116031,0.546861,-0.73217,-0.157201,0.662679,-0.689871,-0.187597,0.699149,-0.733543,-0.343699,0.586291,-0.741264,-0.475234,0.473952,-0.747459,-0.486679,0.452101,-0.725516,-0.577136,0.374798,-0.734458,-0.578112,0.355358,-0.774804,-0.481246,0.409894,-0.766594,-0.557085,0.319254,-0.774041,-0.606037,0.183142,-0.82403,-0.54329,0.160588,-0.857082,-0.476485,0.195746,-0.814234,-0.540696,0.211219,-0.863491,-0.469955,0.182897,-0.889523,-0.406201,0.209143,-0.778283,-0.573748,0.254952,-0.737449,-0.623371,0.259865,-0.722892,-0.644276,0.24958,-0.74752,-0.647206,0.149236,-0.789666,-0.600269,0.126774,-0.829127,-0.535661,0.159856,0.185827,-0.053285,-0.981109,0.102115,-0.036897,-0.994079,0.099857,-0.102237,-0.989715,0.135899,-0.108768,-0.98471,-0.019807,-0.035463,-0.999146,0.003082,-0.113865,-0.993469,0.11594,-0.139195,-0.983428,0.17658,-0.081912,-0.980865,0.006531,-0.163793,-0.98645,0.005432,-0.09476,-0.995453,0.214759,0.037141,-0.975951,0.221442,0.032533,-0.974609,0.196844,0.129704,-0.971801,0.195196,0.087863,-0.976806,0.003204,0.040834,-0.999146,0.001373,0.148839,-0.98883,0.104434,0.024232,-0.994232,-0.033601,0.018006,-0.999268,0.075106,0.049532,-0.995941,-0.053468,0.028382,-0.998138,-0.652058,-0.10596,0.750694,-0.794397,-0.138737,0.591296,-0.800378,-0.138157,0.583331,-0.658406,-0.108493,0.744774,-0.504959,-0.080477,0.859371,-0.492325,-0.074557,0.867183,-0.48384,-0.037111,0.874355,-0.651936,-0.075533,0.754479,-0.788385,-0.124058,0.602496,-0.887204,-0.274483,-0.3708,-0.928709,-0.286935,-0.23484,-0.899991,-0.380413,-0.212775,-0.877773,-0.352672,-0.324137,-0.848445,-0.244087,-0.469588,-0.846217,-0.185919,-0.499283,-0.88992,-0.155889,-0.428571,-0.8876,-0.214087,-0.407788,-0.904904,-0.227454,-0.359691,-0.884426,0.021393,0.466109,-0.875301,-0.012146,0.483383,-0.890378,-0.091586,0.445875,-0.89642,-0.06827,0.43788,-0.949126,-0.033967,0.312998,-0.940397,0.054384,0.335612,-0.936338,0.125706,0.327738,-0.866298,0.108676,0.487503,-0.947111,0.134831,0.291177,-0.860653,0.138432,0.489975,-0.843226,0.072726,0.532548,-0.810419,0.112094,0.574999,-0.331645,-0.015564,-0.943266,-0.499405,0.025575,-0.865963,-0.561327,-0.053072,-0.825861,-0.363811,-0.104312,-0.925596,-0.156957,-0.122806,-0.979919,-0.163915,-0.034577,-0.985839,-0.166753,0.017548,-0.985839,-0.311472,0.028901,-0.949797,-0.171392,0.02707,-0.984802,-0.303812,0.039003,-0.951903,-0.464705,0.053133,-0.883847,-0.453261,0.056948,-0.889523,-0.016663,-0.093356,0.995483,0.042787,-0.021821,0.99884,0.116214,0.003754,0.993194,0.065401,-0.114322,0.991272,-0.003204,0.03415,0.99939,-0.000244,-0.134953,0.990844,-0.020173,0.076052,0.996887,0.050874,0.138066,0.989105,-0.006592,0.192175,0.981323,-0.019318,-0.141881,0.989685,-0.087558,-0.114292,0.989563,-0.00119,-0.15714,0.987548,-0.21482,-0.089816,0.972503,-0.178106,-0.077364,0.980956,-0.153081,-0.016114,0.988067,-0.16953,0.035096,0.984893,0.048341,0.121799,-0.991363,0.040956,0.158849,-0.98645,0.035096,0.277169,-0.960143,0.05652,0.290628,-0.955138,0.000946,0.174963,-0.984558,-0.00058,0.264351,-0.964415,0.021027,0.29957,-0.953825,-0.013428,0.079379,-0.996734,-0.092257,0.287057,-0.953429,-0.105472,0.044404,-0.993408,0.018433,0.015778,-0.999695,0.113804,0.068117,-0.99115,-0.083316,-0.008911,-0.99646,0.123356,0.14008,-0.982421,0.000946,0.175115,-0.984527,-0.750908,-0.095462,0.653432,-0.748466,-0.060274,0.66039,-0.746086,-0.113346,0.656087,-0.743889,-0.119663,0.657491,-0.761895,-0.03061,0.646962,-0.771966,-0.110446,0.625935,-0.730827,-0.110721,0.673482,-0.716941,-0.109989,0.688375,-0.62212,-0.086032,0.778161,-0.558214,-0.087191,0.825068,-0.694052,-0.064516,0.717002,-0.762261,-0.049409,0.645344,-0.477615,-0.037873,0.877712,-0.765191,-0.005707,0.643757,-0.766594,0.043794,0.640584,-0.883816,0.00705,0.467727,-0.872951,0.098453,0.477737,-0.962188,0.09415,0.255562,-0.96942,0.027863,0.243782,-0.99646,0.079196,-0.027406,-0.998627,0.043275,-0.029237,-0.963073,-0.054781,0.263527,-0.880032,-0.091403,0.466018,-0.999695,-0.0206,-0.012574,-0.813288,-0.107822,0.571764,-0.806757,-0.012726,0.590686,-0.799676,0.080538,0.594958,-0.880337,0.023682,-0.473708,-0.8811,0.071993,-0.46736,-0.76632,0.066164,-0.639027,-0.766259,-0.016785,-0.642293,-0.614917,0.048494,-0.787072,-0.627766,-0.057894,-0.776208,-0.753258,-0.108005,-0.648762,-0.864803,-0.075442,-0.496353,-0.643513,-0.053316,-0.763573,-0.959624,-0.026002,-0.280007,-0.960967,0.045442,-0.272866,-0.959136,0.072024,-0.273568,-0.327067,-0.044588,-0.943937,-0.303262,0.002197,-0.952879,-0.180364,-0.011078,-0.98352,-0.20191,0.00351,-0.979369,-0.224158,0.239082,-0.944731,-0.368542,0.151952,-0.917081,-0.522019,0.049379,-0.851497,-0.476852,-0.068484,-0.876278,-0.451918,0.024201,-0.89172,-0.216224,0.065462,0.97412,-0.345195,-0.015931,0.938383,-0.427564,-0.037416,0.903195,-0.269417,0.002838,0.963012,-0.138371,0.012482,0.990295,-0.112796,0.109928,0.987487,-0.004028,0.014649,0.999878,-0.005707,0.122929,0.99237,-0.049715,0.182348,0.981964,-0.120792,0.114322,0.986053,-0.00705,0.211951,0.977233,-0.242164,0.029664,0.969756,-0.638234,-0.121433,0.760155,-0.705008,-0.051027,0.707327,-0.556017,-0.02237,0.830836,-0.491195,-0.084231,0.866939,-0.449293,-0.011383,0.893307,-0.368816,-0.053377,0.927946,-0.718284,-0.007965,0.695669,-0.597583,0.026917,0.801324,-0.555345,0.023621,0.831263,-0.36079,-0.16187,0.918485,-0.43202,-0.227699,0.872616,-0.094913,-0.245216,0.964782,-0.040681,-0.317881,0.947233,-0.253487,-0.109775,0.961058,-0.041963,-0.184362,0.981933,-0.373974,-0.281899,0.883541,-0.700888,-0.149876,0.697317,-0.282266,-0.235633,0.929929,-0.652516,-0.161504,0.740318,0.083163,-0.336924,0.937834,0.144658,-0.280313,0.948912,-0.805078,-0.063021,0.58977,-0.817835,-0.020539,0.57506,-0.84579,-0.065249,0.529496,-0.870113,-0.024445,0.492203,-0.687307,0.033021,0.725578,-0.712577,0.104099,0.693808,-0.641072,0.064882,0.764702,-0.702231,0.016236,0.711753,-0.803552,0.096896,0.587237,-0.682241,0.061037,0.728538,-0.775353,-6.1e-05,0.631489,-0.711325,0.004273,0.702811,-0.81872,-0.018983,0.57384,-0.721763,-0.018067,0.691885,-0.720237,-0.006378,0.693686,-0.763878,0.067873,0.641743,-0.911191,0.046693,0.409284,-0.94232,0.1919,0.274209,-0.698477,-0.038026,0.714591,-0.856288,-0.058718,0.513108,-0.849452,0.16419,0.50145,-0.91818,0.146916,0.36787,-0.959868,0.231483,0.158147,-0.974578,0.210578,0.076296,-0.86877,0.336314,-0.363414,-0.894803,0.336161,-0.293741,-0.944639,0.288003,-0.157018,-0.947996,0.272958,-0.163579,-0.919523,0.288095,-0.267251,-0.950835,0.262001,-0.164953,-0.978118,0.150212,-0.143773,-0.875454,0.201331,-0.439314,-0.999146,-0.027161,-0.03058,-0.908353,-0.010132,-0.418012,-0.820826,0.260323,-0.508347,-0.806024,0.407819,-0.428907,-0.778283,0.312357,-0.544664,-0.736167,0.459761,-0.496628,-0.853298,0.001556,-0.521378,-0.827204,0.03296,-0.560869,-0.849666,0.385784,-0.359416,-0.890255,0.311502,-0.332224,-0.770409,0.434462,-0.466536,-0.819575,0.347942,-0.455184,-0.560991,0.473128,-0.67925,-0.524766,0.462569,-0.71456,-0.658589,0.464187,-0.592242,-0.656636,0.48265,-0.579516,-0.533464,0.395886,-0.747429,-0.696616,0.381115,-0.607776,-0.731895,0.337504,-0.591937,-0.661641,0.332286,-0.67214,-0.799676,0.069277,-0.596393,-0.736564,0.095309,-0.669576,-0.511551,0.304697,-0.803369,-0.417493,0.43263,-0.799036,-0.211432,0.255165,-0.94348,-0.179571,0.369457,-0.91171,-0.564043,0.109317,-0.818445,-0.206671,0.109745,-0.972228,-0.363659,0.433821,-0.824335,-0.350291,0.391675,-0.850795,-0.152593,0.390576,-0.907804,-0.145909,0.374859,-0.915494,0.644459,-0.251228,0.72216,0.364605,-0.312601,0.877102,0.253975,-0.289651,0.922788,0.541032,-0.271279,0.796014,0.241707,-0.248695,0.937925,0.504837,-0.270119,0.819819,0.714805,-0.229011,0.660726,0.788202,-0.178198,0.589007,0.820765,-0.185919,0.540117,0.856685,-0.119358,0.501816,0.687521,-0.251869,0.681051,0.800592,-0.212836,0.560076,0.880245,-0.139592,0.453444,0.759331,-0.227027,0.60976,0.938322,-0.137089,0.317331,0.835261,-0.211371,0.507553,0.911924,-0.067263,0.404797,0.951811,-0.085299,0.294473,0.492294,-0.305826,0.814905,0.563524,-0.277718,0.777978,0.877346,0.224281,-0.424146,0.978057,0.131657,-0.161321,0.989166,0.099796,-0.107547,0.907987,0.196692,-0.369884,0.994446,0.072878,-0.075594,0.927061,0.156529,-0.340648,0.997009,0.036744,0.067843,0.994629,0.016816,0.101871,0.991302,0.003815,0.131352,0.734367,0.281014,-0.617817,0.69866,0.293039,-0.652669,0.535569,0.332987,-0.776055,0.529069,0.326823,-0.783074,0.763024,0.241096,-0.599689,0.549333,0.303629,-0.778466,0.664388,0.23954,-0.707907,0.843104,0.179632,-0.506821,0.646474,0.090457,-0.75753,0.813654,0.027589,-0.580676,0.535417,0.254402,-0.805353,0.547929,0.102603,-0.830164,0.966247,0.09714,-0.238502,0.99939,0.012696,0.032228,0.95407,-0.057009,-0.294015,0.993347,-0.105472,0.045625,0.311502,0.296487,-0.902799,0.252998,0.344188,-0.904141,0.071352,0.354381,-0.932371,0.100986,0.309854,-0.945372,0.213782,0.347331,-0.913022,0.049593,0.357341,-0.932646,0.139164,0.205725,-0.968627,0.36848,0.201117,-0.90759,0.174322,0.097171,-0.979858,0.397778,0.089633,-0.913053,0.47084,0.230171,-0.851619,0.427229,0.319193,-0.845882,0.49617,0.092807,-0.863216,0.387494,0.348582,-0.853389,0.365825,0.335429,-0.868099,0.943297,-0.057497,0.326853,0.906308,-0.082492,0.414411,0.890774,-0.141057,0.431959,0.935789,-0.095065,0.339427,0.873501,-0.163091,0.458632,0.923032,-0.109043,0.368877,0.968932,-0.046266,0.242897,0.973479,-0.026673,0.227149,0.961241,-0.054079,0.270302,0.971007,-0.034944,0.236457,0.936857,-0.036897,0.347728,0.947691,-0.09595,0.304331,0.911985,-0.057192,0.406171,0.915586,-0.033204,0.400708,0.922269,-0.047853,0.383496,-0.819697,0.186987,-0.541398,-0.812189,0.195379,-0.549669,-0.829066,0.220283,-0.513901,-0.844172,0.218146,-0.489669,-0.866634,0.280557,-0.412549,-0.882168,0.270699,-0.385296,-0.812677,0.189306,-0.551042,-0.819697,0.203711,-0.535325,-0.842586,0.279763,-0.460158,-0.862819,0.203742,-0.462569,-0.833613,0.167638,-0.526261,-0.885525,0.17951,-0.428419,-0.853816,0.138371,-0.501816,-0.899167,0.25309,-0.356945,-0.919523,0.226753,-0.320902,-0.818964,0.151952,-0.553331,-0.815455,0.181555,-0.549577,-0.823206,0.162145,-0.544053,-0.831935,0.200293,-0.51738,-0.829341,0.110691,-0.547624,-0.816309,0.10538,-0.567888,-0.817011,0.198218,-0.541429,-0.823115,0.198553,-0.531999,-0.840754,0.217811,-0.495621,-0.848964,0.212012,-0.484024,-0.78634,0.24134,-0.568651,-0.65392,0.243934,-0.716117,-0.673605,0.243233,-0.697867,-0.804315,0.246498,-0.540635,-0.676321,0.201422,-0.708518,-0.812616,0.214454,-0.541856,-0.397534,0.22721,-0.889004,-0.413709,0.220923,-0.883175,-0.407941,0.172979,-0.896451,-0.848994,0.237587,-0.471908,-0.835078,0.225806,-0.501633,-0.858119,0.218055,-0.464766,-0.815577,0.184362,-0.548448,-0.756584,0.200751,-0.622272,-0.789178,0.114353,-0.603381,-0.709006,0.124058,-0.694174,-0.614032,0.206641,-0.761711,-0.35612,0.196356,-0.913541,-0.544908,0.130406,-0.828272,-0.282937,0.129063,-0.950407,0.996948,0.059084,-0.050722,0.998932,0.045869,-0.002777,0.954405,0.107822,-0.278329,0.946379,0.122532,-0.298898,0.745628,0.154607,-0.648152,0.751854,0.172735,-0.63628,0.999237,0.001495,0.038881,0.963378,0.051149,-0.263131,0.747551,0.086612,-0.658528,0.938749,0.099765,-0.329814,0.993561,0.041017,-0.105319,0.926603,0.041505,-0.37373,0.986358,-0.007996,-0.164403,0.76162,0.148808,-0.630665,0.764855,0.08713,-0.638234,0.999329,-0.013367,0.033509,0.996155,-0.000702,0.087405,0.991638,-0.054476,0.116764,0.986724,-0.048677,0.15479,0.998566,-0.050691,-0.016449,0.992828,-0.078951,0.089541,0.98999,-0.01352,0.140446,0.980407,-0.05005,0.190405,0.977844,-0.063509,0.199377,0.964019,-0.097446,0.24723,0.169897,0.190497,-0.966857,0.150273,0.172185,-0.97351,-0.109622,0.189184,-0.975768,-0.095767,0.201849,-0.9747,0.152226,0.111423,-0.982025,-0.101382,0.136235,-0.985443,-0.058687,0.179785,-0.981933,0.208655,0.17307,-0.962523,0.001404,0.124912,-0.992157,0.258583,0.122379,-0.95819,0.480697,0.169469,-0.860317,0.445967,0.188421,-0.874966,0.514389,0.114078,-0.84991,0.423414,0.167516,-0.890286,0.419019,0.099246,-0.902524,0.960479,-0.083132,0.265572,0.93585,-0.056337,0.347758,0.943602,-0.07358,0.322764,0.956023,-0.106632,0.273202,0.946654,-0.119785,0.299051,0.945891,-0.148137,0.288675,0.877621,0.011322,0.479202,0.904172,0.01883,0.426679,0.940397,0.008698,0.339885,0.967559,-0.097629,0.232887,0.975799,-0.078402,0.203955,0.953673,-0.133549,0.269539,0.979705,-0.076418,0.185247,0.960997,-0.076754,0.265664,0.979705,-0.089053,0.179449,0.958373,-0.081729,0.273537,0.928404,-0.054689,0.367504,0.855983,-0.004669,0.516953,0.920164,-0.059877,0.386853,0.836756,-0.021699,0.547105,-0.469588,0.083193,0.878933,-0.490585,0.090518,0.866665,-0.290201,0.059938,0.955077,-0.257546,0.064272,0.96411,-0.081454,0.025605,0.996338,-0.043611,0.042909,0.998108,-0.485549,0.072451,0.871181,-0.290506,0.034852,0.956206,-0.086886,0.000122,0.996216,-0.185888,0.020112,0.98233,-0.425916,0.03534,0.90405,-0.121372,-0.101779,0.987365,-0.397107,-0.067263,0.915281,0.046205,0.010315,0.998871,0.148106,-0.116245,0.982086,-0.641102,0.053194,0.765587,-0.663656,0.098544,0.741478,-0.804407,0.064974,0.590472,-0.820856,0.105502,0.561266,-0.62682,-0.024659,0.778741,-0.788202,0.005249,0.615345,-0.676046,0.115574,0.727714,-0.671712,0.109958,0.732566,-0.832209,0.129276,0.539109,-0.834437,0.138493,0.533372,-0.97409,0.078341,0.212043,-0.979949,0.107669,0.167486,-0.932585,0.125675,0.338298,-0.922025,0.098239,0.374401,-0.981903,0.136937,0.130741,-0.937712,0.147282,0.314554,-0.907102,0.062044,0.416242,-0.964232,0.045412,0.261116,-0.88937,0.013581,0.456954,-0.951567,0.007233,0.30726,-0.993683,0.028901,0.108371,-0.996033,0.052675,0.071413,-0.996124,0.036988,-0.079653,-0.997375,0.031739,-0.064699,-0.993591,0.018494,0.11127,-0.984222,0.069582,-0.162664,-0.996277,0.082308,0.025513,-0.993194,0.114963,-0.017548,-0.993805,0.054292,-0.096652,-0.986267,0.086337,-0.140629,-0.939177,0.036805,-0.34138,-0.948485,0.006623,-0.316721,-0.977783,0.028016,-0.207617,-0.978271,0.026246,-0.205542,-0.939329,0.023408,-0.342204,-0.967864,0.05472,-0.245399,-0.957183,0.07419,-0.279763,-0.891079,0.128086,-0.435377,-0.89526,0.1442,-0.421522,-0.815332,0.200629,-0.543077,-0.827082,0.169195,-0.535966,-0.87878,0.051881,-0.474349,-0.751671,0.178655,-0.634846,-0.772118,0.056734,-0.632893,-0.79812,0.216529,-0.562212,-0.781365,0.202338,-0.59032,-0.896603,-0.009796,-0.442671,-0.89285,-0.005524,-0.45027,-0.791498,-0.024506,-0.610645,-0.803705,-0.032197,-0.594104,-0.358745,0.022004,-0.933164,-0.590075,0.048036,-0.805902,-0.628559,0.162481,-0.760552,-0.475387,0.113987,-0.872341,-0.738975,0.155309,-0.655568,-0.64568,0.077425,-0.759636,-0.299875,0.03238,-0.953398,-0.142796,-0.021729,-0.989502,-0.092868,-0.058687,-0.993927,0.031678,-0.077334,-0.99649,-0.454848,-0.025727,-0.890164,-0.186102,-0.121952,-0.974914,-0.083285,-0.089602,-0.992462,-0.335002,-0.063051,-0.940092,-0.112918,-0.094089,-0.989135,-0.379528,-0.078433,-0.921812,0.095035,-0.11597,-0.988678,0.083224,-0.104465,-0.991028,-0.597797,-0.0412,-0.800562,-0.634022,-0.057161,-0.771172,0.358989,-0.023988,0.933012,0.539048,-0.080813,0.838374,0.506516,-0.030122,0.86169,0.384075,-0.005615,0.923276,0.547655,-0.076479,0.833186,0.475478,-0.098483,0.874172,0.694327,-0.149144,0.704001,0.639393,-0.086123,0.764,0.608936,-0.066622,0.790399,0.240425,0.003418,0.970641,0.164312,0.015748,0.986267,0.350963,-0.112796,0.929563,0.140721,-0.015687,0.989898,0.368786,-0.066378,0.927122,0.138859,-0.035371,0.989654,0.379742,-0.074984,0.922025,0.573168,-0.121891,0.810297,0.731407,-0.174657,0.659139,0.595569,-0.115696,0.794916,0.756157,-0.152318,0.636372,0.916837,-0.263192,-0.30015,0.918577,-0.261605,-0.296243,0.827693,-0.24897,-0.502884,0.835505,-0.250801,-0.488845,0.684896,-0.226478,-0.692526,0.702963,-0.236793,-0.670614,0.927274,-0.229743,-0.295541,0.836451,-0.214423,-0.504288,0.691275,-0.188177,-0.697623,0.844966,-0.259957,-0.46733,0.911039,-0.268868,-0.312479,0.834101,-0.326731,-0.44438,0.891903,-0.320597,-0.318857,0.735435,-0.250862,-0.629414,0.736595,-0.318278,-0.596698,0.950407,-0.280801,-0.133488,0.955748,-0.274545,-0.105533,0.954863,-0.291543,0.056429,0.956206,-0.280099,0.08475,0.934874,-0.317515,-0.158635,0.950774,-0.306742,0.043611,0.959502,-0.265786,-0.093142,0.967803,-0.233772,-0.09302,0.960143,-0.263009,0.094485,0.96939,-0.228248,0.090365,0.355724,-0.179479,-0.917173,0.190374,-0.133091,-0.972625,0.121189,-0.138218,-0.98294,0.34785,-0.198706,-0.916227,0.086917,-0.192846,-0.977355,0.352062,-0.244392,-0.90347,0.561998,-0.233345,-0.793512,0.530839,-0.213721,-0.820063,0.573199,-0.286172,-0.767785,0.518967,-0.197821,-0.831568,0.366344,-0.169378,-0.914914,0.522263,-0.157537,-0.838069,0.367443,-0.132603,-0.92053,0.228889,-0.141942,-0.963042,0.225898,-0.115177,-0.967284,0.87991,-0.259194,0.398144,0.927427,-0.277444,0.250771,0.925382,-0.285226,0.24955,0.863277,-0.242439,0.44264,0.920591,-0.267495,0.284494,0.829402,-0.187506,0.526231,0.765252,-0.165929,0.621967,0.807245,-0.214698,0.54973,0.70922,-0.107028,0.696768,0.833033,-0.214789,0.509781,0.893094,-0.239418,0.380779,0.853542,-0.180151,0.488845,0.908963,-0.200507,0.365398,0.933836,-0.254189,0.251656,0.945982,-0.216407,0.241371,-0.369579,0.547746,0.750572,-0.378124,0.64272,0.66625,-0.175665,0.635914,0.751457,-0.176061,0.525101,0.832606,0.051363,0.594592,0.802362,0.031281,0.475875,0.878933,-0.385601,0.703482,0.596973,-0.18302,0.710898,0.679037,0.051973,0.684378,0.727256,-0.186834,0.383587,0.904386,-0.367199,0.419416,0.830195,-0.207099,0.226905,0.951628,-0.3773,0.269295,0.886044,-0.002167,0.335154,0.942137,-0.037812,0.18775,0.981475,-0.563036,0.443922,0.697043,-0.567522,0.548112,0.614368,-0.765038,0.445112,0.465316,-0.760582,0.51793,0.3914,-0.56917,0.314432,0.759697,-0.773797,0.348003,0.529252,-0.57326,0.622059,0.53328,-0.572314,0.670186,0.472488,-0.753655,0.569536,0.328013,-0.738639,0.609363,0.288186,-0.917875,0.388928,-0.078555,-0.912046,0.328837,-0.244911,-0.897153,0.351177,-0.267861,-0.902219,0.418134,-0.10535,-0.873348,0.390515,-0.291055,-0.87875,0.461501,-0.121555,-0.865261,0.492355,0.094211,-0.880184,0.455397,0.133549,-0.843135,0.532487,0.074557,-0.893246,0.408856,0.186834,-0.933073,0.35725,-0.041597,-0.907102,0.344646,0.241554,-0.949492,0.313761,0.001923,-0.927366,0.305155,-0.216437,-0.945219,0.272652,-0.179327,0.542405,0.257363,0.799707,0.598224,0.342509,0.724418,0.797082,0.168096,0.579943,0.751091,0.121036,0.648976,0.627949,0.440535,0.64153,0.833338,0.240303,0.497757,0.708365,0.075198,0.701804,0.481826,0.173009,0.858974,0.672231,0.031983,0.739616,0.43141,0.091372,0.897488,0.222633,0.265847,0.937925,0.279214,0.387066,0.87875,0.172918,0.14478,0.974212,0.323954,0.498489,0.804071,0.339183,0.599353,0.725028,-0.530412,0.035035,0.84698,-0.158513,0.018006,0.987182,-0.133793,0.077273,0.987976,-0.479232,0.077456,0.874233,-0.152013,-0.109775,0.982238,-0.413099,-0.113407,0.903562,0.187139,-0.010956,0.982269,0.216071,0.053072,0.974914,0.1601,-0.13303,0.978088,-0.725059,0.040315,0.68746,-0.802393,0.031312,0.595965,-0.81756,-0.024506,0.575274,-0.878201,-0.005097,0.478195,-0.607318,-0.129215,0.783837,-0.732475,-0.141697,0.665853,-0.837275,-0.051027,0.544359,-0.583636,-0.096591,0.806238,-0.890316,-0.031739,0.454176,-0.214148,-0.129307,0.968169,0.15775,-0.164861,0.973601,-0.708365,-0.082583,0.700949,-0.723197,-0.051637,0.688681,-0.836634,-0.040925,0.54619,-0.827662,-0.051332,0.558855,-0.787194,-0.083804,0.61095,-0.680074,-0.116214,0.723838,-0.756584,-0.153294,0.635639,-0.694418,-0.134526,0.70687,-0.621418,-0.128483,0.772851,-0.644917,-0.096469,0.75811,-0.808161,-0.125736,0.575365,-0.799951,-0.117191,0.588458,-0.65154,-0.101382,0.751793,-0.845302,-0.057131,0.531175,-0.671712,-0.0665,0.737815,-0.821619,-0.09772,0.561571,-0.963836,-0.090823,-0.250496,-0.940611,-0.10831,-0.321726,-0.991607,-0.101901,0.079287,-0.986877,-0.100772,0.126102,-0.989288,-0.087832,0.116428,-0.97293,-0.057588,-0.223792,-0.995636,-0.030854,0.087771,-0.975158,-0.023347,-0.220252,-0.922941,-0.074404,-0.377636,-0.903867,-0.134251,-0.40614,-0.8493,-0.096133,-0.519059,-0.837886,-0.169805,-0.518723,-0.92877,-0.032075,-0.369243,-0.852443,-0.04181,-0.521073,-0.874813,-0.14835,-0.461165,-0.830409,-0.158971,-0.533982,-0.663747,-0.129124,-0.736686,-0.724784,-0.085025,-0.683676,-0.795709,-0.134556,-0.590503,-0.768731,-0.168615,-0.616901,-0.745354,-0.100955,-0.658956,-0.600269,-0.080416,-0.795709,-0.73159,-0.047456,-0.680044,-0.563738,-0.044954,-0.824702,-0.400067,-0.04471,-0.915372,-0.468673,-0.07535,-0.880123,-0.152318,-0.02414,-0.988006,-0.166875,-0.039216,-0.985168,-0.35905,-0.037172,-0.932554,-0.142033,-0.035615,-0.989196,-0.540239,-0.03357,-0.840815,-0.188055,0.001007,-0.982147,0.8623,-0.122593,0.491287,0.950896,-0.173284,0.256386,0.915555,-0.182592,0.358318,0.809381,-0.098209,0.578967,0.812098,-0.291818,0.505264,0.685446,-0.24778,0.684622,0.952452,-0.206397,0.224097,0.923124,-0.253548,0.28898,0.857845,-0.32548,0.397656,0.558763,-0.003784,0.829279,0.576006,-0.060823,0.815149,0.455214,-0.184881,0.870937,0.571764,-0.200995,0.795373,0.865902,-0.189062,0.463057,0.956267,-0.163366,0.242531,0.962401,-0.147771,0.227821,0.817408,-0.306803,-0.487503,0.64153,-0.24778,-0.725944,0.670156,-0.231391,-0.705191,0.868221,-0.276009,-0.412305,0.709586,-0.170385,-0.683676,0.908994,-0.211158,-0.359325,0.511856,-0.190344,-0.837703,0.497482,-0.185095,-0.847468,0.50737,-0.139317,-0.850368,0.957549,-0.288064,0.00824,0.940214,-0.321787,-0.111545,0.880856,-0.272347,0.387158,0.915525,-0.271889,0.296365,0.966277,-0.250252,0.060396,0.877132,-0.282235,0.388531,0.936552,-0.250954,-0.244667,0.800501,-0.193793,-0.567125,0.961333,-0.230659,0.150426,0.640614,-0.121586,-0.758141,0.539079,-0.08182,-0.838221,0.327403,-0.083224,-0.941191,0.383038,-0.015778,-0.923582,0.173528,0.007355,-0.984771,0.136692,-0.044008,-0.989624,0.086032,-0.04358,-0.995331,0.261208,-0.092563,-0.960814,0.054628,-0.051668,-0.997162,0.215918,-0.081942,-0.97293,0.381664,-0.143406,-0.913083,0.430036,-0.137608,-0.892239,0.357128,-0.113315,-0.927122,0.479873,-0.051729,-0.87579,0.847987,-0.187414,0.495743,0.834529,-0.205878,0.511032,0.805078,-0.258126,0.534013,0.825953,-0.27134,0.494095,0.801416,-0.315256,0.508225,0.805872,-0.347667,0.479202,0.883511,-0.28547,0.371258,0.911802,-0.208075,0.353984,0.8417,-0.355937,0.405927,0.923795,-0.122013,0.362865,0.87582,-0.109714,0.469955,0.891385,-0.16126,0.423505,-0.34962,-0.053591,0.935331,-0.144292,-0.09827,0.984619,-0.17307,-0.08655,0.981079,-0.328501,-0.041993,0.943541,0.094089,-0.156774,0.983123,0.04178,-0.154546,0.987091,-0.468978,-0.020081,0.882962,-0.519395,-0.02707,0.85406,-0.609363,-0.005951,0.79284,-0.656697,-0.009919,0.754051,-0.552873,-0.030671,0.832667,-0.373394,-0.063265,0.925504,-0.576128,-0.041169,0.816279,-0.393445,-0.077517,0.916044,-0.683187,-0.008759,0.730155,-0.701498,-0.017182,0.712424,-0.145207,-0.110446,0.983184,0.108249,-0.163182,0.980621,-0.155614,-0.125553,0.979797,0.103946,-0.173589,0.979308,-0.869381,0.055666,0.490951,-0.877957,0.052828,0.475784,-0.781549,0.014313,0.623646,-0.768426,0.013367,0.63979,-0.890347,0.036439,0.453749,-0.792749,0.003784,0.609516,-0.740715,0.016327,0.67159,-0.856044,0.056703,0.513749,-0.948119,0.119755,0.294412,-0.960936,0.116489,0.251015,-0.981506,0.189825,0.024384,-0.984191,0.169866,-0.049837,-0.976653,0.107639,0.185858,-0.989868,0.097751,0.102725,-0.973479,0.147374,-0.174902,-0.943052,0.156255,-0.293619,-0.943022,0.158513,-0.292428,-0.940062,0.112217,-0.322001,-0.923795,0.140873,-0.355937,-0.949675,0.181768,-0.255074,-0.934538,0.143437,-0.325632,-0.892117,0.165685,-0.42024,-0.9541,0.232704,-0.188452,-0.931669,0.24366,-0.269387,-0.911832,0.258095,-0.319254,-0.942717,0.165075,-0.289834,-0.84817,0.295053,-0.439894,-0.890927,0.226966,-0.393323,-0.968566,0.107059,-0.224464,-0.975738,0.11829,-0.184057,-0.942717,0.160192,-0.29252,-0.968535,0.139225,-0.206214,-0.562426,0.348491,-0.749779,-0.584857,0.322672,-0.744163,-0.80697,0.257973,-0.531205,-0.755638,0.300394,-0.582018,-0.606494,0.281411,-0.743584,-0.846583,0.221473,-0.483932,-0.719504,0.337016,-0.607196,-0.545305,0.363994,-0.755058,-0.353191,0.374615,-0.857234,-0.363689,0.366314,-0.856441,-0.146794,0.371288,-0.916837,-0.150517,0.367138,-0.917875,-0.375713,0.343272,-0.860775,-0.384777,0.299295,-0.873104,-0.155126,0.344554,-0.92584,-0.158635,0.296335,-0.941801,0.522568,-0.229591,0.82107,0.665242,-0.220222,0.713401,0.677023,-0.239814,0.695761,0.515305,-0.249001,0.820002,0.766778,-0.189978,0.613117,0.784082,-0.204169,0.586077,0.292215,-0.21839,0.931059,0.32963,-0.207526,0.920988,0.338633,-0.202246,0.918912,0.517228,-0.216468,0.827998,0.331462,-0.205573,0.920774,0.503952,-0.216224,0.836207,0.650319,-0.20838,0.730491,0.749138,-0.185278,0.635945,0.633229,-0.21015,0.744865,0.731376,-0.193579,0.65389,0.945585,0.105258,-0.30781,0.952849,0.062075,-0.296945,0.813349,0.145054,-0.563341,0.797876,0.187567,-0.572863,0.603687,0.220466,-0.766106,0.585009,0.257179,-0.769127,0.956938,-0.002258,-0.29017,0.825983,0.076693,-0.558428,0.620624,0.154912,-0.768639,0.782128,0.212653,-0.585681,0.937651,0.132756,-0.321207,0.566485,0.277017,-0.776086,0.996887,0.056185,-0.05475,0.998688,0.030488,-0.040803,0.987732,-0.006348,0.15598,0.984344,-0.026399,0.174169,0.999481,-0.00824,-0.030915,0.99762,-0.063753,-0.0253,0.980773,-0.058138,0.186254,0.975616,-0.103671,0.193396,0.210639,0.332652,-0.919218,0.044893,0.353893,-0.934202,0.042207,0.357982,-0.932768,0.203497,0.340922,-0.917783,0.367229,0.317026,-0.874416,0.380322,0.303049,-0.873775,0.395978,0.272774,-0.876797,0.221809,0.306742,-0.925565,0.410627,0.212836,-0.886593,0.232002,0.251411,-0.939634,0.049806,0.330424,-0.942503,0.054109,0.278664,-0.958861,0.895138,-0.109928,0.431989,0.943083,-0.069979,0.325083,0.951933,-0.058107,0.300699,0.908872,-0.107242,0.402997,0.856471,-0.157018,0.491714,0.839473,-0.15067,0.52205,0.823054,-0.155187,0.546312,0.88229,-0.124058,0.454024,0.807031,-0.172399,0.564745,0.869594,-0.15067,0.470199,0.934874,-0.093356,0.342418,0.926206,-0.129276,0.354076,-0.949095,-0.018006,-0.314463,-0.978576,0.007904,-0.205695,-0.922971,0.347026,-0.166265,-0.911557,0.332377,-0.24192,-0.809198,0.579882,-0.094211,-0.814844,0.562975,-0.138127,-0.993316,0.006439,-0.114994,-0.931791,0.347148,-0.10596,-0.804468,0.589892,-0.069308,-0.894498,0.304086,-0.327677,-0.90524,-0.068697,-0.419294,-0.869411,0.270058,-0.413678,-0.856746,-0.11832,-0.501968,-0.81756,0.539598,-0.201025,-0.814142,0.508713,-0.279855,-0.743889,-0.529649,-0.407544,-0.840541,-0.452101,-0.298379,-0.444685,-0.863033,-0.23954,-0.560625,-0.812891,-0.15772,-0.650014,-0.598193,-0.468612,-0.259987,-0.930906,-0.256447,-0.890713,-0.418989,-0.176061,-0.90698,-0.414289,-0.07532,-0.609516,-0.790185,-0.063784,-0.622303,-0.782678,0.009308,0.950407,-0.026093,0.309854,0.91995,-0.306558,0.244301,0.924802,-0.370617,0.085574,0.97705,-0.083895,0.195654,0.899258,-0.435804,-0.036531,0.983642,-0.148259,0.102023,0.734916,-0.650319,0.192114,0.711875,-0.701804,0.025269,0.601642,-0.789636,-0.120273,0.937346,0.171575,0.303171,0.897427,0.224769,0.379528,0.809961,0.425672,0.403394,0.756676,0.481124,0.44261,0.966216,0.107791,0.234077,0.861507,0.358684,0.359294,0.850276,0.261879,0.456496,0.903775,0.010132,0.427839,0.801569,0.282571,0.526841,0.848689,0.027223,0.528184,0.706931,0.524369,0.474593,0.666005,0.554338,0.4991,0.876736,-0.275491,0.394208,0.700369,-0.629048,0.33726,0.820307,-0.260018,0.509354,0.652242,-0.617634,0.439375,0.283883,0.832698,0.475387,0.251045,0.867855,0.428694,0.478347,0.746239,0.462874,0.527024,0.702872,0.477676,0.232459,0.902707,0.362011,0.446638,0.782556,0.433668,0.589709,0.651418,0.477309,0.333293,0.795953,0.505295,0.661397,0.590106,0.462905,0.400464,0.754326,0.520157,0.108921,0.862392,0.494369,0.08359,0.889737,0.448714,-0.061373,0.883816,0.463729,-0.06595,0.908444,0.412702,0.151494,0.835078,0.528825,-0.045717,0.860683,0.507035,0.070803,0.918912,0.387982,0.06708,0.949828,0.305429,-0.062227,0.935148,0.348735,-0.051271,0.963134,0.264046,0.20014,-0.138615,-0.969909,-0.032655,-0.108493,-0.99353,-0.072115,0.042055,-0.99649,0.173101,0.010681,-0.984832,-0.32667,-0.081576,-0.941588,-0.378246,0.079592,-0.922239,0.430219,-0.011536,-0.902615,0.442396,-0.172308,-0.880093,0.754601,-0.030854,-0.655446,0.742576,-0.210456,-0.635792,0.434523,-0.394269,-0.809748,0.219001,-0.360942,-0.906491,0.407483,-0.643849,-0.647603,0.209815,-0.633015,-0.74514,0.674245,-0.439406,-0.593524,0.568194,-0.623371,-0.537126,0.005585,-0.329417,-0.944151,-0.248329,-0.314005,-0.916349,0.010804,-0.588031,-0.80874,-0.157781,-0.525956,-0.835719,-0.783135,-0.054842,-0.619373,-0.671896,-0.375591,-0.638295,-0.75634,-0.392254,-0.523484,-0.853298,-0.043367,-0.519578,-0.783685,-0.404981,-0.470962,-0.864925,-0.033845,-0.500748,-0.406171,-0.730308,-0.549211,-0.473891,-0.782006,-0.404736,-0.492569,-0.814112,-0.307535,-0.863552,0.142277,-0.483718,-0.809839,0.128208,-0.572436,-0.860195,0.149998,-0.48735,-0.65923,0.109409,-0.743919,-0.613514,-0.064516,-0.787011,-0.503861,-0.340037,-0.794,-0.284066,-0.635701,-0.717734,-0.843562,-0.068148,-0.53267,-0.761071,-0.4756,-0.441023,-0.726859,-0.526933,-0.440413,-0.830897,-0.098849,-0.547502,-0.679434,-0.574633,-0.456191,-0.813135,-0.126774,-0.568072,-0.438215,-0.875851,-0.202033,-0.384198,-0.90405,-0.187231,-0.3249,-0.923124,-0.205573,-0.823206,0.125401,-0.553667,-0.826716,0.128819,-0.547655,-0.822779,0.141179,-0.550523,-0.838466,0.14362,-0.525651,-0.854213,-0.041627,-0.518235,-0.780969,-0.430921,-0.452071,-0.477889,-0.844081,-0.243049,-0.797601,-0.161504,-0.581133,-0.817133,-0.150334,-0.556444,-0.843684,0.233711,-0.483261,-0.827601,0.198492,-0.524979,-0.811701,0.4644,-0.354167,-0.81756,0.40904,-0.405225,-0.822352,0.167272,-0.543809,-0.798151,-0.151067,-0.583148,-0.830195,0.345775,-0.437208,-0.630696,-0.612354,-0.476638,-0.599414,-0.632588,-0.490402,-0.273782,-0.931059,-0.241127,-0.240364,-0.931364,-0.273354,-0.601459,-0.629566,-0.491745,-0.228919,-0.9288,-0.291391,0.551866,0.629261,0.547197,0.790216,0.430921,0.435682,0.83047,0.336924,0.443587,0.609577,0.54207,0.578387,0.933653,0.195746,0.299936,0.947661,0.107242,0.300638,0.316568,0.684622,0.656545,0.266457,0.750725,0.60445,0.035371,0.749382,0.661153,0.008301,0.797967,0.602588,0.207312,0.800165,0.562761,0.47734,0.700217,0.530808,-0.020753,0.83404,0.551256,0.731681,0.51616,0.445174,0.904386,0.280923,0.321116,0.962218,-0.269143,0.04062,0.852931,-0.51796,-0.064608,0.834284,-0.550829,0.022523,0.943968,-0.318125,0.08768,0.802667,-0.573016,0.16541,0.916715,-0.352306,0.188299,0.638722,-0.75869,-0.127873,0.633473,-0.773522,-0.017762,0.602435,-0.785638,0.140721,0.977935,-0.107242,0.179235,0.985656,-0.036988,0.164495,0.958312,-0.167638,0.231239,0.982116,0.037629,0.184332,0.976745,-0.209449,0.045717,0.873226,-0.479202,-0.088351,0.629292,-0.756432,-0.178259,0.855831,-0.375713,0.355419,0.855068,-0.360729,0.372387,0.9241,-0.211859,0.317942,0.920438,-0.228797,0.316843,0.874172,-0.335185,0.35136,0.937101,-0.17777,0.300302,0.933287,-0.214576,0.287912,0.87936,-0.373547,0.295236,0.755516,-0.580523,0.303476,0.72277,-0.576861,0.380474,0.551164,-0.784692,0.283547,0.510727,-0.782067,0.357036,0.715476,-0.570452,0.403272,0.73751,-0.564776,0.370159,0.491501,-0.788507,0.369671,0.503281,-0.804437,0.315531,0.957274,-0.271401,0.099765,0.839625,-0.536973,0.081454,0.833216,-0.499313,-0.237434,0.941099,-0.245033,-0.232856,0.597522,-0.801813,-0.002808,0.615528,-0.733634,-0.287851,0.967772,-0.057894,-0.245033,0.992676,-0.093142,0.076724,0.961821,-0.134922,0.238044,0.912656,-0.301614,0.275735,0.786493,-0.552904,0.275063,0.54503,-0.814631,0.198187,-0.432142,0.077822,0.898404,-0.400616,0.144627,0.904752,-0.231666,0.101474,0.967467,-0.25547,0.035646,0.966155,-0.064089,0.071657,0.995361,-0.077517,0.008362,0.996948,-0.276254,0.016633,0.960936,-0.463424,0.057192,0.884274,-0.083804,-0.012909,0.996368,-0.651936,0.106052,0.750786,-0.621235,0.132969,0.77224,-0.82635,0.15128,0.542405,-0.809137,0.187536,0.556841,-0.589923,0.199744,0.782342,-0.789209,0.252419,0.559801,-0.451247,-0.185247,0.872921,-0.461562,-0.183966,0.867794,-0.633931,-0.137364,0.761071,-0.634632,-0.127903,0.762108,-0.754448,-0.100375,0.648579,-0.763298,-0.087588,0.640065,-0.454176,-0.170324,0.874447,-0.628864,-0.125431,0.767296,-0.747002,-0.090701,0.658589,-0.628651,-0.09476,0.771874,-0.417249,-0.158605,0.894833,-0.774194,-0.052583,0.630726,-0.14008,-0.22602,0.963988,-0.208197,-0.251473,0.945189,0.16068,-0.268288,0.949828,0.074068,-0.315073,0.946135,-0.236518,-0.232215,0.943449,-0.226691,-0.212958,0.950377,0.018433,-0.276528,0.960814,0.022248,-0.241737,0.970061,-0.97412,0.213874,0.073,-0.963988,0.26249,0.042055,-0.920225,0.273965,0.279397,-0.930754,0.216254,0.294778,-0.937132,0.1742,0.302316,-0.980041,0.171453,0.100528,-0.986663,0.151952,-0.058321,-0.97644,0.192083,-0.098025,-0.974853,0.122166,-0.186346,-0.959532,0.158635,-0.232521,-0.962554,0.233131,-0.138218,-0.940733,0.193945,-0.278146,-0.924406,-0.054689,0.377392,-0.913358,-0.059511,0.402722,-0.995941,-0.007508,0.089511,-0.996307,-0.020508,0.083132,-0.941313,0.044496,-0.334574,-0.940184,0.025452,-0.33964,-0.907895,-0.042817,0.416944,-0.996368,0.031312,0.078951,-0.931791,0.105686,-0.347209,-0.995972,0.001617,0.089572,-0.938017,-0.029572,0.345286,-0.955382,0.070223,-0.286843,-0.870418,-0.034333,0.491043,-0.853603,-0.065371,0.516739,-0.840022,-0.077212,0.537004,-0.829524,-0.068545,0.554216,-0.902554,0.075991,-0.42375,-0.878323,0.105502,-0.466262,-0.910306,0.149419,-0.385968,-0.933409,0.118351,-0.338664,-0.952849,0.086459,-0.290811,-0.92346,0.048708,-0.380566,-0.8811,0.011902,-0.472762,-0.864284,0.036012,-0.501694,-0.809137,-0.021027,-0.587207,-0.810694,0.004578,-0.585437,-0.845698,0.068392,-0.529221,-0.81222,0.046724,-0.581408,-0.825678,0.023377,-0.563616,-0.887204,-0.003998,-0.461318,-0.835261,0.132908,-0.533525,-0.795129,0.140721,-0.589831,-0.927213,-0.046754,-0.371563,-0.868099,0.094974,-0.487167,-0.82812,0.124271,-0.546556,-0.819605,0.038301,-0.571642,-0.838527,0.049379,-0.542589,-0.857997,0.015564,-0.513352,-0.850368,0.11887,-0.512528,-0.880367,0.08948,-0.465712,-0.923948,-0.039644,-0.380413,-0.953459,-0.080752,-0.290414,-0.94702,0.033601,-0.319346,-0.971679,-0.007843,-0.236122,-0.551408,-0.027863,-0.833735,-0.716727,-0.016846,-0.697134,-0.675436,-0.04828,-0.735801,-0.458296,-0.066073,-0.886319,-0.198187,-0.071413,-0.977538,-0.3184,-0.026521,-0.947569,0.023652,-0.068575,-0.997345,-0.07181,-0.017426,-0.997253,-0.442671,0.042024,-0.895657,-0.639119,0.03708,-0.768181,-0.182562,0.049318,-0.981933,-0.755394,0.038301,-0.654134,-0.767724,-0.127293,-0.627979,-0.433363,-0.153264,-0.888058,-0.501602,-0.128758,-0.855434,-0.763115,-0.042451,-0.644826,-0.115635,-0.148503,-0.982116,-0.170293,-0.183355,-0.968169,-0.858272,0.036378,-0.511826,-0.9111,-0.085543,-0.40315,-0.919065,-0.094577,-0.38255,-0.712485,-0.090823,-0.695761,-0.918821,-0.008148,-0.394574,-0.671926,0.013215,-0.740471,-0.371075,-0.073428,-0.925657,-0.096377,-0.074892,-0.992492,-0.352916,0.019501,-0.935423,-0.109134,-0.00473,-0.993988,0.389019,-0.027619,0.920804,0.626881,-0.049196,0.777551,0.612262,-0.085726,0.785974,0.385632,-0.06003,0.920682,0.79809,-0.074984,0.597827,0.777795,-0.112552,0.618305,0.137638,-0.036409,0.989807,0.135899,-0.00998,0.990661,0.143681,0.04828,0.988433,0.400616,0.022706,0.915952,0.645466,-0.008759,0.763695,0.818049,-0.043306,0.573473,0.560869,-0.373547,0.738792,0.349376,-0.358898,0.865505,0.41023,-0.276528,0.869015,0.564226,-0.258736,0.78399,0.629322,-0.217872,0.745933,0.67394,-0.36195,0.644002,0.658437,-0.17365,0.732292,0.70336,-0.316721,0.636311,0.659139,-0.389386,0.64333,0.500504,-0.359722,0.787408,0.634877,-0.323496,0.70159,0.467757,-0.287423,0.83578,0.718436,-0.396374,0.571581,0.720542,-0.360118,0.592517,0.272408,-0.317972,0.908109,0.256996,-0.261422,0.930357,0.956359,-0.130833,-0.261147,0.97293,-0.071444,-0.219642,0.905362,-0.032777,-0.423292,0.879269,-0.101962,-0.465255,0.753533,0.008606,-0.657308,0.732109,-0.065645,-0.677969,0.854762,-0.162938,-0.492752,0.940428,-0.183874,-0.285897,0.709128,-0.13187,-0.692618,0.97705,-0.192206,-0.091617,0.985748,-0.147435,-0.080813,0.978698,-0.188421,0.081332,0.985839,-0.15009,0.074496,0.993408,-0.099826,-0.056093,0.990631,-0.113407,0.075777,0.851619,-0.458144,-0.254555,0.876339,-0.408765,-0.254738,0.908231,-0.418317,0.009522,0.902799,-0.427595,-0.045808,0.882534,-0.392285,0.259285,0.911344,-0.356151,0.206336,0.906308,-0.322001,-0.27369,0.93527,-0.353893,0.001465,0.895718,-0.369823,0.246681,0.914945,-0.381451,-0.131504,0.863643,-0.411695,-0.290841,0.940947,-0.32432,0.097079,0.799097,-0.418744,-0.431318,0.77337,-0.435743,-0.460402,0.689077,-0.389416,-0.611103,0.641194,-0.368328,-0.673147,0.780755,-0.358867,-0.51146,0.795648,-0.269021,-0.54268,0.619373,-0.279489,-0.733634,0.612842,-0.198767,-0.764763,0.338633,-0.014252,-0.940794,0.142369,-0.010315,-0.989746,0.195654,-0.067873,-0.978301,0.358989,-0.07767,-0.930082,0.530259,-0.099643,-0.841945,0.536058,-0.032685,-0.843532,0.532975,0.040223,-0.845149,0.304636,0.054079,-0.950926,0.073183,0.055147,-0.995788,0.29667,-0.209235,-0.931761,0.469131,-0.281686,-0.83697,0.526231,-0.325144,-0.785699,0.331034,-0.256294,-0.908139,0.10419,-0.209967,-0.972137,0.11417,-0.164006,-0.979797,0.11127,-0.102695,-0.988464,0.276833,-0.140568,-0.95056,0.101413,-0.043733,-0.993866,0.266274,-0.083987,-0.960204,0.437971,-0.198553,-0.876766,0.4214,-0.132511,-0.897122,0.940123,-0.123203,0.317728,0.968749,-0.140812,0.204108,0.958525,-0.175939,0.224158,0.925596,-0.158208,0.343852,0.873836,-0.137028,0.466475,0.892514,-0.10065,0.439558,0.908597,-0.073916,0.411023,0.951476,-0.09711,0.291971,0.975951,-0.111606,0.187109,0.765069,-0.246956,0.594684,0.757256,-0.354015,0.548814,0.729728,-0.375256,0.57152,0.717673,-0.269692,0.641987,0.764763,-0.382397,0.518509,0.739219,-0.381176,0.555162,0.693167,-0.139103,0.707205,0.783105,-0.161199,0.600604,0.895352,-0.238807,0.375866,0.848262,-0.279336,0.449843,0.817316,-0.360302,0.449599,0.824824,-0.377087,0.421186,-0.365215,-0.276833,0.888791,-0.202246,-0.318522,0.926054,-0.196936,-0.217048,0.956053,-0.376995,-0.178594,0.908811,-0.185492,-0.149083,0.971252,-0.377117,-0.111209,0.919431,0.005982,-0.366894,0.930235,0.014527,-0.259774,0.965545,0.021607,-0.187689,0.981964,-0.560442,-0.131718,0.817621,-0.535356,-0.23133,0.812311,-0.75341,-0.073916,0.65334,-0.724357,-0.17243,0.667501,-0.570696,-0.067385,0.818354,-0.761925,-0.017518,0.647389,-0.526322,-0.269295,0.806482,-0.362621,-0.289804,0.885708,-0.690786,-0.23133,0.685018,-0.185186,-0.314402,0.931028,0.047792,-0.356059,0.933226,-0.371319,-0.025361,0.928129,-0.567614,0.020295,0.823023,-0.569353,-0.026887,0.82165,-0.373547,-0.070986,0.924863,-0.759667,0.065218,0.646992,-0.76223,0.019044,0.646992,-0.177587,-0.112857,0.977599,-0.172216,-0.070101,0.982543,0.026429,-0.153996,0.987701,0.032624,-0.114536,0.992859,-0.166417,-0.017762,0.98587,-0.368908,0.028047,0.929014,-0.157598,0.042451,0.986572,-0.362896,0.086795,0.927763,0.040162,-0.065188,0.99704,0.05063,-0.0065,0.998688,-0.566698,0.072359,0.820704,-0.756828,0.112308,0.643849,-0.562822,0.126286,0.816858,-0.751274,0.156926,0.641011,-0.900357,-0.034639,0.433729,-0.794214,-0.10596,0.598315,-0.794733,-0.17658,0.580645,-0.86111,-0.106235,0.497147,-0.895749,-0.017029,0.444197,-0.95172,0.033174,0.305094,-0.90466,0.028077,0.425153,-0.965361,0.055605,0.25483,-0.948943,0.093173,0.301279,-0.876919,0.059847,0.476852,-0.927763,0.155644,0.339061,-0.887143,0.131748,0.442244,-0.973876,0.074831,0.214331,-0.960082,0.107425,0.258156,-0.757256,-0.016907,0.652882,-0.862514,0.052461,0.503281,-0.964049,0.114536,0.239631,-0.962096,0.14658,0.229835,-0.972106,0.149358,0.1807,-0.9747,0.120518,0.188147,-0.953978,0.144414,0.262703,-0.964354,0.112033,0.239631,-0.958678,0.164281,0.232246,-0.970092,0.156438,0.185492,-0.948943,0.15659,0.273782,-0.977599,0.075289,0.196448,-0.966155,0.074252,0.246956,-0.97058,0.075167,0.228614,-0.904477,0.056948,0.422651,-0.901975,0.09891,0.42027,-0.898343,0.137944,0.417066,-0.892148,0.169317,0.418775,-0.985137,0.061769,-0.1601,-0.977935,0.017029,-0.208136,-0.993622,0.054842,0.0983,-0.976806,0.130192,0.169958,-0.954131,0.170202,0.246162,-0.991394,0.106662,-0.075625,-0.957244,0.131169,0.25779,-0.993713,0.109439,0.023133,-0.936888,0.031526,-0.348125,-0.930723,0.009583,-0.365551,-0.850765,-0.004944,-0.525468,-0.850368,-0.011536,-0.526048,-0.948454,0.047578,-0.313242,-0.852504,-0.000427,-0.522691,-0.92935,-0.007691,-0.36906,-0.85107,-0.022156,-0.524552,-0.996307,0.081881,0.024598,-0.994324,0.104801,-0.01767,-0.94528,0.076846,-0.316996,-0.955016,0.055605,-0.291208,-0.853053,0.049104,-0.519486,-0.855647,0.024384,-0.516923,-0.987793,0.145787,-0.054537,-0.933348,0.114963,-0.339976,-0.84698,0.082339,-0.525163,-0.956755,0.049348,-0.286569,-0.995209,0.083773,0.049806,-0.854885,0.007935,-0.518723,-0.966735,0.085482,0.240944,-0.967376,0.097049,0.233894,-0.960265,0.128208,0.24778,-0.957213,0.158635,0.24192,-0.542436,-0.058779,-0.838008,-0.548723,-0.050966,-0.834437,-0.724265,-0.036988,-0.688498,-0.720786,-0.03415,-0.692282,-0.720176,-0.033052,-0.692984,-0.541002,-0.064333,-0.838527,-0.721213,-0.033204,-0.691885,-0.541948,-0.068422,-0.837581,-0.332652,-0.095157,-0.938231,-0.33488,-0.082888,-0.938597,-0.12479,-0.121097,-0.984741,-0.128574,-0.10358,-0.986267,-0.332713,-0.102664,-0.937407,-0.122105,-0.131199,-0.983795,-0.342296,-0.063509,-0.937437,-0.134892,-0.075106,-0.988006,-0.543962,-0.056887,-0.837153,-0.543474,-0.037904,-0.838527,-0.332774,-0.084719,-0.939177,-0.333323,-0.09827,-0.937651,-0.113865,-0.125401,-0.985534,-0.117344,-0.133457,-0.984069,-0.540941,-0.010865,-0.840968,-0.328166,-0.063112,-0.942473,-0.102695,-0.107669,-0.988861,-0.333262,-0.104282,-0.93701,-0.543382,-0.067202,-0.836787,-0.119785,-0.135228,-0.98352,-0.722495,-0.028809,-0.690725,-0.72277,-0.014649,-0.690909,-0.721366,0.008454,-0.692496,-0.717093,0.039583,-0.695822,0.526627,-0.450117,0.721122,0.722495,-0.438185,0.534745,0.808527,-0.332347,0.485549,0.570513,-0.351299,0.742332,0.853816,-0.27781,0.44023,0.586566,-0.283364,0.75869,0.845302,-0.378521,0.376965,0.930021,-0.257973,0.261635,0.957091,-0.231666,0.173986,0.277963,-0.31312,0.908109,0.267098,-0.418744,0.867916,0.274148,-0.236915,0.932035,0.310373,-0.403363,0.860744,0.544725,-0.435957,0.716361,0.706595,-0.435163,0.557939,0.79339,-0.415113,0.445174,0.556383,-0.233314,0.797479,0.259804,-0.165319,0.951384,0.264016,-0.203467,0.942778,0.573046,-0.262734,0.776238,0.857479,-0.279489,0.431928,0.849879,-0.277261,0.448073,0.958403,-0.249062,0.139164,0.95587,-0.265084,0.12653,0.836268,-0.264931,0.480056,0.544816,-0.191382,0.816401,0.82635,-0.230811,0.513657,0.544267,-0.138188,0.827418,0.94705,-0.288705,0.140324,0.938536,-0.291574,0.184606,0.261605,-0.118564,0.957854,0.269448,-0.062868,0.960936,0.925901,-0.183721,-0.329997,0.768914,-0.155065,-0.620228,0.786706,-0.169225,-0.593646,0.927244,-0.180639,-0.327891,0.799005,-0.187262,-0.571368,0.928312,-0.196295,-0.315714,0.566668,-0.142064,-0.811579,0.598315,-0.161473,-0.784783,0.620136,-0.180456,-0.763421,0.983184,-0.181707,-0.016816,0.976989,-0.21305,-0.006775,0.950407,-0.172155,0.25895,0.937803,-0.239204,0.251564,0.980163,-0.197028,0.02002,0.930784,-0.189489,0.31254,0.970611,-0.236763,0.042451,0.924436,-0.185003,-0.333384,0.90347,-0.286721,0.318583,0.744133,-0.1442,-0.652242,0.533189,-0.123692,-0.836879,0.912778,-0.249947,-0.322947,0.963286,-0.266671,0.030396,0.97,-0.240333,0.036073,0.922941,-0.226691,-0.311075,0.898556,-0.260384,0.35316,0.909391,-0.238441,0.340739,0.801813,-0.209876,-0.559465,0.793603,-0.227515,-0.564226,0.629566,-0.197333,-0.751427,0.627827,-0.208167,-0.749962,0.78045,-0.230293,-0.581225,0.903714,-0.25309,-0.345256,0.774499,-0.215094,-0.594836,0.900754,-0.230934,-0.367748,0.623096,-0.209143,-0.753624,0.625233,-0.201972,-0.753838,0.96176,-0.27308,0.020753,0.897031,-0.272652,0.347789,0.967284,-0.253548,-0.004639,0.911557,-0.269356,0.310648,0.213507,-0.130161,-0.9682,0.206275,-0.098361,-0.97351,0.047975,-0.086245,-0.995086,0.053591,-0.119114,-0.991424,0.060457,-0.139531,-0.988342,0.225288,-0.150975,-0.962493,0.067629,-0.15128,-0.986145,0.237892,-0.163854,-0.957335,0.400067,-0.157231,-0.90286,0.37608,-0.137059,-0.916379,0.420545,-0.17304,-0.890591,0.358898,-0.111637,-0.926664,0.259041,-0.175726,-0.949736,0.268471,-0.176061,-0.94705,0.443434,-0.191687,-0.875546,0.437361,-0.190344,-0.878872,0.277078,-0.165044,-0.946562,0.452162,-0.186438,-0.872219,0.431806,-0.184301,-0.8829,0.24897,-0.171484,-0.953185,0.074404,-0.157231,-0.984741,0.080813,-0.159032,-0.983947,0.087741,-0.1554,-0.983917,0.099887,-0.139622,-0.985137,0.894803,-0.244148,0.37373,0.881375,-0.252449,0.399274,0.911039,-0.15775,0.380932,0.934843,-0.157079,0.318308,0.893643,-0.188208,0.407331,0.933927,-0.193152,0.300668,0.960051,-0.187933,0.20716,0.905087,-0.288858,0.31196,0.968688,-0.203223,0.14246,0.826746,-0.393567,0.401929,0.816675,-0.367534,0.444838,0.827998,-0.330699,0.452803,0.921232,-0.25959,0.289682,0.961119,-0.257881,0.098575,0.965667,-0.234169,0.112308,0.926908,-0.233985,0.293374,0.872921,-0.233833,0.428144,0.862117,-0.253914,0.438459,0.859645,-0.278298,0.428419,0.910886,-0.297098,0.286355,0.871364,-0.293344,0.393231,0.906003,-0.323679,0.272683,0.949492,-0.296426,0.102786,0.939299,-0.321421,0.119846,-0.34608,0.163274,0.923856,-0.141057,0.124699,0.982086,-0.140599,0.13712,0.980499,-0.341624,0.173315,0.923704,0.066713,0.076205,0.994842,0.065615,0.089511,0.993805,-0.533525,0.195105,0.822932,-0.544511,0.189062,0.817133,-0.704184,0.198828,0.68157,-0.724479,0.195593,0.660909,-0.555101,0.168096,0.8146,-0.354076,0.135624,0.925291,-0.741172,0.18598,0.645009,-0.147679,0.094119,0.984527,0.061068,0.045045,0.997101,-0.945433,0.150212,0.28901,-0.865169,0.179083,0.468368,-0.839991,0.18247,0.510941,-0.928983,0.150334,0.338115,-0.966796,0.123875,0.223426,-0.968352,0.132878,0.211219,-0.975555,0.133885,0.1742,-0.962401,0.147404,0.228065,-0.96823,0.150945,0.199194,-0.95352,0.164708,0.252297,-0.951842,0.158208,0.262581,-0.88226,0.182714,0.43379,-0.974792,0.189123,-0.11829,-0.980743,0.176855,-0.082797,-0.963805,0.176275,0.199957,-0.974822,0.178381,0.13358,-0.984222,0.170812,0.045564,-0.957335,0.209265,-0.199133,-0.87466,0.23191,-0.425581,-0.915738,0.173528,-0.362316,-0.758293,0.231208,-0.609485,-0.81756,0.149846,-0.555956,-0.924802,0.150121,-0.349559,-0.839198,0.113987,-0.531724,-0.513535,0.081484,-0.854183,-0.537004,0.01648,-0.84341,-0.710044,0.070101,-0.700644,-0.67861,0.122898,-0.724113,-0.607044,0.210211,-0.76632,-0.443403,0.183386,-0.877346,-0.247871,0.132603,-0.959655,-0.299539,0.013977,-0.953948,-0.016541,0.045351,-0.99881,-0.047182,-0.057009,-0.997253,-0.318155,-0.042848,-0.94705,-0.079104,-0.092532,-0.992553,0.566332,-0.062349,0.821802,0.825343,-0.163518,0.540391,0.813776,-0.167882,0.556352,0.568834,-0.057772,0.820399,0.935514,-0.236152,0.262734,0.922971,-0.245338,0.296457,0.294229,0.029328,0.95526,0.289804,0.017792,0.956908,0.280007,-0.012818,0.959899,0.554491,-0.089755,0.827296,0.825678,-0.187597,0.531999,0.937986,-0.259926,0.229286,0.891507,-0.226173,-0.392468,0.898618,-0.215979,-0.381848,0.776513,-0.205939,-0.595447,0.776269,-0.21839,-0.591327,0.632557,-0.197882,-0.748772,0.634114,-0.20423,-0.74575,0.767602,-0.244636,-0.592364,0.882077,-0.249794,-0.399335,0.618885,-0.214423,-0.755608,0.956755,-0.25486,-0.140049,0.96585,-0.240211,-0.096896,0.954527,-0.260201,0.145451,0.946806,-0.250649,0.201727,0.97058,-0.235939,-0.047914,0.93234,-0.254189,0.257118,0.286691,-0.13123,-0.948973,0.283883,-0.15009,-0.94702,0.119419,-0.123356,-0.985137,0.143132,-0.098666,-0.984741,0.154637,-0.030854,-0.987457,0.278085,-0.091189,-0.956206,0.442183,-0.156652,-0.883114,0.45912,-0.172246,-0.871487,0.459487,-0.178899,-0.869961,0.926878,-0.279885,0.250038,0.909238,-0.266488,0.319712,0.915586,-0.271615,0.296457,0.922788,-0.284921,0.259377,0.940275,-0.277505,0.197058,0.948637,-0.268563,0.167119,0.94409,-0.297525,0.141911,0.917325,-0.306253,0.254311,0.89346,-0.280496,0.35078,0.106967,-0.963073,-0.247017,0.07178,-0.995514,-0.061373,-0.151402,-0.965026,-0.213935,-0.112857,-0.915159,-0.386914,0.062624,-0.995483,0.071017,-0.156224,-0.983642,-0.089602,-0.04416,-0.797693,-0.601398,0.162175,-0.853725,-0.494797,0.37141,-0.843318,-0.388348,0.324107,-0.937803,-0.124332,0.28019,-0.957305,0.071047,0.257759,-0.944792,0.202155,0.116489,-0.976867,0.179174,0.159215,-0.975372,0.152593,-0.044038,-0.998383,0.035096,-0.090732,-0.995209,0.036287,0.200598,-0.977264,0.068331,-0.000732,-0.999878,-0.013642,-0.132603,-0.99115,-0.004944,0.079958,-0.984893,0.153539,0.263039,-0.924802,0.274789,0.293466,-0.913541,0.281594,0.336711,-0.913297,0.229102,0.384014,-0.916715,0.109989,0.215674,-0.970489,-0.10773,0.222541,-0.974273,-0.03473,0.409406,-0.911863,-0.028687,0.4008,-0.907163,-0.127934,0.366222,-0.913938,-0.174841,0.187689,-0.972503,-0.13773,0.293313,-0.944639,-0.146855,0.139561,-0.984313,-0.107761,0.031983,-0.987671,-0.153111,0.036561,-0.990509,-0.13245,0.021119,-0.992615,-0.119327,0.027558,-0.996277,-0.081576,-0.306406,0.898099,0.315439,-0.326365,0.872738,0.362957,-0.439192,0.848476,0.295236,-0.411267,0.874905,0.255623,-0.535203,0.816126,0.217841,-0.501206,0.843745,0.191931,-0.344249,0.847285,0.404431,-0.468001,0.821131,0.326609,-0.572893,0.786523,0.230537,-0.379528,0.90228,0.204413,-0.281259,0.924467,0.257332,-0.338298,0.931059,0.136448,-0.246742,0.951781,0.182104,-0.46559,0.871944,0.15125,-0.421888,0.901883,0.092532,-0.173986,0.935728,0.306772,-0.189978,0.909757,0.369091,-0.149754,0.962554,0.225867,-0.199774,0.885159,0.420179,-0.204077,0.861354,0.465163,-0.377117,0.784753,0.491836,-0.38554,0.746391,0.542436,-0.558641,0.709708,0.429151,-0.53328,0.750328,0.390606,-0.709494,0.652211,0.266793,-0.667348,0.701224,0.250801,-0.50087,0.78872,0.356365,-0.362133,0.818567,0.445814,-0.618641,0.748589,0.238441,-0.20423,0.835078,0.510727,-0.200262,0.802667,0.561754,-0.192419,0.762108,0.618183,-0.796075,0.598315,-0.090976,-0.74752,0.662191,-0.051485,-0.700797,0.706473,0.098727,-0.756432,0.648671,0.083438,-0.705893,0.708304,-0.003204,-0.650807,0.749779,0.119388,-0.806513,0.586566,0.073611,-0.842006,0.526414,-0.117649,-0.840724,0.455977,-0.291879,-0.809015,0.528398,-0.257363,-0.779077,0.592242,-0.205573,-0.756005,0.639882,-0.13773,-0.641377,0.765435,0.052248,-0.716361,0.69686,-0.034242,-0.694998,0.718741,-0.018433,-0.610523,0.790704,0.044679,-0.673788,0.738456,-0.025483,-0.576739,0.816767,0.014557,-0.539537,0.836085,0.099216,-0.575121,0.808374,0.12537,-0.498215,0.865413,0.052644,-0.610736,0.780694,0.132237,-0.672536,0.739128,0.036653,-0.737175,0.67159,-0.074068,-0.120823,-0.989044,0.084658,-0.089846,-0.994201,0.05884,-0.044496,-0.99234,0.115055,-0.049837,-0.990905,0.124729,-0.026551,-0.990265,0.136418,-0.040315,-0.998291,0.042238,-0.029786,-0.993774,0.107089,-0.050233,-0.990265,0.129643,-0.129826,-0.986023,0.104343,-0.04944,-0.99002,0.131932,-0.129948,-0.98468,0.116153,-0.026551,-0.990295,0.136387,-0.300729,-0.952544,0.046815,-0.271798,-0.96234,-0.000305,-0.307077,-0.948241,0.080691,-0.197943,-0.979095,-0.046388,-0.078524,-0.99527,-0.056856,0.028809,-0.998749,0.040559,0.06827,-0.997253,0.028535,-0.001068,-0.994537,0.104221,-0.011658,-0.993957,0.109073,-0.026582,-0.990265,0.136418,0.115299,-0.992737,0.03357,0.01413,-0.994568,0.102878,-0.026582,-0.990295,0.136387,-0.019135,-0.993805,0.109317,-0.002747,-0.999054,0.042726,0.011689,-0.999115,-0.039705,0.08182,-0.995911,-0.03769,0.174108,-0.982818,-0.060793,0.286813,-0.956725,-0.048891,0.151769,-0.978423,0.140049,0.406934,-0.900113,0.155522,0.385754,-0.888302,0.249184,0.138554,-0.973693,0.180761,0.356914,-0.881466,0.309183,0.125858,-0.970885,0.203742,0.011536,-0.989105,0.146611,0.017151,-0.990387,0.137181,0.008179,-0.988433,0.151311,0.022217,-0.992615,0.119053,0.148747,-0.985504,0.08121,0.381695,-0.923429,0.039338,-0.428266,-0.122684,0.89526,-0.442457,-0.145665,0.884854,-0.205817,-0.190832,0.959777,-0.186865,-0.169622,0.96762,0.050172,-0.223762,0.973327,0.073672,-0.207678,0.975402,-0.17011,-0.148259,0.974181,-0.411969,-0.100314,0.905637,0.091525,-0.19129,0.977233,-0.595141,-0.060762,0.801294,-0.610034,-0.080844,0.788202,-0.716697,-0.034089,0.696493,-0.728935,-0.05179,0.682577,-0.621296,-0.102115,0.776849,-0.738945,-0.070528,0.670034,-0.907987,0.009156,0.418806,-0.90762,-0.013855,0.419477,-0.820734,-0.049532,0.56914,-0.812189,-0.030976,0.582537,-0.803125,-0.013459,0.59563,-0.902707,0.021058,0.42967,-0.993652,0.105167,0.039674,-0.994354,0.101474,0.030549,-0.921598,0.176977,-0.345378,-0.916623,0.175573,-0.359111,-0.99588,0.071596,0.055422,-0.921018,0.150456,-0.359203,-0.913022,0.147038,-0.380444,-0.897519,0.131504,-0.420881,-0.858699,0.161351,-0.486373,-0.868801,0.183264,-0.459944,-0.879757,0.187536,-0.436811,-0.925748,0.157231,-0.343852,-0.975341,0.110446,-0.190924,-0.970306,0.094852,-0.222388,-0.977996,0.108371,-0.178198,-0.981201,0.078433,-0.176275,-0.961058,0.080905,-0.264138,-0.979125,0.053407,-0.195959,-0.643055,0.162145,-0.748436,-0.655538,0.098025,-0.748741,-0.9111,0.069063,-0.406262,-0.896023,0.118412,-0.427839,-0.873714,0.171117,-0.455306,-0.62624,0.224158,-0.746666,-0.386975,0.238319,-0.890744,-0.379711,0.169958,-0.90933,-0.158116,0.22721,-0.960906,-0.149632,0.148259,-0.977538,-0.363903,0.09769,-0.926267,-0.131748,0.067476,-0.988952,0.472213,-0.234535,0.849666,0.462233,-0.248512,0.851192,0.615284,-0.268899,0.740989,0.607807,-0.241005,0.756584,0.711783,-0.300241,0.634968,0.706198,-0.251564,0.661763,0.616932,-0.221442,0.755181,0.487594,-0.224219,0.843745,0.715354,-0.214179,0.66509,0.317148,-0.216712,0.923246,0.298196,-0.227332,0.92703,0.274819,-0.238685,0.931364,0.945433,-0.158879,-0.284371,0.928953,-0.238197,-0.283273,0.812189,-0.187964,-0.552232,0.826868,-0.100558,-0.55327,0.620014,-0.1236,-0.774743,0.63094,-0.030488,-0.775201,0.831507,-0.009735,-0.555406,0.954833,-0.079867,-0.286111,0.631001,0.066775,-0.772851,0.991394,-0.128819,-0.023011,0.980224,-0.196509,-0.022095,0.967711,-0.157048,0.197028,0.954863,-0.218177,0.201514,0.96234,-0.27134,-0.015687,0.930296,-0.295083,0.217811,0.247047,0.07474,-0.966094,0.255379,-0.018342,-0.966643,0.084811,0.025819,-0.996033,0.068422,0.114292,-0.991058,0.059023,0.202918,-0.977386,0.240028,0.170385,-0.955687,0.420545,0.127689,-0.898221,0.4232,0.029389,-0.905545,0.419599,-0.064882,-0.905362,0.831019,-0.248878,0.497391,0.795404,-0.327982,0.509629,0.863796,-0.315012,0.393139,0.897794,-0.233375,0.373424,0.915494,-0.174108,0.362651,0.854885,-0.189337,0.483016,0.79049,-0.203528,0.577624,0.770989,-0.257759,0.582324,0.753288,-0.324442,0.572039,-0.919401,0.037416,0.391491,-0.95294,-0.04358,0.299936,-0.956572,-0.113895,0.268227,-0.93057,-0.033296,0.364513,-0.952635,-0.189398,0.237892,-0.934477,-0.121311,0.334635,-0.887997,0.043123,0.457808,-0.872219,0.106723,0.477279,-0.827052,0.109897,0.551225,-0.810541,0.161321,0.563005,-0.89993,-0.047945,0.433302,-0.844966,0.022187,0.534318,-0.859004,0.138371,0.492874,-0.906095,0.084292,0.414533,-0.852321,0.139439,0.504044,-0.895413,0.105777,0.432417,-0.801752,0.174352,0.571642,-0.803247,0.152409,0.575793,-0.944151,0.014008,0.329173,-0.934202,0.054109,0.35255,-0.922269,-0.230445,0.310251,-0.927763,-0.251625,0.27546,-0.923704,-0.272683,0.269021,-0.920896,-0.242805,0.30488,-0.917478,-0.302652,0.258034,-0.917508,-0.264657,0.296823,-0.908841,-0.213965,0.358043,-0.909055,-0.208197,0.360912,-0.88171,-0.189886,0.431806,-0.883236,-0.187139,0.429884,-0.906034,-0.232521,0.353557,-0.876827,-0.208991,0.432966,-0.907559,-0.215369,0.360424,-0.922666,-0.227271,0.311411,-0.904965,-0.234443,0.355022,-0.923215,-0.231361,0.306772,-0.881985,-0.200507,0.426435,-0.878018,-0.22837,0.420545,-0.930967,-0.237678,0.277078,-0.935423,-0.223701,0.27369,-0.226386,0.315775,0.921415,-0.334025,0.315683,0.888089,-0.324534,0.342143,0.881802,-0.213111,0.353496,0.910794,-0.333628,0.30546,0.891812,-0.213874,0.332469,0.918516,-0.444746,0.301462,0.843379,-0.444105,0.313486,0.83932,-0.463088,0.261147,0.84695,-0.108585,0.35435,0.92877,-0.118656,0.309244,0.943541,-0.009491,0.353984,0.935179,-0.008759,0.306436,0.951842,-0.105991,0.345286,0.932463,-0.0094,0.349467,0.936888,-0.133091,0.205084,0.969634,-0.249336,0.214972,0.944243,-0.146184,0.044923,0.98822,-0.272927,0.053041,0.96054,-0.007141,0.20188,0.979369,-0.004608,0.043641,0.999023,-0.358287,0.223121,0.906522,-0.463912,0.223762,0.857143,-0.387494,0.065859,0.919492,-0.493637,0.082644,0.865719,-0.928129,-0.243599,0.281442,-0.946348,-0.209418,0.24604,-0.940947,-0.211463,0.26429,-0.925016,-0.237587,0.296457,-0.903104,-0.253578,0.346507,-0.90347,-0.263802,0.337809,-0.873348,-0.253883,0.415601,-0.870571,-0.264046,0.415113,-0.904599,-0.266335,0.332713,-0.93054,-0.251473,0.266091,-0.904935,-0.261391,0.335704,-0.929716,-0.263405,0.257332,-0.869076,-0.259957,0.42082,-0.868068,-0.240242,0.4344,-0.949736,-0.219794,0.222816,-0.947691,-0.246529,0.202643,-0.293893,-0.17716,0.939268,-0.443739,-0.173376,0.879208,-0.439314,-0.214515,0.872311,-0.288064,-0.228645,0.929899,-0.42964,-0.241585,0.870052,-0.279061,-0.26371,0.923338,-0.583514,-0.177374,0.792474,-0.583148,-0.204749,0.786126,-0.576647,-0.218421,0.787225,-0.139256,-0.239387,0.960875,-0.143712,-0.182012,0.972716,0.000183,-0.242012,0.970244,-0.000793,-0.182806,0.983123,-0.133793,-0.277779,0.951262,0.000885,-0.281137,0.959655,-0.145268,-0.101016,0.984191,-0.294229,-0.105106,0.949919,-0.141728,0.010956,0.989837,-0.285928,-0.005615,0.95822,-0.002228,-0.098758,0.995086,-0.004181,0.017701,0.999817,-0.441542,-0.114841,0.889828,-0.578021,-0.13419,0.804895,-0.43025,-0.032868,0.902097,-0.56563,-0.070467,0.821619,-0.883236,0.101138,0.457869,-0.910977,0.11774,0.395245,-0.919248,0.079379,0.38551,-0.889767,0.070467,0.450911,-0.921873,0.074557,0.3802,-0.890316,0.073397,0.449324,-0.856624,0.069002,0.511277,-0.847713,0.096164,0.521622,-0.82107,0.080508,0.565081,-0.808313,0.1095,0.578417,-0.858974,0.071871,0.506912,-0.826991,0.074343,0.557237,-0.830744,0.14597,0.537126,-0.866024,0.160253,0.473586,-0.799676,0.212653,0.56148,-0.830012,0.24247,0.502243,-0.791772,0.152928,0.591327,-0.776543,0.193762,0.599506,-0.892331,0.183813,0.412244,-0.854274,0.274087,0.441664,-0.261025,-0.278909,0.924131,-0.403546,-0.260323,0.877132,-0.393719,-0.250374,0.884457,-0.257088,-0.254738,0.932188,-0.392315,-0.22956,0.890713,-0.261361,-0.213965,0.941191,-0.548845,-0.231025,0.803339,-0.533341,-0.233802,0.812922,-0.52327,-0.233406,0.819544,-0.125797,-0.251076,0.959746,-0.125736,-0.286325,0.949828,0.000275,-0.246895,0.969024,0.000977,-0.286416,0.958068,-0.131291,-0.195532,0.971862,-0.000763,-0.185705,0.982574,-0.12891,-0.295175,0.946684,-0.269326,-0.281716,0.920896,0.00119,-0.29783,0.954588,-0.416852,-0.256966,0.871883,-0.564562,-0.22541,0.79397,-0.771752,-0.177801,0.610523,-0.839167,-0.174322,0.515122,-0.833277,-0.178899,0.523057,-0.759972,-0.186377,0.622608,-0.823939,-0.200873,0.529862,-0.746178,-0.208625,0.63216,-0.660024,-0.208441,0.721702,-0.67629,-0.199133,0.709159,-0.644887,-0.223609,0.730796,-0.689596,-0.197546,0.696707,-0.77929,-0.184149,0.598926,-0.697226,-0.203955,0.687185,-0.781304,-0.204077,0.589801,-0.841182,-0.187109,0.507309,-0.838771,-0.215247,0.500076,-0.772576,-0.218696,0.596026,-0.694815,-0.193213,0.692709,-0.687216,-0.163732,0.707724,-0.765313,-0.199591,0.611896,-0.675832,-0.114689,0.72805,-0.756676,-0.16068,0.633686,-0.823084,-0.234748,0.517075,-0.828089,-0.245888,0.503769,-0.818293,-0.204382,0.537187,-0.833399,-0.239814,0.497848,-0.778252,-0.220435,0.58797,-0.698538,-0.206397,0.68511,-0.649831,0.239692,0.721244,-0.552507,0.273659,0.787286,-0.564165,0.216956,0.796594,-0.65453,0.207068,0.727103,-0.590472,0.103458,0.800378,-0.67449,0.126438,0.727348,-0.733543,0.194769,0.651112,-0.735679,0.203436,0.646046,-0.744957,0.145695,0.65096,-0.750572,0.166814,0.639332,-0.662069,0.219153,0.716636,-0.772546,0.08536,0.62917,-0.686819,0.145177,0.712149,-0.560076,0.26957,0.783319,-0.584796,0.204779,0.784875,-0.69393,0.178381,0.697562,-0.604938,0.219398,0.765435,-0.589526,0.278207,0.758293,-0.675954,0.230293,0.700003,-0.584216,0.342723,0.735649,-0.667867,0.288766,0.685934,-0.487533,0.256081,0.834651,-0.477676,0.321818,0.817438,-0.477035,0.393567,0.785791,-0.743522,0.185034,0.642567,-0.760613,0.139256,0.634083,-0.734489,0.23487,0.636647,-0.776879,0.103397,0.621052,-0.712699,0.132145,0.688894,-0.786431,0.079653,0.612507,-0.724967,0.085665,0.683401,-0.622974,0.16126,0.765404,-0.502548,0.187506,0.843959,-0.636677,0.091922,0.765618,-0.517594,0.099765,0.849757,-0.230628,0.300577,0.925443,-0.115055,0.310587,0.943541,-0.115268,0.386303,0.915098,-0.229011,0.375103,0.898221,-0.119724,0.475906,0.871273,-0.234352,0.461013,0.855861,-0.008881,0.314859,0.949065,-0.01001,0.39079,0.920408,-0.011353,0.481613,0.876278,-0.35078,0.354656,0.866665,-0.355663,0.283242,0.890652,-0.354381,0.434462,0.827998,-0.367443,0.208289,0.9064,-0.23896,0.223518,0.944914,-0.383404,0.110233,0.916959,-0.252907,0.12241,0.959685,-0.119236,0.23365,0.964965,-0.007691,0.238319,0.971129,-0.127476,0.133091,0.982849,-0.006134,0.138585,0.990326,-0.699789,0.411573,0.583819,-0.611499,0.487259,0.623371,-0.627613,0.554979,0.545946,-0.720389,0.465499,0.514084,-0.501907,0.560533,0.65865,-0.512955,0.639515,0.572558,-0.795312,0.372295,0.478378,-0.772637,0.330912,0.541734,-0.747276,0.28489,0.600299,-0.67806,0.351421,0.645527,-0.593707,0.414106,0.689932,-0.487289,0.475082,0.732658,-0.25428,0.66512,0.702048,-0.132694,0.69042,0.711081,-0.13477,0.783959,0.605945,-0.258217,0.756218,0.601184,-0.014313,0.699789,0.714164,-0.015473,0.794061,0.607593,-0.385205,0.708731,0.591021,-0.378216,0.622272,0.685324,-0.365764,0.526536,0.767418,-0.244728,0.561571,0.790368,-0.126774,0.581957,0.803247,-0.012879,0.589618,0.80755,-0.71926,-0.621021,0.31138,-0.723869,-0.561541,0.400769,-0.777062,-0.515,0.361797,-0.773186,-0.565111,0.287759,-0.82168,-0.457564,0.339702,-0.821802,-0.498733,0.275399,-0.723167,-0.481887,0.494736,-0.778863,-0.450911,0.435896,-0.814661,-0.425672,0.393811,-0.761345,-0.610889,0.217109,-0.706168,-0.670156,0.22837,-0.81109,-0.544755,0.212867,-0.640126,-0.72747,0.246895,-0.655232,-0.672079,0.344859,-0.551714,-0.787469,0.274758,-0.56917,-0.724815,0.388073,-0.660512,-0.601733,0.448988,-0.65627,-0.507797,0.558031,-0.578112,-0.64037,0.505661,-0.575365,-0.527177,0.625294,-0.320872,-0.816645,0.47969,-0.339366,-0.705771,0.621815,-0.471389,-0.675832,0.566546,-0.456862,-0.77517,0.436293,-0.347484,-0.547563,0.761162,-0.474013,-0.539537,0.695822,-0.437025,-0.844691,0.308969,-0.300729,-0.890622,0.341044,-0.147008,-0.919675,0.364055,-0.162053,-0.844295,0.510727,0.013855,-0.927641,0.37315,0.012024,-0.852107,0.523179,-0.178137,-0.726951,0.663167,-0.188116,-0.552263,0.812159,0.00943,-0.732688,0.680441,0.005829,-0.551653,0.83404,-0.887356,0.104556,0.449049,-0.921995,0.086215,0.377453,-0.926328,0.077181,0.368664,-0.88934,0.109684,0.443861,-0.851527,0.12479,0.509201,-0.853908,0.107974,0.509049,-0.810755,0.119633,0.572985,-0.819391,0.096591,0.56502,-0.857143,0.090213,0.507065,-0.888363,0.091952,0.449812,-0.82577,0.082156,0.557939,-0.921354,0.084262,0.379406,-0.278787,-0.069826,0.957793,-0.404218,-0.066683,0.912198,-0.402234,-0.057985,0.913663,-0.281991,-0.069033,0.956908,-0.524308,-0.048494,0.850124,-0.513932,-0.033235,0.857143,-0.149754,-0.071261,0.986145,-0.145756,-0.065279,0.987152,-0.002716,-0.069521,0.997559,-0.002869,-0.060915,0.998138,-0.137486,0.018433,0.990326,-0.268075,0.009064,0.963347,-0.004273,0.024171,0.999695,-0.397504,0.003113,0.91757,-0.526048,0.007233,0.850398,-0.928281,-0.247536,0.277383,-0.941313,-0.274178,0.196783,-0.942778,-0.271828,0.192938,-0.927702,-0.267067,0.260689,-0.904874,-0.242836,0.349528,-0.905087,-0.202948,0.373638,-0.866604,-0.203711,0.455489,-0.86346,-0.148686,0.481948,-0.904447,-0.138432,0.403455,-0.931516,-0.200018,0.30372,-0.857112,-0.073366,0.509842,-0.945036,-0.248238,0.212653,-0.247017,0.197333,0.948698,-0.382855,0.150639,0.911435,-0.409436,0.060976,0.910276,-0.268654,0.103153,0.957671,-0.520432,0.090579,0.849055,-0.545946,0.007569,0.837764,-0.132572,0.130253,0.982574,-0.120975,0.226691,0.966399,-0.006165,0.14069,0.99002,-0.007691,0.237617,0.971313,-0.110965,0.29841,0.947935,-0.226966,0.274209,0.934477,-0.008759,0.307108,0.951628,-0.355815,0.232978,0.905026,-0.491745,0.176092,0.852718,-0.731315,-0.034303,0.681143,-0.804163,-0.092044,0.587207,-0.812555,-0.156163,0.561541,-0.745781,-0.104343,0.657949,-0.660054,-0.049409,0.749565,-0.639546,0.026551,0.768273,-0.614307,0.112064,0.78103,-0.712088,0.049348,0.700308,-0.791528,-0.011261,0.610981,-0.714805,0.028993,0.698691,-0.776238,0.068972,0.626606,-0.761315,0.093295,0.641591,-0.696616,0.051729,0.715537,-0.613971,0.005768,0.78927,-0.631001,-0.013886,0.775628,-0.63979,0.023072,0.768181,-0.725639,0.045259,0.686575,-0.785852,0.066744,0.614765,-0.89172,-0.356578,0.278695,-0.882534,-0.322245,0.342387,-0.898526,-0.267434,0.347972,-0.909238,-0.301218,0.287271,-0.852901,-0.302591,0.425398,-0.867763,-0.245705,0.431928,-0.906125,-0.345897,0.243416,-0.885464,-0.404614,0.228431,-0.853755,-0.473251,0.216987,-0.862545,-0.425581,0.273568,-0.856777,-0.389935,0.337382,-0.833064,-0.370342,0.410871,-0.724693,-0.309244,0.615741,-0.636219,-0.316416,0.703604,-0.635487,-0.255196,0.72869,-0.733146,-0.247414,0.633442,-0.538499,-0.311655,0.782861,-0.524094,-0.252144,0.81344,-0.812037,-0.240455,0.531724,-0.799188,-0.300729,0.520402,-0.78695,-0.374859,0.490036,-0.722037,-0.390759,0.570879,-0.645405,-0.40379,0.648335,-0.559374,-0.406262,0.722495,-0.306986,-0.26194,0.914914,-0.164953,-0.234321,0.958037,-0.14478,-0.175237,0.973815,-0.278329,-0.203681,0.938627,-0.000153,-0.221046,0.975249,-0.00116,-0.161687,0.986816,-0.404401,-0.233131,0.884335,-0.430067,-0.291238,0.854488,-0.458022,-0.396374,0.795648,-0.335063,-0.3802,0.862056,-0.18302,-0.364483,0.913022,0.002228,-0.355937,0.934477,-0.325632,0.698386,0.637318,-0.309946,0.683676,0.660665,-0.210639,0.547777,0.809656,-0.216468,0.606189,0.765282,-0.076998,0.362011,0.928953,-0.085177,0.462081,0.882717,-0.275857,0.462416,0.842647,-0.400372,0.520981,0.753807,-0.141728,0.353282,0.924711,-0.498947,0.533769,0.68273,-0.411786,0.732719,0.541734,-0.568804,0.51973,0.63741,-0.4756,0.72512,0.49794,-0.383465,0.77456,0.502945,-0.443892,0.814173,0.374218,-0.628742,0.691031,0.356517,-0.599597,0.775872,0.196081,-0.517045,0.807398,0.284066,-0.5403,0.709677,0.452101,-0.631672,0.515824,0.578661,-0.714133,0.541704,0.443281,-0.796228,0.566668,0.211737,-0.707266,0.679891,0.19367,-0.820246,0.569048,-0.057588,-0.738212,0.67449,0.009583,-0.662801,0.740593,0.110294,-0.696432,0.717124,0.02588,-0.659963,0.663411,-0.352519,-0.67983,0.681661,-0.270394,-0.701895,0.707511,-0.082125,-0.717795,0.676473,-0.164556,-0.779351,0.561968,-0.277108,-0.701498,0.55089,-0.452071,-0.601001,0.509598,-0.61565,-0.582904,0.59563,-0.55266,-0.486648,0.433027,-0.75869,-0.495682,0.455794,-0.73928,-0.619221,0.589648,-0.518509,-0.51912,0.40495,-0.752647,-0.092105,-0.252876,-0.963073,-0.039827,-0.368358,-0.9288,-0.340739,0.065615,-0.937834,-0.352611,0.177404,-0.91876,-0.339702,0.256966,-0.904721,-0.122044,-0.094882,-0.987945,0.063997,-0.439161,-0.896115,0.136784,-0.580309,-0.802789,0.149144,-0.645283,-0.749229,0.231147,-0.718131,-0.656362,0.203528,-0.634022,-0.745994,0.283486,-0.720023,-0.633381,0.190283,-0.738334,-0.646992,0.238044,-0.6863,-0.687246,0.285501,-0.72869,-0.622456,0.231544,-0.754753,-0.613758,0.171819,-0.741417,-0.64861,0.185736,-0.765282,-0.61626,0.286142,-0.795587,-0.533952,0.22486,-0.748558,-0.623737,0.439436,-0.838618,-0.321848,0.372234,-0.829737,-0.415845,0.235939,-0.677602,-0.696524,0.362407,-0.785913,-0.500931,0.533433,-0.841639,0.083804,0.528001,-0.846309,0.069948,0.486526,-0.857692,-0.166204,0.494552,-0.861202,-0.1171,0.531754,-0.842586,-0.085055,0.551195,-0.831172,0.072848,0.540208,-0.825617,0.162664,0.534593,-0.823634,0.189245,0.523789,-0.818354,0.236427,0.526475,-0.811945,0.251991,0.532212,-0.822626,0.199927,0.522202,-0.80987,0.267129,0.515763,-0.725394,0.455763,0.48912,-0.768975,0.411542,0.507157,-0.798944,0.323191,0.52147,-0.787744,0.32786,0.509659,-0.78518,0.351665,0.487808,-0.699698,0.521928,0.438765,-0.556719,0.705344,0.489425,-0.600269,0.632527,0.350749,-0.371075,0.859798,0.423383,-0.404065,0.810816,0.465865,-0.697562,0.544359,0.427198,-0.568224,0.703238,0.198309,0.061403,0.978179,0.241493,-0.140721,0.960112,0.357677,-0.38023,0.852901,0.322214,-0.172277,0.930845,0.236641,-0.170171,0.956542,0.114597,0.024751,0.993072,-0.010834,0.203528,0.979003,0.058351,0.27781,0.958831,0.084414,0.122471,0.988861,0.053224,0.484176,0.873318,0.006256,0.480361,0.87701,-0.050081,0.557726,0.828486,-0.027619,0.588275,0.808161,-0.106296,0.642293,0.759026,-0.082522,0.66213,0.744804,-0.052339,0.606708,0.793176,0.053957,0.476699,0.877377,-0.118473,0.664907,0.737449,0.150273,0.325144,0.933622,0.121158,0.37434,0.919309,0.215552,0.174688,0.960723,0.161504,0.265969,0.950316,0.055605,0.408094,0.911222,0.099612,0.332286,0.937864,-0.237617,0.860134,0.451277,-0.202216,0.843043,0.498337,-0.265328,0.927549,0.263039,-0.365581,0.910184,0.194617,-0.336253,0.940977,-0.03827,-0.457472,0.885098,-0.085238,-0.447493,0.880886,0.154027,-0.332926,0.844447,0.419538,-0.522691,0.844142,-0.118931,-0.205206,0.742759,0.637287,-0.134526,0.749321,0.648335,-0.153905,0.737785,0.657216,-0.427381,0.719352,-0.547563,-0.227119,0.781213,-0.581439,-0.203284,0.701682,-0.682852,-0.301309,0.635182,-0.711112,-0.323191,0.672872,-0.665395,-0.210852,0.59621,-0.774621,-0.472213,0.580767,-0.663076,-0.530747,0.673055,-0.51503,-0.38023,0.488266,-0.785455,-0.54619,0.762749,-0.346202,-0.484451,0.806055,-0.339854,-0.328288,0.88171,-0.338755,-0.164342,0.339427,-0.926145,-0.303995,0.349101,-0.88638,-0.125462,0.025483,-0.99176,-0.049318,0.071566,-0.996185,0.080996,-0.227668,-0.970336,0.096835,-0.199805,-0.975005,-0.084506,-0.031983,-0.99588,-0.204413,0.205512,-0.95706,0.054079,-0.275552,-0.959746,-0.294656,0.382214,-0.87582,-0.208136,0.516007,-0.830897,-0.351604,0.569048,-0.743309,0.302438,-0.570666,-0.763421,0.311838,-0.520859,-0.79461,0.388073,-0.663594,-0.639546,0.376751,-0.683859,-0.624775,0.451979,-0.751061,-0.481185,0.443159,-0.752037,-0.487808,0.366985,-0.6892,-0.624744,0.281289,-0.601276,-0.747887,0.440016,-0.751732,-0.491165,0.179479,-0.468734,-0.864895,0.214545,-0.409528,-0.886685,0.22187,-0.379437,-0.89819,0.549364,-0.821497,-0.152593,0.558397,-0.816462,-0.146855,0.586322,-0.802515,0.110294,0.577899,-0.808039,0.1142,0.567431,-0.728446,0.383862,0.552171,-0.727592,0.406995,0.568682,-0.812128,0.130406,0.543748,-0.826014,-0.148412,0.536546,-0.717185,0.444624,0.497696,-0.79812,-0.339488,0.500046,-0.795526,-0.342112,0.508927,-0.793542,-0.333506,0.445753,-0.576586,0.684683,0.480422,-0.57326,0.663717,0.45793,-0.524461,0.717765,0.41377,-0.552721,0.72335,0.414411,-0.397931,0.818445,0.381359,-0.483413,0.787927,0.397443,-0.533708,0.746422,0.421705,-0.543474,0.725761,0.385113,-0.512223,0.767632,0.469924,-0.597949,0.649281,0.490127,-0.622761,0.609821,0.515885,-0.628834,0.581713,0.264321,-0.110752,0.958037,0.236641,0.045717,0.970489,0.151463,0.228523,0.961669,0.201239,0.113285,0.972961,0.277993,-0.031495,0.960051,0.328715,-0.255928,0.909085,0.36195,-0.421491,0.831446,0.33314,-0.334635,0.881466,0.339335,-0.195044,0.920194,-0.284219,-0.481521,0.829066,-0.339274,-0.297159,0.892483,-0.272439,-0.223273,0.935881,-0.211707,-0.414716,0.884945,-0.069338,-0.665151,0.743461,-0.182012,-0.711966,0.678182,-0.134587,-0.723869,0.676626,-0.176122,-0.474349,0.862514,0.009217,-0.722953,0.690786,0.004273,-0.469069,0.883114,-0.187445,-0.29313,0.937498,0.000946,-0.284951,0.958525,-0.999146,-0.009613,0.039674,-0.919004,-0.385571,0.081912,-0.919553,-0.351756,0.174963,-0.990356,-0.015015,0.137669,-0.892544,-0.337382,0.299142,-0.962767,-0.014161,0.269936,-0.669454,-0.730918,0.132511,-0.713828,-0.666341,0.2154,-0.755882,-0.587848,0.288186,-0.954527,0.283608,0.091769,-0.950133,0.311624,0.008972,-0.850398,0.522477,0.061403,-0.825312,0.564623,-0.007019,-0.938871,0.278298,0.202551,-0.870022,0.474227,0.134709,-0.940397,0.335856,-0.053224,-0.999237,-0.002197,-0.038575,-0.807092,0.588366,-0.048585,-0.912595,-0.408795,0.003296,-0.635914,-0.768914,0.065767,0.731803,0.04825,0.679769,0.701498,0.287973,0.651875,0.62859,0.279366,0.725791,0.649678,0.053682,0.758293,0.512192,0.291665,0.807794,0.521317,0.056795,0.851466,0.593768,0.560625,0.577105,0.541826,0.524735,0.656514,0.486953,0.473617,0.733848,0.6274,-0.187597,0.75573,0.707053,-0.213569,0.674123,0.518509,-0.488266,0.701926,0.571764,-0.555345,0.603839,0.505692,-0.192724,0.840907,0.466842,-0.411115,0.782952,0.766503,-0.241218,0.595203,0.79281,0.038789,0.608203,0.613178,-0.597369,0.516831,0.754021,0.291177,0.588763,0.63155,0.569842,0.525712,0.195135,0.9241,0.328471,0.038911,0.968505,0.245888,0.002594,0.93289,0.360057,0.17011,0.878018,0.44731,-0.049196,0.78753,0.614246,0.137822,0.722648,0.677297,-0.061495,0.977447,0.201972,-0.101718,0.944914,0.311075,-0.169439,0.809626,0.561937,0.371288,0.744163,0.555254,0.399152,0.797754,0.451888,0.344798,0.601642,0.720511,0.423017,0.805231,0.415418,0.216102,0.925718,0.310343,0.059725,0.969054,0.239418,-0.046327,0.979247,0.197272,-0.220283,0.966277,0.133244,-0.302316,0.948424,0.095065,-0.344859,0.918973,0.191046,-0.261574,0.936247,0.234413,-0.441237,0.795526,0.415204,-0.350017,0.810846,0.469008,-0.385174,0.920957,0.058504,-0.430189,0.890591,0.147465,-0.531785,0.767785,0.357311,-0.180334,0.944395,0.27485,-0.139561,0.975555,0.169622,-0.259224,0.81521,0.517869,-0.131932,0.97766,0.163427,-0.218696,0.967711,0.125187,-0.303964,0.94882,0.085574,-0.386242,0.92111,0.048219,-0.562029,0.827082,0.004791,-0.680441,0.732719,-0.007874,-0.727775,0.683279,0.058535,-0.614093,0.785363,0.077761,-0.807367,0.5627,0.177496,-0.716697,0.655507,0.237953,-0.516587,0.8493,0.108493,-0.468459,0.883023,0.02765,-0.622059,0.723808,0.298502,-0.46617,0.884518,0.016327,-0.553026,0.833064,-0.010163,-0.663015,0.747856,-0.032319,0.1077,0.060945,0.992309,-0.093539,0.067782,0.993286,-0.078677,-0.376904,0.92288,0.105869,-0.33195,0.937315,-0.052522,-0.742424,0.667837,0.104282,-0.680105,0.725639,-0.234352,0.077883,0.968993,-0.198279,-0.393109,0.897824,-0.132084,-0.76281,0.632923,0.322336,-0.255776,0.911374,0.332224,0.058443,0.941374,0.307627,-0.556597,0.771722,0.334544,0.351085,0.874508,0.114078,0.42616,0.897397,-0.087313,0.47792,0.87402,-0.224708,0.505356,0.833094,-0.448378,0.087252,0.889553,-0.54796,0.080721,0.832575,-0.458388,-0.400464,0.79339,-0.374523,-0.398328,0.837275,-0.285501,-0.770257,0.570208,-0.232429,-0.770379,0.593677,-0.639393,0.066378,0.765984,-0.535752,-0.405591,0.740562,-0.337413,-0.770379,0.540941,-0.287027,-0.396924,0.871792,-0.34312,0.085849,0.935331,-0.180914,-0.768609,0.613575,-0.329203,0.517991,0.789453,-0.431867,0.518876,0.737693,-0.531571,0.50792,0.677786,-0.626179,0.485519,0.610034,-0.823023,0.018525,0.567675,-0.906064,-0.002106,0.423048,-0.81814,-0.370495,0.43968,-0.716727,-0.402112,0.569689,-0.641957,-0.677206,0.359447,-0.507584,-0.741539,0.438673,-0.617481,-0.410901,0.670705,-0.729301,0.04355,0.68276,-0.401959,-0.767266,0.49968,-0.718589,0.447737,0.53209,-0.810175,0.392621,0.435255,-0.888394,0.326701,0.322489,0.105564,-0.941801,0.319071,-0.010346,-0.969085,0.246406,0.00235,-0.984558,0.174963,0.116214,-0.964324,0.237831,-0.046449,-0.973327,0.224586,-0.031434,-0.98703,0.157262,0.337291,-0.868099,0.364116,0.319987,-0.830988,0.45497,0.305979,-0.74517,0.592486,0.101382,-0.873318,0.476455,-0.028077,-0.918241,0.394971,-0.075533,-0.928129,0.364483,-0.058016,-0.973846,0.219611,-0.067171,-0.973968,0.216346,-0.036164,-0.987121,0.155766,-0.033876,-0.98706,0.15656,-0.078127,-0.974181,0.211737,-0.039094,-0.987182,0.154546,-0.032289,-0.98706,0.157018,-0.051088,-0.973662,0.222175,-0.092257,-0.930021,0.355632,-0.112674,-0.930723,0.34788,-0.13654,-0.930692,0.339274,-0.162542,-0.930601,0.327891,-0.197119,-0.962188,0.187902,-0.379986,-0.909543,0.168249,-0.324595,-0.939085,0.112674,-0.144993,-0.979949,0.136448,-0.063478,-0.986755,0.149144,-0.108921,-0.973235,0.202277,-0.20658,-0.928129,0.30961,-0.308329,-0.907712,0.284463,-0.482284,-0.838588,0.253243,-0.709494,0.659474,0.248299,-0.613575,0.680227,0.400952,-0.544481,0.723777,0.423841,-0.749321,0.636647,0.182043,-0.669698,0.667745,0.32487,-0.655965,0.661031,0.364299,-0.744621,0.631764,0.215339,-0.569994,0.686666,0.451186,-0.486373,0.704947,0.51616,-0.438093,0.690634,0.575396,-0.544481,0.675253,0.497513,-0.834925,0.416913,-0.359203,-0.855678,0.392865,-0.336802,-0.825739,0.325175,-0.460829,-0.809198,0.365062,-0.460311,-0.830683,0.552416,-0.068972,-0.839534,0.541795,-0.040193,-0.860378,0.472945,-0.189856,-0.844508,0.489669,-0.216834,-0.817774,0.575182,-0.018555,-0.758782,0.650685,-0.028138,-0.786462,0.586688,-0.192907,-0.845912,0.504288,-0.173467,-0.490341,0.221015,-0.843013,-0.487014,0.146397,-0.861019,-0.344157,0.111576,-0.932218,-0.353465,0.184484,-0.917051,-0.752068,0.331492,-0.569597,-0.760247,0.273232,-0.589343,-0.641621,0.209815,-0.737724,-0.639607,0.277963,-0.716636,-0.746757,0.250832,-0.615925,-0.711386,0.275246,-0.646626,-0.630238,0.152684,-0.761223,-0.632313,0.167669,-0.75631,0.158971,-0.070223,-0.984771,0.191809,-0.124088,-0.97354,0.366741,-0.229163,-0.901639,0.355663,-0.204138,-0.912015,-0.213569,0.12946,-0.968291,-0.195563,0.062716,-0.978668,-0.007294,-0.024567,-0.999664,-0.036988,0.036103,-0.998657,-0.202918,-0.032685,-0.978637,-0.277566,-0.17246,-0.945067,-0.107273,-0.295816,-0.949187,-0.013916,-0.126713,-0.991821,0.653371,-0.519059,-0.551012,0.573687,-0.475326,-0.667013,0.482894,-0.622303,-0.616047,0.662099,-0.511795,-0.54741,0.605884,-0.447645,-0.657613,0.606769,-0.43086,-0.667928,0.673666,-0.486251,-0.556475,0.508316,-0.342174,-0.790246,0.502426,-0.336772,-0.796289,0.626179,0.495468,0.601947,0.699881,0.490585,0.519059,0.464522,0.578295,0.670644,0.917753,0.310709,0.247261,0.950224,0.260079,0.171514,0.844417,0.402326,0.353679,0.78927,0.426954,0.441267,0.977813,0.148106,0.148076,0.980865,-0.069002,0.181951,0.923551,0.087741,0.373272,0.896634,0.299997,0.325541,-0.70452,0.685507,0.18363,-0.755028,0.649892,0.086703,-0.743553,0.665822,0.06122,-0.625202,0.746483,0.227668,-0.682669,0.720664,0.120701,-0.720115,0.685812,0.105228,-0.664388,0.718375,0.206183,-0.724815,0.688681,0.016907,-0.749657,0.660695,-0.03827,-0.782647,0.61977,-0.057649,-0.758019,0.652181,0.005737,-0.828639,0.407331,-0.383953,-0.773827,0.390454,-0.498672,-0.79339,0.427015,-0.433729,-0.751457,0.466506,-0.466506,-0.688467,0.414838,-0.594897,-0.735954,0.389813,-0.553484,-0.794031,0.428877,-0.430738,-0.591235,0.327616,-0.73693,-0.454604,0.19248,-0.869625,-0.504501,0.176305,-0.84518,-0.641987,0.31138,-0.700613,0.013276,-0.376598,-0.926267,0.026765,-0.36256,-0.931547,0.265969,-0.597247,-0.756645,0.294229,-0.642781,-0.707266,-0.359966,-0.005371,-0.93292,-0.336314,-0.003296,-0.94171,-0.165838,-0.176153,-0.970275,-0.186499,-0.183691,-0.965117,-0.292947,0.017457,-0.955962,-0.246529,0.05945,-0.967284,-0.09769,-0.111454,-0.988952,-0.129063,-0.15183,-0.979919,0.436171,-0.898465,0.049806,0.432203,-0.899258,-0.06708,0.423658,-0.89584,0.133946,0.404187,-0.887997,0.219184,0.473922,-0.818751,-0.324046,0.426923,-0.781457,-0.45497,0.44792,-0.862362,-0.235878,0.474013,-0.874447,-0.10303,0.402448,-0.72219,-0.562517,0.396283,-0.652333,-0.646046,0.459365,-0.760063,-0.459578,0.442732,-0.82284,-0.356212,0.365032,-0.885433,0.287637,0.367321,-0.887295,0.278817,0.32075,-0.940489,0.112094,0.324534,-0.935179,0.141636,0.382641,-0.870785,0.308603,0.405408,-0.874599,0.265847,0.385083,-0.868252,0.312754,0.374981,-0.866909,0.328349,0.431837,-0.877926,0.206702,0.366008,-0.910611,0.191778,0.404981,-0.872768,0.272469,-0.359905,-0.912229,-0.195624,-0.244026,-0.968871,0.041139,-0.710715,-0.673055,0.204505,-0.76397,-0.6451,-0.013459,0.218909,-0.88525,-0.410291,0.354808,-0.921445,-0.158147,0.079287,-0.99469,-0.065127,-0.056551,-0.949156,-0.30958,0.488174,-0.866543,0.103763,0.622944,-0.61269,0.486312,0.410535,-0.659566,0.629597,0.222999,-0.954192,0.199316,0.253304,0.624409,0.738853,0.208167,0.592273,0.778344,0.025544,0.53264,0.845912,-0.000916,0.407422,0.913236,0.035737,0.519608,0.853633,0.082583,0.604358,0.792383,-0.088229,0.256905,0.962371,-0.287454,0.166906,0.943114,-0.233345,0.321757,0.9176,-0.061434,0.41026,0.909879,-0.643422,-0.731376,0.225959,-0.4944,-0.74395,0.449538,-0.688711,-0.416028,0.593768,-0.815241,-0.435438,0.381726,-0.27369,-0.955748,-0.107639,-0.121982,-0.985961,0.113956,-0.321512,-0.894833,0.309641,-0.473037,-0.877041,0.083621,0.057833,-0.929624,0.363903,0.443495,-0.567827,0.693411,0.238472,-0.473159,0.848048,-0.135685,-0.820429,0.555376,0.574175,-0.760216,-0.303842,0.739128,-0.599475,-0.307016,0.685598,-0.545,-0.482559,0.718711,-0.669424,-0.187841,0.835566,-0.506119,-0.21366,0.789575,-0.567461,-0.233497,0.651845,-0.728721,-0.209754,0.915555,-0.351177,-0.195868,0.989746,-0.129368,-0.060427,0.9953,-0.066805,-0.069674,0.894406,-0.388043,-0.222266,-0.44261,0.391369,-0.806757,-0.546648,0.483047,-0.68395,-0.594775,0.389172,-0.70336,-0.466567,0.316446,-0.825892,-0.471541,0.406415,-0.782586,-0.568651,0.505448,-0.648915,-0.542039,0.513535,-0.66512,-0.444685,0.415845,-0.793268,-0.633503,0.574725,-0.51796,-0.672018,0.611682,-0.417371,-0.651814,0.617267,-0.440565,-0.611316,0.583758,-0.534318,0.066591,-0.115879,-0.990997,-0.093204,0.044343,-0.994629,-0.069369,0.061464,-0.995666,0.105686,-0.068178,-0.992035,0.070864,-0.230354,-0.970519,-0.086917,-0.050935,-0.994903,-0.090823,0.005219,-0.995849,0.06711,-0.168249,-0.983428,-0.224586,0.120304,-0.966979,-0.354595,0.27958,-0.892209,-0.341044,0.304453,-0.88934,-0.22599,0.167425,-0.959624,0.601672,-0.623371,-0.499374,0.54619,-0.55443,-0.627888,0.583331,-0.492172,-0.646107,0.637898,-0.558824,-0.529832,0.529069,-0.759545,-0.378338,0.488296,-0.686483,-0.538743,0.514817,-0.618549,-0.593554,0.563311,-0.694143,-0.448103,0.400403,-0.576464,-0.712272,0.251137,-0.415265,-0.874325,0.252724,-0.35139,-0.901456,0.414533,-0.509049,-0.754326,0.201941,-0.876431,0.437086,0.497971,-0.767846,0.402966,0.353587,-0.444349,0.823084,0.254006,-0.960112,-0.116764,0.510361,-0.843349,-0.168065,0.532395,-0.837855,0.120548,0.257668,-0.95352,0.156102,0.709616,-0.662679,-0.239265,0.918973,-0.262001,-0.294595,0.967101,-0.253761,-0.017121,0.742332,-0.667318,0.059694,-0.836695,0.528245,-0.144383,-0.851009,0.471908,-0.230262,-0.822596,0.508377,-0.254646,-0.772515,0.628224,-0.092135,-0.791864,0.578295,-0.196173,-0.828547,0.518418,-0.211493,-0.809107,0.575854,-0.117161,-0.794549,0.527055,-0.301401,-0.78341,0.495193,-0.3755,-0.820185,0.445387,-0.358959,-0.829249,0.468703,-0.304331,-0.731651,0.674703,-0.096927,-0.716361,0.692007,-0.089022,-0.705679,0.708487,0.001343,-0.716178,0.697745,0.01413,-0.700766,0.606586,-0.375408,-0.684194,0.638997,-0.351421,-0.710501,0.668355,-0.220099,-0.727195,0.64388,-0.2378,-0.702048,0.634175,-0.323832,-0.746147,0.591662,-0.305185,-0.754173,0.62859,-0.189947,-0.723777,0.660573,-0.199377,-0.571764,0.770531,0.281594,-0.641255,0.748253,0.169958,-0.659474,0.738548,0.140049,-0.596759,0.762535,0.249763,-0.522813,0.772088,0.361278,-0.608081,0.754753,0.24604,-0.617023,0.759636,0.20542,-0.540849,0.778252,0.318949,-0.676778,0.726585,0.11832,-0.675893,0.732017,0.085391,-0.26307,0.771752,0.578906,-0.446791,0.787011,0.425336,-0.488296,0.788781,0.373241,-0.174902,0.745262,0.643391,-0.344646,0.791223,0.505112,-0.390576,0.791955,0.469283,-0.214789,0.758171,0.61565,-0.476974,0.789758,0.385662,-0.566363,0.767693,0.299753,-0.606708,0.747002,0.271798,-0.520646,0.777764,0.352062,-0.885647,-0.360424,0.292734,-0.763909,-0.337931,0.549699,-0.847163,0.022217,0.530839,-0.365001,-0.927,-0.08594,-0.202185,-0.949309,0.240608,-0.559984,-0.690298,0.458113,-0.704794,-0.696585,0.134129,-0.041078,-0.824732,0.564013,0.238746,-0.450423,0.860286,-0.047639,-0.245643,0.968169,-0.365764,-0.559008,0.744072,0.771599,-0.635243,0.032167,0.565264,-0.820551,0.084567,0.368786,-0.808252,-0.458968,0.715964,-0.419996,0.557634,0.524461,-0.593005,0.61092,0.569536,-0.732902,0.372021,0.766594,-0.552721,0.326731,0.235267,-0.708701,0.66509,-0.347148,-0.71041,0.61214,-0.343883,-0.854579,0.38908,0.285836,-0.857295,0.428114,-0.78573,0.608448,-0.11124,-0.727012,0.684744,0.050539,-0.902188,0.415815,-0.114566,-0.863735,0.487533,0.127445,-0.782159,0.618549,0.07477,-0.82754,0.544267,-0.137486,-0.782495,0.487564,0.387249,-0.435682,0.366619,0.822016,-0.344157,0.468001,0.813929,-0.722282,0.623646,0.298868,-0.437635,-0.324564,-0.838496,-0.469558,-0.304086,-0.828852,-0.788324,-0.461837,0.406476,-0.318857,-0.305826,-0.897092,-0.435896,-0.198096,-0.877895,-0.414655,-0.246681,-0.875881,-0.368877,-0.307688,-0.877071,-0.578326,-0.130955,-0.8052,-0.837916,0.174261,-0.517167,-0.77633,0.203162,-0.596637,-0.475234,-0.204749,-0.855678,0.541124,-0.837031,-0.080935,0.537736,-0.831904,-0.136845,0.3755,-0.920011,-0.111881,0.742912,-0.669362,-0.001038,0.696493,-0.691244,-0.192419,0.620258,-0.767846,-0.160131,0.651112,-0.757683,-0.043977,0.574389,-0.741997,-0.345683,0.132206,-0.659902,-0.739616,0.093539,-0.7163,-0.691458,0.536363,-0.805933,-0.250496,0.237312,0.11832,0.964171,0.276009,0.058351,0.959349,-0.743461,-0.520127,0.420362,0.067232,0.149052,0.986511,0.263649,0.086215,0.960723,0.310404,0.076968,0.947478,0.200751,0.135472,0.970214,0.397595,-0.030305,0.917051,0.69808,-0.427564,0.574297,0.648915,-0.500412,0.573107,0.369579,-0.020203,0.928953,0.641987,-0.692312,0.329386,0.622028,-0.730705,0.281289,0.481948,-0.814081,0.324015,0.483963,-0.818812,0.308695,0.770287,-0.581622,0.26133,0.728843,-0.679434,0.084262,0.677541,-0.707694,0.200171,0.714011,-0.623737,0.317911,0.600055,-0.796411,-0.074892,0.274728,-0.758629,-0.590747,0.252052,-0.818628,-0.516007,0.571429,-0.815058,0.095309,-0.012543,0.153752,0.988006,0.112247,0.107089,0.987884,-0.237007,0.167791,0.956877,0.037904,0.045839,0.998199,0.10184,0.07416,0.992004,-0.123753,0.16541,0.978393,0.236946,-0.079989,0.9682,0.59801,-0.371136,0.710349,0.550005,-0.389294,0.738823,0.231056,-0.061953,0.970946,-0.781823,0.484451,-0.392468,-0.79809,0.547044,-0.252449,-0.87112,0.336375,-0.357738,-0.897946,0.405499,-0.170904,-0.836055,0.505295,-0.21363,-0.806635,0.444777,-0.389172,-0.900571,0.431013,0.056307,-0.726707,0.378307,0.573382,-0.67748,0.436933,0.591662,-0.856807,0.515275,-0.017151,-0.243843,-0.432234,-0.868129,-0.286386,-0.404035,-0.86874,-0.766442,-0.542131,0.344401,-0.082736,-0.385113,-0.919126,-0.179327,-0.274422,-0.944731,-0.1854,-0.325327,-0.927244,-0.150243,-0.391186,-0.907926,-0.326395,-0.222053,-0.91876,-0.70275,0.094699,-0.7051,-0.613392,0.108036,-0.782311,-0.229835,-0.291879,-0.928404,-0.64629,0.741935,0.17832,-0.496445,0.791375,0.3567,-0.711936,0.666158,0.222144,-0.546709,0.67217,0.499222,-0.513047,0.759056,0.400678,-0.680624,0.717582,0.147496,-0.414045,0.612842,0.672994,-0.125706,0.449385,0.884426,-0.012268,0.529679,0.848079,-0.358074,0.717399,0.597552,-0.522568,-0.351207,-0.776879,-0.541673,-0.258827,-0.799707,-0.385449,-0.409864,-0.826655,-0.540574,-0.224616,-0.810724,-0.525986,-0.259194,-0.810022,-0.455885,-0.396405,-0.796869,-0.695669,-0.054109,-0.7163,-0.865596,0.375591,-0.331095,-0.847224,0.353771,-0.396252,-0.637837,-0.146062,-0.756188,0.48909,-0.864315,0.117222,0.40904,-0.909665,-0.07178,0.372631,-0.927885,-0.012665,0.604694,-0.796106,-0.023316,0.500351,-0.838374,-0.216193,0.449049,-0.883633,-0.132328,0.53618,-0.843165,0.039369,0.340861,-0.858882,-0.382244,-0.025178,-0.735008,-0.677572,-0.085635,-0.775506,-0.625477,0.317759,-0.907315,-0.275277,0.341716,0.256783,0.904019,0.461165,-0.001617,0.887295,0.368145,0.06238,0.927671,0.249763,0.165502,0.954039,0.475784,-0.044984,0.878384,0.520005,-0.055483,0.852321,0.358074,0.194372,0.913205,0.631397,-0.291086,0.718741,0.692434,-0.619861,0.369152,0.627247,-0.651906,0.426038,0.603381,-0.31016,0.734642,0.072726,-0.417982,-0.905515,-0.114322,-0.193793,-0.974334,-0.169408,-0.179724,-0.968993,0.019593,-0.393109,-0.919248,0.180578,-0.398602,-0.899136,-0.0665,-0.233894,-0.96997,-0.065249,-0.217902,-0.973754,0.144902,-0.428419,-0.891873,-0.307901,-0.084872,-0.9476,-0.622944,0.120396,-0.772912,-0.551073,0.209906,-0.807611,-0.270852,-0.026978,-0.962218,0.564379,-0.786065,0.252022,0.5992,-0.795251,0.092074,0.51915,-0.854427,0.01999,0.473556,-0.863704,0.172369,0.727683,-0.601062,0.330363,0.767754,-0.629383,0.119999,0.668325,-0.734794,0.115635,0.638325,-0.705893,0.306925,0.737632,-0.670095,-0.082675,0.534806,-0.631794,-0.561022,0.463912,-0.697409,-0.546251,0.647481,-0.758995,-0.0683,-0.19248,0.166143,0.967101,-0.068087,0.155583,0.985443,-0.431288,0.072787,0.899258,-0.197394,0.011292,0.980224,-0.112705,0.080813,0.990326,-0.316019,0.131809,0.939543,0.010559,-0.109409,0.993927,0.450728,-0.409558,0.793115,0.377148,-0.478835,0.792749,-0.011383,-0.076632,0.996979,-0.712607,0.401898,-0.574969,-0.807306,0.449995,-0.381726,-0.845546,0.250984,-0.471175,-0.9317,0.275063,-0.237129,-0.859249,0.398114,-0.321146,-0.763268,0.363384,-0.534165,-0.965484,0.256294,0.045717,-0.812586,0.193274,0.549821,-0.788965,0.291452,0.54088,-0.923734,0.37727,-0.066012,-0.975738,0.21424,-0.044862,-0.944365,0.322764,-0.062838,-0.873836,0.208411,-0.439253,-0.893124,0.10358,-0.437635,-0.840052,0.277352,0.46617,-0.800287,0.401593,0.445235,-0.909391,0.371899,0.186132,-0.944517,0.256996,0.204474,-0.770531,0.451308,0.450056,-0.79049,0.422468,0.443403,-0.899167,0.402631,0.171178,-0.892361,0.413343,0.181097,-0.050691,-0.229499,-0.971954,-0.095553,-0.261055,-0.96057,0.440992,-0.390576,-0.808039,0.445936,-0.304575,-0.841609,-0.615009,-0.055483,-0.786554,-0.633168,0.019715,-0.773736,-0.397198,-0.121769,-0.909604,-0.350414,-0.150395,-0.924406,-0.69158,0.056856,-0.720023,-0.702414,-0.016236,-0.71157,-0.526139,-0.134312,-0.839717,-0.495499,-0.099826,-0.862819,0.924223,-0.376782,-0.061769,0.857448,-0.443556,-0.260781,0.790948,-0.569536,-0.223579,0.85696,-0.514573,0.027497,0.974059,-0.18067,-0.136082,0.916074,-0.214789,-0.338542,0.894528,-0.328074,-0.303568,0.9494,-0.282571,-0.136937,0.809687,-0.269173,-0.521439,0.806574,-0.37376,-0.45793,0.004486,0.309824,0.950774,0.274697,0.179296,0.944639,0.20603,0.153691,0.966369,-0.033326,0.243599,0.969298,0.056398,0.172826,0.983306,0.375317,0.100101,0.921445,0.361553,0.126225,0.923765,0.053652,0.247169,0.967467,0.631184,0.032899,0.774926,0.928831,-0.078799,0.36198,0.922086,-0.158605,0.352886,0.621235,0.00824,0.783563,0.88287,-0.460128,0.093753,0.848689,-0.515122,-0.119724,0.790124,-0.611957,-0.034059,0.827662,-0.525315,0.197485,0.985778,-0.157628,0.058107,0.971892,-0.176183,-0.156011,0.917539,-0.364849,-0.157933,0.939299,-0.34196,0.027314,0.908963,-0.217597,-0.355541,0.585864,-0.277261,-0.761467,0.545244,-0.406629,-0.733024,0.859645,-0.388226,-0.332072,-0.275521,0.194067,0.941496,0.048036,0.0936,0.994446,0.006104,0.074801,0.997162,-0.312998,0.194311,0.929655,-0.177892,0.232246,0.956236,0.110965,0.188482,0.975768,0.105472,0.125858,0.986389,-0.205206,0.210974,0.955687,0.383801,0.130894,0.91406,0.817286,-0.032838,0.575243,0.803644,-0.208899,0.557176,0.384533,0.015259,0.922971,-0.941984,0.107089,-0.318094,-0.933683,0.139897,-0.329569,-0.77987,0.032472,-0.62508,-0.786767,0.004669,-0.617176,-0.954772,0.222266,0.197333,-0.952635,0.242744,0.18305,-0.975921,0.196753,-0.093905,-0.982299,0.172033,-0.073733,-0.964293,0.221168,0.145573,-0.947447,0.30195,0.105411,-0.955718,0.2566,-0.144017,-0.976928,0.171514,-0.12711,0.083926,-0.212867,-0.973449,0.070009,-0.295938,-0.952605,-0.492233,-0.102115,-0.864437,-0.485946,-0.103397,-0.867824,-0.221961,-0.202094,-0.953856,-0.218696,-0.165807,-0.961577,-0.506668,-0.145146,-0.849818,-0.486679,-0.168279,-0.857204,-0.258248,-0.26957,-0.927671,-0.262612,-0.254646,-0.930662,-0.537034,0.516251,0.667104,-0.687185,0.524155,0.502976,-0.763573,0.414136,0.495376,-0.612354,0.417249,0.671468,-0.450148,0.553728,0.700491,-0.612842,0.566301,0.551042,-0.62917,0.586352,0.510178,-0.472121,0.569659,0.672719,-0.788415,0.533036,0.306955,-0.903897,0.380718,-0.194861,-0.868923,0.443556,-0.21952,-0.775201,0.574236,0.263192,-0.448012,-0.366527,-0.815394,-0.405011,-0.216865,-0.88818,0.129978,-0.371624,-0.919218,0.055239,-0.514176,-0.855861,-0.914914,-0.056246,-0.39964,-0.886776,0.023713,-0.461531,-0.681021,-0.098636,-0.725547,-0.731346,-0.217444,-0.646382,-0.85638,0.041597,-0.514634,-0.566759,-0.071047,-0.820795,-0.638997,-0.077273,-0.765282,0.828516,-0.54912,-0.109592,0.902097,-0.386059,-0.192755,0.906522,-0.233314,0.351787,0.828608,-0.348796,0.437849,0.484451,-0.608722,-0.628254,0.584918,-0.436171,-0.683798,0.769494,-0.427808,-0.474136,0.685507,-0.611469,-0.395123,0.604724,-0.378521,-0.700705,0.75045,-0.443434,-0.490066,0.780816,-0.378491,-0.497024,0.746269,-0.122135,0.654286,0.480636,0.095401,0.8717,0.427564,0.077639,0.900632,0.730522,-0.073702,0.678884,0.642628,-0.122532,0.756279,0.39082,0.13187,0.910947,0.462447,0.137608,0.875881,0.713767,-0.112888,0.691183,0.166417,0.291726,0.941893,-0.173223,0.46437,0.868526,-0.182806,0.489792,0.852443,0.192846,0.319987,0.927549,0.23777,-0.283853,-0.928892,-0.082583,-0.264779,-0.960753,-0.111637,-0.247658,-0.962371,0.155126,-0.337962,-0.928251,0.268563,-0.196356,-0.943022,-0.060183,-0.247078,-0.967101,-0.055574,-0.255989,-0.965056,0.280221,-0.225105,-0.933134,-0.366008,-0.241249,-0.89877,-0.729576,-0.168218,-0.662862,-0.729484,-0.163854,-0.664022,-0.362621,-0.2378,-0.901059,0.907163,0.237861,0.347026,0.930784,0.1601,0.328623,0.675314,0.211005,0.706656,0.651936,0.30546,0.693991,0.976257,0.120792,-0.179662,0.981536,0.078829,-0.174139,0.986755,0.138371,0.084353,0.97705,0.189184,0.09772,0.98349,-0.043123,-0.175634,0.90466,-0.39259,-0.165593,0.936705,-0.335521,0.099948,0.997101,0.010895,0.075167,-0.394696,0.187933,0.89935,-0.112003,0.153081,0.981842,-0.203925,0.065157,0.976806,-0.479751,0.106693,0.870876,-0.315989,0.280129,0.90643,-0.010498,0.303385,0.952788,-0.026032,0.245155,0.969115,-0.328166,0.251656,0.910459,0.267373,0.316416,0.910154,0.258827,0.23368,0.937193,-0.936338,-0.060945,-0.345714,-0.937498,-0.067965,-0.341197,-0.989257,0.082614,0.120426,-0.98938,0.078646,0.122196,-0.992462,0.000641,-0.12241,-0.991821,0.013337,-0.126743,-0.988189,0.06418,0.13892,-0.985656,0.129337,0.108371,-0.981475,0.110935,-0.156041,-0.993286,0.000214,-0.115543,-0.96466,-0.261269,0.033326,-0.997711,-0.058718,0.033113,-0.923307,-0.103885,-0.369671,-0.894253,-0.295724,-0.335887,-0.831965,-0.206671,0.514847,-0.857845,-0.000732,0.51384,-0.963317,-0.034181,0.266152,-0.933927,-0.238807,0.265877,-0.858211,0.107822,0.501816,-0.854335,0.186224,0.485153,-0.960723,0.158879,0.227454,-0.965545,0.072817,0.249825,-0.101291,-0.296793,-0.949522,-0.086184,-0.134861,-0.987091,0.425306,-0.078677,-0.901608,0.403088,-0.205084,-0.891873,-0.628376,-0.358409,-0.69039,-0.637257,-0.1583,-0.754204,-0.370495,-0.161504,-0.91467,-0.375134,-0.348247,-0.859035,-0.624409,-0.064669,-0.778375,-0.626606,-0.05475,-0.777367,-0.350108,-0.10828,-0.930418,-0.349956,-0.095248,-0.931883,0.79049,-0.198309,-0.579424,0.916227,-0.209693,-0.341319,0.73159,-0.619617,-0.28428,0.612934,-0.595416,-0.519395,0.812189,-0.014618,-0.583148,0.935057,0.002167,-0.354411,0.93469,-0.029481,-0.354228,0.81402,-0.037996,-0.579577,0.993896,0.024079,-0.107486,0.931761,0.063509,0.357433,0.941496,-0.00943,0.336833,0.993469,-0.026215,-0.110782,0.712027,-0.207862,0.670644,0.477157,-0.195868,0.856685,0.655416,0.10773,0.74752,0.41142,0.143101,0.900113,0.447432,0.039521,0.893429,0.689688,0.014985,0.723899,0.079501,0.176092,0.98114,-0.491195,0.166692,0.854915,-0.478256,0.056307,0.87637,0.110874,0.067904,0.991485,0.93582,0.123936,-0.329905,0.984344,0.163244,-0.066195,0.912778,0.231422,-0.336528,0.955657,0.274392,-0.106662,0.948515,0.304025,-0.088626,0.910672,0.258705,-0.322031,0.942595,0.311014,0.121464,0.733573,0.362346,0.574908,0.735649,0.359447,0.574084,0.933439,0.331431,0.13712,0.422193,0.12009,0.898495,0.179418,0.0094,0.983703,0.334819,0.335673,0.880428,0.082492,0.287332,0.954253,0.113132,0.215735,0.969848,0.359294,0.298868,0.88406,-0.197974,0.21955,0.95529,-0.709952,0.027375,0.703696,-0.674154,-0.115665,0.729453,-0.167455,0.111789,0.979492,-0.730979,-0.625751,-0.272164,-0.861629,-0.39903,-0.313608,-0.656728,-0.384747,-0.648579,-0.540971,-0.599445,-0.589892,-0.80636,-0.534013,0.254067,-0.923368,-0.317087,0.216315,-0.922391,-0.379803,-0.07007,-0.799127,-0.600482,-0.027955,-0.967345,-0.156224,0.199469,-0.980804,0.056063,0.186621,-0.996277,0.00354,-0.085788,-0.971129,-0.221351,-0.088717,0.170934,-0.2631,-0.949492,0.094241,-0.128788,-0.987182,0.608234,0.091647,-0.788415,0.628834,-0.009186,-0.777459,-0.258156,-0.513871,-0.818079,-0.365032,-0.303598,-0.880062,-0.158544,-0.229194,-0.960356,-0.05649,-0.402936,-0.91345,-0.42964,-0.191229,-0.882504,-0.501511,-0.125645,-0.855953,-0.242775,-0.146977,-0.958861,-0.201605,-0.16187,-0.965972,-0.175542,-0.634541,-0.752678,0.098727,-0.536943,-0.837794,-0.316294,-0.289499,-0.903378,-0.023041,-0.238716,-0.970794,0.014252,-0.341868,-0.939604,-0.271981,-0.420454,-0.865566,0.282296,-0.127964,-0.950743,0.735679,0.110172,-0.668264,0.754143,0.074892,-0.652394,0.301584,-0.201514,-0.931883,0.887845,0.205145,0.411847,0.863643,0.358806,0.354015,0.665609,0.380261,0.642109,0.704611,0.233558,0.670003,0.984985,0.141423,-0.098849,0.948943,0.280404,-0.144444,0.934233,0.33311,0.127232,0.966491,0.181097,0.181768,0.93701,0.304819,-0.170446,0.954436,0.232215,-0.187323,0.949767,0.2978,0.096103,0.927366,0.359874,0.102176,0.459792,0.187353,0.868007,0.216163,0.125187,0.968261,0.377697,-0.255898,0.889828,0.566485,-0.153691,0.809595,0.315073,0.426679,0.847713,0.056215,0.397015,0.916074,0.120121,0.318735,0.940184,0.374859,0.363201,0.852931,-0.249519,0.329203,0.910672,-0.728721,0.119755,0.674245,-0.664205,0.001434,0.74752,-0.187811,0.231025,0.954619,-0.779778,-0.621662,-0.073733,-0.891903,-0.408124,-0.194647,-0.663137,-0.454207,-0.594867,-0.567003,-0.667379,-0.482742,-0.780633,-0.436201,0.447523,-0.908414,-0.252693,0.332987,-0.933836,-0.351024,0.068422,-0.814081,-0.548875,0.189703,-0.961272,-0.114078,0.250832,-0.986572,0.01236,0.162786,-0.994018,-0.056185,-0.093387,-0.978759,-0.20426,-0.015931,0.381848,0.436598,0.81457,0.183081,0.539537,0.821772,0.116581,0.592364,0.797143,0.455184,0.329203,0.827265,0.262215,0.479507,0.837428,0.227699,0.499771,0.835658,0.422163,0.373821,0.825831,0.082522,0.603015,0.79342,-0.107608,0.706595,0.699362,-0.156743,0.691,0.705618,0.040071,0.599658,0.799219,-0.355968,0.740349,0.570208,-0.481918,0.767144,0.423322,-0.288858,0.769616,0.569414,-0.445998,0.790338,0.420026,-0.453108,0.788476,0.415876,-0.325785,0.756554,0.566973,-0.562944,0.789209,0.245399,-0.638112,0.768303,0.049745,-0.630543,0.773034,0.06943,-0.553484,0.793603,0.252632,-0.699026,0.710807,-0.077853,-0.717795,0.64922,-0.251503,-0.693472,0.65923,-0.290658,-0.676717,0.727073,-0.115513,-0.676229,0.568133,-0.468917,-0.665548,0.458998,-0.588519,-0.670247,0.456191,-0.585315,-0.687674,0.569903,-0.449721,-0.547777,-0.016816,-0.836451,-0.565813,-0.024384,-0.824152,-0.475143,-0.206488,-0.855312,-0.455672,-0.166875,-0.874355,-0.660817,0.298074,-0.688803,-0.651479,0.315165,-0.690054,-0.620991,0.152867,-0.768731,-0.615589,0.144047,-0.774773,-0.64687,0.462996,-0.605914,-0.655019,0.332774,-0.678335,-0.035615,-0.584307,-0.810724,-0.065249,-0.65801,-0.750145,0.089114,-0.760002,-0.643757,0.11005,-0.674459,-0.730033,-0.3361,-0.312723,-0.888363,-0.351207,-0.378399,-0.85641,-0.212104,-0.52739,-0.822687,-0.190435,-0.456343,-0.869167,0.251015,-0.73571,-0.629017,0.394543,-0.767937,-0.504532,0.389904,-0.832881,-0.392743,0.245735,-0.819147,-0.518235,0.604419,-0.768822,-0.208594,0.507462,-0.813685,-0.283486,0.693442,-0.671621,-0.26075,0.797388,-0.582659,-0.156957,0.821894,-0.498795,-0.275002,0.800256,-0.598407,-0.037873,0.702506,-0.697378,-0.141789,0.905759,-0.388989,0.168035,0.872036,-0.48381,0.073794,0.933622,-0.236824,0.268715,0.892331,-0.119114,0.435347,0.870266,-0.208502,0.446242,0.905484,-0.306742,0.293161,0.634724,0.179449,0.751579,0.796716,-0.055239,0.601764,-0.859737,-0.089969,-0.50267,-0.906064,-0.155675,-0.393384,-0.979308,-0.095157,0.178533,-0.98941,-0.014374,0.144261,-0.789636,-0.289254,-0.541093,-0.672445,-0.361705,-0.645711,-0.77456,-0.209143,-0.596881,-0.827204,-0.125492,-0.547685,-0.521317,-0.36903,-0.769402,-0.441511,-0.236732,-0.865444,-0.648427,-0.204657,-0.733207,-0.692801,-0.28074,-0.664205,0.808069,-0.391247,-0.440321,0.608356,-0.47148,-0.638417,0.916013,-0.280435,-0.286752,0.649495,-0.37672,-0.660451,0.609455,-0.459334,-0.646168,0.834162,-0.402509,-0.376965,0.326212,-0.415662,-0.848964,-0.133763,-0.42909,-0.893277,-0.241981,-0.525376,-0.815699,0.306253,-0.511795,-0.802637,0.528489,0.306406,0.791681,0.737266,0.207953,0.64275,0.746025,0.340587,0.572161,0.559221,0.418653,0.715506,0.400464,0.193945,0.895535,0.774132,0.071841,0.628895,0.751854,0.097537,0.652028,0.488632,0.220283,0.844203,0.916837,-0.015015,0.398968,0.98056,-0.130589,0.146428,0.96997,-0.231239,0.075228,0.890957,-0.043092,0.45201,-0.540544,0.149419,0.827906,-0.425886,0.267678,0.864254,-0.721671,0.051912,0.690237,-0.655629,0.119877,0.745476,-0.550554,0.135777,0.823664,-0.695517,0.036439,0.717551,-0.614734,0.148564,0.77459,-0.190344,0.219703,0.956786,0.042451,0.287667,0.956755,-0.395856,0.227515,0.889645,-0.486435,-0.320536,-0.812738,-0.708304,-0.26194,-0.655477,-0.492935,-0.637684,-0.591876,-0.717124,-0.528794,-0.453902,-0.733909,-0.380566,-0.562578,-0.511246,-0.481765,-0.711661,-0.872738,-0.405805,-0.271279,-0.975066,-0.218848,0.035951,-0.995605,-0.093081,-0.007202,-0.889828,-0.270943,-0.367077,0.905881,-0.194494,-0.376171,0.709525,-0.342784,-0.61568,0.701498,-0.255745,-0.665151,0.930143,-0.115452,-0.348521,0.774621,-0.544755,-0.321177,0.547746,-0.665883,-0.506485,0.662465,-0.476333,-0.578082,0.86227,-0.34315,-0.372448,0.286203,-0.727653,-0.623341,-0.100467,-0.707633,-0.699362,-0.052614,-0.569231,-0.82046,0.415052,-0.559771,-0.717154,0.375835,0.343822,0.8605,0.645527,0.279763,0.710624,0.72689,0.232246,0.64626,0.331584,0.331034,0.883419,0.26252,0.044923,0.963866,0.50325,0.006989,0.864071,0.588702,0.201605,0.782769,0.360637,0.236885,0.902097,0.721274,-0.049745,0.690817,0.943754,-0.269814,0.190985,0.988556,-0.082949,0.12598,0.785913,0.13947,0.602374,-0.881283,0.232582,0.411328,-0.729545,0.324015,0.602252,-0.699057,0.316233,0.641285,-0.773186,0.271676,0.572985,-0.911771,-0.126377,0.390759,-0.77633,-0.062563,0.627155,-0.778832,0.129032,0.613758,-0.922849,0.045351,0.382458,-0.56148,-0.000122,0.827479,-0.147526,0.080844,0.985717,-0.109409,0.26075,0.959166,-0.563311,0.197668,0.80224,-0.198675,0.681265,0.70452,-0.005066,0.65157,0.758538,-0.394696,0.720084,0.570635,-0.253059,0.708853,0.658345,-0.145054,0.686361,0.712607,-0.313669,0.705313,0.635701,-0.108951,0.67861,0.726341,-0.006134,0.62624,0.779565,0.0965,0.632099,0.768822,0.014496,0.646931,0.762383,-0.165746,0.714164,0.680044,-0.172765,0.708975,0.683706,-0.002106,0.603351,0.797449,0.000397,0.604266,0.796777,-0.381298,0.78637,0.485977,-0.401563,0.789727,0.463729,-0.299661,0.76635,0.568194,-0.28135,0.765191,0.579028,-0.427076,0.787744,0.443892,-0.447493,0.790582,0.41792,-0.323801,0.777215,0.539476,-0.318491,0.765191,0.559465,0.481277,0.162694,0.861293,0.328104,0.345622,0.879116,0.268715,0.280496,0.921445,0.387555,0.053957,0.920255,0.445235,0.167669,0.879543,0.321757,0.329173,0.887753,0.351543,0.345561,0.870022,0.499344,0.167669,0.850002,0.178869,0.458846,0.870296,0.191748,0.469497,0.861843,-0.018921,-0.982421,0.185583,0.169256,-0.90994,0.378552,0.254738,-0.920835,0.295144,0.116184,-0.983642,0.137425,-0.058901,-0.971648,0.228919,0.063387,-0.869778,0.489303,0.159337,-0.874905,0.45732,-0.019623,-0.973144,0.229316,0.319651,-0.601123,0.732414,0.501663,-0.177038,0.846736,0.581866,-0.199744,0.788354,0.420209,-0.628254,0.654744,0.37431,-0.186956,0.908231,0.29899,0.093905,0.949614,0.297891,-0.083102,0.950957,0.223395,0.195044,0.954985,0.247383,0.131474,0.959929,0.319041,-0.152409,0.935392,0.135899,0.426283,0.894314,-0.000916,0.625904,0.77987,-0.007355,0.620289,0.784295,0.159307,0.379833,0.911222,0.297525,-0.954283,-0.028291,0.357341,-0.931883,0.061983,0.242286,-0.96765,0.070101,0.312998,-0.927396,0.204749,0.33668,-0.931883,0.134861,0.280892,-0.959502,0.020203,0.384503,-0.825892,0.412305,0.393353,-0.509507,0.765252,0.402905,-0.568621,0.717124,0.399365,-0.849818,0.343944,0.264962,-0.963805,-0.029237,0.240852,-0.966216,-0.091525,0.219611,-0.969543,-0.10828,0.186193,-0.975249,-0.118992,0.219794,-0.970946,-0.094577,0.250587,-0.966094,-0.061769,0.186285,-0.980621,-0.060121,0.200262,-0.979522,-0.020142,0.247444,-0.967864,-0.044191,0.228889,-0.971343,-0.063631,-0.089755,-0.989135,-0.116306,-0.084078,-0.981445,-0.172246,-0.102329,-0.994598,-0.015625,-0.099185,-0.995056,0.002655,-0.033265,-0.988067,-0.150304,-0.015778,-0.986511,-0.162877,-0.043184,-0.980621,-0.190985,-0.058321,-0.983306,-0.172185,0.119755,-0.97702,-0.176214,0.431776,-0.883602,-0.181066,0.36848,-0.907743,-0.200476,0.089999,-0.976165,-0.197363,0.511368,-0.757256,-0.406201,0.475509,-0.846919,-0.237831,0.554399,-0.763939,-0.330119,0.586688,-0.679342,-0.440718,0.272622,-0.918424,-0.286599,0.187506,-0.975799,-0.1124,0.343272,-0.92584,-0.157964,0.40376,-0.847957,-0.343394,0.182562,-0.981079,0.064119,0.332896,-0.942076,0.040681,0.873531,-0.403211,-0.272591,0.811304,-0.447493,-0.376141,0.716269,-0.580645,-0.386944,0.683462,-0.565661,-0.461379,0.765862,-0.476974,-0.431196,0.816187,-0.465651,-0.342021,0.653951,-0.543901,-0.525803,0.662587,-0.533677,-0.525468,0.693167,-0.495621,-0.52327,0.712912,-0.483047,-0.508286,0.48851,-0.110172,0.865535,0.190069,0.012238,0.981689,0.678182,-0.029115,0.734306,0.353374,0.082644,0.931791,0.28077,0.078188,0.956572,0.599567,-0.034547,0.799554,0.018677,0.179998,0.983459,-0.368786,0.289468,0.883267,-0.386334,0.266793,0.8829,-0.020142,0.168462,0.985473,0.466659,-0.649953,-0.59978,0.501236,-0.581286,-0.64095,0.152257,-0.464187,-0.872524,0.108524,-0.557817,-0.82281,0.777703,-0.615864,-0.126011,0.809595,-0.563128,-0.165502,0.681112,-0.597461,-0.42317,0.651753,-0.652272,-0.386914,0.831141,-0.519883,-0.19715,0.701895,-0.551622,-0.450545,-0.763909,0.121952,-0.633656,-0.723991,0.178625,-0.66625,-0.311869,-0.32255,-0.893674,-0.259438,-0.233039,-0.937193,-0.51796,-0.032258,-0.854762,-0.578478,-0.088839,-0.810816,-0.221259,-0.203894,-0.953642,-0.492294,-0.042726,-0.86935,-0.70867,0.250801,0.659413,-0.844447,0.228858,0.484268,-0.657735,0.344981,0.669546,-0.810785,0.337748,0.478011,-0.830378,0.298929,0.470168,-0.67922,0.310495,0.664968,-0.914914,0.295114,0.275338,-0.976287,0.201331,-0.079379,-0.983947,0.173162,-0.043062,-0.923582,0.261361,0.280465,-0.483932,-0.217872,-0.84753,-0.210181,-0.560015,-0.801355,-0.769982,0.627338,0.116306,-0.800287,0.592883,0.089358,-0.799738,0.597461,0.058596,-0.762383,0.646229,0.033631,-0.71514,0.682089,0.152684,-0.640614,0.711692,0.288217,-0.548662,0.729911,0.407605,-0.477157,0.744865,0.466292,-0.771172,0.511826,-0.378521,-0.789239,0.566546,-0.236793,-0.784783,0.613239,-0.089663,0.625416,-0.595264,-0.50444,-0.534104,0.779443,0.327311,-0.72515,0.563829,-0.395215,-0.688803,0.52736,-0.497391,-0.624256,0.4644,-0.628163,-0.525651,0.368297,-0.766808,-0.394604,0.232002,-0.889065,-0.504562,-0.695578,-0.511429,-0.773766,-0.567522,-0.28132,-0.260384,-0.709372,-0.654958,-0.005463,-0.654622,-0.755913,0.358867,-0.48439,-0.797815,-0.029359,0.612629,0.789788,0.086703,0.476028,0.875118,0.146916,0.337748,0.929685,0.158696,0.173528,0.971954,-0.035401,-0.070193,0.996887,-0.783258,-0.617267,-0.073763,-0.900143,-0.421796,0.108554,-0.633381,-0.741569,-0.221046,-0.438154,-0.821894,-0.363964,-0.165502,-0.845149,-0.508194,0.068667,-0.924741,-0.374279,0.260384,-0.937315,-0.231452,0.46556,-0.884396,-0.032258,0.708152,-0.629139,0.320383,0.479019,-0.835261,-0.269875,0.74572,-0.608661,-0.270913,0.849391,-0.462783,-0.25367,0.916013,-0.351848,-0.192602,-0.378063,-0.85403,0.357341,-0.340648,-0.934751,0.100833,-0.292123,-0.948119,-0.12534,-0.234321,-0.87994,-0.413221,0.065981,-0.828547,-0.555986,0.261879,-0.715445,-0.647694,0.44734,-0.548326,-0.706534,0.675588,-0.254891,-0.691763,-0.50679,0.784387,0.357524,-0.475692,0.789209,0.388348,-0.454115,0.783868,0.423414,-0.955931,-0.260994,-0.134373,-0.794763,-0.502579,-0.34022,-0.504807,-0.688314,-0.52089,-0.083163,-0.759728,-0.644856,0.161779,-0.910062,-0.381512,0.343883,-0.931028,-0.121952,0.491562,-0.852901,0.175756,0.606189,-0.591174,0.531968,0.970214,-0.240699,-0.02649,0.949309,-0.187017,0.252571,0.877682,-0.120518,0.46379,0.735282,-0.012207,0.677602,-0.470901,0.372265,0.799768,-0.767296,0.304056,0.564592,-0.899747,0.391552,-0.192663,-0.757469,0.562975,0.330485,-0.588672,0.573656,0.569506,-0.444838,0.543504,0.711814,-0.180731,0.426801,0.886074,0.034059,-0.652821,-0.756706,-0.33845,-0.369762,-0.865261,-0.569567,-0.139622,-0.809961,-0.76165,0.062197,-0.644978,0.763207,-0.52971,0.369915,0.701254,-0.709281,-0.071444,0.579913,-0.751518,-0.314493,0.396893,-0.761803,-0.511917,0.15247,0.21366,0.964904,0.374187,0.040895,0.92642,0.59917,-0.18482,0.778954,0.591906,-0.439314,-0.67571,-0.836879,0.132847,0.530961,-0.503464,0.210456,0.837977,-0.481735,0.261666,0.836329,-0.454268,0.37907,0.806146,-0.441145,0.433973,0.785485,0.916135,0.038148,0.398968,0.994385,-0.043153,-0.09653,0.933683,-0.074557,-0.3502,0.806543,-0.113804,-0.580096,0.042512,0.188055,0.981231,0.37257,0.149174,0.915922,0.614673,0.10477,0.781762,0.752983,0.224433,0.61858,0.985748,0.121158,0.116489,0.987365,0.083743,-0.134434,0.923032,0.027772,-0.383679,-0.747917,0.256691,0.61211,-0.69921,0.277963,0.65862,-0.691275,0.261238,0.673666,-0.740532,0.151952,0.654592,-0.229255,0.248665,0.941038,0.069094,0.262276,0.962493,0.318461,0.269448,0.908811,0.896817,-0.376507,0.232276,0.871944,-0.408612,0.269601,0.829585,-0.454329,0.324534,0.731803,-0.198706,-0.651875,0.756249,-0.086795,-0.648457,0.74456,-0.04297,-0.666128,0.722068,0.047761,-0.690146,0.24839,-0.147252,-0.957396,-0.060305,-0.217872,-0.97409,-0.362682,-0.235725,-0.901578,-0.807459,0.153539,0.569536,-0.788965,0.195898,0.582324,-0.784234,0.200629,0.587085,-0.786157,0.167638,0.594836,-0.320597,0.32487,0.889737,-0.002686,0.378826,0.925443,0.260598,0.398267,0.879452,-0.041688,0.741844,0.66924,-0.634785,0.772149,0.028077,-0.349254,-0.408338,-0.843349,-0.695303,-0.71392,0.082736,-0.680654,-0.724967,-0.105441,-0.562609,-0.802393,-0.198981,-0.398389,0.754875,0.520951,-0.285226,0.741691,0.607044,-0.163976,0.704031,0.690939,0.046907,-0.998871,0.002167,0.07947,-0.98703,-0.139439,0.05179,-0.987732,-0.147252,0.039979,-0.996185,-0.077517,0.042421,-0.998566,-0.032289,0.055605,-0.990692,-0.124058,0.177252,-0.960326,-0.215186,0.580676,-0.640584,-0.502396,0.545061,-0.708029,-0.448927,0.502457,-0.772057,-0.389111,-0.35255,-0.142003,-0.924924,0.465316,0.698721,0.543321,0.390881,0.723502,0.568957,0.241615,0.691824,0.68041,0.332194,0.694266,0.638417,0.835719,0.446791,-0.319224,0.808405,0.359111,-0.466323,0.751488,0.425214,-0.504379,0.774499,0.530625,-0.344279,0.490982,0.100009,-0.865383,0.355266,0.042055,-0.933805,0.414686,-0.057772,-0.908109,0.529984,0.047639,-0.846644,-0.165899,-0.232093,-0.958434,-0.319895,-0.322001,-0.891018,-0.201788,-0.502029,-0.840968,-0.051698,-0.414624,-0.908505,-0.44087,-0.409375,-0.798761,-0.326884,-0.576159,-0.749077,-0.782067,0.387799,0.487777,-0.545305,0.52852,0.650563,-0.560839,0.42967,0.707663,-0.812555,0.212287,0.542833,0.770806,0.637043,0.000397,0.774712,0.63094,-0.040925,0.841578,0.53441,-0.078066,0.796472,0.600574,-0.069887,0.674215,0.346934,-0.651936,0.692007,0.396222,-0.603381,0.541948,0.243477,-0.804346,0.537584,0.202948,-0.818384,-0.033387,-0.333811,-0.942015,-0.234565,-0.549821,-0.80163,-0.231635,-0.492111,-0.839106,-0.051973,-0.292856,-0.954711,-0.411115,-0.893185,-0.182165,-0.414686,-0.908994,0.041566,-0.455947,-0.887173,-0.070742,-0.450392,-0.843532,-0.292489,-0.34138,-0.905759,0.251076,-0.27076,-0.959838,0.073153,-0.127903,-0.991333,0.029359,-0.241157,-0.951903,0.188879,0.122318,-0.944212,0.305704,0.61153,-0.644765,0.458541,0.339732,-0.506333,0.792566,-0.108921,-0.656392,0.746483,-0.136296,0.518815,0.843928,-0.405377,0.542314,0.735893,-0.297128,0.471786,0.830103,0.014222,0.433515,0.900998,0.322245,-0.652577,0.68572,0.509262,-0.338023,0.791406,0.204443,-0.216254,0.95468,-0.042207,-0.374248,0.926359,-0.850551,-0.45204,-0.268624,-0.84106,-0.421979,-0.338359,-0.997131,-0.070193,0.027833,-0.994842,-0.08066,-0.06122,0.610614,0.5721,-0.547533,0.682058,0.471358,-0.559069,0.732322,0.499283,-0.462996,0.653249,0.603656,-0.456984,0.231635,0.196112,-0.952818,0.225257,0.183294,-0.956877,0.346751,0.266732,-0.899197,0.338237,0.310617,-0.888302,-0.416211,-0.465102,-0.781274,-0.46028,-0.401471,-0.791772,-0.293771,-0.239845,-0.92526,-0.246467,-0.303751,-0.920286,-0.699973,-0.630207,0.335856,-0.560533,-0.337382,0.756249,-0.79281,-0.138371,0.593493,-0.938688,-0.252052,0.235145,0.840693,0.448836,-0.302896,0.812281,0.47261,-0.341746,0.866848,0.369427,-0.334758,0.838466,0.43083,-0.333598,0.704184,0.706229,-0.07297,0.693716,0.720206,-0.002747,0.705954,0.70806,-0.015687,0.723075,0.68688,-0.073122,0.668233,0.741264,0.062716,0.681326,0.730918,0.038667,0.546403,0.773797,0.320322,0.594165,0.761925,0.257607,0.685965,0.681997,0.253517,0.62917,0.735801,0.250374,0.583361,-0.258278,0.770012,0.710349,0.062899,0.70098,0.454054,0.112308,0.883847,0.26191,-0.073611,0.962249,-0.263039,-0.953307,0.148228,-0.089206,-0.921812,-0.377148,0.296915,-0.929197,-0.220038,0.347392,-0.926115,0.147008,0.657643,0.720145,0.221076,0.676473,0.713675,0.181646,0.269082,0.541734,0.796289,0.51265,-0.239937,-0.824366,0.918699,-0.143925,0.367748,0.787164,0.242225,-0.567125,-0.461684,-0.872951,-0.157384,-0.337596,-0.939451,-0.058229,-0.305795,-0.930357,0.202155,-0.004852,-0.787286,-0.616504,-0.298257,-0.007782,0.954436,0.600879,-0.671712,0.433271,-0.216285,-0.97586,-0.029328,-0.550432,-0.578051,0.602344,-0.515458,-0.828608,0.218268,-0.413343,-0.860012,0.299142,-0.284921,-0.911832,0.29548,-0.160253,-0.88815,-0.430677,-0.166478,-0.016022,0.9859,0.676015,-0.664357,0.318674,-0.452254,-0.451735,0.768975,0.807825,0.581988,-0.093204,0.813013,0.564623,-0.141972,0.601367,0.484542,0.635243,0.315928,-0.333995,-0.888028,0.930418,-0.287973,0.22663,0.633747,0.123692,-0.763543,0.294473,0.781152,0.550493,0.28193,0.790674,0.543413,-0.00293,0.633869,0.7734,-0.043733,0.595569,0.802087,0.611255,-0.179327,-0.770837,0.881375,-0.150121,0.447859,0.84695,0.361736,-0.389569,-0.281655,-0.942869,-0.177831,-0.271676,-0.957762,-0.09418,0.17777,-0.801386,-0.571062,-0.523209,-0.285318,0.803003,-0.439894,-0.233589,0.867092,-0.486709,-0.644429,0.58974,-0.542222,-0.663411,0.51558,0.297769,0.01886,-0.954436,0.348186,0.025422,-0.93704,0.525681,0.263161,-0.808924,-0.549547,-0.826563,-0.121372,-0.485885,-0.849117,-0.207038,-0.298196,-0.69686,-0.652242,-0.348949,-0.724784,-0.594043,0.061861,0.007935,0.998047,0.647145,-0.725944,0.232734,-0.252113,-0.537522,0.804651,0.867763,0.458632,-0.19129,0.843715,0.44319,-0.302774,0.757286,0.35612,0.54741,0.920316,0.384472,-0.071841,0.872036,0.258339,-0.415693,0.883755,0.206244,-0.420026,0.92703,0.36607,-0.080996,0.235176,-0.269723,-0.933744,-0.309671,-0.483383,-0.818781,-0.136143,-0.563402,-0.814844,0.334483,-0.263192,-0.904874,-0.732902,-0.507462,-0.453078,-0.625874,-0.639729,-0.446089,-0.538316,0.008087,0.842677,-0.412275,0.018403,0.910855,-0.752129,-0.296457,0.588549,-0.870418,-0.231208,0.434614,-0.753746,-0.570299,-0.326426,-0.647175,-0.715934,-0.261849,-0.287484,-0.649953,-0.703452,-0.425733,-0.5515,-0.717307,-0.324778,-0.058107,0.943999,-0.246956,-0.063295,0.966948,-0.632496,-0.348491,0.691671,-0.718772,-0.325968,0.614063,0.930815,0.140385,-0.337382,0.787622,0.022614,-0.615711,0.786767,0.052034,-0.615009,0.919004,0.21366,-0.331217,0.014466,-0.377606,-0.92584,0.084841,-0.370098,-0.925108,0.796747,0.532304,0.285989,0.865993,0.404492,0.293954,0.947813,0.29783,-0.113529,0.887295,0.426099,-0.176336,0.366924,-0.167119,-0.915098,-0.149998,-0.320994,-0.935118,-0.145695,-0.345836,-0.926908,0.296487,-0.174993,-0.938841,-0.89642,-0.369366,-0.244819,-0.934904,-0.237403,0.263741,-0.923795,-0.310129,0.224464,-0.864589,-0.439985,-0.242622,-0.178198,0.270638,0.946013,-0.097842,0.209418,0.9729,0.305277,0.36137,0.881008,0.223945,0.450362,0.864284,0.389416,-0.205359,-0.897855,0.395611,-0.141667,-0.907407,0.718955,0.009339,-0.694968,0.740593,-0.108188,-0.663167,-0.948729,-0.026002,0.31492,-0.66628,0.027467,0.74517,-0.522385,-0.219733,0.823878,-0.867641,-0.354411,0.348674,-0.180975,0.095492,0.97882,-0.043336,-0.022187,0.99881,0.941099,-0.021088,-0.337413,0.917539,0.108036,-0.382611,0.996307,0.083773,0.017792,0.920621,0.034364,-0.388928,0.911283,0.079928,-0.403882,0.986969,0.160405,-0.011292,0.071718,-0.089877,-0.993347,-0.42378,-0.072146,-0.90286,-0.40141,-0.170843,-0.899808,0.075137,-0.137272,-0.987671,-0.965087,-0.245857,-0.090091,-0.784661,-0.61803,-0.048036,-0.81518,-0.513749,0.267373,-0.923246,-0.221198,0.314066,-0.162694,-0.160436,0.97354,0.435316,-0.129673,0.890866,-0.971129,0.156713,0.179693,-0.77633,0.194891,0.599414,0.106998,-0.109806,0.988159,-0.059908,-0.552934,0.83105,0.383496,-0.610462,0.692953,0.599231,-0.299387,0.742454,0.921751,-0.20603,-0.32844,0.729362,-0.207099,-0.651967,0.785882,-0.062105,-0.615223,0.947783,-0.008148,-0.318766,-0.053743,-0.103122,-0.993194,-0.588092,0.039888,-0.807794,-0.538652,-0.131596,-0.832179,-0.023988,-0.140263,-0.989807,-0.338694,-0.382031,-0.859828,-0.558214,-0.653554,-0.511093,-0.808039,-0.324381,-0.491714,-0.7799,-0.069674,-0.621998,-0.871059,0.367626,0.325632,-0.65685,0.410443,0.632496,-0.642354,0.381085,0.664907,-0.887814,0.314676,0.335734,0.081393,0.035585,0.996033,-0.102786,-0.355327,0.929044,0.317057,-0.427168,0.846736,0.554064,-0.151555,0.818537,0.938231,-0.218238,-0.268441,0.717124,-0.267861,-0.643361,0.739067,-0.166234,-0.65276,0.943266,-0.080203,-0.322123,-0.017579,0.621021,0.783563,0.059023,0.649739,0.757836,0.169042,0.695486,0.698325,0.553972,0.787988,0.268563,0.617328,0.737358,0.274148,0.683523,0.718986,0.125767,0.626759,0.772851,0.099307,0.697501,0.58858,-0.408704,0.680715,0.468825,-0.562853,0.631519,0.188788,-0.752007,0.586932,0.00354,-0.809595,0.48265,0.068575,-0.873104,0.554277,0.236579,-0.797967,-0.481857,-0.781213,-0.396802,-0.558306,-0.660237,-0.502335,-0.629048,-0.640156,-0.440962,-0.572588,-0.751274,-0.328135,-0.864071,-0.501419,-0.043916,-0.885311,-0.436079,-0.161382,-0.951201,-0.306223,-0.037751,-0.917753,-0.387463,0.086978,-0.808618,0.010468,0.588183,-0.623829,0.257729,0.737785,0.923399,-0.166448,-0.345805,0.941252,-0.069491,0.330424,0.983581,0.11124,0.142003,0.868191,-0.10654,-0.484634,-0.344157,-0.560594,-0.753166,-0.470504,-0.70397,-0.531968,-0.00293,-0.898526,-0.438856,0.336283,-0.550981,-0.763726,-0.893429,0.049806,0.446364,-0.899686,0.205451,0.385113,-0.980102,-0.19779,0.015381,0.186407,0.356761,0.915372,-0.198218,0.374706,0.905698,0.889553,-0.138371,-0.435347,0.99527,0.096866,0.004425,-0.448012,-0.418012,-0.790246,-0.407819,-0.304209,-0.860866,0.033845,-0.277718,-0.960051,0.029267,-0.38963,-0.920499,-0.839106,0.213874,0.500137,-0.90698,0.153722,0.392041,-0.991577,0.058718,0.115299,-0.996124,0.034059,0.080935,0.524369,0.408277,0.747185,0.612415,0.364879,0.701254,0.258156,0.401349,0.87875,0.10712,0.428175,0.897305,-0.202216,0.594958,0.777856,-0.344157,0.508744,0.789087,-0.313517,0.580096,0.751762,0.153294,0.708365,0.688955,-0.005799,0.603565,0.797266,-0.017792,0.614277,0.788873,0.137822,0.723746,0.676138,-0.187445,0.473037,0.860836,-0.166448,0.453444,0.875576,-0.42317,-0.706259,0.567522,-0.416028,-0.762841,0.494919,-0.501724,-0.369182,0.782281,-0.589435,-0.267098,0.762352,-0.21601,0.341594,0.91467,-0.267647,0.319651,0.908933,-0.012116,0.623951,0.781335,-0.40614,-0.874996,0.263375,-0.42616,-0.886166,0.181829,-0.439558,-0.610981,0.658376,-0.217658,-0.972472,-0.08301,-0.339854,-0.935484,-0.096622,-0.233985,-0.969634,-0.070711,0.003113,-0.97354,-0.228401,0.081027,-0.994354,-0.06827,-0.244667,-0.92761,-0.282235,-0.431532,-0.900662,-0.050478,-0.501511,-0.841639,-0.200201,-0.417676,-0.902219,0.107334,-0.72161,-0.507523,-0.470809,-0.707358,-0.594104,-0.382946,-0.673849,-0.537309,-0.507126,0.042146,0.118839,0.992004,0.397656,0.242378,0.884915,-0.499283,-0.547868,-0.671194,-0.156163,-0.413617,-0.896939,-0.543443,-0.449751,-0.708762,0.733787,0.170049,-0.657704,0.809473,0.095431,-0.579333,0.916166,0.239204,0.321543,0.930876,0.034852,0.363628,0.995697,0.030885,0.087283,0.987426,0.157842,0.006134,0.293008,-0.387127,-0.874203,0.285775,-0.419568,-0.861538,0.057955,-0.406201,-0.911924,0.086367,-0.371349,-0.924436,0.27842,-0.44087,-0.853267,0.031587,-0.421186,-0.9064,-0.157567,-0.388989,-0.907651,-0.12595,-0.356395,-0.925779,-0.18128,-0.396161,-0.900082,0.499435,-0.414624,-0.760643,0.516953,-0.438002,-0.735435,0.721763,-0.449751,-0.526078,0.758812,-0.446913,-0.473739,0.532853,-0.453841,-0.714194,0.784814,-0.424909,-0.451064,0.292306,-0.344584,-0.892056,0.480758,-0.375469,-0.792352,0.278909,-0.298044,-0.91287,0.45027,-0.339366,-0.825861,0.699057,-0.412671,-0.583911,0.667348,-0.407788,-0.623127,0.105075,-0.325205,-0.939756,-0.098086,-0.310648,-0.945433,0.111393,-0.275185,-0.954894,-0.076937,-0.263253,-0.961638,0.020173,-0.026765,0.99942,0.098575,0.015137,0.994995,0.195471,-0.045503,0.979644,0.145726,-0.117252,0.98233,0.141148,0.057833,0.988281,0.224708,0.034516,0.973785,0.342082,-0.082675,0.936003,0.342326,-0.174291,0.923246,0.334727,0.019288,0.942106,-0.022675,0.072634,0.99707,0.05063,0.074801,0.995911,0.079806,0.072085,0.994171,-0.055757,0.030732,0.997955,-0.094302,0.10474,0.98999,-0.099826,0.13419,0.9859,-0.142003,0.166692,0.975707,0.100406,-0.033357,0.994385,0.334971,-0.079714,0.938841,0.072115,0.110416,0.991241,0.311411,0.074007,0.947356,-0.207587,0.199225,-0.957701,-0.195959,0.235878,-0.951811,-0.115268,0.29487,-0.948546,-0.11594,0.224708,-0.967498,-0.163121,0.268197,-0.949431,-0.090945,0.343974,-0.934538,0.016999,0.367046,-0.930021,0.010743,0.269082,-0.963042,0.035615,0.432844,-0.900754,-0.179083,0.188147,-0.965667,-0.163884,0.198309,-0.966308,-0.136753,0.216163,-0.966704,-0.19245,0.158849,-0.968352,-0.178808,0.173711,-0.968413,-0.163182,0.125523,-0.978545,-0.167791,0.14423,-0.975188,-0.091861,0.147801,-0.98471,0.015839,0.159459,-0.98706,-0.058809,0.103397,-0.992889,0.028871,0.091861,-0.995331,0.10007,0.026795,-0.994598,0.10712,0.0618,-0.992309,0.080355,0.062349,-0.994781,0.09769,0.078188,-0.992126,0.065432,0.083438,-0.994354,0.048769,0.077029,-0.995819,0.078494,0.106571,-0.99118,0.140294,0.166478,-0.975982,0.14655,0.045106,-0.988159,0.137516,0.083865,-0.986938,0.194372,0.059816,-0.979095,0.142308,0.064699,-0.987701,0.058596,0.079867,-0.995056,0.026399,0.032563,-0.999115,0.083346,0.006134,-0.99649,0.125248,-0.017609,-0.991943,0.107089,-0.000366,-0.994232,0.136662,-0.08594,-0.986877,0.20481,0.010865,-0.978729,0.222083,-0.067934,-0.972625,0.13422,0.156987,-0.978423,0.237709,0.282662,-0.929289,0.162755,0.248634,-0.954802,0.321665,0.460982,-0.827052,0.23011,-0.130467,-0.964354,0.252846,-0.231635,-0.93936,0.109806,-0.213874,-0.970641,0.112094,-0.130924,-0.985015,-0.059114,-0.210578,-0.975768,-0.038972,-0.143437,-0.988861,0.326426,-0.149022,-0.933378,0.388928,-0.270028,-0.880795,0.408307,-0.190924,-0.892636,0.516312,-0.321787,-0.793603,0.216468,-0.012879,-0.976196,0.272591,-0.032197,-0.961577,0.316752,-0.087344,-0.944456,0.125736,-0.025208,-0.991729,-0.010804,-0.055666,-0.998383,-0.225715,-0.264199,-0.937651,-0.277017,-0.327342,-0.903348,-0.294076,-0.348491,-0.88995,-0.24659,-0.30314,-0.920469,-0.303537,-0.368023,-0.878842,-0.309671,-0.375072,-0.873714,-0.18189,-0.379986,-0.906919,-0.149846,-0.346812,-0.925871,-0.120762,-0.23487,-0.964476,-0.148503,-0.315256,-0.937284,-0.167058,-0.368175,-0.914609,-0.158666,-0.207678,-0.965209,-0.085025,-0.175359,-0.980804,-0.114383,-0.157872,-0.980804,-0.070681,-0.151891,-0.985839,-0.177313,-0.249458,-0.951994,-0.092563,-0.293863,-0.951323,-0.116184,-0.179937,-0.976775,-0.033143,-0.210181,-0.977081,-0.245857,0.643178,0.72515,-0.232368,0.52739,0.817194,-0.096622,0.541063,0.835383,-0.15772,0.691366,0.705039,-0.198584,0.425245,0.882992,-0.033998,0.420698,0.906552,0.104373,0.535325,0.83816,-0.012513,0.728874,0.6845,0.184362,0.410749,0.89288,-0.261422,0.601764,0.754631,-0.268471,0.515885,0.813471,-0.25309,0.434889,0.864162,-0.222571,0.723258,0.653706,-0.225379,0.670644,0.706687,-0.156346,0.74163,0.652303,-0.163213,0.699484,0.69573,-0.168828,0.779473,0.603198,-0.069094,0.831629,0.550981,-0.117008,0.780938,0.613514,-0.027833,0.810053,0.58565,-0.138096,0.283639,0.948912,0.032105,0.280496,0.959288,0.007172,0.342051,0.939634,-0.163732,0.347575,0.923215,0.241707,0.271096,0.9317,0.21897,0.338664,0.915036,-0.120273,0.218451,0.968383,0.051546,0.211707,0.975951,0.27427,0.186254,0.943419,-0.198553,0.298379,0.933531,-0.17127,0.235755,0.956572,-0.2266,0.36317,0.903745,0.742271,-0.033845,0.66924,0.767266,-0.015107,0.641102,0.831965,0.007996,0.554735,0.848781,-0.027528,0.52797,0.770989,-0.005707,0.636799,0.794183,0.031068,0.606861,0.840754,0.052828,0.538804,0.87524,-0.010376,0.483505,0.793298,0.08594,0.60268,0.553423,-0.036256,0.832087,0.592029,-0.011048,0.805811,0.346934,-0.050417,0.936521,0.347911,-0.003357,0.937498,0.648366,-0.007294,0.761254,0.395154,0.016327,0.918455,0.708243,-0.052461,0.70397,0.531907,-0.04471,0.845576,0.676107,-0.072207,0.733238,0.515122,-0.050142,0.855617,0.364696,-0.057375,0.929319,0.371838,-0.044588,0.927213,0.838588,-0.067171,0.540574,0.88641,-0.072207,0.457228,0.815973,-0.098148,0.569659,0.88403,-0.111606,0.453871,0.842677,0.127506,-0.523057,0.861293,0.134709,-0.489914,0.74929,0.126286,-0.650044,0.740989,0.121586,-0.66036,0.874813,0.117038,-0.470077,0.761589,0.110233,-0.638569,0.617878,0.10239,-0.779534,0.638874,0.092471,-0.763695,0.616718,0.094516,-0.781457,0.947081,0.127293,-0.294626,0.951537,0.139286,-0.274087,0.992309,0.116367,0.041993,0.987732,0.153172,0.02942,0.955382,0.122288,-0.268838,0.989807,0.142338,0.001404,0.824519,0.07419,-0.56093,0.937376,0.070711,-0.340953,0.824152,-0.032167,-0.565416,0.918302,-0.02768,-0.394879,0.998932,0.04355,0.014893,0.997986,-0.047456,-0.041841,0.755852,0.066347,-0.651357,0.693289,0.028535,-0.720084,0.807398,-0.062075,-0.586688,0.767205,-0.115024,-0.63097,0.935881,-0.339702,0.093112,0.939756,-0.339122,-0.042665,0.951903,-0.270852,-0.143193,0.958342,-0.285318,0.010926,0.929014,-0.302896,-0.212439,0.932371,-0.209815,-0.294259,0.939573,-0.209326,-0.270821,0.963103,-0.25132,-0.096011,0.938047,-0.187139,-0.291543,0.914487,-0.387646,0.115635,0.922575,-0.383343,0.043123,0.899869,-0.428694,0.080111,0.915311,-0.384686,0.118992,0.935789,-0.336406,-0.105319,0.94586,-0.324473,-0.000763,0.899045,-0.414411,-0.141179,0.876675,-0.427808,-0.220008,0.853786,-0.457076,-0.249123,0.926542,-0.364757,-0.091769,0.931333,-0.334574,-0.143529,0.706717,0.629475,0.322886,0.703146,0.6657,0.249733,0.588733,0.767388,0.253883,0.596606,0.73101,0.331156,0.70632,0.690939,0.153783,0.588305,0.793603,0.155004,0.456374,0.852168,0.255898,0.467116,0.817591,0.336558,0.452132,0.878353,0.155034,0.798059,0.517045,0.309366,0.799921,0.54976,0.240516,0.871975,0.39671,0.286752,0.87933,0.421491,0.221564,0.806848,0.571612,0.149083,0.889401,0.436201,0.136601,0.711905,0.586963,0.385479,0.797632,0.478988,0.366466,0.7116,0.539628,0.449843,0.792718,0.438581,0.423292,0.86578,0.366558,0.340587,0.856838,0.334147,0.392621,0.606342,0.687429,0.39967,0.479598,0.775628,0.410291,0.610828,0.635701,0.471908,0.487991,0.722739,0.489334,0.873165,-0.208808,0.440352,0.868557,-0.181188,0.461226,0.888058,-0.146245,0.435774,0.889981,-0.157445,0.427869,0.869015,-0.146214,0.472671,0.878842,-0.131809,0.45851,0.893979,-0.111209,0.434034,0.898312,-0.102542,0.427168,0.863704,-0.237587,0.444411,0.839351,-0.202704,0.504318,0.889309,-0.1948,0.413648,0.823542,-0.168249,0.541704,0.848689,-0.152165,0.506485,0.824702,-0.125736,0.551347,0.953246,-0.218604,0.208563,0.961211,-0.242225,0.131687,0.965331,-0.194952,-0.173498,0.948027,-0.207282,-0.241371,0.978881,-0.201788,0.032502,0.954466,-0.157109,0.253517,0.954405,-0.081851,0.286996,0.984558,-0.137913,-0.107547,0.43733,0.142705,0.887875,0.458754,0.132206,0.878628,0.550157,0.245369,0.798181,0.505844,0.283059,0.814844,0.438185,0.271645,0.856838,0.532365,0.331004,0.779077,0.628254,0.351268,0.694143,0.581133,0.420423,0.696768,0.604877,0.382641,0.698355,0.368267,-0.024201,0.92938,0.35493,-0.004334,0.934874,0.283822,-0.236122,0.929319,0.237098,-0.16892,0.956664,0.32078,0.184576,0.928983,0.19245,0.066347,0.979034,0.400037,0.233528,0.886227,0.38667,0.047731,0.920957,0.383618,0.341319,0.858058,0.414563,0.153661,0.896939,0.361003,-0.190283,0.912931,0.448103,-0.076235,0.890683,0.41728,0.397748,0.817072,0.454176,0.557482,0.694906,0.355602,0.520341,0.77636,0.332041,0.691855,0.641133,0.680105,0.644734,0.348888,0.729881,0.548357,0.408063,0.768792,0.617573,0.165899,0.704245,0.698691,0.125767,0.738792,0.50322,0.448225,0.811701,0.548173,0.201575,0.78869,0.605243,-0.107883,0.720573,0.685293,-0.105411,0.838527,0.535966,-0.097934,0.642048,0.544633,0.539537,0.685995,0.452101,0.570055,0.669485,0.441572,0.597278,0.567309,0.755303,0.328074,0.507279,0.685018,0.522843,0.357372,0.871364,0.33607,0.324137,0.813044,0.483566,0.624866,0.770714,0.124485,0.661763,0.743339,-0.097201,0.43495,0.885433,0.16361,0.516037,0.853664,-0.070406,0.681539,0.485214,-0.547746,0.743034,0.399579,-0.536821,0.638874,0.246345,-0.728751,0.592547,0.290994,-0.751091,0.747337,0.420576,-0.514359,0.63686,0.350902,-0.686483,0.496506,0.120151,-0.859645,0.463424,0.083773,-0.882138,0.497818,0.291787,-0.816706,0.718863,0.613849,-0.326182,0.784845,0.523759,-0.331126,0.81167,0.482772,-0.328715,0.608142,0.559893,-0.56267,0.656941,0.675741,-0.33433,0.517197,0.631855,-0.577258,0.546617,0.766198,-0.337809,0.522721,0.378338,-0.763909,0.411145,0.148137,-0.899411,0.447676,0.454024,-0.770318,0.358959,0.232368,-0.903928,0.208533,-0.167394,-0.963561,0.208838,-0.072359,-0.975249,0.080966,-0.217689,-0.972625,0.111087,-0.256447,-0.960143,0.175756,-0.0047,-0.984405,0.026368,-0.277413,-0.960356,-0.034364,-0.409619,-0.911588,0.017457,-0.388897,-0.921079,-0.065889,-0.516709,-0.853603,0.325907,-0.071444,-0.942686,0.348308,0.034059,-0.936735,0.343791,0.196448,-0.918241,0.206854,-0.209967,-0.955565,0.295846,-0.065798,-0.95294,0.212195,-0.187414,-0.959075,0.272073,-0.002564,-0.962249,0.149693,-0.30253,-0.941282,0.108341,-0.409406,-0.905881,0.189337,-0.302133,-0.934233,0.196631,-0.395032,-0.897336,-0.195318,-0.808069,-0.555712,-0.270882,-0.769921,-0.577746,-0.376598,-0.848567,-0.371532,-0.23896,-0.922788,-0.302164,-0.219489,-0.769189,-0.600116,-0.350017,-0.822504,-0.448256,-0.445967,-0.87466,-0.189795,-0.250587,-0.962889,-0.099918,-0.467971,-0.846858,-0.252541,-0.096042,-0.601123,-0.793329,-0.149937,-0.616321,-0.773064,-0.132206,-0.681295,-0.719932,0.050356,-0.824396,-0.563738,0.067232,-0.598132,-0.798547,0.322062,-0.71807,-0.616932,0.237556,-0.53386,-0.811487,0.088382,-0.95941,-0.267739,0.134129,-0.990875,-0.011811,0.428266,-0.844844,-0.320597,0.50145,-0.864803,-0.024354,-0.253609,-0.963317,0.087527,-0.468093,-0.883358,0.023011,-0.439314,-0.894986,0.077425,-0.238929,-0.964171,0.115177,-0.52327,-0.851314,0.037233,-0.500656,-0.85818,0.113315,-0.384716,-0.910123,0.15363,-0.179144,-0.967193,0.18009,-0.467238,-0.86227,0.195349,-0.253029,-0.967101,0.026002,-0.472396,-0.87936,-0.059328,-0.521592,-0.849422,-0.079775,0.173803,-0.960295,0.218177,0.160253,-0.975738,0.149052,0.586261,-0.759178,0.282632,0.542802,-0.820246,0.180334,0.189276,-0.951201,0.243568,0.232643,-0.928556,0.289163,0.62273,-0.708335,0.332347,0.6292,-0.695242,0.347423,-0.101932,-0.905301,0.412336,-0.274758,-0.820215,0.501724,-0.239753,-0.738029,0.630696,-0.10181,-0.86578,0.489914,-0.381146,-0.751549,0.538377,-0.33021,-0.631092,0.701865,-0.184606,-0.667806,0.721061,-0.072146,-0.832728,0.548906,-0.259407,-0.496902,0.82812,-0.110691,-0.945494,0.306162,-0.316324,-0.893551,0.318522,-0.42613,-0.835597,0.34666,0.182958,-0.938444,0.292886,0.232582,-0.922269,0.308664,0.509293,-0.842189,0.176977,0.57741,-0.764824,0.285653,0.141636,-0.941832,0.304666,0.130284,-0.927122,0.35136,0.456954,-0.879391,0.13358,0.42967,-0.882595,0.19071,0.074038,-0.66277,0.745109,-0.005097,-0.48439,0.874813,0.112705,-0.339152,0.933927,0.180517,-0.469558,0.864223,-0.055635,-0.219062,0.97409,0.065127,-0.073611,0.995148,-0.013306,-0.777581,0.62859,-0.105899,-0.591021,0.799646,-0.165838,-0.359416,0.918302,0.223243,-0.728568,0.647542,0.155583,-0.872616,0.462935,0.463485,-0.649709,0.602496,0.433454,-0.825861,0.360546,0.305338,-0.475906,0.824763,0.472182,-0.364757,0.802484,0.386364,0.546831,0.742729,0.266884,0.380291,0.885495,0.336314,0.355663,0.871975,0.454634,0.508438,0.731254,0.109745,0.151402,0.98233,0.169836,0.140477,0.975402,0.444472,0.309061,0.840785,0.535173,0.455184,0.71157,0.29667,0.105502,0.949126,0.348186,0.577502,0.738395,0.235603,0.410413,0.880917,0.316813,0.62331,0.714866,0.212958,0.464644,0.859462,0.089053,0.178594,0.979858,0.07593,0.243355,0.966948,0.483108,0.676107,0.556291,0.444838,0.703482,0.554247,0.554338,0.762474,0.333598,0.524186,0.781732,0.337718,0.403943,0.737693,0.54091,0.479843,0.803858,0.351451,0.541002,0.631245,0.555712,0.594501,0.576861,0.560106,0.596606,0.724082,0.346019,0.630634,0.67626,0.380688,0.616718,0.785119,-0.05652,0.621754,0.781854,-0.045778,0.618488,0.769738,-0.157842,0.630848,0.763787,-0.136326,0.625751,0.779717,0.020692,0.626118,0.763024,-0.160283,0.630207,0.748314,-0.206885,0.650899,0.747703,-0.13126,0.639149,0.708487,-0.299142,0.596515,0.794733,0.111789,0.620685,0.773217,0.129795,0.639637,0.744346,0.191778,0.617542,0.78634,-0.016297,0.58034,0.803797,0.130619,0.60033,0.79693,0.066897,0.544847,0.818384,0.182562,0.647114,0.758751,-0.074129,0.672689,0.73693,-0.066286,0.643666,0.76516,0.014222,0.672018,0.740471,-0.005737,0.699545,0.693289,-0.17304,0.71804,0.650685,-0.246895,0.734764,0.564196,-0.376537,0.707083,0.587329,-0.393689,0.732994,0.580187,-0.355022,0.717765,0.606677,-0.341655,0.59096,0.357768,-0.723014,0.591327,0.294717,-0.750603,0.587634,0.539262,-0.603198,0.67217,0.732109,-0.110141,0.666158,0.715171,-0.211463,0.681112,0.634114,-0.365978,0.699393,0.693991,-0.170843,0.690176,0.720573,-0.066286,0.688345,0.69512,-0.20716,0.686636,0.725059,-0.05298,0.674673,0.600146,-0.429609,0.552263,0.347697,-0.757683,0.645375,0.597583,-0.475753,0.529954,0.380932,-0.757591,0.012543,-0.503952,-0.863613,0.28721,-0.178747,-0.941008,0.210273,-0.112857,-0.971099,-0.055788,-0.414075,-0.908505,0.142064,0.11243,-0.983428,-0.197913,-0.279031,-0.939634,-0.006897,-0.479019,-0.877743,0.289285,-0.087832,-0.953185,0.002655,-0.431043,-0.90231,0.311899,-0.008942,-0.950041,-0.105655,-0.61568,-0.780847,-0.159612,-0.637989,-0.753288,-0.168889,-0.669149,-0.723655,-0.227363,-0.693197,-0.68392,-0.194006,-0.638203,-0.744987,-0.262276,-0.70806,-0.655599,-0.123264,-0.501755,-0.856166,-0.231849,-0.388409,-0.891812,-0.144017,-0.536912,-0.831233,-0.203558,-0.410291,-0.888913,-0.16129,-0.589038,-0.791833,-0.181036,-0.657888,-0.73101,-0.143712,-0.540391,-0.829035,-0.119205,-0.484939,-0.866359,-0.163884,-0.384899,-0.908261,-0.145207,-0.331095,-0.93234,-0.216193,-0.630726,-0.745262,-0.242286,-0.690054,-0.681936,-0.233222,-0.649983,-0.723258,-0.27073,-0.709433,-0.650685,-0.175756,-0.54445,-0.820154,-0.2172,-0.588366,-0.778832,-0.336375,-0.695029,-0.635395,-0.357921,-0.724113,-0.589465,-0.231849,-0.62389,-0.7463,-0.360851,-0.750969,-0.552965,-0.15418,-0.441755,-0.883755,-0.201727,-0.385601,-0.900327,-0.357677,-0.659688,-0.660939,-0.398633,-0.641377,-0.655507,-0.501968,-0.851741,0.150121,-0.490036,-0.851283,-0.187506,-0.51973,-0.841548,-0.147038,-0.514389,-0.826289,0.229347,-0.554369,-0.820612,-0.138615,-0.561785,-0.788476,0.250313,-0.520524,-0.848933,0.091403,-0.495987,-0.84521,-0.198859,-0.516221,-0.854244,0.061068,-0.485794,-0.851527,-0.197119,-0.470138,-0.823786,0.316721,-0.500168,-0.82696,0.256783,-0.444655,-0.811426,0.379254,-0.474746,-0.810572,0.342845,-0.508866,-0.833735,0.214148,-0.489486,-0.819941,0.296701,-0.482284,-0.786493,0.385693,-0.536699,-0.728233,0.42613,-0.466597,-0.775079,0.426008,-0.516617,-0.706687,0.483383,-0.455336,-0.807794,0.37431,-0.473495,-0.814173,0.335948,-0.470138,-0.798395,0.376141,-0.455855,-0.780908,0.426984,-0.502609,-0.779565,0.373638,-0.480514,-0.787591,0.385723,-0.444258,-0.744896,0.497726,-0.432783,-0.717948,0.545152,-0.437056,-0.761345,0.478835,-0.443678,-0.812128,0.378857,-0.467757,-0.797693,0.380535,-0.51033,-0.73983,0.438398,-0.451125,-0.795343,0.404828,-0.458022,-0.804071,0.379009,-0.447066,-0.79751,0.405072,-0.467452,-0.812677,0.34785,-0.441054,-0.765252,0.468856,-0.415113,-0.702811,0.577624,-0.424696,-0.760643,0.490921,-0.393048,-0.687094,0.611042,-0.256569,-0.408368,0.876003,-0.23899,-0.419446,0.875729,-0.042055,-0.14359,0.988739,-0.081759,-0.137333,0.987121,-0.158818,-0.462081,0.872463,0.075747,-0.176519,0.981353,-0.37376,-0.603046,0.704672,-0.374401,-0.624348,0.685537,-0.337199,-0.664418,0.666921,-0.254341,-0.396313,0.882168,-0.360698,-0.591113,0.721397,-0.241707,-0.333598,0.911191,-0.340312,-0.553545,0.760094,-0.090732,-0.116337,0.989044,-0.090426,-0.043306,0.994934,-0.058901,0.490921,0.869167,-0.201453,0.294137,0.934263,-0.195196,0.279672,0.940031,-0.048097,0.488021,0.871487,-0.312204,0.099979,0.9447,-0.303629,0.089175,0.948576,-0.179174,0.259865,0.948851,-0.022767,0.477126,0.878506,-0.286813,0.069185,0.955473,-0.058168,0.484298,0.872951,-0.196631,0.297372,0.934263,-0.058992,0.473861,0.878597,-0.184881,0.297464,0.936644,-0.302957,0.102115,0.947508,-0.275307,0.115513,0.954375,0.139958,0.700461,0.69982,0.133641,0.671651,0.72869,0.205939,0.751732,0.626453,0.228584,0.716086,0.659474,0.096622,0.627155,0.772851,0.197394,0.670247,0.715384,0.154363,0.704276,0.692923,0.192114,0.692892,0.694937,0.212775,0.753929,0.621509,0.280831,0.743919,0.606372,0.246773,0.789422,0.562029,0.263924,0.783105,0.563066,0.461745,0.860591,0.214789,0.438246,0.873501,0.211829,0.314066,0.743065,0.590899,0.498459,0.831294,0.245857,0.572466,0.812311,-0.111484,0.546037,0.829646,-0.116123,0.603992,0.790887,-0.098178,0.16245,0.714011,0.68099,0.163915,0.71221,0.682546,0.239387,0.692648,0.680349,0.303537,0.788995,0.534135,0.223365,0.697836,0.680502,0.353099,0.817072,0.455733,0.255837,0.703574,0.662923,0.442671,0.874813,0.196722,0.525925,0.84225,-0.118168,0.448256,0.87878,0.163549,0.513749,0.849574,-0.119327,0.589404,0.629353,-0.506455,0.617084,0.605335,-0.502701,0.606098,0.490158,-0.626362,0.578722,0.516526,-0.631062,0.646199,0.582263,-0.493301,0.633625,0.467483,-0.616382,0.57976,0.362255,-0.729789,0.551714,0.38847,-0.737999,0.6068,0.342235,-0.717368,0.583361,0.73571,-0.344035,0.611011,0.715018,-0.33964,0.641255,0.692648,-0.330149,0.564257,0.651814,-0.506638,0.560137,0.753105,-0.345042,0.54265,0.670766,-0.505509,0.54268,0.765984,-0.344584,0.551927,0.543504,-0.632374,0.522385,0.417615,-0.7434,0.52501,0.568285,-0.633534,0.486129,0.44673,-0.75103,0.417554,0.060854,-0.906583,0.50087,0.236671,-0.832514,0.532456,0.215064,-0.818659,0.457259,0.04825,-0.887997,0.56212,0.200903,-0.80224,0.494491,0.042329,-0.868129,0.375347,0.078097,-0.923551,0.46672,0.262886,-0.844386,0.318979,0.106937,-0.941679,0.418653,0.294473,-0.859035,0.296854,-0.133213,-0.945555,0.244697,-0.127445,-0.96118,0.143529,-0.330607,-0.932768,0.084384,-0.331767,-0.939543,0.185705,-0.109684,-0.97644,0.03058,-0.32551,-0.945036,0.348643,-0.136052,-0.927305,0.39845,-0.134465,-0.907254,0.207251,-0.32902,-0.921262,0.271706,-0.325327,-0.905698,-0.150151,-0.620502,-0.769677,-0.013367,-0.496963,-0.867641,0.056703,-0.499069,-0.864681,-0.080325,-0.631733,-0.770989,0.132237,-0.500595,-0.855495,6.1e-05,-0.643391,-0.765496,-0.203497,-0.613971,-0.762627,-0.072542,-0.497024,-0.864681,-0.234596,-0.614612,-0.753105,-0.115513,-0.497421,-0.859767,-0.268075,-0.71749,-0.642872,-0.312418,-0.702536,-0.639363,-0.36903,-0.796838,-0.478347,-0.40315,-0.774773,-0.486984,-0.331889,-0.698843,-0.633564,-0.412458,-0.766198,-0.492721,-0.203497,-0.739555,-0.64156,-0.124027,-0.76281,-0.634602,-0.313273,-0.827418,-0.466018,-0.239967,-0.859645,-0.450972,-0.486129,-0.871303,-0.066622,-0.441877,-0.84994,-0.286813,-0.39433,-0.881161,-0.260811,-0.44557,-0.894803,-0.027985,-0.329325,-0.914243,-0.235939,-0.390362,-0.920621,0.005188,-0.510269,-0.853633,-0.10419,-0.468886,-0.826777,-0.310678,-0.515915,-0.846004,-0.134465,-0.472945,-0.816706,-0.330546,-0.502396,-0.826319,0.254463,-0.527818,-0.824854,0.202429,-0.459243,-0.676504,0.57564,-0.489242,-0.69451,0.527512,-0.537584,-0.828181,0.158361,-0.504715,-0.714408,0.484603,-0.468398,-0.828455,0.306925,-0.431501,-0.834284,0.34312,-0.431684,-0.650868,0.62447,-0.42555,-0.633839,0.645833,-0.370098,-0.482467,0.793847,-0.396008,-0.525529,0.752953,-0.383465,-0.497543,0.778039,-0.377758,-0.481918,0.790582,-0.411878,-0.487075,0.770104,-0.430158,-0.49321,0.756096,-0.382946,-0.489151,0.783593,-0.422773,-0.550798,0.719626,-0.395367,-0.516007,0.75985,-0.440809,-0.581225,0.683981,-0.382092,-0.535264,0.753288,-0.386639,-0.521226,0.760765,-0.406812,-0.593463,0.694418,-0.405042,-0.556352,0.725516,-0.385388,-0.524613,0.759087,-0.389904,-0.535508,0.749107,-0.400311,-0.555345,0.728904,-0.458113,-0.576769,0.676321,-0.424879,-0.628224,0.651753,-0.483932,-0.653249,0.582263,-0.466292,-0.532853,0.706107,-0.483902,-0.582507,0.653066,-0.444288,-0.263741,0.856166,-0.439467,-0.253578,0.86169,-0.520676,-0.566515,0.638691,-0.439436,-0.255593,0.86111,-0.438276,-0.611988,0.658284,-0.453322,-0.665059,0.593402,-0.508499,-0.680288,0.527818,-0.446181,-0.459548,0.767907,-0.426862,-0.547075,0.720023,-0.398114,-0.372448,0.838282,-0.395459,-0.489639,0.777062,-0.414197,-0.214576,0.884487,-0.362438,-0.145756,0.92053,0.086856,0.506912,0.857601,-0.139561,0.254921,0.956816,-0.123417,0.282815,0.95117,0.116337,0.55736,0.822047,-0.273446,0.046236,0.960753,-0.282113,0.045656,0.958281,-0.103061,0.328196,0.938963,0.13126,0.592303,0.794916,-0.299631,0.052614,0.952574,0.027589,0.473067,0.880551,-0.158361,0.247963,0.955718,-0.275552,0.052278,0.959838,0.325571,0.705679,0.629261,0.265358,0.683187,0.680288,0.390606,0.750084,0.533616,0.379009,0.736015,0.560869,0.313028,0.734611,0.601947,0.263314,0.727348,0.633686,0.329875,0.75988,0.560076,0.256203,0.749229,0.610706,0.280038,0.675253,0.682333,0.329447,0.683645,0.651204,0.327006,0.677236,0.659078,0.320078,0.703787,0.634175,0.44438,0.779962,0.440626,0.509384,0.793207,0.333628,0.630421,0.776208,-0.004883,0.630116,0.774102,-0.06061,0.219245,0.696066,0.683645,0.394391,0.790185,0.469039,0.1854,0.717704,0.671194,0.375988,0.804102,0.460463,0.615558,0.787347,0.03354,0.603168,0.795709,0.054689,0.262978,0.722495,0.639363,0.18955,0.72045,0.667074,0.686148,0.563402,-0.460128,0.670705,0.566729,-0.478469,0.666463,0.675314,-0.315775,0.681082,0.668783,-0.298044,0.672719,0.455,-0.583422,0.657002,0.454604,-0.601367,0.646168,0.338847,-0.683828,0.62981,0.33375,-0.701376,0.694113,0.570421,-0.439039,0.682119,0.466292,-0.56325,0.695212,0.585772,-0.416547,0.685568,0.485916,-0.542039,0.65685,0.354717,-0.665304,0.662221,0.378094,-0.64687,0.687246,0.671896,-0.27604,0.684744,0.684683,-0.24958,0.549333,0.066073,-0.832972,0.605945,0.210211,-0.767205,0.618763,0.231574,-0.750633,0.565722,0.093539,-0.819239,0.626148,0.259163,-0.735343,0.575762,0.126133,-0.807794,0.526109,0.047945,-0.849055,0.58742,0.19892,-0.784417,0.471297,-0.098727,-0.8764,0.440748,-0.122562,-0.889187,0.366222,-0.286599,-0.88525,0.327128,-0.312052,-0.891934,0.492782,-0.066042,-0.867611,0.506027,-0.028809,-0.862026,0.39317,-0.252174,-0.884182,0.409009,-0.213141,-0.887265,0.118595,-0.632435,-0.765435,0.243568,-0.471511,-0.84753,0.273598,-0.441023,-0.854732,0.147771,-0.610218,-0.778314,0.289376,-0.405438,-0.867092,0.159978,-0.581744,-0.797479,0.071413,-0.644826,-0.760949,0.198248,-0.492508,-0.847407,-0.008332,-0.772973,-0.634327,-0.052767,-0.77514,-0.629536,-0.137028,-0.887112,-0.440687,-0.174169,-0.880734,-0.440382,0.015748,-0.760552,-0.649037,0.020295,-0.741295,-0.670858,-0.122806,-0.885006,-0.449049,-0.134159,-0.878628,-0.458205,-0.322489,-0.944884,0.056215,-0.245064,-0.947203,-0.20661,-0.244453,-0.951537,-0.186468,-0.328196,-0.939451,0.098453,-0.279763,-0.946318,-0.161809,-0.378796,-0.916898,0.125492,-0.343883,-0.938505,0.029756,-0.27253,-0.93704,-0.218299,-0.38139,-0.855922,0.349162,-0.402753,-0.844356,0.353282,-0.415479,-0.716544,0.560259,-0.429914,-0.659413,0.616688,-0.381085,-0.855953,0.349406,-0.433821,-0.838557,0.329478,-0.411847,-0.756951,0.507309,-0.464583,-0.764977,0.446028,-0.482864,-0.628742,0.609485,-0.480514,-0.537126,0.693228,-0.447523,-0.536546,0.715384,-0.441755,-0.627155,0.641469,-0.519608,-0.688925,0.505295,-0.514542,-0.614856,0.597613,-0.533891,-0.710868,0.457808,-0.537004,-0.681021,0.497757,-0.471084,-0.723655,0.504349,-0.500076,-0.753197,0.42729,-0.505387,-0.762658,0.403546,-0.518754,-0.77221,0.366771,-0.513596,-0.733543,0.445021,-0.528703,-0.733665,0.426801,-0.436995,-0.706748,0.556322,-0.486373,-0.739891,0.464736,-0.4944,-0.477554,0.726279,-0.521592,-0.510575,0.683523,-0.544939,-0.662587,0.51381,-0.530778,-0.646321,0.548173,-0.400128,-0.214942,0.890866,-0.41908,-0.232643,0.877621,-0.485702,-0.466201,0.739402,-0.404584,-0.214057,0.889065,-0.50795,-0.466842,0.723869,-0.433241,-0.221747,0.873562,-0.514359,-0.64217,0.568316,-0.531449,-0.638417,0.556719,0.501602,0.808374,-0.308054,0.464827,0.71688,-0.519578,0.478103,0.778466,-0.406629,0.516434,0.831996,-0.202582,0.396222,0.616352,-0.680471,0.406873,0.702384,-0.584002,0.476638,0.824488,-0.304971,0.504624,0.856716,-0.106449,0.416364,0.764855,-0.491531,0.517747,0.732963,-0.441176,0.488723,0.596606,-0.636525,0.489029,0.66689,-0.562181,0.422956,0.6133,-0.667013,0.408307,0.481338,-0.775597,0.53267,0.844997,-0.046815,0.551347,0.824183,-0.129246,0.571764,0.780053,0.254128,0.582842,0.772118,0.253151,0.576556,0.783197,-0.232673,0.582781,0.778314,0.233589,0.531846,0.846492,0.023713,0.510971,0.854274,0.095187,0.546434,0.793207,0.268654,0.512528,0.807642,0.291513,0.567797,0.500687,0.65334,0.535417,0.438246,0.721946,0.484909,0.549516,0.680319,0.533677,0.52617,0.662038,0.491867,0.419141,0.763115,0.418287,0.59624,0.685171,0.362957,0.739311,0.567125,0.44438,0.637715,0.629109,0.252663,0.828486,0.499741,0.556261,0.547746,0.624866,0.538957,0.446425,0.714286,0.524094,0.616199,0.587848,0.51854,0.513321,0.683798,0.50618,0.385083,0.771661,0.495193,0.428388,0.755791,0.585528,0.631886,0.507736,0.556902,0.677847,0.479934,0.518784,0.720634,0.459883,0.571764,0.617695,0.539872,0.511551,0.662099,0.547624,0.374004,0.450667,0.810541,0.265084,0.489029,0.830988,0.194006,0.682394,0.704733,0.299814,0.655965,0.692679,0.144963,0.523789,0.839381,0.066439,0.694449,0.716453,0.09238,0.829371,0.550951,0.187597,0.832911,0.520615,0.380932,0.359325,0.851894,0.269478,0.381054,0.884396,0.358379,0.346873,0.866695,0.245338,0.341868,0.907132,0.163823,0.407941,0.89816,0.159764,0.348888,0.923429,0.442946,0.423261,0.790307,0.458296,0.357616,0.813654,0.445662,0.372906,0.813807,0.362072,0.623066,0.693289,0.236396,0.821772,0.518387,0.71395,0.357311,-0.60213,0.817255,0.285134,-0.500717,0.842067,0.198767,-0.501389,0.742882,0.264382,-0.614978,0.89816,0.22013,-0.380566,0.916807,0.1395,-0.374126,0.860103,0.132176,-0.49266,0.766076,0.183477,-0.615955,0.927244,0.08655,-0.364269,0.686453,0.457991,-0.564776,0.789819,0.383679,-0.478469,0.670217,0.55266,-0.495315,0.771538,0.472365,-0.426099,0.869594,0.319285,-0.376568,0.839015,0.401715,-0.366894,0.593616,0.423688,-0.684133,0.568926,0.524735,-0.633168,0.459181,0.475509,-0.750328,0.440596,0.576342,-0.688223,0.555254,0.619068,-0.555315,0.430067,0.669576,-0.605548,0.621174,0.325144,-0.712973,0.644307,0.23249,-0.728538,0.480331,0.373028,-0.793786,0.498245,0.272134,-0.823206,-0.321299,-0.3408,-0.88348,-0.286843,-0.342357,-0.894681,-0.303507,-0.372143,-0.877132,-0.316263,-0.369518,-0.873714,-0.318735,-0.301462,-0.898587,-0.266335,-0.300394,-0.91586,-0.309214,-0.265908,-0.913022,-0.244728,-0.259255,-0.934263,-0.202521,-0.3455,-0.916288,-0.214362,-0.308267,-0.926817,-0.217994,-0.277779,-0.935575,-0.185156,-0.373669,-0.908872,0.203955,0.425703,-0.881558,0.228492,0.529435,-0.81698,0.318278,0.616565,-0.720084,0.300912,0.520615,-0.798975,0.250801,0.603381,-0.756951,0.335734,0.687033,-0.644368,0.113102,0.337352,-0.934538,0.134373,0.445235,-0.88525,0.156102,0.519456,-0.840083,0.179144,0.286264,-0.941221,0.093539,0.206458,-0.973968,0.289285,0.382336,-0.87756,-0.146977,0.051943,-0.987762,-0.03592,0.075991,-0.99646,-0.066286,0.040437,-0.996979,-0.169317,-0.005921,-0.985534,-0.112522,-0.019745,-0.993439,-0.199957,-0.071688,-0.977172,-0.143468,0.095767,-0.984985,-0.035279,0.09125,-0.995178,-0.15537,0.032228,-0.987304,-0.15775,0.095584,-0.982818,-0.158727,-0.036378,-0.986633,-0.166448,-0.106021,-0.980316,-0.197882,-0.223701,-0.954344,-0.180273,-0.172521,-0.968352,-0.265389,-0.194281,-0.944334,-0.29194,-0.234107,-0.927305,-0.212226,-0.254372,-0.94351,-0.19126,-0.16419,-0.967681,-0.220435,-0.217139,-0.950896,-0.232948,-0.138279,-0.962584,-0.156102,-0.095279,-0.983123,0.322275,-0.441664,-0.837275,0.286691,-0.454787,-0.843165,0.555773,-0.471175,-0.684866,0.599017,-0.449538,-0.662587,0.792566,-0.434126,-0.428175,0.806543,-0.402905,-0.432539,0.050783,-0.397717,-0.916074,0.025391,-0.419324,-0.907468,0.372082,-0.369152,-0.851619,0.100192,-0.339854,-0.935118,0.39552,-0.232093,-0.888638,0.146275,-0.235267,-0.960845,0.641163,-0.349712,-0.683065,0.810572,-0.286905,-0.510514,0.632344,-0.184088,-0.752464,0.246162,0.725059,0.643147,0.385968,0.454115,0.802972,0.505478,0.42616,0.750237,0.358776,0.725272,0.587542,0.444136,0.366008,0.817743,0.542711,0.336497,0.769524,0.551286,0.388165,0.738487,0.363048,0.676687,0.640522,0.628895,0.289224,0.721641,0.120151,0.733421,0.669027,0.265145,0.497238,0.826075,0.343211,0.391552,0.853725,0.124973,0.887814,0.442885,0.037935,0.867,0.496811,0.195044,0.886959,0.418622,0.173193,0.878994,0.444227,0.544023,0.208258,0.812769,0.40849,0.248329,0.878292,0.452834,0.13834,0.880764,0.597278,0.083682,0.797632,0.498489,0.015717,0.866726,0.640858,-0.054781,0.765679,0.488479,0.305918,0.817164,0.375286,0.331126,0.865719,0.64156,0.153966,0.751427,0.582659,0.267617,0.767357,0.731132,0.105625,0.673971,0.679037,0.227821,0.697836,0.714377,0.00766,0.699698,0.767693,-0.137181,0.625935,0.803461,-0.07535,0.590533,0.855434,-0.217933,0.469771,0.15302,-0.013886,0.988098,0.068422,-0.016968,0.997497,0.064211,-0.045106,0.996887,0.146672,-0.042604,0.98825,0.056856,-0.082064,0.994995,0.12833,-0.071322,0.989135,0.15421,0.033967,0.987426,0.077059,0.035554,0.996368,0.246467,-0.017212,0.968963,0.238685,0.022126,0.970824,0.352062,-0.03296,0.935362,0.337382,0.003784,0.941343,0.247963,-0.040864,0.967895,0.236763,-0.054689,0.97,0.366497,-0.047639,0.929167,0.202918,0.918821,0.338481,0.08713,0.937559,0.33668,0.09418,0.903775,0.417493,0.21424,0.882748,0.418073,0.100345,0.856838,0.505692,0.223792,0.833735,0.504746,0.192053,0.947844,0.25425,0.080203,0.964446,0.251717,0.183752,0.971068,0.152348,0.074313,0.9859,0.149907,0.330699,0.880703,0.339061,0.318674,0.912564,0.256172,0.311167,0.937712,0.154424,0.34376,0.841639,0.416456,0.353984,0.790185,0.500229,0.038911,0.56563,0.823725,0.066286,0.550432,0.832209,0.109958,0.427869,0.897122,0.092502,0.437971,0.894192,0.139775,0.354991,0.924345,0.136876,0.347209,0.927732,-0.052156,0.686483,0.725242,-0.021821,0.698355,0.715384,0.04239,0.560167,0.827265,-0.045747,0.663228,0.747002,0.08066,0.432661,0.897916,0.105747,0.319712,0.941588,0.204871,0.527268,-0.82461,0.096255,0.536241,-0.838527,0.090335,0.638264,-0.764489,0.195441,0.628956,-0.752434,0.084964,0.732597,-0.675283,0.188177,0.723045,-0.664632,0.214362,0.420667,-0.881497,0.10184,0.428663,-0.897671,0.221351,0.311167,-0.924192,0.105899,0.317209,-0.942412,0.324717,0.508682,-0.797327,0.339213,0.403699,-0.849666,0.350719,0.297555,-0.887936,0.31138,0.609973,-0.728629,0.302805,0.703635,-0.642781,0.521989,0.413099,0.746208,0.480483,0.280984,0.830744,0.328654,0.344432,0.879391,0.36195,0.443434,0.819941,0.403058,0.103977,0.909238,0.250771,0.220557,0.942564,0.090487,0.400555,0.911771,0.126408,0.471725,0.872616,0.027528,0.319254,0.947233,0.566607,0.416974,0.710654,0.511277,0.268868,0.816248,0.412641,0.066225,0.908444,0.54088,0.521378,0.659963,0.603107,0.537675,0.58916,0.538682,0.620685,0.569689,0.622059,0.639576,0.451613,0.369762,0.529832,0.763237,0.148625,0.543504,0.826106,0.364055,0.618885,0.696005,0.169469,0.628712,0.758934,0.477279,0.815424,0.327464,0.584918,0.796564,0.152623,0.61507,0.725944,0.307657,0.514878,0.718314,0.467818,0.432813,0.899777,0.055147,0.569384,0.815149,-0.106113,0.382458,0.86288,-0.330332,0.563433,0.722129,-0.401318,0.329295,0.820948,0.466445,0.301096,0.930052,0.210517,0.215766,0.832209,0.510697,0.225318,0.932737,0.281411,0.236274,0.958617,-0.158788,0.207678,0.977081,-0.045991,0.348827,0.715415,0.605365,0.193915,0.727836,0.657735,0.363994,0.848994,-0.382946,0.583819,0.712455,-0.389233,0.570849,0.668813,-0.476211,0.363475,0.797266,-0.481887,0.392468,0.880764,-0.264931,0.581072,0.761101,-0.288186,0.416974,0.855037,-0.308176,0.547685,0.74633,-0.378155,0.265023,0.861751,-0.432539,0.363018,0.862575,-0.352367,0.179785,0.806909,-0.562578,0.316019,0.791498,-0.523057,0.432142,0.818903,-0.377636,0.433424,0.74218,-0.511155,0.21366,0.864559,-0.454817,0.155004,0.916288,-0.369243,-0.120151,0.013855,-0.992645,0.301096,0.589038,-0.74987,0.40611,0.615436,-0.675466,0.152959,0.160802,-0.975036,0.476241,0.592792,-0.649403,0.345439,0.257149,-0.902493,-0.261299,-0.155705,-0.952605,0.220252,0.389599,-0.894223,-0.392285,-0.294748,-0.871303,-0.368389,-0.338664,-0.86578,-0.430769,-0.367931,-0.82403,-0.324809,-0.371441,-0.869747,-0.190893,-0.227088,-0.954955,0.071413,-0.116367,-0.990631,-0.364147,-0.372631,-0.853511,-0.153142,-0.333384,-0.930235,-0.338664,-0.343242,-0.876034,-0.388318,-0.351665,-0.851772,-0.381634,-0.389782,-0.838069,-0.351848,-0.40904,-0.841914,-0.256569,-0.400616,-0.879574,-0.309519,-0.478133,-0.821925,-0.236976,-0.310068,-0.920682,-0.262795,-0.334147,-0.905118,-0.358806,-0.471114,-0.80578,-0.279885,-0.404736,-0.87051,-0.457839,-0.701346,-0.546281,-0.436445,-0.664388,-0.606677,-0.385723,-0.560442,-0.732841,-0.374554,-0.633473,-0.677023,-0.455275,-0.736015,-0.500961,-0.438276,-0.757775,-0.483352,-0.615131,-0.784387,0.079287,-0.545579,-0.811029,-0.211035,-0.52324,-0.808374,-0.269692,-0.597797,-0.800623,-0.039949,-0.496994,-0.808863,-0.314158,-0.557298,-0.821589,-0.119785,-0.595508,-0.780328,0.190741,-0.556597,-0.814661,-0.162755,-0.636921,-0.6957,0.332072,-0.581805,-0.70101,0.412336,-0.613025,-0.606708,0.506027,-0.551378,-0.647023,0.526597,-0.641102,-0.73751,0.212195,-0.593951,-0.794153,0.128483,-0.63094,-0.642018,0.435499,-0.584002,-0.714377,0.385388,-0.541032,-0.550462,0.635792,-0.515122,-0.691641,0.50618,-0.534227,-0.653981,0.5356,-0.575793,-0.553392,0.601794,-0.495254,-0.571154,0.654561,-0.47438,-0.7257,0.498245,-0.404187,-0.580737,0.706626,-0.402783,-0.735099,0.545305,-0.555834,-0.473128,0.683493,-0.515641,-0.429945,0.741081,-0.516984,-0.537278,0.666341,-0.498978,-0.454299,0.737968,-0.426618,-0.368145,0.826075,-0.439192,-0.311319,0.842708,-0.590533,-0.544267,0.595813,-0.542589,-0.611957,0.575396,0.025117,-0.368572,0.929228,-0.029969,-0.496414,0.86755,-0.260506,-0.676931,0.688375,-0.226081,-0.526231,0.819697,0.257302,-0.127567,0.957854,0.226081,-0.2107,0.951018,-0.069735,-0.091678,0.993316,0.116611,0.072787,0.990478,-0.194525,0.077303,0.977844,-0.068178,0.221168,0.972839,-0.267739,-0.251228,0.930143,-0.330271,-0.118564,0.936399,-0.003876,0.631581,0.775262,0.112339,0.619221,0.777123,-0.096255,0.392987,0.914457,-0.177679,0.418012,0.890866,-0.327555,0.071749,0.942076,-0.392132,0.077242,0.916623,0.047884,0.69396,0.718375,0.178533,0.696127,0.695334,0.03473,0.69686,0.71633,0.159856,0.710624,0.685141,-0.18659,0.594409,0.782189,-0.087497,0.704031,0.704733,-0.292947,0.505539,0.811518,-0.149236,0.658803,0.737327,-0.039918,0.73394,0.67803,-0.339732,0.377758,0.861293,-0.526475,0.081057,0.846309,-0.467849,0.304941,0.829493,-0.77691,-0.1648,0.607593,-0.797815,-0.123417,0.590106,-0.664693,0.101321,0.740196,-0.657643,0.059236,0.750969,0.149388,0.765465,0.625843,0.158238,0.740959,0.652608,0.112888,0.703787,0.701346,0.039735,0.702811,0.710227,0.391125,0.842982,0.369274,0.367534,0.828089,0.4232,0.589801,0.806787,0.034639,0.587481,0.807459,0.053468,0.204047,0.817988,0.537767,0.431623,0.849025,0.304666,0.339335,0.826014,0.449995,0.501022,0.826868,0.25544,0.598559,0.800653,0.025178,0.652547,0.739494,-0.165258,0.647237,0.748527,-0.144078,0.657857,0.684469,-0.314097,0.659871,0.679678,-0.320231,0.035737,0.760765,0.648,0.161016,0.796228,0.583148,0.689596,0.622822,-0.369457,0.692618,0.604633,-0.393292,0.675954,0.701254,-0.226386,0.670003,0.711997,-0.210028,0.685965,0.529801,-0.498733,0.685934,0.508499,-0.520463,0.66744,0.425184,-0.611286,0.664846,0.402997,-0.628895,0.683004,0.645344,-0.34199,0.675039,0.560656,-0.479507,0.657949,0.581622,-0.478286,0.647969,0.464461,-0.603626,0.66689,0.720237,-0.190954,0.589679,0.180731,-0.787133,0.635517,0.308847,-0.707602,0.603595,0.363231,-0.709708,0.53853,0.241401,-0.807245,0.60329,0.379009,-0.701682,0.582568,0.156529,-0.79754,0.630757,0.286294,-0.721213,0.524522,0.031526,-0.850795,0.51503,0.005005,-0.857143,0.429609,-0.1507,-0.890316,0.418958,-0.178106,-0.890347,0.45616,0.097873,-0.884457,0.341014,-0.080782,-0.936552,0.167913,-0.534135,-0.828516,0.306619,-0.348033,-0.885891,0.198859,-0.279641,-0.939268,0.05591,-0.459548,-0.88638,0.226386,-0.329844,-0.916471,0.086947,-0.489761,-0.867489,0.163213,-0.555223,-0.815516,0.297494,-0.373241,-0.878719,0.008881,-0.707663,-0.706473,0.013825,-0.724021,-0.689627,-0.179022,-0.851558,-0.492721,-0.160375,-0.870235,-0.465743,-0.105289,-0.617908,-0.779138,-0.062593,-0.632252,-0.77221,-0.295846,-0.745598,-0.597095,-0.474837,-0.880032,0.005188,-0.438765,-0.893796,0.092593,-0.327525,-0.930021,-0.166601,-0.354595,-0.907559,-0.224799,-0.547899,-0.818934,0.170537,-0.499985,-0.823328,0.268502,-0.591083,-0.759117,0.272561,-0.532426,-0.761773,0.368999,-0.581072,-0.797143,-0.163854,-0.656117,-0.754601,-0.006806,-0.540513,-0.799371,-0.262307,-0.634053,-0.761925,-0.13187,-0.705283,-0.701529,0.10184,-0.516861,-0.836421,-0.182195,-0.617817,-0.7799,-0.100192,-0.707755,-0.705649,-0.032838,-0.467147,-0.804834,-0.366039,-0.416425,-0.80697,-0.418775,0.0936,-0.689962,-0.717734,-0.076785,-0.810266,-0.580981,-0.243294,-0.756859,-0.606586,-0.620746,-0.728629,0.289315,-0.55858,-0.758873,0.334727,-0.55031,-0.742637,0.381573,-0.613971,-0.728385,0.304056,-0.622333,-0.718497,0.310495,-0.563585,-0.756676,0.331309,-0.624897,-0.675375,0.391583,-0.56975,-0.713645,0.407483,-0.733512,-0.637867,0.234626,-0.713126,-0.630726,0.305887,-0.810114,-0.565905,0.153142,-0.81933,-0.513749,0.254341,-0.701712,-0.60979,0.368419,-0.859462,-0.482376,0.169134,-0.885403,-0.415357,0.208625,-0.816736,-0.49733,0.29252,-0.732536,-0.656911,0.17832,-0.769677,-0.636311,0.051271,-0.251106,-0.871242,-0.421705,-0.397107,-0.870083,-0.291879,-0.599902,-0.457839,0.656087,-0.548692,-0.468337,0.692496,-0.570177,-0.625446,0.532579,-0.621967,-0.595935,0.50792,-0.534318,-0.237434,0.811212,-0.477554,-0.230323,0.847865,-0.713004,-0.396588,0.578173,-0.659108,-0.194067,0.726554,-0.824763,-0.334574,0.455824,-0.828333,-0.45323,0.329203,-0.886715,-0.368542,0.279,-0.860256,-0.277749,0.427473,-0.71395,-0.535874,0.450636,-0.713034,-0.700522,-0.027894,-0.799615,-0.596149,0.071718,0.387371,0.49855,0.775445,0.427931,0.306619,0.850185,0.427412,0.25248,0.868068,0.384533,0.431745,0.815882,0.469069,0.16892,0.866817,0.484207,0.063478,0.872616,0.347026,0.582659,0.734886,0.484542,0.338176,0.806726,0.562029,0.234748,0.793085,0.297464,0.747856,0.593463,0.196356,0.835566,0.513047,0.171697,0.908078,0.38197,0.16007,0.916959,0.365429,0.32844,0.636311,0.697989,0.245521,0.81518,0.524552,0.166631,0.897122,0.409101,0.130039,0.923826,0.360027,0.167913,0.902768,0.395978,0.16303,0.927274,0.336985,0.302713,0.791162,0.531419,0.228309,0.866024,0.444777,0.250923,0.902432,0.350139,0.40025,0.775048,0.488937,0.330027,0.941923,0.061678,0.507584,0.846095,0.162694,0.234962,0.944853,0.228034,0.324473,0.94583,-0.007996,0.306009,0.816157,-0.490127,0.371563,0.758263,-0.535661,0.459761,0.827845,-0.321268,0.339396,0.897153,-0.282662,0.297494,0.669027,-0.681082,0.318217,0.699728,-0.639576,0.277047,0.424482,-0.861995,0.241218,0.537645,-0.807917,0.39494,0.737114,-0.548326,0.374401,0.546007,-0.749443,0.321146,0.314035,-0.893399,0.377636,0.875851,-0.300333,0.199042,-0.076388,-0.976989,0.161107,-0.028352,-0.986511,0.190497,0.231849,-0.953887,0.235633,0.15305,-0.959685,0.202704,-0.205054,-0.957518,0.175878,-0.147649,-0.973266,0.259041,-0.247536,-0.933592,0.246986,-0.15717,-0.956175,0.212867,-0.133,-0.967956,0.208197,-0.260842,-0.942625,0.246467,-0.332896,-0.910154,0.257393,0.073397,-0.9635,0.500381,-0.375988,-0.7799,0.470473,-0.260811,-0.842952,0.350047,-0.172826,-0.920621,0.361187,-0.285043,-0.887845,0.649953,-0.491043,-0.580004,0.606464,-0.383923,-0.696249,0.766839,-0.561113,-0.311563,0.743553,-0.474227,-0.471389,0.467574,-0.539506,-0.700186,0.610706,-0.654164,-0.446181,0.703543,-0.694998,-0.148167,0.332865,-0.414747,-0.846828,0.867458,-0.475387,0.146489,0.890896,-0.448195,0.073428,0.847316,-0.497848,-0.184729,0.833552,-0.549486,-0.056642,0.882565,-0.386761,0.267281,0.903287,-0.37965,0.199774,0.906919,-0.342753,0.244911,0.941191,-0.318918,0.111332,0.791192,-0.563982,0.236396,0.821345,-0.471419,0.321116,0.824183,-0.460677,0.329325,0.751793,-0.653462,0.088168,0.913205,-0.406232,0.031251,0.963439,-0.262642,0.052278,0.958861,-0.278603,0.054079,0.921537,-0.357524,0.151341,0.962279,-0.206061,0.177587,0.964904,-0.256966,-0.053804,0.907285,-0.412946,0.079073,0.936979,-0.253914,0.239906,0.891842,-0.369579,0.26072,0.891476,-0.256966,0.373119,0.905148,-0.236274,0.353282,0.764244,-0.638691,0.089206,0.734367,-0.678091,0.029389,0.729637,-0.6657,0.156255,0.806757,-0.544084,0.230354,0.721702,-0.129673,0.679922,0.831568,-0.304636,0.46437,0.71923,-0.581072,0.38081,0.660787,-0.402051,0.633778,0.754509,0.044343,0.654775,0.842219,-0.141942,0.520066,0.572344,0.045442,0.81872,0.653249,0.168645,0.73809,0.567461,-0.158208,0.808039,0.67626,-0.199377,0.709128,0.665944,-0.187262,0.722068,0.528184,-0.127171,0.839534,0.532487,-0.193487,0.824,0.77044,-0.196204,0.606525,0.776971,-0.234352,0.584277,0.823511,-0.159398,0.54442,0.847896,-0.219581,0.482498,0.661428,-0.083193,0.745354,0.767296,-0.092837,0.63451,0.64742,-0.016327,0.761925,0.774865,-0.066652,0.628559,0.810968,-0.105686,0.575427,0.508774,-0.085086,0.856655,0.481429,0.009674,0.8764,-0.129124,0.289193,-0.948485,-0.124058,0.369579,-0.920865,-0.063814,0.38084,-0.922422,-0.021516,0.281259,-0.95938,-0.104953,0.379467,-0.919218,-0.06943,0.406598,-0.910947,0.063509,0.384411,-0.920957,0.116184,0.25959,-0.958678,0.053255,0.439039,-0.896847,-0.151494,0.25309,-0.955473,-0.119602,0.338481,-0.933317,-0.079287,0.354228,-0.931761,-0.123173,0.136235,-0.982971,-0.175085,0.13361,-0.975433,-0.13303,-0.011261,-0.991028,-0.19422,0.003662,-0.980926,0.018769,0.106723,-0.99411,0.159215,0.085238,-0.983551,0.018616,-0.051698,-0.998474,0.163793,-0.083438,-0.98294,0.542863,0.22541,0.808985,0.688375,0.047578,0.723746,0.679525,0.038209,0.732627,0.535508,0.111057,0.837184,0.731925,-0.06241,0.678487,0.730552,-0.048799,0.681082,0.642262,-0.025178,0.766045,0.511063,-0.043733,0.858394,0.658742,-0.04355,0.751061,0.573931,0.270455,0.772942,0.705893,0.048219,0.706626,0.615833,0.280984,0.736045,0.73339,0.05942,0.677175,0.743156,-0.074618,0.664937,0.776757,-0.084475,0.624073,0.39143,0.251839,0.885037,0.476485,0.359905,0.802118,0.343547,0.16007,0.925382,0.526933,0.244118,0.814081,0.553362,0.386242,0.737938,0.654012,0.276864,0.70397,0.32551,0.070009,0.94293,0.323191,-0.114719,0.939329,0.137974,0.027161,0.990051,0.113651,-0.095035,0.988952,0.800043,0.27958,0.530778,0.847194,0.072024,0.526322,0.765374,0.068331,0.639912,0.677877,0.280984,0.679312,0.870846,-0.079134,0.485092,0.807794,-0.084597,0.583331,0.918424,0.260201,0.297891,0.943876,0.083499,0.319498,0.966155,0.252968,0.049959,0.992676,0.109622,0.050203,0.951506,-0.05649,0.302316,0.99939,-0.02826,0.019745,0.829035,0.306986,0.46733,0.934233,0.252846,0.251473,0.888089,0.222449,0.402203,0.957457,0.188147,0.218726,0.976409,0.213691,0.030427,0.985595,0.167608,0.021638,0.673971,0.358592,0.645863,0.780358,0.259713,0.568834,0.861904,0.29078,-0.415357,0.81814,0.280496,-0.501907,0.752556,0.36729,-0.546525,0.835994,0.266732,-0.479537,0.734977,0.26429,-0.624439,0.629475,0.431471,-0.646199,0.75396,0.149998,-0.639515,0.852718,0.045717,-0.520341,0.631794,0.275735,-0.724387,0.907743,0.135502,-0.396954,0.885189,0.082583,-0.457808,0.897214,0.041566,-0.439558,0.905362,0.050417,-0.421583,0.823267,0.113651,-0.556108,0.880123,0.086612,-0.46672,0.932096,0.268227,-0.243263,0.959136,0.182836,-0.215888,0.963408,0.115604,-0.241768,0.939726,0.17008,-0.296609,0.955809,-0.01059,-0.293741,0.465163,0.266793,-0.84402,0.294748,0.262185,-0.918882,0.238746,0.394208,-0.887448,0.391461,0.404462,-0.826502,0.211005,0.451613,-0.866878,0.356182,0.432203,-0.828425,0.526017,0.15714,-0.83578,0.336833,0.120273,-0.933836,0.565508,0.071505,-0.821589,0.356822,-0.002441,-0.934141,0.605762,0.263466,-0.750725,0.683248,0.151524,-0.714255,0.743675,0.101474,-0.660756,0.516098,0.421949,-0.745354,0.496109,0.381664,-0.779839,0.284066,-0.180853,0.941588,0.269692,-0.109439,0.956694,0.108219,-0.129582,0.985626,0.111606,-0.17597,0.978027,0.273141,-0.046846,0.960814,0.126225,-0.069674,0.989532,0.465957,-0.161596,0.8699,0.47972,-0.080416,0.873714,0.464339,-0.039491,0.884732,0.339457,-0.241585,0.909055,0.388745,-0.223029,0.893918,0.369091,-0.244362,0.896664,0.310007,-0.20011,0.929411,0.141606,-0.215369,0.966186,0.178869,-0.230659,0.95642,-0.475875,-0.128788,0.869991,-0.51207,-0.107974,0.852107,-0.270058,0.008026,0.962798,-0.26194,0.004944,0.965056,-0.5515,-0.092441,0.829005,-0.289895,0.013428,0.956938,-0.065188,0.079104,0.99472,-0.099307,0.067537,0.992737,-0.066042,0.08533,0.99414,-0.679342,-0.293161,0.672689,-0.734519,-0.248268,0.631489,-0.804987,-0.400616,0.437544,-0.857997,-0.344676,0.380749,-0.780084,-0.219184,0.585986,-0.891873,-0.306284,0.332713,-0.363079,-0.174535,0.91525,-0.545457,-0.386486,0.743675,-0.07416,-0.252297,0.964782,-0.244392,-0.539232,0.805872,-0.677633,-0.516526,0.523423,-0.425672,-0.685415,0.590747,-0.194861,-0.01825,0.980651,-0.102756,0.024537,0.994385,0.001862,-0.090365,0.99588,-0.580676,-0.730918,-0.358501,-0.332621,-0.860195,-0.386456,-0.302683,-0.779443,-0.548448,-0.497604,-0.693838,-0.520524,-0.297525,-0.628742,-0.718436,-0.419538,-0.617328,-0.665456,-0.615162,-0.755577,-0.224982,-0.344768,-0.902402,-0.25837,-0.591937,-0.787652,-0.170782,-0.315897,-0.925016,-0.210944,-0.720664,-0.60445,-0.339427,-0.770287,-0.606739,-0.196204,-0.783074,-0.519852,-0.341288,-0.849727,-0.495102,-0.181127,-0.763512,-0.633473,-0.125492,-0.855953,-0.509812,-0.085971,-0.595172,-0.603504,-0.530534,-0.437422,-0.577929,-0.688894,-0.620716,-0.543687,-0.564867,-0.406262,-0.536027,-0.739982,-0.253365,-0.275582,-0.927244,-0.244881,-0.236488,-0.940245,-0.219886,-0.11539,-0.968657,-0.18302,-0.134251,-0.973876,-0.333323,-0.458693,-0.823664,-0.275674,-0.413556,-0.867702,-0.124851,-0.312784,-0.941557,-0.266366,-0.476852,-0.837642,-0.038942,-0.345531,-0.93759,-0.20246,-0.469924,-0.859157,-0.036225,-0.173742,-0.9841,0.065615,-0.217902,-0.973754,-0.401044,-0.91583,-0.019379,-0.182531,-0.980041,-0.07831,-0.260628,-0.946715,-0.189123,-0.52031,-0.842372,-0.140172,-0.204962,-0.952086,0.226844,-0.070803,-0.985656,0.15302,0.024506,-0.880184,0.473983,0.057405,-0.906064,0.419141,-0.593402,-0.802179,0.065828,-0.394513,-0.862239,0.317545,-0.731956,-0.664785,0.149174,-0.603473,-0.711936,0.35905,-0.130009,-0.819788,0.557665,-0.704733,-0.705679,-0.072756,-0.815149,-0.579211,-0.001099,0.348399,0.143742,0.926237,0.346812,0.159276,0.924284,0.527085,0.187506,0.828852,0.547258,0.164983,0.820521,0.333872,0.176946,0.92584,0.517106,0.205145,0.830927,0.683737,0.207709,0.699515,0.699026,0.187597,0.690023,0.680013,0.219672,0.699484,0.118076,0.110324,0.986847,0.137455,0.124027,0.982696,0.135197,0.136937,0.981292,0.34785,0.126194,0.928983,0.075716,0.072848,0.994446,0.558733,0.157781,0.814173,0.711631,0.177587,0.679708,0.897397,0.21012,0.387921,0.890133,0.222663,0.397504,0.943205,0.224433,0.244819,0.948882,0.214881,0.231056,0.898618,0.206732,0.386914,0.950133,0.190252,0.246956,0.970061,0.232032,0.071505,0.974578,0.221046,0.036073,0.973449,0.194189,0.120914,0.817103,0.202033,0.539872,0.806574,0.218543,0.549211,0.80929,0.220008,0.544603,0.90762,0.187384,0.375591,0.82577,0.185919,0.532456,0.957335,0.190985,0.216773,0.982116,0.187078,0.019715,0.827265,0.218085,-0.517685,0.934751,0.228553,-0.271981,0.948027,0.258644,-0.185156,0.841853,0.275857,-0.463851,0.970702,0.230476,-0.067721,0.903958,0.275155,-0.32725,0.861507,0.118534,-0.493698,0.942412,0.155858,-0.295816,0.842738,0.206153,-0.497238,0.886349,0.139775,-0.441328,0.856685,0.173772,-0.485641,0.893033,0.130467,-0.430616,0.785943,0.272134,-0.555132,0.790948,0.311869,-0.526414,0.774621,0.250801,-0.580523,0.710593,0.307993,-0.632588,0.570971,-0.028626,-0.820429,0.763909,0.088107,-0.639271,0.678121,0.159307,-0.717429,0.490249,0.040742,-0.870602,0.582049,0.24958,-0.773858,0.411573,0.181707,-0.893063,0.583972,0.010651,-0.81167,0.772362,0.089541,-0.628803,0.385968,-0.133122,-0.912839,0.38554,-0.088839,-0.918393,0.240669,-0.204962,-0.948698,0.221961,-0.179846,-0.958312,0.319742,-0.043794,-0.946471,0.241493,0.12186,-0.962706,0.181433,-0.092837,-0.979003,0.067812,0.060976,-0.995819,-0.832301,-0.39967,-0.384045,-0.642598,-0.426435,-0.636525,-0.694449,-0.347728,-0.629902,-0.848598,-0.347758,-0.398602,-0.404309,-0.40492,-0.820063,-0.491012,-0.297555,-0.818751,-0.732536,-0.302072,-0.610004,-0.859798,-0.309885,-0.405805,-0.564135,-0.247139,-0.787805,-0.810694,-0.460829,-0.361034,-0.614673,-0.502457,-0.60799,-0.370464,-0.494613,-0.786187,-0.916898,-0.354259,-0.183752,-0.89285,-0.41377,-0.177709,-0.936766,-0.349498,-0.016663,-0.908383,-0.41496,-0.051149,-0.927732,-0.32197,-0.188604,-0.936308,-0.303049,-0.177252,-0.946715,-0.321024,0.025208,-0.948485,-0.30665,0.079226,-0.043367,-0.312784,-0.94882,0.106357,-0.25837,-0.960143,0.029786,-0.137761,-0.98999,-0.132115,-0.187353,-0.973357,-0.092196,-0.018586,-0.995544,-0.22425,-0.092074,-0.970153,-0.014496,-0.360485,-0.932646,0.105686,-0.269478,-0.957152,-0.213385,-0.365001,-0.906186,-0.173894,-0.44554,-0.878201,-0.305399,-0.238563,-0.921842,-0.385327,-0.167669,-0.907407,-0.866085,-0.438337,0.24015,-0.888089,-0.382,0.255593,-0.887265,-0.383801,0.255776,-0.847957,-0.442244,0.292093,-0.90289,-0.342784,0.259255,-0.913205,-0.34138,0.222419,-0.907559,-0.393658,0.146092,-0.913236,-0.350261,0.207953,-0.911222,-0.32197,0.256813,-0.819422,-0.533708,0.20893,-0.878353,-0.472274,0.073641,-0.764061,-0.551378,0.33491,0.949644,-0.095859,0.298227,0.871273,-0.098941,0.480697,0.837153,-0.101108,0.537492,0.929594,-0.114383,0.350291,0.953124,-0.150884,0.262185,0.891446,-0.179327,0.416089,0.981903,-0.187475,-0.025819,0.972716,-0.227302,-0.046327,0.985015,-0.16892,-0.034516,0.665365,-0.739647,0.100742,0.716605,-0.687857,0.115085,0.730949,-0.675771,0.094852,0.678976,-0.728782,0.088321,0.749229,-0.649586,0.129063,0.773003,-0.624989,0.108585,0.758446,-0.635029,0.146458,0.70455,-0.693869,0.148595,0.805048,-0.573473,0.151524,0.67803,-0.707755,0.19834,0.727897,-0.652364,0.211066,0.715049,-0.607837,0.345225,0.750694,-0.557939,0.35371,0.744621,-0.626911,0.229011,0.749413,-0.547197,0.372723,0.600024,-0.795434,0.084872,0.606159,-0.776391,0.17246,0.517716,-0.852077,0.076632,0.523545,-0.8399,0.142888,0.640889,-0.690512,0.335276,0.552293,-0.775475,0.305887,0.613575,-0.784753,0.087436,0.638691,-0.753502,0.155675,0.528794,-0.843806,0.091372,0.55211,-0.816431,0.169042,0.299112,-0.950621,0.082705,0.417005,-0.905576,0.077486,0.4232,-0.90051,0.099612,0.299722,-0.9476,0.110446,0.44142,-0.877285,0.188391,0.310953,-0.927213,0.208716,0.309061,-0.942167,0.129582,0.425398,-0.895749,0.128941,0.333445,-0.902676,0.271981,0.453352,-0.846828,0.278085,0.163549,-0.982513,0.088626,0.171636,-0.97586,0.134861,0.18656,-0.94174,0.279733,0.161077,-0.979583,0.12009,0.165349,-0.960418,0.224067,0.121097,-0.451704,-0.883877,0.049471,-0.47145,-0.88049,0.074618,-0.319742,-0.944548,0.167272,-0.30314,-0.938139,0.093966,-0.146977,-0.984649,0.20011,-0.141392,-0.969481,0.079104,-0.534196,-0.841639,0.026063,-0.543229,-0.839137,0.035157,-0.538102,-0.842128,-0.002441,-0.533311,-0.845882,0.239875,-0.426069,-0.87228,0.179083,-0.522874,-0.833369,0.4073,-0.396527,-0.822687,0.332835,-0.500778,-0.799005,0.126011,-0.546434,-0.827937,0.261299,-0.54973,-0.79339,0.296945,-0.284585,-0.911466,0.332194,-0.13773,-0.933073,0.461409,-0.269478,-0.845241,0.491867,-0.138615,-0.859523,0.013947,0.350536,-0.93643,-0.022462,0.347728,-0.937315,-0.05005,0.292489,-0.954924,-0.027528,0.296915,-0.954497,-0.057985,0.134892,-0.989135,-0.066744,0.209876,-0.975433,0.035127,0.358013,-0.933042,-0.008637,0.353832,-0.93524,0.041353,0.3408,-0.939207,-0.006134,0.333659,-0.942656,0.099643,0.364666,-0.925779,0.128391,0.370037,-0.920072,0.225135,0.38374,-0.895535,0.264992,0.384838,-0.884091,0.140233,0.356548,-0.923673,0.281411,0.37846,-0.881771,0.052339,0.314982,-0.94763,0.001831,0.226234,-0.974059,0.169408,0.341136,-0.924589,0.108097,0.251991,-0.961638,-0.085757,0.341258,-0.936033,-0.017609,0.413343,-0.910367,-0.055483,0.38258,-0.922239,-0.123295,0.304422,-0.944487,0.096622,0.507279,-0.856319,0.063051,0.47792,-0.876095,-0.052553,0.366131,-0.929044,0.019623,0.429121,-0.903012,-0.023225,0.370434,-0.928556,0.055361,0.423444,-0.904202,0.134617,0.515976,-0.845943,0.174841,0.498886,-0.848811,-0.08005,0.297342,-0.951384,-0.05884,0.331675,-0.941527,-0.041658,0.343272,-0.938292,-0.106479,0.251869,-0.961852,0.714286,-0.330821,-0.616657,0.675069,-0.405347,-0.616382,0.516953,-0.457533,-0.723472,0.579882,-0.362774,-0.729453,0.62328,-0.4626,-0.630451,0.43788,-0.522813,-0.731346,0.807917,-0.305246,-0.504044,0.784082,-0.363781,-0.502792,0.873379,-0.282083,-0.396954,0.854091,-0.334941,-0.397839,0.760247,-0.403516,-0.509079,0.840022,-0.366985,-0.399548,0.743461,-0.243263,-0.622944,0.831782,-0.228858,-0.505661,0.766228,-0.140934,-0.626881,0.852992,-0.137516,-0.503433,0.894375,-0.2125,-0.393567,0.913022,-0.131443,-0.38609,0.621235,-0.256203,-0.740532,0.646535,-0.140904,-0.749748,0.551408,0.36848,-0.748405,0.377331,0.389355,-0.840236,0.310831,0.355907,-0.881283,0.483535,0.33726,-0.807703,0.242866,0.271004,-0.931394,0.410932,0.259957,-0.873775,0.586413,0.387249,-0.711417,0.424726,0.39143,-0.816279,0.579577,0.425703,-0.694845,0.436384,0.401624,-0.805139,0.725639,0.326945,-0.605396,0.730216,0.377148,-0.569659,0.854427,0.282083,-0.436262,0.837306,0.363292,-0.408521,0.701773,0.449202,-0.552904,0.798181,0.46202,-0.386517,0.691183,0.280251,-0.666097,0.643941,0.207434,-0.736381,0.85342,0.212043,-0.476089,0.842769,0.137455,-0.520371,0.299692,0.692434,-0.65624,0.373882,0.772515,-0.51323,0.35078,0.741935,-0.571368,0.272225,0.6592,-0.700919,0.435438,0.835597,-0.334819,0.419538,0.813227,-0.403211,0.339274,0.695334,-0.633534,0.413739,0.771813,-0.482772,0.392285,0.662831,-0.637745,0.473647,0.735069,-0.485061,0.474349,0.82812,-0.298593,0.540086,0.787072,-0.298013,0.209937,0.603534,-0.769189,0.248421,0.60857,-0.753594,0.294504,0.58211,-0.757866,0.179998,0.571825,-0.800378,0.98291,-0.019532,-0.182897,0.978088,-0.060366,-0.199225,0.960326,-0.060396,-0.272195,0.965789,-0.013611,-0.258919,0.969481,-0.116581,-0.215613,0.950072,-0.12418,-0.286172,0.925169,-0.057558,-0.375103,0.930845,-0.001679,-0.365368,0.990143,-0.019898,-0.138524,0.985839,-0.05594,-0.158055,0.991699,-0.010376,-0.127934,0.987152,-0.040834,-0.154332,0.97821,-0.106357,-0.178167,0.979369,-0.086703,-0.182501,0.985473,0.011475,-0.169408,0.992492,0.007233,-0.12183,0.986145,0.036683,-0.161718,0.993439,0.031098,-0.109928,0.994293,0.010834,-0.106082,0.995575,0.033448,-0.087802,0.968047,0.023072,-0.249672,0.931578,0.044435,-0.360729,0.967284,0.054109,-0.247719,0.958678,0.19837,-0.203803,0.944151,0.2949,-0.146855,0.903684,0.338694,-0.261879,0.922513,0.243934,-0.29902,0.902402,0.420515,-0.093905,0.862758,0.452773,-0.224982,0.981811,0.138493,-0.129826,0.973571,0.223212,-0.047853,0.996429,0.063753,-0.054903,0.990387,0.120212,0.068087,0.93231,0.360668,0.02649,0.942137,0.237007,0.237007,0.959258,0.124302,-0.25367,0.978027,0.074923,-0.194403,0.954588,0.060427,-0.291696,0.969756,0.020142,-0.24311,0.990051,0.019288,-0.139195,0.977355,-0.042299,-0.207282,0.927946,0.164495,-0.334422,0.926572,0.09238,-0.364574,0.486312,0.873379,0.026154,0.494583,0.851131,0.175817,0.486648,0.861049,0.147374,0.481063,0.876278,-0.026307,0.509323,0.800989,0.314585,0.494247,0.812616,0.308725,0.527818,0.847926,0.04886,0.540727,0.820917,0.183477,0.604785,0.79516,0.043519,0.622517,0.763329,0.172582,0.559343,0.768975,0.309397,0.641469,0.708487,0.294137,0.472915,0.869198,-0.144261,0.51146,0.852168,-0.110324,0.582385,0.805322,-0.110569,0.464522,0.859798,-0.211921,0.534532,0.64507,0.545976,0.589312,0.600848,0.540056,0.57384,0.511307,0.639699,0.529405,0.56148,0.635914,0.653493,0.535356,0.535051,0.607776,0.444288,0.658162,0.51677,0.435987,0.736747,0.498337,0.484298,0.719077,0.518052,0.36024,0.77575,0.525834,0.72808,0.439741,0.579547,0.692099,0.430219,0.657216,0.629078,0.415082,0.509873,0.654103,0.558702,0.50383,0.73925,0.44676,0.506668,0.565539,0.650685,0.485824,0.481521,0.729423,0.327097,0.353465,0.87637,0.337657,0.303507,0.890957,0.283517,0.254769,0.924467,0.239601,0.300272,0.923246,0.358959,0.227607,0.905148,0.345103,0.194525,0.918149,0.295419,0.224067,0.928709,0.216987,0.269448,0.938231,0.38612,0.171453,0.906339,0.427015,0.416608,0.802515,0.425977,0.367809,0.826563,0.424329,0.283883,0.859828,0.334849,0.36021,0.870663,0.431043,0.411206,0.803156,0.226661,0.325724,0.917875,0.17008,0.30961,0.935514,0.252693,0.233192,0.938994,0.31196,0.178289,0.933195,0.218055,0.154088,0.963683,0.188421,0.206153,0.960204,0.367901,0.122776,0.92172,0.23899,0.103397,0.965484,0.150517,0.257363,0.954497,0.250954,0.253304,0.934233,0.331431,0.201972,0.921598,0.414716,0.148961,0.897641,0.192022,0.286905,0.938505,0.18479,0.303415,0.934751,0.022523,0.340953,-0.939787,0.114994,0.3755,-0.919645,0.08771,0.401776,-0.911496,0.002014,0.35844,-0.933531,0.246498,0.426099,-0.870418,0.213141,0.464492,-0.859523,0.036836,0.330882,-0.94293,0.134678,0.355937,-0.924741,0.272408,0.392712,-0.878353,-0.016449,0.323496,-0.946074,-0.008789,0.318491,-0.947874,-0.02765,0.336619,-0.941221,0.499008,0.54741,-0.671773,0.599597,0.605823,-0.522874,0.539689,0.674093,-0.504257,0.448347,0.607654,-0.655507,0.682363,0.647206,-0.339793,0.614826,0.721854,-0.317576,0.544725,0.484603,-0.684378,0.654225,0.530625,-0.538865,0.743736,0.561327,-0.362957,0.38197,0.485336,-0.786462,0.417066,0.436598,-0.797113,0.340739,0.535722,-0.772546,0.768822,0.639302,-0.013611,0.792627,0.599384,0.111484,0.713523,0.685141,0.146367,0.692099,0.721519,0.019044,0.804498,0.541124,0.244697,0.730216,0.627766,0.269448,0.838588,0.542375,-0.0506,0.863582,0.498856,0.073153,0.86816,0.437025,0.235023,0.736778,0.657186,-0.158788,0.803308,0.56444,-0.189856,0.6639,0.736106,-0.131657,0.716147,0.385815,0.581561,0.610096,0.27073,0.744621,0.615436,0.369274,0.69631,0.69921,0.464736,0.543199,0.514847,0.160588,0.842067,0.511063,0.267067,0.81698,0.720054,0.263375,0.641957,0.613727,0.140446,0.77691,0.726188,0.07358,0.683523,0.638173,-0.007965,0.769799,0.542894,0.051057,0.838221,0.610523,-0.063723,0.789392,0.784875,0.471114,0.402448,0.818995,0.36372,0.443709,0.847468,0.166601,0.503952,0.732566,0.551317,0.399182,0.465773,0.062868,0.882656,0.512833,0.047334,0.857143,0.423505,0.123661,0.897397,0.39552,0.144841,0.906949,0.555681,0.033448,0.830683,0.475082,0.107364,0.873348,0.561449,-0.020631,0.827235,0.6021,-0.034242,0.797662,0.65569,-0.113163,0.746483,0.674001,-0.126896,0.727714,0.619129,-0.048433,0.783776,0.657918,-0.146306,0.738701,0.459609,0.093966,0.883114,0.530778,0.002289,0.847499,0.626392,-0.096103,0.773522,0.431043,0.187811,0.882534,0.455672,-0.020386,0.889889,0.273476,-0.070193,0.959288,0.256844,0.03177,0.965911,0.417524,0.059297,0.906705,0.164068,0.10538,0.980773,0.475936,-0.118961,0.871364,0.284158,-0.18247,0.941221,0.474288,-0.246071,0.845241,0.278726,-0.311411,0.908444,0.546312,0.014344,0.837428,0.5815,-0.072054,0.810327,0.59444,-0.184851,0.782586,0.489669,0.087344,0.867489,0.906857,-0.317392,-0.277169,0.913419,-0.278237,-0.297006,0.890133,-0.292123,-0.349681,0.88226,-0.334697,-0.331004,0.922849,-0.217017,-0.318094,0.900632,-0.226203,-0.371044,0.8305,-0.321329,-0.45497,0.830348,-0.361553,-0.423933,0.830287,-0.253517,-0.496261,0.917966,-0.304331,-0.254341,0.923765,-0.268502,-0.272958,0.914121,-0.302103,-0.270333,0.920164,-0.269997,-0.283456,0.932401,-0.211493,-0.292978,0.929441,-0.216285,-0.298837,0.906217,-0.331584,-0.262276,0.917936,-0.31605,-0.239692,0.91287,-0.320322,-0.253059,0.925108,-0.302438,-0.229469,0.914579,-0.309519,-0.26014,0.923032,-0.290445,-0.252266,0.880367,-0.351329,-0.318522,0.832606,-0.376141,-0.406537,0.886349,-0.342051,-0.31196,0.221747,0.099673,-0.96997,0.106326,0.101382,-0.989135,0.107364,0.207373,-0.97232,0.223823,0.203742,-0.953063,0.215217,-0.008789,-0.976501,0.102786,-0.008759,-0.994659,0.355205,0.093783,-0.930052,0.348338,-0.011811,-0.937284,0.511246,0.080874,-0.855586,0.506302,-0.019776,-0.862087,0.35612,0.194342,-0.913999,0.508682,0.175481,-0.84286,0.78399,0.037873,-0.619587,0.779962,-0.042573,-0.624317,0.660054,-0.03122,-0.750542,0.663564,0.060854,-0.745628,0.871487,0.016175,-0.490097,0.866421,-0.051546,-0.496597,0.779565,0.110508,-0.616474,0.869594,0.075045,-0.487991,0.658406,0.145909,-0.738334,0.178137,0.980682,-0.080752,0.179235,0.983123,0.035829,0.308939,0.950285,0.038026,0.309763,0.947661,-0.077212,0.454054,0.890042,0.040315,0.457472,0.88638,-0.07062,0.069124,0.994171,-0.0824,0.070498,0.996918,0.033876,0.179174,0.965361,-0.18955,0.069918,0.978942,-0.191778,0.180731,0.941527,-0.28428,0.071932,0.954863,-0.288156,0.311045,0.932554,-0.183081,0.458693,0.872189,-0.169866,0.310648,0.910306,-0.273446,0.45558,0.853694,-0.252205,0.718772,0.693564,-0.047853,0.713645,0.698874,0.047212,0.815546,0.576525,0.04944,0.820765,0.570177,-0.035066,0.898892,0.435591,0.046968,0.904294,0.42616,-0.024964,0.598621,0.79873,-0.060274,0.593707,0.803461,0.043611,0.718955,0.683523,-0.125919,0.599292,0.78637,-0.149754,0.713279,0.675985,-0.185064,0.594073,0.77337,-0.221198,0.821284,0.56148,-0.100803,0.905942,0.416181,-0.077578,0.81698,0.557543,-0.147038,0.904263,0.412122,-0.111423,0.983276,0.179632,-0.029847,0.993744,0.104282,-0.039735,0.993896,0.087313,-0.067385,0.984802,0.162053,-0.06238,0.997375,0.056276,-0.045259,0.996429,0.046632,-0.069979,0.993927,0.07474,-0.08063,0.985534,0.150273,-0.078249,0.980224,0.196692,0.021302,0.992767,0.119907,0.00473,0.974029,0.207343,0.090701,0.989288,0.130314,0.065554,0.997864,0.064638,-0.007324,0.996582,0.069704,0.04413,0.957854,0.286264,-0.022889,0.953124,0.300241,0.036897,0.944578,0.307199,0.115482,0.960051,0.272378,-0.063662,0.960418,0.264595,-0.086856,0.903928,-0.176366,0.389569,0.900266,-0.158147,0.40556,0.913205,-0.139622,0.382763,0.916013,-0.149419,0.372265,0.898495,-0.128727,0.41963,0.911832,-0.117374,0.393414,0.943602,-0.10303,0.314615,0.943907,-0.102481,0.313852,0.942595,-0.093265,0.320566,0.904599,-0.193579,0.379681,0.901456,-0.168645,0.398572,0.91702,-0.201086,0.34434,0.915555,-0.172582,0.363231,0.899838,-0.134892,0.414777,0.914609,-0.138096,0.379955,0.910733,-0.182287,0.370525,0.910123,-0.209387,0.357494,0.921201,-0.177252,0.346324,0.918149,-0.216834,0.331553,0.919553,-0.223395,0.323191,0.923154,-0.239967,0.300241,0.920835,-0.147069,0.361095,0.942656,-0.096988,0.319285,0.930021,-0.13303,0.34254,0.940672,-0.096988,0.325114,0.885617,0.05942,0.460555,0.88113,0.106876,0.460585,0.897641,0.09064,0.431257,0.900082,0.050111,0.432813,0.877132,0.162816,0.451766,0.897122,0.136387,0.420118,0.933714,0.045259,0.355113,0.932859,0.015564,0.359874,0.936888,0.078646,0.340617,0.888333,0.054476,0.455947,0.883572,0.105777,0.45613,0.906674,0.042146,0.419691,0.903378,0.09415,0.418348,0.877285,0.169378,0.449019,0.896542,0.161321,0.412488,0.889798,0.020325,0.455824,0.891903,0.014496,0.451918,0.892972,-0.010254,0.449934,0.894406,-0.014557,0.446944,0.908261,0.003327,0.418317,0.90933,-0.023621,0.415357,0.903165,0.015137,0.428968,0.9335,-0.009919,0.358409,0.905972,-0.013733,0.423078,0.935026,-0.030335,0.353252,0.984161,-0.151494,0.091769,0.992126,-0.124088,-0.015595,0.980102,-0.193396,0.044008,0.960051,-0.249062,0.127323,0.988769,-0.126499,-0.07944,0.985321,-0.170141,-0.012452,0.966704,-0.234199,0.102817,0.942686,-0.294534,0.156652,0.977722,-0.201422,0.058596,0.999023,-0.034577,0.027223,0.995849,-0.046144,-0.078158,0.986816,-0.082125,-0.139286,0.949339,-0.200995,0.241432,0.983062,-0.026399,0.18131,0.878567,-0.259865,0.400678,0.930052,-0.034822,0.365734,0.913572,-0.331797,0.235023,0.897092,-0.383801,0.218787,0.844447,-0.411969,0.342235,0.834925,-0.47441,0.278939,0.747276,-0.335826,0.573382,0.71569,-0.375072,0.589099,0.711875,-0.238014,0.660695,0.721885,-0.218451,0.656606,0.645039,-0.446669,0.619953,0.667959,-0.276559,0.690878,0.744194,-0.466933,0.477584,0.687368,-0.537736,0.488174,0.599017,-0.631397,0.492416,0.752831,-0.321451,0.574328,0.760063,-0.438063,0.479934,0.74633,-0.318918,0.584124,0.756737,-0.438276,0.485,0.709311,-0.205817,0.674123,0.692282,-0.18952,0.696249,0.410535,-0.614734,0.673452,0.231971,-0.670522,0.704642,0.258126,-0.475417,0.841029,0.450117,-0.414289,0.791009,0.369213,-0.794305,0.482406,0.207495,-0.842189,0.497635,0.543718,-0.533189,0.648091,0.496109,-0.721183,0.483444,0.581195,-0.340159,0.739219,0.997131,-0.025178,0.071169,0.996124,-0.060915,0.063021,0.993225,-0.115757,-0.008881,0.996796,-0.079836,0.002533,0.990997,-0.12421,0.049898,0.983581,-0.179418,-0.01764,0.986755,-0.156957,-0.04062,0.992004,-0.123814,-0.024018,0.97589,-0.213385,-0.045198,0.985015,0.030396,0.169713,0.986633,-0.000275,0.162877,0.954985,0.080844,0.285348,0.959136,0.059267,0.27665,0.98761,-0.055544,0.146641,0.965728,0.018342,0.258889,0.996948,-0.012116,0.076968,0.984802,0.041383,0.168584,0.996307,-0.018494,0.083499,0.986511,0.034486,0.159917,0.954436,0.08768,0.285165,0.957732,0.086642,0.27427,0.99765,-0.065737,0.018067,0.994263,-0.106815,0.003143,0.996918,-0.067812,0.039369,0.994354,-0.098422,0.039338,0.932737,-0.283029,0.223304,0.907651,-0.348308,0.234107,0.89819,-0.378765,0.22306,0.934843,-0.298685,0.19187,0.871334,-0.431562,0.233436,0.846492,-0.471175,0.247719,0.933622,-0.261818,0.244423,0.918943,-0.310617,0.242897,0.934843,-0.241127,0.260567,0.927366,-0.274514,0.254097,0.897763,-0.375927,0.229438,0.916684,-0.320048,0.239204,0.94821,-0.241737,0.206,0.943602,-0.229987,0.238136,0.958312,-0.218574,0.183874,0.951201,-0.210395,0.225654,0.94058,-0.217505,0.260689,0.948302,-0.188421,0.255348,0.955931,-0.246437,0.15949,0.968017,-0.216407,0.126774,0.986114,-0.152104,0.066439,0.98645,-0.113163,0.118686,0.992645,-0.043703,0.112857,0.994812,-0.083834,0.057466,0.982543,-0.076388,0.1695,0.986541,-0.016907,0.162603,0.993255,0.014496,0.11478,0.997162,-0.023804,0.071108,0.987945,0.033387,0.150975,0.973022,-0.209998,0.09534,0.975097,-0.176977,0.133488,0.959838,-0.242347,0.141209,0.96234,-0.221351,0.157598,0.975219,-0.134281,0.175787,0.965972,-0.181616,0.184057,0.981017,-0.192511,0.022156,0.970702,-0.230354,0.067934,0.973174,-0.229957,-0.00412,0.970672,-0.233253,0.057955,0.961028,-0.239937,0.137211,0.966735,-0.208991,0.147313,0.990265,-0.138981,0.006317,0.995972,-0.085147,0.027161,0.978149,-0.205756,-0.029572,0.985626,-0.168554,-0.008606,0.953368,0.199744,0.226112,0.940672,0.186529,0.283395,0.959838,0.127689,0.249702,0.972808,0.131291,0.190771,0.924589,0.168249,0.341716,0.93997,0.120548,0.319224,0.976562,0.075167,0.201544,0.986175,0.07065,0.149876,0.923032,0.285928,0.257363,0.912656,0.264504,0.311533,0.900204,0.239326,0.36375,0.964538,0.207343,0.163152,0.933622,0.301218,0.193793,0.982391,0.13303,0.131046,0.992523,0.070589,0.099368,0.44261,0.641255,-0.626759,0.544023,0.702292,-0.45912,0.490555,0.743675,-0.454146,0.412458,0.664846,-0.622761,0.659688,0.704215,-0.262398,0.60506,0.757958,-0.24366,0.667287,0.657552,-0.349742,0.630024,0.58562,-0.509964,0.742302,0.654347,-0.144169,0.634999,0.534074,-0.558092,0.728202,0.559496,-0.395764,0.849544,0.339152,-0.404004,0.900693,0.337352,-0.273659,0.811945,0.537584,-0.227393,0.938414,0.307566,-0.157231,0.365978,0.535752,-0.760918,0.54561,0.461837,-0.699271,0.317698,0.390149,-0.864162,0.475112,0.342753,-0.810389,0.788476,0.304971,-0.534104,0.728233,0.226325,-0.64684,0.370708,0.544542,-0.752312,0.626545,0.468551,-0.622791,0.355235,0.388409,-0.850246,0.639241,0.321024,-0.698752,0.316446,-0.451979,-0.833979,0.356883,-0.457106,-0.814661,0.369976,-0.500198,-0.782861,0.339671,-0.491989,-0.801569,0.603809,-0.432325,-0.669668,0.598193,-0.47084,-0.648396,0.394757,-0.520554,-0.757042,0.374493,-0.506882,-0.776391,0.602649,-0.48793,-0.631428,0.302591,-0.382916,-0.872799,0.35316,-0.386914,-0.851772,0.295022,-0.275979,-0.914731,0.355358,-0.280801,-0.891537,0.617786,-0.368633,-0.694571,0.636494,-0.272286,-0.72158,0.417219,-0.442701,-0.793664,0.403088,-0.381512,-0.831813,0.605853,-0.41789,-0.676931,0.598315,-0.373058,-0.709067,0.398053,-0.280068,-0.873531,0.60387,-0.287851,-0.743278,0.442244,-0.47203,-0.762596,0.478866,-0.472854,-0.739616,0.626118,-0.429273,-0.650868,0.656728,-0.410291,-0.63271,0.775567,-0.631123,-0.010529,0.768517,-0.636982,0.059969,0.820795,-0.534837,0.200568,0.834559,-0.537828,0.119236,0.818476,-0.552355,0.158025,0.843135,-0.456618,0.283853,0.872524,-0.41081,0.264351,0.889492,-0.421125,0.177221,0.872768,-0.349925,0.340312,0.711814,-0.674856,-0.194433,0.709525,-0.690786,-0.139134,0.638539,-0.656545,-0.401471,0.63741,-0.679891,-0.362529,0.789178,-0.613483,-0.028138,0.746239,-0.618061,-0.247139,0.831019,-0.55324,-0.0571,0.77871,-0.590991,-0.210486,0.903897,-0.419172,-0.085177,0.8699,-0.45088,-0.199774,0.718436,-0.575671,-0.390393,0.829341,-0.443464,-0.339824,0.878506,-0.475234,0.04825,0.918912,-0.382427,0.096316,0.931852,-0.362712,-0.006958,0.95172,-0.30491,0.035401,0.991821,0.060091,0.112491,0.993622,0.030396,0.108432,0.992767,0.046419,0.110538,0.992309,0.063753,0.106021,0.99353,-0.025513,0.110599,0.991211,0.003845,0.132054,0.986297,0.039857,0.159886,0.988891,0.052309,0.139073,0.979858,0.00586,0.19953,0.989013,0.039186,0.142399,0.990753,-0.00293,0.135594,0.983001,0.002991,0.183477,0.982971,-0.046052,0.17777,0.990081,-0.069002,0.122257,0.981445,-0.113529,0.154454,0.990112,0.073031,0.119663,0.987793,0.061159,0.143132,0.98938,0.074435,0.12479,0.988586,0.064638,0.135868,0.984191,0.030366,0.174383,0.991241,0.068728,0.112674,0.989502,0.055544,0.133335,0.99002,0.068728,0.122776,0.989105,0.057222,0.135624,0.978057,-0.207465,0.017304,0.986175,-0.158238,0.048494,0.990417,-0.109012,0.084567,0.986145,-0.156774,0.054018,0.993378,-0.085055,0.077151,0.992706,-0.04944,0.109714,0.990509,-0.069582,0.118259,0.988739,-0.111789,0.099429,0.990783,-0.024323,0.13303,0.96823,-0.249641,-0.01355,0.978607,-0.20542,0.009888,0.960967,-0.273568,-0.040986,0.969878,-0.241615,-0.030824,0.991302,-0.126133,0.03708,0.973083,-0.23011,-0.009705,0.96588,-0.256935,-0.031678,0.974456,-0.222938,-0.026124,0.973083,-0.226264,-0.043489,0.963622,-0.263558,-0.043855,0.981323,-0.190374,0.026826,0.985229,-0.149541,0.083163,0.978545,-0.205573,0.012146,0.980712,-0.179632,0.076968,0.852687,0.51793,0.068087,0.912442,0.375225,0.163091,0.899594,0.375805,0.222388,0.830592,0.543016,0.123295,0.948882,0.228462,0.217627,0.938261,0.210578,0.274361,0.936308,0.229835,0.265419,0.897885,0.396039,0.192083,0.95114,0.08414,0.297006,0.924497,0.380078,0.02826,0.956328,0.27369,0.102542,0.980468,0.196539,0.003327,0.990204,0.129521,0.051729,0.975005,0.165136,0.148503,0.994354,0.062746,0.08536,0.767174,0.637043,-0.074709,0.877041,0.473556,-0.080782,0.963805,0.258583,-0.064852,0.728507,0.684133,-0.03415,0.82989,0.555071,0.055849,0.972289,-0.204932,-0.112461,0.978729,-0.198096,-0.053133,0.983978,-0.172674,-0.043916,0.981811,-0.155889,-0.108097,0.980224,-0.19776,0.005554,0.981597,-0.189245,0.025117,0.952025,-0.288156,-0.102847,0.965331,-0.257607,-0.041353,0.972289,-0.233619,0.005554,0.961791,-0.209357,-0.176397,0.933378,-0.307993,-0.184179,0.94702,-0.20249,-0.249214,0.90933,-0.304453,-0.283547,0.975646,-0.141392,-0.167608,0.964843,-0.126835,-0.230171,0.908231,-0.204657,-0.36494,0.93466,-0.149602,-0.32252,0.923521,-0.185339,-0.335765,0.891995,-0.229469,-0.389416,0.946623,-0.112613,-0.30192,0.937925,-0.159215,-0.308054,0.912687,-0.236427,-0.333293,0.876431,-0.274117,-0.395795,0.927396,-0.196142,-0.31843,0.949217,-0.127445,-0.287606,0.959258,-0.073092,-0.272866,0.85165,-0.284829,-0.439863,0.879879,-0.289926,-0.376476,0.736045,-0.392193,-0.551683,0.781762,-0.413892,-0.466384,0.82638,-0.297159,-0.478256,0.802332,-0.332957,-0.495315,0.694174,-0.389477,-0.605274,0.909299,-0.138005,-0.392529,0.940977,-0.173162,-0.29078,0.958617,-0.110385,-0.262337,0.932615,-0.062166,-0.355388,0.952788,-0.199988,-0.228431,0.967528,-0.143254,-0.208167,0.973022,-0.052614,-0.224555,0.953337,0.002197,-0.301889,0.885586,-0.214728,-0.411817,0.922086,-0.233467,-0.308512,0.864864,-0.282388,-0.414991,0.905118,-0.284036,-0.316324,0.93704,-0.250893,-0.242836,0.829035,-0.091037,-0.551683,0.800653,-0.193823,-0.566881,0.644551,-0.031648,-0.763878,0.619922,-0.168371,-0.76635,0.777978,-0.285409,-0.559679,0.860836,0.009583,-0.508774,0.894406,0.091861,-0.437635,0.679586,0.107212,-0.7257,0.999146,0.026673,-0.031251,0.998657,0.029542,-0.042299,0.999573,0.026551,-0.010407,0.999939,0.009461,-0.002808,0.999268,0.022004,0.030671,0.999542,-0.00766,0.029206,0.997375,0.042451,-0.058107,0.997314,0.031068,-0.066012,0.994659,0.055361,-0.087008,0.995849,0.029633,-0.085757,0.997528,0.068545,-0.014924,0.993164,0.103549,-0.053774,0.985656,0.133183,-0.10358,0.999329,0.031739,0.016755,0.998993,-0.004028,0.044588,0.969817,-0.209479,0.124699,0.971435,-0.215857,0.098392,0.969878,-0.193884,0.147313,0.963897,-0.198584,0.177343,0.970946,-0.22187,0.089267,0.976745,-0.176946,0.121036,0.970946,-0.145177,0.190161,0.962584,-0.161748,0.217383,0.976592,-0.204718,0.065645,0.973388,-0.223518,0.049989,0.963591,-0.260109,0.061525,0.965453,-0.208808,0.155797,0.974731,-0.202612,0.093966,0.957884,-0.201147,0.204901,0.955931,-0.172979,0.23719,0.968474,-0.194678,0.15537,0.959807,-0.165349,0.226691,0.98233,-0.067843,0.174352,0.986602,-0.102756,0.12656,0.94055,-0.141911,0.308573,0.965453,-0.056063,0.254372,0.935942,-0.303049,0.179296,0.921751,-0.283731,0.264229,0.907712,-0.243233,0.341807,0.970244,-0.216712,0.107913,0.949431,-0.295053,0.107212,0.983337,-0.143986,0.110691,0.922544,0.196844,0.331828,0.9082,0.213935,0.359691,0.911985,0.201605,0.357189,0.929929,0.183599,0.318522,0.889767,0.224525,0.397351,0.889248,0.215247,0.403607,0.921415,0.147282,0.359508,0.940519,0.133793,0.312204,0.920835,0.166875,0.352367,0.91171,0.178259,0.370128,0.919767,0.084872,0.383129,0.915738,0.085604,0.392499,0.898587,0.183996,0.398328,0.90817,0.084902,0.409864,0.933653,0.170415,0.314982,0.926603,0.148381,0.34547,0.942442,0.131687,0.30726,0.930998,0.114566,0.346477,0.921049,0.08475,0.380078,0.922666,0.084078,0.376263,0.943937,0.159185,0.289163,0.95465,0.118717,0.272927,0.954558,0.126774,0.269662,0.96469,0.099979,0.243538,0.945219,-0.088595,0.314097,0.945402,-0.116214,0.304422,0.953948,-0.121494,0.274148,0.955535,-0.093387,0.279672,0.945006,-0.145756,0.292734,0.951994,-0.15006,0.266732,0.958708,-0.121097,0.257302,0.962493,-0.091708,0.255287,0.954558,-0.150578,0.257057,0.930815,-0.080721,0.356426,0.932066,-0.109104,0.345439,0.912046,-0.072878,0.403485,0.91348,-0.103153,0.393506,0.932707,-0.141057,0.331797,0.945097,-0.062136,0.320811,0.929594,-0.055269,0.36436,0.945616,-0.036439,0.32313,0.929075,-0.03238,0.368419,0.910581,-0.046541,0.410627,0.95703,-0.064943,0.28251,0.965941,-0.061464,0.251259,0.958861,-0.035524,0.281594,0.968993,-0.029633,0.245216,0.976226,-0.017396,0.21601,0.967711,0.0936,0.233894,0.9841,0.064608,0.165319,0.987915,-0.019379,0.15363,0.994903,0.002289,0.100681,0.994354,-0.046937,0.095065,0.963042,-0.049623,0.264718,0.955931,0.065371,0.286203,0.952544,-0.116459,0.281167,0.953368,-0.031465,0.30015,0.979492,-0.099643,0.175054,0.966002,-0.132725,0.221717,0.981536,-0.149968,0.118473,0.969115,-0.183325,0.164892,0.952666,-0.175481,0.248146,0.955809,-0.210456,0.205115,0.989166,-0.082156,0.121525,0.993896,-0.081942,0.073702,0.990051,-0.119327,0.074129,0.996673,-0.030396,0.075411,0.998108,-0.022279,0.057192,0.997223,0.016999,0.07239,0.994049,0.015168,0.107822,0.996155,-0.057527,0.065889,0.997406,-0.035035,0.062563,0.995941,-0.02945,0.084902,0.99588,-0.069338,0.058046,0.99002,0.020997,0.139164,0.940855,-0.239814,-0.239204,0.952361,-0.221076,-0.209967,0.937681,-0.26838,-0.220679,0.925413,-0.28779,-0.246529,0.95642,-0.180944,-0.229133,0.966582,-0.165258,-0.195898,0.916562,-0.259774,-0.303995,0.934538,-0.19599,-0.296976,0.89935,-0.310007,-0.308237,-0.040559,-0.400281,-0.915464,-0.057375,-0.302347,-0.951445,-0.033601,-0.219977,-0.974914,-0.027924,-0.268319,-0.96292,-0.06946,-0.197241,-0.977874,-0.038392,-0.17066,-0.984558,0.022919,-0.432752,-0.901212,-0.007263,-0.332194,-0.943175,0.139561,-0.472793,-0.870022,0.090732,-0.371899,-0.923795,-0.026673,-0.216865,-0.975829,0.057894,-0.245918,-0.967528,-0.010956,-0.484451,-0.874722,0.069735,-0.509476,-0.857631,0.199103,-0.535905,-0.820429,-0.022584,-0.42024,-0.907102,0.494827,-0.483688,-0.721885,0.432142,-0.422987,-0.796411,0.237373,-0.411389,-0.879971,0.295358,-0.499527,-0.814356,0.378582,-0.310343,-0.871975,0.191168,-0.282693,-0.93994,0.704062,-0.420759,-0.572008,0.67098,-0.382122,-0.635395,0.63683,-0.29957,-0.71041,0.561083,-0.493027,-0.664876,0.734062,-0.424085,-0.53032,0.361003,-0.537675,-0.761925,0.896298,-0.065157,0.438581,0.897366,-0.095462,0.430799,0.898648,-0.099063,0.427259,0.897458,-0.067568,0.435804,0.909452,-0.064272,0.410749,0.910672,-0.090121,0.403119,0.938963,-0.063295,0.338084,0.940947,-0.078463,0.329295,0.895016,-0.037111,0.444472,0.907987,-0.039125,0.417096,0.936918,-0.047304,0.346294,0.896146,-0.039705,0.441908,0.884579,0.204321,0.419233,0.876858,0.203925,0.435316,0.873104,0.21839,0.435804,0.877804,0.223395,0.423688,0.909726,0.162969,0.381817,0.900662,0.16773,0.400769,0.949675,0.088931,0.300272,0.942686,0.098972,0.318613,0.898984,0.158879,0.408124,0.921354,0.121555,0.369152,0.914518,0.058412,0.40025,0.933836,0.034364,0.355968,0.953795,0.054628,0.295358,0.952788,0.01352,0.303293,0.890713,0.179449,0.417585,0.904813,0.077151,0.418744,0.971709,-0.236,0.007324,0.980895,-0.192083,0.030305,0.985168,-0.120792,0.121677,0.979919,-0.176885,0.091952,0.971831,-0.036958,0.232734,0.97467,-0.098056,0.200903,0.962035,-0.269845,-0.040407,0.969787,-0.242225,-0.028199,0.968444,-0.249031,-0.007538,0.965239,-0.257607,-0.043886,0.97412,-0.215583,0.067537,0.972747,-0.157537,0.170049,0.969634,-0.073977,0.233009,0.975982,-0.059206,0.20951,0.971221,-0.10535,0.213538,0.964843,-0.107913,0.23954,0.965392,-0.148289,0.214515,0.960143,-0.14124,0.241066,0.975524,-0.037355,0.21659,0.98001,-0.012635,0.198492,0.964995,-0.112186,0.236946,0.9729,-0.084597,0.215064,0.959044,-0.134648,0.249123,0.953734,-0.155644,0.257118,0.288736,0.033937,-0.956786,0.294717,0.215003,-0.931059,0.354045,0.207404,-0.911924,0.356822,0.025239,-0.933805,0.652882,0.157292,-0.740898,0.657765,-0.002899,-0.753166,0.408979,0.017853,-0.912351,0.430952,0.186834,-0.882778,0.290445,-0.133335,-0.947539,0.399915,-0.142216,-0.905423,0.357585,-0.140355,-0.923246,0.651662,-0.147557,-0.74398,0.485336,-0.551561,-0.678365,0.489029,-0.57506,-0.655812,0.55797,-0.626484,-0.544176,0.558306,-0.601093,-0.571764,0.648762,-0.534867,-0.541246,0.693045,-0.57857,-0.429975,0.423139,-0.519547,-0.742271,0.434187,-0.539018,-0.721702,0.618976,-0.503067,-0.603107,0.584948,-0.491012,-0.645527,0.526994,-0.471175,-0.707266,0.649983,-0.529557,-0.54503,0.992706,0.035279,0.115116,0.990295,0.063173,0.123661,0.989593,0.061312,0.129948,0.990509,0.041047,0.131077,0.988708,0.05414,0.139714,0.989013,0.040315,0.142033,0.995331,0.015656,0.095035,0.991577,0.049989,0.119266,0.994934,-0.01297,0.099521,0.996887,-0.04239,0.066408,0.992126,0.005219,0.124943,0.989929,0.014771,0.140568,0.981353,-0.191839,0.009003,0.978576,-0.204657,-0.021455,0.977233,-0.211127,0.019929,0.975311,-0.214331,0.053072,0.975188,-0.203284,0.087527,0.968505,-0.220099,0.116367,0.987091,-0.159276,-0.016327,0.981811,-0.185461,-0.040376,0.982574,-0.176366,0.058535,0.989868,-0.139958,0.023774,0.972625,-0.207404,0.104495,0.961669,-0.223975,0.158147,0.851711,-0.337168,-0.401105,0.861171,-0.318796,-0.395886,0.901212,-0.286508,-0.325114,0.893918,-0.31315,-0.320658,0.767052,-0.374493,-0.52089,0.780694,-0.368328,-0.504776,0.852382,-0.32609,-0.408734,0.765618,-0.34843,-0.540696,0.894681,-0.31312,-0.318522,0.981903,0.062075,-0.178777,0.989624,0.01352,-0.14301,0.993652,0.024781,-0.109592,0.989959,0.063631,-0.126133,0.969848,0.04413,-0.23957,0.983123,-0.010163,-0.182592,0.953917,0.159185,-0.25428,0.926908,0.141728,-0.347453,0.973266,0.153905,-0.170354,0.948241,0.028474,0.316263,0.94705,0.074435,0.312296,0.928068,0.081606,0.363323,0.929807,0.031617,0.366649,0.962127,0.030061,0.270882,0.960875,0.07004,0.267922,0.972045,0.033296,0.232337,0.970428,0.06534,0.232307,0.94705,-0.00763,0.320933,0.960875,-0.004242,0.276864,0.971252,0.002258,0.238014,0.929502,-0.005615,0.368725,0.942076,-0.199744,0.269356,0.943815,-0.174566,0.280557,0.932615,-0.17307,0.316538,0.932188,-0.201209,0.300821,0.947111,-0.198706,0.2519,0.949614,-0.176763,0.258797,0.947172,-0.198675,0.251686,0.950499,-0.177587,0.254891,0.939909,-0.221717,0.259621,0.944456,-0.216712,0.246986,0.937498,-0.240669,0.25132,0.941465,-0.232154,0.24427,0.94467,-0.214484,0.248146,0.94293,-0.22544,0.244972,0.93173,-0.225288,0.284738,0.931333,-0.245369,0.269051,0.989746,0.141911,0.014405,0.979583,-0.015015,0.200323,0.981445,-0.034028,0.188543,0.995819,0.089602,-0.016114,0.980773,-0.046785,0.189337,0.998993,0.037355,-0.023988,0.981475,0.173803,0.080386,0.973144,-0.001526,0.230171,0.972411,0.151708,0.177068,0.961119,-0.018555,0.27543,0.894253,0.370769,-0.250618,0.895871,0.423719,-0.133671,0.915525,0.401288,0.026765,0.905881,0.279458,-0.318155,0.91998,0.173681,-0.351299,0.998566,-0.052278,-0.011383,0.97647,-0.059908,0.207129,0.974181,-0.06296,0.216742,0.995941,-0.089755,-0.003449,0.972472,-0.065004,0.223609,0.992096,-0.125248,0.000793,0.999725,-0.010102,-0.019745,0.978851,-0.054537,0.19715,0.931608,-0.035249,-0.361675,0.929685,0.065676,-0.362346,0.92581,-0.126469,-0.35612,0.913236,-0.209174,-0.34959,0.985382,-0.170141,-0.005982,0.973083,-0.054292,0.223945,0.975921,-0.040498,0.214301,0.984008,-0.177068,-0.018555,0.980499,-0.022034,0.195196,0.983795,-0.174932,-0.038881,0.988128,-0.153539,0.000366,0.971954,-0.062899,0.226569,0.882992,-0.320475,-0.342875,0.897336,-0.275887,-0.344401,0.872127,-0.346965,-0.344951,0.865444,-0.356975,-0.351451,0.976379,-0.207831,-0.058565,0.987091,-0.033204,0.156621,0.983245,-0.069979,0.168279,0.96823,-0.249519,-0.015412,0.9682,-0.105106,0.226936,0.954375,-0.283212,0.094546,0.981842,-0.180212,-0.059053,0.985168,-0.01474,0.170782,0.861354,-0.397809,-0.315836,0.861446,-0.368297,-0.34962,0.869137,-0.437544,-0.230476,0.880947,-0.467605,-0.07242,0.909848,-0.240822,0.33787,0.920316,-0.083468,0.382153,0.907193,-0.058931,0.416517,0.896542,-0.195959,0.397229,0.905972,-0.040925,0.421308,0.895077,-0.155492,0.41789,0.931578,-0.278146,0.233955,0.942839,-0.106296,0.315806,0.877926,-0.400616,0.262093,0.882962,-0.455061,0.115116,0.875484,-0.329173,0.35374,0.883084,-0.257515,0.392193,0.909757,-0.076907,0.40791,0.912748,-0.010315,0.408368,0.916684,0.013153,0.399365,0.92233,-0.031037,0.385144,0.925932,0.04471,0.375011,0.938536,0.013184,0.34489,0.901028,-0.117954,0.417371,0.909787,-0.0271,0.414136,0.91937,-0.118503,0.375072,0.899411,-0.188055,0.394513,0.940367,-0.053163,0.335887,0.958647,-0.000793,0.284555,0.966613,0.057436,0.249672,0.953154,0.089053,0.28898,0.964599,0.090762,0.247505,0.974944,0.062166,0.213446,0.972869,0.078494,0.217505,0.980132,0.060457,0.188757,0.954466,0.04294,0.295175,0.939817,0.073183,0.33372,0.979339,0.044404,0.197241,0.971526,0.030702,0.23484,0.983612,0.049471,0.173315,0.985626,0.0524,0.160466,0.98413,0.037202,0.173437,0.979125,0.035676,0.200018,0.97882,0.009491,0.204352,0.984252,0.01532,0.17597,0.976989,-0.020783,0.212256,0.983306,-0.013367,0.181341,0.982879,0.052614,0.176458,0.977447,0.058748,0.202734,0.987304,0.038606,0.153966,0.986602,0.050386,0.155126,0.987823,0.017701,0.154424,0.987915,-0.012909,0.154332,0.977783,-0.078982,0.194037,0.96942,-0.084384,0.230323,0.964293,-0.115665,0.238166,0.973113,-0.112888,0.200659,0.958373,-0.147771,0.24424,0.966857,-0.147893,0.207984,0.98114,-0.04593,0.187628,0.973724,-0.05298,0.221351,0.985137,-0.085452,0.14893,0.987121,-0.048952,0.152196,0.981536,-0.122013,0.147191,0.975982,-0.157506,0.150426,0.952055,-0.206397,0.225745,0.947233,-0.200842,0.249733,0.94351,-0.217811,0.249611,0.945769,-0.223395,0.235786,0.941343,-0.229499,0.247291,0.941679,-0.231208,0.244423,0.959441,-0.180639,0.216254,0.95233,-0.177557,0.248024,0.95999,-0.213874,0.1807,0.968505,-0.18952,0.161382,0.951933,-0.226722,0.205847,0.946074,-0.226112,0.231971,0.938505,-0.229255,0.258095,0.936399,-0.246681,0.249519,0.93292,-0.247597,0.261422,0.938871,-0.215979,0.267983,0.929807,-0.233985,0.284036,0.941618,-0.184088,0.281869,0.939543,-0.232734,0.251045,0.93936,-0.239113,0.245705,0.944121,-0.186773,0.271493,0.94348,-0.212592,0.254219,0.947691,-0.145329,0.284127,0.953703,-0.079714,0.289926,0.954924,-0.037172,0.294473,0.936644,-0.140355,0.320872,0.947233,-0.074923,0.311594,0.963347,0.066652,0.259835,0.947233,-0.124516,0.295328,0.930357,-0.197485,0.308878,0.955596,0.146855,0.25544,0.958281,0.017548,0.285226,0.94058,0.291604,0.173833,0.974517,0.154363,-0.162542,0.983642,0.14771,-0.102817,0.992126,0.070742,-0.103092,0.984161,0.076907,-0.159581,0.946104,0.271157,-0.176977,0.957244,0.265084,-0.115635,0.884793,0.415906,-0.210059,0.897946,0.414747,-0.147069,0.949767,0.175878,-0.258827,0.920072,0.284707,-0.269051,0.863002,0.410504,-0.294443,0.962706,0.097964,-0.252083,0.685537,0.656209,-0.315195,0.672079,0.620563,-0.403943,0.55681,0.690786,-0.461196,0.568468,0.734916,-0.369701,0.43028,0.744346,-0.510636,0.437788,0.796594,-0.416791,0.792535,0.551927,-0.259255,0.776605,0.527665,-0.344066,0.701224,0.670614,-0.24192,0.807001,0.558275,-0.19245,0.582751,0.759392,-0.289254,0.447676,0.83105,-0.329997,0.182501,0.864223,-0.468825,0.184179,0.802759,-0.567095,0.080721,0.813044,-0.576525,0.077334,0.875607,-0.47676,0.303446,0.839747,-0.450209,0.300851,0.781518,-0.546495,0.181707,0.909482,-0.373882,0.307627,0.881344,-0.358531,0.074465,0.921995,-0.379894,0.939787,-0.074068,-0.333598,0.91699,-0.063967,-0.393689,0.909909,-0.146764,-0.387921,0.9317,-0.145299,-0.332865,0.827723,-0.059572,-0.557939,0.828486,-0.163213,-0.535691,0.947752,-0.004334,-0.318918,0.922422,0.017548,-0.385693,0.832148,0.04593,-0.552599,0.948576,-0.08887,-0.30372,0.959136,-0.032991,-0.280923,0.940397,-0.147618,-0.306284,0.331004,-0.010651,-0.943541,0.161931,0.009705,-0.986724,0.163793,-0.135716,-0.977081,0.341105,-0.164739,-0.925443,0.04294,0.015076,-0.998962,0.04181,-0.112888,-0.992706,0.355602,0.136967,-0.924528,0.188879,0.148381,-0.970702,0.063631,0.138035,-0.988372,0.592975,-0.041932,-0.804102,0.605701,0.095492,-0.789911,0.60683,-0.180273,-0.774102,-0.08771,0.009522,-0.996094,-0.048738,-0.073855,-0.996063,-0.043916,-0.122349,-0.991485,-0.080203,-0.093356,-0.99237,-0.084994,0.11243,-0.98999,-0.049471,-0.024018,-0.998474,-0.040254,0.012726,-0.999084,-0.028901,0.12244,-0.992035,-0.037843,-0.099979,-0.994263,0.751396,-0.318979,0.577593,0.731773,-0.13303,0.668386,0.828211,-0.088656,0.553331,0.798334,-0.304605,0.519425,0.739891,-0.319376,0.592029,0.690237,-0.16483,0.70455,0.759575,-0.461776,0.457991,0.752831,-0.450545,0.479781,0.750359,-0.553606,0.361156,0.741234,-0.552934,0.380505,0.787042,-0.455458,0.415998,0.782067,-0.53151,0.325327,0.792169,-0.580187,0.189276,0.829981,-0.513535,0.217627,0.870785,-0.447951,0.20249,0.840236,-0.515793,0.167089,0.900784,-0.376629,0.216132,0.877102,-0.441237,0.189703,0.755577,-0.598712,0.265694,0.794794,-0.547777,0.261086,0.767296,-0.622211,0.155156,0.741783,-0.620106,0.255287,0.808039,-0.573901,0.133,0.845088,-0.508042,0.166387,-0.176305,-0.059267,-0.982543,-0.124546,-0.113041,-0.985717,-0.088687,-0.10535,-0.990448,-0.093081,-0.040132,-0.994842,0.008423,-0.113773,-0.993469,0.028748,-0.034669,-0.998962,-0.166112,-0.087558,-0.982177,-0.10361,-0.142827,-0.984283,-0.214789,0.02533,-0.976318,-0.208258,0.030152,-0.977599,-0.190344,0.081484,-0.978301,-0.193426,0.123234,-0.973327,-0.097446,0.020905,-0.994995,0.040803,0.019227,-0.998962,-0.068911,0.047151,-0.99649,0.060274,0.030274,-0.997711,0.649342,-0.084475,0.75576,0.655782,-0.086795,0.749901,0.799921,-0.111728,0.589587,0.793908,-0.112522,0.597491,0.487747,-0.058382,0.871029,0.500626,-0.063875,0.863277,0.648152,-0.054079,0.759545,0.477981,-0.021241,0.878079,0.787317,-0.098025,0.608661,0.898648,-0.245003,-0.363811,0.891446,-0.323466,-0.31724,0.913694,-0.350475,-0.205695,0.939451,-0.256081,-0.227577,0.85577,-0.157811,-0.49266,0.859676,-0.215888,-0.462905,0.897336,-0.184606,-0.40083,0.897916,-0.126377,-0.421613,0.914701,-0.197394,-0.35258,0.879574,0.050508,0.473006,0.894742,-0.038697,0.444868,0.889401,-0.062197,0.452803,0.871426,0.016663,0.490188,0.935453,0.08533,0.342936,0.947264,-0.002686,0.320383,0.858425,0.13715,0.494247,0.929106,0.156499,0.335032,0.851772,0.166692,0.496658,0.939848,0.16596,0.298532,0.836207,0.100436,0.539109,0.801752,0.138707,0.581286,0.339335,-0.004486,-0.940642,0.374248,-0.092135,-0.922727,0.569201,-0.034425,-0.821467,0.50502,0.042146,-0.862056,0.172674,-0.029023,-0.984527,0.168584,-0.117435,-0.978637,0.317759,0.039308,-0.947325,0.173772,0.023164,-0.984497,0.309763,0.049135,-0.949522,0.178076,0.032838,-0.983459,0.469558,0.068545,-0.880215,0.458083,0.071993,-0.885983,0.011841,-0.092929,0.995575,-0.069185,-0.116642,0.990753,-0.122806,-0.001129,0.992401,-0.049715,-0.023896,0.998474,-0.063143,0.135441,0.988739,0.010132,0.076113,0.99704,0.083529,-0.111332,0.990265,0.016022,-0.141209,0.989837,0.17304,-0.071566,0.982299,0.21015,-0.082644,0.974151,0.146062,-0.011567,0.989196,0.160558,0.040223,0.986175,-0.044588,0.120273,-0.991729,-0.058626,0.288736,-0.955596,-0.036744,0.275979,-0.960448,-0.038484,0.157537,-0.986755,0.018586,0.079928,-0.996612,-0.023469,0.298837,-0.954009,0.111698,0.047975,-0.992553,0.090182,0.290048,-0.952727,-0.108249,0.064425,-0.992004,-0.01117,0.01529,-0.999817,0.091311,-0.006012,-0.995788,-0.120243,0.136052,-0.983367,0.748527,-0.070711,0.659291,0.742271,-0.095126,0.663289,0.744285,-0.088748,0.661916,0.744865,-0.035615,0.666219,0.770287,-0.084964,0.631977,0.757408,-0.005524,0.652882,0.714774,-0.086367,0.69396,0.728813,-0.086642,0.679189,0.554186,-0.068728,0.829524,0.618519,-0.065523,0.782983,0.758446,-0.024293,0.651265,0.690207,-0.041658,0.722404,0.471419,-0.022156,0.881619,0.759911,0.01944,0.649709,0.759728,0.068972,0.646535,0.879452,0.036164,0.474593,0.966033,0.059847,0.25135,0.956511,0.125828,0.26307,0.865474,0.127171,0.484512,0.996857,0.076235,-0.021455,0.9935,0.112064,-0.019654,0.878903,-0.06238,0.472884,0.962279,-0.02298,0.271035,0.999908,0.012391,-0.004791,0.802118,0.013794,0.596973,0.811914,-0.080996,0.578082,0.791925,0.106815,0.601154,0.882748,0.052797,-0.466842,0.771386,0.008576,-0.63628,0.76867,0.091494,-0.633045,0.881863,0.101108,-0.460494,0.635365,-0.037049,-0.771294,0.619098,0.06885,-0.78225,0.870663,-0.046815,-0.489608,0.761467,-0.08301,-0.642842,0.650838,-0.031953,-0.758507,0.961028,0.077181,-0.265389,0.962127,0.005707,-0.27253,0.958342,0.103702,-0.266091,0.335734,-0.033631,-0.941343,0.209326,0.010315,-0.977783,0.188299,-0.005005,-0.982086,0.310465,0.012329,-0.950499,0.370464,0.164159,-0.914212,0.223518,0.246467,-0.942991,0.485672,-0.052583,-0.872524,0.526719,0.066713,-0.847407,0.457808,0.039247,-0.88818,0.206366,0.072756,0.975738,0.261574,0.011628,0.965087,0.421613,-0.023347,0.906461,0.338206,-0.004273,0.941038,0.101382,0.11359,0.988311,0.13007,0.016907,0.991333,0.109439,0.11829,0.986908,0.036164,0.183782,0.982269,0.233314,0.037721,0.971648,0.635945,-0.100375,0.76513,0.486923,-0.068087,0.870754,0.549974,-0.00412,0.835139,0.700766,-0.027802,0.712821,0.36314,-0.041292,0.930815,0.442427,0.003296,0.896786,0.590106,0.046541,0.805963,0.712729,0.015625,0.701224,0.547777,0.04181,0.835566,0.432508,-0.213446,0.875973,0.358745,-0.149968,0.921293,0.043764,-0.316507,0.947569,0.095431,-0.242073,0.965545,0.249489,-0.101474,0.963042,0.040376,-0.182989,0.982269,0.700003,-0.126774,0.70278,0.376171,-0.269539,0.886441,0.651692,-0.139988,0.745415,0.282632,-0.226295,0.932127,-0.079318,-0.33961,0.937193,-0.142705,-0.285073,0.947813,0.802087,-0.0365,0.596026,0.813562,0.006378,0.581408,0.843318,-0.037355,0.536088,0.866604,0.004212,0.498947,0.680166,0.055605,0.730918,0.69573,0.039308,0.717185,0.632618,0.08591,0.769677,0.70333,0.127476,0.699301,0.674154,0.083438,0.733848,0.795312,0.123295,0.593463,0.705283,0.02768,0.708335,0.769982,0.025422,0.637532,0.716544,0.005646,0.697501,0.814386,0.007965,0.580218,0.756188,0.092959,0.647664,0.714621,0.017273,0.699271,0.933287,0.222846,0.281533,0.905942,0.076693,0.416364,0.693777,-0.015015,0.720023,0.853725,-0.030488,0.519791,0.839625,0.192053,0.508042,0.90994,0.177099,0.374981,0.950438,0.263009,0.165624,0.96646,0.242622,0.083865,0.860012,0.364849,-0.35667,0.939726,0.304117,-0.156194,0.93582,0.319041,-0.149693,0.885495,0.365551,-0.286782,0.94293,0.293283,-0.157567,0.911557,0.318339,-0.260109,0.871731,0.230171,-0.432508,0.973724,0.18244,-0.136174,0.911435,0.019868,-0.410901,0.999695,0.005799,-0.022797,0.795434,0.434248,-0.422651,0.815729,0.287362,-0.501938,0.724448,0.483871,-0.490921,0.771783,0.337962,-0.538591,0.856807,0.029786,-0.514725,0.830012,0.060305,-0.55443,0.839259,0.413678,-0.352824,0.882046,0.34077,-0.325297,0.759239,0.459731,-0.460585,0.811151,0.374859,-0.448836,0.55034,0.491501,-0.674917,0.644826,0.504135,-0.57445,0.647511,0.485733,-0.587146,0.514756,0.47972,-0.710501,0.688375,0.404004,-0.602374,0.525925,0.413373,-0.743278,0.655538,0.354045,-0.666982,0.724937,0.361553,-0.586261,0.738212,0.119663,-0.663808,0.801599,0.095706,-0.590136,0.409192,0.446272,-0.795831,0.507462,0.321512,-0.799402,0.174383,0.375286,-0.910337,0.210242,0.262154,-0.941832,0.566485,0.127995,-0.814051,0.210517,0.116642,-0.97058,0.355571,0.445692,-0.821528,0.343791,0.40315,-0.848079,0.146672,0.39552,-0.906644,0.140599,0.379589,-0.914396,-0.641407,-0.272469,0.717154,-0.53798,-0.289102,0.791803,-0.251473,-0.297983,0.920835,-0.360912,-0.324564,0.874264,-0.502029,-0.286752,0.815912,-0.240669,-0.256661,0.936033,-0.786462,-0.204169,0.582873,-0.711966,-0.252571,0.655171,-0.856166,-0.147649,0.495132,-0.818384,-0.212989,0.533708,-0.684133,-0.274514,0.67568,-0.797479,-0.239235,0.55385,-0.756157,-0.252022,0.60387,-0.878689,-0.168615,0.446608,-0.831751,-0.238899,0.501053,-0.935759,-0.168035,0.310007,-0.91232,-0.097385,0.397687,-0.950743,-0.116703,0.287057,-0.488266,-0.322001,0.81109,-0.560106,-0.296274,0.773583,-0.880947,0.195227,-0.431013,-0.911069,0.166662,-0.376995,-0.991058,0.06711,-0.115268,-0.98059,0.099338,-0.16895,-0.929014,0.125889,-0.34788,-0.995697,0.04001,-0.083377,-0.995422,-0.016022,0.094119,-0.998169,0.003815,0.06006,-0.991882,-0.028901,0.1236,-0.702841,0.269906,-0.658132,-0.738395,0.256691,-0.623554,-0.533464,0.309305,-0.787225,-0.540208,0.315226,-0.780236,-0.765862,0.215857,-0.60567,-0.552965,0.285409,-0.782769,-0.8446,0.151769,-0.513382,-0.666402,0.217566,-0.713095,-0.809564,0.000793,-0.586993,-0.643178,0.069185,-0.762535,-0.537217,0.236702,-0.809534,-0.544542,0.084567,-0.834437,-0.96704,0.065218,-0.24604,-0.999481,-0.020295,0.024415,-0.949339,-0.088412,-0.301462,-0.989654,-0.138188,0.037873,-0.314066,0.286172,-0.905209,-0.103793,0.306467,-0.946196,-0.075716,0.351939,-0.93292,-0.257179,0.335765,-0.906125,-0.054079,0.355602,-0.933042,-0.217994,0.34019,-0.914701,-0.36784,0.188971,-0.910459,-0.13834,0.201147,-0.969726,-0.393414,0.076571,-0.916135,-0.169805,0.091494,-0.981201,-0.430921,0.305032,-0.849239,-0.471541,0.214606,-0.855312,-0.492233,0.076479,-0.867092,-0.392132,0.335704,-0.856441,-0.369915,0.323283,-0.870968,-0.943419,-0.088626,0.319498,-0.934782,-0.125919,0.332133,-0.888974,-0.170446,0.425001,-0.906308,-0.11243,0.407361,-0.921781,-0.1395,0.361705,-0.871212,-0.1919,0.451827,-0.973815,-0.05884,0.21955,-0.968749,-0.07828,0.235328,-0.960997,-0.085818,0.262795,-0.937803,-0.067843,0.340434,-0.971129,-0.067019,0.228889,-0.912717,-0.087313,0.39906,-0.946348,-0.127232,0.296945,-0.917081,-0.063448,0.393567,-0.923154,-0.07831,0.376293,0.817255,0.213996,-0.53502,0.840297,0.245949,-0.483077,0.825312,0.247597,-0.507431,0.809564,0.222175,-0.543352,0.875729,0.299722,-0.37846,0.860073,0.309091,-0.405805,0.816675,0.23072,-0.528947,0.810266,0.216102,-0.544725,0.836451,0.307474,-0.453597,0.83169,0.195135,-0.519761,0.859218,0.232185,-0.455855,0.852657,0.16657,-0.495132,0.882443,0.208716,-0.421522,0.893063,0.282693,-0.349956,0.914029,0.257027,-0.313761,0.813288,0.208441,-0.543229,0.817805,0.178991,-0.546922,0.828883,0.227729,-0.51091,0.821619,0.189276,-0.537645,0.829463,0.138096,-0.541154,0.816797,0.132328,-0.56151,0.814234,0.225135,-0.535081,0.820215,0.225684,-0.52559,0.83694,0.245521,-0.489059,0.845241,0.239998,-0.477401,0.782373,0.26722,-0.562517,0.799921,0.272988,-0.534379,0.670644,0.265419,-0.692618,0.651082,0.265481,-0.71102,0.80929,0.241249,-0.535539,0.674795,0.223731,-0.703238,0.413068,0.234565,-0.87994,0.396741,0.240303,-0.885891,0.408979,0.186468,-0.893246,0.83105,0.253304,-0.495132,0.844356,0.265542,-0.465316,0.85406,0.246315,-0.458113,0.754357,0.225715,-0.616382,0.813288,0.211249,-0.5421,0.709922,0.147496,-0.68865,0.789666,0.140416,-0.597217,0.612812,0.226875,-0.75692,0.356578,0.208106,-0.910764,0.546739,0.148412,-0.824,0.285928,0.138462,-0.94818,-0.997925,0.026154,-0.058504,-0.947539,0.091281,-0.306284,-0.955229,0.076296,-0.285775,-0.999847,0.012848,-0.01059,-0.752159,0.147893,-0.64214,-0.745262,0.130009,-0.653951,-0.962462,0.019349,-0.270669,-0.998993,-0.031465,0.031068,-0.744835,0.061953,-0.664327,-0.99353,0.008209,-0.113071,-0.938932,0.068758,-0.337138,-0.984222,-0.040529,-0.172094,-0.924528,0.010926,-0.380932,-0.761193,0.123661,-0.636586,-0.762322,0.061922,-0.644215,-0.996246,-0.033601,0.079623,-0.998566,-0.046358,0.025727,-0.985748,-0.08124,0.147099,-0.990173,-0.087191,0.109043,-0.996185,-0.083621,-0.024232,-0.990356,-0.111698,0.08179,-0.990051,-0.046205,0.132725,-0.979675,-0.0824,0.182745,-0.976745,-0.095767,0.191748,-0.962188,-0.129246,0.239723,-0.168554,0.184912,-0.968169,0.096652,0.205023,-0.973968,0.110935,0.192846,-0.974914,-0.148289,0.167241,-0.97467,0.104526,0.139622,-0.984649,-0.148137,0.106479,-0.983184,-0.206763,0.166204,-0.964141,0.060366,0.181738,-0.981475,-0.255013,0.113895,-0.960173,0.002197,0.124912,-0.992157,-0.445112,0.173711,-0.878445,-0.479324,0.15363,-0.864071,-0.511246,0.09714,-0.853908,-0.421735,0.153539,-0.893582,-0.415021,0.085452,-0.905759,-0.959258,-0.114811,0.258095,-0.95407,-0.138157,0.265755,-0.943144,-0.104709,0.315409,-0.936186,-0.087252,0.340465,-0.942686,-0.179296,0.281289,-0.944487,-0.151006,0.291665,-0.90762,-0.011048,0.41963,-0.881222,-0.017701,0.472335,-0.942808,-0.02237,0.33253,-0.974242,-0.110599,0.196356,-0.965606,-0.129551,0.225318,-0.950804,-0.164983,0.262123,-0.95996,-0.108463,0.258156,-0.978057,-0.108737,0.177618,-0.957244,-0.113346,0.26606,-0.977599,-0.121372,0.171819,-0.928922,-0.08536,0.36024,-0.85934,-0.032991,0.510269,-0.920682,-0.090274,0.379681,-0.839839,-0.049379,0.540544,0.4597,0.098514,0.882565,0.247749,0.072604,0.966063,0.280618,0.069369,0.957305,0.480544,0.106571,0.870449,0.034394,0.044191,0.998413,0.072787,0.028169,0.996918,0.281716,0.044313,0.958464,0.476089,0.088321,0.874935,0.079073,0.002838,0.996857,0.417432,0.049257,0.907346,0.177465,0.026124,0.983764,0.391949,-0.054231,0.918363,0.116947,-0.097842,0.988281,-0.054292,0.008667,0.998474,-0.15183,-0.12122,0.980926,0.654256,0.120304,0.746605,0.633015,0.074252,0.770562,0.812525,0.132481,0.567614,0.797204,0.091433,0.596698,0.621204,-0.004028,0.783593,0.782769,0.03119,0.621479,0.666158,0.13773,0.732963,0.661977,0.131993,0.737754,0.823267,0.156621,0.545579,0.82522,0.165899,0.539842,0.969298,0.110416,0.219642,0.915342,0.128574,0.381573,0.92526,0.156346,0.345531,0.974548,0.139927,0.175085,0.929868,0.178106,0.321818,0.975799,0.169256,0.138401,0.960143,0.077181,0.268624,0.901303,0.091891,0.423322,0.948393,0.038606,0.314676,0.884854,0.042879,0.463881,0.993164,0.085513,0.079165,0.991302,0.061678,0.116092,0.996277,0.064638,-0.056917,0.994934,0.069857,-0.071902,0.991546,0.051271,0.118992,0.982604,0.102054,-0.154973,0.992767,0.115146,0.033265,0.988952,0.14771,-0.009827,0.992218,0.087069,-0.0889,0.983947,0.11887,-0.132939,0.940092,0.067812,-0.334056,0.978454,0.058565,-0.197913,0.977935,0.060305,-0.199988,0.950194,0.037965,-0.309305,0.967406,0.086673,-0.237861,0.940672,0.054445,-0.334849,0.889737,0.157476,-0.428419,0.956359,0.105808,-0.272286,0.812464,0.227515,-0.536729,0.893277,0.173711,-0.414563,0.880276,0.080935,-0.467483,0.82519,0.196448,-0.529527,0.774743,0.082247,-0.62685,0.750298,0.203436,-0.628986,0.794885,0.242836,-0.556017,0.778832,0.228095,-0.584246,0.899869,0.019837,-0.435652,0.896023,0.023988,-0.443281,0.796594,0.001679,-0.60445,0.808954,-0.005585,-0.587817,0.365093,0.033967,-0.930326,0.478164,0.129734,-0.868618,0.628773,0.183233,-0.755669,0.59444,0.067598,-0.801263,0.648671,0.098788,-0.754601,0.738517,0.179693,-0.6498,0.151158,-0.016877,-0.988342,0.30607,0.04239,-0.951048,-0.021332,-0.078219,-0.996704,0.102512,-0.055452,-0.993164,0.462386,-0.01059,-0.886593,0.197638,-0.115635,-0.973418,0.344218,-0.05182,-0.937437,0.093936,-0.086673,-0.991791,0.389111,-0.065737,-0.918821,0.123692,-0.090182,-0.988189,-0.083468,-0.118931,-0.98938,-0.071993,-0.107028,-0.991638,0.60506,-0.021363,-0.795862,0.64156,-0.036103,-0.766198,-0.365246,-0.035951,0.930204,-0.390851,-0.018403,0.920255,-0.511948,-0.046907,0.857723,-0.542619,-0.098666,0.834132,-0.478774,-0.114231,0.870449,-0.551317,-0.094638,0.828883,-0.64214,-0.107303,0.759026,-0.69451,-0.172063,0.698569,-0.612568,-0.086795,0.785607,-0.17243,0.010193,0.984954,-0.247963,-0.004608,0.968749,-0.354289,-0.124454,0.926786,-0.373608,-0.078616,0.924223,-0.147832,-0.020478,0.988769,-0.384259,-0.087588,0.919034,-0.145329,-0.040071,0.988556,-0.575152,-0.140843,0.805811,-0.730369,-0.198798,0.653432,-0.597613,-0.13538,0.790246,-0.755669,-0.177282,0.630482,-0.905271,-0.293283,-0.30726,-0.822932,-0.278176,-0.495315,-0.815088,-0.27607,-0.509293,-0.907102,-0.291757,-0.303354,-0.689505,-0.259774,-0.676046,-0.671621,-0.248878,-0.697806,-0.824976,-0.241859,-0.510758,-0.916868,-0.26017,-0.302713,-0.67925,-0.210822,-0.702963,-0.899197,-0.298776,-0.319559,-0.83227,-0.287637,-0.473861,-0.878323,-0.349834,-0.325755,-0.819361,-0.354045,-0.45085,-0.721824,-0.274911,-0.63509,-0.72103,-0.342357,-0.602405,-0.945311,-0.305948,-0.112949,-0.939573,-0.311991,-0.140873,-0.94705,-0.311533,0.077334,-0.945128,-0.322916,0.049013,-0.922605,-0.348186,-0.165899,-0.940428,-0.337962,0.036225,-0.949431,-0.297311,-0.100589,-0.9588,-0.265603,-0.100528,-0.951628,-0.294565,0.087039,-0.962004,-0.26014,0.082827,-0.342448,-0.191015,-0.919889,-0.333934,-0.209967,-0.918882,-0.10889,-0.142003,-0.983825,-0.178259,-0.139195,-0.974059,-0.336772,-0.255745,-0.906156,-0.072848,-0.195502,-0.977966,-0.517075,-0.231025,-0.824152,-0.547777,-0.251686,-0.797845,-0.557421,-0.304849,-0.77221,-0.353404,-0.181249,-0.917722,-0.505661,-0.214728,-0.835566,-0.355693,-0.144536,-0.923338,-0.510239,-0.174596,-0.842097,-0.216559,-0.149297,-0.964782,-0.214423,-0.122471,-0.969024,-0.873959,-0.288156,0.391308,-0.858211,-0.270882,0.435926,-0.917386,-0.315622,0.242347,-0.919675,-0.307932,0.243568,-0.826838,-0.21485,0.519761,-0.91345,-0.297769,0.277352,-0.80398,-0.24131,0.543443,-0.764183,-0.191198,0.615986,-0.710715,-0.130467,0.691244,-0.887661,-0.268807,0.373821,-0.829463,-0.242225,0.503281,-0.904691,-0.230445,0.358348,-0.850917,-0.208289,0.482192,-0.926847,-0.28489,0.244392,-0.940184,-0.247566,0.234016,0.345408,0.559526,0.753349,0.152135,0.530534,0.833888,0.148717,0.641285,0.752739,0.351482,0.654775,0.669088,-0.053835,0.474471,0.878597,-0.077212,0.592456,0.801843,0.154149,0.716483,0.680349,0.357524,0.715751,0.599872,-0.080203,0.68218,0.726737,0.34666,0.431196,0.832972,0.166997,0.389447,0.905759,0.361278,0.281503,0.888913,0.192053,0.233497,0.953185,-0.016205,0.33491,0.942076,0.023957,0.188788,0.981719,0.544298,0.566485,0.618702,0.542619,0.462172,0.701346,0.740013,0.542711,0.397259,0.746269,0.470077,0.471206,0.552538,0.332957,0.764061,0.757714,0.373272,0.535234,0.548235,0.640584,0.537645,0.546159,0.68865,0.476852,0.731864,0.594073,0.333811,0.715842,0.633381,0.293863,0.905118,0.419019,-0.071444,0.88873,0.447707,-0.098361,0.887143,0.380627,-0.260903,0.902585,0.358806,-0.237831,0.863948,0.49028,-0.11475,0.862239,0.419172,-0.28431,0.863613,0.484207,0.140355,0.847774,0.520615,0.100894,0.824488,0.560015,0.081057,0.921049,0.38786,-0.034364,0.877773,0.438093,0.193762,0.938566,0.34492,0.009278,0.893307,0.374371,0.248573,0.918424,0.335612,-0.209265,0.937071,0.30372,-0.172002,-0.556841,0.239204,0.795404,-0.759697,0.096103,0.643086,-0.806696,0.141606,0.573687,-0.614826,0.322489,0.719687,-0.844661,0.212592,0.491226,-0.647114,0.419507,0.636555,-0.493973,0.156896,0.85519,-0.715903,0.051668,0.696249,-0.441176,0.076968,0.894101,-0.678671,0.009674,0.734336,-0.298654,0.377514,0.876492,-0.238594,0.258217,0.936125,-0.185217,0.138859,0.972808,-0.346477,0.487411,0.801447,-0.364422,0.587725,0.722282,0.522355,0.0524,0.8511,0.469588,0.093142,0.877926,0.123447,0.081515,0.988983,0.150121,0.023103,0.988372,0.409558,-0.099826,0.906796,0.147893,-0.104801,0.983428,-0.225288,0.045778,0.973205,-0.194311,-0.017243,0.980773,-0.163244,-0.138371,0.976806,0.796228,0.057711,0.602191,0.717948,0.06415,0.693106,0.874142,0.023804,0.485031,0.81344,0.00238,0.581622,0.605121,-0.109195,0.788568,0.731529,-0.117527,0.67156,0.580187,-0.077364,0.810755,0.834223,-0.023408,0.55089,0.887326,-0.00238,0.461104,0.210761,-0.122318,0.969848,-0.159825,-0.17008,0.97235,0.705222,-0.059236,0.706473,0.824519,-0.024049,0.565294,0.833247,-0.013337,0.552721,0.719108,-0.027833,0.694327,0.677877,-0.093814,0.729118,0.784753,-0.057863,0.617054,0.692953,-0.111606,0.712272,0.756249,-0.1283,0.64156,0.641835,-0.075228,0.763115,0.619282,-0.107974,0.777673,0.798761,-0.090823,0.594714,0.807367,-0.099063,0.581652,0.64864,-0.079897,0.756859,0.842555,-0.029267,0.537767,0.667745,-0.044374,0.743034,0.819971,-0.07062,0.56798,0.96823,-0.058931,-0.242958,0.988647,-0.068178,0.133824,0.993774,-0.069124,0.087008,0.946135,-0.077151,-0.314371,0.976012,-0.025422,-0.216163,0.990722,-0.055147,0.124149,0.977081,0.008881,-0.212622,0.995392,0.001984,0.095553,0.910947,-0.104282,-0.39906,0.927824,-0.043825,-0.370403,0.847041,-0.141972,-0.512162,0.856014,-0.067995,-0.512406,0.932157,-0.001343,-0.36198,0.857418,-0.01355,-0.51442,0.882778,-0.119327,-0.454299,0.83932,-0.131413,-0.527451,0.673391,-0.107028,-0.731468,0.77868,-0.143071,-0.610859,0.804315,-0.108158,-0.584246,0.732505,-0.060945,-0.677969,0.608783,-0.060457,-0.791009,0.75338,-0.076205,-0.653127,0.571337,-0.026215,-0.820276,0.73806,-0.023194,-0.674306,0.477737,-0.059725,-0.876431,0.40846,-0.031373,-0.912229,0.175756,-0.03357,-0.983825,0.160741,-0.018952,-0.986785,0.367351,-0.025178,-0.929716,0.150822,-0.030793,-0.988067,0.547594,-0.015595,-0.836573,0.195593,0.007355,-0.980651,-0.861599,-0.151036,0.484573,-0.810205,-0.124943,0.572649,-0.911771,-0.212775,0.351207,-0.946623,-0.204596,0.249001,-0.682211,-0.270363,0.679281,-0.805933,-0.318552,0.498947,-0.916471,-0.283944,0.281808,-0.946837,-0.237739,0.216681,-0.849696,-0.353679,0.391003,-0.580004,-0.079928,0.810663,-0.564776,-0.02234,0.824915,-0.455641,-0.199927,0.867397,-0.862789,-0.217597,0.456313,-0.571001,-0.219855,0.790918,-0.952208,-0.194861,0.235084,-0.958739,-0.179479,0.220344,-0.803003,-0.333567,-0.493851,-0.855373,-0.304453,-0.419019,-0.656636,-0.253304,-0.710379,-0.627308,-0.268715,-0.730918,-0.898679,-0.241005,-0.366375,-0.698233,-0.193609,-0.689169,-0.484481,-0.2013,-0.851314,-0.498733,-0.207007,-0.841639,-0.495834,-0.155889,-0.854274,-0.928159,-0.352611,-0.118809,-0.947569,-0.319529,0.000824,-0.908353,-0.301981,0.289254,-0.874386,-0.301309,0.380322,-0.957945,-0.282022,0.052889,-0.870327,-0.311075,0.381726,-0.789209,-0.220038,-0.573321,-0.92581,-0.281686,-0.25193,-0.954314,-0.262276,0.142949,-0.630329,-0.142582,-0.763085,-0.529557,-0.09946,-0.842402,-0.317118,-0.093875,-0.943693,-0.127445,-0.048372,-0.990661,-0.16599,0.00174,-0.986114,-0.375072,-0.028291,-0.926542,-0.250496,-0.101016,-0.962798,-0.076785,-0.046266,-0.995972,-0.205512,-0.0889,-0.974578,-0.045106,-0.053316,-0.997528,-0.418287,-0.151616,-0.895535,-0.36961,-0.155797,-0.916013,-0.345958,-0.124943,-0.929868,-0.471053,-0.067415,-0.879482,-0.84518,-0.215369,0.48912,-0.820368,-0.298502,0.487655,-0.800256,-0.284646,0.527757,-0.831233,-0.233375,0.504532,-0.797662,-0.374157,0.472945,-0.794519,-0.341594,0.501999,-0.907163,-0.238136,0.346873,-0.876492,-0.314524,0.364391,-0.832636,-0.383587,0.399396,-0.875362,-0.138615,0.463118,-0.922056,-0.15247,0.355663,-0.888852,-0.190649,0.416578,0.343883,-0.042146,0.938047,0.322336,-0.031251,0.946104,0.168187,-0.080935,0.982421,0.139775,-0.0936,0.985717,-0.044374,-0.15598,0.986755,-0.09653,-0.159917,0.982391,0.513352,-0.01001,0.858089,0.462478,-0.0047,0.886593,0.650777,0.011628,0.759178,0.603015,0.014039,0.797571,0.368053,-0.051027,0.928373,0.547075,-0.012513,0.83697,0.388623,-0.064608,0.919095,0.570788,-0.022217,0.820765,0.677389,0.013672,0.735466,0.696097,0.00586,0.717887,0.141118,-0.105716,0.984313,-0.110446,-0.166784,0.979766,0.152013,-0.120457,0.980987,-0.105808,-0.177038,0.978484,0.863216,0.084262,0.497726,0.762535,0.038636,0.645741,0.77575,0.04001,0.629719,0.871975,0.081729,0.48262,0.787408,0.029878,0.61568,0.885098,0.065767,0.460677,0.849666,0.084872,0.520402,0.734489,0.040681,0.677358,0.954588,0.148106,0.258492,0.941343,0.150945,0.301767,0.978393,0.202277,-0.042177,0.974487,0.222114,0.032014,0.971068,0.139775,0.193457,0.98529,0.130345,0.110416,0.96942,0.179449,-0.167333,0.939634,0.187353,-0.286264,0.939543,0.189611,-0.285104,0.945097,0.21305,-0.247658,0.921384,0.171331,-0.348735,0.938322,0.143223,-0.314646,0.889431,0.195105,-0.413282,0.931791,0.174261,-0.318339,0.925199,0.274331,-0.262123,0.947325,0.264107,-0.181036,0.938963,0.196142,-0.28251,0.905271,0.288095,-0.312143,0.885983,0.256294,-0.386395,0.841365,0.322947,-0.433302,0.966216,0.139012,-0.216895,0.972716,0.150456,-0.176458,0.939177,0.19126,-0.285165,0.964995,0.171148,-0.198675,0.556444,0.366985,-0.745415,0.749809,0.325266,-0.576128,0.802149,0.284524,-0.524949,0.579669,0.341899,-0.739616,0.842555,0.249367,-0.47734,0.602649,0.30137,-0.738853,0.538865,0.381909,-0.750816,0.712699,0.360668,-0.601611,0.358074,0.378246,-0.853633,0.347301,0.386181,-0.854518,0.145482,0.372021,-0.916715,0.141606,0.376049,-0.915708,0.370891,0.355602,-0.857875,0.381481,0.31196,-0.870113,0.150884,0.34959,-0.92465,0.156102,0.301523,-0.94058,-0.521104,-0.246834,0.817011,-0.513199,-0.265999,0.815973,-0.674123,-0.262123,0.690512,-0.663137,-0.242134,0.708213,-0.781457,-0.230018,0.579974,-0.764855,-0.215247,0.607135,-0.329783,-0.218421,0.918424,-0.292093,-0.228034,0.92877,-0.516251,-0.233528,0.823969,-0.338939,-0.213446,0.916257,-0.503037,-0.232826,0.83227,-0.331675,-0.216498,0.91818,-0.648762,-0.229804,0.725425,-0.747551,-0.209998,0.630085,-0.631733,-0.231056,0.739921,-0.729667,-0.217689,0.648183,-0.946104,0.074038,-0.315195,-0.799127,0.161199,-0.579089,-0.813288,0.118198,-0.569689,-0.952025,0.03061,-0.304392,-0.587146,0.237831,-0.773705,-0.604633,0.200507,-0.770806,-0.823664,0.049471,-0.564867,-0.95407,-0.033815,-0.297647,-0.619373,0.134434,-0.773461,-0.938963,0.101749,-0.328532,-0.784143,0.186773,-0.591784,-0.569231,0.258278,-0.780511,-0.99881,-0.002441,-0.048585,-0.997742,0.023255,-0.062532,-0.984252,-0.058901,0.166509,-0.988159,-0.038942,0.148289,-0.998383,-0.041231,-0.038697,-0.994751,-0.096622,-0.033082,-0.979736,-0.090487,0.178625,-0.973144,-0.135838,0.185797,-0.214331,0.325632,-0.920865,-0.207465,0.334117,-0.919401,-0.046724,0.356487,-0.933103,-0.049257,0.352336,-0.934568,-0.383312,0.290414,-0.876736,-0.370678,0.304849,-0.877285,-0.224586,0.299387,-0.927305,-0.397931,0.259651,-0.879879,-0.232826,0.243721,-0.941465,-0.410504,0.199255,-0.889798,-0.053316,0.328715,-0.9429,-0.055788,0.276833,-0.959288,-0.894345,-0.13947,0.425001,-0.907956,-0.137242,0.395917,-0.951811,-0.089541,0.293283,-0.942747,-0.101108,0.317728,-0.838069,-0.178381,0.515519,-0.85461,-0.185247,0.485031,-0.881222,-0.153172,0.447157,-0.821711,-0.182348,0.539872,-0.867763,-0.179357,0.463424,-0.805261,-0.199011,0.558458,-0.933927,-0.12421,0.335124,-0.924161,-0.159825,0.346873,0.951567,0.013337,-0.307047,0.901975,0.362316,-0.23484,0.91229,0.377331,-0.159124,0.979339,0.040223,-0.198065,0.796869,0.589557,-0.13184,0.790338,0.606281,-0.087985,0.920621,0.377728,-0.098727,0.993439,0.039247,-0.107242,0.785089,0.616138,-0.063112,0.910245,-0.038728,-0.412214,0.886502,0.333476,-0.320719,0.864071,-0.089908,-0.495254,0.863247,0.298654,-0.406903,0.800836,0.566332,-0.194708,0.799066,0.535356,-0.273568,0.857326,-0.424055,-0.291757,0.764122,-0.504746,-0.401654,0.588366,-0.793908,-0.153233,0.474776,-0.847865,-0.235939,0.673025,-0.576342,-0.463454,0.29255,-0.921781,-0.254311,0.905423,-0.389355,-0.169042,0.920743,-0.384136,-0.068209,0.635731,-0.769616,-0.058931,0.647725,-0.761711,0.014252,-0.951415,-0.057497,0.302438,-0.975249,-0.116123,0.188025,-0.912717,-0.400952,0.078402,-0.911191,-0.336802,0.237159,-0.978973,-0.18067,0.094363,-0.884091,-0.465255,-0.043489,-0.688498,-0.724937,0.019807,-0.71453,-0.674245,0.186468,-0.574297,-0.809046,-0.124851,-0.907315,0.194983,0.372478,-0.944823,0.140477,0.295846,-0.775567,0.455824,0.436628,-0.826685,0.398663,0.397015,-0.971038,0.075808,0.226539,-0.875637,0.329997,0.352519,-0.906919,-0.019715,0.420789,-0.861995,0.233619,0.449812,-0.853206,-0.000855,0.521531,-0.81457,0.255898,0.520554,-0.727531,0.500687,0.469008,-0.687796,0.531968,0.49382,-0.870205,-0.304331,0.387402,-0.681814,-0.651875,0.331858,-0.81521,-0.287027,0.502976,-0.634907,-0.638874,0.43437,-0.31492,0.82281,0.473037,-0.553667,0.685018,0.473434,-0.506333,0.729972,0.459029,-0.282876,0.859035,0.426618,-0.4756,0.767327,0.430067,-0.264931,0.894498,0.360088,-0.363292,0.784448,0.502579,-0.614612,0.63155,0.47261,-0.429182,0.740654,0.516923,-0.684103,0.567888,0.457656,-0.116398,0.88641,0.447951,-0.141209,0.858241,0.493393,0.032716,0.910092,0.413068,0.028565,0.885311,0.464064,-0.183111,0.829554,0.527512,0.013306,0.86166,0.507248,-0.104099,0.916044,0.38731,-0.100772,0.94705,0.304758,0.028626,0.936644,0.349071,0.017365,0.964263,0.264321,-0.187872,-0.145024,-0.971404,-0.165685,0.005097,-0.986145,0.078463,0.044527,-0.995911,0.043977,-0.107212,-0.993255,0.382611,0.092166,-0.919279,0.336528,-0.07062,-0.938994,-0.429609,-0.186682,-0.88348,-0.422559,-0.025605,-0.905942,-0.730247,-0.234748,-0.64153,-0.748039,-0.055666,-0.661306,-0.199896,-0.36784,-0.908109,-0.41493,-0.408307,-0.813074,-0.182989,-0.639515,-0.746666,-0.380932,-0.65688,-0.650655,-0.654714,-0.461348,-0.598712,-0.543107,-0.641743,-0.541429,0.012604,-0.329295,-0.944121,0.265694,-0.305521,-0.914335,0.014893,-0.58797,-0.80871,0.181585,-0.520341,-0.834407,0.789331,-0.028871,-0.613239,0.858303,-0.015076,-0.512894,0.772942,-0.367016,-0.517502,0.688894,-0.35313,-0.632984,0.869442,-0.005219,-0.493973,0.800256,-0.378826,-0.464766,0.502609,-0.765893,-0.400922,0.434309,-0.716422,-0.545915,0.521531,-0.797357,-0.303598,0.809595,0.154942,-0.566118,0.862117,0.170751,-0.477004,0.858547,0.17835,-0.480636,0.621418,-0.04413,-0.78222,0.661031,0.131199,-0.738762,0.520981,-0.32313,-0.790002,0.310495,-0.625874,-0.715415,0.849483,-0.040193,-0.526048,0.837977,-0.071291,-0.541002,0.747276,-0.502609,-0.434645,0.779778,-0.450179,-0.435011,0.821284,-0.099796,-0.561693,0.701559,-0.551836,-0.450819,0.415265,-0.890835,-0.184118,0.468459,-0.860866,-0.198523,0.356792,-0.911863,-0.202918,0.826258,0.156133,-0.541215,0.822901,0.152593,-0.547258,0.821925,0.16831,-0.544115,0.859127,-0.013337,-0.511551,0.837336,0.1713,-0.519089,0.798273,-0.404828,-0.445906,0.50737,-0.827815,-0.239204,0.807001,-0.135014,-0.574877,0.824671,0.225776,-0.51854,0.839229,0.261483,-0.476699,0.825983,-0.123203,-0.550035,0.806757,0.435835,-0.398907,0.798669,0.490982,-0.34788,0.807245,-0.124577,-0.576891,0.820582,0.194372,-0.5374,0.821711,0.373058,-0.430769,0.623768,-0.612415,-0.485611,0.654256,-0.591144,-0.471633,0.27308,-0.92291,-0.27134,0.306223,-0.921476,-0.238868,0.625721,-0.609333,-0.486953,0.261727,-0.920682,-0.289468,-0.576586,0.610614,0.542802,-0.631642,0.521561,0.573534,-0.844569,0.309244,0.437056,-0.807398,0.404553,0.429456,-0.953001,0.075869,0.293222,-0.941893,0.164769,0.292611,-0.295816,0.741447,0.602252,-0.344096,0.673696,0.653951,-0.039338,0.797174,0.602405,-0.065249,0.747703,0.660787,-0.504349,0.684011,0.526994,-0.237983,0.79281,0.561022,-0.011078,0.834223,0.551286,-0.751762,0.491684,0.439406,-0.915647,0.250862,0.314005,-0.953093,-0.30076,0.033143,-0.933592,-0.349132,0.080355,-0.81579,-0.578082,0.016083,-0.834834,-0.545824,-0.0712,-0.906034,-0.382397,0.181188,-0.78457,-0.5992,0.159215,-0.60744,-0.794031,-0.022614,-0.612323,-0.779351,-0.132756,-0.577258,-0.805109,0.136113,-0.985168,-0.069521,0.156804,-0.975219,-0.1395,0.171606,-0.954039,-0.199194,0.223792,-0.969604,-0.241585,0.038118,-0.984252,0.005188,0.176641,-0.856197,-0.507736,-0.095096,-0.602557,-0.776757,-0.18305,-0.845698,-0.403821,0.348796,-0.914853,-0.259072,0.309671,-0.919065,-0.242317,0.310739,-0.845576,-0.388806,0.365764,-0.933042,-0.208655,0.293008,-0.865352,-0.363903,0.344554,-0.868801,-0.402387,0.28843,-0.927915,-0.245308,0.280648,-0.70629,-0.600452,0.37492,-0.738304,-0.605213,0.297647,-0.487381,-0.798547,0.35316,-0.527177,-0.802515,0.279336,-0.699393,-0.593799,0.397748,-0.721335,-0.588885,0.364483,-0.468062,-0.804346,0.365917,-0.478896,-0.820643,0.311686,-0.948546,-0.302866,0.092318,-0.930662,-0.275918,-0.240181,-0.814417,-0.526505,-0.243873,-0.822047,-0.564409,0.074984,-0.588702,-0.753533,-0.29255,-0.570696,-0.8211,-0.007355,-0.989624,-0.125889,0.069002,-0.963378,-0.089785,-0.252571,-0.904324,-0.331614,0.268654,-0.958678,-0.166631,0.230537,-0.769921,-0.5786,0.269021,-0.519364,-0.832209,0.194037,0.422315,0.091922,0.90173,0.24662,0.043916,0.968108,0.220618,0.108951,0.969237,0.388562,0.157659,0.907804,0.06943,0.010804,0.997497,0.053926,0.073611,0.995819,0.45439,0.072329,0.887845,0.268044,0.025605,0.963042,0.076418,-0.010285,0.997009,0.610462,0.153294,0.777032,0.642201,0.127415,0.755852,0.798151,0.214057,0.563097,0.816675,0.178411,0.548814,0.576891,0.219001,0.786889,0.776055,0.278268,0.565905,0.450301,-0.170385,0.876431,0.632557,-0.106998,0.767052,0.63216,-0.116459,0.766015,0.460616,-0.168767,0.871395,0.760735,-0.062441,0.646016,0.752281,-0.075503,0.654469,0.626667,-0.104709,0.772179,0.452712,-0.155339,0.877987,0.744407,-0.066073,0.664388,0.415265,-0.144871,0.898068,0.625385,-0.074038,0.776757,0.770592,-0.02707,0.636738,0.208991,-0.244575,0.946806,0.139958,-0.221381,0.965056,-0.070986,-0.317484,0.945585,-0.159154,-0.273568,0.948576,0.236671,-0.224403,0.94528,0.226173,-0.205481,0.952147,-0.016785,-0.277108,0.960662,-0.021821,-0.24247,0.969909,0.965911,0.245918,0.080569,0.920774,0.246834,0.301981,0.908475,0.304178,0.286538,0.954466,0.294137,0.049532,0.973022,0.203681,0.108127,0.928495,0.205023,0.30958,0.970306,0.224219,-0.090426,0.981506,0.184423,-0.050661,0.955565,0.190252,-0.225043,0.971709,0.154302,-0.178747,0.955382,0.264809,-0.130741,0.935942,0.224921,-0.270821,0.922758,-0.024201,0.384594,0.995758,0.01236,0.090884,0.994934,0.02533,0.097262,0.911649,-0.029389,0.409833,0.941465,0.05652,-0.332316,0.941893,0.075594,-0.327219,0.99414,0.06418,0.086703,0.905545,-0.012879,0.423994,0.930479,0.136418,-0.339946,0.935759,0.001312,0.352611,0.994629,0.034486,0.097324,0.954741,0.101749,-0.279397,0.851253,-0.037233,0.523392,0.867244,-0.005646,0.497818,0.837886,-0.049501,0.543535,0.826991,-0.0412,0.560656,0.902829,0.105777,-0.41673,0.931608,0.149144,-0.3314,0.907865,0.179449,-0.378887,0.877957,0.134495,-0.459395,0.924284,0.079196,-0.373363,0.95172,0.117924,-0.283364,0.866512,0.064577,-0.494949,0.883877,0.041047,-0.465865,0.814631,0.031404,-0.579089,0.813929,0.005737,-0.580889,0.847072,0.096316,-0.522629,0.814753,0.07358,-0.57506,0.828822,0.050691,-0.557176,0.794641,0.166967,-0.583636,0.834529,0.160497,-0.527024,0.890408,0.0253,-0.45439,0.868252,0.12363,-0.480392,0.93112,-0.016053,-0.364299,0.822321,0.065401,-0.565233,0.827784,0.151616,-0.540117,0.860988,0.043947,-0.506668,0.840632,0.07709,-0.536027,0.880551,0.118534,-0.458846,0.849971,0.146947,-0.505905,0.927671,-0.009064,-0.373211,0.957854,-0.049196,-0.282968,0.947844,0.064882,-0.31196,0.973205,0.024232,-0.228553,0.558489,-0.009522,-0.829432,0.467116,-0.050813,-0.882717,0.682394,-0.025849,-0.730491,0.722312,0.006867,-0.691519,0.326456,-0.01587,-0.945067,0.208045,-0.064699,-0.975951,0.080142,-0.014924,-0.996643,-0.013581,-0.069185,-0.997497,0.643513,0.05826,-0.763176,0.448042,0.056734,-0.892178,0.188482,0.055452,-0.980499,0.758782,0.063295,-0.648213,0.776391,-0.10181,-0.621937,0.769097,-0.017151,-0.638844,0.512253,-0.112033,-0.851466,0.445112,-0.138768,-0.88464,0.183813,-0.177496,-0.966796,0.128117,-0.144475,-0.98117,0.916562,-0.055361,-0.396008,0.860591,0.06476,-0.505142,0.720481,-0.067171,-0.690176,0.92465,-0.064119,-0.375347,0.67687,0.035493,-0.735191,0.921628,0.022217,-0.387371,0.380505,-0.061037,-0.922727,0.10654,-0.071566,-0.991699,0.359355,0.031251,-0.932646,0.116977,-0.000977,-0.993133,-0.395062,-0.040559,0.917753,-0.390606,-0.072848,0.917661,-0.615192,-0.105991,0.781182,-0.63094,-0.069948,0.772637,-0.778466,-0.138249,0.612232,-0.799829,-0.101352,0.591571,-0.143223,-0.014588,0.989563,-0.144078,-0.041047,0.988708,-0.408277,0.009339,0.912778,-0.152898,0.043397,0.987274,-0.650777,-0.030152,0.75866,-0.820612,-0.070345,0.567064,-0.553972,-0.39198,0.734458,-0.56148,-0.277322,0.779595,-0.407666,-0.290017,0.865841,-0.344066,-0.370342,0.862789,-0.666616,-0.384075,0.638783,-0.627583,-0.238624,0.74105,-0.69747,-0.339824,0.630848,-0.65804,-0.195379,0.727165,-0.494491,-0.376141,0.783532,-0.650929,-0.411023,0.638203,-0.464522,-0.302835,0.832148,-0.629292,-0.34434,0.696677,-0.709403,-0.419965,0.565996,-0.712882,-0.38377,0.586932,-0.268838,-0.326914,0.905972,-0.25547,-0.269875,0.928373,-0.949461,-0.162297,-0.268593,-0.871761,-0.130863,-0.472091,-0.900479,-0.062593,-0.430342,-0.968322,-0.103519,-0.22721,-0.724235,-0.089694,-0.683676,-0.748253,-0.016175,-0.663167,-0.931578,-0.214789,-0.293222,-0.845027,-0.190985,-0.499374,-0.698965,-0.155126,-0.698111,-0.979675,-0.179876,-0.088473,-0.96942,-0.224342,-0.099216,-0.980895,-0.182562,0.066805,-0.972533,-0.220618,0.073733,-0.989105,-0.132542,-0.063814,-0.986908,-0.146031,0.068056,-0.83401,-0.485977,-0.261147,-0.887783,-0.457167,-0.052767,-0.893979,-0.448073,0.002472,-0.860347,-0.437452,-0.261513,-0.900662,-0.386059,0.199286,-0.87109,-0.421247,0.252419,-0.923063,-0.384594,-0.005768,-0.893002,-0.351695,-0.280709,-0.884915,-0.399213,0.239753,-0.847285,-0.439924,-0.297525,-0.900815,-0.411451,-0.138585,-0.930448,-0.355205,0.089785,-0.754967,-0.460982,-0.466353,-0.781457,-0.444838,-0.437483,-0.623432,-0.389233,-0.678091,-0.671072,-0.411878,-0.616413,-0.764489,-0.38435,-0.517472,-0.782067,-0.295053,-0.548845,-0.604053,-0.299692,-0.738395,-0.599963,-0.218787,-0.769494,-0.330638,-0.0253,-0.943388,-0.348979,-0.089358,-0.932829,-0.185675,-0.07416,-0.979797,-0.13422,-0.014893,-0.990814,-0.528092,-0.050233,-0.847682,-0.520096,-0.116977,-0.846034,-0.298837,0.04413,-0.953276,-0.527421,0.022706,-0.849269,-0.067202,0.052828,-0.996338,-0.282327,-0.218787,-0.934019,-0.315287,-0.266976,-0.910642,-0.509079,-0.342235,-0.789727,-0.453017,-0.296884,-0.840571,-0.101047,-0.167577,-0.980651,-0.089602,-0.213141,-0.972869,-0.264626,-0.14951,-0.952666,-0.100101,-0.106204,-0.989288,-0.255867,-0.092624,-0.962249,-0.092166,-0.046937,-0.994629,-0.424329,-0.212806,-0.880123,-0.409772,-0.146245,-0.900357,-0.937986,-0.15421,0.310404,-0.922513,-0.188726,0.33665,-0.953917,-0.207495,0.216681,-0.965117,-0.172765,0.19657,-0.892117,-0.1301,0.4326,-0.872433,-0.165868,0.45967,-0.94998,-0.128513,0.284555,-0.908841,-0.103916,0.403912,-0.973174,-0.143773,0.17951,-0.761101,-0.272164,0.588702,-0.71337,-0.293313,0.636433,-0.721397,-0.399213,0.565844,-0.749413,-0.378887,0.542924,-0.730552,-0.405469,0.549425,-0.75576,-0.407483,0.512589,-0.782006,-0.187048,0.594501,-0.693686,-0.162023,0.701773,-0.842067,-0.30726,0.443251,-0.889889,-0.268288,0.368908,-0.808466,-0.387158,0.443251,-0.81518,-0.404157,0.414808,0.367229,-0.264718,0.891629,0.375591,-0.166173,0.91174,0.196539,-0.210547,0.95761,0.205451,-0.311777,0.927641,0.373394,-0.098819,0.922361,0.182714,-0.14301,0.972686,-0.013489,-0.260231,0.965423,-0.001129,-0.367016,0.930204,-0.023072,-0.188421,0.981811,0.536332,-0.21363,0.816492,0.558092,-0.113254,0.821986,0.724418,-0.148503,0.673147,0.750328,-0.049074,0.6592,0.56621,-0.048616,0.82281,0.757012,0.007538,0.65331,0.365062,-0.277779,0.888546,0.528611,-0.251869,0.810602,0.692679,-0.208472,0.69042,0.188208,-0.308237,0.932493,-0.043306,-0.357555,0.932859,0.364696,-0.013215,0.930998,0.36845,-0.058748,0.927763,0.563494,-0.008179,0.826044,0.560198,0.038911,0.827418,0.756127,0.04413,0.652913,0.752037,0.090182,0.652913,0.166753,-0.064486,0.983856,0.173589,-0.107059,0.978973,-0.036592,-0.115665,0.992584,-0.029023,-0.154912,0.987487,0.360515,0.040101,0.931852,0.159215,-0.012391,0.987152,0.35258,0.098605,0.93054,0.148412,0.047487,0.987762,-0.045747,-0.066591,0.996704,-0.058168,-0.008301,0.99826,0.557604,0.090915,0.825098,0.747673,0.13712,0.649709,0.551958,0.144688,0.821192,0.740684,0.181555,0.64684,0.89761,-0.004944,0.440718,0.860256,-0.077822,0.503861,0.795587,-0.150334,0.586871,0.792596,-0.079745,0.60448,0.947691,0.064516,0.312479,0.892331,0.012482,0.451155,0.960997,0.087405,0.262337,0.899899,0.057863,0.432203,0.870724,0.088687,0.483657,0.942961,0.124424,0.308664,0.878811,0.160924,0.449141,0.919462,0.186132,0.346263,0.969176,0.106906,0.2219,0.953978,0.139012,0.265603,0.752281,0.007965,0.658773,0.856349,0.080844,0.509964,0.957854,0.146275,0.247108,0.968688,0.152593,0.195715,0.965209,0.181341,0.188238,0.954924,0.178228,0.237312,0.958251,0.143773,0.247108,0.946623,0.175787,0.270119,0.96292,0.18833,0.19306,0.950865,0.195807,0.239692,0.941069,0.187811,0.281167,0.961211,0.106052,0.254463,0.973022,0.107517,0.204047,0.965758,0.10715,0.236152,0.894925,0.128574,0.427259,0.89877,0.086734,0.429701,0.890011,0.167455,0.424024,0.882778,0.198614,0.425703,0.983795,0.094272,-0.152409,0.970611,0.162328,0.177557,0.990478,0.087619,0.106052,0.978423,0.049318,-0.200507,0.987884,0.139348,-0.067904,0.946043,0.201575,0.253578,0.989349,0.142186,0.030854,0.950346,0.162664,0.265236,0.932707,0.040345,-0.358287,0.938047,0.062502,-0.3408,0.854366,0.016572,-0.519395,0.854549,0.023194,-0.518815,0.94879,0.07889,-0.305826,0.856105,0.027772,-0.516037,0.931944,0.023011,-0.361797,0.855403,0.005982,-0.517899,0.992859,0.114719,0.03235,0.954894,0.08713,-0.283761,0.94467,0.108036,-0.30961,0.990417,0.137577,-0.009919,0.858394,0.052675,-0.510239,0.854976,0.077303,-0.512803,0.931669,0.145756,-0.332713,0.982849,0.17832,-0.046876,0.847865,0.110324,-0.51854,0.991485,0.116581,0.057558,0.956816,0.080935,-0.279092,0.85818,0.036195,-0.51204,0.961791,0.12891,0.241432,0.961486,0.117344,0.248451,0.953551,0.159795,0.255257,0.949553,0.1901,0.249367,0.550615,-0.040742,-0.833735,0.72692,-0.010224,-0.686636,0.730461,-0.01297,-0.682821,0.556597,-0.032716,-0.830103,0.549364,-0.046327,-0.834284,0.726249,-0.009156,-0.687338,0.550432,-0.050386,-0.833338,0.727287,-0.009278,-0.686239,0.344737,-0.071688,-0.935942,0.342906,-0.083987,-0.935575,0.139592,-0.099155,-0.985198,0.136387,-0.116794,-0.983734,0.343211,-0.091494,-0.934751,0.134037,-0.126988,-0.982788,0.351512,-0.052065,-0.934721,0.144993,-0.070467,-0.986908,0.552049,-0.038789,-0.832881,0.343699,-0.087069,-0.935026,0.342692,-0.07358,-0.936552,0.550951,-0.019837,-0.834284,0.129368,-0.129368,-0.983093,0.125614,-0.121464,-0.984588,0.337413,-0.052126,-0.939879,0.547533,0.00708,-0.836726,0.113895,-0.104099,-0.988006,0.551805,-0.049135,-0.832514,0.343822,-0.093112,-0.934385,0.13187,-0.131077,-0.982543,0.728233,0.009278,-0.685232,0.728416,-0.004852,-0.68508,0.726066,0.03235,-0.68685,0.720786,0.063326,-0.690207,-0.517075,-0.46736,0.717032,-0.564379,-0.370037,0.737907,-0.800897,-0.358928,0.479263,-0.711783,-0.461867,0.52916,-0.582812,-0.302652,0.754112,-0.84756,-0.305887,0.433607,-0.923032,-0.288552,0.254433,-0.835261,-0.406293,0.370403,-0.950255,-0.263161,0.16654,-0.259896,-0.427473,0.865841,-0.274545,-0.322245,0.905942,-0.273446,-0.245949,0.929899,-0.5356,-0.45378,0.712149,-0.303598,-0.413526,0.858364,-0.696188,-0.458327,0.552477,-0.782708,-0.441145,0.439009,-0.554582,-0.251656,0.793146,-0.570086,-0.281594,0.771783,-0.264504,-0.212195,0.940733,-0.261635,-0.173925,0.949339,-0.843745,-0.305216,0.441481,-0.851131,-0.307718,0.425275,-0.947569,-0.296487,0.119114,-0.950713,-0.280587,0.131718,-0.544542,-0.209357,0.812159,-0.830775,-0.292459,0.473525,-0.545854,-0.156194,0.823176,-0.82226,-0.258034,0.507218,-0.938078,-0.319834,0.132969,-0.929807,-0.322428,0.177343,-0.264992,-0.127262,0.955779,-0.274728,-0.071841,0.958831,-0.916745,-0.214148,-0.337199,-0.91821,-0.211097,-0.335093,-0.776025,-0.195013,-0.59975,-0.758507,-0.180273,-0.626179,-0.918821,-0.226783,-0.322916,-0.787896,-0.213446,-0.577563,-0.586505,-0.181036,-0.789422,-0.555315,-0.160588,-0.815973,-0.607868,-0.20072,-0.768212,-0.969359,-0.245186,-0.014374,-0.976501,-0.214057,-0.024445,-0.931333,-0.270058,0.24427,-0.946196,-0.203467,0.251564,-0.973266,-0.229286,0.012421,-0.92642,-0.220161,0.305307,-0.915189,-0.215369,-0.340556,-0.962584,-0.268685,0.034913,-0.895962,-0.316416,0.311563,-0.733848,-0.168584,-0.65801,-0.522263,-0.141118,-0.840999,-0.901486,-0.279885,-0.330027,-0.912473,-0.256996,-0.318247,-0.961791,-0.272225,0.028535,-0.954161,-0.298318,0.022919,-0.903653,-0.26838,0.333689,-0.892209,-0.289956,0.346171,-0.781243,-0.253517,-0.570391,-0.790063,-0.236152,-0.565691,-0.614734,-0.228675,-0.754814,-0.61684,-0.217902,-0.75631,-0.892148,-0.282723,-0.352275,-0.767876,-0.255837,-0.587268,-0.889767,-0.260506,-0.374737,-0.762322,-0.240455,-0.600818,-0.609973,-0.229499,-0.758415,-0.612323,-0.222388,-0.75866,-0.95233,-0.304697,0.013306,-0.890225,-0.302164,0.340831,-0.958342,-0.285348,-0.012146,-0.904538,-0.299326,0.303568,-0.201544,-0.137028,-0.969817,-0.041902,-0.120701,-0.991791,-0.037324,-0.087649,-0.995422,-0.195318,-0.105014,-0.975097,-0.212653,-0.158208,-0.964202,-0.048097,-0.141301,-0.988769,-0.224891,-0.171484,-0.959166,-0.054903,-0.153294,-0.986633,-0.364208,-0.149297,-0.919248,-0.387616,-0.170232,-0.905942,-0.407636,-0.186712,-0.893826,-0.347789,-0.123295,-0.929411,-0.245674,-0.184057,-0.951689,-0.423963,-0.204566,-0.88226,-0.430036,-0.206122,-0.878933,-0.255135,-0.184698,-0.949095,-0.438948,-0.201147,-0.875698,-0.264077,-0.173956,-0.948668,-0.235755,-0.179479,-0.955077,-0.418592,-0.19834,-0.886227,-0.067843,-0.161473,-0.984527,-0.061495,-0.159459,-0.98526,-0.074892,-0.158086,-0.984558,-0.087527,-0.142705,-0.98587,-0.889157,-0.273598,0.366771,-0.931608,-0.187902,0.311045,-0.908261,-0.18778,0.373821,-0.875637,-0.281472,0.392407,-0.92938,-0.223914,0.293405,-0.890103,-0.217658,0.400372,-0.897458,-0.318613,0.304941,-0.954924,-0.21955,0.199683,-0.962554,-0.235115,0.134922,-0.80755,-0.39436,0.43852,-0.816431,-0.420667,0.39552,-0.820124,-0.357891,0.446394,-0.914365,-0.289895,0.28254,-0.920927,-0.264473,0.286172,-0.958251,-0.265938,0.104801,-0.952818,-0.289468,0.091128,-0.856655,-0.282266,0.431745,-0.868038,-0.262581,0.421339,-0.902768,-0.327036,0.279305,-0.853298,-0.306558,0.421735,-0.896908,-0.353465,0.265664,-0.864254,-0.32197,0.386456,-0.93997,-0.327616,0.095431,-0.929075,-0.352275,0.112552,0.333262,0.174474,0.926511,0.328501,0.184362,0.926298,0.128361,0.141545,0.981567,0.129215,0.129154,0.983154,-0.076266,0.087191,0.993255,-0.076937,0.073824,0.994293,0.531602,0.206824,0.821345,0.520371,0.2125,0.827052,0.712455,0.219337,0.666524,0.691885,0.22187,0.687033,0.342174,0.14713,0.928007,0.542894,0.186224,0.818873,0.729576,0.210242,0.650746,0.136814,0.098819,0.985626,-0.070284,0.042848,0.996582,0.937681,0.18131,0.296365,0.920865,0.180853,0.345347,0.829493,0.210028,0.517441,0.855098,0.207495,0.475082,0.96176,0.164739,0.218757,0.960418,0.155705,0.230964,0.955199,0.179052,0.235542,0.969207,0.16599,0.181768,0.945555,0.196081,0.259713,0.96115,0.182806,0.206732,0.943999,0.189489,0.269997,0.872341,0.211676,0.440626,0.968902,0.221198,-0.110721,0.967345,0.210425,0.141148,0.95587,0.207953,0.207434,0.974975,0.209143,-0.075167,0.951445,0.240791,-0.191687,0.97766,0.203192,0.053224,0.91232,0.203711,-0.355174,0.869839,0.26072,-0.418775,0.816462,0.176824,-0.549608,0.754997,0.256203,-0.603565,0.922025,0.180609,-0.342357,0.839106,0.141697,-0.525163,0.517197,0.098483,-0.850154,0.6798,0.145329,-0.718833,0.71276,0.0936,-0.69509,0.542711,0.034303,-0.839198,0.443922,0.198035,-0.873867,0.605731,0.230232,-0.761589,0.306345,0.023957,-0.951598,0.250832,0.140843,-0.957701,0.056795,-0.055269,-0.996826,0.022828,0.046022,-0.998657,0.326762,-0.032197,-0.944517,0.089846,-0.089724,-0.991882,-0.57036,-0.081088,0.817347,-0.573016,-0.076632,0.815943,-0.812098,-0.194739,0.550035,-0.823695,-0.190741,0.533952,-0.916654,-0.275704,0.289285,-0.929228,-0.266945,0.25544,-0.297678,0.008118,0.954619,-0.302469,0.019471,0.95294,-0.557665,-0.108127,0.822962,-0.286935,-0.022187,0.957671,-0.823145,-0.21482,0.525559,-0.930662,-0.29078,0.221992,-0.880459,-0.255409,-0.399396,-0.764,-0.243812,-0.597308,-0.764641,-0.231391,-0.601459,-0.887997,-0.24546,-0.388806,-0.621174,-0.224982,-0.750664,-0.6198,-0.218574,-0.753655,-0.870235,-0.278695,-0.406171,-0.754479,-0.269753,-0.598285,-0.605548,-0.234626,-0.760399,-0.956603,-0.27195,-0.104404,-0.946715,-0.286294,-0.147465,-0.939543,-0.281808,0.194372,-0.946501,-0.291604,0.138035,-0.961852,-0.26783,-0.055452,-0.925413,-0.28486,0.249855,-0.274789,-0.140477,-0.95117,-0.132115,-0.103214,-0.985839,-0.107608,-0.12711,-0.986023,-0.271401,-0.159246,-0.949187,-0.267464,-0.100192,-0.958312,-0.145848,-0.035829,-0.988647,-0.446364,-0.187201,-0.875027,-0.429884,-0.171056,-0.886502,-0.446547,-0.193854,-0.87347,-0.919065,-0.310343,0.242836,-0.914853,-0.315256,0.252205,-0.908414,-0.301706,0.289346,-0.902402,-0.296396,0.312662,-0.940519,-0.299753,0.159734,-0.932096,-0.30842,0.189764,-0.908658,-0.336406,0.247169,-0.934812,-0.328562,0.134587,-0.88641,-0.309854,0.343822,-0.073183,-0.966033,-0.247719,0.146001,-0.910886,-0.385907,0.184851,-0.959471,-0.212622,-0.038392,-0.997345,-0.0618,0.189306,-0.977935,-0.08826,-0.030274,-0.99704,0.07065,-0.130039,-0.858547,-0.495956,0.075167,-0.79574,-0.60094,-0.292001,-0.947966,-0.126743,-0.340342,-0.855068,-0.391125,-0.249001,-0.966033,0.068972,-0.228004,-0.952818,0.200262,-0.085574,-0.980224,0.178411,0.123234,-0.991668,0.037141,0.076693,-0.996399,0.035554,-0.128117,-0.980102,0.151463,0.033845,-0.999329,-0.013489,-0.168767,-0.983367,0.066866,-0.048616,-0.986999,0.15305,0.165258,-0.986236,-0.003784,-0.265328,-0.922758,0.279427,-0.234504,-0.933012,0.272866,-0.308145,-0.923948,0.2266,-0.354381,-0.928922,0.10712,-0.182684,-0.97705,-0.109287,-0.36961,-0.919889,-0.130955,-0.378857,-0.924894,-0.031739,-0.189978,-0.981109,-0.036348,-0.154424,-0.978149,-0.139073,-0.334452,-0.925504,-0.177587,-0.106174,-0.988372,-0.108707,-0.260811,-0.953795,-0.149022,-0.002808,-0.99115,-0.132603,0.001801,-0.988159,-0.153203,0.012574,-0.992767,-0.119358,0.005951,-0.996612,-0.081668,0.274117,0.907682,0.317728,0.380139,0.887967,0.258705,0.408643,0.862453,0.298532,0.294534,0.882992,0.365398,0.471572,0.859798,0.195715,0.506272,0.833308,0.2219,0.438093,0.836085,0.330149,0.312937,0.858119,0.406995,0.544786,0.804956,0.234901,0.248573,0.933195,0.259407,0.347942,0.914304,0.207251,0.213782,0.95941,0.183905,0.306314,0.94171,0.138981,0.435347,0.886837,0.154759,0.391125,0.915311,0.095706,0.156957,0.915464,0.370464,0.140599,0.940916,0.308023,0.116123,0.966948,0.226905,0.16718,0.891201,0.421583,0.17188,0.86758,0.466628,0.347148,0.796716,0.494675,0.505142,0.767479,0.394665,0.531541,0.727714,0.433393,0.356456,0.758629,0.545335,0.641865,0.722831,0.255898,0.685476,0.675222,0.272225,0.331431,0.830012,0.448531,0.471755,0.804773,0.360149,0.591723,0.768548,0.243171,0.169286,0.808771,0.563219,0.172582,0.841304,0.512223,0.162328,0.767968,0.619556,0.776574,0.624256,-0.084841,0.73394,0.673299,0.089267,0.67629,0.729209,0.104099,0.725639,0.686514,-0.045747,0.624744,0.770837,0.124393,0.682119,0.731193,0.002167,0.825068,0.553941,-0.111148,0.786126,0.612873,0.079836,0.793115,0.554826,-0.251106,0.827479,0.483505,-0.285379,0.760674,0.617664,-0.199561,0.735496,0.66451,-0.131932,0.615314,0.786187,0.057131,0.583728,0.810419,0.049348,0.671041,0.741295,-0.013123,0.693197,0.720145,-0.028748,0.549333,0.835383,0.018952,0.64922,0.760308,-0.020325,0.547105,0.826899,0.129765,0.510849,0.85342,0.103336,0.468978,0.881375,0.056429,0.647481,0.760918,0.04178,0.583575,0.800409,0.136876,0.715171,0.695578,-0.068392,0.152745,-0.984527,0.085696,0.081576,-0.988739,0.125248,0.076357,-0.990356,0.115513,0.122166,-0.990692,0.059664,0.058168,-0.988891,0.136753,0.061739,-0.992279,0.107456,0.072909,-0.996399,0.042695,0.161473,-0.981201,0.105472,0.081881,-0.988098,0.130131,0.161443,-0.979858,0.117283,0.081057,-0.987854,0.13242,0.058168,-0.988891,0.136723,0.303415,-0.952849,0.001892,0.331645,-0.942106,0.049257,0.337565,-0.93759,0.083193,0.230506,-0.972015,-0.04471,0.111759,-0.992126,-0.056124,0.003815,-0.999146,0.040468,0.043641,-0.993042,0.109287,0.033082,-0.993957,0.104373,-0.035524,-0.998962,0.028138,0.017884,-0.994507,0.102908,-0.082705,-0.996002,0.032807,0.035401,-0.998444,0.042848,0.051088,-0.992645,0.109561,-0.048616,-0.998077,-0.038209,0.021607,-0.998962,-0.039644,-0.141087,-0.988037,-0.062014,-0.254707,-0.965667,-0.050996,-0.120457,-0.98291,0.139012,-0.10773,-0.977783,0.179785,-0.358165,-0.900571,0.246284,-0.378185,-0.913053,0.15247,-0.095309,-0.974548,0.202887,-0.330027,-0.892819,0.306497,0.014466,-0.990417,0.137181,0.019959,-0.988983,0.146641,0.023225,-0.988189,0.151372,-0.116764,-0.989898,0.080172,0.009583,-0.992828,0.118992,-0.351299,-0.935545,0.03647,0.425092,-0.108585,0.898587,0.18482,-0.163488,0.969054,0.204505,-0.184057,0.961394,0.440107,-0.131077,0.888302,-0.074404,-0.21012,0.974822,-0.050356,-0.22541,0.97293,0.407971,-0.086795,0.908841,0.167302,-0.142674,0.975494,-0.092776,-0.194342,0.976531,0.60622,-0.060762,0.792962,0.590564,-0.0412,0.805933,0.724906,-0.027772,0.688253,0.711997,-0.010498,0.702078,0.618275,-0.081637,0.781701,0.735618,-0.046175,0.675771,0.903897,0.039064,0.425886,0.808222,-0.004212,0.588855,0.817438,-0.022492,0.575518,0.904294,0.016053,0.426557,0.89816,0.050783,0.43672,0.798456,0.01297,0.601886,0.990204,0.13422,0.038301,0.989288,0.137913,0.047426,0.913083,0.205786,-0.35197,0.917936,0.207343,-0.338206,0.992492,0.104434,0.063204,0.918332,0.180822,-0.352031,0.910611,0.177129,-0.373333,0.865841,0.211921,-0.4532,0.856655,0.189673,-0.47969,0.895932,0.161107,-0.413892,0.922697,0.18775,-0.33665,0.876461,0.216529,-0.429945,0.968352,0.126865,-0.21482,0.972625,0.142613,-0.183325,0.9794,0.110813,-0.168615,0.975249,0.140629,-0.170598,0.959899,0.112613,-0.256661,0.978332,0.085726,-0.18833,0.643178,0.183386,-0.7434,0.894955,0.147984,-0.42085,0.911466,0.099155,-0.399152,0.657765,0.119694,-0.743614,0.624317,0.244819,-0.741783,0.87112,0.199927,-0.4485,0.380963,0.182531,-0.906369,0.385815,0.251076,-0.887722,0.152257,0.153233,-0.976379,0.158025,0.232429,-0.959655,0.367687,0.109775,-0.923429,0.137181,0.071902,-0.987915,-0.47084,-0.250099,0.846004,-0.605396,-0.261025,0.751854,-0.611835,-0.289163,0.736198,-0.460402,-0.263741,0.847591,-0.702658,-0.274819,0.65627,-0.706412,-0.323649,0.629444,-0.486496,-0.240303,0.839961,-0.615162,-0.241798,0.750389,-0.713065,-0.23777,0.659536,-0.297739,-0.237159,0.92468,-0.316996,-0.22718,0.920774,-0.274026,-0.247749,0.929228,-0.937437,-0.189947,-0.291726,-0.818781,-0.12772,-0.559709,-0.801202,-0.214606,-0.558519,-0.918363,-0.268685,-0.290475,-0.623524,-0.05121,-0.780084,-0.609546,-0.143925,-0.779534,-0.949431,-0.111332,-0.293558,-0.82638,-0.03708,-0.561876,-0.62682,0.045991,-0.777764,-0.973022,-0.228736,-0.029695,-0.986389,-0.161473,-0.030732,-0.948668,-0.249611,0.194067,-0.96353,-0.18891,0.189489,-0.952696,-0.302957,-0.023133,-0.92172,-0.325663,0.210578,-0.241859,0.066652,-0.967986,-0.064425,0.112094,-0.991577,-0.077853,0.023133,-0.996673,-0.247108,-0.026643,-0.968596,-0.238075,0.162481,-0.957549,-0.058046,0.200995,-0.977844,-0.416852,0.015534,-0.908811,-0.417524,0.113834,-0.901486,-0.410169,-0.078585,-0.908597,-0.826197,-0.276254,0.490951,-0.892483,-0.262917,0.366436,-0.855983,-0.343394,0.386425,-0.78811,-0.354106,0.503433,-0.851894,-0.217505,0.476363,-0.912046,-0.20426,0.355541,-0.766594,-0.28312,0.576312,-0.787835,-0.229591,0.571459,-0.746605,-0.349193,0.56621,0.914579,0.06769,0.398633,0.928312,-0.002594,0.371776,0.957701,-0.082308,0.275674,0.951506,-0.012116,0.307382,0.935331,-0.090426,0.341929,0.956481,-0.157872,0.245338,0.864467,0.13538,0.484054,0.882473,0.072359,0.464705,0.800348,0.187933,0.569292,0.818659,0.137089,0.557634,0.897641,-0.01825,0.440321,0.839564,0.049989,0.54091,0.899564,0.114109,0.421583,0.850093,0.166601,0.499557,0.888058,0.135228,0.439375,0.843287,0.167455,0.510666,0.79107,0.200629,0.577868,0.793268,0.178777,0.582018,0.94058,0.045137,0.336528,0.929136,0.084872,0.359844,0.926939,-0.199896,0.317484,0.925993,-0.212317,0.312082,0.930082,-0.242073,0.276254,0.933409,-0.220893,0.282693,0.923399,-0.23426,0.304025,0.924924,-0.272225,0.265206,0.912564,-0.178137,0.368023,0.912564,-0.183905,0.365154,0.885556,-0.157933,0.436781,0.884121,-0.16071,0.438704,0.910428,-0.202521,0.360637,0.879849,-0.179968,0.439833,0.927213,-0.196753,0.318644,0.911344,-0.185339,0.367504,0.927915,-0.200812,0.313974,0.909421,-0.204474,0.362102,0.884762,-0.171361,0.433332,0.881771,-0.199316,0.427412,0.936125,-0.206854,0.284341,0.940123,-0.192755,0.281014,0.208625,0.322977,0.923093,0.19422,0.36024,0.912412,0.306192,0.35255,0.884243,0.316477,0.326426,0.890652,0.195624,0.339213,0.920103,0.316416,0.316202,0.894345,0.426954,0.32786,0.842708,0.427961,0.315867,0.846767,0.447584,0.276162,0.85049,0.101047,0.312876,0.944395,0.089602,0.357646,0.929533,0.087252,0.34846,0.933226,0.234718,0.222968,0.946135,0.118686,0.209265,0.970611,0.263527,0.061892,0.962645,0.136906,0.049593,0.989319,0.343669,0.234718,0.909268,0.449568,0.238838,0.860683,0.377941,0.078494,0.922483,0.483871,0.098788,0.869533,0.933439,-0.212867,0.288705,0.929991,-0.206946,0.30369,0.945341,-0.180303,0.271676,0.950804,-0.178076,0.253456,0.909024,-0.233863,0.34489,0.908231,-0.22367,0.353557,0.875576,-0.235206,0.421918,0.877987,-0.224982,0.422437,0.936247,-0.220649,0.273385,0.910276,-0.236396,0.339793,0.93585,-0.232582,0.264626,0.910428,-0.231422,0.342784,0.873867,-0.231178,0.427625,0.872097,-0.211493,0.441176,0.954711,-0.18836,0.230232,0.953703,-0.215155,0.210059,0.292245,-0.167455,0.941527,0.288186,-0.219123,0.932127,0.439344,-0.200018,0.875759,0.442366,-0.158757,0.882656,0.280404,-0.254463,0.925504,0.430586,-0.227393,0.873409,0.583422,-0.185492,0.790674,0.582842,-0.158116,0.797021,0.57738,-0.199377,0.791742,0.142033,-0.177282,0.973846,0.139592,-0.234779,0.961943,0.135472,-0.273324,0.9523,0.290139,-0.095462,0.952178,0.140873,-0.096286,0.985321,0.278481,0.003693,0.960418,0.133549,0.015503,0.990905,0.438154,-0.100314,0.893277,0.575823,-0.115146,0.809381,0.424055,-0.018769,0.905423,0.561235,-0.051881,0.826014,0.87579,0.130161,0.464736,0.883389,0.099734,0.457839,0.913114,0.109622,0.392651,0.90347,0.14771,0.402326,0.883877,0.102664,0.456252,0.915922,0.104892,0.387371,0.839961,0.124027,0.528214,0.849849,0.097171,0.51793,0.799738,0.136052,0.584704,0.813532,0.107486,0.571459,0.852138,0.100101,0.513596,0.819727,0.101505,0.563677,0.856533,0.188696,0.4803,0.821253,0.173254,0.543565,0.817591,0.269692,0.508682,0.787805,0.238838,0.567675,0.781671,0.178899,0.597461,0.765007,0.219214,0.605518,0.882534,0.213111,0.419141,0.841273,0.302072,0.448286,0.262856,-0.270272,0.926176,0.258065,-0.246223,0.934202,0.394879,-0.237342,0.887509,0.405072,-0.246956,0.880276,0.260933,-0.205359,0.943236,0.392712,-0.21659,0.893765,0.53441,-0.216193,0.817072,0.549883,-0.212867,0.807611,0.524277,-0.216102,0.823634,0.12772,-0.282144,0.950804,0.12653,-0.246895,0.960723,0.1301,-0.191198,0.972869,0.27131,-0.272774,0.923002,0.131199,-0.290872,0.947691,0.418287,-0.243171,0.875118,0.565477,-0.206763,0.798395,0.772393,-0.152318,0.616565,0.760826,-0.16126,0.628559,0.83462,-0.151372,0.529588,0.840419,-0.14658,0.521683,0.747703,-0.183966,0.637989,0.825953,-0.17362,0.536302,0.676962,-0.176794,0.714438,0.660909,-0.186651,0.726859,0.646199,-0.202277,0.735832,0.780267,-0.158391,0.605029,0.690298,-0.174749,0.702078,0.783013,-0.178259,0.595904,0.698202,-0.180914,0.692618,0.842921,-0.159307,0.513901,0.841487,-0.187506,0.506638,0.774682,-0.193152,0.602069,0.766686,-0.174291,0.617847,0.686728,-0.141057,0.713065,0.695395,-0.170263,0.698141,0.756615,-0.135716,0.639607,0.673574,-0.09241,0.733299,0.831782,-0.218482,0.510239,0.82635,-0.207495,0.523515,0.820399,-0.177313,0.543565,0.78048,-0.194678,0.594043,0.83697,-0.212226,0.504379,0.699606,-0.183325,0.690573,0.635945,0.260933,0.726279,0.641652,0.228492,0.732139,0.550493,0.235359,0.800958,0.537004,0.291635,0.791528,0.664266,0.148534,0.732566,0.580462,0.122776,0.804956,0.723502,0.227515,0.651723,0.72161,0.218787,0.656789,0.734642,0.170141,0.656758,0.648885,0.240791,0.721763,0.739647,0.191443,0.645131,0.676077,0.167699,0.71746,0.764367,0.110721,0.635182,0.544755,0.28779,0.787622,0.571581,0.223853,0.789392,0.682211,0.201117,0.702933,0.662496,0.252388,0.705222,0.574084,0.297403,0.762841,0.591357,0.239143,0.770104,0.652608,0.310587,0.691092,0.56682,0.361736,0.740135,0.460402,0.337321,0.8211,0.472304,0.27195,0.838404,0.457656,0.409009,0.789422,0.750633,0.16419,0.639973,0.731986,0.209387,0.648305,0.721335,0.258919,0.642323,0.702536,0.155522,0.694418,0.768181,0.12891,0.627094,0.716392,0.10947,0.689016,0.778558,0.105502,0.61861,0.611347,0.181646,0.770226,0.489486,0.203894,0.847835,0.627308,0.112796,0.770531,0.50737,0.116703,0.853755,0.213355,0.307901,0.927152,0.20951,0.382336,0.89993,0.095309,0.389782,0.915922,0.097385,0.314097,0.944365,0.212348,0.468398,0.857601,0.097171,0.479476,0.872127,0.339152,0.294717,0.893338,0.332102,0.365947,0.86935,0.333384,0.445814,0.830683,0.224067,0.231178,0.946745,0.353282,0.220191,0.909207,0.241249,0.130558,0.961638,0.372387,0.122715,0.919889,0.103916,0.237342,0.965819,0.115329,0.13712,0.983795,0.681265,0.43437,0.589221,0.700583,0.488998,0.519639,0.604663,0.575335,0.550737,0.590197,0.507096,0.62804,0.487075,0.656026,0.576464,0.478011,0.576708,0.662465,0.757042,0.356151,0.547716,0.778832,0.398267,0.484512,0.661031,0.373516,0.650746,0.73278,0.309305,0.606067,0.574297,0.433393,0.694479,0.465621,0.490799,0.736381,0.226722,0.673086,0.70394,0.228401,0.764244,0.603076,0.104099,0.787896,0.606891,0.104282,0.694327,0.712027,0.352092,0.634327,0.688192,0.356975,0.720969,0.59389,0.219886,0.569262,0.792199,0.342204,0.538224,0.770196,0.10123,0.585711,0.804132,0.73693,-0.596973,0.317057,0.789148,-0.539323,0.293832,0.790796,-0.48912,0.367901,0.738884,-0.5374,0.406476,0.835658,-0.471389,0.281838,0.833674,-0.430219,0.346171,0.789911,-0.425001,0.442,0.734794,-0.457808,0.500412,0.825159,-0.398602,0.40022,0.726096,-0.646535,0.233955,0.779382,-0.585467,0.223121,0.82696,-0.517716,0.219245,0.674367,-0.650136,0.350047,0.661855,-0.705985,0.251961,0.58977,-0.705679,0.392621,0.575243,-0.768853,0.279153,0.676473,-0.579669,0.454207,0.668294,-0.485916,0.563219,0.594958,-0.620991,0.510239,0.587573,-0.50795,0.629841,0.343883,-0.805689,0.482284,0.478774,-0.759728,0.439955,0.488998,-0.659993,0.570299,0.357616,-0.694266,0.624531,0.486129,-0.523667,0.699545,0.359447,-0.535905,0.763909,0.32728,-0.880245,0.343486,0.462233,-0.829859,0.312479,0.185858,-0.838557,0.51207,0.174444,-0.914365,0.365307,0.196844,-0.720756,0.664632,0.199896,-0.545854,0.813654,0.879879,0.133732,0.455947,0.881741,0.138951,0.450758,0.920377,0.107669,0.375866,0.915647,0.11655,0.384625,0.845882,0.136052,0.515702,0.842952,0.152776,0.515824,0.811335,0.123508,0.571368,0.801874,0.146245,0.579272,0.881283,0.121158,0.45671,0.849727,0.118381,0.513718,0.818232,0.109287,0.564348,0.915098,0.114597,0.386578,0.273476,-0.060701,0.959929,0.27665,-0.059816,0.959075,0.396802,-0.044801,0.916776,0.399091,-0.053438,0.915342,0.508072,-0.016358,0.861141,0.518967,-0.031251,0.854183,0.140141,-0.060549,0.98825,0.144322,-0.066408,0.987274,0.260109,0.017762,0.965392,0.129063,0.022858,0.991363,0.390027,0.016114,0.920652,0.518876,0.024506,0.854457,0.933775,-0.216803,0.284646,0.933958,-0.236335,0.267953,0.949705,-0.240577,0.200323,0.948271,-0.242988,0.204169,0.908353,-0.173009,0.380688,0.909635,-0.212867,0.356609,0.864132,-0.120182,0.488693,0.869259,-0.175054,0.462264,0.935209,-0.169195,0.310984,0.905362,-0.108585,0.410504,0.855068,-0.045106,0.516495,0.951048,-0.216926,0.220038,0.232948,0.205267,0.95056,0.257607,0.11185,0.959746,0.400098,0.074313,0.913419,0.370556,0.16306,0.914365,0.538865,0.025452,0.841975,0.510514,0.107608,0.853084,0.105869,0.230445,0.967284,0.120518,0.134434,0.983551,0.210486,0.281411,0.936186,0.093661,0.301798,0.948729,0.340861,0.244484,0.907743,0.479019,0.192145,0.856502,0.726707,-0.010224,0.686819,0.743645,-0.079745,0.663747,0.81286,-0.129307,0.567888,0.802149,-0.065523,0.593463,0.632282,0.047548,0.773247,0.655477,-0.02768,0.754692,0.704581,0.072756,0.705832,0.604144,0.132176,0.785791,0.786676,0.014771,0.617145,0.707999,0.052492,0.704245,0.688925,0.074618,0.720939,0.7528,0.11829,0.647511,0.768639,0.094485,0.632649,0.62505,0.006806,0.780541,0.607288,0.025941,0.794031,0.718375,0.069094,0.69219,0.632679,0.044069,0.773125,0.778405,0.092563,0.620869,0.900784,-0.326975,0.285684,0.91641,-0.271065,0.294412,0.904111,-0.237678,0.354991,0.890011,-0.292978,0.349315,0.872005,-0.216987,0.438704,0.859066,-0.274331,0.432081,0.896542,-0.375195,0.235389,0.915128,-0.315836,0.250526,0.873959,-0.396893,0.280343,0.867214,-0.444838,0.223701,0.866543,-0.361461,0.344127,0.841578,-0.342692,0.417402,0.729667,-0.285226,0.621418,0.735954,-0.223151,0.63918,0.637867,-0.234169,0.733634,0.640828,-0.295328,0.708579,0.525773,-0.234809,0.81753,0.542375,-0.293802,0.787072,0.80459,-0.274239,0.526658,0.815363,-0.213599,0.538072,0.730064,-0.366802,0.576525,0.795038,-0.348766,0.4962,0.65331,-0.382366,0.653401,0.56682,-0.387646,0.72689,0.308329,-0.251778,0.917325,0.277596,-0.194494,0.940794,0.142888,-0.170476,0.974914,0.165105,-0.228858,0.959319,0.432783,-0.277017,0.857845,0.404981,-0.219764,0.887478,0.340709,-0.36906,0.864681,0.464614,-0.381146,0.799249,0.187811,-0.358348,0.914487,0.297403,0.70867,0.639729,0.190344,0.612903,0.766869,0.186132,0.554338,0.811182,0.282052,0.693442,0.662984,0.06299,0.464522,0.883297,0.057772,0.364238,0.929502,0.377056,0.53383,0.756828,0.253853,0.471175,0.844691,0.122776,0.357646,0.925718,0.38316,0.745842,0.544847,0.475723,0.549852,0.686514,0.447493,0.740379,0.501541,0.546342,0.538163,0.641774,0.353771,0.786737,0.505814,0.413862,0.828333,0.377575,0.602802,0.711356,0.361309,0.513047,0.727073,0.456191,0.487899,0.824,0.287973,0.5721,0.795221,0.200659,0.692404,0.564928,0.448775,0.60976,0.536332,0.583514,0.682882,0.702841,0.199103,0.775414,0.592639,0.217872,0.715445,0.698477,0.015229,0.801447,0.595843,-0.051271,0.637104,0.762047,0.11536,0.67217,0.739708,0.03122,0.640431,0.684866,-0.347453,0.696341,0.69982,-0.159062,0.678762,0.730277,-0.076754,0.659047,0.703757,-0.265175,0.686422,0.573809,-0.446669,0.762505,0.58742,-0.271065,0.567217,0.614612,-0.548173,0.588641,0.529252,-0.611011,0.486099,0.471999,-0.735435,0.477981,0.448958,-0.754936,0.603442,0.609821,-0.513718,0.511307,0.421949,-0.74865,0.107913,-0.24955,-0.96231,0.35374,0.189062,-0.916013,0.345683,0.076968,-0.935179,0.059206,-0.36671,-0.928434,0.132817,-0.09067,-0.986969,0.338084,0.268166,-0.902097,-0.111301,-0.584399,-0.803766,-0.042451,-0.440901,-0.896512,-0.202185,-0.725272,-0.658071,-0.121921,-0.649739,-0.750267,-0.176672,-0.640309,-0.74749,-0.254585,-0.728904,-0.635487,-0.160772,-0.744133,-0.648366,-0.201727,-0.761895,-0.615436,-0.256417,-0.737632,-0.624561,-0.209876,-0.693686,-0.688986,-0.155583,-0.770928,-0.617603,-0.142216,-0.746605,-0.649831,-0.195166,-0.755486,-0.625385,-0.255562,-0.804529,-0.536088,-0.34138,-0.841517,-0.418622,-0.408979,-0.852626,-0.325144,-0.208014,-0.684927,-0.698264,-0.332347,-0.797388,-0.503647,-0.506027,-0.858791,0.079745,-0.464919,-0.877041,-0.120853,-0.456618,-0.873257,-0.169866,-0.50032,-0.863308,0.06595,-0.524003,-0.848933,0.068667,-0.502976,-0.859676,-0.089084,-0.508591,-0.840846,0.185186,-0.513901,-0.843043,0.158574,-0.501358,-0.828913,0.247993,-0.498306,-0.835231,0.232429,-0.506333,-0.839778,0.195898,-0.497269,-0.826716,0.263131,-0.495071,-0.742088,0.451827,-0.497726,-0.804559,0.323893,-0.482986,-0.815271,0.319346,-0.466659,-0.784753,0.407819,-0.46849,-0.715506,0.518204,-0.48619,-0.80163,0.347789,-0.474258,-0.616169,0.628773,-0.425642,-0.570971,0.701956,-0.41615,-0.41792,0.80755,-0.345012,-0.38255,0.857082,-0.446822,-0.712638,0.540788,-0.413678,-0.58211,0.699973,-0.207862,0.054689,0.976623,-0.323618,-0.182958,0.928312,-0.351573,-0.391919,0.850154,-0.244209,-0.148747,0.95822,-0.123081,0.020814,0.992157,-0.23835,-0.178014,0.954711,-0.074984,0.275613,0.958312,-0.00351,0.20365,0.979034,-0.096133,0.11948,0.988159,-0.075991,0.48204,0.872829,0.001862,0.588763,0.808283,0.025178,0.558946,0.828791,-0.028932,0.479781,0.876888,0.054811,0.664418,0.745323,0.079104,0.645344,0.759758,-0.07651,0.474532,0.876888,0.026093,0.60799,0.793481,0.090701,0.668355,0.738273,-0.140629,0.370006,0.918302,-0.168218,0.319895,0.932371,-0.177618,0.260384,0.949004,-0.228675,0.167333,0.958983,-0.076144,0.405927,0.910703,-0.117862,0.328684,0.93704,0.205573,0.867458,0.453017,0.333811,0.92172,0.197333,0.232521,0.935759,0.264992,0.170385,0.849208,0.499802,0.428663,0.899716,-0.08179,0.305277,0.951567,-0.035768,0.301584,0.854946,0.42201,0.416974,0.895169,0.157414,0.495468,0.860958,-0.114963,0.104678,0.753288,0.649281,0.175604,0.749046,0.638783,0.124363,0.742393,0.658315,0.407666,0.733116,-0.544298,0.285714,0.644887,-0.708823,0.185339,0.70809,-0.681326,0.205756,0.788354,-0.57976,0.197089,0.602924,-0.773034,0.305979,0.683248,-0.662954,0.512223,0.690268,-0.510971,0.45793,0.596118,-0.659444,0.370037,0.500656,-0.782556,0.460219,0.82165,-0.336192,0.523392,0.780389,-0.342021,0.301645,0.892117,-0.336314,0.160253,0.344768,-0.924863,0.054689,0.073275,-0.995788,0.132298,0.029756,-0.990753,0.299234,0.35905,-0.88403,-0.082583,-0.202765,-0.975707,-0.065889,-0.230079,-0.970916,0.204993,0.212256,-0.955443,0.093295,-0.029054,-0.995209,-0.037477,-0.277078,-0.960112,0.197455,0.522721,-0.82931,0.288705,0.391827,-0.873531,0.33842,0.580432,-0.740623,-0.277474,-0.580248,-0.765679,-0.349071,-0.695853,-0.627583,-0.360942,-0.675954,-0.642445,-0.288278,-0.530778,-0.79696,-0.414289,-0.766198,-0.491165,-0.42317,-0.765526,-0.484603,-0.25544,-0.610126,-0.749962,-0.339152,-0.700858,-0.627491,-0.411115,-0.76577,-0.494491,-0.194006,-0.416272,-0.888272,-0.15714,-0.474288,-0.866207,-0.202216,-0.386425,-0.899838,-0.520737,-0.839167,-0.156774,-0.551805,-0.826685,0.109806,-0.56035,-0.821436,0.105808,-0.529984,-0.834407,-0.151097,-0.531022,-0.745476,0.402783,-0.546037,-0.746818,0.379528,-0.515,-0.843471,-0.152532,-0.542558,-0.83047,0.126041,-0.516007,-0.734581,0.440535,-0.47084,-0.811548,-0.345897,-0.468429,-0.814051,-0.343242,-0.479843,-0.80987,-0.337352,-0.431806,-0.591083,0.681265,-0.400922,-0.566149,0.720176,-0.445967,-0.539415,0.714225,-0.466384,-0.588916,0.659993,-0.371319,-0.495865,0.784967,-0.407422,-0.411512,0.815241,-0.409192,-0.557176,0.722526,-0.385418,-0.546617,0.74337,-0.373974,-0.524766,0.764672,-0.474044,-0.638661,0.606067,-0.45497,-0.613239,0.64568,-0.499344,-0.645589,0.577776,-0.267983,-0.11951,0.955962,-0.212439,0.106449,0.971343,-0.166417,0.223243,0.960418,-0.245582,0.037751,0.968627,-0.327158,-0.266762,0.906522,-0.28428,-0.040773,0.957854,-0.328776,-0.345592,0.878872,-0.35432,-0.433302,0.828639,-0.339885,-0.206244,0.917539,0.293466,-0.471969,0.831294,0.21839,-0.407636,0.886624,0.272347,-0.21427,0.938017,0.341929,-0.285928,0.895138,0.20011,-0.705649,0.679678,0.085452,-0.662587,0.744072,0.184942,-0.468398,0.863918,0.153142,-0.719138,0.677755,0.189703,-0.286905,0.938963,0.998596,0.023316,0.047487,0.989196,0.01764,0.14539,0.929289,-0.321268,0.182165,0.93057,-0.355022,0.089145,0.96057,0.017579,0.277444,0.900845,-0.307779,0.306131,0.733726,-0.642476,0.221015,0.69216,-0.708426,0.137822,0.772576,-0.562609,0.294168,0.939238,0.342814,0.016327,0.943876,0.314982,0.099155,0.806238,0.59154,-0.000671,0.832179,0.550279,0.067965,0.92758,0.309122,0.209815,0.852809,0.50267,0.141423,0.999023,0.030763,-0.030793,0.929167,0.36671,-0.04593,0.787591,0.614704,-0.04239,0.925535,-0.37846,0.010468,0.660421,-0.74752,0.070803,-0.738273,0.023988,0.674032,-0.656972,0.032105,0.753197,-0.643117,0.25837,0.720817,-0.71569,0.264565,0.646352,-0.529527,0.03943,0.847346,-0.527818,0.274514,0.803735,-0.563952,0.506485,0.652211,-0.616443,0.540666,0.572405,-0.508011,0.457198,0.729972,-0.704825,-0.236885,0.668599,-0.626759,-0.208319,0.750847,-0.557817,-0.573992,0.599445,-0.507553,-0.505203,0.697928,-0.5056,-0.209418,0.83694,-0.45909,-0.426405,0.779351,-0.798364,0.012513,0.602008,-0.762719,-0.266457,0.589221,-0.597125,-0.617359,0.512131,-0.767785,0.26606,0.582812,-0.654103,0.548601,0.520707,-0.228095,0.917112,0.326823,-0.20249,0.871883,0.445845,-0.036195,0.932249,0.359905,-0.072756,0.966643,0.24543,-0.166875,0.717612,0.676107,0.018372,0.788659,0.614521,0.068026,0.947722,0.311747,0.027619,0.978912,0.202307,0.138218,0.814692,0.563128,-0.428785,0.784112,0.448653,-0.399976,0.731437,0.552232,-0.370067,0.589831,0.717704,-0.248939,0.918027,0.308542,-0.452589,0.790796,0.41203,-0.093539,0.966521,0.238807,0.012421,0.980193,0.197485,0.187231,0.972991,0.134831,0.228675,0.944365,0.236335,0.312845,0.929838,0.19364,0.270089,0.957884,0.097293,0.319376,0.821894,0.471633,0.411481,0.809626,0.418531,0.399365,0.904294,0.1507,0.354076,0.933164,0.061373,0.503342,0.784875,0.361339,0.10596,0.979614,0.170568,0.146886,0.949797,0.276131,0.228126,0.823267,0.519761,0.185644,0.974395,0.126774,0.0983,0.981475,0.164342,0.271798,0.958312,0.087832,0.355235,0.933348,0.051088,0.534349,0.84518,0.009064,0.587207,0.8052,0.08243,0.704337,0.706931,0.064119,0.655934,0.754784,-0.002655,0.692801,0.678762,0.243446,0.78692,0.589007,0.183721,0.438826,0.898007,0.03119,0.487411,0.865871,0.11243,0.595477,0.743919,0.303262,0.525285,0.850887,-0.005982,0.436567,0.899441,0.019837,0.638203,0.769341,-0.027253,-0.117405,0.057222,0.991424,-0.102176,-0.335398,0.936491,0.083865,-0.374187,0.923521,0.083499,0.070711,0.993988,-0.087436,-0.683279,0.724876,0.07181,-0.740379,0.668294,0.204138,-0.386456,0.899411,0.224097,0.085452,0.970794,0.152257,-0.758141,0.634053,-0.341319,0.047334,0.938719,-0.320841,-0.266396,0.908872,-0.295083,-0.566546,0.769372,-0.135075,0.42204,0.89642,-0.352763,0.339732,0.871822,0.064669,0.480422,0.87463,0.201392,0.512406,0.834773,0.438307,0.101901,0.893002,0.380932,-0.385846,0.840205,0.465163,-0.385205,0.796991,0.538499,0.098666,0.836818,0.25309,-0.762352,0.595569,0.306314,-0.760491,0.572497,0.543046,-0.387768,0.744774,0.630879,0.087344,0.770928,0.35844,-0.758873,0.543657,0.332804,0.097018,0.937986,0.293191,-0.387341,0.87405,0.201392,-0.762291,0.61507,0.408734,0.532762,0.740959,0.305765,0.528489,0.791955,0.509201,0.525101,0.681845,0.605029,0.505844,0.614826,0.817499,0.045595,0.574084,0.72515,-0.378307,0.575304,0.826472,-0.343333,0.446089,0.90231,0.027711,0.430128,0.528367,-0.724418,0.442732,0.661153,-0.65569,0.364544,0.722129,0.067507,0.688437,0.625477,-0.390362,0.675527,0.42317,-0.753624,0.502884,0.793329,0.419111,0.441511,0.69924,0.471145,0.537614,0.874569,0.355815,0.329386,-0.076907,-0.944823,0.318369,-0.086154,-0.96765,0.237037,0.028748,-0.98413,0.175085,0.040407,-0.968261,0.24662,0.062777,-0.985473,0.157628,0.076815,-0.971282,0.225074,-0.295938,-0.841182,0.452559,-0.311289,-0.878811,0.361583,-0.076205,-0.876247,0.475753,-0.285836,-0.754936,0.590197,0.0553,-0.916868,0.395306,0.103275,-0.925169,0.365154,0.088412,-0.971435,0.220191,0.065218,-0.985443,0.156957,0.067507,-0.985412,0.156194,0.097598,-0.971252,0.216987,0.070467,-0.985382,0.154973,0.108585,-0.971099,0.21247,0.081454,-0.971465,0.222694,0.063631,-0.985473,0.157384,0.140629,-0.926542,0.348888,0.120121,-0.926542,0.356487,0.164525,-0.925748,0.340434,0.190588,-0.924772,0.329264,0.227302,-0.955168,0.18955,0.176183,-0.97467,0.1377,0.354534,-0.927885,0.115329,0.40849,-0.896512,0.171331,0.139409,-0.969146,0.203223,0.094852,-0.98413,0.149754,0.335887,-0.897092,0.286996,0.234687,-0.920835,0.311319,0.507706,-0.82223,0.257088,0.685385,0.682516,0.253761,0.516984,0.741295,0.427992,0.587634,0.700064,0.405652,0.726463,0.661,0.18778,0.721641,0.655995,0.221046,0.63094,0.682272,0.369304,0.644734,0.689444,0.330027,0.543474,0.705039,0.455519,0.517991,0.692801,0.501663,0.410565,0.704642,0.578692,0.458785,0.720573,0.519852,0.823481,0.444319,-0.352733,0.800256,0.391644,-0.454054,0.81814,0.352306,-0.454421,0.844844,0.420911,-0.33018,0.812525,0.579547,-0.062563,0.829524,0.517289,-0.210303,0.845759,0.501114,-0.183203,0.821467,0.569201,-0.033723,0.798486,0.601886,-0.012268,0.830134,0.531968,-0.166906,0.768151,0.612354,-0.186865,0.737083,0.675405,-0.022309,0.489334,0.23719,-0.839198,0.35432,0.196173,-0.914304,0.347545,0.12299,-0.929533,0.488601,0.162511,-0.857204,0.74514,0.356212,-0.563738,0.63567,0.29902,-0.711661,0.640095,0.230995,-0.732719,0.755394,0.298288,-0.583422,0.742851,0.27543,-0.610126,0.632344,0.188543,-0.751366,0.630787,0.173498,-0.756279,0.706931,0.298654,-0.641072,-0.148869,-0.07532,-0.985961,-0.341594,-0.215644,-0.914731,-0.351939,-0.241035,-0.904416,-0.179998,-0.130253,-0.974975,0.216712,0.13657,-0.966613,0.04358,0.037416,-0.998321,0.0159,-0.024171,-0.999573,0.200995,0.069247,-0.977111,0.211493,-0.025819,-0.97702,0.025819,-0.126072,-0.991668,0.124363,-0.292001,-0.948271,0.290475,-0.16306,-0.942869,-0.63155,-0.540269,-0.556017,-0.457259,-0.637806,-0.619709,-0.552446,-0.493912,-0.671407,-0.640553,-0.533311,-0.552477,-0.652913,-0.508164,-0.561632,-0.586993,-0.450575,-0.672567,-0.58562,-0.46733,-0.662282,-0.490555,-0.358654,-0.794153,-0.484817,-0.353069,-0.800165,-0.64687,0.474441,0.597003,-0.488571,0.562548,0.666921,-0.719718,0.467147,0.513535,-0.929411,0.280221,0.240059,-0.80633,0.400616,0.435072,-0.859951,0.374187,0.347026,-0.959593,0.228553,0.164068,-0.983276,0.115726,0.140416,-0.908567,0.27018,0.318522,-0.928831,0.057161,0.366039,-0.97943,-0.101382,0.174322,0.680044,0.708335,0.189032,0.720664,0.689993,0.066958,0.732475,0.674429,0.092502,0.598437,0.766686,0.232429,0.638661,0.739891,0.211249,0.696219,0.709189,0.110721,0.657552,0.74279,0.125919,0.701529,0.712241,0.022492,0.736015,0.67684,0.011567,0.762169,0.645283,-0.051637,0.727714,0.68508,-0.032502,0.817713,0.434492,-0.377514,0.782189,0.452925,-0.427778,0.764397,0.415784,-0.492721,0.73925,0.491134,-0.460707,0.782769,0.454909,-0.424604,0.726981,0.414014,-0.547777,0.678976,0.437452,-0.589557,0.585803,0.347056,-0.732353,0.636738,0.332438,-0.6957,0.504959,0.192907,-0.841273,0.454756,0.207495,-0.866085,0.006348,-0.37669,-0.926298,-0.267342,-0.652058,-0.709433,-0.240181,-0.605579,-0.758629,-0.007508,-0.36314,-0.9317,0.367107,0.006623,-0.930143,0.199957,-0.177313,-0.963591,0.179144,-0.170476,-0.968932,0.343638,0.007935,-0.939055,0.299692,0.027284,-0.953612,0.141636,-0.147374,-0.978851,0.109043,-0.108036,-0.988128,0.251991,0.06769,-0.965331,-0.406659,-0.912381,0.04651,-0.376354,-0.900876,0.216132,-0.394879,-0.90936,0.130772,-0.401715,-0.913022,-0.070345,-0.444105,-0.833918,-0.327586,-0.444075,-0.889584,-0.106601,-0.417341,-0.876675,-0.239265,-0.397351,-0.795068,-0.458174,-0.374004,-0.735008,-0.565569,-0.412519,-0.83697,-0.359539,-0.430433,-0.774743,-0.463057,-0.369488,-0.664968,-0.649037,-0.33784,-0.897031,0.28489,-0.294565,-0.945402,0.139225,-0.290384,-0.950591,0.109714,-0.340007,-0.898984,0.27604,-0.35609,-0.882992,0.305734,-0.348704,-0.878842,0.325541,-0.358623,-0.88052,0.309854,-0.378368,-0.887539,0.262795,-0.404218,-0.89172,0.203436,-0.378063,-0.885678,0.269417,-0.33723,-0.922239,0.189032,0.391247,-0.89993,-0.192389,0.78457,-0.619983,-0.006989,0.731071,-0.649098,0.210059,0.275643,-0.960265,0.043214,-0.186346,-0.891964,-0.411878,0.090304,-0.946776,-0.308939,-0.045717,-0.996796,-0.065493,-0.322947,-0.932646,-0.160772,-0.460067,-0.882199,0.100101,-0.192816,-0.961089,0.197668,-0.393567,-0.672811,0.626392,-0.606128,-0.632954,0.481582,-0.27955,0.615619,0.736778,-0.23365,0.584979,0.776635,-0.049684,0.531419,0.845637,-0.108676,0.601184,0.79165,-0.059511,0.518021,0.853267,-0.019623,0.407117,0.913144,0.072207,0.25956,0.963012,0.040742,0.411969,0.910276,0.21543,0.329173,0.91934,0.274422,0.176183,0.945311,0.665426,-0.709769,0.231086,0.826167,-0.408338,0.388134,0.697409,-0.393139,0.59917,0.515152,-0.727287,0.453444,0.305918,-0.946196,-0.10538,0.501083,-0.860958,0.087405,0.34846,-0.883755,0.312265,0.153569,-0.981414,0.115024,-0.029939,-0.931059,0.363567,0.158361,-0.815577,0.556505,-0.229347,-0.48088,0.846248,-0.429884,-0.582263,0.690023,-0.546403,-0.77871,-0.308237,-0.663472,-0.567278,-0.487808,-0.716514,-0.623524,-0.312693,-0.694723,-0.69277,-0.193365,-0.625782,-0.74984,-0.214759,-0.768578,-0.593188,-0.23957,-0.816706,-0.533403,-0.220099,-0.901914,-0.381176,-0.202979,-0.87936,-0.41731,-0.229194,-0.992004,-0.099612,-0.077425,-0.984436,-0.161962,-0.068117,0.435743,0.405866,-0.803339,0.462294,0.331767,-0.822291,0.587085,0.408673,-0.698752,0.535722,0.5009,-0.679739,0.463942,0.421857,-0.778924,0.436872,0.430403,-0.78985,0.529984,0.531236,-0.660939,0.556719,0.524033,-0.64452,0.618213,0.595386,-0.513077,0.595843,0.603687,-0.529588,0.63448,0.638478,-0.43553,0.654683,0.633564,-0.412214,-0.055025,-0.117893,-0.991485,-0.095614,-0.071505,-0.992828,0.075076,0.063845,-0.995117,0.099429,0.047517,-0.993896,-0.055666,-0.232429,-0.971007,-0.053835,-0.170232,-0.983917,0.098361,0.008362,-0.995086,0.096316,-0.047914,-0.994171,0.228034,0.127781,-0.965209,0.227821,0.174902,-0.957854,0.337718,0.315653,-0.886715,0.352123,0.291238,-0.889462,-0.57683,-0.642811,-0.503952,-0.614978,-0.579516,-0.534715,-0.561693,-0.511063,-0.650563,-0.52266,-0.572069,-0.632069,-0.500748,-0.776543,-0.382336,-0.536576,-0.712302,-0.452406,-0.489486,-0.635121,-0.597461,-0.461165,-0.70217,-0.542436,-0.375591,-0.589251,-0.715293,-0.391614,-0.522355,-0.757469,-0.233955,-0.359416,-0.903348,-0.230445,-0.423231,-0.876186,-0.176305,-0.882656,0.435621,-0.345134,-0.455885,0.820368,-0.475478,-0.783929,0.399182,-0.221229,-0.967956,-0.118656,-0.227241,-0.961547,0.15421,-0.505387,-0.854976,0.116489,-0.480911,-0.859706,-0.171941,-0.685446,-0.68572,-0.244728,-0.720359,-0.691458,0.054018,-0.958037,-0.285562,-0.024628,-0.907498,-0.292154,-0.301706,0.81991,0.555589,-0.137913,0.807306,0.535295,-0.248299,0.836756,0.499771,-0.22367,0.752068,0.653401,-0.086184,0.790521,0.602252,-0.110935,0.812586,0.545518,-0.205084,0.773827,0.604144,-0.190069,0.779046,0.553026,-0.295267,0.815668,0.495865,-0.297922,0.807825,0.472274,-0.35258,0.769524,0.520859,-0.369427,0.709708,0.698508,-0.091311,0.692618,0.721,0.019654,0.681875,0.731407,0.006775,0.693808,0.715293,-0.083529,0.683279,0.629444,-0.369976,0.707389,0.667562,-0.232215,0.689749,0.691458,-0.214637,0.665456,0.661275,-0.346141,0.683248,0.657033,-0.318461,0.703116,0.684103,-0.193823,0.734458,0.653157,-0.184149,0.728568,0.616016,-0.299448,0.543809,0.788965,0.285928,0.569292,0.781793,0.254311,0.633595,0.759911,0.145085,0.614856,0.768975,0.174871,0.494217,0.788873,0.365246,0.512375,0.795648,0.323069,0.589984,0.779565,0.21015,0.580889,0.774377,0.250679,0.651479,0.748497,0.123508,0.650685,0.753899,0.090548,0.232917,0.779931,0.580859,0.45909,0.804437,0.376934,0.417249,0.801294,0.428724,0.145177,0.750542,0.644642,0.184851,0.764763,0.617206,0.360546,0.804346,0.472213,0.314402,0.802118,0.507675,0.447615,0.805017,0.389264,0.491928,0.794488,0.355998,0.579577,0.766564,0.276406,0.538347,0.785913,0.304056,0.894345,-0.331187,0.300729,0.841487,0.049776,0.537919,0.770989,-0.312418,0.554918,0.395795,-0.914579,-0.082766,0.726585,-0.672506,0.140599,0.579211,-0.671407,0.462233,0.23133,-0.942198,0.242225,0.063356,-0.822718,0.564898,0.377392,-0.54799,0.746483,0.045259,-0.245796,0.96823,-0.230995,-0.457381,0.85873,-0.750389,-0.660421,0.026215,-0.338298,-0.81991,-0.461776,-0.538621,-0.838679,0.080203,-0.706015,-0.443464,0.55211,-0.75045,-0.577807,0.320811,-0.548021,-0.751305,0.367687,-0.509323,-0.610065,0.606922,-0.216834,-0.716208,0.66332,-0.260659,-0.866268,0.42616,0.368938,-0.842738,0.39198,0.365734,-0.69863,0.614887,0.766045,0.634083,-0.105197,0.703543,0.708396,0.056063,0.888791,0.445479,-0.107486,0.810205,0.571245,-0.131138,0.760735,0.643971,0.080752,0.846156,0.515702,0.13422,0.763024,0.512925,0.393262,0.698935,0.647084,0.304483,0.322123,0.478988,0.816553,0.417066,0.380688,0.825282,0.454634,-0.309824,-0.835017,0.799951,-0.435621,0.412641,0.485794,-0.288339,-0.825129,0.335765,-0.295022,-0.894528,0.385632,-0.295236,-0.874111,0.429395,-0.232765,-0.872585,0.449019,-0.183508,-0.874447,0.588549,-0.111728,-0.800684,0.488388,-0.188849,-0.851924,0.773858,0.228736,-0.590594,0.835688,0.20191,-0.510697,-0.512528,-0.854427,-0.085055,-0.344066,-0.931913,-0.114689,-0.508896,-0.849178,-0.140934,-0.720389,-0.693533,-0.006714,-0.625385,-0.778741,-0.048952,-0.59331,-0.787896,-0.164861,-0.671773,-0.713828,-0.19776,-0.546892,-0.760491,-0.350047,-0.507492,-0.823176,-0.254555,-0.064455,-0.718925,-0.692068,-0.104617,-0.663808,-0.740532,-0.248604,0.110294,0.962279,0.75692,-0.495346,0.426221,-0.285257,0.049104,0.957183,-0.079745,0.146672,0.985931,-0.212653,0.128636,0.968596,-0.320139,0.066561,0.945006,-0.273873,0.077364,0.958647,-0.403516,-0.04355,0.913907,-0.375958,-0.032502,0.926054,-0.636494,-0.521622,0.568072,-0.68804,-0.450453,0.568896,-0.621326,-0.713187,0.324442,-0.459059,-0.834376,0.305002,-0.45732,-0.829585,0.320322,-0.599719,-0.750877,0.276528,-0.752708,-0.606769,0.255409,-0.695486,-0.647023,0.312418,-0.655354,-0.729698,0.194983,-0.706656,-0.703116,0.078677,-0.572863,-0.81576,-0.07947,-0.544939,-0.833491,0.090945,-0.220862,-0.826441,-0.517869,-0.244942,-0.767205,-0.592761,-0.000183,0.153966,0.988067,-0.123417,0.103183,0.986969,0.223975,0.175481,0.958647,0.110691,0.169317,0.979308,-0.111972,0.07065,0.99118,-0.047212,0.044435,0.997894,-0.241707,-0.087893,0.966338,-0.236457,-0.069643,0.969115,-0.542619,-0.407331,0.734581,-0.59096,-0.390759,0.70571,0.768395,0.510086,-0.386425,0.781487,0.573168,-0.246376,0.86227,0.365246,-0.35081,0.79458,0.471206,-0.382855,0.820612,0.532579,-0.20716,0.885433,0.43498,-0.16364,0.885464,0.460341,0.063326,0.839534,0.543168,-0.010407,0.65801,0.458876,0.597003,0.709433,0.40199,0.578845,0.264748,-0.423841,-0.866146,0.781213,-0.516587,0.350444,0.306345,-0.394269,-0.866421,0.102573,-0.382031,-0.918424,0.170141,-0.385907,-0.906674,0.203253,-0.318888,-0.925718,0.195654,-0.268227,-0.943266,0.340617,-0.211097,-0.916166,0.24659,-0.284036,-0.926542,0.615528,0.128239,-0.777581,0.70455,0.117862,-0.699759,0.620045,0.762841,0.183233,0.467208,0.807306,0.360424,0.687826,0.68923,0.227577,0.655446,0.739616,0.152654,0.484603,0.775536,0.404553,0.520371,0.689749,0.503403,0.388348,0.626026,0.676168,0.329417,0.728721,0.60033,-0.011811,0.529618,0.848109,0.103916,0.453169,0.885342,0.539933,-0.333659,-0.772729,0.556139,-0.240699,-0.795434,0.405194,-0.396832,-0.823573,0.474929,-0.381024,-0.793237,0.540544,-0.241585,-0.805841,0.554003,-0.206549,-0.806482,0.702658,-0.031007,-0.710837,0.648183,-0.124821,-0.751152,0.83813,0.381603,-0.389691,0.855281,0.404004,-0.324381,-0.461196,-0.879971,0.113529,-0.341685,-0.939665,-0.015442,-0.378216,-0.922666,-0.074862,-0.577868,-0.815607,-0.027924,-0.508347,-0.860408,0.035279,-0.418592,-0.897946,-0.135716,-0.470687,-0.854396,-0.219977,-0.309305,-0.869625,-0.384777,-0.28547,-0.917264,-0.277627,0.116062,-0.772179,-0.624683,0.05472,-0.733665,-0.677267,-0.357036,0.245247,0.901303,-0.377209,0.05005,0.924741,-0.467757,-0.016938,0.883663,-0.262459,0.157079,0.952055,-0.37138,0.182348,0.910367,-0.524552,-0.072756,0.848262,-0.48088,-0.060793,0.87466,-0.627033,-0.311869,0.713828,-0.598529,-0.329997,0.729942,-0.608692,-0.672323,0.421216,-0.674459,-0.642415,0.363842,-0.05182,-0.420026,-0.906003,0.000549,-0.393414,-0.91934,0.182745,-0.173986,-0.96762,0.128086,-0.189886,-0.973388,-0.160253,-0.404248,-0.900479,-0.123722,-0.432875,-0.892911,0.079928,-0.215613,-0.973174,0.081729,-0.231513,-0.969359,0.317789,-0.074465,-0.945219,0.279305,-0.018097,-0.960021,0.550462,0.227912,-0.803125,0.624561,0.141179,-0.76809,-0.540056,-0.804315,0.247749,-0.44612,-0.878872,0.168767,-0.490829,-0.87109,0.016053,-0.573321,-0.814631,0.087497,-0.710013,-0.624805,0.324778,-0.617023,-0.726615,0.302042,-0.644581,-0.756462,0.110508,-0.747459,-0.654408,0.114109,-0.714438,-0.694052,-0.088351,-0.621509,-0.779931,-0.073275,-0.436354,-0.712272,-0.54973,-0.509262,-0.649037,-0.565111,0.179357,0.172277,0.968566,0.055208,0.157628,0.985931,0.421644,0.086886,0.902585,0.304178,0.142064,0.941954,0.102237,0.084384,0.99115,0.189245,0.01767,0.98175,-0.014679,-0.109836,0.993835,0.006134,-0.076357,0.99704,-0.36729,-0.491134,0.789819,-0.443129,-0.424299,0.789666,0.703574,0.425459,-0.56914,0.794885,0.476424,-0.375683,0.840419,0.279,-0.464553,0.75512,0.388318,-0.528184,0.84814,0.426252,-0.314554,0.923948,0.305643,-0.229865,0.956145,0.287942,0.053102,0.911283,0.407483,-0.058901,0.774651,0.317209,0.547044,0.801477,0.219916,0.556078,0.968413,0.246529,-0.037172,0.892575,0.133183,-0.430708,0.86993,0.237068,-0.432417,0.933744,0.353587,-0.05533,0.826777,0.304819,0.472732,0.933836,0.288034,0.212043,0.895108,0.401685,0.193426,0.782983,0.427717,0.451613,0.751549,0.476516,0.45613,0.876614,0.442701,0.188482,0.883847,0.432325,0.178564,0.772607,0.448347,0.449446,0.065798,-0.227577,-0.971526,-0.42906,-0.319041,-0.845027,-0.421552,-0.404797,-0.811396,0.111606,-0.257637,-0.959746,0.622456,-0.035096,-0.781854,0.362438,-0.138646,-0.921628,0.408124,-0.108463,-0.90643,0.63805,0.04062,-0.768883,0.694693,0.079562,-0.714866,0.505264,-0.083285,-0.858913,0.53679,-0.116764,-0.835566,0.707846,0.006836,-0.70629,-0.910764,-0.407086,-0.068911,-0.839717,-0.542589,0.020875,-0.769951,-0.595325,-0.229652,-0.840266,-0.471603,-0.267403,-0.96646,-0.212714,-0.143651,-0.938475,-0.31373,-0.144292,-0.880825,-0.357372,-0.310495,-0.90582,-0.24485,-0.345683,-0.796289,-0.295694,-0.527696,-0.790185,-0.400128,-0.464156,-0.022065,0.309397,0.950652,0.017823,0.244514,0.969451,-0.218513,0.146672,0.964721,-0.287851,0.170019,0.942442,-0.069735,0.170721,0.982818,-0.069338,0.245155,0.966979,-0.372723,0.114078,0.920865,-0.385601,0.087527,0.918485,-0.637928,0.011933,0.769951,-0.627247,-0.012329,0.77868,-0.919095,-0.188971,0.345714,-0.928526,-0.10947,0.354747,-0.867916,-0.489029,0.086917,-0.811396,-0.552355,0.191076,-0.769219,-0.637715,-0.040162,-0.830256,-0.542863,-0.126255,-0.980438,-0.1901,0.050447,-0.927671,-0.372784,0.02002,-0.903745,-0.39491,-0.165044,-0.964324,-0.208136,-0.163518,-0.898465,-0.247414,-0.36259,-0.843745,-0.416333,-0.338694,-0.525773,-0.424329,-0.737205,-0.570482,-0.296335,-0.765954,0.261635,0.202918,0.943571,0.299203,0.204474,0.932005,-0.016358,0.074404,0.99707,-0.05887,0.0918,0.994018,0.162664,0.237861,0.957549,0.19068,0.217505,0.957244,-0.117283,0.122196,0.985534,-0.124729,0.184606,0.974853,-0.395032,0.118046,0.911039,-0.39201,0.002441,0.919919,-0.800653,-0.235389,0.55092,-0.820246,-0.059877,0.568834,0.940367,0.13831,-0.310709,0.790979,0.030854,-0.611042,0.783288,0.058138,-0.618915,0.931211,0.170476,-0.322092,0.945341,0.2537,0.204779,0.976623,0.204474,-0.066073,0.969634,0.228767,-0.086184,0.942656,0.274026,0.190466,0.955229,0.252998,0.153203,0.97174,0.203772,-0.1189,0.947752,0.288491,-0.135899,0.936033,0.333262,0.112888,-0.069277,-0.2154,-0.974059,-0.052767,-0.297952,-0.953093,0.502029,-0.085665,-0.860561,0.231574,-0.158391,-0.959807,0.236091,-0.194464,-0.952055,0.495804,-0.087252,-0.864009,0.517502,-0.128452,-0.845973,0.278298,-0.245643,-0.928526,0.274361,-0.26075,-0.925565,0.498123,-0.152287,-0.853603,0.51558,0.534074,0.670003,0.594256,0.437422,0.674886,0.745323,0.439253,0.50148,0.665365,0.546495,0.508499,0.426099,0.568072,0.704031,0.448042,0.584796,0.676168,0.60567,0.606555,0.514969,0.58974,0.586047,0.55562,0.768059,0.558519,0.313181,0.753502,0.599567,0.269631,0.855403,0.472488,-0.212134,0.892422,0.410352,-0.187475,0.466231,-0.35139,-0.811853,-0.031556,-0.51561,-0.856227,-0.110446,-0.375561,-0.920164,0.418928,-0.203223,-0.884945,0.919095,-0.026063,-0.393109,0.743614,-0.192755,-0.640187,0.690054,-0.075747,-0.719748,0.889218,0.053407,-0.454329,0.858699,0.069674,-0.507706,0.647511,-0.055879,-0.759972,0.575274,-0.052248,-0.816248,-0.809076,-0.576098,-0.116062,-0.81988,-0.376049,0.431654,-0.901028,-0.263192,0.344676,-0.887326,-0.415601,-0.199774,-0.459181,-0.624317,-0.631916,-0.661885,-0.633686,-0.400372,-0.751244,-0.452925,-0.480026,-0.564867,-0.455153,-0.688284,-0.586413,-0.398175,-0.705344,-0.764,-0.404004,-0.503037,-0.731529,-0.467879,-0.495865,-0.746727,-0.146611,0.648732,-0.732597,-0.097507,0.673635,-0.437666,0.062746,0.896908,-0.490921,0.079012,0.867611,-0.644032,-0.143742,0.751335,-0.714866,-0.136357,0.685781,-0.473647,0.122044,0.872189,-0.40202,0.118778,0.907865,-0.182867,0.286081,0.94058,-0.210608,0.313547,0.925901,0.160131,0.495773,0.853542,0.151067,0.469619,0.869808,-0.220923,-0.291421,-0.930723,-0.136448,-0.342753,-0.929441,0.127049,-0.243782,-0.961455,0.098636,-0.261757,-0.960051,-0.254555,-0.204993,-0.945067,-0.265297,-0.234107,-0.935301,0.071505,-0.253853,-0.964568,0.075869,-0.244819,-0.966582,0.380779,-0.228858,-0.895871,0.377392,-0.225623,-0.898129,0.739708,-0.139988,-0.658162,0.740013,-0.143742,-0.657033,-0.917203,0.207739,0.339915,-0.667074,0.2837,0.688833,-0.687429,0.188482,0.701346,-0.938078,0.129215,0.321329,-0.978271,0.088534,-0.187292,-0.98349,0.156865,0.090091,-0.991424,0.105686,0.076632,-0.982238,0.046388,-0.181799,-0.980132,-0.075533,-0.183294,-0.997467,-0.022004,0.067385,-0.925871,-0.366283,0.092685,-0.889889,-0.422224,-0.172613,0.381237,0.20072,0.902402,0.469161,0.122349,0.874569,0.194006,0.071718,0.978362,0.099216,0.15656,0.982665,0.299478,0.290292,0.908841,0.31251,0.262215,0.912961,0.010407,0.245735,0.969268,-0.006897,0.303446,0.952788,-0.284768,0.307321,0.907987,-0.27369,0.22486,0.935148,0.940489,-0.029756,-0.33842,0.941832,-0.037416,-0.333903,0.985046,0.115116,0.128086,0.991791,0.046022,-0.119083,0.992828,0.033235,-0.114658,0.985229,0.11124,0.129948,0.984436,0.096866,0.14658,0.993652,0.033113,-0.107456,0.978454,0.14359,-0.148167,0.979949,0.16187,0.11594,0.972503,-0.229133,0.040864,0.906186,-0.265877,-0.328745,0.929075,-0.07355,-0.362407,0.99881,-0.02588,0.041231,0.834742,-0.178625,0.520798,0.939146,-0.207801,0.273476,0.961699,-0.002319,0.274056,0.853359,0.027619,0.520554,0.850215,0.136021,0.50853,0.960448,0.104617,0.257973,0.952971,0.190588,0.235572,0.843959,0.214209,0.491714,0.118442,-0.293161,-0.948668,-0.389141,-0.218146,-0.894955,-0.415448,-0.092563,-0.904874,0.09827,-0.131809,-0.986389,0.645192,-0.337413,-0.685446,0.393109,-0.335551,-0.856044,0.382794,-0.149052,-0.91171,0.647877,-0.137089,-0.74929,0.631977,-0.044038,-0.773705,0.360302,-0.083468,-0.929075,0.360851,-0.09653,-0.92758,0.633717,-0.034059,-0.77279,-0.778985,-0.22425,-0.585528,-0.588855,-0.615253,-0.524064,-0.708487,-0.643391,-0.289895,-0.906125,-0.239753,-0.34843,-0.806696,-0.041353,-0.589465,-0.807764,-0.06476,-0.585894,-0.930418,-0.060244,-0.361492,-0.931852,-0.028626,-0.361675,-0.993286,-0.008728,-0.115238,-0.99118,-0.058992,-0.118503,-0.943266,-0.040559,0.329478,-0.936094,0.032685,0.350139,-0.709952,-0.231361,0.66512,-0.477187,-0.211646,0.852901,-0.664449,0.08591,0.742363,-0.695425,-0.007843,0.718528,-0.455519,0.024628,0.889859,-0.422956,0.129337,0.896847,-0.092654,0.173223,0.980499,-0.120731,0.064272,0.99057,0.469924,0.072085,0.879727,0.479049,0.182592,0.858577,-0.936796,0.093051,-0.337199,-0.988647,0.13065,-0.073916,-0.917264,0.201209,-0.343669,-0.916166,0.228523,-0.329173,-0.957305,0.27253,-0.096072,-0.963347,0.242714,-0.114139,-0.953246,0.279733,0.114078,-0.944914,0.300424,0.129826,-0.751579,0.33488,0.568285,-0.749596,0.33784,0.56914,-0.432936,0.10596,0.895169,-0.187292,0.003357,0.982269,-0.35258,0.324351,0.877743,-0.375835,0.286721,0.881191,-0.12775,0.211737,0.968932,-0.099368,0.284341,0.953551,0.183172,0.225867,0.956755,0.156011,0.11713,0.980773,0.671896,-0.093448,0.734703,0.703146,0.050722,0.709189,0.753319,-0.601245,-0.266366,0.56505,-0.581195,-0.585589,0.674093,-0.362774,-0.643391,0.876736,-0.370312,-0.306803,0.821558,-0.507126,0.260414,0.81872,-0.573748,-0.021638,0.934935,-0.349132,-0.062807,0.931608,-0.286477,0.223548,0.970397,-0.124241,0.207038,0.978576,-0.189184,-0.081057,0.996277,0.036409,-0.077944,0.976928,0.088382,0.194311,-0.154759,-0.268471,-0.950743,-0.62212,-0.029817,-0.782342,-0.604755,0.071627,-0.793146,-0.082217,-0.131687,-0.987854,0.28135,-0.504959,-0.815973,0.076907,-0.400739,-0.912931,0.173528,-0.223701,-0.959075,0.381726,-0.291269,-0.877163,0.442518,-0.176855,-0.879116,0.214393,-0.154973,-0.964354,0.254982,-0.138798,-0.956908,0.511856,-0.108951,-0.852107,0.202216,-0.628193,-0.751274,-0.074404,-0.539811,-0.838466,0.332438,-0.278817,-0.900937,0.292367,-0.411084,-0.863399,0.004395,-0.342021,-0.939665,0.038575,-0.237709,-0.97055,-0.270486,-0.137089,-0.952879,-0.287484,-0.211249,-0.934172,-0.751091,0.05005,-0.658254,-0.733696,0.08591,-0.674001,-0.897305,0.175665,0.404889,-0.717154,0.210089,0.664449,-0.682791,0.358013,0.63683,-0.877773,0.330058,0.347209,-0.988311,0.108829,-0.10654,-0.973357,0.149052,0.1742,-0.945708,0.302072,0.119877,-0.956511,0.248939,-0.151891,-0.945219,0.273751,-0.177801,-0.939512,0.329081,0.094882,-0.959777,0.266274,0.088687,-0.960082,0.200598,-0.194769,-0.472457,0.171972,0.864376,-0.56737,-0.17243,0.80517,-0.375958,-0.268349,0.886898,-0.22776,0.117862,0.966552,-0.335582,0.415937,0.84518,-0.393262,0.350505,0.849971,-0.137974,0.314463,0.939177,-0.076479,0.394848,0.915555,0.231483,0.337138,0.912534,0.172643,0.237007,0.956023,0.65804,0.023225,0.752586,0.719138,0.143651,0.67983,0.800592,-0.595325,-0.067598,0.59273,-0.648,-0.478225,0.682394,-0.432081,-0.589557,0.9064,-0.37846,-0.187475,0.791131,-0.41026,0.453566,0.830256,-0.521714,0.196081,0.944365,-0.320017,0.075716,0.913663,-0.222602,0.340037,0.962554,-0.082339,0.258217,0.985076,-0.171758,-0.008118,0.996063,-0.023255,-0.085421,0.984344,0.044801,0.170385,-0.402417,0.423658,0.811518,-0.142308,0.588092,0.796136,-0.20719,0.533097,0.820276,-0.472243,0.313883,0.823664,-0.440687,0.359569,0.822474,-0.250587,0.491867,0.833796,-0.284433,0.470473,0.835292,-0.108585,0.599872,0.792657,-0.066073,0.597919,0.798791,0.128361,0.6957,0.706748,0.078768,0.709677,0.700095,0.326884,0.75161,0.572863,0.453017,0.782586,0.426954,0.258827,0.778649,0.571551,0.296213,0.766808,0.569384,0.423566,0.802942,0.419294,0.416364,0.80459,0.423383,0.534654,0.807306,0.249672,0.524979,0.811396,0.256844,0.604114,0.79339,0.074252,0.611988,0.788934,0.054628,0.675771,0.733482,-0.072481,0.697897,0.672597,-0.245979,0.653249,0.749046,-0.110294,0.673574,0.681783,-0.285348,0.660726,0.590228,-0.463698,0.671957,0.592364,-0.444441,0.659383,0.478164,-0.580126,0.654592,0.480789,-0.583361,0.554521,0.001373,-0.832148,0.467727,-0.151646,-0.870754,0.488357,-0.190588,-0.851558,0.57271,-0.005585,-0.819727,0.655965,0.319803,-0.683645,0.616504,0.164403,-0.769951,0.621601,0.173376,-0.763878,0.646077,0.336589,-0.685018,0.648946,0.354289,-0.673269,0.635945,0.484176,-0.600909,0.06119,-0.58272,-0.810327,-0.082034,-0.677633,-0.730796,-0.058962,-0.762444,-0.644337,0.092776,-0.655416,-0.749535,0.35316,-0.30134,-0.885678,0.212165,-0.44969,-0.867611,0.235786,-0.520005,-0.820948,0.370159,-0.366466,-0.853603,-0.221686,-0.743522,-0.630848,-0.365032,-0.78048,-0.507492,-0.214545,-0.826746,-0.520035,-0.359142,-0.845241,-0.395672,-0.478103,-0.829951,-0.287332,-0.577074,-0.788324,-0.213233,-0.668844,-0.694113,-0.26603,-0.80282,-0.52562,-0.28135,-0.776482,-0.608661,-0.163091,-0.677969,-0.720176,-0.14716,-0.779748,-0.6245,-0.044038,-0.856136,-0.512345,0.067049,-0.893704,-0.418714,0.161016,-0.927366,-0.267556,0.261452,-0.891263,-0.148564,0.428388,-0.897122,-0.336528,0.286111,-0.866359,-0.237159,0.439467,-0.799127,-0.081576,0.595538,-0.646138,0.1583,0.746605,0.866146,-0.061495,-0.495956,0.988189,0.01825,0.151982,0.980499,-0.062807,0.186193,0.913755,-0.125645,-0.386273,0.802942,-0.262947,-0.534867,0.835139,-0.098056,-0.541185,0.785668,-0.183386,-0.590777,0.689016,-0.339244,-0.6404,0.539201,-0.351543,-0.765282,0.706839,-0.257637,-0.658742,0.660543,-0.18305,-0.728111,0.455824,-0.221931,-0.861934,-0.791314,-0.417707,-0.446425,-0.587481,-0.491226,-0.643055,-0.904111,-0.310373,-0.293649,-0.817621,-0.429701,-0.38319,-0.588916,-0.47911,-0.650838,-0.631519,-0.39787,-0.665456,-0.305704,-0.426099,-0.851436,-0.282937,-0.5215,-0.804926,0.265542,-0.517014,-0.813715,0.15482,-0.424329,-0.892148,-0.544603,0.288675,0.787408,-0.578356,0.399792,0.711051,-0.761193,0.315744,0.566393,-0.748497,0.183599,0.637165,-0.413648,0.180486,0.892361,-0.502274,0.203925,0.840297,-0.759575,0.072695,0.64629,-0.780908,0.046144,0.622913,-0.919034,-0.045625,0.391491,-0.892666,-0.072817,0.444746,-0.962279,-0.263527,0.067263,-0.976867,-0.162816,0.138585,0.528825,0.167089,0.832087,0.410077,0.281472,0.867489,0.714164,0.075594,0.695853,0.688314,0.059297,0.722953,0.539323,0.153783,0.827906,0.645466,0.141362,0.750572,0.603442,0.168676,0.779351,0.381176,0.240364,0.892666,-0.059358,0.285989,0.956359,0.175542,0.225745,0.95822,0.503098,-0.304209,-0.808893,0.721671,-0.238319,-0.649892,0.518326,-0.620991,-0.587939,0.532426,-0.464553,-0.707602,0.75042,-0.356059,-0.55678,0.737724,-0.504776,-0.448225,0.887753,-0.376751,-0.264412,0.90112,-0.241401,-0.360118,0.998169,-0.060152,0.000549,0.981445,-0.18656,0.043611,-0.896023,-0.224219,-0.38316,-0.923124,-0.146001,-0.355663,-0.68746,-0.278664,-0.670583,-0.692984,-0.365917,-0.621143,-0.753685,-0.569964,-0.327128,-0.84753,-0.37138,-0.379101,-0.641865,-0.497848,-0.583178,-0.5215,-0.683554,-0.510636,-0.257149,-0.736625,-0.625477,-0.390759,-0.573077,-0.720298,0.077761,-0.567064,-0.819971,0.129246,-0.703848,-0.698477,-0.393689,0.331095,0.857509,-0.349223,0.319803,0.880734,-0.739311,0.207984,0.6404,-0.659993,0.258248,0.705435,-0.271371,0.036103,0.961791,-0.375286,0.224738,0.899228,-0.601123,0.181951,0.77813,-0.509934,-0.009705,0.860134,-0.724601,-0.073611,0.685171,-0.794763,0.113376,0.59621,-0.986206,-0.115574,0.118259,-0.93582,-0.300821,0.18363,0.8699,0.261483,0.418165,0.759331,0.296976,0.578967,0.683218,0.339061,0.646687,0.713736,0.34785,0.607898,0.912351,-0.096255,0.39787,0.917814,0.075747,0.38966,0.769341,0.154576,0.6198,0.773064,-0.036958,0.633198,0.554704,0.018281,0.831813,0.550188,0.216041,0.806574,0.093265,0.264107,0.95996,0.137089,0.085543,0.986847,0.170598,0.687368,0.705954,-0.02234,0.651295,0.758476,0.366253,0.732658,0.573626,0.285257,0.715201,0.63802,0.116733,0.690695,0.713645,0.224403,0.716727,0.660207,0.080844,0.681722,0.727073,-0.04178,0.645985,0.762169,-0.123295,0.628468,0.767968,-0.020569,0.625996,0.779504,0.136784,0.719169,0.681204,-0.026551,0.603809,0.796655,-0.023988,0.603015,0.797357,0.143925,0.714194,0.684957,0.351329,0.798486,0.488845,0.251442,0.77398,0.581103,0.269753,0.77575,0.570421,0.371654,0.802484,0.46675,0.397351,0.801355,0.447127,0.288705,0.775231,0.561815,0.293741,0.787408,0.541887,0.41789,0.804895,0.421277,-0.493088,0.146611,0.857509,-0.396283,0.041017,0.917203,-0.285012,0.27134,0.919279,-0.346171,0.334513,0.876492,-0.457381,0.152745,0.876034,-0.511216,0.151006,0.846065,-0.369549,0.333659,0.867214,-0.339366,0.318247,0.885159,-0.20069,0.452589,0.868831,-0.213874,0.462813,0.860256,0.049898,-0.981292,0.185858,-0.084719,-0.986969,0.136662,-0.226478,-0.9288,0.293252,-0.142064,-0.915098,0.377331,0.089175,-0.969207,0.229499,0.049959,-0.971984,0.229591,-0.133946,-0.879727,0.45616,-0.038453,-0.871456,0.488937,-0.305338,-0.611438,0.729972,-0.40434,-0.641865,0.651509,-0.581103,-0.21894,0.783807,-0.502121,-0.193579,0.84283,-0.375011,-0.199347,0.905301,-0.309336,0.083865,0.947233,-0.302408,-0.09299,0.948607,-0.321116,-0.162969,0.93289,-0.259072,0.123112,0.957945,-0.237159,0.187445,0.953185,-0.156865,0.42143,0.893155,-0.178869,0.374248,0.909879,-0.019196,0.620106,0.784265,-0.025819,0.625477,0.779778,-0.265633,-0.963561,-0.030488,-0.326853,-0.943175,0.059328,-0.21073,-0.975127,0.068331,-0.249214,-0.968261,0.018159,-0.306772,-0.942503,0.132359,-0.283822,-0.937254,0.202429,-0.36024,-0.838191,0.409406,-0.37376,-0.862575,0.340922,-0.389477,-0.581713,0.714042,-0.382275,-0.522324,0.76223,-0.232765,-0.972015,-0.031159,-0.208106,-0.973632,-0.093265,-0.186621,-0.976257,-0.109867,-0.218085,-0.973846,-0.063601,-0.186865,-0.97766,-0.096164,-0.152959,-0.980865,-0.120304,-0.153356,-0.986236,-0.061464,-0.196204,-0.978362,-0.065279,-0.215003,-0.975524,-0.045991,-0.167669,-0.985595,-0.021577,0.123264,-0.985626,-0.115482,0.131962,-0.991241,0.00354,0.135228,-0.990692,-0.01471,0.117771,-0.978118,-0.171453,0.067049,-0.986389,-0.149907,0.092074,-0.980834,-0.171606,0.076998,-0.978637,-0.190497,0.049623,-0.985412,-0.162633,-0.086062,-0.980407,-0.177007,-0.056154,-0.978576,-0.197913,-0.336772,-0.91937,-0.203253,-0.400952,-0.897336,-0.184301,-0.482925,-0.773675,-0.410077,-0.560503,-0.698294,-0.445204,-0.526292,-0.781793,-0.33433,-0.445418,-0.862117,-0.241432,-0.239906,-0.926878,-0.288614,-0.372875,-0.860775,-0.346416,-0.311289,-0.936644,-0.160497,-0.154332,-0.981445,-0.113742,-0.150578,-0.986572,0.062838,-0.30195,-0.952544,0.038209,-0.857601,-0.431776,-0.279366,-0.793146,-0.473983,-0.382397,-0.693686,-0.603931,-0.392468,-0.797662,-0.492294,-0.348308,-0.7463,-0.501938,-0.437086,-0.660787,-0.587848,-0.466628,-0.631519,-0.565111,-0.530808,-0.692618,-0.506272,-0.513749,-0.672353,-0.518143,-0.528611,-0.640492,-0.555193,-0.530534,-0.49089,-0.126682,0.861934,-0.200079,0.004669,0.979736,-0.682119,-0.051332,0.729423,-0.60387,-0.05417,0.79519,-0.291482,0.068972,0.95407,-0.363536,0.07065,0.928861,-0.032594,0.178472,0.983398,0.006287,0.169988,0.985412,0.371288,0.280374,0.885159,0.352947,0.30079,0.885952,-0.44026,-0.664907,-0.60332,-0.083621,-0.560991,-0.823542,-0.13007,-0.468856,-0.873623,-0.47676,-0.59743,-0.644765,-0.755943,-0.641163,-0.131993,-0.62682,-0.673391,-0.391919,-0.657674,-0.619556,-0.428419,-0.78927,-0.589496,-0.171728,-0.811975,-0.546983,-0.203558,-0.679769,-0.574419,-0.455977,0.764611,0.147313,-0.6274,0.722953,0.202918,-0.66039,0.329295,-0.311991,-0.891171,0.58742,-0.069582,-0.806269,0.525224,-0.015107,-0.850826,0.27427,-0.22425,-0.935118,0.235328,-0.196356,-0.951842,0.499985,-0.026521,-0.865596,0.696066,0.274819,0.663259,0.831751,0.256874,0.492111,0.641255,0.366161,0.674276,0.663503,0.332682,0.670095,0.815943,0.326365,0.477157,0.795373,0.36436,0.484359,0.902341,0.325022,0.283029,0.911954,0.292428,0.287698,0.977874,0.20603,-0.035585,0.969878,0.232826,-0.071535,0.497452,-0.201666,-0.843715,0.234779,-0.55269,-0.799615,0.747917,0.652394,0.122227,0.779565,0.618976,0.095523,0.779077,0.623524,0.06476,0.740349,0.671041,0.039491,0.691,0.705283,0.158177,0.61449,0.732414,0.29313,0.521073,0.747581,0.411786,0.448653,0.760155,0.469924,0.756798,0.537065,-0.372539,0.771935,0.592334,-0.230689,0.764763,0.638813,-0.083621,-0.601489,-0.615528,-0.509201,0.505509,0.796625,0.33137,0.709189,0.587512,-0.38963,0.674856,0.549852,-0.492111,0.613483,0.484817,-0.62331,0.519181,0.38551,-0.762749,0.393689,0.245003,-0.885983,0.531144,-0.678487,-0.507462,0.794214,-0.541642,-0.275338,0.288736,-0.700278,-0.652821,0.03296,-0.653981,-0.75576,-0.336467,-0.495865,-0.800531,0.00296,0.613147,0.789941,-0.109195,0.472793,0.874355,-0.165227,0.332621,0.928465,-0.171911,0.168065,0.970641,0.029939,-0.069124,0.997131,0.803766,-0.591083,-0.067568,0.912687,-0.391888,0.115635,0.65923,-0.720206,-0.21601,0.467879,-0.80694,-0.360454,0.197302,-0.839167,-0.50679,-0.035188,-0.926481,-0.374676,-0.227485,-0.945372,-0.233345,-0.435804,-0.899289,-0.035768,-0.689474,-0.652211,0.314951,-0.449049,-0.850581,-0.273507,-0.723075,-0.632893,-0.27662,-0.831629,-0.490524,-0.260231,-0.902371,-0.381848,-0.199683,0.403241,-0.841121,0.360363,0.370525,-0.923002,0.10361,0.324259,-0.937925,-0.122929,0.266457,-0.87167,-0.411267,-0.034242,-0.830195,-0.556383,-0.23307,-0.723594,-0.649648,-0.423475,-0.5627,-0.709922,-0.661397,-0.276955,-0.696982,0.477828,0.800653,0.36137,0.446364,0.804437,0.391949,0.424696,0.798364,0.426832,0.964934,-0.229591,-0.127018,0.813898,-0.47557,-0.333689,0.531175,-0.671194,-0.516984,0.113224,-0.756462,-0.644124,-0.128605,-0.914945,-0.382458,-0.311777,-0.941954,-0.124332,-0.464217,-0.868679,0.172704,-0.589801,-0.610767,0.528245,-0.961516,-0.272561,-0.033998,-0.944578,-0.218238,0.245155,-0.876827,-0.14951,0.456954,-0.739738,-0.036592,0.671865,0.452315,0.387585,0.803217,0.752647,0.329234,0.570177,0.887814,0.421033,-0.185675,0.735862,0.587634,0.336375,0.56502,0.592669,0.573992,0.421155,0.557726,0.715201,0.159673,0.432356,0.887417,-0.006592,-0.653523,-0.756859,0.357189,-0.358287,-0.862545,0.580157,-0.12064,-0.805475,0.764214,0.087405,-0.638997,-0.748192,-0.554674,0.364025,-0.67687,-0.732047,-0.076846,-0.552293,-0.770196,-0.318888,-0.367534,-0.774438,-0.514908,-0.166814,0.208411,0.963683,-0.382488,0.028382,0.92349,-0.598773,-0.204566,0.774316,-0.571764,-0.458541,-0.680288,0.827967,0.160375,0.537339,0.489914,0.226783,0.841731,0.466475,0.277322,0.8399,0.435377,0.393811,0.809503,0.420667,0.448256,0.78869,-0.91998,0.007843,0.391797,-0.991638,-0.07593,-0.104282,-0.927946,-0.105289,-0.357463,-0.797784,-0.140294,-0.586352,-0.056215,0.186407,0.980834,-0.384442,0.136662,0.912961,-0.623859,0.084323,0.77694,-0.764763,0.199377,0.612629,-0.990081,0.088565,0.108798,-0.988525,0.051119,-0.142094,-0.920408,-0.002625,-0.390881,0.734397,0.281228,0.617664,0.684591,0.300821,0.66393,0.677023,0.283853,0.678976,0.730003,0.176214,0.660298,0.213569,0.255959,0.942778,-0.085208,0.259743,0.961882,-0.334239,0.258675,0.906247,-0.885678,-0.405896,0.225349,-0.860073,-0.437208,0.262856,-0.816645,-0.481521,0.318125,-0.719718,-0.222663,-0.657552,-0.747917,-0.111637,-0.654317,-0.73751,-0.067415,-0.671926,-0.717856,0.023988,-0.695761,-0.235908,-0.155248,-0.959258,0.075198,-0.215674,-0.97354,0.376843,-0.223457,-0.898892,0.79754,0.180029,0.575732,0.77749,0.221747,0.588458,0.772607,0.226295,0.593158,0.775567,0.193365,0.600879,0.302774,0.335124,0.892178,-0.016999,0.378582,0.925382,-0.280465,0.389355,0.877346,0.011933,0.742729,0.669424,0.608692,0.792688,0.032929,0.369121,-0.396466,-0.840541,0.717826,-0.690573,0.08826,0.705008,-0.702078,-0.10004,0.59032,-0.783349,-0.194494,0.369182,0.767541,0.523942,0.255837,0.750603,0.609149,0.135258,0.708975,0.692129,-0.013916,-0.999878,0.001953,-0.045747,-0.989074,-0.139927,-0.018006,-0.988861,-0.147526,-0.006439,-0.996948,-0.077731,-0.009186,-0.99942,-0.032502,-0.021882,-0.991974,-0.124363,-0.143773,-0.965636,-0.216468,-0.555284,-0.659322,-0.506821,-0.517869,-0.725578,-0.453078,-0.473647,-0.788171,-0.392926,0.364238,-0.130161,-0.922147], + + "skinIndices" : [6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,-1,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,7,4,4,1,7,4,7,4,4,1,4,1,4,1,4,1,4,1,4,1,4,7,4,7,4,7,4,7,4,5,4,5,4,5,4,5,4,0,4,0,4,5,4,7,4,0,4,5,4,0,4,0,4,0,4,0,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,0,4,0,0,4,0,4,0,4,0,4,4,0,4,0,4,0,4,5,4,5,4,0,4,5,4,0,4,0,0,-1,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,-1,0,4,0,-1,0,4,0,4,0,-1,0,-1,0,8,0,8,0,-1,0,-1,0,-1,0,8,0,8,0,8,0,4,0,4,0,4,0,4,0,4,0,4,4,0,4,0,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,4,0,0,4,0,4,4,0,4,0,4,0,4,7,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,5,0,4,0,5,0,5,0,4,0,4,0,4,0,4,0,4,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,0,4,0,4,0,4,0,4,0,8,0,4,0,8,0,8,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,8,0,4,5,4,5,4,5,-1,5,4,5,-1,5,-1,5,4,5,-1,5,-1,5,4,5,4,5,4,5,4,5,-1,5,-1,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,-1,5,-1,5,-1,5,-1,5,4,5,-1,5,-1,5,-1,5,4,5,-1,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,-1,5,-1,5,-1,5,4,5,4,5,-1,5,-1,5,4,5,-1,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,-1,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,-1,5,-1,5,6,5,-1,5,-1,5,6,5,-1,5,6,5,-1,5,-1,5,6,5,6,5,6,5,6,5,-1,5,-1,5,6,5,6,5,6,5,6,5,-1,5,6,5,6,5,6,5,6,5,6,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,-1,5,6,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,6,5,6,5,6,5,6,5,6,5,6,5,-1,5,6,5,-1,5,-1,5,6,5,6,5,-1,5,6,5,-1,5,-1,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,-1,5,-1,5,-1,5,-1,5,-1,5,6,5,-1,5,6,5,-1,5,-1,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,-1,5,-1,5,-1,5,-1,5,-1,5,6,5,-1,5,6,5,-1,5,-1,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,5,6,5,6,-1,6,-1,6,-1,6,-1,6,5,6,5,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,5,6,5,6,-1,6,-1,6,-1,6,-1,6,5,6,5,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,4,5,4,7,4,7,4,5,4,7,4,5,4,7,4,7,4,7,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,7,4,7,4,7,4,7,4,7,4,5,4,5,4,7,4,7,4,7,4,5,4,5,4,5,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,5,4,5,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,5,4,0,4,0,4,0,4,0,0,4,4,0,4,0,4,1,4,1,4,0,4,1,4,1,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,8,0,8,0,8,0,8,0,8,0,8,0,8,4,0,4,0,4,0,4,0,4,0,4,0,4,5,4,5,4,0,4,0,4,0,0,4,4,0,4,0,4,0,4,5,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,4,0,0,4,4,0,4,0,0,4,0,4,0,4,0,4,0,-1,0,-1,0,-1,0,-1,0,-1,0,-1,0,8,0,-1,0,-1,0,-1,0,-1,0,-1,0,4,0,-1,0,4,0,-1,0,-1,0,8,0,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,4,7,4,7,4,7,4,7,7,4,7,4,4,7,4,0,0,4,4,7,0,4,0,4,7,4,7,4,7,4,7,4,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,5,6,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,5,6,6,5,5,6,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,5,6,6,5,6,5,5,6,6,5,6,5,5,6,5,6,5,6,5,6,5,6,5,6,6,5,6,5,6,5,6,5,6,5,6,5,6,5,5,6,6,5,6,5,5,6,5,6,5,6,6,5,5,6,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,5,6,5,6,5,6,6,5,5,6,6,5,6,5,6,5,6,5,6,5,6,5,6,5,5,6,5,6,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,5,6,5,6,5,6,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,4,5,5,4,5,4,4,5,5,4,4,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,5,4,5,4,4,5,5,4,5,4,4,5,4,5,4,5,4,5,5,4,4,5,5,4,5,4,5,4,5,4,5,4,4,5,4,5,4,5,5,4,4,5,5,4,5,4,5,4,5,4,5,4,5,4,5,4,4,5,4,5,4,5,5,4,4,5,5,4,5,4,5,4,5,4,5,4,5,4,5,4,4,5,4,5,4,5,5,4,4,5,5,4,5,4,5,4,5,4,5,4,5,4,5,4,0,4,0,5,0,5,5,0,0,5,5,4,5,4,5,4,5,4,5,4,5,4,5,4,5,0,0,5,0,5,0,4,0,4,5,4,0,4,4,5,0,4,5,4,5,4,5,4,5,4,4,5,5,4,5,4,4,5,4,0,4,0,4,5,4,5,5,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,8,12,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,12,8,12,12,8,12,8,0,8,0,12,8,12,8,12,8,12,8,12,12,8,12,8,8,12,8,9,8,12,8,9,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,0,8,8,0,8,0,8,9,8,9,8,0,8,9,8,0,8,9,8,9,8,9,8,0,8,0,8,0,8,0,0,8,0,8,8,0,8,0,8,0,8,0,0,8,0,8,8,0,8,0,8,9,8,9,8,0,8,9,8,0,8,9,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,9,8,9,8,0,8,9,8,0,8,0,8,9,8,0,0,8,8,0,8,0,8,0,8,0,8,0,8,0,8,9,8,9,8,9,8,9,8,9,8,0,8,9,8,0,8,0,8,12,8,12,8,12,8,0,0,8,8,0,12,8,12,8,0,12,8,0,8,0,8,0,8,9,8,12,8,9,8,9,8,12,12,8,8,12,12,8,9,8,9,8,9,8,9,8,9,8,9,8,8,9,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,8,9,9,8,9,8,8,9,8,9,8,9,8,9,8,9,8,12,8,13,8,12,13,8,8,12,8,12,8,12,13,9,9,13,13,9,9,13,13,9,13,9,9,8,9,8,9,8,9,8,9,13,9,13,8,9,8,9,8,9,8,9,8,12,8,12,8,12,8,12,12,8,12,8,8,12,8,12,8,12,8,9,8,9,8,9,8,9,8,12,8,9,9,13,9,13,13,9,13,9,13,9,13,9,9,13,9,13,9,8,9,8,13,9,13,9,9,8,9,13,9,8,9,13,9,8,9,13,9,13,8,9,8,9,9,8,9,8,9,8,9,8,8,9,9,8,9,8,9,8,8,9,9,8,8,9,8,9,8,9,8,9,8,9,8,9,9,8,9,8,9,8,9,8,8,9,9,8,9,8,9,8,8,9,9,8,8,9,8,9,8,9,8,9,8,9,9,8,9,8,8,9,9,8,9,8,8,9,8,9,8,9,8,9,8,9,8,9,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,8,9,8,9,8,9,8,9,8,9,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,13,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,8,9,8,9,8,9,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,13,9,8,9,13,9,8,9,8,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,4,7,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,7,4,7,4,4,7,4,7,7,4,4,7,7,4,4,7,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,7,4,7,4,4,7,7,4,7,4,4,7,4,7,7,4,4,7,4,7,4,7,4,7,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,4,7,4,7,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,-1,7,-1,7,4,7,-1,7,-1,7,-1,7,-1,7,4,7,4,7,4,7,4,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,4,7,4,7,-1,7,4,7,-1,7,4,7,-1,7,-1,7,-1,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,-1,7,4,7,-1,7,4,7,4,7,4,7,4,7,4,7,-1,7,-1,7,-1,7,-1,7,4,7,4,7,4,7,4,7,4,7,4,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,4,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,4,7,-1,7,-1,7,4,7,-1,7,4,7,4,7,4,7,4,7,4,7,-1,7,-1,7,4,7,4,7,-1,7,4,7,-1,7,-1,7,4,7,4,7,4,7,4,7,4,7,4,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,4,7,-1,7,-1,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,4,7,-1,7,4,7,4,7,4,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,12,0,12,0,8,0,8,0,8,0,8,0,12,0,12,0,8,0,8,0,8,0,8,0,4,0,4,0,8,0,8,0,8,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,4,0,8,0,4,0,4,0,4,0,4,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,12,0,12,0,12,0,12,0,8,0,12,0,12,0,8,0,8,0,12,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,12,0,12,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,12,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,8,0,12,0,12,0,8,0,8,0,12,0,8,10,9,10,9,10,9,10,9,10,-1,10,-1,10,9,10,9,10,-1,10,9,10,9,10,9,10,9,10,-1,10,-1,10,9,10,9,9,10,9,10,10,9,9,10,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,-1,10,-1,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,-1,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,-1,10,-1,10,9,10,9,10,9,10,9,10,-1,10,-1,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,-1,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,10,11,10,11,10,11,10,11,11,10,10,11,10,11,11,10,10,11,11,10,10,11,10,11,11,10,11,10,11,10,11,10,10,11,10,11,10,11,11,10,11,10,11,10,10,11,10,11,11,10,11,10,10,11,11,10,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,11,10,11,10,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,11,10,11,10,11,10,11,10,10,11,10,11,10,11,10,11,10,11,11,10,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,11,10,11,10,11,10,11,10,10,11,11,10,11,10,11,10,10,11,11,10,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,11,11,10,11,10,10,11,11,10,10,11,11,10,11,10,11,10,10,11,10,11,10,11,10,11,10,11,10,11,10,11,11,10,10,11,11,10,10,11,10,11,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,10,11,10,11,10,11,10,11,11,10,10,11,10,11,11,10,11,10,10,11,11,10,11,10,10,11,10,11,10,11,10,11,10,11,10,11,10,11,11,10,11,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,10,9,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,10,9,10,9,9,10,10,9,10,9,9,10,9,10,9,10,9,10,9,10,9,10,10,9,9,10,10,9,9,10,9,10,10,9,10,9,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,10,9,9,10,9,10,9,10,9,10,10,9,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,9,10,9,10,9,10,9,10,9,10,10,9,9,10,10,9,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,9,10,10,9,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,9,10,-1,10,9,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,9,10,9,10,9,10,-1,10,9,10,9,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,9,10,-1,10,9,10,-1,10,-1,10,-1,10,9,10,9,10,9,10,9,10,-1,10,9,10,-1,10,-1,10,-1,10,-1,10,-1,10,9,10,9,10,9,10,9,10,9,10,9,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,10,11,-1,11,-1,11,-1,11,-1,11,10,11,10,11,-1,11,-1,11,-1,11,-1,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,10,11,10,11,10,11,10,11,-1,11,10,11,-1,11,-1,11,-1,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,-1,11,10,11,10,11,10,11,10,11,-1,11,10,11,10,11,10,11,10,11,10,11,-1,11,-1,11,-1,11,-1,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,10,11,10,11,10,11,10,11,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,11,10,-1,10,11,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,11,10,11,10,11,10,11,10,-1,10,-1,10,-1,10,11,10,-1,10,-1,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,11,10,11,10,-1,10,-1,10,-1,10,11,10,-1,10,11,10,11,10,11,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,11,10,11,10,-1,10,-1,10,11,10,-1,10,-1,10,11,10,11,10,11,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,11,10,11,10,11,10,11,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,11,10,-1,10,11,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,11,10,11,10,-1,10,-1,10,-1,10,11,10,11,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,11,10,11,10,-1,10,-1,10,-1,10,11,10,11,10,11,10,11,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,10,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,8,9,-1,9,-1,9,8,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,10,9,10,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,10,9,10,9,10,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,10,9,10,9,10,9,-1,9,-1,9,10,9,10,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,10,9,10,9,10,9,10,9,10,9,10,9,-1,9,-1,9,10,9,10,9,10,9,10,9,-1,9,-1,9,10,9,10,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,10,9,10,9,10,9,10,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,8,9,-1,9,-1,9,-1,9,8,9,10,9,10,9,10,9,10,9,-1,9,-1,9,10,9,10,9,-1,9,10,9,10,9,-1,9,10,9,-1,9,-1,9,10,9,10,9,10,9,10,9,10,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,10,9,10,9,10,9,10,9,10,9,10,9,-1,9,-1,9,10,9,10,9,-1,9,10,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,10,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,10,9,10,9,10,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,-1,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,-1,9,-1,9,-1,9,8,9,8,9,-1,9,-1,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,-1,9,-1,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,9,8,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,-1,11,10,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,10,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,10,11,-1,11,10,11,10,11,10,11,-1,11,-1,11,-1,11,10,11,-1,11,-1,11,10,11,10,11,10,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,10,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,4,5,4,5,-1,5,4,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,4,5,4,5,4,5,-1,5,4,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,4,5,4,5,4,5,-1,5,4,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,-1,5,4,5,-1,5,-1,5,-1,5,-1,6,-1,6,5,6,5,6,-1,6,5,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,5,6,5,6,-1,6,-1,6,-1,6,-1,6,5,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,-1,6,5,6,-1,6,-1,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,5,6,-1,6,5,6,5,6,5,6,5,6,5,6,5,6,-1,6,5,6,-1,6,-1,6,-1,6,-1,6,-1,6,5,6,5,6,-1,6,5,6,5,6,-1,6,-1,6,-1,6,-1,6,-1,6,5,8,9,8,9,8,9,8,9,9,8,9,8,8,9,8,12,12,13,12,8,8,12,12,8,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,11,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,6,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,-1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,7,1,7,1,7,1,7,7,1,1,7,1,7,1,7,1,7,1,4,1,7,7,1,1,4,1,4,1,4,1,4,1,7,1,7,1,4,1,7,1,2,1,0,1,2,1,2,1,0,1,0,1,7,1,2,1,2,1,0,1,0,1,0,1,0,1,0,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,0,0,1,0,1,0,1,0,1,0,1,1,0,0,1,1,2,1,0,1,0,1,0,1,2,1,0,1,0,0,-1,0,1,0,1,0,-1,0,1,0,1,0,1,0,1,0,-1,0,-1,0,1,0,-1,0,-1,0,12,0,12,0,-1,0,12,0,12,0,12,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,0,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,0,1,0,1,0,1,0,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,1,0,2,0,2,0,2,0,2,0,2,0,2,0,2,0,1,0,1,0,2,0,1,0,1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,0,1,0,1,0,12,0,12,0,1,0,12,0,12,0,12,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,12,0,12,0,12,2,1,2,1,2,-1,2,1,2,-1,2,-1,2,-1,2,1,2,-1,2,1,2,1,2,1,2,1,2,-1,2,-1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,-1,2,-1,2,1,2,-1,2,-1,2,-1,2,1,2,-1,2,1,2,-1,2,-1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,-1,2,1,2,1,2,-1,2,-1,2,1,2,1,2,-1,2,1,2,-1,2,-1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,-1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,3,2,-1,2,3,2,-1,2,-1,2,-1,2,3,2,3,2,3,2,3,2,-1,2,3,2,-1,2,-1,2,3,2,3,2,-1,2,-1,2,3,2,-1,2,3,2,3,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,3,2,-1,2,3,2,3,2,3,2,-1,2,3,2,-1,2,-1,2,3,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,3,2,3,2,3,2,3,2,3,2,3,2,-1,2,-1,2,-1,2,-1,2,3,2,-1,2,3,2,-1,2,-1,2,-1,2,3,2,3,2,3,2,3,2,-1,2,3,2,3,2,3,2,3,2,3,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,3,2,-1,2,-1,2,-1,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,-1,2,-1,2,-1,2,-1,2,3,2,-1,2,3,2,-1,2,-1,2,-1,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,2,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,2,3,2,3,-1,3,2,3,-1,3,2,3,2,3,2,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,2,3,2,3,2,3,2,3,-1,3,-1,3,-1,3,-1,3,2,3,2,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,2,3,2,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,1,2,1,2,1,7,1,7,1,2,1,7,1,7,1,7,1,7,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,2,1,7,1,7,1,7,1,7,1,7,1,7,1,2,1,2,1,7,1,7,1,2,1,2,1,2,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,2,1,2,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,4,0,4,0,1,0,4,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,2,1,0,1,0,1,0,1,0,1,0,0,1,1,4,1,4,1,4,1,4,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,12,0,12,0,12,0,12,0,12,0,12,0,12,1,0,1,0,1,0,1,0,1,0,1,0,1,2,1,2,1,0,1,0,1,0,0,1,1,0,1,0,1,0,1,2,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,1,0,0,1,1,0,0,1,0,1,0,1,0,1,0,-1,0,-1,0,-1,0,-1,0,12,0,12,0,-1,0,-1,0,-1,0,-1,0,-1,0,1,0,-1,0,12,0,12,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,1,7,1,7,1,7,1,7,7,1,7,1,1,0,1,7,1,0,0,1,7,1,7,1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,2,3,2,3,2,3,3,2,2,3,3,2,2,3,3,2,3,2,3,2,3,2,3,2,2,3,2,3,3,2,3,2,2,3,3,2,2,3,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,2,3,2,3,2,3,2,3,3,2,3,2,2,3,2,3,2,3,2,3,2,3,2,3,2,3,3,2,2,3,3,2,2,3,2,3,2,3,3,2,3,2,3,2,2,3,2,3,2,3,2,3,2,3,2,3,3,2,3,2,3,2,3,2,3,2,2,3,3,2,3,2,3,2,3,2,2,3,2,3,3,2,2,3,3,2,2,3,3,2,3,2,3,2,3,2,3,2,2,3,2,3,3,2,3,2,2,3,3,2,2,3,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,2,3,2,3,3,2,3,2,2,3,3,2,3,2,3,2,3,2,3,2,3,2,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,1,2,1,2,2,1,2,1,1,2,2,1,1,2,1,2,1,2,1,2,1,2,1,2,1,2,2,1,2,1,1,2,1,2,2,1,2,1,1,2,1,2,2,1,1,2,2,1,1,2,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,2,1,1,2,2,1,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,2,1,1,2,2,1,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,2,2,1,0,1,2,1,2,1,2,1,2,1,2,0,0,2,0,2,2,1,2,0,0,2,2,0,0,2,2,1,2,1,2,1,2,1,2,1,0,2,2,0,2,0,0,2,0,2,2,0,0,2,2,0,0,1,0,1,2,1,2,1,2,1,2,1,1,2,1,2,2,1,2,1,1,0,0,1,1,2,1,0,2,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,12,8,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,8,12,8,12,0,12,8,12,8,12,8,12,8,12,13,12,13,12,13,12,13,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,13,12,0,12,13,12,13,12,0,12,13,12,13,12,13,12,13,12,13,12,0,12,0,12,0,12,0,0,12,0,12,12,0,12,0,12,0,12,0,0,12,0,12,12,0,12,0,12,13,12,13,12,13,12,13,12,0,12,13,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,13,12,13,12,13,12,13,12,13,12,13,12,0,12,13,12,13,12,0,0,12,12,13,12,13,12,0,12,0,12,0,12,0,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,0,12,0,12,0,12,0,12,0,12,8,12,0,12,0,12,13,12,0,12,0,12,13,12,13,12,13,12,13,12,8,12,8,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,12,13,12,13,13,12,13,8,13,12,13,12,12,8,12,13,13,9,13,9,13,9,13,9,13,12,13,9,13,12,13,9,13,9,13,9,13,12,13,12,13,12,13,12,12,8,12,8,12,8,12,8,12,8,12,8,12,13,12,13,12,13,13,12,12,13,12,13,13,9,13,9,13,9,13,9,13,12,13,9,13,12,13,12,13,9,13,12,13,9,13,12,13,12,13,9,13,9,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,12,13,12,13,12,13,12,13,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,12,13,12,13,12,13,12,13,13,12,13,12,13,12,13,12,13,12,13,12,13,12,12,13,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,9,13,9,13,9,13,9,13,12,13,12,13,9,13,9,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,9,13,9,13,12,13,12,13,12,13,12,13,9,13,9,13,9,13,9,13,9,13,12,0,1,0,1,0,1,0,1,0,2,0,1,0,2,0,2,0,2,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,1,7,1,7,1,7,1,7,4,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,1,7,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,1,7,1,7,1,7,1,7,1,7,1,7,7,1,1,7,7,1,7,1,1,7,7,1,1,7,7,1,7,1,1,7,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,7,1,1,7,7,1,7,1,7,1,1,7,7,1,1,7,1,7,7,1,1,7,1,7,1,7,1,7,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,1,7,1,7,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,-1,7,1,7,1,7,1,7,-1,7,-1,7,-1,7,-1,7,1,7,1,7,-1,7,1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,1,7,1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,-1,7,1,7,-1,7,1,7,-1,7,1,7,1,7,-1,7,-1,7,-1,7,-1,7,-1,7,1,7,-1,7,1,7,-1,7,1,7,1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,1,7,-1,7,-1,7,1,7,-1,7,1,7,1,7,1,7,-1,7,1,7,-1,7,1,7,1,7,-1,7,-1,7,-1,7,-1,7,1,7,1,7,1,7,1,7,1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,7,-1,7,-1,7,1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,1,0,12,0,12,0,1,0,-1,0,12,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,12,0,12,0,1,0,12,0,1,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,12,0,0,12,0,12,12,0,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,0,12,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,-1,14,-1,14,13,14,13,13,14,13,14,14,13,13,14,14,13,14,13,13,14,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,-1,14,-1,14,13,14,13,14,13,14,13,14,-1,14,-1,14,13,14,13,14,13,13,14,14,13,13,14,14,13,13,14,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,-1,14,-1,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,13,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,15,14,15,14,15,14,15,14,14,15,14,15,14,15,14,15,15,14,14,15,14,15,14,15,14,15,14,15,14,15,15,14,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,15,14,15,14,14,15,14,15,15,14,14,15,14,15,15,14,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,15,15,14,15,14,14,15,14,15,15,14,15,14,15,14,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,15,14,15,14,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,15,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,14,13,14,13,13,14,13,14,14,13,14,13,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,14,13,14,13,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,14,13,13,14,13,14,13,14,13,14,13,14,13,14,13,14,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,13,14,13,14,13,14,13,14,14,13,13,14,13,14,14,13,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,13,14,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,13,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,13,14,13,14,-1,14,13,14,-1,14,13,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,13,14,13,14,-1,14,13,14,13,14,13,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,13,14,13,14,13,14,-1,14,13,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,13,14,13,14,13,14,13,14,13,14,13,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,14,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,14,15,14,15,-1,15,-1,15,-1,15,-1,15,14,15,14,15,-1,15,-1,15,-1,15,-1,14,15,14,15,14,15,15,14,14,15,15,14,14,15,14,15,14,15,14,15,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,-1,15,14,15,-1,15,-1,15,14,15,-1,15,14,15,14,15,14,15,-1,15,14,15,-1,15,14,15,-1,15,-1,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,-1,15,14,15,14,15,-1,15,-1,15,14,15,14,15,14,15,14,15,-1,15,14,15,-1,15,-1,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,14,15,14,15,15,14,15,14,15,14,15,14,14,15,14,15,14,15,14,15,14,15,14,15,14,-1,14,-1,14,-1,14,15,14,-1,14,15,14,-1,14,15,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,15,14,15,14,15,14,-1,14,-1,14,15,14,-1,14,15,14,-1,14,15,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,15,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,15,14,15,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,-1,14,15,14,15,14,15,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,15,14,-1,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,15,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,15,14,15,14,15,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,14,13,14,13,14,13,14,13,14,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,14,13,14,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,14,13,14,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,14,13,14,13,14,13,14,13,-1,13,-1,13,-1,13,-1,13,14,13,-1,13,14,13,14,13,-1,13,-1,13,14,13,14,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,14,13,14,13,14,13,14,13,14,13,14,13,-1,13,-1,13,14,13,14,13,14,13,14,13,-1,13,-1,13,14,13,14,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,14,13,14,13,14,13,14,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,14,13,-1,13,-1,13,14,13,-1,13,-1,13,14,13,14,13,-1,13,14,13,-1,13,-1,13,-1,13,-1,13,-1,13,14,13,14,13,14,13,14,13,14,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,14,13,14,13,14,13,14,13,14,13,14,13,-1,13,-1,13,14,13,14,13,-1,13,14,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,14,13,14,13,14,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,-1,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,-1,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,-1,13,12,13,-1,13,-1,13,-1,13,-1,13,12,13,12,13,-1,13,-1,13,12,13,12,13,12,13,12,13,12,13,12,13,-1,13,-1,13,12,13,12,13,-1,13,-1,13,12,13,12,13,12,13,12,13,12,13,12,13,-1,13,-1,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,12,13,-1,13,-1,13,12,13,12,13,-1,13,-1,13,12,13,-1,13,12,13,12,13,12,13,-1,13,12,13,-1,13,9,13,-1,13,-1,13,12,13,12,13,-1,13,-1,13,12,13,12,13,12,13,12,13,-1,13,-1,13,12,13,12,13,-1,13,12,13,12,13,-1,13,-1,13,-1,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,14,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,14,15,14,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,14,15,-1,15,-1,15,14,15,14,15,14,15,14,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,14,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,7,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,1,2,-1,2,-1,2,1,2,1,2,1,2,-1,2,-1,2,1,2,1,2,-1,2,-1,2,1,2,1,2,-1,2,1,2,-1,2,1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,1,2,1,2,-1,2,1,2,-1,2,1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,2,-1,3,-1,3,-1,3,2,3,2,3,-1,3,2,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,2,3,2,3,-1,3,-1,3,2,3,2,3,-1,3,2,3,-1,3,-1,3,-1,3,-1,3,-1,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,-1,3,2,3,2,3,2,3,-1,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,2,3,-1,3,-1,3,-1,3,2,3,-1,3,2,3,-1,3,-1,3,2,3,2,3,-1,3,-1,3,2,3,-1,3,2,13,12,13,12,12,13,12,13,13,12,13,12,12,13,13,12,12,13,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,15,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1,3,-1], + "skinWeights" : [0.99464,0,0.993592,0,0.99575,0,0.996569,0,0.99731,0,0.998388,0,0.998253,0,0.997229,0,0.996009,0,0.996994,0,0.996489,0,0.995535,0,0.994953,0,0.995909,0,0.995731,0,0.994769,0,0.994574,0,0.995622,0,0.988221,0,0.991557,0,0.990863,0,0.987483,0,0.967502,0,0.968796,0,0.966546,0,0.965088,0,0.973693,0,0.97416,0,0.972024,0,0.971578,0,0.978791,0,0.980995,0,0.98102,0,0.978843,0,0.977135,0,0.979807,0,0.979344,0,0.976486,0,0.974589,0,0.977812,0,0.977021,0,0.973767,0,0.946391,0,0.946406,0,0.946408,0,0.946395,0,0.979928,0,0.977867,0,0.978845,0,0.981374,0,0.971731,0,0.971726,0,0.971751,0,0.971752,0,0.982686,0,0.983857,0,0.980924,0,0.980052,0,0.989005,0,0.988853,0,0.992039,0,0.992097,0,0.989134,0,0.989196,0,0.991934,0,0.991811,0,0.988399,0,0.988843,0,0.991325,0,0.990855,0,0.968892,0,0.968886,0,0.968883,0,0.968889,0,0.971533,0,0.972073,0,0.969866,0,0.969672,0,0.98034,0,0.977057,0,0.976115,0,0.979482,0,0.978391,0,0.975038,0,0.96414,0,0.964044,0,0.961227,0,0.961718,0,0.94494,0,0.944989,0,0.945051,0,0.94503,0,0.957916,0,0.957889,0,0.957899,0,0.957928,0,0.959809,0,0.959049,0,0.962628,0,0.963081,0,0.963067,0,0.96547,0,0.961511,0,0.962629,0,0.96538,0,0.963811,0,0.958315,0,0.958616,0,0.960274,0,0.95894,0,0.968117,0,0.96818,0,0.968653,0,0.968626,0,0.965567,0,0.966072,0,0.964759,0,0.967234,0,0.966382,0,0.968172,0,0.968267,0,0.968499,0,0.968812,0,0.95943,0,0.959704,0,0.962816,0,0.962427,0,0.958333,0,0.958927,0,0.959595,0,0.95908,0,0.958527,0,0.96127,0,0.960584,0,0.960904,0,0.963983,0,0.963643,0,0.970226,0,0.970162,0,0.972078,0,0.969667,0,0.970451,0,0.972233,0,0.97123,0,0.967595,0,0.967793,0,0.968001,0,0.969136,0,0.968871,0,0.970525,0,0.952866,0,0.954602,0,0.954865,0,0.953137,0,0.95301,0,0.955127,0,0.955255,0,0.952993,0,0.952536,0,0.954627,0,0.951815,0,0.951641,0,0.953551,0,0.953607,0,0.968183,0,0.968249,0,0.968129,0,0.968035,0,0.968076,0,0.967969,0,0.967553,0,0.967656,0,0.968462,0,0.96823,0,0.968256,0,0.968444,0,0.968332,0,0.968198,0,0.947642,0,0.947741,0,0.946434,0,0.946443,0,0.945704,0,0.946504,0,0.946559,0,0.945711,0,0.94564,0,0.946523,0,0.946548,0,0.945692,0,0.947578,0,0.947548,0,0.946476,0,0.946487,0,0.964106,0,0.96421,0,0.965358,0,0.965237,0,0.963267,0,0.964435,0,0.964501,0,0.963244,0,0.963451,0,0.964624,0,0.964111,0,0.96529,0,0.947271,0,0.948199,0,0.94828,0,0.947374,0,0.947372,0,0.948098,0,0.948036,0,0.947385,0,0.946558,0,0.946553,0,0.94647,0,0.94647,0,0.946459,0,0.946465,0,0.96887,0,0.968869,0,0.968898,0,0.968907,0,0.968904,0,0.9689,0,0.968921,0,0.968915,0,0.968866,0,0.9689,0,0.968811,0,0.968729,0,0.968677,0,0.96877,0,0.958275,0,0.958459,0,0.958241,0,0.958102,0,0.959285,0,0.960048,0,0.959927,0,0.959226,0,0.958238,0,0.958198,0,0.958056,0,0.958083,0,0.959008,0,0.960626,0,0.960753,0,0.959098,0,0.996598,0,0.997273,0,0.994764,0,0.998767,0,0.999073,0,0.998321,0,0.997848,0,0.999097,0,0.998958,0,0.999208,0,0.999406,0,0.99923,0,0.998976,0,0.996516,0,0.996387,0,0.995163,0,0.995349,0,0.995644,0,0.995264,0,0.993198,0,0.993884,0,0.994254,0,0.993821,0,0.97272,0,0.972251,0,0.972429,0,0.97291,0,0.976015,0,0.975365,0,0.976888,0,0.978019,0,0.977654,0,0.977979,0,0.980435,0,0.975903,0,0.976555,0,0.971871,0,0.972014,0,0.971714,0,0.971768,0,0.972254,0,0.972211,0,0.971776,0,0.971725,0,0.972305,0,0.972311,0,0.972028,0,0.972014,0,0.972464,0,0.97253,0,0.989557,0,0.987991,0,0.992339,0,0.973469,0,0.971176,0,0.971165,0,0.973117,0,0.972837,0,0.971247,0,0.972538,0,0.972495,0,0.971825,0,0.971868,0,0.965169,0,0.964928,0,0.963045,0,0.965509,0,0.966152,0,0.963081,0,0.964673,0,0.963875,0,0.961404,0,0.978018,0,0.978961,0,0.976363,0,0.981402,0,0.978967,0,0.981873,0,0.993275,0,0.993786,0,0.994864,0,0.94511,0,0.945169,0,0.947743,0,0.946526,0,0.947772,0,0.947747,0,0.946426,0,0.945156,0,0.945153,0,0.945006,0,0.944999,0,0.732714,0.152896,0.759774,0.137198,0.811768,0.100005,0.790401,0.112306,0.84993,0.0725656,0.832398,0.0824541,0.78801,0.117382,0.835101,0.0840746,0.869102,0.0601198,0.771638,0.120973,0.705985,0.164857,0.757256,0.124927,0.684409,0.17046,0.818011,0.0892703,0.807172,0.0927146,0.617189,0.224649,0.654695,0.208594,0.501129,0.305496,0.554752,0.283276,0.582211,0.232903,0.426931,0.329213,0.692094,0.186894,0.72914,0.160173,0.606641,0.252544,0.655393,0.216222,0.895213,0.0510177,0.884993,0.0611817,0.900879,0.0477656,0.908842,0.0323655,0.914773,0.0250759,0.920762,0.0137677,0.869653,0.0737341,0.889136,0.0583062,0.906374,0.0382789,0.913622,0.0193753,0.901136,0.0349222,0.915734,0.0069456,0.902334,0.0208761,0.92462,0.00380648,0.926381,0,0.882395,0.0532611,0.875823,0.0644281,0.862555,0.0651838,0.85162,0.0812737,0.872927,0.049443,0.862844,0.0545155,0.861323,0.0792226,0.840704,0.0965809,0.831549,0.102058,0.804803,0.125288,0.49123,0.234818,0.573338,0.214077,0.527354,0.277923,0.452887,0.299294,0.471284,0.349658,0.404018,0.372779,0.632005,0.189831,0.588407,0.248397,0.534801,0.313961,0.380546,0.308859,0.41077,0.242529,0.320859,0.313598,0.342487,0.297446,0.390966,0.33407,0.396599,0.278968,0.434854,0.253148,0.520019,0.180366,0.447518,0.258413,0.537275,0.181557,0.37092,0.313007,0.382284,0.31985,0.607759,0.15905,0.663333,0.139463,0.626428,0.11617,0.679223,0.100337,0.692616,0.143022,0.716528,0.129029,0.706761,0.0934644,0.687288,0.104867,0.67835,0.0938471,0.659628,0.127661,0.727934,0.111738,0.711578,0.0861032,0.653676,0.142644,0.657326,0.156135,0.601244,0.19956,0.61035,0.16574,0.621594,0.176554,0.56356,0.244834,0.636922,0.211554,0.672598,0.200472,0.5836,0.296697,0.610098,0.296941,0.593593,0.220992,0.548331,0.300009,0.700446,0.185958,0.722215,0.161017,0.639831,0.282473,0.676059,0.250816,0.472127,0.324895,0.443309,0.37988,0.501635,0.338225,0.433673,0.381887,0.441224,0.394953,0.561257,0.292074,0.401687,0.376151,0.477887,0.27691,0.407548,0.315327,0.463111,0.276433,0.540923,0.219845,0.547275,0.235519,0.508532,0.295355,0.52963,0.280295,0.48612,0.335094,0.917909,0,0.9129,0.00480804,0.902374,0.0225813,0.907431,0.0141379,0.884116,0.0474655,0.890771,0.0342528,0.90377,0.014168,0.891073,0.0375715,0.909502,0.00891418,0.920289,0,0.91048,0.00377288,0.92137,0,0.89293,0.0273876,0.893886,0.0211733,0.925266,0,0.921707,0,0.921593,0.00154975,0.917872,0.00930786,0.92674,0,0.922905,0,0.916448,0,0.90819,0.00810673,0.912409,0.0182098,0.904358,0.0299371,0.542938,0.284276,0.457765,0.390501,0.54519,0.329805,0.616947,0.242185,0.626416,0.265773,0.68288,0.19906,0.527089,0.339275,0.446803,0.437241,0.548023,0.34715,0.661684,0.178578,0.598964,0.207555,0.686803,0.130863,0.633211,0.151608,0.71796,0.149115,0.736401,0.110588,0.531959,0.235608,0.456723,0.326379,0.454817,0.265983,0.367019,0.366179,0.583847,0.169835,0.537076,0.186905,0.464367,0.339842,0.603795,0.22532,0.504246,0.249842,0.63577,0.158616,0.784992,0.127738,0.739028,0.160049,0.695434,0.209862,0.751,0.165226,0.635443,0.269283,0.70365,0.20986,0.795436,0.130123,0.821861,0.102014,0.759308,0.162653,0.837087,0.0804279,0.805019,0.0992778,0.84188,0.0642087,0.813626,0.0770389,0.765656,0.122132,0.778509,0.0925885,0.875999,0.0789502,0.872578,0.0827763,0.84065,0.114197,0.838193,0.114331,0.799861,0.15207,0.787873,0.159776,0.870445,0.0836045,0.845467,0.109729,0.812031,0.142264,0.839455,0.108236,0.877545,0.0749556,0.840623,0.100199,0.878106,0.0700948,0.786285,0.154395,0.787334,0.142808,0.902089,0.052311,0.899327,0.0550501,0.915658,0.0242531,0.911131,0.0286371,0.904116,0.0480105,0.919168,0.0191812,0.894012,0.0589432,0.888523,0.0619589,0.905001,0.034316,0.897879,0.0399928,0.84428,0.0977841,0.846261,0.0958551,0.815621,0.130102,0.81598,0.13014,0.77393,0.1717,0.778409,0.168255,0.846076,0.0969033,0.81481,0.130725,0.770552,0.173905,0.818657,0.128233,0.843586,0.099393,0.824372,0.123697,0.847952,0.0962896,0.784741,0.163109,0.791402,0.15782,0.859642,0.0754184,0.86112,0.0725181,0.865525,0.0567828,0.866301,0.0553602,0.862746,0.0744513,0.868857,0.0568577,0.863059,0.0715351,0.863881,0.0733359,0.868276,0.0562521,0.870978,0.0579781,0.70527,0.141149,0.7443,0.121576,0.73127,0.127686,0.687383,0.149283,0.741025,0.122097,0.67439,0.154797,0.784557,0.0996969,0.777693,0.10319,0.791183,0.0954899,0.622535,0.179931,0.636926,0.174839,0.544238,0.223337,0.514119,0.233866,0.588979,0.198316,0.498329,0.252221,0.578104,0.218772,0.681056,0.162377,0.461533,0.276897,0.756151,0.118929,0.802737,0.0913884,0.978629,0,0.979905,0,0.97951,0,0.978207,0,0.978913,0,0.977588,0,0.980971,0,0.980609,0,0.980031,0,0.976698,0,0.977203,0,0.975008,0,0.975597,0,0.976014,0,0.974234,0,0.977602,0,0.978977,0,0.977969,0,0.979252,0,0.976032,0,0.976494,0,0.980157,0,0.981143,0,0.980318,0,0.98121,0,0.8715,0.0502716,0.881928,0.0273332,0.889341,0.0228164,0.879698,0.0451636,0.897315,0.0164378,0.888416,0.0381423,0.88419,0.0497741,0.891226,0.0439582,0.86327,0.0610888,0.85199,0.0652711,0.836119,0.0801951,0.819246,0.0861626,0.873084,0.0575306,0.848187,0.0762338,0.837125,0.0705248,0.86034,0.0538202,0.827212,0.0740982,0.851454,0.0569765,0.801931,0.0918684,0.872874,0.0320635,0.875885,0.0538059,0.865233,0.0367838,0.923117,0.0605204,0.939276,0.0500428,0.970073,0,0.961081,0.00916503,0.985754,0,0.980474,0,0.95201,0.0326873,0.976843,0,0.989374,0,0.950314,0.023017,0.903533,0.0726514,0.937263,0.0395365,0.881384,0.0857472,0.975133,0,0.968738,0,0.828837,0.132542,0.859941,0.114266,0.717682,0.225029,0.760013,0.202865,0.795871,0.150359,0.671923,0.246353,0.88732,0.0966159,0.909312,0.0811274,0.799232,0.178516,0.833252,0.15403,0.96516,0.0139821,0.965321,0.0143288,0.982057,0,0.982677,0,0.991396,0,0.991966,0,0.962535,0.0191572,0.979919,0,0.989919,0,0.981014,0,0.960846,0.0200555,0.991365,0,0.925157,0.0691216,0.933365,0.0627452,0.859111,0.133453,0.872139,0.122679,0.934499,0.0620408,0.931375,0.0646609,0.875357,0.119851,0.874583,0.119917,0.953338,0.0326845,0.947336,0.0405384,0.967743,0.00344041,0.972617,0,0.980473,0,0.98445,0,0.939945,0.0496395,0.961537,0.0116105,0.975788,0,0.976753,0,0.958384,0.0255808,0.987627,0,0.926965,0.0678795,0.921837,0.0711525,0.871521,0.121515,0.867483,0.123315,0.915673,0.0747019,0.907591,0.0791657,0.863714,0.123882,0.85774,0.125289,0.91597,0.065023,0.898908,0.0758551,0.930743,0.0509215,0.942414,0.037212,0.955936,0.0128063,0.962685,0.00483921,0.878603,0.087796,0.917002,0.058832,0.947987,0.0217722,0.953236,0.0226655,0.929609,0.0562337,0.969747,0,0.895759,0.0859073,0.879248,0.0956677,0.845064,0.131582,0.826395,0.141352,0.858452,0.108163,0.832315,0.123269,0.801117,0.155073,0.769461,0.17221,0.818756,0.120574,0.782346,0.139623,0.837905,0.104201,0.871685,0.0849977,0.895518,0.0673068,0.917983,0.0539053,0.752926,0.153955,0.806694,0.120992,0.873567,0.079765,0.898375,0.0694399,0.851697,0.102626,0.937256,0.0344348,0.798846,0.141136,0.76263,0.158932,0.730681,0.191595,0.694011,0.207225,0.730646,0.17308,0.706395,0.182592,0.662508,0.218339,0.645179,0.220016,0.719273,0.169725,0.707294,0.174579,0.779871,0.13233,0.78027,0.133446,0.865124,0.0818872,0.856817,0.0875684,0.671341,0.194515,0.770033,0.137684,0.871197,0.078142,0.787302,0.130907,0.732037,0.164367,0.857642,0.0881208,0.686009,0.192482,0.672737,0.19727,0.628449,0.228034,0.620772,0.228287,0.65515,0.204561,0.613835,0.227053,0.601254,0.235331,0.564856,0.254364,0.665771,0.202961,0.706931,0.182201,0.807861,0.121025,0.775386,0.138054,0.894321,0.0673425,0.878253,0.0756959,0.750142,0.159012,0.844185,0.100813,0.915202,0.0556158,0.759997,0.14488,0.650176,0.208493,0.872148,0.0782484,0.593221,0.240784,0.598384,0.241008,0.554243,0.262483,0.55592,0.264616,0.624966,0.23022,0.659604,0.215046,0.563852,0.265073,0.567227,0.272055,0.834994,0.111901,0.859621,0.0980956,0.920962,0.054935,0.901112,0.0671147,0.957148,0.00908784,0.942351,0.0279284,0.877004,0.0816552,0.796334,0.133682,0.929518,0.0439704,0.695118,0.198775,0.72996,0.182156,0.572576,0.279213,0.598686,0.271265,0.762962,0.166752,0.628777,0.263456,0.944908,0.0547683,0.88351,0.116299,0.890103,0.109658,0.944878,0.0546993,0.89613,0.103582,0.946177,0.0532898,0.728519,0.271364,0.767074,0.232785,0.797241,0.202595,0.971936,0.00455709,0.972722,0.00336778,0.985254,0,0.986164,0,0.972532,0.002931,0.985334,0,0.97519,0,0.946431,0.0533235,0.979639,0,0.952996,0.0436641,0.988104,0,0.990745,0,0.879487,0.120368,0.713498,0.286409,0.885876,0.114018,0.712153,0.287774,0.97777,0,0.98267,0,0.958447,0.0330365,0.936806,0.0631488,0.879048,0.120928,0.788156,0.211808,0.983135,0,0.965302,0.0193245,0.913709,0.0862728,0.905239,0.0946903,0.964538,0.0207012,0.736854,0.263092,0.985417,0,0.990064,0,0.993454,0,0.995217,0,0.991704,0,0.991533,0,0.995773,0,0.995575,0,0.974743,0.000312239,0.964381,0.0208966,0.921671,0.0782765,0.953229,0.0434533,0.817061,0.1829,0.860649,0.139324,0.957442,0.0346982,0.906718,0.0932278,0.80401,0.195928,0.963543,0.0228354,0.980949,0,0.903403,0.0965757,0.990039,0,0.986366,0,0.994482,0,0.992146,0,0.981253,0,0.977925,0,0.989258,0,0.987183,0,0.943443,0.0565904,0.894377,0.10563,0.897473,0.102495,0.950705,0.0482974,0.809656,0.190266,0.810691,0.189239,0.975487,0,0.972495,0.00324062,0.985561,0,0.983839,0,0.973253,0.00121872,0.946331,0.053603,0.975737,0,0.951242,0.047114,0.983481,0,0.984643,0,0.903009,0.0970139,0.829381,0.170543,0.911519,0.0885092,0.848244,0.151689,0.961094,0.02703,0.923658,0.0763389,0.919603,0.0804062,0.956455,0.0365133,0.850153,0.149769,0.85472,0.145213,0.978058,0,0.980114,0,0.985012,0,0.985034,0,0.981412,0,0.963774,0.0213978,0.98115,0,0.963358,0.0212556,0.985374,0,0.984695,0,0.920992,0.0790205,0.835549,0.164341,0.920418,0.0793605,0.830348,0.169481,0.964412,0.0186701,0.91908,0.0804673,0.919904,0.0797088,0.964281,0.018936,0.828509,0.171283,0.824996,0.174812,0.981568,0,0.982005,0,0.984456,0,0.984791,0,0.982247,0,0.962529,0.0226087,0.982133,0,0.960556,0.0267311,0.985395,0,0.986028,0,0.915727,0.0838396,0.826716,0.173078,0.911154,0.0884429,0.816838,0.182967,0.960558,0.0268102,0.960158,0.0276145,0.906594,0.0929951,0.906875,0.0927268,0.786519,0.213297,0.793529,0.206285,0.958824,0.0303602,0.907447,0.0921253,0.788338,0.21147,0.908428,0.0911766,0.960409,0.0270976,0.804564,0.195247,0.981883,0,0.981475,0,0.986305,0,0.986449,0,0.980775,0,0.979658,0,0.986692,0,0.986937,0,0.95313,0.0422814,0.949434,0.0498762,0.901021,0.0986454,0.90461,0.0950134,0.807289,0.192524,0.809129,0.19067,0.907517,0.0920715,0.956043,0.0361628,0.802442,0.197354,0.977972,0,0.976023,0,0.986498,0,0.985726,0,0.974091,0,0.985503,0,0.999482,0,0.999736,0,0.999668,0,0.999345,0,0.999559,0,0.999122,0,0.999865,0,0.999835,0,0.999787,0,0.998729,0,0.99898,0,0.997479,0,0.997973,0,0.998287,0,0.996622,0,0.999113,0,0.999551,0,0.999079,0,0.999507,0,0.998229,0,0.998243,0,0.999772,0,0.999875,0,0.999747,0,0.999861,0,0.999208,0,0.998763,0,0.999326,0,0.999564,0,0.999638,0,0.999758,0,0.998148,0,0.999009,0,0.999485,0,0.999696,0,0.999422,0,0.999836,0,0.998923,0,0.998499,0,0.997956,0,0.997091,0,0.99778,0,0.996852,0,0.996125,0,0.994743,0,0.996262,0,0.99448,0,0.996949,0,0.997851,0,0.998359,0,0.998808,0,0.992477,0,0.995876,0,0.997823,0,0.998531,0,0.997366,0,0.99921,0,0.995392,0,0.993201,0,0.992041,0,0.987982,0,0.990313,0,0.987129,0,0.983449,0,0.978358,0,0.989436,0,0.993956,0,0.994835,0,0.99084,0,0.996662,0,0.997201,0,0.984227,0,0.981897,0,0.973125,0.00347706,0.969459,0.0107835,0.980379,0,0.988401,0,0.979686,0,0.987845,0,0.967334,0.0150165,0.966603,0.0164686,0.993303,0,0.99627,0,0.992919,0,0.996012,0,0.988212,0,0.992938,0,0.992811,0,0.987803,0,0.995919,0,0.9959,0,0.979771,0,0.980544,0,0.966992,0.0156866,0.968318,0.0130362,0.981862,0,0.988827,0,0.983472,0,0.989662,0,0.970829,0.00801026,0.973916,0.00182469,0.993271,0,0.996087,0,0.993776,0,0.996372,0,0.991994,0,0.995064,0,0.994397,0,0.990806,0,0.997085,0,0.996703,0,0.98525,0,0.987081,0,0.976676,0,0.979427,0,0.988896,0,0.993064,0,0.990892,0,0.994365,0,0.982454,0,0.98551,0,0.995783,0,0.99757,0,0.996751,0,0.998264,0,0.996663,0,0.998143,0,0.99767,0,0.995704,0,0.998965,0,0.998785,0,0.992658,0,0.994006,0,0.988068,0,0.989834,0,0.994982,0,0.997093,0,0.995518,0,0.997289,0,0.991391,0,0.992536,0,0.998326,0,0.999047,0,0.99842,0,0.999136,0,0.997912,0,0.998689,0,0.999372,0,0.998943,0,0.999714,0,0.999508,0,0.998569,0,0.997459,0,0.999285,0,0.995829,0,0.996414,0,0.993272,0,0.9942,0,0.997523,0,0.995499,0,0.999943,0,0.999944,0,0.999941,0,0.999938,0,0.999934,0,0.999927,0,0.999931,0,0.999926,0,0.99992,0,0.999913,0,0.999924,0,0.999892,0,0.999928,0,0.999943,0,0.99992,0,0.999937,0,0.999946,0,0.999941,0,0.999943,0,0.999946,0,0.999919,0,0.999931,0,0.999908,0,0.999878,0,0.9998,0,0.99988,0,0.999713,0,0.999832,0,0.99991,0,0.999928,0,0.999936,0,0.999943,0,0.999896,0,0.999931,0,0.999939,0,0.999947,0,0.999702,0,0.99978,0,0.999588,0,0.999405,0,0.999153,0,0.99957,0,0.998854,0,0.999408,0,0.999772,0,0.999831,0,0.99986,0,0.999895,0,0.999693,0,0.999812,0,0.999872,0,0.999919,0,0.999107,0,0.999514,0,0.999603,0,0.999256,0,0.999693,0,0.999754,0,0.998547,0,0.998256,0,0.998018,0,0.99897,0,0.997852,0,0.998871,0,0.999437,0,0.999638,0,0.999377,0,0.999594,0,0.998801,0,0.999325,0,0.99934,0,0.998814,0,0.999543,0,0.999562,0,0.997769,0,0.997765,0,0.997834,0,0.998826,0,0.997967,0,0.998886,0,0.999331,0,0.999537,0,0.999357,0,0.999541,0,0.999117,0,0.999455,0,0.999398,0,0.998989,0,0.999586,0,0.999555,0,0.99816,0,0.998395,0,0.998677,0,0.999264,0,0.999068,0,0.999457,0,0.999542,0,0.99964,0,0.99964,0,0.999699,0,0.999662,0,0.999593,0,0.999334,0,0.999423,0,0.999481,0,0.999713,0,0.999562,0,0.999766,0,0.999789,0,0.999752,0,0.999809,0,0.999778,0,0.999831,0,0.999843,0,0.999708,0,0.999744,0,0.999884,0,0.999832,0,0.99968,0,0.999803,0,0.99986,0,0.999911,0,0.999922,0,0.999902,0,0.999911,0,0.999896,0,0.999871,0,0.999874,0,0.745996,0.135975,0.742803,0.127576,0.731805,0.134855,0.738139,0.13185,0.719403,0.147536,0.733232,0.117739,0.725887,0.165583,0.711225,0.17572,0.692622,0.192331,0.717827,0.190711,0.727992,0.18497,0.641601,0.302165,0.633254,0.318573,0.724322,0.167551,0.741435,0.175803,0.757507,0.129872,0.762719,0.158327,0.773239,0.115525,0.683161,0.264059,0.712295,0.238601,0.754528,0.125276,0.738186,0.161817,0.767525,0.127771,0.748775,0.164653,0.829723,0.0881946,0.841222,0.0976225,0.874801,0.0632682,0.851875,0.0860002,0.878156,0.0589631,0.851399,0.0949226,0.815434,0.147975,0.869149,0.0908019,0.885011,0.060116,0.804234,0.141952,0.794063,0.137943,0.741162,0.21072,0.746888,0.193841,0.801599,0.151191,0.725815,0.231536,0.780443,0.133555,0.798728,0.0943851,0.750745,0.176798,0.797748,0.132349,0.761291,0.197461,0.786331,0.142233,0.803921,0.114013,0.732901,0.154401,0.717148,0.185675,0.640464,0.193998,0.629484,0.231852,0.806932,0.092843,0.733525,0.128605,0.672978,0.246047,0.739577,0.19974,0.569767,0.36721,0.645874,0.304312,0.586839,0.301898,0.478767,0.439384,0.781126,0.16936,0.830555,0.112661,0.694965,0.261798,0.853154,0.0813444,0.857643,0.0616146,0.960954,0,0.957855,0,0.957549,0,0.960568,0,0.957393,0,0.960378,0,0.954544,0,0.954278,0,0.954168,0,0.963143,0,0.963688,0,0.965217,0,0.966016,0,0.962879,0,0.964529,0,0.964286,0,0.961424,0,0.964877,0,0.961923,0,0.9668,0,0.967516,0,0.958248,0,0.954856,0,0.958664,0,0.955196,0,0.829464,0.0773515,0.865217,0.0568642,0.855821,0.0671201,0.81626,0.0930771,0.765042,0.127763,0.784049,0.103757,0.702338,0.172114,0.731453,0.135268,0.787605,0.0928999,0.831903,0.0701882,0.788391,0.0861263,0.832498,0.0651547,0.73854,0.118404,0.740184,0.109149,0.867519,0.0519822,0.868364,0.046112,0.706059,0.143334,0.725332,0.122959,0.701422,0.166681,0.673975,0.191066,0.666638,0.216139,0.631186,0.245929,0.636377,0.21883,0.6746,0.166305,0.587771,0.278656,0.700596,0.121454,0.724825,0.103225,0.734604,0.123112,0.504038,0.202879,0.438412,0.297551,0.553702,0.225523,0.602243,0.159405,0.411619,0.352435,0.483822,0.312402,0.626292,0.113781,0.535163,0.178481,0.439738,0.255041,0.39761,0.246063,0.373492,0.314956,0.329343,0.291738,0.358586,0.329465,0.457987,0.271103,0.372514,0.280643,0.468206,0.237227,0.598558,0.219577,0.619487,0.181956,0.684122,0.147068,0.671006,0.17282,0.624646,0.164414,0.687153,0.134546,0.625836,0.229185,0.529132,0.305814,0.412198,0.402649,0.508039,0.282916,0.542343,0.224541,0.550241,0.199546,0.879538,0.0370001,0.870133,0.0437353,0.851066,0.0646852,0.862054,0.0598869,0.863777,0.0466453,0.844256,0.0661682,0.876012,0.0525495,0.891412,0.0262809,0.897108,0.0198418,0.885948,0.0307907,0.893681,0.0446242,0.883085,0.0538015,0.876822,0.0396303,0.870628,0.0438997,0.875019,0.0577823,0.692021,0.187973,0.734947,0.134703,0.65511,0.202785,0.611715,0.266798,0.567089,0.283816,0.515052,0.362615,0.738596,0.140733,0.661722,0.16607,0.584332,0.212936,0.541362,0.344357,0.624198,0.254188,0.446571,0.444245,0.710065,0.147207,0.783446,0.0976585,0.829343,0.0627343,0.840196,0.0866924,0.576293,0.309414,0.65313,0.241249,0.71302,0.199318,0.646407,0.257595,0.719819,0.185262,0.768114,0.153183,0.561896,0.334891,0.479652,0.399995,0.470231,0.379623,0.485193,0.369544,0.509396,0.288965,0.430626,0.375443,0.573045,0.290954,0.658281,0.220007,0.46113,0.3547,0.573145,0.257264,0.933061,0,0.93176,0,0.933455,0,0.935123,0,0.93399,0,0.935863,0,0.923885,0.00562743,0.925305,0,0.925838,0,0.932031,0,0.929251,0,0.93316,0,0.924721,0.00612838,0.929606,0,0.918316,0.0157782,0.924613,0,0.92905,0,0.921901,0.0127072,0.925343,0,0.982484,0,0.98255,0,0.982954,0,0.982923,0,0.983146,0,0.983131,0,0.982474,0,0.982834,0,0.983009,0,0.982661,0,0.982198,0,0.98213,0,0.981656,0,0.982877,0,0.982351,0,0.981508,0,0.981827,0,0.980951,0,0.981943,0,0.981932,0,0.450962,0.276698,0.423713,0.379397,0.49899,0.351745,0.535692,0.266799,0.544593,0.31929,0.513443,0.380545,0.530987,0.213918,0.418903,0.262836,0.358564,0.307246,0.358238,0.258062,0.486545,0.203509,0.276877,0.269437,0.454394,0.293915,0.599856,0.231242,0.565845,0.169214,0.648871,0.140564,0.962739,0,0.96581,0,0.966136,0,0.963063,0,0.966345,0,0.963289,0,0.968597,0,0.968938,0,0.969139,0,0.959762,0,0.959435,0,0.956311,0,0.955974,0,0.96001,0,0.956589,0,0.95906,0,0.962358,0,0.955584,0,0.965387,0,0.968118,0,0.808633,0.191321,0.889864,0.110104,0.884466,0.115501,0.784898,0.215051,0.876838,0.123125,0.759667,0.240277,0.931062,0.0689163,0.930785,0.0691907,0.929985,0.0699882,0.504264,0.495651,0.592852,0.407074,0.557984,0.44192,0.644149,0.355788,0.819815,0.180143,0.660161,0.339787,0.82229,0.177671,0.892313,0.107657,0.932893,0.0670863,0.893923,0.106048,0.93698,0.063,0.817755,0.182216,0.822153,0.177813,0.645788,0.354169,0.565463,0.434504,0.629582,0.370398,0.752322,0.247658,0.745219,0.25477,0.659952,0.340035,0.892462,0.107517,0.900966,0.0990094,0.951076,0.047803,0.948489,0.0514905,0.868238,0.131743,0.945587,0.0543836,0.896872,0.1031,0.94334,0.0566405,0.50779,0.492195,0.506247,0.493743,0.737381,0.262604,0.722476,0.277505,0.705544,0.294429,0.506669,0.49331,0.694204,0.305748,0.516036,0.483926,0.706204,0.293759,0.714406,0.285566,0.806912,0.193033,0.823756,0.176198,0.679712,0.320238,0.798042,0.201893,0.756332,0.243648,0.908598,0.0913652,0.520794,0.47909,0.589849,0.410047,0.618003,0.381917,0.52073,0.479182,0.752524,0.247366,0.777106,0.222809,0.689454,0.310457,0.684023,0.315862,0.702142,0.297734,0.527398,0.472471,0.724816,0.275061,0.531541,0.468322,0.574047,0.425834,0.742998,0.256876,0.576956,0.422916,0.749161,0.250703,0.511839,0.488007,0.668457,0.331396,0.603829,0.396035,0.521057,0.4788,0.7911,0.208754,0.768246,0.23161,0.731572,0.268302,0.7134,0.286463,0.689,0.310842,0.524519,0.475339,0.677356,0.322498,0.552847,0.447028,0.703176,0.296684,0.814713,0.185147,0.733719,0.266154,0.835944,0.163926,0.64886,0.351039,0.795368,0.204526,0.765936,0.233948,0.609481,0.390403,0.872849,0.127035,0.854746,0.145132,0.658121,0.341744,0.646186,0.353698,0.639737,0.360167,0.664912,0.335004,0.614699,0.385214,0.668609,0.331317,0.816265,0.183637,0.891825,0.108065,0.828287,0.171622,0.906869,0.0930249,0.691936,0.307998,0.675833,0.324098,0.582192,0.417724,0.541834,0.458085,0.501503,0.498416,0.706709,0.293226,0.510666,0.48925,0.718571,0.281365,0.827301,0.172631,0.827236,0.17269,0.913847,0.0860736,0.915944,0.0839661,0.831821,0.168117,0.912559,0.0873726,0.829366,0.170552,0.915102,0.0847988,0.730258,0.269679,0.724417,0.27552,0.559459,0.440447,0.601187,0.398708,0.600266,0.39963,0.740604,0.259336,0.866799,0.13316,0.854376,0.145575,0.925502,0.0744632,0.91896,0.0809947,0.841506,0.158437,0.913694,0.0862494,0.999815,0,0.999761,0,0.999878,0,0.999894,0,0.999723,0,0.999866,0,0.999912,0,0.999848,0,0.999924,0,0.999866,0,0.999684,0,0.999593,0,0.999375,0,0.999218,0,0.999748,0,0.999483,0,0.999395,0,0.99905,0,0.998875,0,0.998887,0,0.997859,0,0.997917,0,0.999902,0,0.999882,0,0.999932,0,0.999938,0,0.999944,0,0.999924,0,0.999945,0,0.999936,0,0.99988,0,0.99984,0,0.999755,0,0.999664,0,0.999907,0,0.999848,0,0.999846,0,0.999759,0,0.999765,0,0.999796,0,0.999553,0,0.999928,0,0.999939,0,0.99994,0,0.999924,0,0.999897,0,0.999901,0,0.999855,0,0.999857,0,0.999881,0,0.99991,0,0.999806,0,0.999825,0,0.999917,0,0.999726,0,0.999645,0,0.999745,0,0.999797,0,0.999578,0,0.999803,0,0.999745,0,0.999689,0,0.99965,0,0.999638,0,0.999578,0,0.999532,0,0.99942,0,0.999462,0,0.999235,0,0.999321,0,0.999514,0,0.998828,0,0.998939,0,0.999597,0,0.999568,0,0.999549,0,0.999423,0,0.99954,0,0.9994,0,0.999165,0,0.998719,0,0.999113,0,0.999405,0,0.999393,0,0.999541,0,0.99956,0,0.999602,0,0.999441,0,0.999653,0,0.999492,0,0.999104,0,0.999079,0,0.998601,0,0.998594,0,0.999152,0,0.998632,0,0.998089,0,0.998107,0,0.999084,0,0.998602,0,0.99834,0,0.99863,0,0.998224,0,0.999581,0,0.999541,0,0.999696,0,0.999732,0,0.999764,0,0.999614,0,0.999794,0,0.999641,0,0.99929,0,0.99926,0,0.99885,0,0.998783,0,0.999305,0,0.998889,0,0.998088,0,0.998113,0,0.99921,0,0.998704,0,0.998141,0,0.998176,0,0.999678,0,0.999661,0,0.99982,0,0.999839,0,0.999854,0,0.999699,0,0.999346,0,0.999315,0,0.998871,0,0.99888,0,0.997978,0,0.998028,0,0.999305,0,0.998117,0,0.998101,0,0.579496,0.353763,0.601735,0.347856,0.560796,0.359767,0.540821,0.35312,0.50996,0.37462,0.512557,0.329599,0.626723,0.235131,0.696882,0.212233,0.561656,0.245747,0.771401,0.176863,0.622957,0.33854,0.835218,0.132311,0.688514,0.290113,0.639486,0.331078,0.673817,0.309762,0.551947,0.437659,0.730017,0.262808,0.70142,0.288935,0.681772,0.303784,0.824922,0.150259,0.747818,0.230798,0.665685,0.312401,0.51333,0.476266,0.567789,0.408374,0.55857,0.42922,0.749456,0.243544,0.751928,0.23983,0.542615,0.439238,0.551568,0.433106,0.531695,0.442659,0.541373,0.427428,0.555818,0.406329,0.584384,0.394061,0.530875,0.426641,0.630214,0.341021,0.771758,0.212141,0.768844,0.218818,0.775301,0.20298,0.758175,0.231929,0.63165,0.316664,0.644638,0.318106,0.49059,0.460532,0.475117,0.460261,0.483707,0.423248,0.596597,0.330098,0.467645,0.409585,0.561911,0.340003,0.716973,0.225917,0.747714,0.211239,0.680261,0.243189,0.766944,0.203569,0.47213,0.363137,0.526329,0.346255,0.446761,0.394086,0.427877,0.363286,0.398663,0.331614,0.434529,0.358665,0.357772,0.311195,0.416439,0.341138,0.552745,0.291916,0.596021,0.276176,0.538404,0.285987,0.639747,0.260053,0.437328,0.301338,0.423906,0.317759,0.346221,0.320579,0.372488,0.319831,0.440643,0.284876,0.399855,0.300241,0.380772,0.333067,0.435366,0.289098,0.522919,0.270289,0.547003,0.265705,0.510479,0.27715,0.542138,0.274331,0.462584,0.305377,0.464872,0.296793,0.380616,0.318543,0.351573,0.341554,0.480164,0.267138,0.549361,0.223897,0.438489,0.293132,0.410814,0.317873,0.414787,0.333034,0.351822,0.342534,0.568398,0.235327,0.507658,0.292401,0.518837,0.282743,0.478234,0.31576,0.516543,0.279915,0.439822,0.308709,0.440319,0.375817,0.450812,0.3464,0.391647,0.322977,0.395133,0.350945,0.455926,0.287458,0.511022,0.243166,0.487473,0.303598,0.454303,0.392715,0.824771,0.103227,0.84977,0.0878335,0.813797,0.116285,0.780019,0.137564,0.742474,0.156269,0.79405,0.119598,0.692517,0.178178,0.761739,0.133458,0.830492,0.0940372,0.852546,0.0823697,0.86349,0.0699653,0.879247,0.0616823,0.810059,0.102359,0.87321,0.0694925,0.894414,0.0524097,0.522366,0.208347,0.595998,0.160671,0.553786,0.231108,0.488711,0.236283,0.487585,0.323878,0.418678,0.337885,0.639914,0.155882,0.599203,0.226021,0.517618,0.33941,0.39793,0.296936,0.413076,0.316732,0.373528,0.325598,0.382443,0.346566,0.3405,0.338447,0.332725,0.328597,0.414639,0.324216,0.526423,0.218288,0.405486,0.330224,0.507247,0.232317,0.381691,0.354409,0.375859,0.357649,0.607186,0.14128,0.652071,0.11901,0.590791,0.146513,0.63143,0.180167,0.656471,0.225165,0.651948,0.198081,0.614088,0.265236,0.596311,0.300669,0.531917,0.364078,0.509466,0.399727,0.635396,0.169204,0.606462,0.242532,0.518185,0.35451,0.577262,0.331746,0.637223,0.26003,0.548392,0.368462,0.607086,0.300036,0.487233,0.432575,0.468188,0.45779,0.691343,0.184977,0.686401,0.161347,0.715087,0.150994,0.689997,0.165307,0.678892,0.210094,0.725639,0.137575,0.665378,0.132149,0.637246,0.14292,0.649007,0.178218,0.60071,0.200867,0.575956,0.336298,0.584989,0.327942,0.52213,0.399044,0.502761,0.419773,0.48947,0.439516,0.508729,0.421795,0.506314,0.413234,0.573668,0.334746,0.510659,0.40595,0.581883,0.320787,0.511796,0.417605,0.511712,0.41609,0.656852,0.224349,0.662307,0.225307,0.694362,0.149439,0.70577,0.141893,0.656164,0.218324,0.688266,0.156617,0.666282,0.227196,0.717974,0.13779,0.619301,0.270956,0.535778,0.373505,0.553861,0.351465,0.640729,0.238078,0.580416,0.3167,0.658257,0.208807,0.464472,0.454103,0.485109,0.430318,0.495795,0.416041,0.684162,0.154834,0.670468,0.187221,0.684314,0.205464,0.682264,0.191014,0.690501,0.139955,0.683358,0.203173,0.661482,0.20526,0.599146,0.296915,0.686206,0.166837,0.518097,0.39513,0.491818,0.432217,0.67378,0.168016,0.625099,0.244239,0.621357,0.230287,0.663499,0.158676,0.516022,0.379971,0.519207,0.362852,0.676227,0.130752,0.687867,0.136474,0.658134,0.191484,0.671095,0.197,0.6918,0.138631,0.670194,0.18593,0.679445,0.20013,0.610244,0.273527,0.506696,0.399152,0.526962,0.185979,0.430107,0.278426,0.400048,0.2619,0.48609,0.24701,0.359061,0.340456,0.411467,0.363178,0.406893,0.298137,0.386831,0.274762,0.353581,0.33693,0.556608,0.243605,0.608082,0.149445,0.466267,0.364647,0.609378,0.153029,0.546463,0.170083,0.56937,0.204179,0.522215,0.184515,0.431738,0.281251,0.402076,0.305179,0.412788,0.269322,0.378072,0.297958,0.536558,0.278372,0.482315,0.357782,0.630472,0.247895,0.655843,0.206144,0.734968,0.172574,0.745918,0.151107,0.450127,0.415198,0.603022,0.294609,0.725873,0.194128,0.670425,0.174347,0.557976,0.232601,0.693555,0.143901,0.594006,0.190626,0.756308,0.128775,0.774233,0.10616,0.466609,0.281583,0.425809,0.352581,0.417027,0.303635,0.386358,0.364333,0.513449,0.226293,0.455104,0.25174,0.460078,0.341854,0.550798,0.272984,0.495871,0.284648,0.294462,0.279133,0.348553,0.237645,0.320539,0.226569,0.275257,0.274926,0.286302,0.255857,0.305265,0.251105,0.390594,0.212044,0.353144,0.260535,0.315269,0.298071,0.321014,0.279174,0.310639,0.288932,0.34854,0.301697,0.332352,0.315887,0.351967,0.254635,0.379862,0.277021,0.35535,0.256133,0.327826,0.301722,0.42161,0.246614,0.399655,0.288756,0.374531,0.279182,0.436081,0.23814,0.363989,0.284616,0.417028,0.2549,0.35583,0.35471,0.412479,0.324062,0.444988,0.255308,0.472354,0.251416,0.389131,0.336687,0.379164,0.330355,0.367728,0.358248,0.356618,0.355164,0.372642,0.303959,0.423721,0.235607,0.344138,0.336436,0.465337,0.221895,0.509444,0.186729,0.462863,0.296832,0.513882,0.263619,0.551731,0.159925,0.570761,0.223847,0.368056,0.288685,0.350424,0.318209,0.352861,0.317423,0.328986,0.325378,0.386894,0.289621,0.371107,0.288244,0.320886,0.286436,0.374467,0.244006,0.300615,0.253501,0.362096,0.266466,0.338817,0.270273,0.300052,0.240393,0.429519,0.228178,0.420446,0.248808,0.489061,0.214297,0.487599,0.208663,0.412354,0.263266,0.39437,0.279948,0.456998,0.237244,0.546681,0.372464,0.587646,0.343306,0.556807,0.392932,0.568563,0.366834,0.727585,0.234791,0.71761,0.232424,0.618722,0.320185,0.519472,0.443966,0.739503,0.233525,0.584805,0.333969,0.497778,0.400904,0.718032,0.217634,0.605657,0.257735,0.643611,0.252049,0.678045,0.226857,0.701055,0.207232,0.634289,0.316863,0.573357,0.384198,0.533461,0.450136,0.518278,0.460845,0.799947,0.191018,0.775529,0.211769,0.550525,0.410322,0.54678,0.438892,0.81004,0.182346,0.512081,0.460429,0.640156,0.30567,0.753168,0.228209,0.708607,0.199712,0.701026,0.205878,0.681197,0.22595,0.669829,0.239311,0.539813,0.421039,0.567465,0.415779,0.559499,0.426414,0.538707,0.424422,0.805946,0.184127,0.807459,0.184559,0.660913,0.253366,0.654636,0.266168,0.638078,0.289957,0.519543,0.441083,0.623706,0.304944,0.486798,0.472609,0.583695,0.39379,0.794259,0.191432,0.632393,0.340961,0.798793,0.182601,0.47028,0.469266,0.658816,0.296987,0.661155,0.304461,0.475389,0.475381,0.787871,0.180839,0.798471,0.177549,0.617593,0.301153,0.609346,0.297465,0.59679,0.294326,0.471994,0.452238,0.571696,0.299133,0.457824,0.451458,0.647442,0.297192,0.772404,0.187465,0.646016,0.287676,0.771699,0.180427,0.61131,0.191147,0.707467,0.145192,0.698857,0.167305,0.589545,0.232642,0.689108,0.192144,0.573644,0.270939,0.788166,0.105925,0.787189,0.116646,0.78578,0.128294,0.460376,0.310726,0.50184,0.245185,0.434131,0.362824,0.520042,0.208576,0.61851,0.168698,0.527042,0.194815,0.618983,0.156221,0.710831,0.130021,0.789609,0.0961225,0.712931,0.120177,0.79537,0.0874725,0.532792,0.345761,0.671572,0.238692,0.656861,0.265558,0.482869,0.411377,0.779248,0.156624,0.775862,0.168622,0.52334,0.32984,0.464016,0.37176,0.41058,0.407407,0.558525,0.304137,0.681952,0.214602,0.783227,0.14216,0.65266,0.142459,0.635975,0.156301,0.718985,0.12276,0.734069,0.112197,0.792595,0.0923426,0.805451,0.0844618,0.730314,0.110832,0.640042,0.147098,0.807232,0.081944,0.547055,0.191826,0.571776,0.171484,0.55915,0.187271,0.710152,0.147384,0.771825,0.113756,0.793254,0.107508,0.734231,0.142555,0.660192,0.18445,0.64329,0.181478,0.614013,0.184638,0.6986,0.145897,0.753034,0.118432,0.975793,0,0.976769,0,0.975739,0,0.974285,0,0.974624,0,0.972822,0,0.977445,0,0.97667,0,0.975816,0,0.971809,0,0.973811,0,0.967664,0,0.970069,0,0.969967,0,0.965364,0,0.975674,0,0.976887,0,0.976989,0,0.977732,0,0.973118,0,0.975779,0,0.977623,0,0.978121,0,0.978261,0,0.978645,0,0.978821,0,0.979063,0,0.978726,0,0.978377,0,0.979292,0,0.979018,0,0.977893,0,0.978506,0,0.977078,0,0.977999,0,0.978927,0,0.979125,0,0.97922,0,0.979346,0,0.978655,0,0.979057,0,0.979306,0,0.979493,0,0.979478,0,0.979632,0,0.9394,0,0.941646,0,0.941568,0,0.939221,0,0.941755,0,0.939322,0,0.943865,0,0.944011,0,0.944289,0,0.938063,0,0.938209,0,0.936909,0,0.937018,0,0.938122,0,0.936936,0,0.938511,0,0.940187,0,0.939509,0,0.940934,0,0.937346,0,0.937902,0,0.94192,0,0.943818,0,0.942286,0,0.943869,0,0.880161,0.0522075,0.907376,0.019915,0.909633,0.0148214,0.884313,0.044031,0.910305,0.01216,0.885757,0.0374468,0.918482,0.0056056,0.91942,0.00315494,0.919678,0.00181874,0.838956,0.0731473,0.834788,0.0818843,0.794281,0.0988923,0.790918,0.110637,0.840578,0.0662715,0.795787,0.0882694,0.82896,0.0921768,0.872291,0.0611788,0.822808,0.103212,0.862465,0.0729207,0.786555,0.122764,0.781615,0.135189,0.903055,0.0285915,0.916729,0.00962059,0.895519,0.0442761,0.913481,0.0166914,0.522317,0.297066,0.454476,0.345632,0.430672,0.403837,0.500774,0.345845,0.471604,0.386945,0.464574,0.402629,0.561505,0.296081,0.579638,0.255659,0.614977,0.252924,0.629285,0.22023,0.533293,0.342059,0.594267,0.289006,0.589465,0.219094,0.532972,0.252575,0.593475,0.185375,0.537063,0.212283,0.637746,0.190242,0.641287,0.16256,0.466207,0.291356,0.470518,0.243987,0.942361,0,0.941605,0,0.940574,0,0.941606,0,0.939413,0,0.940767,0,0.942671,0,0.94324,0,0.943861,0,0.944304,0,0.942007,0,0.943325,0,0.943878,0,0.943211,0,0.944777,0,0.944339,0,0.944787,0,0.945489,0,0.9427,0,0.944049,0,0.852157,0.0906166,0.880881,0.0662474,0.886541,0.0576202,0.855475,0.0829822,0.901267,0.0478107,0.906856,0.031987,0.81752,0.11407,0.815556,0.121843,0.776406,0.147822,0.772865,0.158427,0.817262,0.125811,0.853686,0.0938532,0.822002,0.126475,0.859121,0.0928461,0.771692,0.1662,0.773504,0.170625,0.881367,0.0696015,0.901303,0.0519548,0.8865,0.0680812,0.905817,0.050433,0.521854,0.372699,0.580694,0.306092,0.621388,0.272411,0.566113,0.336264,0.653501,0.244554,0.6023,0.305623,0.500167,0.409204,0.452691,0.448214,0.494616,0.420839,0.528105,0.37878,0.539783,0.376166,0.463233,0.459366,0.494182,0.395517,0.465861,0.417016,0.564135,0.332182,0.530608,0.344773,0.95026,0,0.949193,0,0.947584,0,0.948914,0,0.946573,0,0.948033,0,0.948568,0,0.946689,0,0.950622,0,0.951879,0,0.952582,0,0.953838,0,0.949764,0,0.951683,0,0.95358,0,0.952247,0,0.955779,0,0.954847,0,0.95539,0,0.957618,0,0.951485,0,0.951121,0,0.95435,0,0.877885,0.0824534,0.867091,0.0891431,0.829998,0.123689,0.84268,0.116185,0.780004,0.170223,0.793478,0.162911,0.860743,0.103341,0.891484,0.0728593,0.885032,0.0841641,0.907266,0.0609789,0.815404,0.147318,0.852186,0.117849,0.912315,0.051658,0.901919,0.0590445,0.925325,0.025812,0.917693,0.0366317,0.923036,0.0379326,0.934774,0.0119775,0.893379,0.0644734,0.911256,0.0448956,0.649367,0.269973,0.695311,0.212001,0.707619,0.20779,0.660301,0.26666,0.712392,0.213458,0.659638,0.275921,0.594252,0.341643,0.586059,0.342499,0.507008,0.436089,0.503775,0.432188,0.590355,0.353405,0.496795,0.453559,0.567875,0.354146,0.630154,0.283098,0.489232,0.439927,0.677427,0.224481,0.63049,0.32334,0.589186,0.370715,0.485916,0.477775,0.536365,0.421882,0.600225,0.367765,0.541157,0.421625,0.526505,0.437709,0.55937,0.408655,0.688144,0.284723,0.571505,0.380013,0.650307,0.2948,0.494864,0.462086,0.708254,0.229077,0.695634,0.252417,0.671427,0.284768,0.633024,0.327833,0.632684,0.334838,0.72023,0.246679,0.816281,0.160122,0.754691,0.220385,0.872405,0.109989,0.837212,0.143817,0.76444,0.197758,0.841595,0.133579,0.885544,0.0967907,0.662018,0.309807,0.528728,0.437905,0.773826,0.203889,0.577272,0.384642,0.502272,0.457238,0.5718,0.381638,0.638269,0.305178,0.7971,0.150835,0.827941,0.107388,0.886217,0.0730197,0.863242,0.102815,0.918675,0.0526981,0.903759,0.0727854,0.846854,0.0666401,0.899667,0.0404247,0.911404,0.040225,0.850023,0.121199,0.780131,0.175767,0.891925,0.0881871,0.668269,0.262328,0.691511,0.223367,0.73273,0.16118,0.760729,0.0999101,0.714099,0.161407,0.67382,0.188999,0.663011,0.215054,0.706477,0.181751,0.647595,0.243738,0.695857,0.203772,0.746851,0.151703,0.75209,0.136072,0.740225,0.167874,0.755675,0.121096,0.718901,0.142369,0.757358,0.106921,0.72103,0.124413,0.680338,0.164968,0.683103,0.142583,0.667714,0.251297,0.602069,0.310887,0.577164,0.344113,0.651704,0.275232,0.554825,0.373718,0.639396,0.294728,0.716704,0.215776,0.723834,0.201291,0.712955,0.226371,0.732195,0.184815,0.682644,0.227291,0.626829,0.276204,0.642865,0.305155,0.542751,0.39961,0.553123,0.3962,0.658943,0.295925,0.575772,0.380269,0.682969,0.278271,0.748218,0.211343,0.727346,0.225438,0.774082,0.191797,0.715795,0.230233,0.636604,0.304489,0.543421,0.392031,0.759503,0.213092,0.655456,0.312142,0.717201,0.255728,0.814735,0.163658,0.79234,0.186202,0.869942,0.113526,0.885514,0.0976749,0.839511,0.138052,0.924208,0.0618616,0.896013,0.0858375,0.917696,0.0689421,0.937122,0.050759,0.801115,0.17051,0.714896,0.25214,0.868274,0.107881,0.611576,0.350563,0.919395,0.0679243,0.891648,0.09339,0.90448,0.0811928,0.924014,0.0635605,0.910862,0.0747383,0.927972,0.0596022,0.936803,0.0518663,0.933825,0.0545678,0.946756,0.0350989,0.943676,0.0405122,0.940901,0.0460169,0.951833,0.0257435,0.929387,0.0585288,0.90673,0.0795908,0.941124,0.0448386,0.856955,0.125912,0.947072,0.0293379,0.929701,0.0534515,0.942316,0.0237919,0.957451,0.00346452,0.942178,0.00352712,0.960969,0,0.966852,0,0.959121,0.00895313,0.972617,0,0.96697,0,0.969912,0,0.974629,0,0.948443,0.0312261,0.934967,0.0525463,0.959235,0.0112763,0.917608,0.0673356,0.944925,0,0.943117,0,0.941611,0,0.943644,0,0.939342,0,0.941717,0,0.941395,0,0.93967,0,0.937098,0,0.945719,0,0.946785,0,0.947761,0,0.948625,0,0.944151,0,0.946547,0,0.947519,0,0.945779,0,0.948041,0,0.946363,0,0.949257,0,0.949742,0,0.944114,0,0.942555,0,0.944775,0,0.949173,0,0.952466,0,0.952838,0,0.949555,0,0.953153,0,0.949893,0,0.94661,0,0.946316,0,0.946935,0,0.946088,0,0.948857,0,0.945947,0,0.948604,0,0.952091,0,0.951745,0,0.948241,0,0.948377,0,0.945897,0,0.945955,0,0.94619,0,0.948294,0,0.951066,0,0.951205,0,0.951445,0,0.979849,0,0.979146,0,0.980165,0,0.980872,0,0.978216,0,0.97924,0,0.981349,0,0.980318,0,0.981571,0,0.980537,0,0.979043,0,0.978587,0,0.977521,0,0.977081,0,0.979257,0,0.97773,0,0.977895,0,0.976963,0,0.976411,0,0.975499,0,0.975582,0,0.973873,0,0.975069,0,0.976696,0,0.971902,0,0.973219,0,0.978087,0,0.977033,0,0.975768,0,0.974284,0,0.974316,0,0.972834,0,0.972492,0,0.970395,0,0.971024,0,0.968861,0,0.967463,0,0.965432,0,0.962991,0,0.965549,0,0.961559,0,0.964422,0,0.963448,0,0.960385,0,0.967556,0,0.969204,0,0.969001,0,0.970563,0,0.966541,0,0.968041,0,0.971194,0,0.969697,0,0.972378,0,0.968005,0,0.96633,0,0.964301,0,0.963396,0,0.962905,0,0.963865,0,0.962428,0,0.963434,0,0.96262,0,0.962069,0,0.961551,0,0.964891,0,0.965272,0,0.965907,0,0.96623,0,0.964509,0,0.965582,0,0.965617,0,0.964695,0,0.9659,0,0.964993,0,0.966531,0,0.966796,0,0.963851,0,0.963159,0,0.964168,0,0.963583,0,0.962146,0,0.96115,0,0.961191,0,0.962174,0,0.961276,0,0.962225,0,0.960267,0,0.96031,0,0.960406,0,0.963209,0,0.963234,0,0.964231,0,0.964314,0,0.963199,0,0.964159,0,0.9633,0,0.962194,0,0.963423,0,0.962305,0,0.964416,0,0.964557,0,0.961168,0,0.960264,0,0.961256,0,0.96034,0,0.937595,0.0243687,0.948539,0.00637823,0.950731,0,0.942521,0.0111921,0.952957,0,0.946928,0.000279114,0.956689,0,0.957389,0,0.958225,0,0.933134,0.0242592,0.923635,0.0464377,0.940597,0.00754509,0.912588,0.061139,0.933133,0.0377073,0.904927,0.0713387,0.931894,0.0457538,0.946809,0.0140069,0.956374,0,0.946703,0.019339,0.956843,0,0.958811,0.0111325,0.951346,0.0257734,0.955147,0.0190118,0.962663,0.00404349,0.960521,0.00896766,0.967036,0,0.968731,0,0.965168,0,0.972513,0,0.961868,0.00366384,0.955185,0.0173756,0.958783,0.00875306,0.952019,0.022481,0.948472,0.0306069,0.945371,0.0355789,0.97602,0,0.972717,0,0.975759,0,0.977389,0,0.976966,0,0.978159,0,0.978212,0,0.977268,0,0.978764,0,0.97556,0,0.972411,0,0.966978,0,0.971542,0,0.971497,0,0.971752,0,0.971856,0,0.971936,0,0.972081,0,0.971447,0,0.97166,0,0.971809,0,0.972025,0,0.971662,0,0.972343,0,0.971991,0,0.972274,0,0.972584,0,0.971172,0,0.971147,0,0.970603,0,0.97069,0,0.971468,0,0.970711,0,0.971167,0,0.971159,0,0.970764,0,0.97079,0,0.966412,0,0.96257,0,0.963005,0,0.967144,0,0.963832,0,0.968083,0,0.969758,0,0.968952,0,0.971635,0,0.970838,0,0.970652,0,0.97246,0,0.968305,0,0.965922,0,0.967804,0,0.965618,0,0.970092,0,0.969535,0,0.962422,0,0.962475,0,0.971982,0,0.97171,0,0.971401,0,0.971704,0,0.97098,0,0.971318,0,0.971448,0,0.971112,0,0.970668,0,0.972054,0,0.972301,0,0.972491,0,0.972705,0,0.971711,0,0.972194,0,0.972468,0,0.972173,0,0.972562,0,0.972287,0,0.972846,0,0.972917,0,0.971923,0,0.971687,0,0.972054,0,0.97184,0,0.971987,0,0.970628,0,0.971075,0,0.972578,0,0.971734,0,0.973301,0,0.969837,0,0.970238,0,0.974312,0,0.973776,0,0.974933,0,0.973192,0,0.971425,0,0.969981,0,0.969104,0,0.960358,0,0.960694,0,0.961785,0,0.96157,0,0.962975,0,0.962876,0,0.960972,0,0.961957,0,0.963036,0,0.96127,0,0.959957,0,0.960804,0,0.95941,0,0.96268,0,0.96231,0,0.958819,0,0.959325,0,0.957887,0,0.958481,0,0.958202,0,0.9572,0,0.959757,0,0.960119,0,0.958991,0,0.959415,0,0.956202,0,0.956061,0,0.956959,0,0.957043,0,0.957703,0,0.95773,0,0.955995,0,0.956946,0,0.957741,0,0.957183,0,0.956403,0,0.957385,0,0.956662,0,0.957817,0,0.95797,0,0.955532,0,0.955275,0,0.954601,0,0.954281,0,0.95584,0,0.954952,0,0.955064,0,0.954927,0,0.953991,0,0.953759,0,0.95919,0,0.960504,0,0.960997,0,0.959883,0,0.961422,0,0.960444,0,0.961947,0,0.962252,0,0.962537,0,0.958969,0,0.958112,0,0.958262,0,0.957289,0,0.959638,0,0.959015,0,0.957002,0,0.958308,0,0.955526,0,0.957134,0,0.95604,0,0.954416,0,0.95988,0,0.961585,0,0.959072,0,0.961137,0,0.968815,0,0.968518,0,0.967789,0,0.968038,0,0.966935,0,0.967121,0,0.968216,0,0.967549,0,0.966768,0,0.968305,0,0.969109,0,0.968596,0,0.969416,0,0.967347,0,0.967615,0,0.969788,0,0.969471,0,0.970355,0,0.970024,0,0.970104,0,0.969142,0,0.968794,0,0.969674,0,0.969293,0,0.971529,0,0.971248,0,0.971159,0,0.971465,0,0.971006,0,0.971336,0,0.970896,0,0.970782,0,0.970608,0,0.971713,0,0.971755,0,0.971921,0,0.971942,0,0.97161,0,0.97174,0,0.971536,0,0.97167,0,0.971489,0,0.971904,0,0.971277,0,0.970952,0,0.971252,0,0.970951,0,0.965547,0,0.965477,0,0.966661,0,0.966777,0,0.967724,0,0.967858,0,0.965375,0,0.966516,0,0.967556,0,0.966816,0,0.965531,0,0.966748,0,0.965376,0,0.967941,0,0.967935,0,0.964127,0,0.96422,0,0.963869,0,0.964226,0,0.964196,0,0.953361,0,0.950511,0,0.954933,0,0.956607,0,0.95908,0,0.959875,0,0.957988,0,0.955519,0,0.960566,0,0.953489,0,0.950573,0,0.952176,0,0.94888,0,0.946331,0,0.943884,0,0.949314,0,0.950028,0,0.94761,0,0.946149,0,0.944808,0,0.942384,0,0.950748,0,0.948787,0,0.94454,0,0.948733,0,0.93908,0.00027645,0.951702,0,0.951733,0,0.953823,0,0.953586,0,0.952019,0,0.9524,0,0.953612,0,0.953704,0,0.954321,0,0.952824,0,0.952262,0,0.951084,0,0.95061,0,0.955111,0,0.953546,0,0.951793,0,0.953212,0,0.951351,0,0.952767,0,0.950171,0,0.95439,0,0.954893,0,0.955356,0,0.955836,0,0.953958,0,0.955516,0,0.956314,0,0.95643,0,0.964582,0,0.966318,0,0.966223,0,0.964236,0,0.967717,0,0.967716,0,0.961752,0,0.962606,0,0.959209,0,0.960606,0,0.96336,0,0.965038,0,0.961645,0,0.966554,0,0.967833,0,0.96515,0,0.966956,0,0.966642,0,0.965012,0,0.966393,0,0.964902,0,0.968281,0,0.967918,0,0.962975,0,0.962787,0,0.96314,0,0.962609,0,0.965347,0,0.967368,0,0.968755,0,0.964812,0,0.966053,0,0.965937,0,0.9648,0,0.965853,0,0.96482,0,0.963551,0,0.963416,0,0.963698,0,0.963285,0,0.964846,0,0.966202,0,0.965504,0,0.965693,0,0.964921,0,0.964774,0,0.96408,0,0.963978,0,0.965878,0,0.965055,0,0.964614,0,0.965316,0,0.964439,0,0.965149,0,0.96384,0,0.963655,0,0.96592,0,0.966152,0,0.966406,0,0.966709,0,0.96575,0,0.966158,0,0.96639,0,0.966623,0,0.967007,0,0.967292,0,0.967027,0,0.967901,0,0.968094,0,0.96725,0,0.968267,0,0.967467,0,0.968678,0,0.968843,0,0.968974,0,0.966312,0,0.966062,0,0.96528,0,0.965006,0,0.966572,0,0.965831,0,0.966811,0,0.965628,0,0.966605,0,0.964761,0,0.967696,0,0.968487,0,0.967486,0,0.968275,0,0.96957,0,0.969673,0,0.968897,0,0.968795,0,0.969701,0,0.96892,0,0.968638,0,0.969399,0,0.96845,0,0.969193,0,0.970006,0,0.970172,0,0.970466,0,0.970634,0,0.969794,0,0.970245,0,0.970279,0,0.970314,0,0.970747,0,0.969564,0,0.969539,0,0.968791,0,0.968779,0,0.968865,0,0.969661,0,0.970274,0,0.970178,0,0.970091,0,0.947082,0,0.946765,0,0.948446,0,0.948814,0,0.949218,0,0.947461,0,0.945974,0,0.945576,0,0.945223,0,0.933814,0,0.93341,0,0.931838,0,0.932053,0,0.930265,0,0.930384,0,0.932398,0,0.934156,0,0.932749,0,0.934669,0,0.930573,0,0.930691,0,0.935847,0,0.935472,0,0.936523,0,0.935151,0,0.935069,0,0.937673,0,0.935829,0,0.933476,0,0.935255,0,0.930802,0,0.932014,0,0.937661,0,0.939703,0,0.934636,0,0.941078,0,0.939529,0,0.937737,0,0.962728,0,0.963863,0,0.964165,0,0.963052,0,0.962018,0,0.961685,0,0.96112,0,0.960782,0,0.96143,0,0.962478,0,0.960523,0,0.963614,0,0.962276,0,0.963129,0,0.963179,0,0.962262,0,0.961374,0,0.961461,0,0.960552,0,0.96071,0,0.961508,0,0.962231,0,0.961423,0,0.96208,0,0.960859,0,0.960973,0,0.963025,0,0.962845,0,0.9723,0,0.972548,0,0.972445,0,0.972223,0,0.972891,0,0.97277,0,0.972055,0,0.972115,0,0.972116,0,0.972326,0,0.972587,0,0.972934,0,0.970134,0,0.970615,0,0.971244,0,0.970803,0,0.971278,0,0.971805,0,0.970435,0,0.969755,0,0.968927,0,0.96931,0,0.969795,0,0.970566,0,0.957352,0,0.957982,0,0.958416,0,0.957839,0,0.958488,0,0.958885,0,0.957177,0,0.956633,0,0.956197,0,0.956967,0,0.957644,0,0.958188,0,0.956303,0,0.956703,0,0.957731,0,0.957336,0,0.958541,0,0.958171,0,0.957067,0,0.956064,0,0.957893,0,0.954919,0,0.955074,0,0.955427,0,0.970853,0,0.970488,0,0.970119,0,0.970483,0,0.970072,0,0.969715,0,0.970812,0,0.971168,0,0.971431,0,0.971134,0,0.970783,0,0.970371,0,0.97122,0,0.971023,0,0.971171,0,0.971386,0,0.970766,0,0.970893,0,0.971546,0,0.971359,0,0.971098,0,0.970976,0,0.970794,0,0.970557,0,0.951863,0,0.950338,0,0.949662,0,0.951347,0,0.952783,0,0.953169,0,0.953559,0,0.952327,0,0.950882,0,0.95734,0,0.958836,0,0.957229,0,0.955714,0,0.954491,0,0.956108,0,0.957339,0,0.95858,0,0.960033,0,0.966225,0,0.96531,0,0.96518,0,0.966052,0,0.966844,0,0.967059,0,0.967558,0,0.96781,0,0.967274,0,0.966409,0,0.968049,0,0.965456,0,0.96787,0,0.967099,0,0.966837,0,0.967673,0,0.968416,0,0.968541,0,0.969069,0,0.969123,0,0.968643,0,0.968059,0,0.96878,0,0.968259,0,0.969133,0,0.969107,0,0.967352,0,0.967593,0,0.96158,0,0.960873,0,0.961398,0,0.962024,0,0.960268,0,0.960839,0,0.962688,0,0.962372,0,0.963418,0,0.963245,0,0.962138,0,0.961245,0,0.96312,0,0.960464,0,0.959809,0,0.959428,0,0.95916,0,0.959774,0,0.960035,0,0.95898,0,0.959595,0,0.960386,0,0.959791,0,0.959308,0,0.958932,0,0.958653,0,0.958459,0,0.958846,0,0.958891,0,0.959547,0,0.959494,0,0.958996,0,0.959656,0,0.959506,0,0.958876,0,0.958339,0,0.958289,0,0.958305,0,0.958385,0,0.959432,0,0.959751,0,0.960286,0,0.960039,0,0.960151,0,0.96062,0,0.959819,0,0.959166,0,0.958554,0,0.958834,0,0.959188,0,0.959629,0,0.961402,0,0.962231,0,0.962573,0,0.96179,0,0.96314,0,0.963416,0,0.961125,0,0.960703,0,0.960208,0,0.960948,0,0.961841,0,0.96284,0,0.964937,0,0.965738,0,0.965707,0,0.965026,0,0.966442,0,0.966286,0,0.96425,0,0.964057,0,0.963869,0,0.964867,0,0.965788,0,0.966603,0,0.967557,0,0.968005,0,0.967541,0,0.967168,0,0.968402,0,0.967881,0,0.966761,0,0.967042,0,0.967302,0,0.967901,0,0.968418,0,0.968869,0,0.969074,0,0.969353,0,0.96874,0,0.968478,0,0.969596,0,0.968977,0,0.968193,0,0.968758,0,0.969263,0,0.969604,0,0.969896,0,0.970138,0,0.969957,0,0.970068,0,0.9695,0,0.969363,0,0.970126,0,0.969593,0,0.969187,0,0.969799,0,0.970329,0,0.970467,0,0.97055,0,0.970577,0,0.97007,0,0.969927,0,0.969553,0,0.96963,0,0.969674,0,0.969365,0,0.96964,0,0.970131,0,0.970547,0,0.970449,0,0.970265,0,0.969974,0,0.968733,0,0.968039,0,0.967821,0,0.968489,0,0.967207,0,0.967017,0,0.969008,0,0.969278,0,0.969545,0,0.968971,0,0.968249,0,0.967384,0,0.965239,0,0.964215,0,0.964247,0,0.965154,0,0.966104,0,0.966258,0,0.966388,0,0.965303,0,0.964193,0,0.961052,0,0.957906,0,0.958724,0,0.96197,0,0.964944,0,0.963914,0,0.967476,0,0.966295,0,0.963186,0,0.960546,0,0.96529,0,0.957495,0,0.969698,0,0.970947,0,0.968995,0,0.967976,0,0.971957,0,0.969862,0,0.96681,0,0.968182,0,0.969577,0,0.971304,0,0.972711,0,0.973831,0,0.973329,0,0.973722,0,0.971459,0,0.971093,0,0.973925,0,0.971658,0,0.97056,0,0.972744,0,0.974692,0,0.975324,0,0.975743,0,0.975949,0,0.934416,0.00180439,0.938811,0,0.935917,0,0.930757,0.00625989,0.933176,0,0.927283,0.0100719,0.922978,0.0220423,0.927804,0.0159154,0.918645,0.0268697,0.933223,0.00857895,0.938343,0,0.941814,0,0.922368,0.0109769,0.927849,0.00108561,0.927702,0,0.923732,0.0047427,0.928292,0,0.925323,0,0.91778,0.0142742,0.914318,0.0250071,0.921019,0.0058255,0.914794,0.029707,0.923939,0.0127583,0.930242,0.00048976,0.926431,0,0.928581,0,0.92854,0,0.926517,0,0.923749,0,0.923486,0,0.922727,0.00135479,0.926153,0,0.928579,0,0.94358,0.0345971,0.936257,0.0437072,0.912548,0.0679686,0.928673,0.0560726,0.939824,0.044493,0.948426,0.0278906,0.955754,0.0132182,0.952547,0.0170393,0.962873,0,0.960528,0.000250332,0.949102,0.0198161,0.958338,0.00105205,0.97037,0,0.972576,0,0.971601,0,0.969197,0,0.974111,0,0.973288,0,0.965124,0,0.966663,0,0.968293,0,0.971574,0,0.97357,0,0.974951,0,0.881764,0.058429,0.876121,0.0677958,0.899723,0.0459862,0.904971,0.0338926,0.86772,0.0776923,0.891367,0.055612,0.906786,0.0247422,0.879775,0.0518827,0.906164,0.0199477,0.877195,0.051021,0.847186,0.0680938,0.847,0.0792233,0.802608,0.0908792,0.79628,0.111697,0.845423,0.0674295,0.802681,0.088809,0.839412,0.0959012,0.830893,0.108954,0.784359,0.137099,0.776049,0.154067,0.910525,0.0165029,0.900041,0.0275871,0.906525,0.0118794,0.914388,0.00763854,0.920514,0.00388807,0.918379,0.00699935,0.909852,0.0271862,0.89905,0.0403268,0.886167,0.0519514,0.85355,0.0549951,0.829691,0.0726612,0.839851,0.0666448,0.858799,0.0507004,0.86658,0.0416029,0.863327,0.0378282,0.861785,0.0403367,0.848621,0.0582594,0.824114,0.0757659,0.86338,0.0864094,0.868427,0.0838924,0.883747,0.0637258,0.877569,0.0665674,0.869797,0.0706394,0.855905,0.0908565,0.833391,0.11668,0.841975,0.110458,0.799258,0.151696,0.809132,0.143758,0.846273,0.107985,0.813793,0.140299,0.850876,0.0946478,0.846969,0.0974864,0.866008,0.0741516,0.871917,0.071057,0.881234,0.0640738,0.858355,0.0873892,0.822817,0.11977,0.816874,0.12737,0.770629,0.16514,0.767583,0.172037,0.814533,0.130632,0.768033,0.1745,0.870396,0.0598052,0.839391,0.0768603,0.834862,0.0736182,0.843412,0.0692256,0.834205,0.0754995,0.841409,0.0716561,0.801982,0.101109,0.824528,0.0779046,0.827582,0.0790339,0.901408,0.025189,0.904817,0.0311825,0.900308,0.0297108,0.905647,0.039599,0.877311,0.0643519,0.841656,0.08934,0.793297,0.124167,0.566594,0.272417,0.57662,0.222472,0.446263,0.282499,0.449845,0.344978,0.590291,0.194844,0.439756,0.273186,0.450588,0.396803,0.56342,0.308339,0.452268,0.426109,0.559787,0.333046,0.694161,0.206156,0.710959,0.169993,0.687773,0.226333,0.730801,0.131059,0.73646,0.121633,0.650382,0.282874,0.678835,0.261471,0.571906,0.358781,0.533235,0.387917,0.693642,0.251005,0.59095,0.345213,0.503313,0.405234,0.622218,0.298333,0.510122,0.378413,0.626298,0.276847,0.717583,0.214267,0.729987,0.211654,0.717205,0.199846,0.749844,0.197349,0.763863,0.186718,0.698496,0.248303,0.761259,0.189756,0.75426,0.195258,0.696014,0.249756,0.747205,0.200683,0.690084,0.254108,0.615997,0.323161,0.611689,0.327937,0.614771,0.323064,0.602865,0.335911,0.697838,0.248832,0.765824,0.18585,0.668056,0.272057,0.730438,0.21422,0.720865,0.221842,0.648529,0.288245,0.711919,0.228327,0.617955,0.313558,0.512975,0.410676,0.566003,0.364393,0.476966,0.440796,0.600112,0.335046,0.679871,0.262382,0.739302,0.207006,0.572366,0.345347,0.694878,0.236677,0.688837,0.235222,0.559623,0.347041,0.451928,0.443262,0.453832,0.451634,0.466242,0.446617,0.594314,0.331526,0.703243,0.233528,0.638562,0.209924,0.633797,0.245706,0.518509,0.338097,0.519282,0.294966,0.513965,0.244933,0.637377,0.174391,0.506703,0.249329,0.633129,0.181413,0.742942,0.115423,0.734673,0.144496,0.743389,0.123145,0.724571,0.175479,0.923302,0.0764923,0.916502,0.0832807,0.96308,0.023573,0.966016,0.017736,0.986429,0,0.985938,0,0.906522,0.0932464,0.958449,0.0327984,0.986636,0,0.96557,0.0186555,0.91391,0.085906,0.963034,0.0237477,0.905663,0.0941853,0.98539,0,0.98453,0,0.760362,0.239386,0.812851,0.186842,0.578522,0.42123,0.544644,0.455062,0.763289,0.23651,0.545356,0.45443,0.836647,0.163064,0.830925,0.168791,0.646136,0.353538,0.681279,0.318422,0.905317,0.0944304,0.95599,0.0376675,0.955678,0.0383075,0.903848,0.0959111,0.98174,0,0.985154,0,0.829005,0.170711,0.831206,0.168508,0.683241,0.31647,0.698105,0.301605,0.834647,0.165054,0.906052,0.0936923,0.816678,0.183068,0.895193,0.104615,0.718423,0.281276,0.678288,0.321424,0.953772,0.042143,0.976506,0,0.942455,0.0574148,0.9693,0.0112292,0.861709,0.138163,0.922856,0.0770377,0.932119,0.0677676,0.877871,0.121984,0.957443,0.0349568,0.962769,0.0242994,0.800378,0.199448,0.796744,0.203115,0.674039,0.325763,0.712864,0.28698,0.783037,0.216831,0.83972,0.160158,0.771956,0.227914,0.82313,0.176748,0.734529,0.26533,0.733113,0.26675,0.912322,0.0875737,0.952029,0.0457862,0.900221,0.0996742,0.945196,0.0547247,0.820017,0.179863,0.883965,0.115932,0.889952,0.109944,0.816808,0.183072,0.931763,0.0681567,0.938107,0.0618145,0.766871,0.233,0.770722,0.229147,0.727686,0.272177,0.727845,0.272016,0.778431,0.221437,0.824414,0.175467,0.781155,0.218709,0.825996,0.173883,0.73178,0.268075,0.732176,0.267671,0.879862,0.120036,0.925738,0.0741782,0.87566,0.124234,0.920381,0.0795287,0.912524,0.0873716,0.907408,0.0924675,0.961204,0.0274299,0.961081,0.0276926,0.983577,0,0.982733,0,0.962037,0.0257951,0.916093,0.0838182,0.962499,0.0248805,0.91869,0.0812312,0.982171,0,0.980807,0,0.816921,0.182975,0.807994,0.191877,0.634586,0.365299,0.612466,0.387388,0.818854,0.181055,0.621077,0.378818,0.787342,0.212496,0.5482,0.451619,0.860951,0.138927,0.881523,0.118363,0.924748,0.0751479,0.910031,0.0898581,0.95019,0.049423,0.940425,0.0594716,0.898783,0.101115,0.93518,0.0647247,0.958018,0.0337803,0.890393,0.109491,0.833057,0.166813,0.869321,0.130559,0.811768,0.188097,0.931441,0.0684518,0.921446,0.0784442,0.777422,0.222432,0.79587,0.20399,0.725777,0.27406,0.729771,0.270068,0.765342,0.234509,0.72418,0.275657,0.8185,0.18137,0.839661,0.160221,0.740409,0.259436,0.759781,0.240076,0.81043,0.189436,0.861338,0.138543,0.868867,0.131021,0.819833,0.180041,0.911718,0.0881777,0.915165,0.0847378,0.775399,0.224459,0.767373,0.23248,0.729704,0.270138,0.726939,0.2729,0.763171,0.236679,0.805465,0.194399,0.724995,0.274841,0.859524,0.140355,0.913072,0.0868186,0.919156,0.0807659,0.922221,0.0777063,0.959841,0.030194,0.953162,0.0435348,0.977039,0,0.971512,0.00684306,0.944254,0.0556625,0.909317,0.0905932,0.964748,0.0203443,0.851685,0.148214,0.849797,0.150116,0.760237,0.239644,0.68,0.319897,0.831034,0.168882,0.629397,0.370501,0.757443,0.242546,0.863929,0.13605,0.900659,0.09932,0.792556,0.207433,0.917598,0.0823828,0.80964,0.190349,0.89119,0.108775,0.927656,0.0723081,0.954493,0.0409422,0.624019,0.375972,0.724668,0.275323,0.864205,0.135785,0.902111,0.097875,0.514891,0.4851,0.816289,0.183703,0.789242,0.210747,0.681198,0.318791,0.822912,0.177071,0.527779,0.47221,0.922324,0.0776551,0.924188,0.0757788,0.805492,0.194488,0.839515,0.160451,0.712026,0.287954,0.764875,0.235094,0.576287,0.423693,0.594105,0.405868,0.505313,0.494666,0.527863,0.472117,0.545487,0.454489,0.587742,0.412232,0.606612,0.393357,0.525567,0.474412,0.518121,0.481857,0.587675,0.412306,0.541825,0.458161,0.655383,0.344589,0.815145,0.184828,0.802205,0.197755,0.91248,0.0874703,0.901623,0.0983103,0.795759,0.204189,0.792302,0.207637,0.895565,0.104354,0.891948,0.107959,0.552707,0.447255,0.508915,0.491063,0.521993,0.477986,0.564631,0.43533,0.532806,0.467173,0.581115,0.418844,0.52181,0.478172,0.506046,0.493938,0.5036,0.496385,0.748879,0.251059,0.740133,0.259812,0.861502,0.138415,0.854672,0.145255,0.761419,0.238514,0.869958,0.129951,0.740621,0.259329,0.555432,0.444532,0.754145,0.24581,0.612515,0.387452,0.85216,0.147776,0.854352,0.145592,0.507503,0.492474,0.549937,0.450044,0.530876,0.4691,0.602145,0.397834,0.614824,0.385137,0.544218,0.455759,0.53886,0.461118,0.614581,0.385384,0.508567,0.491416,0.500222,0.499758,0.787638,0.212294,0.781298,0.21863,0.887076,0.112825,0.881852,0.118048,0.772842,0.227086,0.605895,0.394064,0.876455,0.123447,0.541783,0.458194,0.509207,0.490777,0.609638,0.390328,0.602141,0.397828,0.62647,0.373501,0.615038,0.384933,0.771673,0.228299,0.739152,0.260821,0.598365,0.40161,0.626839,0.373134,0.803129,0.196845,0.59118,0.408794,0.611257,0.38871,0.693356,0.306619,0.756612,0.243347,0.753577,0.246386,0.853957,0.145996,0.850749,0.149212,0.749684,0.250285,0.748302,0.251673,0.84855,0.151418,0.848041,0.151934,0.998968,0,0.997064,0,0.996832,0,0.998863,0,0.996646,0,0.998777,0,0.992249,0,0.991823,0,0.991469,0,0.999602,0,0.99965,0,0.999851,0,0.999877,0,0.999564,0,0.999822,0,0.999692,0,0.999078,0,0.999721,0,0.999185,0,0.999894,0,0.999902,0,0.997341,0,0.992788,0,0.997639,0,0.993393,0,0.999321,0,0.998183,0,0.997924,0,0.999272,0,0.995027,0,0.994109,0,0.999735,0,0.999725,0,0.999897,0,0.999876,0,0.999677,0,0.999307,0,0.999582,0,0.999192,0,0.999838,0,0.999774,0,0.998366,0,0.995806,0,0.998348,0,0.996239,0,0.998694,0,0.99756,0,0.998061,0,0.99898,0,0.995393,0,0.996137,0,0.999436,0,0.999281,0,0.999672,0,0.999578,0,0.999162,0,0.998397,0,0.999072,0,0.998163,0,0.999512,0,0.999449,0,0.996965,0,0.994328,0,0.996464,0,0.993277,0,0.997998,0,0.998041,0,0.999002,0,0.998948,0,0.999381,0,0.999306,0,0.998892,0,0.997969,0,0.998824,0,0.99793,0,0.999229,0,0.999162,0,0.99606,0,0.996085,0,0.992286,0,0.992388,0,0.996028,0,0.992188,0,0.996194,0,0.992671,0,0.998676,0,0.998562,0,0.999473,0,0.999523,0,0.999752,0,0.999771,0,0.998379,0,0.999361,0,0.999709,0,0.999543,0,0.998734,0,0.999795,0,0.9965,0,0.996368,0,0.991179,0,0.99092,0,0.99622,0,0.996051,0,0.990709,0,0.990539,0,0.997594,0,0.995167,0,0.995361,0,0.997662,0,0.995554,0,0.997725,0,0.990078,0,0.990534,0,0.990987,0,0.998673,0,0.998653,0,0.999021,0,0.99903,0,0.998685,0,0.999009,0,0.998642,0,0.997529,0,0.99868,0,0.997549,0,0.999062,0,0.999125,0,0.995046,0,0.989794,0,0.995066,0,0.989716,0,0.997813,0,0.997872,0,0.998755,0,0.998707,0,0.999086,0,0.999032,0,0.998687,0,0.997765,0,0.999007,0,0.995697,0,0.995815,0,0.991332,0,0.991638,0,0.995932,0,0.991938,0,0.99795,0,0.997715,0,0.998798,0,0.998961,0,0.999237,0,0.999403,0,0.999161,0,0.998169,0,0.999598,0,0.995826,0,0.995555,0,0.990336,0,0.99014,0,0.995275,0,0.989911,0,0.535308,0.464688,0.658836,0.341159,0.614093,0.3859,0.575682,0.424313,0.588284,0.411706,0.598153,0.401837,0.786824,0.213168,0.753304,0.246686,0.732825,0.267164,0.727519,0.272476,0.705764,0.294233,0.838282,0.161712,0.828838,0.171159,0.741089,0.258902,0.844294,0.155697,0.674761,0.325237,0.545296,0.454701,0.636276,0.363721,0.609362,0.390634,0.820539,0.179459,0.811578,0.18842,0.721368,0.278627,0.835061,0.164931,0.781371,0.218623,0.882122,0.11787,0.750991,0.249002,0.792483,0.207507,0.911012,0.0889743,0.880225,0.119766,0.952332,0.0452937,0.935534,0.0644523,0.836993,0.163,0.679475,0.320519,0.91368,0.0863102,0.594771,0.405226,0.54963,0.450365,0.796465,0.203532,0.785701,0.214295,0.531008,0.468985,0.792897,0.207096,0.608925,0.391058,0.589118,0.410865,0.610276,0.389702,0.603452,0.396527,0.738199,0.261783,0.761807,0.238172,0.749129,0.250851,0.749925,0.250059,0.848239,0.151741,0.848496,0.151489,0.74775,0.252237,0.608022,0.391965,0.847502,0.152486,0.581416,0.418571,0.72828,0.271705,0.87705,0.122709,0.871915,0.127831,0.936216,0.0634084,0.939454,0.0602201,0.961846,0.025156,0.963764,0.0214703,0.867551,0.132187,0.932891,0.066708,0.959153,0.0304666,0.939819,0.0599239,0.881962,0.117827,0.934263,0.065536,0.871862,0.127961,0.963872,0.0214708,0.962181,0.0250833,0.743846,0.255921,0.775086,0.224676,0.700867,0.298931,0.765782,0.233989,0.749003,0.25078,0.841662,0.158176,0.633594,0.366211,0.675141,0.324666,0.858922,0.140913,0.925186,0.0746375,0.914108,0.0857262,0.958145,0.0332412,0.953204,0.0431631,0.897408,0.102432,0.800828,0.199005,0.872113,0.127707,0.773824,0.22598,0.946682,0.0531251,0.931748,0.0680577,0.570483,0.429317,0.603022,0.396743,0.691414,0.308397,0.556822,0.443002,0.573128,0.426666,0.738558,0.261247,0.845433,0.154379,0.813475,0.186332,0.912225,0.0875734,0.889669,0.110119,0.782943,0.216856,0.63,0.369819,0.753262,0.246523,0.564137,0.435686,0.869324,0.130442,0.848673,0.151066,0.626845,0.372997,0.662543,0.337307,0.508978,0.490833,0.663243,0.336603,0.666362,0.33349,0.526319,0.473501,0.724379,0.275392,0.697388,0.302368,0.827402,0.172309,0.807532,0.192152,0.669289,0.33045,0.524231,0.475565,0.646945,0.352778,0.537144,0.46264,0.790855,0.2088,0.777546,0.222079,0.659825,0.34001,0.660258,0.339565,0.84983,0.149918,0.827817,0.171974,0.909056,0.0906123,0.924295,0.0752996,0.946134,0.053319,0.951949,0.0448711,0.813406,0.186401,0.884008,0.115713,0.934928,0.0646464,0.930409,0.0691767,0.861343,0.138395,0.955909,0.036934,0.724331,0.275465,0.687705,0.312117,0.682042,0.317812,0.694796,0.305063,0.511561,0.488199,0.546967,0.452807,0.654184,0.345489,0.691803,0.307845,0.795416,0.204073,0.814005,0.18545,0.579343,0.420439,0.617415,0.382281,0.779714,0.219807,0.728744,0.270886,0.555955,0.443792,0.769905,0.229742,0.604313,0.395442,0.840188,0.159253,0.86779,0.131681,0.632433,0.367378,0.646353,0.353459,0.621105,0.378714,0.664625,0.33519,0.673643,0.326175,0.584057,0.415728,0.674877,0.324942,0.66732,0.332499,0.561131,0.438649,0.627306,0.372407,0.606659,0.393052,0.76807,0.231527,0.765295,0.234274,0.600777,0.398931,0.591325,0.408461,0.768097,0.231448,0.677255,0.322564,0.778795,0.221024,0.700981,0.298822,0.823458,0.176268,0.85025,0.149501,0.889241,0.110344,0.902667,0.0969627,0.866098,0.133649,0.79982,0.199994,0.915577,0.0840467,0.679492,0.320367,0.608762,0.391102,0.548714,0.451137,0.997086,0,0.996769,0,0.993232,0,0.993766,0,0.996401,0,0.992721,0,0.994242,0,0.997224,0,0.994159,0,0.997059,0,0.99854,0,0.998479,0,0.999213,0,0.999175,0,0.998445,0,0.999166,0,0.998323,0,0.998102,0,0.999094,0,0.998969,0,0.995333,0,0.997525,0,0.998116,0,0.99644,0,0.998667,0,0.998985,0,0.993074,0,0.990951,0,0.987999,0,0.993737,0,0.984304,0,0.991724,0,0.996659,0,0.998188,0,0.995495,0,0.997328,0,0.988707,0,0.994508,0,0.99476,0,0.989926,0,0.996803,0,0.996897,0,0.980725,0,0.977639,0,0.974173,0.00156768,0.987087,0,0.969549,0.0108136,0.983858,0,0.994002,0,0.996624,0,0.992157,0,0.995875,0,0.977839,0,0.98755,0,0.989349,0,0.980619,0,0.992806,0,0.994031,0,0.964665,0.0205793,0.960329,0.0292446,0.956188,0.0375146,0.975167,0,0.952251,0.0453696,0.97258,0.00480242,0.985886,0,0.99173,0,0.984281,0,0.990703,0,0.995437,0,0.994578,0,0.990558,0,0.991545,0,0.993197,0,0.988847,0,0.992197,0,0.995984,0,0.997818,0,0.99741,0,0.998785,0,0.998497,0,0.996759,0,0.995729,0,0.99803,0,0.997293,0,0.977273,0,0.985719,0,0.984202,0,0.973998,0.00183239,0.98284,0,0.970995,0.00784475,0.990861,0,0.990083,0,0.989448,0,0.957045,0.0357091,0.962848,0.0241084,0.951632,0.0465341,0.968177,0.0134549,0.979959,0,0.972967,0.00388716,0.982667,0,0.987288,0,0.991785,0,0.98889,0,0.992762,0,0.969202,0.0114815,0.970394,0.00913571,0.948876,0.0510487,0.947216,0.0526976,0.948188,0.0517157,0.969429,0.0109946,0.982042,0,0.982068,0,0.98909,0,0.989207,0,0.982893,0,0.989787,0,0.988322,0,0.985475,0,0.977438,0,0.981901,0,0.985911,0,0.991058,0,0.994231,0,0.992414,0,0.996265,0,0.995047,0,0.990568,0,0.993823,0,0.995808,0,0.99708,0,0.997036,0,0.995688,0,0.997119,0,0.99573,0,0.998022,0,0.998013,0,0.998107,0,0.993803,0,0.994029,0,0.991112,0,0.991442,0,0.99377,0,0.990968,0,0.99445,0,0.99608,0,0.995204,0,0.996519,0,0.992016,0,0.9931,0,0.997257,0,0.998139,0,0.997579,0,0.998381,0,0.995187,0,0.996161,0,0.994715,0,0.992964,0,0.992454,0,0.989157,0,0.996948,0,0.996085,0,0.995014,0,0.990827,0,0.994008,0,0.988526,0,0.992797,0,0.985397,0,0.981322,0,0.995976,0,0.996609,0,0.997274,0,0.99762,0,0.995411,0,0.997011,0,0.997191,0,0.997674,0,0.997967,0,0.998285,0,0.991426,0,0.994994,0,0.99507,0,0.99195,0,0.997003,0,0.99693,0,0.986512,0,0.984923,0,0.976518,0,0.972663,0.00461698,0.984174,0,0.991368,0,0.984608,0,0.991878,0,0.970824,0.00829922,0.97093,0.0080922,0.995194,0,0.997238,0,0.995641,0,0.997571,0,0.52648,0.473499,0.538863,0.461111,0.523891,0.476087,0.512611,0.48737,0.561535,0.438436,0.545605,0.454369,0.510521,0.489462,0.523286,0.476694,0.51525,0.484733,0.526981,0.473,0.531869,0.46811,0.536138,0.463838,0.538262,0.461714,0.54368,0.456293,0.533963,0.466015,0.538059,0.461918,0.548052,0.451921,0.56915,0.430819,0.555746,0.444224,0.570562,0.429406,0.64264,0.357319,0.649291,0.350667,0.707253,0.292698,0.714956,0.284998,0.771741,0.228204,0.784239,0.21571,0.631883,0.368074,0.68143,0.31852,0.747432,0.252512,0.704948,0.295012,0.625914,0.374051,0.783909,0.216048,0.577276,0.422695,0.59407,0.405896,0.600256,0.399708,0.590499,0.409465,0.895704,0.104238,0.889524,0.110414,0.928405,0.0715331,0.931492,0.0684496,0.954633,0.040606,0.955738,0.0384042,0.878727,0.12121,0.923421,0.0765148,0.952887,0.0440947,0.932618,0.067332,0.897381,0.102571,0.956161,0.0375734,0.848511,0.151442,0.846103,0.153842,0.836344,0.163597,0.819458,0.180481,0.981307,0,0.98751,0,0.987122,0,0.980987,0,0.986917,0,0.980806,0,0.971314,0.00726538,0.97143,0.00701804,0.971352,0.00721497,0.971247,0.00737603,0.981604,0,0.970831,0.00820426,0.981859,0,0.988083,0,0.988667,0,0.91187,0.088109,0.923726,0.0762493,0.861811,0.138168,0.843939,0.156043,0.938698,0.0612743,0.891968,0.108007,0.834495,0.165489,0.904104,0.0958767,0.835646,0.16434,0.903889,0.0960932,0.946562,0.0534138,0.950832,0.0482829,0.946296,0.0536823,0.95791,0.0341213,0.966645,0.0166449,0.971781,0.0063708,0.980257,0,0.9671,0.0157457,0.953258,0.0434285,0.944959,0.0550156,0.927075,0.0728988,0.985314,0,0.973853,0.00223906,0.952181,0.0455838,0.928692,0.0712809,0.957917,0.0341044,0.888861,0.111113,0.975457,0,0.982714,0,0.988113,0,0.991549,0,0.842936,0.157036,0.764542,0.235429,0.746287,0.253687,0.811054,0.188919,0.667608,0.332364,0.662209,0.337768,0.853909,0.146064,0.892851,0.107123,0.908084,0.0918877,0.847707,0.152262,0.911819,0.0881495,0.846452,0.153513,0.758663,0.241306,0.63719,0.362779,0.752691,0.247273,0.626748,0.373217,0.55279,0.447188,0.553051,0.446924,0.54107,0.458906,0.535936,0.464043,0.551897,0.448076,0.543139,0.456836,0.525146,0.474838,0.543654,0.456328,0.581147,0.418833,0.586882,0.413094,0.573684,0.426288,0.568756,0.431213,0.957597,0.0347888,0.910966,0.0890279,0.91255,0.0874415,0.956928,0.036121,0.913882,0.0861063,0.956557,0.0368563,0.980506,0,0.98118,0,0.979964,0,0.981929,0,0.957849,0.0342913,0.982809,0,0.958336,0.0333196,0.909112,0.0908832,0.907472,0.0925238,0.999967,0,0.999962,0,0.999971,0,0.999975,0,0.999977,0,0.99998,0,0.999934,0,0.999944,0,0.999948,0,0.999957,0,0.999948,0,0.999962,0,0.999933,0,0.999956,0,0.999909,0,0.999938,0,0.99995,0,0.99992,0,0.999932,0,0.999897,0,0.963969,0.0220505,0.909827,0.0901668,0.906855,0.0931407,0.960318,0.0293563,0.984235,0,0.986435,0,0.988787,0,0.96962,0.0107397,0.990758,0,0.976299,0,0.919249,0.0807425,0.934825,0.0651638,0.999986,0,0.999989,0,1,0,0.999982,0,1.00001,0,0.999958,0,0.999946,0,0.999951,0,0.999924,0,0.999927,0,0.999948,0,0.999881,0,0.999959,0,0.999983,0,0.999982,0,0.999946,0,0.982794,0,0.962302,0.0253448,0.951384,0.0471991,0.98108,0,0.991809,0,0.991322,0,0.989605,0,0.981082,0,0.987534,0,0.977517,0,0.964126,0.0216676,0.959568,0.0307462,0.999793,0,0.999757,0,0.999672,0,0.999713,0,0.999717,0,0.99963,0,0.999781,0,0.999853,0,0.999906,0,0.999852,0,0.999797,0,0.999762,0,0.999821,0,0.999775,0,0.999752,0,0.999721,0,0.972367,0.00503111,0.97434,0.0011094,0.98613,0,0.985423,0,0.985032,0,0.971256,0.00723979,0.984678,0,0.970313,0.0091157,0.945314,0.0545819,0.948212,0.051695,0.943179,0.0567081,0.953209,0.0434276,0.999504,0,0.999272,0,0.99935,0,0.999461,0,0.999076,0,0.999225,0,0.999568,0,0.999647,0,0.999653,0,0.999322,0,0.99946,0,0.998934,0,0.999034,0,0.998787,0,0.998606,0,0.99828,0,0.956122,0.0377161,0.9555,0.0389524,0.978809,0,0.979202,0,0.955095,0.0397542,0.978489,0,0.979561,0,0.956374,0.0372166,0.914779,0.085207,0.914789,0.0851943,0.914378,0.0856021,0.914207,0.0857679,0.999864,0,0.999909,0,0.999875,0,0.999838,0,0.999801,0,0.999802,0,0.999756,0,0.99973,0,0.999778,0,0.999855,0,0.99969,0,0.999798,0,0.999678,0,0.999536,0,0.999904,0,0.99986,0,0.959755,0.0302975,0.922055,0.0778572,0.926291,0.07361,0.961912,0.0259585,0.930948,0.0689415,0.964179,0.0214019,0.980816,0,0.979791,0,0.981862,0,0.979024,0,0.958059,0.0337167,0.97867,0,0.957156,0.0355516,0.91952,0.0804046,0.918496,0.0814399,0.998933,0,0.998545,0,0.998735,0,0.999035,0,0.998947,0,0.999166,0,0.997953,0,0.99821,0,0.99853,0,0.999142,0,0.999079,0,0.999242,0,0.999032,0,0.998828,0,0.998996,0,0.998751,0,0.99839,0,0.997774,0,0.9983,0,0.997665,0,0.967673,0.0143841,0.969102,0.0115269,0.984137,0,0.983466,0,0.982702,0,0.966036,0.0176702,0.934578,0.0653042,0.93768,0.062199,0.940567,0.0593133,0.998811,0,0.998745,0,0.998992,0,0.999033,0,0.999112,0,0.99893,0,0.998561,0,0.99839,0,0.997978,0,0.997761,0,0.9983,0,0.997659,0,0.956542,0.0368313,0.957164,0.0355643,0.978799,0,0.978823,0,0.978531,0,0.955471,0.0389908,0.914607,0.0853601,0.916292,0.0836664,0.918117,0.0818305,0.99952,0,0.99939,0,0.999556,0,0.999645,0,0.999134,0,0.999369,0,0.999683,0,0.99956,0,0.999387,0,0.999341,0,0.999177,0,0.998843,0,0.986532,0,0.985474,0,0.990258,0,0.991019,0,0.991053,0,0.991951,0,0.984279,0,0.989524,0,0.99028,0,0.991749,0,0.987409,0,0.992352,0,0.987769,0,0.992938,0,0.993908,0,0.977941,0,0.977003,0,0.977921,0,0.975304,0,0.973033,0.00210401,0.986497,0,0.98862,0,0.991921,0,0.990524,0,0.990791,0,0.993361,0,0.989256,0,0.984545,0,0.988217,0,0.982992,0,0.975721,0,0.978721,0,0.960359,0.00166577,0.96497,0,0.973359,0,0.956869,0.00342086,0.982098,0,0.985631,0,0.970208,0,0.975806,0,0.986785,0,0.97501,0,0.976715,0,0.987578,0,0.992693,0,0.992658,0,0.994668,0,0.995075,0,0.992252,0,0.985593,0,0.991387,0,0.982558,0,0.995142,0,0.994851,0,0.972626,0.00421222,0.965609,0.0182906,0.994981,0,0.992367,0,0.994,0,0.995843,0,0.994579,0,0.996081,0,0.98689,0,0.989923,0,0.99104,0,0.996182,0,0.99576,0,0.996163,0,0.994741,0,0.993095,0,0.989236,0,0.981683,0,0.969108,0.0110134,0.940259,0.0594734,0.953073,0.0433618,0.9762,0,0.988667,0,0.983358,0,0.993898,0,0.991056,0,0.978655,0,0.960234,0.0286275,0.973687,0.000858922,0.951159,0.0466108,0.988024,0,0.985203,0,0.926308,0.0733861,0.912017,0.08763,0.994812,0,0.993086,0,0.991255,0,0.993119,0,0.989327,0,0.991415,0,0.988321,0,0.985868,0,0.983167,0,0.992195,0,0.994285,0,0.990241,0,0.995685,0,0.995936,0,0.99461,0,0.99086,0,0.936893,0.0623942,0.886967,0.112583,0.898833,0.100765,0.943611,0.0557645,0.969112,0.00968975,0.965098,0.0173899,0.982406,0,0.979916,0,0.961604,0.0240438,0.930691,0.068506,0.958594,0.0297172,0.925513,0.0735909,0.977778,0,0.975895,0,0.87656,0.122939,0.868062,0.131385,0.988164,0,0.985382,0,0.983416,0,0.986597,0,0.981431,0,0.985068,0,0.977612,0,0.974799,0,0.971946,0,0.985048,0,0.986649,0,0.983573,0,0.988382,0,0.989765,0,0.987355,0,0.980399,0,0.981583,0,0.98002,0,0.987281,0,0.988019,0,0.988755,0,0.989097,0,0.979387,0,0.986906,0,0.988643,0,0.988803,0,0.983038,0,0.989648,0,0.970614,0.00693036,0.968357,0.0114811,0.965752,0.0168138,0.961682,0.0251255,0.981433,0,0.98197,0,0.987491,0,0.987042,0,0.986841,0,0.981253,0,0.987309,0,0.982299,0,0.970872,0,0.971085,0,0.953895,0,0.953999,0.00178424,0.973036,0,0.957236,0,0.971857,0,0.954877,0.00326465,0.939988,0.0585596,0.930535,0.0680939,0.960412,0.0243318,0.964689,0.0154977,0.975278,0,0.97722,0,0.923086,0.0756348,0.9566,0.0323027,0.973703,0,0.969075,0.00657591,0.947335,0.0511614,0.97596,0,0.954744,0.0375513,0.980025,0,0.984903,0,0.908504,0.0906011,0.892635,0.106499,0.923006,0.0761399,0.87804,0.121148,0.867236,0.132006,0.980176,0,0.980498,0,0.983232,0,0.981898,0,0.982516,0,0.986062,0,0.981002,0,0.980071,0,0.98051,0,0.980395,0,0.972915,0,0.972411,0,0.958576,0,0.957129,0,0.974043,0,0.960773,0,0.972204,0,0.974124,0,0.956416,0,0.959394,0,0.919598,0.0793139,0.859191,0.140149,0.861774,0.137619,0.921601,0.0774064,0.956138,0.0342759,0.954718,0.0367611,0.974253,0,0.973177,0,0.954695,0.0364624,0.920034,0.0787843,0.972896,0,0.861053,0.138237,0.982262,0,0.977453,0,0.975639,0,0.981169,0,0.96616,0,0.963351,0,0.980594,0,0.981198,0,0.98224,0,0.983595,0,0.979416,0,0.969046,0,0.976272,0,0.966994,0.0130268,0.982925,0,0.985455,0,0.987021,0,0.9877,0,0.986519,0,0.979319,0,0.988206,0,0.955942,0.036734,0.947276,0.0520476,0.936002,0.0632751,0.982875,0,0.982803,0,0.987182,0,0.987087,0,0.987074,0,0.98307,0,0.975033,0,0.974947,0,0.961389,0,0.96147,0,0.974684,0,0.960737,0,0.898256,0.0760427,0.88966,0.0778962,0.82014,0.132477,0.828135,0.134881,0.884277,0.0769178,0.817081,0.125486,0.841383,0.131276,0.909492,0.0714724,0.857881,0.122969,0.922829,0.0639221,0.94849,0.0274831,0.940531,0.035129,0.95738,0.0171765,0.93383,0.0397569,0.9291,0.0407841,0.952907,0.0340165,0.937855,0.0536133,0.877144,0.11019,0.898975,0.0931361,0.917266,0.0775659,0.962238,0.0188123,0.922023,0.073632,0.96453,0.0153276,0.981216,0,0.975968,0,0.982996,0,0.967083,0.00422483,0.956464,0.0291231,0.978178,0,0.981963,0,0.96186,0.0203953,0.9167,0.078645,0.909348,0.08451,0.903659,0.0877741,0.950946,0.0369281,0.89775,0.0905929,0.945279,0.0441638,0.974358,0,0.970286,0,0.934432,0.0526504,0.96224,0.00808713,0.966236,0.00379032,0.939814,0.0500808,0.890725,0.0937763,0.882737,0.0972599,0.875464,0.0994956,0.929112,0.0548221,0.870562,0.0991472,0.923709,0.0565548,0.958224,0.0118833,0.954189,0.0150486,0.885282,0.0657538,0.886313,0.0603407,0.828672,0.0905712,0.824592,0.102151,0.890771,0.0529698,0.83839,0.0771724,0.819598,0.11432,0.88342,0.0720185,0.927059,0.0381145,0.926837,0.0334281,0.927062,0.0276598,0.930373,0.017859,0.899363,0.0502906,0.933335,0.016657,0.935777,0.0183335,0.902001,0.0527902,0.938842,0.019377,0.905408,0.0549801,0.853785,0.0794102,0.851886,0.0739705,0.856245,0.0848741,0.851773,0.0686329,0.898705,0.0448528,0.858227,0.0621494,0.903896,0.0361324,0.932398,0.0137734,0.936575,0.00761713,0.913708,0.0571869,0.946275,0.0188504,0.950194,0.0173718,0.918408,0.0574264,0.86486,0.098298,0.861268,0.0948724,0.858739,0.0900958,0.909363,0.0564125,0.942382,0.0196129,0.906453,0.0347119,0.908541,0.0319912,0.865653,0.058773,0.861886,0.0613297,0.852172,0.0669279,0.900626,0.0413863,0.936756,0.00897913,0.939134,0.0060697,0.939708,0.00503524,0.5568,0.443164,0.588318,0.41164,0.624364,0.375589,0.585046,0.414914,0.65241,0.347542,0.689447,0.310499,0.567176,0.43279,0.550328,0.449641,0.543248,0.456725,0.547428,0.45254,0.571114,0.428849,0.631345,0.368615,0.855307,0.144641,0.913925,0.0860246,0.917748,0.0821916,0.866855,0.133085,0.951328,0.0472508,0.950982,0.0479145,0.793766,0.206175,0.769304,0.230644,0.755171,0.244786,0.848315,0.151643,0.912597,0.0873652,0.952829,0.044274,0.98602,0,0.973429,0.00305608,0.97553,0,0.987388,0,0.993345,0,0.992483,0,0.996164,0,0.995765,0,0.989968,0,0.983118,0,0.99389,0,0.971205,0.00747029,0.993998,0,0.988394,0,0.99073,0,0.995065,0,0.992574,0,0.995841,0,0.977609,0,0.98258,0,0.986681,0,0.997284,0,0.996787,0,0.998415,0,0.998179,0,0.9976,0,0.998555,0,0.996206,0,0.992839,0,0.997881,0,0.986147,0,0.973428,0.00310412,0.927608,0.0723791,0.873907,0.126082,0.903696,0.0962951,0.944159,0.0558295,0.930637,0.0693535,0.959089,0.0318008,0.968334,0.0133053,0.958936,0.0320985,0.976577,0,0.950821,0.0483199,0.912174,0.0878104,0.849402,0.150586,0.977578,0,0.985371,0,0.982243,0,0.970782,0.00840788,0.990512,0,0.989252,0,0.950867,0.0482424,0.963965,0.0220365,0.970045,0.00985526,0.980239,0,0.986612,0,0.9909,0,0.996148,0,0.99593,0,0.99732,0,0.997546,0,0.998256,0,0.998416,0,0.997676,0,0.996174,0,0.998554,0,0.993599,0,0.993933,0,0.993887,0,0.998082,0,0.997507,0,0.998417,0,0.998736,0,0.999064,0,0.999282,0,0.997294,0,0.998312,0,0.998889,0,0.99909,0,0.998584,0,0.999398,0,0.999008,0,0.999522,0,0.999703,0,0.998062,0,0.997271,0,0.998677,0,0.996372,0,0.995739,0,0.998576,0,0.998559,0,0.999097,0,0.999116,0,0.999433,0,0.999474,0,0.998096,0,0.998801,0,0.9992,0,0.998665,0,0.997748,0,0.999196,0,0.996261,0,0.997586,0,0.997679,0,0.997033,0,0.998243,0,0.997609,0,0.998107,0,0.998564,0,0.998574,0,0.998887,0,0.999034,0,0.998836,0,0.999476,0,0.999366,0,0.999239,0,0.999567,0,0.998717,0,0.99803,0,0.999262,0,0.997142,0,0.999893,0,0.999697,0,0.999496,0,0.999727,0,0.999636,0,0.999757,0,0.999823,0,0.999974,0,0.999808,0,0.999922,0,0.999823,0,0.999856,0,0.99977,0,0.999878,0,0.999858,0,0.999739,0,0.999826,0,0.999691,0,0.999515,0,0.999563,0,0.999439,0,0.999581,0,0.99978,0,0.999554,0,0.999766,0,0.999888,0,0.999884,0,0.999616,0,0.999832,0,0.999864,0,0.999713,0,0.999452,0,0.999267,0,0.998976,0,0.999466,0,0.998381,0,0.999092,0,0.999787,0,0.999625,0,0.99894,0,0.999476,0,0.999475,0,0.998913,0,0.998138,0,0.998135,0,0.998092,0,0.998954,0,0.997741,0,0.998803,0,0.999481,0,0.999421,0,0.997338,0,0.998342,0,0.998944,0,0.998038,0,0.996542,0,0.995659,0,0.994897,0,0.996754,0,0.994163,0,0.996192,0,0.997895,0,0.997466,0,0.999482,0,0.999686,0,0.999534,0,0.999264,0,0.999306,0,0.998931,0,0.998801,0,0.999124,0,0.998298,0,0.99932,0,0.999614,0,0.999774,0,0.995882,0,0.997123,0,0.996879,0,0.995537,0,0.996731,0,0.995307,0,0.993463,0,0.993953,0,0.9931,0,0.994531,0,0.996282,0,0.99519,0,0.996755,0,0.997434,0,0.997811,0,0.995407,0,0.996853,0,0.997101,0,0.995726,0,0.993524,0,0.993093,0,0.992953,0,0.995262,0,0.996725,0,0.997895,0,0.998656,0,0.99823,0,0.997302,0,0.995927,0,0.996754,0,0.997592,0,0.998468,0,0.999026,0,0.973964,0,0.973355,0,0.973403,0,0.973997,0,0.973398,0,0.973989,0,0.974684,0,0.97469,0,0.975409,0,0.975447,0,0.974654,0,0.97536,0,0.97468,0,0.973909,0,0.974668,0,0.973828,0,0.97548,0,0.975526,0,0.973256,0,0.973106,0,0.973881,0,0.972456,0,0.973166,0,0.974472,0,0.973891,0,0.975073,0,0.975588,0,0.975111,0,0.976522,0,0.976137,0,0.976069,0,0.976904,0,0.974685,0,0.973391,0,0.974362,0,0.973093,0,0.975766,0,0.97544,0,0.971828,0,0.971545,0,0.978782,0,0.978433,0,0.97839,0,0.978736,0,0.978359,0,0.978697,0,0.977995,0,0.977953,0,0.977919,0,0.978981,0,0.979037,0,0.979126,0,0.979185,0,0.97893,0,0.97907,0,0.979088,0,0.978836,0,0.979128,0,0.978884,0,0.979237,0,0.979275,0,0.978485,0,0.978044,0,0.978541,0,0.978108,0,0.97315,0,0.972171,0,0.971775,0,0.973036,0,0.974189,0,0.974142,0,0.97523,0,0.975115,0,0.974188,0,0.973327,0,0.974288,0,0.973518,0,0.975064,0,0.975081,0,0.972517,0,0.972807,0,0.978853,0,0.978545,0,0.978676,0,0.978982,0,0.978826,0,0.979128,0,0.978121,0,0.978257,0,0.978417,0,0.979196,0,0.979071,0,0.979327,0,0.979203,0,0.979337,0,0.979467,0,0.978976,0,0.97876,0,0.978914,0,0.978701,0,0.979107,0,0.979045,0,0.978445,0,0.97801,0,0.978381,0,0.977935,0,0.975328,0,0.974248,0,0.97299,0,0.974391,0,0.972328,0,0.973821,0,0.975616,0,0.97633,0,0.976609,0,0.97717,0,0.975119,0,0.976185,0,0.977142,0,0.976452,0,0.97799,0,0.977587,0,0.977771,0,0.978208,0,0.975678,0,0.977065,0,0.979398,0,0.979114,0,0.979252,0,0.979525,0,0.979388,0,0.979646,0,0.978743,0,0.9789,0,0.979058,0,0.979722,0,0.979603,0,0.979846,0,0.97973,0,0.979832,0,0.979951,0,0.979475,0,0.979267,0,0.979604,0,0.978973,0,0.978583,0,0.977695,0,0.97699,0,0.977298,0,0.977938,0,0.977598,0,0.978172,0,0.978466,0,0.978275,0,0.978656,0,0.978082,0,0.977454,0,0.977888,0,0.977223,0,0.976688,0,0.976407,0,0.976825,0,0.977545,0,0.977434,0,0.976732,0,0.977373,0,0.976697,0,0.97593,0,0.976015,0,0.975909,0,0.976174,0,0.977003,0,0.977702,0,0.976853,0,0.977466,0,0.977521,0,0.976918,0,0.977599,0,0.977005,0,0.976234,0,0.976177,0,0.976313,0,0.976132,0,0.976814,0,0.976089,0,0.976782,0,0.977426,0,0.977396,0,0.978402,0,0.978842,0,0.979374,0,0.978906,0,0.979874,0,0.979332,0,0.979195,0,0.979771,0,0.980347,0,0.978367,0,0.977856,0,0.978738,0,0.97738,0,0.977967,0,0.97702,0,0.977658,0,0.978413,0,0.978754,0,0.97814,0,0.978509,0,0.97967,0,0.979827,0,0.980518,0,0.980339,0,0.981251,0,0.981043,0,0.979932,0,0.98063,0,0.98137,0,0.980091,0,0.979465,0,0.98074,0,0.979016,0,0.979215,0,0.9788,0,0.979022,0,0.979364,0,0.979468,0,0.979183,0,0.979292,0,0.979565,0,0.980386,0,0.980409,0,0.979454,0,0.981071,0,0.981201,0,0.978335,0,0.978634,0,0.978806,0,0.979546,0,0.980214,0,0.980791,0,0.982022,0,0.982298,0,0.98262,0,0.982303,0,0.982441,0,0.982778,0,0.98183,0,0.981612,0,0.981254,0,0.981608,0,0.98185,0,0.981979,0,0.978324,0,0.978578,0,0.978137,0,0.977798,0,0.977691,0,0.977238,0,0.978709,0,0.978306,0,0.977957,0,0.977318,0,0.97795,0,0.976632,0,0.978498,0,0.978782,0,0.978936,0,0.979161,0,0.978979,0,0.979083,0,0.979321,0,0.979408,0,0.979675,0,0.979805,0,0.979595,0,0.979455,0,0.979878,0,0.979674,0,0.979261,0,0.979501,0,0.979675,0,0.979833,0,0.979795,0,0.979938,0,0.979953,0,0.980025,0,0.980049,0,0.98012,0,0.973643,0,0.972672,0,0.972914,0,0.973729,0,0.974672,0,0.974717,0,0.975607,0,0.975728,0,0.974846,0,0.973629,0,0.975909,0,0.97239,0,0.978949,0,0.978658,0,0.978601,0,0.978922,0,0.978288,0,0.978196,0,0.979152,0,0.979158,0,0.979291,0,0.979288,0,0.979155,0,0.97897,0,0.979274,0,0.978715,0,0.978385,0,0.973823,0,0.973216,0,0.97304,0,0.973685,0,0.974399,0,0.974506,0,0.975154,0,0.975239,0,0.974594,0,0.973927,0,0.975305,0,0.973334,0,0.978649,0,0.978332,0,0.978344,0,0.978663,0,0.977908,0,0.977911,0,0.978882,0,0.978876,0,0.979015,0,0.979012,0,0.978893,0,0.978664,0,0.979031,0,0.978339,0,0.977908,0,0.976689,0,0.97598,0,0.975932,0,0.97668,0,0.977351,0,0.977353,0,0.977369,0,0.976736,0,0.976037,0,0.977272,0,0.976577,0,0.976429,0,0.977126,0,0.977709,0,0.977834,0,0.97797,0,0.977443,0,0.976768,0,0.97618,0,0.976929,0,0.976518,0,0.975638,0,0.977562,0,0.977257,0,0.974587,0,0.975269,0,0.975948,0,0.976704,0,0.977299,0,0.97781,0,0.978595,0,0.978998,0,0.978846,0,0.978405,0,0.97934,0,0.979215,0,0.97788,0,0.978115,0,0.978278,0,0.978703,0,0.979084,0,0.979412,0,0.979845,0,0.980008,0,0.979933,0,0.979759,0,0.980112,0,0.980044,0,0.97952,0,0.979624,0,0.97968,0,0.979889,0,0.980042,0,0.980141,0,0.993678,0,0.992247,0,0.990925,0,0.991504,0,0.990007,0,0.988836,0,0.986493,0,0.99011,0,0.982164,0,0.992738,0,0.995201,0,0.994568,0,0.996229,0,0.993969,0,0.995581,0,0.997681,0,0.997344,0,0.996716,0,0.997133,0,0.995816,0,0.996325,0,0.995967,0,0.997634,0,0.994903,0,0.996935,0,0.99749,0,0.997155,0,0.9939,0,0.994285,0,0.996133,0,0.995677,0,0.99308,0,0.99074,0,0.987895,0,0.991785,0,0.984654,0,0.989574,0,0.992302,0,0.990489,0,0.985163,0,0.98733,0,0.988874,0,0.987449,0,0.981311,0,0.977551,0,0.973184,0,0.982865,0,0.968919,0,0.980971,0,0.986073,0,0.985375,0,0.976159,0,0.983377,0,0.984576,0,0.978847,0,0.963999,0,0.956429,0.00661657,0.941932,0.0244654,0.968386,0,0.928004,0.0406562,0.961056,0,0.981405,0,0.978297,0,0.959972,0,0.977666,0,0.97733,0,0.95928,0,0.922165,0.0466334,0.923294,0.0442023,0.928492,0.0372816,0.962032,0,0.93337,0.0313328,0.964361,0,0.978767,0,0.980039,0,0.969365,0,0.982331,0,0.981169,0,0.966733,0,0.936263,0.0283921,0.939902,0.0250135,0.946111,0.0185094,0.972214,0,0.953444,0.0102005,0.975297,0,0.98372,0,0.985316,0,0.983662,0,0.988228,0,0.98696,0,0.979349,0,0.961648,0.000353478,0.970004,0,0.97702,0,0.986441,0,0.989177,0,0.985864,0,0.963632,0.0227078,0.965434,0.0191067,0.987047,0,0.969264,0.0114478,0.98818,0,0.995729,0,0.995195,0,0.996007,0,0.994352,0,0.984962,0,0.993156,0,0.984009,0,0.963757,0.0224499,0.96352,0.022913,0.990416,0,0.976738,0,0.976851,0,0.988327,0,0.971943,0.00603931,0.983893,0,0.993298,0,0.994959,0,0.990715,0,0.995803,0,0.989705,0,0.973666,0.00264063,0.969639,0.0105753,0.928831,0.0711064,0.87478,0.125145,0.945831,0.0540736,0.861375,0.138543,0.919352,0.0805409,0.971944,0.0059172,0.980613,0,0.961878,0.0260038,0.986495,0,0.977521,0,0.958652,0.0325979,0.907028,0.092839,0.852779,0.147104,0.847723,0.152144,0.905033,0.0948236,0.849138,0.15072,0.905784,0.0940659,0.944903,0.0549419,0.947819,0.0520338,0.944588,0.0552528,0.953241,0.0432453,0.910213,0.0896679,0.857619,0.142285,0.91994,0.0799047,0.870191,0.129659,0.88325,0.116606,0.926093,0.0737572,0.895389,0.104474,0.933069,0.0667849,0.953384,0.0429182,0.949497,0.0503438,0.95815,0.0333853,0.946577,0.0532623,0.911624,0.0882203,0.857303,0.142549,0.950669,0.04837,0.921223,0.0786488,0.932499,0.0673748,0.957032,0.0356366,0.942216,0.0576614,0.962828,0.0240473,0.972493,0.00465299,0.968111,0.0134362,0.976681,0,0.963304,0.0230681,0.942091,0.057765,0.90847,0.0913989,0.973499,0.00274079,0.954723,0.0403366,0.957079,0.0356437,0.976414,0,0.957424,0.0349831,0.97837,0,0.985821,0,0.983835,0,0.987303,0,0.980838,0,0.96871,0.0123018,0.949655,0.0502288,0.98243,0,0.960401,0.0290984,0.962147,0.025638,0.983447,0,0.991814,0,0.990388,0,0.988757,0,0.9803,0,0.958602,0.0326609,0.371448,0.28223,0.451898,0.233723,0.490568,0.264691,0.389498,0.326854,0.37944,0.312386,0.328017,0.311793,0.288407,0.261428,0.326874,0.240517,0.266534,0.260734,0.303911,0.261192,0.372771,0.256571,0.342278,0.281761,0.999161,0,0.999202,0,0.999466,0,0.999444,0,0.99962,0,0.999607,0,0.999285,0,0.999519,0,0.999622,0,0.999425,0,0.999153,0,0.999432,0,0.99918,0,0.99958,0,0.999547,0,0.998707,0,0.998709,0,0.998767,0,0.998782,0,0.998921,0,0.998856,0,0.998748,0,0.999094,0,0.999172,0,0.999291,0,0.999356,0,0.998691,0,0.999049,0,0.999223,0,0.999237,0,0.998977,0,0.99931,0,0.999101,0,0.999392,0,0.999405,0,0.998564,0,0.998355,0,0.998766,0,0.99817,0,0.99806,0,0.998836,0,0.998944,0,0.999263,0,0.999178,0,0.999434,0,0.999364,0,0.99906,0,0.999344,0,0.999494,0,0.999095,0,0.99873,0,0.999292,0,0.998068,0,0.998175,0,0.998347,0,0.998544,0,0.999264,0,0.999332,0,0.999513,0,0.999468,0,0.999613,0,0.999579,0,0.999371,0,0.999543,0,0.999639,0,0.999412,0,0.999175,0,0.999539,0,0.99873,0,0.998887,0,0.999006,0,0.999075,0,0.999349,0,0.999263,0,0.999483,0,0.999528,0,0.999595,0,0.999631,0,0.99955,0,0.999377,0,0.999649,0,0.999073,0,0.998997,0,0.998878,0,0.999535,0,0.999586,0,0.999614,0,0.999561,0,0.99962,0,0.999551,0,0.999627,0,0.999657,0,0.999674,0,0.99949,0,0.999463,0,0.999479,0,0.999402,0,0.999476,0,0.999532,0,0.999577,0,0.999687,0,0.999711,0,0.999741,0,0.999718,0,0.99977,0,0.999745,0,0.99973,0,0.999759,0,0.999788,0,0.999691,0,0.99966,0,0.999713,0,0.999614,0,0.999645,0,0.999673,0,0.999694,0,0.999732,0,0.99969,0,0.9997,0,0.999748,0,0.999685,0,0.999738,0,0.999765,0,0.99974,0,0.99978,0,0.999704,0,0.999696,0,0.999657,0,0.999402,0,0.999534,0,0.999411,0,0.999186,0,0.999651,0,0.999612,0,0.998966,0,0.999245,0,0.999407,0,0.999505,0,0.999597,0,0.999676,0,0.999804,0,0.99982,0,0.999832,0,0.999834,0,0.999786,0,0.999758,0,0.999761,0,0.999749,0,0.999734,0,0.999775,0,0.999799,0,0.999803,0,0.999627,0,0.999444,0,0.999136,0,0.999387,0,0.999614,0,0.999726,0,0.999772,0,0.999706,0,0.999621,0,0.996347,0,0.995566,0,0.99727,0,0.991746,0,0.990607,0,0.993327,0,0.994296,0,0.989334,0,0.987984,0,0.990968,0,0.992198,0,0.993899,0,0.995803,0,0.995829,0,0.994073,0,0.993259,0,0.995475,0,0.995708,0,0.993644,0,0.997211,0,0.99843,0,0.998454,0,0.997317,0,0.993499,0,0.994834,0,0.994442,0,0.993233,0,0.99393,0,0.995539,0,0.995214,0,0.993739,0,0.996872,0,0.997898,0,0.997477,0,0.996467,0,0.992715,0,0.993882,0,0.99369,0,0.992526,0,0.993068,0,0.994255,0,0.994051,0,0.992881,0,0.995333,0,0.996285,0,0.996092,0,0.995132,0,0.994094,0,0.994347,0,0.995499,0,0.991253,0,0.99172,0,0.993069,0,0.992713,0,0.992153,0,0.993408,0,0.981996,0,0.984967,0,0.984248,0,0.9832,0,0.98634,0,0.985565,0,0.982552,0,0.989585,0,0.9927,0,0.992133,0,0.988858,0,0.965127,0,0.966274,0,0.96382,0,0.970224,0,0.971322,0,0.968774,0,0.967665,0,0.972449,0,0.973556,0,0.97111,0,0.969937,0,0.97264,0,0.97318,0,0.971075,0,0.977293,0,0.977763,0,0.975437,0,0.974917,0,0.978201,0,0.978616,0,0.976379,0,0.975923,0,0.974346,0,0.976569,0,0.976646,0,0.974433,0,0.974237,0,0.976454,0,0.976504,0,0.974274,0,0.978685,0,0.98091,0,0.98095,0,0.978733,0,0.971814,0,0.974446,0,0.973666,0,0.970994,0,0.97358,0,0.975945,0,0.975223,0,0.972705,0,0.97831,0,0.980642,0,0.980239,0,0.97775,0,0.968954,0,0.971553,0,0.970622,0,0.967816,0,0.970439,0,0.97303,0,0.97236,0,0.969818,0,0.975933,0,0.978476,0,0.975316,0,0.946412,0,0.946391,0,0.946392,0,0.946411,0,0.946459,0,0.946432,0,0.946401,0,0.946427,0,0.946425,0,0.946432,0,0.946411,0,0.946399,0,0.985016,0,0.982294,0,0.980095,0,0.978356,0,0.98031,0,0.982554,0,0.976891,0,0.975616,0,0.976813,0,0.978443,0,0.971677,0,0.971715,0,0.971739,0,0.971712,0,0.971602,0,0.971637,0,0.971681,0,0.971642,0,0.971657,0,0.971659,0,0.971693,0,0.971698,0,0.980202,0,0.981415,0,0.979052,0,0.984485,0,0.986294,0,0.983909,0,0.982395,0,0.988248,0,0.990265,0,0.98707,0,0.985494,0,0.98917,0,0.989097,0,0.992093,0,0.992037,0,0.983313,0,0.983017,0,0.986027,0,0.986227,0,0.982711,0,0.982347,0,0.98557,0,0.985833,0,0.988961,0,0.989043,0,0.991657,0,0.991505,0,0.983662,0,0.983637,0,0.986339,0,0.986321,0,0.983619,0,0.983576,0,0.986375,0,0.986374,0,0.987355,0,0.987898,0,0.990319,0,0.989734,0,0.982375,0,0.982838,0,0.985383,0,0.984895,0,0.983264,0,0.983654,0,0.986262,0,0.985847,0,0.968902,0,0.968897,0,0.968894,0,0.968911,0,0.968908,0,0.968902,0,0.968906,0,0.968903,0,0.968895,0,0.968891,0,0.968897,0,0.969975,0,0.970871,0,0.969112,0,0.974518,0,0.975369,0,0.973003,0,0.972133,0,0.976109,0,0.976782,0,0.97437,0,0.973719,0,0.987259,0,0.983776,0,0.982985,0,0.986537,0,0.988411,0,0.985059,0,0.984453,0,0.987881,0,0.981761,0,0.978626,0,0.977894,0,0.981092,0,0.976092,0,0.977247,0,0.973918,0,0.9728,0,0.982835,0,0.984133,0,0.98067,0,0.979453,0,0.985373,0,0.981863,0,0.964595,0,0.96432,0,0.962059,0,0.968984,0,0.968931,0,0.966612,0,0.966784,0,0.969007,0,0.969183,0,0.966637,0,0.966569,0,0.944908,0,0.944913,0,0.94501,0,0.944821,0,0.944818,0,0.94485,0,0.94485,0,0.944841,0,0.944884,0,0.944923,0,0.944875,0,0.958066,0,0.957967,0,0.957994,0,0.958103,0,0.958034,0,0.957977,0,0.958062,0,0.957994,0,0.957972,0,0.957915,0,0.957934,0,0.961821,0,0.960721,0,0.956687,0,0.956204,0,0.958247,0,0.959039,0,0.955865,0,0.955613,0,0.957372,0,0.957716,0,0.962992,0,0.963302,0,0.96471,0,0.957656,0,0.957743,0,0.960823,0,0.960442,0,0.957493,0,0.957294,0,0.95998,0,0.960352,0,0.959454,0,0.960426,0,0.96253,0,0.955625,0,0.956184,0,0.958358,0,0.957549,0,0.956827,0,0.957548,0,0.960041,0,0.959147,0,0.958498,0,0.958248,0,0.958418,0,0.955474,0,0.955489,0,0.957311,0,0.957256,0,0.955357,0,0.955229,0,0.956974,0,0.95714,0,0.967014,0,0.967734,0,0.968119,0,0.967145,0,0.967427,0,0.967704,0,0.967628,0,0.967155,0,0.967915,0,0.968017,0,0.968029,0,0.967922,0,0.965892,0,0.96541,0,0.967348,0,0.967182,0,0.966313,0,0.966619,0,0.967154,0,0.967182,0,0.966673,0,0.966437,0,0.968308,0,0.967886,0,0.967951,0,0.967826,0,0.967616,0,0.967871,0,0.967668,0,0.967508,0,0.966912,0,0.967281,0,0.968081,0,0.968069,0,0.967946,0,0.968051,0,0.968065,0,0.968055,0,0.968043,0,0.968061,0,0.968027,0,0.968032,0,0.968066,0,0.959013,0,0.959173,0,0.954716,0,0.954524,0,0.956712,0,0.95673,0,0.954381,0,0.954323,0,0.956976,0,0.956828,0,0.958164,0,0.958096,0,0.954876,0,0.954985,0,0.957027,0,0.956834,0,0.954913,0,0.95494,0,0.956883,0,0.956884,0,0.960309,0,0.959705,0,0.962187,0,0.955176,0,0.955048,0,0.957351,0,0.957671,0,0.954937,0,0.954887,0,0.956783,0,0.957006,0,0.959973,0,0.960257,0,0.963307,0,0.954525,0,0.954855,0,0.957457,0,0.957217,0,0.955099,0,0.955307,0,0.95799,0,0.957723,0,0.970348,0,0.970269,0,0.972076,0,0.972135,0,0.966701,0,0.966706,0,0.968485,0,0.968538,0,0.966774,0,0.966838,0,0.968439,0,0.968464,0,0.968238,0,0.968901,0,0.970242,0,0.969344,0,0.965777,0,0.965993,0,0.967519,0,0.967054,0,0.966358,0,0.966727,0,0.968627,0,0.968053,0,0.968121,0,0.96768,0,0.966288,0,0.966054,0,0.9671,0,0.967346,0,0.96584,0,0.965686,0,0.966799,0,0.966903,0,0.969811,0,0.969451,0,0.966716,0,0.966608,0,0.967991,0,0.968209,0,0.96659,0,0.966601,0,0.967781,0,0.967861,0,0.949439,0,0.951057,0,0.951277,0,0.949577,0,0.949183,0,0.950691,0,0.95084,0,0.949298,0,0.952446,0,0.954082,0,0.954315,0,0.952603,0,0.949374,0,0.951025,0,0.950999,0,0.949318,0,0.94951,0,0.951216,0,0.951098,0,0.949427,0,0.953137,0,0.954904,0,0.954977,0,0.95307,0,0.951859,0,0.952133,0,0.954059,0,0.953738,0,0.948742,0,0.948919,0,0.950458,0,0.9502,0,0.949116,0,0.950719,0,0.952206,0,0.952021,0,0.953648,0,0.953746,0,0.948926,0,0.948768,0,0.950199,0,0.95039,0,0.948669,0,0.948569,0,0.94996,0,0.950067,0,0.968009,0,0.968089,0,0.967866,0,0.967691,0,0.968513,0,0.968505,0,0.968304,0,0.968286,0,0.968519,0,0.96854,0,0.968392,0,0.96835,0,0.968321,0,0.968208,0,0.967803,0,0.967936,0,0.968749,0,0.968679,0,0.968492,0,0.96858,0,0.968607,0,0.968541,0,0.968297,0,0.968389,0,0.968813,0,0.968648,0,0.968604,0,0.968764,0,0.968828,0,0.968664,0,0.968671,0,0.968834,0,0.968435,0,0.968107,0,0.968177,0,0.968461,0,0.968599,0,0.968465,0,0.968714,0,0.968563,0,0.96852,0,0.968657,0,0.968419,0,0.968261,0,0.968245,0,0.968386,0,0.947559,0,0.947582,0,0.946456,0,0.94647,0,0.950724,0,0.950785,0,0.94913,0,0.949086,0,0.95088,0,0.950949,0,0.949452,0,0.949258,0,0.944937,0,0.945187,0,0.945166,0,0.944916,0,0.945089,0,0.945363,0,0.945275,0,0.945005,0,0.945758,0,0.946443,0,0.945717,0,0.944882,0,0.945104,0,0.945167,0,0.944938,0,0.944861,0,0.945095,0,0.945058,0,0.944847,0,0.945642,0,0.946502,0,0.945601,0,0.947668,0,0.947622,0,0.946496,0,0.946512,0,0.950801,0,0.950718,0,0.949122,0,0.949163,0,0.950683,0,0.950675,0,0.949064,0,0.949087,0,0.963837,0,0.963976,0,0.965096,0,0.964982,0,0.961504,0,0.961698,0,0.962863,0,0.962686,0,0.961896,0,0.962038,0,0.963137,0,0.963021,0,0.961002,0,0.962118,0,0.962039,0,0.960897,0,0.961181,0,0.962317,0,0.962177,0,0.961073,0,0.963474,0,0.964649,0,0.96445,0,0.96332,0,0.963823,0,0.963633,0,0.964766,0,0.964993,0,0.961414,0,0.961243,0,0.96245,0,0.962639,0,0.961081,0,0.962261,0,0.961919,0,0.963023,0,0.961679,0,0.962855,0,0.962916,0,0.96179,0,0.963999,0,0.965219,0,0.965223,0,0.964025,0,0.946484,0,0.94673,0,0.946818,0,0.946548,0,0.946445,0,0.946651,0,0.946668,0,0.946447,0,0.94714,0,0.94802,0,0.948103,0,0.947186,0,0.946624,0,0.946889,0,0.946938,0,0.946683,0,0.946557,0,0.946828,0,0.946836,0,0.946577,0,0.94737,0,0.948226,0,0.948153,0,0.947354,0,0.946622,0,0.946573,0,0.946479,0,0.946506,0,0.94727,0,0.947158,0,0.94677,0,0.946853,0,0.947099,0,0.947041,0,0.94672,0,0.946744,0,0.946501,0,0.946465,0,0.947025,0,0.947009,0,0.946618,0,0.946666,0,0.947051,0,0.947109,0,0.946655,0,0.94663,0,0.968877,0,0.968875,0,0.968764,0,0.968766,0,0.968837,0,0.968835,0,0.968772,0,0.96879,0,0.968841,0,0.968835,0,0.968878,0,0.968888,0,0.96882,0,0.968854,0,0.968878,0,0.968859,0,0.968885,0,0.968907,0,0.968908,0,0.968895,0,0.968924,0,0.968928,0,0.96892,0,0.968924,0,0.968913,0,0.968919,0,0.968926,0,0.968919,0,0.968922,0,0.96893,0,0.968934,0,0.968928,0,0.968898,0,0.968868,0,0.968838,0,0.968879,0,0.96892,0,0.968909,0,0.968894,0,0.968912,0,0.968877,0,0.968825,0,0.96878,0,0.968848,0,0.957978,0,0.958105,0,0.959104,0,0.959157,0,0.958411,0,0.958274,0,0.959277,0,0.959379,0,0.958813,0,0.958615,0,0.958398,0,0.958744,0,0.958731,0,0.95842,0,0.958421,0,0.958786,0,0.958737,0,0.958389,0,0.959356,0,0.960182,0,0.960116,0,0.959305,0,0.958354,0,0.958286,0,0.958117,0,0.958164,0,0.959218,0,0.959184,0,0.958592,0,0.958668,0,0.95918,0,0.959189,0,0.958521,0,0.958554,0,0.95789,0,0.958157,0,0.958212,0,0.957925,0,0.958036,0,0.958328,0,0.958179,0,0.957924,0,0.959066,0,0.960408,0,0.9605,0,0.958979,0,0.994883,0,0.995814,0,0.993638,0,0.998216,0,0.998535,0,0.997445,0,0.996862,0,0.998805,0,0.999036,0,0.998349,0,0.997932,0,0.99787,0,0.998369,0,0.999283,0,0.99948,0,0.999076,0,0.998749,0,0.999625,0,0.999725,0,0.999507,0,0.999327,0,0.999161,0,0.999169,0,0.999572,0,0.999573,0,0.999705,0,0.9996,0,0.999378,0,0.999506,0,0.99794,0,0.998574,0,0.99827,0,0.997539,0,0.99867,0,0.99915,0,0.998879,0,0.998327,0,0.999721,0,0.999421,0,0.996987,0,0.99778,0,0.997635,0,0.996814,0,0.997349,0,0.998101,0,0.997946,0,0.99717,0,0.996722,0,0.996632,0,0.997503,0,0.997563,0,0.997357,0,0.997429,0,0.996204,0,0.995959,0,0.994432,0,0.997185,0,0.997277,0,0.996904,0,0.997056,0,0.994969,0,0.994628,0,0.996608,0,0.99675,0,0.996204,0,0.996407,0,0.973578,0,0.973216,0,0.97345,0,0.973878,0,0.974497,0,0.973433,0,0.97316,0,0.973762,0,0.972732,0,0.972264,0,0.972224,0,0.972634,0,0.977391,0,0.976667,0,0.973172,0,0.973005,0,0.974609,0,0.975089,0,0.972879,0,0.972807,0,0.973795,0,0.974174,0,0.976892,0,0.977261,0,0.979908,0,0.979405,0,0.973288,0,0.973253,0,0.975021,0,0.974835,0,0.973176,0,0.973217,0,0.975395,0,0.975212,0,0.974499,0,0.975217,0,0.972925,0,0.972923,0,0.974064,0,0.973727,0,0.973016,0,0.973276,0,0.97473,0,0.974414,0,0.971758,0,0.971779,0,0.971506,0,0.971537,0,0.97158,0,0.971548,0,0.971601,0,0.971689,0,0.971789,0,0.971667,0,0.97169,0,0.971677,0,0.972147,0,0.972159,0,0.971535,0,0.971508,0,0.971509,0,0.971531,0,0.971496,0,0.9715,0,0.971555,0,0.971525,0,0.971931,0,0.971833,0,0.972219,0,0.972273,0,0.971728,0,0.971673,0,0.971686,0,0.971771,0,0.971621,0,0.971576,0,0.971572,0,0.971625,0,0.972003,0,0.972017,0,0.972565,0,0.972542,0,0.971734,0,0.971765,0,0.971838,0,0.971819,0,0.971781,0,0.971777,0,0.971846,0,0.971852,0,0.992363,0,0.991022,0,0.986471,0,0.984951,0,0.988103,0,0.989567,0,0.983446,0,0.982002,0,0.985042,0,0.98658,0,0.977996,0,0.975743,0,0.975053,0,0.976975,0,0.980284,0,0.977337,0,0.976495,0,0.979096,0,0.974382,0,0.971443,0,0.971251,0,0.973876,0,0.97252,0,0.972593,0,0.971356,0,0.971582,0,0.97434,0,0.975043,0,0.973835,0,0.973458,0,0.975975,0,0.974422,0,0.973393,0,0.972754,0,0.971975,0,0.972342,0,0.974876,0,0.97382,0,0.973372,0,0.97424,0,0.973459,0,0.973729,0,0.973153,0,0.973093,0,0.965794,0,0.965448,0,0.969812,0,0.969509,0,0.967532,0,0.967862,0,0.969289,0,0.969118,0,0.967036,0,0.967258,0,0.964308,0,0.964889,0,0.969939,0,0.969885,0,0.967491,0,0.967233,0,0.969978,0,0.970129,0,0.968219,0,0.967831,0,0.966696,0,0.965614,0,0.972833,0,0.971877,0,0.968748,0,0.969693,0,0.970985,0,0.970217,0,0.967142,0,0.967881,0,0.975506,0,0.976904,0,0.975561,0,0.974411,0,0.978004,0,0.979872,0,0.978394,0,0.976737,0,0.981417,0,0.982763,0,0.980913,0,0.979723,0,0.985909,0,0.983729,0,0.984351,0,0.986747,0,0.98404,0,0.982207,0,0.983025,0,0.98502,0,0.980087,0,0.980818,0,0.991813,0,0.992613,0,0.987638,0,0.988765,0,0.990682,0,0.989686,0,0.989789,0,0.990702,0,0.992279,0,0.991556,0,0.944971,0,0.945033,0,0.945695,0,0.94574,0,0.945279,0,0.945206,0,0.945802,0,0.945835,0,0.945426,0,0.945361,0,0.950987,0,0.949267,0,0.949322,0,0.951086,0,0.950928,0,0.94922,0,0.949219,0,0.950927,0,0.94773,0,0.947716,0,0.950896,0,0.949454,0,0.951014,0,0.949299,0,0.949382,0,0.950905,0,0.947701,0,0.947702,0,0.945179,0,0.945168,0,0.945836,0,0.945805,0,0.945428,0,0.945443,0,0.945772,0,0.945783,0,0.945411,0,0.945402,0,0.998751,0,0.998475,0,0.997017,0,0.995166,0,0.992789,0,0.989997,0,0.988813,0,0.987541,0,0.986177,0,0.984762,0,0.991672,0,0.991218,0,0.990653,0,0.989064,0,0.971718,0,0.979283,0,0.979731,0,0.980132,0,0.980505,0,0.980851,0,0.946451,0,0.946447,0,0.946469,0,0.946501,0,0.946548,0,0.978873,0,0.977505,0,0.976238,0,0.975172,0,0.974245,0,0.971619,0,0.971672,0,0.971579,0,0.971553,0,0.971535,0,0.971552,0,0.971586,0,0.97161,0,0.971622,0,0.984428,0,0.986411,0,0.988545,0,0.990857,0,0.968906,0,0.96891,0,0.968913,0,0.968915,0,0.968915,0,0.968912,0,0.968906,0,0.968896,0,0.974952,0,0.978231,0,0.98152,0,0.944938,0,0.944883,0,0.94485,0,0.944836,0,0.944811,0,0.94481,0,0.944834,0,0.944875,0,0.958204,0,0.958182,0,0.958189,0,0.958222,0,0.953894,0,0.968035,0,0.952942,0,0.952727,0,0.952524,0,0.95237,0,0.952268,0,0.952945,0,0.952799,0,0.952713,0,0.95278,0,0.952811,0,0.952794,0,0.952787,0,0.952845,0,0.952305,0,0.952412,0,0.95263,0,0.964859,0,0.965237,0,0.947926,0,0.949069,0,0.950557,0,0.952336,0,0.947531,0,0.947648,0,0.947755,0,0.947899,0,0.947795,0,0.947684,0,0.947609,0,0.968705,0,0.968673,0,0.968661,0,0.968664,0,0.968396,0,0.968639,0,0.968804,0,0.96891,0,0.968864,0,0.968811,0,0.968754,0,0.947739,0,0.949231,0,0.950938,0,0.963655,0,0.962463,0,0.961282,0,0.960216,0,0.960363,0,0.960484,0,0.960624,0,0.964005,0,0.962815,0,0.961585,0,0.960331,0,0.960217,0,0.960122,0,0.960039,0,0.999419,0,0.999818,0,0.998608,0,0.973611,0,0.974069,0,0.975327,0,0.983195,0,0.981664,0,0.980216,0,0.973017,0,0.975992,0,0.974926,0,0.973911,0,0.979228,0,0.981291,0,0.983005,0,0.987972,0,0.986827,0,0.985644,0,0.999061,0,0.998439,0,0.999196,0,0.998801,0,0.997739,0,0.999098,0,0.998904,0,0.999266,0,0.99942,0,0.997978,0,0.997645,0,0.998142,0,0.99846,0,0.997243,0,0.997135,0,0.997675,0,0.997772,0,0.997028,0,0.997625,0,0.992967,0,0.99234,0,0.995273,0,0.99568,0,0.966963,0,0.963342,0,0.965267,0,0.968851,0,0.976956,0,0.974339,0,0.975013,0,0.977612,0,0.983024,0,0.983061,0,0.985437,0,0.985427,0,0.981015,0,0.980206,0,0.983586,0,0.984102,0,0.977793,0,0.976725,0,0.981016,0,0.981914,0,0.93598,0,0.935982,0,0.935997,0,0.935997,0,0.984254,0,0.985998,0,0.983062,0,0.981796,0,0.973845,0,0.973837,0,0.973861,0,0.973863,0,0.987616,0,0.984741,0,0.985712,0,0.988811,0,0.993412,0,0.995866,0,0.996026,0,0.993434,0,0.993196,0,0.995258,0,0.995387,0,0.993285,0,0.992547,0,0.994513,0,0.994862,0,0.992915,0,0.956565,0,0.956577,0,0.956625,0,0.956617,0,0.973237,0,0.970276,0,0.971136,0,0.974315,0,0.984732,0,0.983672,0,0.979347,0,0.980627,0,0.98226,0,0.977826,0,0.9609,0,0.956847,0,0.956658,0,0.961129,0,0.922237,0,0.922407,0,0.922445,0,0.922328,0,0.959603,0,0.95962,0,0.959588,0,0.959574,0,0.955318,0,0.959371,0,0.953868,0,0.960135,0,0.963408,0,0.960292,0,0.958026,0,0.961349,0,0.963208,0,0.959465,0,0.952524,0,0.952973,0,0.95527,0,0.953252,0,0.967057,0,0.968658,0,0.969562,0,0.967731,0,0.962668,0,0.962018,0,0.963377,0,0.96534,0,0.967613,0,0.964138,0,0.967903,0,0.969568,0,0.96808,0,0.951916,0,0.957435,0,0.95798,0,0.95229,0,0.951203,0,0.953942,0,0.951997,0,0.951994,0,0.956343,0,0.951334,0,0.953935,0,0.959477,0,0.960225,0,0.954651,0,0.972934,0,0.975154,0,0.972775,0,0.97211,0,0.973988,0,0.97529,0,0.973142,0,0.96837,0,0.968531,0,0.968919,0,0.970842,0,0.972488,0,0.9701,0,0.945777,0,0.946185,0,0.948679,0,0.948282,0,0.946012,0,0.945944,0,0.949201,0,0.949044,0,0.945244,0,0.948253,0,0.944113,0,0.946655,0,0.946575,0,0.943864,0,0.962036,0,0.963205,0,0.963538,0,0.962295,0,0.961296,0,0.962192,0,0.962474,0,0.961571,0,0.961493,0,0.961672,0,0.96274,0,0.962574,0,0.962087,0,0.963258,0,0.927539,0,0.925115,0,0.925115,0,0.92777,0,0.923709,0,0.923733,0,0.925424,0,0.9253,0,0.923582,0,0.923676,0,0.925378,0,0.925343,0,0.927395,0,0.9252,0,0.92517,0,0.927328,0,0.966021,0,0.967224,0,0.967313,0,0.966103,0,0.964928,0,0.964845,0,0.966024,0,0.966066,0,0.965016,0,0.966093,0,0.965954,0,0.96718,0,0.937434,0,0.937613,0,0.93904,0,0.938879,0,0.937618,0,0.937634,0,0.938634,0,0.938735,0,0.936283,0,0.936133,0,0.936124,0,0.936263,0,0.936077,0,0.936088,0,0.956762,0,0.956761,0,0.9566,0,0.956535,0,0.956532,0,0.956601,0,0.957227,0,0.957329,0,0.958198,0,0.95809,0,0.957794,0,0.957999,0,0.95893,0,0.958728,0,0.959982,0,0.9598,0,0.959941,0,0.960166,0,0.960967,0,0.960894,0,0.961589,0,0.961728,0,0.959901,0,0.959752,0,0.959725,0,0.959861,0,0.960708,0,0.960819,0,0.962541,0,0.962381,0,0.999014,0,0.99821,0,0.999333,0,0.999759,0,0.999601,0,0.999668,0,0.999785,0,0.999587,0,0.999466,0,0.99943,0,0.999296,0,0.999401,0,0.999508,0,0.998156,0,0.997526,0,0.997459,0,0.998125,0,0.997933,0,0.996913,0,0.996535,0,0.997781,0,0.997456,0,0.997281,0,0.975406,0,0.975607,0,0.974958,0,0.974771,0,0.979915,0,0.982355,0,0.980921,0,0.979012,0,0.981935,0,0.985143,0,0.982344,0,0.979434,0,0.980323,0,0.974196,0,0.974346,0,0.974148,0,0.974896,0,0.974935,0,0.974204,0,0.974179,0,0.974971,0,0.975008,0,0.974188,0,0.974263,0,0.975043,0,0.974955,0,0.97428,0,0.994338,0,0.996456,0,0.992863,0,0.975126,0,0.974628,0,0.971696,0,0.971716,0,0.97427,0,0.971868,0,0.974481,0,0.973401,0,0.973138,0,0.97419,0,0.962004,0,0.958324,0,0.961624,0,0.963043,0,0.958821,0,0.963953,0,0.96283,0,0.957617,0,0.961231,0,0.982274,0,0.98021,0,0.983423,0,0.985915,0,0.986377,0,0.98317,0,0.996402,0,0.997364,0,0.996648,0,0.922556,0,0.922667,0,0.927845,0,0.927901,0,0.925362,0,0.9278,0,0.925115,0,0.922643,0,0.92235,0,0.922367,0,0.922643,0,0.753745,0.128749,0.805334,0.0945178,0.824295,0.0840525,0.777736,0.115345,0.842662,0.0694021,0.858336,0.0610146,0.844432,0.0705802,0.802001,0.0985025,0.875105,0.0505125,0.729332,0.139072,0.788295,0.101948,0.709266,0.143768,0.774853,0.105409,0.829576,0.0752197,0.819471,0.0782177,0.683825,0.175882,0.64911,0.190008,0.594629,0.239047,0.543572,0.259741,0.617363,0.195836,0.482002,0.271891,0.717103,0.157326,0.749138,0.134334,0.640405,0.213295,0.68351,0.181099,0.899063,0.0393998,0.911746,0.0220826,0.905187,0.0354055,0.890727,0.0535138,0.922727,0.00574953,0.917709,0.0155092,0.894997,0.0508587,0.877432,0.0644284,0.910363,0.0268918,0.903094,0.0246051,0.915153,0.0108584,0.925629,0,0.880925,0.0565214,0.884899,0.0437171,0.858303,0.0714173,0.865811,0.0574271,0.869086,0.0694153,0.851183,0.0845976,0.841841,0.0895841,0.818569,0.110015,0.451839,0.24503,0.412642,0.314531,0.498735,0.288608,0.544272,0.2224,0.395602,0.360305,0.443766,0.361856,0.567344,0.256009,0.610008,0.196238,0.51477,0.322357,0.365075,0.277909,0.328459,0.327694,0.402097,0.295574,0.480337,0.21116,0.385577,0.297986,0.497095,0.21385,0.395909,0.306542,0.57809,0.165101,0.640186,0.144554,0.595855,0.13503,0.654629,0.104282,0.671631,0.142261,0.661939,0.117854,0.684487,0.0942003,0.698471,0.129317,0.629994,0.140962,0.651772,0.102929,0.691524,0.0895083,0.711787,0.1131,0.631926,0.155352,0.623748,0.16114,0.578856,0.189368,0.564318,0.225801,0.586767,0.196247,0.520421,0.274161,0.65751,0.196644,0.616478,0.208531,0.605885,0.284368,0.570389,0.288937,0.567173,0.219218,0.527663,0.295667,0.68703,0.185133,0.709178,0.163228,0.631855,0.279182,0.664008,0.256278,0.423251,0.366961,0.489459,0.320501,0.5454,0.287707,0.42498,0.389859,0.601075,0.243849,0.499411,0.326671,0.436438,0.313173,0.419734,0.353657,0.427979,0.275371,0.364129,0.355544,0.507593,0.267631,0.507345,0.226099,0.480933,0.29308,0.48347,0.316742,0.429704,0.37945,0.918838,0,0.90884,0.00401476,0.904709,0.0110863,0.914248,0,0.892776,0.0209014,0.887738,0.0319253,0.894813,0.0236826,0.905735,0.00410674,0.920768,0,0.910227,0,0.893923,0.0151946,0.921922,0,0.925387,0,0.917261,0.0115141,0.921241,0.00371402,0.916763,0,0.908645,0.00953707,0.911588,0.0204086,0.903334,0.0320443,0.557995,0.262292,0.635069,0.218375,0.577211,0.29471,0.480003,0.364479,0.69986,0.177087,0.655056,0.234323,0.494979,0.387711,0.497872,0.367133,0.591866,0.301908,0.60672,0.189381,0.670242,0.161096,0.726004,0.133248,0.468959,0.30493,0.539188,0.215869,0.377591,0.34393,0.464341,0.240308,0.445864,0.352834,0.590995,0.236761,0.487827,0.259324,0.628456,0.157931,0.796056,0.112638,0.768329,0.145383,0.717934,0.184697,0.753057,0.141523,0.727124,0.184879,0.666895,0.236363,0.830504,0.089787,0.808823,0.114392,0.777271,0.143176,0.810461,0.0878348,0.841348,0.0709797,0.772443,0.108458,0.868587,0.0834034,0.826859,0.120165,0.829938,0.117867,0.865297,0.0857563,0.772174,0.166945,0.78546,0.156499,0.835777,0.111889,0.863629,0.085521,0.799175,0.144543,0.870189,0.0809777,0.828106,0.116754,0.871162,0.0776026,0.829686,0.111301,0.770035,0.165615,0.77082,0.158948,0.89497,0.0582941,0.897698,0.0564303,0.909176,0.0332391,0.913516,0.0298292,0.899968,0.0538598,0.917047,0.0255568,0.889727,0.0612848,0.88445,0.0635487,0.903285,0.0378085,0.89647,0.0424625,0.838553,0.0968855,0.807149,0.128255,0.806809,0.12877,0.840695,0.0953202,0.766535,0.164901,0.76185,0.169053,0.805976,0.130047,0.84034,0.0967573,0.758434,0.172233,0.837632,0.0982354,0.809937,0.126083,0.842082,0.0952782,0.815859,0.121724,0.773261,0.159415,0.780203,0.154281,0.858462,0.072152,0.856569,0.0748717,0.866385,0.0552233,0.865393,0.0565532,0.859499,0.0739951,0.868354,0.0566893,0.860402,0.0713691,0.860805,0.0733733,0.868013,0.056208,0.870129,0.058055,0.726029,0.12909,0.709668,0.140308,0.749962,0.115325,0.761878,0.106762,0.69778,0.149133,0.758882,0.111675,0.792204,0.0891885,0.798364,0.0842564,0.804398,0.0835284,0.662566,0.1665,0.649937,0.177567,0.546031,0.233208,0.578057,0.22371,0.619043,0.198607,0.535374,0.253118,0.704331,0.137813,0.60897,0.186681,0.49867,0.239429,0.772963,0.100598,0.814933,0.0772284,0.979682,0,0.979263,0,0.980381,0,0.980774,0,0.978649,0,0.979786,0,0.981302,0,0.981663,0,0.980723,0,0.978448,0,0.977949,0,0.977047,0,0.976464,0,0.977277,0,0.975712,0,0.980014,0,0.978834,0,0.980268,0,0.979183,0,0.977479,0,0.977925,0,0.981012,0,0.981827,0,0.981153,0,0.981872,0,0.876027,0.0359335,0.883815,0.0315818,0.890946,0.0212118,0.883792,0.0258064,0.891984,0.0257923,0.89843,0.0182469,0.890196,0.0456415,0.883377,0.0503737,0.859455,0.0557276,0.869969,0.0523494,0.830368,0.0734903,0.846032,0.0687477,0.87917,0.0489408,0.857322,0.0656185,0.865449,0.0416221,0.845615,0.0599211,0.85712,0.0466792,0.836415,0.0627473,0.814315,0.0779442,0.874994,0.0319426,0.875214,0.053922,0.867696,0.035858,0.929289,0.0537074,0.964476,0.00152756,0.973774,0,0.946046,0.0365834,0.982125,0,0.987537,0,0.98068,0,0.95908,0.0192505,0.991226,0,0.909131,0.0654212,0.95335,0.0149526,0.886635,0.0775813,0.939971,0.030636,0.976577,0,0.969953,0,0.868939,0.104338,0.836875,0.122017,0.770366,0.191214,0.726447,0.212652,0.803767,0.138032,0.680784,0.231028,0.89767,0.0863759,0.920628,0.0704133,0.812304,0.165703,0.848973,0.139297,0.971147,0.0026875,0.98605,0,0.984877,0,0.970078,0.00514401,0.99373,0,0.992962,0,0.981964,0,0.965833,0.0123807,0.991144,0,0.967655,0.00728827,0.984744,0,0.993223,0,0.943141,0.0534985,0.936345,0.0586119,0.887075,0.108611,0.875918,0.117738,0.942007,0.054802,0.936378,0.0595718,0.886299,0.109395,0.881536,0.112963,0.953392,0.0307329,0.972677,0,0.966702,0.00337828,0.945805,0.0405614,0.984551,0,0.979895,0,0.959337,0.0127287,0.936783,0.0506965,0.974457,0,0.960074,0.021306,0.977849,0,0.988342,0,0.921989,0.0698196,0.929492,0.0647922,0.867923,0.121488,0.875116,0.117315,0.91354,0.0748652,0.903232,0.0805624,0.860929,0.124238,0.851724,0.127554,0.909569,0.0670349,0.93773,0.040144,0.924789,0.0526597,0.891052,0.0780103,0.959512,0.00686159,0.951781,0.0153771,0.909707,0.060672,0.869318,0.0897661,0.942777,0.0247614,0.924758,0.0578139,0.949822,0.0247873,0.967544,0,0.870809,0.0983699,0.889225,0.0882099,0.81527,0.145141,0.836222,0.13505,0.848535,0.110694,0.8212,0.125042,0.788819,0.157881,0.75665,0.173173,0.806422,0.121179,0.861201,0.0864812,0.825538,0.105216,0.768193,0.13967,0.9101,0.0555516,0.88583,0.0689306,0.793184,0.121033,0.737685,0.153194,0.862445,0.0810487,0.840914,0.104001,0.889599,0.071197,0.930897,0.0377111,0.74946,0.157825,0.786719,0.141483,0.681202,0.202927,0.717943,0.189764,0.715396,0.171938,0.688968,0.182047,0.648153,0.213547,0.622746,0.222078,0.709484,0.161246,0.769256,0.128921,0.770452,0.126038,0.699923,0.162917,0.846328,0.0866658,0.85582,0.0802616,0.763304,0.1286,0.668087,0.17737,0.863656,0.0754839,0.7199,0.159176,0.774865,0.128495,0.846482,0.0881833,0.664672,0.185277,0.674752,0.184626,0.614823,0.212382,0.618031,0.217335,0.650044,0.188506,0.613176,0.204801,0.598808,0.214558,0.566517,0.227676,0.668382,0.18179,0.77464,0.125514,0.809119,0.109379,0.711761,0.16234,0.875198,0.070666,0.893105,0.0622746,0.846413,0.0909372,0.756269,0.141158,0.915062,0.0512736,0.650357,0.187715,0.756517,0.133012,0.866874,0.0741578,0.602628,0.214662,0.595496,0.215141,0.561221,0.234815,0.557945,0.233412,0.63186,0.204297,0.67024,0.188971,0.571705,0.235117,0.58257,0.237885,0.840157,0.100691,0.903489,0.0608321,0.923449,0.0495004,0.864712,0.0886548,0.943227,0.0212637,0.958194,0.00383735,0.802788,0.118671,0.87927,0.0739443,0.930003,0.0363028,0.74093,0.161963,0.70882,0.172939,0.615219,0.24208,0.602097,0.23396,0.771722,0.151326,0.639761,0.242887,0.955927,0.0373354,0.954549,0.0398265,0.911199,0.0884592,0.910056,0.0896785,0.954663,0.0393012,0.913546,0.0860339,0.816485,0.183284,0.804226,0.195595,0.831225,0.16849,0.977651,0,0.976325,0,0.988295,0,0.987134,0,0.976294,0,0.986849,0,0.959184,0.0310324,0.980427,0,0.965924,0.0177467,0.984721,0,0.990298,0,0.992838,0,0.912277,0.0875261,0.804534,0.195331,0.920953,0.0789095,0.812664,0.187236,0.986058,0,0.959193,0.0315058,0.975299,0,0.990369,0,0.867876,0.132079,0.917988,0.0819808,0.981332,0,0.991355,0,0.948466,0.0515023,0.975716,0,0.937014,0.0628978,0.834156,0.165774,0.993815,0,0.989815,0,0.996993,0,0.995339,0,0.995452,0,0.995687,0,0.997623,0,0.997657,0,0.987334,0,0.976385,0,0.958178,0.033161,0.980946,0,0.926039,0.0738716,0.899549,0.100296,0.948208,0.0513712,0.976117,0,0.888811,0.110921,0.990586,0,0.981483,0,0.948747,0.0512001,0.992896,0,0.995038,0,0.995394,0,0.997025,0,0.989497,0,0.986964,0,0.99323,0,0.991436,0,0.965682,0.0167204,0.971288,0.00560701,0.941658,0.0577683,0.938015,0.0612839,0.891986,0.107608,0.890028,0.109443,0.98209,0,0.984869,0,0.98791,0,0.989812,0,0.966357,0.0150956,0.981674,0,0.968757,0.00995021,0.982735,0,0.986855,0,0.987084,0,0.941567,0.0576464,0.899799,0.0995892,0.945852,0.0532944,0.909426,0.0899054,0.973875,0,0.971442,0.00432416,0.950109,0.0479174,0.952075,0.0438708,0.912324,0.0869665,0.908471,0.0907933,0.984786,0,0.983839,0,0.986123,0,0.986756,0,0.974858,0,0.98501,0,0.971435,0.00269535,0.983271,0,0.985508,0,0.983913,0,0.949077,0.0497304,0.895697,0.10349,0.940963,0.0577883,0.877319,0.121839,0.96948,0.00682585,0.970437,0.00458479,0.936195,0.0626131,0.931977,0.0669584,0.86191,0.137345,0.855868,0.143484,0.983046,0,0.983031,0,0.983666,0,0.983431,0,0.966964,0.0123573,0.983052,0,0.964833,0.0170201,0.982866,0,0.984306,0,0.985086,0,0.927261,0.0718336,0.851581,0.147863,0.922905,0.0762867,0.843727,0.155775,0.964607,0.0177869,0.919373,0.07988,0.919505,0.0797602,0.964346,0.0184124,0.829071,0.170473,0.825448,0.17411,0.920513,0.078773,0.963415,0.0204645,0.827006,0.172558,0.964521,0.0178544,0.920526,0.0787058,0.835558,0.16397,0.982371,0,0.982655,0,0.98593,0,0.985554,0,0.981892,0,0.981122,0,0.986447,0,0.987003,0,0.959216,0.0296392,0.918404,0.0810208,0.91616,0.083342,0.956612,0.0351367,0.838021,0.161601,0.837101,0.162563,0.961335,0.0249951,0.920452,0.0788964,0.834043,0.165543,0.97856,0,0.979935,0,0.986581,0,0.986941,0,0.977227,0,0.986697,0,0.999274,0,0.999154,0,0.999616,0,0.999668,0,0.998934,0,0.999517,0,0.999834,0,0.999855,0,0.999791,0,0.998458,0,0.998215,0,0.996766,0,0.996252,0,0.997745,0,0.995305,0,0.99931,0,0.998573,0,0.999222,0,0.998512,0,0.997051,0,0.997097,0,0.999675,0,0.999847,0,0.999617,0,0.999813,0,0.99879,0,0.999339,0,0.999014,0,0.99822,0,0.999649,0,0.999473,0,0.998594,0,0.99744,0,0.999257,0,0.999087,0,0.999532,0,0.999769,0,0.997736,0,0.998292,0,0.99572,0,0.996763,0,0.996859,0,0.99572,0,0.994594,0,0.992954,0,0.995041,0,0.997097,0,0.995937,0,0.992737,0,0.998359,0,0.997777,0,0.994551,0,0.990099,0,0.997091,0,0.996446,0,0.997973,0,0.998887,0,0.99112,0,0.993901,0,0.984459,0,0.989648,0,0.98734,0,0.983059,0,0.97841,0,0.971394,0.00703666,0.986071,0,0.987934,0,0.993203,0,0.992071,0,0.996297,0,0.995618,0,0.975894,0,0.979095,0,0.95871,0.032367,0.963948,0.021908,0.984719,0,0.973844,0.00210204,0.98404,0,0.972993,0.00379546,0.955737,0.0383023,0.954878,0.040011,0.991245,0,0.995139,0,0.990791,0,0.994841,0,0.984778,0,0.984091,0,0.990717,0,0.990965,0,0.994741,0,0.994817,0,0.974562,0.000653289,0.97328,0.00321712,0.957945,0.0338706,0.955724,0.0383139,0.985721,0,0.976581,0,0.986934,0,0.978931,0,0.961898,0.025961,0.966517,0.0167194,0.991487,0,0.995092,0,0.992224,0,0.995513,0,0.990154,0,0.988536,0,0.993096,0,0.994015,0,0.995976,0,0.996495,0,0.98391,0,0.981426,0,0.974159,0.00142762,0.970414,0.00892214,0.991588,0,0.986312,0,0.993288,0,0.988894,0,0.97815,0,0.982044,0,0.994986,0,0.997141,0,0.996253,0,0.998024,0,0.996164,0,0.994982,0,0.997395,0,0.997956,0,0.998641,0,0.998855,0,0.992785,0,0.991113,0,0.987402,0,0.98522,0,0.996677,0,0.994,0,0.996909,0,0.994656,0,0.989335,0,0.990748,0,0.99817,0,0.998954,0,0.998281,0,0.999057,0,0.997633,0,0.998888,0,0.999334,0,0.998479,0,0.999492,0,0.999717,0,0.997105,0,0.998457,0,0.999229,0,0.995671,0,0.995014,0,0.992589,0,0.991596,0,0.996905,0,0.994041,0,0.999987,0,0.999976,0,0.999992,0,1,0,0.999962,0,0.999982,0,0.999985,0,0.999987,0,0.999981,0,0.999945,0,0.999934,0,0.999913,0,0.999994,0,0.999947,0,0.999983,0,0.999931,0,1.00001,0,0.999985,0,1.00001,0,0.999983,0,0.99996,0,0.999864,0,0.999912,0,0.999974,0,0.99989,0,0.999736,0,0.999795,0,0.999598,0,0.999996,0,0.999958,0,0.999974,0,0.999954,0,0.999902,0,0.999929,0,1,0,0.99998,0,0.999576,0,0.999161,0,0.999415,0,0.9997,0,0.999392,0,0.998827,0,0.999177,0,0.998441,0,0.999764,0,0.99967,0,0.999845,0,0.999789,0,0.99956,0,0.999723,0,0.999842,0,0.999893,0,0.998797,0,0.998982,0,0.999443,0,0.999335,0,0.999648,0,0.999577,0,0.997694,0,0.998053,0,0.998636,0,0.997408,0,0.998525,0,0.997222,0,0.999246,0,0.999518,0,0.999183,0,0.999475,0,0.998475,0,0.998472,0,0.999147,0,0.999141,0,0.999448,0,0.999437,0,0.997168,0,0.997142,0,0.998527,0,0.997287,0,0.998622,0,0.997487,0,0.999164,0,0.999442,0,0.99921,0,0.999463,0,0.998944,0,0.998768,0,0.999278,0,0.999369,0,0.999498,0,0.999556,0,0.998075,0,0.997758,0,0.999144,0,0.998447,0,0.999395,0,0.99894,0,0.9995,0,0.999642,0,0.999638,0,0.999725,0,0.999652,0,0.999371,0,0.999263,0,0.999566,0,0.999715,0,0.999442,0,0.999779,0,0.999538,0,0.999783,0,0.999827,0,0.999827,0,0.999867,0,0.999874,0,0.999908,0,0.999729,0,0.999784,0,0.999916,0,0.999818,0,0.999675,0,0.999857,0,0.999945,0,0.99988,0,0.99995,0,0.999969,0,0.999963,0,0.999974,0,0.999918,0,0.999942,0,0.740454,0.139687,0.730354,0.135891,0.725839,0.136574,0.739227,0.128616,0.724025,0.120899,0.71153,0.150029,0.706153,0.178115,0.723062,0.167334,0.685503,0.195484,0.717985,0.193508,0.706227,0.198637,0.599536,0.352488,0.623538,0.31711,0.713084,0.172503,0.756182,0.130767,0.737944,0.179189,0.776181,0.113382,0.765615,0.156599,0.67471,0.272839,0.714108,0.238159,0.754171,0.125738,0.737957,0.163037,0.770401,0.127733,0.751027,0.165485,0.841225,0.0855069,0.864915,0.0820903,0.892276,0.0519459,0.857177,0.0862817,0.863468,0.0902195,0.892423,0.0538278,0.891729,0.0739582,0.835322,0.132573,0.900523,0.0505002,0.801541,0.136952,0.812671,0.140244,0.750614,0.195358,0.744554,0.212665,0.809975,0.14861,0.728793,0.233328,0.806281,0.0929093,0.785962,0.133096,0.754247,0.17775,0.807665,0.125201,0.773406,0.188203,0.793,0.137569,0.719641,0.181823,0.734701,0.150058,0.810343,0.109121,0.626234,0.228439,0.635876,0.189654,0.733235,0.124212,0.811763,0.0882062,0.744945,0.196699,0.674703,0.244042,0.6468,0.306005,0.5669,0.371027,0.583466,0.30108,0.47126,0.44565,0.840237,0.107694,0.788563,0.166173,0.697517,0.263102,0.863458,0.0760998,0.86716,0.0568348,0.962871,0,0.962733,0,0.960001,0,0.960031,0,0.962745,0,0.96008,0,0.957051,0,0.957007,0,0.957207,0,0.965396,0,0.965072,0,0.967568,0,0.966985,0,0.964981,0,0.966457,0,0.963065,0,0.965738,0,0.963262,0,0.966053,0,0.968108,0,0.968566,0,0.960116,0,0.956977,0,0.960198,0,0.956945,0,0.833855,0.0650506,0.824882,0.0780946,0.861439,0.0563639,0.868181,0.0456591,0.790406,0.0872755,0.777911,0.107094,0.740359,0.113854,0.720996,0.144042,0.833788,0.0595439,0.790163,0.0786304,0.741923,0.100097,0.868893,0.0379845,0.692592,0.147149,0.66138,0.195511,0.691495,0.170072,0.714431,0.125797,0.619219,0.251023,0.657338,0.220178,0.657509,0.171275,0.620222,0.224588,0.572391,0.285086,0.710175,0.106464,0.682233,0.125603,0.722476,0.1258,0.465457,0.211666,0.56798,0.171807,0.514545,0.244869,0.400477,0.310203,0.434551,0.344122,0.428384,0.313638,0.495376,0.209738,0.59433,0.13269,0.358622,0.275004,0.390462,0.300715,0.363472,0.295425,0.464232,0.238845,0.625132,0.155289,0.54928,0.193799,0.614833,0.185956,0.683187,0.145707,0.68848,0.124752,0.568313,0.253617,0.652654,0.191222,0.52943,0.241895,0.465432,0.334256,0.882996,0.0234252,0.868552,0.05045,0.858379,0.0545442,0.873951,0.0291826,0.851792,0.0558619,0.867645,0.0317384,0.894178,0.0143404,0.881265,0.0384698,0.886848,0.0317188,0.897773,0.0211009,0.88176,0.0544312,0.892436,0.0464151,0.877881,0.040158,0.871916,0.044078,0.873792,0.0581039,0.67771,0.204278,0.585854,0.293326,0.638352,0.220708,0.727618,0.145394,0.479938,0.39753,0.542304,0.308292,0.650501,0.165825,0.736289,0.137872,0.564842,0.231345,0.601369,0.27758,0.499852,0.385696,0.505219,0.385062,0.78307,0.102856,0.703879,0.155508,0.834094,0.0652664,0.849453,0.0798016,0.606009,0.278873,0.674301,0.228754,0.735095,0.176218,0.679018,0.214462,0.785456,0.134774,0.74094,0.163231,0.51272,0.366302,0.599373,0.296622,0.516172,0.337258,0.440988,0.407763,0.407782,0.395674,0.48277,0.3134,0.605352,0.257337,0.684223,0.192939,0.509807,0.303466,0.605013,0.223334,0.933422,0,0.935303,0,0.933253,0,0.931376,0,0.924619,0.00578451,0.922608,0.0127369,0.930357,0,0.932587,0,0.930196,0,0.926416,0,0.925548,0,0.920683,0.00723955,0.92854,0,0.920092,0.0196078,0.924839,0.0040102,0.982833,0,0.983098,0,0.983127,0,0.982896,0,0.983,0,0.982804,0,0.982546,0,0.982835,0,0.982001,0,0.982303,0,0.982348,0,0.982029,0,0.981469,0,0.982458,0,0.982427,0,0.425289,0.266222,0.523984,0.262064,0.489027,0.351573,0.40588,0.376541,0.524027,0.365894,0.553505,0.301111,0.393087,0.274484,0.516507,0.207388,0.305141,0.256252,0.372212,0.272274,0.45063,0.265373,0.604952,0.211762,0.963427,0,0.963409,0,0.966437,0,0.96641,0,0.969204,0,0.969128,0,0.960231,0,0.960164,0,0.956891,0,0.956775,0,0.963378,0,0.960236,0,0.95693,0,0.966278,0,0.96891,0,0.699072,0.300833,0.693055,0.306831,0.824828,0.175081,0.822864,0.177057,0.685446,0.314416,0.824657,0.175238,0.891999,0.107921,0.887052,0.112876,0.896648,0.103262,0.601496,0.39838,0.614837,0.385005,0.635927,0.363875,0.698988,0.300933,0.593282,0.40662,0.690866,0.309068,0.596116,0.403808,0.819124,0.180806,0.886348,0.113586,0.816194,0.183745,0.890217,0.109724,0.682214,0.317746,0.671812,0.328147,0.619535,0.380408,0.685172,0.314775,0.667529,0.332443,0.740027,0.259945,0.592836,0.407143,0.828144,0.171833,0.831521,0.168439,0.844471,0.155498,0.913788,0.0861711,0.929972,0.0699923,0.839685,0.16029,0.934582,0.0653837,0.818469,0.181479,0.900427,0.0995231,0.719527,0.280433,0.846885,0.153058,0.852995,0.146965,0.710205,0.289767,0.712273,0.287673,0.834405,0.16551,0.70624,0.293661,0.822376,0.177461,0.554674,0.445289,0.543876,0.456076,0.744537,0.255417,0.718697,0.281248,0.520458,0.479473,0.705862,0.294073,0.665118,0.334853,0.884349,0.115613,0.722939,0.27679,0.712685,0.28711,0.516721,0.483153,0.548439,0.451385,0.679014,0.320896,0.6168,0.38308,0.818483,0.18114,0.81981,0.179902,0.729195,0.270478,0.82857,0.170986,0.737216,0.262425,0.840469,0.159042,0.545301,0.454475,0.594565,0.405292,0.545986,0.453769,0.606602,0.393237,0.704761,0.294862,0.731033,0.268594,0.536265,0.46349,0.503827,0.495931,0.652994,0.346828,0.694498,0.305314,0.829387,0.170085,0.842678,0.156807,0.652884,0.346744,0.802359,0.197082,0.58178,0.417874,0.762668,0.236797,0.56028,0.439478,0.737036,0.262771,0.638739,0.361021,0.78142,0.218388,0.581893,0.417828,0.500723,0.498979,0.698671,0.3011,0.746796,0.252985,0.812861,0.186952,0.840074,0.159743,0.696575,0.303019,0.726387,0.273143,0.61087,0.388869,0.688482,0.311171,0.60922,0.390533,0.68019,0.319493,0.776807,0.222981,0.865927,0.133894,0.791144,0.208648,0.884617,0.115208,0.616288,0.383481,0.651234,0.348477,0.667793,0.331906,0.607677,0.392084,0.631522,0.368255,0.634077,0.365644,0.64709,0.352696,0.626751,0.372975,0.783789,0.216014,0.782037,0.217773,0.893843,0.105994,0.89003,0.109813,0.787176,0.212645,0.887066,0.112784,0.789364,0.210433,0.89394,0.105892,0.672446,0.327365,0.657789,0.341947,0.638936,0.36079,0.662957,0.336838,0.678094,0.321741,0.657138,0.342626,0.81191,0.187945,0.821251,0.178624,0.890813,0.109068,0.895327,0.10457,0.798564,0.201271,0.886831,0.113033,0.999965,0,0.999984,0,0.99998,0,0.999957,0,0.999973,0,0.999946,0,0.999966,0,0.999983,0,0.999964,0,0.999981,0,0.999919,0,0.999935,0,0.999823,0,0.999867,0,0.999943,0,0.999892,0,0.999864,0,0.999769,0,0.999684,0,0.999311,0,0.999318,0,0.999704,0,0.999953,0,0.999972,0,0.999978,0,0.999961,0,0.999942,0,0.999955,0,0.999928,0,0.999935,0,0.999944,0,0.999936,0,0.999912,0,0.999914,0,0.999923,0,0.999885,0,0.999862,0,0.999816,0,0.999849,0,0.999946,0,0.999901,0,0.999877,0,0.999876,0,0.99991,0,0.999908,0,0.999834,0,0.99983,0,0.999779,0,0.999773,0,0.999871,0,0.999826,0,0.999788,0,0.999766,0,0.999902,0,0.999645,0,0.999714,0,0.999693,0,0.999612,0,0.999617,0,0.999649,0,0.999711,0,0.999582,0,0.999595,0,0.99953,0,0.999552,0,0.999532,0,0.999464,0,0.999473,0,0.999494,0,0.999412,0,0.999378,0,0.999249,0,0.999199,0,0.999511,0,0.999524,0,0.999469,0,0.999512,0,0.999479,0,0.999525,0,0.999362,0,0.99916,0,0.999363,0,0.999548,0,0.999598,0,0.999552,0,0.999505,0,0.999609,0,0.999665,0,0.999677,0,0.999733,0,0.999416,0,0.999471,0,0.999175,0,0.999216,0,0.999539,0,0.99897,0,0.999003,0,0.999274,0,0.99938,0,0.99915,0,0.998984,0,0.998946,0,0.999141,0,0.999784,0,0.999829,0,0.999786,0,0.999736,0,0.999828,0,0.999869,0,0.999869,0,0.999908,0,0.999669,0,0.999718,0,0.999441,0,0.999522,0,0.999763,0,0.999191,0,0.999241,0,0.999591,0,0.999608,0,0.999356,0,0.998943,0,0.998952,0,0.999922,0,0.999956,0,0.999938,0,0.999901,0,0.999936,0,0.999966,0,0.999822,0,0.999843,0,0.999662,0,0.999633,0,0.999277,0,0.999304,0,0.999796,0,0.999066,0,0.999132,0,0.577448,0.353456,0.531639,0.355564,0.567462,0.34811,0.60977,0.338073,0.496426,0.333667,0.516753,0.358894,0.696385,0.209279,0.617224,0.235666,0.544215,0.248494,0.629242,0.333167,0.781865,0.167963,0.708436,0.273174,0.856742,0.115229,0.650595,0.320472,0.689856,0.295381,0.553347,0.438929,0.706931,0.28199,0.722173,0.270099,0.749789,0.244593,0.768849,0.213535,0.852585,0.127326,0.515454,0.475861,0.67694,0.30362,0.562116,0.426341,0.56668,0.410711,0.763089,0.230869,0.760211,0.231815,0.548144,0.432655,0.524904,0.443111,0.523662,0.450682,0.556595,0.427858,0.584935,0.390593,0.543167,0.416419,0.623134,0.34282,0.525977,0.425931,0.770017,0.216158,0.768874,0.212187,0.767661,0.206031,0.762995,0.226642,0.609047,0.326916,0.496948,0.421294,0.493987,0.447248,0.629619,0.324745,0.579441,0.33196,0.47975,0.408551,0.548822,0.334686,0.453607,0.401766,0.732496,0.217041,0.702008,0.228746,0.666391,0.242094,0.754914,0.208825,0.466563,0.3432,0.399845,0.360712,0.425861,0.389218,0.516474,0.334356,0.430659,0.333503,0.364411,0.330098,0.412717,0.313752,0.34852,0.318892,0.585962,0.265331,0.544478,0.27687,0.528112,0.271791,0.627523,0.254325,0.436163,0.278595,0.406373,0.319775,0.380197,0.314574,0.420753,0.290971,0.40159,0.333532,0.484517,0.285874,0.439029,0.298573,0.418042,0.335482,0.543334,0.244696,0.523234,0.243237,0.513673,0.245953,0.53425,0.257733,0.469296,0.266167,0.399515,0.3463,0.384456,0.355294,0.469828,0.261883,0.581418,0.213806,0.516079,0.268916,0.419159,0.291205,0.473072,0.259596,0.350917,0.321912,0.446684,0.296345,0.597361,0.203951,0.524774,0.249743,0.516352,0.258719,0.491662,0.28048,0.521164,0.247561,0.41197,0.315187,0.358017,0.331785,0.466553,0.311136,0.450899,0.348655,0.425981,0.313036,0.383555,0.357532,0.465171,0.308815,0.487419,0.24936,0.462612,0.371401,0.837045,0.0899236,0.796269,0.120395,0.827093,0.101865,0.859718,0.0766618,0.80885,0.103821,0.761678,0.136126,0.778866,0.115244,0.715207,0.154233,0.862116,0.0715967,0.841938,0.0814608,0.88594,0.0535039,0.871521,0.0604882,0.823008,0.0882507,0.880812,0.0605268,0.899678,0.0411346,0.526921,0.18883,0.502722,0.23142,0.551656,0.225175,0.581772,0.156092,0.44106,0.329974,0.493314,0.316197,0.583055,0.218666,0.609524,0.14989,0.511819,0.330034,0.437876,0.287176,0.431714,0.260186,0.380649,0.333221,0.521535,0.203734,0.431419,0.30174,0.495108,0.222894,0.415168,0.314973,0.579963,0.15012,0.603253,0.188284,0.550122,0.194752,0.553851,0.276323,0.631818,0.208507,0.580701,0.280594,0.60219,0.246276,0.631586,0.182943,0.504956,0.375487,0.530975,0.340619,0.603899,0.223653,0.619456,0.156329,0.532235,0.32708,0.607172,0.241265,0.558761,0.309126,0.577544,0.276071,0.532681,0.338193,0.482711,0.40406,0.463815,0.42398,0.645826,0.165711,0.642706,0.169418,0.609943,0.267326,0.620819,0.2661,0.626072,0.191848,0.616806,0.268243,0.628728,0.187018,0.602641,0.213193,0.586328,0.26915,0.54615,0.290967,0.534137,0.315214,0.473472,0.397456,0.501107,0.371364,0.552392,0.303613,0.486104,0.401521,0.460715,0.427214,0.523935,0.315565,0.469323,0.393464,0.526005,0.301408,0.470785,0.385982,0.491506,0.392167,0.491126,0.38956,0.591004,0.206543,0.578175,0.205846,0.586923,0.282993,0.57648,0.28959,0.57024,0.219592,0.562621,0.306016,0.607387,0.207774,0.601713,0.276283,0.557737,0.249799,0.57401,0.218903,0.517735,0.328457,0.50083,0.347314,0.592323,0.192413,0.544386,0.296536,0.46284,0.404805,0.447354,0.420598,0.476967,0.391602,0.571655,0.246611,0.576158,0.26943,0.517898,0.382821,0.511709,0.404555,0.588081,0.26365,0.52244,0.388281,0.538701,0.27623,0.566279,0.236851,0.534035,0.347707,0.479371,0.37255,0.46808,0.406852,0.62074,0.169765,0.620378,0.155713,0.594711,0.220768,0.591534,0.231787,0.508105,0.350216,0.502583,0.362589,0.610479,0.231772,0.611137,0.214168,0.558072,0.327432,0.559282,0.307758,0.609871,0.180396,0.603016,0.246402,0.551149,0.347214,0.573787,0.256632,0.493098,0.374457,0.560589,0.152953,0.533564,0.229996,0.473373,0.235224,0.496689,0.205604,0.473416,0.330557,0.414003,0.343656,0.606563,0.14646,0.578982,0.222784,0.511032,0.326516,0.553145,0.179431,0.581175,0.222266,0.505959,0.25368,0.525602,0.288545,0.486772,0.214114,0.451803,0.21256,0.636043,0.212366,0.73632,0.152037,0.722956,0.179133,0.603539,0.266549,0.809607,0.108203,0.804057,0.122221,0.713099,0.204749,0.575862,0.31491,0.801704,0.134573,0.647353,0.179215,0.744742,0.128858,0.678477,0.142338,0.765723,0.101779,0.816432,0.0916649,0.833506,0.0713663,0.53598,0.28126,0.565828,0.223742,0.464035,0.329719,0.510088,0.255474,0.605155,0.175371,0.550316,0.200552,0.46516,0.372511,0.465498,0.390025,0.429681,0.390181,0.382647,0.227402,0.338864,0.227257,0.286945,0.27708,0.33209,0.288531,0.323204,0.276511,0.340178,0.289051,0.428616,0.20813,0.385448,0.231851,0.454934,0.229301,0.413264,0.253729,0.446319,0.217095,0.490274,0.182099,0.535056,0.185206,0.564745,0.153506,0.512459,0.20162,0.58089,0.170238,0.398012,0.281328,0.358018,0.342186,0.498242,0.239971,0.452668,0.308243,0.422763,0.257611,0.375522,0.329637,0.392786,0.32879,0.456284,0.24729,0.38806,0.243627,0.357809,0.311608,0.452741,0.269105,0.398646,0.315128,0.418969,0.378944,0.419481,0.362222,0.503238,0.231115,0.485446,0.328404,0.470266,0.221162,0.417096,0.253992,0.423206,0.263342,0.466479,0.236473,0.460251,0.194116,0.398186,0.226026,0.443115,0.238095,0.371233,0.243694,0.535614,0.188606,0.523011,0.180279,0.605017,0.155701,0.58918,0.156485,0.501279,0.221839,0.524067,0.206645,0.590577,0.173101,0.577083,0.357896,0.710064,0.238074,0.711851,0.247625,0.540149,0.407634,0.809169,0.15075,0.821393,0.147902,0.721704,0.246754,0.540903,0.415539,0.836745,0.140301,0.588431,0.329437,0.712879,0.221918,0.803387,0.145303,0.51533,0.399236,0.503345,0.384061,0.540127,0.384181,0.52613,0.405085,0.549405,0.417797,0.754061,0.225603,0.766846,0.215314,0.555932,0.412769,0.869253,0.117845,0.882692,0.106876,0.774768,0.208522,0.564173,0.405933,0.88849,0.10197,0.544505,0.41872,0.737734,0.237443,0.853153,0.129841,0.509888,0.422259,0.516954,0.416131,0.502386,0.429427,0.492909,0.440976,0.577789,0.391817,0.575128,0.3964,0.78068,0.202687,0.779039,0.202148,0.888207,0.101856,0.886808,0.10174,0.474684,0.469519,0.478784,0.460481,0.612038,0.355709,0.494881,0.45393,0.658928,0.305691,0.508095,0.439359,0.773121,0.203682,0.877645,0.107435,0.787939,0.186046,0.876045,0.105792,0.665335,0.279915,0.668529,0.287018,0.79116,0.177031,0.785236,0.175038,0.873311,0.104423,0.865139,0.10681,0.485318,0.439321,0.496304,0.440139,0.657566,0.275954,0.471353,0.437871,0.651849,0.268461,0.449689,0.436651,0.776366,0.174883,0.854168,0.110648,0.771474,0.170546,0.85188,0.106707,0.719554,0.110537,0.708726,0.136882,0.790718,0.096315,0.795652,0.0813735,0.698491,0.165687,0.785128,0.113663,0.856384,0.0651631,0.857023,0.0576384,0.855695,0.073368,0.629901,0.147123,0.611176,0.185401,0.590193,0.228371,0.726032,0.0947754,0.646419,0.123535,0.726682,0.105355,0.653849,0.137276,0.798558,0.0701414,0.858452,0.0507017,0.799928,0.0751578,0.86249,0.0501945,0.6793,0.213611,0.660557,0.246163,0.773903,0.158634,0.77865,0.143801,0.852889,0.0997009,0.853607,0.0922997,0.533832,0.318808,0.478619,0.390183,0.690394,0.188964,0.568565,0.268233,0.782048,0.129138,0.854733,0.0830083,0.742377,0.095392,0.80835,0.0684452,0.791829,0.0800467,0.721231,0.106178,0.864505,0.0499766,0.851863,0.0578151,0.738804,0.103304,0.809797,0.0718827,0.868668,0.0446962,0.675372,0.123956,0.664072,0.137684,0.653112,0.132395,0.730754,0.125579,0.753163,0.122452,0.807304,0.0922462,0.787378,0.0969709,0.668863,0.158959,0.684297,0.158835,0.719929,0.13545,0.64157,0.184758,0.769923,0.104995,0.978522,0,0.9776,0,0.978434,0,0.978976,0,0.976622,0,0.97777,0,0.978912,0,0.979282,0,0.97845,0,0.977338,0,0.975981,0,0.974725,0,0.972972,0,0.974639,0,0.971163,0,0.979045,0,0.978464,0,0.979366,0,0.979089,0,0.976888,0,0.978568,0,0.979349,0,0.979551,0,0.97956,0,0.979704,0,0.979562,0,0.979538,0,0.979649,0,0.979653,0,0.979767,0,0.97977,0,0.97947,0,0.979387,0,0.979284,0,0.979069,0,0.979492,0,0.979403,0,0.979276,0,0.979593,0,0.979722,0,0.940636,0,0.939818,0,0.942229,0,0.942949,0,0.944668,0,0.945152,0,0.939155,0,0.938473,0,0.937619,0,0.937152,0,0.942041,0,0.940155,0,0.943355,0,0.941828,0,0.938554,0,0.939965,0,0.943823,0,0.945689,0,0.944725,0,0.946269,0,0.8813,0.0478206,0.884834,0.0380494,0.9099,0.0106586,0.907993,0.0154277,0.919579,0.000838567,0.91887,0.00201136,0.836651,0.076822,0.83986,0.0684967,0.793337,0.103798,0.795499,0.0924747,0.874275,0.0574033,0.831855,0.0866786,0.865523,0.068579,0.826787,0.0973178,0.790133,0.115585,0.786298,0.127769,0.9042,0.0235594,0.917458,0.00575102,0.897532,0.0383289,0.914795,0.0123419,0.531272,0.274015,0.509914,0.325601,0.441448,0.380043,0.467894,0.315138,0.472924,0.385601,0.453034,0.395444,0.58616,0.237332,0.569201,0.278894,0.63435,0.205251,0.621504,0.238299,0.541277,0.326594,0.60168,0.275178,0.538178,0.229785,0.593173,0.201097,0.640528,0.175686,0.473835,0.261777,0.945716,0,0.944954,0,0.943467,0,0.944531,0,0.944082,0,0.942189,0,0.946967,0,0.946398,0,0.948337,0,0.947902,0,0.945735,0,0.94738,0,0.946552,0,0.94759,0,0.94763,0,0.948439,0,0.948804,0,0.949461,0,0.945616,0,0.946918,0,0.857526,0.0854972,0.85971,0.0781709,0.88977,0.054083,0.885433,0.0622507,0.909242,0.0267354,0.905052,0.0416146,0.82165,0.115498,0.822592,0.107878,0.779613,0.150842,0.782148,0.140275,0.860037,0.0885955,0.824325,0.119392,0.866353,0.0875288,0.830037,0.119978,0.779443,0.158587,0.782297,0.162938,0.887022,0.0653845,0.906336,0.0472076,0.892992,0.0638205,0.911668,0.0440966,0.51043,0.379193,0.556946,0.341984,0.612309,0.277232,0.569109,0.31186,0.595114,0.310662,0.646561,0.248561,0.455404,0.441364,0.490697,0.415929,0.535895,0.367605,0.502478,0.410903,0.53202,0.382433,0.467028,0.454662,0.451818,0.424387,0.501922,0.382079,0.571897,0.319593,0.515933,0.351715,0.954291,0,0.953134,0,0.951674,0,0.953086,0,0.952362,0,0.950769,0,0.950552,0,0.952231,0,0.955925,0,0.954875,0,0.957811,0,0.95678,0,0.954145,0,0.956031,0,0.955984,0,0.957342,0,0.958216,0,0.95918,0,0.959074,0,0.960908,0,0.955085,0,0.954499,0,0.957592,0,0.886775,0.0770048,0.852702,0.109367,0.839042,0.117055,0.875197,0.0837559,0.804584,0.154719,0.789918,0.162358,0.900902,0.0674331,0.871519,0.0963868,0.916776,0.0557599,0.895962,0.0773822,0.827631,0.138708,0.865144,0.108961,0.909795,0.0547881,0.920619,0.0450126,0.924724,0.0300046,0.932729,0.0194399,0.931458,0.0300012,0.942241,0.00614958,0.900613,0.0601995,0.917743,0.0381627,0.645693,0.274119,0.657992,0.270774,0.706743,0.210183,0.692431,0.21475,0.657766,0.280705,0.71296,0.215881,0.580956,0.348502,0.589848,0.348067,0.496873,0.440389,0.50008,0.445283,0.585697,0.361009,0.48876,0.464645,0.624768,0.287611,0.561576,0.36015,0.481716,0.447664,0.672494,0.227794,0.626309,0.331648,0.52417,0.43796,0.504159,0.463485,0.57826,0.385803,0.559806,0.406758,0.623552,0.348276,0.58099,0.390865,0.515828,0.452367,0.709202,0.267373,0.648017,0.300826,0.564962,0.390123,0.509651,0.45085,0.695796,0.256816,0.709241,0.232122,0.66902,0.291755,0.629312,0.335777,0.651929,0.318831,0.775071,0.203155,0.836026,0.143148,0.740901,0.228825,0.855448,0.128421,0.889447,0.0954692,0.860009,0.117543,0.784512,0.179888,0.901568,0.0830178,0.545371,0.424901,0.682461,0.29291,0.793673,0.187322,0.495091,0.467374,0.572541,0.393038,0.588563,0.367193,0.656402,0.28832,0.813013,0.135242,0.87758,0.0894355,0.894805,0.0633498,0.837655,0.0953714,0.915952,0.0616456,0.925884,0.0396555,0.741876,0.146271,0.798702,0.158679,0.867059,0.106035,0.906819,0.0751679,0.706358,0.206225,0.685527,0.244908,0.717512,0.151086,0.711278,0.171251,0.668588,0.202633,0.677922,0.176593,0.701806,0.193342,0.654258,0.231655,0.754977,0.127546,0.751013,0.142913,0.745533,0.158975,0.720686,0.1325,0.757159,0.112999,0.682526,0.153021,0.675594,0.24137,0.660354,0.265678,0.58569,0.334123,0.610126,0.300139,0.648844,0.285465,0.563772,0.364442,0.731196,0.192494,0.725042,0.207065,0.722308,0.217679,0.689612,0.217073,0.738566,0.17593,0.6343,0.264717,0.654663,0.295536,0.672331,0.285482,0.565213,0.386715,0.553365,0.390753,0.698038,0.266636,0.589965,0.369303,0.739192,0.216161,0.761528,0.201335,0.788678,0.180919,0.647106,0.295218,0.7263,0.221386,0.553034,0.383172,0.77782,0.198574,0.832129,0.14972,0.737494,0.239166,0.674898,0.296472,0.885381,0.101023,0.811204,0.170711,0.85499,0.126149,0.8996,0.0866683,0.908534,0.0765484,0.935658,0.0531676,0.930267,0.0589971,0.947705,0.0351941,0.731776,0.238972,0.816705,0.15873,0.881608,0.0983128,0.628449,0.337301,0.932455,0.0571729,0.936565,0.0531503,0.919012,0.0689543,0.907222,0.080363,0.939998,0.049073,0.924718,0.0629712,0.945046,0.0410272,0.947717,0.0359563,0.953534,0.0249485,0.956214,0.0200074,0.951246,0.0289792,0.960495,0.0117372,0.920731,0.068153,0.941006,0.0484809,0.951264,0.0290018,0.874062,0.111716,0.955201,0.0147761,0.961577,0,0.94777,0.0116214,0.93983,0.0386467,0.913553,0.0286478,0.965376,0,0.969844,0,0.97164,0,0.974701,0,0.945854,0.036563,0.957425,0.0161215,0.966305,0,0.930434,0.0562771,0.949581,0,0.948456,0,0.946183,0,0.947557,0,0.946755,0,0.944107,0,0.943925,0,0.945544,0,0.941511,0,0.951582,0,0.950693,0,0.953496,0,0.952819,0,0.949373,0,0.95186,0,0.950332,0,0.952198,0,0.95085,0,0.952641,0,0.953997,0,0.95439,0,0.94847,0,0.946643,0,0.949083,0,0.95032,0,0.950138,0,0.953365,0,0.953504,0,0.947547,0,0.947238,0,0.950535,0,0.947884,0,0.950768,0,0.948256,0,0.953612,0,0.953714,0,0.951244,0,0.94914,0,0.948666,0,0.95098,0,0.951652,0,0.949743,0,0.953956,0,0.954154,0,0.953823,0,0.980198,0,0.981216,0,0.980684,0,0.979675,0,0.979938,0,0.978931,0,0.980492,0,0.98152,0,0.978949,0,0.979223,0,0.977463,0,0.97771,0,0.978444,0,0.977709,0,0.97699,0,0.976286,0,0.976715,0,0.977784,0,0.97637,0,0.975235,0,0.974747,0,0.973509,0,0.977951,0,0.978975,0,0.975474,0,0.976728,0,0.974087,0,0.975327,0,0.97393,0,0.972101,0,0.972541,0,0.970664,0,0.969597,0,0.967932,0,0.965611,0,0.967738,0,0.967002,0,0.964424,0,0.963251,0,0.96592,0,0.971205,0,0.96977,0,0.972478,0,0.971118,0,0.968929,0,0.970323,0,0.971566,0,0.972959,0,0.974075,0,0.970001,0,0.968448,0,0.967716,0,0.967387,0,0.966496,0,0.966877,0,0.967072,0,0.966136,0,0.965726,0,0.966166,0,0.965326,0,0.968627,0,0.968351,0,0.969545,0,0.969324,0,0.968085,0,0.969111,0,0.968017,0,0.968882,0,0.96824,0,0.969088,0,0.969755,0,0.969942,0,0.967239,0,0.966605,0,0.967485,0,0.966958,0,0.96653,0,0.966665,0,0.965722,0,0.965575,0,0.966813,0,0.965904,0,0.964879,0,0.964726,0,0.965074,0,0.967573,0,0.967659,0,0.968612,0,0.968643,0,0.96775,0,0.968676,0,0.966459,0,0.967518,0,0.966446,0,0.967514,0,0.968591,0,0.968603,0,0.965478,0,0.964613,0,0.965447,0,0.964575,0,0.945937,0.0161191,0.950282,0.0044037,0.957899,0,0.956044,0,0.954153,0,0.959779,0,0.963923,0,0.963382,0,0.964571,0,0.932713,0.0369468,0.941345,0.0167972,0.948087,0.00161739,0.942032,0.0278504,0.922647,0.0552167,0.941138,0.0345003,0.915847,0.0643152,0.954615,0.00598017,0.963178,0,0.954697,0.0102076,0.963668,0,0.966134,0.00012907,0.969216,0,0.96319,0.00644359,0.959982,0.0124394,0.972452,0,0.967521,0,0.971122,0,0.973799,0,0.976437,0,0.963056,0.00568511,0.96847,0,0.960285,0.0103815,0.965877,0,0.957479,0.0168614,0.954688,0.021559,0.978279,0,0.978465,0,0.977229,0,0.975981,0,0.978955,0,0.979022,0,0.976236,0,0.978274,0,0.9723,0,0.974388,0,0.974783,0,0.974718,0,0.974377,0,0.975086,0,0.974986,0,0.974663,0,0.974362,0,0.974901,0,0.974463,0,0.974904,0,0.974715,0,0.975143,0,0.975224,0,0.975451,0,0.973905,0,0.973884,0,0.973357,0,0.973221,0,0.97411,0,0.973241,0,0.97396,0,0.973989,0,0.973472,0,0.97354,0,0.971703,0,0.972308,0,0.968908,0,0.968508,0,0.973067,0,0.969629,0,0.973671,0,0.974309,0,0.975008,0,0.975625,0,0.974998,0,0.976241,0,0.971276,0,0.973141,0,0.970985,0,0.972722,0,0.974414,0,0.973953,0,0.968339,0,0.968325,0,0.975509,0,0.975365,0,0.975165,0,0.975334,0,0.975137,0,0.974909,0,0.974961,0,0.975152,0,0.974682,0,0.975705,0,0.975579,0,0.975943,0,0.975835,0,0.975382,0,0.975669,0,0.975586,0,0.97577,0,0.975602,0,0.975777,0,0.975998,0,0.976,0,0.975426,0,0.975264,0,0.975452,0,0.975305,0,0.973892,0,0.974513,0,0.973266,0,0.972748,0,0.975239,0,0.973965,0,0.972635,0,0.97213,0,0.975451,0,0.975995,0,0.976603,0,0.973315,0,0.974865,0,0.97207,0,0.971355,0,0.964025,0,0.965043,0,0.965268,0,0.964355,0,0.966159,0,0.966283,0,0.96545,0,0.964628,0,0.966367,0,0.963632,0,0.964737,0,0.963104,0,0.964273,0,0.96594,0,0.965555,0,0.963164,0,0.962681,0,0.96247,0,0.961909,0,0.962093,0,0.961262,0,0.963578,0,0.963922,0,0.962952,0,0.96335,0,0.960881,0,0.961661,0,0.961715,0,0.960885,0,0.962299,0,0.962403,0,0.961849,0,0.960976,0,0.962578,0,0.960966,0,0.961691,0,0.961112,0,0.961786,0,0.962281,0,0.962331,0,0.960019,0,0.960149,0,0.959082,0,0.959266,0,0.96034,0,0.9595,0,0.959959,0,0.95999,0,0.958952,0,0.958903,0,0.9647,0,0.965268,0,0.966342,0,0.965962,0,0.965724,0,0.966669,0,0.967519,0,0.967312,0,0.967716,0,0.963628,0,0.964352,0,0.962766,0,0.963608,0,0.964915,0,0.964255,0,0.963969,0,0.962678,0,0.962983,0,0.961401,0,0.961674,0,0.960241,0,0.965473,0,0.967063,0,0.964829,0,0.966748,0,0.973264,0,0.972649,0,0.972396,0,0.972978,0,0.971893,0,0.971687,0,0.972149,0,0.972685,0,0.971498,0,0.973536,0,0.972914,0,0.973803,0,0.973188,0,0.972137,0,0.972416,0,0.973758,0,0.974037,0,0.974155,0,0.974434,0,0.974299,0,0.973458,0,0.973134,0,0.973849,0,0.973503,0,0.974896,0,0.974937,0,0.974683,0,0.974662,0,0.974921,0,0.974647,0,0.974357,0,0.974359,0,0.974303,0,0.975073,0,0.975133,0,0.975212,0,0.975285,0,0.975136,0,0.974803,0,0.974962,0,0.97466,0,0.9748,0,0.975083,0,0.97459,0,0.974311,0,0.974468,0,0.974213,0,0.968532,0,0.969658,0,0.969596,0,0.968508,0,0.97066,0,0.970583,0,0.969498,0,0.968449,0,0.970469,0,0.968468,0,0.969641,0,0.968264,0,0.969509,0,0.970679,0,0.970603,0,0.967337,0,0.967208,0,0.966916,0,0.967379,0,0.967383,0,0.959756,0,0.962815,0,0.96143,0,0.957285,0,0.965821,0,0.965223,0,0.961608,0,0.963948,0,0.966334,0,0.95704,0,0.959618,0,0.955269,0,0.958244,0,0.953249,0,0.950703,0,0.955171,0,0.952161,0,0.953244,0,0.955586,0,0.948478,0,0.950459,0,0.954112,0,0.956034,0,0.954886,0,0.950983,0,0.945751,0,0.957427,0,0.957611,0,0.959126,0,0.959531,0,0.957472,0,0.95762,0,0.958953,0,0.958128,0,0.956735,0,0.957205,0,0.958634,0,0.955113,0,0.955519,0,0.95781,0,0.959291,0,0.957732,0,0.956349,0,0.957389,0,0.955997,0,0.954744,0,0.959261,0,0.958867,0,0.960154,0,0.959789,0,0.958545,0,0.959766,0,0.960426,0,0.960629,0,0.967358,0,0.966936,0,0.968737,0,0.968929,0,0.970121,0,0.970222,0,0.965599,0,0.964715,0,0.963861,0,0.96249,0,0.967873,0,0.966379,0,0.964887,0,0.969245,0,0.970423,0,0.970492,0,0.970292,0,0.971671,0,0.971984,0,0.970117,0,0.971399,0,0.972703,0,0.973011,0,0.968434,0,0.968507,0,0.968563,0,0.970717,0,0.968365,0,0.972356,0,0.973388,0,0.969903,0,0.969832,0,0.970832,0,0.970987,0,0.969795,0,0.970709,0,0.968654,0,0.968709,0,0.968783,0,0.969999,0,0.96861,0,0.971174,0,0.970126,0,0.969439,0,0.969501,0,0.970233,0,0.96868,0,0.968695,0,0.969538,0,0.970321,0,0.970009,0,0.969354,0,0.969904,0,0.969248,0,0.968623,0,0.968516,0,0.970729,0,0.970564,0,0.971245,0,0.971015,0,0.970451,0,0.970818,0,0.970886,0,0.971024,0,0.971459,0,0.971648,0,0.97074,0,0.97084,0,0.971655,0,0.97158,0,0.970944,0,0.97172,0,0.972372,0,0.972322,0,0.9724,0,0.969807,0,0.96993,0,0.968786,0,0.96893,0,0.970071,0,0.970652,0,0.969707,0,0.970576,0,0.969636,0,0.968673,0,0.9715,0,0.972251,0,0.971416,0,0.972164,0,0.972327,0,0.971555,0,0.971593,0,0.972371,0,0.971548,0,0.972337,0,0.972215,0,0.971457,0,0.972066,0,0.971325,0,0.972961,0,0.97285,0,0.973478,0,0.973362,0,0.972693,0,0.973194,0,0.973014,0,0.972995,0,0.973541,0,0.972063,0,0.971249,0,0.971165,0,0.971952,0,0.97223,0,0.971416,0,0.972752,0,0.972902,0,0.972601,0,0.95151,0,0.953313,0,0.95299,0,0.951216,0,0.951854,0,0.953668,0,0.949845,0,0.950221,0,0.949507,0,0.934136,0,0.932328,0,0.931952,0,0.933543,0,0.93064,0,0.930375,0,0.9348,0,0.932941,0,0.935944,0,0.933811,0,0.931066,0,0.931637,0,0.935879,0,0.936692,0,0.93815,0,0.935318,0,0.94091,0,0.938348,0,0.935466,0,0.938094,0,0.934972,0,0.932597,0,0.94348,0,0.941495,0,0.938548,0,0.942813,0,0.944823,0,0.940313,0,0.966611,0,0.966811,0,0.967862,0,0.96769,0,0.965633,0,0.965845,0,0.964787,0,0.965009,0,0.966492,0,0.965499,0,0.964645,0,0.967573,0,0.967042,0,0.96694,0,0.967825,0,0.967864,0,0.966257,0,0.966087,0,0.965541,0,0.965304,0,0.967079,0,0.966381,0,0.96701,0,0.966369,0,0.965762,0,0.965923,0,0.967845,0,0.967747,0,0.975457,0,0.975305,0,0.975461,0,0.975627,0,0.975688,0,0.975857,0,0.975331,0,0.975189,0,0.975559,0,0.975419,0,0.97573,0,0.975956,0,0.974359,0,0.974808,0,0.975093,0,0.974682,0,0.975438,0,0.975109,0,0.97408,0,0.974551,0,0.973778,0,0.973475,0,0.974123,0,0.974643,0,0.961581,0,0.961955,0,0.962494,0,0.962169,0,0.962932,0,0.96264,0,0.960906,0,0.961332,0,0.96131,0,0.960586,0,0.961942,0,0.96245,0,0.961574,0,0.962492,0,0.963,0,0.962096,0,0.963231,0,0.963706,0,0.961191,0,0.962097,0,0.962842,0,0.960473,0,0.960152,0,0.960966,0,0.974692,0,0.974466,0,0.974156,0,0.974387,0,0.973797,0,0.974024,0,0.974942,0,0.974732,0,0.97484,0,0.975074,0,0.974549,0,0.974192,0,0.974214,0,0.974466,0,0.974295,0,0.974062,0,0.974063,0,0.973853,0,0.974311,0,0.974585,0,0.97389,0,0.973967,0,0.973758,0,0.973571,0,0.956741,0,0.956408,0,0.954742,0,0.95523,0,0.958015,0,0.9578,0,0.957063,0,0.958264,0,0.955635,0,0.961134,0,0.959594,0,0.960827,0,0.962369,0,0.960117,0,0.958592,0,0.96231,0,0.961277,0,0.963522,0,0.970448,0,0.97039,0,0.969557,0,0.969572,0,0.97124,0,0.97114,0,0.971946,0,0.971809,0,0.970509,0,0.971331,0,0.972063,0,0.969594,0,0.971149,0,0.971046,0,0.970227,0,0.970391,0,0.971809,0,0.971772,0,0.972374,0,0.972403,0,0.971254,0,0.971835,0,0.971379,0,0.971907,0,0.972315,0,0.972242,0,0.970555,0,0.970717,0,0.965204,0,0.965617,0,0.96511,0,0.964625,0,0.96467,0,0.964141,0,0.965862,0,0.966165,0,0.966606,0,0.966787,0,0.964889,0,0.965633,0,0.966469,0,0.964245,0,0.963717,0,0.963519,0,0.964088,0,0.96394,0,0.963361,0,0.963868,0,0.963283,0,0.963775,0,0.964328,0,0.963055,0,0.963325,0,0.962885,0,0.96279,0,0.963355,0,0.963981,0,0.964149,0,0.963521,0,0.964363,0,0.96374,0,0.963284,0,0.963885,0,0.962824,0,0.962772,0,0.962966,0,0.963171,0,0.964356,0,0.964912,0,0.965236,0,0.964761,0,0.965622,0,0.965223,0,0.964006,0,0.964614,0,0.963819,0,0.963442,0,0.964268,0,0.964779,0,0.966501,0,0.966803,0,0.967557,0,0.967299,0,0.968354,0,0.968154,0,0.965802,0,0.966147,0,0.966134,0,0.965392,0,0.966996,0,0.967936,0,0.969802,0,0.969837,0,0.97045,0,0.970518,0,0.970958,0,0.971131,0,0.969002,0,0.969127,0,0.969787,0,0.968883,0,0.970605,0,0.971311,0,0.972047,0,0.971675,0,0.971955,0,0.972391,0,0.972196,0,0.972679,0,0.971633,0,0.971354,0,0.972379,0,0.971897,0,0.972779,0,0.973112,0,0.97312,0,0.972569,0,0.972709,0,0.973275,0,0.972821,0,0.973393,0,0.972921,0,0.972399,0,0.973604,0,0.973386,0,0.973769,0,0.973885,0,0.973515,0,0.972965,0,0.972991,0,0.973517,0,0.972984,0,0.973476,0,0.973473,0,0.972908,0,0.973978,0,0.973955,0,0.973955,0,0.973884,0,0.973252,0,0.972851,0,0.972706,0,0.973039,0,0.972465,0,0.972727,0,0.973392,0,0.97294,0,0.973585,0,0.973764,0,0.97333,0,0.972976,0,0.971723,0,0.971541,0,0.970875,0,0.97103,0,0.970094,0,0.970226,0,0.97229,0,0.972076,0,0.9719,0,0.972501,0,0.97118,0,0.970348,0,0.968386,0,0.968345,0,0.967519,0,0.967458,0,0.969331,0,0.969229,0,0.968414,0,0.969414,0,0.967408,0,0.963624,0,0.964537,0,0.961708,0,0.960871,0,0.966134,0,0.96714,0,0.968207,0,0.96935,0,0.963049,0,0.965379,0,0.967216,0,0.960357,0,0.971077,0,0.969426,0,0.970201,0,0.972076,0,0.970822,0,0.972847,0,0.969819,0,0.968495,0,0.97262,0,0.97116,0,0.973777,0,0.974664,0,0.973771,0,0.971573,0,0.9717,0,0.973945,0,0.973406,0,0.971279,0,0.975733,0,0.975308,0,0.975947,0,0.940194,0,0.936156,0.00194541,0.940951,0,0.944163,0,0.9322,0.00568575,0.937799,0,0.934141,0.0106833,0.928858,0.0166869,0.923969,0.0214505,0.944365,0,0.939862,0.0035669,0.947375,0,0.925346,0.00686328,0.92544,0.00110707,0.929391,0,0.93076,0,0.926242,0,0.929188,0,0.91756,0.0200733,0.919682,0.0100591,0.922052,0.00216724,0.928158,0.00837195,0.919342,0.0243262,0.934263,0,0.926701,0,0.926637,0,0.928652,0,0.928837,0,0.92379,0,0.923888,0,0.926655,0,0.923293,0,0.929061,0,0.952569,0.0221318,0.939124,0.0471666,0.923595,0.0604139,0.945546,0.0315095,0.957036,0.0155463,0.949575,0.0300928,0.960323,0.00674035,0.963237,0.00292174,0.967051,0,0.969083,0,0.957062,0.00990272,0.965068,0,0.974853,0,0.973949,0,0.975708,0,0.976409,0,0.976831,0,0.977389,0,0.972018,0,0.970731,0,0.975749,0,0.973342,0,0.977089,0,0.977929,0,0.879438,0.061148,0.903489,0.0373729,0.897653,0.0492771,0.872673,0.0702357,0.888745,0.0571098,0.863346,0.0797633,0.878822,0.0548204,0.90598,0.028342,0.844037,0.0833554,0.84612,0.0724463,0.792169,0.117827,0.801138,0.097423,0.834427,0.0993852,0.824567,0.111674,0.777642,0.141909,0.767588,0.157379,0.912529,0.00759483,0.914462,0.0108205,0.907085,0.014381,0.902822,0.0169783,0.919775,0,0.920212,0.00767868,0.903193,0.0282524,0.913027,0.0170308,0.891351,0.0397174,0.858751,0.0431168,0.863071,0.0358291,0.847477,0.0563352,0.838522,0.0614411,0.865907,0.0371725,0.868465,0.0415866,0.854511,0.0486891,0.864616,0.0356294,0.833593,0.0640825,0.857369,0.0867139,0.874068,0.0670452,0.880005,0.0647377,0.862099,0.0849627,0.850073,0.0904085,0.866455,0.0705819,0.833495,0.110311,0.82503,0.11544,0.797868,0.142548,0.788125,0.148955,0.837362,0.108957,0.801938,0.140832,0.845104,0.0955855,0.868443,0.0718569,0.862562,0.0745139,0.841045,0.097818,0.853174,0.0889578,0.878095,0.0652889,0.808602,0.128353,0.815394,0.121648,0.756567,0.172813,0.76077,0.16714,0.80579,0.130741,0.756155,0.174079,0.866093,0.0700748,0.842832,0.0722251,0.834296,0.0769575,0.833229,0.0913504,0.823552,0.0837786,0.792068,0.121212,0.902317,0.043043,0.900108,0.0377543,0.871337,0.0731792,0.902106,0.0502299,0.832161,0.102643,0.778425,0.143915,0.557853,0.287168,0.438015,0.368752,0.425873,0.329986,0.568534,0.245578,0.553424,0.315807,0.441379,0.404375,0.54804,0.335301,0.441505,0.425179,0.704871,0.179352,0.685445,0.212472,0.677139,0.22987,0.728031,0.141674,0.625823,0.291163,0.506234,0.39294,0.543756,0.362364,0.655852,0.266748,0.561404,0.346596,0.671959,0.252995,0.595786,0.31276,0.475269,0.415634,0.594549,0.305179,0.472721,0.408679,0.710041,0.219724,0.696052,0.227881,0.693484,0.222288,0.731535,0.202467,0.747167,0.189137,0.681119,0.241487,0.679472,0.240767,0.740368,0.190073,0.746903,0.18645,0.673458,0.244235,0.733272,0.194648,0.591316,0.315426,0.59705,0.307829,0.595713,0.307024,0.678702,0.245637,0.579237,0.328524,0.750543,0.185148,0.649268,0.263407,0.629277,0.280464,0.705405,0.217233,0.715358,0.208708,0.597617,0.307544,0.696298,0.2251,0.541653,0.350595,0.488514,0.395444,0.451453,0.42812,0.662363,0.25265,0.57935,0.319505,0.724903,0.200915,0.554191,0.343077,0.545325,0.346056,0.676368,0.236761,0.68065,0.236686,0.444755,0.434824,0.438139,0.437849,0.57292,0.328554,0.446511,0.432963,0.687669,0.232167,0.60507,0.262074,0.455264,0.382799,0.449269,0.41476,0.593711,0.292741,0.621417,0.220947,0.490121,0.311869,0.717164,0.175926,0.735769,0.143263,0.700539,0.205502,0.882289,0.117752,0.942832,0.057178,0.933801,0.0662203,0.86351,0.13654,0.974712,0.000551652,0.973228,0.00352401,0.922897,0.0771347,0.844975,0.155082,0.971472,0.00704443,0.885398,0.114626,0.948149,0.0518511,0.883777,0.116229,0.950174,0.0496386,0.976554,0,0.978139,0,0.755016,0.245033,0.735253,0.264767,0.526373,0.473651,0.531438,0.468561,0.737748,0.262254,0.524248,0.475739,0.755103,0.244955,0.742353,0.257709,0.503996,0.496075,0.554974,0.445099,0.838362,0.161707,0.838404,0.161659,0.915644,0.0843957,0.915717,0.0843257,0.967884,0.0142291,0.962886,0.0242274,0.740808,0.259257,0.738451,0.261613,0.584949,0.41512,0.565697,0.434372,0.842617,0.157449,0.746225,0.253843,0.844757,0.155273,0.750226,0.249823,0.610967,0.389105,0.607887,0.392165,0.917884,0.0821421,0.956743,0.0365059,0.909637,0.090371,0.949568,0.0504199,0.821924,0.178057,0.833324,0.166676,0.902026,0.0979651,0.896519,0.103457,0.944423,0.0555532,0.941464,0.0584993,0.745702,0.254277,0.743494,0.256511,0.649945,0.350031,0.610669,0.389334,0.807077,0.192887,0.744394,0.255567,0.797183,0.202764,0.741208,0.258737,0.691492,0.308467,0.699109,0.300834,0.890417,0.109543,0.938917,0.0610314,0.883301,0.11664,0.935171,0.064757,0.803344,0.196569,0.796241,0.20369,0.877147,0.122774,0.874218,0.125686,0.930723,0.0691842,0.926577,0.0733116,0.74839,0.251526,0.741295,0.258637,0.700876,0.299039,0.698814,0.301116,0.811184,0.18871,0.758635,0.24126,0.816811,0.183062,0.765876,0.233996,0.704151,0.295741,0.708499,0.291368,0.872828,0.127059,0.922588,0.0772836,0.871502,0.128368,0.919321,0.0805351,0.895047,0.104943,0.954959,0.0400498,0.951821,0.0463333,0.887652,0.112345,0.98106,0,0.97952,0,0.902476,0.0975101,0.959664,0.0306351,0.911393,0.0885871,0.964953,0.0200451,0.983182,0,0.984727,0,0.762596,0.23739,0.770046,0.229937,0.501218,0.49876,0.506793,0.493186,0.777163,0.222816,0.53004,0.469939,0.749312,0.25068,0.511869,0.488111,0.868645,0.131216,0.918762,0.0810928,0.934156,0.0657104,0.890687,0.109184,0.948221,0.051627,0.958235,0.0332498,0.945152,0.0547329,0.909311,0.0905805,0.96615,0.0174577,0.83802,0.161837,0.898216,0.101634,0.814435,0.185418,0.87548,0.124369,0.939001,0.0608383,0.928449,0.0713834,0.797926,0.201934,0.777122,0.222732,0.722727,0.277125,0.717857,0.281991,0.763702,0.236149,0.717563,0.282283,0.823387,0.176483,0.846165,0.153726,0.734939,0.264923,0.754405,0.245479,0.809542,0.190309,0.815318,0.18454,0.867978,0.131877,0.863531,0.136318,0.916475,0.0833676,0.915415,0.0844196,0.763007,0.236842,0.766692,0.233164,0.718703,0.281142,0.715894,0.283956,0.806603,0.193248,0.760632,0.239216,0.718431,0.281413,0.863956,0.135891,0.918667,0.0811647,0.93126,0.0686871,0.963262,0.0233612,0.967167,0.0155927,0.92567,0.0742983,0.979096,0,0.983182,0,0.92066,0.0792603,0.954633,0.0405601,0.972858,0.00410125,0.850248,0.149704,0.854617,0.145306,0.610437,0.389523,0.717361,0.282564,0.805992,0.19398,0.510142,0.489833,0.570405,0.429574,0.650098,0.349881,0.828863,0.171116,0.772988,0.226992,0.67742,0.322559,0.850354,0.149626,0.892907,0.107068,0.846613,0.153362,0.927349,0.0726262,0.763459,0.236516,0.707057,0.292919,0.896807,0.103164,0.871021,0.128952,0.668053,0.331923,0.846273,0.153702,0.618804,0.381175,0.828074,0.1719,0.715712,0.284265,0.865582,0.134388,0.920036,0.0799321,0.933051,0.0669111,0.682899,0.317081,0.78268,0.217295,0.536945,0.463037,0.69051,0.309465,0.777665,0.222302,0.573684,0.426292,0.5955,0.404475,0.778343,0.221619,0.51323,0.486747,0.51794,0.482037,0.609213,0.39076,0.777988,0.22197,0.536512,0.463465,0.762355,0.237617,0.53594,0.464039,0.570484,0.429493,0.88053,0.119426,0.878248,0.121716,0.935068,0.0648775,0.935163,0.0647908,0.880254,0.119694,0.880326,0.119615,0.935769,0.0641673,0.9368,0.0631285,0.780395,0.21957,0.771812,0.228148,0.594096,0.405876,0.605808,0.394167,0.773212,0.226745,0.598257,0.401713,0.545098,0.454881,0.542751,0.45723,0.549454,0.450524,0.879244,0.12071,0.876604,0.123342,0.936485,0.0634613,0.935887,0.0640483,0.877765,0.122174,0.936799,0.0631279,0.799857,0.200114,0.886051,0.113912,0.80719,0.192788,0.890009,0.109964,0.938874,0.061085,0.940874,0.0590954,0.673818,0.326161,0.539641,0.460344,0.684162,0.315821,0.502813,0.497175,0.781704,0.218249,0.781264,0.218691,0.618806,0.381165,0.62281,0.377161,0.549028,0.450949,0.555285,0.444693,0.880118,0.119815,0.880543,0.119392,0.937022,0.0628989,0.936997,0.062926,0.777854,0.2221,0.879064,0.12087,0.937112,0.0628096,0.617896,0.382074,0.554771,0.445207,0.791117,0.208866,0.640991,0.358993,0.610543,0.389436,0.768694,0.231287,0.622675,0.377311,0.648492,0.351489,0.590042,0.409932,0.749661,0.250317,0.694558,0.305414,0.8016,0.198382,0.65823,0.341755,0.591564,0.408424,0.877816,0.122165,0.888036,0.111943,0.931758,0.0682238,0.93882,0.0611582,0.86318,0.136802,0.850495,0.149486,0.922258,0.0777252,0.913916,0.0860672,0.99902,0,0.999107,0,0.997558,0,0.997379,0,0.9992,0,0.997764,0,0.993795,0,0.993372,0,0.994228,0,0.999627,0,0.99968,0,0.999844,0,0.999874,0,0.999735,0,0.9999,0,0.998927,0,0.999575,0,0.998848,0,0.999521,0,0.99981,0,0.999769,0,0.997237,0,0.99309,0,0.997133,0,0.992907,0,0.998725,0,0.998786,0,0.997062,0,0.997036,0,0.992779,0,0.992931,0,0.999391,0,0.999461,0,0.999642,0,0.999713,0,0.998652,0,0.999308,0,0.998566,0,0.999216,0,0.999565,0,0.999477,0,0.997066,0,0.993287,0,0.997087,0,0.993682,0,0.998406,0,0.998475,0,0.997036,0,0.996951,0,0.993989,0,0.994077,0,0.999082,0,0.99913,0,0.99931,0,0.999376,0,0.998363,0,0.999063,0,0.998364,0,0.999055,0,0.999276,0,0.999244,0,0.99689,0,0.994061,0,0.996902,0,0.994096,0,0.998535,0,0.999067,0,0.999057,0,0.998432,0,0.999184,0,0.999213,0,0.998616,0,0.999082,0,0.998691,0,0.999102,0,0.999163,0,0.999158,0,0.997209,0,0.997414,0,0.994553,0,0.994945,0,0.997606,0,0.995324,0,0.997025,0,0.994279,0,0.999405,0,0.999814,0,0.999828,0,0.999455,0,0.99992,0,0.999925,0,0.999818,0,0.999482,0,0.999916,0,0.999317,0,0.999782,0,0.999914,0,0.99817,0,0.997972,0,0.995082,0,0.994648,0,0.99836,0,0.998546,0,0.995534,0,0.996027,0,0.999129,0,0.99907,0,0.99824,0,0.998321,0,0.999007,0,0.998159,0,0.996369,0,0.996504,0,0.996241,0,0.999433,0,0.999377,0,0.999477,0,0.999412,0,0.999317,0,0.999347,0,0.999193,0,0.999494,0,0.999278,0,0.999567,0,0.999556,0,0.999643,0,0.998427,0,0.99669,0,0.998561,0,0.996898,0,0.998857,0,0.999185,0,0.999135,0,0.998775,0,0.999213,0,0.999171,0,0.998932,0,0.999249,0,0.999276,0,0.997917,0,0.99805,0,0.995812,0,0.996052,0,0.997769,0,0.99558,0,0.999461,0,0.999724,0,0.999651,0,0.999383,0,0.999815,0,0.999731,0,0.999489,0,0.99978,0,0.999883,0,0.998755,0,0.998695,0,0.996975,0,0.996577,0,0.9987,0,0.997048,0,0.626062,0.373916,0.663848,0.336128,0.512386,0.487588,0.564207,0.435771,0.697094,0.302882,0.547582,0.452391,0.667931,0.332043,0.708631,0.291347,0.634157,0.365813,0.76652,0.23346,0.789924,0.210055,0.864528,0.135453,0.877708,0.122273,0.810237,0.189742,0.888864,0.111118,0.578515,0.421466,0.741183,0.258798,0.5177,0.482282,0.712848,0.287134,0.853782,0.1462,0.843898,0.156084,0.620306,0.379675,0.759547,0.240435,0.684989,0.314995,0.814086,0.1859,0.626286,0.373695,0.797989,0.201995,0.837676,0.162307,0.663844,0.336137,0.885167,0.114819,0.912895,0.0870873,0.56491,0.435072,0.747512,0.252473,0.854181,0.145806,0.653336,0.346645,0.677267,0.322715,0.822656,0.177324,0.829683,0.170298,0.650353,0.349626,0.829385,0.170592,0.730898,0.269077,0.739034,0.260942,0.583415,0.416557,0.582998,0.416974,0.641863,0.358106,0.620562,0.379406,0.833757,0.166222,0.841434,0.158546,0.903125,0.0968585,0.908194,0.0917896,0.718595,0.281381,0.82406,0.17592,0.896957,0.103026,0.573063,0.426909,0.617808,0.38216,0.859796,0.140099,0.927918,0.0718523,0.928467,0.0712885,0.8593,0.140585,0.955629,0.0379782,0.956392,0.036404,0.929177,0.0705784,0.861646,0.138236,0.95633,0.036534,0.86654,0.133369,0.925377,0.0744271,0.85599,0.143936,0.916982,0.0828596,0.953171,0.0430058,0.948845,0.0509002,0.748149,0.251816,0.751749,0.248241,0.719728,0.280279,0.744323,0.255631,0.740266,0.259682,0.827999,0.171956,0.843115,0.156827,0.705578,0.294433,0.680822,0.319196,0.895339,0.104546,0.906629,0.093237,0.937342,0.062457,0.943108,0.0566705,0.798598,0.201371,0.87909,0.120817,0.76936,0.230613,0.857644,0.142278,0.929997,0.069828,0.916819,0.0830334,0.644972,0.355055,0.626591,0.373429,0.719465,0.280481,0.7452,0.25476,0.603053,0.396935,0.535899,0.464068,0.817504,0.182409,0.838432,0.161488,0.885472,0.114391,0.901511,0.0983533,0.659503,0.340427,0.7946,0.205299,0.595889,0.404029,0.772107,0.227777,0.871766,0.128089,0.85827,0.14157,0.571623,0.428328,0.622506,0.377432,0.560673,0.439213,0.565127,0.434778,0.633617,0.366308,0.629172,0.370736,0.735794,0.264057,0.751827,0.24804,0.832904,0.166903,0.844634,0.15519,0.56315,0.436713,0.721049,0.278784,0.545102,0.454739,0.703584,0.296233,0.823708,0.176083,0.815666,0.184109,0.617112,0.382772,0.620238,0.379619,0.866274,0.133625,0.932097,0.0676922,0.931438,0.0684276,0.872538,0.127392,0.956784,0.0357305,0.957687,0.0340878,0.925387,0.0745219,0.878476,0.121466,0.955829,0.0380182,0.863514,0.136369,0.931085,0.0686769,0.956213,0.0368075,0.730983,0.268968,0.73543,0.264517,0.757115,0.242845,0.787129,0.212833,0.565489,0.434337,0.7487,0.251099,0.708229,0.291566,0.50887,0.490953,0.861149,0.138601,0.841885,0.157861,0.667454,0.33234,0.546121,0.453701,0.824746,0.174998,0.615115,0.384722,0.789563,0.210251,0.67795,0.321922,0.840226,0.159629,0.888726,0.111041,0.918435,0.0813751,0.62407,0.375772,0.606829,0.393023,0.570615,0.429263,0.643552,0.356287,0.65929,0.340547,0.559313,0.440507,0.522318,0.477508,0.644957,0.354884,0.660928,0.338908,0.64941,0.350385,0.674852,0.324951,0.805235,0.194515,0.807216,0.192545,0.567467,0.432354,0.645095,0.354698,0.810289,0.189455,0.664885,0.334951,0.871881,0.128072,0.914726,0.0852052,0.899855,0.100065,0.816455,0.18348,0.946195,0.0536913,0.93979,0.060084,0.877437,0.122512,0.91991,0.0800164,0.950159,0.0494336,0.739818,0.260144,0.785709,0.214254,0.599623,0.40031,0.993951,0,0.987962,0,0.988635,0,0.994365,0,0.989508,0,0.994792,0,0.99342,0,0.987373,0,0.992581,0,0.98622,0,0.996704,0,0.996354,0,0.998159,0,0.99795,0,0.995868,0,0.997677,0,0.996985,0,0.997249,0,0.998341,0,0.99851,0,0.989627,0,0.991266,0,0.995184,0,0.994354,0,0.997299,0,0.996848,0,0.980615,0,0.983874,0,0.987771,0,0.977005,0,0.985873,0,0.973403,0.0031383,0.993404,0,0.996348,0,0.992486,0,0.995675,0,0.984374,0,0.984802,0,0.992319,0,0.992399,0,0.995427,0,0.995431,0,0.969025,0.0118485,0.970798,0.00832726,0.98337,0,0.966891,0.0160871,0.980886,0,0.963881,0.0220644,0.99209,0,0.995332,0,0.990459,0,0.99468,0,0.976641,0,0.978546,0,0.988183,0,0.986916,0,0.993154,0,0.992276,0,0.957739,0.0342627,0.960631,0.0285186,0.974865,0,0.955017,0.0396689,0.973271,0.00307072,0.952593,0.0444835,0.985808,0,0.991571,0,0.984832,0,0.990969,0,0.995738,0,0.991448,0,0.992073,0,0.995873,0,0.991969,0,0.995496,0,0.995291,0,0.990513,0,0.997726,0,0.997519,0,0.998761,0,0.998666,0,0.99774,0,0.997391,0,0.998734,0,0.998457,0,0.981784,0,0.978465,0,0.987258,0,0.988825,0,0.975232,0,0.985735,0,0.992134,0,0.992976,0,0.991361,0,0.969079,0.0115153,0.963192,0.0232658,0.957429,0.0347739,0.984415,0,0.974362,0.000980094,0.987038,0,0.979065,0,0.99039,0,0.993922,0,0.99195,0,0.994883,0,0.972078,0.00541933,0.95087,0.0478818,0.950893,0.0478541,0.972189,0.00520946,0.973152,0.00327845,0.953165,0.0432896,0.984031,0,0.984572,0,0.990448,0,0.990733,0,0.984158,0,0.990544,0,0.992237,0,0.987427,0,0.983398,0,0.989715,0,0.994321,0,0.990479,0,0.99514,0,0.996514,0,0.996929,0,0.997838,0,0.993533,0,0.995879,0,0.994475,0,0.994076,0,0.995999,0,0.996178,0,0.993876,0,0.995989,0,0.997371,0,0.997443,0,0.997443,0,0.992125,0,0.991388,0,0.98878,0,0.987588,0,0.990812,0,0.986405,0,0.995012,0,0.993001,0,0.9957,0,0.994187,0,0.990112,0,0.992019,0,0.996511,0,0.997653,0,0.997004,0,0.998016,0,0.993074,0,0.990084,0,0.99271,0,0.994529,0,0.985036,0,0.990039,0,0.994688,0,0.995679,0,0.993783,0,0.991295,0,0.986832,0,0.989452,0,0.983294,0,0.979236,0,0.97291,0.00406635,0.995012,0,0.994063,0,0.996396,0,0.995879,0,0.993214,0,0.995486,0,0.995881,0,0.996594,0,0.996916,0,0.99739,0,0.987332,0,0.98815,0,0.992697,0,0.992591,0,0.995368,0,0.995494,0,0.977692,0,0.98018,0,0.959517,0.0308586,0.965474,0.0189395,0.987228,0,0.976444,0,0.988008,0,0.976949,0,0.956535,0.036829,0.956334,0.0372439,0.992929,0,0.995886,0,0.993674,0,0.996456,0,0.501628,0.498354,0.52164,0.47834,0.514538,0.485442,0.505526,0.494456,0.500574,0.499404,0.519321,0.480659,0.503445,0.496537,0.52271,0.477271,0.500862,0.499121,0.519563,0.480418,0.512435,0.48755,0.50983,0.490155,0.524441,0.475545,0.52091,0.479076,0.511533,0.488452,0.519572,0.480415,0.518759,0.481224,0.53074,0.469241,0.530132,0.469852,0.536582,0.4634,0.574867,0.425108,0.634394,0.365579,0.633813,0.366161,0.580366,0.41961,0.738358,0.261611,0.730727,0.269244,0.621745,0.37823,0.577219,0.42276,0.709892,0.29008,0.555527,0.444448,0.617579,0.382395,0.730247,0.269724,0.5406,0.459377,0.520603,0.479374,0.550123,0.449856,0.550859,0.449122,0.878578,0.12139,0.919444,0.0805243,0.92072,0.079248,0.877338,0.122631,0.94676,0.0532085,0.949104,0.0508632,0.919271,0.0806979,0.870373,0.129598,0.950225,0.0494859,0.871641,0.128329,0.913095,0.0868753,0.941407,0.0585631,0.819149,0.180818,0.812657,0.187314,0.814405,0.185564,0.801247,0.198724,0.976386,0,0.973808,0.0023213,0.982074,0,0.983875,0,0.970358,0.00923086,0.980038,0,0.964781,0.0203704,0.961017,0.0279055,0.95524,0.0394686,0.978312,0,0.967013,0.0159052,0.980194,0,0.968813,0.0123053,0.985581,0,0.987272,0,0.870045,0.129915,0.771399,0.228566,0.793968,0.205997,0.887092,0.112868,0.838478,0.161489,0.909931,0.0900304,0.859679,0.140283,0.761996,0.237971,0.859761,0.140204,0.766918,0.233051,0.927296,0.0726592,0.920835,0.0791223,0.920062,0.0798997,0.937983,0.061971,0.951469,0.0469682,0.963305,0.0233213,0.941234,0.0587411,0.965167,0.0196278,0.977143,0,0.9137,0.0862822,0.946672,0.0533141,0.976984,0,0.985629,0,0.960223,0.029532,0.940707,0.059256,0.899322,0.100648,0.843641,0.156337,0.976764,0,0.965324,0.0192578,0.985196,0,0.990633,0,0.838765,0.161225,0.772188,0.2278,0.705537,0.294453,0.746477,0.253514,0.569752,0.430237,0.60495,0.39504,0.88366,0.116327,0.806047,0.193938,0.857319,0.142672,0.915624,0.0843655,0.860188,0.139802,0.924953,0.075037,0.758479,0.241513,0.615997,0.383994,0.757227,0.242762,0.607034,0.392956,0.519526,0.480459,0.506176,0.493808,0.518944,0.481042,0.532984,0.467003,0.527241,0.472747,0.540963,0.459026,0.503219,0.496765,0.513617,0.486365,0.546054,0.453933,0.517612,0.482375,0.559427,0.440562,0.564538,0.435452,0.965826,0.018307,0.967835,0.0142868,0.934564,0.0654181,0.929068,0.0709137,0.970062,0.0098353,0.939775,0.0602072,0.984457,0,0.985368,0,0.986317,0,0.963446,0.0230692,0.983642,0,0.961412,0.027138,0.982987,0,0.923825,0.0761571,0.919395,0.0805865,0.999913,0,0.999885,0,0.999848,0,0.999881,0,0.999841,0,0.999798,0,0.99977,0,0.999812,0,0.999708,0,0.99991,0,0.999882,0,0.999843,0,0.999931,0,0.99993,0,0.999944,0,0.999944,0,0.999903,0,0.999843,0,0.999919,0,0.999866,0,0.961644,0.0266665,0.960799,0.0283626,0.915917,0.0840634,0.915398,0.0845806,0.983354,0,0.982783,0,0.964508,0.0209304,0.9845,0,0.969105,0.0117268,0.986016,0,0.920718,0.0792578,0.930818,0.0691543,0.999696,0,0.999593,0,0.999475,0,0.999613,0,0.99943,0,0.999237,0,0.999289,0,0.999475,0,0.998924,0,0.999718,0,0.999632,0,0.999511,0,0.99978,0,0.999789,0,0.999721,0,0.999611,0,0.977132,0,0.97363,0.00266274,0.942543,0.0574263,0.95311,0.0437108,0.988455,0,0.98754,0,0.97894,0,0.988773,0,0.979449,0,0.988891,0,0.959958,0.0300032,0.962881,0.0241443,0.999217,0,0.999304,0,0.999275,0,0.999196,0,0.999245,0,0.999164,0,0.999263,0,0.999366,0,0.999008,0,0.99904,0,0.998669,0,0.9987,0,0.999003,0,0.998971,0,0.998658,0,0.998603,0,0.980799,0,0.989683,0,0.989191,0,0.980025,0,0.981522,0,0.990247,0,0.982227,0,0.990796,0,0.964534,0.0208046,0.965454,0.0189507,0.966401,0.0170439,0.963906,0.022078,0.998941,0,0.99914,0,0.999074,0,0.998808,0,0.999027,0,0.998752,0,0.999093,0,0.999204,0,0.998553,0,0.998817,0,0.998073,0,0.998339,0,0.998437,0,0.998382,0,0.997961,0,0.997869,0,0.973864,0.00223818,0.988144,0,0.989028,0,0.975567,0,0.990068,0,0.977765,0,0.972112,0.0057381,0.987245,0,0.947425,0.0525605,0.9441,0.0558842,0.950575,0.0488233,0.954368,0.041237,0.999949,0,0.999948,0,0.999949,0,0.999951,0,0.999932,0,0.99994,0,0.999901,0,0.999923,0,0.999928,0,0.999909,0,0.999881,0,0.999855,0,0.999867,0,0.999789,0,0.999927,0,0.999881,0,0.984467,0,0.984217,0,0.967793,0.0142768,0.968042,0.0138031,0.984092,0,0.968007,0.0138332,0.992596,0,0.992394,0,0.992225,0,0.984909,0,0.992881,0,0.985286,0,0.993176,0,0.969004,0.0119049,0.969692,0.0105546,0.999242,0,0.99937,0,0.999128,0,0.998921,0,0.999505,0,0.999334,0,0.998736,0,0.998464,0,0.999036,0,0.999407,0,0.999498,0,0.999606,0,0.999096,0,0.999318,0,0.998954,0,0.999221,0,0.998724,0,0.998238,0,0.99856,0,0.998045,0,0.983457,0,0.991643,0,0.991256,0,0.982917,0,0.983814,0,0.991964,0,0.96752,0.014793,0.967871,0.0140932,0.967045,0.0157465,0.998783,0,0.999062,0,0.99913,0,0.998846,0,0.998754,0,0.999024,0,0.998378,0,0.99836,0,0.997826,0,0.997817,0,0.998444,0,0.997903,0,0.983289,0,0.992746,0,0.993265,0,0.985136,0,0.980461,0,0.99147,0,0.964681,0.0206031,0.959184,0.031602,0.968765,0.0124258,0.99978,0,0.999857,0,0.999799,0,0.99969,0,0.99969,0,0.999527,0,0.999827,0,0.999891,0,0.999653,0,0.999722,0,0.999531,0,0.999303,0,0.983546,0,0.989741,0,0.98976,0,0.983515,0,0.991953,0,0.99172,0,0.989805,0,0.983486,0,0.99158,0,0.983422,0,0.989716,0,0.982619,0,0.989557,0,0.992267,0,0.992589,0,0.971619,0.00557204,0.970922,0.00709706,0.969045,0.0110424,0.971509,0.00572883,0.971008,0.00674476,0.98943,0,0.991804,0,0.992479,0,0.990628,0,0.993208,0,0.991887,0,0.988377,0,0.991245,0,0.987629,0,0.990867,0,0.984188,0,0.982401,0,0.974648,0,0.971683,0,0.981083,0,0.969501,0,0.986251,0,0.988433,0,0.978013,0,0.981587,0,0.979817,0,0.981354,0,0.966336,0.0166175,0.963823,0.02173,0.988523,0,0.989121,0,0.992626,0,0.992718,0,0.978288,0,0.987847,0,0.975092,0,0.986837,0,0.992417,0,0.992004,0,0.960996,0.0274453,0.954282,0.0409421,0.994442,0,0.994977,0,0.993712,0,0.992649,0,0.995113,0,0.994131,0,0.990466,0,0.988526,0,0.991266,0,0.994425,0,0.994571,0,0.994442,0,0.993275,0,0.993917,0,0.990676,0,0.985283,0,0.963822,0.021667,0.9692,0.0108945,0.943481,0.0562999,0.933994,0.065792,0.979915,0,0.984178,0,0.988953,0,0.991098,0,0.957999,0.03328,0.976899,0,0.952628,0.0439688,0.974094,0.000495508,0.986995,0,0.985467,0,0.924854,0.0749234,0.916095,0.083665,0.994375,0,0.993443,0,0.992539,0,0.993476,0,0.992574,0,0.991582,0,0.988897,0,0.990093,0,0.987552,0,0.99309,0,0.991838,0,0.990809,0,0.995026,0,0.994021,0,0.994254,0,0.991338,0,0.945411,0.0541451,0.948649,0.050935,0.908462,0.09128,0.902014,0.0977092,0.970041,0.00838363,0.971799,0.00497656,0.983097,0,0.984116,0,0.942672,0.0568558,0.96877,0.0108232,0.940789,0.0587131,0.967976,0.0123153,0.982418,0,0.982016,0,0.896736,0.102969,0.892627,0.10706,0.991129,0,0.990529,0,0.98884,0,0.989712,0,0.990028,0,0.988016,0,0.983377,0,0.984763,0,0.982005,0,0.989253,0,0.988726,0,0.988368,0,0.991809,0,0.989947,0,0.99063,0,0.986157,0,0.983389,0,0.989991,0,0.990534,0,0.983997,0,0.991693,0,0.992283,0,0.992154,0,0.986104,0,0.993659,0,0.983445,0,0.989861,0,0.991557,0,0.970929,0.00707605,0.970651,0.00752964,0.97198,0.00513695,0.973355,0.0025844,0.987312,0,0.990827,0,0.990722,0,0.987275,0,0.987839,0,0.991384,0,0.989943,0,0.993005,0,0.980301,0,0.980849,0,0.968283,0,0.968981,0,0.983937,0,0.973254,0,0.9804,0,0.968445,0,0.963252,0.0223315,0.979943,0,0.976207,0,0.954581,0.0396881,0.987973,0,0.986258,0,0.972403,0.00319027,0.947079,0.0523587,0.984517,0,0.96952,0.00980109,0.983435,0,0.975334,0,0.988094,0,0.990096,0,0.99313,0,0.927471,0.0721732,0.942392,0.0572658,0.955723,0.0379481,0.913192,0.0864528,0.901814,0.097834,0.990046,0,0.990976,0,0.992014,0,0.99044,0,0.993649,0,0.99156,0,0.989655,0,0.990062,0,0.989383,0,0.989218,0,0.985395,0,0.98534,0,0.975862,0,0.976334,0,0.98556,0,0.977196,0,0.98551,0,0.986594,0,0.975731,0,0.977524,0,0.940503,0.0589617,0.939918,0.0595634,0.889744,0.109928,0.88985,0.10981,0.968235,0.0116498,0.967712,0.0127624,0.982219,0,0.981897,0,0.942899,0.056553,0.969687,0.00868491,0.98304,0,0.894002,0.105649,0.989401,0,0.98931,0,0.986015,0,0.986577,0,0.978246,0,0.979423,0,0.988269,0,0.988607,0,0.989645,0,0.988197,0,0.987251,0,0.980677,0,0.987772,0,0.99284,0,0.991813,0,0.983224,0,0.994072,0,0.99395,0,0.988422,0,0.993011,0,0.994096,0,0.971644,0.0062439,0.973856,0.00175017,0.966113,0.0172948,0.991201,0,0.993677,0,0.993525,0,0.990939,0,0.991522,0,0.993882,0,0.986371,0,0.986687,0,0.977822,0,0.978176,0,0.985928,0,0.9769,0,0.928652,0.0508075,0.881602,0.0887827,0.873431,0.088999,0.921969,0.05245,0.868752,0.0859211,0.917559,0.0521134,0.937055,0.0447226,0.892999,0.0846528,0.946385,0.0345627,0.906172,0.0775096,0.957694,0.00755602,0.963109,0.00273767,0.968979,0,0.95302,0.0105246,0.949705,0.0110677,0.965153,0.00898962,0.933113,0.0587037,0.919762,0.0686165,0.956073,0.0220788,0.970597,0.000681885,0.943294,0.0505076,0.972061,0,0.946188,0.0463574,0.980566,0,0.983783,0,0.984987,0,0.975129,0,0.968626,0.00346,0.97101,0.000333961,0.984668,0,0.982915,0,0.940779,0.0520491,0.944117,0.0498161,0.965915,0.00641112,0.937553,0.0534185,0.962928,0.00930432,0.933779,0.0548344,0.981064,0,0.978996,0,0.956824,0.0136568,0.95989,0.0116995,0.97688,0,0.974763,0,0.924082,0.0581127,0.929189,0.056477,0.953749,0.0151744,0.919342,0.058972,0.950607,0.0161204,0.915991,0.0583218,0.97262,0,0.970484,0,0.917707,0.0389636,0.871299,0.0711002,0.874728,0.062034,0.919285,0.0298552,0.884909,0.0497193,0.925055,0.0158333,0.916377,0.0480115,0.868596,0.0793496,0.948653,0.00505767,0.948424,0.00889401,0.949651,0,0.954253,0,0.937314,0.00108884,0.938576,0.00510383,0.961462,0,0.960373,0,0.940313,0.00903177,0.96287,0,0.903753,0.0278123,0.904696,0.0358317,0.906129,0.0441181,0.937257,0,0.904086,0.0205418,0.940619,0,0.908472,0.014342,0.960129,0,0.962761,0,0.944813,0.0143588,0.94751,0.0159445,0.968432,0,0.966475,0,0.909401,0.0546725,0.911994,0.0573235,0.942413,0.0120995,0.907681,0.05105,0.964554,0,0.940328,0,0.907626,0.0200068,0.91205,0.0139191,0.942734,0,0.934942,0.0022129,0.898578,0.0302482,0.963031,0,0.960671,0,0.964051,0,0.55403,0.445957,0.56473,0.435253,0.598872,0.401108,0.582542,0.417443,0.658714,0.341262,0.631872,0.368111,0.537118,0.462869,0.544123,0.455861,0.547161,0.452828,0.532266,0.467722,0.571436,0.428553,0.613704,0.386283,0.860727,0.139254,0.864396,0.135579,0.918623,0.0813505,0.921151,0.0788296,0.952277,0.0453906,0.95703,0.0359006,0.766349,0.233632,0.781874,0.218102,0.860303,0.139684,0.758466,0.24152,0.924964,0.0750239,0.961651,0.0266729,0.987877,0,0.989312,0,0.98034,0,0.977006,0,0.993225,0,0.993768,0,0.995952,0,0.996026,0,0.983377,0,0.989886,0,0.993719,0,0.972017,0.00590708,0.991267,0,0.99287,0,0.985607,0,0.98227,0,0.993942,0,0.988215,0,0.971846,0.00626246,0.965067,0.0198076,0.977656,0,0.995598,0,0.996397,0,0.997551,0,0.997971,0,0.996832,0,0.998202,0,0.989489,0,0.994625,0,0.99701,0,0.979085,0,0.959501,0.0309241,0.889917,0.11006,0.910655,0.0893278,0.851636,0.148347,0.816756,0.183222,0.930481,0.0695069,0.885344,0.114643,0.936114,0.0638613,0.948419,0.0515618,0.959836,0.0302987,0.870781,0.12919,0.92574,0.0742281,0.786943,0.213029,0.959279,0.0314136,0.947457,0.0525315,0.968415,0.0131415,0.974124,0.00171664,0.981522,0,0.983842,0,0.933921,0.0660658,0.913048,0.0869407,0.966101,0.0177564,0.947129,0.0528527,0.977647,0,0.985253,0,0.994157,0,0.996535,0,0.996191,0,0.993928,0,0.997881,0,0.997632,0,0.994283,0,0.996831,0,0.99815,0,0.990187,0,0.989624,0,0.990461,0,0.997785,0,0.998573,0,0.998227,0,0.997154,0,0.999175,0,0.998934,0,0.998134,0,0.996988,0,0.998744,0,0.998357,0,0.998965,0,0.998818,0,0.999274,0,0.999428,0,0.999579,0,0.996736,0,0.997673,0,0.998411,0,0.995735,0,0.995198,0,0.998404,0,0.998887,0,0.998752,0,0.998226,0,0.999223,0,0.999089,0,0.998316,0,0.997482,0,0.998783,0,0.997605,0,0.998509,0,0.999031,0,0.997526,0,0.996111,0,0.997381,0,0.996222,0,0.997484,0,0.997863,0,0.99722,0,0.996614,0,0.998256,0,0.997812,0,0.998267,0,0.998499,0,0.998939,0,0.99907,0,0.998739,0,0.999172,0,0.997286,0,0.998154,0,0.998829,0,0.996125,0,0.999648,0,0.999475,0,0.99934,0,0.999562,0,0.999422,0,0.999294,0,0.999704,0,0.999494,0,0.99965,0,0.999478,0,0.999681,0,0.999673,0,0.999441,0,0.999509,0,0.999728,0,0.999683,0,0.999569,0,0.999764,0,0.998987,0,0.999098,0,0.9992,0,0.999356,0,0.998857,0,0.999241,0,0.998688,0,0.999626,0,0.999543,0,0.998878,0,0.999081,0,0.999423,0,0.999258,0,0.998167,0,0.998453,0,0.998582,0,0.997812,0,0.998028,0,0.997192,0,0.999026,0,0.998555,0,0.99786,0,0.99784,0,0.998344,0,0.998325,0,0.997044,0,0.997011,0,0.99785,0,0.997017,0,0.99766,0,0.996673,0,0.998311,0,0.998204,0,0.996554,0,0.996971,0,0.997773,0,0.997465,0,0.995024,0,0.995628,0,0.996249,0,0.994561,0,0.995986,0,0.994164,0,0.99728,0,0.997122,0,0.999642,0,0.999596,0,0.999763,0,0.999798,0,0.999453,0,0.999664,0,0.999337,0,0.99929,0,0.999083,0,0.99962,0,0.99929,0,0.999792,0,0.996903,0,0.996502,0,0.997546,0,0.997841,0,0.996165,0,0.997301,0,0.995416,0,0.994863,0,0.994376,0,0.997324,0,0.996017,0,0.997785,0,0.996665,0,0.998169,0,0.998531,0,0.995823,0,0.995837,0,0.997039,0,0.99705,0,0.993845,0,0.993903,0,0.995935,0,0.994015,0,0.997137,0,0.998767,0,0.99828,0,0.998897,0,0.999237,0,0.998043,0,0.997346,0,0.99918,0,0.998661,0,0.999498,0,0.976563,0,0.97663,0,0.976257,0,0.976167,0,0.976659,0,0.976298,0,0.977024,0,0.977052,0,0.977493,0,0.977485,0,0.977054,0,0.977461,0,0.976467,0,0.976976,0,0.976336,0,0.976915,0,0.977489,0,0.977487,0,0.976025,0,0.975828,0,0.976956,0,0.977391,0,0.976609,0,0.976076,0,0.977818,0,0.977134,0,0.977675,0,0.978025,0,0.97825,0,0.978534,0,0.978366,0,0.978806,0,0.976577,0,0.97735,0,0.976325,0,0.977086,0,0.977964,0,0.9777,0,0.975586,0,0.975332,0,0.97919,0,0.979137,0,0.979018,0,0.979069,0,0.979092,0,0.978977,0,0.978827,0,0.978876,0,0.978786,0,0.979235,0,0.979176,0,0.979122,0,0.979248,0,0.979289,0,0.979293,0,0.979329,0,0.979123,0,0.978924,0,0.979172,0,0.978977,0,0.976263,0,0.976234,0,0.975444,0,0.975672,0,0.976842,0,0.97692,0,0.977392,0,0.977512,0,0.976342,0,0.976834,0,0.976437,0,0.976873,0,0.97732,0,0.977305,0,0.975866,0,0.976026,0,0.979237,0,0.979365,0,0.979271,0,0.979142,0,0.979506,0,0.979416,0,0.979097,0,0.978965,0,0.979249,0,0.979258,0,0.979383,0,0.979522,0,0.979142,0,0.979162,0,0.979082,0,0.9791,0,0.979042,0,0.978855,0,0.978977,0,0.978782,0,0.977071,0,0.976345,0,0.975127,0,0.976119,0,0.975956,0,0.974689,0,0.977911,0,0.977359,0,0.97857,0,0.978128,0,0.977006,0,0.977816,0,0.977981,0,0.978562,0,0.978914,0,0.97925,0,0.979059,0,0.979418,0,0.977295,0,0.978453,0,0.979765,0,0.979883,0,0.97981,0,0.979687,0,0.979992,0,0.979926,0,0.979683,0,0.979547,0,0.979812,0,0.979783,0,0.979898,0,0.980002,0,0.97964,0,0.979657,0,0.979556,0,0.979403,0,0.979065,0,0.979257,0,0.978938,0,0.978705,0,0.979433,0,0.979157,0,0.979344,0,0.979502,0,0.979651,0,0.978867,0,0.979178,0,0.97867,0,0.979005,0,0.978467,0,0.978235,0,0.978307,0,0.978215,0,0.978576,0,0.978684,0,0.978172,0,0.978514,0,0.97788,0,0.97779,0,0.977753,0,0.978473,0,0.978032,0,0.978832,0,0.978294,0,0.978341,0,0.978665,0,0.978617,0,0.978396,0,0.978722,0,0.977923,0,0.977951,0,0.977987,0,0.978261,0,0.977895,0,0.978231,0,0.977864,0,0.978576,0,0.97854,0,0.97942,0,0.979871,0,0.980168,0,0.979675,0,0.980264,0,0.980642,0,0.980397,0,0.979851,0,0.98095,0,0.979065,0,0.979499,0,0.979827,0,0.979048,0,0.978676,0,0.978804,0,0.978401,0,0.979292,0,0.979444,0,0.979067,0,0.979232,0,0.979993,0,0.980648,0,0.980672,0,0.979988,0,0.98134,0,0.981398,0,0.979952,0,0.980556,0,0.981188,0,0.979551,0,0.979525,0,0.979369,0,0.979329,0,0.97953,0,0.979354,0,0.98052,0,0.98044,0,0.981215,0,0.981165,0,0.981835,0,0.981683,0,0.97977,0,0.979514,0,0.980478,0,0.979904,0,0.980979,0,0.981391,0,0.98232,0,0.982614,0,0.982775,0,0.982447,0,0.982065,0,0.982301,0,0.981902,0,0.981699,0,0.981996,0,0.979679,0,0.979457,0,0.979613,0,0.979794,0,0.97918,0,0.979399,0,0.979681,0,0.979846,0,0.97952,0,0.979509,0,0.979228,0,0.978876,0,0.979841,0,0.979711,0,0.979947,0,0.979835,0,0.979933,0,0.979979,0,0.980032,0,0.980081,0,0.980016,0,0.980003,0,0.980093,0,0.980117,0,0.980148,0,0.980178,0,0.979895,0,0.979889,0,0.979997,0,0.979865,0,0.980102,0,0.980169,0,0.976014,0,0.976176,0,0.975575,0,0.97526,0,0.976821,0,0.976856,0,0.977546,0,0.977503,0,0.9759,0,0.976853,0,0.977639,0,0.974889,0,0.979328,0,0.979319,0,0.979211,0,0.97924,0,0.979035,0,0.979089,0,0.979344,0,0.979347,0,0.979331,0,0.979333,0,0.979268,0,0.979144,0,0.976593,0,0.976523,0,0.976152,0,0.97624,0,0.976985,0,0.976928,0,0.977393,0,0.977343,0,0.976643,0,0.97703,0,0.977431,0,0.97629,0,0.979034,0,0.979046,0,0.978944,0,0.978936,0,0.97876,0,0.978763,0,0.979064,0,0.979069,0,0.979053,0,0.979083,0,0.978949,0,0.978768,0,0.978149,0,0.978148,0,0.977756,0,0.977786,0,0.978493,0,0.978491,0,0.978189,0,0.97851,0,0.977826,0,0.978549,0,0.978466,0,0.978042,0,0.978119,0,0.978865,0,0.978791,0,0.978653,0,0.978947,0,0.97823,0,0.978561,0,0.978207,0,0.978674,0,0.978946,0,0.979051,0,0.979259,0,0.978065,0,0.977617,0,0.978881,0,0.978485,0,0.979179,0,0.979426,0,0.979739,0,0.979602,0,0.979792,0,0.979903,0,0.979934,0,0.980028,0,0.979523,0,0.979357,0,0.979823,0,0.979643,0,0.979969,0,0.980081,0,0.980157,0,0.980087,0,0.980094,0,0.98016,0,0.980113,0,0.980032,0,0.980192,0,0.980155,0,0.980188,0,0.994209,0,0.99191,0,0.991667,0,0.99312,0,0.989079,0,0.990549,0,0.990925,0,0.987184,0,0.982692,0,0.995805,0,0.99362,0,0.996876,0,0.995481,0,0.994867,0,0.996453,0,0.998358,0,0.997794,0,0.997583,0,0.998249,0,0.997259,0,0.996745,0,0.998354,0,0.996874,0,0.997701,0,0.995725,0,0.998473,0,0.998274,0,0.994663,0,0.996472,0,0.997463,0,0.995886,0,0.991081,0,0.993716,0,0.992391,0,0.987836,0,0.989925,0,0.984094,0,0.994058,0,0.99223,0,0.984689,0,0.98746,0,0.990432,0,0.98852,0,0.975782,0,0.980194,0,0.981781,0,0.970625,0,0.979349,0,0.965587,0,0.986701,0,0.985472,0,0.973255,0,0.976604,0,0.984065,0,0.982216,0,0.951392,0.00951383,0.9599,0,0.964462,0,0.93544,0.0276519,0.956166,0.000487048,0.920208,0.0437864,0.979482,0,0.975614,0,0.954919,0.000540599,0.954088,0.00215517,0.97438,0,0.97472,0,0.915473,0.0459452,0.913995,0.0491862,0.957414,0,0.921504,0.0381623,0.96038,0,0.927531,0.0311131,0.97603,0,0.977647,0,0.966941,0,0.963519,0,0.979187,0,0.980788,0,0.936765,0.0222495,0.931743,0.0268233,0.970503,0,0.944193,0.0151215,0.974209,0,0.952463,0.00664173,0.982616,0,0.984629,0,0.983446,0,0.978776,0,0.986647,0,0.988236,0,0.970084,0,0.961319,0,0.986476,0,0.977356,0,0.989471,0,0.976173,0,0.977347,0,0.940479,0.0594629,0.939615,0.0603224,0.979289,0,0.945924,0.0540237,0.992024,0,0.992672,0,0.993231,0,0.97586,0,0.991215,0,0.975669,0,0.990088,0,0.942274,0.0576593,0.944676,0.0552501,0.985791,0,0.984037,0,0.967694,0.0145367,0.962898,0.0241227,0.97922,0,0.964259,0.021401,0.992582,0,0.990785,0,0.987804,0,0.982648,0,0.993309,0,0.954308,0.0412919,0.961753,0.0263685,0.927056,0.0728678,0.824646,0.175288,0.905487,0.0944558,0.888387,0.111528,0.804377,0.195551,0.975197,0,0.963199,0.0234483,0.948767,0.0511432,0.971576,0.00674664,0.982693,0,0.947589,0.0523636,0.868154,0.131739,0.86429,0.135592,0.773925,0.225951,0.785476,0.214418,0.86561,0.134263,0.776728,0.223135,0.927991,0.0719004,0.923634,0.0762501,0.923432,0.0764463,0.874415,0.125491,0.9361,0.0638002,0.797831,0.202084,0.890117,0.10974,0.900687,0.0991667,0.839821,0.160016,0.816305,0.183536,0.912028,0.0878252,0.861408,0.138428,0.931944,0.0679265,0.938314,0.0615536,0.945699,0.0541673,0.875847,0.124017,0.926947,0.0729258,0.793266,0.206584,0.93779,0.0620611,0.946177,0.0536731,0.915658,0.0841822,0.900595,0.0992432,0.95349,0.0427196,0.927856,0.0719881,0.959797,0.0301248,0.965574,0.0185614,0.970877,0.00794844,0.925741,0.0741118,0.953177,0.0433737,0.882172,0.117665,0.966434,0.0168551,0.969922,0.0098936,0.945052,0.0548101,0.94265,0.0572064,0.97208,0.00559348,0.94462,0.0552493,0.979669,0,0.982088,0,0.983854,0,0.960642,0.0284267,0.975991,0,0.936741,0.0631094,0.976159,0,0.976391,0,0.945535,0.0543789,0.94548,0.0544182,0.987252,0,0.988727,0,0.974054,0.00167263,0.985496,0,0.944885,0.0549966,0.357395,0.353925,0.416332,0.348184,0.439769,0.359135,0.431328,0.30969,0.405567,0.289633,0.466375,0.274089,0.342413,0.276733,0.325874,0.291479,0.398595,0.231339,0.998874,0,0.999163,0,0.999198,0,0.998939,0,0.999303,0,0.999327,0,0.999262,0,0.99904,0,0.99934,0,0.998844,0,0.999127,0,0.998855,0,0.999121,0,0.999265,0,0.999227,0,0.99834,0,0.998305,0,0.998358,0,0.998463,0,0.998656,0,0.998015,0,0.998411,0,0.998296,0,0.997851,0,0.998644,0,0.998552,0,0.998232,0,0.997767,0,0.99846,0,0.998204,0,0.998514,0,0.998392,0,0.99863,0,0.998705,0,0.998735,0,0.997376,0,0.997693,0,0.997997,0,0.997095,0,0.996931,0,0.997968,0,0.998408,0,0.998533,0,0.998135,0,0.998652,0,0.998755,0,0.998665,0,0.998327,0,0.998853,0,0.997818,0,0.998294,0,0.998552,0,0.997104,0,0.996942,0,0.997382,0,0.997714,0,0.99871,0,0.998912,0,0.999017,0,0.998862,0,0.999033,0,0.999115,0,0.999101,0,0.998968,0,0.999184,0,0.998532,0,0.998792,0,0.998944,0,0.998346,0,0.998046,0,0.998587,0,0.998734,0,0.999026,0,0.999171,0,0.999155,0,0.998937,0,0.999254,0,0.999252,0,0.999022,0,0.999155,0,0.999234,0,0.998652,0,0.998756,0,0.99849,0,0.998883,0,0.998927,0,0.999008,0,0.998963,0,0.998932,0,0.999029,0,0.999083,0,0.999035,0,0.999115,0,0.998784,0,0.998831,0,0.998833,0,0.9988,0,0.998698,0,0.998886,0,0.998964,0,0.999171,0,0.999217,0,0.999277,0,0.999233,0,0.999263,0,0.999324,0,0.999328,0,0.999287,0,0.999377,0,0.999104,0,0.999152,0,0.999192,0,0.999114,0,0.999041,0,0.999183,0,0.999242,0,0.999351,0,0.999382,0,0.99937,0,0.999347,0,0.999399,0,0.999381,0,0.999328,0,0.999366,0,0.999405,0,0.999312,0,0.999287,0,0.999309,0,0.998814,0,0.998601,0,0.998913,0,0.998998,0,0.999191,0,0.999167,0,0.998596,0,0.99829,0,0.998898,0,0.998766,0,0.999026,0,0.999143,0,0.999417,0,0.999536,0,0.999585,0,0.999479,0,0.999554,0,0.999488,0,0.999314,0,0.999403,0,0.999325,0,0.999241,0,0.999389,0,0.999432,0,0.999402,0,0.99921,0,0.998924,0,0.999218,0,0.999467,0,0.999438,0,0.999414,0,0.999442,0,0.999358,0,0.999249,0,0.999454,0,0.998941,0,0.996711,0,0.998402,0,0.997807,0,0.995777,0,0.99459,0,0.996958,0,0.995899,0,0.99322,0,0.997524,0,0.99738,0,0.998316,0,0.998523,0,0.997576,0,0.997613,0,0.998702,0,0.998788,0,0.99937,0,0.999263,0,0.999546,0,0.999631,0,0.99653,0,0.996271,0,0.997024,0,0.997341,0,0.997088,0,0.996806,0,0.997673,0,0.997993,0,0.998621,0,0.998311,0,0.998768,0,0.999037,0,0.995846,0,0.995714,0,0.99648,0,0.996603,0,0.996121,0,0.995971,0,0.99672,0,0.996872,0,0.997497,0,0.997358,0,0.997881,0,0.998004,0,0.996795,0,0.997579,0,0.996915,0,0.994871,0,0.995907,0,0.996107,0,0.995174,0,0.99546,0,0.996307,0,0.986768,0,0.989126,0,0.989884,0,0.988124,0,0.987416,0,0.990479,0,0.991211,0,0.994025,0,0.993471,0,0.995995,0,0.996288,0,0.96315,0,0.961423,0,0.965076,0,0.970785,0,0.966985,0,0.968795,0,0.972517,0,0.974221,0,0.97059,0,0.972341,0,0.975847,0,0.975303,0,0.973513,0,0.976184,0,0.981168,0,0.978238,0,0.978998,0,0.981788,0,0.982339,0,0.979667,0,0.980259,0,0.982837,0,0.977879,0,0.977937,0,0.980552,0,0.980496,0,0.977756,0,0.977815,0,0.980437,0,0.980374,0,0.982923,0,0.982974,0,0.985391,0,0.985358,0,0.974384,0,0.973062,0,0.976654,0,0.977735,0,0.976849,0,0.975679,0,0.978767,0,0.979686,0,0.982437,0,0.981767,0,0.984582,0,0.985026,0,0.969326,0,0.967603,0,0.972147,0,0.973496,0,0.971938,0,0.970755,0,0.974683,0,0.975694,0,0.979501,0,0.978716,0,0.98264,0,0.936036,0,0.936028,0,0.935988,0,0.93599,0,0.936125,0,0.936065,0,0.936014,0,0.936072,0,0.936052,0,0.935999,0,0.93601,0,0.936053,0,0.989909,0,0.986967,0,0.984243,0,0.987205,0,0.984605,0,0.982117,0,0.980317,0,0.982375,0,0.980402,0,0.978776,0,0.973733,0,0.973759,0,0.973794,0,0.9738,0,0.973728,0,0.973722,0,0.9738,0,0.973801,0,0.973844,0,0.973846,0,0.973861,0,0.973857,0,0.984888,0,0.983596,0,0.986255,0,0.989417,0,0.98729,0,0.988868,0,0.99115,0,0.99288,0,0.99042,0,0.991874,0,0.994521,0,0.993333,0,0.995543,0,0.995701,0,0.993366,0,0.987892,0,0.990736,0,0.990609,0,0.987613,0,0.987321,0,0.990501,0,0.990336,0,0.986966,0,0.993018,0,0.994998,0,0.995121,0,0.993098,0,0.988178,0,0.990718,0,0.990746,0,0.988163,0,0.98816,0,0.990798,0,0.990827,0,0.988134,0,0.991698,0,0.993696,0,0.994121,0,0.992137,0,0.986878,0,0.989418,0,0.989856,0,0.987342,0,0.98777,0,0.990277,0,0.990659,0,0.988162,0,0.956516,0,0.956547,0,0.956529,0,0.956534,0,0.956514,0,0.95653,0,0.956558,0,0.956607,0,0.95657,0,0.956631,0,0.956676,0,0.97061,0,0.969126,0,0.972047,0,0.977241,0,0.973873,0,0.975211,0,0.978468,0,0.979529,0,0.976355,0,0.977405,0,0.980474,0,0.992425,0,0.991785,0,0.98787,0,0.988723,0,0.993221,0,0.992894,0,0.98939,0,0.989931,0,0.986356,0,0.985616,0,0.981737,0,0.982679,0,0.979077,0,0.97451,0,0.976196,0,0.980707,0,0.987646,0,0.983488,0,0.985076,0,0.989194,0,0.990586,0,0.986561,0,0.961166,0,0.957032,0,0.960895,0,0.968325,0,0.964801,0,0.964661,0,0.968337,0,0.96861,0,0.96481,0,0.96518,0,0.969064,0,0.922181,0,0.922369,0,0.922188,0,0.922015,0,0.922072,0,0.92207,0,0.922008,0,0.92205,0,0.922114,0,0.922205,0,0.92213,0,0.959758,0,0.959689,0,0.959657,0,0.95978,0,0.959747,0,0.95966,0,0.95971,0,0.959669,0,0.959616,0,0.959594,0,0.959646,0,0.958589,0,0.956906,0,0.951276,0,0.954625,0,0.953403,0,0.950525,0,0.949956,0,0.952478,0,0.951829,0,0.949525,0,0.959867,0,0.961973,0,0.960257,0,0.952629,0,0.956501,0,0.957039,0,0.952777,0,0.952447,0,0.956455,0,0.955971,0,0.952168,0,0.954787,0,0.959539,0,0.956428,0,0.949508,0,0.952153,0,0.953474,0,0.950399,0,0.951391,0,0.954682,0,0.955944,0,0.952455,0,0.952834,0,0.952047,0,0.952253,0,0.949267,0,0.95151,0,0.95144,0,0.949222,0,0.94904,0,0.9513,0,0.951194,0,0.948891,0,0.964805,0,0.965738,0,0.96741,0,0.966081,0,0.963378,0,0.964071,0,0.964966,0,0.963891,0,0.964414,0,0.965676,0,0.966221,0,0.964846,0,0.963252,0,0.962536,0,0.96284,0,0.963246,0,0.962953,0,0.962758,0,0.962824,0,0.963045,0,0.963342,0,0.963039,0,0.967332,0,0.966408,0,0.963842,0,0.965308,0,0.964694,0,0.96348,0,0.96319,0,0.964126,0,0.96363,0,0.962986,0,0.967691,0,0.968379,0,0.967825,0,0.964766,0,0.966285,0,0.966401,0,0.964697,0,0.964458,0,0.966165,0,0.965915,0,0.964225,0,0.951722,0,0.951663,0,0.942647,0,0.947013,0,0.946726,0,0.942095,0,0.941694,0,0.946785,0,0.946973,0,0.941522,0,0.950937,0,0.951019,0,0.943239,0,0.947742,0,0.94834,0,0.943528,0,0.943305,0,0.947818,0,0.947588,0,0.943267,0,0.953702,0,0.957514,0,0.952805,0,0.943402,0,0.948537,0,0.948071,0,0.943241,0,0.943145,0,0.94762,0,0.947387,0,0.943159,0,0.952733,0,0.958798,0,0.953263,0,0.94192,0,0.947405,0,0.947871,0,0.942592,0,0.943116,0,0.948432,0,0.949047,0,0.943598,0,0.973086,0,0.975228,0,0.975179,0,0.973016,0,0.968865,0,0.970959,0,0.970918,0,0.96888,0,0.968927,0,0.970863,0,0.970757,0,0.968937,0,0.969907,0,0.971161,0,0.972621,0,0.971028,0,0.967395,0,0.968663,0,0.969463,0,0.967825,0,0.968359,0,0.970238,0,0.970998,0,0.968851,0,0.969039,0,0.968335,0,0.967621,0,0.968479,0,0.968073,0,0.967321,0,0.96717,0,0.967972,0,0.968108,0,0.967131,0,0.972214,0,0.971563,0,0.968708,0,0.970365,0,0.969927,0,0.968454,0,0.968254,0,0.969515,0,0.969141,0,0.968075,0,0.940751,0,0.940981,0,0.943485,0,0.943143,0,0.940313,0,0.940512,0,0.942798,0,0.942549,0,0.9451,0,0.945365,0,0.94783,0,0.947448,0,0.940656,0,0.940555,0,0.943035,0,0.943103,0,0.940897,0,0.940754,0,0.943242,0,0.943406,0,0.946193,0,0.9461,0,0.948846,0,0.948742,0,0.944193,0,0.946866,0,0.94738,0,0.94462,0,0.939638,0,0.941786,0,0.942188,0,0.939922,0,0.940236,0,0.942599,0,0.944706,0,0.9469,0,0.946734,0,0.94442,0,0.939895,0,0.942066,0,0.941771,0,0.939652,0,0.939506,0,0.941574,0,0.941421,0,0.939366,0,0.961618,0,0.962557,0,0.962799,0,0.961766,0,0.959446,0,0.960582,0,0.960755,0,0.959607,0,0.959827,0,0.960977,0,0.961192,0,0.960049,0,0.961024,0,0.962032,0,0.962045,0,0.961106,0,0.958941,0,0.959976,0,0.960056,0,0.959003,0,0.959129,0,0.960217,0,0.960481,0,0.959327,0,0.959208,0,0.959374,0,0.960613,0,0.9604,0,0.958961,0,0.95907,0,0.960192,0,0.960029,0,0.961119,0,0.961291,0,0.962372,0,0.962197,0,0.959884,0,0.961052,0,0.959548,0,0.959718,0,0.960917,0,0.960776,0,0.961795,0,0.961916,0,0.963005,0,0.962853,0,0.927347,0,0.925154,0,0.925128,0,0.927398,0,0.933898,0,0.930478,0,0.930581,0,0.934052,0,0.934317,0,0.93089,0,0.931336,0,0.934526,0,0.922239,0,0.922196,0,0.922673,0,0.922714,0,0.922525,0,0.922367,0,0.922878,0,0.923041,0,0.923794,0,0.923723,0,0.925162,0,0.922124,0,0.92223,0,0.922663,0,0.922544,0,0.922089,0,0.922058,0,0.922458,0,0.922535,0,0.923599,0,0.923513,0,0.925312,0,0.927615,0,0.925285,0,0.925232,0,0.927496,0,0.934097,0,0.930681,0,0.930567,0,0.933892,0,0.933807,0,0.930486,0,0.930433,0,0.933788,0,0.965745,0,0.966976,0,0.967097,0,0.965899,0,0.963313,0,0.964534,0,0.964732,0,0.963534,0,0.963732,0,0.964889,0,0.964987,0,0.963857,0,0.96268,0,0.962551,0,0.963677,0,0.963792,0,0.962932,0,0.962787,0,0.963904,0,0.964094,0,0.965293,0,0.965063,0,0.966218,0,0.966537,0,0.965388,0,0.966506,0,0.966233,0,0.965181,0,0.963048,0,0.964253,0,0.964056,0,0.962875,0,0.962721,0,0.963876,0,0.963704,0,0.964834,0,0.963369,0,0.963532,0,0.964675,0,0.96455,0,0.965694,0,0.965801,0,0.967028,0,0.966916,0,0.936156,0,0.936274,0,0.936722,0,0.936565,0,0.936061,0,0.936079,0,0.936447,0,0.936405,0,0.937191,0,0.937279,0,0.938703,0,0.938551,0,0.936416,0,0.936512,0,0.936928,0,0.936851,0,0.936298,0,0.936336,0,0.936766,0,0.936747,0,0.937613,0,0.937591,0,0.938825,0,0.938946,0,0.936408,0,0.936208,0,0.936157,0,0.936319,0,0.937449,0,0.936787,0,0.936642,0,0.93726,0,0.93715,0,0.936585,0,0.936532,0,0.937042,0,0.936164,0,0.936092,0,0.937003,0,0.936432,0,0.936343,0,0.936968,0,0.937032,0,0.936358,0,0.936402,0,0.937129,0,0.956861,0,0.956785,0,0.957851,0,0.957229,0,0.957112,0,0.957702,0,0.957621,0,0.957076,0,0.957037,0,0.957534,0,0.956684,0,0.956622,0,0.957383,0,0.95692,0,0.956812,0,0.957246,0,0.957181,0,0.956782,0,0.956773,0,0.957138,0,0.956556,0,0.95661,0,0.956821,0,0.956744,0,0.95656,0,0.956539,0,0.956706,0,0.956733,0,0.957145,0,0.95716,0,0.957985,0,0.957876,0,0.956827,0,0.956952,0,0.957339,0,0.957171,0,0.956658,0,0.956725,0,0.957012,0,0.956909,0,0.957457,0,0.957602,0,0.95853,0,0.958356,0,0.959679,0,0.959809,0,0.960838,0,0.959984,0,0.960126,0,0.960894,0,0.961013,0,0.960332,0,0.960529,0,0.961109,0,0.960078,0,0.960092,0,0.960401,0,0.960425,0,0.960119,0,0.960078,0,0.96043,0,0.960492,0,0.961073,0,0.961004,0,0.961822,0,0.961915,0,0.960022,0,0.959835,0,0.959786,0,0.959951,0,0.960878,0,0.960333,0,0.960251,0,0.960838,0,0.960831,0,0.960211,0,0.960178,0,0.960842,0,0.959578,0,0.95962,0,0.959913,0,0.959846,0,0.959706,0,0.959603,0,0.959855,0,0.959992,0,0.960729,0,0.96066,0,0.96222,0,0.962093,0,0.998001,0,0.997437,0,0.99858,0,0.999489,0,0.998952,0,0.999251,0,0.999625,0,0.999723,0,0.99947,0,0.999628,0,0.999792,0,0.999551,0,0.999687,0,0.99985,0,0.999743,0,0.999812,0,0.999881,0,0.999892,0,0.999844,0,0.99985,0,0.999888,0,0.999752,0,0.999685,0,0.999821,0,0.999767,0,0.999752,0,0.999689,0,0.999591,0,0.999668,0,0.99886,0,0.998628,0,0.998959,0,0.999125,0,0.999293,0,0.999089,0,0.999299,0,0.999456,0,0.999557,0,0.999714,0,0.998323,0,0.998241,0,0.998655,0,0.998714,0,0.998517,0,0.998417,0,0.998791,0,0.998869,0,0.998208,0,0.998185,0,0.998637,0,0.998632,0,0.998627,0,0.99863,0,0.998099,0,0.997184,0,0.998038,0,0.998654,0,0.998673,0,0.998674,0,0.998662,0,0.997702,0,0.997595,0,0.998664,0,0.998664,0,0.998621,0,0.998578,0,0.976392,0,0.976678,0,0.976271,0,0.976027,0,0.977803,0,0.976767,0,0.976043,0,0.976499,0,0.975556,0,0.975362,0,0.974796,0,0.974909,0,0.981644,0,0.980761,0,0.976266,0,0.978819,0,0.978246,0,0.97608,0,0.975881,0,0.977652,0,0.977087,0,0.975737,0,0.980873,0,0.983842,0,0.984501,0,0.981411,0,0.976231,0,0.978302,0,0.978625,0,0.976264,0,0.976216,0,0.978925,0,0.979184,0,0.976297,0,0.977503,0,0.978491,0,0.975612,0,0.976627,0,0.977096,0,0.97561,0,0.975747,0,0.977599,0,0.978062,0,0.976137,0,0.974147,0,0.97412,0,0.973675,0,0.973789,0,0.973778,0,0.973666,0,0.97371,0,0.973852,0,0.973976,0,0.973792,0,0.974163,0,0.974848,0,0.974823,0,0.974119,0,0.973817,0,0.973883,0,0.973826,0,0.973773,0,0.973732,0,0.973825,0,0.973848,0,0.973719,0,0.974227,0,0.974744,0,0.974759,0,0.974172,0,0.973878,0,0.973964,0,0.973915,0,0.973858,0,0.97385,0,0.973914,0,0.97392,0,0.973847,0,0.974278,0,0.975076,0,0.975094,0,0.974247,0,0.973799,0,0.97395,0,0.973948,0,0.973824,0,0.973874,0,0.973994,0,0.974017,0,0.973901,0,0.996787,0,0.995655,0,0.991598,0,0.994584,0,0.993137,0,0.989907,0,0.988193,0,0.991574,0,0.989948,0,0.986522,0,0.981424,0,0.980089,0,0.977425,0,0.978362,0,0.984415,0,0.982865,0,0.979407,0,0.980587,0,0.976529,0,0.975742,0,0.971887,0,0.972282,0,0.974051,0,0.972613,0,0.972131,0,0.974013,0,0.976758,0,0.975468,0,0.975855,0,0.977592,0,0.978786,0,0.976592,0,0.976057,0,0.974488,0,0.973771,0,0.974997,0,0.978211,0,0.977297,0,0.97597,0,0.976682,0,0.975987,0,0.975381,0,0.975216,0,0.976104,0,0.9632,0,0.962514,0,0.969824,0,0.96666,0,0.966011,0,0.969221,0,0.968797,0,0.965503,0,0.965142,0,0.968502,0,0.961598,0,0.962237,0,0.970809,0,0.966469,0,0.966611,0,0.970495,0,0.970422,0,0.966935,0,0.967391,0,0.970474,0,0.966054,0,0.964434,0,0.975457,0,0.970813,0,0.96938,0,0.97408,0,0.972706,0,0.967946,0,0.966577,0,0.971419,0,0.979065,0,0.977551,0,0.979135,0,0.980882,0,0.982243,0,0.980675,0,0.98272,0,0.984444,0,0.986182,0,0.984293,0,0.985657,0,0.987642,0,0.990509,0,0.991206,0,0.988931,0,0.988372,0,0.988843,0,0.989739,0,0.987721,0,0.986932,0,0.984589,0,0.985336,0,0.995551,0,0.99604,0,0.992246,0,0.993955,0,0.994661,0,0.993135,0,0.993886,0,0.995229,0,0.995658,0,0.994509,0,0.922293,0,0.922411,0,0.92367,0,0.922735,0,0.922877,0,0.923749,0,0.92386,0,0.923045,0,0.923149,0,0.923917,0,0.934643,0,0.93488,0,0.931129,0,0.931006,0,0.934444,0,0.93448,0,0.930887,0,0.930864,0,0.927794,0,0.927781,0,0.934454,0,0.931363,0,0.934739,0,0.934492,0,0.931229,0,0.931073,0,0.927743,0,0.927725,0,0.922685,0,0.922664,0,0.923914,0,0.923175,0,0.923146,0,0.923855,0,0.923804,0,0.923103,0,0.923127,0,0.923835,0,0.999174,0,0.99904,0,0.999409,0,0.998781,0,0.997407,0,0.995167,0,0.994141,0,0.992888,0,0.991427,0,0.989828,0,0.995995,0,0.99587,0,0.995598,0,0.993201,0,0.972819,0,0.983489,0,0.984031,0,0.984502,0,0.984924,0,0.985298,0,0.93611,0,0.936098,0,0.936144,0,0.936202,0,0.936281,0,0.982599,0,0.980871,0,0.979259,0,0.977939,0,0.976886,0,0.973694,0,0.973738,0,0.973676,0,0.973683,0,0.973712,0,0.973752,0,0.973799,0,0.973832,0,0.973848,0,0.989307,0,0.991205,0,0.993077,0,0.994905,0,0.956515,0,0.95651,0,0.956518,0,0.956544,0,0.956567,0,0.956604,0,0.956663,0,0.956744,0,0.977418,0,0.981837,0,0.986004,0,0.92224,0,0.922136,0,0.922072,0,0.922044,0,0.921996,0,0.921991,0,0.922036,0,0.922112,0,0.959899,0,0.95987,0,0.959869,0,0.959895,0,0.947138,0,0.962073,0,0.938825,0,0.938287,0,0.937776,0,0.937406,0,0.937174,0,0.938906,0,0.938629,0,0.938431,0,0.938531,0,0.93837,0,0.938385,0,0.938433,0,0.938628,0,0.937248,0,0.937472,0,0.937948,0,0.966823,0,0.966819,0,0.938393,0,0.940122,0,0.94233,0,0.944914,0,0.937799,0,0.937994,0,0.938178,0,0.938413,0,0.938184,0,0.938013,0,0.937905,0,0.958268,0,0.958404,0,0.958545,0,0.958735,0,0.961005,0,0.959925,0,0.958864,0,0.957756,0,0.957859,0,0.957968,0,0.958105,0,0.92779,0,0.930859,0,0.934427,0,0.965528,0,0.964278,0,0.963062,0,0.961969,0,0.962136,0,0.962275,0,0.962427,0,0.965618,0,0.964454,0,0.963228,0,0.961991,0,0.961866,0,0.961767,0,0.961691,0,0.999877,0,0.999905,0,0.999102,0,0.976262,0,0.977096,0,0.978857,0,0.987955,0,0.986086,0,0.984289,0,0.97557,0,0.979733,0,0.978324,0,0.976905,0,0.983664,0,0.985999,0,0.987841,0,0.992371,0,0.991443,0,0.990422,0,0.999308,0], + "morphTargets" : [], + + "bones" : [{"parent":-1,"name":"Back","pos":[-0.175997,3.31359,0.0585606],"rotq":[0.506417,-0.464193,0.492502,0.534331],"scl":[1.42537,1.42537,1.42537]},{"parent":0,"name":"Shoulder_Left","pos":[-1.39698e-08,-3.95812e-09,-1],"rotq":[-0.712951,-0.0754199,-0.154109,0.6799],"scl":[1,1,1]},{"parent":1,"name":"UpperArm_Left","pos":[-2.49594e-07,3.7998e-07,-0.404642],"rotq":[-0.0699928,-0.295615,-0.639689,0.706052],"scl":[1,1,1]},{"parent":2,"name":"LowerArm_Left","pos":[1.49012e-08,-2.23517e-08,-0.601318],"rotq":[0.15874,0.0198854,0.155466,0.974801],"scl":[1,1,1]},{"parent":0,"name":"Shoulder_Right","pos":[-1.39698e-08,-3.95812e-09,-1],"rotq":[-0.10642,0.727313,-0.66909,0.109585],"scl":[1,1,1]},{"parent":4,"name":"UpperArm_Right","pos":[-7.45058e-09,4.45172e-07,-0.420335],"rotq":[-0.260792,0.133321,0.107538,0.950078],"scl":[1,1,1]},{"parent":5,"name":"LowerArm_RIght","pos":[7.45058e-09,1.46218e-07,-0.579121],"rotq":[0.0456725,0.116094,-0.0137691,0.992092],"scl":[1,1,1]},{"parent":0,"name":"Head","pos":[-1.39698e-08,-3.95812e-09,-1],"rotq":[0.0381816,0.00590503,-0.963357,0.265424],"scl":[1,1,1]},{"parent":-1,"name":"Hip_Right","pos":[-0.175997,3.31359,0.0585606],"rotq":[-0.266047,0.638812,0.265037,0.671486],"scl":[1.42537,1.42537,1.42537]},{"parent":8,"name":"Thigh_RIght","pos":[3.72529e-09,1.2794e-07,-0.344487],"rotq":[-0.304832,0.136549,0.306347,0.891394],"scl":[1,1,1]},{"parent":9,"name":"Shin_Right","pos":[2.98023e-08,-4.84288e-08,-0.835819],"rotq":[-0.0375285,-0.0805944,-0.0685979,0.993675],"scl":[1,1,1]},{"parent":10,"name":"Foot_Right","pos":[-2.6077e-08,-4.65661e-09,-1.10893],"rotq":[0.359933,0.441063,-0.037287,0.821292],"scl":[1,1,1]},{"parent":-1,"name":"Hip_Left","pos":[-0.175997,3.31359,0.0585606],"rotq":[0.494133,-0.488426,-0.716366,0.0639734],"scl":[1.42537,1.42537,1.42537]},{"parent":12,"name":"Thigh_Left","pos":[-1.49012e-08,3.72529e-08,-0.302626],"rotq":[0.279052,-0.216734,-0.138653,0.925166],"scl":[1,1,1]},{"parent":13,"name":"Shin_Left","pos":[-7.45058e-09,-3.53903e-08,-0.891344],"rotq":[-0.0421144,-0.0525503,-0.0811722,0.994422],"scl":[1,1,1]},{"parent":14,"name":"Foot_Left","pos":[9.31323e-09,4.09782e-08,-1.13044],"rotq":[0.405356,0.461482,0.464815,0.637705],"scl":[1,1,1]}], + "animations" : [], + + "colors" : [], + "materials" : [ + { + "DbgColor": 15658734, + "DbgIndex": 0, + "DbgName": "None", + "blending": "NormalBlending", + "colorAmbient": [0.6400000190734865, 0.6400000190734865, 0.6400000190734865], + "colorDiffuse": [0.6400000190734865, 0.6400000190734865, 0.6400000190734865], + "colorEmissive": [0.0, 0.0, 0.0], + "colorSpecular": [0.5, 0.5, 0.5], + "depthTest": true, + "depthWrite": true, + "shading": "Lambert", + "specularCoef": 50, + "transparency": 1.0, + "transparent": false, + "vertexColors": false + } + ] + + + } diff --git a/papi/plugin/dpp/Human/index.html b/papi/plugin/dpp/Human/index.html new file mode 100644 index 00000000..978fa169 --- /dev/null +++ b/papi/plugin/dpp/Human/index.html @@ -0,0 +1,322 @@ + + + + + 3d-Simulation + + + + + + + + + +
+
+ +
+
+ + + + diff --git a/papi/plugin/dpp/Human/js/TrackballControls.js b/papi/plugin/dpp/Human/js/TrackballControls.js new file mode 100755 index 00000000..3fb0f512 --- /dev/null +++ b/papi/plugin/dpp/Human/js/TrackballControls.js @@ -0,0 +1,557 @@ +/** + * @author Eberhard Graether / http://egraether.com/ + */ + +THREE.TrackballControls = function ( object, domElement ) { + + var _this = this; + var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 }; + + this.object = object; + this.domElement = ( domElement !== undefined ) ? domElement : document; + + // API + + this.enabled = true; + + this.screen = { left: 0, top: 0, width: 0, height: 0 }; + + this.rotateSpeed = 1.0; + this.zoomSpeed = 1.2; + this.panSpeed = 0.3; + + this.noRotate = false; + this.noZoom = false; + this.noPan = false; + this.noRoll = false; + + this.staticMoving = false; + this.dynamicDampingFactor = 0.2; + + this.minDistance = 0; + this.maxDistance = Infinity; + + this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; + + // internals + + this.target = new THREE.Vector3(); + + var lastPosition = new THREE.Vector3(); + + var _state = STATE.NONE, + _prevState = STATE.NONE, + + _eye = new THREE.Vector3(), + + _rotateStart = new THREE.Vector3(), + _rotateEnd = new THREE.Vector3(), + + _zoomStart = new THREE.Vector2(), + _zoomEnd = new THREE.Vector2(), + + _touchZoomDistanceStart = 0, + _touchZoomDistanceEnd = 0, + + _panStart = new THREE.Vector2(), + _panEnd = new THREE.Vector2(); + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.up0 = this.object.up.clone(); + + // events + + var changeEvent = { type: 'change' }; + + + // methods + + this.handleResize = function () { + + if ( this.domElement === document ) { + + this.screen.left = 0; + this.screen.top = 0; + this.screen.width = window.innerWidth; + this.screen.height = window.innerHeight; + + } else { + + this.screen = this.domElement.getBoundingClientRect(); + + } + + }; + + this.handleEvent = function ( event ) { + + if ( typeof this[ event.type ] == 'function' ) { + + this[ event.type ]( event ); + + } + + }; + + this.getMouseOnScreen = function ( clientX, clientY ) { + + return new THREE.Vector2( + ( clientX - _this.screen.left ) / _this.screen.width, + ( clientY - _this.screen.top ) / _this.screen.height + ); + + }; + + this.getMouseProjectionOnBall = function ( clientX, clientY ) { + + var mouseOnBall = new THREE.Vector3( + ( clientX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width*.5), + ( _this.screen.height * 0.5 + _this.screen.top - clientY ) / (_this.screen.height*.5), + 0.0 + ); + + var length = mouseOnBall.length(); + + if ( _this.noRoll ) { + + if ( length < Math.SQRT1_2 ) { + + mouseOnBall.z = Math.sqrt( 1.0 - length*length ); + + } else { + + mouseOnBall.z = .5 / length; + + } + + } else if ( length > 1.0 ) { + + mouseOnBall.normalize(); + + } else { + + mouseOnBall.z = Math.sqrt( 1.0 - length * length ); + + } + + _eye.copy( _this.object.position ).sub( _this.target ); + + var projection = _this.object.up.clone().setLength( mouseOnBall.y ); + projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) ); + projection.add( _eye.setLength( mouseOnBall.z ) ); + + return projection; + + }; + + this.rotateCamera = function () { + + var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() ); + + if ( angle ) { + + var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(), + quaternion = new THREE.Quaternion(); + + angle *= _this.rotateSpeed; + + quaternion.setFromAxisAngle( axis, -angle ); + + _eye.applyQuaternion( quaternion ); + _this.object.up.applyQuaternion( quaternion ); + + _rotateEnd.applyQuaternion( quaternion ); + + if ( _this.staticMoving ) { + + _rotateStart.copy( _rotateEnd ); + + } else { + + quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) ); + _rotateStart.applyQuaternion( quaternion ); + + } + + } + + }; + + this.zoomCamera = function () { + + if ( _state === STATE.TOUCH_ZOOM ) { + + var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; + _touchZoomDistanceStart = _touchZoomDistanceEnd; + _eye.multiplyScalar( factor ); + + } else { + + var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; + + if ( factor !== 1.0 && factor > 0.0 ) { + + _eye.multiplyScalar( factor ); + + if ( _this.staticMoving ) { + + _zoomStart.copy( _zoomEnd ); + + } else { + + _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; + + } + + } + + } + + }; + + this.panCamera = function () { + + var mouseChange = _panEnd.clone().sub( _panStart ); + + if ( mouseChange.lengthSq() ) { + + mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); + + var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x ); + pan.add( _this.object.up.clone().setLength( mouseChange.y ) ); + + _this.object.position.add( pan ); + _this.target.add( pan ); + + if ( _this.staticMoving ) { + + _panStart = _panEnd; + + } else { + + _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); + + } + + } + + }; + + this.checkDistances = function () { + + if ( !_this.noZoom || !_this.noPan ) { + + if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) { + + _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) ); + + } + + if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { + + _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); + + } + + } + + }; + + this.update = function () { + + _eye.subVectors( _this.object.position, _this.target ); + + if ( !_this.noRotate ) { + + _this.rotateCamera(); + + } + + if ( !_this.noZoom ) { + + _this.zoomCamera(); + + } + + if ( !_this.noPan ) { + + _this.panCamera(); + + } + + _this.object.position.addVectors( _this.target, _eye ); + + _this.checkDistances(); + + _this.object.lookAt( _this.target ); + + if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) { + + _this.dispatchEvent( changeEvent ); + + lastPosition.copy( _this.object.position ); + + } + + }; + + this.reset = function () { + + _state = STATE.NONE; + _prevState = STATE.NONE; + + _this.target.copy( _this.target0 ); + _this.object.position.copy( _this.position0 ); + _this.object.up.copy( _this.up0 ); + + _eye.subVectors( _this.object.position, _this.target ); + + _this.object.lookAt( _this.target ); + + _this.dispatchEvent( changeEvent ); + + lastPosition.copy( _this.object.position ); + + }; + + // listeners + + function keydown( event ) { + + if ( _this.enabled === false ) return; + + window.removeEventListener( 'keydown', keydown ); + + _prevState = _state; + + if ( _state !== STATE.NONE ) { + + return; + + } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) { + + _state = STATE.ROTATE; + + } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) { + + _state = STATE.ZOOM; + + } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) { + + _state = STATE.PAN; + + } + + } + + function keyup( event ) { + + if ( _this.enabled === false ) return; + + _state = _prevState; + + window.addEventListener( 'keydown', keydown, false ); + + } + + function mousedown( event ) { + + if ( _this.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + if ( _state === STATE.NONE ) { + + _state = event.button; + + } + + if ( _state === STATE.ROTATE && !_this.noRotate ) { + + _rotateStart = _this.getMouseProjectionOnBall( event.clientX, event.clientY ); + _rotateEnd.copy(_rotateStart) + + } else if ( _state === STATE.ZOOM && !_this.noZoom ) { + + _zoomStart = _this.getMouseOnScreen( event.clientX, event.clientY ); + _zoomEnd.copy(_zoomStart); + + } else if ( _state === STATE.PAN && !_this.noPan ) { + + _panStart = _this.getMouseOnScreen( event.clientX, event.clientY ); + _panEnd.copy(_panStart) + + } + + document.addEventListener( 'mousemove', mousemove, false ); + document.addEventListener( 'mouseup', mouseup, false ); + + } + + function mousemove( event ) { + + if ( _this.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + if ( _state === STATE.ROTATE && !_this.noRotate ) { + + _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY ); + + } else if ( _state === STATE.ZOOM && !_this.noZoom ) { + + _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); + + } else if ( _state === STATE.PAN && !_this.noPan ) { + + _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); + + } + + } + + function mouseup( event ) { + + if ( _this.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + _state = STATE.NONE; + + document.removeEventListener( 'mousemove', mousemove ); + document.removeEventListener( 'mouseup', mouseup ); + + } + + function mousewheel( event ) { + + if ( _this.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + var delta = 0; + + if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 + + delta = event.wheelDelta / 40; + + } else if ( event.detail ) { // Firefox + + delta = - event.detail / 3; + + } + + _zoomStart.y += delta * 0.01; + + } + + function touchstart( event ) { + + if ( _this.enabled === false ) return; + + switch ( event.touches.length ) { + + case 1: + _state = STATE.TOUCH_ROTATE; + _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + case 2: + _state = STATE.TOUCH_ZOOM; + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); + break; + + case 3: + _state = STATE.TOUCH_PAN; + _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + default: + _state = STATE.NONE; + + } + + } + + function touchmove( event ) { + + if ( _this.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + switch ( event.touches.length ) { + + case 1: + _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + case 2: + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ) + break; + + case 3: + _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + default: + _state = STATE.NONE; + + } + + } + + function touchend( event ) { + + if ( _this.enabled === false ) return; + + switch ( event.touches.length ) { + + case 1: + _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + case 2: + _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; + break; + + case 3: + _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + break; + + } + + _state = STATE.NONE; + + } + + this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); + + this.domElement.addEventListener( 'mousedown', mousedown, false ); + + this.domElement.addEventListener( 'mousewheel', mousewheel, false ); + this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox + + this.domElement.addEventListener( 'touchstart', touchstart, false ); + this.domElement.addEventListener( 'touchend', touchend, false ); + this.domElement.addEventListener( 'touchmove', touchmove, false ); + + window.addEventListener( 'keydown', keydown, false ); + window.addEventListener( 'keyup', keyup, false ); + + this.handleResize(); + +}; + +THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype ); diff --git a/papi/plugin/dpp/Human/js/dat.gui.min.js b/papi/plugin/dpp/Human/js/dat.gui.min.js new file mode 100755 index 00000000..89251411 --- /dev/null +++ b/papi/plugin/dpp/Human/js/dat.gui.min.js @@ -0,0 +1,94 @@ +/** + * dat-gui JavaScript Controller Library + * http://code.google.com/p/dat-gui + * + * Copyright 2011 Data Arts Team, Google Creative Lab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(e,a){var a=a||document,c=a.createElement("link");c.type="text/css";c.rel="stylesheet";c.href=e;a.getElementsByTagName("head")[0].appendChild(c)},inject:function(e,a){var a=a||document,c=document.createElement("style");c.type="text/css";c.innerHTML=e;a.getElementsByTagName("head")[0].appendChild(c)}}}(); +dat.utils.common=function(){var e=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(c){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(a[f])||(c[f]=a[f])},this);return c},defaults:function(c){this.each(a.call(arguments,1),function(a){for(var f in a)this.isUndefined(c[f])&&(c[f]=a[f])},this);return c},compose:function(){var c=a.call(arguments);return function(){for(var d=a.call(arguments),f=c.length-1;f>=0;f--)d=[c[f].apply(this,d)];return d[0]}}, +each:function(a,d,f){if(e&&a.forEach===e)a.forEach(d,f);else if(a.length===a.length+0)for(var b=0,n=a.length;b-1?d.length-d.indexOf(".")-1:0};c.superclass=e;a.extend(c.prototype,e.prototype,{setValue:function(a){if(this.__min!==void 0&&athis.__max)a=this.__max;this.__step!==void 0&&a%this.__step!=0&&(a=Math.round(a/this.__step)*this.__step);return c.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__step=a;return this}});return c}(dat.controllers.Controller,dat.utils.common); +dat.controllers.NumberControllerBox=function(e,a,c){var d=function(f,b,e){function h(){var a=parseFloat(l.__input.value);c.isNaN(a)||l.setValue(a)}function j(a){var b=o-a.clientY;l.setValue(l.getValue()+b*l.__impliedStep);o=a.clientY}function m(){a.unbind(window,"mousemove",j);a.unbind(window,"mouseup",m)}this.__truncationSuspended=false;d.superclass.call(this,f,b,e);var l=this,o;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",h); +a.bind(this.__input,"blur",function(){h();l.__onFinishChange&&l.__onFinishChange.call(l,l.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",j);a.bind(window,"mouseup",m);o=b.clientY});a.bind(this.__input,"keydown",function(a){if(a.keyCode===13)l.__truncationSuspended=true,this.blur(),l.__truncationSuspended=false});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype,e.prototype,{updateDisplay:function(){var a=this.__input, +b;if(this.__truncationSuspended)b=this.getValue();else{b=this.getValue();var c=Math.pow(10,this.__precision);b=Math.round(b*c)/c}a.value=b;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); +dat.controllers.NumberControllerSlider=function(e,a,c,d,f){var b=function(d,c,f,e,l){function o(b){b.preventDefault();var d=a.getOffset(g.__background),c=a.getWidth(g.__background);g.setValue(g.__min+(g.__max-g.__min)*((b.clientX-d.left)/(d.left+c-d.left)));return false}function y(){a.unbind(window,"mousemove",o);a.unbind(window,"mouseup",y);g.__onFinishChange&&g.__onFinishChange.call(g,g.getValue())}b.superclass.call(this,d,c,{min:f,max:e,step:l});var g=this;this.__background=document.createElement("div"); +this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",o);a.bind(window,"mouseup",y);o(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};b.superclass=e;b.useDefaultStyles=function(){c.inject(f)};d.extend(b.prototype,e.prototype,{updateDisplay:function(){this.__foreground.style.width= +(this.getValue()-this.__min)/(this.__max-this.__min)*100+"%";return b.superclass.prototype.updateDisplay.call(this)}});return b}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,".slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); +dat.controllers.FunctionController=function(e,a,c){var d=function(c,b,e){d.superclass.call(this,c,b);var h=this;this.__button=document.createElement("div");this.__button.innerHTML=e===void 0?"Fire":e;a.bind(this.__button,"click",function(a){a.preventDefault();h.fire();return false});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};d.superclass=e;c.extend(d.prototype,e.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.__onFinishChange&&this.__onFinishChange.call(this, +this.getValue());this.getValue().call(this.object)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); +dat.controllers.BooleanController=function(e,a,c){var d=function(c,b){d.superclass.call(this,c,b);var e=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){e.setValue(!e.__prev)},false);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};d.superclass=e;c.extend(d.prototype,e.prototype,{setValue:function(a){a=d.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& +this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){this.getValue()===true?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=true):this.__checkbox.checked=false;return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); +dat.color.toString=function(e){return function(a){if(a.a==1||e.isUndefined(a.a)){for(a=a.hex.toString(16);a.length<6;)a="0"+a;return"#"+a}else return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); +dat.color.interpret=function(e,a){var c,d,f=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:e},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return a===null?false:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:e},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); +return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:e},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return a===null?false:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:e}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return a.length!= +3?false:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return a.length!=4?false:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:false},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& +a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:false},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:false},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:false},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){d= +false;var b=arguments.length>1?a.toArray(arguments):arguments[0];a.each(f,function(e){if(e.litmus(b))return a.each(e.conversions,function(e,f){c=e.read(b);if(d===false&&c!==false)return d=c,c.conversionName=f,c.conversion=e,a.BREAK}),a.BREAK});return d}}(dat.color.toString,dat.utils.common); +dat.GUI=dat.gui.GUI=function(e,a,c,d,f,b,n,h,j,m,l,o,y,g,i){function q(a,b,r,c){if(b[r]===void 0)throw Error("Object "+b+' has no property "'+r+'"');c.color?b=new l(b,r):(b=[b,r].concat(c.factoryArgs),b=d.apply(a,b));if(c.before instanceof f)c.before=c.before.__li;t(a,b);g.addClass(b.domElement,"c");r=document.createElement("span");g.addClass(r,"property-name");r.innerHTML=b.property;var e=document.createElement("div");e.appendChild(r);e.appendChild(b.domElement);c=s(a,e,c.before);g.addClass(c,k.CLASS_CONTROLLER_ROW); +g.addClass(c,typeof b.getValue());p(a,c,b);a.__controllers.push(b);return b}function s(a,b,d){var c=document.createElement("li");b&&c.appendChild(b);d?a.__ul.insertBefore(c,params.before):a.__ul.appendChild(c);a.onResize();return c}function p(a,d,c){c.__li=d;c.__gui=a;i.extend(c,{options:function(b){if(arguments.length>1)return c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[i.toArray(arguments)]});if(i.isArray(b)||i.isObject(b))return c.remove(),q(a,c.object,c.property, +{before:c.__li.nextElementSibling,factoryArgs:[b]})},name:function(a){c.__li.firstElementChild.firstElementChild.innerHTML=a;return c},listen:function(){c.__gui.listen(c);return c},remove:function(){c.__gui.remove(c);return c}});if(c instanceof j){var e=new h(c.object,c.property,{min:c.__min,max:c.__max,step:c.__step});i.each(["updateDisplay","onChange","onFinishChange"],function(a){var b=c[a],H=e[a];c[a]=e[a]=function(){var a=Array.prototype.slice.call(arguments);b.apply(c,a);return H.apply(e,a)}}); +g.addClass(d,"has-slider");c.domElement.insertBefore(e.domElement,c.domElement.firstElementChild)}else if(c instanceof h){var f=function(b){return i.isNumber(c.__min)&&i.isNumber(c.__max)?(c.remove(),q(a,c.object,c.property,{before:c.__li.nextElementSibling,factoryArgs:[c.__min,c.__max,c.__step]})):b};c.min=i.compose(f,c.min);c.max=i.compose(f,c.max)}else if(c instanceof b)g.bind(d,"click",function(){g.fakeEvent(c.__checkbox,"click")}),g.bind(c.__checkbox,"click",function(a){a.stopPropagation()}); +else if(c instanceof n)g.bind(d,"click",function(){g.fakeEvent(c.__button,"click")}),g.bind(d,"mouseover",function(){g.addClass(c.__button,"hover")}),g.bind(d,"mouseout",function(){g.removeClass(c.__button,"hover")});else if(c instanceof l)g.addClass(d,"color"),c.updateDisplay=i.compose(function(a){d.style.borderLeftColor=c.__color.toString();return a},c.updateDisplay),c.updateDisplay();c.setValue=i.compose(function(b){a.getRoot().__preset_select&&c.isModified()&&B(a.getRoot(),true);return b},c.setValue)} +function t(a,b){var c=a.getRoot(),d=c.__rememberedObjects.indexOf(b.object);if(d!=-1){var e=c.__rememberedObjectIndecesToControllers[d];e===void 0&&(e={},c.__rememberedObjectIndecesToControllers[d]=e);e[b.property]=b;if(c.load&&c.load.remembered){c=c.load.remembered;if(c[a.preset])c=c[a.preset];else if(c[w])c=c[w];else return;if(c[d]&&c[d][b.property]!==void 0)d=c[d][b.property],b.initialValue=d,b.setValue(d)}}}function I(a){var b=a.__save_row=document.createElement("li");g.addClass(a.domElement, +"has-save");a.__ul.insertBefore(b,a.__ul.firstChild);g.addClass(b,"save-row");var c=document.createElement("span");c.innerHTML=" ";g.addClass(c,"button gears");var d=document.createElement("span");d.innerHTML="Save";g.addClass(d,"button");g.addClass(d,"save");var e=document.createElement("span");e.innerHTML="New";g.addClass(e,"button");g.addClass(e,"save-as");var f=document.createElement("span");f.innerHTML="Revert";g.addClass(f,"button");g.addClass(f,"revert");var m=a.__preset_select=document.createElement("select"); +a.load&&a.load.remembered?i.each(a.load.remembered,function(b,c){C(a,c,c==a.preset)}):C(a,w,false);g.bind(m,"change",function(){for(var b=0;b0){a.preset=this.preset;if(!a.remembered)a.remembered={};a.remembered[this.preset]=z(this)}a.folders={};i.each(this.__folders,function(b, +c){a.folders[c]=b.getSaveObject()});return a},save:function(){if(!this.load.remembered)this.load.remembered={};this.load.remembered[this.preset]=z(this);B(this,false)},saveAs:function(a){if(!this.load.remembered)this.load.remembered={},this.load.remembered[w]=z(this,true);this.load.remembered[a]=z(this);this.preset=a;C(this,a,true)},revert:function(a){i.each(this.__controllers,function(b){this.getRoot().load.remembered?t(a||this.getRoot(),b):b.setValue(b.initialValue)},this);i.each(this.__folders, +function(a){a.revert(a)});a||B(this.getRoot(),false)},listen:function(a){var b=this.__listening.length==0;this.__listening.push(a);b&&E(this.__listening)}});return k}(dat.utils.css,'
\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n
', +".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity 0.1s linear;-o-transition:opacity 0.1s linear;-moz-transition:opacity 0.1s linear;transition:opacity 0.1s linear;border:0;position:absolute;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-x:hidden}.dg.a.has-save ul{margin-top:27px}.dg.a.has-save ul.closed{margin-top:0}.dg.a .save-row{position:fixed;top:0;z-index:1002}.dg li{-webkit-transition:height 0.1s ease-out;-o-transition:height 0.1s ease-out;-moz-transition:height 0.1s ease-out;transition:height 0.1s ease-out}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;overflow:hidden;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li > *{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:9px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==)}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2fa1d6}.dg .cr.number input[type=text]{color:#2fa1d6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2fa1d6}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n", +dat.controllers.factory=function(e,a,c,d,f,b,n){return function(h,j,m,l){var o=h[j];if(n.isArray(m)||n.isObject(m))return new e(h,j,m);if(n.isNumber(o))return n.isNumber(m)&&n.isNumber(l)?new c(h,j,m,l):new a(h,j,{min:m,max:l});if(n.isString(o))return new d(h,j);if(n.isFunction(o))return new f(h,j,"");if(n.isBoolean(o))return new b(h,j)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(e,a,c){var d= +function(c,b){function e(){h.setValue(h.__input.value)}d.superclass.call(this,c,b);var h=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",e);a.bind(this.__input,"change",e);a.bind(this.__input,"blur",function(){h.__onFinishChange&&h.__onFinishChange.call(h,h.getValue())});a.bind(this.__input,"keydown",function(a){a.keyCode===13&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};d.superclass=e;c.extend(d.prototype, +e.prototype,{updateDisplay:function(){if(!a.isActive(this.__input))this.__input.value=this.getValue();return d.superclass.prototype.updateDisplay.call(this)}});return d}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, +dat.controllers.ColorController=function(e,a,c,d,f){function b(a,b,c,d){a.style.background="";f.each(j,function(e){a.style.cssText+="background: "+e+"linear-gradient("+b+", "+c+" 0%, "+d+" 100%); "})}function n(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; +a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var h=function(e,l){function o(b){q(b);a.bind(window,"mousemove",q);a.bind(window, +"mouseup",j)}function j(){a.unbind(window,"mousemove",q);a.unbind(window,"mouseup",j)}function g(){var a=d(this.value);a!==false?(p.__color.__state=a,p.setValue(p.__color.toOriginal())):this.value=p.__color.toString()}function i(){a.unbind(window,"mousemove",s);a.unbind(window,"mouseup",i)}function q(b){b.preventDefault();var c=a.getWidth(p.__saturation_field),d=a.getOffset(p.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c,b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b= +1:b<0&&(b=0);e>1?e=1:e<0&&(e=0);p.__color.v=b;p.__color.s=e;p.setValue(p.__color.toOriginal());return false}function s(b){b.preventDefault();var c=a.getHeight(p.__hue_field),d=a.getOffset(p.__hue_field),b=1-(b.clientY-d.top+document.body.scrollTop)/c;b>1?b=1:b<0&&(b=0);p.__color.h=b*360;p.setValue(p.__color.toOriginal());return false}h.superclass.call(this,e,l);this.__color=new c(this.getValue());this.__temp=new c(0);var p=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement, +false);this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input= +document.createElement("input");this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){a.keyCode===13&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(){a.addClass(this,"drag").bind(window,"mouseup",function(){a.removeClass(p.__selector,"drag")})});var t=document.createElement("div");f.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"}); +f.extend(this.__field_knob.style,{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(this.__color.v<0.5?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});f.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});f.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});f.extend(t.style, +{width:"100%",height:"100%",background:"none"});b(t,"top","rgba(0,0,0,0)","#000");f.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});n(this.__hue_field);f.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",o);a.bind(this.__field_knob,"mousedown",o);a.bind(this.__hue_field,"mousedown", +function(b){s(b);a.bind(window,"mousemove",s);a.bind(window,"mouseup",i)});this.__saturation_field.appendChild(t);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};h.superclass=e;f.extend(h.prototype,e.prototype,{updateDisplay:function(){var a=d(this.getValue()); +if(a!==false){var e=false;f.each(c.COMPONENTS,function(b){if(!f.isUndefined(a[b])&&!f.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return e=true,{}},this);e&&f.extend(this.__color.__state,a)}f.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var h=this.__color.v<0.5||this.__color.s>0.5?255:0,j=255-h;f.extend(this.__field_knob.style,{marginLeft:100*this.__color.s-7+"px",marginTop:100*(1-this.__color.v)-7+"px",backgroundColor:this.__temp.toString(),border:this.__field_knob_border+ +"rgb("+h+","+h+","+h+")"});this.__hue_knob.style.marginTop=(1-this.__color.h/360)*100+"px";this.__temp.s=1;this.__temp.v=1;b(this.__saturation_field,"left","#fff",this.__temp.toString());f.extend(this.__input.style,{backgroundColor:this.__input.value=this.__color.toString(),color:"rgb("+h+","+h+","+h+")",textShadow:this.__input_textShadow+"rgba("+j+","+j+","+j+",.7)"})}});var j=["-moz-","-o-","-webkit-","-ms-",""];return h}(dat.controllers.Controller,dat.dom.dom,dat.color.Color=function(e,a,c,d){function f(a, +b,c){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="RGB")return this.__state[b];n(this,b,c);return this.__state[b]},set:function(a){if(this.__state.space!=="RGB")n(this,b,c),this.__state.space="RGB";this.__state[b]=a}})}function b(a,b){Object.defineProperty(a,b,{get:function(){if(this.__state.space==="HSV")return this.__state[b];h(this);return this.__state[b]},set:function(a){if(this.__state.space!=="HSV")h(this),this.__state.space="HSV";this.__state[b]=a}})}function n(b,c,e){if(b.__state.space=== +"HEX")b.__state[c]=a.component_from_hex(b.__state.hex,e);else if(b.__state.space==="HSV")d.extend(b.__state,a.hsv_to_rgb(b.__state.h,b.__state.s,b.__state.v));else throw"Corrupted color state";}function h(b){var c=a.rgb_to_hsv(b.r,b.g,b.b);d.extend(b.__state,{s:c.s,v:c.v});if(d.isNaN(c.h)){if(d.isUndefined(b.__state.h))b.__state.h=0}else b.__state.h=c.h}var j=function(){this.__state=e.apply(this,arguments);if(this.__state===false)throw"Failed to interpret color arguments";this.__state.a=this.__state.a|| +1};j.COMPONENTS="r,g,b,h,s,v,hex,a".split(",");d.extend(j.prototype,{toString:function(){return c(this)},toOriginal:function(){return this.__state.conversion.write(this)}});f(j.prototype,"r",2);f(j.prototype,"g",1);f(j.prototype,"b",0);b(j.prototype,"h");b(j.prototype,"s");b(j.prototype,"v");Object.defineProperty(j.prototype,"a",{get:function(){return this.__state.a},set:function(a){this.__state.a=a}});Object.defineProperty(j.prototype,"hex",{get:function(){if(!this.__state.space!=="HEX")this.__state.hex= +a.rgb_to_hex(this.r,this.g,this.b);return this.__state.hex},set:function(a){this.__state.space="HEX";this.__state.hex=a}});return j}(dat.color.interpret,dat.color.math=function(){var e;return{hsv_to_rgb:function(a,c,d){var e=a/60-Math.floor(a/60),b=d*(1-c),n=d*(1-e*c),c=d*(1-(1-e)*c),a=[[d,c,b],[n,d,b],[b,d,c],[b,n,d],[c,b,d],[d,b,n]][Math.floor(a/60)%6];return{r:a[0]*255,g:a[1]*255,b:a[2]*255}},rgb_to_hsv:function(a,c,d){var e=Math.min(a,c,d),b=Math.max(a,c,d),e=b-e;if(b==0)return{h:NaN,s:0,v:0}; +a=a==b?(c-d)/e:c==b?2+(d-a)/e:4+(a-c)/e;a/=6;a<0&&(a+=1);return{h:a*360,s:e/b,v:b/255}},rgb_to_hex:function(a,c,d){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,c);return a=this.hex_with_component(a,0,d)},component_from_hex:function(a,c){return a>>c*8&255},hex_with_component:function(a,c,d){return d<<(e=c*8)|a&~(255< type pairs + class2type = {}, + + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + + core_version = "1.9.0", + + // Save a reference to some core methods + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context, rootjQuery ); + }, + + // Used for matching numbers + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, + + // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + + // JSON RegExp + rvalidchars = /^[\],:{}\s]*$/, + rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, + rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g, + rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }, + + // The ready event handler and self cleanup method + DOMContentLoaded = function() { + if ( document.addEventListener ) { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + } else if ( document.readyState === "complete" ) { + // we're here because readyState === "complete" in oldIE + // which is good enough for us to call the dom ready! + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; + +jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: core_version, + + constructor: jQuery, + init: function( selector, context, rootjQuery ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + jQuery.merge( this, jQuery.parseHTML( + match[1], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + // Properties of context are called as methods if possible + if ( jQuery.isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[2] ); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || rootjQuery ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if ( selector.selector !== undefined ) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return core_slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this[ this.length + num ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Add the callback + jQuery.ready.promise().done( fn ); + + return this; + }, + + slice: function() { + return this.pushStack( core_slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + if ( window.$ === jQuery ) { + window.$ = _$; + } + + if ( deep && window.jQuery === jQuery ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function( hold ) { + if ( hold ) { + jQuery.readyWait++; + } else { + jQuery.ready( true ); + } + }, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + + // Trigger any bound ready events + if ( jQuery.fn.trigger ) { + jQuery( document ).trigger("ready").off("ready"); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return jQuery.type(obj) === "function"; + }, + + isArray: Array.isArray || function( obj ) { + return jQuery.type(obj) === "array"; + }, + + isWindow: function( obj ) { + return obj != null && obj == obj.window; + }, + + isNumeric: function( obj ) { + return !isNaN( parseFloat(obj) ) && isFinite( obj ); + }, + + type: function( obj ) { + if ( obj == null ) { + return String( obj ); + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[ core_toString.call(obj) ] || "object" : + typeof obj; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { + return false; + } + + try { + // Not own constructor property must be Object + if ( obj.constructor && + !core_hasOwn.call(obj, "constructor") && + !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + } catch ( e ) { + // IE8,9 Will throw exceptions on certain host objects #9897 + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || core_hasOwn.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + var name; + for ( name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw new Error( msg ); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function( data, context, keepScripts ) { + if ( !data || typeof data !== "string" ) { + return null; + } + if ( typeof context === "boolean" ) { + keepScripts = context; + context = false; + } + context = context || document; + + var parsed = rsingleTag.exec( data ), + scripts = !keepScripts && []; + + // Single tag + if ( parsed ) { + return [ context.createElement( parsed[1] ) ]; + } + + parsed = jQuery.buildFragment( [ data ], context, scripts ); + if ( scripts ) { + jQuery( scripts ).remove(); + } + return jQuery.merge( [], parsed.childNodes ); + }, + + parseJSON: function( data ) { + // Attempt to parse using the native JSON parser first + if ( window.JSON && window.JSON.parse ) { + return window.JSON.parse( data ); + } + + if ( data === null ) { + return data; + } + + if ( typeof data === "string" ) { + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + if ( data ) { + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( rvalidchars.test( data.replace( rvalidescape, "@" ) + .replace( rvalidtokens, "]" ) + .replace( rvalidbraces, "")) ) { + + return ( new Function( "return " + data ) )(); + } + } + } + + jQuery.error( "Invalid JSON: " + data ); + }, + + // Cross-browser xml parsing + parseXML: function( data ) { + var xml, tmp; + if ( !data || typeof data !== "string" ) { + return null; + } + try { + if ( window.DOMParser ) { // Standard + tmp = new DOMParser(); + xml = tmp.parseFromString( data , "text/xml" ); + } else { // IE + xml = new ActiveXObject( "Microsoft.XMLDOM" ); + xml.async = "false"; + xml.loadXML( data ); + } + } catch( e ) { + xml = undefined; + } + if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + // Workarounds based on findings by Jim Driscoll + // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context + globalEval: function( data ) { + if ( data && jQuery.trim( data ) ) { + // We use execScript on Internet Explorer + // We use an anonymous function so that context is window + // rather than jQuery in Firefox + ( window.execScript || function( data ) { + window[ "eval" ].call( window, data ); + } )( data ); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + // args is for internal usage only + each: function( obj, callback, args ) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike( obj ); + + if ( args ) { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.apply( obj[ i ], args ); + + if ( value === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } else { + for ( i in obj ) { + value = callback.call( obj[ i ], i, obj[ i ] ); + + if ( value === false ) { + break; + } + } + } + } + + return obj; + }, + + // Use native String.trim function wherever possible + trim: core_trim && !core_trim.call("\uFEFF\xA0") ? + function( text ) { + return text == null ? + "" : + core_trim.call( text ); + } : + + // Otherwise use our own trimming functionality + function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArraylike( Object(arr) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + core_push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + var len; + + if ( arr ) { + if ( core_indexOf ) { + return core_indexOf.call( arr, elem, i ); + } + + len = arr.length; + i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; + + for ( ; i < len; i++ ) { + // Skip accessing in sparse arrays + if ( i in arr && arr[ i ] === elem ) { + return i; + } + } + } + + return -1; + }, + + merge: function( first, second ) { + var l = second.length, + i = first.length, + j = 0; + + if ( typeof l === "number" ) { + for ( ; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + retVal = !!callback( elems[ i ], i ); + if ( inv !== retVal ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike( elems ), + ret = []; + + // Go through the array, translating each of the items to their + if ( isArray ) { + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + } + + // Flatten any nested arrays + return core_concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = core_slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if ( jQuery.type( key ) === "object" ) { + chainable = true; + for ( i in key ) { + jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !jQuery.isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < length; i++ ) { + fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); + } + } + } + + return chainable ? + elems : + + // Gets + bulk ? + fn.call( elems ) : + length ? fn( elems[0], key ) : emptyGet; + }, + + now: function() { + return ( new Date() ).getTime(); + } +}); + +jQuery.ready.promise = function( obj ) { + if ( !readyList ) { + + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if ( document.readyState === "complete" ) { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout( jQuery.ready ); + + // Standards-based browsers support DOMContentLoaded + } else if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else { + // Ensure firing before onload, maybe late but safe also for iframes + document.attachEvent( "onreadystatechange", DOMContentLoaded ); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var top = false; + + try { + top = window.frameElement == null && document.documentElement; + } catch(e) {} + + if ( top && top.doScroll ) { + (function doScrollCheck() { + if ( !jQuery.isReady ) { + + try { + // Use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + top.doScroll("left"); + } catch(e) { + return setTimeout( doScrollCheck, 50 ); + } + + // and execute any waiting functions + jQuery.ready(); + } + })(); + } + } + } + return readyList.promise( obj ); +}; + +// Populate the class2type map +jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +function isArraylike( obj ) { + var length = obj.length, + type = jQuery.type( obj ); + + if ( jQuery.isWindow( obj ) ) { + return false; + } + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === "array" || type !== "function" && + ( length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj ); +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); +// String to Object options format cache +var optionsCache = {}; + +// Convert String-formatted options into Object-formatted ones and store in cache +function createOptions( options ) { + var object = optionsCache[ options ] = {}; + jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) { + object[ flag ] = true; + }); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + ( optionsCache[ options ] || createOptions( options ) ) : + jQuery.extend( {}, options ); + + var // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function( data ) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if ( list ) { + if ( stack ) { + if ( stack.length ) { + fire( stack.shift() ); + } + } else if ( memory ) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + // First, we save the current length + var start = list.length; + (function add( args ) { + jQuery.each( args, function( _, arg ) { + var type = jQuery.type( arg ); + if ( type === "function" ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && type !== "string" ) { + // Inspect recursively + add( arg ); + } + }); + })( arguments ); + // Do we need to add the callbacks to the + // current firing batch? + if ( firing ) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if ( list ) { + jQuery.each( arguments, function( _, arg ) { + var index; + while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + // Handle firing indexes + if ( firing ) { + if ( index <= firingLength ) { + firingLength--; + } + if ( index <= firingIndex ) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Control if a given callback is in the list + has: function( fn ) { + return jQuery.inArray( fn, list ) > -1; + }, + // Remove all callbacks from the list + empty: function() { + list = []; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( list && ( !fired || stack ) ) { + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; +jQuery.extend({ + + Deferred: function( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], + [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], + [ "notify", "progress", jQuery.Callbacks("memory") ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return jQuery.Deferred(function( newDefer ) { + jQuery.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[ tuple[1] ](function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && jQuery.isFunction( returned.promise ) ) { + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[1] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[0] ] = function() { + deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); + return this; + }; + deferred[ tuple[0] + "With" ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + resolveValues = core_slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, + + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value; + if( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !( --remaining ) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } +}); +jQuery.support = (function() { + + var support, all, a, select, opt, input, fragment, eventName, isSupported, i, + div = document.createElement("div"); + + // Setup + div.setAttribute( "className", "t" ); + div.innerHTML = "
a"; + + // Support tests won't run in some limited or non-browser environments + all = div.getElementsByTagName("*"); + a = div.getElementsByTagName("a")[ 0 ]; + if ( !all || !a || !all.length ) { + return {}; + } + + // First batch of tests + select = document.createElement("select"); + opt = select.appendChild( document.createElement("option") ); + input = div.getElementsByTagName("input")[ 0 ]; + + a.style.cssText = "top:1px;float:left;opacity:.5"; + support = { + // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) + getSetAttribute: div.className !== "t", + + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText instead) + style: /top/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.5/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Check the default checkbox/radio value ("" on WebKit; "on" elsewhere) + checkOn: !!input.value, + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: opt.selected, + + // Tests for enctype support on a form (#6743) + enctype: !!document.createElement("form").enctype, + + // Makes sure cloning an html5 element does not cause problems + // Where outerHTML is undefined, this still works + html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", + + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + boxModel: document.compatMode === "CSS1Compat", + + // Will be defined later + deleteExpando: true, + noCloneEvent: true, + inlineBlockNeedsLayout: false, + shrinkWrapBlocks: false, + reliableMarginRight: true, + boxSizingReliable: true, + pixelPosition: false + }; + + // Make sure checked status is properly cloned + input.checked = true; + support.noCloneChecked = input.cloneNode( true ).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Support: IE<9 + try { + delete div.test; + } catch( e ) { + support.deleteExpando = false; + } + + // Check if we can trust getAttribute("value") + input = document.createElement("input"); + input.setAttribute( "value", "" ); + support.input = input.getAttribute( "value" ) === ""; + + // Check if an input maintains its value after becoming a radio + input.value = "t"; + input.setAttribute( "type", "radio" ); + support.radioValue = input.value === "t"; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute( "checked", "t" ); + input.setAttribute( "name", "t" ); + + fragment = document.createDocumentFragment(); + fragment.appendChild( input ); + + // Check if a disconnected checkbox will retain its checked + // value of true after appended to the DOM (IE6/7) + support.appendChecked = input.checked; + + // WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE<9 + // Opera does not clone events (and typeof div.attachEvent === undefined). + // IE9-10 clones events bound via attachEvent, but they don't trigger with .click() + if ( div.attachEvent ) { + div.attachEvent( "onclick", function() { + support.noCloneEvent = false; + }); + + div.cloneNode( true ).click(); + } + + // Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event) + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP), test/csp.php + for ( i in { submit: true, change: true, focusin: true }) { + div.setAttribute( eventName = "on" + i, "t" ); + + support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false; + } + + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, marginDiv, tds, + divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;", + body = document.getElementsByTagName("body")[0]; + + if ( !body ) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement("div"); + container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px"; + + body.appendChild( container ).appendChild( div ); + + // Support: IE8 + // Check if table cells still have offsetWidth/Height when they are set + // to display:none and there are still other visible table cells in a + // table row; if so, offsetWidth/Height are not reliable for use when + // determining if an element has been hidden directly using + // display:none (it is still safe to use offsets if a parent element is + // hidden; don safety goggles and see bug #4512 for more information). + div.innerHTML = "
t
"; + tds = div.getElementsByTagName("td"); + tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none"; + isSupported = ( tds[ 0 ].offsetHeight === 0 ); + + tds[ 0 ].style.display = ""; + tds[ 1 ].style.display = "none"; + + // Support: IE8 + // Check if empty table cells still have offsetWidth/Height + support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); + + // Check box-sizing and margin behavior + div.innerHTML = ""; + div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;"; + support.boxSizing = ( div.offsetWidth === 4 ); + support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 ); + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if ( window.getComputedStyle ) { + support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%"; + support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px"; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild( document.createElement("div") ); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = "0"; + div.style.width = "1px"; + + support.reliableMarginRight = + !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight ); + } + + if ( typeof div.style.zoom !== "undefined" ) { + // Support: IE<8 + // Check if natively block-level elements act like inline-block + // elements when setting their display to 'inline' and giving + // them layout + div.innerHTML = ""; + div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1"; + support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); + + // Support: IE6 + // Check if elements with layout shrink-wrap their children + div.style.display = "block"; + div.innerHTML = "
"; + div.firstChild.style.width = "5px"; + support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); + + // Prevent IE 6 from affecting layout for positioned elements #11048 + // Prevent IE from shrinking the body in IE 7 mode #12869 + body.style.zoom = 1; + } + + body.removeChild( container ); + + // Null elements to avoid leaks in IE + container = div = tds = marginDiv = null; + }); + + // Null elements to avoid leaks in IE + all = select = fragment = opt = a = input = null; + + return support; +})(); + +var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + +function internalData( elem, name, data, pvt /* Internal Use Only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, ret, + internalKey = jQuery.expando, + getByName = typeof name === "string", + + // We have to handle DOM nodes and JS objects differently because IE6-7 + // can't GC object references properly across the DOM-JS boundary + isNode = elem.nodeType, + + // Only DOM nodes need the global jQuery cache; JS object data is + // attached directly to the object so GC can occur automatically + cache = isNode ? jQuery.cache : elem, + + // Only defining an ID for JS objects if its cache already exists allows + // the code to shortcut on the same path as a DOM node with no cache + id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey; + + // Avoid doing any more work than we need to when trying to get data on an + // object that has no data at all + if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) { + return; + } + + if ( !id ) { + // Only DOM nodes need a new unique ID for each element since their data + // ends up in the global cache + if ( isNode ) { + elem[ internalKey ] = id = core_deletedIds.pop() || jQuery.guid++; + } else { + id = internalKey; + } + } + + if ( !cache[ id ] ) { + cache[ id ] = {}; + + // Avoids exposing jQuery metadata on plain JS objects when the object + // is serialized using JSON.stringify + if ( !isNode ) { + cache[ id ].toJSON = jQuery.noop; + } + } + + // An object can be passed to jQuery.data instead of a key/value pair; this gets + // shallow copied over onto the existing cache + if ( typeof name === "object" || typeof name === "function" ) { + if ( pvt ) { + cache[ id ] = jQuery.extend( cache[ id ], name ); + } else { + cache[ id ].data = jQuery.extend( cache[ id ].data, name ); + } + } + + thisCache = cache[ id ]; + + // jQuery data() is stored in a separate object inside the object's internal data + // cache in order to avoid key collisions between internal data and user-defined + // data. + if ( !pvt ) { + if ( !thisCache.data ) { + thisCache.data = {}; + } + + thisCache = thisCache.data; + } + + if ( data !== undefined ) { + thisCache[ jQuery.camelCase( name ) ] = data; + } + + // Check for both converted-to-camel and non-converted data property names + // If a data property was specified + if ( getByName ) { + + // First Try to find as-is property data + ret = thisCache[ name ]; + + // Test for null|undefined property data + if ( ret == null ) { + + // Try to find the camelCased property + ret = thisCache[ jQuery.camelCase( name ) ]; + } + } else { + ret = thisCache; + } + + return ret; +} + +function internalRemoveData( elem, name, pvt /* For internal use only */ ){ + if ( !jQuery.acceptData( elem ) ) { + return; + } + + var thisCache, i, l, + + isNode = elem.nodeType, + + // See jQuery.data for more information + cache = isNode ? jQuery.cache : elem, + id = isNode ? elem[ jQuery.expando ] : jQuery.expando; + + // If there is already no cache entry for this object, there is no + // purpose in continuing + if ( !cache[ id ] ) { + return; + } + + if ( name ) { + + thisCache = pvt ? cache[ id ] : cache[ id ].data; + + if ( thisCache ) { + + // Support array or space separated string names for data keys + if ( !jQuery.isArray( name ) ) { + + // try the string as a key before any manipulation + if ( name in thisCache ) { + name = [ name ]; + } else { + + // split the camel cased version by spaces unless a key with the spaces exists + name = jQuery.camelCase( name ); + if ( name in thisCache ) { + name = [ name ]; + } else { + name = name.split(" "); + } + } + } else { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = name.concat( jQuery.map( name, jQuery.camelCase ) ); + } + + for ( i = 0, l = name.length; i < l; i++ ) { + delete thisCache[ name[i] ]; + } + + // If there is no data left in the cache, we want to continue + // and let the cache object itself get destroyed + if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { + return; + } + } + } + + // See jQuery.data for more information + if ( !pvt ) { + delete cache[ id ].data; + + // Don't destroy the parent cache unless the internal data object + // had been the only thing left in it + if ( !isEmptyDataObject( cache[ id ] ) ) { + return; + } + } + + // Destroy the cache + if ( isNode ) { + jQuery.cleanData( [ elem ], true ); + + // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080) + } else if ( jQuery.support.deleteExpando || cache != cache.window ) { + delete cache[ id ]; + + // When all else fails, null + } else { + cache[ id ] = null; + } +} + +jQuery.extend({ + cache: {}, + + // Unique for each copy of jQuery on the page + // Non-digits removed to match rinlinejQuery + expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ), + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + // Ban all objects except for Flash (which handle expandos) + "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", + "applet": true + }, + + hasData: function( elem ) { + elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; + return !!elem && !isEmptyDataObject( elem ); + }, + + data: function( elem, name, data ) { + return internalData( elem, name, data, false ); + }, + + removeData: function( elem, name ) { + return internalRemoveData( elem, name, false ); + }, + + // For internal use only. + _data: function( elem, name, data ) { + return internalData( elem, name, data, true ); + }, + + _removeData: function( elem, name ) { + return internalRemoveData( elem, name, true ); + }, + + // A method for determining if a DOM node can handle the data expando + acceptData: function( elem ) { + var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ]; + + // nodes accept data unless otherwise specified; rejection can be conditional + return !noData || noData !== true && elem.getAttribute("classid") === noData; + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + var attrs, name, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = jQuery.data( elem ); + + if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { + attrs = elem.attributes; + for ( ; i < attrs.length; i++ ) { + name = attrs[i].name; + + if ( !name.indexOf( "data-" ) ) { + name = jQuery.camelCase( name.substring(5) ); + + dataAttr( elem, name, data[ name ] ); + } + } + jQuery._data( elem, "parsedAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + return jQuery.access( this, function( value ) { + + if ( value === undefined ) { + // Try to fetch any internally stored data first + return elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null; + } + + this.each(function() { + jQuery.data( this, key, value ); + }); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); + +function dataAttr( elem, key, data ) { + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + + var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); + + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = data === "true" ? true : + data === "false" ? false : + data === "null" ? null : + // Only convert to a number if it doesn't change the string + +data + "" === data ? +data : + rbrace.test( data ) ? jQuery.parseJSON( data ) : + data; + } catch( e ) {} + + // Make sure we set the data so it isn't changed later + jQuery.data( elem, key, data ); + + } else { + data = undefined; + } + } + + return data; +} + +// checks a cache object for emptiness +function isEmptyDataObject( obj ) { + var name; + for ( name in obj ) { + + // if the public data object is empty, the private is still empty + if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { + continue; + } + if ( name !== "toJSON" ) { + return false; + } + } + + return true; +} +jQuery.extend({ + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = jQuery._data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || jQuery.isArray(data) ) { + queue = jQuery._data( elem, type, jQuery.makeArray(data) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + hooks.cur = fn; + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return jQuery._data( elem, key ) || jQuery._data( elem, key, { + empty: jQuery.Callbacks("once memory").add(function() { + jQuery._removeData( elem, type + "queue" ); + jQuery._removeData( elem, key ); + }) + }); + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[0], type ); + } + + return data === undefined ? + this : + this.each(function() { + var queue = jQuery.queue( this, type, data ); + + // ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = setTimeout( next, time ); + hooks.stop = function() { + clearTimeout( timeout ); + }; + }); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while( i-- ) { + tmp = jQuery._data( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +}); +var nodeHook, boolHook, + rclass = /[\t\r\n]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i, + ruseDefault = /^(?:checked|selected)$/i, + getSetAttribute = jQuery.support.getSetAttribute, + getSetInput = jQuery.support.input; + +jQuery.fn.extend({ + attr: function( name, value ) { + return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each(function() { + jQuery.removeAttr( this, name ); + }); + }, + + prop: function( name, value ) { + return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + name = jQuery.propFix[ name ] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[ name ] = undefined; + delete this[ name ]; + } catch( e ) {} + }); + }, + + addClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).addClass( value.call( this, j, this.className ) ); + }); + } + + if ( proceed ) { + // The disjunction here is for better compressibility (see removeClass) + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + " " + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + elem.className = jQuery.trim( cur ); + + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, clazz, j, + i = 0, + len = this.length, + proceed = arguments.length === 0 || typeof value === "string" && value; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( j ) { + jQuery( this ).removeClass( value.call( this, j, this.className ) ); + }); + } + if ( proceed ) { + classes = ( value || "" ).match( core_rnotwhite ) || []; + + for ( ; i < len; i++ ) { + elem = this[ i ]; + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( elem.className ? + ( " " + elem.className + " " ).replace( rclass, " " ) : + "" + ); + + if ( cur ) { + j = 0; + while ( (clazz = classes[j++]) ) { + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) >= 0 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + elem.className = value ? jQuery.trim( cur ) : ""; + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function( i ) { + jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, + i = 0, + self = jQuery( this ), + state = stateVal, + classNames = value.match( core_rnotwhite ) || []; + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space separated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + // Toggle whole class name + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery._data( this, "__className__", this.className ); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " ", + i = 0, + l = this.length; + for ( ; i < l; i++ ) { + if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + var hooks, ret, isFunction, + elem = this[0]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { + return ret; + } + + ret = elem.value; + + return typeof ret === "string" ? + // handle most common string cases + ret.replace(rreturn, "") : + // handle cases where value is null/undef or number + ret == null ? "" : ret; + } + + return; + } + + isFunction = jQuery.isFunction( value ); + + return this.each(function( i ) { + var val, + self = jQuery(this); + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call( this, i, self.val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + } else if ( typeof val === "number" ) { + val += ""; + } else if ( jQuery.isArray( val ) ) { + val = jQuery.map(val, function ( value ) { + return value == null ? "" : value + ""; + }); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + valHooks: { + option: { + get: function( elem ) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + } + }, + select: { + get: function( elem ) { + var value, option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one" || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? + max : + one ? index : 0; + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // oldIE doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + // Don't return options that are disabled or in a disabled optgroup + ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && + ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var values = jQuery.makeArray( value ); + + jQuery(elem).find("option").each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + elem.selectedIndex = -1; + } + return values; + } + } + }, + + attr: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if ( notxml ) { + name = name.toLowerCase(); + hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); + } + + if ( value !== undefined ) { + + if ( value === null ) { + jQuery.removeAttr( elem, name ); + + } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + elem.setAttribute( name, value + "" ); + return value; + } + + } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + + // In IE9+, Flash objects don't have .getAttribute (#12945) + // Support: IE9+ + if ( typeof elem.getAttribute !== "undefined" ) { + ret = elem.getAttribute( name ); + } + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? + undefined : + ret; + } + }, + + removeAttr: function( elem, value ) { + var name, propName, + i = 0, + attrNames = value && value.match( core_rnotwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( (name = attrNames[i++]) ) { + propName = jQuery.propFix[ name ] || name; + + // Boolean attributes get special treatment (#10870) + if ( rboolean.test( name ) ) { + // Set corresponding property to false for boolean attributes + // Also clear defaultChecked/defaultSelected (if appropriate) for IE<8 + if ( !getSetAttribute && ruseDefault.test( name ) ) { + elem[ jQuery.camelCase( "default-" + name ) ] = + elem[ propName ] = false; + } else { + elem[ propName ] = false; + } + + // See #9699 for explanation of this approach (setting first, then removal) + } else { + jQuery.attr( elem, name, "" ); + } + + elem.removeAttribute( getSetAttribute ? name : propName ); + } + } + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + propFix: { + tabindex: "tabIndex", + readonly: "readOnly", + "for": "htmlFor", + "class": "className", + maxlength: "maxLength", + cellspacing: "cellSpacing", + cellpadding: "cellPadding", + rowspan: "rowSpan", + colspan: "colSpan", + usemap: "useMap", + frameborder: "frameBorder", + contenteditable: "contentEditable" + }, + + prop: function( elem, name, value ) { + var ret, hooks, notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); + + if ( notxml ) { + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { + return ret; + + } else { + return ( elem[ name ] = value ); + } + + } else { + if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { + return ret; + + } else { + return elem[ name ]; + } + } + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode("tabindex"); + + return attributeNode && attributeNode.specified ? + parseInt( attributeNode.value, 10 ) : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + } + } +}); + +// Hook for boolean attributes +boolHook = { + get: function( elem, name ) { + var + // Use .prop to determine if this attribute is understood as boolean + prop = jQuery.prop( elem, name ), + + // Fetch it accordingly + attr = typeof prop === "boolean" && elem.getAttribute( name ), + detail = typeof prop === "boolean" ? + + getSetInput && getSetAttribute ? + attr != null : + // oldIE fabricates an empty string for missing boolean attributes + // and conflates checked/selected into attroperties + ruseDefault.test( name ) ? + elem[ jQuery.camelCase( "default-" + name ) ] : + !!attr : + + // fetch an attribute node for properties not recognized as boolean + elem.getAttributeNode( name ); + + return detail && detail.value !== false ? + name.toLowerCase() : + undefined; + }, + set: function( elem, value, name ) { + if ( value === false ) { + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) { + // IE<8 needs the *property* name + elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name ); + + // Use defaultChecked and defaultSelected for oldIE + } else { + elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true; + } + + return name; + } +}; + +// fix oldIE value attroperty +if ( !getSetInput || !getSetAttribute ) { + jQuery.attrHooks.value = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return jQuery.nodeName( elem, "input" ) ? + + // Ignore the value *property* by using defaultValue + elem.defaultValue : + + ret && ret.specified ? ret.value : undefined; + }, + set: function( elem, value, name ) { + if ( jQuery.nodeName( elem, "input" ) ) { + // Does not return so that setAttribute is also used + elem.defaultValue = value; + } else { + // Use nodeHook if defined (#1954); otherwise setAttribute is fine + return nodeHook && nodeHook.set( elem, value, name ); + } + } + }; +} + +// IE6/7 do not support getting/setting some attributes with get/setAttribute +if ( !getSetAttribute ) { + + // Use this for any attribute in IE6/7 + // This fixes almost every IE6/7 issue + nodeHook = jQuery.valHooks.button = { + get: function( elem, name ) { + var ret = elem.getAttributeNode( name ); + return ret && ( name === "id" || name === "name" || name === "coords" ? ret.value !== "" : ret.specified ) ? + ret.value : + undefined; + }, + set: function( elem, value, name ) { + // Set the existing or create a new attribute node + var ret = elem.getAttributeNode( name ); + if ( !ret ) { + elem.setAttributeNode( + (ret = elem.ownerDocument.createAttribute( name )) + ); + } + + ret.value = value += ""; + + // Break association with cloned elements by also using setAttribute (#9646) + return name === "value" || value === elem.getAttribute( name ) ? + value : + undefined; + } + }; + + // Set contenteditable to false on removals(#10429) + // Setting to empty string throws an error as an invalid value + jQuery.attrHooks.contenteditable = { + get: nodeHook.get, + set: function( elem, value, name ) { + nodeHook.set( elem, value === "" ? false : value, name ); + } + }; + + // Set width and height to auto instead of 0 on empty string( Bug #8150 ) + // This is for removals + jQuery.each([ "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + set: function( elem, value ) { + if ( value === "" ) { + elem.setAttribute( name, "auto" ); + return value; + } + } + }); + }); +} + + +// Some attributes require a special call on IE +// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !jQuery.support.hrefNormalized ) { + jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { + jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { + get: function( elem ) { + var ret = elem.getAttribute( name, 2 ); + return ret == null ? undefined : ret; + } + }); + }); + + // href/src property should get the full normalized URL (#10299/#12915) + jQuery.each([ "href", "src" ], function( i, name ) { + jQuery.propHooks[ name ] = { + get: function( elem ) { + return elem.getAttribute( name, 4 ); + } + }; + }); +} + +if ( !jQuery.support.style ) { + jQuery.attrHooks.style = { + get: function( elem ) { + // Return undefined in the case of empty string + // Note: IE uppercases css property names, but if we were to .toLowerCase() + // .cssText, that would destroy case senstitivity in URL's, like in "background" + return elem.style.cssText || undefined; + }, + set: function( elem, value ) { + return ( elem.style.cssText = value + "" ); + } + }; +} + +// Safari mis-reports the default selected property of an option +// Accessing the parent's selectedIndex property fixes it +if ( !jQuery.support.optSelected ) { + jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { + get: function( elem ) { + var parent = elem.parentNode; + + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + return null; + } + }); +} + +// IE6/7 call enctype encoding +if ( !jQuery.support.enctype ) { + jQuery.propFix.enctype = "encoding"; +} + +// Radios and checkboxes getter/setter +if ( !jQuery.support.checkOn ) { + jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + get: function( elem ) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute("value") === null ? "on" : elem.value; + } + }; + }); +} +jQuery.each([ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { + set: function( elem, value ) { + if ( jQuery.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); + } + } + }); +}); +var rformElems = /^(?:input|select|textarea)$/i, + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + // Don't attach events to noData or text/comment nodes (but allow plain objects) + elemData = elem.nodeType !== 3 && elem.nodeType !== 8 && jQuery._data( elem ); + + if ( !elemData ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !(events = elemData.events) ) { + events = elemData.events = {}; + } + if ( !(eventHandle = elemData.handle) ) { + eventHandle = elemData.handle = function( e ) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? + jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : + undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend({ + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join(".") + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !(handlers = events[ type ]) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener/attachEvent if the special events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = jQuery.hasData( elem ) && jQuery._data( elem ); + + if ( !elemData || !(events = elemData.events) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( core_rnotwhite ) || [""]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[t] ) || []; + type = origType = tmp[1]; + namespaces = ( tmp[2] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData( elem, "events" ); + } + }, + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, + eventPath = [ elem || document ], + type = event.type || event, + namespaces = event.namespace ? event.namespace.split(".") : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf(".") >= 0 ) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split("."); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(":") < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + event.isTrigger = true; + event.namespace = namespaces.join("."); + event.namespace_re = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === (elem.ownerDocument || document) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { + + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && + !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name name as the event. + // Can't use an .isFunction() check here because IE6/7 fails that test. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + try { + elem[ type ](); + } catch ( e ) { + // IE<9 dies on focus/blur to hidden element (#1486,#12518) + // only reproducible on winXP IE8 native, not IE9 in IE8 mode + } + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function( event ) { + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( event ); + + var i, j, ret, matched, handleObj, + handlerQueue = [], + args = core_slice.call( arguments ), + handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { + + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) + .apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( (event.result = ret) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, matches, sel, handleObj, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { + + for ( ; cur != this; cur = cur.parentNode || this ) { + + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.disabled !== true || event.type !== "click" ) { + matches = []; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matches[ sel ] === undefined ) { + matches[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) >= 0 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matches[ sel ] ) { + matches.push( handleObj ); + } + } + if ( matches.length ) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if ( delegateCount < handlers.length ) { + handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); + } + + return handlerQueue; + }, + + fix: function( event ) { + if ( event[ jQuery.expando ] ) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, prop, + originalEvent = event, + fixHook = jQuery.event.fixHooks[ event.type ] || {}, + copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; + + event = new jQuery.Event( originalEvent ); + + i = copy.length; + while ( i-- ) { + prop = copy[ i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Support: IE<9 + // Fix target property (#1925) + if ( !event.target ) { + event.target = originalEvent.srcElement || document; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Support: IE<9 + // For mouse/key events, metaKey==false if it's undefined (#3368, #11328) + event.metaKey = !!event.metaKey; + + return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + + fixHooks: {}, + + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function( event, original ) { + + // Add which for key events + if ( event.which == null ) { + event.which = original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + } + }, + + mouseHooks: { + props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function( event, original ) { + var eventDoc, doc, body, + button = original.button, + fromElement = original.fromElement; + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && original.clientX != null ) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); + event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && fromElement ) { + event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && button !== undefined ) { + event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); + } + + return event; + } + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) { + this.click(); + return false; + } + } + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if ( this !== document.activeElement && this.focus ) { + try { + this.focus(); + return false; + } catch ( e ) { + // Support: IE<9 + // If we error on focus to hidden element (#1486, #12518), + // let .trigger() run the handlers + } + } + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + if ( this === document.activeElement && this.blur ) { + this.blur(); + return false; + } + }, + delegateType: "focusout" + }, + + beforeunload: { + postDispatch: function( event ) { + + // Even when returnValue equals to undefined Firefox will still show alert + if ( event.result !== undefined ) { + event.originalEvent.returnValue = event.result; + } + } + } + }, + + simulate: function( type, elem, event, bubble ) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend( + new jQuery.Event(), + event, + { type: type, + isSimulated: true, + originalEvent: {} + } + ); + if ( bubble ) { + jQuery.event.trigger( e, null, elem ); + } else { + jQuery.event.dispatch.call( elem, e ); + } + if ( e.isDefaultPrevented() ) { + event.preventDefault(); + } + } +}; + +jQuery.removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle, false ); + } + } : + function( elem, type, handle ) { + var name = "on" + type; + + if ( elem.detachEvent ) { + + // #8545, #7054, preventing memory leaks for custom events in IE6-8 + // detachEvent needed property on element, by name of that event, to properly expose it to GC + if ( typeof elem[ name ] === "undefined" ) { + elem[ name ] = null; + } + + elem.detachEvent( name, handle ); + } + }; + +jQuery.Event = function( src, props ) { + // Allow instantiation without the 'new' keyword + if ( !(this instanceof jQuery.Event) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || + src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || jQuery.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + if ( !e ) { + return; + } + + // If preventDefault exists, run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + + // Support: IE + // Otherwise set the returnValue property of the original event to false + } else { + e.returnValue = false; + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + if ( !e ) { + return; + } + // If stopPropagation exists, run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + + // Support: IE + // Set the cancelBubble property of the original event to true + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + } +}; + +// Create mouseenter/leave events using mouseover/out and event-time checks +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || (related !== target && !jQuery.contains( target, related )) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +}); + +// IE submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Lazy-add a submit handler when a descendant form may potentially be submitted + jQuery.event.add( this, "click._submit keypress._submit", function( e ) { + // Node name check avoids a VML-related crash in IE (#9807) + var elem = e.target, + form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; + if ( form && !jQuery._data( form, "submitBubbles" ) ) { + jQuery.event.add( form, "submit._submit", function( event ) { + event._submit_bubble = true; + }); + jQuery._data( form, "submitBubbles", true ); + } + }); + // return undefined since we don't need an event listener + }, + + postDispatch: function( event ) { + // If form was submitted by the user, bubble the event up the tree + if ( event._submit_bubble ) { + delete event._submit_bubble; + if ( this.parentNode && !event.isTrigger ) { + jQuery.event.simulate( "submit", this.parentNode, event, true ); + } + } + }, + + teardown: function() { + // Only need this for delegated form submit events + if ( jQuery.nodeName( this, "form" ) ) { + return false; + } + + // Remove delegated handlers; cleanData eventually reaps submit handlers attached above + jQuery.event.remove( this, "._submit" ); + } + }; +} + +// IE change delegation and checkbox/radio fix +if ( !jQuery.support.changeBubbles ) { + + jQuery.event.special.change = { + + setup: function() { + + if ( rformElems.test( this.nodeName ) ) { + // IE doesn't fire change on a check/radio until blur; trigger it on click + // after a propertychange. Eat the blur-change in special.change.handle. + // This still fires onchange a second time for check/radio after blur. + if ( this.type === "checkbox" || this.type === "radio" ) { + jQuery.event.add( this, "propertychange._change", function( event ) { + if ( event.originalEvent.propertyName === "checked" ) { + this._just_changed = true; + } + }); + jQuery.event.add( this, "click._change", function( event ) { + if ( this._just_changed && !event.isTrigger ) { + this._just_changed = false; + } + // Allow triggered, simulated change events (#11500) + jQuery.event.simulate( "change", this, event, true ); + }); + } + return false; + } + // Delegated event; lazy-add a change handler on descendant inputs + jQuery.event.add( this, "beforeactivate._change", function( e ) { + var elem = e.target; + + if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) { + jQuery.event.add( elem, "change._change", function( event ) { + if ( this.parentNode && !event.isSimulated && !event.isTrigger ) { + jQuery.event.simulate( "change", this.parentNode, event, true ); + } + }); + jQuery._data( elem, "changeBubbles", true ); + } + }); + }, + + handle: function( event ) { + var elem = event.target; + + // Swallow native change events from checkbox/radio, we already triggered them above + if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) { + return event.handleObj.handler.apply( this, arguments ); + } + }, + + teardown: function() { + jQuery.event.remove( this, "._change" ); + + return !rformElems.test( this.nodeName ); + } + }; +} + +// Create "bubbling" focus and blur events +if ( !jQuery.support.focusinBubbles ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + if ( attaches++ === 0 ) { + document.addEventListener( orig, handler, true ); + } + }, + teardown: function() { + if ( --attaches === 0 ) { + document.removeEventListener( orig, handler, true ); + } + } + }; + }); +} + +jQuery.fn.extend({ + + on: function( types, selector, data, fn, /*INTERNAL*/ one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + this.on( type, selector, data, types[ type ], one ); + } + return this; + } + + if ( data == null && fn == null ) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return this; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return this.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + }); + }, + one: function( types, selector, data, fn ) { + return this.on( types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove( this, types, fn, selector ); + }); + }, + + bind: function( types, data, fn ) { + return this.on( types, null, data, fn ); + }, + unbind: function( types, fn ) { + return this.off( types, null, fn ); + }, + + delegate: function( selector, types, data, fn ) { + return this.on( types, selector, data, fn ); + }, + undelegate: function( selector, types, fn ) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + triggerHandler: function( type, data ) { + var elem = this[0]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( data, fn ) { + return arguments.length > 0 ? + this.on( name, null, data, fn ) : + this.trigger( name ); + }; + + if ( rkeyEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; + } + + if ( rmouseEvent.test( name ) ) { + jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; + } +}); +/*! + * Sizzle CSS Selector Engine + * Copyright 2012 jQuery Foundation and other contributors + * Released under the MIT license + * http://sizzlejs.com/ + */ +(function( window, undefined ) { + +var i, + cachedruns, + Expr, + getText, + isXML, + compile, + hasDuplicate, + outermostContext, + + // Local document vars + setDocument, + document, + docElem, + documentIsXML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + sortOrder, + + // Instance-specific data + expando = "sizzle" + -(new Date()), + preferredDoc = window.document, + support = {}, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + + // Array methods + arr = [], + pop = arr.pop, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = arr.indexOf || function( elem ) { + var i = 0, + len = this.length; + for ( ; i < len; i++ ) { + if ( this[i] === elem ) { + return i; + } + } + return -1; + }, + + + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace( "w", "w#" ), + + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + operators = "([*^$|!~]?=)", + attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace + + "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]", + + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ), + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + characterEncoding + ")" ), + "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), + "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ), + "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rsibling = /[\x20\t\r\n\f]*[+~]/, + + rnative = /\{\s*\[native code\]\s*\}/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rescape = /'|\\/g, + rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, + + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, + funescape = function( _, escaped ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + return high !== high ? + escaped : + // BMP codepoint + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }; + +// Use a stripped-down slice if we can't use a native one +try { + slice.call( docElem.childNodes, 0 )[0].nodeType; +} catch ( e ) { + slice = function( i ) { + var elem, + results = []; + for ( ; (elem = this[i]); i++ ) { + results.push( elem ); + } + return results; + }; +} + +/** + * For feature detection + * @param {Function} fn The function to test for native support + */ +function isNative( fn ) { + return rnative.test( fn + "" ); +} + +/** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var cache, + keys = []; + + return (cache = function( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key += " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key ] = value); + }); +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ +function assert( fn ) { + var div = document.createElement("div"); + + try { + return fn( div ); + } catch (e) { + return false; + } finally { + // release memory in IE + div = null; + } +} + +function Sizzle( selector, context, results, seed ) { + var match, elem, m, nodeType, + // QSA vars + i, groups, old, nid, newContext, newSelector; + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + + context = context || document; + results = results || []; + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { + return []; + } + + if ( !documentIsXML && !seed ) { + + // Shortcuts + if ( (match = rquickExpr.exec( selector )) ) { + // Speed-up: Sizzle("#ID") + if ( (m = match[1]) ) { + if ( nodeType === 9 ) { + elem = context.getElementById( m ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if ( elem && elem.parentNode ) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && + contains( context, elem ) && elem.id === m ) { + results.push( elem ); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if ( match[2] ) { + push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) ); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( (m = match[3]) && support.getByClassName && context.getElementsByClassName ) { + push.apply( results, slice.call(context.getElementsByClassName( m ), 0) ); + return results; + } + } + + // QSA path + if ( support.qsa && !rbuggyQSA.test(selector) ) { + old = true; + nid = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { + groups = tokenize( selector ); + + if ( (old = context.getAttribute("id")) ) { + nid = old.replace( rescape, "\\$&" ); + } else { + context.setAttribute( "id", nid ); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while ( i-- ) { + groups[i] = nid + toSelector( groups[i] ); + } + newContext = rsibling.test( selector ) && context.parentNode || context; + newSelector = groups.join(","); + } + + if ( newSelector ) { + try { + push.apply( results, slice.call( newContext.querySelectorAll( + newSelector + ), 0 ) ); + return results; + } catch(qsaError) { + } finally { + if ( !old ) { + context.removeAttribute("id"); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Detect xml + * @param {Element|Object} elem An element or a document + */ +isXML = Sizzle.isXML = function( elem ) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsXML = isXML( doc ); + + // Check if getElementsByTagName("*") returns only elements + support.tagNameNoComments = assert(function( div ) { + div.appendChild( doc.createComment("") ); + return !div.getElementsByTagName("*").length; + }); + + // Check if attributes should be retrieved by attribute nodes + support.attributes = assert(function( div ) { + div.innerHTML = ""; + var type = typeof div.lastChild.getAttribute("multiple"); + // IE8 returns a string for some attributes even when not present + return type !== "boolean" && type !== "string"; + }); + + // Check if getElementsByClassName can be trusted + support.getByClassName = assert(function( div ) { + // Opera can't find a second classname (in 9.6) + div.innerHTML = ""; + if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) { + return false; + } + + // Safari 3.2 caches class attributes and doesn't catch changes + div.lastChild.className = "e"; + return div.getElementsByClassName("e").length === 2; + }); + + // Check if getElementById returns elements by name + // Check if getElementsByName privileges form controls or returns elements by ID + support.getByName = assert(function( div ) { + // Inject content + div.id = expando + 0; + div.innerHTML = "
"; + docElem.insertBefore( div, docElem.firstChild ); + + // Test + var pass = doc.getElementsByName && + // buggy browsers will return fewer than the correct 2 + doc.getElementsByName( expando ).length === 2 + + // buggy browsers will return more than the correct 0 + doc.getElementsByName( expando + 0 ).length; + support.getIdNotName = !doc.getElementById( expando ); + + // Cleanup + docElem.removeChild( div ); + + return pass; + }); + + // IE6/7 return modified attributes + Expr.attrHandle = assert(function( div ) { + div.innerHTML = ""; + return div.firstChild && typeof div.firstChild.getAttribute !== strundefined && + div.firstChild.getAttribute("href") === "#"; + }) ? + {} : + { + "href": function( elem ) { + return elem.getAttribute( "href", 2 ); + }, + "type": function( elem ) { + return elem.getAttribute("type"); + } + }; + + // ID find and filter + if ( support.getIdNotName ) { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute("id") === attrId; + }; + }; + } else { + Expr.find["ID"] = function( id, context ) { + if ( typeof context.getElementById !== strundefined && !documentIsXML ) { + var m = context.getElementById( id ); + + return m ? + m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ? + [m] : + undefined : + []; + } + }; + Expr.filter["ID"] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find["TAG"] = support.tagNameNoComments ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== strundefined ) { + return context.getElementsByTagName( tag ); + } + } : + function( tag, context ) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + for ( ; (elem = results[i]); i++ ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Name + Expr.find["NAME"] = support.getByName && function( tag, context ) { + if ( typeof context.getElementsByName !== strundefined ) { + return context.getElementsByName( name ); + } + }; + + // Class + Expr.find["CLASS"] = support.getByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== strundefined && !documentIsXML ) { + return context.getElementsByClassName( className ); + } + }; + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21), + // no need to also add to buggyMatches since matches checks buggyQSA + // A support test would require too much code (would include document ready) + rbuggyQSA = [ ":focus" ]; + + if ( (support.qsa = isNative(doc.querySelectorAll)) ) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function( div ) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explictly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // IE8 - Some boolean attributes are not treated correctly + if ( !div.querySelectorAll("[selected]").length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":checked").length ) { + rbuggyQSA.push(":checked"); + } + }); + + assert(function( div ) { + + // Opera 10-12/IE8 - ^= $= *= and empty values + // Should not select anything + div.innerHTML = ""; + if ( div.querySelectorAll("[i^='']").length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( !div.querySelectorAll(":enabled").length ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll("*,:x"); + rbuggyQSA.push(",.*:"); + }); + } + + if ( (support.matchesSelector = isNative( (matches = docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) )) ) { + + assert(function( div ) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( div, "div" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( div, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + }); + } + + rbuggyQSA = new RegExp( rbuggyQSA.join("|") ); + rbuggyMatches = new RegExp( rbuggyMatches.join("|") ); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = isNative(docElem.contains) || docElem.compareDocumentPosition ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + )); + } : + function( a, b ) { + if ( b ) { + while ( (b = b.parentNode) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + // Document order sorting + sortOrder = docElem.compareDocumentPosition ? + function( a, b ) { + var compare; + + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + if ( (compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b )) ) { + if ( compare & 1 || a.parentNode && a.parentNode.nodeType === 11 ) { + if ( a === doc || contains( preferredDoc, a ) ) { + return -1; + } + if ( b === doc || contains( preferredDoc, b ) ) { + return 1; + } + return 0; + } + return compare & 4 ? -1 : 1; + } + + return a.compareDocumentPosition ? -1 : 1; + } : + function( a, b ) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // The nodes are identical, we can exit early + if ( a === b ) { + hasDuplicate = true; + return 0; + + // Fallback to using sourceIndex (in IE) if it's available on both nodes + } else if ( a.sourceIndex && b.sourceIndex ) { + return ( ~b.sourceIndex || MAX_NEGATIVE ) - ( contains( preferredDoc, a ) && ~a.sourceIndex || MAX_NEGATIVE ); + + // Parentless nodes are either documents or disconnected + } else if ( !aup || !bup ) { + return a === doc ? -1 : + b === doc ? 1 : + aup ? -1 : + bup ? 1 : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( (cur = cur.parentNode) ) { + ap.unshift( cur ); + } + cur = b; + while ( (cur = cur.parentNode) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[i] === bp[i] ) { + i++; + } + + return i ? + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[i], bp[i] ) : + + // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : + bp[i] === preferredDoc ? 1 : + 0; + }; + + // Always assume the presence of duplicates if sort doesn't + // pass them to our comparison function (as in Google Chrome). + hasDuplicate = false; + [0, 0].sort( sortOrder ); + support.detectDuplicates = hasDuplicate; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace( rattributeQuotes, "='$1']" ); + + // rbuggyQSA always contains :focus, so no need for an existence check + if ( support.matchesSelector && !documentIsXML && (!rbuggyMatches || !rbuggyMatches.test(expr)) && !rbuggyQSA.test(expr) ) { + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch(e) {} + } + + return Sizzle( expr, document, null, [elem] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + // Set document vars if needed + if ( ( context.ownerDocument || context ) !== document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + var val; + + // Set document vars if needed + if ( ( elem.ownerDocument || elem ) !== document ) { + setDocument( elem ); + } + + if ( !documentIsXML ) { + name = name.toLowerCase(); + } + if ( (val = Expr.attrHandle[ name ]) ) { + return val( elem ); + } + if ( documentIsXML || support.attributes ) { + return elem.getAttribute( name ); + } + return ( (val = elem.getAttributeNode( name )) || elem.getAttribute( name ) ) && elem[ name ] === true ? + name : + val && val.specified ? val.value : null; +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +// Document sorting and removing duplicates +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + i = 1, + j = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + results.sort( sortOrder ); + + if ( hasDuplicate ) { + for ( ; (elem = results[i]); i++ ) { + if ( elem === results[ i - 1 ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + return results; +}; + +function siblingCheck( a, b ) { + var cur = a && b && a.nextSibling; + + for ( ; cur; cur = cur.nextSibling ) { + if ( cur === b ) { + return -1; + } + } + + return a ? 1 : -1; +} + +// Returns a function to use in pseudos for input types +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +// Returns a function to use in pseudos for buttons +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +// Returns a function to use in pseudos for positionals +function createPositionalPseudo( fn ) { + return markFunction(function( argument ) { + argument = +argument; + return markFunction(function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ (j = matchIndexes[i]) ] ) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); +} + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + // If no nodeType, this is expected to be an array + for ( ; (node = elem[i]); i++ ) { + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[1] = match[1].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape ); + + if ( match[2] === "~=" ) { + match[3] = " " + match[3] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if ( match[1].slice( 0, 3 ) === "nth" ) { + // nth-* requires argument + if ( !match[3] ) { + Sizzle.error( match[0] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); + match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + + // other types prohibit arguments + } else if ( match[3] ) { + Sizzle.error( match[0] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[5] && match[2]; + + if ( matchExpr["CHILD"].test( match[0] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[4] ) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) + (excess = tokenize( unquoted, true )) && + // advance to the next closing parenthesis + (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + + // excess is a negative index + match[0] = match[0].slice( 0, excess ); + match[2] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeName ) { + if ( nodeName === "*" ) { + return function() { return true; }; + } + + nodeName = nodeName.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && + classCache( className, function( elem ) { + return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" ); + }); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.substr( result.length - check.length ) === check : + operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" : + false; + }; + }, + + "CHILD": function( type, what, argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, context, xml ) { + var cache, outerCache, node, diff, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( (node = node[ dir ]) ) { + if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + // Seek `elem` from a previously-cached index + outerCache = parent[ expando ] || (parent[ expando ] = {}); + cache = outerCache[ type ] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( (node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || start.pop()) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + outerCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + // Use previously-cached element index if available + } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( (node = ++nodeIndex && node && node[ dir ] || + (diff = nodeIndex = 0) || start.pop()) ) { + + if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { + // Cache the index of each encountered element + if ( useCache ) { + (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction(function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf.call( seed, matched[i] ); + seed[ idx ] = !( matches[ idx ] = matched[i] ); + } + }) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + // Potentially complex pseudos + "not": markFunction(function( selector ) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction(function( seed, matches, context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( (elem = unmatched[i]) ) { + seed[i] = !(matches[i] = elem); + } + } + }) : + function( elem, context, xml ) { + input[0] = elem; + matcher( input, null, xml, results ); + return !results.pop(); + }; + }), + + "has": markFunction(function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + }), + + "contains": markFunction(function( text ) { + return function( elem ) { + return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + // lang value must be a valid identifider + if ( !ridentifier.test(lang || "") ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( (elemLang = documentIsXML ? + elem.getAttribute("xml:lang") || elem.getAttribute("lang") : + elem.lang) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + return false; + }; + }), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + }, + + // Boolean properties + "enabled": function( elem ) { + return elem.disabled === false; + }, + + "disabled": function( elem ) { + return elem.disabled === true; + }, + + "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + }, + + "selected": function( elem ) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos["empty"]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type ); + }, + + // Position-in-collection + "first": createPositionalPseudo(function() { + return [ 0 ]; + }), + + "last": createPositionalPseudo(function( matchIndexes, length ) { + return [ length - 1 ]; + }), + + "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + }), + + "even": createPositionalPseudo(function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "odd": createPositionalPseudo(function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }), + + "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + }) + } +}; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +function tokenize( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( match ) { + // Don't consume trailing commas as valid + soFar = soFar.slice( match[0].length ) || soFar; + } + groups.push( tokens = [] ); + } + + matched = false; + + // Combinators + if ( (match = rcombinators.exec( soFar )) ) { + matched = match.shift(); + tokens.push( { + value: matched, + // Cast descendant combinators to space + type: match[0].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || + (match = preFilters[ type ]( match ))) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +} + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[i].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + checkNonElements = base && combinator.dir === "parentNode", + doneName = done++; + + return combinator.first ? + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var data, cache, outerCache, + dirkey = dirruns + " " + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if ( xml ) { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( (elem = elem[ dir ]) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || (elem[ expando ] = {}); + if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) { + if ( (data = cache[1]) === true || data === cachedruns ) { + return data === true; + } + } else { + cache = outerCache[ dir ] = [ dirkey ]; + cache[1] = matcher( elem, context, xml ) || cachedruns; + if ( cache[1] === true ) { + return true; + } + } + } + } + } + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[i]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[0]; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( (elem = unmatched[i]) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction(function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( (elem = temp[i]) ) { + matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) ) { + // Restore matcherIn since elem is not yet a final match + temp.push( (matcherIn[i] = elem) ); + } + } + postFinder( null, (matcherOut = []), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { + + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + }); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[0].type ], + implicitRelative = leadingRelative || Expr.relative[" "], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf.call( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + (checkContext = context).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + } ]; + + for ( ; i < len; i++ ) { + if ( (matcher = Expr.relative[ tokens[i].type ]) ) { + matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + } else { + matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[j].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( tokens.slice( 0, i - 1 ) ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, expandContext ) { + var elem, j, matcher, + setMatched = [], + matchedCount = 0, + i = "0", + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ), + // Nested matchers should use non-integer dirruns + dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E); + + if ( outermost ) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + for ( ; (elem = elems[i]) != null; i++ ) { + if ( byElement && elem ) { + for ( j = 0; (matcher = elementMatchers[j]); j++ ) { + if ( matcher( elem, context, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + // They will have gone through all possible matchers + if ( (elem = !matcher && elem) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // Apply set filters to unmatched elements + // `i` starts as a string, so matchedCount would equal "00" if there are no elements + matchedCount += i; + if ( bySet && i !== matchedCount ) { + for ( j = 0; (matcher = setMatchers[j]); j++ ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !(unmatched[i] || setMatched[i]) ) { + setMatched[i] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element + if ( !group ) { + group = tokenize( selector ); + } + i = group.length; + while ( i-- ) { + cached = matcherFromTokens( group[i] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + } + return cached; +}; + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[i], results ); + } + return results; +} + +function select( selector, context, results, seed ) { + var i, tokens, token, type, find, + match = tokenize( selector ); + + if ( !seed ) { + // Try to minimize operations if there is only one group + if ( match.length === 1 ) { + + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice( 0 ); + if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && + context.nodeType === 9 && !documentIsXML && + Expr.relative[ tokens[1].type ] ) { + + context = Expr.find["ID"]( token.matches[0].replace( runescape, funescape ), context )[0]; + if ( !context ) { + return results; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + for ( i = matchExpr["needsContext"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) { + token = tokens[i]; + + // Abort if we hit a combinator + if ( Expr.relative[ (type = token.type) ] ) { + break; + } + if ( (find = Expr.find[ type ]) ) { + // Search, expanding context for leading sibling combinators + if ( (seed = find( + token.matches[0].replace( runescape, funescape ), + rsibling.test( tokens[0].type ) && context.parentNode || context + )) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, slice.call( seed, 0 ) ); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile( selector, match )( + seed, + context, + documentIsXML, + results, + rsibling.test( selector ) + ); + return results; +} + +// Deprecated +Expr.pseudos["nth"] = Expr.pseudos["eq"]; + +// Easy API for creating new setFilters +function setFilters() {} +Expr.filters = setFilters.prototype = Expr.pseudos; +Expr.setFilters = new setFilters(); + +// Initialize with the default document +setDocument(); + +// Override sizzle attribute retrieval +Sizzle.attr = jQuery.attr; +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.pseudos; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; + + +})( window ); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend({ + find: function( selector ) { + var i, ret, self; + + if ( typeof selector !== "string" ) { + self = this; + return this.pushStack( jQuery( selector ).filter(function() { + for ( i = 0; i < self.length; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + }) ); + } + + ret = []; + for ( i = 0; i < this.length; i++ ) { + jQuery.find( selector, this[ i ], ret ); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + ret = this.pushStack( jQuery.unique( ret ) ); + ret.selector = ( this.selector ? this.selector + " " : "" ) + selector; + return ret; + }, + + has: function( target ) { + var i, + targets = jQuery( target, this ), + len = targets.length; + + return this.filter(function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false) ); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true) ); + }, + + is: function( selector ) { + return !!selector && ( + typeof selector === "string" ? + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test( selector ) ? + jQuery( selector, this.context ).index( this[0] ) >= 0 : + jQuery.filter( selector, this ).length > 0 : + this.filter( selector ).length > 0 ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + ret = [], + pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? + jQuery( selectors, context || this.context ) : + 0; + + for ( ; i < l; i++ ) { + cur = this[i]; + + while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) { + if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { + ret.push( cur ); + break; + } + cur = cur.parentNode; + } + } + + return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1; + } + + // index in selector + if ( typeof elem === "string" ) { + return jQuery.inArray( this[0], jQuery( elem ) ); + } + + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context ) : + jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( jQuery.unique(all) ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter(selector) + ); + } +}); + +jQuery.fn.andSelf = jQuery.fn.addBack; + +function sibling( cur, dir ) { + do { + cur = cur[ dir ]; + } while ( cur && cur.nodeType !== 1 ); + + return cur; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; + + if ( this.length > 1 && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return elems.length === 1 ? + jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : + jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], + cur = elem[ dir ]; + + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, keep ) { + + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + var retVal = !!qualifier.call( elem, i, elem ); + return retVal === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem ) { + return ( elem === qualifier ) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem ) { + return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep; + }); +} +function createSafeFragment( document ) { + var list = nodeNames.split( "|" ), + safeFrag = document.createDocumentFragment(); + + if ( safeFrag.createElement ) { + while ( list.length ) { + safeFrag.createElement( + list.pop() + ); + } + } + return safeFrag; +} + +var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" + + "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video", + rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g, + rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"), + rleadingWhitespace = /^\s+/, + rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rtbody = /\s*$/g, + + // We have to close these tags to support XHTML (#13200) + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + area: [ 1, "", "" ], + param: [ 1, "", "" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + col: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + // IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags, + // unless wrapped in a div with non-breaking characters in front of it. + _default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X
", "
" ] + }, + safeFragment = createSafeFragment( document ), + fragmentDiv = safeFragment.appendChild( document.createElement("div") ); + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +jQuery.fn.extend({ + text: function( value ) { + return jQuery.access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) ); + }, null, value, arguments.length ); + }, + + wrapAll: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapAll( html.call(this, i) ); + }); + } + + if ( this[0] ) { + // The elements to wrap the target around + var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); + + if ( this[0].parentNode ) { + wrap.insertBefore( this[0] ); + } + + wrap.map(function() { + var elem = this; + + while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { + elem = elem.firstChild; + } + + return elem; + }).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( jQuery.isFunction( html ) ) { + return this.each(function(i) { + jQuery(this).wrapInner( html.call(this, i) ); + }); + } + + return this.each(function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + }); + }, + + wrap: function( html ) { + var isFunction = jQuery.isFunction( html ); + + return this.each(function(i) { + jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html ); + }); + }, + + unwrap: function() { + return this.parent().each(function() { + if ( !jQuery.nodeName( this, "body" ) ) { + jQuery( this ).replaceWith( this.childNodes ); + } + }).end(); + }, + + append: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.appendChild( elem ); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.insertBefore( elem, this.firstChild ); + } + }); + }, + + before: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + }); + }, + + after: function() { + return this.domManip( arguments, false, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function( selector, keepData ) { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + if ( !selector || jQuery.filter( selector, [ elem ] ).length > 0 ) { + if ( !keepData && elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem ) ); + } + + if ( elem.parentNode ) { + if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { + setGlobalEval( getAll( elem, "script" ) ); + } + elem.parentNode.removeChild( elem ); + } + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0; + + for ( ; (elem = this[i]) != null; i++ ) { + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + } + + // Remove any remaining nodes + while ( elem.firstChild ) { + elem.removeChild( elem.firstChild ); + } + + // If this is a select, ensure that it displays empty (#12336) + // Support: IE<9 + if ( elem.options && jQuery.nodeName( elem, "select" ) ) { + elem.options.length = 0; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function () { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + }); + }, + + html: function( value ) { + return jQuery.access( this, function( value ) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if ( value === undefined ) { + return elem.nodeType === 1 ? + elem.innerHTML.replace( rinlinejQuery, "" ) : + undefined; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) && + ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) && + !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) { + + value = value.replace( rxhtmlTag, "<$1>" ); + + try { + for (; i < l; i++ ) { + // Remove element nodes and prevent memory leaks + elem = this[i] || {}; + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch(e) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function( value ) { + var isFunc = jQuery.isFunction( value ); + + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if ( !isFunc && typeof value !== "string" ) { + value = jQuery( value ).not( this ).detach(); + } + + return this.domManip( [ value ], true, function( elem ) { + var next = this.nextSibling, + parent = this.parentNode; + + if ( parent && this.nodeType === 1 || this.nodeType === 11 ) { + + jQuery( this ).remove(); + + if ( next ) { + next.parentNode.insertBefore( elem, next ); + } else { + parent.appendChild( elem ); + } + } + }); + }, + + detach: function( selector ) { + return this.remove( selector, true ); + }, + + domManip: function( args, table, callback ) { + + // Flatten any nested arrays + args = core_concat.apply( [], args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) { + return this.each(function( index ) { + var self = set.eq( index ); + if ( isFunction ) { + args[0] = value.call( this, index, table ? self.html() : undefined ); + } + self.domManip( args, table, callback ); + }); + } + + if ( l ) { + fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + if ( first ) { + table = table && jQuery.nodeName( first, "tr" ); + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( + table && jQuery.nodeName( this[i], "table" ) ? + findOrAppend( this[i], "tbody" ) : + this[i], + node, + i + ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) { + + if ( node.src ) { + // Hope ajax is available... + jQuery.ajax({ + url: node.src, + type: "GET", + dataType: "script", + async: false, + global: false, + "throws": true + }); + } else { + jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) ); + } + } + } + } + + // Fix #11809: Avoid leaking memory + fragment = first = null; + } + } + + return this; + } +}); + +function findOrAppend( elem, tag ) { + return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) ); +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + var attr = elem.getAttributeNode("type"); + elem.type = ( attr && attr.specified ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + var match = rscriptTypeMasked.exec( elem.type ); + if ( match ) { + elem.type = match[1]; + } else { + elem.removeAttribute("type"); + } + return elem; +} + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var elem, + i = 0; + for ( ; (elem = elems[i]) != null; i++ ) { + jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) ); + } +} + +function cloneCopyEvent( src, dest ) { + + if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { + return; + } + + var type, i, l, + oldData = jQuery._data( src ), + curData = jQuery._data( dest, oldData ), + events = oldData.events; + + if ( events ) { + delete curData.handle; + curData.events = {}; + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + + // make the cloned public data object a copy from the original + if ( curData.data ) { + curData.data = jQuery.extend( {}, curData.data ); + } +} + +function fixCloneNodeIssues( src, dest ) { + var nodeName, data, e; + + // We do not need to do anything for non-Elements + if ( dest.nodeType !== 1 ) { + return; + } + + nodeName = dest.nodeName.toLowerCase(); + + // IE6-8 copies events bound via attachEvent when using cloneNode. + if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) { + data = jQuery._data( dest ); + + for ( e in data.events ) { + jQuery.removeEvent( dest, e, data.handle ); + } + + // Event data gets referenced instead of copied if the expando gets copied too + dest.removeAttribute( jQuery.expando ); + } + + // IE blanks contents when cloning scripts, and tries to evaluate newly-set text + if ( nodeName === "script" && dest.text !== src.text ) { + disableScript( dest ).text = src.text; + restoreScript( dest ); + + // IE6-10 improperly clones children of object elements using classid. + // IE10 throws NoModificationAllowedError if parent is null, #12132. + } else if ( nodeName === "object" ) { + if ( dest.parentNode ) { + dest.outerHTML = src.outerHTML; + } + + // This path appears unavoidable for IE9. When cloning an object + // element in IE9, the outerHTML strategy above is not sufficient. + // If the src has innerHTML and the destination does not, + // copy the src.innerHTML into the dest.innerHTML. #10324 + if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) { + dest.innerHTML = src.innerHTML; + } + + } else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) { + // IE6-8 fails to persist the checked state of a cloned checkbox + // or radio button. Worse, IE6-7 fail to give the cloned element + // a checked appearance if the defaultChecked value isn't also set + + dest.defaultChecked = dest.checked = src.checked; + + // IE6-7 get confused and end up setting the value of a cloned + // checkbox/radio button to an empty string instead of "on" + if ( dest.value !== src.value ) { + dest.value = src.value; + } + + // IE6-8 fails to return the selected option to the default selected + // state when cloning options + } else if ( nodeName === "option" ) { + dest.defaultSelected = dest.selected = src.defaultSelected; + + // IE6-8 fails to set the defaultValue to the correct value when + // cloning other types of input fields + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + i = 0, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone(true); + jQuery( insert[i] )[ original ]( elems ); + + // Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get() + core_push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +}); + +function getAll( context, tag ) { + var elems, elem, + i = 0, + found = typeof context.getElementsByTagName !== "undefined" ? context.getElementsByTagName( tag || "*" ) : + typeof context.querySelectorAll !== "undefined" ? context.querySelectorAll( tag || "*" ) : + undefined; + + if ( !found ) { + for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) { + if ( !tag || jQuery.nodeName( elem, tag ) ) { + found.push( elem ); + } else { + jQuery.merge( found, getAll( elem, tag ) ); + } + } + } + + return tag === undefined || tag && jQuery.nodeName( context, tag ) ? + jQuery.merge( [ context ], found ) : + found; +} + +// Used in buildFragment, fixes the defaultChecked property +function fixDefaultChecked( elem ) { + if ( manipulation_rcheckableType.test( elem.type ) ) { + elem.defaultChecked = elem.checked; + } +} + +jQuery.extend({ + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var destElements, srcElements, node, i, clone, + inPage = jQuery.contains( elem.ownerDocument, elem ); + + if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) { + clone = elem.cloneNode( true ); + + // IE<=8 does not properly clone detached, unknown element nodes + } else { + fragmentDiv.innerHTML = elem.outerHTML; + fragmentDiv.removeChild( clone = fragmentDiv.firstChild ); + } + + if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) && + (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { + + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + // Fix all IE cloning issues + for ( i = 0; (node = srcElements[i]) != null; ++i ) { + // Ensure that the destination node is not null; Fixes #9587 + if ( destElements[i] ) { + fixCloneNodeIssues( node, destElements[i] ); + } + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0; (node = srcElements[i]) != null; i++ ) { + cloneCopyEvent( node, destElements[i] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + destElements = srcElements = node = null; + + // Return the cloned set + return clone; + }, + + buildFragment: function( elems, context, scripts, selection ) { + var contains, elem, tag, tmp, wrap, tbody, j, + l = elems.length, + + // Ensure a safe fragment + safe = createSafeFragment( context ), + + nodes = [], + i = 0; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( jQuery.type( elem ) === "object" ) { + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || safe.appendChild( context.createElement("div") ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + + tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Manually add leading whitespace removed by IE + if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { + nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) ); + } + + // Remove IE's autoinserted from table fragments + if ( !jQuery.support.tbody ) { + + // String was a , *may* have spurious + elem = tag === "table" && !rtbody.test( elem ) ? + tmp.firstChild : + + // String was a bare or + wrap[1] === "
" && !rtbody.test( elem ) ? + tmp : + 0; + + j = elem && elem.childNodes.length; + while ( j-- ) { + if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) { + elem.removeChild( tbody ); + } + } + } + + jQuery.merge( nodes, tmp.childNodes ); + + // Fix #12392 for WebKit and IE > 9 + tmp.textContent = ""; + + // Fix #12392 for oldIE + while ( tmp.firstChild ) { + tmp.removeChild( tmp.firstChild ); + } + + // Remember the top-level container for proper cleanup + tmp = safe.lastChild; + } + } + } + + // Fix #11356: Clear elements from fragment + if ( tmp ) { + safe.removeChild( tmp ); + } + + // Reset defaultChecked for any radios and checkboxes + // about to be appended to the DOM in IE 6/7 (#8060) + if ( !jQuery.support.appendChecked ) { + jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked ); + } + + i = 0; + while ( (elem = nodes[ i++ ]) ) { + + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { + continue; + } + + contains = jQuery.contains( elem.ownerDocument, elem ); + + // Append to fragment + tmp = getAll( safe.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( contains ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( (elem = tmp[ j++ ]) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + tmp = null; + + return safe; + }, + + cleanData: function( elems, /* internal */ acceptData ) { + var data, id, elem, type, + i = 0, + internalKey = jQuery.expando, + cache = jQuery.cache, + deleteExpando = jQuery.support.deleteExpando, + special = jQuery.event.special; + + for ( ; (elem = elems[i]) != null; i++ ) { + + if ( acceptData || jQuery.acceptData( elem ) ) { + + id = elem[ internalKey ]; + data = id && cache[ id ]; + + if ( data ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Remove cache only if it was not already removed by jQuery.event.remove + if ( cache[ id ] ) { + + delete cache[ id ]; + + // IE does not allow us to delete expando properties from nodes, + // nor does it have a removeAttribute function on Document nodes; + // we must handle all of these cases + if ( deleteExpando ) { + delete elem[ internalKey ]; + + } else if ( typeof elem.removeAttribute !== "undefined" ) { + elem.removeAttribute( internalKey ); + + } else { + elem[ internalKey ] = null; + } + + core_deletedIds.push( id ); + } + } + } + } + } +}); +var curCSS, getStyles, iframe, + ralpha = /alpha\([^)]*\)/i, + ropacity = /opacity\s*=\s*([^)]*)/, + rposition = /^(top|right|bottom|left)$/, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ), + rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ), + rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ), + elemdisplay = { BODY: "block" }, + + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400 + }, + + cssExpand = [ "Top", "Right", "Bottom", "Left" ], + cssPrefixes = [ "Webkit", "O", "Moz", "ms" ]; + +// return a css property mapped to a potentially vendor prefixed property +function vendorPropName( style, name ) { + + // shortcut for names that are not vendor prefixed + if ( name in style ) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in style ) { + return name; + } + } + + return origName; +} + +function isHidden( elem, el ) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); +} + +function showHide( elements, show ) { + var elem, + values = [], + index = 0, + length = elements.length; + + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + values[ index ] = jQuery._data( elem, "olddisplay" ); + if ( show ) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if ( !values[ index ] && elem.style.display === "none" ) { + elem.style.display = ""; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if ( elem.style.display === "" && isHidden( elem ) ) { + values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) ); + } + } else if ( !values[ index ] && !isHidden( elem ) ) { + jQuery._data( elem, "olddisplay", jQuery.css( elem, "display" ) ); + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for ( index = 0; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + if ( !show || elem.style.display === "none" || elem.style.display === "" ) { + elem.style.display = show ? values[ index ] || "" : "none"; + } + } + + return elements; +} + +jQuery.fn.extend({ + css: function( name, value ) { + return jQuery.access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( jQuery.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + }, + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + var bool = typeof state === "boolean"; + + return this.each(function() { + if ( bool ? state : isHidden( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + }); + } +}); + +jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Exclude the following css properties to add px + cssNumber: { + "columnCount": true, + "fillOpacity": true, + "fontWeight": true, + "lineHeight": true, + "opacity": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" + }, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = jQuery.camelCase( name ), + style = elem.style; + + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if ( type === "string" && (ret = rrelNum.exec( value )) ) { + value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) ); + // Fixes bug #9237 + type = "number"; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if ( value == null || type === "number" && isNaN( value ) ) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if ( type === "number" && !jQuery.cssNumber[ origName ] ) { + value += "px"; + } + + // Fixes #8908, it can be done more correctly by specifing setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) { + + // Wrapped to prevent IE from throwing errors when 'invalid' values are provided + // Fixes bug #5509 + try { + style[ name ] = value; + } catch(e) {} + } + + } else { + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = jQuery.camelCase( name ); + + // Make sure that we're working with the right name + name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) ); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + //convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if ( extra ) { + num = parseFloat( val ); + return extra === true || jQuery.isNumeric( num ) ? num || 0 : val; + } + return val; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function( elem, options, callback, args ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.apply( elem, args || [] ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; + } +}); + +// NOTE: we've included the "window" in window.getComputedStyle +// because jsdom on node.js will break without it. +if ( window.getComputedStyle ) { + getStyles = function( elem ) { + return window.getComputedStyle( elem, null ); + }; + + curCSS = function( elem, name, _computed ) { + var width, minWidth, maxWidth, + computed = _computed || getStyles( elem ), + + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined, + style = elem.style; + + if ( computed ) { + + if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; +} else if ( document.documentElement.currentStyle ) { + getStyles = function( elem ) { + return elem.currentStyle; + }; + + curCSS = function( elem, name, _computed ) { + var left, rs, rsLeft, + computed = _computed || getStyles( elem ), + ret = computed ? computed[ name ] : undefined, + style = elem.style; + + // Avoid setting ret to empty string here + // so we don't default to auto + if ( ret == null && style && style[ name ] ) { + ret = style[ name ]; + } + + // From the awesome hack by Dean Edwards + // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + + // If we're not dealing with a regular pixel number + // but a number that has a weird ending, we need to convert it to pixels + // but not position css attributes, as those are proportional to the parent element instead + // and we can't measure the parent instead because it might trigger a "stacking dolls" problem + if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) { + + // Remember the original values + left = style.left; + rs = elem.runtimeStyle; + rsLeft = rs && rs.left; + + // Put in the new values to get a computed value out + if ( rsLeft ) { + rs.left = elem.currentStyle.left; + } + style.left = name === "fontSize" ? "1em" : ret; + ret = style.pixelLeft + "px"; + + // Revert the changed values + style.left = left; + if ( rsLeft ) { + rs.left = rsLeft; + } + } + + return ret === "" ? "auto" : ret; + }; +} + +function setPositiveNumber( elem, value, subtract ) { + var matches = rnumsplit.exec( value ); + return matches ? + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) : + value; +} + +function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) { + var i = extra === ( isBorderBox ? "border" : "content" ) ? + // If we already have the right measurement, avoid augmentation + 4 : + // Otherwise initialize for horizontal or vertical properties + name === "width" ? 1 : 0, + + val = 0; + + for ( ; i < 4; i += 2 ) { + // both box models exclude margin, so add it if we want it + if ( extra === "margin" ) { + val += jQuery.css( elem, extra + cssExpand[ i ], true, styles ); + } + + if ( isBorderBox ) { + // border-box includes padding, so remove it if we want content + if ( extra === "content" ) { + val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // at this point, extra isn't border nor margin, so remove border + if ( extra !== "margin" ) { + val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // at this point, extra isn't content nor padding, so add border + if ( extra !== "padding" ) { + val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + return val; +} + +function getWidthOrHeight( elem, name, extra ) { + + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === "width" ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles( elem ), + isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if ( val <= 0 || val == null ) { + // Fall back to computed then uncomputed css if necessary + val = curCSS( elem, name, styles ); + if ( val < 0 || val == null ) { + val = elem.style[ name ]; + } + + // Computed unit is not pixels. Stop here and return. + if ( rnumnonpx.test(val) ) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] ); + + // Normalize "", auto, and prepare for extra + val = parseFloat( val ) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( val + + augmentWidthOrHeight( + elem, + name, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles + ) + ) + "px"; +} + +// Try to determine the default display value of an element +function css_defaultDisplay( nodeName ) { + var doc = document, + display = elemdisplay[ nodeName ]; + + if ( !display ) { + display = actualDisplay( nodeName, doc ); + + // If the simple way fails, read from inside an iframe + if ( display === "none" || !display ) { + // Use the already-created iframe if possible + iframe = ( iframe || + jQuery("